diff --git a/src/periphery/contracts/dependencies/openzeppelin/ECDSA.sol b/src/periphery/contracts/dependencies/openzeppelin/ECDSA.sol deleted file mode 100644 index e58805c6..00000000 --- a/src/periphery/contracts/dependencies/openzeppelin/ECDSA.sol +++ /dev/null @@ -1,180 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v5.0.0) (utils/cryptography/ECDSA.sol) -pragma solidity ^0.8.0; - -/** - * @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations. - * - * These functions can be used to verify that a message was signed by the holder - * of the private keys of a given address. - */ -library ECDSA { - enum RecoverError { - NoError, - InvalidSignature, - InvalidSignatureLength, - InvalidSignatureS - } - - /** - * @dev The signature derives the `address(0)`. - */ - error ECDSAInvalidSignature(); - - /** - * @dev The signature has an invalid length. - */ - error ECDSAInvalidSignatureLength(uint256 length); - - /** - * @dev The signature has an S value that is in the upper half order. - */ - error ECDSAInvalidSignatureS(bytes32 s); - - /** - * @dev Returns the address that signed a hashed message (`hash`) with `signature` or an error. This will not - * return address(0) without also returning an error description. Errors are documented using an enum (error type) - * and a bytes32 providing additional information about the error. - * - * If no error is returned, then the address can be used for verification purposes. - * - * The `ecrecover` EVM precompile allows for malleable (non-unique) signatures: - * this function rejects them by requiring the `s` value to be in the lower - * half order, and the `v` value to be either 27 or 28. - * - * IMPORTANT: `hash` _must_ be the result of a hash operation for the - * verification to be secure: it is possible to craft signatures that - * recover to arbitrary addresses for non-hashed data. A safe way to ensure - * this is by receiving a hash of the original message (which may otherwise - * be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it. - * - * Documentation for signature generation: - * - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js] - * - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers] - */ - function tryRecover( - bytes32 hash, - bytes memory signature - ) internal pure returns (address, RecoverError, bytes32) { - if (signature.length == 65) { - bytes32 r; - bytes32 s; - uint8 v; - // ecrecover takes the signature parameters, and the only way to get them - // currently is to use assembly. - /// @solidity memory-safe-assembly - assembly { - r := mload(add(signature, 0x20)) - s := mload(add(signature, 0x40)) - v := byte(0, mload(add(signature, 0x60))) - } - return tryRecover(hash, v, r, s); - } else { - return (address(0), RecoverError.InvalidSignatureLength, bytes32(signature.length)); - } - } - - /** - * @dev Returns the address that signed a hashed message (`hash`) with - * `signature`. This address can then be used for verification purposes. - * - * The `ecrecover` EVM precompile allows for malleable (non-unique) signatures: - * this function rejects them by requiring the `s` value to be in the lower - * half order, and the `v` value to be either 27 or 28. - * - * IMPORTANT: `hash` _must_ be the result of a hash operation for the - * verification to be secure: it is possible to craft signatures that - * recover to arbitrary addresses for non-hashed data. A safe way to ensure - * this is by receiving a hash of the original message (which may otherwise - * be too long), and then calling {MessageHashUtils-toEthSignedMessageHash} on it. - */ - function recover(bytes32 hash, bytes memory signature) internal pure returns (address) { - (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, signature); - _throwError(error, errorArg); - return recovered; - } - - /** - * @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately. - * - * See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures] - */ - function tryRecover( - bytes32 hash, - bytes32 r, - bytes32 vs - ) internal pure returns (address, RecoverError, bytes32) { - unchecked { - bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff); - // We do not check for an overflow here since the shift operation results in 0 or 1. - uint8 v = uint8((uint256(vs) >> 255) + 27); - return tryRecover(hash, v, r, s); - } - } - - /** - * @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately. - */ - function recover(bytes32 hash, bytes32 r, bytes32 vs) internal pure returns (address) { - (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, r, vs); - _throwError(error, errorArg); - return recovered; - } - - /** - * @dev Overload of {ECDSA-tryRecover} that receives the `v`, - * `r` and `s` signature fields separately. - */ - function tryRecover( - bytes32 hash, - uint8 v, - bytes32 r, - bytes32 s - ) internal pure returns (address, RecoverError, bytes32) { - // EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature - // unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines - // the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most - // signatures from current libraries generate a unique signature with an s-value in the lower half order. - // - // If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value - // with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or - // vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept - // these malleable signatures as well. - if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) { - return (address(0), RecoverError.InvalidSignatureS, s); - } - - // If the signature is valid (and not malleable), return the signer address - address signer = ecrecover(hash, v, r, s); - if (signer == address(0)) { - return (address(0), RecoverError.InvalidSignature, bytes32(0)); - } - - return (signer, RecoverError.NoError, bytes32(0)); - } - - /** - * @dev Overload of {ECDSA-recover} that receives the `v`, - * `r` and `s` signature fields separately. - */ - function recover(bytes32 hash, uint8 v, bytes32 r, bytes32 s) internal pure returns (address) { - (address recovered, RecoverError error, bytes32 errorArg) = tryRecover(hash, v, r, s); - _throwError(error, errorArg); - return recovered; - } - - /** - * @dev Optionally reverts with the corresponding custom error according to the `error` argument provided. - */ - function _throwError(RecoverError error, bytes32 errorArg) private pure { - if (error == RecoverError.NoError) { - return; // no error: do nothing - } else if (error == RecoverError.InvalidSignature) { - revert ECDSAInvalidSignature(); - } else if (error == RecoverError.InvalidSignatureLength) { - revert ECDSAInvalidSignatureLength(uint256(errorArg)); - } else if (error == RecoverError.InvalidSignatureS) { - revert ECDSAInvalidSignatureS(errorArg); - } - } -} diff --git a/src/periphery/contracts/dependencies/solmate/ERC20.sol b/src/periphery/contracts/dependencies/solmate/ERC20.sol deleted file mode 100644 index 546df288..00000000 --- a/src/periphery/contracts/dependencies/solmate/ERC20.sol +++ /dev/null @@ -1,207 +0,0 @@ -// SPDX-License-Identifier: AGPL-3.0-only -pragma solidity >=0.8.0; - -import {ECDSA} from '../openzeppelin/ECDSA.sol'; - -/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation. -/// @author Solmate (https://github.com/Rari-Capital/solmate/blob/main/src/tokens/ERC20.sol) -/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol) -/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it. -abstract contract ERC20 { - bytes32 public constant PERMIT_TYPEHASH = - keccak256('Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)'); - - /* ////////////////////////////////////////////////////////////// - EVENTS - ////////////////////////////////////////////////////////////// */ - - event Transfer(address indexed from, address indexed to, uint256 amount); - - event Approval(address indexed owner, address indexed spender, uint256 amount); - - /* ////////////////////////////////////////////////////////////// - METADATA STORAGE - ////////////////////////////////////////////////////////////// */ - - string public name; - - string public symbol; - - uint8 public decimals; - - /* ////////////////////////////////////////////////////////////// - ERC20 STORAGE - ////////////////////////////////////////////////////////////// */ - - uint256 public totalSupply; - - mapping(address => uint256) public balanceOf; - - mapping(address => mapping(address => uint256)) public allowance; - - /* ////////////////////////////////////////////////////////////// - EIP-2612 STORAGE - ////////////////////////////////////////////////////////////// */ - - mapping(address => uint256) public nonces; - - /* ////////////////////////////////////////////////////////////// - CONSTRUCTOR - ////////////////////////////////////////////////////////////// */ - - constructor(string memory _name, string memory _symbol, uint8 _decimals) { - name = _name; - symbol = _symbol; - decimals = _decimals; - } - - /* ////////////////////////////////////////////////////////////// - ERC20 LOGIC - ////////////////////////////////////////////////////////////// */ - - function approve(address spender, uint256 amount) public virtual returns (bool) { - allowance[msg.sender][spender] = amount; - - emit Approval(msg.sender, spender, amount); - - return true; - } - - function transfer(address to, uint256 amount) public virtual returns (bool) { - _beforeTokenTransfer(msg.sender, to, amount); - balanceOf[msg.sender] -= amount; - - // Cannot overflow because the sum of all user - // balances can't exceed the max uint256 value. - unchecked { - balanceOf[to] += amount; - } - - emit Transfer(msg.sender, to, amount); - - return true; - } - - function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) { - _beforeTokenTransfer(from, to, amount); - uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals. - - if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount; - - balanceOf[from] -= amount; - - // Cannot overflow because the sum of all user - // balances can't exceed the max uint256 value. - unchecked { - balanceOf[to] += amount; - } - - emit Transfer(from, to, amount); - - return true; - } - - /* ////////////////////////////////////////////////////////////// - EIP-2612 LOGIC - ////////////////////////////////////////////////////////////// */ - - function permit( - address owner, - address spender, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) public virtual { - require(deadline >= block.timestamp, 'PERMIT_DEADLINE_EXPIRED'); - - // Unchecked because the only math done is incrementing - // the owner's nonce which cannot realistically overflow. - unchecked { - address signer = ECDSA.recover( - keccak256( - abi.encodePacked( - '\x19\x01', - DOMAIN_SEPARATOR(), - keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonces[owner]++, deadline)) - ) - ), - v, - r, - s - ); - - require(signer == owner, 'INVALID_SIGNER'); - - allowance[signer][spender] = value; - } - - emit Approval(owner, spender, value); - } - - function DOMAIN_SEPARATOR() public view virtual returns (bytes32) { - return computeDomainSeparator(); - } - - function computeDomainSeparator() internal view virtual returns (bytes32) { - return - keccak256( - abi.encode( - keccak256( - 'EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)' - ), - keccak256(bytes(name)), - keccak256('1'), - block.chainid, - address(this) - ) - ); - } - - /* ////////////////////////////////////////////////////////////// - INTERNAL MINT/BURN LOGIC - ////////////////////////////////////////////////////////////// */ - - function _mint(address to, uint256 amount) internal virtual { - _beforeTokenTransfer(address(0), to, amount); - totalSupply += amount; - - // Cannot overflow because the sum of all user - // balances can't exceed the max uint256 value. - unchecked { - balanceOf[to] += amount; - } - - emit Transfer(address(0), to, amount); - } - - function _burn(address from, uint256 amount) internal virtual { - _beforeTokenTransfer(from, address(0), amount); - balanceOf[from] -= amount; - - // Cannot underflow because a user's balance - // will never be larger than the total supply. - unchecked { - totalSupply -= amount; - } - - emit Transfer(from, address(0), amount); - } - - /** - * @dev Hook that is called before any transfer of tokens. This includes - * minting and burning. - * - * Calling conditions: - * - * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens - * will be to transferred to `to`. - * - when `from` is zero, `amount` tokens will be minted for `to`. - * - when `to` is zero, `amount` of ``from``'s tokens will be burned. - * - `from` and `to` are never both zero. - * - * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. - */ - function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {} -} diff --git a/src/periphery/contracts/static-a-token/DeprecationGap.sol b/src/periphery/contracts/static-a-token/DeprecationGap.sol deleted file mode 100644 index cdc3652c..00000000 --- a/src/periphery/contracts/static-a-token/DeprecationGap.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.10; - -/** - * This contract adds a single slot gap - * The slot is required to account for the now deprecated Initializable. - * The new version of Initializable uses erc7201, so it no longer occupies the first slot. - * https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/master/contracts/proxy/utils/Initializable.sol#L60 - */ -contract DeprecationGap { - uint256 internal __deprecated; -} diff --git a/src/periphery/contracts/static-a-token/StataOracle.sol b/src/periphery/contracts/static-a-token/StataOracle.sol index d1d7e7ca..1a715b07 100644 --- a/src/periphery/contracts/static-a-token/StataOracle.sol +++ b/src/periphery/contracts/static-a-token/StataOracle.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.10; +import {IERC4626} from '@openzeppelin/contracts/interfaces/IERC4626.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 {IStataOracle} from './interfaces/IStataOracle.sol'; -import {IERC4626} from './interfaces/IERC4626.sol'; /** * @title StataOracle diff --git a/src/periphery/contracts/static-a-token/StaticATokenLM.sol b/src/periphery/contracts/static-a-token/StaticATokenLM.sol index 80f40ef4..d546fbb5 100644 --- a/src/periphery/contracts/static-a-token/StaticATokenLM.sol +++ b/src/periphery/contracts/static-a-token/StaticATokenLM.sol @@ -1,5 +1,15 @@ // SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.10; +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} from 'openzeppelin-contracts-upgradeable/contracts/token/ERC20/extensions/ERC4626Upgradeable.sol'; +import {IERC4626} from 'openzeppelin-contracts/contracts/interfaces/IERC4626.sol'; +import {IERC20} from 'openzeppelin-contracts/contracts/interfaces/IERC20.sol'; +import {IERC20Metadata} from 'openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.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'; @@ -10,21 +20,13 @@ import {MathUtils} from '../../../core/contracts/protocol/libraries/math/MathUti import {IACLManager} from '../../../core/contracts/interfaces/IACLManager.sol'; import {IRewardsController} from '../rewards/interfaces/IRewardsController.sol'; import {SafeCast} from 'solidity-utils/contracts/oz-common/SafeCast.sol'; -import {SafeERC20} from 'solidity-utils/contracts/oz-common/SafeERC20.sol'; -import {IERC20Metadata} from 'solidity-utils/contracts/oz-common/interfaces/IERC20Metadata.sol'; -import {IERC20} from 'solidity-utils/contracts/oz-common/interfaces/IERC20.sol'; -import {IERC20WithPermit} from 'solidity-utils/contracts/oz-common/interfaces/IERC20WithPermit.sol'; import {IRescuable, Rescuable} from 'solidity-utils/contracts/utils/Rescuable.sol'; import {IStaticATokenLM} from './interfaces/IStaticATokenLM.sol'; import {IAToken} from './interfaces/IAToken.sol'; -import {ERC20} from '../dependencies/solmate/ERC20.sol'; import {IInitializableStaticATokenLM} from './interfaces/IInitializableStaticATokenLM.sol'; import {StaticATokenErrors} from './StaticATokenErrors.sol'; import {RayMathExplicitRounding, Rounding} from '../libraries/RayMathExplicitRounding.sol'; -import {IERC4626} from './interfaces/IERC4626.sol'; -import {PausableUpgradeable} from 'openzeppelin-contracts-upgradeable/contracts/utils/PausableUpgradeable.sol'; -import {DeprecationGap} from './DeprecationGap.sol'; /** * @title StaticATokenLM @@ -34,37 +36,29 @@ import {DeprecationGap} from './DeprecationGap.sol'; * @author BGD labs */ contract StaticATokenLM is - DeprecationGap, - ERC20('STATIC__aToken_IMPL', 'STATIC__aToken_IMPL', 18), + ERC20Upgradeable, + ERC20PermitUpgradeable, + ERC20PausableUpgradeable, + ERC4626Upgradeable, IStaticATokenLM, - Rescuable, - PausableUpgradeable + Rescuable { using SafeERC20 for IERC20; using SafeCast for uint256; using WadRayMath for uint256; using RayMathExplicitRounding for uint256; - bytes32 public constant METADEPOSIT_TYPEHASH = - keccak256( - 'Deposit(address depositor,address receiver,uint256 assets,uint16 referralCode,bool depositToAave,uint256 nonce,uint256 deadline)' - ); - bytes32 public constant METAWITHDRAWAL_TYPEHASH = - keccak256( - 'Withdraw(address owner,address receiver,uint256 shares,uint256 assets,bool withdrawFromAave,uint256 nonce,uint256 deadline)' - ); - - uint256 public constant STATIC__ATOKEN_LM_REVISION = 3; - IPool public immutable POOL; IPoolAddressesProvider immutable POOL_ADDRESSES_PROVIDER; IRewardsController public immutable INCENTIVES_CONTROLLER; IERC20 internal _aToken; address internal _aTokenUnderlying; + uint8 internal _decimals; address[] internal _rewardTokens; - mapping(address => RewardIndexCache) internal _startIndex; - mapping(address => mapping(address => UserRewardsData)) internal _userRewardsData; + mapping(address user => RewardIndexCache cache) internal _startIndex; + mapping(address user => mapping(address reward => UserRewardsData cache)) + internal _userRewardsData; constructor(IPool pool, IRewardsController rewardsController) { _disableInitializers(); @@ -73,15 +67,6 @@ contract StaticATokenLM is POOL_ADDRESSES_PROVIDER = pool.ADDRESSES_PROVIDER(); } - modifier onlyPauseGuardian() { - if (!canPause(msg.sender)) revert OnlyPauseGuardian(msg.sender); - _; - } - - function canPause(address actor) public view returns (bool) { - return IACLManager(POOL_ADDRESSES_PROVIDER.getACLManager()).isEmergencyAdmin(actor); - } - ///@inheritdoc IInitializableStaticATokenLM function initialize( address newAToken, @@ -89,11 +74,10 @@ contract StaticATokenLM is string calldata staticATokenSymbol ) external initializer { require(IAToken(newAToken).POOL() == address(POOL)); + __ERC20_init(staticATokenName, staticATokenSymbol); + __ERC20Permit_init(staticATokenName); _aToken = IERC20(newAToken); - - name = staticATokenName; - symbol = staticATokenSymbol; - decimals = IERC20Metadata(newAToken).decimals(); + _decimals = IERC20Metadata(address(_aToken)).decimals(); _aTokenUnderlying = IAToken(newAToken).UNDERLYING_ASSET_ADDRESS(); IERC20(_aTokenUnderlying).forceApprove(address(POOL), type(uint256).max); @@ -105,28 +89,25 @@ contract StaticATokenLM is emit Initialized(newAToken, staticATokenName, staticATokenSymbol); } - /// @inheritdoc IRescuable - function whoCanRescue() public view override returns (address) { - return POOL_ADDRESSES_PROVIDER.getACLAdmin(); + modifier onlyPauseGuardian() { + if (!canPause(msg.sender)) revert OnlyPauseGuardian(msg.sender); + _; } ///@inheritdoc IStaticATokenLM - function setPaused(bool paused) external onlyPauseGuardian { - if (paused) _pause(); - else _unpause(); + function canPause(address actor) public view returns (bool) { + return IACLManager(POOL_ADDRESSES_PROVIDER.getACLManager()).isEmergencyAdmin(actor); } - ///@inheritdoc IStaticATokenLM - function refreshRewardTokens() public override { - address[] memory rewards = INCENTIVES_CONTROLLER.getRewardsByAsset(address(_aToken)); - for (uint256 i = 0; i < rewards.length; i++) { - _registerRewardToken(rewards[i]); - } + /// @inheritdoc IRescuable + function whoCanRescue() public view override returns (address) { + return POOL_ADDRESSES_PROVIDER.getACLAdmin(); } - ///@inheritdoc IStaticATokenLM - function isRegisteredRewardToken(address reward) public view override returns (bool) { - return _startIndex[reward].isRegistered; + ///@inheritdoc IERC4626 + function deposit(uint256 assets, address receiver) public override returns (uint256) { + (uint256 shares, ) = _deposit(msg.sender, receiver, 0, assets, 0, true); + return shares; } ///@inheritdoc IStaticATokenLM @@ -140,146 +121,43 @@ contract StaticATokenLM is return shares; } - ///@inheritdoc IStaticATokenLM - function metaDeposit( - address depositor, - address receiver, - uint256 assets, - uint16 referralCode, - bool depositToAave, - uint256 deadline, - PermitParams calldata permit, - SignatureParams calldata sigParams - ) external returns (uint256) { - require(depositor != address(0), StaticATokenErrors.INVALID_DEPOSITOR); - //solium-disable-next-line - require(deadline >= block.timestamp, StaticATokenErrors.INVALID_EXPIRATION); - uint256 nonce = nonces[depositor]; - - // Unchecked because the only math done is incrementing - // the owner's nonce which cannot realistically overflow. - unchecked { - bytes32 digest = keccak256( - abi.encodePacked( - '\x19\x01', - DOMAIN_SEPARATOR(), - keccak256( - abi.encode( - METADEPOSIT_TYPEHASH, - depositor, - receiver, - assets, - referralCode, - depositToAave, - nonce, - deadline - ) - ) - ) - ); - nonces[depositor] = nonce + 1; - require( - depositor == ecrecover(digest, sigParams.v, sigParams.r, sigParams.s), - StaticATokenErrors.INVALID_SIGNATURE - ); - } - // assume if deadline 0 no permit was supplied - if (permit.deadline != 0) { - try - IERC20WithPermit(depositToAave ? address(_aTokenUnderlying) : address(_aToken)).permit( - depositor, - address(this), - permit.value, - permit.deadline, - permit.v, - permit.r, - permit.s - ) - {} catch {} - } - (uint256 shares, ) = _deposit(depositor, receiver, 0, assets, referralCode, depositToAave); - return shares; - } - - ///@inheritdoc IStaticATokenLM - function metaWithdraw( - address owner, - address receiver, - uint256 shares, - uint256 assets, - bool withdrawFromAave, - uint256 deadline, - SignatureParams calldata sigParams - ) external returns (uint256, uint256) { - require(owner != address(0), StaticATokenErrors.INVALID_OWNER); - //solium-disable-next-line - require(deadline >= block.timestamp, StaticATokenErrors.INVALID_EXPIRATION); - uint256 nonce = nonces[owner]; - // Unchecked because the only math done is incrementing - // the owner's nonce which cannot realistically overflow. - unchecked { - bytes32 digest = keccak256( - abi.encodePacked( - '\x19\x01', - DOMAIN_SEPARATOR(), - keccak256( - abi.encode( - METAWITHDRAWAL_TYPEHASH, - owner, - receiver, - shares, - assets, - withdrawFromAave, - nonce, - deadline - ) - ) - ) - ); - nonces[owner] = nonce + 1; - require( - owner == ecrecover(digest, sigParams.v, sigParams.r, sigParams.s), - StaticATokenErrors.INVALID_SIGNATURE - ); - } - return _withdraw(owner, receiver, shares, assets, withdrawFromAave); - } - ///@inheritdoc IERC4626 - function previewRedeem(uint256 shares) public view virtual returns (uint256) { - return _convertToAssets(shares, Rounding.DOWN); - } + function mint(uint256 shares, address receiver) public override returns (uint256) { + (, uint256 assets) = _deposit(msg.sender, receiver, shares, 0, 0, true); - ///@inheritdoc IERC4626 - function previewMint(uint256 shares) public view virtual returns (uint256) { - return _convertToAssets(shares, Rounding.UP); + return assets; } ///@inheritdoc IERC4626 - function previewWithdraw(uint256 assets) public view virtual returns (uint256) { - return _convertToShares(assets, Rounding.UP); + function withdraw( + uint256 assets, + address receiver, + address owner + ) public override returns (uint256) { + (uint256 shares, ) = _withdraw(owner, receiver, 0, assets, true); + + return shares; } ///@inheritdoc IERC4626 - function previewDeposit(uint256 assets) public view virtual returns (uint256) { - return _convertToShares(assets, Rounding.DOWN); - } + function redeem( + uint256 shares, + address receiver, + address owner + ) public override returns (uint256) { + (, uint256 assets) = _withdraw(owner, receiver, shares, 0, true); - ///@inheritdoc IStaticATokenLM - function rate() public view returns (uint256) { - return POOL.getReserveNormalizedIncome(_aTokenUnderlying); + return assets; } ///@inheritdoc IStaticATokenLM - function collectAndUpdateRewards(address reward) public returns (uint256) { - if (reward == address(0)) { - return 0; - } - - address[] memory assets = new address[](1); - assets[0] = address(_aToken); - - return INCENTIVES_CONTROLLER.claimRewards(assets, type(uint256).max, address(this), reward); + function redeem( + uint256 shares, + address receiver, + address owner, + bool withdrawFromAave + ) external returns (uint256, uint256) { + return _withdraw(owner, receiver, shares, 0, withdrawFromAave); } ///@inheritdoc IStaticATokenLM @@ -305,6 +183,42 @@ contract StaticATokenLM is _claimRewardsOnBehalf(msg.sender, msg.sender, rewards); } + /// @inheritdoc IERC20Metadata + function decimals() public view override(ERC20Upgradeable, ERC4626Upgradeable) returns (uint8) { + return _decimals; + } + + ///@inheritdoc IStaticATokenLM + function setPaused(bool paused) external onlyPauseGuardian { + if (paused) _pause(); + else _unpause(); + } + + ///@inheritdoc IStaticATokenLM + function refreshRewardTokens() public override { + address[] memory rewards = INCENTIVES_CONTROLLER.getRewardsByAsset(address(_aToken)); + for (uint256 i = 0; i < rewards.length; i++) { + _registerRewardToken(rewards[i]); + } + } + + ///@inheritdoc IStaticATokenLM + function collectAndUpdateRewards(address reward) public returns (uint256) { + if (reward == address(0)) { + return 0; + } + + address[] memory assets = new address[](1); + assets[0] = address(_aToken); + + return INCENTIVES_CONTROLLER.claimRewards(assets, type(uint256).max, address(this), reward); + } + + ///@inheritdoc IStaticATokenLM + function isRegisteredRewardToken(address reward) public view override returns (bool) { + return _startIndex[reward].isRegistered; + } + ///@inheritdoc IStaticATokenLM function getCurrentRewardsIndex(address reward) public view returns (uint256) { if (address(reward) == address(0)) { @@ -328,7 +242,7 @@ contract StaticATokenLM is ///@inheritdoc IStaticATokenLM function getClaimableRewards(address user, address reward) external view returns (uint256) { - return _getClaimableRewards(user, reward, balanceOf[user], getCurrentRewardsIndex(reward)); + return _getClaimableRewards(user, reward, balanceOf(user), getCurrentRewardsIndex(reward)); } ///@inheritdoc IStaticATokenLM @@ -336,11 +250,21 @@ contract StaticATokenLM is return _userRewardsData[user][reward].unclaimedRewards; } + ///@inheritdoc IStaticATokenLM + function rate() public view returns (uint256) { + return POOL.getReserveNormalizedIncome(_aTokenUnderlying); + } + ///@inheritdoc IERC4626 - function asset() external view returns (address) { + function asset() public view override returns (address) { return address(_aTokenUnderlying); } + ///@inheritdoc IERC4626 + function totalAssets() public view override returns (uint256) { + return _aToken.balanceOf(address(this)); + } + ///@inheritdoc IStaticATokenLM function aToken() external view returns (IERC20) { return _aToken; @@ -352,37 +276,32 @@ contract StaticATokenLM is } ///@inheritdoc IERC4626 - function totalAssets() external view returns (uint256) { - return _aToken.balanceOf(address(this)); - } - - ///@inheritdoc IERC4626 - function convertToShares(uint256 assets) external view returns (uint256) { + function convertToShares(uint256 assets) public view override returns (uint256) { return _convertToShares(assets, Rounding.DOWN); } ///@inheritdoc IERC4626 - function convertToAssets(uint256 shares) external view returns (uint256) { + function convertToAssets(uint256 shares) public view override returns (uint256) { return _convertToAssets(shares, Rounding.DOWN); } ///@inheritdoc IERC4626 - function maxMint(address) public view virtual returns (uint256) { + 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, Rounding.DOWN); } ///@inheritdoc IERC4626 - function maxWithdraw(address owner) public view virtual returns (uint256) { + function maxWithdraw(address owner) public view override returns (uint256) { uint256 shares = maxRedeem(owner); return _convertToAssets(shares, Rounding.DOWN); } ///@inheritdoc IERC4626 - function maxRedeem(address owner) public view virtual returns (uint256) { + function maxRedeem(address owner) public view override returns (uint256) { address cachedATokenUnderlying = _aTokenUnderlying; - DataTypes.ReserveDataLegacy memory reserveData = POOL.getReserveData(cachedATokenUnderlying); + DataTypes.ReserveData memory reserveData = POOL.getReserveDataExtended(cachedATokenUnderlying); // if paused or inactive users cannot withdraw underlying if ( @@ -394,10 +313,10 @@ contract StaticATokenLM is // otherwise users can withdraw up to the available amount uint256 underlyingTokenBalanceInShares = _convertToShares( - IERC20(cachedATokenUnderlying).balanceOf(reserveData.aTokenAddress), + reserveData.virtualUnderlyingBalance, Rounding.DOWN ); - uint256 cachedUserBalance = balanceOf[owner]; + uint256 cachedUserBalance = balanceOf(owner); return underlyingTokenBalanceInShares >= cachedUserBalance ? cachedUserBalance @@ -405,7 +324,7 @@ contract StaticATokenLM is } ///@inheritdoc IERC4626 - function maxDeposit(address) public view virtual returns (uint256) { + function maxDeposit(address) public view override returns (uint256) { DataTypes.ReserveDataLegacy memory reserveData = POOL.getReserveData(_aTokenUnderlying); // if inactive, paused or frozen users cannot deposit underlying @@ -427,51 +346,6 @@ contract StaticATokenLM is return currentSupply > supplyCap ? 0 : supplyCap - currentSupply; } - ///@inheritdoc IERC4626 - function deposit(uint256 assets, address receiver) external virtual returns (uint256) { - (uint256 shares, ) = _deposit(msg.sender, receiver, 0, assets, 0, true); - return shares; - } - - ///@inheritdoc IERC4626 - function mint(uint256 shares, address receiver) external virtual returns (uint256) { - (, uint256 assets) = _deposit(msg.sender, receiver, shares, 0, 0, true); - - return assets; - } - - ///@inheritdoc IERC4626 - function withdraw( - uint256 assets, - address receiver, - address owner - ) external virtual returns (uint256) { - (uint256 shares, ) = _withdraw(owner, receiver, 0, assets, true); - - return shares; - } - - ///@inheritdoc IERC4626 - function redeem( - uint256 shares, - address receiver, - address owner - ) external virtual returns (uint256) { - (, uint256 assets) = _withdraw(owner, receiver, shares, 0, true); - - return assets; - } - - ///@inheritdoc IStaticATokenLM - function redeem( - uint256 shares, - address receiver, - address owner, - bool withdrawFromAave - ) external virtual returns (uint256, uint256) { - return _withdraw(owner, receiver, shares, 0, withdrawFromAave); - } - ///@inheritdoc IStaticATokenLM function latestAnswer() external view returns (int256) { return @@ -549,9 +423,7 @@ contract StaticATokenLM is } if (msg.sender != owner) { - uint256 allowed = allowance[owner][msg.sender]; // Saves gas for limited approvals. - - if (allowed != type(uint256).max) allowance[owner][msg.sender] = allowed - shares; + _spendAllowance(owner, msg.sender, shares); } _burn(owner, shares); @@ -572,7 +444,11 @@ contract StaticATokenLM is * @param from The address of the sender of tokens * @param to The address of the receiver of tokens */ - function _beforeTokenTransfer(address from, address to, uint256) internal override whenNotPaused { + function _update( + address from, + address to, + uint256 amount + ) internal override(ERC20Upgradeable, ERC20PausableUpgradeable) whenNotPaused { for (uint256 i = 0; i < _rewardTokens.length; i++) { address rewardToken = address(_rewardTokens[i]); uint256 rewardsIndex = getCurrentRewardsIndex(rewardToken); @@ -583,6 +459,7 @@ contract StaticATokenLM is _updateUser(to, rewardsIndex, rewardToken); } } + super._update(from, to, amount); } /** @@ -592,7 +469,7 @@ contract StaticATokenLM is * @param rewardToken The address of the reward token */ function _updateUser(address user, uint256 currentRewardsIndex, address rewardToken) internal { - uint256 balance = balanceOf[user]; + uint256 balance = balanceOf(user); if (balance > 0) { _userRewardsData[user][rewardToken].unclaimedRewards = _getClaimableRewards( user, @@ -610,19 +487,17 @@ contract StaticATokenLM is * @param balance The balance of the user * @param rewardsIndexOnLastInteraction The index which was on the last interaction of the user * @param currentRewardsIndex The current rewards index in the system - * @param assetUnit One unit of asset (10**decimals) * @return The amount of pending rewards in WAD */ function _getPendingRewards( uint256 balance, uint256 rewardsIndexOnLastInteraction, - uint256 currentRewardsIndex, - uint256 assetUnit - ) internal pure returns (uint256) { + uint256 currentRewardsIndex + ) internal view returns (uint256) { if (balance == 0) { return 0; } - return (balance * (currentRewardsIndex - rewardsIndexOnLastInteraction)) / assetUnit; + return (balance * (currentRewardsIndex - rewardsIndexOnLastInteraction)) / 10 ** decimals(); } /** @@ -642,7 +517,6 @@ contract StaticATokenLM is RewardIndexCache memory rewardsIndexCache = _startIndex[reward]; require(rewardsIndexCache.isRegistered == true, StaticATokenErrors.REWARD_NOT_INITIALIZED); UserRewardsData memory currentUserRewardsData = _userRewardsData[user][reward]; - uint256 assetUnit = 10 ** decimals; return currentUserRewardsData.unclaimedRewards + _getPendingRewards( @@ -650,8 +524,7 @@ contract StaticATokenLM is currentUserRewardsData.rewardsIndexOnLastInteraction == 0 ? rewardsIndexCache.lastUpdatedIndex : currentUserRewardsData.rewardsIndexOnLastInteraction, - currentRewardsIndex, - assetUnit + currentRewardsIndex ); } @@ -671,7 +544,7 @@ contract StaticATokenLM is continue; } uint256 currentRewardsIndex = getCurrentRewardsIndex(rewards[i]); - uint256 balance = balanceOf[onBehalfOf]; + uint256 balance = balanceOf(onBehalfOf); uint256 userReward = _getClaimableRewards( onBehalfOf, rewards[i], diff --git a/src/periphery/contracts/static-a-token/interfaces/IERC4626.sol b/src/periphery/contracts/static-a-token/interfaces/IERC4626.sol deleted file mode 100644 index 08f14f90..00000000 --- a/src/periphery/contracts/static-a-token/interfaces/IERC4626.sol +++ /dev/null @@ -1,241 +0,0 @@ -// SPDX-License-Identifier: MIT -// OpenZeppelin Contracts (last updated v4.7.0) (interfaces/IERC4626.sol) - -pragma solidity ^0.8.10; - -/** - * @dev Interface of the ERC4626 "Tokenized Vault Standard", as defined in - * https://eips.ethereum.org/EIPS/eip-4626[ERC-4626]. - * - * _Available since v4.7._ - */ -interface IERC4626 { - event Deposit(address indexed sender, address indexed owner, uint256 assets, uint256 shares); - - event Withdraw( - address indexed sender, - address indexed receiver, - address indexed owner, - uint256 assets, - uint256 shares - ); - - /** - * @dev Returns the address of the underlying token used for the Vault for accounting, depositing, and withdrawing. - * - * - MUST be an ERC-20 token contract. - * - MUST NOT revert. - */ - function asset() external view returns (address assetTokenAddress); - - /** - * @dev Returns the total amount of the underlying asset that is “managed” by Vault. - * - * - SHOULD include any compounding that occurs from yield. - * - MUST be inclusive of any fees that are charged against assets in the Vault. - * - MUST NOT revert. - */ - function totalAssets() external view returns (uint256 totalManagedAssets); - - /** - * @dev Returns the amount of shares that the Vault would exchange for the amount of assets provided, in an ideal - * scenario where all the conditions are met. - * - * - MUST NOT be inclusive of any fees that are charged against assets in the Vault. - * - MUST NOT show any variations depending on the caller. - * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. - * - MUST NOT revert. - * - * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the - * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and - * from. - */ - function convertToShares(uint256 assets) external view returns (uint256 shares); - - /** - * @dev Returns the amount of assets that the Vault would exchange for the amount of shares provided, in an ideal - * scenario where all the conditions are met. - * - * - MUST NOT be inclusive of any fees that are charged against assets in the Vault. - * - MUST NOT show any variations depending on the caller. - * - MUST NOT reflect slippage or other on-chain conditions, when performing the actual exchange. - * - MUST NOT revert unless due to integer overflow caused by an unreasonably large input. - * - * NOTE: This calculation MAY NOT reflect the “per-user” price-per-share, and instead should reflect the - * “average-user’s” price-per-share, meaning what the average user should expect to see when exchanging to and - * from. - */ - function convertToAssets(uint256 shares) external view returns (uint256 assets); - - /** - * @dev Returns the maximum amount of the underlying asset that can be deposited into the Vault for the receiver, - * through a deposit call. - * While deposit of aToken is not affected by aave pool configrations, deposit of the aTokenUnderlying will need to deposit to aave - * so it is affected by current aave pool configuration. - * Reference: https://github.com/aave/aave-v3-core/blob/29ff9b9f89af7cd8255231bc5faf26c3ce0fb7ce/contracts/protocol/libraries/logic/ValidationLogic.sol#L57 - * - MUST return a limited value if receiver is subject to some deposit limit. - * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of assets that may be deposited. - * - MUST NOT revert unless due to integer overflow caused by an unreasonably large input. - */ - function maxDeposit(address receiver) external view returns (uint256 maxAssets); - - /** - * @dev Allows an on-chain or off-chain user to simulate the effects of their deposit at the current block, given - * current on-chain conditions. - * - * - MUST return as close to and no more than the exact amount of Vault shares that would be minted in a deposit - * call in the same transaction. I.e. deposit should return the same or more shares as previewDeposit if called - * in the same transaction. - * - MUST NOT account for deposit limits like those returned from maxDeposit and should always act as though the - * deposit would be accepted, regardless if the user has enough tokens approved, etc. - * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. - * - MUST NOT revert. - * - * NOTE: any unfavorable discrepancy between convertToShares and previewDeposit SHOULD be considered slippage in - * share price or some other type of condition, meaning the depositor will lose assets by depositing. - */ - function previewDeposit(uint256 assets) external view returns (uint256 shares); - - /** - * @dev Mints shares Vault shares to receiver by depositing exactly amount of underlying tokens. - * - * - MUST emit the Deposit event. - * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the - * deposit execution, and are accounted for during deposit. - * - MUST revert if all of assets cannot be deposited (due to deposit limit being reached, slippage, the user not - * approving enough underlying tokens to the Vault contract, etc). - * - * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. - */ - function deposit(uint256 assets, address receiver) external returns (uint256 shares); - - /** - * @dev Returns the maximum amount of the Vault shares that can be minted for the receiver, through a mint call. - * - MUST return a limited value if receiver is subject to some mint limit. - * - MUST return 2 ** 256 - 1 if there is no limit on the maximum amount of shares that may be minted. - * - MUST NOT revert. - */ - function maxMint(address receiver) external view returns (uint256 maxShares); - - /** - * @dev Allows an on-chain or off-chain user to simulate the effects of their mint at the current block, given - * current on-chain conditions. - * - * - MUST return as close to and no fewer than the exact amount of assets that would be deposited in a mint call - * in the same transaction. I.e. mint should return the same or fewer assets as previewMint if called in the - * same transaction. - * - MUST NOT account for mint limits like those returned from maxMint and should always act as though the mint - * would be accepted, regardless if the user has enough tokens approved, etc. - * - MUST be inclusive of deposit fees. Integrators should be aware of the existence of deposit fees. - * - MUST NOT revert. - * - * NOTE: any unfavorable discrepancy between convertToAssets and previewMint SHOULD be considered slippage in - * share price or some other type of condition, meaning the depositor will lose assets by minting. - */ - function previewMint(uint256 shares) external view returns (uint256 assets); - - /** - * @dev Mints exactly shares Vault shares to receiver by depositing amount of underlying tokens. - * - * - MUST emit the Deposit event. - * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the mint - * execution, and are accounted for during mint. - * - MUST revert if all of shares cannot be minted (due to deposit limit being reached, slippage, the user not - * approving enough underlying tokens to the Vault contract, etc). - * - * NOTE: most implementations will require pre-approval of the Vault with the Vault’s underlying asset token. - */ - function mint(uint256 shares, address receiver) external returns (uint256 assets); - - /** - * @dev Returns the maximum amount of the underlying asset that can be withdrawn from the owner balance in the - * Vault, through a withdraw call. - * - * - MUST return a limited value if owner is subject to some withdrawal limit or timelock. - * - MUST NOT revert. - */ - function maxWithdraw(address owner) external view returns (uint256 maxAssets); - - /** - * @dev Allows an on-chain or off-chain user to simulate the effects of their withdrawal at the current block, - * given current on-chain conditions. - * - * - MUST return as close to and no fewer than the exact amount of Vault shares that would be burned in a withdraw - * call in the same transaction. I.e. withdraw should return the same or fewer shares as previewWithdraw if - * called - * in the same transaction. - * - MUST NOT account for withdrawal limits like those returned from maxWithdraw and should always act as though - * the withdrawal would be accepted, regardless if the user has enough shares, etc. - * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. - * - MUST NOT revert. - * - * NOTE: any unfavorable discrepancy between convertToShares and previewWithdraw SHOULD be considered slippage in - * share price or some other type of condition, meaning the depositor will lose assets by depositing. - */ - function previewWithdraw(uint256 assets) external view returns (uint256 shares); - - /** - * @dev Burns shares from owner and sends exactly assets of underlying tokens to receiver. - * - * - MUST emit the Withdraw event. - * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the - * withdraw execution, and are accounted for during withdraw. - * - MUST revert if all of assets cannot be withdrawn (due to withdrawal limit being reached, slippage, the owner - * not having enough shares, etc). - * - * Note that some implementations will require pre-requesting to the Vault before a withdrawal may be performed. - * Those methods should be performed separately. - */ - function withdraw( - uint256 assets, - address receiver, - address owner - ) external returns (uint256 shares); - - /** - * @dev Returns the maximum amount of Vault shares that can be redeemed from the owner balance in the Vault, - * through a redeem call to the aToken underlying. - * While redeem of aToken is not affected by aave pool configrations, redeeming of the aTokenUnderlying will need to redeem from aave - * so it is affected by current aave pool configuration. - * Reference: https://github.com/aave/aave-v3-core/blob/29ff9b9f89af7cd8255231bc5faf26c3ce0fb7ce/contracts/protocol/libraries/logic/ValidationLogic.sol#L87 - * - MUST return a limited value if owner is subject to some withdrawal limit or timelock. - * - MUST return balanceOf(owner) if owner is not subject to any withdrawal limit or timelock. - * - MUST NOT revert. - */ - function maxRedeem(address owner) external view returns (uint256 maxShares); - - /** - * @dev Allows an on-chain or off-chain user to simulate the effects of their redeemption at the current block, - * given current on-chain conditions. - * - * - MUST return as close to and no more than the exact amount of assets that would be withdrawn in a redeem call - * in the same transaction. I.e. redeem should return the same or more assets as previewRedeem if called in the - * same transaction. - * - MUST NOT account for redemption limits like those returned from maxRedeem and should always act as though the - * redemption would be accepted, regardless if the user has enough shares, etc. - * - MUST be inclusive of withdrawal fees. Integrators should be aware of the existence of withdrawal fees. - * - MUST NOT revert. - * - * NOTE: any unfavorable discrepancy between convertToAssets and previewRedeem SHOULD be considered slippage in - * share price or some other type of condition, meaning the depositor will lose assets by redeeming. - */ - function previewRedeem(uint256 shares) external view returns (uint256 assets); - - /** - * @dev Burns exactly shares from owner and sends assets of underlying tokens to receiver. - * - * - MUST emit the Withdraw event. - * - MAY support an additional flow in which the underlying tokens are owned by the Vault contract before the - * redeem execution, and are accounted for during redeem. - * - MUST revert if all of shares cannot be redeemed (due to withdrawal limit being reached, slippage, the owner - * not having enough shares, etc). - * - * NOTE: some implementations will require pre-requesting to the Vault before a withdrawal may be performed. - * Those methods should be performed separately. - */ - function redeem( - uint256 shares, - address receiver, - address owner - ) external returns (uint256 assets); -} diff --git a/src/periphery/contracts/static-a-token/interfaces/IStaticATokenLM.sol b/src/periphery/contracts/static-a-token/interfaces/IStaticATokenLM.sol index 2fbdd9cf..026cad99 100644 --- a/src/periphery/contracts/static-a-token/interfaces/IStaticATokenLM.sol +++ b/src/periphery/contracts/static-a-token/interfaces/IStaticATokenLM.sol @@ -1,11 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.10; -import {IERC20} from 'solidity-utils/contracts/oz-common/interfaces/IERC20.sol'; -import {IERC4626} from './IERC4626.sol'; +import {IERC20} from 'openzeppelin-contracts/contracts/interfaces/IERC20.sol'; import {IInitializableStaticATokenLM} from './IInitializableStaticATokenLM.sol'; -interface IStaticATokenLM is IInitializableStaticATokenLM, IERC4626 { +interface IStaticATokenLM is IInitializableStaticATokenLM { struct SignatureParams { uint8 v; bytes32 r; @@ -69,57 +68,6 @@ interface IStaticATokenLM is IInitializableStaticATokenLM, IERC4626 { bool depositToAave ) external returns (uint256); - /** - * @notice Allows to deposit on Aave via meta-transaction - * https://github.com/ethereum/EIPs/blob/8a34d644aacf0f9f8f00815307fd7dd5da07655f/EIPS/eip-2612.md - * @param depositor Address from which the funds to deposit are going to be pulled - * @param receiver Address that will receive the staticATokens, in the average case, same as the `depositor` - * @param assets The amount to deposit - * @param referralCode Code used to register the integrator originating the operation, for potential rewards. - * 0 if the action is executed directly by the user, without any middle-man - * @param depositToAave bool - * - `true` if the msg.sender comes with underlying tokens (e.g. USDC) - * - `false` if the msg.sender comes already with aTokens (e.g. aUSDC) - * @param deadline The deadline timestamp, type(uint256).max for max deadline - * @param sigParams Signature params: v,r,s - * @return uint256 The amount of StaticAToken minted, static balance - */ - function metaDeposit( - address depositor, - address receiver, - uint256 assets, - uint16 referralCode, - bool depositToAave, - uint256 deadline, - PermitParams calldata permit, - SignatureParams calldata sigParams - ) external returns (uint256); - - /** - * @notice Allows to withdraw from Aave via meta-transaction - * https://github.com/ethereum/EIPs/blob/8a34d644aacf0f9f8f00815307fd7dd5da07655f/EIPS/eip-2612.md - * @param owner Address owning the staticATokens - * @param receiver Address that will receive the underlying withdrawn from Aave - * @param shares The amount of staticAToken to withdraw. If > 0, `assets` needs to be 0 - * @param assets The amount of underlying/aToken to withdraw. If > 0, `shares` needs to be 0 - * @param withdrawFromAave bool - * - `true` for the receiver to get underlying tokens (e.g. USDC) - * - `false` for the receiver to get aTokens (e.g. aUSDC) - * @param deadline The deadline timestamp, type(uint256).max for max deadline - * @param sigParams Signature params: v,r,s - * @return amountToBurn: StaticATokens burnt, static balance - * @return amountToWithdraw: underlying/aToken send to `receiver`, dynamic balance - */ - function metaWithdraw( - address owner, - address receiver, - uint256 shares, - uint256 assets, - bool withdrawFromAave, - uint256 deadline, - SignatureParams calldata sigParams - ) external returns (uint256, uint256); - /** * @notice Returns the Aave liquidity index of the underlying aToken, denominated rate here * as it can be considered as an ever-increasing exchange rate @@ -214,6 +162,13 @@ interface IStaticATokenLM is IInitializableStaticATokenLM, IERC4626 { */ function isRegisteredRewardToken(address reward) external view returns (bool); + /** + * @notice Checks if the passed actor is permissioned emergency admin. + * @param actor The reward to claim + * @return bool signaling if actor can pause the vault. + */ + function canPause(address actor) external view returns (bool); + /** * @notice Pauses/unpauses all system's operations * @param paused boolean determining if the token should be paused or unpaused diff --git a/tests/periphery/static-a-token/Pausable.t.sol b/tests/periphery/static-a-token/Pausable.t.sol index 966a33ac..59a24dec 100644 --- a/tests/periphery/static-a-token/Pausable.t.sol +++ b/tests/periphery/static-a-token/Pausable.t.sol @@ -15,7 +15,7 @@ import {IStaticATokenLM} from '../../../src/periphery/contracts/static-a-token/i import {SigUtils} from '../../utils/SigUtils.sol'; import {BaseTest, TestnetERC20} from './TestBase.sol'; -contract Pausable is BaseTest { +contract StataPausableTest is BaseTest { using RayMathExplicitRounding for uint256; function test_setPaused_shouldRevertForInvalidCaller(address actor) external { diff --git a/tests/periphery/static-a-token/Rewards.t.sol b/tests/periphery/static-a-token/Rewards.t.sol index e21c00b2..36a13dd8 100644 --- a/tests/periphery/static-a-token/Rewards.t.sol +++ b/tests/periphery/static-a-token/Rewards.t.sol @@ -5,7 +5,7 @@ import {AToken} from '../../../src/core/contracts/protocol/tokenization/AToken.s import {IERC20} from '../../../src/periphery/contracts/static-a-token/StaticATokenLM.sol'; import {BaseTest} from './TestBase.sol'; -contract StataTokenRewardsTest is BaseTest { +contract StataRewardsTest is BaseTest { function setUp() public override { super.setUp(); @@ -153,4 +153,47 @@ contract StataTokenRewardsTest is BaseTest { assertEq(staticATokenLM.getTotalClaimableRewards(REWARD_TOKEN), 0); assertGe(AToken(UNDERLYING).balanceOf(user), 5 ether); } + + function test_transfer() public { + uint128 amountToDeposit = 10 ether; + _fundUser(amountToDeposit, user); + + _depositAToken(amountToDeposit, user); + + // transfer to 2nd user + staticATokenLM.transfer(user1, amountToDeposit / 2); + assertEq(staticATokenLM.getClaimableRewards(user1, REWARD_TOKEN), 0); + + // forward time + _skipBlocks(60); + + // redeem for both + uint256 claimableUser = staticATokenLM.getClaimableRewards(user, REWARD_TOKEN); + staticATokenLM.redeem(staticATokenLM.maxRedeem(user), user, user); + staticATokenLM.claimRewardsToSelf(rewardTokens); + assertEq(IERC20(REWARD_TOKEN).balanceOf(user), claimableUser); + vm.stopPrank(); + vm.startPrank(user1); + uint256 claimableUser1 = staticATokenLM.getClaimableRewards(user1, REWARD_TOKEN); + staticATokenLM.redeem(staticATokenLM.maxRedeem(user1), user1, user1); + staticATokenLM.claimRewardsToSelf(rewardTokens); + assertEq(IERC20(REWARD_TOKEN).balanceOf(user1), claimableUser1); + assertGt(claimableUser1, 0); + + assertEq(staticATokenLM.getTotalClaimableRewards(REWARD_TOKEN), 0); + assertEq(staticATokenLM.getClaimableRewards(user, REWARD_TOKEN), 0); + assertEq(staticATokenLM.getClaimableRewards(user1, REWARD_TOKEN), 0); + } + + // getUnclaimedRewards + function test_getUnclaimedRewards() public { + uint128 amountToDeposit = 5 ether; + _fundUser(amountToDeposit, user); + + uint256 shares = _depositAToken(amountToDeposit, user); + assertEq(staticATokenLM.getUnclaimedRewards(user, REWARD_TOKEN), 0); + _skipBlocks(1000); + staticATokenLM.redeem(shares, user, user); + assertGt(staticATokenLM.getUnclaimedRewards(user, REWARD_TOKEN), 0); + } } diff --git a/tests/periphery/static-a-token/StataOracle.t.sol b/tests/periphery/static-a-token/StataOracle.t.sol index 3d6b6622..df569d9c 100644 --- a/tests/periphery/static-a-token/StataOracle.t.sol +++ b/tests/periphery/static-a-token/StataOracle.t.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.10; import {StataOracle} from '../../../src/periphery/contracts/static-a-token/StataOracle.sol'; import {StaticATokenLM} from '../../../src/periphery/contracts/static-a-token/StaticATokenLM.sol'; import {BaseTest} from './TestBase.sol'; +import {IPool} from '../../../src/core/contracts/interfaces/IPool.sol'; contract StataOracleTest is BaseTest { StataOracle public oracle; @@ -16,6 +17,7 @@ contract StataOracleTest is BaseTest { contracts.poolConfiguratorProxy.setSupplyCap(UNDERLYING, 1_000_000); } + // ### tests for the dedicated oracle aggregator function test_assetPrice() public view { uint256 stataPrice = oracle.getAssetPrice(address(staticATokenLM)); uint256 underlyingPrice = contracts.aaveOracle.getAssetPrice(UNDERLYING); @@ -54,4 +56,33 @@ contract StataOracleTest is BaseTest { (assets / 1e18) + 1 // there can be imprecision of 1 wei, which will accumulate for each asset ); } + + // ### tests for the token internal oracle + function test_latestAnswer_priceShouldBeEqualOnDefaultIndex() public { + vm.mockCall( + address(POOL), + abi.encodeWithSelector(IPool.getReserveNormalizedIncome.selector), + abi.encode(1e27) + ); + uint256 stataPrice = uint256(staticATokenLM.latestAnswer()); + uint256 underlyingPrice = contracts.aaveOracle.getAssetPrice(UNDERLYING); + assertEq(stataPrice, underlyingPrice); + } + + function test_latestAnswer_priceShouldReflectIndexAccrual(uint256 liquidityIndex) public { + liquidityIndex = bound(liquidityIndex, 1e27, 1e29); + vm.mockCall( + address(POOL), + abi.encodeWithSelector(IPool.getReserveNormalizedIncome.selector), + abi.encode(liquidityIndex) + ); + uint256 stataPrice = uint256(staticATokenLM.latestAnswer()); + uint256 underlyingPrice = contracts.aaveOracle.getAssetPrice(UNDERLYING); + uint256 expectedStataPrice = (underlyingPrice * liquidityIndex) / 1e27; + assertEq(stataPrice, expectedStataPrice); + + // reverse the math to ensure precision loss is within bounds + uint256 reversedUnderlying = (stataPrice * 1e27) / liquidityIndex; + assertApproxEqAbs(underlyingPrice, reversedUnderlying, 1); + } } diff --git a/tests/periphery/static-a-token/StaticATokenLM.t.sol b/tests/periphery/static-a-token/StaticATokenLM.t.sol index ccc4f74b..57009857 100644 --- a/tests/periphery/static-a-token/StaticATokenLM.t.sol +++ b/tests/periphery/static-a-token/StaticATokenLM.t.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.10; import {IRescuable} from 'solidity-utils/contracts/utils/Rescuable.sol'; +import {ERC20PermitUpgradeable} from 'openzeppelin-contracts-upgradeable/contracts/token/ERC20/extensions/ERC20PermitUpgradeable.sol'; import {Initializable} from 'openzeppelin-contracts-upgradeable/contracts/proxy/utils/Initializable.sol'; import {AToken} from '../../../src/core/contracts/protocol/tokenization/AToken.sol'; import {DataTypes} from '../../../src/core/contracts/protocol/libraries/configuration/ReserveConfiguration.sol'; @@ -49,34 +50,6 @@ contract StaticATokenLMTest is BaseTest { ); } - function test_latestAnswer_priceShouldBeEqualOnDefaultIndex() public { - vm.mockCall( - address(POOL), - abi.encodeWithSelector(IPool.getReserveNormalizedIncome.selector), - abi.encode(1e27) - ); - uint256 stataPrice = uint256(staticATokenLM.latestAnswer()); - uint256 underlyingPrice = contracts.aaveOracle.getAssetPrice(UNDERLYING); - assertEq(stataPrice, underlyingPrice); - } - - function test_latestAnswer_priceShouldReflectIndexAccrual(uint256 liquidityIndex) public { - liquidityIndex = bound(liquidityIndex, 1e27, 1e29); - vm.mockCall( - address(POOL), - abi.encodeWithSelector(IPool.getReserveNormalizedIncome.selector), - abi.encode(liquidityIndex) - ); - uint256 stataPrice = uint256(staticATokenLM.latestAnswer()); - uint256 underlyingPrice = contracts.aaveOracle.getAssetPrice(UNDERLYING); - uint256 expectedStataPrice = (underlyingPrice * liquidityIndex) / 1e27; - assertEq(stataPrice, expectedStataPrice); - - // reverse the math to ensure precision loss is within bounds - uint256 reversedUnderlying = (stataPrice * 1e27) / liquidityIndex; - assertApproxEqAbs(underlyingPrice, reversedUnderlying, 1); - } - function test_convertersAndPreviews() public view { uint128 amount = 5 ether; uint256 shares = staticATokenLM.convertToShares(amount); @@ -208,49 +181,6 @@ contract StaticATokenLMTest is BaseTest { staticATokenLM.mint(amountToDeposit, user); } - function test_transfer() public { - uint128 amountToDeposit = 10 ether; - _fundUser(amountToDeposit, user); - - _depositAToken(amountToDeposit, user); - - // transfer to 2nd user - staticATokenLM.transfer(user1, amountToDeposit / 2); - assertEq(staticATokenLM.getClaimableRewards(user1, REWARD_TOKEN), 0); - - // forward time - _skipBlocks(60); - - // redeem for both - uint256 claimableUser = staticATokenLM.getClaimableRewards(user, REWARD_TOKEN); - staticATokenLM.redeem(staticATokenLM.maxRedeem(user), user, user); - staticATokenLM.claimRewardsToSelf(rewardTokens); - assertEq(IERC20(REWARD_TOKEN).balanceOf(user), claimableUser); - vm.stopPrank(); - vm.startPrank(user1); - uint256 claimableUser1 = staticATokenLM.getClaimableRewards(user1, REWARD_TOKEN); - staticATokenLM.redeem(staticATokenLM.maxRedeem(user1), user1, user1); - staticATokenLM.claimRewardsToSelf(rewardTokens); - assertEq(IERC20(REWARD_TOKEN).balanceOf(user1), claimableUser1); - assertGt(claimableUser1, 0); - - assertEq(staticATokenLM.getTotalClaimableRewards(REWARD_TOKEN), 0); - assertEq(staticATokenLM.getClaimableRewards(user, REWARD_TOKEN), 0); - assertEq(staticATokenLM.getClaimableRewards(user1, REWARD_TOKEN), 0); - } - - // getUnclaimedRewards - function test_getUnclaimedRewards() public { - uint128 amountToDeposit = 5 ether; - _fundUser(amountToDeposit, user); - - uint256 shares = _depositAToken(amountToDeposit, user); - assertEq(staticATokenLM.getUnclaimedRewards(user, REWARD_TOKEN), 0); - _skipBlocks(1000); - staticATokenLM.redeem(shares, user, user); - assertGt(staticATokenLM.getUnclaimedRewards(user, REWARD_TOKEN), 0); - } - /** * maxDeposit test */ @@ -398,7 +328,7 @@ contract StaticATokenLMTest is BaseTest { bytes32 permitDigest = SigUtils.getTypedDataHash( permit, - staticATokenLM.PERMIT_TYPEHASH(), + PERMIT_TYPEHASH, staticATokenLM.DOMAIN_SEPARATOR() ); (uint8 v, bytes32 r, bytes32 s) = vm.sign(userPrivateKey, permitDigest); @@ -422,12 +352,17 @@ contract StaticATokenLMTest is BaseTest { bytes32 permitDigest = SigUtils.getTypedDataHash( permit, - staticATokenLM.PERMIT_TYPEHASH(), + PERMIT_TYPEHASH, staticATokenLM.DOMAIN_SEPARATOR() ); (uint8 v, bytes32 r, bytes32 s) = vm.sign(userPrivateKey, permitDigest); - vm.expectRevert('PERMIT_DEADLINE_EXPIRED'); + vm.expectRevert( + abi.encodeWithSelector( + ERC20PermitUpgradeable.ERC2612ExpiredSignature.selector, + permit.deadline + ) + ); staticATokenLM.permit(permit.owner, permit.spender, permit.value, permit.deadline, v, r, s); } @@ -442,12 +377,18 @@ contract StaticATokenLMTest is BaseTest { bytes32 permitDigest = SigUtils.getTypedDataHash( permit, - staticATokenLM.PERMIT_TYPEHASH(), + PERMIT_TYPEHASH, staticATokenLM.DOMAIN_SEPARATOR() ); (uint8 v, bytes32 r, bytes32 s) = vm.sign(userPrivateKey, permitDigest); - vm.expectRevert('INVALID_SIGNER'); + vm.expectRevert( + abi.encodeWithSelector( + ERC20PermitUpgradeable.ERC2612InvalidSigner.selector, + user, + permit.owner + ) + ); staticATokenLM.permit(permit.owner, permit.spender, permit.value, permit.deadline, v, r, s); } diff --git a/tests/periphery/static-a-token/StaticATokenMetaTransactions.t.sol b/tests/periphery/static-a-token/StaticATokenMetaTransactions.t.sol deleted file mode 100644 index dc8c68d2..00000000 --- a/tests/periphery/static-a-token/StaticATokenMetaTransactions.t.sol +++ /dev/null @@ -1,245 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.10; - -import {IERC20WithPermit} from 'solidity-utils/contracts/oz-common/interfaces/IERC20WithPermit.sol'; -import {StaticATokenLM, IStaticATokenLM, IERC20} from '../../../src/periphery/contracts/static-a-token/StaticATokenLM.sol'; -import {SigUtils} from '../../utils/SigUtils.sol'; -import {BaseTest, IAToken, IRewardsController, DataTypes} from './TestBase.sol'; - -contract StaticATokenMetaTransactions is BaseTest { - function setUp() public override { - super.setUp(); - - // Testing meta transactions with USDX as WETH does not support permit - DataTypes.ReserveDataLegacy memory reserveDataUSDX = contracts.poolProxy.getReserveData( - address(usdx) - ); - UNDERLYING = address(usdx); - A_TOKEN = reserveDataUSDX.aTokenAddress; - - staticATokenLM = StaticATokenLM(factory.getStaticAToken(UNDERLYING)); - - vm.startPrank(user); - } - - function test_validateDomainSeparator() public view { - address[] memory staticATokens = factory.getStaticATokens(); - - for (uint256 i = 0; i < staticATokens.length; i++) { - bytes32 separator1 = StaticATokenLM(staticATokens[i]).DOMAIN_SEPARATOR(); - for (uint256 j = 0; j < staticATokens.length; j++) { - if (i != j) { - bytes32 separator2 = StaticATokenLM(staticATokens[j]).DOMAIN_SEPARATOR(); - assertNotEq(separator1, separator2, 'DOMAIN_SEPARATOR_MUST_BE_UNIQUE'); - } - } - } - } - - function test_metaDepositATokenUnderlyingNoPermit() public { - uint128 amountToDeposit = 5e6; - deal(UNDERLYING, user, amountToDeposit); - IERC20(UNDERLYING).approve(address(staticATokenLM), 1e6); - IStaticATokenLM.PermitParams memory permitParams; - - // generate combined permit - SigUtils.MetaDepositParams memory metaDepositParams = SigUtils.MetaDepositParams({ - depositor: user, - receiver: spender, - assets: 1e6, - referralCode: 0, - fromUnderlying: true, - nonce: staticATokenLM.nonces(user), - deadline: block.timestamp + 1 days - }); - bytes32 digest = SigUtils.getTypedDepositHash( - metaDepositParams, - staticATokenLM.METADEPOSIT_TYPEHASH(), - staticATokenLM.DOMAIN_SEPARATOR() - ); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(userPrivateKey, digest); - - IStaticATokenLM.SignatureParams memory sigParams = IStaticATokenLM.SignatureParams(v, r, s); - - uint256 previewDeposit = staticATokenLM.previewDeposit(metaDepositParams.assets); - staticATokenLM.metaDeposit( - metaDepositParams.depositor, - metaDepositParams.receiver, - metaDepositParams.assets, - metaDepositParams.referralCode, - metaDepositParams.fromUnderlying, - metaDepositParams.deadline, - permitParams, - sigParams - ); - - assertEq(staticATokenLM.balanceOf(metaDepositParams.receiver), previewDeposit); - } - - function test_metaDepositATokenUnderlying() public { - uint128 amountToDeposit = 5e6; - deal(UNDERLYING, user, amountToDeposit); - - // permit for aToken deposit - SigUtils.Permit memory permit = SigUtils.Permit({ - owner: user, - spender: address(staticATokenLM), - value: 1e6, - nonce: IERC20WithPermit(UNDERLYING).nonces(user), - deadline: block.timestamp + 1 days - }); - - bytes32 permitDigest = SigUtils.getTypedDataHash( - permit, - staticATokenLM.PERMIT_TYPEHASH(), - IERC20WithPermit(UNDERLYING).DOMAIN_SEPARATOR() - ); - - (uint8 pV, bytes32 pR, bytes32 pS) = vm.sign(userPrivateKey, permitDigest); - - IStaticATokenLM.PermitParams memory permitParams = IStaticATokenLM.PermitParams( - permit.value, - permit.deadline, - pV, - pR, - pS - ); - - // generate combined permit - SigUtils.MetaDepositParams memory metaDepositParams = SigUtils.MetaDepositParams({ - depositor: user, - receiver: spender, - assets: permit.value, - referralCode: 0, - fromUnderlying: true, - nonce: staticATokenLM.nonces(user), - deadline: permit.deadline - }); - (uint8 v, bytes32 r, bytes32 s) = vm.sign( - userPrivateKey, - SigUtils.getTypedDepositHash( - metaDepositParams, - staticATokenLM.METADEPOSIT_TYPEHASH(), - staticATokenLM.DOMAIN_SEPARATOR() - ) - ); - - IStaticATokenLM.SignatureParams memory sigParams = IStaticATokenLM.SignatureParams(v, r, s); - - uint256 previewDeposit = staticATokenLM.previewDeposit(metaDepositParams.assets); - uint256 shares = staticATokenLM.metaDeposit( - metaDepositParams.depositor, - metaDepositParams.receiver, - metaDepositParams.assets, - metaDepositParams.referralCode, - metaDepositParams.fromUnderlying, - metaDepositParams.deadline, - permitParams, - sigParams - ); - assertEq(shares, previewDeposit); - assertEq(staticATokenLM.balanceOf(metaDepositParams.receiver), previewDeposit); - } - - function test_metaDepositAToken() public { - uint128 amountToDeposit = 5e6; - _fundUser(amountToDeposit, user); - _underlyingToAToken(amountToDeposit, user); - - // permit for aToken deposit - SigUtils.Permit memory permit = SigUtils.Permit({ - owner: user, - spender: address(staticATokenLM), - value: 1e6, - nonce: IERC20WithPermit(A_TOKEN).nonces(user), - deadline: block.timestamp + 1 days - }); - - bytes32 permitDigest = SigUtils.getTypedDataHash( - permit, - staticATokenLM.PERMIT_TYPEHASH(), - IERC20WithPermit(A_TOKEN).DOMAIN_SEPARATOR() - ); - - (uint8 pV, bytes32 pR, bytes32 pS) = vm.sign(userPrivateKey, permitDigest); - - IStaticATokenLM.PermitParams memory permitParams = IStaticATokenLM.PermitParams( - permit.value, - permit.deadline, - pV, - pR, - pS - ); - - // generate combined permit - SigUtils.MetaDepositParams memory metaDepositParams = SigUtils.MetaDepositParams({ - depositor: user, - receiver: spender, - assets: permit.value, - referralCode: 0, - fromUnderlying: false, - nonce: staticATokenLM.nonces(user), - deadline: permit.deadline - }); - bytes32 digest = SigUtils.getTypedDepositHash( - metaDepositParams, - staticATokenLM.METADEPOSIT_TYPEHASH(), - staticATokenLM.DOMAIN_SEPARATOR() - ); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(userPrivateKey, digest); - - IStaticATokenLM.SignatureParams memory sigParams = IStaticATokenLM.SignatureParams(v, r, s); - - uint256 previewDeposit = staticATokenLM.previewDeposit(metaDepositParams.assets); - - staticATokenLM.metaDeposit( - metaDepositParams.depositor, - metaDepositParams.receiver, - metaDepositParams.assets, - metaDepositParams.referralCode, - metaDepositParams.fromUnderlying, - metaDepositParams.deadline, - permitParams, - sigParams - ); - - assertEq(staticATokenLM.balanceOf(metaDepositParams.receiver), previewDeposit); - } - - function test_metaWithdraw() public { - uint128 amountToDeposit = 5e6; - _fundUser(amountToDeposit, user); - - _depositAToken(amountToDeposit, user); - - SigUtils.MetaWithdrawParams memory permit = SigUtils.MetaWithdrawParams({ - owner: user, - spender: spender, - staticAmount: 0, - dynamicAmount: 1e6, - toUnderlying: false, - nonce: staticATokenLM.nonces(user), - deadline: block.timestamp + 1 days - }); - bytes32 digest = SigUtils.getTypedWithdrawHash( - permit, - staticATokenLM.METAWITHDRAWAL_TYPEHASH(), - staticATokenLM.DOMAIN_SEPARATOR() - ); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(userPrivateKey, digest); - - IStaticATokenLM.SignatureParams memory sigParams = IStaticATokenLM.SignatureParams(v, r, s); - - staticATokenLM.metaWithdraw( - permit.owner, - permit.spender, - permit.staticAmount, - permit.dynamicAmount, - permit.toUnderlying, - permit.deadline, - sigParams - ); - - assertEq(IERC20(A_TOKEN).balanceOf(permit.spender), permit.dynamicAmount); - } -} diff --git a/tests/periphery/static-a-token/TestBase.sol b/tests/periphery/static-a-token/TestBase.sol index b20aab9c..aa00e0ca 100644 --- a/tests/periphery/static-a-token/TestBase.sol +++ b/tests/periphery/static-a-token/TestBase.sol @@ -10,12 +10,15 @@ import {TransparentUpgradeableProxy} from 'solidity-utils/contracts/transparent- import {ITransparentProxyFactory} from 'solidity-utils/contracts/transparent-proxy/TransparentProxyFactory.sol'; import {IPool} from '../../../src/core/contracts/interfaces/IPool.sol'; import {StaticATokenFactory} from '../../../src/periphery/contracts/static-a-token/StaticATokenFactory.sol'; -import {StaticATokenLM, IStaticATokenLM, IERC20, IERC20Metadata, ERC20} from '../../../src/periphery/contracts/static-a-token/StaticATokenLM.sol'; +import {StaticATokenLM, IStaticATokenLM, IERC20, IERC20Metadata} from '../../../src/periphery/contracts/static-a-token/StaticATokenLM.sol'; import {IAToken} from '../../../src/core/contracts/interfaces/IAToken.sol'; import {TestnetProcedures, TestnetERC20} from '../../utils/TestnetProcedures.sol'; import {DataTypes} from '../../../src/core/contracts/protocol/libraries/configuration/ReserveConfiguration.sol'; abstract contract BaseTest is TestnetProcedures { + bytes32 internal constant PERMIT_TYPEHASH = + keccak256('Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)'); + address constant OWNER = address(1234); address public constant EMISSION_ADMIN = address(25);