From 5f25ba9fc5a44f335b0a7cf5dbfd5df9ceba1cdf Mon Sep 17 00:00:00 2001 From: Chris Walker Date: Wed, 21 Feb 2024 12:01:52 -0800 Subject: [PATCH 01/10] mock of new flow --- .../claim/PerUserTrancheVestingMerkle.sol | 71 +++++++++++++++++++ .../contracts/claim/abstract/Distributor.sol | 2 +- .../claim/abstract/PerUserTrancheVesting.sol | 49 +++++++++++++ 3 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 packages/hardhat/contracts/claim/PerUserTrancheVestingMerkle.sol create mode 100644 packages/hardhat/contracts/claim/abstract/PerUserTrancheVesting.sol diff --git a/packages/hardhat/contracts/claim/PerUserTrancheVestingMerkle.sol b/packages/hardhat/contracts/claim/PerUserTrancheVestingMerkle.sol new file mode 100644 index 00000000..d94f9e2b --- /dev/null +++ b/packages/hardhat/contracts/claim/PerUserTrancheVestingMerkle.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import { IERC20 } from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; + +import { TrancheVesting, Tranche } from './abstract/TrancheVesting.sol'; +import { MerkleSet } from './abstract/MerkleSet.sol'; + +contract PerUserTrancheVestingMerkle is PerUserTrancheVesting, MerkleSet { + constructor( + IERC20 _token, + uint256 _total, + string memory _uri, // information on the sale (e.g. merkle proofs) + uint256 _voteFactor, + Tranche[] memory _tranches, + bytes32 _merkleRoot, + uint160 _maxDelayTime // the maximum delay time for the fair queue + ) + TrancheVesting( + _token, + _total, + _uri, + _voteFactor, + _tranches, + _maxDelayTime, + uint160(uint256(_merkleRoot)) + ) + MerkleSet(_merkleRoot) + {} + + function NAME() external pure override returns (string memory) { + return 'TrancheVestingMerkle'; + } + + function VERSION() external pure override returns (uint256) { + return 3; + } + + function initializeDistributionRecord( + uint256 index, // the beneficiary's index in the merkle root + address beneficiary, // the address that will receive tokens + uint256 amount, // the total claimable by this beneficiary + bytes32[] calldata merkleProof + ) + external + validMerkleProof(keccak256(abi.encodePacked(index, beneficiary, amount)), merkleProof) + { + _initializeDistributionRecord(beneficiary, amount); + } + + function claim( + uint256 index, // the beneficiary's index in the merkle root + address beneficiary, // the address that will receive tokens + uint256 totalAmount, // the total claimable by this beneficiary + Tranche[] calldata tranches, // the tranches for the beneficiary (users can have different vesting schedules) + bytes32[] calldata merkleProof + ) + external + validMerkleProof(keccak256(abi.encodePacked(index, beneficiary, totalAmount, tranches)), merkleProof) + nonReentrant + { + // effects + uint256 claimedAmount = _executeClaim(beneficiary, totalAmount); + // interactions + _settleClaim(beneficiary, claimedAmount); + } + + function setMerkleRoot(bytes32 _merkleRoot) external onlyOwner { + _setMerkleRoot(_merkleRoot); + } +} diff --git a/packages/hardhat/contracts/claim/abstract/Distributor.sol b/packages/hardhat/contracts/claim/abstract/Distributor.sol index 22a97755..8fee75bd 100644 --- a/packages/hardhat/contracts/claim/abstract/Distributor.sol +++ b/packages/hardhat/contracts/claim/abstract/Distributor.sol @@ -113,7 +113,7 @@ abstract contract Distributor is IDistributor, ReentrancyGuard { } // get the number of tokens currently claimable by a specific use - function getClaimableAmount(address beneficiary) public view virtual returns (uint256) { + function getClaimableAmount(address beneficiary, bytes data) public view virtual returns (uint256) { require(records[beneficiary].initialized, 'Distributor: claim not initialized'); DistributionRecord memory record = records[beneficiary]; diff --git a/packages/hardhat/contracts/claim/abstract/PerUserTrancheVesting.sol b/packages/hardhat/contracts/claim/abstract/PerUserTrancheVesting.sol new file mode 100644 index 00000000..5a66a88b --- /dev/null +++ b/packages/hardhat/contracts/claim/abstract/PerUserTrancheVesting.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import { AdvancedDistributor } from './AdvancedDistributor.sol'; +import { ITrancheVesting, Tranche } from '../../interfaces/ITrancheVesting.sol'; +import { IERC20 } from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; +import "hardhat/console.sol"; + +/** + * @title PerUserTrancheVesting + * @notice Distributes funds to beneficiaries over time in tranches. + */ +abstract contract PerUserTrancheVesting is AdvancedDistributor, ITrancheVesting { + constructor( + IERC20 _token, + uint256 _total, + string memory _uri, + uint256 _voteFactor, + uint160 _maxDelayTime, + uint160 _salt + ) AdvancedDistributor(_token, _total, _uri, _voteFactor, 10000, _maxDelayTime, _salt) {} + + /** + * @notice Get the vested fraction for a beneficiary at a given time. + * @dev Before the first tranche time, the vested fraction will be 0. At times between + * tranche_i and tranche_i+1, the vested fraction will be tranche_i+1's vested fraction. + * After the last tranche time, the vested fraction will be the fraction denominator. + */ + function getVestedFraction( + address beneficiary, + uint256 time, + bytes data + ) public view override returns (uint256) { + tranches = abi.decode(data, (Tranche[])); + + uint256 delay = getFairDelayTime(beneficiary); + for (uint256 i = tranches.length; i != 0; ) { + unchecked { + --i; + } + + if (time - delay > tranches[i].time) { + return tranches[i].vestedFraction; + } + } + + return 0; + } +} From 4c04fce86eddfbee318762ee585b10e329644746 Mon Sep 17 00:00:00 2001 From: Chris Walker Date: Mon, 4 Mar 2024 14:55:39 -0800 Subject: [PATCH 02/10] working on new format --- .../contracts/claim/BasicDistributor.sol | 5 +- .../claim/ContinuousVestingMerkle.sol | 2 +- .../claim/PerUserTrancheVestingMerkle.sol | 3 +- .../claim/PriceTierVestingMerkle.sol | 2 +- .../claim/PriceTierVestingSale_2_0.sol | 6 +-- .../contracts/claim/TrancheVestingMerkle.sol | 2 +- .../claim/TrancheVestingSale_1_3.sol | 6 +-- .../claim/TrancheVestingSale_2_0.sol | 6 +-- .../claim/abstract/ContinuousVesting.sol | 3 +- .../contracts/claim/abstract/Distributor.sol | 10 ++-- .../abstract/PerAddressContinuousVesting.sol | 42 +++++++++++++++ ...sting.sol => PerAddressTrancheVesting.sol} | 1 - .../ContinuousVestingMerkleDistributor.sol | 2 +- .../factory/DistributorInitializable.sol | 8 +-- ...rAddressContinuousVestingInitializable.sol | 52 ++++++++++++++++++ .../PerAddressTrancheVestingInitializable.sol | 53 +++++++++++++++++++ .../TrancheVestingMerkleDistributor.sol | 2 +- .../contracts/interfaces/IDistributor.sol | 2 +- ...iceTierVestingSale_2_0_Distributor.test.ts | 12 ++--- ...iceTierVestingSale_2_0_Distributor.test.ts | 12 ++--- ...TrancheVestingSale_1_3_Distributor.test.ts | 8 +-- ...TrancheVestingSale_2_0_Distributor.test.ts | 14 ++--- 22 files changed, 202 insertions(+), 51 deletions(-) create mode 100644 packages/hardhat/contracts/claim/abstract/PerAddressContinuousVesting.sol rename packages/hardhat/contracts/claim/abstract/{PerUserTrancheVesting.sol => PerAddressTrancheVesting.sol} (98%) create mode 100644 packages/hardhat/contracts/claim/factory/PerAddressContinuousVestingInitializable.sol create mode 100644 packages/hardhat/contracts/claim/factory/PerAddressTrancheVestingInitializable.sol diff --git a/packages/hardhat/contracts/claim/BasicDistributor.sol b/packages/hardhat/contracts/claim/BasicDistributor.sol index 7163c3ca..db8a88e5 100644 --- a/packages/hardhat/contracts/claim/BasicDistributor.sol +++ b/packages/hardhat/contracts/claim/BasicDistributor.sol @@ -29,7 +29,8 @@ contract BasicDistributor is AdvancedDistributor { function getVestedFraction( address, /*beneficiary*/ - uint256 /*time*/ + uint256, /*time*/ + bytes calldata /*data*/ ) public view override returns (uint256) { // all tokens vest immediately return fractionDenominator; @@ -45,7 +46,7 @@ contract BasicDistributor is AdvancedDistributor { function claim(address beneficiary) external nonReentrant { // effects - uint256 claimedAmount = super._executeClaim(beneficiary, records[beneficiary].total); + uint256 claimedAmount = super._executeClaim(beneficiary, records[beneficiary].total, ""); // interactions super._settleClaim(beneficiary, claimedAmount); } diff --git a/packages/hardhat/contracts/claim/ContinuousVestingMerkle.sol b/packages/hardhat/contracts/claim/ContinuousVestingMerkle.sol index 2269766e..2ce49a7a 100644 --- a/packages/hardhat/contracts/claim/ContinuousVestingMerkle.sol +++ b/packages/hardhat/contracts/claim/ContinuousVestingMerkle.sol @@ -63,7 +63,7 @@ contract ContinuousVestingMerkle is ContinuousVesting, MerkleSet { nonReentrant { // effects - uint256 claimedAmount = super._executeClaim(beneficiary, totalAmount); + uint256 claimedAmount = super._executeClaim(beneficiary, totalAmount, ""); // interactions super._settleClaim(beneficiary, claimedAmount); } diff --git a/packages/hardhat/contracts/claim/PerUserTrancheVestingMerkle.sol b/packages/hardhat/contracts/claim/PerUserTrancheVestingMerkle.sol index d94f9e2b..aba2fd0d 100644 --- a/packages/hardhat/contracts/claim/PerUserTrancheVestingMerkle.sol +++ b/packages/hardhat/contracts/claim/PerUserTrancheVestingMerkle.sol @@ -59,8 +59,9 @@ contract PerUserTrancheVestingMerkle is PerUserTrancheVesting, MerkleSet { validMerkleProof(keccak256(abi.encodePacked(index, beneficiary, totalAmount, tranches)), merkleProof) nonReentrant { + bytes memory data = abi.encode(tranches); // effects - uint256 claimedAmount = _executeClaim(beneficiary, totalAmount); + uint256 claimedAmount = _executeClaim(beneficiary, totalAmount, data); // interactions _settleClaim(beneficiary, claimedAmount); } diff --git a/packages/hardhat/contracts/claim/PriceTierVestingMerkle.sol b/packages/hardhat/contracts/claim/PriceTierVestingMerkle.sol index 5df561cc..b5bfe6b6 100644 --- a/packages/hardhat/contracts/claim/PriceTierVestingMerkle.sol +++ b/packages/hardhat/contracts/claim/PriceTierVestingMerkle.sol @@ -69,7 +69,7 @@ contract PriceTierVestingMerkle is PriceTierVesting, MerkleSet { nonReentrant { // effects - uint256 claimedAmount = _executeClaim(beneficiary, totalAmount); + uint256 claimedAmount = _executeClaim(beneficiary, totalAmount, ""); // interactions _settleClaim(beneficiary, claimedAmount); } diff --git a/packages/hardhat/contracts/claim/PriceTierVestingSale_2_0.sol b/packages/hardhat/contracts/claim/PriceTierVestingSale_2_0.sol index f0262eb5..9f5e6e3a 100644 --- a/packages/hardhat/contracts/claim/PriceTierVestingSale_2_0.sol +++ b/packages/hardhat/contracts/claim/PriceTierVestingSale_2_0.sol @@ -100,7 +100,7 @@ contract PriceTierVestingSale_2_0 is PriceTierVesting { uint256 totalClaimableAmount = getTotalClaimableAmount(beneficiary); // effects - uint256 claimedAmount = super._executeClaim(beneficiary, totalClaimableAmount); + uint256 claimedAmount = super._executeClaim(beneficiary, totalClaimableAmount, ""); // interactions super._settleClaim(beneficiary, claimedAmount); @@ -119,8 +119,8 @@ contract PriceTierVestingSale_2_0 is PriceTierVesting { } // get the number of tokens currently claimable by a specific user - function getClaimableAmount(address beneficiary) public view override returns (uint256) { - if (records[beneficiary].initialized) return super.getClaimableAmount(beneficiary); + function getClaimableAmount(address beneficiary, bytes calldata data) public view override returns (uint256) { + if (records[beneficiary].initialized) return super.getClaimableAmount(beneficiary, data); // we can get the claimable amount prior to initialization return diff --git a/packages/hardhat/contracts/claim/TrancheVestingMerkle.sol b/packages/hardhat/contracts/claim/TrancheVestingMerkle.sol index 90675c68..7ff66ea2 100644 --- a/packages/hardhat/contracts/claim/TrancheVestingMerkle.sol +++ b/packages/hardhat/contracts/claim/TrancheVestingMerkle.sol @@ -59,7 +59,7 @@ contract TrancheVestingMerkle is TrancheVesting, MerkleSet { nonReentrant { // effects - uint256 claimedAmount = _executeClaim(beneficiary, totalAmount); + uint256 claimedAmount = _executeClaim(beneficiary, totalAmount, ""); // interactions _settleClaim(beneficiary, claimedAmount); } diff --git a/packages/hardhat/contracts/claim/TrancheVestingSale_1_3.sol b/packages/hardhat/contracts/claim/TrancheVestingSale_1_3.sol index 4768548a..18daa56c 100644 --- a/packages/hardhat/contracts/claim/TrancheVestingSale_1_3.sol +++ b/packages/hardhat/contracts/claim/TrancheVestingSale_1_3.sol @@ -92,7 +92,7 @@ contract TrancheVestingSale_1_3 is TrancheVesting { uint256 totalClaimableAmount = getTotalClaimableAmount(beneficiary); // effects - uint256 claimedAmount = super._executeClaim(beneficiary, totalClaimableAmount); + uint256 claimedAmount = super._executeClaim(beneficiary, totalClaimableAmount, ""); // interactions super._settleClaim(beneficiary, claimedAmount); @@ -111,8 +111,8 @@ contract TrancheVestingSale_1_3 is TrancheVesting { } // get the number of tokens currently claimable by a specific user - function getClaimableAmount(address beneficiary) public view override returns (uint256) { - if (records[beneficiary].initialized) return super.getClaimableAmount(beneficiary); + function getClaimableAmount(address beneficiary, bytes calldata data) public view override returns (uint256) { + if (records[beneficiary].initialized) return super.getClaimableAmount(beneficiary, data); // we can get the claimable amount prior to initialization return diff --git a/packages/hardhat/contracts/claim/TrancheVestingSale_2_0.sol b/packages/hardhat/contracts/claim/TrancheVestingSale_2_0.sol index 950bda22..9556d5da 100644 --- a/packages/hardhat/contracts/claim/TrancheVestingSale_2_0.sol +++ b/packages/hardhat/contracts/claim/TrancheVestingSale_2_0.sol @@ -90,7 +90,7 @@ contract TrancheVestingSale_2_0 is TrancheVesting { uint256 totalClaimableAmount = getTotalClaimableAmount(beneficiary); // effects - uint256 claimedAmount = super._executeClaim(beneficiary, totalClaimableAmount); + uint256 claimedAmount = super._executeClaim(beneficiary, totalClaimableAmount, ""); // interactions _settleClaim(beneficiary, claimedAmount); @@ -109,8 +109,8 @@ contract TrancheVestingSale_2_0 is TrancheVesting { } // get the number of tokens currently claimable by a specific user - function getClaimableAmount(address beneficiary) public view override returns (uint256) { - if (records[beneficiary].initialized) return super.getClaimableAmount(beneficiary); + function getClaimableAmount(address beneficiary, bytes calldata data) public view override returns (uint256) { + if (records[beneficiary].initialized) return super.getClaimableAmount(beneficiary, data); // we can get the claimable amount prior to initialization return diff --git a/packages/hardhat/contracts/claim/abstract/ContinuousVesting.sol b/packages/hardhat/contracts/claim/abstract/ContinuousVesting.sol index 9f9a252d..54eaa045 100644 --- a/packages/hardhat/contracts/claim/abstract/ContinuousVesting.sol +++ b/packages/hardhat/contracts/claim/abstract/ContinuousVesting.sol @@ -37,7 +37,8 @@ abstract contract ContinuousVesting is AdvancedDistributor, IContinuousVesting { function getVestedFraction( address beneficiary, - uint256 time // time is in seconds past the epoch (e.g. block.timestamp) + uint256 time, // time is in seconds past the epoch (e.g. block.timestamp) + bytes calldata /*data*/ ) public view override returns (uint256) { uint256 delayedTime = time- getFairDelayTime(beneficiary); // no tokens are vested diff --git a/packages/hardhat/contracts/claim/abstract/Distributor.sol b/packages/hardhat/contracts/claim/abstract/Distributor.sol index 8fee75bd..c678a0f8 100644 --- a/packages/hardhat/contracts/claim/abstract/Distributor.sol +++ b/packages/hardhat/contracts/claim/abstract/Distributor.sol @@ -65,7 +65,8 @@ abstract contract Distributor is IDistributor, ReentrancyGuard { */ function _executeClaim( address beneficiary, - uint256 _totalAmount + uint256 _totalAmount, + bytes calldata data ) internal virtual returns (uint256) { uint120 totalAmount = uint120(_totalAmount); @@ -75,7 +76,7 @@ abstract contract Distributor is IDistributor, ReentrancyGuard { _initializeDistributionRecord(beneficiary, totalAmount); } - uint120 claimableAmount = uint120(getClaimableAmount(beneficiary)); + uint120 claimableAmount = uint120(getClaimableAmount(beneficiary, data)); require(claimableAmount > 0, 'Distributor: no more tokens claimable right now'); records[beneficiary].claimed += claimableAmount; @@ -105,7 +106,8 @@ abstract contract Distributor is IDistributor, ReentrancyGuard { // Get tokens vested as fraction of fractionDenominator function getVestedFraction( address beneficiary, - uint256 time + uint256 time, + bytes data ) public view virtual returns (uint256); function getFractionDenominator() public view returns (uint256) { @@ -118,7 +120,7 @@ abstract contract Distributor is IDistributor, ReentrancyGuard { DistributionRecord memory record = records[beneficiary]; - uint256 claimable = (record.total * getVestedFraction(beneficiary, block.timestamp)) / + uint256 claimable = (record.total * getVestedFraction(beneficiary, block.timestamp, data)) / fractionDenominator; return record.claimed >= claimable diff --git a/packages/hardhat/contracts/claim/abstract/PerAddressContinuousVesting.sol b/packages/hardhat/contracts/claim/abstract/PerAddressContinuousVesting.sol new file mode 100644 index 00000000..fa96a552 --- /dev/null +++ b/packages/hardhat/contracts/claim/abstract/PerAddressContinuousVesting.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import { Distributor, AdvancedDistributor } from "./AdvancedDistributor.sol"; +import { IContinuousVesting } from "../../interfaces/IContinuousVesting.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +abstract contract PerAddressContinuousVesting is AdvancedDistributor, IContinuousVesting { + constructor( + IERC20 _token, + uint256 _total, + string memory _uri, + uint256 _voteFactor, + uint160 _maxDelayTime, + uint160 _salt + ) + // use a large fraction denominator to provide the highest resolution on continuous vesting. + AdvancedDistributor(_token, _total, _uri, _voteFactor, 10**18, _maxDelayTime, _salt) + {} + + function getVestedFraction( + address beneficiary, + uint256 time, // time is in seconds past the epoch (e.g. block.timestamp) + bytes data + ) public view override returns (uint256) { + (uint256 start, uint256 cliff, uint256 end) = abi.decode(data, (Tranche[])); + + uint256 delayedTime = time- getFairDelayTime(beneficiary); + // no tokens are vested + if (delayedTime <= cliff) { + return 0; + } + + // all tokens are vested + if (delayedTime >= end) { + return fractionDenominator; + } + + // some tokens are vested + return (fractionDenominator * (delayedTime - start)) / (end - start); + } +} diff --git a/packages/hardhat/contracts/claim/abstract/PerUserTrancheVesting.sol b/packages/hardhat/contracts/claim/abstract/PerAddressTrancheVesting.sol similarity index 98% rename from packages/hardhat/contracts/claim/abstract/PerUserTrancheVesting.sol rename to packages/hardhat/contracts/claim/abstract/PerAddressTrancheVesting.sol index 5a66a88b..37c014e6 100644 --- a/packages/hardhat/contracts/claim/abstract/PerUserTrancheVesting.sol +++ b/packages/hardhat/contracts/claim/abstract/PerAddressTrancheVesting.sol @@ -4,7 +4,6 @@ pragma solidity 0.8.21; import { AdvancedDistributor } from './AdvancedDistributor.sol'; import { ITrancheVesting, Tranche } from '../../interfaces/ITrancheVesting.sol'; import { IERC20 } from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; -import "hardhat/console.sol"; /** * @title PerUserTrancheVesting diff --git a/packages/hardhat/contracts/claim/factory/ContinuousVestingMerkleDistributor.sol b/packages/hardhat/contracts/claim/factory/ContinuousVestingMerkleDistributor.sol index 1b95d3bf..3100e527 100644 --- a/packages/hardhat/contracts/claim/factory/ContinuousVestingMerkleDistributor.sol +++ b/packages/hardhat/contracts/claim/factory/ContinuousVestingMerkleDistributor.sol @@ -60,7 +60,7 @@ contract ContinuousVestingMerkleDistributor is Initializable, ContinuousVestingI nonReentrant { // effects - uint256 claimedAmount = super._executeClaim(beneficiary, totalAmount); + uint256 claimedAmount = super._executeClaim(beneficiary, totalAmount, ""); // interactions _settleClaim(beneficiary, claimedAmount); } diff --git a/packages/hardhat/contracts/claim/factory/DistributorInitializable.sol b/packages/hardhat/contracts/claim/factory/DistributorInitializable.sol index dae28cc7..0e745135 100644 --- a/packages/hardhat/contracts/claim/factory/DistributorInitializable.sol +++ b/packages/hardhat/contracts/claim/factory/DistributorInitializable.sol @@ -63,7 +63,7 @@ abstract contract DistributorInitializable is Initializable, IDistributor, Reent * @dev This function does not check permissions: caller must verify the claim is valid! * this function should not call any untrusted external contracts to avoid reentrancy */ - function _executeClaim(address beneficiary, uint256 _totalAmount) internal virtual returns (uint256) { + function _executeClaim(address beneficiary, uint256 _totalAmount, bytes data) internal virtual returns (uint256) { uint120 totalAmount = uint120(_totalAmount); // effects @@ -72,7 +72,7 @@ abstract contract DistributorInitializable is Initializable, IDistributor, Reent _initializeDistributionRecord(beneficiary, totalAmount); } - uint120 claimableAmount = uint120(getClaimableAmount(beneficiary)); + uint120 claimableAmount = uint120(getClaimableAmount(beneficiary, data)); require(claimableAmount > 0, "Distributor: no more tokens claimable right now"); records[beneficiary].claimed += claimableAmount; @@ -105,12 +105,12 @@ abstract contract DistributorInitializable is Initializable, IDistributor, Reent } // get the number of tokens currently claimable by a specific use - function getClaimableAmount(address beneficiary) public view virtual returns (uint256) { + function getClaimableAmount(address beneficiary, bytes calldata data) public view virtual returns (uint256) { require(records[beneficiary].initialized, "Distributor: claim not initialized"); DistributionRecord memory record = records[beneficiary]; - uint256 claimable = (record.total * getVestedFraction(beneficiary, block.timestamp)) / fractionDenominator; + uint256 claimable = (record.total * getVestedFraction(beneficiary, block.timestamp, data)) / fractionDenominator; return record.claimed >= claimable ? 0 // no more tokens to claim : claimable - record.claimed; // claim all available tokens diff --git a/packages/hardhat/contracts/claim/factory/PerAddressContinuousVestingInitializable.sol b/packages/hardhat/contracts/claim/factory/PerAddressContinuousVestingInitializable.sol new file mode 100644 index 00000000..7c8183d3 --- /dev/null +++ b/packages/hardhat/contracts/claim/factory/PerAddressContinuousVestingInitializable.sol @@ -0,0 +1,52 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import "./AdvancedDistributorInitializable.sol"; +import {IContinuousVesting} from "../../interfaces/IContinuousVesting.sol"; + +abstract contract ContinuousVestingInitializable is Initializable, AdvancedDistributorInitializable, IContinuousVesting { + function __ContinuousVesting_init( + IERC20 _token, + uint256 _total, + string memory _uri, + uint160 _maxDelayTime, + uint160 _salt, + address _owner + ) internal onlyInitializing { + __AdvancedDistributor_init( + _token, + _total, + _uri, + 10000, // 1x voting power + 10 ** 18, // provides the highest resolution possible for continuous vesting + _maxDelayTime, + _salt, + _owner + ); + } + + function getVestedFraction( + address beneficiary, + uint256 time, // time is in seconds past the epoch (e.g. block.timestamp) + bytes calldata data + ) public view override returns (uint256) { + (uint256 start, uint256 cliff, uint256 end) = abi.decode(data, (uint256, uint256, uint256)); + + uint256 delayedTime = time - getFairDelayTime(beneficiary); + // no tokens are vested + if (delayedTime <= cliff) { + return 0; + } + + // all tokens are vested + if (delayedTime >= end) { + return fractionDenominator; + } + + // some tokens are vested + return (fractionDenominator * (delayedTime - start)) / (end - start); + } +} diff --git a/packages/hardhat/contracts/claim/factory/PerAddressTrancheVestingInitializable.sol b/packages/hardhat/contracts/claim/factory/PerAddressTrancheVestingInitializable.sol new file mode 100644 index 00000000..665a8a74 --- /dev/null +++ b/packages/hardhat/contracts/claim/factory/PerAddressTrancheVestingInitializable.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import "./AdvancedDistributorInitializable.sol"; +import {ITrancheVesting, Tranche} from "../../interfaces/ITrancheVesting.sol"; + +abstract contract TrancheVestingInitializable is Initializable, AdvancedDistributorInitializable, ITrancheVesting { + function __TrancheVesting_init( + IERC20 _token, + uint256 _total, + string memory _uri, + uint160 _maxDelayTime, + uint160 _salt, + address _owner + ) internal onlyInitializing { + __AdvancedDistributor_init( + _token, + _total, + _uri, + 10000, // 1x voting power + 10000, // vested fraction + _maxDelayTime, + _salt, + _owner + ); + } + + /** + * @notice Get the vested fraction for a beneficiary at a given time. + * @dev Before the first tranche time, the vested fraction will be 0. At times between + * tranche_i and tranche_i+1, the vested fraction will be tranche_i+1's vested fraction. + * After the last tranche time, the vested fraction will be the fraction denominator. + */ + function getVestedFraction(address beneficiary, uint256 time, bytes calldata data) public view override returns (uint256) { + Tranche[] memory tranches = abi.decode(data, (Tranche[])); + + uint256 delay = getFairDelayTime(beneficiary); + for (uint256 i = tranches.length; i != 0;) { + unchecked { + --i; + } + + if (time - delay > tranches[i].time) { + return tranches[i].vestedFraction; + } + } + + return 0; + } +} diff --git a/packages/hardhat/contracts/claim/factory/TrancheVestingMerkleDistributor.sol b/packages/hardhat/contracts/claim/factory/TrancheVestingMerkleDistributor.sol index 26b8bf03..0f6df811 100644 --- a/packages/hardhat/contracts/claim/factory/TrancheVestingMerkleDistributor.sol +++ b/packages/hardhat/contracts/claim/factory/TrancheVestingMerkleDistributor.sol @@ -60,7 +60,7 @@ contract TrancheVestingMerkleDistributor is nonReentrant { // effects - uint256 claimedAmount = _executeClaim(beneficiary, totalAmount); + uint256 claimedAmount = _executeClaim(beneficiary, totalAmount, ""); // interactions _settleClaim(beneficiary, claimedAmount); } diff --git a/packages/hardhat/contracts/interfaces/IDistributor.sol b/packages/hardhat/contracts/interfaces/IDistributor.sol index a63dbd65..c9c54929 100644 --- a/packages/hardhat/contracts/interfaces/IDistributor.sol +++ b/packages/hardhat/contracts/interfaces/IDistributor.sol @@ -42,7 +42,7 @@ interface IDistributor { * @dev get the amount of tokens currently claimable by a beneficiary * @param beneficiary the address of the beneficiary */ - function getClaimableAmount(address beneficiary) external view returns (uint256); + function getClaimableAmount(address beneficiary, bytes calldata data) external view returns (uint256); /** * @dev get the denominator for vesting fractions represented as integers diff --git a/packages/hardhat/test/distributor/L2PriceTierVestingSale_2_0_Distributor.test.ts b/packages/hardhat/test/distributor/L2PriceTierVestingSale_2_0_Distributor.test.ts index 5dc2a5c4..262d1a10 100644 --- a/packages/hardhat/test/distributor/L2PriceTierVestingSale_2_0_Distributor.test.ts +++ b/packages/hardhat/test/distributor/L2PriceTierVestingSale_2_0_Distributor.test.ts @@ -552,12 +552,12 @@ describe("PriceTierVestingSale_2_0", function () { await btcOracle.setAnswer(5000000000001n) let currentlyClaimable = buyerTotal // this value should be available before initialization - expect((await distributor.getClaimableAmount(buyer.address)).toBigInt()).toEqual(currentlyClaimable) + expect((await distributor.getClaimableAmount(buyer.address, "")).toBigInt()).toEqual(currentlyClaimable) // only half of the tokens should be claimable await btcOracle.setAnswer(2500000000001n) currentlyClaimable = buyerTotal / 2n - expect((await distributor.getClaimableAmount(buyer.address)).toBigInt()).toEqual(currentlyClaimable) + expect((await distributor.getClaimableAmount(buyer.address, "")).toBigInt()).toEqual(currentlyClaimable) await distributor.initializeDistributionRecord(buyer.address) let distributionRecord = await distributor.getDistributionRecord(buyer.address) @@ -885,7 +885,7 @@ describe("PriceTierVestingSale_2_0", function () { it("Handles negative adjustments to a user's total claimable amount", async () => { const buyer = buyer4 - const initialAllocation = await fullyVestedDistributor.getClaimableAmount(buyer.address) + const initialAllocation = await fullyVestedDistributor.getClaimableAmount(buyer.address, "") // adjust a buyer's allocation downward await fullyVestedDistributor.initializeDistributionRecord(buyer.address) @@ -894,7 +894,7 @@ describe("PriceTierVestingSale_2_0", function () { -10000n ) - const newAllocation = await fullyVestedDistributor.getClaimableAmount(buyer.address) + const newAllocation = await fullyVestedDistributor.getClaimableAmount(buyer.address, "") expect(newAllocation).toEqual(initialAllocation.sub(10000n)) // claim @@ -913,7 +913,7 @@ describe("PriceTierVestingSale_2_0", function () { it("Handles positive adjustments to a user's total claimable amount", async () => { const buyer = buyer5 - const initialAllocation = await fullyVestedDistributor.getClaimableAmount(buyer.address) + const initialAllocation = await fullyVestedDistributor.getClaimableAmount(buyer.address, "") // adjust a buyer's allocation upward await fullyVestedDistributor.initializeDistributionRecord(buyer.address) @@ -923,7 +923,7 @@ describe("PriceTierVestingSale_2_0", function () { ) - const newAllocation = await fullyVestedDistributor.getClaimableAmount(buyer.address) + const newAllocation = await fullyVestedDistributor.getClaimableAmount(buyer.address, "") expect(newAllocation).toEqual(initialAllocation.add(10000n)) // transfer additional tokens to the distributor diff --git a/packages/hardhat/test/distributor/PriceTierVestingSale_2_0_Distributor.test.ts b/packages/hardhat/test/distributor/PriceTierVestingSale_2_0_Distributor.test.ts index 24a75867..7afcb074 100644 --- a/packages/hardhat/test/distributor/PriceTierVestingSale_2_0_Distributor.test.ts +++ b/packages/hardhat/test/distributor/PriceTierVestingSale_2_0_Distributor.test.ts @@ -496,12 +496,12 @@ describe("PriceTierVestingSale_2_0", function () { await btcOracle.setAnswer(5000000000001n) let currentlyClaimable = buyerTotal // this value should be available before initialization - expect((await distributor.getClaimableAmount(buyer.address)).toBigInt()).toEqual(currentlyClaimable) + expect((await distributor.getClaimableAmount(buyer.address, "")).toBigInt()).toEqual(currentlyClaimable) // only half of the tokens should be claimable await btcOracle.setAnswer(2500000000001n) currentlyClaimable = buyerTotal / 2n - expect((await distributor.getClaimableAmount(buyer.address)).toBigInt()).toEqual(currentlyClaimable) + expect((await distributor.getClaimableAmount(buyer.address, "")).toBigInt()).toEqual(currentlyClaimable) await distributor.initializeDistributionRecord(buyer.address) let distributionRecord = await distributor.getDistributionRecord(buyer.address) @@ -829,7 +829,7 @@ describe("PriceTierVestingSale_2_0", function () { it("Handles negative adjustments to a user's total claimable amount", async () => { const buyer = buyer4 - const initialAllocation = await fullyVestedDistributor.getClaimableAmount(buyer.address) + const initialAllocation = await fullyVestedDistributor.getClaimableAmount(buyer.address, "") // adjust a buyer's allocation downward await fullyVestedDistributor.initializeDistributionRecord(buyer.address) @@ -838,7 +838,7 @@ describe("PriceTierVestingSale_2_0", function () { -10000n ) - const newAllocation = await fullyVestedDistributor.getClaimableAmount(buyer.address) + const newAllocation = await fullyVestedDistributor.getClaimableAmount(buyer.address, "") expect(newAllocation).toEqual(initialAllocation.sub(10000n)) // claim @@ -857,7 +857,7 @@ describe("PriceTierVestingSale_2_0", function () { it("Handles positive adjustments to a user's total claimable amount", async () => { const buyer = buyer5 - const initialAllocation = await fullyVestedDistributor.getClaimableAmount(buyer.address) + const initialAllocation = await fullyVestedDistributor.getClaimableAmount(buyer.address, "") // adjust a buyer's allocation upward await fullyVestedDistributor.initializeDistributionRecord(buyer.address) @@ -867,7 +867,7 @@ describe("PriceTierVestingSale_2_0", function () { ) - const newAllocation = await fullyVestedDistributor.getClaimableAmount(buyer.address) + const newAllocation = await fullyVestedDistributor.getClaimableAmount(buyer.address, "") expect(newAllocation).toEqual(initialAllocation.add(10000n)) // transfer additional tokens to the distributor diff --git a/packages/hardhat/test/distributor/TrancheVestingSale_1_3_Distributor.test.ts b/packages/hardhat/test/distributor/TrancheVestingSale_1_3_Distributor.test.ts index f341f333..0b7c969d 100644 --- a/packages/hardhat/test/distributor/TrancheVestingSale_1_3_Distributor.test.ts +++ b/packages/hardhat/test/distributor/TrancheVestingSale_1_3_Distributor.test.ts @@ -662,7 +662,7 @@ describe("TrancheVestingSale_1_3", function () { it("Handles negative adjustments to a user's total claimable amount", async () => { const buyer = buyer4 - const initialAllocation = await fullyVestedDistributor.getClaimableAmount(buyer.address) + const initialAllocation = await fullyVestedDistributor.getClaimableAmount(buyer.address, "") // adjust a buyer's allocation downward await fullyVestedDistributor.initializeDistributionRecord(buyer.address) @@ -671,7 +671,7 @@ describe("TrancheVestingSale_1_3", function () { -10000n ) - const newAllocation = await fullyVestedDistributor.getClaimableAmount(buyer.address) + const newAllocation = await fullyVestedDistributor.getClaimableAmount(buyer.address, "") expect(newAllocation).toEqual(initialAllocation.sub(10000n)) // claim @@ -690,7 +690,7 @@ describe("TrancheVestingSale_1_3", function () { it("Handles positive adjustments to a user's total claimable amount", async () => { const buyer = buyer5 - const initialAllocation = await fullyVestedDistributor.getClaimableAmount(buyer.address) + const initialAllocation = await fullyVestedDistributor.getClaimableAmount(buyer.address, "") // adjust a buyer's allocation upward await fullyVestedDistributor.initializeDistributionRecord(buyer.address) @@ -700,7 +700,7 @@ describe("TrancheVestingSale_1_3", function () { ) - const newAllocation = await fullyVestedDistributor.getClaimableAmount(buyer.address) + const newAllocation = await fullyVestedDistributor.getClaimableAmount(buyer.address, "") expect(newAllocation).toEqual(initialAllocation.add(10000n)) // transfer additional tokens to the distributor diff --git a/packages/hardhat/test/distributor/TrancheVestingSale_2_0_Distributor.test.ts b/packages/hardhat/test/distributor/TrancheVestingSale_2_0_Distributor.test.ts index 750dc1d8..2e4be4a8 100644 --- a/packages/hardhat/test/distributor/TrancheVestingSale_2_0_Distributor.test.ts +++ b/packages/hardhat/test/distributor/TrancheVestingSale_2_0_Distributor.test.ts @@ -387,7 +387,7 @@ describe("TrancheVestingSale_2_0", function () { const currentlyClaimable = buyerTotal / 2n; // getClaimableAmount() works prior to initialization - expect((await distributor.getClaimableAmount(buyer.address)).toBigInt()).toEqual(currentlyClaimable) + expect((await distributor.getClaimableAmount(buyer.address, "")).toBigInt()).toEqual(currentlyClaimable) await distributor.initializeDistributionRecord(buyer.address) const distributionRecord = await distributor.getDistributionRecord(buyer.address) @@ -395,7 +395,7 @@ describe("TrancheVestingSale_2_0", function () { expect(distributionRecord.initialized).toEqual(true) // getClaimableAmount() works after initialization - expect((await distributor.getClaimableAmount(buyer.address)).toBigInt()).toEqual(currentlyClaimable) + expect((await distributor.getClaimableAmount(buyer.address, "")).toBigInt()).toEqual(currentlyClaimable) // nothing has been claimed yet expect(distributionRecord.claimed.toBigInt()).toEqual(0n) @@ -420,7 +420,7 @@ describe("TrancheVestingSale_2_0", function () { const currentlyClaimable = buyerTotal / 2n; // getClaimableAmount() works prior to initialization - expect((await distributor.getClaimableAmount(buyer.address)).toBigInt()).toEqual(currentlyClaimable) + expect((await distributor.getClaimableAmount(buyer.address, "")).toBigInt()).toEqual(currentlyClaimable) await distributor.initializeDistributionRecord(buyer.address) let distributionRecord = await distributor.getDistributionRecord(buyer.address) @@ -719,7 +719,7 @@ describe("TrancheVestingSale_2_0", function () { it("Handles negative adjustments to a user's total claimable amount", async () => { const buyer = buyer4 - const initialAllocation = await fullyVestedDistributor.getClaimableAmount(buyer.address) + const initialAllocation = await fullyVestedDistributor.getClaimableAmount(buyer.address, "") // adjust a buyer's allocation downward await fullyVestedDistributor.initializeDistributionRecord(buyer.address) @@ -728,7 +728,7 @@ describe("TrancheVestingSale_2_0", function () { -10000n ) - const newAllocation = await fullyVestedDistributor.getClaimableAmount(buyer.address) + const newAllocation = await fullyVestedDistributor.getClaimableAmount(buyer.address, "") expect(newAllocation).toEqual(initialAllocation.sub(10000n)) // claim @@ -747,7 +747,7 @@ describe("TrancheVestingSale_2_0", function () { it("Handles positive adjustments to a user's total claimable amount", async () => { const buyer = buyer5 - const initialAllocation = await fullyVestedDistributor.getClaimableAmount(buyer.address) + const initialAllocation = await fullyVestedDistributor.getClaimableAmount(buyer.address, "") // adjust a buyer's allocation upward await fullyVestedDistributor.initializeDistributionRecord(buyer.address) @@ -757,7 +757,7 @@ describe("TrancheVestingSale_2_0", function () { ) - const newAllocation = await fullyVestedDistributor.getClaimableAmount(buyer.address) + const newAllocation = await fullyVestedDistributor.getClaimableAmount(buyer.address, "") expect(newAllocation).toEqual(initialAllocation.add(10000n)) // transfer additional tokens to the distributor From d46caaf53aa598d9a41d4ee3d5ed2167f6490de2 Mon Sep 17 00:00:00 2001 From: jack dishman Date: Wed, 6 Mar 2024 13:32:35 -0500 Subject: [PATCH 03/10] feat: switching callData to memory (#24) * feat: switching callData to memory * fix: compile errors on new contracts * done: tranche --- .../contracts/claim/BasicDistributor.sol | 4 +- .../claim/ContinuousVestingMerkle copy.sol | 74 +++++++++++++++ .../claim/ContinuousVestingMerkle.sol | 2 +- ...sol => PerAddressTrancheVestingMerkle.sol} | 10 +- .../claim/PriceTierVestingMerkle.sol | 2 +- .../claim/PriceTierVestingSale_2_0.sol | 6 +- .../contracts/claim/TrancheVestingMerkle.sol | 2 +- .../claim/TrancheVestingSale_1_3.sol | 6 +- .../claim/TrancheVestingSale_2_0.sol | 6 +- .../claim/abstract/AdvancedDistributor.sol | 5 +- .../claim/abstract/ContinuousVesting.sol | 2 +- .../abstract/CrosschainMerkleDistributor.sol | 6 +- .../contracts/claim/abstract/Distributor.sol | 6 +- .../abstract/PerAddressContinuousVesting.sol | 4 +- .../abstract/PerAddressTrancheVesting.sol | 8 +- .../claim/abstract/PriceTierVesting.sol | 3 +- .../claim/abstract/TrancheVesting.sol | 3 +- .../AdvancedDistributorInitializable.sol | 4 +- .../ContinuousVestingInitializable.sol | 3 +- .../ContinuousVestingMerkleDistributor.sol | 2 +- .../factory/DistributorInitializable.sol | 6 +- ...rAddressContinuousVestingInitializable.sol | 5 +- ...ressContinuousVestingMerkleDistributor.sol | 68 ++++++++++++++ ...tinuousVestingMerkleDistributorFactory.sol | 94 +++++++++++++++++++ .../PerAddressTrancheVestingInitializable.sol | 2 +- .../factory/TrancheVestingInitializable.sol | 2 +- .../TrancheVestingMerkleDistributor.sol | 2 +- packages/hardhat/deploy/00_peraddress.ts | 36 +++++++ 28 files changed, 323 insertions(+), 50 deletions(-) create mode 100644 packages/hardhat/contracts/claim/ContinuousVestingMerkle copy.sol rename packages/hardhat/contracts/claim/{PerUserTrancheVestingMerkle.sol => PerAddressTrancheVestingMerkle.sol} (88%) create mode 100644 packages/hardhat/contracts/claim/factory/PerAddressContinuousVestingMerkleDistributor.sol create mode 100644 packages/hardhat/contracts/claim/factory/PerAddressContinuousVestingMerkleDistributorFactory.sol create mode 100644 packages/hardhat/deploy/00_peraddress.ts diff --git a/packages/hardhat/contracts/claim/BasicDistributor.sol b/packages/hardhat/contracts/claim/BasicDistributor.sol index db8a88e5..376c5fbd 100644 --- a/packages/hardhat/contracts/claim/BasicDistributor.sol +++ b/packages/hardhat/contracts/claim/BasicDistributor.sol @@ -30,7 +30,7 @@ contract BasicDistributor is AdvancedDistributor { function getVestedFraction( address, /*beneficiary*/ uint256, /*time*/ - bytes calldata /*data*/ + bytes memory /*data*/ ) public view override returns (uint256) { // all tokens vest immediately return fractionDenominator; @@ -46,7 +46,7 @@ contract BasicDistributor is AdvancedDistributor { function claim(address beneficiary) external nonReentrant { // effects - uint256 claimedAmount = super._executeClaim(beneficiary, records[beneficiary].total, ""); + uint256 claimedAmount = super._executeClaim(beneficiary, records[beneficiary].total, new bytes(0)); // interactions super._settleClaim(beneficiary, claimedAmount); } diff --git a/packages/hardhat/contracts/claim/ContinuousVestingMerkle copy.sol b/packages/hardhat/contracts/claim/ContinuousVestingMerkle copy.sol new file mode 100644 index 00000000..b21f091b --- /dev/null +++ b/packages/hardhat/contracts/claim/ContinuousVestingMerkle copy.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import '@openzeppelin/contracts/token/ERC20/IERC20.sol'; + +import { ContinuousVesting } from './abstract/ContinuousVesting.sol'; +import { MerkleSet } from './abstract/MerkleSet.sol'; + +contract ContinuousVestingMerkle is ContinuousVesting, MerkleSet { + constructor( + IERC20 _token, // the token being claimed + uint256 _total, // the total claimable by all users + string memory _uri, // information on the sale (e.g. merkle proofs) + uint256 _voteFactor, // votes have this weight + uint256 _start, // vesting clock starts at this time + uint256 _cliff, // claims open at this time + uint256 _end, // vesting clock ends and this time + bytes32 _merkleRoot, // the merkle root for claim membership (also used as salt for the fair queue delay time), + uint160 _maxDelayTime // the maximum delay time for the fair queue + ) + ContinuousVesting( + _token, + _total, + _uri, + _voteFactor, + _start, + _cliff, + _end, + _maxDelayTime, + uint160(uint256(_merkleRoot)) + ) + MerkleSet(_merkleRoot) + {} + + function NAME() external pure override returns (string memory) { + return 'ContinuousVestingMerkle'; + } + + function VERSION() external pure override returns (uint256) { + return 3; + } + + function initializeDistributionRecord( + uint256 index, // the beneficiary's index in the merkle root + address beneficiary, // the address that will receive tokens + uint256 amount, // the total claimable by this beneficiary + bytes32[] calldata merkleProof + ) + external + validMerkleProof(keccak256(abi.encodePacked(index, beneficiary, amount)), merkleProof) + { + _initializeDistributionRecord(beneficiary, amount); + } + + function claim( + uint256 index, // the beneficiary's index in the merkle root + address beneficiary, // the address that will receive tokens + uint256 totalAmount, // the total claimable by this beneficiary + bytes32[] calldata merkleProof + ) + external + validMerkleProof(keccak256(abi.encodePacked(index, beneficiary, totalAmount)), merkleProof) + nonReentrant + { + // effects + uint256 claimedAmount = super._executeClaim(beneficiary, totalAmount, new bytes(0)); + // interactions + super._settleClaim(beneficiary, claimedAmount); + } + + function setMerkleRoot(bytes32 _merkleRoot) external onlyOwner { + _setMerkleRoot(_merkleRoot); + } +} diff --git a/packages/hardhat/contracts/claim/ContinuousVestingMerkle.sol b/packages/hardhat/contracts/claim/ContinuousVestingMerkle.sol index 2ce49a7a..b21f091b 100644 --- a/packages/hardhat/contracts/claim/ContinuousVestingMerkle.sol +++ b/packages/hardhat/contracts/claim/ContinuousVestingMerkle.sol @@ -63,7 +63,7 @@ contract ContinuousVestingMerkle is ContinuousVesting, MerkleSet { nonReentrant { // effects - uint256 claimedAmount = super._executeClaim(beneficiary, totalAmount, ""); + uint256 claimedAmount = super._executeClaim(beneficiary, totalAmount, new bytes(0)); // interactions super._settleClaim(beneficiary, claimedAmount); } diff --git a/packages/hardhat/contracts/claim/PerUserTrancheVestingMerkle.sol b/packages/hardhat/contracts/claim/PerAddressTrancheVestingMerkle.sol similarity index 88% rename from packages/hardhat/contracts/claim/PerUserTrancheVestingMerkle.sol rename to packages/hardhat/contracts/claim/PerAddressTrancheVestingMerkle.sol index aba2fd0d..9d1ab7da 100644 --- a/packages/hardhat/contracts/claim/PerUserTrancheVestingMerkle.sol +++ b/packages/hardhat/contracts/claim/PerAddressTrancheVestingMerkle.sol @@ -3,25 +3,23 @@ pragma solidity 0.8.21; import { IERC20 } from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; -import { TrancheVesting, Tranche } from './abstract/TrancheVesting.sol'; +import { PerAddressTrancheVesting, Tranche } from './abstract/PerAddressTrancheVesting.sol'; import { MerkleSet } from './abstract/MerkleSet.sol'; -contract PerUserTrancheVestingMerkle is PerUserTrancheVesting, MerkleSet { +contract PerAddressTrancheVestingMerkle is PerAddressTrancheVesting, MerkleSet { constructor( IERC20 _token, uint256 _total, string memory _uri, // information on the sale (e.g. merkle proofs) uint256 _voteFactor, - Tranche[] memory _tranches, bytes32 _merkleRoot, uint160 _maxDelayTime // the maximum delay time for the fair queue ) - TrancheVesting( + PerAddressTrancheVesting( _token, _total, _uri, _voteFactor, - _tranches, _maxDelayTime, uint160(uint256(_merkleRoot)) ) @@ -56,7 +54,7 @@ contract PerUserTrancheVestingMerkle is PerUserTrancheVesting, MerkleSet { bytes32[] calldata merkleProof ) external - validMerkleProof(keccak256(abi.encodePacked(index, beneficiary, totalAmount, tranches)), merkleProof) + validMerkleProof(keccak256(abi.encodePacked(index, beneficiary, totalAmount)), merkleProof) nonReentrant { bytes memory data = abi.encode(tranches); diff --git a/packages/hardhat/contracts/claim/PriceTierVestingMerkle.sol b/packages/hardhat/contracts/claim/PriceTierVestingMerkle.sol index b5bfe6b6..ad4ad970 100644 --- a/packages/hardhat/contracts/claim/PriceTierVestingMerkle.sol +++ b/packages/hardhat/contracts/claim/PriceTierVestingMerkle.sol @@ -69,7 +69,7 @@ contract PriceTierVestingMerkle is PriceTierVesting, MerkleSet { nonReentrant { // effects - uint256 claimedAmount = _executeClaim(beneficiary, totalAmount, ""); + uint256 claimedAmount = _executeClaim(beneficiary, totalAmount, new bytes(0)); // interactions _settleClaim(beneficiary, claimedAmount); } diff --git a/packages/hardhat/contracts/claim/PriceTierVestingSale_2_0.sol b/packages/hardhat/contracts/claim/PriceTierVestingSale_2_0.sol index 9f5e6e3a..9fdcdcff 100644 --- a/packages/hardhat/contracts/claim/PriceTierVestingSale_2_0.sol +++ b/packages/hardhat/contracts/claim/PriceTierVestingSale_2_0.sol @@ -100,7 +100,7 @@ contract PriceTierVestingSale_2_0 is PriceTierVesting { uint256 totalClaimableAmount = getTotalClaimableAmount(beneficiary); // effects - uint256 claimedAmount = super._executeClaim(beneficiary, totalClaimableAmount, ""); + uint256 claimedAmount = super._executeClaim(beneficiary, totalClaimableAmount, new bytes(0)); // interactions super._settleClaim(beneficiary, claimedAmount); @@ -119,12 +119,12 @@ contract PriceTierVestingSale_2_0 is PriceTierVesting { } // get the number of tokens currently claimable by a specific user - function getClaimableAmount(address beneficiary, bytes calldata data) public view override returns (uint256) { + function getClaimableAmount(address beneficiary, bytes memory data) public view override returns (uint256) { if (records[beneficiary].initialized) return super.getClaimableAmount(beneficiary, data); // we can get the claimable amount prior to initialization return - (getPurchasedAmount(beneficiary) * getVestedFraction(beneficiary, block.timestamp)) / + (getPurchasedAmount(beneficiary) * getVestedFraction(beneficiary, block.timestamp, new bytes(0))) / fractionDenominator; } diff --git a/packages/hardhat/contracts/claim/TrancheVestingMerkle.sol b/packages/hardhat/contracts/claim/TrancheVestingMerkle.sol index 7ff66ea2..79f3b3e7 100644 --- a/packages/hardhat/contracts/claim/TrancheVestingMerkle.sol +++ b/packages/hardhat/contracts/claim/TrancheVestingMerkle.sol @@ -59,7 +59,7 @@ contract TrancheVestingMerkle is TrancheVesting, MerkleSet { nonReentrant { // effects - uint256 claimedAmount = _executeClaim(beneficiary, totalAmount, ""); + uint256 claimedAmount = _executeClaim(beneficiary, totalAmount, new bytes(0)); // interactions _settleClaim(beneficiary, claimedAmount); } diff --git a/packages/hardhat/contracts/claim/TrancheVestingSale_1_3.sol b/packages/hardhat/contracts/claim/TrancheVestingSale_1_3.sol index 18daa56c..5a128392 100644 --- a/packages/hardhat/contracts/claim/TrancheVestingSale_1_3.sol +++ b/packages/hardhat/contracts/claim/TrancheVestingSale_1_3.sol @@ -92,7 +92,7 @@ contract TrancheVestingSale_1_3 is TrancheVesting { uint256 totalClaimableAmount = getTotalClaimableAmount(beneficiary); // effects - uint256 claimedAmount = super._executeClaim(beneficiary, totalClaimableAmount, ""); + uint256 claimedAmount = super._executeClaim(beneficiary, totalClaimableAmount, new bytes(0)); // interactions super._settleClaim(beneficiary, claimedAmount); @@ -111,12 +111,12 @@ contract TrancheVestingSale_1_3 is TrancheVesting { } // get the number of tokens currently claimable by a specific user - function getClaimableAmount(address beneficiary, bytes calldata data) public view override returns (uint256) { + function getClaimableAmount(address beneficiary, bytes memory data) public view override returns (uint256) { if (records[beneficiary].initialized) return super.getClaimableAmount(beneficiary, data); // we can get the claimable amount prior to initialization return - (getPurchasedAmount(beneficiary) * getVestedFraction(beneficiary, block.timestamp)) / + (getPurchasedAmount(beneficiary) * getVestedFraction(beneficiary, block.timestamp, new bytes(0))) / fractionDenominator; } diff --git a/packages/hardhat/contracts/claim/TrancheVestingSale_2_0.sol b/packages/hardhat/contracts/claim/TrancheVestingSale_2_0.sol index 9556d5da..6dace602 100644 --- a/packages/hardhat/contracts/claim/TrancheVestingSale_2_0.sol +++ b/packages/hardhat/contracts/claim/TrancheVestingSale_2_0.sol @@ -90,7 +90,7 @@ contract TrancheVestingSale_2_0 is TrancheVesting { uint256 totalClaimableAmount = getTotalClaimableAmount(beneficiary); // effects - uint256 claimedAmount = super._executeClaim(beneficiary, totalClaimableAmount, ""); + uint256 claimedAmount = super._executeClaim(beneficiary, totalClaimableAmount, new bytes(0)); // interactions _settleClaim(beneficiary, claimedAmount); @@ -109,12 +109,12 @@ contract TrancheVestingSale_2_0 is TrancheVesting { } // get the number of tokens currently claimable by a specific user - function getClaimableAmount(address beneficiary, bytes calldata data) public view override returns (uint256) { + function getClaimableAmount(address beneficiary, bytes memory data) public view override returns (uint256) { if (records[beneficiary].initialized) return super.getClaimableAmount(beneficiary, data); // we can get the claimable amount prior to initialization return - (getPurchasedAmount(beneficiary) * getVestedFraction(beneficiary, block.timestamp)) / + (getPurchasedAmount(beneficiary) * getVestedFraction(beneficiary, block.timestamp, new bytes(0))) / fractionDenominator; } diff --git a/packages/hardhat/contracts/claim/abstract/AdvancedDistributor.sol b/packages/hardhat/contracts/claim/abstract/AdvancedDistributor.sol index cc7b9a81..b0b251f0 100644 --- a/packages/hardhat/contracts/claim/abstract/AdvancedDistributor.sol +++ b/packages/hardhat/contracts/claim/abstract/AdvancedDistributor.sol @@ -101,9 +101,10 @@ abstract contract AdvancedDistributor is function _executeClaim( address beneficiary, - uint256 totalAmount + uint256 totalAmount, + bytes memory data ) internal virtual override returns (uint256 _claimed) { - _claimed = super._executeClaim(beneficiary, totalAmount); + _claimed = super._executeClaim(beneficiary, totalAmount, data); _reconcileVotingPower(beneficiary); } diff --git a/packages/hardhat/contracts/claim/abstract/ContinuousVesting.sol b/packages/hardhat/contracts/claim/abstract/ContinuousVesting.sol index 54eaa045..10ce5e4b 100644 --- a/packages/hardhat/contracts/claim/abstract/ContinuousVesting.sol +++ b/packages/hardhat/contracts/claim/abstract/ContinuousVesting.sol @@ -38,7 +38,7 @@ abstract contract ContinuousVesting is AdvancedDistributor, IContinuousVesting { function getVestedFraction( address beneficiary, uint256 time, // time is in seconds past the epoch (e.g. block.timestamp) - bytes calldata /*data*/ + bytes memory /*data*/ ) public view override returns (uint256) { uint256 delayedTime = time- getFairDelayTime(beneficiary); // no tokens are vested diff --git a/packages/hardhat/contracts/claim/abstract/CrosschainMerkleDistributor.sol b/packages/hardhat/contracts/claim/abstract/CrosschainMerkleDistributor.sol index b64e6dc6..10a3a3e6 100644 --- a/packages/hardhat/contracts/claim/abstract/CrosschainMerkleDistributor.sol +++ b/packages/hardhat/contracts/claim/abstract/CrosschainMerkleDistributor.sol @@ -74,7 +74,7 @@ abstract contract CrosschainMerkleDistributor is CrosschainDistributor, MerkleSe _verifyMembership(_getLeaf(beneficiary, totalAmount, beneficiaryDomain), proof); // effects - uint256 claimedAmount = _executeClaim(beneficiary, totalAmount); + uint256 claimedAmount = _executeClaim(beneficiary, totalAmount, new bytes(0)); // interactions // NOTE: xReceive is *NOT* payable (Connext does not handle native assets). @@ -100,7 +100,7 @@ abstract contract CrosschainMerkleDistributor is CrosschainDistributor, MerkleSe ) external payable { _verifyMembership(_getLeaf(_beneficiary, _total, domain), _proof); // effects - uint256 claimedAmount = _executeClaim(_beneficiary, _total); + uint256 claimedAmount = _executeClaim(_beneficiary, _total, new bytes(0)); // interactions _settleClaim(_beneficiary, _beneficiary, domain, claimedAmount); @@ -137,7 +137,7 @@ abstract contract CrosschainMerkleDistributor is CrosschainDistributor, MerkleSe // Validate the claim _verifyMembership(_getLeaf(_beneficiary, _total, _beneficiaryDomain), _proof); - uint256 claimedAmount = _executeClaim(_beneficiary, _total); + uint256 claimedAmount = _executeClaim(_beneficiary, _total, new bytes(0)); _settleClaim(_beneficiary, _recipient, _recipientDomain, claimedAmount); } diff --git a/packages/hardhat/contracts/claim/abstract/Distributor.sol b/packages/hardhat/contracts/claim/abstract/Distributor.sol index c678a0f8..01c379cd 100644 --- a/packages/hardhat/contracts/claim/abstract/Distributor.sol +++ b/packages/hardhat/contracts/claim/abstract/Distributor.sol @@ -66,7 +66,7 @@ abstract contract Distributor is IDistributor, ReentrancyGuard { function _executeClaim( address beneficiary, uint256 _totalAmount, - bytes calldata data + bytes memory data ) internal virtual returns (uint256) { uint120 totalAmount = uint120(_totalAmount); @@ -107,7 +107,7 @@ abstract contract Distributor is IDistributor, ReentrancyGuard { function getVestedFraction( address beneficiary, uint256 time, - bytes data + bytes memory data ) public view virtual returns (uint256); function getFractionDenominator() public view returns (uint256) { @@ -115,7 +115,7 @@ abstract contract Distributor is IDistributor, ReentrancyGuard { } // get the number of tokens currently claimable by a specific use - function getClaimableAmount(address beneficiary, bytes data) public view virtual returns (uint256) { + function getClaimableAmount(address beneficiary, bytes memory data) public view virtual returns (uint256) { require(records[beneficiary].initialized, 'Distributor: claim not initialized'); DistributionRecord memory record = records[beneficiary]; diff --git a/packages/hardhat/contracts/claim/abstract/PerAddressContinuousVesting.sol b/packages/hardhat/contracts/claim/abstract/PerAddressContinuousVesting.sol index fa96a552..58451e32 100644 --- a/packages/hardhat/contracts/claim/abstract/PerAddressContinuousVesting.sol +++ b/packages/hardhat/contracts/claim/abstract/PerAddressContinuousVesting.sol @@ -21,9 +21,9 @@ abstract contract PerAddressContinuousVesting is AdvancedDistributor, IContinuou function getVestedFraction( address beneficiary, uint256 time, // time is in seconds past the epoch (e.g. block.timestamp) - bytes data + bytes memory data ) public view override returns (uint256) { - (uint256 start, uint256 cliff, uint256 end) = abi.decode(data, (Tranche[])); + (uint256 start, uint256 cliff, uint256 end) = abi.decode(data, (uint256, uint256, uint256)); uint256 delayedTime = time- getFairDelayTime(beneficiary); // no tokens are vested diff --git a/packages/hardhat/contracts/claim/abstract/PerAddressTrancheVesting.sol b/packages/hardhat/contracts/claim/abstract/PerAddressTrancheVesting.sol index 37c014e6..da117d03 100644 --- a/packages/hardhat/contracts/claim/abstract/PerAddressTrancheVesting.sol +++ b/packages/hardhat/contracts/claim/abstract/PerAddressTrancheVesting.sol @@ -2,14 +2,14 @@ pragma solidity 0.8.21; import { AdvancedDistributor } from './AdvancedDistributor.sol'; -import { ITrancheVesting, Tranche } from '../../interfaces/ITrancheVesting.sol'; +import { Tranche } from '../../interfaces/ITrancheVesting.sol'; import { IERC20 } from '@openzeppelin/contracts/token/ERC20/IERC20.sol'; /** * @title PerUserTrancheVesting * @notice Distributes funds to beneficiaries over time in tranches. */ -abstract contract PerUserTrancheVesting is AdvancedDistributor, ITrancheVesting { +abstract contract PerAddressTrancheVesting is AdvancedDistributor { constructor( IERC20 _token, uint256 _total, @@ -28,9 +28,9 @@ abstract contract PerUserTrancheVesting is AdvancedDistributor, ITrancheVesting function getVestedFraction( address beneficiary, uint256 time, - bytes data + bytes memory data ) public view override returns (uint256) { - tranches = abi.decode(data, (Tranche[])); + Tranche[] memory tranches = abi.decode(data, (Tranche[])); uint256 delay = getFairDelayTime(beneficiary); for (uint256 i = tranches.length; i != 0; ) { diff --git a/packages/hardhat/contracts/claim/abstract/PriceTierVesting.sol b/packages/hardhat/contracts/claim/abstract/PriceTierVesting.sol index 8149ae6b..07165669 100644 --- a/packages/hardhat/contracts/claim/abstract/PriceTierVesting.sol +++ b/packages/hardhat/contracts/claim/abstract/PriceTierVesting.sol @@ -65,7 +65,8 @@ abstract contract PriceTierVesting is AdvancedDistributor, IPriceTierVesting { function getVestedFraction( address beneficiary, - uint256 time // time in seconds past epoch + uint256 time, // time in seconds past epoch + bytes memory /*data*/ ) public view override returns (uint256) { // shift this user's time by their fair delay uint256 delayedTime = time - getFairDelayTime(beneficiary); diff --git a/packages/hardhat/contracts/claim/abstract/TrancheVesting.sol b/packages/hardhat/contracts/claim/abstract/TrancheVesting.sol index f10ed9d8..0eb13fb7 100644 --- a/packages/hardhat/contracts/claim/abstract/TrancheVesting.sol +++ b/packages/hardhat/contracts/claim/abstract/TrancheVesting.sol @@ -35,7 +35,8 @@ abstract contract TrancheVesting is AdvancedDistributor, ITrancheVesting { */ function getVestedFraction( address beneficiary, - uint256 time + uint256 time, + bytes memory /*data*/ ) public view override returns (uint256) { uint256 delay = getFairDelayTime(beneficiary); for (uint256 i = tranches.length; i != 0; ) { diff --git a/packages/hardhat/contracts/claim/factory/AdvancedDistributorInitializable.sol b/packages/hardhat/contracts/claim/factory/AdvancedDistributorInitializable.sol index 37bc5bf1..2059fe35 100644 --- a/packages/hardhat/contracts/claim/factory/AdvancedDistributorInitializable.sol +++ b/packages/hardhat/contracts/claim/factory/AdvancedDistributorInitializable.sol @@ -103,13 +103,13 @@ abstract contract AdvancedDistributorInitializable is _reconcileVotingPower(beneficiary); } - function _executeClaim(address beneficiary, uint256 totalAmount) + function _executeClaim(address beneficiary, uint256 totalAmount, bytes memory) internal virtual override returns (uint256 _claimed) { - _claimed = super._executeClaim(beneficiary, totalAmount); + _claimed = super._executeClaim(beneficiary, totalAmount, new bytes(0)); _reconcileVotingPower(beneficiary); } diff --git a/packages/hardhat/contracts/claim/factory/ContinuousVestingInitializable.sol b/packages/hardhat/contracts/claim/factory/ContinuousVestingInitializable.sol index 8cda73d7..20d4e804 100644 --- a/packages/hardhat/contracts/claim/factory/ContinuousVestingInitializable.sol +++ b/packages/hardhat/contracts/claim/factory/ContinuousVestingInitializable.sol @@ -46,7 +46,8 @@ abstract contract ContinuousVestingInitializable is Initializable, AdvancedDistr function getVestedFraction( address beneficiary, - uint256 time // time is in seconds past the epoch (e.g. block.timestamp) + uint256 time, // time is in seconds past the epoch (e.g. block.timestamp) + bytes memory // data ) public view override returns (uint256) { uint256 delayedTime = time - getFairDelayTime(beneficiary); // no tokens are vested diff --git a/packages/hardhat/contracts/claim/factory/ContinuousVestingMerkleDistributor.sol b/packages/hardhat/contracts/claim/factory/ContinuousVestingMerkleDistributor.sol index 3100e527..851389da 100644 --- a/packages/hardhat/contracts/claim/factory/ContinuousVestingMerkleDistributor.sol +++ b/packages/hardhat/contracts/claim/factory/ContinuousVestingMerkleDistributor.sol @@ -60,7 +60,7 @@ contract ContinuousVestingMerkleDistributor is Initializable, ContinuousVestingI nonReentrant { // effects - uint256 claimedAmount = super._executeClaim(beneficiary, totalAmount, ""); + uint256 claimedAmount = super._executeClaim(beneficiary, totalAmount, new bytes(0)); // interactions _settleClaim(beneficiary, claimedAmount); } diff --git a/packages/hardhat/contracts/claim/factory/DistributorInitializable.sol b/packages/hardhat/contracts/claim/factory/DistributorInitializable.sol index 0e745135..2bcf2483 100644 --- a/packages/hardhat/contracts/claim/factory/DistributorInitializable.sol +++ b/packages/hardhat/contracts/claim/factory/DistributorInitializable.sol @@ -63,7 +63,7 @@ abstract contract DistributorInitializable is Initializable, IDistributor, Reent * @dev This function does not check permissions: caller must verify the claim is valid! * this function should not call any untrusted external contracts to avoid reentrancy */ - function _executeClaim(address beneficiary, uint256 _totalAmount, bytes data) internal virtual returns (uint256) { + function _executeClaim(address beneficiary, uint256 _totalAmount, bytes memory data) internal virtual returns (uint256) { uint120 totalAmount = uint120(_totalAmount); // effects @@ -98,14 +98,14 @@ abstract contract DistributorInitializable is Initializable, IDistributor, Reent } // Get tokens vested as fraction of fractionDenominator - function getVestedFraction(address beneficiary, uint256 time) public view virtual returns (uint256); + function getVestedFraction(address beneficiary, uint256 time, bytes memory data) public view virtual returns (uint256); function getFractionDenominator() public view returns (uint256) { return fractionDenominator; } // get the number of tokens currently claimable by a specific use - function getClaimableAmount(address beneficiary, bytes calldata data) public view virtual returns (uint256) { + function getClaimableAmount(address beneficiary, bytes memory data) public view virtual returns (uint256) { require(records[beneficiary].initialized, "Distributor: claim not initialized"); DistributionRecord memory record = records[beneficiary]; diff --git a/packages/hardhat/contracts/claim/factory/PerAddressContinuousVestingInitializable.sol b/packages/hardhat/contracts/claim/factory/PerAddressContinuousVestingInitializable.sol index 7c8183d3..976efee1 100644 --- a/packages/hardhat/contracts/claim/factory/PerAddressContinuousVestingInitializable.sol +++ b/packages/hardhat/contracts/claim/factory/PerAddressContinuousVestingInitializable.sol @@ -5,9 +5,8 @@ import "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "./AdvancedDistributorInitializable.sol"; -import {IContinuousVesting} from "../../interfaces/IContinuousVesting.sol"; -abstract contract ContinuousVestingInitializable is Initializable, AdvancedDistributorInitializable, IContinuousVesting { +abstract contract PerAddressContinuousVestingInitializable is Initializable, AdvancedDistributorInitializable { function __ContinuousVesting_init( IERC20 _token, uint256 _total, @@ -31,7 +30,7 @@ abstract contract ContinuousVestingInitializable is Initializable, AdvancedDistr function getVestedFraction( address beneficiary, uint256 time, // time is in seconds past the epoch (e.g. block.timestamp) - bytes calldata data + bytes memory data ) public view override returns (uint256) { (uint256 start, uint256 cliff, uint256 end) = abi.decode(data, (uint256, uint256, uint256)); diff --git a/packages/hardhat/contracts/claim/factory/PerAddressContinuousVestingMerkleDistributor.sol b/packages/hardhat/contracts/claim/factory/PerAddressContinuousVestingMerkleDistributor.sol new file mode 100644 index 00000000..257bb292 --- /dev/null +++ b/packages/hardhat/contracts/claim/factory/PerAddressContinuousVestingMerkleDistributor.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; + +import "./PerAddressContinuousVestingInitializable.sol"; +import "./MerkleSetInitializable.sol"; + +contract PerAddressContinuousVestingMerkleDistributor is Initializable, PerAddressContinuousVestingInitializable, MerkleSetInitializable { + constructor() { + _disableInitializers(); + } + + function initialize( + IERC20 _token, // the token being claimed + uint256 _total, // the total claimable by all users + string memory _uri, // information on the sale (e.g. merkle proofs) + bytes32 _merkleRoot, // the merkle root for claim membership (also used as salt for the fair queue delay time), + uint160 _maxDelayTime, // the maximum delay time for the fair queue + address _owner + ) public initializer { + __ContinuousVesting_init( + _token, _total, _uri, _maxDelayTime, uint160(uint256(_merkleRoot)), _owner + ); + + __MerkleSet_init(_merkleRoot); + + _transferOwnership(_owner); + } + + function NAME() external pure override returns (string memory) { + return "ContinuousVestingMerkleInitializable"; + } + + function VERSION() external pure override returns (uint256) { + return 3; + } + + function initializeDistributionRecord( + uint256 index, // the beneficiary's index in the merkle root + address beneficiary, // the address that will receive tokens + uint256 amount, // the total claimable by this beneficiary + bytes32[] calldata merkleProof + ) external validMerkleProof(keccak256(abi.encodePacked(index, beneficiary, amount)), merkleProof) { + _initializeDistributionRecord(beneficiary, amount); + } + + function claim( + uint256 index, // the beneficiary's index in the merkle root + address beneficiary, // the address that will receive tokens + uint256 totalAmount, // the total claimable by this beneficiary + bytes32[] calldata merkleProof + ) + external + validMerkleProof(keccak256(abi.encodePacked(index, beneficiary, totalAmount)), merkleProof) + nonReentrant + { + // effects + uint256 claimedAmount = super._executeClaim(beneficiary, totalAmount, new bytes(0)); + // interactions + _settleClaim(beneficiary, claimedAmount); + } + + function setMerkleRoot(bytes32 _merkleRoot) external onlyOwner { + _setMerkleRoot(_merkleRoot); + } +} diff --git a/packages/hardhat/contracts/claim/factory/PerAddressContinuousVestingMerkleDistributorFactory.sol b/packages/hardhat/contracts/claim/factory/PerAddressContinuousVestingMerkleDistributorFactory.sol new file mode 100644 index 00000000..02aefcf9 --- /dev/null +++ b/packages/hardhat/contracts/claim/factory/PerAddressContinuousVestingMerkleDistributorFactory.sol @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.21; + +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol"; + +import {PerAddressContinuousVestingMerkleDistributor} from "./PerAddressContinuousVestingMerkleDistributor.sol"; + +contract PerAddressContinuousVestingMerkleDistributorFactory { + address private immutable i_implementation; + address[] public distributors; + + event DistributorDeployed(address indexed distributor); + + constructor(address implementation) { + i_implementation = implementation; + } + + function _getSalt( + IERC20 _token, // the token being claimed + uint256 _total, // the total claimable by all users + string memory _uri, // information on the sale (e.g. merkle proofs) + bytes32 _merkleRoot, // the merkle root for claim membership (also used as salt for the fair queue delay time), + uint160 _maxDelayTime, // the maximum delay time for the fair queue + address _owner, + uint256 _nonce + ) private pure returns (bytes32) { + return keccak256(abi.encode( + _token, + _total, + _uri, + _merkleRoot, + _maxDelayTime, + _owner, + _nonce + )); + } + + function deployDistributor( + IERC20 _token, // the token being claimed + uint256 _total, // the total claimable by all users + string memory _uri, // information on the sale (e.g. merkle proofs) + bytes32 _merkleRoot, // the merkle root for claim membership (also used as salt for the fair queue delay time), + uint160 _maxDelayTime, // the maximum delay time for the fair queue + address _owner, + uint256 _nonce + ) public returns (PerAddressContinuousVestingMerkleDistributor distributor) { + bytes32 salt = _getSalt( + _token, + _total, + _uri, + _merkleRoot, + _maxDelayTime, + _owner, + _nonce + ); + + distributor = + PerAddressContinuousVestingMerkleDistributor(Clones.cloneDeterministic(i_implementation, salt)); + distributors.push(address(distributor)); + + emit DistributorDeployed(address(distributor)); + + distributor.initialize(_token, _total, _uri, _merkleRoot, _maxDelayTime, _owner); + + return distributor; + } + + function getImplementation() public view returns (address) { + return i_implementation; + } + + function predictDistributorAddress( + IERC20 _token, // the token being claimed + uint256 _total, // the total claimable by all users + string memory _uri, // information on the sale (e.g. merkle proofs) + bytes32 _merkleRoot, // the merkle root for claim membership (also used as salt for the fair queue delay time), + uint160 _maxDelayTime, // the maximum delay time for the fair queue + address _owner, + uint256 _nonce + ) public view returns (address) { + bytes32 salt = _getSalt( + _token, + _total, + _uri, + _merkleRoot, + _maxDelayTime, + _owner, + _nonce + ); + + return Clones.predictDeterministicAddress(i_implementation, salt, address(this)); + } +} diff --git a/packages/hardhat/contracts/claim/factory/PerAddressTrancheVestingInitializable.sol b/packages/hardhat/contracts/claim/factory/PerAddressTrancheVestingInitializable.sol index 665a8a74..e8972b5e 100644 --- a/packages/hardhat/contracts/claim/factory/PerAddressTrancheVestingInitializable.sol +++ b/packages/hardhat/contracts/claim/factory/PerAddressTrancheVestingInitializable.sol @@ -34,7 +34,7 @@ abstract contract TrancheVestingInitializable is Initializable, AdvancedDistribu * tranche_i and tranche_i+1, the vested fraction will be tranche_i+1's vested fraction. * After the last tranche time, the vested fraction will be the fraction denominator. */ - function getVestedFraction(address beneficiary, uint256 time, bytes calldata data) public view override returns (uint256) { + function getVestedFraction(address beneficiary, uint256 time, bytes memory data) public view override returns (uint256) { Tranche[] memory tranches = abi.decode(data, (Tranche[])); uint256 delay = getFairDelayTime(beneficiary); diff --git a/packages/hardhat/contracts/claim/factory/TrancheVestingInitializable.sol b/packages/hardhat/contracts/claim/factory/TrancheVestingInitializable.sol index c92f8144..457d228a 100644 --- a/packages/hardhat/contracts/claim/factory/TrancheVestingInitializable.sol +++ b/packages/hardhat/contracts/claim/factory/TrancheVestingInitializable.sol @@ -39,7 +39,7 @@ abstract contract TrancheVestingInitializable is Initializable, AdvancedDistribu * tranche_i and tranche_i+1, the vested fraction will be tranche_i+1's vested fraction. * After the last tranche time, the vested fraction will be the fraction denominator. */ - function getVestedFraction(address beneficiary, uint256 time) public view override returns (uint256) { + function getVestedFraction(address beneficiary, uint256 time, bytes memory) public view override returns (uint256) { uint256 delay = getFairDelayTime(beneficiary); for (uint256 i = tranches.length; i != 0;) { unchecked { diff --git a/packages/hardhat/contracts/claim/factory/TrancheVestingMerkleDistributor.sol b/packages/hardhat/contracts/claim/factory/TrancheVestingMerkleDistributor.sol index 0f6df811..31cec35c 100644 --- a/packages/hardhat/contracts/claim/factory/TrancheVestingMerkleDistributor.sol +++ b/packages/hardhat/contracts/claim/factory/TrancheVestingMerkleDistributor.sol @@ -60,7 +60,7 @@ contract TrancheVestingMerkleDistributor is nonReentrant { // effects - uint256 claimedAmount = _executeClaim(beneficiary, totalAmount, ""); + uint256 claimedAmount = _executeClaim(beneficiary, totalAmount, new bytes(0)); // interactions _settleClaim(beneficiary, claimedAmount); } diff --git a/packages/hardhat/deploy/00_peraddress.ts b/packages/hardhat/deploy/00_peraddress.ts new file mode 100644 index 00000000..f4c8a8c4 --- /dev/null +++ b/packages/hardhat/deploy/00_peraddress.ts @@ -0,0 +1,36 @@ +module.exports = async ({ getNamedAccounts, deployments }) => { + const { deploy } = deployments; + const { deployer } = await getNamedAccounts(); + + // const TEN_BILLION = "10000000000000000000000000000"; + + // deploy a distributor implementation + await deploy("ContinuousVestingMerkleDistributor", { + from: deployer, + args: [], + log: true, + }); + + // deploy a distributor factory + // await deploy("ContinuousVestingMerkleDistributorFactory", { + // from: deployer, + // args: ["0x31184BEc8DA86A9A9041C835Ce020e2862b78138"],