Skip to content

Commit 67395a8

Browse files
committed
fix(ATokenVaultMerklRewardClaimer): do not allow claiming and forwarding of aToken
- The change is intended to protect against an attack which leverages a malicious distributor contract which enters into the ATokenVault via depositATokens(uint256 assets, address receiver) then forwards the aToken after the IMerklDistributor::claim call executes, effectively allowing an actor to obtain vault shares for free
1 parent 5516540 commit 67395a8

3 files changed

Lines changed: 7 additions & 36 deletions

File tree

src/ATokenVaultMerklRewardClaimer.sol

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ contract ATokenVaultMerklRewardClaimer is ATokenVault, IATokenVaultMerklRewardCl
4545

4646
uint256[] memory currentBalancesOfRewardTokens = new uint256[](rewardTokensToForward.length);
4747
for (uint256 i = 0; i < rewardTokensToForward.length; i++) {
48+
require(rewardTokensToForward[i] != address(ATOKEN), "CANNOT_FORWARD_ATOKEN");
4849
currentBalancesOfRewardTokens[i] = IERC20Upgradeable(rewardTokensToForward[i]).balanceOf(address(this));
4950
}
5051

test/ATokenVaultMerklRewardClaimer.t.sol

Lines changed: 4 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ contract ATokenVaultMerklRewardClaimerTest is ATokenVaultBaseTest {
7676
}
7777

