Skip to content

Commit

Permalink
Merge pull request #136 from ajna-finance/develop
Browse files Browse the repository at this point in the history
Merge develop
  • Loading branch information
EdNoepel authored Oct 13, 2023
2 parents 545ec42 + 65be9c1 commit 640d5c9
Show file tree
Hide file tree
Showing 9 changed files with 284 additions and 3 deletions.
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

0 comments on commit 640d5c9

Please sign in to comment.