Skip to content

Commit

Permalink
L02 (#57)
Browse files Browse the repository at this point in the history
  • Loading branch information
wildmolasses authored Jul 1, 2024
1 parent 92ffae9 commit ac5f5e5
Show file tree
Hide file tree
Showing 4 changed files with 109 additions and 27 deletions.
2 changes: 1 addition & 1 deletion src/VotesPartialDelegationUpgradeable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,7 @@ abstract contract VotesPartialDelegationUpgradeable is
j++;
}

if (_delegationAdjustment._amount != 0) {
if (_delegationAdjustment._amount != 0 && _delegationAdjustment._delegatee != address(0)) {
(uint256 oldValue, uint256 newValue) = _push(
$._delegateCheckpoints[_delegationAdjustment._delegatee],
_operation(_op),
Expand Down
18 changes: 13 additions & 5 deletions test/ERC20VotesPartialDelegationUpgradeable.invariants.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -40,23 +40,26 @@ contract ERC20VotesPartialDelegationUpgradeableInvariants is Test {
assertEq(_sumOfBalances, tokenProxy.totalSupply(), "sum of balances does not equal total supply");
}

function invariant_SumOfVotesPlusRemainderVotesPlusSumOfNonDelegateBalancesEqualsTotalSupply() public {
function invariant_SumOfVotesPlusRemainderVotesPlusSumOfNonDelegateBalancesPlusSumOfZeroAddressVotesEqualsTotalSupply(
) public {
uint256 _sumOfVotes = handler.reduceDelegatees(0, this.accumulateVotes);
uint256 _sumOfRemainders = handler.reduceDelegators(0, this.accumulateRemainders);
uint256 _sumOfNonDelegateBalances = handler.reduceNonDelegators(0, this.accumulateBalances);
uint256 _sumOfZeroAddressVotes = handler.reduceDelegators(0, this.accumulateZeroAddressVotes);
assertEq(
_sumOfVotes + _sumOfRemainders + _sumOfNonDelegateBalances,
_sumOfVotes + _sumOfRemainders + _sumOfNonDelegateBalances + _sumOfZeroAddressVotes,
tokenProxy.totalSupply(),
"sum of votes plus undelegated remainders plus sum of non-delegate balances does not equal total supply"
);
}

function invariant_SumOfVotesPlusRemainderVotesEqualsSumOfDelegatorBalances() public {
function invariant_SumOfVotesPlusRemainderVotesPlusZeroAddressVotesEqualsSumOfDelegatorBalances() public {
uint256 _sumOfVotes = handler.reduceDelegatees(0, this.accumulateVotes);
uint256 _sumOfRemainders = handler.reduceDelegators(0, this.accumulateRemainders);
uint256 _sumOfDelegatorBalances = handler.reduceDelegators(0, this.accumulateBalances);
uint256 _sumOfZeroAddressVotes = handler.reduceDelegators(0, this.accumulateZeroAddressVotes);
assertEq(
_sumOfVotes + _sumOfRemainders,
_sumOfVotes + _sumOfRemainders + _sumOfZeroAddressVotes,
_sumOfDelegatorBalances,
"sum of votes plus undelegated remainders does not equal sum of delegator balances"
);
Expand All @@ -66,7 +69,8 @@ contract ERC20VotesPartialDelegationUpgradeableInvariants is Test {
uint256 blockNum = vm.getBlockNumber();
vm.roll(blockNum + 1);
uint256 _sumOfVotes = handler.reduceDelegatees(0, this.accumulateVotes)
+ handler.reduceDelegators(0, this.accumulateRemainders) + handler.reduceNonDelegators(0, this.accumulateBalances);
+ handler.reduceDelegators(0, this.accumulateRemainders) + handler.reduceNonDelegators(0, this.accumulateBalances)
+ handler.reduceDelegators(0, this.accumulateZeroAddressVotes);
assertEq(_sumOfVotes, tokenProxy.getPastTotalSupply(blockNum), "sum of votes does not equal past total supply");
}

Expand All @@ -86,4 +90,8 @@ contract ERC20VotesPartialDelegationUpgradeableInvariants is Test {
function accumulateRemainders(uint256 acc, address delegator) public view returns (uint256) {
return acc + handler.ghost_delegatorVoteRemainder(delegator);
}

function accumulateZeroAddressVotes(uint256 acc, address delegator) public view returns (uint256) {
return acc + handler.ghost_delegatorZeroAddressVotes(delegator);
}
}
88 changes: 78 additions & 10 deletions test/ERC20VotesPartialDelegationUpgradeable.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ contract PartialDelegationTest is DelegationAndEventHelpers {
DelegationAdjustment[] memory _votes = tokenProxy.exposed_calculateWeightDistribution(_delegations, _amount);
uint256 _totalWeight = 0;
for (uint256 i = 0; i < _delegations.length; i++) {
uint256 _expectedVoteWeight = _votes[i]._amount;
uint256 _expectedVoteWeight = _delegations[i]._delegatee == address(0) ? 0 : _votes[i]._amount;
assertEq(
tokenProxy.getVotes(_delegations[i]._delegatee), _expectedVoteWeight, "incorrect vote weight for delegate"
);
Expand Down Expand Up @@ -201,10 +201,14 @@ contract PartialDelegationTest is DelegationAndEventHelpers {
}

contract Delegate is PartialDelegationTest {
function testFuzz_DelegatesToAnySingleAddress(address _actor, address _delegatee, uint96 _numerator, uint256 _amount)
public
{
function testFuzz_DelegatesToAnySingleNonZeroAddress(
address _actor,
address _delegatee,
uint96 _numerator,
uint256 _amount
) public {
vm.assume(_actor != address(0));
vm.assume(_delegatee != address(0));
_numerator = uint96(bound(_numerator, 1, tokenProxy.DENOMINATOR()));
_amount = bound(_amount, 0, type(uint208).max);
vm.startPrank(_actor);
Expand All @@ -218,6 +222,21 @@ contract Delegate is PartialDelegationTest {
assertEq(tokenProxy.getVotes(_delegatee), adjustments[0]._amount);
}

function testFuzz_DelegatesOnlyToZeroAddress(address _actor, uint96 _numerator, uint256 _amount) public {
vm.assume(_actor != address(0));
address _delegatee = address(0);
_numerator = uint96(bound(_numerator, 1, tokenProxy.DENOMINATOR()));
_amount = bound(_amount, 0, type(uint208).max);
vm.startPrank(_actor);
tokenProxy.mint(_amount);
PartialDelegation[] memory delegations = new PartialDelegation[](1);
delegations[0] = PartialDelegation(_delegatee, _numerator);
tokenProxy.delegate(delegations);
vm.stopPrank();
assertEq(tokenProxy.delegates(_actor), delegations);
assertEq(tokenProxy.getVotes(_delegatee), 0);
}

function testFuzz_DelegatesToTwoAddresses(
address _actor,
address _delegatee1,
Expand Down Expand Up @@ -651,12 +670,13 @@ contract Delegate is PartialDelegationTest {
}

contract DelegateLegacy is PartialDelegationTest {
function testFuzz_DelegatesSuccessfully(
function testFuzz_DelegatesSuccessfullyToNonZeroAddress(
uint256 _delegatorPrivateKey,
address _delegatee,
uint256 _delegatorBalance,
uint256 _deadline
) public {
vm.assume(_delegatee != address(0));
_delegatorPrivateKey = bound(_delegatorPrivateKey, 1, 100e18);
address _delegator = vm.addr(_delegatorPrivateKey);
_delegatorBalance = bound(_delegatorBalance, 0, type(uint208).max);
Expand All @@ -669,9 +689,28 @@ contract DelegateLegacy is PartialDelegationTest {
assertEq(tokenProxy.getVotes(_delegatee), _delegatorBalance);
}

function testFuzz_DelegatesSuccessfullyToZeroAddress(
uint256 _delegatorPrivateKey,
uint256 _delegatorBalance,
uint256 _deadline
) public {
address _delegatee = address(0);
_delegatorPrivateKey = bound(_delegatorPrivateKey, 1, 100e18);
address _delegator = vm.addr(_delegatorPrivateKey);
_delegatorBalance = bound(_delegatorBalance, 0, type(uint208).max);
_deadline = bound(_deadline, block.timestamp, type(uint256).max);
_mint(_delegator, _delegatorBalance);

vm.prank(_delegator);
tokenProxy.delegate(_delegatee);
assertEq(tokenProxy.delegates(_delegator), _createSingleFullDelegation(_delegatee));
assertEq(tokenProxy.getVotes(_delegatee), 0);
}

function testFuzz_RedelegatesSuccessfully(
uint256 _delegatorPrivateKey,
address _delegatee,
address _newDelegatee,
uint256 _delegatorBalance,
uint256 _deadline
) public {
Expand All @@ -684,13 +723,14 @@ contract DelegateLegacy is PartialDelegationTest {
vm.prank(_delegator);
tokenProxy.delegate(_delegatee);
assertEq(tokenProxy.delegates(_delegator), _createSingleFullDelegation(_delegatee));
assertEq(tokenProxy.getVotes(_delegatee), _delegatorBalance);
uint256 _expectedVotes = _delegatee == address(0) ? 0 : _delegatorBalance;
assertEq(tokenProxy.getVotes(_delegatee), _expectedVotes);

address _newDelegatee = address(uint160(uint256(keccak256(abi.encode(_delegatee)))));
vm.prank(_delegator);
tokenProxy.delegate(_newDelegatee);
assertEq(tokenProxy.delegates(_delegator), _createSingleFullDelegation(_newDelegatee));
assertEq(tokenProxy.getVotes(_newDelegatee), _delegatorBalance);
_expectedVotes = _newDelegatee == address(0) ? 0 : _delegatorBalance;
assertEq(tokenProxy.getVotes(_newDelegatee), _expectedVotes);
}

function testFuzz_RedelegatesToAPartialDelegationSuccessfully(
Expand All @@ -709,7 +749,8 @@ contract DelegateLegacy is PartialDelegationTest {
vm.prank(_delegator);
tokenProxy.delegate(_delegatee);
assertEq(tokenProxy.delegates(_delegator), _createSingleFullDelegation(_delegatee));
assertEq(tokenProxy.getVotes(_delegatee), _delegatorBalance);
uint256 _expectedVotes = _delegatee == address(0) ? 0 : _delegatorBalance;
assertEq(tokenProxy.getVotes(_delegatee), _expectedVotes);

PartialDelegation[] memory newDelegations = _createValidPartialDelegation(0, uint256(keccak256(abi.encode(_seed))));
vm.prank(_delegator);
Expand All @@ -722,7 +763,7 @@ contract DelegateLegacy is PartialDelegationTest {
contract DelegateBySig is PartialDelegationTest {
using stdStorage for StdStorage;

function testFuzz_DelegatesSuccessfully(
function testFuzz_DelegatesSuccessfullyToNonZeroAddress(
address _actor,
uint256 _delegatorPrivateKey,
address _delegatee,
Expand All @@ -731,6 +772,7 @@ contract DelegateBySig is PartialDelegationTest {
uint256 _deadline
) public {
vm.assume(_actor != address(0));
vm.assume(_delegatee != address(0));
_delegatorPrivateKey = bound(_delegatorPrivateKey, 1, 100e18);
address _delegator = vm.addr(_delegatorPrivateKey);
stdstore.target(address(tokenProxy)).sig("nonces(address)").with_key(_delegator).checked_write(_currentNonce);
Expand All @@ -748,6 +790,32 @@ contract DelegateBySig is PartialDelegationTest {
assertEq(tokenProxy.getVotes(_delegatee), _delegatorBalance);
}

function testFuzz_DelegatesSuccessfullyToZeroAddress(
address _actor,
uint256 _delegatorPrivateKey,
uint256 _delegatorBalance,
uint256 _currentNonce,
uint256 _deadline
) public {
vm.assume(_actor != address(0));
address _delegatee = address(0);
_delegatorPrivateKey = bound(_delegatorPrivateKey, 1, 100e18);
address _delegator = vm.addr(_delegatorPrivateKey);
stdstore.target(address(tokenProxy)).sig("nonces(address)").with_key(_delegator).checked_write(_currentNonce);
_delegatorBalance = bound(_delegatorBalance, 0, type(uint208).max);
_deadline = bound(_deadline, block.timestamp, type(uint256).max);
_mint(_delegator, _delegatorBalance);

bytes32 _message = keccak256(abi.encode(tokenProxy.DELEGATION_TYPEHASH(), address(0), _currentNonce, _deadline));

bytes32 _messageHash = keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, _message));
(uint8 _v, bytes32 _r, bytes32 _s) = vm.sign(_delegatorPrivateKey, _messageHash);
vm.prank(_actor);
tokenProxy.delegateBySig(_delegatee, _currentNonce, _deadline, _v, _r, _s);
assertEq(tokenProxy.delegates(_delegator), _createSingleFullDelegation(_delegatee));
assertEq(tokenProxy.getVotes(_delegatee), 0);
}

function testFuzz_RevertIf_DelegatesViaERC712SignatureWithExpiredDeadline(
address _actor,
uint256 _delegatorPrivateKey,
Expand Down
28 changes: 17 additions & 11 deletions test/Handler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ contract Handler is CommonBase, StdCheats, StdUtils {

// ghost vars
mapping(address => uint208) public ghost_delegatorVoteRemainder;
mapping(address => uint208) public ghost_delegatorZeroAddressVotes;

modifier countCall(bytes32 key) {
calls[key]++;
Expand Down Expand Up @@ -67,11 +68,17 @@ contract Handler is CommonBase, StdCheats, StdUtils {
tokenProxy.mint(_amount);
}

function _adjustDelegatorRemainder(address _delegator) public {
(, uint208 _remainder) = _calculateWeightDistributionAndRemainder(
function _adjustDelegatorRemainderAndZeroAddressVotes(address _delegator) public {
(DelegationAdjustment[] memory _delegationAdjustments, uint208 _remainder) =
_calculateWeightDistributionAndRemainder(
tokenProxy.delegates(_delegator), uint208(tokenProxy.balanceOf(_delegator))
);
ghost_delegatorVoteRemainder[_delegator] = _remainder;
if (_delegationAdjustments[0]._delegatee == address(0)) {
ghost_delegatorZeroAddressVotes[_delegator] = _delegationAdjustments[0]._amount;
} else {
ghost_delegatorZeroAddressVotes[_delegator] = 0;
}
}

function _createValidPartialDelegation(uint256 _n, uint256 _seed) internal returns (PartialDelegation[] memory) {
Expand Down Expand Up @@ -123,7 +130,7 @@ contract Handler is CommonBase, StdCheats, StdUtils {
// holder is also a nondelegator, because there's no delegation
_nondelegators.add(_holder);
} else {
_adjustDelegatorRemainder(_holder);
_adjustDelegatorRemainderAndZeroAddressVotes(_holder);
}
}

Expand All @@ -141,7 +148,7 @@ contract Handler is CommonBase, StdCheats, StdUtils {
_holders.add(_holder);
_delegators.add(_holder);
_delegatees.add(_delegatee);
_adjustDelegatorRemainder(_holder);
_adjustDelegatorRemainderAndZeroAddressVotes(_holder);
}

function handler_mintAndDelegateMulti(address _holder, uint256 _amount, uint256 _delegationSeed)
Expand All @@ -157,26 +164,25 @@ contract Handler is CommonBase, StdCheats, StdUtils {
tokenProxy.delegate(_delegations);
_holders.add(_holder);
_delegators.add(_holder);
_adjustDelegatorRemainder(_holder);
_adjustDelegatorRemainderAndZeroAddressVotes(_holder);
}

function handler_redelegate(uint256 _actorSeed, uint256 _delegationSeed) public countCall("handler_redelegate") {
address _delegator = _useActor(_delegators, _actorSeed);
PartialDelegation[] memory _delegations = _createValidPartialDelegation(0, _delegationSeed);
vm.prank(_delegator);
tokenProxy.delegate(_delegations);
_adjustDelegatorRemainder(_delegator);
_adjustDelegatorRemainderAndZeroAddressVotes(_delegator);
}

function handler_undelegate(uint256 _actorSeed) public countCall("handler_undelegate") {
address _holder = _useActor(_delegators, _actorSeed);
vm.prank(_holder);
tokenProxy.delegate(address(0));

// technically address(0) is a delegatee now
_delegatees.add(address(0));
// _currentActor is also still technically a delegator delegating to address(0), so we won't add to nondelegates set
_adjustDelegatorRemainder(_holder);
_adjustDelegatorRemainderAndZeroAddressVotes(_holder);
}

function handler_validNonZeroTransferToDelegator(uint256 _amount, uint256 _actorSeed, uint256 _delegatorSeed)
Expand All @@ -188,8 +194,8 @@ contract Handler is CommonBase, StdCheats, StdUtils {
address _to = _useActor(_delegators, _delegatorSeed);
vm.prank(_currentActor);
tokenProxy.transfer(_to, _amount);
_adjustDelegatorRemainder(_currentActor);
_adjustDelegatorRemainder(_to);
_adjustDelegatorRemainderAndZeroAddressVotes(_currentActor);
_adjustDelegatorRemainderAndZeroAddressVotes(_to);
}

function handler_validNonZeroTransferToNonDelegator(uint256 _amount, uint256 _actorSeed, address _to)
Expand All @@ -207,7 +213,7 @@ contract Handler is CommonBase, StdCheats, StdUtils {
_holders.add(_to);
// we also know they're a nondelegator
_nondelegators.add(_to);
_adjustDelegatorRemainder(_currentActor);
_adjustDelegatorRemainderAndZeroAddressVotes(_currentActor);
}

function handler_invalidTransfer(uint256 _amount, address _actor, address _to) public countCall("invalidTransfer") {
Expand Down

0 comments on commit ac5f5e5

Please sign in to comment.