Skip to content

Commit 4232f8c

Browse files
committed
feat(ATokenVaultMerklRewardClaimer): allow setting destination to forward reward tokens to
1 parent 510fc9c commit 4232f8c

File tree

5 files changed

+220
-27
lines changed

5 files changed

+220
-27
lines changed

src/ATokenVaultMerklRewardClaimer.sol

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
pragma solidity ^0.8.10;
55

66
import {IPoolAddressesProvider} from "@aave-v3-core/interfaces/IPoolAddressesProvider.sol";
7-
import {IERC20} from "@openzeppelin/interfaces/IERC20.sol";
7+
8+
import {IERC20Upgradeable} from "@openzeppelin-upgradeable/interfaces/IERC20Upgradeable.sol";
9+
import {SafeERC20Upgradeable} from "@openzeppelin-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
810

911
import {ATokenVault} from "./ATokenVault.sol";
1012
import {IATokenVaultMerklRewardClaimer} from "./interfaces/IATokenVaultMerklRewardClaimer.sol";
@@ -16,6 +18,8 @@ import {IMerklDistributor} from "./dependencies/merkl/DistributorInterface.sol";
1618
* @notice ATokenVault, with Merkl reward claiming capability
1719
*/
1820
contract ATokenVaultMerklRewardClaimer is ATokenVault, IATokenVaultMerklRewardClaimer {
21+
using SafeERC20Upgradeable for IERC20Upgradeable;
22+
1923
/**
2024
* @dev Constructor.
2125
* @param underlying The underlying ERC20 asset which can be supplied to Aave
@@ -27,20 +31,40 @@ contract ATokenVaultMerklRewardClaimer is ATokenVault, IATokenVaultMerklRewardCl
2731
{}
2832

2933
/// @inheritdoc IATokenVaultMerklRewardClaimer
30-
function claimMerklRewards(address[] calldata rewardTokens, uint256[] calldata amounts, bytes32[][] calldata proofs)
34+
function claimMerklRewards(
35+
address[] calldata tokens,
36+
uint256[] calldata amounts,
37+
bytes32[][] calldata proofs,
38+
address[] calldata rewardTokensToForward,
39+
address destination
40+
)
3141
public
3242
override
3343
onlyOwner
3444
{
3545
require(_s.merklDistributor != address(0), "MERKL_DISTRIBUTOR_NOT_SET");
36-
require(rewardTokens.length == amounts.length && rewardTokens.length == proofs.length, "ARRAY_LENGTH_MISMATCH");
46+
require(tokens.length == amounts.length && tokens.length == proofs.length, "ARRAY_LENGTH_MISMATCH");
47+
48+
uint256[] memory currentBalancesOfRewardTokens = new uint256[](rewardTokensToForward.length);
49+
for (uint256 i = 0; i < rewardTokensToForward.length; i++) {
50+
currentBalancesOfRewardTokens[i] = IERC20Upgradeable(rewardTokensToForward[i]).balanceOf(address(this));
51+
}
3752

38-
address[] memory users = new address[](rewardTokens.length);
39-
for (uint256 i = 0; i < rewardTokens.length; i++) {
53+
address[] memory users = new address[](tokens.length);
54+
for (uint256 i = 0; i < tokens.length; i++) {
4055
users[i] = address(this);
4156
}
42-
IMerklDistributor(_s.merklDistributor).claim(users, rewardTokens, amounts, proofs);
43-
emit MerklRewardsClaimed(_s.merklDistributor, rewardTokens, amounts);
57+
IMerklDistributor(_s.merklDistributor).claim(users, tokens, amounts, proofs);
58+
emit MerklRewardsClaimed(_s.merklDistributor, tokens, amounts);
59+
60+
for (uint256 i = 0; i < rewardTokensToForward.length; i++) {
61+
uint256 newBalance = IERC20Upgradeable(rewardTokensToForward[i]).balanceOf(address(this));
62+
uint256 amountToForward = newBalance - currentBalancesOfRewardTokens[i];
63+
if (amountToForward > 0) {
64+
IERC20Upgradeable(rewardTokensToForward[i]).safeTransfer(destination, amountToForward);
65+
emit MerklRewardsTokenForwarded(rewardTokensToForward[i], destination, amountToForward);
66+
}
67+
}
4468
}
4569

4670
/// @inheritdoc IATokenVaultMerklRewardClaimer

src/interfaces/IATokenVaultMerklRewardClaimer.sol

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@ interface IATokenVaultMerklRewardClaimer {
2020
*/
2121
event MerklRewardsClaimed(address indexed distributor, address[] tokens, uint256[] amounts);
2222

23+
/**
24+
* @dev Emitted when a reward token is forwarded to a destination
25+
* @param token Address of the ERC-20 reward token
26+
* @param destination Address of the destination receiving the reward token
27+
* @param amount Amount of the reward token transferred to the destination
28+
*/
29+
event MerklRewardsTokenForwarded(address indexed token, address indexed destination, uint256 indexed amount);
30+
2331
/**
2432
* @dev Emitted when the Merkl distributor address is updated
2533
* @param oldMerklDistributor The old address of the Merkl distributor contract
@@ -33,12 +41,19 @@ interface IATokenVaultMerklRewardClaimer {
3341
* @dev Merkl distributor address must be set
3442
* @dev The IMerklDistributor.claim(...) function does not return a list of tokens and amounts the users actually receive
3543
* @dev The order of the tokens, amounts, and proofs must align with eachother
36-
* @param rewardTokens Addresses of the ERC-20 reward tokens to claim (the tokens passed as params to the Merkl distributor contract)
44+
* @param tokens Addresses of the ERC-20 reward tokens to claim (the tokens passed as params to the Merkl distributor contract)
3745
* @param amounts Amounts of the reward tokens to claim for each token
3846
* @param proofs Merkl proof passed to the Merkl distributor contract
47+
* @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`)
48+
* @param destination Address to send the received tokens to
3949
*/
40-
function claimMerklRewards(address[] calldata rewardTokens, uint256[] calldata amounts, bytes32[][] calldata proofs)
41-
external;
50+
function claimMerklRewards(
51+
address[] calldata tokens,
52+
uint256[] calldata amounts,
53+
bytes32[][] calldata proofs,
54+
address[] calldata rewardTokensToForward,
55+
address destination
56+
) external;
4257

4358
/**
4459
* @notice Sets the Merkl distributor address for the vault uses to claim Merkl rewards.

test/ATokenVaultMerklRewardClaimer.t.sol

Lines changed: 132 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ contract ATokenVaultMerklRewardClaimerTest is ATokenVaultBaseTest {
5454

5555
bytes32 proof = keccak256("proof1");
5656
(address[] memory rewardTokens, uint256[] memory amounts, bytes32[][] memory proofs) = _buildMerklRewardsClaimData(address(_dai), 1000, proof);
57+
address[] memory rewardTokensToForward = new address[](0);
58+
address destination = address(0);
5759

5860
address[] memory users = new address[](rewardTokens.length);
5961
for (uint256 i = 0; i < rewardTokens.length; i++) {
@@ -68,7 +70,112 @@ contract ATokenVaultMerklRewardClaimerTest is ATokenVaultBaseTest {
6870
vm.expectEmit(true, true, false, true, address(_vaultMerklRewardClaimer));
6971
emit IATokenVaultMerklRewardClaimer.MerklRewardsClaimed(address(_merklDistributor), rewardTokens, amounts);
7072
vm.prank(OWNER);
71-
_vaultMerklRewardClaimer.claimMerklRewards(rewardTokens, amounts, proofs);
73+
_vaultMerklRewardClaimer.claimMerklRewards(rewardTokens, amounts, proofs, rewardTokensToForward, destination);
74+
}
75+
76+
function testClaimMerklRewardsAndForwardPartialTokenToDestination() public {
77+
// Context: 2 tokens will be rewarded, but only one will be forwarded to the destination.
78+
_setMerklDistributor();
79+
80+
uint256 amountOfATokenRewarded = 789 * 1e18;
81+
uint256 amountOfDAIRewarded = 1234 * 1e18;
82+
address[] memory mockRecipients = new address[](2);
83+
mockRecipients[0] = address(_vaultMerklRewardClaimer);
84+
mockRecipients[1] = address(_vaultMerklRewardClaimer);
85+
address[] memory mockRewardTokens = new address[](2);
86+
mockRewardTokens[0] = address(_aDai);
87+
mockRewardTokens[1] = address(_dai);
88+
uint256[] memory mockAmounts = new uint256[](2);
89+
mockAmounts[0] = amountOfATokenRewarded;
90+
mockAmounts[1] = amountOfDAIRewarded;
91+
_merklDistributor.mockTokensToSend(mockRecipients, mockRewardTokens, mockAmounts);
92+
93+
_aDai.mint(address(this), address(_merklDistributor), amountOfATokenRewarded, 0);
94+
assertEq(_aDai.balanceOf(address(_merklDistributor)), amountOfATokenRewarded);
95+
_dai.mint(address(_merklDistributor), amountOfDAIRewarded);
96+
assertEq(_dai.balanceOf(address(_merklDistributor)), amountOfDAIRewarded);
97+
98+
bytes32 proof = keccak256("proof1");
99+
bytes32[][] memory proofs = new bytes32[][](2);
100+
proofs[0] = new bytes32[](1);
101+
proofs[0][0] = proof;
102+
proofs[1] = new bytes32[](1);
103+
proofs[1][0] = proof;
104+
// Forward the DAI only to the destination. Leave the aDAI in the vault.
105+
address[] memory rewardTokensToForward = new address[](1);
106+
rewardTokensToForward[0] = address(_dai);
107+
address destination = makeAddr("destination");
108+
109+
// Check that the vault does not have any aDAI.
110+
uint256 beforeBalanceOfAToken = _aDai.balanceOf(address(_vaultMerklRewardClaimer));
111+
uint256 beforeBalanceOfDAI = _dai.balanceOf(address(_vaultMerklRewardClaimer));
112+
113+
vm.expectEmit(true, true, false, true, address(_vaultMerklRewardClaimer));
114+
emit IATokenVaultMerklRewardClaimer.MerklRewardsClaimed(address(_merklDistributor), mockRewardTokens, mockAmounts);
115+
vm.expectEmit(true, true, false, true, address(_vaultMerklRewardClaimer));
116+
emit IATokenVaultMerklRewardClaimer.MerklRewardsTokenForwarded(address(_dai), destination, amountOfDAIRewarded);
117+
vm.prank(OWNER);
118+
_vaultMerklRewardClaimer.claimMerklRewards(mockRewardTokens, mockAmounts, proofs, rewardTokensToForward, destination);
119+
120+
// Check that the vault did not hold onto the DAI.
121+
assertEq(_dai.balanceOf(address(_vaultMerklRewardClaimer)), beforeBalanceOfDAI);
122+
// Check that the destination received the DAI.
123+
assertEq(_dai.balanceOf(destination), amountOfDAIRewarded);
124+
// Check that the vault did hold onto the aDAI. The aDAI balance is initialized with a virtual amount.
125+
assertEq(_aDai.balanceOf(address(_vaultMerklRewardClaimer)), beforeBalanceOfAToken + amountOfATokenRewarded);
126+
}
127+
128+
function testClaimMerklRewardsAndForwardFullTokenToDestination() public {
129+
// Context: 2 tokens will be rewarded, and both will be forwarded to the destination.
130+
_setMerklDistributor();
131+
132+
uint256 amountOfATokenRewarded = 789 * 1e18;
133+
uint256 amountOfDAIRewarded = 1234 * 1e18;
134+
address[] memory mockRecipients = new address[](2);
135+
mockRecipients[0] = address(_vaultMerklRewardClaimer);
136+
mockRecipients[1] = address(_vaultMerklRewardClaimer);
137+
address[] memory mockRewardTokens = new address[](2);
138+
mockRewardTokens[0] = address(_aDai);
139+
mockRewardTokens[1] = address(_dai);
140+
uint256[] memory mockAmounts = new uint256[](2);
141+
mockAmounts[0] = amountOfATokenRewarded;
142+
mockAmounts[1] = amountOfDAIRewarded;
143+
_merklDistributor.mockTokensToSend(mockRecipients, mockRewardTokens, mockAmounts);
144+
145+
_aDai.mint(address(this), address(_merklDistributor), amountOfATokenRewarded, 0);
146+
assertEq(_aDai.balanceOf(address(_merklDistributor)), amountOfATokenRewarded);
147+
_dai.mint(address(_merklDistributor), amountOfDAIRewarded);
148+
assertEq(_dai.balanceOf(address(_merklDistributor)), amountOfDAIRewarded);
149+
150+
bytes32 proof = keccak256("proof1");
151+
bytes32[][] memory proofs = new bytes32[][](2);
152+
proofs[0] = new bytes32[](1);
153+
proofs[0][0] = proof;
154+
proofs[1] = new bytes32[](1);
155+
proofs[1][0] = proof;
156+
// Forward the DAI only to the destination. Leave the aDAI in the vault.
157+
address[] memory rewardTokensToForward = new address[](2);
158+
rewardTokensToForward[0] = address(_aDai);
159+
rewardTokensToForward[1] = address(_dai);
160+
address destination = makeAddr("destination");
161+
162+
// Check that the vault does not have any aDAI.
163+
uint256 beforeBalanceOfAToken = _aDai.balanceOf(address(_vaultMerklRewardClaimer));
164+
uint256 beforeBalanceOfDAI = _dai.balanceOf(address(_vaultMerklRewardClaimer));
165+
166+
vm.expectEmit(true, true, false, true, address(_vaultMerklRewardClaimer));
167+
emit IATokenVaultMerklRewardClaimer.MerklRewardsClaimed(address(_merklDistributor), mockRewardTokens, mockAmounts);
168+
vm.expectEmit(true, true, false, true, address(_vaultMerklRewardClaimer));
169+
emit IATokenVaultMerklRewardClaimer.MerklRewardsTokenForwarded(address(_dai), destination, amountOfDAIRewarded);
170+
vm.prank(OWNER);
171+
_vaultMerklRewardClaimer.claimMerklRewards(mockRewardTokens, mockAmounts, proofs, rewardTokensToForward, destination);
172+
173+
// Check that the destination received the DAI and aDAI.
174+
assertEq(_dai.balanceOf(destination), amountOfDAIRewarded);
175+
assertEq(_aDai.balanceOf(destination), amountOfATokenRewarded);
176+
// Check that the vault did not hold onto the aDAI and DAI.
177+
assertEq(_aDai.balanceOf(address(_vaultMerklRewardClaimer)), beforeBalanceOfAToken);
178+
assertEq(_dai.balanceOf(address(_vaultMerklRewardClaimer)), beforeBalanceOfDAI);
72179
}
73180

74181
function testClaimMerklRewardsIfATokenIsRewarded() public {
@@ -163,18 +270,22 @@ contract ATokenVaultMerklRewardClaimerTest is ATokenVaultBaseTest {
163270

164271
bytes32 proof = keccak256("proof1");
165272
(address[] memory rewardTokens, uint256[] memory amounts, bytes32[][] memory proofs) = _buildMerklRewardsClaimData(address(_dai), 1000, proof);
273+
address[] memory rewardTokensToForward = new address[](0);
274+
address destination = address(0);
166275
vm.expectRevert();
167276
vm.prank(OWNER);
168-
_vaultMerklRewardClaimer.claimMerklRewards(rewardTokens, amounts, proofs);
277+
_vaultMerklRewardClaimer.claimMerklRewards(rewardTokens, amounts, proofs, rewardTokensToForward, destination);
169278
}
170279

171280
function testClaimMerklRewardsRevertsIfMerklDistributorNotSet() public {
172281
address[] memory rewardTokens = new address[](0);
173282
uint256[] memory amounts = new uint256[](0);
174283
bytes32[][] memory proofs = new bytes32[][](0);
284+
address[] memory rewardTokensToForward = new address[](0);
285+
address destination = address(0);
175286
vm.prank(OWNER);
176287
vm.expectRevert(bytes("MERKL_DISTRIBUTOR_NOT_SET"));
177-
_vaultMerklRewardClaimer.claimMerklRewards(rewardTokens, amounts, proofs);
288+
_vaultMerklRewardClaimer.claimMerklRewards(rewardTokens, amounts, proofs, rewardTokensToForward, destination);
178289
}
179290

180291
function testClaimMerklRewardsRevertsIfArrayLengthMismatchFromTokens() public {
@@ -188,9 +299,11 @@ contract ATokenVaultMerklRewardClaimerTest is ATokenVaultBaseTest {
188299
bytes32[][] memory proofs = new bytes32[][](1);
189300
proofs[0] = new bytes32[](1);
190301
proofs[0][0] = proof;
302+
address[] memory rewardTokensToForward = new address[](0);
303+
address destination = address(0);
191304
vm.expectRevert(bytes("ARRAY_LENGTH_MISMATCH"));
192305
vm.prank(OWNER);
193-
_vaultMerklRewardClaimer.claimMerklRewards(rewardTokens, amounts, proofs);
306+
_vaultMerklRewardClaimer.claimMerklRewards(rewardTokens, amounts, proofs, rewardTokensToForward, destination);
194307
}
195308

196309
function testClaimMerklRewardsRevertsIfArrayLengthMismatchFromAmounts() public {
@@ -204,9 +317,11 @@ contract ATokenVaultMerklRewardClaimerTest is ATokenVaultBaseTest {
204317
bytes32[][] memory proofs = new bytes32[][](1);
205318
proofs[0] = new bytes32[](1);
206319
proofs[0][0] = proof;
320+
address[] memory rewardTokensToForward = new address[](0);
321+
address destination = address(0);
207322
vm.expectRevert(bytes("ARRAY_LENGTH_MISMATCH"));
208323
vm.prank(OWNER);
209-
_vaultMerklRewardClaimer.claimMerklRewards(rewardTokens, amounts, proofs);
324+
_vaultMerklRewardClaimer.claimMerklRewards(rewardTokens, amounts, proofs, rewardTokensToForward, destination);
210325
}
211326

212327
function testClaimMerklRewardsRevertsIfArrayLengthMismatchFromProofs() public {
@@ -221,29 +336,35 @@ contract ATokenVaultMerklRewardClaimerTest is ATokenVaultBaseTest {
221336
proofs[0][0] = proof;
222337
proofs[1] = new bytes32[](1);
223338
proofs[1][0] = proof;
339+
address[] memory rewardTokensToForward = new address[](0);
340+
address destination = address(0);
224341
vm.expectRevert(bytes("ARRAY_LENGTH_MISMATCH"));
225342
vm.prank(OWNER);
226-
_vaultMerklRewardClaimer.claimMerklRewards(rewardTokens, amounts, proofs);
343+
_vaultMerklRewardClaimer.claimMerklRewards(rewardTokens, amounts, proofs, rewardTokensToForward, destination);
227344
}
228345

229346
function testClaimMerklRewardsRevertsIfNotOwner() public {
230347
address[] memory rewardTokens = new address[](0);
231348
uint256[] memory amounts = new uint256[](0);
232349
bytes32[][] memory proofs = new bytes32[][](0);
350+
address[] memory rewardTokensToForward = new address[](0);
351+
address destination = address(0);
233352
vm.expectRevert(bytes("Ownable: caller is not the owner"));
234-
_vaultMerklRewardClaimer.claimMerklRewards(rewardTokens, amounts, proofs);
353+
_vaultMerklRewardClaimer.claimMerklRewards(rewardTokens, amounts, proofs, rewardTokensToForward, destination);
235354
}
236355

237356
function testClaimMerklRewardsRevertsIfMerklDistributorReverts() public {
238357
_setMerklDistributor();
239358

240359
bytes32 proof = keccak256("proof1");
241360
(address[] memory rewardTokens, uint256[] memory amounts, bytes32[][] memory proofs) = _buildMerklRewardsClaimData(address(_dai), 1000, proof);
361+
address[] memory rewardTokensToForward = new address[](0);
362+
address destination = address(0);
242363
string memory revertReason = "revert because of insufficient balance";
243364
_merklDistributor.setShouldRevert(true, revertReason);
244365
vm.expectRevert(bytes(revertReason));
245366
vm.prank(OWNER);
246-
_vaultMerklRewardClaimer.claimMerklRewards(rewardTokens, amounts, proofs);
367+
_vaultMerklRewardClaimer.claimMerklRewards(rewardTokens, amounts, proofs, rewardTokensToForward, destination);
247368
}
248369

249370
function testSetMerklDistributor() public {
@@ -297,8 +418,10 @@ contract ATokenVaultMerklRewardClaimerTest is ATokenVaultBaseTest {
297418
function _claimMerklRewards() internal {
298419
bytes32 proof = keccak256("proof1");
299420
(address[] memory rewardTokens, uint256[] memory amounts, bytes32[][] memory proofs) = _buildMerklRewardsClaimData(address(_dai), 1000, proof);
421+
address[] memory rewardTokensToForward = new address[](0);
422+
address destination = address(0);
300423
vm.prank(OWNER);
301-
_vaultMerklRewardClaimer.claimMerklRewards(rewardTokens, amounts, proofs);
424+
_vaultMerklRewardClaimer.claimMerklRewards(rewardTokens, amounts, proofs, rewardTokensToForward, destination);
302425
}
303426

304427
function _depositFromUser(address user, uint256 amount) internal {

0 commit comments

Comments
 (0)