From 46f4580b16857e7819f8d743b7262b632f133b31 Mon Sep 17 00:00:00 2001 From: gs8nrv <55771972+GuillaumeNervoXS@users.noreply.github.com> Date: Thu, 21 Nov 2024 17:29:17 +0100 Subject: [PATCH] feat: v1 override and reallocation --- contracts/DistributionCreator.sol | 55 +- foundry.toml | 24 +- helpers/fork.sh | 37 + lib/forge-std | 2 +- package.json | 9 +- scripts/foundry/CreateCampaign.s.sol | 62 + scripts/foundry/UpdateCampaign.s.sol | 138 ++ .../foundry/UpgradeDistributionCreator.s.sol | 87 ++ test/foundry/DistributionCreator.t.sol | 1182 +++++++++++++++-- test/foundry/unit/DistributionCreator.t.sol | 66 +- test/foundry/unit/Distributor.t.sol | 76 +- utils/forwardUtils.js | 21 + yarn.lock | 95 +- 13 files changed, 1656 insertions(+), 198 deletions(-) create mode 100644 helpers/fork.sh create mode 100644 scripts/foundry/CreateCampaign.s.sol create mode 100644 scripts/foundry/UpdateCampaign.s.sol create mode 100644 scripts/foundry/UpgradeDistributionCreator.s.sol create mode 100755 utils/forwardUtils.js diff --git a/contracts/DistributionCreator.sol b/contracts/DistributionCreator.sol index 30a5d41..7a1a0fc 100644 --- a/contracts/DistributionCreator.sol +++ b/contracts/DistributionCreator.sol @@ -51,7 +51,7 @@ import { Distributor } from "./Distributor.sol"; /// @author Angle Labs, Inc. /// @notice Manages the distribution of rewards through the Merkl system /// @dev This contract is mostly a helper for APIs built on top of Merkl -/// @dev This contract is distinguishes two types of different rewards: +/// @dev This contract distinguishes two types of different rewards: /// - distributions: type of campaign for concentrated liquidity pools created before Feb 15 2024, /// now deprecated /// - campaigns: the more global name to describe any reward program on top of Merkl @@ -130,7 +130,13 @@ contract DistributionCreator is UUPSHelper, ReentrancyGuardUpgradeable { mapping(bytes32 => CampaignParameters) public campaignOverrides; /// @notice Maps a campaignId to the block numbers at which it's been updated - mapping(bytes32 => uint256[]) public campaignOverridesBlocks; + mapping(bytes32 => uint256[]) public campaignOverridesTimestamp; + + /// @notice Maps one address to another one to reallocate rewards for a given campaign + mapping(bytes32 => mapping(address => address)) public campaignReallocation; + + /// @notice List all reallocated address for a given campaign + mapping(bytes32 => address[]) public campaignListReallocation; /*////////////////////////////////////////////////////////////////////////////////////////////////////////////////// EVENTS @@ -282,12 +288,18 @@ contract DistributionCreator is UUPSHelper, ReentrancyGuardUpgradeable { newCampaign.rewardToken != _campaign.rewardToken || newCampaign.amount != _campaign.amount || newCampaign.startTimestamp != _campaign.startTimestamp || - // TODO we may want to be able to override the duration - // but we need to make sure that rewards/s is not an invariant in the engine - newCampaign.duration != _campaign.duration + // End timestamp should be in the future + newCampaign.duration <= block.timestamp - _campaign.startTimestamp ) revert InvalidOverride(); + + // Take a new fee to not trick the system by creating a campaign with the smallest fee + // and then overriding it with a campaign with a bigger fee + _computeFees(newCampaign.campaignType, newCampaign.amount, newCampaign.rewardToken); + + newCampaign.campaignId = _campaignId; + newCampaign.creator = msg.sender; campaignOverrides[_campaignId] = newCampaign; - campaignOverridesBlocks[_campaignId].push(block.number); + campaignOverridesTimestamp[_campaignId].push(block.timestamp); emit CampaignOverride(_campaignId, newCampaign); } @@ -299,7 +311,8 @@ contract DistributionCreator is UUPSHelper, ReentrancyGuardUpgradeable { /// @dev It is meant to be used for the case of addresses accruing rewards but unable to claim them function reallocateCampaignRewards(bytes32 _campaignId, address[] memory froms, address to) external { CampaignParameters memory _campaign = campaign(_campaignId); - if (_campaign.creator != msg.sender) revert InvalidOverride(); + if (_campaign.creator != msg.sender || block.timestamp < _campaign.startTimestamp + _campaign.duration) + revert InvalidOverride(); uint256 fromsLength = froms.length; address[] memory successfullFrom = new address[](fromsLength); @@ -308,6 +321,8 @@ contract DistributionCreator is UUPSHelper, ReentrancyGuardUpgradeable { (uint208 amount, uint48 timestamp, ) = Distributor(distributor).claimed(froms[i], _campaign.rewardToken); if (amount == 0 && timestamp == 0) { successfullFrom[count] = froms[i]; + campaignReallocation[_campaignId][froms[i]] = to; + campaignListReallocation[_campaignId].push(froms[i]); count++; } } @@ -400,6 +415,14 @@ contract DistributionCreator is UUPSHelper, ReentrancyGuardUpgradeable { return _getCampaignsBetween(start, end, skip, first); } + function getCampaignOverridesTimestamp(bytes32 _campaignId) external view returns (uint256[] memory) { + return campaignOverridesTimestamp[_campaignId]; + } + + function getCampaignListReallocation(bytes32 _campaignId) external view returns (address[] memory) { + return campaignListReallocation[_campaignId]; + } + /*////////////////////////////////////////////////////////////////////////////////////////////////////////////////// GOVERNANCE FUNCTIONS //////////////////////////////////////////////////////////////////////////////////////////////////////////////////*/ @@ -507,12 +530,13 @@ contract DistributionCreator is UUPSHelper, ReentrancyGuardUpgradeable { if (newCampaign.creator == address(0)) newCampaign.creator = msg.sender; // Computing fees: these are waived for whitelisted addresses and if there is a whitelisted token in a pool - uint256 _fees = campaignSpecificFees[newCampaign.campaignType]; - if (_fees == 1) _fees = 0; - else if (_fees == 0) _fees = defaultFees; - uint256 campaignAmountMinusFees = _computeFees(_fees, newCampaign.amount, newCampaign.rewardToken); + uint256 campaignAmountMinusFees = _computeFees( + newCampaign.campaignType, + newCampaign.amount, + newCampaign.rewardToken + ); + IERC20(newCampaign.rewardToken).safeTransferFrom(msg.sender, distributor, campaignAmountMinusFees); newCampaign.amount = campaignAmountMinusFees; - newCampaign.campaignId = campaignId(newCampaign); if (_campaignLookup[newCampaign.campaignId] != 0) revert CampaignAlreadyExists(); @@ -581,10 +605,14 @@ contract DistributionCreator is UUPSHelper, ReentrancyGuardUpgradeable { /// @notice Computes the fees to be taken on a campaign and transfers them to the fee recipient function _computeFees( - uint256 baseFeesValue, + uint32 campaignType, uint256 distributionAmount, address rewardToken ) internal returns (uint256 distributionAmountMinusFees) { + uint256 baseFeesValue = campaignSpecificFees[campaignType]; + if (baseFeesValue == 1) baseFeesValue = 0; + else if (baseFeesValue == 0) baseFeesValue = defaultFees; + uint256 _fees = (baseFeesValue * (BASE_9 - feeRebate[msg.sender])) / BASE_9; distributionAmountMinusFees = distributionAmount; if (_fees != 0) { @@ -597,7 +625,6 @@ contract DistributionCreator is UUPSHelper, ReentrancyGuardUpgradeable { distributionAmount - distributionAmountMinusFees ); } - IERC20(rewardToken).safeTransferFrom(msg.sender, distributor, distributionAmountMinusFees); } /// @notice Internal version of the `sign` function diff --git a/foundry.toml b/foundry.toml index 8714838..815f78c 100644 --- a/foundry.toml +++ b/foundry.toml @@ -9,7 +9,7 @@ gas_reports = ["*"] via_ir = true optimizer_runs=100 -# solc_version = '0.8.17' +solc_version = '0.8.25' ffi = true @@ -20,15 +20,31 @@ runs = 500 runs = 500 [profile.dev] -via_ir = false +via_ir = true [rpc_endpoints] +arbitrum = "${ETH_NODE_URI_ARBITRUM}" +gnosis = "${ETH_NODE_URI_GNOSIS}" mainnet = "${ETH_NODE_URI_MAINNET}" +optimism = "${ETH_NODE_URI_OPTIMISM}" polygon = "${ETH_NODE_URI_POLYGON}" +fork = "${ETH_NODE_URI_FORK}" +avalanche = "${ETH_NODE_URI_AVALANCHE}" +celo = "${ETH_NODE_URI_CELO}" polygonzkevm = "${ETH_NODE_URI_POLYGONZKEVM}" +bsc = "${ETH_NODE_URI_BSC}" +base = "${ETH_NODE_URI_BASE}" +linea = "${ETH_NODE_URI_LINEA}" [etherscan] +arbitrum = { key = "${ARBITRUM_ETHERSCAN_API_KEY}" } +gnosis = { key = "${GNOSIS_ETHERSCAN_API_KEY}" , url = "https://api.gnosisscan.io/api"} mainnet = { key = "${MAINNET_ETHERSCAN_API_KEY}" } +optimism = { key = "${OPTIMISM_ETHERSCAN_API_KEY}" } polygon = { key = "${POLYGON_ETHERSCAN_API_KEY}" } -gnosis = { key = "${GNOSIS_ETHERSCAN_API_KEY}" , url = "https://api.gnosisscan.io/api"} -polygon-zkevm = { key = "${POLYGONZKEVM_ETHERSCAN_API_KEY}" , url = "https://api-zkevm.polygonscan.com/api"} \ No newline at end of file +avalanche = { key = "${AVALANCHE_ETHERSCAN_API_KEY}" } +celo = { key = "${CELO_ETHERSCAN_API_KEY}", url = "https://api.celoscan.io/api" } +base = { key = "${BASE_ETHERSCAN_API_KEY}", url = "https://api.basescan.org/api" } +polygon-zkevm = { key = "${POLYGONZKEVM_ETHERSCAN_API_KEY}", url = "https://api-zkevm.polygonscan.com/api" } +bsc = { key = "${BSC_ETHERSCAN_API_KEY}"} +linea = { key = "${LINEA_ETHERSCAN_API_KEY}"} \ No newline at end of file diff --git a/helpers/fork.sh b/helpers/fork.sh new file mode 100644 index 0000000..ea784a1 --- /dev/null +++ b/helpers/fork.sh @@ -0,0 +1,37 @@ +#! /bin/bash + +source lib/utils/helpers/common.sh + +function main { + if [ ! -f .env ]; then + echo ".env not found!" + exit 1 + fi + source .env + + echo "Which chain would you like to fork ?" + echo "- 1: Ethereum Mainnet" + echo "- 2: Arbitrum" + echo "- 3: Polygon" + echo "- 4: Gnosis" + echo "- 5: Avalanche" + echo "- 6: Base" + echo "- 7: Binance Smart Chain" + echo "- 8: Celo" + echo "- 9: Polygon ZkEvm" + echo "- 10: Optimism" + echo "- 11: Linea" + + read option + + uri=$(chain_to_uri $option) + if [ -z "$uri" ]; then + echo "Unknown network" + exit 1 + fi + + echo "Forking $uri" + anvil --fork-url $uri +} + +main diff --git a/lib/forge-std b/lib/forge-std index 705263c..0e70977 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit 705263c95892a906d7af65f0f73ce8a4a0c80b80 +Subproject commit 0e7097750918380d84dd3cfdef595bee74dabb70 diff --git a/package.json b/package.json index 0a5507f..462223a 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "deploy:agla": "hardhat deploy --tags aglaMerkl --network", "deploy": "hardhat deploy --tags distributionCreator --network", "etherscan": "hardhat etherscan-verify --network", + "fork": "bash helpers/fork.sh", "foundry:compile": "forge build --optimize --optimizer-runs 1000", "foundry:coverage": "forge coverage --ir-minimum --report lcov && yarn lcov:clean && yarn lcov:generate-html", "foundry:deploy": "forge script --broadcast --verify -vvvv", @@ -34,6 +35,10 @@ "hardhat:deploy": "hardhat deploy --network linea --tags", "hardhat:test": "hardhat test", "check-upgradeability": "hardhat run scripts/checkUpgradeability.ts", + "impersonate": "cast rpc anvil_impersonateAccount", + "impersonate:script": "FOUNDRY_PROFILE=dev forge script --skip test --fork-url fork --broadcast -vvvv --gas-price 0 --priority-gas-price 0 --unlocked --sender", + "impersonate:setBalance": "cast rpc anvil_setBalance 0x19c41f6607b2c0e80e84baadaf886b17565f278e 1000000000000000000", + "fork:advanceTime": "cast rpc evm_increaseTime 3600 && cast rpc anvil_mine", "license": "hardhat prepend-spdx-license", "node:fork": "FORK=true hardhat node", "size": "yarn hardhat:compile && hardhat size-contracts", @@ -52,7 +57,7 @@ "url": "https://github.com/AngleProtocol/merkl-contracts/issues" }, "devDependencies": { - "@angleprotocol/sdk": "2.7.0", + "@angleprotocol/sdk": "^v2.28.19", "@ethersproject/abi": "^5.7.0", "@ethersproject/providers": "^5.7.1", "@nomicfoundation/hardhat-chai-matchers": "^1.0.3", @@ -118,4 +123,4 @@ "solc": "0.8.12", "yargs": "^17.5.1" } -} +} \ No newline at end of file diff --git a/scripts/foundry/CreateCampaign.s.sol b/scripts/foundry/CreateCampaign.s.sol new file mode 100644 index 0000000..b097a93 --- /dev/null +++ b/scripts/foundry/CreateCampaign.s.sol @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.17; + +import { console } from "forge-std/console.sol"; +import { DistributionCreator, DistributionParameters, CampaignParameters } from "contracts/DistributionCreator.sol"; +import { MockToken, IERC20 } from "contracts/mock/MockToken.sol"; +import { CommonUtils, ContractType } from "utils/src/CommonUtils.sol"; +import { CHAIN_BASE } from "utils/src/Constants.sol"; +import { StdAssertions } from "forge-std/Test.sol"; + +contract CreateCampaign is CommonUtils, StdAssertions { + function run() external { + uint256 deployerPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY"); + address deployer = vm.addr(deployerPrivateKey); + + /// TODO: COMPLETE + uint256 chainId = CHAIN_BASE; + IERC20 rewardToken = IERC20(0xC011882d0f7672D8942e7fE2248C174eeD640c8f); + uint256 amount = 100 ether; + /// END + + DistributionCreator distributionCreator = DistributionCreator( + _chainToContract(chainId, ContractType.DistributionCreator) + ); + + vm.startBroadcast(deployer); + + MockToken(address(rewardToken)).mint(deployer, amount); + rewardToken.approve(address(distributionCreator), amount); + uint32 startTimestamp = uint32(block.timestamp + 600); + bytes32 campaignId = distributionCreator.createCampaign( + CampaignParameters({ + campaignId: bytes32(0), + creator: deployer, + rewardToken: address(rewardToken), + amount: amount, + campaignType: 1, + startTimestamp: startTimestamp, + duration: 3600 * 24, + campaignData: abi.encode( + 0xbEEfa1aBfEbE621DF50ceaEF9f54FdB73648c92C, + new address[](0), + new address[](0), + "", + new bytes[](0), + new bytes[](0), + hex"" + ) + }) + ); + + CampaignParameters memory campaign = distributionCreator.campaign(campaignId); + assertEq(campaign.creator, deployer); + assertEq(campaign.rewardToken, address(rewardToken)); + assertEq(campaign.amount, (amount * (1e9 - distributionCreator.defaultFees())) / 1e9); + assertEq(campaign.campaignType, 1); + assertEq(campaign.startTimestamp, startTimestamp); + assertEq(campaign.duration, 3600 * 24); + + vm.stopBroadcast(); + } +} diff --git a/scripts/foundry/UpdateCampaign.s.sol b/scripts/foundry/UpdateCampaign.s.sol new file mode 100644 index 0000000..9e7ff86 --- /dev/null +++ b/scripts/foundry/UpdateCampaign.s.sol @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.17; + +import { console } from "forge-std/console.sol"; +import { DistributionCreator, DistributionParameters, CampaignParameters } from "contracts/DistributionCreator.sol"; +import { MockToken, IERC20 } from "contracts/mock/MockToken.sol"; +import { CommonUtils, ContractType } from "utils/src/CommonUtils.sol"; +import { CHAIN_BASE } from "utils/src/Constants.sol"; +import { StdAssertions } from "forge-std/Test.sol"; +import { ERC4626 } from "@openzeppelin/contracts/token/ERC20/extensions/ERC4626.sol"; + +contract UpdateCampaign is CommonUtils, StdAssertions { + function run() external { + uint256 deployerPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY"); + address deployer = vm.addr(deployerPrivateKey); + + /// TODO: COMPLETE + uint256 chainId = CHAIN_BASE; + IERC20 rewardToken = IERC20(0xC011882d0f7672D8942e7fE2248C174eeD640c8f); + uint256 amount = 97 ether; // after fees + bytes32 campaignId = 0xba2af37b09cc7627766d25a587bb481cede79c7e7db30ce68ca01f0555cdd828; + uint32 startTimestamp = uint32(1732191162); + uint32 duration = 3600 * 10; + /// END + + DistributionCreator distributionCreator = DistributionCreator( + _chainToContract(chainId, ContractType.DistributionCreator) + ); + uint32 timestamp = uint32(block.timestamp); + + // // Do some mint and deposit to change a lot reward distribution + // vm.startBroadcast(0x0022228a2cc5E7eF0274A7Baa600d44da5aB5776); + // MockToken(address(0x0000206329b97DB379d5E1Bf586BbDB969C63274)).mint(deployer, 100_000 ether); + // vm.stopBroadcast(); + + vm.startBroadcast(deployer); + + // IERC20(0x0000206329b97DB379d5E1Bf586BbDB969C63274).approve( + // address(0xbEEfa1aBfEbE621DF50ceaEF9f54FdB73648c92C), + // 100_000 ether + // ); + // ERC4626(0xbEEfa1aBfEbE621DF50ceaEF9f54FdB73648c92C).deposit(100_000 ether, deployer); + + // // ERC20 distrib change duration + // uint32 campaignType = 1; + // bytes memory campaignData = abi.encode( + // 0xbEEfa1aBfEbE621DF50ceaEF9f54FdB73648c92C, + // new address[](0), + // new address[](0), + // "", + // new bytes[](0), + // new bytes[](0), + // hex"" + // ); + + // // ERC20 distrib + // uint32 campaignType = 1; + // bytes memory campaignData = abi.encode( + // 0x70F796946eD919E4Bc6cD506F8dACC45E4539771, + // new address[](0), + // new address[](0), + // "", + // new bytes[](0), + // new bytes[](0), + // hex"" + // ); + + // // Silo distrib + // address[] memory whitelist = new address[](1); + // whitelist[0] = 0x8095806d8753C0443C118D1C5e5eEC472e30BFeC; + // uint32 campaignType = 5; + // bytes memory campaignData = abi.encode( + // 0x04C0599Ae5A44757c0af6F9eC3b93da8976c150A, + // 2, + // 0xa42001D6d2237d2c74108FE360403C4b796B7170, + // whitelist, + // new address[](0), + // hex"" + // ); + + // CLAMM distrib + uint32 campaignType = 2; + bytes memory campaignData = abi.encode( + 0x5280d5E63b416277d0F81FAe54Bb1e0444cAbDAA, + 5100, + 1700, + 3200, + false, + address(0), + 1, + new address[](0), + new address[](0), + "", + new bytes[](0), + hex"" + ); + + distributionCreator.overrideCampaign( + campaignId, + CampaignParameters({ + campaignId: campaignId, + creator: deployer, + rewardToken: address(rewardToken), + amount: amount, + campaignType: campaignType, + startTimestamp: startTimestamp, + duration: duration, + campaignData: campaignData + }) + ); + + ( + , + address campaignCreator, + address campaignRewardToken, + uint256 campaignAmount, + uint256 campaignCampaignType, + uint32 campaignStartTimestamp, + uint32 campaignDuration, + bytes memory campaignCampaignData + ) = distributionCreator.campaignOverrides(campaignId); + assertEq(campaignCreator, deployer); + assertEq(campaignRewardToken, address(rewardToken)); + assertEq(campaignAmount, amount); + assertEq(campaignCampaignType, campaignType); + assertEq(campaignStartTimestamp, startTimestamp); + assertEq(campaignDuration, duration); + assertEq(campaignCampaignData, campaignData); + // assertLt(distributionCreator.campaignOverridesTimestamp(campaignId, 0), timestamp); + // assertLt(distributionCreator.campaignOverridesTimestamp(campaignId, 1), timestamp); + // assertLt(distributionCreator.campaignOverridesTimestamp(campaignId, 2), timestamp); + assertGe(distributionCreator.campaignOverridesTimestamp(campaignId, 0), timestamp); + vm.expectRevert(); + distributionCreator.campaignOverridesTimestamp(campaignId, 1); + + vm.stopBroadcast(); + } +} diff --git a/scripts/foundry/UpgradeDistributionCreator.s.sol b/scripts/foundry/UpgradeDistributionCreator.s.sol new file mode 100644 index 0000000..220e6f8 --- /dev/null +++ b/scripts/foundry/UpgradeDistributionCreator.s.sol @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.17; + +import { console } from "forge-std/console.sol"; +import { DistributionCreator, DistributionParameters, CampaignParameters } from "contracts/DistributionCreator.sol"; +import { CommonUtils, ContractType } from "utils/src/CommonUtils.sol"; +import { CHAIN_BASE } from "utils/src/Constants.sol"; +import { StdAssertions } from "forge-std/Test.sol"; + +contract UpgradeDistributionCreator is CommonUtils, StdAssertions { + function run() external { + uint256 deployerPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY"); + address deployer = vm.addr(deployerPrivateKey); + + /// TODO: COMPLETE + uint256 chainId = CHAIN_BASE; + /// END + + DistributionCreator distributionCreator = DistributionCreator( + _chainToContract(chainId, ContractType.DistributionCreator) + ); + address governor = _chainToContract(chainId, ContractType.AngleLabsMultisig); + + vm.startBroadcast(deployer); + // We deploy the new implementation + address creatorImpl = address(new DistributionCreator()); + vm.stopBroadcast(); + + // Upgrade + vm.startBroadcast(governor); + distributionCreator.upgradeTo(address(creatorImpl)); + vm.stopBroadcast(); + + // Test storage + assertEq(address(distributionCreator.core()), _chainToContract(chainId, ContractType.CoreMerkl)); + assertEq(address(distributionCreator.distributor()), _chainToContract(chainId, ContractType.Distributor)); + assertEq(distributionCreator.defaultFees(), 0.03e9); + assertEq( + distributionCreator.message(), + '" 1. Merkl is experimental software provided as is, use it at your own discretion. There may notably be delays in the onchain Merkle root updates and there may be flaws in the script (or engine) or in the infrastructure used to update results onchain. In that regard, everyone can permissionlessly dispute the rewards which are posted onchain, and when creating a distribution, you are responsible for checking the results and eventually dispute them. 2. If you are specifying an invalid pool address or a pool from an AMM that is not marked as supported, your rewards will not be taken into account and you will not be able to recover them. 3. If you do not blacklist liquidity position managers or smart contract addresses holding LP tokens that are not natively supported by the Merkl system, or if you don\'t specify the addresses of the liquidity position managers that are not automatically handled by the system, then the script will not be able to take the specifities of these addresses into account, and it will reward them like a normal externally owned account would be. If these are smart contracts that do not support external rewards, then rewards that should be accruing to it will be lost. 4. If rewards sent through Merkl remain unclaimed for a period of more than 1 year after the end of the distribution (because they are meant for instance for smart contract addresses that cannot claim or deal with them), then we reserve the right to recover these rewards. 5. Fees apply to incentives deposited on Merkl, unless the pools incentivized contain a whitelisted token (e.g an Angle Protocol stablecoin). 6. By interacting with the Merkl smart contract to deposit an incentive for a pool, you are exposed to smart contract risk and to the offchain mechanism used to compute reward distribution. 7. If the rewards you are sending are too small in value, or if you are sending rewards using a token that is not approved for it, your rewards will not be handled by the script, and they may be lost. 8. If you mistakenly send too much rewards compared with what you wanted to send, you will not be able to call them back. You will also not be able to prematurely end a reward distribution once created. 9. The engine handling reward distribution for a pool may not look at all the swaps occurring on the pool during the time for which you are incentivizing, but just at a subset of it to gain in efficiency. Overall, if you distribute incentives using Merkl, it means that you are aware of how the engine works, of the approximations it makes and of the behaviors it may trigger (e.g. just in time liquidity). 10. Rewards corresponding to incentives distributed through Merkl do not compound block by block, but are regularly made available (through a Merkle root update) at a frequency which depends on the chain. "' + ); + assertEq(distributionCreator.messageHash(), 0x08dabc24dcfcb230453d08bce47c730ed6f1cce205bc153680488959b503644e); + { + (bytes32 campaignId, , , , , , , , , , , , ) = distributionCreator.distributionList(0); + assertEq(campaignId, 0xb3fc2abc303c70a16ab9d5fc38d7e8aeae66593a87a3d971b024dd34b97e94b1); + } + { + (bytes32 campaignId, , , , , , , , , , , , ) = distributionCreator.distributionList(73); + assertEq(campaignId, 0x157a32c11ce34030465e1c28c309f38c18161028355f3446f54b677d11ceb63a); + } + assertEq(distributionCreator.feeRebate(0xfdA462548Ce04282f4B6D6619823a7C64Fdc0185), 0); + assertEq(distributionCreator.isWhitelistedToken(_chainToContract(chainId, ContractType.AgEUR)), 1); + assertEq(distributionCreator._nonces(0xfdA462548Ce04282f4B6D6619823a7C64Fdc0185), 4); + assertEq( + distributionCreator.userSignatures(0xfdA462548Ce04282f4B6D6619823a7C64Fdc0185), + 0x08dabc24dcfcb230453d08bce47c730ed6f1cce205bc153680488959b503644e + ); + assertEq(distributionCreator.userSignatureWhitelist(0xfdA462548Ce04282f4B6D6619823a7C64Fdc0185), 0); + + assertEq(distributionCreator.rewardTokens(0), 0x7D49a065D17d6d4a55dc13649901fdBB98B2AFBA); + assertEq(distributionCreator.rewardTokens(21), 0xF734eFdE0C424BA2B547b186586dE417b0954802); + assertEq(distributionCreator.rewardTokenMinAmounts(0x7D49a065D17d6d4a55dc13649901fdBB98B2AFBA), 1 ether); + + { + (bytes32 campaignId, , , , , , , ) = distributionCreator.campaignList(0); + assertEq(campaignId, 0x4e2bf13f682a244a80e0f25e1545fc8ad3a181d60658d22a3d347ee493e2a740); + } + { + (bytes32 campaignId, , , , , , , ) = distributionCreator.campaignList(67); + assertEq(campaignId, 0xf7d416acc480a41cd4cbb1bd68941f2f585adb659bd95d45e193589175356972); + } + assertEq(distributionCreator.campaignSpecificFees(4), 0.005e9); + + { + (bytes32 campaignId, , , , , , , ) = distributionCreator.campaignOverrides( + 0xf7d416acc480a41cd4cbb1bd68941f2f585adb659bd95d45e193589175356972 + ); + assertEq(campaignId, bytes32(0)); + } + + vm.expectRevert(); + distributionCreator.campaignOverridesTimestamp( + 0x4e2bf13f682a244a80e0f25e1545fc8ad3a181d60658d22a3d347ee493e2a740, + 0 + ); + } +} diff --git a/test/foundry/DistributionCreator.t.sol b/test/foundry/DistributionCreator.t.sol index adba03d..7b3c424 100644 --- a/test/foundry/DistributionCreator.t.sol +++ b/test/foundry/DistributionCreator.t.sol @@ -4,8 +4,11 @@ pragma solidity ^0.8.17; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { DistributionParameters } from "../../contracts/DistributionCreator.sol"; import "./Fixture.t.sol"; +import { CampaignParameters } from "contracts/DistributionCreator.sol"; +import { Distributor, MerkleTree } from "contracts/Distributor.sol"; +import "contracts/utils/Errors.sol" as Errors; -contract DistributionCreatorTest is Fixture { +contract DistributionCreatorCreateCampaignTest is Fixture { using SafeERC20 for IERC20; uint256 constant maxDistribForOOG = 1e4; @@ -23,6 +26,9 @@ contract DistributionCreatorTest is Fixture { numEpoch = 25; initEndTime = startTime + numEpoch * EPOCH_DURATION; + vm.prank(governor); + creator.setFeeRecipient(dylan); + vm.startPrank(guardian); creator.toggleSigningWhitelist(alice); creator.toggleTokenWhitelist(address(agEUR)); @@ -37,115 +43,1101 @@ contract DistributionCreatorTest is Fixture { vm.prank(alice); angle.approve(address(creator), type(uint256).max); - address[] memory positionWrappers = new address[](3); - uint32[] memory wrapperTypes = new uint32[](3); - positionWrappers[0] = alice; - positionWrappers[1] = bob; - positionWrappers[2] = charlie; - wrapperTypes[0] = 0; - wrapperTypes[1] = 1; - wrapperTypes[2] = 2; - - vm.startPrank(alice); - // struct DistributionParameters memory - // create a bunch of distributions to make the view function call fail - DistributionParameters memory params = DistributionParameters({ - uniV3Pool: address(pool), - rewardToken: address(angle), - positionWrappers: positionWrappers, - wrapperTypes: wrapperTypes, - amount: 1e10, - propToken0: 4000, - propToken1: 2000, - propFees: 4000, - isOutOfRangeIncentivized: 0, - epochStart: initStartTime, - numEpoch: 25, - boostedReward: 0, - boostingAddress: address(0), - rewardId: keccak256("TEST"), - additionalData: hex"" - }); - // create a first distrib way before the others - creator.createDistribution(params); - - vm.warp(startTime + 3600 * 24 * 1000); - startTime = uint32(block.timestamp); - endTime = startTime + numEpoch * EPOCH_DURATION; - params.epochStart = startTime; - for (uint256 i; i < nbrDistrib; i++) creator.createDistribution(params); + vm.stopPrank(); + } + + function testUnit_CreateCampaignWithDefaultFees() public { + IERC20 rewardToken = IERC20(address(angle)); + uint256 amount = 100 ether; + uint256 amountAfterFees = 90 ether; + uint32 startTimestamp = uint32(block.timestamp + 600); + + vm.prank(alice); + bytes32 campaignId = creator.createCampaign( + CampaignParameters({ + campaignId: bytes32(0), + creator: alice, + rewardToken: address(rewardToken), + amount: amount, + campaignType: 1, + startTimestamp: startTimestamp, + duration: 3600 * 24, + campaignData: abi.encode( + 0xbEEfa1aBfEbE621DF50ceaEF9f54FdB73648c92C, + new address[](0), + new address[](0), + "", + new bytes[](0), + new bytes[](0), + hex"" + ) + }) + ); + + CampaignParameters memory campaign = creator.campaign(campaignId); + assertEq(campaign.creator, alice); + assertEq(campaign.rewardToken, address(rewardToken)); + assertEq(campaign.amount, (amount * (1e9 - creator.defaultFees())) / 1e9); + assertEq(campaign.campaignType, 1); + assertEq(campaign.startTimestamp, startTimestamp); + assertEq(campaign.duration, 3600 * 24); + } + + function testUnit_CreateCampaignWithSetFees() public { + uint32 campaignType = 1; + vm.prank(guardian); + creator.setCampaignFees(campaignType, 1e7); + + IERC20 rewardToken = IERC20(address(angle)); + uint256 amount = 100 ether; + uint256 amountAfterFees = 90 ether; + uint32 startTimestamp = uint32(block.timestamp + 600); + + uint256 prevBalance = rewardToken.balanceOf(alice); + + vm.prank(alice); + bytes32 campaignId = creator.createCampaign( + CampaignParameters({ + campaignId: bytes32(0), + creator: alice, + rewardToken: address(rewardToken), + amount: amount, + campaignType: campaignType, + startTimestamp: startTimestamp, + duration: 3600 * 24, + campaignData: abi.encode( + 0xbEEfa1aBfEbE621DF50ceaEF9f54FdB73648c92C, + new address[](0), + new address[](0), + "", + new bytes[](0), + new bytes[](0), + hex"" + ) + }) + ); + + CampaignParameters memory campaign = creator.campaign(campaignId); + assertEq(campaign.creator, alice); + assertEq(campaign.rewardToken, address(rewardToken)); + assertEq(campaign.amount, (amount * (1e9 - 1e7)) / 1e9); + assertEq(campaign.campaignType, 1); + assertEq(campaign.startTimestamp, startTimestamp); + assertEq(campaign.duration, 3600 * 24); + assertEq(rewardToken.balanceOf(alice), prevBalance - amount); + assertEq(rewardToken.balanceOf(dylan), (amount * 1e7) / 1e9); + } +} +contract DistributionCreatorCreateReallocationTest is Fixture { + using SafeERC20 for IERC20; + + Distributor public distributor; + Distributor public distributorImpl; + uint32 initStartTime; + uint32 initEndTime; + uint32 startTime; + uint32 endTime; + uint32 numEpoch; + + function setUp() public override { + super.setUp(); + + distributorImpl = new Distributor(); + distributor = Distributor(deployUUPS(address(distributorImpl), hex"")); + distributor.initialize(IAccessControlManager(address(coreBorrow))); + + vm.startPrank(governor); + distributor.setDisputeAmount(1e18); + distributor.setDisputePeriod(1 days); + distributor.setDisputeToken(angle); vm.stopPrank(); + + initStartTime = uint32(block.timestamp); + numEpoch = 25; + initEndTime = startTime + numEpoch * EPOCH_DURATION; + + vm.startPrank(governor); + creator.setNewDistributor(address(distributor)); + creator.setFeeRecipient(dylan); + vm.stopPrank(); + + vm.startPrank(guardian); + creator.toggleSigningWhitelist(alice); + creator.toggleTokenWhitelist(address(agEUR)); + address[] memory tokens = new address[](2); + uint256[] memory amounts = new uint256[](2); + tokens[0] = address(angle); + amounts[0] = 1e8; + tokens[1] = address(agEUR); + amounts[1] = 1e8; + creator.setRewardTokenMinAmounts(tokens, amounts); + vm.stopPrank(); + + angle.mint(address(alice), 1e22); + vm.prank(alice); + angle.approve(address(creator), type(uint256).max); + + agEUR.mint(address(alice), 1e22); + vm.prank(alice); + agEUR.approve(address(creator), type(uint256).max); + + vm.stopPrank(); + } + + function testUnit_ReallocationCampaignRewards_revertWhen_TooSoon() public { + IERC20 rewardToken = IERC20(address(agEUR)); + uint256 amount = 100 ether; + uint32 startTimestamp = uint32(block.timestamp + 600); + + vm.prank(alice); + bytes32 campaignId = creator.createCampaign( + CampaignParameters({ + campaignId: bytes32(0), + creator: alice, + rewardToken: address(rewardToken), + amount: amount, + campaignType: 1, + startTimestamp: startTimestamp, + duration: 3600 * 48, + campaignData: abi.encode( + 0xbEEfa1aBfEbE621DF50ceaEF9f54FdB73648c92C, + new address[](0), + new address[](0), + "", + new bytes[](0), + new bytes[](0), + hex"" + ) + }) + ); + + vm.prank(governor); + // Create false tree + distributor.updateTree( + MerkleTree({ + merkleRoot: bytes32(0x0b70a97c062cb747158b89e27df5bbda859ba072232efcbe92e383e9d74b8555), + ipfsHash: keccak256("IPFS_HASH") + }) + ); + + angle.mint(address(distributor), 1e18); + agEUR.mint(address(distributor), 5e17); + + vm.warp(distributor.endOfDisputePeriod() + 1); + { + bytes32[][] memory proofs = new bytes32[][](1); + address[] memory users = new address[](1); + address[] memory tokens = new address[](1); + uint256[] memory amounts = new uint256[](1); + // proofs[0] = new bytes32[](1); + // proofs[0][0] = bytes32(0x6f46ee2909b99367a0d9932a11f1bdb85c9354480c9de277d21086f9a8925c0a); + // users[0] = alice; + // tokens[0] = address(angle); + // amounts[0] = 1e18; + proofs[0] = new bytes32[](1); + proofs[0][0] = bytes32(0x3a64e591d79db8530701e6f3dbdd95dc74681291b327d0ce4acc97024a61430c); + users[0] = bob; + tokens[0] = address(agEUR); + amounts[0] = 5e17; + vm.prank(bob); + distributor.claim(users, tokens, amounts, proofs); + } + + { + address[] memory users = new address[](1); + users[0] = bob; + + vm.prank(alice); + vm.expectRevert(Errors.InvalidOverride.selector); + creator.reallocateCampaignRewards(campaignId, users, address(governor)); + + assertEq(creator.campaignReallocation(campaignId, alice), address(0)); + address[] memory listReallocation = creator.getCampaignListReallocation(campaignId); + assertEq(listReallocation.length, 0); + } + } + + function testUnit_ReallocationCampaignRewards_revertWhen_AlreadyClaimed() public { + IERC20 rewardToken = IERC20(address(agEUR)); + uint256 amount = 100 ether; + uint32 startTimestamp = uint32(block.timestamp + 600); + + vm.prank(alice); + bytes32 campaignId = creator.createCampaign( + CampaignParameters({ + campaignId: bytes32(0), + creator: alice, + rewardToken: address(rewardToken), + amount: amount, + campaignType: 1, + startTimestamp: startTimestamp, + duration: 3600 * 24, + campaignData: abi.encode( + 0xbEEfa1aBfEbE621DF50ceaEF9f54FdB73648c92C, + new address[](0), + new address[](0), + "", + new bytes[](0), + new bytes[](0), + hex"" + ) + }) + ); + + vm.prank(governor); + // Create false tree + distributor.updateTree( + MerkleTree({ + merkleRoot: bytes32(0x0b70a97c062cb747158b89e27df5bbda859ba072232efcbe92e383e9d74b8555), + ipfsHash: keccak256("IPFS_HASH") + }) + ); + + angle.mint(address(distributor), 1e18); + agEUR.mint(address(distributor), 5e17); + + vm.warp(distributor.endOfDisputePeriod() + 1); + { + bytes32[][] memory proofs = new bytes32[][](1); + address[] memory users = new address[](1); + address[] memory tokens = new address[](1); + uint256[] memory amounts = new uint256[](1); + // proofs[0] = new bytes32[](1); + // proofs[0][0] = bytes32(0x6f46ee2909b99367a0d9932a11f1bdb85c9354480c9de277d21086f9a8925c0a); + // users[0] = alice; + // tokens[0] = address(angle); + // amounts[0] = 1e18; + proofs[0] = new bytes32[](1); + proofs[0][0] = bytes32(0x3a64e591d79db8530701e6f3dbdd95dc74681291b327d0ce4acc97024a61430c); + users[0] = bob; + tokens[0] = address(agEUR); + amounts[0] = 5e17; + vm.prank(bob); + distributor.claim(users, tokens, amounts, proofs); + } + + { + address[] memory users = new address[](1); + users[0] = bob; + + vm.prank(alice); + vm.expectRevert(Errors.InvalidOverride.selector); + creator.reallocateCampaignRewards(campaignId, users, address(governor)); + + assertEq(creator.campaignReallocation(campaignId, alice), address(0)); + address[] memory listReallocation = creator.getCampaignListReallocation(campaignId); + assertEq(listReallocation.length, 0); + } } - /* - // Commented because of an update in Foundry which does not handle well out of gas issues - function testFuzz_GetDistributionsOutOfGas() public { - address[] memory positionWrappers = new address[](3); - uint32[] memory wrapperTypes = new uint32[](3); - positionWrappers[0] = alice; - positionWrappers[1] = bob; - positionWrappers[2] = charlie; - wrapperTypes[0] = 0; - wrapperTypes[1] = 1; - wrapperTypes[2] = 2; - - vm.startPrank(alice); - // struct DistributionParameters memory - // create a bunch of distributions to make the view function call fail - DistributionParameters memory params = DistributionParameters({ - uniV3Pool: address(pool), - rewardToken: address(angle), - positionWrappers: positionWrappers, - wrapperTypes: wrapperTypes, - amount: 1e10, - propToken0: 4000, - propToken1: 2000, - propFees: 4000, - isOutOfRangeIncentivized: 0, - epochStart: startTime, - numEpoch: 25, - boostedReward: 0, - boostingAddress: address(0), - rewardId: keccak256("TEST"), - additionalData: hex"" - }); - - vm.warp(startTime + 3600 * 24 * 10); - startTime = uint32(block.timestamp); - endTime = startTime + numEpoch * EPOCH_DURATION; - params.epochStart = startTime; - for (uint256 i; i < maxDistribForOOG; i++) creator.createDistribution(params); + function testUnit_ReallocationCampaignRewards_Success() public { + IERC20 rewardToken = IERC20(address(angle)); + uint256 amount = 100 ether; + uint32 startTimestamp = uint32(block.timestamp + 600); + + vm.prank(alice); + bytes32 campaignId = creator.createCampaign( + CampaignParameters({ + campaignId: bytes32(0), + creator: alice, + rewardToken: address(rewardToken), + amount: amount, + campaignType: 1, + startTimestamp: startTimestamp, + duration: 3600 * 24, + campaignData: abi.encode( + 0xbEEfa1aBfEbE621DF50ceaEF9f54FdB73648c92C, + new address[](0), + new address[](0), + "", + new bytes[](0), + new bytes[](0), + hex"" + ) + }) + ); + + vm.prank(governor); + // Create false tree + distributor.updateTree( + MerkleTree({ + merkleRoot: bytes32(0x0b70a97c062cb747158b89e27df5bbda859ba072232efcbe92e383e9d74b8555), + ipfsHash: keccak256("IPFS_HASH") + }) + ); + + angle.mint(address(distributor), 1e18); + agEUR.mint(address(distributor), 5e17); + + vm.warp(distributor.endOfDisputePeriod() + 1); + { + bytes32[][] memory proofs = new bytes32[][](1); + address[] memory users = new address[](1); + address[] memory tokens = new address[](1); + uint256[] memory amounts = new uint256[](1); + // proofs[0] = new bytes32[](1); + // proofs[0][0] = bytes32(0x6f46ee2909b99367a0d9932a11f1bdb85c9354480c9de277d21086f9a8925c0a); + // users[0] = alice; + // tokens[0] = address(angle); + // amounts[0] = 1e18; + proofs[0] = new bytes32[](1); + proofs[0][0] = bytes32(0x3a64e591d79db8530701e6f3dbdd95dc74681291b327d0ce4acc97024a61430c); + users[0] = bob; + tokens[0] = address(agEUR); + amounts[0] = 5e17; + + uint256 aliceBalance = angle.balanceOf(address(alice)); + uint256 bobBalance = agEUR.balanceOf(address(bob)); + + vm.prank(bob); + distributor.claim(users, tokens, amounts, proofs); + } + { + address[] memory users = new address[](1); + users[0] = alice; + + vm.prank(bob); + vm.expectRevert(Errors.InvalidOverride.selector); + creator.reallocateCampaignRewards(campaignId, users, address(governor)); + + assertEq(creator.campaignReallocation(campaignId, alice), address(0)); + } + + { + address[] memory users = new address[](1); + users[0] = alice; + + vm.prank(alice); + creator.reallocateCampaignRewards(campaignId, users, address(governor)); + + assertEq(creator.campaignReallocation(campaignId, alice), address(governor)); + + address[] memory listReallocation = creator.getCampaignListReallocation(campaignId); + assertEq(listReallocation.length, 1); + assertEq(listReallocation[0], alice); + } + } +} + +contract DistributionCreatorOverrideTest is Fixture { + using SafeERC20 for IERC20; + + uint256 constant maxDistribForOOG = 1e4; + uint256 constant nbrDistrib = 10; + uint32 initStartTime; + uint32 initEndTime; + uint32 startTime; + uint32 endTime; + uint32 numEpoch; + + function setUp() public override { + super.setUp(); + + initStartTime = uint32(block.timestamp); + numEpoch = 25; + initEndTime = startTime + numEpoch * EPOCH_DURATION; + + vm.startPrank(guardian); + creator.toggleSigningWhitelist(alice); + creator.toggleTokenWhitelist(address(agEUR)); + address[] memory tokens = new address[](1); + uint256[] memory amounts = new uint256[](1); + tokens[0] = address(angle); + amounts[0] = 1e8; + creator.setRewardTokenMinAmounts(tokens, amounts); vm.stopPrank(); - // All calls will revert because it is oog - vm.expectRevert(); - creator.getActiveDistributions(); - vm.expectRevert(); - creator.getDistributionsForEpoch(startTime); + angle.mint(address(alice), 1e22); + vm.prank(alice); + angle.approve(address(creator), type(uint256).max); - vm.expectRevert(); - creator.getDistributionsBetweenEpochs(startTime, endTime); + vm.stopPrank(); + } - vm.expectRevert(); - creator.getDistributionsAfterEpoch(startTime); + function testUnit_OverrideCampaignData_RevertWhen_IncorrectCampaignId() public { + IERC20 rewardToken = IERC20(address(angle)); + uint256 amount = 100 ether; + uint256 amountAfterFees = 90 ether; + uint32 startTimestamp = uint32(block.timestamp + 600); - vm.expectRevert(); - creator.getActivePoolDistributions(address(pool)); + vm.prank(alice); + bytes32 campaignId = creator.createCampaign( + CampaignParameters({ + campaignId: bytes32(0), + creator: alice, + rewardToken: address(rewardToken), + amount: amount, + campaignType: 1, + startTimestamp: startTimestamp, + duration: 3600 * 24, + campaignData: abi.encode( + 0xbEEfa1aBfEbE621DF50ceaEF9f54FdB73648c92C, + new address[](0), + new address[](0), + "", + new bytes[](0), + new bytes[](0), + hex"" + ) + }) + ); - vm.expectRevert(); - creator.getPoolDistributionsForEpoch(address(pool), startTime); + vm.warp(block.timestamp + 1000); + vm.roll(4); + // override + + // Silo distrib + address[] memory whitelist = new address[](1); + whitelist[0] = 0x8095806d8753C0443C118D1C5e5eEC472e30BFeC; + bytes memory campaignData = abi.encode( + 0x04C0599Ae5A44757c0af6F9eC3b93da8976c150A, + 2, + 0xa42001D6d2237d2c74108FE360403C4b796B7170, + whitelist, + new address[](0), + hex"" + ); + + vm.expectRevert(Errors.CampaignDoesNotExist.selector); + vm.prank(alice); + creator.overrideCampaign( + keccak256("test"), + CampaignParameters({ + campaignId: campaignId, + creator: alice, + rewardToken: address(rewardToken), + amount: amountAfterFees, + campaignType: 5, + startTimestamp: startTimestamp, + duration: 3600 * 24, + campaignData: campaignData + }) + ); + } + + function testUnit_OverrideCampaignData_RevertWhen_IncorrectCreator() public { + IERC20 rewardToken = IERC20(address(angle)); + uint256 amount = 100 ether; + uint256 amountAfterFees = 90 ether; + uint32 startTimestamp = uint32(block.timestamp + 600); + vm.prank(alice); + bytes32 campaignId = creator.createCampaign( + CampaignParameters({ + campaignId: bytes32(0), + creator: alice, + rewardToken: address(rewardToken), + amount: amount, + campaignType: 1, + startTimestamp: startTimestamp, + duration: 3600 * 24, + campaignData: abi.encode( + 0xbEEfa1aBfEbE621DF50ceaEF9f54FdB73648c92C, + new address[](0), + new address[](0), + "", + new bytes[](0), + new bytes[](0), + hex"" + ) + }) + ); + + vm.warp(block.timestamp + 1000); + vm.roll(4); + // override + + // Silo distrib + address[] memory whitelist = new address[](1); + whitelist[0] = 0x8095806d8753C0443C118D1C5e5eEC472e30BFeC; + bytes memory campaignData = abi.encode( + 0x04C0599Ae5A44757c0af6F9eC3b93da8976c150A, + 2, + 0xa42001D6d2237d2c74108FE360403C4b796B7170, + whitelist, + new address[](0), + hex"" + ); + + vm.expectRevert(Errors.InvalidOverride.selector); + vm.prank(bob); + creator.overrideCampaign( + campaignId, + CampaignParameters({ + campaignId: campaignId, + creator: bob, + rewardToken: address(rewardToken), + amount: amountAfterFees, + campaignType: 5, + startTimestamp: startTimestamp, + duration: 3600 * 24, + campaignData: campaignData + }) + ); + + vm.expectRevert(Errors.InvalidOverride.selector); + vm.prank(bob); + creator.overrideCampaign( + campaignId, + CampaignParameters({ + campaignId: campaignId, + creator: alice, + rewardToken: address(rewardToken), + amount: amountAfterFees, + campaignType: 5, + startTimestamp: startTimestamp, + duration: 3600 * 24, + campaignData: campaignData + }) + ); + } + + function testUnit_OverrideCampaignData_RevertWhen_IncorrectRewardToken() public { + IERC20 rewardToken = IERC20(address(angle)); + uint256 amount = 100 ether; + uint256 amountAfterFees = 90 ether; + uint32 startTimestamp = uint32(block.timestamp + 600); + + vm.prank(alice); + bytes32 campaignId = creator.createCampaign( + CampaignParameters({ + campaignId: bytes32(0), + creator: alice, + rewardToken: address(rewardToken), + amount: amount, + campaignType: 1, + startTimestamp: startTimestamp, + duration: 3600 * 24, + campaignData: abi.encode( + 0xbEEfa1aBfEbE621DF50ceaEF9f54FdB73648c92C, + new address[](0), + new address[](0), + "", + new bytes[](0), + new bytes[](0), + hex"" + ) + }) + ); + + vm.warp(block.timestamp + 1000); + vm.roll(4); + // override + + // Silo distrib + address[] memory whitelist = new address[](1); + whitelist[0] = 0x8095806d8753C0443C118D1C5e5eEC472e30BFeC; + bytes memory campaignData = abi.encode( + 0x04C0599Ae5A44757c0af6F9eC3b93da8976c150A, + 2, + 0xa42001D6d2237d2c74108FE360403C4b796B7170, + whitelist, + new address[](0), + hex"" + ); + + vm.expectRevert(Errors.InvalidOverride.selector); + vm.prank(alice); + creator.overrideCampaign( + campaignId, + CampaignParameters({ + campaignId: campaignId, + creator: alice, + rewardToken: address(alice), + amount: amountAfterFees, + campaignType: 5, + startTimestamp: startTimestamp, + duration: 3600 * 24, + campaignData: campaignData + }) + ); + } + + function testUnit_OverrideCampaignData_RevertWhen_IncorrectRewardAmount() public { + IERC20 rewardToken = IERC20(address(angle)); + uint256 amount = 100 ether; + uint256 amountAfterFees = 90 ether; + uint32 startTimestamp = uint32(block.timestamp + 600); + + vm.prank(alice); + bytes32 campaignId = creator.createCampaign( + CampaignParameters({ + campaignId: bytes32(0), + creator: alice, + rewardToken: address(rewardToken), + amount: amount, + campaignType: 1, + startTimestamp: startTimestamp, + duration: 3600 * 24, + campaignData: abi.encode( + 0xbEEfa1aBfEbE621DF50ceaEF9f54FdB73648c92C, + new address[](0), + new address[](0), + "", + new bytes[](0), + new bytes[](0), + hex"" + ) + }) + ); + + vm.warp(block.timestamp + 1000); + vm.roll(4); + // override + + // Silo distrib + address[] memory whitelist = new address[](1); + whitelist[0] = 0x8095806d8753C0443C118D1C5e5eEC472e30BFeC; + bytes memory campaignData = abi.encode( + 0x04C0599Ae5A44757c0af6F9eC3b93da8976c150A, + 2, + 0xa42001D6d2237d2c74108FE360403C4b796B7170, + whitelist, + new address[](0), + hex"" + ); + + vm.expectRevert(Errors.InvalidOverride.selector); + vm.prank(alice); + creator.overrideCampaign( + campaignId, + CampaignParameters({ + campaignId: campaignId, + creator: alice, + rewardToken: address(alice), + amount: amount, + campaignType: 5, + startTimestamp: startTimestamp, + duration: 3600 * 24, + campaignData: campaignData + }) + ); + } + + function testUnit_OverrideCampaignData_RevertWhen_IncorrectStartTimestamp() public { + IERC20 rewardToken = IERC20(address(angle)); + uint256 amount = 100 ether; + uint256 amountAfterFees = 90 ether; + uint32 startTimestamp = uint32(block.timestamp + 600); + + vm.prank(alice); + bytes32 campaignId = creator.createCampaign( + CampaignParameters({ + campaignId: bytes32(0), + creator: alice, + rewardToken: address(rewardToken), + amount: amount, + campaignType: 1, + startTimestamp: startTimestamp, + duration: 3600 * 24, + campaignData: abi.encode( + 0xbEEfa1aBfEbE621DF50ceaEF9f54FdB73648c92C, + new address[](0), + new address[](0), + "", + new bytes[](0), + new bytes[](0), + hex"" + ) + }) + ); + + vm.warp(block.timestamp + 1000); + vm.roll(4); + // override + + // Silo distrib + address[] memory whitelist = new address[](1); + whitelist[0] = 0x8095806d8753C0443C118D1C5e5eEC472e30BFeC; + bytes memory campaignData = abi.encode( + 0x04C0599Ae5A44757c0af6F9eC3b93da8976c150A, + 2, + 0xa42001D6d2237d2c74108FE360403C4b796B7170, + whitelist, + new address[](0), + hex"" + ); + + vm.expectRevert(Errors.InvalidOverride.selector); + vm.prank(alice); + creator.overrideCampaign( + campaignId, + CampaignParameters({ + campaignId: campaignId, + creator: alice, + rewardToken: address(rewardToken), + amount: amount, + campaignType: 5, + startTimestamp: startTimestamp + 1, + duration: 3600 * 24, + campaignData: campaignData + }) + ); + } + + function testUnit_OverrideCampaignData_RevertWhen_IncorrectDuration() public { + IERC20 rewardToken = IERC20(address(angle)); + uint256 amount = 100 ether; + uint256 amountAfterFees = 90 ether; + uint32 startTimestamp = uint32(block.timestamp + 600); + + vm.prank(alice); + bytes32 campaignId = creator.createCampaign( + CampaignParameters({ + campaignId: bytes32(0), + creator: alice, + rewardToken: address(rewardToken), + amount: amount, + campaignType: 1, + startTimestamp: startTimestamp, + duration: 3600 * 24, + campaignData: abi.encode( + 0xbEEfa1aBfEbE621DF50ceaEF9f54FdB73648c92C, + new address[](0), + new address[](0), + "", + new bytes[](0), + new bytes[](0), + hex"" + ) + }) + ); + + vm.warp(block.timestamp + 1000); + vm.roll(4); + // override + + // Silo distrib + address[] memory whitelist = new address[](1); + whitelist[0] = 0x8095806d8753C0443C118D1C5e5eEC472e30BFeC; + bytes memory campaignData = abi.encode( + 0x04C0599Ae5A44757c0af6F9eC3b93da8976c150A, + 2, + 0xa42001D6d2237d2c74108FE360403C4b796B7170, + whitelist, + new address[](0), + hex"" + ); + + vm.expectRevert(Errors.InvalidOverride.selector); + vm.prank(alice); + creator.overrideCampaign( + campaignId, + CampaignParameters({ + campaignId: campaignId, + creator: alice, + rewardToken: address(rewardToken), + amount: amount, + campaignType: 5, + startTimestamp: startTimestamp, + duration: 399, + campaignData: campaignData + }) + ); + } + + function testUnit_OverrideCampaignData() public { + IERC20 rewardToken = IERC20(address(angle)); + uint256 amount = 100 ether; + uint256 amountAfterFees = 90 ether; + uint32 startTimestamp = uint32(block.timestamp + 600); + + vm.prank(alice); + bytes32 campaignId = creator.createCampaign( + CampaignParameters({ + campaignId: bytes32(0), + creator: alice, + rewardToken: address(rewardToken), + amount: amount, + campaignType: 1, + startTimestamp: startTimestamp, + duration: 3600 * 24, + campaignData: abi.encode( + 0xbEEfa1aBfEbE621DF50ceaEF9f54FdB73648c92C, + new address[](0), + new address[](0), + "", + new bytes[](0), + new bytes[](0), + hex"" + ) + }) + ); + + CampaignParameters memory campaign = creator.campaign(campaignId); + assertEq(campaign.creator, alice); + assertEq(campaign.rewardToken, address(rewardToken)); + assertEq(campaign.amount, (amount * (1e9 - creator.defaultFees())) / 1e9); + assertEq(campaign.campaignType, 1); + assertEq(campaign.startTimestamp, startTimestamp); + assertEq(campaign.duration, 3600 * 24); + + vm.warp(block.timestamp + 1000); + vm.roll(4); + // override + + // Silo distrib + address[] memory whitelist = new address[](1); + whitelist[0] = 0x8095806d8753C0443C118D1C5e5eEC472e30BFeC; + bytes memory campaignData = abi.encode( + 0x04C0599Ae5A44757c0af6F9eC3b93da8976c150A, + 2, + 0xa42001D6d2237d2c74108FE360403C4b796B7170, + whitelist, + new address[](0), + hex"" + ); + + vm.prank(alice); + creator.overrideCampaign( + campaignId, + CampaignParameters({ + campaignId: campaignId, + creator: alice, + rewardToken: address(rewardToken), + amount: amountAfterFees, + campaignType: 5, + startTimestamp: startTimestamp, + duration: 3600 * 24, + campaignData: campaignData + }) + ); + + ( + , + address campaignCreator, + address campaignRewardToken, + uint256 campaignAmount, + uint256 campaignType, + uint32 campaignStartTimestamp, + uint32 campaignDuration, + bytes memory campaignCampaignData + ) = creator.campaignOverrides(campaignId); + assertEq(campaignCreator, alice); + assertEq(campaignRewardToken, address(rewardToken)); + assertEq(campaignAmount, amountAfterFees); + assertEq(campaignType, 5); + assertEq(campaignStartTimestamp, startTimestamp); + assertEq(campaignDuration, 3600 * 24); + assertEq(campaignCampaignData, campaignData); + assertGe(creator.campaignOverridesTimestamp(campaignId, 0), startTimestamp); vm.expectRevert(); - creator.getPoolDistributionsBetweenEpochs(address(pool), startTime, endTime); + creator.campaignOverridesTimestamp(campaignId, 1); + } + + function testUnit_OverrideCampaignDuration() public { + IERC20 rewardToken = IERC20(address(angle)); + uint256 amount = 100 ether; + uint256 amountAfterFees = 90 ether; + uint32 startTimestamp = uint32(block.timestamp + 600); + uint32 duration = 3600 * 24; + uint32 durationAfterOverride = 3600 * 12; + + bytes memory campaignData = abi.encode( + 0xbEEfa1aBfEbE621DF50ceaEF9f54FdB73648c92C, + new address[](0), + new address[](0), + "", + new bytes[](0), + new bytes[](0), + hex"" + ); + vm.prank(alice); + bytes32 campaignId = creator.createCampaign( + CampaignParameters({ + campaignId: bytes32(0), + creator: alice, + rewardToken: address(rewardToken), + amount: amount, + campaignType: 1, + startTimestamp: startTimestamp, + duration: duration, + campaignData: campaignData + }) + ); + + CampaignParameters memory campaign = creator.campaign(campaignId); + assertEq(campaign.creator, alice); + assertEq(campaign.rewardToken, address(rewardToken)); + assertEq(campaign.amount, (amount * (1e9 - creator.defaultFees())) / 1e9); + assertEq(campaign.campaignType, 1); + assertEq(campaign.startTimestamp, startTimestamp); + assertEq(campaign.duration, duration); + vm.warp(block.timestamp + 1000); + vm.roll(4); + // override + + vm.prank(alice); + creator.overrideCampaign( + campaignId, + CampaignParameters({ + campaignId: campaignId, + creator: alice, + rewardToken: address(rewardToken), + amount: amountAfterFees, + campaignType: 1, + startTimestamp: startTimestamp, + duration: durationAfterOverride, + campaignData: campaignData + }) + ); + + ( + , + address campaignCreator, + address campaignRewardToken, + uint256 campaignAmount, + uint256 campaignType, + uint32 campaignStartTimestamp, + uint32 campaignDuration, + bytes memory campaignCampaignData + ) = creator.campaignOverrides(campaignId); + assertEq(campaignCreator, alice); + assertEq(campaignRewardToken, address(rewardToken)); + assertEq(campaignAmount, amountAfterFees); + assertEq(campaignType, 1); + assertEq(campaignStartTimestamp, startTimestamp); + assertEq(campaignDuration, durationAfterOverride); + assertEq(campaignCampaignData, campaignData); + assertGe(creator.campaignOverridesTimestamp(campaignId, 0), startTimestamp); vm.expectRevert(); - creator.getPoolDistributionsAfterEpoch(address(pool), startTime); + creator.campaignOverridesTimestamp(campaignId, 1); } - */ - /*////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - WITH DIFFERENT POOLS - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////*/ + function testUnit_GetCampaignOverridesTimestamp() public { + IERC20 rewardToken = IERC20(address(angle)); + uint256 amount = 100 ether; + uint256 amountAfterFees = 90 ether; + uint32 startTimestamp = uint32(block.timestamp + 600); + uint32 duration = 3600 * 24; + uint32 durationAfterOverride = 3600 * 12; + + bytes memory campaignData = abi.encode( + 0xbEEfa1aBfEbE621DF50ceaEF9f54FdB73648c92C, + new address[](0), + new address[](0), + "", + new bytes[](0), + new bytes[](0), + hex"" + ); + vm.prank(alice); + bytes32 campaignId = creator.createCampaign( + CampaignParameters({ + campaignId: bytes32(0), + creator: alice, + rewardToken: address(rewardToken), + amount: amount, + campaignType: 1, + startTimestamp: startTimestamp, + duration: duration, + campaignData: campaignData + }) + ); + + vm.warp(block.timestamp + 1000); + vm.roll(4); + // override + + vm.prank(alice); + creator.overrideCampaign( + campaignId, + CampaignParameters({ + campaignId: campaignId, + creator: alice, + rewardToken: address(rewardToken), + amount: amountAfterFees, + campaignType: 1, + startTimestamp: startTimestamp, + duration: durationAfterOverride, + campaignData: campaignData + }) + ); + + vm.warp(block.timestamp + 1000); + vm.roll(4); + // override + + vm.prank(alice); + creator.overrideCampaign( + campaignId, + CampaignParameters({ + campaignId: campaignId, + creator: alice, + rewardToken: address(rewardToken), + amount: amountAfterFees, + campaignType: 10, + startTimestamp: startTimestamp, + duration: durationAfterOverride * 10, + campaignData: campaignData + }) + ); + + uint256[] memory timestamps = creator.getCampaignOverridesTimestamp(campaignId); + assertEq(timestamps.length, 2); + assertEq(timestamps[0], 1001); + assertEq(timestamps[1], 2001); + } + + function testUnit_OverrideCampaignAdditionalFee() public { + vm.prank(governor); + creator.setCampaignFees(3, 1e7); + + IERC20 rewardToken = IERC20(address(angle)); + uint256 amount = 100 ether; + uint256 amountAfterFees = 90 ether; + uint32 startTimestamp = uint32(block.timestamp + 600); + uint32 duration = 3600 * 24; + uint32 durationAfterOverride = 3600 * 12; + + uint256 prevBalance = rewardToken.balanceOf(alice); + + bytes memory campaignData = abi.encode( + 0xbEEfa1aBfEbE621DF50ceaEF9f54FdB73648c92C, + new address[](0), + new address[](0), + "", + new bytes[](0), + new bytes[](0), + hex"" + ); + vm.prank(alice); + bytes32 campaignId = creator.createCampaign( + CampaignParameters({ + campaignId: bytes32(0), + creator: alice, + rewardToken: address(rewardToken), + amount: amount, + campaignType: 1, + startTimestamp: startTimestamp, + duration: duration, + campaignData: campaignData + }) + ); + + assertEq(rewardToken.balanceOf(alice), prevBalance - amount); + + vm.warp(block.timestamp + 1000); + vm.roll(4); + // override + vm.prank(alice); + creator.overrideCampaign( + campaignId, + CampaignParameters({ + campaignId: campaignId, + creator: alice, + rewardToken: address(rewardToken), + amount: amountAfterFees, + campaignType: 3, + startTimestamp: startTimestamp, + duration: durationAfterOverride, + campaignData: campaignData + }) + ); + + assertEq(rewardToken.balanceOf(alice), prevBalance - amount - (amountAfterFees * 1e7) / 1e9); + } } diff --git a/test/foundry/unit/DistributionCreator.t.sol b/test/foundry/unit/DistributionCreator.t.sol index 92c898b..33ba762 100644 --- a/test/foundry/unit/DistributionCreator.t.sol +++ b/test/foundry/unit/DistributionCreator.t.sol @@ -947,36 +947,36 @@ contract Test_DistributionCreator_distribution is DistributionCreatorForkTest { } } -contract Test_DistributionCreator_getDistributionsBetweenEpochs is DistributionCreatorForkTest { - function test_Success() public { - (DistributionParameters[] memory distributions, ) = creator.getDistributionsBetweenEpochs( - 1681380000, - 1681380000 + 3600, - 0, - type(uint32).max - ); - - assertEq(distributions.length, 1); - assertEq(distributions[0].uniV3Pool, address(0x149e36E72726e0BceA5c59d40df2c43F60f5A22D)); - assertEq(distributions[0].rewardToken, address(0xE0688A2FE90d0f93F17f273235031062a210d691)); - assertEq(distributions[0].amount, 9700000000000000000000); - assertEq(distributions[0].positionWrappers.length, 0); - assertEq(distributions[0].wrapperTypes.length, 0); - assertEq(distributions[0].propToken0, 2000); - assertEq(distributions[0].propToken1, 5000); - assertEq(distributions[0].propFees, 3000); - assertEq(distributions[0].isOutOfRangeIncentivized, 0); - assertEq(distributions[0].epochStart, 1681380000); - assertEq(distributions[0].numEpoch, 24); - assertEq(distributions[0].boostedReward, 0); - assertEq(distributions[0].boostingAddress, address(0)); - assertEq( - distributions[0].rewardId, - bytes32(0x7570c9deb1660ed82ff01f760b2883edb9bdb881933b0e4085854d0d717ea268) - ); - assertEq( - distributions[0].additionalData, - hex"290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563" - ); - } -} +// contract Test_DistributionCreator_getDistributionsBetweenEpochs is DistributionCreatorForkTest { +// function test_Success() public { +// (DistributionParameters[] memory distributions, ) = creator.getDistributionsBetweenEpochs( +// 1681380000, +// 1681380000 + 3600, +// 0, +// type(uint32).max +// ); + +// assertEq(distributions.length, 1); +// assertEq(distributions[0].uniV3Pool, address(0x149e36E72726e0BceA5c59d40df2c43F60f5A22D)); +// assertEq(distributions[0].rewardToken, address(0xE0688A2FE90d0f93F17f273235031062a210d691)); +// assertEq(distributions[0].amount, 9700000000000000000000); +// assertEq(distributions[0].positionWrappers.length, 0); +// assertEq(distributions[0].wrapperTypes.length, 0); +// assertEq(distributions[0].propToken0, 2000); +// assertEq(distributions[0].propToken1, 5000); +// assertEq(distributions[0].propFees, 3000); +// assertEq(distributions[0].isOutOfRangeIncentivized, 0); +// assertEq(distributions[0].epochStart, 1681380000); +// assertEq(distributions[0].numEpoch, 24); +// assertEq(distributions[0].boostedReward, 0); +// assertEq(distributions[0].boostingAddress, address(0)); +// assertEq( +// distributions[0].rewardId, +// bytes32(0x7570c9deb1660ed82ff01f760b2883edb9bdb881933b0e4085854d0d717ea268) +// ); +// assertEq( +// distributions[0].additionalData, +// hex"290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563" +// ); +// } +// } diff --git a/test/foundry/unit/Distributor.t.sol b/test/foundry/unit/Distributor.t.sol index 1e4ca14..b4b58dd 100644 --- a/test/foundry/unit/Distributor.t.sol +++ b/test/foundry/unit/Distributor.t.sol @@ -88,22 +88,22 @@ contract Test_Distributor_toggleOperator is DistributorCreatorTest { } } -contract Test_Distributor_toggleOnlyOperatorCanClaim is DistributorCreatorTest { - function test_RevertWhen_NotTrusted() public { - vm.expectRevert(NotTrusted.selector); - distributor.toggleOnlyOperatorCanClaim(bob); - } - - function test_Success() public { - vm.prank(governor); - distributor.toggleOnlyOperatorCanClaim(bob); - assertEq(distributor.onlyOperatorCanClaim(bob), 1); - - vm.prank(bob); - distributor.toggleOnlyOperatorCanClaim(bob); - assertEq(distributor.onlyOperatorCanClaim(bob), 0); - } -} +// contract Test_Distributor_toggleOnlyOperatorCanClaim is DistributorCreatorTest { +// function test_RevertWhen_NotTrusted() public { +// vm.expectRevert(NotTrusted.selector); +// distributor.toggleOnlyOperatorCanClaim(bob); +// } + +// function test_Success() public { +// vm.prank(governor); +// distributor.toggleOnlyOperatorCanClaim(bob); +// assertEq(distributor.onlyOperatorCanClaim(bob), 1); + +// vm.prank(bob); +// distributor.toggleOnlyOperatorCanClaim(bob); +// assertEq(distributor.onlyOperatorCanClaim(bob), 0); +// } +// } contract Test_Distributor_recoverERC20 is DistributorCreatorTest { function test_RevertWhen_NotGovernor() public { @@ -368,28 +368,28 @@ contract Test_Distributor_resolveDispute is DistributorCreatorTest { } contract Test_Distributor_claim is DistributorCreatorTest { - function test_RevertWhen_NotWhitelisted() public { - vm.prank(governor); - distributor.updateTree(MerkleTree({ merkleRoot: getRoot(), ipfsHash: keccak256("IPFS_HASH") })); - - vm.warp(distributor.endOfDisputePeriod() + 1); - - vm.prank(bob); - distributor.toggleOnlyOperatorCanClaim(bob); - - bytes32[][] memory proofs = new bytes32[][](1); - address[] memory users = new address[](1); - address[] memory tokens = new address[](1); - uint256[] memory amounts = new uint256[](1); - proofs[0] = new bytes32[](1); - users[0] = bob; - tokens[0] = address(angle); - amounts[0] = 1e18; - - vm.expectRevert(NotWhitelisted.selector); - vm.prank(alice); - distributor.claim(users, tokens, amounts, proofs); - } + // function test_RevertWhen_NotWhitelisted() public { + // vm.prank(governor); + // distributor.updateTree(MerkleTree({ merkleRoot: getRoot(), ipfsHash: keccak256("IPFS_HASH") })); + + // vm.warp(distributor.endOfDisputePeriod() + 1); + + // vm.prank(bob); + // distributor.toggleOnlyOperatorCanClaim(bob); + + // bytes32[][] memory proofs = new bytes32[][](1); + // address[] memory users = new address[](1); + // address[] memory tokens = new address[](1); + // uint256[] memory amounts = new uint256[](1); + // proofs[0] = new bytes32[](1); + // users[0] = bob; + // tokens[0] = address(angle); + // amounts[0] = 1e18; + + // vm.expectRevert(NotWhitelisted.selector); + // vm.prank(alice); + // distributor.claim(users, tokens, amounts, proofs); + // } function test_RevertWhen_InvalidLengths() public { vm.prank(governor); diff --git a/utils/forwardUtils.js b/utils/forwardUtils.js new file mode 100755 index 0000000..d5679d2 --- /dev/null +++ b/utils/forwardUtils.js @@ -0,0 +1,21 @@ +const { exec } = require('child_process'); + +if (process.argv.length < 3) { + console.error('Please provide a chain input as an argument.'); + process.exit(1); +} + +const command = process.argv[2]; +const extraArgs = process.argv.slice(3).join(' '); + +exec(`bun run lib/utils/utils/${command}.js ${extraArgs}`, (error, stdout, stderr) => { + if (error) { + console.log(error); + process.exit(1); + } + if (stderr) { + console.log(stderr); + process.exit(1); + } + console.log(stdout); +}); diff --git a/yarn.lock b/yarn.lock index b268705..4803640 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7,21 +7,24 @@ resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== -"@angleprotocol/sdk@2.7.0": - version "2.7.0" - resolved "https://npm.pkg.github.com/download/@angleprotocol/sdk/2.7.0/1c2a1d32c4ca3792c2325d86d4830aed03f18442#1c2a1d32c4ca3792c2325d86d4830aed03f18442" - integrity sha512-CjLSxubhuRw68DP4u6Fpa/GnluaxB86FmAIC2W+ysF5o38nMP/TcHbWDK/WyM85QorZWT5AdzyS/fggyZrAUOA== +"@angleprotocol/sdk@^v2.28.19": + version "2.33.34" + resolved "https://npm.pkg.github.com/download/@angleprotocol/sdk/2.33.34/0268b1d3f9ac2b37d3a7eb267aa077f24fa36426#0268b1d3f9ac2b37d3a7eb267aa077f24fa36426" + integrity sha512-GPVN8azvCgqT8F/rGp5d0m4uJ8ZFO7wKwfOFgLEK2bM5V0UziQBWX/2sbLKQNUzEcNVklRqnK/nNufFDrPqxwg== dependencies: "@apollo/client" "^3.7.17" "@typechain/ethers-v5" "^10.0.0" - "@types/lodash" "^4.14.180" + bun-types "^1.1.27" + class-transformer "^0.5.1" + class-validator "^0.14.1" + cross-fetch "^4.0.0" ethers "^5.6.4" graphql "^15.7.1" graphql-request "^3.6.1" jsbi "^4.3.0" keccak256 "^1.0.6" - lodash "^4.17.21" merkletreejs "^0.3.10" + reflect-metadata "^0.2.2" tiny-invariant "^1.1.0" typechain "^8.3.2" @@ -2165,11 +2168,6 @@ dependencies: keyv "*" -"@types/lodash@^4.14.180": - version "4.14.181" - resolved "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.181.tgz" - integrity sha512-n3tyKthHJbkiWhDZs3DkhkCzt2MexYHXlX0td5iMplyfwketaOeKboEVBqzceH7juqvEg3q5oUoBFxSLu7zFag== - "@types/lru-cache@^5.1.0": version "5.1.1" resolved "https://registry.npmjs.org/@types/lru-cache/-/lru-cache-5.1.1.tgz" @@ -2205,6 +2203,13 @@ resolved "https://registry.npmjs.org/@types/node/-/node-8.10.66.tgz" integrity sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw== +"@types/node@~20.12.8": + version "20.12.14" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.12.14.tgz#0c5cf7ef26aedfd64b0539bba9380ed1f57dcc77" + integrity sha512-scnD59RpYD91xngrQQLGkE+6UrHUPzeKZWhhjBSa3HSkwjbQc38+q3RoIVEwxQGRw3M+j5hpNAM+lgV3cVormg== + dependencies: + undici-types "~5.26.4" + "@types/pbkdf2@^3.0.0": version "3.1.0" resolved "https://registry.npmjs.org/@types/pbkdf2/-/pbkdf2-3.1.0.tgz" @@ -2249,6 +2254,18 @@ resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.4.tgz#0a41252ad431c473158b22f9bfb9a63df7541cff" integrity sha512-MMzuxN3GdFwskAnb6fz0orFvhfqi752yjaXylr0Rp4oDg5H0Zn1IuyRhDVvYOwAXoJirx2xuS16I3WjxnAIHiQ== +"@types/validator@^13.11.8": + version "13.12.2" + resolved "https://registry.yarnpkg.com/@types/validator/-/validator-13.12.2.tgz#760329e756e18a4aab82fc502b51ebdfebbe49f5" + integrity sha512-6SlHBzUW8Jhf3liqrGGXyTJSIFe4nqlJ5A5KaMZ2l/vbM3Wh3KSybots/wfWVzNLK4D1NZluDlSQIbIEPx6oyA== + +"@types/ws@~8.5.10": + version "8.5.13" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.13.tgz#6414c280875e2691d0d1e080b05addbf5cb91e20" + integrity sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA== + dependencies: + "@types/node" "*" + "@types/yargs-parser@*": version "21.0.0" resolved "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz" @@ -3144,6 +3161,14 @@ builtins@^5.0.1: dependencies: semver "^7.0.0" +bun-types@^1.1.27: + version "1.1.34" + resolved "https://registry.yarnpkg.com/bun-types/-/bun-types-1.1.34.tgz#cf0e1dc5aa8875573a3acb09bead0f23bab5aca2" + integrity sha512-br5QygTEL/TwB4uQOb96Ky22j4Gq2WxWH/8Oqv20fk5HagwKXo/akB+LiYgSfzexCt6kkcUaVm+bKiPl71xPvw== + dependencies: + "@types/node" "~20.12.8" + "@types/ws" "~8.5.10" + busboy@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893" @@ -3460,6 +3485,20 @@ class-is@^1.1.0: resolved "https://registry.npmjs.org/class-is/-/class-is-1.1.0.tgz" integrity sha512-rhjH9AG1fvabIDoGRVH587413LPjTZgmDF9fOFCbFJQV4yuocX1mHxxvXI4g3cGwbVY9wAYIoKlg1N79frJKQw== +class-transformer@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/class-transformer/-/class-transformer-0.5.1.tgz#24147d5dffd2a6cea930a3250a677addf96ab336" + integrity sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw== + +class-validator@^0.14.1: + version "0.14.1" + resolved "https://registry.yarnpkg.com/class-validator/-/class-validator-0.14.1.tgz#ff2411ed8134e9d76acfeb14872884448be98110" + integrity sha512-2VEG9JICxIqTpoK1eMzZqaV+u/EiwEJkMGzTrZf6sU/fwsnOITVgYJ8yojSy6CaXtO9V0Cc6ZQZ8h8m4UBuLwQ== + dependencies: + "@types/validator" "^13.11.8" + libphonenumber-js "^1.10.53" + validator "^13.9.0" + classic-level@^1.2.0: version "1.2.0" resolved "https://registry.npmjs.org/classic-level/-/classic-level-1.2.0.tgz" @@ -3790,6 +3829,13 @@ cross-fetch@^3.0.6, cross-fetch@^3.1.4: dependencies: node-fetch "2.6.7" +cross-fetch@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-4.0.0.tgz#f037aef1580bb3a1a35164ea2a848ba81b445983" + integrity sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g== + dependencies: + node-fetch "^2.6.12" + cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz" @@ -6990,6 +7036,11 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" +libphonenumber-js@^1.10.53: + version "1.11.14" + resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.11.14.tgz#d753524fd30e6433834a1464baf7efed4a06b593" + integrity sha512-sexvAfwcW1Lqws4zFp8heAtAEXbEDnvkYCEGzvOoMgZR7JhXo/IkE9MkkGACgBed5fWqh3ShBGnJBdDnU9N8EQ== + lines-and-columns@^1.1.6: version "1.2.4" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" @@ -7702,6 +7753,13 @@ node-fetch@2.6.7, node-fetch@^2.6.0: dependencies: whatwg-url "^5.0.0" +node-fetch@^2.6.12: + version "2.7.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== + dependencies: + whatwg-url "^5.0.0" + node-gyp-build@^4.2.0, node-gyp-build@^4.3.0: version "4.3.0" resolved "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.3.0.tgz" @@ -8574,6 +8632,11 @@ reduce-flatten@^2.0.0: resolved "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-2.0.0.tgz" integrity sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w== +reflect-metadata@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.2.2.tgz#400c845b6cba87a21f2c65c4aeb158f4fa4d9c5b" + integrity sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q== + regenerator-runtime@^0.13.4: version "0.13.9" resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz" @@ -10140,6 +10203,11 @@ underscore@^1.8.3: resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.13.6.tgz#04786a1f589dc6c09f761fc5f45b89e935136441" integrity sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A== +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + undici@^5.14.0: version "5.18.0" resolved "https://registry.yarnpkg.com/undici/-/undici-5.18.0.tgz#e88a77a74d991a30701e9a6751e4193a26fabda9" @@ -10267,6 +10335,11 @@ validate-npm-package-license@^3.0.1: spdx-correct "^3.0.0" spdx-expression-parse "^3.0.0" +validator@^13.9.0: + version "13.12.0" + resolved "https://registry.yarnpkg.com/validator/-/validator-13.12.0.tgz#7d78e76ba85504da3fee4fd1922b385914d4b35f" + integrity sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg== + varint@^5.0.0: version "5.0.2" resolved "https://registry.npmjs.org/varint/-/varint-5.0.2.tgz"