diff --git a/README.md b/README.md index 73b2e1a..d829439 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ This script will: #### Ethena Deposit flow -Look at [function _doDeposit()](test/BaseIntegrationMorpho.t.sol#L1000) for a reference implementation of the flow. +Look at [function _doDeposit()](test/BaseMorphoUSDC.t.sol#L1000) for a reference implementation of the flow. 1. Website User (called Client in contracts) calls Backend with its (User's) Ethereum address and some Merchant info. @@ -96,7 +96,7 @@ Look at [function _doDeposit()](test/BaseIntegrationMorpho.t.sol#L1000) for a re #### Ethena Withdrawal flow -Look at [function _doWithdraw()](test/BaseIntegrationMorpho.t.sol#L1024) for a reference implementation of the flow. +Look at [function _doWithdraw()](test/BaseMorphoUSDC.t.sol#L1024) for a reference implementation of the flow. 1. Client-side JS code prepares all the necessary data for the Ethena `cooldownShares` function. diff --git a/script/RunTestDepositNativeOptimism.s.sol b/script/RunTestDepositNativeOptimism.s.sol new file mode 100644 index 0000000..22a607a --- /dev/null +++ b/script/RunTestDepositNativeOptimism.s.sol @@ -0,0 +1,117 @@ +// SPDX-FileCopyrightText: 2025 P2P Validator +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.27; + +import "../lib/forge-std/src/Vm.sol"; +import "../src/adapters/superform/p2pSuperformProxyFactory/P2pSuperformProxyFactory.sol"; +import {Script} from "forge-std/Script.sol"; +import {PermitHash} from "../src/@permit2/libraries/PermitHash.sol"; + +contract RunTestDepositNativeOptimism is Script { + using SafeERC20 for IERC20; + + address constant SuperformRouter = 0xa195608C2306A26f727d5199D5A382a4508308DA; + address constant SuperPositions = 0x01dF6fb6a28a89d6bFa53b2b3F20644AbF417678; + address constant P2pTreasury = 0x641ca805C75cC5D1ffa78C0181Aba1F77BD17904; + + address constant USDT = 0x94b008aA00579c1307B0EF2c499aD98a8ce58e58; + uint256 constant SigDeadline = 1746420983; + uint48 constant ClientBasisPointsOfProfit = 8700; // 13% fee + uint48 constant ClientBasisPointsOfDeposit = 10000; // 0% fee + uint256 constant DepositAmount = 12340000000000000; + + P2pSuperformProxyFactory factory; + address proxyAddress; + + function run() + external + { + uint256 deployerKey = vm.envUint("PRIVATE_KEY"); + Vm.Wallet memory wallet = vm.createWallet(deployerKey); + + factory = P2pSuperformProxyFactory(0xEF3c3034FD8E396b11713183C7080811E6cFD026); + proxyAddress = factory.predictP2pYieldProxyAddress( + wallet.addr, + ClientBasisPointsOfDeposit, + ClientBasisPointsOfProfit + ); + + IAllowanceTransfer.PermitSingle memory permitSingleForP2pYieldProxy; + bytes memory permit2SignatureForP2pYieldProxy; + bytes memory p2pSignerSignature = _getP2pSignerSignature( + wallet.addr, + ClientBasisPointsOfDeposit, + ClientBasisPointsOfProfit, + SigDeadline + ); + + bytes memory superformCalldata = hex'b19dcc3300000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020000000000000000a00000001b8138fff124dd7f91abf412b73be453fb140568c00000000000000000000000000000000000000000000000000248238191b12ce00000000000000000000000000000000000000000000000000247a8437f408dc00000000000000000000000000000000000000000000000000000000000001f400000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b3055ebe6b9c43c86fb684d512123fd88f8a6f19000000000000000000000000b3055ebe6b9c43c86fb684d512123fd88f8a6f190000000000000000000000000000000000000000000000000000000000000c2000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000065000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000002bd72a2487400000000000000000000000000000000000000000000000000000000000000009a44630a0d8f8cf2c5ebadc417dcbfbeb1f04438e7bbf259fee0d2d9c72f870d72eba1fc7a300000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000b8138fff124dd7f91abf412b73be453fb140568c0000000000000000000000000000000000000000000000000022aee87e401eaa0000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000000d7375706572666f726d2e78797a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a30783030303030303030303030303030303030303030303030303030303030303030303030303030303000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000006131b5fae19ea4f9d964eac0408e4408b66337b50000000000000000000000006131b5fae19ea4f9d964eac0408e4408b66337b500000000000000000000000000000000000000000000000000000000000000000000000000000000000000001f32b1c2345538c0c6f582fcb022739c4a194ebb000000000000000000000000000000000000000000000000002bd72a2487400000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000006e4e21fd0e90000000000000000000000000000000000000000000000000000000000000020000000000000000000000000c7d3ab410d49b664d03fe5b1038852ac852b1b29000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000024000000000000000000000000000000000000000000000000000000000000004400000000000000000000000000000000000000000000000000000000000000164010300000048000000ba12222222228d8ba445958a75a0704d566bf2c839965c9dab5448482cf7e002f583c812ceb53046000100000000000000000003020000000000000000002bd72a248740000b00000039000000ba12222222228d8ba445958a75a0704d566bf2c89da11ff60bfc5af527f58fd61679c3ac98d040d900000000000000000000010000010b0000002e02000000ee1bac98527a9fdd57fccf967817215b083ce1f001010000000000000000000000000000000100eed0b40aeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee1f32b1c2345538c0c6f582fcb022739c4a194ebb1231deb6f5749ef6ce6943a275a1d3e7486f4eae0000000000000000000000006809faee00000054000000000000000000000000000000000000000000000000000000000000000000000000000000000000000264838984000000000000000000248238191b12ce4f82e73edb06d29ff62c91ec8f5ff06571bdeb2900000000000000000000000000000000000000000000000000000000000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee0000000000000000000000001f32b1c2345538c0c6f582fcb022739c4a194ebb0000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000001231deb6f5749ef6ce6943a275a1d3e7486f4eae000000000000000000000000000000000000000000000000002bd72a248740000000000000000000000000000000000000000000000000000022aee87e401eaa000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000025d7b22536f75726365223a226c692e6669222c22416d6f756e74496e555344223a2232312e353635373136363038383835303538222c22416d6f756e744f7574555344223a2232312e3533303838303233373730393835222c22526566657272616c223a22222c22466c616773223a302c22416d6f756e744f7574223a223130323736323736363132353637373538222c2254696d657374616d70223a313734353438333332362c22526f7574654944223a2263326437633637662d356338622d343961312d613232652d616164613631383065386234222c22496e74656772697479496e666f223a7b224b65794944223a2231222c225369676e6174757265223a224d34424131515533726935564b424148616e443250687435567130654654314c6e7056354c542f50474a364259514379714e7838744855412b6a486633414a64454c74625378635658544e6e56763349317a3431746c68774f524c576475596664426e394a55446b4373624f5a58474e543644335554694e636c47485032356170415035465950706164624c6c34377653616c59676d78795565585a3946704d49686944446442737431344e53472f4c596a6e345a5a6e697276697770776835534b6c74396d7074674538436c432f456b35594351636c424b64337765697767316b546c436e36573543553547786b643742634b544a76504e4a6c70496a307962674a523742384848472b574f535865686c4d3865635a4d762b7242675570504c756e4b4552644f7942492f48584b4343506c4654324144775171367957394e67725249386e5557524a37544e4554513150334e33413d3d227d7d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'; //abi.encodeCall(IBaseRouter.singleDirectSingleVaultDeposit, (req)); + + vm.startBroadcast(deployerKey); + if (IERC20(USDT).allowance(wallet.addr, address(Permit2Lib.PERMIT2)) == 0) { + IERC20(USDT).safeApprove(address(Permit2Lib.PERMIT2), type(uint256).max); + } + factory.deposit{value: (12340000000000000) * 113 / 100}( + permitSingleForP2pYieldProxy, + permit2SignatureForP2pYieldProxy, + + superformCalldata, + + ClientBasisPointsOfDeposit, + ClientBasisPointsOfProfit, + SigDeadline, + p2pSignerSignature + ); + vm.stopBroadcast(); + } + + function _getPermitSingleForP2pYieldProxy() private returns(IAllowanceTransfer.PermitSingle memory) { + uint256 deployerKey = vm.envUint("PRIVATE_KEY"); + Vm.Wallet memory wallet = vm.createWallet(deployerKey); + (, , uint48 nonce) = IAllowanceTransfer(0x000000000022D473030F116dDEE9F6B43aC78BA3).allowance(wallet.addr, USDT, proxyAddress); + + IAllowanceTransfer.PermitDetails memory permitDetails = IAllowanceTransfer.PermitDetails({ + token: USDT, + amount: uint160(DepositAmount), + expiration: uint48(SigDeadline), + nonce: nonce + }); + + // data for factory + IAllowanceTransfer.PermitSingle memory permitSingleForP2pYieldProxy = IAllowanceTransfer.PermitSingle({ + details: permitDetails, + spender: proxyAddress, + sigDeadline: SigDeadline + }); + + return permitSingleForP2pYieldProxy; + } + + function _getPermit2SignatureForP2pYieldProxy(IAllowanceTransfer.PermitSingle memory permitSingleForP2pYieldProxy) private view returns(bytes memory) { + bytes32 permitSingleForP2pYieldProxyHash = factory.getPermit2HashTypedData(PermitHash.hash(permitSingleForP2pYieldProxy)); + (uint8 v1, bytes32 r1, bytes32 s1) = vm.sign(vm.envUint("PRIVATE_KEY"), permitSingleForP2pYieldProxyHash); + bytes memory permit2SignatureForP2pYieldProxy = abi.encodePacked(r1, s1, v1); + return permit2SignatureForP2pYieldProxy; + } + + function _getP2pSignerSignature( + address _clientAddress, + uint48 _clientBasisPointsOfDeposit, + uint48 _clientBasisPointsOfProfit, + uint256 _sigDeadline + ) private view returns(bytes memory) { + // p2p signer signing + bytes32 hashForP2pSigner = factory.getHashForP2pSigner( + _clientAddress, + _clientBasisPointsOfDeposit, + _clientBasisPointsOfProfit, + _sigDeadline + ); + bytes32 ethSignedMessageHashForP2pSigner = ECDSA.toEthSignedMessageHash(hashForP2pSigner); + (uint8 v2, bytes32 r2, bytes32 s2) = vm.sign(vm.envUint("PRIVATE_KEY"), ethSignedMessageHashForP2pSigner); + bytes memory p2pSignerSignature = abi.encodePacked(r2, s2, v2); + return p2pSignerSignature; + } +} + diff --git a/script/RunTestWithdrawNativeOptimism.s.sol b/script/RunTestWithdrawNativeOptimism.s.sol new file mode 100644 index 0000000..5204d7e --- /dev/null +++ b/script/RunTestWithdrawNativeOptimism.s.sol @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: 2025 P2P Validator +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.27; + +import "../lib/forge-std/src/Vm.sol"; +import "../src/adapters/superform/p2pSuperformProxyFactory/P2pSuperformProxyFactory.sol"; +import {Script} from "forge-std/Script.sol"; +import {PermitHash} from "../src/@permit2/libraries/PermitHash.sol"; + +contract RunTestWithdrawNativeOptimism is Script { + using SafeERC20 for IERC20; + + uint48 constant ClientBasisPointsOfProfit = 8700; // 13% fee + uint48 constant ClientBasisPointsOfDeposit = 10000; // 0% fee + + P2pSuperformProxyFactory factory; + address proxyAddress; + + function run() + external + { + uint256 deployerKey = vm.envUint("PRIVATE_KEY"); + Vm.Wallet memory wallet = vm.createWallet(deployerKey); + + factory = P2pSuperformProxyFactory(0xEF3c3034FD8E396b11713183C7080811E6cFD026); + proxyAddress = factory.predictP2pYieldProxyAddress( + wallet.addr, + ClientBasisPointsOfDeposit, + ClientBasisPointsOfProfit + ); + + bytes memory superformCalldata = hex'407c7b1d00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020000000000000000a00000001b8138fff124dd7f91abf412b73be453fb140568c00000000000000000000000000000000000000000000000000246bce0022c1630000000000000000000000000000000000000000000000000024737ec621e13800000000000000000000000000000000000000000000000000000000000013880000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000076000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b3055ebe6b9c43c86fb684d512123fd88f8a6f19000000000000000000000000b3055ebe6b9c43c86fb684d512123fd88f8a6f19000000000000000000000000000000000000000000000000000000000000078000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000065000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005044630a0d83e20e4c98c0e7de0d36b81a9b48d0c4a88617f17c0d65659ae71752f4c62daba00000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000b3055ebe6b9c43c86fb684d512123fd88f8a6f190000000000000000000000000000000000000000000000000015d983e600aeef0000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000000d7375706572666f726d2e78797a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a3078303030303030303030303030303030303030303030303030303030303030303030303030303030300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000060e8c01e8e39b10202e39e62001f08092cc03ca000000000000000000000000060e8c01e8e39b10202e39e62001f08092cc03ca0000000000000000000000001f32b1c2345538c0c6f582fcb022739c4a194ebb00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024737ec621e13800000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000002442646478b0000000000000000000000001f32b1c2345538c0c6f582fcb022739c4a194ebb0000000000000000000000000000000000000000000000000024737ec621e138000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee0000000000000000000000000000000000000000000000000015d983e600aeef0000000000000000000000001231deb6f5749ef6ce6943a275a1d3e7486f4eae00000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000014c021f32b1c2345538c0c6f582fcb022739c4a194ebb0395550104f6c85a1b00f6d9b75f91fd23835974cc07e65c01060e8c01e8e39b10202e39e62001f08092cc03ca66660195d7d146ae40d4822c2750276b54b6eed530d37401060e8c01e8e39b10202e39e62001f08092cc03caffff01766854992bd5363ebeeff0113f5a5795796befab01060e8c01e8e39b10202e39e62001f08092cc03ca017f5c764cbc14f9669b88837ca1490cca17c3160701ffff0185149247691df622eaf1a8bd0cafd40bc45154a900060e8c01e8e39b10202e39e62001f08092cc03ca0194b008aa00579c1307b0ef2c499ad98a8ce58e5801ffff01c858a329bf053be78d6239c4a4343b8fbd21472b00060e8c01e8e39b10202e39e62001f08092cc03ca01420000000000000000000000000000000000000601ffff02001231deb6f5749ef6ce6943a275a1d3e7486f4eae0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'; + + vm.startBroadcast(deployerKey); + P2pSuperformProxy(payable(proxyAddress)).withdraw(superformCalldata); + vm.stopBroadcast(); + } +} + diff --git a/src/adapters/superform/p2pSuperformProxy/IP2pSuperformProxy.sol b/src/adapters/superform/p2pSuperformProxy/IP2pSuperformProxy.sol index 74fa632..0bf4158 100644 --- a/src/adapters/superform/p2pSuperformProxy/IP2pSuperformProxy.sol +++ b/src/adapters/superform/p2pSuperformProxy/IP2pSuperformProxy.sol @@ -3,8 +3,9 @@ pragma solidity 0.8.27; import "../../../@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; +import "../../../p2pYieldProxy/IP2pYieldProxy.sol"; -interface IP2pSuperformProxy is IERC1155Receiver { +interface IP2pSuperformProxy is IP2pYieldProxy, IERC1155Receiver { event P2pSuperformProxy__Claimed( address indexed _token, uint256 _totalAmount, diff --git a/src/adapters/superform/p2pSuperformProxy/P2pSuperformProxy.sol b/src/adapters/superform/p2pSuperformProxy/P2pSuperformProxy.sol index 71e4512..e792d35 100644 --- a/src/adapters/superform/p2pSuperformProxy/P2pSuperformProxy.sol +++ b/src/adapters/superform/p2pSuperformProxy/P2pSuperformProxy.sol @@ -30,8 +30,6 @@ error P2pSuperformProxy__ReceiverAddressSPShouldBeP2pSuperformProxy( ); error P2pSuperformProxy__AssetShouldNotBeZeroAddress(); error P2pSuperformProxy__NotClaimed(address _token); -error P2pSuperformProxy__WrongFundingAssetAmountsCount(); -error P2pSuperformProxy__IncorrectNativeFundingAssetAmount(); contract P2pSuperformProxy is P2pYieldProxy, IP2pSuperformProxy { @@ -68,7 +66,7 @@ contract P2pSuperformProxy is P2pYieldProxy, IP2pSuperformProxy { IAllowanceTransfer.PermitSingle calldata _permitSingleForP2pYieldProxy, bytes calldata _permit2SignatureForP2pYieldProxy, bytes calldata _superformCalldata - ) external override payable { + ) external override(P2pYieldProxy, IP2pYieldProxy) payable { require (_superformCalldata.length > 4, P2pSuperformProxy__SuperformCalldataTooShort()); bytes4 selector = bytes4(_superformCalldata[:4]); @@ -119,12 +117,6 @@ contract P2pSuperformProxy is P2pYieldProxy, IP2pSuperformProxy { isNative, nativeAmountToDepositAfterFee ); - - IERC1155A(i_superPositions).increaseAllowance( - i_yieldProtocolAddress, - req.superformData.superformId, - req.superformData.outputAmount - ); } function withdraw( @@ -159,6 +151,12 @@ contract P2pSuperformProxy is P2pYieldProxy, IP2pSuperformProxy { } require (asset != address(0), P2pSuperformProxy__AssetShouldNotBeZeroAddress()); + IERC1155A(i_superPositions).increaseAllowance( + i_yieldProtocolAddress, + req.superformData.superformId, + req.superformData.amount + ); + _withdraw( req.superformData.superformId, asset, diff --git a/src/adapters/superform/p2pSuperformProxyFactory/IP2pSuperformProxyFactory.sol b/src/adapters/superform/p2pSuperformProxyFactory/IP2pSuperformProxyFactory.sol index 85e636d..6a462bb 100644 --- a/src/adapters/superform/p2pSuperformProxyFactory/IP2pSuperformProxyFactory.sol +++ b/src/adapters/superform/p2pSuperformProxyFactory/IP2pSuperformProxyFactory.sol @@ -3,9 +3,10 @@ pragma solidity 0.8.27; import "../../../@permit2/interfaces/IAllowanceTransfer.sol"; +import "../../../p2pYieldProxyFactory/IP2pYieldProxyFactory.sol"; /// @dev External interface of P2pSuperformProxyFactory -interface IP2pSuperformProxyFactory { +interface IP2pSuperformProxyFactory is IP2pYieldProxyFactory { /// @dev Checks if the claim is valid /// @param _p2pOperatorToCheck The P2pOperator to check function checkClaim( diff --git a/src/adapters/superform/p2pSuperformProxyFactory/P2pSuperformProxyFactory.sol b/src/adapters/superform/p2pSuperformProxyFactory/P2pSuperformProxyFactory.sol index 316d074..67ba63a 100644 --- a/src/adapters/superform/p2pSuperformProxyFactory/P2pSuperformProxyFactory.sol +++ b/src/adapters/superform/p2pSuperformProxyFactory/P2pSuperformProxyFactory.sol @@ -49,7 +49,7 @@ contract P2pSuperformProxyFactory is P2pYieldProxyFactory, IP2pSuperformProxyFac } /// @inheritdoc ERC165 - function supportsInterface(bytes4 interfaceId) public view virtual override(P2pYieldProxyFactory) returns (bool) { + function supportsInterface(bytes4 interfaceId) public view virtual override(P2pYieldProxyFactory, IERC165) returns (bool) { return interfaceId == type(IP2pSuperformProxyFactory).interfaceId || super.supportsInterface(interfaceId); } diff --git a/src/p2pYieldProxy/IP2pYieldProxy.sol b/src/p2pYieldProxy/IP2pYieldProxy.sol index 7385298..e8d8524 100644 --- a/src/p2pYieldProxy/IP2pYieldProxy.sol +++ b/src/p2pYieldProxy/IP2pYieldProxy.sol @@ -75,6 +75,14 @@ interface IP2pYieldProxy is IERC165 { /// @return The P2pTreasury address function getP2pTreasury() external view returns (address); + /// @notice Gets the Yield Protocol address + /// @return The Yield Protocol address + function getYieldProtocolAddress() external view returns (address); + + /// @notice Gets the AllowedCalldataChecker address + /// @return The AllowedCalldataChecker address + function getAllowedCalldataChecker() external view returns (address); + /// @notice Gets the client address /// @return The client address function getClient() external view returns (address); diff --git a/src/p2pYieldProxy/P2pYieldProxy.sol b/src/p2pYieldProxy/P2pYieldProxy.sol index c8c7c80..e7e440b 100644 --- a/src/p2pYieldProxy/P2pYieldProxy.sol +++ b/src/p2pYieldProxy/P2pYieldProxy.sol @@ -40,7 +40,6 @@ error P2pYieldProxy__ZeroAddressYieldProtocolAddress(); error P2pYieldProxy__ZeroNewAssetAmount(address _asset); error P2pYieldProxy__ZeroAllowedCalldataChecker(); error P2pYieldProxy__DataTooShort(); -error P2pYieldProxy__AllAssetsMustBeUnique(); /// @title P2pYieldProxy /// @notice P2pYieldProxy is a contract that allows a client to deposit and withdraw assets from a yield protocol. @@ -379,6 +378,16 @@ abstract contract P2pYieldProxy is return i_p2pTreasury; } + /// @inheritdoc IP2pYieldProxy + function getYieldProtocolAddress() external view returns (address) { + return i_yieldProtocolAddress; + } + + /// @inheritdoc IP2pYieldProxy + function getAllowedCalldataChecker() external view returns (address) { + return address(i_allowedCalldataChecker); + } + /// @inheritdoc IP2pYieldProxy function getClient() external view returns (address) { return s_client; diff --git a/test/BaseIntegrationMorpho.t.sol b/test/BaseMorphoUSDC.t.sol similarity index 99% rename from test/BaseIntegrationMorpho.t.sol rename to test/BaseMorphoUSDC.t.sol index 4303abe..103b06d 100644 --- a/test/BaseIntegrationMorpho.t.sol +++ b/test/BaseMorphoUSDC.t.sol @@ -17,7 +17,7 @@ import "forge-std/console2.sol"; import {PermitHash} from "../src/@permit2/libraries/PermitHash.sol"; -contract BaseIntegrationMorpho is Test { +contract BaseMorphoUSDC is Test { using SafeERC20 for IERC20; address constant USDC = 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913; diff --git a/test/OptimismIntegration.t.sol b/test/OptimismIntegration.t.sol deleted file mode 100644 index f1987b5..0000000 --- a/test/OptimismIntegration.t.sol +++ /dev/null @@ -1,313 +0,0 @@ -// SPDX-FileCopyrightText: 2025 P2P Validator -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.27; - -import "../src/@openzeppelin/contracts/interfaces/IERC4626.sol"; -import "../src/@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; -import "../src/@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -import "../src/access/P2pOperator.sol"; -import "../src/adapters/superform/p2pSuperformProxyFactory/P2pSuperformProxyFactory.sol"; -import "../src/common/AllowedCalldataChecker.sol"; -import "../src/p2pYieldProxyFactory/P2pYieldProxyFactory.sol"; -import "./utils/merkle/helper/MerkleReader.sol"; -import "forge-std/Test.sol"; -import "forge-std/Vm.sol"; -import "forge-std/console.sol"; -import "forge-std/console2.sol"; -import {PermitHash} from "../src/@permit2/libraries/PermitHash.sol"; - - -contract OptimismIntegration is Test, MerkleReader { - using SafeERC20 for IERC20; - - address constant USDT = 0x94b008aA00579c1307B0EF2c499aD98a8ce58e58; - address constant SuperformRouter = 0xa195608C2306A26f727d5199D5A382a4508308DA; - address constant SuperPositions = 0x01dF6fb6a28a89d6bFa53b2b3F20644AbF417678; - address constant RewardsDistributorInstance = 0xce23bD7205bF2B543F6B4eeC00Add0C111FEFc3B; - - address constant RewardsDistributorAdmin = 0xf82F3D7Df94FC2994315c32322DA6238cA2A2f7f; - - address constant P2pTreasury = 0x641ca805C75cC5D1ffa78C0181Aba1F77BD17904; - - uint256 constant SuperformId = 62771017356190754913478451444852273738203985736479809223259; - - P2pSuperformProxyFactory private factory; - - address private clientAddress; - uint256 private clientPrivateKey; - - address private p2pSignerAddress; - uint256 private p2pSignerPrivateKey; - - address private p2pOperatorAddress; - address private nobody; - - uint256 constant SigDeadline = 1789558996; - uint48 constant ClientBasisPointsOfProfit = 8700; // 13% fee - uint48 constant ClientBasisPointsOfDeposit = 10_000; // 0% fee - uint256 constant DepositAmount = 199918306828021388981; - uint256 constant SharesAmount = 199918306828021388981; - - address proxyAddress; - - uint48 nonce; - - uint64 public constant CHAIN_ID = 10; - - uint256 totalUSDCToDeposit; - uint256 totalDAIToDeposit; - - function setUp() public { - vm.createSelectFork("optimism", 133700000); - - (clientAddress, clientPrivateKey) = makeAddrAndKey("client"); - (p2pSignerAddress, p2pSignerPrivateKey) = makeAddrAndKey("p2pSigner"); - p2pOperatorAddress = makeAddr("p2pOperator"); - nobody = makeAddr("nobody"); - - vm.startPrank(p2pOperatorAddress); - AllowedCalldataChecker implementation = new AllowedCalldataChecker(); - ProxyAdmin admin = new ProxyAdmin(); - bytes memory initData = abi.encodeWithSelector(AllowedCalldataChecker.initialize.selector); - TransparentUpgradeableProxy tup = new TransparentUpgradeableProxy( - address(implementation), - address(admin), - initData - ); - factory = new P2pSuperformProxyFactory( - p2pSignerAddress, - P2pTreasury, - SuperformRouter, - SuperPositions, - address(tup), - RewardsDistributorInstance - ); - vm.stopPrank(); - - proxyAddress = factory.predictP2pYieldProxyAddress( - clientAddress, - ClientBasisPointsOfDeposit, - ClientBasisPointsOfProfit - ); - } - - function test_happyPath_Optimism() public { - deal(USDT, clientAddress, 10000e18); - - _doDeposit(); - _doWithdraw(); - } - - function test_batchclaim_proxy() public { - deal(USDT, clientAddress, 10000e18); - - _doDeposit(); - - _addRoot(); - _addRoot24(); - - // common user - address user = proxyAddress; - - uint256[] memory periodIds = new uint256[](2); - periodIds[0] = 23; - periodIds[1] = 24; - - bytes32[][] memory proofs = new bytes32[][](2); - - address[][] memory tokensToClaim = new address[][](2); - - uint256[][] memory amountsToClaim = new uint256[][](2); - for (uint256 periodId = 0; periodId < 2; periodId++) { - (,,,, bytes32[] memory proof_, address[] memory tokensToClaim_, uint256[] memory amountsToClaim_) = - _generateMerkleTree(MerkleReader.MerkleArgs(periodId + 23, user, CHAIN_ID)); - - proofs[periodId] = proof_; - tokensToClaim[periodId] = tokensToClaim_; - amountsToClaim[periodId] = amountsToClaim_; - } - - vm.prank(clientAddress); - IP2pSuperformProxy(payable(proxyAddress)).batchClaim( - periodIds, - tokensToClaim, - amountsToClaim, - proofs - ); - } - - function test_batchclaim_randomClaimer_claimAndAlreadyClaimed() public { - _addRoot(); - _addRoot24(); - - // common user - address user = proxyAddress; - - uint256[] memory periodIds = new uint256[](2); - periodIds[0] = 23; - periodIds[1] = 24; - - bytes32[][] memory proofs = new bytes32[][](2); - - address[][] memory tokensToClaim = new address[][](2); - - uint256[][] memory amountsToClaim = new uint256[][](2); - for (uint256 periodId = 0; periodId < 2; periodId++) { - (,,,, bytes32[] memory proof_, address[] memory tokensToClaim_, uint256[] memory amountsToClaim_) = - _generateMerkleTree(MerkleReader.MerkleArgs(periodId + 23, user, CHAIN_ID)); - - proofs[periodId] = proof_; - tokensToClaim[periodId] = tokensToClaim_; - amountsToClaim[periodId] = amountsToClaim_; - } - - /// @dev tests a claim initiated by a random user on behalf of user - vm.prank(address(0x777)); - IRewardsDistributor(RewardsDistributorInstance).batchClaim(user, periodIds, tokensToClaim, amountsToClaim, proofs); - - vm.expectRevert(IRewardsDistributor.ALREADY_CLAIMED.selector); - vm.prank(user); - IRewardsDistributor(RewardsDistributorInstance).batchClaim(user, periodIds, tokensToClaim, amountsToClaim, proofs); - } - - function _addRoot() internal { - bytes32 root; - uint256 usdcToDeposit; - uint256 daiToDeposit; - uint256 periodId = 23; // IRewardsDistributor(RewardsDistributorInstance).currentPeriodId(); - (root,, usdcToDeposit, daiToDeposit,,,) = _generateMerkleTree(MerkleReader.MerkleArgs(periodId, proxyAddress, CHAIN_ID)); - - vm.startPrank(RewardsDistributorAdmin); - IRewardsDistributor(RewardsDistributorInstance).setPeriodicRewards(root); - totalUSDCToDeposit += usdcToDeposit; - totalDAIToDeposit += daiToDeposit; - - deal(USDC, RewardsDistributorInstance, totalUSDCToDeposit); - deal(DAI, RewardsDistributorInstance, totalDAIToDeposit); - vm.stopPrank(); - } - - function _addRoot24() internal { - bytes32 root; - uint256 usdcToDeposit; - uint256 daiToDeposit; - (root,, usdcToDeposit, daiToDeposit,,,) = _generateMerkleTree(MerkleReader.MerkleArgs(24, proxyAddress, CHAIN_ID)); - - vm.startPrank(RewardsDistributorAdmin); - IRewardsDistributor(RewardsDistributorInstance).setPeriodicRewards(root); - totalUSDCToDeposit += usdcToDeposit; - totalDAIToDeposit += daiToDeposit; - - deal(USDC, RewardsDistributorInstance, totalUSDCToDeposit); - deal(DAI, RewardsDistributorInstance, totalDAIToDeposit); - vm.stopPrank(); - } - - function _getVaultAddress() private pure returns(address) { - return address(uint160(SuperformId)); - } - - function _getPermitSingleForP2pYieldProxy() private returns(IAllowanceTransfer.PermitSingle memory) { - IAllowanceTransfer.PermitDetails memory permitDetails = IAllowanceTransfer.PermitDetails({ - token: USDT, - amount: uint160(DepositAmount), - expiration: uint48(SigDeadline), - nonce: nonce - }); - nonce++; - - // data for factory - IAllowanceTransfer.PermitSingle memory permitSingleForP2pYieldProxy = IAllowanceTransfer.PermitSingle({ - details: permitDetails, - spender: proxyAddress, - sigDeadline: SigDeadline - }); - - return permitSingleForP2pYieldProxy; - } - - function _getPermit2SignatureForP2pYieldProxy(IAllowanceTransfer.PermitSingle memory permitSingleForP2pYieldProxy) private view returns(bytes memory) { - bytes32 permitSingleForP2pYieldProxyHash = factory.getPermit2HashTypedData(PermitHash.hash(permitSingleForP2pYieldProxy)); - (uint8 v1, bytes32 r1, bytes32 s1) = vm.sign(clientPrivateKey, permitSingleForP2pYieldProxyHash); - bytes memory permit2SignatureForP2pYieldProxy = abi.encodePacked(r1, s1, v1); - return permit2SignatureForP2pYieldProxy; - } - - function _getP2pSignerSignature( - address _clientAddress, - uint48 _clientBasisPointsOfDeposit, - uint48 _clientBasisPointsOfProfit, - uint256 _sigDeadline - ) private view returns(bytes memory) { - // p2p signer signing - bytes32 hashForP2pSigner = factory.getHashForP2pSigner( - _clientAddress, - _clientBasisPointsOfDeposit, - _clientBasisPointsOfProfit, - _sigDeadline - ); - bytes32 ethSignedMessageHashForP2pSigner = ECDSA.toEthSignedMessageHash(hashForP2pSigner); - (uint8 v2, bytes32 r2, bytes32 s2) = vm.sign(p2pSignerPrivateKey, ethSignedMessageHashForP2pSigner); - bytes memory p2pSignerSignature = abi.encodePacked(r2, s2, v2); - return p2pSignerSignature; - } - - function _doDeposit() private { - IAllowanceTransfer.PermitSingle memory permitSingleForP2pYieldProxy = _getPermitSingleForP2pYieldProxy(); - bytes memory permit2SignatureForP2pYieldProxy = _getPermit2SignatureForP2pYieldProxy(permitSingleForP2pYieldProxy); - bytes memory p2pSignerSignature = _getP2pSignerSignature( - clientAddress, - ClientBasisPointsOfDeposit, - ClientBasisPointsOfProfit, - SigDeadline - ); - - vm.startPrank(clientAddress); - if (IERC20(USDT).allowance(clientAddress, address(Permit2Lib.PERMIT2)) == 0) { - IERC20(USDT).safeApprove(address(Permit2Lib.PERMIT2), type(uint256).max); - } - - bytes memory PLACEHOLDER = abi.encodePacked(proxyAddress); - - bytes memory superformCalldata = bytes.concat( bytes.concat( bytes.concat(hex'b19dcc3300000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020000000000000000a0000000197116661c85c4e1ee35aa10f7fc5fe5e67b83a5b00000000000000000000000000000000000000000000000001b8ca1b687f4d6000000000000000000000000000000000000000000000000001b8ca1b687f4d6000000000000000000000000000000000000000000000000000000000000001f4000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000006a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', PLACEHOLDER), -bytes.concat(hex'000000000000000000000000', PLACEHOLDER) -), -hex'00000000000000000000000000000000000000000000000000000000000006c000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000094b008aa00579c1307b0ef2c499ad98a8ce58e5800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000065000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004444630a0d896ba9cffae8a22aa75ffdc6910e52d52ee9a199ee31eb8893dc693d7c89ed4a800000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000097116661c85c4e1ee35aa10f7fc5fe5e67b83a5b00000000000000000000000000000000000000000000000001a2c000701289810000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000000d7375706572666f726d2e78797a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a30783030303030303030303030303030303030303030303030303030303030303030303030303030303000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000006140b987d6b51fd75b66c3b07733beb5167c42fc0000000000000000000000006140b987d6b51fd75b66c3b07733beb5167c42fc00000000000000000000000094b008aa00579c1307b0ef2c499ad98a8ce58e58000000000000000000000000c40f949f8a4e094d1b49a23ea9241d289b7b2819000000000000000000000000000000000000000000000000000000000001e20800000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000001842646478b00000000000000000000000094b008aa00579c1307b0ef2c499ad98a8ce58e58000000000000000000000000000000000000000000000000000000000001e208000000000000000000000000c40f949f8a4e094d1b49a23ea9241d289b7b281900000000000000000000000000000000000000000000000001a2c000701289810000000000000000000000001231deb6f5749ef6ce6943a275a1d3e7486f4eae00000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000840294b008aa00579c1307b0ef2c499ad98a8ce58e5801ffff01962e23cd3f58f887a5238082a75d223f71890629006140b987d6b51fd75b66c3b07733beb5167c42fc010b2c639c533813f4aa9d7837caf62653d097ff8501ffff018ac2f9dac7a2852d44f3c09634444d533e4c078e011231deb6f5749ef6ce6943a275a1d3e7486f4eae00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' - ); // abi.encodeCall(IBaseRouter.singleDirectSingleVaultDeposit, (req)); - - factory.deposit( - permitSingleForP2pYieldProxy, - permit2SignatureForP2pYieldProxy, - - superformCalldata, - - ClientBasisPointsOfDeposit, - ClientBasisPointsOfProfit, - SigDeadline, - p2pSignerSignature - ); - vm.stopPrank(); - } - - function _doWithdraw() private { - bytes memory PLACEHOLDER = abi.encodePacked(proxyAddress); - - bytes memory superformCalldata = bytes.concat(bytes.concat( bytes.concat(bytes.concat(bytes.concat(bytes.concat( - hex'407c7b1d00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000020000000000000000a0000000197116661c85c4e1ee35aa10f7fc5fe5e67b83a5b000000000000000000000000000000000000000000000000002c14939f0666fb000000000000000000000000000000000000000000000000002c14939f0666fb00000000000000000000000000000000000000000000000000000000000013880000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000066000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', - PLACEHOLDER - ), hex'000000000000000000000000') - , PLACEHOLDER), hex'000000000000000000000000000000000000000000000000000000000000068000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000094b008aa00579c1307b0ef2c499ad98a8ce58e5800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000065000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004044630a0d8db58392a5ec14b23ef56401c814f1ce0bff3fd4f7f74c06e092670b4c587c7a600000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000'), - PLACEHOLDER), hex'000000000000000000000000000000000000000000000000000000000000181a0000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000000d7375706572666f726d2e78797a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a30783030303030303030303030303030303030303030303030303030303030303030303030303030303000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000006140b987d6b51fd75b66c3b07733beb5167c42fc0000000000000000000000006140b987d6b51fd75b66c3b07733beb5167c42fc000000000000000000000000c40f949f8a4e094d1b49a23ea9241d289b7b281900000000000000000000000094b008aa00579c1307b0ef2c499ad98a8ce58e58000000000000000000000000000000000000000000000000002c14939f0666fb00000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000001442646478b000000000000000000000000c40f949f8a4e094d1b49a23ea9241d289b7b2819000000000000000000000000000000000000000000000000002c14939f0666fb00000000000000000000000094b008aa00579c1307b0ef2c499ad98a8ce58e58000000000000000000000000000000000000000000000000000000000000181a0000000000000000000000001231deb6f5749ef6ce6943a275a1d3e7486f4eae00000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000004202c40f949f8a4e094d1b49a23ea9241d289b7b281901ffff01e8a05463f7a2796e1bf11a25d317f17ed7fce5e7001231deb6f5749ef6ce6943a275a1d3e7486f4eae000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'); // abi.encodeCall(IBaseRouter.singleDirectSingleVaultWithdraw, (req)); - - vm.startPrank(clientAddress); - P2pSuperformProxy(payable(proxyAddress)).withdraw(superformCalldata); - vm.stopPrank(); - } - - /// @dev Rolls & warps the given number of blocks forward the blockchain. - function _forward(uint256 blocks) internal { - vm.roll(block.number + blocks); - vm.warp(block.timestamp + blocks * 13); - } -} \ No newline at end of file diff --git a/test/OptimismNative.t.sol b/test/OptimismNative.t.sol new file mode 100644 index 0000000..518f9bc --- /dev/null +++ b/test/OptimismNative.t.sol @@ -0,0 +1,554 @@ +// SPDX-FileCopyrightText: 2025 P2P Validator +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.27; + +import "../src/@openzeppelin/contracts/interfaces/IERC4626.sol"; +import "../src/@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import "../src/@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import "../src/access/P2pOperator.sol"; +import "../src/access/P2pOperator.sol"; +import "../src/adapters/superform/p2pSuperformProxyFactory/P2pSuperformProxyFactory.sol"; +import "../src/common/AllowedCalldataChecker.sol"; +import "../src/p2pYieldProxyFactory/P2pYieldProxyFactory.sol"; +import "./mocks/MockAllowedCalldataChecker.sol"; +import "./utils/Error.sol"; +import "./utils/merkle/helper/MerkleReader.sol"; +import "forge-std/Test.sol"; +import "forge-std/Vm.sol"; +import "forge-std/console.sol"; +import "forge-std/console2.sol"; +import {PermitHash} from "../src/@permit2/libraries/PermitHash.sol"; + + +contract OptimismNative is Test, MerkleReader { + using SafeERC20 for IERC20; + + address constant NATIVE = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; + address constant SuperformRouter = 0xa195608C2306A26f727d5199D5A382a4508308DA; + address constant SuperPositions = 0x01dF6fb6a28a89d6bFa53b2b3F20644AbF417678; + address constant RewardsDistributorInstance = 0xce23bD7205bF2B543F6B4eeC00Add0C111FEFc3B; + + address constant RewardsDistributorAdmin = 0xf82F3D7Df94FC2994315c32322DA6238cA2A2f7f; + + address constant P2pTreasury = 0x641ca805C75cC5D1ffa78C0181Aba1F77BD17904; + + uint256 constant SuperformId = 62771017356379199835532377802369906037722899472923496568460; + + bytes constant LiqRequestTxSata = hex'4630a0d8dac814cc41f28f3f61b1c75cf080011e2f868b0037392f8ae14f7b42bae3be4a00000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000b8138fff124dd7f91abf412b73be453fb140568c0000000000000000000000000000000000000000000000000022bd6ab3cf83760000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000000d7375706572666f726d2e78797a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a30783030303030303030303030303030303030303030303030303030303030303030303030303030303000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000006131b5fae19ea4f9d964eac0408e4408b66337b50000000000000000000000006131b5fae19ea4f9d964eac0408e4408b66337b500000000000000000000000000000000000000000000000000000000000000000000000000000000000000001f32b1c2345538c0c6f582fcb022739c4a194ebb000000000000000000000000000000000000000000000000002bd72a2487400000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000664e21fd0e90000000000000000000000000000000000000000000000000000000000000020000000000000000000000000c7d3ab410d49b664d03fe5b1038852ac852b1b29000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000003c000000000000000000000000000000000000000000000000000000000000000f3010100000048000000ba12222222228d8ba445958a75a0704d566bf2c87ca75bdea9dede97f8b13c6641b768650cb837820002000000000000000000d5000000000000000000002bd72a248740000beeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee1f32b1c2345538c0c6f582fcb022739c4a194ebb1231deb6f5749ef6ce6943a275a1d3e7486f4eae000000000000000000000000680a01200000005400000000000000000000000000000000000000000000000000000000000000000000000000000000000000026583c34c00000000000000000024917dcabf7ce84f82e73edb06d29ff62c91ec8f5ff06571bdeb2900000000000000000000000000000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee0000000000000000000000001f32b1c2345538c0c6f582fcb022739c4a194ebb0000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000001c00000000000000000000000001231deb6f5749ef6ce6943a275a1d3e7486f4eae000000000000000000000000000000000000000000000000002bd72a248740000000000000000000000000000000000000000000000000000022bd6ab3cf8376000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000025d7b22536f75726365223a226c692e6669222c22416d6f756e74496e555344223a2232312e3339343431353230343638313437222c22416d6f756e744f7574555344223a2232312e333934363835343533303031343635222c22526566657272616c223a22222c22466c616773223a302c22416d6f756e744f7574223a223130323933303638363230303730313230222c2254696d657374616d70223a313734353438343931322c22526f7574654944223a2238626338353330632d376465312d343437302d393631622d363734666431616662663337222c22496e74656772697479496e666f223a7b224b65794944223a2231222c225369676e6174757265223a2258424a3749307656656b7368513459435953343971574345494e53793755696f304e6249706d556f574945506c743335616b55734c394a3333777857506956794d37685357545470642b62627a56574a394d67694a32346a4e43324861767463766a4a77716145554b723757474b5448387835747134304743362f7478374735594635487a664a49342f66456c353657474a39327635304d664d4b31527366516d326a43444a7849656d376e71484a4644684f43686b6e67394433454d6f6b686d3030696675637a6158574f444d722f346464396e656e58336869364847775a51412b754667557a77387331526373646570725a3557656958705033572b41596a6a394b5a794a4a627346424768524c6768516f3238486843314c3373412f6842346e347a59374669566d6967534a614b4c325475734535677450484935684d6d66764d2f4876437858797a4c413359666b734678413d3d227d7d00000000000000000000000000000000000000000000000000000000000000'; + + P2pSuperformProxyFactory private factory; + + address private clientAddress; + uint256 private clientPrivateKey; + + address private p2pSignerAddress; + uint256 private p2pSignerPrivateKey; + + address private p2pOperatorAddress; + address private nobody; + + uint256 constant SigDeadline = 1789558996; + uint48 constant ClientBasisPointsOfProfit = 8700; // 13% fee + uint48 constant ClientBasisPointsOfDeposit = 10_000; // 0% fee + uint256 constant DepositAmount = 12340000000000000; + uint256 constant VaultAmount = 10293068620070120; + uint256 constant VaultOutputAmount = 10284585609330856; + + address proxyAddress; + + uint48 nonce; + + uint64 public constant CHAIN_ID = 10; + + uint256 totalUSDCToDeposit; + uint256 totalDAIToDeposit; + + ProxyAdmin private admin; + TransparentUpgradeableProxy private tup; + + function setUp() public { + vm.createSelectFork("optimism", 134943023); + + (clientAddress, clientPrivateKey) = makeAddrAndKey("client"); + (p2pSignerAddress, p2pSignerPrivateKey) = makeAddrAndKey("p2pSigner"); + p2pOperatorAddress = makeAddr("p2pOperator"); + nobody = makeAddr("nobody"); + + vm.startPrank(p2pOperatorAddress); + AllowedCalldataChecker implementation = new AllowedCalldataChecker(); + admin = new ProxyAdmin(); + bytes memory initData = abi.encodeWithSelector(AllowedCalldataChecker.initialize.selector); + tup = new TransparentUpgradeableProxy( + address(implementation), + address(admin), + initData + ); + factory = new P2pSuperformProxyFactory( + p2pSignerAddress, + P2pTreasury, + SuperformRouter, + SuperPositions, + address(tup), + RewardsDistributorInstance + ); + vm.stopPrank(); + + proxyAddress = factory.predictP2pYieldProxyAddress( + clientAddress, + ClientBasisPointsOfDeposit, + ClientBasisPointsOfProfit + ); + + deal(clientAddress, 10000e18); + } + + function test_happyPath_native_Optimism() public { + _doDeposit(); + _doWithdraw(); + } + + function test_P2pSuperformProxy__NativeAmountToDepositAfterFeeLessThanliqRequestNativeAmount() public { + IAllowanceTransfer.PermitSingle memory permitSingleForP2pYieldProxy; + bytes memory permit2SignatureForP2pYieldProxy; + bytes memory p2pSignerSignature = _getP2pSignerSignature( + clientAddress, + ClientBasisPointsOfDeposit, + ClientBasisPointsOfProfit, + SigDeadline + ); + + vm.startPrank(clientAddress); + + LiqRequest memory liqRequest = LiqRequest({ + txData: LiqRequestTxSata, + token: NATIVE, + interimToken: address(0), + bridgeId: 101, + liqDstChainId: 10, + nativeAmount: DepositAmount + }); + SingleVaultSFData memory superformData = SingleVaultSFData({ + superformId: SuperformId, + amount: VaultAmount, + outputAmount: VaultOutputAmount, + maxSlippage: 500, + liqRequest: liqRequest, + permit2data: "", + hasDstSwap: false, + retain4626: false, + receiverAddress: proxyAddress, + receiverAddressSP: proxyAddress, + extraFormData: "" + }); + SingleDirectSingleVaultStateReq memory req = SingleDirectSingleVaultStateReq({ + superformData: superformData + }); + + bytes memory superformCalldata = abi.encodeCall(IBaseRouter.singleDirectSingleVaultDeposit, (req)); + + uint256 actual = DepositAmount - 1; + vm.expectRevert(abi.encodeWithSelector( + P2pSuperformProxy__NativeAmountToDepositAfterFeeLessThanliqRequestNativeAmount.selector, + actual, + DepositAmount + )); + factory.deposit{value: actual}( + permitSingleForP2pYieldProxy, + permit2SignatureForP2pYieldProxy, + + superformCalldata, + + ClientBasisPointsOfDeposit, + ClientBasisPointsOfProfit, + SigDeadline, + p2pSignerSignature + ); + vm.stopPrank(); + } + + function testAllowedCalldataChecker__NoAllowedCalldata() public { + _doDeposit(); + + address yieldProtocolAddress = makeAddr("yieldProtocolAddress"); + bytes memory yieldProtocolCalldata = new bytes(42); + + vm.startPrank(clientAddress); + vm.expectRevert(AllowedCalldataChecker__NoAllowedCalldata.selector); + IP2pSuperformProxy(proxyAddress).callAnyFunction( + yieldProtocolAddress, + yieldProtocolCalldata + ); + vm.stopPrank(); + } + + function testP2pSuperformProxy_GetClientAddress() public { + _doDeposit(); + + assertEq(IP2pSuperformProxy(proxyAddress).getClient(), clientAddress); + } + + function testP2pSuperformProxy_GetP2pTreasuryAddress() public { + _doDeposit(); + + assertEq(IP2pSuperformProxy(proxyAddress).getP2pTreasury(), P2pTreasury); + } + + function testP2pSuperformProxy_GetClientBasisPointsOfDeposit() public { + _doDeposit(); + + assertEq(IP2pSuperformProxy(proxyAddress).getClientBasisPointsOfDeposit(), ClientBasisPointsOfDeposit); + } + + function testP2pSuperformProxy_GetClientBasisPointsOfProfit() public { + _doDeposit(); + + assertEq(IP2pSuperformProxy(proxyAddress).getClientBasisPointsOfProfit(), ClientBasisPointsOfProfit); + } + + function testP2pSuperformProxyFactory_GetP2pOperatorAddress() public { + assertEq(factory.getP2pOperator(), p2pOperatorAddress); + } + + function testP2pSuperformProxyFactory_GetP2pSignerAddress() public { + assertEq(factory.getP2pSigner(), p2pSignerAddress); + } + + function testP2pSuperformProxyFactory_GetClientToProxy() public { + _doDeposit(); + + address proxy = factory.predictP2pYieldProxyAddress( + clientAddress, + ClientBasisPointsOfDeposit, + ClientBasisPointsOfProfit + ); + assertEq(proxy, proxyAddress); + } + + function testAllowedCalldataCheckerUpgrade() public { + _doDeposit(); + + address yieldProtocolAddress = makeAddr("yieldProtocolAddress"); + bytes memory yieldProtocolCalldata = new bytes(42); + + vm.startPrank(clientAddress); + vm.expectRevert(AllowedCalldataChecker__NoAllowedCalldata.selector); + IP2pSuperformProxy(proxyAddress).callAnyFunction( + yieldProtocolAddress, + yieldProtocolCalldata + ); + vm.stopPrank(); + + vm.startPrank(p2pOperatorAddress); + MockAllowedCalldataChecker newImplementation = new MockAllowedCalldataChecker(); + admin.upgrade(ITransparentUpgradeableProxy(address(tup)), address(newImplementation)); + vm.stopPrank(); + + vm.startPrank(clientAddress); + vm.expectRevert("Address: call to non-contract"); + IP2pSuperformProxy(proxyAddress).callAnyFunction( + yieldProtocolAddress, + yieldProtocolCalldata + ); + vm.stopPrank(); + } + + function testP2pSuperformProxy_CheckClaim_UnauthorizedAccount() public { + _doDeposit(); + + address unauthorizedClaimer = makeAddr("unauthorizedClaimer"); + uint256[] memory periodIds = new uint256[](1); + periodIds[0] = 1; + + address[][] memory rewardTokens = new address[][](1); + rewardTokens[0] = new address[](1); + rewardTokens[0][0] = makeAddr("rewardToken"); + + uint256[][] memory amountsClaimed = new uint256[][](1); + amountsClaimed[0] = new uint256[](1); + amountsClaimed[0][0] = 100; + + bytes32[][] memory proofs = new bytes32[][](1); + proofs[0] = new bytes32[](1); + proofs[0][0] = bytes32(0); + + vm.startPrank(unauthorizedClaimer); + vm.expectRevert(abi.encodeWithSelector(P2pOperator.P2pOperator__UnauthorizedAccount.selector, unauthorizedClaimer)); + IP2pSuperformProxy(proxyAddress).batchClaim( + periodIds, + rewardTokens, + amountsClaimed, + proofs + ); + vm.stopPrank(); + } + + function testP2pSuperformProxy__ReceiverAddressShouldBeP2pSuperformProxy() public { + IAllowanceTransfer.PermitSingle memory permitSingleForP2pYieldProxy; + bytes memory permit2SignatureForP2pYieldProxy; + bytes memory p2pSignerSignature = _getP2pSignerSignature( + clientAddress, + ClientBasisPointsOfDeposit, + ClientBasisPointsOfProfit, + SigDeadline + ); + + vm.startPrank(clientAddress); + + LiqRequest memory liqRequest = LiqRequest({ + txData: LiqRequestTxSata, + token: NATIVE, + interimToken: address(0), + bridgeId: 101, + liqDstChainId: 10, + nativeAmount: DepositAmount + }); + SingleVaultSFData memory superformData = SingleVaultSFData({ + superformId: SuperformId, + amount: VaultAmount, + outputAmount: VaultOutputAmount, + maxSlippage: 500, + liqRequest: liqRequest, + permit2data: "", + hasDstSwap: false, + retain4626: false, + receiverAddress: address(0x123), // Setting to a different address than proxyAddress + receiverAddressSP: proxyAddress, + extraFormData: "" + }); + SingleDirectSingleVaultStateReq memory req = SingleDirectSingleVaultStateReq({ + superformData: superformData + }); + + bytes memory superformCalldata = abi.encodeCall(IBaseRouter.singleDirectSingleVaultDeposit, (req)); + + vm.expectRevert(abi.encodeWithSelector(P2pSuperformProxy__ReceiverAddressShouldBeP2pSuperformProxy.selector, address(0x123))); + factory.deposit{value: DepositAmount}( + permitSingleForP2pYieldProxy, + permit2SignatureForP2pYieldProxy, + superformCalldata, + ClientBasisPointsOfDeposit, + ClientBasisPointsOfProfit, + SigDeadline, + p2pSignerSignature + ); + vm.stopPrank(); + } + + function testP2pSuperformProxy__ReceiverAddressSPShouldBeP2pSuperformProxy() public { + IAllowanceTransfer.PermitSingle memory permitSingleForP2pYieldProxy; + bytes memory permit2SignatureForP2pYieldProxy; + bytes memory p2pSignerSignature = _getP2pSignerSignature( + clientAddress, + ClientBasisPointsOfDeposit, + ClientBasisPointsOfProfit, + SigDeadline + ); + + vm.startPrank(clientAddress); + + LiqRequest memory liqRequest = LiqRequest({ + txData: LiqRequestTxSata, + token: NATIVE, + interimToken: address(0), + bridgeId: 101, + liqDstChainId: 10, + nativeAmount: DepositAmount + }); + SingleVaultSFData memory superformData = SingleVaultSFData({ + superformId: SuperformId, + amount: VaultAmount, + outputAmount: VaultOutputAmount, + maxSlippage: 500, + liqRequest: liqRequest, + permit2data: "", + hasDstSwap: false, + retain4626: false, + receiverAddress: proxyAddress, + receiverAddressSP: address(0x123), // Setting to a different address than proxyAddress + extraFormData: "" + }); + SingleDirectSingleVaultStateReq memory req = SingleDirectSingleVaultStateReq({ + superformData: superformData + }); + + bytes memory superformCalldata = abi.encodeCall(IBaseRouter.singleDirectSingleVaultDeposit, (req)); + + vm.expectRevert(abi.encodeWithSelector(P2pSuperformProxy__ReceiverAddressSPShouldBeP2pSuperformProxy.selector, address(0x123))); + factory.deposit{value: DepositAmount}( + permitSingleForP2pYieldProxy, + permit2SignatureForP2pYieldProxy, + superformCalldata, + ClientBasisPointsOfDeposit, + ClientBasisPointsOfProfit, + SigDeadline, + p2pSignerSignature + ); + vm.stopPrank(); + } + + function testP2pSuperformProxy__ShouldNotRetain4626() public { + IAllowanceTransfer.PermitSingle memory permitSingleForP2pYieldProxy; + bytes memory permit2SignatureForP2pYieldProxy; + bytes memory p2pSignerSignature = _getP2pSignerSignature( + clientAddress, + ClientBasisPointsOfDeposit, + ClientBasisPointsOfProfit, + SigDeadline + ); + + vm.startPrank(clientAddress); + + LiqRequest memory liqRequest = LiqRequest({ + txData: LiqRequestTxSata, + token: NATIVE, + interimToken: address(0), + bridgeId: 101, + liqDstChainId: 10, + nativeAmount: DepositAmount + }); + SingleVaultSFData memory superformData = SingleVaultSFData({ + superformId: SuperformId, + amount: VaultAmount, + outputAmount: VaultOutputAmount, + maxSlippage: 500, + liqRequest: liqRequest, + permit2data: "", + hasDstSwap: false, + retain4626: true, // Setting retain4626 to true + receiverAddress: proxyAddress, + receiverAddressSP: proxyAddress, + extraFormData: "" + }); + SingleDirectSingleVaultStateReq memory req = SingleDirectSingleVaultStateReq({ + superformData: superformData + }); + + bytes memory superformCalldata = abi.encodeCall(IBaseRouter.singleDirectSingleVaultDeposit, (req)); + + vm.expectRevert(P2pSuperformProxy__ShouldNotRetain4626.selector); + factory.deposit{value: DepositAmount}( + permitSingleForP2pYieldProxy, + permit2SignatureForP2pYieldProxy, + superformCalldata, + ClientBasisPointsOfDeposit, + ClientBasisPointsOfProfit, + SigDeadline, + p2pSignerSignature + ); + vm.stopPrank(); + } + + function _getVaultAddress() private pure returns(address) { + return address(uint160(SuperformId)); + } + + function _getP2pSignerSignature( + address _clientAddress, + uint48 _clientBasisPointsOfDeposit, + uint48 _clientBasisPointsOfProfit, + uint256 _sigDeadline + ) private view returns(bytes memory) { + // p2p signer signing + bytes32 hashForP2pSigner = factory.getHashForP2pSigner( + _clientAddress, + _clientBasisPointsOfDeposit, + _clientBasisPointsOfProfit, + _sigDeadline + ); + bytes32 ethSignedMessageHashForP2pSigner = ECDSA.toEthSignedMessageHash(hashForP2pSigner); + (uint8 v2, bytes32 r2, bytes32 s2) = vm.sign(p2pSignerPrivateKey, ethSignedMessageHashForP2pSigner); + bytes memory p2pSignerSignature = abi.encodePacked(r2, s2, v2); + return p2pSignerSignature; + } + + function _doDeposit() private { + IAllowanceTransfer.PermitSingle memory permitSingleForP2pYieldProxy; + bytes memory permit2SignatureForP2pYieldProxy; + bytes memory p2pSignerSignature = _getP2pSignerSignature( + clientAddress, + ClientBasisPointsOfDeposit, + ClientBasisPointsOfProfit, + SigDeadline + ); + + vm.startPrank(clientAddress); + + LiqRequest memory liqRequest = LiqRequest({ + txData: LiqRequestTxSata, + token: NATIVE, + interimToken: address(0), + bridgeId: 101, + liqDstChainId: 10, + nativeAmount: DepositAmount + }); + SingleVaultSFData memory superformData = SingleVaultSFData({ + superformId: SuperformId, + amount: VaultAmount, + outputAmount: VaultOutputAmount, + maxSlippage: 500, + liqRequest: liqRequest, + permit2data: "", + hasDstSwap: false, + retain4626: false, + receiverAddress: proxyAddress, + receiverAddressSP: proxyAddress, + extraFormData: "" + }); + SingleDirectSingleVaultStateReq memory req = SingleDirectSingleVaultStateReq({ + superformData: superformData + }); + + bytes memory superformCalldata = abi.encodeCall(IBaseRouter.singleDirectSingleVaultDeposit, (req)); + + factory.deposit{value: DepositAmount * 113 / 100}( + permitSingleForP2pYieldProxy, + permit2SignatureForP2pYieldProxy, + + superformCalldata, + + ClientBasisPointsOfDeposit, + ClientBasisPointsOfProfit, + SigDeadline, + p2pSignerSignature + ); + vm.stopPrank(); + } + + function _doWithdraw() private { + LiqRequest memory liqRequest = LiqRequest({ + txData: hex'4630a0d8043bf297c37e0f5ca30079351a85da894514103f8da67224f7bb0b337a1ff61300000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000fd35454f266dc9f672985260029f1686c6b6036c000000000000000000000000000000000000000000000000000227962180fd4f0000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000000d7375706572666f726d2e78797a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a3078303030303030303030303030303030303030303030303030303030303030303030303030303030300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000060e8c01e8e39b10202e39e62001f08092cc03ca000000000000000000000000060e8c01e8e39b10202e39e62001f08092cc03ca0000000000000000000000001f32b1c2345538c0c6f582fcb022739c4a194ebb000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000039843d934a78a00000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000001642646478b0000000000000000000000001f32b1c2345538c0c6f582fcb022739c4a194ebb00000000000000000000000000000000000000000000000000039843d934a78a000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee000000000000000000000000000000000000000000000000000227962180fd4f0000000000000000000000001231deb6f5749ef6ce6943a275a1d3e7486f4eae00000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000070021f32b1c2345538c0c6f582fcb022739c4a194ebb01ffff01bf30ff33cf9c6b0c48702ff17891293b002dfea401060e8c01e8e39b10202e39e62001f08092cc03ca01420000000000000000000000000000000000000601ffff02001231deb6f5749ef6ce6943a275a1d3e7486f4eae0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + token: NATIVE, + interimToken: address(0), + bridgeId: 101, + liqDstChainId: 10, + nativeAmount: 0 + }); + SingleVaultSFData memory superformData = SingleVaultSFData({ + superformId: SuperformId, + amount: 1011008197038423, + outputAmount: 1011842104469386, + maxSlippage: 5000, + liqRequest: liqRequest, + permit2data: "", + hasDstSwap: false, + retain4626: false, + receiverAddress: proxyAddress, + receiverAddressSP: proxyAddress, + extraFormData: "" + }); + SingleDirectSingleVaultStateReq memory req = SingleDirectSingleVaultStateReq({ + superformData: superformData + }); + bytes memory superformCalldata = abi.encodeCall(IBaseRouter.singleDirectSingleVaultWithdraw, (req)); + vm.startPrank(clientAddress); + P2pSuperformProxy(payable(proxyAddress)).withdraw(superformCalldata); + vm.stopPrank(); + } + + /// @dev Rolls & warps the given number of blocks forward the blockchain. + function _forward(uint256 blocks) internal { + vm.roll(block.number + blocks); + vm.warp(block.timestamp + blocks * 13); + } +} \ No newline at end of file diff --git a/test/OptimismUSDT.t.sol b/test/OptimismUSDT.t.sol new file mode 100644 index 0000000..d6cde75 --- /dev/null +++ b/test/OptimismUSDT.t.sol @@ -0,0 +1,523 @@ +// SPDX-FileCopyrightText: 2025 P2P Validator +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.27; + +import "../src/@openzeppelin/contracts/interfaces/IERC4626.sol"; +import "../src/@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; +import "../src/@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; +import "../src/access/P2pOperator.sol"; +import "../src/adapters/superform/p2pSuperformProxyFactory/P2pSuperformProxyFactory.sol"; +import "../src/common/AllowedCalldataChecker.sol"; +import "../src/p2pYieldProxyFactory/P2pYieldProxyFactory.sol"; +import "./utils/merkle/helper/MerkleReader.sol"; +import "forge-std/Test.sol"; +import "forge-std/Vm.sol"; +import "forge-std/console.sol"; +import "forge-std/console2.sol"; +import {PermitHash} from "../src/@permit2/libraries/PermitHash.sol"; + + +contract OptimismUSDT is Test, MerkleReader { + using SafeERC20 for IERC20; + + address constant USDT = 0x94b008aA00579c1307B0EF2c499aD98a8ce58e58; + address constant SuperformRouter = 0xa195608C2306A26f727d5199D5A382a4508308DA; + address constant SuperPositions = 0x01dF6fb6a28a89d6bFa53b2b3F20644AbF417678; + address constant RewardsDistributorInstance = 0xce23bD7205bF2B543F6B4eeC00Add0C111FEFc3B; + + address constant RewardsDistributorAdmin = 0xf82F3D7Df94FC2994315c32322DA6238cA2A2f7f; + + address constant P2pTreasury = 0x641ca805C75cC5D1ffa78C0181Aba1F77BD17904; + + uint256 constant SuperformId = 62771017356190754913478451444852273738203985736479809223259; + + P2pSuperformProxyFactory private factory; + + address private clientAddress; + uint256 private clientPrivateKey; + + address private p2pSignerAddress; + uint256 private p2pSignerPrivateKey; + + address private p2pOperatorAddress; + address private nobody; + + uint256 constant SigDeadline = 1789558996; + uint48 constant ClientBasisPointsOfProfit = 8700; // 13% fee + uint48 constant ClientBasisPointsOfDeposit = 10_000; // 0% fee + uint256 constant DepositAmount = 124071208818789728; + uint256 constant SharesAmount = 124071208818789728; + + address proxyAddress; + + uint48 nonce; + + uint64 public constant CHAIN_ID = 10; + + uint256 totalUSDCToDeposit; + uint256 totalDAIToDeposit; + + function setUp() public { + vm.createSelectFork("optimism", 133700000); + + (clientAddress, clientPrivateKey) = makeAddrAndKey("client"); + (p2pSignerAddress, p2pSignerPrivateKey) = makeAddrAndKey("p2pSigner"); + p2pOperatorAddress = makeAddr("p2pOperator"); + nobody = makeAddr("nobody"); + + vm.startPrank(p2pOperatorAddress); + AllowedCalldataChecker implementation = new AllowedCalldataChecker(); + ProxyAdmin admin = new ProxyAdmin(); + bytes memory initData = abi.encodeWithSelector(AllowedCalldataChecker.initialize.selector); + TransparentUpgradeableProxy tup = new TransparentUpgradeableProxy( + address(implementation), + address(admin), + initData + ); + factory = new P2pSuperformProxyFactory( + p2pSignerAddress, + P2pTreasury, + SuperformRouter, + SuperPositions, + address(tup), + RewardsDistributorInstance + ); + vm.stopPrank(); + + proxyAddress = factory.predictP2pYieldProxyAddress( + clientAddress, + ClientBasisPointsOfDeposit, + ClientBasisPointsOfProfit + ); + + deal(USDT, clientAddress, 10000e18); + } + + function test_happyPath_Optimism() public { + _doDeposit(); + _doWithdraw(); + } + + function test_P2pOperator2Step() public { + // Get initial P2pOperator + address initialP2pOperator = factory.getP2pOperator(); + assertEq(initialP2pOperator, p2pOperatorAddress); + + // Create new P2pOperator address + address newP2pOperator = makeAddr("newP2pOperator"); + + // Step 1: Current P2pOperator initiates transfer + vm.prank(p2pOperatorAddress); + factory.transferP2pOperator(newP2pOperator); + + // Verify pending P2pOperator is set + assertEq(factory.getPendingP2pOperator(), newP2pOperator); + // Verify current P2pOperator hasn't changed yet + assertEq(factory.getP2pOperator(), p2pOperatorAddress); + + // Step 2: New P2pOperator accepts transfer + vm.prank(newP2pOperator); + factory.acceptP2pOperator(); + + // Verify P2pOperator was updated + assertEq(factory.getP2pOperator(), newP2pOperator); + // Verify pending P2pOperator was cleared + assertEq(factory.getPendingP2pOperator(), address(0)); + } + + function testP2pSuperformProxy__LiqRequestTokenShouldBeEqualToPermitForP2pYieldProxyToken() public { + IAllowanceTransfer.PermitSingle memory permitSingleForP2pYieldProxy = IAllowanceTransfer.PermitSingle({ + details: IAllowanceTransfer.PermitDetails({ + token: address(0x1234), // Different token than in liqRequest + amount: uint160(DepositAmount), + expiration: uint48(block.timestamp + 1 days), + nonce: 0 + }), + spender: address(factory), + sigDeadline: SigDeadline + }); + bytes memory permit2SignatureForP2pYieldProxy = _getPermit2SignatureForP2pYieldProxy(permitSingleForP2pYieldProxy); + bytes memory p2pSignerSignature = _getP2pSignerSignature( + clientAddress, + ClientBasisPointsOfDeposit, + ClientBasisPointsOfProfit, + SigDeadline + ); + + vm.startPrank(clientAddress); + if (IERC20(USDT).allowance(clientAddress, address(Permit2Lib.PERMIT2)) == 0) { + IERC20(USDT).safeApprove(address(Permit2Lib.PERMIT2), type(uint256).max); + } + + LiqRequest memory liqRequest = LiqRequest({ + txData: hex'4630a0d896ba9cffae8a22aa75ffdc6910e52d52ee9a199ee31eb8893dc693d7c89ed4a800000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000097116661c85c4e1ee35aa10f7fc5fe5e67b83a5b00000000000000000000000000000000000000000000000001a2c000701289810000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000000d7375706572666f726d2e78797a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a30783030303030303030303030303030303030303030303030303030303030303030303030303030303000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000006140b987d6b51fd75b66c3b07733beb5167c42fc0000000000000000000000006140b987d6b51fd75b66c3b07733beb5167c42fc00000000000000000000000094b008aa00579c1307b0ef2c499ad98a8ce58e58000000000000000000000000c40f949f8a4e094d1b49a23ea9241d289b7b2819000000000000000000000000000000000000000000000000000000000001e20800000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000001842646478b00000000000000000000000094b008aa00579c1307b0ef2c499ad98a8ce58e58000000000000000000000000000000000000000000000000000000000001e208000000000000000000000000c40f949f8a4e094d1b49a23ea9241d289b7b281900000000000000000000000000000000000000000000000001a2c000701289810000000000000000000000001231deb6f5749ef6ce6943a275a1d3e7486f4eae00000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000840294b008aa00579c1307b0ef2c499ad98a8ce58e5801ffff01962e23cd3f58f887a5238082a75d223f71890629006140b987d6b51fd75b66c3b07733beb5167c42fc010b2c639c533813f4aa9d7837caf62653d097ff8501ffff018ac2f9dac7a2852d44f3c09634444d533e4c078e011231deb6f5749ef6ce6943a275a1d3e7486f4eae0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + token: USDT, + interimToken: address(0), + bridgeId: 101, + liqDstChainId: 10, + nativeAmount: 0 + }); + SingleVaultSFData memory superformData = SingleVaultSFData({ + superformId: SuperformId, + amount: DepositAmount, + outputAmount: SharesAmount, + maxSlippage: 500, + liqRequest: liqRequest, + permit2data: "", + hasDstSwap: false, + retain4626: false, + receiverAddress: proxyAddress, + receiverAddressSP: proxyAddress, + extraFormData: "" + }); + SingleDirectSingleVaultStateReq memory req = SingleDirectSingleVaultStateReq({ + superformData: superformData + }); + + bytes memory superformCalldata = abi.encodeCall(IBaseRouter.singleDirectSingleVaultDeposit, (req)); + + vm.expectRevert(abi.encodeWithSelector( + P2pSuperformProxy__LiqRequestTokenShouldBeEqualToPermitForP2pYieldProxyToken.selector, + USDT, + address(0x1234) + )); + factory.deposit( + permitSingleForP2pYieldProxy, + permit2SignatureForP2pYieldProxy, + + superformCalldata, + + ClientBasisPointsOfDeposit, + ClientBasisPointsOfProfit, + SigDeadline, + p2pSignerSignature + ); + vm.stopPrank(); + } + + function test_P2pYieldProxyFactory__InvalidP2pSignerSignature() public { + IAllowanceTransfer.PermitSingle memory permitSingleForP2pYieldProxy; + bytes memory permit2SignatureForP2pYieldProxy; + bytes memory p2pSignerSignature; + bytes memory superformCalldata = new bytes(3); // Less than 4 bytes for function selector + + vm.startPrank(clientAddress); + vm.expectRevert(P2pYieldProxyFactory__InvalidP2pSignerSignature.selector); + factory.deposit( + permitSingleForP2pYieldProxy, + permit2SignatureForP2pYieldProxy, + + superformCalldata, + + ClientBasisPointsOfDeposit, + ClientBasisPointsOfProfit, + SigDeadline, + p2pSignerSignature + ); + vm.stopPrank(); + } + + function test_P2pSuperformProxy__SuperformCalldataTooShort() public { + IAllowanceTransfer.PermitSingle memory permitSingleForP2pYieldProxy; + bytes memory permit2SignatureForP2pYieldProxy; + bytes memory p2pSignerSignature = _getP2pSignerSignature( + clientAddress, + ClientBasisPointsOfDeposit, + ClientBasisPointsOfProfit, + SigDeadline + ); + bytes memory superformCalldata = new bytes(3); // Less than 4 bytes for function selector + + vm.startPrank(clientAddress); + vm.expectRevert(P2pSuperformProxy__SuperformCalldataTooShort.selector); + factory.deposit( + permitSingleForP2pYieldProxy, + permit2SignatureForP2pYieldProxy, + + superformCalldata, + + ClientBasisPointsOfDeposit, + ClientBasisPointsOfProfit, + SigDeadline, + p2pSignerSignature + ); + vm.stopPrank(); + } + + function test_P2pSuperformProxy__SelectorNotSupported() public { + IAllowanceTransfer.PermitSingle memory permitSingleForP2pYieldProxy; + bytes memory permit2SignatureForP2pYieldProxy; + bytes memory p2pSignerSignature = _getP2pSignerSignature( + clientAddress, + ClientBasisPointsOfDeposit, + ClientBasisPointsOfProfit, + SigDeadline + ); + + // Create calldata with an unsupported selector + bytes4 unsupportedSelector = bytes4(keccak256("unsupportedFunction()")); + bytes memory superformCalldata = abi.encodePacked(unsupportedSelector, "42"); + + vm.startPrank(clientAddress); + vm.expectRevert(abi.encodeWithSelector(P2pSuperformProxy__SelectorNotSupported.selector, unsupportedSelector)); + factory.deposit( + permitSingleForP2pYieldProxy, + permit2SignatureForP2pYieldProxy, + + superformCalldata, + + ClientBasisPointsOfDeposit, + ClientBasisPointsOfProfit, + SigDeadline, + p2pSignerSignature + ); + vm.stopPrank(); + } + + function test_batchclaim_proxy() public { + _doDeposit(); + + _addRoot(); + _addRoot24(); + + // common user + address user = proxyAddress; + + uint256[] memory periodIds = new uint256[](2); + periodIds[0] = 23; + periodIds[1] = 24; + + bytes32[][] memory proofs = new bytes32[][](2); + + address[][] memory tokensToClaim = new address[][](2); + + uint256[][] memory amountsToClaim = new uint256[][](2); + for (uint256 periodId = 0; periodId < 2; periodId++) { + (,,,, bytes32[] memory proof_, address[] memory tokensToClaim_, uint256[] memory amountsToClaim_) = + _generateMerkleTree(MerkleReader.MerkleArgs(periodId + 23, user, CHAIN_ID)); + + proofs[periodId] = proof_; + tokensToClaim[periodId] = tokensToClaim_; + amountsToClaim[periodId] = amountsToClaim_; + } + + vm.prank(clientAddress); + IP2pSuperformProxy(payable(proxyAddress)).batchClaim( + periodIds, + tokensToClaim, + amountsToClaim, + proofs + ); + } + + function test_batchclaim_randomClaimer_claimAndAlreadyClaimed() public { + _addRoot(); + _addRoot24(); + + // common user + address user = proxyAddress; + + uint256[] memory periodIds = new uint256[](2); + periodIds[0] = 23; + periodIds[1] = 24; + + bytes32[][] memory proofs = new bytes32[][](2); + + address[][] memory tokensToClaim = new address[][](2); + + uint256[][] memory amountsToClaim = new uint256[][](2); + for (uint256 periodId = 0; periodId < 2; periodId++) { + (,,,, bytes32[] memory proof_, address[] memory tokensToClaim_, uint256[] memory amountsToClaim_) = + _generateMerkleTree(MerkleReader.MerkleArgs(periodId + 23, user, CHAIN_ID)); + + proofs[periodId] = proof_; + tokensToClaim[periodId] = tokensToClaim_; + amountsToClaim[periodId] = amountsToClaim_; + } + + /// @dev tests a claim initiated by a random user on behalf of user + vm.prank(address(0x777)); + IRewardsDistributor(RewardsDistributorInstance).batchClaim(user, periodIds, tokensToClaim, amountsToClaim, proofs); + + vm.expectRevert(IRewardsDistributor.ALREADY_CLAIMED.selector); + vm.prank(user); + IRewardsDistributor(RewardsDistributorInstance).batchClaim(user, periodIds, tokensToClaim, amountsToClaim, proofs); + } + + function _addRoot() internal { + bytes32 root; + uint256 usdcToDeposit; + uint256 daiToDeposit; + uint256 periodId = 23; // IRewardsDistributor(RewardsDistributorInstance).currentPeriodId(); + (root,, usdcToDeposit, daiToDeposit,,,) = _generateMerkleTree(MerkleReader.MerkleArgs(periodId, proxyAddress, CHAIN_ID)); + + vm.startPrank(RewardsDistributorAdmin); + IRewardsDistributor(RewardsDistributorInstance).setPeriodicRewards(root); + totalUSDCToDeposit += usdcToDeposit; + totalDAIToDeposit += daiToDeposit; + + deal(USDC, RewardsDistributorInstance, totalUSDCToDeposit); + deal(DAI, RewardsDistributorInstance, totalDAIToDeposit); + vm.stopPrank(); + } + + function _addRoot24() internal { + bytes32 root; + uint256 usdcToDeposit; + uint256 daiToDeposit; + (root,, usdcToDeposit, daiToDeposit,,,) = _generateMerkleTree(MerkleReader.MerkleArgs(24, proxyAddress, CHAIN_ID)); + + vm.startPrank(RewardsDistributorAdmin); + IRewardsDistributor(RewardsDistributorInstance).setPeriodicRewards(root); + totalUSDCToDeposit += usdcToDeposit; + totalDAIToDeposit += daiToDeposit; + + deal(USDC, RewardsDistributorInstance, totalUSDCToDeposit); + deal(DAI, RewardsDistributorInstance, totalDAIToDeposit); + vm.stopPrank(); + } + + function _getVaultAddress() private pure returns(address) { + return address(uint160(SuperformId)); + } + + function _getPermitSingleForP2pYieldProxy() private returns(IAllowanceTransfer.PermitSingle memory) { + IAllowanceTransfer.PermitDetails memory permitDetails = IAllowanceTransfer.PermitDetails({ + token: USDT, + amount: uint160(DepositAmount), + expiration: uint48(SigDeadline), + nonce: nonce + }); + nonce++; + + // data for factory + IAllowanceTransfer.PermitSingle memory permitSingleForP2pYieldProxy = IAllowanceTransfer.PermitSingle({ + details: permitDetails, + spender: proxyAddress, + sigDeadline: SigDeadline + }); + + return permitSingleForP2pYieldProxy; + } + + function _getPermit2SignatureForP2pYieldProxy(IAllowanceTransfer.PermitSingle memory permitSingleForP2pYieldProxy) private view returns(bytes memory) { + bytes32 permitSingleForP2pYieldProxyHash = factory.getPermit2HashTypedData(PermitHash.hash(permitSingleForP2pYieldProxy)); + (uint8 v1, bytes32 r1, bytes32 s1) = vm.sign(clientPrivateKey, permitSingleForP2pYieldProxyHash); + bytes memory permit2SignatureForP2pYieldProxy = abi.encodePacked(r1, s1, v1); + return permit2SignatureForP2pYieldProxy; + } + + function _getP2pSignerSignature( + address _clientAddress, + uint48 _clientBasisPointsOfDeposit, + uint48 _clientBasisPointsOfProfit, + uint256 _sigDeadline + ) private view returns(bytes memory) { + // p2p signer signing + bytes32 hashForP2pSigner = factory.getHashForP2pSigner( + _clientAddress, + _clientBasisPointsOfDeposit, + _clientBasisPointsOfProfit, + _sigDeadline + ); + bytes32 ethSignedMessageHashForP2pSigner = ECDSA.toEthSignedMessageHash(hashForP2pSigner); + (uint8 v2, bytes32 r2, bytes32 s2) = vm.sign(p2pSignerPrivateKey, ethSignedMessageHashForP2pSigner); + bytes memory p2pSignerSignature = abi.encodePacked(r2, s2, v2); + return p2pSignerSignature; + } + + function _doDeposit() private { + IAllowanceTransfer.PermitSingle memory permitSingleForP2pYieldProxy = _getPermitSingleForP2pYieldProxy(); + bytes memory permit2SignatureForP2pYieldProxy = _getPermit2SignatureForP2pYieldProxy(permitSingleForP2pYieldProxy); + bytes memory p2pSignerSignature = _getP2pSignerSignature( + clientAddress, + ClientBasisPointsOfDeposit, + ClientBasisPointsOfProfit, + SigDeadline + ); + + vm.startPrank(clientAddress); + if (IERC20(USDT).allowance(clientAddress, address(Permit2Lib.PERMIT2)) == 0) { + IERC20(USDT).safeApprove(address(Permit2Lib.PERMIT2), type(uint256).max); + } + + LiqRequest memory liqRequest = LiqRequest({ + txData: hex'4630a0d896ba9cffae8a22aa75ffdc6910e52d52ee9a199ee31eb8893dc693d7c89ed4a800000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000010000000000000000000000000097116661c85c4e1ee35aa10f7fc5fe5e67b83a5b00000000000000000000000000000000000000000000000001a2c000701289810000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000000d7375706572666f726d2e78797a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a30783030303030303030303030303030303030303030303030303030303030303030303030303030303000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000006140b987d6b51fd75b66c3b07733beb5167c42fc0000000000000000000000006140b987d6b51fd75b66c3b07733beb5167c42fc00000000000000000000000094b008aa00579c1307b0ef2c499ad98a8ce58e58000000000000000000000000c40f949f8a4e094d1b49a23ea9241d289b7b2819000000000000000000000000000000000000000000000000000000000001e20800000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000001842646478b00000000000000000000000094b008aa00579c1307b0ef2c499ad98a8ce58e58000000000000000000000000000000000000000000000000000000000001e208000000000000000000000000c40f949f8a4e094d1b49a23ea9241d289b7b281900000000000000000000000000000000000000000000000001a2c000701289810000000000000000000000001231deb6f5749ef6ce6943a275a1d3e7486f4eae00000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000840294b008aa00579c1307b0ef2c499ad98a8ce58e5801ffff01962e23cd3f58f887a5238082a75d223f71890629006140b987d6b51fd75b66c3b07733beb5167c42fc010b2c639c533813f4aa9d7837caf62653d097ff8501ffff018ac2f9dac7a2852d44f3c09634444d533e4c078e011231deb6f5749ef6ce6943a275a1d3e7486f4eae0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + token: USDT, + interimToken: address(0), + bridgeId: 101, + liqDstChainId: 10, + nativeAmount: 0 + }); + SingleVaultSFData memory superformData = SingleVaultSFData({ + superformId: SuperformId, + amount: DepositAmount, + outputAmount: SharesAmount, + maxSlippage: 500, + liqRequest: liqRequest, + permit2data: "", + hasDstSwap: false, + retain4626: false, + receiverAddress: proxyAddress, + receiverAddressSP: proxyAddress, + extraFormData: "" + }); + SingleDirectSingleVaultStateReq memory req = SingleDirectSingleVaultStateReq({ + superformData: superformData + }); + + bytes memory superformCalldata = abi.encodeCall(IBaseRouter.singleDirectSingleVaultDeposit, (req)); + + factory.deposit( + permitSingleForP2pYieldProxy, + permit2SignatureForP2pYieldProxy, + + superformCalldata, + + ClientBasisPointsOfDeposit, + ClientBasisPointsOfProfit, + SigDeadline, + p2pSignerSignature + ); + vm.stopPrank(); + } + + function _doWithdraw() private { + LiqRequest memory liqRequest = LiqRequest({ + txData: hex'4630a0d8db58392a5ec14b23ef56401c814f1ce0bff3fd4f7f74c06e092670b4c587c7a600000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000100000000000000000000000000fd35454f266dc9f672985260029f1686c6b6036c000000000000000000000000000000000000000000000000000000000000181a0000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000000d7375706572666f726d2e78797a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002a30783030303030303030303030303030303030303030303030303030303030303030303030303030303000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000006140b987d6b51fd75b66c3b07733beb5167c42fc0000000000000000000000006140b987d6b51fd75b66c3b07733beb5167c42fc000000000000000000000000c40f949f8a4e094d1b49a23ea9241d289b7b281900000000000000000000000094b008aa00579c1307b0ef2c499ad98a8ce58e58000000000000000000000000000000000000000000000000002c14939f0666fb00000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000001442646478b000000000000000000000000c40f949f8a4e094d1b49a23ea9241d289b7b2819000000000000000000000000000000000000000000000000002c14939f0666fb00000000000000000000000094b008aa00579c1307b0ef2c499ad98a8ce58e58000000000000000000000000000000000000000000000000000000000000181a0000000000000000000000001231deb6f5749ef6ce6943a275a1d3e7486f4eae00000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000004202c40f949f8a4e094d1b49a23ea9241d289b7b281901ffff01e8a05463f7a2796e1bf11a25d317f17ed7fce5e7001231deb6f5749ef6ce6943a275a1d3e7486f4eae00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', + token: USDT, + interimToken: address(0), + bridgeId: 101, + liqDstChainId: 10, + nativeAmount: 0 + }); + SingleVaultSFData memory superformData = SingleVaultSFData({ + superformId: SuperformId, + amount: 12407523236013819, + outputAmount: 12407523236013819, + maxSlippage: 5000, + liqRequest: liqRequest, + permit2data: "", + hasDstSwap: false, + retain4626: false, + receiverAddress: proxyAddress, + receiverAddressSP: proxyAddress, + extraFormData: "" + }); + SingleDirectSingleVaultStateReq memory req = SingleDirectSingleVaultStateReq({ + superformData: superformData + }); + bytes memory superformCalldata = abi.encodeCall(IBaseRouter.singleDirectSingleVaultWithdraw, (req)); + + vm.startPrank(clientAddress); + P2pSuperformProxy(payable(proxyAddress)).withdraw(superformCalldata); + vm.stopPrank(); + } + + /// @dev Rolls & warps the given number of blocks forward the blockchain. + function _forward(uint256 blocks) internal { + vm.roll(block.number + blocks); + vm.warp(block.timestamp + blocks * 13); + } +} \ No newline at end of file diff --git a/test/mocks/MockAllowedCalldataChecker.sol b/test/mocks/MockAllowedCalldataChecker.sol new file mode 100644 index 0000000..abc33dd --- /dev/null +++ b/test/mocks/MockAllowedCalldataChecker.sol @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: 2025 P2P Validator +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.27; + +import "../../src/@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import "../../src/common/IAllowedCalldataChecker.sol"; + +/// @title MockAllowedCalldataChecker +/// @author P2P Validator +/// @notice Mock. Do NOT deploy!! +contract MockAllowedCalldataChecker is IAllowedCalldataChecker, Initializable { + + function initialize() public initializer { + // do nothing in this implementation + } + + /// @inheritdoc IAllowedCalldataChecker + function checkCalldata( + address, + bytes4, + bytes calldata + ) public pure { + // don't revert + } +} diff --git a/test/utils/Error.sol b/test/utils/Error.sol new file mode 100644 index 0000000..4a28483 --- /dev/null +++ b/test/utils/Error.sol @@ -0,0 +1,490 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity 0.8.27; + +library Error { + ////////////////////////////////////////////////////////////// + // CONFIGURATION ERRORS // + ////////////////////////////////////////////////////////////// + ///@notice errors thrown in protocol setup + + /// @dev thrown if chain id exceeds max(uint64) + error BLOCK_CHAIN_ID_OUT_OF_BOUNDS(); // 7ecdf933 + + /// @dev thrown if not possible to revoke a role in broadcasting + error CANNOT_REVOKE_NON_BROADCASTABLE_ROLES(); // c1901a05 + + /// @dev thrown if not possible to revoke last admin + error CANNOT_REVOKE_LAST_ADMIN(); // a567a5a2 + + /// @dev thrown if trying to set again pseudo immutables in super registry + error DISABLED(); // acb78998 + + /// @dev thrown if rescue delay is not yet set for a chain + error DELAY_NOT_SET(); // d0b3066f + + /// @dev thrown if get native token price estimate in paymentHelper is 0 + error INVALID_NATIVE_TOKEN_PRICE(); // 991f334e + + /// @dev thrown if wormhole refund chain id is not set + error REFUND_CHAIN_ID_NOT_SET(); // 789bb36b + + /// @dev thrown if wormhole relayer is not set + error RELAYER_NOT_SET(); // e8baf999 + + /// @dev thrown if a role to be revoked is not assigned + error ROLE_NOT_ASSIGNED(); // 35c2f322 + + ////////////////////////////////////////////////////////////// + // AUTHORIZATION ERRORS // + ////////////////////////////////////////////////////////////// + ///@notice errors thrown if functions cannot be called + + /// COMMON AUTHORIZATION ERRORS + /// --------------------------------------------------------- + + /// @dev thrown if caller is not address(this), internal call + error INVALID_INTERNAL_CALL(); // 6566f0ae + + /// @dev thrown if msg.sender is not a valid amb implementation + error NOT_AMB_IMPLEMENTATION(); // 00e1b7be + + /// @dev thrown if msg.sender is not an allowed broadcaster + error NOT_ALLOWED_BROADCASTER(); // 5ac755f1 + + /// @dev thrown if msg.sender is not broadcast amb implementation + error NOT_BROADCAST_AMB_IMPLEMENTATION(); // b3af61c9 + + /// @dev thrown if msg.sender is not broadcast state registry + error NOT_BROADCAST_REGISTRY(); // b0acaf9d + + /// @dev thrown if msg.sender is not core state registry + error NOT_CORE_STATE_REGISTRY(); // 8c1715e2 + + /// @dev thrown if msg.sender is not emergency admin + error NOT_EMERGENCY_ADMIN(); // 445b0c3a + + /// @dev thrown if msg.sender is not emergency queue + error NOT_EMERGENCY_QUEUE(); // 94949e62 + + /// @dev thrown if msg.sender is not minter + error NOT_MINTER(); // 00914334 + + /// @dev thrown if msg.sender is not minter state registry + error NOT_MINTER_STATE_REGISTRY_ROLE(); // ec1845b2 + + /// @dev thrown if msg.sender is not paymaster + error NOT_PAYMASTER(); // 74891f76 + + /// @dev thrown if msg.sender is not payment admin + error NOT_PAYMENT_ADMIN(); // 62108606 + + /// @dev thrown if msg.sender is not protocol admin + error NOT_PROTOCOL_ADMIN(); // dc855554 + + /// @dev thrown if msg.sender is not state registry + error NOT_STATE_REGISTRY(); // 60e6a64e + + /// @dev thrown if msg.sender is not super registry + error NOT_SUPER_REGISTRY(); // e7b6503f + + /// @dev thrown if msg.sender is not superform router + error NOT_SUPERFORM_ROUTER(); // 158a2f6b + + /// @dev thrown if msg.sender is not a superform + error NOT_SUPERFORM(); // 5279abe6 + + /// @dev thrown if msg.sender is not superform factory + error NOT_SUPERFORM_FACTORY(); // c6ca730c + + /// @dev thrown if msg.sender is not timelock form + error NOT_TIMELOCK_SUPERFORM(); // 41645ec2 + + /// @dev thrown if msg.sender is not timelock state registry + error NOT_TIMELOCK_STATE_REGISTRY(); // ebe1e902 + + /// @dev thrown if msg.sender is not user or disputer + error NOT_VALID_DISPUTER(); // d8f8c1c0 + + /// @dev thrown if the msg.sender is not privileged caller + error NOT_PRIVILEGED_CALLER(bytes32 role); // c2703cd6 + + /// STATE REGISTRY AUTHORIZATION ERRORS + /// --------------------------------------------------------- + + /// @dev layerzero adapter specific error, thrown if caller not layerzero endpoint + error CALLER_NOT_ENDPOINT(); // 785db8f2 + + /// @dev hyperlane adapter specific error, thrown if caller not hyperlane mailbox + error CALLER_NOT_MAILBOX(); // b3c9ad31 + + /// @dev wormhole relayer specific error, thrown if caller not wormhole relayer + error CALLER_NOT_RELAYER(); // 5aa1e945 + + /// @dev thrown if src chain sender is not valid + error INVALID_SRC_SENDER(); // 1d527bfc + + ////////////////////////////////////////////////////////////// + // INPUT VALIDATION ERRORS // + ////////////////////////////////////////////////////////////// + ///@notice errors thrown if input variables are not valid + + /// COMMON INPUT VALIDATION ERRORS + /// --------------------------------------------------------- + + /// @dev thrown if there is an array length mismatch + error ARRAY_LENGTH_MISMATCH(); // 88adebd2 + + /// @dev thrown if payload id does not exist + error INVALID_PAYLOAD_ID(); // abb45946 + + /// @dev error thrown when msg value should be zero in certain payable functions + error MSG_VALUE_NOT_ZERO(); // 308f275d + + /// @dev thrown if amb ids length is 0 + error ZERO_AMB_ID_LENGTH(); // 308f275d + + /// @dev thrown if address input is address 0 + error ZERO_ADDRESS(); // 538ba4f9 + + /// @dev thrown if amount input is 0 + error ZERO_AMOUNT(); // 538ba4f9 + + /// @dev thrown if final token is address 0 + error ZERO_FINAL_TOKEN(); // 636b4ef2 + + /// @dev thrown if value input is 0 + error ZERO_INPUT_VALUE(); // 021b4ea1 + + /// SUPERFORM ROUTER INPUT VALIDATION ERRORS + /// --------------------------------------------------------- + + /// @dev thrown if the vaults data is invalid + error INVALID_SUPERFORMS_DATA(); // 29c0f4af + + /// @dev thrown if receiver address is not set + error RECEIVER_ADDRESS_NOT_SET(); // abd840db + + /// SUPERFORM FACTORY INPUT VALIDATION ERRORS + /// --------------------------------------------------------- + + /// @dev thrown if a form is not ERC165 compatible + error ERC165_UNSUPPORTED(); + + /// @dev thrown if a form is not form interface compatible + error FORM_INTERFACE_UNSUPPORTED(); + + /// @dev error thrown if form implementation address already exists + error FORM_IMPLEMENTATION_ALREADY_EXISTS(); + + /// @dev error thrown if form implementation id already exists + error FORM_IMPLEMENTATION_ID_ALREADY_EXISTS(); + + /// @dev thrown if a form does not exist + error FORM_DOES_NOT_EXIST(); + + /// @dev thrown if form id is larger than max uint16 + error INVALID_FORM_ID(); + + /// @dev thrown if superform not on factory + error SUPERFORM_ID_NONEXISTENT(); + + /// @dev thrown if same vault and form implementation is used to create new superform + error VAULT_FORM_IMPLEMENTATION_COMBINATION_EXISTS(); + + /// FORM INPUT VALIDATION ERRORS + /// --------------------------------------------------------- + + /// @dev thrown if in case of no txData, if liqData.token != vault.asset() + /// in case of txData, if token output of swap != vault.asset() + error DIFFERENT_TOKENS(); + + /// @dev thrown if the amount in direct withdraw is not correct + error DIRECT_WITHDRAW_INVALID_LIQ_REQUEST(); + + /// @dev thrown if the amount in xchain withdraw is not correct + error XCHAIN_WITHDRAW_INVALID_LIQ_REQUEST(); + + /// LIQUIDITY BRIDGE INPUT VALIDATION ERRORS + /// --------------------------------------------------------- + + /// @dev thrown if route id is blacklisted in socket + error BLACKLISTED_ROUTE_ID(); + + /// @dev thrown if route id is not blacklisted in socket + error NOT_BLACKLISTED_ROUTE_ID(); + + /// @dev error thrown when txData selector of lifi bridge is a blacklisted selector + error BLACKLISTED_SELECTOR(); + + /// @dev error thrown when txData selector of lifi bridge is not a blacklisted selector + error NOT_BLACKLISTED_SELECTOR(); + + /// @dev thrown if a certain action of the user is not allowed given the txData provided + error INVALID_ACTION(); + + /// @dev thrown if in deposits, the liqDstChainId doesn't match the stateReq dstChainId + error INVALID_DEPOSIT_LIQ_DST_CHAIN_ID(); + + /// @dev thrown if index is invalid + error INVALID_INDEX(); + + /// @dev thrown if the chain id in the txdata is invalid + error INVALID_TXDATA_CHAIN_ID(); + + /// @dev thrown if the validation of bridge txData fails due to a destination call present + error INVALID_TXDATA_NO_DESTINATIONCALL_ALLOWED(); + + /// @dev thrown if the validation of bridge txData fails due to wrong receiver + error INVALID_TXDATA_RECEIVER(); + + /// @dev thrown if the validation of bridge txData fails due to wrong token + error INVALID_TXDATA_TOKEN(); + + /// @dev thrown if txData is not present (in case of xChain actions) + error NO_TXDATA_PRESENT(); + + /// STATE REGISTRY INPUT VALIDATION ERRORS + /// --------------------------------------------------------- + + /// @dev thrown if payload is being updated with final amounts length different than amounts length + error DIFFERENT_PAYLOAD_UPDATE_AMOUNTS_LENGTH(); + + /// @dev thrown if payload is being updated with tx data length different than liq data length + error DIFFERENT_PAYLOAD_UPDATE_TX_DATA_LENGTH(); + + /// @dev thrown if keeper update final token is different than the vault underlying + error INVALID_UPDATE_FINAL_TOKEN(); + + /// @dev thrown if broadcast finality for wormhole is invalid + error INVALID_BROADCAST_FINALITY(); + + /// @dev thrown if amb id is not valid leading to an address 0 of the implementation + error INVALID_BRIDGE_ID(); + + /// @dev thrown if chain id involved in xchain message is invalid + error INVALID_CHAIN_ID(); + + /// @dev thrown if payload update amount isn't equal to dst swapper amount + error INVALID_DST_SWAP_AMOUNT(); + + /// @dev thrown if message amb and proof amb are the same + error INVALID_PROOF_BRIDGE_ID(); + + /// @dev thrown if order of proof AMBs is incorrect, either duplicated or not incrementing + error INVALID_PROOF_BRIDGE_IDS(); + + /// @dev thrown if rescue data lengths are invalid + error INVALID_RESCUE_DATA(); + + /// @dev thrown if delay is invalid + error INVALID_TIMELOCK_DELAY(); + + /// @dev thrown if amounts being sent in update payload mean a negative slippage + error NEGATIVE_SLIPPAGE(); + + /// @dev thrown if slippage is outside of bounds + error SLIPPAGE_OUT_OF_BOUNDS(); + + /// SUPERPOSITION INPUT VALIDATION ERRORS + /// --------------------------------------------------------- + + /// @dev thrown if src senders mismatch in state sync + error SRC_SENDER_MISMATCH(); + + /// @dev thrown if src tx types mismatch in state sync + error SRC_TX_TYPE_MISMATCH(); + + ////////////////////////////////////////////////////////////// + // EXECUTION ERRORS // + ////////////////////////////////////////////////////////////// + ///@notice errors thrown due to function execution logic + + /// COMMON EXECUTION ERRORS + /// --------------------------------------------------------- + + /// @dev thrown if the swap in a direct deposit resulted in insufficient tokens + error DIRECT_DEPOSIT_SWAP_FAILED(); + + /// @dev thrown if payload is not unique + error DUPLICATE_PAYLOAD(); + + /// @dev thrown if native tokens fail to be sent to superform contracts + error FAILED_TO_SEND_NATIVE(); + + /// @dev thrown if allowance is not correct to deposit + error INSUFFICIENT_ALLOWANCE_FOR_DEPOSIT(); + + /// @dev thrown if contract has insufficient balance for operations + error INSUFFICIENT_BALANCE(); + + /// @dev thrown if native amount is not at least equal to the amount in the request + error INSUFFICIENT_NATIVE_AMOUNT(); + + /// @dev thrown if payload cannot be decoded + error INVALID_PAYLOAD(); + + /// @dev thrown if payload status is invalid + error INVALID_PAYLOAD_STATUS(); + + /// @dev thrown if payload type is invalid + error INVALID_PAYLOAD_TYPE(); + + /// LIQUIDITY BRIDGE EXECUTION ERRORS + /// --------------------------------------------------------- + + /// @dev thrown if we try to decode the final swap output token in a xChain liquidity bridging action + error CANNOT_DECODE_FINAL_SWAP_OUTPUT_TOKEN(); + + /// @dev thrown if liquidity bridge fails for erc20 or native tokens + error FAILED_TO_EXECUTE_TXDATA(address token); + + /// @dev thrown if asset being used for deposit mismatches in multivault deposits + error INVALID_DEPOSIT_TOKEN(); + + /// STATE REGISTRY EXECUTION ERRORS + /// --------------------------------------------------------- + + /// @dev thrown if bridge tokens haven't arrived to destination + error BRIDGE_TOKENS_PENDING(); + + /// @dev thrown if withdrawal tx data cannot be updated + error CANNOT_UPDATE_WITHDRAW_TX_DATA(); + + /// @dev thrown if rescue passed dispute deadline + error DISPUTE_TIME_ELAPSED(); + + /// @dev thrown if message failed to reach the specified level of quorum needed + error INSUFFICIENT_QUORUM(); + + /// @dev thrown if broadcast payload is invalid + error INVALID_BROADCAST_PAYLOAD(); + + /// @dev thrown if broadcast fee is invalid + error INVALID_BROADCAST_FEE(); + + /// @dev thrown if retry fees is less than required + error INVALID_RETRY_FEE(); + + /// @dev thrown if broadcast message type is wrong + error INVALID_MESSAGE_TYPE(); + + /// @dev thrown if payload hash is invalid during `retryMessage` on Layezero implementation + error INVALID_PAYLOAD_HASH(); + + /// @dev thrown if update payload function was called on a wrong payload + error INVALID_PAYLOAD_UPDATE_REQUEST(); + + /// @dev thrown if a state registry id is 0 + error INVALID_REGISTRY_ID(); + + /// @dev thrown if a form state registry id is 0 + error INVALID_FORM_REGISTRY_ID(); + + /// @dev thrown if trying to finalize the payload but the withdraw is still locked + error LOCKED(); + + /// @dev thrown if payload is already updated (during xChain deposits) + error PAYLOAD_ALREADY_UPDATED(); + + /// @dev thrown if payload is already processed + error PAYLOAD_ALREADY_PROCESSED(); + + /// @dev thrown if payload is not in UPDATED state + error PAYLOAD_NOT_UPDATED(); + + /// @dev thrown if rescue is still in timelocked state + error RESCUE_LOCKED(); + + /// @dev thrown if rescue is already proposed + error RESCUE_ALREADY_PROPOSED(); + + /// @dev thrown if payload hash is zero during `retryMessage` on Layezero implementation + error ZERO_PAYLOAD_HASH(); + + /// DST SWAPPER EXECUTION ERRORS + /// --------------------------------------------------------- + + /// @dev thrown if process dst swap is tried for processed payload id + error DST_SWAP_ALREADY_PROCESSED(); + + /// @dev thrown if indices have duplicates + error DUPLICATE_INDEX(); + + /// @dev thrown if failed dst swap is already updated + error FAILED_DST_SWAP_ALREADY_UPDATED(); + + /// @dev thrown if indices are out of bounds + error INDEX_OUT_OF_BOUNDS(); + + /// @dev thrown if failed swap token amount is 0 + error INVALID_DST_SWAPPER_FAILED_SWAP(); + + /// @dev thrown if failed swap token amount is not 0 and if token balance is less than amount (non zero) + error INVALID_DST_SWAPPER_FAILED_SWAP_NO_TOKEN_BALANCE(); + + /// @dev thrown if failed swap token amount is not 0 and if native amount is less than amount (non zero) + error INVALID_DST_SWAPPER_FAILED_SWAP_NO_NATIVE_BALANCE(); + + /// @dev forbid xChain deposits with destination swaps without interim token set (for user protection) + error INVALID_INTERIM_TOKEN(); + + /// @dev thrown if dst swap output is less than minimum expected + error INVALID_SWAP_OUTPUT(); + + /// FORM EXECUTION ERRORS + /// --------------------------------------------------------- + + /// @dev thrown if try to forward 4626 share from the superform + error CANNOT_FORWARD_4646_TOKEN(); + + /// @dev thrown in KYCDAO form if no KYC token is present + error NO_VALID_KYC_TOKEN(); + + /// @dev thrown in forms where a certain functionality is not allowed or implemented + error NOT_IMPLEMENTED(); + + /// @dev thrown if form implementation is PAUSED, users cannot perform any action + error PAUSED(); + + /// @dev thrown if shares != deposit output or assets != redeem output when minting SuperPositions + error VAULT_IMPLEMENTATION_FAILED(); + + /// @dev thrown if withdrawal tx data is not updated + error WITHDRAW_TOKEN_NOT_UPDATED(); + + /// @dev thrown if withdrawal tx data is not updated + error WITHDRAW_TX_DATA_NOT_UPDATED(); + + /// @dev thrown when redeeming from vault yields zero collateral + error WITHDRAW_ZERO_COLLATERAL(); + + /// PAYMENT HELPER EXECUTION ERRORS + /// --------------------------------------------------------- + + /// @dev thrown if chainlink is reporting an improper price + error CHAINLINK_MALFUNCTION(); + + /// @dev thrown if chainlink is reporting an incomplete round + error CHAINLINK_INCOMPLETE_ROUND(); + + /// @dev thrown if feed decimals is not 8 + error CHAINLINK_UNSUPPORTED_DECIMAL(); + + /// EMERGENCY QUEUE EXECUTION ERRORS + /// --------------------------------------------------------- + + /// @dev thrown if emergency withdraw is not queued + error EMERGENCY_WITHDRAW_NOT_QUEUED(); + + /// @dev thrown if emergency withdraw is already processed + error EMERGENCY_WITHDRAW_PROCESSED_ALREADY(); + + /// SUPERPOSITION EXECUTION ERRORS + /// --------------------------------------------------------- + + /// @dev thrown if uri cannot be updated + error DYNAMIC_URI_FROZEN(); + + /// @dev thrown if tx history is not found while state sync + error TX_HISTORY_NOT_FOUND(); +}