7878
function testClaimMerklRewardsAndForwardPartialTokenToDestination() public {
79-
// Context: 2 tokens will be rewarded, but only one will be forwarded to the destination.
79+
// Context: 2 tokens will be rewarded, but only one (the non-aToken) will be forwarded to the destination.
8080
_setMerklDistributor();
8181

8282
uint256 amountOfATokenRewarded = 789 * 1e18;
@@ -127,8 +127,8 @@ contract ATokenVaultMerklRewardClaimerTest is ATokenVaultBaseTest {
127127
assertEq(_aDai.balanceOf(address(_vaultMerklRewardClaimer)), beforeBalanceOfAToken + amountOfATokenRewarded);
128128
}
129129

130-
function testClaimMerklRewardsAndForwardFullTokenToDestination() public {
131-
// Context: 2 tokens will be rewarded, and both will be forwarded to the destination.
130+
function testClaimMerklRewardsAndForwardFailsGivenATokenIsForwarded() public {
131+
// Context: 2 tokens will be rewarded, and both will be attempted to be forwarded to the destination.
132132
_setMerklDistributor();
133133

134134
uint256 amountOfATokenRewarded = 789 * 1e18;
@@ -160,24 +160,10 @@ contract ATokenVaultMerklRewardClaimerTest is ATokenVaultBaseTest {
160160
rewardTokensToForward[0] = address(_aDai);
161161
rewardTokensToForward[1] = address(_dai);
162162
address destination = makeAddr("destination");
163-
164-
// Check that the vault does not have any aDAI.
165-
uint256 beforeBalanceOfAToken = _aDai.balanceOf(address(_vaultMerklRewardClaimer));
166-
uint256 beforeBalanceOfDAI = _dai.balanceOf(address(_vaultMerklRewardClaimer));
167163

168-
vm.expectEmit(true, true, false, true, address(_vaultMerklRewardClaimer));
169-
emit IATokenVaultMerklRewardClaimer.MerklRewardsClaimed(address(_merklDistributor), mockRewardTokens, mockAmounts);
170-
vm.expectEmit(true, true, false, true, address(_vaultMerklRewardClaimer));
171-
emit IATokenVaultMerklRewardClaimer.MerklRewardsTokenForwarded(address(_dai), destination, amountOfDAIRewarded);
164+
vm.expectRevert(bytes("CANNOT_FORWARD_ATOKEN"));
172165
vm.prank(OWNER);
173166
_vaultMerklRewardClaimer.claimMerklRewards(mockRewardTokens, mockAmounts, proofs, rewardTokensToForward, destination);
174-
175-
// Check that the destination received the DAI and aDAI.
176-
assertEq(_dai.balanceOf(destination), amountOfDAIRewarded);
177-
assertEq(_aDai.balanceOf(destination), amountOfATokenRewarded);
178-
// Check that the vault did not hold onto the aDAI and DAI.
179-
assertEq(_aDai.balanceOf(address(_vaultMerklRewardClaimer)), beforeBalanceOfAToken);
180-
assertEq(_dai.balanceOf(address(_vaultMerklRewardClaimer)), beforeBalanceOfDAI);
181167
}
182168

183169
function testClaimMerklRewardsIfATokenIsRewarded() public {

test/ATokenVaultMerklRewardClaimerFork.t.sol

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -70,36 +70,20 @@ contract ATokenVaultMerklRewardClaimerForkTest is ATokenVaultBaseTest {
7070
assertEq(IERC20(WRAPPED_A_HOR_RWA_RLUSD).balanceOf(ADDRESS_WITH_CLAIMABLE_REWARDS), beforeBalanceOfWrappedAHorRwaRLUSD);
7171
}
7272

73-
function testOwnerCanClaimMerklRewardsAndForwardToDestination() public {
74-
uint256 expectedAmountOfAHorRwaRLUSDReceived = 3772222577889726879658;
73+
function testOwnerCanNotClaimMerklRewardsAndForwardToDestination() public {
7574
_setMerklDistributor();
7675
// Set the code for an address that has claimable rewards as of the fork block
7776
// We will use this in place of the vault deployment
7877
_etchVault(ADDRESS_WITH_CLAIMABLE_REWARDS);
7978
address destination = makeAddr("destination");
80-
81-
uint256 beforeBalanceOfAHorRwaRLUSD = IERC20(A_HOR_RWA_RLUSD).balanceOf(ADDRESS_WITH_CLAIMABLE_REWARDS);
82-
uint256 beforeBalanceOfWrappedAHorRwaRLUSD = IERC20(WRAPPED_A_HOR_RWA_RLUSD).balanceOf(ADDRESS_WITH_CLAIMABLE_REWARDS);
8379

8480
(address[] memory tokens, uint256[] memory amounts, bytes32[][] memory proofs) = _buildMerklRewardsClaimData();
8581
address[] memory rewardTokensToForward = new address[](1);
8682
rewardTokensToForward[0] = address(A_HOR_RWA_RLUSD);
8783

88-
vm.expectEmit(true, true, false, true, ADDRESS_WITH_CLAIMABLE_REWARDS);
89-
emit IATokenVaultMerklRewardClaimer.MerklRewardsClaimed(MERKL_DISTRIBUTOR, tokens, amounts);
90-
vm.expectEmit(true, true, false, true, ADDRESS_WITH_CLAIMABLE_REWARDS);
91-
emit IATokenVaultMerklRewardClaimer.MerklRewardsTokenForwarded(address(A_HOR_RWA_RLUSD), destination, expectedAmountOfAHorRwaRLUSDReceived);
84+
vm.expectRevert(bytes("CANNOT_FORWARD_ATOKEN"));
9285
vm.prank(OWNER);
9386
IATokenVaultMerklRewardClaimer(ADDRESS_WITH_CLAIMABLE_REWARDS).claimMerklRewards(tokens, amounts, proofs, rewardTokensToForward, destination);
94-
95-
// Check that the vault did not hold onto the A tokens.
96-
assertEq(IERC20(A_HOR_RWA_RLUSD).balanceOf(ADDRESS_WITH_CLAIMABLE_REWARDS), beforeBalanceOfAHorRwaRLUSD);
97-
// Check that total assets is the same as the balance of the A tokens.
98-
assertEq(IERC20(A_HOR_RWA_RLUSD).balanceOf(ADDRESS_WITH_CLAIMABLE_REWARDS), IATokenVault(ADDRESS_WITH_CLAIMABLE_REWARDS).totalAssets());
99-
// Check that the vault's balance of the wrapped aToken is unchcanged.
100-
assertEq(IERC20(WRAPPED_A_HOR_RWA_RLUSD).balanceOf(ADDRESS_WITH_CLAIMABLE_REWARDS), beforeBalanceOfWrappedAHorRwaRLUSD);
101-
// Check that the destination received the A tokens.
102-
assertEq(IERC20(A_HOR_RWA_RLUSD).balanceOf(destination), expectedAmountOfAHorRwaRLUSDReceived);
10387
}
10488

10589
function _buildMerklRewardsClaimData() internal pure returns (address[] memory tokens, uint256[] memory amounts, bytes32[][] memory proofs) {

0 commit comments

Comments
 (0)