ghost-dao-contracts/test/tokens/Votes.t.sol
Uncle Fatso 46b33b4c75
initial push for smart-contracts
Signed-off-by: Uncle Fatso <uncle.fatso@ghostchain.io>
2025-04-28 14:17:04 +03:00

270 lines
10 KiB
Solidity

pragma solidity 0.8.20;
import {Test} from "forge-std/Test.sol";
import "@openzeppelin-contracts/token/ERC20/extensions/ERC20Votes.sol";
import "@openzeppelin-contracts/utils/structs/Checkpoints.sol";
abstract contract ERC20VotesTest is Test {
ERC20Votes tokenVotes;
uint256 amountVotes;
address public aliceVotes;
address public bobVotes;
address constant public charlieVotes = 0x0000000000000000000000000000000000000069;
address constant public eveVotes = 0x0000000000000000000000000000000000001337;
event DelegateChanged(
address indexed delegator,
address indexed fromDelegate,
address indexed toDelegate
);
event DelegateVotesChanged(
address indexed delegate,
uint256 previousBalance,
uint256 newBalance
);
function initializeVotes(
address alice,
address bob,
address token,
uint256 amount
) public {
aliceVotes = alice;
bobVotes = bob;
amountVotes = amount;
tokenVotes = ERC20Votes(token);
}
function test_votes_delegate() external {
// case 1: first delegation without balance
assertEq(tokenVotes.delegates(address(this)), address(0));
vm.expectEmit(true, true, true, false, address(tokenVotes));
emit DelegateChanged(address(this), address(0), aliceVotes);
tokenVotes.delegate(aliceVotes);
assertEq(tokenVotes.delegates(address(this)), aliceVotes);
// no votes in aliceVotes
assertEq(tokenVotes.getVotes(aliceVotes), 0);
// no Checkpoint generated
assertEq(tokenVotes.numCheckpoints(aliceVotes), 0);
// case 2: first delegate with balance
_mintVotesTokens(aliceVotes, amountVotes);
assertEq(tokenVotes.delegates(aliceVotes), address(0));
vm.expectEmit(true, true, true, false, address(tokenVotes));
emit DelegateChanged(aliceVotes, address(0), bobVotes);
vm.expectEmit(true, false, false, true, address(tokenVotes));
emit DelegateVotesChanged(bobVotes, 0, amountVotes);
vm.prank(aliceVotes);
tokenVotes.delegate(bobVotes);
assertEq(tokenVotes.delegates(aliceVotes), bobVotes);
// amountVotes votes in bobVotes
assertEq(tokenVotes.getVotes(bobVotes), amountVotes);
// 1 Checkpoint generated
assertEq(tokenVotes.numCheckpoints(bobVotes), 1);
Checkpoints.Checkpoint208 memory ckpt = tokenVotes.checkpoints(bobVotes, 0);
assertEq(ckpt._key, 1);
assertEq(ckpt._value, amountVotes);
// case 3: delegate with balance not first
vm.roll(2);
vm.expectEmit(true, true, true, false, address(tokenVotes));
emit DelegateChanged(aliceVotes, bobVotes, charlieVotes);
vm.expectEmit(true, false, false, true, address(tokenVotes));
emit DelegateVotesChanged(bobVotes, amountVotes, 0);
vm.expectEmit(true, false, false, true, address(tokenVotes));
emit DelegateVotesChanged(charlieVotes, 0, amountVotes);
vm.prank(aliceVotes);
tokenVotes.delegate(charlieVotes);
assertEq(tokenVotes.delegates(aliceVotes), charlieVotes);
// amountVotes votes in charlieVotes
assertEq(tokenVotes.getVotes(charlieVotes), amountVotes);
// 1 Checkpoint generated
assertEq(tokenVotes.numCheckpoints(charlieVotes), 1);
ckpt = tokenVotes.checkpoints(charlieVotes, 0);
assertEq(ckpt._key, 2);
assertEq(ckpt._value, amountVotes);
// 0 votes in bobVotes
assertEq(tokenVotes.getVotes(bobVotes), 0);
// 1 Checkpoint generated
assertEq(tokenVotes.numCheckpoints(bobVotes), 2);
ckpt = tokenVotes.checkpoints(bobVotes, 1);
assertEq(ckpt._key, 2);
assertEq(ckpt._value, 0);
}
function test_votes_mintAndBurnAndMaxSupply() external {
// test for {_mint}
// case 1: receiver has no delegatee
assertEq(tokenVotes.totalSupply(), 0);
_mintVotesTokens(address(this), 1);
assertEq(tokenVotes.totalSupply(), 1);
assertEq(tokenVotes.balanceOf(address(this)), 1);
// revert if total supply exceeds the ceiling
vm.expectRevert();
_mintVotesTokens(aliceVotes, type(uint224).max + 1);
// case 2: receiver has a delegatee
vm.prank(aliceVotes);
tokenVotes.delegate(address(this));
assertEq(tokenVotes.getVotes(address(this)), 0);
_mintVotesTokens(aliceVotes, 2);
assertEq(tokenVotes.totalSupply(), 1 + 2);
assertEq(tokenVotes.balanceOf(aliceVotes), 0 + 2);
// delegatee's votes increased
assertEq(tokenVotes.getVotes(address(this)), 0 + 2);
// revert if total supply exceeds the ceiling (happens in {_afterTokenTransfer})
vm.expectRevert();
_mintVotesTokens(aliceVotes, type(uint224).max);
// test for {_burn}
// case 3: receiver has no delegatee
_burnVotesTokens(address(this), 1);
assertEq(tokenVotes.totalSupply(), 3 - 1);
// case 4: receiver has a delegatee
_burnVotesTokens(aliceVotes, 1);
assertEq(tokenVotes.totalSupply(), 2 - 1);
// delegatee's votes decreased
assertEq(tokenVotes.getVotes(address(this)), 2 - 1);
}
function test_votes_afterTokenTransfer() external {
_mintVotesTokens(address(this), 100);
tokenVotes.delegate(aliceVotes);
assertEq(tokenVotes.delegates(address(this)), aliceVotes);
assertEq(tokenVotes.numCheckpoints(aliceVotes), 1);
// test for {transfer}
// case 1: 'to' has no delegatee
vm.roll(2);
vm.expectEmit(true, false, false, true, address(tokenVotes));
emit DelegateVotesChanged(aliceVotes, 100, 100 - 1);
vm.prank(address(this));
tokenVotes.transfer(bobVotes, 1);
assertEq(tokenVotes.getVotes(aliceVotes), 100 - 1);
// case 2: 'to' has a delegatee
_mintVotesTokens(charlieVotes, 100);
vm.prank(charlieVotes);
tokenVotes.delegate(eveVotes);
vm.roll(3);
vm.expectEmit(true, false, false, true, address(tokenVotes));
emit DelegateVotesChanged(aliceVotes, 99, 99 - 1);
vm.expectEmit(true, false, false, true, address(tokenVotes));
emit DelegateVotesChanged(eveVotes, 100, 100 + 1);
tokenVotes.transfer(charlieVotes, 1);
assertEq(tokenVotes.getVotes(aliceVotes), 99 - 1);
assertEq(tokenVotes.getVotes(eveVotes), 100 + 1);
// test for {transferFrom}
// case 3: 'to' has no delegatee
vm.roll(4);
assertEq(tokenVotes.delegates(bobVotes), address(0));
tokenVotes.approve(aliceVotes, 100);
vm.startPrank(aliceVotes);
vm.expectEmit(true, false, false, true, address(tokenVotes));
emit DelegateVotesChanged(aliceVotes, 98, 98 - 1);
tokenVotes.transferFrom(address(this), bobVotes, 1);
// case 4: 'to' has a delegatee
vm.roll(5);
assertEq(tokenVotes.delegates(charlieVotes), eveVotes);
vm.expectEmit(true, false, false, true, address(tokenVotes));
emit DelegateVotesChanged(aliceVotes, 97, 97 - 1);
vm.expectEmit(true, false, false, true, address(tokenVotes));
emit DelegateVotesChanged(eveVotes, 101, 101 + 1);
tokenVotes.transferFrom(address(this), charlieVotes, 1);
}
function test_votes_getPastVotesAndGetPastTotalSupply() external {
// 6 Checkpoints of aliceVotes:
// block votes index
// 2 10 0
// 3 15 1
// 6 19 2
// 10 20 3
// 11 23 4
// 13 31 5
//
// 6 Checkpoints of total supply:
// block total supply index
// 2 10 0
// 3 15 1
// 6 19 2
// 10 20 3
// 11 23 4
// 13 31 5
tokenVotes.delegate(aliceVotes);
vm.roll(2);
_mintVotesTokens(address(this), 10);
vm.roll(3);
_mintVotesTokens(address(this), 15 - 10);
vm.roll(6);
_mintVotesTokens(address(this), 19 - 15);
vm.roll(10);
_mintVotesTokens(address(this), 20 - 19);
vm.roll(11);
_mintVotesTokens(address(this), 23 - 20);
vm.roll(13);
_mintVotesTokens(address(this), 31 - 23);
vm.roll(20);
// check {getPastVotes} && {getPastTotalSupply}
assertEq(tokenVotes.numCheckpoints(aliceVotes), 6);
assertEq(tokenVotes.getPastVotes(aliceVotes, 1), 0);
assertEq(tokenVotes.getPastTotalSupply(1), 0);
assertEq(tokenVotes.getPastVotes(aliceVotes, 2), 10);
assertEq(tokenVotes.getPastTotalSupply(2), 10);
assertEq(tokenVotes.getPastVotes(aliceVotes, 4), 15);
assertEq(tokenVotes.getPastTotalSupply(4), 15);
assertEq(tokenVotes.getPastVotes(aliceVotes, 6), 19);
assertEq(tokenVotes.getPastTotalSupply(6), 19);
assertEq(tokenVotes.getPastVotes(aliceVotes, 9), 19);
assertEq(tokenVotes.getPastTotalSupply(9), 19);
assertEq(tokenVotes.getPastVotes(aliceVotes, 10), 20);
assertEq(tokenVotes.getPastTotalSupply(10), 20);
assertEq(tokenVotes.getPastVotes(aliceVotes, 12), 23);
assertEq(tokenVotes.getPastTotalSupply(12), 23);
assertEq(tokenVotes.getPastVotes(aliceVotes, 13), 31);
assertEq(tokenVotes.getPastTotalSupply(13), 31);
assertEq(tokenVotes.getPastVotes(aliceVotes, 19), 31);
assertEq(tokenVotes.getPastTotalSupply(19), 31);
// revert if block not mined
vm.expectRevert();
tokenVotes.getPastVotes(aliceVotes, 9999);
vm.expectRevert();
tokenVotes.getPastTotalSupply(9999);
}
function _mintVotesTokens(address who, uint256 value) internal virtual;
function _burnVotesTokens(address who, uint256 amount) internal virtual;
}