diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index b8e19d39b8..daf17c0809 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -52,17 +52,6 @@ jobs: - name: Benchmark Join/Exit run: yarn workspace @balancer-labs/v2-benchmarks measure-join-exit - merkle-claim: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Set up environment - uses: ./.github/actions/setup - - name: Compile - run: yarn build - - name: Benchmark Merkle Claim - run: yarn workspace @balancer-labs/v2-benchmarks measure-merkle-claim - relayer: runs-on: ubuntu-latest steps: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b41ffd5656..390429a768 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -163,25 +163,6 @@ jobs: - name: Run Forge tests run: yarn workspace @balancer-labs/v2-pool-linear test-fuzz - test-distributors: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - with: - submodules: recursive - - name: Set up environment - uses: ./.github/actions/setup - - name: Compile - run: yarn build - - name: Test - run: yarn workspace @balancer-labs/v2-distributors test - - name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1 - with: - version: nightly - - name: Run Forge tests - run: yarn workspace @balancer-labs/v2-distributors test-fuzz - test-liquidity-mining: runs-on: ubuntu-latest steps: diff --git a/audits/test-report.md b/audits/test-report.md index beeff2972d..774bb636d3 100644 --- a/audits/test-report.md +++ b/audits/test-report.md @@ -138,73 +138,6 @@ codes instead of standard revert strings. ➤ [@balancer-labs/v2-asset-manager-utils]: ➤ [@balancer-labs/v2-asset-manager-utils]: 54 passing (41s) ➤ [@balancer-labs/v2-asset-manager-utils]: -➤ [@balancer-labs/v2-distributors]: -➤ [@balancer-labs/v2-distributors]: -➤ [@balancer-labs/v2-distributors]: MerkleRedeem -➤ [@balancer-labs/v2-distributors]: ✓ stores an allocation (284ms) -➤ [@balancer-labs/v2-distributors]: ✓ emits RewardAdded when an allocation is stored (252ms) -➤ [@balancer-labs/v2-distributors]: ✓ requisitions tokens when it stores a balance -➤ [@balancer-labs/v2-distributors]: ✓ reverts when unauthorized to seed allocation -➤ [@balancer-labs/v2-distributors]: ✓ stores multiple allocations -➤ [@balancer-labs/v2-distributors]: with an allocation -➤ [@balancer-labs/v2-distributors]: ✓ allows the user to claimWeek (255ms) -➤ [@balancer-labs/v2-distributors]: ✓ emits RewardPaid when an allocation is claimed -➤ [@balancer-labs/v2-distributors]: ✓ marks claimed weeks as claimed -➤ [@balancer-labs/v2-distributors]: ✓ allows the user to claimWeek to internal balance -➤ [@balancer-labs/v2-distributors]: ✓ reverts when a user attempts to claim for another user -➤ [@balancer-labs/v2-distributors]: ✓ reverts when the user attempts to claim the wrong balance -➤ [@balancer-labs/v2-distributors]: ✓ reverts when the user attempts to claim twice -➤ [@balancer-labs/v2-distributors]: ✓ reverts when an admin attempts to overwrite an allocationn -➤ [@balancer-labs/v2-distributors]: with several allocations -➤ [@balancer-labs/v2-distributors]: ✓ allows the user to claim multiple weeks at once -➤ [@balancer-labs/v2-distributors]: ✓ reports weeks as unclaimed -➤ [@balancer-labs/v2-distributors]: ✓ returns an array of merkle roots -➤ [@balancer-labs/v2-distributors]: When a user has claimed one of their allocations -➤ [@balancer-labs/v2-distributors]: ✓ reports one of the weeks as claimed -➤ [@balancer-labs/v2-distributors]: -➤ [@balancer-labs/v2-distributors]: Staking contract -➤ [@balancer-labs/v2-distributors]: isAllowlistedRewarder -➤ [@balancer-labs/v2-distributors]: ✓ allows thet asset managers to allowlist themselves -➤ [@balancer-labs/v2-distributors]: ✓ allows the owner to allowlist someone -➤ [@balancer-labs/v2-distributors]: ✓ returns false for random users -➤ [@balancer-labs/v2-distributors]: addReward -➤ [@balancer-labs/v2-distributors]: ✓ sets up a reward for an asset manager -➤ [@balancer-labs/v2-distributors]: stakeWithPermit -➤ [@balancer-labs/v2-distributors]: ✓ stakes with a permit signature -➤ [@balancer-labs/v2-distributors]: ✓ stakes with a permit signature to a recipient -➤ [@balancer-labs/v2-distributors]: with two stakes -➤ [@balancer-labs/v2-distributors]: ✓ sends expected amount of reward token to the rewards contract -➤ [@balancer-labs/v2-distributors]: ✓ emits RewardAdded when an allocation is stored -➤ [@balancer-labs/v2-distributors]: when the rewarder has called notifyRewardAmount -➤ [@balancer-labs/v2-distributors]: ✓ distributes the reward according to the fraction of staked LP tokens -➤ [@balancer-labs/v2-distributors]: ✓ allows a user to claim the reward to an EOA -➤ [@balancer-labs/v2-distributors]: ✓ allows a user to claim the reward to internal balance -➤ [@balancer-labs/v2-distributors]: ✓ emits RewardPaid when an allocation is claimed -➤ [@balancer-labs/v2-distributors]: with a second distribution from the same rewarder -➤ [@balancer-labs/v2-distributors]: ✓ calculates totalEarned from both distributions -➤ [@balancer-labs/v2-distributors]: with a second distributions from another rewarder -➤ [@balancer-labs/v2-distributors]: ✓ calculates totalEarned from both distributions -➤ [@balancer-labs/v2-distributors]: with two pools -➤ [@balancer-labs/v2-distributors]: ✓ allows you to claim across multiple pools (431ms) -➤ [@balancer-labs/v2-distributors]: - emits RewardPaid for each pool -➤ [@balancer-labs/v2-distributors]: -➤ [@balancer-labs/v2-distributors]: Staking contract -➤ [@balancer-labs/v2-distributors]: with a stake and a reward -➤ [@balancer-labs/v2-distributors]: ✓ allows a user to claim the reward to a callback contract -➤ [@balancer-labs/v2-distributors]: ✓ calls the callback on the contract -➤ [@balancer-labs/v2-distributors]: -➤ [@balancer-labs/v2-distributors]: Reinvestor -➤ [@balancer-labs/v2-distributors]: with a stake and a reward -➤ [@balancer-labs/v2-distributors]: with a pool to claim into -➤ [@balancer-labs/v2-distributors]: ✓ emits PoolBalanceChanged when a LP claims to weighted pool (413ms) -➤ [@balancer-labs/v2-distributors]: ✓ mints bpt to a LP when they claim to weighted pool (380ms) -➤ [@balancer-labs/v2-distributors]: addReward -➤ [@balancer-labs/v2-distributors]: ✓ returns rewards that are unused in reinvestment (438ms) -➤ [@balancer-labs/v2-distributors]: -➤ [@balancer-labs/v2-distributors]: -➤ [@balancer-labs/v2-distributors]: 37 passing (41s) -➤ [@balancer-labs/v2-distributors]: 1 pending -➤ [@balancer-labs/v2-distributors]: ➤ [@balancer-labs/v2-solidity-utils]: ➤ [@balancer-labs/v2-solidity-utils]: ➤ [@balancer-labs/v2-solidity-utils]: BalancerErrors diff --git a/pkg/deployments/tasks/deprecated/20210811-ldo-merkle/test/task.fork.ts b/pkg/deployments/tasks/deprecated/20210811-ldo-merkle/test/task.fork.ts index 84be0d04a3..d464202bd1 100644 --- a/pkg/deployments/tasks/deprecated/20210811-ldo-merkle/test/task.fork.ts +++ b/pkg/deployments/tasks/deprecated/20210811-ldo-merkle/test/task.fork.ts @@ -1,10 +1,9 @@ import hre, { ethers } from 'hardhat'; -import { Contract, BigNumber, utils } from 'ethers'; +import { Contract, BigNumber } from 'ethers'; import { bn, fp } from '@balancer-labs/v2-helpers/src/numbers'; import { expectEqualWithError } from '@balancer-labs/v2-helpers/src/test/relativeError'; -import { MerkleTree } from '@balancer-labs/v2-distributors/lib/merkleTree'; -import { deploy } from '@balancer-labs/v2-helpers/src/contract'; +import { MerkleTree } from '@balancer-labs/v2-helpers/src/merkleTree'; import { MAX_UINT256 } from '@balancer-labs/v2-helpers/src/constants'; import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/dist/src/signer-with-address'; @@ -66,17 +65,5 @@ describeForkTest('MerkleRedeem', 'mainnet', 14850000, function () { await distributor.connect(lp).claimWeek(lp.address, bn(1), fp(66), proof); expectEqualWithError(await token.balanceOf(lp.address), fp(66), fp(1)); }); - - it('can claim a reward to a callback', async () => { - await distributor.connect(whale).seedAllocations(bn(2), root, fp(100)); - - const calldata = utils.defaultAbiCoder.encode([], []); - const callbackContract = await deploy('v2-distributors/MockRewardCallback', { args: [] }); - - const claims = [{ week: bn(2), balance: fp(66), merkleProof: proof }]; - - await distributor.connect(lp).claimWeeksWithCallback(lp.address, callbackContract.address, calldata, claims); - expectEqualWithError(await token.balanceOf(callbackContract.address), fp(66), fp(1)); - }); }); }); diff --git a/pkg/deployments/tasks/deprecated/20210913-bal-arbitrum-merkle/test/task.fork.ts b/pkg/deployments/tasks/deprecated/20210913-bal-arbitrum-merkle/test/task.fork.ts index 26d1bf7b2d..a879097cfa 100644 --- a/pkg/deployments/tasks/deprecated/20210913-bal-arbitrum-merkle/test/task.fork.ts +++ b/pkg/deployments/tasks/deprecated/20210913-bal-arbitrum-merkle/test/task.fork.ts @@ -1,10 +1,9 @@ import hre, { ethers } from 'hardhat'; -import { Contract, BigNumber, utils } from 'ethers'; +import { Contract, BigNumber } from 'ethers'; import { bn, fp } from '@balancer-labs/v2-helpers/src/numbers'; import { expectEqualWithError } from '@balancer-labs/v2-helpers/src/test/relativeError'; -import { MerkleTree } from '@balancer-labs/v2-distributors/lib/merkleTree'; -import { deploy } from '@balancer-labs/v2-helpers/src/contract'; +import { MerkleTree } from '@balancer-labs/v2-helpers/src/merkleTree'; import { MAX_UINT256 } from '@balancer-labs/v2-helpers/src/constants'; import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/dist/src/signer-with-address'; @@ -66,17 +65,5 @@ describeForkTest('MerkleRedeem', 'arbitrum', 846769, function () { await distributor.connect(lp).claimWeek(lp.address, bn(1), fp(66), proof); expectEqualWithError(await token.balanceOf(lp.address), fp(66), fp(1)); }); - - it('can claim a reward to a callback', async () => { - await distributor.connect(whale).seedAllocations(bn(2), root, fp(100)); - - const calldata = utils.defaultAbiCoder.encode([], []); - const callbackContract = await deploy('v2-distributors/MockRewardCallback', { args: [] }); - - const claims = [{ week: bn(2), balance: fp(66), merkleProof: proof }]; - - await distributor.connect(lp).claimWeeksWithCallback(lp.address, callbackContract.address, calldata, claims); - expectEqualWithError(await token.balanceOf(callbackContract.address), fp(66), fp(1)); - }); }); }); diff --git a/pkg/deployments/tasks/deprecated/20210928-mcb-arbitrum-merkle/test/task.fork.ts b/pkg/deployments/tasks/deprecated/20210928-mcb-arbitrum-merkle/test/task.fork.ts index ec58e14f9e..f4827c9a62 100644 --- a/pkg/deployments/tasks/deprecated/20210928-mcb-arbitrum-merkle/test/task.fork.ts +++ b/pkg/deployments/tasks/deprecated/20210928-mcb-arbitrum-merkle/test/task.fork.ts @@ -1,10 +1,9 @@ import hre, { ethers } from 'hardhat'; -import { Contract, BigNumber, utils } from 'ethers'; +import { Contract, BigNumber } from 'ethers'; import { bn, fp } from '@balancer-labs/v2-helpers/src/numbers'; import { expectEqualWithError } from '@balancer-labs/v2-helpers/src/test/relativeError'; -import { MerkleTree } from '@balancer-labs/v2-distributors/lib/merkleTree'; -import { deploy } from '@balancer-labs/v2-helpers/src/contract'; +import { MerkleTree } from '@balancer-labs/v2-helpers/src/merkleTree'; import { MAX_UINT256 } from '@balancer-labs/v2-helpers/src/constants'; import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/dist/src/signer-with-address'; @@ -66,17 +65,5 @@ describeForkTest('MerkleRedeem', 'arbitrum', 1731663, function () { await distributor.connect(lp).claimWeek(lp.address, bn(1), fp(66), proof); expectEqualWithError(await token.balanceOf(lp.address), fp(66), fp(1)); }); - - it('can claim a reward to a callback', async () => { - await distributor.connect(whale).seedAllocations(bn(2), root, fp(100)); - - const calldata = utils.defaultAbiCoder.encode([], []); - const callbackContract = await deploy('v2-distributors/MockRewardCallback', { args: [] }); - - const claims = [{ week: bn(2), balance: fp(66), merkleProof: proof }]; - - await distributor.connect(lp).claimWeeksWithCallback(lp.address, callbackContract.address, calldata, claims); - expectEqualWithError(await token.balanceOf(callbackContract.address), fp(66), fp(1)); - }); }); }); diff --git a/pkg/distributors/contracts/MerkleOrchard.sol b/pkg/distributors/contracts/MerkleOrchard.sol deleted file mode 100644 index ebe464a331..0000000000 --- a/pkg/distributors/contracts/MerkleOrchard.sol +++ /dev/null @@ -1,361 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -pragma experimental ABIEncoderV2; - -import "@balancer-labs/v2-interfaces/contracts/solidity-utils/openzeppelin/IERC20.sol"; -import "@balancer-labs/v2-interfaces/contracts/distributors/IDistributorCallback.sol"; -import "@balancer-labs/v2-interfaces/contracts/vault/IVault.sol"; -import "@balancer-labs/v2-interfaces/contracts/vault/IAsset.sol"; - -import "@balancer-labs/v2-solidity-utils/contracts/openzeppelin/MerkleProof.sol"; -import "@balancer-labs/v2-solidity-utils/contracts/openzeppelin/SafeERC20.sol"; - -pragma solidity ^0.7.0; - -contract MerkleOrchard { - using SafeERC20 for IERC20; - - // Recorded distributions - // channelId > distributionId - mapping(bytes32 => uint256) private _nextDistributionId; - // channelId > distributionId > root - mapping(bytes32 => mapping(uint256 => bytes32)) private _distributionRoot; - // channelId > claimer > distributionId / 256 (word index) -> bitmap - mapping(bytes32 => mapping(address => mapping(uint256 => uint256))) private _claimedBitmap; - // channelId > balance - mapping(bytes32 => uint256) private _remainingBalance; - - event DistributionAdded( - address indexed distributor, - IERC20 indexed token, - uint256 distributionId, - bytes32 merkleRoot, - uint256 amount - ); - event DistributionClaimed( - address indexed distributor, - IERC20 indexed token, - uint256 distributionId, - address indexed claimer, - address recipient, - uint256 amount - ); - - IVault private immutable _vault; - - constructor(IVault vault) { - _vault = vault; - } - - struct Claim { - uint256 distributionId; - uint256 balance; - address distributor; - uint256 tokenIndex; - bytes32[] merkleProof; - } - - // Getters - function getVault() public view returns (IVault) { - return _vault; - } - - function getDistributionRoot( - IERC20 token, - address distributor, - uint256 distributionId - ) external view returns (bytes32) { - bytes32 channelId = _getChannelId(token, distributor); - return _distributionRoot[channelId][distributionId]; - } - - function getRemainingBalance(IERC20 token, address distributor) external view returns (uint256) { - bytes32 channelId = _getChannelId(token, distributor); - return _remainingBalance[channelId]; - } - - /** - * @notice distribution ids must be sequential and can have an optional offset - */ - function getNextDistributionId(IERC20 token, address distributor) external view returns (uint256) { - bytes32 channelId = _getChannelId(token, distributor); - return _nextDistributionId[channelId]; - } - - function isClaimed( - IERC20 token, - address distributor, - uint256 distributionId, - address claimer - ) public view returns (bool) { - (uint256 distributionWordIndex, uint256 distributionBitIndex) = _getIndices(distributionId); - - bytes32 channelId = _getChannelId(token, distributor); - return (_claimedBitmap[channelId][claimer][distributionWordIndex] & (1 << distributionBitIndex)) != 0; - } - - function verifyClaim( - IERC20 token, - address distributor, - uint256 distributionId, - address claimer, - uint256 claimedBalance, - bytes32[] memory merkleProof - ) external view returns (bool) { - bytes32 channelId = _getChannelId(token, distributor); - return _verifyClaim(channelId, distributionId, claimer, claimedBalance, merkleProof); - } - - // Claim functions - - /** - * @notice Allows anyone to claim multiple distributions for a claimer. - */ - function claimDistributions( - address claimer, - Claim[] memory claims, - IERC20[] memory tokens - ) external { - _processClaims(claimer, claimer, claims, tokens, false); - } - - /** - * @notice Allows a user to claim their own multiple distributions to internal balance. - */ - function claimDistributionsToInternalBalance( - address claimer, - Claim[] memory claims, - IERC20[] memory tokens - ) external { - require(msg.sender == claimer, "user must claim own balance"); - _processClaims(claimer, claimer, claims, tokens, true); - } - - /** - * @notice Allows a user to claim their own several distributions to a callback. - */ - function claimDistributionsWithCallback( - address claimer, - Claim[] memory claims, - IERC20[] memory tokens, - IDistributorCallback callbackContract, - bytes calldata callbackData - ) external { - require(msg.sender == claimer, "user must claim own balance"); - _processClaims(claimer, address(callbackContract), claims, tokens, true); - callbackContract.distributorCallback(callbackData); - } - - /** - * @notice Allows a distributor to add funds to the contract as a merkle tree. - */ - function createDistribution( - IERC20 token, - bytes32 merkleRoot, - uint256 amount, - uint256 distributionId - ) external { - address distributor = msg.sender; - - bytes32 channelId = _getChannelId(token, distributor); - require( - _nextDistributionId[channelId] == distributionId || _nextDistributionId[channelId] == 0, - "invalid distribution ID" - ); - token.safeTransferFrom(distributor, address(this), amount); - - token.safeApprove(address(getVault()), amount); - IVault.UserBalanceOp[] memory ops = new IVault.UserBalanceOp[](1); - - ops[0] = IVault.UserBalanceOp({ - asset: IAsset(address(token)), - amount: amount, - sender: address(this), - recipient: payable(address(this)), - kind: IVault.UserBalanceOpKind.DEPOSIT_INTERNAL - }); - - getVault().manageUserBalance(ops); - - _remainingBalance[channelId] += amount; - _distributionRoot[channelId][distributionId] = merkleRoot; - _nextDistributionId[channelId] = distributionId + 1; - emit DistributionAdded(distributor, token, distributionId, merkleRoot, amount); - } - - // Helper functions - - function _getChannelId(IERC20 token, address distributor) private pure returns (bytes32) { - return keccak256(abi.encodePacked(token, distributor)); - } - - function _processClaims( - address claimer, - address recipient, - Claim[] memory claims, - IERC20[] memory tokens, - bool asInternalBalance - ) internal { - uint256[] memory amounts = new uint256[](tokens.length); - - // To save gas when setting claimed statuses in storage, we group claims for each channel and word index - // (referred to as a 'claims set'), aggregating the claim bits to set and total claimed amount, only committing - // to storage when changing claims sets (or when processing the last claim). - // This means that callers should sort claims by grouping distribution channels and distributions with the same - // word index in order to achieve reduced gas costs. - - // Variables to support claims set aggregation - bytes32 currentChannelId; // Since channel ids are a hash, the initial zero id can be safely considered invalid - uint256 currentWordIndex; - - uint256 currentBits; // The accumulated claimed bits to set in storage - uint256 currentClaimAmount; // The accumulated tokens to be claimed from the current channel (not claims set!) - - Claim memory claim; - for (uint256 i = 0; i < claims.length; i++) { - claim = claims[i]; - - // New scope to avoid stack-too-deep issues - { - (uint256 distributionWordIndex, uint256 distributionBitIndex) = _getIndices(claim.distributionId); - - if (currentChannelId == _getChannelId(tokens[claim.tokenIndex], claim.distributor)) { - if (currentWordIndex == distributionWordIndex) { - // Same claims set as the previous one: simply track the new bit to set. - currentBits |= 1 << distributionBitIndex; - } else { - // This case is an odd exception: the claims set is not the same, but the channel id is. This - // happens for example when there are so many distributions that they don't fit in a single 32 - // byte bitmap. - // Since the channel is the same, we can continue accumulating the claim amount, but must commit - // the previous claim bits as they correspond to a different word index. - _setClaimedBits(currentChannelId, claimer, currentWordIndex, currentBits); - - // Start a new claims set, except channel id is the same as the previous one, and amount is not - // reset. - currentWordIndex = distributionWordIndex; - currentBits = 1 << distributionBitIndex; - } - - // Amounts are always accumulated for the same channel id - currentClaimAmount += claim.balance; - } else { - // Skip initial invalid claims set - if (currentChannelId != bytes32(0)) { - // Commit previous claims set - _setClaimedBits(currentChannelId, claimer, currentWordIndex, currentBits); - _deductClaimedBalance(currentChannelId, currentClaimAmount); - } - - // Start a new claims set - currentChannelId = _getChannelId(tokens[claim.tokenIndex], claim.distributor); - currentWordIndex = distributionWordIndex; - currentBits = 1 << distributionBitIndex; - currentClaimAmount = claim.balance; - } - } - - // Since a claims set is only committed if the next one is not part of the same set, the last claims set - // must be manually committed always. - if (i == claims.length - 1) { - _setClaimedBits(currentChannelId, claimer, currentWordIndex, currentBits); - _deductClaimedBalance(currentChannelId, currentClaimAmount); - } - - require( - _verifyClaim(currentChannelId, claim.distributionId, claimer, claim.balance, claim.merkleProof), - "incorrect merkle proof" - ); - - // Note that balances to claim are here accumulated *per token*, independent of the distribution channel and - // claims set accounting. - amounts[claim.tokenIndex] += claim.balance; - - emit DistributionClaimed( - claim.distributor, - tokens[claim.tokenIndex], - claim.distributionId, - claimer, - recipient, - claim.balance - ); - } - - IVault.UserBalanceOpKind kind = asInternalBalance - ? IVault.UserBalanceOpKind.TRANSFER_INTERNAL - : IVault.UserBalanceOpKind.WITHDRAW_INTERNAL; - IVault.UserBalanceOp[] memory ops = new IVault.UserBalanceOp[](tokens.length); - - for (uint256 i = 0; i < tokens.length; i++) { - ops[i] = IVault.UserBalanceOp({ - asset: IAsset(address(tokens[i])), - amount: amounts[i], - sender: address(this), - recipient: payable(recipient), - kind: kind - }); - } - getVault().manageUserBalance(ops); - } - - /** - * @dev Sets the bits set in `newClaimsBitmap` for the corresponding distribution. - */ - function _setClaimedBits( - bytes32 channelId, - address claimer, - uint256 wordIndex, - uint256 newClaimsBitmap - ) private { - uint256 currentBitmap = _claimedBitmap[channelId][claimer][wordIndex]; - - // All newly set bits must not have been previously set - require((newClaimsBitmap & currentBitmap) == 0, "cannot claim twice"); - - _claimedBitmap[channelId][claimer][wordIndex] = currentBitmap | newClaimsBitmap; - } - - /** - * @dev Deducts `balanceBeingClaimed` from a distribution channel's allocation. This isolates tokens accross - * distribution channels, and prevents claims for one channel from using the tokens of another one. - */ - function _deductClaimedBalance(bytes32 channelId, uint256 balanceBeingClaimed) private { - require( - _remainingBalance[channelId] >= balanceBeingClaimed, - "distributor hasn't provided sufficient tokens for claim" - ); - _remainingBalance[channelId] -= balanceBeingClaimed; - } - - function _verifyClaim( - bytes32 channelId, - uint256 distributionId, - address claimer, - uint256 claimedBalance, - bytes32[] memory merkleProof - ) internal view returns (bool) { - bytes32 leaf = keccak256(abi.encodePacked(claimer, claimedBalance)); - return MerkleProof.verify(merkleProof, _distributionRoot[channelId][distributionId], leaf); - } - - function _getIndices(uint256 distributionId) - private - pure - returns (uint256 distributionWordIndex, uint256 distributionBitIndex) - { - distributionWordIndex = distributionId / 256; - distributionBitIndex = distributionId % 256; - } -} diff --git a/pkg/distributors/contracts/MerkleRedeem.sol b/pkg/distributors/contracts/MerkleRedeem.sol deleted file mode 100644 index 5b3db335b5..0000000000 --- a/pkg/distributors/contracts/MerkleRedeem.sol +++ /dev/null @@ -1,201 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -pragma experimental ABIEncoderV2; - -import "@balancer-labs/v2-interfaces/contracts/solidity-utils/openzeppelin/IERC20.sol"; -import "@balancer-labs/v2-interfaces/contracts/distributors/IDistributorCallback.sol"; -import "@balancer-labs/v2-interfaces/contracts/vault/IVault.sol"; -import "@balancer-labs/v2-interfaces/contracts/vault/IAsset.sol"; - -import "@balancer-labs/v2-solidity-utils/contracts/openzeppelin/MerkleProof.sol"; -import "@balancer-labs/v2-solidity-utils/contracts/openzeppelin/SafeERC20.sol"; -import "@balancer-labs/v2-solidity-utils/contracts/openzeppelin/Ownable.sol"; -import "@balancer-labs/v2-solidity-utils/contracts/math/FixedPoint.sol"; - -pragma solidity ^0.7.0; - -contract MerkleRedeem is Ownable { - using FixedPoint for uint256; - using SafeERC20 for IERC20; - - IERC20 public immutable rewardToken; - - // Recorded weeks - mapping(uint256 => bytes32) public weekMerkleRoots; - mapping(uint256 => mapping(address => bool)) public claimed; - - IVault public immutable vault; - - event RewardAdded(address indexed token, uint256 amount); - event RewardPaid(address indexed user, address indexed rewardToken, uint256 amount); - - constructor(IVault _vault, IERC20 _rewardToken) { - vault = _vault; - rewardToken = _rewardToken; - _rewardToken.safeApprove(address(_vault), type(uint256).max); - } - - function _disburse(address recipient, uint256 balance) private { - if (balance > 0) { - emit RewardPaid(recipient, address(rewardToken), balance); - rewardToken.safeTransfer(recipient, balance); - } - } - - function _disburseToInternalBalance(address recipient, uint256 balance) private { - if (balance > 0) { - IVault.UserBalanceOp[] memory ops = new IVault.UserBalanceOp[](1); - - ops[0] = IVault.UserBalanceOp({ - asset: IAsset(address(rewardToken)), - amount: balance, - sender: address(this), - recipient: payable(recipient), - kind: IVault.UserBalanceOpKind.DEPOSIT_INTERNAL - }); - - emit RewardPaid(recipient, address(rewardToken), balance); - vault.manageUserBalance(ops); - } - } - - /** - * @notice Allows a user to claim a particular week's worth of rewards - */ - function claimWeek( - address liquidityProvider, - uint256 week, - uint256 claimedBalance, - bytes32[] memory merkleProof - ) external { - require(msg.sender == liquidityProvider, "user must claim own balance"); - require(!claimed[week][liquidityProvider], "cannot claim twice"); - require(verifyClaim(liquidityProvider, week, claimedBalance, merkleProof), "Incorrect merkle proof"); - - claimed[week][liquidityProvider] = true; - _disburse(liquidityProvider, claimedBalance); - } - - struct Claim { - uint256 week; - uint256 balance; - bytes32[] merkleProof; - } - - function _processClaims(address liquidityProvider, Claim[] memory claims) internal returns (uint256 totalBalance) { - Claim memory claim; - for (uint256 i = 0; i < claims.length; i++) { - claim = claims[i]; - - require(!claimed[claim.week][liquidityProvider], "cannot claim twice"); - require( - verifyClaim(liquidityProvider, claim.week, claim.balance, claim.merkleProof), - "Incorrect merkle proof" - ); - - totalBalance = totalBalance.add(claim.balance); - claimed[claim.week][liquidityProvider] = true; - } - } - - /** - * @notice Allows a user to claim multiple weeks of reward - */ - function claimWeeks(address liquidityProvider, Claim[] memory claims) external { - require(msg.sender == liquidityProvider, "user must claim own balance"); - - uint256 totalBalance = _processClaims(liquidityProvider, claims); - _disburse(liquidityProvider, totalBalance); - } - - /** - * @notice Allows a user to claim multiple weeks of reward to internal balance - */ - function claimWeeksToInternalBalance(address liquidityProvider, Claim[] memory claims) external { - require(msg.sender == liquidityProvider, "user must claim own balance"); - - uint256 totalBalance = _processClaims(liquidityProvider, claims); - - _disburseToInternalBalance(liquidityProvider, totalBalance); - } - - /** - * @notice Allows a user to claim several weeks of rewards to a callback - */ - function claimWeeksWithCallback( - address liquidityProvider, - IDistributorCallback callbackContract, - bytes calldata callbackData, - Claim[] memory claims - ) external { - require(msg.sender == liquidityProvider, "user must claim own balance"); - uint256 totalBalance = _processClaims(liquidityProvider, claims); - - _disburseToInternalBalance(address(callbackContract), totalBalance); - - callbackContract.distributorCallback(callbackData); - } - - function claimStatus( - address liquidityProvider, - uint256 begin, - uint256 end - ) external view returns (bool[] memory) { - require(begin <= end, "weeks must be specified in ascending order"); - uint256 size = 1 + end - begin; - bool[] memory arr = new bool[](size); - for (uint256 i = 0; i < size; i++) { - arr[i] = claimed[begin + i][liquidityProvider]; - } - return arr; - } - - function merkleRoots(uint256 begin, uint256 end) external view returns (bytes32[] memory) { - require(begin <= end, "weeks must be specified in ascending order"); - uint256 size = 1 + end - begin; - bytes32[] memory arr = new bytes32[](size); - for (uint256 i = 0; i < size; i++) { - arr[i] = weekMerkleRoots[begin + i]; - } - return arr; - } - - function verifyClaim( - address liquidityProvider, - uint256 week, - uint256 claimedBalance, - bytes32[] memory merkleProof - ) public view returns (bool) { - bytes32 leaf = keccak256(abi.encodePacked(liquidityProvider, claimedBalance)); - return MerkleProof.verify(merkleProof, weekMerkleRoots[week], leaf); - } - - /** - * @notice - * Allows the owner to add funds to the contract as a merkle tree, These tokens will - * be withdrawn from the sender - * These will be pulled from the user - */ - function seedAllocations( - uint256 week, - bytes32 _merkleRoot, - uint256 amount - ) external onlyOwner { - require(weekMerkleRoots[week] == bytes32(0), "cannot rewrite merkle root"); - weekMerkleRoots[week] = _merkleRoot; - rewardToken.safeTransferFrom(msg.sender, address(this), amount); - emit RewardAdded(address(rewardToken), amount); - } -} diff --git a/pkg/distributors/contracts/test/MockRewardCallback.sol b/pkg/distributors/contracts/test/MockRewardCallback.sol deleted file mode 100644 index 9af62e2551..0000000000 --- a/pkg/distributors/contracts/test/MockRewardCallback.sol +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. - -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -pragma solidity ^0.7.0; -pragma experimental ABIEncoderV2; - -import "@balancer-labs/v2-interfaces/contracts/distributors/IDistributorCallback.sol"; - -contract MockRewardCallback is IDistributorCallback { - event CallbackReceived(); - - function distributorCallback(bytes calldata) external override { - emit CallbackReceived(); - return; - } -} diff --git a/pkg/distributors/foundry.toml b/pkg/distributors/foundry.toml deleted file mode 120000 index 2d554be761..0000000000 --- a/pkg/distributors/foundry.toml +++ /dev/null @@ -1 +0,0 @@ -../../foundry.toml \ No newline at end of file diff --git a/pkg/distributors/hardhat.config.ts b/pkg/distributors/hardhat.config.ts deleted file mode 100644 index f218c31a09..0000000000 --- a/pkg/distributors/hardhat.config.ts +++ /dev/null @@ -1,20 +0,0 @@ -import '@nomiclabs/hardhat-ethers'; -import '@nomiclabs/hardhat-waffle'; -import 'hardhat-ignore-warnings'; - -import { hardhatBaseConfig } from '@balancer-labs/v2-common'; -import { name } from './package.json'; - -import { task } from 'hardhat/config'; -import { TASK_COMPILE } from 'hardhat/builtin-tasks/task-names'; -import overrideQueryFunctions from '@balancer-labs/v2-helpers/plugins/overrideQueryFunctions'; - -task(TASK_COMPILE).setAction(overrideQueryFunctions); - -export default { - solidity: { - compilers: hardhatBaseConfig.compilers, - overrides: { ...hardhatBaseConfig.overrides(name) }, - }, - warnings: hardhatBaseConfig.warnings, -}; diff --git a/pkg/distributors/package.json b/pkg/distributors/package.json deleted file mode 100644 index bc990a4371..0000000000 --- a/pkg/distributors/package.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "name": "@balancer-labs/v2-distributors", - "version": "0.1.0", - "description": "Utilities for distributing tokens from internal balance", - "license": "GPL-3.0-only", - "homepage": "https://github.com/balancer-labs/balancer-v2-monorepo/pkg/distributors#readme", - "repository": { - "type": "git", - "url": "https://github.com/balancer-labs/balancer-v2-monorepo.git", - "directory": "pkg/distributors" - }, - "bugs": { - "url": "https://github.com/balancer-labs/balancer-v2-monorepo/issues" - }, - "scripts": { - "build": "yarn compile", - "compile": "hardhat compile && rm -rf artifacts/build-info", - "compile:watch": "nodemon --ext sol --exec yarn compile", - "lint": "yarn lint:solidity && yarn lint:typescript", - "lint:solidity": "solhint 'contracts/**/*.sol'", - "lint:typescript": "eslint --max-warnings 0 . --ext .ts", - "test": "yarn compile && mocha --extension ts --require hardhat/register --require @balancer-labs/v2-common/setupTests --recursive", - "test:fast": "yarn compile && mocha --extension ts --require hardhat/register --require @balancer-labs/v2-common/setupTests --recursive --parallel --exit", - "test:watch": "nodemon --ext js,ts --watch test --watch lib --exec 'clear && yarn test --no-compile'", - "test-fuzz": "forge test" - }, - "devDependencies": { - "@balancer-labs/balancer-js": "workspace:*", - "@balancer-labs/v2-interfaces": "workspace:*", - "@balancer-labs/v2-solidity-utils": "workspace:*", - "@nomiclabs/hardhat-ethers": "^2.2.1", - "@nomiclabs/hardhat-waffle": "^2.0.3", - "@types/chai": "^4.3.3", - "@types/lodash": "^4.14.186", - "@types/mocha": "^10.0.0", - "@types/node": "^14.14.31", - "@typescript-eslint/eslint-plugin": "^5.41.0", - "@typescript-eslint/parser": "^5.41.0", - "chai": "^4.3.6", - "decimal.js": "^10.4.2", - "eslint": "^8.26.0", - "eslint-plugin-mocha-no-only": "^1.1.1", - "eslint-plugin-prettier": "^4.2.1", - "ethereum-waffle": "^3.4.4", - "ethereumjs-util": "^7.1.5", - "ethers": "^5.7.2", - "hardhat": "^2.12.2", - "hardhat-ignore-warnings": "^0.2.4", - "lodash.frompairs": "^4.0.1", - "lodash.pick": "^4.4.0", - "lodash.range": "^3.2.0", - "lodash.times": "^4.3.2", - "lodash.zip": "^4.2.0", - "mocha": "^10.1.0", - "nodemon": "^2.0.20", - "prettier": "^2.7.1", - "prettier-plugin-solidity": "v1.0.0-alpha.59", - "solhint": "^3.2.0", - "solhint-plugin-prettier": "^0.0.4", - "ts-node": "^10.9.1", - "typescript": "^4.0.2" - } -} diff --git a/pkg/distributors/test/MerkleOrchard.test.ts b/pkg/distributors/test/MerkleOrchard.test.ts deleted file mode 100644 index 08dbc630e1..0000000000 --- a/pkg/distributors/test/MerkleOrchard.test.ts +++ /dev/null @@ -1,618 +0,0 @@ -import { ethers } from 'hardhat'; -import { BytesLike, BigNumber } from 'ethers'; -import { expect } from 'chai'; -import { Contract, utils } from 'ethers'; - -import { deploy } from '@balancer-labs/v2-helpers/src/contract'; -import Vault from '@balancer-labs/v2-helpers/src/models/vault/Vault'; -import { expectBalanceChange } from '@balancer-labs/v2-helpers/src/test/tokenBalance'; -import Token from '@balancer-labs/v2-helpers/src/models/tokens/Token'; -import TokenList from '@balancer-labs/v2-helpers/src/models/tokens/TokenList'; -import * as expectEvent from '@balancer-labs/v2-helpers/src/test/expectEvent'; - -import { bn } from '@balancer-labs/v2-helpers/src/numbers'; -import { MerkleTree } from '../lib/merkleTree'; - -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/dist/src/signer-with-address'; - -function encodeElement(address: string, balance: BigNumber): string { - return ethers.utils.solidityKeccak256(['address', 'uint'], [address, balance]); -} - -interface Claim { - distributionId: BigNumber; - balance: BigNumber; - distributor: string; - tokenIndex: number; - merkleProof: BytesLike[]; -} - -describe('MerkleOrchard', () => { - let tokens: TokenList, - token1: Token, - token2: Token, - vault: Contract, - merkleOrchard: Contract, - tokenAddresses: string[]; - - let admin: SignerWithAddress, - distributor: SignerWithAddress, - claimer1: SignerWithAddress, - claimer2: SignerWithAddress, - other: SignerWithAddress; - const tokenInitialBalance = bn(100e18); - const claimBalance = bn('9876'); - - before('setup', async () => { - [, admin, distributor, claimer1, claimer2, other] = await ethers.getSigners(); - }); - - sharedBeforeEach('deploy vault and tokens', async () => { - const vaultHelper = await Vault.create({ admin }); - vault = vaultHelper.instance; - - tokens = await TokenList.create(['DAI', 'BAT'], { sorted: true }); - token1 = tokens.DAI; - token2 = tokens.BAT; - tokenAddresses = [token1.address, token2.address]; - - merkleOrchard = await deploy('MerkleOrchard', { - args: [vault.address], - from: admin, - }); - await tokens.mint({ to: distributor.address, amount: tokenInitialBalance }); - await tokens.approve({ to: merkleOrchard.address, from: [distributor] }); - }); - - it('stores an allocation', async () => { - const elements = [encodeElement(claimer1.address, claimBalance)]; - const merkleTree = new MerkleTree(elements); - const root = merkleTree.getHexRoot(); - - await merkleOrchard.connect(distributor).createDistribution(token1.address, root, claimBalance, bn(1)); - - const proof = merkleTree.getHexProof(elements[0]); - - const result = await merkleOrchard.verifyClaim( - token1.address, - distributor.address, - 1, - claimer1.address, - claimBalance, - proof - ); - expect(result).to.equal(true); - }); - - it('emits DistributionAdded when an allocation is stored', async () => { - const elements = [encodeElement(claimer1.address, claimBalance)]; - const merkleTree = new MerkleTree(elements); - const root = merkleTree.getHexRoot(); - - const receipt = await ( - await merkleOrchard.connect(distributor).createDistribution(token1.address, root, claimBalance, bn(1)) - ).wait(); - - expectEvent.inReceipt(receipt, 'DistributionAdded', { - token: token1.address, - distributor: distributor.address, - merkleRoot: root, - amount: claimBalance, - }); - }); - - it('requisitions tokens when it stores a balance', async () => { - const elements = [encodeElement(claimer1.address, claimBalance)]; - const merkleTree = new MerkleTree(elements); - const root = merkleTree.getHexRoot(); - - await expectBalanceChange( - () => merkleOrchard.connect(distributor).createDistribution(token1.address, root, claimBalance, bn(1)), - tokens, - [{ account: merkleOrchard, changes: { DAI: claimBalance } }], - vault - ); - }); - - it("increments the distribution channel's distributionId", async () => { - const elements = [encodeElement(claimer1.address, claimBalance)]; - const merkleTree = new MerkleTree(elements); - const root = merkleTree.getHexRoot(); - - expect(await merkleOrchard.getNextDistributionId(token1.address, distributor.address)).to.be.eq(0); - expect(await merkleOrchard.getNextDistributionId(token1.address, claimer1.address)).to.be.eq(0); - - await merkleOrchard.connect(distributor).createDistribution(token1.address, root, claimBalance, 0); - - expect(await merkleOrchard.getNextDistributionId(token1.address, distributor.address)).to.be.eq(1); - expect(await merkleOrchard.getNextDistributionId(token1.address, claimer1.address)).to.be.eq(0); - - await merkleOrchard.connect(distributor).createDistribution(token1.address, root, claimBalance, 1); - - expect(await merkleOrchard.getNextDistributionId(token1.address, distributor.address)).to.be.eq(2); - expect(await merkleOrchard.getNextDistributionId(token1.address, claimer1.address)).to.be.eq(0); - }); - - it('allows the distribution id to have an offset', async () => { - const elements = [encodeElement(claimer1.address, claimBalance)]; - const merkleTree = new MerkleTree(elements); - const root = merkleTree.getHexRoot(); - await merkleOrchard.connect(distributor).createDistribution(token1.address, root, claimBalance, 72); - expect(await merkleOrchard.getNextDistributionId(token1.address, distributor.address)).to.be.eq(73); - await merkleOrchard.connect(distributor).createDistribution(token1.address, root, claimBalance, 73); - expect(await merkleOrchard.getNextDistributionId(token1.address, distributor.address)).to.be.eq(74); - }); - - context('when provided an invalid distribution ID', () => { - it('reverts', async () => { - const elements = [encodeElement(claimer1.address, claimBalance)]; - const merkleTree = new MerkleTree(elements); - const root = merkleTree.getHexRoot(); - await merkleOrchard.connect(distributor).createDistribution(token1.address, root, claimBalance, 0); - // Here's we're providing an old ID (i.e. we've accidentally created the same distribution twice) - await expect( - merkleOrchard.connect(distributor).createDistribution(token1.address, root, claimBalance, 0) - ).to.be.revertedWith('invalid distribution ID'); - }); - }); - - it('stores multiple allocations', async () => { - const claimBalance0 = bn('1000'); - const claimBalance1 = bn('2000'); - - const elements = [encodeElement(claimer1.address, claimBalance0), encodeElement(claimer2.address, claimBalance1)]; - const merkleTree = new MerkleTree(elements); - const root = merkleTree.getHexRoot(); - - await merkleOrchard.connect(distributor).createDistribution(token1.address, root, bn('3000'), 1); - - const proof0 = merkleTree.getHexProof(elements[0]); - let result = await merkleOrchard.verifyClaim( - token1.address, - distributor.address, - 1, - claimer1.address, - claimBalance0, - proof0 - ); - expect(result).to.equal(true); //"account 0 should have an allocation"; - - const proof1 = merkleTree.getHexProof(elements[1]); - result = await merkleOrchard.verifyClaim( - token1.address, - distributor.address, - 1, - claimer2.address, - claimBalance1, - proof1 - ); - expect(result).to.equal(true); // "account 1 should have an allocation"; - }); - - describe('with an allocation', () => { - const claimableBalance = bn('1000'); - let elements: string[]; - let merkleTree: MerkleTree; - let claims: Claim[]; - - sharedBeforeEach(async () => { - elements = [encodeElement(claimer1.address, claimableBalance)]; - merkleTree = new MerkleTree(elements); - const root = merkleTree.getHexRoot(); - - await merkleOrchard.connect(distributor).createDistribution(token1.address, root, claimableBalance, 1); - const merkleProof: BytesLike[] = merkleTree.getHexProof(elements[0]); - - claims = [ - { - distributionId: bn(1), - balance: claimableBalance, - distributor: distributor.address, - tokenIndex: 0, - merkleProof, - }, - ]; - }); - - it('allows the user to claim a single distribution', async () => { - await expectBalanceChange( - () => merkleOrchard.connect(claimer1).claimDistributions(claimer1.address, claims, tokenAddresses), - tokens, - [{ account: claimer1, changes: { DAI: claimableBalance } }] - ); - }); - - it('emits DistributionClaimed when an allocation is claimed', async () => { - const receipt = await ( - await merkleOrchard.connect(claimer1).claimDistributions(claimer1.address, claims, tokenAddresses) - ).wait(); - - expectEvent.inReceipt(receipt, 'DistributionClaimed', { - claimer: claimer1.address, - recipient: claimer1.address, - token: token1.address, - distributor: distributor.address, - amount: claimableBalance, - }); - }); - - it('marks claimed distributions as claimed', async () => { - await merkleOrchard.connect(claimer1).claimDistributions(claimer1.address, claims, tokenAddresses); - - const isClaimed = await merkleOrchard.isClaimed(token1.address, distributor.address, 1, claimer1.address); - expect(isClaimed).to.equal(true); // "claim should be marked as claimed"; - }); - - it('allows anyone to claim for another user', async () => { - await expectBalanceChange( - () => merkleOrchard.connect(other).claimDistributions(claimer1.address, claims, tokenAddresses), - tokens, - [{ account: claimer1, changes: { DAI: claimableBalance } }] - ); - }); - - it('reverts when a user attempts to claim to internal balance for another user', async () => { - const errorMsg = 'user must claim own balance'; - await expect( - merkleOrchard.connect(other).claimDistributionsToInternalBalance(claimer1.address, claims, tokenAddresses) - ).to.be.revertedWith(errorMsg); - }); - - it('reverts when the user attempts to claim the wrong balance', async () => { - const incorrectClaimedBalance = bn('666'); - const merkleProof = merkleTree.getHexProof(elements[0]); - const errorMsg = 'incorrect merkle proof'; - - const claimsWithIncorrectClaimableBalance = [ - { - distributionId: 1, - balance: incorrectClaimedBalance, - distributor: distributor.address, - tokenIndex: 0, - merkleProof, - }, - ]; - await expect( - merkleOrchard - .connect(claimer1) - .claimDistributions(claimer1.address, claimsWithIncorrectClaimableBalance, tokenAddresses) - ).to.be.revertedWith(errorMsg); - }); - - it('reverts when the user attempts to claim twice', async () => { - await merkleOrchard.connect(claimer1).claimDistributions(claimer1.address, claims, tokenAddresses); - - const errorMsg = 'cannot claim twice'; - await expect( - merkleOrchard.connect(claimer1).claimDistributions(claimer1.address, claims, tokenAddresses) - ).to.be.revertedWith(errorMsg); - }); - - it('reverts when an admin attempts to overwrite an allocationn', async () => { - const elements2 = [ - encodeElement(claimer1.address, claimableBalance), - encodeElement(claimer2.address, claimableBalance), - ]; - const merkleTree2 = new MerkleTree(elements2); - const root2 = merkleTree2.getHexRoot(); - - const errorMsg = 'invalid distribution ID'; - expect( - merkleOrchard.connect(admin).createDistribution(token1.address, root2, claimableBalance.mul(2), 1) - ).to.be.revertedWith(errorMsg); - }); - }); - - describe('with several allocations in the same channel', () => { - const claimBalance1 = bn('1000'); - const claimBalance2 = bn('1234'); - - let elements1: string[]; - let merkleTree1: MerkleTree; - let root1: string; - - let elements2: string[]; - let merkleTree2: MerkleTree; - let root2: string; - - sharedBeforeEach(async () => { - elements1 = [encodeElement(claimer1.address, claimBalance1)]; - merkleTree1 = new MerkleTree(elements1); - root1 = merkleTree1.getHexRoot(); - - elements2 = [encodeElement(claimer1.address, claimBalance2)]; - merkleTree2 = new MerkleTree(elements2); - root2 = merkleTree2.getHexRoot(); - - await merkleOrchard.connect(distributor).createDistribution(token1.address, root1, claimBalance1, bn(1)); - - await merkleOrchard.connect(distributor).createDistribution(token1.address, root2, claimBalance2, bn(2)); - }); - - it('allows the user to claim multiple distributions at once', async () => { - const claimedBalance1 = bn('1000'); - const claimedBalance2 = bn('1234'); - - const proof1: BytesLike[] = merkleTree1.getHexProof(elements1[0]); - const proof2: BytesLike[] = merkleTree2.getHexProof(elements2[0]); - - const claims: Claim[] = [ - { - distributionId: bn(1), - balance: claimedBalance1, - distributor: distributor.address, - tokenIndex: 0, - merkleProof: proof1, - }, - { - distributionId: bn(2), - balance: claimedBalance2, - distributor: distributor.address, - tokenIndex: 0, - merkleProof: proof2, - }, - ]; - - await expectBalanceChange( - () => merkleOrchard.connect(claimer1).claimDistributions(claimer1.address, claims, tokenAddresses), - tokens, - [{ account: claimer1, changes: { DAI: bn('2234') } }] - ); - }); - - it('allows the user to claim multiple distributions at once to internal balance', async () => { - const claimedBalance1 = bn('1000'); - const claimedBalance2 = bn('1234'); - - const proof1: BytesLike[] = merkleTree1.getHexProof(elements1[0]); - const proof2: BytesLike[] = merkleTree2.getHexProof(elements2[0]); - - const claims: Claim[] = [ - { - distributionId: bn(1), - balance: claimedBalance1, - distributor: distributor.address, - tokenIndex: 0, - merkleProof: proof1, - }, - { - distributionId: bn(2), - balance: claimedBalance2, - distributor: distributor.address, - tokenIndex: 0, - merkleProof: proof2, - }, - ]; - - await expectBalanceChange( - () => - merkleOrchard.connect(claimer1).claimDistributionsToInternalBalance(claimer1.address, claims, tokenAddresses), - tokens, - [{ account: claimer1, changes: { DAI: bn('2234') } }], - vault - ); - }); - - it('reports distributions as unclaimed', async () => { - expect(await merkleOrchard.isClaimed(token1.address, distributor.address, 1, claimer1.address)).to.eql(false); - expect(await merkleOrchard.isClaimed(token1.address, distributor.address, 2, claimer1.address)).to.eql(false); - }); - - describe('with a callback', () => { - let callbackContract: Contract; - let claims: Claim[]; - - sharedBeforeEach('set up mock callback', async () => { - callbackContract = await deploy('MockRewardCallback'); - - const proof1: BytesLike[] = merkleTree1.getHexProof(elements1[0]); - const proof2: BytesLike[] = merkleTree2.getHexProof(elements2[0]); - - claims = [ - { - distributionId: bn(1), - balance: claimBalance1, - distributor: distributor.address, - tokenIndex: 0, - merkleProof: proof1, - }, - { - distributionId: bn(2), - balance: claimBalance2, - distributor: distributor.address, - tokenIndex: 0, - merkleProof: proof2, - }, - ]; - }); - - it('allows a user to claim the tokens to a callback contract', async () => { - const expectedClaim = claimBalance1.add(claimBalance2); - const calldata = utils.defaultAbiCoder.encode([], []); - - await expectBalanceChange( - () => - merkleOrchard - .connect(claimer1) - .claimDistributionsWithCallback( - claimer1.address, - claims, - tokenAddresses, - callbackContract.address, - calldata - ), - tokens, - [{ account: callbackContract.address, changes: { DAI: ['very-near', expectedClaim] } }], - vault - ); - }); - - it('calls the callback on the contract', async () => { - const calldata = utils.defaultAbiCoder.encode([], []); - - const receipt = await ( - await merkleOrchard - .connect(claimer1) - .claimDistributionsWithCallback( - claimer1.address, - claims, - tokenAddresses, - callbackContract.address, - calldata - ) - ).wait(); - - expectEvent.inIndirectReceipt(receipt, callbackContract.interface, 'CallbackReceived', {}); - }); - }); - - describe('When a user has claimed one of their allocations', async () => { - sharedBeforeEach(async () => { - const claimedBalance1 = bn('1000'); - const proof1 = merkleTree1.getHexProof(elements1[0]); - - const claims: Claim[] = [ - { - distributionId: bn(1), - balance: claimedBalance1, - distributor: distributor.address, - tokenIndex: 0, - merkleProof: proof1, - }, - ]; - - await merkleOrchard.connect(claimer1).claimDistributions(claimer1.address, claims, tokenAddresses); - }); - - it('reports one of the distributions as claimed', async () => { - expect(await merkleOrchard.isClaimed(token1.address, distributor.address, 1, claimer1.address)).to.eql(true); - expect(await merkleOrchard.isClaimed(token1.address, distributor.address, 2, claimer1.address)).to.eql(false); - }); - }); - }); - - describe('with allocations in the same channel, crossing claim storage slots', () => { - const claimBalance1 = bn('1000'); - const claimBalance2 = bn('1234'); - - let elements1: string[]; - let merkleTree1: MerkleTree; - let root1: string; - - let elements2: string[]; - let merkleTree2: MerkleTree; - let root2: string; - - let proof1: BytesLike[]; - let proof2: BytesLike[]; - let claims: Claim[]; - - sharedBeforeEach(async () => { - elements1 = [encodeElement(claimer1.address, claimBalance1)]; - merkleTree1 = new MerkleTree(elements1); - root1 = merkleTree1.getHexRoot(); - proof1 = merkleTree1.getHexProof(elements1[0]); - - elements2 = [encodeElement(claimer1.address, claimBalance2)]; - merkleTree2 = new MerkleTree(elements2); - root2 = merkleTree2.getHexRoot(); - proof2 = merkleTree2.getHexProof(elements2[0]); - - // very end of one claim storage slot - await merkleOrchard.connect(distributor).createDistribution(token1.address, root1, claimBalance1, bn(255)); - // very beginning of the next claim storage slot - await merkleOrchard.connect(distributor).createDistribution(token1.address, root2, claimBalance2, bn(256)); - - claims = [ - { - distributionId: bn(255), - balance: claimBalance1, - distributor: distributor.address, - tokenIndex: 0, - merkleProof: proof1, - }, - { - distributionId: bn(256), - balance: claimBalance2, - distributor: distributor.address, - tokenIndex: 0, - merkleProof: proof2, - }, - ]; - }); - - it('allows the user to claim multiple distributions at once', async () => { - await expectBalanceChange( - () => merkleOrchard.connect(claimer1).claimDistributions(claimer1.address, claims, tokenAddresses), - tokens, - [{ account: claimer1, changes: { DAI: bn('2234') } }] - ); - }); - - it('marks distributions as claimed', async () => { - await merkleOrchard.connect(claimer1).claimDistributions(claimer1.address, claims, tokenAddresses); - expect(await merkleOrchard.isClaimed(token1.address, distributor.address, 254, claimer1.address)).to.eql(false); - expect(await merkleOrchard.isClaimed(token1.address, distributor.address, 255, claimer1.address)).to.eql(true); - expect(await merkleOrchard.isClaimed(token1.address, distributor.address, 256, claimer1.address)).to.eql(true); - expect(await merkleOrchard.isClaimed(token1.address, distributor.address, 257, claimer1.address)).to.eql(false); - }); - }); - - describe('with several allocations accross multiple channels', () => { - const claimBalance1 = bn('1000'); - const claimBalance2 = bn('1234'); - - let elements1: string[]; - let merkleTree1: MerkleTree; - let root1: string; - - let elements2: string[]; - let merkleTree2: MerkleTree; - let root2: string; - - sharedBeforeEach(async () => { - elements1 = [encodeElement(claimer1.address, claimBalance1)]; - merkleTree1 = new MerkleTree(elements1); - root1 = merkleTree1.getHexRoot(); - - elements2 = [encodeElement(claimer1.address, claimBalance2)]; - merkleTree2 = new MerkleTree(elements2); - root2 = merkleTree2.getHexRoot(); - - await merkleOrchard.connect(distributor).createDistribution(token1.address, root1, claimBalance1, bn(1)); - - await merkleOrchard.connect(distributor).createDistribution(token2.address, root2, claimBalance2, bn(2)); - }); - - it('allows the user to claim multiple distributions at once', async () => { - const claimedBalance1 = bn('1000'); - const claimedBalance2 = bn('1234'); - - const proof1: BytesLike[] = merkleTree1.getHexProof(elements1[0]); - const proof2: BytesLike[] = merkleTree2.getHexProof(elements2[0]); - - const claims: Claim[] = [ - { - distributionId: bn(1), - balance: claimedBalance1, - distributor: distributor.address, - tokenIndex: 0, - merkleProof: proof1, - }, - { - distributionId: bn(2), - balance: claimedBalance2, - distributor: distributor.address, - tokenIndex: 1, - merkleProof: proof2, - }, - ]; - - await expectBalanceChange( - () => merkleOrchard.connect(claimer1).claimDistributions(claimer1.address, claims, tokenAddresses), - tokens, - [{ account: claimer1, changes: { DAI: bn('1000'), BAT: bn('1234') } }] - ); - }); - }); -}); diff --git a/pkg/distributors/test/MerkleRedeem.test.ts b/pkg/distributors/test/MerkleRedeem.test.ts deleted file mode 100644 index 89b3baf31a..0000000000 --- a/pkg/distributors/test/MerkleRedeem.test.ts +++ /dev/null @@ -1,352 +0,0 @@ -import { ethers } from 'hardhat'; -import { BytesLike, BigNumber } from 'ethers'; -import { expect } from 'chai'; -import { Contract, utils } from 'ethers'; - -import { deploy } from '@balancer-labs/v2-helpers/src/contract'; -import Vault from '@balancer-labs/v2-helpers/src/models/vault/Vault'; -import { expectBalanceChange } from '@balancer-labs/v2-helpers/src/test/tokenBalance'; -import Token from '@balancer-labs/v2-helpers/src/models/tokens/Token'; -import TokenList from '@balancer-labs/v2-helpers/src/models/tokens/TokenList'; -import * as expectEvent from '@balancer-labs/v2-helpers/src/test/expectEvent'; - -import { bn } from '@balancer-labs/v2-helpers/src/numbers'; -import { MerkleTree } from '../lib/merkleTree'; - -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/dist/src/signer-with-address'; - -function encodeElement(address: string, balance: BigNumber): string { - return ethers.utils.solidityKeccak256(['address', 'uint'], [address, balance]); -} - -describe('MerkleRedeem', () => { - let rewardTokens: TokenList, rewardToken: Token, vault: Contract, merkleRedeem: Contract; - - let admin: SignerWithAddress, lp1: SignerWithAddress, lp2: SignerWithAddress, other: SignerWithAddress; - const rewardTokenInitialBalance = bn(100e18); - const week1 = bn(1); - - before('setup', async () => { - [, admin, lp1, lp2, other] = await ethers.getSigners(); - }); - - sharedBeforeEach('deploy vault and tokens', async () => { - const vaultHelper = await Vault.create({ admin }); - vault = vaultHelper.instance; - - rewardTokens = await TokenList.create(['DAI'], { sorted: true }); - rewardToken = rewardTokens.DAI; - - merkleRedeem = await deploy('MerkleRedeem', { - args: [vault.address, rewardToken.address], - from: admin, - }); - await rewardTokens.mint({ to: admin.address, amount: rewardTokenInitialBalance }); - await rewardTokens.approve({ to: merkleRedeem.address, from: [admin] }); - }); - - it('stores an allocation', async () => { - const claimBalance = bn('9876'); - - const elements = [encodeElement(lp1.address, claimBalance)]; - const merkleTree = new MerkleTree(elements); - const root = merkleTree.getHexRoot(); - - await merkleRedeem.connect(admin).seedAllocations(week1, root, claimBalance); - - const proof = merkleTree.getHexProof(elements[0]); - - const result = await merkleRedeem.verifyClaim(lp1.address, 1, claimBalance, proof); - expect(result).to.equal(true); - }); - - it('emits RewardAdded when an allocation is stored', async () => { - const claimBalance = bn('9876'); - - const elements = [encodeElement(lp1.address, claimBalance)]; - const merkleTree = new MerkleTree(elements); - const root = merkleTree.getHexRoot(); - - const receipt = await (await merkleRedeem.connect(admin).seedAllocations(week1, root, claimBalance)).wait(); - - expectEvent.inReceipt(receipt, 'RewardAdded', { - token: rewardToken.address, - amount: claimBalance, - }); - }); - - it('requisitions tokens when it stores a balance', async () => { - const claimBalance = bn('9876'); - - const elements = [encodeElement(lp1.address, claimBalance)]; - const merkleTree = new MerkleTree(elements); - const root = merkleTree.getHexRoot(); - - await expectBalanceChange( - () => merkleRedeem.connect(admin).seedAllocations(week1, root, claimBalance), - rewardTokens, - [ - { account: admin, changes: { DAI: claimBalance.mul(-1) } }, - { account: merkleRedeem, changes: { DAI: claimBalance } }, - ] - ); - }); - - it('reverts when unauthorized to seed allocation', async () => { - const claimBalance = bn('9876'); - - const elements = [encodeElement(lp1.address, claimBalance)]; - const merkleTree = new MerkleTree(elements); - const root = merkleTree.getHexRoot(); - - await expect(merkleRedeem.connect(other).seedAllocations(week1, root, claimBalance)).to.be.revertedWith( - 'CALLER_IS_NOT_OWNER' - ); - }); - - it('stores multiple allocations', async () => { - const claimBalance0 = bn('1000'); - const claimBalance1 = bn('2000'); - - const elements = [encodeElement(lp1.address, claimBalance0), encodeElement(lp2.address, claimBalance1)]; - const merkleTree = new MerkleTree(elements); - const root = merkleTree.getHexRoot(); - - await merkleRedeem.connect(admin).seedAllocations(1, root, bn('3000')); - - const proof0 = merkleTree.getHexProof(elements[0]); - let result = await merkleRedeem.verifyClaim(lp1.address, 1, claimBalance0, proof0); - expect(result).to.equal(true); //"account 0 should have an allocation"; - - const proof1 = merkleTree.getHexProof(elements[1]); - result = await merkleRedeem.verifyClaim(lp2.address, 1, claimBalance1, proof1); - expect(result).to.equal(true); // "account 1 should have an allocation"; - }); - - describe('with an allocation', () => { - const claimableBalance = bn('1000'); - let elements: string[]; - let merkleTree: MerkleTree; - - sharedBeforeEach(async () => { - elements = [encodeElement(lp1.address, claimableBalance)]; - merkleTree = new MerkleTree(elements); - const root = merkleTree.getHexRoot(); - - await merkleRedeem.connect(admin).seedAllocations(1, root, claimableBalance); - }); - - it('allows the user to claimWeek', async () => { - const merkleProof: BytesLike[] = merkleTree.getHexProof(elements[0]); - - await expectBalanceChange( - () => merkleRedeem.connect(lp1).claimWeek(lp1.address, 1, claimableBalance, merkleProof), - rewardTokens, - [{ account: lp1, changes: { DAI: claimableBalance } }] - ); - }); - - it('emits RewardPaid when an allocation is claimed', async () => { - const merkleProof: BytesLike[] = merkleTree.getHexProof(elements[0]); - - const receipt = await ( - await merkleRedeem.connect(lp1).claimWeek(lp1.address, 1, claimableBalance, merkleProof) - ).wait(); - - expectEvent.inReceipt(receipt, 'RewardPaid', { - user: lp1.address, - rewardToken: rewardToken.address, - amount: claimableBalance, - }); - }); - - it('marks claimed weeks as claimed', async () => { - const merkleProof: BytesLike[] = merkleTree.getHexProof(elements[0]); - await merkleRedeem.connect(lp1).claimWeek(lp1.address, 1, claimableBalance, merkleProof); - - const isClaimed = await merkleRedeem.claimed(1, lp1.address); - expect(isClaimed).to.equal(true); // "claim should be marked as claimed"; - }); - - it('reverts when a user attempts to claim for another user', async () => { - const merkleProof = merkleTree.getHexProof(elements[0]); - - const errorMsg = 'user must claim own balance'; - await expect( - merkleRedeem.connect(other).claimWeek(lp1.address, 1, claimableBalance, merkleProof) - ).to.be.revertedWith(errorMsg); - }); - - it('reverts when the user attempts to claim the wrong balance', async () => { - const incorrectClaimedBalance = bn('666'); - const merkleProof = merkleTree.getHexProof(elements[0]); - const errorMsg = 'Incorrect merkle proof'; - await expect( - merkleRedeem.connect(lp1).claimWeek(lp1.address, 1, incorrectClaimedBalance, merkleProof) - ).to.be.revertedWith(errorMsg); - }); - - it('reverts when the user attempts to claim twice', async () => { - const merkleProof = merkleTree.getHexProof(elements[0]); - - await merkleRedeem.connect(lp1).claimWeek(lp1.address, 1, claimableBalance, merkleProof); - - const errorMsg = 'cannot claim twice'; - await expect( - merkleRedeem.connect(lp1).claimWeek(lp1.address, 1, claimableBalance, merkleProof) - ).to.be.revertedWith(errorMsg); - }); - - it('reverts when an admin attempts to overwrite an allocationn', async () => { - const elements2 = [encodeElement(lp1.address, claimableBalance), encodeElement(lp2.address, claimableBalance)]; - const merkleTree2 = new MerkleTree(elements2); - const root2 = merkleTree2.getHexRoot(); - - const errorMsg = 'cannot rewrite merkle root'; - await expect(merkleRedeem.connect(admin).seedAllocations(1, root2, claimableBalance.mul(2))).to.be.revertedWith( - errorMsg - ); - }); - }); - - describe('with several allocations', () => { - const claimBalance1 = bn('1000'); - const claimBalance2 = bn('1234'); - - let elements1: string[]; - let merkleTree1: MerkleTree; - let root1: string; - - let elements2: string[]; - let merkleTree2: MerkleTree; - let root2: string; - - sharedBeforeEach(async () => { - elements1 = [encodeElement(lp1.address, claimBalance1)]; - merkleTree1 = new MerkleTree(elements1); - root1 = merkleTree1.getHexRoot(); - - elements2 = [encodeElement(lp1.address, claimBalance2)]; - merkleTree2 = new MerkleTree(elements2); - root2 = merkleTree2.getHexRoot(); - - await merkleRedeem.connect(admin).seedAllocations(week1, root1, claimBalance1); - - await merkleRedeem.connect(admin).seedAllocations(bn(2), root2, claimBalance2); - }); - - it('allows the user to claim multiple weeks at once', async () => { - const claimedBalance1 = bn('1000'); - const claimedBalance2 = bn('1234'); - - const proof1: BytesLike[] = merkleTree1.getHexProof(elements1[0]); - const proof2: BytesLike[] = merkleTree2.getHexProof(elements2[0]); - - const merkleProofs = [ - { week: week1, balance: claimedBalance1, merkleProof: proof1 }, - { week: bn(2), balance: claimedBalance2, merkleProof: proof2 }, - ]; - - await expectBalanceChange(() => merkleRedeem.connect(lp1).claimWeeks(lp1.address, merkleProofs), rewardTokens, [ - { account: lp1, changes: { DAI: bn('2234') } }, - ]); - }); - - it('allows the user to claim multiple weeks at once to internal balance', async () => { - const claimedBalance1 = bn('1000'); - const claimedBalance2 = bn('1234'); - - const proof1: BytesLike[] = merkleTree1.getHexProof(elements1[0]); - const proof2: BytesLike[] = merkleTree2.getHexProof(elements2[0]); - - const merkleProofs = [ - { week: week1, balance: claimedBalance1, merkleProof: proof1 }, - { week: bn(2), balance: claimedBalance2, merkleProof: proof2 }, - ]; - - await expectBalanceChange( - () => merkleRedeem.connect(lp1).claimWeeksToInternalBalance(lp1.address, merkleProofs), - rewardTokens, - [{ account: lp1, changes: { DAI: bn('2234') } }], - vault - ); - }); - - it('reports weeks as unclaimed', async () => { - const expectedResult = [false, false]; - const result = await merkleRedeem.claimStatus(lp1.address, 1, 2); - expect(result).to.eql(expectedResult); - }); - - it('returns an array of merkle roots', async () => { - const expectedResult = [root1, root2]; - const result = await merkleRedeem.merkleRoots(1, 2); - expect(result).to.eql(expectedResult); // "claim status should be accurate" - }); - - interface Claim { - week: BigNumber; - balance: BigNumber; - merkleProof: BytesLike[]; - } - - describe('with a callback', () => { - let callbackContract: Contract; - let claims: Claim[]; - - sharedBeforeEach('set up mock callback', async () => { - callbackContract = await deploy('MockRewardCallback'); - - const proof1: BytesLike[] = merkleTree1.getHexProof(elements1[0]); - const proof2: BytesLike[] = merkleTree2.getHexProof(elements2[0]); - - claims = [ - { week: bn(1), balance: claimBalance1, merkleProof: proof1 }, - { week: bn(2), balance: claimBalance2, merkleProof: proof2 }, - ]; - }); - - it('allows a user to claim the reward to a callback contract', async () => { - const expectedReward = claimBalance1.add(claimBalance2); - const calldata = utils.defaultAbiCoder.encode([], []); - - await expectBalanceChange( - () => - merkleRedeem.connect(lp1).claimWeeksWithCallback(lp1.address, callbackContract.address, calldata, claims), - rewardTokens, - [{ account: callbackContract.address, changes: { DAI: ['very-near', expectedReward] } }], - vault - ); - }); - - it('calls the callback on the contract', async () => { - const calldata = utils.defaultAbiCoder.encode([], []); - - const receipt = await ( - await merkleRedeem - .connect(lp1) - .claimWeeksWithCallback(lp1.address, callbackContract.address, calldata, claims) - ).wait(); - - expectEvent.inIndirectReceipt(receipt, callbackContract.interface, 'CallbackReceived', {}); - }); - }); - - describe('When a user has claimed one of their allocations', async () => { - sharedBeforeEach(async () => { - const claimedBalance1 = bn('1000'); - const proof1 = merkleTree1.getHexProof(elements1[0]); - - const merkleProofs = [{ week: week1, balance: claimedBalance1, merkleProof: proof1 }]; - - await merkleRedeem.connect(lp1).claimWeeks(lp1.address, merkleProofs); - }); - - it('reports one of the weeks as claimed', async () => { - const expectedResult = [true, false]; - const result = await merkleRedeem.claimStatus(lp1.address, 1, 2); - expect(result).to.eql(expectedResult); - }); - }); - }); -}); diff --git a/pkg/distributors/tsconfig.json b/pkg/distributors/tsconfig.json deleted file mode 100644 index b4e69ae1f9..0000000000 --- a/pkg/distributors/tsconfig.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "dist" - } -} diff --git a/pvt/benchmarks/merkleClaim.ts b/pvt/benchmarks/merkleClaim.ts deleted file mode 100644 index 4795e9d8d3..0000000000 --- a/pvt/benchmarks/merkleClaim.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { BigNumber } from 'ethers'; -import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/dist/src/signer-with-address'; - -import { setupEnvironment } from './misc'; -import { printGas } from '@balancer-labs/v2-helpers/src/numbers'; -import { BytesLike, solidityKeccak256 } from 'ethers/lib/utils'; -import { deploy } from '@balancer-labs/v2-helpers/src/contract'; -import TokenList from '@balancer-labs/v2-helpers/src/models/tokens/TokenList'; -import Vault from '@balancer-labs/v2-helpers/src/models/vault/Vault'; - -interface Claim { - distributionId: BigNumber; - balance: BigNumber; - distributor: string; - tokenIndex: BigNumber; - merkleProof: BytesLike[]; -} - -let vault: Vault; -let tokens: TokenList; -let trader: SignerWithAddress; - -async function main() { - ({ vault, tokens, trader } = await setupEnvironment()); - - for (let i = 1; i <= 5; i++) { - console.log(`\n# Claiming ${i} distributions`); - - await claimDistributions(i, false); - await claimDistributions(i, true); - } -} - -async function claimDistributions(numberOfDistributions: number, useInternalBalance: boolean) { - console.log(`\n## ${useInternalBalance ? 'Using Internal Balance' : 'Sending and receiving tokens'}`); - - const merkleOrchard = await deploy('v2-distributors/MerkleOrchard', { args: [vault.address] }); - - const token = tokens.first; - const tokenAddresses = tokens.subset(1).addresses; - const amount = BigNumber.from(100); - const merkleLeaf = solidityKeccak256(['address', 'uint256'], [trader.address, amount]); - - const claims: Claim[] = Array.from({ length: numberOfDistributions }, (_, distribution) => ({ - distributionId: BigNumber.from(distribution), - balance: amount, - distributor: trader.address, - tokenIndex: BigNumber.from(0), - merkleProof: [], - })); - - await token.approve(merkleOrchard.address, amount.mul(numberOfDistributions), { from: trader }); - for (let distribution = 0; distribution < numberOfDistributions; ++distribution) { - await ( - await merkleOrchard.connect(trader).createDistribution(token.address, merkleLeaf, amount, distribution) - ).wait(); - } - - let receipt; - if (useInternalBalance) { - receipt = await ( - await merkleOrchard.connect(trader).claimDistributionsToInternalBalance(trader.address, claims, tokenAddresses) - ).wait(); - } else { - receipt = await ( - await merkleOrchard.connect(trader).claimDistributions(trader.address, claims, tokenAddresses) - ).wait(); - } - - console.log( - `${numberOfDistributions} claims: ${printGas(receipt.gasUsed)} (${printGas( - receipt.gasUsed / numberOfDistributions - )} per claim)` - ); -} - -main() - .then(() => process.exit(0)) - .catch((error) => { - console.error(error); - process.exit(1); - }); diff --git a/pvt/benchmarks/package.json b/pvt/benchmarks/package.json index 0e4c70c972..223b16164f 100644 --- a/pvt/benchmarks/package.json +++ b/pvt/benchmarks/package.json @@ -9,9 +9,8 @@ "measure-single-pair": "hardhat run singlePair.ts", "measure-multihop": "hardhat run multihop.ts", "measure-join-exit": "hardhat run joinExit.ts", - "measure-merkle-claim": "hardhat run merkleClaim.ts", "measure-relayer": "hardhat run relayer.ts", - "benchmark": "yarn measure-deployment && yarn measure-single-pair && yarn measure-multihop && yarn measure-join-exit && yarn measure-merkle-claim && yarn measure-relayer", + "benchmark": "yarn measure-deployment && yarn measure-single-pair && yarn measure-multihop && yarn measure-join-exit && yarn measure-relayer", "lint": "yarn lint:typescript", "lint:typescript": "eslint . --ext .ts --ignore-path ../../.eslintignore --max-warnings 0" }, diff --git a/pvt/helpers/package.json b/pvt/helpers/package.json index 272da726a7..1cee03e802 100644 --- a/pvt/helpers/package.json +++ b/pvt/helpers/package.json @@ -14,6 +14,7 @@ "@types/lodash": "^4.14.186", "@types/node": "^14.14.31", "decimal.js": "^10.4.2", + "ethereumjs-util": "^7.1.5", "ethers": "^5.7.2", "hardhat": "^2.12.2", "lodash.frompairs": "^4.0.1", diff --git a/pkg/distributors/lib/merkleTree.ts b/pvt/helpers/src/merkleTree.ts similarity index 100% rename from pkg/distributors/lib/merkleTree.ts rename to pvt/helpers/src/merkleTree.ts diff --git a/yarn.lock b/yarn.lock index 94c63b7429..7811ab841f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -183,52 +183,11 @@ __metadata: ts-node: ^10.9.1 typescript: ^4.0.2 peerDependencies: - "@nomiclabs/hardhat-ethers": ^2.0.6 + "@ethersproject/contracts": ^5.0.0 hardhat: ^2.8.3 languageName: unknown linkType: soft -"@balancer-labs/v2-distributors@workspace:pkg/distributors": - version: 0.0.0-use.local - resolution: "@balancer-labs/v2-distributors@workspace:pkg/distributors" - dependencies: - "@balancer-labs/balancer-js": "workspace:*" - "@balancer-labs/v2-interfaces": "workspace:*" - "@balancer-labs/v2-solidity-utils": "workspace:*" - "@nomiclabs/hardhat-ethers": ^2.2.1 - "@nomiclabs/hardhat-waffle": ^2.0.3 - "@types/chai": ^4.3.3 - "@types/lodash": ^4.14.186 - "@types/mocha": ^10.0.0 - "@types/node": ^14.14.31 - "@typescript-eslint/eslint-plugin": ^5.41.0 - "@typescript-eslint/parser": ^5.41.0 - chai: ^4.3.6 - decimal.js: ^10.4.2 - eslint: ^8.26.0 - eslint-plugin-mocha-no-only: ^1.1.1 - eslint-plugin-prettier: ^4.2.1 - ethereum-waffle: ^3.4.4 - ethereumjs-util: ^7.1.5 - ethers: ^5.7.2 - hardhat: ^2.12.2 - hardhat-ignore-warnings: ^0.2.4 - lodash.frompairs: ^4.0.1 - lodash.pick: ^4.4.0 - lodash.range: ^3.2.0 - lodash.times: ^4.3.2 - lodash.zip: ^4.2.0 - mocha: ^10.1.0 - nodemon: ^2.0.20 - prettier: ^2.7.1 - prettier-plugin-solidity: v1.0.0-alpha.59 - solhint: ^3.2.0 - solhint-plugin-prettier: ^0.0.4 - ts-node: ^10.9.1 - typescript: ^4.0.2 - languageName: unknown - linkType: soft - "@balancer-labs/v2-governance-scripts@workspace:pkg/governance-scripts": version: 0.0.0-use.local resolution: "@balancer-labs/v2-governance-scripts@workspace:pkg/governance-scripts" @@ -278,6 +237,7 @@ __metadata: decimal.js: ^10.4.2 eslint: ^8.26.0 eslint-plugin-prettier: ^4.2.1 + ethereumjs-util: ^7.1.5 ethers: ^5.7.2 hardhat: ^2.12.2 lodash.frompairs: ^4.0.1