From 511ca59f5942af342bfa9f8825b77cd0f61cbd25 Mon Sep 17 00:00:00 2001 From: Ale personal Date: Sat, 16 Mar 2024 19:45:15 +0100 Subject: [PATCH] feat: Improve adapter and tests --- evm/src/angle/AngleAdapter.sol | 575 ++++++++++++++++++++++----------- evm/test/AngleAdapter.t.sol | 73 ++--- 2 files changed, 404 insertions(+), 244 deletions(-) diff --git a/evm/src/angle/AngleAdapter.sol b/evm/src/angle/AngleAdapter.sol index 96aeab8..521b688 100644 --- a/evm/src/angle/AngleAdapter.sol +++ b/evm/src/angle/AngleAdapter.sol @@ -3,36 +3,34 @@ pragma experimental ABIEncoderV2; pragma solidity ^0.8.13; import {IERC20, ISwapAdapter} from "src/interfaces/ISwapAdapter.sol"; +import {IERC20Metadata} from + "openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol"; /// @dev custom reserve limit factor to prevent revert errors in OrderSide.Buy uint256 constant RESERVE_LIMIT_FACTOR = 10; +uint256 constant STANDARD_TOKEN_DECIMALS = 10 ** 18; /// @title AngleAdapter -/// @dev Information about prices: When swapping collateral to agEUR, the trade price will not decrease(amountOut); -/// Instead, when swapping agEUR to collateral, it will, because agEUR is minted, and this mechanism is used to +/// @dev Information about prices: When swapping collateral to agEUR, the trade +/// price will not decrease(amountOut); +/// Instead, when swapping agEUR to collateral, it will, because agEUR is +/// minted, and this mechanism is used to /// stabilize the agEUR price. contract AngleAdapter is ISwapAdapter { - - ITransmuter transmuter; + ITransmuter immutable transmuter; constructor(ITransmuter _transmuter) { transmuter = _transmuter; } /// @inheritdoc ISwapAdapter - function price( - bytes32, - IERC20 _sellToken, - IERC20 _buyToken, - uint256[] memory _specifiedAmounts - ) external view override returns (Fraction[] memory _prices) { - _prices = new Fraction[](_specifiedAmounts.length); - address sellTokenAddress = address(_sellToken); - address buyTokenAddress = address(_buyToken); - - for (uint256 i = 0; i < _specifiedAmounts.length; i++) { - _prices[i] = getPriceAt(_specifiedAmounts[i], sellTokenAddress, buyTokenAddress, OrderSide.Sell); - } + function price(bytes32, IERC20, IERC20, uint256[] memory) + external + pure + override + returns (Fraction[] memory) + { + revert NotImplemented("AngleAdapter.price"); } /// @inheritdoc ISwapAdapter @@ -49,21 +47,27 @@ contract AngleAdapter is ISwapAdapter { uint256 gasBefore = gasleft(); if (side == OrderSide.Sell) { - trade.calculatedAmount = - sell(sellToken, buyToken, specifiedAmount); + trade.calculatedAmount = sell(sellToken, buyToken, specifiedAmount); } else { - trade.calculatedAmount = - buy(sellToken, buyToken, specifiedAmount); + trade.calculatedAmount = buy(sellToken, buyToken, specifiedAmount); } - trade.price = getPriceAt(specifiedAmount, address(sellToken), address(buyToken), side); trade.gasUsed = gasBefore - gasleft(); + uint8 decimals = side == OrderSide.Sell + ? IERC20Metadata(address(sellToken)).decimals() + : IERC20Metadata(address(buyToken)).decimals(); + trade.price = + getPriceAt(address(sellToken), address(buyToken), side, decimals); } /// @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. - /// In addition, we underestimate to / RESERVE_LIMIT_FACTOR to ensure swaps with OrderSide.Buy won't fail anyway. + /// @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. + /// In addition, we underestimate to / RESERVE_LIMIT_FACTOR to ensure swaps + /// with OrderSide.Buy won't fail anyway. function getLimits(bytes32, IERC20 sellToken, IERC20 buyToken) external view @@ -75,29 +79,33 @@ contract AngleAdapter is ISwapAdapter { address buyTokenAddress = address(buyToken); address transmuterAddress = address(transmuter); - 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 { + 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(transmuterAddress); } - limits[1] = transmuter.quoteIn(limits[0], sellTokenAddress, buyTokenAddress); + limits[1] = + transmuter.quoteIn(limits[0], sellTokenAddress, buyTokenAddress); limits[1] = limits[1] / RESERVE_LIMIT_FACTOR; limits[0] = limits[0] / RESERVE_LIMIT_FACTOR; - } - else { // burn(sell agToken) - Collateral memory collatInfo = transmuter.getCollateralInfo(buyTokenAddress); - uint256 collatLimit; - if(collatInfo.isManaged > 0) { - collatLimit = LibManager.maxAvailable(collatInfo.managerData.config); + } else { + // burn(sell agToken) + Collateral memory collatInfo = + transmuter.getCollateralInfo(buyTokenAddress); + if (collatInfo.isManaged > 0) { + limits[1] = + LibManager.maxAvailable(collatInfo.managerData.config); + } else { + limits[1] = buyToken.balanceOf(transmuterAddress); } - else { - collatLimit = buyToken.balanceOf(transmuterAddress); - } - limits[0] = transmuter.quoteIn(collatLimit, buyTokenAddress, sellTokenAddress); - limits[1] = collatLimit / RESERVE_LIMIT_FACTOR; + limits[0] = + transmuter.quoteIn(limits[1], buyTokenAddress, sellTokenAddress); + limits[1] = limits[1] / RESERVE_LIMIT_FACTOR; limits[0] = limits[0] / RESERVE_LIMIT_FACTOR; } } @@ -112,11 +120,11 @@ contract AngleAdapter is ISwapAdapter { capabilities = new Capability[](3); capabilities[0] = Capability.SellOrder; capabilities[1] = Capability.BuyOrder; - capabilities[2] = Capability.PriceFunction; } /// @inheritdoc ISwapAdapter - /// @dev Since Angle has no pool IDs but supports 3 tokens(agToken and the collaterals), + /// @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 @@ -126,7 +134,7 @@ contract AngleAdapter is ISwapAdapter { { address[] memory collateralsAddresses = transmuter.getCollateralList(); tokens = new IERC20[](collateralsAddresses.length + 1); - for(uint256 i = 0; i < collateralsAddresses.length; i++) { + for (uint256 i = 0; i < collateralsAddresses.length; i++) { tokens[i] = IERC20(collateralsAddresses[i]); } tokens[collateralsAddresses.length] = IERC20(transmuter.agToken()); @@ -142,30 +150,27 @@ contract AngleAdapter is ISwapAdapter { } /// @notice Calculates pool prices for specified amounts - /// @param amount The amount of the token being sold(if side == Sell) or bought(if side == Buy) /// @param tokenIn The token being sold /// @param tokenOut The token being bought /// @param side Order side + /// @param decimals Decimals of the sell token /// @return The price as a fraction corresponding to the provided amount. - function getPriceAt(uint256 amount, address tokenIn, address tokenOut, OrderSide side) - internal - view - returns (Fraction memory) - { + function getPriceAt( + address tokenIn, + address tokenOut, + OrderSide side, + uint8 decimals + ) internal view returns (Fraction memory) { uint256 amountOut; uint256 amountIn; - if(side == OrderSide.Sell) { - amountIn = amount; + if (side == OrderSide.Sell) { + amountIn = 10 ** decimals; amountOut = transmuter.quoteIn(amountIn, tokenIn, tokenOut); + } else { + amountOut = 10 ** decimals; + amountIn = transmuter.quoteOut(amountOut, tokenIn, tokenOut); } - else { - amountOut = amount; - amountIn = transmuter.quoteOut(amount, tokenIn, tokenOut); - } - return Fraction( - amountOut, - amountIn - ); + return Fraction(amountOut, amountIn); } /// @notice Executes a sell order on the contract. @@ -173,20 +178,20 @@ contract AngleAdapter is ISwapAdapter { /// @param buyToken The token being bought. /// @param amount The amount to be traded. /// @return calculatedAmount The amount of tokens received. - function sell( - IERC20 sellToken, - IERC20 buyToken, - uint256 amount - ) internal returns (uint256 calculatedAmount) { + function sell(IERC20 sellToken, IERC20 buyToken, uint256 amount) + internal + returns (uint256 calculatedAmount) + { address sellTokenAddress = address(sellToken); address buyTokenAddress = address(buyToken); - uint256 amountOut = transmuter.quoteIn(amount, sellTokenAddress, buyTokenAddress); + calculatedAmount = + transmuter.quoteIn(amount, sellTokenAddress, buyTokenAddress); - // TODO: use safeTransferFrom sellToken.transferFrom(msg.sender, address(this), amount); sellToken.approve(address(transmuter), amount); - transmuter.swapExactInput(amount, 0, sellTokenAddress, buyTokenAddress, msg.sender, 0); - return amountOut; + transmuter.swapExactInput( + amount, 0, sellTokenAddress, buyTokenAddress, msg.sender, 0 + ); } /// @notice Executes a buy order on the contract. @@ -194,26 +199,31 @@ contract AngleAdapter is ISwapAdapter { /// @param buyToken The token being bought. /// @param amountOut The amount of buyToken to receive. /// @return calculatedAmount The amount of tokens received. - function buy( - IERC20 sellToken, - IERC20 buyToken, - uint256 amountOut - ) internal returns (uint256 calculatedAmount) { + function buy(IERC20 sellToken, IERC20 buyToken, uint256 amountOut) + internal + returns (uint256 calculatedAmount) + { address sellTokenAddress = address(sellToken); address buyTokenAddress = address(buyToken); - uint256 amountIn = transmuter.quoteOut(amountOut, sellTokenAddress, buyTokenAddress); + calculatedAmount = + transmuter.quoteOut(amountOut, sellTokenAddress, buyTokenAddress); - // TODO: use safeTransferFrom - sellToken.transferFrom(msg.sender, address(this), amountIn); - sellToken.approve(address(transmuter), amountIn); - transmuter.swapExactOutput(amountOut, type(uint256).max, sellTokenAddress, buyTokenAddress, msg.sender, 0); - return amountIn; + sellToken.transferFrom(msg.sender, address(this), calculatedAmount); + sellToken.approve(address(transmuter), calculatedAmount); + transmuter.swapExactOutput( + amountOut, + type(uint256).max, + sellTokenAddress, + buyTokenAddress, + msg.sender, + 0 + ); } } interface IAgToken is IERC20 { /*////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - MINTER ROLE ONLY FUNCTIONS + MINTER ROLE ONLY FUNCTIONS //////////////////////////////////////////////////////////////////////////////////////////////////////////////////*/ /// @notice Lets a whitelisted contract mint agTokens @@ -221,34 +231,42 @@ interface IAgToken is IERC20 { /// @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` + /// @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; + /// @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 + /// @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 + 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 + /// @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 + /// @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 @@ -256,7 +274,7 @@ interface IAgToken is IERC20 { function setTreasury(address _treasury) external; /*////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - EXTERNAL FUNCTIONS + EXTERNAL FUNCTIONS //////////////////////////////////////////////////////////////////////////////////////////////////////////////////*/ /// @notice Checks whether an address has the right to mint agTokens @@ -277,46 +295,61 @@ enum WhitelistType { } struct ManagerStorage { - IERC20[] subCollaterals; // Subtokens handled by the manager or strategies - bytes config; // Additional configuration data + 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 + 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 + 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 + /// @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); + 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 @@ -329,121 +362,224 @@ interface IManager { /// @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 + /// @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 +/// @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); + 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)); + 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 + /// @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); + 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 + /// @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(); + /// @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 + /// @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); + (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 + /// @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(); + 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) { + function parseManagerConfig(bytes memory config) + internal + pure + returns (ManagerType managerType, bytes memory data) + { (managerType, data) = abi.decode(config, (ManagerType, bytes)); } } abstract contract ITransmuter { - function implementation() external view returns (address) {} function setDummyImplementation(address _implementation) external {} - function facetAddress(bytes4 _functionSelector) external view returns (address facetAddress_) {} + function facetAddress(bytes4 _functionSelector) + external + view + returns (address facetAddress_) + {} - function facetAddresses() external view returns (address[] memory facetAddresses_) {} + function facetAddresses() + external + view + returns (address[] memory facetAddresses_) + {} - function facetFunctionSelectors(address _facet) external view returns (bytes4[] memory _facetFunctionSelectors) {} + function facetFunctionSelectors(address _facet) + external + view + returns (bytes4[] memory _facetFunctionSelectors) + {} function accessControlManager() external view returns (address) {} function agToken() external view returns (address) {} - function getCollateralBurnFees( - address collateral - ) external view returns (uint64[] memory xFeeBurn, int64[] memory yFeeBurn) {} + function getCollateralBurnFees(address collateral) + external + view + returns (uint64[] memory xFeeBurn, int64[] memory yFeeBurn) + {} - function getCollateralDecimals(address collateral) external view returns (uint8) {} + function getCollateralDecimals(address collateral) + external + view + returns (uint8) + {} - function getCollateralInfo(address collateral) external view returns (Collateral memory) {} + function getCollateralInfo(address collateral) + external + view + returns (Collateral memory) + {} function getCollateralList() external view returns (address[] memory) {} - function getCollateralMintFees( - address collateral - ) external view returns (uint64[] memory xFeeMint, int64[] memory yFeeMint) {} + function getCollateralMintFees(address collateral) + external + view + returns (uint64[] memory xFeeMint, int64[] memory yFeeMint) + {} - function getCollateralRatio() external view returns (uint64 collatRatio, uint256 stablecoinsIssued) {} + function getCollateralRatio() + external + view + returns (uint64 collatRatio, uint256 stablecoinsIssued) + {} - function getCollateralWhitelistData(address collateral) external view returns (bytes memory) {} + function getCollateralWhitelistData(address collateral) + external + view + returns (bytes memory) + {} - function getIssuedByCollateral( - address collateral - ) external view returns (uint256 stablecoinsFromCollateral, uint256 stablecoinsIssued) {} + function getIssuedByCollateral(address collateral) + external + view + returns (uint256 stablecoinsFromCollateral, uint256 stablecoinsIssued) + {} - function getManagerData(address collateral) external view returns (bool, address[] memory, bytes memory) {} + function getManagerData(address collateral) + external + view + returns (bool, address[] memory, bytes memory) + {} - function getOracle( - address collateral - ) external view returns (uint8 oracleType, uint8 targetType, bytes memory oracleData, bytes memory targetData) {} + function getOracle(address collateral) + external + view + returns ( + uint8 oracleType, + uint8 targetType, + bytes memory oracleData, + bytes memory targetData + ) + {} - function getOracleValues( - address collateral - ) external view returns (uint256 mint, uint256 burn, uint256 ratio, uint256 minRatio, uint256 redemption) {} + function getOracleValues(address collateral) + external + view + returns ( + uint256 mint, + uint256 burn, + uint256 ratio, + uint256 minRatio, + uint256 redemption + ) + {} function getRedemptionFees() external view - returns (uint64[] memory xRedemptionCurve, int64[] memory yRedemptionCurve) + returns ( + uint64[] memory xRedemptionCurve, + int64[] memory yRedemptionCurve + ) {} function getTotalIssued() external view returns (uint256) {} - function isPaused(address collateral, uint8 action) external view returns (bool) {} + function isPaused(address collateral, uint8 action) + external + view + returns (bool) + {} function isTrusted(address sender) external view returns (bool) {} @@ -451,43 +587,91 @@ abstract contract ITransmuter { function isValidSelector(bytes4 selector) external view returns (bool) {} - function isWhitelistedCollateral(address collateral) external view returns (bool) {} + function isWhitelistedCollateral(address collateral) + external + view + returns (bool) + {} - function isWhitelistedForCollateral(address collateral, address sender) external returns (bool) {} + function isWhitelistedForCollateral(address collateral, address sender) + external + returns (bool) + {} - function isWhitelistedForType(uint8 whitelistType, address sender) external view returns (bool) {} + function isWhitelistedForType(uint8 whitelistType, address sender) + external + view + returns (bool) + {} - function sellRewards(uint256 minAmountOut, bytes memory payload) external returns (uint256 amountOut) {} + function sellRewards(uint256 minAmountOut, bytes memory payload) + external + returns (uint256 amountOut) + {} function addCollateral(address collateral) external {} - function adjustStablecoins(address collateral, uint128 amount, bool increase) external {} + function adjustStablecoins( + address collateral, + uint128 amount, + bool increase + ) external {} - function changeAllowance(address token, address spender, uint256 amount) external {} + function changeAllowance(address token, address spender, uint256 amount) + external + {} - function recoverERC20(address collateral, address token, address to, uint256 amount) external {} + function recoverERC20( + address collateral, + address token, + address to, + uint256 amount + ) external {} function revokeCollateral(address collateral) external {} - function setAccessControlManager(address _newAccessControlManager) external {} + function setAccessControlManager(address _newAccessControlManager) + external + {} - function setOracle(address collateral, bytes memory oracleConfig) external {} + function setOracle(address collateral, bytes memory oracleConfig) + external + {} - function setWhitelistStatus(address collateral, uint8 whitelistStatus, bytes memory whitelistData) external {} + function setWhitelistStatus( + address collateral, + uint8 whitelistStatus, + bytes memory whitelistData + ) external {} function toggleTrusted(address sender, uint8 t) external {} - function setFees(address collateral, uint64[] memory xFee, int64[] memory yFee, bool mint) external {} + function setFees( + address collateral, + uint64[] memory xFee, + int64[] memory yFee, + bool mint + ) external {} - function setRedemptionCurveParams(uint64[] memory xFee, int64[] memory yFee) external {} + function setRedemptionCurveParams(uint64[] memory xFee, int64[] memory yFee) + external + {} function togglePause(address collateral, uint8 pausedType) external {} function toggleWhitelist(uint8 whitelistType, address who) external {} - function quoteIn(uint256 amountIn, address tokenIn, address tokenOut) external view returns (uint256 amountOut) {} + function quoteIn(uint256 amountIn, address tokenIn, address tokenOut) + external + view + returns (uint256 amountOut) + {} - function quoteOut(uint256 amountOut, address tokenIn, address tokenOut) external view returns (uint256 amountIn) {} + function quoteOut(uint256 amountOut, address tokenIn, address tokenOut) + external + view + returns (uint256 amountIn) + {} function swapExactInput( uint256 amountIn, @@ -525,9 +709,11 @@ abstract contract ITransmuter { bytes memory permitData ) external returns (uint256 amountIn) {} - function quoteRedemptionCurve( - uint256 amount - ) external view returns (address[] memory tokens, uint256[] memory amounts) {} + function quoteRedemptionCurve(uint256 amount) + external + view + returns (address[] memory tokens, uint256[] memory amounts) + {} function redeem( uint256 amount, @@ -544,5 +730,8 @@ abstract contract ITransmuter { address[] memory forfeitTokens ) external returns (address[] memory tokens, uint256[] memory amounts) {} - function updateNormalizer(uint256 amount, bool increase) external returns (uint256) {} + function updateNormalizer(uint256 amount, bool increase) + external + returns (uint256) + {} } diff --git a/evm/test/AngleAdapter.t.sol b/evm/test/AngleAdapter.t.sol index 433174b..c8e23f1 100644 --- a/evm/test/AngleAdapter.t.sol +++ b/evm/test/AngleAdapter.t.sol @@ -6,6 +6,7 @@ import "openzeppelin-contracts/contracts/interfaces/IERC20.sol"; import "src/angle/AngleAdapter.sol"; import "src/interfaces/ISwapAdapterTypes.sol"; import "src/libraries/FractionMath.sol"; +import "forge-std/console.sol"; contract AngleAdapterTest is Test, ISwapAdapterTypes { using FractionMath for Fraction; @@ -13,15 +14,15 @@ contract AngleAdapterTest is Test, ISwapAdapterTypes { AngleAdapter adapter; IERC20 agEUR; IERC20 constant EURC = IERC20(0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c); - ITransmuter constant transmuter = ITransmuter(0x00253582b2a3FE112feEC532221d9708c64cEFAb); + ITransmuter constant transmuter = + ITransmuter(0x00253582b2a3FE112feEC532221d9708c64cEFAb); uint256 constant TEST_ITERATIONS = 100; function setUp() public { uint256 forkBlock = 18921770; vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock); - adapter = new - AngleAdapter(transmuter); + adapter = new AngleAdapter(transmuter); agEUR = IERC20(transmuter.agToken()); vm.label(address(adapter), "AngleAdapter"); @@ -29,43 +30,9 @@ contract AngleAdapterTest is Test, ISwapAdapterTypes { vm.label(address(EURC), "EURC"); } - function testPriceFuzzAngle(uint256 amount0, uint256 amount1) public { - bytes32 pair = bytes32(0); - uint256[] memory limits = adapter.getLimits(pair, EURC, agEUR); - vm.assume(amount0 < limits[0] && amount0 > 0); - vm.assume(amount1 < limits[0] && amount1 > 0); - - uint256[] memory amounts = new uint256[](2); - amounts[0] = amount0; - amounts[1] = amount1; - - Fraction[] memory prices = adapter.price(pair, EURC, agEUR, amounts); - - for (uint256 i = 0; i < prices.length; i++) { - assertGt(prices[i].numerator, 0); - assertGt(prices[i].denominator, 0); - } - } - - - function testPriceDecreasingAngle() public { - bytes32 pair = bytes32(0); - uint256[] memory amounts = new uint256[](TEST_ITERATIONS); - - for (uint256 i = 0; i < TEST_ITERATIONS; i++) { - amounts[i] = 1000 * (i+1) * 10 ** 18; - } - - Fraction[] memory prices = adapter.price(pair, agEUR, EURC, amounts); - - for (uint256 i = 0; i < TEST_ITERATIONS - 1; i++) { - assertEq(prices[i].compareFractions(prices[i + 1]), 1); - assertGt(prices[i].denominator, 0); - assertGt(prices[i + 1].denominator, 0); - } - } - - function testSwapFuzzAngleMint(uint256 specifiedAmount, bool isBuy) public { + function testSwapFuzzAngleMint(uint256 specifiedAmount, bool isBuy) + public + { OrderSide side = isBuy ? OrderSide.Buy : OrderSide.Sell; bytes32 pair = bytes32(0); @@ -112,12 +79,13 @@ contract AngleAdapterTest is Test, ISwapAdapterTypes { } } - function testSwapFuzzAngleRedeem(uint256 specifiedAmount, bool isBuy) public { + function testSwapFuzzAngleRedeem(uint256 specifiedAmount, bool isBuy) + public + { OrderSide side = isBuy ? OrderSide.Buy : OrderSide.Sell; bytes32 pair = bytes32(0); uint256[] memory limits = adapter.getLimits(pair, agEUR, EURC); - console.log(limits[0], limits[1]); if (side == OrderSide.Buy) { vm.assume(specifiedAmount < limits[1] && specifiedAmount > 0); @@ -169,8 +137,9 @@ contract AngleAdapterTest is Test, ISwapAdapterTypes { uint256[] memory amounts = new uint256[](TEST_ITERATIONS); for (uint256 i = 0; i < TEST_ITERATIONS; i++) { - amounts[i] = - side == OrderSide.Sell ? (100 * i * 10 ** 18) : (100 * i * 10 ** 6); + amounts[i] = side == OrderSide.Sell + ? (100 * i * 10 ** 18) + : (100 * i * 10 ** 6); } Trade[] memory trades = new Trade[](TEST_ITERATIONS); @@ -178,22 +147,22 @@ contract AngleAdapterTest is Test, ISwapAdapterTypes { for (uint256 i = 1; i < TEST_ITERATIONS; i++) { beforeSwap = vm.snapshot(); - if(side == OrderSide.Sell) { + if (side == OrderSide.Sell) { deal(address(agEUR), address(this), amounts[i]); agEUR.approve(address(adapter), amounts[i]); - } - else { + } else { deal(address(agEUR), address(this), type(uint256).max); - agEUR.approve(address(adapter), type(uint256).max); + agEUR.approve(address(adapter), type(uint256).max); } - trades[i] = adapter.swap(pair, agEUR, EURC, side, amounts[i]); vm.revertTo(beforeSwap); } for (uint256 i = 1; i < TEST_ITERATIONS - 1; i++) { assertLe(trades[i].calculatedAmount, trades[i + 1].calculatedAmount); - assertEq(trades[i].price.compareFractions(trades[i + 1].price), 1); + assertFalse( + trades[i].price.compareFractions(trades[i + 1].price) == -1 + ); } } @@ -201,7 +170,9 @@ contract AngleAdapterTest is Test, ISwapAdapterTypes { executeIncreasingSwapsAngle(OrderSide.Buy); } - function testGetCapabilitiesAngle(bytes32 pair, address t0, address t1) public { + function testGetCapabilitiesAngle(bytes32 pair, address t0, address t1) + public + { Capability[] memory res = adapter.getCapabilities(pair, IERC20(t0), IERC20(t1));