├── .env.example ├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ ├── lint.yml │ └── test.yml ├── .gitignore ├── .npmignore ├── .prettierignore ├── .prettierrc ├── .solhint.json ├── .solhintignore ├── README.md ├── contracts ├── ArenaGovernor.sol ├── ArenaToken.sol ├── MerkleProof.sol ├── RevokableTokenLock.sol ├── TimelockController.sol ├── TokenLock.sol ├── TokenSale.sol └── test │ └── TestERC20.sol ├── deployments ├── polygonAddresses.json ├── rinkebyAddresses.json └── rinkebyMerkleTree.json ├── hardhat.config.ts ├── interfaces ├── IRevokableTokenLock.sol └── ITokenLockVestReader.sol ├── merkle-data.csv ├── package.json ├── scripts ├── airdrop │ ├── example.json │ ├── generate-merkle-root.ts │ ├── mainnetMerkle.json │ └── rinkeby.json ├── deploy │ ├── config.ts │ ├── deployGov.ts │ ├── deployTokenSale.ts │ ├── index.ts │ └── verify.ts ├── proposals │ ├── example.json │ ├── index.ts │ ├── simulateExistingProposal.ts │ └── transfer.ts └── verify.ts ├── shared ├── AccountManipulation.ts ├── Constants.ts ├── Forking.ts ├── Governance.ts ├── Polygonscan.ts └── TimeManipulation.ts ├── src ├── balance-tree.ts ├── merkle-tree.ts └── parse-balance-map.ts ├── test ├── ArenaToken.spec.ts ├── GovernanceSim.spec.ts ├── RevokableTokenLock.spec.ts ├── TokenLock.spec.ts ├── TokenSale.spec.ts └── fixtures │ └── testAirdrop.json ├── tsconfig.json └── yarn.lock /.env.example: -------------------------------------------------------------------------------- 1 | ETHERSCAN_API_KEY=ABC123ABC123ABC123ABC123ABC123ABC1 2 | RINKEBY_URL=https://eth-rinkeby.alchemyapi.io/v2/ 3 | POLYGON_URL=https://polygon-rpc.com 4 | PRIVATE_KEY=0xabc123abc123abc123abc123abc123abc123abc123abc123abc123abc123abc1 5 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | artifacts 3 | cache 4 | coverage 5 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: false, 4 | es2021: true, 5 | mocha: true, 6 | node: true, 7 | }, 8 | plugins: ["@typescript-eslint"], 9 | extends: [ 10 | "standard", 11 | "plugin:prettier/recommended", 12 | "plugin:node/recommended", 13 | ], 14 | parser: "@typescript-eslint/parser", 15 | parserOptions: { 16 | ecmaVersion: 12, 17 | }, 18 | rules: { 19 | "node/no-unsupported-features/es-syntax": [ 20 | "error", 21 | { ignores: ["modules"] }, 22 | ], 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | # Script name 2 | name: Lint code 3 | 4 | # Prerequisites for execution: any actions with the pool-request 5 | on: [pull_request] 6 | 7 | # Task list 8 | jobs: 9 | lint: 10 | # Task name 11 | name: Lint Code 12 | 13 | # Run on a virtual machine with Ubuntu 14 | runs-on: ubuntu-latest 15 | 16 | # List of steps 17 | steps: 18 | # Use project code from a specific commit 19 | # By default, the branch whose changes caused the script to run is used 20 | - uses: actions/checkout@v2 21 | 22 | # Configuring Node.js 14 to run on a virtual machine 23 | - name: Use Node.js 14 24 | uses: actions/setup-node@v1 25 | with: 26 | node-version: 14 27 | 28 | # The described bash commands will be executed 29 | - name: Lint 30 | run: yarn && yarn lint:check 31 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | # Script name 2 | name: Compile and run hardhat tests 3 | 4 | # Prerequisites for execution: any actions with the pool-request 5 | on: [pull_request] 6 | 7 | # Task list 8 | jobs: 9 | test: 10 | # Task name 11 | name: Compile contracts & run tests 12 | 13 | # Run on a virtual machine with Ubuntu 14 | runs-on: ubuntu-latest 15 | 16 | # List of steps 17 | steps: 18 | # Use project code from a specific commit 19 | # By default, the branch whose changes caused the script to run is used 20 | - uses: actions/checkout@v2 21 | 22 | # Configuring Node.js 14 to run on a virtual machine 23 | - name: Use Node.js 14 24 | uses: actions/setup-node@v1 25 | with: 26 | node-version: 14 27 | 28 | # The described bash commands will be executed 29 | - name: Compile 30 | run: yarn && yarn first-compile 31 | - name: Test 32 | run: yarn test-no-compile 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | .env 4 | coverage 5 | coverage.json 6 | typechain 7 | 8 | #Hardhat files 9 | cache 10 | artifacts 11 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | hardhat.config.ts 2 | scripts 3 | test 4 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | artifacts 3 | cache 4 | coverage* 5 | gasReporterOutput.json 6 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "overrides": [ 3 | { 4 | "files": "*.js", 5 | "options": { 6 | "printWidth": 120, 7 | "tabWidth": 2, 8 | "useTabs": false, 9 | "singleQuote": true, 10 | "bracketSpacing": false, 11 | "explicitTypes": "always", 12 | "semi": true, 13 | "endOfLine": "lf" 14 | } 15 | }, 16 | { 17 | "files": "*.ts", 18 | "options": { 19 | "printWidth": 120, 20 | "tabWidth": 2, 21 | "useTabs": false, 22 | "singleQuote": true, 23 | "bracketSpacing": false, 24 | "explicitTypes": "always", 25 | "semi": true, 26 | "endOfLine": "lf" 27 | } 28 | }, 29 | { 30 | "files": "*.sol", 31 | "options": { 32 | "printWidth": 100, 33 | "tabWidth": 4, 34 | "useTabs": false, 35 | "singleQuote": false, 36 | "bracketSpacing": false, 37 | "explicitTypes": "always", 38 | "endOfLine": "lf" 39 | } 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:recommended", 3 | "rules": { 4 | "compiler-version": ["error", "^0.8.0"], 5 | "func-visibility": ["warn", { "ignoreConstructors": true }] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.solhintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Code4rena contracts 2 | 3 | 4 | # Scripts 5 | 6 | ## Proposing transfers 7 | 8 | ```bash 9 | # fill out example file for your network 10 | cp .env.example .env 11 | # create a batch transfers JSON file 12 | # propose it 13 | yarn hardhat propose --network polygon --json scripts/proposals/example.json 14 | ``` 15 | -------------------------------------------------------------------------------- /contracts/ArenaGovernor.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.10; 3 | 4 | import "@openzeppelin/contracts/governance/Governor.sol"; 5 | import "@openzeppelin/contracts/governance/extensions/GovernorSettings.sol"; 6 | import "@openzeppelin/contracts/governance/compatibility/GovernorCompatibilityBravo.sol"; 7 | import "@openzeppelin/contracts/governance/extensions/GovernorPreventLateQuorum.sol"; 8 | import "@openzeppelin/contracts/governance/extensions/GovernorVotes.sol"; 9 | import "@openzeppelin/contracts/governance/extensions/GovernorTimelockControl.sol"; 10 | 11 | contract ArenaGovernor is 12 | Governor, 13 | GovernorSettings, 14 | GovernorCompatibilityBravo, 15 | GovernorPreventLateQuorum, 16 | GovernorVotes, 17 | GovernorTimelockControl 18 | { 19 | constructor(IVotes _token, TimelockController _timelock) 20 | Governor("ArenaGovernor") 21 | GovernorSettings( 22 | 1, /* 1 block */ 23 | 216_000, /* 5 days */ 24 | 50_000e18 /* minimum proposal threshold of 50_000 tokens */ 25 | ) 26 | GovernorPreventLateQuorum( 27 | 129_600 /* 3 days */ 28 | ) 29 | GovernorVotes(_token) 30 | GovernorTimelockControl(_timelock) 31 | {} 32 | 33 | function quorum(uint256 blockNumber) public pure override returns (uint256) { 34 | return 10_000_000e18; // 10M tokens 35 | } 36 | 37 | // The following functions are overrides required by Solidity. 38 | 39 | function votingDelay() public view override(IGovernor, GovernorSettings) returns (uint256) { 40 | return super.votingDelay(); 41 | } 42 | 43 | function votingPeriod() public view override(IGovernor, GovernorSettings) returns (uint256) { 44 | return super.votingPeriod(); 45 | } 46 | 47 | function getVotes(address account, uint256 blockNumber) 48 | public 49 | view 50 | override(IGovernor, GovernorVotes) 51 | returns (uint256) 52 | { 53 | return super.getVotes(account, blockNumber); 54 | } 55 | 56 | function state(uint256 proposalId) 57 | public 58 | view 59 | override(Governor, IGovernor, GovernorTimelockControl) 60 | returns (ProposalState) 61 | { 62 | return super.state(proposalId); 63 | } 64 | 65 | function proposalDeadline(uint256 proposalId) 66 | public 67 | view 68 | override(Governor, IGovernor, GovernorPreventLateQuorum) 69 | returns (uint256) 70 | { 71 | return super.proposalDeadline(proposalId); 72 | } 73 | 74 | function propose( 75 | address[] memory targets, 76 | uint256[] memory values, 77 | bytes[] memory calldatas, 78 | string memory description 79 | ) public override(Governor, GovernorCompatibilityBravo, IGovernor) returns (uint256) { 80 | return super.propose(targets, values, calldatas, description); 81 | } 82 | 83 | function proposalThreshold() 84 | public 85 | view 86 | override(Governor, GovernorSettings) 87 | returns (uint256) 88 | { 89 | return super.proposalThreshold(); 90 | } 91 | 92 | function _execute( 93 | uint256 proposalId, 94 | address[] memory targets, 95 | uint256[] memory values, 96 | bytes[] memory calldatas, 97 | bytes32 descriptionHash 98 | ) internal override(Governor, GovernorTimelockControl) { 99 | super._execute(proposalId, targets, values, calldatas, descriptionHash); 100 | } 101 | 102 | function _cancel( 103 | address[] memory targets, 104 | uint256[] memory values, 105 | bytes[] memory calldatas, 106 | bytes32 descriptionHash 107 | ) internal override(Governor, GovernorTimelockControl) returns (uint256) { 108 | return super._cancel(targets, values, calldatas, descriptionHash); 109 | } 110 | 111 | function _castVote( 112 | uint256 proposalId, 113 | address account, 114 | uint8 support, 115 | string memory reason 116 | ) internal override(Governor, GovernorPreventLateQuorum) returns (uint256) { 117 | return super._castVote(proposalId, account, support, reason); 118 | } 119 | 120 | function _executor() 121 | internal 122 | view 123 | override(Governor, GovernorTimelockControl) 124 | returns (address) 125 | { 126 | return super._executor(); 127 | } 128 | 129 | function supportsInterface(bytes4 interfaceId) 130 | public 131 | view 132 | override(Governor, IERC165, GovernorTimelockControl) 133 | returns (bool) 134 | { 135 | return super.supportsInterface(interfaceId); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /contracts/ArenaToken.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Modified from https://github.com/ensdomains/governance/blob/master/contracts/ENSToken.sol 3 | pragma solidity 0.8.10; 4 | 5 | import "@openzeppelin/contracts/access/Ownable.sol"; 6 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 7 | import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol"; 8 | import "@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Permit.sol"; 9 | import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol"; 10 | import "@openzeppelin/contracts/utils/structs/BitMaps.sol"; 11 | 12 | import "./MerkleProof.sol"; 13 | import "../interfaces/IRevokableTokenLock.sol"; 14 | 15 | contract ArenaToken is ERC20, ERC20Burnable, Ownable, ERC20Permit, ERC20Votes { 16 | using BitMaps for BitMaps.BitMap; 17 | 18 | /// mint cooldown period 19 | uint256 public constant MIN_MINT_INTERVAL = 365 days; 20 | /// maximum tokens allowed per mint 21 | /// 10_000 = 100% 22 | uint256 public constant MINT_CAP = 200; // 2% 23 | 24 | bytes32 public merkleRoot; 25 | /// Proportion of airdropped tokens that are immediately claimable 26 | /// 10_000 = 100% 27 | uint256 public immutable claimableProportion; 28 | /// Timestamp at which tokens are no longer claimable 29 | uint256 public immutable claimPeriodEnds; 30 | /// vesting contract 31 | IRevokableTokenLock public tokenLock; 32 | BitMaps.BitMap private claimed; 33 | 34 | /// vesting duration 35 | uint256 public vestDuration; 36 | /// timestamp till next mint is allowed 37 | uint256 public nextMint; 38 | 39 | event MerkleRootChanged(bytes32 merkleRoot); 40 | event Claim(address indexed claimant, uint256 amount); 41 | event Vest(address indexed claimant, uint256 amount); 42 | 43 | /** 44 | * @dev Constructor. 45 | * @param _freeSupply The number of tokens to mint for contract deployer (then transferred to timelock controller after deployment) 46 | * @param _airdropSupply The number of tokens to reserve for the airdrop 47 | * @param _claimableProportion The value in BPS of the % of claimable vs vested 48 | * @param _claimPeriodEnds The timestamp at which tokens are no longer claimable 49 | * @param _vestDuration The token vesting duration 50 | */ 51 | constructor( 52 | uint256 _freeSupply, 53 | uint256 _airdropSupply, 54 | uint256 _claimableProportion, 55 | uint256 _claimPeriodEnds, 56 | uint256 _vestDuration 57 | ) ERC20("Code4rena", "ARENA") ERC20Permit("Code4rena") { 58 | // TODO: Change Symbol TBD 59 | require(_claimableProportion <= 10_000, "claimable exceeds limit"); 60 | require(_claimPeriodEnds > block.timestamp, "cannot have a backward time"); 61 | _mint(msg.sender, _freeSupply); 62 | _mint(address(this), _airdropSupply); 63 | nextMint = block.timestamp + MIN_MINT_INTERVAL; 64 | claimableProportion = _claimableProportion; 65 | claimPeriodEnds = _claimPeriodEnds; 66 | vestDuration = _vestDuration; 67 | } 68 | 69 | /** 70 | * @dev set vesting contract 71 | * @param _tokenLock address of the vesting contract 72 | */ 73 | function setTokenLock(address _tokenLock) external onlyOwner { 74 | require(_tokenLock != address(0), "Address cannot be 0x"); 75 | tokenLock = IRevokableTokenLock(_tokenLock); 76 | } 77 | 78 | /** 79 | * @dev Claims airdropped tokens. 80 | * @param amount The amount of the claim being made. 81 | * @param merkleProof A merkle proof proving the claim is valid. 82 | */ 83 | function claimTokens(uint256 amount, bytes32[] calldata merkleProof) external { 84 | require(block.timestamp < claimPeriodEnds, "ArenaToken: Claim period ended"); 85 | // we don't need to check that `merkleProof` has the correct length as 86 | // submitting a valid partial merkle proof would require `leaf` to map 87 | // to an intermediate hash in the merkle tree but `leaf` uses msg.sender 88 | // which is 20 bytes instead of 32 bytes and can't be chosen arbitrarily 89 | bytes32 leaf = keccak256(abi.encodePacked(msg.sender, amount)); 90 | (bool valid, uint256 index) = MerkleProof.verify(merkleProof, merkleRoot, leaf); 91 | require(valid, "ArenaToken: Valid proof required."); 92 | require(!isClaimed(index), "ArenaToken: Tokens already claimed."); 93 | 94 | claimed.set(index); 95 | 96 | uint256 claimableAmount; 97 | uint256 remainingAmount; 98 | 99 | unchecked { 100 | claimableAmount = (amount * claimableProportion) / 10_000; 101 | remainingAmount = amount - claimableAmount; 102 | } 103 | 104 | emit Claim(msg.sender, claimableAmount); 105 | 106 | // transfer claimable proportion to caller 107 | _transfer(address(this), msg.sender, claimableAmount); 108 | // self-delegate if no prior delegatee was chosen 109 | if (delegates(msg.sender) == address(0)) { 110 | _delegate(msg.sender, msg.sender); 111 | } 112 | 113 | require(address(tokenLock) != address(0), "Vesting contract not initialized"); 114 | tokenLock.setupVesting( 115 | msg.sender, 116 | block.timestamp, 117 | block.timestamp, 118 | block.timestamp + vestDuration 119 | ); 120 | // approve TokenLock for token transfer 121 | _approve(address(this), address(tokenLock), remainingAmount); 122 | tokenLock.lock(msg.sender, remainingAmount); 123 | emit Vest(msg.sender, remainingAmount); 124 | } 125 | 126 | /** 127 | * @dev Allows the owner to sweep unclaimed tokens after the claim period ends. 128 | * @param dest The address to sweep the tokens to. 129 | */ 130 | function sweep(address dest) external onlyOwner { 131 | require(block.timestamp >= claimPeriodEnds, "ArenaToken: Claim period not yet ended"); 132 | _transfer(address(this), dest, balanceOf(address(this))); 133 | } 134 | 135 | /** 136 | * @dev Returns true if the claim at the given index in the merkle tree has already been made. 137 | * @param index The index into the merkle tree. 138 | */ 139 | function isClaimed(uint256 index) public view returns (bool) { 140 | return claimed.get(index); 141 | } 142 | 143 | /** 144 | * @dev Sets the merkle root. Only callable if the root is not yet set. 145 | * @param _merkleRoot The merkle root to set. 146 | */ 147 | function setMerkleRoot(bytes32 _merkleRoot) external onlyOwner { 148 | require(merkleRoot == bytes32(0), "ArenaToken: Merkle root already set"); 149 | merkleRoot = _merkleRoot; 150 | emit MerkleRootChanged(_merkleRoot); 151 | } 152 | 153 | /** 154 | * @dev Mints new tokens. 155 | * @param dest The address to mint the new tokens to. 156 | * @param amount The quantity of tokens to mint. 157 | */ 158 | function mint(address dest, uint256 amount) external onlyOwner { 159 | require( 160 | amount <= (totalSupply() * MINT_CAP) / 10_000, 161 | "ArenaToken: Mint exceeds maximum amount" 162 | ); 163 | require(block.timestamp >= nextMint, "ArenaToken: Cannot mint yet"); 164 | 165 | nextMint = block.timestamp + MIN_MINT_INTERVAL; 166 | _mint(dest, amount); 167 | } 168 | 169 | // The following functions are overrides required by Solidity. 170 | 171 | function _afterTokenTransfer( 172 | address from, 173 | address to, 174 | uint256 amount 175 | ) internal override(ERC20, ERC20Votes) { 176 | super._afterTokenTransfer(from, to, amount); 177 | } 178 | 179 | function _mint(address to, uint256 amount) internal override(ERC20, ERC20Votes) { 180 | super._mint(to, amount); 181 | } 182 | 183 | function _burn(address account, uint256 amount) internal override(ERC20, ERC20Votes) { 184 | super._burn(account, amount); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /contracts/MerkleProof.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Taken from https://github.com/ensdomains/governance/blob/master/contracts/MerkleProof.sol 3 | 4 | pragma solidity 0.8.10; 5 | 6 | /** 7 | * @dev These functions deal with verification of Merkle Trees proofs. 8 | * 9 | * The proofs can be generated using the JavaScript library 10 | * https://github.com/miguelmota/merkletreejs[merkletreejs]. 11 | * Note: the hashing algorithm should be keccak256 and pair sorting should be enabled. 12 | * 13 | * See `test/utils/cryptography/MerkleProof.test.js` for some examples. 14 | */ 15 | library MerkleProof { 16 | /** 17 | * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree 18 | * defined by `root`. For this, a `proof` must be provided, containing 19 | * sibling hashes on the branch from the leaf to the root of the tree. Each 20 | * pair of leaves and each pair of pre-images are assumed to be sorted. 21 | * the returned `index` is not the leaf order index, it's more like a unique identifier 22 | * for that leaf 23 | */ 24 | function verify( 25 | bytes32[] memory proof, 26 | bytes32 root, 27 | bytes32 leaf 28 | ) internal pure returns (bool, uint256) { 29 | bytes32 computedHash = leaf; 30 | uint256 index = 0; 31 | 32 | for (uint256 i = 0; i < proof.length; i++) { 33 | index *= 2; 34 | bytes32 proofElement = proof[i]; 35 | 36 | if (computedHash <= proofElement) { 37 | // Hash(current computed hash + current element of the proof) 38 | computedHash = keccak256(abi.encodePacked(computedHash, proofElement)); 39 | } else { 40 | // Hash(current element of the proof + current computed hash) 41 | computedHash = keccak256(abi.encodePacked(proofElement, computedHash)); 42 | index += 1; 43 | } 44 | } 45 | 46 | // Check if the computed hash (root) is equal to the provided root 47 | return (computedHash == root, index); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /contracts/RevokableTokenLock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.10; 3 | 4 | import "@openzeppelin/contracts/access/Ownable.sol"; 5 | 6 | import "./TokenLock.sol"; 7 | 8 | /// @dev Same as TokenLock, but enables a revoker to end vesting prematurely and send locked tokens to governance. 9 | contract RevokableTokenLock is TokenLock { 10 | address public revoker; 11 | 12 | event Revoked(address indexed revokedOwner, uint256 amount); 13 | 14 | constructor(ERC20 _token, address _revoker) TokenLock(_token) { 15 | require(_revoker != address(0), "RevokableTokenLock: revoker address cannot be set to 0"); 16 | revoker = _revoker; 17 | } 18 | 19 | /** 20 | * @dev set revoker address 21 | * @param _revoker The account with revoking rights 22 | */ 23 | function setRevoker(address _revoker) external onlyOwner { 24 | require(_revoker != address(0), "RevokableTokenLock: null address"); 25 | revoker = _revoker; 26 | } 27 | 28 | /** 29 | * @dev revoke access of a owner and transfer pending 30 | * @param recipient The account whose access will be revoked. 31 | */ 32 | function revoke(address recipient) external { 33 | require( 34 | msg.sender == revoker || msg.sender == owner(), 35 | "RevokableTokenLock: onlyAuthorizedActors" 36 | ); 37 | 38 | // claim any vested but unclaimed parts for recipient first 39 | uint256 claimable = claimableBalance(recipient); 40 | if (claimable > 0) { 41 | vesting[recipient].claimedAmounts += claimable; 42 | require(token.transfer(recipient, claimable), "RevokableTokenLock: Transfer failed"); 43 | emit Claimed(recipient, recipient, claimable); 44 | } 45 | 46 | // revoke the rest that is still being vested 47 | uint256 remaining = vesting[recipient].lockedAmounts - vesting[recipient].claimedAmounts; 48 | if (remaining > 0) { 49 | require(token.transfer(owner(), remaining), "RevokableTokenLock: Transfer failed"); 50 | // no new claims 51 | vesting[recipient].lockedAmounts = vesting[recipient].claimedAmounts; 52 | vesting[recipient].unlockEnd = block.timestamp; 53 | } 54 | emit Revoked(recipient, remaining); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /contracts/TimelockController.sol: -------------------------------------------------------------------------------- 1 | import "@openzeppelin/contracts/governance/TimelockController.sol"; 2 | -------------------------------------------------------------------------------- /contracts/TokenLock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | // Taken from https://github.com/ensdomains/governance/blob/master/contracts/TokenLock.sol 3 | pragma solidity 0.8.10; 4 | 5 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 6 | import "@openzeppelin/contracts/access/Ownable.sol"; 7 | 8 | /** 9 | * @dev Time-locks tokens according to an unlock schedule. 10 | */ 11 | 12 | contract TokenLock is Ownable { 13 | ERC20 public immutable token; 14 | 15 | struct VestingParams { 16 | uint256 unlockBegin; 17 | uint256 unlockCliff; 18 | uint256 unlockEnd; 19 | uint256 lockedAmounts; 20 | uint256 claimedAmounts; 21 | } 22 | 23 | address public tokenSale; 24 | mapping(address => VestingParams) public vesting; 25 | 26 | event Setup( 27 | address indexed recipient, 28 | uint256 _unlockBegin, 29 | uint256 _unlockCliff, 30 | uint256 _unlockEnd 31 | ); 32 | event Locked(address indexed sender, address indexed recipient, uint256 amount); 33 | event Claimed(address indexed owner, address indexed recipient, uint256 amount); 34 | 35 | /** 36 | * @dev Constructor. 37 | * @param _token The token this contract will lock 38 | */ 39 | constructor(ERC20 _token) { 40 | token = _token; 41 | } 42 | 43 | /** 44 | * @dev set sale contract 45 | * @param _tokenSale address of token sale contract 46 | */ 47 | function setTokenSale(address _tokenSale) external onlyOwner { 48 | require(_tokenSale != address(0), "TokenLock: Null address"); 49 | tokenSale = _tokenSale; 50 | } 51 | 52 | /** 53 | * @dev setup vesting for recipient. 54 | * @param recipient The account for which vesting will be setup. 55 | * @param _unlockBegin The time at which unlocking of tokens will begin. 56 | * @param _unlockCliff The first time at which tokens are claimable. 57 | * @param _unlockEnd The time at which the last token will unlock. 58 | */ 59 | function setupVesting( 60 | address recipient, 61 | uint256 _unlockBegin, 62 | uint256 _unlockCliff, 63 | uint256 _unlockEnd 64 | ) external { 65 | require( 66 | msg.sender == owner() || msg.sender == address(token) || msg.sender == tokenSale, 67 | "TokenLock: Only owner/ claims/ sale contract can call" 68 | ); 69 | require( 70 | _unlockCliff >= _unlockBegin, 71 | "TokenLock: Unlock cliff must not be before unlock begin" 72 | ); 73 | require( 74 | _unlockEnd >= _unlockCliff, 75 | "TokenLock: Unlock end must not be before unlock cliff" 76 | ); 77 | vesting[recipient].unlockBegin = _unlockBegin; 78 | vesting[recipient].unlockCliff = _unlockCliff; 79 | vesting[recipient].unlockEnd = _unlockEnd; 80 | } 81 | 82 | /** 83 | * @dev Returns the maximum number of tokens currently claimable by `owner`. 84 | * @param owner The account to check the claimable balance of. 85 | * @return The number of tokens currently claimable. 86 | */ 87 | function claimableBalance(address owner) public view virtual returns (uint256) { 88 | if (block.timestamp < vesting[owner].unlockCliff) { 89 | return 0; 90 | } 91 | 92 | uint256 locked = vesting[owner].lockedAmounts; 93 | uint256 claimed = vesting[owner].claimedAmounts; 94 | if (block.timestamp >= vesting[owner].unlockEnd) { 95 | return locked - claimed; 96 | } 97 | return 98 | (locked * (block.timestamp - vesting[owner].unlockBegin)) / 99 | (vesting[owner].unlockEnd - vesting[owner].unlockBegin) - 100 | claimed; 101 | } 102 | 103 | /** 104 | * @dev Transfers tokens from the caller to the token lock contract and locks them for benefit of `recipient`. 105 | * Requires that the caller has authorised this contract with the token contract. 106 | * @param recipient The account the tokens will be claimable by. 107 | * @param amount The number of tokens to transfer and lock. 108 | */ 109 | function lock(address recipient, uint256 amount) external { 110 | require( 111 | block.timestamp < vesting[recipient].unlockEnd, 112 | "TokenLock: Unlock period already complete" 113 | ); 114 | vesting[recipient].lockedAmounts += amount; 115 | require( 116 | token.transferFrom(msg.sender, address(this), amount), 117 | "TokenLock: Transfer failed" 118 | ); 119 | emit Locked(msg.sender, recipient, amount); 120 | } 121 | 122 | /** 123 | * @dev Claims the caller's tokens that have been unlocked, sending them to `recipient`. 124 | * @param recipient The account to transfer unlocked tokens to. 125 | * @param amount The amount to transfer. If greater than the claimable amount, the maximum is transferred. 126 | */ 127 | function claim(address recipient, uint256 amount) external { 128 | uint256 claimable = claimableBalance(msg.sender); 129 | if (amount > claimable) { 130 | amount = claimable; 131 | } 132 | if (amount != 0) { 133 | vesting[msg.sender].claimedAmounts += amount; 134 | require(token.transfer(recipient, amount), "TokenLock: Transfer failed"); 135 | emit Claimed(msg.sender, recipient, amount); 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /contracts/TokenSale.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.10; 3 | 4 | import "@openzeppelin/contracts/access/Ownable.sol"; 5 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 6 | 7 | import "../interfaces/ITokenLockVestReader.sol"; 8 | 9 | /** 10 | * @dev Sells a token at a predetermined price to whitelisted buyers. The number of tokens each address can buy can be regulated. 11 | */ 12 | contract TokenSale is Ownable { 13 | /// token to take in (USDC) 14 | ERC20 public immutable tokenIn; 15 | /// token to give out (ARENA) 16 | ERC20 public immutable tokenOut; 17 | /// time when tokens can be first purchased 18 | uint64 public saleStart; 19 | /// duration of the token sale, cannot purchase afterwards 20 | uint64 public immutable saleDuration; 21 | /// address receiving a defined portion proceeds of the sale 22 | address internal immutable saleRecipient; 23 | /// amount receivable by sale recipient 24 | uint256 public remainingSaleRecipientAmount; 25 | /// vesting contract 26 | ITokenLockVestReader public immutable tokenLock; 27 | /// vesting duration 28 | uint256 public immutable vestDuration; 29 | 30 | /// how many `tokenOut`s each address may buy 31 | mapping(address => uint256) public whitelistedBuyersAmount; 32 | /// tokenIn per tokenOut price. precision is in tokenInDecimals - tokenOutDecimals + 18 33 | /// i.e., it should be provided as tokenInAmount * 1e18 / tokenOutAmount 34 | uint256 public immutable tokenOutPrice; 35 | 36 | event BuyerWhitelisted(address indexed buyer, uint256 amount); 37 | event Sale(address indexed buyer, uint256 amountIn, uint256 amountOut); 38 | 39 | /** 40 | * @dev Constructor. 41 | * @param _tokenIn The token this contract will receive in a trade 42 | * @param _tokenOut The token this contract will return in a trade 43 | * @param _saleStart The time when tokens can be first purchased 44 | * @param _saleDuration The duration of the token sale 45 | * @param _tokenOutPrice The tokenIn per tokenOut price. precision should be in tokenInDecimals - tokenOutDecimals + 18 46 | * @param _saleRecipient The address receiving a portion proceeds of the sale 47 | * @param _tokenLock The contract in which _tokenOut will be vested in 48 | * @param _vestDuration Token vesting duration 49 | * @param _saleRecipientAmount Amount receivable by sale recipient 50 | */ 51 | constructor( 52 | ERC20 _tokenIn, 53 | ERC20 _tokenOut, 54 | uint64 _saleStart, 55 | uint64 _saleDuration, 56 | uint256 _tokenOutPrice, 57 | address _saleRecipient, 58 | address _tokenLock, 59 | uint256 _vestDuration, 60 | uint256 _saleRecipientAmount 61 | ) { 62 | require(block.timestamp <= _saleStart, "TokenSale: start date may not be in the past"); 63 | require(_saleDuration > 0, "TokenSale: the sale duration must not be zero"); 64 | require(_tokenOutPrice > 0, "TokenSale: the price must not be zero"); 65 | require(_vestDuration > 0, "TokenSale: the vest duration must not be zero"); 66 | require( 67 | _saleRecipient != address(0) && _saleRecipient != address(this), 68 | "TokenSale: sale recipient should not be zero or this" 69 | ); 70 | require(_tokenLock != address(0), "Address cannot be 0x"); 71 | 72 | tokenIn = _tokenIn; 73 | tokenOut = _tokenOut; 74 | saleStart = _saleStart; 75 | saleDuration = _saleDuration; 76 | tokenOutPrice = _tokenOutPrice; 77 | saleRecipient = _saleRecipient; 78 | tokenLock = ITokenLockVestReader(_tokenLock); 79 | vestDuration = _vestDuration; 80 | remainingSaleRecipientAmount = _saleRecipientAmount; 81 | } 82 | 83 | /** 84 | * @dev Whitelisted buyers can buy `tokenOutAmount` tokens at the `tokenOutPrice`. 85 | * @return tokenInAmount_ The amount of `tokenIn`s bought. 86 | */ 87 | function buy() external returns (uint256 tokenInAmount_) { 88 | require(saleStart <= block.timestamp, "TokenSale: not started"); 89 | require(block.timestamp <= saleStart + saleDuration, "TokenSale: already ended"); 90 | uint256 _tokenOutAmount = whitelistedBuyersAmount[msg.sender]; 91 | require(_tokenOutAmount > 0, "TokenSale: non-whitelisted purchaser or have already bought"); 92 | whitelistedBuyersAmount[msg.sender] = 0; 93 | tokenInAmount_ = (_tokenOutAmount * tokenOutPrice) / 1e18; 94 | 95 | // saleRecipient will receive proceeds first, until fully allocated 96 | if (tokenInAmount_ <= remainingSaleRecipientAmount) { 97 | remainingSaleRecipientAmount -= tokenInAmount_; 98 | require( 99 | tokenIn.transferFrom(msg.sender, saleRecipient, tokenInAmount_), 100 | "TokenSale: tokenIn transfer failed" 101 | ); 102 | } else { 103 | // saleRecipient will either be receiving or have received full allocation 104 | // portion will go to owner 105 | uint256 ownerAmount = tokenInAmount_ - remainingSaleRecipientAmount; 106 | require( 107 | tokenIn.transferFrom(msg.sender, owner(), ownerAmount), 108 | "TokenSale: tokenIn transfer failed" 109 | ); 110 | if (remainingSaleRecipientAmount > 0) { 111 | uint256 saleRecipientAmount = remainingSaleRecipientAmount; 112 | remainingSaleRecipientAmount = 0; 113 | require( 114 | tokenIn.transferFrom(msg.sender, saleRecipient, saleRecipientAmount), 115 | "TokenSale: tokenIn transfer failed" 116 | ); 117 | } 118 | } 119 | 120 | uint256 claimableAmount = (_tokenOutAmount * 2_000) / 10_000; 121 | uint256 remainingAmount; 122 | unchecked { 123 | // this subtraction does not underflow as claimableAmount is a percentage on _tokenOutAmount 124 | remainingAmount = _tokenOutAmount - claimableAmount; 125 | } 126 | 127 | require( 128 | tokenOut.transfer(msg.sender, claimableAmount), 129 | "TokenSale: tokenOut transfer failed" 130 | ); 131 | 132 | // we use same tokenLock instance as airdrop, we make sure that 133 | // the claimers and buyers are distinct to not reinitialize vesting 134 | tokenLock.setupVesting( 135 | msg.sender, 136 | block.timestamp, 137 | block.timestamp, 138 | block.timestamp + vestDuration 139 | ); 140 | // approve TokenLock for token transfer 141 | require(tokenOut.approve(address(tokenLock), remainingAmount), "Approve failed"); 142 | tokenLock.lock(msg.sender, remainingAmount); 143 | 144 | emit Sale(msg.sender, tokenInAmount_, _tokenOutAmount); 145 | } 146 | 147 | /** 148 | * @dev Changes the allowed token allocation for a list of buyers 149 | * @param _buyers The buyers to change the allocation for 150 | * @param _newTokenOutAmounts The new token amounts 151 | */ 152 | function changeWhiteList(address[] memory _buyers, uint256[] memory _newTokenOutAmounts) 153 | external 154 | { 155 | require(msg.sender == owner() || msg.sender == saleRecipient, "TokenSale: not authorized"); 156 | require( 157 | _buyers.length == _newTokenOutAmounts.length, 158 | "TokenSale: parameter length mismatch" 159 | ); 160 | require( 161 | block.timestamp < saleStart || block.timestamp > saleStart + saleDuration, 162 | "TokenSale: ongoing sale" 163 | ); 164 | 165 | for (uint256 i = 0; i < _buyers.length; i++) { 166 | // Does not cover the case that the buyer has not claimed his airdrop 167 | // So it will have to be somewhat manually checked 168 | ITokenLockVestReader.VestingParams memory vestParams = tokenLock.vesting(_buyers[i]); 169 | require(vestParams.unlockBegin == 0, "TokenSale: buyer has existing vest schedule"); 170 | whitelistedBuyersAmount[_buyers[i]] = _newTokenOutAmounts[i]; 171 | emit BuyerWhitelisted(_buyers[i], _newTokenOutAmounts[i]); 172 | } 173 | } 174 | 175 | /** 176 | * @dev Modifies the start time of the sale. Enables a new sale to be created assuming one is not ongoing 177 | * @dev A new list of buyers and tokenAmounts can be done by calling changeWhiteList() 178 | * @param _newSaleStart The new start time of the token sale 179 | */ 180 | function setNewSaleStart(uint64 _newSaleStart) external { 181 | require(msg.sender == owner() || msg.sender == saleRecipient, "TokenSale: not authorized"); 182 | // can only change if there is no ongoing sale 183 | require( 184 | block.timestamp < saleStart || block.timestamp > saleStart + saleDuration, 185 | "TokenSale: ongoing sale" 186 | ); 187 | require(block.timestamp < _newSaleStart, "TokenSale: new sale too early"); 188 | saleStart = _newSaleStart; 189 | } 190 | 191 | /** 192 | * @dev Transfers out any remaining `tokenOut` after the sale to owner 193 | */ 194 | function sweepTokenOut() external { 195 | require(saleStart + saleDuration < block.timestamp, "TokenSale: ongoing sale"); 196 | 197 | uint256 tokenOutBalance = tokenOut.balanceOf(address(this)); 198 | require(tokenOut.transfer(owner(), tokenOutBalance), "TokenSale: transfer failed"); 199 | } 200 | 201 | /** 202 | * @dev Transfers out any tokens (except `tokenOut`) accidentally sent to the contract. 203 | * @param _token The token to sweep 204 | */ 205 | function sweep(ERC20 _token) external { 206 | require(msg.sender == owner() || msg.sender == saleRecipient, "TokenSale: not authorized"); 207 | require(_token != tokenOut, "TokenSale: cannot sweep tokenOut as it belongs to owner"); 208 | require( 209 | _token.transfer(msg.sender, _token.balanceOf(address(this))), 210 | "TokenSale: transfer failed" 211 | ); 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /contracts/test/TestERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 3 | import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 4 | 5 | pragma solidity 0.8.10; 6 | 7 | // initial supply of 100M 8 | contract TestERC20 is ERC20 { 9 | constructor(string memory name_, string memory symbol_) ERC20(name_, symbol_) { 10 | _mint(msg.sender, 100_000_000e18); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /deployments/polygonAddresses.json: -------------------------------------------------------------------------------- 1 | { 2 | "deployer": "0x3d67109E0200abD4D39Cb38377C9f81573f9F191", 3 | "token": "0x6847D3A4c80a82e1fb26f1fC6F09F3Ad5BEB5222", 4 | "tokenLock": "0xB17828789280C77C17B02fc8E6F20Ddc5721f2C2", 5 | "timelock": "0xdFB26381aFBc37f0Fae4A77D385b91B90347aA12", 6 | "governorV1": "0xc6eaDcC36aFcf1C430962506ad79145aD5140E58", 7 | "governor": "0x4Db7E521942BDA8b1fB1B310280135ba4B9c2bee", 8 | "tokenSale": "0xD0e7d5a2220e32914540D97A6D0548658050180b" 9 | } 10 | -------------------------------------------------------------------------------- /deployments/rinkebyAddresses.json: -------------------------------------------------------------------------------- 1 | { 2 | "deployer": "0x3d67109E0200abD4D39Cb38377C9f81573f9F191", 3 | "token": "0xc1D2300f0065FC076c36F6D414a31A2949ca3027", 4 | "tokenLock": "0x60d052Bc46e8Fc115f19541ECcfC42c9E4d5012C", 5 | "timelock": "0x0991406D7034f6D626190f1bf3A737ddba504d27", 6 | "governor": "0x015292c88c6d8446e223d35ad5C52b395b4B5C1B" 7 | } 8 | -------------------------------------------------------------------------------- /deployments/rinkebyMerkleTree.json: -------------------------------------------------------------------------------- 1 | { 2 | "merkleRoot": "0xd97c9a423833d78e0562b8ed2d14752b54e7ef9b52314cafb197e3a339299901", 3 | "tokenTotal": "100000000000000000000000000", 4 | "claims": { 5 | "0x0f4Aeb1847B7F1a735f4a5Af7E8C299b793c1a9A": { 6 | "index": 25, 7 | "amount": "5000000000000000000000000", 8 | "proof": [ 9 | "0x64c899a3904a64775204b11894131944810e73b0630c1dfba4e8d11858be2b82", 10 | "0x73cba145ff47a81e9a26343211ffcf44d442b3a31527a1d30e7743933b4a4f67", 11 | "0xd07eb56cb1bd226f16f4cf89338ccb26ae5f22cf92941f08d9ee00aa746c52c1", 12 | "0xdc7dbfc6e9825aa07f1571db96db618ef376fb7b499d59897412a189ef74c4f8", 13 | "0x35ce38a5a0bb1bdd44f90e6e7caae886c795bd4ae4e5b5c3060e5730d4147235" 14 | ] 15 | }, 16 | "0x3Ab0029e1C4515134464b267557cB80A39902699": { 17 | "index": 17, 18 | "amount": "5000000000000000000000000", 19 | "proof": [ 20 | "0x277c112679aaf71c75809debae294d0e0fdd42ddad79afbd5a102bcea921ffc3", 21 | "0xe5962421fcd4aae831bd71b888701a88a8e6039c372b7d9cadf6893a1828796d", 22 | "0xd07eb56cb1bd226f16f4cf89338ccb26ae5f22cf92941f08d9ee00aa746c52c1", 23 | "0xdc7dbfc6e9825aa07f1571db96db618ef376fb7b499d59897412a189ef74c4f8", 24 | "0x35ce38a5a0bb1bdd44f90e6e7caae886c795bd4ae4e5b5c3060e5730d4147235" 25 | ] 26 | }, 27 | "0x4F3F7ca7E91D869180EBbA55e4322845a8Dc6862": { 28 | "index": 1, 29 | "amount": "5000000000000000000000000", 30 | "proof": [ 31 | "0x5722eb57f553e2616933dd90f1e94323771e3d16e8c1236e0b6c5b51312a9a75", 32 | "0xe5962421fcd4aae831bd71b888701a88a8e6039c372b7d9cadf6893a1828796d", 33 | "0xd07eb56cb1bd226f16f4cf89338ccb26ae5f22cf92941f08d9ee00aa746c52c1", 34 | "0xdc7dbfc6e9825aa07f1571db96db618ef376fb7b499d59897412a189ef74c4f8", 35 | "0x35ce38a5a0bb1bdd44f90e6e7caae886c795bd4ae4e5b5c3060e5730d4147235" 36 | ] 37 | }, 38 | "0x5dcEb6f4dc5b64Af6271A5Ab3297DbE3C01dd57B": { 39 | "index": 9, 40 | "amount": "5000000000000000000000000", 41 | "proof": [ 42 | "0x6b60f13d94a613991cd461cc8803831534c94765f6f547e5fdc56d2b695d535a", 43 | "0x73cba145ff47a81e9a26343211ffcf44d442b3a31527a1d30e7743933b4a4f67", 44 | "0xd07eb56cb1bd226f16f4cf89338ccb26ae5f22cf92941f08d9ee00aa746c52c1", 45 | "0xdc7dbfc6e9825aa07f1571db96db618ef376fb7b499d59897412a189ef74c4f8", 46 | "0x35ce38a5a0bb1bdd44f90e6e7caae886c795bd4ae4e5b5c3060e5730d4147235" 47 | ] 48 | }, 49 | "0x62641eAE546835813B56EC7b544756A532275Dd3": { 50 | "index": 29, 51 | "amount": "5000000000000000000000000", 52 | "proof": [ 53 | "0x0376407535759139c660ce58c2918c3c9a4d88159680be28b7601baa55b8d00f", 54 | "0x46592624689eed94a4f18d0592196bc0cfd8dfbcb024a917bce4b02dcaf8f947", 55 | "0x180b36236aed7bec3a3f0bf8cc3d94d9325e60c77222d60ad0199c9b9a35f9af", 56 | "0xdc7dbfc6e9825aa07f1571db96db618ef376fb7b499d59897412a189ef74c4f8", 57 | "0x35ce38a5a0bb1bdd44f90e6e7caae886c795bd4ae4e5b5c3060e5730d4147235" 58 | ] 59 | }, 60 | "0x670f9e8B37d5816c2eB93A1D94841C66652a8E26": { 61 | "index": 0, 62 | "amount": "5000000000000000000000000", 63 | "proof": [ 64 | "0xd1b8cee75ec1c6f19546555b56415b8f3828bd60b444400c26bf449eccc8c6e4", 65 | "0xbbbdf58455a8a93f3c01f0866a2531501f7d697ae881010a43f26c30e10ec670", 66 | "0x8f2b97da96dccff0e55b0a135d930de9864faacc6e78a5827298be209b77497c" 67 | ] 68 | }, 69 | "0x691Cbab55CC1806d29994784Ba9d9e679c03f164": { 70 | "index": 19, 71 | "amount": "5000000000000000000000000", 72 | "proof": [ 73 | "0x9399ed13f0f6488b2e55c4b9d64b2a1f812beca85926f676312703639e59658b", 74 | "0xef8b9443f6a3365e812c8413cf2e34f28ef1145e7ad7aeff696c717fe311f900", 75 | "0xfd0d21b78fb7d070618b741e7457c01334f982d6c249bb69e032df81f205fd12", 76 | "0xd06405e2454fd9e2b6242f613939e97d77f45e7b962491bcd6eb3567fb77471e", 77 | "0x35ce38a5a0bb1bdd44f90e6e7caae886c795bd4ae4e5b5c3060e5730d4147235" 78 | ] 79 | }, 80 | "0x697ccd97C8419EBba7347CEF03a0CD02804EbF54": { 81 | "index": 3, 82 | "amount": "5000000000000000000000000", 83 | "proof": [ 84 | "0x9cf928c2c33588bd7848227853086a4df71a7ccec884b88c7e8351624263d83b", 85 | "0xef8b9443f6a3365e812c8413cf2e34f28ef1145e7ad7aeff696c717fe311f900", 86 | "0xfd0d21b78fb7d070618b741e7457c01334f982d6c249bb69e032df81f205fd12", 87 | "0xd06405e2454fd9e2b6242f613939e97d77f45e7b962491bcd6eb3567fb77471e", 88 | "0x35ce38a5a0bb1bdd44f90e6e7caae886c795bd4ae4e5b5c3060e5730d4147235" 89 | ] 90 | }, 91 | "0x6c422839E7EceDb6d2A86F3F2bFd03aDd154Fc27": { 92 | "index": 27, 93 | "amount": "5000000000000000000000000", 94 | "proof": [ 95 | "0xa16191924fbf5654cf91964d4139fce4d44e4b95997fd87b8784c2149dba3456", 96 | "0xab8aac7223250b650a901edf78fcc03ffb5edb8cb51570bab034350e5d82de9f", 97 | "0xfd0d21b78fb7d070618b741e7457c01334f982d6c249bb69e032df81f205fd12", 98 | "0xd06405e2454fd9e2b6242f613939e97d77f45e7b962491bcd6eb3567fb77471e", 99 | "0x35ce38a5a0bb1bdd44f90e6e7caae886c795bd4ae4e5b5c3060e5730d4147235" 100 | ] 101 | }, 102 | "0x7C0fb88c87c30eBF70340E25fe47763e53b907cF": { 103 | "index": 2, 104 | "amount": "5000000000000000000000000", 105 | "proof": [ 106 | "0xf1397ede8a9396443d3f26ac1cceeb9848a171b066d3eb053ed2cc916cd44625", 107 | "0x44cff0b9cde51403a6afbdbaa5078cee26ac3956db6a9b3c91ca0cee86b4f1c4", 108 | "0x8f2b97da96dccff0e55b0a135d930de9864faacc6e78a5827298be209b77497c" 109 | ] 110 | }, 111 | "0x8498EAb53e03E3143d77B2303eDBdAC6C9041D33": { 112 | "index": 23, 113 | "amount": "5000000000000000000000000", 114 | "proof": [ 115 | "0x6d0e33acba7893addfb43805b2c3d84e0c13980a726769ec33faae55804c3a08", 116 | "0x833bf8e340444fe6fd0f6e19628eff2878f0f62a808c65125c6dfbcb4b22787f", 117 | "0xca99d5101393669db5d5634aefd5eeff73fbd87751309567124bdf35c1dbfc88", 118 | "0xd06405e2454fd9e2b6242f613939e97d77f45e7b962491bcd6eb3567fb77471e", 119 | "0x35ce38a5a0bb1bdd44f90e6e7caae886c795bd4ae4e5b5c3060e5730d4147235" 120 | ] 121 | }, 122 | "0x8D31BAC0870e323354eAF6F98277860772FFB2d4": { 123 | "index": 7, 124 | "amount": "5000000000000000000000000", 125 | "proof": [ 126 | "0x6d2d722e5a34775acb92388eae3ae751dfb9fc468fff36d9a89bed9c3acc0c27", 127 | "0x833bf8e340444fe6fd0f6e19628eff2878f0f62a808c65125c6dfbcb4b22787f", 128 | "0xca99d5101393669db5d5634aefd5eeff73fbd87751309567124bdf35c1dbfc88", 129 | "0xd06405e2454fd9e2b6242f613939e97d77f45e7b962491bcd6eb3567fb77471e", 130 | "0x35ce38a5a0bb1bdd44f90e6e7caae886c795bd4ae4e5b5c3060e5730d4147235" 131 | ] 132 | }, 133 | "0xA432F83d8054F5F859cAcb86574baC5e07DD6529": { 134 | "index": 31, 135 | "amount": "5000000000000000000000000", 136 | "proof": [ 137 | "0x7b340f8e3f1c1eb2b55b08beb0ed65b33033a1c4406de7dbc68b2ecd06155dc3", 138 | "0x205bcc56124e8b1934f4a6bb5a5c6dc79afda593c183ee855ab1a139848bc404", 139 | "0xca99d5101393669db5d5634aefd5eeff73fbd87751309567124bdf35c1dbfc88", 140 | "0xd06405e2454fd9e2b6242f613939e97d77f45e7b962491bcd6eb3567fb77471e", 141 | "0x35ce38a5a0bb1bdd44f90e6e7caae886c795bd4ae4e5b5c3060e5730d4147235" 142 | ] 143 | }, 144 | "0xD3488b8C87416946D82CC957178B0863A1F089b2": { 145 | "index": 4, 146 | "amount": "5000000000000000000000000", 147 | "proof": [ 148 | "0xbef40f67f4762fa15e228b7a02b6e6ca011934f4ca4af0b7704e497e0ed45c74", 149 | "0xbbbdf58455a8a93f3c01f0866a2531501f7d697ae881010a43f26c30e10ec670", 150 | "0x8f2b97da96dccff0e55b0a135d930de9864faacc6e78a5827298be209b77497c" 151 | ] 152 | }, 153 | "0xD5388291EAbe96b56069440C97046791E2F72573": { 154 | "index": 11, 155 | "amount": "5000000000000000000000000", 156 | "proof": [ 157 | "0xa24e25dd9643fd7408b84337023e083f8482862ee697b7a88b01cad949f47430", 158 | "0xab8aac7223250b650a901edf78fcc03ffb5edb8cb51570bab034350e5d82de9f", 159 | "0xfd0d21b78fb7d070618b741e7457c01334f982d6c249bb69e032df81f205fd12", 160 | "0xd06405e2454fd9e2b6242f613939e97d77f45e7b962491bcd6eb3567fb77471e", 161 | "0x35ce38a5a0bb1bdd44f90e6e7caae886c795bd4ae4e5b5c3060e5730d4147235" 162 | ] 163 | }, 164 | "0xF20eb7eAf52712EA0Aa80467741f34E6b0dB18F8": { 165 | "index": 21, 166 | "amount": "5000000000000000000000000", 167 | "proof": [ 168 | "0x06939865a30b63066f5c891496f0d91899c62a67d78a7fe403ab453829e5a9ab", 169 | "0xfd68a5ad5939cc37502cccce2e3cc3c73e11825ee4a87ff23aeafecb320d0f16", 170 | "0x180b36236aed7bec3a3f0bf8cc3d94d9325e60c77222d60ad0199c9b9a35f9af", 171 | "0xdc7dbfc6e9825aa07f1571db96db618ef376fb7b499d59897412a189ef74c4f8", 172 | "0x35ce38a5a0bb1bdd44f90e6e7caae886c795bd4ae4e5b5c3060e5730d4147235" 173 | ] 174 | }, 175 | "0xa1fA3C686C9c4E5e8407b32B67191B079a65ffD2": { 176 | "index": 13, 177 | "amount": "5000000000000000000000000", 178 | "proof": [ 179 | "0x05a43b9c1e21a19d206f29214fb3fc8b5612f11f5ac98f4511b0805b22a46476", 180 | "0x46592624689eed94a4f18d0592196bc0cfd8dfbcb024a917bce4b02dcaf8f947", 181 | "0x180b36236aed7bec3a3f0bf8cc3d94d9325e60c77222d60ad0199c9b9a35f9af", 182 | "0xdc7dbfc6e9825aa07f1571db96db618ef376fb7b499d59897412a189ef74c4f8", 183 | "0x35ce38a5a0bb1bdd44f90e6e7caae886c795bd4ae4e5b5c3060e5730d4147235" 184 | ] 185 | }, 186 | "0xbB79597641483Ed96BCE9fc24b4D63F720898b8A": { 187 | "index": 15, 188 | "amount": "5000000000000000000000000", 189 | "proof": [ 190 | "0x8f199fabf2256c07fe06fbf2fcbc48fc0e41ec0b8b380a76c8784cdf350a94de", 191 | "0x205bcc56124e8b1934f4a6bb5a5c6dc79afda593c183ee855ab1a139848bc404", 192 | "0xca99d5101393669db5d5634aefd5eeff73fbd87751309567124bdf35c1dbfc88", 193 | "0xd06405e2454fd9e2b6242f613939e97d77f45e7b962491bcd6eb3567fb77471e", 194 | "0x35ce38a5a0bb1bdd44f90e6e7caae886c795bd4ae4e5b5c3060e5730d4147235" 195 | ] 196 | }, 197 | "0xe552C6A88E71B2A5069Dec480507F54321Dc65F3": { 198 | "index": 5, 199 | "amount": "5000000000000000000000000", 200 | "proof": [ 201 | "0x0a03058b2d7a5803af405b13c82a6dff9e9ac0a100a039006fc8a31eda238375", 202 | "0xfd68a5ad5939cc37502cccce2e3cc3c73e11825ee4a87ff23aeafecb320d0f16", 203 | "0x180b36236aed7bec3a3f0bf8cc3d94d9325e60c77222d60ad0199c9b9a35f9af", 204 | "0xdc7dbfc6e9825aa07f1571db96db618ef376fb7b499d59897412a189ef74c4f8", 205 | "0x35ce38a5a0bb1bdd44f90e6e7caae886c795bd4ae4e5b5c3060e5730d4147235" 206 | ] 207 | }, 208 | "0xf4290941dBc8b31c277E30deFF3fC59979FC6757": { 209 | "index": 6, 210 | "amount": "5000000000000000000000000", 211 | "proof": [ 212 | "0xd98db0b66f416d88a64415362c3b47b66f806b4837760655e490951be10378f9", 213 | "0x44cff0b9cde51403a6afbdbaa5078cee26ac3956db6a9b3c91ca0cee86b4f1c4", 214 | "0x8f2b97da96dccff0e55b0a135d930de9864faacc6e78a5827298be209b77497c" 215 | ] 216 | } 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import * as dotenv from 'dotenv'; 2 | 3 | import {HardhatUserConfig} from 'hardhat/config'; 4 | import '@nomiclabs/hardhat-etherscan'; 5 | import '@typechain/hardhat'; 6 | import '@nomiclabs/hardhat-ethers'; 7 | import '@nomiclabs/hardhat-waffle'; 8 | import 'hardhat-gas-reporter'; 9 | import 'solidity-coverage'; 10 | import './scripts/deploy'; 11 | import './scripts/verify'; 12 | import './scripts/proposals'; 13 | 14 | dotenv.config(); 15 | 16 | // You need to export an object to set up your config 17 | // Go to https://hardhat.org/config/ to learn more 18 | 19 | const config: HardhatUserConfig = { 20 | solidity: { 21 | version: '0.8.10', 22 | settings: { 23 | optimizer: { 24 | enabled: true, 25 | runs: 999999, 26 | }, 27 | }, 28 | }, 29 | networks: { 30 | hardhat: { 31 | // forking: { 32 | // url: process.env.POLYGON_URL!, 33 | // }, 34 | }, 35 | develop: { 36 | url: 'http://127.0.0.1:8545/' 37 | }, 38 | rinkeby: { 39 | chainId: 4, 40 | url: process.env.RINKEBY_URL || '', 41 | accounts: process.env.PRIVATE_KEY !== undefined ? [process.env.PRIVATE_KEY] : [], 42 | }, 43 | polygon: { 44 | chainId: 137, 45 | url: process.env.POLYGON_URL || '', 46 | accounts: process.env.PRIVATE_KEY !== undefined ? [process.env.PRIVATE_KEY] : [], 47 | }, 48 | }, 49 | etherscan: { 50 | apiKey: { 51 | polygon: process.env.POLYGONSCAN_API_KEY == undefined ? '' : process.env.POLYGONSCAN_API_KEY, 52 | } 53 | }, 54 | typechain: { 55 | outDir: 'typechain', 56 | target: 'ethers-v5', 57 | }, 58 | gasReporter: { 59 | enabled: process.env.REPORT_GAS !== undefined, 60 | currency: 'USD', 61 | }, 62 | mocha: { 63 | // 1 hour, essentially disabled auto timeout 64 | timeout: 60 * 60 * 1000, 65 | }, 66 | }; 67 | 68 | export default config; 69 | -------------------------------------------------------------------------------- /interfaces/IRevokableTokenLock.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.10; 3 | 4 | interface IRevokableTokenLock { 5 | function setupVesting( 6 | address recipient, 7 | uint256 _unlockBegin, 8 | uint256 _unlockCliff, 9 | uint256 _unlockEnd 10 | ) external; 11 | 12 | function lock(address recipient, uint256 amount) external; 13 | } 14 | -------------------------------------------------------------------------------- /interfaces/ITokenLockVestReader.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.10; 3 | 4 | import "./IRevokableTokenLock.sol"; 5 | 6 | interface ITokenLockVestReader is IRevokableTokenLock { 7 | struct VestingParams { 8 | uint256 unlockBegin; 9 | uint256 unlockCliff; 10 | uint256 unlockEnd; 11 | uint256 lockedAmounts; 12 | uint256 claimedAmounts; 13 | } 14 | 15 | function vesting(address) external view returns (VestingParams memory); 16 | } 17 | -------------------------------------------------------------------------------- /merkle-data.csv: -------------------------------------------------------------------------------- 1 | polygonAddress,tokens10e18 2 | 0x02435fb305D2BBA609E044279C50498Abe279380,6069000000000000000000 3 | 0x068484F7BD2b7D7C5a698d89e75ddcaf3a92B879,335796000000000000000000 4 | 0x068484F7BD2b7D7C5a698d89e75ddcaf3a92B879,32524000000000000000000 5 | 0x07f0571566E4b07eA825798A70Fd67fE79077f4E,737957000000000000000000 6 | 0x09e68152332CbD3A71DE42769439663D1e283855,Incomplete 7 | 0x09e68152332CbD3A71DE42769439663D1e283855,Incomplete 8 | 0x0c4243ca087F4e4738596F33292064e847DA80dA,10756400000000000000000000 9 | 0x0c4243ca087F4e4738596F33292064e847DA80dA,89024000000000000000000 10 | 0x0e21C3Ce673ec7F4C346f587cDB35Ce1a3c12885,500000000000000000000000 11 | 0x0F81a649448c6593629F7893d9C5e04738b508D0,402955000000000000000000 12 | 0x160834291e67Aa55F830062cA8a47186b5E319A9,335796000000000000000000 13 | 0x17175C04daC5984d7e98b4Da665ECd09E4bc477e,596781000000000000000000 14 | 0x192bDD30D272AabC2B1c3c719c518F0f2d10cc60,7011280000000000000000000 15 | 0x1bBcc9B996D70dA47557A0BbEF2979d6C5699518,Incomplete 16 | 0x1c95a25459edd226aCF8061D40C7FB03716371F7,344921000000000000000000 17 | 0x1df6D5549893Ce1480f7cb1FC380FA90bAB3C2D9,671592000000000000000000 18 | 0x24805db6a8439ad2559b495e784e40e444bb480a,176385000000000000000000 19 | 0x25c84Be73af28d65E71F092344Efd92B2c79e3f9,743835000000000000000000 20 | 0x25cB64Aa24f560A0719e92F4EBFf2876f0dA4267,402955000000000000000000 21 | 0x273BA31C706b9D9FdAe1FD999183Bfa865895bE9,671592000000000000000000 22 | 0x28235dE0121700a462B5098Cff7D3615b050551F,2663910000000000000000000 23 | 0x28235dE0121700a462B5098Cff7D3615b050551F,139157000000000000000000 24 | 0x2999Fe533BC08A03304C96E8668BfA17D9D0D35b,353833000000000000000000 25 | 0x2bc996ded6d9325311dee1bd32998e2fc7f9c922,95165000000000000000000 26 | 0x2dca6A81aE189c26Bd63316e659d2b6fB7755aAe,537273000000000000000000 27 | 0x2eCf1B0456F4C352775fcF68cFE7354Bf389A701,1575380000000000000000000 28 | 0x2eCf1B0456F4C352775fcF68cFE7354Bf389A701,201478000000000000000000 29 | 0x30fC9D86149c9241FDd5450748F8CB6734E1fdeF,Incomplete 30 | 0x38BCE8C1Ca5a061aa8e2b4750C18BA3efb27CeD9,670572000000000000000000 31 | 0x3C2b8156D8AbE9814BFc800b170b60C66eC33cc2,772853000000000000000000 32 | 0x3Ca8C469ff8156eeBB1518FA631bf8ebc5EE0688,Incomplete 33 | 0x3d977eF4855A526500E901bFA43c209E0C51BA84,6204000000000000000000 34 | 0x3f60008Dfd0EfC03F476D9B489D6C5B13B3eBF2C,Incomplete 35 | 0x3feE50d2888F2F7106fcdC0120295EBA3ae59245,1242450000000000000000000 36 | 0x45d28aA363fF215B4c6b6a212DC610f004272bb5,443251000000000000000000 37 | 0x4666e94300429C6d3340449bC828E4218F360672,Incomplete 38 | 0x4666e94300429C6d3340449bC828E4218F360672,Incomplete 39 | 0x4977110Ed3CD5eC5598e88c8965951a47dd4e738,Incomplete 40 | 0x4b551a7e465b96a411969d0044a57866b2b3f9a8,570853000000000000000000 41 | 0x4DF37CC3C48eC3EB689c8Bf0D91249cE2506f73B,1287610000000000000000000 42 | 0x5138bA98c03EA6275682b00A33b0D13362072301,174517000000000000000000 43 | 0x53D8EDF6a54239eB785eC72213919Fb6b6B73598,Incomplete 44 | 0x58993cFa67cD6151EC3D806AE93a7Ed365dB2f88,1157030000000000000000000 45 | 0x60959ed8307ee2b0d04306f6b319aeee8864f1ee,751700000000000000000000 46 | 0x609fB8f19B28a76F12557A235C3e88F621267c36,500000000000000000000000 47 | 0x611DaBF15f0077c64fDB5067376Ad593191e65F3,562417000000000000000000 48 | 0x63A62BDCA68DCA4B98385De493f73C7393b14F6b,66822000000000000000000 49 | 0x63D43942A69b961E398A0F7C260bC3718d320eE2,162056000000000000000000 50 | 0x64b29cf8a5b2ec1133f2ea5b8be644f41c9d6d53,83066000000000000000000 51 | 0x64fd9A3Ca8f856CeD235Fd4F634A026B891A10f6,150298000000000000000000 52 | 0x660f9D529F01DAfF0A7F15213a8fA39F42FCFe62,261704000000000000000000 53 | 0x6823636c2462cfdcD8d33fE53fBCD0EdbE2752ad,22278000000000000000000000 54 | 0x6ae089D92a2E88CA18a2d463aD32849cB41C4D0f,94461000000000000000000 55 | 0x70aF29f754988473fcAbA6E01AbfbafF871046d1,2167350000000000000000000 56 | 0x7125412Fc011D74C26E3CA768aB6224643702313,1033630000000000000000000 57 | 0x75aAfefc48D23C65F49603491630308DcBd468f5,306843000000000000000000 58 | 0x7c23Ec85618C51Ab13310df4aAe8bc2DC9Cd4D38,500000000000000000000000 59 | 0x7c6F92B3689C1fF4beF9f812Ca40E244417e9Af1,Incomplete 60 | 0x7d7935EDd4b6cDB5f34B0E1cCEAF85a3C4A11254,671592000000000000000000 61 | 0x7e026a0C061382B0F5935a90BC7324ab0a5A3aCc,11415700000000000000000000 62 | 0x7eD52863829AB99354F3a0503A622e82AcD5F7d3,Incomplete 63 | 0x7F6f138C955e5B1017A12E4567D90C62abb00074,671592000000000000000000 64 | 0x81D4cf8A3892557e4a1571e97b88f0351eF0dcFD,1517520000000000000000000 65 | 0x825aE6fb177186e9551ab1CDd6D4aB10B22A0Dba,445640000000000000000000 66 | 0x82bF03b118AC4B9Cf9A9D520B7c23128B68774aa,335796000000000000000000 67 | 0x84F9079a9A39ae80Cf9DEE65a0E316B1a897684A,78869000000000000000000 68 | 0x8885b44032Cba8813e870dA237f0598D6C822424,90751000000000000000000 69 | 0x8b8f9A972B19A26287b28162e49787a1f3ec5A32,Incomplete 70 | 0x8e2A89fF2F45ed7f8C8506f846200D671e2f176f,9911400000000000000000000 71 | 0x8efe50a4b8c8b64fdc93a5b1712fd735d8223cd5,684205000000000000000000 72 | 0x945D28dF9779795BDab5BC6FDD9f36c0Ef1F7190,725608000000000000000000 73 | 0x94e0c714FaD746AC9e220E1B9396771C1FA7A236,74408000000000000000000 74 | 0x95687C27DcE4D413a91A0E1fd0478F4751da8730,Incomplete 75 | 0x96EdF266EeAbBF4e2C136EAFf7BF30eECaD52c49,Incomplete 76 | 0x9b3E9e3E4a174d59279FC7cd268e035992412384,Incomplete 77 | 0x9E4A6Ef535B28bbC947E4E75644dDC9b2C114ee6,350313000000000000000000 78 | 0xa360E38cdf57e092218E7A5f4Eb73032eE74A8bD,50763000000000000000000 79 | 0xA5BF5254FaAe168231140e139db98F79B65503C8,13873000000000000000000 80 | 0xABb99042FAE2C86b8437585467a24a71b17d23bA,1262230000000000000000000 81 | 0xABb99042FAE2C86b8437585467a24a71b17d23bA,55098000000000000000000 82 | 0xacD628D01dd8534Db6Ebe4894C1be3c8D34ebe14,604433000000000000000000 83 | 0xAdBb28C2FEe078440B7088bbcd68DCfA63e55625,196753000000000000000000 84 | 0xAe4281d74056F3975941b66daf324bB893279cF0,73981000000000000000000 85 | 0xAfD5f60aA8eb4F488eAA0eF98c1C5B0645D9A0A0,302216000000000000000000 86 | 0xb084F0AdB86BF6B001E540Ac05439D98A499ce1D,165163000000000000000000 87 | 0xB0b63f022275234F9821f2e92A9d4ae5BCDDB4F5,436535000000000000000000 88 | 0xb8150a1B6945e75D05769D685b127b41E6335Bbc,Incomplete 89 | 0xb8c2C29ee19D8307cb7255e1Cd9CbDE883A267d5,Incomplete 90 | 0xBaf2a684c27133D094e810B6652e9d23F56F07CA,873069000000000000000000 91 | 0xBb46360a467da8b1c548a28C77dAb44758ab471c,434375000000000000000000 92 | 0xBCa294543b46fF11CFAFbCF67434BEa23c21404b,993615000000000000000000 93 | 0xC3e61F4ef0a95E616F75f94D5b6A358bD9DC18ac,Incomplete 94 | 0xC59f50aC5C90Ac8AFb9936daB81fe9Dad20808fC,72944000000000000000000 95 | 0xcCd72BeA12f3927063bE3D8798a4b74082713cb5,Incomplete 96 | 0xd0C81E82AbDdF29C6505d660f5bEBe60CDFf03c5,220121000000000000000000 97 | 0xd295584135829D89427674B72560222B06E08a15,339751000000000000000000 98 | 0xD5388291EAbe96b56069440C97046791E2F72573,3982590000000000000000000 99 | 0xd685Dd21BD58Ff6976f14C4E7F847a2Da0047965,133331000000000000000000 100 | 0xD6FCD2F85fE975bB9b0f3C1B1c6802bB09d33E43,205852000000000000000000 101 | 0xd70804463bb2760c3384fc87bbe779e3d91bab3a,Incomplete 102 | 0xd7b3b50977a5947774bFC46B760c0871e4018e97,Incomplete 103 | 0xd7f69D59B6E8E6f3031B12f692d468bC88Ebdd97,Incomplete 104 | 0xDA3Cf9e341A4C7ecf41070b074EaBDC5D433f159,59882000000000000000000 105 | 0xDaA39d73eF9b4495bfe206FF13eE29E759F95d63,121030000000000000000000 106 | 0xE16584424F34380DBf798f547Af684597788FbC7,638803000000000000000000 107 | 0xE400820f3D60d77a3EC8018d44366ed0d334f93C,537273000000000000000000 108 | 0xe88cAc4e10C4D316E0d52B82dd54f26ade3f0Bb2,701064000000000000000000 109 | 0xEBB014649852462f09489E3DFbe45D9759E9b819,1809720000000000000000000 110 | 0xeFBFe9c6a66d871175d2B0ab35bc00255Aa36298,636271000000000000000000 111 | 0xf048819F1190E911b5a78C7b204dD3a1F5f0e509,335796000000000000000000 112 | 0xf38746dC80Af641247F4cA11ECE611696806D438,Incomplete 113 | 0xf4519d24f8B2cc80c41ad6800CE0F27210848658,580586000000000000000000 114 | 0xf4519d24f8B2cc80c41ad6800CE0F27210848658,483546000000000000000000 115 | 0xF4F56fA0D045ae0e6Ba8f82e2C32887FE0B152Ea,62903000000000000000000 116 | 0xf6401adc23Faa6B9AD83eA8604CA7254CB7F53e7,Incomplete 117 | 0xf944069B489F1ebfF4C3C6a6014d58cBEf7C7009,335796000000000000000000 118 | 0xfa2Df13a636b8F2FAaA179785797f1f16fDdF8ED,81104000000000000000000 119 | 0xFDf01E09b9bd3f3bE062a3085fa2c945a263F5a7,191364000000000000000000 -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "c4genesisdao", 3 | "version": "1.0.0", 4 | "description": "Code4rena Genesis DAO", 5 | "main": "hardhat.config.ts", 6 | "scripts": { 7 | "test": "yarn hardhat test", 8 | "test-no-compile": "TS_NODE_TRANSPILE_ONLY=1 yarn hardhat test --no-compile", 9 | "first-compile": "TS_NODE_TRANSPILE_ONLY=1 yarn hardhat typechain", 10 | "compile": "yarn hardhat compile", 11 | "generate-merkle-root": "ts-node ./scripts/airdrop/generate-merkle-root.ts", 12 | "generate-merkle-root:example": "ts-node ./scripts/airdrop/generate-merkle-root.ts -i ./scripts/airdrop/example.json -o resultExample.json", 13 | "lint:check": "prettier --check '(contracts|interfaces|src|scripts|test|deployments)/**/*.(sol|json|ts)'", 14 | "lint:write": "prettier -w '(contracts|interfaces|src|scripts|test|deployments)/**/*.(sol|json|ts)'" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/code-423n4/genesis.git" 19 | }, 20 | "author": "", 21 | "license": "AGPL-3.0", 22 | "bugs": { 23 | "url": "https://github.com/code-423n4/genesis/issues" 24 | }, 25 | "homepage": "https://github.com/code-423n4/genesis#readme", 26 | "dependencies": { 27 | "hardhat": "2.9.0", 28 | "lodash": "^4.17.21" 29 | }, 30 | "devDependencies": { 31 | "@nomiclabs/hardhat-ethers": "^2.0.4", 32 | "@nomiclabs/hardhat-etherscan": "3.0.3", 33 | "@nomiclabs/hardhat-waffle": "^2.0.0", 34 | "@openzeppelin/contracts": "^4.5.0", 35 | "@typechain/ethers-v5": "^9.0.0", 36 | "@typechain/hardhat": "^4.0.0", 37 | "@types/chai": "^4.2.21", 38 | "@types/lodash": "^4.14.178", 39 | "@types/mocha": "^9.0.0", 40 | "@types/node": "^16.4.13", 41 | "@typescript-eslint/eslint-plugin": "^4.29.1", 42 | "@typescript-eslint/parser": "^4.29.1", 43 | "chai": "^4.2.0", 44 | "commander": "^8.3.0", 45 | "dotenv": "^10.0.0", 46 | "eslint": "^7.29.0", 47 | "eslint-config-prettier": "^8.3.0", 48 | "eslint-config-standard": "^16.0.3", 49 | "eslint-plugin-import": "^2.23.4", 50 | "eslint-plugin-node": "^11.1.0", 51 | "eslint-plugin-prettier": "^3.4.0", 52 | "eslint-plugin-promise": "^5.1.0", 53 | "ethereum-waffle": "^3.0.0", 54 | "ethereumjs-util": "^7.1.3", 55 | "ethers": "^5.0.0", 56 | "hardhat-gas-reporter": "^1.0.4", 57 | "prettier": "^2.3.2", 58 | "prettier-plugin-solidity": "^1.0.0-beta.19", 59 | "solhint": "^3.3.6", 60 | "solidity-coverage": "^0.7.16", 61 | "ts-generator": "^0.1.1", 62 | "ts-node": "^10.4.0", 63 | "typechain": "^7.0.0", 64 | "typescript": "^4.5.5" 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /scripts/airdrop/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "0xF3c6F5F265F503f53EAD8aae90FC257A5aa49AC1": "10000000000000000000", 3 | "0xB9CcDD7Bedb7157798e10Ff06C7F10e0F37C6BdD": "20000000000000000000", 4 | "0xf94DbB18cc2a7852C9CEd052393d517408E8C20C": "30000000000000000000", 5 | "0xf0591a60b8dBa2420408Acc5eDFA4f8A15d87308": "40000000000000000000", 6 | "0x6A2dE67981CbE91209c1046D67eF7a45631d0666": "50000000000000000000", 7 | "0x7C262baf13794f54e3514539c411f92716996C38": "12345678901234567890", 8 | "0x57E7c6B647C004CFB7A38E08fDDef09Af5Ea55eD": "11111111111111111111111", 9 | "0x05fc93DeFFFe436822100E795F376228470FB514": "99999999999999999999999" 10 | } 11 | -------------------------------------------------------------------------------- /scripts/airdrop/generate-merkle-root.ts: -------------------------------------------------------------------------------- 1 | // modified from https://github.com/Uniswap/merkle-distributor/blob/master/scripts/generate-merkle-root.ts 2 | import {program} from 'commander'; 3 | import fs from 'fs'; 4 | import {parseBalanceMap} from '../../src/parse-balance-map'; 5 | 6 | program 7 | .version('0.0.0') 8 | .requiredOption( 9 | '-i, --input ', 10 | 'input JSON file location containing a map of account addresses to string balances' 11 | ) 12 | .requiredOption('-o, --output ', 'output JSON filename'); 13 | 14 | program.parse(process.argv); 15 | const options = program.opts(); 16 | 17 | const json = JSON.parse(fs.readFileSync(options.input, {encoding: 'utf8'})); 18 | 19 | if (typeof json !== 'object') throw new Error('Invalid JSON'); 20 | let resultJson = JSON.stringify(parseBalanceMap(json), null, 2); 21 | fs.writeFileSync(options.output, resultJson); 22 | -------------------------------------------------------------------------------- /scripts/airdrop/rinkeby.json: -------------------------------------------------------------------------------- 1 | { 2 | "0x670f9e8B37d5816c2eB93A1D94841C66652a8E26": "5000000000000000000000000", 3 | "0xD5388291EAbe96b56069440C97046791E2F72573": "5000000000000000000000000", 4 | "0x5dcEb6f4dc5b64Af6271A5Ab3297DbE3C01dd57B": "5000000000000000000000000", 5 | "0x7C0fb88c87c30eBF70340E25fe47763e53b907cF": "5000000000000000000000000", 6 | "0x3Ab0029e1C4515134464b267557cB80A39902699": "5000000000000000000000000", 7 | "0xD3488b8C87416946D82CC957178B0863A1F089b2": "5000000000000000000000000", 8 | "0x8498EAb53e03E3143d77B2303eDBdAC6C9041D33": "5000000000000000000000000", 9 | "0x697ccd97C8419EBba7347CEF03a0CD02804EbF54": "5000000000000000000000000", 10 | "0x8D31BAC0870e323354eAF6F98277860772FFB2d4": "5000000000000000000000000", 11 | "0x0f4Aeb1847B7F1a735f4a5Af7E8C299b793c1a9A": "5000000000000000000000000", 12 | "0xf4290941dBc8b31c277E30deFF3fC59979FC6757": "5000000000000000000000000", 13 | "0x62641eAE546835813B56EC7b544756A532275Dd3": "5000000000000000000000000", 14 | "0xa1fA3C686C9c4E5e8407b32B67191B079a65ffD2": "5000000000000000000000000", 15 | "0xA432F83d8054F5F859cAcb86574baC5e07DD6529": "5000000000000000000000000", 16 | "0x4F3F7ca7E91D869180EBbA55e4322845a8Dc6862": "5000000000000000000000000", 17 | "0xbB79597641483Ed96BCE9fc24b4D63F720898b8A": "5000000000000000000000000", 18 | "0xe552C6A88E71B2A5069Dec480507F54321Dc65F3": "5000000000000000000000000", 19 | "0x6c422839E7EceDb6d2A86F3F2bFd03aDd154Fc27": "5000000000000000000000000", 20 | "0x691Cbab55CC1806d29994784Ba9d9e679c03f164": "5000000000000000000000000", 21 | "0xF20eb7eAf52712EA0Aa80467741f34E6b0dB18F8": "5000000000000000000000000" 22 | } 23 | -------------------------------------------------------------------------------- /scripts/deploy/config.ts: -------------------------------------------------------------------------------- 1 | import {BigNumber as BN, constants} from 'ethers'; 2 | import {ONE_18, ONE_DAY, ONE_YEAR} from '../../shared/Constants'; 3 | 4 | type Config = { 5 | FREE_SUPPLY: BN; 6 | AIRDROP_SUPPLY: BN; 7 | CLAIMABLE_PROPORTION: number; 8 | CLAIM_END_DATE: string; 9 | VEST_DURATION: number; 10 | MERKLE_ROOT: string; 11 | TIMELOCK_DELAY: number; 12 | EXPORT_FILENAME: string; 13 | }; 14 | 15 | type TokenSaleConfig = { 16 | TOKEN_SALE_START: number; 17 | TOKEN_SALE_DURATION: number; 18 | TOKEN_SALE_USDC: string; 19 | TOKEN_SALE_ARENA_PRICE: BN; 20 | TOKEN_SALE_RECIPIENT: string; 21 | TOKEN_SALE_WHITELIST: typeof TOKEN_SALE_WHITELIST; 22 | RECIPIENT_AMOUNT: BN; 23 | TOKEN_SALE_SUPPLY: BN; 24 | }; 25 | 26 | const TOKEN_SALE_WHITELIST = [ 27 | {buyer: '0x1aa1F9f80f4c5dCe34d0f4faB4F66AAF562330bd', arenaAmount: BN.from(33_333_333).mul(ONE_18)}, 28 | {buyer: '0x3a5c572aE7a806c661970058450dC90D9eF0f353', arenaAmount: BN.from(13_333_333).mul(ONE_18)}, 29 | {buyer: '0xcfc50541c3dEaf725ce738EF87Ace2Ad778Ba0C5', arenaAmount: BN.from(10_166_666).mul(ONE_18)}, 30 | {buyer: '0xC02ad7b9a9121fc849196E844DC869D2250DF3A6', arenaAmount: BN.from(8_333_333).mul(ONE_18)}, 31 | {buyer: '0xCfCA53C4b6d3f763969c9A9C36DBCAd61F11F36D', arenaAmount: BN.from(6_666_666).mul(ONE_18)}, 32 | {buyer: '0x636EDa86F6EC324347Bd560c1045192586b9DEE8', arenaAmount: BN.from(6_666_666).mul(ONE_18)}, 33 | {buyer: '0xDbBB1bD4cbDA95dd2f1477be139C3D6cb9d2B349', arenaAmount: BN.from(3_333_333).mul(ONE_18)}, 34 | {buyer: '0x4dA94e682326BD14997D1E1c62350654D8e44c5d', arenaAmount: BN.from(2_500_000).mul(ONE_18)}, 35 | {buyer: '0x20392b9607dc8cC49BEa5B7B90E65d6251617538', arenaAmount: BN.from(1_166_666).mul(ONE_18)}, 36 | {buyer: '0x83b23E8e5da74fD3f3E5471865FC778d9c843df0', arenaAmount: BN.from(833_333).mul(ONE_18)}, 37 | {buyer: '0x7fCAf93cc92d51c490FFF701fb2C6197497a80db', arenaAmount: BN.from(833_333).mul(ONE_18)}, 38 | ]; 39 | 40 | export const allConfigs: {[key: number]: Config} = { 41 | // rinkeby 42 | 4: { 43 | FREE_SUPPLY: BN.from(900).mul(1_000_000).mul(constants.WeiPerEther), // 900M 44 | AIRDROP_SUPPLY: BN.from(100).mul(1_000_000).mul(constants.WeiPerEther), // 100M 45 | CLAIMABLE_PROPORTION: 2000, // 20% 46 | CLAIM_END_DATE: '2022-12-25', 47 | VEST_DURATION: 4 * ONE_DAY, 48 | MERKLE_ROOT: '0xd97c9a423833d78e0562b8ed2d14752b54e7ef9b52314cafb197e3a339299901', 49 | TIMELOCK_DELAY: 1800, // 30 mins 50 | EXPORT_FILENAME: 'rinkebyAddresses.json', 51 | }, 52 | // polygon mainnet 53 | 137: { 54 | FREE_SUPPLY: BN.from(640_826_767).mul(constants.WeiPerEther), // 1B - mainnet markle tokenTotal 55 | AIRDROP_SUPPLY: BN.from(359_173_233).mul(constants.WeiPerEther), // mainnet merkle tokenTotal 56 | CLAIMABLE_PROPORTION: 2000, // 20% 57 | CLAIM_END_DATE: '2023-1-11', 58 | VEST_DURATION: 4 * ONE_YEAR, 59 | MERKLE_ROOT: '0xb86e0dced055310e26ce11e69d47b6e6064be988564fb002d6ba5a29e7eee713', 60 | TIMELOCK_DELAY: 2 * ONE_DAY, // 2 days (same as ENS) 61 | EXPORT_FILENAME: 'polygonAddresses.json', 62 | }, 63 | }; 64 | 65 | export const tokenSaleConfigs: {[key: number]: TokenSaleConfig} = { 66 | // polygon mainnet 67 | 137: { 68 | TOKEN_SALE_START: 1644451200, // Thursday, February 10, 2022 12:00:00 AM UTC 69 | TOKEN_SALE_DURATION: 10 * ONE_DAY, 70 | TOKEN_SALE_USDC: '0x2791bca1f2de4661ed88a30c99a7a9449aa84174', 71 | TOKEN_SALE_ARENA_PRICE: BN.from(30_000).mul(ONE_18).div(ONE_18), // 0.03 USDC * 1e18 / 1.0 ARENA 72 | TOKEN_SALE_RECIPIENT: '0x7f0049597056E37B4B1f887196E44CAc050D4863', // C4 Polygon multisig 73 | TOKEN_SALE_WHITELIST, 74 | RECIPIENT_AMOUNT: BN.from(1_750_000).mul(BN.from(10).pow(6)), // 1.75M USDC, rest to treasury 75 | TOKEN_SALE_SUPPLY: BN.from(100_000_000).mul(ONE_18), // 100M ARENA tokens 76 | }, 77 | }; 78 | -------------------------------------------------------------------------------- /scripts/deploy/deployGov.ts: -------------------------------------------------------------------------------- 1 | import {expect} from 'chai'; 2 | import fs from 'fs'; 3 | 4 | import { 5 | ArenaToken__factory, 6 | ArenaToken, 7 | RevokableTokenLock__factory, 8 | RevokableTokenLock, 9 | TimelockController__factory, 10 | TimelockController, 11 | ArenaGovernor__factory, 12 | ArenaGovernor, 13 | } from '../../typechain'; 14 | 15 | import {allConfigs} from './config'; 16 | import {HardhatRuntimeEnvironment} from 'hardhat/types'; 17 | 18 | let deployerAddress: string; 19 | let token: ArenaToken; 20 | let revokableTokenLock: RevokableTokenLock; 21 | let timelock: TimelockController; 22 | let governor: ArenaGovernor; 23 | 24 | // see OZ docs: https://docs.openzeppelin.com/contracts/4.x/api/governance#timelock-roles 25 | const ADMIN_ROLE = '0x5f58e3a2316349923ce3780f8d587db2d72378aed66a8261c916544fa6846ca5'; 26 | const PROPOSER_ROLE = '0xb09aa5aeb3702cfd50b6b62bc4532604938f21248a27a1d5ca736082b6819cc1'; 27 | const EXECUTOR_ROLE = '0xd8aa0f3194971a2a116679f7c2090f6939c8d4e01a2a8d7e41d55e5351469e63'; 28 | 29 | export async function deployGov(hre: HardhatRuntimeEnvironment) { 30 | const networkId = hre.network.config.chainId as number; 31 | const [deployer] = await hre.ethers.getSigners(); 32 | deployerAddress = await deployer.getAddress(); 33 | console.log(`Deployer: ${deployerAddress}`); 34 | 35 | let config = allConfigs[networkId]; 36 | 37 | console.log(`deploying token...`); 38 | const TokenFactory = (await hre.ethers.getContractFactory('ArenaToken')) as ArenaToken__factory; 39 | token = await TokenFactory.deploy( 40 | config.FREE_SUPPLY, 41 | config.AIRDROP_SUPPLY, 42 | config.CLAIMABLE_PROPORTION, 43 | new Date(config.CLAIM_END_DATE).getTime() / 1000, 44 | config.VEST_DURATION 45 | ); 46 | await token.deployed(); 47 | console.log(`token address: ${token.address}`); 48 | 49 | console.log(`deploying lock...`); 50 | const RevokableTokenLockFactory = (await hre.ethers.getContractFactory( 51 | 'RevokableTokenLock' 52 | )) as RevokableTokenLock__factory; 53 | revokableTokenLock = await RevokableTokenLockFactory.deploy(token.address, deployerAddress); 54 | await revokableTokenLock.deployed(); 55 | console.log(`revokableLock address: ${revokableTokenLock.address}`); 56 | 57 | // set lock in token 58 | await token.setTokenLock(revokableTokenLock.address); 59 | 60 | await token.setMerkleRoot(config.MERKLE_ROOT); 61 | 62 | console.log(`deploying timelock...`); 63 | const TimelockControllerFactory = (await hre.ethers.getContractFactory( 64 | 'TimelockController' 65 | )) as TimelockController__factory; 66 | timelock = await TimelockControllerFactory.deploy( 67 | config.TIMELOCK_DELAY, // minDelay of 1 day 68 | [], // proposer and executor roles to be set after governor deployment 69 | [] 70 | ); 71 | await timelock.deployed(); 72 | console.log(`timelock address: ${timelock.address}`); 73 | 74 | console.log(`deploying governor...`); 75 | const ArenaGovernorFactory = (await hre.ethers.getContractFactory('ArenaGovernor')) as ArenaGovernor__factory; 76 | governor = await ArenaGovernorFactory.deploy(token.address, timelock.address); 77 | await governor.deployed(); 78 | console.log(`governor address: ${governor.address}`); 79 | 80 | console.log(`transfer remaining tokens to timelock`); 81 | await token.transfer(timelock.address, config.FREE_SUPPLY); 82 | 83 | // give governor proposer role 84 | // https://docs.openzeppelin.com/contracts/4.x/api/governance#timelock-proposer 85 | await timelock.grantRole(PROPOSER_ROLE, governor.address); 86 | 87 | // set executor role to null address so that ANY address can execute a queued proposal 88 | // https://docs.openzeppelin.com/contracts/4.x/api/governance#timelock-executor 89 | await timelock.grantRole(EXECUTOR_ROLE, hre.ethers.constants.AddressZero); 90 | 91 | // https://docs.openzeppelin.com/contracts/4.x/api/governance#timelock-admin 92 | // Timelock is self-governed, admin role has already been bestowed to itself 93 | // Deployer renounce roles so that all further maintenance operations have to go through the timelock process 94 | await timelock.renounceRole(ADMIN_ROLE, deployerAddress); 95 | 96 | // set revoker role in TokenLock to timelock 97 | await revokableTokenLock.setRevoker(timelock.address); 98 | 99 | // transfer tokenlock admin role to timelock 100 | await revokableTokenLock.transferOwnership(timelock.address); 101 | 102 | // transfer token admin role to timelock 103 | await token.transferOwnership(timelock.address); 104 | 105 | console.log('exporting addresses...'); 106 | let addressesToExport = { 107 | deployer: deployerAddress, 108 | token: token.address, 109 | tokenLock: revokableTokenLock.address, 110 | timelock: timelock.address, 111 | governor: governor.address, 112 | }; 113 | let exportJson = JSON.stringify(addressesToExport, null, 2); 114 | fs.writeFileSync(config.EXPORT_FILENAME, exportJson); 115 | 116 | ///////////////////////////////// 117 | // ACCESS CONTROL VERIFICATION // 118 | ///////////////////////////////// 119 | console.log('verifying access control settings...'); 120 | 121 | // deployer does not hold any role in timelock 122 | expect(await timelock.hasRole(ADMIN_ROLE, deployerAddress)).to.be.false; 123 | expect(await timelock.hasRole(PROPOSER_ROLE, deployerAddress)).to.be.false; 124 | expect(await timelock.hasRole(EXECUTOR_ROLE, deployerAddress)).to.be.false; 125 | 126 | // timelock is admin of itself 127 | expect(await timelock.hasRole(ADMIN_ROLE, timelock.address)).to.be.true; 128 | 129 | // governor is given proposer role 130 | expect(await timelock.hasRole(PROPOSER_ROLE, governor.address)).to.be.true; 131 | 132 | // null address is given executor role so that any address is allowed to execute proposal 133 | // see onlyRoleOrOpenRole modifier of TimelockController 134 | expect(await timelock.hasRole(EXECUTOR_ROLE, hre.ethers.constants.AddressZero)).to.be.true; 135 | 136 | // TokenLock revoker should be timelock 137 | expect(await revokableTokenLock.revoker()).to.be.eq(timelock.address); 138 | 139 | // TokenLock owner should be timelock 140 | expect(await revokableTokenLock.owner()).to.be.eq(timelock.address); 141 | 142 | // Token's owner should be timelock 143 | expect(await token.owner()).to.be.eq(timelock.address); 144 | 145 | // check Token's tokenlock has been set 146 | expect(await token.tokenLock()).to.be.eq(revokableTokenLock.address); 147 | 148 | ///////////////////////// 149 | // CONFIG VERIFICATION // 150 | ///////////////////////// 151 | console.log('verifying config settings...'); 152 | 153 | // check ArenaToken's token balance == AIRDROP_SUPPLY 154 | expect(await token.balanceOf(token.address)).to.be.eq(config.AIRDROP_SUPPLY); 155 | 156 | // check timelock's token balance == FREE_SUPPLY 157 | expect(await token.balanceOf(timelock.address)).to.be.eq(config.FREE_SUPPLY); 158 | 159 | // check timelock's minDelay 160 | expect(await timelock.getMinDelay()).to.be.eq(config.TIMELOCK_DELAY); 161 | 162 | // check merkle root is set 163 | expect(await token.merkleRoot()).to.be.eq(config.MERKLE_ROOT); 164 | 165 | // check claimable proportion 166 | expect(await token.claimableProportion()).to.be.eq(config.CLAIMABLE_PROPORTION); 167 | 168 | // check vest duration 169 | expect(await token.vestDuration()).to.be.eq(config.VEST_DURATION); 170 | 171 | console.log('verification complete!'); 172 | process.exit(0); 173 | } 174 | -------------------------------------------------------------------------------- /scripts/deploy/deployTokenSale.ts: -------------------------------------------------------------------------------- 1 | import {BigNumber as BN, Signer} from 'ethers'; 2 | import {expect} from 'chai'; 3 | import fs from 'fs'; 4 | import path from 'path'; 5 | 6 | import { 7 | ArenaToken__factory, 8 | TimelockController__factory, 9 | ArenaGovernor__factory, 10 | TokenSale__factory, 11 | TokenSale, 12 | TokenLock__factory, 13 | } from '../../typechain'; 14 | 15 | import {allConfigs, tokenSaleConfigs} from './config'; 16 | import {HardhatRuntimeEnvironment} from 'hardhat/types'; 17 | import {verifyContract} from './verify'; 18 | 19 | let proposerAddress: string; 20 | let tokenSale: TokenSale; 21 | 22 | const getContracts = (signer: Signer, config: typeof allConfigs[0]) => { 23 | const deploymentFilePath = path.join(`deployments`, config.EXPORT_FILENAME); 24 | if (!fs.existsSync(deploymentFilePath)) throw new Error(`File '${path.resolve(deploymentFilePath)}' does not exist.`); 25 | 26 | const contents = fs.readFileSync(deploymentFilePath, `utf8`); 27 | let governorAddress; 28 | let arenaAddress; 29 | let timelockAddress; 30 | let tokenLockAddress; 31 | try { 32 | ({ 33 | governor: governorAddress, 34 | token: arenaAddress, 35 | tokenLock: tokenLockAddress, 36 | timelock: timelockAddress, 37 | } = JSON.parse(contents)); 38 | } catch (error) { 39 | throw new Error(`Cannot parse deployment config at '${path.resolve(deploymentFilePath)}'.`); 40 | } 41 | if (!governorAddress) throw new Error(`Deployment file did not include governor address '${deploymentFilePath}'.`); 42 | if (!arenaAddress) throw new Error(`Deployment file did not include arena token address '${deploymentFilePath}'.`); 43 | if (!timelockAddress) throw new Error(`Deployment file did not include timelock address '${deploymentFilePath}'.`); 44 | if (!tokenLockAddress) throw new Error(`Deployment file did not include tokenLock address '${deploymentFilePath}'.`); 45 | 46 | return { 47 | contents: contents, 48 | deploymentFilePath: deploymentFilePath, 49 | governor: ArenaGovernor__factory.connect(governorAddress, signer), 50 | arenaToken: ArenaToken__factory.connect(arenaAddress, signer), 51 | timelock: TimelockController__factory.connect(timelockAddress, signer), 52 | tokenLock: TokenLock__factory.connect(tokenLockAddress, signer), 53 | }; 54 | }; 55 | 56 | export async function deployTokenSale(hre: HardhatRuntimeEnvironment) { 57 | const networkId = hre.network.config.chainId as number; 58 | const [proposer] = await hre.ethers.getSigners(); 59 | proposerAddress = await proposer.getAddress(); 60 | console.log(`Proposer: ${proposerAddress}`); 61 | 62 | let config = tokenSaleConfigs[networkId]; 63 | let deployConfig = allConfigs[networkId]; 64 | if (!config) throw new Error(`No config exists for network ${hre.network.name} (${networkId})`); 65 | const {contents, deploymentFilePath, governor, arenaToken, timelock, tokenLock} = getContracts( 66 | proposer, 67 | deployConfig 68 | ); 69 | 70 | console.log(`deploying tokensale...`); 71 | const TokenSaleFactory = (await hre.ethers.getContractFactory('TokenSale')) as TokenSale__factory; 72 | 73 | tokenSale = await TokenSaleFactory.deploy( 74 | config.TOKEN_SALE_USDC, 75 | arenaToken.address, 76 | config.TOKEN_SALE_START, 77 | config.TOKEN_SALE_DURATION, 78 | config.TOKEN_SALE_ARENA_PRICE, 79 | config.TOKEN_SALE_RECIPIENT, 80 | tokenLock.address, 81 | allConfigs[networkId].VEST_DURATION, 82 | config.RECIPIENT_AMOUNT 83 | ); 84 | await tokenSale.deployed(); 85 | console.log(`tokenSale address: ${tokenSale.address}`); 86 | 87 | // set up token sale whitelist 88 | await tokenSale.changeWhiteList( 89 | config.TOKEN_SALE_WHITELIST.map(({buyer}) => buyer), 90 | config.TOKEN_SALE_WHITELIST.map(({arenaAmount}) => arenaAmount) 91 | ); 92 | // transfer token sale admin role to timelock 93 | await tokenSale.transferOwnership(timelock.address); 94 | 95 | // 1st action: set token sale in TokenLock 96 | // 2nd action: request TOKEN_SALE_SUPPLY tokens from timelock to tokenSale 97 | let targets: string[] = [tokenLock.address, arenaToken.address]; 98 | let values: string[] = ['0', '0']; 99 | let calldatas: string[] = [ 100 | tokenLock.interface.encodeFunctionData('setTokenSale', [tokenSale.address]), 101 | arenaToken.interface.encodeFunctionData('transfer', [tokenSale.address, config.TOKEN_SALE_SUPPLY]), 102 | ]; 103 | 104 | const tx = await governor['propose(address[],uint256[],bytes[],string)']( 105 | targets, 106 | values, 107 | calldatas, 108 | `# C4IP-6: Transfer ARENA tokens for token sale\nThis proposal takes action on the token sale approved by [C4IP-1]() and the hiring of Code4 Corporation approved by [C4IP-3]() both of which are discussed in detail in [this forum post]()
\n\n- 100,000,000 $ARENA tokens transferred to the [token sale contract]()\n\n- Tokens are sold at price of 1 ARENA = .03 USDC\n\n- Token sale details to be administered by Code4 Corporation\n\n- $1.75M of the initial sale will immediately be used to fund Code4 Corporation operations\n\n- Remaining $1.25M proceeds will be transferred to the Code4rena treasury\n\n\n\n\n` 109 | ); 110 | console.log(`proposal submitted: ${tx.hash}`); 111 | console.log(`waiting for block inclusion ...`); 112 | await tx.wait(1); 113 | 114 | console.log('exporting addresses...'); 115 | let addressesToExport = JSON.parse(contents); 116 | addressesToExport.tokenSale = tokenSale.address; 117 | let exportJson = JSON.stringify(addressesToExport, null, 2); 118 | fs.writeFileSync(deploymentFilePath, exportJson); 119 | 120 | console.log(`Verifying tokenSale contract...`); 121 | await verifyContract(hre, tokenSale.address, [ 122 | config.TOKEN_SALE_USDC, 123 | arenaToken.address, 124 | config.TOKEN_SALE_START, 125 | config.TOKEN_SALE_DURATION, 126 | config.TOKEN_SALE_ARENA_PRICE, 127 | config.TOKEN_SALE_RECIPIENT, 128 | tokenLock.address, 129 | allConfigs[networkId].VEST_DURATION, 130 | config.RECIPIENT_AMOUNT, 131 | ]); 132 | 133 | ///////////////////////////////// 134 | // ACCESS CONTROL VERIFICATION // 135 | ///////////////////////////////// 136 | console.log('verifying access control settings...'); 137 | // tokenSale's owner should be timelock 138 | expect(await tokenSale.owner()).to.be.eq(timelock.address); 139 | 140 | console.log('verification complete!'); 141 | process.exit(0); 142 | } 143 | -------------------------------------------------------------------------------- /scripts/deploy/index.ts: -------------------------------------------------------------------------------- 1 | import {task} from 'hardhat/config'; 2 | 3 | task('deployGov', 'deploy governance and token contracts').setAction(async (taskArgs, hre) => { 4 | // only load this file when task is run because it depends on typechain built artifacts 5 | // which will create a circular dependency when required by hardhat.config.ts for first compilation 6 | const {deployGov} = await import('./deployGov'); 7 | await deployGov(hre); 8 | }); 9 | 10 | task('deployTokenSale', 'deploy token sale and make proposal for relevant actions').setAction(async (taskArgs, hre) => { 11 | // only load this file when task is run because it depends on typechain built artifacts 12 | // which will create a circular dependency when required by hardhat.config.ts for first compilation 13 | const {deployTokenSale} = await import('./deployTokenSale'); 14 | await deployTokenSale(hre); 15 | }); 16 | -------------------------------------------------------------------------------- /scripts/deploy/verify.ts: -------------------------------------------------------------------------------- 1 | import {HardhatRuntimeEnvironment} from 'hardhat/types'; 2 | 3 | export async function verifyContract(hre: HardhatRuntimeEnvironment, contractAddress: string, ctorArgs: any[]) { 4 | await hre.run('verify:verify', { 5 | address: contractAddress, 6 | constructorArguments: ctorArgs, 7 | }); 8 | } 9 | -------------------------------------------------------------------------------- /scripts/proposals/example.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "token": "0xc1D2300f0065FC076c36F6D414a31A2949ca3027", 4 | "transfers": [ 5 | { 6 | "to": "0x0f4Aeb1847B7F1a735f4a5Af7E8C299b793c1a9A", 7 | "amount": "1000000000000000000" 8 | }, 9 | { 10 | "to": "0x3Ab0029e1C4515134464b267557cB80A39902699", 11 | "amount": "2000000000000000000" 12 | }, 13 | { 14 | "to": "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", 15 | "amount": "4000000000000000000" 16 | }, 17 | { 18 | "to": "0x0000000000000000000000000000000000000000", 19 | "amount": "4000000000000000000" 20 | } 21 | ] 22 | } 23 | ] 24 | -------------------------------------------------------------------------------- /scripts/proposals/index.ts: -------------------------------------------------------------------------------- 1 | import {task} from 'hardhat/config'; 2 | 3 | task('propose', 'propose transfer') 4 | .addOptionalParam('json', 'The path to batch transfer JSON file', `transfers.json`) 5 | .setAction(async ({json}, hre) => { 6 | // only load this file when task is run because it depends on typechain built artifacts 7 | // which will create a circular dependency when required by hardhat.config.ts for first compilation 8 | const {transferProposal} = await import('./transfer'); 9 | await transferProposal(json, hre); 10 | }); 11 | 12 | task('simulateExistingProposal', 'simulates an existing proposal (by cloning it)') 13 | .addParam('id', 'Proposal ID') 14 | .setAction(async ({id}, hre) => { 15 | const {simulateExistingProposal} = await import('./simulateExistingProposal'); 16 | await simulateExistingProposal(id, hre); 17 | }); 18 | -------------------------------------------------------------------------------- /scripts/proposals/simulateExistingProposal.ts: -------------------------------------------------------------------------------- 1 | import {HardhatRuntimeEnvironment} from 'hardhat/types'; 2 | import {getPolygonContracts, getForkParams} from '../../shared/Forking'; 3 | import {createAndExecuteProposal} from '../../shared/Governance'; 4 | 5 | export async function simulateExistingProposal(proposalId: string, hre: HardhatRuntimeEnvironment) { 6 | const [user] = await hre.ethers.getSigners(); 7 | const deployment = getPolygonContracts(user); 8 | 9 | // attempt mainnet forking 10 | await hre.network.provider.request({ 11 | method: 'hardhat_reset', 12 | params: [getForkParams()], 13 | }); 14 | 15 | const proposalActions = await deployment.governor.getActions(proposalId); 16 | let valuesArray = proposalActions[1].map((value) => value.toString()); 17 | console.log(`proposal targets: ${proposalActions.targets}`); 18 | console.log(`proposal values: ${valuesArray}`); 19 | console.log(`proposal calldatas: ${proposalActions.calldatas}`); 20 | console.log(`cloning proposal...`); 21 | await createAndExecuteProposal({ 22 | user, 23 | targets: proposalActions.targets, 24 | values: valuesArray, 25 | calldatas: proposalActions.calldatas, 26 | ...deployment, 27 | }); 28 | } 29 | -------------------------------------------------------------------------------- /scripts/proposals/transfer.ts: -------------------------------------------------------------------------------- 1 | import {ethers, Signer} from 'ethers'; 2 | import fs from 'fs'; 3 | import path from 'path'; 4 | import _ from 'lodash'; 5 | import {ArenaGovernor__factory, ArenaToken__factory} from '../../typechain'; 6 | import {allConfigs} from '../deploy/config'; 7 | import {HardhatRuntimeEnvironment} from 'hardhat/types'; 8 | 9 | let transferInterface = new ethers.utils.Interface([`function transfer(address to, uint256 amount)`]); 10 | const getContracts = (signer: Signer, config: typeof allConfigs[0]) => { 11 | const deploymentFilePath = path.join(`deployments`, config.EXPORT_FILENAME); 12 | if (!fs.existsSync(deploymentFilePath)) throw new Error(`File '${path.resolve(deploymentFilePath)}' does not exist.`); 13 | 14 | const contents = fs.readFileSync(deploymentFilePath, `utf8`); 15 | let governorAddress; 16 | let arenaAddress; 17 | try { 18 | ({governor: governorAddress, token: arenaAddress} = JSON.parse(contents)); 19 | } catch (error) { 20 | throw new Error(`Cannot parse deployment config at '${path.resolve(deploymentFilePath)}'.`); 21 | } 22 | if (!governorAddress) throw new Error(`Deployment file did not include governor address '${deploymentFilePath}'.`); 23 | if (!arenaAddress) throw new Error(`Deployment file did not include arena token address '${deploymentFilePath}'.`); 24 | 25 | return { 26 | governor: ArenaGovernor__factory.connect(governorAddress, signer), 27 | arenaToken: ArenaToken__factory.connect(arenaAddress, signer), 28 | }; 29 | }; 30 | 31 | type BatchTransfer = { 32 | token: string; 33 | transfers: Array<{to: string; amount: string}>; 34 | }; 35 | const getTransfers = (transferPath: string) => { 36 | if (!fs.existsSync(transferPath)) throw new Error(`File '${path.resolve(transferPath)}' does not exist.`); 37 | 38 | const contents = fs.readFileSync(transferPath, `utf8`); 39 | let json; 40 | try { 41 | json = JSON.parse(contents); 42 | } catch (error) { 43 | throw new Error(`Cannot parse transfer JSON file at '${path.resolve(transferPath)}'.`); 44 | } 45 | if (!Array.isArray(json)) throw new Error(`Transfer file must be an array of batch transfers`); 46 | 47 | return json as BatchTransfer[]; 48 | }; 49 | 50 | const toProposalPayload = (batchTransfer: BatchTransfer) => { 51 | return batchTransfer.transfers.map((transfer) => ({ 52 | target: batchTransfer.token, 53 | calldata: transferInterface.encodeFunctionData(`transfer`, [transfer.to, transfer.amount]), 54 | value: `0`, 55 | })); 56 | }; 57 | 58 | export async function transferProposal(json: string, hre: HardhatRuntimeEnvironment) { 59 | const networkId = hre.network.config.chainId as number; 60 | const [proposer] = await hre.ethers.getSigners(); 61 | const proposerAddress = await proposer.getAddress(); 62 | let config = allConfigs[networkId]; 63 | if (!config) throw new Error(`Unknown network ${hre.network.name} (${networkId})`); 64 | 65 | const batchTransfers = _.flattenDeep(getTransfers(json).map(toProposalPayload)); 66 | const targets = batchTransfers.map(({target}) => target); 67 | const values = batchTransfers.map(({value}) => value); 68 | const calldatas = batchTransfers.map(({calldata}) => calldata); 69 | 70 | const {governor, arenaToken} = getContracts(proposer, config); 71 | console.log(`Proposer: ${proposerAddress}`); 72 | console.log(`Governor: ${governor.address}`); 73 | console.log(`Proposal Threshold: ${await governor.proposalThreshold()}`); 74 | console.log(`Proposer Votes: ${await arenaToken.getVotes(proposerAddress)}`); 75 | 76 | console.log(JSON.stringify(targets)); 77 | console.log(JSON.stringify(calldatas)); 78 | 79 | const tx = await governor['propose(address[],uint256[],bytes[],string)']( 80 | targets, 81 | values, 82 | calldatas, 83 | `Distribute tokens for contest #Test` 84 | ); 85 | console.log(`proposal submitted: ${tx.hash}`); 86 | console.log(`waiting for block inclusion ...`); 87 | await tx.wait(1); 88 | // TODO: query the transaction for the ProposalCreated event so we can get the proposalId 89 | 90 | console.log(`transaction included - proposal created!`); 91 | process.exit(0); 92 | } 93 | -------------------------------------------------------------------------------- /scripts/verify.ts: -------------------------------------------------------------------------------- 1 | import {task} from 'hardhat/config'; 2 | import {HardhatRuntimeEnvironment} from 'hardhat/types'; 3 | import fs from 'fs'; 4 | 5 | import {ArenaToken, RevokableTokenLock, TimelockController, ArenaGovernor, TokenSale} from '../typechain'; 6 | 7 | import {allConfigs} from './deploy/config'; 8 | 9 | let token: ArenaToken; 10 | let revokableTokenLock: RevokableTokenLock; 11 | let timelock: TimelockController; 12 | let governor: ArenaGovernor; 13 | let tokenSale: TokenSale; 14 | 15 | task('verifyContracts', 'verify deployed contracts') 16 | .addParam('i', 'JSON file containing exported addresses') 17 | .setAction(async (taskArgs, hre) => { 18 | const networkId = hre.network.config.chainId as number; 19 | const addresses = JSON.parse(fs.readFileSync(taskArgs.i, 'utf8')); 20 | 21 | token = await hre.ethers.getContractAt('ArenaToken', addresses['token']); 22 | revokableTokenLock = await hre.ethers.getContractAt('RevokableTokenLock', addresses['tokenLock']); 23 | timelock = await hre.ethers.getContractAt('TimelockController', addresses['timelock']); 24 | governor = await hre.ethers.getContractAt('ArenaGovernor', addresses['governor']); 25 | // tokenSale = await hre.ethers.getContractAt('TokenSale', addresses['tokenSale']); 26 | 27 | let config = allConfigs[networkId]; 28 | 29 | console.log('verifying addresses on etherscan...'); 30 | await verifyContract(hre, token.address, [ 31 | config.FREE_SUPPLY, 32 | config.AIRDROP_SUPPLY, 33 | config.CLAIMABLE_PROPORTION, 34 | new Date(config.CLAIM_END_DATE).getTime() / 1000, 35 | config.VEST_DURATION, 36 | ]); 37 | await verifyContract(hre, revokableTokenLock.address, [token.address, addresses['deployer']]); 38 | await verifyContract(hre, timelock.address, [config.TIMELOCK_DELAY, [], []]); 39 | await verifyContract(hre, governor.address, [token.address, timelock.address]); 40 | // await verifyContract(hre, tokenSale.address, [ 41 | // config.TOKEN_SALE_USDC, 42 | // token.address, 43 | // config.TOKEN_SALE_START, 44 | // config.TOKEN_SALE_DURATION, 45 | // config.TOKEN_SALE_ARENA_PRICE, 46 | // config.TOKEN_SALE_RECIPIENT, 47 | // revokableTokenLock.address, 48 | // config.VEST_DURATION, 49 | // ]); 50 | process.exit(0); 51 | }); 52 | 53 | async function verifyContract(hre: HardhatRuntimeEnvironment, contractAddress: string, ctorArgs: any[]) { 54 | await hre.run('verify:verify', { 55 | address: contractAddress, 56 | constructorArguments: ctorArgs, 57 | }); 58 | } 59 | -------------------------------------------------------------------------------- /shared/AccountManipulation.ts: -------------------------------------------------------------------------------- 1 | import hre, {ethers} from 'hardhat'; 2 | import {ONE_18} from './Constants'; 3 | 4 | export const impersonateAccountWithFunds = async (address: string) => { 5 | await hre.network.provider.request({ 6 | method: 'hardhat_impersonateAccount', 7 | params: [address], 8 | }); 9 | // these accounts don't start with a balance, give them one 10 | await hre.network.provider.send('hardhat_setBalance', [ 11 | address, 12 | // need to strip leading zeroes here https://github.com/nomiclabs/hardhat/issues/1585 13 | ONE_18.toHexString().replace(/0x0+/, '0x'), 14 | ]); 15 | 16 | return ethers.provider.getSigner(address); 17 | }; 18 | 19 | export const stopImpersonateAccount = async (address: string) => { 20 | await hre.network.provider.request({ 21 | method: 'hardhat_stopImpersonatingAccount', 22 | params: [address], 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /shared/Constants.ts: -------------------------------------------------------------------------------- 1 | import {constants} from 'ethers'; 2 | 3 | export const ZERO_ADDRESS = constants.AddressZero; 4 | export const ONE_18 = constants.WeiPerEther; 5 | export const ZERO = constants.Zero; 6 | export const ONE = constants.One; 7 | export const TWO = constants.Two; 8 | export const MAX_UINT = constants.MaxUint256; 9 | export const ONE_HOUR = 60 * 60; 10 | export const ONE_DAY = 24 * ONE_HOUR; 11 | export const ONE_YEAR = 365 * ONE_DAY; 12 | export const POLYGON_AVERAGE_BLOCK_TIME = '0x2'; 13 | -------------------------------------------------------------------------------- /shared/Forking.ts: -------------------------------------------------------------------------------- 1 | import {Signer} from 'ethers'; 2 | import * as fs from 'fs'; 3 | import * as path from 'path'; 4 | import { 5 | ArenaGovernor, 6 | ArenaGovernor__factory, 7 | ArenaToken, 8 | ArenaToken__factory, 9 | TimelockController, 10 | TimelockController__factory, 11 | TokenLock, 12 | TokenLock__factory, 13 | } from '../typechain'; 14 | 15 | export type DeployedContracts = { 16 | governorV1: ArenaGovernor, 17 | governor: ArenaGovernor; 18 | timeLock: TimelockController; 19 | tokenLock: TokenLock; 20 | arenaToken: ArenaToken; 21 | }; 22 | export const getPolygonContracts = (signer: Signer): DeployedContracts => { 23 | const deploymentFilePath = path.join(`deployments`, `polygonAddresses.json`); 24 | if (!fs.existsSync(deploymentFilePath)) throw new Error(`File '${path.resolve(deploymentFilePath)}' does not exist.`); 25 | 26 | const contents = fs.readFileSync(deploymentFilePath, `utf8`); 27 | let governorV1Address; 28 | let governorAddress; 29 | let arenaAddress; 30 | let timelockAddress; 31 | let tokenLockAddress; 32 | try { 33 | ({ 34 | governorV1: governorV1Address, 35 | governor: governorAddress, 36 | token: arenaAddress, 37 | tokenLock: tokenLockAddress, 38 | timelock: timelockAddress, 39 | } = JSON.parse(contents)); 40 | } catch (error) { 41 | throw new Error(`Cannot parse deployment config at '${path.resolve(deploymentFilePath)}'.`); 42 | } 43 | if (!governorAddress) throw new Error(`Deployment file did not include governor address '${deploymentFilePath}'.`); 44 | if (!arenaAddress) throw new Error(`Deployment file did not include arena token address '${deploymentFilePath}'.`); 45 | if (!timelockAddress) throw new Error(`Deployment file did not include timelock address '${deploymentFilePath}'.`); 46 | if (!tokenLockAddress) throw new Error(`Deployment file did not include tokenLock address '${deploymentFilePath}'.`); 47 | 48 | return { 49 | governorV1: ArenaGovernor__factory.connect(governorV1Address, signer), 50 | governor: ArenaGovernor__factory.connect(governorAddress, signer), 51 | arenaToken: ArenaToken__factory.connect(arenaAddress, signer), 52 | timeLock: TimelockController__factory.connect(timelockAddress, signer), 53 | tokenLock: TokenLock__factory.connect(tokenLockAddress, signer), 54 | }; 55 | }; 56 | 57 | export function getForkParams() { 58 | if (process.env.POLYGON_URL == undefined) { 59 | console.log(`Missing POLYGON_URL in .env`); 60 | process.exit(1); 61 | } 62 | let forkParams: any = { 63 | forking: { 64 | jsonRpcUrl: process.env.POLYGON_URL 65 | } 66 | }; 67 | if (process.env.FORK_BLOCK) forkParams['forking']['blockNumber'] = Number(process.env.FORK_BLOCK); 68 | return forkParams; 69 | } 70 | -------------------------------------------------------------------------------- /shared/Governance.ts: -------------------------------------------------------------------------------- 1 | import {Signer} from 'ethers'; 2 | import {ethers} from 'hardhat'; 3 | import {impersonateAccountWithFunds, stopImpersonateAccount} from './AccountManipulation'; 4 | import {increaseNextBlockTime, setNextBlockNumber} from './TimeManipulation'; 5 | import {DeployedContracts} from './Forking'; 6 | import {getABIFromPolygonscan} from './Polygonscan'; 7 | 8 | export const createAndExecuteProposal = async ({ 9 | governor, 10 | timeLock, 11 | arenaToken, 12 | user, 13 | targets, 14 | values, 15 | calldatas, 16 | }: { 17 | user: Signer; 18 | targets: string[]; 19 | values: string[]; 20 | calldatas: string[]; 21 | } & DeployedContracts) => { 22 | const timeLockSigner = await impersonateAccountWithFunds(timeLock.address); 23 | 24 | // 1. borrow some treasury tokens to user as we need signer with min. proposalThreshold tokens to propose 25 | const quorumAmount = await governor.quorumVotes(); 26 | // careful, this sends ETH to timelock which might break real-world simulation for proposals involving Timelock ETH 27 | console.log('transferring tokens to user for proposal creation...'); 28 | await arenaToken.connect(timeLockSigner).transfer(await user.getAddress(), quorumAmount); 29 | await arenaToken.connect(user).delegate(await user.getAddress()); 30 | console.log('creating proposal...'); 31 | let tx = await governor.connect(user)['propose(address[],uint256[],bytes[],string)'](targets, values, calldatas, ``); 32 | let {events} = await tx.wait(); 33 | // get first event (ProposalCreated), then get first arg of that event (proposalId) 34 | const proposalId: string = events![0].args![0].toString(); 35 | 36 | // 2. advance time past voting delay and vote on proposal 37 | const voteStartBlock = await governor.proposalSnapshot(proposalId); 38 | // simulate to voteStartBlock 39 | await setNextBlockNumber(voteStartBlock.toNumber()); 40 | console.log('casting vote...'); 41 | tx = await governor.connect(user)['castVote'](proposalId, `1`); 42 | 43 | // 3. return borrowed tokens 44 | tx = await arenaToken.connect(user).transfer(timeLock.address, quorumAmount); 45 | 46 | // 4. advance time past voting period and queue proposal calls to Timelock via GovernorTimelockControl.queue 47 | const voteEndBlock = await governor.proposalDeadline(proposalId); 48 | // simulate to voteEndBlock + 1 49 | await setNextBlockNumber(voteEndBlock.toNumber() + 1); 50 | console.log('queueing proposal...'); 51 | tx = await governor.connect(user)['queue(uint256)'](proposalId); 52 | await tx.wait(); 53 | 54 | await stopImpersonateAccount(timeLock.address); 55 | 56 | // 5. advance time past timelock delay and then execute 57 | const timeLockMinDelaySeconds = await timeLock.getMinDelay(); 58 | await increaseNextBlockTime(timeLockMinDelaySeconds.toNumber()); 59 | console.log('executing proposal...'); 60 | tx = await governor.connect(user)['execute(uint256)'](proposalId); 61 | 62 | let result = await tx.wait(1); 63 | 64 | for (let i = 0; i < targets.length; i++) { 65 | let abi = await getABIFromPolygonscan(targets[i]); 66 | // is proxy contract, need to fetch implementation 67 | if (JSON.stringify(abi).includes("implementation")) { 68 | let proxy = new ethers.Contract(targets[i], abi, user); 69 | console.log(await proxy.implementation()) 70 | abi = await getABIFromPolygonscan(await proxy.implementation()); 71 | } 72 | let iface = new ethers.utils.Interface(abi); 73 | let events = result.logs.map((log) => { 74 | try { 75 | return iface.parseLog(log); 76 | } catch (e) { 77 | // no matching event 78 | } 79 | }); 80 | console.log(`### TARGET ${targets[i]} EVENTS ###`); 81 | console.log(events); 82 | console.log(`###################################`); 83 | } 84 | 85 | let timelockEvents = result.logs.map((log) => { 86 | try { 87 | return timeLock.interface.parseLog(log); 88 | } catch (e) { 89 | // no matching event 90 | } 91 | }); 92 | console.log(`### TIMELOCK EVENTS ###`); 93 | console.log(timelockEvents); 94 | 95 | return proposalId; 96 | }; 97 | -------------------------------------------------------------------------------- /shared/Polygonscan.ts: -------------------------------------------------------------------------------- 1 | import fetch from 'node-fetch'; 2 | 3 | export async function getABIFromPolygonscan(address: string) { 4 | if (process.env.POLYGONSCAN_API_KEY == undefined) { 5 | console.log('Require polygonscan key, exiting...'); 6 | process.exit(1); 7 | } 8 | 9 | let abiRequest = await fetch( 10 | `https://api.polygonscan.com/api?module=contract&action=getabi` + 11 | `&address=${address}` + 12 | `&apikey=${process.env.POLYGONSCAN_API_KEY}` 13 | ); 14 | let abi = await abiRequest.json(); 15 | if (abi.status == '0') { 16 | console.log(abi.result); 17 | process.exit(1); 18 | } 19 | return abi.result; 20 | } 21 | -------------------------------------------------------------------------------- /shared/TimeManipulation.ts: -------------------------------------------------------------------------------- 1 | import hre from 'hardhat'; 2 | import {BigNumber as BN} from 'ethers'; 3 | import {POLYGON_AVERAGE_BLOCK_TIME} from './Constants'; 4 | 5 | export async function getHeadBlockNumber(): Promise { 6 | return BN.from(await hre.network.provider.send('eth_blockNumber', [])).toNumber(); 7 | } 8 | 9 | export const setNextBlockTimeStamp = async (timestamp: number) => { 10 | return hre.network.provider.send('evm_setNextBlockTimestamp', [timestamp]); 11 | }; 12 | 13 | export const increaseNextBlockTime = async (seconds: number) => { 14 | return hre.network.provider.send('evm_increaseTime', [seconds]); 15 | }; 16 | 17 | export const setNextBlockNumber = async (blockNumber: number) => { 18 | let currentBlock = await getHeadBlockNumber(); 19 | await hre.network.provider.send("hardhat_mine", ['0x' + (blockNumber - currentBlock).toString(16), POLYGON_AVERAGE_BLOCK_TIME]); 20 | }; 21 | 22 | export const mineBlockAt = async (timestamp: number) => { 23 | await hre.network.provider.send('evm_setNextBlockTimestamp', [timestamp]); 24 | return hre.network.provider.send('evm_mine', []); 25 | }; 26 | 27 | // default 2s per block 28 | export const mineBlocks = async (numBlocks: string, interval: string = POLYGON_AVERAGE_BLOCK_TIME) => { 29 | await hre.network.provider.send("hardhat_mine", [numBlocks, interval]); 30 | } 31 | 32 | export const resetNetwork = async () => { 33 | return hre.network.provider.request({ 34 | method: 'hardhat_reset', 35 | params: [], 36 | }); 37 | }; 38 | -------------------------------------------------------------------------------- /src/balance-tree.ts: -------------------------------------------------------------------------------- 1 | // modified from https://github.com/Uniswap/merkle-distributor/blob/master/src/balance-tree.ts 2 | import MerkleTree from './merkle-tree'; 3 | import {BigNumber as BN, utils} from 'ethers'; 4 | import {keccak256} from 'ethereumjs-util'; 5 | 6 | export default class BalanceTree { 7 | private readonly tree: MerkleTree; 8 | constructor(balances: {account: string; amount: BN}[]) { 9 | this.tree = new MerkleTree( 10 | balances.map(({account, amount}) => { 11 | return BalanceTree.toNode(account, amount); 12 | }) 13 | ); 14 | } 15 | 16 | public static verifyProof(account: string, amount: BN, proof: Buffer[], root: Buffer): boolean { 17 | let pair = BalanceTree.toNode(account, amount); 18 | for (const item of proof) { 19 | pair = MerkleTree.combinedHash(pair, item); 20 | } 21 | 22 | return pair.equals(root); 23 | } 24 | 25 | // keccak256(abi.encodePacked(index, account, amount)) 26 | public static toNode(account: string, amount: BN): Buffer { 27 | return Buffer.from(utils.solidityKeccak256(['address', 'uint256'], [account, amount]).substr(2), 'hex'); 28 | } 29 | 30 | public getHexRoot(): string { 31 | return this.tree.getHexRoot(); 32 | } 33 | 34 | // returns the hex bytes32 values of the proof 35 | public getProof(account: string, amount: BN): string[] { 36 | return this.tree.getHexProof(BalanceTree.toNode(account, amount)); 37 | } 38 | 39 | // returns the same leaf index the contract is using 40 | public getLeafIndex(account: string, amount: BN): number { 41 | let computedHash = BalanceTree.toNode(account, amount); // leaf 42 | let proof = this.tree.getProof(computedHash); 43 | let index = 0; 44 | 45 | for (let i = 0; i < proof.length; i++) { 46 | index *= 2; 47 | let proofElement = proof[i]; 48 | 49 | // computedHash <= proofElement 50 | if (computedHash.compare(proofElement) !== 1) { 51 | computedHash = keccak256(Buffer.concat([computedHash, proofElement])); 52 | } else { 53 | computedHash = keccak256(Buffer.concat([proofElement, computedHash])); 54 | index += 1; 55 | } 56 | } 57 | 58 | if (this.tree.getRoot().compare(computedHash) !== 0) throw new Error('getLeafIndex did not recompute correct root'); 59 | 60 | return index; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/merkle-tree.ts: -------------------------------------------------------------------------------- 1 | // taken from https://github.com/Uniswap/merkle-distributor/blob/master/src/merkle-tree.ts 2 | import {bufferToHex, keccak256} from 'ethereumjs-util'; 3 | 4 | export default class MerkleTree { 5 | private readonly elements: Buffer[]; 6 | private readonly bufferElementPositionIndex: {[hexElement: string]: number}; 7 | private readonly layers: Buffer[][]; 8 | 9 | constructor(elements: Buffer[]) { 10 | this.elements = [...elements]; 11 | // Sort elements 12 | this.elements.sort(Buffer.compare); 13 | // Deduplicate elements 14 | this.elements = MerkleTree.bufDedup(this.elements); 15 | 16 | this.bufferElementPositionIndex = this.elements.reduce<{[hexElement: string]: number}>((memo, el, index) => { 17 | memo[bufferToHex(el)] = index; 18 | return memo; 19 | }, {}); 20 | 21 | // Create layers 22 | this.layers = this.getLayers(this.elements); 23 | } 24 | 25 | getLayers(elements: Buffer[]): Buffer[][] { 26 | if (elements.length === 0) { 27 | throw new Error('empty tree'); 28 | } 29 | 30 | const layers = []; 31 | layers.push(elements); 32 | 33 | // Get next layer until we reach the root 34 | while (layers[layers.length - 1].length > 1) { 35 | layers.push(this.getNextLayer(layers[layers.length - 1])); 36 | } 37 | 38 | return layers; 39 | } 40 | 41 | getNextLayer(elements: Buffer[]): Buffer[] { 42 | return elements.reduce((layer, el, idx, arr) => { 43 | if (idx % 2 === 0) { 44 | // Hash the current element with its pair element 45 | layer.push(MerkleTree.combinedHash(el, arr[idx + 1])); 46 | } 47 | 48 | return layer; 49 | }, []); 50 | } 51 | 52 | static combinedHash(first: Buffer, second: Buffer): Buffer { 53 | if (!first) { 54 | return second; 55 | } 56 | if (!second) { 57 | return first; 58 | } 59 | 60 | return keccak256(MerkleTree.sortAndConcat(first, second)); 61 | } 62 | 63 | getRoot(): Buffer { 64 | return this.layers[this.layers.length - 1][0]; 65 | } 66 | 67 | getHexRoot(): string { 68 | return bufferToHex(this.getRoot()); 69 | } 70 | 71 | getProof(el: Buffer) { 72 | let idx = this.bufferElementPositionIndex[bufferToHex(el)]; 73 | 74 | if (typeof idx !== 'number') { 75 | throw new Error('Element does not exist in Merkle tree'); 76 | } 77 | 78 | return this.layers.reduce((proof, layer) => { 79 | const pairElement = MerkleTree.getPairElement(idx, layer); 80 | 81 | if (pairElement) { 82 | proof.push(pairElement); 83 | } 84 | 85 | idx = Math.floor(idx / 2); 86 | 87 | return proof; 88 | }, []); 89 | } 90 | 91 | getHexProof(el: Buffer): string[] { 92 | const proof = this.getProof(el); 93 | 94 | return MerkleTree.bufArrToHexArr(proof); 95 | } 96 | 97 | private static getPairElement(idx: number, layer: Buffer[]): Buffer | null { 98 | const pairIdx = idx % 2 === 0 ? idx + 1 : idx - 1; 99 | 100 | if (pairIdx < layer.length) { 101 | return layer[pairIdx]; 102 | } else { 103 | return null; 104 | } 105 | } 106 | 107 | private static bufDedup(elements: Buffer[]): Buffer[] { 108 | return elements.filter((el, idx) => { 109 | return idx === 0 || !elements[idx - 1].equals(el); 110 | }); 111 | } 112 | 113 | private static bufArrToHexArr(arr: Buffer[]): string[] { 114 | if (arr.some((el) => !Buffer.isBuffer(el))) { 115 | throw new Error('Array is not an array of buffers'); 116 | } 117 | 118 | return arr.map((el) => '0x' + el.toString('hex')); 119 | } 120 | 121 | private static sortAndConcat(...args: Buffer[]): Buffer { 122 | return Buffer.concat([...args].sort(Buffer.compare)); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/parse-balance-map.ts: -------------------------------------------------------------------------------- 1 | // modified from https://github.com/Uniswap/merkle-distributor/blob/master/src/parse-balance-map.ts 2 | import BalanceTree from './balance-tree'; 3 | import {BigNumber as BN, utils} from 'ethers'; 4 | const {isAddress, getAddress} = utils; 5 | 6 | export interface MerkleDistributorInfo { 7 | merkleRoot: string; 8 | tokenTotal: string; 9 | claims: { 10 | [account: string]: { 11 | index: number; 12 | amount: string; 13 | proof: string[]; 14 | }; 15 | }; 16 | } 17 | 18 | type OldFormat = {[account: string]: string}; 19 | type Format = {address: string; amount: string}; 20 | export function parseBalanceMap(balances: OldFormat): MerkleDistributorInfo { 21 | const formatBalances: Format[] = Object.keys(balances).map((account) => ({ 22 | address: account, 23 | amount: balances[account], 24 | })); 25 | 26 | const dataByAddress = formatBalances.reduce<{ 27 | [address: string]: {amount: BN}; 28 | }>((memo, {address: account, amount}) => { 29 | if (!isAddress(account)) { 30 | throw new Error(`Found invalid address: ${account}`); 31 | } 32 | const parsed = getAddress(account); 33 | if (memo[parsed]) throw new Error(`Duplicate address: ${parsed}`); 34 | // make sure all input amounts are strings, otherwise we already have precision errors when parsing the file and converting to Number 35 | if (typeof amount !== `string`) throw new Error(`Amount in input not a string: ${amount}`); 36 | const parsedNum = BN.from(amount); 37 | if (parsedNum.lte(0)) throw new Error(`Invalid amount for account: ${account}`); 38 | 39 | memo[parsed] = {amount: parsedNum}; 40 | return memo; 41 | }, {}); 42 | 43 | const sortedAddresses = Object.keys(dataByAddress).sort(); 44 | 45 | // construct a tree 46 | const tree = new BalanceTree( 47 | sortedAddresses.map((address) => ({account: address, amount: dataByAddress[address].amount})) 48 | ); 49 | 50 | // generate claims 51 | const claims = sortedAddresses.reduce<{ 52 | [address: string]: {index: number; amount: string; proof: string[]}; 53 | }>((memo, address) => { 54 | const {amount} = dataByAddress[address]; 55 | memo[address] = { 56 | index: tree.getLeafIndex(address, amount), 57 | amount: amount.toString(), 58 | proof: tree.getProof(address, amount), 59 | }; 60 | return memo; 61 | }, {}); 62 | 63 | const tokenTotal: BN = sortedAddresses.reduce((memo, key) => memo.add(dataByAddress[key].amount), BN.from(0)); 64 | 65 | return { 66 | merkleRoot: tree.getHexRoot(), 67 | tokenTotal: tokenTotal.toString(), 68 | claims, 69 | }; 70 | } 71 | -------------------------------------------------------------------------------- /test/ArenaToken.spec.ts: -------------------------------------------------------------------------------- 1 | import chai, {expect} from 'chai'; 2 | import {BigNumber as BN} from 'ethers'; 3 | import * as fs from 'fs'; 4 | import {ethers, waffle} from 'hardhat'; 5 | import * as path from 'path'; 6 | import {ArenaToken, RevokableTokenLock} from '../typechain'; 7 | import {impersonateAccountWithFunds, stopImpersonateAccount} from '../shared/AccountManipulation'; 8 | import {ONE_18, ONE_DAY, ONE_YEAR} from '../shared/Constants'; 9 | import {resetNetwork, setNextBlockTimeStamp} from '../shared/TimeManipulation'; 10 | import {MerkleDistributorInfo} from '../src/parse-balance-map'; 11 | 12 | const {solidity, loadFixture} = waffle; 13 | chai.use(solidity); 14 | 15 | let tokenLock: RevokableTokenLock; 16 | let token: ArenaToken; 17 | const CLAIMABLE_PROPORTION = 2000; 18 | const VEST_DURATION = 4 * ONE_YEAR; 19 | const NOW = Math.floor(Date.now() / 1000); 20 | const CLAIM_END_TIME = NOW + ONE_YEAR; 21 | 22 | const airdropJson = JSON.parse( 23 | fs.readFileSync(path.resolve(__dirname, `./fixtures/testAirdrop.json`), `utf8`) 24 | ) as MerkleDistributorInfo; 25 | const {claims} = airdropJson; 26 | const claimers = Object.keys(claims); 27 | const AIRDROP_SUPPLY = BN.from(airdropJson.tokenTotal); 28 | const FREE_SUPPLY = BN.from(1000_000_000).mul(ONE_18).sub(AIRDROP_SUPPLY); 29 | 30 | describe('TokenSale', async () => { 31 | const [user, admin, other] = waffle.provider.getWallets(); 32 | 33 | async function fixture() { 34 | let ArenaTokenFactory = await ethers.getContractFactory('ArenaToken'); 35 | let token = (await ArenaTokenFactory.connect(admin).deploy( 36 | FREE_SUPPLY, 37 | AIRDROP_SUPPLY, 38 | CLAIMABLE_PROPORTION, 39 | CLAIM_END_TIME, 40 | VEST_DURATION 41 | )) as ArenaToken; 42 | 43 | let TokenLockFactory = await ethers.getContractFactory('RevokableTokenLock'); 44 | let tokenLock = (await TokenLockFactory.connect(admin).deploy(token.address, admin.address)) as RevokableTokenLock; 45 | 46 | await token.setTokenLock(tokenLock.address); 47 | return {token, tokenLock}; 48 | } 49 | 50 | beforeEach('deploy fixture', async () => { 51 | ({token, tokenLock} = await loadFixture(fixture)); 52 | }); 53 | 54 | describe('#setTokenLock', async () => { 55 | it('should revert if caller is not owner', async () => { 56 | await expect(token.connect(user).setTokenLock(tokenLock.address)).to.be.revertedWith( 57 | 'Ownable: caller is not the owner' 58 | ); 59 | }); 60 | 61 | it('should allow owner to set', async () => { 62 | await token.connect(admin).setTokenLock(tokenLock.address); 63 | expect(await token.tokenLock()).to.eq(tokenLock.address); 64 | }); 65 | }); 66 | 67 | describe('#setMerkleRoot', async () => { 68 | it('should revert if caller is not owner', async () => { 69 | await expect(token.connect(user).setTokenLock(tokenLock.address)).to.be.revertedWith( 70 | 'Ownable: caller is not the owner' 71 | ); 72 | }); 73 | 74 | it('should allow owner to set exactly once', async () => { 75 | await token.connect(admin).setMerkleRoot(airdropJson.merkleRoot); 76 | expect(await token.merkleRoot()).to.eq(airdropJson.merkleRoot); 77 | await expect( 78 | token.connect(admin).setMerkleRoot(`0xdeadbeef42c0ffeedeadbeef42c0ffeedeadbeef42c0ffeedeadbeef42c0ffee`) 79 | ).to.be.revertedWith('ArenaToken: Merkle root already set'); 80 | }); 81 | }); 82 | 83 | describe('#mint', async () => { 84 | it('should revert if caller is not owner', async () => { 85 | await expect(token.connect(user).mint(user.address, ONE_18)).to.be.revertedWith( 86 | 'Ownable: caller is not the owner' 87 | ); 88 | }); 89 | 90 | it('should not allow minting until the first mint window', async () => { 91 | await expect(token.connect(admin).mint(user.address, ONE_18)).to.be.revertedWith('ArenaToken: Cannot mint yet'); 92 | }); 93 | 94 | it('should allow owner to mint new tokens', async () => { 95 | await setNextBlockTimeStamp((await token.nextMint()).toNumber()); 96 | await token.connect(admin).mint(user.address, ONE_18); 97 | expect(await token.balanceOf(user.address)).to.eq(ONE_18); 98 | }); 99 | 100 | it('should not allow minting more than the prescribed amount', async () => { 101 | await setNextBlockTimeStamp((await token.nextMint()).toNumber()); 102 | await expect(token.mint(user.address, (await token.totalSupply()).div(50).add(1))).to.be.revertedWith( 103 | 'ArenaToken: Mint exceeds maximum amount' 104 | ); 105 | }); 106 | }); 107 | 108 | describe('#sweep', async () => { 109 | it('should revert if trying to sweep before claim period ended', async () => { 110 | await expect(token.connect(admin).sweep(other.address)).to.be.revertedWith( 111 | 'ArenaToken: Claim period not yet ended' 112 | ); 113 | }); 114 | 115 | it('should revert if caller is not owner', async () => { 116 | await setNextBlockTimeStamp(CLAIM_END_TIME - 1); 117 | await expect(token.connect(user).sweep(other.address)).to.be.revertedWith('Ownable: caller is not the owner'); 118 | }); 119 | 120 | it('should allow owner to sweep after claim period ended', async () => { 121 | await setNextBlockTimeStamp(CLAIM_END_TIME); 122 | await expect(() => token.connect(admin).sweep(other.address)).to.changeTokenBalances( 123 | token, 124 | [token, other], 125 | [AIRDROP_SUPPLY.mul(-1), AIRDROP_SUPPLY] 126 | ); 127 | }); 128 | }); 129 | 130 | describe('#claimTokens', async () => { 131 | describe('when no merkle root is set', async () => { 132 | it('should revert when trying to claim', async () => { 133 | const claim = claims[claimers[0]]; 134 | const claimerSigner = await impersonateAccountWithFunds(claimers[0]); 135 | await expect(token.connect(claimerSigner).claimTokens(claim.amount, claim.proof)).to.be.revertedWith( 136 | 'ArenaToken: Valid proof required.' 137 | ); 138 | }); 139 | }); 140 | 141 | describe('when merkle root is set', async () => { 142 | beforeEach('set merkle root', async () => { 143 | await token.setMerkleRoot(airdropJson.merkleRoot); 144 | }); 145 | 146 | it('should revert when claimer not in merkle tree', async () => { 147 | const claim = claims[claimers[0]]; 148 | await expect(token.connect(user).claimTokens(claim.amount, claim.proof)).to.be.revertedWith( 149 | 'ArenaToken: Valid proof required.' 150 | ); 151 | }); 152 | 153 | it('should let everyone in the merkle tree claim exactly once', async () => { 154 | for (let i = 0; i < claimers.length; i++) { 155 | const claimer = claimers[i]; 156 | const claim = claims[claimer]; 157 | const claimerSigner = await impersonateAccountWithFunds(claimer); 158 | const claimAmount = BN.from(claim.amount); 159 | const distributedAmount = claimAmount.mul(CLAIMABLE_PROPORTION).div(1e4); 160 | 161 | const nextBlock = NOW + ONE_DAY + i * 13; 162 | await setNextBlockTimeStamp(nextBlock); 163 | await expect(() => 164 | token.connect(claimerSigner).claimTokens(claim.amount, claim.proof) 165 | ).to.changeTokenBalances( 166 | token, 167 | [claimerSigner, tokenLock, token], 168 | [distributedAmount, claimAmount.sub(distributedAmount), claimAmount.mul(-1)] 169 | ); 170 | 171 | const vesting = await tokenLock.vesting(claimer); 172 | expect(vesting.unlockBegin).to.eq(nextBlock); 173 | expect(vesting.unlockCliff).to.eq(nextBlock); 174 | expect(vesting.unlockEnd).to.eq(nextBlock + VEST_DURATION); 175 | expect(vesting.lockedAmounts).to.eq(claimAmount.sub(distributedAmount)); 176 | 177 | await expect(token.connect(claimerSigner).claimTokens(claim.amount, claim.proof)).to.be.revertedWith( 178 | 'ArenaToken: Tokens already claimed.' 179 | ); 180 | expect(await token.isClaimed(claim.index)).to.be.true; 181 | await stopImpersonateAccount(claimer); 182 | } 183 | 184 | // all airdrops distributed => zero balance left 185 | expect(await token.balanceOf(token.address)).to.be.eq(`0`); 186 | }); 187 | }); 188 | }); 189 | 190 | after('reset network', async () => { 191 | await resetNetwork(); 192 | }); 193 | }); 194 | -------------------------------------------------------------------------------- /test/GovernanceSim.spec.ts: -------------------------------------------------------------------------------- 1 | import chai, {expect} from 'chai'; 2 | import {waffle} from 'hardhat'; 3 | import {ZERO} from '../shared/Constants'; 4 | import {getPolygonContracts} from '../shared/Forking'; 5 | import {createAndExecuteProposal} from '../shared/Governance'; 6 | 7 | const {solidity} = waffle; 8 | chai.use(solidity); 9 | 10 | // can simulate poylgon mainnet governance proposals here, enable fork object in hardhat.config.ts 11 | describe.skip('Governance - Polygon mainnet proposal simulations', async () => { 12 | const [user] = waffle.provider.getWallets(); 13 | let deployment = getPolygonContracts(user); 14 | const {governorV1, governor, arenaToken, timeLock} = deployment; 15 | 16 | it('should allow governance to move tokens in timeLock contract', async () => { 17 | const treasuryAmount = await arenaToken.balanceOf(timeLock.address); 18 | expect(treasuryAmount).to.be.gt(ZERO, `Treasury currently does not have any ARENA tokens`); 19 | 20 | let targets: string[] = [arenaToken.address]; 21 | let values: string[] = [`0`]; 22 | let calldatas: string[] = [arenaToken.interface.encodeFunctionData('transfer', [user.address, treasuryAmount])]; 23 | await createAndExecuteProposal({targets, values, calldatas, user, ...deployment}); 24 | 25 | expect(await arenaToken.balanceOf(timeLock.address)).to.eq(ZERO); 26 | }); 27 | 28 | it('should migrate to new governor', async () => { 29 | // set to current existing governor for proposal creation 30 | deployment.governor = governorV1; 31 | const PROPOSER_ROLE = '0xb09aa5aeb3702cfd50b6b62bc4532604938f21248a27a1d5ca736082b6819cc1'; 32 | let targets: string[] = [timeLock.address, timeLock.address]; 33 | let values: string[] = [`0`, `0`]; 34 | let calldatas: string[] = [ 35 | timeLock.interface.encodeFunctionData('grantRole', [PROPOSER_ROLE, governor.address]), 36 | timeLock.interface.encodeFunctionData('revokeRole', [PROPOSER_ROLE, governorV1.address]), 37 | ]; 38 | await createAndExecuteProposal({targets, values, calldatas, user, ...deployment}); 39 | 40 | const treasuryAmount = await arenaToken.balanceOf(timeLock.address); 41 | targets = [arenaToken.address]; 42 | values = [`0`]; 43 | calldatas = [arenaToken.interface.encodeFunctionData('transfer', [user.address, treasuryAmount])]; 44 | 45 | // attempt to move funds through old governor, will fail 46 | try { 47 | await createAndExecuteProposal({targets, values, calldatas, user, ...deployment}); 48 | } catch (e) { 49 | console.log(e); 50 | } 51 | 52 | // attempt to move funds through new governor, should be successful 53 | deployment.governor = governor; 54 | await createAndExecuteProposal({targets, values, calldatas, user, ...deployment}); 55 | expect(await arenaToken.balanceOf(timeLock.address)).to.eq(ZERO); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /test/RevokableTokenLock.spec.ts: -------------------------------------------------------------------------------- 1 | import {expect} from 'chai'; 2 | import {BigNumber} from 'ethers'; 3 | import {ethers, waffle} from 'hardhat'; 4 | import {TestERC20, RevokableTokenLock} from '../typechain'; 5 | import {ZERO_ADDRESS, ONE_HOUR} from '../shared/Constants'; 6 | import {setNextBlockTimeStamp, mineBlockAt} from '../shared/TimeManipulation'; 7 | 8 | const {loadFixture} = waffle; 9 | 10 | let token: TestERC20; 11 | let revokableTokenLock: RevokableTokenLock; 12 | 13 | describe('RevokableTokenLock', async () => { 14 | const [owner, revoker, recipient, other] = waffle.provider.getWallets(); 15 | const amount = ethers.utils.parseEther('20'); 16 | 17 | async function fixture() { 18 | const TokenFactory = await ethers.getContractFactory('TestERC20'); 19 | const token = await TokenFactory.deploy('TEST', 'TEST'); 20 | const RevokableTokenLockFactory = await ethers.getContractFactory('RevokableTokenLock'); 21 | const revokableTokenLock = (await RevokableTokenLockFactory.deploy( 22 | token.address, 23 | revoker.address 24 | )) as RevokableTokenLock; 25 | 26 | const dt = Math.floor(new Date().getTime() / 1000); 27 | const begin = dt + ONE_HOUR; 28 | const cliff = dt + 5 * ONE_HOUR; 29 | const end = dt + 20 * ONE_HOUR; 30 | await revokableTokenLock.setupVesting(recipient.address, begin, cliff, end); 31 | await token.approve(revokableTokenLock.address, amount); 32 | await revokableTokenLock.lock(recipient.address, amount); 33 | 34 | return {token, revokableTokenLock}; 35 | } 36 | beforeEach('deploy fixture', async () => { 37 | ({token, revokableTokenLock} = await loadFixture(fixture)); 38 | }); 39 | describe('#setRevoker', async () => { 40 | it('should revert if caller is not owner', async () => { 41 | await expect(revokableTokenLock.connect(recipient).setRevoker(other.address)).to.be.revertedWith( 42 | 'Ownable: caller is not the owner' 43 | ); 44 | }); 45 | it('should revert if _revoker is the null address', async () => { 46 | await expect(revokableTokenLock.setRevoker(ZERO_ADDRESS)).to.be.revertedWith('RevokableTokenLock: null address'); 47 | }); 48 | it('should not revert if caller is owner and _revoker is not the null address', async () => { 49 | await expect(revokableTokenLock.setRevoker(other.address)).to.not.be.reverted; 50 | }); 51 | it('should update value of revoker in contract to other address', async () => { 52 | await revokableTokenLock.setRevoker(other.address); 53 | const revoker = await revokableTokenLock.revoker(); 54 | expect(revoker).to.equal(other.address); 55 | }); 56 | }); 57 | describe('#revoke', async () => { 58 | let transferred: BigNumber; 59 | let remaining: BigNumber; 60 | let unlockBegin: BigNumber; 61 | let unlockCliff: BigNumber; 62 | let unlockEnd: BigNumber; 63 | before('Set up data', async () => { 64 | ({unlockBegin, unlockCliff, unlockEnd} = await revokableTokenLock.vesting(recipient.address)); 65 | transferred = amount.mul(unlockCliff.sub(unlockBegin)).div(unlockEnd.sub(unlockBegin)); 66 | remaining = amount.sub(transferred); 67 | }); 68 | it('should revert if caller is not owner or revoker', async () => { 69 | await expect(revokableTokenLock.connect(other).revoke(recipient.address)).to.be.revertedWith( 70 | 'RevokableTokenLock: onlyAuthorizedActors' 71 | ); 72 | }); 73 | it('should not revert if caller is revoker', async () => { 74 | await expect(revokableTokenLock.connect(revoker).revoke(recipient.address)).to.not.be.reverted; 75 | }); 76 | it('should not revert if caller is owner', async () => { 77 | await expect(revokableTokenLock.revoke(recipient.address)).to.not.be.reverted; 78 | }); 79 | it('should claim none and revoke all tokens to owner if revoke is called before unlockCliff', async () => { 80 | await expect(() => revokableTokenLock.revoke(recipient.address)).to.changeTokenBalances( 81 | token, 82 | [recipient, owner], 83 | [0, amount] 84 | ); 85 | }); 86 | it('should claim any tokens vested and revoke the remaining to owner when revoke is called after unlockCliff but before unlockEnd', async () => { 87 | await setNextBlockTimeStamp(unlockCliff.toNumber()); 88 | await expect(() => revokableTokenLock.revoke(recipient.address)).to.changeTokenBalances( 89 | token, 90 | [recipient, owner], 91 | [transferred, remaining] 92 | ); 93 | }); 94 | it('should claim all tokens and revoke none to owner when revoke is called after unlockEnd', async () => { 95 | await setNextBlockTimeStamp(unlockEnd.toNumber()); 96 | await expect(() => revokableTokenLock.revoke(recipient.address)).to.changeTokenBalances( 97 | token, 98 | [recipient, owner], 99 | [amount, 0] 100 | ); 101 | }); 102 | it('should return claimable balance of 0 after some time has passed since revoke was called.', async () => { 103 | const unlock = unlockCliff.toNumber(); 104 | await setNextBlockTimeStamp(unlock); 105 | await revokableTokenLock.revoke(recipient.address); 106 | 107 | // Set next block timestamp to 2 hour after revoke was called and mine a block. 108 | await mineBlockAt(unlock + 2 * ONE_HOUR); 109 | const bal = await revokableTokenLock.claimableBalance(recipient.address); 110 | expect(bal).to.equal(0); 111 | }); 112 | it('should emit the Revoked event when revoke is called', async () => { 113 | await setNextBlockTimeStamp(unlockCliff.toNumber()); 114 | expect(await revokableTokenLock.revoke(recipient.address)) 115 | .to.emit(revokableTokenLock, 'Revoked') 116 | .withArgs(recipient.address, remaining); 117 | }); 118 | }); 119 | }); 120 | -------------------------------------------------------------------------------- /test/TokenLock.spec.ts: -------------------------------------------------------------------------------- 1 | import {expect} from 'chai'; 2 | import chai from 'chai'; 3 | import {ethers, waffle} from 'hardhat'; 4 | import {TestERC20, TokenLock} from '../typechain'; 5 | import {MAX_UINT, ONE, ONE_DAY, ONE_YEAR, ONE_18, ZERO, ZERO_ADDRESS} from '../shared/Constants'; 6 | import {mineBlockAt, resetNetwork, setNextBlockTimeStamp} from '../shared/TimeManipulation'; 7 | 8 | const {solidity, loadFixture} = waffle; 9 | chai.use(solidity); 10 | 11 | let token: TestERC20; 12 | let tokenLock: TokenLock; 13 | let unlockBegin: number; 14 | let unlockCliff: number; 15 | let unlockEnd: number; 16 | 17 | describe('TokenLock', async () => { 18 | const [user, admin, tokenSale, other] = waffle.provider.getWallets(); 19 | 20 | // TODO: maybe create shared fixtures that can be imported by the test files 21 | async function fixture() { 22 | const TokenFactory = await ethers.getContractFactory('TestERC20'); 23 | const token = (await TokenFactory.connect(admin).deploy('TEST', 'TEST')) as TestERC20; 24 | const TokenLockFactory = await ethers.getContractFactory('TokenLock'); 25 | const tokenLock = (await TokenLockFactory.connect(admin).deploy(token.address)) as TokenLock; 26 | return {token, tokenLock}; 27 | } 28 | beforeEach('deploy fixture, update time', async () => { 29 | ({token, tokenLock} = await loadFixture(fixture)); 30 | // hardhat implicitly uses now as block time, so need to make tests relative to now 31 | unlockBegin = Math.floor(Date.now() / 1000) + ONE_DAY; 32 | unlockCliff = unlockBegin; // in our case, there isn't a cliff 33 | unlockEnd = unlockBegin + 4 * ONE_YEAR; 34 | }); 35 | 36 | describe('#setTokenSale', async () => { 37 | it('should revert if caller is not owner', async () => { 38 | await expect(tokenLock.connect(user).setTokenSale(tokenSale.address)).to.be.revertedWith( 39 | 'Ownable: caller is not the owner' 40 | ); 41 | await expect(tokenLock.connect(other).setTokenSale(tokenSale.address)).to.be.revertedWith( 42 | 'Ownable: caller is not the owner' 43 | ); 44 | }); 45 | 46 | it('should revert if owner tries to set null address', async () => { 47 | await expect(tokenLock.connect(admin).setTokenSale(ZERO_ADDRESS)).to.be.revertedWith('TokenLock: Null address'); 48 | }); 49 | 50 | it('should set tokenSale address', async () => { 51 | await tokenLock.connect(admin).setTokenSale(tokenSale.address); 52 | expect(await tokenLock.tokenSale()).to.be.eq(tokenSale.address); 53 | 54 | await tokenLock.connect(admin).setTokenSale(user.address); 55 | expect(await tokenLock.tokenSale()).to.be.eq(user.address); 56 | }); 57 | }); 58 | 59 | describe('#setupVesting', async () => { 60 | it('should revert for non-authorized actors', async () => { 61 | await expect( 62 | tokenLock.connect(user).setupVesting(user.address, unlockBegin, unlockCliff, unlockEnd) 63 | ).to.be.revertedWith('TokenLock: Only owner/ claims/ sale contract can call'); 64 | 65 | await expect( 66 | tokenLock.connect(other).setupVesting(other.address, unlockBegin, unlockCliff, unlockEnd) 67 | ).to.be.revertedWith('TokenLock: Only owner/ claims/ sale contract can call'); 68 | }); 69 | 70 | it('should setup vesting for authorized actors', async () => { 71 | await tokenLock.connect(admin).setupVesting(admin.address, unlockBegin, unlockCliff, unlockEnd); 72 | let vest = await tokenLock.vesting(admin.address); 73 | expect(vest.unlockBegin).to.be.eq(unlockBegin); 74 | expect(vest.unlockCliff).to.be.eq(unlockCliff); 75 | expect(vest.unlockEnd).to.be.eq(unlockEnd); 76 | 77 | await tokenLock.connect(admin).setTokenSale(tokenSale.address); 78 | await tokenLock.connect(tokenSale).setupVesting(user.address, unlockBegin, unlockCliff, unlockEnd); 79 | vest = await tokenLock.vesting(user.address); 80 | expect(vest.unlockBegin).to.be.eq(unlockBegin); 81 | expect(vest.unlockCliff).to.be.eq(unlockCliff); 82 | expect(vest.unlockEnd).to.be.eq(unlockEnd); 83 | }); 84 | 85 | it('should revert if begin > cliff', async () => { 86 | await expect(tokenLock.connect(admin).setupVesting(user.address, 1000, 999, 2000)).to.be.revertedWith( 87 | 'TokenLock: Unlock cliff must not be before unlock begin' 88 | ); 89 | 90 | await expect( 91 | tokenLock.connect(admin).setupVesting(user.address, unlockBegin, unlockBegin - 1, unlockEnd) 92 | ).to.be.revertedWith('TokenLock: Unlock cliff must not be before unlock begin'); 93 | }); 94 | 95 | it('should revert if cliff > end', async () => { 96 | await expect(tokenLock.connect(admin).setupVesting(user.address, 1000, 1000, 999)).to.be.revertedWith( 97 | 'TokenLock: Unlock end must not be before unlock cliff' 98 | ); 99 | 100 | await expect( 101 | tokenLock.connect(admin).setupVesting(user.address, unlockBegin, unlockCliff, unlockCliff - 1) 102 | ).to.be.revertedWith('TokenLock: Unlock end must not be before unlock cliff'); 103 | }); 104 | }); 105 | 106 | describe('#lock', async () => { 107 | beforeEach('set vesting schedule for user', async () => { 108 | await tokenLock.connect(admin).setupVesting(user.address, unlockBegin, unlockCliff, unlockEnd); 109 | }); 110 | 111 | it('should revert locks if unlock period has completed', async () => { 112 | await setNextBlockTimeStamp(unlockEnd); 113 | await expect(tokenLock.connect(admin).lock(user.address, ONE)).to.be.revertedWith( 114 | 'TokenLock: Unlock period already complete' 115 | ); 116 | }); 117 | 118 | it('should revert lock if vesting has not been setup for recipient', async () => { 119 | await expect(tokenLock.connect(admin).lock(other.address, ONE)).to.be.revertedWith( 120 | 'TokenLock: Unlock period already complete' 121 | ); 122 | }); 123 | 124 | it('should revert if lock() is called without sufficient balance or allowance', async () => { 125 | await expect(tokenLock.connect(admin).lock(user.address, ONE)).to.be.revertedWith( 126 | 'ERC20: insufficient allowance' 127 | ); 128 | await token.connect(user).approve(tokenLock.address, ONE); 129 | await expect(tokenLock.connect(user).lock(user.address, ONE)).to.be.revertedWith( 130 | 'ERC20: transfer amount exceeds balance' 131 | ); 132 | }); 133 | 134 | describe('successful lock', async () => { 135 | beforeEach('admin gives tokenLock token allowance', async () => { 136 | await token.connect(admin).approve(tokenLock.address, MAX_UINT); 137 | }); 138 | 139 | it('should emit Locked event', async () => { 140 | expect(await tokenLock.connect(admin).lock(user.address, ONE_18)) 141 | .to.emit(tokenLock, 'Locked') 142 | .withArgs(admin.address, user.address, ONE_18); 143 | }); 144 | 145 | it('should have transferred tokens from caller to contract', async () => { 146 | await expect(() => tokenLock.connect(admin).lock(user.address, ONE_18)).to.changeTokenBalances( 147 | token, 148 | [admin, tokenLock], 149 | [ONE_18.mul(-1), ONE_18] 150 | ); 151 | }); 152 | 153 | it('should have successfully locked user balance before UnlockBegin', async () => { 154 | await setNextBlockTimeStamp(unlockBegin - 1); 155 | // verify zero locked amount prior to lock 156 | expect((await tokenLock.vesting(user.address)).lockedAmounts).to.be.eq(ZERO); 157 | await tokenLock.connect(admin).lock(user.address, ONE_18); 158 | expect((await tokenLock.vesting(user.address)).lockedAmounts).to.be.eq(ONE_18); 159 | }); 160 | 161 | it('should have successfully locked user balance before UnlockEnd', async () => { 162 | await setNextBlockTimeStamp(unlockEnd - 1); 163 | // verify zero locked amount prior to lock 164 | expect((await tokenLock.vesting(user.address)).lockedAmounts).to.be.eq(ZERO); 165 | await tokenLock.connect(admin).lock(user.address, ONE_18); 166 | expect((await tokenLock.vesting(user.address)).lockedAmounts).to.be.eq(ONE_18); 167 | }); 168 | }); 169 | }); 170 | 171 | describe('#claimableBalance, #claim', async () => { 172 | beforeEach('reset time, setup vesting schedule and lock some tokens', async () => { 173 | await setNextBlockTimeStamp(unlockBegin - 100); 174 | await tokenLock.connect(admin).setupVesting(user.address, unlockBegin, unlockCliff, unlockEnd); 175 | await token.connect(admin).approve(tokenLock.address, ONE_18); 176 | await tokenLock.connect(admin).lock(user.address, ONE_18); 177 | }); 178 | 179 | it('should return 0 claimable balance for user without vesting schedule setup', async () => { 180 | expect(await tokenLock.claimableBalance(other.address)).to.be.eq(ZERO); 181 | }); 182 | 183 | it('should have claimed 0 tokens if timestamp < cliff', async () => { 184 | await expect(() => tokenLock.connect(user).claim(user.address, ONE)).to.changeTokenBalance(token, user, ZERO); 185 | }); 186 | 187 | it('should allow a proportional amount to be claimed after the cliff', async () => { 188 | let claimTime = unlockEnd - ONE_YEAR; 189 | await mineBlockAt(claimTime); 190 | const unlockAmount = ONE_18.mul(claimTime - unlockBegin).div(unlockEnd - unlockBegin); 191 | expect(await tokenLock.claimableBalance(user.address)).to.be.eq(unlockAmount); 192 | const balanceBefore = await token.balanceOf(user.address); 193 | await expect(tokenLock.connect(user).claim(user.address, unlockAmount)) 194 | .to.emit(tokenLock, 'Claimed') 195 | .withArgs(user.address, user.address, unlockAmount); 196 | expect(await token.balanceOf(user.address)).to.be.eq(balanceBefore.add(unlockAmount)); 197 | }); 198 | 199 | it('should automatically limit claims to the maximum allowed', async () => { 200 | let claimTime = unlockEnd - ONE_YEAR; 201 | await setNextBlockTimeStamp(claimTime); 202 | const unlockAmount = ONE_18.mul(claimTime - unlockBegin).div(unlockEnd - unlockBegin); 203 | await expect(() => tokenLock.connect(user).claim(user.address, MAX_UINT)).to.changeTokenBalance( 204 | token, 205 | user, 206 | unlockAmount 207 | ); 208 | }); 209 | 210 | it('should return 0 claimable balance immediately after claiming', async () => { 211 | let claimTime = unlockEnd - ONE_YEAR; 212 | await setNextBlockTimeStamp(claimTime); 213 | await tokenLock.connect(user).claim(user.address, MAX_UINT); 214 | expect(await tokenLock.claimableBalance(user.address)).to.be.eq(ZERO); 215 | }); 216 | 217 | it('should have non-zero claimable balance as remaining tokens continue to be vested', async () => { 218 | let claimTime = unlockEnd - ONE_YEAR; 219 | await setNextBlockTimeStamp(claimTime); 220 | await tokenLock.connect(user).claim(user.address, MAX_UINT); 221 | // 1 day after claim 222 | await mineBlockAt(claimTime + ONE_DAY); 223 | expect(await tokenLock.claimableBalance(user.address)).to.be.gt(ZERO); 224 | }); 225 | 226 | it('should have all tokens claimable after tokens are fully unlocked', async () => { 227 | await mineBlockAt(unlockEnd); 228 | expect(await tokenLock.claimableBalance(user.address)).to.equal(ONE_18); 229 | await expect(() => tokenLock.connect(user).claim(user.address, ONE_18)).to.changeTokenBalance( 230 | token, 231 | user, 232 | ONE_18 233 | ); 234 | expect(await tokenLock.claimableBalance(user.address)).to.be.eq(ZERO); 235 | // 1 year after claim 236 | await mineBlockAt(unlockEnd + ONE_YEAR); 237 | expect(await tokenLock.claimableBalance(user.address)).to.be.eq(ZERO); 238 | // should have no change in token balance if user attempts to claim 239 | await expect(() => tokenLock.connect(user).claim(user.address, MAX_UINT)).to.changeTokenBalance( 240 | token, 241 | user, 242 | ZERO 243 | ); 244 | }); 245 | }); 246 | 247 | after('reset network', async () => { 248 | await resetNetwork(); 249 | }); 250 | }); 251 | -------------------------------------------------------------------------------- /test/TokenSale.spec.ts: -------------------------------------------------------------------------------- 1 | import {expect} from 'chai'; 2 | import chai from 'chai'; 3 | import {ethers, waffle} from 'hardhat'; 4 | import {BigNumber as BN} from 'ethers'; 5 | import {TestERC20, RevokableTokenLock, TokenSale} from '../typechain'; 6 | import {ONE_DAY, ONE_18, MAX_UINT, ONE_YEAR} from '../shared/Constants'; 7 | import {setNextBlockTimeStamp, resetNetwork} from '../shared/TimeManipulation'; 8 | 9 | const {solidity, loadFixture} = waffle; 10 | chai.use(solidity); 11 | 12 | let tokenIn: TestERC20; 13 | let tokenOut: TestERC20; 14 | let tokenLock: RevokableTokenLock; 15 | let tokenSale: TokenSale; 16 | // hardhat implicitly uses now as block time, so need to make tests relative to now 17 | const SALE_START = Math.floor(Date.now() / 1000) + ONE_YEAR; 18 | const ARENA_DECIMALS = 18; 19 | const ONE_ARENA = ethers.utils.parseUnits(`1`, ARENA_DECIMALS); 20 | // Polygon USDC only has 6 decimals 21 | const TOKEN_IN_DECIMALS = 6; 22 | const ONE_USDC = ethers.utils.parseUnits(`1`, TOKEN_IN_DECIMALS); 23 | // 0.05 USDC per 1.0 ARENA (0.05e6 * 1e18 / 1*1e18) 24 | const TOKEN_OUT_PRICE = ethers.utils.parseUnits(`0.05`, TOKEN_IN_DECIMALS).mul(ONE_18).div(ONE_ARENA); 25 | const SALE_DURATION = 7 * ONE_DAY; 26 | // 75 USDC 27 | const SALE_RECIPIENT_AMOUNT = ONE_USDC.mul(75); 28 | const WHITELISTED_ACCOUNTS: string[] = []; 29 | // each buyer allowed 50 USDC worth of ARENA tokens (1000) 30 | const BUYER_USDC_AMOUNT = ONE_USDC.mul(50); 31 | const BUYER_ARENA_AMOUNT = BN.from(`1000`).mul(ONE_ARENA); 32 | const WHITELISTED_AMOUNTS = Array(3).fill(BUYER_ARENA_AMOUNT); 33 | 34 | describe('TokenSale', async () => { 35 | const [user, admin, saleRecipient, buyer1, buyer2, buyer3, other] = waffle.provider.getWallets(); 36 | WHITELISTED_ACCOUNTS.push(buyer1.address, buyer2.address, buyer3.address); 37 | 38 | async function fixture() { 39 | let TokenFactory = await ethers.getContractFactory('TestERC20'); 40 | let tokenIn = await TokenFactory.connect(admin).deploy('USDC', 'USDC'); 41 | let tokenOut = await TokenFactory.connect(admin).deploy('ARENA', 'ARENA'); 42 | let TokenLockFactory = await ethers.getContractFactory('RevokableTokenLock'); 43 | let tokenLock = (await TokenLockFactory.connect(admin).deploy( 44 | tokenOut.address, 45 | admin.address 46 | )) as RevokableTokenLock; 47 | 48 | let TokenSaleFactory = await ethers.getContractFactory('TokenSale'); 49 | let tokenSale = (await TokenSaleFactory.connect(admin).deploy( 50 | tokenIn.address, 51 | tokenOut.address, 52 | SALE_START, 53 | SALE_DURATION, 54 | TOKEN_OUT_PRICE, 55 | saleRecipient.address, 56 | tokenLock.address, 57 | ONE_DAY, 58 | SALE_RECIPIENT_AMOUNT 59 | )) as TokenSale; 60 | await tokenLock.setTokenSale(tokenSale.address); 61 | await tokenSale.changeWhiteList(WHITELISTED_ACCOUNTS, WHITELISTED_AMOUNTS); 62 | 63 | // distribute tokens and set approvals 64 | await tokenOut.connect(admin).transfer( 65 | tokenSale.address, 66 | WHITELISTED_AMOUNTS.reduce((acc, amount) => acc.add(amount), BN.from(`0`)) 67 | ); 68 | for (const buyer of [buyer1, buyer2, buyer3]) { 69 | await tokenIn.connect(admin).transfer(buyer.address, BN.from(1_000_000).mul(ONE_USDC)); 70 | await tokenIn.connect(buyer).approve(tokenSale.address, MAX_UINT); 71 | } 72 | 73 | return {tokenIn, tokenOut, tokenSale, tokenLock}; 74 | } 75 | 76 | beforeEach('deploy fixture', async () => { 77 | ({tokenIn, tokenOut, tokenSale, tokenLock} = await loadFixture(fixture)); 78 | }); 79 | 80 | describe('#changeWhiteList', async () => { 81 | beforeEach('reset time', async () => { 82 | // don't start sale yet 83 | await setNextBlockTimeStamp(SALE_START - ONE_DAY); 84 | }); 85 | 86 | it('should revert if caller is not owner or seller', async () => { 87 | await expect( 88 | tokenSale.connect(user).changeWhiteList(WHITELISTED_ACCOUNTS, WHITELISTED_AMOUNTS) 89 | ).to.be.revertedWith('TokenSale: not authorized'); 90 | await expect( 91 | tokenSale.connect(other).changeWhiteList(WHITELISTED_ACCOUNTS, WHITELISTED_AMOUNTS) 92 | ).to.be.revertedWith('TokenSale: not authorized'); 93 | }); 94 | 95 | it('should allow owner and seller to set', async () => { 96 | let buyers = [...WHITELISTED_ACCOUNTS, other.address]; 97 | let amounts = [...WHITELISTED_AMOUNTS, 69]; 98 | await tokenSale.connect(admin).changeWhiteList(buyers, amounts); 99 | 100 | // seller modifies first entry 101 | let diff = buyers.slice(0, 1); 102 | let diffAmounts = [420]; 103 | await tokenSale.connect(saleRecipient).changeWhiteList(diff, diffAmounts); 104 | 105 | for (let i = 0; i < buyers.length; i++) { 106 | let mergedAmount = i === 0 ? diffAmounts[i] : amounts[i]; 107 | expect(await tokenSale.whitelistedBuyersAmount(buyers[i])).to.eq(mergedAmount); 108 | } 109 | }); 110 | 111 | it('should revert if sale is ongoing', async () => { 112 | await setNextBlockTimeStamp(SALE_START); 113 | await expect( 114 | tokenSale.connect(admin).changeWhiteList(WHITELISTED_ACCOUNTS, WHITELISTED_AMOUNTS) 115 | ).to.be.revertedWith('TokenSale: ongoing sale'); 116 | }); 117 | 118 | it('should revert if a buyer has an existing vesting schedule', async () => { 119 | await tokenLock.connect(admin).setupVesting(buyer1.address, 1, 2, 3); 120 | await expect( 121 | tokenSale.connect(admin).changeWhiteList(WHITELISTED_ACCOUNTS, WHITELISTED_AMOUNTS) 122 | ).to.be.revertedWith('TokenSale: buyer has existing vest schedule'); 123 | }); 124 | }); 125 | 126 | describe('#setNewSaleStart', async () => { 127 | it('should revert if caller is not owner or seller', async () => { 128 | await expect(tokenSale.connect(user).setNewSaleStart(SALE_START + SALE_DURATION - 1)).to.be.revertedWith( 129 | 'TokenSale: not authorized' 130 | ); 131 | await expect(tokenSale.connect(other).setNewSaleStart(SALE_START + SALE_DURATION - 1)).to.be.revertedWith( 132 | 'TokenSale: not authorized' 133 | ); 134 | }); 135 | 136 | it('should revert if sale is ongoing', async () => { 137 | await setNextBlockTimeStamp(SALE_START); 138 | await expect(tokenSale.connect(admin).setNewSaleStart(SALE_START + 2 * SALE_DURATION)).to.be.revertedWith( 139 | 'TokenSale: ongoing sale' 140 | ); 141 | await setNextBlockTimeStamp(SALE_START + SALE_DURATION); 142 | await expect(tokenSale.connect(saleRecipient).setNewSaleStart(SALE_START + 2 * SALE_DURATION)).to.be.revertedWith( 143 | 'TokenSale: ongoing sale' 144 | ); 145 | }); 146 | 147 | it('will revert if sale start time is set before block.timestamp', async () => { 148 | await setNextBlockTimeStamp(SALE_START - 2 * SALE_DURATION); 149 | await expect(tokenSale.connect(admin).setNewSaleStart(SALE_START - 2 * SALE_DURATION - 1)).to.be.revertedWith( 150 | 'TokenSale: new sale too early' 151 | ); 152 | 153 | await setNextBlockTimeStamp(SALE_START - SALE_DURATION); 154 | await expect(tokenSale.connect(admin).setNewSaleStart(SALE_START - SALE_DURATION)).to.be.revertedWith( 155 | 'TokenSale: new sale too early' 156 | ); 157 | }); 158 | 159 | it('should bring forward sale start time before sale starts', async () => { 160 | await setNextBlockTimeStamp(SALE_START - 1.5 * SALE_DURATION); 161 | expect(await tokenSale.saleStart()).to.eq(SALE_START); 162 | await tokenSale.connect(admin).setNewSaleStart(SALE_START - SALE_DURATION); 163 | expect(await tokenSale.saleStart()).to.eq(SALE_START - SALE_DURATION); 164 | }); 165 | 166 | it('should set new sale start after current one ends', async () => { 167 | await setNextBlockTimeStamp(SALE_START + SALE_DURATION + 1); 168 | await tokenSale.connect(admin).setNewSaleStart(SALE_START + 2 * SALE_DURATION); 169 | expect(await tokenSale.saleStart()).to.eq(SALE_START + 2 * SALE_DURATION); 170 | }); 171 | }); 172 | 173 | describe('#sweepTokenOut', async () => { 174 | it('should revert if called before token sale end', async () => { 175 | await setNextBlockTimeStamp(SALE_START + SALE_DURATION - 1); 176 | await expect(tokenSale.connect(user).sweepTokenOut()).to.be.revertedWith('TokenSale: ongoing sale'); 177 | }); 178 | 179 | it('should send back any remaining tokenOut', async () => { 180 | await setNextBlockTimeStamp(SALE_START + SALE_DURATION + 1); 181 | 182 | let preTokenOut = await tokenOut.balanceOf(admin.address); 183 | await tokenSale.connect(other).sweepTokenOut(); 184 | let postTokenOut = await tokenOut.balanceOf(admin.address); 185 | expect(postTokenOut.sub(preTokenOut)).to.eq( 186 | WHITELISTED_AMOUNTS.reduce((acc, amount) => acc.add(amount), BN.from(`0`)) 187 | ); 188 | }); 189 | }); 190 | 191 | describe('#sweep', async () => { 192 | let otherToken: TestERC20; 193 | beforeEach('distribute other token', async () => { 194 | let TokenFactory = await ethers.getContractFactory('TestERC20'); 195 | otherToken = await TokenFactory.connect(admin).deploy('OTHER', 'OTHER'); 196 | await otherToken.transfer(tokenSale.address, ONE_18); 197 | }); 198 | 199 | it('should revert if caller is not owner or seller', async () => { 200 | await expect(tokenSale.connect(user).sweep(otherToken.address)).to.be.revertedWith('TokenSale: not authorized'); 201 | await expect(tokenSale.connect(other).sweep(otherToken.address)).to.be.revertedWith('TokenSale: not authorized'); 202 | }); 203 | 204 | it('should not allow sending back the tokenOut', async () => { 205 | await expect(tokenSale.connect(admin).sweep(tokenOut.address)).to.be.revertedWith( 206 | 'TokenSale: cannot sweep tokenOut as it belongs to owner' 207 | ); 208 | }); 209 | 210 | it('should allow sweeping other tokens', async () => { 211 | await tokenSale.connect(saleRecipient).sweep(otherToken.address); 212 | let saleBalance = await otherToken.balanceOf(tokenSale.address); 213 | expect(saleBalance).to.eq(`0`); 214 | let sellerBalance = await otherToken.balanceOf(saleRecipient.address); 215 | expect(sellerBalance).to.eq(ONE_18); 216 | }); 217 | }); 218 | 219 | describe('#buy', async () => { 220 | beforeEach('reset time', async () => { 221 | // start sale 222 | await setNextBlockTimeStamp(SALE_START); 223 | }); 224 | 225 | it('should revert if trying to buy before sale', async () => { 226 | await setNextBlockTimeStamp(SALE_START - 1); 227 | await expect(tokenSale.connect(buyer1).buy()).to.be.revertedWith('TokenSale: not started'); 228 | }); 229 | 230 | it('should revert if trying to buy after sale duration', async () => { 231 | await setNextBlockTimeStamp(SALE_START + SALE_DURATION + 1); 232 | await expect(tokenSale.connect(buyer1).buy()).to.be.revertedWith('TokenSale: already ended'); 233 | }); 234 | 235 | it('should revert if non-whitelisted tries to buy', async () => { 236 | await expect(tokenSale.connect(user).buy()).to.be.revertedWith( 237 | 'TokenSale: non-whitelisted purchaser or have already bought' 238 | ); 239 | await expect(tokenSale.connect(other).buy()).to.be.revertedWith( 240 | 'TokenSale: non-whitelisted purchaser or have already bought' 241 | ); 242 | }); 243 | 244 | it('should let whitelisted buy only full amount', async () => { 245 | let preBuyerTokenInBalance = await tokenIn.balanceOf(buyer1.address); 246 | let preSellerTokenInBalance = await tokenIn.balanceOf(saleRecipient.address); 247 | await tokenSale.connect(buyer1).buy(); 248 | let postBuyerTokenInBalance = await tokenIn.balanceOf(buyer1.address); 249 | let tokenInPaid = preBuyerTokenInBalance.sub(postBuyerTokenInBalance); 250 | 251 | // 0.05 USDC per ARENA => buying 1000 ARENA should cost 0.05 USDC / ARENA * 1000 ARENA = 50 USDC 252 | let expectedTokenIn = ethers.utils.parseUnits(`50`, TOKEN_IN_DECIMALS); 253 | expect(tokenInPaid).eq(expectedTokenIn); 254 | 255 | // saleRecipient received entire amount 256 | expect(await tokenIn.balanceOf(saleRecipient.address)).to.eq(preSellerTokenInBalance.add(tokenInPaid)); 257 | 258 | // buyer received 20% immediately, 80% locked 259 | expect(await tokenOut.balanceOf(buyer1.address)).to.eq(WHITELISTED_AMOUNTS[0].div(5)); 260 | const vesting = await tokenLock.vesting(buyer1.address); 261 | expect(vesting.unlockBegin).to.eq(SALE_START); 262 | expect(vesting.unlockCliff).to.eq(SALE_START); 263 | expect(vesting.unlockEnd).to.eq(SALE_START + ONE_DAY); 264 | expect(vesting.lockedAmounts).to.eq(WHITELISTED_AMOUNTS[0].mul(4).div(5)); 265 | 266 | // whitelisted cannot buy again 267 | await expect(tokenSale.connect(buyer1).buy()).to.be.revertedWith( 268 | 'TokenSale: non-whitelisted purchaser or have already bought' 269 | ); 270 | }); 271 | 272 | it('should revert if buyer has insufficient funds', async () => { 273 | await tokenIn.connect(buyer1).transfer(buyer2.address, await tokenIn.balanceOf(buyer1.address)); 274 | await expect(tokenSale.connect(buyer1).buy()).to.be.revertedWith('ERC20: transfer amount exceeds balance'); 275 | }); 276 | 277 | it('should transfer correct amounts to saleRecipient and owner depending on remaining sale recipient', async () => { 278 | // not exceeded, transfer fully to saleRecipient 279 | await expect(() => tokenSale.connect(buyer1).buy()).to.changeTokenBalances( 280 | tokenIn, 281 | [buyer1, saleRecipient], 282 | [BUYER_USDC_AMOUNT.mul(-1), BUYER_USDC_AMOUNT] 283 | ); 284 | 285 | // will be exceeded, half to saleRecipient, remaining to owner 286 | await expect(() => tokenSale.connect(buyer2).buy()).to.changeTokenBalances( 287 | tokenIn, 288 | [buyer2, saleRecipient, admin], 289 | [BUYER_USDC_AMOUNT.mul(-1), BUYER_USDC_AMOUNT.div(2), BUYER_USDC_AMOUNT.div(2)] 290 | ); 291 | 292 | // have been exceeded, all to admin 293 | await expect(() => tokenSale.connect(buyer3).buy()).to.changeTokenBalances( 294 | tokenIn, 295 | [buyer3, admin], 296 | [BUYER_USDC_AMOUNT.mul(-1), BUYER_USDC_AMOUNT] 297 | ); 298 | }); 299 | 300 | it('should allow new buyers (distinct from the current one) to participate in a subsequent token sale', async () => { 301 | // set current sale to only buyer1 302 | await setNextBlockTimeStamp(SALE_START - 10); 303 | await tokenSale.connect(admin).changeWhiteList([buyer1.address], [BUYER_ARENA_AMOUNT]); 304 | await setNextBlockTimeStamp(SALE_START); 305 | await tokenSale.connect(buyer1).buy(); 306 | await setNextBlockTimeStamp(SALE_START + SALE_DURATION + 1); 307 | 308 | // start new sale for buyer2, buyer3 and other 309 | await tokenSale.connect(admin).setNewSaleStart(SALE_START + 2 * SALE_DURATION); 310 | // should fail if buyer partipicated in previous sale 311 | await expect( 312 | tokenSale.connect(admin).changeWhiteList(WHITELISTED_ACCOUNTS, WHITELISTED_ACCOUNTS) 313 | ).to.be.revertedWith('TokenSale: buyer has existing vest schedule'); 314 | 315 | // set new sale for buyer2, buyer3 and other 316 | await tokenSale 317 | .connect(admin) 318 | .changeWhiteList([buyer2.address, buyer3.address, other.address], WHITELISTED_AMOUNTS); 319 | await tokenOut.transfer(tokenSale.address, BUYER_ARENA_AMOUNT); 320 | 321 | // buyer1 should fail because sale hasnt started 322 | await expect(tokenSale.connect(buyer1).buy()).to.be.revertedWith('TokenSale: not started'); 323 | 324 | await setNextBlockTimeStamp(SALE_START + 2 * SALE_DURATION); 325 | // buyer1 should fail because not whitelisted anymore 326 | await expect(tokenSale.connect(buyer1).buy()).to.be.revertedWith( 327 | 'TokenSale: non-whitelisted purchaser or have already bought' 328 | ); 329 | 330 | // make purchases, expect correct USDC amounts to be transferred to saleRecipient / admin 331 | // will exceed remainingSaleRecipient amount, half to saleRecipient, remaining to admin 332 | await expect(() => tokenSale.connect(buyer2).buy()).to.changeTokenBalances( 333 | tokenIn, 334 | [buyer2, saleRecipient, admin], 335 | [BUYER_USDC_AMOUNT.mul(-1), BUYER_USDC_AMOUNT.div(2), BUYER_USDC_AMOUNT.div(2)] 336 | ); 337 | 338 | // remainingSaleRecipient exceeded, all to admin 339 | await expect(() => tokenSale.connect(buyer3).buy()).to.changeTokenBalances( 340 | tokenIn, 341 | [buyer3, admin], 342 | [BUYER_USDC_AMOUNT.mul(-1), BUYER_USDC_AMOUNT] 343 | ); 344 | }); 345 | }); 346 | 347 | after('reset network', async () => { 348 | await resetNetwork(); 349 | }); 350 | }); 351 | -------------------------------------------------------------------------------- /test/fixtures/testAirdrop.json: -------------------------------------------------------------------------------- 1 | { 2 | "merkleRoot": "0xb86e0dced055310e26ce11e69d47b6e6064be988564fb002d6ba5a29e7eee713", 3 | "tokenTotal": "359173233000000000000000000", 4 | "claims": { 5 | "0x02435fb305D2BBA609E044279C50498Abe279380": { 6 | "index": 88, 7 | "amount": "6069000000000000000000", 8 | "proof": [ 9 | "0x0343a94a8d0d791c45b0a14c6a72933af20b922faa3cf69a68b2ef3df4e026c9", 10 | "0xe3b24386a8949a8b14e0afa234a9ba2ae6e4f2df1ba7e68f6add15040ab95599", 11 | "0x4780e7cf776f4b6802159462f760558db6e16a518c587cb05fdf43eae395a349", 12 | "0x74be56af0a163b4dc10c22f6e56cc1392c6ed354fd8ed3cc7a0d855f70ea0d55", 13 | "0xc8567efa56c5077c54d5c6c9ef095f44260b4037f776efef493667db87582e34", 14 | "0x719b60f2a548c56f3157ad3143c2bc038f0637c39823e3737d0efa7aaca9646f", 15 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 16 | ] 17 | }, 18 | "0x07f0571566E4b07eA825798A70Fd67fE79077f4E": { 19 | "index": 118, 20 | "amount": "737957000000000000000000", 21 | "proof": [ 22 | "0xb7c18c8154b7105ad501294cae96088138f83984bc9ddc217535888fe23d01dd", 23 | "0x3ccda1dd8d02e91ecff95a307e66d63a1f488b5133b1f8abd53f7a4ed61f366a", 24 | "0x80b870b998f5a6ec943786f3c181a566c098d14f11f4bbbd5eb465921d366945", 25 | "0xe248ba3fa1e713310abb8dec1ab7a20d9057440c69a1cf672418ae08450649f7", 26 | "0x74c3e30eeee252121a9ab017d2dd83663b2cadb68ba4e1f3f2a449aa96de14b4", 27 | "0x6fc24878e99262e915df7cd55e09dccbc4118d513db5e1db3c175149fdf02643", 28 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 29 | ] 30 | }, 31 | "0x09e68152332CbD3A71DE42769439663D1e283855": { 32 | "index": 44, 33 | "amount": "949826000000000000000000", 34 | "proof": [ 35 | "0x2a71a76d52095517003fde76f678bbcd3e2f5b36d06b099443142d48e7f1127c", 36 | "0x0990bcbdf4108201fbd4b767d362b30897fa0a654af21cd2657941e174857a53", 37 | "0x706b742b3f487f51969bd22883f43d631c077cb74696e9b06b0adb78a159b7bc", 38 | "0x44a9a71b20be4019dd4f5e6b689022783850a4d9e3d3ce986a55680f332a9256", 39 | "0x6d704f752c80875dd796c231215e7b7b7a6453ae11328cd21ee4a652aecb621a", 40 | "0x719b60f2a548c56f3157ad3143c2bc038f0637c39823e3737d0efa7aaca9646f", 41 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 42 | ] 43 | }, 44 | "0x0F81a649448c6593629F7893d9C5e04738b508D0": { 45 | "index": 78, 46 | "amount": "402955000000000000000000", 47 | "proof": [ 48 | "0x945caed82cac7144875dbcec1760784b0ab51260d972da9258a8c2ca18d31a6b", 49 | "0x4092f7fef2622168643e4bbbaad345ef463f30e3bbd19463677ff303400bb17d", 50 | "0xf5f6c4cd3c27ba0c8b707319b2e1ae4bfd7ad3efab6dcc6b92fe825012365b65", 51 | "0x3b2099a51483ee781a1f463943910bbf9283e6a91e7286c37444f8ab36fa9017", 52 | "0x74c3e30eeee252121a9ab017d2dd83663b2cadb68ba4e1f3f2a449aa96de14b4", 53 | "0x6fc24878e99262e915df7cd55e09dccbc4118d513db5e1db3c175149fdf02643", 54 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 55 | ] 56 | }, 57 | "0x0c4243ca087F4e4738596F33292064e847DA80dA": { 58 | "index": 13, 59 | "amount": "10756434000000000000000000", 60 | "proof": [ 61 | "0xc836e5af1ea6b1a72de894da6130a51a587ffdb1f29d59fb8fd2de964c980324", 62 | "0xdc90814b38d75ba65ee14947d9c575c76bbbc5a9b2adc4c8edf445bf10880202", 63 | "0x3dbbdc8f762b91d1e5bf6a83e72f0f003bfdb56945d256044e03220b5c35b294", 64 | "0xacfc1cb78689f5ebd2a640b44b29afc78a963f40fc35cfda7f9acfd0f6a1e141", 65 | "0x7e64ae0eaad21b4fe1a5f16bba161679d9978178f5088a152aa2fe904945ec74", 66 | "0x1d658fca51d066969f3f771c5b984d03e830e81980dee305b311849182450981" 67 | ] 68 | }, 69 | "0x0e21C3Ce673ec7F4C346f587cDB35Ce1a3c12885": { 70 | "index": 62, 71 | "amount": "500000000000000000000000", 72 | "proof": [ 73 | "0x9e6ce10e4ec98abdd0076bff359b1814e5e37a417f4bcc474954683b8b0847c8", 74 | "0xa570975e24da4bbd6f9c24db43279f205d52c5c5ab1b6e1d282381eccb451166", 75 | "0x1bed5030987d460823099ff05ef827bc38c9bff6d896d4c50505fe4b0e1d451d", 76 | "0x3b2099a51483ee781a1f463943910bbf9283e6a91e7286c37444f8ab36fa9017", 77 | "0x74c3e30eeee252121a9ab017d2dd83663b2cadb68ba4e1f3f2a449aa96de14b4", 78 | "0x6fc24878e99262e915df7cd55e09dccbc4118d513db5e1db3c175149fdf02643", 79 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 80 | ] 81 | }, 82 | "0x160834291e67Aa55F830062cA8a47186b5E319A9": { 83 | "index": 2, 84 | "amount": "335796000000000000000000", 85 | "proof": [ 86 | "0x591632af3dd8baafa9f83f55f0153756bcfcdefb32985ec7c2b5350ea285b7b0", 87 | "0xfa35b5aba341607ad310a0f2891569435ec26271370f5a8e42dd779d1f6170c7", 88 | "0x6e55da19895a764b49d26711ef8f0ed4f41be319741e0e5f1a719430c6f77898", 89 | "0xa5f4cc10324f5eb27f3533cd177e62a313b21ffe7980e5d257af1c8303ab7897", 90 | "0xb34fddba2171999f0c79d9cd6b679a218e8a0ee144874e3f4acb0c6a0bece1a4", 91 | "0x6fc24878e99262e915df7cd55e09dccbc4118d513db5e1db3c175149fdf02643", 92 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 93 | ] 94 | }, 95 | "0x17175C04daC5984d7e98b4Da665ECd09E4bc477e": { 96 | "index": 32, 97 | "amount": "596781000000000000000000", 98 | "proof": [ 99 | "0x0ea02b8f02b879571ceac89358828f77a56fb34b820c3cf1d322e6a1a5b8fd08", 100 | "0x36cd3c692f034d98f84bd30ca2d39de2ced063d6e5f625660097ba72e256f67c", 101 | "0xaf5e98775d654397a20e5aa8bc24f07ef168e5e92246b5c0e57e0ab78e6856fd", 102 | "0x86cf09d7574cac8d269dd7fa1ab326e18a463bbae0d8d31cc5f599147d0dcd09", 103 | "0xc8567efa56c5077c54d5c6c9ef095f44260b4037f776efef493667db87582e34", 104 | "0x719b60f2a548c56f3157ad3143c2bc038f0637c39823e3737d0efa7aaca9646f", 105 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 106 | ] 107 | }, 108 | "0x192bDD30D272AabC2B1c3c719c518F0f2d10cc60": { 109 | "index": 16, 110 | "amount": "7011276000000000000000000", 111 | "proof": [ 112 | "0x1a270be32c9f7a3a6cab24c9fd4ae8a4d77e148c564110f7da820ec6cce6618e", 113 | "0xb17a36194c66404b0cb824825cf99df5ca2ce75330e924addd078e9c98d3070e", 114 | "0x80bc22cc22ba858ff146bd0f4ec9b3f8289393e581b0587088b2dcd908e0d572", 115 | "0x86cf09d7574cac8d269dd7fa1ab326e18a463bbae0d8d31cc5f599147d0dcd09", 116 | "0xc8567efa56c5077c54d5c6c9ef095f44260b4037f776efef493667db87582e34", 117 | "0x719b60f2a548c56f3157ad3143c2bc038f0637c39823e3737d0efa7aaca9646f", 118 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 119 | ] 120 | }, 121 | "0x1c95a25459edd226aCF8061D40C7FB03716371F7": { 122 | "index": 15, 123 | "amount": "344921000000000000000000", 124 | "proof": [ 125 | "0xe63122610ee6e4edd223327cfdd392a14e69e58914f8e30bdd28a34d954fbc03", 126 | "0xd329db1c4ca1ef811ea6b45b20ea7ac177f1dba6ae34803d5ff671b27b8f54aa", 127 | "0x7403ff8aecae90a4479842f55c1562922cc787bd169b2bcf485fa0718e91e2a9", 128 | "0x7b5a09bd7e95215fcdefbd381494597556bc6be17fc6bb184cec3044716dcbb8", 129 | "0x0356c76cf9a5d9f793f361e7056ec4ea428f2d61eea3b98f35eb78ee858f3aa0", 130 | "0x1d658fca51d066969f3f771c5b984d03e830e81980dee305b311849182450981" 131 | ] 132 | }, 133 | "0x1df6D5549893Ce1480f7cb1FC380FA90bAB3C2D9": { 134 | "index": 29, 135 | "amount": "671592000000000000000000", 136 | "proof": [ 137 | "0xc600af6ae8f2e1485fa89bce65f34de0e87ee96cd8a61cd2a416cd1e623e9bcc", 138 | "0x9d95f2df221f43af86637b0a63abd6336b01db34f844662a53336583c13a1804", 139 | "0x3dbbdc8f762b91d1e5bf6a83e72f0f003bfdb56945d256044e03220b5c35b294", 140 | "0xacfc1cb78689f5ebd2a640b44b29afc78a963f40fc35cfda7f9acfd0f6a1e141", 141 | "0x7e64ae0eaad21b4fe1a5f16bba161679d9978178f5088a152aa2fe904945ec74", 142 | "0x1d658fca51d066969f3f771c5b984d03e830e81980dee305b311849182450981" 143 | ] 144 | }, 145 | "0x24805dB6a8439Ad2559B495e784E40E444bB480a": { 146 | "index": 20, 147 | "amount": "176385000000000000000000", 148 | "proof": [ 149 | "0x3a5c7d34862487c2f741c900c2061df11ba6643f324e3298a2744cf9606af7fd", 150 | "0xe57d35f66cc55e051648507e1ce1c00be1b0f57e6804519fe38f02254eca5ba0", 151 | "0x6c2fc5225c59673c99b767ee3f9a63d31e0118be6cf225866f92a627babebc6e", 152 | "0x9a538600ae45ff5358943bbf26b3c947617c105d95626220425119a3df5c55b0", 153 | "0x6d704f752c80875dd796c231215e7b7b7a6453ae11328cd21ee4a652aecb621a", 154 | "0x719b60f2a548c56f3157ad3143c2bc038f0637c39823e3737d0efa7aaca9646f", 155 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 156 | ] 157 | }, 158 | "0x25c84Be73af28d65E71F092344Efd92B2c79e3f9": { 159 | "index": 94, 160 | "amount": "743835000000000000000000", 161 | "proof": [ 162 | "0x9922ca761e5b1c3dccd65b7890be3ec6e05bccf8b19456d8e474ce8c0196dfae", 163 | "0xf125d10aaf0b855b8283e4349bd263b7787b478a46d15c48679b680d10c686e3", 164 | "0x1bed5030987d460823099ff05ef827bc38c9bff6d896d4c50505fe4b0e1d451d", 165 | "0x3b2099a51483ee781a1f463943910bbf9283e6a91e7286c37444f8ab36fa9017", 166 | "0x74c3e30eeee252121a9ab017d2dd83663b2cadb68ba4e1f3f2a449aa96de14b4", 167 | "0x6fc24878e99262e915df7cd55e09dccbc4118d513db5e1db3c175149fdf02643", 168 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 169 | ] 170 | }, 171 | "0x25cB64Aa24f560A0719e92F4EBFf2876f0dA4267": { 172 | "index": 86, 173 | "amount": "402955000000000000000000", 174 | "proof": [ 175 | "0xae109cf8ccd0fbc6982e97ab961a7eaa7e8323b2f22243901409e2f01afdd252", 176 | "0x505961f67198045bc7c6daf04875c67e5d43b1b406dc3dd8c94587a018d16387", 177 | "0x80b870b998f5a6ec943786f3c181a566c098d14f11f4bbbd5eb465921d366945", 178 | "0xe248ba3fa1e713310abb8dec1ab7a20d9057440c69a1cf672418ae08450649f7", 179 | "0x74c3e30eeee252121a9ab017d2dd83663b2cadb68ba4e1f3f2a449aa96de14b4", 180 | "0x6fc24878e99262e915df7cd55e09dccbc4118d513db5e1db3c175149fdf02643", 181 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 182 | ] 183 | }, 184 | "0x273BA31C706b9D9FdAe1FD999183Bfa865895bE9": { 185 | "index": 60, 186 | "amount": "671592000000000000000000", 187 | "proof": [ 188 | "0x20e2d0d80401aea77ab1b7ddac937a947571d615b3b6ad1962d7ad58c1c2fb3e", 189 | "0x12fff11838a2215309720b11d4cac3c9099a11b3557775623a153d2a51f07761", 190 | "0x225df9eb2528cd19e8c498af4f90e591fc131d96800f7bcba58c69a7db8ec1c7", 191 | "0x44a9a71b20be4019dd4f5e6b689022783850a4d9e3d3ce986a55680f332a9256", 192 | "0x6d704f752c80875dd796c231215e7b7b7a6453ae11328cd21ee4a652aecb621a", 193 | "0x719b60f2a548c56f3157ad3143c2bc038f0637c39823e3737d0efa7aaca9646f", 194 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 195 | ] 196 | }, 197 | "0x28235dE0121700a462B5098Cff7D3615b050551F": { 198 | "index": 12, 199 | "amount": "2663908000000000000000000", 200 | "proof": [ 201 | "0x2ee8294aa8910a13ecd37e9238fc0bdc1902c848e27d169ab4a61f2211d20fd3", 202 | "0x1caf35dd583abbb8b4f9ee3346f27abbcdc45cf740046c847523921a2668c00a", 203 | "0x706b742b3f487f51969bd22883f43d631c077cb74696e9b06b0adb78a159b7bc", 204 | "0x44a9a71b20be4019dd4f5e6b689022783850a4d9e3d3ce986a55680f332a9256", 205 | "0x6d704f752c80875dd796c231215e7b7b7a6453ae11328cd21ee4a652aecb621a", 206 | "0x719b60f2a548c56f3157ad3143c2bc038f0637c39823e3737d0efa7aaca9646f", 207 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 208 | ] 209 | }, 210 | "0x2999Fe533BC08A03304C96E8668BfA17D9D0D35b": { 211 | "index": 28, 212 | "amount": "353833000000000000000000", 213 | "proof": [ 214 | "0x2a4428ec8773fe462be9568182aa13590b6b583adea9750f59928a62ecb4a19d", 215 | "0xc5ba3432397ec06e3352e775db90fe103c8ebc7f95daeebfc74648aa363ecdc3", 216 | "0x225df9eb2528cd19e8c498af4f90e591fc131d96800f7bcba58c69a7db8ec1c7", 217 | "0x44a9a71b20be4019dd4f5e6b689022783850a4d9e3d3ce986a55680f332a9256", 218 | "0x6d704f752c80875dd796c231215e7b7b7a6453ae11328cd21ee4a652aecb621a", 219 | "0x719b60f2a548c56f3157ad3143c2bc038f0637c39823e3737d0efa7aaca9646f", 220 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 221 | ] 222 | }, 223 | "0x2bc996DED6d9325311DEE1bd32998e2fc7f9c922": { 224 | "index": 51, 225 | "amount": "95165000000000000000000", 226 | "proof": [ 227 | "0xfa04e684bd4b52b6b0d8988c3227b746392fc0e3b8155c55d15cad7eef6f2956", 228 | "0x5f6c7b5cdeb706bf33a314dd648c11a611996e62d2ac02f4448f4cb6c0d07795", 229 | "0xff777aab69fb43586c9eb3391f5f8288571f28ac065d61993bad6bcb22cb5783", 230 | "0xa1695fecfa61649bdfc483dc2813a82e5a854ad2416a34c6659fac30b72a71bf", 231 | "0x0356c76cf9a5d9f793f361e7056ec4ea428f2d61eea3b98f35eb78ee858f3aa0", 232 | "0x1d658fca51d066969f3f771c5b984d03e830e81980dee305b311849182450981" 233 | ] 234 | }, 235 | "0x2dca6A81aE189c26Bd63316e659d2b6fB7755aAe": { 236 | "index": 17, 237 | "amount": "537273000000000000000000", 238 | "proof": [ 239 | "0xe0ec631016194ffb06d556a6766d0b67b7dbf470e7337d728d23e07c99d79c05", 240 | "0xcddc8c57ebbf32b4d776f329aa2165a88e03040c0e52ed8046128f004097fbc0", 241 | "0xee43336d4892e70b1b7730094118d8343971d884c160f413e74b1b1e1c00bc83", 242 | "0xf895e65da62c1574529a2b41336e56bbdc29da4da4c501a6d1fdaff43f2eb655", 243 | "0x7e64ae0eaad21b4fe1a5f16bba161679d9978178f5088a152aa2fe904945ec74", 244 | "0x1d658fca51d066969f3f771c5b984d03e830e81980dee305b311849182450981" 245 | ] 246 | }, 247 | "0x2eCf1B0456F4C352775fcF68cFE7354Bf389A701": { 248 | "index": 10, 249 | "amount": "1575380000000000000000000", 250 | "proof": [ 251 | "0x8a287b840516de18b809e06241381bd5859d2990aa4884b06cd27e161db546dd", 252 | "0x9776cc7f006424e20c091dc391d5856bc90651425d18ac85530e5750bab4a44e", 253 | "0x917f91343cc20f4569bea2a259bb3981990cfd71c437e050a2ca7212ad69f7ba", 254 | "0x8984927efebda59207d4897115788045e902f9e8f56c85e2452e67a361f051e2", 255 | "0xb34fddba2171999f0c79d9cd6b679a218e8a0ee144874e3f4acb0c6a0bece1a4", 256 | "0x6fc24878e99262e915df7cd55e09dccbc4118d513db5e1db3c175149fdf02643", 257 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 258 | ] 259 | }, 260 | "0x32F96F4Aac17e62386FED629b403a61d54672c5c": { 261 | "index": 9, 262 | "amount": "42000000000000000000000000", 263 | "proof": [ 264 | "0xd1688ac2ef5256b923f8e50cb021475547e88210cce59b33452306f2e04dc353", 265 | "0xf200c09c910ee07a8b074f18092b6f47427ed1812c246f746fde31130a5f7c4f", 266 | "0x5ad1882ec43a028a5e2fe113850e19ffa460edd404c0765799b8cdabc943fb71", 267 | "0xf895e65da62c1574529a2b41336e56bbdc29da4da4c501a6d1fdaff43f2eb655", 268 | "0x7e64ae0eaad21b4fe1a5f16bba161679d9978178f5088a152aa2fe904945ec74", 269 | "0x1d658fca51d066969f3f771c5b984d03e830e81980dee305b311849182450981" 270 | ] 271 | }, 272 | "0x38BCE8C1Ca5a061aa8e2b4750C18BA3efb27CeD9": { 273 | "index": 54, 274 | "amount": "670572000000000000000000", 275 | "proof": [ 276 | "0xbe20d98a505f4374025b39f8ac994f223ccf95688bef0300fdb769957251901d", 277 | "0x3ccda1dd8d02e91ecff95a307e66d63a1f488b5133b1f8abd53f7a4ed61f366a", 278 | "0x80b870b998f5a6ec943786f3c181a566c098d14f11f4bbbd5eb465921d366945", 279 | "0xe248ba3fa1e713310abb8dec1ab7a20d9057440c69a1cf672418ae08450649f7", 280 | "0x74c3e30eeee252121a9ab017d2dd83663b2cadb68ba4e1f3f2a449aa96de14b4", 281 | "0x6fc24878e99262e915df7cd55e09dccbc4118d513db5e1db3c175149fdf02643", 282 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 283 | ] 284 | }, 285 | "0x3B9Deb72B25C58D25840FC73DcC76cA25E34457b": { 286 | "index": 112, 287 | "amount": "335796000000000000000000", 288 | "proof": [ 289 | "0x1a7dcd21f2b79c563cda37687d5a46f25f522ad6de6a96ec6a27c24ff514f216", 290 | "0x41de793dafe41eb57d1fa78db611d3b810bba68e5f558a877c9063377d637309", 291 | "0x80bc22cc22ba858ff146bd0f4ec9b3f8289393e581b0587088b2dcd908e0d572", 292 | "0x86cf09d7574cac8d269dd7fa1ab326e18a463bbae0d8d31cc5f599147d0dcd09", 293 | "0xc8567efa56c5077c54d5c6c9ef095f44260b4037f776efef493667db87582e34", 294 | "0x719b60f2a548c56f3157ad3143c2bc038f0637c39823e3737d0efa7aaca9646f", 295 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 296 | ] 297 | }, 298 | "0x3C2b8156D8AbE9814BFc800b170b60C66eC33cc2": { 299 | "index": 57, 300 | "amount": "772853000000000000000000", 301 | "proof": [ 302 | "0xd4c8663a04591e27a626e8129a9a5e0533c9699bbd3f50245b38e03470530051", 303 | "0xae5de8dcce21685f54f719653570e2773ac6ac29b12d7e738b65d56e203651c8", 304 | "0x5ad1882ec43a028a5e2fe113850e19ffa460edd404c0765799b8cdabc943fb71", 305 | "0xf895e65da62c1574529a2b41336e56bbdc29da4da4c501a6d1fdaff43f2eb655", 306 | "0x7e64ae0eaad21b4fe1a5f16bba161679d9978178f5088a152aa2fe904945ec74", 307 | "0x1d658fca51d066969f3f771c5b984d03e830e81980dee305b311849182450981" 308 | ] 309 | }, 310 | "0x3d977eF4855A526500E901bFA43c209E0C51BA84": { 311 | "index": 22, 312 | "amount": "6204000000000000000000", 313 | "proof": [ 314 | "0xb5ea53bfbc893706451ca66d63a2b59f4a4d8f1f0076b5d781a7221cc76985ed", 315 | "0x505961f67198045bc7c6daf04875c67e5d43b1b406dc3dd8c94587a018d16387", 316 | "0x80b870b998f5a6ec943786f3c181a566c098d14f11f4bbbd5eb465921d366945", 317 | "0xe248ba3fa1e713310abb8dec1ab7a20d9057440c69a1cf672418ae08450649f7", 318 | "0x74c3e30eeee252121a9ab017d2dd83663b2cadb68ba4e1f3f2a449aa96de14b4", 319 | "0x6fc24878e99262e915df7cd55e09dccbc4118d513db5e1db3c175149fdf02643", 320 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 321 | ] 322 | }, 323 | "0x3feE50d2888F2F7106fcdC0120295EBA3ae59245": { 324 | "index": 21, 325 | "amount": "1242445000000000000000000", 326 | "proof": [ 327 | "0xcb48c63fd1d575e99b27af021415efe4a08e8ada10a0df6a4cd23bdd4b439d91", 328 | "0x07ecdeb1734ceb967e7942989afe29fbc154ee544a539b3cf2a81d1a2dccd7e5", 329 | "0xf2dd0e857df142c7952905d8ee29f05832d7db07c84d4146740cbcb94bef893c", 330 | "0xacfc1cb78689f5ebd2a640b44b29afc78a963f40fc35cfda7f9acfd0f6a1e141", 331 | "0x7e64ae0eaad21b4fe1a5f16bba161679d9978178f5088a152aa2fe904945ec74", 332 | "0x1d658fca51d066969f3f771c5b984d03e830e81980dee305b311849182450981" 333 | ] 334 | }, 335 | "0x423DD109146cac89aF10CAbdf34A0a1c843ED12e": { 336 | "index": 55, 337 | "amount": "56500000000000000000000000", 338 | "proof": [ 339 | "0xebbc28cb12f2741ca8eccb217ad84f0f1bdd4a0fa8bdc964c7c8f6df819aa56d", 340 | "0xa33634b0d5c29aff8fd959a81c4c3f75a260d734688808b50ba078f9a13e8394", 341 | "0x79aabe3c3b6d9a1842f28c36846e49f4d2192f4aa0c9ca2d567c61f0010fa91c", 342 | "0x7b5a09bd7e95215fcdefbd381494597556bc6be17fc6bb184cec3044716dcbb8", 343 | "0x0356c76cf9a5d9f793f361e7056ec4ea428f2d61eea3b98f35eb78ee858f3aa0", 344 | "0x1d658fca51d066969f3f771c5b984d03e830e81980dee305b311849182450981" 345 | ] 346 | }, 347 | "0x45d28aA363fF215B4c6b6a212DC610f004272bb5": { 348 | "index": 66, 349 | "amount": "443251000000000000000000", 350 | "proof": [ 351 | "0x58720ac604f3ffab45f5e0168b88f5206db6e13b00a7fa5f5c5fe2b953e281a7", 352 | "0xfa35b5aba341607ad310a0f2891569435ec26271370f5a8e42dd779d1f6170c7", 353 | "0x6e55da19895a764b49d26711ef8f0ed4f41be319741e0e5f1a719430c6f77898", 354 | "0xa5f4cc10324f5eb27f3533cd177e62a313b21ffe7980e5d257af1c8303ab7897", 355 | "0xb34fddba2171999f0c79d9cd6b679a218e8a0ee144874e3f4acb0c6a0bece1a4", 356 | "0x6fc24878e99262e915df7cd55e09dccbc4118d513db5e1db3c175149fdf02643", 357 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 358 | ] 359 | }, 360 | "0x4666e94300429C6d3340449bC828E4218F360672": { 361 | "index": 126, 362 | "amount": "2133201000000000000000000", 363 | "proof": [ 364 | "0x9e1c3855d655b932ef55040ddaccb6346d2483531c5952e0a47f9db9e3e9da5e", 365 | "0xa570975e24da4bbd6f9c24db43279f205d52c5c5ab1b6e1d282381eccb451166", 366 | "0x1bed5030987d460823099ff05ef827bc38c9bff6d896d4c50505fe4b0e1d451d", 367 | "0x3b2099a51483ee781a1f463943910bbf9283e6a91e7286c37444f8ab36fa9017", 368 | "0x74c3e30eeee252121a9ab017d2dd83663b2cadb68ba4e1f3f2a449aa96de14b4", 369 | "0x6fc24878e99262e915df7cd55e09dccbc4118d513db5e1db3c175149fdf02643", 370 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 371 | ] 372 | }, 373 | "0x4B551a7E465B96a411969d0044a57866b2B3F9a8": { 374 | "index": 58, 375 | "amount": "570853000000000000000000", 376 | "proof": [ 377 | "0x76d4aaa6d738da4db1a9f6cb31c8cdd0cdfebfdfdffe14a4a697b012eaf9596c", 378 | "0x047675f1aa0ebc69ef9b16fe7d32933fba86515694301b2508ace078c02a6cc4", 379 | "0x03d6e42699ae096353af3bbfc82fa0a7cdddb19aa22e6ea2305237e50087062b", 380 | "0x8984927efebda59207d4897115788045e902f9e8f56c85e2452e67a361f051e2", 381 | "0xb34fddba2171999f0c79d9cd6b679a218e8a0ee144874e3f4acb0c6a0bece1a4", 382 | "0x6fc24878e99262e915df7cd55e09dccbc4118d513db5e1db3c175149fdf02643", 383 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 384 | ] 385 | }, 386 | "0x4DF37CC3C48eC3EB689c8Bf0D91249cE2506f73B": { 387 | "index": 120, 388 | "amount": "1287613000000000000000000", 389 | "proof": [ 390 | "0x00aca9ab2842b6d50f0abba9202cbe8da2e2982bb0ac95b6f46fe3053de5686a", 391 | "0x915bc537d9e40bf1a977c3c66a81361361248e0f19324bc44a55d9e6da3ff24f", 392 | "0x4780e7cf776f4b6802159462f760558db6e16a518c587cb05fdf43eae395a349", 393 | "0x74be56af0a163b4dc10c22f6e56cc1392c6ed354fd8ed3cc7a0d855f70ea0d55", 394 | "0xc8567efa56c5077c54d5c6c9ef095f44260b4037f776efef493667db87582e34", 395 | "0x719b60f2a548c56f3157ad3143c2bc038f0637c39823e3737d0efa7aaca9646f", 396 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 397 | ] 398 | }, 399 | "0x5138bA98c03EA6275682b00A33b0D13362072301": { 400 | "index": 53, 401 | "amount": "174517000000000000000000", 402 | "proof": [ 403 | "0xc9fc7f1a0450f9bdf8b80dafeb1e6eb63cadcdae322542f768de2fdbc605906b", 404 | "0x07ecdeb1734ceb967e7942989afe29fbc154ee544a539b3cf2a81d1a2dccd7e5", 405 | "0xf2dd0e857df142c7952905d8ee29f05832d7db07c84d4146740cbcb94bef893c", 406 | "0xacfc1cb78689f5ebd2a640b44b29afc78a963f40fc35cfda7f9acfd0f6a1e141", 407 | "0x7e64ae0eaad21b4fe1a5f16bba161679d9978178f5088a152aa2fe904945ec74", 408 | "0x1d658fca51d066969f3f771c5b984d03e830e81980dee305b311849182450981" 409 | ] 410 | }, 411 | "0x554c5aF96E9e3c05AEC01ce18221d0DD25975aB4": { 412 | "index": 76, 413 | "amount": "75000000000000000000000000", 414 | "proof": [ 415 | "0x2dc500badcbfc5c7f1f9c234a283e95c38a5f04292f1baad088016eb19246f8f", 416 | "0x1caf35dd583abbb8b4f9ee3346f27abbcdc45cf740046c847523921a2668c00a", 417 | "0x706b742b3f487f51969bd22883f43d631c077cb74696e9b06b0adb78a159b7bc", 418 | "0x44a9a71b20be4019dd4f5e6b689022783850a4d9e3d3ce986a55680f332a9256", 419 | "0x6d704f752c80875dd796c231215e7b7b7a6453ae11328cd21ee4a652aecb621a", 420 | "0x719b60f2a548c56f3157ad3143c2bc038f0637c39823e3737d0efa7aaca9646f", 421 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 422 | ] 423 | }, 424 | "0x58993cFa67cD6151EC3D806AE93a7Ed365dB2f88": { 425 | "index": 63, 426 | "amount": "1157026000000000000000000", 427 | "proof": [ 428 | "0xe6ae2599030568b879d9ecafc340916e79f63733d13feb3d18debb0d86afcf63", 429 | "0x0a4175eb4ca083fabe049ad5e1bcf39c0c003f1619a1657c0f924b05378b3d15", 430 | "0x7403ff8aecae90a4479842f55c1562922cc787bd169b2bcf485fa0718e91e2a9", 431 | "0x7b5a09bd7e95215fcdefbd381494597556bc6be17fc6bb184cec3044716dcbb8", 432 | "0x0356c76cf9a5d9f793f361e7056ec4ea428f2d61eea3b98f35eb78ee858f3aa0", 433 | "0x1d658fca51d066969f3f771c5b984d03e830e81980dee305b311849182450981" 434 | ] 435 | }, 436 | "0x60959Ed8307EE2b0d04306f6b319AEeE8864f1Ee": { 437 | "index": 90, 438 | "amount": "751700000000000000000000", 439 | "proof": [ 440 | "0x734df59ca36c7c5bc14f0d885eaf7af2dc8d71a310f6874e8b44d580b124882b", 441 | "0x1ec5322ab6873d1d5dfb51ecb37a2062cb93985cec8572ff53dbc745c960d1a1", 442 | "0x03d6e42699ae096353af3bbfc82fa0a7cdddb19aa22e6ea2305237e50087062b", 443 | "0x8984927efebda59207d4897115788045e902f9e8f56c85e2452e67a361f051e2", 444 | "0xb34fddba2171999f0c79d9cd6b679a218e8a0ee144874e3f4acb0c6a0bece1a4", 445 | "0x6fc24878e99262e915df7cd55e09dccbc4118d513db5e1db3c175149fdf02643", 446 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 447 | ] 448 | }, 449 | "0x609fB8f19B28a76F12557A235C3e88F621267c36": { 450 | "index": 0, 451 | "amount": "500000000000000000000000", 452 | "proof": [ 453 | "0x0fe1b57083bdf97f24a5db5cfdc7fac3b420c32774519c76cc2341ebcf4f9759", 454 | "0x96cf7c0280030a29c74430b0ed5dbab0e415f640af21bf2be7db073debfd92b8", 455 | "0xaf5e98775d654397a20e5aa8bc24f07ef168e5e92246b5c0e57e0ab78e6856fd", 456 | "0x86cf09d7574cac8d269dd7fa1ab326e18a463bbae0d8d31cc5f599147d0dcd09", 457 | "0xc8567efa56c5077c54d5c6c9ef095f44260b4037f776efef493667db87582e34", 458 | "0x719b60f2a548c56f3157ad3143c2bc038f0637c39823e3737d0efa7aaca9646f", 459 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 460 | ] 461 | }, 462 | "0x611DaBF15f0077c64fDB5067376Ad593191e65F3": { 463 | "index": 68, 464 | "amount": "562417000000000000000000", 465 | "proof": [ 466 | "0x313d99d1d6ab2626a1df0686d8aabf91014c6234293a12ec52d6f0a97fe58e7f", 467 | "0xf0713f9cbcc51e4bed98b52c32257900d9c6ce76da867a865763c6263c30cce9", 468 | "0x779b84ce154bc70b115fd2f38634267219129635f0ce2db7797bebb114934074", 469 | "0x9a538600ae45ff5358943bbf26b3c947617c105d95626220425119a3df5c55b0", 470 | "0x6d704f752c80875dd796c231215e7b7b7a6453ae11328cd21ee4a652aecb621a", 471 | "0x719b60f2a548c56f3157ad3143c2bc038f0637c39823e3737d0efa7aaca9646f", 472 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 473 | ] 474 | }, 475 | "0x63A62BDCA68DCA4B98385De493f73C7393b14F6b": { 476 | "index": 110, 477 | "amount": "66822000000000000000000", 478 | "proof": [ 479 | "0x9791c625bb8cf95b5053d15afa6023f9f2157a800de8838bed335bb9d236bcf3", 480 | "0x1b360fd951febd44513ae408d579b527310356a3ec146bbd29d961a95baa189f", 481 | "0xf5f6c4cd3c27ba0c8b707319b2e1ae4bfd7ad3efab6dcc6b92fe825012365b65", 482 | "0x3b2099a51483ee781a1f463943910bbf9283e6a91e7286c37444f8ab36fa9017", 483 | "0x74c3e30eeee252121a9ab017d2dd83663b2cadb68ba4e1f3f2a449aa96de14b4", 484 | "0x6fc24878e99262e915df7cd55e09dccbc4118d513db5e1db3c175149fdf02643", 485 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 486 | ] 487 | }, 488 | "0x63D43942A69b961E398A0F7C260bC3718d320eE2": { 489 | "index": 8, 490 | "amount": "162056000000000000000000", 491 | "proof": [ 492 | "0x07709c22b655425236f407e686f966bcca42efbb0f04ca20b8c69ea0357a539f", 493 | "0xc0a13f6970c0aa618e3ae8f11a07b02a85c6774f18c6758896a37eae56655def", 494 | "0x4eea6795dcb82009bbe0cf790589b848b0d73a2bc5e5f68131d504fed3f91aaf", 495 | "0x74be56af0a163b4dc10c22f6e56cc1392c6ed354fd8ed3cc7a0d855f70ea0d55", 496 | "0xc8567efa56c5077c54d5c6c9ef095f44260b4037f776efef493667db87582e34", 497 | "0x719b60f2a548c56f3157ad3143c2bc038f0637c39823e3737d0efa7aaca9646f", 498 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 499 | ] 500 | }, 501 | "0x64b29cf8A5b2eC1133f2eA5B8BE644F41C9D6d53": { 502 | "index": 56, 503 | "amount": "83066000000000000000000", 504 | "proof": [ 505 | "0x025c4f39c4d5c2dcf2c0ca71af0ef7e5a2ef30e4fd68a9300fab2909d8e97957", 506 | "0x915bc537d9e40bf1a977c3c66a81361361248e0f19324bc44a55d9e6da3ff24f", 507 | "0x4780e7cf776f4b6802159462f760558db6e16a518c587cb05fdf43eae395a349", 508 | "0x74be56af0a163b4dc10c22f6e56cc1392c6ed354fd8ed3cc7a0d855f70ea0d55", 509 | "0xc8567efa56c5077c54d5c6c9ef095f44260b4037f776efef493667db87582e34", 510 | "0x719b60f2a548c56f3157ad3143c2bc038f0637c39823e3737d0efa7aaca9646f", 511 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 512 | ] 513 | }, 514 | "0x64fd9A3Ca8f856CeD235Fd4F634A026B891A10f6": { 515 | "index": 37, 516 | "amount": "150298000000000000000000", 517 | "proof": [ 518 | "0xccade9054dfaff002fa43e3b8180006363a6b82ff8f9c83cd33c5d3258519a73", 519 | "0x52f16150d311628f85775c0bf1d3b0f893b20dca55c8d01b54ecb8fe2e2e4a18", 520 | "0xf2dd0e857df142c7952905d8ee29f05832d7db07c84d4146740cbcb94bef893c", 521 | "0xacfc1cb78689f5ebd2a640b44b29afc78a963f40fc35cfda7f9acfd0f6a1e141", 522 | "0x7e64ae0eaad21b4fe1a5f16bba161679d9978178f5088a152aa2fe904945ec74", 523 | "0x1d658fca51d066969f3f771c5b984d03e830e81980dee305b311849182450981" 524 | ] 525 | }, 526 | "0x660f9D529F01DAfF0A7F15213a8fA39F42FCFe62": { 527 | "index": 100, 528 | "amount": "261704000000000000000000", 529 | "proof": [ 530 | "0x33a1ff497aa5e8c7ee4da1d5c19bfcbd811d29b22b740ef333a617cf15c44491", 531 | "0xb1b21897f0036f2914067da4c4a40393df43c1b6c8a3f33416cb1120989894bb", 532 | "0x779b84ce154bc70b115fd2f38634267219129635f0ce2db7797bebb114934074", 533 | "0x9a538600ae45ff5358943bbf26b3c947617c105d95626220425119a3df5c55b0", 534 | "0x6d704f752c80875dd796c231215e7b7b7a6453ae11328cd21ee4a652aecb621a", 535 | "0x719b60f2a548c56f3157ad3143c2bc038f0637c39823e3737d0efa7aaca9646f", 536 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 537 | ] 538 | }, 539 | "0x6823636c2462cfdcD8d33fE53fBCD0EdbE2752ad": { 540 | "index": 74, 541 | "amount": "22278049000000000000000000", 542 | "proof": [ 543 | "0x83f7a768b5b0332128eafb7830702a5b7edac517bc8908f2d92a86791e8f0b62", 544 | "0x9776cc7f006424e20c091dc391d5856bc90651425d18ac85530e5750bab4a44e", 545 | "0x917f91343cc20f4569bea2a259bb3981990cfd71c437e050a2ca7212ad69f7ba", 546 | "0x8984927efebda59207d4897115788045e902f9e8f56c85e2452e67a361f051e2", 547 | "0xb34fddba2171999f0c79d9cd6b679a218e8a0ee144874e3f4acb0c6a0bece1a4", 548 | "0x6fc24878e99262e915df7cd55e09dccbc4118d513db5e1db3c175149fdf02643", 549 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 550 | ] 551 | }, 552 | "0x6ae089D92a2E88CA18a2d463aD32849cB41C4D0f": { 553 | "index": 49, 554 | "amount": "94461000000000000000000", 555 | "proof": [ 556 | "0xdc542155bf813354fbae03e45d8e6d697fdd2bda30971f3170ac7dacf01e1def", 557 | "0xcddc8c57ebbf32b4d776f329aa2165a88e03040c0e52ed8046128f004097fbc0", 558 | "0xee43336d4892e70b1b7730094118d8343971d884c160f413e74b1b1e1c00bc83", 559 | "0xf895e65da62c1574529a2b41336e56bbdc29da4da4c501a6d1fdaff43f2eb655", 560 | "0x7e64ae0eaad21b4fe1a5f16bba161679d9978178f5088a152aa2fe904945ec74", 561 | "0x1d658fca51d066969f3f771c5b984d03e830e81980dee305b311849182450981" 562 | ] 563 | }, 564 | "0x70aF29f754988473fcAbA6E01AbfbafF871046d1": { 565 | "index": 70, 566 | "amount": "2167352000000000000000000", 567 | "proof": [ 568 | "0x9fcd03523d3a864c2b395f427affa3984fc01d520dc4f9d506b4e6f39dd3820e", 569 | "0x526179014b132adc05558f1db4a1062c4b0024c8fc2c907b09b9eeee62d908eb", 570 | "0xcd80a2bd85828aa0813e79288b1379f7fd8d103369ec434068931c1499e0b804", 571 | "0xe248ba3fa1e713310abb8dec1ab7a20d9057440c69a1cf672418ae08450649f7", 572 | "0x74c3e30eeee252121a9ab017d2dd83663b2cadb68ba4e1f3f2a449aa96de14b4", 573 | "0x6fc24878e99262e915df7cd55e09dccbc4118d513db5e1db3c175149fdf02643", 574 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 575 | ] 576 | }, 577 | "0x7125412Fc011D74C26E3CA768aB6224643702313": { 578 | "index": 52, 579 | "amount": "1033629000000000000000000", 580 | "proof": [ 581 | "0x484c6409db811fbde607a7e1e206d3accac3e996aac0fd345f6afcbf8622e290", 582 | "0xcfa78f03015784a7bd1e3ac2248ca1dabbbb36d06aadf04c1e2ce6a26b550129", 583 | "0x6c2fc5225c59673c99b767ee3f9a63d31e0118be6cf225866f92a627babebc6e", 584 | "0x9a538600ae45ff5358943bbf26b3c947617c105d95626220425119a3df5c55b0", 585 | "0x6d704f752c80875dd796c231215e7b7b7a6453ae11328cd21ee4a652aecb621a", 586 | "0x719b60f2a548c56f3157ad3143c2bc038f0637c39823e3737d0efa7aaca9646f", 587 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 588 | ] 589 | }, 590 | "0x75aAfefc48D23C65F49603491630308DcBd468f5": { 591 | "index": 23, 592 | "amount": "306843000000000000000000", 593 | "proof": [ 594 | "0xf0f87f8cd4e4556d288f437097c5e3cae5b0d465c4441a50e4dd60520c9860e5", 595 | "0xa33634b0d5c29aff8fd959a81c4c3f75a260d734688808b50ba078f9a13e8394", 596 | "0x79aabe3c3b6d9a1842f28c36846e49f4d2192f4aa0c9ca2d567c61f0010fa91c", 597 | "0x7b5a09bd7e95215fcdefbd381494597556bc6be17fc6bb184cec3044716dcbb8", 598 | "0x0356c76cf9a5d9f793f361e7056ec4ea428f2d61eea3b98f35eb78ee858f3aa0", 599 | "0x1d658fca51d066969f3f771c5b984d03e830e81980dee305b311849182450981" 600 | ] 601 | }, 602 | "0x7F6f138C955e5B1017A12E4567D90C62abb00074": { 603 | "index": 30, 604 | "amount": "671592000000000000000000", 605 | "proof": [ 606 | "0x9a628ee17ec2d9f9c76f2173bbfa580654a84bbe6e6c09fb8e8b9f5c8bd00c37", 607 | "0xf125d10aaf0b855b8283e4349bd263b7787b478a46d15c48679b680d10c686e3", 608 | "0x1bed5030987d460823099ff05ef827bc38c9bff6d896d4c50505fe4b0e1d451d", 609 | "0x3b2099a51483ee781a1f463943910bbf9283e6a91e7286c37444f8ab36fa9017", 610 | "0x74c3e30eeee252121a9ab017d2dd83663b2cadb68ba4e1f3f2a449aa96de14b4", 611 | "0x6fc24878e99262e915df7cd55e09dccbc4118d513db5e1db3c175149fdf02643", 612 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 613 | ] 614 | }, 615 | "0x7c23Ec85618C51Ab13310df4aAe8bc2DC9Cd4D38": { 616 | "index": 1, 617 | "amount": "500000000000000000000000", 618 | "proof": [ 619 | "0xe3f9a501d45fa1714b9b55dd496ddc91e1c04262c56d7797923f687fd23eeaa7", 620 | "0xf2b921b0eb6a8c458b023391e2247b6ba40be908ffb3f5b5111f9749f23ffe72", 621 | "0xee43336d4892e70b1b7730094118d8343971d884c160f413e74b1b1e1c00bc83", 622 | "0xf895e65da62c1574529a2b41336e56bbdc29da4da4c501a6d1fdaff43f2eb655", 623 | "0x7e64ae0eaad21b4fe1a5f16bba161679d9978178f5088a152aa2fe904945ec74", 624 | "0x1d658fca51d066969f3f771c5b984d03e830e81980dee305b311849182450981" 625 | ] 626 | }, 627 | "0x7d7935EDd4b6cDB5f34B0E1cCEAF85a3C4A11254": { 628 | "index": 61, 629 | "amount": "671592000000000000000000", 630 | "proof": [ 631 | "0xc38186ea0a1c5a9bdf57eff23b69c5e3383c2538d882c3a43e0991209f7a9d37", 632 | "0x9d95f2df221f43af86637b0a63abd6336b01db34f844662a53336583c13a1804", 633 | "0x3dbbdc8f762b91d1e5bf6a83e72f0f003bfdb56945d256044e03220b5c35b294", 634 | "0xacfc1cb78689f5ebd2a640b44b29afc78a963f40fc35cfda7f9acfd0f6a1e141", 635 | "0x7e64ae0eaad21b4fe1a5f16bba161679d9978178f5088a152aa2fe904945ec74", 636 | "0x1d658fca51d066969f3f771c5b984d03e830e81980dee305b311849182450981" 637 | ] 638 | }, 639 | "0x7e026a0C061382B0F5935a90BC7324ab0a5A3aCc": { 640 | "index": 41, 641 | "amount": "11415691000000000000000000", 642 | "proof": [ 643 | "0xd15cfe20e5c66e54d86dc598966b2db4df16f378777e88971ad5d5213645a118", 644 | "0xf200c09c910ee07a8b074f18092b6f47427ed1812c246f746fde31130a5f7c4f", 645 | "0x5ad1882ec43a028a5e2fe113850e19ffa460edd404c0765799b8cdabc943fb71", 646 | "0xf895e65da62c1574529a2b41336e56bbdc29da4da4c501a6d1fdaff43f2eb655", 647 | "0x7e64ae0eaad21b4fe1a5f16bba161679d9978178f5088a152aa2fe904945ec74", 648 | "0x1d658fca51d066969f3f771c5b984d03e830e81980dee305b311849182450981" 649 | ] 650 | }, 651 | "0x81D4cf8A3892557e4a1571e97b88f0351eF0dcFD": { 652 | "index": 108, 653 | "amount": "1517516000000000000000000", 654 | "proof": [ 655 | "0x2a6c130a52b4919384d48ac32e784cb7f65c9888db4056f85d77fe6d98eac4ec", 656 | "0x0990bcbdf4108201fbd4b767d362b30897fa0a654af21cd2657941e174857a53", 657 | "0x706b742b3f487f51969bd22883f43d631c077cb74696e9b06b0adb78a159b7bc", 658 | "0x44a9a71b20be4019dd4f5e6b689022783850a4d9e3d3ce986a55680f332a9256", 659 | "0x6d704f752c80875dd796c231215e7b7b7a6453ae11328cd21ee4a652aecb621a", 660 | "0x719b60f2a548c56f3157ad3143c2bc038f0637c39823e3737d0efa7aaca9646f", 661 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 662 | ] 663 | }, 664 | "0x825aE6fb177186e9551ab1CDd6D4aB10B22A0Dba": { 665 | "index": 39, 666 | "amount": "445640000000000000000000", 667 | "proof": [ 668 | "0xf22eaf614b1fb6bd6f392146b8984f8baa6037dbf9f7099913816510f3534a8b", 669 | "0xc5a261c0b67b8b4a771d4576627d6f719e38c1d0385ed44ea15d2a188c819d2e", 670 | "0x79aabe3c3b6d9a1842f28c36846e49f4d2192f4aa0c9ca2d567c61f0010fa91c", 671 | "0x7b5a09bd7e95215fcdefbd381494597556bc6be17fc6bb184cec3044716dcbb8", 672 | "0x0356c76cf9a5d9f793f361e7056ec4ea428f2d61eea3b98f35eb78ee858f3aa0", 673 | "0x1d658fca51d066969f3f771c5b984d03e830e81980dee305b311849182450981" 674 | ] 675 | }, 676 | "0x82bF03b118AC4B9Cf9A9D520B7c23128B68774aa": { 677 | "index": 14, 678 | "amount": "335796000000000000000000", 679 | "proof": [ 680 | "0x96c51265e95af02de93f672c4d8423c262615e06a8b65da630c83f1e085e5ff6", 681 | "0x4092f7fef2622168643e4bbbaad345ef463f30e3bbd19463677ff303400bb17d", 682 | "0xf5f6c4cd3c27ba0c8b707319b2e1ae4bfd7ad3efab6dcc6b92fe825012365b65", 683 | "0x3b2099a51483ee781a1f463943910bbf9283e6a91e7286c37444f8ab36fa9017", 684 | "0x74c3e30eeee252121a9ab017d2dd83663b2cadb68ba4e1f3f2a449aa96de14b4", 685 | "0x6fc24878e99262e915df7cd55e09dccbc4118d513db5e1db3c175149fdf02643", 686 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 687 | ] 688 | }, 689 | "0x84F9079a9A39ae80Cf9DEE65a0E316B1a897684A": { 690 | "index": 33, 691 | "amount": "78869000000000000000000", 692 | "proof": [ 693 | "0xe317455e9ff26accb56b03015762983e542ae7fe7bb439976b52066eff0789a6", 694 | "0xf2b921b0eb6a8c458b023391e2247b6ba40be908ffb3f5b5111f9749f23ffe72", 695 | "0xee43336d4892e70b1b7730094118d8343971d884c160f413e74b1b1e1c00bc83", 696 | "0xf895e65da62c1574529a2b41336e56bbdc29da4da4c501a6d1fdaff43f2eb655", 697 | "0x7e64ae0eaad21b4fe1a5f16bba161679d9978178f5088a152aa2fe904945ec74", 698 | "0x1d658fca51d066969f3f771c5b984d03e830e81980dee305b311849182450981" 699 | ] 700 | }, 701 | "0x8885b44032Cba8813e870dA237f0598D6C822424": { 702 | "index": 6, 703 | "amount": "90751000000000000000000", 704 | "proof": [ 705 | "0xa2e880291a0cac9897d4fef38f3bc6070740dc5cc8aa075920d4fcd5fdba1db2", 706 | "0x526179014b132adc05558f1db4a1062c4b0024c8fc2c907b09b9eeee62d908eb", 707 | "0xcd80a2bd85828aa0813e79288b1379f7fd8d103369ec434068931c1499e0b804", 708 | "0xe248ba3fa1e713310abb8dec1ab7a20d9057440c69a1cf672418ae08450649f7", 709 | "0x74c3e30eeee252121a9ab017d2dd83663b2cadb68ba4e1f3f2a449aa96de14b4", 710 | "0x6fc24878e99262e915df7cd55e09dccbc4118d513db5e1db3c175149fdf02643", 711 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 712 | ] 713 | }, 714 | "0x8e2A89fF2F45ed7f8C8506f846200D671e2f176f": { 715 | "index": 38, 716 | "amount": "9911404000000000000000000", 717 | "proof": [ 718 | "0xad791afb4e1f4c5d5b8f2d38799d32e9064019ff443c9e4729550bc3a4d8f2b7", 719 | "0x1ff7c8fc48456faa4290601aa035a1df3a71d3289bc173eedb5ce820c4618172", 720 | "0xcd80a2bd85828aa0813e79288b1379f7fd8d103369ec434068931c1499e0b804", 721 | "0xe248ba3fa1e713310abb8dec1ab7a20d9057440c69a1cf672418ae08450649f7", 722 | "0x74c3e30eeee252121a9ab017d2dd83663b2cadb68ba4e1f3f2a449aa96de14b4", 723 | "0x6fc24878e99262e915df7cd55e09dccbc4118d513db5e1db3c175149fdf02643", 724 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 725 | ] 726 | }, 727 | "0x8efE50a4b8c8B64FDC93a5b1712Fd735d8223cD5": { 728 | "index": 42, 729 | "amount": "684205000000000000000000", 730 | "proof": [ 731 | "0x8f0150e910aba7a7a5bfccf811154c8cc39bc7c9efa6c6a694bdc74a28deaf63", 732 | "0x4f6470c5176ca2d7d23529abbcc0b8cf44921b7322f60b36194db0adf9ed6834", 733 | "0x917f91343cc20f4569bea2a259bb3981990cfd71c437e050a2ca7212ad69f7ba", 734 | "0x8984927efebda59207d4897115788045e902f9e8f56c85e2452e67a361f051e2", 735 | "0xb34fddba2171999f0c79d9cd6b679a218e8a0ee144874e3f4acb0c6a0bece1a4", 736 | "0x6fc24878e99262e915df7cd55e09dccbc4118d513db5e1db3c175149fdf02643", 737 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 738 | ] 739 | }, 740 | "0x945D28dF9779795BDab5BC6FDD9f36c0Ef1F7190": { 741 | "index": 80, 742 | "amount": "725608000000000000000000", 743 | "proof": [ 744 | "0x11e71873f3f7582d43969f308746f19d9297a73ff2f37228139f22f299bf83f8", 745 | "0xb17a36194c66404b0cb824825cf99df5ca2ce75330e924addd078e9c98d3070e", 746 | "0x80bc22cc22ba858ff146bd0f4ec9b3f8289393e581b0587088b2dcd908e0d572", 747 | "0x86cf09d7574cac8d269dd7fa1ab326e18a463bbae0d8d31cc5f599147d0dcd09", 748 | "0xc8567efa56c5077c54d5c6c9ef095f44260b4037f776efef493667db87582e34", 749 | "0x719b60f2a548c56f3157ad3143c2bc038f0637c39823e3737d0efa7aaca9646f", 750 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 751 | ] 752 | }, 753 | "0x94e0c714FaD746AC9e220E1B9396771C1FA7A236": { 754 | "index": 82, 755 | "amount": "74408000000000000000000", 756 | "proof": [ 757 | "0x6bb0e08c97b9b9a3d35b2d1e44971a76595887b8f8fe6ed454199f9479d33f97", 758 | "0xf1d9b7a7301791a4216e3c2130c359bbd7b50d240fc2646d03fb3176a425883e", 759 | "0x63d2bccadd3e6ec9cc9ccf225923f36ef4d256f07ed740ae3baabcc2edce0ef3", 760 | "0xa5f4cc10324f5eb27f3533cd177e62a313b21ffe7980e5d257af1c8303ab7897", 761 | "0xb34fddba2171999f0c79d9cd6b679a218e8a0ee144874e3f4acb0c6a0bece1a4", 762 | "0x6fc24878e99262e915df7cd55e09dccbc4118d513db5e1db3c175149fdf02643", 763 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 764 | ] 765 | }, 766 | "0x9E4A6Ef535B28bbC947E4E75644dDC9b2C114ee6": { 767 | "index": 24, 768 | "amount": "350313000000000000000000", 769 | "proof": [ 770 | "0x038fa8414c1778a5c2a86507d789af05e3b9ceecdf2ea49ebf778ab0499bbc29", 771 | "0xe3b24386a8949a8b14e0afa234a9ba2ae6e4f2df1ba7e68f6add15040ab95599", 772 | "0x4780e7cf776f4b6802159462f760558db6e16a518c587cb05fdf43eae395a349", 773 | "0x74be56af0a163b4dc10c22f6e56cc1392c6ed354fd8ed3cc7a0d855f70ea0d55", 774 | "0xc8567efa56c5077c54d5c6c9ef095f44260b4037f776efef493667db87582e34", 775 | "0x719b60f2a548c56f3157ad3143c2bc038f0637c39823e3737d0efa7aaca9646f", 776 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 777 | ] 778 | }, 779 | "0xA5BF5254FaAe168231140e139db98F79B65503C8": { 780 | "index": 106, 781 | "amount": "13873000000000000000000", 782 | "proof": [ 783 | "0x8c41445ce443d5e873cc27976602af309796a9f6dfdba874ca7619a6e4e40388", 784 | "0x4f6470c5176ca2d7d23529abbcc0b8cf44921b7322f60b36194db0adf9ed6834", 785 | "0x917f91343cc20f4569bea2a259bb3981990cfd71c437e050a2ca7212ad69f7ba", 786 | "0x8984927efebda59207d4897115788045e902f9e8f56c85e2452e67a361f051e2", 787 | "0xb34fddba2171999f0c79d9cd6b679a218e8a0ee144874e3f4acb0c6a0bece1a4", 788 | "0x6fc24878e99262e915df7cd55e09dccbc4118d513db5e1db3c175149fdf02643", 789 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 790 | ] 791 | }, 792 | "0xABb99042FAE2C86b8437585467a24a71b17d23bA": { 793 | "index": 35, 794 | "amount": "1262229000000000000000000", 795 | "proof": [ 796 | "0xf926893b9614297f6b9d1217b05c7d2052b3ff5371b345838966589728d90571", 797 | "0xea6a97ceaecd09bd23a616206726102e85789fb93ab44b632a795684b1e2bd12", 798 | "0xff777aab69fb43586c9eb3391f5f8288571f28ac065d61993bad6bcb22cb5783", 799 | "0xa1695fecfa61649bdfc483dc2813a82e5a854ad2416a34c6659fac30b72a71bf", 800 | "0x0356c76cf9a5d9f793f361e7056ec4ea428f2d61eea3b98f35eb78ee858f3aa0", 801 | "0x1d658fca51d066969f3f771c5b984d03e830e81980dee305b311849182450981" 802 | ] 803 | }, 804 | "0xAdBb28C2FEe078440B7088bbcd68DCfA63e55625": { 805 | "index": 104, 806 | "amount": "196753000000000000000000", 807 | "proof": [ 808 | "0x04930732eb1359c615bd58507ad28c06ea3f567526651555532a2ae2e025ed3d", 809 | "0x3fd94887d7207b83831629443c79021e03acff880a82e6d59e6ced7e6dd7eb22", 810 | "0x4eea6795dcb82009bbe0cf790589b848b0d73a2bc5e5f68131d504fed3f91aaf", 811 | "0x74be56af0a163b4dc10c22f6e56cc1392c6ed354fd8ed3cc7a0d855f70ea0d55", 812 | "0xc8567efa56c5077c54d5c6c9ef095f44260b4037f776efef493667db87582e34", 813 | "0x719b60f2a548c56f3157ad3143c2bc038f0637c39823e3737d0efa7aaca9646f", 814 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 815 | ] 816 | }, 817 | "0xAe4281d74056F3975941b66daf324bB893279cF0": { 818 | "index": 64, 819 | "amount": "73981000000000000000000", 820 | "proof": [ 821 | "0x0ee123b015ab3ab511168537e7f25573082b6be6236d223888856f3b35051012", 822 | "0x96cf7c0280030a29c74430b0ed5dbab0e415f640af21bf2be7db073debfd92b8", 823 | "0xaf5e98775d654397a20e5aa8bc24f07ef168e5e92246b5c0e57e0ab78e6856fd", 824 | "0x86cf09d7574cac8d269dd7fa1ab326e18a463bbae0d8d31cc5f599147d0dcd09", 825 | "0xc8567efa56c5077c54d5c6c9ef095f44260b4037f776efef493667db87582e34", 826 | "0x719b60f2a548c56f3157ad3143c2bc038f0637c39823e3737d0efa7aaca9646f", 827 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 828 | ] 829 | }, 830 | "0xAfD5f60aA8eb4F488eAA0eF98c1C5B0645D9A0A0": { 831 | "index": 5, 832 | "amount": "302216000000000000000000", 833 | "proof": [ 834 | "0xcff774783f68fb049341f5deb5329932098b21481c6ac5824853908066e5bb62", 835 | "0x52f16150d311628f85775c0bf1d3b0f893b20dca55c8d01b54ecb8fe2e2e4a18", 836 | "0xf2dd0e857df142c7952905d8ee29f05832d7db07c84d4146740cbcb94bef893c", 837 | "0xacfc1cb78689f5ebd2a640b44b29afc78a963f40fc35cfda7f9acfd0f6a1e141", 838 | "0x7e64ae0eaad21b4fe1a5f16bba161679d9978178f5088a152aa2fe904945ec74", 839 | "0x1d658fca51d066969f3f771c5b984d03e830e81980dee305b311849182450981" 840 | ] 841 | }, 842 | "0xB0b63f022275234F9821f2e92A9d4ae5BCDDB4F5": { 843 | "index": 7, 844 | "amount": "436535000000000000000000", 845 | "proof": [ 846 | "0xf83238b3aa5df85389cdc7dcf7cd9426a57bdea736f3a270fd9b49a8e30ab743", 847 | "0xc5a261c0b67b8b4a771d4576627d6f719e38c1d0385ed44ea15d2a188c819d2e", 848 | "0x79aabe3c3b6d9a1842f28c36846e49f4d2192f4aa0c9ca2d567c61f0010fa91c", 849 | "0x7b5a09bd7e95215fcdefbd381494597556bc6be17fc6bb184cec3044716dcbb8", 850 | "0x0356c76cf9a5d9f793f361e7056ec4ea428f2d61eea3b98f35eb78ee858f3aa0", 851 | "0x1d658fca51d066969f3f771c5b984d03e830e81980dee305b311849182450981" 852 | ] 853 | }, 854 | "0xBCa294543b46fF11CFAFbCF67434BEa23c21404b": { 855 | "index": 50, 856 | "amount": "993615000000000000000000", 857 | "proof": [ 858 | "0x6a5b41e05ac9c48269eca6236e9f5c9d63830c8a581f9ee896379568e4be6c05", 859 | "0xe58678c38b82be470686030b6b211dd6628244808f8e0034d82292a354c78d76", 860 | "0x63d2bccadd3e6ec9cc9ccf225923f36ef4d256f07ed740ae3baabcc2edce0ef3", 861 | "0xa5f4cc10324f5eb27f3533cd177e62a313b21ffe7980e5d257af1c8303ab7897", 862 | "0xb34fddba2171999f0c79d9cd6b679a218e8a0ee144874e3f4acb0c6a0bece1a4", 863 | "0x6fc24878e99262e915df7cd55e09dccbc4118d513db5e1db3c175149fdf02643", 864 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 865 | ] 866 | }, 867 | "0xBaf2a684c27133D094e810B6652e9d23F56F07CA": { 868 | "index": 124, 869 | "amount": "873069000000000000000000", 870 | "proof": [ 871 | "0x1ec16274aed6ce35dfe0364e58f71c8a7fd1e08ccfd685e7a929544d88a45bb0", 872 | "0x12fff11838a2215309720b11d4cac3c9099a11b3557775623a153d2a51f07761", 873 | "0x225df9eb2528cd19e8c498af4f90e591fc131d96800f7bcba58c69a7db8ec1c7", 874 | "0x44a9a71b20be4019dd4f5e6b689022783850a4d9e3d3ce986a55680f332a9256", 875 | "0x6d704f752c80875dd796c231215e7b7b7a6453ae11328cd21ee4a652aecb621a", 876 | "0x719b60f2a548c56f3157ad3143c2bc038f0637c39823e3737d0efa7aaca9646f", 877 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 878 | ] 879 | }, 880 | "0xBb46360a467da8b1c548a28C77dAb44758ab471c": { 881 | "index": 36, 882 | "amount": "434375000000000000000000", 883 | "proof": [ 884 | "0x33e631bc132bf561debd071007293b6563755bd15cc8c52bd947a809719a5a49", 885 | "0xb1b21897f0036f2914067da4c4a40393df43c1b6c8a3f33416cb1120989894bb", 886 | "0x779b84ce154bc70b115fd2f38634267219129635f0ce2db7797bebb114934074", 887 | "0x9a538600ae45ff5358943bbf26b3c947617c105d95626220425119a3df5c55b0", 888 | "0x6d704f752c80875dd796c231215e7b7b7a6453ae11328cd21ee4a652aecb621a", 889 | "0x719b60f2a548c56f3157ad3143c2bc038f0637c39823e3737d0efa7aaca9646f", 890 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 891 | ] 892 | }, 893 | "0xC59f50aC5C90Ac8AFb9936daB81fe9Dad20808fC": { 894 | "index": 18, 895 | "amount": "72944000000000000000000", 896 | "proof": [ 897 | "0x6f5a853bd0a55994c3197f1f46d4914c2e969a435907a5beb4ba8a204e8f300f", 898 | "0xf1d9b7a7301791a4216e3c2130c359bbd7b50d240fc2646d03fb3176a425883e", 899 | "0x63d2bccadd3e6ec9cc9ccf225923f36ef4d256f07ed740ae3baabcc2edce0ef3", 900 | "0xa5f4cc10324f5eb27f3533cd177e62a313b21ffe7980e5d257af1c8303ab7897", 901 | "0xb34fddba2171999f0c79d9cd6b679a218e8a0ee144874e3f4acb0c6a0bece1a4", 902 | "0x6fc24878e99262e915df7cd55e09dccbc4118d513db5e1db3c175149fdf02643", 903 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 904 | ] 905 | }, 906 | "0xD5388291EAbe96b56069440C97046791E2F72573": { 907 | "index": 122, 908 | "amount": "3982590000000000000000000", 909 | "proof": [ 910 | "0x75b339a85aa6cf11f20390f51baf11e29c1101ed63d48c3c35933d5f08225cdc", 911 | "0x047675f1aa0ebc69ef9b16fe7d32933fba86515694301b2508ace078c02a6cc4", 912 | "0x03d6e42699ae096353af3bbfc82fa0a7cdddb19aa22e6ea2305237e50087062b", 913 | "0x8984927efebda59207d4897115788045e902f9e8f56c85e2452e67a361f051e2", 914 | "0xb34fddba2171999f0c79d9cd6b679a218e8a0ee144874e3f4acb0c6a0bece1a4", 915 | "0x6fc24878e99262e915df7cd55e09dccbc4118d513db5e1db3c175149fdf02643", 916 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 917 | ] 918 | }, 919 | "0xD6FCD2F85fE975bB9b0f3C1B1c6802bB09d33E43": { 920 | "index": 72, 921 | "amount": "205852000000000000000000", 922 | "proof": [ 923 | "0x063dc5915c0e85e5435fef020f988b123a59546e3c203720feab29d8ecfaa172", 924 | "0xc0a13f6970c0aa618e3ae8f11a07b02a85c6774f18c6758896a37eae56655def", 925 | "0x4eea6795dcb82009bbe0cf790589b848b0d73a2bc5e5f68131d504fed3f91aaf", 926 | "0x74be56af0a163b4dc10c22f6e56cc1392c6ed354fd8ed3cc7a0d855f70ea0d55", 927 | "0xc8567efa56c5077c54d5c6c9ef095f44260b4037f776efef493667db87582e34", 928 | "0x719b60f2a548c56f3157ad3143c2bc038f0637c39823e3737d0efa7aaca9646f", 929 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 930 | ] 931 | }, 932 | "0xDA3Cf9e341A4C7ecf41070b074EaBDC5D433f159": { 933 | "index": 102, 934 | "amount": "59882000000000000000000", 935 | "proof": [ 936 | "0xac742fb78ec99f5cfd3a21a7e1a0fc51ca2249467a1598654396951b6bc44af1", 937 | "0x1ff7c8fc48456faa4290601aa035a1df3a71d3289bc173eedb5ce820c4618172", 938 | "0xcd80a2bd85828aa0813e79288b1379f7fd8d103369ec434068931c1499e0b804", 939 | "0xe248ba3fa1e713310abb8dec1ab7a20d9057440c69a1cf672418ae08450649f7", 940 | "0x74c3e30eeee252121a9ab017d2dd83663b2cadb68ba4e1f3f2a449aa96de14b4", 941 | "0x6fc24878e99262e915df7cd55e09dccbc4118d513db5e1db3c175149fdf02643", 942 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 943 | ] 944 | }, 945 | "0xDaA39d73eF9b4495bfe206FF13eE29E759F95d63": { 946 | "index": 116, 947 | "amount": "121030000000000000000000", 948 | "proof": [ 949 | "0x48223e220ea5f86e5ae7acfbf5b61d2553c6f1a9364b495d6176b416c13607b3", 950 | "0xcfa78f03015784a7bd1e3ac2248ca1dabbbb36d06aadf04c1e2ce6a26b550129", 951 | "0x6c2fc5225c59673c99b767ee3f9a63d31e0118be6cf225866f92a627babebc6e", 952 | "0x9a538600ae45ff5358943bbf26b3c947617c105d95626220425119a3df5c55b0", 953 | "0x6d704f752c80875dd796c231215e7b7b7a6453ae11328cd21ee4a652aecb621a", 954 | "0x719b60f2a548c56f3157ad3143c2bc038f0637c39823e3737d0efa7aaca9646f", 955 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 956 | ] 957 | }, 958 | "0xE16584424F34380DBf798f547Af684597788FbC7": { 959 | "index": 96, 960 | "amount": "638803000000000000000000", 961 | "proof": [ 962 | "0x09801897c716185d82c29e40611e381f922c75f7c3e6e1c265ffed5c8e300d0b", 963 | "0x36cd3c692f034d98f84bd30ca2d39de2ced063d6e5f625660097ba72e256f67c", 964 | "0xaf5e98775d654397a20e5aa8bc24f07ef168e5e92246b5c0e57e0ab78e6856fd", 965 | "0x86cf09d7574cac8d269dd7fa1ab326e18a463bbae0d8d31cc5f599147d0dcd09", 966 | "0xc8567efa56c5077c54d5c6c9ef095f44260b4037f776efef493667db87582e34", 967 | "0x719b60f2a548c56f3157ad3143c2bc038f0637c39823e3737d0efa7aaca9646f", 968 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 969 | ] 970 | }, 971 | "0xE400820f3D60d77a3EC8018d44366ed0d334f93C": { 972 | "index": 25, 973 | "amount": "537273000000000000000000", 974 | "proof": [ 975 | "0xd5d2c0a431246f8f1feb01c6766a10e24e0cccf2d1008924336d8fa252ba5b86", 976 | "0xae5de8dcce21685f54f719653570e2773ac6ac29b12d7e738b65d56e203651c8", 977 | "0x5ad1882ec43a028a5e2fe113850e19ffa460edd404c0765799b8cdabc943fb71", 978 | "0xf895e65da62c1574529a2b41336e56bbdc29da4da4c501a6d1fdaff43f2eb655", 979 | "0x7e64ae0eaad21b4fe1a5f16bba161679d9978178f5088a152aa2fe904945ec74", 980 | "0x1d658fca51d066969f3f771c5b984d03e830e81980dee305b311849182450981" 981 | ] 982 | }, 983 | "0xEBB014649852462f09489E3DFbe45D9759E9b819": { 984 | "index": 3, 985 | "amount": "1809721000000000000000000", 986 | "proof": [ 987 | "0xf95101f5fb0dd75bde7d70567fef30335070e2a8b52711994853792d242a5c1a", 988 | "0xea6a97ceaecd09bd23a616206726102e85789fb93ab44b632a795684b1e2bd12", 989 | "0xff777aab69fb43586c9eb3391f5f8288571f28ac065d61993bad6bcb22cb5783", 990 | "0xa1695fecfa61649bdfc483dc2813a82e5a854ad2416a34c6659fac30b72a71bf", 991 | "0x0356c76cf9a5d9f793f361e7056ec4ea428f2d61eea3b98f35eb78ee858f3aa0", 992 | "0x1d658fca51d066969f3f771c5b984d03e830e81980dee305b311849182450981" 993 | ] 994 | }, 995 | "0xEC6f7607cD7E4C942a75d40C269deC01BBc9A15e": { 996 | "index": 45, 997 | "amount": "75000000000000000000000000", 998 | "proof": [ 999 | "0xc7583603533e70cc7372ee501967853c816d74a8da15a8b7766cbe419073edb9", 1000 | "0xdc90814b38d75ba65ee14947d9c575c76bbbc5a9b2adc4c8edf445bf10880202", 1001 | "0x3dbbdc8f762b91d1e5bf6a83e72f0f003bfdb56945d256044e03220b5c35b294", 1002 | "0xacfc1cb78689f5ebd2a640b44b29afc78a963f40fc35cfda7f9acfd0f6a1e141", 1003 | "0x7e64ae0eaad21b4fe1a5f16bba161679d9978178f5088a152aa2fe904945ec74", 1004 | "0x1d658fca51d066969f3f771c5b984d03e830e81980dee305b311849182450981" 1005 | ] 1006 | }, 1007 | "0xF4F56fA0D045ae0e6Ba8f82e2C32887FE0B152Ea": { 1008 | "index": 98, 1009 | "amount": "62903000000000000000000", 1010 | "proof": [ 1011 | "0x4fe75fec328495667692e4fd440b4783ed28752f8c1d4e5616733bf8d5fa6b61", 1012 | "0xf7acb7f151b8c15c2e3a6402bb5d0c34dabaa1925f03533cd0d2be7477f8bbf6", 1013 | "0x6e55da19895a764b49d26711ef8f0ed4f41be319741e0e5f1a719430c6f77898", 1014 | "0xa5f4cc10324f5eb27f3533cd177e62a313b21ffe7980e5d257af1c8303ab7897", 1015 | "0xb34fddba2171999f0c79d9cd6b679a218e8a0ee144874e3f4acb0c6a0bece1a4", 1016 | "0x6fc24878e99262e915df7cd55e09dccbc4118d513db5e1db3c175149fdf02643", 1017 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 1018 | ] 1019 | }, 1020 | "0xFDf01E09b9bd3f3bE062a3085fa2c945a263F5a7": { 1021 | "index": 47, 1022 | "amount": "191364000000000000000000", 1023 | "proof": [ 1024 | "0xe4eb9f6e394f5f27a9021868fac94c23a25a8063b3994c8e32b39eb2505728c3", 1025 | "0xd329db1c4ca1ef811ea6b45b20ea7ac177f1dba6ae34803d5ff671b27b8f54aa", 1026 | "0x7403ff8aecae90a4479842f55c1562922cc787bd169b2bcf485fa0718e91e2a9", 1027 | "0x7b5a09bd7e95215fcdefbd381494597556bc6be17fc6bb184cec3044716dcbb8", 1028 | "0x0356c76cf9a5d9f793f361e7056ec4ea428f2d61eea3b98f35eb78ee858f3aa0", 1029 | "0x1d658fca51d066969f3f771c5b984d03e830e81980dee305b311849182450981" 1030 | ] 1031 | }, 1032 | "0xa360E38cdf57e092218E7A5f4Eb73032eE74A8bD": { 1033 | "index": 40, 1034 | "amount": "50763000000000000000000", 1035 | "proof": [ 1036 | "0x0523c620a71cfa795183c9a1880de06db839cb3225b562388529efe01a8abb28", 1037 | "0x3fd94887d7207b83831629443c79021e03acff880a82e6d59e6ced7e6dd7eb22", 1038 | "0x4eea6795dcb82009bbe0cf790589b848b0d73a2bc5e5f68131d504fed3f91aaf", 1039 | "0x74be56af0a163b4dc10c22f6e56cc1392c6ed354fd8ed3cc7a0d855f70ea0d55", 1040 | "0xc8567efa56c5077c54d5c6c9ef095f44260b4037f776efef493667db87582e34", 1041 | "0x719b60f2a548c56f3157ad3143c2bc038f0637c39823e3737d0efa7aaca9646f", 1042 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 1043 | ] 1044 | }, 1045 | "0xacD628D01dd8534Db6Ebe4894C1be3c8D34ebe14": { 1046 | "index": 31, 1047 | "amount": "604433000000000000000000", 1048 | "proof": [ 1049 | "0xe9855dd8c3871120b785f6e42e8ea47ab33119c58d1a8e095a40774eaf9f4308", 1050 | "0x0a4175eb4ca083fabe049ad5e1bcf39c0c003f1619a1657c0f924b05378b3d15", 1051 | "0x7403ff8aecae90a4479842f55c1562922cc787bd169b2bcf485fa0718e91e2a9", 1052 | "0x7b5a09bd7e95215fcdefbd381494597556bc6be17fc6bb184cec3044716dcbb8", 1053 | "0x0356c76cf9a5d9f793f361e7056ec4ea428f2d61eea3b98f35eb78ee858f3aa0", 1054 | "0x1d658fca51d066969f3f771c5b984d03e830e81980dee305b311849182450981" 1055 | ] 1056 | }, 1057 | "0xb084F0AdB86BF6B001E540Ac05439D98A499ce1D": { 1058 | "index": 48, 1059 | "amount": "165163000000000000000000", 1060 | "proof": [ 1061 | "0x1c2a589d0281295e71fa9b5529e60cad2ae6b2637ac509fa33b026d493fa9b79", 1062 | "0x41de793dafe41eb57d1fa78db611d3b810bba68e5f558a877c9063377d637309", 1063 | "0x80bc22cc22ba858ff146bd0f4ec9b3f8289393e581b0587088b2dcd908e0d572", 1064 | "0x86cf09d7574cac8d269dd7fa1ab326e18a463bbae0d8d31cc5f599147d0dcd09", 1065 | "0xc8567efa56c5077c54d5c6c9ef095f44260b4037f776efef493667db87582e34", 1066 | "0x719b60f2a548c56f3157ad3143c2bc038f0637c39823e3737d0efa7aaca9646f", 1067 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 1068 | ] 1069 | }, 1070 | "0xd0C81E82AbDdF29C6505d660f5bEBe60CDFf03c5": { 1071 | "index": 4, 1072 | "amount": "220121000000000000000000", 1073 | "proof": [ 1074 | "0x33016782b33e5c20dfc6acec40135be9dcd936e94096f8df5027b74c54ac5ca9", 1075 | "0xf0713f9cbcc51e4bed98b52c32257900d9c6ce76da867a865763c6263c30cce9", 1076 | "0x779b84ce154bc70b115fd2f38634267219129635f0ce2db7797bebb114934074", 1077 | "0x9a538600ae45ff5358943bbf26b3c947617c105d95626220425119a3df5c55b0", 1078 | "0x6d704f752c80875dd796c231215e7b7b7a6453ae11328cd21ee4a652aecb621a", 1079 | "0x719b60f2a548c56f3157ad3143c2bc038f0637c39823e3737d0efa7aaca9646f", 1080 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 1081 | ] 1082 | }, 1083 | "0xd295584135829D89427674B72560222B06E08a15": { 1084 | "index": 11, 1085 | "amount": "339751000000000000000000", 1086 | "proof": [ 1087 | "0x3d4d8808bf469724198d43356f9119200067c39a15e697876e39c2ba67742ce3", 1088 | "0xa1695fecfa61649bdfc483dc2813a82e5a854ad2416a34c6659fac30b72a71bf", 1089 | "0x0356c76cf9a5d9f793f361e7056ec4ea428f2d61eea3b98f35eb78ee858f3aa0", 1090 | "0x1d658fca51d066969f3f771c5b984d03e830e81980dee305b311849182450981" 1091 | ] 1092 | }, 1093 | "0xd685Dd21BD58Ff6976f14C4E7F847a2Da0047965": { 1094 | "index": 26, 1095 | "amount": "133331000000000000000000", 1096 | "proof": [ 1097 | "0x73ee18d3f2e837fba6478cf239e05d453e2cad5de2444f8591262757d3dfc280", 1098 | "0x1ec5322ab6873d1d5dfb51ecb37a2062cb93985cec8572ff53dbc745c960d1a1", 1099 | "0x03d6e42699ae096353af3bbfc82fa0a7cdddb19aa22e6ea2305237e50087062b", 1100 | "0x8984927efebda59207d4897115788045e902f9e8f56c85e2452e67a361f051e2", 1101 | "0xb34fddba2171999f0c79d9cd6b679a218e8a0ee144874e3f4acb0c6a0bece1a4", 1102 | "0x6fc24878e99262e915df7cd55e09dccbc4118d513db5e1db3c175149fdf02643", 1103 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 1104 | ] 1105 | }, 1106 | "0xe88cAc4e10C4D316E0d52B82dd54f26ade3f0Bb2": { 1107 | "index": 114, 1108 | "amount": "701064000000000000000000", 1109 | "proof": [ 1110 | "0x659efcd02b5b16846a2e880b4a797321cdedda289eecca00e17acee990e52a18", 1111 | "0xe58678c38b82be470686030b6b211dd6628244808f8e0034d82292a354c78d76", 1112 | "0x63d2bccadd3e6ec9cc9ccf225923f36ef4d256f07ed740ae3baabcc2edce0ef3", 1113 | "0xa5f4cc10324f5eb27f3533cd177e62a313b21ffe7980e5d257af1c8303ab7897", 1114 | "0xb34fddba2171999f0c79d9cd6b679a218e8a0ee144874e3f4acb0c6a0bece1a4", 1115 | "0x6fc24878e99262e915df7cd55e09dccbc4118d513db5e1db3c175149fdf02643", 1116 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 1117 | ] 1118 | }, 1119 | "0xeFBFe9c6a66d871175d2B0ab35bc00255Aa36298": { 1120 | "index": 46, 1121 | "amount": "636271000000000000000000", 1122 | "proof": [ 1123 | "0x9822495ddaa9d76bf2825d5b7cf1d4d53dc773da0ecb96ff48c373c407e5f207", 1124 | "0x1b360fd951febd44513ae408d579b527310356a3ec146bbd29d961a95baa189f", 1125 | "0xf5f6c4cd3c27ba0c8b707319b2e1ae4bfd7ad3efab6dcc6b92fe825012365b65", 1126 | "0x3b2099a51483ee781a1f463943910bbf9283e6a91e7286c37444f8ab36fa9017", 1127 | "0x74c3e30eeee252121a9ab017d2dd83663b2cadb68ba4e1f3f2a449aa96de14b4", 1128 | "0x6fc24878e99262e915df7cd55e09dccbc4118d513db5e1db3c175149fdf02643", 1129 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 1130 | ] 1131 | }, 1132 | "0xf048819F1190E911b5a78C7b204dD3a1F5f0e509": { 1133 | "index": 84, 1134 | "amount": "335796000000000000000000", 1135 | "proof": [ 1136 | "0x376d0e9c8168cf180136f653a4a29beb9610a178546c3bb78a16cd3e2690865f", 1137 | "0xe57d35f66cc55e051648507e1ce1c00be1b0f57e6804519fe38f02254eca5ba0", 1138 | "0x6c2fc5225c59673c99b767ee3f9a63d31e0118be6cf225866f92a627babebc6e", 1139 | "0x9a538600ae45ff5358943bbf26b3c947617c105d95626220425119a3df5c55b0", 1140 | "0x6d704f752c80875dd796c231215e7b7b7a6453ae11328cd21ee4a652aecb621a", 1141 | "0x719b60f2a548c56f3157ad3143c2bc038f0637c39823e3737d0efa7aaca9646f", 1142 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 1143 | ] 1144 | }, 1145 | "0xf4519d24f8B2cc80c41ad6800CE0F27210848658": { 1146 | "index": 19, 1147 | "amount": "580586000000000000000000", 1148 | "proof": [ 1149 | "0xfb71d0ecadffee57dad1415ea8e777fb11c2202c3ad8e0c8f7c34b5cc88e6963", 1150 | "0x5f6c7b5cdeb706bf33a314dd648c11a611996e62d2ac02f4448f4cb6c0d07795", 1151 | "0xff777aab69fb43586c9eb3391f5f8288571f28ac065d61993bad6bcb22cb5783", 1152 | "0xa1695fecfa61649bdfc483dc2813a82e5a854ad2416a34c6659fac30b72a71bf", 1153 | "0x0356c76cf9a5d9f793f361e7056ec4ea428f2d61eea3b98f35eb78ee858f3aa0", 1154 | "0x1d658fca51d066969f3f771c5b984d03e830e81980dee305b311849182450981" 1155 | ] 1156 | }, 1157 | "0xf944069B489F1ebfF4C3C6a6014d58cBEf7C7009": { 1158 | "index": 92, 1159 | "amount": "335796000000000000000000", 1160 | "proof": [ 1161 | "0x249c1fef69bef412351cfdf2212bae038e93b3970dc0b3e76736278cf45720ee", 1162 | "0xc5ba3432397ec06e3352e775db90fe103c8ebc7f95daeebfc74648aa363ecdc3", 1163 | "0x225df9eb2528cd19e8c498af4f90e591fc131d96800f7bcba58c69a7db8ec1c7", 1164 | "0x44a9a71b20be4019dd4f5e6b689022783850a4d9e3d3ce986a55680f332a9256", 1165 | "0x6d704f752c80875dd796c231215e7b7b7a6453ae11328cd21ee4a652aecb621a", 1166 | "0x719b60f2a548c56f3157ad3143c2bc038f0637c39823e3737d0efa7aaca9646f", 1167 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 1168 | ] 1169 | }, 1170 | "0xfa2Df13a636b8F2FAaA179785797f1f16fDdF8ED": { 1171 | "index": 34, 1172 | "amount": "81104000000000000000000", 1173 | "proof": [ 1174 | "0x54221d75cecce3d45f4ba66e0ce602eee27e0949d36a1ab9d6f44ba16aa5d081", 1175 | "0xf7acb7f151b8c15c2e3a6402bb5d0c34dabaa1925f03533cd0d2be7477f8bbf6", 1176 | "0x6e55da19895a764b49d26711ef8f0ed4f41be319741e0e5f1a719430c6f77898", 1177 | "0xa5f4cc10324f5eb27f3533cd177e62a313b21ffe7980e5d257af1c8303ab7897", 1178 | "0xb34fddba2171999f0c79d9cd6b679a218e8a0ee144874e3f4acb0c6a0bece1a4", 1179 | "0x6fc24878e99262e915df7cd55e09dccbc4118d513db5e1db3c175149fdf02643", 1180 | "0x93d109bff0ff2259f6b2465f5c6e5261b0b3b50d3f15edb7ff35031dbd504621" 1181 | ] 1182 | } 1183 | } 1184 | } 1185 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2018", 4 | "module": "commonjs", 5 | "strict": true, 6 | "esModuleInterop": true, 7 | "outDir": "dist", 8 | "declaration": true 9 | }, 10 | "include": ["./scripts", "./test", "./typechain", "shared"], 11 | "files": ["./hardhat.config.ts"] 12 | } 13 | --------------------------------------------------------------------------------