From 72659140659bcdc74c1fb2b948f0e6bf7340916b Mon Sep 17 00:00:00 2001 From: kemuru <102478601+kemuru@users.noreply.github.com> Date: Thu, 3 Apr 2025 02:39:22 +0200 Subject: [PATCH 1/2] feat: support for v2 rewards, do everything in safe --- contracts/deployments/kovan/.chainId | 1 - contracts/deployments/kovan/MerkleRedeem.json | 522 ------------------ snapshots/.env.example | 3 +- snapshots/cli.js | 108 ++-- snapshots/src/helpers/subgraph-events.js | 111 ++-- snapshots/src/helpers/tx-builder.js | 61 ++ 6 files changed, 198 insertions(+), 608 deletions(-) delete mode 100644 contracts/deployments/kovan/.chainId delete mode 100644 contracts/deployments/kovan/MerkleRedeem.json create mode 100644 snapshots/src/helpers/tx-builder.js diff --git a/contracts/deployments/kovan/.chainId b/contracts/deployments/kovan/.chainId deleted file mode 100644 index f70d7bb..0000000 --- a/contracts/deployments/kovan/.chainId +++ /dev/null @@ -1 +0,0 @@ -42 \ No newline at end of file diff --git a/contracts/deployments/kovan/MerkleRedeem.json b/contracts/deployments/kovan/MerkleRedeem.json deleted file mode 100644 index 4c0b9a6..0000000 --- a/contracts/deployments/kovan/MerkleRedeem.json +++ /dev/null @@ -1,522 +0,0 @@ -{ - "address": "0x193353d006Ab015216D34419a845989e76612475", - "abi": [ - { - "inputs": [ - { - "internalType": "address", - "name": "_token", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "_claimant", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "_balance", - "type": "uint256" - } - ], - "name": "Claimed", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "previousOwner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "OwnershipTransferred", - "type": "event" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_liquidityProvider", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_begin", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_end", - "type": "uint256" - } - ], - "name": "claimStatus", - "outputs": [ - { - "internalType": "bool[]", - "name": "", - "type": "bool[]" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_liquidityProvider", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_week", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_claimedBalance", - "type": "uint256" - }, - { - "internalType": "bytes32[]", - "name": "_merkleProof", - "type": "bytes32[]" - } - ], - "name": "claimWeek", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_liquidityProvider", - "type": "address" - }, - { - "components": [ - { - "internalType": "uint256", - "name": "week", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "balance", - "type": "uint256" - }, - { - "internalType": "bytes32[]", - "name": "merkleProof", - "type": "bytes32[]" - } - ], - "internalType": "struct MerkleRedeem.Claim[]", - "name": "claims", - "type": "tuple[]" - } - ], - "name": "claimWeeks", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - }, - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "claimed", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_begin", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_end", - "type": "uint256" - } - ], - "name": "merkleRoots", - "outputs": [ - { - "internalType": "bytes32[]", - "name": "", - "type": "bytes32[]" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "owner", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "renounceOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "_week", - "type": "uint256" - }, - { - "internalType": "bytes32", - "name": "_merkleRoot", - "type": "bytes32" - }, - { - "internalType": "uint256", - "name": "_totalAllocation", - "type": "uint256" - } - ], - "name": "seedAllocations", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "token", - "outputs": [ - { - "internalType": "contract IERC20", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "transferOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "_liquidityProvider", - "type": "address" - }, - { - "internalType": "uint256", - "name": "_week", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "_claimedBalance", - "type": "uint256" - }, - { - "internalType": "bytes32[]", - "name": "_merkleProof", - "type": "bytes32[]" - } - ], - "name": "verifyClaim", - "outputs": [ - { - "internalType": "bool", - "name": "valid", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "name": "weekMerkleRoots", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - } - ], - "transactionHash": "0xe65d70481b616c91b464b1fc1d3ca7d6ced1875e3bf4105fbfc6c0b8030d3336", - "receipt": { - "to": null, - "from": "0xceB4c079Dd21494E0bc99DA732EAdf220b727389", - "contractAddress": "0x193353d006Ab015216D34419a845989e76612475", - "transactionIndex": 4, - "gasUsed": "918674", - "logsBloom": "0x20000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000001000000000000000000000000000000000000020000000000000000000800000000000000000000000000000000400000000000000000000000000000000000000000800000000040000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000010000000000000000000000000000000000000000000000000000000", - "blockHash": "0x8baf634be5cca3dfd896d5cc50f9e35d7c3d228a5baee006bb6974871a95076f", - "transactionHash": "0xe65d70481b616c91b464b1fc1d3ca7d6ced1875e3bf4105fbfc6c0b8030d3336", - "logs": [ - { - "transactionIndex": 4, - "blockNumber": 23404724, - "transactionHash": "0xe65d70481b616c91b464b1fc1d3ca7d6ced1875e3bf4105fbfc6c0b8030d3336", - "address": "0x193353d006Ab015216D34419a845989e76612475", - "topics": [ - "0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0", - "0x0000000000000000000000000000000000000000000000000000000000000000", - "0x000000000000000000000000ceb4c079dd21494e0bc99da732eadf220b727389" - ], - "data": "0x", - "logIndex": 22, - "blockHash": "0x8baf634be5cca3dfd896d5cc50f9e35d7c3d228a5baee006bb6974871a95076f" - } - ], - "blockNumber": 23404724, - "cumulativeGasUsed": "1540403", - "status": 1, - "byzantium": true - }, - "args": [ - "0x1EE318dBC19267dBCE08F54A66ab198F73EdE356" - ], - "solcInputHash": "d4877a002975406a2453170cccec33dc", - "metadata": "{\"compiler\":{\"version\":\"0.6.8+commit.0bbfe453\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_token\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"_claimant\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"_balance\",\"type\":\"uint256\"}],\"name\":\"Claimed\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_liquidityProvider\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_begin\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_end\",\"type\":\"uint256\"}],\"name\":\"claimStatus\",\"outputs\":[{\"internalType\":\"bool[]\",\"name\":\"\",\"type\":\"bool[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_liquidityProvider\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_week\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_claimedBalance\",\"type\":\"uint256\"},{\"internalType\":\"bytes32[]\",\"name\":\"_merkleProof\",\"type\":\"bytes32[]\"}],\"name\":\"claimWeek\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_liquidityProvider\",\"type\":\"address\"},{\"components\":[{\"internalType\":\"uint256\",\"name\":\"week\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"balance\",\"type\":\"uint256\"},{\"internalType\":\"bytes32[]\",\"name\":\"merkleProof\",\"type\":\"bytes32[]\"}],\"internalType\":\"struct MerkleRedeem.Claim[]\",\"name\":\"claims\",\"type\":\"tuple[]\"}],\"name\":\"claimWeeks\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"claimed\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_begin\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_end\",\"type\":\"uint256\"}],\"name\":\"merkleRoots\",\"outputs\":[{\"internalType\":\"bytes32[]\",\"name\":\"\",\"type\":\"bytes32[]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_week\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"_merkleRoot\",\"type\":\"bytes32\"},{\"internalType\":\"uint256\",\"name\":\"_totalAllocation\",\"type\":\"uint256\"}],\"name\":\"seedAllocations\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"token\",\"outputs\":[{\"internalType\":\"contract IERC20\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_liquidityProvider\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_week\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"_claimedBalance\",\"type\":\"uint256\"},{\"internalType\":\"bytes32[]\",\"name\":\"_merkleProof\",\"type\":\"bytes32[]\"}],\"name\":\"verifyClaim\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"valid\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"weekMerkleRoots\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"methods\":{\"claimStatus(address,uint256,uint256)\":{\"params\":{\"_begin\":\"The week to start with (inclusive).\",\"_end\":\"The week to end with (inclusive).\",\"_liquidityProvider\":\"The address of the claimant.\"}},\"claimWeek(address,uint256,uint256,bytes32[])\":{\"params\":{\"_claimedBalance\":\"The amount being claimed.\",\"_liquidityProvider\":\"The address of the claimant.\",\"_merkleProof\":\"The merkle proof for the claim, sorted from the leaf to the root of the tree.\",\"_week\":\"The week for the claim.\"}},\"claimWeeks(address,(uint256,uint256,bytes32[])[])\":{\"params\":{\"_liquidityProvider\":\"The address of the claimant.\",\"claims\":\"An array of claims containing the week, balance and the merkle proof.\"}},\"constructor\":{\"params\":{\"_token\":\"The address of the token being distributed.\"}},\"merkleRoots(uint256,uint256)\":{\"params\":{\"_begin\":\"The week to start with (inclusive).\",\"_end\":\"The week to end with (inclusive).\"}},\"owner()\":{\"details\":\"Returns the address of the current owner.\"},\"renounceOwnership()\":{\"details\":\"Leaves the contract without owner. It will not be possible to call `onlyOwner` functions anymore. Can only be called by the current owner. * NOTE: Renouncing ownership will leave the contract without an owner, thereby removing any functionality that is only available to the owner.\"},\"seedAllocations(uint256,bytes32,uint256)\":{\"details\":\"Will transfer tokens from the owner to this contract.\",\"params\":{\"_merkleRoot\":\"The merkle root of the claims for that period.\",\"_totalAllocation\":\"The amount of tokens allocated for the distribution.\",\"_week\":\"The airdrop week.\"}},\"transferOwnership(address)\":{\"details\":\"Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner.\"},\"verifyClaim(address,uint256,uint256,bytes32[])\":{\"params\":{\"_claimedBalance\":\"The amount being claimed.\",\"_liquidityProvider\":\"The address of the claimant.\",\"_merkleProof\":\"The merkle proof for the claim, sorted from the leaf to the root of the tree.\",\"_week\":\"The week for the claim.\"}}},\"title\":\"Distribution of tokens in a recurrent fashion.\"},\"userdoc\":{\"methods\":{\"claimStatus(address,uint256,uint256)\":{\"notice\":\"Gets the claim status for given claimant from `_begin` to `_end` weeks.\"},\"claimWeek(address,uint256,uint256,bytes32[])\":{\"notice\":\"Makes a claim for a given claimant in a week.\"},\"claimWeeks(address,(uint256,uint256,bytes32[])[])\":{\"notice\":\"Makes multiple claims for a given claimant.\"},\"merkleRoots(uint256,uint256)\":{\"notice\":\"Gets all merkle roots for from `_begin` to `_end` weeks.\"},\"seedAllocations(uint256,bytes32,uint256)\":{\"notice\":\"Seeds a new round for the airdrop.\"},\"verifyClaim(address,uint256,uint256,bytes32[])\":{\"notice\":\"Verifies a claim.\"}}}},\"settings\":{\"compilationTarget\":{\"src/MerkleRedeem.sol\":\"MerkleRedeem\"},\"evmVersion\":\"istanbul\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\",\"useLiteralContent\":true},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"@openzeppelin/contracts/access/Ownable.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity >=0.6.0 <0.8.0;\\n\\nimport \\\"../utils/Context.sol\\\";\\n/**\\n * @dev Contract module which provides a basic access control mechanism, where\\n * there is an account (an owner) that can be granted exclusive access to\\n * specific functions.\\n *\\n * By default, the owner account will be the one that deploys the contract. This\\n * can later be changed with {transferOwnership}.\\n *\\n * This module is used through inheritance. It will make available the modifier\\n * `onlyOwner`, which can be applied to your functions to restrict their use to\\n * the owner.\\n */\\nabstract contract Ownable is Context {\\n address private _owner;\\n\\n event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);\\n\\n /**\\n * @dev Initializes the contract setting the deployer as the initial owner.\\n */\\n constructor () internal {\\n address msgSender = _msgSender();\\n _owner = msgSender;\\n emit OwnershipTransferred(address(0), msgSender);\\n }\\n\\n /**\\n * @dev Returns the address of the current owner.\\n */\\n function owner() public view virtual returns (address) {\\n return _owner;\\n }\\n\\n /**\\n * @dev Throws if called by any account other than the owner.\\n */\\n modifier onlyOwner() {\\n require(owner() == _msgSender(), \\\"Ownable: caller is not the owner\\\");\\n _;\\n }\\n\\n /**\\n * @dev Leaves the contract without owner. It will not be possible to call\\n * `onlyOwner` functions anymore. Can only be called by the current owner.\\n *\\n * NOTE: Renouncing ownership will leave the contract without an owner,\\n * thereby removing any functionality that is only available to the owner.\\n */\\n function renounceOwnership() public virtual onlyOwner {\\n emit OwnershipTransferred(_owner, address(0));\\n _owner = address(0);\\n }\\n\\n /**\\n * @dev Transfers ownership of the contract to a new account (`newOwner`).\\n * Can only be called by the current owner.\\n */\\n function transferOwnership(address newOwner) public virtual onlyOwner {\\n require(newOwner != address(0), \\\"Ownable: new owner is the zero address\\\");\\n emit OwnershipTransferred(_owner, newOwner);\\n _owner = newOwner;\\n }\\n}\\n\",\"keccak256\":\"0x15e2d5bd4c28a88548074c54d220e8086f638a71ed07e6b3ba5a70066fcf458d\",\"license\":\"MIT\"},\"@openzeppelin/contracts/cryptography/MerkleProof.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity >=0.6.0 <0.8.0;\\n\\n/**\\n * @dev These functions deal with verification of Merkle trees (hash trees),\\n */\\nlibrary MerkleProof {\\n /**\\n * @dev Returns true if a `leaf` can be proved to be a part of a Merkle tree\\n * defined by `root`. For this, a `proof` must be provided, containing\\n * sibling hashes on the branch from the leaf to the root of the tree. Each\\n * pair of leaves and each pair of pre-images are assumed to be sorted.\\n */\\n function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf) internal pure returns (bool) {\\n bytes32 computedHash = leaf;\\n\\n for (uint256 i = 0; i < proof.length; i++) {\\n bytes32 proofElement = proof[i];\\n\\n if (computedHash <= proofElement) {\\n // Hash(current computed hash + current element of the proof)\\n computedHash = keccak256(abi.encodePacked(computedHash, proofElement));\\n } else {\\n // Hash(current element of the proof + current computed hash)\\n computedHash = keccak256(abi.encodePacked(proofElement, computedHash));\\n }\\n }\\n\\n // Check if the computed hash (root) is equal to the provided root\\n return computedHash == root;\\n }\\n}\\n\",\"keccak256\":\"0x782ac63a026ef1d6ab242478016bf7006074726786de9a94bc6c0c33a87dd40d\",\"license\":\"MIT\"},\"@openzeppelin/contracts/token/ERC20/IERC20.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity >=0.6.0 <0.8.0;\\n\\n/**\\n * @dev Interface of the ERC20 standard as defined in the EIP.\\n */\\ninterface IERC20 {\\n /**\\n * @dev Returns the amount of tokens in existence.\\n */\\n function totalSupply() external view returns (uint256);\\n\\n /**\\n * @dev Returns the amount of tokens owned by `account`.\\n */\\n function balanceOf(address account) external view returns (uint256);\\n\\n /**\\n * @dev Moves `amount` tokens from the caller's account to `recipient`.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transfer(address recipient, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Returns the remaining number of tokens that `spender` will be\\n * allowed to spend on behalf of `owner` through {transferFrom}. This is\\n * zero by default.\\n *\\n * This value changes when {approve} or {transferFrom} are called.\\n */\\n function allowance(address owner, address spender) external view returns (uint256);\\n\\n /**\\n * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * IMPORTANT: Beware that changing an allowance with this method brings the risk\\n * that someone may use both the old and the new allowance by unfortunate\\n * transaction ordering. One possible solution to mitigate this race\\n * condition is to first reduce the spender's allowance to 0 and set the\\n * desired value afterwards:\\n * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729\\n *\\n * Emits an {Approval} event.\\n */\\n function approve(address spender, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Moves `amount` tokens from `sender` to `recipient` using the\\n * allowance mechanism. `amount` is then deducted from the caller's\\n * allowance.\\n *\\n * Returns a boolean value indicating whether the operation succeeded.\\n *\\n * Emits a {Transfer} event.\\n */\\n function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);\\n\\n /**\\n * @dev Emitted when `value` tokens are moved from one account (`from`) to\\n * another (`to`).\\n *\\n * Note that `value` may be zero.\\n */\\n event Transfer(address indexed from, address indexed to, uint256 value);\\n\\n /**\\n * @dev Emitted when the allowance of a `spender` for an `owner` is set by\\n * a call to {approve}. `value` is the new allowance.\\n */\\n event Approval(address indexed owner, address indexed spender, uint256 value);\\n}\\n\",\"keccak256\":\"0x5f02220344881ce43204ae4a6281145a67bc52c2bb1290a791857df3d19d78f5\",\"license\":\"MIT\"},\"@openzeppelin/contracts/utils/Context.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\npragma solidity >=0.6.0 <0.8.0;\\n\\n/*\\n * @dev Provides information about the current execution context, including the\\n * sender of the transaction and its data. While these are generally available\\n * via msg.sender and msg.data, they should not be accessed in such a direct\\n * manner, since when dealing with GSN meta-transactions the account sending and\\n * paying for execution may not be the actual sender (as far as an application\\n * is concerned).\\n *\\n * This contract is only required for intermediate, library-like contracts.\\n */\\nabstract contract Context {\\n function _msgSender() internal view virtual returns (address payable) {\\n return msg.sender;\\n }\\n\\n function _msgData() internal view virtual returns (bytes memory) {\\n this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691\\n return msg.data;\\n }\\n}\\n\",\"keccak256\":\"0x8d3cb350f04ff49cfb10aef08d87f19dcbaecc8027b0bed12f3275cd12f38cf0\",\"license\":\"MIT\"},\"src/MerkleRedeem.sol\":{\"content\":\"// SPDX-License-Identifier: MIT\\n\\n/**\\n * Original code taken from: https://github.com/balancer-labs/erc20-redeemable/blob/13d478a043ec7bfce7abefe708d027dfe3e2ea84/merkle/contracts/MerkleRedeem.sol\\n * Only comments and events were added, some variable names changed for clarity and the compiler version was upgraded to 0.7.x.\\n *\\n * @reviewers: [@hbarcelos]\\n * @auditors: []\\n * @bounties: []\\n * @deployments: []\\n */\\npragma solidity 0.6.8;\\npragma experimental ABIEncoderV2;\\n\\nimport \\\"@openzeppelin/contracts/cryptography/MerkleProof.sol\\\";\\nimport \\\"@openzeppelin/contracts/access/Ownable.sol\\\";\\nimport \\\"@openzeppelin/contracts/token/ERC20/IERC20.sol\\\";\\n\\n/**\\n * @title Distribution of tokens in a recurrent fashion.\\n */\\ncontract MerkleRedeem is Ownable {\\n /// @dev The address of the token being distributed.\\n IERC20 public token;\\n\\n /**\\n * @dev To be emitted when a claim is made.\\n * @param _claimant The address of the claimant.\\n * @param _balance The amount being claimed.\\n */\\n event Claimed(address _claimant, uint256 _balance);\\n\\n /// @dev The merkle roots of each week. weekMerkleRoots[week].\\n mapping(uint => bytes32) public weekMerkleRoots;\\n\\n /// @dev Keeps track of the claim status for the given period and claimant. claimed[period][claimant].\\n mapping(uint => mapping(address => bool)) public claimed;\\n\\n /**\\n * @param _token The address of the token being distributed.\\n */\\n constructor(\\n address _token\\n ) public {\\n token = IERC20(_token);\\n }\\n\\n /**\\n * @dev Effectively pays a claimant.\\n * @param _liquidityProvider The address of the claimant.\\n * @param _balance The amount being claimed.\\n */\\n function disburse(\\n address _liquidityProvider,\\n uint _balance\\n )\\n private\\n {\\n if (_balance > 0) {\\n emit Claimed(_liquidityProvider, _balance);\\n require(token.transfer(_liquidityProvider, _balance), \\\"ERR_TRANSFER_FAILED\\\");\\n }\\n }\\n\\n /**\\n * @notice Makes a claim for a given claimant in a week.\\n * @param _liquidityProvider The address of the claimant.\\n * @param _week The week for the claim.\\n * @param _claimedBalance The amount being claimed.\\n * @param _merkleProof The merkle proof for the claim, sorted from the leaf to the root of the tree.\\n */\\n function claimWeek(\\n address _liquidityProvider,\\n uint _week,\\n uint _claimedBalance,\\n bytes32[] memory _merkleProof\\n )\\n public\\n {\\n require(!claimed[_week][_liquidityProvider]);\\n require(verifyClaim(_liquidityProvider, _week, _claimedBalance, _merkleProof), 'Incorrect merkle proof');\\n\\n claimed[_week][_liquidityProvider] = true;\\n disburse(_liquidityProvider, _claimedBalance);\\n }\\n\\n struct Claim {\\n // The week the claim is related to.\\n uint week;\\n // The amount being claimed.\\n uint balance;\\n // The merkle proof for the claim, sorted from the leaf to the root of the tree.\\n bytes32[] merkleProof;\\n }\\n\\n /**\\n * @notice Makes multiple claims for a given claimant.\\n * @param _liquidityProvider The address of the claimant.\\n * @param claims An array of claims containing the week, balance and the merkle proof.\\n */\\n function claimWeeks(\\n address _liquidityProvider,\\n Claim[] memory claims\\n )\\n public\\n {\\n uint totalBalance = 0;\\n Claim memory claim ;\\n for(uint i = 0; i < claims.length; i++) {\\n claim = claims[i];\\n\\n require(!claimed[claim.week][_liquidityProvider]);\\n require(verifyClaim(_liquidityProvider, claim.week, claim.balance, claim.merkleProof), 'Incorrect merkle proof');\\n\\n totalBalance += claim.balance;\\n claimed[claim.week][_liquidityProvider] = true;\\n }\\n disburse(_liquidityProvider, totalBalance);\\n }\\n\\n /**\\n * @notice Gets the claim status for given claimant from `_begin` to `_end` weeks.\\n * @param _liquidityProvider The address of the claimant.\\n * @param _begin The week to start with (inclusive).\\n * @param _end The week to end with (inclusive).\\n */\\n function claimStatus(\\n address _liquidityProvider,\\n uint _begin,\\n uint _end\\n )\\n external\\n view\\n returns (bool[] memory)\\n {\\n uint size = 1 + _end - _begin;\\n bool[] memory arr = new bool[](size);\\n for(uint i = 0; i < size; i++) {\\n arr[i] = claimed[_begin + i][_liquidityProvider];\\n }\\n return arr;\\n }\\n\\n /**\\n * @notice Gets all merkle roots for from `_begin` to `_end` weeks.\\n * @param _begin The week to start with (inclusive).\\n * @param _end The week to end with (inclusive).\\n */\\n function merkleRoots(\\n uint _begin,\\n uint _end\\n )\\n external\\n view\\n returns (bytes32[] memory)\\n {\\n uint size = 1 + _end - _begin;\\n bytes32[] memory arr = new bytes32[](size);\\n for(uint i = 0; i < size; i++) {\\n arr[i] = weekMerkleRoots[_begin + i];\\n }\\n return arr;\\n }\\n\\n /**\\n * @notice Verifies a claim.\\n * @param _liquidityProvider The address of the claimant.\\n * @param _week The week for the claim.\\n * @param _claimedBalance The amount being claimed.\\n * @param _merkleProof The merkle proof for the claim, sorted from the leaf to the root of the tree.\\n */\\n function verifyClaim(\\n address _liquidityProvider,\\n uint _week,\\n uint _claimedBalance,\\n bytes32[] memory _merkleProof\\n )\\n public\\n view\\n returns (bool valid)\\n {\\n bytes32 leaf = keccak256(abi.encodePacked(_liquidityProvider, _claimedBalance));\\n return MerkleProof.verify(_merkleProof, weekMerkleRoots[_week], leaf);\\n }\\n\\n /**\\n * @notice Seeds a new round for the airdrop.\\n * @dev Will transfer tokens from the owner to this contract.\\n * @param _week The airdrop week.\\n * @param _merkleRoot The merkle root of the claims for that period.\\n * @param _totalAllocation The amount of tokens allocated for the distribution.\\n */\\n function seedAllocations(\\n uint _week,\\n bytes32 _merkleRoot,\\n uint _totalAllocation\\n )\\n external\\n onlyOwner\\n {\\n require(weekMerkleRoots[_week] == bytes32(0), \\\"cannot rewrite merkle root\\\");\\n weekMerkleRoots[_week] = _merkleRoot;\\n\\n require(token.transferFrom(msg.sender, address(this), _totalAllocation), \\\"ERR_TRANSFER_FAILED\\\");\\n }\\n}\\n\",\"keccak256\":\"0x8cedba0574e8ffffa5dd705f60aa6d363b8b832f15162ce67b8fc60268f6ec72\",\"license\":\"MIT\"}},\"version\":1}", - "bytecode": "0x608060405234801561001057600080fd5b50604051610fbf380380610fbf83398101604081905261002f916100b5565b60006100426001600160e01b036100b116565b600080546001600160a01b0319166001600160a01b0383169081178255604051929350917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908290a350600180546001600160a01b0319166001600160a01b03929092169190911790556100e3565b3390565b6000602082840312156100c6578081fd5b81516001600160a01b03811681146100dc578182fd5b9392505050565b610ecd806100f26000396000f3fe608060405234801561001057600080fd5b50600436106100b45760003560e01c80638da5cb5b116100715780638da5cb5b14610152578063c804c39a14610167578063dd8c9c9d1461017a578063eb0d07f51461019a578063f2fde38b146101ad578063fc0c546a146101c0576100b4565b8063120aa877146100b957806339436b00146100e257806347fb23c1146101025780634cd488ab1461012257806358b4e4b414610137578063715018a61461014a575b600080fd5b6100cc6100c7366004610bb6565b6101c8565b6040516100d99190610d2d565b60405180910390f35b6100f56100f0366004610c0d565b6101e8565b6040516100d99190610cf5565b610115610110366004610aeb565b61027e565b6040516100d99190610caf565b610135610130366004610be2565b610332565b005b610135610145366004610b1e565b61045f565b6101356104f4565b61015a61057d565b6040516100d99190610c5e565b610135610175366004610a4d565b61058c565b61018d610188366004610b9e565b61066d565b6040516100d99190610d38565b6100cc6101a8366004610b1e565b61067f565b6101356101bb366004610a2b565b6106d5565b61015a610795565b600360209081526000928352604080842090915290825290205460ff1681565b6060828203600101818167ffffffffffffffff8111801561020857600080fd5b50604051908082528060200260200182016040528015610232578160200160208202803683370190505b50905060005b8281101561027357858101600090815260026020526040902054825183908390811061026057fe5b6020908102919091010152600101610238565b509150505b92915050565b6060828203600101818167ffffffffffffffff8111801561029e57600080fd5b506040519080825280602002602001820160405280156102c8578160200160208202803683370190505b50905060005b828110156103285785810160009081526003602090815260408083206001600160a01b038b168452909152902054825160ff9091169083908390811061031057fe5b911515602092830291909101909101526001016102ce565b5095945050505050565b61033a6107a4565b6001600160a01b031661034b61057d565b6001600160a01b03161461037a5760405162461bcd60e51b815260040161037190610db7565b60405180910390fd5b600083815260026020526040902054156103a65760405162461bcd60e51b815260040161037190610e19565b6000838152600260205260409081902083905560015490516323b872dd60e01b81526001600160a01b03909116906323b872dd906103ec90339030908690600401610c72565b602060405180830381600087803b15801561040657600080fd5b505af115801561041a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061043e9190610b7e565b61045a5760405162461bcd60e51b815260040161037190610dec565b505050565b60008381526003602090815260408083206001600160a01b038816845290915290205460ff161561048f57600080fd5b61049b8484848461067f565b6104b75760405162461bcd60e51b815260040161037190610d87565b60008381526003602090815260408083206001600160a01b03881684529091529020805460ff191660011790556104ee84836107a8565b50505050565b6104fc6107a4565b6001600160a01b031661050d61057d565b6001600160a01b0316146105335760405162461bcd60e51b815260040161037190610db7565b600080546040516001600160a01b03909116907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908390a3600080546001600160a01b0319169055565b6000546001600160a01b031690565b6000610596610928565b60005b8351811015610662578381815181106105ae57fe5b602090810291909101810151805160009081526003835260408082206001600160a01b038a168352909352919091205490925060ff16156105ee57600080fd5b6106068583600001518460200151856040015161067f565b6106225760405162461bcd60e51b815260040161037190610d87565b602080830151835160009081526003835260408082206001600160a01b038a16835290935291909120805460ff1916600190811790915593019201610599565b506104ee84836107a8565b60026020526000908152604090205481565b6000808584604051602001610695929190610c2e565b6040516020818303038152906040528051906020012090506106cb8360026000888152602001908152602001600020548361088b565b9695505050505050565b6106dd6107a4565b6001600160a01b03166106ee61057d565b6001600160a01b0316146107145760405162461bcd60e51b815260040161037190610db7565b6001600160a01b03811661073a5760405162461bcd60e51b815260040161037190610d41565b600080546040516001600160a01b03808516939216917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a3600080546001600160a01b0319166001600160a01b0392909216919091179055565b6001546001600160a01b031681565b3390565b8015610887577fd8138f8a3f377c5259ca548e70e4c2de94f129f5a11036a15b69513cba2b426a82826040516107df929190610c96565b60405180910390a160015460405163a9059cbb60e01b81526001600160a01b039091169063a9059cbb906108199085908590600401610c96565b602060405180830381600087803b15801561083357600080fd5b505af1158015610847573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061086b9190610b7e565b6108875760405162461bcd60e51b815260040161037190610dec565b5050565b600081815b855181101561091d5760008682815181106108a757fe5b602002602001015190508083116108e85782816040516020016108cb929190610c50565b604051602081830303815290604052805190602001209250610914565b80836040516020016108fb929190610c50565b6040516020818303038152906040528051906020012092505b50600101610890565b509092149392505050565b60405180606001604052806000815260200160008152602001606081525090565b80356001600160a01b038116811461027857600080fd5b600082601f830112610970578081fd5b813561098361097e82610e77565b610e50565b8181529150602080830190848101818402860182018710156109a457600080fd5b60005b848110156109c3578135845292820192908201906001016109a7565b505050505092915050565b6000606082840312156109df578081fd5b6109e96060610e50565b90508135815260208201356020820152604082013567ffffffffffffffff811115610a1357600080fd5b610a1f84828501610960565b60408301525092915050565b600060208284031215610a3c578081fd5b610a468383610949565b9392505050565b60008060408385031215610a5f578081fd5b610a698484610949565b915060208084013567ffffffffffffffff811115610a85578283fd5b80850186601f820112610a96578384fd5b80359150610aa661097e83610e77565b82815283810190828501865b85811015610adb57610ac98b8884358801016109ce565b84529286019290860190600101610ab2565b5096999098509650505050505050565b600080600060608486031215610aff578081fd5b610b098585610949565b95602085013595506040909401359392505050565b60008060008060808587031215610b33578081fd5b610b3d8686610949565b93506020850135925060408501359150606085013567ffffffffffffffff811115610b66578182fd5b610b7287828801610960565b91505092959194509250565b600060208284031215610b8f578081fd5b81518015158114610a46578182fd5b600060208284031215610baf578081fd5b5035919050565b60008060408385031215610bc8578182fd5b82359150610bd98460208501610949565b90509250929050565b600080600060608486031215610bf6578283fd5b505081359360208301359350604090920135919050565b60008060408385031215610c1f578182fd5b50508035926020909101359150565b60609290921b6bffffffffffffffffffffffff19168252601482015260340190565b918252602082015260400190565b6001600160a01b0391909116815260200190565b6001600160a01b039384168152919092166020820152604081019190915260600190565b6001600160a01b03929092168252602082015260400190565b6020808252825182820181905260009190848201906040850190845b81811015610ce9578351151583529284019291840191600101610ccb565b50909695505050505050565b6020808252825182820181905260009190848201906040850190845b81811015610ce957835183529284019291840191600101610d11565b901515815260200190565b90815260200190565b60208082526026908201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160408201526564647265737360d01b606082015260800190565b60208082526016908201527524b731b7b93932b1ba1036b2b935b63290383937b7b360511b604082015260600190565b6020808252818101527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604082015260600190565b60208082526013908201527211549497d514905394d1915497d19052531151606a1b604082015260600190565b6020808252601a908201527f63616e6e6f742072657772697465206d65726b6c6520726f6f74000000000000604082015260600190565b60405181810167ffffffffffffffff81118282101715610e6f57600080fd5b604052919050565b600067ffffffffffffffff821115610e8d578081fd5b506020908102019056fea2646970667358221220296eac005c7f95edb24611c66f89fda0554fdb45eefce851f379d955ac72932064736f6c63430006080033", - "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106100b45760003560e01c80638da5cb5b116100715780638da5cb5b14610152578063c804c39a14610167578063dd8c9c9d1461017a578063eb0d07f51461019a578063f2fde38b146101ad578063fc0c546a146101c0576100b4565b8063120aa877146100b957806339436b00146100e257806347fb23c1146101025780634cd488ab1461012257806358b4e4b414610137578063715018a61461014a575b600080fd5b6100cc6100c7366004610bb6565b6101c8565b6040516100d99190610d2d565b60405180910390f35b6100f56100f0366004610c0d565b6101e8565b6040516100d99190610cf5565b610115610110366004610aeb565b61027e565b6040516100d99190610caf565b610135610130366004610be2565b610332565b005b610135610145366004610b1e565b61045f565b6101356104f4565b61015a61057d565b6040516100d99190610c5e565b610135610175366004610a4d565b61058c565b61018d610188366004610b9e565b61066d565b6040516100d99190610d38565b6100cc6101a8366004610b1e565b61067f565b6101356101bb366004610a2b565b6106d5565b61015a610795565b600360209081526000928352604080842090915290825290205460ff1681565b6060828203600101818167ffffffffffffffff8111801561020857600080fd5b50604051908082528060200260200182016040528015610232578160200160208202803683370190505b50905060005b8281101561027357858101600090815260026020526040902054825183908390811061026057fe5b6020908102919091010152600101610238565b509150505b92915050565b6060828203600101818167ffffffffffffffff8111801561029e57600080fd5b506040519080825280602002602001820160405280156102c8578160200160208202803683370190505b50905060005b828110156103285785810160009081526003602090815260408083206001600160a01b038b168452909152902054825160ff9091169083908390811061031057fe5b911515602092830291909101909101526001016102ce565b5095945050505050565b61033a6107a4565b6001600160a01b031661034b61057d565b6001600160a01b03161461037a5760405162461bcd60e51b815260040161037190610db7565b60405180910390fd5b600083815260026020526040902054156103a65760405162461bcd60e51b815260040161037190610e19565b6000838152600260205260409081902083905560015490516323b872dd60e01b81526001600160a01b03909116906323b872dd906103ec90339030908690600401610c72565b602060405180830381600087803b15801561040657600080fd5b505af115801561041a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061043e9190610b7e565b61045a5760405162461bcd60e51b815260040161037190610dec565b505050565b60008381526003602090815260408083206001600160a01b038816845290915290205460ff161561048f57600080fd5b61049b8484848461067f565b6104b75760405162461bcd60e51b815260040161037190610d87565b60008381526003602090815260408083206001600160a01b03881684529091529020805460ff191660011790556104ee84836107a8565b50505050565b6104fc6107a4565b6001600160a01b031661050d61057d565b6001600160a01b0316146105335760405162461bcd60e51b815260040161037190610db7565b600080546040516001600160a01b03909116907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0908390a3600080546001600160a01b0319169055565b6000546001600160a01b031690565b6000610596610928565b60005b8351811015610662578381815181106105ae57fe5b602090810291909101810151805160009081526003835260408082206001600160a01b038a168352909352919091205490925060ff16156105ee57600080fd5b6106068583600001518460200151856040015161067f565b6106225760405162461bcd60e51b815260040161037190610d87565b602080830151835160009081526003835260408082206001600160a01b038a16835290935291909120805460ff1916600190811790915593019201610599565b506104ee84836107a8565b60026020526000908152604090205481565b6000808584604051602001610695929190610c2e565b6040516020818303038152906040528051906020012090506106cb8360026000888152602001908152602001600020548361088b565b9695505050505050565b6106dd6107a4565b6001600160a01b03166106ee61057d565b6001600160a01b0316146107145760405162461bcd60e51b815260040161037190610db7565b6001600160a01b03811661073a5760405162461bcd60e51b815260040161037190610d41565b600080546040516001600160a01b03808516939216917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a3600080546001600160a01b0319166001600160a01b0392909216919091179055565b6001546001600160a01b031681565b3390565b8015610887577fd8138f8a3f377c5259ca548e70e4c2de94f129f5a11036a15b69513cba2b426a82826040516107df929190610c96565b60405180910390a160015460405163a9059cbb60e01b81526001600160a01b039091169063a9059cbb906108199085908590600401610c96565b602060405180830381600087803b15801561083357600080fd5b505af1158015610847573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061086b9190610b7e565b6108875760405162461bcd60e51b815260040161037190610dec565b5050565b600081815b855181101561091d5760008682815181106108a757fe5b602002602001015190508083116108e85782816040516020016108cb929190610c50565b604051602081830303815290604052805190602001209250610914565b80836040516020016108fb929190610c50565b6040516020818303038152906040528051906020012092505b50600101610890565b509092149392505050565b60405180606001604052806000815260200160008152602001606081525090565b80356001600160a01b038116811461027857600080fd5b600082601f830112610970578081fd5b813561098361097e82610e77565b610e50565b8181529150602080830190848101818402860182018710156109a457600080fd5b60005b848110156109c3578135845292820192908201906001016109a7565b505050505092915050565b6000606082840312156109df578081fd5b6109e96060610e50565b90508135815260208201356020820152604082013567ffffffffffffffff811115610a1357600080fd5b610a1f84828501610960565b60408301525092915050565b600060208284031215610a3c578081fd5b610a468383610949565b9392505050565b60008060408385031215610a5f578081fd5b610a698484610949565b915060208084013567ffffffffffffffff811115610a85578283fd5b80850186601f820112610a96578384fd5b80359150610aa661097e83610e77565b82815283810190828501865b85811015610adb57610ac98b8884358801016109ce565b84529286019290860190600101610ab2565b5096999098509650505050505050565b600080600060608486031215610aff578081fd5b610b098585610949565b95602085013595506040909401359392505050565b60008060008060808587031215610b33578081fd5b610b3d8686610949565b93506020850135925060408501359150606085013567ffffffffffffffff811115610b66578182fd5b610b7287828801610960565b91505092959194509250565b600060208284031215610b8f578081fd5b81518015158114610a46578182fd5b600060208284031215610baf578081fd5b5035919050565b60008060408385031215610bc8578182fd5b82359150610bd98460208501610949565b90509250929050565b600080600060608486031215610bf6578283fd5b505081359360208301359350604090920135919050565b60008060408385031215610c1f578182fd5b50508035926020909101359150565b60609290921b6bffffffffffffffffffffffff19168252601482015260340190565b918252602082015260400190565b6001600160a01b0391909116815260200190565b6001600160a01b039384168152919092166020820152604081019190915260600190565b6001600160a01b03929092168252602082015260400190565b6020808252825182820181905260009190848201906040850190845b81811015610ce9578351151583529284019291840191600101610ccb565b50909695505050505050565b6020808252825182820181905260009190848201906040850190845b81811015610ce957835183529284019291840191600101610d11565b901515815260200190565b90815260200190565b60208082526026908201527f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160408201526564647265737360d01b606082015260800190565b60208082526016908201527524b731b7b93932b1ba1036b2b935b63290383937b7b360511b604082015260600190565b6020808252818101527f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572604082015260600190565b60208082526013908201527211549497d514905394d1915497d19052531151606a1b604082015260600190565b6020808252601a908201527f63616e6e6f742072657772697465206d65726b6c6520726f6f74000000000000604082015260600190565b60405181810167ffffffffffffffff81118282101715610e6f57600080fd5b604052919050565b600067ffffffffffffffff821115610e8d578081fd5b506020908102019056fea2646970667358221220296eac005c7f95edb24611c66f89fda0554fdb45eefce851f379d955ac72932064736f6c63430006080033", - "devdoc": { - "methods": { - "claimStatus(address,uint256,uint256)": { - "params": { - "_begin": "The week to start with (inclusive).", - "_end": "The week to end with (inclusive).", - "_liquidityProvider": "The address of the claimant." - } - }, - "claimWeek(address,uint256,uint256,bytes32[])": { - "params": { - "_claimedBalance": "The amount being claimed.", - "_liquidityProvider": "The address of the claimant.", - "_merkleProof": "The merkle proof for the claim, sorted from the leaf to the root of the tree.", - "_week": "The week for the claim." - } - }, - "claimWeeks(address,(uint256,uint256,bytes32[])[])": { - "params": { - "_liquidityProvider": "The address of the claimant.", - "claims": "An array of claims containing the week, balance and the merkle proof." - } - }, - "constructor": { - "params": { - "_token": "The address of the token being distributed." - } - }, - "merkleRoots(uint256,uint256)": { - "params": { - "_begin": "The week to start with (inclusive).", - "_end": "The week to end with (inclusive)." - } - }, - "owner()": { - "details": "Returns the address of the current owner." - }, - "renounceOwnership()": { - "details": "Leaves the contract without owner. It will not be possible to call `onlyOwner` functions anymore. Can only be called by the current owner. * NOTE: Renouncing ownership will leave the contract without an owner, thereby removing any functionality that is only available to the owner." - }, - "seedAllocations(uint256,bytes32,uint256)": { - "details": "Will transfer tokens from the owner to this contract.", - "params": { - "_merkleRoot": "The merkle root of the claims for that period.", - "_totalAllocation": "The amount of tokens allocated for the distribution.", - "_week": "The airdrop week." - } - }, - "transferOwnership(address)": { - "details": "Transfers ownership of the contract to a new account (`newOwner`). Can only be called by the current owner." - }, - "verifyClaim(address,uint256,uint256,bytes32[])": { - "params": { - "_claimedBalance": "The amount being claimed.", - "_liquidityProvider": "The address of the claimant.", - "_merkleProof": "The merkle proof for the claim, sorted from the leaf to the root of the tree.", - "_week": "The week for the claim." - } - } - }, - "title": "Distribution of tokens in a recurrent fashion." - }, - "userdoc": { - "methods": { - "claimStatus(address,uint256,uint256)": { - "notice": "Gets the claim status for given claimant from `_begin` to `_end` weeks." - }, - "claimWeek(address,uint256,uint256,bytes32[])": { - "notice": "Makes a claim for a given claimant in a week." - }, - "claimWeeks(address,(uint256,uint256,bytes32[])[])": { - "notice": "Makes multiple claims for a given claimant." - }, - "merkleRoots(uint256,uint256)": { - "notice": "Gets all merkle roots for from `_begin` to `_end` weeks." - }, - "seedAllocations(uint256,bytes32,uint256)": { - "notice": "Seeds a new round for the airdrop." - }, - "verifyClaim(address,uint256,uint256,bytes32[])": { - "notice": "Verifies a claim." - } - } - }, - "storageLayout": { - "storage": [ - { - "astId": 7, - "contract": "src/MerkleRedeem.sol:MerkleRedeem", - "label": "_owner", - "offset": 0, - "slot": "0", - "type": "t_address" - }, - { - "astId": 292, - "contract": "src/MerkleRedeem.sol:MerkleRedeem", - "label": "token", - "offset": 0, - "slot": "1", - "type": "t_contract(IERC20)258" - }, - { - "astId": 303, - "contract": "src/MerkleRedeem.sol:MerkleRedeem", - "label": "weekMerkleRoots", - "offset": 0, - "slot": "2", - "type": "t_mapping(t_uint256,t_bytes32)" - }, - { - "astId": 309, - "contract": "src/MerkleRedeem.sol:MerkleRedeem", - "label": "claimed", - "offset": 0, - "slot": "3", - "type": "t_mapping(t_uint256,t_mapping(t_address,t_bool))" - } - ], - "types": { - "t_address": { - "encoding": "inplace", - "label": "address", - "numberOfBytes": "20" - }, - "t_bool": { - "encoding": "inplace", - "label": "bool", - "numberOfBytes": "1" - }, - "t_bytes32": { - "encoding": "inplace", - "label": "bytes32", - "numberOfBytes": "32" - }, - "t_contract(IERC20)258": { - "encoding": "inplace", - "label": "contract IERC20", - "numberOfBytes": "20" - }, - "t_mapping(t_address,t_bool)": { - "encoding": "mapping", - "key": "t_address", - "label": "mapping(address => bool)", - "numberOfBytes": "32", - "value": "t_bool" - }, - "t_mapping(t_uint256,t_bytes32)": { - "encoding": "mapping", - "key": "t_uint256", - "label": "mapping(uint256 => bytes32)", - "numberOfBytes": "32", - "value": "t_bytes32" - }, - "t_mapping(t_uint256,t_mapping(t_address,t_bool))": { - "encoding": "mapping", - "key": "t_uint256", - "label": "mapping(uint256 => mapping(address => bool))", - "numberOfBytes": "32", - "value": "t_mapping(t_address,t_bool)" - }, - "t_uint256": { - "encoding": "inplace", - "label": "uint256", - "numberOfBytes": "32" - } - } - } -} \ No newline at end of file diff --git a/snapshots/.env.example b/snapshots/.env.example index c3aeaab..98252f5 100644 --- a/snapshots/.env.example +++ b/snapshots/.env.example @@ -1,2 +1,3 @@ -PNK_DROP_JSON_RPC_URL='' +INFURA_ARB_ONE_RPC=https://arbitrum-mainnet.infura.io/v3/{apiKey} +PNK_DROP_JSON_RPC_URL=https://mainnet.infura.io/v3/{apiKey} FILEBASE_TOKEN='' \ No newline at end of file diff --git a/snapshots/cli.js b/snapshots/cli.js index 6ba9ef0..d85c771 100755 --- a/snapshots/cli.js +++ b/snapshots/cli.js @@ -6,9 +6,9 @@ import { hideBin } from "yargs/helpers"; import dayjs from "dayjs"; import utc from "dayjs/plugin/utc.js"; import { createSnapshotCreator } from "./src/create-snapshot-from-block-limits.js"; -import { formatEther } from "ethers/lib/utils.js"; import fs from "fs"; import { fileToIpfs } from "./src/fileToIpfs.js"; +import { addTransactionToBatch, writeTransactionBatch, createNewBatch } from "./src/helpers/tx-builder.js"; dotenv.config(); @@ -16,22 +16,43 @@ dayjs.extend(utc); const chains = [ { + version: "v1", chainId: 1, + chainShortName: "eth", blocksPerSecond: 0.066667, klerosLiquidAddress: "0x988b3a538b618c7a603e1c11ab82cd16dbe28069", token: "0x93ed3fbe21207ec2e8f2d3c3de6e058cb73bc04d", - pnkDropRatio: BigNumber.from("900000000"), + pnkDropRatio: BigNumber.from("800000000"), fromBlock: 7300000, provider: getDefaultProvider(process.env.PNK_DROP_JSON_RPC_URL), + merkleDropAddress: "0xdbc3088Dfebc3cc6A84B0271DaDe2696DB00Af38", + safeAddress: "0xSAFEADDRESS_MAINNET", }, { + version: "v1", chainId: 100, + chainShortName: "gno", blocksPerSecond: 0.2, klerosLiquidAddress: "0x9C1dA9A04925bDfDedf0f6421bC7EEa8305F9002", token: "0xcb3231aBA3b451343e0Fddfc45883c842f223846", pnkDropRatio: BigNumber.from("100000000"), fromBlock: 16895601, provider: getDefaultProvider("https://rpc.gnosischain.com"), + merkleDropAddress: "0xf1A9589880DbF393F32A5b2d5a0054Fa10385074", + safeAddress: "0xSAFEADDRESS_GNOSIS", + }, + { + version: "v2", + chainId: 42161, + chainShortName: "arb", + blocksPerSecond: 0.26, + klerosCoreAddress: "0x991d2df165670b9cac3B022f4B68D65b664222ea", + token: "0x330bD769382cFc6d50175903434CCC8D206DCAE5", + pnkDropRatio: BigNumber.from("1000000000"), + fromBlock: 272063254, + provider: getDefaultProvider(process.env.INFURA_ARB_ONE_RPC), + merkleDropAddress: "0x2a23B84078b287753A91C522c3bB3b6B32f6F8f1", + safeAddress: "0x66e8DE9B42308c6Ca913D1EE041d6F6fD037A57e", }, ]; @@ -39,7 +60,7 @@ const argv = yargs(hideBin(process.argv)) .strict(true) .locale("en") .usage(`Usage: $0 --lastamount={n}`) - .epilogue("Alternatively you can set the same params in the .env file. Check .env.example.") + .epilogue("Alternatively set the same params in the .env file. Check .env.example.") .option("lastamount", { description: "The amount of tokens, in wei, that were distributed in the last period", }) @@ -54,6 +75,8 @@ const normalizeArgs = ({ lastamount }) => ({ const { lastamount } = normalizeArgs(argv); +const formatDateMonth = (date) => dayjs(date).utc().format("YYYY-MM"); + const getDatesAndPeriod = () => { const currentDate = new Date(); // Current date in local time zone const currentMonth = currentDate.getUTCMonth(); // Get current month in UTC @@ -75,10 +98,11 @@ const getDatesAndPeriod = () => { // target starts at 29 for January 2024 and increases by 1 each period // maxes at 50 const target = BigNumber.from(Math.min(29 + monthDiff, 50)).mul(BigNumber.from("10000000")); - // mainnetPeriod starts at 35 for January 2024 and also increases by 1 each period - // gnosisPeriod starts at 30 for January 2024 and increases by 1 each period + // v1's mainnetPeriod starts at 35 for January 2024 and also increases by 1 each period + // v1's gnosisPeriod starts at 30 for January 2024 and increases by 1 each period + // v2's arbitrumPeriod starts at 1 for January 2024 and increases by 1 each period // only used for _week argument in merkledrop.seedAllocations() - const periods = { 1: 35 + monthDiff, 100: 30 + monthDiff }; + const periods = { 1: 35 + monthDiff, 100: 30 + monthDiff, 42161: 1 + monthDiff }; return { startDate, endDate, previousDate, target, periods }; }; @@ -100,6 +124,7 @@ const main = async () => { const createSnapshot = await createSnapshotCreator({ provider: chain.provider, klerosLiquidAddress: chain.klerosLiquidAddress, + klerosCoreAddress: chain.klerosCoreAddress, droppedAmount: BigNumber.from(0), // we're not awarding anything, just counting. }); @@ -124,12 +149,12 @@ const main = async () => { const totalPNKStaked = await getTotalPNKStaked(); // lets compute the formula to figure out how much will be awarded in total this month - const pnkMainnet = new Contract( + const pnkSupplyChecker = new Contract( chains[0].token, ["function totalSupply() view returns (uint256)"], chains[0].provider ); - const totalSupply = await pnkMainnet.totalSupply(); + const totalSupply = await pnkSupplyChecker.totalSupply(); console.log( "Total PNK staked:", BigNumber.from(totalPNKStaked).div(BigNumber.from("1000000000000000000")).toString(), @@ -156,23 +181,25 @@ const main = async () => { const snapshotInfos = []; for (const c of chains) { const droppedAmount = fullReward.mul(c.pnkDropRatio).div(basis); - console.log("> Chain [", c.chainId, "] ", droppedAmount.toString(), "PNK (wei) will be rewarded"); + console.log(`> Chain [${c.chainId}] ${droppedAmount.toString()} PNK (wei) will be rewarded`); + const createSnapshot = await createSnapshotCreator({ provider: c.provider, klerosLiquidAddress: c.klerosLiquidAddress, + klerosCoreAddress: c.klerosCoreAddress, droppedAmount, }); const snapshot = await createSnapshot({ fromBlock: c.fromBlock, startDate, endDate }); + snapshotInfos.push({ - // edit when arbitrum inclusion - filename: `${c.chainId == "1" ? "" : "xdai-"}snapshot-${startDate.toISOString().slice(0, 7)}.json`, + filename: `${c.chainShortName}-snapshot-${startDate.toISOString().slice(0, 7)}.json`, chain: c, snapshot, period: periods[c.chainId], }); } - // paste these into kleros/court + // paste these ipfs hashes into kleros/court's claim-modal file so people can claim the rewards. for (const sinfo of snapshotInfos) { const path = `.cache/${sinfo.filename}`; fs.writeFileSync(path, JSON.stringify(sinfo.snapshot)); @@ -180,39 +207,30 @@ const main = async () => { console.log(`https://cdn.kleros.link/ipfs/${ipfsPath}`); } - // txs to run sequentially, hardcoded section. - //1. Approve `0xdbc3088Dfebc3cc6A84B0271DaDe2696DB00Af38` (mainnet) to spend 900k PNK (token address `0x93ed3fbe21207ec2e8f2d3c3de6e058cb73bc04d`) - // >>>> ignoring. - //2. Seed week X on Mainnet. - const merkleContractMainnet = new Contract("0xdbc3088Dfebc3cc6A84B0271DaDe2696DB00Af38", [ - "function seedAllocations(uint _week, bytes32 _merkleRoot, uint _totalAllocation) external", - ]); - const txToUrl = (tx, chainId) => - `https://greenlucid.github.io/lame-tx-prompt/site?to=${tx.to}&data=${tx.data}&value=0&chainId=${chainId}`; - const tx1 = await merkleContractMainnet.populateTransaction.seedAllocations( - snapshotInfos[0].period, - snapshotInfos[0].snapshot.merkleTree.root, - snapshotInfos[0].snapshot.droppedAmount - ); - console.log("PNK should be already approved to Merkle Drop"); - console.log("1: ", txToUrl(tx1, 1)); - console.log( - "2: ", - "https://omni.gnosischain.com/bridge", - " amount: ", - formatEther(snapshotInfos[1].snapshot.droppedAmount) - ); - console.log("3: http://court.kleros.io and xPNK -> stPNK"); - console.log("stPNK should be already approved to Merkle Drop"); - const merkleContractGnosis = new Contract("0xf1A9589880DbF393F32A5b2d5a0054Fa10385074", [ - "function seedAllocations(uint _week, bytes32 _merkleRoot, uint _totalAllocation) external", - ]); - const tx2 = await merkleContractGnosis.populateTransaction.seedAllocations( - snapshotInfos[1].period, - snapshotInfos[1].snapshot.merkleTree.root, - snapshotInfos[1].snapshot.droppedAmount - ); - console.log("4: ", txToUrl(tx2, 100)); + // 1. For each chain, the Safe account must have approved the spending of an unlimited amount of PNK tokens to its MerkleRedeem contract. + // This means, approve it 1 time for Mainnet, 1 time for Gnosis, 1 time for Arbitrum, and you're all set. + console.log("Generating batched transactions..."); + const merkleDropABI = [ + "function seedAllocations(uint256 _period, bytes32 _merkleRoot, uint256 _totalAllocation) external", + ]; + + for (const sinfo of snapshotInfos) { + const tx = await new Contract(sinfo.chain.merkleDropAddress, merkleDropABI).populateTransaction.seedAllocations( + sinfo.period, + sinfo.snapshot.merkleTree.root, + sinfo.snapshot.droppedAmount + ); + + createNewBatch(); + addTransactionToBatch(tx); + writeTransactionBatch({ + name: "Seed allocations", + chainId: sinfo.chain.chainId, + chainShortName: sinfo.chain.chainShortName, + safeAddress: sinfo.chain.safeAddress, + outputPath: `tx-batch-${sinfo.chain.chainShortName}-${formatDateMonth(startDate)}.json`, + }); + } }; main(); diff --git a/snapshots/src/helpers/subgraph-events.js b/snapshots/src/helpers/subgraph-events.js index 99fbe26..d088638 100644 --- a/snapshots/src/helpers/subgraph-events.js +++ b/snapshots/src/helpers/subgraph-events.js @@ -1,48 +1,70 @@ import { BigNumber, utils } from "ethers"; import fetch from "node-fetch"; -const fetchStakeSets = async (blockStart, blockEnd, subgraphEndpoint, lastId) => { - const subgraphQuery = { - query: ` - { - stakeSets(where: { - blocknumber_gte: ${blockStart}, - blocknumber_lt: ${blockEnd}, - id_gt: "${lastId}" - }, - orderBy: id, - orderDirection: asc, - first: 1000) { +const isV2 = (chainId) => chainId === 42161; + +const fetchStakeSets = async (blockStart, blockEnd, subgraphEndpoint, lastId, chainId) => { + + const query = isV2(chainId) + ? ` + { + stakeSets(where: { + blocknumber_gte: ${blockStart}, + blocknumber_lt: ${blockEnd}, + id_gt: "${lastId}" + }, + orderBy: id, + orderDirection: asc, + first: 1000) { + id + juror { id - address - subcourtID - stake - newTotalStake - logIndex - blocknumber } + courtID + stake + newTotalStake + logIndex + blocknumber + } + } + ` + : ` + { + stakeSets(where: { + blocknumber_gte: ${blockStart}, + blocknumber_lt: ${blockEnd}, + id_gt: "${lastId}" + }, + orderBy: id, + orderDirection: asc, + first: 1000) { + id + address + subcourtID + stake + newTotalStake + logIndex + blocknumber } - `, - }; + } + `; + const response = await fetch(subgraphEndpoint, { method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(subgraphQuery), + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ query }), }); - const { data } = await response.json(); - const stakeSets = data.stakeSets; - return stakeSets; + const { data } = await response.json(); + return data.stakeSets; }; -const fetchAllStakeSets = async (blockStart, blockEnd, subgraphEndpoint) => { +const fetchAllStakeSets = async (blockStart, blockEnd, subgraphEndpoint, chainId) => { const batches = []; let lastId = ""; for (let i = 0; i < 1000; i++) { //console.log("Stake sets batch", batches.length); - const sets = await fetchStakeSets(blockStart, blockEnd, subgraphEndpoint, lastId); + const sets = await fetchStakeSets(blockStart, blockEnd, subgraphEndpoint, lastId, chainId); //console.log("Batch got length:", sets.length); batches.push(sets); if (sets.length < 1000) break; @@ -51,15 +73,22 @@ const fetchAllStakeSets = async (blockStart, blockEnd, subgraphEndpoint) => { return batches.flat(1); }; -const parseStakeSetsIntoEvents = (subgraphStakeSets) => { +const parseStakeSetsIntoEvents = (subgraphStakeSets, chainId) => { return subgraphStakeSets.map((s) => { return { - args: { - _address: utils.getAddress(s.address), // to checksum - _subcourtID: BigNumber.from(s.subcourtID), - _stake: BigNumber.from(s.stake), - _newTotalStake: BigNumber.from(s.newTotalStake), - }, + args: isV2(chainId) + ? { + _address: utils.getAddress(s.juror.id), // to checksum + _courtID: BigNumber.from(s.courtID), + _stake: BigNumber.from(s.stake), + _newTotalStake: BigNumber.from(s.newTotalStake), + } + : { + _address: utils.getAddress(s.address), // to checksum + _subcourtID: BigNumber.from(s.subcourtID), + _stake: BigNumber.from(s.stake), + _newTotalStake: BigNumber.from(s.newTotalStake), + }, logIndex: Number(s.logIndex), blockNumber: Number(s.blocknumber), }; @@ -72,15 +101,19 @@ export const getStakeSets = async (blockStart, blockEnd, chainId) => { endpoint = "https://api.studio.thegraph.com/query/61738/kleros-display-mainnet/version/latest"; } else if (chainId === 100) { endpoint = "https://api.studio.thegraph.com/query/61738/kleros-display-gnosis/version/latest"; + } else if (chainId === 42161) { + endpoint = "https://api.studio.thegraph.com/query/44313/kleros-v2-neo-mainnet/version/latest"; } else { - throw new Error("Unsupported Chain, nor mainnet nor gnosis"); + throw new Error("Unsupported chain"); } - const subgraphStakeSets = await fetchAllStakeSets(blockStart, blockEnd, endpoint); - const parsed = parseStakeSetsIntoEvents(subgraphStakeSets); + + const subgraphStakeSets = await fetchAllStakeSets(blockStart, blockEnd, endpoint, chainId); + const parsed = parseStakeSetsIntoEvents(subgraphStakeSets, chainId); const sorted = parsed.sort((a, b) => { if (a.blockNumber === b.blockNumber) { return a.logIndex - b.logIndex; } else return a.blockNumber - b.blockNumber; }); + return sorted; }; diff --git a/snapshots/src/helpers/tx-builder.js b/snapshots/src/helpers/tx-builder.js new file mode 100644 index 0000000..364779c --- /dev/null +++ b/snapshots/src/helpers/tx-builder.js @@ -0,0 +1,61 @@ +import fs from "fs"; + +// Transaction batch example: https://github.com/safe-global/safe-wallet-monorepo/blob/8bbf3b82edc347b70a038629cd9afd45eb1ed38a/apps/web/cypress/fixtures/test-working-batch.json + +const signer = "0x28A81EC3045F079DCf051BA2F3280335D18144cC"; +const transactions = []; + +export const createNewBatch = () => { + transactions.length = 0; +}; + +const template = ({ name, chainId, safeAddress, transactions }) => ({ + version: "1.0", + chainId, + createdAt: Date.now(), + meta: { + name, + description: "", // Not used because the Safe app doesn't show it + txBuilderVersion: "1.18.0", + createdFromSafeAddress: safeAddress, + createdFromOwnerAddress: signer, + }, + transactions, +}); + +const transaction = ({ to, value, data }) => ({ + to, + value: value?.toString() ?? "0", + data, + contractMethod: null, + contractInputsValues: null, +}); + +const transactionBuilderUrl = ({ chainShortName, safeAddress }) => + `https://app.safe.global/apps/open?safe=${chainShortName}:${safeAddress}&appUrl=https%3A%2F%2Fapps-portal.safe.global%2Ftx-builder`; + +export const addTransactionToBatch = (tx) => { + const { to, value, data } = tx; + transactions.push(transaction({ to, value, data })); + console.log("tx = %O", tx); +}; + +export function writeTransactionBatch({ name, chainId, chainShortName, safeAddress, outputPath = "tx-batch.json" }) { + if (!name?.trim()) throw new Error("Batch name is required"); + + if (!transactions?.length) { + console.log("No transaction batch to write"); + return; + } + + try { + const templateObject = template({ name, chainId, safeAddress, transactions }); + fs.writeFileSync(outputPath, JSON.stringify(templateObject, null, 2)); + console.log(`Transaction batch written to ${outputPath}`); + console.log( + `The batch can be submitted to the Safe app at: ${transactionBuilderUrl({ chainShortName, safeAddress })}` + ); + } catch (error) { + throw new Error(`Failed to write transaction batch: ${error.message}`); + } +} \ No newline at end of file From 04815d6e57500790ba306ea64463a2f1af098e81 Mon Sep 17 00:00:00 2001 From: kemuru <102478601+kemuru@users.noreply.github.com> Date: Wed, 9 Apr 2025 11:48:45 +0200 Subject: [PATCH 2/2] chore: add safe addresses for mainnet and gnosis --- snapshots/cli.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/snapshots/cli.js b/snapshots/cli.js index d85c771..b22ddf6 100755 --- a/snapshots/cli.js +++ b/snapshots/cli.js @@ -26,7 +26,7 @@ const chains = [ fromBlock: 7300000, provider: getDefaultProvider(process.env.PNK_DROP_JSON_RPC_URL), merkleDropAddress: "0xdbc3088Dfebc3cc6A84B0271DaDe2696DB00Af38", - safeAddress: "0xSAFEADDRESS_MAINNET", + safeAddress: "0x3CDe6e49AC61B268dBFce31B73DEA440c4E09162", }, { version: "v1", @@ -39,7 +39,7 @@ const chains = [ fromBlock: 16895601, provider: getDefaultProvider("https://rpc.gnosischain.com"), merkleDropAddress: "0xf1A9589880DbF393F32A5b2d5a0054Fa10385074", - safeAddress: "0xSAFEADDRESS_GNOSIS", + safeAddress: "0x3CDe6e49AC61B268dBFce31B73DEA440c4E09162", }, { version: "v2",