generated from AngleProtocol/boilerplate
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: base of vedelegation invariant tests (#12)
* feat: base of vedelegation invariant tests * refactor: improve the invariant tests * refactor: doesn't use anymore the lockAmounts * wip: delegator invariant failing * tests: use TimestampStore for invariant tests * tests: add Param handler * tests: add extendLock amount and duration for delegation * chore: remove useless params in foundry.toml
- Loading branch information
1 parent
2a69d47
commit f31c4e3
Showing
8 changed files
with
265 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
// 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 { Param } from "./actors/Param.t.sol"; | ||
import { Fixture, AngleGovernor } from "../Fixture.t.sol"; | ||
import { TimestampStore } from "./stores/TimestampStore.sol"; | ||
|
||
//solhint-disable | ||
import { console } from "forge-std/console.sol"; | ||
|
||
contract DelegationInvariants is Fixture { | ||
uint256 internal constant _NUM_DELEGATORS = 10; | ||
uint256 internal constant _NUM_PARAMS = 1; | ||
|
||
Delegator internal _delegatorHandler; | ||
Param internal _paramHandler; | ||
TimestampStore internal _timestampStore; | ||
|
||
modifier useCurrentTimestamp() { | ||
vm.warp(_timestampStore.currentTimestamp()); | ||
_; | ||
} | ||
|
||
function setUp() public virtual override { | ||
super.setUp(); | ||
|
||
_timestampStore = new TimestampStore(); | ||
_delegatorHandler = new Delegator(_NUM_DELEGATORS, ANGLE, address(veANGLE), address(token), _timestampStore); | ||
_paramHandler = new Param(_NUM_PARAMS, ANGLE, _timestampStore); | ||
|
||
// Label newly created addresses | ||
for (uint256 i; i < _NUM_DELEGATORS; i++) | ||
vm.label(_delegatorHandler.actors(i), string.concat("Delegator ", Strings.toString(i))); | ||
vm.label({ account: address(_timestampStore), newLabel: "TimestampStore" }); | ||
vm.label({ account: address(_paramHandler), newLabel: "Param" }); | ||
|
||
targetContract(address(_delegatorHandler)); | ||
targetContract(address(_paramHandler)); | ||
|
||
{ | ||
bytes4[] memory selectors = new bytes4[](5); | ||
selectors[0] = Delegator.delegate.selector; | ||
selectors[1] = Delegator.createLock.selector; | ||
selectors[2] = Delegator.withdraw.selector; | ||
selectors[3] = Delegator.extendLockTime.selector; | ||
selectors[4] = Delegator.extendLockAmount.selector; | ||
targetSelector(FuzzSelector({ addr: address(_delegatorHandler), selectors: selectors })); | ||
} | ||
{ | ||
bytes4[] memory selectors = new bytes4[](1); | ||
selectors[0] = Param.wrap.selector; | ||
targetSelector(FuzzSelector({ addr: address(_paramHandler), selectors: selectors })); | ||
} | ||
} | ||
|
||
function invariant_RightNumberOfVotesDelegated() public useCurrentTimestamp { | ||
for (uint256 i; i < _NUM_DELEGATORS; i++) { | ||
address actor = _delegatorHandler.actors(i); | ||
|
||
assertEq( | ||
token.delegates(actor), | ||
_delegatorHandler.delegations(actor), | ||
"delegatee should be the same as actor" | ||
); | ||
} | ||
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]; | ||
uint256 balance = veANGLE.balanceOf(delegator); | ||
amount += balance; | ||
} | ||
assertEq(votes, amount, "Delegatee should have votes"); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
// 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"; | ||
import { TimestampStore } from "../stores/TimestampStore.sol"; | ||
|
||
contract Delegator is BaseActor { | ||
IveANGLE public veToken; | ||
IERC5805 public veDelegation; | ||
|
||
mapping(address => address) public delegations; | ||
mapping(address => address[]) public reverseDelegations; | ||
address[] public delegatees; | ||
TimestampStore public timestampStore; | ||
|
||
constructor( | ||
uint256 _nbrActor, | ||
IERC20 _agToken, | ||
address _veToken, | ||
address _veDelegation, | ||
TimestampStore _timestampStore | ||
) BaseActor(_nbrActor, "Delegator", _agToken) { | ||
veToken = IveANGLE(_veToken); | ||
veDelegation = IERC5805(_veDelegation); | ||
timestampStore = _timestampStore; | ||
} | ||
|
||
function reverseDelegationsView(address locker) public view returns (address[] memory) { | ||
return reverseDelegations[locker]; | ||
} | ||
|
||
function delegateesLength() public view returns (uint256) { | ||
return delegatees.length; | ||
} | ||
|
||
function delegate(uint256 actorIndex, address toDelegate) public useActor(actorIndex) { | ||
if (toDelegate == address(0)) return; | ||
|
||
uint256 balance = veToken.balanceOf(_currentActor); | ||
address currentDelegatee = delegations[_currentActor]; | ||
|
||
if (balance == 0) { | ||
return; | ||
} | ||
|
||
veDelegation.delegate(toDelegate); | ||
timestampStore.increaseCurrentTimestamp(1 weeks); | ||
vm.warp(timestampStore.currentTimestamp()); | ||
|
||
// Update delegations | ||
if (toDelegate == currentDelegatee) { | ||
return; | ||
} | ||
reverseDelegations[toDelegate].push(_currentActor); | ||
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; | ||
} | ||
} | ||
delegations[_currentActor] = toDelegate; | ||
for (uint256 i; i < delegatees.length; i++) { | ||
if (delegatees[i] == toDelegate) { | ||
return; | ||
} | ||
} | ||
delegatees.push(toDelegate); | ||
} | ||
|
||
function createLock(uint256 actorIndex, uint256 amount, uint256 duration) public useActor(actorIndex) { | ||
if (veToken.locked__end(_currentActor) != 0) { | ||
return; | ||
} | ||
duration = bound(duration, 1 weeks, 365 days * 4); | ||
amount = bound(amount, 1e18, 100e18); | ||
|
||
MockANGLE(address(angle)).mint(_currentActor, amount); | ||
angle.approve(address(veToken), amount); | ||
|
||
veToken.create_lock(amount, block.timestamp + duration); | ||
} | ||
|
||
function withdraw() public { | ||
if (veToken.locked__end(_currentActor) != 0 && veToken.locked__end(_currentActor) < block.timestamp) { | ||
veToken.withdraw(); | ||
} | ||
} | ||
|
||
function extendLockTime(uint256 actorIndex, uint256 duration) public useActor(actorIndex) { | ||
uint256 end = veToken.locked__end(_currentActor); | ||
if (end == 0 || end < block.timestamp || 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); | ||
if (delegations[_currentActor] != address(0)) { | ||
veDelegation.delegate(delegations[_currentActor]); | ||
} | ||
} | ||
|
||
function extendLockAmount(uint256 actorIndex, uint256 amount) public useActor(actorIndex) { | ||
if (veToken.balanceOf(_currentActor) == 0) { | ||
return; | ||
} | ||
amount = bound(amount, 1e18, 100e18); | ||
|
||
MockANGLE(address(angle)).mint(_currentActor, amount); | ||
angle.approve(address(veToken), amount); | ||
veToken.increase_amount(amount); | ||
if (delegations[_currentActor] != address(0)) { | ||
veDelegation.delegate(delegations[_currentActor]); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
// 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"; | ||
import { TimestampStore } from "../stores/TimestampStore.sol"; | ||
|
||
contract Param is BaseActor { | ||
IveANGLE public veToken; | ||
TimestampStore public timestampStore; | ||
|
||
constructor( | ||
uint256 _nbrActor, | ||
IERC20 _agToken, | ||
TimestampStore _timestampStore | ||
) BaseActor(_nbrActor, "Param", _agToken) { | ||
timestampStore = _timestampStore; | ||
} | ||
|
||
function wrap(uint256 duration) public { | ||
duration = bound(duration, 0, 365 days * 5); | ||
timestampStore.increaseCurrentTimestamp(duration); | ||
vm.warp(timestampStore.currentTimestamp()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity >=0.8.19 <0.9.0; | ||
|
||
/// @dev Because Foundry does not commit the state changes between invariant runs, we need to | ||
/// save the current timestamp in a contract with persistent storage. | ||
contract TimestampStore { | ||
uint256 public currentTimestamp; | ||
|
||
constructor() { | ||
currentTimestamp = block.timestamp; | ||
} | ||
|
||
function increaseCurrentTimestamp(uint256 timeJump) external { | ||
currentTimestamp += timeJump; | ||
} | ||
} |