Skip to content

Commit

Permalink
Merge f619b8f into 27f152f
Browse files Browse the repository at this point in the history
  • Loading branch information
PierrickGT committed Jun 2, 2022
2 parents 27f152f + f619b8f commit 918c4f5
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 105 deletions.
135 changes: 75 additions & 60 deletions contracts/AaveV3YieldSource.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import { IRewardsController } from "@aave/periphery-v3/contracts/rewards/interfa
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { SafeMath } from "@openzeppelin/contracts/utils/math/SafeMath.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/security/ReentrancyGuard.sol";

import { Manageable, Ownable } from "@pooltogether/owner-manager-contracts/contracts/Manageable.sol";
Expand All @@ -23,7 +22,6 @@ import { IYieldSource } from "@pooltogether/yield-source-interface/contracts/IYi
* @notice Yield Source for a PoolTogether prize pool that generates yield by depositing into Aave V3.
*/
contract AaveV3YieldSource is ERC20, IYieldSource, Manageable, ReentrancyGuard {
using SafeMath for uint256;
using SafeERC20 for IERC20;

/* ============ Events ============ */
Expand All @@ -35,7 +33,7 @@ contract AaveV3YieldSource is ERC20, IYieldSource, Manageable, ReentrancyGuard {
* @param poolAddressesProviderRegistry Aave poolAddressesProviderRegistry address
* @param name Token name for the underlying ERC20 shares
* @param symbol Token symbol for the underlying ERC20 shares
* @param decimals Number of decimals the shares (inhereted ERC20) will have. Same as underlying asset to ensure sane exchange rates for shares.
* @param decimals Number of decimals the shares (inherited ERC20) will have. Same as underlying asset to ensure sane exchange rates for shares.
* @param owner Owner of this contract
*/
event AaveV3YieldSourceInitialized(
Expand All @@ -45,7 +43,7 @@ contract AaveV3YieldSource is ERC20, IYieldSource, Manageable, ReentrancyGuard {
string name,
string symbol,
uint8 decimals,
address owner
address indexed owner
);

/**
Expand Down Expand Up @@ -132,6 +130,12 @@ contract AaveV3YieldSource is ERC20, IYieldSource, Manageable, ReentrancyGuard {
/// @notice Aave poolAddressesProviderRegistry address.
IPoolAddressesProviderRegistry public immutable poolAddressesProviderRegistry;

/// @notice Underlying asset token address.
address private immutable _tokenAddress;

/// @notice Underlying asset unit.
uint256 private immutable _tokenUnit;

/// @notice ERC20 token decimals.
uint8 private immutable _decimals;

Expand All @@ -153,7 +157,7 @@ contract AaveV3YieldSource is ERC20, IYieldSource, Manageable, ReentrancyGuard {
* @param _poolAddressesProviderRegistry Aave poolAddressesProviderRegistry address
* @param _name Token name for the underlying ERC20 shares
* @param _symbol Token symbol for the underlying ERC20 shares
* @param decimals_ Number of decimals the shares (inhereted ERC20) will have. Same as underlying asset to ensure sane exchange rates for shares.
* @param decimals_ Number of decimals the shares (inherited ERC20) will have. Same as underlying asset to ensure sane exchange rates for shares.
* @param _owner Owner of this contract
*/
constructor(
Expand All @@ -165,22 +169,21 @@ contract AaveV3YieldSource is ERC20, IYieldSource, Manageable, ReentrancyGuard {
uint8 decimals_,
address _owner
) Ownable(_owner) ERC20(_name, _symbol) ReentrancyGuard() {
require(_owner != address(0), "AaveV3YS/owner-not-zero-address");
require(address(_aToken) != address(0), "AaveV3YS/aToken-not-zero-address");
aToken = _aToken;

require(decimals_ > 0, "AaveV3YS/decimals-gt-zero");
require(address(_rewardsController) != address(0), "AaveV3YS/RC-not-zero-address");
rewardsController = _rewardsController;

require(address(_poolAddressesProviderRegistry) != address(0), "AaveV3YS/PR-not-zero-address");
poolAddressesProviderRegistry = _poolAddressesProviderRegistry;

require(_owner != address(0), "AaveV3YS/owner-not-zero-address");

require(decimals_ > 0, "AaveV3YS/decimals-gt-zero");
aToken = _aToken;
_decimals = decimals_;
_tokenUnit = 10**decimals_;
_tokenAddress = address(_aToken.UNDERLYING_ASSET_ADDRESS());
rewardsController = _rewardsController;
poolAddressesProviderRegistry = _poolAddressesProviderRegistry;

// Approve once for max amount
IERC20(_aToken.UNDERLYING_ASSET_ADDRESS()).safeApprove(address(_pool()), type(uint256).max);
IERC20(_tokenAddress).safeApprove(address(_pool()), type(uint256).max);

emit AaveV3YieldSourceInitialized(
_aToken,
Expand Down Expand Up @@ -209,7 +212,7 @@ contract AaveV3YieldSource is ERC20, IYieldSource, Manageable, ReentrancyGuard {
* @return The ERC20 asset token address.
*/
function depositToken() public view override returns (address) {
return _tokenAddress();
return _tokenAddress;
}

/**
Expand All @@ -230,11 +233,10 @@ contract AaveV3YieldSource is ERC20, IYieldSource, Manageable, ReentrancyGuard {
*/
function supplyTokenTo(uint256 _depositAmount, address _to) external override nonReentrant {
uint256 _shares = _tokenToShares(_depositAmount);
require(_shares > 0, "AaveV3YS/shares-gt-zero");
_requireSharesGTZero(_shares);

address _underlyingAssetAddress = _tokenAddress();
IERC20(_underlyingAssetAddress).safeTransferFrom(msg.sender, address(this), _depositAmount);
_pool().supply(_underlyingAssetAddress, _depositAmount, address(this), REFERRAL_CODE);
IERC20(_tokenAddress).safeTransferFrom(msg.sender, address(this), _depositAmount);
_pool().supply(_tokenAddress, _depositAmount, address(this), REFERRAL_CODE);

_mint(_to, _shares);

Expand All @@ -249,17 +251,21 @@ contract AaveV3YieldSource is ERC20, IYieldSource, Manageable, ReentrancyGuard {
* @return The actual amount of asset tokens that were redeemed.
*/
function redeemToken(uint256 _redeemAmount) external override nonReentrant returns (uint256) {
address _underlyingAssetAddress = _tokenAddress();
IERC20 _assetToken = IERC20(_underlyingAssetAddress);

uint256 _shares = _tokenToShares(_redeemAmount);
_requireSharesGTZero(_shares);

_burn(msg.sender, _shares);

IERC20 _assetToken = IERC20(_tokenAddress);
uint256 _beforeBalance = _assetToken.balanceOf(address(this));
_pool().withdraw(_underlyingAssetAddress, _redeemAmount, address(this));
uint256 _afterBalance = _assetToken.balanceOf(address(this));
_pool().withdraw(_tokenAddress, _redeemAmount, address(this));

uint256 _balanceDiff;

unchecked {
_balanceDiff = _assetToken.balanceOf(address(this)) - _beforeBalance;
}

uint256 _balanceDiff = _afterBalance.sub(_beforeBalance);
_assetToken.safeTransfer(msg.sender, _balanceDiff);

emit RedeemedToken(msg.sender, _shares, _redeemAmount);
Expand All @@ -270,9 +276,8 @@ contract AaveV3YieldSource is ERC20, IYieldSource, Manageable, ReentrancyGuard {
* @notice Claims the accrued rewards for the aToken, accumulating any pending rewards.
* @dev Only callable by the owner or manager.
* @param _to Address where the claimed rewards will be sent
* @return True if operation was successful.
*/
function claimRewards(address _to) external onlyManagerOrOwner returns (bool) {
function claimRewards(address _to) external onlyManagerOrOwner {
require(_to != address(0), "AaveV3YS/payee-not-zero-address");

address[] memory _assets = new address[](1);
Expand All @@ -282,7 +287,6 @@ contract AaveV3YieldSource is ERC20, IYieldSource, Manageable, ReentrancyGuard {
.claimAllRewards(_assets, _to);

emit Claimed(msg.sender, _to, _rewardsList, _claimedAmounts);
return true;
}

/**
Expand All @@ -298,7 +302,7 @@ contract AaveV3YieldSource is ERC20, IYieldSource, Manageable, ReentrancyGuard {
address _spender,
uint256 _amount
) external onlyManagerOrOwner {
_requireNotAToken(address(_token));
_requireNotATokenAllowance(address(_token));
_token.safeDecreaseAllowance(_spender, _amount);
emit DecreasedERC20Allowance(msg.sender, _spender, _amount, _token);
}
Expand All @@ -317,7 +321,7 @@ contract AaveV3YieldSource is ERC20, IYieldSource, Manageable, ReentrancyGuard {
address _spender,
uint256 _amount
) external onlyManagerOrOwner {
_requireNotAToken(address(_token));
_requireNotATokenAllowance(address(_token));
_token.safeIncreaseAllowance(_spender, _amount);
emit IncreasedERC20Allowance(msg.sender, _spender, _amount, _token);
}
Expand All @@ -334,69 +338,80 @@ contract AaveV3YieldSource is ERC20, IYieldSource, Manageable, ReentrancyGuard {
address _to,
uint256 _amount
) external onlyManagerOrOwner {
require(address(_token) != address(aToken), "AaveV3YS/forbid-aToken-transfer");
_requireNotAToken(address(_token), "AaveV3YS/forbid-aToken-transfer");
_token.safeTransfer(_to, _amount);
emit TransferredERC20(msg.sender, _to, _amount, _token);
}

/* ============ Internal Functions ============ */

/**
* @notice Check that the token address passed is not the aToken address.
* @param _token Address of the ERC20 token to check
* @notice Checks that the amount of shares is greater than zero.
* @param _shares Amount of shares to check
*/
function _requireNotAToken(address _token) internal view {
require(_token != address(aToken), "AaveV3YS/forbid-aToken-allowance");
function _requireSharesGTZero(uint256 _shares) internal pure {
require(_shares > 0, "AaveV3YS/shares-gt-zero");
}

/**
* @notice Calculates the number of shares that should be minted or burnt when a user deposit or withdraw.
* @param _tokens Amount of asset tokens
* @return Number of shares.
* @notice Checks that the token address passed is not the aToken address.
* @param _token Address of the ERC20 token to check
* @param _errorMessage Error message to be thrown if the token is the aToken
*/
function _tokenToShares(uint256 _tokens) internal view returns (uint256) {
uint256 _supply = totalSupply();
function _requireNotAToken(address _token, string memory _errorMessage) internal view {
require(_token != address(aToken), _errorMessage);
}

// shares = (tokens * totalShares) / yieldSourceATokenTotalSupply
return _supply == 0 ? _tokens : _tokens.mul(_supply).div(aToken.balanceOf(address(this)));
/**
* @notice Checks that the token address passed for allowance is not the aToken address.
* @param _token Address of the ERC20 token to check
*/
function _requireNotATokenAllowance(address _token) internal view {
_requireNotAToken(_token, "AaveV3YS/forbid-aToken-allowance");
}

/**
* @notice Calculates the number of asset tokens a user has in the yield source.
* @param _shares Amount of shares
* @return Number of asset tokens.
* @notice Calculates the price of a full share.
* @dev We use this calculation to ensure that the price per share can't be manipulated.
* @return The current price per share
*/
function _sharesToToken(uint256 _shares) internal view returns (uint256) {
function _pricePerShare() internal view returns (uint256) {
uint256 _supply = totalSupply();

// tokens = (shares * yieldSourceATokenTotalSupply) / totalShares
return _supply == 0 ? _shares : _shares.mul(aToken.balanceOf(address(this))).div(_supply);
// pricePerShare = (token * yieldSourceBalanceOfAToken) / totalSupply
return _supply == 0 ? _tokenUnit : (_tokenUnit * aToken.balanceOf(address(this))) / _supply;
}

/**
* @notice Returns the underlying asset token address.
* @return Underlying asset token address.
* @notice Calculates the number of shares that should be minted or burnt when a user deposit or withdraw.
* @param _tokens Amount of asset tokens
* @return Number of shares.
*/
function _tokenAddress() internal view returns (address) {
return aToken.UNDERLYING_ASSET_ADDRESS();
function _tokenToShares(uint256 _tokens) internal view returns (uint256) {
// shares = (tokens * totalSupply) / yieldSourceBalanceOfAToken
return _tokens == 0 ? _tokens : (_tokens * _tokenUnit) / _pricePerShare();
}

/**
* @notice Retrieves Aave PoolAddressesProvider address.
* @return A reference to PoolAddressesProvider interface.
* @notice Calculates the number of asset tokens a user has in the yield source.
* @param _shares Amount of shares
* @return Number of asset tokens.
*/
function _poolProvider() internal view returns (IPoolAddressesProvider) {
return
IPoolAddressesProvider(
poolAddressesProviderRegistry.getAddressesProvidersList()[ADDRESSES_PROVIDER_ID]
);
function _sharesToToken(uint256 _shares) internal view returns (uint256) {
// tokens = (shares * yieldSourceBalanceOfAToken) / totalSupply
return _shares == 0 ? _shares : (_shares * _pricePerShare()) / _tokenUnit;
}

/**
* @notice Retrieves Aave Pool address.
* @return A reference to Pool interface.
*/
function _pool() internal view returns (IPool) {
return IPool(_poolProvider().getPool());
return
IPool(
IPoolAddressesProvider(
poolAddressesProviderRegistry.getAddressesProvidersList()[ADDRESSES_PROVIDER_ID]
).getPool()
);
}
}
8 changes: 0 additions & 8 deletions contracts/test/AaveV3YieldSourceHarness.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,6 @@ contract AaveV3YieldSourceHarness is AaveV3YieldSource {
return _sharesToToken(shares);
}

function tokenAddress() external view returns (address) {
return _tokenAddress();
}

function poolProvider() external view returns (IPoolAddressesProvider) {
return _poolProvider();
}

function pool() external view returns (IPool) {
return _pool();
}
Expand Down

0 comments on commit 918c4f5

Please sign in to comment.