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

Merge develop #136

Merged
merged 31 commits into from
Oct 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
3d85654
expand unit test code coverage
May 25, 2023
052687e
Merge branch 'main' of github.com:ajna-finance/ecosystem-coordination…
May 27, 2023
1016cc5
Merge branch 'main' of github.com:ajna-finance/ecosystem-coordination…
May 31, 2023
9334339
Merge branch 'main' of github.com:ajna-finance/ecosystem-coordination…
Jun 2, 2023
c3aaf10
Merge branch 'main' of github.com:ajna-finance/ecosystem-coordination…
Jun 3, 2023
39dff97
Merge branch 'main' of github.com:ajna-finance/ecosystem-coordination…
Jun 6, 2023
ed47e52
Add invariants DP7, SS10, SS11, ES4, ES5 and DR4 (#105)
prateek105 Jun 9, 2023
e7e9d32
Update invariants (#106)
MikeHathaway Jun 13, 2023
48f027f
Check all invariants across multiple distribution periods (#107)
MikeHathaway Jun 16, 2023
dc667a7
Add grant fund interaction with gnosis safe unit test (#109)
prateek105 Jun 19, 2023
85ac65e
Invariant improvements (#108)
MikeHathaway Jun 21, 2023
ce90939
update makefile (#115)
MikeHathaway Jun 22, 2023
23cdb60
various invariant doc cleanups (#117)
MikeHathaway Jun 27, 2023
e618039
Add fuzz test for screening and funding stage (#116)
prateek105 Jun 27, 2023
76ce84e
expand CS7 invariant check (#118)
MikeHathaway Jun 27, 2023
cf5e7f9
Merge branch 'main' of github.com:ajna-finance/ecosystem-coordination…
Jun 28, 2023
1446c5b
adjusted README
EdNoepel Jul 4, 2023
ef9ccfa
Merge pull request #122 from ajna-finance/deployment
EdNoepel Jul 4, 2023
545ab18
Fix make file spacing
prateek105 Jul 5, 2023
7e546ff
deploy BurnWrapper
EdNoepel Aug 1, 2023
dea251c
Merge pull request #123 from ajna-finance/burnwrapper-deployment
EdNoepel Aug 2, 2023
82bb87c
Add deployer contract to deploy grantfund, fund treasury and startNew…
prateek105 Aug 4, 2023
9732ea2
Add readme for deployer contract
prateek105 Aug 7, 2023
b8662da
Merge branch 'develop' into grantfund-deployer
prateek105 Aug 9, 2023
d50335b
PR feedback
prateek105 Aug 9, 2023
7d49734
Update unit test to improve test coverage (#126)
prateek105 Aug 16, 2023
42bb2ca
Add unit test for cycling voting delegation (#128)
prateek105 Aug 16, 2023
86bd09c
Add invariant scenario for multiple fund treasury (#129)
prateek105 Aug 16, 2023
92880a9
PR feedback
prateek105 Aug 18, 2023
0a7edb0
Merge pull request #125 from ajna-finance/grantfund-deployer
EdNoepel Oct 13, 2023
65be9c1
merge main
Oct 13, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,6 @@ deploy-grantfund:
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
--rpc-url ${ETH_RPC_URL} --sender ${DEPLOY_ADDRESS} --keystore ${DEPLOY_KEY} --broadcast -vvv --verify
deploy-grantfund-deployer:
forge create --rpc-url ${ETH_RPC_URL} --keystore ${DEPLOY_KEY} --verify src/grants/Deployer.sol:Deployer
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,17 @@ make deploy-grantfund ajna=<AJNA_TOKEN_ADDRESS>
```

See [GRANT_FUND.md](src/grants/GRANT_FUND.md#deployment) for next steps.

#### Grant Fund deployer
Deployer contract can be used to deploy grant fund, fund treasury and start distribution in a single call to avoid someone starting a distribution without treasury.

Steps to use Deployer contract to deploy grant Fund:
1. Deploy `Deployer` contract.
2. Approve `<treasury_amount>` `AJNA ` to `Deployer` contract from `treasury` address.
3. Call `deployGrantFund(address ajnaToken_, uint256 treasury_)` from `treasury` address to deploy grant fund and start distribution with treasury amount.
4. GrantFund can be verified using remix contract verification plugin, foundry or hardhat.

To deploy Deployer contract, run:
```
make deploy-grantfund-deployer
```
40 changes: 40 additions & 0 deletions src/grants/Deployer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.18;

import { IERC20 } from "@oz/token/ERC20/IERC20.sol";

import { GrantFund } from "./GrantFund.sol";

contract Deployer {

error IncorrectTreasuryBalance();

error DistributionNotStarted();

GrantFund public grantFund;

function deployGrantFund(address ajnaToken_, uint256 treasury_) public returns (GrantFund grantFund_) {

// deploy grant Fund
grantFund_ = new GrantFund(ajnaToken_);

// Approve ajna token to fund treasury
IERC20(ajnaToken_).approve(address(grantFund_), treasury_);

// Transfer treasury ajna tokens to Deployer contract
IERC20(ajnaToken_).transferFrom(msg.sender, address(this), treasury_);

// Fund treasury and start new distribution
grantFund_.fundTreasury(treasury_);
grantFund_.startNewDistributionPeriod();

// check treasury balance is correct
if(IERC20(ajnaToken_).balanceOf(address(grantFund_)) != treasury_) revert IncorrectTreasuryBalance();

// check new distribution started
if(grantFund_.getDistributionId() != 1) revert DistributionNotStarted();

grantFund = grantFund_;
}
}
9 changes: 7 additions & 2 deletions test/invariants/handlers/Handler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,10 @@ contract Handler is Test, GrantFundTestHelper {
_tokenDeployer = tokenDeployer_;

// instantiate actors
actors = _buildActors(numOfActors_, tokensToDistribute_);
address[] memory newActors = _buildActors(numOfActors_, tokensToDistribute_);
for (uint256 i = 0; i < newActors.length; ++i) {
if (newActors[i] != address(0)) actors.push(newActors[i]);
}

// set Test invariant contract
testContract = ITestBase(testContract_);
Expand Down Expand Up @@ -141,9 +144,11 @@ contract Handler is Test, GrantFundTestHelper {
actors_ = new address[](numOfActors_);
uint256 tokensDistributed = 0;

uint256 existingActors = actors.length;

for (uint256 i = 0; i < numOfActors_; ++i) {
// create actor
address actor = makeAddr(string(abi.encodePacked("Actor", Strings.toString(i))));
address actor = makeAddr(string(abi.encodePacked("Actor", Strings.toString(existingActors + i))));
actors_[i] = actor;

// transfer ajna tokens to the actor
Expand Down
49 changes: 49 additions & 0 deletions test/invariants/handlers/StandardHandler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,55 @@ contract StandardHandler is Handler {
}
}

function fundTreasury(uint256 actorIndex_, uint256 treasuryAmount_) external useCurrentBlock useRandomActor(actorIndex_) {
numberOfCalls['SFH.fundTreasury']++;

// bound treasury amount
treasuryAmount_ = bound(treasuryAmount_, 0, _ajna.balanceOf(_actor));

if (treasuryAmount_ == 0) return;

uint256 previousTreasury = _grantFund.treasury();

// fund treasury
changePrank(_actor);
_ajna.approve(address(_grantFund), type(uint256).max);
_grantFund.fundTreasury(treasuryAmount_);

// ensure amount is added into treasury
assertEq(_grantFund.treasury(), previousTreasury + treasuryAmount_);
}

function transferAjna(uint256 fromActorIndex_, uint256 toActorIndex_, uint256 amountToTransfer_) external useCurrentBlock useRandomActor(fromActorIndex_) {
numberOfCalls['SFH.transferAjna']++;

// bound actor
toActorIndex_ = bound(toActorIndex_, 0, actors.length - 1);
address toActor = actors[toActorIndex_];

amountToTransfer_ = bound(amountToTransfer_, 0, _ajna.balanceOf(_actor));

if (amountToTransfer_ == 0 || _actor == toActor) return;

_ajna.transfer(toActor, amountToTransfer_);
}

function addActors(uint256 noOfActorsToAdd_, uint256 tokensToDistribute_) external useCurrentBlock {
numberOfCalls['SFH.addActors']++;

// bound tokens to distribute and no of actors to add
noOfActorsToAdd_ = bound(noOfActorsToAdd_, 1, 10);
tokensToDistribute_ = bound(tokensToDistribute_, 0, _ajna.balanceOf(_tokenDeployer));

if (tokensToDistribute_ == 0) return;

address[] memory newActors = _buildActors(noOfActorsToAdd_, tokensToDistribute_);

// add new actors to actors array
for (uint256 i = 0; i < newActors.length; ++i) {
if (newActors[i] != address(0)) actors.push(newActors[i]);
}
}
/**********************************/
/*** External Utility Functions ***/
/**********************************/
Expand Down
85 changes: 85 additions & 0 deletions test/invariants/scenarios/MultipleTreasuryFundingInvariant.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// SPDX-License-Identifier: MIT

pragma solidity 0.8.18;

import { console } from "@std/console.sol";
import { SafeCast } from "@oz/utils/math/SafeCast.sol";

import { Maths } from "../../../src/grants/libraries/Maths.sol";

import { StandardTestBase } from "../base/StandardTestBase.sol";
import { StandardHandler } from "../handlers/StandardHandler.sol";
import { Handler } from "../handlers/Handler.sol";

contract MultipleTreasuryFundingInvariant is StandardTestBase {

// run tests against all functions, having just started a distribution period
function setUp() public virtual override {
super.setUp();

// set the list of function selectors to run
bytes4[] memory selectors = new bytes4[](11);
selectors[0] = _standardHandler.startNewDistributionPeriod.selector;
selectors[1] = _standardHandler.propose.selector;
selectors[2] = _standardHandler.screeningVote.selector;
selectors[3] = _standardHandler.fundingVote.selector;
selectors[4] = _standardHandler.updateSlate.selector;
selectors[5] = _standardHandler.execute.selector;
selectors[6] = _standardHandler.claimDelegateReward.selector;
selectors[7] = _standardHandler.roll.selector;
selectors[8] = _standardHandler.fundTreasury.selector;
selectors[9] = _standardHandler.transferAjna.selector;
selectors[10] = _standardHandler.addActors.selector;

// ensure utility functions are excluded from the invariant runs
targetSelector(FuzzSelector({
addr: address(_standardHandler),
selectors: selectors
}));

// update scenarioType to fast to have larger rolls
_standardHandler.setCurrentScenarioType(Handler.ScenarioType.Fast);

vm.roll(block.number + 100);
currentBlock = block.number;
}

function invariant_all() external useCurrentBlock {
// screening invariants
_invariant_SS1_SS3_SS4_SS5_SS6_SS7_SS8_SS10_SS11_P1_P2(_grantFund, _standardHandler);
_invariant_SS2_SS4_SS9(_grantFund, _standardHandler);

// funding invariants
_invariant_FS1_FS2_FS3(_grantFund, _standardHandler);
_invariant_FS4_FS5_FS6_FS7_FS8(_grantFund, _standardHandler);

// finalize invariants
_invariant_CS1_CS2_CS3_CS4_CS5_CS6_CS7(_grantFund, _standardHandler);
_invariant_ES1_ES2_ES3_ES4_ES5(_grantFund, _standardHandler);
_invariant_DR1_DR2_DR3_DR4_DR5(_grantFund, _standardHandler);

// distribution period invariants
_invariant_DP1_DP2_DP3_DP4_DP5(_grantFund, _standardHandler);
_invariant_DP6(_grantFund, _standardHandler);
_invariant_T1_T2(_grantFund);
}

function invariant_call_summary() external useCurrentBlock {
uint24 distributionId = _grantFund.getDistributionId();

_logger.logCallSummary();
_logger.logTimeSummary();
_logger.logProposalSummary();
console.log("scenario type", uint8(_standardHandler.getCurrentScenarioType()));

while (distributionId > 0) {

_logger.logFundingSummary(distributionId);
_logger.logFinalizeSummary(distributionId);
_logger.logActorSummary(distributionId, true, true);
_logger.logActorDelegationRewards(distributionId);

--distributionId;
}
}
}
48 changes: 48 additions & 0 deletions test/unit/AjnaToken.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -313,4 +313,52 @@ contract AjnaTokenTest is Test {
assertEq(_token.getVotes(address(3333)), 0);
assertEq(_token.getVotes(address(4444)), 2_000_000_000 * 1e18);
}

function testCyclingVotingDelegation() external {
// define actors and set their balances
address actor1 = makeAddr("actor1");
deal(address(_token), actor1, 1_000 * 1e18);

address actor2 = makeAddr("actor2");
deal(address(_token), actor2, 2_000 * 1e18);

address actor3 = makeAddr("actor3");
deal(address(_token), actor3, 5_000 * 1e18);

// actor1 delegates votes to actor2
changePrank(actor1);
_token.delegate(actor2);

// ensure actor2 has votes equals to actor1 balance
assertEq(_token.getVotes(actor1), 0);
assertEq(_token.getVotes(actor2), 1_000 * 1e18);
assertEq(_token.getVotes(actor3), 0);

// actor2 delegates votes to actor3
changePrank(actor2);
_token.delegate(actor3);

// ensure actor3 has votes equals to actor2 balance
assertEq(_token.getVotes(actor1), 0);
assertEq(_token.getVotes(actor2), 1_000 * 1e18);
assertEq(_token.getVotes(actor3), 2_000 * 1e18);

// actor3 delegates votes to actor1
changePrank(actor3);
_token.delegate(actor1);

// ensure actor1 has votes equals to actor3 balance
assertEq(_token.getVotes(actor1), 5_000 * 1e18);
assertEq(_token.getVotes(actor2), 1_000 * 1e18);
assertEq(_token.getVotes(actor3), 2_000 * 1e18);

// actor3 delegates votes to actor2
changePrank(actor3);
_token.delegate(actor2);

// ensure actor2 has votes equals to sum of actor3 and actor1 balance
assertEq(_token.getVotes(actor1), 0);
assertEq(_token.getVotes(actor2), 6_000 * 1e18);
assertEq(_token.getVotes(actor3), 2_000 * 1e18);
}
}
34 changes: 34 additions & 0 deletions test/unit/Deployer.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.18;

import { Test } from "@std/Test.sol";

import { Deployer } from "../../src/grants/Deployer.sol";
import { GrantFund } from "../../src/grants/GrantFund.sol";
import { TestAjnaToken } from "../utils/harness/TestAjnaToken.sol";

contract DeployerTest is Test {

function testGrantFundDeployment() external {
address owner = makeAddr("owner");
vm.startPrank(owner);

uint256 treasury = 50_000_000 * 1e18;

TestAjnaToken ajnaToken = new TestAjnaToken();
ajnaToken.mint(owner, treasury);

Deployer deployer = new Deployer();
ajnaToken.approve(address(deployer), treasury);

GrantFund grantFund = deployer.deployGrantFund(address(ajnaToken), treasury);

assertEq(grantFund.getDistributionId(), 1);

(,,,uint256 fundAvailable,,) = grantFund.getDistributionPeriodInfo(1);

assertEq(grantFund.treasury(), treasury - fundAvailable);

assertEq(fundAvailable, treasury * 3 / 100);
}
}
4 changes: 4 additions & 0 deletions test/unit/StandardFunding.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -905,6 +905,10 @@ contract StandardFundingGrantFundTest is GrantFundTestHelper {
_screeningVote(_grantFund, _tokenHolder3, testProposals[12].proposalId, 5_000 * 1e18);
_screeningVote(_grantFund, _tokenHolder5, testProposals[12].proposalId, 50_000 * 1e18);
assertEq(_findProposalIndex(testProposals[12].proposalId, _grantFund.getTopTenProposals(distributionId)), -1);

// cast screening votes on a proposal not in the top 10 to make it top 10 proposal
_screeningVote(_grantFund, _tokenHolder7, testProposals[12].proposalId, 2_500_000 * 1e18);
assertEq(_findProposalIndex(testProposals[12].proposalId, _grantFund.getTopTenProposals(distributionId)), 2);
}

function testStartNewDistributionPeriod() external {
Expand Down
Loading