-
Notifications
You must be signed in to change notification settings - Fork 75
feat: add new contract to support merkl reward claiming #112
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
34ae3cb
a2bdcc3
99a4131
75990a7
ffc8063
adabf30
7d5c2f0
558c9ef
510fc9c
4232f8c
4db183a
7fedcd1
704b181
5516540
67395a8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| // SPDX-License-Identifier: MIT | ||
|
|
||
| pragma solidity ^0.8.10; | ||
|
|
||
| import "forge-std/Script.sol"; | ||
| import {TransparentUpgradeableProxy} from "@openzeppelin/proxy/transparent/TransparentUpgradeableProxy.sol"; | ||
| import {IERC20Upgradeable} from "@openzeppelin-upgradeable/interfaces/IERC20Upgradeable.sol"; | ||
| import {IPoolAddressesProvider} from "@aave-v3-core/interfaces/IPoolAddressesProvider.sol"; | ||
|
|
||
| import { ATokenVault } from "src/ATokenVault.sol"; | ||
| import { ATokenVaultMerklRewardClaimer } from "src/ATokenVaultMerklRewardClaimer.sol"; | ||
|
|
||
| contract DeployATokenVaultMerklRewardClaimer is Script { | ||
| // DEPLOYMENT PARAMETERS - CHANGE THESE FOR YOUR VAULT | ||
| // =================================================== | ||
| address constant DEPLOYER_ADDRESS = address(0); // Address of the deployer | ||
| address constant UNDERLYING_ASSET_ADDRESS = address(0); // Underlying asset listed in the Aave Protocol | ||
| uint16 constant REFERRAL_CODE = 0; // Referral code to use | ||
| address constant AAVE_POOL_ADDRESSES_PROVIDER_ADDRESS = address(0); // PoolAddressesProvider contract of the Aave Pool | ||
| address constant PROXY_ADMIN_ADDRESS = address(0); // Address of the proxy admin | ||
| address constant OWNER_ADDRESS = address(0); // Address of the vault owner | ||
| string constant SHARE_NAME = ""; // Name of the token shares | ||
| string constant SHARE_SYMBOL = ""; // Symbol of the token shares | ||
| uint256 constant FEE = 0; // Vault Fee bps in wad (e.g. 0.1e18 results in 10%) | ||
| uint256 constant INITIAL_LOCK_DEPOSIT = 0; // Initial deposit on behalf of the vault | ||
| address constant MERKL_DISTRIBUTOR_ADDRESS = address(0); // Address of the Merkl distributor contract | ||
| // =================================================== | ||
|
|
||
|
|
||
| function run() external { | ||
| require(DEPLOYER_ADDRESS != address(0), "DEPLOYER_ADDRESS is not set"); | ||
| require(UNDERLYING_ASSET_ADDRESS != address(0), "UNDERLYING_ASSET_ADDRESS is not set"); | ||
| require(AAVE_POOL_ADDRESSES_PROVIDER_ADDRESS != address(0), "AAVE_POOL_ADDRESSES_PROVIDER_ADDRESS is not set"); | ||
| require(PROXY_ADMIN_ADDRESS != address(0), "PROXY_ADMIN_ADDRESS is not set"); | ||
| require(OWNER_ADDRESS != address(0), "OWNER_ADDRESS is not set"); | ||
| require(bytes(SHARE_NAME).length > 0, "SHARE_NAME is not set"); | ||
| require(bytes(SHARE_SYMBOL).length > 0, "SHARE_SYMBOL is not set"); | ||
| require(FEE != 0, "FEE is not set. Comment this requirement out if you want a zero fee vault."); | ||
| require(INITIAL_LOCK_DEPOSIT != 0, "INITIAL_LOCK_DEPOSIT is not set"); | ||
| require(MERKL_DISTRIBUTOR_ADDRESS != address(0), "MERKL_DISTRIBUTOR_ADDRESS is not set. Comment this requirement out if you want to set it later."); | ||
|
|
||
| vm.startBroadcast(DEPLOYER_ADDRESS); | ||
|
|
||
| // Deploy the implementation, which disables initializers on construction | ||
| ATokenVaultMerklRewardClaimer vault = new ATokenVaultMerklRewardClaimer( | ||
| UNDERLYING_ASSET_ADDRESS, | ||
| REFERRAL_CODE, | ||
| IPoolAddressesProvider(AAVE_POOL_ADDRESSES_PROVIDER_ADDRESS) | ||
| ); | ||
| console.log("Vault impl deployed at: ", address(vault)); | ||
|
|
||
| console.log("Deploying proxy..."); | ||
| // Encode the initializer call | ||
| bytes memory initData = abi.encodeWithSelector( | ||
| ATokenVault.initialize.selector, | ||
| OWNER_ADDRESS, | ||
| FEE, | ||
| SHARE_NAME, | ||
| SHARE_SYMBOL, | ||
| INITIAL_LOCK_DEPOSIT | ||
| ); | ||
| console.logBytes(initData); | ||
|
|
||
| address proxyAddress = computeCreateAddress(DEPLOYER_ADDRESS, vm.getNonce(DEPLOYER_ADDRESS) + 1); | ||
| IERC20Upgradeable(UNDERLYING_ASSET_ADDRESS).approve(proxyAddress, INITIAL_LOCK_DEPOSIT); | ||
| console.log("Precomputed proxy address: ", proxyAddress); | ||
|
|
||
| // Deploy and initialize the proxy | ||
| TransparentUpgradeableProxy proxy = new TransparentUpgradeableProxy(address(vault), PROXY_ADMIN_ADDRESS, initData); | ||
| console.log("Vault proxy deployed and initialized at: ", address(proxy)); | ||
|
|
||
| vault = ATokenVaultMerklRewardClaimer(address(proxy)); | ||
| if (MERKL_DISTRIBUTOR_ADDRESS != address(0)) { | ||
| vault.setMerklDistributor(MERKL_DISTRIBUTOR_ADDRESS); | ||
| } | ||
| vm.stopBroadcast(); | ||
|
|
||
| console.log("\nVault data:"); | ||
| console.log("Pool Addresses Provider:", address(vault.POOL_ADDRESSES_PROVIDER())); | ||
| console.log("Referral Code:", vault.REFERRAL_CODE()); | ||
| console.log("Underlying:", address(vault.UNDERLYING())); | ||
| console.log("aToken:", address(vault.ATOKEN())); | ||
| console.log("Name:", vault.name()); | ||
| console.log("Symbol:", vault.symbol()); | ||
| console.log("Owner:", vault.owner()); | ||
| console.log("Fee:", vault.getFee()); | ||
| console.log("Merkl Distributor:", vault.getMerklDistributor()); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| // SPDX-License-Identifier: UNLICENSED | ||
| // All Rights Reserved © AaveCo | ||
|
|
||
| pragma solidity ^0.8.10; | ||
|
|
||
| import {IPoolAddressesProvider} from "@aave-v3-core/interfaces/IPoolAddressesProvider.sol"; | ||
| import {IERC20Upgradeable} from "@openzeppelin-upgradeable/interfaces/IERC20Upgradeable.sol"; | ||
| import {SafeERC20Upgradeable} from "@openzeppelin-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol"; | ||
| import {IMerklDistributor} from "./dependencies/merkl/DistributorInterface.sol"; | ||
| import {ATokenVault} from "./ATokenVault.sol"; | ||
| import {IATokenVaultMerklRewardClaimer} from "./interfaces/IATokenVaultMerklRewardClaimer.sol"; | ||
|
|
||
| /** | ||
| * @title ATokenVaultMerklRewardClaimer | ||
| * @author Aave Protocol | ||
| * @notice ATokenVault, with Merkl reward claiming capability | ||
| */ | ||
| contract ATokenVaultMerklRewardClaimer is ATokenVault, IATokenVaultMerklRewardClaimer { | ||
| using SafeERC20Upgradeable for IERC20Upgradeable; | ||
|
|
||
| /** | ||
| * @dev Constructor. | ||
| * @param underlying The underlying ERC20 asset which can be supplied to Aave | ||
| * @param referralCode The Aave referral code to use for deposits from this vault | ||
| * @param poolAddressesProvider The address of the Aave v3 Pool Addresses Provider | ||
| */ | ||
| constructor(address underlying, uint16 referralCode, IPoolAddressesProvider poolAddressesProvider) | ||
| ATokenVault(underlying, referralCode, poolAddressesProvider) | ||
| {} | ||
|
|
||
| /// @inheritdoc IATokenVaultMerklRewardClaimer | ||
| function claimMerklRewards( | ||
| address[] calldata tokens, | ||
| uint256[] calldata amounts, | ||
| bytes32[][] calldata proofs, | ||
| address[] calldata rewardTokensToForward, | ||
| address destination | ||
| ) | ||
| public | ||
| override | ||
| onlyOwner | ||
| { | ||
| require(_s.merklDistributor != address(0), "MERKL_DISTRIBUTOR_NOT_SET"); | ||
| require(tokens.length == amounts.length && tokens.length == proofs.length, "ARRAY_LENGTH_MISMATCH"); | ||
|
|
||
| uint256[] memory currentBalancesOfRewardTokens = new uint256[](rewardTokensToForward.length); | ||
| for (uint256 i = 0; i < rewardTokensToForward.length; i++) { | ||
| require(rewardTokensToForward[i] != address(ATOKEN), "CANNOT_FORWARD_ATOKEN"); | ||
| currentBalancesOfRewardTokens[i] = IERC20Upgradeable(rewardTokensToForward[i]).balanceOf(address(this)); | ||
| } | ||
|
|
||
| address[] memory users = new address[](tokens.length); | ||
| for (uint256 i = 0; i < tokens.length; i++) { | ||
| users[i] = address(this); | ||
| } | ||
| IMerklDistributor(_s.merklDistributor).claim(users, tokens, amounts, proofs); | ||
| emit MerklRewardsClaimed(_s.merklDistributor, tokens, amounts); | ||
|
|
||
| for (uint256 i = 0; i < rewardTokensToForward.length; i++) { | ||
| uint256 newBalance = IERC20Upgradeable(rewardTokensToForward[i]).balanceOf(address(this)); | ||
| uint256 amountToForward = newBalance - currentBalancesOfRewardTokens[i]; | ||
| if (amountToForward > 0) { | ||
| IERC20Upgradeable(rewardTokensToForward[i]).safeTransfer(destination, amountToForward); | ||
| emit MerklRewardsTokenForwarded(rewardTokensToForward[i], destination, amountToForward); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// @inheritdoc IATokenVaultMerklRewardClaimer | ||
| /// @dev Allow setting address(0) to reset the Merkl distributor | ||
| function setMerklDistributor(address merklDistributor) external override onlyOwner { | ||
| address currentMerklDistributor = _s.merklDistributor; | ||
| _s.merklDistributor = merklDistributor; | ||
| emit MerklDistributorUpdated(currentMerklDistributor, merklDistributor); | ||
| } | ||
|
|
||
| /// @inheritdoc IATokenVaultMerklRewardClaimer | ||
| function getMerklDistributor() external view override returns (address) { | ||
| return _s.merklDistributor; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| // SPDX-License-Identifier: BUSL-1.1 | ||
| // Based on implementation from https://github.com/AngleProtocol/merkl-contracts/blob/b7bd0e65a3f366e4041bc83494cbd981f8852b16/contracts/Distributor.sol#L202 | ||
| pragma solidity ^0.8.10; | ||
|
|
||
| interface IMerklDistributor { | ||
| function claim( | ||
| address[] calldata users, | ||
| address[] calldata tokens, | ||
| uint256[] calldata amounts, | ||
| bytes32[][] calldata proofs | ||
| ) external; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| // SPDX-License-Identifier: UNLICENSED | ||
| // All Rights Reserved © AaveCo | ||
|
|
||
| pragma solidity ^0.8.10; | ||
|
|
||
| /** | ||
| * @title IATokenVaultMerklRewardClaimer | ||
| * @author Aave Protocol | ||
| * @notice Defines the basic interface of the ATokenVaultMerklRewardClaimer | ||
aa-eyup marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| */ | ||
| interface IATokenVaultMerklRewardClaimer { | ||
| /** | ||
| * @dev Emitted when Merkl rewards are claimed by the vault contract | ||
| * @dev The token addresses do not always match the actual tokens received by the vault contract after rewards are claimed | ||
| * @dev The amounts do not always match the actual amounts received (the amounts may be the cumulative rewards earned by the user) | ||
| * @param distributor Address of the Merkl distributor contract | ||
| * @param tokens Addresses of the ERC-20 reward tokens claimed (the tokens passed as params to the Merkl distributor contract) | ||
| * @param amounts Amounts of the reward tokens claimed for each token (the amounts passed as params to the Merkl distributor contract) | ||
| */ | ||
| event MerklRewardsClaimed(address indexed distributor, address[] tokens, uint256[] amounts); | ||
|
|
||
| /** | ||
| * @dev Emitted when a reward token is forwarded to a destination | ||
| * @param token Address of the ERC-20 reward token | ||
| * @param destination Address of the destination receiving the reward token | ||
| * @param amount Amount of the reward token transferred to the destination | ||
| */ | ||
| event MerklRewardsTokenForwarded(address indexed token, address indexed destination, uint256 indexed amount); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We could do smt similar to
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have no strong preference, but I wanted to avoid emitting the event if the Imagine it would look like this: We can also pass in an array of destinations to |
||
|
|
||
| /** | ||
| * @dev Emitted when the Merkl distributor address is updated | ||
| * @param oldMerklDistributor The old address of the Merkl distributor contract | ||
| * @param newMerklDistributor The new address of the Merkl distributor contract | ||
| */ | ||
| event MerklDistributorUpdated(address indexed oldMerklDistributor, address indexed newMerklDistributor); | ||
|
|
||
| /** | ||
| * @notice Claims Merkl protocol rewards accrued from vault deposits. | ||
| * @dev Only callable by the owner | ||
| * @dev Merkl distributor address must be set | ||
| * @dev The IMerklDistributor.claim(...) function does not return a list of tokens and amounts the users actually receive | ||
| * @dev The order of the tokens, amounts, and proofs must align with eachother | ||
| * @param tokens Addresses of the ERC-20 reward tokens to claim (the tokens passed as params to the Merkl distributor contract) | ||
| * @param amounts Amounts of the reward tokens to claim for each token | ||
| * @param proofs Merkl proof passed to the Merkl distributor contract | ||
aa-eyup marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| * @param rewardTokensToForward Addresses of the ERC-20 tokens received by the vault contract from reward claiming (to be set if rewards should be forwarded to the `destination`) | ||
| * @param destination Address to send the received tokens to | ||
| */ | ||
| function claimMerklRewards( | ||
| address[] calldata tokens, | ||
| uint256[] calldata amounts, | ||
| bytes32[][] calldata proofs, | ||
| address[] calldata rewardTokensToForward, | ||
| address destination | ||
| ) external; | ||
|
|
||
| /** | ||
| * @notice Sets the Merkl distributor address for the vault uses to claim Merkl rewards. | ||
| * @dev Only callable by the owner | ||
| * @param merklDistributor Address of the new Merkl distributor contract | ||
| */ | ||
| function setMerklDistributor(address merklDistributor) external; | ||
|
|
||
| /** | ||
| * @notice Returns the address of the Merkl distributor contract. | ||
| * @return The address of the Merkl distributor contract | ||
| */ | ||
| function getMerklDistributor() external view returns (address); | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.