forked from aave-dao/aave-v3-origin
-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Separate Stata4626 #8
Merged
Merged
Changes from 3 commits
Commits
Show all changes
43 commits
Select commit
Hold shift + click to select a range
c225aa5
Separate Stata4626
kyzia551 4475a18
change to erc7201
kyzia551 863485f
regenerated storage location
kyzia551 6034a94
change latestAnswer calculation logic
kyzia551 0ecfd37
DRAFT: Refactoring in extensions style
kyzia551 b651f7f
add initializer
kyzia551 8e2a8a8
remove unused params at __Stata4626_init
kyzia551 87b5390
remove RayMathExplicitRounding
kyzia551 7c0e5b6
regenerated ERC20AaveLMStorageLocation
kyzia551 721b437
add RAY constant
kyzia551 9b6691f
remove IInitializableStata4626LM
kyzia551 482f8ca
depositWithPermit
kyzia551 4434e4f
disclamer on _update overload
kyzia551 b596ccc
some descriptions cleanup
kyzia551 09d6ec2
change require to revert
kyzia551 1ad079d
add comment to latestAnswer calc
kyzia551 3021817
add comment to latestAnswer calc -1
kyzia551 2fffe82
make ERC20AaveLMUpgradable abstract
kyzia551 08c95d1
update license
kyzia551 cef08a2
rename merger and 4626 contracts
kyzia551 915282d
change Upgradable to Upgradeable
kyzia551 7fbb149
move _disableInitializers into StataTokenV2
kyzia551 89d258d
rename IStata4626 to IERC4626StataToken
kyzia551 24ede0b
rename init on ERC4626StataToken
kyzia551 66b8fec
Changes on stata initializations, to follow more strict guidelines
eboadom 4035b6f
Changes to make stata more consistent with using ERC20 extensions
eboadom 425c7db
Fix on function called on initialize of stata
eboadom 589abf9
Merge pull request #11 from bgd-labs/feat/stata-oz-extensions-standards
kyzia551 114be98
feat: improved tests
sakulstra 87cee0e
fix: update test
sakulstra fbaa45f
feat: add erc4626 tests
sakulstra 3fe5ae2
fix: migrate some more tests
sakulstra 1906609
fix: improve tests
sakulstra 588e1fc
refactor: move to dedicated files
sakulstra bd5b9a9
feat: improve tests
sakulstra 0698a73
fix typo
kyzia551 37b55db
feat: add permit tests
sakulstra 174cd79
fix: linting
sakulstra 617be16
feat: improved docs
sakulstra 9cf1cc0
fix: typos
sakulstra c6befb0
Merge pull request #12 from bgd-labs/feat/improved-tests
kyzia551 c193fa8
fix: use internal function
sakulstra dca9d8c
Merge pull request #9 from bgd-labs/ref/to-oz-extensions
sakulstra File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,303 @@ | ||
// SPDX-License-Identifier: BUSL-1.1 | ||
pragma solidity ^0.8.17; | ||
|
||
import {PausableUpgradeable} from 'openzeppelin-contracts-upgradeable/contracts/utils/PausableUpgradeable.sol'; | ||
import {ERC20Upgradeable} from 'openzeppelin-contracts-upgradeable/contracts/token/ERC20/ERC20Upgradeable.sol'; | ||
import {ERC20PermitUpgradeable} from 'openzeppelin-contracts-upgradeable/contracts/token/ERC20/extensions/ERC20PermitUpgradeable.sol'; | ||
import {ERC20PausableUpgradeable} from 'openzeppelin-contracts-upgradeable/contracts/token/ERC20/extensions/ERC20PausableUpgradeable.sol'; | ||
import {ERC4626Upgradeable, Math, IERC4626} from 'openzeppelin-contracts-upgradeable/contracts/token/ERC20/extensions/ERC4626Upgradeable.sol'; | ||
import {IERC20} from 'openzeppelin-contracts/contracts/interfaces/IERC20.sol'; | ||
import {SafeERC20} from 'openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol'; | ||
|
||
import {IPool} from '../../../core/contracts/interfaces/IPool.sol'; | ||
import {IPoolAddressesProvider} from '../../../core/contracts/interfaces/IPoolAddressesProvider.sol'; | ||
import {IAaveOracle} from '../../../core/contracts/interfaces/IAaveOracle.sol'; | ||
import {DataTypes, ReserveConfiguration} from '../../../core/contracts/protocol/libraries/configuration/ReserveConfiguration.sol'; | ||
import {IACLManager} from '../../../core/contracts/interfaces/IACLManager.sol'; | ||
import {IRescuable, Rescuable} from 'solidity-utils/contracts/utils/Rescuable.sol'; | ||
|
||
import {IAToken} from './interfaces/IAToken.sol'; | ||
import {RayMathExplicitRounding} from '../libraries/RayMathExplicitRounding.sol'; | ||
import {IStata4626} from './interfaces/IStata4626.sol'; | ||
|
||
/** | ||
* @title Stata4626 | ||
* @notice Wrapper smart contract that allows to deposit tokens on the Aave protocol and receive | ||
* a token which balance doesn't increase automatically, but uses an ever-increasing exchange rate. | ||
* @author BGD labs | ||
*/ | ||
contract Stata4626 is | ||
ERC20Upgradeable, | ||
ERC20PermitUpgradeable, | ||
ERC20PausableUpgradeable, | ||
ERC4626Upgradeable, | ||
Rescuable, | ||
IStata4626 | ||
{ | ||
using RayMathExplicitRounding for uint256; | ||
|
||
/// @custom:storage-location erc7201:aave-dao.storage.Stata4626 | ||
struct Stata4626Storage { | ||
IERC20 _aToken; | ||
} | ||
|
||
// keccak256(abi.encode(uint256(keccak256("aave-dao.storage.Stata4626")) - 1)) & ~bytes32(uint256(0xff)) | ||
bytes32 private constant Stata4626StorageLocation = | ||
0x4865e395e8f896b2ca01e8489fd809975f6c70c69fd3d1cf5d2263a21a649200; | ||
|
||
function _getStata4626Storage() private pure returns (Stata4626Storage storage $) { | ||
assembly { | ||
$.slot := Stata4626StorageLocation | ||
} | ||
} | ||
|
||
IPool public immutable POOL; | ||
IPoolAddressesProvider public immutable POOL_ADDRESSES_PROVIDER; | ||
|
||
constructor(IPool pool) { | ||
_disableInitializers(); | ||
POOL = pool; | ||
POOL_ADDRESSES_PROVIDER = pool.ADDRESSES_PROVIDER(); | ||
} | ||
|
||
// TODO: maybe I should not put calls to initializers here | ||
function __Stata4626_init( | ||
address newAToken, | ||
string calldata staticATokenName, | ||
string calldata staticATokenSymbol | ||
) internal onlyInitializing { | ||
require(IAToken(newAToken).POOL() == address(POOL)); | ||
|
||
IERC20 aTokenUnderlying = IERC20(IAToken(newAToken).UNDERLYING_ASSET_ADDRESS()); | ||
|
||
__ERC20_init(staticATokenName, staticATokenSymbol); | ||
__ERC20Permit_init(staticATokenName); | ||
__ERC4626_init(aTokenUnderlying); | ||
__ERC20Pausable_init(); | ||
sakulstra marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
Stata4626Storage storage $ = _getStata4626Storage(); | ||
$._aToken = IERC20(newAToken); | ||
|
||
SafeERC20.forceApprove(aTokenUnderlying, address(POOL), type(uint256).max); | ||
} | ||
|
||
modifier onlyPauseGuardian() { | ||
if (!canPause(_msgSender())) revert OnlyPauseGuardian(_msgSender()); | ||
_; | ||
} | ||
|
||
function decimals() public view override(ERC20Upgradeable, ERC4626Upgradeable) returns (uint8) { | ||
sakulstra marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return ERC4626Upgradeable.decimals(); | ||
} | ||
///@inheritdoc IStata4626 | ||
function canPause(address actor) public view returns (bool) { | ||
return IACLManager(POOL_ADDRESSES_PROVIDER.getACLManager()).isEmergencyAdmin(actor); | ||
} | ||
|
||
/// @inheritdoc IRescuable | ||
function whoCanRescue() public view override returns (address) { | ||
return POOL_ADDRESSES_PROVIDER.getACLAdmin(); | ||
} | ||
|
||
///@inheritdoc IStata4626 | ||
function depositATokens(uint256 assets, address receiver) public returns (uint256) { | ||
uint256 shares = previewDeposit(assets); | ||
_deposit(_msgSender(), receiver, assets, shares, false); | ||
|
||
return shares; | ||
} | ||
|
||
///@inheritdoc IStata4626 | ||
function redeemATokens(uint256 shares, address receiver, address owner) public returns (uint256) { | ||
uint256 assets = previewRedeem(shares); | ||
_withdraw(_msgSender(), receiver, owner, shares, assets, false); | ||
|
||
return assets; | ||
} | ||
|
||
///@inheritdoc IStata4626 | ||
function setPaused(bool paused) external onlyPauseGuardian { | ||
if (paused) _pause(); | ||
else _unpause(); | ||
} | ||
|
||
///@inheritdoc IStata4626 | ||
function aToken() public view returns (IERC20) { | ||
Stata4626Storage storage $ = _getStata4626Storage(); | ||
return $._aToken; | ||
} | ||
|
||
///@inheritdoc IERC4626 | ||
function maxMint(address) public view override returns (uint256) { | ||
uint256 assets = maxDeposit(address(0)); | ||
if (assets == type(uint256).max) return type(uint256).max; | ||
return convertToShares(assets); | ||
} | ||
|
||
///@inheritdoc IERC4626 | ||
function maxWithdraw(address owner) public view override returns (uint256) { | ||
return convertToAssets(maxRedeem(owner)); | ||
} | ||
|
||
///@inheritdoc IERC4626 | ||
function maxRedeem(address owner) public view override returns (uint256) { | ||
DataTypes.ReserveData memory reserveData = POOL.getReserveDataExtended(asset()); | ||
|
||
// if paused or inactive users cannot withdraw underlying | ||
if ( | ||
!ReserveConfiguration.getActive(reserveData.configuration) || | ||
ReserveConfiguration.getPaused(reserveData.configuration) | ||
) { | ||
return 0; | ||
} | ||
|
||
// otherwise users can withdraw up to the available amount | ||
uint256 underlyingTokenBalanceInShares = convertToShares(reserveData.virtualUnderlyingBalance); | ||
uint256 cachedUserBalance = balanceOf(owner); | ||
return | ||
underlyingTokenBalanceInShares >= cachedUserBalance | ||
? cachedUserBalance | ||
: underlyingTokenBalanceInShares; | ||
} | ||
|
||
///@inheritdoc IERC4626 | ||
function maxDeposit(address) public view override returns (uint256) { | ||
DataTypes.ReserveDataLegacy memory reserveData = POOL.getReserveData(asset()); | ||
|
||
// if inactive, paused or frozen users cannot deposit underlying | ||
if ( | ||
!ReserveConfiguration.getActive(reserveData.configuration) || | ||
ReserveConfiguration.getPaused(reserveData.configuration) || | ||
ReserveConfiguration.getFrozen(reserveData.configuration) | ||
) { | ||
return 0; | ||
} | ||
|
||
uint256 supplyCap = ReserveConfiguration.getSupplyCap(reserveData.configuration) * | ||
(10 ** ReserveConfiguration.getDecimals(reserveData.configuration)); | ||
// if no supply cap deposit is unlimited | ||
if (supplyCap == 0) return type(uint256).max; | ||
// return remaining supply cap margin | ||
uint256 currentSupply = (IAToken(reserveData.aTokenAddress).scaledTotalSupply() + | ||
reserveData.accruedToTreasury).rayMulRoundUp(_rate()); | ||
return currentSupply > supplyCap ? 0 : supplyCap - currentSupply; | ||
} | ||
|
||
///@inheritdoc IStata4626 | ||
function latestAnswer() external view returns (int256) { | ||
uint256 aTokenUnderlyingAssetPrice = IAaveOracle(POOL_ADDRESSES_PROVIDER.getPriceOracle()) | ||
.getAssetPrice(asset()); | ||
return int256(convertToAssets(aTokenUnderlyingAssetPrice)); | ||
} | ||
|
||
function _deposit( | ||
address caller, | ||
address receiver, | ||
uint256 assets, | ||
uint256 shares, | ||
bool depositToAave | ||
) internal virtual { | ||
if (shares == 0) { | ||
revert StaticATokenInvalidZeroShares(); | ||
} | ||
// If _asset is ERC777, `transferFrom` can trigger a reentrancy BEFORE the transfer happens through the | ||
// `tokensToSend` hook. On the other hand, the `tokenReceived` hook, that is triggered after the transfer, | ||
// calls the vault, which is assumed not malicious. | ||
// | ||
// Conclusion: we need to do the transfer before we mint so that any reentrancy would happen before the | ||
// assets are transferred and before the shares are minted, which is a valid state. | ||
// slither-disable-next-line reentrancy-no-eth | ||
|
||
if (depositToAave) { | ||
address cachedAsset = asset(); | ||
SafeERC20.safeTransferFrom(IERC20(cachedAsset), caller, address(this), assets); | ||
POOL.deposit(cachedAsset, assets, address(this), 0); | ||
} else { | ||
Stata4626Storage storage $ = _getStata4626Storage(); | ||
SafeERC20.safeTransferFrom($._aToken, caller, address(this), assets); | ||
} | ||
_mint(receiver, shares); | ||
|
||
emit Deposit(caller, receiver, assets, shares); | ||
} | ||
|
||
function _deposit( | ||
address caller, | ||
address receiver, | ||
uint256 assets, | ||
uint256 shares | ||
) internal virtual override { | ||
_deposit(caller, receiver, assets, shares, true); | ||
} | ||
|
||
function _withdraw( | ||
address caller, | ||
address receiver, | ||
address owner, | ||
uint256 assets, | ||
uint256 shares, | ||
bool withdrawFromAave | ||
) internal virtual { | ||
if (caller != owner) { | ||
_spendAllowance(owner, caller, shares); | ||
} | ||
|
||
// If _asset is ERC777, `transfer` can trigger a reentrancy AFTER the transfer happens through the | ||
// `tokensReceived` hook. On the other hand, the `tokensToSend` hook, that is triggered before the transfer, | ||
// calls the vault, which is assumed not malicious. | ||
// | ||
// Conclusion: we need to do the transfer after the burn so that any reentrancy would happen after the | ||
// shares are burned and after the assets are transferred, which is a valid state. | ||
_burn(owner, shares); | ||
if (withdrawFromAave) { | ||
POOL.withdraw(asset(), assets, receiver); | ||
} else { | ||
Stata4626Storage storage $ = _getStata4626Storage(); | ||
SafeERC20.safeTransfer($._aToken, receiver, assets); | ||
} | ||
|
||
emit Withdraw(caller, receiver, owner, assets, shares); | ||
} | ||
|
||
function _withdraw( | ||
address caller, | ||
address receiver, | ||
address owner, | ||
uint256 assets, | ||
uint256 shares | ||
) internal virtual override { | ||
_withdraw(caller, receiver, owner, assets, shares, true); | ||
} | ||
|
||
function _convertToShares( | ||
uint256 assets, | ||
Math.Rounding rounding | ||
) internal view virtual override returns (uint256) { | ||
// @dev we use unsignedRoundsUp instead of just simple comparison to be sure that the code will work as expected | ||
// in case Math.Rounding.Trunc or Math.Rounding.Expand will be passed | ||
if (Math.unsignedRoundsUp(rounding)) return assets.rayDivRoundUp(_rate()); | ||
return assets.rayDivRoundDown(_rate()); | ||
} | ||
|
||
function _convertToAssets( | ||
uint256 shares, | ||
Math.Rounding rounding | ||
) internal view virtual override returns (uint256) { | ||
// @dev we use unsignedRoundsUp instead of just simple comparison to be sure that the code will work as expected | ||
// in case Math.Rounding.Trunc or Math.Rounding.Expand will be passed | ||
if (Math.unsignedRoundsUp(rounding)) return shares.rayMulRoundUp(_rate()); | ||
return shares.rayMulRoundDown(_rate()); | ||
} | ||
|
||
function _update( | ||
address from, | ||
address to, | ||
uint256 amount | ||
) internal virtual override(ERC20Upgradeable, ERC20PausableUpgradeable) whenNotPaused { | ||
super._update(from, to, amount); | ||
} | ||
|
||
function _rate() internal view returns (uint256) { | ||
return POOL.getReserveNormalizedIncome(asset()); | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why the erc7201 prefix? I don't see that included in the hash calculation for the slot
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://eips.ethereum.org/EIPS/eip-7201 because that's how it's supposed to be done. The erc2701 prefix helps with generating some artifacts it think.