Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: base of vedelegation invariant tests #12

Merged
merged 8 commits into from
Dec 11, 2023
6 changes: 5 additions & 1 deletion test/Fixture.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
pragma solidity ^0.8.19;

import { IveANGLEVotingDelegation } from "contracts/interfaces/IveANGLEVotingDelegation.sol";
import { deployMockANGLE, deployVeANGLE } from "../../scripts/test/DeployANGLE.s.sol";
import { deployMockANGLE, deployVeANGLE } from "../scripts/test/DeployANGLE.s.sol";
import { TimelockController } from "oz/governance/TimelockController.sol";
import { ERC20 } from "oz/token/ERC20/ERC20.sol";
import "contracts/interfaces/IveANGLE.sol";
Expand Down Expand Up @@ -56,8 +56,12 @@ contract Fixture is Test {
vm.label(sweeper, "Sweeper");

vm.roll(block.number + FORK_BLOCK_NUMBER);
console.log("timepstamp", block.timestamp);

vm.warp(block.timestamp + FORK_BLOCK_TIMSESTAMP);

console.log("timepstamp", block.timestamp);

// Deploy necessary contracts - for governance to be deployed
vyperDeployer = new VyperDeployer();
(address _mockANGLE, , ) = deployMockANGLE();
Expand Down
2 changes: 1 addition & 1 deletion test/invariant/BasicInvariants.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ contract BasicInvariants is Fixture {
function setUp() public virtual override {
super.setUp();

_voterHandler = new Voter(angleGovernor, _NUM_VOTER);
_voterHandler = new Voter(angleGovernor, ANGLE, _NUM_VOTER);

// Label newly created addresses
for (uint256 i; i < _NUM_VOTER; i++)
Expand Down
73 changes: 73 additions & 0 deletions test/invariant/DelegationInvariants.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.8.19;

import { IERC20 } from "oz/token/ERC20/IERC20.sol";
import { IERC20Metadata } from "oz/token/ERC20/extensions/IERC20Metadata.sol";
import "oz/utils/Strings.sol";
import { Delegator } from "./actors/Delegator.t.sol";
import { Fixture, AngleGovernor } from "../Fixture.t.sol";

//solhint-disable
import { console } from "forge-std/console.sol";

contract DelegationInvariants is Fixture {
uint256 internal constant _NUM_DELEGATORS = 10;

Delegator internal _delegatorHandler;

function setUp() public virtual override {
super.setUp();

_delegatorHandler = new Delegator(_NUM_DELEGATORS, ANGLE, address(veANGLE), address(token));

// Label newly created addresses
for (uint256 i; i < _NUM_DELEGATORS; i++)
vm.label(_delegatorHandler.actors(i), string.concat("Delegator ", Strings.toString(i)));

targetContract(address(_delegatorHandler));

{
bytes4[] memory selectors = new bytes4[](6);
selectors[0] = Delegator.delegate.selector;
selectors[1] = Delegator.createLock.selector;
selectors[2] = Delegator.extandLockTime.selector;
selectors[3] = Delegator.extandLockAmount.selector;
selectors[4] = Delegator.wrap.selector;
selectors[5] = Delegator.withdraw.selector;
targetSelector(FuzzSelector({ addr: address(_delegatorHandler), selectors: selectors }));
}
}

function invariant_wow() public {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this one you can also compare to what voting power there is in the veANGLE
Also totalSupply == sum all delegate (also the self delegation)

for (uint256 i; i < _NUM_DELEGATORS; i++) {
address actor = _delegatorHandler.actors(i);
uint256 votes = token.getVotes(actor);

assertEq(token.delegates(actor), _delegatorHandler.delegations(actor));
if (_delegatorHandler.delegations(actor) != address(0)) {
assertEq(votes, 0, "Delegator should not have votes");
} else {
Delegator.Lock memory lock = _delegatorHandler.locks(actor);
if (lock.end > block.timestamp) {
assertEq(votes, lock.amount, "Delegator should have votes");
} else {
assertEq(votes, 0, "Delegator should not have votes");
}
}
}
for (uint256 i; i < _delegatorHandler.delegateesLength(); i++) {
address delegatee = _delegatorHandler.delegatees(i);
uint256 votes = token.getVotes(delegatee);

uint256 amount = 0;
address[] memory delegators = _delegatorHandler.reverseDelegationsView(delegatee);
for (uint256 j; j < delegators.length; j++) {
address delegator = delegators[j];
Delegator.Lock memory lock = _delegatorHandler.locks(delegator);
if (lock.end > block.timestamp) amount += lock.amount;
}
assertEq(votes, _delegatorHandler.delegationAmounts(delegatee), "Delegatee should have votes");
}
}
}
9 changes: 4 additions & 5 deletions test/invariant/actors/BaseActor.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@ contract BaseActor is Test {
uint256 public nbrActor;
address internal _currentActor;

IVotes public agToken;
AngleGovernor internal _angleGovernor;
IERC20 public agToken;
0xtekgrinder marked this conversation as resolved.
Show resolved Hide resolved

modifier countCall(bytes32 key) {
calls[key]++;
Expand All @@ -35,17 +34,17 @@ contract BaseActor is Test {

modifier useActor(uint256 actorIndexSeed) {
_currentActor = actors[bound(actorIndexSeed, 0, actors.length - 1)];
vm.startPrank(_currentActor);
vm.startPrank(_currentActor, _currentActor);
_;
vm.stopPrank();
}

constructor(uint256 _nbrActor, string memory actorType, AngleGovernor angleGovernor) {
constructor(uint256 _nbrActor, string memory actorType, IERC20 _agToken) {
for (uint256 i; i < _nbrActor; ++i) {
address actor = address(uint160(uint256(keccak256(abi.encodePacked("actor", actorType, i)))));
actors.push(actor);
}
nbrActor = _nbrActor;
_angleGovernor = angleGovernor;
agToken = _agToken;
}
}
134 changes: 134 additions & 0 deletions test/invariant/actors/Delegator.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.19;

import "./BaseActor.t.sol";
import { IERC5805 } from "oz/interfaces/IERC5805.sol";
import { MockANGLE } from "../../external/MockANGLE.sol";
import "contracts/interfaces/IveANGLE.sol";
import "contracts/utils/Errors.sol";
import { console } from "forge-std/console.sol";

contract Delegator is BaseActor {
IveANGLE public veToken;
IERC5805 public veDelegation;

struct Lock {
uint256 amount;
uint256 end;
}

mapping(address => uint256) public delegationAmounts;
mapping(address => address) public delegations;
mapping(address => address[]) public reverseDelegations;
mapping(address => Lock) public _locks;
address[] public delegatees;

constructor(
uint256 _nbrActor,
IERC20 _agToken,
address _veToken,
address _veDelegation
) BaseActor(_nbrActor, "Delegator", _agToken) {
veToken = IveANGLE(_veToken);
veDelegation = IERC5805(_veDelegation);
}

function locks(address locker) public view returns (Lock memory) {
return _locks[locker];
}

function reverseDelegationsView(address locker) public view returns (address[] memory) {
return reverseDelegations[locker];
}

function delegateesLength() public view returns (uint256) {
return delegatees.length;
}

function delegate(uint256 acordIndex, address toDelegate) public useActor(acordIndex) {
if (toDelegate == address(0)) return;

uint256 balance = veToken.balanceOf(_currentActor);
address currentDelegatee = delegations[_currentActor];

if (balance == 0) {
return;
}

veDelegation.delegate(toDelegate);

// Update delegations
delegations[_currentActor] = toDelegate;
for (uint256 i; i < delegatees.length; i++) {
if (delegatees[i] == toDelegate) {
return;
}
}
delegatees.push(toDelegate);
reverseDelegations[toDelegate].push(_currentActor);
0xtekgrinder marked this conversation as resolved.
Show resolved Hide resolved
for (uint256 i; i < reverseDelegations[currentDelegatee].length; i++) {
if (reverseDelegations[currentDelegatee][i] == _currentActor) {
reverseDelegations[currentDelegatee][i] = reverseDelegations[currentDelegatee][
reverseDelegations[currentDelegatee].length - 1
];
reverseDelegations[currentDelegatee].pop();
break;
}
}
}

function createLock(uint256 acordIndex, uint256 amount, uint256 duration) public useActor(acordIndex) {
0xtekgrinder marked this conversation as resolved.
Show resolved Hide resolved
if (veToken.locked__end(_currentActor) != 0) {
return;
}
duration = bound(duration, 1 weeks, 365 days * 4);
amount = bound(amount, 100e18, 1_500e18);

MockANGLE(address(agToken)).mint(_currentActor, amount);
agToken.approve(address(veToken), amount);

veToken.create_lock(amount, block.timestamp + duration);

_locks[_currentActor] = Lock({
amount: veToken.balanceOf(_currentActor),
end: veToken.locked__end(_currentActor)
});
}

function withdraw() public {
if (veToken.locked__end(_currentActor) != 0 && veToken.locked__end(_currentActor) < block.timestamp) {
veToken.withdraw();

_locks[_currentActor] = Lock({ amount: 0, end: 0 });
}
}

function extandLockTime(uint256 acordIndex, uint256 duration) public useActor(acordIndex) {
0xtekgrinder marked this conversation as resolved.
Show resolved Hide resolved
uint256 end = veToken.locked__end(_currentActor);
if (end == 0 || end + 1 weeks > block.timestamp + 365 days * 4) {
return;
}

duration = bound(duration, end + 1 weeks, block.timestamp + 365 days * 4);
veToken.increase_unlock_time(duration);
_locks[_currentActor].end = veToken.locked__end(_currentActor);
}

function extandLockAmount(uint256 acordIndex, uint256 amount) public useActor(acordIndex) {
if (veToken.balanceOf(_currentActor) == 0) {
return;
}
amount = bound(amount, 100e18, 1_500e18);

MockANGLE(address(agToken)).mint(_currentActor, amount);
agToken.approve(address(veToken), amount);
veToken.increase_amount(amount);

_locks[_currentActor].amount = veToken.balanceOf(_currentActor);
}

function wrap(uint256 timestamp) public {
0xtekgrinder marked this conversation as resolved.
Show resolved Hide resolved
timestamp = bound(timestamp, block.timestamp, 365 days * 5);
vm.warp(timestamp);
}
}
6 changes: 5 additions & 1 deletion test/invariant/actors/Voter.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,9 @@ import { BaseActor, IERC20, IERC20Metadata, AngleGovernor, TestStorage } from ".
import { console } from "forge-std/console.sol";

contract Voter is BaseActor {
constructor(AngleGovernor angleGovernor, uint256 nbrVoter) BaseActor(nbrVoter, "Voter", angleGovernor) {}
AngleGovernor internal _angleGovernor;

constructor(AngleGovernor angleGovernor, IERC20 _agToken, uint256 nbrVoter) BaseActor(nbrVoter, "Voter", _agToken) {
_angleGovernor = angleGovernor;
}
}
Loading