Skip to content

Commit

Permalink
Merge the develop branch to the master branch, preparation to v1.1.0
Browse files Browse the repository at this point in the history
This update for the `master` branch contains the changes addressed the findings discovered during a security audit:
  * [Improvement] Add claiming of stkAAVE rewards (#65)
  * [Fix] Small gas optimization after EIP2929 (#58)
  * [Fix] Fix interest impl comments (#59)
  * [Fix] Use single delete (#62)
  * [Fix] Fix minor issues (#63)
  * [Fix] Lazy AAVE token balance query (#60)
  * [Fix] Stricter preconditions for payInterest (#57)
  * [Fix] Use SafeERC20 in interest implementations (#64)
  * [Fix] Use SafeERC20.safeApprove (#67)
  * [Other] Bump package and contracts interfaces version prior to 1.1.0 (#66)
  • Loading branch information
akolotov authored Sep 6, 2021
2 parents d22cd50 + b4935ff commit c814f68
Show file tree
Hide file tree
Showing 14 changed files with 334 additions and 98 deletions.
7 changes: 7 additions & 0 deletions contracts/interfaces/IInterestImplementation.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@ pragma solidity 0.7.5;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

interface IInterestImplementation {
event InterestEnabled(address indexed token, address xToken);
event InterestDustUpdated(address indexed token, uint96 dust);
event InterestReceiverUpdated(address indexed token, address receiver);
event MinInterestPaidUpdated(address indexed token, uint256 amount);
event PaidInterest(address indexed token, address to, uint256 value);
event ForceDisable(address indexed token, uint256 tokensAmount, uint256 xTokensAmount, uint256 investedAmount);

function isInterestSupported(address _token) external view returns (bool);

function invest(address _token, uint256 _amount) external;
Expand Down
5 changes: 5 additions & 0 deletions contracts/interfaces/ILegacyERC20.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
pragma solidity 0.7.5;

interface ILegacyERC20 {
function approve(address spender, uint256 amount) external; // returns (bool);
}
15 changes: 15 additions & 0 deletions contracts/interfaces/IStakedAave.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
pragma solidity 0.7.5;

interface IStakedAave {
function stakersCooldowns(address staker) external view returns (uint256);

function balanceOf(address user) external view returns (uint256);

function stake(address to, uint256 amount) external;

function redeem(address to, uint256 amount) external;

function cooldown() external;

function claimRewards(address to, uint256 amount) external;
}
17 changes: 17 additions & 0 deletions contracts/interfaces/IStakedTokenIncentivesController.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
pragma solidity 0.7.5;

interface IStakedTokenIncentivesController {
function claimRewards(
address[] calldata assets,
uint256 amount,
address to
) external;

function getRewardsBalance(address[] calldata assets, address user) external view returns (uint256);

function configureAssets(address[] calldata assets, uint256[] calldata emissionsPerSecond) external;

function setDistributionEnd(uint256 distributionEnd) external;

function initialize(address addressesProvider) external;
}
15 changes: 14 additions & 1 deletion contracts/mocks/AAVEInterestERC20Mock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,20 @@ pragma solidity 0.7.5;
import "../upgradeable_contracts/modules/interest/AAVEInterestERC20.sol";

contract AAVEInterestERC20Mock is AAVEInterestERC20 {
constructor(address _omnibridge, address _owner) AAVEInterestERC20(_omnibridge, _owner) {}
constructor(
address _omnibridge,
address _owner,
uint256 _minAavePaid,
address _aaveReceiver
) AAVEInterestERC20(_omnibridge, _owner, _minAavePaid, _aaveReceiver) {}

function incentivesController() public pure override returns (IStakedTokenIncentivesController) {
return IStakedTokenIncentivesController(0x00B2952e5FfC9737efee35De2912fAD143c7cA1F);
}

function stkAAVEToken() public pure override returns (address) {
return 0x2F2B2FE9C08d39b1F1C22940a9850e2851F40f99;
}

function lendingPool() public pure override returns (ILendingPool) {
return ILendingPool(0xDe4e2b5D55D2eE0F95b6D96C1BF86b45364e45B0);
Expand Down
5 changes: 4 additions & 1 deletion contracts/upgradeable_contracts/ForeignOmnibridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,10 @@ contract ForeignOmnibridge is BasicOmnibridge, GasLimitManager, InterestConnecto
}

IInterestImplementation impl = interestImplementation(_token);
if (Address.isContract(address(impl))) {
// can be used instead of Address.isContract(address(impl)),
// since _setInterestImplementation guarantees that impl is either a contract or zero address
// and interest implementation does not contain any selfdestruct opcode
if (address(impl) != address(0)) {
uint256 availableBalance = balance.sub(impl.investedAmount(_token));
if (_value > availableBalance) {
impl.withdraw(_token, (_value - availableBalance).add(minCashThreshold(_token)));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pragma solidity 0.7.5;

import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/token/ERC20/SafeERC20.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
import "../../Ownable.sol";
import "../../../interfaces/IInterestReceiver.sol";
Expand All @@ -13,6 +14,7 @@ import "../native/MediatorBalanceStorage.sol";
*/
contract InterestConnector is Ownable, MediatorBalanceStorage {
using SafeMath for uint256;
using SafeERC20 for IERC20;

/**
* @dev Tells address of the interest earning implementation for the specific token contract.
Expand Down Expand Up @@ -86,7 +88,7 @@ contract InterestConnector is Ownable, MediatorBalanceStorage {
require(balance > minCash);
uint256 amount = balance - minCash;

IERC20(_token).transfer(address(impl), amount);
IERC20(_token).safeTransfer(address(impl), amount);
impl.invest(_token, amount);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ contract OmnibridgeInfo is VersionableBridge {
uint64 patch
)
{
return (3, 2, 0);
return (3, 3, 0);
}

/**
Expand Down
147 changes: 100 additions & 47 deletions contracts/upgradeable_contracts/modules/interest/AAVEInterestERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,20 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/math/SafeMath.sol";
import "../../../interfaces/IAToken.sol";
import "../../../interfaces/IOwnable.sol";
import "../../../interfaces/IInterestReceiver.sol";
import "../../../interfaces/IInterestImplementation.sol";
import "../../../interfaces/ILendingPool.sol";
import "../../../interfaces/IStakedTokenIncentivesController.sol";
import "../../../interfaces/ILegacyERC20.sol";
import "../MediatorOwnableModule.sol";
import "./BaseInterestERC20.sol";

/**
* @title AAVEInterestERC20
* @dev This contract contains token-specific logic for investing ERC20 tokens into AAVE protocol.
*/
contract AAVEInterestERC20 is IInterestImplementation, MediatorOwnableModule {
contract AAVEInterestERC20 is BaseInterestERC20, MediatorOwnableModule {
using SafeMath for uint256;

event PaidInterest(address indexed token, address to, uint256 value);
event ForceDisable(address token, uint256 tokensAmount, uint256 aTokensAmount, uint256 investedAmount);
using SafeERC20 for IERC20;
using SafeERC20 for IAToken;

struct InterestParams {
IAToken aToken;
Expand All @@ -28,8 +28,18 @@ contract AAVEInterestERC20 is IInterestImplementation, MediatorOwnableModule {
}

mapping(address => InterestParams) public interestParams;

constructor(address _omnibridge, address _owner) MediatorOwnableModule(_omnibridge, _owner) {}
uint256 public minAavePaid;
address public aaveReceiver;

constructor(
address _omnibridge,
address _owner,
uint256 _minAavePaid,
address _aaveReceiver
) MediatorOwnableModule(_omnibridge, _owner) {
minAavePaid = _minAavePaid;
aaveReceiver = _aaveReceiver;
}

/**
* @dev Tells the module interface version that this contract supports.
Expand All @@ -56,11 +66,25 @@ contract AAVEInterestERC20 is IInterestImplementation, MediatorOwnableModule {
return ILendingPool(0x7d2768dE32b0b80b7a3454c06BdAc94A69DDc7A9);
}

/**
* @dev Tells the address of the StakedTokenIncentivesController contract in the Ethereum Mainnet.
*/
function incentivesController() public pure virtual returns (IStakedTokenIncentivesController) {
return IStakedTokenIncentivesController(0xd784927Ff2f95ba542BfC824c8a8a98F3495f6b5);
}

/**
* @dev Tells the address of the StkAAVE token contract in the Ethereum Mainnet.
*/
function stkAAVEToken() public pure virtual returns (address) {
return 0x4da27a545c0c5B758a6BA100e3a049001de870f5;
}

/**
* @dev Enables support for interest earning through a specific aToken.
* @param _token address of the token contract for which to enable interest.
* @param _dust small amount of underlying tokens that cannot be paid as an interest. Accounts for possible truncation errors.
* @param _interestReceiver address of the interest receiver for underlying token and associated COMP tokens.
* @param _interestReceiver address of the interest receiver for underlying token.
* @param _minInterestPaid min amount of underlying tokens to be paid as an interest.
*/
function enableInterestToken(
Expand All @@ -77,7 +101,14 @@ contract AAVEInterestERC20 is IInterestImplementation, MediatorOwnableModule {

interestParams[_token] = InterestParams(aToken, _dust, 0, _interestReceiver, _minInterestPaid);

IERC20(_token).approve(address(lendingPool()), uint256(-1));
// SafeERC20.safeApprove does not work here in case of possible interest reinitialization,
// since it does not allow positive->positive allowance change. However, it would be safe to make such change here.
ILegacyERC20(_token).approve(address(lendingPool()), uint256(-1));

emit InterestEnabled(_token, address(aToken));
emit InterestDustUpdated(_token, _dust);
emit InterestReceiverUpdated(_token, _interestReceiver);
emit MinInterestPaidUpdated(_token, _minInterestPaid);
}

/**
Expand Down Expand Up @@ -112,7 +143,7 @@ contract AAVEInterestERC20 is IInterestImplementation, MediatorOwnableModule {
}

/**
* @dev Withdraws at least the given amount of tokens from the AAVE protocol.
* @dev Withdraws at least min(_amount, investedAmount) of tokens from the AAVE protocol.
* Only Omnibridge contract is allowed to call this method.
* Converts aTOKENs into _amount of TOKENs.
* @param _token address of the invested token contract.
Expand All @@ -123,7 +154,7 @@ contract AAVEInterestERC20 is IInterestImplementation, MediatorOwnableModule {
uint256 invested = params.investedAmount;
uint256 redeemed = _safeWithdraw(_token, _amount > invested ? invested : _amount);
params.investedAmount = redeemed > invested ? 0 : invested - redeemed;
IERC20(_token).transfer(mediator, redeemed);
IERC20(_token).safeTransfer(mediator, redeemed);
}

/**
Expand All @@ -146,13 +177,36 @@ contract AAVEInterestERC20 is IInterestImplementation, MediatorOwnableModule {
* Earned interest is withdrawn and transferred to the specified interest receiver account.
* @param _token address of the invested token contract in which interest should be paid.
*/
function payInterest(address _token) external {
function payInterest(address _token) external onlyEOA {
InterestParams storage params = interestParams[_token];
uint256 interest = interestAmount(_token);
require(interest >= params.minInterestPaid);
_transferInterest(params.interestReceiver, address(_token), _safeWithdraw(_token, interest));
}

/**
* @dev Tells the amount of earned stkAAVE tokens for supplying assets into the protocol that can be withdrawn.
* Intended to be called via eth_call to obtain the current accumulated value for stkAAVE.
* @param _assets aTokens addresses to claim stkAAVE for.
* @return amount of accumulated stkAAVE tokens across given markets.
*/
function aaveAmount(address[] calldata _assets) public view returns (uint256) {
return incentivesController().getRewardsBalance(_assets, address(this));
}

/**
* @dev Claims stkAAVE token received by supplying underlying tokens and transfers it to the associated AAVE receiver.
* @param _assets aTokens addresses to claim stkAAVE for.
*/
function claimAaveAndPay(address[] calldata _assets) external onlyEOA {
uint256 balance = aaveAmount(_assets);
require(balance >= minAavePaid);

incentivesController().claimRewards(_assets, balance, address(this));

_transferInterest(aaveReceiver, stkAAVEToken(), balance);
}

/**
* @dev Last-resort function for returning assets to the Omnibridge contract in case of some failures in the logic.
* Disables this contract and transfers locked tokens back to the mediator.
Expand All @@ -163,24 +217,22 @@ contract AAVEInterestERC20 is IInterestImplementation, MediatorOwnableModule {
InterestParams storage params = interestParams[_token];
IAToken aToken = params.aToken;

uint256 aTokenBalance = aToken.balanceOf(address(this));
uint256 aTokenBalance = 0;
// try to redeem all aTokens
try lendingPool().withdraw(_token, aTokenBalance, mediator) {
aTokenBalance = 0;
} catch {
aToken.transfer(mediator, aTokenBalance);
// it is safe to specify uint256(-1) as max amount of redeemed tokens
// since the withdraw method of the pool contract will return the entire balance
try lendingPool().withdraw(_token, uint256(-1), mediator) {} catch {
aTokenBalance = aToken.balanceOf(address(this));
aToken.safeTransfer(mediator, aTokenBalance);
}

uint256 balance = IERC20(_token).balanceOf(address(this));
IERC20(_token).transfer(mediator, balance);
IERC20(_token).safeTransfer(mediator, balance);
IERC20(_token).safeApprove(address(lendingPool()), 0);

emit ForceDisable(_token, balance, aTokenBalance, params.investedAmount);

delete params.aToken;
delete params.dust;
delete params.investedAmount;
delete params.minInterestPaid;
delete params.interestReceiver;
delete interestParams[_token];
}

/**
Expand All @@ -191,6 +243,7 @@ contract AAVEInterestERC20 is IInterestImplementation, MediatorOwnableModule {
*/
function setDust(address _token, uint96 _dust) external onlyOwner {
interestParams[_token].dust = _dust;
emit InterestDustUpdated(_token, _dust);
}

/**
Expand All @@ -202,6 +255,7 @@ contract AAVEInterestERC20 is IInterestImplementation, MediatorOwnableModule {
*/
function setInterestReceiver(address _token, address _receiver) external onlyOwner {
interestParams[_token].interestReceiver = _receiver;
emit InterestReceiverUpdated(_token, _receiver);
}

/**
Expand All @@ -212,6 +266,28 @@ contract AAVEInterestERC20 is IInterestImplementation, MediatorOwnableModule {
*/
function setMinInterestPaid(address _token, uint256 _minInterestPaid) external onlyOwner {
interestParams[_token].minInterestPaid = _minInterestPaid;
emit MinInterestPaidUpdated(_token, _minInterestPaid);
}

/**
* @dev Updates min stkAAVE amount that can be transferred in single call.
* Only owner is allowed to call this method.
* @param _minAavePaid new amount of stkAAVE and can be transferred to the interest receiver in single operation.
*/
function setMinAavePaid(uint256 _minAavePaid) external onlyOwner {
minAavePaid = _minAavePaid;
emit MinInterestPaidUpdated(address(stkAAVEToken()), _minAavePaid);
}

/**
* @dev Updates address of the accumulated stkAAVE receiver. Can be any address, EOA or contract.
* Set to 0x00..00 to disable stkAAVE claims and transfers.
* Only owner is allowed to call this method.
* @param _receiver address of the interest receiver.
*/
function setAaveReceiver(address _receiver) external onlyOwner {
aaveReceiver = _receiver;
emit InterestReceiverUpdated(address(stkAAVEToken()), _receiver);
}

/**
Expand All @@ -231,27 +307,4 @@ contract AAVEInterestERC20 is IInterestImplementation, MediatorOwnableModule {

return redeemed;
}

/**
* @dev Internal function transferring interest tokens to the interest receiver.
* Calls a callback on the receiver, interest receiver is a contract.
* @param _receiver address of the tokens receiver.
* @param _token address of the token contract to send.
* @param _amount amount of tokens to transfer.
*/
function _transferInterest(
address _receiver,
address _token,
uint256 _amount
) internal {
require(_receiver != address(0));

IERC20(_token).transfer(_receiver, _amount);

if (Address.isContract(_receiver)) {
IInterestReceiver(_receiver).onInterestReceived(_token);
}

emit PaidInterest(_token, _receiver, _amount);
}
}
Loading

0 comments on commit c814f68

Please sign in to comment.