Skip to content

Commit

Permalink
Create and deploy a revertible distributor for testing (#220)
Browse files Browse the repository at this point in the history
* feat: add a testing merkle tree distributor that can be unclaimed

* feat: add full contracts to the testing file
  • Loading branch information
bowd authored Sep 26, 2024
1 parent d5292ff commit 28a09d4
Show file tree
Hide file tree
Showing 14 changed files with 188 additions and 11 deletions.
1 change: 0 additions & 1 deletion bin/verify-celoscan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,6 @@ function makeStandardJson(metadata: Metadata, sources: Sources, libraryMap: Reco
settings: {
viaIR: true,
metadata: {
appendCBOR: true,
bytecodeHash: "none",
useLiteralContent: true,
},
Expand Down
130 changes: 130 additions & 0 deletions contracts/TestingMerkleDistributor.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity =0.8.17;

import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { IERC20, SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { MerkleProof } from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import { IMerkleDistributor } from "merkle-distributor/interfaces/IMerkleDistributor.sol";

error EndTimeInPast();
error ClaimWindowFinished();
error NoWithdrawDuringClaim();
error AlreadyClaimed();
error InvalidProof();

contract MerkleDistributor is IMerkleDistributor {
using SafeERC20 for IERC20;

address public immutable override token;
bytes32 public immutable override merkleRoot;

// This is a packed array of booleans.
mapping(uint256 => uint256) private claimedBitMap;

constructor(address token_, bytes32 merkleRoot_) {
token = token_;
merkleRoot = merkleRoot_;
}

function isClaimed(uint256 index) public view virtual override returns (bool) {
uint256 claimedWordIndex = index / 256;
uint256 claimedBitIndex = index % 256;
uint256 claimedWord = claimedBitMap[claimedWordIndex];
uint256 mask = (1 << claimedBitIndex);
return claimedWord & mask == mask;
}

function _setClaimed(uint256 index) private {
uint256 claimedWordIndex = index / 256;
uint256 claimedBitIndex = index % 256;
claimedBitMap[claimedWordIndex] = claimedBitMap[claimedWordIndex] | (1 << claimedBitIndex);
}

function claim(
uint256 index,
address account,
uint256 amount,
bytes32[] calldata merkleProof
) public virtual override {
if (isClaimed(index)) revert AlreadyClaimed();

// Verify the merkle proof.
bytes32 node = keccak256(abi.encodePacked(index, account, amount));
if (!MerkleProof.verify(merkleProof, merkleRoot, node)) revert InvalidProof();

// Mark it claimed and send the token.
_setClaimed(index);
IERC20(token).safeTransfer(account, amount);

emit Claimed(index, account, amount);
}
}

contract MerkleDistributorWithDeadline is MerkleDistributor, Ownable {
using SafeERC20 for IERC20;

uint256 public immutable endTime;

constructor(address token_, bytes32 merkleRoot_, uint256 endTime_) MerkleDistributor(token_, merkleRoot_) {
if (endTime_ <= block.timestamp) revert EndTimeInPast();
endTime = endTime_;
}

function claim(
uint256 index,
address account,
uint256 amount,
bytes32[] calldata merkleProof
) public virtual override {
if (block.timestamp > endTime) revert ClaimWindowFinished();
super.claim(index, account, amount, merkleProof);
}

function withdraw() external onlyOwner {
if (block.timestamp < endTime) revert NoWithdrawDuringClaim();
IERC20(token).safeTransfer(msg.sender, IERC20(token).balanceOf(address(this)));
}
}

contract TestingMerkleDistributor is MerkleDistributorWithDeadline {
// This is a packed array of booleans.
mapping(uint256 => uint256) private claimedBitMap;

constructor(
address token_,
bytes32 merkleRoot_,
uint256 endTime_
) MerkleDistributorWithDeadline(token_, merkleRoot_, endTime_) {}

function setClaimed(uint256 index, bool claimed) public onlyOwner {
_setClaimed(index, claimed);
}

function _setClaimed(uint256 index, bool claimed) public {
uint256 claimedWordIndex = index / 256;
uint256 claimedBitIndex = index % 256;
if (claimed) {
claimedBitMap[claimedWordIndex] = claimedBitMap[claimedWordIndex] | (1 << claimedBitIndex);
} else {
claimedBitMap[claimedWordIndex] = claimedBitMap[claimedWordIndex] & ~(1 << claimedBitIndex);
}
}

function isClaimed(uint256 index) public view virtual override returns (bool) {
uint256 claimedWordIndex = index / 256;
uint256 claimedBitIndex = index % 256;
uint256 claimedWord = claimedBitMap[claimedWordIndex];
uint256 mask = (1 << claimedBitIndex);
return claimedWord & mask == mask;
}

function claim(
uint256 index,
address account,
uint256 amount,
bytes32[] calldata merkleProof
) public virtual override {
super.claim(index, account, amount, merkleProof);
_setClaimed(index, true);
}
}
21 changes: 21 additions & 0 deletions script/dev/dev-DeployTestingMerkleDistributor.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity =0.8.17;

import { Script } from "script/utils/mento/Script.sol";
import { Chain as ChainLib } from "script/utils/mento/Chain.sol";
import { Contracts } from "script/utils/mento/Contracts.sol";
import { TestingMerkleDistributor } from "contracts/TestingMerkleDistributor.sol";

contract DeployTestingMerkleDistributor is Script {
using Contracts for Contracts.Cache;
bytes32 constant MERKLE_ROOT = 0xc8148553672963b66a8972e21bff370b4c8dba9ce7f108282edf784c76875a43;

Check warning on line 11 in script/dev/dev-DeployTestingMerkleDistributor.sol

View workflow job for this annotation

GitHub Actions / Lint & Build

Explicitly mark visibility of state

function run() public {
address token = contracts.celoRegistry("StableToken");
vm.startBroadcast(ChainLib.deployerPrivateKey());
{
new TestingMerkleDistributor(token, MERKLE_ROOT, block.timestamp + 1 weeks);
}
vm.stopBroadcast();
}
}
27 changes: 27 additions & 0 deletions script/dev/dev-ToggleTestingDistributorClaimed.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.18;

import { console } from "forge-std-next/console.sol";
import { Script } from "script/utils/mento/Script.sol";
import { Chain as ChainLib } from "script/utils/mento/Chain.sol";

interface IDistributor {
function setClaimed(uint256 index, bool claimed) external;
}

/*
* How to run:
* env DISTRIBUTOR=0x... CLAIMER_INDEX=0x0 yarn script:dev -n celo -s ToggleTestingDistributorClaimed
*/
contract ToggleTestingDistributorClaimed is Script {
function run() public {
address distributor = vm.envAddress("DISTRIBUTOR");
uint256 index = vm.envUint("CLAIMER_INDEX");

vm.startBroadcast(ChainLib.deployerPrivateKey());
{
IDistributor(distributor).setClaimed(index, false);
}
vm.stopBroadcast();
}
}
2 changes: 1 addition & 1 deletion script/interfaces/IGovernor.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
pragma solidity 0.8.18;
pragma solidity ^0.8;

/**
* @dev Interface of the Bravo Compatible Governor.
Expand Down
2 changes: 1 addition & 1 deletion script/utils/mento/Chain.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.18;
pragma solidity ^0.8;

import { Vm } from "forge-std/Vm.sol";

Expand Down
2 changes: 1 addition & 1 deletion script/utils/mento/Contracts.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.18;
pragma solidity ^0.8;

import { console } from "forge-std/console.sol";

Expand Down
2 changes: 1 addition & 1 deletion script/utils/mento/ExecuteProposal.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.18;
pragma solidity ^0.8;

import { Script } from "./Script.sol";
import { IGovernanceFactory } from "../../interfaces/IGovernanceFactory.sol";
Expand Down
2 changes: 1 addition & 1 deletion script/utils/mento/GovernanceHelper.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.18;
pragma solidity ^0.8;

import { Script, console2 } from "forge-std/Script.sol";
import { ICeloGovernance } from "../../interfaces/ICeloGovernance.sol";
Expand Down
2 changes: 1 addition & 1 deletion script/utils/mento/Oracles.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// solhint-disable func-visibility
pragma solidity ^0.8.18;
pragma solidity ^0.8;

import { IChainlinkRelayer } from "mento-core-2.5.0/interfaces/IChainlinkRelayer.sol";

Expand Down
2 changes: 1 addition & 1 deletion script/utils/mento/PassProposal.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.18;
pragma solidity ^0.8;

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

Expand Down
2 changes: 1 addition & 1 deletion script/utils/mento/QueueProposal.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.18;
pragma solidity ^0.8;

import { Script } from "./Script.sol";
import { IGovernanceFactory } from "../../interfaces/IGovernanceFactory.sol";
Expand Down
2 changes: 1 addition & 1 deletion script/utils/mento/Script.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.18;
pragma solidity ^0.8;

import { Script as BaseScript } from "forge-std/Script.sol";
import { FixidityLib } from "../FixidityLib.sol";
Expand Down
2 changes: 1 addition & 1 deletion script/utils/mento/SimulateUpgrade.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-or-later
// solhint-disable func-name-mixedcase, contract-name-camelcase
pragma solidity ^0.8.18;
pragma solidity ^0.8;

import { console } from "forge-std-next/console.sol";
import { Contracts } from "script/utils/mento/Contracts.sol";
Expand Down

0 comments on commit 28a09d4

Please sign in to comment.