From ba289d5bd1f41d28db15d837ebca5f8299d55bbb Mon Sep 17 00:00:00 2001 From: domenicodev Date: Tue, 23 Jan 2024 16:54:38 +0100 Subject: [PATCH] feat: Added imports in a different file as too long, implemented getLimits and getTokens --- evm/src/angle/AngleAdapter.sol | 90 ++++++++-------- evm/src/angle/AngleUtils.sol | 187 +++++++++++++++++++++++++++++++++ 2 files changed, 235 insertions(+), 42 deletions(-) create mode 100644 evm/src/angle/AngleUtils.sol diff --git a/evm/src/angle/AngleAdapter.sol b/evm/src/angle/AngleAdapter.sol index b2363a7..062e27c 100644 --- a/evm/src/angle/AngleAdapter.sol +++ b/evm/src/angle/AngleAdapter.sol @@ -2,7 +2,8 @@ pragma experimental ABIEncoderV2; pragma solidity ^0.8.13; -import {IERC20, ISwapAdapter} from "src/interfaces/ISwapAdapter.sol"; +/// @dev Wrapped imports (incl. ISwapAdapter and IERC20) are included in utils +import "./AngleUtils.sol"; /// @title AngleAdapter contract AngleAdapter is ISwapAdapter { @@ -13,6 +14,7 @@ contract AngleAdapter is ISwapAdapter { transmuter = _transmuter; } + /// @inheritdoc ISwapAdapter function price( bytes32, IERC20 _sellToken, @@ -38,11 +40,42 @@ contract AngleAdapter is ISwapAdapter { revert NotImplemented("TemplateSwapAdapter.swap"); } + /// @inheritdoc ISwapAdapter + /// @dev mint may have no limits, but we underestimate them to make sure, with the same amount of sellToken. + /// We use the quoteIn (incl. fee), because calculating fee requires a part of the implementation of + /// the Angle Diamond Storage, and therefore redundant functions and excessive contract size, with an high complexity. function getLimits(bytes32, IERC20 sellToken, IERC20 buyToken) external + view + override returns (uint256[] memory limits) { - revert NotImplemented("TemplateSwapAdapter.getLimits"); + limits = new uint256[](2); + address sellTokenAddress = address(sellToken); + address buyTokenAddress = address(buyToken); + + if(buyTokenAddress == transmuter.agToken()) { // mint(buy agToken) + Collateral memory collatInfo = transmuter.getCollateralInfo(sellTokenAddress); + if(collatInfo.isManaged > 0) { + limits[0] = LibManager.maxAvailable(collatInfo.managerData.config); + } + else { + limits[0] = sellToken.balanceOf(address(transmuter)); + } + limits[1] = transmuter.quoteIn(limits[0], sellTokenAddress, buyTokenAddress); + } + else { // burn(sell agToken) + Collateral memory collatInfo = transmuter.getCollateralInfo(buyTokenAddress); + uint256 collatLimit; + if(collatInfo.isManaged > 0) { + collatLimit = LibManager.maxAvailable(collatInfo.managerData.config); + } + else { + collatLimit = buyToken.balanceOf(address(transmuter)); + } + limits[0] = transmuter.quoteIn(collatLimit, buyTokenAddress, sellTokenAddress); + limits[1] = collatLimit; + } } function getCapabilities(bytes32 poolId, IERC20 sellToken, IERC20 buyToken) @@ -52,11 +85,21 @@ contract AngleAdapter is ISwapAdapter { revert NotImplemented("TemplateSwapAdapter.getCapabilities"); } - function getTokens(bytes32 poolId) + /// @inheritdoc ISwapAdapter + /// @dev Since Angle has no pool IDs but supports 3 tokens(agToken and the collaterals), + /// we return all the available collaterals and the agToken(agEUR) + function getTokens(bytes32) external + view + override returns (IERC20[] memory tokens) { - revert NotImplemented("TemplateSwapAdapter.getTokens"); + address[] memory collateralsAddresses = transmuter.getCollateralList(); + tokens = new IERC20[](collateralsAddresses.length + 1); + for(uint256 i = 0; i < collateralsAddresses.length; i++) { + tokens[i] = IERC20(collateralsAddresses[i]); + } + tokens[collateralsAddresses.length] = IERC20(transmuter.agToken()); } function getPoolIds(uint256 offset, uint256 limit) @@ -85,39 +128,6 @@ contract AngleAdapter is ISwapAdapter { } abstract contract ITransmuter { - struct Tuple6871229 { - address facetAddress; - uint8 action; - bytes4[] functionSelectors; - } - - struct Tuple1236461 { - address facetAddress; - bytes4[] functionSelectors; - } - - struct Tuple3550792 { - uint8 isManaged; - uint8 isMintLive; - uint8 isBurnLive; - uint8 decimals; - uint8 onlyWhitelisted; - uint216 normalizedStables; - uint64[] xFeeMint; - int64[] yFeeMint; - uint64[] xFeeBurn; - int64[] yFeeBurn; - bytes oracleConfig; - bytes whitelistData; - Tuple5479340 managerData; - } - - struct Tuple5479340 { - address[] subCollaterals; - bytes config; - } - - function diamondCut(Tuple6871229[] memory _diamondCut, address _init, bytes memory _calldata) external {} function implementation() external view returns (address) {} @@ -129,8 +139,6 @@ abstract contract ITransmuter { function facetFunctionSelectors(address _facet) external view returns (bytes4[] memory _facetFunctionSelectors) {} - function facets() external view returns (Tuple1236461[] memory facets_) {} - function accessControlManager() external view returns (address) {} function agToken() external view returns (address) {} @@ -141,7 +149,7 @@ abstract contract ITransmuter { function getCollateralDecimals(address collateral) external view returns (uint8) {} - function getCollateralInfo(address collateral) external view returns (Tuple3550792 memory) {} + function getCollateralInfo(address collateral) external view returns (Collateral memory) {} function getCollateralList() external view returns (address[] memory) {} @@ -203,8 +211,6 @@ abstract contract ITransmuter { function setAccessControlManager(address _newAccessControlManager) external {} - function setCollateralManager(address collateral, Tuple5479340 memory managerData) external {} - function setOracle(address collateral, bytes memory oracleConfig) external {} function setWhitelistStatus(address collateral, uint8 whitelistStatus, bytes memory whitelistData) external {} diff --git a/evm/src/angle/AngleUtils.sol b/evm/src/angle/AngleUtils.sol new file mode 100644 index 0000000..95ca64c --- /dev/null +++ b/evm/src/angle/AngleUtils.sol @@ -0,0 +1,187 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +/** + * @dev Collection of wrapped interfaces, items and libraries for Angle + * we use them here to maintain integrity and readability of contracts + * as there are many different imports and items + */ + +import {IERC20, ISwapAdapter} from "src/interfaces/ISwapAdapter.sol"; + + +interface IAgToken is IERC20 { + /*////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + MINTER ROLE ONLY FUNCTIONS + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////*/ + + /// @notice Lets a whitelisted contract mint agTokens + /// @param account Address to mint to + /// @param amount Amount to mint + function mint(address account, uint256 amount) external; + + /// @notice Burns `amount` tokens from a `burner` address after being asked to by `sender` + /// @param amount Amount of tokens to burn + /// @param burner Address to burn from + /// @param sender Address which requested the burn from `burner` + /// @dev This method is to be called by a contract with the minter right after being requested + /// to do so by a `sender` address willing to burn tokens from another `burner` address + /// @dev The method checks the allowance between the `sender` and the `burner` + function burnFrom(uint256 amount, address burner, address sender) external; + + /// @notice Burns `amount` tokens from a `burner` address + /// @param amount Amount of tokens to burn + /// @param burner Address to burn from + /// @dev This method is to be called by a contract with a minter right on the AgToken after being + /// requested to do so by an address willing to burn tokens from its address + function burnSelf(uint256 amount, address burner) external; + + /*////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + TREASURY ONLY FUNCTIONS + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////*/ + + /// @notice Adds a minter in the contract + /// @param minter Minter address to add + /// @dev Zero address checks are performed directly in the `Treasury` contract + function addMinter(address minter) external; + + /// @notice Removes a minter from the contract + /// @param minter Minter address to remove + /// @dev This function can also be called by a minter wishing to revoke itself + function removeMinter(address minter) external; + + /// @notice Sets a new treasury contract + /// @param _treasury New treasury address + function setTreasury(address _treasury) external; + + /*////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + EXTERNAL FUNCTIONS + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////*/ + + /// @notice Checks whether an address has the right to mint agTokens + /// @param minter Address for which the minting right should be checked + /// @return Whether the address has the right to mint agTokens or not + function isMinter(address minter) external view returns (bool); + + /// @notice Amount of decimals of the stablecoin + function decimals() external view returns (uint8); +} + +enum ManagerType { + EXTERNAL +} + +enum WhitelistType { + BACKED +} + +struct ManagerStorage { + IERC20[] subCollaterals; // Subtokens handled by the manager or strategies + bytes config; // Additional configuration data +} + +struct Collateral { + uint8 isManaged; // If the collateral is managed through external strategies + uint8 isMintLive; // If minting from this asset is unpaused + uint8 isBurnLive; // If burning to this asset is unpaused + uint8 decimals; // IERC20Metadata(collateral).decimals() + uint8 onlyWhitelisted; // If only whitelisted addresses can burn or redeem for this token + uint216 normalizedStables; // Normalized amount of stablecoins issued from this collateral + uint64[] xFeeMint; // Increasing exposures in [0,BASE_9[ + int64[] yFeeMint; // Mint fees at the exposures specified in `xFeeMint` + uint64[] xFeeBurn; // Decreasing exposures in ]0,BASE_9] + int64[] yFeeBurn; // Burn fees at the exposures specified in `xFeeBurn` + bytes oracleConfig; // Data about the oracle used for the collateral + bytes whitelistData; // For whitelisted collateral, data used to verify whitelists + ManagerStorage managerData; // For managed collateral, data used to handle the strategies +} + +struct TransmuterStorage { + IAgToken agToken; // agToken handled by the system + uint8 isRedemptionLive; // If redemption is unpaused + uint8 statusReentrant; // If call is reentrant or not + uint128 normalizedStables; // Normalized amount of stablecoins issued by the system + uint128 normalizer; // To reconcile `normalizedStables` values with the actual amount + address[] collateralList; // List of collateral assets supported by the system + uint64[] xRedemptionCurve; // Increasing collateral ratios > 0 + int64[] yRedemptionCurve; // Value of the redemption fees at `xRedemptionCurve` + mapping(address => Collateral) collaterals; // Maps a collateral asset to its parameters + mapping(address => uint256) isTrusted; // If an address is trusted to update the normalizer value + mapping(address => uint256) isSellerTrusted; // If an address is trusted to sell accruing reward tokens + mapping(WhitelistType => mapping(address => uint256)) isWhitelistedForType; +} + +interface IManager { + /// @notice Returns the amount of collateral managed by the Manager + /// @return balances Balances of all the subCollaterals handled by the manager + /// @dev MUST NOT revert + function totalAssets() external view returns (uint256[] memory balances, uint256 totalValue); + + /// @notice Hook to invest `amount` of `collateral` + /// @dev MUST revert if the manager cannot accept these funds + /// @dev MUST have received the funds beforehand + function invest(uint256 amount) external; + + /// @notice Sends `amount` of `collateral` to the `to` address + /// @dev Called when `agToken` are burnt and during redemptions + // @dev MUST revert if there are not funds enough available + /// @dev MUST be callable only by the transmuter + function release(address asset, address to, uint256 amount) external; + + /// @notice Gives the maximum amount of collateral immediately available for a transfer + /// @dev Useful for integrators using `quoteIn` and `quoteOut` + function maxAvailable() external view returns (uint256); +} + +/// @title LibManager +/// @author Angle Labs, Inc. +/// @dev Managed collateral assets may be handled through external smart contracts or directly through this library +/// @dev There is no implementation at this point for a managed collateral handled through this library, and +/// a new specific `ManagerType` would need to be added in this case +library LibManager { + /// @notice Checks to which address managed funds must be transferred + function transferRecipient(bytes memory config) internal view returns (address recipient) { + (ManagerType managerType, bytes memory data) = parseManagerConfig(config); + recipient = address(this); + if (managerType == ManagerType.EXTERNAL) return abi.decode(data, (address)); + } + + /// @notice Performs a transfer of `token` for a collateral that is managed to a `to` address + /// @dev `token` may not be the actual collateral itself, as some collaterals have subcollaterals associated + /// with it + /// @dev Eventually pulls funds from strategies + function release(address token, address to, uint256 amount, bytes memory config) internal { + (ManagerType managerType, bytes memory data) = parseManagerConfig(config); + if (managerType == ManagerType.EXTERNAL) abi.decode(data, (IManager)).release(token, to, amount); + } + + /// @notice Gets the balances of all the tokens controlled through `managerData` + /// @return balances An array of size `subCollaterals` with current balances of all subCollaterals + /// including the one corresponding to the `managerData` given + /// @return totalValue The value of all the `subCollaterals` in `collateral` + /// @dev `subCollaterals` must always have as first token (index 0) the collateral itself + function totalAssets(bytes memory config) internal view returns (uint256[] memory balances, uint256 totalValue) { + (ManagerType managerType, bytes memory data) = parseManagerConfig(config); + if (managerType == ManagerType.EXTERNAL) return abi.decode(data, (IManager)).totalAssets(); + } + + /// @notice Calls a hook if needed after new funds have been transfered to a manager + function invest(uint256 amount, bytes memory config) internal { + (ManagerType managerType, bytes memory data) = parseManagerConfig(config); + if (managerType == ManagerType.EXTERNAL) abi.decode(data, (IManager)).invest(amount); + } + + /// @notice Returns available underlying tokens, for instance if liquidity is fully used and + /// not withdrawable the function will return 0 + function maxAvailable(bytes memory config) internal view returns (uint256 available) { + (ManagerType managerType, bytes memory data) = parseManagerConfig(config); + if (managerType == ManagerType.EXTERNAL) return abi.decode(data, (IManager)).maxAvailable(); + } + + /// @notice Decodes the `managerData` associated to a collateral + function parseManagerConfig( + bytes memory config + ) internal pure returns (ManagerType managerType, bytes memory data) { + (managerType, data) = abi.decode(config, (ManagerType, bytes)); + } +}