diff --git a/Makefile b/Makefile index be1e153d..36e08d01 100644 --- a/Makefile +++ b/Makefile @@ -27,7 +27,7 @@ snapshot: # Tests test-std: - forge test --summary --fail-fast --show-progress + forge test --summary --fail-fast --show-progress -vvv test: @FOUNDRY_NO_MATCH_CONTRACT=Fuzzer $(MAKE) test-std @@ -42,7 +42,7 @@ test-all: @$(MAKE) test-std test-invariant-lido: - @FOUNDRY_INVARIANT_FAIL_ON_REVERT=false FOUNDRY_MATCH_CONTRACT=FuzzerFoundry_OethARM $(MAKE) test-std + @FOUNDRY_INVARIANT_FAIL_ON_REVERT=false FOUNDRY_MATCH_CONTRACT=FuzzerFoundry_LidoARM $(MAKE) test-std test-invariant-origin: @FOUNDRY_INVARIANT_FAIL_ON_REVERT=true FOUNDRY_MATCH_CONTRACT=FuzzerFoundry_OriginARM $(MAKE) test-std diff --git a/README.md b/README.md index 467a609a..45ced935 100644 --- a/README.md +++ b/README.md @@ -245,9 +245,10 @@ If the verification doesn't work with the deployment, it can be done separately For example ``` -# Verify OethARM -forge verify-contract 0xd8fF298eAed581f74ab845Af62C48aCF85B2f05e OethARM \ - --constructor-args $(cast abi-encode "constructor(address,address,address)" 0x856c4Efb76C1D1AE02e20CEB03A2A6a08b0b8dC3 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 0x39254033945AA2E4809Cc2977E7087BEE48bd7Ab ) +# Verify LidoARM +forge verify-contract 0xeC6FdCc3904F8dD6a9cbbBCC41B741df5963B42E LidoARM \ + --constructor-args $(cast abi-encode "constructor(address,address,address,uint256,uint256,int256)" 0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 0x889edC2eDab5f40e902b864aD4d7AdE8E412F9B1 600 0 0 ) + # Verify Proxy forge verify-contract 0x6bac785889A4127dB0e0CeFEE88E0a9F1Aaf3cC7 Proxy diff --git a/docs/OEthARMHierarchy.svg b/docs/OEthARMHierarchy.svg deleted file mode 100644 index 89dbae31..00000000 --- a/docs/OEthARMHierarchy.svg +++ /dev/null @@ -1,113 +0,0 @@ - - - - - - -UmlClassDiagram - - - -0 - -<<Abstract>> -AbstractARM -../src/contracts/AbstractARM.sol - - - -22 - -OwnableOperable -../src/contracts/OwnableOperable.sol - - - -0->22 - - - - - -19 - -OethARM -../src/contracts/OethARM.sol - - - -20 - -OethLiquidityManager -../src/contracts/OethLiquidityManager.sol - - - -19->20 - - - - - -23 - -<<Abstract>> -OwnerLP -../src/contracts/OwnerLP.sol - - - -19->23 - - - - - -24 - -<<Abstract>> -PeggedARM -../src/contracts/PeggedARM.sol - - - -19->24 - - - - - -20->22 - - - - - -21 - -Ownable -../src/contracts/Ownable.sol - - - -22->21 - - - - - -23->21 - - - - - -24->0 - - - - - diff --git a/docs/OEthARMSquashed.svg b/docs/OEthARMSquashed.svg deleted file mode 100644 index 62cc57f9..00000000 --- a/docs/OEthARMSquashed.svg +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - -UmlClassDiagram - - - -19 - -OethARM -../src/contracts/OethARM.sol - -Private: -   _gap: uint256[49] <<OwnableOperable>> -   _gap: uint256[50] <<AbstractARM>> -Internal: -   OWNER_SLOT: bytes32 <<Ownable>> -Public: -   operator: address <<OwnableOperable>> -   token0: IERC20 <<AbstractARM>> -   token1: IERC20 <<AbstractARM>> -   bothDirections: bool <<PeggedARM>> -   oeth: address <<OethLiquidityManager>> -   oethVault: address <<OethLiquidityManager>> - -Internal: -    _owner(): (ownerOut: address) <<Ownable>> -    _setOwner(newOwner: address) <<Ownable>> -    _onlyOwner() <<Ownable>> -    _initOwnableOperable(_operator: address) <<OwnableOperable>> -    _setOperator(newOperator: address) <<OwnableOperable>> -    _swapExactTokensForTokens(inToken: IERC20, outToken: IERC20, amountIn: uint256, to: address): (amountOut: uint256) <<PeggedARM>> -    _swapTokensForExactTokens(inToken: IERC20, outToken: IERC20, amountOut: uint256, to: address): (amountIn: uint256) <<PeggedARM>> -    _inDeadline(deadline: uint256) <<AbstractARM>> -    _transferAsset(asset: address, to: address, amount: uint256) <<AbstractARM>> -    _transferAssetFrom(asset: address, from: address, to: address, amount: uint256) <<AbstractARM>> -    _swap(inToken: IERC20, outToken: IERC20, amount: uint256, to: address): uint256 <<PeggedARM>> -    _approvals() <<OethLiquidityManager>> -External: -    owner(): address <<Ownable>> -    setOwner(newOwner: address) <<onlyOwner>> <<Ownable>> -    transferToken(token: address, to: address, amount: uint256) <<onlyOwner>> <<OwnerLP>> -    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>> -    approvals() <<onlyOwner>> <<OethLiquidityManager>> -    requestWithdrawal(amount: uint256): (requestId: uint256, queued: uint256) <<onlyOperatorOrOwner>> <<OethLiquidityManager>> -    claimWithdrawal(requestId: uint256) <<onlyOperatorOrOwner>> <<OethLiquidityManager>> -    claimWithdrawals(requestIds: uint256[]) <<onlyOperatorOrOwner>> <<OethLiquidityManager>> -    initialize(_operator: address) <<initializer>> <<OethARM>> -Public: -    <<event>> AdminChanged(previousAdmin: address, newAdmin: address) <<Ownable>> -    <<event>> OperatorChanged(newAdmin: address) <<OwnableOperable>> -    <<modifier>> onlyOwner() <<Ownable>> -    <<modifier>> onlyOperatorOrOwner() <<OwnableOperable>> -    constructor() <<Ownable>> -    constructor(_oeth: address, _oethVault: address) <<OethLiquidityManager>> -    constructor(_bothDirections: bool) <<PeggedARM>> -    constructor(_oeth: address, _weth: address, _oethVault: address) <<OethARM>> - - - diff --git a/docs/OEthARMStorage.svg b/docs/OEthARMStorage.svg deleted file mode 100644 index 05a02ce2..00000000 --- a/docs/OEthARMStorage.svg +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - -StorageDiagram - - - -1 - -OethARM <<Contract>> - -slot - -0 - -1-50 - -51 - -52-101 - -0x360..bbd - -0xb53..104 - -type: <inherited contract>.variable (bytes) - -unallocated (30) - -bool: Initializable.initializing (1) - -bool: Initializable.initialized (1) - -uint256[50]: Initializable.gap (1600) - -unallocated (12) - -address: OwnableOperable.operator (20) - -uint256[50]: OwnableOperable._gap (1600) - -unallocated (12) - -address: eip1967.proxy.implementation (20) - -unallocated (12) - -address: eip1967.proxy.admin (20) - - - diff --git a/docs/OriginARMHierarchy.svg b/docs/OriginARMHierarchy.svg new file mode 100644 index 00000000..3d21b159 --- /dev/null +++ b/docs/OriginARMHierarchy.svg @@ -0,0 +1,60 @@ + + + + + + +UmlClassDiagram + + + +0 + +<<Abstract>> +AbstractARM +../src/contracts/AbstractARM.sol + + + +29 + +OwnableOperable +../src/contracts/OwnableOperable.sol + + + +0->29 + + + + + +27 + +OriginARM +../src/contracts/OriginARM.sol + + + +27->0 + + + + + +28 + +Ownable +../src/contracts/Ownable.sol + + + +29->28 + + + + + diff --git a/docs/OriginARMPublicSquashed.svg b/docs/OriginARMPublicSquashed.svg new file mode 100644 index 00000000..b65e6e4d --- /dev/null +++ b/docs/OriginARMPublicSquashed.svg @@ -0,0 +1,108 @@ + + + + + + +UmlClassDiagram + + + +27 + +OriginARM +../src/contracts/OriginARM.sol + +Public: +   operator: address <<OwnableOperable>> +   MAX_CROSS_PRICE_DEVIATION: uint256 <<AbstractARM>> +   PRICE_SCALE: uint256 <<AbstractARM>> +   FEE_SCALE: uint256 <<AbstractARM>> +   minSharesToRedeem: uint256 <<AbstractARM>> +   allocateThreshold: int256 <<AbstractARM>> +   liquidityAsset: address <<AbstractARM>> +   baseAsset: address <<AbstractARM>> +   token0: IERC20 <<AbstractARM>> +   token1: IERC20 <<AbstractARM>> +   claimDelay: uint256 <<AbstractARM>> +   traderate0: uint256 <<AbstractARM>> +   traderate1: uint256 <<AbstractARM>> +   crossPrice: uint256 <<AbstractARM>> +   withdrawsQueued: uint128 <<AbstractARM>> +   withdrawsClaimed: uint128 <<AbstractARM>> +   nextWithdrawalIndex: uint256 <<AbstractARM>> +   withdrawalRequests: mapping(uint256=>WithdrawalRequest) <<AbstractARM>> +   fee: uint16 <<AbstractARM>> +   lastAvailableAssets: int128 <<AbstractARM>> +   feeCollector: address <<AbstractARM>> +   capManager: address <<AbstractARM>> +   activeMarket: address <<AbstractARM>> +   supportedMarkets: mapping(address=>bool) <<AbstractARM>> +   armBuffer: uint256 <<AbstractARM>> +   vault: address <<OriginARM>> +   vaultWithdrawalAmount: uint256 <<OriginARM>> + +External: +    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>> +    getReserves(): (reserve0: uint256, reserve1: uint256) <<AbstractARM>> +    setPrices(buyT1: uint256, sellT1: uint256) <<onlyOperatorOrOwner>> <<AbstractARM>> +    setCrossPrice(newCrossPrice: uint256) <<onlyOwner>> <<AbstractARM>> +    previewDeposit(assets: uint256): (shares: uint256) <<AbstractARM>> +    deposit(assets: uint256): (shares: uint256) <<AbstractARM>> +    deposit(assets: uint256, receiver: address): (shares: uint256) <<AbstractARM>> +    previewRedeem(shares: uint256): (assets: uint256) <<AbstractARM>> +    requestRedeem(shares: uint256): (requestId: uint256, assets: uint256) <<AbstractARM>> +    claimRedeem(requestId: uint256): (assets: uint256) <<AbstractARM>> +    asset(): address <<AbstractARM>> +    setFee(_fee: uint256) <<onlyOwner>> <<AbstractARM>> +    setFeeCollector(_feeCollector: address) <<onlyOwner>> <<AbstractARM>> +    feesAccrued(): (fees: uint256) <<AbstractARM>> +    addMarkets(_markets: address[]) <<onlyOwner>> <<AbstractARM>> +    removeMarket(_market: address) <<onlyOwner>> <<AbstractARM>> +    setActiveMarket(_market: address) <<onlyOperatorOrOwner>> <<AbstractARM>> +    allocate(): (liquidityDelta: int256) <<AbstractARM>> +    setCapManager(_capManager: address) <<onlyOwner>> <<AbstractARM>> +    setARMBuffer(_armBuffer: uint256) <<onlyOperatorOrOwner>> <<AbstractARM>> +    initialize(_name: string, _symbol: string, _operator: address, _fee: uint256, _feeCollector: address, _capManager: address) <<initializer>> <<OriginARM>> +    requestOriginWithdrawal(amount: uint256): (requestId: uint256) <<onlyOperatorOrOwner>> <<OriginARM>> +    claimOriginWithdrawals(requestIds: uint256[]): (amountClaimed: uint256) <<OriginARM>> +Public: +    <<event>> AdminChanged(previousAdmin: address, newAdmin: address) <<Ownable>> +    <<event>> OperatorChanged(newAdmin: address) <<OwnableOperable>> +    <<event>> TraderateChanged(traderate0: uint256, traderate1: uint256) <<AbstractARM>> +    <<event>> CrossPriceUpdated(crossPrice: uint256) <<AbstractARM>> +    <<event>> Deposit(owner: address, assets: uint256, shares: 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>> FeeCollected(feeCollector: address, fee: uint256) <<AbstractARM>> +    <<event>> FeeUpdated(fee: uint256) <<AbstractARM>> +    <<event>> FeeCollectorUpdated(newFeeCollector: address) <<AbstractARM>> +    <<event>> CapManagerUpdated(capManager: address) <<AbstractARM>> +    <<event>> ActiveMarketUpdated(market: address) <<AbstractARM>> +    <<event>> MarketAdded(market: address) <<AbstractARM>> +    <<event>> MarketRemoved(market: address) <<AbstractARM>> +    <<event>> ARMBufferUpdated(armBuffer: uint256) <<AbstractARM>> +    <<event>> Allocated(market: address, assets: int256) <<AbstractARM>> +    <<event>> RequestOriginWithdrawal(amount: uint256, requestId: uint256) <<OriginARM>> +    <<event>> ClaimOriginWithdrawals(requestIds: uint256[], amountClaimed: uint256) <<OriginARM>> +    <<modifier>> onlyOwner() <<Ownable>> +    <<modifier>> onlyOperatorOrOwner() <<OwnableOperable>> +    constructor() <<Ownable>> +    owner(): address <<Ownable>> +    constructor(_otoken: address, _liquidityAsset: address, _vault: address, _claimDelay: uint256, _minSharesToRedeem: uint256, _allocateThreshold: int256) <<OriginARM>> +    claimable(): (claimableAmount: uint256) <<AbstractARM>> +    totalAssets(): uint256 <<AbstractARM>> +    convertToShares(assets: uint256): (shares: uint256) <<AbstractARM>> +    convertToAssets(shares: uint256): (assets: uint256) <<AbstractARM>> +    collectFees(): (fees: uint256) <<AbstractARM>> + + + diff --git a/docs/OriginARMSquashed.svg b/docs/OriginARMSquashed.svg new file mode 100644 index 00000000..8540d3e3 --- /dev/null +++ b/docs/OriginARMSquashed.svg @@ -0,0 +1,135 @@ + + + + + + +UmlClassDiagram + + + +27 + +OriginARM +../src/contracts/OriginARM.sol + +Private: +   _gap: uint256[49] <<OwnableOperable>> +   _gap: uint256[38] <<AbstractARM>> +Internal: +   OWNER_SLOT: bytes32 <<Ownable>> +   MIN_TOTAL_SUPPLY: uint256 <<AbstractARM>> +   DEAD_ACCOUNT: address <<AbstractARM>> +Public: +   operator: address <<OwnableOperable>> +   MAX_CROSS_PRICE_DEVIATION: uint256 <<AbstractARM>> +   PRICE_SCALE: uint256 <<AbstractARM>> +   FEE_SCALE: uint256 <<AbstractARM>> +   minSharesToRedeem: uint256 <<AbstractARM>> +   allocateThreshold: int256 <<AbstractARM>> +   liquidityAsset: address <<AbstractARM>> +   baseAsset: address <<AbstractARM>> +   token0: IERC20 <<AbstractARM>> +   token1: IERC20 <<AbstractARM>> +   claimDelay: uint256 <<AbstractARM>> +   traderate0: uint256 <<AbstractARM>> +   traderate1: uint256 <<AbstractARM>> +   crossPrice: uint256 <<AbstractARM>> +   withdrawsQueued: uint128 <<AbstractARM>> +   withdrawsClaimed: uint128 <<AbstractARM>> +   nextWithdrawalIndex: uint256 <<AbstractARM>> +   withdrawalRequests: mapping(uint256=>WithdrawalRequest) <<AbstractARM>> +   fee: uint16 <<AbstractARM>> +   lastAvailableAssets: int128 <<AbstractARM>> +   feeCollector: address <<AbstractARM>> +   capManager: address <<AbstractARM>> +   activeMarket: address <<AbstractARM>> +   supportedMarkets: mapping(address=>bool) <<AbstractARM>> +   armBuffer: uint256 <<AbstractARM>> +   vault: address <<OriginARM>> +   vaultWithdrawalAmount: uint256 <<OriginARM>> + +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, _capManager: address) <<AbstractARM>> +    _inDeadline(deadline: uint256) <<AbstractARM>> +    _transferAsset(asset: address, to: address, amount: uint256) <<AbstractARM>> +    _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>> +    _deposit(assets: uint256, receiver: address): (shares: uint256) <<AbstractARM>> +    _requireLiquidityAvailable(amount: uint256) <<AbstractARM>> +    _availableAssets(): (availableAssets: uint256, outstandingWithdrawals: uint256) <<AbstractARM>> +    _externalWithdrawQueue(): uint256 <<OriginARM>> +    _setFee(_fee: uint256) <<AbstractARM>> +    _setFeeCollector(_feeCollector: address) <<AbstractARM>> +    _feesAccrued(): (fees: uint256, newAvailableAssets: uint256) <<AbstractARM>> +    _allocate(): (liquidityDelta: int256) <<AbstractARM>> +External: +    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>> +    getReserves(): (reserve0: uint256, reserve1: uint256) <<AbstractARM>> +    setPrices(buyT1: uint256, sellT1: uint256) <<onlyOperatorOrOwner>> <<AbstractARM>> +    setCrossPrice(newCrossPrice: uint256) <<onlyOwner>> <<AbstractARM>> +    previewDeposit(assets: uint256): (shares: uint256) <<AbstractARM>> +    deposit(assets: uint256): (shares: uint256) <<AbstractARM>> +    deposit(assets: uint256, receiver: address): (shares: uint256) <<AbstractARM>> +    previewRedeem(shares: uint256): (assets: uint256) <<AbstractARM>> +    requestRedeem(shares: uint256): (requestId: uint256, assets: uint256) <<AbstractARM>> +    claimRedeem(requestId: uint256): (assets: uint256) <<AbstractARM>> +    asset(): address <<AbstractARM>> +    setFee(_fee: uint256) <<onlyOwner>> <<AbstractARM>> +    setFeeCollector(_feeCollector: address) <<onlyOwner>> <<AbstractARM>> +    feesAccrued(): (fees: uint256) <<AbstractARM>> +    addMarkets(_markets: address[]) <<onlyOwner>> <<AbstractARM>> +    removeMarket(_market: address) <<onlyOwner>> <<AbstractARM>> +    setActiveMarket(_market: address) <<onlyOperatorOrOwner>> <<AbstractARM>> +    allocate(): (liquidityDelta: int256) <<AbstractARM>> +    setCapManager(_capManager: address) <<onlyOwner>> <<AbstractARM>> +    setARMBuffer(_armBuffer: uint256) <<onlyOperatorOrOwner>> <<AbstractARM>> +    initialize(_name: string, _symbol: string, _operator: address, _fee: uint256, _feeCollector: address, _capManager: address) <<initializer>> <<OriginARM>> +    requestOriginWithdrawal(amount: uint256): (requestId: uint256) <<onlyOperatorOrOwner>> <<OriginARM>> +    claimOriginWithdrawals(requestIds: uint256[]): (amountClaimed: uint256) <<OriginARM>> +Public: +    <<event>> AdminChanged(previousAdmin: address, newAdmin: address) <<Ownable>> +    <<event>> OperatorChanged(newAdmin: address) <<OwnableOperable>> +    <<event>> TraderateChanged(traderate0: uint256, traderate1: uint256) <<AbstractARM>> +    <<event>> CrossPriceUpdated(crossPrice: uint256) <<AbstractARM>> +    <<event>> Deposit(owner: address, assets: uint256, shares: 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>> FeeCollected(feeCollector: address, fee: uint256) <<AbstractARM>> +    <<event>> FeeUpdated(fee: uint256) <<AbstractARM>> +    <<event>> FeeCollectorUpdated(newFeeCollector: address) <<AbstractARM>> +    <<event>> CapManagerUpdated(capManager: address) <<AbstractARM>> +    <<event>> ActiveMarketUpdated(market: address) <<AbstractARM>> +    <<event>> MarketAdded(market: address) <<AbstractARM>> +    <<event>> MarketRemoved(market: address) <<AbstractARM>> +    <<event>> ARMBufferUpdated(armBuffer: uint256) <<AbstractARM>> +    <<event>> Allocated(market: address, assets: int256) <<AbstractARM>> +    <<event>> RequestOriginWithdrawal(amount: uint256, requestId: uint256) <<OriginARM>> +    <<event>> ClaimOriginWithdrawals(requestIds: uint256[], amountClaimed: uint256) <<OriginARM>> +    <<modifier>> onlyOwner() <<Ownable>> +    <<modifier>> onlyOperatorOrOwner() <<OwnableOperable>> +    constructor() <<Ownable>> +    owner(): address <<Ownable>> +    constructor(_otoken: address, _liquidityAsset: address, _vault: address, _claimDelay: uint256, _minSharesToRedeem: uint256, _allocateThreshold: int256) <<OriginARM>> +    claimable(): (claimableAmount: uint256) <<AbstractARM>> +    totalAssets(): uint256 <<AbstractARM>> +    convertToShares(assets: uint256): (shares: uint256) <<AbstractARM>> +    convertToAssets(shares: uint256): (assets: uint256) <<AbstractARM>> +    collectFees(): (fees: uint256) <<AbstractARM>> + + + diff --git a/docs/generate.sh b/docs/generate.sh index 3c607ab1..c51df7d4 100644 --- a/docs/generate.sh +++ b/docs/generate.sh @@ -6,13 +6,6 @@ sol2uml storage ../src/contracts -c Proxy -o ProxyStorage.svg \ -sn eip1967.proxy.implementation,eip1967.proxy.admin \ -st address,address -sol2uml ../src/contracts -v -hv -hf -he -hs -hl -hi -b OethARM -o OethARMHierarchy.svg -sol2uml ../src/contracts -s -d 0 -b OethARM -o OethARMSquashed.svg -sol2uml storage ../src/contracts -c OethARM -o OethARMStorage.svg \ - -sn eip1967.proxy.implementation,eip1967.proxy.admin \ - -st address,address \ - --hideExpand gap,_gap - 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 @@ -21,6 +14,15 @@ sol2uml storage ../src/contracts,../lib -c LidoARM -o LidoARMStorage.svg \ -st address,address \ --hideExpand gap,_gap +sol2uml ../src/contracts -v -hv -hf -he -hs -hl -hi -b OriginARM -o OriginARMHierarchy.svg +sol2uml ../src/contracts -s -d 0 -b OriginARM -o OriginARMSquashed.svg +sol2uml ../src/contracts -hp -s -d 0 -b OriginARM -o OriginARMPublicSquashed.svg +sol2uml storage ../src/contracts,../lib -c OriginARM -o OriginARMStorage.svg \ + -sn eip1967.proxy.implementation,eip1967.proxy.admin \ + -st address,address \ + --hideExpand gap,_gap + + sol2uml ../src/contracts -v -hv -hf -he -hs -hl -hi -b CapManager -o CapManagerHierarchy.svg sol2uml ../src/contracts -s -d 0 -b CapManager -o CapManagerSquashed.svg diff --git a/docs/plantuml/originContracts.png b/docs/plantuml/originContracts.png new file mode 100644 index 00000000..e3e31351 Binary files /dev/null and b/docs/plantuml/originContracts.png differ diff --git a/docs/plantuml/originContracts.puml b/docs/plantuml/originContracts.puml new file mode 100644 index 00000000..c53cfd4d --- /dev/null +++ b/docs/plantuml/originContracts.puml @@ -0,0 +1,34 @@ +@startuml + +!$originColor = DeepSkyBlue +' !$originColor = WhiteSmoke +!$newColor = LightGreen +!$changedColor = Orange +!$thirdPartyColor = WhiteSmoke + +' legend +' blue - Origin +' ' green - new +' ' orange - changed +' white - 3rd Party +' end legend + +title "Origin OETH Automated Redemption Manager (ARM) Contract Dependencies" + + +object "ZapperOriginARM" as zap <> #$originColor { +} + +object "OriginARM" as arm <><> #$originColor { + shares: ARM-oETH-WETH + assets: oETH, WETH +} + +object "OETHVault" as oethVault <><> #$thirdPartyColor { + assets: oETH, WETH +} + +zap <..> arm +arm ..> oethVault + +@enduml \ No newline at end of file diff --git a/foundry.toml b/foundry.toml index 5803d379..85c172c9 100644 --- a/foundry.toml +++ b/foundry.toml @@ -6,7 +6,7 @@ verbosity = 3 sender = "0x0165C55EF814dEFdd658532A48Bd17B2c8356322" tx_origin = "0x0165C55EF814dEFdd658532A48Bd17B2c8356322" auto_detect_remappings = false -gas_reports = ["OethARM", "Proxy", "LidoARM", "OriginARM"] +gas_reports = ["Proxy", "LidoARM", "OriginARM"] fs_permissions = [{ access = "read-write", path = "./build" }] extra_output_files = ["metadata"] ignored_warnings_from = ["src/contracts/Proxy.sol"] diff --git a/script/deploy/DeployManager.sol b/script/deploy/DeployManager.sol index 7aa9052a..f83458b9 100644 --- a/script/deploy/DeployManager.sol +++ b/script/deploy/DeployManager.sol @@ -6,14 +6,10 @@ import {VmSafe} from "forge-std/Vm.sol"; import {stdJson} from "forge-std/StdJson.sol"; import {AbstractDeployScript} from "./AbstractDeployScript.sol"; -import {DeployCoreMainnetScript} from "./mainnet/001_DeployCoreScript.sol"; -import {UpgradeMainnetScript} from "./mainnet/002_UpgradeScript.sol"; import {UpgradeLidoARMMainnetScript} from "./mainnet/003_UpgradeLidoARMScript.sol"; import {UpdateCrossPriceMainnetScript} from "./mainnet/004_UpdateCrossPriceScript.sol"; import {RegisterLidoWithdrawalsScript} from "./mainnet/005_RegisterLidoWithdrawalsScript.sol"; import {ChangeFeeCollectorScript} from "./mainnet/006_ChangeFeeCollector.sol"; -import {DeployCoreHoleskyScript} from "./holesky/001_DeployCoreScript.sol"; -import {UpgradeHoleskyScript} from "./holesky/002_UpgradeScript.sol"; import {DeployOriginARMProxyScript} from "./sonic/001_DeployOriginARMProxy.sol"; import {DeployOriginARMScript} from "./sonic/002_DeployOriginARM.sol"; import {UpgradeOriginARMScript} from "./sonic/003_UpgradeOriginARM.sol"; @@ -24,6 +20,7 @@ import {UpgradeLidoARMSetBufferScript} from "./mainnet/009_UpgradeLidoARMSetBuff import {UpgradeOriginARMSetBufferScript} from "./sonic/005_UpgradeOriginARMSetBufferScript.sol"; import {UpgradeLidoARMAssetScript} from "./mainnet/010_UpgradeLidoARMAssetScript.sol"; import {DeployEtherFiARMScript} from "./mainnet/011_DeployEtherFiARMScript.sol"; +import {UpgradeOETHARMScript} from "./mainnet/012_UpgradeOETHARMScript.sol"; contract DeployManager is Script { using stdJson for string; @@ -73,8 +70,6 @@ contract DeployManager is Script { function run() external { if (block.chainid == 1 || block.chainid == 31337) { // TODO: Use vm.readDir to recursively build this? - _runDeployFile(new DeployCoreMainnetScript()); - _runDeployFile(new UpgradeMainnetScript(getDeployment("OETH_ARM"))); _runDeployFile(new UpgradeLidoARMMainnetScript()); _runDeployFile(new UpdateCrossPriceMainnetScript()); _runDeployFile(new RegisterLidoWithdrawalsScript()); @@ -84,10 +79,9 @@ contract DeployManager is Script { _runDeployFile(new DeployPendleAdaptor()); _runDeployFile(new UpgradeLidoARMAssetScript()); _runDeployFile(new DeployEtherFiARMScript()); + _runDeployFile(new UpgradeOETHARMScript()); } else if (block.chainid == 17000) { // Holesky - _runDeployFile(new DeployCoreHoleskyScript()); - _runDeployFile(new UpgradeHoleskyScript(getDeployment("OETH_ARM"))); } else if (block.chainid == 146) { // Sonic console.log("Deploying Origin ARM"); diff --git a/script/deploy/holesky/001_DeployCoreScript.sol b/script/deploy/holesky/001_DeployCoreScript.sol deleted file mode 100644 index 8650642e..00000000 --- a/script/deploy/holesky/001_DeployCoreScript.sol +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.23; - -import "forge-std/console.sol"; -import {Vm} from "forge-std/Vm.sol"; - -import {OethARM} from "contracts/OethARM.sol"; -import {Proxy} from "contracts/Proxy.sol"; -import {Holesky} from "contracts/utils/Addresses.sol"; -import {AbstractDeployScript} from "../AbstractDeployScript.sol"; - -contract DeployCoreHoleskyScript is AbstractDeployScript { - string public constant override DEPLOY_NAME = "001_CoreHolesky"; - bool public constant override proposalExecuted = false; - - function _execute() internal override { - console.log("Deploy:", DEPLOY_NAME); - console.log("------------"); - - // 1. Deploy proxy contracts - Proxy proxy = new Proxy(); - _recordDeploy("OETH_ARM", address(proxy)); - - // 2. Deploy implementation - OethARM implementation = new OethARM(Holesky.OETH, Holesky.WETH, Holesky.OETH_VAULT); - _recordDeploy("OETH_ARM_IMPL", address(implementation)); - - // 3. Initialize proxy, set the owner and operator to the RELAYER and approve the OETH Vault to transfer OETH - bytes memory data = abi.encodeWithSignature("initialize(address)", Holesky.RELAYER); - proxy.initialize(address(implementation), Holesky.RELAYER, data); - } -} diff --git a/script/deploy/holesky/002_UpgradeScript.sol b/script/deploy/holesky/002_UpgradeScript.sol deleted file mode 100644 index 30dd8b2f..00000000 --- a/script/deploy/holesky/002_UpgradeScript.sol +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.23; - -import "forge-std/console.sol"; -import {Vm} from "forge-std/Vm.sol"; - -import {OethARM} from "contracts/OethARM.sol"; -import {Proxy} from "contracts/Proxy.sol"; -import {Holesky} from "contracts/utils/Addresses.sol"; -import {AbstractDeployScript} from "../AbstractDeployScript.sol"; - -contract UpgradeHoleskyScript is AbstractDeployScript { - string public constant override DEPLOY_NAME = "002_UpgradeHolesky"; - bool public constant override proposalExecuted = false; - - address newImpl; - Proxy internal proxy; - - constructor(address _proxy) { - proxy = Proxy(payable(_proxy)); - } - - function _execute() internal override { - console.log("Deploy:", DEPLOY_NAME); - console.log("------------"); - - // 1. Deploy new implementation - newImpl = address(new OethARM(Holesky.OETH, Holesky.WETH, Holesky.OETH_VAULT)); - _recordDeploy("OETH_ARM_IMPL", newImpl); - } - - function _fork() internal override { - // Upgrade the proxy - vm.prank(Holesky.RELAYER); - proxy.upgradeTo(newImpl); - } -} diff --git a/script/deploy/mainnet/001_DeployCoreScript.sol b/script/deploy/mainnet/001_DeployCoreScript.sol deleted file mode 100644 index 2b97c617..00000000 --- a/script/deploy/mainnet/001_DeployCoreScript.sol +++ /dev/null @@ -1,53 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.23; - -import "forge-std/console.sol"; -import {Vm} from "forge-std/Vm.sol"; - -import {OethARM} from "contracts/OethARM.sol"; -import {Proxy} from "contracts/Proxy.sol"; -import {Mainnet} from "contracts/utils/Addresses.sol"; -import {GovProposal, GovSixHelper} from "contracts/utils/GovSixHelper.sol"; -import {AbstractDeployScript} from "../AbstractDeployScript.sol"; - -contract DeployCoreMainnetScript is AbstractDeployScript { - using GovSixHelper for GovProposal; - - GovProposal public govProposal; - - string public constant override DEPLOY_NAME = "001_CoreMainnet"; - bool public constant override proposalExecuted = true; - - function _execute() internal override { - console.log("Deploy:", DEPLOY_NAME); - console.log("------------"); - - // 1. Deploy proxy contracts - Proxy proxy = new Proxy(); - _recordDeploy("OETH_ARM", address(proxy)); - - // 2. Deploy implementation - OethARM implementation = new OethARM(Mainnet.OETH, Mainnet.WETH, Mainnet.OETH_VAULT); - _recordDeploy("OETH_ARM_IMPL", address(implementation)); - - // 3. Initialize proxy, set the owner to TIMELOCK, set the operator to the OETH Relayer and approve the OETH Vault to transfer OETH - bytes memory data = abi.encodeWithSignature("initialize(address)", Mainnet.OETH_RELAYER); - proxy.initialize(address(implementation), Mainnet.TIMELOCK, data); - } - - function _buildGovernanceProposal() internal override { - // govProposal.setDescription("Setup OETH ARM Contract"); - - // NOTE: This has already been done during deployment - // but doing this here to test governance flow. - - // Set operator - // govProposal.action(deployedContracts["OETH_ARM"], "initialize(address)", abi.encode(Mainnet.OETH_RELAYER)); - } - - function _fork() internal override { - // Simulate on fork - // govProposal.simulate(); - } -} diff --git a/script/deploy/mainnet/002_UpgradeScript.sol b/script/deploy/mainnet/002_UpgradeScript.sol deleted file mode 100644 index f9804f80..00000000 --- a/script/deploy/mainnet/002_UpgradeScript.sol +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.23; - -import "forge-std/console.sol"; -import {Vm} from "forge-std/Vm.sol"; - -import {OethARM} from "contracts/OethARM.sol"; -import {Proxy} from "contracts/Proxy.sol"; -import {Mainnet} from "contracts/utils/Addresses.sol"; -import {AbstractDeployScript} from "../AbstractDeployScript.sol"; - -contract UpgradeMainnetScript is AbstractDeployScript { - string public constant override DEPLOY_NAME = "002_UpgradeMainnet"; - bool public constant override proposalExecuted = true; - - address newImpl; - Proxy internal proxy; - - constructor(address _proxy) { - proxy = Proxy(payable(_proxy)); - } - - function _execute() internal override { - console.log("Deploy:", DEPLOY_NAME); - console.log("------------"); - - // 1. Deploy new implementation - newImpl = address(new OethARM(Mainnet.OETH, Mainnet.WETH, Mainnet.OETH_VAULT)); - _recordDeploy("OETH_ARM_IMPL", newImpl); - } - - function _fork() internal override { - // Upgrade the proxy - vm.prank(Mainnet.TIMELOCK); - proxy.upgradeTo(newImpl); - console.log("OethARM upgraded"); - } -} diff --git a/script/deploy/mainnet/005_RegisterLidoWithdrawalsScript.sol b/script/deploy/mainnet/005_RegisterLidoWithdrawalsScript.sol index 7fd9fafb..0a16afb1 100644 --- a/script/deploy/mainnet/005_RegisterLidoWithdrawalsScript.sol +++ b/script/deploy/mainnet/005_RegisterLidoWithdrawalsScript.sol @@ -3,7 +3,6 @@ pragma solidity 0.8.23; import "forge-std/console.sol"; -import {Vm} from "forge-std/Vm.sol"; import {LidoARM} from "contracts/LidoARM.sol"; import {Mainnet} from "contracts/utils/Addresses.sol"; diff --git a/script/deploy/mainnet/012_UpgradeOETHARMScript.sol b/script/deploy/mainnet/012_UpgradeOETHARMScript.sol new file mode 100644 index 00000000..282085df --- /dev/null +++ b/script/deploy/mainnet/012_UpgradeOETHARMScript.sol @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.23; + +// Foundry imports +import {console} from "forge-std/console.sol"; + +// Contract imports +import {Proxy} from "contracts/Proxy.sol"; +import {Mainnet} from "contracts/utils/Addresses.sol"; +import {OriginARM} from "contracts/OriginARM.sol"; +import {IERC20} from "contracts/Interfaces.sol"; + +// Deployment imports +import {GovProposal, GovSixHelper} from "contracts/utils/GovSixHelper.sol"; +import {AbstractDeployScript} from "../AbstractDeployScript.sol"; + +contract UpgradeOETHARMScript is AbstractDeployScript { + using GovSixHelper for GovProposal; + + GovProposal public govProposal; + + string public constant override DEPLOY_NAME = "012_UpgradeOETHARMScript"; + bool public constant override proposalExecuted = false; + + Proxy morphoMarketProxy; + OriginARM originARMImpl; + OriginARM oethARM; + + function _execute() internal override { + console.log("Deploy:", DEPLOY_NAME); + console.log("------------"); + + // 1. Deploy new Origin implementation + uint256 claimDelay = tenderlyTestnet ? 1 minutes : 10 minutes; + originARMImpl = new OriginARM(Mainnet.OETH, Mainnet.WETH, Mainnet.OETH_VAULT, claimDelay, 1e7, 1e18); + _recordDeploy("OETH_ARM_IMPL", address(originARMImpl)); + + console.log("Finished deploying", DEPLOY_NAME); + } + + function _buildGovernanceProposal() internal override { + govProposal.setDescription("Update OETH ARM to use Origin ARM contract"); + + // 1. Transfer OETH out of the existing OETH ARM, to have a clean assets per share ratio. + uint256 balanceOETH = IERC20(Mainnet.OETH).balanceOf(deployedContracts["OETH_ARM"]); + govProposal.action( + deployedContracts["OETH_ARM"], + "transferToken(address,address,uint256)", + abi.encode(Mainnet.OETH, Mainnet.TREASURY_LP, balanceOETH) + ); + + // 2. Transfer WETH out of the existing OETH ARM, to have a clean assets per share ratio. + uint256 balanceWETH = IERC20(Mainnet.WETH).balanceOf(deployedContracts["OETH_ARM"]); + govProposal.action( + deployedContracts["OETH_ARM"], + "transferToken(address,address,uint256)", + abi.encode(Mainnet.WETH, Mainnet.TREASURY_LP, balanceWETH) + ); + + // 3. Timelock needs to approve the OETH ARM to pull WETH for initialization. + govProposal.action(Mainnet.WETH, "approve(address,uint256)", abi.encode(deployedContracts["OETH_ARM"], 1e12)); + + // 4. Upgrade the OETH ARM implementation, and initialize. + bytes memory initializeData = abi.encodeWithSelector( + OriginARM.initialize.selector, + "Origin ARM", + "ARM-WETH-OETH", + Mainnet.ARM_RELAYER, + 2000, // 20% performance fee + Mainnet.ARM_BUYBACK, + address(0) + ); + + govProposal.action( + deployedContracts["OETH_ARM"], + "upgradeToAndCall(address,bytes)", + abi.encode(deployedContracts["OETH_ARM_IMPL"], initializeData) + ); + + govProposal.simulate(); + } +} diff --git a/script/deploy/sonic/001_DeployOriginARMProxy.sol b/script/deploy/sonic/001_DeployOriginARMProxy.sol index b2a453c9..63a84203 100644 --- a/script/deploy/sonic/001_DeployOriginARMProxy.sol +++ b/script/deploy/sonic/001_DeployOriginARMProxy.sol @@ -3,7 +3,6 @@ pragma solidity 0.8.23; import "forge-std/console.sol"; -import {Vm} from "forge-std/Vm.sol"; import {Proxy} from "contracts/Proxy.sol"; import {AbstractDeployScript} from "../AbstractDeployScript.sol"; diff --git a/src/contracts/Interfaces.sol b/src/contracts/Interfaces.sol index 3f36b906..623002c6 100644 --- a/src/contracts/Interfaces.sol +++ b/src/contracts/Interfaces.sol @@ -13,100 +13,6 @@ interface IERC20 { event Transfer(address indexed from, address indexed to, uint256 value); } -interface IOethARM { - function token0() external returns (address); - function token1() external returns (address); - function owner() external returns (address); - - /** - * @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 - * at least amountIn on the input token. - * - * @param inToken Input token. - * @param outToken Output token. - * @param amountIn The amount of input tokens to send. - * @param amountOutMin The minimum amount of output tokens that must be received for the transaction not to revert. - * @param to Recipient of the output tokens. - */ - function swapExactTokensForTokens( - IERC20 inToken, - IERC20 outToken, - uint256 amountIn, - uint256 amountOutMin, - address to - ) external; - - /** - * @notice Uniswap V2 Router compatible interface. 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 - * at least amountIn on the input token. - * - * @param amountIn The amount of input tokens to send. - * @param amountOutMin The minimum amount of output tokens that must be received for the transaction not to revert. - * @param path The input and output token addresses. - * @param to Recipient of the output tokens. - * @param deadline Unix timestamp after which the transaction will revert. - * @return amounts The input and output token amounts. - */ - function swapExactTokensForTokens( - uint256 amountIn, - uint256 amountOutMin, - address[] calldata path, - address to, - uint256 deadline - ) external returns (uint256[] memory amounts); - - /** - * @notice Receive an exact amount of output tokens for as few input tokens as possible. - * msg.sender should have already given the router an allowance of - * at least amountInMax on the input token. - * - * @param inToken Input token. - * @param outToken Output token. - * @param amountOut The amount of output tokens to receive. - * @param amountInMax The maximum amount of input tokens that can be required before the transaction reverts. - * @param to Recipient of the output tokens. - */ - function swapTokensForExactTokens( - IERC20 inToken, - IERC20 outToken, - uint256 amountOut, - uint256 amountInMax, - address to - ) external; - - /** - * @notice Uniswap V2 Router compatible interface. Receive an exact amount of - * output tokens for as few input tokens as possible. - * msg.sender should have already given the router an allowance of - * at least amountInMax on the input token. - * - * @param amountOut The amount of output tokens to receive. - * @param amountInMax The maximum amount of input tokens that can be required before the transaction reverts. - * @param path The input and output token addresses. - * @param to Recipient of the output tokens. - * @param deadline Unix timestamp after which the transaction will revert. - * @return amounts The input and output token amounts. - */ - function swapTokensForExactTokens( - uint256 amountOut, - uint256 amountInMax, - address[] calldata path, - address to, - uint256 deadline - ) external returns (uint256[] memory amounts); - - function setOwner(address newOwner) external; - function transferToken(address token, address to, uint256 amount) external; - - // From OethLiquidityManager - function requestWithdrawal(uint256 amount) external returns (uint256 requestId, uint256 queued); - function claimWithdrawal(uint256 requestId) external; - function claimWithdrawals(uint256[] calldata requestIds) external; -} - interface ILiquidityProviderARM is IERC20 { function previewDeposit(uint256 assets) external returns (uint256 shares); function deposit(uint256 assets) external returns (uint256 shares); @@ -319,6 +225,7 @@ interface IEETHWithdrawalNFT { } interface IEETHRedemptionManager { + function redeemEEth(uint256 amount, address receiver) external; function redeemWeEth(uint256 amount, address receiver) external; function canRedeem(uint256 amount) external view returns (bool); } diff --git a/src/contracts/OethARM.sol b/src/contracts/OethARM.sol deleted file mode 100644 index 027448ff..00000000 --- a/src/contracts/OethARM.sol +++ /dev/null @@ -1,35 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.23; - -import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; - -import {AbstractARM} from "./AbstractARM.sol"; -import {PeggedARM} from "./PeggedARM.sol"; -import {OwnerLP} from "./OwnerLP.sol"; -import {OethLiquidityManager} from "./OethLiquidityManager.sol"; - -/** - * @title Origin Ether (OETH) Automated Redemption Manager (ARM) - * @author Origin Protocol Inc - */ -contract OethARM is Initializable, OwnerLP, PeggedARM, OethLiquidityManager { - /// @param _oeth The address of the OETH token that is being swapped into this contract. - /// @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, _weth, 10 minutes, 0, 0) - PeggedARM(false) - OethLiquidityManager(_oeth, _oethVault) - {} - - /// @notice Initialize the contract. - /// @param _operator The address of the account that can request and claim OETH withdrawals from the OETH Vault. - function initialize(address _operator) external initializer { - _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/OethLiquidityManager.sol b/src/contracts/OethLiquidityManager.sol deleted file mode 100644 index 21517ca8..00000000 --- a/src/contracts/OethLiquidityManager.sol +++ /dev/null @@ -1,62 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.23; - -import {OwnableOperable} from "./OwnableOperable.sol"; -import {IERC20, IOriginVault} from "./Interfaces.sol"; - -/** - * @title Manages OETH liquidity against the OETH Vault. - * @author Origin Protocol Inc - */ -contract OethLiquidityManager is OwnableOperable { - address public immutable oeth; - address public immutable oethVault; - - /// @param _oeth The address of the OETH token. - /// @param _oethVault The address of the OETH Vault proxy. - constructor(address _oeth, address _oethVault) { - oeth = _oeth; - oethVault = _oethVault; - } - - /** - * @notice Approve the OETH Vault to transfer OETH from this cont4act. - */ - function approvals() external onlyOwner { - _approvals(); - } - - function _approvals() internal { - IERC20(oeth).approve(oethVault, type(uint256).max); - } - - /** - * @notice Request withdrawal of WETH from the OETH Vault. - * @param amount The amount of OETH to burn and WETH to withdraw. - */ - function requestWithdrawal(uint256 amount) - external - onlyOperatorOrOwner - returns (uint256 requestId, uint256 queued) - { - return IOriginVault(oethVault).requestWithdrawal(amount); - } - - /** - * @notice Claim previously requested withdrawal of WETH from the OETH Vault. - * The Vault's claimable WETH needs to be greater than or equal to the queued amount of the request. - * @param requestId The ID of the OETH Vault's withdrawal request. - */ - function claimWithdrawal(uint256 requestId) external onlyOperatorOrOwner { - IOriginVault(oethVault).claimWithdrawal(requestId); - } - - /** - * @notice Claim multiple previously requested withdrawals of WETH from the OETH Vault. - * The Vault's claimable WETH needs to be greater than or equal to the queued amount of the request. - * @param requestIds List of request IDs from the OETH Vault's withdrawal requests. - */ - function claimWithdrawals(uint256[] memory requestIds) external onlyOperatorOrOwner { - IOriginVault(oethVault).claimWithdrawals(requestIds); - } -} diff --git a/src/contracts/OwnerLP.sol b/src/contracts/OwnerLP.sol deleted file mode 100644 index 6778172a..00000000 --- a/src/contracts/OwnerLP.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {Ownable} from "./Ownable.sol"; -import {IERC20} from "./Interfaces.sol"; - -abstract contract OwnerLP is Ownable { - /** - * @notice Owner can transfer out any ERC20 token. - */ - function transferToken(address token, address to, uint256 amount) external onlyOwner { - IERC20(token).transfer(to, amount); - } -} diff --git a/src/contracts/PeggedARM.sol b/src/contracts/PeggedARM.sol deleted file mode 100644 index 9a2f59ae..00000000 --- a/src/contracts/PeggedARM.sol +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-License-Identifier: BUSL-1.1 -pragma solidity ^0.8.23; - -import {AbstractARM} from "./AbstractARM.sol"; -import {IERC20} from "./Interfaces.sol"; - -abstract contract PeggedARM is AbstractARM { - /// @notice If true, the ARM contract can swap in both directions between token0 and token1. - bool public immutable bothDirections; - - constructor(bool _bothDirections) { - bothDirections = _bothDirections; - } - - function _swapExactTokensForTokens(IERC20 inToken, IERC20 outToken, uint256 amountIn, address to) - internal - override - returns (uint256 amountOut) - { - return _swap(inToken, outToken, amountIn, to); - } - - function _swapTokensForExactTokens(IERC20 inToken, IERC20 outToken, uint256 amountOut, address to) - internal - override - returns (uint256 amountIn) - { - return _swap(inToken, outToken, amountOut, to); - } - - function _swap(IERC20 inToken, IERC20 outToken, uint256 amount, address to) internal returns (uint256) { - if (bothDirections) { - require( - inToken == token0 && outToken == token1 || inToken == token1 && outToken == token0, "ARM: Invalid swap" - ); - } else { - require(inToken == token0 && outToken == token1, "ARM: Invalid swap"); - } - - // Transfer the input tokens from the caller to this ARM contract - require(inToken.transferFrom(msg.sender, address(this), amount), "failed transfer in"); - - // Transfer the same amount of output tokens to the recipient - require(outToken.transfer(to, amount), "failed transfer out"); - - // 1:1 swaps so the exact amount is returned as the calculated amount - return amount; - } -} diff --git a/src/contracts/utils/Addresses.sol b/src/contracts/utils/Addresses.sol index f7f24a71..8c9f4660 100644 --- a/src/contracts/utils/Addresses.sol +++ b/src/contracts/utils/Addresses.sol @@ -30,6 +30,7 @@ library Mainnet { address public constant EETH = 0x35fA164735182de50811E8e2E824cFb9B6118ac2; address public constant WEETH = 0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee; address public constant STETH = 0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84; + address public constant WOETH = 0xDcEe70654261AF21C44c093C300eD3Bb97b78192; address public constant WSTETH = 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0; address public constant MORPHO = 0x58D97B57BB95320F9a05dC918Aef65434969c2B2; diff --git a/test/Base.sol b/test/Base.sol index 234a59f3..86cf685d 100644 --- a/test/Base.sol +++ b/test/Base.sol @@ -6,7 +6,6 @@ import {Test} from "forge-std/Test.sol"; // Contracts import {Proxy} from "contracts/Proxy.sol"; -import {OethARM} from "contracts/OethARM.sol"; import {LidoARM} from "contracts/LidoARM.sol"; import {EtherFiARM} from "contracts/EtherFiARM.sol"; import {OriginARM} from "contracts/OriginARM.sol"; @@ -37,14 +36,12 @@ abstract contract Base_Test_ is Test { ////////////////////////////////////////////////////// /// --- CONTRACTS ////////////////////////////////////////////////////// - Proxy public proxy; Proxy public lpcProxy; Proxy public lidoProxy; Proxy public etherfiProxy; Proxy public originARMProxy; Proxy public harvesterProxy; Proxy public morphoMarketProxy; - OethARM public oethARM; LidoARM public lidoARM; EtherFiARM public etherfiARM; SonicHarvester public harvester; @@ -105,11 +102,9 @@ abstract contract Base_Test_ is Test { /// @notice Better if called once all contract have been depoyed. function labelAll() public virtual { // Contracts - _labelNotNull(address(proxy), "DEFAULT PROXY"); _labelNotNull(address(lpcProxy), "LPC PROXY"); _labelNotNull(address(lidoProxy), "LIDO ARM PROXY"); _labelNotNull(address(etherfiProxy), "ETHERFI ARM PROXY"); - _labelNotNull(address(oethARM), "OETH ARM"); _labelNotNull(address(lidoARM), "LIDO ARM"); _labelNotNull(address(originARM), "ORIGIN ARM"); _labelNotNull(address(capManager), "CAP MANAGER"); diff --git a/test/fork/EtherFiARM/RequestWithdraw.t.sol b/test/fork/EtherFiARM/RequestWithdraw.t.sol index 2e761d84..f1c47b85 100644 --- a/test/fork/EtherFiARM/RequestWithdraw.t.sol +++ b/test/fork/EtherFiARM/RequestWithdraw.t.sol @@ -2,7 +2,6 @@ pragma solidity 0.8.23; // Test -import {Mainnet} from "src/contracts/utils/Addresses.sol"; import {Fork_Shared_Test} from "test/fork/EtherFiARM/shared/Shared.sol"; contract Fork_Concrete_EtherFiARM_RequestWithdraw_Test_ is Fork_Shared_Test { diff --git a/test/fork/Harvester/Swap.sol b/test/fork/Harvester/Swap.sol index eb833ac1..4bba23f8 100644 --- a/test/fork/Harvester/Swap.sol +++ b/test/fork/Harvester/Swap.sol @@ -5,7 +5,6 @@ import {Sonic} from "contracts/utils/Addresses.sol"; import {SonicHarvester} from "contracts/SonicHarvester.sol"; import {Fork_Shared_Test} from "test/fork/Harvester/shared/Shared.sol"; -import {console} from "forge-std/console.sol"; contract Fork_Concrete_Harvester_Swap_Test_ is Fork_Shared_Test { address public constant OS_WHALE = 0x9F0dF7799f6FDAd409300080cfF680f5A23df4b1; diff --git a/test/fork/LidoFixedPriceMultiLpARM/ClaimRedeem.t.sol b/test/fork/LidoARM/ClaimRedeem.t.sol similarity index 100% rename from test/fork/LidoFixedPriceMultiLpARM/ClaimRedeem.t.sol rename to test/fork/LidoARM/ClaimRedeem.t.sol diff --git a/test/fork/LidoFixedPriceMultiLpARM/ClaimStETHWithdrawalForWETH.t.sol b/test/fork/LidoARM/ClaimStETHWithdrawalForWETH.t.sol similarity index 96% rename from test/fork/LidoFixedPriceMultiLpARM/ClaimStETHWithdrawalForWETH.t.sol rename to test/fork/LidoARM/ClaimStETHWithdrawalForWETH.t.sol index 5ea698e8..00dff276 100644 --- a/test/fork/LidoFixedPriceMultiLpARM/ClaimStETHWithdrawalForWETH.t.sol +++ b/test/fork/LidoARM/ClaimStETHWithdrawalForWETH.t.sol @@ -5,11 +5,11 @@ pragma solidity 0.8.23; import {Fork_Shared_Test_} from "test/fork/shared/Shared.sol"; // Contracts -import {IERC20, IStETHWithdrawal} from "contracts/Interfaces.sol"; +import {IStETHWithdrawal} from "contracts/Interfaces.sol"; import {LidoARM} from "contracts/LidoARM.sol"; import {Mainnet} from "contracts/utils/Addresses.sol"; -contract Fork_Concrete_LidoARM_RequestLidoWithdrawals_Test_ is Fork_Shared_Test_ { +contract Fork_Concrete_LidoARM_ClaimLidoWithdrawals_Test_ is Fork_Shared_Test_ { uint256[] amounts0; uint256[] amounts1; uint256[] amounts2; diff --git a/test/fork/LidoFixedPriceMultiLpARM/CollectFees.t.sol b/test/fork/LidoARM/CollectFees.t.sol similarity index 100% rename from test/fork/LidoFixedPriceMultiLpARM/CollectFees.t.sol rename to test/fork/LidoARM/CollectFees.t.sol diff --git a/test/fork/LidoFixedPriceMultiLpARM/Constructor.t.sol b/test/fork/LidoARM/Constructor.t.sol similarity index 95% rename from test/fork/LidoFixedPriceMultiLpARM/Constructor.t.sol rename to test/fork/LidoARM/Constructor.t.sol index 03462efb..934d62df 100644 --- a/test/fork/LidoFixedPriceMultiLpARM/Constructor.t.sol +++ b/test/fork/LidoARM/Constructor.t.sol @@ -2,6 +2,7 @@ pragma solidity 0.8.23; // Test imports +import {Mainnet} from "src/contracts/utils/Addresses.sol"; import {Fork_Shared_Test_} from "test/fork/shared/Shared.sol"; contract Fork_Concrete_LidoARM_Constructor_Test is Fork_Shared_Test_ { diff --git a/test/fork/LidoFixedPriceMultiLpARM/Deposit.t.sol b/test/fork/LidoARM/Deposit.t.sol similarity index 100% rename from test/fork/LidoFixedPriceMultiLpARM/Deposit.t.sol rename to test/fork/LidoARM/Deposit.t.sol diff --git a/test/fork/LidoARM/Proxy.t.sol b/test/fork/LidoARM/Proxy.t.sol new file mode 100644 index 00000000..6c238c8e --- /dev/null +++ b/test/fork/LidoARM/Proxy.t.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.23; + +// Contracts +import {LidoARM} from "contracts/LidoARM.sol"; + +// Test imports +import {Fork_Shared_Test_} from "test/fork/shared/Shared.sol"; + +// Utils +import {Mainnet} from "contracts/utils/Addresses.sol"; + +/// @notice The purpose of this contract is to test the `Proxy` contract. +contract Fork_Concrete_LidoARM_Proxy_Test_ is Fork_Shared_Test_ { + ////////////////////////////////////////////////////// + /// --- SETUP + ////////////////////////////////////////////////////// + function setUp() public virtual override { + super.setUp(); + lidoProxy.setOwner(Mainnet.TIMELOCK); + } + + ////////////////////////////////////////////////////// + /// --- REVERTING TESTS + ////////////////////////////////////////////////////// + function test_RevertWhen_UnauthorizedAccess() public { + vm.startPrank(address(0x123)); + vm.expectRevert("ARM: Only owner can call this function."); + lidoProxy.setOwner(deployer); + + vm.expectRevert("ARM: Only owner can call this function."); + lidoProxy.initialize(address(this), address(this), ""); + + vm.expectRevert("ARM: Only owner can call this function."); + lidoProxy.upgradeTo(address(this)); + + vm.expectRevert("ARM: Only owner can call this function."); + lidoProxy.upgradeToAndCall(address(this), ""); + vm.stopPrank(); + } + + ////////////////////////////////////////////////////// + /// --- PASSING TESTS + ////////////////////////////////////////////////////// + function test_Upgrade() public asLidoARMOwner { + address owner = Mainnet.TIMELOCK; + + // Deploy new implementation + LidoARM newImplementation = new LidoARM(Mainnet.STETH, Mainnet.WETH, Mainnet.OETH_VAULT, 10 minutes, 0, 0); + lidoProxy.upgradeTo(address(newImplementation)); + assertEq(lidoProxy.implementation(), address(newImplementation)); + + // Ensure ownership was preserved. + assertEq(lidoProxy.owner(), owner); + assertEq(lidoARM.owner(), owner); + + // Ensure the storage was preserved through the upgrade. + assertEq(address(lidoARM.token0()), Mainnet.WETH); + assertEq(address(lidoARM.token1()), Mainnet.STETH); + } + + function test_UpgradeAndCall() public asLidoARMOwner { + address owner = Mainnet.TIMELOCK; + + // Deploy new implementation + LidoARM newImplementation = new LidoARM(Mainnet.STETH, Mainnet.WETH, Mainnet.OETH_VAULT, 10 minutes, 0, 0); + bytes memory data = abi.encodeWithSignature("setOperator(address)", address(0x123)); + + lidoProxy.upgradeToAndCall(address(newImplementation), data); + assertEq(lidoProxy.implementation(), address(newImplementation)); + + // Ensure ownership was preserved. + assertEq(lidoProxy.owner(), owner); + assertEq(lidoARM.owner(), owner); + + // Ensure the post upgrade code was run + assertEq(lidoARM.operator(), address(0x123)); + } +} diff --git a/test/fork/LidoFixedPriceMultiLpARM/RequestRedeem.t.sol b/test/fork/LidoARM/RequestRedeem.t.sol similarity index 100% rename from test/fork/LidoFixedPriceMultiLpARM/RequestRedeem.t.sol rename to test/fork/LidoARM/RequestRedeem.t.sol diff --git a/test/fork/LidoFixedPriceMultiLpARM/RequestStETHWithdrawalForETH.t.sol b/test/fork/LidoARM/RequestStETHWithdrawalForETH.t.sol similarity index 100% rename from test/fork/LidoFixedPriceMultiLpARM/RequestStETHWithdrawalForETH.t.sol rename to test/fork/LidoARM/RequestStETHWithdrawalForETH.t.sol diff --git a/test/fork/LidoFixedPriceMultiLpARM/SetCrossPrice.t.sol b/test/fork/LidoARM/SetCrossPrice.t.sol similarity index 100% rename from test/fork/LidoFixedPriceMultiLpARM/SetCrossPrice.t.sol rename to test/fork/LidoARM/SetCrossPrice.t.sol diff --git a/test/fork/LidoFixedPriceMultiLpARM/Setters.t.sol b/test/fork/LidoARM/Setters.t.sol similarity index 99% rename from test/fork/LidoFixedPriceMultiLpARM/Setters.t.sol rename to test/fork/LidoARM/Setters.t.sol index 9de35b6f..9b625227 100644 --- a/test/fork/LidoFixedPriceMultiLpARM/Setters.t.sol +++ b/test/fork/LidoARM/Setters.t.sol @@ -5,11 +5,10 @@ pragma solidity 0.8.23; import {Fork_Shared_Test_} from "test/fork/shared/Shared.sol"; // Contracts -import {IERC20} from "contracts/Interfaces.sol"; import {AbstractARM} from "contracts/AbstractARM.sol"; import {CapManager} from "contracts/CapManager.sol"; -contract Fork_Concrete_lidoARM_Setters_Test_ is Fork_Shared_Test_ { +contract Fork_Concrete_LidoARM_Setters_Test_ is Fork_Shared_Test_ { address[] testProviders; ////////////////////////////////////////////////////// diff --git a/test/fork/LidoFixedPriceMultiLpARM/SwapExactTokensForTokens.t.sol b/test/fork/LidoARM/SwapExactTokensForTokens.t.sol similarity index 100% rename from test/fork/LidoFixedPriceMultiLpARM/SwapExactTokensForTokens.t.sol rename to test/fork/LidoARM/SwapExactTokensForTokens.t.sol diff --git a/test/fork/LidoFixedPriceMultiLpARM/SwapTokensForExactTokens.t.sol b/test/fork/LidoARM/SwapTokensForExactTokens.t.sol similarity index 100% rename from test/fork/LidoFixedPriceMultiLpARM/SwapTokensForExactTokens.t.sol rename to test/fork/LidoARM/SwapTokensForExactTokens.t.sol diff --git a/test/fork/LidoFixedPriceMultiLpARM/TotalAssets.t.sol b/test/fork/LidoARM/TotalAssets.t.sol similarity index 98% rename from test/fork/LidoFixedPriceMultiLpARM/TotalAssets.t.sol rename to test/fork/LidoARM/TotalAssets.t.sol index 84078e1b..f77ec6fb 100644 --- a/test/fork/LidoFixedPriceMultiLpARM/TotalAssets.t.sol +++ b/test/fork/LidoARM/TotalAssets.t.sol @@ -7,10 +7,6 @@ import {stdError} from "forge-std/StdError.sol"; // Test imports import {Fork_Shared_Test_} from "test/fork/shared/Shared.sol"; -// Contracts -import {IERC20} from "contracts/Interfaces.sol"; -import {AbstractARM} from "contracts/AbstractARM.sol"; - contract Fork_Concrete_LidoARM_TotalAssets_Test_ is Fork_Shared_Test_ { ////////////////////////////////////////////////////// /// --- SETUP diff --git a/test/fork/OethARM/Ownable.t.sol b/test/fork/OethARM/Ownable.t.sol deleted file mode 100644 index 02240846..00000000 --- a/test/fork/OethARM/Ownable.t.sol +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.23; - -// Test imports -import {Fork_Shared_Test_} from "test/fork/shared/Shared.sol"; - -// Utils -import {Mainnet} from "contracts/utils/Addresses.sol"; - -/// @notice The purpose of this contract is to test the `Ownable` contract. -contract Fork_Concrete_OethARM_Ownable_Test_ is Fork_Shared_Test_ { - ////////////////////////////////////////////////////// - /// --- REVERTING TESTS - ////////////////////////////////////////////////////// - function test_RevertWhen_SetOperator_Because_NotOwner() public { - vm.expectRevert("ARM: Only owner can call this function."); - vm.prank(alice); - oethARM.setOperator(deployer); - } - - function test_RevertWhen_SetOwner_Because_NotOwner() public { - vm.expectRevert("ARM: Only owner can call this function."); - vm.prank(alice); - oethARM.setOwner(deployer); - } - - ////////////////////////////////////////////////////// - /// --- PASSING TESTS - ////////////////////////////////////////////////////// - function test_SetOperator() public asOwner { - // Assertions before - assertEq(oethARM.operator(), operator); - - oethARM.setOperator(alice); - - // Assertions after - assertEq(oethARM.operator(), alice); - } - - function test_SetOwner() public asOwner { - // Assertions before - assertEq(oethARM.owner(), Mainnet.TIMELOCK); - - oethARM.setOwner(alice); - - // Assertions after - assertEq(oethARM.owner(), alice); - } -} diff --git a/test/fork/OethARM/Proxy.t.sol b/test/fork/OethARM/Proxy.t.sol deleted file mode 100644 index e55f2512..00000000 --- a/test/fork/OethARM/Proxy.t.sol +++ /dev/null @@ -1,69 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.23; - -// Contracts -import {OethARM} from "contracts/OethARM.sol"; - -// Test imports -import {Fork_Shared_Test_} from "test/fork/shared/Shared.sol"; - -// Utils -import {Mainnet} from "contracts/utils/Addresses.sol"; - -/// @notice The purpose of this contract is to test the `Proxy` contract. -contract Fork_Concrete_OethARM_Proxy_Test_ is Fork_Shared_Test_ { - ////////////////////////////////////////////////////// - /// --- REVERTING TESTS - ////////////////////////////////////////////////////// - function test_RevertWhen_UnauthorizedAccess() public { - vm.expectRevert("ARM: Only owner can call this function."); - proxy.setOwner(deployer); - - vm.expectRevert("ARM: Only owner can call this function."); - proxy.initialize(address(this), address(this), ""); - - vm.expectRevert("ARM: Only owner can call this function."); - proxy.upgradeTo(address(this)); - - vm.expectRevert("ARM: Only owner can call this function."); - proxy.upgradeToAndCall(address(this), ""); - } - - ////////////////////////////////////////////////////// - /// --- PASSING TESTS - ////////////////////////////////////////////////////// - function test_Upgrade() public asOwner { - address owner = Mainnet.TIMELOCK; - - // Deploy new implementation - OethARM newImplementation = new OethARM(Mainnet.OETH, Mainnet.WETH, Mainnet.OETH_VAULT); - proxy.upgradeTo(address(newImplementation)); - assertEq(proxy.implementation(), address(newImplementation)); - - // Ensure ownership was preserved. - assertEq(proxy.owner(), owner); - assertEq(oethARM.owner(), owner); - - // Ensure the storage was preserved through the upgrade. - assertEq(address(oethARM.token0()), Mainnet.OETH); - assertEq(address(oethARM.token1()), Mainnet.WETH); - } - - function test_UpgradeAndCall() public asOwner { - address owner = Mainnet.TIMELOCK; - - // Deploy new implementation - OethARM newImplementation = new OethARM(Mainnet.OETH, Mainnet.WETH, Mainnet.OETH_VAULT); - bytes memory data = abi.encodeWithSignature("setOperator(address)", address(0x123)); - - proxy.upgradeToAndCall(address(newImplementation), data); - assertEq(proxy.implementation(), address(newImplementation)); - - // Ensure ownership was preserved. - assertEq(proxy.owner(), owner); - assertEq(oethARM.owner(), owner); - - // Ensure the post upgrade code was run - assertEq(oethARM.operator(), address(0x123)); - } -} diff --git a/test/fork/OethARM/SwapExactTokensForTokens.t.sol b/test/fork/OethARM/SwapExactTokensForTokens.t.sol deleted file mode 100644 index 111afa2a..00000000 --- a/test/fork/OethARM/SwapExactTokensForTokens.t.sol +++ /dev/null @@ -1,132 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.23; - -// Test imports -import {Fork_Shared_Test_} from "test/fork/shared/Shared.sol"; - -// Interfaces -import {IERC20} from "contracts/Interfaces.sol"; - -/// @notice The purpose of this contract is to test the `swapExactTokensForTokens` function in the `OethARM` contract. -contract Fork_Concrete_OethARM_SwapExactTokensForTokens_Test_ is Fork_Shared_Test_ { - address[] path; - - ////////////////////////////////////////////////////// - /// --- SETUP - ////////////////////////////////////////////////////// - function setUp() public override { - super.setUp(); - - path = new address[](2); - path[0] = address(oeth); - path[1] = address(weth); - - // Deal tokens - deal(address(oeth), address(this), 100 ether); - deal(address(weth), address(oethARM), 100 ether); - deal(address(oeth), address(oethARM), 100 ether); - - // Approve tokens - oeth.approve(address(oethARM), type(uint256).max); - } - - ////////////////////////////////////////////////////// - /// --- REVERTING TESTS - ////////////////////////////////////////////////////// - function test_RevertWhen_SwapExactTokensForTokens_Simple_Because_InsufficientOutputAmount() public { - vm.expectRevert("ARM: Insufficient output amount"); - oethARM.swapExactTokensForTokens(oeth, weth, 10 ether, 11 ether, address(this)); - } - - function test_RevertWhen_SwapExactTokensForTokens_Simple_Because_InvalidSwap_TokenIn() public { - vm.expectRevert("ARM: Invalid swap"); - oethARM.swapExactTokensForTokens(weth, weth, 10 ether, 10 ether, address(this)); - } - - function test_RevertWhen_SwapExactTokensForTokens_Simple_Because_InvalidSwap_TokenOut() public { - vm.expectRevert("ARM: Invalid swap"); - oethARM.swapExactTokensForTokens(oeth, oeth, 10 ether, 10 ether, address(this)); - } - - function test_RevertWhen_SwapExactTokensForTokens_Complex_Because_InsuficientOutputAmount() public { - vm.expectRevert("ARM: Insufficient output amount"); - oethARM.swapExactTokensForTokens(10 ether, 11 ether, path, address(this), block.timestamp + 10); - } - - function test_RevertWhen_SwapExactTokensForTokens_Complex_Because_InvalidPathLength() public { - vm.expectRevert("ARM: Invalid path length"); - oethARM.swapExactTokensForTokens(10 ether, 10 ether, new address[](3), address(this), 0); - } - - function test_RevertWhen_SwapExactTokensForTokens_Complex_Because_DeadlineExpired() public { - vm.expectRevert("ARM: Deadline expired"); - oethARM.swapExactTokensForTokens(10 ether, 10 ether, path, address(this), 0); - } - - function test_RevertWhen_SwapExactTokensForTokens_Complex_Because_InvalidSwap_TokenIn() public { - path[0] = address(weth); - path[1] = address(weth); - vm.expectRevert("ARM: Invalid swap"); - oethARM.swapExactTokensForTokens(10 ether, 10 ether, path, address(this), block.timestamp + 10); - } - - function test_RevertWhen_SwapExactTokensForTokens_Complex_Because_InvalidSwap_TokenOut() public { - path[0] = address(oeth); - path[1] = address(oeth); - vm.expectRevert("ARM: Invalid swap"); - oethARM.swapExactTokensForTokens(10 ether, 10 ether, path, address(this), block.timestamp + 10); - } - - ////////////////////////////////////////////////////// - /// --- PASSING TESTS - ////////////////////////////////////////////////////// - function test_SwapExactTokensForTokens_Simple() public { - // Assertions before - assertEq(weth.balanceOf(address(this)), 0 ether, "WETH balance user"); - assertEq(oeth.balanceOf(address(this)), 100 ether, "OETH balance user"); - assertEq(weth.balanceOf(address(oethARM)), 100 ether, "OETH balance ARM"); - assertEq(weth.balanceOf(address(oethARM)), 100 ether, "WETH balance ARM"); - - // Expected events - vm.expectEmit({emitter: address(oeth)}); - emit IERC20.Transfer(address(this), address(oethARM), 10 ether); - vm.expectEmit({emitter: address(weth)}); - emit IERC20.Transfer(address(oethARM), address(this), 10 ether); - // Main call - oethARM.swapExactTokensForTokens(oeth, weth, 10 ether, 10 ether, address(this)); - - // Assertions after - assertEq(weth.balanceOf(address(this)), 10 ether, "WETH balance user"); - assertEq(oeth.balanceOf(address(this)), 90 ether, "OETH balance"); - assertEq(weth.balanceOf(address(oethARM)), 90 ether, "WETH balance ARM"); - assertEq(oeth.balanceOf(address(oethARM)), 110 ether, "OETH balance ARM"); - } - - function test_SwapExactTokensForTokens_Complex() public { - // Assertions before - assertEq(weth.balanceOf(address(this)), 0 ether, "WETH balance user"); - assertEq(oeth.balanceOf(address(this)), 100 ether, "OETH balance user"); - assertEq(weth.balanceOf(address(oethARM)), 100 ether, "OETH balance ARM"); - assertEq(weth.balanceOf(address(oethARM)), 100 ether, "WETH balance ARM"); - - path[0] = address(oeth); - path[1] = address(weth); - - // Expected events - vm.expectEmit({emitter: address(oeth)}); - emit IERC20.Transfer(address(this), address(oethARM), 10 ether); - vm.expectEmit({emitter: address(weth)}); - emit IERC20.Transfer(address(oethARM), address(this), 10 ether); - // Main call - uint256[] memory amounts = - oethARM.swapExactTokensForTokens(10 ether, 10 ether, path, address(this), block.timestamp + 1000); - - // Assertions after - assertEq(amounts[0], 10 ether, "Amounts[0]"); - assertEq(amounts[1], 10 ether, "Amounts[1]"); - assertEq(weth.balanceOf(address(this)), 10 ether, "WETH balance user"); - assertEq(oeth.balanceOf(address(this)), 90 ether, "OETH balance"); - assertEq(weth.balanceOf(address(oethARM)), 90 ether, "WETH balance ARM"); - assertEq(oeth.balanceOf(address(oethARM)), 110 ether, "OETH balance ARM"); - } -} diff --git a/test/fork/OethARM/SwapTokensForExactTokens.t.sol b/test/fork/OethARM/SwapTokensForExactTokens.t.sol deleted file mode 100644 index 3e012379..00000000 --- a/test/fork/OethARM/SwapTokensForExactTokens.t.sol +++ /dev/null @@ -1,131 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.23; - -// Test imports -import {Fork_Shared_Test_} from "test/fork/shared/Shared.sol"; - -// Interfaces -import {IERC20} from "contracts/Interfaces.sol"; - -/// @notice The purpose of this contract is to test the `swapTokensForExactTokens` function in the `OethARM` contract. -contract Fork_Concrete_OethARM_SwapTokensForExactTokens_Test_ is Fork_Shared_Test_ { - address[] path; - - ////////////////////////////////////////////////////// - /// --- SETUP - ////////////////////////////////////////////////////// - function setUp() public override { - super.setUp(); - - path = new address[](2); - path[0] = address(oeth); - path[1] = address(weth); - - // Deal tokens - deal(address(oeth), address(this), 100 ether); - deal(address(weth), address(oethARM), 100 ether); - deal(address(oeth), address(oethARM), 100 ether); - - // Approve tokens - oeth.approve(address(oethARM), type(uint256).max); - } - - ////////////////////////////////////////////////////// - /// --- REVERTING TESTS - ////////////////////////////////////////////////////// - function test_RevertWhen_SwapTokensForExactTokens_Simple_Because_InsufficientOutputAmount() public { - vm.expectRevert("ARM: Excess input amount"); - oethARM.swapTokensForExactTokens(oeth, weth, 10 ether, 9 ether, address(this)); - } - - function test_RevertWhen_SwapTokensForExactTokens_Simple_Because_InvalidSwap_TokenIn() public { - vm.expectRevert("ARM: Invalid swap"); - oethARM.swapTokensForExactTokens(weth, weth, 10 ether, 10 ether, address(this)); - } - - function test_RevertWhen_SwapTokensForExactTokens_Simple_Because_InvalidSwap_TokenOut() public { - vm.expectRevert("ARM: Invalid swap"); - oethARM.swapTokensForExactTokens(oeth, oeth, 10 ether, 10 ether, address(this)); - } - - function test_RevertWhen_SwapTokensForExactTokens_Complex_Because_InsufficientOutputAmount() public { - vm.expectRevert("ARM: Excess input amount"); - oethARM.swapTokensForExactTokens(10 ether, 9 ether, path, address(this), block.timestamp + 10); - } - - function test_RevertWhen_SwapTokensForExactTokens_Complex_Because_InvalidPathLength() public { - vm.expectRevert("ARM: Invalid path length"); - oethARM.swapTokensForExactTokens(10 ether, 10 ether, new address[](3), address(this), block.timestamp + 10); - } - - function test_RevertWhen_SwapTokensForExactTokens_Complex_Because_DeadlineExpired() public { - vm.expectRevert("ARM: Deadline expired"); - oethARM.swapTokensForExactTokens(10 ether, 10 ether, path, address(this), block.timestamp - 1); - } - - function test_RevertWhen_SwapTokensForExactTokens_Complex_Because_InvalidSwap_TokenIn() public { - path[0] = address(weth); - path[1] = address(weth); - vm.expectRevert("ARM: Invalid swap"); - oethARM.swapTokensForExactTokens(10 ether, 10 ether, path, address(this), block.timestamp + 10); - } - - function test_RevertWhen_SwapTokensForExactTokens_Complex_Because_InvalidSwap_TokenOut() public { - path[0] = address(oeth); - path[1] = address(oeth); - vm.expectRevert("ARM: Invalid swap"); - oethARM.swapTokensForExactTokens(10 ether, 10 ether, path, address(this), block.timestamp + 10); - } - - ////////////////////////////////////////////////////// - /// --- PASSING TESTS - ////////////////////////////////////////////////////// - function test_SwapTokensForExactTokens_Simple() public { - // Assertions before - assertEq(weth.balanceOf(address(this)), 0 ether, "WETH balance user"); - assertEq(oeth.balanceOf(address(this)), 100 ether, "OETH balance user"); - assertEq(weth.balanceOf(address(oethARM)), 100 ether, "OETH balance ARM"); - assertEq(weth.balanceOf(address(oethARM)), 100 ether, "WETH balance ARM"); - - // Expected events - vm.expectEmit({emitter: address(oeth)}); - emit IERC20.Transfer(address(this), address(oethARM), 10 ether); - vm.expectEmit({emitter: address(weth)}); - emit IERC20.Transfer(address(oethARM), address(this), 10 ether); - // Main call - oethARM.swapTokensForExactTokens(oeth, weth, 10 ether, 10 ether, address(this)); - - // Assertions after - assertEq(weth.balanceOf(address(this)), 10 ether, "WETH balance user"); - assertEq(oeth.balanceOf(address(this)), 90 ether, "OETH balance"); - assertEq(weth.balanceOf(address(oethARM)), 90 ether, "WETH balance ARM"); - assertEq(oeth.balanceOf(address(oethARM)), 110 ether, "OETH balance ARM"); - } - - function test_SwapTokensForExactTokens_Complex() public { - // Assertions before - assertEq(weth.balanceOf(address(this)), 0 ether, "WETH balance user"); - assertEq(oeth.balanceOf(address(this)), 100 ether, "OETH balance user"); - assertEq(weth.balanceOf(address(oethARM)), 100 ether, "OETH balance ARM"); - assertEq(weth.balanceOf(address(oethARM)), 100 ether, "WETH balance ARM"); - - path[0] = address(oeth); - path[1] = address(weth); - // Expected events - vm.expectEmit({emitter: address(oeth)}); - emit IERC20.Transfer(address(this), address(oethARM), 10 ether); - vm.expectEmit({emitter: address(weth)}); - emit IERC20.Transfer(address(oethARM), address(this), 10 ether); - // Main call - uint256[] memory amounts = - oethARM.swapTokensForExactTokens(10 ether, 10 ether, path, address(this), block.timestamp + 1000); - - // Assertions after - assertEq(amounts[0], 10 ether, "Amounts[0]"); - assertEq(amounts[1], 10 ether, "Amounts[1]"); - assertEq(weth.balanceOf(address(this)), 10 ether, "WETH balance user"); - assertEq(oeth.balanceOf(address(this)), 90 ether, "OETH balance"); - assertEq(weth.balanceOf(address(oethARM)), 90 ether, "WETH balance ARM"); - assertEq(oeth.balanceOf(address(oethARM)), 110 ether, "OETH balance ARM"); - } -} diff --git a/test/fork/OethARM/Transfer.t.sol b/test/fork/OethARM/Transfer.t.sol deleted file mode 100644 index 90832e33..00000000 --- a/test/fork/OethARM/Transfer.t.sol +++ /dev/null @@ -1,57 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.23; - -// Test imports -import {Fork_Shared_Test_} from "test/fork/shared/Shared.sol"; - -// Interfaces -import {IERC20} from "contracts/Interfaces.sol"; - -/// @notice The purpose of this contract is to test the `transferToken` and `transferEth` functions in the `OethARM` contract. -contract Fork_Concrete_OethARM_Transfer_Test_ is Fork_Shared_Test_ { - bool public shouldRevertOnReceive; - - ////////////////////////////////////////////////////// - /// --- SETUP - ////////////////////////////////////////////////////// - - function setUp() public override { - super.setUp(); - - // Deal tokens - deal(address(oethARM), 100 ether); - deal(address(weth), address(oethARM), 100 ether); - } - - ////////////////////////////////////////////////////// - /// --- REVERTING TESTS - ////////////////////////////////////////////////////// - function test_RevertWhen_TransferToken_Because_NotOwner() public { - vm.expectRevert("ARM: Only owner can call this function."); - oethARM.transferToken(address(0), address(0), 0); - } - - ////////////////////////////////////////////////////// - /// --- PASSING TESTS - ////////////////////////////////////////////////////// - function test_TransferToken() public asOwner { - // Assertions before - assertEq(weth.balanceOf(address(this)), 0); - assertEq(weth.balanceOf(address(oethARM)), 100 ether); - - vm.expectEmit({emitter: address(weth)}); - emit IERC20.Transfer(address(oethARM), address(this), 10 ether); - oethARM.transferToken(address(weth), address(this), 10 ether); - - // Assertions after - assertEq(weth.balanceOf(address(this)), 10 ether); - assertEq(weth.balanceOf(address(oethARM)), 90 ether); - } - - ////////////////////////////////////////////////////// - /// --- RECEIVE - ////////////////////////////////////////////////////// - receive() external payable { - if (shouldRevertOnReceive) revert(); - } -} diff --git a/test/fork/OethARM/Withdraw.t.sol b/test/fork/OethARM/Withdraw.t.sol deleted file mode 100644 index f63728ee..00000000 --- a/test/fork/OethARM/Withdraw.t.sol +++ /dev/null @@ -1,105 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.23; - -// Test imports -import {Fork_Shared_Test_} from "test/fork/shared/Shared.sol"; - -// Interfaces -import {IERC20} from "contracts/Interfaces.sol"; - -/// @notice The purpose of this contract is to test the `requestWithdrawal`, -/// `claimWithdrawal` and `claimWithdrawals` functions in the `OethARM` contract. -contract Fork_Concrete_OethARM_Withdraw_Test_ is Fork_Shared_Test_ { - ////////////////////////////////////////////////////// - /// --- SETUP - ////////////////////////////////////////////////////// - function setUp() public override { - super.setUp(); - - // Deal tokens - deal(address(oeth), address(oethARM), 10 ether); - deal(address(weth), address(vault), 10 ether); - - // Remove solvency check - vm.prank(vault.governor()); - vault.setMaxSupplyDiff(0); - } - - ////////////////////////////////////////////////////// - /// --- REVERTING TESTS - ////////////////////////////////////////////////////// - function test_RevertWhen_RequestWithdraw() public { - vm.expectRevert("ARM: Only operator or owner can call this function."); - oethARM.requestWithdrawal(1 ether); - } - - function test_RevertWhen_ClaimWithdraw() public { - vm.expectRevert("ARM: Only operator or owner can call this function."); - oethARM.claimWithdrawal(0); - } - - function test_RevertWhen_ClaimWithdraws() public { - vm.expectRevert("ARM: Only operator or owner can call this function."); - oethARM.claimWithdrawals(new uint256[](0)); - } - - ////////////////////////////////////////////////////// - /// --- PASSING TESTS - ////////////////////////////////////////////////////// - function test_RequestWithdraw() public asOwner mockCallDripperCollect { - (uint128 queuedBefore,,, uint128 nextWithdrawalIndex) = vault.withdrawalQueueMetadata(); - vm.expectEmit({emitter: address(oeth)}); - emit IERC20.Transfer(address(oethARM), address(0), 1 ether); - (uint256 requestId, uint256 queued) = oethARM.requestWithdrawal(1 ether); - - // Assertions after - assertEq(requestId, nextWithdrawalIndex, "Request ID"); - assertEq(queued, queuedBefore + 1 ether, "Queued amount should be 1 ether"); - assertEq(oeth.balanceOf(address(oethARM)), 9 ether, "OETH balance should be 99 ether"); - } - - function test_ClaimWithdraw_() public asOwner mockCallDripperCollect { - // First request withdrawal - (uint256 requestId,) = oethARM.requestWithdrawal(1 ether); - - // Add more liquidity to facilitate withdrawal - (uint128 queued,, uint128 claimed,) = vault.withdrawalQueueMetadata(); - deal(address(weth), address(vault), queued - claimed + 1 ether); - - // Add liquidity to the withdrawal queue - vault.addWithdrawalQueueLiquidity(); - - // Skip delay - skip(10 minutes); - - // Then claim withdrawal - oethARM.claimWithdrawal(requestId); - - // Assertions after - assertEq(weth.balanceOf(address(oethARM)), 1 ether, "WETH balance should be 1 ether"); - } - - function test_ClaimWithdraws() public asOwner mockCallDripperCollect { - (,,, uint128 nextWithdrawalIndex) = vault.withdrawalQueueMetadata(); - - // First request withdrawal - oethARM.requestWithdrawal(1 ether); - oethARM.requestWithdrawal(1 ether); - - // Add more liquidity to facilitate withdrawal - (uint128 queued,, uint128 claimed,) = vault.withdrawalQueueMetadata(); - deal(address(weth), address(vault), queued - claimed + 2 ether); - - // Skip withdrawal queue delay - skip(10 minutes); - - uint256[] memory requestIds = new uint256[](2); - requestIds[0] = nextWithdrawalIndex; - requestIds[1] = nextWithdrawalIndex + 1; - // Then claim withdrawal - oethARM.claimWithdrawals(requestIds); - - // Assertions after - assertEq(weth.balanceOf(address(oethARM)), 2 ether, "WETH balance should be 1 ether"); - } -} diff --git a/test/fork/shared/Shared.sol b/test/fork/shared/Shared.sol index b583f31a..707dde99 100644 --- a/test/fork/shared/Shared.sol +++ b/test/fork/shared/Shared.sol @@ -9,7 +9,6 @@ import {Modifiers} from "test/fork/utils/Modifiers.sol"; // Contracts import {Proxy} from "contracts/Proxy.sol"; -import {OethARM} from "contracts/OethARM.sol"; import {LidoARM} from "contracts/LidoARM.sol"; import {CapManager} from "contracts/CapManager.sol"; import {ZapperLidoARM} from "contracts/ZapperLidoARM.sol"; @@ -20,7 +19,6 @@ import {IOriginVault} from "contracts/Interfaces.sol"; // Utils import {Mainnet} from "contracts/utils/Addresses.sol"; -import {AddressResolver} from "contracts/utils/Addresses.sol"; /// @notice This contract should inherit (directly or indirectly) from `Base_Test_`. /// It should be used to setup the FORK test ONLY! @@ -106,27 +104,14 @@ abstract contract Fork_Shared_Test_ is Modifiers { function _deployContracts() internal { // --- Deploy all proxies --- - proxy = new Proxy(); lpcProxy = new Proxy(); lidoProxy = new Proxy(); - - // --- Deploy OethARM implementation --- - // Deploy OethARM implementation. - address implementation = address(new OethARM(address(oeth), address(weth), address(vault))); - vm.label(implementation, "OETH ARM IMPLEMENTATION"); - - // Initialize Proxy with OethARM implementation. - bytes memory data = abi.encodeWithSignature("initialize(address)", operator); - proxy.initialize(implementation, governor, data); - - // Set the Proxy as the OethARM. - oethARM = OethARM(address(proxy)); - // --- Deploy CapManager implementation --- // Deploy CapManager implementation. CapManager capManagerImpl = new CapManager(address(lidoProxy)); // Initialize Proxy with CapManager implementation. + bytes memory data = abi.encodeWithSignature("initialize(address)", operator); lpcProxy.initialize(address(capManagerImpl), address(this), data); // Set the Proxy as the CapManager. @@ -176,8 +161,6 @@ abstract contract Fork_Shared_Test_ is Modifiers { vm.label(address(steth), "stETH"); vm.label(address(badToken), "BAD TOKEN"); vm.label(address(vault), "OETH VAULT"); - vm.label(address(oethARM), "OETH ARM"); - vm.label(address(proxy), "OETH ARM PROXY"); vm.label(address(lidoARM), "LIDO ARM"); vm.label(address(lidoProxy), "LIDO ARM PROXY"); vm.label(address(capManager), "LIQUIDITY PROVIDER CONTROLLER"); diff --git a/test/fork/utils/Modifiers.sol b/test/fork/utils/Modifiers.sol index f55933d4..753aac74 100644 --- a/test/fork/utils/Modifiers.sol +++ b/test/fork/utils/Modifiers.sol @@ -21,13 +21,6 @@ abstract contract Modifiers is Helpers { vm.stopPrank(); } - /// @notice Impersonate the owner of the OethARM contract. - modifier asOwner() { - vm.startPrank(oethARM.owner()); - _; - vm.stopPrank(); - } - /// @notice Impersonate the governor of the vault. modifier asGovernor() { vm.startPrank(vault.governor()); diff --git a/test/invariants/LidoARM/FuzzerFoundry.sol b/test/invariants/LidoARM/FuzzerFoundry.sol index df7815ca..f5c6a1a4 100644 --- a/test/invariants/LidoARM/FuzzerFoundry.sol +++ b/test/invariants/LidoARM/FuzzerFoundry.sol @@ -4,7 +4,7 @@ pragma solidity 0.8.23; // Test imports import {TargetFunction} from "test/invariants/LidoARM/TargetFunction.sol"; -contract FuzzerFoundry_OethARM is TargetFunction { +contract FuzzerFoundry_LidoARM is TargetFunction { uint256 private constant NUM_LPS = 4; uint256 private constant NUM_SWAPS = 3; uint256 private constant MAX_WETH_PER_USERS = 1_000_000 ether; diff --git a/test/invariants/LidoARM/Unit.sol b/test/invariants/LidoARM/Unit.sol index 65497401..ba3cbcbd 100644 --- a/test/invariants/LidoARM/Unit.sol +++ b/test/invariants/LidoARM/Unit.sol @@ -2,13 +2,13 @@ pragma solidity 0.8.23; import {Test} from "forge-std/Test.sol"; -import {FuzzerFoundry_OethARM} from "test/invariants/LidoARM/FuzzerFoundry.sol"; +import {FuzzerFoundry_LidoARM} from "test/invariants/LidoARM/FuzzerFoundry.sol"; contract Unit is Test { - FuzzerFoundry_OethARM f; + FuzzerFoundry_LidoARM f; function setUp() public { - f = new FuzzerFoundry_OethARM(); + f = new FuzzerFoundry_LidoARM(); f.setUp(); } diff --git a/test/smoke/EtherFiARMSmokeTest.t.sol b/test/smoke/EtherFiARMSmokeTest.t.sol index b5756604..d9c4b0a8 100644 --- a/test/smoke/EtherFiARMSmokeTest.t.sol +++ b/test/smoke/EtherFiARMSmokeTest.t.sol @@ -1,11 +1,9 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.23; -import {Test, console2} from "forge-std/Test.sol"; - import {AbstractSmokeTest} from "./AbstractSmokeTest.sol"; -import {IERC20, IEETHWithdrawal, IEETHWithdrawalNFT} from "contracts/Interfaces.sol"; +import {IERC20, IEETHWithdrawalNFT} from "contracts/Interfaces.sol"; import {EtherFiARM} from "contracts/EtherFiARM.sol"; import {CapManager} from "contracts/CapManager.sol"; import {Proxy} from "contracts/Proxy.sol"; diff --git a/test/smoke/LidoARMSmokeTest.t.sol b/test/smoke/LidoARMSmokeTest.t.sol index 62c83c01..45a9dfda 100644 --- a/test/smoke/LidoARMSmokeTest.t.sol +++ b/test/smoke/LidoARMSmokeTest.t.sol @@ -1,8 +1,6 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.23; -import {Test, console2} from "forge-std/Test.sol"; - import {AbstractSmokeTest} from "./AbstractSmokeTest.sol"; import {IERC20, IStETHWithdrawal} from "contracts/Interfaces.sol"; diff --git a/test/smoke/OethARMSmokeTest.t.sol b/test/smoke/OethARMSmokeTest.t.sol index ed15f0d6..dcc19b2d 100644 --- a/test/smoke/OethARMSmokeTest.t.sol +++ b/test/smoke/OethARMSmokeTest.t.sol @@ -1,22 +1,22 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.23; -import {Test, console2} from "forge-std/Test.sol"; +import {Test} from "forge-std/Test.sol"; import {AbstractSmokeTest} from "./AbstractSmokeTest.sol"; import {IERC20} from "contracts/Interfaces.sol"; -import {OethARM} from "contracts/OethARM.sol"; import {Proxy} from "contracts/Proxy.sol"; import {Mainnet} from "contracts/utils/Addresses.sol"; +import {OriginARM} from "contracts/OriginARM.sol"; -contract Fork_OethARM_Smoke_Test is AbstractSmokeTest { +contract Fork_OriginARM_Smoke_Test is AbstractSmokeTest { IERC20 BAD_TOKEN = IERC20(makeAddr("bad token")); IERC20 weth; IERC20 oeth; Proxy proxy; - OethARM oethARM; + OriginARM originARM; address operator; function setUp() public { @@ -29,61 +29,224 @@ contract Fork_OethARM_Smoke_Test is AbstractSmokeTest { vm.label(address(operator), "OPERATOR"); proxy = Proxy(payable(deployManager.getDeployment("OETH_ARM"))); - oethARM = OethARM(deployManager.getDeployment("OETH_ARM")); + originARM = OriginARM(deployManager.getDeployment("OETH_ARM")); - _dealWETH(address(oethARM), 100 ether); - _dealOETH(address(oethARM), 100 ether); + _dealWETH(address(originARM), 100 ether); + _dealOETH(address(originARM), 100 ether); // Only fuzz from this address. Big speedup on fork. targetSender(address(this)); } - function test_swapExactTokensForTokens() external { - _swapExactTokensForTokens(oeth, weth, 10 ether, 10 ether); + //////////////////////////////////////////////////// + /// --- HELPERS + //////////////////////////////////////////////////// + function _dealWETH(address to, uint256 amount) internal { + deal(address(weth), to, amount); + } + + // Helper functions to deal tokens from whales, because oeth is rebasing, so deal() doesn't work + function _dealOETH(address to, uint256 amount) internal { + vm.prank(Mainnet.WOETH); + oeth.transfer(to, amount); + } + + //////////////////////////////////////////////////// + /// --- INITIAL CONFIG + //////////////////////////////////////////////////// + function test_initialConfig() external view { + // Ownership and fees + assertEq(originARM.name(), "Origin ARM", "Name"); + assertEq(originARM.symbol(), "ARM-WETH-OETH", "Symbol"); + assertEq(originARM.owner(), Mainnet.TIMELOCK, "Owner"); + assertEq(originARM.operator(), Mainnet.ARM_RELAYER, "Operator"); + assertEq(originARM.feeCollector(), Mainnet.ARM_BUYBACK, "Fee collector"); + assertEq((100 * uint256(originARM.fee())) / originARM.FEE_SCALE(), 20, "Performance fee as a percentage"); + + // Assets + assertEq(address(originARM.token0()), address(weth), "token0"); + assertEq(address(originARM.token1()), address(oeth), "token1"); + assertEq(originARM.liquidityAsset(), Mainnet.WETH, "liquidity asset"); + assertEq(originARM.baseAsset(), Mainnet.OETH, "base asset"); + assertEq(originARM.asset(), Mainnet.WETH, "ERC-4626 asset"); + + // Prices + assertNotEq(originARM.crossPrice(), 0, "cross price"); + assertNotEq(originARM.traderate0(), 0, "traderate0"); + assertNotEq(originARM.traderate1(), 0, "traderate1"); + + // Redemption + assertEq(address(originARM.vault()), Mainnet.OETH_VAULT, "OETH Vault"); + assertEq(originARM.claimDelay(), 10 minutes, "claim delay"); } - function test_swapTokensForExactTokens() external { - _swapTokensForExactTokens(oeth, weth, 10 ether, 10 ether); + //////////////////////////////////////////////////// + /// --- SWAP TESTS + //////////////////////////////////////////////////// + + function test_swap_exact_oeth_for_weth() external { + // trader sells OETH and buys WETH, the ARM buys OETH as a + // 4 bps discount + _swapExactTokensForTokens(oeth, weth, 0.9996e36, 100 ether); + // 10 bps discount + _swapExactTokensForTokens(oeth, weth, 0.999e36, 1e15); + // 20 bps discount + _swapExactTokensForTokens(oeth, weth, 0.998e36, 1 ether); } - function _swapExactTokensForTokens(IERC20 inToken, IERC20 outToken, uint256 amountIn, uint256 expectedOut) - internal - { + function test_swap_exact_weth_for_oeth() external { + // For this test, we need to set the cross price to 0.9999e36, which requires + // moving out all OETH from the ARM. + vm.startPrank(address(originARM)); + oeth.transfer(address(this), oeth.balanceOf(address(originARM))); + vm.stopPrank(); + vm.prank(Mainnet.TIMELOCK); + originARM.setCrossPrice(0.9999e36); + + // trader buys OETH and sells WETH, the ARM sells OETH at a + // 0.5 bps discount + _swapExactTokensForTokens(weth, oeth, 0.99995e36, 10 ether); + // 1 bps discount + _swapExactTokensForTokens(weth, oeth, 0.9999e36, 100 ether); + } + + function _swapExactTokensForTokens(IERC20 inToken, IERC20 outToken, uint256 price, uint256 amountIn) internal { + uint256 expectedOut; if (inToken == weth) { - _dealWETH(address(this), amountIn + 1000); + // Trader is buying stETH and selling WETH + // the ARM is selling stETH and buying WETH + deal(address(weth), address(this), 1_000_000 ether); + _dealOETH(address(originARM), 1000 ether); + + expectedOut = amountIn * 1e36 / price; + + vm.prank(Mainnet.ARM_RELAYER); + originARM.setPrices(price - 2e32, price); } else { - _dealOETH(address(this), amountIn + 1000); + // Trader is selling stETH and buying WETH + // the ARM is buying stETH and selling WETH + _dealOETH(address(this), 1000 ether); + deal(address(weth), address(originARM), 1_000_000 ether); + + expectedOut = amountIn * price / 1e36; + + vm.prank(Mainnet.ARM_RELAYER); + originARM.setPrices(price, 1e36); } // Approve the ARM to transfer the input token of the swap. - inToken.approve(address(oethARM), amountIn); + inToken.approve(address(originARM), amountIn); uint256 startIn = inToken.balanceOf(address(this)); uint256 startOut = outToken.balanceOf(address(this)); - oethARM.swapExactTokensForTokens(inToken, outToken, amountIn, 0, address(this)); - assertEq(inToken.balanceOf(address(this)), startIn - amountIn, "In actual"); - assertEq(outToken.balanceOf(address(this)), startOut + expectedOut, "Out actual"); + + originARM.swapExactTokensForTokens(inToken, outToken, amountIn, 0, address(this)); + + assertApproxEqAbs(inToken.balanceOf(address(this)), startIn - amountIn, 2, "In actual"); + assertApproxEqAbs(outToken.balanceOf(address(this)), startOut + expectedOut, 2, "Out actual"); } - function _swapTokensForExactTokens(IERC20 inToken, IERC20 outToken, uint256 amountIn, uint256 expectedOut) - internal - { + function test_swap_oeth_for_exact_weth() external { + // trader sells OETH and buys WETH, the ARM buys OETH at a + // 4 bps discount + _swapTokensForExactTokens(oeth, weth, 0.9996e36, 10 ether); + // 10 bps discount + _swapTokensForExactTokens(oeth, weth, 0.999e36, 100 ether); + // 50 bps discount + _swapTokensForExactTokens(oeth, weth, 0.995e36, 10 ether); + } + + function test_swap_weth_for_exact_oeth() external { + // For this test, we need to set the cross price to 0.9999e36, which requires + // moving out all OETH from the ARM. + vm.startPrank(address(originARM)); + oeth.transfer(address(this), oeth.balanceOf(address(originARM))); + vm.stopPrank(); + vm.prank(Mainnet.TIMELOCK); + originARM.setCrossPrice(0.9999e36); + + // trader buys OETH and sells WETH, the ARM sells OETH at a + // 0.5 bps discount + _swapTokensForExactTokens(weth, oeth, 0.99995e36, 10 ether); + // 1 bps discount + _swapTokensForExactTokens(weth, oeth, 0.9999e36, 100 ether); + } + + function _swapTokensForExactTokens(IERC20 inToken, IERC20 outToken, uint256 price, uint256 amountOut) internal { + uint256 expectedIn; if (inToken == weth) { - _dealWETH(address(this), amountIn + 1000); + // Trader is buying stETH and selling WETH + // the ARM is selling stETH and buying WETH + deal(address(weth), address(this), 1_000_000 ether); + _dealOETH(address(originARM), 1000 ether); + + expectedIn = amountOut * price / 1e36; + + vm.prank(Mainnet.ARM_RELAYER); + originARM.setPrices(price - 2e32, price); } else { - _dealOETH(address(this), amountIn + 1000); + // Trader is selling stETH and buying WETH + // the ARM is buying stETH and selling WETH + _dealOETH(address(this), 1000 ether); + deal(address(weth), address(originARM), 1_000_000 ether); + + expectedIn = amountOut * 1e36 / price + 3; + + vm.prank(Mainnet.ARM_RELAYER); + originARM.setPrices(price, 1e36); } // Approve the ARM to transfer the input token of the swap. - inToken.approve(address(oethARM), amountIn); + inToken.approve(address(originARM), expectedIn + 10000); uint256 startIn = inToken.balanceOf(address(this)); + uint256 startOut = outToken.balanceOf(address(this)); + + originARM.swapTokensForExactTokens(inToken, outToken, amountOut, 3 * amountOut, address(this)); + + assertApproxEqAbs(inToken.balanceOf(address(this)), startIn - expectedIn, 3, "In actual"); + assertApproxEqAbs(outToken.balanceOf(address(this)), startOut + amountOut, 3, "Out actual"); + } - oethARM.swapTokensForExactTokens(inToken, outToken, expectedOut, 3 * expectedOut, address(this)); - assertEq(inToken.balanceOf(address(this)), startIn - amountIn, "In actual"); - assertEq(outToken.balanceOf(address(this)), expectedOut, "Out actual"); + function test_wrongInTokenExactIn() external { + vm.expectRevert("ARM: Invalid in token"); + originARM.swapExactTokensForTokens(BAD_TOKEN, oeth, 10 ether, 0, address(this)); + vm.expectRevert("ARM: Invalid in token"); + originARM.swapExactTokensForTokens(BAD_TOKEN, weth, 10 ether, 0, address(this)); + } + + function test_wrongOutTokenExactIn() external { + vm.expectRevert("ARM: Invalid out token"); + originARM.swapTokensForExactTokens(weth, BAD_TOKEN, 10 ether, 10 ether, address(this)); + vm.expectRevert("ARM: Invalid out token"); + originARM.swapTokensForExactTokens(oeth, BAD_TOKEN, 10 ether, 10 ether, address(this)); + vm.expectRevert("ARM: Invalid out token"); + originARM.swapTokensForExactTokens(weth, weth, 10 ether, 10 ether, address(this)); + vm.expectRevert("ARM: Invalid out token"); + originARM.swapTokensForExactTokens(oeth, oeth, 10 ether, 10 ether, address(this)); } + function test_wrongInTokenExactOut() external { + vm.expectRevert("ARM: Invalid in token"); + originARM.swapTokensForExactTokens(BAD_TOKEN, oeth, 10 ether, 10 ether, address(this)); + vm.expectRevert("ARM: Invalid in token"); + originARM.swapTokensForExactTokens(BAD_TOKEN, weth, 10 ether, 10 ether, address(this)); + } + + function test_wrongOutTokenExactOut() external { + vm.expectRevert("ARM: Invalid out token"); + originARM.swapTokensForExactTokens(weth, BAD_TOKEN, 10 ether, 10 ether, address(this)); + vm.expectRevert("ARM: Invalid out token"); + originARM.swapTokensForExactTokens(oeth, BAD_TOKEN, 10 ether, 10 ether, address(this)); + vm.expectRevert("ARM: Invalid out token"); + originARM.swapTokensForExactTokens(weth, weth, 10 ether, 10 ether, address(this)); + vm.expectRevert("ARM: Invalid out token"); + originARM.swapTokensForExactTokens(oeth, oeth, 10 ether, 10 ether, address(this)); + } + + //////////////////////////////////////////////////// + /// --- AUTHORIZATION + //////////////////////////////////////////////////// function test_unauthorizedAccess() external { - address RANDOM_ADDRESS = 0xfEEDBeef00000000000000000000000000000000; + address RANDOM_ADDRESS = vm.randomAddress(); vm.startPrank(RANDOM_ADDRESS); // Proxy's restricted methods. @@ -101,87 +264,57 @@ contract Fork_OethARM_Smoke_Test is AbstractSmokeTest { // Implementation's restricted methods. vm.expectRevert("ARM: Only owner can call this function."); - oethARM.setOwner(RANDOM_ADDRESS); - } - - function test_wrongInTokenExactIn() external { - vm.expectRevert("ARM: Invalid swap"); - oethARM.swapExactTokensForTokens(BAD_TOKEN, oeth, 10 ether, 0, address(this)); - vm.expectRevert("ARM: Invalid swap"); - oethARM.swapExactTokensForTokens(BAD_TOKEN, weth, 10 ether, 0, address(this)); - vm.expectRevert("ARM: Invalid swap"); - oethARM.swapExactTokensForTokens(weth, weth, 10 ether, 0, address(this)); - vm.expectRevert("ARM: Invalid swap"); - oethARM.swapExactTokensForTokens(oeth, oeth, 10 ether, 0, address(this)); - } - - function test_wrongOutTokenExactIn() external { - vm.expectRevert("ARM: Invalid swap"); - oethARM.swapTokensForExactTokens(weth, BAD_TOKEN, 10 ether, 10 ether, address(this)); - vm.expectRevert("ARM: Invalid swap"); - oethARM.swapTokensForExactTokens(oeth, BAD_TOKEN, 10 ether, 10 ether, address(this)); - vm.expectRevert("ARM: Invalid swap"); - oethARM.swapTokensForExactTokens(weth, weth, 10 ether, 10 ether, address(this)); - vm.expectRevert("ARM: Invalid swap"); - oethARM.swapTokensForExactTokens(oeth, oeth, 10 ether, 10 ether, address(this)); - } - - function test_wrongInTokenExactOut() external { - vm.expectRevert("ARM: Invalid swap"); - oethARM.swapTokensForExactTokens(BAD_TOKEN, oeth, 10 ether, 10 ether, address(this)); - vm.expectRevert("ARM: Invalid swap"); - oethARM.swapTokensForExactTokens(BAD_TOKEN, weth, 10 ether, 10 ether, address(this)); - vm.expectRevert("ARM: Invalid swap"); - oethARM.swapTokensForExactTokens(weth, weth, 10 ether, 10 ether, address(this)); - vm.expectRevert("ARM: Invalid swap"); - oethARM.swapTokensForExactTokens(oeth, oeth, 10 ether, 10 ether, address(this)); - } - - function test_wrongOutTokenExactOut() external { - vm.expectRevert("ARM: Invalid swap"); - oethARM.swapTokensForExactTokens(weth, BAD_TOKEN, 10 ether, 10 ether, address(this)); - vm.expectRevert("ARM: Invalid swap"); - oethARM.swapTokensForExactTokens(oeth, BAD_TOKEN, 10 ether, 10 ether, address(this)); - vm.expectRevert("ARM: Invalid swap"); - oethARM.swapTokensForExactTokens(weth, weth, 10 ether, 10 ether, address(this)); - vm.expectRevert("ARM: Invalid swap"); - oethARM.swapTokensForExactTokens(oeth, oeth, 10 ether, 10 ether, address(this)); - } - - function test_collectTokens() external { - vm.startPrank(Mainnet.TIMELOCK); - - oethARM.transferToken(address(weth), address(this), weth.balanceOf(address(oethARM))); - assertGt(weth.balanceOf(address(this)), 50 ether); - assertEq(weth.balanceOf(address(oethARM)), 0); - - oethARM.transferToken(address(oeth), address(this), oeth.balanceOf(address(oethARM))); - assertGt(oeth.balanceOf(address(this)), 50 ether); - assertLt(oeth.balanceOf(address(oethARM)), 3); - + originARM.setOwner(RANDOM_ADDRESS); vm.stopPrank(); - } - - function _dealOETH(address to, uint256 amount) internal { - vm.prank(0xDcEe70654261AF21C44c093C300eD3Bb97b78192); - oeth.transfer(to, amount); - } - function _dealWETH(address to, uint256 amount) internal { - deal(address(weth), to, amount); + vm.expectRevert("ARM: Only owner can call this function."); + vm.prank(operator); + originARM.setOperator(operator); } - /* Operator Tests */ - function test_setOperator() external { vm.prank(Mainnet.TIMELOCK); - oethARM.setOperator(address(this)); - assertEq(oethARM.operator(), address(this)); + originARM.setOperator(address(this)); + assertEq(originARM.operator(), address(this)); } - function test_nonOwnerCannotSetOperator() external { - vm.expectRevert("ARM: Only owner can call this function."); - vm.prank(operator); - oethARM.setOperator(operator); + //////////////////////////////////////////////////// + /// --- VAULT WITHDRAWALS + //////////////////////////////////////////////////// + function test_request_origin_withdrawal() external { + _dealOETH(address(originARM), 10 ether); + vm.prank(Mainnet.ARM_RELAYER); + uint256 requestId = originARM.requestOriginWithdrawal(10 ether); + assertNotEq(requestId, 0); + } + + function test_claim_origin_withdrawal() external { + // Cheat section + // Deal OETH to the ARM, in order to have some asset to withdraw + _dealOETH(address(originARM), 10 ether); + // Deal WETH to this test account to mint OETH in the Vault, directly increasing + // the Vault's liquidity doesn't work because of the "Backing supply liquidity error" check + _dealWETH(address(this), 10_000 ether); + (bool success,) = + Mainnet.WETH.call(abi.encodeWithSignature("approve(address,uint256)", Mainnet.OETH_VAULT, 10_000 ether)); + require(success, "Approve failed"); + (success,) = Mainnet.OETH_VAULT + .call(abi.encodeWithSignature("mint(address,uint256,uint256)", Mainnet.WETH, 10_000 ether, 0)); + require(success, "Mint failed"); + // End cheat section + + // Request a withdrawal + vm.prank(Mainnet.ARM_RELAYER); + uint256 requestId = originARM.requestOriginWithdrawal(10 ether); + + // Fast forward time by 1 day to pass the claim delay + vm.warp(block.timestamp + 1 days); + + // Claim the withdrawal + uint256[] memory requestIds = new uint256[](1); + requestIds[0] = requestId; + + uint256 amountClaimed = originARM.claimOriginWithdrawals(requestIds); + assertEq(amountClaimed, 10 ether); } }