-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* expand unit test code coverage * Add invariants DP7, SS10, SS11, ES4, ES5 and DR4 (#105) * Add invariants DP7, SS10, SS11, ES4, ES5 and DR4 * Fix stack too deep issue * PR feedback * Update invariants (#106) * rename invariant tests * refactor invariant test structure * additional refactoring * refactor multiple distribution invariants * fix stack too deep error * fix screening invariants * fix DR4 bug * remove redundant DP6 check * pr feedback --------- Co-authored-by: Mike <[email protected]> * Check all invariants across multiple distribution periods (#107) * add support for multiple distribution periods to funding invariants * fix FS7 for multiple dist * begin updating screening invariants * remaining fixes to funding stage invariants * get screening stage invariants working in multiple distribution scenario * pr feedback --------- Co-authored-by: Mike <[email protected]> * Add grant fund interaction with gnosis safe unit test (#109) * Invariant improvements (#108) * add support for multiple distribution periods to funding invariants * fix FS7 for multiple dist * begin updating screening invariants * remaining fixes to funding stage invariants * get screening stage invariants working in multiple distribution scenario * begin fixing todos * fix negative funding votes in happy path; update proposal token requested; update DR5 invariant check * improve ES2 invariant check * add Logger contract; cleanup logging; add useCurrentBlock modifier to TestBase * add support for env variables in invariant tests * expand usage of useCurrentBlock * cleanup invariants list doc * add P1 invariant * cleanups * cleanup multiple distribution scenario logging; add logActorDelegationRewards * fix findUnclaimedRewards; expand logging * improve DR5 check across multiple periods * cleanups * initial pr feedback * Invariant Improvements: Fix Actor log and improve `screeningVote` handler (#110) * Fix actors logs for Multiple Distribution Invariant when no distribution started * Improve screeningVote by using _screeningVoteParams to generate parameter * Add configuration for logging in invariants (#111) * update README * update docs * paramterize percentageTokensReq * fix compilation warnings * Add invariant CS7: The highest submitted funded proposal slate should have won or tied depending on when it was submitted. (#113) --------- Co-authored-by: Mike <[email protected]> Co-authored-by: Prateek Gupta <[email protected]> * update makefile (#115) * various invariant doc cleanups (#117) * Add fuzz test for screening and funding stage (#116) * expand CS7 invariant check (#118) * expand CS7 invariant check * fix nit --------- Co-authored-by: Mike <[email protected]> * adjusted README * Fix make file spacing * deploy BurnWrapper --------- Co-authored-by: Mike <[email protected]> Co-authored-by: Prateek Gupta <[email protected]> Co-authored-by: Ed Noepel <[email protected]> Co-authored-by: Ed Noepel <[email protected]> Co-authored-by: prateek105 <[email protected]>
- Loading branch information
1 parent
09c90ce
commit c657492
Showing
29 changed files
with
1,899 additions
and
994 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,7 @@ | ||
## Ethereum node endpoint ## | ||
ETH_RPC_URL= | ||
FOUNDRY_EVM_VERSION=paris | ||
FOUNDRY_EVM_VERSION=paris | ||
SCENARIO=MultipleDistribution # Type of Invariant Scenario to run: Screening | FundingInvariant | Finalize | MultipleDistribution | ||
NUM_ACTORS=10 # Number of actors to simulate in invariants | ||
NUM_PROPOSALS=10 # Maximum number of proposals to simulate in invariants | ||
PER_ADDRESS_TOKEN_REQ_CAP=10 # Percentage of funds available to request per proposal recipient in invariants |
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
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,20 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity 0.8.7; | ||
|
||
import { Script } from "forge-std/Script.sol"; | ||
import { console } from "forge-std/console.sol"; | ||
|
||
import { BurnWrappedAjna } from "../src/token/BurnWrapper.sol"; | ||
import { IERC20 } from "@oz/token/ERC20/IERC20.sol"; | ||
|
||
contract DeployBurnWrapper is Script { | ||
function run() public { | ||
IERC20 ajna = IERC20(vm.envAddress("AJNA_TOKEN")); | ||
|
||
vm.startBroadcast(); | ||
address wrapperAddress = address(new BurnWrappedAjna(ajna)); | ||
vm.stopBroadcast(); | ||
|
||
console.log("Created BurnWrapper at %s for AJNA token at %s", wrapperAddress, address(ajna)); | ||
} | ||
} |
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,51 @@ | ||
# Ecosystem Coordination Tests | ||
## Forge tests | ||
### Unit tests | ||
- validation tests: | ||
```bash | ||
make tests | ||
``` | ||
- validation tests with gas report: | ||
```bash | ||
make test-with-gas-report | ||
``` | ||
|
||
### Invariant tests | ||
#### Configuration | ||
Invariant test scenarios can be externally configured by customizing following environment variables: | ||
| Variable | Default | Description | | ||
| ------------- | ------------- | ------------- | | ||
| SCENARIO | MultipleDistribution | Type of Invariant Scenario to run: Screening, FundingInvariant, Finalize, MultipleDistribution | | ||
| PER_ADDRESS_TOKEN_REQ_CAP | 10 | Percentage of funds available to request per proposal recipient in invariants | | ||
| NUM_ACTORS | 20 | Max number of actors to participate in invariant testing | | ||
| NUM_PROPOSALS | 200 | Max number of proposals that can be proposed in invariant testing | | ||
| LOGS_VERBOSITY | 0 | <p> Details to log <p> 0 = No Logs <p> 1 = Calls details, Proposal details, Time details <p> 2 = Calls details, Proposal details, Time details, Funding proposal details, Finalize proposals details <p> 3 = Calls details, Proposal details, Time details, Funding proposal details, Finalize proposals details, Actor details | ||
|
||
#### Custom Scenarios | ||
|
||
Custom scenario configurations are defined in [scenarios](forge/invariants/scenarios/) directory. | ||
For running a custom scenario | ||
```bash | ||
make test-invariant SCENARIO=<custom-pool> | ||
``` | ||
For example, to test all invariants for multiple distribution (Roll between each handler call can be max 5000 blocks): | ||
```bash | ||
make test-invariant SCENARIO=MultipleDistribution | ||
``` | ||
|
||
#### Commands | ||
- run all invariant tests: | ||
```bash | ||
make test-invariant-all | ||
``` | ||
|
||
### Code Coverage | ||
- generate basic code coverage report: | ||
```bash | ||
make coverage | ||
``` | ||
- exclude tests from code coverage report: | ||
``` | ||
apt-get install lcov | ||
bash ../check-code-coverage.sh | ||
``` |
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,191 @@ | ||
// SPDX-License-Identifier: MIT | ||
|
||
pragma solidity 0.8.18; | ||
|
||
import { Strings } from "@oz/utils/Strings.sol"; | ||
|
||
import { GrantFund } from "../../src/grants/GrantFund.sol"; | ||
import { IGrantFundState } from "../../src/grants/interfaces/IGrantFundState.sol"; | ||
|
||
import "./interfaces.sol"; | ||
import { GrantFundTestHelper } from "../utils/GrantFundTestHelper.sol"; | ||
import { IAjnaToken } from "../utils/IAjnaToken.sol"; | ||
|
||
contract GrantFundWithGnosisSafe is GrantFundTestHelper { | ||
|
||
IGnosisSafeFactory internal _gnosisSafeFactory; | ||
IGnosisSafe internal _gnosisSafe; | ||
|
||
IAjnaToken internal _token; | ||
GrantFund internal _grantFund; | ||
|
||
// Ajna token Holder at the Ajna contract creation on mainnet | ||
address internal _tokenDeployer = 0x666cf594fB18622e1ddB91468309a7E194ccb799; | ||
|
||
struct MultiSigOwner { | ||
address walletAddress; | ||
uint256 privateKey; | ||
} | ||
|
||
struct Proposals { | ||
address[] targets; | ||
uint256[] values; | ||
bytes[] calldatas; | ||
string description; | ||
bytes32 descriptionHash; | ||
uint256 proposalId; | ||
} | ||
|
||
address[] internal _votersArr; | ||
|
||
uint256 _treasury = 500_000_000 * 1e18; | ||
|
||
uint256 _nonces = 0; | ||
|
||
function setUp() external { | ||
vm.createSelectFork(vm.envString("ETH_RPC_URL")); | ||
address gnosisSafeFactoryAddress = 0xa6B71E26C5e0845f74c812102Ca7114b6a896AB2; // mainnet gnosisSafeFactory contract address | ||
_gnosisSafeFactory = IGnosisSafeFactory(gnosisSafeFactoryAddress); | ||
|
||
address singletonAddress = 0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552; // mainnet singleton contract address | ||
|
||
// deploy gnosis safe | ||
address gnosisSafeAddress = _gnosisSafeFactory.createProxy(singletonAddress, ""); | ||
|
||
_gnosisSafe = IGnosisSafe(gnosisSafeAddress); | ||
|
||
(_grantFund, _token) = _deployAndFundGrantFund(_tokenDeployer, _treasury, _votersArr, 0); | ||
|
||
// transfer tokens to gnosis safe | ||
changePrank(_tokenDeployer); | ||
_token.transfer(gnosisSafeAddress, 25_000_000 * 1e18); | ||
} | ||
|
||
function testGrantFundWithMultiSigWallet() external { | ||
MultiSigOwner[] memory multiSigOwners = new MultiSigOwner[](3); | ||
|
||
(multiSigOwners[0].walletAddress, multiSigOwners[0].privateKey) = makeAddrAndKey("_multiSigOwner1"); | ||
(multiSigOwners[1].walletAddress, multiSigOwners[1].privateKey) = makeAddrAndKey("_multiSigOwner2"); | ||
(multiSigOwners[2].walletAddress, multiSigOwners[2].privateKey) = makeAddrAndKey("_multiSigOwner3"); | ||
|
||
address[] memory owners = new address[](3); | ||
owners[0] = multiSigOwners[0].walletAddress; | ||
owners[1] = multiSigOwners[1].walletAddress; | ||
owners[2] = multiSigOwners[2].walletAddress; | ||
|
||
// Setup gnosis safe with 3 owners and 2 threshold to execute transaction | ||
_gnosisSafe.setup(owners, 2, address(0), "", address(0), address(0), 0, payable(address(0))); | ||
|
||
// self delegate votes | ||
bytes memory callData = abi.encodeWithSignature("delegate(address)", address(_gnosisSafe)); | ||
_executeTransaction(address(_token), callData, multiSigOwners); | ||
|
||
vm.roll(block.number + 100); | ||
|
||
// Start distribution period | ||
_startDistributionPeriod(_grantFund); | ||
|
||
uint24 distributionId = _grantFund.getDistributionId(); | ||
|
||
// generate proposals for distribution | ||
Proposals[] memory proposals = _generateProposals(2); | ||
|
||
// propose first proposal | ||
callData = abi.encodeWithSignature("propose(address[],uint256[],bytes[],string)", proposals[0].targets, proposals[0].values, proposals[0].calldatas, proposals[0].description); | ||
_executeTransaction(address(_grantFund), callData, multiSigOwners); | ||
|
||
// propose second proposal | ||
callData = abi.encodeWithSignature("propose(address[],uint256[],bytes[],string)", proposals[1].targets, proposals[1].values, proposals[1].calldatas, proposals[1].description); | ||
_executeTransaction(address(_grantFund), callData, multiSigOwners); | ||
|
||
// skip to screening stage | ||
vm.roll(block.number + 100); | ||
|
||
// construct vote params | ||
IGrantFundState.ScreeningVoteParams[] memory screeningVoteParams = new IGrantFundState.ScreeningVoteParams[](1); | ||
screeningVoteParams[0].proposalId = proposals[0].proposalId; | ||
screeningVoteParams[0].votes = 20_000_000 * 1e18; | ||
|
||
// cast screening vote | ||
callData = abi.encodeWithSignature("screeningVote((uint256,uint256)[])", screeningVoteParams); | ||
_executeTransaction(address(_grantFund), callData, multiSigOwners); | ||
|
||
// skip to funding stage | ||
vm.roll(block.number + 550_000); | ||
|
||
// construct vote params | ||
IGrantFundState.FundingVoteParams[] memory fundingVoteParams = new IGrantFundState.FundingVoteParams[](1); | ||
fundingVoteParams[0].proposalId = proposals[0].proposalId; | ||
fundingVoteParams[0].votesUsed = 20_000_000 * 1e18; | ||
|
||
// cast funding vote | ||
callData = abi.encodeWithSignature("fundingVote((uint256,int256)[])", fundingVoteParams); | ||
_executeTransaction(address(_grantFund), callData, multiSigOwners); | ||
|
||
// skip to the Challenge period | ||
vm.roll(block.number + 50_000); | ||
|
||
// construct potential proposal slate | ||
uint256[] memory potentialProposalSlate = new uint256[](1); | ||
potentialProposalSlate[0] = proposals[0].proposalId; | ||
|
||
// update slate | ||
callData = abi.encodeWithSignature("updateSlate(uint256[],uint24)", potentialProposalSlate, distributionId); | ||
_executeTransaction(address(_grantFund), callData, multiSigOwners); | ||
|
||
// skip to the end of distribution period | ||
vm.roll(block.number + 100_000); | ||
|
||
// execute proposal | ||
callData = abi.encodeWithSignature("execute(address[],uint256[],bytes[],bytes32)", proposals[0].targets, proposals[0].values, proposals[0].calldatas, proposals[0].descriptionHash); | ||
_executeTransaction(address(_grantFund), callData, multiSigOwners); | ||
|
||
// claim delegate reward | ||
callData = abi.encodeWithSignature("claimDelegateReward(uint24)", distributionId); | ||
_executeTransaction(address(_grantFund), callData, multiSigOwners); | ||
} | ||
|
||
function _executeTransaction(address contractAddress, bytes memory callData, MultiSigOwner[] memory multiSigOwners) internal { | ||
bytes32 transactionHash = _gnosisSafe.getTransactionHash(contractAddress, 0, callData, IGnosisSafe.Operation.Call, 0, 0, 0, address(0), address(0), _nonces++); | ||
|
||
(uint8 v, bytes32 r, bytes32 s) = vm.sign(multiSigOwners[0].privateKey, transactionHash); | ||
bytes memory signature1 = abi.encodePacked(r, s, v); | ||
|
||
(v, r, s) = vm.sign(multiSigOwners[1].privateKey, transactionHash); | ||
bytes memory signature2 = abi.encodePacked(r, s, v); | ||
|
||
bytes memory signatures = abi.encodePacked(signature1, signature2); | ||
_gnosisSafe.execTransaction(contractAddress, 0, callData, IGnosisSafe.Operation.Call, 0, 0, 0, address(0), payable(address(0)), signatures); | ||
|
||
} | ||
|
||
function _generateProposals(uint256 noOfProposals_) internal view returns(Proposals[] memory) { | ||
Proposals[] memory proposals_ = new Proposals[](noOfProposals_); | ||
|
||
// generate proposal targets | ||
address[] memory ajnaTokenTargets = new address[](1); | ||
ajnaTokenTargets[0] = address(_token); | ||
|
||
// generate proposal values | ||
uint256[] memory values = new uint256[](1); | ||
values[0] = 0; | ||
|
||
// generate proposal calldata | ||
bytes[] memory proposalCalldata = new bytes[](1); | ||
proposalCalldata[0] = abi.encodeWithSignature( | ||
"transfer(address,uint256)", | ||
address(_gnosisSafe), | ||
1_000_000 * 1e18 | ||
); | ||
|
||
for(uint i = 0; i < noOfProposals_; i++) { | ||
// generate proposal message | ||
string memory description = string(abi.encodePacked("Proposal", Strings.toString(i))); | ||
bytes32 descriptionHash = _grantFund.getDescriptionHash(description); | ||
uint256 proposalId = _grantFund.hashProposal(ajnaTokenTargets, values, proposalCalldata, descriptionHash); | ||
proposals_[i] = Proposals(ajnaTokenTargets, values, proposalCalldata, description, descriptionHash, proposalId); | ||
} | ||
return proposals_; | ||
} | ||
|
||
} |
Oops, something went wrong.