|
| 1 | +// SPDX-License-Identifier: MIT |
| 2 | + |
| 3 | +pragma solidity ^0.8.10; |
| 4 | + |
| 5 | +import "forge-std/Test.sol"; |
| 6 | +import {stdStorage, StdStorage} from "forge-std/Test.sol"; |
| 7 | + |
| 8 | +import {IAToken} from "@aave-v3-core/interfaces/IAToken.sol"; |
| 9 | +import {IERC20} from "@openzeppelin/interfaces/IERC20.sol"; |
| 10 | + |
| 11 | +import {ATokenVaultBaseTest} from "./ATokenVaultBaseTest.t.sol"; |
| 12 | + |
| 13 | +import {IATokenVaultMerklRewardClaimer} from "../src/interfaces/IATokenVaultMerklRewardClaimer.sol"; |
| 14 | + |
| 15 | +/** |
| 16 | + * @title ATokenVaultMerklRewardClaimerTest |
| 17 | + * @notice Test suite for claiming Merkl rewards from the ATokenVault |
| 18 | + * @dev Forks Ethereum mainnet to etch the ATokenVault onto an address that has claimable rewards as of the forked block |
| 19 | + * @dev foundry.toml must use evm_version = 'cancun' to run this test |
| 20 | + */ |
| 21 | +contract ATokenVaultMerklRewardClaimerTest is ATokenVaultBaseTest { |
| 22 | + using stdStorage for StdStorage; |
| 23 | + uint256 ethereumFork; |
| 24 | + // The block before rewards claim in tx: https://etherscan.io/tx/0x42ef6b499d1b6e96a4250f2d5a005b60173386e2ac3a3e424aa407db3da802ea |
| 25 | + uint256 ETHEREUM_FORK_BLOCK = 23921479; // Dec 1st 2025 |
| 26 | + |
| 27 | + address constant MERKL_DISTRIBUTOR = 0x3Ef3D8bA38EBe18DB133cEc108f4D14CE00Dd9Ae; |
| 28 | + address constant ADDRESS_WITH_CLAIMABLE_REWARDS = 0x424629A0D581B6076322A952FB43b78624dB8A15; |
| 29 | + address constant ETHEREUM_POOL_ADDRESSES_PROVIDER = 0x2f39d218133AFaB8F2B819B1066c7E434Ad94E9e; |
| 30 | + address constant ETHEREUM_RLUSD = 0x8292Bb45bf1Ee4d140127049757C2E0fF06317eD; |
| 31 | + address constant A_HOR_RWA_RLUSD = 0xE3190143Eb552456F88464662f0c0C4aC67A77eB; |
| 32 | + address constant WRAPPED_A_HOR_RWA_RLUSD = 0x503D751B13a71D8e69Db021DF110bfa7aE1dA889; |
| 33 | + address constant A_ETH_RLUSD = 0xFa82580c16A31D0c1bC632A36F82e83EfEF3Eec0; |
| 34 | + address constant WRAPPED_A_ETH_RLUSD = 0x72eEED8043Dcce2Fe7CdAC950D928F80f472ab80; |
| 35 | + IAToken constant aHorRwaRLUSD = IAToken(A_HOR_RWA_RLUSD); |
| 36 | + |
| 37 | + function setUp() public override { |
| 38 | + ethereumFork = vm.createFork(vm.envString("ETHEREUM_RPC_URL")); |
| 39 | + vm.selectFork(ethereumFork); |
| 40 | + vm.rollFork(ETHEREUM_FORK_BLOCK); |
| 41 | + |
| 42 | + vaultAssetAddress = address(aHorRwaRLUSD); |
| 43 | + |
| 44 | + // Sets the `vault`, but we will not use the vault deployment |
| 45 | + _deployATokenVaultMerklRewardClaimer(ETHEREUM_RLUSD, ETHEREUM_POOL_ADDRESSES_PROVIDER); |
| 46 | + } |
| 47 | + |
| 48 | + /*////////////////////////////////////////////////////////////// |
| 49 | + ETHEREUM FORK TESTS |
| 50 | + //////////////////////////////////////////////////////////////*/ |
| 51 | + |
| 52 | + function testEthereumForkWorks() public { |
| 53 | + assertEq(vm.activeFork(), ethereumFork); |
| 54 | + } |
| 55 | + |
| 56 | + function testEthereumForkAtExpectedBlock() public { |
| 57 | + assertEq(block.number, ETHEREUM_FORK_BLOCK); |
| 58 | + } |
| 59 | + |
| 60 | + function testEthereumForkBalanceOfAddressWithClaimableRewards() public { |
| 61 | + assertEq(ADDRESS_WITH_CLAIMABLE_REWARDS.balance, 154288817306978598); |
| 62 | + } |
| 63 | + |
| 64 | + /*////////////////////////////////////////////////////////////// |
| 65 | + MERKL REWARDS CLAIM TESTS |
| 66 | + //////////////////////////////////////////////////////////////*/ |
| 67 | + |
| 68 | + function testOwnerCanClaimMerklRewards() public { |
| 69 | + _setMerklDistributor(); |
| 70 | + // Set the code for an address that has claimable rewards as of the fork block |
| 71 | + // We will use this in place of the vault deployment |
| 72 | + _etchVault(ADDRESS_WITH_CLAIMABLE_REWARDS); |
| 73 | + |
| 74 | + uint256 beforeBalanceOfAHorRwaRLUSD = IERC20(A_HOR_RWA_RLUSD).balanceOf(ADDRESS_WITH_CLAIMABLE_REWARDS); |
| 75 | + uint256 beforeBalanceOfWrappedAHorRwaRLUSD = IERC20(WRAPPED_A_HOR_RWA_RLUSD).balanceOf(ADDRESS_WITH_CLAIMABLE_REWARDS); |
| 76 | + uint256 beforeBalanceOfAETHRLUSD = IERC20(A_ETH_RLUSD).balanceOf(ADDRESS_WITH_CLAIMABLE_REWARDS); |
| 77 | + uint256 beforeBalanceOfWrappedAETHRLUSD = IERC20(WRAPPED_A_ETH_RLUSD).balanceOf(ADDRESS_WITH_CLAIMABLE_REWARDS); |
| 78 | + |
| 79 | + address[] memory tokens = new address[](2); |
| 80 | + tokens[0] = address(WRAPPED_A_HOR_RWA_RLUSD); |
| 81 | + tokens[1] = address(WRAPPED_A_ETH_RLUSD); |
| 82 | + |
| 83 | + uint256[] memory amounts = new uint256[](2); |
| 84 | + amounts[0] = 108475300663546315531064; |
| 85 | + amounts[1] = 39262212365620131551622; |
| 86 | + |
| 87 | + bytes32[][] memory proofs = new bytes32[][](2); |
| 88 | + proofs[0] = new bytes32[](18); |
| 89 | + proofs[1] = new bytes32[](18); |
| 90 | + proofs[0][0] = 0xcd60c655efb4b907fcd241863868d14b469487021d66d1edfdbda9472f9a64fa; |
| 91 | + proofs[0][1] = 0xb8fb6ed03a4fe66e40d0393936caaf89288f0f0d45a54b2d021e6436e08d5cab; |
| 92 | + proofs[0][2] = 0x51da9788480e72478a3983b1fcc6417d6e2fb23f17d82402980bf7a662951ebc; |
| 93 | + proofs[0][3] = 0xbd1e1f1f6b3bff2c96cdaac05eef648a12d7b26aff0cb6bf2e01df739326d221; |
| 94 | + proofs[0][4] = 0xc3666e909b4378eb3190a55407b3992791f45947ccfb78a450618fd0d2d186ef; |
| 95 | + proofs[0][5] = 0xeae511fe93c29449d7dc421a00989e9b90193b1ea95a6727edfd0ac23c85b1a8; |
| 96 | + proofs[0][6] = 0x7cb3e28ed93250acb734cf34cf07ae533800fc0161e936ee29c20f36c66e7d4a; |
| 97 | + proofs[0][7] = 0x467aa2d6889230db0b841374136b586ded4697016c9cb5b9259677357c9de435; |
| 98 | + proofs[0][8] = 0xb1b5b41688ba77f9426265f60c5f9a922062156ab08bdde674dceebacc1c92b3; |
| 99 | + proofs[0][9] = 0x51dda19784b96153bcda1086489d5d6c5f17566202c92a0d911cbd2aa7a3ff7a; |
| 100 | + proofs[0][10] = 0x4606c269974519cf0d4fe1907baab3ca27c5eef64560408e4137eaf62d273c08; |
| 101 | + proofs[0][11] = 0xcc7bdca69b12e1f75d7b26045ba5e0c108de8d85bcee444ac05a02b940a20ea7; |
| 102 | + proofs[0][12] = 0x67cfe6ef02168544a983543b06f1823d97e78d17e67bd6833f7fc6b8e3a2cc77; |
| 103 | + proofs[0][13] = 0x3f2a8d6cb9b7784fe51ac5719c41e1a973856a7f89f3847b2ec5e259b6977b90; |
| 104 | + proofs[0][14] = 0xed9131f643d7100fcdfcebbf4abb97e7bcf31ee08f7ab8fb3230231f6f3f7533; |
| 105 | + proofs[0][15] = 0x4dd1263f903416095b1556a5b9473082ee23a3eda267d6f98dbdcb200ba1d648; |
| 106 | + proofs[0][16] = 0x5df7d74a8a29e2552ddecdd29e57b8e1daedbc786f9f9ca6d308506f14b13995; |
| 107 | + proofs[0][17] = 0xa8568c4dc81b1ee1667791a9567914d57f698ab8f6444fd1bd9fa88b158598c6; |
| 108 | + |
| 109 | + proofs[1][0] = 0x2553b0621d281b8ff48e95ae951cca33ea3468a8938b2e4df5432143652d4e4a; |
| 110 | + proofs[1][1] = 0xd98dc700cb20b65209167e4794e111a3e6f5b321e87addd9f31fad5b9227aa77; |
| 111 | + proofs[1][2] = 0xef27cdadbbce6f35a63b010602c810e1f610924d97671658f721c39b14314b23; |
| 112 | + proofs[1][3] = 0x6e0a90db4e79aaa85f7d02e5b51e731ae6d3c5a55e7f7823e69617dc658cf403; |
| 113 | + proofs[1][4] = 0xeaac81bbf63022e8203c818f098166e2a07dc5606ec3c78a8025910420c4bf5f; |
| 114 | + proofs[1][5] = 0x757d3fd4bfe411a574405bf91b1cbd2f11987fb4237b8d5fa64176cf0cb20fe3; |
| 115 | + proofs[1][6] = 0xbd699a6f29ee97f0234bf9322953a1d284e2c911a0b86aa256f5265fd03df512; |
| 116 | + proofs[1][7] = 0xe37c2c040c7977b9d4b05857925086e6ba7e7227b836f241c3f5dddd852852e6; |
| 117 | + proofs[1][8] = 0xd981d4e3f9077020c96ba37e849ec7d27de10a84269255a563f4f5387e15a42e; |
| 118 | + proofs[1][9] = 0x7755254f566658f3d7cae2cca32b7428ba4faa8471bbec83987cf0748b374f42; |
| 119 | + proofs[1][10] = 0x58cd9e5a37c2770d44fdc248cf3e2d7b335156a1613721c3ef390e03637748ac; |
| 120 | + proofs[1][11] = 0x53fea05c23a6e006a865c38e05f42ebb46f7ab9e09337e0ee2a064d4b8e2b90a; |
| 121 | + proofs[1][12] = 0x4d98b5449e9af673e13565b42fe5129c5d774be4a5d87d46ef7773913161679a; |
| 122 | + proofs[1][13] = 0x6b120817b20d5b394061ee70d0c5b222282054a4ecff068943c8f29455a5a0ae; |
| 123 | + proofs[1][14] = 0x880e3fa286a8e8f7cda32454aba6f4e03a5a4e6e12765c2ed81d02f1f387cd5d; |
| 124 | + proofs[1][15] = 0x9bfa48207586367dd5d549803d11cc165a50760ef6da82c2200289fae3cd9188; |
| 125 | + proofs[1][16] = 0xd973d9d68f5e750326e9811fccb748c601432c59b2d2ac08d334218027b0f754; |
| 126 | + proofs[1][17] = 0x6fd7ba9a8ab999e39fa33e96b722ab10d2e07d65b53319639a7145f9f0cbfece; |
| 127 | + |
| 128 | + vm.prank(OWNER); |
| 129 | + IATokenVaultMerklRewardClaimer(ADDRESS_WITH_CLAIMABLE_REWARDS).claimMerklRewards(tokens, amounts, proofs); |
| 130 | + |
| 131 | + uint256 afterBalanceOfAHorRwaRLUSD = IERC20(A_HOR_RWA_RLUSD).balanceOf(ADDRESS_WITH_CLAIMABLE_REWARDS); |
| 132 | + uint256 afterBalanceOfWrappedAHorRwaRLUSD = IERC20(WRAPPED_A_HOR_RWA_RLUSD).balanceOf(ADDRESS_WITH_CLAIMABLE_REWARDS); |
| 133 | + uint256 afterBalanceOfAETHRLUSD = IERC20(A_ETH_RLUSD).balanceOf(ADDRESS_WITH_CLAIMABLE_REWARDS); |
| 134 | + uint256 afterBalanceOfWrappedAETHRLUSD = IERC20(WRAPPED_A_ETH_RLUSD).balanceOf(ADDRESS_WITH_CLAIMABLE_REWARDS); |
| 135 | + |
| 136 | + // Wrapped token balances should not change because the claimer receives the unwrapped tokens |
| 137 | + assertEq(afterBalanceOfWrappedAHorRwaRLUSD, beforeBalanceOfWrappedAHorRwaRLUSD); |
| 138 | + assertGt(afterBalanceOfAHorRwaRLUSD, beforeBalanceOfAHorRwaRLUSD); |
| 139 | + assertEq(afterBalanceOfWrappedAETHRLUSD, beforeBalanceOfWrappedAETHRLUSD); |
| 140 | + assertGt(afterBalanceOfAETHRLUSD, beforeBalanceOfAETHRLUSD); |
| 141 | + } |
| 142 | + |
| 143 | + // TODO: add moar tests for the Merkl distributor setter and getter |
| 144 | + // TODO: add error path tests |
| 145 | + |
| 146 | + function _setMerklDistributor() internal { |
| 147 | + vm.prank(OWNER); |
| 148 | + IATokenVaultMerklRewardClaimer(address(vault)).setMerklDistributor(MERKL_DISTRIBUTOR); |
| 149 | + } |
| 150 | + |
| 151 | + function _etchVault(address target) internal { |
| 152 | + // 1. Fix the Proxy: Copy the implementation address from 'vault' to the etched address |
| 153 | + // EIP-1967 Implementation slot: bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1) |
| 154 | + bytes32 implSlot = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; |
| 155 | + bytes32 implementation = vm.load(address(vault), implSlot); |
| 156 | + |
| 157 | + vm.etch(target, address(vault).code); |
| 158 | + vm.store(target, implSlot, implementation); |
| 159 | + |
| 160 | + // 2. Fix State: Copy Owner and MerklDistributor |
| 161 | + // Based on forge inspect storage layout: |
| 162 | + // _owner is at slot 151 |
| 163 | + uint256 ownerSlot = 151; |
| 164 | + vm.store(target, bytes32(ownerSlot), vm.load(address(vault), bytes32(ownerSlot))); |
| 165 | + // _s starts at slot 254 |
| 166 | + // _s.merklDistributor is at slot 256 (254: balances/fees, 255: gap/fee, 256: merkl) |
| 167 | + uint256 merklSlot = 256; |
| 168 | + vm.store(target, bytes32(merklSlot), vm.load(address(vault), bytes32(merklSlot))); |
| 169 | + } |
| 170 | +} |
0 commit comments