v2 Distributor
Changelog
Reasons for changes
The Aave protocol, when calling supply()
, may return a smaller amount of aToken
than was deposited. This leads to the fact that the lastUnderlyingBalance
is less than the deposited
amount. This is unexpected behavior that causes an error in the Distributor
contract code during staking, withdrawal, or claiming operations. An example of such a transaction will be provided below.
Transaction - here.
Location of error, Distributor.sol -> _withdrawYield()
:
uint256 yield_ = depositPool.lastUnderlyingBalance - depositPool.deposited;
Yield
Yield is calculated based on the amount of aToken
, which may be a few wei higher or lower than the amount of tokens deposited. This leads to minimal inaccuracies in yield calculation, which is acceptable.
Solution
It is necessary to track the aToken
balance independently from the deposit token, separating the amount_
specified during deposit and withdrawal, and the one recorded in underlyingAmount_
.
Pull request: https://github.com/MorpheusAIs/SmartContracts/pull/60.
Consequences
We do not see any critical consequences from this issue. Only one deposit pool (wBTC) was affected and is currently unable to operate due to the error described above. Other deposit pools are functioning since the amount of aToken
is greater than the deposited amount, which does not violate the contract logic. After the update, the distributeRewards()
function will overwrite the lastUnderlyingBalance
variable to the most recent value.
Code changelog
supply()
A calculation of the actual amount of aToken
received by the Distributor
contract has been added to the supply()
function. Thus, in the case of the Aave pool, we will add the actual aTokens
received to the last calculated balance. The deposit token is deducted in the amount specified by the user (without changes). For stETH, the underlyingAmount_
variable is set equal to the deposit amount (without changes).
function supply(
uint256 rewardPoolIndex_,
address holder_,
uint256 amount_
) external returns (uint256) {
...
uint256 underlyingAmount_ = amount_;
if (depositPool.strategy == Strategy.AAVE) {
...
uint256 underlyingTokenBalanceBefore_ = IERC20(depositPool.aToken).balanceOf(address(this));
AaveIPool(aavePool_).supply(depositPool.token, amount_, address(this), 0);
uint256 underlyingTokenBalanceAfter_ = IERC20(depositPool.aToken).balanceOf(address(this));
underlyingAmount_ = underlyingTokenBalanceAfter_ - underlyingTokenBalanceBefore_;
}
...
depositPool.lastUnderlyingBalance += underlyingAmount_;
return amount_;
}
withdraw()
When withdrawing the deposit token, the exact amount check, which was previously implemented only for stETH, has now been applied to all deposit pools. This was done to improve security, as Aave declares the "exact" withdrawal amount in the return value, and it can be assumed that this amount may differ. Additionally, a calculation has been added to determine the precise amount of aToken
spent.
function withdraw(
uint256 rewardPoolIndex_,
address receiver_,
uint256 amount_
) external returns (uint256) {
...
uint256 underlyingAmount_ = amount_;
uint256 depositTokenBalanceBefore_ = IERC20(depositPool.token).balanceOf(receiver_);
if (depositPool.strategy == Strategy.AAVE) {
uint256 underlyingTokenBalanceBefore_ = IERC20(depositPool.aToken).balanceOf(address(this));
AaveIPool(AaveIPoolAddressesProvider(aavePoolAddressesProvider).getPool()).withdraw(
depositPool.token,
amount_,
receiver_
);
uint256 underlyingTokenBalanceAfter_ = IERC20(depositPool.aToken).balanceOf(address(this));
underlyingAmount_ = underlyingTokenBalanceBefore_ - underlyingTokenBalanceAfter_;
} else {
IERC20(depositPool.token).safeTransfer(receiver_, amount_);
}
uint256 depositTokenBalanceAfter_ = IERC20(depositPool.token).balanceOf(receiver_);
amount_ = depositTokenBalanceAfter_ - depositTokenBalanceBefore_;
depositPool.deposited -= amount_;
depositPool.lastUnderlyingBalance -= underlyingAmount_;
...
}
_withdrawYield()
Processing has been added to ensure that the lastUnderlyingBalance_
amount must be greater than the deposited_
amount.
function _withdrawYield(
uint256 rewardPoolIndex_,
address depositPoolAddress_
) private {
...
uint256 lastUnderlyingBalance_ = depositPool.lastUnderlyingBalance;
uint256 deposited_ = depositPool.deposited;
if (lastUnderlyingBalance_ <= deposited_) {
return;
}
uint256 yield_ = lastUnderlyingBalance_ - deposited_;
if (depositPool.strategy == Strategy.AAVE) {
uint256 underlyingTokenBalanceBefore_ = IERC20(depositPool.aToken).balanceOf(address(this));
AaveIPool(AaveIPoolAddressesProvider(aavePoolAddressesProvider).getPool()).withdraw(
depositPool.token,
yield_,
l1Sender
);
uint256 underlyingTokenBalanceAfter_ = IERC20(depositPool.aToken).balanceOf(address(this));
yield_ = underlyingTokenBalanceBefore_ - underlyingTokenBalanceAfter_;
} else {
...
}
...
}
Other
Some internal variable names were also changed, and the version and name of the smart contract were updated (Distributor -> DistributorV2).
Last updated
Was this helpful?