Skip to content

Commit 640d5c9

Browse files
authored
Merge pull request #136 from ajna-finance/develop
Merge develop
2 parents 545ec42 + 65be9c1 commit 640d5c9

File tree

9 files changed

+284
-3
lines changed

9 files changed

+284
-3
lines changed

Makefile

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,6 @@ deploy-grantfund:
3838
deploy-burnwrapper:
3939
eval AJNA_TOKEN=${ajna}
4040
forge script script/BurnWrapper.s.sol:DeployBurnWrapper \
41-
--rpc-url ${ETH_RPC_URL} --sender ${DEPLOY_ADDRESS} --keystore ${DEPLOY_KEY} --broadcast -vvv --verify
41+
--rpc-url ${ETH_RPC_URL} --sender ${DEPLOY_ADDRESS} --keystore ${DEPLOY_KEY} --broadcast -vvv --verify
42+
deploy-grantfund-deployer:
43+
forge create --rpc-url ${ETH_RPC_URL} --keystore ${DEPLOY_KEY} --verify src/grants/Deployer.sol:Deployer

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,3 +83,17 @@ make deploy-grantfund ajna=<AJNA_TOKEN_ADDRESS>
8383
```
8484

8585
See [GRANT_FUND.md](src/grants/GRANT_FUND.md#deployment) for next steps.
86+
87+
#### Grant Fund deployer
88+
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.
89+
90+
Steps to use Deployer contract to deploy grant Fund:
91+
1. Deploy `Deployer` contract.
92+
2. Approve `<treasury_amount>` `AJNA ` to `Deployer` contract from `treasury` address.
93+
3. Call `deployGrantFund(address ajnaToken_, uint256 treasury_)` from `treasury` address to deploy grant fund and start distribution with treasury amount.
94+
4. GrantFund can be verified using remix contract verification plugin, foundry or hardhat.
95+
96+
To deploy Deployer contract, run:
97+
```
98+
make deploy-grantfund-deployer
99+
```

src/grants/Deployer.sol

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity 0.8.18;
4+
5+
import { IERC20 } from "@oz/token/ERC20/IERC20.sol";
6+
7+
import { GrantFund } from "./GrantFund.sol";
8+
9+
contract Deployer {
10+
11+
error IncorrectTreasuryBalance();
12+
13+
error DistributionNotStarted();
14+
15+
GrantFund public grantFund;
16+
17+
function deployGrantFund(address ajnaToken_, uint256 treasury_) public returns (GrantFund grantFund_) {
18+
19+
// deploy grant Fund
20+
grantFund_ = new GrantFund(ajnaToken_);
21+
22+
// Approve ajna token to fund treasury
23+
IERC20(ajnaToken_).approve(address(grantFund_), treasury_);
24+
25+
// Transfer treasury ajna tokens to Deployer contract
26+
IERC20(ajnaToken_).transferFrom(msg.sender, address(this), treasury_);
27+
28+
// Fund treasury and start new distribution
29+
grantFund_.fundTreasury(treasury_);
30+
grantFund_.startNewDistributionPeriod();
31+
32+
// check treasury balance is correct
33+
if(IERC20(ajnaToken_).balanceOf(address(grantFund_)) != treasury_) revert IncorrectTreasuryBalance();
34+
35+
// check new distribution started
36+
if(grantFund_.getDistributionId() != 1) revert DistributionNotStarted();
37+
38+
grantFund = grantFund_;
39+
}
40+
}

test/invariants/handlers/Handler.sol

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,10 @@ contract Handler is Test, GrantFundTestHelper {
7070
_tokenDeployer = tokenDeployer_;
7171

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

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

147+
uint256 existingActors = actors.length;
148+
144149
for (uint256 i = 0; i < numOfActors_; ++i) {
145150
// create actor
146-
address actor = makeAddr(string(abi.encodePacked("Actor", Strings.toString(i))));
151+
address actor = makeAddr(string(abi.encodePacked("Actor", Strings.toString(existingActors + i))));
147152
actors_[i] = actor;
148153

149154
// transfer ajna tokens to the actor

test/invariants/handlers/StandardHandler.sol

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,55 @@ contract StandardHandler is Handler {
388388
}
389389
}
390390

391+
function fundTreasury(uint256 actorIndex_, uint256 treasuryAmount_) external useCurrentBlock useRandomActor(actorIndex_) {
392+
numberOfCalls['SFH.fundTreasury']++;
393+
394+
// bound treasury amount
395+
treasuryAmount_ = bound(treasuryAmount_, 0, _ajna.balanceOf(_actor));
396+
397+
if (treasuryAmount_ == 0) return;
398+
399+
uint256 previousTreasury = _grantFund.treasury();
400+
401+
// fund treasury
402+
changePrank(_actor);
403+
_ajna.approve(address(_grantFund), type(uint256).max);
404+
_grantFund.fundTreasury(treasuryAmount_);
405+
406+
// ensure amount is added into treasury
407+
assertEq(_grantFund.treasury(), previousTreasury + treasuryAmount_);
408+
}
409+
410+
function transferAjna(uint256 fromActorIndex_, uint256 toActorIndex_, uint256 amountToTransfer_) external useCurrentBlock useRandomActor(fromActorIndex_) {
411+
numberOfCalls['SFH.transferAjna']++;
412+
413+
// bound actor
414+
toActorIndex_ = bound(toActorIndex_, 0, actors.length - 1);
415+
address toActor = actors[toActorIndex_];
416+
417+
amountToTransfer_ = bound(amountToTransfer_, 0, _ajna.balanceOf(_actor));
418+
419+
if (amountToTransfer_ == 0 || _actor == toActor) return;
420+
421+
_ajna.transfer(toActor, amountToTransfer_);
422+
}
423+
424+
function addActors(uint256 noOfActorsToAdd_, uint256 tokensToDistribute_) external useCurrentBlock {
425+
numberOfCalls['SFH.addActors']++;
426+
427+
// bound tokens to distribute and no of actors to add
428+
noOfActorsToAdd_ = bound(noOfActorsToAdd_, 1, 10);
429+
tokensToDistribute_ = bound(tokensToDistribute_, 0, _ajna.balanceOf(_tokenDeployer));
430+
431+
if (tokensToDistribute_ == 0) return;
432+
433+
address[] memory newActors = _buildActors(noOfActorsToAdd_, tokensToDistribute_);
434+
435+
// add new actors to actors array
436+
for (uint256 i = 0; i < newActors.length; ++i) {
437+
if (newActors[i] != address(0)) actors.push(newActors[i]);
438+
}
439+
}
391440
/**********************************/
392441
/*** External Utility Functions ***/
393442
/**********************************/
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity 0.8.18;
4+
5+
import { console } from "@std/console.sol";
6+
import { SafeCast } from "@oz/utils/math/SafeCast.sol";
7+
8+
import { Maths } from "../../../src/grants/libraries/Maths.sol";
9+
10+
import { StandardTestBase } from "../base/StandardTestBase.sol";
11+
import { StandardHandler } from "../handlers/StandardHandler.sol";
12+
import { Handler } from "../handlers/Handler.sol";
13+
14+
contract MultipleTreasuryFundingInvariant is StandardTestBase {
15+
16+
// run tests against all functions, having just started a distribution period
17+
function setUp() public virtual override {
18+
super.setUp();
19+
20+
// set the list of function selectors to run
21+
bytes4[] memory selectors = new bytes4[](11);
22+
selectors[0] = _standardHandler.startNewDistributionPeriod.selector;
23+
selectors[1] = _standardHandler.propose.selector;
24+
selectors[2] = _standardHandler.screeningVote.selector;
25+
selectors[3] = _standardHandler.fundingVote.selector;
26+
selectors[4] = _standardHandler.updateSlate.selector;
27+
selectors[5] = _standardHandler.execute.selector;
28+
selectors[6] = _standardHandler.claimDelegateReward.selector;
29+
selectors[7] = _standardHandler.roll.selector;
30+
selectors[8] = _standardHandler.fundTreasury.selector;
31+
selectors[9] = _standardHandler.transferAjna.selector;
32+
selectors[10] = _standardHandler.addActors.selector;
33+
34+
// ensure utility functions are excluded from the invariant runs
35+
targetSelector(FuzzSelector({
36+
addr: address(_standardHandler),
37+
selectors: selectors
38+
}));
39+
40+
// update scenarioType to fast to have larger rolls
41+
_standardHandler.setCurrentScenarioType(Handler.ScenarioType.Fast);
42+
43+
vm.roll(block.number + 100);
44+
currentBlock = block.number;
45+
}
46+
47+
function invariant_all() external useCurrentBlock {
48+
// screening invariants
49+
_invariant_SS1_SS3_SS4_SS5_SS6_SS7_SS8_SS10_SS11_P1_P2(_grantFund, _standardHandler);
50+
_invariant_SS2_SS4_SS9(_grantFund, _standardHandler);
51+
52+
// funding invariants
53+
_invariant_FS1_FS2_FS3(_grantFund, _standardHandler);
54+
_invariant_FS4_FS5_FS6_FS7_FS8(_grantFund, _standardHandler);
55+
56+
// finalize invariants
57+
_invariant_CS1_CS2_CS3_CS4_CS5_CS6_CS7(_grantFund, _standardHandler);
58+
_invariant_ES1_ES2_ES3_ES4_ES5(_grantFund, _standardHandler);
59+
_invariant_DR1_DR2_DR3_DR4_DR5(_grantFund, _standardHandler);
60+
61+
// distribution period invariants
62+
_invariant_DP1_DP2_DP3_DP4_DP5(_grantFund, _standardHandler);
63+
_invariant_DP6(_grantFund, _standardHandler);
64+
_invariant_T1_T2(_grantFund);
65+
}
66+
67+
function invariant_call_summary() external useCurrentBlock {
68+
uint24 distributionId = _grantFund.getDistributionId();
69+
70+
_logger.logCallSummary();
71+
_logger.logTimeSummary();
72+
_logger.logProposalSummary();
73+
console.log("scenario type", uint8(_standardHandler.getCurrentScenarioType()));
74+
75+
while (distributionId > 0) {
76+
77+
_logger.logFundingSummary(distributionId);
78+
_logger.logFinalizeSummary(distributionId);
79+
_logger.logActorSummary(distributionId, true, true);
80+
_logger.logActorDelegationRewards(distributionId);
81+
82+
--distributionId;
83+
}
84+
}
85+
}

test/unit/AjnaToken.t.sol

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,4 +313,52 @@ contract AjnaTokenTest is Test {
313313
assertEq(_token.getVotes(address(3333)), 0);
314314
assertEq(_token.getVotes(address(4444)), 2_000_000_000 * 1e18);
315315
}
316+
317+
function testCyclingVotingDelegation() external {
318+
// define actors and set their balances
319+
address actor1 = makeAddr("actor1");
320+
deal(address(_token), actor1, 1_000 * 1e18);
321+
322+
address actor2 = makeAddr("actor2");
323+
deal(address(_token), actor2, 2_000 * 1e18);
324+
325+
address actor3 = makeAddr("actor3");
326+
deal(address(_token), actor3, 5_000 * 1e18);
327+
328+
// actor1 delegates votes to actor2
329+
changePrank(actor1);
330+
_token.delegate(actor2);
331+
332+
// ensure actor2 has votes equals to actor1 balance
333+
assertEq(_token.getVotes(actor1), 0);
334+
assertEq(_token.getVotes(actor2), 1_000 * 1e18);
335+
assertEq(_token.getVotes(actor3), 0);
336+
337+
// actor2 delegates votes to actor3
338+
changePrank(actor2);
339+
_token.delegate(actor3);
340+
341+
// ensure actor3 has votes equals to actor2 balance
342+
assertEq(_token.getVotes(actor1), 0);
343+
assertEq(_token.getVotes(actor2), 1_000 * 1e18);
344+
assertEq(_token.getVotes(actor3), 2_000 * 1e18);
345+
346+
// actor3 delegates votes to actor1
347+
changePrank(actor3);
348+
_token.delegate(actor1);
349+
350+
// ensure actor1 has votes equals to actor3 balance
351+
assertEq(_token.getVotes(actor1), 5_000 * 1e18);
352+
assertEq(_token.getVotes(actor2), 1_000 * 1e18);
353+
assertEq(_token.getVotes(actor3), 2_000 * 1e18);
354+
355+
// actor3 delegates votes to actor2
356+
changePrank(actor3);
357+
_token.delegate(actor2);
358+
359+
// ensure actor2 has votes equals to sum of actor3 and actor1 balance
360+
assertEq(_token.getVotes(actor1), 0);
361+
assertEq(_token.getVotes(actor2), 6_000 * 1e18);
362+
assertEq(_token.getVotes(actor3), 2_000 * 1e18);
363+
}
316364
}

test/unit/Deployer.t.sol

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity 0.8.18;
3+
4+
import { Test } from "@std/Test.sol";
5+
6+
import { Deployer } from "../../src/grants/Deployer.sol";
7+
import { GrantFund } from "../../src/grants/GrantFund.sol";
8+
import { TestAjnaToken } from "../utils/harness/TestAjnaToken.sol";
9+
10+
contract DeployerTest is Test {
11+
12+
function testGrantFundDeployment() external {
13+
address owner = makeAddr("owner");
14+
vm.startPrank(owner);
15+
16+
uint256 treasury = 50_000_000 * 1e18;
17+
18+
TestAjnaToken ajnaToken = new TestAjnaToken();
19+
ajnaToken.mint(owner, treasury);
20+
21+
Deployer deployer = new Deployer();
22+
ajnaToken.approve(address(deployer), treasury);
23+
24+
GrantFund grantFund = deployer.deployGrantFund(address(ajnaToken), treasury);
25+
26+
assertEq(grantFund.getDistributionId(), 1);
27+
28+
(,,,uint256 fundAvailable,,) = grantFund.getDistributionPeriodInfo(1);
29+
30+
assertEq(grantFund.treasury(), treasury - fundAvailable);
31+
32+
assertEq(fundAvailable, treasury * 3 / 100);
33+
}
34+
}

test/unit/StandardFunding.t.sol

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -905,6 +905,10 @@ contract StandardFundingGrantFundTest is GrantFundTestHelper {
905905
_screeningVote(_grantFund, _tokenHolder3, testProposals[12].proposalId, 5_000 * 1e18);
906906
_screeningVote(_grantFund, _tokenHolder5, testProposals[12].proposalId, 50_000 * 1e18);
907907
assertEq(_findProposalIndex(testProposals[12].proposalId, _grantFund.getTopTenProposals(distributionId)), -1);
908+
909+
// cast screening votes on a proposal not in the top 10 to make it top 10 proposal
910+
_screeningVote(_grantFund, _tokenHolder7, testProposals[12].proposalId, 2_500_000 * 1e18);
911+
assertEq(_findProposalIndex(testProposals[12].proposalId, _grantFund.getTopTenProposals(distributionId)), 2);
908912
}
909913

910914
function testStartNewDistributionPeriod() external {

0 commit comments

Comments
 (0)