Skip to content

Commit

Permalink
Merge Develop (#121)
Browse files Browse the repository at this point in the history
* 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
6 people authored Aug 3, 2023
1 parent 09c90ce commit c657492
Show file tree
Hide file tree
Showing 29 changed files with 1,899 additions and 994 deletions.
6 changes: 5 additions & 1 deletion .env.example
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
14 changes: 10 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@ install :; git submodule update --init --recursive
build :; forge clean && forge build --optimize --optimizer-runs 1000000

# Tests
tests :; forge clean && forge test --mt test --optimize --optimizer-runs 1000000 -v # --ffi # enable if you need the `ffi` cheat code on HEVM
test-with-gas-report :; forge clean && forge build && forge test --mt test --optimize --optimizer-runs 1000000 -v --gas-report # --ffi # enable if you need the `ffi` cheat code on HEVM
test-invariant :; forge clean && forge t --mt invariant
coverage :; forge coverage
tests :; forge clean && forge test --mt test --optimize --optimizer-runs 1000000 -v # --ffi # enable if you need the `ffi` cheat code on HEVM
test-with-gas-report :; forge clean && forge build && forge test --mt test --optimize --optimizer-runs 1000000 -v --gas-report # --ffi # enable if you need the `ffi` cheat code on HEVM
test-invariant :; ./test/invariants/test-invariant.sh ${SCENARIO} ${NUM_ACTORS} ${NUM_PROPOSALS} ${PER_ADDRESS_TOKEN_REQ_CAP}
test-invariant-all :; forge clean && forge t --mt invariant
test-invariant-multiple-distribution :; forge clean && ./test/invariants/test-invariant.sh MultipleDistribution 2 25 200
coverage :; forge coverage

# Generate Gas Snapshots
snapshot :; forge clean && forge snapshot --optimize --optimize-runs 1000000
Expand All @@ -32,3 +34,7 @@ deploy-grantfund:
eval AJNA_TOKEN=${ajna}
forge script script/GrantFund.s.sol:DeployGrantFund \
--rpc-url ${ETH_RPC_URL} --sender ${DEPLOY_ADDRESS} --keystore ${DEPLOY_KEY} --broadcast -vvv --verify
deploy-burnwrapper:
eval AJNA_TOKEN=${ajna}
forge script script/BurnWrapper.s.sol:DeployBurnWrapper \
--rpc-url ${ETH_RPC_URL} --sender ${DEPLOY_ADDRESS} --keystore ${DEPLOY_KEY} --broadcast -vvv --verify
30 changes: 11 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@

<br>

## **[Tests](./test/README.md)**

For information on running tests and checking code coverage see the **[Tests README](./test/README.md)**.


## **Development**

Install Foundry [instructions](https://github.com/gakonst/foundry/blob/master/README.md#installation) then, install the [foundry](https://github.com/gakonst/foundry) toolchain installer (`foundryup`) with:
Expand All @@ -31,23 +36,6 @@ foundryup
make all
```

#### Run Tests

```bash
make tests
```

#### 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
```

### Contract Deployment
Ensure the following env variables are in your `.env` file or exported into your environment.
| Environment Variable | Purpose |
Expand Down Expand Up @@ -80,11 +68,15 @@ make deploy-ajnatoken mintto=<MINT_TO_ADDRESS>
```
Record the address of the token upon deployment. See [AJNA_TOKEN.md](src/token/AJNA_TOKEN.md#deployment) for validation.

#### Burn Wrapper
To deploy, ensure the correct AJNA token address is specified in code. Then, run:
```
make deploy-burnwrapper ajna=<AJNA_TOKEN_ADDRESS>
```

#### Grant Fund
Deployment of the Grant Coordination Fund requires an argument to specify the address of the AJNA token. The deployment script also uses the token address to determine funding level.

Before deploying, edit `src/grants/base/Storage.sol` to set the correct AJNA token address for the target chain.

To deploy, run:
```
make deploy-grantfund ajna=<AJNA_TOKEN_ADDRESS>
Expand Down
3 changes: 2 additions & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ runs = 150

[invariant]
runs = 3 # The number of calls to make in the invariant tests
depth = 10000 # The number of times to run the invariant tests
depth = 10000 # The number of times to run the invariant tests
call_override = false # Override calls
fail_on_revert = true # Fail the test if the contract reverts
dictionary_weight = 80
include_storage = true
include_push_bytes = true
shrink_sequence = false

# See more config options https://github.com/foundry-rs/foundry/tree/master/config
20 changes: 20 additions & 0 deletions script/BurnWrapper.s.sol
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));
}
}
51 changes: 51 additions & 0 deletions test/README.md
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
```
191 changes: 191 additions & 0 deletions test/interactions/GrantFundWithGnosisSafe.t.sol
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_;
}

}
Loading

0 comments on commit c657492

Please sign in to comment.