From 6a8652bdf5aedaf206446c1d28938a067767fadd Mon Sep 17 00:00:00 2001 From: Nick Addison Date: Thu, 26 Sep 2024 17:18:47 +1000 Subject: [PATCH] Flatten the LidoARM inheritance (#25) * Flatten LidoARM's inheritance * Renamed LidoARM contract * Changed to LidoARM name in tests * Generated new contract diagrams * Minor changes to the LidoARM * Renamed lidoARM variable in tests * Updated deposit test comments * Updated setPrice tests * Small change to setPrices test --- docs/LidoARMHierarchy.svg | 80 +++ docs/LidoARMPublicSquashed.svg | 87 +++ docs/LidoARMSquashed.svg | 117 ++++ docs/generate.sh | 8 +- .../mainnet/003_UpgradeLidoARMScript.sol | 8 +- src/contracts/AbstractARM.sol | 547 +++++++++++++++++- src/contracts/FixedPriceARM.sol | 113 ---- ...doFixedPriceMultiLpARM.sol => LidoARM.sol} | 63 +- src/contracts/LidoLiquidityManager.sol | 7 + .../LiquidityProviderControllerARM.sol | 40 -- src/contracts/MultiLP.sol | 269 --------- src/contracts/OethARM.sol | 6 +- src/contracts/PerformanceFee.sol | 161 ------ src/contracts/README.md | 6 +- test/Base.sol | 4 +- .../ClaimRedeem.t.sol | 165 +++--- .../ClaimStETHWithdrawalForWETH.t.sol | 48 +- .../CollectFees.t.sol | 33 +- .../Constructor.t.sol | 24 +- .../LidoFixedPriceMultiLpARM/Deposit.t.sol | 254 ++++---- .../RequestRedeem.t.sol | 151 +++-- .../RequestStETHWithdrawalForETH.t.sol | 45 +- .../LidoFixedPriceMultiLpARM/Setters.t.sol | 113 ++-- .../SwapExactTokensForTokens.t.sol | 102 ++-- .../SwapTokensForExactTokens.t.sol | 98 ++-- .../TotalAssets.t.sol | 123 ++-- test/fork/shared/Shared.sol | 15 +- test/fork/utils/Helpers.sol | 10 +- test/fork/utils/MockCall.sol | 6 +- test/fork/utils/Modifiers.sol | 72 +-- test/smoke/LidoARMSmokeTest.t.sol | 6 +- 31 files changed, 1433 insertions(+), 1348 deletions(-) create mode 100644 docs/LidoARMHierarchy.svg create mode 100644 docs/LidoARMPublicSquashed.svg create mode 100644 docs/LidoARMSquashed.svg delete mode 100644 src/contracts/FixedPriceARM.sol rename src/contracts/{LidoFixedPriceMultiLpARM.sol => LidoARM.sol} (56%) delete mode 100644 src/contracts/LiquidityProviderControllerARM.sol delete mode 100644 src/contracts/MultiLP.sol delete mode 100644 src/contracts/PerformanceFee.sol diff --git a/docs/LidoARMHierarchy.svg b/docs/LidoARMHierarchy.svg new file mode 100644 index 0000000..2fcc331 --- /dev/null +++ b/docs/LidoARMHierarchy.svg @@ -0,0 +1,80 @@ + + + + + + +UmlClassDiagram + + + +0 + +<<Abstract>> +AbstractARM +../src/contracts/AbstractARM.sol + + + +19 + +OwnableOperable +../src/contracts/OwnableOperable.sol + + + +0->19 + + + + + +13 + +LidoARM +../src/contracts/LidoARM.sol + + + +13->0 + + + + + +14 + +<<Abstract>> +LidoLiquidityManager +../src/contracts/LidoLiquidityManager.sol + + + +13->14 + + + + + +14->19 + + + + + +18 + +Ownable +../src/contracts/Ownable.sol + + + +19->18 + + + + + diff --git a/docs/LidoARMPublicSquashed.svg b/docs/LidoARMPublicSquashed.svg new file mode 100644 index 0000000..9b0c980 --- /dev/null +++ b/docs/LidoARMPublicSquashed.svg @@ -0,0 +1,87 @@ + + + + + + +UmlClassDiagram + + + +13 + +LidoARM +../src/contracts/LidoARM.sol + +Public: +   operator: address <<OwnableOperable>> +   MAX_PRICE_DEVIATION: uint256 <<AbstractARM>> +   PRICE_SCALE: uint256 <<AbstractARM>> +   CLAIM_DELAY: uint256 <<AbstractARM>> +   FEE_SCALE: uint256 <<AbstractARM>> +   token0: IERC20 <<AbstractARM>> +   token1: IERC20 <<AbstractARM>> +   traderate0: uint256 <<AbstractARM>> +   traderate1: uint256 <<AbstractARM>> +   withdrawsQueued: uint128 <<AbstractARM>> +   withdrawsClaimed: uint128 <<AbstractARM>> +   withdrawsClaimable: uint128 <<AbstractARM>> +   nextWithdrawalIndex: uint128 <<AbstractARM>> +   withdrawalRequests: mapping(uint256=>WithdrawalRequest) <<AbstractARM>> +   feeCollector: address <<AbstractARM>> +   fee: uint16 <<AbstractARM>> +   feesAccrued: uint112 <<AbstractARM>> +   lastTotalAssets: uint128 <<AbstractARM>> +   liquidityProviderController: address <<AbstractARM>> +   steth: IERC20 <<LidoLiquidityManager>> +   weth: IWETH <<LidoLiquidityManager>> +   withdrawalQueue: IStETHWithdrawal <<LidoLiquidityManager>> +   outstandingEther: uint256 <<LidoLiquidityManager>> + +External: +    <<payable>> null() <<LidoLiquidityManager>> +    owner(): address <<Ownable>> +    setOwner(newOwner: address) <<onlyOwner>> <<Ownable>> +    setOperator(newOperator: address) <<onlyOwner>> <<OwnableOperable>> +    swapExactTokensForTokens(inToken: IERC20, outToken: IERC20, amountIn: uint256, amountOutMin: uint256, to: address) <<AbstractARM>> +    swapExactTokensForTokens(amountIn: uint256, amountOutMin: uint256, path: address[], to: address, deadline: uint256): (amounts: uint256[]) <<AbstractARM>> +    swapTokensForExactTokens(inToken: IERC20, outToken: IERC20, amountOut: uint256, amountInMax: uint256, to: address) <<AbstractARM>> +    swapTokensForExactTokens(amountOut: uint256, amountInMax: uint256, path: address[], to: address, deadline: uint256): (amounts: uint256[]) <<AbstractARM>> +    setPrices(buyT1: uint256, sellT1: uint256) <<onlyOperatorOrOwner>> <<AbstractARM>> +    previewDeposit(assets: uint256): (shares: uint256) <<AbstractARM>> +    deposit(assets: uint256): (shares: uint256) <<AbstractARM>> +    previewRedeem(shares: uint256): (assets: uint256) <<AbstractARM>> +    requestRedeem(shares: uint256): (requestId: uint256, assets: uint256) <<AbstractARM>> +    claimRedeem(requestId: uint256): (assets: uint256) <<AbstractARM>> +    setFee(_fee: uint256) <<onlyOwner>> <<AbstractARM>> +    setFeeCollector(_feeCollector: address) <<onlyOwner>> <<AbstractARM>> +    collectFees(): (fees: uint256) <<AbstractARM>> +    setLiquidityProviderController(_liquidityProviderController: address) <<onlyOwner>> <<AbstractARM>> +    approveStETH() <<onlyOperatorOrOwner>> <<LidoLiquidityManager>> +    requestStETHWithdrawalForETH(amounts: uint256[]): (requestIds: uint256[]) <<onlyOperatorOrOwner>> <<LidoLiquidityManager>> +    claimStETHWithdrawalForWETH(requestIds: uint256[]) <<onlyOperatorOrOwner>> <<LidoLiquidityManager>> +    initialize(_name: string, _symbol: string, _operator: address, _fee: uint256, _feeCollector: address, _liquidityProviderController: address) <<initializer>> <<LidoARM>> +Public: +    <<event>> AdminChanged(previousAdmin: address, newAdmin: address) <<Ownable>> +    <<event>> OperatorChanged(newAdmin: address) <<OwnableOperable>> +    <<event>> TraderateChanged(traderate0: uint256, traderate1: uint256) <<AbstractARM>> +    <<event>> RedeemRequested(withdrawer: address, requestId: uint256, assets: uint256, queued: uint256, claimTimestamp: uint256) <<AbstractARM>> +    <<event>> RedeemClaimed(withdrawer: address, requestId: uint256, assets: uint256) <<AbstractARM>> +    <<event>> FeeCalculated(newFeesAccrued: uint256, assetIncrease: uint256) <<AbstractARM>> +    <<event>> FeeCollected(feeCollector: address, fee: uint256) <<AbstractARM>> +    <<event>> FeeUpdated(fee: uint256) <<AbstractARM>> +    <<event>> FeeCollectorUpdated(newFeeCollector: address) <<AbstractARM>> +    <<event>> LiquidityProviderControllerUpdated(liquidityProviderController: address) <<AbstractARM>> +    <<modifier>> onlyOwner() <<Ownable>> +    <<modifier>> onlyOperatorOrOwner() <<OwnableOperable>> +    constructor() <<Ownable>> +    constructor(_steth: address, _weth: address, _lidoWithdrawalQueue: address) <<LidoARM>> +    totalAssets(): uint256 <<AbstractARM>> +    convertToShares(assets: uint256): (shares: uint256) <<AbstractARM>> +    convertToAssets(shares: uint256): (assets: uint256) <<AbstractARM>> + + + diff --git a/docs/LidoARMSquashed.svg b/docs/LidoARMSquashed.svg new file mode 100644 index 0000000..880ffda --- /dev/null +++ b/docs/LidoARMSquashed.svg @@ -0,0 +1,117 @@ + + + + + + +UmlClassDiagram + + + +13 + +LidoARM +../src/contracts/LidoARM.sol + +Private: +   _gap: uint256[49] <<OwnableOperable>> +   _gap: uint256[50] <<AbstractARM>> +   _gap: uint256[49] <<LidoLiquidityManager>> +Internal: +   OWNER_SLOT: bytes32 <<Ownable>> +   MIN_TOTAL_SUPPLY: uint256 <<AbstractARM>> +   DEAD_ACCOUNT: address <<AbstractARM>> +   liquidityAsset: address <<AbstractARM>> +Public: +   operator: address <<OwnableOperable>> +   MAX_PRICE_DEVIATION: uint256 <<AbstractARM>> +   PRICE_SCALE: uint256 <<AbstractARM>> +   CLAIM_DELAY: uint256 <<AbstractARM>> +   FEE_SCALE: uint256 <<AbstractARM>> +   token0: IERC20 <<AbstractARM>> +   token1: IERC20 <<AbstractARM>> +   traderate0: uint256 <<AbstractARM>> +   traderate1: uint256 <<AbstractARM>> +   withdrawsQueued: uint128 <<AbstractARM>> +   withdrawsClaimed: uint128 <<AbstractARM>> +   withdrawsClaimable: uint128 <<AbstractARM>> +   nextWithdrawalIndex: uint128 <<AbstractARM>> +   withdrawalRequests: mapping(uint256=>WithdrawalRequest) <<AbstractARM>> +   feeCollector: address <<AbstractARM>> +   fee: uint16 <<AbstractARM>> +   feesAccrued: uint112 <<AbstractARM>> +   lastTotalAssets: uint128 <<AbstractARM>> +   liquidityProviderController: address <<AbstractARM>> +   steth: IERC20 <<LidoLiquidityManager>> +   weth: IWETH <<LidoLiquidityManager>> +   withdrawalQueue: IStETHWithdrawal <<LidoLiquidityManager>> +   outstandingEther: uint256 <<LidoLiquidityManager>> + +Internal: +    _owner(): (ownerOut: address) <<Ownable>> +    _setOwner(newOwner: address) <<Ownable>> +    _onlyOwner() <<Ownable>> +    _initOwnableOperable(_operator: address) <<OwnableOperable>> +    _setOperator(newOperator: address) <<OwnableOperable>> +    _initARM(_operator: address, _name: string, _symbol: string, _fee: uint256, _feeCollector: address, _liquidityProviderController: address) <<AbstractARM>> +    _inDeadline(deadline: uint256) <<AbstractARM>> +    _transferAsset(asset: address, to: address, amount: uint256) <<LidoARM>> +    _transferAssetFrom(asset: address, from: address, to: address, amount: uint256) <<AbstractARM>> +    _swapExactTokensForTokens(inToken: IERC20, outToken: IERC20, amountIn: uint256, to: address): (amountOut: uint256) <<AbstractARM>> +    _swapTokensForExactTokens(inToken: IERC20, outToken: IERC20, amountOut: uint256, to: address): (amountIn: uint256) <<AbstractARM>> +    _setTraderates(_traderate0: uint256, _traderate1: uint256) <<AbstractARM>> +    _updateWithdrawalQueueLiquidity() <<AbstractARM>> +    _liquidityAvailable(): uint256 <<AbstractARM>> +    _rawTotalAssets(): uint256 <<AbstractARM>> +    _externalWithdrawQueue(): uint256 <<LidoARM>> +    _calcFee() <<AbstractARM>> +    _setFee(_fee: uint256) <<AbstractARM>> +    _setFeeCollector(_feeCollector: address) <<AbstractARM>> +    _initLidoLiquidityManager() <<LidoLiquidityManager>> +External: +    <<payable>> null() <<LidoLiquidityManager>> +    owner(): address <<Ownable>> +    setOwner(newOwner: address) <<onlyOwner>> <<Ownable>> +    setOperator(newOperator: address) <<onlyOwner>> <<OwnableOperable>> +    swapExactTokensForTokens(inToken: IERC20, outToken: IERC20, amountIn: uint256, amountOutMin: uint256, to: address) <<AbstractARM>> +    swapExactTokensForTokens(amountIn: uint256, amountOutMin: uint256, path: address[], to: address, deadline: uint256): (amounts: uint256[]) <<AbstractARM>> +    swapTokensForExactTokens(inToken: IERC20, outToken: IERC20, amountOut: uint256, amountInMax: uint256, to: address) <<AbstractARM>> +    swapTokensForExactTokens(amountOut: uint256, amountInMax: uint256, path: address[], to: address, deadline: uint256): (amounts: uint256[]) <<AbstractARM>> +    setPrices(buyT1: uint256, sellT1: uint256) <<onlyOperatorOrOwner>> <<AbstractARM>> +    previewDeposit(assets: uint256): (shares: uint256) <<AbstractARM>> +    deposit(assets: uint256): (shares: uint256) <<AbstractARM>> +    previewRedeem(shares: uint256): (assets: uint256) <<AbstractARM>> +    requestRedeem(shares: uint256): (requestId: uint256, assets: uint256) <<AbstractARM>> +    claimRedeem(requestId: uint256): (assets: uint256) <<AbstractARM>> +    setFee(_fee: uint256) <<onlyOwner>> <<AbstractARM>> +    setFeeCollector(_feeCollector: address) <<onlyOwner>> <<AbstractARM>> +    collectFees(): (fees: uint256) <<AbstractARM>> +    setLiquidityProviderController(_liquidityProviderController: address) <<onlyOwner>> <<AbstractARM>> +    approveStETH() <<onlyOperatorOrOwner>> <<LidoLiquidityManager>> +    requestStETHWithdrawalForETH(amounts: uint256[]): (requestIds: uint256[]) <<onlyOperatorOrOwner>> <<LidoLiquidityManager>> +    claimStETHWithdrawalForWETH(requestIds: uint256[]) <<onlyOperatorOrOwner>> <<LidoLiquidityManager>> +    initialize(_name: string, _symbol: string, _operator: address, _fee: uint256, _feeCollector: address, _liquidityProviderController: address) <<initializer>> <<LidoARM>> +Public: +    <<event>> AdminChanged(previousAdmin: address, newAdmin: address) <<Ownable>> +    <<event>> OperatorChanged(newAdmin: address) <<OwnableOperable>> +    <<event>> TraderateChanged(traderate0: uint256, traderate1: uint256) <<AbstractARM>> +    <<event>> RedeemRequested(withdrawer: address, requestId: uint256, assets: uint256, queued: uint256, claimTimestamp: uint256) <<AbstractARM>> +    <<event>> RedeemClaimed(withdrawer: address, requestId: uint256, assets: uint256) <<AbstractARM>> +    <<event>> FeeCalculated(newFeesAccrued: uint256, assetIncrease: uint256) <<AbstractARM>> +    <<event>> FeeCollected(feeCollector: address, fee: uint256) <<AbstractARM>> +    <<event>> FeeUpdated(fee: uint256) <<AbstractARM>> +    <<event>> FeeCollectorUpdated(newFeeCollector: address) <<AbstractARM>> +    <<event>> LiquidityProviderControllerUpdated(liquidityProviderController: address) <<AbstractARM>> +    <<modifier>> onlyOwner() <<Ownable>> +    <<modifier>> onlyOperatorOrOwner() <<OwnableOperable>> +    constructor() <<Ownable>> +    constructor(_steth: address, _weth: address, _lidoWithdrawalQueue: address) <<LidoARM>> +    totalAssets(): uint256 <<AbstractARM>> +    convertToShares(assets: uint256): (shares: uint256) <<AbstractARM>> +    convertToAssets(shares: uint256): (assets: uint256) <<AbstractARM>> + + + diff --git a/docs/generate.sh b/docs/generate.sh index aa0c8b5..4589e56 100644 --- a/docs/generate.sh +++ b/docs/generate.sh @@ -13,10 +13,10 @@ sol2uml storage ../src/contracts -c OethARM -o OethARMStorage.svg \ -st address,address \ --hideExpand gap,_gap -sol2uml ../src/contracts -v -hv -hf -he -hs -hl -hi -b LidoFixedPriceMultiLpARM -o LidoFixedPriceMultiLpARMHierarchy.svg -sol2uml ../src/contracts -s -d 0 -b LidoFixedPriceMultiLpARM -o LidoFixedPriceMultiLpARMSquashed.svg -sol2uml ../src/contracts -hp -s -d 0 -b LidoFixedPriceMultiLpARM -o LidoFixedPriceMultiLpARMPublicSquashed.svg -sol2uml storage ../src/contracts,../lib -c LidoFixedPriceMultiLpARM -o LidoFixedPriceMultiLpARMStorage.svg \ +sol2uml ../src/contracts -v -hv -hf -he -hs -hl -hi -b LidoARM -o LidoARMHierarchy.svg +sol2uml ../src/contracts -s -d 0 -b LidoARM -o LidoARMSquashed.svg +sol2uml ../src/contracts -hp -s -d 0 -b LidoARM -o LidoARMPublicSquashed.svg +sol2uml storage ../src/contracts,../lib -c LidoARM -o LidoARMStorage.svg \ -sn eip1967.proxy.implementation,eip1967.proxy.admin \ -st address,address \ --hideExpand gap,_gap diff --git a/script/deploy/mainnet/003_UpgradeLidoARMScript.sol b/script/deploy/mainnet/003_UpgradeLidoARMScript.sol index 62b3d51..9f8ae83 100644 --- a/script/deploy/mainnet/003_UpgradeLidoARMScript.sol +++ b/script/deploy/mainnet/003_UpgradeLidoARMScript.sol @@ -6,7 +6,7 @@ import "forge-std/console.sol"; import {Vm} from "forge-std/Vm.sol"; import {IERC20, IWETH, LegacyAMM} from "contracts/Interfaces.sol"; -import {LidoFixedPriceMultiLpARM} from "contracts/LidoFixedPriceMultiLpARM.sol"; +import {LidoARM} from "contracts/LidoARM.sol"; import {LiquidityProviderController} from "contracts/LiquidityProviderController.sol"; import {Proxy} from "contracts/Proxy.sol"; import {Mainnet} from "contracts/utils/Addresses.sol"; @@ -23,7 +23,7 @@ contract UpgradeLidoARMMainnetScript is AbstractDeployScript { Proxy lidoARMProxy; Proxy lpcProxy; - LidoFixedPriceMultiLpARM lidoARMImpl; + LidoARM lidoARMImpl; function _execute() internal override { console.log("Deploy:", DEPLOY_NAME); @@ -53,7 +53,7 @@ contract UpgradeLidoARMMainnetScript is AbstractDeployScript { liquidityProviderController.setLiquidityProviderCaps(liquidityProviders, 10 ether); // 6. Deploy Lido implementation - lidoARMImpl = new LidoFixedPriceMultiLpARM(Mainnet.STETH, Mainnet.WETH, Mainnet.LIDO_WITHDRAWAL); + lidoARMImpl = new LidoARM(Mainnet.STETH, Mainnet.WETH, Mainnet.LIDO_WITHDRAWAL); _recordDeploy("LIDO_ARM_IMPL", address(lidoARMImpl)); // 7. Transfer ownership of LiquidityProviderController to the mainnet 5/8 multisig @@ -113,7 +113,7 @@ contract UpgradeLidoARMMainnetScript is AbstractDeployScript { lidoARMProxy.upgradeToAndCall(address(lidoARMImpl), data); // Set the buy price with a 8 basis point discount. The sell price is 1.0 - LidoFixedPriceMultiLpARM(payable(Mainnet.LIDO_ARM)).setPrices(9994e32, 1e36); + LidoARM(payable(Mainnet.LIDO_ARM)).setPrices(9994e32, 1e36); // transfer ownership of the Lido ARM proxy to the mainnet 5/8 multisig lidoARMProxy.setOwner(Mainnet.GOV_MULTISIG); diff --git a/src/contracts/AbstractARM.sol b/src/contracts/AbstractARM.sol index 8132261..74d8c85 100644 --- a/src/contracts/AbstractARM.sol +++ b/src/contracts/AbstractARM.sol @@ -1,10 +1,38 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.23; +import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; +import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; + import {OwnableOperable} from "./OwnableOperable.sol"; -import {IERC20} from "./Interfaces.sol"; +import {IERC20, ILiquidityProviderController} from "./Interfaces.sol"; + +abstract contract AbstractARM is OwnableOperable, ERC20Upgradeable { + //////////////////////////////////////////////////// + /// Constants + //////////////////////////////////////////////////// + + /// @notice Maximum amount the Operator can set the price from 1 scaled to 36 decimals. + /// 1e33 is a 0.1% deviation, or 10 basis points. + uint256 public constant MAX_PRICE_DEVIATION = 1e33; + /// @notice Scale of the prices. + uint256 public constant PRICE_SCALE = 1e36; + /// @notice The delay before a withdrawal request can be claimed in seconds + uint256 public constant CLAIM_DELAY = 10 minutes; + /// @dev The amount of shares that are minted to a dead address on initalization + uint256 internal constant MIN_TOTAL_SUPPLY = 1e12; + /// @dev The address with no known private key that the initial shares are minted to + address internal constant DEAD_ACCOUNT = 0x000000000000000000000000000000000000dEaD; + /// @notice The scale of the performance fee + /// 10,000 = 100% performance fee + uint256 public constant FEE_SCALE = 10000; -abstract contract AbstractARM is OwnableOperable { + //////////////////////////////////////////////////// + /// Immutable Variables + //////////////////////////////////////////////////// + + /// @notice The address of the asset that is used to add and remove liquidity. eg WETH + address internal immutable liquidityAsset; /// @notice The swap input token that is transferred to this contract. /// From a User perspective, this is the token being sold. /// token0 is also compatible with the Uniswap V2 Router interface. @@ -14,9 +42,85 @@ abstract contract AbstractARM is OwnableOperable { /// token1 is also compatible with the Uniswap V2 Router interface. IERC20 public immutable token1; - uint256[50] private _gap; + //////////////////////////////////////////////////// + /// Storage Variables + //////////////////////////////////////////////////// + + /** + * @notice For one `token0` from a Trader, how many `token1` does the pool send. + * For example, if `token0` is WETH and `token1` is stETH then + * `traderate0` is the WETH/stETH price. + * From a Trader's perspective, this is the stETH/WETH buy price. + * Rate is to 36 decimals (1e36). + */ + uint256 public traderate0; + /** + * @notice For one `token1` from a Trader, how many `token0` does the pool send. + * For example, if `token0` is WETH and `token1` is stETH then + * `traderate1` is the stETH/WETH price. + * From a Trader's perspective, this is the stETH/WETH sell price. + * Rate is to 36 decimals (1e36). + */ + uint256 public traderate1; + + /// @notice cumulative total of all withdrawal requests included the ones that have already been claimed + uint128 public withdrawsQueued; + /// @notice total of all the withdrawal requests that have been claimed + uint128 public withdrawsClaimed; + /// @notice cumulative total of all the withdrawal requests that can be claimed including the ones already claimed + uint128 public withdrawsClaimable; + /// @notice index of the next withdrawal request starting at 0 + uint128 public nextWithdrawalIndex; + + struct WithdrawalRequest { + address withdrawer; + bool claimed; + // When the withdrawal can be claimed + uint40 claimTimestamp; + // Amount of assets to withdraw + uint128 assets; + // cumulative total of all withdrawal requests including this one. + // this request can be claimed when this queued amount is less than or equal to the queue's claimable amount. + uint128 queued; + } + + /// @notice Mapping of withdrawal request indices to the user withdrawal request data + mapping(uint256 requestId => WithdrawalRequest) public withdrawalRequests; + + /// @notice The account that can collect the performance fee + address public feeCollector; + /// @notice Performance fee that is collected by the feeCollector measured in basis points (1/100th of a percent) + /// 10,000 = 100% performance fee + /// 2,000 = 20% performance fee + /// 500 = 5% performance fee + uint16 public fee; + /// @notice The performance fees accrued but not collected. + /// This is removed from the total assets. + uint112 public feesAccrued; + /// @notice The total assets at the last time performance fees were calculated. + /// This can only go up so is a high watermark. + uint128 public lastTotalAssets; + + address public liquidityProviderController; + + uint256[42] private _gap; + + //////////////////////////////////////////////////// + /// Events + //////////////////////////////////////////////////// + + event TraderateChanged(uint256 traderate0, uint256 traderate1); + event RedeemRequested( + address indexed withdrawer, uint256 indexed requestId, uint256 assets, uint256 queued, uint256 claimTimestamp + ); + event RedeemClaimed(address indexed withdrawer, uint256 indexed requestId, uint256 assets); + event FeeCalculated(uint256 newFeesAccrued, uint256 assetIncrease); + event FeeCollected(address indexed feeCollector, uint256 fee); + event FeeUpdated(uint256 fee); + event FeeCollectorUpdated(address indexed newFeeCollector); + event LiquidityProviderControllerUpdated(address indexed liquidityProviderController); - constructor(address _inputToken, address _outputToken1) { + constructor(address _inputToken, address _outputToken1, address _liquidityAsset) { require(IERC20(_inputToken).decimals() == 18); require(IERC20(_outputToken1).decimals() == 18); @@ -24,8 +128,54 @@ abstract contract AbstractARM is OwnableOperable { token1 = IERC20(_outputToken1); _setOwner(address(0)); // Revoke owner for implementation contract at deployment + + require(_liquidityAsset == address(token0) || _liquidityAsset == address(token1), "invalid liquidity asset"); + liquidityAsset = _liquidityAsset; } + /// @notice Initialize the contract. + /// The deployer that calls initialize has to approve the this ARM's proxy contract to transfer 1e12 WETH. + /// @param _operator The address of the account that can request and claim Lido withdrawals. + /// @param _name The name of the liquidity provider (LP) token. + /// @param _symbol The symbol of the liquidity provider (LP) token. + /// @param _fee The performance fee that is collected by the feeCollector measured in basis points (1/100th of a percent). + /// 10,000 = 100% performance fee + /// 500 = 5% performance fee + /// @param _feeCollector The account that can collect the performance fee + /// @param _liquidityProviderController The address of the Liquidity Provider Controller + function _initARM( + address _operator, + string calldata _name, + string calldata _symbol, + uint256 _fee, + address _feeCollector, + address _liquidityProviderController + ) internal { + _initOwnableOperable(_operator); + + __ERC20_init(_name, _symbol); + + // Transfer a small bit of liquidity from the intializer to this contract + IERC20(liquidityAsset).transferFrom(msg.sender, address(this), MIN_TOTAL_SUPPLY); + + // mint a small amount of shares to a dead account so the total supply can never be zero + // This avoids donation attacks when there are no assets in the ARM contract + _mint(DEAD_ACCOUNT, MIN_TOTAL_SUPPLY); + + // Initialize the last total assets to the current total assets + // This ensures no performance fee is accrued when the performance fee is calculated when the fee is set + lastTotalAssets = SafeCast.toUint128(_rawTotalAssets()); + _setFee(_fee); + _setFeeCollector(_feeCollector); + + liquidityProviderController = _liquidityProviderController; + emit LiquidityProviderControllerUpdated(_liquidityProviderController); + } + + //////////////////////////////////////////////////// + /// Swap Functions + //////////////////////////////////////////////////// + /** * @notice Swaps an exact amount of input tokens for as many output tokens as possible. * msg.sender should have already given the ARM contract an allowance of @@ -141,27 +291,396 @@ abstract contract AbstractARM is OwnableOperable { amounts[1] = amountOut; } + function _inDeadline(uint256 deadline) internal view { + require(deadline >= block.timestamp, "ARM: Deadline expired"); + } + + /// @dev Ensure any liquidity assets reserved for the withdrawal queue are not used + /// in swaps that send liquidity assets out of the ARM + function _transferAsset(address asset, address to, uint256 amount) internal virtual { + if (asset == liquidityAsset) { + require(amount <= _liquidityAvailable(), "ARM: Insufficient liquidity"); + } + + IERC20(asset).transfer(to, amount); + } + + /// @dev Hook to transfer assets into the ARM contract + function _transferAssetFrom(address asset, address from, address to, uint256 amount) internal virtual { + IERC20(asset).transferFrom(from, to, amount); + } + function _swapExactTokensForTokens(IERC20 inToken, IERC20 outToken, uint256 amountIn, address to) internal virtual - returns (uint256 amountOut); + returns (uint256 amountOut) + { + uint256 price; + if (inToken == token0) { + require(outToken == token1, "ARM: Invalid out token"); + price = traderate0; + } else if (inToken == token1) { + require(outToken == token0, "ARM: Invalid out token"); + price = traderate1; + } else { + revert("ARM: Invalid in token"); + } + amountOut = amountIn * price / PRICE_SCALE; + + // Transfer the input tokens from the caller to this ARM contract + _transferAssetFrom(address(inToken), msg.sender, address(this), amountIn); + + // Transfer the output tokens to the recipient + _transferAsset(address(outToken), to, amountOut); + } function _swapTokensForExactTokens(IERC20 inToken, IERC20 outToken, uint256 amountOut, address to) internal virtual - returns (uint256 amountIn); + returns (uint256 amountIn) + { + uint256 price; + if (inToken == token0) { + require(outToken == token1, "ARM: Invalid out token"); + price = traderate0; + } else if (inToken == token1) { + require(outToken == token0, "ARM: Invalid out token"); + price = traderate1; + } else { + revert("ARM: Invalid in token"); + } + amountIn = ((amountOut * PRICE_SCALE) / price) + 1; // +1 to always round in our favor - function _inDeadline(uint256 deadline) internal view { - require(deadline >= block.timestamp, "ARM: Deadline expired"); + // Transfer the input tokens from the caller to this ARM contract + _transferAssetFrom(address(inToken), msg.sender, address(this), amountIn); + + // Transfer the output tokens to the recipient + _transferAsset(address(outToken), to, amountOut); } - /// @dev Hook to transfer assets out of the ARM contract - function _transferAsset(address asset, address to, uint256 amount) internal virtual { - IERC20(asset).transfer(to, amount); + /** + * @notice Set exchange rates from an operator account from the ARM's perspective. + * If token 0 is WETH and token 1 is stETH, then both prices will be set using the stETH/WETH price. + * @param buyT1 The price the ARM buys Token 1 from the Trader, denominated in Token 0, scaled to 36 decimals. + * From the Trader's perspective, this is the sell price. + * @param sellT1 The price the ARM sells Token 1 to the Trader, denominated in Token 0, scaled to 36 decimals. + * From the Trader's perspective, this is the buy price. + */ + function setPrices(uint256 buyT1, uint256 sellT1) external onlyOperatorOrOwner { + // Limit funds and loss when called by the Operator + if (msg.sender == operator) { + require(sellT1 >= PRICE_SCALE - MAX_PRICE_DEVIATION, "ARM: sell price too low"); + require(buyT1 <= PRICE_SCALE + MAX_PRICE_DEVIATION, "ARM: buy price too high"); + } + uint256 _traderate0 = 1e72 / sellT1; // base (t0) -> token (t1) + uint256 _traderate1 = buyT1; // token (t1) -> base (t0) + _setTraderates(_traderate0, _traderate1); } - /// @dev Hook to transfer assets into the ARM contract - function _transferAssetFrom(address asset, address from, address to, uint256 amount) internal virtual { - IERC20(asset).transferFrom(from, to, amount); + function _setTraderates(uint256 _traderate0, uint256 _traderate1) internal { + require((1e72 / (_traderate0)) > _traderate1, "ARM: Price cross"); + traderate0 = _traderate0; + traderate1 = _traderate1; + + emit TraderateChanged(_traderate0, _traderate1); + } + + //////////////////////////////////////////////////// + /// Liquidity Provider Functions + //////////////////////////////////////////////////// + + /// @notice Preview the amount of shares that would be minted for a given amount of assets + /// @param assets The amount of liquidity assets to deposit + /// @return shares The amount of shares that would be minted + function previewDeposit(uint256 assets) external view returns (uint256 shares) { + shares = convertToShares(assets); + } + + /// @notice deposit liquidity assets in exchange for liquidity provider (LP) shares. + /// The caller needs to have approved the contract to transfer the assets. + /// @param assets The amount of liquidity assets to deposit + /// @return shares The amount of shares that were minted + function deposit(uint256 assets) external returns (uint256 shares) { + // Accrue any performance fees based on the increase in total assets before + // the liquidity asset from the deposit is transferred into the ARM + _accruePerformanceFee(); + + // Calculate the amount of shares to mint after the performance fees have been accrued + // which reduces the total assets and before new assets are deposited. + shares = convertToShares(assets); + + // Transfer the liquidity asset from the sender to this contract + IERC20(liquidityAsset).transferFrom(msg.sender, address(this), assets); + + // mint shares + _mint(msg.sender, shares); + + // Save the new total assets after the performance fee accrued and new assets deposited + lastTotalAssets = SafeCast.toUint128(_rawTotalAssets()); + + // Check the liquidity provider caps after the new assets have been deposited + if (liquidityProviderController != address(0)) { + ILiquidityProviderController(liquidityProviderController).postDepositHook(msg.sender, assets); + } + } + + /// @notice Preview the amount of assets that would be received for burning a given amount of shares + /// @param shares The amount of shares to burn + /// @return assets The amount of liquidity assets that would be received + function previewRedeem(uint256 shares) external view returns (uint256 assets) { + assets = convertToAssets(shares); + } + + /// @notice Request to redeem liquidity provider shares for liquidity assets + /// @param shares The amount of shares the redeemer wants to burn for liquidity assets + /// @return requestId The index of the withdrawal request + /// @return assets The amount of liquidity assets that will be claimable by the redeemer + function requestRedeem(uint256 shares) external returns (uint256 requestId, uint256 assets) { + // Accrue any performance fees based on the increase in total assets before + // the liquidity asset from the redeem is reserved for the ARM withdrawal queue + _accruePerformanceFee(); + + // Calculate the amount of assets to transfer to the redeemer + assets = convertToAssets(shares); + + requestId = nextWithdrawalIndex; + uint128 queued = SafeCast.toUint128(withdrawsQueued + assets); + uint40 claimTimestamp = uint40(block.timestamp + CLAIM_DELAY); + + // Store the next withdrawal request + nextWithdrawalIndex = SafeCast.toUint128(requestId + 1); + // Store the updated queued amount which reserves WETH in the withdrawal queue + withdrawsQueued = queued; + // Store requests + withdrawalRequests[requestId] = WithdrawalRequest({ + withdrawer: msg.sender, + claimed: false, + claimTimestamp: claimTimestamp, + assets: SafeCast.toUint128(assets), + queued: queued + }); + + // burn redeemer's shares + _burn(msg.sender, shares); + + // Save the new total assets after performance fee accrued and withdrawal queue updated + lastTotalAssets = SafeCast.toUint128(_rawTotalAssets()); + + emit RedeemRequested(msg.sender, requestId, assets, queued, claimTimestamp); + } + + /// @notice Claim liquidity assets from a previous withdrawal request after the claim delay has passed + /// @param requestId The index of the withdrawal request + /// @return assets The amount of liquidity assets that were transferred to the redeemer + function claimRedeem(uint256 requestId) external returns (uint256 assets) { + // Update the ARM's withdrawal queue's claimable amount + _updateWithdrawalQueueLiquidity(); + + // Load the structs from storage into memory + WithdrawalRequest memory request = withdrawalRequests[requestId]; + + require(request.claimTimestamp <= block.timestamp, "Claim delay not met"); + // If there isn't enough reserved liquidity in the queue to claim + require(request.queued <= withdrawsClaimable, "Queue pending liquidity"); + require(request.withdrawer == msg.sender, "Not requester"); + require(request.claimed == false, "Already claimed"); + + // Store the request as claimed + withdrawalRequests[requestId].claimed = true; + // Store the updated claimed amount + withdrawsClaimed += request.assets; + + assets = request.assets; + + emit RedeemClaimed(msg.sender, requestId, assets); + + // transfer the liquidity asset to the withdrawer + IERC20(liquidityAsset).transfer(msg.sender, assets); + } + + /// @dev Updates the claimable amount in the ARM's withdrawal queue. + /// That's the amount that is used to check if a request can be claimed or not. + function _updateWithdrawalQueueLiquidity() internal { + // Load the claimable amount from storage into memory + uint256 withdrawsClaimableMem = withdrawsClaimable; + + // Check if the claimable amount is less than the queued amount + uint256 queueShortfall = withdrawsQueued - withdrawsClaimableMem; + + // No need to do anything is the withdrawal queue is fully funded + if (queueShortfall == 0) { + return; + } + + uint256 liquidityBalance = IERC20(liquidityAsset).balanceOf(address(this)); + + // Of the claimable withdrawal requests, how much is unclaimed? + // That is, the amount of the liquidity assets that is currently allocated for the withdrawal queue + uint256 allocatedLiquidity = withdrawsClaimableMem - withdrawsClaimed; + + // If there is no unallocated liquidity assets then there is nothing to add to the queue + if (liquidityBalance <= allocatedLiquidity) { + return; + } + + uint256 unallocatedLiquidity = liquidityBalance - allocatedLiquidity; + + // the new claimable amount is the smaller of the queue shortfall or unallocated weth + uint256 addedClaimable = queueShortfall < unallocatedLiquidity ? queueShortfall : unallocatedLiquidity; + + // Store the new claimable amount back to storage + withdrawsClaimable = SafeCast.toUint128(withdrawsClaimableMem + addedClaimable); + } + + /// @dev Calculate how much of the liquidity asset in the ARM is not reserved for the withdrawal queue. + // That is, it is available to be swapped. + function _liquidityAvailable() internal view returns (uint256) { + // The amount of WETH that is still to be claimed in the withdrawal queue + uint256 outstandingWithdrawals = withdrawsQueued - withdrawsClaimed; + + // The amount of the liquidity asset is in the ARM + uint256 liquidityBalance = IERC20(liquidityAsset).balanceOf(address(this)); + + // If there is not enough liquidity assets in the ARM to cover the outstanding withdrawals + if (liquidityBalance <= outstandingWithdrawals) { + return 0; + } + + return liquidityBalance - outstandingWithdrawals; + } + + /// @notice The total amount of assets in the ARM and external withdrawal queue, + /// less the liquidity assets reserved for the ARM's withdrawal queue and accrued fees. + function totalAssets() public view virtual returns (uint256) { + uint256 totalAssetsBeforeFees = _rawTotalAssets(); + + // If the total assets have decreased, then we don't charge a performance fee + if (totalAssetsBeforeFees <= lastTotalAssets) return totalAssetsBeforeFees; + + // Calculate the increase in assets since the last time fees were calculated + uint256 assetIncrease = totalAssetsBeforeFees - lastTotalAssets; + + // Calculate the performance fee and remove from the total assets before new fees are removed + return totalAssetsBeforeFees - ((assetIncrease * fee) / FEE_SCALE); + } + + /// @dev Calculate the total assets in the ARM, external withdrawal queue, + /// less liquidity assets reserved for the ARM's withdrawal queue and past accrued fees. + /// The accrued fees are from the last time fees were calculated. + function _rawTotalAssets() internal view returns (uint256) { + // Get the assets in the ARM and external withdrawal queue + uint256 assets = token0.balanceOf(address(this)) + token1.balanceOf(address(this)) + _externalWithdrawQueue(); + + // Load the queue metadata from storage into memory + uint256 queuedMem = withdrawsQueued; + uint256 claimedMem = withdrawsClaimed; + + // If the ARM becomes insolvent enough that the total value in the ARM and external withdrawal queue + // is less than the outstanding withdrawals. + if (assets + claimedMem < queuedMem) { + return 0; + } + + // Need to remove the liquidity assets that have been reserved for the withdrawal queue + // and any accrued fees + return assets + claimedMem - queuedMem - feesAccrued; + } + + /// @dev Hook for calculating the amount of assets in an external withdrawal queue like Lido or OETH + /// This is not the ARM's withdrawal queue + function _externalWithdrawQueue() internal view virtual returns (uint256 assets); + + /// @notice Calculates the amount of shares for a given amount of liquidity assets + function convertToShares(uint256 assets) public view returns (uint256 shares) { + uint256 totalAssetsMem = totalAssets(); + shares = (totalAssetsMem == 0) ? assets : (assets * totalSupply()) / totalAssetsMem; + } + + /// @notice Calculates the amount of liquidity assets for a given amount of shares + function convertToAssets(uint256 shares) public view returns (uint256 assets) { + assets = (shares * totalAssets()) / totalSupply(); + } + + /// @notice Set the Liquidity Provider Controller contract address. + /// Set to a zero address to disable the controller. + function setLiquidityProviderController(address _liquidityProviderController) external onlyOwner { + liquidityProviderController = _liquidityProviderController; + + emit LiquidityProviderControllerUpdated(_liquidityProviderController); + } + + //////////////////////////////////////////////////// + /// Performance Fee Functions + //////////////////////////////////////////////////// + + /// @dev Accrues the performance fee based on the increase in total assets + /// Needs to be called before any action that changes the liquidity provider shares. eg deposit and redeem + function _accruePerformanceFee() internal { + uint256 newTotalAssets = _rawTotalAssets(); + + // Do not accrued a performance fee if the total assets has decreased + if (newTotalAssets <= lastTotalAssets) return; + + uint256 assetIncrease = newTotalAssets - lastTotalAssets; + uint256 newFeesAccrued = (assetIncrease * fee) / FEE_SCALE; + + // Save the new accrued fees back to storage + feesAccrued = SafeCast.toUint112(feesAccrued + newFeesAccrued); + // Save the new total assets back to storage less the new accrued fees. + // This is be updated again in the post deposit and post withdraw hooks to include + // the assets deposited or withdrawn + lastTotalAssets = SafeCast.toUint128(newTotalAssets - newFeesAccrued); + + emit FeeCalculated(newFeesAccrued, assetIncrease); + } + + /// @notice Owner sets the performance fee on increased assets + /// @param _fee The performance fee measured in basis points (1/100th of a percent) + /// 10,000 = 100% performance fee + /// 500 = 5% performance fee + function setFee(uint256 _fee) external onlyOwner { + _setFee(_fee); + } + + /// @notice Owner sets the account/contract that receives the performance fee + function setFeeCollector(address _feeCollector) external onlyOwner { + _setFeeCollector(_feeCollector); + } + + function _setFee(uint256 _fee) internal { + require(_fee <= FEE_SCALE, "ARM: fee too high"); + + // Accrued any performance fees up to this point using the old fee + _accruePerformanceFee(); + + fee = SafeCast.toUint16(_fee); + + emit FeeUpdated(_fee); + } + + function _setFeeCollector(address _feeCollector) internal { + require(_feeCollector != address(0), "ARM: invalid fee collector"); + + feeCollector = _feeCollector; + + emit FeeCollectorUpdated(_feeCollector); + } + + /// @notice Transfer accrued performance fees to the fee collector + /// This requires enough liquidity assets in the ARM to cover the accrued fees. + function collectFees() external returns (uint256 fees) { + // Accrue any performance fees up to this point + _accruePerformanceFee(); + + // Read the updated accrued fees from storage + fees = feesAccrued; + require(fees <= IERC20(liquidityAsset).balanceOf(address(this)), "ARM: insufficient liquidity"); + + // Reset the accrued fees in storage + feesAccrued = 0; + + IERC20(liquidityAsset).transfer(feeCollector, fees); + + emit FeeCollected(feeCollector, fees); } } diff --git a/src/contracts/FixedPriceARM.sol b/src/contracts/FixedPriceARM.sol deleted file mode 100644 index c6587aa..0000000 --- a/src/contracts/FixedPriceARM.sol +++ /dev/null @@ -1,113 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {AbstractARM} from "./AbstractARM.sol"; -import {IERC20} from "./Interfaces.sol"; - -/** - * @title Abstract support to an ARM with a single buy and sell price. - * @author Origin Protocol Inc - */ -abstract contract FixedPriceARM is AbstractARM { - /** - * @notice For one `token0` from a Trader, how many `token1` does the pool send. - * For example, if `token0` is WETH and `token1` is stETH then - * `traderate0` is the WETH/stETH price. - * From a Trader's perspective, this is the stETH/WETH buy price. - * Rate is to 36 decimals (1e36). - */ - uint256 public traderate0; - /** - * @notice For one `token1` from a Trader, how many `token0` does the pool send. - * For example, if `token0` is WETH and `token1` is stETH then - * `traderate1` is the stETH/WETH price. - * From a Trader's perspective, this is the stETH/WETH sell price. - * Rate is to 36 decimals (1e36). - */ - uint256 public traderate1; - - /// @notice Maximum amount the Operator can set the price from 1 scaled to 36 decimals. - /// 1e33 is a 0.1% deviation, or 10 basis points. - uint256 public constant MAX_PRICE_DEVIATION = 1e33; - /// @notice Scale of the prices. - uint256 public constant PRICE_SCALE = 1e36; - - uint256[48] private _gap; - - event TraderateChanged(uint256 traderate0, uint256 traderate1); - - function _swapExactTokensForTokens(IERC20 inToken, IERC20 outToken, uint256 amountIn, address to) - internal - override - returns (uint256 amountOut) - { - uint256 price; - if (inToken == token0) { - require(outToken == token1, "ARM: Invalid out token"); - price = traderate0; - } else if (inToken == token1) { - require(outToken == token0, "ARM: Invalid out token"); - price = traderate1; - } else { - revert("ARM: Invalid in token"); - } - amountOut = amountIn * price / PRICE_SCALE; - - // Transfer the input tokens from the caller to this ARM contract - _transferAssetFrom(address(inToken), msg.sender, address(this), amountIn); - - // Transfer the output tokens to the recipient - _transferAsset(address(outToken), to, amountOut); - } - - function _swapTokensForExactTokens(IERC20 inToken, IERC20 outToken, uint256 amountOut, address to) - internal - override - returns (uint256 amountIn) - { - uint256 price; - if (inToken == token0) { - require(outToken == token1, "ARM: Invalid out token"); - price = traderate0; - } else if (inToken == token1) { - require(outToken == token0, "ARM: Invalid out token"); - price = traderate1; - } else { - revert("ARM: Invalid in token"); - } - amountIn = ((amountOut * PRICE_SCALE) / price) + 1; // +1 to always round in our favor - - // Transfer the input tokens from the caller to this ARM contract - _transferAssetFrom(address(inToken), msg.sender, address(this), amountIn); - - // Transfer the output tokens to the recipient - _transferAsset(address(outToken), to, amountOut); - } - - /** - * @notice Set exchange rates from an operator account from the ARM's perspective. - * If token 0 is WETH and token 1 is stETH, then both prices will be set using the stETH/WETH price. - * @param buyT1 The price the ARM buys Token 1 from the Trader, denominated in Token 0, scaled to 36 decimals. - * From the Trader's perspective, this is the sell price. - * @param sellT1 The price the ARM sells Token 1 to the Trader, denominated in Token 0, scaled to 36 decimals. - * From the Trader's perspective, this is the buy price. - */ - function setPrices(uint256 buyT1, uint256 sellT1) external onlyOperatorOrOwner { - // Limit funds and loss when called by the Operator - if (msg.sender == operator) { - require(sellT1 >= PRICE_SCALE - MAX_PRICE_DEVIATION, "ARM: sell price too low"); - require(buyT1 <= PRICE_SCALE + MAX_PRICE_DEVIATION, "ARM: buy price too high"); - } - uint256 _traderate0 = 1e72 / sellT1; // base (t0) -> token (t1) - uint256 _traderate1 = buyT1; // token (t1) -> base (t0) - _setTraderates(_traderate0, _traderate1); - } - - function _setTraderates(uint256 _traderate0, uint256 _traderate1) internal { - require((1e72 / (_traderate0)) > _traderate1, "ARM: Price cross"); - traderate0 = _traderate0; - traderate1 = _traderate1; - - emit TraderateChanged(_traderate0, _traderate1); - } -} diff --git a/src/contracts/LidoFixedPriceMultiLpARM.sol b/src/contracts/LidoARM.sol similarity index 56% rename from src/contracts/LidoFixedPriceMultiLpARM.sol rename to src/contracts/LidoARM.sol index 5b2a1a8..b2aeed5 100644 --- a/src/contracts/LidoFixedPriceMultiLpARM.sol +++ b/src/contracts/LidoARM.sol @@ -5,11 +5,7 @@ import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Ini import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; import {AbstractARM} from "./AbstractARM.sol"; -import {LiquidityProviderControllerARM} from "./LiquidityProviderControllerARM.sol"; -import {FixedPriceARM} from "./FixedPriceARM.sol"; import {LidoLiquidityManager} from "./LidoLiquidityManager.sol"; -import {MultiLP} from "./MultiLP.sol"; -import {PerformanceFee} from "./PerformanceFee.sol"; /** * @title Lido (stETH) Application Redemption Manager (ARM) @@ -19,32 +15,23 @@ import {PerformanceFee} from "./PerformanceFee.sol"; * A performance fee is also collected on increases in the ARM's total assets. * @author Origin Protocol Inc */ -contract LidoFixedPriceMultiLpARM is - Initializable, - MultiLP, - PerformanceFee, - LiquidityProviderControllerARM, - FixedPriceARM, - LidoLiquidityManager -{ +contract LidoARM is Initializable, AbstractARM, LidoLiquidityManager { /// @param _steth The address of the stETH token /// @param _weth The address of the WETH token /// @param _lidoWithdrawalQueue The address of the Lido's withdrawal queue contract constructor(address _steth, address _weth, address _lidoWithdrawalQueue) - AbstractARM(_steth, _weth) - MultiLP(_weth) - FixedPriceARM() + AbstractARM(_steth, _weth, _weth) LidoLiquidityManager(_steth, _weth, _lidoWithdrawalQueue) {} - /// @notice Initialize the contract. + /// @notice Initialize the storage variables stored in the proxy contract. /// The deployer that calls initialize has to approve the this ARM's proxy contract to transfer 1e12 WETH. /// @param _name The name of the liquidity provider (LP) token. /// @param _symbol The symbol of the liquidity provider (LP) token. /// @param _operator The address of the account that can request and claim Lido withdrawals. /// @param _fee The performance fee that is collected by the feeCollector measured in basis points (1/100th of a percent). /// 10,000 = 100% performance fee - /// 500 = 5% performance fee + /// 1,500 = 15% performance fee /// @param _feeCollector The account that can collect the performance fee /// @param _liquidityProviderController The address of the Liquidity Provider Controller function initialize( @@ -55,10 +42,8 @@ contract LidoFixedPriceMultiLpARM is address _feeCollector, address _liquidityProviderController ) external initializer { - _initOwnableOperable(_operator); - _initMultiLP(_name, _symbol); - _initPerformanceFee(_fee, _feeCollector); - _initLPControllerARM(_liquidityProviderController); + _initARM(_operator, _name, _symbol, _fee, _feeCollector, _liquidityProviderController); + _initLidoLiquidityManager(); } /** @@ -67,47 +52,17 @@ contract LidoFixedPriceMultiLpARM is * * The MultiLP implementation ensures any WETH reserved for the withdrawal queue is not used in swaps from stETH to WETH. */ - function _transferAsset(address asset, address to, uint256 amount) internal override(AbstractARM, MultiLP) { + function _transferAsset(address asset, address to, uint256 amount) internal override { // Add 2 wei if transferring stETH uint256 transferAmount = asset == address(token0) ? amount + 2 : amount; - MultiLP._transferAsset(asset, to, transferAmount); + super._transferAsset(asset, to, transferAmount); } /** * @dev Calculates the amount of stETH in the Lido Withdrawal Queue. */ - function _externalWithdrawQueue() internal view override(MultiLP, LidoLiquidityManager) returns (uint256) { + function _externalWithdrawQueue() internal view override(AbstractARM, LidoLiquidityManager) returns (uint256) { return LidoLiquidityManager._externalWithdrawQueue(); } - - /** - * @dev Is called after assets are transferred to the ARM in the `deposit` method. - */ - function _postDepositHook(uint256 assets) - internal - override(MultiLP, LiquidityProviderControllerARM, PerformanceFee) - { - // Store the new total assets after the deposit and performance fee accrued - PerformanceFee._postDepositHook(assets); - - // Check the LP can deposit the assets - LiquidityProviderControllerARM._postDepositHook(assets); - } - - /** - * @dev Is called after the performance fee is accrued in the `requestRedeem` method. - */ - function _postRequestRedeemHook() internal override(MultiLP, PerformanceFee) { - // Store the new total assets after the withdrawal and performance fee accrued - PerformanceFee._postRequestRedeemHook(); - } - - /** - * @notice The total amount of assets in the ARM and Lido withdrawal queue, - * less the WETH reserved for the ARM's withdrawal queue and accrued fees. - */ - function totalAssets() public view override(MultiLP, PerformanceFee) returns (uint256) { - return PerformanceFee.totalAssets(); - } } diff --git a/src/contracts/LidoLiquidityManager.sol b/src/contracts/LidoLiquidityManager.sol index 311438b..5b27093 100644 --- a/src/contracts/LidoLiquidityManager.sol +++ b/src/contracts/LidoLiquidityManager.sol @@ -23,6 +23,13 @@ abstract contract LidoLiquidityManager is OwnableOperable { withdrawalQueue = IStETHWithdrawal(_lidoWithdrawalQueue); } + /** + * @dev Approve the stETH withdrawal contract. Used for redemption requests. + */ + function _initLidoLiquidityManager() internal { + steth.approve(address(withdrawalQueue), type(uint256).max); + } + /** * @notice Approve the stETH withdrawal contract. Used for redemption requests. */ diff --git a/src/contracts/LiquidityProviderControllerARM.sol b/src/contracts/LiquidityProviderControllerARM.sol deleted file mode 100644 index 07aefa9..0000000 --- a/src/contracts/LiquidityProviderControllerARM.sol +++ /dev/null @@ -1,40 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {MultiLP} from "./MultiLP.sol"; -import {ILiquidityProviderController} from "./Interfaces.sol"; - -/** - * @title ARM integration to the Liquidity Provider Controller that whitelists liquidity providers - * and enforces a total assets cap. - * @author Origin Protocol Inc - */ -abstract contract LiquidityProviderControllerARM is MultiLP { - address public liquidityProviderController; - - uint256[49] private _gap; - - event LiquidityProviderControllerUpdated(address indexed liquidityProviderController); - - /// @dev called in the ARM's initialize function to set the Liquidity Provider Controller - function _initLPControllerARM(address _liquidityProviderController) internal { - liquidityProviderController = _liquidityProviderController; - - emit LiquidityProviderControllerUpdated(_liquidityProviderController); - } - - /// @dev calls the liquidity provider controller if one is configured to check the liquidity provider and total assets caps - function _postDepositHook(uint256 assets) internal virtual override { - if (liquidityProviderController != address(0)) { - ILiquidityProviderController(liquidityProviderController).postDepositHook(msg.sender, assets); - } - } - - /// @notice Set the Liquidity Provider Controller contract address. - /// Set to a zero address to disable the controller. - function setLiquidityProviderController(address _liquidityProviderController) external onlyOwner { - liquidityProviderController = _liquidityProviderController; - - emit LiquidityProviderControllerUpdated(_liquidityProviderController); - } -} diff --git a/src/contracts/MultiLP.sol b/src/contracts/MultiLP.sol deleted file mode 100644 index 651927e..0000000 --- a/src/contracts/MultiLP.sol +++ /dev/null @@ -1,269 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {IERC20, ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; -import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; - -import {AbstractARM} from "./AbstractARM.sol"; - -/** - * @title Abstract support to an ARM for multiple Liquidity Providers (LP) - * @author Origin Protocol Inc - */ -abstract contract MultiLP is AbstractARM, ERC20Upgradeable { - /// @notice The delay before a withdrawal request can be claimed in seconds - uint256 public constant CLAIM_DELAY = 10 minutes; - /// @dev The amount of shares that are minted to a dead address on initalization - uint256 internal constant MIN_TOTAL_SUPPLY = 1e12; - /// @dev The address with no known private key that the initial shares are minted to - address internal constant DEAD_ACCOUNT = 0x000000000000000000000000000000000000dEaD; - - /// @notice The address of the asset that is used to add and remove liquidity. eg WETH - address internal immutable liquidityAsset; - - /// @notice cumulative total of all withdrawal requests included the ones that have already been claimed - uint128 public withdrawsQueued; - /// @notice total of all the withdrawal requests that have been claimed - uint128 public withdrawsClaimed; - /// @notice cumulative total of all the withdrawal requests that can be claimed including the ones already claimed - uint128 public withdrawsClaimable; - /// @notice index of the next withdrawal request starting at 0 - uint128 public nextWithdrawalIndex; - - struct WithdrawalRequest { - address withdrawer; - bool claimed; - // When the withdrawal can be claimed - uint40 claimTimestamp; - // Amount of assets to withdraw - uint128 assets; - // cumulative total of all withdrawal requests including this one. - // this request can be claimed when this queued amount is less than or equal to the queue's claimable amount. - uint128 queued; - } - - /// @notice Mapping of withdrawal request indices to the user withdrawal request data - mapping(uint256 requestId => WithdrawalRequest) public withdrawalRequests; - - uint256[47] private _gap; - - event RedeemRequested( - address indexed withdrawer, uint256 indexed requestId, uint256 assets, uint256 queued, uint256 claimTimestamp - ); - event RedeemClaimed(address indexed withdrawer, uint256 indexed requestId, uint256 assets); - - constructor(address _liquidityAsset) { - require(_liquidityAsset == address(token0) || _liquidityAsset == address(token1), "invalid liquidity asset"); - liquidityAsset = _liquidityAsset; - } - - /// @dev called by the concrete contract's `initialize` function - function _initMultiLP(string calldata _name, string calldata _symbol) internal { - __ERC20_init(_name, _symbol); - - // Transfer a small bit of liquidity from the intializer to this contract - IERC20(liquidityAsset).transferFrom(msg.sender, address(this), MIN_TOTAL_SUPPLY); - - // mint a small amount of shares to a dead account so the total supply can never be zero - // This avoids donation attacks when there are no assets in the ARM contract - _mint(DEAD_ACCOUNT, MIN_TOTAL_SUPPLY); - } - - /// @notice Preview the amount of shares that would be minted for a given amount of assets - /// @param assets The amount of liquidity assets to deposit - /// @return shares The amount of shares that would be minted - function previewDeposit(uint256 assets) external view returns (uint256 shares) { - shares = convertToShares(assets); - } - - /// @notice deposit liquidity assets in exchange for liquidity provider (LP) shares. - /// The caller needs to have approved the contract to transfer the assets. - /// @param assets The amount of liquidity assets to deposit - /// @return shares The amount of shares that were minted - function deposit(uint256 assets) external returns (uint256 shares) { - _preDepositHook(); - - shares = convertToShares(assets); - - // Transfer the liquidity asset from the sender to this contract - IERC20(liquidityAsset).transferFrom(msg.sender, address(this), assets); - - // mint shares - _mint(msg.sender, shares); - - _postDepositHook(assets); - } - - function _preDepositHook() internal virtual; - function _postDepositHook(uint256 assets) internal virtual; - - /// @notice Preview the amount of assets that would be received for burning a given amount of shares - /// @param shares The amount of shares to burn - /// @return assets The amount of liquidity assets that would be received - function previewRedeem(uint256 shares) external view returns (uint256 assets) { - assets = convertToAssets(shares); - } - - /// @notice Request to redeem liquidity provider shares for liquidity assets - /// @param shares The amount of shares the redeemer wants to burn for liquidity assets - /// @return requestId The index of the withdrawal request - /// @return assets The amount of liquidity assets that will be claimable by the redeemer - function requestRedeem(uint256 shares) external returns (uint256 requestId, uint256 assets) { - _preWithdrawHook(); - - // Calculate the amount of assets to transfer to the redeemer - assets = convertToAssets(shares); - - requestId = nextWithdrawalIndex; - uint128 queued = SafeCast.toUint128(withdrawsQueued + assets); - uint40 claimTimestamp = uint40(block.timestamp + CLAIM_DELAY); - - // Store the next withdrawal request - nextWithdrawalIndex = SafeCast.toUint128(requestId + 1); - // Store the updated queued amount which reserves WETH in the withdrawal queue - withdrawsQueued = queued; - // Store requests - withdrawalRequests[requestId] = WithdrawalRequest({ - withdrawer: msg.sender, - claimed: false, - claimTimestamp: claimTimestamp, - assets: SafeCast.toUint128(assets), - queued: queued - }); - - // burn redeemer's shares - _burn(msg.sender, shares); - - _postRequestRedeemHook(); - - emit RedeemRequested(msg.sender, requestId, assets, queued, claimTimestamp); - } - - function _preWithdrawHook() internal virtual; - function _postRequestRedeemHook() internal virtual; - - /// @notice Claim liquidity assets from a previous withdrawal request after the claim delay has passed - /// @param requestId The index of the withdrawal request - /// @return assets The amount of liquidity assets that were transferred to the redeemer - function claimRedeem(uint256 requestId) external returns (uint256 assets) { - // Update the ARM's withdrawal queue's claimable amount - _updateWithdrawalQueueLiquidity(); - - // Load the structs from storage into memory - WithdrawalRequest memory request = withdrawalRequests[requestId]; - - require(request.claimTimestamp <= block.timestamp, "Claim delay not met"); - // If there isn't enough reserved liquidity in the queue to claim - require(request.queued <= withdrawsClaimable, "Queue pending liquidity"); - require(request.withdrawer == msg.sender, "Not requester"); - require(request.claimed == false, "Already claimed"); - - // Store the request as claimed - withdrawalRequests[requestId].claimed = true; - // Store the updated claimed amount - withdrawsClaimed += request.assets; - - assets = request.assets; - - emit RedeemClaimed(msg.sender, requestId, assets); - - // transfer the liquidity asset to the withdrawer - IERC20(liquidityAsset).transfer(msg.sender, assets); - } - - /// @dev Updates the claimable amount in the ARM's withdrawal queue. - /// That's the amount that is used to check if a request can be claimed or not. - function _updateWithdrawalQueueLiquidity() internal { - // Load the claimable amount from storage into memory - uint256 withdrawsClaimableMem = withdrawsClaimable; - - // Check if the claimable amount is less than the queued amount - uint256 queueShortfall = withdrawsQueued - withdrawsClaimableMem; - - // No need to do anything is the withdrawal queue is fully funded - if (queueShortfall == 0) { - return; - } - - uint256 liquidityBalance = IERC20(liquidityAsset).balanceOf(address(this)); - - // Of the claimable withdrawal requests, how much is unclaimed? - // That is, the amount of the liquidity assets that is currently allocated for the withdrawal queue - uint256 allocatedLiquidity = withdrawsClaimableMem - withdrawsClaimed; - - // If there is no unallocated liquidity assets then there is nothing to add to the queue - if (liquidityBalance <= allocatedLiquidity) { - return; - } - - uint256 unallocatedLiquidity = liquidityBalance - allocatedLiquidity; - - // the new claimable amount is the smaller of the queue shortfall or unallocated weth - uint256 addedClaimable = queueShortfall < unallocatedLiquidity ? queueShortfall : unallocatedLiquidity; - - // Store the new claimable amount back to storage - withdrawsClaimable = SafeCast.toUint128(withdrawsClaimableMem + addedClaimable); - } - - /// @dev Calculate how much of the liquidity asset in the ARM is not reserved for the withdrawal queue. - // That is, it is available to be swapped. - function _liquidityAvailable() internal view returns (uint256) { - // The amount of WETH that is still to be claimed in the withdrawal queue - uint256 outstandingWithdrawals = withdrawsQueued - withdrawsClaimed; - - // The amount of the liquidity asset is in the ARM - uint256 liquidityBalance = IERC20(liquidityAsset).balanceOf(address(this)); - - // If there is not enough liquidity assets in the ARM to cover the outstanding withdrawals - if (liquidityBalance <= outstandingWithdrawals) { - return 0; - } - - return liquidityBalance - outstandingWithdrawals; - } - - /// @dev Ensure any liquidity assets reserved for the withdrawal queue are not used - /// in swaps that send liquidity assets out of the ARM - function _transferAsset(address asset, address to, uint256 amount) internal virtual override { - if (asset == liquidityAsset) { - require(amount <= _liquidityAvailable(), "ARM: Insufficient liquidity"); - } - - IERC20(asset).transfer(to, amount); - } - - /// @notice The total amount of assets in the ARM and external withdrawal queue, - /// less the liquidity assets reserved for the withdrawal queue - function totalAssets() public view virtual returns (uint256 assets) { - // Get the assets in the ARM and external withdrawal queue - assets = token0.balanceOf(address(this)) + token1.balanceOf(address(this)) + _externalWithdrawQueue(); - - // Load the queue metadata from storage into memory - uint256 queuedMem = withdrawsQueued; - uint256 claimedMem = withdrawsClaimed; - - // If the ARM becomes insolvent enough that the total value in the ARM and external withdrawal queue - // is less than the outstanding withdrawals. - if (assets + claimedMem < queuedMem) { - return 0; - } - - // Need to remove the liquidity assets that have been reserved for the withdrawal queue - return assets + claimedMem - queuedMem; - } - - /// @notice Calculates the amount of shares for a given amount of liquidity assets - function convertToShares(uint256 assets) public view returns (uint256 shares) { - uint256 totalAssetsMem = totalAssets(); - shares = (totalAssetsMem == 0) ? assets : (assets * totalSupply()) / totalAssetsMem; - } - - /// @notice Calculates the amount of liquidity assets for a given amount of shares - function convertToAssets(uint256 shares) public view returns (uint256 assets) { - assets = (shares * totalAssets()) / totalSupply(); - } - - /// @dev Hook for calculating the amount of assets in an external withdrawal queue like Lido or OETH - /// This is not the ARM's withdrawal queue - function _externalWithdrawQueue() internal view virtual returns (uint256 assets); -} diff --git a/src/contracts/OethARM.sol b/src/contracts/OethARM.sol index 64174ff..2d546ac 100644 --- a/src/contracts/OethARM.sol +++ b/src/contracts/OethARM.sol @@ -17,7 +17,7 @@ contract OethARM is Initializable, OwnerLP, PeggedARM, OethLiquidityManager { /// @param _weth The address of the WETH token that is being swapped out of this contract. /// @param _oethVault The address of the OETH Vault proxy. constructor(address _oeth, address _weth, address _oethVault) - AbstractARM(_oeth, _weth) + AbstractARM(_oeth, _weth, _weth) PeggedARM(false) OethLiquidityManager(_oeth, _oethVault) {} @@ -28,4 +28,8 @@ contract OethARM is Initializable, OwnerLP, PeggedARM, OethLiquidityManager { _setOperator(_operator); _approvals(); } + + function _externalWithdrawQueue() internal view override returns (uint256 assets) { + // TODO track OETH sent to the OETH Vault's withdrawal queue + } } diff --git a/src/contracts/PerformanceFee.sol b/src/contracts/PerformanceFee.sol deleted file mode 100644 index 32abc8c..0000000 --- a/src/contracts/PerformanceFee.sol +++ /dev/null @@ -1,161 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {IERC20} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; -import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol"; - -import {MultiLP} from "./MultiLP.sol"; - -/** - * @title Added a performance fee to an ARM with Liquidity Providers (LP) - * @author Origin Protocol Inc - */ -abstract contract PerformanceFee is MultiLP { - /// @notice The scale of the performance fee - /// 10,000 = 100% performance fee - uint256 public constant FEE_SCALE = 10000; - - /// @notice The account that can collect the performance fee - address public feeCollector; - /// @notice Performance fee that is collected by the feeCollector measured in basis points (1/100th of a percent) - /// 10,000 = 100% performance fee - /// 2,000 = 20% performance fee - /// 500 = 5% performance fee - uint16 public fee; - /// @notice The performance fees accrued but not collected. - /// This is removed from the total assets. - uint112 public feesAccrued; - /// @notice The total assets at the last time performance fees were calculated. - /// This can only go up so is a high watermark. - uint128 public lastTotalAssets; - - uint256[48] private _gap; - - event FeeCalculated(uint256 newFeesAccrued, uint256 assetIncrease); - event FeeCollected(address indexed feeCollector, uint256 fee); - event FeeUpdated(uint256 fee); - event FeeCollectorUpdated(address indexed newFeeCollector); - - function _initPerformanceFee(uint256 _fee, address _feeCollector) internal { - // Initialize the last total assets to the current total assets - // This ensures no performance fee is accrued when the performance fee is calculated when the fee is set - lastTotalAssets = SafeCast.toUint128(_rawTotalAssets()); - _setFee(_fee); - _setFeeCollector(_feeCollector); - } - - /// @dev Calculate the performance fee based on the increase in total assets before - /// the liquidity asset from the deposit is transferred into the ARM - function _preDepositHook() internal virtual override { - _calcFee(); - } - - /// @dev Calculate the performance fee based on the increase in total assets before - /// the liquidity asset from the redeem is reserved for the ARM withdrawal queue - function _preWithdrawHook() internal virtual override { - _calcFee(); - } - - /// @dev Save the new total assets after the deposit and performance fee accrued - function _postDepositHook(uint256) internal virtual override { - lastTotalAssets = SafeCast.toUint128(_rawTotalAssets()); - } - - /// @dev Save the new total assets after the requestRedeem and performance fee accrued - function _postRequestRedeemHook() internal virtual override { - lastTotalAssets = SafeCast.toUint128(_rawTotalAssets()); - } - - /// @dev Calculate the performance fee based on the increase in total assets - /// Needs to be called before any action that changes the liquidity provider shares. eg deposit and redeem - function _calcFee() internal { - uint256 newTotalAssets = _rawTotalAssets(); - - // Do not accrued a performance fee if the total assets has decreased - if (newTotalAssets <= lastTotalAssets) return; - - uint256 assetIncrease = newTotalAssets - lastTotalAssets; - uint256 newFeesAccrued = (assetIncrease * fee) / FEE_SCALE; - - // Save the new accrued fees back to storage - feesAccrued = SafeCast.toUint112(feesAccrued + newFeesAccrued); - // Save the new total assets back to storage less the new accrued fees. - // This is be updated again in the post deposit and post withdraw hooks to include - // the assets deposited or withdrawn - lastTotalAssets = SafeCast.toUint128(newTotalAssets - newFeesAccrued); - - emit FeeCalculated(newFeesAccrued, assetIncrease); - } - - /// @notice The total amount of assets in the ARM and external withdrawal queue, - /// less the liquidity assets reserved for the ARM's withdrawal queue and accrued fees. - function totalAssets() public view virtual override returns (uint256) { - uint256 totalAssetsBeforeFees = _rawTotalAssets(); - - // If the total assets have decreased, then we don't charge a performance fee - if (totalAssetsBeforeFees <= lastTotalAssets) return totalAssetsBeforeFees; - - // Calculate the increase in assets since the last time fees were calculated - uint256 assetIncrease = totalAssetsBeforeFees - lastTotalAssets; - - // Calculate the performance fee and remove from the total assets before new fees are removed - return totalAssetsBeforeFees - ((assetIncrease * fee) / FEE_SCALE); - } - - /// @dev Calculate the total assets in the ARM, external withdrawal queue, - /// less liquidity assets reserved for the ARM's withdrawal queue and past accrued fees. - /// The accrued fees are from the last time fees were calculated. - function _rawTotalAssets() internal view returns (uint256) { - return super.totalAssets() - feesAccrued; - } - - /// @notice Owner sets the performance fee on increased assets - /// @param _fee The performance fee measured in basis points (1/100th of a percent) - /// 10,000 = 100% performance fee - /// 500 = 5% performance fee - function setFee(uint256 _fee) external onlyOwner { - _setFee(_fee); - } - - /// @notice Owner sets the account/contract that receives the performance fee - function setFeeCollector(address _feeCollector) external onlyOwner { - _setFeeCollector(_feeCollector); - } - - function _setFee(uint256 _fee) internal { - require(_fee <= FEE_SCALE, "ARM: fee too high"); - - // Calculate fees up to this point using the old fee - _calcFee(); - - fee = SafeCast.toUint16(_fee); - - emit FeeUpdated(_fee); - } - - function _setFeeCollector(address _feeCollector) internal { - require(_feeCollector != address(0), "ARM: invalid fee collector"); - - feeCollector = _feeCollector; - - emit FeeCollectorUpdated(_feeCollector); - } - - /// @notice Transfer accrued performance fees to the fee collector - /// This requires enough liquidity assets in the ARM to cover the accrued fees. - function collectFees() external returns (uint256 fees) { - // Accrued all fees up to this point - _calcFee(); - - // Read the updated accrued fees from storage - fees = feesAccrued; - require(fees <= IERC20(liquidityAsset).balanceOf(address(this)), "ARM: insufficient liquidity"); - - // Reset the accrued fees in storage - feesAccrued = 0; - - IERC20(liquidityAsset).transfer(feeCollector, fees); - - emit FeeCollected(feeCollector, fees); - } -} diff --git a/src/contracts/README.md b/src/contracts/README.md index 494648e..d421790 100644 --- a/src/contracts/README.md +++ b/src/contracts/README.md @@ -30,12 +30,12 @@ ### Hierarchy -![Lido ARM Hierarchy](../../docs/LidoFixedPriceMultiLpARMHierarchy.svg) +![Lido ARM Hierarchy](../../docs/LidoARMHierarchy.svg) ## OETH ARM Squashed -![Lido ARM Squashed](../../docs/LidoFixedPriceMultiLpARMSquashed.svg) +![Lido ARM Squashed](../../docs/LidoARMSquashed.svg) +![Lido ARM Storage](../../docs/LidoARMStorage.svg) --> diff --git a/test/Base.sol b/test/Base.sol index 7be4705..f4b4b57 100644 --- a/test/Base.sol +++ b/test/Base.sol @@ -7,7 +7,7 @@ import {Test} from "forge-std/Test.sol"; // Contracts import {Proxy} from "contracts/Proxy.sol"; import {OethARM} from "contracts/OethARM.sol"; -import {LidoFixedPriceMultiLpARM} from "contracts/LidoFixedPriceMultiLpARM.sol"; +import {LidoARM} from "contracts/LidoARM.sol"; import {LiquidityProviderController} from "contracts/LiquidityProviderController.sol"; // Interfaces @@ -35,7 +35,7 @@ abstract contract Base_Test_ is Test { Proxy public lidoProxy; Proxy public lidoOwnerProxy; OethARM public oethARM; - LidoFixedPriceMultiLpARM public lidoFixedPriceMultiLpARM; + LidoARM public lidoARM; LiquidityProviderController public liquidityProviderController; IERC20 public oeth; diff --git a/test/fork/LidoFixedPriceMultiLpARM/ClaimRedeem.t.sol b/test/fork/LidoFixedPriceMultiLpARM/ClaimRedeem.t.sol index 6e1bf00..fc738f4 100644 --- a/test/fork/LidoFixedPriceMultiLpARM/ClaimRedeem.t.sol +++ b/test/fork/LidoFixedPriceMultiLpARM/ClaimRedeem.t.sol @@ -6,10 +6,9 @@ import {Fork_Shared_Test_} from "test/fork/shared/Shared.sol"; // Contracts import {IERC20} from "contracts/Interfaces.sol"; -import {MultiLP} from "contracts/MultiLP.sol"; -import {PerformanceFee} from "contracts/PerformanceFee.sol"; +import {AbstractARM} from "contracts/AbstractARM.sol"; -contract Fork_Concrete_LidoFixedPriceMultiLpARM_ClaimRedeem_Test_ is Fork_Shared_Test_ { +contract Fork_Concrete_LidoARM_ClaimRedeem_Test_ is Fork_Shared_Test_ { uint256 private delay; ////////////////////////////////////////////////////// /// --- SETUP @@ -18,7 +17,7 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_ClaimRedeem_Test_ is Fork_Shared function setUp() public override { super.setUp(); - delay = lidoFixedPriceMultiLpARM.CLAIM_DELAY(); + delay = lidoARM.CLAIM_DELAY(); deal(address(weth), address(this), 1_000 ether); } @@ -30,57 +29,57 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_ClaimRedeem_Test_ is Fork_Shared public setTotalAssetsCap(DEFAULT_AMOUNT + MIN_TOTAL_SUPPLY) setLiquidityProviderCap(address(this), DEFAULT_AMOUNT) - depositInLidoFixedPriceMultiLpARM(address(this), DEFAULT_AMOUNT) - requestRedeemFromLidoFixedPriceMultiLpARM(address(this), DEFAULT_AMOUNT) + depositInLidoARM(address(this), DEFAULT_AMOUNT) + requestRedeemFromLidoARM(address(this), DEFAULT_AMOUNT) { skip(delay - 1); vm.expectRevert("Claim delay not met"); - lidoFixedPriceMultiLpARM.claimRedeem(0); + lidoARM.claimRedeem(0); } function test_RevertWhen_ClaimRequest_Because_QueuePendingLiquidity_NoLiquidity() public setTotalAssetsCap(DEFAULT_AMOUNT + MIN_TOTAL_SUPPLY) setLiquidityProviderCap(address(this), DEFAULT_AMOUNT) - depositInLidoFixedPriceMultiLpARM(address(this), DEFAULT_AMOUNT) - requestRedeemFromLidoFixedPriceMultiLpARM(address(this), DEFAULT_AMOUNT) + depositInLidoARM(address(this), DEFAULT_AMOUNT) + requestRedeemFromLidoARM(address(this), DEFAULT_AMOUNT) { // Remove all weth liquidity from ARM - deal(address(weth), address(lidoFixedPriceMultiLpARM), 0); + deal(address(weth), address(lidoARM), 0); // Time jump claim delay skip(delay); // Expect revert vm.expectRevert("Queue pending liquidity"); - lidoFixedPriceMultiLpARM.claimRedeem(0); + lidoARM.claimRedeem(0); } function test_RevertWhen_ClaimRequest_Because_QueuePendingLiquidity_NoEnoughLiquidity() public setTotalAssetsCap(DEFAULT_AMOUNT + MIN_TOTAL_SUPPLY) setLiquidityProviderCap(address(this), DEFAULT_AMOUNT) - depositInLidoFixedPriceMultiLpARM(address(this), DEFAULT_AMOUNT) - requestRedeemFromLidoFixedPriceMultiLpARM(address(this), DEFAULT_AMOUNT) + depositInLidoARM(address(this), DEFAULT_AMOUNT) + requestRedeemFromLidoARM(address(this), DEFAULT_AMOUNT) { // Remove half of weth liquidity from ARM - uint256 halfAmount = weth.balanceOf(address(lidoFixedPriceMultiLpARM)) / 2; - deal(address(weth), address(lidoFixedPriceMultiLpARM), halfAmount); + uint256 halfAmount = weth.balanceOf(address(lidoARM)) / 2; + deal(address(weth), address(lidoARM), halfAmount); // Time jump claim delay skip(delay); // Expect revert vm.expectRevert("Queue pending liquidity"); - lidoFixedPriceMultiLpARM.claimRedeem(0); + lidoARM.claimRedeem(0); } function test_RevertWhen_ClaimRequest_Because_NotRequester() public setTotalAssetsCap(DEFAULT_AMOUNT + MIN_TOTAL_SUPPLY) setLiquidityProviderCap(address(this), DEFAULT_AMOUNT) - depositInLidoFixedPriceMultiLpARM(address(this), DEFAULT_AMOUNT) - requestRedeemFromLidoFixedPriceMultiLpARM(address(this), DEFAULT_AMOUNT) + depositInLidoARM(address(this), DEFAULT_AMOUNT) + requestRedeemFromLidoARM(address(this), DEFAULT_AMOUNT) { // Time jump claim delay skip(delay); @@ -88,21 +87,21 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_ClaimRedeem_Test_ is Fork_Shared // Expect revert vm.startPrank(vm.randomAddress()); vm.expectRevert("Not requester"); - lidoFixedPriceMultiLpARM.claimRedeem(0); + lidoARM.claimRedeem(0); } function test_RevertWhen_ClaimRequest_Because_AlreadyClaimed() public setTotalAssetsCap(DEFAULT_AMOUNT + MIN_TOTAL_SUPPLY) setLiquidityProviderCap(address(this), DEFAULT_AMOUNT) - depositInLidoFixedPriceMultiLpARM(address(this), DEFAULT_AMOUNT) - requestRedeemFromLidoFixedPriceMultiLpARM(address(this), DEFAULT_AMOUNT) + depositInLidoARM(address(this), DEFAULT_AMOUNT) + requestRedeemFromLidoARM(address(this), DEFAULT_AMOUNT) skipTime(delay) - claimRequestOnLidoFixedPriceMultiLpARM(address(this), 0) + claimRequestOnLidoARM(address(this), 0) { // Expect revert vm.expectRevert("Already claimed"); - lidoFixedPriceMultiLpARM.claimRedeem(0); + lidoARM.claimRedeem(0); } ////////////////////////////////////////////////////// @@ -113,39 +112,39 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_ClaimRedeem_Test_ is Fork_Shared public setTotalAssetsCap(DEFAULT_AMOUNT + MIN_TOTAL_SUPPLY) setLiquidityProviderCap(address(this), DEFAULT_AMOUNT) - depositInLidoFixedPriceMultiLpARM(address(this), DEFAULT_AMOUNT) - requestRedeemFromLidoFixedPriceMultiLpARM(address(this), DEFAULT_AMOUNT) + depositInLidoARM(address(this), DEFAULT_AMOUNT) + requestRedeemFromLidoARM(address(this), DEFAULT_AMOUNT) skipTime(delay) { // Assertions before - assertEq(steth.balanceOf(address(lidoFixedPriceMultiLpARM)), 0); - assertEq(weth.balanceOf(address(lidoFixedPriceMultiLpARM)), MIN_TOTAL_SUPPLY + DEFAULT_AMOUNT); - assertEq(lidoFixedPriceMultiLpARM.outstandingEther(), 0); - assertEq(lidoFixedPriceMultiLpARM.feesAccrued(), 0); // No perfs so no fees - assertEq(lidoFixedPriceMultiLpARM.lastTotalAssets(), MIN_TOTAL_SUPPLY); - assertEq(lidoFixedPriceMultiLpARM.balanceOf(address(this)), 0); - assertEq(lidoFixedPriceMultiLpARM.totalSupply(), MIN_TOTAL_SUPPLY); + assertEq(steth.balanceOf(address(lidoARM)), 0); + assertEq(weth.balanceOf(address(lidoARM)), MIN_TOTAL_SUPPLY + DEFAULT_AMOUNT); + assertEq(lidoARM.outstandingEther(), 0); + assertEq(lidoARM.feesAccrued(), 0); // No perfs so no fees + assertEq(lidoARM.lastTotalAssets(), MIN_TOTAL_SUPPLY); + assertEq(lidoARM.balanceOf(address(this)), 0); + assertEq(lidoARM.totalSupply(), MIN_TOTAL_SUPPLY); assertEq(liquidityProviderController.liquidityProviderCaps(address(this)), 0); assertEqQueueMetadata(DEFAULT_AMOUNT, 0, 0, 1); assertEqUserRequest(0, address(this), false, block.timestamp, DEFAULT_AMOUNT, DEFAULT_AMOUNT); // Expected events - vm.expectEmit({emitter: address(lidoFixedPriceMultiLpARM)}); - emit MultiLP.RedeemClaimed(address(this), 0, DEFAULT_AMOUNT); + vm.expectEmit({emitter: address(lidoARM)}); + emit AbstractARM.RedeemClaimed(address(this), 0, DEFAULT_AMOUNT); vm.expectEmit({emitter: address(weth)}); - emit IERC20.Transfer(address(lidoFixedPriceMultiLpARM), address(this), DEFAULT_AMOUNT); + emit IERC20.Transfer(address(lidoARM), address(this), DEFAULT_AMOUNT); // Main call - (uint256 assets) = lidoFixedPriceMultiLpARM.claimRedeem(0); + (uint256 assets) = lidoARM.claimRedeem(0); // Assertions after - assertEq(steth.balanceOf(address(lidoFixedPriceMultiLpARM)), 0); - assertEq(weth.balanceOf(address(lidoFixedPriceMultiLpARM)), MIN_TOTAL_SUPPLY); - assertEq(lidoFixedPriceMultiLpARM.outstandingEther(), 0); - assertEq(lidoFixedPriceMultiLpARM.feesAccrued(), 0); // No perfs so no fees - assertEq(lidoFixedPriceMultiLpARM.lastTotalAssets(), MIN_TOTAL_SUPPLY); - assertEq(lidoFixedPriceMultiLpARM.balanceOf(address(this)), 0); - assertEq(lidoFixedPriceMultiLpARM.totalSupply(), MIN_TOTAL_SUPPLY); + assertEq(steth.balanceOf(address(lidoARM)), 0); + assertEq(weth.balanceOf(address(lidoARM)), MIN_TOTAL_SUPPLY); + assertEq(lidoARM.outstandingEther(), 0); + assertEq(lidoARM.feesAccrued(), 0); // No perfs so no fees + assertEq(lidoARM.lastTotalAssets(), MIN_TOTAL_SUPPLY); + assertEq(lidoARM.balanceOf(address(this)), 0); + assertEq(lidoARM.totalSupply(), MIN_TOTAL_SUPPLY); assertEq(liquidityProviderController.liquidityProviderCaps(address(this)), 0); assertEqQueueMetadata(DEFAULT_AMOUNT, DEFAULT_AMOUNT, DEFAULT_AMOUNT, 1); assertEqUserRequest(0, address(this), true, block.timestamp, DEFAULT_AMOUNT, DEFAULT_AMOUNT); @@ -156,40 +155,40 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_ClaimRedeem_Test_ is Fork_Shared public setTotalAssetsCap(DEFAULT_AMOUNT + MIN_TOTAL_SUPPLY) setLiquidityProviderCap(address(this), DEFAULT_AMOUNT) - depositInLidoFixedPriceMultiLpARM(address(this), DEFAULT_AMOUNT) - requestRedeemFromLidoFixedPriceMultiLpARM(address(this), DEFAULT_AMOUNT) + depositInLidoARM(address(this), DEFAULT_AMOUNT) + requestRedeemFromLidoARM(address(this), DEFAULT_AMOUNT) skipTime(delay) { // Assertions before // Same situation as above // Swap MIN_TOTAL_SUPPLY from WETH in STETH - deal(address(weth), address(lidoFixedPriceMultiLpARM), DEFAULT_AMOUNT); - deal(address(steth), address(lidoFixedPriceMultiLpARM), MIN_TOTAL_SUPPLY); + deal(address(weth), address(lidoARM), DEFAULT_AMOUNT); + deal(address(steth), address(lidoARM), MIN_TOTAL_SUPPLY); // Handle lido rounding issue to ensure that balance is exactly MIN_TOTAL_SUPPLY - if (steth.balanceOf(address(lidoFixedPriceMultiLpARM)) == MIN_TOTAL_SUPPLY - 1) { - deal(address(steth), address(lidoFixedPriceMultiLpARM), 0); - deal(address(steth), address(lidoFixedPriceMultiLpARM), MIN_TOTAL_SUPPLY + 1); + if (steth.balanceOf(address(lidoARM)) == MIN_TOTAL_SUPPLY - 1) { + deal(address(steth), address(lidoARM), 0); + deal(address(steth), address(lidoARM), MIN_TOTAL_SUPPLY + 1); } // Expected events - vm.expectEmit({emitter: address(lidoFixedPriceMultiLpARM)}); - emit MultiLP.RedeemClaimed(address(this), 0, DEFAULT_AMOUNT); + vm.expectEmit({emitter: address(lidoARM)}); + emit AbstractARM.RedeemClaimed(address(this), 0, DEFAULT_AMOUNT); vm.expectEmit({emitter: address(weth)}); - emit IERC20.Transfer(address(lidoFixedPriceMultiLpARM), address(this), DEFAULT_AMOUNT); + emit IERC20.Transfer(address(lidoARM), address(this), DEFAULT_AMOUNT); // Main call - (uint256 assets) = lidoFixedPriceMultiLpARM.claimRedeem(0); + (uint256 assets) = lidoARM.claimRedeem(0); // Assertions after - assertApproxEqAbs(steth.balanceOf(address(lidoFixedPriceMultiLpARM)), MIN_TOTAL_SUPPLY, 1); - assertEq(weth.balanceOf(address(lidoFixedPriceMultiLpARM)), 0); - assertEq(lidoFixedPriceMultiLpARM.outstandingEther(), 0); - assertEq(lidoFixedPriceMultiLpARM.feesAccrued(), 0); // No perfs so no fees - assertEq(lidoFixedPriceMultiLpARM.lastTotalAssets(), MIN_TOTAL_SUPPLY); - assertEq(lidoFixedPriceMultiLpARM.balanceOf(address(this)), 0); - assertEq(lidoFixedPriceMultiLpARM.totalSupply(), MIN_TOTAL_SUPPLY); + assertApproxEqAbs(steth.balanceOf(address(lidoARM)), MIN_TOTAL_SUPPLY, 1); + assertEq(weth.balanceOf(address(lidoARM)), 0); + assertEq(lidoARM.outstandingEther(), 0); + assertEq(lidoARM.feesAccrued(), 0); // No perfs so no fees + assertEq(lidoARM.lastTotalAssets(), MIN_TOTAL_SUPPLY); + assertEq(lidoARM.balanceOf(address(this)), 0); + assertEq(lidoARM.totalSupply(), MIN_TOTAL_SUPPLY); assertEq(liquidityProviderController.liquidityProviderCaps(address(this)), 0); assertEqQueueMetadata(DEFAULT_AMOUNT, DEFAULT_AMOUNT, DEFAULT_AMOUNT, 1); assertEqUserRequest(0, address(this), true, block.timestamp, DEFAULT_AMOUNT, DEFAULT_AMOUNT); @@ -200,43 +199,43 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_ClaimRedeem_Test_ is Fork_Shared public setTotalAssetsCap(DEFAULT_AMOUNT + MIN_TOTAL_SUPPLY) setLiquidityProviderCap(address(this), DEFAULT_AMOUNT) - depositInLidoFixedPriceMultiLpARM(address(this), DEFAULT_AMOUNT) - requestRedeemFromLidoFixedPriceMultiLpARM(address(this), DEFAULT_AMOUNT / 2) + depositInLidoARM(address(this), DEFAULT_AMOUNT) + requestRedeemFromLidoARM(address(this), DEFAULT_AMOUNT / 2) skipTime(delay) - claimRequestOnLidoFixedPriceMultiLpARM(address(this), 0) - requestRedeemFromLidoFixedPriceMultiLpARM(address(this), DEFAULT_AMOUNT / 2) + claimRequestOnLidoARM(address(this), 0) + requestRedeemFromLidoARM(address(this), DEFAULT_AMOUNT / 2) { // Assertions before - assertEq(steth.balanceOf(address(lidoFixedPriceMultiLpARM)), 0); - assertEq(weth.balanceOf(address(lidoFixedPriceMultiLpARM)), MIN_TOTAL_SUPPLY + DEFAULT_AMOUNT / 2); - assertEq(lidoFixedPriceMultiLpARM.outstandingEther(), 0); - assertEq(lidoFixedPriceMultiLpARM.feesAccrued(), 0); // No perfs so no fees - assertEq(lidoFixedPriceMultiLpARM.lastTotalAssets(), MIN_TOTAL_SUPPLY); - assertEq(lidoFixedPriceMultiLpARM.balanceOf(address(this)), 0); - assertEq(lidoFixedPriceMultiLpARM.totalSupply(), MIN_TOTAL_SUPPLY); + assertEq(steth.balanceOf(address(lidoARM)), 0); + assertEq(weth.balanceOf(address(lidoARM)), MIN_TOTAL_SUPPLY + DEFAULT_AMOUNT / 2); + assertEq(lidoARM.outstandingEther(), 0); + assertEq(lidoARM.feesAccrued(), 0); // No perfs so no fees + assertEq(lidoARM.lastTotalAssets(), MIN_TOTAL_SUPPLY); + assertEq(lidoARM.balanceOf(address(this)), 0); + assertEq(lidoARM.totalSupply(), MIN_TOTAL_SUPPLY); assertEq(liquidityProviderController.liquidityProviderCaps(address(this)), 0); assertEqQueueMetadata(DEFAULT_AMOUNT, DEFAULT_AMOUNT / 2, DEFAULT_AMOUNT / 2, 2); assertEqUserRequest(0, address(this), true, block.timestamp, DEFAULT_AMOUNT / 2, DEFAULT_AMOUNT / 2); assertEqUserRequest(1, address(this), false, block.timestamp + delay, DEFAULT_AMOUNT / 2, DEFAULT_AMOUNT); // Expected events - vm.expectEmit({emitter: address(lidoFixedPriceMultiLpARM)}); - emit MultiLP.RedeemClaimed(address(this), 1, DEFAULT_AMOUNT / 2); + vm.expectEmit({emitter: address(lidoARM)}); + emit AbstractARM.RedeemClaimed(address(this), 1, DEFAULT_AMOUNT / 2); vm.expectEmit({emitter: address(weth)}); - emit IERC20.Transfer(address(lidoFixedPriceMultiLpARM), address(this), DEFAULT_AMOUNT / 2); + emit IERC20.Transfer(address(lidoARM), address(this), DEFAULT_AMOUNT / 2); // Main call skip(delay); - (uint256 assets) = lidoFixedPriceMultiLpARM.claimRedeem(1); + (uint256 assets) = lidoARM.claimRedeem(1); // Assertions after - assertEq(steth.balanceOf(address(lidoFixedPriceMultiLpARM)), 0); - assertEq(weth.balanceOf(address(lidoFixedPriceMultiLpARM)), MIN_TOTAL_SUPPLY); - assertEq(lidoFixedPriceMultiLpARM.outstandingEther(), 0); - assertEq(lidoFixedPriceMultiLpARM.feesAccrued(), 0); // No perfs so no fees - assertEq(lidoFixedPriceMultiLpARM.lastTotalAssets(), MIN_TOTAL_SUPPLY); - assertEq(lidoFixedPriceMultiLpARM.balanceOf(address(this)), 0); - assertEq(lidoFixedPriceMultiLpARM.totalSupply(), MIN_TOTAL_SUPPLY); + assertEq(steth.balanceOf(address(lidoARM)), 0); + assertEq(weth.balanceOf(address(lidoARM)), MIN_TOTAL_SUPPLY); + assertEq(lidoARM.outstandingEther(), 0); + assertEq(lidoARM.feesAccrued(), 0); // No perfs so no fees + assertEq(lidoARM.lastTotalAssets(), MIN_TOTAL_SUPPLY); + assertEq(lidoARM.balanceOf(address(this)), 0); + assertEq(lidoARM.totalSupply(), MIN_TOTAL_SUPPLY); assertEq(liquidityProviderController.liquidityProviderCaps(address(this)), 0); assertEqQueueMetadata(DEFAULT_AMOUNT, DEFAULT_AMOUNT, DEFAULT_AMOUNT, 2); assertEqUserRequest(0, address(this), true, block.timestamp - delay, DEFAULT_AMOUNT / 2, DEFAULT_AMOUNT / 2); diff --git a/test/fork/LidoFixedPriceMultiLpARM/ClaimStETHWithdrawalForWETH.t.sol b/test/fork/LidoFixedPriceMultiLpARM/ClaimStETHWithdrawalForWETH.t.sol index 6b8e9d3..ad75713 100644 --- a/test/fork/LidoFixedPriceMultiLpARM/ClaimStETHWithdrawalForWETH.t.sol +++ b/test/fork/LidoFixedPriceMultiLpARM/ClaimStETHWithdrawalForWETH.t.sol @@ -9,7 +9,7 @@ import {IERC20} from "contracts/Interfaces.sol"; import {IStETHWithdrawal} from "contracts/Interfaces.sol"; import {Mainnet} from "contracts/utils/Addresses.sol"; -contract Fork_Concrete_LidoFixedPriceMultiLpARM_RequestStETHWithdrawalForETH_Test_ is Fork_Shared_Test_ { +contract Fork_Concrete_LidoARM_RequestStETHWithdrawalForETH_Test_ is Fork_Shared_Test_ { uint256[] amounts0; uint256[] amounts1; uint256[] amounts2; @@ -22,7 +22,7 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_RequestStETHWithdrawalForETH_Tes function setUp() public override { super.setUp(); - deal(address(steth), address(lidoFixedPriceMultiLpARM), 10_000 ether); + deal(address(steth), address(lidoARM), 10_000 ether); amounts0 = new uint256[](0); @@ -40,61 +40,61 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_RequestStETHWithdrawalForETH_Tes function test_ClaimStETHWithdrawalForWETH_EmptyList() public asLidoFixedPriceMulltiLpARMOperator - requestStETHWithdrawalForETHOnLidoFixedPriceMultiLpARM(new uint256[](0)) + requestStETHWithdrawalForETHOnLidoARM(new uint256[](0)) { - assertEq(address(lidoFixedPriceMultiLpARM).balance, 0); - assertEq(lidoFixedPriceMultiLpARM.outstandingEther(), 0); + assertEq(address(lidoARM).balance, 0); + assertEq(lidoARM.outstandingEther(), 0); // Main call - lidoFixedPriceMultiLpARM.claimStETHWithdrawalForWETH(new uint256[](0)); + lidoARM.claimStETHWithdrawalForWETH(new uint256[](0)); - assertEq(address(lidoFixedPriceMultiLpARM).balance, 0); - assertEq(lidoFixedPriceMultiLpARM.outstandingEther(), 0); + assertEq(address(lidoARM).balance, 0); + assertEq(lidoARM.outstandingEther(), 0); } function test_ClaimStETHWithdrawalForWETH_SingleRequest() public asLidoFixedPriceMulltiLpARMOperator - approveStETHOnLidoFixedPriceMultiLpARM - requestStETHWithdrawalForETHOnLidoFixedPriceMultiLpARM(amounts1) - mockFunctionClaimWithdrawOnLidoFixedPriceMultiLpARM(DEFAULT_AMOUNT) + approveStETHOnLidoARM + requestStETHWithdrawalForETHOnLidoARM(amounts1) + mockFunctionClaimWithdrawOnLidoARM(DEFAULT_AMOUNT) { // Assertions before - uint256 balanceBefore = weth.balanceOf(address(lidoFixedPriceMultiLpARM)); - assertEq(lidoFixedPriceMultiLpARM.outstandingEther(), DEFAULT_AMOUNT); + uint256 balanceBefore = weth.balanceOf(address(lidoARM)); + assertEq(lidoARM.outstandingEther(), DEFAULT_AMOUNT); stETHWithdrawal.getLastRequestId(); uint256[] memory requests = new uint256[](1); requests[0] = stETHWithdrawal.getLastRequestId(); // Main call - lidoFixedPriceMultiLpARM.claimStETHWithdrawalForWETH(requests); + lidoARM.claimStETHWithdrawalForWETH(requests); // Assertions after - assertEq(lidoFixedPriceMultiLpARM.outstandingEther(), 0); - assertEq(weth.balanceOf(address(lidoFixedPriceMultiLpARM)), balanceBefore + DEFAULT_AMOUNT); + assertEq(lidoARM.outstandingEther(), 0); + assertEq(weth.balanceOf(address(lidoARM)), balanceBefore + DEFAULT_AMOUNT); } function test_ClaimStETHWithdrawalForWETH_MultiRequest() public asLidoFixedPriceMulltiLpARMOperator - approveStETHOnLidoFixedPriceMultiLpARM - requestStETHWithdrawalForETHOnLidoFixedPriceMultiLpARM(amounts2) + approveStETHOnLidoARM + requestStETHWithdrawalForETHOnLidoARM(amounts2) mockCallLidoFindCheckpointHints - mockFunctionClaimWithdrawOnLidoFixedPriceMultiLpARM(amounts2[0] + amounts2[1]) + mockFunctionClaimWithdrawOnLidoARM(amounts2[0] + amounts2[1]) { // Assertions before - uint256 balanceBefore = weth.balanceOf(address(lidoFixedPriceMultiLpARM)); - assertEq(lidoFixedPriceMultiLpARM.outstandingEther(), amounts2[0] + amounts2[1]); + uint256 balanceBefore = weth.balanceOf(address(lidoARM)); + assertEq(lidoARM.outstandingEther(), amounts2[0] + amounts2[1]); stETHWithdrawal.getLastRequestId(); uint256[] memory requests = new uint256[](2); requests[0] = stETHWithdrawal.getLastRequestId() - 1; requests[1] = stETHWithdrawal.getLastRequestId(); // Main call - lidoFixedPriceMultiLpARM.claimStETHWithdrawalForWETH(requests); + lidoARM.claimStETHWithdrawalForWETH(requests); // Assertions after - assertEq(lidoFixedPriceMultiLpARM.outstandingEther(), 0); - assertEq(weth.balanceOf(address(lidoFixedPriceMultiLpARM)), balanceBefore + amounts2[0] + amounts2[1]); + assertEq(lidoARM.outstandingEther(), 0); + assertEq(weth.balanceOf(address(lidoARM)), balanceBefore + amounts2[0] + amounts2[1]); } } diff --git a/test/fork/LidoFixedPriceMultiLpARM/CollectFees.t.sol b/test/fork/LidoFixedPriceMultiLpARM/CollectFees.t.sol index c10f272..dd23f07 100644 --- a/test/fork/LidoFixedPriceMultiLpARM/CollectFees.t.sol +++ b/test/fork/LidoFixedPriceMultiLpARM/CollectFees.t.sol @@ -6,9 +6,9 @@ import {Fork_Shared_Test_} from "test/fork/shared/Shared.sol"; // Contracts import {IERC20} from "contracts/Interfaces.sol"; -import {PerformanceFee} from "contracts/PerformanceFee.sol"; +import {AbstractARM} from "contracts/AbstractARM.sol"; -contract Fork_Concrete_LidoFixedPriceMultiLpARM_CollectFees_Test_ is Fork_Shared_Test_ { +contract Fork_Concrete_LidoARM_CollectFees_Test_ is Fork_Shared_Test_ { ////////////////////////////////////////////////////// /// --- SETUP ////////////////////////////////////////////////////// @@ -22,44 +22,41 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_CollectFees_Test_ is Fork_Shared /// @notice This test is expected to revert as almost all the liquidity is in stETH function test_RevertWhen_CollectFees_Because_InsufficientLiquidity() public - simulateAssetGainInLidoFixedPriceMultiLpARM(DEFAULT_AMOUNT, address(steth), true) + simulateAssetGainInLidoARM(DEFAULT_AMOUNT, address(steth), true) { vm.expectRevert("ARM: insufficient liquidity"); - lidoFixedPriceMultiLpARM.collectFees(); + lidoARM.collectFees(); } ////////////////////////////////////////////////////// /// --- PASSING TESTS ////////////////////////////////////////////////////// - function test_CollectFees_Once() - public - simulateAssetGainInLidoFixedPriceMultiLpARM(DEFAULT_AMOUNT, address(weth), true) - { - address feeCollector = lidoFixedPriceMultiLpARM.feeCollector(); + function test_CollectFees_Once() public simulateAssetGainInLidoARM(DEFAULT_AMOUNT, address(weth), true) { + address feeCollector = lidoARM.feeCollector(); uint256 fee = DEFAULT_AMOUNT * 20 / 100; // Expected Events vm.expectEmit({emitter: address(weth)}); - emit IERC20.Transfer(address(lidoFixedPriceMultiLpARM), feeCollector, fee); - vm.expectEmit({emitter: address(lidoFixedPriceMultiLpARM)}); - emit PerformanceFee.FeeCollected(feeCollector, fee); + emit IERC20.Transfer(address(lidoARM), feeCollector, fee); + vm.expectEmit({emitter: address(lidoARM)}); + emit AbstractARM.FeeCollected(feeCollector, fee); // Main call - uint256 claimedFee = lidoFixedPriceMultiLpARM.collectFees(); + uint256 claimedFee = lidoARM.collectFees(); // Assertions after assertEq(claimedFee, fee); - assertEq(lidoFixedPriceMultiLpARM.feesAccrued(), 0); + assertEq(lidoARM.feesAccrued(), 0); } function test_CollectFees_Twice() public - simulateAssetGainInLidoFixedPriceMultiLpARM(DEFAULT_AMOUNT, address(weth), true) - collectFeesOnLidoFixedPriceMultiLpARM - simulateAssetGainInLidoFixedPriceMultiLpARM(DEFAULT_AMOUNT, address(weth), true) + simulateAssetGainInLidoARM(DEFAULT_AMOUNT, address(weth), true) + collectFeesOnLidoARM + simulateAssetGainInLidoARM(DEFAULT_AMOUNT, address(weth), true) { // Main call - uint256 claimedFee = lidoFixedPriceMultiLpARM.collectFees(); + uint256 claimedFee = lidoARM.collectFees(); // Assertions after assertEq(claimedFee, DEFAULT_AMOUNT * 20 / 100); // This test should pass! diff --git a/test/fork/LidoFixedPriceMultiLpARM/Constructor.t.sol b/test/fork/LidoFixedPriceMultiLpARM/Constructor.t.sol index dc7f048..debd3a3 100644 --- a/test/fork/LidoFixedPriceMultiLpARM/Constructor.t.sol +++ b/test/fork/LidoFixedPriceMultiLpARM/Constructor.t.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.23; // Test imports import {Fork_Shared_Test_} from "test/fork/shared/Shared.sol"; -contract Fork_Concrete_LidoFixedPriceMultiLpARM_Constructor_Test is Fork_Shared_Test_ { +contract Fork_Concrete_LidoARM_Constructor_Test is Fork_Shared_Test_ { ////////////////////////////////////////////////////// /// --- SETUP ////////////////////////////////////////////////////// @@ -16,18 +16,18 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_Constructor_Test is Fork_Shared_ /// --- PASSING TESTS ////////////////////////////////////////////////////// function test_Initial_State() public view { - assertEq(lidoFixedPriceMultiLpARM.name(), "Lido ARM"); - assertEq(lidoFixedPriceMultiLpARM.symbol(), "ARM-ST"); - assertEq(lidoFixedPriceMultiLpARM.owner(), address(this)); - assertEq(lidoFixedPriceMultiLpARM.operator(), operator); - assertEq(lidoFixedPriceMultiLpARM.feeCollector(), feeCollector); - assertEq(lidoFixedPriceMultiLpARM.fee(), 2000); - assertEq(lidoFixedPriceMultiLpARM.lastTotalAssets(), 1e12); - assertEq(lidoFixedPriceMultiLpARM.feesAccrued(), 0); + assertEq(lidoARM.name(), "Lido ARM"); + assertEq(lidoARM.symbol(), "ARM-ST"); + assertEq(lidoARM.owner(), address(this)); + assertEq(lidoARM.operator(), operator); + assertEq(lidoARM.feeCollector(), feeCollector); + assertEq(lidoARM.fee(), 2000); + assertEq(lidoARM.lastTotalAssets(), 1e12); + assertEq(lidoARM.feesAccrued(), 0); // the 20% performance fee is removed on initialization - assertEq(lidoFixedPriceMultiLpARM.totalAssets(), 1e12); - assertEq(lidoFixedPriceMultiLpARM.totalSupply(), 1e12); - assertEq(weth.balanceOf(address(lidoFixedPriceMultiLpARM)), 1e12); + assertEq(lidoARM.totalAssets(), 1e12); + assertEq(lidoARM.totalSupply(), 1e12); + assertEq(weth.balanceOf(address(lidoARM)), 1e12); assertEq(liquidityProviderController.totalAssetsCap(), 100 ether); } } diff --git a/test/fork/LidoFixedPriceMultiLpARM/Deposit.t.sol b/test/fork/LidoFixedPriceMultiLpARM/Deposit.t.sol index c88c02a..9ac0e9b 100644 --- a/test/fork/LidoFixedPriceMultiLpARM/Deposit.t.sol +++ b/test/fork/LidoFixedPriceMultiLpARM/Deposit.t.sol @@ -6,27 +6,9 @@ import {Fork_Shared_Test_} from "test/fork/shared/Shared.sol"; // Contracts import {IERC20} from "contracts/Interfaces.sol"; -import {MultiLP} from "contracts/MultiLP.sol"; import {LiquidityProviderController} from "contracts/LiquidityProviderController.sol"; -contract Fork_Concrete_LidoFixedPriceMultiLpARM_Deposit_Test_ is Fork_Shared_Test_ { - /** - * As Deposit is complex function due to the entanglement of virtual and override functions in inheritance. - * This is a small recap of the functions that are called in the deposit function. - * 1. ML: _preDepositHook() -> PF: _calcFee() -> PF: _rawTotalAssets() -> ML: _totalAssets() - * 2. ML: convertToShares() -> ML: _totalAssets() -> ARM: totalAssets() -> PF : totalAssets() -> - * -> PF: _rawTotalAssets() -> ML: _totalAssets() - * 3. ML: _postDepositHook() -> ARM: _postDepositHook() => - * | -> LCPARM: postDepositHook() -> LPC: postDepositHook() -> ARM: totalAssets() -> - * -> PF : totalAssets() -> PF: _rawTotalAssets() -> ML: _totalAssets() - * | -> PF: _postDepositHook() -> PF: _rawTotalAssets() -> ML: _totalAssets() - * - * ML = MultiLP - * PF = PerformanceFee - * ARM = LidoFixedPriceMultiLpARM - * LPC = LiquidityProviderController - * LCPARM = LiquidityProviderControllerARM - */ +contract Fork_Concrete_LidoARM_Deposit_Test_ is Fork_Shared_Test_ { ////////////////////////////////////////////////////// /// --- SETUP ////////////////////////////////////////////////////// @@ -38,7 +20,7 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_Deposit_Test_ is Fork_Shared_Tes // Alice deal(address(weth), alice, 1_000 ether); vm.prank(alice); - weth.approve(address(lidoFixedPriceMultiLpARM), type(uint256).max); + weth.approve(address(lidoARM), type(uint256).max); } ////////////////////////////////////////////////////// @@ -49,7 +31,7 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_Deposit_Test_ is Fork_Shared_Tes setLiquidityProviderCap(address(this), 0) { vm.expectRevert("LPC: LP cap exceeded"); - lidoFixedPriceMultiLpARM.deposit(DEFAULT_AMOUNT); + lidoARM.deposit(DEFAULT_AMOUNT); } function test_RevertWhen_Deposit_Because_LiquidityProviderCapExceeded_WithCapNotNull() @@ -57,7 +39,7 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_Deposit_Test_ is Fork_Shared_Tes setLiquidityProviderCap(address(this), DEFAULT_AMOUNT) { vm.expectRevert("LPC: LP cap exceeded"); - lidoFixedPriceMultiLpARM.deposit(DEFAULT_AMOUNT + 1); + lidoARM.deposit(DEFAULT_AMOUNT + 1); } function test_RevertWhen_Deposit_Because_LiquidityProviderCapExceeded_WithCapReached() @@ -65,11 +47,11 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_Deposit_Test_ is Fork_Shared_Tes setLiquidityProviderCap(address(this), DEFAULT_AMOUNT) { // Initial deposit - lidoFixedPriceMultiLpARM.deposit(DEFAULT_AMOUNT / 2); + lidoARM.deposit(DEFAULT_AMOUNT / 2); // Cap is now 0.5 ether vm.expectRevert("LPC: LP cap exceeded"); - lidoFixedPriceMultiLpARM.deposit((DEFAULT_AMOUNT / 2) + 1); + lidoARM.deposit((DEFAULT_AMOUNT / 2) + 1); } function test_RevertWhen_Deposit_Because_TotalAssetsCapExceeded_WithCapNull() @@ -78,7 +60,7 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_Deposit_Test_ is Fork_Shared_Tes setLiquidityProviderCap(address(this), DEFAULT_AMOUNT + 1) { vm.expectRevert("LPC: Total assets cap exceeded"); - lidoFixedPriceMultiLpARM.deposit(DEFAULT_AMOUNT); + lidoARM.deposit(DEFAULT_AMOUNT); } function test_RevertWhen_Deposit_Because_TotalAssetsCapExceeded_WithCapNotNull() @@ -87,7 +69,7 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_Deposit_Test_ is Fork_Shared_Tes setLiquidityProviderCap(address(this), DEFAULT_AMOUNT) { vm.expectRevert("LPC: Total assets cap exceeded"); - lidoFixedPriceMultiLpARM.deposit(DEFAULT_AMOUNT - MIN_TOTAL_SUPPLY + 1); + lidoARM.deposit(DEFAULT_AMOUNT - MIN_TOTAL_SUPPLY + 1); } function test_RevertWhen_Deposit_Because_TotalAssetsCapExceeded_WithCapReached() @@ -95,9 +77,9 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_Deposit_Test_ is Fork_Shared_Tes setTotalAssetsCap(DEFAULT_AMOUNT) setLiquidityProviderCap(address(this), DEFAULT_AMOUNT) { - lidoFixedPriceMultiLpARM.deposit(DEFAULT_AMOUNT / 2); + lidoARM.deposit(DEFAULT_AMOUNT / 2); vm.expectRevert("LPC: Total assets cap exceeded"); - lidoFixedPriceMultiLpARM.deposit((DEFAULT_AMOUNT / 2) - MIN_TOTAL_SUPPLY + 1); // This should revert! + lidoARM.deposit((DEFAULT_AMOUNT / 2) - MIN_TOTAL_SUPPLY + 1); // This should revert! } ////////////////////////////////////////////////////// @@ -113,37 +95,37 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_Deposit_Test_ is Fork_Shared_Tes { uint256 amount = DEFAULT_AMOUNT; // Assertions Before - assertEq(steth.balanceOf(address(lidoFixedPriceMultiLpARM)), 0); - assertEq(weth.balanceOf(address(lidoFixedPriceMultiLpARM)), MIN_TOTAL_SUPPLY); - assertEq(lidoFixedPriceMultiLpARM.outstandingEther(), 0); - assertEq(lidoFixedPriceMultiLpARM.feesAccrued(), 0); // No perfs so no fees - assertEq(lidoFixedPriceMultiLpARM.lastTotalAssets(), MIN_TOTAL_SUPPLY); - assertEq(lidoFixedPriceMultiLpARM.balanceOf(address(this)), 0); // Ensure no shares before - assertEq(lidoFixedPriceMultiLpARM.totalSupply(), MIN_TOTAL_SUPPLY); // Minted to dead on deploy - assertEq(lidoFixedPriceMultiLpARM.totalAssets(), MIN_TOTAL_SUPPLY); + assertEq(steth.balanceOf(address(lidoARM)), 0); + assertEq(weth.balanceOf(address(lidoARM)), MIN_TOTAL_SUPPLY); + assertEq(lidoARM.outstandingEther(), 0); + assertEq(lidoARM.feesAccrued(), 0); // No perfs so no fees + assertEq(lidoARM.lastTotalAssets(), MIN_TOTAL_SUPPLY); + assertEq(lidoARM.balanceOf(address(this)), 0); // Ensure no shares before + assertEq(lidoARM.totalSupply(), MIN_TOTAL_SUPPLY); // Minted to dead on deploy + assertEq(lidoARM.totalAssets(), MIN_TOTAL_SUPPLY); assertEq(liquidityProviderController.liquidityProviderCaps(address(this)), amount); assertEqQueueMetadata(0, 0, 0, 0); // Expected events vm.expectEmit({emitter: address(weth)}); - emit IERC20.Transfer(address(this), address(lidoFixedPriceMultiLpARM), amount); - vm.expectEmit({emitter: address(lidoFixedPriceMultiLpARM)}); + emit IERC20.Transfer(address(this), address(lidoARM), amount); + vm.expectEmit({emitter: address(lidoARM)}); emit IERC20.Transfer(address(0), address(this), amount); // shares == amount here vm.expectEmit({emitter: address(liquidityProviderController)}); emit LiquidityProviderController.LiquidityProviderCap(address(this), 0); // Main call - uint256 shares = lidoFixedPriceMultiLpARM.deposit(amount); + uint256 shares = lidoARM.deposit(amount); // Assertions After - assertEq(steth.balanceOf(address(lidoFixedPriceMultiLpARM)), 0); - assertEq(weth.balanceOf(address(lidoFixedPriceMultiLpARM)), MIN_TOTAL_SUPPLY + amount); - assertEq(lidoFixedPriceMultiLpARM.outstandingEther(), 0); - assertEq(lidoFixedPriceMultiLpARM.feesAccrued(), 0); // No perfs so no fees - assertEq(lidoFixedPriceMultiLpARM.lastTotalAssets(), MIN_TOTAL_SUPPLY + amount); - assertEq(lidoFixedPriceMultiLpARM.balanceOf(address(this)), shares); - assertEq(lidoFixedPriceMultiLpARM.totalSupply(), MIN_TOTAL_SUPPLY + amount); - assertEq(lidoFixedPriceMultiLpARM.totalAssets(), MIN_TOTAL_SUPPLY + amount); + assertEq(steth.balanceOf(address(lidoARM)), 0); + assertEq(weth.balanceOf(address(lidoARM)), MIN_TOTAL_SUPPLY + amount); + assertEq(lidoARM.outstandingEther(), 0); + assertEq(lidoARM.feesAccrued(), 0); // No perfs so no fees + assertEq(lidoARM.lastTotalAssets(), MIN_TOTAL_SUPPLY + amount); + assertEq(lidoARM.balanceOf(address(this)), shares); + assertEq(lidoARM.totalSupply(), MIN_TOTAL_SUPPLY + amount); + assertEq(lidoARM.totalAssets(), MIN_TOTAL_SUPPLY + amount); assertEq(liquidityProviderController.liquidityProviderCaps(address(this)), 0); // All the caps are used assertEqQueueMetadata(0, 0, 0, 0); assertEq(shares, amount); // No perfs, so 1 ether * totalSupply (1e12) / totalAssets (1e12) = 1 ether @@ -155,41 +137,41 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_Deposit_Test_ is Fork_Shared_Tes public setTotalAssetsCap(DEFAULT_AMOUNT * 2 + MIN_TOTAL_SUPPLY) setLiquidityProviderCap(address(this), DEFAULT_AMOUNT * 2) - depositInLidoFixedPriceMultiLpARM(address(this), DEFAULT_AMOUNT) + depositInLidoARM(address(this), DEFAULT_AMOUNT) { uint256 amount = DEFAULT_AMOUNT; // Assertions Before - assertEq(steth.balanceOf(address(lidoFixedPriceMultiLpARM)), 0); - assertEq(weth.balanceOf(address(lidoFixedPriceMultiLpARM)), MIN_TOTAL_SUPPLY + amount); - assertEq(lidoFixedPriceMultiLpARM.outstandingEther(), 0); - assertEq(lidoFixedPriceMultiLpARM.feesAccrued(), 0); // No perfs so no fees - assertEq(lidoFixedPriceMultiLpARM.lastTotalAssets(), MIN_TOTAL_SUPPLY + amount); - assertEq(lidoFixedPriceMultiLpARM.balanceOf(address(this)), amount); - assertEq(lidoFixedPriceMultiLpARM.totalSupply(), MIN_TOTAL_SUPPLY + amount); // Minted to dead on deploy - assertEq(lidoFixedPriceMultiLpARM.totalAssets(), MIN_TOTAL_SUPPLY + amount); + assertEq(steth.balanceOf(address(lidoARM)), 0); + assertEq(weth.balanceOf(address(lidoARM)), MIN_TOTAL_SUPPLY + amount); + assertEq(lidoARM.outstandingEther(), 0); + assertEq(lidoARM.feesAccrued(), 0); // No perfs so no fees + assertEq(lidoARM.lastTotalAssets(), MIN_TOTAL_SUPPLY + amount); + assertEq(lidoARM.balanceOf(address(this)), amount); + assertEq(lidoARM.totalSupply(), MIN_TOTAL_SUPPLY + amount); // Minted to dead on deploy + assertEq(lidoARM.totalAssets(), MIN_TOTAL_SUPPLY + amount); assertEq(liquidityProviderController.liquidityProviderCaps(address(this)), amount); assertEqQueueMetadata(0, 0, 0, 0); // Expected events vm.expectEmit({emitter: address(weth)}); - emit IERC20.Transfer(address(this), address(lidoFixedPriceMultiLpARM), amount); - vm.expectEmit({emitter: address(lidoFixedPriceMultiLpARM)}); + emit IERC20.Transfer(address(this), address(lidoARM), amount); + vm.expectEmit({emitter: address(lidoARM)}); emit IERC20.Transfer(address(0), address(this), amount); // shares == amount here vm.expectEmit({emitter: address(liquidityProviderController)}); emit LiquidityProviderController.LiquidityProviderCap(address(this), 0); // Main call - uint256 shares = lidoFixedPriceMultiLpARM.deposit(amount); + uint256 shares = lidoARM.deposit(amount); // Assertions After - assertEq(steth.balanceOf(address(lidoFixedPriceMultiLpARM)), 0); - assertEq(weth.balanceOf(address(lidoFixedPriceMultiLpARM)), MIN_TOTAL_SUPPLY + amount * 2); - assertEq(lidoFixedPriceMultiLpARM.outstandingEther(), 0); - assertEq(lidoFixedPriceMultiLpARM.feesAccrued(), 0); // No perfs so no fees - assertEq(lidoFixedPriceMultiLpARM.lastTotalAssets(), MIN_TOTAL_SUPPLY + amount * 2); - assertEq(lidoFixedPriceMultiLpARM.balanceOf(address(this)), shares * 2); - assertEq(lidoFixedPriceMultiLpARM.totalSupply(), MIN_TOTAL_SUPPLY + amount * 2); - assertEq(lidoFixedPriceMultiLpARM.totalAssets(), MIN_TOTAL_SUPPLY + amount * 2); + assertEq(steth.balanceOf(address(lidoARM)), 0); + assertEq(weth.balanceOf(address(lidoARM)), MIN_TOTAL_SUPPLY + amount * 2); + assertEq(lidoARM.outstandingEther(), 0); + assertEq(lidoARM.feesAccrued(), 0); // No perfs so no fees + assertEq(lidoARM.lastTotalAssets(), MIN_TOTAL_SUPPLY + amount * 2); + assertEq(lidoARM.balanceOf(address(this)), shares * 2); + assertEq(lidoARM.totalSupply(), MIN_TOTAL_SUPPLY + amount * 2); + assertEq(lidoARM.totalAssets(), MIN_TOTAL_SUPPLY + amount * 2); assertEq(liquidityProviderController.liquidityProviderCaps(address(this)), 0); // All the caps are used assertEqQueueMetadata(0, 0, 0, 0); assertEq(shares, amount); // No perfs, so 1 ether * totalSupply (1e18 + 1e12) / totalAssets (1e18 + 1e12) = 1 ether @@ -202,42 +184,42 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_Deposit_Test_ is Fork_Shared_Tes setTotalAssetsCap(DEFAULT_AMOUNT * 2 + MIN_TOTAL_SUPPLY) setLiquidityProviderCap(address(this), DEFAULT_AMOUNT) setLiquidityProviderCap(alice, DEFAULT_AMOUNT) - depositInLidoFixedPriceMultiLpARM(address(this), DEFAULT_AMOUNT) + depositInLidoARM(address(this), DEFAULT_AMOUNT) { uint256 amount = DEFAULT_AMOUNT; // Assertions Before - assertEq(steth.balanceOf(address(lidoFixedPriceMultiLpARM)), 0); - assertEq(weth.balanceOf(address(lidoFixedPriceMultiLpARM)), MIN_TOTAL_SUPPLY + amount); - assertEq(lidoFixedPriceMultiLpARM.outstandingEther(), 0); - assertEq(lidoFixedPriceMultiLpARM.feesAccrued(), 0); // No perfs so no fees - assertEq(lidoFixedPriceMultiLpARM.lastTotalAssets(), MIN_TOTAL_SUPPLY + amount); - assertEq(lidoFixedPriceMultiLpARM.balanceOf(alice), 0); - assertEq(lidoFixedPriceMultiLpARM.totalSupply(), MIN_TOTAL_SUPPLY + amount); // Minted to dead on deploy - assertEq(lidoFixedPriceMultiLpARM.totalAssets(), MIN_TOTAL_SUPPLY + amount); + assertEq(steth.balanceOf(address(lidoARM)), 0); + assertEq(weth.balanceOf(address(lidoARM)), MIN_TOTAL_SUPPLY + amount); + assertEq(lidoARM.outstandingEther(), 0); + assertEq(lidoARM.feesAccrued(), 0); // No perfs so no fees + assertEq(lidoARM.lastTotalAssets(), MIN_TOTAL_SUPPLY + amount); + assertEq(lidoARM.balanceOf(alice), 0); + assertEq(lidoARM.totalSupply(), MIN_TOTAL_SUPPLY + amount); // Minted to dead on deploy + assertEq(lidoARM.totalAssets(), MIN_TOTAL_SUPPLY + amount); assertEq(liquidityProviderController.liquidityProviderCaps(alice), amount); assertEqQueueMetadata(0, 0, 0, 0); // Expected events vm.expectEmit({emitter: address(weth)}); - emit IERC20.Transfer(alice, address(lidoFixedPriceMultiLpARM), amount); - vm.expectEmit({emitter: address(lidoFixedPriceMultiLpARM)}); + emit IERC20.Transfer(alice, address(lidoARM), amount); + vm.expectEmit({emitter: address(lidoARM)}); emit IERC20.Transfer(address(0), alice, amount); // shares == amount here vm.expectEmit({emitter: address(liquidityProviderController)}); emit LiquidityProviderController.LiquidityProviderCap(alice, 0); vm.prank(alice); // Main call - uint256 shares = lidoFixedPriceMultiLpARM.deposit(amount); + uint256 shares = lidoARM.deposit(amount); // Assertions After - assertEq(steth.balanceOf(address(lidoFixedPriceMultiLpARM)), 0); - assertEq(weth.balanceOf(address(lidoFixedPriceMultiLpARM)), MIN_TOTAL_SUPPLY + amount * 2); - assertEq(lidoFixedPriceMultiLpARM.outstandingEther(), 0); - assertEq(lidoFixedPriceMultiLpARM.feesAccrued(), 0); // No perfs so no fees - assertEq(lidoFixedPriceMultiLpARM.lastTotalAssets(), MIN_TOTAL_SUPPLY + amount * 2); - assertEq(lidoFixedPriceMultiLpARM.balanceOf(alice), shares); - assertEq(lidoFixedPriceMultiLpARM.totalSupply(), MIN_TOTAL_SUPPLY + amount * 2); - assertEq(lidoFixedPriceMultiLpARM.totalAssets(), MIN_TOTAL_SUPPLY + amount * 2); + assertEq(steth.balanceOf(address(lidoARM)), 0); + assertEq(weth.balanceOf(address(lidoARM)), MIN_TOTAL_SUPPLY + amount * 2); + assertEq(lidoARM.outstandingEther(), 0); + assertEq(lidoARM.feesAccrued(), 0); // No perfs so no fees + assertEq(lidoARM.lastTotalAssets(), MIN_TOTAL_SUPPLY + amount * 2); + assertEq(lidoARM.balanceOf(alice), shares); + assertEq(lidoARM.totalSupply(), MIN_TOTAL_SUPPLY + amount * 2); + assertEq(lidoARM.totalAssets(), MIN_TOTAL_SUPPLY + amount * 2); assertEq(liquidityProviderController.liquidityProviderCaps(alice), 0); // All the caps are used assertEqQueueMetadata(0, 0, 0, 0); assertEq(shares, amount); // No perfs, so 1 ether * totalSupply (1e18 + 1e12) / totalAssets (1e18 + 1e12) = 1 ether @@ -251,59 +233,55 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_Deposit_Test_ is Fork_Shared_Tes setLiquidityProviderCap(address(this), DEFAULT_AMOUNT) { // simulate asset gain - uint256 balanceBefore = weth.balanceOf(address(lidoFixedPriceMultiLpARM)); + uint256 balanceBefore = weth.balanceOf(address(lidoARM)); uint256 assetGain = DEFAULT_AMOUNT; - deal(address(weth), address(lidoFixedPriceMultiLpARM), balanceBefore + assetGain); + deal(address(weth), address(lidoARM), balanceBefore + assetGain); // Assertions Before - assertEq(steth.balanceOf(address(lidoFixedPriceMultiLpARM)), 0); - assertEq(weth.balanceOf(address(lidoFixedPriceMultiLpARM)), MIN_TOTAL_SUPPLY + assetGain); - assertEq(lidoFixedPriceMultiLpARM.outstandingEther(), 0, "Outstanding ether before"); - assertEq(lidoFixedPriceMultiLpARM.feesAccrued(), 0, "fee accrued before"); // No perfs so no fees - assertEq(lidoFixedPriceMultiLpARM.lastTotalAssets(), MIN_TOTAL_SUPPLY, "last total assets before"); - assertEq(lidoFixedPriceMultiLpARM.balanceOf(address(this)), 0, "user shares before"); // Ensure no shares before - assertEq(lidoFixedPriceMultiLpARM.totalSupply(), MIN_TOTAL_SUPPLY, "Total supply before"); // Minted to dead on deploy + assertEq(steth.balanceOf(address(lidoARM)), 0); + assertEq(weth.balanceOf(address(lidoARM)), MIN_TOTAL_SUPPLY + assetGain); + assertEq(lidoARM.outstandingEther(), 0, "Outstanding ether before"); + assertEq(lidoARM.feesAccrued(), 0, "fee accrued before"); // No perfs so no fees + assertEq(lidoARM.lastTotalAssets(), MIN_TOTAL_SUPPLY, "last total assets before"); + assertEq(lidoARM.balanceOf(address(this)), 0, "user shares before"); // Ensure no shares before + assertEq(lidoARM.totalSupply(), MIN_TOTAL_SUPPLY, "Total supply before"); // Minted to dead on deploy // 80% of the asset gain goes to the total assets - assertEq(lidoFixedPriceMultiLpARM.totalAssets(), balanceBefore + assetGain * 80 / 100, "Total assets before"); + assertEq(lidoARM.totalAssets(), balanceBefore + assetGain * 80 / 100, "Total assets before"); assertEq(liquidityProviderController.liquidityProviderCaps(address(this)), DEFAULT_AMOUNT, "lp cap before"); assertEqQueueMetadata(0, 0, 0, 0); // 20% of the asset gain goes to the performance fees uint256 feesAccrued = assetGain * 20 / 100; - uint256 rawTotalAsset = weth.balanceOf(address(lidoFixedPriceMultiLpARM)) - feesAccrued; // No steth and no externalWithdrawQueue + uint256 rawTotalAsset = weth.balanceOf(address(lidoARM)) - feesAccrued; // No steth and no externalWithdrawQueue uint256 depositedAssets = DEFAULT_AMOUNT; uint256 expectedShares = depositedAssets * MIN_TOTAL_SUPPLY / rawTotalAsset; // Expected events vm.expectEmit({emitter: address(weth)}); - emit IERC20.Transfer(address(this), address(lidoFixedPriceMultiLpARM), depositedAssets); - vm.expectEmit({emitter: address(lidoFixedPriceMultiLpARM)}); + emit IERC20.Transfer(address(this), address(lidoARM), depositedAssets); + vm.expectEmit({emitter: address(lidoARM)}); emit IERC20.Transfer(address(0), address(this), expectedShares); vm.expectEmit({emitter: address(liquidityProviderController)}); emit LiquidityProviderController.LiquidityProviderCap(address(this), 0); // deposit assets - uint256 shares = lidoFixedPriceMultiLpARM.deposit(depositedAssets); + uint256 shares = lidoARM.deposit(depositedAssets); assertEq(shares, expectedShares, "minted shares"); // No perfs, so 1 ether * totalSupply (1e12) / totalAssets (1e12) = 1 ether // Assertions After - assertEq(steth.balanceOf(address(lidoFixedPriceMultiLpARM)), 0, "stETH balance after"); + assertEq(steth.balanceOf(address(lidoARM)), 0, "stETH balance after"); + assertEq(weth.balanceOf(address(lidoARM)), MIN_TOTAL_SUPPLY + assetGain + depositedAssets, "WETH balance after"); + assertEq(lidoARM.outstandingEther(), 0, "Outstanding ether after"); + assertEq(lidoARM.feesAccrued(), feesAccrued, "fees accrued after"); // No perfs so no fees assertEq( - weth.balanceOf(address(lidoFixedPriceMultiLpARM)), - MIN_TOTAL_SUPPLY + assetGain + depositedAssets, - "WETH balance after" - ); - assertEq(lidoFixedPriceMultiLpARM.outstandingEther(), 0, "Outstanding ether after"); - assertEq(lidoFixedPriceMultiLpARM.feesAccrued(), feesAccrued, "fees accrued after"); // No perfs so no fees - assertEq( - lidoFixedPriceMultiLpARM.lastTotalAssets(), + lidoARM.lastTotalAssets(), MIN_TOTAL_SUPPLY + (assetGain * 80 / 100) + depositedAssets, "last total assets after" ); - assertEq(lidoFixedPriceMultiLpARM.balanceOf(address(this)), expectedShares, "user shares after"); - assertEq(lidoFixedPriceMultiLpARM.totalSupply(), MIN_TOTAL_SUPPLY + expectedShares, "total supply after"); + assertEq(lidoARM.balanceOf(address(this)), expectedShares, "user shares after"); + assertEq(lidoARM.totalSupply(), MIN_TOTAL_SUPPLY + expectedShares, "total supply after"); assertEq(liquidityProviderController.liquidityProviderCaps(address(this)), 0, "lp cap after"); // All the caps are used assertEqQueueMetadata(0, 0, 0, 0); } @@ -315,32 +293,30 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_Deposit_Test_ is Fork_Shared_Tes setTotalAssetsCap(DEFAULT_AMOUNT * 3 + MIN_TOTAL_SUPPLY) setLiquidityProviderCap(address(this), DEFAULT_AMOUNT) setLiquidityProviderCap(alice, DEFAULT_AMOUNT * 5) - depositInLidoFixedPriceMultiLpARM(address(this), DEFAULT_AMOUNT) + depositInLidoARM(address(this), DEFAULT_AMOUNT) { // set stETH/WETH buy price to 1 - lidoFixedPriceMultiLpARM.setPrices(1e36, 1e36 + 1); + lidoARM.setPrices(1e36, 1e36 + 1); // User Swap stETH for 3/4 of WETH in the ARM deal(address(steth), address(this), DEFAULT_AMOUNT); - lidoFixedPriceMultiLpARM.swapTokensForExactTokens( - steth, weth, 3 * DEFAULT_AMOUNT / 4, DEFAULT_AMOUNT, address(this) - ); + lidoARM.swapTokensForExactTokens(steth, weth, 3 * DEFAULT_AMOUNT / 4, DEFAULT_AMOUNT, address(this)); // First user requests a full withdrawal - uint256 firstUserShares = lidoFixedPriceMultiLpARM.balanceOf(address(this)); - lidoFixedPriceMultiLpARM.requestRedeem(firstUserShares); + uint256 firstUserShares = lidoARM.balanceOf(address(this)); + lidoARM.requestRedeem(firstUserShares); // Assertions Before uint256 stethBalanceBefore = 3 * DEFAULT_AMOUNT / 4; - assertEq(steth.balanceOf(address(lidoFixedPriceMultiLpARM)), stethBalanceBefore, "stETH ARM balance before"); + assertEq(steth.balanceOf(address(lidoARM)), stethBalanceBefore, "stETH ARM balance before"); uint256 wethBalanceBefore = MIN_TOTAL_SUPPLY + DEFAULT_AMOUNT - 3 * DEFAULT_AMOUNT / 4; - assertEq(weth.balanceOf(address(lidoFixedPriceMultiLpARM)), wethBalanceBefore, "WETH ARM balance before"); - assertEq(lidoFixedPriceMultiLpARM.outstandingEther(), 0, "Outstanding ether before"); - assertEq(lidoFixedPriceMultiLpARM.feesAccrued(), 0, "Fees accrued before"); - assertEq(lidoFixedPriceMultiLpARM.lastTotalAssets(), MIN_TOTAL_SUPPLY, "last total assets before"); - assertEq(lidoFixedPriceMultiLpARM.balanceOf(alice), 0, "alice shares before"); - assertEq(lidoFixedPriceMultiLpARM.totalSupply(), MIN_TOTAL_SUPPLY, "total supply before"); - assertEq(lidoFixedPriceMultiLpARM.totalAssets(), MIN_TOTAL_SUPPLY, "total assets before"); + assertEq(weth.balanceOf(address(lidoARM)), wethBalanceBefore, "WETH ARM balance before"); + assertEq(lidoARM.outstandingEther(), 0, "Outstanding ether before"); + assertEq(lidoARM.feesAccrued(), 0, "Fees accrued before"); + assertEq(lidoARM.lastTotalAssets(), MIN_TOTAL_SUPPLY, "last total assets before"); + assertEq(lidoARM.balanceOf(alice), 0, "alice shares before"); + assertEq(lidoARM.totalSupply(), MIN_TOTAL_SUPPLY, "total supply before"); + assertEq(lidoARM.totalAssets(), MIN_TOTAL_SUPPLY, "total assets before"); assertEq(liquidityProviderController.liquidityProviderCaps(alice), DEFAULT_AMOUNT * 5, "lp cap before"); assertEqQueueMetadata(DEFAULT_AMOUNT, 0, 0, 1); @@ -348,27 +324,25 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_Deposit_Test_ is Fork_Shared_Tes // Expected events vm.expectEmit({emitter: address(weth)}); - emit IERC20.Transfer(alice, address(lidoFixedPriceMultiLpARM), amount); - vm.expectEmit({emitter: address(lidoFixedPriceMultiLpARM)}); + emit IERC20.Transfer(alice, address(lidoARM), amount); + vm.expectEmit({emitter: address(lidoARM)}); emit IERC20.Transfer(address(0), alice, amount); // shares == amount here vm.expectEmit({emitter: address(liquidityProviderController)}); emit LiquidityProviderController.LiquidityProviderCap(alice, DEFAULT_AMOUNT * 3); vm.prank(alice); // Main call - uint256 shares = lidoFixedPriceMultiLpARM.deposit(amount); + uint256 shares = lidoARM.deposit(amount); // Assertions After - assertEq(steth.balanceOf(address(lidoFixedPriceMultiLpARM)), stethBalanceBefore, "stETH ARM balance after"); - assertEq( - weth.balanceOf(address(lidoFixedPriceMultiLpARM)), wethBalanceBefore + amount, "WETH ARM balance after" - ); - assertEq(lidoFixedPriceMultiLpARM.outstandingEther(), 0, "Outstanding ether after"); - assertEq(lidoFixedPriceMultiLpARM.feesAccrued(), 0, "Fees accrued after"); // No perfs so no fees - assertEq(lidoFixedPriceMultiLpARM.lastTotalAssets(), MIN_TOTAL_SUPPLY + amount, "last total assets after"); - assertEq(lidoFixedPriceMultiLpARM.balanceOf(alice), shares, "alice shares after"); - assertEq(lidoFixedPriceMultiLpARM.totalSupply(), MIN_TOTAL_SUPPLY + amount, "total supply after"); - assertEq(lidoFixedPriceMultiLpARM.totalAssets(), MIN_TOTAL_SUPPLY + amount, "total assets after"); + assertEq(steth.balanceOf(address(lidoARM)), stethBalanceBefore, "stETH ARM balance after"); + assertEq(weth.balanceOf(address(lidoARM)), wethBalanceBefore + amount, "WETH ARM balance after"); + assertEq(lidoARM.outstandingEther(), 0, "Outstanding ether after"); + assertEq(lidoARM.feesAccrued(), 0, "Fees accrued after"); // No perfs so no fees + assertEq(lidoARM.lastTotalAssets(), MIN_TOTAL_SUPPLY + amount, "last total assets after"); + assertEq(lidoARM.balanceOf(alice), shares, "alice shares after"); + assertEq(lidoARM.totalSupply(), MIN_TOTAL_SUPPLY + amount, "total supply after"); + assertEq(lidoARM.totalAssets(), MIN_TOTAL_SUPPLY + amount, "total assets after"); assertEq(liquidityProviderController.liquidityProviderCaps(alice), DEFAULT_AMOUNT * 3, "alice cap after"); // All the caps are used // withdrawal request is now claimable assertEqQueueMetadata(DEFAULT_AMOUNT, 0, 0, 1); diff --git a/test/fork/LidoFixedPriceMultiLpARM/RequestRedeem.t.sol b/test/fork/LidoFixedPriceMultiLpARM/RequestRedeem.t.sol index 4e8ff32..dc5b949 100644 --- a/test/fork/LidoFixedPriceMultiLpARM/RequestRedeem.t.sol +++ b/test/fork/LidoFixedPriceMultiLpARM/RequestRedeem.t.sol @@ -6,10 +6,9 @@ import {Fork_Shared_Test_} from "test/fork/shared/Shared.sol"; // Contracts import {IERC20} from "contracts/Interfaces.sol"; -import {MultiLP} from "contracts/MultiLP.sol"; -import {PerformanceFee} from "contracts/PerformanceFee.sol"; +import {AbstractARM} from "contracts/AbstractARM.sol"; -contract Fork_Concrete_LidoFixedPriceMultiLpARM_RequestRedeem_Test_ is Fork_Shared_Test_ { +contract Fork_Concrete_LidoARM_RequestRedeem_Test_ is Fork_Shared_Test_ { ////////////////////////////////////////////////////// /// --- SETUP ////////////////////////////////////////////////////// @@ -27,40 +26,40 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_RequestRedeem_Test_ is Fork_Shar public setTotalAssetsCap(DEFAULT_AMOUNT + MIN_TOTAL_SUPPLY) setLiquidityProviderCap(address(this), DEFAULT_AMOUNT) - depositInLidoFixedPriceMultiLpARM(address(this), DEFAULT_AMOUNT) + depositInLidoARM(address(this), DEFAULT_AMOUNT) { // Assertions Before - assertEq(steth.balanceOf(address(lidoFixedPriceMultiLpARM)), 0); - assertEq(weth.balanceOf(address(lidoFixedPriceMultiLpARM)), MIN_TOTAL_SUPPLY + DEFAULT_AMOUNT); - assertEq(lidoFixedPriceMultiLpARM.outstandingEther(), 0); - assertEq(lidoFixedPriceMultiLpARM.feesAccrued(), 0); // No perfs so no fees - assertEq(lidoFixedPriceMultiLpARM.lastTotalAssets(), MIN_TOTAL_SUPPLY + DEFAULT_AMOUNT); - assertEq(lidoFixedPriceMultiLpARM.balanceOf(address(this)), DEFAULT_AMOUNT); - assertEq(lidoFixedPriceMultiLpARM.totalSupply(), MIN_TOTAL_SUPPLY + DEFAULT_AMOUNT); + assertEq(steth.balanceOf(address(lidoARM)), 0); + assertEq(weth.balanceOf(address(lidoARM)), MIN_TOTAL_SUPPLY + DEFAULT_AMOUNT); + assertEq(lidoARM.outstandingEther(), 0); + assertEq(lidoARM.feesAccrued(), 0); // No perfs so no fees + assertEq(lidoARM.lastTotalAssets(), MIN_TOTAL_SUPPLY + DEFAULT_AMOUNT); + assertEq(lidoARM.balanceOf(address(this)), DEFAULT_AMOUNT); + assertEq(lidoARM.totalSupply(), MIN_TOTAL_SUPPLY + DEFAULT_AMOUNT); assertEq(liquidityProviderController.liquidityProviderCaps(address(this)), 0); assertEqQueueMetadata(0, 0, 0, 0); - uint256 delay = lidoFixedPriceMultiLpARM.CLAIM_DELAY(); + uint256 delay = lidoARM.CLAIM_DELAY(); - vm.expectEmit({emitter: address(lidoFixedPriceMultiLpARM)}); + vm.expectEmit({emitter: address(lidoARM)}); emit IERC20.Transfer(address(this), address(0), DEFAULT_AMOUNT); - vm.expectEmit({emitter: address(lidoFixedPriceMultiLpARM)}); - emit MultiLP.RedeemRequested(address(this), 0, DEFAULT_AMOUNT, DEFAULT_AMOUNT, block.timestamp + delay); + vm.expectEmit({emitter: address(lidoARM)}); + emit AbstractARM.RedeemRequested(address(this), 0, DEFAULT_AMOUNT, DEFAULT_AMOUNT, block.timestamp + delay); // Main Call - (uint256 requestId, uint256 assets) = lidoFixedPriceMultiLpARM.requestRedeem(DEFAULT_AMOUNT); + (uint256 requestId, uint256 assets) = lidoARM.requestRedeem(DEFAULT_AMOUNT); // Assertions After assertEq(requestId, 0); // First request assertEqQueueMetadata(DEFAULT_AMOUNT, 0, 0, 1); // One request in the queue assertEqUserRequest(0, address(this), false, block.timestamp + delay, DEFAULT_AMOUNT, DEFAULT_AMOUNT); // Requested the full amount assertEq(assets, DEFAULT_AMOUNT, "Wrong amount of assets"); // As no profits, assets returned are the same as deposited - assertEq(steth.balanceOf(address(lidoFixedPriceMultiLpARM)), 0); - assertEq(weth.balanceOf(address(lidoFixedPriceMultiLpARM)), MIN_TOTAL_SUPPLY + DEFAULT_AMOUNT); - assertEq(lidoFixedPriceMultiLpARM.outstandingEther(), 0); - assertEq(lidoFixedPriceMultiLpARM.feesAccrued(), 0); // No perfs so no fees - assertEq(lidoFixedPriceMultiLpARM.lastTotalAssets(), MIN_TOTAL_SUPPLY); - assertEq(lidoFixedPriceMultiLpARM.balanceOf(address(this)), 0); - assertEq(lidoFixedPriceMultiLpARM.totalSupply(), MIN_TOTAL_SUPPLY); + assertEq(steth.balanceOf(address(lidoARM)), 0); + assertEq(weth.balanceOf(address(lidoARM)), MIN_TOTAL_SUPPLY + DEFAULT_AMOUNT); + assertEq(lidoARM.outstandingEther(), 0); + assertEq(lidoARM.feesAccrued(), 0); // No perfs so no fees + assertEq(lidoARM.lastTotalAssets(), MIN_TOTAL_SUPPLY); + assertEq(lidoARM.balanceOf(address(this)), 0); + assertEq(lidoARM.totalSupply(), MIN_TOTAL_SUPPLY); assertEq(liquidityProviderController.liquidityProviderCaps(address(this)), 0); } @@ -69,30 +68,30 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_RequestRedeem_Test_ is Fork_Shar public setTotalAssetsCap(DEFAULT_AMOUNT + MIN_TOTAL_SUPPLY) setLiquidityProviderCap(address(this), DEFAULT_AMOUNT) - depositInLidoFixedPriceMultiLpARM(address(this), DEFAULT_AMOUNT) - requestRedeemFromLidoFixedPriceMultiLpARM(address(this), DEFAULT_AMOUNT / 4) + depositInLidoARM(address(this), DEFAULT_AMOUNT) + requestRedeemFromLidoARM(address(this), DEFAULT_AMOUNT / 4) { // Assertions Before - assertEq(steth.balanceOf(address(lidoFixedPriceMultiLpARM)), 0); - assertEq(weth.balanceOf(address(lidoFixedPriceMultiLpARM)), MIN_TOTAL_SUPPLY + DEFAULT_AMOUNT); - assertEq(lidoFixedPriceMultiLpARM.outstandingEther(), 0); - assertEq(lidoFixedPriceMultiLpARM.feesAccrued(), 0); // No perfs so no fees - assertEq(lidoFixedPriceMultiLpARM.lastTotalAssets(), MIN_TOTAL_SUPPLY + DEFAULT_AMOUNT * 3 / 4); - assertEq(lidoFixedPriceMultiLpARM.balanceOf(address(this)), DEFAULT_AMOUNT * 3 / 4); - assertEq(lidoFixedPriceMultiLpARM.totalSupply(), MIN_TOTAL_SUPPLY + DEFAULT_AMOUNT * 3 / 4); + assertEq(steth.balanceOf(address(lidoARM)), 0); + assertEq(weth.balanceOf(address(lidoARM)), MIN_TOTAL_SUPPLY + DEFAULT_AMOUNT); + assertEq(lidoARM.outstandingEther(), 0); + assertEq(lidoARM.feesAccrued(), 0); // No perfs so no fees + assertEq(lidoARM.lastTotalAssets(), MIN_TOTAL_SUPPLY + DEFAULT_AMOUNT * 3 / 4); + assertEq(lidoARM.balanceOf(address(this)), DEFAULT_AMOUNT * 3 / 4); + assertEq(lidoARM.totalSupply(), MIN_TOTAL_SUPPLY + DEFAULT_AMOUNT * 3 / 4); assertEq(liquidityProviderController.liquidityProviderCaps(address(this)), 0); // Down only assertEqQueueMetadata(DEFAULT_AMOUNT / 4, 0, 0, 1); - uint256 delay = lidoFixedPriceMultiLpARM.CLAIM_DELAY(); + uint256 delay = lidoARM.CLAIM_DELAY(); - vm.expectEmit({emitter: address(lidoFixedPriceMultiLpARM)}); + vm.expectEmit({emitter: address(lidoARM)}); emit IERC20.Transfer(address(this), address(0), DEFAULT_AMOUNT / 2); - vm.expectEmit({emitter: address(lidoFixedPriceMultiLpARM)}); - emit MultiLP.RedeemRequested( + vm.expectEmit({emitter: address(lidoARM)}); + emit AbstractARM.RedeemRequested( address(this), 1, DEFAULT_AMOUNT / 2, DEFAULT_AMOUNT * 3 / 4, block.timestamp + delay ); // Main Call - (uint256 requestId, uint256 assets) = lidoFixedPriceMultiLpARM.requestRedeem(DEFAULT_AMOUNT / 2); + (uint256 requestId, uint256 assets) = lidoARM.requestRedeem(DEFAULT_AMOUNT / 2); // Assertions After assertEq(requestId, 1); // Second request @@ -101,13 +100,13 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_RequestRedeem_Test_ is Fork_Shar 1, address(this), false, block.timestamp + delay, DEFAULT_AMOUNT / 2, DEFAULT_AMOUNT * 3 / 4 ); assertEq(assets, DEFAULT_AMOUNT / 2, "Wrong amount of assets"); // As no profits, assets returned are the same as deposited - assertEq(steth.balanceOf(address(lidoFixedPriceMultiLpARM)), 0); - assertEq(weth.balanceOf(address(lidoFixedPriceMultiLpARM)), MIN_TOTAL_SUPPLY + DEFAULT_AMOUNT); - assertEq(lidoFixedPriceMultiLpARM.outstandingEther(), 0); - assertEq(lidoFixedPriceMultiLpARM.feesAccrued(), 0); // No perfs so no fees - assertEq(lidoFixedPriceMultiLpARM.lastTotalAssets(), MIN_TOTAL_SUPPLY + DEFAULT_AMOUNT * 1 / 4); - assertEq(lidoFixedPriceMultiLpARM.balanceOf(address(this)), DEFAULT_AMOUNT * 1 / 4); - assertEq(lidoFixedPriceMultiLpARM.totalSupply(), MIN_TOTAL_SUPPLY + DEFAULT_AMOUNT * 1 / 4); + assertEq(steth.balanceOf(address(lidoARM)), 0); + assertEq(weth.balanceOf(address(lidoARM)), MIN_TOTAL_SUPPLY + DEFAULT_AMOUNT); + assertEq(lidoARM.outstandingEther(), 0); + assertEq(lidoARM.feesAccrued(), 0); // No perfs so no fees + assertEq(lidoARM.lastTotalAssets(), MIN_TOTAL_SUPPLY + DEFAULT_AMOUNT * 1 / 4); + assertEq(lidoARM.balanceOf(address(this)), DEFAULT_AMOUNT * 1 / 4); + assertEq(lidoARM.totalSupply(), MIN_TOTAL_SUPPLY + DEFAULT_AMOUNT * 1 / 4); assertEq(liquidityProviderController.liquidityProviderCaps(address(this)), 0); // Down only } @@ -116,41 +115,37 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_RequestRedeem_Test_ is Fork_Shar public setTotalAssetsCap(DEFAULT_AMOUNT + MIN_TOTAL_SUPPLY) setLiquidityProviderCap(address(this), DEFAULT_AMOUNT) - depositInLidoFixedPriceMultiLpARM(address(this), DEFAULT_AMOUNT) + depositInLidoARM(address(this), DEFAULT_AMOUNT) { // Assertions Before // Not needed as the same as in `test_RequestRedeem_AfterFirstDeposit_NoPerfs_EmptyWithdrawQueue` // Simulate assets gain in ARM uint256 assetsGain = DEFAULT_AMOUNT; - deal( - address(weth), - address(lidoFixedPriceMultiLpARM), - weth.balanceOf(address(lidoFixedPriceMultiLpARM)) + assetsGain - ); + deal(address(weth), address(lidoARM), weth.balanceOf(address(lidoARM)) + assetsGain); // Calculate expected values uint256 feeAccrued = assetsGain * 20 / 100; // 20% fee - uint256 totalAsset = weth.balanceOf(address(lidoFixedPriceMultiLpARM)) - feeAccrued; + uint256 totalAsset = weth.balanceOf(address(lidoARM)) - feeAccrued; uint256 expectedAssets = DEFAULT_AMOUNT * totalAsset / (MIN_TOTAL_SUPPLY + DEFAULT_AMOUNT); uint256 expectedAssetsDead = MIN_TOTAL_SUPPLY * totalAsset / (MIN_TOTAL_SUPPLY + DEFAULT_AMOUNT); - vm.expectEmit({emitter: address(lidoFixedPriceMultiLpARM)}); - emit PerformanceFee.FeeCalculated(feeAccrued, assetsGain); - vm.expectEmit({emitter: address(lidoFixedPriceMultiLpARM)}); + vm.expectEmit({emitter: address(lidoARM)}); + emit AbstractARM.FeeCalculated(feeAccrued, assetsGain); + vm.expectEmit({emitter: address(lidoARM)}); emit IERC20.Transfer(address(this), address(0), DEFAULT_AMOUNT); // Main call - lidoFixedPriceMultiLpARM.requestRedeem(DEFAULT_AMOUNT); + lidoARM.requestRedeem(DEFAULT_AMOUNT); - uint256 delay = lidoFixedPriceMultiLpARM.CLAIM_DELAY(); + uint256 delay = lidoARM.CLAIM_DELAY(); // Assertions After - assertEq(steth.balanceOf(address(lidoFixedPriceMultiLpARM)), 0); - assertEq(weth.balanceOf(address(lidoFixedPriceMultiLpARM)), MIN_TOTAL_SUPPLY + DEFAULT_AMOUNT * 2); // +perfs - assertEq(lidoFixedPriceMultiLpARM.outstandingEther(), 0); - assertEq(lidoFixedPriceMultiLpARM.feesAccrued(), feeAccrued); - assertApproxEqAbs(lidoFixedPriceMultiLpARM.lastTotalAssets(), expectedAssetsDead, 1); // 1 wei of error - assertEq(lidoFixedPriceMultiLpARM.balanceOf(address(this)), 0); - assertEq(lidoFixedPriceMultiLpARM.totalSupply(), MIN_TOTAL_SUPPLY); + assertEq(steth.balanceOf(address(lidoARM)), 0); + assertEq(weth.balanceOf(address(lidoARM)), MIN_TOTAL_SUPPLY + DEFAULT_AMOUNT * 2); // +perfs + assertEq(lidoARM.outstandingEther(), 0); + assertEq(lidoARM.feesAccrued(), feeAccrued); + assertApproxEqAbs(lidoARM.lastTotalAssets(), expectedAssetsDead, 1); // 1 wei of error + assertEq(lidoARM.balanceOf(address(this)), 0); + assertEq(lidoARM.totalSupply(), MIN_TOTAL_SUPPLY); assertEq(liquidityProviderController.liquidityProviderCaps(address(this)), 0); assertEqQueueMetadata(expectedAssets, 0, 0, 1); assertEqUserRequest(0, address(this), false, block.timestamp + delay, expectedAssets, expectedAssets); @@ -161,39 +156,35 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_RequestRedeem_Test_ is Fork_Shar public setTotalAssetsCap(DEFAULT_AMOUNT + MIN_TOTAL_SUPPLY) setLiquidityProviderCap(address(this), DEFAULT_AMOUNT) - depositInLidoFixedPriceMultiLpARM(address(this), DEFAULT_AMOUNT) + depositInLidoARM(address(this), DEFAULT_AMOUNT) { // Assertions Before // Not needed as the same as in `test_RequestRedeem_AfterFirstDeposit_NoPerfs_EmptyWithdrawQueue` // Simulate assets loss in ARM uint256 assetsLoss = DEFAULT_AMOUNT / 10; // 0.1 ether of loss - deal( - address(weth), - address(lidoFixedPriceMultiLpARM), - weth.balanceOf(address(lidoFixedPriceMultiLpARM)) - assetsLoss - ); + deal(address(weth), address(lidoARM), weth.balanceOf(address(lidoARM)) - assetsLoss); // Calculate expected values uint256 feeAccrued = 0; // No profits - uint256 totalAsset = weth.balanceOf(address(lidoFixedPriceMultiLpARM)); + uint256 totalAsset = weth.balanceOf(address(lidoARM)); uint256 expectedAssets = DEFAULT_AMOUNT * totalAsset / (MIN_TOTAL_SUPPLY + DEFAULT_AMOUNT); uint256 expectedAssetsDead = MIN_TOTAL_SUPPLY * totalAsset / (MIN_TOTAL_SUPPLY + DEFAULT_AMOUNT); - vm.expectEmit({emitter: address(lidoFixedPriceMultiLpARM)}); + vm.expectEmit({emitter: address(lidoARM)}); emit IERC20.Transfer(address(this), address(0), DEFAULT_AMOUNT); // Main call - lidoFixedPriceMultiLpARM.requestRedeem(DEFAULT_AMOUNT); + lidoARM.requestRedeem(DEFAULT_AMOUNT); - uint256 delay = lidoFixedPriceMultiLpARM.CLAIM_DELAY(); + uint256 delay = lidoARM.CLAIM_DELAY(); // Assertions After - assertEq(steth.balanceOf(address(lidoFixedPriceMultiLpARM)), 0); - assertEq(weth.balanceOf(address(lidoFixedPriceMultiLpARM)), MIN_TOTAL_SUPPLY + DEFAULT_AMOUNT - assetsLoss); - assertEq(lidoFixedPriceMultiLpARM.outstandingEther(), 0); - assertEq(lidoFixedPriceMultiLpARM.feesAccrued(), feeAccrued); - assertApproxEqAbs(lidoFixedPriceMultiLpARM.lastTotalAssets(), expectedAssetsDead, 1); // 1 wei of error - assertEq(lidoFixedPriceMultiLpARM.balanceOf(address(this)), 0); - assertEq(lidoFixedPriceMultiLpARM.totalSupply(), MIN_TOTAL_SUPPLY); + assertEq(steth.balanceOf(address(lidoARM)), 0); + assertEq(weth.balanceOf(address(lidoARM)), MIN_TOTAL_SUPPLY + DEFAULT_AMOUNT - assetsLoss); + assertEq(lidoARM.outstandingEther(), 0); + assertEq(lidoARM.feesAccrued(), feeAccrued); + assertApproxEqAbs(lidoARM.lastTotalAssets(), expectedAssetsDead, 1); // 1 wei of error + assertEq(lidoARM.balanceOf(address(this)), 0); + assertEq(lidoARM.totalSupply(), MIN_TOTAL_SUPPLY); assertEq(liquidityProviderController.liquidityProviderCaps(address(this)), 0); assertEqQueueMetadata(expectedAssets, 0, 0, 1); assertEqUserRequest(0, address(this), false, block.timestamp + delay, expectedAssets, expectedAssets); diff --git a/test/fork/LidoFixedPriceMultiLpARM/RequestStETHWithdrawalForETH.t.sol b/test/fork/LidoFixedPriceMultiLpARM/RequestStETHWithdrawalForETH.t.sol index 1b3c259..8a4d896 100644 --- a/test/fork/LidoFixedPriceMultiLpARM/RequestStETHWithdrawalForETH.t.sol +++ b/test/fork/LidoFixedPriceMultiLpARM/RequestStETHWithdrawalForETH.t.sol @@ -7,14 +7,14 @@ import {Fork_Shared_Test_} from "test/fork/shared/Shared.sol"; // Contracts import {IERC20} from "contracts/Interfaces.sol"; -contract Fork_Concrete_LidoFixedPriceMultiLpARM_RequestStETHWithdrawalForETH_Test_ is Fork_Shared_Test_ { +contract Fork_Concrete_LidoARM_RequestStETHWithdrawalForETH_Test_ is Fork_Shared_Test_ { ////////////////////////////////////////////////////// /// --- SETUP ////////////////////////////////////////////////////// function setUp() public override { super.setUp(); - deal(address(steth), address(lidoFixedPriceMultiLpARM), 10_000 ether); + deal(address(steth), address(lidoARM), 10_000 ether); } ////////////////////////////////////////////////////// @@ -22,59 +22,46 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_RequestStETHWithdrawalForETH_Tes ////////////////////////////////////////////////////// function test_RevertWhen_RequestStETHWithdrawalForETH_NotOperator() public asRandomAddress { vm.expectRevert("ARM: Only operator or owner can call this function."); - lidoFixedPriceMultiLpARM.requestStETHWithdrawalForETH(new uint256[](0)); - } - - function test_RevertWhen_RequestStETHWithdrawalForETH_Because_AllowanceExceeded() - public - asLidoFixedPriceMulltiLpARMOperator - { - uint256[] memory amounts = new uint256[](1); - amounts[0] = DEFAULT_AMOUNT; - - vm.expectRevert("ALLOWANCE_EXCEEDED"); - lidoFixedPriceMultiLpARM.requestStETHWithdrawalForETH(amounts); + lidoARM.requestStETHWithdrawalForETH(new uint256[](0)); } function test_RevertWhen_RequestStETHWithdrawalForETH_Because_BalanceExceeded() public asLidoFixedPriceMulltiLpARMOperator - approveStETHOnLidoFixedPriceMultiLpARM + approveStETHOnLidoARM { // Remove all stETH from the contract - deal(address(steth), address(lidoFixedPriceMultiLpARM), 0); + deal(address(steth), address(lidoARM), 0); uint256[] memory amounts = new uint256[](1); amounts[0] = DEFAULT_AMOUNT; vm.expectRevert("BALANCE_EXCEEDED"); - lidoFixedPriceMultiLpARM.requestStETHWithdrawalForETH(amounts); + lidoARM.requestStETHWithdrawalForETH(amounts); } ////////////////////////////////////////////////////// /// --- PASSING TESTS ////////////////////////////////////////////////////// function test_RequestStETHWithdrawalForETH_EmptyList() public asLidoFixedPriceMulltiLpARMOperator { - uint256[] memory requestIds = lidoFixedPriceMultiLpARM.requestStETHWithdrawalForETH(new uint256[](0)); + uint256[] memory requestIds = lidoARM.requestStETHWithdrawalForETH(new uint256[](0)); assertEq(requestIds.length, 0); } function test_RequestStETHWithdrawalForETH_SingleAmount_1ether() public asLidoFixedPriceMulltiLpARMOperator - approveStETHOnLidoFixedPriceMultiLpARM + approveStETHOnLidoARM { uint256[] memory amounts = new uint256[](1); amounts[0] = DEFAULT_AMOUNT; // Expected events vm.expectEmit({emitter: address(steth)}); - emit IERC20.Transfer( - address(lidoFixedPriceMultiLpARM), address(lidoFixedPriceMultiLpARM.withdrawalQueue()), amounts[0] - ); + emit IERC20.Transfer(address(lidoARM), address(lidoARM.withdrawalQueue()), amounts[0]); // Main call - uint256[] memory requestIds = lidoFixedPriceMultiLpARM.requestStETHWithdrawalForETH(amounts); + uint256[] memory requestIds = lidoARM.requestStETHWithdrawalForETH(amounts); assertEq(requestIds.length, 1); assertGt(requestIds[0], 0); @@ -83,19 +70,17 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_RequestStETHWithdrawalForETH_Tes function test_RequestStETHWithdrawalForETH_SingleAmount_1000ethers() public asLidoFixedPriceMulltiLpARMOperator - approveStETHOnLidoFixedPriceMultiLpARM + approveStETHOnLidoARM { uint256[] memory amounts = new uint256[](1); amounts[0] = 1_000 ether; // Expected events vm.expectEmit({emitter: address(steth)}); - emit IERC20.Transfer( - address(lidoFixedPriceMultiLpARM), address(lidoFixedPriceMultiLpARM.withdrawalQueue()), amounts[0] - ); + emit IERC20.Transfer(address(lidoARM), address(lidoARM.withdrawalQueue()), amounts[0]); // Main call - uint256[] memory requestIds = lidoFixedPriceMultiLpARM.requestStETHWithdrawalForETH(amounts); + uint256[] memory requestIds = lidoARM.requestStETHWithdrawalForETH(amounts); assertEq(requestIds.length, 1); assertGt(requestIds[0], 0); @@ -104,7 +89,7 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_RequestStETHWithdrawalForETH_Tes function test_RequestStETHWithdrawalForETH_MultipleAmount() public asLidoFixedPriceMulltiLpARMOperator - approveStETHOnLidoFixedPriceMultiLpARM + approveStETHOnLidoARM { uint256 length = _bound(vm.randomUint(), 2, 10); uint256[] memory amounts = new uint256[](length); @@ -113,7 +98,7 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_RequestStETHWithdrawalForETH_Tes } // Main call - uint256[] memory requestIds = lidoFixedPriceMultiLpARM.requestStETHWithdrawalForETH(amounts); + uint256[] memory requestIds = lidoARM.requestStETHWithdrawalForETH(amounts); uint256 initialRequestId = requestIds[0]; assertGt(initialRequestId, 0); diff --git a/test/fork/LidoFixedPriceMultiLpARM/Setters.t.sol b/test/fork/LidoFixedPriceMultiLpARM/Setters.t.sol index 1881d65..4cf049c 100644 --- a/test/fork/LidoFixedPriceMultiLpARM/Setters.t.sol +++ b/test/fork/LidoFixedPriceMultiLpARM/Setters.t.sol @@ -6,11 +6,9 @@ import {Fork_Shared_Test_} from "test/fork/shared/Shared.sol"; // Contracts import {IERC20} from "contracts/Interfaces.sol"; -import {MultiLP} from "contracts/MultiLP.sol"; -import {PerformanceFee} from "contracts/PerformanceFee.sol"; -import {LiquidityProviderControllerARM} from "contracts/LiquidityProviderControllerARM.sol"; +import {AbstractARM} from "contracts/AbstractARM.sol"; -contract Fork_Concrete_lidoFixedPriceMulltiLpARM_Setters_Test_ is Fork_Shared_Test_ { +contract Fork_Concrete_lidoARM_Setters_Test_ is Fork_Shared_Test_ { ////////////////////////////////////////////////////// /// --- SETUP ////////////////////////////////////////////////////// @@ -23,55 +21,52 @@ contract Fork_Concrete_lidoFixedPriceMulltiLpARM_Setters_Test_ is Fork_Shared_Te ////////////////////////////////////////////////////// function test_RevertWhen_PerformanceFee_SetFee_Because_NotOwner() public asRandomAddress { vm.expectRevert("ARM: Only owner can call this function."); - lidoFixedPriceMultiLpARM.setFee(0); + lidoARM.setFee(0); } - function test_RevertWhen_PerformanceFee_SetFee_Because_FeeIsTooHigh() public asLidoFixedPriceMultiLpARMOwner { - uint256 max = lidoFixedPriceMultiLpARM.FEE_SCALE(); + function test_RevertWhen_PerformanceFee_SetFee_Because_FeeIsTooHigh() public asLidoARMOwner { + uint256 max = lidoARM.FEE_SCALE(); vm.expectRevert("ARM: fee too high"); - lidoFixedPriceMultiLpARM.setFee(max + 1); + lidoARM.setFee(max + 1); } function test_RevertWhen_PerformanceFee_SetFeeCollector_Because_NotOwner() public asRandomAddress { vm.expectRevert("ARM: Only owner can call this function."); - lidoFixedPriceMultiLpARM.setFeeCollector(address(0)); + lidoARM.setFeeCollector(address(0)); } - function test_RevertWhen_PerformanceFee_SetFeeCollector_Because_FeeCollectorIsZero() - public - asLidoFixedPriceMultiLpARMOwner - { + function test_RevertWhen_PerformanceFee_SetFeeCollector_Because_FeeCollectorIsZero() public asLidoARMOwner { vm.expectRevert("ARM: invalid fee collector"); - lidoFixedPriceMultiLpARM.setFeeCollector(address(0)); + lidoARM.setFeeCollector(address(0)); } ////////////////////////////////////////////////////// /// --- PERFORMANCE FEE - PASSING TEST ////////////////////////////////////////////////////// - function test_PerformanceFee_SetFee_() public asLidoFixedPriceMultiLpARMOwner { - uint256 feeBefore = lidoFixedPriceMultiLpARM.fee(); + function test_PerformanceFee_SetFee_() public asLidoARMOwner { + uint256 feeBefore = lidoARM.fee(); - uint256 newFee = _bound(vm.randomUint(), 0, lidoFixedPriceMultiLpARM.FEE_SCALE()); + uint256 newFee = _bound(vm.randomUint(), 0, lidoARM.FEE_SCALE()); - vm.expectEmit({emitter: address(lidoFixedPriceMultiLpARM)}); - emit PerformanceFee.FeeUpdated(newFee); - lidoFixedPriceMultiLpARM.setFee(newFee); + vm.expectEmit({emitter: address(lidoARM)}); + emit AbstractARM.FeeUpdated(newFee); + lidoARM.setFee(newFee); - assertEq(lidoFixedPriceMultiLpARM.fee(), newFee); - assertNotEq(feeBefore, lidoFixedPriceMultiLpARM.fee()); + assertEq(lidoARM.fee(), newFee); + assertNotEq(feeBefore, lidoARM.fee()); } - function test_PerformanceFee_SetFeeCollector() public asLidoFixedPriceMultiLpARMOwner { - address feeCollectorBefore = lidoFixedPriceMultiLpARM.feeCollector(); + function test_PerformanceFee_SetFeeCollector() public asLidoARMOwner { + address feeCollectorBefore = lidoARM.feeCollector(); address newFeeCollector = vm.randomAddress(); - vm.expectEmit({emitter: address(lidoFixedPriceMultiLpARM)}); - emit PerformanceFee.FeeCollectorUpdated(newFeeCollector); - lidoFixedPriceMultiLpARM.setFeeCollector(newFeeCollector); + vm.expectEmit({emitter: address(lidoARM)}); + emit AbstractARM.FeeCollectorUpdated(newFeeCollector); + lidoARM.setFeeCollector(newFeeCollector); - assertEq(lidoFixedPriceMultiLpARM.feeCollector(), newFeeCollector); - assertNotEq(feeCollectorBefore, lidoFixedPriceMultiLpARM.feeCollector()); + assertEq(lidoARM.feeCollector(), newFeeCollector); + assertNotEq(feeCollectorBefore, lidoARM.feeCollector()); } ////////////////////////////////////////////////////// @@ -79,46 +74,46 @@ contract Fork_Concrete_lidoFixedPriceMulltiLpARM_Setters_Test_ is Fork_Shared_Te ////////////////////////////////////////////////////// function test_RevertWhen_SetPrices_Because_PriceCross() public { vm.expectRevert("ARM: Price cross"); - lidoFixedPriceMultiLpARM.setPrices(90 * 1e33, 89 * 1e33); + lidoARM.setPrices(90 * 1e33, 89 * 1e33); vm.expectRevert("ARM: Price cross"); - lidoFixedPriceMultiLpARM.setPrices(72, 70); + lidoARM.setPrices(72, 70); vm.expectRevert("ARM: Price cross"); - lidoFixedPriceMultiLpARM.setPrices(1005 * 1e33, 1000 * 1e33); + lidoARM.setPrices(1005 * 1e33, 1000 * 1e33); // Both set to 1.0 vm.expectRevert("ARM: Price cross"); - lidoFixedPriceMultiLpARM.setPrices(1e36, 1e36); + lidoARM.setPrices(1e36, 1e36); } function test_RevertWhen_FixedPriceARM_SetPrices_Because_PriceRange() public asLidoFixedPriceMulltiLpARMOperator { // buy price 11 basis points higher than 1.0 vm.expectRevert("ARM: buy price too high"); - lidoFixedPriceMultiLpARM.setPrices(10011e32, 10020e32); + lidoARM.setPrices(1.0011 * 1e36, 1.002 * 1e36); // sell price 11 basis points lower than 1.0 vm.expectRevert("ARM: sell price too low"); - lidoFixedPriceMultiLpARM.setPrices(9980e32, 9989e32); + lidoARM.setPrices(0.998 * 1e36, 0.9989 * 1e36); // Forgot to scale up to 36 decimals vm.expectRevert("ARM: sell price too low"); - lidoFixedPriceMultiLpARM.setPrices(1e18, 1e18); + lidoARM.setPrices(1e18, 1e18); } function test_RevertWhen_FixedPriceARM_SetPrices_Because_NotOwnerOrOperator() public asRandomAddress { vm.expectRevert("ARM: Only operator or owner can call this function."); - lidoFixedPriceMultiLpARM.setPrices(0, 0); + lidoARM.setPrices(0, 0); } - function test_SellPriceCannotCrossOne() public asLidoFixedPriceMulltiLpARMOperator { + function test_SellPriceCannotCrossOneByMoreThanTenBps() public asLidoFixedPriceMulltiLpARMOperator { vm.expectRevert("ARM: sell price too low"); - lidoFixedPriceMulltiLpARM.setPrices(0.9997 * 1e36, 0.99999 * 1e36); + lidoARM.setPrices(0.998 * 1e36, 0.9989 * 1e36); } - function test_BuyPriceCannotCrossOne() public asLidoFixedPriceMulltiLpARMOperator { + function test_BuyPriceCannotCrossOneByMoreThanTenBps() public asLidoFixedPriceMulltiLpARMOperator { vm.expectRevert("ARM: buy price too high"); - lidoFixedPriceMulltiLpARM.setPrices(1.0 * 1e36, 1.0001 * 1e36); + lidoARM.setPrices(1.0011 * 1e36, 1.002 * 1e36); } ////////////////////////////////////////////////////// @@ -126,27 +121,27 @@ contract Fork_Concrete_lidoFixedPriceMulltiLpARM_Setters_Test_ is Fork_Shared_Te ////////////////////////////////////////////////////// function test_FixedPriceARM_SetPrices_Operator() public asLidoFixedPriceMulltiLpARMOperator { // buy price 10 basis points higher than 1.0 - lidoFixedPriceMultiLpARM.setPrices(1001e33, 1002e33); + lidoARM.setPrices(1001e33, 1002e33); // sell price 10 basis points lower than 1.0 - lidoFixedPriceMultiLpARM.setPrices(9980e32, 9991e32); + lidoARM.setPrices(9980e32, 9991e32); // 2% of one basis point spread - lidoFixedPriceMultiLpARM.setPrices(999999e30, 1000001e30); + lidoARM.setPrices(999999e30, 1000001e30); - lidoFixedPriceMultiLpARM.setPrices(992 * 1e33, 1001 * 1e33); - lidoFixedPriceMultiLpARM.setPrices(1001 * 1e33, 1004 * 1e33); - lidoFixedPriceMultiLpARM.setPrices(992 * 1e33, 2000 * 1e33); + lidoARM.setPrices(992 * 1e33, 1001 * 1e33); + lidoARM.setPrices(1001 * 1e33, 1004 * 1e33); + lidoARM.setPrices(992 * 1e33, 2000 * 1e33); // Check the traderates - assertEq(lidoFixedPriceMultiLpARM.traderate0(), 500 * 1e33); - assertEq(lidoFixedPriceMultiLpARM.traderate1(), 992 * 1e33); + assertEq(lidoARM.traderate0(), 500 * 1e33); + assertEq(lidoARM.traderate1(), 992 * 1e33); } function test_FixedPriceARM_SetPrices_Owner() public { // buy price 11 basis points higher than 1.0 - lidoFixedPriceMultiLpARM.setPrices(10011e32, 10020e32); + lidoARM.setPrices(10011e32, 10020e32); // sell price 11 basis points lower than 1.0 - lidoFixedPriceMultiLpARM.setPrices(9980e32, 9989e32); + lidoARM.setPrices(9980e32, 9989e32); } ////////////////////////////////////////////////////// @@ -154,12 +149,12 @@ contract Fork_Concrete_lidoFixedPriceMulltiLpARM_Setters_Test_ is Fork_Shared_Te ////////////////////////////////////////////////////// function test_RevertWhen_Ownable_SetOwner_Because_NotOwner() public asRandomAddress { vm.expectRevert("ARM: Only owner can call this function."); - lidoFixedPriceMultiLpARM.setOwner(address(0)); + lidoARM.setOwner(address(0)); } function test_RevertWhen_Ownable_SetOperator_Because_NotOwner() public asRandomAddress { vm.expectRevert("ARM: Only owner can call this function."); - lidoFixedPriceMultiLpARM.setOperator(address(0)); + lidoARM.setOperator(address(0)); } ////////////////////////////////////////////////////// @@ -170,19 +165,19 @@ contract Fork_Concrete_lidoFixedPriceMulltiLpARM_Setters_Test_ is Fork_Shared_Te asRandomAddress { vm.expectRevert("ARM: Only owner can call this function."); - lidoFixedPriceMultiLpARM.setLiquidityProviderController(address(0)); + lidoARM.setLiquidityProviderController(address(0)); } ////////////////////////////////////////////////////// /// --- LIQUIIDITY PROVIDER CONTROLLER - PASSING TESTS ////////////////////////////////////////////////////// - function test_LiquidityProviderController_SetLiquidityProvider() public asLidoFixedPriceMultiLpARMOwner { + function test_LiquidityProviderController_SetLiquidityProvider() public asLidoARMOwner { address newLiquidityProviderController = vm.randomAddress(); - vm.expectEmit({emitter: address(lidoFixedPriceMultiLpARM)}); - emit LiquidityProviderControllerARM.LiquidityProviderControllerUpdated(newLiquidityProviderController); - lidoFixedPriceMultiLpARM.setLiquidityProviderController(newLiquidityProviderController); + vm.expectEmit({emitter: address(lidoARM)}); + emit AbstractARM.LiquidityProviderControllerUpdated(newLiquidityProviderController); + lidoARM.setLiquidityProviderController(newLiquidityProviderController); - assertEq(lidoFixedPriceMultiLpARM.liquidityProviderController(), newLiquidityProviderController); + assertEq(lidoARM.liquidityProviderController(), newLiquidityProviderController); } } diff --git a/test/fork/LidoFixedPriceMultiLpARM/SwapExactTokensForTokens.t.sol b/test/fork/LidoFixedPriceMultiLpARM/SwapExactTokensForTokens.t.sol index 9f2f14b..219b322 100644 --- a/test/fork/LidoFixedPriceMultiLpARM/SwapExactTokensForTokens.t.sol +++ b/test/fork/LidoFixedPriceMultiLpARM/SwapExactTokensForTokens.t.sol @@ -6,7 +6,7 @@ import {Fork_Shared_Test_} from "test/fork/shared/Shared.sol"; import {IERC20} from "contracts/Interfaces.sol"; -contract Fork_Concrete_LidoFixedPriceMultiLpARM_SwapExactTokensForTokens_Test is Fork_Shared_Test_ { +contract Fork_Concrete_LidoARM_SwapExactTokensForTokens_Test is Fork_Shared_Test_ { ////////////////////////////////////////////////////// /// --- CONSTANTS ////////////////////////////////////////////////////// @@ -26,17 +26,17 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_SwapExactTokensForTokens_Test is deal(address(weth), address(this), 1_000 ether); deal(address(steth), address(this), 1_000 ether); - deal(address(weth), address(lidoFixedPriceMultiLpARM), 1_000 ether); - deal(address(steth), address(lidoFixedPriceMultiLpARM), 1_000 ether); + deal(address(weth), address(lidoARM), 1_000 ether); + deal(address(steth), address(lidoARM), 1_000 ether); } ////////////////////////////////////////////////////// /// --- REVERTING TESTS ////////////////////////////////////////////////////// function test_RevertWhen_SwapExactTokensForTokens_Because_InvalidTokenOut1() public { - lidoFixedPriceMultiLpARM.token0(); + lidoARM.token0(); vm.expectRevert("ARM: Invalid out token"); - lidoFixedPriceMultiLpARM.swapExactTokensForTokens( + lidoARM.swapExactTokensForTokens( steth, // inToken badToken, // outToken 1, // amountIn @@ -47,7 +47,7 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_SwapExactTokensForTokens_Test is function test_RevertWhen_SwapExactTokensForTokens_Because_InvalidTokenOut0() public { vm.expectRevert("ARM: Invalid out token"); - lidoFixedPriceMultiLpARM.swapExactTokensForTokens( + lidoARM.swapExactTokensForTokens( weth, // inToken badToken, // outToken 1, // amountIn @@ -58,7 +58,7 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_SwapExactTokensForTokens_Test is function test_RevertWhen_SwapExactTokensForTokens_Because_InvalidTokenIn() public { vm.expectRevert("ARM: Invalid in token"); - lidoFixedPriceMultiLpARM.swapExactTokensForTokens( + lidoARM.swapExactTokensForTokens( badToken, // inToken steth, // outToken 1, // amountIn @@ -69,7 +69,7 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_SwapExactTokensForTokens_Test is function test_RevertWhen_SwapExactTokensForTokens_Because_BothInvalidTokens() public { vm.expectRevert("ARM: Invalid in token"); - lidoFixedPriceMultiLpARM.swapExactTokensForTokens( + lidoARM.swapExactTokensForTokens( badToken, // inToken badToken, // outToken 1, // amountIn @@ -82,7 +82,7 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_SwapExactTokensForTokens_Test is uint256 initialBalance = weth.balanceOf(address(this)); vm.expectRevert(); - lidoFixedPriceMultiLpARM.swapExactTokensForTokens( + lidoARM.swapExactTokensForTokens( weth, // inToken steth, // outToken initialBalance + 1, // amountIn @@ -92,7 +92,7 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_SwapExactTokensForTokens_Test is initialBalance = steth.balanceOf(address(this)); vm.expectRevert("BALANCE_EXCEEDED"); // Lido error - lidoFixedPriceMultiLpARM.swapExactTokensForTokens( + lidoARM.swapExactTokensForTokens( steth, // inToken weth, // outToken initialBalance + 3, // amountIn @@ -102,11 +102,11 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_SwapExactTokensForTokens_Test is } function test_RevertWhen_SwapExactTokensForTokens_Because_NotEnoughTokenOut() public { - uint256 initialBalance = steth.balanceOf(address(lidoFixedPriceMultiLpARM)); + uint256 initialBalance = steth.balanceOf(address(lidoARM)); deal(address(weth), address(this), initialBalance * 2); vm.expectRevert("BALANCE_EXCEEDED"); // Lido error - lidoFixedPriceMultiLpARM.swapExactTokensForTokens( + lidoARM.swapExactTokensForTokens( weth, // inToken steth, // outToken initialBalance * 2, // amountIn @@ -114,10 +114,10 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_SwapExactTokensForTokens_Test is address(this) // to ); - initialBalance = weth.balanceOf(address(lidoFixedPriceMultiLpARM)); + initialBalance = weth.balanceOf(address(lidoARM)); deal(address(steth), address(this), initialBalance * 2); vm.expectRevert("ARM: Insufficient liquidity"); - lidoFixedPriceMultiLpARM.swapExactTokensForTokens( + lidoARM.swapExactTokensForTokens( steth, // inToken weth, // outToken initialBalance * 2, // amountIn @@ -127,11 +127,11 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_SwapExactTokensForTokens_Test is } function test_RevertWhen_SwapExactTokensForTokens_Because_InsufficientOutputAmount() public { - deal(address(steth), address(lidoFixedPriceMultiLpARM), 100 wei); + deal(address(steth), address(lidoARM), 100 wei); // Test for this function signature: swapExactTokensForTokens(IERC20,IERC20,uint56,uint256,address) vm.expectRevert("ARM: Insufficient output amount"); - lidoFixedPriceMultiLpARM.swapExactTokensForTokens( + lidoARM.swapExactTokensForTokens( weth, // inToken steth, // outToken 1, // amountIn @@ -144,7 +144,7 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_SwapExactTokensForTokens_Test is path[0] = address(weth); path[1] = address(steth); vm.expectRevert("ARM: Insufficient output amount"); - lidoFixedPriceMultiLpARM.swapExactTokensForTokens( + lidoARM.swapExactTokensForTokens( 1, // amountIn 1_000 ether, // amountOutMin path, // path @@ -155,7 +155,7 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_SwapExactTokensForTokens_Test is function test_RevertWhen_SwapExactTokensForTokens_Because_InvalidePathLength() public { vm.expectRevert("ARM: Invalid path length"); - lidoFixedPriceMultiLpARM.swapExactTokensForTokens( + lidoARM.swapExactTokensForTokens( 1, // amountIn 1, // amountOutMin new address[](3), // path @@ -166,7 +166,7 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_SwapExactTokensForTokens_Test is function test_RevertWhen_SwapExactTokensForTokens_Because_DeadlineExpired() public { vm.expectRevert("ARM: Deadline expired"); - lidoFixedPriceMultiLpARM.swapExactTokensForTokens( + lidoARM.swapExactTokensForTokens( 1, // amountIn 1, // amountOutMin new address[](2), // path @@ -187,18 +187,18 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_SwapExactTokensForTokens_Test is // State before uint256 balanceWETHBeforeThis = weth.balanceOf(address(this)); uint256 balanceSTETHBeforeThis = steth.balanceOf(address(this)); - uint256 balanceWETHBeforeARM = weth.balanceOf(address(lidoFixedPriceMultiLpARM)); - uint256 balanceSTETHBeforeARM = steth.balanceOf(address(lidoFixedPriceMultiLpARM)); + uint256 balanceWETHBeforeARM = weth.balanceOf(address(lidoARM)); + uint256 balanceSTETHBeforeARM = steth.balanceOf(address(lidoARM)); // Get minimum amount of STETH to receive - uint256 traderates1 = lidoFixedPriceMultiLpARM.traderate1(); + uint256 traderates1 = lidoARM.traderate1(); uint256 minAmount = amountIn * traderates1 / 1e36; // Expected events: Already checked in fuzz tests uint256[] memory outputs = new uint256[](2); // Main call - outputs = lidoFixedPriceMultiLpARM.swapExactTokensForTokens( + outputs = lidoARM.swapExactTokensForTokens( amountIn, // amountIn minAmount, // amountOutMin path, // path @@ -209,8 +209,8 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_SwapExactTokensForTokens_Test is // State after uint256 balanceWETHAfterThis = weth.balanceOf(address(this)); uint256 balanceSTETHAfterThis = steth.balanceOf(address(this)); - uint256 balanceWETHAfterARM = weth.balanceOf(address(lidoFixedPriceMultiLpARM)); - uint256 balanceSTETHAfterARM = steth.balanceOf(address(lidoFixedPriceMultiLpARM)); + uint256 balanceWETHAfterARM = weth.balanceOf(address(lidoARM)); + uint256 balanceSTETHAfterARM = steth.balanceOf(address(lidoARM)); // Assertions assertEq(balanceWETHBeforeThis, balanceWETHAfterThis + amountIn); @@ -230,18 +230,18 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_SwapExactTokensForTokens_Test is // State before uint256 balanceWETHBeforeThis = weth.balanceOf(address(this)); uint256 balanceSTETHBeforeThis = steth.balanceOf(address(this)); - uint256 balanceWETHBeforeARM = weth.balanceOf(address(lidoFixedPriceMultiLpARM)); - uint256 balanceSTETHBeforeARM = steth.balanceOf(address(lidoFixedPriceMultiLpARM)); + uint256 balanceWETHBeforeARM = weth.balanceOf(address(lidoARM)); + uint256 balanceSTETHBeforeARM = steth.balanceOf(address(lidoARM)); // Get minimum amount of WETH to receive - uint256 traderates0 = lidoFixedPriceMultiLpARM.traderate0(); + uint256 traderates0 = lidoARM.traderate0(); uint256 minAmount = amountIn * traderates0 / 1e36; // Expected events: Already checked in fuzz tests uint256[] memory outputs = new uint256[](2); // Main call - outputs = lidoFixedPriceMultiLpARM.swapExactTokensForTokens( + outputs = lidoARM.swapExactTokensForTokens( amountIn, // amountIn minAmount, // amountOutMin path, // path @@ -252,8 +252,8 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_SwapExactTokensForTokens_Test is // State after uint256 balanceWETHAfterThis = weth.balanceOf(address(this)); uint256 balanceSTETHAfterThis = steth.balanceOf(address(this)); - uint256 balanceWETHAfterARM = weth.balanceOf(address(lidoFixedPriceMultiLpARM)); - uint256 balanceSTETHAfterARM = steth.balanceOf(address(lidoFixedPriceMultiLpARM)); + uint256 balanceWETHAfterARM = weth.balanceOf(address(lidoARM)); + uint256 balanceSTETHAfterARM = steth.balanceOf(address(lidoARM)); // Assertions assertEq(balanceWETHBeforeThis + minAmount, balanceWETHAfterThis); @@ -277,11 +277,11 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_SwapExactTokensForTokens_Test is // Use random price between 0.98 and 1 for traderate1, // Traderate0 value doesn't matter as it is not used in this test. price = _bound(price, MIN_PRICE0, MAX_PRICE0); - lidoFixedPriceMultiLpARM.setPrices(price, MAX_PRICE1); + lidoARM.setPrices(price, MAX_PRICE1); // Set random amount of stETH in the ARM stethReserve = _bound(stethReserve, 0, MAX_STETH_RESERVE); - deal(address(steth), address(lidoFixedPriceMultiLpARM), stethReserve); + deal(address(steth), address(lidoARM), stethReserve); // Calculate maximum amount of WETH to swap // It is ok to take 100% of the balance of stETH of the ARM as the price is below 1. @@ -291,20 +291,20 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_SwapExactTokensForTokens_Test is // State before uint256 balanceWETHBeforeThis = weth.balanceOf(address(this)); uint256 balanceSTETHBeforeThis = steth.balanceOf(address(this)); - uint256 balanceWETHBeforeARM = weth.balanceOf(address(lidoFixedPriceMultiLpARM)); - uint256 balanceSTETHBeforeARM = steth.balanceOf(address(lidoFixedPriceMultiLpARM)); + uint256 balanceWETHBeforeARM = weth.balanceOf(address(lidoARM)); + uint256 balanceSTETHBeforeARM = steth.balanceOf(address(lidoARM)); // Get minimum amount of STETH to receive - uint256 traderates1 = lidoFixedPriceMultiLpARM.traderate1(); + uint256 traderates1 = lidoARM.traderate1(); uint256 minAmount = amountIn * traderates1 / 1e36; // Expected events vm.expectEmit({emitter: address(weth)}); - emit IERC20.Transfer(address(this), address(lidoFixedPriceMultiLpARM), amountIn); + emit IERC20.Transfer(address(this), address(lidoARM), amountIn); vm.expectEmit({emitter: address(steth)}); - emit IERC20.Transfer(address(lidoFixedPriceMultiLpARM), address(this), minAmount + STETH_ERROR_ROUNDING); + emit IERC20.Transfer(address(lidoARM), address(this), minAmount + STETH_ERROR_ROUNDING); // Main call - lidoFixedPriceMultiLpARM.swapExactTokensForTokens( + lidoARM.swapExactTokensForTokens( weth, // inToken steth, // outToken amountIn, // amountIn @@ -315,8 +315,8 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_SwapExactTokensForTokens_Test is // State after uint256 balanceWETHAfterThis = weth.balanceOf(address(this)); uint256 balanceSTETHAfterThis = steth.balanceOf(address(this)); - uint256 balanceWETHAfterARM = weth.balanceOf(address(lidoFixedPriceMultiLpARM)); - uint256 balanceSTETHAfterARM = steth.balanceOf(address(lidoFixedPriceMultiLpARM)); + uint256 balanceWETHAfterARM = weth.balanceOf(address(lidoARM)); + uint256 balanceSTETHAfterARM = steth.balanceOf(address(lidoARM)); // Assertions assertEq(balanceWETHBeforeThis, balanceWETHAfterThis + amountIn); @@ -333,11 +333,11 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_SwapExactTokensForTokens_Test is // Use random price between MIN_PRICE1 and MAX_PRICE1 for traderate1, // Traderate0 value doesn't matter as it is not used in this test. price = _bound(price, MIN_PRICE1, MAX_PRICE1); - lidoFixedPriceMultiLpARM.setPrices(MIN_PRICE0, price); + lidoARM.setPrices(MIN_PRICE0, price); // Set random amount of WETH in the ARM wethReserve = _bound(wethReserve, 0, MAX_WETH_RESERVE); - deal(address(weth), address(lidoFixedPriceMultiLpARM), wethReserve); + deal(address(weth), address(lidoARM), wethReserve); // Calculate maximum amount of stETH to swap // As the price is below 1, we can take 100% of the balance of WETH of the ARM. @@ -347,21 +347,21 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_SwapExactTokensForTokens_Test is // State before uint256 balanceWETHBeforeThis = weth.balanceOf(address(this)); uint256 balanceSTETHBeforeThis = steth.balanceOf(address(this)); - uint256 balanceWETHBeforeARM = weth.balanceOf(address(lidoFixedPriceMultiLpARM)); - uint256 balanceSTETHBeforeARM = steth.balanceOf(address(lidoFixedPriceMultiLpARM)); + uint256 balanceWETHBeforeARM = weth.balanceOf(address(lidoARM)); + uint256 balanceSTETHBeforeARM = steth.balanceOf(address(lidoARM)); // Get minimum amount of WETH to receive - uint256 traderates0 = lidoFixedPriceMultiLpARM.traderate0(); + uint256 traderates0 = lidoARM.traderate0(); uint256 minAmount = amountIn * traderates0 / 1e36; // Expected events vm.expectEmit({emitter: address(steth)}); - emit IERC20.Transfer(address(this), address(lidoFixedPriceMultiLpARM), amountIn); + emit IERC20.Transfer(address(this), address(lidoARM), amountIn); vm.expectEmit({emitter: address(weth)}); - emit IERC20.Transfer(address(lidoFixedPriceMultiLpARM), address(this), minAmount); + emit IERC20.Transfer(address(lidoARM), address(this), minAmount); // Main call - lidoFixedPriceMultiLpARM.swapExactTokensForTokens( + lidoARM.swapExactTokensForTokens( steth, // inToken weth, // outToken amountIn, // amountIn @@ -372,8 +372,8 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_SwapExactTokensForTokens_Test is // State after uint256 balanceWETHAfterThis = weth.balanceOf(address(this)); uint256 balanceSTETHAfterThis = steth.balanceOf(address(this)); - uint256 balanceWETHAfterARM = weth.balanceOf(address(lidoFixedPriceMultiLpARM)); - uint256 balanceSTETHAfterARM = steth.balanceOf(address(lidoFixedPriceMultiLpARM)); + uint256 balanceWETHAfterARM = weth.balanceOf(address(lidoARM)); + uint256 balanceSTETHAfterARM = steth.balanceOf(address(lidoARM)); // Assertions assertEq(balanceWETHBeforeThis + minAmount, balanceWETHAfterThis); diff --git a/test/fork/LidoFixedPriceMultiLpARM/SwapTokensForExactTokens.t.sol b/test/fork/LidoFixedPriceMultiLpARM/SwapTokensForExactTokens.t.sol index ee59dc0..28eb17f 100644 --- a/test/fork/LidoFixedPriceMultiLpARM/SwapTokensForExactTokens.t.sol +++ b/test/fork/LidoFixedPriceMultiLpARM/SwapTokensForExactTokens.t.sol @@ -6,7 +6,7 @@ import {Fork_Shared_Test_} from "test/fork/shared/Shared.sol"; import {IERC20} from "contracts/Interfaces.sol"; -contract Fork_Concrete_LidoFixedPriceMultiLpARM_SwapTokensForExactTokens_Test is Fork_Shared_Test_ { +contract Fork_Concrete_LidoARM_SwapTokensForExactTokens_Test is Fork_Shared_Test_ { ////////////////////////////////////////////////////// /// --- CONSTANTS ////////////////////////////////////////////////////// @@ -26,17 +26,17 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_SwapTokensForExactTokens_Test is deal(address(weth), address(this), 1_000 ether); deal(address(steth), address(this), 1_000 ether); - deal(address(weth), address(lidoFixedPriceMultiLpARM), 1_000 ether); - deal(address(steth), address(lidoFixedPriceMultiLpARM), 1_000 ether); + deal(address(weth), address(lidoARM), 1_000 ether); + deal(address(steth), address(lidoARM), 1_000 ether); } ////////////////////////////////////////////////////// /// --- REVERTING TESTS ////////////////////////////////////////////////////// function test_RevertWhen_SwapTokensForExactTokens_Because_InvalidTokenOut1() public { - lidoFixedPriceMultiLpARM.token0(); + lidoARM.token0(); vm.expectRevert("ARM: Invalid out token"); - lidoFixedPriceMultiLpARM.swapTokensForExactTokens( + lidoARM.swapTokensForExactTokens( steth, // inToken badToken, // outToken 1, // amountOut @@ -47,7 +47,7 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_SwapTokensForExactTokens_Test is function test_RevertWhen_SwapTokensForExactTokens_Because_InvalidTokenOut0() public { vm.expectRevert("ARM: Invalid out token"); - lidoFixedPriceMultiLpARM.swapTokensForExactTokens( + lidoARM.swapTokensForExactTokens( weth, // inToken badToken, // outToken 1, // amountOut @@ -58,7 +58,7 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_SwapTokensForExactTokens_Test is function test_RevertWhen_SwapTokensForExactTokens_Because_InvalidTokenIn() public { vm.expectRevert("ARM: Invalid in token"); - lidoFixedPriceMultiLpARM.swapTokensForExactTokens( + lidoARM.swapTokensForExactTokens( badToken, // inToken steth, // outToken 1, // amountOut @@ -69,7 +69,7 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_SwapTokensForExactTokens_Test is function test_RevertWhen_SwapTokensForExactTokens_Because_BothInvalidTokens() public { vm.expectRevert("ARM: Invalid in token"); - lidoFixedPriceMultiLpARM.swapTokensForExactTokens( + lidoARM.swapTokensForExactTokens( badToken, // inToken badToken, // outToken 1, // amountOut @@ -82,7 +82,7 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_SwapTokensForExactTokens_Test is deal(address(weth), address(this), 0); vm.expectRevert(); - lidoFixedPriceMultiLpARM.swapTokensForExactTokens( + lidoARM.swapTokensForExactTokens( weth, // inToken steth, // outToken 1, // amountOut @@ -92,7 +92,7 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_SwapTokensForExactTokens_Test is deal(address(steth), address(this), 0); vm.expectRevert(); - lidoFixedPriceMultiLpARM.swapTokensForExactTokens( + lidoARM.swapTokensForExactTokens( steth, // inToken weth, // outToken STETH_ERROR_ROUNDING + 1, // amountOut * @@ -106,7 +106,7 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_SwapTokensForExactTokens_Test is deal(address(weth), address(this), 0); vm.expectRevert(); - lidoFixedPriceMultiLpARM.swapTokensForExactTokens( + lidoARM.swapTokensForExactTokens( weth, // inToken steth, // outToken 1 ether, // amountOut @@ -116,7 +116,7 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_SwapTokensForExactTokens_Test is deal(address(steth), address(this), 0); vm.expectRevert("BALANCE_EXCEEDED"); // Lido error - lidoFixedPriceMultiLpARM.swapTokensForExactTokens( + lidoARM.swapTokensForExactTokens( steth, // inToken weth, // outToken 1 ether, // amountOut @@ -126,11 +126,11 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_SwapTokensForExactTokens_Test is } function test_RevertWhen_SwapTokensForExactTokens_Because_InsufficientOutputAmount() public { - deal(address(steth), address(lidoFixedPriceMultiLpARM), 100 wei); + deal(address(steth), address(lidoARM), 100 wei); // Test for this function signature: swapTokensForExactTokens(IERC20,IERC20,uint56,uint256,address) vm.expectRevert("ARM: Excess input amount"); - lidoFixedPriceMultiLpARM.swapTokensForExactTokens( + lidoARM.swapTokensForExactTokens( weth, // inToken steth, // outToken 1, // amountOut @@ -143,7 +143,7 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_SwapTokensForExactTokens_Test is path[0] = address(weth); path[1] = address(steth); vm.expectRevert("ARM: Excess input amount"); - lidoFixedPriceMultiLpARM.swapTokensForExactTokens( + lidoARM.swapTokensForExactTokens( 1, // amountOut 0, // amountInMax path, // path @@ -154,7 +154,7 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_SwapTokensForExactTokens_Test is function test_RevertWhen_SwapTokensForExactTokens_Because_InvalidePathLength() public { vm.expectRevert("ARM: Invalid path length"); - lidoFixedPriceMultiLpARM.swapTokensForExactTokens( + lidoARM.swapTokensForExactTokens( 1, // amountOut 1, // amountInMax new address[](3), // path @@ -165,7 +165,7 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_SwapTokensForExactTokens_Test is function test_RevertWhen_SwapTokensForExactTokens_Because_DeadlineExpired() public { vm.expectRevert("ARM: Deadline expired"); - lidoFixedPriceMultiLpARM.swapTokensForExactTokens( + lidoARM.swapTokensForExactTokens( 1, // amountOut 1, // amountInMax new address[](2), // path @@ -186,18 +186,18 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_SwapTokensForExactTokens_Test is // State before uint256 balanceWETHBeforeThis = weth.balanceOf(address(this)); uint256 balanceSTETHBeforeThis = steth.balanceOf(address(this)); - uint256 balanceWETHBeforeARM = weth.balanceOf(address(lidoFixedPriceMultiLpARM)); - uint256 balanceSTETHBeforeARM = steth.balanceOf(address(lidoFixedPriceMultiLpARM)); + uint256 balanceWETHBeforeARM = weth.balanceOf(address(lidoARM)); + uint256 balanceSTETHBeforeARM = steth.balanceOf(address(lidoARM)); // Get minimum amount of STETH to receive - uint256 traderates1 = lidoFixedPriceMultiLpARM.traderate1(); + uint256 traderates1 = lidoARM.traderate1(); uint256 amountIn = (amountOut * 1e36 / traderates1) + 1; // Expected events: Already checked in fuzz tests uint256[] memory outputs = new uint256[](2); // Main call - outputs = lidoFixedPriceMultiLpARM.swapTokensForExactTokens( + outputs = lidoARM.swapTokensForExactTokens( amountOut, // amountOut amountIn, // amountInMax path, // path @@ -208,8 +208,8 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_SwapTokensForExactTokens_Test is // State after uint256 balanceWETHAfterThis = weth.balanceOf(address(this)); uint256 balanceSTETHAfterThis = steth.balanceOf(address(this)); - uint256 balanceWETHAfterARM = weth.balanceOf(address(lidoFixedPriceMultiLpARM)); - uint256 balanceSTETHAfterARM = steth.balanceOf(address(lidoFixedPriceMultiLpARM)); + uint256 balanceWETHAfterARM = weth.balanceOf(address(lidoARM)); + uint256 balanceSTETHAfterARM = steth.balanceOf(address(lidoARM)); // Assertions assertEq(balanceWETHBeforeThis, balanceWETHAfterThis + amountIn); @@ -229,18 +229,18 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_SwapTokensForExactTokens_Test is // State before uint256 balanceWETHBeforeThis = weth.balanceOf(address(this)); uint256 balanceSTETHBeforeThis = steth.balanceOf(address(this)); - uint256 balanceWETHBeforeARM = weth.balanceOf(address(lidoFixedPriceMultiLpARM)); - uint256 balanceSTETHBeforeARM = steth.balanceOf(address(lidoFixedPriceMultiLpARM)); + uint256 balanceWETHBeforeARM = weth.balanceOf(address(lidoARM)); + uint256 balanceSTETHBeforeARM = steth.balanceOf(address(lidoARM)); // Get minimum amount of WETH to receive - uint256 traderates0 = lidoFixedPriceMultiLpARM.traderate0(); + uint256 traderates0 = lidoARM.traderate0(); uint256 amountIn = (amountOut * 1e36 / traderates0) + 1; // Expected events: Already checked in fuzz tests uint256[] memory outputs = new uint256[](2); // Main call - outputs = lidoFixedPriceMultiLpARM.swapTokensForExactTokens( + outputs = lidoARM.swapTokensForExactTokens( amountOut, // amountOut amountIn, // amountInMax path, // path @@ -251,8 +251,8 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_SwapTokensForExactTokens_Test is // State after uint256 balanceWETHAfterThis = weth.balanceOf(address(this)); uint256 balanceSTETHAfterThis = steth.balanceOf(address(this)); - uint256 balanceWETHAfterARM = weth.balanceOf(address(lidoFixedPriceMultiLpARM)); - uint256 balanceSTETHAfterARM = steth.balanceOf(address(lidoFixedPriceMultiLpARM)); + uint256 balanceWETHAfterARM = weth.balanceOf(address(lidoARM)); + uint256 balanceSTETHAfterARM = steth.balanceOf(address(lidoARM)); // Assertions assertEq(balanceWETHBeforeThis + amountOut, balanceWETHAfterThis); @@ -276,11 +276,11 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_SwapTokensForExactTokens_Test is // Use random price between 0.98 and 1 for traderate1, // Traderate0 value doesn't matter as it is not used in this test. price = _bound(price, MIN_PRICE0, MAX_PRICE0); - lidoFixedPriceMultiLpARM.setPrices(price, MAX_PRICE1); + lidoARM.setPrices(price, MAX_PRICE1); // Set random amount of stETH in the ARM stethReserve = _bound(stethReserve, 0, MAX_STETH_RESERVE); - deal(address(steth), address(lidoFixedPriceMultiLpARM), stethReserve); + deal(address(steth), address(lidoARM), stethReserve); // Calculate maximum amount of WETH to swap // It is ok to take 100% of the balance of stETH of the ARM as the price is below 1. @@ -290,20 +290,20 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_SwapTokensForExactTokens_Test is // State before uint256 balanceWETHBeforeThis = weth.balanceOf(address(this)); uint256 balanceSTETHBeforeThis = steth.balanceOf(address(this)); - uint256 balanceWETHBeforeARM = weth.balanceOf(address(lidoFixedPriceMultiLpARM)); - uint256 balanceSTETHBeforeARM = steth.balanceOf(address(lidoFixedPriceMultiLpARM)); + uint256 balanceWETHBeforeARM = weth.balanceOf(address(lidoARM)); + uint256 balanceSTETHBeforeARM = steth.balanceOf(address(lidoARM)); // Get minimum amount of STETH to receive - uint256 traderates1 = lidoFixedPriceMultiLpARM.traderate1(); + uint256 traderates1 = lidoARM.traderate1(); uint256 amountIn = (amountOut * 1e36 / traderates1) + 1; // Expected events vm.expectEmit({emitter: address(weth)}); - emit IERC20.Transfer(address(this), address(lidoFixedPriceMultiLpARM), amountIn); + emit IERC20.Transfer(address(this), address(lidoARM), amountIn); vm.expectEmit({emitter: address(steth)}); - emit IERC20.Transfer(address(lidoFixedPriceMultiLpARM), address(this), amountOut + STETH_ERROR_ROUNDING); + emit IERC20.Transfer(address(lidoARM), address(this), amountOut + STETH_ERROR_ROUNDING); // Main call - lidoFixedPriceMultiLpARM.swapTokensForExactTokens( + lidoARM.swapTokensForExactTokens( weth, // inToken steth, // outToken amountOut, // amountOut @@ -314,8 +314,8 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_SwapTokensForExactTokens_Test is // State after uint256 balanceWETHAfterThis = weth.balanceOf(address(this)); uint256 balanceSTETHAfterThis = steth.balanceOf(address(this)); - uint256 balanceWETHAfterARM = weth.balanceOf(address(lidoFixedPriceMultiLpARM)); - uint256 balanceSTETHAfterARM = steth.balanceOf(address(lidoFixedPriceMultiLpARM)); + uint256 balanceWETHAfterARM = weth.balanceOf(address(lidoARM)); + uint256 balanceSTETHAfterARM = steth.balanceOf(address(lidoARM)); // Assertions assertEq(balanceWETHBeforeThis, balanceWETHAfterThis + amountIn); @@ -334,11 +334,11 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_SwapTokensForExactTokens_Test is // Use random price between MIN_PRICE1 and MAX_PRICE1 for traderate1, // Traderate0 value doesn't matter as it is not used in this test. price = _bound(price, MIN_PRICE1, MAX_PRICE1); - lidoFixedPriceMultiLpARM.setPrices(MIN_PRICE0, price); + lidoARM.setPrices(MIN_PRICE0, price); // Set random amount of WETH in the ARM wethReserve = _bound(wethReserve, 0, MAX_WETH_RESERVE); - deal(address(weth), address(lidoFixedPriceMultiLpARM), wethReserve); + deal(address(weth), address(lidoARM), wethReserve); // Calculate maximum amount of stETH to swap // As the price is below 1, we can take 100% of the balance of WETH of the ARM. @@ -348,21 +348,21 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_SwapTokensForExactTokens_Test is // State before uint256 balanceWETHBeforeThis = weth.balanceOf(address(this)); uint256 balanceSTETHBeforeThis = steth.balanceOf(address(this)); - uint256 balanceWETHBeforeARM = weth.balanceOf(address(lidoFixedPriceMultiLpARM)); - uint256 balanceSTETHBeforeARM = steth.balanceOf(address(lidoFixedPriceMultiLpARM)); + uint256 balanceWETHBeforeARM = weth.balanceOf(address(lidoARM)); + uint256 balanceSTETHBeforeARM = steth.balanceOf(address(lidoARM)); // Get minimum amount of WETH to receive - uint256 traderates0 = lidoFixedPriceMultiLpARM.traderate0(); + uint256 traderates0 = lidoARM.traderate0(); uint256 amountIn = (amountOut * 1e36 / traderates0) + 1; // Expected events vm.expectEmit({emitter: address(steth)}); - emit IERC20.Transfer(address(this), address(lidoFixedPriceMultiLpARM), amountIn); + emit IERC20.Transfer(address(this), address(lidoARM), amountIn); vm.expectEmit({emitter: address(weth)}); - emit IERC20.Transfer(address(lidoFixedPriceMultiLpARM), address(this), amountOut); + emit IERC20.Transfer(address(lidoARM), address(this), amountOut); // Main call - lidoFixedPriceMultiLpARM.swapTokensForExactTokens( + lidoARM.swapTokensForExactTokens( steth, // inToken weth, // outToken amountOut, // amountOut @@ -373,8 +373,8 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_SwapTokensForExactTokens_Test is // State after uint256 balanceWETHAfterThis = weth.balanceOf(address(this)); uint256 balanceSTETHAfterThis = steth.balanceOf(address(this)); - uint256 balanceWETHAfterARM = weth.balanceOf(address(lidoFixedPriceMultiLpARM)); - uint256 balanceSTETHAfterARM = steth.balanceOf(address(lidoFixedPriceMultiLpARM)); + uint256 balanceWETHAfterARM = weth.balanceOf(address(lidoARM)); + uint256 balanceSTETHAfterARM = steth.balanceOf(address(lidoARM)); // Assertions assertEq(balanceWETHBeforeThis + amountOut, balanceWETHAfterThis); diff --git a/test/fork/LidoFixedPriceMultiLpARM/TotalAssets.t.sol b/test/fork/LidoFixedPriceMultiLpARM/TotalAssets.t.sol index 16d9633..4599b4b 100644 --- a/test/fork/LidoFixedPriceMultiLpARM/TotalAssets.t.sol +++ b/test/fork/LidoFixedPriceMultiLpARM/TotalAssets.t.sol @@ -9,10 +9,9 @@ import {Fork_Shared_Test_} from "test/fork/shared/Shared.sol"; // Contracts import {IERC20} from "contracts/Interfaces.sol"; -import {MultiLP} from "contracts/MultiLP.sol"; -import {PerformanceFee} from "contracts/PerformanceFee.sol"; +import {AbstractARM} from "contracts/AbstractARM.sol"; -contract Fork_Concrete_LidoFixedPriceMultiLpARM_TotalAssets_Test_ is Fork_Shared_Test_ { +contract Fork_Concrete_LidoARM_TotalAssets_Test_ is Fork_Shared_Test_ { ////////////////////////////////////////////////////// /// --- SETUP ////////////////////////////////////////////////////// @@ -26,162 +25,130 @@ contract Fork_Concrete_LidoFixedPriceMultiLpARM_TotalAssets_Test_ is Fork_Shared liquidityProviderController.setTotalAssetsCap(type(uint256).max); // Approve STETH for Lido - lidoFixedPriceMultiLpARM.approveStETH(); + lidoARM.approveStETH(); deal(address(weth), address(this), 1_000 ether); - weth.approve(address(lidoFixedPriceMultiLpARM), type(uint256).max); - } - - ////////////////////////////////////////////////////// - /// --- REVERTING TEST - ////////////////////////////////////////////////////// - function test_RevertWhen_TotalAssets_Because_MathError() - public - depositInLidoFixedPriceMultiLpARM(address(this), DEFAULT_AMOUNT) - simulateAssetGainInLidoFixedPriceMultiLpARM(DEFAULT_AMOUNT, address(weth), true) - requestRedeemFromLidoFixedPriceMultiLpARM(address(this), DEFAULT_AMOUNT) - simulateAssetGainInLidoFixedPriceMultiLpARM(DEFAULT_AMOUNT * 2, address(weth), false) - { - vm.expectRevert(stdError.arithmeticError); - lidoFixedPriceMultiLpARM.totalAssets(); + weth.approve(address(lidoARM), type(uint256).max); } ////////////////////////////////////////////////////// /// --- PASSING TEST ////////////////////////////////////////////////////// function test_TotalAssets_AfterInitialization() public view { - assertEq(lidoFixedPriceMultiLpARM.totalAssets(), MIN_TOTAL_SUPPLY); + assertEq(lidoARM.totalAssets(), MIN_TOTAL_SUPPLY); } - function test_TotalAssets_AfterDeposit_NoAssetGainOrLoss() - public - depositInLidoFixedPriceMultiLpARM(address(this), DEFAULT_AMOUNT) - { - assertEq(lidoFixedPriceMultiLpARM.totalAssets(), MIN_TOTAL_SUPPLY + DEFAULT_AMOUNT); + function test_TotalAssets_AfterDeposit_NoAssetGainOrLoss() public depositInLidoARM(address(this), DEFAULT_AMOUNT) { + assertEq(lidoARM.totalAssets(), MIN_TOTAL_SUPPLY + DEFAULT_AMOUNT); } function test_TotalAssets_AfterDeposit_WithAssetGain_InWETH() public - depositInLidoFixedPriceMultiLpARM(address(this), DEFAULT_AMOUNT) + depositInLidoARM(address(this), DEFAULT_AMOUNT) { // Simulate asset gain uint256 assetGain = DEFAULT_AMOUNT / 2; - deal( - address(weth), - address(lidoFixedPriceMultiLpARM), - weth.balanceOf(address(lidoFixedPriceMultiLpARM)) + assetGain - ); + deal(address(weth), address(lidoARM), weth.balanceOf(address(lidoARM)) + assetGain); // Calculate Fees uint256 fee = assetGain * 20 / 100; // 20% fee - assertEq(lidoFixedPriceMultiLpARM.totalAssets(), MIN_TOTAL_SUPPLY + DEFAULT_AMOUNT + assetGain - fee); + assertEq(lidoARM.totalAssets(), MIN_TOTAL_SUPPLY + DEFAULT_AMOUNT + assetGain - fee); } function test_TotalAssets_AfterDeposit_WithAssetGain_InSTETH() public - depositInLidoFixedPriceMultiLpARM(address(this), DEFAULT_AMOUNT) + depositInLidoARM(address(this), DEFAULT_AMOUNT) { - assertEq(steth.balanceOf(address(lidoFixedPriceMultiLpARM)), 0); + assertEq(steth.balanceOf(address(lidoARM)), 0); // Simulate asset gain uint256 assetGain = DEFAULT_AMOUNT / 2 + 1; // We are sure that steth balance is empty, so we can deal directly final amount. - deal(address(steth), address(lidoFixedPriceMultiLpARM), assetGain); + deal(address(steth), address(lidoARM), assetGain); // Calculate Fees uint256 fee = assetGain * 20 / 100; // 20% fee assertApproxEqAbs( - lidoFixedPriceMultiLpARM.totalAssets(), - MIN_TOTAL_SUPPLY + DEFAULT_AMOUNT + assetGain - fee, - STETH_ERROR_ROUNDING + lidoARM.totalAssets(), MIN_TOTAL_SUPPLY + DEFAULT_AMOUNT + assetGain - fee, STETH_ERROR_ROUNDING ); } function test_TotalAssets_AfterDeposit_WithAssetLoss_InWETH() public - depositInLidoFixedPriceMultiLpARM(address(this), DEFAULT_AMOUNT) + depositInLidoARM(address(this), DEFAULT_AMOUNT) { // Simulate asset loss uint256 assetLoss = DEFAULT_AMOUNT / 2; - deal( - address(weth), - address(lidoFixedPriceMultiLpARM), - weth.balanceOf(address(lidoFixedPriceMultiLpARM)) - assetLoss - ); + deal(address(weth), address(lidoARM), weth.balanceOf(address(lidoARM)) - assetLoss); - assertEq(lidoFixedPriceMultiLpARM.totalAssets(), MIN_TOTAL_SUPPLY + DEFAULT_AMOUNT - assetLoss); + assertEq(lidoARM.totalAssets(), MIN_TOTAL_SUPPLY + DEFAULT_AMOUNT - assetLoss); } function test_TotalAssets_AfterDeposit_WithAssetLoss_InSTETH() public - depositInLidoFixedPriceMultiLpARM(address(this), DEFAULT_AMOUNT) + depositInLidoARM(address(this), DEFAULT_AMOUNT) { // Simulate Swap at 1:1 between WETH and stETH uint256 swapAmount = DEFAULT_AMOUNT / 2; - deal( - address(weth), - address(lidoFixedPriceMultiLpARM), - weth.balanceOf(address(lidoFixedPriceMultiLpARM)) - swapAmount - ); + deal(address(weth), address(lidoARM), weth.balanceOf(address(lidoARM)) - swapAmount); // Then simulate a loss on stETH, do all in the same deal uint256 assetLoss = swapAmount / 2; - deal(address(steth), address(lidoFixedPriceMultiLpARM), swapAmount / 2); + deal(address(steth), address(lidoARM), swapAmount / 2); - assertApproxEqAbs( - lidoFixedPriceMultiLpARM.totalAssets(), MIN_TOTAL_SUPPLY + DEFAULT_AMOUNT - assetLoss, STETH_ERROR_ROUNDING - ); + assertApproxEqAbs(lidoARM.totalAssets(), MIN_TOTAL_SUPPLY + DEFAULT_AMOUNT - assetLoss, STETH_ERROR_ROUNDING); } function test_TotalAssets_After_WithdrawingFromLido() public { // Simulate a Swap at 1:1 between WETH and stETH using initial liquidity uint256 swapAmount = MIN_TOTAL_SUPPLY / 2; - deal( - address(weth), - address(lidoFixedPriceMultiLpARM), - weth.balanceOf(address(lidoFixedPriceMultiLpARM)) - swapAmount - ); - deal(address(steth), address(lidoFixedPriceMultiLpARM), swapAmount); // Empty stETH balance, so we can deal directly + deal(address(weth), address(lidoARM), weth.balanceOf(address(lidoARM)) - swapAmount); + deal(address(steth), address(lidoARM), swapAmount); // Empty stETH balance, so we can deal directly - uint256 totalAssetsBefore = lidoFixedPriceMultiLpARM.totalAssets(); + uint256 totalAssetsBefore = lidoARM.totalAssets(); // Request a redeem on Lido uint256[] memory amounts = new uint256[](1); amounts[0] = swapAmount; - lidoFixedPriceMultiLpARM.requestStETHWithdrawalForETH(amounts); + lidoARM.requestStETHWithdrawalForETH(amounts); // Check total assets after withdrawal is the same as before - assertApproxEqAbs(lidoFixedPriceMultiLpARM.totalAssets(), totalAssetsBefore, STETH_ERROR_ROUNDING); + assertApproxEqAbs(lidoARM.totalAssets(), totalAssetsBefore, STETH_ERROR_ROUNDING); } function test_TotalAssets_With_FeeAccrued_NotNull() public { uint256 assetGain = DEFAULT_AMOUNT; // Simulate asset gain - deal( - address(weth), - address(lidoFixedPriceMultiLpARM), - weth.balanceOf(address(lidoFixedPriceMultiLpARM)) + assetGain - ); + deal(address(weth), address(lidoARM), weth.balanceOf(address(lidoARM)) + assetGain); // User deposit, this will trigger a fee calculation - lidoFixedPriceMultiLpARM.deposit(DEFAULT_AMOUNT); + lidoARM.deposit(DEFAULT_AMOUNT); // Assert fee accrued is not null - assertEq(lidoFixedPriceMultiLpARM.feesAccrued(), assetGain * 20 / 100); + assertEq(lidoARM.feesAccrued(), assetGain * 20 / 100); - assertEq( - lidoFixedPriceMultiLpARM.totalAssets(), MIN_TOTAL_SUPPLY + DEFAULT_AMOUNT + assetGain - assetGain * 20 / 100 - ); + assertEq(lidoARM.totalAssets(), MIN_TOTAL_SUPPLY + DEFAULT_AMOUNT + assetGain - assetGain * 20 / 100); } function test_TotalAssets_When_ARMIsInsolvent() public - depositInLidoFixedPriceMultiLpARM(address(this), DEFAULT_AMOUNT) - requestRedeemFromLidoFixedPriceMultiLpARM(address(this), DEFAULT_AMOUNT) + depositInLidoARM(address(this), DEFAULT_AMOUNT) + requestRedeemFromLidoARM(address(this), DEFAULT_AMOUNT) { // Simulate a loss of assets - deal(address(weth), address(lidoFixedPriceMultiLpARM), DEFAULT_AMOUNT - 1); + deal(address(weth), address(lidoARM), DEFAULT_AMOUNT - 1); - assertEq(lidoFixedPriceMultiLpARM.totalAssets(), 0); + assertEq(lidoARM.totalAssets(), 0); + } + + function test_RevertWhen_TotalAssets_Because_MathError() + public + depositInLidoARM(address(this), DEFAULT_AMOUNT) + simulateAssetGainInLidoARM(DEFAULT_AMOUNT, address(weth), true) + requestRedeemFromLidoARM(address(this), DEFAULT_AMOUNT) + simulateAssetGainInLidoARM(DEFAULT_AMOUNT * 2, address(weth), false) + { + // vm.expectRevert(stdError.arithmeticError); + assertEq(lidoARM.totalAssets(), 0); } } diff --git a/test/fork/shared/Shared.sol b/test/fork/shared/Shared.sol index 99824b3..dda8910 100644 --- a/test/fork/shared/Shared.sol +++ b/test/fork/shared/Shared.sol @@ -10,7 +10,7 @@ import {Modifiers} from "test/fork/utils/Modifiers.sol"; // Contracts import {Proxy} from "contracts/Proxy.sol"; import {OethARM} from "contracts/OethARM.sol"; -import {LidoFixedPriceMultiLpARM} from "contracts/LidoFixedPriceMultiLpARM.sol"; +import {LidoARM} from "contracts/LidoARM.sol"; import {LiquidityProviderController} from "contracts/LiquidityProviderController.sol"; // Interfaces @@ -139,17 +139,16 @@ abstract contract Fork_Shared_Test_ is Modifiers { liquidityProviderController.setLiquidityProviderCaps(liquidityProviders, 20 ether); liquidityProviderController.setTotalAssetsCap(100 ether); - // --- Deploy LidoFixedPriceMultiLpARM implementation --- + // --- Deploy LidoARM implementation --- // Deploy LidoARM implementation. - LidoFixedPriceMultiLpARM lidoImpl = - new LidoFixedPriceMultiLpARM(address(steth), address(weth), Mainnet.LIDO_WITHDRAWAL); + LidoARM lidoImpl = new LidoARM(address(steth), address(weth), Mainnet.LIDO_WITHDRAWAL); // Deployer will need WETH to initialize the ARM. deal(address(weth), address(this), 1e12); weth.approve(address(lidoProxy), type(uint256).max); steth.approve(address(lidoProxy), type(uint256).max); - // Initialize Proxy with LidoFixedPriceMultiLpARM implementation. + // Initialize Proxy with LidoARM implementation. data = abi.encodeWithSignature( "initialize(string,string,address,uint256,address,address)", "Lido ARM", @@ -162,10 +161,10 @@ abstract contract Fork_Shared_Test_ is Modifiers { lidoProxy.initialize(address(lidoImpl), address(this), data); // Set the Proxy as the LidoARM. - lidoFixedPriceMultiLpARM = LidoFixedPriceMultiLpARM(payable(address(lidoProxy))); + lidoARM = LidoARM(payable(address(lidoProxy))); // set prices - lidoFixedPriceMultiLpARM.setPrices(992 * 1e33, 1001 * 1e33); + lidoARM.setPrices(992 * 1e33, 1001 * 1e33); } function _label() internal { @@ -176,7 +175,7 @@ abstract contract Fork_Shared_Test_ is Modifiers { vm.label(address(vault), "OETH VAULT"); vm.label(address(oethARM), "OETH ARM"); vm.label(address(proxy), "OETH ARM PROXY"); - vm.label(address(lidoFixedPriceMultiLpARM), "LIDO ARM"); + vm.label(address(lidoARM), "LIDO ARM"); vm.label(address(lidoProxy), "LIDO ARM PROXY"); vm.label(address(liquidityProviderController), "LIQUIDITY PROVIDER CONTROLLER"); vm.label(operator, "OPERATOR"); diff --git a/test/fork/utils/Helpers.sol b/test/fork/utils/Helpers.sol index b4bfc70..9223da7 100644 --- a/test/fork/utils/Helpers.sol +++ b/test/fork/utils/Helpers.sol @@ -40,10 +40,10 @@ abstract contract Helpers is Base_Test_ { uint256 expectedClaimed, uint256 expectedNextIndex ) public view { - assertEq(lidoFixedPriceMultiLpARM.withdrawsQueued(), expectedQueued, "metadata queued"); - assertEq(lidoFixedPriceMultiLpARM.withdrawsClaimable(), expectedClaimable, "metadata claimable"); - assertEq(lidoFixedPriceMultiLpARM.withdrawsClaimed(), expectedClaimed, "metadata claimed"); - assertEq(lidoFixedPriceMultiLpARM.nextWithdrawalIndex(), expectedNextIndex, "metadata nextWithdrawalIndex"); + assertEq(lidoARM.withdrawsQueued(), expectedQueued, "metadata queued"); + assertEq(lidoARM.withdrawsClaimable(), expectedClaimable, "metadata claimable"); + assertEq(lidoARM.withdrawsClaimed(), expectedClaimed, "metadata claimed"); + assertEq(lidoARM.nextWithdrawalIndex(), expectedNextIndex, "metadata nextWithdrawalIndex"); } /// @notice Asserts the equality bewteen value of `withdrawalRequests()` and the expected values. @@ -56,7 +56,7 @@ abstract contract Helpers is Base_Test_ { uint256 queued ) public view { (address _withdrawer, bool _claimed, uint40 _claimTimestamp, uint128 _assets, uint128 _queued) = - lidoFixedPriceMultiLpARM.withdrawalRequests(requestId); + lidoARM.withdrawalRequests(requestId); assertEq(_withdrawer, withdrawer, "Wrong withdrawer"); assertEq(_claimed, claimed, "Wrong claimed"); assertEq(_claimTimestamp, claimTimestamp, "Wrong claimTimestamp"); diff --git a/test/fork/utils/MockCall.sol b/test/fork/utils/MockCall.sol index e1d4f23..1a6de11 100644 --- a/test/fork/utils/MockCall.sol +++ b/test/fork/utils/MockCall.sol @@ -33,18 +33,18 @@ library MockCall { contract MockLidoWithdraw { ETHSender public immutable ethSender; - address public immutable lidoFixedPriceMultiLpARM; + address public immutable lidoARM; constructor(address _lidoFixedPriceMulltiLpARM) { ethSender = new ETHSender(); - lidoFixedPriceMultiLpARM = _lidoFixedPriceMulltiLpARM; + lidoARM = _lidoFixedPriceMulltiLpARM; } /// @notice Mock the call to the Lido contract's `claimWithdrawals` function. /// @dev as it is not possible to transfer ETH from the mocked contract (seems to be an issue with forge) /// we use the ETHSender contract intermediary to send the ETH to the target contract. function claimWithdrawals(uint256[] memory, uint256[] memory) external { - ethSender.sendETH(lidoFixedPriceMultiLpARM); + ethSender.sendETH(lidoARM); } } diff --git a/test/fork/utils/Modifiers.sol b/test/fork/utils/Modifiers.sol index f9c592c..23b69cd 100644 --- a/test/fork/utils/Modifiers.sol +++ b/test/fork/utils/Modifiers.sol @@ -37,14 +37,14 @@ abstract contract Modifiers is Helpers { /// @notice Impersonate the operator of LidoOwnerLpARM contract. modifier asLidoFixedPriceMulltiLpARMOperator() { - vm.startPrank(lidoFixedPriceMultiLpARM.operator()); + vm.startPrank(lidoARM.operator()); _; vm.stopPrank(); } - /// @notice Impersonate the owner of LidoFixedPriceMultiLpARM contract. - modifier asLidoFixedPriceMultiLpARMOwner() { - vm.startPrank(lidoFixedPriceMultiLpARM.owner()); + /// @notice Impersonate the owner of LidoARM contract. + modifier asLidoARMOwner() { + vm.startPrank(lidoARM.owner()); _; vm.stopPrank(); } @@ -77,8 +77,8 @@ abstract contract Modifiers is Helpers { _; } - /// @notice Deposit WETH into the LidoFixedPriceMultiLpARM contract. - modifier depositInLidoFixedPriceMultiLpARM(address user, uint256 amount) { + /// @notice Deposit WETH into the LidoARM contract. + modifier depositInLidoARM(address user, uint256 amount) { // Todo: extend this logic to other modifier if needed (VmSafe.CallerMode mode, address _address, address _origin) = vm.readCallers(); vm.stopPrank(); @@ -89,8 +89,8 @@ abstract contract Modifiers is Helpers { // Deal amount as "extra" to user deal(address(weth), user, amount + balance); vm.startPrank(user); - weth.approve(address(lidoFixedPriceMultiLpARM), type(uint256).max); - lidoFixedPriceMultiLpARM.deposit(amount); + weth.approve(address(lidoARM), type(uint256).max); + lidoARM.deposit(amount); vm.stopPrank(); if (mode == VmSafe.CallerMode.Prank) { @@ -101,14 +101,14 @@ abstract contract Modifiers is Helpers { _; } - /// @notice Request redeem from LidoFixedPriceMultiLpARM contract. - modifier requestRedeemFromLidoFixedPriceMultiLpARM(address user, uint256 amount) { + /// @notice Request redeem from LidoARM contract. + modifier requestRedeemFromLidoARM(address user, uint256 amount) { // Todo: extend this logic to other modifier if needed (VmSafe.CallerMode mode, address _address, address _origin) = vm.readCallers(); vm.stopPrank(); vm.startPrank(user); - lidoFixedPriceMultiLpARM.requestRedeem(amount); + lidoARM.requestRedeem(amount); vm.stopPrank(); if (mode == VmSafe.CallerMode.Prank) { @@ -119,14 +119,14 @@ abstract contract Modifiers is Helpers { _; } - /// @notice Claim redeem from LidoFixedPriceMultiLpARM contract. - modifier claimRequestOnLidoFixedPriceMultiLpARM(address user, uint256 requestId) { + /// @notice Claim redeem from LidoARM contract. + modifier claimRequestOnLidoARM(address user, uint256 requestId) { // Todo: extend this logic to other modifier if needed (VmSafe.CallerMode mode, address _address, address _origin) = vm.readCallers(); vm.stopPrank(); vm.startPrank(user); - lidoFixedPriceMultiLpARM.claimRedeem(requestId); + lidoARM.claimRedeem(requestId); vm.stopPrank(); if (mode == VmSafe.CallerMode.Prank) { @@ -137,24 +137,16 @@ abstract contract Modifiers is Helpers { _; } - /// @notice Simulate asset gain or loss in LidoFixedPriceMultiLpARM contract. - modifier simulateAssetGainInLidoFixedPriceMultiLpARM(uint256 assetGain, address token, bool gain) { + /// @notice Simulate asset gain or loss in LidoARM contract. + modifier simulateAssetGainInLidoARM(uint256 assetGain, address token, bool gain) { // Todo: extend this logic to other modifier if needed (VmSafe.CallerMode mode, address _address, address _origin) = vm.readCallers(); vm.stopPrank(); if (gain) { - deal( - token, - address(lidoFixedPriceMultiLpARM), - IERC20(token).balanceOf(address(lidoFixedPriceMultiLpARM)) + uint256(assetGain) - ); + deal(token, address(lidoARM), IERC20(token).balanceOf(address(lidoARM)) + uint256(assetGain)); } else { - deal( - token, - address(lidoFixedPriceMultiLpARM), - IERC20(token).balanceOf(address(lidoFixedPriceMultiLpARM)) - uint256(assetGain) - ); + deal(token, address(lidoARM), IERC20(token).balanceOf(address(lidoARM)) - uint256(assetGain)); } if (mode == VmSafe.CallerMode.Prank) { @@ -165,20 +157,20 @@ abstract contract Modifiers is Helpers { _; } - /// @notice Collect fees on LidoFixedPriceMultiLpARM contract. - modifier collectFeesOnLidoFixedPriceMultiLpARM() { - lidoFixedPriceMultiLpARM.collectFees(); + /// @notice Collect fees on LidoARM contract. + modifier collectFeesOnLidoARM() { + lidoARM.collectFees(); _; } - /// @notice Approve stETH on LidoFixedPriceMultiLpARM contract. - modifier approveStETHOnLidoFixedPriceMultiLpARM() { + /// @notice Approve stETH on LidoARM contract. + modifier approveStETHOnLidoARM() { // Todo: extend this logic to other modifier if needed (VmSafe.CallerMode mode, address _address, address _origin) = vm.readCallers(); vm.stopPrank(); - vm.prank(lidoFixedPriceMultiLpARM.owner()); - lidoFixedPriceMultiLpARM.approveStETH(); + vm.prank(lidoARM.owner()); + lidoARM.approveStETH(); if (mode == VmSafe.CallerMode.Prank) { vm.prank(_address, _origin); @@ -188,14 +180,14 @@ abstract contract Modifiers is Helpers { _; } - /// @notice Request stETH withdrawal for ETH on LidoFixedPriceMultiLpARM contract. - modifier requestStETHWithdrawalForETHOnLidoFixedPriceMultiLpARM(uint256[] memory amounts) { + /// @notice Request stETH withdrawal for ETH on LidoARM contract. + modifier requestStETHWithdrawalForETHOnLidoARM(uint256[] memory amounts) { // Todo: extend this logic to other modifier if needed (VmSafe.CallerMode mode, address _address, address _origin) = vm.readCallers(); vm.stopPrank(); - vm.prank(lidoFixedPriceMultiLpARM.owner()); - lidoFixedPriceMultiLpARM.requestStETHWithdrawalForETH(amounts); + vm.prank(lidoARM.owner()); + lidoARM.requestStETHWithdrawalForETH(amounts); if (mode == VmSafe.CallerMode.Prank) { vm.prank(_address, _origin); @@ -222,14 +214,14 @@ abstract contract Modifiers is Helpers { } /// @notice mock call for `claimWithdrawals` on lido withdraw contracts. - /// @dev this will send eth directly to the lidoFixedPriceMultiLpARM contract. - modifier mockFunctionClaimWithdrawOnLidoFixedPriceMultiLpARM(uint256 amount) { + /// @dev this will send eth directly to the lidoARM contract. + modifier mockFunctionClaimWithdrawOnLidoARM(uint256 amount) { // Todo: extend this logic to other modifier if needed (VmSafe.CallerMode mode, address _address, address _origin) = vm.readCallers(); vm.stopPrank(); // Deploy fake lido withdraw contract - MockLidoWithdraw mocklidoWithdraw = new MockLidoWithdraw(address(lidoFixedPriceMultiLpARM)); + MockLidoWithdraw mocklidoWithdraw = new MockLidoWithdraw(address(lidoARM)); // Give ETH to the ETH Sender contract vm.deal(address(mocklidoWithdraw.ethSender()), amount); // Mock all the call to the fake lido withdraw contract diff --git a/test/smoke/LidoARMSmokeTest.t.sol b/test/smoke/LidoARMSmokeTest.t.sol index 081f8e1..1852417 100644 --- a/test/smoke/LidoARMSmokeTest.t.sol +++ b/test/smoke/LidoARMSmokeTest.t.sol @@ -6,7 +6,7 @@ import {Test, console2} from "forge-std/Test.sol"; import {AbstractSmokeTest} from "./AbstractSmokeTest.sol"; import {IERC20} from "contracts/Interfaces.sol"; -import {LidoFixedPriceMultiLpARM} from "contracts/LidoFixedPriceMultiLpARM.sol"; +import {LidoARM} from "contracts/LidoARM.sol"; import {Proxy} from "contracts/Proxy.sol"; import {Mainnet} from "contracts/utils/Addresses.sol"; import {console} from "forge-std/console.sol"; @@ -17,7 +17,7 @@ contract Fork_LidoARM_Smoke_Test is AbstractSmokeTest { IERC20 weth; IERC20 steth; Proxy proxy; - LidoFixedPriceMultiLpARM lidoARM; + LidoARM lidoARM; address operator; function setUp() public { @@ -30,7 +30,7 @@ contract Fork_LidoARM_Smoke_Test is AbstractSmokeTest { vm.label(address(operator), "OPERATOR"); proxy = Proxy(deployManager.getDeployment("LIDO_ARM")); - lidoARM = LidoFixedPriceMultiLpARM(payable(deployManager.getDeployment("LIDO_ARM"))); + lidoARM = LidoARM(payable(deployManager.getDeployment("LIDO_ARM"))); // Only fuzz from this address. Big speedup on fork. targetSender(address(this));