From d6c1343b5c5e3f935d9835c29517a3853d280cf9 Mon Sep 17 00:00:00 2001 From: 0xRaccoon Date: Wed, 20 Dec 2023 12:58:39 -0300 Subject: [PATCH 01/16] feat: add wonderGovernor and wonderVotes contract examples Signed-off-by: 0xRaccoon --- .gitignore | 1 + .../contracts/governance/WonderGovernor.sol | 4 + .../governance/utils/WonderVotes.sol | 6 +- solidity/examples/AliceGovernor.sol | 134 ++++++++++++++++++ solidity/examples/RabbitToken.sol | 35 +++++ .../governance/utils/IWonderVotes.sol | 1 + 6 files changed, 180 insertions(+), 1 deletion(-) create mode 100644 solidity/examples/AliceGovernor.sol create mode 100644 solidity/examples/RabbitToken.sol diff --git a/.gitignore b/.gitignore index 9be8a61..96330bd 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ out-via-ir # Config files .env +.vscode # Avoid ignoring gitkeep !/**/.gitkeep diff --git a/solidity/contracts/governance/WonderGovernor.sol b/solidity/contracts/governance/WonderGovernor.sol index 67d2f87..bdc84ca 100644 --- a/solidity/contracts/governance/WonderGovernor.sol +++ b/solidity/contracts/governance/WonderGovernor.sol @@ -889,4 +889,8 @@ abstract contract WonderGovernor is function proposalTypes() public view virtual override returns (uint8[] memory) { return _proposalTypes(); } + + function _getProposal(uint256 _proposalId) internal view virtual returns (ProposalCore storage) { + return _proposals[_proposalId]; + } } diff --git a/solidity/contracts/governance/utils/WonderVotes.sol b/solidity/contracts/governance/utils/WonderVotes.sol index f2903f2..1b3db47 100644 --- a/solidity/contracts/governance/utils/WonderVotes.sol +++ b/solidity/contracts/governance/utils/WonderVotes.sol @@ -252,7 +252,11 @@ abstract contract WonderVotes is Context, EIP712, Nonces, IERC6372, IWonderVotes if (_weightSum != _weightNormalizer()) revert InvalidWeightSum(_weightSum); Delegate[] memory _oldDelegates = delegates(account, proposalType); - _delegatees[account][proposalType] = delegatees; + + delete _delegatees[account][proposalType]; + for (uint256 i = 0; i < delegatees.length; i++) { + _delegatees[account][proposalType].push(delegatees[i]); + } emit DelegateChanged(account, proposalType, _oldDelegates, delegatees); _moveDelegateVotes(proposalType, _oldDelegates, delegatees, _getVotingUnits(account)); diff --git a/solidity/examples/AliceGovernor.sol b/solidity/examples/AliceGovernor.sol new file mode 100644 index 0000000..75b2930 --- /dev/null +++ b/solidity/examples/AliceGovernor.sol @@ -0,0 +1,134 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import 'contracts/governance/WonderGovernor.sol'; +import 'contracts/governance/utils/WonderVotes.sol'; + +contract AliceGovernor is WonderGovernor { + WonderVotes public votes; + string internal _countingMode = 'support=bravo&quorum=bravo'; + uint8[] internal __proposalTypes = [1, 2, 3]; + + mapping(uint256 proposalId => mapping(address => Receipt)) public receipts; + mapping(uint256 proposalId => ProposalTrack) public proposalTracks; + + /// @notice Ballot receipt record for a voter + struct Receipt { + /// @notice Whether or not a vote has been cast + bool hasVoted; + /// @notice 0 = Against, 1 = For, 2 = Abstain + uint8 support; + /// @notice The number of votes the voter had, which were cast + uint256 votes; + } + + struct ProposalTrack { + uint256 proposalId; + uint256 votes; + uint256 forVotes; + uint256 againstVotes; + uint256 abstainVotes; + } + + constructor(address _wonderToken) WonderGovernor('AliceGovernor') { + votes = WonderVotes(_wonderToken); + } + + function CLOCK_MODE() public view override returns (string memory _clockMode) { + return votes.CLOCK_MODE(); + } + + function COUNTING_MODE() external view override returns (string memory) { + return _countingMode; + } + + function _getVotes( + address _account, + uint8 _proposalType, + uint256 _timepoint, + bytes memory _params + ) internal view virtual override returns (uint256) { + return votes.getPastVotes(_account, _proposalType, _timepoint); + } + + function clock() public view override returns (uint48) { + return votes.clock(); + } + + function votingPeriod() public view override returns (uint256) { + // ~3 days in blocks (assuming 15s blocks) + return 17_280; + } + + function votingDelay() public view override returns (uint256) { + // 1 block + return 1; + } + + function quorum(uint256 _timepoint, uint8 _proposalType) public view override returns (uint256) { + // same quorum for all proposals types and timepoints + return 400_000e18; + } + + function _proposalTypes() internal view override returns (uint8[] memory) { + return __proposalTypes; + } + + function _isValidProposalType(uint8 _proposalType) internal view virtual override returns (bool) { + return _proposalType < __proposalTypes.length; + } + + function _countVote( + uint256 _proposalId, + address _account, + uint8 _support, + uint256 _weight, + bytes memory _params + ) internal virtual override { + if (_support == 0) { + proposalTracks[_proposalId].againstVotes += _weight; + } else if (_support == 1) { + proposalTracks[_proposalId].forVotes += _weight; + } else if (_support == 2) { + proposalTracks[_proposalId].abstainVotes += _weight; + } else { + revert InvalidVoteType(_support); + } + + Receipt storage receipt = receipts[_proposalId][_account]; + + receipt.hasVoted = true; + receipt.support = _support; + receipt.votes = _weight; + } + + function hasVoted(uint256 _proposalId, address _account) external view override returns (bool) { + return receipts[_proposalId][_account].hasVoted; + } + + function _quorumReached(uint256 _proposalId) internal view virtual override returns (bool) { + ProposalTrack memory _proposalTrack = proposalTracks[_proposalId]; + ProposalCore memory _proposal = _getProposal(_proposalId); + + uint256 _totalVotes = _proposalTrack.forVotes + _proposalTrack.againstVotes + _proposalTrack.abstainVotes; + return _totalVotes >= quorum(_proposal.voteStart, _proposal.proposalType); + } + + function _voteSucceeded(uint256 _proposalId) internal view virtual override returns (bool) { + ProposalTrack memory _proposalTrack = proposalTracks[_proposalId]; + + bool _succeded = _quorumReached(_proposalId) && _proposalTrack.forVotes > _proposalTrack.againstVotes; + return _succeded; + } + + function proposalThreshold(uint8 _proposalType) public view override returns (uint256) { + // same threshold for all proposals types + return 100_000e18; + } + + function isValidProposalType(uint8 _proposalType) external view returns (bool) { + return _isValidProposalType(_proposalType); + } + + error InvalidVoteType(uint8 voteType); +} diff --git a/solidity/examples/RabbitToken.sol b/solidity/examples/RabbitToken.sol new file mode 100644 index 0000000..f0112a8 --- /dev/null +++ b/solidity/examples/RabbitToken.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {WonderERC20Votes} from 'contracts/token/ERC20/extensions/WonderERC20Votes.sol'; +import {AliceGovernor} from './AliceGovernor.sol'; +import {EIP712} from '@openzeppelin/contracts/utils/cryptography/EIP712.sol'; +import {ERC20} from '@openzeppelin/contracts/token/ERC20/ERC20.sol'; + +contract RabbitToken is WonderERC20Votes { + AliceGovernor public governor; + + constructor(AliceGovernor _governor) EIP712('RabbitToken', '1') ERC20('RabbitToken', 'RBT') { + governor = _governor; + } + + function _getProposalTypes() internal view virtual override returns (uint8[] memory) { + return governor.proposalTypes(); + } + + function _maxDelegates() internal view virtual override returns (uint8) { + return 4; + } + + function _validProposalType(uint8 _proposalType) internal view virtual override returns (bool) { + return governor.isValidProposalType(_proposalType); + } + + function _weightNormalizer() internal view virtual override returns (uint256) { + return 100; + } + + function proposalTypes() external view returns (uint8[] memory) { + return governor.proposalTypes(); + } +} diff --git a/solidity/interfaces/governance/utils/IWonderVotes.sol b/solidity/interfaces/governance/utils/IWonderVotes.sol index c595243..db510b8 100644 --- a/solidity/interfaces/governance/utils/IWonderVotes.sol +++ b/solidity/interfaces/governance/utils/IWonderVotes.sol @@ -112,6 +112,7 @@ interface IWonderVotes { */ function delegateBySig( Delegate[] memory delegates, + uint8 proposalType, uint256 nonce, uint256 expiry, uint8 v, From b4410f12073f7c2065a86d0809d03c954e12c6e9 Mon Sep 17 00:00:00 2001 From: 0xRaccoon Date: Wed, 20 Dec 2023 13:05:10 -0300 Subject: [PATCH 02/16] fix: return memory proposal instead of storage Signed-off-by: 0xRaccoon --- solidity/contracts/governance/WonderGovernor.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/solidity/contracts/governance/WonderGovernor.sol b/solidity/contracts/governance/WonderGovernor.sol index bdc84ca..f564ded 100644 --- a/solidity/contracts/governance/WonderGovernor.sol +++ b/solidity/contracts/governance/WonderGovernor.sol @@ -890,7 +890,7 @@ abstract contract WonderGovernor is return _proposalTypes(); } - function _getProposal(uint256 _proposalId) internal view virtual returns (ProposalCore storage) { + function _getProposal(uint256 _proposalId) internal view virtual returns (ProposalCore memory) { return _proposals[_proposalId]; } } From d7300bc7078446486efef5047adf11d950e03b37 Mon Sep 17 00:00:00 2001 From: 0xRaccoon Date: Thu, 21 Dec 2023 09:31:41 -0300 Subject: [PATCH 03/16] test: add Governor unit tests Signed-off-by: 0xRaccoon --- package.json | 3 +- remappings.txt | 1 + solidity/examples/AliceGovernor.sol | 12 +- solidity/test/smock/SmockHelper.sol | 20 ++ .../test/smock/examples/MockAliceGovernor.sol | 271 ++++++++++++++++++ .../test/smock/examples/MockRabbitToken.sol | 147 ++++++++++ solidity/test/unit/WonderGovernor.t.sol | 94 ++++++ yarn.lock | 46 ++- 8 files changed, 584 insertions(+), 10 deletions(-) create mode 100644 solidity/test/smock/SmockHelper.sol create mode 100644 solidity/test/smock/examples/MockAliceGovernor.sol create mode 100644 solidity/test/smock/examples/MockRabbitToken.sol create mode 100644 solidity/test/unit/WonderGovernor.t.sol diff --git a/package.json b/package.json index ffa408b..01ed179 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,8 @@ "package.json": "sort-package-json" }, "dependencies": { - "@openzeppelin/contracts": "^5.0.1", + "@defi-wonderland/smock-foundry": "1.0.6", + "@openzeppelin/contracts": "5.0.1", "isolmate": "github:defi-wonderland/isolmate#59e1804" }, "devDependencies": { diff --git a/remappings.txt b/remappings.txt index 83b675a..db861fd 100644 --- a/remappings.txt +++ b/remappings.txt @@ -4,4 +4,5 @@ isolmate/=node_modules/isolmate/src contracts/=solidity/contracts interfaces/=solidity/interfaces +examples/=solidity/examples test/=solidity/test \ No newline at end of file diff --git a/solidity/examples/AliceGovernor.sol b/solidity/examples/AliceGovernor.sol index 75b2930..74eee89 100644 --- a/solidity/examples/AliceGovernor.sol +++ b/solidity/examples/AliceGovernor.sol @@ -9,11 +9,11 @@ contract AliceGovernor is WonderGovernor { string internal _countingMode = 'support=bravo&quorum=bravo'; uint8[] internal __proposalTypes = [1, 2, 3]; - mapping(uint256 proposalId => mapping(address => Receipt)) public receipts; + mapping(uint256 proposalId => mapping(address => BallotReceipt)) public receipts; mapping(uint256 proposalId => ProposalTrack) public proposalTracks; /// @notice Ballot receipt record for a voter - struct Receipt { + struct BallotReceipt { /// @notice Whether or not a vote has been cast bool hasVoted; /// @notice 0 = Against, 1 = For, 2 = Abstain @@ -95,11 +95,11 @@ contract AliceGovernor is WonderGovernor { revert InvalidVoteType(_support); } - Receipt storage receipt = receipts[_proposalId][_account]; + BallotReceipt storage _receipt = receipts[_proposalId][_account]; - receipt.hasVoted = true; - receipt.support = _support; - receipt.votes = _weight; + _receipt.hasVoted = true; + _receipt.support = _support; + _receipt.votes = _weight; } function hasVoted(uint256 _proposalId, address _account) external view override returns (bool) { diff --git a/solidity/test/smock/SmockHelper.sol b/solidity/test/smock/SmockHelper.sol new file mode 100644 index 0000000..442961e --- /dev/null +++ b/solidity/test/smock/SmockHelper.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {Test} from 'forge-std/Test.sol'; + +contract SmockHelper is Test { + function deployMock( + string memory _label, + bytes memory _creationCode, + bytes memory _encodedArgs + ) internal returns (address _deployed) { + bytes memory _bytecode = abi.encodePacked(_creationCode, _encodedArgs); + assembly { + mstore(0x0, _creationCode) + _deployed := create2(0, add(_bytecode, 0x20), mload(_bytecode), 'Wonderland') + } + vm.label(_deployed, _label); + vm.allowCheatcodes(_deployed); + } +} diff --git a/solidity/test/smock/examples/MockAliceGovernor.sol b/solidity/test/smock/examples/MockAliceGovernor.sol new file mode 100644 index 0000000..5b2623d --- /dev/null +++ b/solidity/test/smock/examples/MockAliceGovernor.sol @@ -0,0 +1,271 @@ +/// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +import {Test} from 'forge-std/Test.sol'; +import { + Address, + AliceGovernor, + Checkpoints, + Context, + DoubleEndedQueue, + ECDSA, + EIP712, + ERC165, + IERC1155Receiver, + IERC165, + IERC6372, + IERC721Receiver, + IWonderGovernor, + IWonderVotes, + Nonces, + SafeCast, + SignatureChecker, + Time, + WonderGovernor, + WonderVotes +} from 'solidity/examples/AliceGovernor.sol'; +import 'solidity/contracts/governance/WonderGovernor.sol'; +import 'solidity/contracts/governance/utils/WonderVotes.sol'; + +contract MockAliceGovernor is AliceGovernor, Test { + constructor(address _wonderToken) AliceGovernor(_wonderToken) {} + + /// Mocked State Variables + + function set_votes(WonderVotes _votes) public { + votes = _votes; + } + + function mock_call_votes(WonderVotes _votes) public { + vm.mockCall(address(this), abi.encodeWithSignature('votes()'), abi.encode(_votes)); + } + + function set__countingMode(string memory __countingMode) public { + _countingMode = __countingMode; + } + + function set___proposalTypes(uint8[] memory ___proposalTypes) public { + __proposalTypes = ___proposalTypes; + } + + function set_receipts(uint256 _key0, address _key1, AliceGovernor.BallotReceipt memory _value) public { + receipts[_key0][_key1] = _value; + } + + function mock_call_receipts(uint256 _key0, address _key1, AliceGovernor.BallotReceipt memory _value) public { + vm.mockCall(address(this), abi.encodeWithSignature('receipts(uint256,address)', _key0, _key1), abi.encode(_value)); + } + + function set_proposalTracks(uint256 _key0, AliceGovernor.ProposalTrack memory _value) public { + proposalTracks[_key0] = _value; + } + + function mock_call_proposalTracks(uint256 _key0, AliceGovernor.ProposalTrack memory _value) public { + vm.mockCall(address(this), abi.encodeWithSignature('proposalTracks(uint256)', _key0), abi.encode(_value)); + } + + /// Mocked External Functions + + function mock_call_CLOCK_MODE(string memory _clockMode) public { + vm.mockCall(address(this), abi.encodeWithSignature('CLOCK_MODE()'), abi.encode(_clockMode)); + } + + function mock_call_COUNTING_MODE(string memory _return0) public { + vm.mockCall(address(this), abi.encodeWithSignature('COUNTING_MODE()'), abi.encode(_return0)); + } + + function mock_call_clock(uint48 _return0) public { + vm.mockCall(address(this), abi.encodeWithSignature('clock()'), abi.encode(_return0)); + } + + function mock_call_votingPeriod(uint256 _return0) public { + vm.mockCall(address(this), abi.encodeWithSignature('votingPeriod()'), abi.encode(_return0)); + } + + function mock_call_votingDelay(uint256 _return0) public { + vm.mockCall(address(this), abi.encodeWithSignature('votingDelay()'), abi.encode(_return0)); + } + + function mock_call_quorum(uint256 _timepoint, uint8 _proposalType, uint256 _return0) public { + vm.mockCall( + address(this), abi.encodeWithSignature('quorum(uint256,uint8)', _timepoint, _proposalType), abi.encode(_return0) + ); + } + + function mock_call_hasVoted(uint256 _proposalId, address _account, bool _return0) public { + vm.mockCall( + address(this), abi.encodeWithSignature('hasVoted(uint256,address)', _proposalId, _account), abi.encode(_return0) + ); + } + + function mock_call_proposalThreshold(uint8 _proposalType, uint256 _return0) public { + vm.mockCall(address(this), abi.encodeWithSignature('proposalThreshold(uint8)', _proposalType), abi.encode(_return0)); + } + + function mock_call_isValidProposalType(uint8 _proposalType, bool _return0) public { + vm.mockCall( + address(this), abi.encodeWithSignature('isValidProposalType(uint8)', _proposalType), abi.encode(_return0) + ); + } + + /// Mocked Internal Functions + + struct _getVotesOutput { + uint256 _returnParam0; + } + + mapping(bytes32 => _getVotesOutput) private _getVotesOutputs; + bytes32[] private _getVotesInputHashes; + + function mock_call__getVotes( + address _account, + uint8 _proposalType, + uint256 _timepoint, + bytes memory _params, + uint256 _returnParam0 + ) public { + bytes32 _key = keccak256(abi.encode(_account, _proposalType, _timepoint, _params)); + _getVotesOutputs[_key] = _getVotesOutput(_returnParam0); + for (uint256 _i; _i < _getVotesInputHashes.length; ++_i) { + if (_key == _getVotesInputHashes[_i]) { + return; + } + } + _getVotesInputHashes.push(_key); + } + + function _getVotes( + address _account, + uint8 _proposalType, + uint256 _timepoint, + bytes memory _params + ) internal view override returns (uint256 _returnParam0) { + bytes32 _key = keccak256(abi.encode(_account, _proposalType, _timepoint, _params)); + for (uint256 _i; _i < _getVotesInputHashes.length; ++_i) { + if (_key == _getVotesInputHashes[_i]) { + _getVotesOutput memory _output = _getVotesOutputs[_key]; + return (_output._returnParam0); + } + } + return super._getVotes(_account, _proposalType, _timepoint, _params); + } + + struct _isValidProposalTypeOutput { + bool _returnParam0; + } + + mapping(bytes32 => _isValidProposalTypeOutput) private _isValidProposalTypeOutputs; + bytes32[] private _isValidProposalTypeInputHashes; + + function mock_call__isValidProposalType(uint8 _proposalType, bool _returnParam0) public { + bytes32 _key = keccak256(abi.encode(_proposalType)); + _isValidProposalTypeOutputs[_key] = _isValidProposalTypeOutput(_returnParam0); + for (uint256 _i; _i < _isValidProposalTypeInputHashes.length; ++_i) { + if (_key == _isValidProposalTypeInputHashes[_i]) { + return; + } + } + _isValidProposalTypeInputHashes.push(_key); + } + + function _isValidProposalType(uint8 _proposalType) internal view override returns (bool _returnParam0) { + bytes32 _key = keccak256(abi.encode(_proposalType)); + for (uint256 _i; _i < _isValidProposalTypeInputHashes.length; ++_i) { + if (_key == _isValidProposalTypeInputHashes[_i]) { + _isValidProposalTypeOutput memory _output = _isValidProposalTypeOutputs[_key]; + return (_output._returnParam0); + } + } + return super._isValidProposalType(_proposalType); + } + + function mock_call__countVote( + uint256 _proposalId, + address _account, + uint8 _support, + uint256 _weight, + bytes memory _params + ) public { + vm.mockCall( + address(this), + abi.encodeWithSignature( + '_countVote(uint256,address,uint8,uint256,bytes)', _proposalId, _account, _support, _weight, _params + ), + abi.encode() + ); + } + + function _countVote( + uint256 _proposalId, + address _account, + uint8 _support, + uint256 _weight, + bytes memory _params + ) internal override { + (bool _success, bytes memory _data) = address(this).call( + abi.encodeWithSignature( + '_countVote(uint256,address,uint8,uint256,bytes)', _proposalId, _account, _support, _weight, _params + ) + ); + if (_success) return abi.decode(_data, ()); + else return super._countVote(_proposalId, _account, _support, _weight, _params); + } + + struct _quorumReachedOutput { + bool _returnParam0; + } + + mapping(bytes32 => _quorumReachedOutput) private _quorumReachedOutputs; + bytes32[] private _quorumReachedInputHashes; + + function mock_call__quorumReached(uint256 _proposalId, bool _returnParam0) public { + bytes32 _key = keccak256(abi.encode(_proposalId)); + _quorumReachedOutputs[_key] = _quorumReachedOutput(_returnParam0); + for (uint256 _i; _i < _quorumReachedInputHashes.length; ++_i) { + if (_key == _quorumReachedInputHashes[_i]) { + return; + } + } + _quorumReachedInputHashes.push(_key); + } + + function _quorumReached(uint256 _proposalId) internal view override returns (bool _returnParam0) { + bytes32 _key = keccak256(abi.encode(_proposalId)); + for (uint256 _i; _i < _quorumReachedInputHashes.length; ++_i) { + if (_key == _quorumReachedInputHashes[_i]) { + _quorumReachedOutput memory _output = _quorumReachedOutputs[_key]; + return (_output._returnParam0); + } + } + return super._quorumReached(_proposalId); + } + + struct _voteSucceededOutput { + bool _returnParam0; + } + + mapping(bytes32 => _voteSucceededOutput) private _voteSucceededOutputs; + bytes32[] private _voteSucceededInputHashes; + + function mock_call__voteSucceeded(uint256 _proposalId, bool _returnParam0) public { + bytes32 _key = keccak256(abi.encode(_proposalId)); + _voteSucceededOutputs[_key] = _voteSucceededOutput(_returnParam0); + for (uint256 _i; _i < _voteSucceededInputHashes.length; ++_i) { + if (_key == _voteSucceededInputHashes[_i]) { + return; + } + } + _voteSucceededInputHashes.push(_key); + } + + function _voteSucceeded(uint256 _proposalId) internal view override returns (bool _returnParam0) { + bytes32 _key = keccak256(abi.encode(_proposalId)); + for (uint256 _i; _i < _voteSucceededInputHashes.length; ++_i) { + if (_key == _voteSucceededInputHashes[_i]) { + _voteSucceededOutput memory _output = _voteSucceededOutputs[_key]; + return (_output._returnParam0); + } + } + return super._voteSucceeded(_proposalId); + } +} diff --git a/solidity/test/smock/examples/MockRabbitToken.sol b/solidity/test/smock/examples/MockRabbitToken.sol new file mode 100644 index 0000000..c1fa222 --- /dev/null +++ b/solidity/test/smock/examples/MockRabbitToken.sol @@ -0,0 +1,147 @@ +/// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.0; + +import {Test} from 'forge-std/Test.sol'; +import {AliceGovernor, EIP712, ERC20, RabbitToken, WonderERC20Votes} from 'solidity/examples/RabbitToken.sol'; +import {WonderERC20Votes} from 'solidity/contracts/token/ERC20/extensions/WonderERC20Votes.sol'; +import {AliceGovernor} from 'solidity/examples/AliceGovernor.sol'; +import {EIP712} from 'node_modules/@openzeppelin/contracts/utils/cryptography/EIP712.sol'; +import {ERC20} from 'node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol'; + +contract MockRabbitToken is RabbitToken, Test { + constructor(AliceGovernor _governor) RabbitToken(_governor) {} + + /// Mocked State Variables + + function set_governor(AliceGovernor _governor) public { + governor = _governor; + } + + function mock_call_governor(AliceGovernor _governor) public { + vm.mockCall(address(this), abi.encodeWithSignature('governor()'), abi.encode(_governor)); + } + + /// Mocked External Functions + + function mock_call_proposalTypes(uint8[] memory _return0) public { + vm.mockCall(address(this), abi.encodeWithSignature('proposalTypes()'), abi.encode(_return0)); + } + + /// Mocked Internal Functions + + struct _getProposalTypesOutput { + uint8[] _returnParam0; + } + + mapping(bytes32 => _getProposalTypesOutput) private _getProposalTypesOutputs; + bytes32[] private _getProposalTypesInputHashes; + + function mock_call__getProposalTypes(uint8[] memory _returnParam0) public { + bytes32 _key = keccak256(abi.encode()); + _getProposalTypesOutputs[_key] = _getProposalTypesOutput(_returnParam0); + for (uint256 _i; _i < _getProposalTypesInputHashes.length; ++_i) { + if (_key == _getProposalTypesInputHashes[_i]) { + return; + } + } + _getProposalTypesInputHashes.push(_key); + } + + function _getProposalTypes() internal view override returns (uint8[] memory _returnParam0) { + bytes32 _key = keccak256(abi.encode()); + for (uint256 _i; _i < _getProposalTypesInputHashes.length; ++_i) { + if (_key == _getProposalTypesInputHashes[_i]) { + _getProposalTypesOutput memory _output = _getProposalTypesOutputs[_key]; + return (_output._returnParam0); + } + } + return super._getProposalTypes(); + } + + struct _maxDelegatesOutput { + uint8 _returnParam0; + } + + mapping(bytes32 => _maxDelegatesOutput) private _maxDelegatesOutputs; + bytes32[] private _maxDelegatesInputHashes; + + function mock_call__maxDelegates(uint8 _returnParam0) public { + bytes32 _key = keccak256(abi.encode()); + _maxDelegatesOutputs[_key] = _maxDelegatesOutput(_returnParam0); + for (uint256 _i; _i < _maxDelegatesInputHashes.length; ++_i) { + if (_key == _maxDelegatesInputHashes[_i]) { + return; + } + } + _maxDelegatesInputHashes.push(_key); + } + + function _maxDelegates() internal view override returns (uint8 _returnParam0) { + bytes32 _key = keccak256(abi.encode()); + for (uint256 _i; _i < _maxDelegatesInputHashes.length; ++_i) { + if (_key == _maxDelegatesInputHashes[_i]) { + _maxDelegatesOutput memory _output = _maxDelegatesOutputs[_key]; + return (_output._returnParam0); + } + } + return super._maxDelegates(); + } + + struct _validProposalTypeOutput { + bool _returnParam0; + } + + mapping(bytes32 => _validProposalTypeOutput) private _validProposalTypeOutputs; + bytes32[] private _validProposalTypeInputHashes; + + function mock_call__validProposalType(uint8 _proposalType, bool _returnParam0) public { + bytes32 _key = keccak256(abi.encode(_proposalType)); + _validProposalTypeOutputs[_key] = _validProposalTypeOutput(_returnParam0); + for (uint256 _i; _i < _validProposalTypeInputHashes.length; ++_i) { + if (_key == _validProposalTypeInputHashes[_i]) { + return; + } + } + _validProposalTypeInputHashes.push(_key); + } + + function _validProposalType(uint8 _proposalType) internal view override returns (bool _returnParam0) { + bytes32 _key = keccak256(abi.encode(_proposalType)); + for (uint256 _i; _i < _validProposalTypeInputHashes.length; ++_i) { + if (_key == _validProposalTypeInputHashes[_i]) { + _validProposalTypeOutput memory _output = _validProposalTypeOutputs[_key]; + return (_output._returnParam0); + } + } + return super._validProposalType(_proposalType); + } + + struct _weightNormalizerOutput { + uint256 _returnParam0; + } + + mapping(bytes32 => _weightNormalizerOutput) private _weightNormalizerOutputs; + bytes32[] private _weightNormalizerInputHashes; + + function mock_call__weightNormalizer(uint256 _returnParam0) public { + bytes32 _key = keccak256(abi.encode()); + _weightNormalizerOutputs[_key] = _weightNormalizerOutput(_returnParam0); + for (uint256 _i; _i < _weightNormalizerInputHashes.length; ++_i) { + if (_key == _weightNormalizerInputHashes[_i]) { + return; + } + } + _weightNormalizerInputHashes.push(_key); + } + + function _weightNormalizer() internal view override returns (uint256 _returnParam0) { + bytes32 _key = keccak256(abi.encode()); + for (uint256 _i; _i < _weightNormalizerInputHashes.length; ++_i) { + if (_key == _weightNormalizerInputHashes[_i]) { + _weightNormalizerOutput memory _output = _weightNormalizerOutputs[_key]; + return (_output._returnParam0); + } + } + return super._weightNormalizer(); + } +} diff --git a/solidity/test/unit/WonderGovernor.t.sol b/solidity/test/unit/WonderGovernor.t.sol new file mode 100644 index 0000000..df6909c --- /dev/null +++ b/solidity/test/unit/WonderGovernor.t.sol @@ -0,0 +1,94 @@ +import 'forge-std/Test.sol'; + +import {WonderGovernor} from 'contracts/governance/WonderGovernor.sol'; +import {IWonderGovernor} from 'interfaces/governance/IWonderGovernor.sol'; +import {AliceGovernor} from 'examples/AliceGovernor.sol'; +import {MockRabbitToken} from '../smock/examples/MockRabbitToken.sol'; +import {IWonderVotes} from 'interfaces/governance/utils/IWonderVotes.sol'; + +contract BaseTest is Test { + address deployer = makeAddr('deployer'); + address hatter = makeAddr('hatter'); + + IWonderGovernor governor; + MockRabbitToken rabbit; + + function _mockGetPastVotes(address _account, uint8 _proposalType, uint256 _timePoint, uint256 _votes) internal { + vm.mockCall( + address(rabbit), + abi.encodeWithSelector(IWonderVotes.getPastVotes.selector, _account, _proposalType, _timePoint), + abi.encode(_votes) + ); + } + + function setUp() public { + vm.startPrank(deployer); + + address tokenAddress = vm.computeCreateAddress(deployer, vm.getNonce(deployer) + 1); + governor = new AliceGovernor(tokenAddress); + rabbit = new MockRabbitToken(AliceGovernor(payable(address(governor)))); + + vm.stopPrank(); + } + + function _expectEmit(address _contract) internal { + vm.expectEmit(true, true, true, true, _contract); + } +} + +contract Unit_Propose is BaseTest { + event ProposalCreated( + uint256 proposalId, + uint8 proposalType, + address proposer, + address[] targets, + uint256[] values, + string[] signatures, + bytes[] calldatas, + uint256 voteStart, + uint256 voteEnd, + string description + ); + + function test_propose_Emit_ProposalCreated( + uint8 _proposalType, + address _target, + uint256 _value, + bytes memory _calldata, + string memory _description + ) public { + vm.assume(_proposalType < governor.proposalTypes().length); + + // hatter will pass the proposal threshold limit + _mockGetPastVotes(hatter, _proposalType, block.number - 1, governor.proposalThreshold(_proposalType)); + + address[] memory _targets = new address[](1); + _targets[0] = _target; + + uint256[] memory _values = new uint256[](1); + _values[0] = _value; + + bytes[] memory _calldatas = new bytes[](1); + _calldatas[0] = _calldata; + + uint256 _precomputedProposalId = + governor.hashProposal(_proposalType, _targets, _values, _calldatas, keccak256(bytes(_description))); + _expectEmit(address(governor)); + + emit ProposalCreated( + _precomputedProposalId, + _proposalType, + address(hatter), + _targets, + _values, + new string[](1), + _calldatas, + block.number + 1, + block.number + governor.votingPeriod() + 1, + _description + ); + + vm.prank(hatter); + uint256 _proposeId = governor.propose(_proposalType, _targets, _values, _calldatas, _description); + } +} diff --git a/yarn.lock b/yarn.lock index a80fc74..6d6371a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -192,6 +192,14 @@ dependencies: "@jridgewell/trace-mapping" "0.3.9" +"@defi-wonderland/smock-foundry@1.0.6": + version "1.0.6" + resolved "https://registry.yarnpkg.com/@defi-wonderland/smock-foundry/-/smock-foundry-1.0.6.tgz#25cef951d3ab4d038581a64d9d7cb55538bee53e" + integrity sha512-TEvPrkTBao1bMUnmIasBPdDQ46AC/pUFVrcvtvJ9Mc3krZkeZWGP+UDW786B9lGoGWkXFTWoiR6fZuoekczM1w== + dependencies: + handlebars "4.7.7" + yargs "17.7.2" + "@jridgewell/resolve-uri@^3.0.3": version "3.1.1" resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" @@ -231,7 +239,7 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" -"@openzeppelin/contracts@^5.0.1": +"@openzeppelin/contracts@5.0.1": version "5.0.1" resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-5.0.1.tgz#93da90fc209a0a4ff09c1deb037fbb35e4020890" integrity sha512-yQJaT5HDp9hYOOp4jTYxMsR02gdFZFXhewX5HW9Jo4fsqSVqqyIO/xTHdWDaKX5a3pv1txmf076Lziz+sO7L1w== @@ -1192,6 +1200,18 @@ graceful-fs@^4.1.6, graceful-fs@^4.2.0: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== +handlebars@4.7.7: + version "4.7.7" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1" + integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA== + dependencies: + minimist "^1.2.5" + neo-async "^2.6.0" + source-map "^0.6.1" + wordwrap "^1.0.0" + optionalDependencies: + uglify-js "^3.1.4" + hard-rejection@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/hard-rejection/-/hard-rejection-2.1.0.tgz#1c6eda5c1685c63942766d79bb40ae773cecd883" @@ -1701,7 +1721,7 @@ minimist-options@4.1.0: is-plain-obj "^1.1.0" kind-of "^6.0.3" -minimist@^1.2.6: +minimist@^1.2.5, minimist@^1.2.6: version "1.2.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== @@ -1728,6 +1748,11 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw== +neo-async@^2.6.0: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + nice-try@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" @@ -2262,6 +2287,11 @@ sort-package-json@1.53.1: is-plain-obj "2.1.0" sort-object-keys "^1.1.3" +source-map@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + spdx-correct@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.2.0.tgz#4f5ab0668f0059e34f9c00dce331784a12de4e9c" @@ -2534,6 +2564,11 @@ type-fest@^1.0.2: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.2.tgz#00d1c7c1c46928c5845c1ee8d0cc2791031d4c43" integrity sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ== +uglify-js@^3.1.4: + version "3.17.4" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.17.4.tgz#61678cf5fa3f5b7eb789bb345df29afb8257c22c" + integrity sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g== + undici-types@~5.26.4: version "5.26.5" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" @@ -2588,6 +2623,11 @@ word-wrap@~1.2.3: resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.5.tgz#d2c45c6dd4fbce621a66f136cbe328afd0410b34" integrity sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA== +wordwrap@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== + wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" @@ -2643,7 +2683,7 @@ yargs-parser@^21.1.1: resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== -yargs@^17.0.0: +yargs@17.7.2, yargs@^17.0.0: version "17.7.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== From d07cb6c9134d0fa637102aa1b9fa7c005f49c875 Mon Sep 17 00:00:00 2001 From: 0xRaccoon Date: Thu, 21 Dec 2023 10:14:57 -0300 Subject: [PATCH 04/16] test: add governor unit tests Signed-off-by: 0xRaccoon --- .../contracts/governance/WonderGovernor.sol | 2 +- .../interfaces/governance/IWonderGovernor.sol | 2 +- solidity/test/unit/WonderGovernor.t.sol | 132 +++++++++++++++++- 3 files changed, 130 insertions(+), 6 deletions(-) diff --git a/solidity/contracts/governance/WonderGovernor.sol b/solidity/contracts/governance/WonderGovernor.sol index f564ded..820d254 100644 --- a/solidity/contracts/governance/WonderGovernor.sol +++ b/solidity/contracts/governance/WonderGovernor.sol @@ -79,7 +79,7 @@ abstract contract WonderGovernor is */ modifier validProposalType(uint8 proposalType) { if (!_isValidProposalType(proposalType)) { - revert InvalidProposalType(proposalType); + revert GovernorInvalidProposalType(proposalType); } _; } diff --git a/solidity/interfaces/governance/IWonderGovernor.sol b/solidity/interfaces/governance/IWonderGovernor.sol index 2e49684..eb8663f 100644 --- a/solidity/interfaces/governance/IWonderGovernor.sol +++ b/solidity/interfaces/governance/IWonderGovernor.sol @@ -105,7 +105,7 @@ interface IWonderGovernor is IERC165, IERC6372 { /** * @dev The proposalType is not supported by the governor. */ - error InvalidProposalType(uint8 proposalType); + error GovernorInvalidProposalType(uint8 proposalType); /** * @dev Emitted when a proposal is created. diff --git a/solidity/test/unit/WonderGovernor.t.sol b/solidity/test/unit/WonderGovernor.t.sol index df6909c..576bb2e 100644 --- a/solidity/test/unit/WonderGovernor.t.sol +++ b/solidity/test/unit/WonderGovernor.t.sol @@ -5,6 +5,15 @@ import {IWonderGovernor} from 'interfaces/governance/IWonderGovernor.sol'; import {AliceGovernor} from 'examples/AliceGovernor.sol'; import {MockRabbitToken} from '../smock/examples/MockRabbitToken.sol'; import {IWonderVotes} from 'interfaces/governance/utils/IWonderVotes.sol'; +import {IWonderGovernor} from 'interfaces/governance/IWonderGovernor.sol'; + +contract GovernorForTest is AliceGovernor { + constructor(address _wonderToken) AliceGovernor(_wonderToken) {} + + function getProposal(uint256 _proposalId) public view returns (ProposalCore memory) { + return _getProposal(_proposalId); + } +} contract BaseTest is Test { address deployer = makeAddr('deployer'); @@ -25,7 +34,7 @@ contract BaseTest is Test { vm.startPrank(deployer); address tokenAddress = vm.computeCreateAddress(deployer, vm.getNonce(deployer) + 1); - governor = new AliceGovernor(tokenAddress); + governor = new GovernorForTest(tokenAddress); rabbit = new MockRabbitToken(AliceGovernor(payable(address(governor)))); vm.stopPrank(); @@ -50,17 +59,19 @@ contract Unit_Propose is BaseTest { string description ); - function test_propose_Emit_ProposalCreated( + function test_Emit_ProposalCreated( uint8 _proposalType, address _target, uint256 _value, bytes memory _calldata, - string memory _description + string memory _description, + uint256 _proposerVotes ) public { vm.assume(_proposalType < governor.proposalTypes().length); + vm.assume(_proposerVotes >= governor.proposalThreshold(_proposalType)); // hatter will pass the proposal threshold limit - _mockGetPastVotes(hatter, _proposalType, block.number - 1, governor.proposalThreshold(_proposalType)); + _mockGetPastVotes(hatter, _proposalType, block.number - 1, _proposerVotes); address[] memory _targets = new address[](1); _targets[0] = _target; @@ -91,4 +102,117 @@ contract Unit_Propose is BaseTest { vm.prank(hatter); uint256 _proposeId = governor.propose(_proposalType, _targets, _values, _calldatas, _description); } + + function test_Stores_New_Proposal( + uint8 _proposalType, + address _target, + uint256 _value, + bytes memory _calldata, + string memory _description, + uint256 _proposerVotes + ) public { + vm.assume(_proposalType < governor.proposalTypes().length); + vm.assume(_proposerVotes >= governor.proposalThreshold(_proposalType)); + + _mockGetPastVotes(hatter, _proposalType, block.number - 1, _proposerVotes); + + address[] memory _targets = new address[](1); + _targets[0] = _target; + + uint256[] memory _values = new uint256[](1); + _values[0] = _value; + + bytes[] memory _calldatas = new bytes[](1); + _calldatas[0] = _calldata; + + vm.prank(hatter); + uint256 _proposeId = governor.propose(_proposalType, _targets, _values, _calldatas, _description); + + WonderGovernor.ProposalCore memory _proposal = GovernorForTest(payable(address(governor))).getProposal(_proposeId); + + assertEq(_proposal.proposer, hatter); + assertEq(_proposal.proposalType, _proposalType); + assertEq(_proposal.voteStart, block.number + 1); + assertEq(_proposal.voteDuration, governor.votingPeriod()); + assertEq(_proposal.executed, false); + assertEq(_proposal.canceled, false); + assertEq(_proposal.etaSeconds, 0); + } + + function test_Revert_GovernorInvalidProposalType( + uint8 _proposalType, + address _target, + uint256 _value, + bytes memory _calldata + ) public { + vm.assume(_proposalType >= governor.proposalTypes().length); + + _mockGetPastVotes(hatter, _proposalType, block.number - 1, governor.proposalThreshold(_proposalType)); + + address[] memory _targets = new address[](1); + _targets[0] = _target; + + uint256[] memory _values = new uint256[](1); + _values[0] = _value; + + bytes[] memory _calldatas = new bytes[](1); + _calldatas[0] = _calldata; + + vm.prank(hatter); + + vm.expectRevert(abi.encodeWithSelector(IWonderGovernor.GovernorInvalidProposalType.selector, _proposalType)); + governor.propose(_proposalType, _targets, _values, _calldatas, ''); + } + + function test_Revert_GovernorInsufficientProposerVotes( + uint8 _proposalType, + address _target, + uint256 _value, + bytes memory _calldata, + uint256 _proposerVotes + ) public { + vm.assume(_proposalType < governor.proposalTypes().length); + uint256 _votesThreshold = governor.proposalThreshold(_proposalType); + vm.assume(_proposerVotes < _votesThreshold); + + _mockGetPastVotes(hatter, _proposalType, block.number - 1, _proposerVotes); + + address[] memory _targets = new address[](1); + _targets[0] = _target; + + uint256[] memory _values = new uint256[](1); + _values[0] = _value; + + bytes[] memory _calldatas = new bytes[](1); + _calldatas[0] = _calldata; + + vm.prank(hatter); + + vm.expectRevert( + abi.encodeWithSelector( + IWonderGovernor.GovernorInsufficientProposerVotes.selector, hatter, _proposerVotes, _votesThreshold + ) + ); + governor.propose(_proposalType, _targets, _values, _calldatas, ''); + } + + function test_Revert_GovernorInvalidProposalLength( + uint8 _proposalType, + address[] memory _targets, + uint256[] memory _values, + bytes[] memory _calldatas + ) public { + vm.assume(_proposalType < governor.proposalTypes().length); + vm.assume(_targets.length != _values.length || _targets.length != _calldatas.length || _targets.length == 0); + _mockGetPastVotes(hatter, _proposalType, block.number - 1, governor.proposalThreshold(_proposalType)); + + vm.prank(hatter); + vm.expectRevert( + abi.encodeWithSelector( + IWonderGovernor.GovernorInvalidProposalLength.selector, _targets.length, _calldatas.length, _values.length + ) + ); + + governor.propose(_proposalType, _targets, _values, _calldatas, ''); + } } From 3a3a65db28b195920ed7893b98f12be0cb060e40 Mon Sep 17 00:00:00 2001 From: 0xRaccoon Date: Thu, 21 Dec 2023 12:52:38 -0300 Subject: [PATCH 05/16] test: add governor castVote tests Signed-off-by: 0xRaccoon --- solidity/test/unit/WonderGovernor.t.sol | 217 ++++++++++++++++++++++++ 1 file changed, 217 insertions(+) diff --git a/solidity/test/unit/WonderGovernor.t.sol b/solidity/test/unit/WonderGovernor.t.sol index 576bb2e..35730b9 100644 --- a/solidity/test/unit/WonderGovernor.t.sol +++ b/solidity/test/unit/WonderGovernor.t.sol @@ -6,6 +6,7 @@ import {AliceGovernor} from 'examples/AliceGovernor.sol'; import {MockRabbitToken} from '../smock/examples/MockRabbitToken.sol'; import {IWonderVotes} from 'interfaces/governance/utils/IWonderVotes.sol'; import {IWonderGovernor} from 'interfaces/governance/IWonderGovernor.sol'; +import {WonderVotes} from 'contracts/governance/utils/WonderVotes.sol'; contract GovernorForTest is AliceGovernor { constructor(address _wonderToken) AliceGovernor(_wonderToken) {} @@ -18,6 +19,7 @@ contract GovernorForTest is AliceGovernor { contract BaseTest is Test { address deployer = makeAddr('deployer'); address hatter = makeAddr('hatter'); + address cat = makeAddr('cat'); IWonderGovernor governor; MockRabbitToken rabbit; @@ -139,6 +141,39 @@ contract Unit_Propose is BaseTest { assertEq(_proposal.etaSeconds, 0); } + function test_Call_IWonderVotes_GetVotes( + uint8 _proposalType, + address _target, + uint256 _value, + bytes memory _calldata, + string memory _description, + uint256 _proposerVotes + ) public { + vm.assume(_proposalType < governor.proposalTypes().length); + vm.assume(_proposerVotes >= governor.proposalThreshold(_proposalType)); + + // hatter will pass the proposal threshold limit + _mockGetPastVotes(hatter, _proposalType, block.number - 1, _proposerVotes); + + address[] memory _targets = new address[](1); + _targets[0] = _target; + + uint256[] memory _values = new uint256[](1); + _values[0] = _value; + + bytes[] memory _calldatas = new bytes[](1); + _calldatas[0] = _calldata; + + vm.expectCall( + address(rabbit), + abi.encodeWithSelector(IWonderVotes.getPastVotes.selector, hatter, _proposalType, block.number - 1), + 1 + ); + + vm.prank(hatter); + governor.propose(_proposalType, _targets, _values, _calldatas, _description); + } + function test_Revert_GovernorInvalidProposalType( uint8 _proposalType, address _target, @@ -216,3 +251,185 @@ contract Unit_Propose is BaseTest { governor.propose(_proposalType, _targets, _values, _calldatas, ''); } } + +contract Unit_CastVote is BaseTest { + event VoteCast(address indexed voter, uint256 proposalId, uint8 support, uint256 weight, string reason); + + function _createProposal( + uint8 _proposalType, + address _target, + uint256 _value, + bytes memory _calldata, + string memory _description, + uint256 _proposerVotes + ) internal returns (uint256) { + vm.assume(_proposalType < governor.proposalTypes().length); + vm.assume(_proposerVotes >= governor.proposalThreshold(_proposalType)); + + _mockGetPastVotes(hatter, _proposalType, block.number - 1, _proposerVotes); + + address[] memory _targets = new address[](1); + _targets[0] = _target; + + uint256[] memory _values = new uint256[](1); + _values[0] = _value; + + bytes[] memory _calldatas = new bytes[](1); + _calldatas[0] = _calldata; + + vm.prank(hatter); + return governor.propose(_proposalType, _targets, _values, _calldatas, _description); + } + + function test_Emit_VoteCast( + uint8 _proposalType, + uint8 _support, + address _target, + uint256 _value, + bytes memory _calldata, + string memory _description, + uint256 _proposerVotes, + uint256 _voterVotes + ) public { + vm.assume(_proposalType < governor.proposalTypes().length); + vm.assume(_proposerVotes >= governor.proposalThreshold(_proposalType)); + vm.assume(_support < 2); + + _mockGetPastVotes(hatter, _proposalType, block.number - 1, _proposerVotes); + _mockGetPastVotes(cat, _proposalType, block.number + governor.votingDelay(), _voterVotes); + + uint256 _proposalId = _createProposal(_proposalType, _target, _value, _calldata, _description, _proposerVotes); + + vm.roll(block.number + governor.votingDelay() + 1); + + _expectEmit(address(governor)); + emit VoteCast(cat, _proposalId, _support, _voterVotes, ''); + + vm.prank(cat); + governor.castVote(_proposalId, _support); + } + + function test_Call_GetVotes( + uint8 _proposalType, + uint8 _support, + address _target, + uint256 _value, + bytes memory _calldata, + string memory _description, + uint256 _proposerVotes, + uint256 _voterVotes + ) public { + vm.assume(_proposalType < governor.proposalTypes().length); + vm.assume(_proposerVotes >= governor.proposalThreshold(_proposalType)); + vm.assume(_voterVotes > 0); + vm.assume(_support < 2); + + uint256 _voteStart = block.number + governor.votingDelay(); + _mockGetPastVotes(hatter, _proposalType, block.number - 1, _proposerVotes); + _mockGetPastVotes(cat, _proposalType, _voteStart, _voterVotes); + + uint256 _proposalId = _createProposal(_proposalType, _target, _value, _calldata, _description, _proposerVotes); + + vm.roll(block.number + governor.votingDelay() + 1); + + vm.expectCall( + address(rabbit), abi.encodeWithSelector(IWonderVotes.getPastVotes.selector, cat, _proposalType, _voteStart), 1 + ); + + vm.prank(cat); + governor.castVote(_proposalId, _support); + } + + function test_Count_VoteFor( + uint8 _proposalType, + address _target, + uint256 _value, + bytes memory _calldata, + string memory _description, + uint256 _proposerVotes, + uint256 _voterVotes + ) public { + vm.assume(_proposalType < governor.proposalTypes().length); + vm.assume(_proposerVotes >= governor.proposalThreshold(_proposalType)); + + uint256 _voteStart = block.number + governor.votingDelay(); + _mockGetPastVotes(hatter, _proposalType, block.number - 1, _proposerVotes); + _mockGetPastVotes(cat, _proposalType, _voteStart, _voterVotes); + + uint256 _proposalId = _createProposal(_proposalType, _target, _value, _calldata, _description, _proposerVotes); + + vm.roll(block.number + governor.votingDelay() + 1); + + vm.prank(cat); + governor.castVote(_proposalId, 1); + + (uint256 _id, uint256 _votes, uint256 _forVotes, uint256 _againstVotes, uint256 _abstainVotes) = + AliceGovernor(payable(address(governor))).proposalTracks(_proposalId); + + assertEq(_forVotes, _voterVotes); + assertEq(_againstVotes, 0); + assertEq(_abstainVotes, 0); + } + + function test_Count_VoteAgainst( + uint8 _proposalType, + address _target, + uint256 _value, + bytes memory _calldata, + string memory _description, + uint256 _proposerVotes, + uint256 _voterVotes + ) public { + vm.assume(_proposalType < governor.proposalTypes().length); + vm.assume(_proposerVotes >= governor.proposalThreshold(_proposalType)); + + uint256 _voteStart = block.number + governor.votingDelay(); + _mockGetPastVotes(hatter, _proposalType, block.number - 1, _proposerVotes); + _mockGetPastVotes(cat, _proposalType, _voteStart, _voterVotes); + + uint256 _proposalId = _createProposal(_proposalType, _target, _value, _calldata, _description, _proposerVotes); + + vm.roll(block.number + governor.votingDelay() + 1); + + vm.prank(cat); + governor.castVote(_proposalId, 0); + + (uint256 _id, uint256 _votes, uint256 _forVotes, uint256 _againstVotes, uint256 _abstainVotes) = + AliceGovernor(payable(address(governor))).proposalTracks(_proposalId); + + assertEq(_forVotes, 0); + assertEq(_againstVotes, _voterVotes); + assertEq(_abstainVotes, 0); + } + + function test_Count_VoteAbstain( + uint8 _proposalType, + address _target, + uint256 _value, + bytes memory _calldata, + string memory _description, + uint256 _proposerVotes, + uint256 _voterVotes + ) public { + vm.assume(_proposalType < governor.proposalTypes().length); + vm.assume(_proposerVotes >= governor.proposalThreshold(_proposalType)); + + uint256 _voteStart = block.number + governor.votingDelay(); + _mockGetPastVotes(hatter, _proposalType, block.number - 1, _proposerVotes); + _mockGetPastVotes(cat, _proposalType, _voteStart, _voterVotes); + + uint256 _proposalId = _createProposal(_proposalType, _target, _value, _calldata, _description, _proposerVotes); + + vm.roll(block.number + governor.votingDelay() + 1); + + vm.prank(cat); + governor.castVote(_proposalId, 2); + + (uint256 _id, uint256 _votes, uint256 _forVotes, uint256 _againstVotes, uint256 _abstainVotes) = + AliceGovernor(payable(address(governor))).proposalTracks(_proposalId); + + assertEq(_forVotes, 0); + assertEq(_againstVotes, 0); + assertEq(_abstainVotes, _voterVotes); + } +} From 6414e1a27cb998ed8b1228956c800327f81a1927 Mon Sep 17 00:00:00 2001 From: 0xRaccoon Date: Thu, 21 Dec 2023 14:29:18 -0300 Subject: [PATCH 06/16] test: add governor unit tests Signed-off-by: 0xRaccoon --- solidity/test/unit/WonderGovernor.t.sol | 377 ++++++++++++++++++++++-- 1 file changed, 355 insertions(+), 22 deletions(-) diff --git a/solidity/test/unit/WonderGovernor.t.sol b/solidity/test/unit/WonderGovernor.t.sol index 35730b9..cd0a0aa 100644 --- a/solidity/test/unit/WonderGovernor.t.sol +++ b/solidity/test/unit/WonderGovernor.t.sol @@ -45,6 +45,32 @@ contract BaseTest is Test { function _expectEmit(address _contract) internal { vm.expectEmit(true, true, true, true, _contract); } + + function _createProposal( + uint8 _proposalType, + address _target, + uint256 _value, + bytes memory _calldata, + string memory _description, + uint256 _proposerVotes + ) internal returns (uint256) { + vm.assume(_proposalType < governor.proposalTypes().length); + vm.assume(_proposerVotes >= governor.proposalThreshold(_proposalType)); + + _mockGetPastVotes(hatter, _proposalType, block.number - 1, _proposerVotes); + + address[] memory _targets = new address[](1); + _targets[0] = _target; + + uint256[] memory _values = new uint256[](1); + _values[0] = _value; + + bytes[] memory _calldatas = new bytes[](1); + _calldatas[0] = _calldata; + + vm.prank(hatter); + return governor.propose(_proposalType, _targets, _values, _calldatas, _description); + } } contract Unit_Propose is BaseTest { @@ -255,33 +281,35 @@ contract Unit_Propose is BaseTest { contract Unit_CastVote is BaseTest { event VoteCast(address indexed voter, uint256 proposalId, uint8 support, uint256 weight, string reason); - function _createProposal( + function test_Emit_VoteCast( uint8 _proposalType, + uint8 _support, address _target, uint256 _value, bytes memory _calldata, string memory _description, - uint256 _proposerVotes - ) internal returns (uint256) { + uint256 _proposerVotes, + uint256 _voterVotes + ) public { vm.assume(_proposalType < governor.proposalTypes().length); vm.assume(_proposerVotes >= governor.proposalThreshold(_proposalType)); + vm.assume(_support < 2); _mockGetPastVotes(hatter, _proposalType, block.number - 1, _proposerVotes); + _mockGetPastVotes(cat, _proposalType, block.number + governor.votingDelay(), _voterVotes); - address[] memory _targets = new address[](1); - _targets[0] = _target; + uint256 _proposalId = _createProposal(_proposalType, _target, _value, _calldata, _description, _proposerVotes); - uint256[] memory _values = new uint256[](1); - _values[0] = _value; + vm.roll(block.number + governor.votingDelay() + 1); - bytes[] memory _calldatas = new bytes[](1); - _calldatas[0] = _calldata; + _expectEmit(address(governor)); + emit VoteCast(cat, _proposalId, _support, _voterVotes, ''); - vm.prank(hatter); - return governor.propose(_proposalType, _targets, _values, _calldatas, _description); + vm.prank(cat); + governor.castVote(_proposalId, _support); } - function test_Emit_VoteCast( + function test_Call_GetVotes( uint8 _proposalType, uint8 _support, address _target, @@ -290,6 +318,135 @@ contract Unit_CastVote is BaseTest { string memory _description, uint256 _proposerVotes, uint256 _voterVotes + ) public { + vm.assume(_proposalType < governor.proposalTypes().length); + vm.assume(_proposerVotes >= governor.proposalThreshold(_proposalType)); + vm.assume(_voterVotes > 0); + vm.assume(_support < 2); + + uint256 _voteStart = block.number + governor.votingDelay(); + _mockGetPastVotes(hatter, _proposalType, block.number - 1, _proposerVotes); + _mockGetPastVotes(cat, _proposalType, _voteStart, _voterVotes); + + uint256 _proposalId = _createProposal(_proposalType, _target, _value, _calldata, _description, _proposerVotes); + + vm.roll(block.number + governor.votingDelay() + 1); + + vm.expectCall( + address(rabbit), abi.encodeWithSelector(IWonderVotes.getPastVotes.selector, cat, _proposalType, _voteStart), 1 + ); + + vm.prank(cat); + governor.castVote(_proposalId, _support); + } + + function test_Count_VoteFor( + uint8 _proposalType, + address _target, + uint256 _value, + bytes memory _calldata, + string memory _description, + uint256 _proposerVotes, + uint256 _voterVotes + ) public { + vm.assume(_proposalType < governor.proposalTypes().length); + vm.assume(_proposerVotes >= governor.proposalThreshold(_proposalType)); + + uint256 _voteStart = block.number + governor.votingDelay(); + _mockGetPastVotes(hatter, _proposalType, block.number - 1, _proposerVotes); + _mockGetPastVotes(cat, _proposalType, _voteStart, _voterVotes); + + uint256 _proposalId = _createProposal(_proposalType, _target, _value, _calldata, _description, _proposerVotes); + + vm.roll(block.number + governor.votingDelay() + 1); + + vm.prank(cat); + governor.castVote(_proposalId, 1); + + (uint256 _id, uint256 _votes, uint256 _forVotes, uint256 _againstVotes, uint256 _abstainVotes) = + AliceGovernor(payable(address(governor))).proposalTracks(_proposalId); + + assertEq(_forVotes, _voterVotes); + assertEq(_againstVotes, 0); + assertEq(_abstainVotes, 0); + } + + function test_Count_VoteAgainst( + uint8 _proposalType, + address _target, + uint256 _value, + bytes memory _calldata, + string memory _description, + uint256 _proposerVotes, + uint256 _voterVotes + ) public { + vm.assume(_proposalType < governor.proposalTypes().length); + vm.assume(_proposerVotes >= governor.proposalThreshold(_proposalType)); + + uint256 _voteStart = block.number + governor.votingDelay(); + _mockGetPastVotes(hatter, _proposalType, block.number - 1, _proposerVotes); + _mockGetPastVotes(cat, _proposalType, _voteStart, _voterVotes); + + uint256 _proposalId = _createProposal(_proposalType, _target, _value, _calldata, _description, _proposerVotes); + + vm.roll(block.number + governor.votingDelay() + 1); + + vm.prank(cat); + governor.castVote(_proposalId, 0); + + (uint256 _id, uint256 _votes, uint256 _forVotes, uint256 _againstVotes, uint256 _abstainVotes) = + AliceGovernor(payable(address(governor))).proposalTracks(_proposalId); + + assertEq(_forVotes, 0); + assertEq(_againstVotes, _voterVotes); + assertEq(_abstainVotes, 0); + } + + function test_Count_VoteAbstain( + uint8 _proposalType, + address _target, + uint256 _value, + bytes memory _calldata, + string memory _description, + uint256 _proposerVotes, + uint256 _voterVotes + ) public { + vm.assume(_proposalType < governor.proposalTypes().length); + vm.assume(_proposerVotes >= governor.proposalThreshold(_proposalType)); + + uint256 _voteStart = block.number + governor.votingDelay(); + _mockGetPastVotes(hatter, _proposalType, block.number - 1, _proposerVotes); + _mockGetPastVotes(cat, _proposalType, _voteStart, _voterVotes); + + uint256 _proposalId = _createProposal(_proposalType, _target, _value, _calldata, _description, _proposerVotes); + + vm.roll(block.number + governor.votingDelay() + 1); + + vm.prank(cat); + governor.castVote(_proposalId, 2); + + (uint256 _id, uint256 _votes, uint256 _forVotes, uint256 _againstVotes, uint256 _abstainVotes) = + AliceGovernor(payable(address(governor))).proposalTracks(_proposalId); + + assertEq(_forVotes, 0); + assertEq(_againstVotes, 0); + assertEq(_abstainVotes, _voterVotes); + } +} + +contract Unit_CastVoteWithReason is BaseTest { + event VoteCast(address indexed voter, uint256 proposalId, uint8 support, uint256 weight, string reason); + + function test_Emit_VoteCast( + uint8 _proposalType, + uint8 _support, + address _target, + uint256 _value, + bytes memory _calldata, + string memory _description, + uint256 _proposerVotes, + uint256 _voterVotes, + string memory _reason ) public { vm.assume(_proposalType < governor.proposalTypes().length); vm.assume(_proposerVotes >= governor.proposalThreshold(_proposalType)); @@ -303,10 +460,10 @@ contract Unit_CastVote is BaseTest { vm.roll(block.number + governor.votingDelay() + 1); _expectEmit(address(governor)); - emit VoteCast(cat, _proposalId, _support, _voterVotes, ''); + emit VoteCast(cat, _proposalId, _support, _voterVotes, _reason); vm.prank(cat); - governor.castVote(_proposalId, _support); + governor.castVoteWithReason(_proposalId, _support, _reason); } function test_Call_GetVotes( @@ -317,7 +474,8 @@ contract Unit_CastVote is BaseTest { bytes memory _calldata, string memory _description, uint256 _proposerVotes, - uint256 _voterVotes + uint256 _voterVotes, + string memory _reason ) public { vm.assume(_proposalType < governor.proposalTypes().length); vm.assume(_proposerVotes >= governor.proposalThreshold(_proposalType)); @@ -337,7 +495,7 @@ contract Unit_CastVote is BaseTest { ); vm.prank(cat); - governor.castVote(_proposalId, _support); + governor.castVoteWithReason(_proposalId, _support, _reason); } function test_Count_VoteFor( @@ -347,7 +505,8 @@ contract Unit_CastVote is BaseTest { bytes memory _calldata, string memory _description, uint256 _proposerVotes, - uint256 _voterVotes + uint256 _voterVotes, + string memory _reason ) public { vm.assume(_proposalType < governor.proposalTypes().length); vm.assume(_proposerVotes >= governor.proposalThreshold(_proposalType)); @@ -361,7 +520,7 @@ contract Unit_CastVote is BaseTest { vm.roll(block.number + governor.votingDelay() + 1); vm.prank(cat); - governor.castVote(_proposalId, 1); + governor.castVoteWithReason(_proposalId, 1, _reason); (uint256 _id, uint256 _votes, uint256 _forVotes, uint256 _againstVotes, uint256 _abstainVotes) = AliceGovernor(payable(address(governor))).proposalTracks(_proposalId); @@ -378,7 +537,8 @@ contract Unit_CastVote is BaseTest { bytes memory _calldata, string memory _description, uint256 _proposerVotes, - uint256 _voterVotes + uint256 _voterVotes, + string memory _reason ) public { vm.assume(_proposalType < governor.proposalTypes().length); vm.assume(_proposerVotes >= governor.proposalThreshold(_proposalType)); @@ -392,7 +552,7 @@ contract Unit_CastVote is BaseTest { vm.roll(block.number + governor.votingDelay() + 1); vm.prank(cat); - governor.castVote(_proposalId, 0); + governor.castVoteWithReason(_proposalId, 0, _reason); (uint256 _id, uint256 _votes, uint256 _forVotes, uint256 _againstVotes, uint256 _abstainVotes) = AliceGovernor(payable(address(governor))).proposalTracks(_proposalId); @@ -409,7 +569,8 @@ contract Unit_CastVote is BaseTest { bytes memory _calldata, string memory _description, uint256 _proposerVotes, - uint256 _voterVotes + uint256 _voterVotes, + string memory _reason ) public { vm.assume(_proposalType < governor.proposalTypes().length); vm.assume(_proposerVotes >= governor.proposalThreshold(_proposalType)); @@ -423,7 +584,179 @@ contract Unit_CastVote is BaseTest { vm.roll(block.number + governor.votingDelay() + 1); vm.prank(cat); - governor.castVote(_proposalId, 2); + governor.castVoteWithReason(_proposalId, 2, _reason); + + (uint256 _id, uint256 _votes, uint256 _forVotes, uint256 _againstVotes, uint256 _abstainVotes) = + AliceGovernor(payable(address(governor))).proposalTracks(_proposalId); + + assertEq(_forVotes, 0); + assertEq(_againstVotes, 0); + assertEq(_abstainVotes, _voterVotes); + } +} + +contract Unit_CastVoteWithReasonAndParams is BaseTest { + event VoteCastWithParams( + address indexed voter, uint256 proposalId, uint8 support, uint256 weight, string reason, bytes params + ); + + function test_Emit_VoteCastWithParams( + uint8 _proposalType, + uint8 _support, + address _target, + uint256 _value, + bytes memory _calldata, + string memory _description, + uint256 _proposerVotes, + uint256 _voterVotes, + string memory _reason, + bytes memory _params + ) public { + vm.assume(_proposalType < governor.proposalTypes().length); + vm.assume(_proposerVotes >= governor.proposalThreshold(_proposalType)); + vm.assume(_support < 2); + vm.assume(_params.length > 0); + + _mockGetPastVotes(hatter, _proposalType, block.number - 1, _proposerVotes); + _mockGetPastVotes(cat, _proposalType, block.number + governor.votingDelay(), _voterVotes); + + uint256 _proposalId = _createProposal(_proposalType, _target, _value, _calldata, _description, _proposerVotes); + + vm.roll(block.number + governor.votingDelay() + 1); + + _expectEmit(address(governor)); + emit VoteCastWithParams(cat, _proposalId, _support, _voterVotes, _reason, _params); + + vm.prank(cat); + governor.castVoteWithReasonAndParams(_proposalId, _support, _reason, _params); + } + + function test_Call_GetVotes( + uint8 _proposalType, + uint8 _support, + address _target, + uint256 _value, + bytes memory _calldata, + string memory _description, + uint256 _proposerVotes, + uint256 _voterVotes, + bytes memory _params + ) public { + vm.assume(_proposalType < governor.proposalTypes().length); + vm.assume(_proposerVotes >= governor.proposalThreshold(_proposalType)); + vm.assume(_voterVotes > 0); + vm.assume(_support < 2); + vm.assume(_params.length > 0); + + uint256 _voteStart = block.number + governor.votingDelay(); + _mockGetPastVotes(hatter, _proposalType, block.number - 1, _proposerVotes); + _mockGetPastVotes(cat, _proposalType, _voteStart, _voterVotes); + + uint256 _proposalId = _createProposal(_proposalType, _target, _value, _calldata, _description, _proposerVotes); + + vm.roll(block.number + governor.votingDelay() + 1); + + vm.expectCall( + address(rabbit), abi.encodeWithSelector(IWonderVotes.getPastVotes.selector, cat, _proposalType, _voteStart), 1 + ); + + vm.prank(cat); + governor.castVoteWithReasonAndParams(_proposalId, _support, '', _params); + } + + function test_Count_VoteFor( + uint8 _proposalType, + address _target, + uint256 _value, + bytes memory _calldata, + string memory _description, + uint256 _proposerVotes, + uint256 _voterVotes, + string memory _reason, + bytes memory _params + ) public { + vm.assume(_proposalType < governor.proposalTypes().length); + vm.assume(_proposerVotes >= governor.proposalThreshold(_proposalType)); + vm.assume(_params.length > 0); + + uint256 _voteStart = block.number + governor.votingDelay(); + _mockGetPastVotes(hatter, _proposalType, block.number - 1, _proposerVotes); + _mockGetPastVotes(cat, _proposalType, _voteStart, _voterVotes); + + uint256 _proposalId = _createProposal(_proposalType, _target, _value, _calldata, _description, _proposerVotes); + + vm.roll(block.number + governor.votingDelay() + 1); + + vm.prank(cat); + governor.castVoteWithReasonAndParams(_proposalId, 1, _reason, _params); + + (uint256 _id, uint256 _votes, uint256 _forVotes, uint256 _againstVotes, uint256 _abstainVotes) = + AliceGovernor(payable(address(governor))).proposalTracks(_proposalId); + + assertEq(_forVotes, _voterVotes); + assertEq(_againstVotes, 0); + assertEq(_abstainVotes, 0); + } + + function test_Count_VoteAgainst( + uint8 _proposalType, + address _target, + uint256 _value, + bytes memory _calldata, + string memory _description, + uint256 _proposerVotes, + uint256 _voterVotes, + string memory _reason, + bytes memory _params + ) public { + vm.assume(_proposalType < governor.proposalTypes().length); + vm.assume(_proposerVotes >= governor.proposalThreshold(_proposalType)); + vm.assume(_params.length > 0); + + uint256 _voteStart = block.number + governor.votingDelay(); + _mockGetPastVotes(hatter, _proposalType, block.number - 1, _proposerVotes); + _mockGetPastVotes(cat, _proposalType, _voteStart, _voterVotes); + + uint256 _proposalId = _createProposal(_proposalType, _target, _value, _calldata, _description, _proposerVotes); + + vm.roll(block.number + governor.votingDelay() + 1); + + vm.prank(cat); + governor.castVoteWithReasonAndParams(_proposalId, 0, _reason, _params); + + (uint256 _id, uint256 _votes, uint256 _forVotes, uint256 _againstVotes, uint256 _abstainVotes) = + AliceGovernor(payable(address(governor))).proposalTracks(_proposalId); + + assertEq(_forVotes, 0); + assertEq(_againstVotes, _voterVotes); + assertEq(_abstainVotes, 0); + } + + function test_Count_VoteAbstain( + uint8 _proposalType, + address _target, + uint256 _value, + bytes memory _calldata, + string memory _description, + uint256 _proposerVotes, + uint256 _voterVotes, + string memory _reason, + bytes memory _params + ) public { + vm.assume(_proposalType < governor.proposalTypes().length); + vm.assume(_proposerVotes >= governor.proposalThreshold(_proposalType)); + vm.assume(_params.length > 0); + + uint256 _voteStart = block.number + governor.votingDelay(); + _mockGetPastVotes(hatter, _proposalType, block.number - 1, _proposerVotes); + _mockGetPastVotes(cat, _proposalType, _voteStart, _voterVotes); + + uint256 _proposalId = _createProposal(_proposalType, _target, _value, _calldata, _description, _proposerVotes); + + vm.roll(block.number + governor.votingDelay() + 1); + + vm.prank(cat); + governor.castVoteWithReasonAndParams(_proposalId, 2, _reason, _params); (uint256 _id, uint256 _votes, uint256 _forVotes, uint256 _againstVotes, uint256 _abstainVotes) = AliceGovernor(payable(address(governor))).proposalTracks(_proposalId); From 681175bdf22cdb0e3e7a68feed1bb1cf480c7330 Mon Sep 17 00:00:00 2001 From: 0xRaccoon Date: Fri, 22 Dec 2023 13:49:57 -0300 Subject: [PATCH 07/16] test: add wonder votes unit tests Signed-off-by: 0xRaccoon --- solidity/examples/AliceGovernor.sol | 2 +- solidity/test/unit/WonderVotes.t.sol | 617 +++++++++++++++++++++++++++ 2 files changed, 618 insertions(+), 1 deletion(-) create mode 100644 solidity/test/unit/WonderVotes.t.sol diff --git a/solidity/examples/AliceGovernor.sol b/solidity/examples/AliceGovernor.sol index 74eee89..2c2488d 100644 --- a/solidity/examples/AliceGovernor.sol +++ b/solidity/examples/AliceGovernor.sol @@ -7,7 +7,7 @@ import 'contracts/governance/utils/WonderVotes.sol'; contract AliceGovernor is WonderGovernor { WonderVotes public votes; string internal _countingMode = 'support=bravo&quorum=bravo'; - uint8[] internal __proposalTypes = [1, 2, 3]; + uint8[] internal __proposalTypes = [0, 1, 2, 3]; mapping(uint256 proposalId => mapping(address => BallotReceipt)) public receipts; mapping(uint256 proposalId => ProposalTrack) public proposalTracks; diff --git a/solidity/test/unit/WonderVotes.t.sol b/solidity/test/unit/WonderVotes.t.sol new file mode 100644 index 0000000..62e911b --- /dev/null +++ b/solidity/test/unit/WonderVotes.t.sol @@ -0,0 +1,617 @@ +import 'forge-std/Test.sol'; + +import {WonderGovernor} from 'contracts/governance/WonderGovernor.sol'; +import {IWonderGovernor} from 'interfaces/governance/IWonderGovernor.sol'; +import {IWonderVotes} from 'interfaces/governance/utils/IWonderVotes.sol'; +import {IWonderGovernor} from 'interfaces/governance/IWonderGovernor.sol'; +import {WonderVotes} from 'contracts/governance/utils/WonderVotes.sol'; +import {RabbitToken} from 'examples/RabbitToken.sol'; +import {MockAliceGovernor} from '../smock/examples/MockAliceGovernor.sol'; +import {AliceGovernor} from 'examples/AliceGovernor.sol'; + +contract WonderVotesForTest is RabbitToken { + constructor(AliceGovernor _governor) RabbitToken(_governor) {} + + function mint(address _account, uint256 _amount) public { + _mint(_account, _amount); + } + + function burn(uint256 _amount) public { + _burn(msg.sender, _amount); + } +} + +contract BaseTest is Test { + address deployer = makeAddr('deployer'); + address hatter = makeAddr('hatter'); + address cat = makeAddr('cat'); + + MockAliceGovernor governor; + RabbitToken rabbitToken; + + event DelegateVotesChanged(address indexed delegate, uint8 proposalType, uint256 previousVotes, uint256 newVotes); + + function _mockGetPastVotes(address _account, uint8 _proposalType, uint256 _timePoint, uint256 _votes) internal { + vm.mockCall( + address(rabbitToken), + abi.encodeWithSelector(IWonderVotes.getPastVotes.selector, _account, _proposalType, _timePoint), + abi.encode(_votes) + ); + } + + function setUp() public virtual { + vm.startPrank(deployer); + + address tokenAddress = vm.computeCreateAddress(deployer, vm.getNonce(deployer) + 1); + governor = new MockAliceGovernor(tokenAddress); + rabbitToken = new WonderVotesForTest(AliceGovernor(payable(address(governor)))); + + vm.stopPrank(); + } + + function _expectEmit(address _contract) internal { + vm.expectEmit(true, true, true, true, _contract); + } + + function _createProposal( + uint8 _proposalType, + address _target, + uint256 _value, + bytes memory _calldata, + string memory _description, + uint256 _proposerVotes + ) internal returns (uint256) { + vm.assume(_proposalType < governor.proposalTypes().length); + vm.assume(_proposerVotes >= governor.proposalThreshold(_proposalType)); + + _mockGetPastVotes(hatter, _proposalType, block.number - 1, _proposerVotes); + + address[] memory _targets = new address[](1); + _targets[0] = _target; + + uint256[] memory _values = new uint256[](1); + _values[0] = _value; + + bytes[] memory _calldatas = new bytes[](1); + _calldatas[0] = _calldata; + + vm.prank(hatter); + return governor.propose(_proposalType, _targets, _values, _calldatas, _description); + } +} + +contract Unit_Delegate_Simple is BaseTest { + function test_Minting_WithoutTracking_Add_Zero(uint128 _amount) public { + WonderVotesForTest(address(rabbitToken)).mint(hatter, _amount); + uint8[] memory _proposalTypes = rabbitToken.proposalTypes(); + + for (uint256 i = 0; i < _proposalTypes.length; i++) { + assertEq(rabbitToken.getVotes(hatter, _proposalTypes[i]), 0); + } + } + + function test_Minting_SelfDelegate_Before(uint128 _amount) public { + // To start tracking votes the account delegates himself + vm.prank(hatter); + rabbitToken.delegate(hatter); + + WonderVotesForTest(address(rabbitToken)).mint(hatter, _amount); + uint8[] memory _proposalTypes = rabbitToken.proposalTypes(); + + for (uint256 i = 0; i < _proposalTypes.length; i++) { + assertEq(rabbitToken.getVotes(hatter, _proposalTypes[i]), _amount); + } + } + + function test_Minting_SelfDelegate_After(uint128 _amount) public { + WonderVotesForTest(address(rabbitToken)).mint(hatter, _amount); + uint8[] memory _proposalTypes = rabbitToken.proposalTypes(); + + // If the account does not have delegates it will not track votes + for (uint256 i = 0; i < _proposalTypes.length; i++) { + assertEq(rabbitToken.getVotes(hatter, _proposalTypes[i]), 0); + } + + // To start tracking votes the account delegates himself + vm.prank(hatter); + rabbitToken.delegate(hatter); + + for (uint256 i = 0; i < _proposalTypes.length; i++) { + assertEq(rabbitToken.getVotes(hatter, _proposalTypes[i]), _amount); + } + } + + function test_SelfDelegate_Changes(uint128 _amount) public { + // To start tracking votes the account delegates himself + vm.prank(hatter); + rabbitToken.delegate(hatter); + + WonderVotesForTest(address(rabbitToken)).mint(hatter, _amount); + uint8[] memory _proposalTypes = rabbitToken.proposalTypes(); + + for (uint256 i = 0; i < _proposalTypes.length; i++) { + assertEq(rabbitToken.getVotes(hatter, _proposalTypes[i]), _amount); + } + + vm.prank(hatter); + rabbitToken.delegate(cat); + for (uint256 i = 0; i < _proposalTypes.length; i++) { + assertEq(rabbitToken.getVotes(hatter, _proposalTypes[i]), 0); + assertEq(rabbitToken.getVotes(cat, _proposalTypes[i]), _amount); + } + } + + function test_SelfDelegate_Burns(uint128 _amount) public { + vm.prank(hatter); + rabbitToken.delegate(hatter); + + WonderVotesForTest(address(rabbitToken)).mint(hatter, _amount); + uint8[] memory _proposalTypes = rabbitToken.proposalTypes(); + + vm.prank(hatter); + WonderVotesForTest(address(rabbitToken)).burn(_amount); + + for (uint256 i = 0; i < _proposalTypes.length; i++) { + assertEq(rabbitToken.getVotes(hatter, _proposalTypes[i]), 0); + } + } + + function test_Emit_DelegateVotesChanged(uint128 _amount) public { + vm.prank(hatter); + rabbitToken.delegate(hatter); + + uint8[] memory _proposalTypes = rabbitToken.proposalTypes(); + for (uint256 i = 0; i < _proposalTypes.length; i++) { + _expectEmit(address(rabbitToken)); + emit DelegateVotesChanged(hatter, _proposalTypes[i], 0, _amount); + } + + WonderVotesForTest(address(rabbitToken)).mint(hatter, _amount); + } +} + +contract Unit_Delegate_Smart is BaseTest { + function test_Minting_SmartDelegation_Before(uint128 _amount) public { + uint8[] memory _proposalTypes = rabbitToken.proposalTypes(); + + // Will define one delegate per proposal type + address[] memory _delegates = new address[](_proposalTypes.length); + + vm.startPrank(hatter); + for (uint256 i = 0; i < _proposalTypes.length; i++) { + _delegates[i] = makeAddr(string(abi.encodePacked('delegate', i))); + + // 100% voting power to the delegate for the proposalType + rabbitToken.delegate(_delegates[i], _proposalTypes[i]); + } + vm.stopPrank(); + WonderVotesForTest(address(rabbitToken)).mint(hatter, _amount); + + for (uint256 i = 0; i < _proposalTypes.length; i++) { + assertEq(rabbitToken.getVotes(hatter, _proposalTypes[i]), 0); + assertEq(rabbitToken.getVotes(_delegates[i], _proposalTypes[i]), _amount); + } + } + + function test_Minting_SmartDelegation_After(uint128 _amount) public { + uint8[] memory _proposalTypes = rabbitToken.proposalTypes(); + + WonderVotesForTest(address(rabbitToken)).mint(hatter, _amount); + + // Will define one delegate per proposal type + address[] memory _delegates = new address[](_proposalTypes.length); + + vm.startPrank(hatter); + for (uint256 i = 0; i < _proposalTypes.length; i++) { + _delegates[i] = makeAddr(string(abi.encodePacked('delegate', i))); + + // 100% voting power to the delegate for the proposalType + rabbitToken.delegate(_delegates[i], _proposalTypes[i]); + } + vm.stopPrank(); + + for (uint256 i = 0; i < _proposalTypes.length; i++) { + assertEq(rabbitToken.getVotes(hatter, _proposalTypes[i]), 0); + assertEq(rabbitToken.getVotes(_delegates[i], _proposalTypes[i]), _amount); + } + } + + function test_Minting_SmartDelegation_Changes(uint128 _amount) public { + uint8[] memory _proposalTypes = rabbitToken.proposalTypes(); + + // Will define one delegate per proposal type + address[] memory _delegates = new address[](_proposalTypes.length); + address[] memory _delegatesChange = new address[](_proposalTypes.length); + + vm.startPrank(hatter); + for (uint256 i = 0; i < _proposalTypes.length; i++) { + _delegates[i] = makeAddr(string(abi.encodePacked('delegate', i))); + + // 100% voting power to the delegate for the proposalType + rabbitToken.delegate(_delegates[i], _proposalTypes[i]); + } + + WonderVotesForTest(address(rabbitToken)).mint(hatter, _amount); + + for (uint256 i = 0; i < _proposalTypes.length; i++) { + assertEq(rabbitToken.getVotes(hatter, _proposalTypes[i]), 0); + assertEq(rabbitToken.getVotes(_delegates[i], _proposalTypes[i]), _amount); + } + + // Delegates changes + for (uint256 i = 0; i < _proposalTypes.length; i++) { + _delegatesChange[i] = makeAddr(string(abi.encodePacked('delegateChange', i))); + + // 100% voting power to the delegate for the proposalType + rabbitToken.delegate(_delegatesChange[i], _proposalTypes[i]); + } + + for (uint256 i = 0; i < _proposalTypes.length; i++) { + assertEq(rabbitToken.getVotes(hatter, _proposalTypes[i]), 0); + assertEq(rabbitToken.getVotes(_delegates[i], _proposalTypes[i]), 0); + assertEq(rabbitToken.getVotes(_delegatesChange[i], _proposalTypes[i]), _amount); + } + } + + function test_Revert_InvalidProposalType(uint8 _proposalType) public { + vm.assume(_proposalType >= rabbitToken.proposalTypes().length); + + vm.expectRevert(abi.encodeWithSelector(IWonderVotes.InvalidProposalType.selector, _proposalType)); + + vm.prank(hatter); + rabbitToken.delegate(hatter, _proposalType); + } + + function test_Emit_DelegateVotesChanged(uint128 _amount) public { + vm.prank(hatter); + rabbitToken.delegate(hatter); + WonderVotesForTest(address(rabbitToken)).mint(hatter, _amount); + + uint8[] memory _proposalTypes = rabbitToken.proposalTypes(); + + address[] memory _delegates = new address[](_proposalTypes.length); + + vm.startPrank(hatter); + for (uint256 i = 0; i < _proposalTypes.length; i++) { + _delegates[i] = makeAddr(string(abi.encodePacked('delegate', i))); + + _expectEmit(address(rabbitToken)); + emit DelegateVotesChanged(_delegates[i], _proposalTypes[i], 0, _amount); + + rabbitToken.delegate(_delegates[i], _proposalTypes[i]); + } + vm.stopPrank(); + } +} + +contract Unit_Delegate_SmartAndPartial is BaseTest { + function test_Minting_SmartAndPartialDelegation_Before(uint128 _amount) public { + uint8[] memory _proposalTypes = rabbitToken.proposalTypes(); + + // To simply we will divide the voting power into 2 delegates 50% each + // We can add a more complex test of this further + uint256 _weightNormalizer = rabbitToken.weightNormalizer(); + uint256 _weight = _weightNormalizer / 2; + + address[] memory _delegates = new address[](_proposalTypes.length); + address[] memory _delegates2 = new address[](_proposalTypes.length); + + vm.startPrank(hatter); + for (uint256 i = 0; i < _proposalTypes.length; i++) { + _delegates[i] = makeAddr(string(abi.encodePacked('delegate', i))); + _delegates2[i] = makeAddr(string(abi.encodePacked('delegate2', i))); + + IWonderVotes.Delegate memory _delegate = IWonderVotes.Delegate({account: _delegates[i], weight: _weight}); + IWonderVotes.Delegate memory _delegate2 = IWonderVotes.Delegate({account: _delegates2[i], weight: _weight}); + IWonderVotes.Delegate[] memory _delegatesStruct = new IWonderVotes.Delegate[](2); + _delegatesStruct[0] = _delegate; + _delegatesStruct[1] = _delegate2; + + rabbitToken.delegate(_delegatesStruct, _proposalTypes[i]); + } + + vm.stopPrank(); + WonderVotesForTest(address(rabbitToken)).mint(hatter, _amount); + + for (uint256 i = 0; i < _proposalTypes.length; i++) { + assertEq(rabbitToken.getVotes(hatter, _proposalTypes[i]), 0); + emit log_uint(rabbitToken.getVotes(_delegates[i], _proposalTypes[i])); + emit log_uint(rabbitToken.getVotes(_delegates2[i], _proposalTypes[i])); + + assertEq(rabbitToken.getVotes(_delegates[i], _proposalTypes[i]), _amount / 2); + assertEq(rabbitToken.getVotes(_delegates2[i], _proposalTypes[i]), _amount / 2); + } + } + + function test_Minting_SmartAndPartialDelegation_After(uint128 _amount) public { + WonderVotesForTest(address(rabbitToken)).mint(hatter, _amount); + + uint8[] memory _proposalTypes = rabbitToken.proposalTypes(); + + // To simply we will divide the voting power into 2 delegates 50% each + // We can add a more complex test of this further + uint256 _weightNormalizer = rabbitToken.weightNormalizer(); + uint256 _weight = _weightNormalizer / 2; + + address[] memory _delegates = new address[](_proposalTypes.length); + address[] memory _delegates2 = new address[](_proposalTypes.length); + + vm.startPrank(hatter); + for (uint256 i = 0; i < _proposalTypes.length; i++) { + _delegates[i] = makeAddr(string(abi.encodePacked('delegate', i))); + _delegates2[i] = makeAddr(string(abi.encodePacked('delegate2', i))); + + IWonderVotes.Delegate memory _delegate = IWonderVotes.Delegate({account: _delegates[i], weight: _weight}); + IWonderVotes.Delegate memory _delegate2 = IWonderVotes.Delegate({account: _delegates2[i], weight: _weight}); + IWonderVotes.Delegate[] memory _delegatesStruct = new IWonderVotes.Delegate[](2); + _delegatesStruct[0] = _delegate; + _delegatesStruct[1] = _delegate2; + + rabbitToken.delegate(_delegatesStruct, _proposalTypes[i]); + } + vm.stopPrank(); + + for (uint256 i = 0; i < _proposalTypes.length; i++) { + assertEq(rabbitToken.getVotes(hatter, _proposalTypes[i]), 0); + emit log_uint(rabbitToken.getVotes(_delegates[i], _proposalTypes[i])); + emit log_uint(rabbitToken.getVotes(_delegates2[i], _proposalTypes[i])); + + assertEq(rabbitToken.getVotes(_delegates[i], _proposalTypes[i]), _amount / 2); + assertEq(rabbitToken.getVotes(_delegates2[i], _proposalTypes[i]), _amount / 2); + } + } + + function test_Revert_InvalidProposalType(uint8 _proposalType) public { + vm.assume(_proposalType >= rabbitToken.proposalTypes().length); + + IWonderVotes.Delegate[] memory _delegates = new IWonderVotes.Delegate[](1); + _delegates[0] = IWonderVotes.Delegate({account: makeAddr('delegate'), weight: rabbitToken.weightNormalizer()}); + + vm.expectRevert(abi.encodeWithSelector(IWonderVotes.InvalidProposalType.selector, _proposalType)); + + vm.prank(hatter); + rabbitToken.delegate(_delegates, _proposalType); + } + + function test_Emit_DelegateVotesChanged(uint128 _amount) public { + uint8[] memory _proposalTypes = rabbitToken.proposalTypes(); + WonderVotesForTest(address(rabbitToken)).mint(hatter, _amount); + + // To simply we will divide the voting power into 2 delegates 50% each + // We can add a more complex test of this further + uint256 _weightNormalizer = rabbitToken.weightNormalizer(); + uint256 _weight = _weightNormalizer / 2; + + address[] memory _delegates = new address[](_proposalTypes.length); + address[] memory _delegates2 = new address[](_proposalTypes.length); + + vm.startPrank(hatter); + for (uint256 i = 0; i < _proposalTypes.length; i++) { + _delegates[i] = makeAddr(string(abi.encodePacked('delegate', i))); + _delegates2[i] = makeAddr(string(abi.encodePacked('delegate2', i))); + + IWonderVotes.Delegate memory _delegate = IWonderVotes.Delegate({account: _delegates[i], weight: _weight}); + IWonderVotes.Delegate memory _delegate2 = IWonderVotes.Delegate({account: _delegates2[i], weight: _weight}); + IWonderVotes.Delegate[] memory _delegatesStruct = new IWonderVotes.Delegate[](2); + _delegatesStruct[0] = _delegate; + _delegatesStruct[1] = _delegate2; + + _expectEmit(address(rabbitToken)); + + emit DelegateVotesChanged(_delegates[i], _proposalTypes[i], 0, _amount / 2); + emit DelegateVotesChanged(_delegates2[i], _proposalTypes[i], 0, _amount / 2); + + rabbitToken.delegate(_delegatesStruct, _proposalTypes[i]); + } + + vm.stopPrank(); + } + + function test_Revert_ZeroWeight(uint8 _proposalType) public { + vm.assume(_proposalType < rabbitToken.proposalTypes().length); + + IWonderVotes.Delegate[] memory _delegatesStruct = new IWonderVotes.Delegate[](1); + _delegatesStruct[0] = IWonderVotes.Delegate({account: makeAddr('delegate'), weight: 0}); + + vm.expectRevert(abi.encodeWithSelector(IWonderVotes.ZeroWeight.selector)); + + vm.prank(hatter); + rabbitToken.delegate(_delegatesStruct, _proposalType); + } + + function test_Revert_InvalidWeightSum_LessThan_WeighNormalizer(uint8 _proposalType, uint256 _weightSum) public { + vm.assume(_proposalType < rabbitToken.proposalTypes().length); + vm.assume(_weightSum > 0 && (_weightSum > rabbitToken.weightNormalizer())); + + IWonderVotes.Delegate[] memory _delegatesStruct = new IWonderVotes.Delegate[](1); + _delegatesStruct[0] = IWonderVotes.Delegate({account: makeAddr('delegate'), weight: _weightSum}); + + vm.expectRevert(abi.encodeWithSelector(IWonderVotes.InvalidWeightSum.selector, _weightSum)); + + vm.prank(hatter); + rabbitToken.delegate(_delegatesStruct, _proposalType); + } + + function test_Revert_InvalidWeightSum_MoreThan_WeighNormalizer(uint8 _proposalType, uint256 _weightSum) public { + vm.assume(_proposalType < rabbitToken.proposalTypes().length); + vm.assume(_weightSum > 0 && (_weightSum > rabbitToken.weightNormalizer())); + + IWonderVotes.Delegate[] memory _delegatesStruct = new IWonderVotes.Delegate[](1); + _delegatesStruct[0] = IWonderVotes.Delegate({account: makeAddr('delegate'), weight: _weightSum}); + + vm.expectRevert(abi.encodeWithSelector(IWonderVotes.InvalidWeightSum.selector, _weightSum)); + + vm.prank(hatter); + rabbitToken.delegate(_delegatesStruct, _proposalType); + } +} + +contract Unit_TransferVotes is BaseTest { + function setUp() public override { + super.setUp(); + + // To start tracking votes the accounts delegates themselves + vm.prank(hatter); + rabbitToken.delegate(hatter); + vm.prank(cat); + rabbitToken.delegate(cat); + } + + function test_TransferVotes_SimpleDelegation(uint128 _balance, uint128 _transferAmount) public { + vm.assume(_balance >= _transferAmount); + uint8[] memory _proposalTypes = rabbitToken.proposalTypes(); + + WonderVotesForTest(address(rabbitToken)).mint(hatter, _balance); + + vm.prank(hatter); + rabbitToken.transfer(cat, _transferAmount); + + for (uint256 i = 0; i < _proposalTypes.length; i++) { + assertEq(rabbitToken.getVotes(hatter, _proposalTypes[i]), _balance - _transferAmount); + assertEq(rabbitToken.getVotes(cat, _proposalTypes[i]), _transferAmount); + } + } + + function test_TransferVotes_SimpleDelegation_Emit_DelegateVotesChanged( + uint128 _balance, + uint128 _transferAmount + ) public { + vm.assume(_balance >= _transferAmount); + uint8[] memory _proposalTypes = rabbitToken.proposalTypes(); + + WonderVotesForTest(address(rabbitToken)).mint(hatter, _balance); + + _expectEmit(address(rabbitToken)); + + for (uint256 i = 0; i < _proposalTypes.length; i++) { + emit DelegateVotesChanged(hatter, _proposalTypes[i], _balance, _balance - _transferAmount); + emit DelegateVotesChanged(cat, _proposalTypes[i], 0, _transferAmount); + } + + vm.prank(hatter); + rabbitToken.transfer(cat, _transferAmount); + } + + function test_TransferVotes_SmartDelegation(uint128 _balance, uint128 _transferAmount) public { + vm.assume(_balance >= _transferAmount); + uint8[] memory _proposalTypes = rabbitToken.proposalTypes(); + + // Will define one delegate per proposal type + address[] memory _hatterDelegates = new address[](_proposalTypes.length); + address[] memory _catDelegates = new address[](_proposalTypes.length); + + for (uint256 i = 0; i < _proposalTypes.length; i++) { + _hatterDelegates[i] = makeAddr(string(abi.encodePacked('hatterDelegate', i))); + _catDelegates[i] = makeAddr(string(abi.encodePacked('catDelegate', i))); + + // 100% voting power to the delegate for the proposalType + + vm.prank(hatter); + rabbitToken.delegate(_hatterDelegates[i], _proposalTypes[i]); + + vm.prank(cat); + rabbitToken.delegate(_catDelegates[i], _proposalTypes[i]); + } + + WonderVotesForTest(address(rabbitToken)).mint(hatter, _balance); + + vm.prank(hatter); + rabbitToken.transfer(cat, _transferAmount); + + for (uint256 i = 0; i < _proposalTypes.length; i++) { + assertEq(rabbitToken.getVotes(_hatterDelegates[i], _proposalTypes[i]), _balance - _transferAmount); + assertEq(rabbitToken.getVotes(_catDelegates[i], _proposalTypes[i]), _transferAmount); + } + } + + function test_TransferVotes_SmartDelegation_Emits_DelegateVotesChanged( + uint128 _balance, + uint128 _transferAmount + ) public { + vm.assume(_balance >= _transferAmount); + uint8[] memory _proposalTypes = rabbitToken.proposalTypes(); + + // Will define one delegate per proposal type + address[] memory _hatterDelegates = new address[](_proposalTypes.length); + address[] memory _catDelegates = new address[](_proposalTypes.length); + + for (uint256 i = 0; i < _proposalTypes.length; i++) { + _hatterDelegates[i] = makeAddr(string(abi.encodePacked('hatterDelegate', i))); + _catDelegates[i] = makeAddr(string(abi.encodePacked('catDelegate', i))); + + // 100% voting power to the delegate for the proposalType + + vm.prank(hatter); + rabbitToken.delegate(_hatterDelegates[i], _proposalTypes[i]); + + vm.prank(cat); + rabbitToken.delegate(_catDelegates[i], _proposalTypes[i]); + } + + WonderVotesForTest(address(rabbitToken)).mint(hatter, _balance); + + for (uint256 i = 0; i < _proposalTypes.length; i++) { + emit DelegateVotesChanged(_hatterDelegates[i], _proposalTypes[i], _balance, _balance - _transferAmount); + emit DelegateVotesChanged(_catDelegates[i], _proposalTypes[i], 0, _transferAmount); + } + + vm.prank(hatter); + rabbitToken.transfer(cat, _transferAmount); + } + + function _partialDelegate( + string memory _nameHash, + address _account, + uint8[] memory _proposalTypes + ) internal returns (address[] memory, address[] memory) { + // To simply we will divide the voting power into 2 delegates 50% each + uint256 _weightNormalizer = rabbitToken.weightNormalizer(); + uint256 _weight = _weightNormalizer / 2; + + address[] memory _delegates = new address[](_proposalTypes.length); + address[] memory _delegates2 = new address[](_proposalTypes.length); + + for (uint256 i = 0; i < _proposalTypes.length; i++) { + // smart partial delegation + _delegates[i] = makeAddr(string(abi.encodePacked('1', _nameHash, i))); + _delegates2[i] = makeAddr(string(abi.encodePacked('2', _nameHash, i))); + + IWonderVotes.Delegate memory _delegate = IWonderVotes.Delegate({account: _delegates[i], weight: _weight}); + IWonderVotes.Delegate memory _delegate2 = IWonderVotes.Delegate({account: _delegates2[i], weight: _weight}); + IWonderVotes.Delegate[] memory _delegatesStruct = new IWonderVotes.Delegate[](2); + _delegatesStruct[0] = _delegate; + _delegatesStruct[1] = _delegate2; + + vm.prank(_account); + rabbitToken.delegate(_delegatesStruct, _proposalTypes[i]); + } + + return (_delegates, _delegates2); + } + + function test_TransferVotes_SmartAndPartialDelegation(uint128 _balance, uint128 _transferAmount) public { + vm.assume(_balance >= _transferAmount); + uint8[] memory _proposalTypes = rabbitToken.proposalTypes(); + + (address[] memory _hatterDelegates1, address[] memory _hatterDelegates2) = + _partialDelegate('hatter', hatter, _proposalTypes); + (address[] memory _catDelegates1, address[] memory _catDelegates2) = _partialDelegate('cat', cat, _proposalTypes); + + WonderVotesForTest(address(rabbitToken)).mint(hatter, _balance); + + vm.prank(hatter); + rabbitToken.transfer(cat, _transferAmount); + + for (uint256 i = 0; i < _proposalTypes.length; i++) { + assertApproxEqAbs( + rabbitToken.getVotes(_hatterDelegates1[i], _proposalTypes[i]), (_balance - _transferAmount) / 2, 1, '' + ); + assertApproxEqAbs( + rabbitToken.getVotes(_hatterDelegates2[i], _proposalTypes[i]), (_balance - _transferAmount) / 2, 1, '' + ); + + assertApproxEqAbs(rabbitToken.getVotes(_catDelegates1[i], _proposalTypes[i]), _transferAmount / 2, 1, ''); + assertApproxEqAbs(rabbitToken.getVotes(_catDelegates2[i], _proposalTypes[i]), _transferAmount / 2, 1, ''); + } + } +} From 71d224240a42dd7c4411cf1fca538f27befd4cef Mon Sep 17 00:00:00 2001 From: 0xRaccoon Date: Fri, 22 Dec 2023 18:44:14 -0300 Subject: [PATCH 08/16] test: add wonder votes unit tests Signed-off-by: 0xRaccoon --- solidity/test/unit/WonderVotes.t.sol | 481 ++++++++++++++++++++++++++- 1 file changed, 476 insertions(+), 5 deletions(-) diff --git a/solidity/test/unit/WonderVotes.t.sol b/solidity/test/unit/WonderVotes.t.sol index 62e911b..a87209a 100644 --- a/solidity/test/unit/WonderVotes.t.sol +++ b/solidity/test/unit/WonderVotes.t.sol @@ -191,6 +191,14 @@ contract Unit_Delegate_Smart is BaseTest { assertEq(rabbitToken.getVotes(hatter, _proposalTypes[i]), 0); assertEq(rabbitToken.getVotes(_delegates[i], _proposalTypes[i]), _amount); } + + for (uint256 i = 0; i < _proposalTypes.length; i++) { + for (uint256 j = 0; j < _delegates.length; j++) { + if (j != i) { + assertEq(rabbitToken.getVotes(_delegates[j], _proposalTypes[i]), 0); + } + } + } } function test_Minting_SmartDelegation_After(uint128 _amount) public { @@ -214,6 +222,14 @@ contract Unit_Delegate_Smart is BaseTest { assertEq(rabbitToken.getVotes(hatter, _proposalTypes[i]), 0); assertEq(rabbitToken.getVotes(_delegates[i], _proposalTypes[i]), _amount); } + + for (uint256 i = 0; i < _proposalTypes.length; i++) { + for (uint256 j = 0; j < _delegates.length; j++) { + if (j != i) { + assertEq(rabbitToken.getVotes(_delegates[j], _proposalTypes[i]), 0); + } + } + } } function test_Minting_SmartDelegation_Changes(uint128 _amount) public { @@ -251,6 +267,15 @@ contract Unit_Delegate_Smart is BaseTest { assertEq(rabbitToken.getVotes(_delegates[i], _proposalTypes[i]), 0); assertEq(rabbitToken.getVotes(_delegatesChange[i], _proposalTypes[i]), _amount); } + + for (uint256 i = 0; i < _proposalTypes.length; i++) { + for (uint256 j = 0; j < _delegates.length; j++) { + if (j != i) { + assertEq(rabbitToken.getVotes(_delegates[j], _proposalTypes[i]), 0); + assertEq(rabbitToken.getVotes(_delegatesChange[j], _proposalTypes[i]), 0); + } + } + } } function test_Revert_InvalidProposalType(uint8 _proposalType) public { @@ -315,12 +340,18 @@ contract Unit_Delegate_SmartAndPartial is BaseTest { for (uint256 i = 0; i < _proposalTypes.length; i++) { assertEq(rabbitToken.getVotes(hatter, _proposalTypes[i]), 0); - emit log_uint(rabbitToken.getVotes(_delegates[i], _proposalTypes[i])); - emit log_uint(rabbitToken.getVotes(_delegates2[i], _proposalTypes[i])); - assertEq(rabbitToken.getVotes(_delegates[i], _proposalTypes[i]), _amount / 2); assertEq(rabbitToken.getVotes(_delegates2[i], _proposalTypes[i]), _amount / 2); } + + for (uint256 i = 0; i < _proposalTypes.length; i++) { + for (uint256 j = 0; j < _delegates.length; j++) { + if (j != i) { + assertEq(rabbitToken.getVotes(_delegates[j], _proposalTypes[i]), 0); + assertEq(rabbitToken.getVotes(_delegates2[j], _proposalTypes[i]), 0); + } + } + } } function test_Minting_SmartAndPartialDelegation_After(uint128 _amount) public { @@ -353,12 +384,19 @@ contract Unit_Delegate_SmartAndPartial is BaseTest { for (uint256 i = 0; i < _proposalTypes.length; i++) { assertEq(rabbitToken.getVotes(hatter, _proposalTypes[i]), 0); - emit log_uint(rabbitToken.getVotes(_delegates[i], _proposalTypes[i])); - emit log_uint(rabbitToken.getVotes(_delegates2[i], _proposalTypes[i])); assertEq(rabbitToken.getVotes(_delegates[i], _proposalTypes[i]), _amount / 2); assertEq(rabbitToken.getVotes(_delegates2[i], _proposalTypes[i]), _amount / 2); } + + for (uint256 i = 0; i < _proposalTypes.length; i++) { + for (uint256 j = 0; j < _delegates.length; j++) { + if (j != i) { + assertEq(rabbitToken.getVotes(_delegates[j], _proposalTypes[i]), 0); + assertEq(rabbitToken.getVotes(_delegates2[j], _proposalTypes[i]), 0); + } + } + } } function test_Revert_InvalidProposalType(uint8 _proposalType) public { @@ -522,6 +560,15 @@ contract Unit_TransferVotes is BaseTest { assertEq(rabbitToken.getVotes(_hatterDelegates[i], _proposalTypes[i]), _balance - _transferAmount); assertEq(rabbitToken.getVotes(_catDelegates[i], _proposalTypes[i]), _transferAmount); } + + for (uint256 i = 0; i < _proposalTypes.length; i++) { + for (uint256 j = 0; j < _catDelegates.length; j++) { + if (j != i) { + assertEq(rabbitToken.getVotes(_hatterDelegates[j], _proposalTypes[i]), 0); + assertEq(rabbitToken.getVotes(_catDelegates[j], _proposalTypes[i]), 0); + } + } + } } function test_TransferVotes_SmartDelegation_Emits_DelegateVotesChanged( @@ -613,5 +660,429 @@ contract Unit_TransferVotes is BaseTest { assertApproxEqAbs(rabbitToken.getVotes(_catDelegates1[i], _proposalTypes[i]), _transferAmount / 2, 1, ''); assertApproxEqAbs(rabbitToken.getVotes(_catDelegates2[i], _proposalTypes[i]), _transferAmount / 2, 1, ''); } + + for (uint256 i = 0; i < _proposalTypes.length; i++) { + for (uint256 j = 0; j < _catDelegates1.length; j++) { + if (j != i) { + assertEq(rabbitToken.getVotes(_hatterDelegates1[j], _proposalTypes[i]), 0); + assertEq(rabbitToken.getVotes(_hatterDelegates2[j], _proposalTypes[i]), 0); + + assertEq(rabbitToken.getVotes(_catDelegates1[j], _proposalTypes[i]), 0); + assertEq(rabbitToken.getVotes(_catDelegates2[j], _proposalTypes[i]), 0); + } + } + } + } +} + +contract Unit_GetPastVotes is BaseTest { + function setUp() public override { + super.setUp(); + + // To track votes, if not it will be always 0 + vm.prank(hatter); + rabbitToken.delegate(hatter); + vm.prank(cat); + rabbitToken.delegate(cat); + } + + // Simple delegation + function test_GetPastVotes_After_Mint(uint128 _previousBalance, uint128 _addBalance) public { + uint8[] memory _proposalTypes = rabbitToken.proposalTypes(); + + WonderVotesForTest(address(rabbitToken)).mint(hatter, _previousBalance); + vm.roll(block.number + 1); + + WonderVotesForTest(address(rabbitToken)).mint(hatter, _addBalance); + vm.roll(block.number + 1); + + for (uint256 i = 0; i < _proposalTypes.length; i++) { + assertEq(rabbitToken.getPastVotes(hatter, _proposalTypes[i], block.number - 2), _previousBalance); + assertEq( + rabbitToken.getPastVotes(hatter, _proposalTypes[i], block.number - 1), uint256(_previousBalance) + _addBalance + ); + } + } + + function test_GetPastVotes_After_Burn(uint128 _previousBalance, uint128 _subsBalance) public { + vm.assume(_previousBalance >= _subsBalance); + uint8[] memory _proposalTypes = rabbitToken.proposalTypes(); + + WonderVotesForTest(address(rabbitToken)).mint(hatter, _previousBalance); + vm.roll(block.number + 1); + + vm.prank(hatter); + WonderVotesForTest(address(rabbitToken)).burn(_subsBalance); + vm.roll(block.number + 1); + + for (uint256 i = 0; i < _proposalTypes.length; i++) { + assertEq(rabbitToken.getPastVotes(hatter, _proposalTypes[i], block.number - 2), _previousBalance); + assertEq(rabbitToken.getPastVotes(hatter, _proposalTypes[i], block.number - 1), _previousBalance - _subsBalance); + } + } + + function test_GetPastVotes_After_Transfer(uint128 _previousBalance, uint128 _addBalance) public { + uint8[] memory _proposalTypes = rabbitToken.proposalTypes(); + + WonderVotesForTest(address(rabbitToken)).mint(hatter, _previousBalance); + WonderVotesForTest(address(rabbitToken)).mint(cat, _addBalance); + + vm.roll(block.number + 1); + + vm.prank(cat); + rabbitToken.transfer(hatter, _addBalance); + vm.roll(block.number + 1); + + for (uint256 i = 0; i < _proposalTypes.length; i++) { + assertEq(rabbitToken.getPastVotes(hatter, _proposalTypes[i], block.number - 2), _previousBalance); + assertEq( + rabbitToken.getPastVotes(hatter, _proposalTypes[i], block.number - 1), uint256(_previousBalance) + _addBalance + ); + + assertEq(rabbitToken.getPastVotes(cat, _proposalTypes[i], block.number - 2), _addBalance); + assertEq(rabbitToken.getPastVotes(cat, _proposalTypes[i], block.number - 1), 0); + } + } + + // Smart Delegation + function test_GetPastVotes_After_Mint_SmartDelegation(uint128 _previousBalance, uint128 _addBalance) public { + uint8[] memory _proposalTypes = rabbitToken.proposalTypes(); + + address[] memory _delegates = new address[](_proposalTypes.length); + + for (uint256 i = 0; i < _proposalTypes.length; i++) { + _delegates[i] = makeAddr(string(abi.encodePacked('delegate', i))); + + // 100% voting power to the delegate for the proposalType + vm.prank(hatter); + rabbitToken.delegate(_delegates[i], _proposalTypes[i]); + } + + WonderVotesForTest(address(rabbitToken)).mint(hatter, _previousBalance); + vm.roll(block.number + 1); + + WonderVotesForTest(address(rabbitToken)).mint(hatter, _addBalance); + vm.roll(block.number + 1); + + for (uint256 i = 0; i < _proposalTypes.length; i++) { + assertEq(rabbitToken.getPastVotes(hatter, _proposalTypes[i], block.number - 2), 0); + assertEq(rabbitToken.getPastVotes(hatter, _proposalTypes[i], block.number - 2), 0); + + assertEq(rabbitToken.getPastVotes(_delegates[i], _proposalTypes[i], block.number - 2), _previousBalance); + assertEq( + rabbitToken.getPastVotes(_delegates[i], _proposalTypes[i], block.number - 1), + uint256(_previousBalance) + _addBalance + ); + } + } + + function test_GetPastVotes_After_Burn_SmartDelegation(uint128 _previousBalance, uint128 _subsBalance) public { + vm.assume(_previousBalance >= _subsBalance); + uint8[] memory _proposalTypes = rabbitToken.proposalTypes(); + + address[] memory _delegates = new address[](_proposalTypes.length); + + for (uint256 i = 0; i < _proposalTypes.length; i++) { + _delegates[i] = makeAddr(string(abi.encodePacked('delegate', i))); + + // 100% voting power to the delegate for the proposalType + vm.prank(hatter); + rabbitToken.delegate(_delegates[i], _proposalTypes[i]); + } + + WonderVotesForTest(address(rabbitToken)).mint(hatter, _previousBalance); + vm.roll(block.number + 1); + + vm.prank(hatter); + WonderVotesForTest(address(rabbitToken)).burn(_subsBalance); + vm.roll(block.number + 1); + + for (uint256 i = 0; i < _proposalTypes.length; i++) { + assertEq(rabbitToken.getPastVotes(hatter, _proposalTypes[i], block.number - 2), 0); + assertEq(rabbitToken.getPastVotes(hatter, _proposalTypes[i], block.number - 1), 0); + + assertEq(rabbitToken.getPastVotes(_delegates[i], _proposalTypes[i], block.number - 2), _previousBalance); + assertEq( + rabbitToken.getPastVotes(_delegates[i], _proposalTypes[i], block.number - 1), _previousBalance - _subsBalance + ); + } + + for (uint256 i = 0; i < _proposalTypes.length; i++) { + for (uint256 j = 0; j < _delegates.length; j++) { + if (j != i) { + assertEq(rabbitToken.getPastVotes(_delegates[j], _proposalTypes[i], block.number - 2), 0); + assertEq(rabbitToken.getPastVotes(_delegates[j], _proposalTypes[i], block.number - 1), 0); + } + } + } + } + + function test_GetPastVotes_After_Transfer_SmartDelegation(uint128 _previousBalance, uint128 _addBalance) public { + uint8[] memory _proposalTypes = rabbitToken.proposalTypes(); + address[] memory _delegates = new address[](_proposalTypes.length); + + for (uint256 i = 0; i < _proposalTypes.length; i++) { + _delegates[i] = makeAddr(string(abi.encodePacked('delegate', i))); + + // 100% voting power to the delegate for the proposalType + vm.prank(hatter); + rabbitToken.delegate(_delegates[i], _proposalTypes[i]); + } + + WonderVotesForTest(address(rabbitToken)).mint(hatter, _previousBalance); + WonderVotesForTest(address(rabbitToken)).mint(cat, _addBalance); + + vm.roll(block.number + 1); + + vm.prank(cat); + rabbitToken.transfer(hatter, _addBalance); + vm.roll(block.number + 1); + + for (uint256 i = 0; i < _proposalTypes.length; i++) { + assertEq(rabbitToken.getPastVotes(hatter, _proposalTypes[i], block.number - 2), 0); + assertEq(rabbitToken.getPastVotes(hatter, _proposalTypes[i], block.number - 1), 0); + + assertEq(rabbitToken.getPastVotes(_delegates[i], _proposalTypes[i], block.number - 2), _previousBalance); + assertEq( + rabbitToken.getPastVotes(_delegates[i], _proposalTypes[i], block.number - 1), + uint256(_previousBalance) + _addBalance + ); + + assertEq(rabbitToken.getPastVotes(cat, _proposalTypes[i], block.number - 2), _addBalance); + assertEq(rabbitToken.getPastVotes(cat, _proposalTypes[i], block.number - 1), 0); + } + + for (uint256 i = 0; i < _proposalTypes.length; i++) { + for (uint256 j = 0; j < _delegates.length; j++) { + if (j != i) { + assertEq(rabbitToken.getPastVotes(_delegates[j], _proposalTypes[i], block.number - 2), 0); + assertEq(rabbitToken.getPastVotes(_delegates[j], _proposalTypes[i], block.number - 1), 0); + } + } + } + } + + // Smart and partial delegation + function test_GetPastVotes_After_Mint_SmartAndPartialDelegation(uint128 _previousBalance, uint128 _addBalance) public { + uint8[] memory _proposalTypes = rabbitToken.proposalTypes(); + + // 2 delegates 50% each + uint256 _weightNormalizer = rabbitToken.weightNormalizer(); + uint256 _weight = _weightNormalizer / 2; + + address[] memory _delegates = new address[](_proposalTypes.length); + address[] memory _delegates2 = new address[](_proposalTypes.length); + + vm.startPrank(hatter); + for (uint256 i = 0; i < _proposalTypes.length; i++) { + _delegates[i] = makeAddr(string(abi.encodePacked('delegate', i))); + _delegates2[i] = makeAddr(string(abi.encodePacked('delegate2', i))); + + IWonderVotes.Delegate memory _delegate = IWonderVotes.Delegate({account: _delegates[i], weight: _weight}); + IWonderVotes.Delegate memory _delegate2 = IWonderVotes.Delegate({account: _delegates2[i], weight: _weight}); + IWonderVotes.Delegate[] memory _delegatesStruct = new IWonderVotes.Delegate[](2); + _delegatesStruct[0] = _delegate; + _delegatesStruct[1] = _delegate2; + + rabbitToken.delegate(_delegatesStruct, _proposalTypes[i]); + } + + vm.stopPrank(); + + WonderVotesForTest(address(rabbitToken)).mint(hatter, _previousBalance); + vm.roll(block.number + 1); + + WonderVotesForTest(address(rabbitToken)).mint(hatter, _addBalance); + vm.roll(block.number + 1); + + for (uint256 i = 0; i < _proposalTypes.length; i++) { + assertEq(rabbitToken.getPastVotes(hatter, _proposalTypes[i], block.number - 2), 0); + assertEq(rabbitToken.getPastVotes(hatter, _proposalTypes[i], block.number - 1), 0); + + assertApproxEqAbs( + rabbitToken.getPastVotes(_delegates[i], _proposalTypes[i], block.number - 2), _previousBalance / 2, 1, '' + ); + assertApproxEqAbs( + rabbitToken.getPastVotes(_delegates2[i], _proposalTypes[i], block.number - 2), _previousBalance / 2, 1, '' + ); + + assertApproxEqAbs( + rabbitToken.getPastVotes(_delegates[i], _proposalTypes[i], block.number - 1), + (uint256(_previousBalance) + _addBalance) / 2, + 1, + '' + ); + assertApproxEqAbs( + rabbitToken.getPastVotes(_delegates2[i], _proposalTypes[i], block.number - 1), + (uint256(_previousBalance) + _addBalance) / 2, + 1, + '' + ); + } + + for (uint256 i = 0; i < _proposalTypes.length; i++) { + for (uint256 j = 0; j < _delegates.length; j++) { + if (j != i) { + assertEq(rabbitToken.getPastVotes(_delegates[j], _proposalTypes[i], block.number - 2), 0); + assertEq(rabbitToken.getPastVotes(_delegates[j], _proposalTypes[i], block.number - 1), 0); + + assertEq(rabbitToken.getPastVotes(_delegates2[j], _proposalTypes[i], block.number - 2), 0); + assertEq(rabbitToken.getPastVotes(_delegates2[j], _proposalTypes[i], block.number - 1), 0); + } + } + } + } + + function test_GetPastVotes_After_Burn_SmartAndPartialDelegation( + uint128 _previousBalance, + uint128 _subsBalance + ) public { + vm.assume(_previousBalance >= _subsBalance); + uint8[] memory _proposalTypes = rabbitToken.proposalTypes(); + + // 2 delegates 50% each + uint256 _weightNormalizer = rabbitToken.weightNormalizer(); + uint256 _weight = _weightNormalizer / 2; + + address[] memory _delegates = new address[](_proposalTypes.length); + address[] memory _delegates2 = new address[](_proposalTypes.length); + + vm.startPrank(hatter); + for (uint256 i = 0; i < _proposalTypes.length; i++) { + _delegates[i] = makeAddr(string(abi.encodePacked('delegate', i))); + _delegates2[i] = makeAddr(string(abi.encodePacked('delegate2', i))); + + IWonderVotes.Delegate memory _delegate = IWonderVotes.Delegate({account: _delegates[i], weight: _weight}); + IWonderVotes.Delegate memory _delegate2 = IWonderVotes.Delegate({account: _delegates2[i], weight: _weight}); + IWonderVotes.Delegate[] memory _delegatesStruct = new IWonderVotes.Delegate[](2); + _delegatesStruct[0] = _delegate; + _delegatesStruct[1] = _delegate2; + + rabbitToken.delegate(_delegatesStruct, _proposalTypes[i]); + } + + vm.stopPrank(); + + WonderVotesForTest(address(rabbitToken)).mint(hatter, _previousBalance); + vm.roll(block.number + 1); + + vm.prank(hatter); + WonderVotesForTest(address(rabbitToken)).burn(_subsBalance); + vm.roll(block.number + 1); + + for (uint256 i = 0; i < _proposalTypes.length; i++) { + assertEq(rabbitToken.getPastVotes(hatter, _proposalTypes[i], block.number - 2), 0); + assertEq(rabbitToken.getPastVotes(hatter, _proposalTypes[i], block.number - 1), 0); + + assertApproxEqAbs( + rabbitToken.getPastVotes(_delegates[i], _proposalTypes[i], block.number - 2), _previousBalance / 2, 1, '' + ); + assertApproxEqAbs( + rabbitToken.getPastVotes(_delegates2[i], _proposalTypes[i], block.number - 2), _previousBalance / 2, 1, '' + ); + + assertApproxEqAbs( + rabbitToken.getPastVotes(_delegates[i], _proposalTypes[i], block.number - 1), + (uint256(_previousBalance) - _subsBalance) / 2, + 1, + '' + ); + assertApproxEqAbs( + rabbitToken.getPastVotes(_delegates2[i], _proposalTypes[i], block.number - 1), + (uint256(_previousBalance) - _subsBalance) / 2, + 1, + '' + ); + } + + for (uint256 i = 0; i < _proposalTypes.length; i++) { + for (uint256 j = 0; j < _delegates.length; j++) { + if (j != i) { + assertEq(rabbitToken.getPastVotes(_delegates[j], _proposalTypes[i], block.number - 2), 0); + assertEq(rabbitToken.getPastVotes(_delegates[j], _proposalTypes[i], block.number - 1), 0); + + assertEq(rabbitToken.getPastVotes(_delegates2[j], _proposalTypes[i], block.number - 2), 0); + assertEq(rabbitToken.getPastVotes(_delegates2[j], _proposalTypes[i], block.number - 1), 0); + } + } + } + } + + function test_GetPastVotes_After_Transfer_SmartAndPartialDelegation( + uint128 _previousBalance, + uint128 _addBalance + ) public { + uint8[] memory _proposalTypes = rabbitToken.proposalTypes(); + + // 2 delegates 50% each + uint256 _weightNormalizer = rabbitToken.weightNormalizer(); + uint256 _weight = _weightNormalizer / 2; + + address[] memory _delegates = new address[](_proposalTypes.length); + address[] memory _delegates2 = new address[](_proposalTypes.length); + + vm.startPrank(hatter); + for (uint256 i = 0; i < _proposalTypes.length; i++) { + _delegates[i] = makeAddr(string(abi.encodePacked('delegate', i))); + _delegates2[i] = makeAddr(string(abi.encodePacked('delegate2', i))); + + IWonderVotes.Delegate memory _delegate = IWonderVotes.Delegate({account: _delegates[i], weight: _weight}); + IWonderVotes.Delegate memory _delegate2 = IWonderVotes.Delegate({account: _delegates2[i], weight: _weight}); + IWonderVotes.Delegate[] memory _delegatesStruct = new IWonderVotes.Delegate[](2); + _delegatesStruct[0] = _delegate; + _delegatesStruct[1] = _delegate2; + + rabbitToken.delegate(_delegatesStruct, _proposalTypes[i]); + } + vm.stopPrank(); + + WonderVotesForTest(address(rabbitToken)).mint(hatter, _previousBalance); + WonderVotesForTest(address(rabbitToken)).mint(cat, _addBalance); + + vm.roll(block.number + 1); + + vm.prank(cat); + rabbitToken.transfer(hatter, _addBalance); + vm.roll(block.number + 1); + + for (uint256 i = 0; i < _proposalTypes.length; i++) { + assertEq(rabbitToken.getPastVotes(hatter, _proposalTypes[i], block.number - 2), 0); + assertEq(rabbitToken.getPastVotes(hatter, _proposalTypes[i], block.number - 1), 0); + + assertApproxEqAbs( + rabbitToken.getPastVotes(_delegates[i], _proposalTypes[i], block.number - 2), _previousBalance / 2, 1, '' + ); + assertApproxEqAbs( + rabbitToken.getPastVotes(_delegates2[i], _proposalTypes[i], block.number - 2), _previousBalance / 2, 1, '' + ); + + assertApproxEqAbs( + rabbitToken.getPastVotes(_delegates[i], _proposalTypes[i], block.number - 1), + (uint256(_previousBalance) + _addBalance) / 2, + 1, + '' + ); + assertApproxEqAbs( + rabbitToken.getPastVotes(_delegates2[i], _proposalTypes[i], block.number - 1), + (uint256(_previousBalance) + _addBalance) / 2, + 1, + '' + ); + + assertEq(rabbitToken.getPastVotes(cat, _proposalTypes[i], block.number - 2), _addBalance); + assertEq(rabbitToken.getPastVotes(cat, _proposalTypes[i], block.number - 1), 0); + } + + for (uint256 i = 0; i < _proposalTypes.length; i++) { + for (uint256 j = 0; j < _delegates.length; j++) { + if (j != i) { + assertEq(rabbitToken.getPastVotes(_delegates[j], _proposalTypes[i], block.number - 2), 0); + assertEq(rabbitToken.getPastVotes(_delegates[j], _proposalTypes[i], block.number - 1), 0); + + assertEq(rabbitToken.getPastVotes(_delegates2[j], _proposalTypes[i], block.number - 2), 0); + assertEq(rabbitToken.getPastVotes(_delegates2[j], _proposalTypes[i], block.number - 1), 0); + } + } + } } } From 3856d2d16e9a500a383d21eb2c37649764bd8283 Mon Sep 17 00:00:00 2001 From: 0xRaccoon Date: Fri, 22 Dec 2023 18:59:30 -0300 Subject: [PATCH 09/16] test: add wonder votes unit tests Signed-off-by: 0xRaccoon --- solidity/test/unit/WonderVotes.t.sol | 152 +++++++-------------------- 1 file changed, 37 insertions(+), 115 deletions(-) diff --git a/solidity/test/unit/WonderVotes.t.sol b/solidity/test/unit/WonderVotes.t.sol index a87209a..03bec0d 100644 --- a/solidity/test/unit/WonderVotes.t.sol +++ b/solidity/test/unit/WonderVotes.t.sol @@ -52,32 +52,6 @@ contract BaseTest is Test { function _expectEmit(address _contract) internal { vm.expectEmit(true, true, true, true, _contract); } - - function _createProposal( - uint8 _proposalType, - address _target, - uint256 _value, - bytes memory _calldata, - string memory _description, - uint256 _proposerVotes - ) internal returns (uint256) { - vm.assume(_proposalType < governor.proposalTypes().length); - vm.assume(_proposerVotes >= governor.proposalThreshold(_proposalType)); - - _mockGetPastVotes(hatter, _proposalType, block.number - 1, _proposerVotes); - - address[] memory _targets = new address[](1); - _targets[0] = _target; - - uint256[] memory _values = new uint256[](1); - _values[0] = _value; - - bytes[] memory _calldatas = new bytes[](1); - _calldatas[0] = _calldata; - - vm.prank(hatter); - return governor.propose(_proposalType, _targets, _values, _calldatas, _description); - } } contract Unit_Delegate_Simple is BaseTest { @@ -676,6 +650,15 @@ contract Unit_TransferVotes is BaseTest { } contract Unit_GetPastVotes is BaseTest { + uint8[] internal _proposalTypes; + + // 2 delegates 50% each + uint256 internal _weightNormalizer; + uint256 internal _weight; + + address[] internal _delegates; + address[] internal _delegates2; + function setUp() public override { super.setUp(); @@ -684,12 +667,19 @@ contract Unit_GetPastVotes is BaseTest { rabbitToken.delegate(hatter); vm.prank(cat); rabbitToken.delegate(cat); + + _proposalTypes = rabbitToken.proposalTypes(); + + // 2 delegates 50% each + _weightNormalizer = rabbitToken.weightNormalizer(); + _weight = _weightNormalizer / 2; + + _delegates = new address[](_proposalTypes.length); + _delegates2 = new address[](_proposalTypes.length); } // Simple delegation function test_GetPastVotes_After_Mint(uint128 _previousBalance, uint128 _addBalance) public { - uint8[] memory _proposalTypes = rabbitToken.proposalTypes(); - WonderVotesForTest(address(rabbitToken)).mint(hatter, _previousBalance); vm.roll(block.number + 1); @@ -706,7 +696,6 @@ contract Unit_GetPastVotes is BaseTest { function test_GetPastVotes_After_Burn(uint128 _previousBalance, uint128 _subsBalance) public { vm.assume(_previousBalance >= _subsBalance); - uint8[] memory _proposalTypes = rabbitToken.proposalTypes(); WonderVotesForTest(address(rabbitToken)).mint(hatter, _previousBalance); vm.roll(block.number + 1); @@ -722,8 +711,6 @@ contract Unit_GetPastVotes is BaseTest { } function test_GetPastVotes_After_Transfer(uint128 _previousBalance, uint128 _addBalance) public { - uint8[] memory _proposalTypes = rabbitToken.proposalTypes(); - WonderVotesForTest(address(rabbitToken)).mint(hatter, _previousBalance); WonderVotesForTest(address(rabbitToken)).mint(cat, _addBalance); @@ -744,19 +731,21 @@ contract Unit_GetPastVotes is BaseTest { } } - // Smart Delegation - function test_GetPastVotes_After_Mint_SmartDelegation(uint128 _previousBalance, uint128 _addBalance) public { - uint8[] memory _proposalTypes = rabbitToken.proposalTypes(); - - address[] memory _delegates = new address[](_proposalTypes.length); + function _smartDelegate() internal { + vm.startPrank(hatter); for (uint256 i = 0; i < _proposalTypes.length; i++) { _delegates[i] = makeAddr(string(abi.encodePacked('delegate', i))); // 100% voting power to the delegate for the proposalType - vm.prank(hatter); rabbitToken.delegate(_delegates[i], _proposalTypes[i]); } + vm.stopPrank(); + } + + // Smart Delegation + function test_GetPastVotes_After_Mint_SmartDelegation(uint128 _previousBalance, uint128 _addBalance) public { + _smartDelegate(); WonderVotesForTest(address(rabbitToken)).mint(hatter, _previousBalance); vm.roll(block.number + 1); @@ -778,17 +767,8 @@ contract Unit_GetPastVotes is BaseTest { function test_GetPastVotes_After_Burn_SmartDelegation(uint128 _previousBalance, uint128 _subsBalance) public { vm.assume(_previousBalance >= _subsBalance); - uint8[] memory _proposalTypes = rabbitToken.proposalTypes(); - address[] memory _delegates = new address[](_proposalTypes.length); - - for (uint256 i = 0; i < _proposalTypes.length; i++) { - _delegates[i] = makeAddr(string(abi.encodePacked('delegate', i))); - - // 100% voting power to the delegate for the proposalType - vm.prank(hatter); - rabbitToken.delegate(_delegates[i], _proposalTypes[i]); - } + _smartDelegate(); WonderVotesForTest(address(rabbitToken)).mint(hatter, _previousBalance); vm.roll(block.number + 1); @@ -818,16 +798,7 @@ contract Unit_GetPastVotes is BaseTest { } function test_GetPastVotes_After_Transfer_SmartDelegation(uint128 _previousBalance, uint128 _addBalance) public { - uint8[] memory _proposalTypes = rabbitToken.proposalTypes(); - address[] memory _delegates = new address[](_proposalTypes.length); - - for (uint256 i = 0; i < _proposalTypes.length; i++) { - _delegates[i] = makeAddr(string(abi.encodePacked('delegate', i))); - - // 100% voting power to the delegate for the proposalType - vm.prank(hatter); - rabbitToken.delegate(_delegates[i], _proposalTypes[i]); - } + _smartDelegate(); WonderVotesForTest(address(rabbitToken)).mint(hatter, _previousBalance); WonderVotesForTest(address(rabbitToken)).mint(cat, _addBalance); @@ -862,18 +833,9 @@ contract Unit_GetPastVotes is BaseTest { } } - // Smart and partial delegation - function test_GetPastVotes_After_Mint_SmartAndPartialDelegation(uint128 _previousBalance, uint128 _addBalance) public { - uint8[] memory _proposalTypes = rabbitToken.proposalTypes(); - - // 2 delegates 50% each - uint256 _weightNormalizer = rabbitToken.weightNormalizer(); - uint256 _weight = _weightNormalizer / 2; - - address[] memory _delegates = new address[](_proposalTypes.length); - address[] memory _delegates2 = new address[](_proposalTypes.length); - + function _smartAndPartialDelegate() internal { vm.startPrank(hatter); + for (uint256 i = 0; i < _proposalTypes.length; i++) { _delegates[i] = makeAddr(string(abi.encodePacked('delegate', i))); _delegates2[i] = makeAddr(string(abi.encodePacked('delegate2', i))); @@ -886,8 +848,12 @@ contract Unit_GetPastVotes is BaseTest { rabbitToken.delegate(_delegatesStruct, _proposalTypes[i]); } - vm.stopPrank(); + } + + // Smart and partial delegation + function test_GetPastVotes_After_Mint_SmartAndPartialDelegation(uint128 _previousBalance, uint128 _addBalance) public { + _smartAndPartialDelegate(); WonderVotesForTest(address(rabbitToken)).mint(hatter, _previousBalance); vm.roll(block.number + 1); @@ -938,30 +904,8 @@ contract Unit_GetPastVotes is BaseTest { uint128 _subsBalance ) public { vm.assume(_previousBalance >= _subsBalance); - uint8[] memory _proposalTypes = rabbitToken.proposalTypes(); - // 2 delegates 50% each - uint256 _weightNormalizer = rabbitToken.weightNormalizer(); - uint256 _weight = _weightNormalizer / 2; - - address[] memory _delegates = new address[](_proposalTypes.length); - address[] memory _delegates2 = new address[](_proposalTypes.length); - - vm.startPrank(hatter); - for (uint256 i = 0; i < _proposalTypes.length; i++) { - _delegates[i] = makeAddr(string(abi.encodePacked('delegate', i))); - _delegates2[i] = makeAddr(string(abi.encodePacked('delegate2', i))); - - IWonderVotes.Delegate memory _delegate = IWonderVotes.Delegate({account: _delegates[i], weight: _weight}); - IWonderVotes.Delegate memory _delegate2 = IWonderVotes.Delegate({account: _delegates2[i], weight: _weight}); - IWonderVotes.Delegate[] memory _delegatesStruct = new IWonderVotes.Delegate[](2); - _delegatesStruct[0] = _delegate; - _delegatesStruct[1] = _delegate2; - - rabbitToken.delegate(_delegatesStruct, _proposalTypes[i]); - } - - vm.stopPrank(); + _smartAndPartialDelegate(); WonderVotesForTest(address(rabbitToken)).mint(hatter, _previousBalance); vm.roll(block.number + 1); @@ -1012,29 +956,7 @@ contract Unit_GetPastVotes is BaseTest { uint128 _previousBalance, uint128 _addBalance ) public { - uint8[] memory _proposalTypes = rabbitToken.proposalTypes(); - - // 2 delegates 50% each - uint256 _weightNormalizer = rabbitToken.weightNormalizer(); - uint256 _weight = _weightNormalizer / 2; - - address[] memory _delegates = new address[](_proposalTypes.length); - address[] memory _delegates2 = new address[](_proposalTypes.length); - - vm.startPrank(hatter); - for (uint256 i = 0; i < _proposalTypes.length; i++) { - _delegates[i] = makeAddr(string(abi.encodePacked('delegate', i))); - _delegates2[i] = makeAddr(string(abi.encodePacked('delegate2', i))); - - IWonderVotes.Delegate memory _delegate = IWonderVotes.Delegate({account: _delegates[i], weight: _weight}); - IWonderVotes.Delegate memory _delegate2 = IWonderVotes.Delegate({account: _delegates2[i], weight: _weight}); - IWonderVotes.Delegate[] memory _delegatesStruct = new IWonderVotes.Delegate[](2); - _delegatesStruct[0] = _delegate; - _delegatesStruct[1] = _delegate2; - - rabbitToken.delegate(_delegatesStruct, _proposalTypes[i]); - } - vm.stopPrank(); + _smartAndPartialDelegate(); WonderVotesForTest(address(rabbitToken)).mint(hatter, _previousBalance); WonderVotesForTest(address(rabbitToken)).mint(cat, _addBalance); From fbeccaf03fdb5a01b0e6b24c88a99c02b55d87c7 Mon Sep 17 00:00:00 2001 From: 0xRaccoon Date: Tue, 26 Dec 2023 20:14:31 -0300 Subject: [PATCH 10/16] test: add integration tests Signed-off-by: 0xRaccoon --- .env.example | 2 + foundry.toml | 3 +- solidity/examples/AliceGovernor.sol | 1 + solidity/test/integration/Delegation.t.sol | 152 ++++++++++++++++++ .../test/integration/IntegrationBase.t.sol | 72 +++++++++ solidity/test/integration/Propose.t.sol | 87 ++++++++++ solidity/test/smock/SmockHelper.sol | 2 +- .../test/smock/examples/MockAliceGovernor.sol | 4 +- .../test/smock/examples/MockRabbitToken.sol | 4 +- solidity/test/unit/WonderGovernor.t.sol | 20 ++- solidity/test/unit/WonderVotes.t.sol | 11 +- solidity/test/utils/TestExtended.sol | 21 +++ 12 files changed, 361 insertions(+), 18 deletions(-) create mode 100644 solidity/test/integration/Delegation.t.sol create mode 100644 solidity/test/integration/IntegrationBase.t.sol create mode 100644 solidity/test/integration/Propose.t.sol create mode 100644 solidity/test/utils/TestExtended.sol diff --git a/.env.example b/.env.example index aa5efdc..5b6781a 100644 --- a/.env.example +++ b/.env.example @@ -5,3 +5,5 @@ GOERLI_RPC= GOERLI_DEPLOYER_PK= ETHERSCAN_API_KEY= + +OPTIMISM_RPC= \ No newline at end of file diff --git a/foundry.toml b/foundry.toml index 3f6d2a0..f2d2201 100644 --- a/foundry.toml +++ b/foundry.toml @@ -31,4 +31,5 @@ src = 'solidity/interfaces/' runs = 1000 [rpc_endpoints] -mainnet = "${MAINNET_RPC}" \ No newline at end of file +mainnet = "${MAINNET_RPC}" +optimism = "${OPTIMISM_RPC}" \ No newline at end of file diff --git a/solidity/examples/AliceGovernor.sol b/solidity/examples/AliceGovernor.sol index 2c2488d..e8e1691 100644 --- a/solidity/examples/AliceGovernor.sol +++ b/solidity/examples/AliceGovernor.sol @@ -85,6 +85,7 @@ contract AliceGovernor is WonderGovernor { uint256 _weight, bytes memory _params ) internal virtual override { + proposalTracks[_proposalId].votes += _weight; if (_support == 0) { proposalTracks[_proposalId].againstVotes += _weight; } else if (_support == 1) { diff --git a/solidity/test/integration/Delegation.t.sol b/solidity/test/integration/Delegation.t.sol new file mode 100644 index 0000000..779863d --- /dev/null +++ b/solidity/test/integration/Delegation.t.sol @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import './IntegrationBase.t.sol'; + +import {WonderVotes} from 'contracts/governance/utils/WonderVotes.sol'; +import {WonderGovernor} from 'contracts/governance/WonderGovernor.sol'; + +contract Integration_Delegation is IntegrationBase { + function test_AllVotersDelegateToProposer() public { + // AllVoters delegates to proposer + for (uint256 _i = 0; _i < VOTERS_NUMBER; _i++) { + address holder = holders[_i]; + vm.prank(holder); + rabbitToken.delegate(proposer); + } + + for (uint256 _i = 0; _i < VOTERS_NUMBER; _i++) { + address holder = holders[_i]; + + for (uint256 _j = 0; _j < governor.proposalTypes().length; _j++) { + uint8 proposalType = governor.proposalTypes()[_j]; + assertEq(rabbitToken.getVotes(holder, proposalType), 0); + } + } + + for (uint256 _j = 0; _j < governor.proposalTypes().length; _j++) { + uint8 proposalType = governor.proposalTypes()[_j]; + assertEq(rabbitToken.getVotes(proposer, proposalType), INITIAL_VOTERS_BALANCE * VOTERS_NUMBER); + } + } + + function test_AllVotersDelegateByProposalType() public { + uint8[] memory proposalTypes = governor.proposalTypes(); + + for (uint256 _i = 0; _i < proposalTypes.length; _i++) { + address _holder = holders[_i]; + vm.prank(_holder); + rabbitToken.delegate(proposer, proposalTypes[_i]); + } + + for (uint256 _i = 0; _i < proposalTypes.length; _i++) { + address _holder = holders[_i]; + assertEq(rabbitToken.getVotes(_holder, proposalTypes[_i]), 0); + assertEq(rabbitToken.getVotes(proposer, proposalTypes[_i]), INITIAL_VOTERS_BALANCE); + } + } + + function test_AllVotersDelegatePartially() public { + uint8[] memory proposalTypes = governor.proposalTypes(); + + // 50% of votes + uint256 _weight = rabbitToken.weightNormalizer() / 2; + + IWonderVotes.Delegate memory _delegate = IWonderVotes.Delegate({account: proposer, weight: _weight}); + IWonderVotes.Delegate memory _delegate2 = IWonderVotes.Delegate({account: proposer2, weight: _weight}); + + IWonderVotes.Delegate[] memory _delegates = new IWonderVotes.Delegate[](2); + _delegates[0] = _delegate; + _delegates[1] = _delegate2; + + for (uint256 _i = 0; _i < proposalTypes.length; _i++) { + for (uint256 _j = 0; _j < VOTERS_NUMBER; _j++) { + address _holder = holders[_j]; + vm.prank(_holder); + rabbitToken.delegate(_delegates, proposalTypes[_i]); + } + } + + for (uint256 _i = 0; _i < proposalTypes.length; _i++) { + assertEq(rabbitToken.getVotes(proposer, proposalTypes[_i]), INITIAL_VOTERS_BALANCE * VOTERS_NUMBER / 2); + assertEq(rabbitToken.getVotes(proposer2, proposalTypes[_i]), INITIAL_VOTERS_BALANCE * VOTERS_NUMBER / 2); + + for (uint256 _j = 0; _j < VOTERS_NUMBER; _j++) { + address _holder = holders[_j]; + assertEq(rabbitToken.getVotes(_holder, proposalTypes[_i]), 0); + } + } + } + + function test_ProposeWithDelegatedVotes() public { + address _voter1 = holders[0]; + + vm.prank(proposer); + rabbitToken.delegate(proposer); + + // delegate to proposer + vm.prank(_voter1); + rabbitToken.delegate(proposer); + + address[] memory _targets = new address[](1); + _targets[0] = address(governor); + + uint256[] memory _values = new uint256[](1); + _values[0] = 1; + + bytes[] memory _calldatas = new bytes[](1); + _calldatas[0] = abi.encode(0); + + string memory _description = 'test proposal'; + + // To propose Governor controls the proposal threshold calling getPastVotes, so we need to mine a block to be able to propose + _mineBlock(); + + uint8[] memory _proposalTypes = governor.proposalTypes(); + + vm.startPrank(proposer); + for (uint256 _i = 0; _i < _proposalTypes.length; _i++) { + uint8 _proposalType = _proposalTypes[_i]; + + uint256 _precomputedProposalId = + governor.hashProposal(_proposalType, _targets, _values, _calldatas, keccak256(bytes(_description))); + _expectEmit(address(governor)); + + emit ProposalCreated( + _precomputedProposalId, + _proposalType, + address(proposer), + _targets, + _values, + new string[](1), + _calldatas, + block.number + 1, + block.number + governor.votingPeriod() + 1, + _description + ); + governor.propose(_proposalType, _targets, _values, _calldatas, _description); + } + vm.stopPrank(); + } + + function test_AllVotersChangeDelegation() public { + uint8[] memory _proposalTypes = governor.proposalTypes(); + + for (uint256 _i = 0; _i < VOTERS_NUMBER; _i++) { + address _holder = holders[_i]; + vm.prank(_holder); + rabbitToken.delegate(proposer); + } + + for (uint256 _i = 0; _i < VOTERS_NUMBER; _i++) { + address _holder = holders[_i]; + vm.prank(_holder); + rabbitToken.delegate(proposer2); + } + + for (uint8 i = 0; i < _proposalTypes.length; i++) { + assertEq(rabbitToken.getVotes(proposer, _proposalTypes[i]), 0); + assertEq(rabbitToken.getVotes(proposer2, _proposalTypes[i]), INITIAL_VOTERS_BALANCE * VOTERS_NUMBER); + } + } +} diff --git a/solidity/test/integration/IntegrationBase.t.sol b/solidity/test/integration/IntegrationBase.t.sol new file mode 100644 index 0000000..8c2765c --- /dev/null +++ b/solidity/test/integration/IntegrationBase.t.sol @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +// solhint-disable no-unused-import +// solhint-disable-next-line no-console +import {console} from 'forge-std/console.sol'; + +import {IERC20} from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; + +import {IWonderVotes} from 'interfaces/governance/utils/IWonderVotes.sol'; +import {IWonderGovernor} from 'interfaces/governance/IWonderGovernor.sol'; + +import {AliceGovernor} from 'examples/AliceGovernor.sol'; +import {RabbitToken} from 'examples/RabbitToken.sol'; + +import {TestExtended} from '../utils/TestExtended.sol'; + +contract IntegrationBase is TestExtended { + uint256 public constant FORK_BLOCK = 111_361_902; + + uint256 internal _initialBalance = 100_000 ether; + + address public deployer = makeAddr('deployer'); + address public proposer = makeAddr('proposer'); + address public proposer2 = makeAddr('proposer2'); + + address[] public holders; + + IWonderVotes public rabbitToken; + IWonderGovernor public governor; + + uint256 public constant INITIAL_VOTERS_BALANCE = 100_000e18; + uint8 public constant VOTERS_NUMBER = 10; + + function setUp() public virtual { + vm.createSelectFork(vm.rpcUrl('optimism'), FORK_BLOCK); + + // Deploy the governance contracts + vm.startPrank(deployer); + + address tokenAddress = vm.computeCreateAddress(deployer, vm.getNonce(deployer) + 1); + governor = new AliceGovernor(tokenAddress); + rabbitToken = new RabbitToken(AliceGovernor(payable(address(governor)))); + + vm.stopPrank(); + + for (uint256 i = 0; i < VOTERS_NUMBER; i++) { + address holder = makeAddr(string(abi.encodePacked('holder', i))); + holders.push(holder); + deal(tokenAddress, holder, INITIAL_VOTERS_BALANCE); + vm.prank(holder); + + // start tracking votes + rabbitToken.delegate(holder); + } + + _mineBlock(); + } + + event ProposalCreated( + uint256 proposalId, + uint8 proposalType, + address proposer, + address[] targets, + uint256[] values, + string[] signatures, + bytes[] calldatas, + uint256 voteStart, + uint256 voteEnd, + string description + ); +} diff --git a/solidity/test/integration/Propose.t.sol b/solidity/test/integration/Propose.t.sol new file mode 100644 index 0000000..57087c7 --- /dev/null +++ b/solidity/test/integration/Propose.t.sol @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import './IntegrationBase.t.sol'; + +import {WonderVotes} from 'contracts/governance/utils/WonderVotes.sol'; +import {WonderGovernor} from 'contracts/governance/WonderGovernor.sol'; + +contract Integration_Propose is IntegrationBase { + function _propose() internal returns (uint256 _proposalId) { + address[] memory _targets = new address[](1); + _targets[0] = address(governor); + + uint256[] memory _values = new uint256[](1); + _values[0] = 1; + + bytes[] memory _calldatas = new bytes[](1); + _calldatas[0] = abi.encode(0); + + string memory _description = 'test proposal'; + + vm.prank(holders[0]); + + // Propose + return governor.propose(0, _targets, _values, _calldatas, _description); + } + + function _vote(uint256 _proposalId, uint256 _forVoters) internal { + for (uint256 _i = 0; _i < VOTERS_NUMBER; _i++) { + address _holder = holders[_i]; + vm.prank(_holder); + + // for 60% , against 40% + uint8 _vote = _forVoters > _i ? 1 : 0; + // Vote + governor.castVote(_proposalId, _vote); + } + } + + function test_ProposalSucceeded() public { + uint256 _proposalId = _propose(); + + _mineBlocks(governor.votingDelay() + 1); + + uint256 _forVoters = VOTERS_NUMBER / 2 + 1; + uint256 _againstVoters = VOTERS_NUMBER - _forVoters; + + _vote(_proposalId, _forVoters); + + (uint256 _id, uint256 _votes, uint256 _forVotes, uint256 _againstVotes, uint256 _abstainVotes) = + AliceGovernor(payable(address(governor))).proposalTracks(_proposalId); + + assertEq(_forVotes, INITIAL_VOTERS_BALANCE * _forVoters); + assertEq(_againstVotes, INITIAL_VOTERS_BALANCE * _againstVoters); + assertEq(_abstainVotes, 0); + assertEq(_votes, INITIAL_VOTERS_BALANCE * VOTERS_NUMBER); + + // End voting period + _mineBlocks(governor.votingPeriod()); + + assertEq(uint256(governor.state(_proposalId)), 4); + } + + function test_ProposalDefeated() public { + uint256 _proposalId = _propose(); + + _mineBlocks(governor.votingDelay() + 1); + + uint256 _againstVoters = VOTERS_NUMBER / 2 + 1; + uint256 _forVoters = VOTERS_NUMBER - _againstVoters; + + _vote(_proposalId, _forVoters); + + (uint256 _id, uint256 _votes, uint256 _forVotes, uint256 _againstVotes, uint256 _abstainVotes) = + AliceGovernor(payable(address(governor))).proposalTracks(_proposalId); + + assertEq(_forVotes, INITIAL_VOTERS_BALANCE * _forVoters); + assertEq(_againstVotes, INITIAL_VOTERS_BALANCE * _againstVoters); + assertEq(_abstainVotes, 0); + assertEq(_votes, INITIAL_VOTERS_BALANCE * VOTERS_NUMBER); + + // End voting period + _mineBlocks(governor.votingPeriod()); + + assertEq(uint256(governor.state(_proposalId)), 3); + } +} diff --git a/solidity/test/smock/SmockHelper.sol b/solidity/test/smock/SmockHelper.sol index 442961e..14ef54d 100644 --- a/solidity/test/smock/SmockHelper.sol +++ b/solidity/test/smock/SmockHelper.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.19; import {Test} from 'forge-std/Test.sol'; diff --git a/solidity/test/smock/examples/MockAliceGovernor.sol b/solidity/test/smock/examples/MockAliceGovernor.sol index 5b2623d..9bf7328 100644 --- a/solidity/test/smock/examples/MockAliceGovernor.sol +++ b/solidity/test/smock/examples/MockAliceGovernor.sol @@ -1,5 +1,5 @@ -/// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.0; +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; import {Test} from 'forge-std/Test.sol'; import { diff --git a/solidity/test/smock/examples/MockRabbitToken.sol b/solidity/test/smock/examples/MockRabbitToken.sol index c1fa222..1042fc5 100644 --- a/solidity/test/smock/examples/MockRabbitToken.sol +++ b/solidity/test/smock/examples/MockRabbitToken.sol @@ -1,5 +1,5 @@ -/// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.0; +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; import {Test} from 'forge-std/Test.sol'; import {AliceGovernor, EIP712, ERC20, RabbitToken, WonderERC20Votes} from 'solidity/examples/RabbitToken.sol'; diff --git a/solidity/test/unit/WonderGovernor.t.sol b/solidity/test/unit/WonderGovernor.t.sol index cd0a0aa..3320a10 100644 --- a/solidity/test/unit/WonderGovernor.t.sol +++ b/solidity/test/unit/WonderGovernor.t.sol @@ -1,4 +1,5 @@ -import 'forge-std/Test.sol'; +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; import {WonderGovernor} from 'contracts/governance/WonderGovernor.sol'; import {IWonderGovernor} from 'interfaces/governance/IWonderGovernor.sol'; @@ -8,6 +9,8 @@ import {IWonderVotes} from 'interfaces/governance/utils/IWonderVotes.sol'; import {IWonderGovernor} from 'interfaces/governance/IWonderGovernor.sol'; import {WonderVotes} from 'contracts/governance/utils/WonderVotes.sol'; +import {TestExtended} from '../utils/TestExtended.sol'; + contract GovernorForTest is AliceGovernor { constructor(address _wonderToken) AliceGovernor(_wonderToken) {} @@ -16,7 +19,7 @@ contract GovernorForTest is AliceGovernor { } } -contract BaseTest is Test { +contract BaseTest is TestExtended { address deployer = makeAddr('deployer'); address hatter = makeAddr('hatter'); address cat = makeAddr('cat'); @@ -42,10 +45,6 @@ contract BaseTest is Test { vm.stopPrank(); } - function _expectEmit(address _contract) internal { - vm.expectEmit(true, true, true, true, _contract); - } - function _createProposal( uint8 _proposalType, address _target, @@ -366,6 +365,7 @@ contract Unit_CastVote is BaseTest { (uint256 _id, uint256 _votes, uint256 _forVotes, uint256 _againstVotes, uint256 _abstainVotes) = AliceGovernor(payable(address(governor))).proposalTracks(_proposalId); + assertEq(_votes, _voterVotes); assertEq(_forVotes, _voterVotes); assertEq(_againstVotes, 0); assertEq(_abstainVotes, 0); @@ -397,6 +397,7 @@ contract Unit_CastVote is BaseTest { (uint256 _id, uint256 _votes, uint256 _forVotes, uint256 _againstVotes, uint256 _abstainVotes) = AliceGovernor(payable(address(governor))).proposalTracks(_proposalId); + assertEq(_votes, _voterVotes); assertEq(_forVotes, 0); assertEq(_againstVotes, _voterVotes); assertEq(_abstainVotes, 0); @@ -428,6 +429,7 @@ contract Unit_CastVote is BaseTest { (uint256 _id, uint256 _votes, uint256 _forVotes, uint256 _againstVotes, uint256 _abstainVotes) = AliceGovernor(payable(address(governor))).proposalTracks(_proposalId); + assertEq(_votes, _voterVotes); assertEq(_forVotes, 0); assertEq(_againstVotes, 0); assertEq(_abstainVotes, _voterVotes); @@ -525,6 +527,7 @@ contract Unit_CastVoteWithReason is BaseTest { (uint256 _id, uint256 _votes, uint256 _forVotes, uint256 _againstVotes, uint256 _abstainVotes) = AliceGovernor(payable(address(governor))).proposalTracks(_proposalId); + assertEq(_votes, _voterVotes); assertEq(_forVotes, _voterVotes); assertEq(_againstVotes, 0); assertEq(_abstainVotes, 0); @@ -557,6 +560,7 @@ contract Unit_CastVoteWithReason is BaseTest { (uint256 _id, uint256 _votes, uint256 _forVotes, uint256 _againstVotes, uint256 _abstainVotes) = AliceGovernor(payable(address(governor))).proposalTracks(_proposalId); + assertEq(_votes, _voterVotes); assertEq(_forVotes, 0); assertEq(_againstVotes, _voterVotes); assertEq(_abstainVotes, 0); @@ -589,6 +593,7 @@ contract Unit_CastVoteWithReason is BaseTest { (uint256 _id, uint256 _votes, uint256 _forVotes, uint256 _againstVotes, uint256 _abstainVotes) = AliceGovernor(payable(address(governor))).proposalTracks(_proposalId); + assertEq(_votes, _voterVotes); assertEq(_forVotes, 0); assertEq(_againstVotes, 0); assertEq(_abstainVotes, _voterVotes); @@ -693,6 +698,7 @@ contract Unit_CastVoteWithReasonAndParams is BaseTest { (uint256 _id, uint256 _votes, uint256 _forVotes, uint256 _againstVotes, uint256 _abstainVotes) = AliceGovernor(payable(address(governor))).proposalTracks(_proposalId); + assertEq(_votes, _voterVotes); assertEq(_forVotes, _voterVotes); assertEq(_againstVotes, 0); assertEq(_abstainVotes, 0); @@ -727,6 +733,7 @@ contract Unit_CastVoteWithReasonAndParams is BaseTest { (uint256 _id, uint256 _votes, uint256 _forVotes, uint256 _againstVotes, uint256 _abstainVotes) = AliceGovernor(payable(address(governor))).proposalTracks(_proposalId); + assertEq(_votes, _voterVotes); assertEq(_forVotes, 0); assertEq(_againstVotes, _voterVotes); assertEq(_abstainVotes, 0); @@ -761,6 +768,7 @@ contract Unit_CastVoteWithReasonAndParams is BaseTest { (uint256 _id, uint256 _votes, uint256 _forVotes, uint256 _againstVotes, uint256 _abstainVotes) = AliceGovernor(payable(address(governor))).proposalTracks(_proposalId); + assertEq(_votes, _voterVotes); assertEq(_forVotes, 0); assertEq(_againstVotes, 0); assertEq(_abstainVotes, _voterVotes); diff --git a/solidity/test/unit/WonderVotes.t.sol b/solidity/test/unit/WonderVotes.t.sol index 03bec0d..3607876 100644 --- a/solidity/test/unit/WonderVotes.t.sol +++ b/solidity/test/unit/WonderVotes.t.sol @@ -1,4 +1,5 @@ -import 'forge-std/Test.sol'; +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; import {WonderGovernor} from 'contracts/governance/WonderGovernor.sol'; import {IWonderGovernor} from 'interfaces/governance/IWonderGovernor.sol'; @@ -9,6 +10,8 @@ import {RabbitToken} from 'examples/RabbitToken.sol'; import {MockAliceGovernor} from '../smock/examples/MockAliceGovernor.sol'; import {AliceGovernor} from 'examples/AliceGovernor.sol'; +import {TestExtended} from '../utils/TestExtended.sol'; + contract WonderVotesForTest is RabbitToken { constructor(AliceGovernor _governor) RabbitToken(_governor) {} @@ -21,7 +24,7 @@ contract WonderVotesForTest is RabbitToken { } } -contract BaseTest is Test { +contract BaseTest is TestExtended { address deployer = makeAddr('deployer'); address hatter = makeAddr('hatter'); address cat = makeAddr('cat'); @@ -48,10 +51,6 @@ contract BaseTest is Test { vm.stopPrank(); } - - function _expectEmit(address _contract) internal { - vm.expectEmit(true, true, true, true, _contract); - } } contract Unit_Delegate_Simple is BaseTest { diff --git a/solidity/test/utils/TestExtended.sol b/solidity/test/utils/TestExtended.sol new file mode 100644 index 0000000..e3dd99d --- /dev/null +++ b/solidity/test/utils/TestExtended.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import 'forge-std/Test.sol'; + +contract TestExtended is Test { + uint256 public constant BLOCK_TIME = 12 seconds; + + function _mineBlock() internal { + _mineBlocks(1); + } + + function _mineBlocks(uint256 _blocks) internal { + vm.warp(block.timestamp + _blocks * BLOCK_TIME); + vm.roll(block.number + _blocks); + } + + function _expectEmit(address _contract) internal { + vm.expectEmit(true, true, true, true, _contract); + } +} From ef0afb9d312afa91aa526dfa32471e15c13cd059 Mon Sep 17 00:00:00 2001 From: 0xRaccoon Date: Tue, 26 Dec 2023 20:44:17 -0300 Subject: [PATCH 11/16] fix: add optimism rpc Signed-off-by: 0xRaccoon --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 010bb44..5db6910 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -35,6 +35,7 @@ jobs: touch .env echo MAINNET_RPC="${{ secrets.MAINNET_RPC }}" >> .env echo GOERLI_RPC="${{ secrets.GOERLI_RPC }}" >> .env + echo GOERLI_RPC="${{ secrets.OPTIMISM_RPC }}" >> .env cat .env - name: Run tests From d8440ad1fd77167e4b6da409eb9c92d7af440c3e Mon Sep 17 00:00:00 2001 From: 0xRaccoon Date: Tue, 26 Dec 2023 20:45:56 -0300 Subject: [PATCH 12/16] fix: add optimism rpc Signed-off-by: 0xRaccoon --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 5db6910..7f5c524 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -35,7 +35,7 @@ jobs: touch .env echo MAINNET_RPC="${{ secrets.MAINNET_RPC }}" >> .env echo GOERLI_RPC="${{ secrets.GOERLI_RPC }}" >> .env - echo GOERLI_RPC="${{ secrets.OPTIMISM_RPC }}" >> .env + echo OPTIMISM_RPC="${{ secrets.OPTIMISM_RPC }}" >> .env cat .env - name: Run tests From 66a61f254dc8a76877cbdced0c69eeb0accb2be4 Mon Sep 17 00:00:00 2001 From: 0xRaccoon Date: Wed, 27 Dec 2023 11:08:55 -0300 Subject: [PATCH 13/16] test: add integration tests Signed-off-by: 0xRaccoon --- solidity/test/integration/Propose.t.sol | 94 +++++++++++++++++++++++-- 1 file changed, 89 insertions(+), 5 deletions(-) diff --git a/solidity/test/integration/Propose.t.sol b/solidity/test/integration/Propose.t.sol index 57087c7..1645691 100644 --- a/solidity/test/integration/Propose.t.sol +++ b/solidity/test/integration/Propose.t.sol @@ -7,6 +7,8 @@ import {WonderVotes} from 'contracts/governance/utils/WonderVotes.sol'; import {WonderGovernor} from 'contracts/governance/WonderGovernor.sol'; contract Integration_Propose is IntegrationBase { + event ProposalCanceled(uint256 _proposalId); + function _propose() internal returns (uint256 _proposalId) { address[] memory _targets = new address[](1); _targets[0] = address(governor); @@ -25,15 +27,15 @@ contract Integration_Propose is IntegrationBase { return governor.propose(0, _targets, _values, _calldatas, _description); } - function _vote(uint256 _proposalId, uint256 _forVoters) internal { + function _vote(uint256 _proposalId, uint256 _forVoters, uint256 _againstVoters) internal { for (uint256 _i = 0; _i < VOTERS_NUMBER; _i++) { address _holder = holders[_i]; vm.prank(_holder); // for 60% , against 40% - uint8 _vote = _forVoters > _i ? 1 : 0; + uint8 _support = _forVoters > _i ? 1 : _forVoters + _againstVoters > _i ? 0 : 2; // Vote - governor.castVote(_proposalId, _vote); + governor.castVote(_proposalId, _support); } } @@ -45,7 +47,7 @@ contract Integration_Propose is IntegrationBase { uint256 _forVoters = VOTERS_NUMBER / 2 + 1; uint256 _againstVoters = VOTERS_NUMBER - _forVoters; - _vote(_proposalId, _forVoters); + _vote(_proposalId, _forVoters, _againstVoters); (uint256 _id, uint256 _votes, uint256 _forVotes, uint256 _againstVotes, uint256 _abstainVotes) = AliceGovernor(payable(address(governor))).proposalTracks(_proposalId); @@ -69,7 +71,31 @@ contract Integration_Propose is IntegrationBase { uint256 _againstVoters = VOTERS_NUMBER / 2 + 1; uint256 _forVoters = VOTERS_NUMBER - _againstVoters; - _vote(_proposalId, _forVoters); + _vote(_proposalId, _forVoters, _againstVoters); + + (uint256 _id, uint256 _votes, uint256 _forVotes, uint256 _againstVotes, uint256 _abstainVotes) = + AliceGovernor(payable(address(governor))).proposalTracks(_proposalId); + + assertEq(_forVotes, INITIAL_VOTERS_BALANCE * _forVoters); + assertEq(_againstVotes, INITIAL_VOTERS_BALANCE * _againstVoters); + assertEq(_abstainVotes, 0); + assertEq(_votes, INITIAL_VOTERS_BALANCE * VOTERS_NUMBER); + + // End voting period + _mineBlocks(governor.votingPeriod()); + + assertEq(uint256(governor.state(_proposalId)), 3); + } + + function test_ProposalEven() public { + uint256 _proposalId = _propose(); + + _mineBlocks(governor.votingDelay() + 1); + + uint256 _againstVoters = VOTERS_NUMBER / 2; + uint256 _forVoters = VOTERS_NUMBER - _againstVoters; + + _vote(_proposalId, _forVoters, _againstVoters); (uint256 _id, uint256 _votes, uint256 _forVotes, uint256 _againstVotes, uint256 _abstainVotes) = AliceGovernor(payable(address(governor))).proposalTracks(_proposalId); @@ -84,4 +110,62 @@ contract Integration_Propose is IntegrationBase { assertEq(uint256(governor.state(_proposalId)), 3); } + + function test_ProposalSucceedWithAbstentions() public { + uint256 _proposalId = _propose(); + + _mineBlocks(governor.votingDelay() + 1); + + uint256 _abstainVoters = VOTERS_NUMBER / 2 + 1; + uint256 _againstVoters = (VOTERS_NUMBER - _abstainVoters) / 2 - 1; + uint256 _forVoters = VOTERS_NUMBER - _abstainVoters - _againstVoters; + + _vote(_proposalId, _forVoters, _againstVoters); + + (uint256 _id, uint256 _votes, uint256 _forVotes, uint256 _againstVotes, uint256 _abstainVotes) = + AliceGovernor(payable(address(governor))).proposalTracks(_proposalId); + + assertEq(_forVotes, INITIAL_VOTERS_BALANCE * _forVoters); + assertEq(_againstVotes, INITIAL_VOTERS_BALANCE * _againstVoters); + assertEq(_abstainVotes, INITIAL_VOTERS_BALANCE * _abstainVoters); + assertEq(_votes, INITIAL_VOTERS_BALANCE * VOTERS_NUMBER); + + // End voting period + _mineBlocks(governor.votingPeriod()); + + assertEq(uint256(governor.state(_proposalId)), 4); + } + + function test_ProposeAndCancel() public { + address[] memory _targets = new address[](1); + _targets[0] = address(governor); + + uint256[] memory _values = new uint256[](1); + _values[0] = 1; + + bytes[] memory _calldatas = new bytes[](1); + _calldatas[0] = abi.encode(0); + + string memory _description = 'test proposal'; + + vm.startPrank(holders[0]); + + // Propose + uint256 _proposalId = governor.propose(0, _targets, _values, _calldatas, _description); + + _expectEmit(address(governor)); + emit ProposalCanceled(_proposalId); + + // Cancel proposal + governor.cancel(0, _targets, _values, _calldatas, keccak256(bytes(_description))); + + vm.stopPrank(); + + _mineBlocks(governor.votingDelay() + 1); + + vm.prank(holders[1]); + + vm.expectRevert(abi.encodeWithSelector(IWonderGovernor.GovernorUnexpectedProposalState.selector, _proposalId, 2, 2)); + governor.castVote(_proposalId, 1); + } } From 8f0f9b0098fba4e78da28e494041206ec23b4f72 Mon Sep 17 00:00:00 2001 From: 0xRaccoon Date: Wed, 27 Dec 2023 11:25:10 -0300 Subject: [PATCH 14/16] fix: add missing file headers Signed-off-by: 0xRaccoon --- solidity/test/smock/SmockHelper.sol | 2 +- solidity/test/smock/examples/MockAliceGovernor.sol | 4 ++-- solidity/test/smock/examples/MockRabbitToken.sol | 4 ++-- solidity/test/unit/WonderGovernor.t.sol | 3 +++ 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/solidity/test/smock/SmockHelper.sol b/solidity/test/smock/SmockHelper.sol index 442961e..7ef3520 100644 --- a/solidity/test/smock/SmockHelper.sol +++ b/solidity/test/smock/SmockHelper.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +pragma solidity ^0.8.20; import {Test} from 'forge-std/Test.sol'; diff --git a/solidity/test/smock/examples/MockAliceGovernor.sol b/solidity/test/smock/examples/MockAliceGovernor.sol index 5b2623d..759c1a5 100644 --- a/solidity/test/smock/examples/MockAliceGovernor.sol +++ b/solidity/test/smock/examples/MockAliceGovernor.sol @@ -1,5 +1,5 @@ -/// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.0; +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; import {Test} from 'forge-std/Test.sol'; import { diff --git a/solidity/test/smock/examples/MockRabbitToken.sol b/solidity/test/smock/examples/MockRabbitToken.sol index c1fa222..ded229b 100644 --- a/solidity/test/smock/examples/MockRabbitToken.sol +++ b/solidity/test/smock/examples/MockRabbitToken.sol @@ -1,5 +1,5 @@ -/// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.0; +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; import {Test} from 'forge-std/Test.sol'; import {AliceGovernor, EIP712, ERC20, RabbitToken, WonderERC20Votes} from 'solidity/examples/RabbitToken.sol'; diff --git a/solidity/test/unit/WonderGovernor.t.sol b/solidity/test/unit/WonderGovernor.t.sol index cd0a0aa..fa480e7 100644 --- a/solidity/test/unit/WonderGovernor.t.sol +++ b/solidity/test/unit/WonderGovernor.t.sol @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + import 'forge-std/Test.sol'; import {WonderGovernor} from 'contracts/governance/WonderGovernor.sol'; From 278f18aee8e23d3039e828f9235564c71374f4ba Mon Sep 17 00:00:00 2001 From: 0xRaccoon Date: Wed, 27 Dec 2023 11:28:03 -0300 Subject: [PATCH 15/16] fix: add missing headers Signed-off-by: 0xRaccoon --- solidity/test/unit/WonderVotes.t.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/solidity/test/unit/WonderVotes.t.sol b/solidity/test/unit/WonderVotes.t.sol index 03bec0d..8b7c1c8 100644 --- a/solidity/test/unit/WonderVotes.t.sol +++ b/solidity/test/unit/WonderVotes.t.sol @@ -1,3 +1,6 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + import 'forge-std/Test.sol'; import {WonderGovernor} from 'contracts/governance/WonderGovernor.sol'; From 60cf2ffc0f0a99214db540d9bbfd9668dc2fc7e9 Mon Sep 17 00:00:00 2001 From: 0xRaccoon Date: Thu, 28 Dec 2023 09:51:58 -0300 Subject: [PATCH 16/16] fix: typos Signed-off-by: 0xRaccoon --- solidity/test/unit/WonderVotes.t.sol | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/solidity/test/unit/WonderVotes.t.sol b/solidity/test/unit/WonderVotes.t.sol index 5fd901b..fb8ee85 100644 --- a/solidity/test/unit/WonderVotes.t.sol +++ b/solidity/test/unit/WonderVotes.t.sol @@ -288,7 +288,7 @@ contract Unit_Delegate_SmartAndPartial is BaseTest { function test_Minting_SmartAndPartialDelegation_Before(uint128 _amount) public { uint8[] memory _proposalTypes = rabbitToken.proposalTypes(); - // To simply we will divide the voting power into 2 delegates 50% each + // To simplify we will divide the voting power into 2 delegates 50% each // We can add a more complex test of this further uint256 _weightNormalizer = rabbitToken.weightNormalizer(); uint256 _weight = _weightNormalizer / 2; @@ -334,8 +334,7 @@ contract Unit_Delegate_SmartAndPartial is BaseTest { uint8[] memory _proposalTypes = rabbitToken.proposalTypes(); - // To simply we will divide the voting power into 2 delegates 50% each - // We can add a more complex test of this further + // 50% each uint256 _weightNormalizer = rabbitToken.weightNormalizer(); uint256 _weight = _weightNormalizer / 2; @@ -390,8 +389,7 @@ contract Unit_Delegate_SmartAndPartial is BaseTest { uint8[] memory _proposalTypes = rabbitToken.proposalTypes(); WonderVotesForTest(address(rabbitToken)).mint(hatter, _amount); - // To simply we will divide the voting power into 2 delegates 50% each - // We can add a more complex test of this further + // 50% each uint256 _weightNormalizer = rabbitToken.weightNormalizer(); uint256 _weight = _weightNormalizer / 2; @@ -586,7 +584,7 @@ contract Unit_TransferVotes is BaseTest { address _account, uint8[] memory _proposalTypes ) internal returns (address[] memory, address[] memory) { - // To simply we will divide the voting power into 2 delegates 50% each + // To simplify we will divide the voting power into 2 delegates 50% each uint256 _weightNormalizer = rabbitToken.weightNormalizer(); uint256 _weight = _weightNormalizer / 2;