From 07b0320cde6d1e45b569fb290fcfeac15d74a065 Mon Sep 17 00:00:00 2001 From: Daniel Gretzke Date: Mon, 24 Aug 2026 21:55:07 +0200 Subject: [PATCH] chore: remove UniswapX v4 from main v4 was never deployed to any mainnet and is in no tagged release. The code is preserved on the archive/uniswapx-v4 branch. Removes src/v4, test/v4, the v4 quoter deploy script, the v4 sample executor and its test, and four orphaned gas snapshots. Drops the v4-only imports and signing helpers from test/util/PermitSignature.sol. --- .../DeployV4QuoterAndTokenTransferHook.s.sol | 37 - snapshots/DCALibGasTest.json | 6 - snapshots/PriorityAuctionResolverTest.json | 6 - snapshots/ReactorTest.json | 9 - snapshots/UnifiedReactorTest.json | 10 - .../V4UniversalRouterExecutor.sol | 143 - src/v4/Reactor.sol | 225 - src/v4/base/ProtocolFees.sol | 123 - src/v4/base/ReactorStructs.sol | 45 - src/v4/hooks/TokenTransferHook.sol | 32 - src/v4/hooks/dca/DCAHook.sol | 544 - src/v4/hooks/dca/DCALib.sol | 215 - src/v4/hooks/dca/DCAStructs.sol | 73 - src/v4/interfaces/IAuctionResolver.sol | 17 - src/v4/interfaces/IDCAHook.sol | 208 - src/v4/interfaces/IHook.sol | 20 - src/v4/interfaces/IProtocolFeeController.sol | 14 - src/v4/interfaces/IReactor.sol | 36 - src/v4/interfaces/IReactorCallback.sol | 12 - src/v4/lens/OrderQuoterV4.sol | 76 - src/v4/lib/ExclusivityLib.sol | 107 - src/v4/lib/HybridOrderLib.sol | 215 - src/v4/lib/OrderInfoLib.sol | 30 - src/v4/lib/Permit2Lib.sol | 28 - src/v4/lib/PriorityOrderLib.sol | 162 - src/v4/lib/TokenTransferLib.sol | 38 - src/v4/resolvers/HybridAuctionResolver.sol | 150 - src/v4/resolvers/PriorityAuctionResolver.sol | 134 - .../V4UniversalRouterExecutor.t.sol | 444 - test/util/PermitSignature.sol | 105 - test/v4/EthOutput.t.sol | 146 - test/v4/ProtocolFees.t.sol | 786 -- test/v4/Reactor.t.sol | 1025 -- test/v4/hooks/dca/DCAHook.t.sol | 1172 --- test/v4/hooks/dca/DCAHookHarness.sol | 130 - .../hooks/dca/DCAHook_DomainSeparator.t.sol | 141 - .../dca/DCAHook_transferInputTokens.t.sol | 452 - .../hooks/dca/DCAHook_validateChunkSize.t.sol | 349 - .../DCAHook_validateOutputDistribution.t.sol | 89 - .../dca/DCAHook_validatePriceFloor.t.sol | 239 - .../dca/DCAHook_validateStaticFields.t.sol | 324 - test/v4/hooks/dca/DCALibGasTest.t.sol | 139 - test/v4/hooks/dca/DCALibTest.t.sol | 390 - .../hooks/dca/DCALib_EIP712Compliance.t.sol | 255 - test/v4/hooks/dca/FFISignDCAIntent.sol | 191 - test/v4/hooks/dca/js-scripts/build.js | 32 - .../dca/js-scripts/dist/sign-dca-intent.js | 9204 ----------------- .../v4/hooks/dca/js-scripts/package-lock.json | 860 -- test/v4/hooks/dca/js-scripts/package.json | 18 - .../dca/js-scripts/src/sign-dca-intent.ts | 309 - test/v4/lens/OrderQuoterV4.t.sol | 332 - test/v4/resolvers/HybridAuctionResolver.t.sol | 2621 ----- .../resolvers/PriorityAuctionResolver.t.sol | 767 -- test/v4/util/OrderInfoBuilder.sol | 83 - test/v4/util/mock/MockAuctionResolver.sol | 74 - test/v4/util/mock/MockFeeController.sol | 64 - .../util/mock/MockFeeControllerDuplicates.sol | 38 - .../MockFeeControllerInputAndOutputFees.sol | 38 - .../util/mock/MockFeeControllerInputFees.sol | 33 - .../v4/util/mock/MockFeeControllerZeroFee.sol | 37 - test/v4/util/mock/MockFillContract.sol | 51 - test/v4/util/mock/MockOrderLib.sol | 85 - test/v4/util/mock/MockPostExecutionHook.sol | 56 - test/v4/util/mock/MockPreExecutionHook.sol | 63 - 64 files changed, 23857 deletions(-) delete mode 100644 script/DeployV4QuoterAndTokenTransferHook.s.sol delete mode 100644 snapshots/DCALibGasTest.json delete mode 100644 snapshots/PriorityAuctionResolverTest.json delete mode 100644 snapshots/ReactorTest.json delete mode 100644 snapshots/UnifiedReactorTest.json delete mode 100644 src/sample-executors/V4UniversalRouterExecutor.sol delete mode 100644 src/v4/Reactor.sol delete mode 100644 src/v4/base/ProtocolFees.sol delete mode 100644 src/v4/base/ReactorStructs.sol delete mode 100644 src/v4/hooks/TokenTransferHook.sol delete mode 100644 src/v4/hooks/dca/DCAHook.sol delete mode 100644 src/v4/hooks/dca/DCALib.sol delete mode 100644 src/v4/hooks/dca/DCAStructs.sol delete mode 100644 src/v4/interfaces/IAuctionResolver.sol delete mode 100644 src/v4/interfaces/IDCAHook.sol delete mode 100644 src/v4/interfaces/IHook.sol delete mode 100644 src/v4/interfaces/IProtocolFeeController.sol delete mode 100644 src/v4/interfaces/IReactor.sol delete mode 100644 src/v4/interfaces/IReactorCallback.sol delete mode 100644 src/v4/lens/OrderQuoterV4.sol delete mode 100644 src/v4/lib/ExclusivityLib.sol delete mode 100644 src/v4/lib/HybridOrderLib.sol delete mode 100644 src/v4/lib/OrderInfoLib.sol delete mode 100644 src/v4/lib/Permit2Lib.sol delete mode 100644 src/v4/lib/PriorityOrderLib.sol delete mode 100644 src/v4/lib/TokenTransferLib.sol delete mode 100644 src/v4/resolvers/HybridAuctionResolver.sol delete mode 100644 src/v4/resolvers/PriorityAuctionResolver.sol delete mode 100644 test/sample-executors/V4UniversalRouterExecutor.t.sol delete mode 100644 test/v4/EthOutput.t.sol delete mode 100644 test/v4/ProtocolFees.t.sol delete mode 100644 test/v4/Reactor.t.sol delete mode 100644 test/v4/hooks/dca/DCAHook.t.sol delete mode 100644 test/v4/hooks/dca/DCAHookHarness.sol delete mode 100644 test/v4/hooks/dca/DCAHook_DomainSeparator.t.sol delete mode 100644 test/v4/hooks/dca/DCAHook_transferInputTokens.t.sol delete mode 100644 test/v4/hooks/dca/DCAHook_validateChunkSize.t.sol delete mode 100644 test/v4/hooks/dca/DCAHook_validateOutputDistribution.t.sol delete mode 100644 test/v4/hooks/dca/DCAHook_validatePriceFloor.t.sol delete mode 100644 test/v4/hooks/dca/DCAHook_validateStaticFields.t.sol delete mode 100644 test/v4/hooks/dca/DCALibGasTest.t.sol delete mode 100644 test/v4/hooks/dca/DCALibTest.t.sol delete mode 100644 test/v4/hooks/dca/DCALib_EIP712Compliance.t.sol delete mode 100644 test/v4/hooks/dca/FFISignDCAIntent.sol delete mode 100644 test/v4/hooks/dca/js-scripts/build.js delete mode 100755 test/v4/hooks/dca/js-scripts/dist/sign-dca-intent.js delete mode 100644 test/v4/hooks/dca/js-scripts/package-lock.json delete mode 100644 test/v4/hooks/dca/js-scripts/package.json delete mode 100644 test/v4/hooks/dca/js-scripts/src/sign-dca-intent.ts delete mode 100644 test/v4/lens/OrderQuoterV4.t.sol delete mode 100644 test/v4/resolvers/HybridAuctionResolver.t.sol delete mode 100644 test/v4/resolvers/PriorityAuctionResolver.t.sol delete mode 100644 test/v4/util/OrderInfoBuilder.sol delete mode 100644 test/v4/util/mock/MockAuctionResolver.sol delete mode 100644 test/v4/util/mock/MockFeeController.sol delete mode 100644 test/v4/util/mock/MockFeeControllerDuplicates.sol delete mode 100644 test/v4/util/mock/MockFeeControllerInputAndOutputFees.sol delete mode 100644 test/v4/util/mock/MockFeeControllerInputFees.sol delete mode 100644 test/v4/util/mock/MockFeeControllerZeroFee.sol delete mode 100644 test/v4/util/mock/MockFillContract.sol delete mode 100644 test/v4/util/mock/MockOrderLib.sol delete mode 100644 test/v4/util/mock/MockPostExecutionHook.sol delete mode 100644 test/v4/util/mock/MockPreExecutionHook.sol diff --git a/script/DeployV4QuoterAndTokenTransferHook.s.sol b/script/DeployV4QuoterAndTokenTransferHook.s.sol deleted file mode 100644 index 712f2240..00000000 --- a/script/DeployV4QuoterAndTokenTransferHook.s.sol +++ /dev/null @@ -1,37 +0,0 @@ -pragma solidity ^0.8.13; - -import "forge-std/console2.sol"; -import "forge-std/Script.sol"; -import {OrderQuoterV4} from "../src/v4/lens/OrderQuoterV4.sol"; -import {TokenTransferHook} from "../src/v4/hooks/TokenTransferHook.sol"; -import {IPermit2} from "permit2/src/interfaces/IPermit2.sol"; -import {IReactor} from "../src/v4/interfaces/IReactor.sol"; - -struct V4OrderQuoterDeployment { - OrderQuoterV4 quoter; - TokenTransferHook tokenTransferHook; -} - -contract DeployV4QuoterAndTokenTransferHook is Script { - // Permit2 is deployed at the same address on all chains - address constant PERMIT2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3; - - function setUp() public {} - - function run() public returns (V4OrderQuoterDeployment memory deployment) { - // Read reactor address from environment: FOUNDRY_V4_REACTOR - address reactor = vm.envAddress("FOUNDRY_V4_REACTOR"); - - vm.startBroadcast(); - - OrderQuoterV4 quoter = new OrderQuoterV4{salt: 0x00}(); - console2.log("V4 OrderQuoter", address(quoter)); - - TokenTransferHook tokenTransferHook = new TokenTransferHook{salt: 0x00}(IPermit2(PERMIT2), IReactor(reactor)); - console2.log("TokenTransferHook", address(tokenTransferHook)); - - vm.stopBroadcast(); - - return V4OrderQuoterDeployment(quoter, tokenTransferHook); - } -} diff --git a/snapshots/DCALibGasTest.json b/snapshots/DCALibGasTest.json deleted file mode 100644 index 2d22c505..00000000 --- a/snapshots/DCALibGasTest.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "isValidSignature_EOA": "3000", - "isValidSignature_EOA_Invalid": "3000", - "isValidSignature_ERC1271": "6315", - "isValidSignature_ERC1271_Invalid": "6301" -} \ No newline at end of file diff --git a/snapshots/PriorityAuctionResolverTest.json b/snapshots/PriorityAuctionResolverTest.json deleted file mode 100644 index c8a71f4a..00000000 --- a/snapshots/PriorityAuctionResolverTest.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "Reactor_OverrideAuctionTargetBlock": "161738", - "Reactor_PriorityInputFee": "192288", - "Reactor_PriorityOutputFee": "192293", - "Reactor_PriorityOutputFeeWithBaseline": "192293" -} \ No newline at end of file diff --git a/snapshots/ReactorTest.json b/snapshots/ReactorTest.json deleted file mode 100644 index e06ef78f..00000000 --- a/snapshots/ReactorTest.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "BaseExecuteSingleWithFee": "208297", - "ReactorExecuteBatch": "244617", - "ReactorExecuteBatchMultipleOutputsDifferentTokens": "308895", - "ReactorExecuteBatchNativeOutput": "240656", - "ReactorExecuteSingle": "173076", - "ReactorExecuteSingleNativeInput": "172983", - "ReactorExecuteSingleNativeOutput": "161144" -} \ No newline at end of file diff --git a/snapshots/UnifiedReactorTest.json b/snapshots/UnifiedReactorTest.json deleted file mode 100644 index f166078b..00000000 --- a/snapshots/UnifiedReactorTest.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "BaseExecuteSingleWithFee": "197565", - "UnifiedReactorExecuteBatch": "222373", - "UnifiedReactorExecuteBatchMultipleOutputsDifferentTokens": "286527", - "UnifiedReactorExecuteBatchNativeOutput": "218161", - "UnifiedReactorExecuteSingle": "162258", - "UnifiedReactorExecuteSingleNativeOutput": "150201", - "UnifiedReactorExecuteSingleWithHook": "204608", - "UnifiedReactorRevertInvalidNonce": "35621" -} \ No newline at end of file diff --git a/src/sample-executors/V4UniversalRouterExecutor.sol b/src/sample-executors/V4UniversalRouterExecutor.sol deleted file mode 100644 index 33060090..00000000 --- a/src/sample-executors/V4UniversalRouterExecutor.sol +++ /dev/null @@ -1,143 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {Owned} from "solmate/src/auth/Owned.sol"; -import {SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol"; -import {ERC20} from "solmate/src/tokens/ERC20.sol"; -import {IPermit2} from "permit2/src/interfaces/IPermit2.sol"; -import {IReactorCallback} from "../v4/interfaces/IReactorCallback.sol"; -import {IReactor} from "../v4/interfaces/IReactor.sol"; -import {CurrencyLibrary, NATIVE} from "../lib/CurrencyLibrary.sol"; -import {ResolvedOrder} from "../v4/base/ReactorStructs.sol"; -import {SignedOrder} from "../base/ReactorStructs.sol"; - -/// @notice A fill contract that uses UniversalRouter to execute trades on V4 Reactor -contract V4UniversalRouterExecutor is IReactorCallback, Owned { - using SafeTransferLib for ERC20; - using CurrencyLibrary for address; - - /// @notice thrown if reactorCallback is called with a non-whitelisted filler - error CallerNotWhitelisted(); - /// @notice thrown if reactorCallback is called by an address other than the reactor - error MsgSenderNotReactor(); - - address public immutable universalRouter; - mapping(address => bool) whitelistedCallers; - IReactor public immutable reactor; - IPermit2 public immutable permit2; - - modifier onlyWhitelistedCaller() { - if (whitelistedCallers[msg.sender] == false) { - revert CallerNotWhitelisted(); - } - _; - } - - modifier onlyReactor() { - if (msg.sender != address(reactor)) { - revert MsgSenderNotReactor(); - } - _; - } - - constructor( - address[] memory _whitelistedCallers, - IReactor _reactor, - address _owner, - address _universalRouter, - IPermit2 _permit2 - ) Owned(_owner) { - for (uint256 i = 0; i < _whitelistedCallers.length; i++) { - whitelistedCallers[_whitelistedCallers[i]] = true; - } - reactor = _reactor; - universalRouter = _universalRouter; - permit2 = _permit2; - } - - /// @notice assume that we already have all output tokens - function execute(SignedOrder calldata order, bytes calldata callbackData) external onlyWhitelistedCaller { - reactor.executeWithCallback(order, callbackData); - } - - /// @notice assume that we already have all output tokens - function executeBatch(SignedOrder[] calldata orders, bytes calldata callbackData) external onlyWhitelistedCaller { - reactor.executeBatchWithCallback(orders, callbackData); - } - - /// @notice fill UniswapX orders using UniversalRouter - /// @param resolvedOrders The resolved orders with inputs and outputs - /// @param callbackData It has the below encoded: - /// address[] memory tokensToApproveForUniversalRouter: Max approve these tokens to permit2 and universalRouter - /// address[] memory tokensToApproveForReactor: Max approve these tokens to reactor - /// bytes memory data: execution data - function reactorCallback(ResolvedOrder[] memory resolvedOrders, bytes memory callbackData) external onlyReactor { - ( - address[] memory tokensToApproveForUniversalRouter, - address[] memory tokensToApproveForReactor, - bytes memory data - ) = abi.decode(callbackData, (address[], address[], bytes)); - - unchecked { - for (uint256 i = 0; i < tokensToApproveForUniversalRouter.length; i++) { - // Max approve token to permit2 - ERC20(tokensToApproveForUniversalRouter[i]).safeApprove(address(permit2), type(uint256).max); - // Max approve token to universalRouter via permit2 - permit2.approve( - tokensToApproveForUniversalRouter[i], address(universalRouter), type(uint160).max, type(uint48).max - ); - } - - for (uint256 i = 0; i < tokensToApproveForReactor.length; i++) { - ERC20(tokensToApproveForReactor[i]).safeApprove(address(reactor), type(uint256).max); - } - } - - // Sum up ETH amounts from ERC20ETH input tokens - // ERC20ETH transfers send native ETH to this contract, which needs to be forwarded to Universal Router - uint256 ethAmount = 0; - uint256 ordersLength = resolvedOrders.length; - for (uint256 i = 0; i < ordersLength; i++) { - // ERC20ETH is at 0x00000000e20E49e6dCeE6e8283A0C090578F0fb9 - // When ERC20ETH is the input token, it transfers native ETH to this contract - // Also check for NATIVE address (0x0) as a defensive measure, though native ETH cannot be an input token - if ( - address(resolvedOrders[i].input.token) == 0x00000000e20E49e6dCeE6e8283A0C090578F0fb9 - || address(resolvedOrders[i].input.token) == NATIVE - ) { - ethAmount += resolvedOrders[i].input.amount; - } - } - - // Forward ETH to Universal Router (e.g., from ERC20ETH transfers) - // The Universal Router will use what it needs and return any excess - (bool success, bytes memory returnData) = universalRouter.call{value: ethAmount}(data); - if (!success) { - assembly { - revert(add(returnData, 32), mload(returnData)) - } - } - - // transfer any native balance to the reactor - // it will refund any excess - if (address(this).balance > 0) { - CurrencyLibrary.transferNative(address(reactor), address(this).balance); - } - } - - /// @notice Transfer all ETH in this contract to the recipient. Can only be called by owner. - /// @param recipient The recipient of the ETH - function withdrawETH(address recipient) external onlyOwner { - SafeTransferLib.safeTransferETH(recipient, address(this).balance); - } - - /// @notice Transfer the entire balance of an ERC20 token in this contract to a recipient. Can only be called by owner. - /// @param token The ERC20 token to withdraw - /// @param to The recipient of the tokens - function withdrawERC20(ERC20 token, address to) external onlyOwner { - token.safeTransfer(to, token.balanceOf(address(this))); - } - - /// @notice Necessary for this contract to receive ETH - receive() external payable {} -} diff --git a/src/v4/Reactor.sol b/src/v4/Reactor.sol deleted file mode 100644 index f783add2..00000000 --- a/src/v4/Reactor.sol +++ /dev/null @@ -1,225 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {IReactor} from "./interfaces/IReactor.sol"; -import {IReactorCallback} from "./interfaces/IReactorCallback.sol"; - -import {SignedOrder, OutputToken} from "../../src/base/ReactorStructs.sol"; -import {ResolvedOrder} from "./base/ReactorStructs.sol"; -import {IAuctionResolver} from "./interfaces/IAuctionResolver.sol"; -import {CurrencyLibrary} from "../../src/lib/CurrencyLibrary.sol"; -import {ReactorEvents} from "../../src/base/ReactorEvents.sol"; -import {ReentrancyGuard} from "openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol"; -import {SignatureVerification} from "permit2/src/libraries/SignatureVerification.sol"; -import {ProtocolFees} from "./base/ProtocolFees.sol"; -import {IPermit2} from "permit2/src/interfaces/IPermit2.sol"; - -/// @notice modular UniswapX Reactor that supports pre-and-post fill hooks and auction resolver plugins -contract Reactor is IReactor, ReactorEvents, ProtocolFees, ReentrancyGuard { - using CurrencyLibrary for address; - using SignatureVerification for bytes; - - /// @notice Permit2 address for EIP-712 domain separator - IPermit2 public immutable permit2; - - bytes32 private constant _TOKEN_PERMISSIONS_TYPEHASH = keccak256("TokenPermissions(address token,uint256 amount)"); - - string private constant _PERMIT_TRANSFER_FROM_WITNESS_TYPEHASH_STUB = - "PermitWitnessTransferFrom(TokenPermissions permitted,address spender,uint256 nonce,uint256 deadline,"; - - constructor(address _protocolFeeOwner, IPermit2 _permit2) ProtocolFees(_protocolFeeOwner) { - permit2 = _permit2; - } - - /// @inheritdoc IReactor - function execute(SignedOrder calldata order) external payable override nonReentrant { - ResolvedOrder[] memory resolvedOrders = new ResolvedOrder[](1); - ResolvedOrder memory resolvedOrder = _resolve(order); - resolvedOrders[0] = resolvedOrder; - - // Build full EIP-712 hash for signature verification - bytes32 fullHash = _buildPermitHash(resolvedOrder); - order.sig.verify(fullHash, resolvedOrder.info.swapper); - - _prepare(resolvedOrders); - _fill(resolvedOrders); - } - - /// @inheritdoc IReactor - function executeBatch(SignedOrder[] calldata orders) external payable override nonReentrant { - uint256 ordersLength = orders.length; - ResolvedOrder[] memory resolvedOrders = new ResolvedOrder[](ordersLength); - - unchecked { - for (uint256 i = 0; i < ordersLength; i++) { - resolvedOrders[i] = _resolve(orders[i]); - bytes32 fullHash = _buildPermitHash(resolvedOrders[i]); - orders[i].sig.verify(fullHash, resolvedOrders[i].info.swapper); - } - } - - _prepare(resolvedOrders); - _fill(resolvedOrders); - } - - /// @inheritdoc IReactor - function executeWithCallback(SignedOrder calldata order, bytes calldata callbackData) - external - payable - override - nonReentrant - { - ResolvedOrder[] memory resolvedOrders = new ResolvedOrder[](1); - ResolvedOrder memory resolvedOrder = _resolve(order); - resolvedOrders[0] = resolvedOrder; - bytes32 fullHash = _buildPermitHash(resolvedOrder); - order.sig.verify(fullHash, resolvedOrder.info.swapper); - - _prepare(resolvedOrders); - IReactorCallback(msg.sender).reactorCallback(resolvedOrders, callbackData); - _fill(resolvedOrders); - } - - /// @inheritdoc IReactor - function executeBatchWithCallback(SignedOrder[] calldata orders, bytes calldata callbackData) - external - payable - override - nonReentrant - { - uint256 ordersLength = orders.length; - ResolvedOrder[] memory resolvedOrders = new ResolvedOrder[](ordersLength); - - unchecked { - for (uint256 i = 0; i < ordersLength; i++) { - resolvedOrders[i] = _resolve(orders[i]); - bytes32 fullHash = _buildPermitHash(resolvedOrders[i]); - orders[i].sig.verify(fullHash, resolvedOrders[i].info.swapper); - } - } - - _prepare(resolvedOrders); - IReactorCallback(msg.sender).reactorCallback(resolvedOrders, callbackData); - _fill(resolvedOrders); - } - - /// @notice Resolve a SignedOrder into a ResolvedOrder using the auction resolver - function _resolve(SignedOrder calldata signedOrder) internal view returns (ResolvedOrder memory resolvedOrder) { - (address auctionResolver, bytes memory orderData) = abi.decode(signedOrder.order, (address, bytes)); - - if (auctionResolver == address(0)) { - revert EmptyAuctionResolver(); - } - - IAuctionResolver resolver = IAuctionResolver(auctionResolver); - resolvedOrder = resolver.resolve(SignedOrder({order: orderData, sig: signedOrder.sig})); - - if (address(resolvedOrder.info.auctionResolver) != auctionResolver) { - revert ResolverMismatch(); - } - - // Resolver provides the witness hash that binds resolver to order - // No need to wrap it again - the witness already includes the resolver address - } - - /// @notice Prepare orders for execution by calling pre-execution hooks and injecting fees - function _prepare(ResolvedOrder[] memory orders) internal { - uint256 ordersLength = orders.length; - unchecked { - for (uint256 i = 0; i < ordersLength; i++) { - ResolvedOrder memory order = orders[i]; - _validateOrder(order); - _callPreExecutionHook(order); - _injectFees(order); - // Token transfer is handled by the hook - } - } - } - - /// @notice Fill orders by transferring output tokens - function _fill(ResolvedOrder[] memory orders) internal { - uint256 ordersLength = orders.length; - unchecked { - for (uint256 i = 0; i < ordersLength; i++) { - ResolvedOrder memory order = orders[i]; - _transferOutputTokens(order); - _callPostExecutionHook(order); - emit Fill(order.hash, msg.sender, order.info.swapper, order.info.nonce); - } - } - - // refund any remaining ETH to the filler. Only occurs when filler sends more ETH than required to - // `execute()` or `executeBatch()`, or when there is excess contract balance remaining from others - // incorrectly calling execute/executeBatch without direct filler method but with a msg.value - if (address(this).balance > 0) { - CurrencyLibrary.transferNative(msg.sender, address(this).balance); - } - } - - /// @notice Call post-execution hook if set - function _callPostExecutionHook(ResolvedOrder memory order) internal { - if (address(order.info.postExecutionHook) != address(0)) { - order.info.postExecutionHook.postExecutionHook(msg.sender, order); - } - } - - /// @notice Validate basic order properties - function _validateOrder(ResolvedOrder memory order) internal view { - if (address(this) != address(order.info.reactor)) { - revert InvalidReactor(); - } - - if (order.info.deadline < block.timestamp) { - revert DeadlinePassed(); - } - } - - /// @notice Call pre-execution hook (required for all orders) - function _callPreExecutionHook(ResolvedOrder memory order) internal { - if (address(order.info.preExecutionHook) == address(0)) { - revert MissingPreExecutionHook(); - } - order.info.preExecutionHook.preExecutionHook(msg.sender, order); - } - - /// @notice Transfer output tokens to their recipients - function _transferOutputTokens(ResolvedOrder memory order) internal { - uint256 outputsLength = order.outputs.length; - unchecked { - for (uint256 i = 0; i < outputsLength; i++) { - OutputToken memory output = order.outputs[i]; - output.token.transferFill(output.recipient, output.amount); - } - } - } - - /// @notice Build the full EIP-712 hash for signature verification - /// @param order The resolved order - /// @return The full EIP-712 hash that was signed by the swapper - function _buildPermitHash(ResolvedOrder memory order) internal view returns (bytes32) { - // Build the full PermitWitnessTransferFrom type hash from the witness type string - // based on `PermitHash.hashWithWitness` logic - bytes32 typeHash = - keccak256(abi.encodePacked(_PERMIT_TRANSFER_FROM_WITNESS_TYPEHASH_STUB, order.witnessTypeString)); - - // based `PermitHash._hashTokenPermissions` logic - bytes32 tokenPermissionsHash = - keccak256(abi.encode(_TOKEN_PERMISSIONS_TYPEHASH, address(order.input.token), order.input.maxAmount)); - - bytes32 structHash = keccak256( - abi.encode( - typeHash, - tokenPermissionsHash, - address(order.info.preExecutionHook), // spender - order.info.nonce, - order.info.deadline, - order.hash - ) - ); - - return keccak256(abi.encodePacked("\x19\x01", permit2.DOMAIN_SEPARATOR(), structHash)); - } - - /// @notice Allow contract to receive ETH for native output orders - receive() external payable {} -} diff --git a/src/v4/base/ProtocolFees.sol b/src/v4/base/ProtocolFees.sol deleted file mode 100644 index 842a18b3..00000000 --- a/src/v4/base/ProtocolFees.sol +++ /dev/null @@ -1,123 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {Owned} from "solmate/src/auth/Owned.sol"; -import {SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol"; -import {FixedPointMathLib} from "solmate/src/utils/FixedPointMathLib.sol"; -import {ERC20} from "solmate/src/tokens/ERC20.sol"; -import {IProtocolFeeController} from "../interfaces/IProtocolFeeController.sol"; -import {CurrencyLibrary} from "../../lib/CurrencyLibrary.sol"; -import {ResolvedOrder} from "../base/ReactorStructs.sol"; -import {OutputToken} from "../../base/ReactorStructs.sol"; - -/// @notice Handling for protocol fees -/// @dev depends on chosen FeeController to get fee outputs from resolved orders -abstract contract ProtocolFees is Owned { - using SafeTransferLib for ERC20; - using FixedPointMathLib for uint256; - using CurrencyLibrary for address; - - /// @notice thrown if two fee outputs have the same token - error DuplicateFeeOutput(address duplicateToken); - /// @notice thrown if a given fee output is greater than MAX_FEE_BPS of the order outputs - error FeeTooLarge(address token, uint256 amount, address recipient); - /// @notice thrown if a fee output token does not have a corresponding non-fee output - error InvalidFeeToken(address feeToken); - /// @notice thrown if fees are taken on both inputs and outputs - error InputAndOutputFees(); - /// @notice thrown if input token is one of the output tokens - error InputTokenInOutputs(address token); - - event ProtocolFeeControllerSet(address oldFeeController, address newFeeController); - - uint256 private constant BPS = 10_000; - uint256 private constant MAX_FEE_BPS = 5; - - /// @dev The address of the fee controller - IProtocolFeeController public feeController; - - // @notice Required to customize owner from constructor of BaseReactor.sol - constructor(address _owner) Owned(_owner) {} - - /// @notice Injects fees into an order - /// @dev modifies the orders to include protocol fee outputs - /// @param order The encoded order to inject fees into - function _injectFees(ResolvedOrder memory order) internal view { - // Validate that input token is not one of the output tokens - // This prevents change of behavior depending on fee configuration: - // - fee on input token => revert InputAndOutputFees() - // - fee on output token => no revert - address inputToken = address(order.input.token); - uint256 outputsLength = order.outputs.length; - for (uint256 i = 0; i < outputsLength; i++) { - if (order.outputs[i].token == inputToken) { - revert InputTokenInOutputs(inputToken); - } - } - - if (address(feeController) == address(0)) { - return; - } - - OutputToken[] memory feeOutputs = feeController.getFeeOutputs(order); - uint256 feeOutputsLength = feeOutputs.length; - - // apply fee outputs - // fill new outputs with old outputs - OutputToken[] memory newOutputs = new OutputToken[](outputsLength + feeOutputsLength); - - for (uint256 i = 0; i < outputsLength; i++) { - newOutputs[i] = order.outputs[i]; - } - - bool outputFeeTaken = false; - bool inputFeeTaken = false; - for (uint256 i = 0; i < feeOutputsLength; i++) { - OutputToken memory feeOutput = feeOutputs[i]; - // assert no duplicates - for (uint256 j = 0; j < i; j++) { - if (feeOutput.token == feeOutputs[j].token) { - revert DuplicateFeeOutput(feeOutput.token); - } - } - - // assert not greater than MAX_FEE_BPS - uint256 tokenValue; - for (uint256 j = 0; j < outputsLength; j++) { - OutputToken memory output = order.outputs[j]; - if (output.token == feeOutput.token) { - if (inputFeeTaken) revert InputAndOutputFees(); - tokenValue += output.amount; - outputFeeTaken = true; - } - } - - // allow fee on input token as well - if (address(order.input.token) == feeOutput.token) { - if (outputFeeTaken) revert InputAndOutputFees(); - tokenValue += order.input.amount; - inputFeeTaken = true; - } - - if (tokenValue == 0) revert InvalidFeeToken(feeOutput.token); - - if (feeOutput.amount > tokenValue.mulDivDown(MAX_FEE_BPS, BPS)) { - revert FeeTooLarge(feeOutput.token, feeOutput.amount, feeOutput.recipient); - } - unchecked { - newOutputs[outputsLength + i] = feeOutput; - } - } - - order.outputs = newOutputs; - } - - /// @notice sets the protocol fee controller - /// @dev only callable by the owner - /// @param _newFeeController the new fee controller - function setProtocolFeeController(address _newFeeController) external onlyOwner { - address oldFeeController = address(feeController); - feeController = IProtocolFeeController(_newFeeController); - emit ProtocolFeeControllerSet(oldFeeController, _newFeeController); - } -} diff --git a/src/v4/base/ReactorStructs.sol b/src/v4/base/ReactorStructs.sol deleted file mode 100644 index 9b142702..00000000 --- a/src/v4/base/ReactorStructs.sol +++ /dev/null @@ -1,45 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {IReactor} from "../interfaces/IReactor.sol"; -import {IPreExecutionHook, IPostExecutionHook} from "../interfaces/IHook.sol"; -import {IAuctionResolver} from "../interfaces/IAuctionResolver.sol"; -import {InputToken, OutputToken} from "../../base/ReactorStructs.sol"; - -/// @dev generic order information -/// should be included as the first field in any concrete order types -struct OrderInfo { - // The address of the reactor that this order is targeting - // Note that this must be included in every order so the swapper - // signature commits to the specific reactor that they trust to fill their order properly - IReactor reactor; - // The address of the user which created the order - // Note that this must be included so that order hashes are unique by swapper - address swapper; - // The nonce of the order, allowing for signature replay protection and cancellation - uint256 nonce; - // The timestamp after which this order is no longer valid - uint256 deadline; - // Pre-execution hook contract - IPreExecutionHook preExecutionHook; - // Encoded pre-execution hook data - bytes preExecutionHookData; - // Post-execution hook contract - IPostExecutionHook postExecutionHook; - // Encoded post-execution hook data - bytes postExecutionHookData; - // Auction resolver contract - IAuctionResolver auctionResolver; -} - -/// @dev generic concrete order that specifies exact tokens which need to be sent and received -struct ResolvedOrder { - OrderInfo info; - InputToken input; - OutputToken[] outputs; - bytes sig; - bytes32 hash; // The witness hash that includes resolver address and full order (what was signed) - address auctionResolver; - // Witness type string provided by resolver for Permit2 verification - string witnessTypeString; -} diff --git a/src/v4/hooks/TokenTransferHook.sol b/src/v4/hooks/TokenTransferHook.sol deleted file mode 100644 index fcd39a0f..00000000 --- a/src/v4/hooks/TokenTransferHook.sol +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {IPreExecutionHook} from "../interfaces/IHook.sol"; -import {ResolvedOrder} from "../base/ReactorStructs.sol"; -import {IPermit2} from "permit2/src/interfaces/IPermit2.sol"; -import {IReactor} from "../interfaces/IReactor.sol"; -import {TokenTransferLib} from "../lib/TokenTransferLib.sol"; - -/// @notice Canonical token transfer hook contract that uses permit2's Signature transfer -contract TokenTransferHook is IPreExecutionHook { - /// @notice Permit2 instance for signature verification and token transfers - IPermit2 public immutable permit2; - - /// @notice v4 Reactor - IReactor public immutable reactor; - - modifier onlyReactor() { - require(msg.sender == address(reactor)); - _; - } - - constructor(IPermit2 _permit2, IReactor _reactor) { - permit2 = _permit2; - reactor = _reactor; - } - - /// @inheritdoc IPreExecutionHook - function preExecutionHook(address filler, ResolvedOrder calldata resolvedOrder) external override onlyReactor { - TokenTransferLib.signatureTransferInputTokens(permit2, resolvedOrder, filler); - } -} diff --git a/src/v4/hooks/dca/DCAHook.sol b/src/v4/hooks/dca/DCAHook.sol deleted file mode 100644 index 220b93b3..00000000 --- a/src/v4/hooks/dca/DCAHook.sol +++ /dev/null @@ -1,544 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {IDCAHook} from "../../interfaces/IDCAHook.sol"; -import {IPreExecutionHook} from "../../interfaces/IHook.sol"; -import {ResolvedOrder, OutputToken} from "../../base/ReactorStructs.sol"; -import {DCAIntent, DCAExecutionState, DCAOrderCosignerData, OutputAllocation, PermitData} from "./DCAStructs.sol"; -import {DCALib} from "./DCALib.sol"; -import {IPermit2} from "permit2/src/interfaces/IPermit2.sol"; -import {IReactor} from "../../interfaces/IReactor.sol"; -import {Permit2Lib} from "../../lib/Permit2Lib.sol"; -import {Math} from "lib/openzeppelin-contracts/contracts/utils/math/Math.sol"; -import {TokenTransferLib} from "../../lib/TokenTransferLib.sol"; - -/// @title DCAHook -/// @notice DCA hook implementation for UniswapX that validates and executes DCA intents -/// @dev Implements IPreExecutionHook for flexibility -contract DCAHook is IPreExecutionHook, IDCAHook { - using Permit2Lib for ResolvedOrder; - - /// @notice Basis points constant (100% = 10000) - uint256 private constant BPS = 10000; - - /// @notice Common denominator in Wad math - uint256 private constant DENOMINATOR = 1e18; - - /// @notice Permit2 instance for signature verification and token transfers - IPermit2 public immutable permit2; - - /// @notice UniswapX V4 Reactor - IReactor public immutable reactor; - - /// @notice Cached EIP-712 domain separator for gas optimization - bytes32 private immutable _CACHED_DOMAIN_SEPARATOR; - - /// @notice Cached chain ID to detect forks - uint256 private immutable _CACHED_CHAIN_ID; - - /// @notice Mapping from intentId to execution state - /// @dev intentId is computed as keccak256(abi.encodePacked(swapper, nonce)) - mapping(bytes32 => DCAExecutionState) internal executionStates; - - constructor(IPermit2 _permit2, IReactor _reactor) { - permit2 = _permit2; - reactor = _reactor; - _CACHED_CHAIN_ID = block.chainid; - _CACHED_DOMAIN_SEPARATOR = DCALib.computeDomainSeparator(address(this)); - } - - /// @notice Returns the domain separator for the current chain - /// @dev Uses cached version if chainid is unchanged from construction - /// @return The domain separator for EIP-712 signatures - function DOMAIN_SEPARATOR() public view returns (bytes32) { - return - block.chainid == _CACHED_CHAIN_ID ? _CACHED_DOMAIN_SEPARATOR : DCALib.computeDomainSeparator(address(this)); - } - - modifier onlyReactor() { - require(msg.sender == address(reactor)); - _; - } - - /// @inheritdoc IPreExecutionHook - function preExecutionHook(address filler, ResolvedOrder calldata resolvedOrder) external override onlyReactor { - // 1) Decode pre-execution data - ( - DCAIntent memory intent, - bytes memory swapperSignature, - bytes32 privateIntentHash, - DCAOrderCosignerData memory cosignerData, - bytes memory cosignerSignature, - PermitData memory permitData - ) = abi.decode( - resolvedOrder.info.preExecutionHookData, - (DCAIntent, bytes, bytes32, DCAOrderCosignerData, bytes, PermitData) - ); - - // 2) Compute intentId for state lookups - bytes32 intentId = keccak256(abi.encodePacked(intent.swapper, intent.nonce)); - - // 3) Validate the DCA intent - _validateDCAIntent( - intent, intentId, privateIntentHash, swapperSignature, cosignerData, cosignerSignature, resolvedOrder - ); - - // 4) Update execution state - (uint256 totalInputExecuted, uint256 totalOutputExecuted) = - _updateExecutionState(intentId, resolvedOrder.input.amount, resolvedOrder.outputs); - - // 5) Transfer input tokens with optional permit - _transferInputTokens(resolvedOrder, filler, permitData); - - // 6) Emit execution event - emit ChunkExecuted( - intentId, cosignerData.execAmount, cosignerData.limitAmount, totalInputExecuted, totalOutputExecuted - ); - } - - /// @notice Validates DCA intent parameters and execution conditions - /// @dev Performs all validation checks but does not modify state - /// @param intent The decoded DCA intent - /// @param intentId The computed intent identifier - /// @param privateIntentHash The hash of private intent data - /// @param swapperSignature The swapper's signature - /// @param cosignerData The cosigner authorization data - /// @param cosignerSignature The cosigner's signature - /// @param resolvedOrder The resolved order to validate against - function _validateDCAIntent( - DCAIntent memory intent, - bytes32 intentId, - bytes32 privateIntentHash, - bytes memory swapperSignature, - DCAOrderCosignerData memory cosignerData, - bytes memory cosignerSignature, - ResolvedOrder calldata resolvedOrder - ) internal view { - // 1) Verify swapper signature (EIP-712) over full intent with privateIntentHash - _validateSwapperSignature(intent, privateIntentHash, swapperSignature); - - // 2) Static field checks (binding correctness) - _validateStaticFields(intent, resolvedOrder); - - // 3) Validate allocation structure (sum to 100%, no zeros) - _validateAllocationStructure(intent.outputAllocations); - - // 4) Verify cosigner authorization - _validateCosignerSignature(intent, cosignerData, cosignerSignature); - - // 5) State checks and period gating - _validateStateAndTiming(intentId, intent, cosignerData); - - // 6) Chunk size checks - _validateChunkSize(intent, cosignerData, resolvedOrder.input.amount); - - // 7) Price floor check (1e18 scaling) - _validatePriceFloor(intent, cosignerData); - - // 8) Validate outputs match allocations and meet requirements - _validateOutputDistribution(intent, cosignerData, resolvedOrder.outputs); - } - - function _transferInputTokens(ResolvedOrder calldata order, address to, PermitData memory permitData) internal { - // If a permit signature is provided, attempt to set the allowance - if (permitData.hasPermit) { - // Always try to use the new permit to refresh expiration and amount for future DCA chunks - // If front-run, the permit will fail but the allowance is already set for the hook - try permit2.permit(order.info.swapper, permitData.permitSingle, permitData.signature) { - // Permit succeeded - new allowance set with fresh expiration - } - catch { - // Permit failed (likely front-run) - allowance should already be set - // Transfer will succeed if sufficient, otherwise it will revert in the transfer call - } - } - - // Transfer tokens using existing allowance (either just set or previously set) - TokenTransferLib.allowanceTransferInputTokens(permit2, order, to); - } - - /// @inheritdoc IDCAHook - function cancelIntents(uint256[] calldata nonces) external override { - uint256 length = nonces.length; - for (uint256 i = 0; i < length; i++) { - _cancelIntent(msg.sender, nonces[i]); - } - } - - /// @inheritdoc IDCAHook - function cancelIntent(uint256 nonce) external override { - _cancelIntent(msg.sender, nonce); - } - - function _cancelIntent(address swapper, uint256 nonce) internal { - bytes32 intentId = keccak256(abi.encodePacked(swapper, nonce)); - if (executionStates[intentId].cancelled) { - revert IntentAlreadyCancelled(intentId); - } - executionStates[intentId].cancelled = true; - emit IntentCancelled(intentId, swapper); - } - - /// @notice Validates the swapper's EIP-712 signature over the DCA intent - /// @dev Reconstructs the original signed message by replacing the zeroed privateIntent field with its hash. - /// This preserves privacy by keeping sensitive DCA parameters (totalAmount, frequency, chunks) off-chain - /// while maintaining signature integrity through hash commitment. - /// Supports both EOA signatures (ECDSA) and smart contract wallet signatures (EIP-1271). - /// @param intent The DCA intent with privateIntent field zeroed for privacy - /// @param privateIntentHash Keccak256 hash of the original privateIntent data - /// @param swapperSignature The EIP-712 signature from the swapper - function _validateSwapperSignature( - DCAIntent memory intent, - bytes32 privateIntentHash, - bytes memory swapperSignature - ) internal view { - bytes32 fullIntentHash = DCALib.hashWithInnerHash(intent, privateIntentHash); - bytes32 digest = DCALib.digest(DOMAIN_SEPARATOR(), fullIntentHash); - if (!DCALib.isValidSignature(intent.swapper, digest, swapperSignature)) { - revert InvalidSwapperSignature(address(0), intent.swapper); - } - } - - /// @notice Validates the cosigner's EIP-712 signature and authorization data - /// @dev Verifies both the signature and that cosigner data matches the intent. - /// Supports both EOA signatures (ECDSA) and smart contract wallet signatures (EIP-1271). - /// @param intent The DCA intent containing expected cosigner and swapper/nonce info - /// @param cosignerData The cosigner authorization data containing execution parameters - /// @param cosignerSignature The EIP-712 signature from the cosigner - function _validateCosignerSignature( - DCAIntent memory intent, - DCAOrderCosignerData memory cosignerData, - bytes memory cosignerSignature - ) internal view { - bytes32 cosignerStructHash = DCALib.hashCosignerData(cosignerData); - bytes32 cosignerDigest = DCALib.digest(DOMAIN_SEPARATOR(), cosignerStructHash); - if (!DCALib.isValidSignature(intent.cosigner, cosignerDigest, cosignerSignature)) { - revert InvalidCosignerSignature(address(0), intent.cosigner); - } - if (cosignerData.swapper != intent.swapper) { - revert CosignerSwapperMismatch(cosignerData.swapper, intent.swapper); - } - if (cosignerData.nonce != intent.nonce) { - revert CosignerNonceMismatch(cosignerData.nonce, intent.nonce); - } - } - - /// @notice Validates that output allocations sum to exactly 100% (10000 basis points) and have no duplicate recipients - /// @dev Reverts if allocations don't sum to 10000, if array is empty, or if there are duplicate recipients - /// @dev NOTE: This function intentionally allows: - /// - Zero address as recipient - validated off-chain for user safety - /// This is permitted at the contract level to support advanced use cases - /// but should be prevented in the UI/frontend for typical users - /// @param outputAllocations The array of output allocations to validate - function _validateAllocationStructure(OutputAllocation[] memory outputAllocations) internal pure { - uint256 length = outputAllocations.length; - if (length == 0) { - revert EmptyAllocations(); - } - - uint256 totalBasisPoints; - for (uint256 i = 0; i < length;) { - uint16 basisPoints = outputAllocations[i].basisPoints; - if (basisPoints == 0) { - revert ZeroAllocation(); - } - - // Check for duplicate recipients - address recipient = outputAllocations[i].recipient; - for (uint256 j = i + 1; j < length;) { - if (outputAllocations[j].recipient == recipient) { - revert DuplicateRecipient(recipient); - } - unchecked { - ++j; - } - } - - totalBasisPoints += basisPoints; - - unchecked { - ++i; - } - } - - if (totalBasisPoints != BPS) { - revert AllocationsNot100Percent(totalBasisPoints); - } - } - - /// @notice Validates static fields match between intent and order - /// @dev Ensures the intent is bound to correct hook, chain, swapper, and tokens - /// @param intent The DCA intent containing expected values - /// @param resolvedOrder The resolved order to validate against - function _validateStaticFields(DCAIntent memory intent, ResolvedOrder memory resolvedOrder) internal view { - if (intent.hookAddress != address(this)) { - revert WrongHook(intent.hookAddress, address(this)); - } - if (intent.chainId != block.chainid) { - revert WrongChain(intent.chainId, block.chainid); - } - if (resolvedOrder.info.swapper != intent.swapper) { - revert SwapperMismatch(resolvedOrder.info.swapper, intent.swapper); - } - if (address(resolvedOrder.input.token) != intent.inputToken) { - revert WrongInputToken(address(resolvedOrder.input.token), intent.inputToken); - } - - // Verify all outputs use the correct output token - uint256 outputsLength = resolvedOrder.outputs.length; - for (uint256 i = 0; i < outputsLength; i++) { - if (resolvedOrder.outputs[i].token != intent.outputToken) { - revert WrongOutputToken(resolvedOrder.outputs[i].token, intent.outputToken); - } - } - } - - /// @notice Validates chunk size is within the allowed bounds - /// @dev Checks that execAmount is within min/max chunk size for the given order type - /// @param intent The DCA intent containing chunk size constraints - /// @param cosignerData The cosigner data containing execution amounts - /// @param inputAmount The actual input amount from the resolved order - function _validateChunkSize(DCAIntent memory intent, DCAOrderCosignerData memory cosignerData, uint256 inputAmount) - internal - pure - { - // Validate chunk size bounds (same logic for both order types) - if (cosignerData.execAmount < intent.minChunkSize) { - revert ChunkSizeBelowMin(cosignerData.execAmount, intent.minChunkSize); - } - if (cosignerData.execAmount > intent.maxChunkSize) { - revert ChunkSizeAboveMax(cosignerData.execAmount, intent.maxChunkSize); - } - - // Order-type specific validations - if (intent.isExactIn) { - // We will transfer order.input.amount; ensure it matches execAmount for EXACT_IN - if (inputAmount != cosignerData.execAmount) { - revert InputAmountMismatch(inputAmount, cosignerData.execAmount); - } - } else { - // EXACT_OUT: validate input constraints - if (inputAmount == 0) { - revert ZeroInput(); - } - if (inputAmount > cosignerData.limitAmount) { - revert InputAboveLimit(inputAmount, cosignerData.limitAmount); - } - } - } - - /// @notice Validates execution state and timing constraints - /// @dev Checks cancellation status, deadline, nonce, and period gating - /// @param intentId The unique identifier for this DCA intent - /// @param intent The DCA intent containing timing constraints - /// @param cosignerData The cosigner data containing the order nonce - function _validateStateAndTiming( - bytes32 intentId, - DCAIntent memory intent, - DCAOrderCosignerData memory cosignerData - ) internal view { - // Load to memory to minimize SLOADs - DCAExecutionState memory state = executionStates[intentId]; - - // State checks - if (state.cancelled) { - revert IntentIsCancelled(intentId); - } - if (intent.deadline != 0 && block.timestamp > intent.deadline) { - revert IntentExpired(block.timestamp, intent.deadline); - } - if (cosignerData.orderNonce != state.executedChunks) { - revert WrongChunkNonce(cosignerData.orderNonce, uint96(state.executedChunks)); - } - - // Period gating (enforce minPeriod/maxPeriod only after first execution) - if (state.executedChunks > 0) { - uint256 elapsed = block.timestamp - state.lastExecutionTime; - if (elapsed < intent.minPeriod) { - revert TooSoon(elapsed, intent.minPeriod); - } - if (intent.maxPeriod != 0 && elapsed > intent.maxPeriod) { - revert TooLate(elapsed, intent.maxPeriod); - } - } - } - - /// @notice Validates that the execution price meets the minimum price floor - /// @dev Calculates price based on order type and ensures it meets the minimum - /// @param intent The DCA intent containing the minimum price requirement - /// @param cosignerData The cosigner data containing execution and limit amounts - function _validatePriceFloor(DCAIntent memory intent, DCAOrderCosignerData memory cosignerData) internal pure { - uint256 executionPrice; - if (intent.isExactIn) { - // limitAmount = min acceptable output; execAmount = exact input - // Price = output/input * 1e18 - executionPrice = Math.mulDiv(cosignerData.limitAmount, DENOMINATOR, cosignerData.execAmount); - } else { - // execAmount = exact output; limitAmount = max acceptable input - // Price = output/input * 1e18 - executionPrice = Math.mulDiv(cosignerData.execAmount, DENOMINATOR, cosignerData.limitAmount); - } - if (executionPrice < intent.minPrice) { - revert PriceBelowMin(executionPrice, intent.minPrice); - } - } - - /// @notice Validates that actual outputs match expected distribution and meet limit requirements - /// @dev Verifies outputs are distributed per allocations and total meets minimum/exact requirements - /// @param intent The DCA intent containing allocation requirements - /// @param cosignerData The cosigner data containing limit amounts - /// @param outputs The actual outputs from the resolved order - function _validateOutputDistribution( - DCAIntent memory intent, - DCAOrderCosignerData memory cosignerData, - OutputToken[] memory outputs - ) internal pure { - // Aggregate outputs and compute totalOutput - uint256 totalOutput = 0; - uint256 outputsLength = outputs.length; - for (uint256 i = 0; i < outputsLength; i++) { - // token already checked equals intent.outputToken in _beforeTokenTransfer - totalOutput += outputs[i].amount; - } - - uint256 allocationsLength = intent.outputAllocations.length; - uint256[] memory expected = new uint256[](allocationsLength); - uint256 sumExpected = 0; - - // Select a deterministic recipient to receive any rounding remainder for EXACT_OUT. - // We choose the allocation with the highest bps; ties pick the first max due to strict `>`. - uint256 maxBps = 0; - uint256 maxBpsIndex = 0; - - for (uint256 i = 0; i < allocationsLength; i++) { - uint256 bps = uint256(intent.outputAllocations[i].basisPoints); - if (bps > maxBps) { - maxBps = bps; - maxBpsIndex = i; - } - // Floor(totalOutput * bps / BPS). Sum of floors may be < totalOutput. - expected[i] = Math.mulDiv(totalOutput, bps, BPS); - sumExpected += expected[i]; - } - - if (!intent.isExactIn) { - // EXACT_OUT requires expected[] to sum exactly to totalOutput; otherwise allocation checks can be unfillable. - uint256 remainder = totalOutput - sumExpected; - if (remainder > 0) { - expected[maxBpsIndex] += remainder; - } - } - - for (uint256 i = 0; i < allocationsLength; i++) { - address rcpt = intent.outputAllocations[i].recipient; - uint256 actual = 0; - for (uint256 j = 0; j < outputsLength; j++) { - if (outputs[j].recipient == rcpt) actual += outputs[j].amount; - } - if (intent.isExactIn) { - // Allow ±1 wei for integer division rounding - uint256 exp = expected[i]; - if (!(actual + 1 >= exp && actual <= exp + 1)) { - revert AllocationMismatch(rcpt, actual, exp); - } - } else { - if (actual != expected[i]) { - revert AllocationMismatch(rcpt, actual, expected[i]); - } - } - } - - if (intent.isExactIn) { - // total output produced must meet the limit - if (totalOutput < cosignerData.limitAmount) { - revert InsufficientOutput(totalOutput, cosignerData.limitAmount); - } - } else { - // exact output must be matched - if (totalOutput != cosignerData.execAmount) { - revert WrongTotalOutput(totalOutput, cosignerData.execAmount); - } - } - } - - /// @notice Updates the execution state after successful validation - /// @dev Updates counters, totals, timestamps and nonce for the DCA intent - /// @param intentId The unique identifier for this DCA intent - /// @param inputAmount The amount of input tokens being executed - /// @param outputs The output tokens being distributed - /// @return totalInputExecuted The cumulative input amount after this execution - /// @return totalOutputExecuted The cumulative output amount after this execution - function _updateExecutionState(bytes32 intentId, uint256 inputAmount, OutputToken[] memory outputs) - internal - returns (uint256, uint256) - { - // Use memory to reduce SSTOREs - DCAExecutionState memory state = executionStates[intentId]; - - // Calculate total output amount - uint256 totalOutput = 0; - uint256 outputsLength = outputs.length; - for (uint256 i = 0; i < outputsLength; i++) { - totalOutput += outputs[i].amount; - } - - // Update state in memory - state.executedChunks++; - state.lastExecutionTime = uint120(block.timestamp); - state.totalInputExecuted += inputAmount; - state.totalOutput += totalOutput; - - // single SSTORE - executionStates[intentId] = state; - - // Return cumulative totals for event emission - return (state.totalInputExecuted, state.totalOutput); - } - - /// @inheritdoc IDCAHook - function computeIntentId(address swapper, uint256 nonce) external pure override returns (bytes32) { - return keccak256(abi.encodePacked(swapper, nonce)); - } - - /// @inheritdoc IDCAHook - function getExecutionState(bytes32 intentId) external view override returns (DCAExecutionState memory) { - return executionStates[intentId]; - } - - /// @inheritdoc IDCAHook - function isIntentActive(bytes32 intentId, uint256 maxPeriod, uint256 deadline) - external - view - override - returns (bool) - { - DCAExecutionState storage s = executionStates[intentId]; - if (s.cancelled) return false; - if (deadline != 0 && block.timestamp > deadline) return false; - if (s.executedChunks == 0) return true; - if (maxPeriod != 0 && block.timestamp - s.lastExecutionTime > maxPeriod) return false; - return true; - } - - /// @inheritdoc IDCAHook - function getNextNonce(bytes32 intentId) external view override returns (uint96) { - // The next valid nonce is always equal to the number of chunks already executed. - // This is because nonces start at 0 and increment by 1 with each execution. - // After N executions (executedChunks = N), the next valid nonce is N. - return uint96(executionStates[intentId].executedChunks); - } - - /// @inheritdoc IDCAHook - function getIntentStatistics(bytes32 intentId) - external - view - override - returns (uint256 totalChunks, uint256 totalInput, uint256 totalOutput, uint256 lastExecutionTime) - { - DCAExecutionState memory s = executionStates[intentId]; - totalChunks = s.executedChunks; - totalInput = s.totalInputExecuted; - totalOutput = s.totalOutput; - lastExecutionTime = s.lastExecutionTime; - } -} diff --git a/src/v4/hooks/dca/DCALib.sol b/src/v4/hooks/dca/DCALib.sol deleted file mode 100644 index db86ee88..00000000 --- a/src/v4/hooks/dca/DCALib.sol +++ /dev/null @@ -1,215 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import { - DCAIntent, - PrivateIntent, - OutputAllocation, - DCAOrderCosignerData, - FeedInfo, - FeedTemplate -} from "./DCAStructs.sol"; -import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; - -/// @notice helpers for handling DCA intent specs -library DCALib { - // ----- EIP-712 Domain ----- - bytes32 constant EIP712_DOMAIN_TYPEHASH = - keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"); - - // ----- Type strings ----- - bytes constant FEED_TEMPLATE_TYPE = - "FeedTemplate(string name,string expression,string[] parameters,string[] secrets,uint256 retryCount)"; - bytes32 constant FEED_TEMPLATE_TYPEHASH = keccak256(FEED_TEMPLATE_TYPE); - - bytes constant FEED_INFO_TYPE = "FeedInfo(FeedTemplate feedTemplate,address feedAddress,string feedType)" - "FeedTemplate(string name,string expression,string[] parameters,string[] secrets,uint256 retryCount)"; - bytes32 constant FEED_INFO_TYPEHASH = keccak256(FEED_INFO_TYPE); - - bytes constant PRIVATE_INTENT_TYPE = "PrivateIntent(uint256 totalAmount,uint256 exactFrequency,uint256 numChunks,bytes32 salt,FeedInfo[] oracleFeeds)" - "FeedInfo(FeedTemplate feedTemplate,address feedAddress,string feedType)" - "FeedTemplate(string name,string expression,string[] parameters,string[] secrets,uint256 retryCount)"; - bytes32 constant PRIVATE_INTENT_TYPEHASH = keccak256(PRIVATE_INTENT_TYPE); - - bytes constant OUTPUT_ALLOCATION_TYPE = "OutputAllocation(address recipient,uint16 basisPoints)"; - bytes32 constant OUTPUT_ALLOCATION_TYPEHASH = keccak256(OUTPUT_ALLOCATION_TYPE); - - bytes constant DCA_INTENT_TYPE = "DCAIntent(address swapper,uint256 nonce,uint256 chainId,address hookAddress,bool isExactIn,address inputToken,address outputToken,address cosigner,uint256 minPeriod,uint256 maxPeriod,uint256 minChunkSize,uint256 maxChunkSize,uint256 minPrice,uint256 deadline,OutputAllocation[] outputAllocations,PrivateIntent privateIntent)" - "FeedInfo(FeedTemplate feedTemplate,address feedAddress,string feedType)" - "FeedTemplate(string name,string expression,string[] parameters,string[] secrets,uint256 retryCount)" - "OutputAllocation(address recipient,uint16 basisPoints)" - "PrivateIntent(uint256 totalAmount,uint256 exactFrequency,uint256 numChunks,bytes32 salt,FeedInfo[] oracleFeeds)"; - bytes32 constant DCA_INTENT_TYPEHASH = keccak256(DCA_INTENT_TYPE); - - bytes constant DCA_COSIGNER_DATA_TYPE = - "DCAOrderCosignerData(address swapper,uint96 nonce,uint160 execAmount,uint96 orderNonce,uint160 limitAmount)"; - bytes32 constant DCA_COSIGNER_DATA_TYPEHASH = keccak256(DCA_COSIGNER_DATA_TYPE); - - // ----- Hash helpers ----- - - function _hashStringArray(string[] memory arr) private pure returns (bytes32) { - uint256 len = arr.length; - bytes32[] memory hashes = new bytes32[](len); - for (uint256 i = 0; i < len; i++) { - hashes[i] = keccak256(bytes(arr[i])); - } - return keccak256(abi.encodePacked(hashes)); - } - - function _hashFeedTemplate(FeedTemplate memory template) private pure returns (bytes32) { - return keccak256( - abi.encode( - FEED_TEMPLATE_TYPEHASH, - keccak256(bytes(template.name)), - keccak256(bytes(template.expression)), - _hashStringArray(template.parameters), - _hashStringArray(template.secrets), - template.retryCount - ) - ); - } - - function _hashFeedInfoArray(FeedInfo[] memory feeds) private pure returns (bytes32) { - uint256 len = feeds.length; - bytes32[] memory feedHashes = new bytes32[](len); - for (uint256 i = 0; i < len; i++) { - bytes32 templateHash = _hashFeedTemplate(feeds[i].feedTemplate); - feedHashes[i] = keccak256( - abi.encode(FEED_INFO_TYPEHASH, templateHash, feeds[i].feedAddress, keccak256(bytes(feeds[i].feedType))) - ); - } - return keccak256(abi.encodePacked(feedHashes)); - } - - function _hashOutputAllocations(OutputAllocation[] memory a) private pure returns (bytes32) { - uint256 len = a.length; - bytes32[] memory elHashes = new bytes32[](len); - for (uint256 i = 0; i < len; i++) { - elHashes[i] = keccak256(abi.encode(OUTPUT_ALLOCATION_TYPEHASH, a[i].recipient, a[i].basisPoints)); - } - return keccak256(abi.encodePacked(elHashes)); - } - - function hashPrivateIntent(PrivateIntent memory p) internal pure returns (bytes32) { - bytes32 oracleFeedsHash = _hashFeedInfoArray(p.oracleFeeds); - return keccak256( - abi.encode(PRIVATE_INTENT_TYPEHASH, p.totalAmount, p.exactFrequency, p.numChunks, p.salt, oracleFeedsHash) - ); - } - - function hash(DCAIntent memory intent) internal pure returns (bytes32) { - bytes32 outputAllocHash = _hashOutputAllocations(intent.outputAllocations); - bytes32 privateHash = hashPrivateIntent(intent.privateIntent); - - // Use inline assembly to avoid stack-too-deep while maintaining EIP-712 compliance - // We encode: keccak256(abi.encode(TYPEHASH, swapper, nonce, chainId, hookAddress, - // isExactIn, inputToken, outputToken, cosigner, - // minPeriod, maxPeriod, minChunkSize, maxChunkSize, - // minPrice, deadline, outputAllocHash, privateHash)) - // Total: 17 fields * 32 bytes = 544 bytes (0x220) - bytes32 typeHash = DCA_INTENT_TYPEHASH; - bytes32 structHash; - assembly ("memory-safe") { - let ptr := mload(0x40) // Get free memory pointer - - // Store all fields in memory - mstore(ptr, typeHash) // offset 0x00 - mstore(add(ptr, 0x20), mload(intent)) // swapper (offset 0x00 in struct) - mstore(add(ptr, 0x40), mload(add(intent, 0x20))) // nonce - mstore(add(ptr, 0x60), mload(add(intent, 0x40))) // chainId - mstore(add(ptr, 0x80), mload(add(intent, 0x60))) // hookAddress - mstore(add(ptr, 0xa0), mload(add(intent, 0x80))) // isExactIn - mstore(add(ptr, 0xc0), mload(add(intent, 0xa0))) // inputToken - mstore(add(ptr, 0xe0), mload(add(intent, 0xc0))) // outputToken - mstore(add(ptr, 0x100), mload(add(intent, 0xe0))) // cosigner - mstore(add(ptr, 0x120), mload(add(intent, 0x100))) // minPeriod - mstore(add(ptr, 0x140), mload(add(intent, 0x120))) // maxPeriod - mstore(add(ptr, 0x160), mload(add(intent, 0x140))) // minChunkSize - mstore(add(ptr, 0x180), mload(add(intent, 0x160))) // maxChunkSize - mstore(add(ptr, 0x1a0), mload(add(intent, 0x180))) // minPrice - mstore(add(ptr, 0x1c0), mload(add(intent, 0x1a0))) // deadline - mstore(add(ptr, 0x1e0), outputAllocHash) // outputAllocations hash - mstore(add(ptr, 0x200), privateHash) // privateIntent hash - - // Hash the entire 544 bytes (17 * 32) - structHash := keccak256(ptr, 0x220) - mstore(0x40, add(ptr, 0x220)) // Update free memory pointer - } - - return structHash; - } - - function hashWithInnerHash(DCAIntent memory intent, bytes32 privateIntentHash) internal pure returns (bytes32) { - bytes32 outputAllocHash = _hashOutputAllocations(intent.outputAllocations); - - // Use inline assembly to avoid stack-too-deep while maintaining EIP-712 compliance - // Same as hash() but uses the precomputed privateIntentHash instead of computing it - bytes32 typeHash = DCA_INTENT_TYPEHASH; - bytes32 structHash; - assembly ("memory-safe") { - let ptr := mload(0x40) // Get free memory pointer - - // Store all fields in memory - mstore(ptr, typeHash) // offset 0x00 - mstore(add(ptr, 0x20), mload(intent)) // swapper - mstore(add(ptr, 0x40), mload(add(intent, 0x20))) // nonce - mstore(add(ptr, 0x60), mload(add(intent, 0x40))) // chainId - mstore(add(ptr, 0x80), mload(add(intent, 0x60))) // hookAddress - mstore(add(ptr, 0xa0), mload(add(intent, 0x80))) // isExactIn - mstore(add(ptr, 0xc0), mload(add(intent, 0xa0))) // inputToken - mstore(add(ptr, 0xe0), mload(add(intent, 0xc0))) // outputToken - mstore(add(ptr, 0x100), mload(add(intent, 0xe0))) // cosigner - mstore(add(ptr, 0x120), mload(add(intent, 0x100))) // minPeriod - mstore(add(ptr, 0x140), mload(add(intent, 0x120))) // maxPeriod - mstore(add(ptr, 0x160), mload(add(intent, 0x140))) // minChunkSize - mstore(add(ptr, 0x180), mload(add(intent, 0x160))) // maxChunkSize - mstore(add(ptr, 0x1a0), mload(add(intent, 0x180))) // minPrice - mstore(add(ptr, 0x1c0), mload(add(intent, 0x1a0))) // deadline - mstore(add(ptr, 0x1e0), outputAllocHash) // outputAllocations hash - mstore(add(ptr, 0x200), privateIntentHash) // privateIntent hash (precomputed) - - // Hash the entire 544 bytes (17 * 32) - structHash := keccak256(ptr, 0x220) - mstore(0x40, add(ptr, 0x220)) // Update free memory pointer - } - - return structHash; - } - - function hashCosignerData(DCAOrderCosignerData memory cosignerData) internal pure returns (bytes32) { - return keccak256( - abi.encode( - DCA_COSIGNER_DATA_TYPEHASH, - cosignerData.swapper, - cosignerData.nonce, - cosignerData.execAmount, - cosignerData.orderNonce, - cosignerData.limitAmount - ) - ); - } - - function digest(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) { - return keccak256(abi.encodePacked("\x19\x01", domainSeparator, structHash)); - } - - // Validate signature (supports both EOA and EIP-1271 smart contract wallets) - function isValidSignature(address signer, bytes32 digest_, bytes memory signature) internal view returns (bool) { - return SignatureChecker.isValidSignatureNow(signer, digest_, signature); - } - - /// @notice Computes the domain separator using the current chainId and contract address - /// @param verifyingContract The address of the contract that will verify signatures - /// @return The EIP-712 domain separator - function computeDomainSeparator(address verifyingContract) internal view returns (bytes32) { - return keccak256( - abi.encode( - EIP712_DOMAIN_TYPEHASH, - keccak256(bytes("DCAHook")), - keccak256(bytes("1")), - block.chainid, - verifyingContract - ) - ); - } -} diff --git a/src/v4/hooks/dca/DCAStructs.sol b/src/v4/hooks/dca/DCAStructs.sol deleted file mode 100644 index fd60d847..00000000 --- a/src/v4/hooks/dca/DCAStructs.sol +++ /dev/null @@ -1,73 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol"; - -struct FeedTemplate { - string name; - string expression; - string[] parameters; - string[] secrets; - uint256 retryCount; -} - -struct FeedInfo { - FeedTemplate feedTemplate; - address feedAddress; - string feedType; -} - -struct DCAIntent { - address swapper; - uint256 nonce; - uint256 chainId; - address hookAddress; // DCA contract's address - bool isExactIn; // EXACT_IN or EXACT_OUT - address inputToken; // Token to sell - address outputToken; // Token to buy - address cosigner; // TEE address that authorizes executions - uint256 minPeriod; // Minimum seconds between chunks - uint256 maxPeriod; // Maximum seconds between chunks - uint256 minChunkSize; // Min input or min output per chunk - uint256 maxChunkSize; // Max input or max output per chunk - uint256 minPrice; // Minimum price (output/input * 1e18) - uint256 deadline; // Intent expiration timestamp - OutputAllocation[] outputAllocations; // Distribution of output tokens - PrivateIntent privateIntent; // Private execution parameters - included in EIP-712 signature but zeroed on-chain, only hash revealed -} - -struct PrivateIntent { - uint256 totalAmount; // Total amount on the exact side (input for EXACT_IN, output for EXACT_OUT) - uint256 exactFrequency; - uint256 numChunks; - bytes32 salt; - FeedInfo[] oracleFeeds; // Array of possible oracle feeds -} - -struct OutputAllocation { - address recipient; // 20 bytes - uint16 basisPoints; // 2 bytes - Out of 10000 (100% = 10000), packed in same slot -} - -struct DCAOrderCosignerData { - address swapper; // 20 bytes, slot 1 - uint96 nonce; // 12 bytes - uint160 execAmount; // 20 bytes, slot 2 - uint96 orderNonce; // 12 bytes Unique execution chunk identifier - uint160 limitAmount; // 20 bytes, slot 3 (12 bytes padding) - // uint160 matches Permit2's transferFrom amount limit -} - -struct DCAExecutionState { - uint128 executedChunks; // 16 bytes slot 1 (perfectly packed) - uint120 lastExecutionTime; // 15 bytes - bool cancelled; // 1 byte - uint256 totalInputExecuted; // 32 bytes slot 2 - Cumulative input amount - uint256 totalOutput; // 32 bytes slot 3 - Cumulative output amount -} - -struct PermitData { - bool hasPermit; // Whether a permit signature is included - IAllowanceTransfer.PermitSingle permitSingle; // The permit data (if hasPermit is true) - bytes signature; // The permit signature (if hasPermit is true) -} diff --git a/src/v4/interfaces/IAuctionResolver.sol b/src/v4/interfaces/IAuctionResolver.sol deleted file mode 100644 index 4e9f136c..00000000 --- a/src/v4/interfaces/IAuctionResolver.sol +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {ResolvedOrder} from "../base/ReactorStructs.sol"; -import {SignedOrder} from "../../base/ReactorStructs.sol"; - -/// @notice Interface for auction mechanism resolvers (for UnifiedReactor) -interface IAuctionResolver { - /// @notice Resolves a signed order into a resolved order based on auction rules - /// @param signedOrder The signed order with auction-specific order data (resolver address already stripped) - /// @return resolvedOrder The resolved order with final amounts - function resolve(SignedOrder calldata signedOrder) external view returns (ResolvedOrder memory resolvedOrder); - - /// @notice Get the Permit2 order type string for EIP-712 signature verification - /// @return orderType The EIP-712 order type string for this resolver's orders - function getPermit2OrderType() external pure returns (string memory); -} diff --git a/src/v4/interfaces/IDCAHook.sol b/src/v4/interfaces/IDCAHook.sol deleted file mode 100644 index 30d91b7f..00000000 --- a/src/v4/interfaces/IDCAHook.sol +++ /dev/null @@ -1,208 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {IPreExecutionHook} from "./IHook.sol"; -import {DCAExecutionState} from "../hooks/dca/DCAStructs.sol"; - -/// @title IDCAHook -/// @notice Interface for the DCA (Dollar-Cost Averaging) hook contract -/// @dev Extends IPreExecutionHook to enable periodic execution of DCA intents -interface IDCAHook is IPreExecutionHook { - /// @notice Thrown when attempting to cancel an already cancelled intent - /// @param intentId The identifier of the intent that was already cancelled - error IntentAlreadyCancelled(bytes32 intentId); - - /// @notice Thrown when the swapper signature is invalid - /// @param recoveredSigner The address recovered from the signature - /// @param expectedSwapper The expected swapper address - error InvalidSwapperSignature(address recoveredSigner, address expectedSwapper); - - /// @notice Thrown when the cosigner signature is invalid - /// @param recoveredCosigner The address recovered from the signature - /// @param expectedCosigner The expected cosigner address - error InvalidCosignerSignature(address recoveredCosigner, address expectedCosigner); - - /// @notice Thrown when the cosigner data swapper doesn't match the intent swapper - /// @param cosignerSwapper The swapper address in cosigner data - /// @param intentSwapper The swapper address in the intent - error CosignerSwapperMismatch(address cosignerSwapper, address intentSwapper); - - /// @notice Thrown when the cosigner data nonce doesn't match the intent nonce - /// @param cosignerNonce The nonce in cosigner data - /// @param intentNonce The nonce in the intent - error CosignerNonceMismatch(uint96 cosignerNonce, uint256 intentNonce); - - /// @notice Thrown when output allocations array is empty - error EmptyAllocations(); - - /// @notice Thrown when an output allocation has zero basis points - error ZeroAllocation(); - - /// @notice Thrown when allocations don't sum to exactly 100% (10000 basis points) - /// @param totalBasisPoints The actual sum of basis points - error AllocationsNot100Percent(uint256 totalBasisPoints); - - /// @notice Thrown when multiple allocations have the same recipient - /// @param recipient The duplicate recipient address - error DuplicateRecipient(address recipient); - - /// @notice Thrown when the hook address doesn't match the expected hook - /// @param providedHook The hook address provided in the intent - /// @param expectedHook The expected hook address (this contract) - error WrongHook(address providedHook, address expectedHook); - - /// @notice Thrown when the chain ID doesn't match the current chain - /// @param providedChainId The chain ID provided in the intent - /// @param currentChainId The current blockchain's chain ID - error WrongChain(uint256 providedChainId, uint256 currentChainId); - - /// @notice Thrown when the swapper address doesn't match between intent and order - /// @param orderSwapper The swapper address in the resolved order - /// @param intentSwapper The swapper address in the intent - error SwapperMismatch(address orderSwapper, address intentSwapper); - - /// @notice Thrown when the input token doesn't match the intent - /// @param orderInputToken The input token in the resolved order - /// @param intentInputToken The input token in the intent - error WrongInputToken(address orderInputToken, address intentInputToken); - - /// @notice Thrown when an output token doesn't match the intent - /// @param outputToken The output token in the resolved order - /// @param expectedToken The expected output token from the intent - error WrongOutputToken(address outputToken, address expectedToken); - - /// @notice Thrown when chunk size is below minimum allowed - /// @param amount The actual chunk size (input for EXACT_IN, output for EXACT_OUT) - /// @param minChunkSize The minimum allowed chunk size - error ChunkSizeBelowMin(uint256 amount, uint256 minChunkSize); - - /// @notice Thrown when chunk size exceeds maximum allowed - /// @param amount The actual chunk size (input for EXACT_IN, output for EXACT_OUT) - /// @param maxChunkSize The maximum allowed chunk size - error ChunkSizeAboveMax(uint256 amount, uint256 maxChunkSize); - - /// @notice Thrown when input amount doesn't match execAmount (EXACT_IN) - /// @param inputAmount The input amount in the order - /// @param execAmount The expected exec amount from cosigner data - error InputAmountMismatch(uint256 inputAmount, uint256 execAmount); - - /// @notice Thrown when input amount is zero (EXACT_OUT) - error ZeroInput(); - - /// @notice Thrown when input exceeds cosigner's limit (EXACT_OUT) - /// @param inputAmount The actual input amount - /// @param limitAmount The cosigner's limit amount - error InputAboveLimit(uint256 inputAmount, uint256 limitAmount); - - /// @notice Thrown when attempting to execute a cancelled intent - /// @param intentId The identifier of the cancelled intent - error IntentIsCancelled(bytes32 intentId); - - /// @notice Thrown when the intent has expired - /// @param currentTime The current block timestamp - /// @param deadline The intent's deadline - error IntentExpired(uint256 currentTime, uint256 deadline); - - /// @notice Thrown when the order nonce doesn't match the expected nonce - /// @param providedNonce The nonce provided in the cosigner data - /// @param expectedNonce The expected next nonce for the intent - error WrongChunkNonce(uint96 providedNonce, uint96 expectedNonce); - - /// @notice Thrown when execution is attempted too soon after the last execution - /// @param elapsed The time elapsed since last execution - /// @param minPeriod The minimum required period between executions - error TooSoon(uint256 elapsed, uint256 minPeriod); - - /// @notice Thrown when execution is attempted too late after the last execution - /// @param elapsed The time elapsed since last execution - /// @param maxPeriod The maximum allowed period between executions - error TooLate(uint256 elapsed, uint256 maxPeriod); - - /// @notice Thrown when the execution price is below the minimum price floor - /// @param executionPrice The actual execution price (scaled by 1e18) - /// @param minPrice The minimum acceptable price (scaled by 1e18) - error PriceBelowMin(uint256 executionPrice, uint256 minPrice); - - /// @notice Thrown when output allocation doesn't match expected amount - /// @param recipient The recipient address - /// @param actual The actual amount allocated to the recipient - /// @param expected The expected amount for the recipient - error AllocationMismatch(address recipient, uint256 actual, uint256 expected); - - /// @notice Thrown when total output is insufficient (EXACT_IN) - /// @param totalOutput The total output amount produced - /// @param limitAmount The minimum required output amount - error InsufficientOutput(uint256 totalOutput, uint256 limitAmount); - - /// @notice Thrown when total output doesn't match expected amount (EXACT_OUT) - /// @param totalOutput The actual total output amount - /// @param execAmount The expected exact output amount - error WrongTotalOutput(uint256 totalOutput, uint256 execAmount); - - /// @notice Emitted when an intent is cancelled - /// @param intentId The unique identifier of the intent - /// @param swapper The address of the swapper who cancelled the intent - event IntentCancelled(bytes32 indexed intentId, address indexed swapper); - - /// @notice Emitted when a DCA chunk is executing - /// @param intentId The unique identifier of the intent - /// @param execAmount The amount being executed (input for EXACT_IN, output for EXACT_OUT) - /// @param limitAmount The limit amount (min output for EXACT_IN, max input for EXACT_OUT) - /// @param totalInputExecuted Cumulative input amount after this execution - /// @param totalOutput Cumulative output amount after this execution - event ChunkExecuted( - bytes32 indexed intentId, - uint256 execAmount, - uint256 limitAmount, - uint256 totalInputExecuted, - uint256 totalOutput - ); - - /// @notice Cancel a single DCA intent - /// @param nonce The nonce of the intent to cancel - /// @dev Only callable by the intent owner (verified via msg.sender and nonce) - function cancelIntent(uint256 nonce) external; - - /// @notice Cancel multiple DCA intents in a single transaction - /// @param nonces Array of intent nonces to cancel - /// @dev Only callable by the intent owner for each intent (verified via msg.sender and nonces) - function cancelIntents(uint256[] calldata nonces) external; - - /// @notice Compute the unique identifier for an intent - /// @param swapper The address of the swapper - /// @param nonce The nonce of the intent - /// @return intentId The computed intent identifier - function computeIntentId(address swapper, uint256 nonce) external pure returns (bytes32); - - /// @notice Get the execution state for a specific intent - /// @param intentId The unique identifier of the intent - /// @return state The execution state of the intent - function getExecutionState(bytes32 intentId) external view returns (DCAExecutionState memory state); - - /// @notice Check if an intent is currently active (not cancelled and within period/deadline) - /// @dev Semantics: - /// - Uninitialized intents (no executed chunks) are considered active unless cancelled or past deadline. - /// - maxPeriod is enforced only after the first execution; before that, it is ignored. - /// - A maxPeriod of 0 means no upper bound; a deadline of 0 means no deadline. - /// @param intentId The unique identifier of the intent - /// @param maxPeriod The maximum allowed seconds since last execution (0 = no upper bound) - /// @param deadline The intent expiration timestamp (0 = no deadline) - /// @return active True if the intent is active, false otherwise - function isIntentActive(bytes32 intentId, uint256 maxPeriod, uint256 deadline) external view returns (bool active); - - /// @notice Get the next expected nonce for an intent - /// @param intentId The unique identifier of the intent - /// @return nextNonce The next nonce that should be used for this intent - function getNextNonce(bytes32 intentId) external view returns (uint96 nextNonce); - - /// @notice Get comprehensive statistics for an intent - /// @param intentId The unique identifier of the intent - /// @return totalChunks Number of chunks executed - /// @return totalInput Total input amount executed - /// @return totalOutput Total output amount received - /// @return lastExecutionTime Timestamp of last execution - function getIntentStatistics(bytes32 intentId) - external - view - returns (uint256 totalChunks, uint256 totalInput, uint256 totalOutput, uint256 lastExecutionTime); -} diff --git a/src/v4/interfaces/IHook.sol b/src/v4/interfaces/IHook.sol deleted file mode 100644 index d02351e0..00000000 --- a/src/v4/interfaces/IHook.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import {ResolvedOrder} from "../base/ReactorStructs.sol"; - -/// @notice Hook to be called before order execution, allowing state modifications -interface IPreExecutionHook { - /// @notice Called by the reactor before order execution for custom validation and state changes - /// @param filler The filler of the order - /// @param resolvedOrder The resolved order to fill - function preExecutionHook(address filler, ResolvedOrder calldata resolvedOrder) external; -} - -/// @notice Hook to be called after transferring output tokens, enabling chained actions -interface IPostExecutionHook { - /// @notice Called by the reactor after order execution for chained actions - /// @param filler The filler of the order - /// @param resolvedOrder The resolved order that was filled - function postExecutionHook(address filler, ResolvedOrder calldata resolvedOrder) external; -} diff --git a/src/v4/interfaces/IProtocolFeeController.sol b/src/v4/interfaces/IProtocolFeeController.sol deleted file mode 100644 index f1bccb4c..00000000 --- a/src/v4/interfaces/IProtocolFeeController.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {OutputToken} from "../../base/ReactorStructs.sol"; -import {ResolvedOrder} from "../base/ReactorStructs.sol"; - -/// @notice Interface for getting fee outputs for resolved orders -/// @dev feeController can only take fees on input or output tokens of the order -interface IProtocolFeeController { - /// @notice Get fee outputs for the given orders - /// @param order The orders to get fee outputs for - /// @return List of fee outputs to append for each provided order - function getFeeOutputs(ResolvedOrder memory order) external view returns (OutputToken[] memory); -} diff --git a/src/v4/interfaces/IReactor.sol b/src/v4/interfaces/IReactor.sol deleted file mode 100644 index 750c30a4..00000000 --- a/src/v4/interfaces/IReactor.sol +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {SignedOrder} from "../../base/ReactorStructs.sol"; - -/// @notice Interface for order execution reactors -interface IReactor { - /// @notice thrown when an auction resolver is not set - error EmptyAuctionResolver(); - /// @notice thrown when the order targets a different reactor - error InvalidReactor(); - /// @notice thrown when the order's deadline has passed - error DeadlinePassed(); - /// @notice thrown when a pre-execution hook is not set - error MissingPreExecutionHook(); - /// @notice thrown when resolver addr encoded in SignedOrder doesn't match signed resolver in OrderInfo - error ResolverMismatch(); - - /// @notice Execute a single order - /// @param order The order definition and valid signature to execute - function execute(SignedOrder calldata order) external payable; - - /// @notice Execute a single order using the given callback data - /// @param order The order definition and valid signature to execute - /// @param callbackData The callbackData to pass to the callback - function executeWithCallback(SignedOrder calldata order, bytes calldata callbackData) external payable; - - /// @notice Execute the given orders at once - /// @param orders The order definitions and valid signatures to execute - function executeBatch(SignedOrder[] calldata orders) external payable; - - /// @notice Execute the given orders at once using a callback with the given callback data - /// @param orders The order definitions and valid signatures to execute - /// @param callbackData The callbackData to pass to the callback - function executeBatchWithCallback(SignedOrder[] calldata orders, bytes calldata callbackData) external payable; -} diff --git a/src/v4/interfaces/IReactorCallback.sol b/src/v4/interfaces/IReactorCallback.sol deleted file mode 100644 index 9c3d1cf8..00000000 --- a/src/v4/interfaces/IReactorCallback.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {ResolvedOrder} from "../base/ReactorStructs.sol"; - -/// @notice Callback for executing orders through a reactor -interface IReactorCallback { - /// @notice Called by the reactor during order execution - /// @param resolvedOrders The orders to execute - /// @param callbackData The callback data - function reactorCallback(ResolvedOrder[] calldata resolvedOrders, bytes calldata callbackData) external; -} diff --git a/src/v4/lens/OrderQuoterV4.sol b/src/v4/lens/OrderQuoterV4.sol deleted file mode 100644 index 3d583646..00000000 --- a/src/v4/lens/OrderQuoterV4.sol +++ /dev/null @@ -1,76 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {IReactorCallback} from "../interfaces/IReactorCallback.sol"; -import {IReactor} from "../interfaces/IReactor.sol"; -import {ResolvedOrder} from "../base/ReactorStructs.sol"; -import {SignedOrder} from "../../base/ReactorStructs.sol"; - -/// @notice Quoter contract for v4 orders -/// @dev Note this is meant to be used as an off-chain lens contract to pre-validate generic orders -contract OrderQuoterV4 is IReactorCallback { - /// @notice thrown if reactorCallback receives more than one order - error OrdersLengthIncorrect(); - - uint256 private constant RESOLVED_ORDER_MIN_LENGTH = 768; - - /// @notice Quote the given order, returning the ResolvedOrder object which defines - /// the current input and output token amounts required to satisfy it - /// Also bubbles up any reverts that would occur during the processing of the order - /// @param reactor The v4 reactor address to use for quoting - /// @param order abi-encoded order, including `auctionResolver` as the first encoded struct member - /// @param sig The order signature - /// @return result The ResolvedOrder - function quote(IReactor reactor, bytes memory order, bytes memory sig) - external - returns (ResolvedOrder memory result) - { - try reactor.executeWithCallback(SignedOrder(order, sig), bytes("")) {} - catch (bytes memory reason) { - result = parseRevertReason(reason); - } - } - - /// @notice Return the auction resolver address from a given order (abi-encoded bytes) - /// @param order abi-encoded order with auctionResolver as the first field - /// @return auctionResolver The auction resolver address - function getAuctionResolver(bytes memory order) public pure returns (address auctionResolver) { - // In v4, orders are encoded as: abi.encode(auctionResolver, orderData) - // The first 32 bytes after the length prefix contain the auctionResolver address - assembly { - // Skip the 32-byte length prefix, read the first 32 bytes (address is right-padded) - auctionResolver := mload(add(order, 32)) - } - } - - /// @notice Parse the revert reason into a ResolvedOrder - /// @param reason The revert reason bytes - /// @return The decoded ResolvedOrder - function parseRevertReason(bytes memory reason) private pure returns (ResolvedOrder memory) { - if (reason.length < RESOLVED_ORDER_MIN_LENGTH) { - assembly { - revert(add(32, reason), mload(reason)) - } - } else { - return abi.decode(reason, (ResolvedOrder)); - } - } - - /// @notice Reactor callback function - /// @dev Reverts with the resolved order as reason - /// @param resolvedOrders The resolved orders - function reactorCallback(ResolvedOrder[] calldata resolvedOrders, bytes calldata) external pure { - if (resolvedOrders.length != 1) { - revert OrdersLengthIncorrect(); - } - bytes memory order = abi.encode(resolvedOrders[0]); - assembly { - revert(add(32, order), mload(order)) - } - } - - /// @notice Fallback function to receive ETH - /// @dev Required for ERC20ETH transfers which send ETH to this contract - receive() external payable {} -} - diff --git a/src/v4/lib/ExclusivityLib.sol b/src/v4/lib/ExclusivityLib.sol deleted file mode 100644 index 1816445f..00000000 --- a/src/v4/lib/ExclusivityLib.sol +++ /dev/null @@ -1,107 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {FixedPointMathLib} from "solmate/src/utils/FixedPointMathLib.sol"; -import {ResolvedOrder} from "../base/ReactorStructs.sol"; -import {OutputToken} from "../../base/ReactorStructs.sol"; - -/// @title ExclusiveOverride -/// @dev This library handles order exclusivity for V4 orders -/// giving the configured filler exclusive rights to fill the order before exclusivityEnd -/// or enforcing an override price improvement by non-exclusive fillers -library ExclusivityLib { - using FixedPointMathLib for uint256; - - /// @notice thrown when an order has strict exclusivity and the filler does not have it - error NoExclusiveOverride(); - - uint256 private constant STRICT_EXCLUSIVITY = 0; - uint256 private constant BPS = 10_000; - - /// @notice Applies exclusivity override to the resolved order if necessary - /// @param order The order to apply exclusivity override to - /// @param exclusive The exclusive address - /// @param exclusivityEnd The exclusivity end time - /// @param exclusivityOverrideBps The exclusivity override BPS - /// @param filler The address of the filler - function handleExclusiveOverrideTimestamp( - ResolvedOrder memory order, - address exclusive, - uint256 exclusivityEnd, - uint256 exclusivityOverrideBps, - address filler - ) internal view { - _handleExclusiveOverride(order, exclusive, exclusivityEnd, exclusivityOverrideBps, block.timestamp, filler); - } - - /// @notice Applies exclusivity override to the resolved order if necessary - /// @param order The order to apply exclusivity override to - /// @param exclusive The exclusive address - /// @param exclusivityEnd The exclusivity end block number - /// @param exclusivityOverrideBps The exclusivity override BPS - /// @param blockNumberish The current block number - /// @param filler The address of the filler - function handleExclusiveOverrideBlock( - ResolvedOrder memory order, - address exclusive, - uint256 exclusivityEnd, - uint256 exclusivityOverrideBps, - uint256 blockNumberish, - address filler - ) internal pure { - _handleExclusiveOverride(order, exclusive, exclusivityEnd, exclusivityOverrideBps, blockNumberish, filler); - } - - /// @notice Applies exclusivity override to the resolved order if necessary - /// @param order The order to apply exclusivity override to - /// @param exclusive The exclusive address - /// @param exclusivityEnd The exclusivity end timestamp or block number - /// @param exclusivityOverrideBps The exclusivity override BPS - /// @param currentPosition The block timestamp or number to determine exclusivity - /// @param filler The address of the filler - function _handleExclusiveOverride( - ResolvedOrder memory order, - address exclusive, - uint256 exclusivityEnd, - uint256 exclusivityOverrideBps, - uint256 currentPosition, - address filler - ) internal pure { - // if the filler has fill right, we proceed with the order as-is - if (hasFillingRights(exclusive, exclusivityEnd, currentPosition, filler)) { - return; - } - - // if override is 0, then assume strict exclusivity so the order cannot be filled - if (exclusivityOverrideBps == STRICT_EXCLUSIVITY) { - revert NoExclusiveOverride(); - } - - // scale outputs by override amount - OutputToken[] memory outputs = order.outputs; - for (uint256 i = 0; i < outputs.length;) { - OutputToken memory output = outputs[i]; - output.amount = output.amount.mulDivUp(BPS + exclusivityOverrideBps, BPS); - - unchecked { - i++; - } - } - } - - /// @notice checks if the caller currently has filling rights on the order - /// @param exclusive The exclusive address - /// @param exclusivityEnd The exclusivity end timestamp or block number - /// @param currentPosition The timestamp or block number to determine exclusivity - /// @param filler The address of the filler - /// @dev if the order has no exclusivity, always returns true - /// @dev if the order has active exclusivity and the current filler is the exclusive address, returns true - /// @dev if the order has active exclusivity and the current filler is not the exclusive address, returns false - function hasFillingRights(address exclusive, uint256 exclusivityEnd, uint256 currentPosition, address filler) - internal - pure - returns (bool) - { - return exclusive == address(0) || currentPosition > exclusivityEnd || exclusive == filler; - } -} diff --git a/src/v4/lib/HybridOrderLib.sol b/src/v4/lib/HybridOrderLib.sol deleted file mode 100644 index 7cbcb637..00000000 --- a/src/v4/lib/HybridOrderLib.sol +++ /dev/null @@ -1,215 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {InputToken, OutputToken} from "../../base/ReactorStructs.sol"; -import {OrderInfo} from "../base/ReactorStructs.sol"; -import {OrderInfoLib} from "./OrderInfoLib.sol"; -import {ERC20} from "solmate/src/tokens/ERC20.sol"; -import {PriceCurveLib, PriceCurveElement} from "tribunal/src/lib/PriceCurveLib.sol"; -import {FixedPointMathLib} from "solady/utils/FixedPointMathLib.sol"; - -/// @notice Cosigner data for hybrid auction orders -struct HybridCosignerData { - uint256 auctionTargetBlock; - uint256[] supplementalPriceCurve; - address exclusiveFiller; - uint256 exclusivityOverrideBps; - uint256 exclusivityEndBlock; -} - -/// @notice Input tokens for hybrid auction -/// @dev if exact-in, input amount is fixed at maxAmount -/// @dev if exact-out, scale down from maxAmount -struct HybridInput { - ERC20 token; - uint256 maxAmount; -} - -/// @notice Output tokens for hybrid auction -/// @dev if exact-in, scale up from minAmount -/// @dev if exact-out, output amount is fixed at minAmount -struct HybridOutput { - address token; - uint256 minAmount; - address recipient; -} - -/// @notice Hybrid auction order combining Dutch decay and priority gas auctions -struct HybridOrder { - OrderInfo info; - address cosigner; - HybridInput input; - HybridOutput[] outputs; - uint256 auctionStartBlock; - uint256 baselinePriorityFee; - uint256 scalingFactor; - uint256[] priceCurve; - HybridCosignerData cosignerData; - bytes cosignature; -} - -/// @notice Library for handling hybrid auction orders -library HybridOrderLib { - using OrderInfoLib for OrderInfo; - using FixedPointMathLib for uint256; - using PriceCurveLib for uint256[]; - using PriceCurveLib for uint256; - - error InvalidTargetBlock(uint256 blockNumber, uint256 targetBlockNumber); - error InvalidTargetBlockDesignation(); - - bytes internal constant HYBRID_ORDER_TYPE = abi.encodePacked( - "HybridOrder(", - "OrderInfo info,", - "address cosigner,", - "HybridInput input,", - "HybridOutput[] outputs,", - "uint256 auctionStartBlock,", - "uint256 baselinePriorityFee,", - "uint256 scalingFactor,", - "uint256[] priceCurve)" - ); - // Note: cosignerData and cosignature are not included in EIP-712 type hash - - bytes internal constant HYBRID_INPUT_TYPE = - abi.encodePacked("HybridInput(", "address token,", "uint256 maxAmount)"); - - bytes internal constant HYBRID_OUTPUT_TYPE = - abi.encodePacked("HybridOutput(", "address token,", "uint256 minAmount,", "address recipient)"); - - bytes internal constant ORDER_INFO_TYPE = abi.encodePacked( - "OrderInfo(", - "address reactor,", - "address swapper,", - "uint256 nonce,", - "uint256 deadline,", - "address preExecutionHook,", - "bytes preExecutionHookData,", - "address postExecutionHook,", - "bytes postExecutionHookData,", - "address auctionResolver)" - ); - - bytes internal constant TOKEN_PERMISSIONS_TYPE = "TokenPermissions(address token,uint256 amount)"; - - bytes32 internal constant HYBRID_INPUT_TYPE_HASH = keccak256(HYBRID_INPUT_TYPE); - bytes32 internal constant HYBRID_OUTPUT_TYPE_HASH = keccak256(HYBRID_OUTPUT_TYPE); - bytes32 internal constant ORDER_INFO_TYPE_HASH = keccak256(ORDER_INFO_TYPE); - - bytes32 internal constant HYBRID_ORDER_TYPE_HASH = - keccak256(abi.encodePacked(HYBRID_ORDER_TYPE, HYBRID_INPUT_TYPE, HYBRID_OUTPUT_TYPE, ORDER_INFO_TYPE)); - - // Note: Sub-structs must be defined in alphabetical order in the EIP-712 spec - string internal constant PERMIT2_ORDER_TYPE = string( - abi.encodePacked( - "HybridOrder witness)", - HYBRID_INPUT_TYPE, - HYBRID_ORDER_TYPE, - HYBRID_OUTPUT_TYPE, - ORDER_INFO_TYPE, - TOKEN_PERMISSIONS_TYPE - ) - ); - - /// @notice Hash a hybrid order - function hash(HybridOrder memory order) internal pure returns (bytes32) { - return keccak256( - abi.encode( - HYBRID_ORDER_TYPE_HASH, - order.info.hash(), - order.cosigner, - hashInput(order.input), - hashOutputs(order.outputs), - order.auctionStartBlock, - order.baselinePriorityFee, - order.scalingFactor, - keccak256(abi.encodePacked(order.priceCurve)) - ) - ); - } - - /// @notice Hash hybrid input - function hashInput(HybridInput memory input) private pure returns (bytes32) { - return keccak256(abi.encode(HYBRID_INPUT_TYPE_HASH, input.token, input.maxAmount)); - } - - /// @notice Hash hybrid outputs - function hashOutputs(HybridOutput[] memory outputs) private pure returns (bytes32) { - bytes32[] memory hashes = new bytes32[](outputs.length); - for (uint256 i = 0; i < outputs.length; i++) { - hashes[i] = keccak256( - abi.encode(HYBRID_OUTPUT_TYPE_HASH, outputs[i].token, outputs[i].minAmount, outputs[i].recipient) - ); - } - return keccak256(abi.encodePacked(hashes)); - } - - /// @notice get the digest of the cosigner data - /// @param order the hybridOrder - /// @param orderHash the hash of the order - function cosignerDigest(HybridOrder memory order, bytes32 orderHash) internal view returns (bytes32) { - return keccak256(abi.encodePacked(orderHash, block.chainid, abi.encode(order.cosignerData))); - } - - /// @notice Derive scaling factor for the current block for the hybrid auction - /// @dev Adapted from Tribunal's deriveAmounts to work with UniswapX HybridOrder structure - /// @param order The hybrid order containing all auction parameters - /// @param priceCurve The effective price curve to use - /// @param targetBlock The target block for the auction start - /// @param fillBlock The block at which the fill is happening - /// @return currentScalingFactor The current scaling factor - function deriveCurrentScalingFactor( - HybridOrder memory order, - uint256[] memory priceCurve, - uint256 targetBlock, - uint256 fillBlock - ) internal pure returns (uint256 currentScalingFactor) { - currentScalingFactor = 1e18; - - // Calculate scaling from price curve if auction is active - if (targetBlock != 0) { - if (targetBlock > fillBlock) { - revert InvalidTargetBlock(targetBlock, fillBlock); - } - // Derive the total blocks passed since the target block. - uint256 blocksPassed; - unchecked { - blocksPassed = fillBlock - targetBlock; - } - currentScalingFactor = priceCurve.getCalculatedValues(blocksPassed); - } else { - if (priceCurve.length != 0) { - revert InvalidTargetBlockDesignation(); - } - } - - if (!order.scalingFactor.sharesScalingDirection(currentScalingFactor)) { - revert PriceCurveLib.InvalidPriceCurveParameters(); - } - } - - /// @notice scale the outputs of a hybrid order for exact-in orders - /// @param outputs the outputs to scale - /// @param scalingFactor the scaling factor to use - /// @return outputs scaled up from minAmount - function scale(HybridOutput[] memory outputs, uint256 scalingFactor) internal pure returns (OutputToken[] memory) { - OutputToken[] memory result = new OutputToken[](outputs.length); - for (uint256 i = 0; i < outputs.length; i++) { - result[i] = OutputToken({ - token: outputs[i].token, - amount: outputs[i].minAmount.mulWadUp(scalingFactor), - recipient: outputs[i].recipient - }); - } - return result; - } - - /// @notice scale the input of a hybrid order for exact-out orders - /// @param input the input to scale - /// @param scalingFactor the scaling factor to use - /// @return input scaled down from maxAmount - function scale(HybridInput memory input, uint256 scalingFactor) internal pure returns (InputToken memory) { - return - InputToken({token: input.token, amount: input.maxAmount.mulWad(scalingFactor), maxAmount: input.maxAmount}); - } -} diff --git a/src/v4/lib/OrderInfoLib.sol b/src/v4/lib/OrderInfoLib.sol deleted file mode 100644 index 51dcf5d4..00000000 --- a/src/v4/lib/OrderInfoLib.sol +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {OrderInfo} from "../base/ReactorStructs.sol"; - -/// @notice helpers for handling OrderInfo objects -library OrderInfoLib { - bytes internal constant ORDER_INFO_TYPE = - "OrderInfo(address reactor,address swapper,uint256 nonce,uint256 deadline,address preExecutionHook,bytes preExecutionHookData,address postExecutionHook,bytes postExecutionHookData,address auctionResolver)"; - bytes32 internal constant ORDER_INFO_TYPE_HASH = keccak256(ORDER_INFO_TYPE); - - /// @notice hash an OrderInfo object - /// @param info The OrderInfo object to hash - function hash(OrderInfo memory info) internal pure returns (bytes32) { - return keccak256( - abi.encode( - ORDER_INFO_TYPE_HASH, - info.reactor, - info.swapper, - info.nonce, - info.deadline, - info.preExecutionHook, - keccak256(info.preExecutionHookData), - info.postExecutionHook, - keccak256(info.postExecutionHookData), - info.auctionResolver - ) - ); - } -} diff --git a/src/v4/lib/Permit2Lib.sol b/src/v4/lib/Permit2Lib.sol deleted file mode 100644 index 38c93a83..00000000 --- a/src/v4/lib/Permit2Lib.sol +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {ISignatureTransfer} from "permit2/src/interfaces/ISignatureTransfer.sol"; -import {ResolvedOrder} from "../base/ReactorStructs.sol"; - -/// @notice handling some permit2-specific encoding -library Permit2Lib { - /// @notice returns a ResolvedOrder into a permit object - function toPermit(ResolvedOrder memory order) internal pure returns (ISignatureTransfer.PermitTransferFrom memory) { - return ISignatureTransfer.PermitTransferFrom({ - permitted: ISignatureTransfer.TokenPermissions({ - token: address(order.input.token), amount: order.input.maxAmount - }), - nonce: order.info.nonce, - deadline: order.info.deadline - }); - } - - /// @notice returns a ResolvedOrder into a permit object - function transferDetails(ResolvedOrder memory order, address to) - internal - pure - returns (ISignatureTransfer.SignatureTransferDetails memory) - { - return ISignatureTransfer.SignatureTransferDetails({to: to, requestedAmount: order.input.amount}); - } -} diff --git a/src/v4/lib/PriorityOrderLib.sol b/src/v4/lib/PriorityOrderLib.sol deleted file mode 100644 index 78878bca..00000000 --- a/src/v4/lib/PriorityOrderLib.sol +++ /dev/null @@ -1,162 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {OrderInfo} from "../base/ReactorStructs.sol"; -import {OrderInfoLib} from "./OrderInfoLib.sol"; -import {PriorityInput, PriorityOutput, PriorityCosignerData} from "../../lib/PriorityOrderLib.sol"; - -/// @dev External struct used to specify priority orders -struct PriorityOrder { - // generic order information - OrderInfo info; - // The address which may cosign the order - address cosigner; - // the block at which the order can be executed - uint256 auctionStartBlock; - // the baseline priority fee for the order, above which additional taxes are applied - uint256 baselinePriorityFeeWei; - // The tokens that the swapper will provide when settling the order - PriorityInput input; - // The tokens that must be received to satisfy the order - PriorityOutput[] outputs; - // signed over by the cosigner - PriorityCosignerData cosignerData; - // signature from the cosigner over (orderHash || cosignerData) - bytes cosignature; -} - -/// @notice helpers for handling priority order objects -library PriorityOrderLib { - using OrderInfoLib for OrderInfo; - - bytes internal constant PRIORITY_INPUT_TOKEN_TYPE = - "PriorityInput(address token,uint256 amount,uint256 mpsPerPriorityFeeWei)"; - - bytes32 internal constant PRIORITY_INPUT_TOKEN_TYPE_HASH = keccak256(PRIORITY_INPUT_TOKEN_TYPE); - - bytes internal constant PRIORITY_OUTPUT_TOKEN_TYPE = - "PriorityOutput(address token,uint256 amount,uint256 mpsPerPriorityFeeWei,address recipient)"; - - bytes32 internal constant PRIORITY_OUTPUT_TOKEN_TYPE_HASH = keccak256(PRIORITY_OUTPUT_TOKEN_TYPE); - - string internal constant TOKEN_PERMISSIONS_TYPE = "TokenPermissions(address token,uint256 amount)"; - - // Witness wrapper that includes the resolver address for security - bytes internal constant PRIORITY_ORDER_WITNESS_TYPE = - abi.encodePacked("PriorityOrderWitness(", "address resolver,", "PriorityOrder order)"); - - bytes32 internal constant PRIORITY_ORDER_WITNESS_TYPE_HASH = keccak256( - abi.encodePacked( - PRIORITY_ORDER_WITNESS_TYPE, - OrderInfoLib.ORDER_INFO_TYPE, - PRIORITY_INPUT_TOKEN_TYPE, - TOPLEVEL_PRIORITY_ORDER_TYPE, - PRIORITY_OUTPUT_TOKEN_TYPE - ) - ); - - // EIP712 notes that nested structs should be ordered alphabetically. - // With our added PriorityOrderWitness witness, the top level type becomes - // "PermitWitnessTransferFrom(TokenPermissions permitted,address spender,uint256 nonce,uint256 deadline,PriorityOrderWitness witness)" - // Meaning we order the nested structs as follows: - // OrderInfo, PriorityInput, PriorityOrder, PriorityOrderWitness, PriorityOutput - string internal constant PERMIT2_ORDER_TYPE = string( - abi.encodePacked( - "PriorityOrderWitness witness)", - OrderInfoLib.ORDER_INFO_TYPE, - PRIORITY_INPUT_TOKEN_TYPE, - TOPLEVEL_PRIORITY_ORDER_TYPE, - PRIORITY_ORDER_WITNESS_TYPE, - PRIORITY_OUTPUT_TOKEN_TYPE, - TOKEN_PERMISSIONS_TYPE - ) - ); - - bytes internal constant TOPLEVEL_PRIORITY_ORDER_TYPE = abi.encodePacked( - "PriorityOrder(", - "OrderInfo info,", - "address cosigner,", - "uint256 auctionStartBlock,", - "uint256 baselinePriorityFeeWei,", - "PriorityInput input,", - "PriorityOutput[] outputs)" - ); - - // EIP712 notes that nested structs should be ordered alphabetically: - // OrderInfo, PriorityInput, PriorityOutput - bytes internal constant ORDER_TYPE = abi.encodePacked( - TOPLEVEL_PRIORITY_ORDER_TYPE, - OrderInfoLib.ORDER_INFO_TYPE, - PRIORITY_INPUT_TOKEN_TYPE, - PRIORITY_OUTPUT_TOKEN_TYPE - ); - bytes32 internal constant ORDER_TYPE_HASH = keccak256(ORDER_TYPE); - - /// @notice returns the hash of an input token struct - function hash(PriorityInput memory input) private pure returns (bytes32) { - return - keccak256(abi.encode(PRIORITY_INPUT_TOKEN_TYPE_HASH, input.token, input.amount, input.mpsPerPriorityFeeWei)); - } - - /// @notice returns the hash of an output token struct - function hash(PriorityOutput memory output) private pure returns (bytes32) { - return keccak256( - abi.encode( - PRIORITY_OUTPUT_TOKEN_TYPE_HASH, - output.token, - output.amount, - output.mpsPerPriorityFeeWei, - output.recipient - ) - ); - } - - /// @notice returns the hash of an array of output token struct - function hash(PriorityOutput[] memory outputs) private pure returns (bytes32) { - unchecked { - bytes memory packedHashes = new bytes(32 * outputs.length); - - for (uint256 i = 0; i < outputs.length; i++) { - bytes32 outputHash = hash(outputs[i]); - assembly { - mstore(add(add(packedHashes, 0x20), mul(i, 0x20)), outputHash) - } - } - - return keccak256(packedHashes); - } - } - - /// @notice hash the given order - /// @param order the order to hash - /// @return the eip-712 order hash - function hash(PriorityOrder memory order) internal pure returns (bytes32) { - return keccak256( - abi.encode( - ORDER_TYPE_HASH, - order.info.hash(), - order.cosigner, - order.auctionStartBlock, - order.baselinePriorityFeeWei, - hash(order.input), - hash(order.outputs) - ) - ); - } - - /// @notice Compute the witness hash that includes the resolver address - /// @param order the priorityOrder - /// @param resolver the auction resolver address - /// @return witness hash that binds the order to the resolver - function witnessHash(PriorityOrder memory order, address resolver) internal pure returns (bytes32) { - return keccak256(abi.encode(PRIORITY_ORDER_WITNESS_TYPE_HASH, resolver, hash(order))); - } - - /// @notice get the digest of the cosigner data - /// @param order the priorityOrder - /// @param orderHash the hash of the order - /// @return the digest of the cosigner data - function cosignerDigest(PriorityOrder memory order, bytes32 orderHash) internal view returns (bytes32) { - return keccak256(abi.encodePacked(orderHash, block.chainid, abi.encode(order.cosignerData))); - } -} diff --git a/src/v4/lib/TokenTransferLib.sol b/src/v4/lib/TokenTransferLib.sol deleted file mode 100644 index d96b4d0b..00000000 --- a/src/v4/lib/TokenTransferLib.sol +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {IPermit2} from "permit2/src/interfaces/IPermit2.sol"; -import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol"; -import {ResolvedOrder} from "../base/ReactorStructs.sol"; -import {Permit2Lib} from "./Permit2Lib.sol"; - -/// @notice Library for transferring input tokens using Permit2 -library TokenTransferLib { - using Permit2Lib for ResolvedOrder; - - /// @notice Transfer input tokens from swapper to filler using permitWitnessTransferFrom - /// @param permit2 The Permit2 contract instance - /// @param order The resolved order containing transfer details - /// @param to The recipient address (typically the filler) - function signatureTransferInputTokens(IPermit2 permit2, ResolvedOrder calldata order, address to) internal { - // Execute the token transfer via Permit2 with resolver-provided witness - // order.hash contains the witness hash, witnessTypeString is provided by the resolver - permit2.permitWitnessTransferFrom( - order.toPermit(), - order.transferDetails(to), - order.info.swapper, - order.hash, - order.witnessTypeString, - order.sig - ); - } - - /// @notice Transfer input tokens using existing allowance - /// @dev Assumes allowance has been set (either via setAllowance or previously) - /// @param permit2 The Permit2 contract instance - /// @param order The resolved order containing transfer details - /// @param to The recipient address (typically the filler) - function allowanceTransferInputTokens(IPermit2 permit2, ResolvedOrder calldata order, address to) internal { - permit2.transferFrom(order.info.swapper, to, uint160(order.input.amount), address(order.input.token)); - } -} diff --git a/src/v4/resolvers/HybridAuctionResolver.sol b/src/v4/resolvers/HybridAuctionResolver.sol deleted file mode 100644 index 5ff6db22..00000000 --- a/src/v4/resolvers/HybridAuctionResolver.sol +++ /dev/null @@ -1,150 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {PriceCurveLib} from "tribunal/src/lib/PriceCurveLib.sol"; - -import {IAuctionResolver} from "../interfaces/IAuctionResolver.sol"; -import {ExclusivityLib} from "../lib/ExclusivityLib.sol"; -import {SignedOrder, InputToken, OutputToken} from "../../base/ReactorStructs.sol"; -import {ResolvedOrder} from "../base/ReactorStructs.sol"; -import {HybridOrder, HybridOrderLib, HybridInput, HybridOutput} from "../lib/HybridOrderLib.sol"; -import {CosignerLib} from "../../lib/CosignerLib.sol"; -import {BlockNumberish} from "blocknumberish/src/BlockNumberish.sol"; - -/// @notice Resolver for hybrid Dutch + priority gas auctions following Tribunal's model -contract HybridAuctionResolver is IAuctionResolver, BlockNumberish { - using HybridOrderLib for HybridOrder; - using HybridOrderLib for HybridOutput[]; - using HybridOrderLib for HybridInput; - using PriceCurveLib for uint256[]; - using PriceCurveLib for uint256; - - /// @notice Base scaling factor (1e18). - uint256 public constant BASE_SCALING_FACTOR = 1e18; - - error InvalidAuctionBlock(); - error InvalidExclusivityEndBlock(); - error InvalidGasPrice(); - - constructor() BlockNumberish() {} - - /// @inheritdoc IAuctionResolver - function resolve(SignedOrder calldata signedOrder) - external - view - override - returns (ResolvedOrder memory resolvedOrder) - { - HybridOrder memory order = abi.decode(signedOrder.order, (HybridOrder)); - - // Extract cosigner data and determine target block + supplemental curve - uint256 auctionTargetBlock = order.auctionStartBlock; - uint256[] memory effectivePriceCurve = order.priceCurve; - uint256 exclusivityEndBlock = 0; - - if (order.cosigner != address(0)) { - // Verify cosigner signature - bytes32 orderHash = order.hash(); - CosignerLib.verify(order.cosigner, order.cosignerDigest(orderHash), order.cosignature); - - if (order.cosignerData.auctionTargetBlock != 0) { - auctionTargetBlock = order.cosignerData.auctionTargetBlock; - } - - if (order.cosignerData.supplementalPriceCurve.length > 0) { - effectivePriceCurve = - order.priceCurve.applyMemorySupplementalPriceCurve(order.cosignerData.supplementalPriceCurve); - } - - exclusivityEndBlock = order.cosignerData.exclusivityEndBlock; - if (exclusivityEndBlock != 0 && auctionTargetBlock != 0 && exclusivityEndBlock < auctionTargetBlock) { - revert InvalidExclusivityEndBlock(); - } - } - - uint256 blockNumberish = _getBlockNumberish(); - if (auctionTargetBlock != 0 && blockNumberish < auctionTargetBlock) { - revert InvalidAuctionBlock(); - } - - uint256 currentScalingFactor = - HybridOrderLib.deriveCurrentScalingFactor(order, effectivePriceCurve, auctionTargetBlock, blockNumberish); - - uint256 scalingMultiplier; - // When neutral (scalingFactor == 1e18), determine mode from currentScalingFactor - bool useExactIn = (order.scalingFactor > BASE_SCALING_FACTOR) - || (order.scalingFactor == BASE_SCALING_FACTOR && currentScalingFactor >= BASE_SCALING_FACTOR); - - uint256 priorityFeeAboveBaseline = _getPriorityFee(order.baselinePriorityFee); - if (useExactIn) { - scalingMultiplier = - currentScalingFactor + ((order.scalingFactor - BASE_SCALING_FACTOR) * priorityFeeAboveBaseline); - resolvedOrder = ResolvedOrder({ - info: order.info, - input: InputToken({ - token: order.input.token, amount: order.input.maxAmount, maxAmount: order.input.maxAmount - }), - outputs: order.outputs.scale(scalingMultiplier), - sig: signedOrder.sig, - hash: order.hash(), - auctionResolver: address(this), - witnessTypeString: HybridOrderLib.PERMIT2_ORDER_TYPE - }); - } else { - scalingMultiplier = - currentScalingFactor - ((BASE_SCALING_FACTOR - order.scalingFactor) * priorityFeeAboveBaseline); - OutputToken[] memory outputs = new OutputToken[](order.outputs.length); - for (uint256 i = 0; i < order.outputs.length; i++) { - outputs[i] = OutputToken({ - token: order.outputs[i].token, - amount: order.outputs[i].minAmount, - recipient: order.outputs[i].recipient - }); - } - resolvedOrder = ResolvedOrder({ - info: order.info, - input: order.input.scale(scalingMultiplier), - outputs: outputs, - sig: signedOrder.sig, - hash: order.hash(), - auctionResolver: address(this), - witnessTypeString: HybridOrderLib.PERMIT2_ORDER_TYPE - }); - } - - // Handle exclusivity only when explicitly configured. - // Note: Uses tx.origin to identify filler since resolver is called via staticcall - // Note: fillers should use EOA to call a deployed executor contract, instead of using a contract wallet directly - if (order.cosigner != address(0) && exclusivityEndBlock != 0) { - ExclusivityLib.handleExclusiveOverrideBlock( - resolvedOrder, - order.cosignerData.exclusiveFiller, - exclusivityEndBlock, - order.cosignerData.exclusivityOverrideBps, - blockNumberish, - tx.origin - ); - } - } - - /// @inheritdoc IAuctionResolver - function getPermit2OrderType() external pure override returns (string memory) { - return HybridOrderLib.PERMIT2_ORDER_TYPE; - } - - /// @notice resolve the priority fee for the current transaction - /// @notice tx.gasprice must be greater than or equal to block.basefee - /// @param baselinePriorityFeeWei the baseline priority fee to be subtracted from calculated priority fee - /// @return priorityFee the resolved priority fee - function _getPriorityFee(uint256 baselinePriorityFeeWei) private view returns (uint256 priorityFee) { - if (tx.gasprice < block.basefee) revert InvalidGasPrice(); - unchecked { - priorityFee = tx.gasprice - block.basefee; - if (priorityFee > baselinePriorityFeeWei) { - priorityFee -= baselinePriorityFeeWei; - } else { - priorityFee = 0; - } - } - } -} diff --git a/src/v4/resolvers/PriorityAuctionResolver.sol b/src/v4/resolvers/PriorityAuctionResolver.sol deleted file mode 100644 index db397fee..00000000 --- a/src/v4/resolvers/PriorityAuctionResolver.sol +++ /dev/null @@ -1,134 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {IAuctionResolver} from "../interfaces/IAuctionResolver.sol"; -import {SignedOrder, InputToken, OutputToken} from "../../base/ReactorStructs.sol"; -import {ResolvedOrder} from "../base/ReactorStructs.sol"; -import {PriorityInput, PriorityOutput} from "../../lib/PriorityOrderLib.sol"; -import {PriorityOrder, PriorityOrderLib} from "../lib/PriorityOrderLib.sol"; -import {PriorityFeeLib} from "../../lib/PriorityFeeLib.sol"; -import {CosignerLib} from "../../lib/CosignerLib.sol"; -import {IPermit2} from "permit2/src/interfaces/IPermit2.sol"; - -/// @notice Auction resolver for priority fee based orders -contract PriorityAuctionResolver is IAuctionResolver { - using PriorityOrderLib for PriorityOrder; - using PriorityFeeLib for PriorityInput; - using PriorityFeeLib for PriorityOutput; - using PriorityFeeLib for PriorityOutput[]; - - /// @notice thrown when an order's auctionStartBlock is in the future - error OrderNotFillable(); - /// @notice thrown when an order's nonce has already been used - error OrderAlreadyFilled(); - /// @notice thrown when an order's input and outputs both scale with priority fee - error InputOutputScaling(); - /// @notice thrown when tx gasprice is less than block.basefee - error InvalidGasPrice(); - - /// @notice Permit2 instance for nonce checking - IPermit2 public immutable permit2; - - constructor(IPermit2 _permit2) { - permit2 = _permit2; - } - - /// @inheritdoc IAuctionResolver - function resolve(SignedOrder calldata signedOrder) - external - view - override - returns (ResolvedOrder memory resolvedOrder) - { - PriorityOrder memory order = abi.decode(signedOrder.order, (PriorityOrder)); - - _checkPermit2Nonce(order.info.swapper, order.info.nonce); - - bytes32 orderHash = order.hash(); - - _validateOrder(orderHash, order); - - uint256 priorityFee = _getPriorityFee(order.baselinePriorityFeeWei); - - InputToken memory scaledInput = order.input.scale(priorityFee); - OutputToken[] memory scaledOutputs = order.outputs.scale(priorityFee); - - resolvedOrder = ResolvedOrder({ - info: order.info, - input: scaledInput, - outputs: scaledOutputs, - sig: signedOrder.sig, - hash: order.witnessHash(address(this)), // Witness hash that includes resolver and full order - auctionResolver: address(this), - witnessTypeString: PriorityOrderLib.PERMIT2_ORDER_TYPE - }); - } - - /// @inheritdoc IAuctionResolver - function getPermit2OrderType() external pure override returns (string memory) { - return PriorityOrderLib.PERMIT2_ORDER_TYPE; - } - - /// @notice validate the priority order fields - /// - resolved auctionStartBlock must not be in the future - /// - if input scales with priority fee, outputs must not scale - /// @dev Throws if the order is invalid - function _validateOrder(bytes32 orderHash, PriorityOrder memory order) internal view { - uint256 auctionStartBlock = order.auctionStartBlock; - - // we override auctionStartBlock with the cosigned auctionTargetBlock only if: - // - cosigner is specified - // - current block is before the auctionStartBlock signed by the user - // - cosigned auctionTargetBlock is before the auctionStartBlock signed by the user - if ( - order.cosigner != address(0) && block.number < auctionStartBlock - && order.cosignerData.auctionTargetBlock < auctionStartBlock - ) { - CosignerLib.verify(order.cosigner, order.cosignerDigest(orderHash), order.cosignature); - - auctionStartBlock = order.cosignerData.auctionTargetBlock; - } - - /// revert if the resolved auctionStartBlock is in the future - if (block.number < auctionStartBlock) { - revert OrderNotFillable(); - } - - if (order.input.mpsPerPriorityFeeWei > 0) { - for (uint256 i = 0; i < order.outputs.length; i++) { - if (order.outputs[i].mpsPerPriorityFeeWei > 0) { - revert InputOutputScaling(); - } - } - } - } - - /// @notice resolve the priority fee for the current transaction - /// @notice tx.gasprice must be greater than or equal to block.basefee - /// @param baselinePriorityFeeWei the baseline priority fee to be subtracted from calculated priority fee - /// @return priorityFee the resolved priority fee - function _getPriorityFee(uint256 baselinePriorityFeeWei) internal view returns (uint256 priorityFee) { - if (tx.gasprice < block.basefee) revert InvalidGasPrice(); - unchecked { - priorityFee = tx.gasprice - block.basefee; - if (priorityFee > baselinePriorityFeeWei) { - priorityFee -= baselinePriorityFeeWei; - } else { - priorityFee = 0; - } - } - } - - /// @notice check if an order has already been filled - /// @dev implementation copied from https://github.com/Uniswap/permit2/blob/cc56ad0f3439c502c246fc5cfcc3db92bb8b7219/src/SignatureTransfer.sol#L150 - /// @param swapper the address of the swapper - /// @param nonce the nonce associated with the order - function _checkPermit2Nonce(address swapper, uint256 nonce) internal view { - uint256 wordPos = uint248(nonce >> 8); - uint256 bit = 1 << uint8(nonce); // bitPos - uint256 bitmap = permit2.nonceBitmap(swapper, wordPos); - uint256 flipped = bitmap ^ bit; - - if (flipped & bit == 0) revert OrderAlreadyFilled(); - } -} diff --git a/test/sample-executors/V4UniversalRouterExecutor.t.sol b/test/sample-executors/V4UniversalRouterExecutor.t.sol deleted file mode 100644 index 16374342..00000000 --- a/test/sample-executors/V4UniversalRouterExecutor.t.sol +++ /dev/null @@ -1,444 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {Test} from "forge-std/Test.sol"; -import {IPermit2} from "permit2/src/interfaces/IPermit2.sol"; -import {ERC20} from "solmate/src/tokens/ERC20.sol"; -import {SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol"; - -import {DeployPermit2} from "../util/DeployPermit2.sol"; -import {PermitSignature} from "../util/PermitSignature.sol"; -import {MockERC20} from "../util/mock/MockERC20.sol"; -import {OutputsBuilder} from "../util/OutputsBuilder.sol"; - -import {Reactor} from "../../src/v4/Reactor.sol"; -import {IReactor} from "../../src/v4/interfaces/IReactor.sol"; -import {OrderInfo, ResolvedOrder} from "../../src/v4/base/ReactorStructs.sol"; -import {SignedOrder, InputToken} from "../../src/base/ReactorStructs.sol"; -import {OrderInfoBuilder} from "../v4/util/OrderInfoBuilder.sol"; -import {MockAuctionResolver} from "../v4/util/mock/MockAuctionResolver.sol"; -import {MockOrder, MockOrderLib} from "../v4/util/mock/MockOrderLib.sol"; -import {TokenTransferHook} from "../../src/v4/hooks/TokenTransferHook.sol"; -import {ReactorEvents} from "../../src/base/ReactorEvents.sol"; -import {NATIVE} from "../../src/lib/CurrencyLibrary.sol"; - -import {V4UniversalRouterExecutor} from "../../src/sample-executors/V4UniversalRouterExecutor.sol"; - -/// @notice Mock Universal Router for testing -contract MockUniversalRouter { - uint256 public receivedETH; - bool public shouldRevert; - - function setShouldRevert(bool _shouldRevert) external { - shouldRevert = _shouldRevert; - } - - fallback() external payable { - if (shouldRevert) { - revert("Mock revert"); - } - receivedETH = msg.value; - } - - receive() external payable { - if (shouldRevert) { - revert("Mock revert"); - } - receivedETH = msg.value; - } -} - -contract V4UniversalRouterExecutorTest is Test, PermitSignature, DeployPermit2, ReactorEvents { - using OrderInfoBuilder for OrderInfo; - using MockOrderLib for MockOrder; - using SafeTransferLib for ERC20; - - uint256 constant ONE = 10 ** 18; - address internal constant PROTOCOL_FEE_OWNER = address(1); - - MockERC20 tokenIn; - MockERC20 tokenOut; - IPermit2 permit2; - Reactor reactor; - MockAuctionResolver mockResolver; - TokenTransferHook tokenTransferHook; - MockUniversalRouter mockUniversalRouter; - V4UniversalRouterExecutor executor; - - uint256 swapperPrivateKey; - address swapper; - address whitelistedCaller; - address owner; - - function setUp() public { - tokenIn = new MockERC20("Input", "IN", 18); - tokenOut = new MockERC20("Output", "OUT", 18); - swapperPrivateKey = 0x12341234; - swapper = vm.addr(swapperPrivateKey); - whitelistedCaller = makeAddr("whitelistedCaller"); - owner = makeAddr("owner"); - - permit2 = IPermit2(deployPermit2()); - reactor = new Reactor(PROTOCOL_FEE_OWNER, permit2); - mockResolver = new MockAuctionResolver(); - tokenTransferHook = new TokenTransferHook(permit2, reactor); - mockUniversalRouter = new MockUniversalRouter(); - - address[] memory whitelistedCallers = new address[](1); - whitelistedCallers[0] = whitelistedCaller; - - executor = new V4UniversalRouterExecutor( - whitelistedCallers, IReactor(address(reactor)), owner, address(mockUniversalRouter), permit2 - ); - - // Fund executor with output tokens for fills - tokenOut.mint(address(executor), 100 * ONE); - vm.deal(address(executor), 100 ether); - } - - /// @dev Create a signed order for V4 Reactor using MockOrder - function createAndSignOrder(MockOrder memory mockOrder) - public - view - returns (SignedOrder memory signedOrder, bytes32 orderHash) - { - orderHash = mockOrder.witnessHash(address(mockOrder.info.auctionResolver)); - bytes memory sig = signOrder(swapperPrivateKey, address(permit2), mockOrder); - bytes memory orderData = abi.encode(mockOrder); - bytes memory encodedOrder = abi.encode(address(mockResolver), orderData); - signedOrder = SignedOrder(encodedOrder, sig); - } - - /// @dev Helper to create a basic MockOrder - function createBasicOrder(uint256 inputAmount, uint256 outputAmount, uint256 deadline) - internal - view - returns (MockOrder memory) - { - return MockOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(deadline) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(mockResolver), - input: InputToken(tokenIn, inputAmount, inputAmount), - outputs: OutputsBuilder.single(address(tokenOut), outputAmount, swapper) - }); - } - - /// @notice Test that the executor can fill an order through the V4 reactor - function test_executeOrder() public { - uint256 inputAmount = 1 ether; - uint256 outputAmount = 1 ether; - uint256 deadline = block.timestamp + 1000; - - tokenIn.mint(swapper, inputAmount); - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - - MockOrder memory order = createBasicOrder(inputAmount, outputAmount, deadline); - (SignedOrder memory signedOrder, bytes32 orderHash) = createAndSignOrder(order); - - uint256 swapperInputBefore = tokenIn.balanceOf(swapper); - uint256 swapperOutputBefore = tokenOut.balanceOf(swapper); - uint256 executorInputBefore = tokenIn.balanceOf(address(executor)); - uint256 executorOutputBefore = tokenOut.balanceOf(address(executor)); - - // Prepare callback data - no approvals needed for this simple test - address[] memory tokensToApproveForUniversalRouter = new address[](0); - address[] memory tokensToApproveForReactor = new address[](1); - tokensToApproveForReactor[0] = address(tokenOut); - bytes memory routerData = ""; // Empty data for mock router - - bytes memory callbackData = abi.encode(tokensToApproveForUniversalRouter, tokensToApproveForReactor, routerData); - - vm.expectEmit(true, true, true, true, address(reactor)); - emit Fill(orderHash, address(executor), swapper, order.info.nonce); - - vm.prank(whitelistedCaller); - executor.execute(signedOrder, callbackData); - - assertEq(tokenIn.balanceOf(swapper), swapperInputBefore - inputAmount, "Swapper input balance incorrect"); - assertEq(tokenOut.balanceOf(swapper), swapperOutputBefore + outputAmount, "Swapper output balance incorrect"); - assertEq( - tokenIn.balanceOf(address(executor)), executorInputBefore + inputAmount, "Executor input balance incorrect" - ); - assertEq( - tokenOut.balanceOf(address(executor)), - executorOutputBefore - outputAmount, - "Executor output balance incorrect" - ); - } - - /// @notice Test batch execution - function test_executeBatch() public { - uint256 inputAmount = 1 ether; - uint256 outputAmount = 1 ether; - - tokenIn.mint(swapper, inputAmount * 2); - tokenIn.forceApprove(swapper, address(permit2), type(uint256).max); - - MockOrder[] memory orders = new MockOrder[](2); - orders[0] = MockOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 100) - .withNonce(0).withPreExecutionHook(tokenTransferHook).withAuctionResolver(mockResolver), - input: InputToken(tokenIn, inputAmount, inputAmount), - outputs: OutputsBuilder.single(address(tokenOut), outputAmount, swapper) - }); - - orders[1] = MockOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 100) - .withNonce(1).withPreExecutionHook(tokenTransferHook).withAuctionResolver(mockResolver), - input: InputToken(tokenIn, inputAmount, inputAmount), - outputs: OutputsBuilder.single(address(tokenOut), outputAmount, swapper) - }); - - SignedOrder[] memory signedOrders = new SignedOrder[](2); - for (uint256 i = 0; i < orders.length; i++) { - (SignedOrder memory signed,) = createAndSignOrder(orders[i]); - signedOrders[i] = signed; - } - - address[] memory tokensToApproveForUniversalRouter = new address[](0); - address[] memory tokensToApproveForReactor = new address[](1); - tokensToApproveForReactor[0] = address(tokenOut); - bytes memory routerData = ""; - - bytes memory callbackData = abi.encode(tokensToApproveForUniversalRouter, tokensToApproveForReactor, routerData); - - uint256 swapperInputBefore = tokenIn.balanceOf(swapper); - uint256 swapperOutputBefore = tokenOut.balanceOf(swapper); - - vm.prank(whitelistedCaller); - executor.executeBatch(signedOrders, callbackData); - - assertEq(tokenIn.balanceOf(swapper), swapperInputBefore - inputAmount * 2, "Swapper input balance incorrect"); - assertEq( - tokenOut.balanceOf(swapper), swapperOutputBefore + outputAmount * 2, "Swapper output balance incorrect" - ); - } - - /// @notice Test native output (ETH) fills - function test_executeNativeOutput() public { - uint256 inputAmount = 1 ether; - uint256 outputAmount = 1 ether; - uint256 deadline = block.timestamp + 1000; - - tokenIn.mint(swapper, inputAmount); - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - - MockOrder memory order = MockOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(deadline) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(mockResolver), - input: InputToken(tokenIn, inputAmount, inputAmount), - outputs: OutputsBuilder.single(NATIVE, outputAmount, swapper) - }); - - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - - uint256 swapperEthBefore = swapper.balance; - - address[] memory tokensToApproveForUniversalRouter = new address[](0); - address[] memory tokensToApproveForReactor = new address[](0); - bytes memory routerData = ""; - - bytes memory callbackData = abi.encode(tokensToApproveForUniversalRouter, tokensToApproveForReactor, routerData); - - vm.prank(whitelistedCaller); - executor.execute(signedOrder, callbackData); - - assertEq(swapper.balance, swapperEthBefore + outputAmount, "Swapper ETH balance incorrect"); - } - - /// @notice Regression: v4 reactor should refund any excess ETH back to the caller (executor), - /// matching BaseReactor behavior. This protects fillers from accidentally stranding ETH. - function test_v4ReactorRefundsExcessEthToExecutor() public { - uint256 inputAmount = 1 ether; - uint256 outputAmount = 1 ether; - uint256 deadline = block.timestamp + 1000; - - tokenIn.mint(swapper, inputAmount); - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - - MockOrder memory order = createBasicOrder(inputAmount, outputAmount, deadline); - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - - // The executor is pre-funded with ETH in setUp(). It will forward its entire ETH balance to the reactor - // during `reactorCallback()`. The reactor must refund it back at the end of execution. - uint256 executorEthBefore = address(executor).balance; - uint256 reactorEthBefore = address(reactor).balance; - - address[] memory tokensToApproveForUniversalRouter = new address[](0); - address[] memory tokensToApproveForReactor = new address[](1); - tokensToApproveForReactor[0] = address(tokenOut); - bytes memory callbackData = abi.encode(tokensToApproveForUniversalRouter, tokensToApproveForReactor, bytes("")); // empty router data - - vm.prank(whitelistedCaller); - executor.execute(signedOrder, callbackData); - - assertEq(address(executor).balance, executorEthBefore, "Executor should receive refunded ETH back"); - assertEq(address(reactor).balance, reactorEthBefore, "Reactor should not retain excess ETH"); - } - - /// @notice Test ERC20ETH input forwards ETH to Universal Router - function test_ERC20ETHInputForwardsETH() public { - address erc20ethAddress = 0x00000000e20E49e6dCeE6e8283A0C090578F0fb9; - uint256 ethAmount = 1 ether; - - // Simulate ERC20ETH transferring ETH to executor - vm.deal(address(executor), ethAmount); - - // Create mock resolved orders with ERC20ETH input - ResolvedOrder[] memory resolvedOrders = new ResolvedOrder[](1); - resolvedOrders[0].info.reactor = IReactor(address(reactor)); - resolvedOrders[0].info.swapper = swapper; - resolvedOrders[0].input.token = ERC20(erc20ethAddress); - resolvedOrders[0].input.amount = ethAmount; - resolvedOrders[0].input.maxAmount = ethAmount; - - address[] memory tokensToApproveForUniversalRouter = new address[](0); - address[] memory tokensToApproveForReactor = new address[](0); - bytes memory routerData = ""; - - bytes memory callbackData = abi.encode(tokensToApproveForUniversalRouter, tokensToApproveForReactor, routerData); - - uint256 routerEthBefore = address(mockUniversalRouter).balance; - - vm.prank(address(reactor)); - executor.reactorCallback(resolvedOrders, callbackData); - - assertEq(mockUniversalRouter.receivedETH(), ethAmount, "Router should receive ETH"); - assertEq( - address(mockUniversalRouter).balance, routerEthBefore + ethAmount, "Router ETH balance should increase" - ); - } - - /// @notice Test onlyWhitelistedCaller modifier - function test_onlyWhitelistedCaller() public { - uint256 inputAmount = 1 ether; - uint256 outputAmount = 1 ether; - - tokenIn.mint(swapper, inputAmount); - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - - MockOrder memory order = createBasicOrder(inputAmount, outputAmount, block.timestamp + 1000); - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - - bytes memory callbackData = abi.encode(new address[](0), new address[](0), ""); - - address nonWhitelisted = makeAddr("nonWhitelisted"); - vm.prank(nonWhitelisted); - vm.expectRevert(V4UniversalRouterExecutor.CallerNotWhitelisted.selector); - executor.execute(signedOrder, callbackData); - } - - /// @notice Test onlyReactor modifier - function test_onlyReactor() public { - ResolvedOrder[] memory resolvedOrders = new ResolvedOrder[](0); - bytes memory callbackData = abi.encode(new address[](0), new address[](0), ""); - - address notReactor = makeAddr("notReactor"); - vm.prank(notReactor); - vm.expectRevert(V4UniversalRouterExecutor.MsgSenderNotReactor.selector); - executor.reactorCallback(resolvedOrders, callbackData); - } - - /// @notice Test withdrawETH only by owner - function test_withdrawETH() public { - address recipient = makeAddr("recipient"); - uint256 amount = 1 ether; - vm.deal(address(executor), amount); - - address nonOwner = makeAddr("nonOwner"); - vm.prank(nonOwner); - vm.expectRevert("UNAUTHORIZED"); - executor.withdrawETH(recipient); - - uint256 recipientBefore = recipient.balance; - vm.prank(owner); - executor.withdrawETH(recipient); - assertEq(recipient.balance, recipientBefore + amount, "Recipient should receive ETH"); - } - - /// @notice Test withdrawERC20 only by owner - function test_withdrawERC20() public { - address recipient = makeAddr("recipient"); - uint256 amount = 10 * ONE; - tokenIn.mint(address(executor), amount); - - address nonOwner = makeAddr("nonOwner"); - vm.prank(nonOwner); - vm.expectRevert("UNAUTHORIZED"); - executor.withdrawERC20(tokenIn, recipient); - - uint256 recipientBefore = tokenIn.balanceOf(recipient); - vm.prank(owner); - executor.withdrawERC20(tokenIn, recipient); - assertEq(tokenIn.balanceOf(recipient), recipientBefore + amount, "Recipient should receive tokens"); - } - - /// @notice Test Universal Router reverts propagate correctly - function test_universalRouterRevertPropagates() public { - mockUniversalRouter.setShouldRevert(true); - - ResolvedOrder[] memory resolvedOrders = new ResolvedOrder[](1); - resolvedOrders[0].info.reactor = IReactor(address(reactor)); - resolvedOrders[0].input.token = tokenIn; - resolvedOrders[0].input.amount = 0; - - address[] memory tokensToApproveForUniversalRouter = new address[](0); - address[] memory tokensToApproveForReactor = new address[](0); - bytes memory routerData = ""; - - bytes memory callbackData = abi.encode(tokensToApproveForUniversalRouter, tokensToApproveForReactor, routerData); - - vm.prank(address(reactor)); - vm.expectRevert("Mock revert"); - executor.reactorCallback(resolvedOrders, callbackData); - } - - /// @notice Test that executor can receive ETH - function test_receiveETH() public { - uint256 amount = 1 ether; - vm.deal(address(this), amount); - - (bool success,) = address(executor).call{value: amount}(""); - assertTrue(success, "Should be able to send ETH to executor"); - assertEq(address(executor).balance, 100 ether + amount, "Executor should have received ETH"); - } - - /// @notice Fuzz test for execute - function testFuzz_execute(uint128 inputAmount, uint128 outputAmount, uint256 deadline) public { - vm.assume(deadline > block.timestamp); - vm.assume(inputAmount > 0); - vm.assume(outputAmount > 0); - vm.assume(outputAmount <= 100 * ONE); // Don't exceed executor balance - - tokenIn.mint(swapper, inputAmount); - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - - MockOrder memory order = MockOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(deadline) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(mockResolver), - input: InputToken(tokenIn, inputAmount, inputAmount), - outputs: OutputsBuilder.single(address(tokenOut), outputAmount, swapper) - }); - - (SignedOrder memory signedOrder, bytes32 orderHash) = createAndSignOrder(order); - - address[] memory tokensToApproveForUniversalRouter = new address[](0); - address[] memory tokensToApproveForReactor = new address[](1); - tokensToApproveForReactor[0] = address(tokenOut); - bytes memory routerData = ""; - - bytes memory callbackData = abi.encode(tokensToApproveForUniversalRouter, tokensToApproveForReactor, routerData); - - uint256 swapperInputBefore = tokenIn.balanceOf(swapper); - uint256 swapperOutputBefore = tokenOut.balanceOf(swapper); - - vm.expectEmit(true, true, true, true, address(reactor)); - emit Fill(orderHash, address(executor), swapper, order.info.nonce); - - vm.prank(whitelistedCaller); - executor.execute(signedOrder, callbackData); - - assertEq(tokenIn.balanceOf(swapper), swapperInputBefore - inputAmount, "Swapper input balance incorrect"); - assertEq(tokenOut.balanceOf(swapper), swapperOutputBefore + outputAmount, "Swapper output balance incorrect"); - } -} diff --git a/test/util/PermitSignature.sol b/test/util/PermitSignature.sol index 020a6ce5..adedb212 100644 --- a/test/util/PermitSignature.sol +++ b/test/util/PermitSignature.sol @@ -11,14 +11,7 @@ import {ExclusiveDutchOrder, ExclusiveDutchOrderLib} from "../../src/lib/Exclusi import {V2DutchOrder, V2DutchOrderLib} from "../../src/lib/V2DutchOrderLib.sol"; import {V3DutchOrder, V3DutchOrderLib} from "../../src/lib/V3DutchOrderLib.sol"; import {PriorityOrder, PriorityOrderLib} from "../../src/lib/PriorityOrderLib.sol"; -import { - PriorityOrder as PriorityOrderV2, - PriorityOrderLib as PriorityOrderLibV2 -} from "../../src/v4/lib/PriorityOrderLib.sol"; import {OrderInfo, InputToken} from "../../src/base/ReactorStructs.sol"; -import {OrderInfo as OrderInfoV2} from "../../src/v4/base/ReactorStructs.sol"; -import {MockOrder, MockOrderLib} from "../v4/util/mock/MockOrderLib.sol"; -import {HybridOrder, HybridOrderLib} from "../../src/v4/lib/HybridOrderLib.sol"; contract PermitSignature is Test { using LimitOrderLib for LimitOrder; @@ -26,10 +19,7 @@ contract PermitSignature is Test { using ExclusiveDutchOrderLib for ExclusiveDutchOrder; using V2DutchOrderLib for V2DutchOrder; using PriorityOrderLib for PriorityOrder; - using PriorityOrderLibV2 for PriorityOrderV2; using V3DutchOrderLib for V3DutchOrder; - using MockOrderLib for MockOrder; - using HybridOrderLib for HybridOrder; bytes32 public constant NAME_HASH = keccak256("Permit2"); bytes32 public constant TYPE_HASH = @@ -54,20 +44,9 @@ contract PermitSignature is Test { bytes32 constant PRIORITY_ORDER_TYPE_HASH = keccak256(abi.encodePacked(TYPEHASH_STUB, PriorityOrderLib.PERMIT2_ORDER_TYPE)); - bytes32 constant PRIORITY_ORDER_V2_TYPE_HASH = - keccak256(abi.encodePacked(TYPEHASH_STUB, PriorityOrderLibV2.PERMIT2_ORDER_TYPE)); - - // Alias for the new witness-based type hash - bytes32 constant PRIORITY_ORDER_V2_WITNESS_TYPE_HASH = PRIORITY_ORDER_V2_TYPE_HASH; - bytes32 constant V3_DUTCH_ORDER_TYPE_HASH = keccak256(abi.encodePacked(TYPEHASH_STUB, V3DutchOrderLib.PERMIT2_ORDER_TYPE)); - bytes32 constant HYBRID_ORDER_TYPE_HASH = - keccak256(abi.encodePacked(TYPEHASH_STUB, HybridOrderLib.PERMIT2_ORDER_TYPE)); - - bytes32 constant MOCK_ORDER_TYPE_HASH = keccak256(abi.encodePacked(TYPEHASH_STUB, MockOrderLib.PERMIT2_ORDER_TYPE)); - function getPermitSignature( uint256 privateKey, address permit2, @@ -187,32 +166,6 @@ contract PermitSignature is Test { ); } - function signOrder(uint256 privateKey, address permit2, PriorityOrderV2 memory order) - internal - view - returns (bytes memory sig) - { - ISignatureTransfer.PermitTransferFrom memory permit = ISignatureTransfer.PermitTransferFrom({ - permitted: ISignatureTransfer.TokenPermissions({ - token: address(order.input.token), amount: order.input.amount - }), - nonce: order.info.nonce, - deadline: order.info.deadline - }); - - // Use the new witness hash that includes resolver address and full order - bytes32 witness = order.witnessHash(address(order.info.auctionResolver)); - - return getPermitSignature( - privateKey, - permit2, - permit, - address(order.info.preExecutionHook), - PRIORITY_ORDER_V2_WITNESS_TYPE_HASH, - witness - ); - } - function signOrder(uint256 privateKey, address permit2, V3DutchOrder memory order) internal view @@ -229,64 +182,6 @@ contract PermitSignature is Test { ); } - function signOrder( - uint256 privateKey, - address permit2, - OrderInfoV2 memory info, - address inputToken, - uint256 inputAmount, - bytes32 typeHash, - bytes32 orderHash - ) internal view returns (bytes memory sig) { - ISignatureTransfer.PermitTransferFrom memory permit = ISignatureTransfer.PermitTransferFrom({ - permitted: ISignatureTransfer.TokenPermissions({token: inputToken, amount: inputAmount}), - nonce: info.nonce, - deadline: info.deadline - }); - return getPermitSignature(privateKey, permit2, permit, address(info.preExecutionHook), typeHash, orderHash); - } - - function signOrder(uint256 privateKey, address permit2, MockOrder memory order) - internal - view - returns (bytes memory sig) - { - ISignatureTransfer.PermitTransferFrom memory permit = ISignatureTransfer.PermitTransferFrom({ - permitted: ISignatureTransfer.TokenPermissions({ - token: address(order.input.token), amount: order.input.maxAmount - }), - nonce: order.info.nonce, - deadline: order.info.deadline - }); - - // Use the new witness hash that includes resolver address and full order - bytes32 witness = order.witnessHash(address(order.info.auctionResolver)); - - return getPermitSignature( - privateKey, permit2, permit, address(order.info.preExecutionHook), MOCK_ORDER_TYPE_HASH, witness - ); - } - - function signOrder(uint256 privateKey, address permit2, HybridOrder memory order) - internal - view - returns (bytes memory sig) - { - ISignatureTransfer.PermitTransferFrom memory permit = ISignatureTransfer.PermitTransferFrom({ - permitted: ISignatureTransfer.TokenPermissions({ - token: address(order.input.token), amount: order.input.maxAmount - }), - nonce: order.info.nonce, - deadline: order.info.deadline - }); - - bytes32 witness = order.hash(); - - return getPermitSignature( - privateKey, permit2, permit, address(order.info.preExecutionHook), HYBRID_ORDER_TYPE_HASH, witness - ); - } - function _domainSeparatorV4(address permit2) internal view returns (bytes32) { return keccak256(abi.encode(TYPE_HASH, NAME_HASH, block.chainid, permit2)); } diff --git a/test/v4/EthOutput.t.sol b/test/v4/EthOutput.t.sol deleted file mode 100644 index ec066651..00000000 --- a/test/v4/EthOutput.t.sol +++ /dev/null @@ -1,146 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {Test} from "forge-std/Test.sol"; -import {IPermit2} from "permit2/src/interfaces/IPermit2.sol"; - -import {DeployPermit2} from "../util/DeployPermit2.sol"; -import {PermitSignature} from "../util/PermitSignature.sol"; -import {OutputsBuilder} from "../util/OutputsBuilder.sol"; -import {MockERC20} from "../util/mock/MockERC20.sol"; - -import {Reactor} from "../../src/v4/Reactor.sol"; -import {TokenTransferHook} from "../../src/v4/hooks/TokenTransferHook.sol"; -import {SignedOrder, InputToken} from "../../src/base/ReactorStructs.sol"; -import {OrderInfo} from "../../src/v4/base/ReactorStructs.sol"; -import {OrderInfoBuilder} from "../v4/util/OrderInfoBuilder.sol"; -import {MockAuctionResolver} from "../v4/util/mock/MockAuctionResolver.sol"; -import {MockOrder, MockOrderLib} from "../v4/util/mock/MockOrderLib.sol"; -import {NATIVE} from "../../src/lib/CurrencyLibrary.sol"; - -/// @notice V4 native-output tests -contract EthOutputV4Test is Test, PermitSignature, DeployPermit2 { - using OrderInfoBuilder for OrderInfo; - using MockOrderLib for MockOrder; - - address internal constant PROTOCOL_FEE_OWNER = address(1); - - MockERC20 internal tokenIn; - IPermit2 internal permit2; - Reactor internal reactor; - MockAuctionResolver internal mockResolver; - TokenTransferHook internal tokenTransferHook; - - uint256 internal swapperPrivateKey; - address internal swapper; - address internal directFiller; - - function setUp() public { - // Make ETH balance assertions stable (ignore gas costs). - vm.txGasPrice(0); - - tokenIn = new MockERC20("Input", "IN", 18); - swapperPrivateKey = 0x12341234; - swapper = vm.addr(swapperPrivateKey); - directFiller = address(888); - - permit2 = IPermit2(deployPermit2()); - reactor = new Reactor(PROTOCOL_FEE_OWNER, permit2); - mockResolver = new MockAuctionResolver(); - tokenTransferHook = new TokenTransferHook(permit2, reactor); - } - - // Fill 1 order with requested output = 2 ETH. - function testEth1Output() public { - uint256 inputAmount = 1 ether; - uint256 outputAmount = 2 * inputAmount; - uint256 deadline = block.timestamp + 1000; - - tokenIn.mint(swapper, inputAmount); - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - vm.deal(directFiller, outputAmount); - - MockOrder memory order = MockOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(deadline) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(mockResolver), - input: InputToken(tokenIn, inputAmount, inputAmount), - outputs: OutputsBuilder.single(NATIVE, outputAmount, swapper) - }); - - (SignedOrder memory signedOrder,) = _createAndSignOrder(order); - - uint256 swapperEthBefore = swapper.balance; - uint256 fillerEthBefore = directFiller.balance; - - vm.prank(directFiller); - reactor.execute{value: outputAmount}(signedOrder); - - assertEq(swapper.balance, swapperEthBefore + outputAmount); - assertEq(directFiller.balance, fillerEthBefore - outputAmount); - } - - function testExcessETHIsReturned() public { - uint256 inputAmount = 1 ether; - uint256 outputAmount = 2 * inputAmount; - uint256 deadline = block.timestamp + 1000; - - tokenIn.mint(swapper, inputAmount); - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - vm.deal(directFiller, outputAmount * 2); - - MockOrder memory order = MockOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(deadline) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(mockResolver), - input: InputToken(tokenIn, inputAmount, inputAmount), - outputs: OutputsBuilder.single(NATIVE, outputAmount, swapper) - }); - - (SignedOrder memory signedOrder,) = _createAndSignOrder(order); - - uint256 fillerEthBefore = directFiller.balance; - - vm.prank(directFiller); - reactor.execute{value: outputAmount * 2}(signedOrder); - - // check directFiller received refund (only outputAmount should be spent) - assertEq(directFiller.balance, fillerEthBefore - outputAmount); - assertEq(address(reactor).balance, 0); - } - - // Same as testEth1Output, but reverts because directFiller doesn't send enough ether - function testEth1OutputInsufficientEthSent() public { - uint256 inputAmount = 1 ether; - uint256 outputAmount = 2 * inputAmount; - uint256 deadline = block.timestamp + 1000; - - tokenIn.mint(swapper, inputAmount); - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - vm.deal(directFiller, outputAmount); - - MockOrder memory order = MockOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(deadline) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(mockResolver), - input: InputToken(tokenIn, inputAmount, inputAmount), - outputs: OutputsBuilder.single(NATIVE, outputAmount, swapper) - }); - - (SignedOrder memory signedOrder,) = _createAndSignOrder(order); - - vm.prank(directFiller); - vm.expectRevert(); // CurrencyLibrary.NativeTransferFailed (selector differs by version; keep broad) - reactor.execute{value: outputAmount - 1}(signedOrder); - } - - function _createAndSignOrder(MockOrder memory mockOrder) - internal - view - returns (SignedOrder memory signedOrder, bytes32 orderHash) - { - orderHash = mockOrder.witnessHash(address(mockOrder.info.auctionResolver)); - bytes memory sig = signOrder(swapperPrivateKey, address(permit2), mockOrder); - bytes memory orderData = abi.encode(mockOrder); - bytes memory encodedOrder = abi.encode(address(mockResolver), orderData); - signedOrder = SignedOrder(encodedOrder, sig); - } -} - diff --git a/test/v4/ProtocolFees.t.sol b/test/v4/ProtocolFees.t.sol deleted file mode 100644 index f56a0d2a..00000000 --- a/test/v4/ProtocolFees.t.sol +++ /dev/null @@ -1,786 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {Test} from "forge-std/Test.sol"; -import {ERC20} from "solmate/src/tokens/ERC20.sol"; -import {ProtocolFees} from "../../src/v4/base/ProtocolFees.sol"; -import {ResolvedOrder, OrderInfo} from "../../src/v4/base/ReactorStructs.sol"; -import {InputToken, OutputToken, SignedOrder} from "../../src/base/ReactorStructs.sol"; -import {Reactor} from "../../src/v4/Reactor.sol"; -import {MockERC20} from "../util/mock/MockERC20.sol"; -import {MockFeeController} from "./util/mock/MockFeeController.sol"; -import {MockFeeControllerInputFees} from "./util/mock/MockFeeControllerInputFees.sol"; -import {MockFeeControllerInputAndOutputFees} from "./util/mock/MockFeeControllerInputAndOutputFees.sol"; -import {MockFeeControllerDuplicates} from "./util/mock/MockFeeControllerDuplicates.sol"; -import {MockFeeControllerZeroFee} from "./util/mock/MockFeeControllerZeroFee.sol"; -import {MockFillContract} from "./util/mock/MockFillContract.sol"; -import {MockAuctionResolver} from "./util/mock/MockAuctionResolver.sol"; -import {MockOrder, MockOrderLib} from "./util/mock/MockOrderLib.sol"; -import {OrderInfoBuilder} from "./util/OrderInfoBuilder.sol"; -import {PermitSignature} from "../util/PermitSignature.sol"; -import {DeployPermit2} from "../util/DeployPermit2.sol"; -import {IPermit2} from "permit2/src/interfaces/IPermit2.sol"; -import {TokenTransferHook} from "../../src/v4/hooks/TokenTransferHook.sol"; - -/// @notice Mock contract to expose internal _injectFees function for testing -/// @dev This wrapper allows us to test the internal _injectFees function -/// by exposing it as a public function called takeFees (to match the test expectations) -contract MockProtocolFeesV4 is ProtocolFees { - constructor(address owner) ProtocolFees(owner) {} - - function takeFees(ResolvedOrder memory order) external view returns (ResolvedOrder memory) { - _injectFees(order); - return order; - } -} - -contract ProtocolFeesTest is Test { - using OrderInfoBuilder for OrderInfo; - - event ProtocolFeeControllerSet(address oldFeeController, address newFeeController); - - address constant INTERFACE_FEE_RECIPIENT = address(10); - address constant PROTOCOL_FEE_OWNER = address(11); - address constant RECIPIENT = address(12); - address constant SWAPPER = address(13); - - MockERC20 tokenIn; - MockERC20 tokenOut; - MockERC20 tokenOut2; - MockProtocolFeesV4 fees; - MockFeeController feeController; - MockFeeControllerInputFees inputFeeController; - MockFeeControllerInputAndOutputFees inputOutputFeeController; - - function setUp() public { - fees = new MockProtocolFeesV4(PROTOCOL_FEE_OWNER); - tokenIn = new MockERC20("Input", "IN", 18); - tokenOut = new MockERC20("Output", "OUT", 18); - tokenOut2 = new MockERC20("Output2", "OUT", 18); - feeController = new MockFeeController(RECIPIENT); - inputFeeController = new MockFeeControllerInputFees(RECIPIENT); - inputOutputFeeController = new MockFeeControllerInputAndOutputFees(RECIPIENT); - vm.prank(PROTOCOL_FEE_OWNER); - fees.setProtocolFeeController(address(feeController)); - } - - function testSetFeeController() public { - assertEq(address(fees.feeController()), address(feeController)); - vm.expectEmit(true, true, false, false); - emit ProtocolFeeControllerSet(address(feeController), address(2)); - - vm.prank(PROTOCOL_FEE_OWNER); - fees.setProtocolFeeController(address(2)); - assertEq(address(fees.feeController()), address(2)); - } - - function testSetFeeControllerOnlyOwner() public { - assertEq(address(fees.feeController()), address(feeController)); - vm.prank(address(1)); - vm.expectRevert("UNAUTHORIZED"); - fees.setProtocolFeeController(address(2)); - assertEq(address(fees.feeController()), address(feeController)); - } - - function testTakeFeesNoFees() public view { - ResolvedOrder memory order = createOrder(1 ether, false); - - assertEq(order.outputs.length, 1); - ResolvedOrder memory afterFees = fees.takeFees(order); - assertEq(afterFees.outputs.length, 1); - assertEq(afterFees.outputs[0].token, order.outputs[0].token); - assertEq(afterFees.outputs[0].amount, order.outputs[0].amount); - assertEq(afterFees.outputs[0].recipient, order.outputs[0].recipient); - } - - function testTakeFees() public { - ResolvedOrder memory order = createOrder(1 ether, false); - uint256 feeBps = 3; - feeController.setFee(tokenIn, address(tokenOut), feeBps); - - assertEq(order.outputs.length, 1); - ResolvedOrder memory afterFees = fees.takeFees(order); - assertEq(afterFees.outputs.length, 2); - assertEq(afterFees.outputs[0].token, order.outputs[0].token); - assertEq(afterFees.outputs[0].amount, order.outputs[0].amount); - assertEq(afterFees.outputs[0].recipient, order.outputs[0].recipient); - assertEq(afterFees.outputs[1].token, order.outputs[0].token); - assertEq(afterFees.outputs[1].amount, order.outputs[0].amount * feeBps / 10000); - assertEq(afterFees.outputs[1].recipient, RECIPIENT); - } - - function testTakeInputFees() public { - vm.prank(PROTOCOL_FEE_OWNER); - fees.setProtocolFeeController(address(inputFeeController)); - - ResolvedOrder memory order = createOrder(1 ether, false); - uint256 feeBps = 3; - inputFeeController.setFee(tokenIn, feeBps); - - assertEq(order.outputs.length, 1); - ResolvedOrder memory afterFees = fees.takeFees(order); - assertEq(afterFees.outputs.length, 2); - assertEq(afterFees.outputs[0].token, order.outputs[0].token); - assertEq(afterFees.outputs[0].amount, order.outputs[0].amount); - assertEq(afterFees.outputs[0].recipient, order.outputs[0].recipient); - assertEq(afterFees.outputs[1].token, address(order.input.token)); - assertEq(afterFees.outputs[1].amount, order.input.amount * feeBps / 10000); - assertEq(afterFees.outputs[1].recipient, RECIPIENT); - } - - function testTakeInputTokenFees() public { - ResolvedOrder memory order = createOrder(1 ether, false); - uint256 feeBps = 3; - feeController.setFee(tokenIn, address(tokenOut), feeBps); - - assertEq(order.outputs.length, 1); - ResolvedOrder memory afterFees = fees.takeFees(order); - assertEq(afterFees.outputs.length, 2); - assertEq(afterFees.outputs[0].token, order.outputs[0].token); - assertEq(afterFees.outputs[0].amount, order.outputs[0].amount); - assertEq(afterFees.outputs[0].recipient, order.outputs[0].recipient); - assertEq(afterFees.outputs[1].token, order.outputs[0].token); - assertEq(afterFees.outputs[1].amount, order.outputs[0].amount * feeBps / 10000); - assertEq(afterFees.outputs[1].recipient, RECIPIENT); - } - - function testTakeFeesFuzzOutputs(uint128 inputAmount, uint128[] memory outputAmounts, uint256 feeBps) public { - vm.assume(feeBps <= 5); - vm.assume(outputAmounts.length > 0); - OutputToken[] memory outputs = new OutputToken[](outputAmounts.length); - for (uint256 i = 0; i < outputAmounts.length; i++) { - outputs[i] = OutputToken(address(tokenOut), outputAmounts[i], RECIPIENT); - } - ResolvedOrder memory order = ResolvedOrder({ - info: OrderInfoBuilder.init(address(0)), - input: InputToken(tokenIn, inputAmount, inputAmount), - outputs: outputs, - sig: hex"00", - hash: bytes32(0), - auctionResolver: address(0), - witnessTypeString: "" - }); - feeController.setFee(tokenIn, address(outputs[0].token), feeBps); - - ResolvedOrder memory afterFees = fees.takeFees(order); - assertGe(afterFees.outputs.length, outputs.length); - - for (uint256 i = 0; i < outputAmounts.length; i++) { - address tokenAddress = order.outputs[i].token; - uint256 baseAmount = order.outputs[i].amount; - - uint256 extraOutputs = afterFees.outputs.length - outputAmounts.length; - for (uint256 j = 0; j < extraOutputs; j++) { - OutputToken memory output = afterFees.outputs[outputAmounts.length + j]; - if (output.token == tokenAddress) { - assertGe(output.amount, baseAmount * feeBps / 10000); - } - } - } - } - - function testTakeFeesWithInterfaceFee() public { - ResolvedOrder memory order = createOrderWithInterfaceFee(1 ether, false); - uint256 feeBps = 3; - feeController.setFee(tokenIn, address(tokenOut), feeBps); - - assertEq(order.outputs.length, 2); - ResolvedOrder memory afterFees = fees.takeFees(order); - assertEq(afterFees.outputs.length, 3); - assertEq(afterFees.outputs[0].token, order.outputs[0].token); - assertEq(afterFees.outputs[0].amount, order.outputs[0].amount); - assertEq(afterFees.outputs[0].recipient, order.outputs[0].recipient); - assertEq(afterFees.outputs[1].token, order.outputs[1].token); - assertEq(afterFees.outputs[1].amount, order.outputs[1].amount); - assertEq(afterFees.outputs[1].recipient, order.outputs[1].recipient); - assertEq(afterFees.outputs[2].token, order.outputs[1].token); - assertEq(afterFees.outputs[2].amount, (order.outputs[1].amount + order.outputs[1].amount) * feeBps / 10000); - assertEq(afterFees.outputs[2].recipient, RECIPIENT); - } - - function testTakeFeesTooMuch() public { - ResolvedOrder memory order = createOrderWithInterfaceFee(1 ether, false); - uint256 feeBps = 10; - feeController.setFee(tokenIn, address(tokenOut), feeBps); - - vm.expectRevert( - abi.encodeWithSelector( - ProtocolFees.FeeTooLarge.selector, - address(tokenOut), - order.outputs[0].amount * 2 * 10 / 10000, - RECIPIENT - ) - ); - fees.takeFees(order); - } - - function testTakeInputFeesTooMuch() public { - vm.prank(PROTOCOL_FEE_OWNER); - fees.setProtocolFeeController(address(inputFeeController)); - - ResolvedOrder memory order = createOrder(1 ether, false); - uint256 feeBps = 10; - inputFeeController.setFee(tokenIn, feeBps); - - vm.expectRevert( - abi.encodeWithSelector( - ProtocolFees.FeeTooLarge.selector, address(tokenIn), order.input.amount * 10 / 10000, RECIPIENT - ) - ); - fees.takeFees(order); - } - - function testTakeInputAndOutputFees() public { - vm.prank(PROTOCOL_FEE_OWNER); - fees.setProtocolFeeController(address(inputOutputFeeController)); - - ResolvedOrder memory order = createOrder(1 ether, false); - uint256 feeBps = 5; - inputOutputFeeController.setFee(tokenIn, feeBps); - inputOutputFeeController.setFee(tokenOut, feeBps); - - vm.expectRevert(ProtocolFees.InputAndOutputFees.selector); - fees.takeFees(order); - } - - function testTakeFeesDuplicate() public { - MockFeeControllerDuplicates controller = new MockFeeControllerDuplicates(RECIPIENT); - vm.prank(PROTOCOL_FEE_OWNER); - fees.setProtocolFeeController(address(controller)); - - ResolvedOrder memory order = createOrderWithInterfaceFee(1 ether, false); - uint256 feeBps = 10; - controller.setFee(tokenIn, address(tokenOut), feeBps); - - vm.expectRevert(abi.encodeWithSelector(ProtocolFees.DuplicateFeeOutput.selector, tokenOut)); - fees.takeFees(order); - } - - // The order contains 2 outputs: 1 tokenOut to SWAPPER and 2 tokenOut2 to SWAPPER - function testTakeFeesMultipleOutputTokens() public { - OutputToken[] memory outputs = new OutputToken[](2); - outputs[0] = OutputToken(address(tokenOut), 1 ether, SWAPPER); - outputs[1] = OutputToken(address(tokenOut2), 2 ether, SWAPPER); - ResolvedOrder memory order = ResolvedOrder({ - info: OrderInfoBuilder.init(address(0)), - input: InputToken(tokenIn, 1 ether, 1 ether), - outputs: outputs, - sig: hex"00", - hash: bytes32(0), - auctionResolver: address(0), - witnessTypeString: "" - }); - feeController.setFee(tokenIn, address(tokenOut), 4); - feeController.setFee(tokenIn, address(tokenOut2), 3); - - ResolvedOrder memory afterFees = fees.takeFees(order); - assertEq(afterFees.outputs.length, 4); - assertEq(afterFees.outputs[0].token, address(tokenOut)); - assertEq(afterFees.outputs[0].amount, 1 ether); - assertEq(afterFees.outputs[0].recipient, SWAPPER); - assertEq(afterFees.outputs[1].token, address(tokenOut2)); - assertEq(afterFees.outputs[1].amount, 2 ether); - assertEq(afterFees.outputs[1].recipient, SWAPPER); - assertEq(afterFees.outputs[2].token, address(tokenOut)); - assertEq(afterFees.outputs[2].amount, 1 ether * 4 / 10000); - assertEq(afterFees.outputs[2].recipient, RECIPIENT); - assertEq(afterFees.outputs[3].token, address(tokenOut2)); - assertEq(afterFees.outputs[3].amount, 2 ether * 3 / 10000); - assertEq(afterFees.outputs[3].recipient, RECIPIENT); - } - - // The order contains 4 outputs: - // 1 tokenOut to SWAPPER - // 0.05 tokenOut to INTERFACE_FEE_RECIPIENT - // 2 tokenOut2 to SWAPPER - // 0.1 tokenOut2 to INTERFACE_FEE_RECIPIENT - // There will only be protocol fee enabled for tokenOut2 - function testTakeFeesMultipleOutputTokensWithInterfaceFee() public { - OutputToken[] memory outputs = new OutputToken[](4); - outputs[0] = OutputToken(address(tokenOut), 1 ether, SWAPPER); - outputs[1] = OutputToken(address(tokenOut), 1 ether / 20, INTERFACE_FEE_RECIPIENT); - outputs[2] = OutputToken(address(tokenOut2), 2 ether, SWAPPER); - outputs[3] = OutputToken(address(tokenOut2), 2 ether / 20, INTERFACE_FEE_RECIPIENT); - ResolvedOrder memory order = ResolvedOrder({ - info: OrderInfoBuilder.init(address(0)), - input: InputToken(tokenIn, 1 ether, 1 ether), - outputs: outputs, - sig: hex"00", - hash: bytes32(0), - auctionResolver: address(0), - witnessTypeString: "" - }); - feeController.setFee(tokenIn, address(tokenOut2), 3); - - ResolvedOrder memory afterFees = fees.takeFees(order); - assertEq(afterFees.outputs.length, 5); - assertEq(afterFees.outputs[0].token, address(tokenOut)); - assertEq(afterFees.outputs[0].amount, 1 ether); - assertEq(afterFees.outputs[0].recipient, SWAPPER); - assertEq(afterFees.outputs[1].token, address(tokenOut)); - assertEq(afterFees.outputs[1].amount, 1 ether / 20); - assertEq(afterFees.outputs[1].recipient, INTERFACE_FEE_RECIPIENT); - assertEq(afterFees.outputs[2].token, address(tokenOut2)); - assertEq(afterFees.outputs[2].amount, 2 ether); - assertEq(afterFees.outputs[2].recipient, SWAPPER); - assertEq(afterFees.outputs[3].token, address(tokenOut2)); - assertEq(afterFees.outputs[3].amount, 2 ether / 20); - assertEq(afterFees.outputs[3].recipient, INTERFACE_FEE_RECIPIENT); - assertEq(afterFees.outputs[4].token, address(tokenOut2)); - assertEq(afterFees.outputs[4].amount, 2 ether * 21 / 20 * 3 / 10000); - assertEq(afterFees.outputs[4].recipient, RECIPIENT); - } - - // The same as testTakeFeesMultipleOutputTokensWithInterfaceFee but change the order of some outputs - // The order contains 4 outputs: - // 0.1 tokenOut2 to INTERFACE_FEE_RECIPIENT - // 1 tokenOut to SWAPPER - // 2 tokenOut2 to SWAPPER - // 0.05 tokenOut to INTERFACE_FEE_RECIPIENT - // There will only be protocol fee enabled for tokenOut2 - function testTakeFeesMultipleOutputTokensWithInterfaceFeeChangeOrder() public { - OutputToken[] memory outputs = new OutputToken[](4); - outputs[0] = OutputToken(address(tokenOut2), 2 ether / 20, INTERFACE_FEE_RECIPIENT); - outputs[1] = OutputToken(address(tokenOut), 1 ether, SWAPPER); - outputs[2] = OutputToken(address(tokenOut2), 2 ether, SWAPPER); - outputs[3] = OutputToken(address(tokenOut), 1 ether / 20, INTERFACE_FEE_RECIPIENT); - ResolvedOrder memory order = ResolvedOrder({ - info: OrderInfoBuilder.init(address(0)), - input: InputToken(tokenIn, 1 ether, 1 ether), - outputs: outputs, - sig: hex"00", - hash: bytes32(0), - auctionResolver: address(0), - witnessTypeString: "" - }); - feeController.setFee(tokenIn, address(tokenOut2), 3); - - ResolvedOrder memory afterFees = fees.takeFees(order); - assertEq(afterFees.outputs.length, 5); - assertEq(afterFees.outputs[0].token, address(tokenOut2)); - assertEq(afterFees.outputs[0].amount, 2 ether / 20); - assertEq(afterFees.outputs[0].recipient, INTERFACE_FEE_RECIPIENT); - assertEq(afterFees.outputs[1].token, address(tokenOut)); - assertEq(afterFees.outputs[2].token, address(tokenOut2)); - assertEq(afterFees.outputs[2].amount, 2 ether); - assertEq(afterFees.outputs[2].recipient, SWAPPER); - assertEq(afterFees.outputs[1].amount, 1 ether); - assertEq(afterFees.outputs[1].recipient, SWAPPER); - assertEq(afterFees.outputs[3].token, address(tokenOut)); - assertEq(afterFees.outputs[3].amount, 1 ether / 20); - assertEq(afterFees.outputs[3].recipient, INTERFACE_FEE_RECIPIENT); - assertEq(afterFees.outputs[4].token, address(tokenOut2)); - assertEq(afterFees.outputs[4].amount, 2 ether * 21 / 20 * 3 / 10000); - assertEq(afterFees.outputs[4].recipient, RECIPIENT); - } - - // The same as testTakeFeesMultipleOutputTokensWithInterfaceFee but enable fees for tokenOut as well - function testTakeFeesMultipleOutputTokensWithInterfaceFeeBothFees() public { - OutputToken[] memory outputs = new OutputToken[](4); - outputs[0] = OutputToken(address(tokenOut), 1 ether, SWAPPER); - outputs[1] = OutputToken(address(tokenOut), 1 ether / 20, INTERFACE_FEE_RECIPIENT); - outputs[2] = OutputToken(address(tokenOut2), 2 ether, SWAPPER); - outputs[3] = OutputToken(address(tokenOut2), 2 ether / 20, INTERFACE_FEE_RECIPIENT); - ResolvedOrder memory order = ResolvedOrder({ - info: OrderInfoBuilder.init(address(0)), - input: InputToken(tokenIn, 1 ether, 1 ether), - outputs: outputs, - sig: hex"00", - hash: bytes32(0), - auctionResolver: address(0), - witnessTypeString: "" - }); - feeController.setFee(tokenIn, address(tokenOut), 5); - feeController.setFee(tokenIn, address(tokenOut2), 3); - - ResolvedOrder memory afterFees = fees.takeFees(order); - assertEq(afterFees.outputs.length, 6); - assertEq(afterFees.outputs[0].token, address(tokenOut)); - assertEq(afterFees.outputs[0].amount, 1 ether); - assertEq(afterFees.outputs[0].recipient, SWAPPER); - assertEq(afterFees.outputs[1].token, address(tokenOut)); - assertEq(afterFees.outputs[1].amount, 1 ether / 20); - assertEq(afterFees.outputs[1].recipient, INTERFACE_FEE_RECIPIENT); - assertEq(afterFees.outputs[2].token, address(tokenOut2)); - assertEq(afterFees.outputs[2].amount, 2 ether); - assertEq(afterFees.outputs[2].recipient, SWAPPER); - assertEq(afterFees.outputs[3].token, address(tokenOut2)); - assertEq(afterFees.outputs[3].amount, 2 ether / 20); - assertEq(afterFees.outputs[3].recipient, INTERFACE_FEE_RECIPIENT); - assertEq(afterFees.outputs[4].token, address(tokenOut)); - assertEq(afterFees.outputs[4].amount, 1 ether * 21 / 20 * 5 / 10000); - assertEq(afterFees.outputs[4].recipient, RECIPIENT); - assertEq(afterFees.outputs[5].token, address(tokenOut2)); - assertEq(afterFees.outputs[5].amount, 2 ether * 21 / 20 * 3 / 10000); - assertEq(afterFees.outputs[5].recipient, RECIPIENT); - } - - // The same as testTakeFeesMultipleOutputTokensWithInterfaceFeeBothFees but change the order of outputs - function testTakeFeesMultipleOutputTokensWithInterfaceFeeBothFeesChangeOrder() public { - OutputToken[] memory outputs = new OutputToken[](4); - outputs[3] = OutputToken(address(tokenOut), 1 ether, SWAPPER); - outputs[1] = OutputToken(address(tokenOut), 1 ether / 20, INTERFACE_FEE_RECIPIENT); - outputs[2] = OutputToken(address(tokenOut2), 2 ether, SWAPPER); - outputs[0] = OutputToken(address(tokenOut2), 2 ether / 20, INTERFACE_FEE_RECIPIENT); - ResolvedOrder memory order = ResolvedOrder({ - info: OrderInfoBuilder.init(address(0)), - input: InputToken(tokenIn, 1 ether, 1 ether), - outputs: outputs, - sig: hex"00", - hash: bytes32(0), - auctionResolver: address(0), - witnessTypeString: "" - }); - feeController.setFee(tokenIn, address(tokenOut), 5); - feeController.setFee(tokenIn, address(tokenOut2), 3); - - ResolvedOrder memory afterFees = fees.takeFees(order); - assertEq(afterFees.outputs.length, 6); - assertEq(afterFees.outputs[3].token, address(tokenOut)); - assertEq(afterFees.outputs[3].amount, 1 ether); - assertEq(afterFees.outputs[3].recipient, SWAPPER); - assertEq(afterFees.outputs[1].token, address(tokenOut)); - assertEq(afterFees.outputs[1].amount, 1 ether / 20); - assertEq(afterFees.outputs[1].recipient, INTERFACE_FEE_RECIPIENT); - assertEq(afterFees.outputs[2].token, address(tokenOut2)); - assertEq(afterFees.outputs[2].amount, 2 ether); - assertEq(afterFees.outputs[2].recipient, SWAPPER); - assertEq(afterFees.outputs[0].token, address(tokenOut2)); - assertEq(afterFees.outputs[0].amount, 2 ether / 20); - assertEq(afterFees.outputs[0].recipient, INTERFACE_FEE_RECIPIENT); - assertEq(afterFees.outputs[5].token, address(tokenOut)); - assertEq(afterFees.outputs[5].amount, 1 ether * 21 / 20 * 5 / 10000); - assertEq(afterFees.outputs[5].recipient, RECIPIENT); - assertEq(afterFees.outputs[4].token, address(tokenOut2)); - assertEq(afterFees.outputs[4].amount, 2 ether * 21 / 20 * 3 / 10000); - assertEq(afterFees.outputs[4].recipient, RECIPIENT); - } - - function testTakeFeesInvalidFeeToken() public { - MockFeeControllerZeroFee controller = new MockFeeControllerZeroFee(RECIPIENT); - vm.prank(PROTOCOL_FEE_OWNER); - fees.setProtocolFeeController(address(controller)); - - ResolvedOrder memory order = createOrderWithInterfaceFee(1 ether, false); - uint256 feeBps = 5; - controller.setFee(tokenIn, address(tokenOut), feeBps); - - vm.expectRevert(abi.encodeWithSelector(ProtocolFees.InvalidFeeToken.selector, address(0))); - fees.takeFees(order); - } - - // ======== Tests for InputTokenInOutputs validation ======== - - /// @notice Test that orders with input token in outputs always revert (regardless of fee controller) - function test_RevertIf_InputTokenInOutputs() public { - // Create order where input token (tokenIn) is also an output token - OutputToken[] memory outputs = new OutputToken[](1); - outputs[0] = OutputToken(address(tokenIn), 1 ether, SWAPPER); - ResolvedOrder memory order = ResolvedOrder({ - info: OrderInfoBuilder.init(address(0)), - input: InputToken(tokenIn, 1 ether, 1 ether), - outputs: outputs, - sig: hex"00", - hash: bytes32(0), - auctionResolver: address(0), - witnessTypeString: "" - }); - - vm.expectRevert(abi.encodeWithSelector(ProtocolFees.InputTokenInOutputs.selector, address(tokenIn))); - fees.takeFees(order); - } - - /// @notice Test that orders with input token in one of multiple outputs revert - function test_RevertIf_InputTokenInOutputs_MultipleOutputs() public { - // Create order where input token appears in second output - OutputToken[] memory outputs = new OutputToken[](3); - outputs[0] = OutputToken(address(tokenOut), 1 ether, SWAPPER); - outputs[1] = OutputToken(address(tokenIn), 0.5 ether, SWAPPER); // Input token as output! - outputs[2] = OutputToken(address(tokenOut2), 2 ether, SWAPPER); - ResolvedOrder memory order = ResolvedOrder({ - info: OrderInfoBuilder.init(address(0)), - input: InputToken(tokenIn, 1 ether, 1 ether), - outputs: outputs, - sig: hex"00", - hash: bytes32(0), - auctionResolver: address(0), - witnessTypeString: "" - }); - - vm.expectRevert(abi.encodeWithSelector(ProtocolFees.InputTokenInOutputs.selector, address(tokenIn))); - fees.takeFees(order); - } - - /// @notice Test that validation happens before fee controller check (even with no fee controller) - function test_RevertIf_InputTokenInOutputs_NoFeeController() public { - // Remove fee controller - vm.prank(PROTOCOL_FEE_OWNER); - fees.setProtocolFeeController(address(0)); - - // Create order where input token is also output - OutputToken[] memory outputs = new OutputToken[](1); - outputs[0] = OutputToken(address(tokenIn), 1 ether, SWAPPER); - ResolvedOrder memory order = ResolvedOrder({ - info: OrderInfoBuilder.init(address(0)), - input: InputToken(tokenIn, 1 ether, 1 ether), - outputs: outputs, - sig: hex"00", - hash: bytes32(0), - auctionResolver: address(0), - witnessTypeString: "" - }); - - // Should still revert even without fee controller - vm.expectRevert(abi.encodeWithSelector(ProtocolFees.InputTokenInOutputs.selector, address(tokenIn))); - fees.takeFees(order); - } - - /// @notice Test that valid orders (input token NOT in outputs) work correctly - function test_ValidOrder_InputNotInOutputs() public view { - // Create normal order where input and outputs are different tokens - OutputToken[] memory outputs = new OutputToken[](2); - outputs[0] = OutputToken(address(tokenOut), 1 ether, SWAPPER); - outputs[1] = OutputToken(address(tokenOut2), 2 ether, SWAPPER); - ResolvedOrder memory order = ResolvedOrder({ - info: OrderInfoBuilder.init(address(0)), - input: InputToken(tokenIn, 1 ether, 1 ether), - outputs: outputs, - sig: hex"00", - hash: bytes32(0), - auctionResolver: address(0), - witnessTypeString: "" - }); - - // Should not revert - fees.takeFees(order); - } - - function createOrder(uint256 amount, bool isEthOutput) private view returns (ResolvedOrder memory) { - OutputToken[] memory outputs = new OutputToken[](1); - address outputToken = isEthOutput ? address(0) : address(tokenOut); - outputs[0] = OutputToken(outputToken, amount, SWAPPER); - return ResolvedOrder({ - info: OrderInfoBuilder.init(address(0)), - input: InputToken(tokenIn, 1 ether, 1 ether), - outputs: outputs, - sig: hex"00", - hash: bytes32(0), - auctionResolver: address(0), - witnessTypeString: "" - }); - } - - function createOrderWithInterfaceFee(uint256 amount, bool isEthOutput) private view returns (ResolvedOrder memory) { - OutputToken[] memory outputs = new OutputToken[](2); - address outputToken = isEthOutput ? address(0) : address(tokenOut); - outputs[0] = OutputToken(outputToken, amount, RECIPIENT); - outputs[1] = OutputToken(outputToken, amount, INTERFACE_FEE_RECIPIENT); - return ResolvedOrder({ - info: OrderInfoBuilder.init(address(0)), - input: InputToken(tokenIn, 1 ether, 1 ether), - outputs: outputs, - sig: hex"00", - hash: bytes32(0), - auctionResolver: address(0), - witnessTypeString: "" - }); - } -} - -// The purpose of ProtocolFeesGasComparisonTest is to see how much gas increases when interface and/or -// protocol fees are added. -contract ProtocolFeesGasComparisonTest is Test, PermitSignature, DeployPermit2 { - using OrderInfoBuilder for OrderInfo; - - address constant PROTOCOL_FEE_OWNER = address(1001); - address constant INTERFACE_FEE_RECIPIENT = address(1002); - address constant PROTOCOL_FEE_RECIPIENT = address(1003); - - MockERC20 tokenIn1; - MockERC20 tokenOut1; - uint256 swapperPrivateKey1; - address swapper1; - Reactor reactor; - IPermit2 permit2; - MockFillContract fillContract; - MockFeeController feeController; - MockAuctionResolver auctionResolver; - TokenTransferHook tokenTransferHook; - - function setUp() public { - tokenIn1 = new MockERC20("tokenIn1", "IN1", 18); - tokenOut1 = new MockERC20("tokenOut1", "OUT1", 18); - swapperPrivateKey1 = 0x12341234; - swapper1 = vm.addr(swapperPrivateKey1); - - feeController = new MockFeeController(PROTOCOL_FEE_RECIPIENT); - permit2 = IPermit2(deployPermit2()); - reactor = new Reactor(PROTOCOL_FEE_OWNER, permit2); - tokenTransferHook = new TokenTransferHook(permit2, reactor); - auctionResolver = new MockAuctionResolver(); - fillContract = new MockFillContract(address(reactor)); - vm.prank(PROTOCOL_FEE_OWNER); - reactor.setProtocolFeeController(address(feeController)); - - tokenIn1.forceApprove(swapper1, address(permit2), type(uint256).max); - // Keep non 0 balances in swapper1, INTERFACE_FEE_RECIPIENT, PROTOCOL_FEE_RECIPIENT to simulate best - // case gas scenario - tokenOut1.mint(swapper1, 1 ether); - tokenOut1.mint(INTERFACE_FEE_RECIPIENT, 1 ether); - tokenOut1.mint(PROTOCOL_FEE_RECIPIENT, 1 ether); - tokenIn1.mint(address(fillContract), 1 ether); - vm.deal(swapper1, 1 ether); - vm.deal(INTERFACE_FEE_RECIPIENT, 1 ether); - vm.deal(PROTOCOL_FEE_RECIPIENT, 1 ether); - } - - // Fill an order without fees: input = 1 tokenIn, output = 1 tokenOut - function testNoFees() public { - tokenIn1.mint(swapper1, 1 ether); - tokenOut1.mint(address(fillContract), 1 ether); - - OutputToken[] memory outputs = new OutputToken[](1); - outputs[0] = OutputToken(address(tokenOut1), 1 ether, swapper1); - MockOrder memory mockOrder = MockOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper1).withDeadline(block.timestamp + 100) - .withAuctionResolver(auctionResolver).withPreExecutionHook(tokenTransferHook), - input: InputToken(tokenIn1, 1 ether, 1 ether), - outputs: outputs - }); - bytes memory encodedOrder = abi.encode(address(auctionResolver), abi.encode(mockOrder)); - vm.startSnapshotGas("ProtocolFeesGasComparisonTest-NoFees"); - fillContract.execute(SignedOrder(encodedOrder, signOrder(swapperPrivateKey1, address(permit2), mockOrder))); - vm.stopSnapshotGas(); - assertEq(tokenIn1.balanceOf(address(fillContract)), 2 ether); - assertEq(tokenOut1.balanceOf(address(swapper1)), 2 ether); - } - - // Fill an order with an interface fee: input = 1 tokenIn, output = [1 tokenOut to swapper1, 0.05 tokenOut to interface] - function testInterfaceFee() public { - tokenIn1.mint(address(swapper1), 1 ether); - tokenOut1.mint(address(fillContract), 2 ether); - - OutputToken[] memory outputs = new OutputToken[](2); - outputs[0] = OutputToken(address(tokenOut1), 1 ether, swapper1); - outputs[1] = OutputToken(address(tokenOut1), 1 ether / 20, INTERFACE_FEE_RECIPIENT); - MockOrder memory mockOrder = MockOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper1).withDeadline(block.timestamp + 100) - .withAuctionResolver(auctionResolver).withPreExecutionHook(tokenTransferHook), - input: InputToken(tokenIn1, 1 ether, 1 ether), - outputs: outputs - }); - bytes memory encodedOrder = abi.encode(address(auctionResolver), abi.encode(mockOrder)); - vm.startSnapshotGas("ProtocolFeesGasComparisonTest-InterfaceFee"); - fillContract.execute(SignedOrder(encodedOrder, signOrder(swapperPrivateKey1, address(permit2), mockOrder))); - vm.stopSnapshotGas(); - assertEq(tokenIn1.balanceOf(address(fillContract)), 2 ether); - assertEq(tokenOut1.balanceOf(address(swapper1)), 2 ether); - assertEq(tokenOut1.balanceOf(address(INTERFACE_FEE_RECIPIENT)), 21 ether / 20); - } - - // Fill an order with an interface fee and protocol fee: input = 1 tokenIn, - // output = [1 tokenOut to swapper1, 0.05 tokenOut to interface]. Protocol fee = 5bps - function testInterfaceAndProtocolFee() public { - feeController.setFee(tokenIn1, address(tokenOut1), 5); - - tokenIn1.mint(address(swapper1), 1 ether); - tokenOut1.mint(address(fillContract), 2 ether); - - OutputToken[] memory outputs = new OutputToken[](2); - outputs[0] = OutputToken(address(tokenOut1), 1 ether, swapper1); - outputs[1] = OutputToken(address(tokenOut1), 1 ether / 20, INTERFACE_FEE_RECIPIENT); - MockOrder memory mockOrder = MockOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper1).withDeadline(block.timestamp + 100) - .withAuctionResolver(auctionResolver).withPreExecutionHook(tokenTransferHook), - input: InputToken(tokenIn1, 1 ether, 1 ether), - outputs: outputs - }); - bytes memory encodedOrder = abi.encode(address(auctionResolver), abi.encode(mockOrder)); - vm.startSnapshotGas("ProtocolFeesGasComparisonTest-InterfaceAndProtocolFee"); - fillContract.execute(SignedOrder(encodedOrder, signOrder(swapperPrivateKey1, address(permit2), mockOrder))); - vm.stopSnapshotGas(); - // fillContract had 1 tokenIn1 preminted to it - assertEq(tokenIn1.balanceOf(address(fillContract)), 2 ether); - // swapper had 1 tokenOut1 preminted to it - assertEq(tokenOut1.balanceOf(swapper1), 2 ether); - // INTERFACE_FEE_RECIPIENT had 1 tokenOut1 preminted to it - assertEq(tokenOut1.balanceOf(INTERFACE_FEE_RECIPIENT), 21 ether / 20); - // Protocol fee is 5 bps * 1.05 - assertEq(tokenOut1.balanceOf(PROTOCOL_FEE_RECIPIENT), 1 ether + 21 ether / 20 * 5 / 10000); - } - - // The same as `testNoFees`, but output = 1 ether - function testNoFeesEthOutput() public { - tokenIn1.mint(swapper1, 1 ether); - vm.deal(address(fillContract), 1 ether); - - OutputToken[] memory outputs = new OutputToken[](1); - outputs[0] = OutputToken(address(0), 1 ether, swapper1); - MockOrder memory mockOrder = MockOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper1).withDeadline(block.timestamp + 100) - .withAuctionResolver(auctionResolver).withPreExecutionHook(tokenTransferHook), - input: InputToken(tokenIn1, 1 ether, 1 ether), - outputs: outputs - }); - bytes memory encodedOrder = abi.encode(address(auctionResolver), abi.encode(mockOrder)); - vm.startSnapshotGas("ProtocolFeesGasComparisonTest-NoFeesEthOutput"); - fillContract.execute(SignedOrder(encodedOrder, signOrder(swapperPrivateKey1, address(permit2), mockOrder))); - vm.stopSnapshotGas(); - assertEq(tokenIn1.balanceOf(address(fillContract)), 2 ether); - assertEq(swapper1.balance, 2 ether); - } - - // Fill an order with an interface fee: input = 1 tokenIn, output = [1 ether to swapper1, 0.05 ether to interface] - function testInterfaceFeeEthOutput() public { - tokenIn1.mint(address(swapper1), 1 ether); - vm.deal(address(fillContract), 2 ether); - - OutputToken[] memory outputs = new OutputToken[](2); - outputs[0] = OutputToken(address(0), 1 ether, swapper1); - outputs[1] = OutputToken(address(0), 1 ether / 20, INTERFACE_FEE_RECIPIENT); - MockOrder memory mockOrder = MockOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper1).withDeadline(block.timestamp + 100) - .withAuctionResolver(auctionResolver).withPreExecutionHook(tokenTransferHook), - input: InputToken(tokenIn1, 1 ether, 1 ether), - outputs: outputs - }); - bytes memory encodedOrder = abi.encode(address(auctionResolver), abi.encode(mockOrder)); - vm.startSnapshotGas("ProtocolFeesGasComparisonTest-InterfaceFeeEthOutput"); - fillContract.execute(SignedOrder(encodedOrder, signOrder(swapperPrivateKey1, address(permit2), mockOrder))); - vm.stopSnapshotGas(); - assertEq(tokenIn1.balanceOf(address(fillContract)), 2 ether); - assertEq(swapper1.balance, 2 ether); - assertEq(INTERFACE_FEE_RECIPIENT.balance, 21 ether / 20); - } - - // Fill an order with an interface fee and protocol fee: input = 1 tokenIn, - // output = [1 ether to swapper1, 0.05 ether to interface]. Protocol fee = 5bps - function testInterfaceAndProtocolFeeEthOutput() public { - feeController.setFee(tokenIn1, address(0), 5); - - tokenIn1.mint(address(swapper1), 1 ether); - vm.deal(address(fillContract), 2 ether); - - OutputToken[] memory outputs = new OutputToken[](2); - outputs[0] = OutputToken(address(0), 1 ether, swapper1); - outputs[1] = OutputToken(address(0), 1 ether / 20, INTERFACE_FEE_RECIPIENT); - MockOrder memory mockOrder = MockOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper1).withDeadline(block.timestamp + 100) - .withAuctionResolver(auctionResolver).withPreExecutionHook(tokenTransferHook), - input: InputToken(tokenIn1, 1 ether, 1 ether), - outputs: outputs - }); - bytes memory encodedOrder = abi.encode(address(auctionResolver), abi.encode(mockOrder)); - vm.startSnapshotGas("ProtocolFeesGasComparisonTest-InterfaceAndProtocolFeeEthOutput"); - fillContract.execute(SignedOrder(encodedOrder, signOrder(swapperPrivateKey1, address(permit2), mockOrder))); - vm.stopSnapshotGas(); - // fillContract had 1 tokenIn1 preminted to it - assertEq(tokenIn1.balanceOf(address(fillContract)), 2 ether); - // swapper had 1 tokenOut1 preminted to it - assertEq(swapper1.balance, 2 ether); - // INTERFACE_FEE_RECIPIENT had 1 tokenOut1 preminted to it - assertEq(INTERFACE_FEE_RECIPIENT.balance, 21 ether / 20); - // Protocol fee is 5 bps * 1.05 - assertEq(PROTOCOL_FEE_RECIPIENT.balance, 1 ether + 21 ether / 20 * 5 / 10000); - } -} diff --git a/test/v4/Reactor.t.sol b/test/v4/Reactor.t.sol deleted file mode 100644 index f1689b58..00000000 --- a/test/v4/Reactor.t.sol +++ /dev/null @@ -1,1025 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {Test} from "forge-std/Test.sol"; -import {IPermit2} from "permit2/src/interfaces/IPermit2.sol"; - -import {DeployPermit2} from "../util/DeployPermit2.sol"; -import {IReactor} from "../../src/v4/interfaces/IReactor.sol"; -import {PermitSignature} from "../util/PermitSignature.sol"; -import {Reactor} from "../../src/v4/Reactor.sol"; -import {ReactorEvents} from "../../src/base/ReactorEvents.sol"; -import {SignedOrder, InputToken, OutputToken} from "../../src/base/ReactorStructs.sol"; -import {OrderInfo, ResolvedOrder} from "../../src/v4/base/ReactorStructs.sol"; -import {OrderInfoBuilder} from "../v4/util/OrderInfoBuilder.sol"; -import {OutputsBuilder} from "../util/OutputsBuilder.sol"; -import {MockERC20} from "../util/mock/MockERC20.sol"; -import {MockFillContract} from "../v4/util/mock/MockFillContract.sol"; -import {MockFeeController} from "../v4/util/mock/MockFeeController.sol"; -import {MockPreExecutionHook} from "../v4/util/mock/MockPreExecutionHook.sol"; -import {MockPostExecutionHook} from "../v4/util/mock/MockPostExecutionHook.sol"; -import {TokenTransferHook} from "../../src/v4/hooks/TokenTransferHook.sol"; -import {MockAuctionResolver, MaliciousAuctionResolver} from "./util/mock/MockAuctionResolver.sol"; -import {MockOrder, MockOrderLib} from "./util/mock/MockOrderLib.sol"; -import {ArrayBuilder} from "../util/ArrayBuilder.sol"; -import {NATIVE} from "../../src/lib/CurrencyLibrary.sol"; -import {ERC20ETH} from "../../lib/calibur/lib/erc20-eth/src/ERC20Eth.sol"; -import {DelegationHandler} from "../native-input/DelegationHandler.sol"; -import {ERC20} from "solmate/src/tokens/ERC20.sol"; -import "forge-std/console2.sol"; - -contract ReactorTest is ReactorEvents, Test, PermitSignature, DeployPermit2, DelegationHandler { - using OrderInfoBuilder for OrderInfo; - using MockOrderLib for MockOrder; - using ArrayBuilder for uint256[]; - - uint256 constant ONE = 10 ** 18; - bytes4 constant INVALID_NONCE_SELECTOR = 0x756688fe; - address internal constant PROTOCOL_FEE_OWNER = address(1); - - MockERC20 tokenIn; - MockERC20 tokenOut; - MockERC20 tokenOut2; - MockFillContract fillContract; - MockPreExecutionHook preExecutionHook; - MockPostExecutionHook postExecutionHook; - TokenTransferHook tokenTransferHook; - IPermit2 permit2; - MockFeeController feeController; - address feeRecipient; - Reactor reactor; - MockAuctionResolver mockResolver; - uint256 swapperPrivateKey; - address swapper; - ERC20ETH erc20eth; - - function setUp() public { - tokenIn = new MockERC20("Input", "IN", 18); - tokenOut = new MockERC20("Output", "OUT", 18); - tokenOut2 = new MockERC20("Output2", "OUT2", 18); - swapperPrivateKey = 0x12341234; - swapper = vm.addr(swapperPrivateKey); - permit2 = IPermit2(deployPermit2()); - - reactor = new Reactor(PROTOCOL_FEE_OWNER, permit2); - preExecutionHook = new MockPreExecutionHook(permit2, reactor); - preExecutionHook.setValid(true); - postExecutionHook = new MockPostExecutionHook(); - tokenTransferHook = new TokenTransferHook(permit2, reactor); - feeRecipient = makeAddr("feeRecipient"); - feeController = new MockFeeController(feeRecipient); - mockResolver = new MockAuctionResolver(); - fillContract = new MockFillContract(address(reactor)); - vm.deal(address(fillContract), type(uint256).max); - - setUpDelegation(); - erc20eth = new ERC20ETH(); - } - - /// @dev Create a signed order for Reactor using MockOrder - function createAndSignOrder(MockOrder memory mockOrder) - public - view - returns (SignedOrder memory signedOrder, bytes32 orderHash) - { - // Use the new witness hash that includes resolver and full order - orderHash = mockOrder.witnessHash(address(mockOrder.info.auctionResolver)); - - bytes memory sig = signOrder(swapperPrivateKey, address(permit2), mockOrder); - - bytes memory orderData = abi.encode(mockOrder); - - bytes memory encodedOrder = abi.encode(address(mockResolver), orderData); - - signedOrder = SignedOrder(encodedOrder, sig); - } - - /// @dev Create many signed orders and return - function createAndSignBatchOrders(MockOrder[] memory orders) - public - view - returns (SignedOrder[] memory signedOrders, bytes32[] memory orderHashes) - { - signedOrders = new SignedOrder[](orders.length); - orderHashes = new bytes32[](orders.length); - for (uint256 i = 0; i < orders.length; i++) { - (SignedOrder memory signed, bytes32 hash) = createAndSignOrder(orders[i]); - signedOrders[i] = signed; - orderHashes[i] = hash; - } - } - - /// @dev Helper to create a basic MockOrder with the standard token transfer hook - function createBasicOrder(uint256 inputAmount, uint256 outputAmount, uint256 deadline) - internal - view - returns (MockOrder memory) - { - return MockOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(deadline) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(mockResolver), - input: InputToken(tokenIn, inputAmount, inputAmount), - outputs: OutputsBuilder.single(address(tokenOut), outputAmount, swapper) - }); - } - - /// @dev Checkpoint token balances for assertions - function _checkpointBalances() - internal - view - returns ( - uint256 swapperInputBalance, - uint256 fillContractInputBalance, - uint256 swapperOutputBalance, - uint256 fillContractOutputBalance - ) - { - swapperInputBalance = tokenIn.balanceOf(swapper); - fillContractInputBalance = tokenIn.balanceOf(address(fillContract)); - swapperOutputBalance = tokenOut.balanceOf(swapper); - fillContractOutputBalance = tokenOut.balanceOf(address(fillContract)); - } - - function exploitSignedOrder(SignedOrder memory signedOrder, address maliciousResolver) - public - view - returns (SignedOrder memory modifiedOrder) - { - modifiedOrder.sig = signedOrder.sig; - (address auctResolver, bytes memory orderData) = abi.decode(signedOrder.order, (address, bytes)); - console2.log("exploitSignedOrder - Original auction resolver:", auctResolver); - modifiedOrder.order = abi.encode(maliciousResolver, orderData); - } - - /// @dev Test of a simple execute - function test_executeBaseCase() public { - uint256 inputAmount = 1 ether; - uint256 outputAmount = 1 ether; - uint256 deadline = block.timestamp + 1000; - - tokenIn.mint(address(swapper), inputAmount); - tokenOut.mint(address(fillContract), outputAmount); - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - - MockOrder memory order = createBasicOrder(inputAmount, outputAmount, deadline); - - (SignedOrder memory signedOrder, bytes32 orderHash) = createAndSignOrder(order); - - ( - uint256 swapperInputBalanceStart, - uint256 fillContractInputBalanceStart, - uint256 swapperOutputBalanceStart, - uint256 fillContractOutputBalanceStart - ) = _checkpointBalances(); - - vm.expectEmit(true, true, true, true, address(reactor)); - emit Fill(orderHash, address(fillContract), swapper, order.info.nonce); - fillContract.execute(signedOrder); - vm.snapshotGasLastCall("ReactorExecuteSingle"); - - assertEq(tokenIn.balanceOf(address(swapper)), swapperInputBalanceStart - inputAmount); - assertEq(tokenIn.balanceOf(address(fillContract)), fillContractInputBalanceStart + inputAmount); - assertEq(tokenOut.balanceOf(address(swapper)), swapperOutputBalanceStart + outputAmount); - assertEq(tokenOut.balanceOf(address(fillContract)), fillContractOutputBalanceStart - outputAmount); - } - - function test_executeWithFee() public { - uint256 inputAmount = 1 ether; - uint256 outputAmount = 1 ether; - uint256 deadline = block.timestamp + 1000; - uint8 feeBps = 3; - - vm.prank(PROTOCOL_FEE_OWNER); - reactor.setProtocolFeeController(address(feeController)); - feeController.setFee(tokenIn, address(tokenOut), feeBps); - tokenIn.mint(address(swapper), uint256(inputAmount) * 100); - tokenOut.mint(address(fillContract), uint256(outputAmount) * 100); - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - - MockOrder memory order = createBasicOrder(inputAmount, outputAmount, deadline); - - (SignedOrder memory signedOrder, bytes32 orderHash) = createAndSignOrder(order); - - ( - uint256 swapperInputBalanceStart, - uint256 fillContractInputBalanceStart, - uint256 swapperOutputBalanceStart, - uint256 fillContractOutputBalanceStart - ) = _checkpointBalances(); - - vm.expectEmit(true, true, true, true, address(reactor)); - emit Fill(orderHash, address(fillContract), swapper, order.info.nonce); - fillContract.execute(signedOrder); - vm.snapshotGasLastCall("BaseExecuteSingleWithFee"); - - uint256 feeAmount = uint256(outputAmount) * feeBps / 10000; - assertEq(tokenIn.balanceOf(address(swapper)), swapperInputBalanceStart - inputAmount); - assertEq(tokenIn.balanceOf(address(fillContract)), fillContractInputBalanceStart + inputAmount); - assertEq(tokenOut.balanceOf(address(swapper)), swapperOutputBalanceStart + outputAmount); - assertEq(tokenOut.balanceOf(address(fillContract)), fillContractOutputBalanceStart - outputAmount - feeAmount); - assertEq(tokenOut.balanceOf(address(feeRecipient)), feeAmount); - } - - /// @dev execute test for native currency input - function test_executeNativeInput() public { - uint256 inputAmount = 1 ether; - uint256 outputAmount = 1 ether; - uint256 deadline = block.timestamp + 1000; - - // Reset fillContract ETH balance to avoid overflow (it's set to type(uint256).max in setUp) - vm.deal(address(fillContract), 0); - - // Fund the signerAccount (smart contract account) with native ETH - vm.deal(address(signerAccount), inputAmount); - - // Approve ERC20ETH to use signerAccount's native ETH - vm.prank(address(signerAccount)); - signerAccount.approveNative(address(erc20eth), type(uint256).max); - - // Mint output tokens for the filler - tokenOut.mint(address(fillContract), outputAmount); - - // Create order with native ETH input - MockOrder memory order = MockOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(address(signerAccount)).withDeadline(deadline) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(mockResolver), - input: InputToken(MockERC20(address(erc20eth)), inputAmount, inputAmount), - outputs: OutputsBuilder.single(address(tokenOut), outputAmount, address(signerAccount)) - }); - - // Sign with the signerAccount's private key from DelegationHandler - bytes32 orderHash = order.witnessHash(address(order.info.auctionResolver)); - bytes memory sig = signOrder(signerPrivateKey, address(permit2), order); - bytes memory orderData = abi.encode(order); - bytes memory encodedOrder = abi.encode(address(mockResolver), orderData); - SignedOrder memory signedOrder = SignedOrder(encodedOrder, sig); - - // Check balances before - uint256 signerAccountEthBalanceStart = address(signerAccount).balance; - uint256 fillContractEthBalanceStart = address(fillContract).balance; - uint256 signerAccountTokenOutBalanceStart = tokenOut.balanceOf(address(signerAccount)); - uint256 fillContractTokenOutBalanceStart = tokenOut.balanceOf(address(fillContract)); - - // Execute the order - vm.expectEmit(true, true, true, true, address(reactor)); - emit Fill(orderHash, address(fillContract), address(signerAccount), order.info.nonce); - fillContract.execute(signedOrder); - vm.snapshotGasLastCall("ReactorExecuteSingleNativeInput"); - - // Verify balances after execution - assertEq( - address(signerAccount).balance, signerAccountEthBalanceStart - inputAmount, "Swapper ETH balance incorrect" - ); - assertEq( - address(fillContract).balance, fillContractEthBalanceStart + inputAmount, "Filler ETH balance incorrect" - ); - assertEq( - tokenOut.balanceOf(address(signerAccount)), - signerAccountTokenOutBalanceStart + outputAmount, - "Swapper tokenOut balance incorrect" - ); - assertEq( - tokenOut.balanceOf(address(fillContract)), - fillContractTokenOutBalanceStart - outputAmount, - "Filler tokenOut balance incorrect" - ); - } - - /// @dev execute test for native currency output - function test_executeNativeOutput() public { - uint256 inputAmount = 1 ether; - uint256 outputAmount = 1 ether; - uint256 deadline = block.timestamp + 1000; - - tokenIn.mint(address(swapper), inputAmount); - vm.deal(address(fillContract), outputAmount); - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - - MockOrder memory order = MockOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(deadline) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(mockResolver), - input: InputToken(tokenIn, inputAmount, inputAmount), - outputs: OutputsBuilder.single(NATIVE, outputAmount, swapper) - }); - - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - - uint256 swapperOutputBalanceStart = address(swapper).balance; - uint256 fillContractOutputBalanceStart = address(fillContract).balance; - (uint256 swapperInputBalanceStart, uint256 fillContractInputBalanceStart,,) = _checkpointBalances(); - - fillContract.execute(signedOrder); - vm.snapshotGasLastCall("ReactorExecuteSingleNativeOutput"); - - assertEq(tokenIn.balanceOf(address(swapper)), swapperInputBalanceStart - inputAmount); - assertEq(tokenIn.balanceOf(address(fillContract)), fillContractInputBalanceStart + inputAmount); - assertEq(address(swapper).balance, swapperOutputBalanceStart + outputAmount); - assertEq(address(fillContract).balance, fillContractOutputBalanceStart - outputAmount); - } - - /// @dev Execute test with a pre-execution hook - function test_executeWithPreExecutionHook() public { - uint256 inputAmount = 1 ether; - uint256 outputAmount = 1 ether; - uint256 deadline = block.timestamp + 1000; - - tokenIn.mint(address(swapper), inputAmount); - tokenOut.mint(address(fillContract), outputAmount); - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - - MockOrder memory order = MockOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(deadline) - .withPreExecutionHook(preExecutionHook).withAuctionResolver(mockResolver), - input: InputToken(tokenIn, inputAmount, inputAmount), - outputs: OutputsBuilder.single(address(tokenOut), outputAmount, swapper) - }); - - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - - ( - uint256 swapperInputBalanceStart, - uint256 fillContractInputBalanceStart, - uint256 swapperOutputBalanceStart, - uint256 fillContractOutputBalanceStart - ) = _checkpointBalances(); - - uint256 counterBefore = preExecutionHook.preExecutionCounter(); - uint256 fillerExecutionsBefore = preExecutionHook.fillerExecutions(address(fillContract)); - - fillContract.execute(signedOrder); - - // Verify hook was called and state was modified - assertEq(preExecutionHook.preExecutionCounter(), counterBefore + 1); - assertEq(preExecutionHook.fillerExecutions(address(fillContract)), fillerExecutionsBefore + 1); - - assertEq(tokenIn.balanceOf(address(swapper)), swapperInputBalanceStart - inputAmount); - assertEq(tokenIn.balanceOf(address(fillContract)), fillContractInputBalanceStart + inputAmount); - assertEq(tokenOut.balanceOf(address(swapper)), swapperOutputBalanceStart + outputAmount); - assertEq(tokenOut.balanceOf(address(fillContract)), fillContractOutputBalanceStart - outputAmount); - } - - /// @dev Test pre-execution hook that fails validation - function test_executeWithPreExecutionHookRevert() public { - uint256 inputAmount = 1 ether; - uint256 outputAmount = 1 ether; - uint256 deadline = block.timestamp + 1000; - - tokenIn.mint(address(swapper), inputAmount); - tokenOut.mint(address(fillContract), outputAmount); - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - - // Set hook to invalid state - preExecutionHook.setValid(false); - - MockOrder memory order = MockOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(deadline) - .withPreExecutionHook(preExecutionHook).withAuctionResolver(mockResolver), - input: InputToken(tokenIn, inputAmount, inputAmount), - outputs: OutputsBuilder.single(address(tokenOut), outputAmount, swapper) - }); - - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - - vm.expectRevert(MockPreExecutionHook.MockPreExecutionError.selector); - fillContract.execute(signedOrder); - } - - /// @dev Test execute with post-execution hook - function test_executeWithPostExecutionHook() public { - uint256 inputAmount = 1 ether; - uint256 outputAmount = 1 ether; - uint256 deadline = block.timestamp + 1000; - - tokenIn.mint(address(swapper), inputAmount); - tokenOut.mint(address(fillContract), outputAmount); - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - - MockOrder memory order = MockOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(deadline) - .withPreExecutionHook(tokenTransferHook).withPostExecutionHook(postExecutionHook) - .withAuctionResolver(mockResolver), - input: InputToken(tokenIn, inputAmount, inputAmount), - outputs: OutputsBuilder.single(address(tokenOut), outputAmount, swapper) - }); - - (SignedOrder memory signedOrder, bytes32 orderHash) = createAndSignOrder(order); - - uint256 counterBefore = postExecutionHook.postExecutionCounter(); - uint256 fillerExecutionsBefore = postExecutionHook.fillerExecutions(address(fillContract)); - uint256 swapperExecutionsBefore = postExecutionHook.swapperExecutions(swapper); - - fillContract.execute(signedOrder); - - // Verify post-hook was called - assertEq(postExecutionHook.postExecutionCounter(), counterBefore + 1); - assertEq(postExecutionHook.fillerExecutions(address(fillContract)), fillerExecutionsBefore + 1); - assertEq(postExecutionHook.swapperExecutions(swapper), swapperExecutionsBefore + 1); - assertEq(postExecutionHook.lastFiller(), address(fillContract)); - assertEq(postExecutionHook.lastSwapper(), swapper); - assertEq(postExecutionHook.lastOrderHash(), orderHash); - assertEq(postExecutionHook.lastInputAmount(), inputAmount); - assertEq(postExecutionHook.lastOutputAmount(), outputAmount); - - // Verify tokens transferred correctly - assertEq(tokenIn.balanceOf(address(swapper)), 0); - assertEq(tokenIn.balanceOf(address(fillContract)), inputAmount); - assertEq(tokenOut.balanceOf(address(swapper)), outputAmount); - assertEq(tokenOut.balanceOf(address(fillContract)), 0); - } - - /// @dev Test execute with post-execution hook that reverts - function test_executeWithPostExecutionHookRevert() public { - uint256 inputAmount = 1 ether; - uint256 outputAmount = 1 ether; - uint256 deadline = block.timestamp + 1000; - - tokenIn.mint(address(swapper), inputAmount); - tokenOut.mint(address(fillContract), outputAmount); - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - - postExecutionHook.setShouldRevert(true); - - MockOrder memory order = MockOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(deadline) - .withPreExecutionHook(tokenTransferHook).withPostExecutionHook(postExecutionHook) - .withAuctionResolver(mockResolver), - input: InputToken(tokenIn, inputAmount, inputAmount), - outputs: OutputsBuilder.single(address(tokenOut), outputAmount, swapper) - }); - - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - - // Should revert with MockPostExecutionError - vm.expectRevert(MockPostExecutionHook.MockPostExecutionError.selector); - fillContract.execute(signedOrder); - - // Verify no tokens were transferred due to revert - assertEq(tokenIn.balanceOf(address(swapper)), inputAmount); - assertEq(tokenIn.balanceOf(address(fillContract)), 0); - assertEq(tokenOut.balanceOf(address(swapper)), 0); - assertEq(tokenOut.balanceOf(address(fillContract)), outputAmount); - } - - /// @dev Test execute with both pre and post execution hooks - function test_executeWithBothHooks() public { - uint256 inputAmount = 1 ether; - uint256 outputAmount = 1 ether; - uint256 deadline = block.timestamp + 1000; - - tokenIn.mint(address(swapper), inputAmount); - tokenOut.mint(address(fillContract), outputAmount); - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - - MockOrder memory order = MockOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(deadline) - .withPreExecutionHook(preExecutionHook).withPostExecutionHook(postExecutionHook) - .withAuctionResolver(mockResolver), - input: InputToken(tokenIn, inputAmount, inputAmount), - outputs: OutputsBuilder.single(address(tokenOut), outputAmount, swapper) - }); - - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - - uint256 preCounterBefore = preExecutionHook.preExecutionCounter(); - uint256 postCounterBefore = postExecutionHook.postExecutionCounter(); - - fillContract.execute(signedOrder); - - // Verify both hooks were called - assertEq(preExecutionHook.preExecutionCounter(), preCounterBefore + 1); - assertEq(postExecutionHook.postExecutionCounter(), postCounterBefore + 1); - assertEq(postExecutionHook.lastFiller(), address(fillContract)); - assertEq(postExecutionHook.lastSwapper(), swapper); - } - - /// @dev Basic batch execute test - function test_executeBatch() public { - uint256 inputAmount = ONE; - uint256 outputAmount = 2 * inputAmount; - - tokenIn.mint(address(swapper), inputAmount * 3); - tokenOut.mint(address(fillContract), 6 ether); - tokenIn.forceApprove(swapper, address(permit2), type(uint256).max); - - uint256 totalOutputAmount = 3 * outputAmount; - uint256 totalInputAmount = 3 * inputAmount; - - MockOrder[] memory orders = new MockOrder[](2); - - orders[0] = MockOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 100) - .withNonce(0).withPreExecutionHook(tokenTransferHook).withAuctionResolver(mockResolver), - input: InputToken(tokenIn, inputAmount, inputAmount), - outputs: OutputsBuilder.single(address(tokenOut), outputAmount, swapper) - }); - - orders[1] = MockOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 100) - .withNonce(1).withPreExecutionHook(tokenTransferHook).withAuctionResolver(mockResolver), - input: InputToken(tokenIn, 2 * inputAmount, 2 * inputAmount), - outputs: OutputsBuilder.single(address(tokenOut), 2 * outputAmount, swapper) - }); - - (SignedOrder[] memory signedOrders, bytes32[] memory orderHashes) = createAndSignBatchOrders(orders); - - vm.expectEmit(true, true, true, true); - emit Fill(orderHashes[0], address(fillContract), swapper, orders[0].info.nonce); - vm.expectEmit(true, true, true, true); - emit Fill(orderHashes[1], address(fillContract), swapper, orders[1].info.nonce); - - fillContract.executeBatch(signedOrders); - vm.snapshotGasLastCall("ReactorExecuteBatch"); - - assertEq(tokenIn.balanceOf(address(swapper)), 0); - assertEq(tokenIn.balanceOf(address(fillContract)), totalInputAmount); - assertEq(tokenOut.balanceOf(address(swapper)), totalOutputAmount); - assertEq(tokenOut.balanceOf(address(fillContract)), 6 ether - totalOutputAmount); - } - - /// @dev Basic batch execute test with native output - function test_executeBatchNativeOutput() public { - uint256 inputAmount = ONE; - uint256 outputAmount = 2 * inputAmount; - - tokenIn.mint(address(swapper), inputAmount * 3); - vm.deal(address(fillContract), 6 ether); - tokenIn.forceApprove(swapper, address(permit2), type(uint256).max); - - uint256 totalOutputAmount = 3 * outputAmount; - uint256 totalInputAmount = 3 * inputAmount; - - MockOrder[] memory orders = new MockOrder[](2); - - orders[0] = MockOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 100) - .withNonce(0).withPreExecutionHook(tokenTransferHook).withAuctionResolver(mockResolver), - input: InputToken(tokenIn, inputAmount, inputAmount), - outputs: OutputsBuilder.single(NATIVE, outputAmount, swapper) - }); - - orders[1] = MockOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 100) - .withNonce(1).withPreExecutionHook(tokenTransferHook).withAuctionResolver(mockResolver), - input: InputToken(tokenIn, 2 * inputAmount, 2 * inputAmount), - outputs: OutputsBuilder.single(NATIVE, 2 * outputAmount, swapper) - }); - - (SignedOrder[] memory signedOrders, bytes32[] memory orderHashes) = createAndSignBatchOrders(orders); - - vm.expectEmit(true, true, true, true); - emit Fill(orderHashes[0], address(fillContract), swapper, orders[0].info.nonce); - vm.expectEmit(true, true, true, true); - emit Fill(orderHashes[1], address(fillContract), swapper, orders[1].info.nonce); - - fillContract.executeBatch(signedOrders); - vm.snapshotGasLastCall("ReactorExecuteBatchNativeOutput"); - - assertEq(address(swapper).balance, totalOutputAmount); - assertEq(tokenIn.balanceOf(address(fillContract)), totalInputAmount); - } - - /// @dev Test with multiple outputs - function test_executeBatchMultipleOutputs() public { - uint256 inputAmount = 3 ether; - uint256[] memory outputAmounts = new uint256[](2); - outputAmounts[0] = 2 ether; - outputAmounts[1] = 1 ether; - - tokenIn.mint(address(swapper), inputAmount); - tokenOut.mint(address(fillContract), 3 ether); - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - - MockOrder memory order = MockOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 100) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(mockResolver), - input: InputToken(tokenIn, inputAmount, inputAmount), - outputs: OutputsBuilder.multiple(address(tokenOut), outputAmounts, swapper) - }); - - (SignedOrder memory signedOrder, bytes32 orderHash) = createAndSignOrder(order); - - vm.expectEmit(true, true, true, true, address(reactor)); - emit Fill(orderHash, address(fillContract), swapper, order.info.nonce); - - fillContract.execute(signedOrder); - - assertEq(tokenIn.balanceOf(address(swapper)), 0); - assertEq(tokenIn.balanceOf(address(fillContract)), inputAmount); - assertEq(tokenOut.balanceOf(address(swapper)), 3 ether); - assertEq(tokenOut.balanceOf(address(fillContract)), 0); - } - - /// @dev Execute batch with multiple outputs using different tokens - function test_executeBatchMultipleOutputsDifferentTokens() public { - uint256[] memory output1 = ArrayBuilder.fill(1, 2 * ONE).push(ONE); - uint256[] memory output2 = ArrayBuilder.fill(1, 3 * ONE).push(ONE); - - OutputToken[] memory outputs1 = OutputsBuilder.multiple(address(tokenOut), output1, swapper); - outputs1[1].token = address(tokenOut2); - - OutputToken[] memory outputs2 = OutputsBuilder.multiple(address(tokenOut), output2, swapper); - outputs2[0].token = address(tokenOut2); - - uint256 totalInputAmount = 3 * ONE; - uint256 totalOutputAmount1 = 3 * ONE; - uint256 totalOutputAmount2 = 4 * ONE; - tokenIn.mint(address(swapper), totalInputAmount); - tokenOut.mint(address(fillContract), totalOutputAmount1); - tokenOut2.mint(address(fillContract), totalOutputAmount2); - tokenIn.forceApprove(swapper, address(permit2), type(uint256).max); - - MockOrder[] memory orders = new MockOrder[](2); - - orders[0] = MockOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 100) - .withNonce(0).withPreExecutionHook(tokenTransferHook).withAuctionResolver(mockResolver), - input: InputToken(tokenIn, ONE, ONE), - outputs: outputs1 - }); - - orders[1] = MockOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 100) - .withNonce(1).withPreExecutionHook(tokenTransferHook).withAuctionResolver(mockResolver), - input: InputToken(tokenIn, ONE * 2, ONE * 2), - outputs: outputs2 - }); - - (SignedOrder[] memory signedOrders, bytes32[] memory orderHashes) = createAndSignBatchOrders(orders); - vm.expectEmit(true, true, true, true); - emit Fill(orderHashes[0], address(fillContract), swapper, orders[0].info.nonce); - vm.expectEmit(true, true, true, true); - emit Fill(orderHashes[1], address(fillContract), swapper, orders[1].info.nonce); - - fillContract.executeBatch(signedOrders); - vm.snapshotGasLastCall("ReactorExecuteBatchMultipleOutputsDifferentTokens"); - - assertEq(tokenOut.balanceOf(swapper), totalOutputAmount1); - assertEq(tokenOut2.balanceOf(swapper), totalOutputAmount2); - assertEq(tokenIn.balanceOf(address(fillContract)), totalInputAmount); - } - - /// @dev Test executeBatch with post-execution hook - function test_executeBatchWithPostExecutionHook() public { - uint256 inputAmount = 1 ether; - uint256 outputAmount = 1 ether; - uint256 deadline = block.timestamp + 1000; - - // Create 3 orders - MockOrder[] memory orders = new MockOrder[](3); - for (uint256 i = 0; i < 3; i++) { - tokenIn.mint(address(swapper), inputAmount); - tokenOut.mint(address(fillContract), outputAmount); - tokenIn.forceApprove(swapper, address(permit2), inputAmount * (i + 1)); - - orders[i] = MockOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(deadline).withNonce(i) - .withPreExecutionHook(tokenTransferHook).withPostExecutionHook(postExecutionHook) - .withAuctionResolver(mockResolver), - input: InputToken(tokenIn, inputAmount, inputAmount), - outputs: OutputsBuilder.single(address(tokenOut), outputAmount, swapper) - }); - } - - (SignedOrder[] memory signedOrders,) = createAndSignBatchOrders(orders); - - uint256 counterBefore = postExecutionHook.postExecutionCounter(); - - fillContract.executeBatch(signedOrders); - - // Verify post-hook was called for each order - assertEq(postExecutionHook.postExecutionCounter(), counterBefore + 3); - assertEq(postExecutionHook.fillerExecutions(address(fillContract)), 3); - assertEq(postExecutionHook.swapperExecutions(swapper), 3); - } - - /// @dev Test invalid reactor error - function test_executeInvalidReactor() public { - uint256 inputAmount = 1 ether; - uint256 outputAmount = 1 ether; - uint256 deadline = block.timestamp + 1000; - - tokenIn.mint(address(swapper), inputAmount); - tokenOut.mint(address(fillContract), outputAmount); - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - - // Create order with wrong reactor address - MockOrder memory order = MockOrder({ - info: OrderInfoBuilder.init(address(0x1234)).withSwapper(swapper).withDeadline(deadline) - .withPreExecutionHook(tokenTransferHook) // Wrong reactor - .withAuctionResolver(mockResolver), - input: InputToken(tokenIn, inputAmount, inputAmount), - outputs: OutputsBuilder.single(address(tokenOut), outputAmount, swapper) - }); - - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - - vm.expectRevert(IReactor.InvalidReactor.selector); - fillContract.execute(signedOrder); - } - - /// @dev Test missing pre-execution hook error - function test_executeMissingHook() public { - uint256 inputAmount = 1 ether; - uint256 outputAmount = 1 ether; - uint256 deadline = block.timestamp + 1000; - - tokenIn.mint(address(swapper), inputAmount); - tokenOut.mint(address(fillContract), outputAmount); - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - - // Create order without a pre-execution hook - MockOrder memory order = MockOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(deadline) - .withAuctionResolver(mockResolver), - // No preExecutionHook set - input: InputToken(tokenIn, inputAmount, inputAmount), - outputs: OutputsBuilder.single(address(tokenOut), outputAmount, swapper) - }); - - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - - vm.expectRevert(IReactor.MissingPreExecutionHook.selector); - fillContract.execute(signedOrder); - } - - /// @dev Test resolver substitution attack (ResolverMismatch error) - function test_executeResolverMismatch() public { - uint256 inputAmount = 1 ether; - uint256 outputAmount = 1 ether; - uint256 deadline = block.timestamp + 1000; - - tokenIn.mint(address(swapper), inputAmount); - tokenOut.mint(address(fillContract), outputAmount); - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - - // Create order with mockResolver properly set in OrderInfo - MockOrder memory order = MockOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(deadline) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(mockResolver), - input: InputToken(tokenIn, inputAmount, inputAmount), - outputs: OutputsBuilder.single(address(tokenOut), outputAmount, swapper) - }); - - bytes memory sig = signOrder(swapperPrivateKey, address(permit2), order); - bytes memory orderData = abi.encode(order); - - MockAuctionResolver maliciousResolver = new MockAuctionResolver(); - bytes memory encodedOrder = abi.encode(address(maliciousResolver), orderData); - - SignedOrder memory signedOrder = SignedOrder(encodedOrder, sig); - - vm.expectRevert(IReactor.ResolverMismatch.selector); - fillContract.execute(signedOrder); - } - - /// @dev Test deadline passed error - function test_executeDeadlinePassed() public { - uint256 inputAmount = 1 ether; - uint256 outputAmount = 1 ether; - uint256 deadline = block.timestamp - 1; // Past deadline - - tokenIn.mint(address(swapper), inputAmount); - tokenOut.mint(address(fillContract), outputAmount); - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - - MockOrder memory order = createBasicOrder(inputAmount, outputAmount, deadline); - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - - vm.expectRevert(IReactor.DeadlinePassed.selector); - fillContract.execute(signedOrder); - } - - /// @dev Test signature replay protection - function test_executeSignatureReplay() public { - uint256 inputAmount = 1 ether; - uint256 outputAmount = 1 ether; - uint256 deadline = block.timestamp + 1000; - - tokenIn.mint(address(swapper), inputAmount * 2); - tokenOut.mint(address(fillContract), outputAmount * 2); - tokenIn.forceApprove(swapper, address(permit2), inputAmount * 2); - - MockOrder memory order = createBasicOrder(inputAmount, outputAmount, deadline); - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - - fillContract.execute(signedOrder); - - // Try to replay - should fail with InvalidNonce since permit2 tracks nonce usage - vm.expectRevert(INVALID_NONCE_SELECTOR); - fillContract.execute(signedOrder); - } - - function test_exploitMaliciousFillerResolver() public { - uint256 inputAmount = 1 ether; - uint256 outputAmount = 1 ether; - uint256 deadline = block.timestamp + 1000; - address attacker = address(0xA11A); - - tokenIn.mint(address(swapper), inputAmount); - tokenOut.mint(address(fillContract), outputAmount); - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - - MockOrder memory order = createBasicOrder(inputAmount, outputAmount, deadline); - - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - - // Attacker attempts exploit: use malicious resolver instead of expected one - assertEq(tokenOut.balanceOf(address(attacker)), 0); - vm.startPrank(attacker); - MaliciousAuctionResolver maliciousResolver = new MaliciousAuctionResolver(); - (SignedOrder memory modifiedOrder) = exploitSignedOrder(signedOrder, address(maliciousResolver)); - vm.stopPrank(); - - bytes4 invalidSignerSelector = 0x815e1d64; // InvalidSigner() - vm.expectRevert(invalidSignerSelector); - fillContract.execute(modifiedOrder); - - assertEq(tokenOut.balanceOf(address(attacker)), 0, "Attacker should not receive any tokens"); - assertEq(tokenIn.balanceOf(address(swapper)), inputAmount, "Swapper input should not be transferred"); - assertEq(tokenOut.balanceOf(address(swapper)), 0, "Swapper should not receive output yet"); - } - - /// @dev Basic execute fuzz test, checks balance before and after - function testFuzz_execute(uint128 inputAmount, uint128 outputAmount, uint256 deadline) public { - vm.assume(deadline > block.timestamp); - vm.assume(inputAmount > 0); - vm.assume(outputAmount > 0); - - // Seed both swapper and fillContract with enough tokens - tokenIn.mint(address(swapper), uint256(inputAmount)); - tokenOut.mint(address(fillContract), uint256(outputAmount)); - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - - MockOrder memory order = MockOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(deadline) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(mockResolver), - input: InputToken(tokenIn, inputAmount, inputAmount), - outputs: OutputsBuilder.single(address(tokenOut), outputAmount, swapper) - }); - - (SignedOrder memory signedOrder, bytes32 orderHash) = createAndSignOrder(order); - - ( - uint256 swapperInputBalanceStart, - uint256 fillContractInputBalanceStart, - uint256 swapperOutputBalanceStart, - uint256 fillContractOutputBalanceStart - ) = _checkpointBalances(); - - vm.expectEmit(true, true, true, true, address(reactor)); - emit Fill(orderHash, address(fillContract), swapper, order.info.nonce); - fillContract.execute(signedOrder); - - assertEq(tokenIn.balanceOf(address(swapper)), swapperInputBalanceStart - inputAmount); - assertEq(tokenIn.balanceOf(address(fillContract)), fillContractInputBalanceStart + inputAmount); - assertEq(tokenOut.balanceOf(address(swapper)), swapperOutputBalanceStart + outputAmount); - assertEq(tokenOut.balanceOf(address(fillContract)), fillContractOutputBalanceStart - outputAmount); - } - - /// @dev Fuzz test executeWithFee with protocol fees - function testFuzz_executeWithFee(uint128 inputAmount, uint128 outputAmount, uint256 deadline, uint8 feeBps) public { - vm.assume(deadline > block.timestamp); - vm.assume(feeBps <= 5); - vm.assume(inputAmount > 0); - vm.assume(outputAmount > 0); - - vm.prank(PROTOCOL_FEE_OWNER); - reactor.setProtocolFeeController(address(feeController)); - feeController.setFee(tokenIn, address(tokenOut), feeBps); - tokenIn.mint(address(swapper), uint256(inputAmount)); - tokenOut.mint(address(fillContract), uint256(outputAmount) * 100); - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - - MockOrder memory order = MockOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(deadline) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(mockResolver), - input: InputToken(tokenIn, inputAmount, inputAmount), - outputs: OutputsBuilder.single(address(tokenOut), outputAmount, swapper) - }); - - (SignedOrder memory signedOrder, bytes32 orderHash) = createAndSignOrder(order); - - ( - uint256 swapperInputBalanceStart, - uint256 fillContractInputBalanceStart, - uint256 swapperOutputBalanceStart, - uint256 fillContractOutputBalanceStart - ) = _checkpointBalances(); - - vm.expectEmit(true, true, true, true, address(reactor)); - emit Fill(orderHash, address(fillContract), swapper, order.info.nonce); - fillContract.execute(signedOrder); - - uint256 feeAmount = uint256(outputAmount) * feeBps / 10000; - assertEq(tokenIn.balanceOf(address(swapper)), swapperInputBalanceStart - inputAmount); - assertEq(tokenIn.balanceOf(address(fillContract)), fillContractInputBalanceStart + inputAmount); - assertEq(tokenOut.balanceOf(address(swapper)), swapperOutputBalanceStart + outputAmount); - assertEq(tokenOut.balanceOf(address(fillContract)), fillContractOutputBalanceStart - outputAmount - feeAmount); - assertEq(tokenOut.balanceOf(address(feeRecipient)), feeAmount); - } - - /// @dev Fuzz test for native currency output, checks balance before and after - function testFuzz_executeNativeOutput(uint128 inputAmount, uint128 outputAmount, uint256 deadline) public { - vm.assume(deadline > block.timestamp); - vm.assume(inputAmount > 0); - vm.assume(outputAmount > 0); - - tokenIn.mint(address(swapper), uint256(inputAmount)); - vm.deal(address(fillContract), uint256(outputAmount)); - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - - MockOrder memory order = MockOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(deadline) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(mockResolver), - input: InputToken(tokenIn, inputAmount, inputAmount), - outputs: OutputsBuilder.single(NATIVE, outputAmount, swapper) - }); - - (SignedOrder memory signedOrder, bytes32 orderHash) = createAndSignOrder(order); - - uint256 swapperOutputBalanceStart = address(swapper).balance; - uint256 fillContractOutputBalanceStart = address(fillContract).balance; - (uint256 swapperInputBalanceStart, uint256 fillContractInputBalanceStart,,) = _checkpointBalances(); - - vm.expectEmit(true, true, true, true, address(reactor)); - emit Fill(orderHash, address(fillContract), swapper, order.info.nonce); - fillContract.execute(signedOrder); - - assertEq(tokenIn.balanceOf(address(swapper)), swapperInputBalanceStart - inputAmount); - assertEq(tokenIn.balanceOf(address(fillContract)), fillContractInputBalanceStart + inputAmount); - assertEq(address(swapper).balance, swapperOutputBalanceStart + outputAmount); - assertEq(address(fillContract).balance, fillContractOutputBalanceStart - outputAmount); - } - - /// @dev Fuzz test preExecutionHook with random amounts - function testFuzz_executeWithPreExecutionHook(uint128 inputAmount, uint128 outputAmount, uint256 deadline) public { - vm.assume(deadline > block.timestamp); - vm.assume(inputAmount > 0); - vm.assume(outputAmount > 0); - - tokenIn.mint(address(swapper), uint256(inputAmount)); - tokenOut.mint(address(fillContract), uint256(outputAmount)); - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - - MockOrder memory order = MockOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(deadline) - .withPreExecutionHook(preExecutionHook).withAuctionResolver(mockResolver), - input: InputToken(tokenIn, inputAmount, inputAmount), - outputs: OutputsBuilder.single(address(tokenOut), outputAmount, swapper) - }); - - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - - uint256 counterBefore = preExecutionHook.preExecutionCounter(); - uint256 fillerExecutionsBefore = preExecutionHook.fillerExecutions(address(fillContract)); - - fillContract.execute(signedOrder); - - assertEq(preExecutionHook.preExecutionCounter(), counterBefore + 1); - assertEq(preExecutionHook.fillerExecutions(address(fillContract)), fillerExecutionsBefore + 1); - - assertEq(tokenIn.balanceOf(address(swapper)), 0); - assertEq(tokenIn.balanceOf(address(fillContract)), uint256(inputAmount)); - assertEq(tokenOut.balanceOf(address(swapper)), uint256(outputAmount)); - assertEq(tokenOut.balanceOf(address(fillContract)), 0); - } - - /// @dev Fuzz test with post-execution hook - function testFuzz_executeWithPostExecutionHook(uint128 inputAmount, uint128 outputAmount, uint256 deadline) public { - vm.assume(deadline > block.timestamp); - vm.assume(inputAmount > 0); - vm.assume(outputAmount > 0); - - tokenIn.mint(address(swapper), uint256(inputAmount)); - tokenOut.mint(address(fillContract), uint256(outputAmount)); - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - - MockOrder memory order = MockOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(deadline) - .withPreExecutionHook(tokenTransferHook).withPostExecutionHook(postExecutionHook) - .withAuctionResolver(mockResolver), - input: InputToken(tokenIn, inputAmount, inputAmount), - outputs: OutputsBuilder.single(address(tokenOut), outputAmount, swapper) - }); - - (SignedOrder memory signedOrder, bytes32 orderHash) = createAndSignOrder(order); - - uint256 counterBefore = postExecutionHook.postExecutionCounter(); - - fillContract.execute(signedOrder); - - // Verify post-hook was called with correct data - assertEq(postExecutionHook.postExecutionCounter(), counterBefore + 1); - assertEq(postExecutionHook.lastFiller(), address(fillContract)); - assertEq(postExecutionHook.lastSwapper(), swapper); - assertEq(postExecutionHook.lastOrderHash(), orderHash); - assertEq(postExecutionHook.lastInputAmount(), uint256(inputAmount)); - assertEq(postExecutionHook.lastOutputAmount(), uint256(outputAmount)); - - // Verify tokens transferred correctly - assertEq(tokenIn.balanceOf(address(swapper)), 0); - assertEq(tokenIn.balanceOf(address(fillContract)), uint256(inputAmount)); - assertEq(tokenOut.balanceOf(address(swapper)), uint256(outputAmount)); - assertEq(tokenOut.balanceOf(address(fillContract)), 0); - } -} diff --git a/test/v4/hooks/dca/DCAHook.t.sol b/test/v4/hooks/dca/DCAHook.t.sol deleted file mode 100644 index 55102b76..00000000 --- a/test/v4/hooks/dca/DCAHook.t.sol +++ /dev/null @@ -1,1172 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {Test} from "forge-std/Test.sol"; -import {IPermit2} from "permit2/src/interfaces/IPermit2.sol"; -import {DeployPermit2} from "../../../util/DeployPermit2.sol"; -import {DCAHookHarness} from "./DCAHookHarness.sol"; -import {IReactor} from "../../../../src/v4/interfaces/IReactor.sol"; -import {DCAExecutionState, OutputAllocation} from "../../../../src/v4/hooks/dca/DCAStructs.sol"; -import {IDCAHook} from "../../../../src/v4/interfaces/IDCAHook.sol"; - -contract DCAHookTest is Test, DeployPermit2 { - DCAHookHarness hook; - IPermit2 permit2; - IReactor constant REACTOR = IReactor(address(0x2345)); - address constant SWAPPER = address(0x1234); - uint256 constant NONCE = 0; - - // Events from IDCAHook - event IntentCancelled(bytes32 indexed intentId, address indexed swapper); - - function setUp() public { - permit2 = IPermit2(deployPermit2()); - hook = new DCAHookHarness(permit2, REACTOR); - vm.warp(1 days); - } - - // ============ computeIntentId Tests ============ - - function test_computeIntentId() public view { - // Test deterministic behavior - same inputs always produce same output - bytes32 expectedId = keccak256(abi.encodePacked(SWAPPER, NONCE)); - bytes32 actualId1 = hook.computeIntentId(SWAPPER, NONCE); - bytes32 actualId2 = hook.computeIntentId(SWAPPER, NONCE); - - assertEq(actualId1, expectedId, "Intent ID should match expected hash"); - assertEq(actualId1, actualId2, "Same inputs should produce same ID"); - - // Test encoding consistency with different values - address testSwapper = address(0xBEEF); - uint256 testNonce = 42; - - bytes32 expected2 = keccak256(abi.encodePacked(testSwapper, testNonce)); - bytes32 actual2 = hook.computeIntentId(testSwapper, testNonce); - - assertEq(actual2, expected2, "Should match abi.encodePacked encoding"); - } - - // ============ getExecutionState Tests ============ - - function test_getExecutionState_uninitialized() public view { - bytes32 intentId = hook.computeIntentId(SWAPPER, NONCE); - DCAExecutionState memory state = hook.getExecutionState(intentId); - - assertEq(state.cancelled, false, "Cancelled should be false for uninitialized state"); - assertEq(state.executedChunks, 0, "Executed chunks should be 0 for uninitialized state"); - assertEq(state.lastExecutionTime, 0, "Last execution time should be 0 for uninitialized state"); - assertEq(state.totalInputExecuted, 0, "Total input should be 0 for uninitialized state"); - assertEq(state.totalOutput, 0, "Total output should be 0 for uninitialized state"); - } - - function test_getExecutionState_afterPackedWrite() public { - bytes32 intentId = hook.computeIntentId(SWAPPER, NONCE); - uint96 expectedExecutedChunks = 42; - bool expectedCancelled = true; - - hook.__setPacked(intentId, expectedExecutedChunks, expectedCancelled); - DCAExecutionState memory state = hook.getExecutionState(intentId); - - assertEq(state.executedChunks, expectedExecutedChunks, "Should return exact executed chunks written"); - assertEq(state.cancelled, expectedCancelled, "Should return exact cancelled flag written"); - assertEq(state.lastExecutionTime, 0, "Unwritten fields remain zero"); - assertEq(state.totalInputExecuted, 0, "Unwritten fields remain zero"); - assertEq(state.totalOutput, 0, "Unwritten fields remain zero"); - } - - function test_getExecutionState_afterExecutedMetaWrite() public { - bytes32 intentId = hook.computeIntentId(SWAPPER, NONCE); - uint120 expectedLastExecution = uint120(block.timestamp); - - hook.__setExecutedMeta(intentId, expectedLastExecution); - DCAExecutionState memory state = hook.getExecutionState(intentId); - - assertEq(state.lastExecutionTime, expectedLastExecution, "Should return exact lastExecutionTime written"); - assertEq(state.executedChunks, 0, "Unwritten fields remain zero"); - assertEq(state.cancelled, false, "Unwritten fields remain false"); - assertEq(state.totalInputExecuted, 0, "Unwritten fields remain zero"); - assertEq(state.totalOutput, 0, "Unwritten fields remain zero"); - } - - function test_getExecutionState_afterTotalsWrite() public { - bytes32 intentId = hook.computeIntentId(SWAPPER, NONCE); - uint256 expectedInputExecuted = 1e18; - uint256 expectedOutput = 2000e6; - - hook.__setTotals(intentId, expectedInputExecuted, expectedOutput); - DCAExecutionState memory state = hook.getExecutionState(intentId); - - assertEq(state.totalInputExecuted, expectedInputExecuted, "Should return exact totalInputExecuted written"); - assertEq(state.totalOutput, expectedOutput, "Should return exact totalOutput written"); - assertEq(state.cancelled, false, "Unwritten fields remain false"); - assertEq(state.executedChunks, 0, "Unwritten fields remain zero"); - assertEq(state.lastExecutionTime, 0, "Unwritten fields remain zero"); - } - - function test_getExecutionState_fullStateWrite() public { - bytes32 intentId = hook.computeIntentId(SWAPPER, NONCE); - - // Write all fields - uint96 expectedExecutedChunks = 100; - bool expectedCancelled = true; - uint120 expectedLastExecution = uint120(block.timestamp - 3600); - uint256 expectedInputExecuted = 5e18; - uint256 expectedOutput = 10000e6; - - hook.__setPacked(intentId, expectedExecutedChunks, expectedCancelled); - hook.__setExecutedMeta(intentId, expectedLastExecution); - hook.__setTotals(intentId, expectedInputExecuted, expectedOutput); - - DCAExecutionState memory state = hook.getExecutionState(intentId); - - assertEq(state.cancelled, expectedCancelled, "Should return exact cancelled flag"); - assertEq(state.executedChunks, expectedExecutedChunks, "Should return exact executedChunks"); - assertEq(state.lastExecutionTime, expectedLastExecution, "Should return exact lastExecutionTime"); - assertEq(state.totalInputExecuted, expectedInputExecuted, "Should return exact totalInputExecuted"); - assertEq(state.totalOutput, expectedOutput, "Should return exact totalOutput"); - } - - function testFuzz_getExecutionState_precision( - uint96 executedChunks, - bool cancelled, - uint120 lastExec, - uint128 inputExecuted, - uint128 output - ) public { - bytes32 intentId = hook.computeIntentId(SWAPPER, NONCE); - - hook.__setPacked(intentId, executedChunks, cancelled); - hook.__setExecutedMeta(intentId, lastExec); - hook.__setTotals(intentId, inputExecuted, output); - - DCAExecutionState memory state = hook.getExecutionState(intentId); - - assertEq(state.cancelled, cancelled, "Fuzz: cancelled precision"); - assertEq(state.executedChunks, executedChunks, "Fuzz: executedChunks precision"); - assertEq(state.lastExecutionTime, lastExec, "Fuzz: lastExecutionTime precision"); - assertEq(state.totalInputExecuted, inputExecuted, "Fuzz: totalInputExecuted precision"); - assertEq(state.totalOutput, output, "Fuzz: totalOutput precision"); - } - - // ============ getNextNonce Tests ============ - - function test_getNextNonce_default() public view { - bytes32 intentId = hook.computeIntentId(SWAPPER, NONCE); - uint96 nextNonce = hook.getNextNonce(intentId); - // This should return the executed chunks, which is 0 for an uninitialized intent - assertEq(nextNonce, 0, "Uninitialized intent should have nextNonce of 0"); - } - - function test_getNextNonce_afterSet() public { - bytes32 intentId = hook.computeIntentId(SWAPPER, NONCE); - uint96 expectedExecutedChunks = 5; - - hook.__setPacked(intentId, expectedExecutedChunks, false); - uint96 nextNonce = hook.getNextNonce(intentId); - - assertEq(nextNonce, expectedExecutedChunks, "Should return the executed chunks"); - } - - function test_getNextNonce_nearMaxValue() public { - bytes32 intentId = hook.computeIntentId(SWAPPER, NONCE); - uint96 nearMax = type(uint96).max - 1; - - hook.__setPacked(intentId, nearMax, false); - uint96 nextNonce = hook.getNextNonce(intentId); - - assertEq(nextNonce, nearMax, "Should handle values near uint96 max without overflow"); - } - - function test_getNextNonce_maxValue() public { - bytes32 intentId = hook.computeIntentId(SWAPPER, NONCE); - uint96 maxValue = type(uint96).max; - - hook.__setPacked(intentId, maxValue, false); - uint96 nextNonce = hook.getNextNonce(intentId); - - assertEq(nextNonce, maxValue, "Should handle uint96 max value"); - } - - function test_getNextNonce_isolatedFromOtherFields() public { - bytes32 intentId = hook.computeIntentId(SWAPPER, NONCE); - uint96 expectedExecutedChunks = 42; - - // Set nonce with cancelled=true and set other fields - hook.__setPacked(intentId, expectedExecutedChunks, true); - hook.__setExecutedMeta(intentId, uint120(block.timestamp)); - hook.__setTotals(intentId, 1e18, 2000e6); - - uint96 nextNonce = hook.getNextNonce(intentId); - - assertEq(nextNonce, expectedExecutedChunks, "nextNonce should be isolated from other state modifications"); - } - - function testFuzz_getNextNonce_precision(uint96 expectedExecutedChunks) public { - bytes32 intentId = hook.computeIntentId(SWAPPER, NONCE); - - hook.__setPacked(intentId, expectedExecutedChunks, false); - uint96 retrievedNonce = hook.getNextNonce(intentId); - - assertEq(retrievedNonce, expectedExecutedChunks, "Should preserve exact uint96 value through storage"); - } - - // TODO: overflow nonce test when complete flow is implemented - - // ============ getIntentStatistics Tests ============ - - function test_getIntentStatistics_default() public view { - bytes32 intentId = hook.computeIntentId(SWAPPER, NONCE); - - (uint256 totalChunks, uint256 totalInput, uint256 totalOutput, uint256 lastExecutionTime) = - hook.getIntentStatistics(intentId); - - assertEq(totalChunks, 0, "Default totalChunks should be 0"); - assertEq(totalInput, 0, "Default totalInput should be 0"); - assertEq(totalOutput, 0, "Default totalOutput should be 0"); - assertEq(lastExecutionTime, 0, "Default lastExecutionTime should be 0"); - } - - function test_getIntentStatistics_populatedValues() public { - bytes32 intentId = hook.computeIntentId(SWAPPER, NONCE); - - uint128 expectedChunks = 10; - bool expectedCancelled = true; - uint120 expectedLastExec = uint120(block.timestamp - 3600); - uint256 expectedInput = 5e18; // 5 tokens with 18 decimals - uint256 expectedOutput = 10000e6; // 10000 tokens with 6 decimals - - hook.__setPacked(intentId, expectedChunks, expectedCancelled); - hook.__setExecutedMeta(intentId, expectedLastExec); - hook.__setTotals(intentId, expectedInput, expectedOutput); - - (uint256 totalChunks, uint256 totalInput, uint256 totalOutput, uint256 lastExecutionTime) = - hook.getIntentStatistics(intentId); - - assertEq(totalChunks, expectedChunks, "Should return exact executedChunks"); - assertEq(totalInput, expectedInput, "Should return exact totalInputExecuted"); - assertEq(totalOutput, expectedOutput, "Should return exact totalOutput"); - assertEq(lastExecutionTime, expectedLastExec, "Should return exact lastExecutionTime"); - } - - function test_getIntentStatistics_zeroOutput() public { - bytes32 intentId = hook.computeIntentId(SWAPPER, NONCE); - - // Set input but no output - hook.__setTotals(intentId, 1000e18, 0); - - (, uint256 totalInput, uint256 totalOutput,) = hook.getIntentStatistics(intentId); - - assertEq(totalInput, 1000e18, "Should return totalInput even with zero output"); - assertEq(totalOutput, 0, "Should return zero output"); - } - - function test_getIntentStatistics_bothZero() public { - bytes32 intentId = hook.computeIntentId(SWAPPER, NONCE); - - // Both input and output are zero - hook.__setTotals(intentId, 0, 0); - - (, uint256 totalInput, uint256 totalOutput,) = hook.getIntentStatistics(intentId); - - assertEq(totalInput, 0, "Should return zero input"); - assertEq(totalOutput, 0, "Should return zero output"); - } - - function testFuzz_getIntentStatistics_allFields( - uint128 chunks, - bool cancelled, - uint120 lastExec, - uint128 inputAmount, - uint128 outputAmount - ) public { - bytes32 intentId = hook.computeIntentId(SWAPPER, NONCE); - - hook.__setPacked(intentId, chunks, cancelled); - hook.__setExecutedMeta(intentId, lastExec); - hook.__setTotals(intentId, inputAmount, outputAmount); - - (uint256 totalChunks, uint256 totalInput, uint256 totalOutput, uint256 lastExecutionTime) = - hook.getIntentStatistics(intentId); - - assertEq(totalChunks, chunks, "Fuzz: totalChunks precision"); - assertEq(cancelled, cancelled, "Fuzz: cancelled precision"); - assertEq(totalInput, inputAmount, "Fuzz: totalInput precision"); - assertEq(totalOutput, outputAmount, "Fuzz: totalOutput precision"); - assertEq(lastExecutionTime, lastExec, "Fuzz: lastExecutionTime precision"); - } - - function testFuzz_computeIntentId_determinism(address swapper, uint256 nonce) public view { - // Fuzz test: verify abi.encodePacked equality for any inputs - bytes32 expectedId = keccak256(abi.encodePacked(swapper, nonce)); - bytes32 actualId = hook.computeIntentId(swapper, nonce); - assertEq(actualId, expectedId, "Intent ID should match abi.encodePacked for any inputs"); - - // Verify determinism - calling again should produce same result - bytes32 actualId2 = hook.computeIntentId(swapper, nonce); - assertEq(actualId, actualId2, "Should be deterministic for fuzzed inputs"); - } - - // ============ isIntentActive Tests ============ - - function test_isIntentActive_uninitialized_noConstraints() public view { - bytes32 intentId = hook.computeIntentId(SWAPPER, NONCE); - - // No deadline or maxPeriod => always true for uninitialized - assertTrue(hook.isIntentActive(intentId, 0, 0), "Uninitialized with no constraints should be active"); - } - - function test_isIntentActive_uninitialized_deadlineConstraints() public view { - bytes32 intentId = hook.computeIntentId(SWAPPER, NONCE); - - // Future deadline => true - uint256 futureDeadline = block.timestamp + 1000; - assertTrue(hook.isIntentActive(intentId, 0, futureDeadline), "Should be active before deadline"); - - // Past deadline => false - uint256 pastDeadline = block.timestamp - 1; - assertFalse(hook.isIntentActive(intentId, 0, pastDeadline), "Should be inactive after deadline"); - - // Exactly at deadline - assertTrue(hook.isIntentActive(intentId, 0, block.timestamp), "Should be active at exact deadline"); - } - - function test_isIntentActive_uninitialized_maxPeriodIgnored() public view { - bytes32 intentId = hook.computeIntentId(SWAPPER, NONCE); - - // maxPeriod should be ignored when executedChunks == 0 - assertTrue(hook.isIntentActive(intentId, 1, 0), "maxPeriod=1 ignored for uninitialized"); - assertTrue(hook.isIntentActive(intentId, 3600, 0), "maxPeriod=3600 ignored for uninitialized"); - assertTrue(hook.isIntentActive(intentId, type(uint256).max, 0), "maxPeriod=max ignored for uninitialized"); - } - - function test_isIntentActive_withExecutions_withinMaxPeriod() public { - bytes32 intentId = hook.computeIntentId(SWAPPER, NONCE); - - uint120 lastExecTime = uint120(block.timestamp - 3600); // 1 hour ago - hook.__setPacked(intentId, 1, false); // Set executedChunks to 1 so that period checks are performed - hook.__setExecutedMeta(intentId, lastExecTime); - - // Within maxPeriod window => true - assertTrue(hook.isIntentActive(intentId, 3601, 0), "Active when within maxPeriod by 1 second"); - assertTrue(hook.isIntentActive(intentId, 7200, 0), "Active when well within maxPeriod"); - } - - function test_isIntentActive_withExecutions_overMaxPeriod() public { - bytes32 intentId = hook.computeIntentId(SWAPPER, NONCE); - - uint120 lastExecTime = uint120(block.timestamp - 3600); // 1 hour ago - hook.__setPacked(intentId, 1, false); // Set executedChunks to 1 so that period checks are performed - hook.__setExecutedMeta(intentId, lastExecTime); - - // Over maxPeriod window => false - assertFalse(hook.isIntentActive(intentId, 3599, 0), "Inactive when over maxPeriod by 1 second"); - assertFalse(hook.isIntentActive(intentId, 1800, 0), "Inactive when well over maxPeriod"); - assertFalse(hook.isIntentActive(intentId, 1, 0), "Inactive when far over maxPeriod"); - } - - function test_isIntentActive_withExecutions_exactMaxPeriod() public { - bytes32 intentId = hook.computeIntentId(SWAPPER, NONCE); - - uint120 lastExecTime = uint120(block.timestamp - 3600); // Exactly 1 hour ago - hook.__setPacked(intentId, 1, false); // Set executedChunks to 1 so that period checks are performed - hook.__setExecutedMeta(intentId, lastExecTime); - - // Exactly at maxPeriod boundary - assertTrue(hook.isIntentActive(intentId, 3600, 0), "Active at exact maxPeriod boundary"); - } - - function test_isIntentActive_deadlineDominance() public { - bytes32 intentId = hook.computeIntentId(SWAPPER, NONCE); - - uint120 lastExecTime = uint120(block.timestamp - 100); // Recent execution - hook.__setExecutedMeta(intentId, lastExecTime); - - // Past deadline => false regardless of valid maxPeriod - uint120 pastDeadline = uint120(block.timestamp - 1); - assertFalse(hook.isIntentActive(intentId, 7200, pastDeadline), "Deadline dominates: past deadline always false"); - assertFalse(hook.isIntentActive(intentId, 0, pastDeadline), "Past deadline with maxPeriod=0 still false"); - } - - function test_isIntentActive_cancelledDominance() public { - bytes32 intentId = hook.computeIntentId(SWAPPER, NONCE); - - // Set cancelled flag - hook.__setPacked(intentId, 0, true); - - // Cancelled => always false regardless of other conditions - assertFalse(hook.isIntentActive(intentId, 0, 0), "Cancelled with no constraints"); - assertFalse(hook.isIntentActive(intentId, 0, block.timestamp + 1000), "Cancelled with future deadline"); - assertFalse(hook.isIntentActive(intentId, type(uint256).max, type(uint256).max), "Cancelled with max values"); - - // Even with executions and valid periods - hook.__setExecutedMeta(intentId, uint120(block.timestamp - 100)); - assertFalse( - hook.isIntentActive(intentId, 7200, block.timestamp + 1000), "Cancelled dominates all valid conditions" - ); - } - - function test_isIntentActive_sentinel_maxPeriodZero() public { - bytes32 intentId = hook.computeIntentId(SWAPPER, NONCE); - - // Set execution far in the past - uint120 veryOldExecution = 1; - vm.warp(1000000); - hook.__setPacked(intentId, 1, false); // Set executedChunks to 1 so that period checks are performed - hook.__setExecutedMeta(intentId, veryOldExecution); - - // maxPeriod = 0 => no upper bound check (sentinel value) - assertTrue(hook.isIntentActive(intentId, 0, 0), "maxPeriod=0 disables period check"); - assertTrue(hook.isIntentActive(intentId, 0, block.timestamp + 1000), "maxPeriod=0 with future deadline"); - } - - function test_isIntentActive_sentinel_deadlineZero() public { - bytes32 intentId = hook.computeIntentId(SWAPPER, NONCE); - - // deadline = 0 => no deadline check (sentinel value) - assertTrue(hook.isIntentActive(intentId, 0, 0), "deadline=0 disables deadline check"); - - // With executions - hook.__setPacked(intentId, 1, false); // Set executedChunks to 1 so that period checks are performed - hook.__setExecutedMeta(intentId, uint120(block.timestamp - 100)); - assertTrue(hook.isIntentActive(intentId, 7200, 0), "deadline=0 with valid maxPeriod"); - } - - function test_isIntentActive_priorityOrder() public { - bytes32 intentId = hook.computeIntentId(SWAPPER, NONCE); - - // Test check priority: cancelled > deadline > executedChunks > maxPeriod - - // 1. Cancelled overrides everything - hook.__setPacked(intentId, 0, true); - assertFalse(hook.isIntentActive(intentId, type(uint256).max, type(uint256).max), "Cancelled checked first"); - - // 2. Reset and test deadline priority - hook.__setPacked(intentId, 0, false); - assertFalse(hook.isIntentActive(intentId, 0, block.timestamp - 1), "Deadline checked second"); - - // 3. With no executions, returns true even with maxPeriod - assertTrue(hook.isIntentActive(intentId, 1, block.timestamp + 1000), "No executions returns true"); - - // 4. With executions, maxPeriod is checked - hook.__setPacked(intentId, 1, false); // Set executedChunks to 1 so that period checks are performed - hook.__setExecutedMeta(intentId, uint120(block.timestamp - 3600)); - assertFalse(hook.isIntentActive(intentId, 1800, block.timestamp + 1000), "maxPeriod checked last"); - } - - function testFuzz_isIntentActive_boundaries( - uint128 timeSinceLastExec, - uint128 maxPeriod, - uint128 timeUntilDeadline, - bool cancelled, - bool hasExecutions - ) public { - vm.assume(timeSinceLastExec < block.timestamp); - bytes32 intentId = hook.computeIntentId(SWAPPER, NONCE); - - // Setup state - hook.__setPacked(intentId, 0, cancelled); - if (hasExecutions) { - hook.__setPacked(intentId, 1, cancelled); // Set executedChunks to 1 - hook.__setExecutedMeta(intentId, uint120(block.timestamp - timeSinceLastExec)); - } - - uint256 deadline = timeUntilDeadline == 0 ? 0 : block.timestamp + timeUntilDeadline; - - bool result = hook.isIntentActive(intentId, maxPeriod, deadline); - - // Verify logic - if (cancelled) { - assertFalse(result, "Fuzz: cancelled always false"); - } else if (deadline != 0 && block.timestamp > deadline) { - assertFalse(result, "Fuzz: past deadline always false"); - } else if (!hasExecutions) { - assertTrue(result, "Fuzz: no executions always true (if not cancelled/past deadline)"); - } else if (maxPeriod != 0 && timeSinceLastExec > maxPeriod) { - assertFalse(result, "Fuzz: over maxPeriod false"); - } else { - assertTrue(result, "Fuzz: should be active"); - } - } - - // ============ cancelIntent Tests ============ - - function test_cancelIntent_success() public { - uint256 nonce = 42; - bytes32 expectedIntentId = hook.computeIntentId(SWAPPER, nonce); - - // Setup: SWAPPER calls cancelIntent - vm.prank(SWAPPER); - vm.expectEmit(true, true, false, true); - emit IntentCancelled(expectedIntentId, SWAPPER); - hook.cancelIntent(nonce); - - // Verify state changed - DCAExecutionState memory state = hook.getExecutionState(expectedIntentId); - assertTrue(state.cancelled, "Intent should be marked as cancelled"); - - // Verify intent is inactive - assertFalse(hook.isIntentActive(expectedIntentId, 0, 0), "Cancelled intent should be inactive"); - } - - function test_cancelIntent_onlyMsgSender() public { - uint256 nonce = 42; - address otherUser = address(0x9999); - - // Attempt to cancel another user's intent fails - bytes32 swapperIntentId = hook.computeIntentId(SWAPPER, nonce); - bytes32 otherIntentId = hook.computeIntentId(otherUser, nonce); - - // otherUser cannot cancel SWAPPER's intent (different intentId computed) - vm.prank(otherUser); - hook.cancelIntent(nonce); // This cancels otherUser's intent, not SWAPPER's - - // Verify SWAPPER's intent is still active - DCAExecutionState memory swapperState = hook.getExecutionState(swapperIntentId); - assertFalse(swapperState.cancelled, "SWAPPER's intent should not be cancelled by other user"); - - // Verify otherUser's intent is cancelled - DCAExecutionState memory otherState = hook.getExecutionState(otherIntentId); - assertTrue(otherState.cancelled, "Other user's intent should be cancelled"); - } - - function test_cancelIntent_idempotent() public { - uint256 nonce = 42; - bytes32 expectedIntentId = hook.computeIntentId(SWAPPER, nonce); - - // First cancel - vm.startPrank(SWAPPER); - vm.expectEmit(true, true, false, true); - emit IntentCancelled(expectedIntentId, SWAPPER); - hook.cancelIntent(nonce); - - // Verify cancelled - DCAExecutionState memory state1 = hook.getExecutionState(expectedIntentId); - assertTrue(state1.cancelled, "Should be cancelled after first call"); - - // Second cancel - should revert per the implementation - vm.expectRevert(abi.encodeWithSelector(IDCAHook.IntentAlreadyCancelled.selector, expectedIntentId)); - hook.cancelIntent(nonce); - - // State remains unchanged - DCAExecutionState memory state2 = hook.getExecutionState(expectedIntentId); - assertTrue(state2.cancelled, "Should remain cancelled"); - vm.stopPrank(); - } - - function test_cancelIntent_differentNonces() public { - uint256 nonce1 = 42; - uint256 nonce2 = 43; - bytes32 intentId1 = hook.computeIntentId(SWAPPER, nonce1); - bytes32 intentId2 = hook.computeIntentId(SWAPPER, nonce2); - - // Cancel only first intent - vm.prank(SWAPPER); - hook.cancelIntent(nonce1); - - // Verify first is cancelled, second is not - DCAExecutionState memory state1 = hook.getExecutionState(intentId1); - DCAExecutionState memory state2 = hook.getExecutionState(intentId2); - - assertTrue(state1.cancelled, "First intent should be cancelled"); - assertFalse(state2.cancelled, "Second intent should not be cancelled"); - } - - function test_cancelIntent_withExistingState() public { - uint256 nonce = 42; - bytes32 intentId = hook.computeIntentId(SWAPPER, nonce); - - // Setup existing state - hook.__setPacked(intentId, 5, false); - hook.__setExecutedMeta(intentId, uint120(block.timestamp - 100)); - hook.__setTotals(intentId, 1e18, 2000e6); - - // Verify state before cancel - DCAExecutionState memory stateBefore = hook.getExecutionState(intentId); - assertEq(stateBefore.executedChunks, 5, "executedChunks should be set"); - assertFalse(stateBefore.cancelled, "Should not be cancelled yet"); - - // Cancel - vm.prank(SWAPPER); - hook.cancelIntent(nonce); - - // Verify cancelled but other state preserved - DCAExecutionState memory stateAfter = hook.getExecutionState(intentId); - assertTrue(stateAfter.cancelled, "Should be cancelled"); - assertEq(stateAfter.executedChunks, 5, "executedChunks should be preserved"); - assertEq(stateAfter.totalInputExecuted, 1e18, "totalInputExecuted should be preserved"); - assertEq(stateAfter.totalOutput, 2000e6, "totalOutput should be preserved"); - } - - function test_cancelIntent_emitsCorrectEvent() public { - uint256 nonce = 123; - bytes32 expectedIntentId = hook.computeIntentId(SWAPPER, nonce); - - // Expect exact event parameters - vm.prank(SWAPPER); - vm.expectEmit(true, true, false, true); - emit IntentCancelled(expectedIntentId, SWAPPER); - hook.cancelIntent(nonce); - } - - function testFuzz_cancelIntent_variousNonces(uint256 nonce) public { - bytes32 intentId = hook.computeIntentId(SWAPPER, nonce); - - // Cancel with fuzzed nonce - vm.prank(SWAPPER); - vm.expectEmit(true, true, false, true); - emit IntentCancelled(intentId, SWAPPER); - hook.cancelIntent(nonce); - - // Verify cancelled - DCAExecutionState memory state = hook.getExecutionState(intentId); - assertTrue(state.cancelled, "Fuzz: intent should be cancelled"); - - // Verify idempotent revert - vm.prank(SWAPPER); - vm.expectRevert(abi.encodeWithSelector(IDCAHook.IntentAlreadyCancelled.selector, intentId)); - hook.cancelIntent(nonce); - } - - function testFuzz_cancelIntent_differentSwappers(address swapper1, address swapper2, uint256 nonce) public { - vm.assume(swapper1 != swapper2); - vm.assume(swapper1 != address(0)); - vm.assume(swapper2 != address(0)); - - bytes32 intentId1 = hook.computeIntentId(swapper1, nonce); - bytes32 intentId2 = hook.computeIntentId(swapper2, nonce); - - // swapper1 cancels their intent - vm.prank(swapper1); - hook.cancelIntent(nonce); - - // Only swapper1's intent is cancelled - DCAExecutionState memory state1 = hook.getExecutionState(intentId1); - DCAExecutionState memory state2 = hook.getExecutionState(intentId2); - - assertTrue(state1.cancelled, "Fuzz: swapper1's intent should be cancelled"); - assertFalse(state2.cancelled, "Fuzz: swapper2's intent should not be cancelled"); - } - - // ============ cancelIntents (batch) Tests ============ - - function test_cancelIntents_emptyArray() public { - uint256[] memory nonces = new uint256[](0); - - // Empty array should succeed without doing anything - vm.prank(SWAPPER); - hook.cancelIntents(nonces); - - // No state changes - bytes32 intentId = hook.computeIntentId(SWAPPER, 0); - DCAExecutionState memory state = hook.getExecutionState(intentId); - assertFalse(state.cancelled, "Should not cancel any intents"); - } - - function test_cancelIntents_singleIntent() public { - uint256[] memory nonces = new uint256[](1); - nonces[0] = 42; - - bytes32 intentId = hook.computeIntentId(SWAPPER, 42); - - // Cancel single intent via batch - vm.prank(SWAPPER); - vm.expectEmit(true, true, false, true); - emit IntentCancelled(intentId, SWAPPER); - hook.cancelIntents(nonces); - - // Verify cancelled - DCAExecutionState memory state = hook.getExecutionState(intentId); - assertTrue(state.cancelled, "Intent should be cancelled"); - } - - function test_cancelIntents_multipleIntents() public { - uint256[] memory nonces = new uint256[](3); - nonces[0] = 10; - nonces[1] = 20; - nonces[2] = 30; - - bytes32 intentId1 = hook.computeIntentId(SWAPPER, 10); - bytes32 intentId2 = hook.computeIntentId(SWAPPER, 20); - bytes32 intentId3 = hook.computeIntentId(SWAPPER, 30); - - // Expect events for all three - vm.prank(SWAPPER); - vm.expectEmit(true, true, false, true); - emit IntentCancelled(intentId1, SWAPPER); - vm.expectEmit(true, true, false, true); - emit IntentCancelled(intentId2, SWAPPER); - vm.expectEmit(true, true, false, true); - emit IntentCancelled(intentId3, SWAPPER); - - hook.cancelIntents(nonces); - - // Verify all cancelled - assertTrue(hook.getExecutionState(intentId1).cancelled, "Intent 1 should be cancelled"); - assertTrue(hook.getExecutionState(intentId2).cancelled, "Intent 2 should be cancelled"); - assertTrue(hook.getExecutionState(intentId3).cancelled, "Intent 3 should be cancelled"); - } - - function test_cancelIntents_partialRepeats_revertsAll() public { - uint256[] memory nonces = new uint256[](5); - nonces[0] = 10; - nonces[1] = 20; - nonces[2] = 10; // Repeat - will cause revert - nonces[3] = 30; - nonces[4] = 20; // Another repeat - - bytes32 intentId1 = hook.computeIntentId(SWAPPER, 10); - bytes32 intentId2 = hook.computeIntentId(SWAPPER, 20); - bytes32 intentId3 = hook.computeIntentId(SWAPPER, 30); - - // Entire transaction reverts on duplicate (first duplicate is at index 2, which is nonce 10) - vm.prank(SWAPPER); - vm.expectRevert(abi.encodeWithSelector(IDCAHook.IntentAlreadyCancelled.selector, intentId1)); - hook.cancelIntents(nonces); - - // Nothing was cancelled - transaction reverted - assertFalse(hook.getExecutionState(intentId1).cancelled, "Intent 1 not cancelled due to revert"); - assertFalse(hook.getExecutionState(intentId2).cancelled, "Intent 2 not cancelled due to revert"); - assertFalse(hook.getExecutionState(intentId3).cancelled, "Intent 3 not cancelled due to revert"); - } - - function test_cancelIntents_allRepeats_revertsAll() public { - uint256[] memory nonces = new uint256[](3); - nonces[0] = 42; - nonces[1] = 42; // Repeat - will cause revert - nonces[2] = 42; - - bytes32 intentId = hook.computeIntentId(SWAPPER, 42); - - // Transaction reverts on first duplicate - vm.prank(SWAPPER); - vm.expectRevert(abi.encodeWithSelector(IDCAHook.IntentAlreadyCancelled.selector, intentId)); - hook.cancelIntents(nonces); - - // Nothing was cancelled - transaction reverted - assertFalse(hook.getExecutionState(intentId).cancelled, "Intent not cancelled due to revert"); - } - - function test_cancelIntents_preCancelledInBatch_revertsAll() public { - uint256[] memory nonces = new uint256[](3); - nonces[0] = 100; - nonces[1] = 101; - nonces[2] = 102; - - bytes32 intentId1 = hook.computeIntentId(SWAPPER, 100); - bytes32 intentId2 = hook.computeIntentId(SWAPPER, 101); - bytes32 intentId3 = hook.computeIntentId(SWAPPER, 102); - - // Pre-cancel the middle one - vm.prank(SWAPPER); - hook.cancelIntent(101); - assertTrue(hook.getExecutionState(intentId2).cancelled, "Intent 2 pre-cancelled"); - - // Try to cancel all three (should fail on middle) - vm.prank(SWAPPER); - vm.expectRevert(abi.encodeWithSelector(IDCAHook.IntentAlreadyCancelled.selector, intentId2)); - hook.cancelIntents(nonces); - - // First and third remain uncancelled due to revert - assertFalse(hook.getExecutionState(intentId1).cancelled, "Intent 1 not cancelled due to revert"); - assertTrue(hook.getExecutionState(intentId2).cancelled, "Intent 2 remains cancelled from before"); - assertFalse(hook.getExecutionState(intentId3).cancelled, "Intent 3 not cancelled due to revert"); - } - - function test_cancelIntents_onlyMsgSender() public { - uint256[] memory nonces = new uint256[](2); - nonces[0] = 50; - nonces[1] = 51; - - address otherUser = address(0x9999); - - bytes32 swapperIntentId1 = hook.computeIntentId(SWAPPER, 50); - bytes32 swapperIntentId2 = hook.computeIntentId(SWAPPER, 51); - bytes32 otherIntentId1 = hook.computeIntentId(otherUser, 50); - bytes32 otherIntentId2 = hook.computeIntentId(otherUser, 51); - - // Other user cancels their own intents (not SWAPPER's) - vm.prank(otherUser); - hook.cancelIntents(nonces); - - // SWAPPER's intents remain active - assertFalse(hook.getExecutionState(swapperIntentId1).cancelled, "SWAPPER intent 1 should not be cancelled"); - assertFalse(hook.getExecutionState(swapperIntentId2).cancelled, "SWAPPER intent 2 should not be cancelled"); - - // Other user's intents are cancelled - assertTrue(hook.getExecutionState(otherIntentId1).cancelled, "Other intent 1 should be cancelled"); - assertTrue(hook.getExecutionState(otherIntentId2).cancelled, "Other intent 2 should be cancelled"); - } - - function test_cancelIntents_largeArray() public { - uint256 count = 100; - uint256[] memory nonces = new uint256[](count); - - // Fill array with unique nonces - for (uint256 i = 0; i < count; i++) { - nonces[i] = i + 1000; - } - - // Cancel all - vm.prank(SWAPPER); - hook.cancelIntents(nonces); - - // Verify sampling (first, middle, last) - bytes32 firstId = hook.computeIntentId(SWAPPER, 1000); - bytes32 middleId = hook.computeIntentId(SWAPPER, 1050); - bytes32 lastId = hook.computeIntentId(SWAPPER, 1099); - - assertTrue(hook.getExecutionState(firstId).cancelled, "First intent should be cancelled"); - assertTrue(hook.getExecutionState(middleId).cancelled, "Middle intent should be cancelled"); - assertTrue(hook.getExecutionState(lastId).cancelled, "Last intent should be cancelled"); - } - - function testFuzz_cancelIntents_variousSizes(uint8 size) public { - vm.assume(size > 0 && size <= 20); // Reasonable bounds for fuzzing - - uint256[] memory nonces = new uint256[](size); - for (uint256 i = 0; i < size; i++) { - nonces[i] = i + 5000; // Offset to avoid collision with other tests - } - - // Cancel all - vm.prank(SWAPPER); - hook.cancelIntents(nonces); - - // Verify all cancelled - for (uint256 i = 0; i < size; i++) { - bytes32 intentId = hook.computeIntentId(SWAPPER, nonces[i]); - assertTrue(hook.getExecutionState(intentId).cancelled, "Fuzz: all intents should be cancelled"); - } - } - - function testFuzz_cancelIntents_withRepeats(uint256 nonce1, uint256 nonce2) public { - vm.assume(nonce1 != nonce2); - - uint256[] memory nonces = new uint256[](4); - nonces[0] = nonce1; - nonces[1] = nonce2; - nonces[2] = nonce1; // Repeat - nonces[3] = nonce2; // Would repeat but not reached - - bytes32 intentId1 = hook.computeIntentId(SWAPPER, nonce1); - bytes32 intentId2 = hook.computeIntentId(SWAPPER, nonce2); - - // Should revert on first repeat - nothing gets cancelled - vm.prank(SWAPPER); - vm.expectRevert(abi.encodeWithSelector(IDCAHook.IntentAlreadyCancelled.selector, intentId1)); - hook.cancelIntents(nonces); - - // Nothing cancelled due to revert - assertFalse(hook.getExecutionState(intentId1).cancelled, "Fuzz: intent1 not cancelled due to revert"); - assertFalse(hook.getExecutionState(intentId2).cancelled, "Fuzz: intent2 not cancelled due to revert"); - } - - function test_getNextNonce() public { - bytes32 intentId = hook.computeIntentId(SWAPPER, NONCE); - - // Initially should be 0 - assertEq(hook.getNextNonce(intentId), 0, "Initial nextNonce should be 0"); - - // Set nextNonce to 5 via harness - hook.__setPacked(intentId, 5, false); - - assertEq(hook.getNextNonce(intentId), 5, "Should return stored nextNonce"); - } - - function test_getIntentStatistics_uninitialized() public view { - bytes32 intentId = hook.computeIntentId(SWAPPER, NONCE); - - (uint256 totalChunks, uint256 totalInput, uint256 totalOutput, uint256 lastExecutionTime) = - hook.getIntentStatistics(intentId); - - assertEq(totalChunks, 0, "Uninitialized chunks should be 0"); - assertEq(totalInput, 0, "Uninitialized input should be 0"); - assertEq(totalOutput, 0, "Uninitialized output should be 0"); - assertEq(lastExecutionTime, 0, "Uninitialized execution time should be 0"); - } - - function test_getIntentStatistics_withExecutions() public { - bytes32 intentId = hook.computeIntentId(SWAPPER, NONCE); - - // Set up state via harness - uint120 execTime = 1234567890; - hook.__setPacked(intentId, 5, false); - hook.__setExecutedMeta(intentId, execTime); - hook.__setTotals(intentId, 1000 ether, 2000 ether); - - (uint256 totalChunks, uint256 totalInput, uint256 totalOutput, uint256 lastExecutionTime) = - hook.getIntentStatistics(intentId); - - assertEq(totalChunks, 5, "Should return correct chunks"); - assertEq(totalInput, 1000 ether, "Should return correct input"); - assertEq(totalOutput, 2000 ether, "Should return correct output"); - assertEq(lastExecutionTime, execTime, "Should return correct execution time"); - } - - address constant OTHER = address(0xBEEF); - - function test_cancelIntent_setsCancelledAndEmits() public { - uint256 nonce = 7; - bytes32 intentId = hook.computeIntentId(SWAPPER, nonce); - - vm.expectEmit(true, true, false, true); - emit IntentCancelled(intentId, SWAPPER); - - vm.prank(SWAPPER); - hook.cancelIntent(nonce); - - DCAExecutionState memory s = hook.getExecutionState(intentId); - assertTrue(s.cancelled, "should be cancelled"); - } - - function test_cancelIntent_revertsWhenAlreadyCancelled() public { - uint256 nonce = 8; - bytes32 intentId = hook.computeIntentId(SWAPPER, nonce); - - vm.prank(SWAPPER); - hook.cancelIntent(nonce); - - vm.prank(SWAPPER); - vm.expectRevert(abi.encodeWithSelector(IDCAHook.IntentAlreadyCancelled.selector, intentId)); - hook.cancelIntent(nonce); - } - - function test_cancelIntents_batchSuccess() public { - uint256[] memory nonces = new uint256[](3); - nonces[0] = 1; - nonces[1] = 2; - nonces[2] = 3; - - vm.prank(SWAPPER); - hook.cancelIntents(nonces); - - for (uint256 i = 0; i < nonces.length; i++) { - bytes32 id = hook.computeIntentId(SWAPPER, nonces[i]); - assertTrue(hook.getExecutionState(id).cancelled, "batch: each should be cancelled"); - } - } - - function test_cancelIntents_duplicateInBatch_revertsAtomically() public { - // Pre-state: nothing cancelled - uint256[] memory nonces = new uint256[](3); - nonces[0] = 11; - nonces[1] = 11; - nonces[2] = 12; - - bytes32 intentId = hook.computeIntentId(SWAPPER, 11); - - vm.prank(SWAPPER); - vm.expectRevert(abi.encodeWithSelector(IDCAHook.IntentAlreadyCancelled.selector, intentId)); - hook.cancelIntents(nonces); - - // Atomicity: no partial writes persisted - for (uint256 i = 0; i < nonces.length; i++) { - bytes32 id = hook.computeIntentId(SWAPPER, nonces[i]); - assertFalse(hook.getExecutionState(id).cancelled, "no state changes after revert"); - } - } - - function test_cancelIntents_revertsIfOnePreCancelled_doesNotAffectOthers() public { - // Pre-cancel one nonce in a separate tx - vm.prank(SWAPPER); - hook.cancelIntent(21); - - bytes32 intentId = hook.computeIntentId(SWAPPER, 21); - - uint256[] memory nonces = new uint256[](3); - nonces[0] = 21; - nonces[1] = 22; - nonces[2] = 23; - - vm.prank(SWAPPER); - vm.expectRevert(abi.encodeWithSelector(IDCAHook.IntentAlreadyCancelled.selector, intentId)); - hook.cancelIntents(nonces); - - // Pre-cancelled remains cancelled (from prior tx); others unchanged - bytes32 id21 = hook.computeIntentId(SWAPPER, 21); - bytes32 id22 = hook.computeIntentId(SWAPPER, 22); - bytes32 id23 = hook.computeIntentId(SWAPPER, 23); - - assertTrue(hook.getExecutionState(id21).cancelled, "pre-cancelled persists across tx"); - assertFalse(hook.getExecutionState(id22).cancelled, "others unaffected"); - assertFalse(hook.getExecutionState(id23).cancelled, "others unaffected"); - } - - function test_cancelIntents_emptyNoop() public { - uint256[] memory nonces = new uint256[](0); - vm.prank(SWAPPER); - hook.cancelIntents(nonces); - // no revert, no state change - } - - function test_cancelIsolationAcrossSenders() public { - uint256 nonce = 42; - - // OTHER cancels THEIR own intent - vm.prank(OTHER); - hook.cancelIntent(nonce); - - bytes32 idOther = hook.computeIntentId(OTHER, nonce); - assertTrue(hook.getExecutionState(idOther).cancelled, "other's intent cancelled"); - - // SWAPPER's intent with same nonce remains not cancelled - bytes32 idSwapper = hook.computeIntentId(SWAPPER, nonce); - assertFalse(hook.getExecutionState(idSwapper).cancelled, "isolation across senders"); - } - - // ======================================== - // Output Allocations Tests - // ======================================== - function test_validateAllocationStructure_validSingleRecipient() public view { - OutputAllocation[] memory allocations = new OutputAllocation[](1); - allocations[0] = OutputAllocation({recipient: SWAPPER, basisPoints: 10000}); - - // Should not revert - hook.validateAllocationStructure(allocations); - } - - function test_validateAllocationStructure_validWithFees() public view { - OutputAllocation[] memory allocations = new OutputAllocation[](2); - allocations[0] = OutputAllocation({ - recipient: SWAPPER, - basisPoints: 9975 // 99.75% - }); - allocations[1] = OutputAllocation({ - recipient: address(0xFEE), - basisPoints: 25 // 0.25% fee - }); - - // Should not revert - hook.validateAllocationStructure(allocations); - } - - function test_validateAllocationStructure_revertsEmptyArray() public { - OutputAllocation[] memory allocations = new OutputAllocation[](0); - - vm.expectRevert(IDCAHook.EmptyAllocations.selector); - hook.validateAllocationStructure(allocations); - } - - function test_validateAllocationStructure_revertsZeroAllocation() public { - OutputAllocation[] memory allocations = new OutputAllocation[](2); - allocations[0] = OutputAllocation({recipient: SWAPPER, basisPoints: 10000}); - allocations[1] = OutputAllocation({ - recipient: OTHER, - basisPoints: 0 // Invalid: zero allocation - }); - - vm.expectRevert(IDCAHook.ZeroAllocation.selector); - hook.validateAllocationStructure(allocations); - } - - function test_validateAllocationStructure_revertsBelow100Percent() public { - OutputAllocation[] memory allocations = new OutputAllocation[](2); - allocations[0] = OutputAllocation({ - recipient: SWAPPER, - basisPoints: 4000 // 40% - }); - allocations[1] = OutputAllocation({ - recipient: OTHER, - basisPoints: 5999 // 59.99% - total 99.99% - }); - - vm.expectRevert(abi.encodeWithSelector(IDCAHook.AllocationsNot100Percent.selector, 9999)); - hook.validateAllocationStructure(allocations); - } - - function test_validateAllocationStructure_revertsExceedsDuringSum() public { - // Test that allocations exceeding 100% are caught at the end - OutputAllocation[] memory allocations = new OutputAllocation[](3); - allocations[0] = OutputAllocation({ - recipient: SWAPPER, - basisPoints: 5000 // 50% - }); - allocations[1] = OutputAllocation({ - recipient: OTHER, - basisPoints: 4000 // 40% - }); - allocations[2] = OutputAllocation({ - recipient: address(0x3), - basisPoints: 1001 // 10.01% - total 100.01% - }); - - vm.expectRevert(abi.encodeWithSelector(IDCAHook.AllocationsNot100Percent.selector, 10001)); - hook.validateAllocationStructure(allocations); - } - - function test_validateAllocationStructure_manyRecipients() public view { - OutputAllocation[] memory allocations = new OutputAllocation[](10); - - for (uint256 i = 0; i < 9; i++) { - allocations[i] = OutputAllocation({ - recipient: address(uint160(i + 1)), - basisPoints: 1000 // 10% each - }); - } - - allocations[9] = OutputAllocation({ - recipient: address(uint160(10)), - basisPoints: 1000 // Last 10% - }); - - // Should not revert - hook.validateAllocationStructure(allocations); - } - - function testFuzz_validateAllocationStructure_validDistributions(uint8 numRecipients, uint256 seed) public view { - vm.assume(numRecipients > 0 && numRecipients <= 10); - - OutputAllocation[] memory allocations = new OutputAllocation[](numRecipients); - uint256 remainingBasisPoints = 10000; - - for (uint256 i = 0; i < numRecipients - 1; i++) { - // Distribute randomly but ensure we don't exceed remaining - uint256 maxAllocation = remainingBasisPoints / (numRecipients - i); - uint256 allocation = (uint256(keccak256(abi.encode(seed, i))) % maxAllocation) + 1; - - allocations[i] = OutputAllocation({recipient: address(uint160(i + 1)), basisPoints: uint16(allocation)}); - - remainingBasisPoints -= allocation; - } - - // Last recipient gets the remainder to ensure exactly 100% - allocations[numRecipients - 1] = - OutputAllocation({recipient: address(uint160(numRecipients)), basisPoints: uint16(remainingBasisPoints)}); - - // Should not revert for any valid distribution - hook.validateAllocationStructure(allocations); - } - - function test_validateAllocationStructure_revertsDuplicateRecipient_sameBasisPoints() public { - // Test the scenario of duplicate recipients - // with the same basis points could lead to output theft - OutputAllocation[] memory allocations = new OutputAllocation[](2); - allocations[0] = OutputAllocation({ - recipient: SWAPPER, - basisPoints: 5000 // 50% - }); - allocations[1] = OutputAllocation({ - recipient: SWAPPER, // Duplicate recipient - basisPoints: 5000 // 50% - }); - - vm.expectRevert(abi.encodeWithSelector(IDCAHook.DuplicateRecipient.selector, SWAPPER)); - hook.validateAllocationStructure(allocations); - } - - function test_validateAllocationStructure_revertsDuplicateRecipient_differentBasisPoints() public { - // Test duplicate recipients with different basis points configuration - OutputAllocation[] memory allocations = new OutputAllocation[](2); - allocations[0] = OutputAllocation({ - recipient: SWAPPER, - basisPoints: 7000 // 70% - }); - allocations[1] = OutputAllocation({ - recipient: SWAPPER, // Duplicate recipient - basisPoints: 3000 // 30% - }); - - vm.expectRevert(abi.encodeWithSelector(IDCAHook.DuplicateRecipient.selector, SWAPPER)); - hook.validateAllocationStructure(allocations); - } -} diff --git a/test/v4/hooks/dca/DCAHookHarness.sol b/test/v4/hooks/dca/DCAHookHarness.sol deleted file mode 100644 index c12ac7b1..00000000 --- a/test/v4/hooks/dca/DCAHookHarness.sol +++ /dev/null @@ -1,130 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {DCAHook} from "../../../../src/v4/hooks/dca/DCAHook.sol"; -import { - DCAExecutionState, - OutputAllocation, - DCAIntent, - DCAOrderCosignerData, - PrivateIntent, - FeedInfo, - PermitData -} from "../../../../src/v4/hooks/dca/DCAStructs.sol"; -import {ResolvedOrder} from "../../../../src/v4/base/ReactorStructs.sol"; -import {OutputToken} from "../../../../src/base/ReactorStructs.sol"; -import {IPermit2} from "permit2/src/interfaces/IPermit2.sol"; -import {IReactor} from "../../../../src/v4/interfaces/IReactor.sol"; - -contract DCAHookHarness is DCAHook { - constructor(IPermit2 p, IReactor r) DCAHook(p, r) {} - - function __setPacked(bytes32 intentId, uint128 executedChunks, bool cancelled) external { - DCAExecutionState storage s = executionStates[intentId]; - s.executedChunks = executedChunks; - s.cancelled = cancelled; - } - - function __setExecutedMeta(bytes32 intentId, uint120 lastExecutionTime) external { - DCAExecutionState storage s = executionStates[intentId]; - s.lastExecutionTime = lastExecutionTime; - } - - function __setTotals(bytes32 intentId, uint256 totalInputExecuted, uint256 totalOutput) external { - DCAExecutionState storage s = executionStates[intentId]; - s.totalInputExecuted = totalInputExecuted; - s.totalOutput = totalOutput; - } - - /// @notice Exposes the internal _validateAllocationStructure function for testing - function validateAllocationStructure(OutputAllocation[] memory outputAllocations) external pure { - _validateAllocationStructure(outputAllocations); - } - - /// @notice Exposes the internal _validatePriceFloor function for testing - function validatePriceFloor(bool isExactIn, uint160 execAmount, uint160 limitAmount, uint256 minPrice) - external - pure - { - DCAIntent memory intent; - DCAOrderCosignerData memory cd; - intent.isExactIn = isExactIn; - intent.minPrice = minPrice; - cd.execAmount = execAmount; - cd.limitAmount = limitAmount; - _validatePriceFloor(intent, cd); - } - - /// @notice Exposes the internal _validateStaticFields function for testing - function validateStaticFields(DCAIntent memory intent, ResolvedOrder memory resolvedOrder) external view { - _validateStaticFields(intent, resolvedOrder); - } - - /// @notice Exposes the internal _validateChunkSize function for testing - function validateChunkSize(DCAIntent memory intent, DCAOrderCosignerData memory cosignerData, uint256 inputAmount) - external - pure - { - _validateChunkSize(intent, cosignerData, inputAmount); - } - - /// @notice Exposes the internal _validateOutputDistribution function for testing - function validateOutputDistribution( - DCAIntent memory intent, - DCAOrderCosignerData memory cosignerData, - OutputToken[] memory outputs - ) external pure { - _validateOutputDistribution(intent, cosignerData, outputs); - } - - /// @notice Helper to create a basic DCA intent for testing - function createTestIntent(address swapper, uint96 nonce, bool isExactIn, uint256 minChunk, uint256 maxChunk) - external - view - returns (DCAIntent memory) - { - OutputAllocation[] memory allocations = new OutputAllocation[](1); - allocations[0] = OutputAllocation({recipient: address(0x9ABC), basisPoints: 10000}); - - PrivateIntent memory privateIntent = PrivateIntent({ - totalAmount: 1000e18, exactFrequency: 3600, numChunks: 10, salt: bytes32(0), oracleFeeds: new FeedInfo[](0) - }); - - return DCAIntent({ - swapper: swapper, - nonce: nonce, - chainId: block.chainid, - hookAddress: address(this), - isExactIn: isExactIn, - inputToken: address(0xAAAA), - outputToken: address(0xBBBB), - cosigner: address(0x5678), - minPeriod: 300, - maxPeriod: 7200, - minChunkSize: minChunk, - maxChunkSize: maxChunk, - minPrice: 0, - deadline: block.timestamp + 1 days, - outputAllocations: allocations, - privateIntent: privateIntent - }); - } - - /// @notice Exposes the internal _transferInputTokens function for testing - function transferInputTokens(ResolvedOrder calldata order, address to, PermitData memory permitData) external { - _transferInputTokens(order, to, permitData); - } - - /// @notice Helper to create cosigner data for testing - function createTestCosignerData( - address swapper, - uint96 nonce, - uint160 execAmount, - uint160 limitAmount, - uint96 orderNonce - ) external pure returns (DCAOrderCosignerData memory) { - return DCAOrderCosignerData({ - swapper: swapper, nonce: nonce, execAmount: execAmount, orderNonce: orderNonce, limitAmount: limitAmount - }); - } -} diff --git a/test/v4/hooks/dca/DCAHook_DomainSeparator.t.sol b/test/v4/hooks/dca/DCAHook_DomainSeparator.t.sol deleted file mode 100644 index 55fad373..00000000 --- a/test/v4/hooks/dca/DCAHook_DomainSeparator.t.sol +++ /dev/null @@ -1,141 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import "forge-std/Test.sol"; -import {DCAHook} from "../../../../src/v4/hooks/dca/DCAHook.sol"; -import {DCALib} from "../../../../src/v4/hooks/dca/DCALib.sol"; -import {IPermit2} from "permit2/src/interfaces/IPermit2.sol"; -import {IReactor} from "../../../../src/v4/interfaces/IReactor.sol"; - -/// @title DCAHook Domain Separator Test -/// @notice Tests that domain separator is dynamically computed after chain fork -contract DCAHook_DomainSeparatorTest is Test { - DCAHook hook; - IPermit2 permit2; - IReactor reactor; - - function setUp() public { - // Deploy mock contracts - permit2 = IPermit2(address(0x1)); - reactor = IReactor(address(0x2)); - - // Deploy DCAHook - hook = new DCAHook(permit2, reactor); - } - - /// @notice Test that domain separator is cached on deployment chain - function test_domainSeparator_cachedOnDeploymentChain() public view { - // Get domain separator - bytes32 domainSep = hook.DOMAIN_SEPARATOR(); - - // Verify it matches the expected value - bytes32 expected = DCALib.computeDomainSeparator(address(hook)); - assertEq(domainSep, expected, "Domain separator should match expected value"); - } - - /// @notice Test that domain separator changes when chain ID changes (simulating a fork) - function test_domainSeparator_changesAfterFork() public { - // Get initial domain separator - bytes32 initialDomainSep = hook.DOMAIN_SEPARATOR(); - uint256 initialChainId = block.chainid; - - // Simulate chain fork by changing chain ID - uint256 newChainId = initialChainId + 1; - vm.chainId(newChainId); - - // Get domain separator after fork - bytes32 newDomainSep = hook.DOMAIN_SEPARATOR(); - - // Verify domain separator has changed - assertTrue(newDomainSep != initialDomainSep, "Domain separator should change after fork"); - - // Verify new domain separator is correct for new chain - bytes32 expected = DCALib.computeDomainSeparator(address(hook)); - assertEq(newDomainSep, expected, "Domain separator should match new chain ID"); - } - - /// @notice Test that domain separator returns to cached value when chain ID returns to original - function test_domainSeparator_returnsToCachedValue() public { - // Get initial values - bytes32 initialDomainSep = hook.DOMAIN_SEPARATOR(); - uint256 initialChainId = block.chainid; - - // Change chain ID - vm.chainId(initialChainId + 1); - bytes32 forkedDomainSep = hook.DOMAIN_SEPARATOR(); - assertTrue(forkedDomainSep != initialDomainSep, "Should change on fork"); - - // Return to original chain ID - vm.chainId(initialChainId); - bytes32 restoredDomainSep = hook.DOMAIN_SEPARATOR(); - - // Verify it matches the original cached value - assertEq(restoredDomainSep, initialDomainSep, "Should return to cached value"); - } - - /// @notice Fuzz test domain separator with various chain IDs - function testFuzz_domainSeparator_variousChainIds(uint256 chainId) public { - // Bound chain ID to reasonable values - chainId = bound(chainId, 1, type(uint64).max); - - // Set chain ID - vm.chainId(chainId); - - // Get domain separator - bytes32 domainSep = hook.DOMAIN_SEPARATOR(); - - // Verify it matches the expected value for this chain - bytes32 expected = DCALib.computeDomainSeparator(address(hook)); - assertEq(domainSep, expected, "Domain separator should match expected value for chain ID"); - } - - /// @notice Test that different contracts have different domain separators - function test_domainSeparator_differentPerContract() public { - // Deploy another hook - DCAHook hook2 = new DCAHook(permit2, reactor); - - // Get domain separators - bytes32 domainSep1 = hook.DOMAIN_SEPARATOR(); - bytes32 domainSep2 = hook2.DOMAIN_SEPARATOR(); - - // Verify they are different (different contract addresses) - assertTrue(domainSep1 != domainSep2, "Different contracts should have different domain separators"); - } - - /// @notice Test gas cost of domain separator getter (cached case) - function test_domainSeparator_gasCost_cached() public { - // This should use the cached immutable value - just verify it works - bytes32 domainSep = hook.DOMAIN_SEPARATOR(); - assertTrue(domainSep != bytes32(0), "Domain separator should not be zero"); - } - - /// @notice Test gas cost of domain separator getter (recomputed case) - function test_domainSeparator_gasCost_recomputed() public { - // Change chain ID to force recomputation - vm.chainId(block.chainid + 1); - - // This should recompute the domain separator - verify it works - bytes32 domainSep = hook.DOMAIN_SEPARATOR(); - assertTrue(domainSep != bytes32(0), "Domain separator should not be zero"); - } - - /// @notice Test that replay attacks are prevented after fork - function test_replayProtection_afterFork() public { - // Get initial domain separator - bytes32 initialDomainSep = hook.DOMAIN_SEPARATOR(); - - // Simulate creating a signature on the original chain - // (In a real scenario, this would be used in _validateSwapperSignature or _validateCosignerSignature) - bytes32 structHash = keccak256("test data"); - bytes32 originalDigest = keccak256(abi.encodePacked("\x19\x01", initialDomainSep, structHash)); - - // Simulate chain fork - vm.chainId(block.chainid + 1); - bytes32 newDomainSep = hook.DOMAIN_SEPARATOR(); - - // Digest computed on new chain should be different - bytes32 newDigest = keccak256(abi.encodePacked("\x19\x01", newDomainSep, structHash)); - - assertTrue(originalDigest != newDigest, "Digest should differ after fork, preventing replay"); - } -} diff --git a/test/v4/hooks/dca/DCAHook_transferInputTokens.t.sol b/test/v4/hooks/dca/DCAHook_transferInputTokens.t.sol deleted file mode 100644 index ea44d322..00000000 --- a/test/v4/hooks/dca/DCAHook_transferInputTokens.t.sol +++ /dev/null @@ -1,452 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {Test} from "forge-std/Test.sol"; -import {IPermit2} from "permit2/src/interfaces/IPermit2.sol"; -import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol"; -import {DeployPermit2} from "../../../util/DeployPermit2.sol"; -import {DCAHookHarness} from "./DCAHookHarness.sol"; -import {IReactor} from "../../../../src/v4/interfaces/IReactor.sol"; -import {PermitData} from "../../../../src/v4/hooks/dca/DCAStructs.sol"; -import {ResolvedOrder, OrderInfo, InputToken, OutputToken} from "../../../../src/v4/base/ReactorStructs.sol"; -import {IPreExecutionHook, IPostExecutionHook} from "../../../../src/v4/interfaces/IHook.sol"; -import {IAuctionResolver} from "../../../../src/v4/interfaces/IAuctionResolver.sol"; -import {ERC20} from "solmate/src/tokens/ERC20.sol"; -import {MockERC20} from "../../../util/mock/MockERC20.sol"; - -contract DCAHook_transferInputTokensTest is Test, DeployPermit2 { - DCAHookHarness hook; - IPermit2 permit2; - address constant REACTOR_ADDRESS = address(0x2345); - IReactor constant REACTOR = IReactor(REACTOR_ADDRESS); - - MockERC20 inputToken; - MockERC20 outputToken; - - address SWAPPER; - address constant FILLER = address(0x5678); - address constant RECIPIENT = address(0x9ABC); - - uint256 constant SWAPPER_PRIVATE_KEY = 0x12345678; - uint256 constant AMOUNT = 1000e18; - uint256 constant NONCE = 42; - uint256 constant DEADLINE = 1000000000000; - - bytes32 DOMAIN_SEPARATOR; - - // EIP-712 typehashes for AllowanceTransfer - bytes32 constant _PERMIT_DETAILS_TYPEHASH = - keccak256("PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)"); - bytes32 constant _PERMIT_SINGLE_TYPEHASH = keccak256( - "PermitSingle(PermitDetails details,address spender,uint256 sigDeadline)PermitDetails(address token,uint160 amount,uint48 expiration,uint48 nonce)" - ); - - function setUp() public { - permit2 = IPermit2(deployPermit2()); - hook = new DCAHookHarness(permit2, REACTOR); - DOMAIN_SEPARATOR = permit2.DOMAIN_SEPARATOR(); - - // Derive swapper address from private key - SWAPPER = vm.addr(SWAPPER_PRIVATE_KEY); - - // Deploy mock tokens - inputToken = new MockERC20("Input Token", "INPUT", 18); - outputToken = new MockERC20("Output Token", "OUTPUT", 18); - - // Fund the swapper - inputToken.mint(SWAPPER, AMOUNT * 10); - - // Approve permit2 from swapper for max amount (required for permit2 to work) - vm.prank(SWAPPER); - inputToken.approve(address(permit2), type(uint256).max); - } - - function _createResolvedOrder(address swapper, address token, uint256 amount) - internal - view - returns (ResolvedOrder memory) - { - InputToken memory input = InputToken({token: ERC20(token), amount: amount, maxAmount: amount}); - - OutputToken[] memory outputs = new OutputToken[](1); - outputs[0] = OutputToken({token: address(outputToken), amount: amount, recipient: RECIPIENT}); - - return ResolvedOrder({ - info: OrderInfo({ - reactor: REACTOR, - swapper: swapper, - nonce: NONCE, - deadline: block.timestamp + 1000, - preExecutionHook: IPreExecutionHook(address(hook)), - preExecutionHookData: "", - postExecutionHook: IPostExecutionHook(address(0)), - postExecutionHookData: "", - auctionResolver: IAuctionResolver(address(0)) - }), - input: input, - outputs: outputs, - sig: "", - hash: bytes32(0), - auctionResolver: address(0), - witnessTypeString: "" - }); - } - - function _createPermitData(bool hasPermit) internal view returns (PermitData memory) { - if (!hasPermit) { - return PermitData({ - hasPermit: false, - permitSingle: IAllowanceTransfer.PermitSingle({ - details: IAllowanceTransfer.PermitDetails({token: address(0), amount: 0, expiration: 0, nonce: 0}), - spender: address(0), - sigDeadline: 0 - }), - signature: "" - }); - } - - IAllowanceTransfer.PermitSingle memory permitSingle = IAllowanceTransfer.PermitSingle({ - details: IAllowanceTransfer.PermitDetails({ - token: address(inputToken), - amount: uint160(AMOUNT), - expiration: uint48(block.timestamp + 1000), - nonce: uint48(0) // Use current nonce from permit2 - }), - spender: address(hook), - sigDeadline: block.timestamp + 1000 - }); - - return - PermitData({hasPermit: true, permitSingle: permitSingle, signature: _generatePermitSignature(permitSingle)}); - } - - function _generatePermitSignature(IAllowanceTransfer.PermitSingle memory permit) - internal - view - returns (bytes memory) - { - // Generate proper EIP-712 signature - bytes32 permitHash = keccak256(abi.encode(_PERMIT_DETAILS_TYPEHASH, permit.details)); - - bytes32 msgHash = keccak256( - abi.encodePacked( - "\x19\x01", - DOMAIN_SEPARATOR, - keccak256(abi.encode(_PERMIT_SINGLE_TYPEHASH, permitHash, permit.spender, permit.sigDeadline)) - ) - ); - - (uint8 v, bytes32 r, bytes32 s) = vm.sign(SWAPPER_PRIVATE_KEY, msgHash); - return bytes.concat(r, s, bytes1(v)); - } - - // ============ Tests with existing allowance ============ - - function test_transferInputTokens_existingAllowance_success() public { - // Setup: Swapper sets allowance to hook via Permit2 - vm.startPrank(SWAPPER); - permit2.approve(address(inputToken), address(hook), uint160(AMOUNT), uint48(block.timestamp + 1000)); - vm.stopPrank(); - - ResolvedOrder memory order = _createResolvedOrder(SWAPPER, address(inputToken), AMOUNT); - PermitData memory permitData = _createPermitData(false); - - uint256 swapperBalanceBefore = inputToken.balanceOf(SWAPPER); - uint256 fillerBalanceBefore = inputToken.balanceOf(FILLER); - - // Execute transfer - hook.transferInputTokens(order, FILLER, permitData); - - // Verify balances - assertEq(inputToken.balanceOf(SWAPPER), swapperBalanceBefore - AMOUNT); - assertEq(inputToken.balanceOf(FILLER), fillerBalanceBefore + AMOUNT); - } - - function test_transferInputTokens_existingAllowance_insufficientAllowance() public { - // Setup: Swapper sets insufficient allowance - vm.startPrank(SWAPPER); - permit2.approve(address(inputToken), address(hook), uint160(AMOUNT - 1), uint48(block.timestamp + 1000)); - vm.stopPrank(); - - ResolvedOrder memory order = _createResolvedOrder(SWAPPER, address(inputToken), AMOUNT); - PermitData memory permitData = _createPermitData(false); - - // Should revert due to insufficient allowance - vm.expectRevert(); - hook.transferInputTokens(order, FILLER, permitData); - } - - function test_transferInputTokens_existingAllowance_expiredAllowance() public { - // Setup: Swapper sets allowance that's expired - vm.startPrank(SWAPPER); - permit2.approve(address(inputToken), address(hook), uint160(AMOUNT), uint48(block.timestamp - 1)); - vm.stopPrank(); - - // Move time forward - vm.warp(block.timestamp + 100); - - ResolvedOrder memory order = _createResolvedOrder(SWAPPER, address(inputToken), AMOUNT); - PermitData memory permitData = _createPermitData(false); - - // Should revert due to expired allowance - vm.expectRevert(); - hook.transferInputTokens(order, FILLER, permitData); - } - - function test_transferInputTokens_existingAllowance_zeroAmount() public { - vm.startPrank(SWAPPER); - permit2.approve(address(inputToken), address(hook), uint160(AMOUNT), uint48(block.timestamp + 1000)); - vm.stopPrank(); - - ResolvedOrder memory order = _createResolvedOrder(SWAPPER, address(inputToken), 0); - PermitData memory permitData = _createPermitData(false); - - uint256 swapperBalanceBefore = inputToken.balanceOf(SWAPPER); - uint256 fillerBalanceBefore = inputToken.balanceOf(FILLER); - - // Transfer zero amount should succeed - hook.transferInputTokens(order, FILLER, permitData); - - // Verify no balance changes - assertEq(inputToken.balanceOf(SWAPPER), swapperBalanceBefore); - assertEq(inputToken.balanceOf(FILLER), fillerBalanceBefore); - } - - // ============ Tests with permit signature ============ - - function test_transferInputTokens_withPermit_success() public { - // Get the current nonce for the swapper - (,, uint48 currentNonce) = permit2.allowance(SWAPPER, address(inputToken), address(hook)); - - // Create permit with proper signature - IAllowanceTransfer.PermitSingle memory permitSingle = IAllowanceTransfer.PermitSingle({ - details: IAllowanceTransfer.PermitDetails({ - token: address(inputToken), - amount: uint160(AMOUNT), - expiration: uint48(block.timestamp + 1000), - nonce: currentNonce - }), - spender: address(hook), - sigDeadline: block.timestamp + 1000 - }); - - bytes memory signature = _generatePermitSignature(permitSingle); - - PermitData memory permitData = PermitData({hasPermit: true, permitSingle: permitSingle, signature: signature}); - - ResolvedOrder memory order = _createResolvedOrder(SWAPPER, address(inputToken), AMOUNT); - - uint256 swapperBalanceBefore = inputToken.balanceOf(SWAPPER); - uint256 fillerBalanceBefore = inputToken.balanceOf(FILLER); - - // Execute transfer with permit - this should call permit2.permit and then transferFrom - hook.transferInputTokens(order, FILLER, permitData); - - // Verify balances changed correctly - assertEq(inputToken.balanceOf(SWAPPER), swapperBalanceBefore - AMOUNT); - assertEq(inputToken.balanceOf(FILLER), fillerBalanceBefore + AMOUNT); - - // Verify the nonce was incremented - (,, uint48 newNonce) = permit2.allowance(SWAPPER, address(inputToken), address(hook)); - assertEq(newNonce, currentNonce + 1); - } - - function test_transferInputTokens_withPermit_invalidSignature() public { - ResolvedOrder memory order = _createResolvedOrder(SWAPPER, address(inputToken), AMOUNT); - - // Create permit with invalid signature - IAllowanceTransfer.PermitSingle memory permitSingle = IAllowanceTransfer.PermitSingle({ - details: IAllowanceTransfer.PermitDetails({ - token: address(inputToken), - amount: uint160(AMOUNT), - expiration: uint48(block.timestamp + 1000), - nonce: uint48(0) - }), - spender: address(hook), - sigDeadline: block.timestamp + 1000 - }); - - // Use invalid signature (all zeros) - PermitData memory permitData = PermitData({ - hasPermit: true, permitSingle: permitSingle, signature: abi.encodePacked(bytes32(0), bytes32(0), uint8(0)) - }); - - // Should revert due to invalid permit signature - vm.expectRevert(); - hook.transferInputTokens(order, FILLER, permitData); - } - - function test_transferInputTokens_withPermit_expiredDeadline() public { - // Create permit with expired deadline - IAllowanceTransfer.PermitSingle memory permitSingle = IAllowanceTransfer.PermitSingle({ - details: IAllowanceTransfer.PermitDetails({ - token: address(inputToken), - amount: uint160(AMOUNT), - expiration: uint48(block.timestamp + 1000), - nonce: uint48(0) - }), - spender: address(hook), - sigDeadline: block.timestamp - 1 // Expired deadline - }); - - bytes memory signature = _generatePermitSignature(permitSingle); - - PermitData memory permitData = PermitData({hasPermit: true, permitSingle: permitSingle, signature: signature}); - - ResolvedOrder memory order = _createResolvedOrder(SWAPPER, address(inputToken), AMOUNT); - - // Should revert due to expired signature deadline - vm.expectRevert(); - hook.transferInputTokens(order, FILLER, permitData); - } - - function test_transferInputTokens_withPermit_wrongNonce() public { - // Create permit with wrong nonce - IAllowanceTransfer.PermitSingle memory permitSingle = IAllowanceTransfer.PermitSingle({ - details: IAllowanceTransfer.PermitDetails({ - token: address(inputToken), - amount: uint160(AMOUNT), - expiration: uint48(block.timestamp + 1000), - nonce: uint48(999) // Wrong nonce - }), - spender: address(hook), - sigDeadline: block.timestamp + 1000 - }); - - bytes memory signature = _generatePermitSignature(permitSingle); - - PermitData memory permitData = PermitData({hasPermit: true, permitSingle: permitSingle, signature: signature}); - - ResolvedOrder memory order = _createResolvedOrder(SWAPPER, address(inputToken), AMOUNT); - - // Should revert due to invalid nonce - vm.expectRevert(); - hook.transferInputTokens(order, FILLER, permitData); - } - - function test_transferInputTokens_withPermit_frontRunProtection() public { - // Get the current nonce for the swapper - (,, uint48 currentNonce) = permit2.allowance(SWAPPER, address(inputToken), address(hook)); - - // Create permit with proper signature - IAllowanceTransfer.PermitSingle memory permitSingle = IAllowanceTransfer.PermitSingle({ - details: IAllowanceTransfer.PermitDetails({ - token: address(inputToken), - amount: uint160(AMOUNT), - expiration: uint48(block.timestamp + 1000), - nonce: currentNonce - }), - spender: address(hook), - sigDeadline: block.timestamp + 1000 - }); - - bytes memory signature = _generatePermitSignature(permitSingle); - PermitData memory permitData = PermitData({hasPermit: true, permitSingle: permitSingle, signature: signature}); - ResolvedOrder memory order = _createResolvedOrder(SWAPPER, address(inputToken), AMOUNT); - - // ATTACK: Front-runner extracts the permit from mempool and calls permit2.permit() directly - // This consumes the nonce but sets the allowance for the hook (as specified in the permit) - permit2.permit(SWAPPER, permitSingle, signature); - - // Verify the nonce was consumed by the front-runner - (,, uint48 nonceAfterFrontRun) = permit2.allowance(SWAPPER, address(inputToken), address(hook)); - assertEq(nonceAfterFrontRun, currentNonce + 1); - - // PROTECTED: User's transaction should still succeed despite front-run - uint256 swapperBalanceBefore = inputToken.balanceOf(SWAPPER); - uint256 fillerBalanceBefore = inputToken.balanceOf(FILLER); - - hook.transferInputTokens(order, FILLER, permitData); - - // Verify the transfer succeeded despite the front-run - assertEq(inputToken.balanceOf(SWAPPER), swapperBalanceBefore - AMOUNT); - assertEq(inputToken.balanceOf(FILLER), fillerBalanceBefore + AMOUNT); - } - - // ============ Edge cases ============ - - function test_transferInputTokens_differentRecipient() public { - address customRecipient = address(0xDEAD); - - vm.startPrank(SWAPPER); - permit2.approve(address(inputToken), address(hook), uint160(AMOUNT), uint48(block.timestamp + 1000)); - vm.stopPrank(); - - ResolvedOrder memory order = _createResolvedOrder(SWAPPER, address(inputToken), AMOUNT); - PermitData memory permitData = _createPermitData(false); - - uint256 recipientBalanceBefore = inputToken.balanceOf(customRecipient); - - // Transfer to custom recipient - hook.transferInputTokens(order, customRecipient, permitData); - - // Verify transfer went to correct recipient - assertEq(inputToken.balanceOf(customRecipient), recipientBalanceBefore + AMOUNT); - } - - function test_transferInputTokens_maxUint160Amount() public { - uint256 maxAmount = type(uint160).max; - - // Fund swapper with max amount - inputToken.mint(SWAPPER, maxAmount); - - vm.startPrank(SWAPPER); - permit2.approve(address(inputToken), address(hook), uint160(maxAmount), uint48(block.timestamp + 1000)); - vm.stopPrank(); - - ResolvedOrder memory order = _createResolvedOrder(SWAPPER, address(inputToken), maxAmount); - PermitData memory permitData = _createPermitData(false); - - uint256 swapperBalanceBefore = inputToken.balanceOf(SWAPPER); - uint256 fillerBalanceBefore = inputToken.balanceOf(FILLER); - - // Execute transfer with max amount - hook.transferInputTokens(order, FILLER, permitData); - - // Verify balances - assertEq(inputToken.balanceOf(SWAPPER), swapperBalanceBefore - maxAmount); - assertEq(inputToken.balanceOf(FILLER), fillerBalanceBefore + maxAmount); - } - - // ============ Fuzz tests ============ - - function testFuzz_transferInputTokens_variousAmounts(uint160 amount) public { - vm.assume(amount > 0 && amount <= AMOUNT); - - vm.startPrank(SWAPPER); - permit2.approve(address(inputToken), address(hook), amount, uint48(block.timestamp + 1000)); - vm.stopPrank(); - - ResolvedOrder memory order = _createResolvedOrder(SWAPPER, address(inputToken), amount); - PermitData memory permitData = _createPermitData(false); - - uint256 swapperBalanceBefore = inputToken.balanceOf(SWAPPER); - uint256 fillerBalanceBefore = inputToken.balanceOf(FILLER); - - // Execute transfer - hook.transferInputTokens(order, FILLER, permitData); - - // Verify balances - assertEq(inputToken.balanceOf(SWAPPER), swapperBalanceBefore - amount); - assertEq(inputToken.balanceOf(FILLER), fillerBalanceBefore + amount); - } - - function testFuzz_transferInputTokens_variousRecipients(address recipient) public { - vm.assume(recipient != address(0) && recipient != SWAPPER); - - vm.startPrank(SWAPPER); - permit2.approve(address(inputToken), address(hook), uint160(AMOUNT), uint48(block.timestamp + 1000)); - vm.stopPrank(); - - ResolvedOrder memory order = _createResolvedOrder(SWAPPER, address(inputToken), AMOUNT); - PermitData memory permitData = _createPermitData(false); - - uint256 recipientBalanceBefore = inputToken.balanceOf(recipient); - - // Execute transfer - hook.transferInputTokens(order, recipient, permitData); - - // Verify transfer went to correct recipient - assertEq(inputToken.balanceOf(recipient), recipientBalanceBefore + AMOUNT); - } -} diff --git a/test/v4/hooks/dca/DCAHook_validateChunkSize.t.sol b/test/v4/hooks/dca/DCAHook_validateChunkSize.t.sol deleted file mode 100644 index 49a3189f..00000000 --- a/test/v4/hooks/dca/DCAHook_validateChunkSize.t.sol +++ /dev/null @@ -1,349 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {Test} from "forge-std/Test.sol"; -import {IPermit2} from "permit2/src/interfaces/IPermit2.sol"; -import {DeployPermit2} from "../../../util/DeployPermit2.sol"; -import {DCAHookHarness} from "./DCAHookHarness.sol"; -import {IReactor} from "../../../../src/v4/interfaces/IReactor.sol"; -import {DCAIntent, DCAOrderCosignerData} from "../../../../src/v4/hooks/dca/DCAStructs.sol"; -import {IDCAHook} from "../../../../src/v4/interfaces/IDCAHook.sol"; - -contract DCAHook_validateChunkSizeTest is Test, DeployPermit2 { - DCAHookHarness hook; - IPermit2 permit2; - address constant REACTOR_ADDRESS = address(0x2345); - IReactor constant REACTOR = IReactor(REACTOR_ADDRESS); - - address constant SWAPPER = address(0x1234); - uint96 constant NONCE = 42; - - uint160 constant MIN_CHUNK_SIZE = 10e18; - uint160 constant MAX_CHUNK_SIZE = 100e18; - - function setUp() public { - permit2 = IPermit2(deployPermit2()); - hook = new DCAHookHarness(permit2, REACTOR); - } - - // ============================================ - // EXACT_IN Tests - // ============================================ - - function test_validateChunkSize_exactIn_validChunkWithinBounds() public view { - DCAIntent memory intent = hook.createTestIntent(SWAPPER, NONCE, true, MIN_CHUNK_SIZE, MAX_CHUNK_SIZE); - DCAOrderCosignerData memory cosignerData = hook.createTestCosignerData(SWAPPER, NONCE, 60e18, 90e18, 1); - - // Should not revert - execAmount is within bounds and matches input - hook.validateChunkSize(intent, cosignerData, 60e18); - } - - function test_validateChunkSize_exactIn_validChunkAtMinimum() public view { - DCAIntent memory intent = hook.createTestIntent(SWAPPER, NONCE, true, MIN_CHUNK_SIZE, MAX_CHUNK_SIZE); - DCAOrderCosignerData memory cosignerData = hook.createTestCosignerData(SWAPPER, NONCE, MIN_CHUNK_SIZE, 8e18, 1); - - // Should not revert - execAmount equals minimum - hook.validateChunkSize(intent, cosignerData, MIN_CHUNK_SIZE); - } - - function test_validateChunkSize_exactIn_validChunkAtMaximum() public view { - DCAIntent memory intent = hook.createTestIntent(SWAPPER, NONCE, true, MIN_CHUNK_SIZE, MAX_CHUNK_SIZE); - DCAOrderCosignerData memory cosignerData = hook.createTestCosignerData(SWAPPER, NONCE, MAX_CHUNK_SIZE, 80e18, 1); - - // Should not revert - execAmount equals maximum - hook.validateChunkSize(intent, cosignerData, MAX_CHUNK_SIZE); - } - - function test_validateChunkSize_exactIn_revertWhenBelowMinimum() public { - DCAIntent memory intent = hook.createTestIntent(SWAPPER, NONCE, true, MIN_CHUNK_SIZE, MAX_CHUNK_SIZE); - uint160 belowMin = MIN_CHUNK_SIZE - 1; - DCAOrderCosignerData memory cosignerData = hook.createTestCosignerData(SWAPPER, NONCE, belowMin, 8e18, 1); - - vm.expectRevert(abi.encodeWithSelector(IDCAHook.ChunkSizeBelowMin.selector, belowMin, MIN_CHUNK_SIZE)); - hook.validateChunkSize(intent, cosignerData, belowMin); - } - - function test_validateChunkSize_exactIn_revertWhenAboveMaximum() public { - DCAIntent memory intent = hook.createTestIntent(SWAPPER, NONCE, true, MIN_CHUNK_SIZE, MAX_CHUNK_SIZE); - uint160 aboveMax = MAX_CHUNK_SIZE + 1; - DCAOrderCosignerData memory cosignerData = hook.createTestCosignerData(SWAPPER, NONCE, aboveMax, 80e18, 1); - - vm.expectRevert(abi.encodeWithSelector(IDCAHook.ChunkSizeAboveMax.selector, aboveMax, MAX_CHUNK_SIZE)); - hook.validateChunkSize(intent, cosignerData, aboveMax); - } - - function test_validateChunkSize_exactIn_revertWhenInputMismatch() public { - DCAIntent memory intent = hook.createTestIntent(SWAPPER, NONCE, true, MIN_CHUNK_SIZE, MAX_CHUNK_SIZE); - DCAOrderCosignerData memory cosignerData = hook.createTestCosignerData(SWAPPER, NONCE, 50e18, 40e18, 1); - uint160 wrongInput = 60e18; - - vm.expectRevert(abi.encodeWithSelector(IDCAHook.InputAmountMismatch.selector, wrongInput, 50e18)); - hook.validateChunkSize(intent, cosignerData, wrongInput); - } - - function test_validateChunkSize_exactIn_revertWhenExecAmountZero() public { - DCAIntent memory intent = hook.createTestIntent(SWAPPER, NONCE, true, 1, MAX_CHUNK_SIZE); - DCAOrderCosignerData memory cosignerData = hook.createTestCosignerData(SWAPPER, NONCE, 0, 0, 1); - - vm.expectRevert(abi.encodeWithSelector(IDCAHook.ChunkSizeBelowMin.selector, 0, 1)); - hook.validateChunkSize(intent, cosignerData, 0); - } - - // ============================================ - // EXACT_OUT Tests - // ============================================ - - function test_validateChunkSize_exactOut_validChunkWithinBounds() public view { - DCAIntent memory intent = hook.createTestIntent(SWAPPER, NONCE, false, MIN_CHUNK_SIZE, MAX_CHUNK_SIZE); - DCAOrderCosignerData memory cosignerData = hook.createTestCosignerData(SWAPPER, NONCE, 50e18, 60e18, 1); - - // Should not revert - execAmount within bounds, input <= limit - hook.validateChunkSize(intent, cosignerData, 55e18); - } - - function test_validateChunkSize_exactOut_validChunkAtMinimum() public view { - DCAIntent memory intent = hook.createTestIntent(SWAPPER, NONCE, false, MIN_CHUNK_SIZE, MAX_CHUNK_SIZE); - DCAOrderCosignerData memory cosignerData = hook.createTestCosignerData(SWAPPER, NONCE, MIN_CHUNK_SIZE, 12e18, 1); - - // Should not revert - execAmount is MIN_CHUNK_SIZE so it's acceptable - hook.validateChunkSize(intent, cosignerData, 6e18); // 6e18 input is not greater than the limit of 12e18 - } - - function test_validateChunkSize_exactOut_validChunkAtMaximum() public view { - DCAIntent memory intent = hook.createTestIntent(SWAPPER, NONCE, false, MIN_CHUNK_SIZE, MAX_CHUNK_SIZE); - DCAOrderCosignerData memory cosignerData = - hook.createTestCosignerData(SWAPPER, NONCE, MAX_CHUNK_SIZE, 120e18, 1); - - // Should not revert - execAmount is MAX_CHUNK_SIZE so it's acceptables - hook.validateChunkSize(intent, cosignerData, 110e18); // Uses 110e18 input which is not greater than the limit of 120e18 - } - - function test_validateChunkSize_exactOut_validInputAtLimit() public view { - DCAIntent memory intent = hook.createTestIntent(SWAPPER, NONCE, false, MIN_CHUNK_SIZE, MAX_CHUNK_SIZE); - uint160 limit = 60e18; - DCAOrderCosignerData memory cosignerData = hook.createTestCosignerData(SWAPPER, NONCE, 50e18, limit, 1); - - // Should not revert - input exactly at limit - hook.validateChunkSize(intent, cosignerData, limit); // Uses 60e18 input which is exactly the limit of 60e18 - } - - function test_validateChunkSize_exactOut_revertWhenBelowMinimum() public { - DCAIntent memory intent = hook.createTestIntent(SWAPPER, NONCE, false, MIN_CHUNK_SIZE, MAX_CHUNK_SIZE); - uint160 belowMin = MIN_CHUNK_SIZE - 1; - DCAOrderCosignerData memory cosignerData = hook.createTestCosignerData(SWAPPER, NONCE, belowMin, 12e18, 1); - - vm.expectRevert(abi.encodeWithSelector(IDCAHook.ChunkSizeBelowMin.selector, belowMin, MIN_CHUNK_SIZE)); - // Even though the input didn't exceed the limit, the desired MAX_OUTPUT is not valid - hook.validateChunkSize(intent, cosignerData, 10e18); - } - - function test_validateChunkSize_exactOut_revertWhenAboveMaximum() public { - DCAIntent memory intent = hook.createTestIntent(SWAPPER, NONCE, false, MIN_CHUNK_SIZE, MAX_CHUNK_SIZE); - uint160 aboveMax = MAX_CHUNK_SIZE + 1; - DCAOrderCosignerData memory cosignerData = hook.createTestCosignerData(SWAPPER, NONCE, aboveMax, 120e18, 1); - - vm.expectRevert(abi.encodeWithSelector(IDCAHook.ChunkSizeAboveMax.selector, aboveMax, MAX_CHUNK_SIZE)); - hook.validateChunkSize(intent, cosignerData, 110e18); - } - - function test_validateChunkSize_exactOut_revertWhenZeroInput() public { - DCAIntent memory intent = hook.createTestIntent(SWAPPER, NONCE, false, MIN_CHUNK_SIZE, MAX_CHUNK_SIZE); - DCAOrderCosignerData memory cosignerData = hook.createTestCosignerData(SWAPPER, NONCE, 50e18, 60e18, 1); - - vm.expectRevert(IDCAHook.ZeroInput.selector); - hook.validateChunkSize(intent, cosignerData, 0); - } - - function test_validateChunkSize_exactOut_revertWhenInputAboveLimit() public { - DCAIntent memory intent = hook.createTestIntent(SWAPPER, NONCE, false, MIN_CHUNK_SIZE, MAX_CHUNK_SIZE); - uint160 limit = 60e18; - uint160 excessiveInput = limit + 1; - DCAOrderCosignerData memory cosignerData = hook.createTestCosignerData(SWAPPER, NONCE, 50e18, limit, 1); - - // Exceeds the maximum input the swapper is willing to give up for 50e18 of output - vm.expectRevert(abi.encodeWithSelector(IDCAHook.InputAboveLimit.selector, excessiveInput, limit)); - hook.validateChunkSize(intent, cosignerData, excessiveInput); - } - - // ============================================ - // Edge Cases and Boundary Tests - // ============================================ - - function test_validateChunkSize_exactIn_minMaxEqual() public view { - uint160 fixedChunkSize = 50e18; - DCAIntent memory intent = hook.createTestIntent(SWAPPER, NONCE, true, fixedChunkSize, fixedChunkSize); - DCAOrderCosignerData memory cosignerData = hook.createTestCosignerData(SWAPPER, NONCE, fixedChunkSize, 40e18, 1); - - // Should not revert - execAmount equals both min and max - hook.validateChunkSize(intent, cosignerData, fixedChunkSize); - } - - function test_validateChunkSize_exactOut_minMaxEqual() public view { - uint160 fixedChunkSize = 50e18; - DCAIntent memory intent = hook.createTestIntent(SWAPPER, NONCE, false, fixedChunkSize, fixedChunkSize); - DCAOrderCosignerData memory cosignerData = hook.createTestCosignerData(SWAPPER, NONCE, fixedChunkSize, 60e18, 1); - - // Should not revert - execAmount equals both min and max - hook.validateChunkSize(intent, cosignerData, 55e18); // 55e18 is just a safe input that doesn't exceed 60e18 - } - - function test_validateChunkSize_exactIn_largeValues() public view { - uint160 minChunk = 1000000e18; - uint160 maxChunk = 10000000e18; - uint160 execAmount = 5000000e18; - uint160 desiredOutput = 9000000e18; - - DCAIntent memory intent = hook.createTestIntent(SWAPPER, NONCE, true, minChunk, maxChunk); - DCAOrderCosignerData memory cosignerData = - hook.createTestCosignerData(SWAPPER, NONCE, execAmount, desiredOutput, 1); - - // Should not revert with large values - hook.validateChunkSize(intent, cosignerData, execAmount); - } - - function test_validateChunkSize_exactOut_largeValues() public view { - uint160 minChunk = 1000000e18; - uint160 maxChunk = 10000000e18; - uint160 execAmount = 5000000e18; - uint160 inputAmount = 6000000e18; - uint160 limit = 7000000e18; - - DCAIntent memory intent = hook.createTestIntent(SWAPPER, NONCE, false, minChunk, maxChunk); - DCAOrderCosignerData memory cosignerData = hook.createTestCosignerData(SWAPPER, NONCE, execAmount, limit, 1); - - // Should not revert with large values - hook.validateChunkSize(intent, cosignerData, inputAmount); - } - - // ============================================ - // Fuzz Tests - // ============================================ - - function testFuzz_validateChunkSize_exactIn_validRange(uint256 minChunk, uint256 maxChunk, uint256 execAmount) - public - view - { - // Bound inputs to reasonable ranges - minChunk = bound(minChunk, 1, type(uint160).max); - maxChunk = bound(maxChunk, minChunk, type(uint160).max); - execAmount = bound(execAmount, minChunk, maxChunk); - - DCAIntent memory intent = hook.createTestIntent(SWAPPER, NONCE, true, minChunk, maxChunk); - DCAOrderCosignerData memory cosignerData = - hook.createTestCosignerData(SWAPPER, NONCE, uint160(execAmount), uint160(execAmount / 2), 1); - - // Should not revert for any valid exec amount within bounds - hook.validateChunkSize(intent, cosignerData, execAmount); - } - - function testFuzz_validateChunkSize_exactOut_validRange( - uint256 minChunk, - uint256 maxChunk, - uint256 execAmount, - uint256 inputAmount, - uint256 limit - ) public view { - minChunk = bound(minChunk, 1, type(uint160).max); - maxChunk = bound(maxChunk, minChunk, type(uint160).max); - execAmount = bound(execAmount, minChunk, maxChunk); - - // For EXACT_OUT, input must be non-zero and <= limit - inputAmount = bound(inputAmount, 1, type(uint160).max); - limit = bound(limit, inputAmount, type(uint160).max); - - DCAIntent memory intent = hook.createTestIntent(SWAPPER, NONCE, false, minChunk, maxChunk); - DCAOrderCosignerData memory cosignerData = - hook.createTestCosignerData(SWAPPER, NONCE, uint160(execAmount), uint160(limit), 1); - - // Should not revert for valid combinations - hook.validateChunkSize(intent, cosignerData, inputAmount); - } - - function testFuzz_validateChunkSize_exactIn_revertBelowMin(uint256 minChunk, uint256 maxChunk, uint256 execAmount) - public - { - // Setup reasonable bounds ensuring minChunk > 0 for valid "below" range - minChunk = bound(minChunk, 1, type(uint160).max / 2); - maxChunk = bound(maxChunk, minChunk, type(uint160).max); - execAmount = bound(execAmount, 0, minChunk - 1); - - DCAIntent memory intent = hook.createTestIntent(SWAPPER, NONCE, true, minChunk, maxChunk); - DCAOrderCosignerData memory cosignerData = - hook.createTestCosignerData(SWAPPER, NONCE, uint160(execAmount), uint160(execAmount / 2), 1); - - vm.expectRevert(abi.encodeWithSelector(IDCAHook.ChunkSizeBelowMin.selector, execAmount, minChunk)); - hook.validateChunkSize(intent, cosignerData, execAmount); - } - - function testFuzz_validateChunkSize_exactIn_revertAboveMax(uint256 minChunk, uint256 maxChunk, uint256 execAmount) - public - { - // Setup reasonable bounds - minChunk = bound(minChunk, 1, type(uint160).max / 2); - maxChunk = bound(maxChunk, minChunk, type(uint160).max - 1); // Leave room for above max - execAmount = bound(execAmount, maxChunk + 1, type(uint160).max); - - DCAIntent memory intent = hook.createTestIntent(SWAPPER, NONCE, true, minChunk, maxChunk); - DCAOrderCosignerData memory cosignerData = - hook.createTestCosignerData(SWAPPER, NONCE, uint160(execAmount), uint160(execAmount / 2), 1); - - vm.expectRevert(abi.encodeWithSelector(IDCAHook.ChunkSizeAboveMax.selector, execAmount, maxChunk)); - hook.validateChunkSize(intent, cosignerData, execAmount); - } - - function testFuzz_validateChunkSize_exactOut_revertBelowMin(uint256 minChunk, uint256 maxChunk, uint256 execAmount) - public - { - // Setup reasonable bounds ensuring minChunk > 0 for valid "below" range - minChunk = bound(minChunk, 1, type(uint160).max / 2); - maxChunk = bound(maxChunk, minChunk, type(uint160).max); - execAmount = bound(execAmount, 0, minChunk - 1); - - DCAIntent memory intent = hook.createTestIntent(SWAPPER, NONCE, false, minChunk, maxChunk); - DCAOrderCosignerData memory cosignerData = - hook.createTestCosignerData(SWAPPER, NONCE, uint160(execAmount), type(uint160).max, 1); - - vm.expectRevert(abi.encodeWithSelector(IDCAHook.ChunkSizeBelowMin.selector, execAmount, minChunk)); - hook.validateChunkSize(intent, cosignerData, 100e18); - } - - function testFuzz_validateChunkSize_exactOut_revertAboveMax(uint256 minChunk, uint256 maxChunk, uint256 execAmount) - public - { - // Setup reasonable bounds - minChunk = bound(minChunk, 1, type(uint160).max / 2); - maxChunk = bound(maxChunk, minChunk, type(uint160).max - 1); // Leave room for above max - execAmount = bound(execAmount, maxChunk + 1, type(uint160).max); - - DCAIntent memory intent = hook.createTestIntent(SWAPPER, NONCE, false, minChunk, maxChunk); - DCAOrderCosignerData memory cosignerData = - hook.createTestCosignerData(SWAPPER, NONCE, uint160(execAmount), type(uint160).max, 1); - - vm.expectRevert(abi.encodeWithSelector(IDCAHook.ChunkSizeAboveMax.selector, execAmount, maxChunk)); - hook.validateChunkSize(intent, cosignerData, 100e18); - } - - function testFuzz_validateChunkSize_exactIn_inputMismatch(uint256 execAmount, uint256 inputAmount) public { - vm.assume(execAmount != inputAmount); - vm.assume(execAmount > 0 && execAmount <= type(uint160).max); - vm.assume(inputAmount > 0 && inputAmount <= type(uint160).max); - - DCAIntent memory intent = hook.createTestIntent(SWAPPER, NONCE, true, 1, type(uint256).max); - DCAOrderCosignerData memory cosignerData = - hook.createTestCosignerData(SWAPPER, NONCE, uint160(execAmount), uint160(execAmount / 2), 1); - - vm.expectRevert(abi.encodeWithSelector(IDCAHook.InputAmountMismatch.selector, inputAmount, execAmount)); - hook.validateChunkSize(intent, cosignerData, inputAmount); - } - - function testFuzz_validateChunkSize_exactOut_inputAboveLimit(uint256 limit, uint256 inputAmount) public { - limit = bound(limit, 1, type(uint160).max - 1); // Leave room for above limit - inputAmount = bound(inputAmount, limit + 1, type(uint160).max); - - DCAIntent memory intent = hook.createTestIntent(SWAPPER, NONCE, false, 1, type(uint160).max); - DCAOrderCosignerData memory cosignerData = hook.createTestCosignerData(SWAPPER, NONCE, 50e18, uint160(limit), 1); - - vm.expectRevert(abi.encodeWithSelector(IDCAHook.InputAboveLimit.selector, inputAmount, limit)); - hook.validateChunkSize(intent, cosignerData, inputAmount); - } -} diff --git a/test/v4/hooks/dca/DCAHook_validateOutputDistribution.t.sol b/test/v4/hooks/dca/DCAHook_validateOutputDistribution.t.sol deleted file mode 100644 index d030ca60..00000000 --- a/test/v4/hooks/dca/DCAHook_validateOutputDistribution.t.sol +++ /dev/null @@ -1,89 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {Test} from "forge-std/Test.sol"; -import {IPermit2} from "permit2/src/interfaces/IPermit2.sol"; -import {DeployPermit2} from "../../../util/DeployPermit2.sol"; -import {DCAHookHarness} from "./DCAHookHarness.sol"; -import {IReactor} from "../../../../src/v4/interfaces/IReactor.sol"; -import { - DCAIntent, - DCAOrderCosignerData, - OutputAllocation, - PrivateIntent, - FeedInfo -} from "../../../../src/v4/hooks/dca/DCAStructs.sol"; -import {OutputToken} from "../../../../src/base/ReactorStructs.sol"; -import {IDCAHook} from "../../../../src/v4/interfaces/IDCAHook.sol"; - -contract DCAHook_validateOutputDistributionTest is Test, DeployPermit2 { - DCAHookHarness hook; - IPermit2 permit2; - address constant REACTOR_ADDRESS = address(0x2345); - IReactor constant REACTOR = IReactor(REACTOR_ADDRESS); - - address constant SWAPPER = address(0x1234); - uint96 constant NONCE = 42; - address constant COSIGNER = address(0x5678); - address constant RECIPIENT_A = address(0xAAAA); - address constant RECIPIENT_B = address(0xBBBB); - - function setUp() public { - permit2 = IPermit2(deployPermit2()); - hook = new DCAHookHarness(permit2, REACTOR); - } - - function _createExactOutIntent() internal view returns (DCAIntent memory) { - OutputAllocation[] memory allocations = new OutputAllocation[](2); - allocations[0] = OutputAllocation({recipient: RECIPIENT_A, basisPoints: 5000}); - allocations[1] = OutputAllocation({recipient: RECIPIENT_B, basisPoints: 5000}); - - PrivateIntent memory privateIntent = PrivateIntent({ - totalAmount: 0, exactFrequency: 0, numChunks: 0, salt: bytes32(0), oracleFeeds: new FeedInfo[](0) - }); - - return DCAIntent({ - swapper: SWAPPER, - nonce: NONCE, - chainId: block.chainid, - hookAddress: address(hook), - isExactIn: false, - inputToken: address(0x1111), - outputToken: address(0x2222), - cosigner: COSIGNER, - minPeriod: 0, - maxPeriod: 0, - minChunkSize: 1, - maxChunkSize: 10_000, - minPrice: 0, - deadline: block.timestamp + 1 days, - outputAllocations: allocations, - privateIntent: privateIntent - }); - } - - function test_validateOutputDistribution_exactOut_remainderAssignedToFirstMaxBpsRecipient() public { - DCAIntent memory intent = _createExactOutIntent(); - DCAOrderCosignerData memory cosignerData = hook.createTestCosignerData(SWAPPER, NONCE, 101, 0, 0); - - // 50/50 split with odd total: remainder should go to first max-bps recipient (RECIPIENT_A). - OutputToken[] memory outputs = new OutputToken[](2); - outputs[0] = OutputToken({token: intent.outputToken, amount: 51, recipient: RECIPIENT_A}); - outputs[1] = OutputToken({token: intent.outputToken, amount: 50, recipient: RECIPIENT_B}); - - hook.validateOutputDistribution(intent, cosignerData, outputs); - } - - function test_validateOutputDistribution_exactOut_remainderOnOtherRecipient_reverts() public { - DCAIntent memory intent = _createExactOutIntent(); - DCAOrderCosignerData memory cosignerData = hook.createTestCosignerData(SWAPPER, NONCE, 101, 0, 0); - - // Remainder incorrectly assigned to RECIPIENT_B should revert. - OutputToken[] memory outputs = new OutputToken[](2); - outputs[0] = OutputToken({token: intent.outputToken, amount: 50, recipient: RECIPIENT_A}); - outputs[1] = OutputToken({token: intent.outputToken, amount: 51, recipient: RECIPIENT_B}); - - vm.expectRevert(abi.encodeWithSelector(IDCAHook.AllocationMismatch.selector, RECIPIENT_A, 50, 51)); - hook.validateOutputDistribution(intent, cosignerData, outputs); - } -} diff --git a/test/v4/hooks/dca/DCAHook_validatePriceFloor.t.sol b/test/v4/hooks/dca/DCAHook_validatePriceFloor.t.sol deleted file mode 100644 index a4fc8484..00000000 --- a/test/v4/hooks/dca/DCAHook_validatePriceFloor.t.sol +++ /dev/null @@ -1,239 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {Test} from "forge-std/Test.sol"; -import {IPermit2} from "permit2/src/interfaces/IPermit2.sol"; -import {DeployPermit2} from "../../../util/DeployPermit2.sol"; -import {DCAHookHarness} from "./DCAHookHarness.sol"; -import {IReactor} from "../../../../src/v4/interfaces/IReactor.sol"; -import {IDCAHook} from "../../../../src/v4/interfaces/IDCAHook.sol"; - -contract DCAHook_validatePriceFloorTest is Test, DeployPermit2 { - DCAHookHarness hook; - IPermit2 permit2; - address constant REACTOR_ADDRESS = address(0x2345); - IReactor constant REACTOR = IReactor(REACTOR_ADDRESS); - - uint256 constant MIN_PRICE_1_TO_1 = 1e18; - uint256 constant MIN_PRICE_2_TO_1 = 2e18; - uint256 constant MIN_PRICE_HALF = 0.5e18; - uint256 constant MIN_PRICE_ZERO = 0; - - function setUp() public { - permit2 = IPermit2(deployPermit2()); - hook = new DCAHookHarness(permit2, REACTOR); - } - - // ============ EXACT_IN Tests ============ - - function test_validatePriceFloor_exactIn_success_noPriceFloor() public view { - hook.validatePriceFloor(true, 100e18, 50e18, MIN_PRICE_ZERO); - } - - function test_validatePriceFloor_exactIn_success_atExactMinPrice() public view { - // Price = output/input = 100/100 = 1.0 - hook.validatePriceFloor(true, 100e18, 100e18, MIN_PRICE_1_TO_1); - } - - function test_validatePriceFloor_exactIn_success_aboveMinPrice() public view { - // Price = output/input = 200/100 = 2.0, min = 1.0 - hook.validatePriceFloor(true, 100e18, 200e18, MIN_PRICE_1_TO_1); - } - - function test_validatePriceFloor_exactIn_revert_belowMinPrice() public { - // Price = output/input = 50/100 = 0.5, min = 1.0 - uint256 actualPrice = (50e18 * 1e18) / 100e18; - vm.expectRevert(abi.encodeWithSelector(IDCAHook.PriceBelowMin.selector, actualPrice, MIN_PRICE_1_TO_1)); - hook.validatePriceFloor(true, 100e18, 50e18, MIN_PRICE_1_TO_1); - } - - function test_validatePriceFloor_exactIn_revert_justBelowMinPrice() public { - // Price = 99.999.../100 < 1.0 - uint256 limitAmount = 99999999999999999999; - uint256 actualPrice = (limitAmount * 1e18) / 100e18; - vm.expectRevert(abi.encodeWithSelector(IDCAHook.PriceBelowMin.selector, actualPrice, MIN_PRICE_1_TO_1)); - hook.validatePriceFloor(true, 100e18, uint160(limitAmount), MIN_PRICE_1_TO_1); - } - - function test_validatePriceFloor_exactIn_success_largeNumbers() public view { - uint160 execAmount = 1000000e18; - uint160 limitAmount = 2000000e18; - // Price = 2000000/1000000 = 2.0, min = 1.5 - hook.validatePriceFloor(true, execAmount, limitAmount, 1.5e18); - } - - function test_validatePriceFloor_exactIn_success_smallNumbers() public view { - // Price = 2/1 = 2.0, min = 1.0 - hook.validatePriceFloor(true, 1, 2, MIN_PRICE_1_TO_1); - } - - function test_validatePriceFloor_exactIn_revert_zeroInput() public { - vm.expectRevert(); - hook.validatePriceFloor(true, 0, 100e18, MIN_PRICE_1_TO_1); - } - - // ============ EXACT_OUT Tests ============ - - function test_validatePriceFloor_exactOut_success_noPriceFloor() public view { - hook.validatePriceFloor(false, 100e18, 200e18, MIN_PRICE_ZERO); - } - - function test_validatePriceFloor_exactOut_success_atExactMinPrice() public view { - // Price = output/input = 100/100 = 1.0 - hook.validatePriceFloor(false, 100e18, 100e18, MIN_PRICE_1_TO_1); - } - - function test_validatePriceFloor_exactOut_success_aboveMinPrice() public view { - // Price = output/input = 100/50 = 2.0, min = 1.0 - hook.validatePriceFloor(false, 100e18, 50e18, MIN_PRICE_1_TO_1); - } - - function test_validatePriceFloor_exactOut_revert_belowMinPrice() public { - // Price = output/input = 100/200 = 0.5, min = 1.0 - uint256 actualPrice = (100e18 * 1e18) / 200e18; - vm.expectRevert(abi.encodeWithSelector(IDCAHook.PriceBelowMin.selector, actualPrice, MIN_PRICE_1_TO_1)); - hook.validatePriceFloor(false, 100e18, 200e18, MIN_PRICE_1_TO_1); - } - - function test_validatePriceFloor_exactOut_revert_justBelowMinPrice() public { - // Price = 100/100.000...01 < 1.0 - uint256 limitAmount = 100000000000000000001; - uint256 actualPrice = (100e18 * 1e18) / limitAmount; - vm.expectRevert(abi.encodeWithSelector(IDCAHook.PriceBelowMin.selector, actualPrice, MIN_PRICE_1_TO_1)); - hook.validatePriceFloor(false, 100e18, uint160(limitAmount), MIN_PRICE_1_TO_1); - } - - function test_validatePriceFloor_exactOut_success_largeNumbers() public view { - uint160 execAmount = 1000000e18; - uint160 limitAmount = 500000e18; - // Price = 1000000/500000 = 2.0, min = 1.5 - hook.validatePriceFloor(false, execAmount, limitAmount, 1.5e18); - } - - function test_validatePriceFloor_exactOut_success_smallNumbers() public view { - // Price = 2/1 = 2.0, min = 1.0 - hook.validatePriceFloor(false, 2, 1, MIN_PRICE_1_TO_1); - } - - function test_validatePriceFloor_exactOut_revert_zeroInput() public { - vm.expectRevert(); - hook.validatePriceFloor(false, 100e18, 0, MIN_PRICE_1_TO_1); - } - - // ============ Fuzz Tests ============ - - function testFuzz_validatePriceFloor_exactIn_success(uint160 execAmount, uint160 limitAmount, uint256 minPrice) - public - view - { - vm.assume(execAmount > 0 && execAmount <= type(uint160).max); - vm.assume(limitAmount > 0 && limitAmount <= type(uint160).max); - vm.assume(minPrice <= 1e36); // Reasonable price range - - // Calculate actual price - uint256 actualPrice = (uint256(limitAmount) * 1e18) / uint256(execAmount); - - // Only test cases where price >= minPrice - vm.assume(actualPrice >= minPrice); - - hook.validatePriceFloor(true, execAmount, limitAmount, minPrice); - } - - function testFuzz_validatePriceFloor_exactIn_revert(uint160 execAmount, uint160 limitAmount, uint256 minPrice) - public - { - vm.assume(execAmount > 0 && execAmount <= type(uint160).max); - vm.assume(limitAmount > 0 && limitAmount <= type(uint160).max); - vm.assume(minPrice > 0 && minPrice <= 1e36); - - // Calculate actual price - uint256 actualPrice = (uint256(limitAmount) * 1e18) / uint256(execAmount); - - // Only test cases where price < minPrice - vm.assume(actualPrice < minPrice); - - vm.expectRevert(); - hook.validatePriceFloor(true, execAmount, limitAmount, minPrice); - } - - function testFuzz_validatePriceFloor_exactOut_success(uint160 execAmount, uint160 limitAmount, uint256 minPrice) - public - view - { - vm.assume(execAmount > 0 && execAmount <= type(uint160).max); - vm.assume(limitAmount > 0 && limitAmount <= type(uint160).max); - vm.assume(minPrice <= 1e36); - - // Calculate actual price - uint256 actualPrice = (uint256(execAmount) * 1e18) / uint256(limitAmount); - - // Only test cases where price >= minPrice - vm.assume(actualPrice >= minPrice); - - hook.validatePriceFloor(false, execAmount, limitAmount, minPrice); - } - - function testFuzz_validatePriceFloor_exactOut_revert(uint160 execAmount, uint160 limitAmount, uint256 minPrice) - public - { - vm.assume(execAmount > 0 && execAmount <= type(uint160).max); - vm.assume(limitAmount > 0 && limitAmount <= type(uint160).max); - vm.assume(minPrice > 0 && minPrice <= 1e36); - - // Calculate actual price - uint256 actualPrice = (uint256(execAmount) * 1e18) / uint256(limitAmount); - - // Only test cases where price < minPrice - vm.assume(actualPrice < minPrice); - - vm.expectRevert(); - hook.validatePriceFloor(false, execAmount, limitAmount, minPrice); - } - - // ============ Edge Cases ============ - - function test_validatePriceFloor_exactIn_maxValues() public view { - uint160 maxUint160 = type(uint160).max; - // Price = max/max = 1.0 - hook.validatePriceFloor(true, maxUint160, maxUint160, MIN_PRICE_1_TO_1); - } - - function test_validatePriceFloor_exactOut_maxValues() public view { - uint160 maxUint160 = type(uint160).max; - // Price = max/max = 1.0 - hook.validatePriceFloor(false, maxUint160, maxUint160, MIN_PRICE_1_TO_1); - } - - function test_validatePriceFloor_exactIn_overflow_protection() public view { - uint160 execAmount = 1; - uint160 limitAmount = type(uint160).max; - // This should handle overflow gracefully - // Price = max/1 = very high, min = 1.0 - hook.validatePriceFloor(true, execAmount, limitAmount, MIN_PRICE_1_TO_1); - } - - function test_validatePriceFloor_exactOut_overflow_protection() public view { - uint160 execAmount = type(uint160).max; - uint160 limitAmount = 1; - // Price = max/1 = very high, min = 1.0 - hook.validatePriceFloor(false, execAmount, limitAmount, MIN_PRICE_1_TO_1); - } - - function test_validatePriceFloor_precision_18Decimals() public view { - // Testing with 18 decimal precision - uint160 execAmount = 1234567890123456789; - uint160 limitAmount = 2469135780246913578; - // Price = exactly 2.0 - hook.validatePriceFloor(true, execAmount, limitAmount, MIN_PRICE_2_TO_1); - } - - function test_validatePriceFloor_precision_nonStandardDecimals() public view { - // USDC-like (6 decimals) to WETH-like (18 decimals) - uint160 execAmount = 1000 * 1e6; // 1000 USDC - uint160 limitAmount = 1 * 1e18; // 1 WETH - uint256 minPrice = 1000 * 1e18; // 1 WETH = 1000 USDC - - // Price = (1e18 * 1e18) / (1000 * 1e6) = 1e30 / 1e9 = 1e21 = 1000 * 1e18 - hook.validatePriceFloor(true, execAmount, limitAmount, minPrice); - } -} diff --git a/test/v4/hooks/dca/DCAHook_validateStaticFields.t.sol b/test/v4/hooks/dca/DCAHook_validateStaticFields.t.sol deleted file mode 100644 index 671d7c08..00000000 --- a/test/v4/hooks/dca/DCAHook_validateStaticFields.t.sol +++ /dev/null @@ -1,324 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {Test} from "forge-std/Test.sol"; -import {IPermit2} from "permit2/src/interfaces/IPermit2.sol"; -import {DeployPermit2} from "../../../util/DeployPermit2.sol"; -import {DCAHookHarness} from "./DCAHookHarness.sol"; -import {IReactor} from "../../../../src/v4/interfaces/IReactor.sol"; -import {DCAIntent, OutputAllocation, PrivateIntent, FeedInfo} from "../../../../src/v4/hooks/dca/DCAStructs.sol"; -import {InputToken, OutputToken} from "../../../../src/base/ReactorStructs.sol"; -import {ResolvedOrder, OrderInfo} from "../../../../src/v4/base/ReactorStructs.sol"; -import {IPreExecutionHook, IPostExecutionHook} from "../../../../src/v4/interfaces/IHook.sol"; -import {ERC20} from "solmate/src/tokens/ERC20.sol"; -import {IDCAHook} from "../../../../src/v4/interfaces/IDCAHook.sol"; -import {IAuctionResolver} from "../../../../src/v4/interfaces/IAuctionResolver.sol"; - -contract DCAHook_validateStaticFieldsTest is Test, DeployPermit2 { - DCAHookHarness hook; - IPermit2 permit2; - address constant REACTOR_ADDRESS = address(0x2345); - IReactor constant REACTOR = IReactor(REACTOR_ADDRESS); - - address constant SWAPPER = address(0x1234); - address constant COSIGNER = address(0x5678); - address constant RECIPIENT_1 = address(0x9ABC); - address constant RECIPIENT_2 = address(0x4455); - - ERC20 constant INPUT_TOKEN = ERC20(address(0xAAAA)); - ERC20 constant OUTPUT_TOKEN = ERC20(address(0xBBBB)); - ERC20 constant WRONG_INPUT_TOKEN = ERC20(address(0xCCCC)); - ERC20 constant WRONG_OUTPUT_TOKEN = ERC20(address(0xDDDD)); - - uint256 constant CHAIN_ID = 1; - uint256 constant WRONG_CHAIN_ID = 137; - uint256 constant NONCE = 42; - - function setUp() public { - permit2 = IPermit2(deployPermit2()); - hook = new DCAHookHarness(permit2, REACTOR); - vm.chainId(CHAIN_ID); - } - - function _createIntent( - address hookAddress, - uint256 chainId, - address swapper, - address inputToken, - address outputToken - ) internal view returns (DCAIntent memory) { - OutputAllocation[] memory allocations = new OutputAllocation[](1); - allocations[0] = OutputAllocation({recipient: RECIPIENT_1, basisPoints: 10000}); - - PrivateIntent memory privateIntent = PrivateIntent({ - totalAmount: 1000e18, exactFrequency: 3600, numChunks: 10, salt: bytes32(0), oracleFeeds: new FeedInfo[](0) - }); - - return DCAIntent({ - swapper: swapper, - nonce: NONCE, - chainId: chainId, - hookAddress: hookAddress, - isExactIn: true, - inputToken: inputToken, - outputToken: outputToken, - cosigner: COSIGNER, - minPeriod: 300, - maxPeriod: 7200, - minChunkSize: 1e18, - maxChunkSize: 100e18, - minPrice: 0, - deadline: block.timestamp + 1 days, - outputAllocations: allocations, - privateIntent: privateIntent - }); - } - - function _createResolvedOrder(address swapper, ERC20 inputToken, ERC20 outputToken, uint256 outputCount) - internal - view - returns (ResolvedOrder memory) - { - OutputToken[] memory outputs = new OutputToken[](outputCount); - for (uint256 i = 0; i < outputCount; i++) { - outputs[i] = OutputToken({token: address(outputToken), amount: 100e18, recipient: RECIPIENT_1}); - } - - return ResolvedOrder({ - info: OrderInfo({ - reactor: IReactor(REACTOR_ADDRESS), - swapper: swapper, - nonce: NONCE, - deadline: block.timestamp + 1 days, - preExecutionHook: IPreExecutionHook(address(0)), - preExecutionHookData: "", - postExecutionHook: IPostExecutionHook(address(0)), - postExecutionHookData: "", - auctionResolver: IAuctionResolver(address(0)) - }), - input: InputToken({token: inputToken, amount: 10e18, maxAmount: 10e18}), - outputs: outputs, - sig: "", - hash: bytes32(0), - auctionResolver: address(0), - witnessTypeString: "" - }); - } - - function test_validateStaticFields_success() public view { - DCAIntent memory intent = - _createIntent(address(hook), CHAIN_ID, SWAPPER, address(INPUT_TOKEN), address(OUTPUT_TOKEN)); - - ResolvedOrder memory order = _createResolvedOrder(SWAPPER, INPUT_TOKEN, OUTPUT_TOKEN, 1); - - hook.validateStaticFields(intent, order); - } - - function test_validateStaticFields_success_multipleOutputs() public view { - DCAIntent memory intent = - _createIntent(address(hook), CHAIN_ID, SWAPPER, address(INPUT_TOKEN), address(OUTPUT_TOKEN)); - - ResolvedOrder memory order = _createResolvedOrder(SWAPPER, INPUT_TOKEN, OUTPUT_TOKEN, 3); - - hook.validateStaticFields(intent, order); - } - - function test_validateStaticFields_revert_wrongHook() public { - address wrongHook = address(0xEEEE); - DCAIntent memory intent = - _createIntent(wrongHook, CHAIN_ID, SWAPPER, address(INPUT_TOKEN), address(OUTPUT_TOKEN)); - - ResolvedOrder memory order = _createResolvedOrder(SWAPPER, INPUT_TOKEN, OUTPUT_TOKEN, 1); - - vm.expectRevert(abi.encodeWithSelector(IDCAHook.WrongHook.selector, wrongHook, address(hook))); - hook.validateStaticFields(intent, order); - } - - function test_validateStaticFields_revert_wrongChain() public { - DCAIntent memory intent = - _createIntent(address(hook), WRONG_CHAIN_ID, SWAPPER, address(INPUT_TOKEN), address(OUTPUT_TOKEN)); - - ResolvedOrder memory order = _createResolvedOrder(SWAPPER, INPUT_TOKEN, OUTPUT_TOKEN, 1); - - vm.expectRevert(abi.encodeWithSelector(IDCAHook.WrongChain.selector, WRONG_CHAIN_ID, CHAIN_ID)); - hook.validateStaticFields(intent, order); - } - - function test_validateStaticFields_revert_swapperMismatch() public { - address wrongSwapper = address(0xFFFF); - DCAIntent memory intent = - _createIntent(address(hook), CHAIN_ID, SWAPPER, address(INPUT_TOKEN), address(OUTPUT_TOKEN)); - - ResolvedOrder memory order = _createResolvedOrder(wrongSwapper, INPUT_TOKEN, OUTPUT_TOKEN, 1); - - vm.expectRevert(abi.encodeWithSelector(IDCAHook.SwapperMismatch.selector, wrongSwapper, SWAPPER)); - hook.validateStaticFields(intent, order); - } - - function test_validateStaticFields_revert_wrongInputToken() public { - DCAIntent memory intent = - _createIntent(address(hook), CHAIN_ID, SWAPPER, address(INPUT_TOKEN), address(OUTPUT_TOKEN)); - - ResolvedOrder memory order = _createResolvedOrder(SWAPPER, WRONG_INPUT_TOKEN, OUTPUT_TOKEN, 1); - - vm.expectRevert( - abi.encodeWithSelector(IDCAHook.WrongInputToken.selector, address(WRONG_INPUT_TOKEN), address(INPUT_TOKEN)) - ); - hook.validateStaticFields(intent, order); - } - - function test_validateStaticFields_revert_wrongOutputToken_singleOutput() public { - DCAIntent memory intent = - _createIntent(address(hook), CHAIN_ID, SWAPPER, address(INPUT_TOKEN), address(OUTPUT_TOKEN)); - - ResolvedOrder memory order = _createResolvedOrder(SWAPPER, INPUT_TOKEN, WRONG_OUTPUT_TOKEN, 1); - - vm.expectRevert( - abi.encodeWithSelector( - IDCAHook.WrongOutputToken.selector, address(WRONG_OUTPUT_TOKEN), address(OUTPUT_TOKEN) - ) - ); - hook.validateStaticFields(intent, order); - } - - function test_validateStaticFields_revert_wrongOutputToken_multipleOutputs() public { - DCAIntent memory intent = - _createIntent(address(hook), CHAIN_ID, SWAPPER, address(INPUT_TOKEN), address(OUTPUT_TOKEN)); - - ResolvedOrder memory order = _createResolvedOrder(SWAPPER, INPUT_TOKEN, OUTPUT_TOKEN, 3); - - order.outputs[1].token = address(WRONG_OUTPUT_TOKEN); - - vm.expectRevert( - abi.encodeWithSelector( - IDCAHook.WrongOutputToken.selector, address(WRONG_OUTPUT_TOKEN), address(OUTPUT_TOKEN) - ) - ); - hook.validateStaticFields(intent, order); - } - - function test_validateStaticFields_revert_wrongOutputToken_lastOutput() public { - DCAIntent memory intent = - _createIntent(address(hook), CHAIN_ID, SWAPPER, address(INPUT_TOKEN), address(OUTPUT_TOKEN)); - - ResolvedOrder memory order = _createResolvedOrder(SWAPPER, INPUT_TOKEN, OUTPUT_TOKEN, 5); - - order.outputs[4].token = address(WRONG_OUTPUT_TOKEN); - - vm.expectRevert( - abi.encodeWithSelector( - IDCAHook.WrongOutputToken.selector, address(WRONG_OUTPUT_TOKEN), address(OUTPUT_TOKEN) - ) - ); - hook.validateStaticFields(intent, order); - } - - function test_validateStaticFields_revert_emptyOutputs() public view { - DCAIntent memory intent = - _createIntent(address(hook), CHAIN_ID, SWAPPER, address(INPUT_TOKEN), address(OUTPUT_TOKEN)); - - ResolvedOrder memory order = _createResolvedOrder(SWAPPER, INPUT_TOKEN, OUTPUT_TOKEN, 0); - - hook.validateStaticFields(intent, order); - } - - function test_validateStaticFields_orderOfValidation() public { - DCAIntent memory intent = _createIntent( - address(0xBAD), WRONG_CHAIN_ID, address(0x9999), address(WRONG_INPUT_TOKEN), address(WRONG_OUTPUT_TOKEN) - ); - - ResolvedOrder memory order = _createResolvedOrder(SWAPPER, INPUT_TOKEN, OUTPUT_TOKEN, 1); - - vm.expectRevert(abi.encodeWithSelector(IDCAHook.WrongHook.selector, address(0xBAD), address(hook))); - hook.validateStaticFields(intent, order); - } - - function testFuzz_validateStaticFields_differentAddresses( - address fuzzHook, - address fuzzSwapper, - address fuzzInputToken, - address fuzzOutputToken - ) public { - vm.assume(fuzzHook != address(0)); - vm.assume(fuzzSwapper != address(0)); - vm.assume(fuzzInputToken != address(0)); - vm.assume(fuzzOutputToken != address(0)); - vm.assume(fuzzHook != address(hook)); - - DCAIntent memory intent = _createIntent(fuzzHook, CHAIN_ID, fuzzSwapper, fuzzInputToken, fuzzOutputToken); - - ResolvedOrder memory order = _createResolvedOrder(SWAPPER, INPUT_TOKEN, OUTPUT_TOKEN, 1); - - vm.expectRevert(abi.encodeWithSelector(IDCAHook.WrongHook.selector, fuzzHook, address(hook))); - hook.validateStaticFields(intent, order); - } - - function testFuzz_validateStaticFields_differentChainIds(uint256 fuzzChainId) public { - vm.assume(fuzzChainId != CHAIN_ID); - vm.assume(fuzzChainId > 0 && fuzzChainId < type(uint256).max); - - DCAIntent memory intent = - _createIntent(address(hook), fuzzChainId, SWAPPER, address(INPUT_TOKEN), address(OUTPUT_TOKEN)); - - ResolvedOrder memory order = _createResolvedOrder(SWAPPER, INPUT_TOKEN, OUTPUT_TOKEN, 1); - - vm.expectRevert(abi.encodeWithSelector(IDCAHook.WrongChain.selector, fuzzChainId, CHAIN_ID)); - hook.validateStaticFields(intent, order); - } - - function testFuzz_validateStaticFields_multipleOutputs(uint8 outputCount) public view { - vm.assume(outputCount > 0 && outputCount <= 10); - - DCAIntent memory intent = - _createIntent(address(hook), CHAIN_ID, SWAPPER, address(INPUT_TOKEN), address(OUTPUT_TOKEN)); - - ResolvedOrder memory order = _createResolvedOrder(SWAPPER, INPUT_TOKEN, OUTPUT_TOKEN, outputCount); - - hook.validateStaticFields(intent, order); - } - - function test_validateStaticFields_allFieldsWrong() public { - DCAIntent memory intent = _createIntent( - address(0xBAD), WRONG_CHAIN_ID, address(0x8888), address(WRONG_INPUT_TOKEN), address(WRONG_OUTPUT_TOKEN) - ); - - ResolvedOrder memory order = _createResolvedOrder(SWAPPER, INPUT_TOKEN, OUTPUT_TOKEN, 1); - - vm.expectRevert(abi.encodeWithSelector(IDCAHook.WrongHook.selector, address(0xBAD), address(hook))); - hook.validateStaticFields(intent, order); - } - - function test_validateStaticFields_zeroAddressInputToken() public view { - DCAIntent memory intent = _createIntent(address(hook), CHAIN_ID, SWAPPER, address(0), address(OUTPUT_TOKEN)); - - ResolvedOrder memory order = _createResolvedOrder(SWAPPER, ERC20(address(0)), OUTPUT_TOKEN, 1); - - hook.validateStaticFields(intent, order); - } - - function test_validateStaticFields_zeroAddressOutputToken() public view { - DCAIntent memory intent = _createIntent(address(hook), CHAIN_ID, SWAPPER, address(INPUT_TOKEN), address(0)); - - ResolvedOrder memory order = _createResolvedOrder(SWAPPER, INPUT_TOKEN, ERC20(address(0)), 1); - - hook.validateStaticFields(intent, order); - } - - function test_validateStaticFields_zeroAddressSwapper() public view { - DCAIntent memory intent = - _createIntent(address(hook), CHAIN_ID, address(0), address(INPUT_TOKEN), address(OUTPUT_TOKEN)); - - ResolvedOrder memory order = _createResolvedOrder(address(0), INPUT_TOKEN, OUTPUT_TOKEN, 1); - - hook.validateStaticFields(intent, order); - } - - function test_validateStaticFields_identicalInputOutputTokens() public view { - DCAIntent memory intent = - _createIntent(address(hook), CHAIN_ID, SWAPPER, address(INPUT_TOKEN), address(INPUT_TOKEN)); - - ResolvedOrder memory order = _createResolvedOrder(SWAPPER, INPUT_TOKEN, INPUT_TOKEN, 1); - - hook.validateStaticFields(intent, order); - } -} diff --git a/test/v4/hooks/dca/DCALibGasTest.t.sol b/test/v4/hooks/dca/DCALibGasTest.t.sol deleted file mode 100644 index bf427404..00000000 --- a/test/v4/hooks/dca/DCALibGasTest.t.sol +++ /dev/null @@ -1,139 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.20; - -import {Test} from "forge-std/Test.sol"; - -import {DCALib} from "src/v4/hooks/dca/DCALib.sol"; -import {DCAOrderCosignerData} from "src/v4/hooks/dca/DCAStructs.sol"; -import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol"; - -/// @notice Mock ERC1271 wallet for testing smart contract signature validation -contract MockERC1271Wallet is IERC1271 { - address public owner; - - constructor(address _owner) { - owner = _owner; - } - - function isValidSignature(bytes32 hash, bytes memory signature) external view override returns (bytes4) { - // Recover the signer from the signature - (uint8 v, bytes32 r, bytes32 s) = abi.decode(signature, (uint8, bytes32, bytes32)); - address recovered = ecrecover(hash, v, r, s); - - // If the recovered address matches the owner, return the magic value - if (recovered == owner) { - return IERC1271.isValidSignature.selector; - } - return bytes4(0); - } -} - -/// @title DCALibGasTest -/// @notice Gas benchmarking tests for DCALib.isValidSignature function -/// @dev These tests measure gas consumption for EOA and ERC-1271 signature validation -contract DCALibGasTest is Test { - uint256 private constant PK = 0xAAAAA; - address private signer; - - function setUp() public { - signer = vm.addr(PK); - vm.chainId(1); - } - - function _domainSeparator() internal view returns (bytes32) { - return DCALib.computeDomainSeparator(address(this)); - } - - function _createCosignerData() internal view returns (DCAOrderCosignerData memory) { - return - DCAOrderCosignerData({ - swapper: signer, nonce: 42, execAmount: 100 ether, limitAmount: 95 ether, orderNonce: 5 - }); - } - - /// forge-config: default.isolate = true - /// @notice Gas benchmark: EOA signature validation (ECDSA path) - function testGas_isValidSignature_EOA() public { - bytes32 domainSeparator = _domainSeparator(); - DCAOrderCosignerData memory cosignerData = _createCosignerData(); - - // Hash and sign - bytes32 structHash = DCALib.hashCosignerData(cosignerData); - bytes32 digest = DCALib.digest(domainSeparator, structHash); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(PK, digest); - bytes memory sig = abi.encodePacked(r, s, v); - - // Measure gas for EOA signature validation - bool isValid = DCALib.isValidSignature(signer, digest, sig); - vm.snapshotGasLastCall("isValidSignature_EOA"); - require(isValid, "EOA signature should be valid"); - } - - /// forge-config: default.isolate = true - /// @notice Gas benchmark: Smart contract wallet signature validation (EIP-1271 path) - function testGas_isValidSignature_ERC1271() public { - bytes32 domainSeparator = _domainSeparator(); - - // Deploy mock ERC1271 wallet with signer as owner - MockERC1271Wallet wallet = new MockERC1271Wallet(signer); - - DCAOrderCosignerData memory cosignerData = _createCosignerData(); - - // Hash and sign with owner's key - bytes32 structHash = DCALib.hashCosignerData(cosignerData); - bytes32 digest = DCALib.digest(domainSeparator, structHash); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(PK, digest); - - // Encode signature for ERC1271 (v, r, s format) - bytes memory sig = abi.encode(v, r, s); - - // Measure gas for ERC1271 signature validation - bool isValid = DCALib.isValidSignature(address(wallet), digest, sig); - vm.snapshotGasLastCall("isValidSignature_ERC1271"); - require(isValid, "ERC1271 signature should be valid"); - } - - /// forge-config: default.isolate = true - /// @notice Gas benchmark: Invalid EOA signature - function testGas_isValidSignature_EOA_Invalid() public { - bytes32 domainSeparator = _domainSeparator(); - DCAOrderCosignerData memory cosignerData = _createCosignerData(); - - // Hash and sign with WRONG key - bytes32 structHash = DCALib.hashCosignerData(cosignerData); - bytes32 digest = DCALib.digest(domainSeparator, structHash); - uint256 wrongKey = 0x99999; - (uint8 v, bytes32 r, bytes32 s) = vm.sign(wrongKey, digest); - bytes memory sig = abi.encodePacked(r, s, v); - - // Measure gas for invalid EOA signature validation - bool isValid = DCALib.isValidSignature(signer, digest, sig); - vm.snapshotGasLastCall("isValidSignature_EOA_Invalid"); - require(!isValid, "Invalid EOA signature should fail"); - } - - /// forge-config: default.isolate = true - /// @notice Gas benchmark: Invalid ERC1271 signature - function testGas_isValidSignature_ERC1271_Invalid() public { - bytes32 domainSeparator = _domainSeparator(); - - // Deploy mock ERC1271 wallet with signer as owner - MockERC1271Wallet wallet = new MockERC1271Wallet(signer); - - DCAOrderCosignerData memory cosignerData = _createCosignerData(); - - // Hash and sign with WRONG key - bytes32 structHash = DCALib.hashCosignerData(cosignerData); - bytes32 digest = DCALib.digest(domainSeparator, structHash); - uint256 wrongKey = 0x88888; - (uint8 v, bytes32 r, bytes32 s) = vm.sign(wrongKey, digest); - - // Encode signature for ERC1271 (v, r, s format) - bytes memory sig = abi.encode(v, r, s); - - // Measure gas for invalid ERC1271 signature validation - bool isValid = DCALib.isValidSignature(address(wallet), digest, sig); - vm.snapshotGasLastCall("isValidSignature_ERC1271_Invalid"); - require(!isValid, "Invalid ERC1271 signature should fail"); - } -} diff --git a/test/v4/hooks/dca/DCALibTest.t.sol b/test/v4/hooks/dca/DCALibTest.t.sol deleted file mode 100644 index 8945e5fc..00000000 --- a/test/v4/hooks/dca/DCALibTest.t.sol +++ /dev/null @@ -1,390 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.20; - -import {Test} from "forge-std/Test.sol"; - -import {DCALib} from "src/v4/hooks/dca/DCALib.sol"; -import { - DCAIntent, - PrivateIntent, - OutputAllocation, - DCAOrderCosignerData, - FeedInfo, - FeedTemplate -} from "src/v4/hooks/dca/DCAStructs.sol"; - -contract DCALibTest is Test { - // deterministic test key - uint256 private constant PK = 0xAAAAA; - address private signer; - - string constant NAME = "DCAHook"; - string constant VERSION = "1"; - uint256 constant CHAINID = 1; - - function setUp() public { - signer = vm.addr(PK); - vm.chainId(CHAINID); - } - - // --- helpers --- - - function _domainSeparator(address verifying) internal view returns (bytes32) { - return DCALib.computeDomainSeparator(verifying); - } - - function _sampleIntent(address verifying, uint256 deadline) internal view returns (DCAIntent memory) { - // build PrivateIntent - PrivateIntent memory priv = PrivateIntent({ - totalAmount: 1000, - exactFrequency: 3600, // 1h - numChunks: 10, - salt: keccak256("test-salt"), - oracleFeeds: _feedIds() - }); - - // one or more output allocations - OutputAllocation[] memory outs = new OutputAllocation[](2); - outs[0] = OutputAllocation({recipient: address(0xAAAAA), basisPoints: 9975}); - outs[1] = OutputAllocation({recipient: address(0xFFFFF), basisPoints: 25}); - - // outer struct - return DCAIntent({ - swapper: signer, - nonce: 42, - chainId: CHAINID, - hookAddress: verifying, - isExactIn: true, - inputToken: address(0x1111), - outputToken: address(0x2222), - cosigner: address(0x3333), - minPeriod: 300, - maxPeriod: 7200, - minChunkSize: 1, - maxChunkSize: 200, - minPrice: 0, - deadline: deadline, - outputAllocations: outs, - privateIntent: priv - }); - } - - function _sampleIntentOnChain(address verifying, uint256 deadline) internal view returns (DCAIntent memory) { - // build PrivateIntent with all 0s - PrivateIntent memory priv = PrivateIntent({ - totalAmount: 0, exactFrequency: 0, numChunks: 0, salt: bytes32(0), oracleFeeds: new FeedInfo[](0) - }); - - // one or more output allocations - OutputAllocation[] memory outs = new OutputAllocation[](2); - outs[0] = OutputAllocation({recipient: address(0xAAAAA), basisPoints: 9975}); - outs[1] = OutputAllocation({recipient: address(0xFFFFF), basisPoints: 25}); - - // outer struct - return DCAIntent({ - swapper: signer, - nonce: 42, - chainId: CHAINID, - hookAddress: verifying, - isExactIn: true, - inputToken: address(0x1111), - outputToken: address(0x2222), - cosigner: address(0x3333), - minPeriod: 300, - maxPeriod: 7200, - minChunkSize: 1, - maxChunkSize: 200, - minPrice: 0, - deadline: deadline, - outputAllocations: outs, - privateIntent: priv - }); - } - - function _feedIds() internal pure returns (FeedInfo[] memory a) { - a = new FeedInfo[](2); - - string[] memory params0 = new string[](0); - string[] memory secrets0 = new string[](0); - - a[0] = FeedInfo({ - feedTemplate: FeedTemplate({ - name: "feed-0", - expression: "$average(data.prices)", - parameters: params0, - secrets: secrets0, - retryCount: 3 - }), - feedAddress: address(0x1111111111111111111111111111111111111111), - feedType: "price" - }); - - string[] memory params1 = new string[](0); - string[] memory secrets1 = new string[](0); - - a[1] = FeedInfo({ - feedTemplate: FeedTemplate({ - name: "feed-1", - expression: "$average(data.prices)", - parameters: params1, - secrets: secrets1, - retryCount: 5 - }), - feedAddress: address(0x2222222222222222222222222222222222222222), - feedType: "price" - }); - } - - // --- tests --- - - function test_HashEquivalence_FullVsInnerHash() public view { - address verifying = address(this); - bytes32 domainSeparator = _domainSeparator(verifying); - uint256 deadline = block.timestamp + 1000; - - DCAIntent memory msgFull = _sampleIntent(verifying, deadline); - DCAIntent memory msgPartial = _sampleIntentOnChain(verifying, deadline); - - // 1) struct hash via full nested struct - bytes32 structFull = DCALib.hash(msgFull); - - // 2) struct hash via only inner struct hash - bytes32 innerHash = DCALib.hashPrivateIntent(msgFull.privateIntent); - // This struct has the private part 0'd out - bytes32 structFromInner = DCALib.hashWithInnerHash(msgPartial, innerHash); - - assertEq(structFromInner, structFull, "struct hashes must match"); - // 3) wrap into EIP-712 digest - bytes32 digest1 = DCALib.digest(domainSeparator, structFull); - bytes32 digest2 = DCALib.digest(domainSeparator, structFromInner); - assertEq(digest1, digest2, "digests must match"); - } - - function test_SignAndValidate() public view { - address verifying = address(this); - bytes32 domainSeparator = _domainSeparator(verifying); - - uint256 deadline = block.timestamp + 1000; - DCAIntent memory msgFull = _sampleIntent(verifying, deadline); - DCAIntent memory msgPartial = _sampleIntentOnChain(verifying, deadline); - - // Hashes - bytes32 structFull = DCALib.hash(msgFull); - bytes32 innerHash = DCALib.hashPrivateIntent(msgFull.privateIntent); - bytes32 structFromInner = DCALib.hashWithInnerHash(msgPartial, innerHash); - assertEq(structFromInner, structFull); - - // Digests - bytes32 digest = DCALib.digest(domainSeparator, structFull); - bytes32 digest2 = DCALib.digest(domainSeparator, structFromInner); - assertEq(digest2, digest); - - // Sign and validate - (uint8 v, bytes32 r, bytes32 s) = vm.sign(PK, digest); - bytes memory sig = abi.encodePacked(r, s, v); - - assertTrue(DCALib.isValidSignature(signer, digest, sig), "signature validation (full) must succeed"); - assertTrue(DCALib.isValidSignature(signer, digest2, sig), "signature validation (from inner hash) must succeed"); - } - - function test_Negative_WrongInnerHashBreaksVerification() public view { - address verifying = address(this); - bytes32 domainSeparator = _domainSeparator(verifying); - - uint256 deadline = block.timestamp + 1000; - DCAIntent memory msgFull = _sampleIntent(verifying, deadline); - - // Sign correct digest - bytes32 structFull = DCALib.hash(msgFull); - bytes32 digest = DCALib.digest(domainSeparator, structFull); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(PK, digest); - bytes memory sig = abi.encodePacked(r, s, v); - - // Tamper only the inner hash - PrivateIntent memory tampered = msgFull.privateIntent; - tampered.numChunks = tampered.numChunks + 1; - bytes32 wrongInner = DCALib.hashPrivateIntent(tampered); - - // Rebuild digest using same outer fields but wrong inner hash - bytes32 structWrong = DCALib.hashWithInnerHash(msgFull, wrongInner); - bytes32 digestWrong = DCALib.digest(domainSeparator, structWrong); - assertTrue(digestWrong != digest, "tampered digest should differ"); - - assertFalse(DCALib.isValidSignature(signer, digestWrong, sig), "validation should fail on wrong digest"); - } - - function test_Negative_WrongOuterField_EvenWithCorrectInnerHash() public view { - address verifying = address(this); - bytes32 domainSeparator = _domainSeparator(verifying); - - uint256 deadline = block.timestamp + 1000; - - // 1) Build the original full message and sign its digest - DCAIntent memory msgFull = _sampleIntent(verifying, deadline); - bytes32 structFull = DCALib.hash(msgFull); - bytes32 digest = DCALib.digest(domainSeparator, structFull); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(PK, digest); - bytes memory sig = abi.encodePacked(r, s, v); - - // 2) Compute the CORRECT inner hash from the original inner struct - bytes32 innerHash = DCALib.hashPrivateIntent(msgFull.privateIntent); - - // 3) Tamper an OUTER field (keep inner hash the same) - DCAIntent memory tamperedOuter = msgFull; - tamperedOuter.minChunkSize = tamperedOuter.minChunkSize + 2000; // mutate some outer field - - // 4) Rebuild the outer struct hash using the tampered outer + correct innerHash - bytes32 structWrong = DCALib.hashWithInnerHash(tamperedOuter, innerHash); - bytes32 digestWrong = DCALib.digest(domainSeparator, structWrong); - - // 5) The digest must differ and validation must fail - assertTrue(digestWrong != digest, "tampered outer digest should differ"); - assertFalse( - DCALib.isValidSignature(signer, digestWrong, sig), "validation should fail on tampered outer digest" - ); - } - - // --- Cosigner Data Tests --- - - function _sampleCosignerData() internal view returns (DCAOrderCosignerData memory) { - return - DCAOrderCosignerData({ - swapper: signer, nonce: 42, execAmount: 100 ether, limitAmount: 95 ether, orderNonce: 5 - }); - } - - function test_CosignerData_HashAndValidate() public view { - address verifying = address(this); - bytes32 domainSeparator = _domainSeparator(verifying); - - DCAOrderCosignerData memory cosignerData = _sampleCosignerData(); - - // Hash the cosigner data - bytes32 structHash = DCALib.hashCosignerData(cosignerData); - bytes32 digest = DCALib.digest(domainSeparator, structHash); - - // Sign with cosigner private key - (uint8 v, bytes32 r, bytes32 s) = vm.sign(PK, digest); - bytes memory sig = abi.encodePacked(r, s, v); - - // Validate signature - assertTrue(DCALib.isValidSignature(signer, digest, sig), "Cosigner signature validation must succeed"); - } - - function test_CosignerData_DifferentFieldsProduceDifferentHashes() public view { - DCAOrderCosignerData memory data1 = _sampleCosignerData(); - DCAOrderCosignerData memory data2 = _sampleCosignerData(); - - bytes32 hash1 = DCALib.hashCosignerData(data1); - bytes32 hashOriginal = DCALib.hashCosignerData(data2); - assertEq(hash1, hashOriginal, "Same data should produce same hash"); - - // Test each field produces different hash - data2.swapper = address(0xBEEF); - bytes32 hash2 = DCALib.hashCosignerData(data2); - assertTrue(hash2 != hash1, "Different swapper should produce different hash"); - - data2 = _sampleCosignerData(); - data2.nonce = 43; - hash2 = DCALib.hashCosignerData(data2); - assertTrue(hash2 != hash1, "Different nonce should produce different hash"); - - data2 = _sampleCosignerData(); - data2.execAmount = 101 ether; - hash2 = DCALib.hashCosignerData(data2); - assertTrue(hash2 != hash1, "Different execAmount should produce different hash"); - - data2 = _sampleCosignerData(); - data2.limitAmount = 96 ether; - hash2 = DCALib.hashCosignerData(data2); - assertTrue(hash2 != hash1, "Different limitAmount should produce different hash"); - - data2 = _sampleCosignerData(); - data2.orderNonce = 6; - hash2 = DCALib.hashCosignerData(data2); - assertTrue(hash2 != hash1, "Different orderNonce should produce different hash"); - } - - function test_CosignerData_WrongSignatureFails() public view { - address verifying = address(this); - bytes32 domainSeparator = _domainSeparator(verifying); - - DCAOrderCosignerData memory cosignerData = _sampleCosignerData(); - - // Hash the correct data - bytes32 structHash = DCALib.hashCosignerData(cosignerData); - bytes32 digest = DCALib.digest(domainSeparator, structHash); - - // Sign with cosigner private key - (uint8 v, bytes32 r, bytes32 s) = vm.sign(PK, digest); - bytes memory sig = abi.encodePacked(r, s, v); - - // Tamper with the data - cosignerData.execAmount = 200 ether; - bytes32 tamperedHash = DCALib.hashCosignerData(cosignerData); - bytes32 tamperedDigest = DCALib.digest(domainSeparator, tamperedHash); - - // Validation with tampered digest should fail - assertFalse(DCALib.isValidSignature(signer, tamperedDigest, sig), "Tampered data should not verify"); - } - - function test_CosignerData_CrossChainReplay() public view { - DCAOrderCosignerData memory cosignerData = _sampleCosignerData(); - - // Create domain separators for different chains/contracts - address verifying1 = address(0x1111); - address verifying2 = address(0x2222); - - bytes32 domain1 = _domainSeparator(verifying1); - bytes32 domain2 = _domainSeparator(verifying2); - - assertTrue(domain1 != domain2, "Different verifying contracts should have different domains"); - - // Same struct hash - bytes32 structHash = DCALib.hashCosignerData(cosignerData); - - // Different digests due to different domains - bytes32 digest1 = DCALib.digest(domain1, structHash); - bytes32 digest2 = DCALib.digest(domain2, structHash); - - assertTrue(digest1 != digest2, "Same data on different domains should produce different digests"); - - // Sign for domain1 - (uint8 v, bytes32 r, bytes32 s) = vm.sign(PK, digest1); - bytes memory sig = abi.encodePacked(r, s, v); - - // Verify signature is valid for domain1 - assertTrue(DCALib.isValidSignature(signer, digest1, sig), "Signature should be valid for domain1"); - - // Verify signature is invalid for domain2 (replay protection) - assertFalse(DCALib.isValidSignature(signer, digest2, sig), "Signature should be invalid for domain2"); - } - - function testFuzz_CosignerData_AllFields( - address swapper, - uint96 nonce, - uint160 execAmount, - uint160 limitAmount, - uint96 orderNonce - ) public view { - address verifying = address(this); - bytes32 domainSeparator = _domainSeparator(verifying); - - DCAOrderCosignerData memory cosignerData = DCAOrderCosignerData({ - swapper: swapper, nonce: nonce, execAmount: execAmount, orderNonce: orderNonce, limitAmount: limitAmount - }); - - // Hash should be deterministic - bytes32 hash1 = DCALib.hashCosignerData(cosignerData); - bytes32 hash2 = DCALib.hashCosignerData(cosignerData); - assertEq(hash1, hash2, "Hash should be deterministic"); - - // Create digest and sign - bytes32 digest = DCALib.digest(domainSeparator, hash1); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(PK, digest); - bytes memory sig = abi.encodePacked(r, s, v); - - // Signature validation should work - assertTrue(DCALib.isValidSignature(signer, digest, sig), "Signature validation should work for any valid data"); - } -} diff --git a/test/v4/hooks/dca/DCALib_EIP712Compliance.t.sol b/test/v4/hooks/dca/DCALib_EIP712Compliance.t.sol deleted file mode 100644 index c1ded7ff..00000000 --- a/test/v4/hooks/dca/DCALib_EIP712Compliance.t.sol +++ /dev/null @@ -1,255 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import "forge-std/Test.sol"; -import {DCALib} from "src/v4/hooks/dca/DCALib.sol"; -import {DCAIntent, PrivateIntent, OutputAllocation, FeedInfo, FeedTemplate} from "src/v4/hooks/dca/DCAStructs.sol"; -import {FFISignDCAIntent} from "./FFISignDCAIntent.sol"; - -/** - * @title DCALib EIP-712 Compliance Test - * @notice This test verifies that our inline assembly implementation of DCAIntent hashing - * produces the exact same results as standard JavaScript EIP-712 libraries (viem). - * This is critical to ensure users can sign intents with standard wallets. - */ -contract DCALibEIP712ComplianceTest is Test, FFISignDCAIntent { - address constant HOOK_ADDRESS = address(0x1111); - uint256 constant PRIVATE_KEY = 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef; - address immutable SIGNER; - - constructor() { - SIGNER = vm.addr(PRIVATE_KEY); - } - - function setUp() public { - // Label addresses for better trace output - vm.label(HOOK_ADDRESS, "DCAHook"); - vm.label(SIGNER, "Signer"); - } - - /** - * @notice Test that our Solidity hash matches the hash computed by standard JS library - * @dev This is the core test - if this passes, our assembly implementation is EIP-712 compliant - */ - function test_HashMatchesJavaScriptLibrary() public { - DCAIntent memory intent = _createSimpleIntent(); - - // Compute hash using our Solidity implementation (with inline assembly) - bytes32 solidityHash = DCALib.hash(intent); - - // Compute hash using standard JavaScript EIP-712 library (viem) - SignResult memory jsResult = ffi_signDCAIntent(PRIVATE_KEY, HOOK_ADDRESS, block.chainid, intent); - - // The hashes MUST match exactly - assertEq(solidityHash, jsResult.structHash, "Solidity hash does not match JavaScript library hash!"); - - console2.log("SUCCESS: Solidity hash matches JavaScript EIP-712 library"); - console2.log("Hash:", vm.toString(solidityHash)); - } - - /** - * @notice Test signature recovery - verifies complete EIP-712 flow - * @dev If we can recover the correct signer, our implementation is fully EIP-712 compliant - */ - function test_SignatureRecoveryFromJavaScript() public { - DCAIntent memory intent = _createSimpleIntent(); - - // Get signature from JavaScript using standard signTypedData - SignResult memory jsResult = ffi_signDCAIntent(PRIVATE_KEY, HOOK_ADDRESS, block.chainid, intent); - - // Compute the full EIP-712 digest - bytes32 domainSeparator = DCALib.computeDomainSeparator(HOOK_ADDRESS); - bytes32 structHash = DCALib.hash(intent); - bytes32 digest = DCALib.digest(domainSeparator, structHash); - - // Validate the signature from JavaScript - bool isValid = DCALib.isValidSignature(SIGNER, digest, jsResult.signature); - - // Should validate correctly - assertTrue(isValid, "Failed to validate JavaScript signature!"); - - console2.log("SUCCESS: Validated correct signer from standard wallet signature"); - console2.log("Expected:", SIGNER); - } - - /** - * @notice Test with complex intent (multiple allocations, oracle feeds) - */ - function test_ComplexIntentMatchesJavaScript() public { - DCAIntent memory intent = _createComplexIntent(); - - bytes32 solidityHash = DCALib.hash(intent); - SignResult memory jsResult = ffi_signDCAIntent(PRIVATE_KEY, HOOK_ADDRESS, block.chainid, intent); - - assertEq(solidityHash, jsResult.structHash, "Complex intent hash mismatch!"); - - console2.log("SUCCESS: Complex intent hash matches JavaScript library"); - } - - /** - * @notice Test hashWithInnerHash variant - */ - function test_HashWithInnerHashMatchesJavaScript() public { - DCAIntent memory intent = _createSimpleIntent(); - - // Pre-compute the private intent hash - bytes32 privateIntentHash = DCALib.hashPrivateIntent(intent.privateIntent); - - // Use the variant that accepts pre-computed hash - bytes32 solidityHash = DCALib.hashWithInnerHash(intent, privateIntentHash); - - // Should match the JavaScript library - SignResult memory jsResult = ffi_signDCAIntent(PRIVATE_KEY, HOOK_ADDRESS, block.chainid, intent); - - assertEq(solidityHash, jsResult.structHash, "hashWithInnerHash does not match JavaScript!"); - - console2.log("SUCCESS: hashWithInnerHash variant matches JavaScript library"); - } - - /** - * @notice Fuzz test - verify compliance across random inputs - */ - function testFuzz_HashMatchesJavaScript( - address swapper, - uint256 nonce, - uint256 minPeriod, - uint256 maxPeriod, - uint256 minChunkSize, - uint256 maxChunkSize, - uint256 minPrice, - uint256 deadline - ) public { - // Bound inputs to reasonable ranges - vm.assume(maxPeriod >= minPeriod && minPeriod > 0); - vm.assume(maxChunkSize >= minChunkSize && minChunkSize > 0); - vm.assume(deadline > block.timestamp); - vm.assume(swapper != address(0)); - - DCAIntent memory intent = DCAIntent({ - swapper: swapper, - nonce: nonce, - chainId: block.chainid, - hookAddress: HOOK_ADDRESS, - isExactIn: true, - inputToken: address(0x1), - outputToken: address(0x2), - cosigner: address(0x3), - minPeriod: minPeriod, - maxPeriod: maxPeriod, - minChunkSize: minChunkSize, - maxChunkSize: maxChunkSize, - minPrice: minPrice, - deadline: deadline, - outputAllocations: _createSimpleAllocations(), - privateIntent: _createSimplePrivateIntent() - }); - - bytes32 solidityHash = DCALib.hash(intent); - SignResult memory jsResult = ffi_signDCAIntent(PRIVATE_KEY, HOOK_ADDRESS, block.chainid, intent); - - assertEq(solidityHash, jsResult.structHash, "Fuzz test: hash mismatch!"); - } - - // Helper functions to create test data - - function _createSimpleIntent() internal view returns (DCAIntent memory) { - return DCAIntent({ - swapper: address(0xABCD), - nonce: 1, - chainId: block.chainid, - hookAddress: HOOK_ADDRESS, - isExactIn: true, - inputToken: address(0x1111111111111111111111111111111111111111), - outputToken: address(0x2222222222222222222222222222222222222222), - cosigner: address(0x3333333333333333333333333333333333333333), - minPeriod: 3600, - maxPeriod: 7200, - minChunkSize: 1e18, - maxChunkSize: 10e18, - minPrice: 1e18, - deadline: block.timestamp + 30 days, - outputAllocations: _createSimpleAllocations(), - privateIntent: _createSimplePrivateIntent() - }); - } - - function _createComplexIntent() internal view returns (DCAIntent memory) { - OutputAllocation[] memory allocations = new OutputAllocation[](3); - allocations[0] = OutputAllocation({recipient: address(0xAAAA), basisPoints: 5000}); - allocations[1] = OutputAllocation({recipient: address(0xBBBB), basisPoints: 3000}); - allocations[2] = OutputAllocation({recipient: address(0xCCCC), basisPoints: 2000}); - - FeedInfo[] memory feeds = new FeedInfo[](2); - - string[] memory params0 = new string[](0); - string[] memory secrets0 = new string[](0); - feeds[0] = FeedInfo({ - feedTemplate: FeedTemplate({ - name: "feed-1", expression: "$average(prices)", parameters: params0, secrets: secrets0, retryCount: 3 - }), - feedAddress: address(0xFEED1), - feedType: "asdf" - }); - - string[] memory params1 = new string[](0); - string[] memory secrets1 = new string[](0); - feeds[1] = FeedInfo({ - feedTemplate: FeedTemplate({ - name: "feed-2", expression: "$median(prices)", parameters: params1, secrets: secrets1, retryCount: 5 - }), - feedAddress: address(0xFEED2), - feedType: "qwer" - }); - - PrivateIntent memory privateIntent = PrivateIntent({ - totalAmount: 100e18, exactFrequency: 3600, numChunks: 10, salt: keccak256("test-salt"), oracleFeeds: feeds - }); - - return DCAIntent({ - swapper: address(0xABCD), - nonce: 42, - chainId: block.chainid, - hookAddress: HOOK_ADDRESS, - isExactIn: false, - inputToken: address(0x1111111111111111111111111111111111111111), - outputToken: address(0x2222222222222222222222222222222222222222), - cosigner: address(0x3333333333333333333333333333333333333333), - minPeriod: 1800, - maxPeriod: 14400, - minChunkSize: 5e18, - maxChunkSize: 50e18, - minPrice: 95e16, // 0.95e18 - deadline: block.timestamp + 60 days, - outputAllocations: allocations, - privateIntent: privateIntent - }); - } - - function _createSimpleAllocations() internal pure returns (OutputAllocation[] memory) { - OutputAllocation[] memory allocations = new OutputAllocation[](1); - allocations[0] = OutputAllocation({recipient: address(0x9999), basisPoints: 10000}); - return allocations; - } - - function _createSimplePrivateIntent() internal pure returns (PrivateIntent memory) { - FeedInfo[] memory feeds = new FeedInfo[](1); - - string[] memory params = new string[](0); - string[] memory secrets = new string[](0); - feeds[0] = FeedInfo({ - feedTemplate: FeedTemplate({ - name: "simple-feed", - expression: "$number(data.value)", - parameters: params, - secrets: secrets, - retryCount: 3 - }), - feedAddress: address(0xFEED), - feedType: "asdf" - }); - - return PrivateIntent({ - totalAmount: 100e18, exactFrequency: 3600, numChunks: 10, salt: bytes32(uint256(0x42)), oracleFeeds: feeds - }); - } -} diff --git a/test/v4/hooks/dca/FFISignDCAIntent.sol b/test/v4/hooks/dca/FFISignDCAIntent.sol deleted file mode 100644 index 24a8d785..00000000 --- a/test/v4/hooks/dca/FFISignDCAIntent.sol +++ /dev/null @@ -1,191 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import {CommonBase} from "forge-std/Base.sol"; -import {stdJson} from "forge-std/StdJson.sol"; -import {console2} from "forge-std/console2.sol"; -import {DCAIntent, PrivateIntent, OutputAllocation, FeedInfo, FeedTemplate} from "src/v4/hooks/dca/DCAStructs.sol"; - -contract FFISignDCAIntent is CommonBase { - using stdJson for string; - - struct SignResult { - bytes signature; - bytes32 structHash; - } - - function ffi_signDCAIntent(uint256 privateKey, address verifyingContract, uint256 chainId, DCAIntent memory intent) - public - returns (SignResult memory) - { - // Build arrays separately - string memory allocationsJson = _buildAllocationsArray(intent.outputAllocations); - string memory feedsJson = _buildFeedsArray(intent.privateIntent.oracleFeeds); - - // Build privateIntent - string memory privateIntentJson = _buildPrivateIntent(intent.privateIntent, feedsJson); - - // Build intent fields in parts - string memory intentPart1 = _buildIntentPart1(intent); - string memory intentPart2 = _buildIntentPart2(intent, allocationsJson, privateIntentJson); - - // Combine everything - string memory jsonObj = string.concat( - '{"privateKey":"', - vm.toString(privateKey), - '","verifyingContract":"', - vm.toString(verifyingContract), - '","chainId":', - vm.toString(chainId), - ',"intent":{', - intentPart1, - intentPart2, - "}}" - ); - - console2.log("FFI JSON Input:"); - console2.log(jsonObj); - - // Run the JavaScript script - string[] memory inputs = new string[](8); - inputs[0] = "npm"; - inputs[1] = "--silent"; - inputs[2] = "--prefix"; - inputs[3] = "./test/v4/hooks/dca/js-scripts"; - inputs[4] = "run"; - inputs[5] = "sign-dca-intent"; - inputs[6] = "--"; - inputs[7] = jsonObj; - - bytes memory result = vm.ffi(inputs); - - // Parse the JSON result - string memory resultStr = string(result); - bytes memory signature = vm.parseJsonBytes(resultStr, ".signature"); - bytes32 structHash = vm.parseJsonBytes32(resultStr, ".structHash"); - - return SignResult({signature: signature, structHash: structHash}); - } - - function _buildAllocationsArray(OutputAllocation[] memory allocations) private pure returns (string memory) { - string memory result = "["; - for (uint256 i = 0; i < allocations.length; i++) { - if (i > 0) result = string.concat(result, ","); - result = string.concat( - result, - '{"recipient":"', - vm.toString(allocations[i].recipient), - '","basisPoints":', - vm.toString(allocations[i].basisPoints), - "}" - ); - } - return string.concat(result, "]"); - } - - function _buildStringArray(string[] memory arr) private pure returns (string memory) { - string memory result = "["; - for (uint256 i = 0; i < arr.length; i++) { - if (i > 0) result = string.concat(result, ","); - result = string.concat(result, '"', arr[i], '"'); - } - return string.concat(result, "]"); - } - - function _buildFeedTemplate(FeedTemplate memory template) private pure returns (string memory) { - return string.concat( - '{"name":"', - template.name, - '","expression":"', - template.expression, - '","parameters":', - _buildStringArray(template.parameters), - ',"secrets":', - _buildStringArray(template.secrets), - ',"retryCount":', - vm.toString(template.retryCount), - "}" - ); - } - - function _buildFeedsArray(FeedInfo[] memory feeds) private pure returns (string memory) { - string memory result = "["; - for (uint256 i = 0; i < feeds.length; i++) { - if (i > 0) result = string.concat(result, ","); - result = string.concat( - result, - '{"feedTemplate":', - _buildFeedTemplate(feeds[i].feedTemplate), - ',"feedAddress":"', - vm.toString(feeds[i].feedAddress), - '","feedType":"', - feeds[i].feedType, - '"}' - ); - } - return string.concat(result, "]"); - } - - function _buildPrivateIntent(PrivateIntent memory p, string memory feedsJson) private pure returns (string memory) { - return string.concat( - '{"totalAmount":"', - vm.toString(p.totalAmount), - '","exactFrequency":"', - vm.toString(p.exactFrequency), - '","numChunks":"', - vm.toString(p.numChunks), - '","salt":"', - vm.toString(p.salt), - '","oracleFeeds":', - feedsJson, - "}" - ); - } - - function _buildIntentPart1(DCAIntent memory intent) private pure returns (string memory) { - return string.concat( - '"swapper":"', - vm.toString(intent.swapper), - '","nonce":"', - vm.toString(intent.nonce), - '","chainId":"', - vm.toString(intent.chainId), - '","hookAddress":"', - vm.toString(intent.hookAddress), - '","isExactIn":', - intent.isExactIn ? "true" : "false", - ',"inputToken":"', - vm.toString(intent.inputToken), - '","outputToken":"', - vm.toString(intent.outputToken), - '","cosigner":"', - vm.toString(intent.cosigner), - '",' - ); - } - - function _buildIntentPart2(DCAIntent memory intent, string memory allocationsJson, string memory privateIntentJson) - private - pure - returns (string memory) - { - return string.concat( - '"minPeriod":"', - vm.toString(intent.minPeriod), - '","maxPeriod":"', - vm.toString(intent.maxPeriod), - '","minChunkSize":"', - vm.toString(intent.minChunkSize), - '","maxChunkSize":"', - vm.toString(intent.maxChunkSize), - '","minPrice":"', - vm.toString(intent.minPrice), - '","deadline":"', - vm.toString(intent.deadline), - '","outputAllocations":', - allocationsJson, - ',"privateIntent":', - privateIntentJson - ); - } -} diff --git a/test/v4/hooks/dca/js-scripts/build.js b/test/v4/hooks/dca/js-scripts/build.js deleted file mode 100644 index 41c01655..00000000 --- a/test/v4/hooks/dca/js-scripts/build.js +++ /dev/null @@ -1,32 +0,0 @@ -const esbuild = require('esbuild'); -const fs = require('fs'); -const path = require('path'); - -// Ensure dist directory exists -const distDir = path.join(__dirname, 'dist'); -if (!fs.existsSync(distDir)) { - fs.mkdirSync(distDir, { recursive: true }); -} - -// Build all TypeScript files in src directory -const srcDir = path.join(__dirname, 'src'); -const files = fs.readdirSync(srcDir).filter(file => file.endsWith('.ts')); - -files.forEach(file => { - const inputFile = path.join(srcDir, file); - const outputFile = path.join(distDir, file.replace('.ts', '.js')); - - esbuild.buildSync({ - entryPoints: [inputFile], - bundle: true, - platform: 'node', - target: 'node18', - outfile: outputFile, - format: 'cjs', - external: [], - }); - - console.log(`Built ${file} -> ${path.basename(outputFile)}`); -}); - -console.log('Build complete!'); diff --git a/test/v4/hooks/dca/js-scripts/dist/sign-dca-intent.js b/test/v4/hooks/dca/js-scripts/dist/sign-dca-intent.js deleted file mode 100755 index 2556c390..00000000 --- a/test/v4/hooks/dca/js-scripts/dist/sign-dca-intent.js +++ /dev/null @@ -1,9204 +0,0 @@ -#!/usr/bin/env node -var __create = Object.create; -var __defProp = Object.defineProperty; -var __getOwnPropDesc = Object.getOwnPropertyDescriptor; -var __getOwnPropNames = Object.getOwnPropertyNames; -var __getProtoOf = Object.getPrototypeOf; -var __hasOwnProp = Object.prototype.hasOwnProperty; -var __esm = (fn, res) => function __init() { - return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res; -}; -var __export = (target, all) => { - for (var name in all) - __defProp(target, name, { get: all[name], enumerable: true }); -}; -var __copyProps = (to, from, except, desc) => { - if (from && typeof from === "object" || typeof from === "function") { - for (let key of __getOwnPropNames(from)) - if (!__hasOwnProp.call(to, key) && key !== except) - __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); - } - return to; -}; -var __toESM = (mod2, isNodeMode, target) => (target = mod2 != null ? __create(__getProtoOf(mod2)) : {}, __copyProps( - // If the importer is in node compatibility mode or this is not an ESM - // file that has been converted to a CommonJS file using a Babel- - // compatible transform (i.e. "__esModule" has not been set), then set - // "default" to the CommonJS "module.exports" for node compatibility. - isNodeMode || !mod2 || !mod2.__esModule ? __defProp(target, "default", { value: mod2, enumerable: true }) : target, - mod2 -)); - -// node_modules/@noble/hashes/esm/_assert.js -function anumber(n) { - if (!Number.isSafeInteger(n) || n < 0) - throw new Error("positive integer expected, got " + n); -} -function isBytes(a) { - return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array"; -} -function abytes(b, ...lengths) { - if (!isBytes(b)) - throw new Error("Uint8Array expected"); - if (lengths.length > 0 && !lengths.includes(b.length)) - throw new Error("Uint8Array expected of length " + lengths + ", got length=" + b.length); -} -function ahash(h) { - if (typeof h !== "function" || typeof h.create !== "function") - throw new Error("Hash should be wrapped by utils.wrapConstructor"); - anumber(h.outputLen); - anumber(h.blockLen); -} -function aexists(instance, checkFinished = true) { - if (instance.destroyed) - throw new Error("Hash instance has been destroyed"); - if (checkFinished && instance.finished) - throw new Error("Hash#digest() has already been called"); -} -function aoutput(out, instance) { - abytes(out); - const min = instance.outputLen; - if (out.length < min) { - throw new Error("digestInto() expects output buffer of length at least " + min); - } -} -var init_assert = __esm({ - "node_modules/@noble/hashes/esm/_assert.js"() { - } -}); - -// node_modules/@noble/hashes/esm/cryptoNode.js -var nc, crypto; -var init_cryptoNode = __esm({ - "node_modules/@noble/hashes/esm/cryptoNode.js"() { - nc = __toESM(require("node:crypto"), 1); - crypto = nc && typeof nc === "object" && "webcrypto" in nc ? nc.webcrypto : nc && typeof nc === "object" && "randomBytes" in nc ? nc : void 0; - } -}); - -// node_modules/@noble/hashes/esm/utils.js -function u32(arr) { - return new Uint32Array(arr.buffer, arr.byteOffset, Math.floor(arr.byteLength / 4)); -} -function createView(arr) { - return new DataView(arr.buffer, arr.byteOffset, arr.byteLength); -} -function rotr(word, shift) { - return word << 32 - shift | word >>> shift; -} -function byteSwap(word) { - return word << 24 & 4278190080 | word << 8 & 16711680 | word >>> 8 & 65280 | word >>> 24 & 255; -} -function byteSwap32(arr) { - for (let i = 0; i < arr.length; i++) { - arr[i] = byteSwap(arr[i]); - } -} -function utf8ToBytes(str) { - if (typeof str !== "string") - throw new Error("utf8ToBytes expected string, got " + typeof str); - return new Uint8Array(new TextEncoder().encode(str)); -} -function toBytes(data) { - if (typeof data === "string") - data = utf8ToBytes(data); - abytes(data); - return data; -} -function concatBytes(...arrays) { - let sum = 0; - for (let i = 0; i < arrays.length; i++) { - const a = arrays[i]; - abytes(a); - sum += a.length; - } - const res = new Uint8Array(sum); - for (let i = 0, pad2 = 0; i < arrays.length; i++) { - const a = arrays[i]; - res.set(a, pad2); - pad2 += a.length; - } - return res; -} -function wrapConstructor(hashCons) { - const hashC = (msg) => hashCons().update(toBytes(msg)).digest(); - const tmp = hashCons(); - hashC.outputLen = tmp.outputLen; - hashC.blockLen = tmp.blockLen; - hashC.create = () => hashCons(); - return hashC; -} -function wrapXOFConstructorWithOpts(hashCons) { - const hashC = (msg, opts) => hashCons(opts).update(toBytes(msg)).digest(); - const tmp = hashCons({}); - hashC.outputLen = tmp.outputLen; - hashC.blockLen = tmp.blockLen; - hashC.create = (opts) => hashCons(opts); - return hashC; -} -function randomBytes(bytesLength = 32) { - if (crypto && typeof crypto.getRandomValues === "function") { - return crypto.getRandomValues(new Uint8Array(bytesLength)); - } - if (crypto && typeof crypto.randomBytes === "function") { - return crypto.randomBytes(bytesLength); - } - throw new Error("crypto.getRandomValues must be defined"); -} -var isLE, Hash; -var init_utils = __esm({ - "node_modules/@noble/hashes/esm/utils.js"() { - init_cryptoNode(); - init_assert(); - isLE = /* @__PURE__ */ (() => new Uint8Array(new Uint32Array([287454020]).buffer)[0] === 68)(); - Hash = class { - // Safe version that clones internal state - clone() { - return this._cloneInto(); - } - }; - } -}); - -// node_modules/@noble/hashes/esm/hmac.js -var HMAC, hmac; -var init_hmac = __esm({ - "node_modules/@noble/hashes/esm/hmac.js"() { - init_assert(); - init_utils(); - HMAC = class extends Hash { - constructor(hash2, _key) { - super(); - this.finished = false; - this.destroyed = false; - ahash(hash2); - const key = toBytes(_key); - this.iHash = hash2.create(); - if (typeof this.iHash.update !== "function") - throw new Error("Expected instance of class which extends utils.Hash"); - this.blockLen = this.iHash.blockLen; - this.outputLen = this.iHash.outputLen; - const blockLen = this.blockLen; - const pad2 = new Uint8Array(blockLen); - pad2.set(key.length > blockLen ? hash2.create().update(key).digest() : key); - for (let i = 0; i < pad2.length; i++) - pad2[i] ^= 54; - this.iHash.update(pad2); - this.oHash = hash2.create(); - for (let i = 0; i < pad2.length; i++) - pad2[i] ^= 54 ^ 92; - this.oHash.update(pad2); - pad2.fill(0); - } - update(buf) { - aexists(this); - this.iHash.update(buf); - return this; - } - digestInto(out) { - aexists(this); - abytes(out, this.outputLen); - this.finished = true; - this.iHash.digestInto(out); - this.oHash.update(out); - this.oHash.digestInto(out); - this.destroy(); - } - digest() { - const out = new Uint8Array(this.oHash.outputLen); - this.digestInto(out); - return out; - } - _cloneInto(to) { - to || (to = Object.create(Object.getPrototypeOf(this), {})); - const { oHash, iHash, finished, destroyed, blockLen, outputLen } = this; - to = to; - to.finished = finished; - to.destroyed = destroyed; - to.blockLen = blockLen; - to.outputLen = outputLen; - to.oHash = oHash._cloneInto(to.oHash); - to.iHash = iHash._cloneInto(to.iHash); - return to; - } - destroy() { - this.destroyed = true; - this.oHash.destroy(); - this.iHash.destroy(); - } - }; - hmac = (hash2, key, message) => new HMAC(hash2, key).update(message).digest(); - hmac.create = (hash2, key) => new HMAC(hash2, key); - } -}); - -// node_modules/@noble/hashes/esm/_md.js -function setBigUint64(view, byteOffset, value, isLE2) { - if (typeof view.setBigUint64 === "function") - return view.setBigUint64(byteOffset, value, isLE2); - const _32n2 = BigInt(32); - const _u32_max = BigInt(4294967295); - const wh = Number(value >> _32n2 & _u32_max); - const wl = Number(value & _u32_max); - const h = isLE2 ? 4 : 0; - const l = isLE2 ? 0 : 4; - view.setUint32(byteOffset + h, wh, isLE2); - view.setUint32(byteOffset + l, wl, isLE2); -} -function Chi(a, b, c) { - return a & b ^ ~a & c; -} -function Maj(a, b, c) { - return a & b ^ a & c ^ b & c; -} -var HashMD; -var init_md = __esm({ - "node_modules/@noble/hashes/esm/_md.js"() { - init_assert(); - init_utils(); - HashMD = class extends Hash { - constructor(blockLen, outputLen, padOffset, isLE2) { - super(); - this.blockLen = blockLen; - this.outputLen = outputLen; - this.padOffset = padOffset; - this.isLE = isLE2; - this.finished = false; - this.length = 0; - this.pos = 0; - this.destroyed = false; - this.buffer = new Uint8Array(blockLen); - this.view = createView(this.buffer); - } - update(data) { - aexists(this); - const { view, buffer: buffer2, blockLen } = this; - data = toBytes(data); - const len = data.length; - for (let pos = 0; pos < len; ) { - const take = Math.min(blockLen - this.pos, len - pos); - if (take === blockLen) { - const dataView = createView(data); - for (; blockLen <= len - pos; pos += blockLen) - this.process(dataView, pos); - continue; - } - buffer2.set(data.subarray(pos, pos + take), this.pos); - this.pos += take; - pos += take; - if (this.pos === blockLen) { - this.process(view, 0); - this.pos = 0; - } - } - this.length += data.length; - this.roundClean(); - return this; - } - digestInto(out) { - aexists(this); - aoutput(out, this); - this.finished = true; - const { buffer: buffer2, view, blockLen, isLE: isLE2 } = this; - let { pos } = this; - buffer2[pos++] = 128; - this.buffer.subarray(pos).fill(0); - if (this.padOffset > blockLen - pos) { - this.process(view, 0); - pos = 0; - } - for (let i = pos; i < blockLen; i++) - buffer2[i] = 0; - setBigUint64(view, blockLen - 8, BigInt(this.length * 8), isLE2); - this.process(view, 0); - const oview = createView(out); - const len = this.outputLen; - if (len % 4) - throw new Error("_sha2: outputLen should be aligned to 32bit"); - const outLen = len / 4; - const state = this.get(); - if (outLen > state.length) - throw new Error("_sha2: outputLen bigger than state"); - for (let i = 0; i < outLen; i++) - oview.setUint32(4 * i, state[i], isLE2); - } - digest() { - const { buffer: buffer2, outputLen } = this; - this.digestInto(buffer2); - const res = buffer2.slice(0, outputLen); - this.destroy(); - return res; - } - _cloneInto(to) { - to || (to = new this.constructor()); - to.set(...this.get()); - const { blockLen, buffer: buffer2, length, finished, destroyed, pos } = this; - to.length = length; - to.pos = pos; - to.finished = finished; - to.destroyed = destroyed; - if (length % blockLen) - to.buffer.set(buffer2); - return to; - } - }; - } -}); - -// node_modules/@noble/hashes/esm/sha256.js -var SHA256_K, SHA256_IV, SHA256_W, SHA256, sha256; -var init_sha256 = __esm({ - "node_modules/@noble/hashes/esm/sha256.js"() { - init_md(); - init_utils(); - SHA256_K = /* @__PURE__ */ new Uint32Array([ - 1116352408, - 1899447441, - 3049323471, - 3921009573, - 961987163, - 1508970993, - 2453635748, - 2870763221, - 3624381080, - 310598401, - 607225278, - 1426881987, - 1925078388, - 2162078206, - 2614888103, - 3248222580, - 3835390401, - 4022224774, - 264347078, - 604807628, - 770255983, - 1249150122, - 1555081692, - 1996064986, - 2554220882, - 2821834349, - 2952996808, - 3210313671, - 3336571891, - 3584528711, - 113926993, - 338241895, - 666307205, - 773529912, - 1294757372, - 1396182291, - 1695183700, - 1986661051, - 2177026350, - 2456956037, - 2730485921, - 2820302411, - 3259730800, - 3345764771, - 3516065817, - 3600352804, - 4094571909, - 275423344, - 430227734, - 506948616, - 659060556, - 883997877, - 958139571, - 1322822218, - 1537002063, - 1747873779, - 1955562222, - 2024104815, - 2227730452, - 2361852424, - 2428436474, - 2756734187, - 3204031479, - 3329325298 - ]); - SHA256_IV = /* @__PURE__ */ new Uint32Array([ - 1779033703, - 3144134277, - 1013904242, - 2773480762, - 1359893119, - 2600822924, - 528734635, - 1541459225 - ]); - SHA256_W = /* @__PURE__ */ new Uint32Array(64); - SHA256 = class extends HashMD { - constructor() { - super(64, 32, 8, false); - this.A = SHA256_IV[0] | 0; - this.B = SHA256_IV[1] | 0; - this.C = SHA256_IV[2] | 0; - this.D = SHA256_IV[3] | 0; - this.E = SHA256_IV[4] | 0; - this.F = SHA256_IV[5] | 0; - this.G = SHA256_IV[6] | 0; - this.H = SHA256_IV[7] | 0; - } - get() { - const { A, B, C, D, E, F, G, H } = this; - return [A, B, C, D, E, F, G, H]; - } - // prettier-ignore - set(A, B, C, D, E, F, G, H) { - this.A = A | 0; - this.B = B | 0; - this.C = C | 0; - this.D = D | 0; - this.E = E | 0; - this.F = F | 0; - this.G = G | 0; - this.H = H | 0; - } - process(view, offset) { - for (let i = 0; i < 16; i++, offset += 4) - SHA256_W[i] = view.getUint32(offset, false); - for (let i = 16; i < 64; i++) { - const W15 = SHA256_W[i - 15]; - const W2 = SHA256_W[i - 2]; - const s0 = rotr(W15, 7) ^ rotr(W15, 18) ^ W15 >>> 3; - const s1 = rotr(W2, 17) ^ rotr(W2, 19) ^ W2 >>> 10; - SHA256_W[i] = s1 + SHA256_W[i - 7] + s0 + SHA256_W[i - 16] | 0; - } - let { A, B, C, D, E, F, G, H } = this; - for (let i = 0; i < 64; i++) { - const sigma1 = rotr(E, 6) ^ rotr(E, 11) ^ rotr(E, 25); - const T1 = H + sigma1 + Chi(E, F, G) + SHA256_K[i] + SHA256_W[i] | 0; - const sigma0 = rotr(A, 2) ^ rotr(A, 13) ^ rotr(A, 22); - const T2 = sigma0 + Maj(A, B, C) | 0; - H = G; - G = F; - F = E; - E = D + T1 | 0; - D = C; - C = B; - B = A; - A = T1 + T2 | 0; - } - A = A + this.A | 0; - B = B + this.B | 0; - C = C + this.C | 0; - D = D + this.D | 0; - E = E + this.E | 0; - F = F + this.F | 0; - G = G + this.G | 0; - H = H + this.H | 0; - this.set(A, B, C, D, E, F, G, H); - } - roundClean() { - SHA256_W.fill(0); - } - destroy() { - this.set(0, 0, 0, 0, 0, 0, 0, 0); - this.buffer.fill(0); - } - }; - sha256 = /* @__PURE__ */ wrapConstructor(() => new SHA256()); - } -}); - -// node_modules/@noble/hashes/esm/_u64.js -function fromBig(n, le = false) { - if (le) - return { h: Number(n & U32_MASK64), l: Number(n >> _32n & U32_MASK64) }; - return { h: Number(n >> _32n & U32_MASK64) | 0, l: Number(n & U32_MASK64) | 0 }; -} -function split(lst, le = false) { - let Ah = new Uint32Array(lst.length); - let Al = new Uint32Array(lst.length); - for (let i = 0; i < lst.length; i++) { - const { h, l } = fromBig(lst[i], le); - [Ah[i], Al[i]] = [h, l]; - } - return [Ah, Al]; -} -var U32_MASK64, _32n, rotlSH, rotlSL, rotlBH, rotlBL; -var init_u64 = __esm({ - "node_modules/@noble/hashes/esm/_u64.js"() { - U32_MASK64 = /* @__PURE__ */ BigInt(2 ** 32 - 1); - _32n = /* @__PURE__ */ BigInt(32); - rotlSH = (h, l, s) => h << s | l >>> 32 - s; - rotlSL = (h, l, s) => l << s | h >>> 32 - s; - rotlBH = (h, l, s) => l << s - 32 | h >>> 64 - s; - rotlBL = (h, l, s) => h << s - 32 | l >>> 64 - s; - } -}); - -// node_modules/@noble/curves/esm/abstract/utils.js -var utils_exports = {}; -__export(utils_exports, { - aInRange: () => aInRange, - abool: () => abool, - abytes: () => abytes2, - bitGet: () => bitGet, - bitLen: () => bitLen, - bitMask: () => bitMask, - bitSet: () => bitSet, - bytesToHex: () => bytesToHex, - bytesToNumberBE: () => bytesToNumberBE, - bytesToNumberLE: () => bytesToNumberLE, - concatBytes: () => concatBytes2, - createHmacDrbg: () => createHmacDrbg, - ensureBytes: () => ensureBytes, - equalBytes: () => equalBytes, - hexToBytes: () => hexToBytes, - hexToNumber: () => hexToNumber, - inRange: () => inRange, - isBytes: () => isBytes2, - memoized: () => memoized, - notImplemented: () => notImplemented, - numberToBytesBE: () => numberToBytesBE, - numberToBytesLE: () => numberToBytesLE, - numberToHexUnpadded: () => numberToHexUnpadded, - numberToVarBytesBE: () => numberToVarBytesBE, - utf8ToBytes: () => utf8ToBytes2, - validateObject: () => validateObject -}); -function isBytes2(a) { - return a instanceof Uint8Array || ArrayBuffer.isView(a) && a.constructor.name === "Uint8Array"; -} -function abytes2(item) { - if (!isBytes2(item)) - throw new Error("Uint8Array expected"); -} -function abool(title, value) { - if (typeof value !== "boolean") - throw new Error(title + " boolean expected, got " + value); -} -function bytesToHex(bytes) { - abytes2(bytes); - let hex = ""; - for (let i = 0; i < bytes.length; i++) { - hex += hexes[bytes[i]]; - } - return hex; -} -function numberToHexUnpadded(num2) { - const hex = num2.toString(16); - return hex.length & 1 ? "0" + hex : hex; -} -function hexToNumber(hex) { - if (typeof hex !== "string") - throw new Error("hex string expected, got " + typeof hex); - return hex === "" ? _0n : BigInt("0x" + hex); -} -function asciiToBase16(ch) { - if (ch >= asciis._0 && ch <= asciis._9) - return ch - asciis._0; - if (ch >= asciis.A && ch <= asciis.F) - return ch - (asciis.A - 10); - if (ch >= asciis.a && ch <= asciis.f) - return ch - (asciis.a - 10); - return; -} -function hexToBytes(hex) { - if (typeof hex !== "string") - throw new Error("hex string expected, got " + typeof hex); - const hl = hex.length; - const al = hl / 2; - if (hl % 2) - throw new Error("hex string expected, got unpadded hex of length " + hl); - const array = new Uint8Array(al); - for (let ai = 0, hi = 0; ai < al; ai++, hi += 2) { - const n1 = asciiToBase16(hex.charCodeAt(hi)); - const n2 = asciiToBase16(hex.charCodeAt(hi + 1)); - if (n1 === void 0 || n2 === void 0) { - const char = hex[hi] + hex[hi + 1]; - throw new Error('hex string expected, got non-hex character "' + char + '" at index ' + hi); - } - array[ai] = n1 * 16 + n2; - } - return array; -} -function bytesToNumberBE(bytes) { - return hexToNumber(bytesToHex(bytes)); -} -function bytesToNumberLE(bytes) { - abytes2(bytes); - return hexToNumber(bytesToHex(Uint8Array.from(bytes).reverse())); -} -function numberToBytesBE(n, len) { - return hexToBytes(n.toString(16).padStart(len * 2, "0")); -} -function numberToBytesLE(n, len) { - return numberToBytesBE(n, len).reverse(); -} -function numberToVarBytesBE(n) { - return hexToBytes(numberToHexUnpadded(n)); -} -function ensureBytes(title, hex, expectedLength) { - let res; - if (typeof hex === "string") { - try { - res = hexToBytes(hex); - } catch (e) { - throw new Error(title + " must be hex string or Uint8Array, cause: " + e); - } - } else if (isBytes2(hex)) { - res = Uint8Array.from(hex); - } else { - throw new Error(title + " must be hex string or Uint8Array"); - } - const len = res.length; - if (typeof expectedLength === "number" && len !== expectedLength) - throw new Error(title + " of length " + expectedLength + " expected, got " + len); - return res; -} -function concatBytes2(...arrays) { - let sum = 0; - for (let i = 0; i < arrays.length; i++) { - const a = arrays[i]; - abytes2(a); - sum += a.length; - } - const res = new Uint8Array(sum); - for (let i = 0, pad2 = 0; i < arrays.length; i++) { - const a = arrays[i]; - res.set(a, pad2); - pad2 += a.length; - } - return res; -} -function equalBytes(a, b) { - if (a.length !== b.length) - return false; - let diff = 0; - for (let i = 0; i < a.length; i++) - diff |= a[i] ^ b[i]; - return diff === 0; -} -function utf8ToBytes2(str) { - if (typeof str !== "string") - throw new Error("string expected"); - return new Uint8Array(new TextEncoder().encode(str)); -} -function inRange(n, min, max) { - return isPosBig(n) && isPosBig(min) && isPosBig(max) && min <= n && n < max; -} -function aInRange(title, n, min, max) { - if (!inRange(n, min, max)) - throw new Error("expected valid " + title + ": " + min + " <= n < " + max + ", got " + n); -} -function bitLen(n) { - let len; - for (len = 0; n > _0n; n >>= _1n, len += 1) - ; - return len; -} -function bitGet(n, pos) { - return n >> BigInt(pos) & _1n; -} -function bitSet(n, pos, value) { - return n | (value ? _1n : _0n) << BigInt(pos); -} -function createHmacDrbg(hashLen, qByteLen, hmacFn) { - if (typeof hashLen !== "number" || hashLen < 2) - throw new Error("hashLen must be a number"); - if (typeof qByteLen !== "number" || qByteLen < 2) - throw new Error("qByteLen must be a number"); - if (typeof hmacFn !== "function") - throw new Error("hmacFn must be a function"); - let v = u8n(hashLen); - let k = u8n(hashLen); - let i = 0; - const reset = () => { - v.fill(1); - k.fill(0); - i = 0; - }; - const h = (...b) => hmacFn(k, v, ...b); - const reseed = (seed = u8n()) => { - k = h(u8fr([0]), seed); - v = h(); - if (seed.length === 0) - return; - k = h(u8fr([1]), seed); - v = h(); - }; - const gen2 = () => { - if (i++ >= 1e3) - throw new Error("drbg: tried 1000 values"); - let len = 0; - const out = []; - while (len < qByteLen) { - v = h(); - const sl = v.slice(); - out.push(sl); - len += v.length; - } - return concatBytes2(...out); - }; - const genUntil = (seed, pred) => { - reset(); - reseed(seed); - let res = void 0; - while (!(res = pred(gen2()))) - reseed(); - reset(); - return res; - }; - return genUntil; -} -function validateObject(object, validators, optValidators = {}) { - const checkField = (fieldName, type, isOptional) => { - const checkVal = validatorFns[type]; - if (typeof checkVal !== "function") - throw new Error("invalid validator function"); - const val = object[fieldName]; - if (isOptional && val === void 0) - return; - if (!checkVal(val, object)) { - throw new Error("param " + String(fieldName) + " is invalid. Expected " + type + ", got " + val); - } - }; - for (const [fieldName, type] of Object.entries(validators)) - checkField(fieldName, type, false); - for (const [fieldName, type] of Object.entries(optValidators)) - checkField(fieldName, type, true); - return object; -} -function memoized(fn) { - const map = /* @__PURE__ */ new WeakMap(); - return (arg, ...args2) => { - const val = map.get(arg); - if (val !== void 0) - return val; - const computed = fn(arg, ...args2); - map.set(arg, computed); - return computed; - }; -} -var _0n, _1n, _2n, hexes, asciis, isPosBig, bitMask, u8n, u8fr, validatorFns, notImplemented; -var init_utils2 = __esm({ - "node_modules/@noble/curves/esm/abstract/utils.js"() { - _0n = /* @__PURE__ */ BigInt(0); - _1n = /* @__PURE__ */ BigInt(1); - _2n = /* @__PURE__ */ BigInt(2); - hexes = /* @__PURE__ */ Array.from({ length: 256 }, (_, i) => i.toString(16).padStart(2, "0")); - asciis = { _0: 48, _9: 57, A: 65, F: 70, a: 97, f: 102 }; - isPosBig = (n) => typeof n === "bigint" && _0n <= n; - bitMask = (n) => (_2n << BigInt(n - 1)) - _1n; - u8n = (data) => new Uint8Array(data); - u8fr = (arr) => Uint8Array.from(arr); - validatorFns = { - bigint: (val) => typeof val === "bigint", - function: (val) => typeof val === "function", - boolean: (val) => typeof val === "boolean", - string: (val) => typeof val === "string", - stringOrUint8Array: (val) => typeof val === "string" || isBytes2(val), - isSafeInteger: (val) => Number.isSafeInteger(val), - array: (val) => Array.isArray(val), - field: (val, object) => object.Fp.isValid(val), - hash: (val) => typeof val === "function" && Number.isSafeInteger(val.outputLen) - }; - notImplemented = () => { - throw new Error("not implemented"); - }; - } -}); - -// node_modules/@noble/curves/esm/abstract/modular.js -function mod(a, b) { - const result = a % b; - return result >= _0n2 ? result : b + result; -} -function pow(num2, power, modulo) { - if (power < _0n2) - throw new Error("invalid exponent, negatives unsupported"); - if (modulo <= _0n2) - throw new Error("invalid modulus"); - if (modulo === _1n2) - return _0n2; - let res = _1n2; - while (power > _0n2) { - if (power & _1n2) - res = res * num2 % modulo; - num2 = num2 * num2 % modulo; - power >>= _1n2; - } - return res; -} -function pow2(x, power, modulo) { - let res = x; - while (power-- > _0n2) { - res *= res; - res %= modulo; - } - return res; -} -function invert(number, modulo) { - if (number === _0n2) - throw new Error("invert: expected non-zero number"); - if (modulo <= _0n2) - throw new Error("invert: expected positive modulus, got " + modulo); - let a = mod(number, modulo); - let b = modulo; - let x = _0n2, y = _1n2, u = _1n2, v = _0n2; - while (a !== _0n2) { - const q = b / a; - const r = b % a; - const m = x - u * q; - const n = y - v * q; - b = a, a = r, x = u, y = v, u = m, v = n; - } - const gcd = b; - if (gcd !== _1n2) - throw new Error("invert: does not exist"); - return mod(x, modulo); -} -function tonelliShanks(P) { - const legendreC = (P - _1n2) / _2n2; - let Q, S, Z; - for (Q = P - _1n2, S = 0; Q % _2n2 === _0n2; Q /= _2n2, S++) - ; - for (Z = _2n2; Z < P && pow(Z, legendreC, P) !== P - _1n2; Z++) { - if (Z > 1e3) - throw new Error("Cannot find square root: likely non-prime P"); - } - if (S === 1) { - const p1div4 = (P + _1n2) / _4n; - return function tonelliFast(Fp, n) { - const root = Fp.pow(n, p1div4); - if (!Fp.eql(Fp.sqr(root), n)) - throw new Error("Cannot find square root"); - return root; - }; - } - const Q1div2 = (Q + _1n2) / _2n2; - return function tonelliSlow(Fp, n) { - if (Fp.pow(n, legendreC) === Fp.neg(Fp.ONE)) - throw new Error("Cannot find square root"); - let r = S; - let g = Fp.pow(Fp.mul(Fp.ONE, Z), Q); - let x = Fp.pow(n, Q1div2); - let b = Fp.pow(n, Q); - while (!Fp.eql(b, Fp.ONE)) { - if (Fp.eql(b, Fp.ZERO)) - return Fp.ZERO; - let m = 1; - for (let t2 = Fp.sqr(b); m < r; m++) { - if (Fp.eql(t2, Fp.ONE)) - break; - t2 = Fp.sqr(t2); - } - const ge = Fp.pow(g, _1n2 << BigInt(r - m - 1)); - g = Fp.sqr(ge); - x = Fp.mul(x, ge); - b = Fp.mul(b, g); - r = m; - } - return x; - }; -} -function FpSqrt(P) { - if (P % _4n === _3n) { - const p1div4 = (P + _1n2) / _4n; - return function sqrt3mod4(Fp, n) { - const root = Fp.pow(n, p1div4); - if (!Fp.eql(Fp.sqr(root), n)) - throw new Error("Cannot find square root"); - return root; - }; - } - if (P % _8n === _5n) { - const c1 = (P - _5n) / _8n; - return function sqrt5mod8(Fp, n) { - const n2 = Fp.mul(n, _2n2); - const v = Fp.pow(n2, c1); - const nv = Fp.mul(n, v); - const i = Fp.mul(Fp.mul(nv, _2n2), v); - const root = Fp.mul(nv, Fp.sub(i, Fp.ONE)); - if (!Fp.eql(Fp.sqr(root), n)) - throw new Error("Cannot find square root"); - return root; - }; - } - if (P % _16n === _9n) { - } - return tonelliShanks(P); -} -function validateField(field) { - const initial = { - ORDER: "bigint", - MASK: "bigint", - BYTES: "isSafeInteger", - BITS: "isSafeInteger" - }; - const opts = FIELD_FIELDS.reduce((map, val) => { - map[val] = "function"; - return map; - }, initial); - return validateObject(field, opts); -} -function FpPow(f, num2, power) { - if (power < _0n2) - throw new Error("invalid exponent, negatives unsupported"); - if (power === _0n2) - return f.ONE; - if (power === _1n2) - return num2; - let p = f.ONE; - let d = num2; - while (power > _0n2) { - if (power & _1n2) - p = f.mul(p, d); - d = f.sqr(d); - power >>= _1n2; - } - return p; -} -function FpInvertBatch(f, nums) { - const tmp = new Array(nums.length); - const lastMultiplied = nums.reduce((acc, num2, i) => { - if (f.is0(num2)) - return acc; - tmp[i] = acc; - return f.mul(acc, num2); - }, f.ONE); - const inverted = f.inv(lastMultiplied); - nums.reduceRight((acc, num2, i) => { - if (f.is0(num2)) - return acc; - tmp[i] = f.mul(acc, tmp[i]); - return f.mul(acc, num2); - }, inverted); - return tmp; -} -function nLength(n, nBitLength) { - const _nBitLength = nBitLength !== void 0 ? nBitLength : n.toString(2).length; - const nByteLength = Math.ceil(_nBitLength / 8); - return { nBitLength: _nBitLength, nByteLength }; -} -function Field(ORDER, bitLen2, isLE2 = false, redef = {}) { - if (ORDER <= _0n2) - throw new Error("invalid field: expected ORDER > 0, got " + ORDER); - const { nBitLength: BITS, nByteLength: BYTES } = nLength(ORDER, bitLen2); - if (BYTES > 2048) - throw new Error("invalid field: expected ORDER of <= 2048 bytes"); - let sqrtP; - const f = Object.freeze({ - ORDER, - isLE: isLE2, - BITS, - BYTES, - MASK: bitMask(BITS), - ZERO: _0n2, - ONE: _1n2, - create: (num2) => mod(num2, ORDER), - isValid: (num2) => { - if (typeof num2 !== "bigint") - throw new Error("invalid field element: expected bigint, got " + typeof num2); - return _0n2 <= num2 && num2 < ORDER; - }, - is0: (num2) => num2 === _0n2, - isOdd: (num2) => (num2 & _1n2) === _1n2, - neg: (num2) => mod(-num2, ORDER), - eql: (lhs, rhs) => lhs === rhs, - sqr: (num2) => mod(num2 * num2, ORDER), - add: (lhs, rhs) => mod(lhs + rhs, ORDER), - sub: (lhs, rhs) => mod(lhs - rhs, ORDER), - mul: (lhs, rhs) => mod(lhs * rhs, ORDER), - pow: (num2, power) => FpPow(f, num2, power), - div: (lhs, rhs) => mod(lhs * invert(rhs, ORDER), ORDER), - // Same as above, but doesn't normalize - sqrN: (num2) => num2 * num2, - addN: (lhs, rhs) => lhs + rhs, - subN: (lhs, rhs) => lhs - rhs, - mulN: (lhs, rhs) => lhs * rhs, - inv: (num2) => invert(num2, ORDER), - sqrt: redef.sqrt || ((n) => { - if (!sqrtP) - sqrtP = FpSqrt(ORDER); - return sqrtP(f, n); - }), - invertBatch: (lst) => FpInvertBatch(f, lst), - // TODO: do we really need constant cmov? - // We don't have const-time bigints anyway, so probably will be not very useful - cmov: (a, b, c) => c ? b : a, - toBytes: (num2) => isLE2 ? numberToBytesLE(num2, BYTES) : numberToBytesBE(num2, BYTES), - fromBytes: (bytes) => { - if (bytes.length !== BYTES) - throw new Error("Field.fromBytes: expected " + BYTES + " bytes, got " + bytes.length); - return isLE2 ? bytesToNumberLE(bytes) : bytesToNumberBE(bytes); - } - }); - return Object.freeze(f); -} -function getFieldBytesLength(fieldOrder) { - if (typeof fieldOrder !== "bigint") - throw new Error("field order must be bigint"); - const bitLength = fieldOrder.toString(2).length; - return Math.ceil(bitLength / 8); -} -function getMinHashLength(fieldOrder) { - const length = getFieldBytesLength(fieldOrder); - return length + Math.ceil(length / 2); -} -function mapHashToField(key, fieldOrder, isLE2 = false) { - const len = key.length; - const fieldLen = getFieldBytesLength(fieldOrder); - const minLen = getMinHashLength(fieldOrder); - if (len < 16 || len < minLen || len > 1024) - throw new Error("expected " + minLen + "-1024 bytes of input, got " + len); - const num2 = isLE2 ? bytesToNumberLE(key) : bytesToNumberBE(key); - const reduced = mod(num2, fieldOrder - _1n2) + _1n2; - return isLE2 ? numberToBytesLE(reduced, fieldLen) : numberToBytesBE(reduced, fieldLen); -} -var _0n2, _1n2, _2n2, _3n, _4n, _5n, _8n, _9n, _16n, FIELD_FIELDS; -var init_modular = __esm({ - "node_modules/@noble/curves/esm/abstract/modular.js"() { - init_utils2(); - _0n2 = BigInt(0); - _1n2 = BigInt(1); - _2n2 = /* @__PURE__ */ BigInt(2); - _3n = /* @__PURE__ */ BigInt(3); - _4n = /* @__PURE__ */ BigInt(4); - _5n = /* @__PURE__ */ BigInt(5); - _8n = /* @__PURE__ */ BigInt(8); - _9n = /* @__PURE__ */ BigInt(9); - _16n = /* @__PURE__ */ BigInt(16); - FIELD_FIELDS = [ - "create", - "isValid", - "is0", - "neg", - "inv", - "sqrt", - "sqr", - "eql", - "add", - "sub", - "mul", - "pow", - "div", - "addN", - "subN", - "mulN", - "sqrN" - ]; - } -}); - -// node_modules/@noble/curves/esm/abstract/curve.js -function constTimeNegate(condition, item) { - const neg = item.negate(); - return condition ? neg : item; -} -function validateW(W, bits) { - if (!Number.isSafeInteger(W) || W <= 0 || W > bits) - throw new Error("invalid window size, expected [1.." + bits + "], got W=" + W); -} -function calcWOpts(W, bits) { - validateW(W, bits); - const windows = Math.ceil(bits / W) + 1; - const windowSize = 2 ** (W - 1); - return { windows, windowSize }; -} -function validateMSMPoints(points, c) { - if (!Array.isArray(points)) - throw new Error("array expected"); - points.forEach((p, i) => { - if (!(p instanceof c)) - throw new Error("invalid point at index " + i); - }); -} -function validateMSMScalars(scalars, field) { - if (!Array.isArray(scalars)) - throw new Error("array of scalars expected"); - scalars.forEach((s, i) => { - if (!field.isValid(s)) - throw new Error("invalid scalar at index " + i); - }); -} -function getW(P) { - return pointWindowSizes.get(P) || 1; -} -function wNAF(c, bits) { - return { - constTimeNegate, - hasPrecomputes(elm) { - return getW(elm) !== 1; - }, - // non-const time multiplication ladder - unsafeLadder(elm, n, p = c.ZERO) { - let d = elm; - while (n > _0n3) { - if (n & _1n3) - p = p.add(d); - d = d.double(); - n >>= _1n3; - } - return p; - }, - /** - * Creates a wNAF precomputation window. Used for caching. - * Default window size is set by `utils.precompute()` and is equal to 8. - * Number of precomputed points depends on the curve size: - * 2^(𝑊−1) * (Math.ceil(𝑛 / 𝑊) + 1), where: - * - 𝑊 is the window size - * - 𝑛 is the bitlength of the curve order. - * For a 256-bit curve and window size 8, the number of precomputed points is 128 * 33 = 4224. - * @param elm Point instance - * @param W window size - * @returns precomputed point tables flattened to a single array - */ - precomputeWindow(elm, W) { - const { windows, windowSize } = calcWOpts(W, bits); - const points = []; - let p = elm; - let base = p; - for (let window = 0; window < windows; window++) { - base = p; - points.push(base); - for (let i = 1; i < windowSize; i++) { - base = base.add(p); - points.push(base); - } - p = base.double(); - } - return points; - }, - /** - * Implements ec multiplication using precomputed tables and w-ary non-adjacent form. - * @param W window size - * @param precomputes precomputed tables - * @param n scalar (we don't check here, but should be less than curve order) - * @returns real and fake (for const-time) points - */ - wNAF(W, precomputes, n) { - const { windows, windowSize } = calcWOpts(W, bits); - let p = c.ZERO; - let f = c.BASE; - const mask = BigInt(2 ** W - 1); - const maxNumber = 2 ** W; - const shiftBy = BigInt(W); - for (let window = 0; window < windows; window++) { - const offset = window * windowSize; - let wbits = Number(n & mask); - n >>= shiftBy; - if (wbits > windowSize) { - wbits -= maxNumber; - n += _1n3; - } - const offset1 = offset; - const offset2 = offset + Math.abs(wbits) - 1; - const cond1 = window % 2 !== 0; - const cond2 = wbits < 0; - if (wbits === 0) { - f = f.add(constTimeNegate(cond1, precomputes[offset1])); - } else { - p = p.add(constTimeNegate(cond2, precomputes[offset2])); - } - } - return { p, f }; - }, - /** - * Implements ec unsafe (non const-time) multiplication using precomputed tables and w-ary non-adjacent form. - * @param W window size - * @param precomputes precomputed tables - * @param n scalar (we don't check here, but should be less than curve order) - * @param acc accumulator point to add result of multiplication - * @returns point - */ - wNAFUnsafe(W, precomputes, n, acc = c.ZERO) { - const { windows, windowSize } = calcWOpts(W, bits); - const mask = BigInt(2 ** W - 1); - const maxNumber = 2 ** W; - const shiftBy = BigInt(W); - for (let window = 0; window < windows; window++) { - const offset = window * windowSize; - if (n === _0n3) - break; - let wbits = Number(n & mask); - n >>= shiftBy; - if (wbits > windowSize) { - wbits -= maxNumber; - n += _1n3; - } - if (wbits === 0) - continue; - let curr = precomputes[offset + Math.abs(wbits) - 1]; - if (wbits < 0) - curr = curr.negate(); - acc = acc.add(curr); - } - return acc; - }, - getPrecomputes(W, P, transform) { - let comp = pointPrecomputes.get(P); - if (!comp) { - comp = this.precomputeWindow(P, W); - if (W !== 1) - pointPrecomputes.set(P, transform(comp)); - } - return comp; - }, - wNAFCached(P, n, transform) { - const W = getW(P); - return this.wNAF(W, this.getPrecomputes(W, P, transform), n); - }, - wNAFCachedUnsafe(P, n, transform, prev) { - const W = getW(P); - if (W === 1) - return this.unsafeLadder(P, n, prev); - return this.wNAFUnsafe(W, this.getPrecomputes(W, P, transform), n, prev); - }, - // We calculate precomputes for elliptic curve point multiplication - // using windowed method. This specifies window size and - // stores precomputed values. Usually only base point would be precomputed. - setWindowSize(P, W) { - validateW(W, bits); - pointWindowSizes.set(P, W); - pointPrecomputes.delete(P); - } - }; -} -function pippenger(c, fieldN, points, scalars) { - validateMSMPoints(points, c); - validateMSMScalars(scalars, fieldN); - if (points.length !== scalars.length) - throw new Error("arrays of points and scalars must have equal length"); - const zero = c.ZERO; - const wbits = bitLen(BigInt(points.length)); - const windowSize = wbits > 12 ? wbits - 3 : wbits > 4 ? wbits - 2 : wbits ? 2 : 1; - const MASK = (1 << windowSize) - 1; - const buckets = new Array(MASK + 1).fill(zero); - const lastBits = Math.floor((fieldN.BITS - 1) / windowSize) * windowSize; - let sum = zero; - for (let i = lastBits; i >= 0; i -= windowSize) { - buckets.fill(zero); - for (let j = 0; j < scalars.length; j++) { - const scalar = scalars[j]; - const wbits2 = Number(scalar >> BigInt(i) & BigInt(MASK)); - buckets[wbits2] = buckets[wbits2].add(points[j]); - } - let resI = zero; - for (let j = buckets.length - 1, sumI = zero; j > 0; j--) { - sumI = sumI.add(buckets[j]); - resI = resI.add(sumI); - } - sum = sum.add(resI); - if (i !== 0) - for (let j = 0; j < windowSize; j++) - sum = sum.double(); - } - return sum; -} -function validateBasic(curve) { - validateField(curve.Fp); - validateObject(curve, { - n: "bigint", - h: "bigint", - Gx: "field", - Gy: "field" - }, { - nBitLength: "isSafeInteger", - nByteLength: "isSafeInteger" - }); - return Object.freeze({ - ...nLength(curve.n, curve.nBitLength), - ...curve, - ...{ p: curve.Fp.ORDER } - }); -} -var _0n3, _1n3, pointPrecomputes, pointWindowSizes; -var init_curve = __esm({ - "node_modules/@noble/curves/esm/abstract/curve.js"() { - init_modular(); - init_utils2(); - _0n3 = BigInt(0); - _1n3 = BigInt(1); - pointPrecomputes = /* @__PURE__ */ new WeakMap(); - pointWindowSizes = /* @__PURE__ */ new WeakMap(); - } -}); - -// node_modules/@noble/curves/esm/abstract/weierstrass.js -function validateSigVerOpts(opts) { - if (opts.lowS !== void 0) - abool("lowS", opts.lowS); - if (opts.prehash !== void 0) - abool("prehash", opts.prehash); -} -function validatePointOpts(curve) { - const opts = validateBasic(curve); - validateObject(opts, { - a: "field", - b: "field" - }, { - allowedPrivateKeyLengths: "array", - wrapPrivateKey: "boolean", - isTorsionFree: "function", - clearCofactor: "function", - allowInfinityPoint: "boolean", - fromBytes: "function", - toBytes: "function" - }); - const { endo, Fp, a } = opts; - if (endo) { - if (!Fp.eql(a, Fp.ZERO)) { - throw new Error("invalid endomorphism, can only be defined for Koblitz curves that have a=0"); - } - if (typeof endo !== "object" || typeof endo.beta !== "bigint" || typeof endo.splitScalar !== "function") { - throw new Error("invalid endomorphism, expected beta: bigint and splitScalar: function"); - } - } - return Object.freeze({ ...opts }); -} -function weierstrassPoints(opts) { - const CURVE = validatePointOpts(opts); - const { Fp } = CURVE; - const Fn = Field(CURVE.n, CURVE.nBitLength); - const toBytes3 = CURVE.toBytes || ((_c, point, _isCompressed) => { - const a = point.toAffine(); - return concatBytes2(Uint8Array.from([4]), Fp.toBytes(a.x), Fp.toBytes(a.y)); - }); - const fromBytes = CURVE.fromBytes || ((bytes) => { - const tail = bytes.subarray(1); - const x = Fp.fromBytes(tail.subarray(0, Fp.BYTES)); - const y = Fp.fromBytes(tail.subarray(Fp.BYTES, 2 * Fp.BYTES)); - return { x, y }; - }); - function weierstrassEquation(x) { - const { a, b } = CURVE; - const x2 = Fp.sqr(x); - const x3 = Fp.mul(x2, x); - return Fp.add(Fp.add(x3, Fp.mul(x, a)), b); - } - if (!Fp.eql(Fp.sqr(CURVE.Gy), weierstrassEquation(CURVE.Gx))) - throw new Error("bad generator point: equation left != right"); - function isWithinCurveOrder(num2) { - return inRange(num2, _1n4, CURVE.n); - } - function normPrivateKeyToScalar(key) { - const { allowedPrivateKeyLengths: lengths, nByteLength, wrapPrivateKey, n: N } = CURVE; - if (lengths && typeof key !== "bigint") { - if (isBytes2(key)) - key = bytesToHex(key); - if (typeof key !== "string" || !lengths.includes(key.length)) - throw new Error("invalid private key"); - key = key.padStart(nByteLength * 2, "0"); - } - let num2; - try { - num2 = typeof key === "bigint" ? key : bytesToNumberBE(ensureBytes("private key", key, nByteLength)); - } catch (error) { - throw new Error("invalid private key, expected hex or " + nByteLength + " bytes, got " + typeof key); - } - if (wrapPrivateKey) - num2 = mod(num2, N); - aInRange("private key", num2, _1n4, N); - return num2; - } - function assertPrjPoint(other) { - if (!(other instanceof Point2)) - throw new Error("ProjectivePoint expected"); - } - const toAffineMemo = memoized((p, iz) => { - const { px: x, py: y, pz: z } = p; - if (Fp.eql(z, Fp.ONE)) - return { x, y }; - const is0 = p.is0(); - if (iz == null) - iz = is0 ? Fp.ONE : Fp.inv(z); - const ax = Fp.mul(x, iz); - const ay = Fp.mul(y, iz); - const zz = Fp.mul(z, iz); - if (is0) - return { x: Fp.ZERO, y: Fp.ZERO }; - if (!Fp.eql(zz, Fp.ONE)) - throw new Error("invZ was invalid"); - return { x: ax, y: ay }; - }); - const assertValidMemo = memoized((p) => { - if (p.is0()) { - if (CURVE.allowInfinityPoint && !Fp.is0(p.py)) - return; - throw new Error("bad point: ZERO"); - } - const { x, y } = p.toAffine(); - if (!Fp.isValid(x) || !Fp.isValid(y)) - throw new Error("bad point: x or y not FE"); - const left = Fp.sqr(y); - const right = weierstrassEquation(x); - if (!Fp.eql(left, right)) - throw new Error("bad point: equation left != right"); - if (!p.isTorsionFree()) - throw new Error("bad point: not in prime-order subgroup"); - return true; - }); - class Point2 { - constructor(px, py, pz) { - this.px = px; - this.py = py; - this.pz = pz; - if (px == null || !Fp.isValid(px)) - throw new Error("x required"); - if (py == null || !Fp.isValid(py)) - throw new Error("y required"); - if (pz == null || !Fp.isValid(pz)) - throw new Error("z required"); - Object.freeze(this); - } - // Does not validate if the point is on-curve. - // Use fromHex instead, or call assertValidity() later. - static fromAffine(p) { - const { x, y } = p || {}; - if (!p || !Fp.isValid(x) || !Fp.isValid(y)) - throw new Error("invalid affine point"); - if (p instanceof Point2) - throw new Error("projective point not allowed"); - const is0 = (i) => Fp.eql(i, Fp.ZERO); - if (is0(x) && is0(y)) - return Point2.ZERO; - return new Point2(x, y, Fp.ONE); - } - get x() { - return this.toAffine().x; - } - get y() { - return this.toAffine().y; - } - /** - * Takes a bunch of Projective Points but executes only one - * inversion on all of them. Inversion is very slow operation, - * so this improves performance massively. - * Optimization: converts a list of projective points to a list of identical points with Z=1. - */ - static normalizeZ(points) { - const toInv = Fp.invertBatch(points.map((p) => p.pz)); - return points.map((p, i) => p.toAffine(toInv[i])).map(Point2.fromAffine); - } - /** - * Converts hash string or Uint8Array to Point. - * @param hex short/long ECDSA hex - */ - static fromHex(hex) { - const P = Point2.fromAffine(fromBytes(ensureBytes("pointHex", hex))); - P.assertValidity(); - return P; - } - // Multiplies generator point by privateKey. - static fromPrivateKey(privateKey2) { - return Point2.BASE.multiply(normPrivateKeyToScalar(privateKey2)); - } - // Multiscalar Multiplication - static msm(points, scalars) { - return pippenger(Point2, Fn, points, scalars); - } - // "Private method", don't use it directly - _setWindowSize(windowSize) { - wnaf.setWindowSize(this, windowSize); - } - // A point on curve is valid if it conforms to equation. - assertValidity() { - assertValidMemo(this); - } - hasEvenY() { - const { y } = this.toAffine(); - if (Fp.isOdd) - return !Fp.isOdd(y); - throw new Error("Field doesn't support isOdd"); - } - /** - * Compare one point to another. - */ - equals(other) { - assertPrjPoint(other); - const { px: X1, py: Y1, pz: Z1 } = this; - const { px: X2, py: Y2, pz: Z2 } = other; - const U1 = Fp.eql(Fp.mul(X1, Z2), Fp.mul(X2, Z1)); - const U2 = Fp.eql(Fp.mul(Y1, Z2), Fp.mul(Y2, Z1)); - return U1 && U2; - } - /** - * Flips point to one corresponding to (x, -y) in Affine coordinates. - */ - negate() { - return new Point2(this.px, Fp.neg(this.py), this.pz); - } - // Renes-Costello-Batina exception-free doubling formula. - // There is 30% faster Jacobian formula, but it is not complete. - // https://eprint.iacr.org/2015/1060, algorithm 3 - // Cost: 8M + 3S + 3*a + 2*b3 + 15add. - double() { - const { a, b } = CURVE; - const b3 = Fp.mul(b, _3n2); - const { px: X1, py: Y1, pz: Z1 } = this; - let X3 = Fp.ZERO, Y3 = Fp.ZERO, Z3 = Fp.ZERO; - let t0 = Fp.mul(X1, X1); - let t1 = Fp.mul(Y1, Y1); - let t2 = Fp.mul(Z1, Z1); - let t3 = Fp.mul(X1, Y1); - t3 = Fp.add(t3, t3); - Z3 = Fp.mul(X1, Z1); - Z3 = Fp.add(Z3, Z3); - X3 = Fp.mul(a, Z3); - Y3 = Fp.mul(b3, t2); - Y3 = Fp.add(X3, Y3); - X3 = Fp.sub(t1, Y3); - Y3 = Fp.add(t1, Y3); - Y3 = Fp.mul(X3, Y3); - X3 = Fp.mul(t3, X3); - Z3 = Fp.mul(b3, Z3); - t2 = Fp.mul(a, t2); - t3 = Fp.sub(t0, t2); - t3 = Fp.mul(a, t3); - t3 = Fp.add(t3, Z3); - Z3 = Fp.add(t0, t0); - t0 = Fp.add(Z3, t0); - t0 = Fp.add(t0, t2); - t0 = Fp.mul(t0, t3); - Y3 = Fp.add(Y3, t0); - t2 = Fp.mul(Y1, Z1); - t2 = Fp.add(t2, t2); - t0 = Fp.mul(t2, t3); - X3 = Fp.sub(X3, t0); - Z3 = Fp.mul(t2, t1); - Z3 = Fp.add(Z3, Z3); - Z3 = Fp.add(Z3, Z3); - return new Point2(X3, Y3, Z3); - } - // Renes-Costello-Batina exception-free addition formula. - // There is 30% faster Jacobian formula, but it is not complete. - // https://eprint.iacr.org/2015/1060, algorithm 1 - // Cost: 12M + 0S + 3*a + 3*b3 + 23add. - add(other) { - assertPrjPoint(other); - const { px: X1, py: Y1, pz: Z1 } = this; - const { px: X2, py: Y2, pz: Z2 } = other; - let X3 = Fp.ZERO, Y3 = Fp.ZERO, Z3 = Fp.ZERO; - const a = CURVE.a; - const b3 = Fp.mul(CURVE.b, _3n2); - let t0 = Fp.mul(X1, X2); - let t1 = Fp.mul(Y1, Y2); - let t2 = Fp.mul(Z1, Z2); - let t3 = Fp.add(X1, Y1); - let t4 = Fp.add(X2, Y2); - t3 = Fp.mul(t3, t4); - t4 = Fp.add(t0, t1); - t3 = Fp.sub(t3, t4); - t4 = Fp.add(X1, Z1); - let t5 = Fp.add(X2, Z2); - t4 = Fp.mul(t4, t5); - t5 = Fp.add(t0, t2); - t4 = Fp.sub(t4, t5); - t5 = Fp.add(Y1, Z1); - X3 = Fp.add(Y2, Z2); - t5 = Fp.mul(t5, X3); - X3 = Fp.add(t1, t2); - t5 = Fp.sub(t5, X3); - Z3 = Fp.mul(a, t4); - X3 = Fp.mul(b3, t2); - Z3 = Fp.add(X3, Z3); - X3 = Fp.sub(t1, Z3); - Z3 = Fp.add(t1, Z3); - Y3 = Fp.mul(X3, Z3); - t1 = Fp.add(t0, t0); - t1 = Fp.add(t1, t0); - t2 = Fp.mul(a, t2); - t4 = Fp.mul(b3, t4); - t1 = Fp.add(t1, t2); - t2 = Fp.sub(t0, t2); - t2 = Fp.mul(a, t2); - t4 = Fp.add(t4, t2); - t0 = Fp.mul(t1, t4); - Y3 = Fp.add(Y3, t0); - t0 = Fp.mul(t5, t4); - X3 = Fp.mul(t3, X3); - X3 = Fp.sub(X3, t0); - t0 = Fp.mul(t3, t1); - Z3 = Fp.mul(t5, Z3); - Z3 = Fp.add(Z3, t0); - return new Point2(X3, Y3, Z3); - } - subtract(other) { - return this.add(other.negate()); - } - is0() { - return this.equals(Point2.ZERO); - } - wNAF(n) { - return wnaf.wNAFCached(this, n, Point2.normalizeZ); - } - /** - * Non-constant-time multiplication. Uses double-and-add algorithm. - * It's faster, but should only be used when you don't care about - * an exposed private key e.g. sig verification, which works over *public* keys. - */ - multiplyUnsafe(sc) { - const { endo, n: N } = CURVE; - aInRange("scalar", sc, _0n4, N); - const I = Point2.ZERO; - if (sc === _0n4) - return I; - if (this.is0() || sc === _1n4) - return this; - if (!endo || wnaf.hasPrecomputes(this)) - return wnaf.wNAFCachedUnsafe(this, sc, Point2.normalizeZ); - let { k1neg, k1, k2neg, k2 } = endo.splitScalar(sc); - let k1p = I; - let k2p = I; - let d = this; - while (k1 > _0n4 || k2 > _0n4) { - if (k1 & _1n4) - k1p = k1p.add(d); - if (k2 & _1n4) - k2p = k2p.add(d); - d = d.double(); - k1 >>= _1n4; - k2 >>= _1n4; - } - if (k1neg) - k1p = k1p.negate(); - if (k2neg) - k2p = k2p.negate(); - k2p = new Point2(Fp.mul(k2p.px, endo.beta), k2p.py, k2p.pz); - return k1p.add(k2p); - } - /** - * Constant time multiplication. - * Uses wNAF method. Windowed method may be 10% faster, - * but takes 2x longer to generate and consumes 2x memory. - * Uses precomputes when available. - * Uses endomorphism for Koblitz curves. - * @param scalar by which the point would be multiplied - * @returns New point - */ - multiply(scalar) { - const { endo, n: N } = CURVE; - aInRange("scalar", scalar, _1n4, N); - let point, fake; - if (endo) { - const { k1neg, k1, k2neg, k2 } = endo.splitScalar(scalar); - let { p: k1p, f: f1p } = this.wNAF(k1); - let { p: k2p, f: f2p } = this.wNAF(k2); - k1p = wnaf.constTimeNegate(k1neg, k1p); - k2p = wnaf.constTimeNegate(k2neg, k2p); - k2p = new Point2(Fp.mul(k2p.px, endo.beta), k2p.py, k2p.pz); - point = k1p.add(k2p); - fake = f1p.add(f2p); - } else { - const { p, f } = this.wNAF(scalar); - point = p; - fake = f; - } - return Point2.normalizeZ([point, fake])[0]; - } - /** - * Efficiently calculate `aP + bQ`. Unsafe, can expose private key, if used incorrectly. - * Not using Strauss-Shamir trick: precomputation tables are faster. - * The trick could be useful if both P and Q are not G (not in our case). - * @returns non-zero affine point - */ - multiplyAndAddUnsafe(Q, a, b) { - const G = Point2.BASE; - const mul = (P, a2) => a2 === _0n4 || a2 === _1n4 || !P.equals(G) ? P.multiplyUnsafe(a2) : P.multiply(a2); - const sum = mul(this, a).add(mul(Q, b)); - return sum.is0() ? void 0 : sum; - } - // Converts Projective point to affine (x, y) coordinates. - // Can accept precomputed Z^-1 - for example, from invertBatch. - // (x, y, z) ∋ (x=x/z, y=y/z) - toAffine(iz) { - return toAffineMemo(this, iz); - } - isTorsionFree() { - const { h: cofactor, isTorsionFree } = CURVE; - if (cofactor === _1n4) - return true; - if (isTorsionFree) - return isTorsionFree(Point2, this); - throw new Error("isTorsionFree() has not been declared for the elliptic curve"); - } - clearCofactor() { - const { h: cofactor, clearCofactor } = CURVE; - if (cofactor === _1n4) - return this; - if (clearCofactor) - return clearCofactor(Point2, this); - return this.multiplyUnsafe(CURVE.h); - } - toRawBytes(isCompressed = true) { - abool("isCompressed", isCompressed); - this.assertValidity(); - return toBytes3(Point2, this, isCompressed); - } - toHex(isCompressed = true) { - abool("isCompressed", isCompressed); - return bytesToHex(this.toRawBytes(isCompressed)); - } - } - Point2.BASE = new Point2(CURVE.Gx, CURVE.Gy, Fp.ONE); - Point2.ZERO = new Point2(Fp.ZERO, Fp.ONE, Fp.ZERO); - const _bits = CURVE.nBitLength; - const wnaf = wNAF(Point2, CURVE.endo ? Math.ceil(_bits / 2) : _bits); - return { - CURVE, - ProjectivePoint: Point2, - normPrivateKeyToScalar, - weierstrassEquation, - isWithinCurveOrder - }; -} -function validateOpts(curve) { - const opts = validateBasic(curve); - validateObject(opts, { - hash: "hash", - hmac: "function", - randomBytes: "function" - }, { - bits2int: "function", - bits2int_modN: "function", - lowS: "boolean" - }); - return Object.freeze({ lowS: true, ...opts }); -} -function weierstrass(curveDef) { - const CURVE = validateOpts(curveDef); - const { Fp, n: CURVE_ORDER } = CURVE; - const compressedLen = Fp.BYTES + 1; - const uncompressedLen = 2 * Fp.BYTES + 1; - function modN2(a) { - return mod(a, CURVE_ORDER); - } - function invN(a) { - return invert(a, CURVE_ORDER); - } - const { ProjectivePoint: Point2, normPrivateKeyToScalar, weierstrassEquation, isWithinCurveOrder } = weierstrassPoints({ - ...CURVE, - toBytes(_c, point, isCompressed) { - const a = point.toAffine(); - const x = Fp.toBytes(a.x); - const cat = concatBytes2; - abool("isCompressed", isCompressed); - if (isCompressed) { - return cat(Uint8Array.from([point.hasEvenY() ? 2 : 3]), x); - } else { - return cat(Uint8Array.from([4]), x, Fp.toBytes(a.y)); - } - }, - fromBytes(bytes) { - const len = bytes.length; - const head = bytes[0]; - const tail = bytes.subarray(1); - if (len === compressedLen && (head === 2 || head === 3)) { - const x = bytesToNumberBE(tail); - if (!inRange(x, _1n4, Fp.ORDER)) - throw new Error("Point is not on curve"); - const y2 = weierstrassEquation(x); - let y; - try { - y = Fp.sqrt(y2); - } catch (sqrtError) { - const suffix = sqrtError instanceof Error ? ": " + sqrtError.message : ""; - throw new Error("Point is not on curve" + suffix); - } - const isYOdd = (y & _1n4) === _1n4; - const isHeadOdd = (head & 1) === 1; - if (isHeadOdd !== isYOdd) - y = Fp.neg(y); - return { x, y }; - } else if (len === uncompressedLen && head === 4) { - const x = Fp.fromBytes(tail.subarray(0, Fp.BYTES)); - const y = Fp.fromBytes(tail.subarray(Fp.BYTES, 2 * Fp.BYTES)); - return { x, y }; - } else { - const cl = compressedLen; - const ul = uncompressedLen; - throw new Error("invalid Point, expected length of " + cl + ", or uncompressed " + ul + ", got " + len); - } - } - }); - const numToNByteStr = (num2) => bytesToHex(numberToBytesBE(num2, CURVE.nByteLength)); - function isBiggerThanHalfOrder(number) { - const HALF = CURVE_ORDER >> _1n4; - return number > HALF; - } - function normalizeS(s) { - return isBiggerThanHalfOrder(s) ? modN2(-s) : s; - } - const slcNum = (b, from, to) => bytesToNumberBE(b.slice(from, to)); - class Signature { - constructor(r, s, recovery) { - this.r = r; - this.s = s; - this.recovery = recovery; - this.assertValidity(); - } - // pair (bytes of r, bytes of s) - static fromCompact(hex) { - const l = CURVE.nByteLength; - hex = ensureBytes("compactSignature", hex, l * 2); - return new Signature(slcNum(hex, 0, l), slcNum(hex, l, 2 * l)); - } - // DER encoded ECDSA signature - // https://bitcoin.stackexchange.com/questions/57644/what-are-the-parts-of-a-bitcoin-transaction-input-script - static fromDER(hex) { - const { r, s } = DER.toSig(ensureBytes("DER", hex)); - return new Signature(r, s); - } - assertValidity() { - aInRange("r", this.r, _1n4, CURVE_ORDER); - aInRange("s", this.s, _1n4, CURVE_ORDER); - } - addRecoveryBit(recovery) { - return new Signature(this.r, this.s, recovery); - } - recoverPublicKey(msgHash) { - const { r, s, recovery: rec } = this; - const h = bits2int_modN(ensureBytes("msgHash", msgHash)); - if (rec == null || ![0, 1, 2, 3].includes(rec)) - throw new Error("recovery id invalid"); - const radj = rec === 2 || rec === 3 ? r + CURVE.n : r; - if (radj >= Fp.ORDER) - throw new Error("recovery id 2 or 3 invalid"); - const prefix = (rec & 1) === 0 ? "02" : "03"; - const R = Point2.fromHex(prefix + numToNByteStr(radj)); - const ir = invN(radj); - const u1 = modN2(-h * ir); - const u2 = modN2(s * ir); - const Q = Point2.BASE.multiplyAndAddUnsafe(R, u1, u2); - if (!Q) - throw new Error("point at infinify"); - Q.assertValidity(); - return Q; - } - // Signatures should be low-s, to prevent malleability. - hasHighS() { - return isBiggerThanHalfOrder(this.s); - } - normalizeS() { - return this.hasHighS() ? new Signature(this.r, modN2(-this.s), this.recovery) : this; - } - // DER-encoded - toDERRawBytes() { - return hexToBytes(this.toDERHex()); - } - toDERHex() { - return DER.hexFromSig({ r: this.r, s: this.s }); - } - // padded bytes of r, then padded bytes of s - toCompactRawBytes() { - return hexToBytes(this.toCompactHex()); - } - toCompactHex() { - return numToNByteStr(this.r) + numToNByteStr(this.s); - } - } - const utils = { - isValidPrivateKey(privateKey2) { - try { - normPrivateKeyToScalar(privateKey2); - return true; - } catch (error) { - return false; - } - }, - normPrivateKeyToScalar, - /** - * Produces cryptographically secure private key from random of size - * (groupLen + ceil(groupLen / 2)) with modulo bias being negligible. - */ - randomPrivateKey: () => { - const length = getMinHashLength(CURVE.n); - return mapHashToField(CURVE.randomBytes(length), CURVE.n); - }, - /** - * Creates precompute table for an arbitrary EC point. Makes point "cached". - * Allows to massively speed-up `point.multiply(scalar)`. - * @returns cached point - * @example - * const fast = utils.precompute(8, ProjectivePoint.fromHex(someonesPubKey)); - * fast.multiply(privKey); // much faster ECDH now - */ - precompute(windowSize = 8, point = Point2.BASE) { - point._setWindowSize(windowSize); - point.multiply(BigInt(3)); - return point; - } - }; - function getPublicKey(privateKey2, isCompressed = true) { - return Point2.fromPrivateKey(privateKey2).toRawBytes(isCompressed); - } - function isProbPub(item) { - const arr = isBytes2(item); - const str = typeof item === "string"; - const len = (arr || str) && item.length; - if (arr) - return len === compressedLen || len === uncompressedLen; - if (str) - return len === 2 * compressedLen || len === 2 * uncompressedLen; - if (item instanceof Point2) - return true; - return false; - } - function getSharedSecret(privateA, publicB, isCompressed = true) { - if (isProbPub(privateA)) - throw new Error("first arg must be private key"); - if (!isProbPub(publicB)) - throw new Error("second arg must be public key"); - const b = Point2.fromHex(publicB); - return b.multiply(normPrivateKeyToScalar(privateA)).toRawBytes(isCompressed); - } - const bits2int = CURVE.bits2int || function(bytes) { - if (bytes.length > 8192) - throw new Error("input is too large"); - const num2 = bytesToNumberBE(bytes); - const delta = bytes.length * 8 - CURVE.nBitLength; - return delta > 0 ? num2 >> BigInt(delta) : num2; - }; - const bits2int_modN = CURVE.bits2int_modN || function(bytes) { - return modN2(bits2int(bytes)); - }; - const ORDER_MASK = bitMask(CURVE.nBitLength); - function int2octets(num2) { - aInRange("num < 2^" + CURVE.nBitLength, num2, _0n4, ORDER_MASK); - return numberToBytesBE(num2, CURVE.nByteLength); - } - function prepSig(msgHash, privateKey2, opts = defaultSigOpts) { - if (["recovered", "canonical"].some((k) => k in opts)) - throw new Error("sign() legacy options not supported"); - const { hash: hash2, randomBytes: randomBytes2 } = CURVE; - let { lowS, prehash, extraEntropy: ent } = opts; - if (lowS == null) - lowS = true; - msgHash = ensureBytes("msgHash", msgHash); - validateSigVerOpts(opts); - if (prehash) - msgHash = ensureBytes("prehashed msgHash", hash2(msgHash)); - const h1int = bits2int_modN(msgHash); - const d = normPrivateKeyToScalar(privateKey2); - const seedArgs = [int2octets(d), int2octets(h1int)]; - if (ent != null && ent !== false) { - const e = ent === true ? randomBytes2(Fp.BYTES) : ent; - seedArgs.push(ensureBytes("extraEntropy", e)); - } - const seed = concatBytes2(...seedArgs); - const m = h1int; - function k2sig(kBytes) { - const k = bits2int(kBytes); - if (!isWithinCurveOrder(k)) - return; - const ik = invN(k); - const q = Point2.BASE.multiply(k).toAffine(); - const r = modN2(q.x); - if (r === _0n4) - return; - const s = modN2(ik * modN2(m + r * d)); - if (s === _0n4) - return; - let recovery = (q.x === r ? 0 : 2) | Number(q.y & _1n4); - let normS = s; - if (lowS && isBiggerThanHalfOrder(s)) { - normS = normalizeS(s); - recovery ^= 1; - } - return new Signature(r, normS, recovery); - } - return { seed, k2sig }; - } - const defaultSigOpts = { lowS: CURVE.lowS, prehash: false }; - const defaultVerOpts = { lowS: CURVE.lowS, prehash: false }; - function sign2(msgHash, privKey, opts = defaultSigOpts) { - const { seed, k2sig } = prepSig(msgHash, privKey, opts); - const C = CURVE; - const drbg = createHmacDrbg(C.hash.outputLen, C.nByteLength, C.hmac); - return drbg(seed, k2sig); - } - Point2.BASE._setWindowSize(8); - function verify(signature, msgHash, publicKey, opts = defaultVerOpts) { - const sg = signature; - msgHash = ensureBytes("msgHash", msgHash); - publicKey = ensureBytes("publicKey", publicKey); - const { lowS, prehash, format } = opts; - validateSigVerOpts(opts); - if ("strict" in opts) - throw new Error("options.strict was renamed to lowS"); - if (format !== void 0 && format !== "compact" && format !== "der") - throw new Error("format must be compact or der"); - const isHex2 = typeof sg === "string" || isBytes2(sg); - const isObj = !isHex2 && !format && typeof sg === "object" && sg !== null && typeof sg.r === "bigint" && typeof sg.s === "bigint"; - if (!isHex2 && !isObj) - throw new Error("invalid signature, expected Uint8Array, hex string or Signature instance"); - let _sig = void 0; - let P; - try { - if (isObj) - _sig = new Signature(sg.r, sg.s); - if (isHex2) { - try { - if (format !== "compact") - _sig = Signature.fromDER(sg); - } catch (derError) { - if (!(derError instanceof DER.Err)) - throw derError; - } - if (!_sig && format !== "der") - _sig = Signature.fromCompact(sg); - } - P = Point2.fromHex(publicKey); - } catch (error) { - return false; - } - if (!_sig) - return false; - if (lowS && _sig.hasHighS()) - return false; - if (prehash) - msgHash = CURVE.hash(msgHash); - const { r, s } = _sig; - const h = bits2int_modN(msgHash); - const is = invN(s); - const u1 = modN2(h * is); - const u2 = modN2(r * is); - const R = Point2.BASE.multiplyAndAddUnsafe(P, u1, u2)?.toAffine(); - if (!R) - return false; - const v = modN2(R.x); - return v === r; - } - return { - CURVE, - getPublicKey, - getSharedSecret, - sign: sign2, - verify, - ProjectivePoint: Point2, - Signature, - utils - }; -} -function SWUFpSqrtRatio(Fp, Z) { - const q = Fp.ORDER; - let l = _0n4; - for (let o = q - _1n4; o % _2n3 === _0n4; o /= _2n3) - l += _1n4; - const c1 = l; - const _2n_pow_c1_1 = _2n3 << c1 - _1n4 - _1n4; - const _2n_pow_c1 = _2n_pow_c1_1 * _2n3; - const c2 = (q - _1n4) / _2n_pow_c1; - const c3 = (c2 - _1n4) / _2n3; - const c4 = _2n_pow_c1 - _1n4; - const c5 = _2n_pow_c1_1; - const c6 = Fp.pow(Z, c2); - const c7 = Fp.pow(Z, (c2 + _1n4) / _2n3); - let sqrtRatio = (u, v) => { - let tv1 = c6; - let tv2 = Fp.pow(v, c4); - let tv3 = Fp.sqr(tv2); - tv3 = Fp.mul(tv3, v); - let tv5 = Fp.mul(u, tv3); - tv5 = Fp.pow(tv5, c3); - tv5 = Fp.mul(tv5, tv2); - tv2 = Fp.mul(tv5, v); - tv3 = Fp.mul(tv5, u); - let tv4 = Fp.mul(tv3, tv2); - tv5 = Fp.pow(tv4, c5); - let isQR = Fp.eql(tv5, Fp.ONE); - tv2 = Fp.mul(tv3, c7); - tv5 = Fp.mul(tv4, tv1); - tv3 = Fp.cmov(tv2, tv3, isQR); - tv4 = Fp.cmov(tv5, tv4, isQR); - for (let i = c1; i > _1n4; i--) { - let tv52 = i - _2n3; - tv52 = _2n3 << tv52 - _1n4; - let tvv5 = Fp.pow(tv4, tv52); - const e1 = Fp.eql(tvv5, Fp.ONE); - tv2 = Fp.mul(tv3, tv1); - tv1 = Fp.mul(tv1, tv1); - tvv5 = Fp.mul(tv4, tv1); - tv3 = Fp.cmov(tv2, tv3, e1); - tv4 = Fp.cmov(tvv5, tv4, e1); - } - return { isValid: isQR, value: tv3 }; - }; - if (Fp.ORDER % _4n2 === _3n2) { - const c12 = (Fp.ORDER - _3n2) / _4n2; - const c22 = Fp.sqrt(Fp.neg(Z)); - sqrtRatio = (u, v) => { - let tv1 = Fp.sqr(v); - const tv2 = Fp.mul(u, v); - tv1 = Fp.mul(tv1, tv2); - let y1 = Fp.pow(tv1, c12); - y1 = Fp.mul(y1, tv2); - const y2 = Fp.mul(y1, c22); - const tv3 = Fp.mul(Fp.sqr(y1), v); - const isQR = Fp.eql(tv3, u); - let y = Fp.cmov(y2, y1, isQR); - return { isValid: isQR, value: y }; - }; - } - return sqrtRatio; -} -function mapToCurveSimpleSWU(Fp, opts) { - validateField(Fp); - if (!Fp.isValid(opts.A) || !Fp.isValid(opts.B) || !Fp.isValid(opts.Z)) - throw new Error("mapToCurveSimpleSWU: invalid opts"); - const sqrtRatio = SWUFpSqrtRatio(Fp, opts.Z); - if (!Fp.isOdd) - throw new Error("Fp.isOdd is not implemented!"); - return (u) => { - let tv1, tv2, tv3, tv4, tv5, tv6, x, y; - tv1 = Fp.sqr(u); - tv1 = Fp.mul(tv1, opts.Z); - tv2 = Fp.sqr(tv1); - tv2 = Fp.add(tv2, tv1); - tv3 = Fp.add(tv2, Fp.ONE); - tv3 = Fp.mul(tv3, opts.B); - tv4 = Fp.cmov(opts.Z, Fp.neg(tv2), !Fp.eql(tv2, Fp.ZERO)); - tv4 = Fp.mul(tv4, opts.A); - tv2 = Fp.sqr(tv3); - tv6 = Fp.sqr(tv4); - tv5 = Fp.mul(tv6, opts.A); - tv2 = Fp.add(tv2, tv5); - tv2 = Fp.mul(tv2, tv3); - tv6 = Fp.mul(tv6, tv4); - tv5 = Fp.mul(tv6, opts.B); - tv2 = Fp.add(tv2, tv5); - x = Fp.mul(tv1, tv3); - const { isValid, value } = sqrtRatio(tv2, tv6); - y = Fp.mul(tv1, u); - y = Fp.mul(y, value); - x = Fp.cmov(x, tv3, isValid); - y = Fp.cmov(y, value, isValid); - const e1 = Fp.isOdd(u) === Fp.isOdd(y); - y = Fp.cmov(Fp.neg(y), y, e1); - x = Fp.div(x, tv4); - return { x, y }; - }; -} -var b2n, h2b, DERErr, DER, _0n4, _1n4, _2n3, _3n2, _4n2; -var init_weierstrass = __esm({ - "node_modules/@noble/curves/esm/abstract/weierstrass.js"() { - init_curve(); - init_modular(); - init_utils2(); - init_utils2(); - ({ bytesToNumberBE: b2n, hexToBytes: h2b } = utils_exports); - DERErr = class extends Error { - constructor(m = "") { - super(m); - } - }; - DER = { - // asn.1 DER encoding utils - Err: DERErr, - // Basic building block is TLV (Tag-Length-Value) - _tlv: { - encode: (tag, data) => { - const { Err: E } = DER; - if (tag < 0 || tag > 256) - throw new E("tlv.encode: wrong tag"); - if (data.length & 1) - throw new E("tlv.encode: unpadded data"); - const dataLen = data.length / 2; - const len = numberToHexUnpadded(dataLen); - if (len.length / 2 & 128) - throw new E("tlv.encode: long form length too big"); - const lenLen = dataLen > 127 ? numberToHexUnpadded(len.length / 2 | 128) : ""; - const t = numberToHexUnpadded(tag); - return t + lenLen + len + data; - }, - // v - value, l - left bytes (unparsed) - decode(tag, data) { - const { Err: E } = DER; - let pos = 0; - if (tag < 0 || tag > 256) - throw new E("tlv.encode: wrong tag"); - if (data.length < 2 || data[pos++] !== tag) - throw new E("tlv.decode: wrong tlv"); - const first = data[pos++]; - const isLong = !!(first & 128); - let length = 0; - if (!isLong) - length = first; - else { - const lenLen = first & 127; - if (!lenLen) - throw new E("tlv.decode(long): indefinite length not supported"); - if (lenLen > 4) - throw new E("tlv.decode(long): byte length is too big"); - const lengthBytes = data.subarray(pos, pos + lenLen); - if (lengthBytes.length !== lenLen) - throw new E("tlv.decode: length bytes not complete"); - if (lengthBytes[0] === 0) - throw new E("tlv.decode(long): zero leftmost byte"); - for (const b of lengthBytes) - length = length << 8 | b; - pos += lenLen; - if (length < 128) - throw new E("tlv.decode(long): not minimal encoding"); - } - const v = data.subarray(pos, pos + length); - if (v.length !== length) - throw new E("tlv.decode: wrong value length"); - return { v, l: data.subarray(pos + length) }; - } - }, - // https://crypto.stackexchange.com/a/57734 Leftmost bit of first byte is 'negative' flag, - // since we always use positive integers here. It must always be empty: - // - add zero byte if exists - // - if next byte doesn't have a flag, leading zero is not allowed (minimal encoding) - _int: { - encode(num2) { - const { Err: E } = DER; - if (num2 < _0n4) - throw new E("integer: negative integers are not allowed"); - let hex = numberToHexUnpadded(num2); - if (Number.parseInt(hex[0], 16) & 8) - hex = "00" + hex; - if (hex.length & 1) - throw new E("unexpected DER parsing assertion: unpadded hex"); - return hex; - }, - decode(data) { - const { Err: E } = DER; - if (data[0] & 128) - throw new E("invalid signature integer: negative"); - if (data[0] === 0 && !(data[1] & 128)) - throw new E("invalid signature integer: unnecessary leading zero"); - return b2n(data); - } - }, - toSig(hex) { - const { Err: E, _int: int, _tlv: tlv } = DER; - const data = typeof hex === "string" ? h2b(hex) : hex; - abytes2(data); - const { v: seqBytes, l: seqLeftBytes } = tlv.decode(48, data); - if (seqLeftBytes.length) - throw new E("invalid signature: left bytes after parsing"); - const { v: rBytes, l: rLeftBytes } = tlv.decode(2, seqBytes); - const { v: sBytes, l: sLeftBytes } = tlv.decode(2, rLeftBytes); - if (sLeftBytes.length) - throw new E("invalid signature: left bytes after parsing"); - return { r: int.decode(rBytes), s: int.decode(sBytes) }; - }, - hexFromSig(sig) { - const { _tlv: tlv, _int: int } = DER; - const rs = tlv.encode(2, int.encode(sig.r)); - const ss = tlv.encode(2, int.encode(sig.s)); - const seq = rs + ss; - return tlv.encode(48, seq); - } - }; - _0n4 = BigInt(0); - _1n4 = BigInt(1); - _2n3 = BigInt(2); - _3n2 = BigInt(3); - _4n2 = BigInt(4); - } -}); - -// node_modules/@noble/curves/esm/_shortw_utils.js -function getHash(hash2) { - return { - hash: hash2, - hmac: (key, ...msgs) => hmac(hash2, key, concatBytes(...msgs)), - randomBytes - }; -} -function createCurve(curveDef, defHash) { - const create = (hash2) => weierstrass({ ...curveDef, ...getHash(hash2) }); - return { ...create(defHash), create }; -} -var init_shortw_utils = __esm({ - "node_modules/@noble/curves/esm/_shortw_utils.js"() { - init_hmac(); - init_utils(); - init_weierstrass(); - } -}); - -// node_modules/@noble/curves/esm/abstract/hash-to-curve.js -function i2osp(value, length) { - anum(value); - anum(length); - if (value < 0 || value >= 1 << 8 * length) - throw new Error("invalid I2OSP input: " + value); - const res = Array.from({ length }).fill(0); - for (let i = length - 1; i >= 0; i--) { - res[i] = value & 255; - value >>>= 8; - } - return new Uint8Array(res); -} -function strxor(a, b) { - const arr = new Uint8Array(a.length); - for (let i = 0; i < a.length; i++) { - arr[i] = a[i] ^ b[i]; - } - return arr; -} -function anum(item) { - if (!Number.isSafeInteger(item)) - throw new Error("number expected"); -} -function expand_message_xmd(msg, DST, lenInBytes, H) { - abytes2(msg); - abytes2(DST); - anum(lenInBytes); - if (DST.length > 255) - DST = H(concatBytes2(utf8ToBytes2("H2C-OVERSIZE-DST-"), DST)); - const { outputLen: b_in_bytes, blockLen: r_in_bytes } = H; - const ell = Math.ceil(lenInBytes / b_in_bytes); - if (lenInBytes > 65535 || ell > 255) - throw new Error("expand_message_xmd: invalid lenInBytes"); - const DST_prime = concatBytes2(DST, i2osp(DST.length, 1)); - const Z_pad = i2osp(0, r_in_bytes); - const l_i_b_str = i2osp(lenInBytes, 2); - const b = new Array(ell); - const b_0 = H(concatBytes2(Z_pad, msg, l_i_b_str, i2osp(0, 1), DST_prime)); - b[0] = H(concatBytes2(b_0, i2osp(1, 1), DST_prime)); - for (let i = 1; i <= ell; i++) { - const args2 = [strxor(b_0, b[i - 1]), i2osp(i + 1, 1), DST_prime]; - b[i] = H(concatBytes2(...args2)); - } - const pseudo_random_bytes = concatBytes2(...b); - return pseudo_random_bytes.slice(0, lenInBytes); -} -function expand_message_xof(msg, DST, lenInBytes, k, H) { - abytes2(msg); - abytes2(DST); - anum(lenInBytes); - if (DST.length > 255) { - const dkLen = Math.ceil(2 * k / 8); - DST = H.create({ dkLen }).update(utf8ToBytes2("H2C-OVERSIZE-DST-")).update(DST).digest(); - } - if (lenInBytes > 65535 || DST.length > 255) - throw new Error("expand_message_xof: invalid lenInBytes"); - return H.create({ dkLen: lenInBytes }).update(msg).update(i2osp(lenInBytes, 2)).update(DST).update(i2osp(DST.length, 1)).digest(); -} -function hash_to_field(msg, count, options) { - validateObject(options, { - DST: "stringOrUint8Array", - p: "bigint", - m: "isSafeInteger", - k: "isSafeInteger", - hash: "hash" - }); - const { p, k, m, hash: hash2, expand, DST: _DST } = options; - abytes2(msg); - anum(count); - const DST = typeof _DST === "string" ? utf8ToBytes2(_DST) : _DST; - const log2p = p.toString(2).length; - const L = Math.ceil((log2p + k) / 8); - const len_in_bytes = count * m * L; - let prb; - if (expand === "xmd") { - prb = expand_message_xmd(msg, DST, len_in_bytes, hash2); - } else if (expand === "xof") { - prb = expand_message_xof(msg, DST, len_in_bytes, k, hash2); - } else if (expand === "_internal_pass") { - prb = msg; - } else { - throw new Error('expand must be "xmd" or "xof"'); - } - const u = new Array(count); - for (let i = 0; i < count; i++) { - const e = new Array(m); - for (let j = 0; j < m; j++) { - const elm_offset = L * (j + i * m); - const tv = prb.subarray(elm_offset, elm_offset + L); - e[j] = mod(os2ip(tv), p); - } - u[i] = e; - } - return u; -} -function isogenyMap(field, map) { - const COEFF = map.map((i) => Array.from(i).reverse()); - return (x, y) => { - const [xNum, xDen, yNum, yDen] = COEFF.map((val) => val.reduce((acc, i) => field.add(field.mul(acc, x), i))); - x = field.div(xNum, xDen); - y = field.mul(y, field.div(yNum, yDen)); - return { x, y }; - }; -} -function createHasher(Point2, mapToCurve, def) { - if (typeof mapToCurve !== "function") - throw new Error("mapToCurve() must be defined"); - return { - // Encodes byte string to elliptic curve. - // hash_to_curve from https://www.rfc-editor.org/rfc/rfc9380#section-3 - hashToCurve(msg, options) { - const u = hash_to_field(msg, 2, { ...def, DST: def.DST, ...options }); - const u0 = Point2.fromAffine(mapToCurve(u[0])); - const u1 = Point2.fromAffine(mapToCurve(u[1])); - const P = u0.add(u1).clearCofactor(); - P.assertValidity(); - return P; - }, - // Encodes byte string to elliptic curve. - // encode_to_curve from https://www.rfc-editor.org/rfc/rfc9380#section-3 - encodeToCurve(msg, options) { - const u = hash_to_field(msg, 1, { ...def, DST: def.encodeDST, ...options }); - const P = Point2.fromAffine(mapToCurve(u[0])).clearCofactor(); - P.assertValidity(); - return P; - }, - // Same as encodeToCurve, but without hash - mapToCurve(scalars) { - if (!Array.isArray(scalars)) - throw new Error("mapToCurve: expected array of bigints"); - for (const i of scalars) - if (typeof i !== "bigint") - throw new Error("mapToCurve: expected array of bigints"); - const P = Point2.fromAffine(mapToCurve(scalars)).clearCofactor(); - P.assertValidity(); - return P; - } - }; -} -var os2ip; -var init_hash_to_curve = __esm({ - "node_modules/@noble/curves/esm/abstract/hash-to-curve.js"() { - init_modular(); - init_utils2(); - os2ip = bytesToNumberBE; - } -}); - -// node_modules/@noble/curves/esm/secp256k1.js -var secp256k1_exports = {}; -__export(secp256k1_exports, { - encodeToCurve: () => encodeToCurve, - hashToCurve: () => hashToCurve, - schnorr: () => schnorr, - secp256k1: () => secp256k1 -}); -function sqrtMod(y) { - const P = secp256k1P; - const _3n3 = BigInt(3), _6n = BigInt(6), _11n = BigInt(11), _22n = BigInt(22); - const _23n = BigInt(23), _44n = BigInt(44), _88n = BigInt(88); - const b2 = y * y * y % P; - const b3 = b2 * b2 * y % P; - const b6 = pow2(b3, _3n3, P) * b3 % P; - const b9 = pow2(b6, _3n3, P) * b3 % P; - const b11 = pow2(b9, _2n4, P) * b2 % P; - const b22 = pow2(b11, _11n, P) * b11 % P; - const b44 = pow2(b22, _22n, P) * b22 % P; - const b88 = pow2(b44, _44n, P) * b44 % P; - const b176 = pow2(b88, _88n, P) * b88 % P; - const b220 = pow2(b176, _44n, P) * b44 % P; - const b223 = pow2(b220, _3n3, P) * b3 % P; - const t1 = pow2(b223, _23n, P) * b22 % P; - const t2 = pow2(t1, _6n, P) * b2 % P; - const root = pow2(t2, _2n4, P); - if (!Fpk1.eql(Fpk1.sqr(root), y)) - throw new Error("Cannot find square root"); - return root; -} -function taggedHash(tag, ...messages) { - let tagP = TAGGED_HASH_PREFIXES[tag]; - if (tagP === void 0) { - const tagH = sha256(Uint8Array.from(tag, (c) => c.charCodeAt(0))); - tagP = concatBytes2(tagH, tagH); - TAGGED_HASH_PREFIXES[tag] = tagP; - } - return sha256(concatBytes2(tagP, ...messages)); -} -function schnorrGetExtPubKey(priv) { - let d_ = secp256k1.utils.normPrivateKeyToScalar(priv); - let p = Point.fromPrivateKey(d_); - const scalar = p.hasEvenY() ? d_ : modN(-d_); - return { scalar, bytes: pointToBytes(p) }; -} -function lift_x(x) { - aInRange("x", x, _1n5, secp256k1P); - const xx = modP(x * x); - const c = modP(xx * x + BigInt(7)); - let y = sqrtMod(c); - if (y % _2n4 !== _0n5) - y = modP(-y); - const p = new Point(x, y, _1n5); - p.assertValidity(); - return p; -} -function challenge(...args2) { - return modN(num(taggedHash("BIP0340/challenge", ...args2))); -} -function schnorrGetPublicKey(privateKey2) { - return schnorrGetExtPubKey(privateKey2).bytes; -} -function schnorrSign(message, privateKey2, auxRand = randomBytes(32)) { - const m = ensureBytes("message", message); - const { bytes: px, scalar: d } = schnorrGetExtPubKey(privateKey2); - const a = ensureBytes("auxRand", auxRand, 32); - const t = numTo32b(d ^ num(taggedHash("BIP0340/aux", a))); - const rand = taggedHash("BIP0340/nonce", t, px, m); - const k_ = modN(num(rand)); - if (k_ === _0n5) - throw new Error("sign failed: k is zero"); - const { bytes: rx, scalar: k } = schnorrGetExtPubKey(k_); - const e = challenge(rx, px, m); - const sig = new Uint8Array(64); - sig.set(rx, 0); - sig.set(numTo32b(modN(k + e * d)), 32); - if (!schnorrVerify(sig, m, px)) - throw new Error("sign: Invalid signature produced"); - return sig; -} -function schnorrVerify(signature, message, publicKey) { - const sig = ensureBytes("signature", signature, 64); - const m = ensureBytes("message", message); - const pub = ensureBytes("publicKey", publicKey, 32); - try { - const P = lift_x(num(pub)); - const r = num(sig.subarray(0, 32)); - if (!inRange(r, _1n5, secp256k1P)) - return false; - const s = num(sig.subarray(32, 64)); - if (!inRange(s, _1n5, secp256k1N)) - return false; - const e = challenge(numTo32b(r), pointToBytes(P), m); - const R = GmulAdd(P, s, modN(-e)); - if (!R || !R.hasEvenY() || R.toAffine().x !== r) - return false; - return true; - } catch (error) { - return false; - } -} -var secp256k1P, secp256k1N, _1n5, _2n4, divNearest, Fpk1, secp256k1, _0n5, TAGGED_HASH_PREFIXES, pointToBytes, numTo32b, modP, modN, Point, GmulAdd, num, schnorr, isoMap, mapSWU, htf, hashToCurve, encodeToCurve; -var init_secp256k1 = __esm({ - "node_modules/@noble/curves/esm/secp256k1.js"() { - init_sha256(); - init_utils(); - init_shortw_utils(); - init_hash_to_curve(); - init_modular(); - init_utils2(); - init_weierstrass(); - secp256k1P = BigInt("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f"); - secp256k1N = BigInt("0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"); - _1n5 = BigInt(1); - _2n4 = BigInt(2); - divNearest = (a, b) => (a + b / _2n4) / b; - Fpk1 = Field(secp256k1P, void 0, void 0, { sqrt: sqrtMod }); - secp256k1 = createCurve({ - a: BigInt(0), - // equation params: a, b - b: BigInt(7), - Fp: Fpk1, - // Field's prime: 2n**256n - 2n**32n - 2n**9n - 2n**8n - 2n**7n - 2n**6n - 2n**4n - 1n - n: secp256k1N, - // Curve order, total count of valid points in the field - // Base point (x, y) aka generator point - Gx: BigInt("55066263022277343669578718895168534326250603453777594175500187360389116729240"), - Gy: BigInt("32670510020758816978083085130507043184471273380659243275938904335757337482424"), - h: BigInt(1), - // Cofactor - lowS: true, - // Allow only low-S signatures by default in sign() and verify() - endo: { - // Endomorphism, see above - beta: BigInt("0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee"), - splitScalar: (k) => { - const n = secp256k1N; - const a1 = BigInt("0x3086d221a7d46bcde86c90e49284eb15"); - const b1 = -_1n5 * BigInt("0xe4437ed6010e88286f547fa90abfe4c3"); - const a2 = BigInt("0x114ca50f7a8e2f3f657c1108d9d44cfd8"); - const b2 = a1; - const POW_2_128 = BigInt("0x100000000000000000000000000000000"); - const c1 = divNearest(b2 * k, n); - const c2 = divNearest(-b1 * k, n); - let k1 = mod(k - c1 * a1 - c2 * a2, n); - let k2 = mod(-c1 * b1 - c2 * b2, n); - const k1neg = k1 > POW_2_128; - const k2neg = k2 > POW_2_128; - if (k1neg) - k1 = n - k1; - if (k2neg) - k2 = n - k2; - if (k1 > POW_2_128 || k2 > POW_2_128) { - throw new Error("splitScalar: Endomorphism failed, k=" + k); - } - return { k1neg, k1, k2neg, k2 }; - } - } - }, sha256); - _0n5 = BigInt(0); - TAGGED_HASH_PREFIXES = {}; - pointToBytes = (point) => point.toRawBytes(true).slice(1); - numTo32b = (n) => numberToBytesBE(n, 32); - modP = (x) => mod(x, secp256k1P); - modN = (x) => mod(x, secp256k1N); - Point = secp256k1.ProjectivePoint; - GmulAdd = (Q, a, b) => Point.BASE.multiplyAndAddUnsafe(Q, a, b); - num = bytesToNumberBE; - schnorr = /* @__PURE__ */ (() => ({ - getPublicKey: schnorrGetPublicKey, - sign: schnorrSign, - verify: schnorrVerify, - utils: { - randomPrivateKey: secp256k1.utils.randomPrivateKey, - lift_x, - pointToBytes, - numberToBytesBE, - bytesToNumberBE, - taggedHash, - mod - } - }))(); - isoMap = /* @__PURE__ */ (() => isogenyMap(Fpk1, [ - // xNum - [ - "0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa8c7", - "0x7d3d4c80bc321d5b9f315cea7fd44c5d595d2fc0bf63b92dfff1044f17c6581", - "0x534c328d23f234e6e2a413deca25caece4506144037c40314ecbd0b53d9dd262", - "0x8e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38e38daaaaa88c" - ], - // xDen - [ - "0xd35771193d94918a9ca34ccbb7b640dd86cd409542f8487d9fe6b745781eb49b", - "0xedadc6f64383dc1df7c4b2d51b54225406d36b641f5e41bbc52a56612a8c6d14", - "0x0000000000000000000000000000000000000000000000000000000000000001" - // LAST 1 - ], - // yNum - [ - "0x4bda12f684bda12f684bda12f684bda12f684bda12f684bda12f684b8e38e23c", - "0xc75e0c32d5cb7c0fa9d0a54b12a0a6d5647ab046d686da6fdffc90fc201d71a3", - "0x29a6194691f91a73715209ef6512e576722830a201be2018a765e85a9ecee931", - "0x2f684bda12f684bda12f684bda12f684bda12f684bda12f684bda12f38e38d84" - ], - // yDen - [ - "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffff93b", - "0x7a06534bb8bdb49fd5e9e6632722c2989467c1bfc8e8d978dfb425d2685c2573", - "0x6484aa716545ca2cf3a70c3fa8fe337e0a3d21162f0d6299a7bf8192bfd2a76f", - "0x0000000000000000000000000000000000000000000000000000000000000001" - // LAST 1 - ] - ].map((i) => i.map((j) => BigInt(j)))))(); - mapSWU = /* @__PURE__ */ (() => mapToCurveSimpleSWU(Fpk1, { - A: BigInt("0x3f8731abdd661adca08a5558f0f5d272e953d363cb6f0e5d405447c01a444533"), - B: BigInt("1771"), - Z: Fpk1.create(BigInt("-11")) - }))(); - htf = /* @__PURE__ */ (() => createHasher(secp256k1.ProjectivePoint, (scalars) => { - const { x, y } = mapSWU(Fpk1.create(scalars[0])); - return isoMap(x, y); - }, { - DST: "secp256k1_XMD:SHA-256_SSWU_RO_", - encodeDST: "secp256k1_XMD:SHA-256_SSWU_NU_", - p: Fpk1.ORDER, - m: 1, - k: 128, - expand: "xmd", - hash: sha256 - }))(); - hashToCurve = /* @__PURE__ */ (() => htf.hashToCurve)(); - encodeToCurve = /* @__PURE__ */ (() => htf.encodeToCurve)(); - } -}); - -// node_modules/viem/_esm/errors/version.js -var version; -var init_version = __esm({ - "node_modules/viem/_esm/errors/version.js"() { - version = "2.23.15"; - } -}); - -// node_modules/viem/_esm/errors/base.js -function walk(err, fn) { - if (fn?.(err)) - return err; - if (err && typeof err === "object" && "cause" in err && err.cause !== void 0) - return walk(err.cause, fn); - return fn ? null : err; -} -var errorConfig, BaseError; -var init_base = __esm({ - "node_modules/viem/_esm/errors/base.js"() { - init_version(); - errorConfig = { - getDocsUrl: ({ docsBaseUrl, docsPath: docsPath3 = "", docsSlug }) => docsPath3 ? `${docsBaseUrl ?? "https://viem.sh"}${docsPath3}${docsSlug ? `#${docsSlug}` : ""}` : void 0, - version: `viem@${version}` - }; - BaseError = class _BaseError extends Error { - constructor(shortMessage, args2 = {}) { - const details = (() => { - if (args2.cause instanceof _BaseError) - return args2.cause.details; - if (args2.cause?.message) - return args2.cause.message; - return args2.details; - })(); - const docsPath3 = (() => { - if (args2.cause instanceof _BaseError) - return args2.cause.docsPath || args2.docsPath; - return args2.docsPath; - })(); - const docsUrl = errorConfig.getDocsUrl?.({ ...args2, docsPath: docsPath3 }); - const message = [ - shortMessage || "An error occurred.", - "", - ...args2.metaMessages ? [...args2.metaMessages, ""] : [], - ...docsUrl ? [`Docs: ${docsUrl}`] : [], - ...details ? [`Details: ${details}`] : [], - ...errorConfig.version ? [`Version: ${errorConfig.version}`] : [] - ].join("\n"); - super(message, args2.cause ? { cause: args2.cause } : void 0); - Object.defineProperty(this, "details", { - enumerable: true, - configurable: true, - writable: true, - value: void 0 - }); - Object.defineProperty(this, "docsPath", { - enumerable: true, - configurable: true, - writable: true, - value: void 0 - }); - Object.defineProperty(this, "metaMessages", { - enumerable: true, - configurable: true, - writable: true, - value: void 0 - }); - Object.defineProperty(this, "shortMessage", { - enumerable: true, - configurable: true, - writable: true, - value: void 0 - }); - Object.defineProperty(this, "version", { - enumerable: true, - configurable: true, - writable: true, - value: void 0 - }); - Object.defineProperty(this, "name", { - enumerable: true, - configurable: true, - writable: true, - value: "BaseError" - }); - this.details = details; - this.docsPath = docsPath3; - this.metaMessages = args2.metaMessages; - this.name = args2.name ?? this.name; - this.shortMessage = shortMessage; - this.version = version; - } - walk(fn) { - return walk(this, fn); - } - }; - } -}); - -// node_modules/viem/_esm/errors/encoding.js -var IntegerOutOfRangeError, InvalidBytesBooleanError, SizeOverflowError; -var init_encoding = __esm({ - "node_modules/viem/_esm/errors/encoding.js"() { - init_base(); - IntegerOutOfRangeError = class extends BaseError { - constructor({ max, min, signed, size: size3, value }) { - super(`Number "${value}" is not in safe ${size3 ? `${size3 * 8}-bit ${signed ? "signed" : "unsigned"} ` : ""}integer range ${max ? `(${min} to ${max})` : `(above ${min})`}`, { name: "IntegerOutOfRangeError" }); - } - }; - InvalidBytesBooleanError = class extends BaseError { - constructor(bytes) { - super(`Bytes value "${bytes}" is not a valid boolean. The bytes array must contain a single byte of either a 0 or 1 value.`, { - name: "InvalidBytesBooleanError" - }); - } - }; - SizeOverflowError = class extends BaseError { - constructor({ givenSize, maxSize }) { - super(`Size cannot exceed ${maxSize} bytes. Given size: ${givenSize} bytes.`, { name: "SizeOverflowError" }); - } - }; - } -}); - -// node_modules/viem/_esm/errors/data.js -var SliceOffsetOutOfBoundsError, SizeExceedsPaddingSizeError, InvalidBytesLengthError; -var init_data = __esm({ - "node_modules/viem/_esm/errors/data.js"() { - init_base(); - SliceOffsetOutOfBoundsError = class extends BaseError { - constructor({ offset, position, size: size3 }) { - super(`Slice ${position === "start" ? "starting" : "ending"} at offset "${offset}" is out-of-bounds (size: ${size3}).`, { name: "SliceOffsetOutOfBoundsError" }); - } - }; - SizeExceedsPaddingSizeError = class extends BaseError { - constructor({ size: size3, targetSize, type }) { - super(`${type.charAt(0).toUpperCase()}${type.slice(1).toLowerCase()} size (${size3}) exceeds padding size (${targetSize}).`, { name: "SizeExceedsPaddingSizeError" }); - } - }; - InvalidBytesLengthError = class extends BaseError { - constructor({ size: size3, targetSize, type }) { - super(`${type.charAt(0).toUpperCase()}${type.slice(1).toLowerCase()} is expected to be ${targetSize} ${type} long, but is ${size3} ${type} long.`, { name: "InvalidBytesLengthError" }); - } - }; - } -}); - -// node_modules/viem/_esm/utils/data/pad.js -function pad(hexOrBytes, { dir, size: size3 = 32 } = {}) { - if (typeof hexOrBytes === "string") - return padHex(hexOrBytes, { dir, size: size3 }); - return padBytes(hexOrBytes, { dir, size: size3 }); -} -function padHex(hex_, { dir, size: size3 = 32 } = {}) { - if (size3 === null) - return hex_; - const hex = hex_.replace("0x", ""); - if (hex.length > size3 * 2) - throw new SizeExceedsPaddingSizeError({ - size: Math.ceil(hex.length / 2), - targetSize: size3, - type: "hex" - }); - return `0x${hex[dir === "right" ? "padEnd" : "padStart"](size3 * 2, "0")}`; -} -function padBytes(bytes, { dir, size: size3 = 32 } = {}) { - if (size3 === null) - return bytes; - if (bytes.length > size3) - throw new SizeExceedsPaddingSizeError({ - size: bytes.length, - targetSize: size3, - type: "bytes" - }); - const paddedBytes = new Uint8Array(size3); - for (let i = 0; i < size3; i++) { - const padEnd = dir === "right"; - paddedBytes[padEnd ? i : size3 - i - 1] = bytes[padEnd ? i : bytes.length - i - 1]; - } - return paddedBytes; -} -var init_pad = __esm({ - "node_modules/viem/_esm/utils/data/pad.js"() { - init_data(); - } -}); - -// node_modules/viem/_esm/utils/data/isHex.js -function isHex(value, { strict = true } = {}) { - if (!value) - return false; - if (typeof value !== "string") - return false; - return strict ? /^0x[0-9a-fA-F]*$/.test(value) : value.startsWith("0x"); -} -var init_isHex = __esm({ - "node_modules/viem/_esm/utils/data/isHex.js"() { - } -}); - -// node_modules/viem/_esm/utils/data/size.js -function size(value) { - if (isHex(value, { strict: false })) - return Math.ceil((value.length - 2) / 2); - return value.length; -} -var init_size = __esm({ - "node_modules/viem/_esm/utils/data/size.js"() { - init_isHex(); - } -}); - -// node_modules/viem/_esm/utils/data/trim.js -function trim(hexOrBytes, { dir = "left" } = {}) { - let data = typeof hexOrBytes === "string" ? hexOrBytes.replace("0x", "") : hexOrBytes; - let sliceLength = 0; - for (let i = 0; i < data.length - 1; i++) { - if (data[dir === "left" ? i : data.length - i - 1].toString() === "0") - sliceLength++; - else - break; - } - data = dir === "left" ? data.slice(sliceLength) : data.slice(0, data.length - sliceLength); - if (typeof hexOrBytes === "string") { - if (data.length === 1 && dir === "right") - data = `${data}0`; - return `0x${data.length % 2 === 1 ? `0${data}` : data}`; - } - return data; -} -var init_trim = __esm({ - "node_modules/viem/_esm/utils/data/trim.js"() { - } -}); - -// node_modules/viem/_esm/utils/encoding/toBytes.js -function toBytes2(value, opts = {}) { - if (typeof value === "number" || typeof value === "bigint") - return numberToBytes(value, opts); - if (typeof value === "boolean") - return boolToBytes(value, opts); - if (isHex(value)) - return hexToBytes2(value, opts); - return stringToBytes(value, opts); -} -function boolToBytes(value, opts = {}) { - const bytes = new Uint8Array(1); - bytes[0] = Number(value); - if (typeof opts.size === "number") { - assertSize(bytes, { size: opts.size }); - return pad(bytes, { size: opts.size }); - } - return bytes; -} -function charCodeToBase16(char) { - if (char >= charCodeMap.zero && char <= charCodeMap.nine) - return char - charCodeMap.zero; - if (char >= charCodeMap.A && char <= charCodeMap.F) - return char - (charCodeMap.A - 10); - if (char >= charCodeMap.a && char <= charCodeMap.f) - return char - (charCodeMap.a - 10); - return void 0; -} -function hexToBytes2(hex_, opts = {}) { - let hex = hex_; - if (opts.size) { - assertSize(hex, { size: opts.size }); - hex = pad(hex, { dir: "right", size: opts.size }); - } - let hexString = hex.slice(2); - if (hexString.length % 2) - hexString = `0${hexString}`; - const length = hexString.length / 2; - const bytes = new Uint8Array(length); - for (let index2 = 0, j = 0; index2 < length; index2++) { - const nibbleLeft = charCodeToBase16(hexString.charCodeAt(j++)); - const nibbleRight = charCodeToBase16(hexString.charCodeAt(j++)); - if (nibbleLeft === void 0 || nibbleRight === void 0) { - throw new BaseError(`Invalid byte sequence ("${hexString[j - 2]}${hexString[j - 1]}" in "${hexString}").`); - } - bytes[index2] = nibbleLeft * 16 + nibbleRight; - } - return bytes; -} -function numberToBytes(value, opts) { - const hex = numberToHex(value, opts); - return hexToBytes2(hex); -} -function stringToBytes(value, opts = {}) { - const bytes = encoder.encode(value); - if (typeof opts.size === "number") { - assertSize(bytes, { size: opts.size }); - return pad(bytes, { dir: "right", size: opts.size }); - } - return bytes; -} -var encoder, charCodeMap; -var init_toBytes = __esm({ - "node_modules/viem/_esm/utils/encoding/toBytes.js"() { - init_base(); - init_isHex(); - init_pad(); - init_fromHex(); - init_toHex(); - encoder = /* @__PURE__ */ new TextEncoder(); - charCodeMap = { - zero: 48, - nine: 57, - A: 65, - F: 70, - a: 97, - f: 102 - }; - } -}); - -// node_modules/viem/_esm/utils/encoding/fromHex.js -function assertSize(hexOrBytes, { size: size3 }) { - if (size(hexOrBytes) > size3) - throw new SizeOverflowError({ - givenSize: size(hexOrBytes), - maxSize: size3 - }); -} -function hexToBigInt(hex, opts = {}) { - const { signed } = opts; - if (opts.size) - assertSize(hex, { size: opts.size }); - const value = BigInt(hex); - if (!signed) - return value; - const size3 = (hex.length - 2) / 2; - const max = (1n << BigInt(size3) * 8n - 1n) - 1n; - if (value <= max) - return value; - return value - BigInt(`0x${"f".padStart(size3 * 2, "f")}`) - 1n; -} -function hexToNumber2(hex, opts = {}) { - return Number(hexToBigInt(hex, opts)); -} -var init_fromHex = __esm({ - "node_modules/viem/_esm/utils/encoding/fromHex.js"() { - init_encoding(); - init_size(); - } -}); - -// node_modules/viem/_esm/utils/encoding/toHex.js -function toHex(value, opts = {}) { - if (typeof value === "number" || typeof value === "bigint") - return numberToHex(value, opts); - if (typeof value === "string") { - return stringToHex(value, opts); - } - if (typeof value === "boolean") - return boolToHex(value, opts); - return bytesToHex2(value, opts); -} -function boolToHex(value, opts = {}) { - const hex = `0x${Number(value)}`; - if (typeof opts.size === "number") { - assertSize(hex, { size: opts.size }); - return pad(hex, { size: opts.size }); - } - return hex; -} -function bytesToHex2(value, opts = {}) { - let string = ""; - for (let i = 0; i < value.length; i++) { - string += hexes2[value[i]]; - } - const hex = `0x${string}`; - if (typeof opts.size === "number") { - assertSize(hex, { size: opts.size }); - return pad(hex, { dir: "right", size: opts.size }); - } - return hex; -} -function numberToHex(value_, opts = {}) { - const { signed, size: size3 } = opts; - const value = BigInt(value_); - let maxValue; - if (size3) { - if (signed) - maxValue = (1n << BigInt(size3) * 8n - 1n) - 1n; - else - maxValue = 2n ** (BigInt(size3) * 8n) - 1n; - } else if (typeof value_ === "number") { - maxValue = BigInt(Number.MAX_SAFE_INTEGER); - } - const minValue = typeof maxValue === "bigint" && signed ? -maxValue - 1n : 0; - if (maxValue && value > maxValue || value < minValue) { - const suffix = typeof value_ === "bigint" ? "n" : ""; - throw new IntegerOutOfRangeError({ - max: maxValue ? `${maxValue}${suffix}` : void 0, - min: `${minValue}${suffix}`, - signed, - size: size3, - value: `${value_}${suffix}` - }); - } - const hex = `0x${(signed && value < 0 ? (1n << BigInt(size3 * 8)) + BigInt(value) : value).toString(16)}`; - if (size3) - return pad(hex, { size: size3 }); - return hex; -} -function stringToHex(value_, opts = {}) { - const value = encoder2.encode(value_); - return bytesToHex2(value, opts); -} -var hexes2, encoder2; -var init_toHex = __esm({ - "node_modules/viem/_esm/utils/encoding/toHex.js"() { - init_encoding(); - init_pad(); - init_fromHex(); - hexes2 = /* @__PURE__ */ Array.from({ length: 256 }, (_v, i) => i.toString(16).padStart(2, "0")); - encoder2 = /* @__PURE__ */ new TextEncoder(); - } -}); - -// node_modules/viem/_esm/errors/address.js -var InvalidAddressError; -var init_address = __esm({ - "node_modules/viem/_esm/errors/address.js"() { - init_base(); - InvalidAddressError = class extends BaseError { - constructor({ address }) { - super(`Address "${address}" is invalid.`, { - metaMessages: [ - "- Address must be a hex value of 20 bytes (40 hex characters).", - "- Address must match its checksum counterpart." - ], - name: "InvalidAddressError" - }); - } - }; - } -}); - -// node_modules/viem/_esm/utils/lru.js -var LruMap; -var init_lru = __esm({ - "node_modules/viem/_esm/utils/lru.js"() { - LruMap = class extends Map { - constructor(size3) { - super(); - Object.defineProperty(this, "maxSize", { - enumerable: true, - configurable: true, - writable: true, - value: void 0 - }); - this.maxSize = size3; - } - get(key) { - const value = super.get(key); - if (super.has(key) && value !== void 0) { - this.delete(key); - super.set(key, value); - } - return value; - } - set(key, value) { - super.set(key, value); - if (this.maxSize && this.size > this.maxSize) { - const firstKey = this.keys().next().value; - if (firstKey) - this.delete(firstKey); - } - return this; - } - }; - } -}); - -// node_modules/@noble/hashes/esm/sha3.js -function keccakP(s, rounds = 24) { - const B = new Uint32Array(5 * 2); - for (let round = 24 - rounds; round < 24; round++) { - for (let x = 0; x < 10; x++) - B[x] = s[x] ^ s[x + 10] ^ s[x + 20] ^ s[x + 30] ^ s[x + 40]; - for (let x = 0; x < 10; x += 2) { - const idx1 = (x + 8) % 10; - const idx0 = (x + 2) % 10; - const B0 = B[idx0]; - const B1 = B[idx0 + 1]; - const Th = rotlH(B0, B1, 1) ^ B[idx1]; - const Tl = rotlL(B0, B1, 1) ^ B[idx1 + 1]; - for (let y = 0; y < 50; y += 10) { - s[x + y] ^= Th; - s[x + y + 1] ^= Tl; - } - } - let curH = s[2]; - let curL = s[3]; - for (let t = 0; t < 24; t++) { - const shift = SHA3_ROTL[t]; - const Th = rotlH(curH, curL, shift); - const Tl = rotlL(curH, curL, shift); - const PI = SHA3_PI[t]; - curH = s[PI]; - curL = s[PI + 1]; - s[PI] = Th; - s[PI + 1] = Tl; - } - for (let y = 0; y < 50; y += 10) { - for (let x = 0; x < 10; x++) - B[x] = s[y + x]; - for (let x = 0; x < 10; x++) - s[y + x] ^= ~B[(x + 2) % 10] & B[(x + 4) % 10]; - } - s[0] ^= SHA3_IOTA_H[round]; - s[1] ^= SHA3_IOTA_L[round]; - } - B.fill(0); -} -var SHA3_PI, SHA3_ROTL, _SHA3_IOTA, _0n6, _1n6, _2n5, _7n, _256n, _0x71n, SHA3_IOTA_H, SHA3_IOTA_L, rotlH, rotlL, Keccak, gen, sha3_224, sha3_256, sha3_384, sha3_512, keccak_224, keccak_256, keccak_384, keccak_512, genShake, shake128, shake256; -var init_sha3 = __esm({ - "node_modules/@noble/hashes/esm/sha3.js"() { - init_assert(); - init_u64(); - init_utils(); - SHA3_PI = []; - SHA3_ROTL = []; - _SHA3_IOTA = []; - _0n6 = /* @__PURE__ */ BigInt(0); - _1n6 = /* @__PURE__ */ BigInt(1); - _2n5 = /* @__PURE__ */ BigInt(2); - _7n = /* @__PURE__ */ BigInt(7); - _256n = /* @__PURE__ */ BigInt(256); - _0x71n = /* @__PURE__ */ BigInt(113); - for (let round = 0, R = _1n6, x = 1, y = 0; round < 24; round++) { - [x, y] = [y, (2 * x + 3 * y) % 5]; - SHA3_PI.push(2 * (5 * y + x)); - SHA3_ROTL.push((round + 1) * (round + 2) / 2 % 64); - let t = _0n6; - for (let j = 0; j < 7; j++) { - R = (R << _1n6 ^ (R >> _7n) * _0x71n) % _256n; - if (R & _2n5) - t ^= _1n6 << (_1n6 << /* @__PURE__ */ BigInt(j)) - _1n6; - } - _SHA3_IOTA.push(t); - } - [SHA3_IOTA_H, SHA3_IOTA_L] = /* @__PURE__ */ split(_SHA3_IOTA, true); - rotlH = (h, l, s) => s > 32 ? rotlBH(h, l, s) : rotlSH(h, l, s); - rotlL = (h, l, s) => s > 32 ? rotlBL(h, l, s) : rotlSL(h, l, s); - Keccak = class _Keccak extends Hash { - // NOTE: we accept arguments in bytes instead of bits here. - constructor(blockLen, suffix, outputLen, enableXOF = false, rounds = 24) { - super(); - this.blockLen = blockLen; - this.suffix = suffix; - this.outputLen = outputLen; - this.enableXOF = enableXOF; - this.rounds = rounds; - this.pos = 0; - this.posOut = 0; - this.finished = false; - this.destroyed = false; - anumber(outputLen); - if (0 >= this.blockLen || this.blockLen >= 200) - throw new Error("Sha3 supports only keccak-f1600 function"); - this.state = new Uint8Array(200); - this.state32 = u32(this.state); - } - keccak() { - if (!isLE) - byteSwap32(this.state32); - keccakP(this.state32, this.rounds); - if (!isLE) - byteSwap32(this.state32); - this.posOut = 0; - this.pos = 0; - } - update(data) { - aexists(this); - const { blockLen, state } = this; - data = toBytes(data); - const len = data.length; - for (let pos = 0; pos < len; ) { - const take = Math.min(blockLen - this.pos, len - pos); - for (let i = 0; i < take; i++) - state[this.pos++] ^= data[pos++]; - if (this.pos === blockLen) - this.keccak(); - } - return this; - } - finish() { - if (this.finished) - return; - this.finished = true; - const { state, suffix, pos, blockLen } = this; - state[pos] ^= suffix; - if ((suffix & 128) !== 0 && pos === blockLen - 1) - this.keccak(); - state[blockLen - 1] ^= 128; - this.keccak(); - } - writeInto(out) { - aexists(this, false); - abytes(out); - this.finish(); - const bufferOut = this.state; - const { blockLen } = this; - for (let pos = 0, len = out.length; pos < len; ) { - if (this.posOut >= blockLen) - this.keccak(); - const take = Math.min(blockLen - this.posOut, len - pos); - out.set(bufferOut.subarray(this.posOut, this.posOut + take), pos); - this.posOut += take; - pos += take; - } - return out; - } - xofInto(out) { - if (!this.enableXOF) - throw new Error("XOF is not possible for this instance"); - return this.writeInto(out); - } - xof(bytes) { - anumber(bytes); - return this.xofInto(new Uint8Array(bytes)); - } - digestInto(out) { - aoutput(out, this); - if (this.finished) - throw new Error("digest() was already called"); - this.writeInto(out); - this.destroy(); - return out; - } - digest() { - return this.digestInto(new Uint8Array(this.outputLen)); - } - destroy() { - this.destroyed = true; - this.state.fill(0); - } - _cloneInto(to) { - const { blockLen, suffix, outputLen, rounds, enableXOF } = this; - to || (to = new _Keccak(blockLen, suffix, outputLen, enableXOF, rounds)); - to.state32.set(this.state32); - to.pos = this.pos; - to.posOut = this.posOut; - to.finished = this.finished; - to.rounds = rounds; - to.suffix = suffix; - to.outputLen = outputLen; - to.enableXOF = enableXOF; - to.destroyed = this.destroyed; - return to; - } - }; - gen = (suffix, blockLen, outputLen) => wrapConstructor(() => new Keccak(blockLen, suffix, outputLen)); - sha3_224 = /* @__PURE__ */ gen(6, 144, 224 / 8); - sha3_256 = /* @__PURE__ */ gen(6, 136, 256 / 8); - sha3_384 = /* @__PURE__ */ gen(6, 104, 384 / 8); - sha3_512 = /* @__PURE__ */ gen(6, 72, 512 / 8); - keccak_224 = /* @__PURE__ */ gen(1, 144, 224 / 8); - keccak_256 = /* @__PURE__ */ gen(1, 136, 256 / 8); - keccak_384 = /* @__PURE__ */ gen(1, 104, 384 / 8); - keccak_512 = /* @__PURE__ */ gen(1, 72, 512 / 8); - genShake = (suffix, blockLen, outputLen) => wrapXOFConstructorWithOpts((opts = {}) => new Keccak(blockLen, suffix, opts.dkLen === void 0 ? outputLen : opts.dkLen, true)); - shake128 = /* @__PURE__ */ genShake(31, 168, 128 / 8); - shake256 = /* @__PURE__ */ genShake(31, 136, 256 / 8); - } -}); - -// node_modules/viem/_esm/utils/hash/keccak256.js -function keccak256(value, to_) { - const to = to_ || "hex"; - const bytes = keccak_256(isHex(value, { strict: false }) ? toBytes2(value) : value); - if (to === "bytes") - return bytes; - return toHex(bytes); -} -var init_keccak256 = __esm({ - "node_modules/viem/_esm/utils/hash/keccak256.js"() { - init_sha3(); - init_isHex(); - init_toBytes(); - init_toHex(); - } -}); - -// node_modules/viem/_esm/utils/address/getAddress.js -function checksumAddress(address_, chainId2) { - if (checksumAddressCache.has(`${address_}.${chainId2}`)) - return checksumAddressCache.get(`${address_}.${chainId2}`); - const hexAddress = chainId2 ? `${chainId2}${address_.toLowerCase()}` : address_.substring(2).toLowerCase(); - const hash2 = keccak256(stringToBytes(hexAddress), "bytes"); - const address = (chainId2 ? hexAddress.substring(`${chainId2}0x`.length) : hexAddress).split(""); - for (let i = 0; i < 40; i += 2) { - if (hash2[i >> 1] >> 4 >= 8 && address[i]) { - address[i] = address[i].toUpperCase(); - } - if ((hash2[i >> 1] & 15) >= 8 && address[i + 1]) { - address[i + 1] = address[i + 1].toUpperCase(); - } - } - const result = `0x${address.join("")}`; - checksumAddressCache.set(`${address_}.${chainId2}`, result); - return result; -} -function getAddress(address, chainId2) { - if (!isAddress(address, { strict: false })) - throw new InvalidAddressError({ address }); - return checksumAddress(address, chainId2); -} -var checksumAddressCache; -var init_getAddress = __esm({ - "node_modules/viem/_esm/utils/address/getAddress.js"() { - init_address(); - init_toBytes(); - init_keccak256(); - init_lru(); - init_isAddress(); - checksumAddressCache = /* @__PURE__ */ new LruMap(8192); - } -}); - -// node_modules/viem/_esm/utils/address/isAddress.js -function isAddress(address, options) { - const { strict = true } = options ?? {}; - const cacheKey = `${address}.${strict}`; - if (isAddressCache.has(cacheKey)) - return isAddressCache.get(cacheKey); - const result = (() => { - if (!addressRegex.test(address)) - return false; - if (address.toLowerCase() === address) - return true; - if (strict) - return checksumAddress(address) === address; - return true; - })(); - isAddressCache.set(cacheKey, result); - return result; -} -var addressRegex, isAddressCache; -var init_isAddress = __esm({ - "node_modules/viem/_esm/utils/address/isAddress.js"() { - init_lru(); - init_getAddress(); - addressRegex = /^0x[a-fA-F0-9]{40}$/; - isAddressCache = /* @__PURE__ */ new LruMap(8192); - } -}); - -// node_modules/viem/_esm/utils/data/concat.js -function concat(values) { - if (typeof values[0] === "string") - return concatHex(values); - return concatBytes3(values); -} -function concatBytes3(values) { - let length = 0; - for (const arr of values) { - length += arr.length; - } - const result = new Uint8Array(length); - let offset = 0; - for (const arr of values) { - result.set(arr, offset); - offset += arr.length; - } - return result; -} -function concatHex(values) { - return `0x${values.reduce((acc, x) => acc + x.replace("0x", ""), "")}`; -} -var init_concat = __esm({ - "node_modules/viem/_esm/utils/data/concat.js"() { - } -}); - -// node_modules/viem/_esm/errors/cursor.js -var NegativeOffsetError, PositionOutOfBoundsError, RecursiveReadLimitExceededError; -var init_cursor = __esm({ - "node_modules/viem/_esm/errors/cursor.js"() { - init_base(); - NegativeOffsetError = class extends BaseError { - constructor({ offset }) { - super(`Offset \`${offset}\` cannot be negative.`, { - name: "NegativeOffsetError" - }); - } - }; - PositionOutOfBoundsError = class extends BaseError { - constructor({ length, position }) { - super(`Position \`${position}\` is out of bounds (\`0 < position < ${length}\`).`, { name: "PositionOutOfBoundsError" }); - } - }; - RecursiveReadLimitExceededError = class extends BaseError { - constructor({ count, limit }) { - super(`Recursive read limit of \`${limit}\` exceeded (recursive read count: \`${count}\`).`, { name: "RecursiveReadLimitExceededError" }); - } - }; - } -}); - -// node_modules/viem/_esm/utils/cursor.js -function createCursor(bytes, { recursiveReadLimit = 8192 } = {}) { - const cursor = Object.create(staticCursor); - cursor.bytes = bytes; - cursor.dataView = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength); - cursor.positionReadCount = /* @__PURE__ */ new Map(); - cursor.recursiveReadLimit = recursiveReadLimit; - return cursor; -} -var staticCursor; -var init_cursor2 = __esm({ - "node_modules/viem/_esm/utils/cursor.js"() { - init_cursor(); - staticCursor = { - bytes: new Uint8Array(), - dataView: new DataView(new ArrayBuffer(0)), - position: 0, - positionReadCount: /* @__PURE__ */ new Map(), - recursiveReadCount: 0, - recursiveReadLimit: Number.POSITIVE_INFINITY, - assertReadLimit() { - if (this.recursiveReadCount >= this.recursiveReadLimit) - throw new RecursiveReadLimitExceededError({ - count: this.recursiveReadCount + 1, - limit: this.recursiveReadLimit - }); - }, - assertPosition(position) { - if (position < 0 || position > this.bytes.length - 1) - throw new PositionOutOfBoundsError({ - length: this.bytes.length, - position - }); - }, - decrementPosition(offset) { - if (offset < 0) - throw new NegativeOffsetError({ offset }); - const position = this.position - offset; - this.assertPosition(position); - this.position = position; - }, - getReadCount(position) { - return this.positionReadCount.get(position || this.position) || 0; - }, - incrementPosition(offset) { - if (offset < 0) - throw new NegativeOffsetError({ offset }); - const position = this.position + offset; - this.assertPosition(position); - this.position = position; - }, - inspectByte(position_) { - const position = position_ ?? this.position; - this.assertPosition(position); - return this.bytes[position]; - }, - inspectBytes(length, position_) { - const position = position_ ?? this.position; - this.assertPosition(position + length - 1); - return this.bytes.subarray(position, position + length); - }, - inspectUint8(position_) { - const position = position_ ?? this.position; - this.assertPosition(position); - return this.bytes[position]; - }, - inspectUint16(position_) { - const position = position_ ?? this.position; - this.assertPosition(position + 1); - return this.dataView.getUint16(position); - }, - inspectUint24(position_) { - const position = position_ ?? this.position; - this.assertPosition(position + 2); - return (this.dataView.getUint16(position) << 8) + this.dataView.getUint8(position + 2); - }, - inspectUint32(position_) { - const position = position_ ?? this.position; - this.assertPosition(position + 3); - return this.dataView.getUint32(position); - }, - pushByte(byte) { - this.assertPosition(this.position); - this.bytes[this.position] = byte; - this.position++; - }, - pushBytes(bytes) { - this.assertPosition(this.position + bytes.length - 1); - this.bytes.set(bytes, this.position); - this.position += bytes.length; - }, - pushUint8(value) { - this.assertPosition(this.position); - this.bytes[this.position] = value; - this.position++; - }, - pushUint16(value) { - this.assertPosition(this.position + 1); - this.dataView.setUint16(this.position, value); - this.position += 2; - }, - pushUint24(value) { - this.assertPosition(this.position + 2); - this.dataView.setUint16(this.position, value >> 8); - this.dataView.setUint8(this.position + 2, value & ~4294967040); - this.position += 3; - }, - pushUint32(value) { - this.assertPosition(this.position + 3); - this.dataView.setUint32(this.position, value); - this.position += 4; - }, - readByte() { - this.assertReadLimit(); - this._touch(); - const value = this.inspectByte(); - this.position++; - return value; - }, - readBytes(length, size3) { - this.assertReadLimit(); - this._touch(); - const value = this.inspectBytes(length); - this.position += size3 ?? length; - return value; - }, - readUint8() { - this.assertReadLimit(); - this._touch(); - const value = this.inspectUint8(); - this.position += 1; - return value; - }, - readUint16() { - this.assertReadLimit(); - this._touch(); - const value = this.inspectUint16(); - this.position += 2; - return value; - }, - readUint24() { - this.assertReadLimit(); - this._touch(); - const value = this.inspectUint24(); - this.position += 3; - return value; - }, - readUint32() { - this.assertReadLimit(); - this._touch(); - const value = this.inspectUint32(); - this.position += 4; - return value; - }, - get remaining() { - return this.bytes.length - this.position; - }, - setPosition(position) { - const oldPosition = this.position; - this.assertPosition(position); - this.position = position; - return () => this.position = oldPosition; - }, - _touch() { - if (this.recursiveReadLimit === Number.POSITIVE_INFINITY) - return; - const count = this.getReadCount(); - this.positionReadCount.set(this.position, count + 1); - if (count > 0) - this.recursiveReadCount++; - } - }; - } -}); - -// node_modules/viem/_esm/constants/unit.js -var etherUnits, gweiUnits; -var init_unit = __esm({ - "node_modules/viem/_esm/constants/unit.js"() { - etherUnits = { - gwei: 9, - wei: 18 - }; - gweiUnits = { - ether: -9, - wei: 9 - }; - } -}); - -// node_modules/viem/_esm/utils/unit/formatUnits.js -function formatUnits(value, decimals) { - let display = value.toString(); - const negative = display.startsWith("-"); - if (negative) - display = display.slice(1); - display = display.padStart(decimals, "0"); - let [integer, fraction] = [ - display.slice(0, display.length - decimals), - display.slice(display.length - decimals) - ]; - fraction = fraction.replace(/(0+)$/, ""); - return `${negative ? "-" : ""}${integer || "0"}${fraction ? `.${fraction}` : ""}`; -} -var init_formatUnits = __esm({ - "node_modules/viem/_esm/utils/unit/formatUnits.js"() { - } -}); - -// node_modules/viem/_esm/utils/unit/formatEther.js -function formatEther(wei, unit = "wei") { - return formatUnits(wei, etherUnits[unit]); -} -var init_formatEther = __esm({ - "node_modules/viem/_esm/utils/unit/formatEther.js"() { - init_unit(); - init_formatUnits(); - } -}); - -// node_modules/viem/_esm/utils/unit/formatGwei.js -function formatGwei(wei, unit = "wei") { - return formatUnits(wei, gweiUnits[unit]); -} -var init_formatGwei = __esm({ - "node_modules/viem/_esm/utils/unit/formatGwei.js"() { - init_unit(); - init_formatUnits(); - } -}); - -// node_modules/viem/_esm/errors/transaction.js -function prettyPrint(args2) { - const entries = Object.entries(args2).map(([key, value]) => { - if (value === void 0 || value === false) - return null; - return [key, value]; - }).filter(Boolean); - const maxLength = entries.reduce((acc, [key]) => Math.max(acc, key.length), 0); - return entries.map(([key, value]) => ` ${`${key}:`.padEnd(maxLength + 1)} ${value}`).join("\n"); -} -var FeeConflictError, InvalidLegacyVError, InvalidSerializableTransactionError, InvalidStorageKeySizeError, TransactionExecutionError; -var init_transaction = __esm({ - "node_modules/viem/_esm/errors/transaction.js"() { - init_formatEther(); - init_formatGwei(); - init_base(); - FeeConflictError = class extends BaseError { - constructor() { - super([ - "Cannot specify both a `gasPrice` and a `maxFeePerGas`/`maxPriorityFeePerGas`.", - "Use `maxFeePerGas`/`maxPriorityFeePerGas` for EIP-1559 compatible networks, and `gasPrice` for others." - ].join("\n"), { name: "FeeConflictError" }); - } - }; - InvalidLegacyVError = class extends BaseError { - constructor({ v }) { - super(`Invalid \`v\` value "${v}". Expected 27 or 28.`, { - name: "InvalidLegacyVError" - }); - } - }; - InvalidSerializableTransactionError = class extends BaseError { - constructor({ transaction }) { - super("Cannot infer a transaction type from provided transaction.", { - metaMessages: [ - "Provided Transaction:", - "{", - prettyPrint(transaction), - "}", - "", - "To infer the type, either provide:", - "- a `type` to the Transaction, or", - "- an EIP-1559 Transaction with `maxFeePerGas`, or", - "- an EIP-2930 Transaction with `gasPrice` & `accessList`, or", - "- an EIP-4844 Transaction with `blobs`, `blobVersionedHashes`, `sidecars`, or", - "- an EIP-7702 Transaction with `authorizationList`, or", - "- a Legacy Transaction with `gasPrice`" - ], - name: "InvalidSerializableTransactionError" - }); - } - }; - InvalidStorageKeySizeError = class extends BaseError { - constructor({ storageKey }) { - super(`Size for storage key "${storageKey}" is invalid. Expected 32 bytes. Got ${Math.floor((storageKey.length - 2) / 2)} bytes.`, { name: "InvalidStorageKeySizeError" }); - } - }; - TransactionExecutionError = class extends BaseError { - constructor(cause, { account: account2, docsPath: docsPath3, chain, data, gas, gasPrice, maxFeePerGas, maxPriorityFeePerGas, nonce, to, value }) { - const prettyArgs = prettyPrint({ - chain: chain && `${chain?.name} (id: ${chain?.id})`, - from: account2?.address, - to, - value: typeof value !== "undefined" && `${formatEther(value)} ${chain?.nativeCurrency?.symbol || "ETH"}`, - data, - gas, - gasPrice: typeof gasPrice !== "undefined" && `${formatGwei(gasPrice)} gwei`, - maxFeePerGas: typeof maxFeePerGas !== "undefined" && `${formatGwei(maxFeePerGas)} gwei`, - maxPriorityFeePerGas: typeof maxPriorityFeePerGas !== "undefined" && `${formatGwei(maxPriorityFeePerGas)} gwei`, - nonce - }); - super(cause.shortMessage, { - cause, - docsPath: docsPath3, - metaMessages: [ - ...cause.metaMessages ? [...cause.metaMessages, " "] : [], - "Request Arguments:", - prettyArgs - ].filter(Boolean), - name: "TransactionExecutionError" - }); - Object.defineProperty(this, "cause", { - enumerable: true, - configurable: true, - writable: true, - value: void 0 - }); - this.cause = cause; - } - }; - } -}); - -// node_modules/viem/_esm/constants/number.js -var maxInt8, maxInt16, maxInt24, maxInt32, maxInt40, maxInt48, maxInt56, maxInt64, maxInt72, maxInt80, maxInt88, maxInt96, maxInt104, maxInt112, maxInt120, maxInt128, maxInt136, maxInt144, maxInt152, maxInt160, maxInt168, maxInt176, maxInt184, maxInt192, maxInt200, maxInt208, maxInt216, maxInt224, maxInt232, maxInt240, maxInt248, maxInt256, minInt8, minInt16, minInt24, minInt32, minInt40, minInt48, minInt56, minInt64, minInt72, minInt80, minInt88, minInt96, minInt104, minInt112, minInt120, minInt128, minInt136, minInt144, minInt152, minInt160, minInt168, minInt176, minInt184, minInt192, minInt200, minInt208, minInt216, minInt224, minInt232, minInt240, minInt248, minInt256, maxUint8, maxUint16, maxUint24, maxUint32, maxUint40, maxUint48, maxUint56, maxUint64, maxUint72, maxUint80, maxUint88, maxUint96, maxUint104, maxUint112, maxUint120, maxUint128, maxUint136, maxUint144, maxUint152, maxUint160, maxUint168, maxUint176, maxUint184, maxUint192, maxUint200, maxUint208, maxUint216, maxUint224, maxUint232, maxUint240, maxUint248, maxUint256; -var init_number = __esm({ - "node_modules/viem/_esm/constants/number.js"() { - maxInt8 = 2n ** (8n - 1n) - 1n; - maxInt16 = 2n ** (16n - 1n) - 1n; - maxInt24 = 2n ** (24n - 1n) - 1n; - maxInt32 = 2n ** (32n - 1n) - 1n; - maxInt40 = 2n ** (40n - 1n) - 1n; - maxInt48 = 2n ** (48n - 1n) - 1n; - maxInt56 = 2n ** (56n - 1n) - 1n; - maxInt64 = 2n ** (64n - 1n) - 1n; - maxInt72 = 2n ** (72n - 1n) - 1n; - maxInt80 = 2n ** (80n - 1n) - 1n; - maxInt88 = 2n ** (88n - 1n) - 1n; - maxInt96 = 2n ** (96n - 1n) - 1n; - maxInt104 = 2n ** (104n - 1n) - 1n; - maxInt112 = 2n ** (112n - 1n) - 1n; - maxInt120 = 2n ** (120n - 1n) - 1n; - maxInt128 = 2n ** (128n - 1n) - 1n; - maxInt136 = 2n ** (136n - 1n) - 1n; - maxInt144 = 2n ** (144n - 1n) - 1n; - maxInt152 = 2n ** (152n - 1n) - 1n; - maxInt160 = 2n ** (160n - 1n) - 1n; - maxInt168 = 2n ** (168n - 1n) - 1n; - maxInt176 = 2n ** (176n - 1n) - 1n; - maxInt184 = 2n ** (184n - 1n) - 1n; - maxInt192 = 2n ** (192n - 1n) - 1n; - maxInt200 = 2n ** (200n - 1n) - 1n; - maxInt208 = 2n ** (208n - 1n) - 1n; - maxInt216 = 2n ** (216n - 1n) - 1n; - maxInt224 = 2n ** (224n - 1n) - 1n; - maxInt232 = 2n ** (232n - 1n) - 1n; - maxInt240 = 2n ** (240n - 1n) - 1n; - maxInt248 = 2n ** (248n - 1n) - 1n; - maxInt256 = 2n ** (256n - 1n) - 1n; - minInt8 = -(2n ** (8n - 1n)); - minInt16 = -(2n ** (16n - 1n)); - minInt24 = -(2n ** (24n - 1n)); - minInt32 = -(2n ** (32n - 1n)); - minInt40 = -(2n ** (40n - 1n)); - minInt48 = -(2n ** (48n - 1n)); - minInt56 = -(2n ** (56n - 1n)); - minInt64 = -(2n ** (64n - 1n)); - minInt72 = -(2n ** (72n - 1n)); - minInt80 = -(2n ** (80n - 1n)); - minInt88 = -(2n ** (88n - 1n)); - minInt96 = -(2n ** (96n - 1n)); - minInt104 = -(2n ** (104n - 1n)); - minInt112 = -(2n ** (112n - 1n)); - minInt120 = -(2n ** (120n - 1n)); - minInt128 = -(2n ** (128n - 1n)); - minInt136 = -(2n ** (136n - 1n)); - minInt144 = -(2n ** (144n - 1n)); - minInt152 = -(2n ** (152n - 1n)); - minInt160 = -(2n ** (160n - 1n)); - minInt168 = -(2n ** (168n - 1n)); - minInt176 = -(2n ** (176n - 1n)); - minInt184 = -(2n ** (184n - 1n)); - minInt192 = -(2n ** (192n - 1n)); - minInt200 = -(2n ** (200n - 1n)); - minInt208 = -(2n ** (208n - 1n)); - minInt216 = -(2n ** (216n - 1n)); - minInt224 = -(2n ** (224n - 1n)); - minInt232 = -(2n ** (232n - 1n)); - minInt240 = -(2n ** (240n - 1n)); - minInt248 = -(2n ** (248n - 1n)); - minInt256 = -(2n ** (256n - 1n)); - maxUint8 = 2n ** 8n - 1n; - maxUint16 = 2n ** 16n - 1n; - maxUint24 = 2n ** 24n - 1n; - maxUint32 = 2n ** 32n - 1n; - maxUint40 = 2n ** 40n - 1n; - maxUint48 = 2n ** 48n - 1n; - maxUint56 = 2n ** 56n - 1n; - maxUint64 = 2n ** 64n - 1n; - maxUint72 = 2n ** 72n - 1n; - maxUint80 = 2n ** 80n - 1n; - maxUint88 = 2n ** 88n - 1n; - maxUint96 = 2n ** 96n - 1n; - maxUint104 = 2n ** 104n - 1n; - maxUint112 = 2n ** 112n - 1n; - maxUint120 = 2n ** 120n - 1n; - maxUint128 = 2n ** 128n - 1n; - maxUint136 = 2n ** 136n - 1n; - maxUint144 = 2n ** 144n - 1n; - maxUint152 = 2n ** 152n - 1n; - maxUint160 = 2n ** 160n - 1n; - maxUint168 = 2n ** 168n - 1n; - maxUint176 = 2n ** 176n - 1n; - maxUint184 = 2n ** 184n - 1n; - maxUint192 = 2n ** 192n - 1n; - maxUint200 = 2n ** 200n - 1n; - maxUint208 = 2n ** 208n - 1n; - maxUint216 = 2n ** 216n - 1n; - maxUint224 = 2n ** 224n - 1n; - maxUint232 = 2n ** 232n - 1n; - maxUint240 = 2n ** 240n - 1n; - maxUint248 = 2n ** 248n - 1n; - maxUint256 = 2n ** 256n - 1n; - } -}); - -// node_modules/viem/_esm/errors/chain.js -var ChainMismatchError, ChainNotFoundError, InvalidChainIdError; -var init_chain = __esm({ - "node_modules/viem/_esm/errors/chain.js"() { - init_base(); - ChainMismatchError = class extends BaseError { - constructor({ chain, currentChainId }) { - super(`The current chain of the wallet (id: ${currentChainId}) does not match the target chain for the transaction (id: ${chain.id} \u2013 ${chain.name}).`, { - metaMessages: [ - `Current Chain ID: ${currentChainId}`, - `Expected Chain ID: ${chain.id} \u2013 ${chain.name}` - ], - name: "ChainMismatchError" - }); - } - }; - ChainNotFoundError = class extends BaseError { - constructor() { - super([ - "No chain was provided to the request.", - "Please provide a chain with the `chain` argument on the Action, or by supplying a `chain` to WalletClient." - ].join("\n"), { - name: "ChainNotFoundError" - }); - } - }; - InvalidChainIdError = class extends BaseError { - constructor({ chainId: chainId2 }) { - super(typeof chainId2 === "number" ? `Chain ID "${chainId2}" is invalid.` : "Chain ID is invalid.", { name: "InvalidChainIdError" }); - } - }; - } -}); - -// node_modules/viem/_esm/errors/node.js -var ExecutionRevertedError, FeeCapTooHighError, FeeCapTooLowError, NonceTooHighError, NonceTooLowError, NonceMaxValueError, InsufficientFundsError, IntrinsicGasTooHighError, IntrinsicGasTooLowError, TransactionTypeNotSupportedError, TipAboveFeeCapError, UnknownNodeError; -var init_node = __esm({ - "node_modules/viem/_esm/errors/node.js"() { - init_formatGwei(); - init_base(); - ExecutionRevertedError = class extends BaseError { - constructor({ cause, message } = {}) { - const reason = message?.replace("execution reverted: ", "")?.replace("execution reverted", ""); - super(`Execution reverted ${reason ? `with reason: ${reason}` : "for an unknown reason"}.`, { - cause, - name: "ExecutionRevertedError" - }); - } - }; - Object.defineProperty(ExecutionRevertedError, "code", { - enumerable: true, - configurable: true, - writable: true, - value: 3 - }); - Object.defineProperty(ExecutionRevertedError, "nodeMessage", { - enumerable: true, - configurable: true, - writable: true, - value: /execution reverted/ - }); - FeeCapTooHighError = class extends BaseError { - constructor({ cause, maxFeePerGas } = {}) { - super(`The fee cap (\`maxFeePerGas\`${maxFeePerGas ? ` = ${formatGwei(maxFeePerGas)} gwei` : ""}) cannot be higher than the maximum allowed value (2^256-1).`, { - cause, - name: "FeeCapTooHighError" - }); - } - }; - Object.defineProperty(FeeCapTooHighError, "nodeMessage", { - enumerable: true, - configurable: true, - writable: true, - value: /max fee per gas higher than 2\^256-1|fee cap higher than 2\^256-1/ - }); - FeeCapTooLowError = class extends BaseError { - constructor({ cause, maxFeePerGas } = {}) { - super(`The fee cap (\`maxFeePerGas\`${maxFeePerGas ? ` = ${formatGwei(maxFeePerGas)}` : ""} gwei) cannot be lower than the block base fee.`, { - cause, - name: "FeeCapTooLowError" - }); - } - }; - Object.defineProperty(FeeCapTooLowError, "nodeMessage", { - enumerable: true, - configurable: true, - writable: true, - value: /max fee per gas less than block base fee|fee cap less than block base fee|transaction is outdated/ - }); - NonceTooHighError = class extends BaseError { - constructor({ cause, nonce } = {}) { - super(`Nonce provided for the transaction ${nonce ? `(${nonce}) ` : ""}is higher than the next one expected.`, { cause, name: "NonceTooHighError" }); - } - }; - Object.defineProperty(NonceTooHighError, "nodeMessage", { - enumerable: true, - configurable: true, - writable: true, - value: /nonce too high/ - }); - NonceTooLowError = class extends BaseError { - constructor({ cause, nonce } = {}) { - super([ - `Nonce provided for the transaction ${nonce ? `(${nonce}) ` : ""}is lower than the current nonce of the account.`, - "Try increasing the nonce or find the latest nonce with `getTransactionCount`." - ].join("\n"), { cause, name: "NonceTooLowError" }); - } - }; - Object.defineProperty(NonceTooLowError, "nodeMessage", { - enumerable: true, - configurable: true, - writable: true, - value: /nonce too low|transaction already imported|already known/ - }); - NonceMaxValueError = class extends BaseError { - constructor({ cause, nonce } = {}) { - super(`Nonce provided for the transaction ${nonce ? `(${nonce}) ` : ""}exceeds the maximum allowed nonce.`, { cause, name: "NonceMaxValueError" }); - } - }; - Object.defineProperty(NonceMaxValueError, "nodeMessage", { - enumerable: true, - configurable: true, - writable: true, - value: /nonce has max value/ - }); - InsufficientFundsError = class extends BaseError { - constructor({ cause } = {}) { - super([ - "The total cost (gas * gas fee + value) of executing this transaction exceeds the balance of the account." - ].join("\n"), { - cause, - metaMessages: [ - "This error could arise when the account does not have enough funds to:", - " - pay for the total gas fee,", - " - pay for the value to send.", - " ", - "The cost of the transaction is calculated as `gas * gas fee + value`, where:", - " - `gas` is the amount of gas needed for transaction to execute,", - " - `gas fee` is the gas fee,", - " - `value` is the amount of ether to send to the recipient." - ], - name: "InsufficientFundsError" - }); - } - }; - Object.defineProperty(InsufficientFundsError, "nodeMessage", { - enumerable: true, - configurable: true, - writable: true, - value: /insufficient funds|exceeds transaction sender account balance/ - }); - IntrinsicGasTooHighError = class extends BaseError { - constructor({ cause, gas } = {}) { - super(`The amount of gas ${gas ? `(${gas}) ` : ""}provided for the transaction exceeds the limit allowed for the block.`, { - cause, - name: "IntrinsicGasTooHighError" - }); - } - }; - Object.defineProperty(IntrinsicGasTooHighError, "nodeMessage", { - enumerable: true, - configurable: true, - writable: true, - value: /intrinsic gas too high|gas limit reached/ - }); - IntrinsicGasTooLowError = class extends BaseError { - constructor({ cause, gas } = {}) { - super(`The amount of gas ${gas ? `(${gas}) ` : ""}provided for the transaction is too low.`, { - cause, - name: "IntrinsicGasTooLowError" - }); - } - }; - Object.defineProperty(IntrinsicGasTooLowError, "nodeMessage", { - enumerable: true, - configurable: true, - writable: true, - value: /intrinsic gas too low/ - }); - TransactionTypeNotSupportedError = class extends BaseError { - constructor({ cause }) { - super("The transaction type is not supported for this chain.", { - cause, - name: "TransactionTypeNotSupportedError" - }); - } - }; - Object.defineProperty(TransactionTypeNotSupportedError, "nodeMessage", { - enumerable: true, - configurable: true, - writable: true, - value: /transaction type not valid/ - }); - TipAboveFeeCapError = class extends BaseError { - constructor({ cause, maxPriorityFeePerGas, maxFeePerGas } = {}) { - super([ - `The provided tip (\`maxPriorityFeePerGas\`${maxPriorityFeePerGas ? ` = ${formatGwei(maxPriorityFeePerGas)} gwei` : ""}) cannot be higher than the fee cap (\`maxFeePerGas\`${maxFeePerGas ? ` = ${formatGwei(maxFeePerGas)} gwei` : ""}).` - ].join("\n"), { - cause, - name: "TipAboveFeeCapError" - }); - } - }; - Object.defineProperty(TipAboveFeeCapError, "nodeMessage", { - enumerable: true, - configurable: true, - writable: true, - value: /max priority fee per gas higher than max fee per gas|tip higher than fee cap/ - }); - UnknownNodeError = class extends BaseError { - constructor({ cause }) { - super(`An error occurred while executing: ${cause?.shortMessage}`, { - cause, - name: "UnknownNodeError" - }); - } - }; - } -}); - -// node_modules/viem/_esm/utils/data/slice.js -function slice(value, start, end, { strict } = {}) { - if (isHex(value, { strict: false })) - return sliceHex(value, start, end, { - strict - }); - return sliceBytes(value, start, end, { - strict - }); -} -function assertStartOffset(value, start) { - if (typeof start === "number" && start > 0 && start > size(value) - 1) - throw new SliceOffsetOutOfBoundsError({ - offset: start, - position: "start", - size: size(value) - }); -} -function assertEndOffset(value, start, end) { - if (typeof start === "number" && typeof end === "number" && size(value) !== end - start) { - throw new SliceOffsetOutOfBoundsError({ - offset: end, - position: "end", - size: size(value) - }); - } -} -function sliceBytes(value_, start, end, { strict } = {}) { - assertStartOffset(value_, start); - const value = value_.slice(start, end); - if (strict) - assertEndOffset(value, start, end); - return value; -} -function sliceHex(value_, start, end, { strict } = {}) { - assertStartOffset(value_, start); - const value = `0x${value_.replace("0x", "").slice((start ?? 0) * 2, (end ?? value_.length) * 2)}`; - if (strict) - assertEndOffset(value, start, end); - return value; -} -var init_slice = __esm({ - "node_modules/viem/_esm/utils/data/slice.js"() { - init_data(); - init_isHex(); - init_size(); - } -}); - -// node_modules/viem/_esm/utils/abi/formatAbiItem.js -function formatAbiItem(abiItem, { includeName = false } = {}) { - if (abiItem.type !== "function" && abiItem.type !== "event" && abiItem.type !== "error") - throw new InvalidDefinitionTypeError(abiItem.type); - return `${abiItem.name}(${formatAbiParams(abiItem.inputs, { includeName })})`; -} -function formatAbiParams(params, { includeName = false } = {}) { - if (!params) - return ""; - return params.map((param) => formatAbiParam(param, { includeName })).join(includeName ? ", " : ","); -} -function formatAbiParam(param, { includeName }) { - if (param.type.startsWith("tuple")) { - return `(${formatAbiParams(param.components, { includeName })})${param.type.slice("tuple".length)}`; - } - return param.type + (includeName && param.name ? ` ${param.name}` : ""); -} -var init_formatAbiItem = __esm({ - "node_modules/viem/_esm/utils/abi/formatAbiItem.js"() { - init_abi(); - } -}); - -// node_modules/viem/_esm/errors/abi.js -var AbiConstructorNotFoundError, AbiConstructorParamsNotFoundError, AbiDecodingDataSizeTooSmallError, AbiDecodingZeroDataError, AbiEncodingArrayLengthMismatchError, AbiEncodingBytesSizeMismatchError, AbiEncodingLengthMismatchError, AbiErrorSignatureNotFoundError, AbiFunctionNotFoundError, AbiItemAmbiguityError, BytesSizeMismatchError, InvalidAbiEncodingTypeError, InvalidAbiDecodingTypeError, InvalidArrayError, InvalidDefinitionTypeError; -var init_abi = __esm({ - "node_modules/viem/_esm/errors/abi.js"() { - init_formatAbiItem(); - init_size(); - init_base(); - AbiConstructorNotFoundError = class extends BaseError { - constructor({ docsPath: docsPath3 }) { - super([ - "A constructor was not found on the ABI.", - "Make sure you are using the correct ABI and that the constructor exists on it." - ].join("\n"), { - docsPath: docsPath3, - name: "AbiConstructorNotFoundError" - }); - } - }; - AbiConstructorParamsNotFoundError = class extends BaseError { - constructor({ docsPath: docsPath3 }) { - super([ - "Constructor arguments were provided (`args`), but a constructor parameters (`inputs`) were not found on the ABI.", - "Make sure you are using the correct ABI, and that the `inputs` attribute on the constructor exists." - ].join("\n"), { - docsPath: docsPath3, - name: "AbiConstructorParamsNotFoundError" - }); - } - }; - AbiDecodingDataSizeTooSmallError = class extends BaseError { - constructor({ data, params, size: size3 }) { - super([`Data size of ${size3} bytes is too small for given parameters.`].join("\n"), { - metaMessages: [ - `Params: (${formatAbiParams(params, { includeName: true })})`, - `Data: ${data} (${size3} bytes)` - ], - name: "AbiDecodingDataSizeTooSmallError" - }); - Object.defineProperty(this, "data", { - enumerable: true, - configurable: true, - writable: true, - value: void 0 - }); - Object.defineProperty(this, "params", { - enumerable: true, - configurable: true, - writable: true, - value: void 0 - }); - Object.defineProperty(this, "size", { - enumerable: true, - configurable: true, - writable: true, - value: void 0 - }); - this.data = data; - this.params = params; - this.size = size3; - } - }; - AbiDecodingZeroDataError = class extends BaseError { - constructor() { - super('Cannot decode zero data ("0x") with ABI parameters.', { - name: "AbiDecodingZeroDataError" - }); - } - }; - AbiEncodingArrayLengthMismatchError = class extends BaseError { - constructor({ expectedLength, givenLength, type }) { - super([ - `ABI encoding array length mismatch for type ${type}.`, - `Expected length: ${expectedLength}`, - `Given length: ${givenLength}` - ].join("\n"), { name: "AbiEncodingArrayLengthMismatchError" }); - } - }; - AbiEncodingBytesSizeMismatchError = class extends BaseError { - constructor({ expectedSize, value }) { - super(`Size of bytes "${value}" (bytes${size(value)}) does not match expected size (bytes${expectedSize}).`, { name: "AbiEncodingBytesSizeMismatchError" }); - } - }; - AbiEncodingLengthMismatchError = class extends BaseError { - constructor({ expectedLength, givenLength }) { - super([ - "ABI encoding params/values length mismatch.", - `Expected length (params): ${expectedLength}`, - `Given length (values): ${givenLength}` - ].join("\n"), { name: "AbiEncodingLengthMismatchError" }); - } - }; - AbiErrorSignatureNotFoundError = class extends BaseError { - constructor(signature, { docsPath: docsPath3 }) { - super([ - `Encoded error signature "${signature}" not found on ABI.`, - "Make sure you are using the correct ABI and that the error exists on it.", - `You can look up the decoded signature here: https://openchain.xyz/signatures?query=${signature}.` - ].join("\n"), { - docsPath: docsPath3, - name: "AbiErrorSignatureNotFoundError" - }); - Object.defineProperty(this, "signature", { - enumerable: true, - configurable: true, - writable: true, - value: void 0 - }); - this.signature = signature; - } - }; - AbiFunctionNotFoundError = class extends BaseError { - constructor(functionName, { docsPath: docsPath3 } = {}) { - super([ - `Function ${functionName ? `"${functionName}" ` : ""}not found on ABI.`, - "Make sure you are using the correct ABI and that the function exists on it." - ].join("\n"), { - docsPath: docsPath3, - name: "AbiFunctionNotFoundError" - }); - } - }; - AbiItemAmbiguityError = class extends BaseError { - constructor(x, y) { - super("Found ambiguous types in overloaded ABI items.", { - metaMessages: [ - `\`${x.type}\` in \`${formatAbiItem(x.abiItem)}\`, and`, - `\`${y.type}\` in \`${formatAbiItem(y.abiItem)}\``, - "", - "These types encode differently and cannot be distinguished at runtime.", - "Remove one of the ambiguous items in the ABI." - ], - name: "AbiItemAmbiguityError" - }); - } - }; - BytesSizeMismatchError = class extends BaseError { - constructor({ expectedSize, givenSize }) { - super(`Expected bytes${expectedSize}, got bytes${givenSize}.`, { - name: "BytesSizeMismatchError" - }); - } - }; - InvalidAbiEncodingTypeError = class extends BaseError { - constructor(type, { docsPath: docsPath3 }) { - super([ - `Type "${type}" is not a valid encoding type.`, - "Please provide a valid ABI type." - ].join("\n"), { docsPath: docsPath3, name: "InvalidAbiEncodingType" }); - } - }; - InvalidAbiDecodingTypeError = class extends BaseError { - constructor(type, { docsPath: docsPath3 }) { - super([ - `Type "${type}" is not a valid decoding type.`, - "Please provide a valid ABI type." - ].join("\n"), { docsPath: docsPath3, name: "InvalidAbiDecodingType" }); - } - }; - InvalidArrayError = class extends BaseError { - constructor(value) { - super([`Value "${value}" is not a valid array.`].join("\n"), { - name: "InvalidArrayError" - }); - } - }; - InvalidDefinitionTypeError = class extends BaseError { - constructor(type) { - super([ - `"${type}" is not a valid definition type.`, - 'Valid types: "function", "event", "error"' - ].join("\n"), { name: "InvalidDefinitionTypeError" }); - } - }; - } -}); - -// node_modules/viem/_esm/utils/regex.js -var bytesRegex, integerRegex; -var init_regex = __esm({ - "node_modules/viem/_esm/utils/regex.js"() { - bytesRegex = /^bytes([1-9]|1[0-9]|2[0-9]|3[0-2])?$/; - integerRegex = /^(u?int)(8|16|24|32|40|48|56|64|72|80|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256)?$/; - } -}); - -// node_modules/viem/_esm/utils/abi/encodeAbiParameters.js -function encodeAbiParameters(params, values) { - if (params.length !== values.length) - throw new AbiEncodingLengthMismatchError({ - expectedLength: params.length, - givenLength: values.length - }); - const preparedParams = prepareParams({ - params, - values - }); - const data = encodeParams(preparedParams); - if (data.length === 0) - return "0x"; - return data; -} -function prepareParams({ params, values }) { - const preparedParams = []; - for (let i = 0; i < params.length; i++) { - preparedParams.push(prepareParam({ param: params[i], value: values[i] })); - } - return preparedParams; -} -function prepareParam({ param, value }) { - const arrayComponents = getArrayComponents(param.type); - if (arrayComponents) { - const [length, type] = arrayComponents; - return encodeArray(value, { length, param: { ...param, type } }); - } - if (param.type === "tuple") { - return encodeTuple(value, { - param - }); - } - if (param.type === "address") { - return encodeAddress(value); - } - if (param.type === "bool") { - return encodeBool(value); - } - if (param.type.startsWith("uint") || param.type.startsWith("int")) { - const signed = param.type.startsWith("int"); - const [, , size3 = "256"] = integerRegex.exec(param.type) ?? []; - return encodeNumber(value, { - signed, - size: Number(size3) - }); - } - if (param.type.startsWith("bytes")) { - return encodeBytes(value, { param }); - } - if (param.type === "string") { - return encodeString(value); - } - throw new InvalidAbiEncodingTypeError(param.type, { - docsPath: "/docs/contract/encodeAbiParameters" - }); -} -function encodeParams(preparedParams) { - let staticSize = 0; - for (let i = 0; i < preparedParams.length; i++) { - const { dynamic, encoded } = preparedParams[i]; - if (dynamic) - staticSize += 32; - else - staticSize += size(encoded); - } - const staticParams = []; - const dynamicParams = []; - let dynamicSize = 0; - for (let i = 0; i < preparedParams.length; i++) { - const { dynamic, encoded } = preparedParams[i]; - if (dynamic) { - staticParams.push(numberToHex(staticSize + dynamicSize, { size: 32 })); - dynamicParams.push(encoded); - dynamicSize += size(encoded); - } else { - staticParams.push(encoded); - } - } - return concat([...staticParams, ...dynamicParams]); -} -function encodeAddress(value) { - if (!isAddress(value)) - throw new InvalidAddressError({ address: value }); - return { dynamic: false, encoded: padHex(value.toLowerCase()) }; -} -function encodeArray(value, { length, param }) { - const dynamic = length === null; - if (!Array.isArray(value)) - throw new InvalidArrayError(value); - if (!dynamic && value.length !== length) - throw new AbiEncodingArrayLengthMismatchError({ - expectedLength: length, - givenLength: value.length, - type: `${param.type}[${length}]` - }); - let dynamicChild = false; - const preparedParams = []; - for (let i = 0; i < value.length; i++) { - const preparedParam = prepareParam({ param, value: value[i] }); - if (preparedParam.dynamic) - dynamicChild = true; - preparedParams.push(preparedParam); - } - if (dynamic || dynamicChild) { - const data = encodeParams(preparedParams); - if (dynamic) { - const length2 = numberToHex(preparedParams.length, { size: 32 }); - return { - dynamic: true, - encoded: preparedParams.length > 0 ? concat([length2, data]) : length2 - }; - } - if (dynamicChild) - return { dynamic: true, encoded: data }; - } - return { - dynamic: false, - encoded: concat(preparedParams.map(({ encoded }) => encoded)) - }; -} -function encodeBytes(value, { param }) { - const [, paramSize] = param.type.split("bytes"); - const bytesSize = size(value); - if (!paramSize) { - let value_ = value; - if (bytesSize % 32 !== 0) - value_ = padHex(value_, { - dir: "right", - size: Math.ceil((value.length - 2) / 2 / 32) * 32 - }); - return { - dynamic: true, - encoded: concat([padHex(numberToHex(bytesSize, { size: 32 })), value_]) - }; - } - if (bytesSize !== Number.parseInt(paramSize)) - throw new AbiEncodingBytesSizeMismatchError({ - expectedSize: Number.parseInt(paramSize), - value - }); - return { dynamic: false, encoded: padHex(value, { dir: "right" }) }; -} -function encodeBool(value) { - if (typeof value !== "boolean") - throw new BaseError(`Invalid boolean value: "${value}" (type: ${typeof value}). Expected: \`true\` or \`false\`.`); - return { dynamic: false, encoded: padHex(boolToHex(value)) }; -} -function encodeNumber(value, { signed, size: size3 = 256 }) { - if (typeof size3 === "number") { - const max = 2n ** (BigInt(size3) - (signed ? 1n : 0n)) - 1n; - const min = signed ? -max - 1n : 0n; - if (value > max || value < min) - throw new IntegerOutOfRangeError({ - max: max.toString(), - min: min.toString(), - signed, - size: size3 / 8, - value: value.toString() - }); - } - return { - dynamic: false, - encoded: numberToHex(value, { - size: 32, - signed - }) - }; -} -function encodeString(value) { - const hexValue = stringToHex(value); - const partsLength = Math.ceil(size(hexValue) / 32); - const parts = []; - for (let i = 0; i < partsLength; i++) { - parts.push(padHex(slice(hexValue, i * 32, (i + 1) * 32), { - dir: "right" - })); - } - return { - dynamic: true, - encoded: concat([ - padHex(numberToHex(size(hexValue), { size: 32 })), - ...parts - ]) - }; -} -function encodeTuple(value, { param }) { - let dynamic = false; - const preparedParams = []; - for (let i = 0; i < param.components.length; i++) { - const param_ = param.components[i]; - const index2 = Array.isArray(value) ? i : param_.name; - const preparedParam = prepareParam({ - param: param_, - value: value[index2] - }); - preparedParams.push(preparedParam); - if (preparedParam.dynamic) - dynamic = true; - } - return { - dynamic, - encoded: dynamic ? encodeParams(preparedParams) : concat(preparedParams.map(({ encoded }) => encoded)) - }; -} -function getArrayComponents(type) { - const matches = type.match(/^(.*)\[(\d+)?\]$/); - return matches ? ( - // Return `null` if the array is dynamic. - [matches[2] ? Number(matches[2]) : null, matches[1]] - ) : void 0; -} -var init_encodeAbiParameters = __esm({ - "node_modules/viem/_esm/utils/abi/encodeAbiParameters.js"() { - init_abi(); - init_address(); - init_base(); - init_encoding(); - init_isAddress(); - init_concat(); - init_pad(); - init_size(); - init_slice(); - init_toHex(); - init_regex(); - } -}); - -// node_modules/viem/_esm/utils/stringify.js -var stringify; -var init_stringify = __esm({ - "node_modules/viem/_esm/utils/stringify.js"() { - stringify = (value, replacer, space) => JSON.stringify(value, (key, value_) => { - const value2 = typeof value_ === "bigint" ? value_.toString() : value_; - return typeof replacer === "function" ? replacer(key, value2) : value2; - }, space); - } -}); - -// node_modules/viem/_esm/accounts/utils/parseAccount.js -function parseAccount(account2) { - if (typeof account2 === "string") - return { address: account2, type: "json-rpc" }; - return account2; -} -var init_parseAccount = __esm({ - "node_modules/viem/_esm/accounts/utils/parseAccount.js"() { - } -}); - -// node_modules/abitype/dist/esm/regex.js -function execTyped(regex, string) { - const match = regex.exec(string); - return match?.groups; -} -var init_regex2 = __esm({ - "node_modules/abitype/dist/esm/regex.js"() { - } -}); - -// node_modules/abitype/dist/esm/human-readable/formatAbiParameter.js -function formatAbiParameter(abiParameter) { - let type = abiParameter.type; - if (tupleRegex.test(abiParameter.type) && "components" in abiParameter) { - type = "("; - const length = abiParameter.components.length; - for (let i = 0; i < length; i++) { - const component = abiParameter.components[i]; - type += formatAbiParameter(component); - if (i < length - 1) - type += ", "; - } - const result = execTyped(tupleRegex, abiParameter.type); - type += `)${result?.array ?? ""}`; - return formatAbiParameter({ - ...abiParameter, - type - }); - } - if ("indexed" in abiParameter && abiParameter.indexed) - type = `${type} indexed`; - if (abiParameter.name) - return `${type} ${abiParameter.name}`; - return type; -} -var tupleRegex; -var init_formatAbiParameter = __esm({ - "node_modules/abitype/dist/esm/human-readable/formatAbiParameter.js"() { - init_regex2(); - tupleRegex = /^tuple(?(\[(\d*)\])*)$/; - } -}); - -// node_modules/abitype/dist/esm/human-readable/formatAbiParameters.js -function formatAbiParameters(abiParameters) { - let params = ""; - const length = abiParameters.length; - for (let i = 0; i < length; i++) { - const abiParameter = abiParameters[i]; - params += formatAbiParameter(abiParameter); - if (i !== length - 1) - params += ", "; - } - return params; -} -var init_formatAbiParameters = __esm({ - "node_modules/abitype/dist/esm/human-readable/formatAbiParameters.js"() { - init_formatAbiParameter(); - } -}); - -// node_modules/abitype/dist/esm/human-readable/formatAbiItem.js -function formatAbiItem2(abiItem) { - if (abiItem.type === "function") - return `function ${abiItem.name}(${formatAbiParameters(abiItem.inputs)})${abiItem.stateMutability && abiItem.stateMutability !== "nonpayable" ? ` ${abiItem.stateMutability}` : ""}${abiItem.outputs?.length ? ` returns (${formatAbiParameters(abiItem.outputs)})` : ""}`; - if (abiItem.type === "event") - return `event ${abiItem.name}(${formatAbiParameters(abiItem.inputs)})`; - if (abiItem.type === "error") - return `error ${abiItem.name}(${formatAbiParameters(abiItem.inputs)})`; - if (abiItem.type === "constructor") - return `constructor(${formatAbiParameters(abiItem.inputs)})${abiItem.stateMutability === "payable" ? " payable" : ""}`; - if (abiItem.type === "fallback") - return `fallback() external${abiItem.stateMutability === "payable" ? " payable" : ""}`; - return "receive() external payable"; -} -var init_formatAbiItem2 = __esm({ - "node_modules/abitype/dist/esm/human-readable/formatAbiItem.js"() { - init_formatAbiParameters(); - } -}); - -// node_modules/abitype/dist/esm/exports/index.js -var init_exports = __esm({ - "node_modules/abitype/dist/esm/exports/index.js"() { - init_formatAbiItem2(); - } -}); - -// node_modules/viem/_esm/utils/hash/hashSignature.js -function hashSignature(sig) { - return hash(sig); -} -var hash; -var init_hashSignature = __esm({ - "node_modules/viem/_esm/utils/hash/hashSignature.js"() { - init_toBytes(); - init_keccak256(); - hash = (value) => keccak256(toBytes2(value)); - } -}); - -// node_modules/viem/_esm/utils/hash/normalizeSignature.js -function normalizeSignature(signature) { - let active = true; - let current = ""; - let level = 0; - let result = ""; - let valid = false; - for (let i = 0; i < signature.length; i++) { - const char = signature[i]; - if (["(", ")", ","].includes(char)) - active = true; - if (char === "(") - level++; - if (char === ")") - level--; - if (!active) - continue; - if (level === 0) { - if (char === " " && ["event", "function", ""].includes(result)) - result = ""; - else { - result += char; - if (char === ")") { - valid = true; - break; - } - } - continue; - } - if (char === " ") { - if (signature[i - 1] !== "," && current !== "," && current !== ",(") { - current = ""; - active = false; - } - continue; - } - result += char; - current += char; - } - if (!valid) - throw new BaseError("Unable to normalize signature."); - return result; -} -var init_normalizeSignature = __esm({ - "node_modules/viem/_esm/utils/hash/normalizeSignature.js"() { - init_base(); - } -}); - -// node_modules/viem/_esm/utils/hash/toSignature.js -var toSignature; -var init_toSignature = __esm({ - "node_modules/viem/_esm/utils/hash/toSignature.js"() { - init_exports(); - init_normalizeSignature(); - toSignature = (def) => { - const def_ = (() => { - if (typeof def === "string") - return def; - return formatAbiItem2(def); - })(); - return normalizeSignature(def_); - }; - } -}); - -// node_modules/viem/_esm/utils/hash/toSignatureHash.js -function toSignatureHash(fn) { - return hashSignature(toSignature(fn)); -} -var init_toSignatureHash = __esm({ - "node_modules/viem/_esm/utils/hash/toSignatureHash.js"() { - init_hashSignature(); - init_toSignature(); - } -}); - -// node_modules/viem/_esm/utils/hash/toEventSelector.js -var toEventSelector; -var init_toEventSelector = __esm({ - "node_modules/viem/_esm/utils/hash/toEventSelector.js"() { - init_toSignatureHash(); - toEventSelector = toSignatureHash; - } -}); - -// node_modules/viem/_esm/utils/hash/toFunctionSelector.js -var toFunctionSelector; -var init_toFunctionSelector = __esm({ - "node_modules/viem/_esm/utils/hash/toFunctionSelector.js"() { - init_slice(); - init_toSignatureHash(); - toFunctionSelector = (fn) => slice(toSignatureHash(fn), 0, 4); - } -}); - -// node_modules/viem/_esm/utils/abi/getAbiItem.js -function getAbiItem(parameters) { - const { abi, args: args2 = [], name } = parameters; - const isSelector = isHex(name, { strict: false }); - const abiItems = abi.filter((abiItem) => { - if (isSelector) { - if (abiItem.type === "function") - return toFunctionSelector(abiItem) === name; - if (abiItem.type === "event") - return toEventSelector(abiItem) === name; - return false; - } - return "name" in abiItem && abiItem.name === name; - }); - if (abiItems.length === 0) - return void 0; - if (abiItems.length === 1) - return abiItems[0]; - let matchedAbiItem = void 0; - for (const abiItem of abiItems) { - if (!("inputs" in abiItem)) - continue; - if (!args2 || args2.length === 0) { - if (!abiItem.inputs || abiItem.inputs.length === 0) - return abiItem; - continue; - } - if (!abiItem.inputs) - continue; - if (abiItem.inputs.length === 0) - continue; - if (abiItem.inputs.length !== args2.length) - continue; - const matched = args2.every((arg, index2) => { - const abiParameter = "inputs" in abiItem && abiItem.inputs[index2]; - if (!abiParameter) - return false; - return isArgOfType(arg, abiParameter); - }); - if (matched) { - if (matchedAbiItem && "inputs" in matchedAbiItem && matchedAbiItem.inputs) { - const ambiguousTypes = getAmbiguousTypes(abiItem.inputs, matchedAbiItem.inputs, args2); - if (ambiguousTypes) - throw new AbiItemAmbiguityError({ - abiItem, - type: ambiguousTypes[0] - }, { - abiItem: matchedAbiItem, - type: ambiguousTypes[1] - }); - } - matchedAbiItem = abiItem; - } - } - if (matchedAbiItem) - return matchedAbiItem; - return abiItems[0]; -} -function isArgOfType(arg, abiParameter) { - const argType = typeof arg; - const abiParameterType = abiParameter.type; - switch (abiParameterType) { - case "address": - return isAddress(arg, { strict: false }); - case "bool": - return argType === "boolean"; - case "function": - return argType === "string"; - case "string": - return argType === "string"; - default: { - if (abiParameterType === "tuple" && "components" in abiParameter) - return Object.values(abiParameter.components).every((component, index2) => { - return isArgOfType(Object.values(arg)[index2], component); - }); - if (/^u?int(8|16|24|32|40|48|56|64|72|80|88|96|104|112|120|128|136|144|152|160|168|176|184|192|200|208|216|224|232|240|248|256)?$/.test(abiParameterType)) - return argType === "number" || argType === "bigint"; - if (/^bytes([1-9]|1[0-9]|2[0-9]|3[0-2])?$/.test(abiParameterType)) - return argType === "string" || arg instanceof Uint8Array; - if (/[a-z]+[1-9]{0,3}(\[[0-9]{0,}\])+$/.test(abiParameterType)) { - return Array.isArray(arg) && arg.every((x) => isArgOfType(x, { - ...abiParameter, - // Pop off `[]` or `[M]` from end of type - type: abiParameterType.replace(/(\[[0-9]{0,}\])$/, "") - })); - } - return false; - } - } -} -function getAmbiguousTypes(sourceParameters, targetParameters, args2) { - for (const parameterIndex in sourceParameters) { - const sourceParameter = sourceParameters[parameterIndex]; - const targetParameter = targetParameters[parameterIndex]; - if (sourceParameter.type === "tuple" && targetParameter.type === "tuple" && "components" in sourceParameter && "components" in targetParameter) - return getAmbiguousTypes(sourceParameter.components, targetParameter.components, args2[parameterIndex]); - const types = [sourceParameter.type, targetParameter.type]; - const ambiguous = (() => { - if (types.includes("address") && types.includes("bytes20")) - return true; - if (types.includes("address") && types.includes("string")) - return isAddress(args2[parameterIndex], { strict: false }); - if (types.includes("address") && types.includes("bytes")) - return isAddress(args2[parameterIndex], { strict: false }); - return false; - })(); - if (ambiguous) - return types; - } - return; -} -var init_getAbiItem = __esm({ - "node_modules/viem/_esm/utils/abi/getAbiItem.js"() { - init_abi(); - init_isHex(); - init_isAddress(); - init_toEventSelector(); - init_toFunctionSelector(); - } -}); - -// node_modules/viem/_esm/utils/abi/prepareEncodeFunctionData.js -function prepareEncodeFunctionData(parameters) { - const { abi, args: args2, functionName } = parameters; - let abiItem = abi[0]; - if (functionName) { - const item = getAbiItem({ - abi, - args: args2, - name: functionName - }); - if (!item) - throw new AbiFunctionNotFoundError(functionName, { docsPath }); - abiItem = item; - } - if (abiItem.type !== "function") - throw new AbiFunctionNotFoundError(void 0, { docsPath }); - return { - abi: [abiItem], - functionName: toFunctionSelector(formatAbiItem(abiItem)) - }; -} -var docsPath; -var init_prepareEncodeFunctionData = __esm({ - "node_modules/viem/_esm/utils/abi/prepareEncodeFunctionData.js"() { - init_abi(); - init_toFunctionSelector(); - init_formatAbiItem(); - init_getAbiItem(); - docsPath = "/docs/contract/encodeFunctionData"; - } -}); - -// node_modules/viem/_esm/utils/abi/encodeFunctionData.js -function encodeFunctionData(parameters) { - const { args: args2 } = parameters; - const { abi, functionName } = (() => { - if (parameters.abi.length === 1 && parameters.functionName?.startsWith("0x")) - return parameters; - return prepareEncodeFunctionData(parameters); - })(); - const abiItem = abi[0]; - const signature = functionName; - const data = "inputs" in abiItem && abiItem.inputs ? encodeAbiParameters(abiItem.inputs, args2 ?? []) : void 0; - return concatHex([signature, data ?? "0x"]); -} -var init_encodeFunctionData = __esm({ - "node_modules/viem/_esm/utils/abi/encodeFunctionData.js"() { - init_concat(); - init_encodeAbiParameters(); - init_prepareEncodeFunctionData(); - } -}); - -// node_modules/viem/_esm/constants/solidity.js -var panicReasons, solidityError, solidityPanic; -var init_solidity = __esm({ - "node_modules/viem/_esm/constants/solidity.js"() { - panicReasons = { - 1: "An `assert` condition failed.", - 17: "Arithmetic operation resulted in underflow or overflow.", - 18: "Division or modulo by zero (e.g. `5 / 0` or `23 % 0`).", - 33: "Attempted to convert to an invalid type.", - 34: "Attempted to access a storage byte array that is incorrectly encoded.", - 49: "Performed `.pop()` on an empty array", - 50: "Array index is out of bounds.", - 65: "Allocated too much memory or created an array which is too large.", - 81: "Attempted to call a zero-initialized variable of internal function type." - }; - solidityError = { - inputs: [ - { - name: "message", - type: "string" - } - ], - name: "Error", - type: "error" - }; - solidityPanic = { - inputs: [ - { - name: "reason", - type: "uint256" - } - ], - name: "Panic", - type: "error" - }; - } -}); - -// node_modules/viem/_esm/utils/encoding/fromBytes.js -function bytesToBigInt(bytes, opts = {}) { - if (typeof opts.size !== "undefined") - assertSize(bytes, { size: opts.size }); - const hex = bytesToHex2(bytes, opts); - return hexToBigInt(hex, opts); -} -function bytesToBool(bytes_, opts = {}) { - let bytes = bytes_; - if (typeof opts.size !== "undefined") { - assertSize(bytes, { size: opts.size }); - bytes = trim(bytes); - } - if (bytes.length > 1 || bytes[0] > 1) - throw new InvalidBytesBooleanError(bytes); - return Boolean(bytes[0]); -} -function bytesToNumber(bytes, opts = {}) { - if (typeof opts.size !== "undefined") - assertSize(bytes, { size: opts.size }); - const hex = bytesToHex2(bytes, opts); - return hexToNumber2(hex, opts); -} -function bytesToString(bytes_, opts = {}) { - let bytes = bytes_; - if (typeof opts.size !== "undefined") { - assertSize(bytes, { size: opts.size }); - bytes = trim(bytes, { dir: "right" }); - } - return new TextDecoder().decode(bytes); -} -var init_fromBytes = __esm({ - "node_modules/viem/_esm/utils/encoding/fromBytes.js"() { - init_encoding(); - init_trim(); - init_fromHex(); - init_toHex(); - } -}); - -// node_modules/viem/_esm/utils/abi/decodeAbiParameters.js -function decodeAbiParameters(params, data) { - const bytes = typeof data === "string" ? hexToBytes2(data) : data; - const cursor = createCursor(bytes); - if (size(bytes) === 0 && params.length > 0) - throw new AbiDecodingZeroDataError(); - if (size(data) && size(data) < 32) - throw new AbiDecodingDataSizeTooSmallError({ - data: typeof data === "string" ? data : bytesToHex2(data), - params, - size: size(data) - }); - let consumed = 0; - const values = []; - for (let i = 0; i < params.length; ++i) { - const param = params[i]; - cursor.setPosition(consumed); - const [data2, consumed_] = decodeParameter(cursor, param, { - staticPosition: 0 - }); - consumed += consumed_; - values.push(data2); - } - return values; -} -function decodeParameter(cursor, param, { staticPosition }) { - const arrayComponents = getArrayComponents(param.type); - if (arrayComponents) { - const [length, type] = arrayComponents; - return decodeArray(cursor, { ...param, type }, { length, staticPosition }); - } - if (param.type === "tuple") - return decodeTuple(cursor, param, { staticPosition }); - if (param.type === "address") - return decodeAddress(cursor); - if (param.type === "bool") - return decodeBool(cursor); - if (param.type.startsWith("bytes")) - return decodeBytes(cursor, param, { staticPosition }); - if (param.type.startsWith("uint") || param.type.startsWith("int")) - return decodeNumber(cursor, param); - if (param.type === "string") - return decodeString(cursor, { staticPosition }); - throw new InvalidAbiDecodingTypeError(param.type, { - docsPath: "/docs/contract/decodeAbiParameters" - }); -} -function decodeAddress(cursor) { - const value = cursor.readBytes(32); - return [checksumAddress(bytesToHex2(sliceBytes(value, -20))), 32]; -} -function decodeArray(cursor, param, { length, staticPosition }) { - if (!length) { - const offset = bytesToNumber(cursor.readBytes(sizeOfOffset)); - const start = staticPosition + offset; - const startOfData = start + sizeOfLength; - cursor.setPosition(start); - const length2 = bytesToNumber(cursor.readBytes(sizeOfLength)); - const dynamicChild = hasDynamicChild(param); - let consumed2 = 0; - const value2 = []; - for (let i = 0; i < length2; ++i) { - cursor.setPosition(startOfData + (dynamicChild ? i * 32 : consumed2)); - const [data, consumed_] = decodeParameter(cursor, param, { - staticPosition: startOfData - }); - consumed2 += consumed_; - value2.push(data); - } - cursor.setPosition(staticPosition + 32); - return [value2, 32]; - } - if (hasDynamicChild(param)) { - const offset = bytesToNumber(cursor.readBytes(sizeOfOffset)); - const start = staticPosition + offset; - const value2 = []; - for (let i = 0; i < length; ++i) { - cursor.setPosition(start + i * 32); - const [data] = decodeParameter(cursor, param, { - staticPosition: start - }); - value2.push(data); - } - cursor.setPosition(staticPosition + 32); - return [value2, 32]; - } - let consumed = 0; - const value = []; - for (let i = 0; i < length; ++i) { - const [data, consumed_] = decodeParameter(cursor, param, { - staticPosition: staticPosition + consumed - }); - consumed += consumed_; - value.push(data); - } - return [value, consumed]; -} -function decodeBool(cursor) { - return [bytesToBool(cursor.readBytes(32), { size: 32 }), 32]; -} -function decodeBytes(cursor, param, { staticPosition }) { - const [_, size3] = param.type.split("bytes"); - if (!size3) { - const offset = bytesToNumber(cursor.readBytes(32)); - cursor.setPosition(staticPosition + offset); - const length = bytesToNumber(cursor.readBytes(32)); - if (length === 0) { - cursor.setPosition(staticPosition + 32); - return ["0x", 32]; - } - const data = cursor.readBytes(length); - cursor.setPosition(staticPosition + 32); - return [bytesToHex2(data), 32]; - } - const value = bytesToHex2(cursor.readBytes(Number.parseInt(size3), 32)); - return [value, 32]; -} -function decodeNumber(cursor, param) { - const signed = param.type.startsWith("int"); - const size3 = Number.parseInt(param.type.split("int")[1] || "256"); - const value = cursor.readBytes(32); - return [ - size3 > 48 ? bytesToBigInt(value, { signed }) : bytesToNumber(value, { signed }), - 32 - ]; -} -function decodeTuple(cursor, param, { staticPosition }) { - const hasUnnamedChild = param.components.length === 0 || param.components.some(({ name }) => !name); - const value = hasUnnamedChild ? [] : {}; - let consumed = 0; - if (hasDynamicChild(param)) { - const offset = bytesToNumber(cursor.readBytes(sizeOfOffset)); - const start = staticPosition + offset; - for (let i = 0; i < param.components.length; ++i) { - const component = param.components[i]; - cursor.setPosition(start + consumed); - const [data, consumed_] = decodeParameter(cursor, component, { - staticPosition: start - }); - consumed += consumed_; - value[hasUnnamedChild ? i : component?.name] = data; - } - cursor.setPosition(staticPosition + 32); - return [value, 32]; - } - for (let i = 0; i < param.components.length; ++i) { - const component = param.components[i]; - const [data, consumed_] = decodeParameter(cursor, component, { - staticPosition - }); - value[hasUnnamedChild ? i : component?.name] = data; - consumed += consumed_; - } - return [value, consumed]; -} -function decodeString(cursor, { staticPosition }) { - const offset = bytesToNumber(cursor.readBytes(32)); - const start = staticPosition + offset; - cursor.setPosition(start); - const length = bytesToNumber(cursor.readBytes(32)); - if (length === 0) { - cursor.setPosition(staticPosition + 32); - return ["", 32]; - } - const data = cursor.readBytes(length, 32); - const value = bytesToString(trim(data)); - cursor.setPosition(staticPosition + 32); - return [value, 32]; -} -function hasDynamicChild(param) { - const { type } = param; - if (type === "string") - return true; - if (type === "bytes") - return true; - if (type.endsWith("[]")) - return true; - if (type === "tuple") - return param.components?.some(hasDynamicChild); - const arrayComponents = getArrayComponents(param.type); - if (arrayComponents && hasDynamicChild({ ...param, type: arrayComponents[1] })) - return true; - return false; -} -var sizeOfLength, sizeOfOffset; -var init_decodeAbiParameters = __esm({ - "node_modules/viem/_esm/utils/abi/decodeAbiParameters.js"() { - init_abi(); - init_getAddress(); - init_cursor2(); - init_size(); - init_slice(); - init_trim(); - init_fromBytes(); - init_toBytes(); - init_toHex(); - init_encodeAbiParameters(); - sizeOfLength = 32; - sizeOfOffset = 32; - } -}); - -// node_modules/viem/_esm/utils/abi/decodeErrorResult.js -function decodeErrorResult(parameters) { - const { abi, data } = parameters; - const signature = slice(data, 0, 4); - if (signature === "0x") - throw new AbiDecodingZeroDataError(); - const abi_ = [...abi || [], solidityError, solidityPanic]; - const abiItem = abi_.find((x) => x.type === "error" && signature === toFunctionSelector(formatAbiItem(x))); - if (!abiItem) - throw new AbiErrorSignatureNotFoundError(signature, { - docsPath: "/docs/contract/decodeErrorResult" - }); - return { - abiItem, - args: "inputs" in abiItem && abiItem.inputs && abiItem.inputs.length > 0 ? decodeAbiParameters(abiItem.inputs, slice(data, 4)) : void 0, - errorName: abiItem.name - }; -} -var init_decodeErrorResult = __esm({ - "node_modules/viem/_esm/utils/abi/decodeErrorResult.js"() { - init_solidity(); - init_abi(); - init_slice(); - init_toFunctionSelector(); - init_decodeAbiParameters(); - init_formatAbiItem(); - } -}); - -// node_modules/viem/_esm/utils/abi/formatAbiItemWithArgs.js -function formatAbiItemWithArgs({ abiItem, args: args2, includeFunctionName = true, includeName = false }) { - if (!("name" in abiItem)) - return; - if (!("inputs" in abiItem)) - return; - if (!abiItem.inputs) - return; - return `${includeFunctionName ? abiItem.name : ""}(${abiItem.inputs.map((input, i) => `${includeName && input.name ? `${input.name}: ` : ""}${typeof args2[i] === "object" ? stringify(args2[i]) : args2[i]}`).join(", ")})`; -} -var init_formatAbiItemWithArgs = __esm({ - "node_modules/viem/_esm/utils/abi/formatAbiItemWithArgs.js"() { - init_stringify(); - } -}); - -// node_modules/viem/_esm/errors/stateOverride.js -var AccountStateConflictError, StateAssignmentConflictError; -var init_stateOverride = __esm({ - "node_modules/viem/_esm/errors/stateOverride.js"() { - init_base(); - AccountStateConflictError = class extends BaseError { - constructor({ address }) { - super(`State for account "${address}" is set multiple times.`, { - name: "AccountStateConflictError" - }); - } - }; - StateAssignmentConflictError = class extends BaseError { - constructor() { - super("state and stateDiff are set on the same account.", { - name: "StateAssignmentConflictError" - }); - } - }; - } -}); - -// node_modules/viem/_esm/errors/utils.js -var getContractAddress, getUrl; -var init_utils3 = __esm({ - "node_modules/viem/_esm/errors/utils.js"() { - getContractAddress = (address) => address; - getUrl = (url) => url; - } -}); - -// node_modules/viem/_esm/errors/contract.js -var ContractFunctionExecutionError, ContractFunctionRevertedError, ContractFunctionZeroDataError, RawContractError; -var init_contract = __esm({ - "node_modules/viem/_esm/errors/contract.js"() { - init_solidity(); - init_decodeErrorResult(); - init_formatAbiItem(); - init_formatAbiItemWithArgs(); - init_getAbiItem(); - init_abi(); - init_base(); - init_transaction(); - init_utils3(); - ContractFunctionExecutionError = class extends BaseError { - constructor(cause, { abi, args: args2, contractAddress, docsPath: docsPath3, functionName, sender }) { - const abiItem = getAbiItem({ abi, args: args2, name: functionName }); - const formattedArgs = abiItem ? formatAbiItemWithArgs({ - abiItem, - args: args2, - includeFunctionName: false, - includeName: false - }) : void 0; - const functionWithParams = abiItem ? formatAbiItem(abiItem, { includeName: true }) : void 0; - const prettyArgs = prettyPrint({ - address: contractAddress && getContractAddress(contractAddress), - function: functionWithParams, - args: formattedArgs && formattedArgs !== "()" && `${[...Array(functionName?.length ?? 0).keys()].map(() => " ").join("")}${formattedArgs}`, - sender - }); - super(cause.shortMessage || `An unknown error occurred while executing the contract function "${functionName}".`, { - cause, - docsPath: docsPath3, - metaMessages: [ - ...cause.metaMessages ? [...cause.metaMessages, " "] : [], - prettyArgs && "Contract Call:", - prettyArgs - ].filter(Boolean), - name: "ContractFunctionExecutionError" - }); - Object.defineProperty(this, "abi", { - enumerable: true, - configurable: true, - writable: true, - value: void 0 - }); - Object.defineProperty(this, "args", { - enumerable: true, - configurable: true, - writable: true, - value: void 0 - }); - Object.defineProperty(this, "cause", { - enumerable: true, - configurable: true, - writable: true, - value: void 0 - }); - Object.defineProperty(this, "contractAddress", { - enumerable: true, - configurable: true, - writable: true, - value: void 0 - }); - Object.defineProperty(this, "formattedArgs", { - enumerable: true, - configurable: true, - writable: true, - value: void 0 - }); - Object.defineProperty(this, "functionName", { - enumerable: true, - configurable: true, - writable: true, - value: void 0 - }); - Object.defineProperty(this, "sender", { - enumerable: true, - configurable: true, - writable: true, - value: void 0 - }); - this.abi = abi; - this.args = args2; - this.cause = cause; - this.contractAddress = contractAddress; - this.functionName = functionName; - this.sender = sender; - } - }; - ContractFunctionRevertedError = class extends BaseError { - constructor({ abi, data, functionName, message }) { - let cause; - let decodedData = void 0; - let metaMessages; - let reason; - if (data && data !== "0x") { - try { - decodedData = decodeErrorResult({ abi, data }); - const { abiItem, errorName, args: errorArgs } = decodedData; - if (errorName === "Error") { - reason = errorArgs[0]; - } else if (errorName === "Panic") { - const [firstArg] = errorArgs; - reason = panicReasons[firstArg]; - } else { - const errorWithParams = abiItem ? formatAbiItem(abiItem, { includeName: true }) : void 0; - const formattedArgs = abiItem && errorArgs ? formatAbiItemWithArgs({ - abiItem, - args: errorArgs, - includeFunctionName: false, - includeName: false - }) : void 0; - metaMessages = [ - errorWithParams ? `Error: ${errorWithParams}` : "", - formattedArgs && formattedArgs !== "()" ? ` ${[...Array(errorName?.length ?? 0).keys()].map(() => " ").join("")}${formattedArgs}` : "" - ]; - } - } catch (err) { - cause = err; - } - } else if (message) - reason = message; - let signature; - if (cause instanceof AbiErrorSignatureNotFoundError) { - signature = cause.signature; - metaMessages = [ - `Unable to decode signature "${signature}" as it was not found on the provided ABI.`, - "Make sure you are using the correct ABI and that the error exists on it.", - `You can look up the decoded signature here: https://openchain.xyz/signatures?query=${signature}.` - ]; - } - super(reason && reason !== "execution reverted" || signature ? [ - `The contract function "${functionName}" reverted with the following ${signature ? "signature" : "reason"}:`, - reason || signature - ].join("\n") : `The contract function "${functionName}" reverted.`, { - cause, - metaMessages, - name: "ContractFunctionRevertedError" - }); - Object.defineProperty(this, "data", { - enumerable: true, - configurable: true, - writable: true, - value: void 0 - }); - Object.defineProperty(this, "raw", { - enumerable: true, - configurable: true, - writable: true, - value: void 0 - }); - Object.defineProperty(this, "reason", { - enumerable: true, - configurable: true, - writable: true, - value: void 0 - }); - Object.defineProperty(this, "signature", { - enumerable: true, - configurable: true, - writable: true, - value: void 0 - }); - this.data = decodedData; - this.raw = data; - this.reason = reason; - this.signature = signature; - } - }; - ContractFunctionZeroDataError = class extends BaseError { - constructor({ functionName }) { - super(`The contract function "${functionName}" returned no data ("0x").`, { - metaMessages: [ - "This could be due to any of the following:", - ` - The contract does not have the function "${functionName}",`, - " - The parameters passed to the contract function may be invalid, or", - " - The address is not a contract." - ], - name: "ContractFunctionZeroDataError" - }); - } - }; - RawContractError = class extends BaseError { - constructor({ data, message }) { - super(message || "", { name: "RawContractError" }); - Object.defineProperty(this, "code", { - enumerable: true, - configurable: true, - writable: true, - value: 3 - }); - Object.defineProperty(this, "data", { - enumerable: true, - configurable: true, - writable: true, - value: void 0 - }); - this.data = data; - } - }; - } -}); - -// node_modules/viem/_esm/errors/request.js -var HttpRequestError, RpcRequestError, TimeoutError; -var init_request = __esm({ - "node_modules/viem/_esm/errors/request.js"() { - init_stringify(); - init_base(); - init_utils3(); - HttpRequestError = class extends BaseError { - constructor({ body, cause, details, headers, status, url }) { - super("HTTP request failed.", { - cause, - details, - metaMessages: [ - status && `Status: ${status}`, - `URL: ${getUrl(url)}`, - body && `Request body: ${stringify(body)}` - ].filter(Boolean), - name: "HttpRequestError" - }); - Object.defineProperty(this, "body", { - enumerable: true, - configurable: true, - writable: true, - value: void 0 - }); - Object.defineProperty(this, "headers", { - enumerable: true, - configurable: true, - writable: true, - value: void 0 - }); - Object.defineProperty(this, "status", { - enumerable: true, - configurable: true, - writable: true, - value: void 0 - }); - Object.defineProperty(this, "url", { - enumerable: true, - configurable: true, - writable: true, - value: void 0 - }); - this.body = body; - this.headers = headers; - this.status = status; - this.url = url; - } - }; - RpcRequestError = class extends BaseError { - constructor({ body, error, url }) { - super("RPC Request failed.", { - cause: error, - details: error.message, - metaMessages: [`URL: ${getUrl(url)}`, `Request body: ${stringify(body)}`], - name: "RpcRequestError" - }); - Object.defineProperty(this, "code", { - enumerable: true, - configurable: true, - writable: true, - value: void 0 - }); - Object.defineProperty(this, "data", { - enumerable: true, - configurable: true, - writable: true, - value: void 0 - }); - this.code = error.code; - this.data = error.data; - } - }; - TimeoutError = class extends BaseError { - constructor({ body, url }) { - super("The request took too long to respond.", { - details: "The request timed out.", - metaMessages: [`URL: ${getUrl(url)}`, `Request body: ${stringify(body)}`], - name: "TimeoutError" - }); - } - }; - } -}); - -// node_modules/viem/_esm/errors/rpc.js -var unknownErrorCode, RpcError, ProviderRpcError, ParseRpcError, InvalidRequestRpcError, MethodNotFoundRpcError, InvalidParamsRpcError, InternalRpcError, InvalidInputRpcError, ResourceNotFoundRpcError, ResourceUnavailableRpcError, TransactionRejectedRpcError, MethodNotSupportedRpcError, LimitExceededRpcError, JsonRpcVersionUnsupportedError, UserRejectedRequestError, UnauthorizedProviderError, UnsupportedProviderMethodError, ProviderDisconnectedError, ChainDisconnectedError, SwitchChainError, UnknownRpcError; -var init_rpc = __esm({ - "node_modules/viem/_esm/errors/rpc.js"() { - init_base(); - init_request(); - unknownErrorCode = -1; - RpcError = class extends BaseError { - constructor(cause, { code, docsPath: docsPath3, metaMessages, name, shortMessage }) { - super(shortMessage, { - cause, - docsPath: docsPath3, - metaMessages: metaMessages || cause?.metaMessages, - name: name || "RpcError" - }); - Object.defineProperty(this, "code", { - enumerable: true, - configurable: true, - writable: true, - value: void 0 - }); - this.name = name || cause.name; - this.code = cause instanceof RpcRequestError ? cause.code : code ?? unknownErrorCode; - } - }; - ProviderRpcError = class extends RpcError { - constructor(cause, options) { - super(cause, options); - Object.defineProperty(this, "data", { - enumerable: true, - configurable: true, - writable: true, - value: void 0 - }); - this.data = options.data; - } - }; - ParseRpcError = class _ParseRpcError extends RpcError { - constructor(cause) { - super(cause, { - code: _ParseRpcError.code, - name: "ParseRpcError", - shortMessage: "Invalid JSON was received by the server. An error occurred on the server while parsing the JSON text." - }); - } - }; - Object.defineProperty(ParseRpcError, "code", { - enumerable: true, - configurable: true, - writable: true, - value: -32700 - }); - InvalidRequestRpcError = class _InvalidRequestRpcError extends RpcError { - constructor(cause) { - super(cause, { - code: _InvalidRequestRpcError.code, - name: "InvalidRequestRpcError", - shortMessage: "JSON is not a valid request object." - }); - } - }; - Object.defineProperty(InvalidRequestRpcError, "code", { - enumerable: true, - configurable: true, - writable: true, - value: -32600 - }); - MethodNotFoundRpcError = class _MethodNotFoundRpcError extends RpcError { - constructor(cause, { method } = {}) { - super(cause, { - code: _MethodNotFoundRpcError.code, - name: "MethodNotFoundRpcError", - shortMessage: `The method${method ? ` "${method}"` : ""} does not exist / is not available.` - }); - } - }; - Object.defineProperty(MethodNotFoundRpcError, "code", { - enumerable: true, - configurable: true, - writable: true, - value: -32601 - }); - InvalidParamsRpcError = class _InvalidParamsRpcError extends RpcError { - constructor(cause) { - super(cause, { - code: _InvalidParamsRpcError.code, - name: "InvalidParamsRpcError", - shortMessage: [ - "Invalid parameters were provided to the RPC method.", - "Double check you have provided the correct parameters." - ].join("\n") - }); - } - }; - Object.defineProperty(InvalidParamsRpcError, "code", { - enumerable: true, - configurable: true, - writable: true, - value: -32602 - }); - InternalRpcError = class _InternalRpcError extends RpcError { - constructor(cause) { - super(cause, { - code: _InternalRpcError.code, - name: "InternalRpcError", - shortMessage: "An internal error was received." - }); - } - }; - Object.defineProperty(InternalRpcError, "code", { - enumerable: true, - configurable: true, - writable: true, - value: -32603 - }); - InvalidInputRpcError = class _InvalidInputRpcError extends RpcError { - constructor(cause) { - super(cause, { - code: _InvalidInputRpcError.code, - name: "InvalidInputRpcError", - shortMessage: [ - "Missing or invalid parameters.", - "Double check you have provided the correct parameters." - ].join("\n") - }); - } - }; - Object.defineProperty(InvalidInputRpcError, "code", { - enumerable: true, - configurable: true, - writable: true, - value: -32e3 - }); - ResourceNotFoundRpcError = class _ResourceNotFoundRpcError extends RpcError { - constructor(cause) { - super(cause, { - code: _ResourceNotFoundRpcError.code, - name: "ResourceNotFoundRpcError", - shortMessage: "Requested resource not found." - }); - Object.defineProperty(this, "name", { - enumerable: true, - configurable: true, - writable: true, - value: "ResourceNotFoundRpcError" - }); - } - }; - Object.defineProperty(ResourceNotFoundRpcError, "code", { - enumerable: true, - configurable: true, - writable: true, - value: -32001 - }); - ResourceUnavailableRpcError = class _ResourceUnavailableRpcError extends RpcError { - constructor(cause) { - super(cause, { - code: _ResourceUnavailableRpcError.code, - name: "ResourceUnavailableRpcError", - shortMessage: "Requested resource not available." - }); - } - }; - Object.defineProperty(ResourceUnavailableRpcError, "code", { - enumerable: true, - configurable: true, - writable: true, - value: -32002 - }); - TransactionRejectedRpcError = class _TransactionRejectedRpcError extends RpcError { - constructor(cause) { - super(cause, { - code: _TransactionRejectedRpcError.code, - name: "TransactionRejectedRpcError", - shortMessage: "Transaction creation failed." - }); - } - }; - Object.defineProperty(TransactionRejectedRpcError, "code", { - enumerable: true, - configurable: true, - writable: true, - value: -32003 - }); - MethodNotSupportedRpcError = class _MethodNotSupportedRpcError extends RpcError { - constructor(cause, { method } = {}) { - super(cause, { - code: _MethodNotSupportedRpcError.code, - name: "MethodNotSupportedRpcError", - shortMessage: `Method${method ? ` "${method}"` : ""} is not supported.` - }); - } - }; - Object.defineProperty(MethodNotSupportedRpcError, "code", { - enumerable: true, - configurable: true, - writable: true, - value: -32004 - }); - LimitExceededRpcError = class _LimitExceededRpcError extends RpcError { - constructor(cause) { - super(cause, { - code: _LimitExceededRpcError.code, - name: "LimitExceededRpcError", - shortMessage: "Request exceeds defined limit." - }); - } - }; - Object.defineProperty(LimitExceededRpcError, "code", { - enumerable: true, - configurable: true, - writable: true, - value: -32005 - }); - JsonRpcVersionUnsupportedError = class _JsonRpcVersionUnsupportedError extends RpcError { - constructor(cause) { - super(cause, { - code: _JsonRpcVersionUnsupportedError.code, - name: "JsonRpcVersionUnsupportedError", - shortMessage: "Version of JSON-RPC protocol is not supported." - }); - } - }; - Object.defineProperty(JsonRpcVersionUnsupportedError, "code", { - enumerable: true, - configurable: true, - writable: true, - value: -32006 - }); - UserRejectedRequestError = class _UserRejectedRequestError extends ProviderRpcError { - constructor(cause) { - super(cause, { - code: _UserRejectedRequestError.code, - name: "UserRejectedRequestError", - shortMessage: "User rejected the request." - }); - } - }; - Object.defineProperty(UserRejectedRequestError, "code", { - enumerable: true, - configurable: true, - writable: true, - value: 4001 - }); - UnauthorizedProviderError = class _UnauthorizedProviderError extends ProviderRpcError { - constructor(cause) { - super(cause, { - code: _UnauthorizedProviderError.code, - name: "UnauthorizedProviderError", - shortMessage: "The requested method and/or account has not been authorized by the user." - }); - } - }; - Object.defineProperty(UnauthorizedProviderError, "code", { - enumerable: true, - configurable: true, - writable: true, - value: 4100 - }); - UnsupportedProviderMethodError = class _UnsupportedProviderMethodError extends ProviderRpcError { - constructor(cause, { method } = {}) { - super(cause, { - code: _UnsupportedProviderMethodError.code, - name: "UnsupportedProviderMethodError", - shortMessage: `The Provider does not support the requested method${method ? ` " ${method}"` : ""}.` - }); - } - }; - Object.defineProperty(UnsupportedProviderMethodError, "code", { - enumerable: true, - configurable: true, - writable: true, - value: 4200 - }); - ProviderDisconnectedError = class _ProviderDisconnectedError extends ProviderRpcError { - constructor(cause) { - super(cause, { - code: _ProviderDisconnectedError.code, - name: "ProviderDisconnectedError", - shortMessage: "The Provider is disconnected from all chains." - }); - } - }; - Object.defineProperty(ProviderDisconnectedError, "code", { - enumerable: true, - configurable: true, - writable: true, - value: 4900 - }); - ChainDisconnectedError = class _ChainDisconnectedError extends ProviderRpcError { - constructor(cause) { - super(cause, { - code: _ChainDisconnectedError.code, - name: "ChainDisconnectedError", - shortMessage: "The Provider is not connected to the requested chain." - }); - } - }; - Object.defineProperty(ChainDisconnectedError, "code", { - enumerable: true, - configurable: true, - writable: true, - value: 4901 - }); - SwitchChainError = class _SwitchChainError extends ProviderRpcError { - constructor(cause) { - super(cause, { - code: _SwitchChainError.code, - name: "SwitchChainError", - shortMessage: "An error occurred when attempting to switch chain." - }); - } - }; - Object.defineProperty(SwitchChainError, "code", { - enumerable: true, - configurable: true, - writable: true, - value: 4902 - }); - UnknownRpcError = class extends RpcError { - constructor(cause) { - super(cause, { - name: "UnknownRpcError", - shortMessage: "An unknown RPC error occurred." - }); - } - }; - } -}); - -// node_modules/viem/_esm/utils/errors/getNodeError.js -function getNodeError(err, args2) { - const message = (err.details || "").toLowerCase(); - const executionRevertedError = err instanceof BaseError ? err.walk((e) => e?.code === ExecutionRevertedError.code) : err; - if (executionRevertedError instanceof BaseError) - return new ExecutionRevertedError({ - cause: err, - message: executionRevertedError.details - }); - if (ExecutionRevertedError.nodeMessage.test(message)) - return new ExecutionRevertedError({ - cause: err, - message: err.details - }); - if (FeeCapTooHighError.nodeMessage.test(message)) - return new FeeCapTooHighError({ - cause: err, - maxFeePerGas: args2?.maxFeePerGas - }); - if (FeeCapTooLowError.nodeMessage.test(message)) - return new FeeCapTooLowError({ - cause: err, - maxFeePerGas: args2?.maxFeePerGas - }); - if (NonceTooHighError.nodeMessage.test(message)) - return new NonceTooHighError({ cause: err, nonce: args2?.nonce }); - if (NonceTooLowError.nodeMessage.test(message)) - return new NonceTooLowError({ cause: err, nonce: args2?.nonce }); - if (NonceMaxValueError.nodeMessage.test(message)) - return new NonceMaxValueError({ cause: err, nonce: args2?.nonce }); - if (InsufficientFundsError.nodeMessage.test(message)) - return new InsufficientFundsError({ cause: err }); - if (IntrinsicGasTooHighError.nodeMessage.test(message)) - return new IntrinsicGasTooHighError({ cause: err, gas: args2?.gas }); - if (IntrinsicGasTooLowError.nodeMessage.test(message)) - return new IntrinsicGasTooLowError({ cause: err, gas: args2?.gas }); - if (TransactionTypeNotSupportedError.nodeMessage.test(message)) - return new TransactionTypeNotSupportedError({ cause: err }); - if (TipAboveFeeCapError.nodeMessage.test(message)) - return new TipAboveFeeCapError({ - cause: err, - maxFeePerGas: args2?.maxFeePerGas, - maxPriorityFeePerGas: args2?.maxPriorityFeePerGas - }); - return new UnknownNodeError({ - cause: err - }); -} -var init_getNodeError = __esm({ - "node_modules/viem/_esm/utils/errors/getNodeError.js"() { - init_base(); - init_node(); - } -}); - -// node_modules/viem/_esm/utils/formatters/extract.js -function extract(value_, { format }) { - if (!format) - return {}; - const value = {}; - function extract_(formatted2) { - const keys = Object.keys(formatted2); - for (const key of keys) { - if (key in value_) - value[key] = value_[key]; - if (formatted2[key] && typeof formatted2[key] === "object" && !Array.isArray(formatted2[key])) - extract_(formatted2[key]); - } - } - const formatted = format(value_ || {}); - extract_(formatted); - return value; -} -var init_extract = __esm({ - "node_modules/viem/_esm/utils/formatters/extract.js"() { - } -}); - -// node_modules/viem/_esm/utils/formatters/transactionRequest.js -function formatTransactionRequest(request) { - const rpcRequest = {}; - if (typeof request.authorizationList !== "undefined") - rpcRequest.authorizationList = formatAuthorizationList(request.authorizationList); - if (typeof request.accessList !== "undefined") - rpcRequest.accessList = request.accessList; - if (typeof request.blobVersionedHashes !== "undefined") - rpcRequest.blobVersionedHashes = request.blobVersionedHashes; - if (typeof request.blobs !== "undefined") { - if (typeof request.blobs[0] !== "string") - rpcRequest.blobs = request.blobs.map((x) => bytesToHex2(x)); - else - rpcRequest.blobs = request.blobs; - } - if (typeof request.data !== "undefined") - rpcRequest.data = request.data; - if (typeof request.from !== "undefined") - rpcRequest.from = request.from; - if (typeof request.gas !== "undefined") - rpcRequest.gas = numberToHex(request.gas); - if (typeof request.gasPrice !== "undefined") - rpcRequest.gasPrice = numberToHex(request.gasPrice); - if (typeof request.maxFeePerBlobGas !== "undefined") - rpcRequest.maxFeePerBlobGas = numberToHex(request.maxFeePerBlobGas); - if (typeof request.maxFeePerGas !== "undefined") - rpcRequest.maxFeePerGas = numberToHex(request.maxFeePerGas); - if (typeof request.maxPriorityFeePerGas !== "undefined") - rpcRequest.maxPriorityFeePerGas = numberToHex(request.maxPriorityFeePerGas); - if (typeof request.nonce !== "undefined") - rpcRequest.nonce = numberToHex(request.nonce); - if (typeof request.to !== "undefined") - rpcRequest.to = request.to; - if (typeof request.type !== "undefined") - rpcRequest.type = rpcTransactionType[request.type]; - if (typeof request.value !== "undefined") - rpcRequest.value = numberToHex(request.value); - return rpcRequest; -} -function formatAuthorizationList(authorizationList) { - return authorizationList.map((authorization) => ({ - address: authorization.contractAddress, - r: authorization.r ? numberToHex(BigInt(authorization.r)) : authorization.r, - s: authorization.s ? numberToHex(BigInt(authorization.s)) : authorization.s, - chainId: numberToHex(authorization.chainId), - nonce: numberToHex(authorization.nonce), - ...typeof authorization.yParity !== "undefined" ? { yParity: numberToHex(authorization.yParity) } : {}, - ...typeof authorization.v !== "undefined" && typeof authorization.yParity === "undefined" ? { v: numberToHex(authorization.v) } : {} - })); -} -var rpcTransactionType; -var init_transactionRequest = __esm({ - "node_modules/viem/_esm/utils/formatters/transactionRequest.js"() { - init_toHex(); - rpcTransactionType = { - legacy: "0x0", - eip2930: "0x1", - eip1559: "0x2", - eip4844: "0x3", - eip7702: "0x4" - }; - } -}); - -// node_modules/viem/_esm/utils/stateOverride.js -function serializeStateMapping(stateMapping) { - if (!stateMapping || stateMapping.length === 0) - return void 0; - return stateMapping.reduce((acc, { slot, value }) => { - if (slot.length !== 66) - throw new InvalidBytesLengthError({ - size: slot.length, - targetSize: 66, - type: "hex" - }); - if (value.length !== 66) - throw new InvalidBytesLengthError({ - size: value.length, - targetSize: 66, - type: "hex" - }); - acc[slot] = value; - return acc; - }, {}); -} -function serializeAccountStateOverride(parameters) { - const { balance, nonce, state, stateDiff, code } = parameters; - const rpcAccountStateOverride = {}; - if (code !== void 0) - rpcAccountStateOverride.code = code; - if (balance !== void 0) - rpcAccountStateOverride.balance = numberToHex(balance); - if (nonce !== void 0) - rpcAccountStateOverride.nonce = numberToHex(nonce); - if (state !== void 0) - rpcAccountStateOverride.state = serializeStateMapping(state); - if (stateDiff !== void 0) { - if (rpcAccountStateOverride.state) - throw new StateAssignmentConflictError(); - rpcAccountStateOverride.stateDiff = serializeStateMapping(stateDiff); - } - return rpcAccountStateOverride; -} -function serializeStateOverride(parameters) { - if (!parameters) - return void 0; - const rpcStateOverride = {}; - for (const { address, ...accountState } of parameters) { - if (!isAddress(address, { strict: false })) - throw new InvalidAddressError({ address }); - if (rpcStateOverride[address]) - throw new AccountStateConflictError({ address }); - rpcStateOverride[address] = serializeAccountStateOverride(accountState); - } - return rpcStateOverride; -} -var init_stateOverride2 = __esm({ - "node_modules/viem/_esm/utils/stateOverride.js"() { - init_address(); - init_data(); - init_stateOverride(); - init_isAddress(); - init_toHex(); - } -}); - -// node_modules/viem/_esm/utils/transaction/assertRequest.js -function assertRequest(args2) { - const { account: account_, gasPrice, maxFeePerGas, maxPriorityFeePerGas, to } = args2; - const account2 = account_ ? parseAccount(account_) : void 0; - if (account2 && !isAddress(account2.address)) - throw new InvalidAddressError({ address: account2.address }); - if (to && !isAddress(to)) - throw new InvalidAddressError({ address: to }); - if (typeof gasPrice !== "undefined" && (typeof maxFeePerGas !== "undefined" || typeof maxPriorityFeePerGas !== "undefined")) - throw new FeeConflictError(); - if (maxFeePerGas && maxFeePerGas > maxUint256) - throw new FeeCapTooHighError({ maxFeePerGas }); - if (maxPriorityFeePerGas && maxFeePerGas && maxPriorityFeePerGas > maxFeePerGas) - throw new TipAboveFeeCapError({ maxFeePerGas, maxPriorityFeePerGas }); -} -var init_assertRequest = __esm({ - "node_modules/viem/_esm/utils/transaction/assertRequest.js"() { - init_parseAccount(); - init_number(); - init_address(); - init_node(); - init_transaction(); - init_isAddress(); - } -}); - -// node_modules/viem/_esm/utils/abi/encodeDeployData.js -function encodeDeployData(parameters) { - const { abi, args: args2, bytecode } = parameters; - if (!args2 || args2.length === 0) - return bytecode; - const description = abi.find((x) => "type" in x && x.type === "constructor"); - if (!description) - throw new AbiConstructorNotFoundError({ docsPath: docsPath2 }); - if (!("inputs" in description)) - throw new AbiConstructorParamsNotFoundError({ docsPath: docsPath2 }); - if (!description.inputs || description.inputs.length === 0) - throw new AbiConstructorParamsNotFoundError({ docsPath: docsPath2 }); - const data = encodeAbiParameters(description.inputs, args2); - return concatHex([bytecode, data]); -} -var docsPath2; -var init_encodeDeployData = __esm({ - "node_modules/viem/_esm/utils/abi/encodeDeployData.js"() { - init_abi(); - init_concat(); - init_encodeAbiParameters(); - docsPath2 = "/docs/contract/encodeDeployData"; - } -}); - -// node_modules/viem/_esm/utils/promise/withResolvers.js -function withResolvers() { - let resolve = () => void 0; - let reject = () => void 0; - const promise = new Promise((resolve_, reject_) => { - resolve = resolve_; - reject = reject_; - }); - return { promise, resolve, reject }; -} -var init_withResolvers = __esm({ - "node_modules/viem/_esm/utils/promise/withResolvers.js"() { - } -}); - -// node_modules/viem/_esm/utils/promise/createBatchScheduler.js -function createBatchScheduler({ fn, id, shouldSplitBatch, wait: wait2 = 0, sort }) { - const exec = async () => { - const scheduler = getScheduler(); - flush(); - const args2 = scheduler.map(({ args: args3 }) => args3); - if (args2.length === 0) - return; - fn(args2).then((data) => { - if (sort && Array.isArray(data)) - data.sort(sort); - for (let i = 0; i < scheduler.length; i++) { - const { resolve } = scheduler[i]; - resolve?.([data[i], data]); - } - }).catch((err) => { - for (let i = 0; i < scheduler.length; i++) { - const { reject } = scheduler[i]; - reject?.(err); - } - }); - }; - const flush = () => schedulerCache.delete(id); - const getBatchedArgs = () => getScheduler().map(({ args: args2 }) => args2); - const getScheduler = () => schedulerCache.get(id) || []; - const setScheduler = (item) => schedulerCache.set(id, [...getScheduler(), item]); - return { - flush, - async schedule(args2) { - const { promise, resolve, reject } = withResolvers(); - const split2 = shouldSplitBatch?.([...getBatchedArgs(), args2]); - if (split2) - exec(); - const hasActiveScheduler = getScheduler().length > 0; - if (hasActiveScheduler) { - setScheduler({ args: args2, resolve, reject }); - return promise; - } - setScheduler({ args: args2, resolve, reject }); - setTimeout(exec, wait2); - return promise; - } - }; -} -var schedulerCache; -var init_createBatchScheduler = __esm({ - "node_modules/viem/_esm/utils/promise/createBatchScheduler.js"() { - init_withResolvers(); - schedulerCache = /* @__PURE__ */ new Map(); - } -}); - -// node_modules/viem/_esm/accounts/privateKeyToAccount.js -init_secp256k1(); -init_toHex(); - -// node_modules/viem/_esm/accounts/toAccount.js -init_address(); -init_isAddress(); -function toAccount(source) { - if (typeof source === "string") { - if (!isAddress(source, { strict: false })) - throw new InvalidAddressError({ address: source }); - return { - address: source, - type: "json-rpc" - }; - } - if (!isAddress(source.address, { strict: false })) - throw new InvalidAddressError({ address: source.address }); - return { - address: source.address, - nonceManager: source.nonceManager, - sign: source.sign, - experimental_signAuthorization: source.experimental_signAuthorization, - signMessage: source.signMessage, - signTransaction: source.signTransaction, - signTypedData: source.signTypedData, - source: "custom", - type: "local" - }; -} - -// node_modules/viem/_esm/accounts/utils/publicKeyToAddress.js -init_getAddress(); -init_keccak256(); -function publicKeyToAddress(publicKey) { - const address = keccak256(`0x${publicKey.substring(4)}`).substring(26); - return checksumAddress(`0x${address}`); -} - -// node_modules/viem/_esm/accounts/utils/sign.js -init_secp256k1(); -init_toHex(); - -// node_modules/viem/_esm/utils/signature/serializeSignature.js -init_secp256k1(); -init_fromHex(); -init_toBytes(); -function serializeSignature({ r, s, to = "hex", v, yParity }) { - const yParity_ = (() => { - if (yParity === 0 || yParity === 1) - return yParity; - if (v && (v === 27n || v === 28n || v >= 35n)) - return v % 2n === 0n ? 1 : 0; - throw new Error("Invalid `v` or `yParity` value"); - })(); - const signature = `0x${new secp256k1.Signature(hexToBigInt(r), hexToBigInt(s)).toCompactHex()}${yParity_ === 0 ? "1b" : "1c"}`; - if (to === "hex") - return signature; - return hexToBytes2(signature); -} - -// node_modules/viem/_esm/accounts/utils/sign.js -var extraEntropy = false; -async function sign({ hash: hash2, privateKey: privateKey2, to = "object" }) { - const { r, s, recovery } = secp256k1.sign(hash2.slice(2), privateKey2.slice(2), { lowS: true, extraEntropy }); - const signature = { - r: numberToHex(r, { size: 32 }), - s: numberToHex(s, { size: 32 }), - v: recovery ? 28n : 27n, - yParity: recovery - }; - return (() => { - if (to === "bytes" || to === "hex") - return serializeSignature({ ...signature, to }); - return signature; - })(); -} - -// node_modules/viem/_esm/experimental/eip7702/utils/hashAuthorization.js -init_concat(); -init_toBytes(); -init_toHex(); - -// node_modules/viem/_esm/utils/encoding/toRlp.js -init_base(); -init_cursor2(); -init_toBytes(); -init_toHex(); -function toRlp(bytes, to = "hex") { - const encodable = getEncodable(bytes); - const cursor = createCursor(new Uint8Array(encodable.length)); - encodable.encode(cursor); - if (to === "hex") - return bytesToHex2(cursor.bytes); - return cursor.bytes; -} -function getEncodable(bytes) { - if (Array.isArray(bytes)) - return getEncodableList(bytes.map((x) => getEncodable(x))); - return getEncodableBytes(bytes); -} -function getEncodableList(list) { - const bodyLength = list.reduce((acc, x) => acc + x.length, 0); - const sizeOfBodyLength = getSizeOfLength(bodyLength); - const length = (() => { - if (bodyLength <= 55) - return 1 + bodyLength; - return 1 + sizeOfBodyLength + bodyLength; - })(); - return { - length, - encode(cursor) { - if (bodyLength <= 55) { - cursor.pushByte(192 + bodyLength); - } else { - cursor.pushByte(192 + 55 + sizeOfBodyLength); - if (sizeOfBodyLength === 1) - cursor.pushUint8(bodyLength); - else if (sizeOfBodyLength === 2) - cursor.pushUint16(bodyLength); - else if (sizeOfBodyLength === 3) - cursor.pushUint24(bodyLength); - else - cursor.pushUint32(bodyLength); - } - for (const { encode } of list) { - encode(cursor); - } - } - }; -} -function getEncodableBytes(bytesOrHex) { - const bytes = typeof bytesOrHex === "string" ? hexToBytes2(bytesOrHex) : bytesOrHex; - const sizeOfBytesLength = getSizeOfLength(bytes.length); - const length = (() => { - if (bytes.length === 1 && bytes[0] < 128) - return 1; - if (bytes.length <= 55) - return 1 + bytes.length; - return 1 + sizeOfBytesLength + bytes.length; - })(); - return { - length, - encode(cursor) { - if (bytes.length === 1 && bytes[0] < 128) { - cursor.pushBytes(bytes); - } else if (bytes.length <= 55) { - cursor.pushByte(128 + bytes.length); - cursor.pushBytes(bytes); - } else { - cursor.pushByte(128 + 55 + sizeOfBytesLength); - if (sizeOfBytesLength === 1) - cursor.pushUint8(bytes.length); - else if (sizeOfBytesLength === 2) - cursor.pushUint16(bytes.length); - else if (sizeOfBytesLength === 3) - cursor.pushUint24(bytes.length); - else - cursor.pushUint32(bytes.length); - cursor.pushBytes(bytes); - } - } - }; -} -function getSizeOfLength(length) { - if (length < 2 ** 8) - return 1; - if (length < 2 ** 16) - return 2; - if (length < 2 ** 24) - return 3; - if (length < 2 ** 32) - return 4; - throw new BaseError("Length is too large."); -} - -// node_modules/viem/_esm/experimental/eip7702/utils/hashAuthorization.js -init_keccak256(); -function hashAuthorization(parameters) { - const { chainId: chainId2, contractAddress, nonce, to } = parameters; - const hash2 = keccak256(concatHex([ - "0x05", - toRlp([ - chainId2 ? numberToHex(chainId2) : "0x", - contractAddress, - nonce ? numberToHex(nonce) : "0x" - ]) - ])); - if (to === "bytes") - return hexToBytes2(hash2); - return hash2; -} - -// node_modules/viem/_esm/accounts/utils/signAuthorization.js -async function experimental_signAuthorization(parameters) { - const { contractAddress, chainId: chainId2, nonce, privateKey: privateKey2, to = "object" } = parameters; - const signature = await sign({ - hash: hashAuthorization({ contractAddress, chainId: chainId2, nonce }), - privateKey: privateKey2, - to - }); - if (to === "object") - return { - contractAddress, - chainId: chainId2, - nonce, - ...signature - }; - return signature; -} - -// node_modules/viem/_esm/utils/signature/hashMessage.js -init_keccak256(); - -// node_modules/viem/_esm/constants/strings.js -var presignMessagePrefix = "Ethereum Signed Message:\n"; - -// node_modules/viem/_esm/utils/signature/toPrefixedMessage.js -init_concat(); -init_size(); -init_toHex(); -function toPrefixedMessage(message_) { - const message = (() => { - if (typeof message_ === "string") - return stringToHex(message_); - if (typeof message_.raw === "string") - return message_.raw; - return bytesToHex2(message_.raw); - })(); - const prefix = stringToHex(`${presignMessagePrefix}${size(message)}`); - return concat([prefix, message]); -} - -// node_modules/viem/_esm/utils/signature/hashMessage.js -function hashMessage(message, to_) { - return keccak256(toPrefixedMessage(message), to_); -} - -// node_modules/viem/_esm/accounts/utils/signMessage.js -async function signMessage({ message, privateKey: privateKey2 }) { - return await sign({ hash: hashMessage(message), privateKey: privateKey2, to: "hex" }); -} - -// node_modules/viem/_esm/accounts/utils/signTransaction.js -init_keccak256(); - -// node_modules/viem/_esm/utils/transaction/serializeTransaction.js -init_transaction(); - -// node_modules/viem/_esm/utils/blob/blobsToCommitments.js -init_toBytes(); -init_toHex(); -function blobsToCommitments(parameters) { - const { kzg } = parameters; - const to = parameters.to ?? (typeof parameters.blobs[0] === "string" ? "hex" : "bytes"); - const blobs = typeof parameters.blobs[0] === "string" ? parameters.blobs.map((x) => hexToBytes2(x)) : parameters.blobs; - const commitments = []; - for (const blob of blobs) - commitments.push(Uint8Array.from(kzg.blobToKzgCommitment(blob))); - return to === "bytes" ? commitments : commitments.map((x) => bytesToHex2(x)); -} - -// node_modules/viem/_esm/utils/blob/blobsToProofs.js -init_toBytes(); -init_toHex(); -function blobsToProofs(parameters) { - const { kzg } = parameters; - const to = parameters.to ?? (typeof parameters.blobs[0] === "string" ? "hex" : "bytes"); - const blobs = typeof parameters.blobs[0] === "string" ? parameters.blobs.map((x) => hexToBytes2(x)) : parameters.blobs; - const commitments = typeof parameters.commitments[0] === "string" ? parameters.commitments.map((x) => hexToBytes2(x)) : parameters.commitments; - const proofs = []; - for (let i = 0; i < blobs.length; i++) { - const blob = blobs[i]; - const commitment = commitments[i]; - proofs.push(Uint8Array.from(kzg.computeBlobKzgProof(blob, commitment))); - } - return to === "bytes" ? proofs : proofs.map((x) => bytesToHex2(x)); -} - -// node_modules/viem/_esm/utils/blob/commitmentToVersionedHash.js -init_toHex(); - -// node_modules/viem/_esm/utils/hash/sha256.js -init_sha256(); -init_isHex(); -init_toBytes(); -init_toHex(); -function sha2562(value, to_) { - const to = to_ || "hex"; - const bytes = sha256(isHex(value, { strict: false }) ? toBytes2(value) : value); - if (to === "bytes") - return bytes; - return toHex(bytes); -} - -// node_modules/viem/_esm/utils/blob/commitmentToVersionedHash.js -function commitmentToVersionedHash(parameters) { - const { commitment, version: version2 = 1 } = parameters; - const to = parameters.to ?? (typeof commitment === "string" ? "hex" : "bytes"); - const versionedHash = sha2562(commitment, "bytes"); - versionedHash.set([version2], 0); - return to === "bytes" ? versionedHash : bytesToHex2(versionedHash); -} - -// node_modules/viem/_esm/utils/blob/commitmentsToVersionedHashes.js -function commitmentsToVersionedHashes(parameters) { - const { commitments, version: version2 } = parameters; - const to = parameters.to ?? (typeof commitments[0] === "string" ? "hex" : "bytes"); - const hashes = []; - for (const commitment of commitments) { - hashes.push(commitmentToVersionedHash({ - commitment, - to, - version: version2 - })); - } - return hashes; -} - -// node_modules/viem/_esm/constants/blob.js -var blobsPerTransaction = 6; -var bytesPerFieldElement = 32; -var fieldElementsPerBlob = 4096; -var bytesPerBlob = bytesPerFieldElement * fieldElementsPerBlob; -var maxBytesPerTransaction = bytesPerBlob * blobsPerTransaction - // terminator byte (0x80). -1 - // zero byte (0x00) appended to each field element. -1 * fieldElementsPerBlob * blobsPerTransaction; - -// node_modules/viem/_esm/constants/kzg.js -var versionedHashVersionKzg = 1; - -// node_modules/viem/_esm/errors/blob.js -init_base(); -var BlobSizeTooLargeError = class extends BaseError { - constructor({ maxSize, size: size3 }) { - super("Blob size is too large.", { - metaMessages: [`Max: ${maxSize} bytes`, `Given: ${size3} bytes`], - name: "BlobSizeTooLargeError" - }); - } -}; -var EmptyBlobError = class extends BaseError { - constructor() { - super("Blob data must not be empty.", { name: "EmptyBlobError" }); - } -}; -var InvalidVersionedHashSizeError = class extends BaseError { - constructor({ hash: hash2, size: size3 }) { - super(`Versioned hash "${hash2}" size is invalid.`, { - metaMessages: ["Expected: 32", `Received: ${size3}`], - name: "InvalidVersionedHashSizeError" - }); - } -}; -var InvalidVersionedHashVersionError = class extends BaseError { - constructor({ hash: hash2, version: version2 }) { - super(`Versioned hash "${hash2}" version is invalid.`, { - metaMessages: [ - `Expected: ${versionedHashVersionKzg}`, - `Received: ${version2}` - ], - name: "InvalidVersionedHashVersionError" - }); - } -}; - -// node_modules/viem/_esm/utils/blob/toBlobs.js -init_cursor2(); -init_size(); -init_toBytes(); -init_toHex(); -function toBlobs(parameters) { - const to = parameters.to ?? (typeof parameters.data === "string" ? "hex" : "bytes"); - const data = typeof parameters.data === "string" ? hexToBytes2(parameters.data) : parameters.data; - const size_ = size(data); - if (!size_) - throw new EmptyBlobError(); - if (size_ > maxBytesPerTransaction) - throw new BlobSizeTooLargeError({ - maxSize: maxBytesPerTransaction, - size: size_ - }); - const blobs = []; - let active = true; - let position = 0; - while (active) { - const blob = createCursor(new Uint8Array(bytesPerBlob)); - let size3 = 0; - while (size3 < fieldElementsPerBlob) { - const bytes = data.slice(position, position + (bytesPerFieldElement - 1)); - blob.pushByte(0); - blob.pushBytes(bytes); - if (bytes.length < 31) { - blob.pushByte(128); - active = false; - break; - } - size3++; - position += 31; - } - blobs.push(blob); - } - return to === "bytes" ? blobs.map((x) => x.bytes) : blobs.map((x) => bytesToHex2(x.bytes)); -} - -// node_modules/viem/_esm/utils/blob/toBlobSidecars.js -function toBlobSidecars(parameters) { - const { data, kzg, to } = parameters; - const blobs = parameters.blobs ?? toBlobs({ data, to }); - const commitments = parameters.commitments ?? blobsToCommitments({ blobs, kzg, to }); - const proofs = parameters.proofs ?? blobsToProofs({ blobs, commitments, kzg, to }); - const sidecars = []; - for (let i = 0; i < blobs.length; i++) - sidecars.push({ - blob: blobs[i], - commitment: commitments[i], - proof: proofs[i] - }); - return sidecars; -} - -// node_modules/viem/_esm/utils/transaction/serializeTransaction.js -init_concat(); -init_trim(); -init_toHex(); - -// node_modules/viem/_esm/experimental/eip7702/utils/serializeAuthorizationList.js -init_toHex(); -function serializeAuthorizationList(authorizationList) { - if (!authorizationList || authorizationList.length === 0) - return []; - const serializedAuthorizationList = []; - for (const authorization of authorizationList) { - const { contractAddress, chainId: chainId2, nonce, ...signature } = authorization; - serializedAuthorizationList.push([ - chainId2 ? toHex(chainId2) : "0x", - contractAddress, - nonce ? toHex(nonce) : "0x", - ...toYParitySignatureArray({}, signature) - ]); - } - return serializedAuthorizationList; -} - -// node_modules/viem/_esm/utils/transaction/assertTransaction.js -init_number(); -init_address(); -init_base(); -init_chain(); -init_node(); -init_isAddress(); -init_size(); -init_slice(); -init_fromHex(); -function assertTransactionEIP7702(transaction) { - const { authorizationList } = transaction; - if (authorizationList) { - for (const authorization of authorizationList) { - const { contractAddress, chainId: chainId2 } = authorization; - if (!isAddress(contractAddress)) - throw new InvalidAddressError({ address: contractAddress }); - if (chainId2 < 0) - throw new InvalidChainIdError({ chainId: chainId2 }); - } - } - assertTransactionEIP1559(transaction); -} -function assertTransactionEIP4844(transaction) { - const { blobVersionedHashes } = transaction; - if (blobVersionedHashes) { - if (blobVersionedHashes.length === 0) - throw new EmptyBlobError(); - for (const hash2 of blobVersionedHashes) { - const size_ = size(hash2); - const version2 = hexToNumber2(slice(hash2, 0, 1)); - if (size_ !== 32) - throw new InvalidVersionedHashSizeError({ hash: hash2, size: size_ }); - if (version2 !== versionedHashVersionKzg) - throw new InvalidVersionedHashVersionError({ - hash: hash2, - version: version2 - }); - } - } - assertTransactionEIP1559(transaction); -} -function assertTransactionEIP1559(transaction) { - const { chainId: chainId2, maxPriorityFeePerGas, maxFeePerGas, to } = transaction; - if (chainId2 <= 0) - throw new InvalidChainIdError({ chainId: chainId2 }); - if (to && !isAddress(to)) - throw new InvalidAddressError({ address: to }); - if (maxFeePerGas && maxFeePerGas > maxUint256) - throw new FeeCapTooHighError({ maxFeePerGas }); - if (maxPriorityFeePerGas && maxFeePerGas && maxPriorityFeePerGas > maxFeePerGas) - throw new TipAboveFeeCapError({ maxFeePerGas, maxPriorityFeePerGas }); -} -function assertTransactionEIP2930(transaction) { - const { chainId: chainId2, maxPriorityFeePerGas, gasPrice, maxFeePerGas, to } = transaction; - if (chainId2 <= 0) - throw new InvalidChainIdError({ chainId: chainId2 }); - if (to && !isAddress(to)) - throw new InvalidAddressError({ address: to }); - if (maxPriorityFeePerGas || maxFeePerGas) - throw new BaseError("`maxFeePerGas`/`maxPriorityFeePerGas` is not a valid EIP-2930 Transaction attribute."); - if (gasPrice && gasPrice > maxUint256) - throw new FeeCapTooHighError({ maxFeePerGas: gasPrice }); -} -function assertTransactionLegacy(transaction) { - const { chainId: chainId2, maxPriorityFeePerGas, gasPrice, maxFeePerGas, to } = transaction; - if (to && !isAddress(to)) - throw new InvalidAddressError({ address: to }); - if (typeof chainId2 !== "undefined" && chainId2 <= 0) - throw new InvalidChainIdError({ chainId: chainId2 }); - if (maxPriorityFeePerGas || maxFeePerGas) - throw new BaseError("`maxFeePerGas`/`maxPriorityFeePerGas` is not a valid Legacy Transaction attribute."); - if (gasPrice && gasPrice > maxUint256) - throw new FeeCapTooHighError({ maxFeePerGas: gasPrice }); -} - -// node_modules/viem/_esm/utils/transaction/getTransactionType.js -init_transaction(); -function getTransactionType(transaction) { - if (transaction.type) - return transaction.type; - if (typeof transaction.authorizationList !== "undefined") - return "eip7702"; - if (typeof transaction.blobs !== "undefined" || typeof transaction.blobVersionedHashes !== "undefined" || typeof transaction.maxFeePerBlobGas !== "undefined" || typeof transaction.sidecars !== "undefined") - return "eip4844"; - if (typeof transaction.maxFeePerGas !== "undefined" || typeof transaction.maxPriorityFeePerGas !== "undefined") { - return "eip1559"; - } - if (typeof transaction.gasPrice !== "undefined") { - if (typeof transaction.accessList !== "undefined") - return "eip2930"; - return "legacy"; - } - throw new InvalidSerializableTransactionError({ transaction }); -} - -// node_modules/viem/_esm/utils/transaction/serializeAccessList.js -init_address(); -init_transaction(); -init_isAddress(); -function serializeAccessList(accessList) { - if (!accessList || accessList.length === 0) - return []; - const serializedAccessList = []; - for (let i = 0; i < accessList.length; i++) { - const { address, storageKeys } = accessList[i]; - for (let j = 0; j < storageKeys.length; j++) { - if (storageKeys[j].length - 2 !== 64) { - throw new InvalidStorageKeySizeError({ storageKey: storageKeys[j] }); - } - } - if (!isAddress(address, { strict: false })) { - throw new InvalidAddressError({ address }); - } - serializedAccessList.push([address, storageKeys]); - } - return serializedAccessList; -} - -// node_modules/viem/_esm/utils/transaction/serializeTransaction.js -function serializeTransaction(transaction, signature) { - const type = getTransactionType(transaction); - if (type === "eip1559") - return serializeTransactionEIP1559(transaction, signature); - if (type === "eip2930") - return serializeTransactionEIP2930(transaction, signature); - if (type === "eip4844") - return serializeTransactionEIP4844(transaction, signature); - if (type === "eip7702") - return serializeTransactionEIP7702(transaction, signature); - return serializeTransactionLegacy(transaction, signature); -} -function serializeTransactionEIP7702(transaction, signature) { - const { authorizationList, chainId: chainId2, gas, nonce, to, value, maxFeePerGas, maxPriorityFeePerGas, accessList, data } = transaction; - assertTransactionEIP7702(transaction); - const serializedAccessList = serializeAccessList(accessList); - const serializedAuthorizationList = serializeAuthorizationList(authorizationList); - return concatHex([ - "0x04", - toRlp([ - toHex(chainId2), - nonce ? toHex(nonce) : "0x", - maxPriorityFeePerGas ? toHex(maxPriorityFeePerGas) : "0x", - maxFeePerGas ? toHex(maxFeePerGas) : "0x", - gas ? toHex(gas) : "0x", - to ?? "0x", - value ? toHex(value) : "0x", - data ?? "0x", - serializedAccessList, - serializedAuthorizationList, - ...toYParitySignatureArray(transaction, signature) - ]) - ]); -} -function serializeTransactionEIP4844(transaction, signature) { - const { chainId: chainId2, gas, nonce, to, value, maxFeePerBlobGas, maxFeePerGas, maxPriorityFeePerGas, accessList, data } = transaction; - assertTransactionEIP4844(transaction); - let blobVersionedHashes = transaction.blobVersionedHashes; - let sidecars = transaction.sidecars; - if (transaction.blobs && (typeof blobVersionedHashes === "undefined" || typeof sidecars === "undefined")) { - const blobs2 = typeof transaction.blobs[0] === "string" ? transaction.blobs : transaction.blobs.map((x) => bytesToHex2(x)); - const kzg = transaction.kzg; - const commitments2 = blobsToCommitments({ - blobs: blobs2, - kzg - }); - if (typeof blobVersionedHashes === "undefined") - blobVersionedHashes = commitmentsToVersionedHashes({ - commitments: commitments2 - }); - if (typeof sidecars === "undefined") { - const proofs2 = blobsToProofs({ blobs: blobs2, commitments: commitments2, kzg }); - sidecars = toBlobSidecars({ blobs: blobs2, commitments: commitments2, proofs: proofs2 }); - } - } - const serializedAccessList = serializeAccessList(accessList); - const serializedTransaction = [ - toHex(chainId2), - nonce ? toHex(nonce) : "0x", - maxPriorityFeePerGas ? toHex(maxPriorityFeePerGas) : "0x", - maxFeePerGas ? toHex(maxFeePerGas) : "0x", - gas ? toHex(gas) : "0x", - to ?? "0x", - value ? toHex(value) : "0x", - data ?? "0x", - serializedAccessList, - maxFeePerBlobGas ? toHex(maxFeePerBlobGas) : "0x", - blobVersionedHashes ?? [], - ...toYParitySignatureArray(transaction, signature) - ]; - const blobs = []; - const commitments = []; - const proofs = []; - if (sidecars) - for (let i = 0; i < sidecars.length; i++) { - const { blob, commitment, proof } = sidecars[i]; - blobs.push(blob); - commitments.push(commitment); - proofs.push(proof); - } - return concatHex([ - "0x03", - sidecars ? ( - // If sidecars are enabled, envelope turns into a "wrapper": - toRlp([serializedTransaction, blobs, commitments, proofs]) - ) : ( - // If sidecars are disabled, standard envelope is used: - toRlp(serializedTransaction) - ) - ]); -} -function serializeTransactionEIP1559(transaction, signature) { - const { chainId: chainId2, gas, nonce, to, value, maxFeePerGas, maxPriorityFeePerGas, accessList, data } = transaction; - assertTransactionEIP1559(transaction); - const serializedAccessList = serializeAccessList(accessList); - const serializedTransaction = [ - toHex(chainId2), - nonce ? toHex(nonce) : "0x", - maxPriorityFeePerGas ? toHex(maxPriorityFeePerGas) : "0x", - maxFeePerGas ? toHex(maxFeePerGas) : "0x", - gas ? toHex(gas) : "0x", - to ?? "0x", - value ? toHex(value) : "0x", - data ?? "0x", - serializedAccessList, - ...toYParitySignatureArray(transaction, signature) - ]; - return concatHex([ - "0x02", - toRlp(serializedTransaction) - ]); -} -function serializeTransactionEIP2930(transaction, signature) { - const { chainId: chainId2, gas, data, nonce, to, value, accessList, gasPrice } = transaction; - assertTransactionEIP2930(transaction); - const serializedAccessList = serializeAccessList(accessList); - const serializedTransaction = [ - toHex(chainId2), - nonce ? toHex(nonce) : "0x", - gasPrice ? toHex(gasPrice) : "0x", - gas ? toHex(gas) : "0x", - to ?? "0x", - value ? toHex(value) : "0x", - data ?? "0x", - serializedAccessList, - ...toYParitySignatureArray(transaction, signature) - ]; - return concatHex([ - "0x01", - toRlp(serializedTransaction) - ]); -} -function serializeTransactionLegacy(transaction, signature) { - const { chainId: chainId2 = 0, gas, data, nonce, to, value, gasPrice } = transaction; - assertTransactionLegacy(transaction); - let serializedTransaction = [ - nonce ? toHex(nonce) : "0x", - gasPrice ? toHex(gasPrice) : "0x", - gas ? toHex(gas) : "0x", - to ?? "0x", - value ? toHex(value) : "0x", - data ?? "0x" - ]; - if (signature) { - const v = (() => { - if (signature.v >= 35n) { - const inferredChainId = (signature.v - 35n) / 2n; - if (inferredChainId > 0) - return signature.v; - return 27n + (signature.v === 35n ? 0n : 1n); - } - if (chainId2 > 0) - return BigInt(chainId2 * 2) + BigInt(35n + signature.v - 27n); - const v2 = 27n + (signature.v === 27n ? 0n : 1n); - if (signature.v !== v2) - throw new InvalidLegacyVError({ v: signature.v }); - return v2; - })(); - const r = trim(signature.r); - const s = trim(signature.s); - serializedTransaction = [ - ...serializedTransaction, - toHex(v), - r === "0x00" ? "0x" : r, - s === "0x00" ? "0x" : s - ]; - } else if (chainId2 > 0) { - serializedTransaction = [ - ...serializedTransaction, - toHex(chainId2), - "0x", - "0x" - ]; - } - return toRlp(serializedTransaction); -} -function toYParitySignatureArray(transaction, signature_) { - const signature = signature_ ?? transaction; - const { v, yParity } = signature; - if (typeof signature.r === "undefined") - return []; - if (typeof signature.s === "undefined") - return []; - if (typeof v === "undefined" && typeof yParity === "undefined") - return []; - const r = trim(signature.r); - const s = trim(signature.s); - const yParity_ = (() => { - if (typeof yParity === "number") - return yParity ? toHex(1) : "0x"; - if (v === 0n) - return "0x"; - if (v === 1n) - return toHex(1); - return v === 27n ? "0x" : toHex(1); - })(); - return [yParity_, r === "0x00" ? "0x" : r, s === "0x00" ? "0x" : s]; -} - -// node_modules/viem/_esm/accounts/utils/signTransaction.js -async function signTransaction(parameters) { - const { privateKey: privateKey2, transaction, serializer = serializeTransaction } = parameters; - const signableTransaction = (() => { - if (transaction.type === "eip4844") - return { - ...transaction, - sidecars: false - }; - return transaction; - })(); - const signature = await sign({ - hash: keccak256(serializer(signableTransaction)), - privateKey: privateKey2 - }); - return serializer(transaction, signature); -} - -// node_modules/viem/_esm/utils/signature/hashTypedData.js -init_encodeAbiParameters(); -init_concat(); -init_toHex(); -init_keccak256(); - -// node_modules/viem/_esm/utils/typedData.js -init_abi(); -init_address(); - -// node_modules/viem/_esm/errors/typedData.js -init_stringify(); -init_base(); -var InvalidDomainError = class extends BaseError { - constructor({ domain }) { - super(`Invalid domain "${stringify(domain)}".`, { - metaMessages: ["Must be a valid EIP-712 domain."] - }); - } -}; -var InvalidPrimaryTypeError = class extends BaseError { - constructor({ primaryType, types }) { - super(`Invalid primary type \`${primaryType}\` must be one of \`${JSON.stringify(Object.keys(types))}\`.`, { - docsPath: "/api/glossary/Errors#typeddatainvalidprimarytypeerror", - metaMessages: ["Check that the primary type is a key in `types`."] - }); - } -}; -var InvalidStructTypeError = class extends BaseError { - constructor({ type }) { - super(`Struct type "${type}" is invalid.`, { - metaMessages: ["Struct type must not be a Solidity type."], - name: "InvalidStructTypeError" - }); - } -}; - -// node_modules/viem/_esm/utils/typedData.js -init_isAddress(); -init_size(); -init_toHex(); -init_regex(); -init_stringify(); -function serializeTypedData(parameters) { - const { domain: domain_, message: message_, primaryType, types } = parameters; - const normalizeData = (struct, data_) => { - const data = { ...data_ }; - for (const param of struct) { - const { name, type } = param; - if (type === "address") - data[name] = data[name].toLowerCase(); - } - return data; - }; - const domain = (() => { - if (!types.EIP712Domain) - return {}; - if (!domain_) - return {}; - return normalizeData(types.EIP712Domain, domain_); - })(); - const message = (() => { - if (primaryType === "EIP712Domain") - return void 0; - return normalizeData(types[primaryType], message_); - })(); - return stringify({ domain, message, primaryType, types }); -} -function validateTypedData(parameters) { - const { domain, message, primaryType, types } = parameters; - const validateData = (struct, data) => { - for (const param of struct) { - const { name, type } = param; - const value = data[name]; - const integerMatch = type.match(integerRegex); - if (integerMatch && (typeof value === "number" || typeof value === "bigint")) { - const [_type, base, size_] = integerMatch; - numberToHex(value, { - signed: base === "int", - size: Number.parseInt(size_) / 8 - }); - } - if (type === "address" && typeof value === "string" && !isAddress(value)) - throw new InvalidAddressError({ address: value }); - const bytesMatch = type.match(bytesRegex); - if (bytesMatch) { - const [_type, size_] = bytesMatch; - if (size_ && size(value) !== Number.parseInt(size_)) - throw new BytesSizeMismatchError({ - expectedSize: Number.parseInt(size_), - givenSize: size(value) - }); - } - const struct2 = types[type]; - if (struct2) { - validateReference(type); - validateData(struct2, value); - } - } - }; - if (types.EIP712Domain && domain) { - if (typeof domain !== "object") - throw new InvalidDomainError({ domain }); - validateData(types.EIP712Domain, domain); - } - if (primaryType !== "EIP712Domain") { - if (types[primaryType]) - validateData(types[primaryType], message); - else - throw new InvalidPrimaryTypeError({ primaryType, types }); - } -} -function getTypesForEIP712Domain({ domain }) { - return [ - typeof domain?.name === "string" && { name: "name", type: "string" }, - domain?.version && { name: "version", type: "string" }, - (typeof domain?.chainId === "number" || typeof domain?.chainId === "bigint") && { - name: "chainId", - type: "uint256" - }, - domain?.verifyingContract && { - name: "verifyingContract", - type: "address" - }, - domain?.salt && { name: "salt", type: "bytes32" } - ].filter(Boolean); -} -function validateReference(type) { - if (type === "address" || type === "bool" || type === "string" || type.startsWith("bytes") || type.startsWith("uint") || type.startsWith("int")) - throw new InvalidStructTypeError({ type }); -} - -// node_modules/viem/_esm/utils/signature/hashTypedData.js -function hashTypedData(parameters) { - const { domain = {}, message, primaryType } = parameters; - const types = { - EIP712Domain: getTypesForEIP712Domain({ domain }), - ...parameters.types - }; - validateTypedData({ - domain, - message, - primaryType, - types - }); - const parts = ["0x1901"]; - if (domain) - parts.push(hashDomain({ - domain, - types - })); - if (primaryType !== "EIP712Domain") - parts.push(hashStruct({ - data: message, - primaryType, - types - })); - return keccak256(concat(parts)); -} -function hashDomain({ domain, types }) { - return hashStruct({ - data: domain, - primaryType: "EIP712Domain", - types - }); -} -function hashStruct({ data, primaryType, types }) { - const encoded = encodeData({ - data, - primaryType, - types - }); - return keccak256(encoded); -} -function encodeData({ data, primaryType, types }) { - const encodedTypes = [{ type: "bytes32" }]; - const encodedValues = [hashType({ primaryType, types })]; - for (const field of types[primaryType]) { - const [type, value] = encodeField({ - types, - name: field.name, - type: field.type, - value: data[field.name] - }); - encodedTypes.push(type); - encodedValues.push(value); - } - return encodeAbiParameters(encodedTypes, encodedValues); -} -function hashType({ primaryType, types }) { - const encodedHashType = toHex(encodeType({ primaryType, types })); - return keccak256(encodedHashType); -} -function encodeType({ primaryType, types }) { - let result = ""; - const unsortedDeps = findTypeDependencies({ primaryType, types }); - unsortedDeps.delete(primaryType); - const deps = [primaryType, ...Array.from(unsortedDeps).sort()]; - for (const type of deps) { - result += `${type}(${types[type].map(({ name, type: t }) => `${t} ${name}`).join(",")})`; - } - return result; -} -function findTypeDependencies({ primaryType: primaryType_, types }, results = /* @__PURE__ */ new Set()) { - const match = primaryType_.match(/^\w*/u); - const primaryType = match?.[0]; - if (results.has(primaryType) || types[primaryType] === void 0) { - return results; - } - results.add(primaryType); - for (const field of types[primaryType]) { - findTypeDependencies({ primaryType: field.type, types }, results); - } - return results; -} -function encodeField({ types, name, type, value }) { - if (types[type] !== void 0) { - return [ - { type: "bytes32" }, - keccak256(encodeData({ data: value, primaryType: type, types })) - ]; - } - if (type === "bytes") { - const prepend = value.length % 2 ? "0" : ""; - value = `0x${prepend + value.slice(2)}`; - return [{ type: "bytes32" }, keccak256(value)]; - } - if (type === "string") - return [{ type: "bytes32" }, keccak256(toHex(value))]; - if (type.lastIndexOf("]") === type.length - 1) { - const parsedType = type.slice(0, type.lastIndexOf("[")); - const typeValuePairs = value.map((item) => encodeField({ - name, - type: parsedType, - types, - value: item - })); - return [ - { type: "bytes32" }, - keccak256(encodeAbiParameters(typeValuePairs.map(([t]) => t), typeValuePairs.map(([, v]) => v))) - ]; - } - return [{ type }, value]; -} - -// node_modules/viem/_esm/accounts/utils/signTypedData.js -async function signTypedData(parameters) { - const { privateKey: privateKey2, ...typedData } = parameters; - return await sign({ - hash: hashTypedData(typedData), - privateKey: privateKey2, - to: "hex" - }); -} - -// node_modules/viem/_esm/accounts/privateKeyToAccount.js -function privateKeyToAccount(privateKey2, options = {}) { - const { nonceManager } = options; - const publicKey = toHex(secp256k1.getPublicKey(privateKey2.slice(2), false)); - const address = publicKeyToAddress(publicKey); - const account2 = toAccount({ - address, - nonceManager, - async sign({ hash: hash2 }) { - return sign({ hash: hash2, privateKey: privateKey2, to: "hex" }); - }, - async experimental_signAuthorization(authorization) { - return experimental_signAuthorization({ ...authorization, privateKey: privateKey2 }); - }, - async signMessage({ message }) { - return signMessage({ message, privateKey: privateKey2 }); - }, - async signTransaction(transaction, { serializer } = {}) { - return signTransaction({ privateKey: privateKey2, transaction, serializer }); - }, - async signTypedData(typedData) { - return signTypedData({ ...typedData, privateKey: privateKey2 }); - } - }); - return { - ...account2, - publicKey, - source: "privateKey" - }; -} - -// node_modules/viem/_esm/actions/public/getTransactionCount.js -init_fromHex(); -init_toHex(); -async function getTransactionCount(client, { address, blockTag = "latest", blockNumber }) { - const count = await client.request({ - method: "eth_getTransactionCount", - params: [address, blockNumber ? numberToHex(blockNumber) : blockTag] - }, { dedupe: Boolean(blockNumber) }); - return hexToNumber2(count); -} - -// node_modules/viem/_esm/utils/getAction.js -function getAction(client, actionFn, name) { - const action_implicit = client[actionFn.name]; - if (typeof action_implicit === "function") - return action_implicit; - const action_explicit = client[name]; - if (typeof action_explicit === "function") - return action_explicit; - return (params) => actionFn(client, params); -} - -// node_modules/viem/_esm/utils/errors/getContractError.js -init_abi(); -init_base(); -init_contract(); -init_request(); -init_rpc(); -var EXECUTION_REVERTED_ERROR_CODE = 3; -function getContractError(err, { abi, address, args: args2, docsPath: docsPath3, functionName, sender }) { - const error = err instanceof RawContractError ? err : err instanceof BaseError ? err.walk((err2) => "data" in err2) || err.walk() : {}; - const { code, data, details, message, shortMessage } = error; - const cause = (() => { - if (err instanceof AbiDecodingZeroDataError) - return new ContractFunctionZeroDataError({ functionName }); - if ([EXECUTION_REVERTED_ERROR_CODE, InternalRpcError.code].includes(code) && (data || details || message || shortMessage)) { - return new ContractFunctionRevertedError({ - abi, - data: typeof data === "object" ? data.data : data, - functionName, - message: error instanceof RpcRequestError ? details : shortMessage ?? message - }); - } - return err; - })(); - return new ContractFunctionExecutionError(cause, { - abi, - args: args2, - contractAddress: address, - docsPath: docsPath3, - functionName, - sender - }); -} - -// node_modules/viem/_esm/actions/public/estimateGas.js -init_parseAccount(); -init_base(); - -// node_modules/viem/_esm/utils/signature/recoverPublicKey.js -init_isHex(); -init_fromHex(); -init_toHex(); -async function recoverPublicKey({ hash: hash2, signature }) { - const hashHex = isHex(hash2) ? hash2 : toHex(hash2); - const { secp256k1: secp256k12 } = await Promise.resolve().then(() => (init_secp256k1(), secp256k1_exports)); - const signature_ = (() => { - if (typeof signature === "object" && "r" in signature && "s" in signature) { - const { r, s, v, yParity } = signature; - const yParityOrV2 = Number(yParity ?? v); - const recoveryBit2 = toRecoveryBit(yParityOrV2); - return new secp256k12.Signature(hexToBigInt(r), hexToBigInt(s)).addRecoveryBit(recoveryBit2); - } - const signatureHex = isHex(signature) ? signature : toHex(signature); - const yParityOrV = hexToNumber2(`0x${signatureHex.slice(130)}`); - const recoveryBit = toRecoveryBit(yParityOrV); - return secp256k12.Signature.fromCompact(signatureHex.substring(2, 130)).addRecoveryBit(recoveryBit); - })(); - const publicKey = signature_.recoverPublicKey(hashHex.substring(2)).toHex(false); - return `0x${publicKey}`; -} -function toRecoveryBit(yParityOrV) { - if (yParityOrV === 0 || yParityOrV === 1) - return yParityOrV; - if (yParityOrV === 27) - return 0; - if (yParityOrV === 28) - return 1; - throw new Error("Invalid yParityOrV value"); -} - -// node_modules/viem/_esm/utils/signature/recoverAddress.js -async function recoverAddress({ hash: hash2, signature }) { - return publicKeyToAddress(await recoverPublicKey({ hash: hash2, signature })); -} - -// node_modules/viem/_esm/experimental/eip7702/utils/recoverAuthorizationAddress.js -async function recoverAuthorizationAddress(parameters) { - const { authorization, signature } = parameters; - return recoverAddress({ - hash: hashAuthorization(authorization), - signature: signature ?? authorization - }); -} - -// node_modules/viem/_esm/actions/public/estimateGas.js -init_toHex(); - -// node_modules/viem/_esm/errors/estimateGas.js -init_formatEther(); -init_formatGwei(); -init_base(); -init_transaction(); -var EstimateGasExecutionError = class extends BaseError { - constructor(cause, { account: account2, docsPath: docsPath3, chain, data, gas, gasPrice, maxFeePerGas, maxPriorityFeePerGas, nonce, to, value }) { - const prettyArgs = prettyPrint({ - from: account2?.address, - to, - value: typeof value !== "undefined" && `${formatEther(value)} ${chain?.nativeCurrency?.symbol || "ETH"}`, - data, - gas, - gasPrice: typeof gasPrice !== "undefined" && `${formatGwei(gasPrice)} gwei`, - maxFeePerGas: typeof maxFeePerGas !== "undefined" && `${formatGwei(maxFeePerGas)} gwei`, - maxPriorityFeePerGas: typeof maxPriorityFeePerGas !== "undefined" && `${formatGwei(maxPriorityFeePerGas)} gwei`, - nonce - }); - super(cause.shortMessage, { - cause, - docsPath: docsPath3, - metaMessages: [ - ...cause.metaMessages ? [...cause.metaMessages, " "] : [], - "Estimate Gas Arguments:", - prettyArgs - ].filter(Boolean), - name: "EstimateGasExecutionError" - }); - Object.defineProperty(this, "cause", { - enumerable: true, - configurable: true, - writable: true, - value: void 0 - }); - this.cause = cause; - } -}; - -// node_modules/viem/_esm/utils/errors/getEstimateGasError.js -init_node(); -init_getNodeError(); -function getEstimateGasError(err, { docsPath: docsPath3, ...args2 }) { - const cause = (() => { - const cause2 = getNodeError(err, args2); - if (cause2 instanceof UnknownNodeError) - return err; - return cause2; - })(); - return new EstimateGasExecutionError(cause, { - docsPath: docsPath3, - ...args2 - }); -} - -// node_modules/viem/_esm/actions/public/estimateGas.js -init_extract(); -init_transactionRequest(); -init_stateOverride2(); -init_assertRequest(); - -// node_modules/viem/_esm/actions/wallet/prepareTransactionRequest.js -init_parseAccount(); - -// node_modules/viem/_esm/errors/fee.js -init_formatGwei(); -init_base(); -var BaseFeeScalarError = class extends BaseError { - constructor() { - super("`baseFeeMultiplier` must be greater than 1.", { - name: "BaseFeeScalarError" - }); - } -}; -var Eip1559FeesNotSupportedError = class extends BaseError { - constructor() { - super("Chain does not support EIP-1559 fees.", { - name: "Eip1559FeesNotSupportedError" - }); - } -}; -var MaxFeePerGasTooLowError = class extends BaseError { - constructor({ maxPriorityFeePerGas }) { - super(`\`maxFeePerGas\` cannot be less than the \`maxPriorityFeePerGas\` (${formatGwei(maxPriorityFeePerGas)} gwei).`, { name: "MaxFeePerGasTooLowError" }); - } -}; - -// node_modules/viem/_esm/actions/public/estimateMaxPriorityFeePerGas.js -init_fromHex(); - -// node_modules/viem/_esm/errors/block.js -init_base(); -var BlockNotFoundError = class extends BaseError { - constructor({ blockHash, blockNumber }) { - let identifier = "Block"; - if (blockHash) - identifier = `Block at hash "${blockHash}"`; - if (blockNumber) - identifier = `Block at number "${blockNumber}"`; - super(`${identifier} could not be found.`, { name: "BlockNotFoundError" }); - } -}; - -// node_modules/viem/_esm/actions/public/getBlock.js -init_toHex(); - -// node_modules/viem/_esm/utils/formatters/transaction.js -init_fromHex(); -var transactionType = { - "0x0": "legacy", - "0x1": "eip2930", - "0x2": "eip1559", - "0x3": "eip4844", - "0x4": "eip7702" -}; -function formatTransaction(transaction) { - const transaction_ = { - ...transaction, - blockHash: transaction.blockHash ? transaction.blockHash : null, - blockNumber: transaction.blockNumber ? BigInt(transaction.blockNumber) : null, - chainId: transaction.chainId ? hexToNumber2(transaction.chainId) : void 0, - gas: transaction.gas ? BigInt(transaction.gas) : void 0, - gasPrice: transaction.gasPrice ? BigInt(transaction.gasPrice) : void 0, - maxFeePerBlobGas: transaction.maxFeePerBlobGas ? BigInt(transaction.maxFeePerBlobGas) : void 0, - maxFeePerGas: transaction.maxFeePerGas ? BigInt(transaction.maxFeePerGas) : void 0, - maxPriorityFeePerGas: transaction.maxPriorityFeePerGas ? BigInt(transaction.maxPriorityFeePerGas) : void 0, - nonce: transaction.nonce ? hexToNumber2(transaction.nonce) : void 0, - to: transaction.to ? transaction.to : null, - transactionIndex: transaction.transactionIndex ? Number(transaction.transactionIndex) : null, - type: transaction.type ? transactionType[transaction.type] : void 0, - typeHex: transaction.type ? transaction.type : void 0, - value: transaction.value ? BigInt(transaction.value) : void 0, - v: transaction.v ? BigInt(transaction.v) : void 0 - }; - if (transaction.authorizationList) - transaction_.authorizationList = formatAuthorizationList2(transaction.authorizationList); - transaction_.yParity = (() => { - if (transaction.yParity) - return Number(transaction.yParity); - if (typeof transaction_.v === "bigint") { - if (transaction_.v === 0n || transaction_.v === 27n) - return 0; - if (transaction_.v === 1n || transaction_.v === 28n) - return 1; - if (transaction_.v >= 35n) - return transaction_.v % 2n === 0n ? 1 : 0; - } - return void 0; - })(); - if (transaction_.type === "legacy") { - delete transaction_.accessList; - delete transaction_.maxFeePerBlobGas; - delete transaction_.maxFeePerGas; - delete transaction_.maxPriorityFeePerGas; - delete transaction_.yParity; - } - if (transaction_.type === "eip2930") { - delete transaction_.maxFeePerBlobGas; - delete transaction_.maxFeePerGas; - delete transaction_.maxPriorityFeePerGas; - } - if (transaction_.type === "eip1559") { - delete transaction_.maxFeePerBlobGas; - } - return transaction_; -} -function formatAuthorizationList2(authorizationList) { - return authorizationList.map((authorization) => ({ - contractAddress: authorization.address, - chainId: Number(authorization.chainId), - nonce: Number(authorization.nonce), - r: authorization.r, - s: authorization.s, - yParity: Number(authorization.yParity) - })); -} - -// node_modules/viem/_esm/utils/formatters/block.js -function formatBlock(block) { - const transactions = (block.transactions ?? []).map((transaction) => { - if (typeof transaction === "string") - return transaction; - return formatTransaction(transaction); - }); - return { - ...block, - baseFeePerGas: block.baseFeePerGas ? BigInt(block.baseFeePerGas) : null, - blobGasUsed: block.blobGasUsed ? BigInt(block.blobGasUsed) : void 0, - difficulty: block.difficulty ? BigInt(block.difficulty) : void 0, - excessBlobGas: block.excessBlobGas ? BigInt(block.excessBlobGas) : void 0, - gasLimit: block.gasLimit ? BigInt(block.gasLimit) : void 0, - gasUsed: block.gasUsed ? BigInt(block.gasUsed) : void 0, - hash: block.hash ? block.hash : null, - logsBloom: block.logsBloom ? block.logsBloom : null, - nonce: block.nonce ? block.nonce : null, - number: block.number ? BigInt(block.number) : null, - size: block.size ? BigInt(block.size) : void 0, - timestamp: block.timestamp ? BigInt(block.timestamp) : void 0, - transactions, - totalDifficulty: block.totalDifficulty ? BigInt(block.totalDifficulty) : null - }; -} - -// node_modules/viem/_esm/actions/public/getBlock.js -async function getBlock(client, { blockHash, blockNumber, blockTag: blockTag_, includeTransactions: includeTransactions_ } = {}) { - const blockTag = blockTag_ ?? "latest"; - const includeTransactions = includeTransactions_ ?? false; - const blockNumberHex = blockNumber !== void 0 ? numberToHex(blockNumber) : void 0; - let block = null; - if (blockHash) { - block = await client.request({ - method: "eth_getBlockByHash", - params: [blockHash, includeTransactions] - }, { dedupe: true }); - } else { - block = await client.request({ - method: "eth_getBlockByNumber", - params: [blockNumberHex || blockTag, includeTransactions] - }, { dedupe: Boolean(blockNumberHex) }); - } - if (!block) - throw new BlockNotFoundError({ blockHash, blockNumber }); - const format = client.chain?.formatters?.block?.format || formatBlock; - return format(block); -} - -// node_modules/viem/_esm/actions/public/getGasPrice.js -async function getGasPrice(client) { - const gasPrice = await client.request({ - method: "eth_gasPrice" - }); - return BigInt(gasPrice); -} - -// node_modules/viem/_esm/actions/public/estimateMaxPriorityFeePerGas.js -async function internal_estimateMaxPriorityFeePerGas(client, args2) { - const { block: block_, chain = client.chain, request } = args2 || {}; - try { - const maxPriorityFeePerGas = chain?.fees?.maxPriorityFeePerGas ?? chain?.fees?.defaultPriorityFee; - if (typeof maxPriorityFeePerGas === "function") { - const block = block_ || await getAction(client, getBlock, "getBlock")({}); - const maxPriorityFeePerGas_ = await maxPriorityFeePerGas({ - block, - client, - request - }); - if (maxPriorityFeePerGas_ === null) - throw new Error(); - return maxPriorityFeePerGas_; - } - if (typeof maxPriorityFeePerGas !== "undefined") - return maxPriorityFeePerGas; - const maxPriorityFeePerGasHex = await client.request({ - method: "eth_maxPriorityFeePerGas" - }); - return hexToBigInt(maxPriorityFeePerGasHex); - } catch { - const [block, gasPrice] = await Promise.all([ - block_ ? Promise.resolve(block_) : getAction(client, getBlock, "getBlock")({}), - getAction(client, getGasPrice, "getGasPrice")({}) - ]); - if (typeof block.baseFeePerGas !== "bigint") - throw new Eip1559FeesNotSupportedError(); - const maxPriorityFeePerGas = gasPrice - block.baseFeePerGas; - if (maxPriorityFeePerGas < 0n) - return 0n; - return maxPriorityFeePerGas; - } -} - -// node_modules/viem/_esm/actions/public/estimateFeesPerGas.js -async function internal_estimateFeesPerGas(client, args2) { - const { block: block_, chain = client.chain, request, type = "eip1559" } = args2 || {}; - const baseFeeMultiplier = await (async () => { - if (typeof chain?.fees?.baseFeeMultiplier === "function") - return chain.fees.baseFeeMultiplier({ - block: block_, - client, - request - }); - return chain?.fees?.baseFeeMultiplier ?? 1.2; - })(); - if (baseFeeMultiplier < 1) - throw new BaseFeeScalarError(); - const decimals = baseFeeMultiplier.toString().split(".")[1]?.length ?? 0; - const denominator = 10 ** decimals; - const multiply = (base) => base * BigInt(Math.ceil(baseFeeMultiplier * denominator)) / BigInt(denominator); - const block = block_ ? block_ : await getAction(client, getBlock, "getBlock")({}); - if (typeof chain?.fees?.estimateFeesPerGas === "function") { - const fees = await chain.fees.estimateFeesPerGas({ - block: block_, - client, - multiply, - request, - type - }); - if (fees !== null) - return fees; - } - if (type === "eip1559") { - if (typeof block.baseFeePerGas !== "bigint") - throw new Eip1559FeesNotSupportedError(); - const maxPriorityFeePerGas = typeof request?.maxPriorityFeePerGas === "bigint" ? request.maxPriorityFeePerGas : await internal_estimateMaxPriorityFeePerGas(client, { - block, - chain, - request - }); - const baseFeePerGas = multiply(block.baseFeePerGas); - const maxFeePerGas = request?.maxFeePerGas ?? baseFeePerGas + maxPriorityFeePerGas; - return { - maxFeePerGas, - maxPriorityFeePerGas - }; - } - const gasPrice = request?.gasPrice ?? multiply(await getAction(client, getGasPrice, "getGasPrice")({})); - return { - gasPrice - }; -} - -// node_modules/viem/_esm/actions/wallet/prepareTransactionRequest.js -init_assertRequest(); - -// node_modules/viem/_esm/actions/public/getChainId.js -init_fromHex(); -async function getChainId(client) { - const chainIdHex = await client.request({ - method: "eth_chainId" - }, { dedupe: true }); - return hexToNumber2(chainIdHex); -} - -// node_modules/viem/_esm/actions/wallet/prepareTransactionRequest.js -var defaultParameters = [ - "blobVersionedHashes", - "chainId", - "fees", - "gas", - "nonce", - "type" -]; -var eip1559NetworkCache = /* @__PURE__ */ new Map(); -async function prepareTransactionRequest(client, args2) { - const { account: account_ = client.account, blobs, chain, gas, kzg, nonce, nonceManager, parameters = defaultParameters, type } = args2; - const account2 = account_ ? parseAccount(account_) : account_; - const request = { ...args2, ...account2 ? { from: account2?.address } : {} }; - let block; - async function getBlock2() { - if (block) - return block; - block = await getAction(client, getBlock, "getBlock")({ blockTag: "latest" }); - return block; - } - let chainId2; - async function getChainId2() { - if (chainId2) - return chainId2; - if (chain) - return chain.id; - if (typeof args2.chainId !== "undefined") - return args2.chainId; - const chainId_ = await getAction(client, getChainId, "getChainId")({}); - chainId2 = chainId_; - return chainId2; - } - if (parameters.includes("nonce") && typeof nonce === "undefined" && account2) { - if (nonceManager) { - const chainId3 = await getChainId2(); - request.nonce = await nonceManager.consume({ - address: account2.address, - chainId: chainId3, - client - }); - } else { - request.nonce = await getAction(client, getTransactionCount, "getTransactionCount")({ - address: account2.address, - blockTag: "pending" - }); - } - } - if ((parameters.includes("blobVersionedHashes") || parameters.includes("sidecars")) && blobs && kzg) { - const commitments = blobsToCommitments({ blobs, kzg }); - if (parameters.includes("blobVersionedHashes")) { - const versionedHashes = commitmentsToVersionedHashes({ - commitments, - to: "hex" - }); - request.blobVersionedHashes = versionedHashes; - } - if (parameters.includes("sidecars")) { - const proofs = blobsToProofs({ blobs, commitments, kzg }); - const sidecars = toBlobSidecars({ - blobs, - commitments, - proofs, - to: "hex" - }); - request.sidecars = sidecars; - } - } - if (parameters.includes("chainId")) - request.chainId = await getChainId2(); - if ((parameters.includes("fees") || parameters.includes("type")) && typeof type === "undefined") { - try { - request.type = getTransactionType(request); - } catch { - let isEip1559Network = eip1559NetworkCache.get(client.uid); - if (typeof isEip1559Network === "undefined") { - const block2 = await getBlock2(); - isEip1559Network = typeof block2?.baseFeePerGas === "bigint"; - eip1559NetworkCache.set(client.uid, isEip1559Network); - } - request.type = isEip1559Network ? "eip1559" : "legacy"; - } - } - if (parameters.includes("fees")) { - if (request.type !== "legacy" && request.type !== "eip2930") { - if (typeof request.maxFeePerGas === "undefined" || typeof request.maxPriorityFeePerGas === "undefined") { - const block2 = await getBlock2(); - const { maxFeePerGas, maxPriorityFeePerGas } = await internal_estimateFeesPerGas(client, { - block: block2, - chain, - request - }); - if (typeof args2.maxPriorityFeePerGas === "undefined" && args2.maxFeePerGas && args2.maxFeePerGas < maxPriorityFeePerGas) - throw new MaxFeePerGasTooLowError({ - maxPriorityFeePerGas - }); - request.maxPriorityFeePerGas = maxPriorityFeePerGas; - request.maxFeePerGas = maxFeePerGas; - } - } else { - if (typeof args2.maxFeePerGas !== "undefined" || typeof args2.maxPriorityFeePerGas !== "undefined") - throw new Eip1559FeesNotSupportedError(); - if (typeof args2.gasPrice === "undefined") { - const block2 = await getBlock2(); - const { gasPrice: gasPrice_ } = await internal_estimateFeesPerGas(client, { - block: block2, - chain, - request, - type: "legacy" - }); - request.gasPrice = gasPrice_; - } - } - } - if (parameters.includes("gas") && typeof gas === "undefined") - request.gas = await getAction(client, estimateGas, "estimateGas")({ - ...request, - account: account2 ? { address: account2.address, type: "json-rpc" } : account2 - }); - assertRequest(request); - delete request.parameters; - return request; -} - -// node_modules/viem/_esm/actions/public/getBalance.js -init_toHex(); -async function getBalance(client, { address, blockNumber, blockTag = "latest" }) { - const blockNumberHex = blockNumber ? numberToHex(blockNumber) : void 0; - const balance = await client.request({ - method: "eth_getBalance", - params: [address, blockNumberHex || blockTag] - }); - return BigInt(balance); -} - -// node_modules/viem/_esm/actions/public/estimateGas.js -async function estimateGas(client, args2) { - const { account: account_ = client.account } = args2; - const account2 = account_ ? parseAccount(account_) : void 0; - try { - let estimateGas_rpc = function(parameters) { - const { block: block2, request: request2, rpcStateOverride: rpcStateOverride2 } = parameters; - return client.request({ - method: "eth_estimateGas", - params: rpcStateOverride2 ? [request2, block2 ?? "latest", rpcStateOverride2] : block2 ? [request2, block2] : [request2] - }); - }; - const { accessList, authorizationList, blobs, blobVersionedHashes, blockNumber, blockTag, data, gas, gasPrice, maxFeePerBlobGas, maxFeePerGas, maxPriorityFeePerGas, nonce, value, stateOverride, ...rest } = await prepareTransactionRequest(client, { - ...args2, - parameters: ( - // Some RPC Providers do not compute versioned hashes from blobs. We will need - // to compute them. - account2?.type === "local" ? void 0 : ["blobVersionedHashes"] - ) - }); - const blockNumberHex = blockNumber ? numberToHex(blockNumber) : void 0; - const block = blockNumberHex || blockTag; - const rpcStateOverride = serializeStateOverride(stateOverride); - const to = await (async () => { - if (rest.to) - return rest.to; - if (authorizationList && authorizationList.length > 0) - return await recoverAuthorizationAddress({ - authorization: authorizationList[0] - }).catch(() => { - throw new BaseError("`to` is required. Could not infer from `authorizationList`"); - }); - return void 0; - })(); - assertRequest(args2); - const chainFormat = client.chain?.formatters?.transactionRequest?.format; - const format = chainFormat || formatTransactionRequest; - const request = format({ - // Pick out extra data that might exist on the chain's transaction request type. - ...extract(rest, { format: chainFormat }), - from: account2?.address, - accessList, - authorizationList, - blobs, - blobVersionedHashes, - data, - gas, - gasPrice, - maxFeePerBlobGas, - maxFeePerGas, - maxPriorityFeePerGas, - nonce, - to, - value - }); - let estimate = BigInt(await estimateGas_rpc({ block, request, rpcStateOverride })); - if (authorizationList) { - const value2 = await getBalance(client, { address: request.from }); - const estimates = await Promise.all(authorizationList.map(async (authorization) => { - const { contractAddress } = authorization; - const estimate2 = await estimateGas_rpc({ - block, - request: { - authorizationList: void 0, - data, - from: account2?.address, - to: contractAddress, - value: numberToHex(value2) - }, - rpcStateOverride - }).catch(() => 100000n); - return 2n * BigInt(estimate2); - })); - estimate += estimates.reduce((acc, curr) => acc + curr, 0n); - } - return estimate; - } catch (err) { - throw getEstimateGasError(err, { - ...args2, - account: account2, - chain: client.chain - }); - } -} - -// node_modules/viem/_esm/utils/wait.js -async function wait(time) { - return new Promise((res) => setTimeout(res, time)); -} - -// node_modules/viem/_esm/actions/wallet/writeContract.js -init_parseAccount(); - -// node_modules/viem/_esm/errors/account.js -init_base(); -var AccountNotFoundError = class extends BaseError { - constructor({ docsPath: docsPath3 } = {}) { - super([ - "Could not find an Account to execute with this Action.", - "Please provide an Account with the `account` argument on the Action, or by supplying an `account` to the Client." - ].join("\n"), { - docsPath: docsPath3, - docsSlug: "account", - name: "AccountNotFoundError" - }); - } -}; -var AccountTypeNotSupportedError = class extends BaseError { - constructor({ docsPath: docsPath3, metaMessages, type }) { - super(`Account type "${type}" is not supported.`, { - docsPath: docsPath3, - metaMessages, - name: "AccountTypeNotSupportedError" - }); - } -}; - -// node_modules/viem/_esm/actions/wallet/writeContract.js -init_encodeFunctionData(); - -// node_modules/viem/_esm/actions/wallet/sendTransaction.js -init_parseAccount(); -init_base(); - -// node_modules/viem/_esm/utils/chain/assertCurrentChain.js -init_chain(); -function assertCurrentChain({ chain, currentChainId }) { - if (!chain) - throw new ChainNotFoundError(); - if (currentChainId !== chain.id) - throw new ChainMismatchError({ chain, currentChainId }); -} - -// node_modules/viem/_esm/utils/errors/getTransactionError.js -init_node(); -init_transaction(); -init_getNodeError(); -function getTransactionError(err, { docsPath: docsPath3, ...args2 }) { - const cause = (() => { - const cause2 = getNodeError(err, args2); - if (cause2 instanceof UnknownNodeError) - return err; - return cause2; - })(); - return new TransactionExecutionError(cause, { - docsPath: docsPath3, - ...args2 - }); -} - -// node_modules/viem/_esm/actions/wallet/sendTransaction.js -init_extract(); -init_transactionRequest(); -init_lru(); -init_assertRequest(); - -// node_modules/viem/_esm/actions/wallet/sendRawTransaction.js -async function sendRawTransaction(client, { serializedTransaction }) { - return client.request({ - method: "eth_sendRawTransaction", - params: [serializedTransaction] - }, { retryCount: 0 }); -} - -// node_modules/viem/_esm/actions/wallet/sendTransaction.js -var supportsWalletNamespace = new LruMap(128); -async function sendTransaction(client, parameters) { - const { account: account_ = client.account, chain = client.chain, accessList, authorizationList, blobs, data, gas, gasPrice, maxFeePerBlobGas, maxFeePerGas, maxPriorityFeePerGas, nonce, value, ...rest } = parameters; - if (typeof account_ === "undefined") - throw new AccountNotFoundError({ - docsPath: "/docs/actions/wallet/sendTransaction" - }); - const account2 = account_ ? parseAccount(account_) : null; - try { - assertRequest(parameters); - const to = await (async () => { - if (parameters.to) - return parameters.to; - if (parameters.to === null) - return void 0; - if (authorizationList && authorizationList.length > 0) - return await recoverAuthorizationAddress({ - authorization: authorizationList[0] - }).catch(() => { - throw new BaseError("`to` is required. Could not infer from `authorizationList`."); - }); - return void 0; - })(); - if (account2?.type === "json-rpc" || account2 === null) { - let chainId2; - if (chain !== null) { - chainId2 = await getAction(client, getChainId, "getChainId")({}); - assertCurrentChain({ - currentChainId: chainId2, - chain - }); - } - const chainFormat = client.chain?.formatters?.transactionRequest?.format; - const format = chainFormat || formatTransactionRequest; - const request = format({ - // Pick out extra data that might exist on the chain's transaction request type. - ...extract(rest, { format: chainFormat }), - accessList, - authorizationList, - blobs, - chainId: chainId2, - data, - from: account2?.address, - gas, - gasPrice, - maxFeePerBlobGas, - maxFeePerGas, - maxPriorityFeePerGas, - nonce, - to, - value - }); - const isWalletNamespaceSupported = supportsWalletNamespace.get(client.uid); - const method = isWalletNamespaceSupported ? "wallet_sendTransaction" : "eth_sendTransaction"; - try { - return await client.request({ - method, - params: [request] - }, { retryCount: 0 }); - } catch (e) { - if (isWalletNamespaceSupported === false) - throw e; - const error = e; - if (error.name === "InvalidInputRpcError" || error.name === "InvalidParamsRpcError" || error.name === "MethodNotFoundRpcError" || error.name === "MethodNotSupportedRpcError") { - return await client.request({ - method: "wallet_sendTransaction", - params: [request] - }, { retryCount: 0 }).then((hash2) => { - supportsWalletNamespace.set(client.uid, true); - return hash2; - }).catch((e2) => { - const walletNamespaceError = e2; - if (walletNamespaceError.name === "MethodNotFoundRpcError" || walletNamespaceError.name === "MethodNotSupportedRpcError") { - supportsWalletNamespace.set(client.uid, false); - throw error; - } - throw walletNamespaceError; - }); - } - throw error; - } - } - if (account2?.type === "local") { - const request = await getAction(client, prepareTransactionRequest, "prepareTransactionRequest")({ - account: account2, - accessList, - authorizationList, - blobs, - chain, - data, - gas, - gasPrice, - maxFeePerBlobGas, - maxFeePerGas, - maxPriorityFeePerGas, - nonce, - nonceManager: account2.nonceManager, - parameters: [...defaultParameters, "sidecars"], - value, - ...rest, - to - }); - const serializer = chain?.serializers?.transaction; - const serializedTransaction = await account2.signTransaction(request, { - serializer - }); - return await getAction(client, sendRawTransaction, "sendRawTransaction")({ - serializedTransaction - }); - } - if (account2?.type === "smart") - throw new AccountTypeNotSupportedError({ - metaMessages: [ - "Consider using the `sendUserOperation` Action instead." - ], - docsPath: "/docs/actions/bundler/sendUserOperation", - type: "smart" - }); - throw new AccountTypeNotSupportedError({ - docsPath: "/docs/actions/wallet/sendTransaction", - type: account2?.type - }); - } catch (err) { - if (err instanceof AccountTypeNotSupportedError) - throw err; - throw getTransactionError(err, { - ...parameters, - account: account2, - chain: parameters.chain || void 0 - }); - } -} - -// node_modules/viem/_esm/actions/wallet/writeContract.js -async function writeContract(client, parameters) { - const { abi, account: account_ = client.account, address, args: args2, dataSuffix, functionName, ...request } = parameters; - if (typeof account_ === "undefined") - throw new AccountNotFoundError({ - docsPath: "/docs/contract/writeContract" - }); - const account2 = account_ ? parseAccount(account_) : null; - const data = encodeFunctionData({ - abi, - args: args2, - functionName - }); - try { - return await getAction(client, sendTransaction, "sendTransaction")({ - data: `${data}${dataSuffix ? dataSuffix.replace("0x", "") : ""}`, - to: address, - account: account2, - ...request - }); - } catch (error) { - throw getContractError(error, { - abi, - address, - args: args2, - docsPath: "/docs/contract/writeContract", - functionName, - sender: account2?.address - }); - } -} - -// node_modules/viem/_esm/actions/wallet/addChain.js -init_toHex(); -async function addChain(client, { chain }) { - const { id, name, nativeCurrency, rpcUrls, blockExplorers } = chain; - await client.request({ - method: "wallet_addEthereumChain", - params: [ - { - chainId: numberToHex(id), - chainName: name, - nativeCurrency, - rpcUrls: rpcUrls.default.http, - blockExplorerUrls: blockExplorers ? Object.values(blockExplorers).map(({ url }) => url) : void 0 - } - ] - }, { dedupe: true, retryCount: 0 }); -} - -// node_modules/viem/_esm/clients/createClient.js -init_parseAccount(); - -// node_modules/viem/_esm/utils/uid.js -var size2 = 256; -var index = size2; -var buffer; -function uid(length = 11) { - if (!buffer || index + length > size2 * 2) { - buffer = ""; - index = 0; - for (let i = 0; i < size2; i++) { - buffer += (256 + Math.random() * 256 | 0).toString(16).substring(1); - } - } - return buffer.substring(index, index++ + length); -} - -// node_modules/viem/_esm/clients/createClient.js -function createClient(parameters) { - const { batch, cacheTime = parameters.pollingInterval ?? 4e3, ccipRead, key = "base", name = "Base Client", pollingInterval = 4e3, type = "base" } = parameters; - const chain = parameters.chain; - const account2 = parameters.account ? parseAccount(parameters.account) : void 0; - const { config, request, value } = parameters.transport({ - chain, - pollingInterval - }); - const transport = { ...config, ...value }; - const client = { - account: account2, - batch, - cacheTime, - ccipRead, - chain, - key, - name, - pollingInterval, - request, - transport, - type, - uid: uid() - }; - function extend(base) { - return (extendFn) => { - const extended = extendFn(base); - for (const key2 in client) - delete extended[key2]; - const combined = { ...base, ...extended }; - return Object.assign(combined, { extend: extend(combined) }); - }; - } - return Object.assign(client, { extend: extend(client) }); -} - -// node_modules/viem/_esm/utils/buildRequest.js -init_base(); -init_request(); -init_rpc(); -init_toHex(); - -// node_modules/viem/_esm/utils/promise/withDedupe.js -init_lru(); -var promiseCache = /* @__PURE__ */ new LruMap(8192); -function withDedupe(fn, { enabled = true, id }) { - if (!enabled || !id) - return fn(); - if (promiseCache.get(id)) - return promiseCache.get(id); - const promise = fn().finally(() => promiseCache.delete(id)); - promiseCache.set(id, promise); - return promise; -} - -// node_modules/viem/_esm/utils/promise/withRetry.js -function withRetry(fn, { delay: delay_ = 100, retryCount = 2, shouldRetry: shouldRetry2 = () => true } = {}) { - return new Promise((resolve, reject) => { - const attemptRetry = async ({ count = 0 } = {}) => { - const retry = async ({ error }) => { - const delay = typeof delay_ === "function" ? delay_({ count, error }) : delay_; - if (delay) - await wait(delay); - attemptRetry({ count: count + 1 }); - }; - try { - const data = await fn(); - resolve(data); - } catch (err) { - if (count < retryCount && await shouldRetry2({ count, error: err })) - return retry({ error: err }); - reject(err); - } - }; - attemptRetry(); - }); -} - -// node_modules/viem/_esm/utils/buildRequest.js -init_stringify(); -function buildRequest(request, options = {}) { - return async (args2, overrideOptions = {}) => { - const { dedupe = false, methods, retryDelay = 150, retryCount = 3, uid: uid2 } = { - ...options, - ...overrideOptions - }; - const { method } = args2; - if (methods?.exclude?.includes(method)) - throw new MethodNotSupportedRpcError(new Error("method not supported"), { - method - }); - if (methods?.include && !methods.include.includes(method)) - throw new MethodNotSupportedRpcError(new Error("method not supported"), { - method - }); - const requestId = dedupe ? stringToHex(`${uid2}.${stringify(args2)}`) : void 0; - return withDedupe(() => withRetry(async () => { - try { - return await request(args2); - } catch (err_) { - const err = err_; - switch (err.code) { - case ParseRpcError.code: - throw new ParseRpcError(err); - case InvalidRequestRpcError.code: - throw new InvalidRequestRpcError(err); - case MethodNotFoundRpcError.code: - throw new MethodNotFoundRpcError(err, { method: args2.method }); - case InvalidParamsRpcError.code: - throw new InvalidParamsRpcError(err); - case InternalRpcError.code: - throw new InternalRpcError(err); - case InvalidInputRpcError.code: - throw new InvalidInputRpcError(err); - case ResourceNotFoundRpcError.code: - throw new ResourceNotFoundRpcError(err); - case ResourceUnavailableRpcError.code: - throw new ResourceUnavailableRpcError(err); - case TransactionRejectedRpcError.code: - throw new TransactionRejectedRpcError(err); - case MethodNotSupportedRpcError.code: - throw new MethodNotSupportedRpcError(err, { - method: args2.method - }); - case LimitExceededRpcError.code: - throw new LimitExceededRpcError(err); - case JsonRpcVersionUnsupportedError.code: - throw new JsonRpcVersionUnsupportedError(err); - case UserRejectedRequestError.code: - throw new UserRejectedRequestError(err); - case UnauthorizedProviderError.code: - throw new UnauthorizedProviderError(err); - case UnsupportedProviderMethodError.code: - throw new UnsupportedProviderMethodError(err); - case ProviderDisconnectedError.code: - throw new ProviderDisconnectedError(err); - case ChainDisconnectedError.code: - throw new ChainDisconnectedError(err); - case SwitchChainError.code: - throw new SwitchChainError(err); - case 5e3: - throw new UserRejectedRequestError(err); - default: - if (err_ instanceof BaseError) - throw err_; - throw new UnknownRpcError(err); - } - } - }, { - delay: ({ count, error }) => { - if (error && error instanceof HttpRequestError) { - const retryAfter = error?.headers?.get("Retry-After"); - if (retryAfter?.match(/\d/)) - return Number.parseInt(retryAfter) * 1e3; - } - return ~~(1 << count) * retryDelay; - }, - retryCount, - shouldRetry: ({ error }) => shouldRetry(error) - }), { enabled: dedupe, id: requestId }); - }; -} -function shouldRetry(error) { - if ("code" in error && typeof error.code === "number") { - if (error.code === -1) - return true; - if (error.code === LimitExceededRpcError.code) - return true; - if (error.code === InternalRpcError.code) - return true; - return false; - } - if (error instanceof HttpRequestError && error.status) { - if (error.status === 403) - return true; - if (error.status === 408) - return true; - if (error.status === 413) - return true; - if (error.status === 429) - return true; - if (error.status === 500) - return true; - if (error.status === 502) - return true; - if (error.status === 503) - return true; - if (error.status === 504) - return true; - return false; - } - return true; -} - -// node_modules/viem/_esm/clients/transports/createTransport.js -function createTransport({ key, methods, name, request, retryCount = 3, retryDelay = 150, timeout, type }, value) { - const uid2 = uid(); - return { - config: { - key, - methods, - name, - request, - retryCount, - retryDelay, - timeout, - type - }, - request: buildRequest(request, { methods, retryCount, retryDelay, uid: uid2 }), - value - }; -} - -// node_modules/viem/_esm/clients/transports/http.js -init_request(); - -// node_modules/viem/_esm/errors/transport.js -init_base(); -var UrlRequiredError = class extends BaseError { - constructor() { - super("No URL was provided to the Transport. Please provide a valid RPC URL to the Transport.", { - docsPath: "/docs/clients/intro", - name: "UrlRequiredError" - }); - } -}; - -// node_modules/viem/_esm/clients/transports/http.js -init_createBatchScheduler(); - -// node_modules/viem/_esm/utils/rpc/http.js -init_request(); - -// node_modules/viem/_esm/utils/promise/withTimeout.js -function withTimeout(fn, { errorInstance = new Error("timed out"), timeout, signal }) { - return new Promise((resolve, reject) => { - ; - (async () => { - let timeoutId; - try { - const controller = new AbortController(); - if (timeout > 0) { - timeoutId = setTimeout(() => { - if (signal) { - controller.abort(); - } else { - reject(errorInstance); - } - }, timeout); - } - resolve(await fn({ signal: controller?.signal || null })); - } catch (err) { - if (err?.name === "AbortError") - reject(errorInstance); - reject(err); - } finally { - clearTimeout(timeoutId); - } - })(); - }); -} - -// node_modules/viem/_esm/utils/rpc/http.js -init_stringify(); - -// node_modules/viem/_esm/utils/rpc/id.js -function createIdStore() { - return { - current: 0, - take() { - return this.current++; - }, - reset() { - this.current = 0; - } - }; -} -var idCache = /* @__PURE__ */ createIdStore(); - -// node_modules/viem/_esm/utils/rpc/http.js -function getHttpRpcClient(url, options = {}) { - return { - async request(params) { - const { body, onRequest = options.onRequest, onResponse = options.onResponse, timeout = options.timeout ?? 1e4 } = params; - const fetchOptions = { - ...options.fetchOptions ?? {}, - ...params.fetchOptions ?? {} - }; - const { headers, method, signal: signal_ } = fetchOptions; - try { - const response = await withTimeout(async ({ signal }) => { - const init = { - ...fetchOptions, - body: Array.isArray(body) ? stringify(body.map((body2) => ({ - jsonrpc: "2.0", - id: body2.id ?? idCache.take(), - ...body2 - }))) : stringify({ - jsonrpc: "2.0", - id: body.id ?? idCache.take(), - ...body - }), - headers: { - "Content-Type": "application/json", - ...headers - }, - method: method || "POST", - signal: signal_ || (timeout > 0 ? signal : null) - }; - const request = new Request(url, init); - const args2 = await onRequest?.(request, init) ?? { ...init, url }; - const response2 = await fetch(args2.url ?? url, args2); - return response2; - }, { - errorInstance: new TimeoutError({ body, url }), - timeout, - signal: true - }); - if (onResponse) - await onResponse(response); - let data; - if (response.headers.get("Content-Type")?.startsWith("application/json")) - data = await response.json(); - else { - data = await response.text(); - try { - data = JSON.parse(data || "{}"); - } catch (err) { - if (response.ok) - throw err; - data = { error: data }; - } - } - if (!response.ok) { - throw new HttpRequestError({ - body, - details: stringify(data.error) || response.statusText, - headers: response.headers, - status: response.status, - url - }); - } - return data; - } catch (err) { - if (err instanceof HttpRequestError) - throw err; - if (err instanceof TimeoutError) - throw err; - throw new HttpRequestError({ - body, - cause: err, - url - }); - } - } - }; -} - -// node_modules/viem/_esm/clients/transports/http.js -function http(url, config = {}) { - const { batch, fetchOptions, key = "http", methods, name = "HTTP JSON-RPC", onFetchRequest, onFetchResponse, retryDelay, raw } = config; - return ({ chain, retryCount: retryCount_, timeout: timeout_ }) => { - const { batchSize = 1e3, wait: wait2 = 0 } = typeof batch === "object" ? batch : {}; - const retryCount = config.retryCount ?? retryCount_; - const timeout = timeout_ ?? config.timeout ?? 1e4; - const url_ = url || chain?.rpcUrls.default.http[0]; - if (!url_) - throw new UrlRequiredError(); - const rpcClient = getHttpRpcClient(url_, { - fetchOptions, - onRequest: onFetchRequest, - onResponse: onFetchResponse, - timeout - }); - return createTransport({ - key, - methods, - name, - async request({ method, params }) { - const body = { method, params }; - const { schedule } = createBatchScheduler({ - id: url_, - wait: wait2, - shouldSplitBatch(requests) { - return requests.length > batchSize; - }, - fn: (body2) => rpcClient.request({ - body: body2 - }), - sort: (a, b) => a.id - b.id - }); - const fn = async (body2) => batch ? schedule(body2) : [ - await rpcClient.request({ - body: body2 - }) - ]; - const [{ error, result }] = await fn(body); - if (raw) - return { error, result }; - if (error) - throw new RpcRequestError({ - body, - error, - url: url_ - }); - return result; - }, - retryCount, - retryDelay, - timeout, - type: "http" - }, { - fetchOptions, - url: url_ - }); - }; -} - -// node_modules/viem/_esm/actions/wallet/deployContract.js -init_encodeDeployData(); -function deployContract(walletClient2, parameters) { - const { abi, args: args2, bytecode, ...request } = parameters; - const calldata = encodeDeployData({ abi, args: args2, bytecode }); - return sendTransaction(walletClient2, { - ...request, - ...request.authorizationList ? { to: null } : {}, - data: calldata - }); -} - -// node_modules/viem/_esm/actions/wallet/getAddresses.js -init_getAddress(); -async function getAddresses(client) { - if (client.account?.type === "local") - return [client.account.address]; - const addresses = await client.request({ method: "eth_accounts" }, { dedupe: true }); - return addresses.map((address) => checksumAddress(address)); -} - -// node_modules/viem/_esm/actions/wallet/getPermissions.js -async function getPermissions(client) { - const permissions = await client.request({ method: "wallet_getPermissions" }, { dedupe: true }); - return permissions; -} - -// node_modules/viem/_esm/actions/wallet/requestAddresses.js -init_getAddress(); -async function requestAddresses(client) { - const addresses = await client.request({ method: "eth_requestAccounts" }, { dedupe: true, retryCount: 0 }); - return addresses.map((address) => getAddress(address)); -} - -// node_modules/viem/_esm/actions/wallet/requestPermissions.js -async function requestPermissions(client, permissions) { - return client.request({ - method: "wallet_requestPermissions", - params: [permissions] - }, { retryCount: 0 }); -} - -// node_modules/viem/_esm/actions/wallet/signMessage.js -init_parseAccount(); -init_toHex(); -async function signMessage2(client, { account: account_ = client.account, message }) { - if (!account_) - throw new AccountNotFoundError({ - docsPath: "/docs/actions/wallet/signMessage" - }); - const account2 = parseAccount(account_); - if (account2.signMessage) - return account2.signMessage({ message }); - const message_ = (() => { - if (typeof message === "string") - return stringToHex(message); - if (message.raw instanceof Uint8Array) - return toHex(message.raw); - return message.raw; - })(); - return client.request({ - method: "personal_sign", - params: [message_, account2.address] - }, { retryCount: 0 }); -} - -// node_modules/viem/_esm/actions/wallet/signTransaction.js -init_parseAccount(); -init_toHex(); -init_transactionRequest(); -init_assertRequest(); -async function signTransaction2(client, parameters) { - const { account: account_ = client.account, chain = client.chain, ...transaction } = parameters; - if (!account_) - throw new AccountNotFoundError({ - docsPath: "/docs/actions/wallet/signTransaction" - }); - const account2 = parseAccount(account_); - assertRequest({ - account: account2, - ...parameters - }); - const chainId2 = await getAction(client, getChainId, "getChainId")({}); - if (chain !== null) - assertCurrentChain({ - currentChainId: chainId2, - chain - }); - const formatters = chain?.formatters || client.chain?.formatters; - const format = formatters?.transactionRequest?.format || formatTransactionRequest; - if (account2.signTransaction) - return account2.signTransaction({ - ...transaction, - chainId: chainId2 - }, { serializer: client.chain?.serializers?.transaction }); - return await client.request({ - method: "eth_signTransaction", - params: [ - { - ...format(transaction), - chainId: numberToHex(chainId2), - from: account2.address - } - ] - }, { retryCount: 0 }); -} - -// node_modules/viem/_esm/actions/wallet/signTypedData.js -init_parseAccount(); -async function signTypedData2(client, parameters) { - const { account: account_ = client.account, domain, message, primaryType } = parameters; - if (!account_) - throw new AccountNotFoundError({ - docsPath: "/docs/actions/wallet/signTypedData" - }); - const account2 = parseAccount(account_); - const types = { - EIP712Domain: getTypesForEIP712Domain({ domain }), - ...parameters.types - }; - validateTypedData({ domain, message, primaryType, types }); - if (account2.signTypedData) - return account2.signTypedData({ domain, message, primaryType, types }); - const typedData = serializeTypedData({ domain, message, primaryType, types }); - return client.request({ - method: "eth_signTypedData_v4", - params: [account2.address, typedData] - }, { retryCount: 0 }); -} - -// node_modules/viem/_esm/actions/wallet/switchChain.js -init_toHex(); -async function switchChain(client, { id }) { - await client.request({ - method: "wallet_switchEthereumChain", - params: [ - { - chainId: numberToHex(id) - } - ] - }, { retryCount: 0 }); -} - -// node_modules/viem/_esm/actions/wallet/watchAsset.js -async function watchAsset(client, params) { - const added = await client.request({ - method: "wallet_watchAsset", - params - }, { retryCount: 0 }); - return added; -} - -// node_modules/viem/_esm/clients/decorators/wallet.js -function walletActions(client) { - return { - addChain: (args2) => addChain(client, args2), - deployContract: (args2) => deployContract(client, args2), - getAddresses: () => getAddresses(client), - getChainId: () => getChainId(client), - getPermissions: () => getPermissions(client), - prepareTransactionRequest: (args2) => prepareTransactionRequest(client, args2), - requestAddresses: () => requestAddresses(client), - requestPermissions: (args2) => requestPermissions(client, args2), - sendRawTransaction: (args2) => sendRawTransaction(client, args2), - sendTransaction: (args2) => sendTransaction(client, args2), - signMessage: (args2) => signMessage2(client, args2), - signTransaction: (args2) => signTransaction2(client, args2), - signTypedData: (args2) => signTypedData2(client, args2), - switchChain: (args2) => switchChain(client, args2), - watchAsset: (args2) => watchAsset(client, args2), - writeContract: (args2) => writeContract(client, args2) - }; -} - -// node_modules/viem/_esm/clients/createWalletClient.js -function createWalletClient(parameters) { - const { key = "wallet", name = "Wallet Client", transport } = parameters; - const client = createClient({ - ...parameters, - key, - name, - transport, - type: "walletClient" - }); - return client.extend(walletActions); -} - -// node_modules/viem/_esm/index.js -init_encodeAbiParameters(); -init_toHex(); -init_keccak256(); -init_pad(); - -// src/sign-dca-intent.ts -var args = process.argv.slice(2); -if (args.length < 1) { - console.error("Usage: sign-dca-intent "); - process.exit(1); -} -var DCAIntentTypes = { - DCAIntent: [ - { name: "swapper", type: "address" }, - { name: "nonce", type: "uint256" }, - { name: "chainId", type: "uint256" }, - { name: "hookAddress", type: "address" }, - { name: "isExactIn", type: "bool" }, - { name: "inputToken", type: "address" }, - { name: "outputToken", type: "address" }, - { name: "cosigner", type: "address" }, - { name: "minPeriod", type: "uint256" }, - { name: "maxPeriod", type: "uint256" }, - { name: "minChunkSize", type: "uint256" }, - { name: "maxChunkSize", type: "uint256" }, - { name: "minPrice", type: "uint256" }, - { name: "deadline", type: "uint256" }, - { name: "outputAllocations", type: "OutputAllocation[]" }, - { name: "privateIntent", type: "PrivateIntent" } - ], - OutputAllocation: [ - { name: "recipient", type: "address" }, - { name: "basisPoints", type: "uint16" } - ], - PrivateIntent: [ - { name: "totalAmount", type: "uint256" }, - { name: "exactFrequency", type: "uint256" }, - { name: "numChunks", type: "uint256" }, - { name: "salt", type: "bytes32" }, - { name: "oracleFeeds", type: "FeedInfo[]" } - ], - FeedInfo: [ - { name: "feedTemplate", type: "FeedTemplate" }, - { name: "feedAddress", type: "address" }, - { name: "feedType", type: "string" } - ], - FeedTemplate: [ - { name: "name", type: "string" }, - { name: "expression", type: "string" }, - { name: "parameters", type: "string[]" }, - { name: "secrets", type: "string[]" }, - { name: "retryCount", type: "uint256" } - ] -}; -var jsonInput = JSON.parse(args[0]); -var { privateKey, verifyingContract, chainId, intent } = jsonInput; -var account = privateKeyToAccount(pad(toHex(BigInt(privateKey)))); -var walletClient = createWalletClient({ - account, - transport: http("http://127.0.0.1:8545") -}); -async function signDCAIntent() { - try { - const domain = { - name: "DCAHook", - version: "1", - chainId, - verifyingContract - }; - const signature = await walletClient.signTypedData({ - account, - domain, - types: DCAIntentTypes, - primaryType: "DCAIntent", - message: intent - }); - const structHash = keccak256( - encodeAbiParameters( - [ - { type: "bytes32" }, - // typehash - { type: "address" }, - // swapper - { type: "uint256" }, - // nonce - { type: "uint256" }, - // chainId - { type: "address" }, - // hookAddress - { type: "bool" }, - // isExactIn - { type: "address" }, - // inputToken - { type: "address" }, - // outputToken - { type: "address" }, - // cosigner - { type: "uint256" }, - // minPeriod - { type: "uint256" }, - // maxPeriod - { type: "uint256" }, - // minChunkSize - { type: "uint256" }, - // maxChunkSize - { type: "uint256" }, - // minPrice - { type: "uint256" }, - // deadline - { type: "bytes32" }, - // outputAllocations hash - { type: "bytes32" } - // privateIntent hash - ], - [ - keccak256(toHex("DCAIntent(address swapper,uint256 nonce,uint256 chainId,address hookAddress,bool isExactIn,address inputToken,address outputToken,address cosigner,uint256 minPeriod,uint256 maxPeriod,uint256 minChunkSize,uint256 maxChunkSize,uint256 minPrice,uint256 deadline,OutputAllocation[] outputAllocations,PrivateIntent privateIntent)FeedInfo(FeedTemplate feedTemplate,address feedAddress,string feedType)FeedTemplate(string name,string expression,string[] parameters,string[] secrets,uint256 retryCount)OutputAllocation(address recipient,uint16 basisPoints)PrivateIntent(uint256 totalAmount,uint256 exactFrequency,uint256 numChunks,bytes32 salt,FeedInfo[] oracleFeeds)")), - intent.swapper, - intent.nonce, - intent.chainId, - intent.hookAddress, - intent.isExactIn, - intent.inputToken, - intent.outputToken, - intent.cosigner, - intent.minPeriod, - intent.maxPeriod, - intent.minChunkSize, - intent.maxChunkSize, - intent.minPrice, - intent.deadline, - hashOutputAllocations(intent.outputAllocations), - hashPrivateIntent(intent.privateIntent) - ] - ) - ); - const result = JSON.stringify({ - signature, - structHash - }); - process.stdout.write(result); - process.exit(0); - } catch (error) { - console.error("Error signing DCA intent:", error); - process.exit(1); - } -} -function hashOutputAllocations(allocations) { - const hashes = allocations.map( - (alloc) => keccak256( - encodeAbiParameters( - [ - { type: "bytes32" }, - { type: "address" }, - { type: "uint16" } - ], - [ - keccak256(toHex("OutputAllocation(address recipient,uint16 basisPoints)")), - alloc.recipient, - alloc.basisPoints - ] - ) - ) - ); - return keccak256(encodeAbiParameters( - hashes.map(() => ({ type: "bytes32" })), - hashes - )); -} -function hashStringArray(arr) { - const hashes = arr.map((str) => keccak256(toHex(str))); - return keccak256(encodeAbiParameters( - hashes.map(() => ({ type: "bytes32" })), - hashes - )); -} -function hashFeedTemplate(template) { - return keccak256( - encodeAbiParameters( - [ - { type: "bytes32" }, - { type: "bytes32" }, - { type: "bytes32" }, - { type: "bytes32" }, - { type: "bytes32" }, - { type: "uint256" } - ], - [ - keccak256(toHex("FeedTemplate(string name,string expression,string[] parameters,string[] secrets,uint256 retryCount)")), - keccak256(toHex(template.name)), - keccak256(toHex(template.expression)), - hashStringArray(template.parameters), - hashStringArray(template.secrets), - BigInt(template.retryCount) - ] - ) - ); -} -function hashPrivateIntent(privateIntent) { - const feedHashes = privateIntent.oracleFeeds.map((feed) => { - const templateHash = hashFeedTemplate(feed.feedTemplate); - return keccak256( - encodeAbiParameters( - [ - { type: "bytes32" }, - { type: "bytes32" }, - { type: "address" }, - { type: "bytes32" } - ], - [ - keccak256(toHex("FeedInfo(FeedTemplate feedTemplate,address feedAddress,string feedType)FeedTemplate(string name,string expression,string[] parameters,string[] secrets,uint256 retryCount)")), - templateHash, - feed.feedAddress, - keccak256(toHex(feed.feedType)) - ] - ) - ); - }); - const feedsHash = keccak256(encodeAbiParameters( - feedHashes.map(() => ({ type: "bytes32" })), - feedHashes - )); - return keccak256( - encodeAbiParameters( - [ - { type: "bytes32" }, - { type: "uint256" }, - { type: "uint256" }, - { type: "uint256" }, - { type: "bytes32" }, - { type: "bytes32" } - ], - [ - keccak256(toHex("PrivateIntent(uint256 totalAmount,uint256 exactFrequency,uint256 numChunks,bytes32 salt,FeedInfo[] oracleFeeds)FeedInfo(FeedTemplate feedTemplate,address feedAddress,string feedType)FeedTemplate(string name,string expression,string[] parameters,string[] secrets,uint256 retryCount)")), - privateIntent.totalAmount, - privateIntent.exactFrequency, - privateIntent.numChunks, - privateIntent.salt, - feedsHash - ] - ) - ); -} -signDCAIntent().catch(console.error); -/*! Bundled license information: - -@noble/hashes/esm/utils.js: - (*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) *) - -@noble/curves/esm/abstract/utils.js: - (*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) *) - -@noble/curves/esm/abstract/modular.js: - (*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) *) - -@noble/curves/esm/abstract/curve.js: - (*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) *) - -@noble/curves/esm/abstract/weierstrass.js: - (*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) *) - -@noble/curves/esm/_shortw_utils.js: - (*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) *) - -@noble/curves/esm/secp256k1.js: - (*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) *) -*/ diff --git a/test/v4/hooks/dca/js-scripts/package-lock.json b/test/v4/hooks/dca/js-scripts/package-lock.json deleted file mode 100644 index 0da54ae2..00000000 --- a/test/v4/hooks/dca/js-scripts/package-lock.json +++ /dev/null @@ -1,860 +0,0 @@ -{ - "name": "dca-js-scripts", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "dca-js-scripts", - "version": "1.0.0", - "dependencies": { - "viem": "2.23.15" - }, - "devDependencies": { - "@types/node": "^20.11.24", - "esbuild": "^0.21.3", - "ts-node": "^10.9.2", - "typescript": "^5.3.3" - } - }, - "node_modules/@adraffy/ens-normalize": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.1.tgz", - "integrity": "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==", - "license": "MIT" - }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@noble/curves": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.8.1.tgz", - "integrity": "sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ==", - "license": "MIT", - "dependencies": { - "@noble/hashes": "1.7.1" - }, - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@noble/hashes": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.1.tgz", - "integrity": "sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==", - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@scure/base": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", - "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", - "license": "MIT", - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@scure/bip32": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.6.2.tgz", - "integrity": "sha512-t96EPDMbtGgtb7onKKqxRLfE5g05k7uHnHRM2xdE6BP/ZmxaLtPek4J4KfVn/90IQNrU1IOAqMgiDtUdtbe3nw==", - "license": "MIT", - "dependencies": { - "@noble/curves": "~1.8.1", - "@noble/hashes": "~1.7.1", - "@scure/base": "~1.2.2" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@scure/bip39": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.5.4.tgz", - "integrity": "sha512-TFM4ni0vKvCfBpohoh+/lY05i9gRbSwXWngAsF4CABQxoaOHijxuaZ2R6cStDQ5CHtHO9aGJTr4ksVJASRRyMA==", - "license": "MIT", - "dependencies": { - "@noble/hashes": "~1.7.1", - "@scure/base": "~1.2.4" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@tsconfig/node10": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", - "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true, - "license": "MIT" - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", - "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "20.19.24", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.24.tgz", - "integrity": "sha512-FE5u0ezmi6y9OZEzlJfg37mqqf6ZDSF2V/NLjUyGrR9uTZ7Sb9F7bLNZ03S4XVUNRWGA7Ck4c1kK+YnuWjl+DA==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~6.21.0" - } - }, - "node_modules/abitype": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.0.8.tgz", - "integrity": "sha512-ZeiI6h3GnW06uYDLx0etQtX/p8E24UaHHBj57RSjK7YBFe7iuVn07EDpOeP451D06sF27VOz9JJPlIKJmXgkEg==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/wevm" - }, - "peerDependencies": { - "typescript": ">=5.0.4", - "zod": "^3 >=3.22.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - }, - "zod": { - "optional": true - } - } - }, - "node_modules/acorn": { - "version": "8.15.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", - "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "acorn": "^8.11.0" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true, - "license": "MIT" - }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" - } - }, - "node_modules/eventemitter3": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", - "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", - "license": "MIT" - }, - "node_modules/isows": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.6.tgz", - "integrity": "sha512-lPHCayd40oW98/I0uvgaHKWCSvkzY27LjWLbtzOm64yQ+G3Q5npjjbdppU65iZXkK1Zt+kH9pfegli0AYfwYYw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wevm" - } - ], - "license": "MIT", - "peerDependencies": { - "ws": "*" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true, - "license": "ISC" - }, - "node_modules/ox": { - "version": "0.6.9", - "resolved": "https://registry.npmjs.org/ox/-/ox-0.6.9.tgz", - "integrity": "sha512-wi5ShvzE4eOcTwQVsIPdFr+8ycyX+5le/96iAJutaZAvCes1J0+RvpEPg5QDPDiaR0XQQAvZVl7AwqQcINuUug==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wevm" - } - ], - "license": "MIT", - "dependencies": { - "@adraffy/ens-normalize": "^1.10.1", - "@noble/curves": "^1.6.0", - "@noble/hashes": "^1.5.0", - "@scure/bip32": "^1.5.0", - "@scure/bip39": "^1.4.0", - "abitype": "^1.0.6", - "eventemitter3": "5.0.1" - }, - "peerDependencies": { - "typescript": ">=5.4.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/ts-node": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", - "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/typescript": { - "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "devOptional": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true, - "license": "MIT" - }, - "node_modules/viem": { - "version": "2.23.15", - "resolved": "https://registry.npmjs.org/viem/-/viem-2.23.15.tgz", - "integrity": "sha512-2t9lROkSzj/ciEZ08NqAHZ6c+J1wKLwJ4qpUxcHdVHcLBt6GfO9+ycuZycTT05ckfJ6TbwnMXMa3bMonvhtUMw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wevm" - } - ], - "license": "MIT", - "dependencies": { - "@noble/curves": "1.8.1", - "@noble/hashes": "1.7.1", - "@scure/bip32": "1.6.2", - "@scure/bip39": "1.5.4", - "abitype": "1.0.8", - "isows": "1.0.6", - "ox": "0.6.9", - "ws": "8.18.1" - }, - "peerDependencies": { - "typescript": ">=5.0.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - } - } -} diff --git a/test/v4/hooks/dca/js-scripts/package.json b/test/v4/hooks/dca/js-scripts/package.json deleted file mode 100644 index abadb703..00000000 --- a/test/v4/hooks/dca/js-scripts/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "dca-js-scripts", - "version": "1.0.0", - "description": "JavaScript scripts for DCA FFI with Foundry", - "scripts": { - "build": "npm i && node build.js && rm -rf node_modules", - "sign-dca-intent": "node dist/sign-dca-intent.js" - }, - "dependencies": { - "viem": "2.23.15" - }, - "devDependencies": { - "typescript": "^5.3.3", - "ts-node": "^10.9.2", - "@types/node": "^20.11.24", - "esbuild": "^0.21.3" - } -} diff --git a/test/v4/hooks/dca/js-scripts/src/sign-dca-intent.ts b/test/v4/hooks/dca/js-scripts/src/sign-dca-intent.ts deleted file mode 100644 index 841ac639..00000000 --- a/test/v4/hooks/dca/js-scripts/src/sign-dca-intent.ts +++ /dev/null @@ -1,309 +0,0 @@ -#!/usr/bin/env node -import { - privateKeyToAccount, -} from 'viem/accounts' -import { - createWalletClient, - http, - type Address, - toHex, - pad, - keccak256, - encodeAbiParameters, -} from 'viem' - -// Read command line arguments -const args = process.argv.slice(2); -if (args.length < 1) { - console.error("Usage: sign-dca-intent "); - process.exit(1); -} - -// Parse the JSON input -interface FeedTemplate { - name: string; - expression: string; - parameters: string[]; - secrets: string[]; - retryCount: number; -} - -interface FeedInfo { - feedTemplate: FeedTemplate; - feedAddress: Address; - feedType: string; -} - -interface OutputAllocation { - recipient: Address; - basisPoints: number; -} - -interface PrivateIntent { - totalAmount: bigint; - exactFrequency: bigint; - numChunks: bigint; - salt: `0x${string}`; - oracleFeeds: FeedInfo[]; -} - -interface DCAIntent { - swapper: Address; - nonce: bigint; - chainId: bigint; - hookAddress: Address; - isExactIn: boolean; - inputToken: Address; - outputToken: Address; - cosigner: Address; - minPeriod: bigint; - maxPeriod: bigint; - minChunkSize: bigint; - maxChunkSize: bigint; - minPrice: bigint; - deadline: bigint; - outputAllocations: OutputAllocation[]; - privateIntent: PrivateIntent; -} - -interface SignDCAIntentInput { - privateKey: string; - verifyingContract: Address; - chainId: number; - intent: DCAIntent; -} - -// Define the EIP-712 types -const DCAIntentTypes = { - DCAIntent: [ - { name: 'swapper', type: 'address' }, - { name: 'nonce', type: 'uint256' }, - { name: 'chainId', type: 'uint256' }, - { name: 'hookAddress', type: 'address' }, - { name: 'isExactIn', type: 'bool' }, - { name: 'inputToken', type: 'address' }, - { name: 'outputToken', type: 'address' }, - { name: 'cosigner', type: 'address' }, - { name: 'minPeriod', type: 'uint256' }, - { name: 'maxPeriod', type: 'uint256' }, - { name: 'minChunkSize', type: 'uint256' }, - { name: 'maxChunkSize', type: 'uint256' }, - { name: 'minPrice', type: 'uint256' }, - { name: 'deadline', type: 'uint256' }, - { name: 'outputAllocations', type: 'OutputAllocation[]' }, - { name: 'privateIntent', type: 'PrivateIntent' }, - ], - OutputAllocation: [ - { name: 'recipient', type: 'address' }, - { name: 'basisPoints', type: 'uint16' }, - ], - PrivateIntent: [ - { name: 'totalAmount', type: 'uint256' }, - { name: 'exactFrequency', type: 'uint256' }, - { name: 'numChunks', type: 'uint256' }, - { name: 'salt', type: 'bytes32' }, - { name: 'oracleFeeds', type: 'FeedInfo[]' }, - ], - FeedInfo: [ - { name: 'feedTemplate', type: 'FeedTemplate' }, - { name: 'feedAddress', type: 'address' }, - { name: 'feedType', type: 'string' }, - ], - FeedTemplate: [ - { name: 'name', type: 'string' }, - { name: 'expression', type: 'string' }, - { name: 'parameters', type: 'string[]' }, - { name: 'secrets', type: 'string[]' }, - { name: 'retryCount', type: 'uint256' }, - ], -} as const; - -const jsonInput = JSON.parse(args[0]) as SignDCAIntentInput; -const { privateKey, verifyingContract, chainId, intent } = jsonInput; - -const account = privateKeyToAccount(pad(toHex(BigInt(privateKey)))); - -const walletClient = createWalletClient({ - account, - transport: http('http://127.0.0.1:8545') -}) - -async function signDCAIntent(): Promise { - try { - const domain = { - name: 'DCAHook', - version: '1', - chainId: chainId, - verifyingContract: verifyingContract, - } - - const signature = await walletClient.signTypedData({ - account, - domain, - types: DCAIntentTypes, - primaryType: 'DCAIntent', - message: intent, - }) - - // Also compute and return the hash for verification - const structHash = keccak256( - encodeAbiParameters( - [ - { type: 'bytes32' }, // typehash - { type: 'address' }, // swapper - { type: 'uint256' }, // nonce - { type: 'uint256' }, // chainId - { type: 'address' }, // hookAddress - { type: 'bool' }, // isExactIn - { type: 'address' }, // inputToken - { type: 'address' }, // outputToken - { type: 'address' }, // cosigner - { type: 'uint256' }, // minPeriod - { type: 'uint256' }, // maxPeriod - { type: 'uint256' }, // minChunkSize - { type: 'uint256' }, // maxChunkSize - { type: 'uint256' }, // minPrice - { type: 'uint256' }, // deadline - { type: 'bytes32' }, // outputAllocations hash - { type: 'bytes32' }, // privateIntent hash - ], - [ - keccak256(toHex('DCAIntent(address swapper,uint256 nonce,uint256 chainId,address hookAddress,bool isExactIn,address inputToken,address outputToken,address cosigner,uint256 minPeriod,uint256 maxPeriod,uint256 minChunkSize,uint256 maxChunkSize,uint256 minPrice,uint256 deadline,OutputAllocation[] outputAllocations,PrivateIntent privateIntent)FeedInfo(FeedTemplate feedTemplate,address feedAddress,string feedType)FeedTemplate(string name,string expression,string[] parameters,string[] secrets,uint256 retryCount)OutputAllocation(address recipient,uint16 basisPoints)PrivateIntent(uint256 totalAmount,uint256 exactFrequency,uint256 numChunks,bytes32 salt,FeedInfo[] oracleFeeds)')), - intent.swapper, - intent.nonce, - intent.chainId, - intent.hookAddress, - intent.isExactIn, - intent.inputToken, - intent.outputToken, - intent.cosigner, - intent.minPeriod, - intent.maxPeriod, - intent.minChunkSize, - intent.maxChunkSize, - intent.minPrice, - intent.deadline, - hashOutputAllocations(intent.outputAllocations), - hashPrivateIntent(intent.privateIntent), - ] - ) - ); - - // Return both signature and hash as JSON - const result = JSON.stringify({ - signature, - structHash, - }); - - process.stdout.write(result); - process.exit(0); - } catch (error) { - console.error('Error signing DCA intent:', error); - process.exit(1); - } -} - -function hashOutputAllocations(allocations: OutputAllocation[]): `0x${string}` { - const hashes = allocations.map(alloc => - keccak256( - encodeAbiParameters( - [ - { type: 'bytes32' }, - { type: 'address' }, - { type: 'uint16' }, - ], - [ - keccak256(toHex('OutputAllocation(address recipient,uint16 basisPoints)')), - alloc.recipient, - alloc.basisPoints, - ] - ) - ) - ); - - return keccak256(encodeAbiParameters( - hashes.map(() => ({ type: 'bytes32' })), - hashes - )); -} - -function hashStringArray(arr: string[]): `0x${string}` { - const hashes = arr.map(str => keccak256(toHex(str))); - return keccak256(encodeAbiParameters( - hashes.map(() => ({ type: 'bytes32' })), - hashes - )); -} - -function hashFeedTemplate(template: FeedTemplate): `0x${string}` { - return keccak256( - encodeAbiParameters( - [ - { type: 'bytes32' }, - { type: 'bytes32' }, - { type: 'bytes32' }, - { type: 'bytes32' }, - { type: 'bytes32' }, - { type: 'uint256' }, - ], - [ - keccak256(toHex('FeedTemplate(string name,string expression,string[] parameters,string[] secrets,uint256 retryCount)')), - keccak256(toHex(template.name)), - keccak256(toHex(template.expression)), - hashStringArray(template.parameters), - hashStringArray(template.secrets), - BigInt(template.retryCount), - ] - ) - ); -} - -function hashPrivateIntent(privateIntent: PrivateIntent): `0x${string}` { - const feedHashes = privateIntent.oracleFeeds.map(feed => { - const templateHash = hashFeedTemplate(feed.feedTemplate); - return keccak256( - encodeAbiParameters( - [ - { type: 'bytes32' }, - { type: 'bytes32' }, - { type: 'address' }, - { type: 'bytes32' }, - ], - [ - keccak256(toHex('FeedInfo(FeedTemplate feedTemplate,address feedAddress,string feedType)FeedTemplate(string name,string expression,string[] parameters,string[] secrets,uint256 retryCount)')), - templateHash, - feed.feedAddress, - keccak256(toHex(feed.feedType)), - ] - ) - ); - }); - - const feedsHash = keccak256(encodeAbiParameters( - feedHashes.map(() => ({ type: 'bytes32' })), - feedHashes - )); - - return keccak256( - encodeAbiParameters( - [ - { type: 'bytes32' }, - { type: 'uint256' }, - { type: 'uint256' }, - { type: 'uint256' }, - { type: 'bytes32' }, - { type: 'bytes32' }, - ], - [ - keccak256(toHex('PrivateIntent(uint256 totalAmount,uint256 exactFrequency,uint256 numChunks,bytes32 salt,FeedInfo[] oracleFeeds)FeedInfo(FeedTemplate feedTemplate,address feedAddress,string feedType)FeedTemplate(string name,string expression,string[] parameters,string[] secrets,uint256 retryCount)')), - privateIntent.totalAmount, - privateIntent.exactFrequency, - privateIntent.numChunks, - privateIntent.salt, - feedsHash, - ] - ) - ); -} - -signDCAIntent().catch(console.error); diff --git a/test/v4/lens/OrderQuoterV4.t.sol b/test/v4/lens/OrderQuoterV4.t.sol deleted file mode 100644 index 691b322d..00000000 --- a/test/v4/lens/OrderQuoterV4.t.sol +++ /dev/null @@ -1,332 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {Test} from "forge-std/Test.sol"; -import {IPermit2} from "permit2/src/interfaces/IPermit2.sol"; -import {DeployPermit2} from "../../util/DeployPermit2.sol"; -import {PermitSignature} from "../../util/PermitSignature.sol"; -import {OrderInfo, ResolvedOrder} from "../../../src/v4/base/ReactorStructs.sol"; -import {Reactor} from "../../../src/v4/Reactor.sol"; -import {HybridAuctionResolver} from "../../../src/v4/resolvers/HybridAuctionResolver.sol"; -import { - HybridOrder, - HybridInput, - HybridOutput, - HybridCosignerData, - HybridOrderLib -} from "../../../src/v4/lib/HybridOrderLib.sol"; -import {OrderInfoBuilder} from "../util/OrderInfoBuilder.sol"; -import {MockERC20} from "../../util/mock/MockERC20.sol"; -import {TokenTransferHook} from "../../../src/v4/hooks/TokenTransferHook.sol"; -import {OrderQuoterV4} from "../../../src/v4/lens/OrderQuoterV4.sol"; -import {IReactor} from "../../../src/v4/interfaces/IReactor.sol"; - -contract OrderQuoterV4Test is Test, PermitSignature, DeployPermit2 { - using OrderInfoBuilder for OrderInfo; - using HybridOrderLib for HybridOrder; - - uint256 constant ONE = 10 ** 18; - uint256 constant NEUTRAL_SCALING_FACTOR = 1e18; - address internal constant PROTOCOL_FEE_OWNER = address(1); - - MockERC20 tokenIn; - MockERC20 tokenOut; - IPermit2 permit2; - TokenTransferHook tokenTransferHook; - Reactor reactor; - HybridAuctionResolver resolver; - OrderQuoterV4 quoter; - uint256 swapperPrivateKey; - address swapper; - - function setUp() public { - tokenIn = new MockERC20("Input", "IN", 18); - tokenOut = new MockERC20("Output", "OUT", 18); - swapperPrivateKey = 0x12341234; - swapper = vm.addr(swapperPrivateKey); - permit2 = IPermit2(deployPermit2()); - - reactor = new Reactor(PROTOCOL_FEE_OWNER, permit2); - resolver = new HybridAuctionResolver(); - tokenTransferHook = new TokenTransferHook(permit2, reactor); - quoter = new OrderQuoterV4(); - - // Provide tokens for tests - tokenIn.mint(address(swapper), ONE * 1000); - - // Approve permit2 for swapper - vm.prank(swapper); - tokenIn.approve(address(permit2), type(uint256).max); - } - - /// @dev Create and sign a HybridOrder, returning the encoded order bytes and signature - function createAndSignOrder(HybridOrder memory order) - internal - view - returns (bytes memory encodedOrder, bytes memory sig) - { - // Sign the order with swapper's key - sig = signOrder(swapperPrivateKey, address(permit2), order); - - // Encode the order data for the resolver - bytes memory orderData = abi.encode(order); - - // Wrap with resolver address - encodedOrder = abi.encode(address(resolver), orderData); - } - - /// @dev Create a basic HybridOrder for testing - function createBasicOrder(uint256 inputAmount, uint256 outputAmount, uint256 nonce) - internal - view - returns (HybridOrder memory) - { - HybridOutput[] memory outputs = new HybridOutput[](1); - outputs[0] = HybridOutput({token: address(tokenOut), minAmount: outputAmount, recipient: swapper}); - - return HybridOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 1000) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(resolver).withNonce(nonce), - cosigner: address(0), - input: HybridInput(tokenIn, inputAmount), - outputs: outputs, - auctionStartBlock: block.number, - baselinePriorityFee: 0, - scalingFactor: NEUTRAL_SCALING_FACTOR, - priceCurve: new uint256[](0), - cosignerData: HybridCosignerData({ - auctionTargetBlock: 0, - supplementalPriceCurve: new uint256[](0), - exclusiveFiller: address(0), - exclusivityOverrideBps: 0, - exclusivityEndBlock: 0 - }), - cosignature: "" - }); - } - - // ============================================================================ - // Basic Quote Tests - // ============================================================================ - - function test_quoteHybridOrder() public { - uint256 inputAmount = 1 ether; - uint256 outputAmount = 2 ether; - - HybridOrder memory order = createBasicOrder(inputAmount, outputAmount, 0); - (bytes memory encodedOrder, bytes memory sig) = createAndSignOrder(order); - - ResolvedOrder memory quote = quoter.quote(IReactor(address(reactor)), encodedOrder, sig); - - // Verify the resolved order - assertEq(address(quote.input.token), address(tokenIn)); - assertEq(quote.input.amount, inputAmount); - assertEq(quote.input.maxAmount, inputAmount); - assertEq(quote.outputs.length, 1); - assertEq(quote.outputs[0].token, address(tokenOut)); - assertEq(quote.outputs[0].amount, outputAmount); - assertEq(quote.outputs[0].recipient, swapper); - assertEq(quote.info.swapper, swapper); - assertEq(quote.auctionResolver, address(resolver)); - } - - function test_quoteHybridOrder_multipleOutputs() public { - uint256 inputAmount = 1 ether; - uint256 outputAmount1 = 1 ether; - uint256 outputAmount2 = 0.5 ether; - address recipient2 = makeAddr("recipient2"); - - HybridOutput[] memory outputs = new HybridOutput[](2); - outputs[0] = HybridOutput({token: address(tokenOut), minAmount: outputAmount1, recipient: swapper}); - outputs[1] = HybridOutput({token: address(tokenOut), minAmount: outputAmount2, recipient: recipient2}); - - HybridOrder memory order = HybridOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 1000) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(resolver).withNonce(0), - cosigner: address(0), - input: HybridInput(tokenIn, inputAmount), - outputs: outputs, - auctionStartBlock: block.number, - baselinePriorityFee: 0, - scalingFactor: NEUTRAL_SCALING_FACTOR, - priceCurve: new uint256[](0), - cosignerData: HybridCosignerData({ - auctionTargetBlock: 0, - supplementalPriceCurve: new uint256[](0), - exclusiveFiller: address(0), - exclusivityOverrideBps: 0, - exclusivityEndBlock: 0 - }), - cosignature: "" - }); - - (bytes memory encodedOrder, bytes memory sig) = createAndSignOrder(order); - ResolvedOrder memory quote = quoter.quote(IReactor(address(reactor)), encodedOrder, sig); - - assertEq(quote.outputs.length, 2); - assertEq(quote.outputs[0].amount, outputAmount1); - assertEq(quote.outputs[0].recipient, swapper); - assertEq(quote.outputs[1].amount, outputAmount2); - assertEq(quote.outputs[1].recipient, recipient2); - } - - // ============================================================================ - // getAuctionResolver Tests - // ============================================================================ - - function test_getAuctionResolver() public view { - HybridOrder memory order = createBasicOrder(1 ether, 1 ether, 0); - (bytes memory encodedOrder,) = createAndSignOrder(order); - - address extractedResolver = quoter.getAuctionResolver(encodedOrder); - assertEq(extractedResolver, address(resolver)); - } - - function test_getAuctionResolver_differentResolver() public { - HybridAuctionResolver otherResolver = new HybridAuctionResolver(); - - HybridOutput[] memory outputs = new HybridOutput[](1); - outputs[0] = HybridOutput({token: address(tokenOut), minAmount: 1 ether, recipient: swapper}); - - HybridOrder memory order = HybridOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 1000) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(otherResolver).withNonce(0), - cosigner: address(0), - input: HybridInput(tokenIn, 1 ether), - outputs: outputs, - auctionStartBlock: block.number, - baselinePriorityFee: 0, - scalingFactor: NEUTRAL_SCALING_FACTOR, - priceCurve: new uint256[](0), - cosignerData: HybridCosignerData({ - auctionTargetBlock: 0, - supplementalPriceCurve: new uint256[](0), - exclusiveFiller: address(0), - exclusivityOverrideBps: 0, - exclusivityEndBlock: 0 - }), - cosignature: "" - }); - - bytes memory orderData = abi.encode(order); - bytes memory encodedOrder = abi.encode(address(otherResolver), orderData); - - address extractedResolver = quoter.getAuctionResolver(encodedOrder); - assertEq(extractedResolver, address(otherResolver)); - } - - // ============================================================================ - // Error Handling Tests - // ============================================================================ - - function test_quote_expiredOrder() public { - HybridOutput[] memory outputs = new HybridOutput[](1); - outputs[0] = HybridOutput({token: address(tokenOut), minAmount: 1 ether, recipient: swapper}); - - // Create order with deadline in the past - HybridOrder memory order = HybridOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp - 1) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(resolver).withNonce(0), - cosigner: address(0), - input: HybridInput(tokenIn, 1 ether), - outputs: outputs, - auctionStartBlock: block.number, - baselinePriorityFee: 0, - scalingFactor: NEUTRAL_SCALING_FACTOR, - priceCurve: new uint256[](0), - cosignerData: HybridCosignerData({ - auctionTargetBlock: 0, - supplementalPriceCurve: new uint256[](0), - exclusiveFiller: address(0), - exclusivityOverrideBps: 0, - exclusivityEndBlock: 0 - }), - cosignature: "" - }); - - (bytes memory encodedOrder, bytes memory sig) = createAndSignOrder(order); - - // Quote should revert with DeadlinePassed - vm.expectRevert(IReactor.DeadlinePassed.selector); - quoter.quote(IReactor(address(reactor)), encodedOrder, sig); - } - - function test_quote_invalidReactor() public { - // Deploy a different reactor - Reactor otherReactor = new Reactor(PROTOCOL_FEE_OWNER, permit2); - - HybridOrder memory order = createBasicOrder(1 ether, 1 ether, 0); - (bytes memory encodedOrder, bytes memory sig) = createAndSignOrder(order); - - // Quote using wrong reactor should revert - vm.expectRevert(IReactor.InvalidReactor.selector); - quoter.quote(IReactor(address(otherReactor)), encodedOrder, sig); - } - - function test_quote_emptyAuctionResolver() public { - // Create order with zero address resolver - encode manually - bytes memory orderData = abi.encode(createBasicOrder(1 ether, 1 ether, 0)); - bytes memory encodedOrder = abi.encode(address(0), orderData); - bytes memory sig = hex""; // Dummy sig, won't get that far - - vm.expectRevert(IReactor.EmptyAuctionResolver.selector); - quoter.quote(IReactor(address(reactor)), encodedOrder, sig); - } - - // ============================================================================ - // Callback Tests - // ============================================================================ - - function test_reactorCallback_tooManyOrders() public { - ResolvedOrder[] memory orders = new ResolvedOrder[](2); - - vm.expectRevert(OrderQuoterV4.OrdersLengthIncorrect.selector); - quoter.reactorCallback(orders, bytes("")); - } - - function test_reactorCallback_zeroOrders() public { - ResolvedOrder[] memory orders = new ResolvedOrder[](0); - - vm.expectRevert(OrderQuoterV4.OrdersLengthIncorrect.selector); - quoter.reactorCallback(orders, bytes("")); - } - - // ============================================================================ - // Witness Type String Tests - // ============================================================================ - - function test_quote_witnessTypeString() public { - HybridOrder memory order = createBasicOrder(1 ether, 1 ether, 0); - (bytes memory encodedOrder, bytes memory sig) = createAndSignOrder(order); - - ResolvedOrder memory quote = quoter.quote(IReactor(address(reactor)), encodedOrder, sig); - - // Check that witness type string is set correctly - assertEq(quote.witnessTypeString, HybridOrderLib.PERMIT2_ORDER_TYPE); - } - - // ============================================================================ - // Order Hash Tests - // ============================================================================ - - function test_quote_orderHash() public { - HybridOrder memory order = createBasicOrder(1 ether, 1 ether, 0); - bytes32 expectedHash = order.hash(); - - (bytes memory encodedOrder, bytes memory sig) = createAndSignOrder(order); - ResolvedOrder memory quote = quoter.quote(IReactor(address(reactor)), encodedOrder, sig); - - assertEq(quote.hash, expectedHash); - } - - // ============================================================================ - // ETH Receive Tests - // ============================================================================ - - function test_receiveETH() public { - // Quoter should be able to receive ETH - (bool success,) = address(quoter).call{value: 1 ether}(""); - assertTrue(success); - assertEq(address(quoter).balance, 1 ether); - } -} diff --git a/test/v4/resolvers/HybridAuctionResolver.t.sol b/test/v4/resolvers/HybridAuctionResolver.t.sol deleted file mode 100644 index 1f144eef..00000000 --- a/test/v4/resolvers/HybridAuctionResolver.t.sol +++ /dev/null @@ -1,2621 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {Test} from "forge-std/Test.sol"; -import {IPermit2} from "permit2/src/interfaces/IPermit2.sol"; -import {DeployPermit2} from "../../util/DeployPermit2.sol"; -import {PermitSignature} from "../../util/PermitSignature.sol"; -import {OrderInfo} from "../../../src/v4/base/ReactorStructs.sol"; -import {SignedOrder, InputToken, OutputToken} from "../../../src/base/ReactorStructs.sol"; -import {ResolvedOrder} from "../../../src/v4/base/ReactorStructs.sol"; -import {ReactorEvents} from "../../../src/base/ReactorEvents.sol"; -import {Reactor} from "../../../src/v4/Reactor.sol"; -import {HybridAuctionResolver} from "../../../src/v4/resolvers/HybridAuctionResolver.sol"; -import { - HybridOrder, - HybridInput, - HybridOutput, - HybridCosignerData, - HybridOrderLib -} from "../../../src/v4/lib/HybridOrderLib.sol"; -import {CosignerLib} from "../../../src/lib/CosignerLib.sol"; -import {ExclusivityLib} from "../../../src/v4/lib/ExclusivityLib.sol"; -import {OrderInfoBuilder} from "../util/OrderInfoBuilder.sol"; -import {MockERC20} from "../../util/mock/MockERC20.sol"; -import {MockFillContract} from "../util/mock/MockFillContract.sol"; -import {TokenTransferHook} from "../../../src/v4/hooks/TokenTransferHook.sol"; -import {PriceCurveLib, PriceCurveElement} from "tribunal/src/lib/PriceCurveLib.sol"; -import {FixedPointMathLib} from "solady/utils/FixedPointMathLib.sol"; - -/** - * @title HybridAuctionResolverTest - * @notice Comprehensive test suite for HybridAuctionResolver covering all PriceCurveLib edge cases - * @dev Migrated from Tribunal's PriceCurveDocumentationTests, PriceCurveEdgeCasesTest, and MultipleZeroDurationTest - */ -contract HybridAuctionResolverTest is ReactorEvents, Test, PermitSignature, DeployPermit2 { - using OrderInfoBuilder for OrderInfo; - using HybridOrderLib for HybridOrder; - using PriceCurveLib for uint256[]; - using FixedPointMathLib for uint256; - - uint256 constant ONE = 10 ** 18; - uint256 constant COSIGNER_PRIVATE_KEY = 0x99999999; - uint256 constant NEUTRAL_SCALING_FACTOR = 1e18; - address internal constant PROTOCOL_FEE_OWNER = address(1); - address internal constant EXCLUSIVE_FILLER = address(0x1111111111111111111111111111111111111111); - - MockERC20 tokenIn; - MockERC20 tokenOut; - MockERC20 tokenOut2; - MockFillContract fillContract; - IPermit2 permit2; - TokenTransferHook tokenTransferHook; - Reactor reactor; - HybridAuctionResolver resolver; - uint256 swapperPrivateKey; - address swapper; - address cosigner; - - function setUp() public { - tokenIn = new MockERC20("Input", "IN", 18); - tokenOut = new MockERC20("Output", "OUT", 18); - tokenOut2 = new MockERC20("Output2", "OUT2", 18); - swapperPrivateKey = 0x12341234; - swapper = vm.addr(swapperPrivateKey); - cosigner = vm.addr(COSIGNER_PRIVATE_KEY); - permit2 = IPermit2(deployPermit2()); - - reactor = new Reactor(PROTOCOL_FEE_OWNER, permit2); - resolver = new HybridAuctionResolver(); - tokenTransferHook = new TokenTransferHook(permit2, reactor); - - fillContract = new MockFillContract(address(reactor)); - - // Provide tokens for tests - tokenIn.mint(address(swapper), ONE * 1000); - tokenOut.mint(address(fillContract), ONE * 1000); - tokenOut2.mint(address(fillContract), ONE * 1000); - - // Provide ETH to fill contract for native transfers - vm.deal(address(fillContract), type(uint256).max); - } - - /// @dev Create and sign a HybridOrder - function createAndSignOrder(HybridOrder memory order) - internal - view - returns (SignedOrder memory signedOrder, bytes32 orderHash) - { - orderHash = order.hash(); - - // Sign the order with swapper's key - bytes memory sig = signOrder(swapperPrivateKey, address(permit2), order); - - // Encode the order data for the resolver - bytes memory orderData = abi.encode(order); - - // Wrap with resolver address - bytes memory encodedOrder = abi.encode(address(resolver), orderData); - - signedOrder = SignedOrder(encodedOrder, sig); - } - - /// @dev Helper to cosign an order - function cosignOrder(bytes32 orderHash, HybridCosignerData memory cosignerData) - internal - view - returns (bytes memory cosignature) - { - bytes32 msgHash = keccak256(abi.encodePacked(orderHash, block.chainid, abi.encode(cosignerData))); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(COSIGNER_PRIVATE_KEY, msgHash); - cosignature = bytes.concat(r, s, bytes1(v)); - } - - /// @dev Helper to create a basic HybridOrder - function createBasicOrder( - uint256 inputAmount, - uint256 outputAmount, - uint256 scalingFactor, - uint256[] memory priceCurve, - uint256 auctionStartBlock, - uint256 nonce - ) internal view returns (HybridOrder memory) { - HybridOutput[] memory outputs = new HybridOutput[](1); - outputs[0] = HybridOutput({token: address(tokenOut), minAmount: outputAmount, recipient: swapper}); - - return HybridOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 1000) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(resolver).withNonce(nonce), - cosigner: address(0), - input: HybridInput(tokenIn, inputAmount), - outputs: outputs, - auctionStartBlock: auctionStartBlock, - baselinePriorityFee: 0, - scalingFactor: scalingFactor, - priceCurve: priceCurve, - cosignerData: HybridCosignerData({ - auctionTargetBlock: 0, - supplementalPriceCurve: new uint256[](0), - exclusiveFiller: address(0), - exclusivityOverrideBps: 0, - exclusivityEndBlock: 0 - }), - cosignature: "" - }); - } - - /// @dev Helper to create and cosign a HybridOrder with a single output - function _createAndCosignOrder( - uint256 inputAmount, - uint256 outputMinAmount, - uint256[] memory priceCurve, - uint256 auctionStartBlock, - uint256 auctionTargetBlock, - uint256 exclusivityEndBlock, - uint256 exclusivityOverrideBps, - uint256 nonce - ) internal view returns (SignedOrder memory signedOrder) { - HybridInput memory input = HybridInput({token: tokenIn, maxAmount: inputAmount}); - - HybridOutput[] memory outputs = new HybridOutput[](1); - outputs[0] = HybridOutput({token: address(tokenOut), minAmount: outputMinAmount, recipient: swapper}); - - HybridCosignerData memory cosignerData = HybridCosignerData({ - auctionTargetBlock: auctionTargetBlock, - supplementalPriceCurve: new uint256[](0), - exclusiveFiller: EXCLUSIVE_FILLER, - exclusivityOverrideBps: exclusivityOverrideBps, - exclusivityEndBlock: exclusivityEndBlock - }); - - HybridOrder memory order = HybridOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 1000) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(resolver).withNonce(nonce), - cosigner: cosigner, - input: input, - outputs: outputs, - auctionStartBlock: auctionStartBlock, - baselinePriorityFee: 0, - scalingFactor: NEUTRAL_SCALING_FACTOR, - priceCurve: priceCurve, - cosignerData: cosignerData, - cosignature: bytes("") - }); - - order.cosignature = cosignOrder(order.hash(), cosignerData); - (signedOrder,) = createAndSignOrder(order); - } - - /// @dev Helper to create and cosign a priority-only HybridOrder with a single output - function _createAndCosignPriorityOrder( - uint256 inputAmount, - uint256 outputMinAmount, - uint256 scalingFactor, - uint256 auctionStartBlock, - uint256 auctionTargetBlock, - uint256 exclusivityEndBlock, - uint256 exclusivityOverrideBps, - uint256 nonce - ) internal view returns (SignedOrder memory signedOrder) { - HybridInput memory input = HybridInput({token: tokenIn, maxAmount: inputAmount}); - - HybridOutput[] memory outputs = new HybridOutput[](1); - outputs[0] = HybridOutput({token: address(tokenOut), minAmount: outputMinAmount, recipient: swapper}); - - HybridCosignerData memory cosignerData = HybridCosignerData({ - auctionTargetBlock: auctionTargetBlock, - supplementalPriceCurve: new uint256[](0), - exclusiveFiller: EXCLUSIVE_FILLER, - exclusivityOverrideBps: exclusivityOverrideBps, - exclusivityEndBlock: exclusivityEndBlock - }); - - HybridOrder memory order = HybridOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 1000) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(resolver).withNonce(nonce), - cosigner: cosigner, - input: input, - outputs: outputs, - auctionStartBlock: auctionStartBlock, - baselinePriorityFee: 0, - scalingFactor: scalingFactor, - priceCurve: new uint256[](0), - cosignerData: cosignerData, - cosignature: bytes("") - }); - - order.cosignature = cosignOrder(order.hash(), cosignerData); - (signedOrder,) = createAndSignOrder(order); - } - - // ============================================================================ - // Basic Functionality Tests - // ============================================================================ - - function test_basicNoAuctionFill() public { - uint256 inputAmount = 1 ether; - uint256 outputAmount = 1 ether; - - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - - HybridOrder memory order = createBasicOrder(inputAmount, outputAmount, 1e18, new uint256[](0), block.number, 0); - - (SignedOrder memory signedOrder, bytes32 orderHash) = createAndSignOrder(order); - - vm.expectEmit(true, true, true, true, address(reactor)); - emit Fill(orderHash, address(fillContract), swapper, order.info.nonce); - fillContract.execute(signedOrder); - - assertEq(tokenIn.balanceOf(address(fillContract)), inputAmount); - assertEq(tokenOut.balanceOf(swapper), outputAmount); - } - - // ============================================================================ - // Documentation Test Cases (from PriceCurveDocumentationTests.t.sol) - // ============================================================================ - - function test_Doc_LinearDecay_DutchAuction() public { - uint256[] memory priceCurve = new uint256[](1); - priceCurve[0] = (100 << 240) | uint256(0.8e18); // 100 blocks from 0.8x to 1x - - uint256 inputMaxAmount = 1 ether; - uint256 outputAmount = 1 ether; - uint256 auctionStartBlock = block.number; - - tokenIn.forceApprove(swapper, address(permit2), inputMaxAmount * 2); - - // At start (block 0): 0.8x scaling - HybridOrder memory order1 = - createBasicOrder(inputMaxAmount, outputAmount, 0.9e18, priceCurve, auctionStartBlock, 1); - (SignedOrder memory signedOrder1,) = createAndSignOrder(order1); - fillContract.execute(signedOrder1); - - uint256 expectedScaling0 = 0.8e18; - assertEq(tokenIn.balanceOf(address(fillContract)), inputMaxAmount.mulWad(expectedScaling0)); - - // At block 50: 0.9x scaling (midpoint) - vm.roll(auctionStartBlock + 50); - HybridOrder memory order2 = - createBasicOrder(inputMaxAmount, outputAmount, 0.9e18, priceCurve, auctionStartBlock, 2); - (SignedOrder memory signedOrder2,) = createAndSignOrder(order2); - fillContract.execute(signedOrder2); - - uint256 expectedScaling50 = 0.9e18; - assertEq( - tokenIn.balanceOf(address(fillContract)), - inputMaxAmount.mulWad(expectedScaling0) + inputMaxAmount.mulWad(expectedScaling50) - ); - } - - function test_Doc_StepFunctionWithPlateaus() public { - uint256[] memory priceCurve = new uint256[](4); - priceCurve[0] = (50 << 240) | uint256(1.5e18); // High price for 50 blocks - priceCurve[1] = (0 << 240) | uint256(1.2e18); // Drop to 1.2x (zero-duration) - priceCurve[2] = (50 << 240) | uint256(1.2e18); // Hold at 1.2x for 50 blocks (plateau) - priceCurve[3] = (50 << 240) | uint256(1e18); // Final decay to 1.0x - - uint256 inputAmount = 1 ether; - uint256 outputMinAmount = 1 ether; - uint256 auctionStartBlock = block.number; - - tokenIn.forceApprove(swapper, address(permit2), inputAmount * 5); - - // At block 25: interpolating from 1.5x to 1.2x - vm.roll(auctionStartBlock + 25); - _executeOrder(inputAmount, outputMinAmount, NEUTRAL_SCALING_FACTOR, priceCurve, auctionStartBlock, 1); - - // Expected: 1.5 - (1.5 - 1.2) * (25/50) = 1.35 - uint256 balance1 = tokenOut.balanceOf(swapper); - assertEq(balance1, outputMinAmount.mulWadUp(1.35e18)); - - // At block 50: exactly at zero-duration element - vm.roll(auctionStartBlock + 50); - _executeOrder(inputAmount, outputMinAmount, NEUTRAL_SCALING_FACTOR, priceCurve, auctionStartBlock, 2); - - uint256 balance2 = tokenOut.balanceOf(swapper); - assertEq(balance2, balance1 + outputMinAmount.mulWadUp(1.2e18)); - - // At block 75: on plateau - vm.roll(auctionStartBlock + 75); - _executeOrder(inputAmount, outputMinAmount, NEUTRAL_SCALING_FACTOR, priceCurve, auctionStartBlock, 3); - - uint256 balance3 = tokenOut.balanceOf(swapper); - assertEq(balance3, balance2 + outputMinAmount.mulWadUp(1.2e18)); - } - - function test_Doc_AggressiveInitialDiscount() public { - uint256[] memory priceCurve = new uint256[](2); - priceCurve[0] = (10 << 240) | uint256(5e17); // Start at 0.5x for 10 blocks - priceCurve[1] = (90 << 240) | uint256(9e17); // Then 0.9x for 90 blocks - - uint256 inputMaxAmount = 1 ether; - uint256 outputAmount = 1 ether; - uint256 auctionStartBlock = block.number; - - tokenIn.forceApprove(swapper, address(permit2), inputMaxAmount * 5); - - // At block 0: 0.5x - HybridOrder memory order1 = - createBasicOrder(inputMaxAmount, outputAmount, NEUTRAL_SCALING_FACTOR, priceCurve, auctionStartBlock, 1); - (SignedOrder memory signedOrder1,) = createAndSignOrder(order1); - fillContract.execute(signedOrder1); - - assertEq(tokenIn.balanceOf(address(fillContract)), inputMaxAmount.mulWad(0.5e18)); - - // At block 5: midway through first segment - vm.roll(auctionStartBlock + 5); - HybridOrder memory order2 = - createBasicOrder(inputMaxAmount, outputAmount, NEUTRAL_SCALING_FACTOR, priceCurve, auctionStartBlock, 2); - (SignedOrder memory signedOrder2,) = createAndSignOrder(order2); - fillContract.execute(signedOrder2); - - // Expected: 0.5 + (0.9 - 0.5) * (5/10) = 0.7 - // Total input: 0.5 + 0.7 = 1.2 - assertEq(tokenIn.balanceOf(address(fillContract)), inputMaxAmount.mulWad(1.2e18)); - - // At block 10: start of second segment - vm.roll(auctionStartBlock + 10); - HybridOrder memory order3 = - createBasicOrder(inputMaxAmount, outputAmount, NEUTRAL_SCALING_FACTOR, priceCurve, auctionStartBlock, 3); - (SignedOrder memory signedOrder3,) = createAndSignOrder(order3); - fillContract.execute(signedOrder3); - - // Expected: start of second segment at 0.9x - // Total: 1.2 + 0.9 = 2.1 - assertEq(tokenIn.balanceOf(address(fillContract)), inputMaxAmount.mulWad(2.1e18)); - - // At block 55: midway through second segment - vm.roll(auctionStartBlock + 55); - HybridOrder memory order4 = - createBasicOrder(inputMaxAmount, outputAmount, NEUTRAL_SCALING_FACTOR, priceCurve, auctionStartBlock, 4); - (SignedOrder memory signedOrder4,) = createAndSignOrder(order4); - fillContract.execute(signedOrder4); - - // Expected: 0.9 + (1.0 - 0.9) * (45/90) = 0.95 - // Total: 2.1 + 0.95 = 3.05 - assertEq(tokenIn.balanceOf(address(fillContract)), inputMaxAmount.mulWad(3.05e18)); - } - - function test_Doc_AggressiveInitialDiscount_ExceedsDuration() public { - uint256[] memory priceCurve = new uint256[](2); - priceCurve[0] = (10 << 240) | uint256(5e17); - priceCurve[1] = (90 << 240) | uint256(9e17); - - uint256 inputAmount = 1 ether; - uint256 outputAmount = 1 ether; - uint256 auctionStartBlock = block.number; - - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - - // Try to execute at block 100 (exceeds curve duration of 0-99) - vm.roll(auctionStartBlock + 100); - - HybridOrder memory order = createBasicOrder(inputAmount, outputAmount, 0.6e18, priceCurve, auctionStartBlock, 1); - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - - vm.expectRevert(PriceCurveLib.PriceCurveBlocksExceeded.selector); - fillContract.execute(signedOrder); - } - - function test_Doc_ReverseDutchAuction() public { - uint256[] memory priceCurve = new uint256[](1); - priceCurve[0] = (200 << 240) | uint256(2e18); // Start at 2x for 200 blocks - - uint256 inputAmount = 1 ether; - uint256 outputMinAmount = 1 ether; - uint256 auctionStartBlock = block.number; - - tokenIn.forceApprove(swapper, address(permit2), inputAmount * 3); - - // At block 0: 2x - HybridOrder memory order1 = - createBasicOrder(inputAmount, outputMinAmount, NEUTRAL_SCALING_FACTOR, priceCurve, auctionStartBlock, 1); - (SignedOrder memory signedOrder1,) = createAndSignOrder(order1); - fillContract.execute(signedOrder1); - - assertEq(tokenOut.balanceOf(swapper), outputMinAmount.mulWadUp(2e18)); - - // At block 100: midway, should be 1.5x - vm.roll(auctionStartBlock + 100); - HybridOrder memory order2 = - createBasicOrder(inputAmount, outputMinAmount, NEUTRAL_SCALING_FACTOR, priceCurve, auctionStartBlock, 2); - (SignedOrder memory signedOrder2,) = createAndSignOrder(order2); - fillContract.execute(signedOrder2); - - assertEq(tokenOut.balanceOf(swapper), outputMinAmount.mulWadUp(2e18) + outputMinAmount.mulWadUp(1.5e18)); - - // At block 199: last valid block, should be close to 1x - vm.roll(auctionStartBlock + 199); - HybridOrder memory order3 = - createBasicOrder(inputAmount, outputMinAmount, NEUTRAL_SCALING_FACTOR, priceCurve, auctionStartBlock, 3); - (SignedOrder memory signedOrder3,) = createAndSignOrder(order3); - fillContract.execute(signedOrder3); - - // Expected: 2.0 - (2.0 - 1.0) * (199/200) = 1.005 - assertApproxEqRel( - tokenOut.balanceOf(swapper), outputMinAmount.mulWadUp(3.5e18) + outputMinAmount.mulWadUp(1.005e18), 0.001e18 - ); - } - - function test_Doc_ReverseDutchAuction_ExceedsDuration() public { - uint256[] memory priceCurve = new uint256[](1); - priceCurve[0] = (200 << 240) | uint256(2e18); - - uint256 inputAmount = 1 ether; - uint256 outputAmount = 1 ether; - uint256 auctionStartBlock = block.number; - - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - - // Try to execute at block 200 (exceeds curve duration of 0-199) - vm.roll(auctionStartBlock + 200); - - HybridOrder memory order = - createBasicOrder(inputAmount, outputAmount, NEUTRAL_SCALING_FACTOR, priceCurve, auctionStartBlock, 1); - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - - vm.expectRevert(PriceCurveLib.PriceCurveBlocksExceeded.selector); - fillContract.execute(signedOrder); - } - - function test_Doc_ComplexMultiPhaseCurve() public { - uint256[] memory priceCurve = new uint256[](3); - priceCurve[0] = (30 << 240) | uint256(0.5e18); // Start at 0.5x - priceCurve[1] = (40 << 240) | uint256(0.7e18); // Rise to 0.7x at block 30 - priceCurve[2] = (30 << 240) | uint256(0.8e18); // Rise to 0.8x at block 70 - - uint256 inputMaxAmount = 1 ether; - uint256 outputAmount = 1 ether; - uint256 auctionStartBlock = block.number; - - tokenIn.forceApprove(swapper, address(permit2), inputMaxAmount * 4); - - // Block 15: interpolating from 0.5 to 0.7 - vm.roll(auctionStartBlock + 15); - HybridOrder memory order1 = - createBasicOrder(inputMaxAmount, outputAmount, NEUTRAL_SCALING_FACTOR, priceCurve, auctionStartBlock, 1); - (SignedOrder memory signedOrder1,) = createAndSignOrder(order1); - fillContract.execute(signedOrder1); - - // Expected: 0.5 + (0.7 - 0.5) * (15/30) = 0.6 - assertEq(tokenIn.balanceOf(address(fillContract)), inputMaxAmount.mulWad(0.6e18)); - - // Block 50: interpolating from 0.7 to 0.8 - vm.roll(auctionStartBlock + 50); - HybridOrder memory order2 = - createBasicOrder(inputMaxAmount, outputAmount, NEUTRAL_SCALING_FACTOR, priceCurve, auctionStartBlock, 2); - (SignedOrder memory signedOrder2,) = createAndSignOrder(order2); - fillContract.execute(signedOrder2); - - // Expected: 0.7 + (0.8 - 0.7) * (20/40) = 0.75 - // Total: 0.6 + 0.75 = 1.35 - assertEq(tokenIn.balanceOf(address(fillContract)), inputMaxAmount.mulWad(1.35e18)); - - // Block 85: interpolating from 0.8 to 1.0 - vm.roll(auctionStartBlock + 85); - HybridOrder memory order3 = - createBasicOrder(inputMaxAmount, outputAmount, NEUTRAL_SCALING_FACTOR, priceCurve, auctionStartBlock, 3); - (SignedOrder memory signedOrder3,) = createAndSignOrder(order3); - fillContract.execute(signedOrder3); - - // Expected: 0.8 + (1.0 - 0.8) * (15/30) = 0.9 - // Total: 1.35 + 0.9 = 2.25 - assertEq(tokenIn.balanceOf(address(fillContract)), inputMaxAmount.mulWad(2.25e18)); - - // Block 99: last valid block - vm.roll(auctionStartBlock + 99); - HybridOrder memory order4 = - createBasicOrder(inputMaxAmount, outputAmount, NEUTRAL_SCALING_FACTOR, priceCurve, auctionStartBlock, 4); - (SignedOrder memory signedOrder4,) = createAndSignOrder(order4); - fillContract.execute(signedOrder4); - - // Expected: 0.8 + (1.0 - 0.8) * (29/30) ≈ 0.9933 - // Total: 2.25 + 0.9933 = 3.2433 - assertApproxEqRel(tokenIn.balanceOf(address(fillContract)), inputMaxAmount.mulWad(3.2433e18), 0.001e18); - } - - // ============================================================================ - // Edge Cases (from PriceCurveEdgeCasesTest.t.sol) - // ============================================================================ - - function test_EmptyPriceCurve_ReturnsNeutralScaling() public { - uint256 inputAmount = 1 ether; - uint256 outputAmount = 1 ether; - - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - - HybridOrder memory order = - createBasicOrder(inputAmount, outputAmount, NEUTRAL_SCALING_FACTOR, new uint256[](0), 0, 0); - - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - fillContract.execute(signedOrder); - - // Neutral scaling: input and output unchanged - assertEq(tokenIn.balanceOf(address(fillContract)), inputAmount); - assertEq(tokenOut.balanceOf(swapper), outputAmount); - } - - function test_ZeroDuration_InstantaneousPricePoint() public { - uint256[] memory priceCurve = new uint256[](3); - priceCurve[0] = (10 << 240) | uint256(1.2e18); // 10 blocks at 1.2x - priceCurve[1] = (0 << 240) | uint256(1.5e18); // Zero duration at 1.5x - priceCurve[2] = (20 << 240) | uint256(1e18); // 20 blocks ending at 1x - - uint256 inputAmount = 1 ether; - uint256 outputMinAmount = 1 ether; - uint256 auctionStartBlock = block.number; - - tokenIn.forceApprove(swapper, address(permit2), inputAmount * 3); - - // At block 5: interpolating from 1.2x towards 1.5x - vm.roll(auctionStartBlock + 5); - HybridOrder memory order1 = - createBasicOrder(inputAmount, outputMinAmount, NEUTRAL_SCALING_FACTOR, priceCurve, auctionStartBlock, 1); - (SignedOrder memory signedOrder1,) = createAndSignOrder(order1); - fillContract.execute(signedOrder1); - - // Expected: 1.2 + (1.5 - 1.2) * (5/10) = 1.35 - assertApproxEqRel(tokenOut.balanceOf(swapper), outputMinAmount.mulWadUp(1.35e18), 0.01e18); - - // At block 10: exactly at zero-duration element - vm.roll(auctionStartBlock + 10); - HybridOrder memory order2 = - createBasicOrder(inputAmount, outputMinAmount, NEUTRAL_SCALING_FACTOR, priceCurve, auctionStartBlock, 2); - (SignedOrder memory signedOrder2,) = createAndSignOrder(order2); - fillContract.execute(signedOrder2); - - assertApproxEqRel( - tokenOut.balanceOf(swapper), outputMinAmount.mulWadUp(1.35e18) + outputMinAmount.mulWadUp(1.5e18), 0.01e18 - ); - } - - function test_ZeroScalingFactor_ExactOut() public { - uint256[] memory priceCurve = new uint256[](1); - priceCurve[0] = (10 << 240) | uint256(0); // Start at 0 - - uint256 inputMaxAmount = 1 ether; - uint256 outputAmount = 1 ether; - uint256 auctionStartBlock = block.number; - - tokenIn.forceApprove(swapper, address(permit2), inputMaxAmount); - - // At targetBlock, scaling is 0 - HybridOrder memory order = - createBasicOrder(inputMaxAmount, outputAmount, NEUTRAL_SCALING_FACTOR, priceCurve, auctionStartBlock, 1); - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - fillContract.execute(signedOrder); - - // Input scaled to 0 - assertEq(tokenIn.balanceOf(address(fillContract)), 0); - assertEq(tokenOut.balanceOf(swapper), outputAmount); - } - - function test_RevertsExceedingTotalBlockDuration() public { - uint256[] memory priceCurve = new uint256[](1); - priceCurve[0] = (10 << 240) | uint256(1.2e18); // 10 blocks only - - uint256 inputAmount = 1 ether; - uint256 outputAmount = 1 ether; - uint256 auctionStartBlock = block.number; - - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - - // Try to execute at block 10 (exceeds curve duration of 0-9) - vm.roll(auctionStartBlock + 10); - - HybridOrder memory order = - createBasicOrder(inputAmount, outputAmount, NEUTRAL_SCALING_FACTOR, priceCurve, auctionStartBlock, 1); - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - - vm.expectRevert(PriceCurveLib.PriceCurveBlocksExceeded.selector); - fillContract.execute(signedOrder); - } - - function test_RevertsInconsistentScalingDirections() public { - uint256[] memory priceCurve = new uint256[](2); - priceCurve[0] = (10 << 240) | uint256(1.5e18); // Increase (>1e18) - priceCurve[1] = (10 << 240) | uint256(0.5e18); // Decrease (<1e18) - INVALID! - - uint256 inputAmount = 1 ether; - uint256 outputAmount = 1 ether; - uint256 auctionStartBlock = block.number; - - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - - vm.roll(auctionStartBlock + 5); - - HybridOrder memory order = - createBasicOrder(inputAmount, outputAmount, NEUTRAL_SCALING_FACTOR, priceCurve, auctionStartBlock, 1); - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - - vm.expectRevert(PriceCurveLib.InvalidPriceCurveParameters.selector); - fillContract.execute(signedOrder); - } - - function test_RevertsInvalidAuctionBlock() public { - uint256 inputAmount = 1 ether; - uint256 outputAmount = 1 ether; - uint256 auctionStartBlock = block.number + 10; // Future block - - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - - HybridOrder memory order = - createBasicOrder(inputAmount, outputAmount, NEUTRAL_SCALING_FACTOR, new uint256[](0), auctionStartBlock, 1); - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - - vm.expectRevert(HybridAuctionResolver.InvalidAuctionBlock.selector); - fillContract.execute(signedOrder); - } - - function test_LinearDecay_DutchAuction_ExceedsDuration() public { - uint256[] memory priceCurve = new uint256[](1); - priceCurve[0] = (100 << 240) | uint256(0.8e18); // 100 blocks total duration - - uint256 inputAmount = 1 ether; - uint256 outputAmount = 1 ether; - uint256 auctionStartBlock = block.number; - - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - - // At block 100: should revert (exceeds total duration) - vm.roll(auctionStartBlock + 100); - - HybridOrder memory order = - createBasicOrder(inputAmount, outputAmount, NEUTRAL_SCALING_FACTOR, priceCurve, auctionStartBlock, 1); - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - - vm.expectRevert(PriceCurveLib.PriceCurveBlocksExceeded.selector); - fillContract.execute(signedOrder); - } - - function test_StepFunctionWithPlateaus() public { - uint256[] memory priceCurve = new uint256[](3); - priceCurve[0] = (50 << 240) | uint256(1.5e18); // 50 blocks - priceCurve[1] = (50 << 240) | uint256(1.2e18); // 50 blocks - priceCurve[2] = (50 << 240) | uint256(1e18); // 50 blocks - // Total duration: 150 blocks - - uint256 inputAmount = 1 ether; - uint256 outputMinAmount = 1 ether; - uint256 auctionStartBlock = block.number; - - tokenIn.forceApprove(swapper, address(permit2), inputAmount * 4); - - // During first segment (block 25) - vm.roll(auctionStartBlock + 25); - _executeOrder(inputAmount, outputMinAmount, NEUTRAL_SCALING_FACTOR, priceCurve, auctionStartBlock, 1); - - // Should interpolate from 1.5 towards 1.2 - // Expected: 1.5 - (1.5 - 1.2) * (25/50) = 1.35 - uint256 balance1 = tokenOut.balanceOf(swapper); - assertEq(balance1, outputMinAmount.mulWadUp(1.35e18)); - - // At block 50 (start of second segment) - vm.roll(auctionStartBlock + 50); - _executeOrder(inputAmount, outputMinAmount, NEUTRAL_SCALING_FACTOR, priceCurve, auctionStartBlock, 2); - - // Should interpolate from 1.2 towards 1.0 - // Expected: 1.2 - (1.2 - 1.0) * (0/50) = 1.2 - uint256 balance2 = tokenOut.balanceOf(swapper); - assertEq(balance2, balance1 + outputMinAmount.mulWadUp(1.2e18)); - - // At block 75 (halfway through second segment) - vm.roll(auctionStartBlock + 75); - _executeOrder(inputAmount, outputMinAmount, NEUTRAL_SCALING_FACTOR, priceCurve, auctionStartBlock, 3); - - // Block 75 is 25 blocks into segment 1 (blocks 50-100) - // Expected: 1.2 - (1.2 - 1.0) * (25/50) = 1.1 - uint256 balance3 = tokenOut.balanceOf(swapper); - assertEq(balance3, balance2 + outputMinAmount.mulWadUp(1.1e18)); - - // At block 100 (start of third segment) - vm.roll(auctionStartBlock + 100); - _executeOrder(inputAmount, outputMinAmount, NEUTRAL_SCALING_FACTOR, priceCurve, auctionStartBlock, 4); - - // Expected: 1.0 - (1.0 - 1.0) * (0/50) = 1.0 - uint256 balance4 = tokenOut.balanceOf(swapper); - assertEq(balance4, balance3 + outputMinAmount.mulWadUp(1e18)); - } - - function test_StepFunctionWithPlateaus_ExceedsDuration() public { - uint256[] memory priceCurve = new uint256[](3); - priceCurve[0] = (50 << 240) | uint256(1.5e18); // 50 blocks - priceCurve[1] = (50 << 240) | uint256(1.2e18); // 50 blocks - priceCurve[2] = (50 << 240) | uint256(1e18); // 50 blocks - // Total duration: 150 blocks - - uint256 inputAmount = 1 ether; - uint256 outputAmount = 1 ether; - uint256 auctionStartBlock = block.number; - - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - - // At block 150: should revert (exceeds total duration) - vm.roll(auctionStartBlock + 150); - - HybridOrder memory order = - createBasicOrder(inputAmount, outputAmount, NEUTRAL_SCALING_FACTOR, priceCurve, auctionStartBlock, 1); - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - - vm.expectRevert(PriceCurveLib.PriceCurveBlocksExceeded.selector); - fillContract.execute(signedOrder); - } - - function test_InvertedAuction_PriceIncreasesOverTime() public { - uint256[] memory priceCurve = new uint256[](1); - priceCurve[0] = (100 << 240) | uint256(0.5e18); // 100 blocks total duration - - uint256 inputMaxAmount = 1 ether; - uint256 outputAmount = 1 ether; - uint256 auctionStartBlock = block.number; - - tokenIn.forceApprove(swapper, address(permit2), inputMaxAmount * 3); - - // Price should increase from 0.5x to 1x over 100 blocks - // At block 0: 0.5x - HybridOrder memory order1 = - createBasicOrder(inputMaxAmount, outputAmount, NEUTRAL_SCALING_FACTOR, priceCurve, auctionStartBlock, 1); - (SignedOrder memory signedOrder1,) = createAndSignOrder(order1); - fillContract.execute(signedOrder1); - - assertEq(tokenIn.balanceOf(address(fillContract)), inputMaxAmount.mulWad(0.5e18)); - - // At block 50: midpoint, should be 0.75x - vm.roll(auctionStartBlock + 50); - HybridOrder memory order2 = - createBasicOrder(inputMaxAmount, outputAmount, NEUTRAL_SCALING_FACTOR, priceCurve, auctionStartBlock, 2); - (SignedOrder memory signedOrder2,) = createAndSignOrder(order2); - fillContract.execute(signedOrder2); - - assertEq( - tokenIn.balanceOf(address(fillContract)), inputMaxAmount.mulWad(0.5e18) + inputMaxAmount.mulWad(0.75e18) - ); - - // At block 99: close to 1.0x - vm.roll(auctionStartBlock + 99); - HybridOrder memory order3 = - createBasicOrder(inputMaxAmount, outputAmount, NEUTRAL_SCALING_FACTOR, priceCurve, auctionStartBlock, 3); - (SignedOrder memory signedOrder3,) = createAndSignOrder(order3); - fillContract.execute(signedOrder3); - - assertApproxEqRel( - tokenIn.balanceOf(address(fillContract)), - inputMaxAmount.mulWad(0.5e18) + inputMaxAmount.mulWad(0.75e18) + inputMaxAmount.mulWad(0.995e18), - 0.001e18 - ); - } - - function test_ComplexMultiPhaseCurve() public { - uint256[] memory priceCurve = new uint256[](3); - priceCurve[0] = (30 << 240) | uint256(1.5e18); // 30 blocks - priceCurve[1] = (40 << 240) | uint256(1.3e18); // 40 blocks - priceCurve[2] = (30 << 240) | uint256(1.1e18); // 30 blocks - // Total duration: 30 + 40 + 30 = 100 blocks - - uint256 inputAmount = 1 ether; - uint256 outputMinAmount = 1 ether; - uint256 auctionStartBlock = block.number; - - tokenIn.forceApprove(swapper, address(permit2), inputAmount * 4); - - // Block 15: interpolating from 1.5 to 1.3 - vm.roll(auctionStartBlock + 15); - _executeOrder(inputAmount, outputMinAmount, NEUTRAL_SCALING_FACTOR, priceCurve, auctionStartBlock, 1); - - // Expected: 1.5 - (1.5 - 1.3) * (15/30) = 1.4 - uint256 balance1 = tokenOut.balanceOf(swapper); - assertEq(balance1, outputMinAmount.mulWadUp(1.4e18)); - - // Block 50: interpolating from 1.3 to 1.1 - vm.roll(auctionStartBlock + 50); - _executeOrder(inputAmount, outputMinAmount, NEUTRAL_SCALING_FACTOR, priceCurve, auctionStartBlock, 2); - - // Expected: 1.3 - (1.3 - 1.1) * (20/40) = 1.2 - uint256 balance2 = tokenOut.balanceOf(swapper); - assertEq(balance2, balance1 + outputMinAmount.mulWadUp(1.2e18)); - - // Block 85: interpolating from 1.1 to 1.0 - vm.roll(auctionStartBlock + 85); - _executeOrder(inputAmount, outputMinAmount, NEUTRAL_SCALING_FACTOR, priceCurve, auctionStartBlock, 3); - - // Expected: 1.1 - (1.1 - 1.0) * (15/30) = 1.05 - uint256 balance3 = tokenOut.balanceOf(swapper); - assertEq(balance3, balance2 + outputMinAmount.mulWadUp(1.05e18)); - - // Block 99: last valid block - vm.roll(auctionStartBlock + 99); - _executeOrder(inputAmount, outputMinAmount, NEUTRAL_SCALING_FACTOR, priceCurve, auctionStartBlock, 4); - - // Expected: 1.1 - (1.1 - 1.0) * (29/30) ≈ 1.0033 - uint256 balance4 = tokenOut.balanceOf(swapper); - assertApproxEqRel(balance4, balance3 + outputMinAmount.mulWadUp(1.0033e18), 0.001e18); - } - - // ============================================================================ - // Multiple Zero Duration Tests (from MultipleZeroDurationTest.t.sol) - // ============================================================================ - - function test_MultipleConsecutiveZeroDuration_DetailedBehavior() public { - uint256[] memory priceCurve = new uint256[](4); - priceCurve[0] = (10 << 240) | uint256(1.2e18); // 10 blocks at 1.2x - priceCurve[1] = (0 << 240) | uint256(1.5e18); // First zero-duration at block 10 - priceCurve[2] = (0 << 240) | uint256(1.3e18); // Second zero-duration at block 10 - priceCurve[3] = (10 << 240) | uint256(1e18); // 10 blocks ending at 1x - - uint256 inputAmount = 1 ether; - uint256 outputMinAmount = 1 ether; - uint256 auctionStartBlock = block.number; - - tokenIn.forceApprove(swapper, address(permit2), inputAmount * 4); - - // At block 5: interpolating from 1.2x towards 1.5x - vm.roll(auctionStartBlock + 5); - HybridOrder memory order1 = - createBasicOrder(inputAmount, outputMinAmount, NEUTRAL_SCALING_FACTOR, priceCurve, auctionStartBlock, 1); - (SignedOrder memory signedOrder1,) = createAndSignOrder(order1); - fillContract.execute(signedOrder1); - - // Expected: 1.2 + (1.5 - 1.2) * (5/10) = 1.35 - uint256 balance1 = tokenOut.balanceOf(swapper); - assertEq(balance1, outputMinAmount.mulWadUp(1.35e18)); - - // At block 10: returns FIRST zero-duration element (1.5x) - vm.roll(auctionStartBlock + 10); - HybridOrder memory order2 = - createBasicOrder(inputAmount, outputMinAmount, NEUTRAL_SCALING_FACTOR, priceCurve, auctionStartBlock, 2); - (SignedOrder memory signedOrder2,) = createAndSignOrder(order2); - fillContract.execute(signedOrder2); - - uint256 balance2 = tokenOut.balanceOf(swapper); - assertEq(balance2, balance1 + outputMinAmount.mulWadUp(1.5e18)); - - // At block 11: interpolates from LAST zero-duration element (1.3x) - vm.roll(auctionStartBlock + 11); - HybridOrder memory order3 = - createBasicOrder(inputAmount, outputMinAmount, NEUTRAL_SCALING_FACTOR, priceCurve, auctionStartBlock, 3); - (SignedOrder memory signedOrder3,) = createAndSignOrder(order3); - fillContract.execute(signedOrder3); - - // Expected: 1.3 - (1.3 - 1.0) * (1/10) = 1.27 - uint256 balance3 = tokenOut.balanceOf(swapper); - assertEq(balance3, balance2 + outputMinAmount.mulWadUp(1.27e18)); - } - - function test_ThreeConsecutiveZeroDuration() public { - uint256[] memory priceCurve = new uint256[](5); - priceCurve[0] = (10 << 240) | uint256(1.1e18); // 10 blocks at 1.1x - priceCurve[1] = (0 << 240) | uint256(1.6e18); // First zero-duration - priceCurve[2] = (0 << 240) | uint256(1.4e18); // Second zero-duration - priceCurve[3] = (0 << 240) | uint256(1.2e18); // Third zero-duration (last) - priceCurve[4] = (10 << 240) | uint256(1e18); // 10 blocks to 1x - - uint256 inputAmount = 1 ether; - uint256 outputMinAmount = 1 ether; - uint256 auctionStartBlock = block.number; - - tokenIn.forceApprove(swapper, address(permit2), inputAmount * 3); - - // At block 10: returns FIRST zero-duration (1.6x) - vm.roll(auctionStartBlock + 10); - HybridOrder memory order1 = - createBasicOrder(inputAmount, outputMinAmount, NEUTRAL_SCALING_FACTOR, priceCurve, auctionStartBlock, 1); - (SignedOrder memory signedOrder1,) = createAndSignOrder(order1); - fillContract.execute(signedOrder1); - - assertEq(tokenOut.balanceOf(swapper), outputMinAmount.mulWadUp(1.6e18)); - - // At block 11: interpolates from LAST (third) zero-duration (1.2x) - vm.roll(auctionStartBlock + 11); - HybridOrder memory order2 = - createBasicOrder(inputAmount, outputMinAmount, NEUTRAL_SCALING_FACTOR, priceCurve, auctionStartBlock, 2); - (SignedOrder memory signedOrder2,) = createAndSignOrder(order2); - fillContract.execute(signedOrder2); - - // Expected: 1.2 - (1.2 - 1.0) * (1/10) = 1.18 - assertEq(tokenOut.balanceOf(swapper), outputMinAmount.mulWadUp(1.6e18) + outputMinAmount.mulWadUp(1.18e18)); - } - - // ============================================================================ - // Priority Fee Tests - // ============================================================================ - - function test_PriorityFee_ExactIn_IncreasesWithGas() public { - uint256[] memory priceCurve = new uint256[](1); - priceCurve[0] = (100 << 240) | uint256(1.2e18); - - uint256 baselinePriorityFee = 10 gwei; - uint256 scalingFactor = 1.00000000001e18; // Exact-in mode (very close to neutral) - uint256 inputAmount = 1 ether; - uint256 outputMinAmount = 1 ether; - uint256 auctionStartBlock = block.number; - - // Set priority fee of 5 gwei above baseline - vm.fee(1 gwei); - vm.txGasPrice(1 gwei + baselinePriorityFee + 5 gwei); - - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - - vm.roll(auctionStartBlock + 50); // Midway through curve - - HybridOutput[] memory outputs = new HybridOutput[](1); - outputs[0] = HybridOutput({token: address(tokenOut), minAmount: outputMinAmount, recipient: swapper}); - - HybridOrder memory order = HybridOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 1000) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(resolver), - cosigner: address(0), - input: HybridInput(tokenIn, inputAmount), - outputs: outputs, - auctionStartBlock: auctionStartBlock, - baselinePriorityFee: baselinePriorityFee, - scalingFactor: scalingFactor, - priceCurve: priceCurve, - cosignerData: HybridCosignerData({ - auctionTargetBlock: 0, - supplementalPriceCurve: new uint256[](0), - exclusiveFiller: address(0), - exclusivityOverrideBps: 0, - exclusivityEndBlock: 0 - }), - cosignature: "" - }); - - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - fillContract.execute(signedOrder); - - // Current scaling from curve: 1.2 - (1.2-1.0) * 0.5 = 1.1 - // scalingFactor = 1.00000000001e18, priorityFee = 5 gwei - // scalingMultiplier = 1.1e18 + ((1.00000000001e18 - 1e18) * 5 gwei) - uint256 expectedScaling = 1.1e18 + ((scalingFactor - 1e18) * 5 gwei); - - assertApproxEqRel(tokenOut.balanceOf(swapper), outputMinAmount.mulWadUp(expectedScaling), 0.001e18); - } - - function test_PriorityFee_ExactOut_DecreasesWithGas() public { - uint256[] memory priceCurve = new uint256[](1); - priceCurve[0] = (100 << 240) | uint256(0.8e18); - - uint256 baselinePriorityFee = 10 gwei; - uint256 scalingFactor = 0.999e18; // Exact-out mode - uint256 inputMaxAmount = 1 ether; - uint256 outputAmount = 1 ether; - uint256 auctionStartBlock = block.number; - - // Set smaller priority fee to avoid underflow - vm.fee(1 gwei); - vm.txGasPrice(1 gwei + baselinePriorityFee + 1 wei); - - tokenIn.forceApprove(swapper, address(permit2), inputMaxAmount); - - vm.roll(auctionStartBlock + 50); // Midway through curve - - HybridOutput[] memory outputs = new HybridOutput[](1); - outputs[0] = HybridOutput({token: address(tokenOut), minAmount: outputAmount, recipient: swapper}); - - HybridOrder memory order = HybridOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 1000) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(resolver), - cosigner: address(0), - input: HybridInput(tokenIn, inputMaxAmount), - outputs: outputs, - auctionStartBlock: auctionStartBlock, - baselinePriorityFee: baselinePriorityFee, - scalingFactor: scalingFactor, - priceCurve: priceCurve, - cosignerData: HybridCosignerData({ - auctionTargetBlock: 0, - supplementalPriceCurve: new uint256[](0), - exclusiveFiller: address(0), - exclusivityOverrideBps: 0, - exclusivityEndBlock: 0 - }), - cosignature: "" - }); - - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - fillContract.execute(signedOrder); - - // Output fixed in exact-out - assertEq(tokenOut.balanceOf(swapper), outputAmount); - - // Current scaling from curve: 0.8 + (1.0-0.8) * 0.5 = 0.9 - // Priority adjustment: 0.9 - (1.0 - 0.999) * 1 wei - uint256 currentCurveScaling = 0.9e18; - uint256 expectedScaling = currentCurveScaling - ((1e18 - scalingFactor) * 1); - assertApproxEqRel(tokenIn.balanceOf(address(fillContract)), inputMaxAmount.mulWad(expectedScaling), 0.001e18); - } - - // ============================================================================ - // DeriveAmounts Tests (from TribunalDeriveAmountsTest.t.sol) - // ============================================================================ - - function test_DeriveAmounts_NoPriorityFee() public { - uint256 inputAmount = 100 ether; - uint256 outputAmount = 95 ether; - uint256 baselinePriorityFee = 100 gwei; - - vm.fee(baselinePriorityFee); - vm.txGasPrice(baselinePriorityFee + 1 wei); - - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - - HybridOrder memory order = - createBasicOrder(inputAmount, outputAmount, NEUTRAL_SCALING_FACTOR, new uint256[](0), 0, 0); - - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - fillContract.execute(signedOrder); - - // Neutral scaling with no priority fee above baseline - assertEq(tokenOut.balanceOf(swapper), outputAmount); - assertEq(tokenIn.balanceOf(address(fillContract)), inputAmount); - } - - function test_DeriveAmounts_ExactOut() public { - uint256[] memory priceCurve = new uint256[](0); - uint256 inputMaxAmount = 1 ether; - uint256 outputAmount = 0.95 ether; - uint256 baselinePriorityFee = 100 gwei; - uint256 scalingFactor = 0.5e18; - uint256 auctionStartBlock = block.number; - - vm.fee(1 gwei); - vm.txGasPrice(1 gwei + baselinePriorityFee + 2 wei); - - tokenIn.forceApprove(swapper, address(permit2), inputMaxAmount); - - HybridOutput[] memory outputs = new HybridOutput[](1); - outputs[0] = HybridOutput({token: address(tokenOut), minAmount: outputAmount, recipient: swapper}); - - HybridOrder memory order = HybridOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 1000) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(resolver), - cosigner: address(0), - input: HybridInput(tokenIn, inputMaxAmount), - outputs: outputs, - auctionStartBlock: auctionStartBlock, - baselinePriorityFee: baselinePriorityFee, - scalingFactor: scalingFactor, - priceCurve: priceCurve, - cosignerData: HybridCosignerData({ - auctionTargetBlock: 0, - supplementalPriceCurve: new uint256[](0), - exclusiveFiller: address(0), - exclusivityOverrideBps: 0, - exclusivityEndBlock: 0 - }), - cosignature: "" - }); - - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - fillContract.execute(signedOrder); - - // Output fixed in exact-out mode - assertEq(tokenOut.balanceOf(swapper), outputAmount); - - // scalingMultiplier = 1e18 - ((1e18 - 0.5e18) * 2) - uint256 scalingMultiplier = 1e18 - ((1e18 - scalingFactor) * 2); - uint256 expectedInput = inputMaxAmount.mulWad(scalingMultiplier); - assertEq(tokenIn.balanceOf(address(fillContract)), expectedInput); - } - - function test_DeriveAmounts_ExactIn() public { - uint256[] memory priceCurve = new uint256[](0); - uint256 inputAmount = 1 ether; - uint256 outputMinAmount = 0.95 ether; - uint256 baselinePriorityFee = 100 gwei; - uint256 scalingFactor = 1.5e18; - uint256 auctionStartBlock = block.number; - - vm.fee(1 gwei); - vm.txGasPrice(1 gwei + baselinePriorityFee + 2 wei); - - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - - HybridOutput[] memory outputs = new HybridOutput[](1); - outputs[0] = HybridOutput({token: address(tokenOut), minAmount: outputMinAmount, recipient: swapper}); - - HybridOrder memory order = HybridOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 1000) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(resolver), - cosigner: address(0), - input: HybridInput(tokenIn, inputAmount), - outputs: outputs, - auctionStartBlock: auctionStartBlock, - baselinePriorityFee: baselinePriorityFee, - scalingFactor: scalingFactor, - priceCurve: priceCurve, - cosignerData: HybridCosignerData({ - auctionTargetBlock: 0, - supplementalPriceCurve: new uint256[](0), - exclusiveFiller: address(0), - exclusivityOverrideBps: 0, - exclusivityEndBlock: 0 - }), - cosignature: "" - }); - - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - fillContract.execute(signedOrder); - - // Input unchanged in exact-in mode - assertEq(tokenIn.balanceOf(address(fillContract)), inputAmount); - - // scalingMultiplier = 1e18 + ((1.5e18 - 1e18) * 2) - uint256 scalingMultiplier = 1e18 + ((scalingFactor - 1e18) * 2); - uint256 expectedOutput = outputMinAmount.mulWadUp(scalingMultiplier); - assertEq(tokenOut.balanceOf(swapper), expectedOutput); - } - - function test_DeriveAmounts_ExtremePriorityFee() public { - uint256[] memory priceCurve = new uint256[](0); - uint256 inputAmount = 1 ether; - uint256 outputMinAmount = 0.95 ether; - uint256 baselinePriorityFee = 100 gwei; - uint256 scalingFactor = 1.5e18; - uint256 auctionStartBlock = block.number; - - uint256 baseFee = 1 gwei; - vm.fee(baseFee); - vm.txGasPrice(baseFee + baselinePriorityFee + 10 wei); - - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - - HybridOutput[] memory outputs = new HybridOutput[](1); - outputs[0] = HybridOutput({token: address(tokenOut), minAmount: outputMinAmount, recipient: swapper}); - - HybridOrder memory order = HybridOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 1000) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(resolver), - cosigner: address(0), - input: HybridInput(tokenIn, inputAmount), - outputs: outputs, - auctionStartBlock: auctionStartBlock, - baselinePriorityFee: baselinePriorityFee, - scalingFactor: scalingFactor, - priceCurve: priceCurve, - cosignerData: HybridCosignerData({ - auctionTargetBlock: 0, - supplementalPriceCurve: new uint256[](0), - exclusiveFiller: address(0), - exclusivityOverrideBps: 0, - exclusivityEndBlock: 0 - }), - cosignature: "" - }); - - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - fillContract.execute(signedOrder); - - // Output unchanged in exact-in mode - assertEq(tokenIn.balanceOf(address(fillContract)), inputAmount); - - // scalingMultiplier = 1e18 + ((1.5e18 - 1e18) * 10) - uint256 scalingMultiplier = 1e18 + ((scalingFactor - 1e18) * 10); - uint256 expectedOutput = outputMinAmount.mulWadUp(scalingMultiplier); - assertEq(tokenOut.balanceOf(swapper), expectedOutput); - } - - function test_DeriveAmounts_RealisticExactIn() public { - uint256[] memory priceCurve = new uint256[](0); - uint256 inputAmount = 1 ether; - uint256 outputMinAmount = 0.95 ether; - uint256 baselinePriorityFee = 100 gwei; - uint256 scalingFactor = 1000000000100000000; // 1.0000000001e18 - uint256 auctionStartBlock = block.number; - - vm.fee(1 gwei); - vm.txGasPrice(1 gwei + baselinePriorityFee + 5 gwei); - - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - - HybridOutput[] memory outputs = new HybridOutput[](1); - outputs[0] = HybridOutput({token: address(tokenOut), minAmount: outputMinAmount, recipient: swapper}); - - HybridOrder memory order = HybridOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 1000) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(resolver), - cosigner: address(0), - input: HybridInput(tokenIn, inputAmount), - outputs: outputs, - auctionStartBlock: auctionStartBlock, - baselinePriorityFee: baselinePriorityFee, - scalingFactor: scalingFactor, - priceCurve: priceCurve, - cosignerData: HybridCosignerData({ - auctionTargetBlock: 0, - supplementalPriceCurve: new uint256[](0), - exclusiveFiller: address(0), - exclusivityOverrideBps: 0, - exclusivityEndBlock: 0 - }), - cosignature: "" - }); - - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - fillContract.execute(signedOrder); - - // Output unchanged in exact-in mode - assertEq(tokenIn.balanceOf(address(fillContract)), inputAmount); - - uint256 scalingMultiplier = 1e18 + ((scalingFactor - 1e18) * 5 gwei); - uint256 expectedOutput = outputMinAmount.mulWadUp(scalingMultiplier); - assertEq(tokenOut.balanceOf(swapper), expectedOutput); - } - - function test_DeriveAmounts_RealisticExactOut() public { - uint256[] memory priceCurve = new uint256[](0); - uint256 inputMaxAmount = 1 ether; - uint256 outputAmount = 0.95 ether; - uint256 baselinePriorityFee = 100 gwei; - uint256 scalingFactor = 999999999900000000; // 0.9999999999e18 - uint256 auctionStartBlock = block.number; - - vm.fee(1 gwei); - vm.txGasPrice(1 gwei + baselinePriorityFee + 5 gwei); - - tokenIn.forceApprove(swapper, address(permit2), inputMaxAmount); - - HybridOutput[] memory outputs = new HybridOutput[](1); - outputs[0] = HybridOutput({token: address(tokenOut), minAmount: outputAmount, recipient: swapper}); - - HybridOrder memory order = HybridOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 1000) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(resolver), - cosigner: address(0), - input: HybridInput(tokenIn, inputMaxAmount), - outputs: outputs, - auctionStartBlock: auctionStartBlock, - baselinePriorityFee: baselinePriorityFee, - scalingFactor: scalingFactor, - priceCurve: priceCurve, - cosignerData: HybridCosignerData({ - auctionTargetBlock: 0, - supplementalPriceCurve: new uint256[](0), - exclusiveFiller: address(0), - exclusivityOverrideBps: 0, - exclusivityEndBlock: 0 - }), - cosignature: "" - }); - - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - fillContract.execute(signedOrder); - - // Output fixed in exact-out mode - assertEq(tokenOut.balanceOf(swapper), outputAmount); - - uint256 scalingMultiplier = 1e18 - ((1e18 - scalingFactor) * 5 gwei); - uint256 expectedInput = inputMaxAmount.mulWad(scalingMultiplier); - assertEq(tokenIn.balanceOf(address(fillContract)), expectedInput); - } - - function test_DeriveAmounts_WithPriceCurve() public { - uint256[] memory priceCurve = new uint256[](3); - priceCurve[0] = (3 << 240) | uint256(0.8e18); // 0.8 * 10^18 (scaling down) - priceCurve[1] = (10 << 240) | uint256(0.6e18); // 0.6 * 10^18 (scaling down more) - priceCurve[2] = (10 << 240) | uint256(0); // 0 * 10^18 (scaling down to 0) - - uint256 inputMaxAmount = 1 ether; - uint256 outputAmount = 0.95 ether; - uint256 auctionStartBlock = block.number; - - tokenIn.forceApprove(swapper, address(permit2), inputMaxAmount); - - // Fill at block 5 - vm.roll(auctionStartBlock + 5); - - HybridOrder memory order = - createBasicOrder(inputMaxAmount, outputAmount, NEUTRAL_SCALING_FACTOR, priceCurve, auctionStartBlock, 1); - - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - fillContract.execute(signedOrder); - - // Output fixed in exact-out mode - assertEq(tokenOut.balanceOf(swapper), outputAmount); - - // Calculate expected scaling at block 5 - // We're 5 blocks in, with first segment ending at block 3 - // So we're 5-3=2 blocks into the second segment (which has 10 blocks duration) - // Interpolating from 0.6 to 0 (last segment ends at 0) - // scalingMultiplier = 0.6 - (0.6 * 2/10) = 0.6 * 0.8 = 0.48 - uint256 expectedScaling = 0.48e18; - uint256 expectedInput = inputMaxAmount.mulWad(expectedScaling); - assertEq(tokenIn.balanceOf(address(fillContract)), expectedInput); - } - - function test_DeriveAmounts_WithPriceCurve_Dutch() public { - uint256[] memory priceCurve = new uint256[](1); - priceCurve[0] = (10 << 240) | uint256(1.2e18); - - uint256 inputAmount = 1 ether; - uint256 outputMinAmount = 0.95 ether; - uint256 auctionStartBlock = block.number; - - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - - vm.roll(auctionStartBlock + 5); - - HybridOrder memory order = - createBasicOrder(inputAmount, outputMinAmount, NEUTRAL_SCALING_FACTOR, priceCurve, auctionStartBlock, 1); - - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - fillContract.execute(signedOrder); - - // With exact-in mode and price curve scaling down - // 5 blocks in, 10 blocks in segment - // Interpolating from 1.2 to 1 (last segment ends at 1e18) - // scalingMultiplier = 1.2 - (0.2 * 5/10) = 1.1 - uint256 expectedScaling = 1.1e18; - assertEq(tokenOut.balanceOf(swapper), outputMinAmount.mulWadUp(expectedScaling)); - assertEq(tokenIn.balanceOf(address(fillContract)), inputAmount); - } - - function test_DeriveAmounts_WithPriceCurve_Dutch_nonNeutralEndScalingFactor() public { - uint256[] memory priceCurve = new uint256[](2); - priceCurve[0] = (10 << 240) | uint256(1.2e18); - priceCurve[1] = (0 << 240) | uint256(1.1e18); - - uint256 inputAmount = 1 ether; - uint256 outputMinAmount = 0.95 ether; - uint256 auctionStartBlock = block.number; - - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - - vm.roll(auctionStartBlock + 5); - - HybridOrder memory order = - createBasicOrder(inputAmount, outputMinAmount, NEUTRAL_SCALING_FACTOR, priceCurve, auctionStartBlock, 1); - - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - fillContract.execute(signedOrder); - - // With exact-in mode and price curve scaling down - // 5 blocks in, 10 blocks in segment - // Interpolating from 1.2 to 1.1 (zero-duration element) - // scalingMultiplier = 1.2 - (0.1 * 5/10) = 1.15 - uint256 expectedScaling = 1.15e18; - assertEq(tokenOut.balanceOf(swapper), outputMinAmount.mulWadUp(expectedScaling)); - assertEq(tokenIn.balanceOf(address(fillContract)), inputAmount); - } - - function test_DeriveAmounts_WithPriceCurve_ReverseDutch() public { - uint256[] memory priceCurve = new uint256[](2); - priceCurve[0] = (10 << 240) | uint256(0.8e18); - priceCurve[1] = (10 << 240) | uint256(1e18); - - uint256 inputMaxAmount = 1 ether; - uint256 outputAmount = 0.95 ether; - uint256 auctionStartBlock = block.number; - - tokenIn.forceApprove(swapper, address(permit2), inputMaxAmount * 2); - - // Test at block 5 - vm.roll(auctionStartBlock + 5); - HybridOrder memory order1 = - createBasicOrder(inputMaxAmount, outputAmount, NEUTRAL_SCALING_FACTOR, priceCurve, auctionStartBlock, 1); - (SignedOrder memory signedOrder1,) = createAndSignOrder(order1); - fillContract.execute(signedOrder1); - - // With exact-out mode and price curve scaling up - assertEq(tokenOut.balanceOf(swapper), outputAmount); // Output stays the same - - // Calculate expected claim amount based on interpolation at block 5 - // We're 5 blocks in, with segment ending at block 10 - // Interpolating from 0.8 to 1 - // scalingMultiplier = 0.8 + (0.2 * 5/10) = 0.9 - uint256 expectedScaling = 0.9e18; - uint256 expectedInput = inputMaxAmount.mulWad(expectedScaling); - assertEq(tokenIn.balanceOf(address(fillContract)), expectedInput); - } - - function test_DeriveAmounts_InvalidTargetBlockDesignation() public { - uint256 inputAmount = 1 ether; - uint256 outputAmount = 1 ether; - - uint256[] memory priceCurve = new uint256[](1); - priceCurve[0] = 1e18; - - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - - HybridOrder memory order = createBasicOrder(inputAmount, outputAmount, NEUTRAL_SCALING_FACTOR, priceCurve, 0, 1); - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - - vm.expectRevert(HybridOrderLib.InvalidTargetBlockDesignation.selector); - fillContract.execute(signedOrder); - } - - // ============================================================================ - // Cosigner Tests - // ============================================================================ - - function test_CosignerOverrideAuctionTargetBlock() public { - uint256[] memory priceCurve = new uint256[](1); - priceCurve[0] = (10 << 240) | uint256(1.2e18); - - uint256 inputAmount = 1 ether; - uint256 outputMinAmount = 1 ether; - uint256 originalAuctionStart = block.number + 10; - uint256 cosignerOverrideBlock = block.number; - - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - - HybridOutput[] memory outputs = new HybridOutput[](1); - outputs[0] = HybridOutput({token: address(tokenOut), minAmount: outputMinAmount, recipient: swapper}); - - HybridCosignerData memory cosignerData = HybridCosignerData({ - auctionTargetBlock: cosignerOverrideBlock, - supplementalPriceCurve: new uint256[](0), - exclusiveFiller: address(0), - exclusivityOverrideBps: 0, - exclusivityEndBlock: 0 - }); - - HybridOrder memory order = HybridOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 1000) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(resolver), - cosigner: cosigner, - input: HybridInput(tokenIn, inputAmount), - outputs: outputs, - auctionStartBlock: originalAuctionStart, - baselinePriorityFee: 0, - scalingFactor: 1.3e18, - priceCurve: priceCurve, - cosignerData: cosignerData, - cosignature: bytes("") - }); - order.cosignature = cosignOrder(order.hash(), cosignerData); - - (SignedOrder memory signedOrder, bytes32 orderHash) = createAndSignOrder(order); - - // Should succeed because cosigner overrides to current block - vm.expectEmit(true, true, true, true, address(reactor)); - emit Fill(orderHash, address(fillContract), swapper, order.info.nonce); - fillContract.execute(signedOrder); - } - - function test_CosignerSupplementalPriceCurve() public { - uint256[] memory baseCurve = new uint256[](1); - baseCurve[0] = (10 << 240) | uint256(1.2e18); // Base: 1.2x - - uint256[] memory supplementalCurve = new uint256[](1); - supplementalCurve[0] = uint256(1.1e18); // Add 0.1x (combined: 1.3x) - - uint256 inputAmount = 1 ether; - uint256 outputMinAmount = 1 ether; - uint256 auctionStartBlock = block.number; - - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - - HybridOutput[] memory outputs = new HybridOutput[](1); - outputs[0] = HybridOutput({token: address(tokenOut), minAmount: outputMinAmount, recipient: swapper}); - - HybridCosignerData memory cosignerData = HybridCosignerData({ - auctionTargetBlock: 0, - supplementalPriceCurve: supplementalCurve, - exclusiveFiller: address(0), - exclusivityOverrideBps: 0, - exclusivityEndBlock: 0 - }); - - HybridOrder memory order = HybridOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 1000) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(resolver), - cosigner: cosigner, - input: HybridInput(tokenIn, inputAmount), - outputs: outputs, - auctionStartBlock: auctionStartBlock, - baselinePriorityFee: 0, - scalingFactor: 1.2e18, - priceCurve: baseCurve, - cosignerData: cosignerData, - cosignature: bytes("") - }); - order.cosignature = cosignOrder(order.hash(), cosignerData); - - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - fillContract.execute(signedOrder); - - // Combined scaling: 1.2 + 1.1 - 1.0 = 1.3 - assertEq(tokenOut.balanceOf(swapper), outputMinAmount.mulWadUp(1.3e18)); - } - - function test_RevertsWrongCosigner() public { - address wrongCosigner = makeAddr("wrongCosigner"); - - HybridOutput[] memory outputs = new HybridOutput[](1); - outputs[0] = HybridOutput({token: address(tokenOut), minAmount: 0, recipient: swapper}); - - HybridCosignerData memory cosignerData = HybridCosignerData({ - auctionTargetBlock: block.number, - supplementalPriceCurve: new uint256[](0), - exclusiveFiller: address(0), - exclusivityOverrideBps: 0, - exclusivityEndBlock: 0 - }); - - HybridOrder memory order = HybridOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 1000) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(resolver), - cosigner: wrongCosigner, - input: HybridInput(tokenIn, 0), - outputs: outputs, - auctionStartBlock: block.number, - baselinePriorityFee: 0, - scalingFactor: NEUTRAL_SCALING_FACTOR, - priceCurve: new uint256[](0), - cosignerData: cosignerData, - cosignature: bytes("") - }); - order.cosignature = cosignOrder(order.hash(), cosignerData); - - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - - vm.expectRevert(CosignerLib.InvalidCosignature.selector); - fillContract.execute(signedOrder); - } - - // ============================================================================ - // Multiple Outputs Tests - // ============================================================================ - - function test_MultipleOutputs_ExactIn() public { - uint256[] memory priceCurve = new uint256[](1); - priceCurve[0] = (100 << 240) | uint256(1.2e18); // Start at 1.2x for 100 blocks - - uint256 inputAmount = 1 ether; - uint256 output1MinAmount = 0.5 ether; - uint256 output2MinAmount = 0.3 ether; - uint256 scalingFactor = 1.2e18; - uint256 auctionStartBlock = block.number; - - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - - HybridOutput[] memory outputs = new HybridOutput[](2); - outputs[0] = HybridOutput({token: address(tokenOut), minAmount: output1MinAmount, recipient: swapper}); - outputs[1] = HybridOutput({token: address(tokenOut2), minAmount: output2MinAmount, recipient: swapper}); - - HybridOrder memory order = HybridOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 1000) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(resolver), - cosigner: address(0), - input: HybridInput(tokenIn, inputAmount), - outputs: outputs, - auctionStartBlock: auctionStartBlock, - baselinePriorityFee: 0, - scalingFactor: scalingFactor, - priceCurve: priceCurve, - cosignerData: HybridCosignerData({ - auctionTargetBlock: 0, - supplementalPriceCurve: new uint256[](0), - exclusiveFiller: address(0), - exclusivityOverrideBps: 0, - exclusivityEndBlock: 0 - }), - cosignature: "" - }); - - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - fillContract.execute(signedOrder); - - // At block 0, price curve gives 1.2x scaling - assertEq(tokenIn.balanceOf(address(fillContract)), inputAmount); - assertEq(tokenOut.balanceOf(swapper), output1MinAmount.mulWadUp(1.2e18)); - assertEq(tokenOut2.balanceOf(swapper), output2MinAmount.mulWadUp(1.2e18)); - } - - // ============================================================================ - // Fuzz Tests for Min/Max Amount Invariants - // ============================================================================ - - /// @dev Fuzz test to verify output amounts never violate minAmount in exact-in mode - function testFuzz_ExactIn_OutputsNeverBelowMin( - uint128 inputAmount, - uint128 outputMinAmount, - uint256 priorityScalingFactor, - uint64 priorityFeeWei, - uint8 blocksPassed - ) public { - // Bound parameters to reasonable ranges - vm.assume(inputAmount > 0 && inputAmount < type(uint128).max / 2); - vm.assume(outputMinAmount > 0 && outputMinAmount < type(uint128).max / 2); - vm.assume(blocksPassed < 100); - - // IMPORTANT: scalingFactor must be very close to 1e18 to avoid overflow - // To match PriorityOrder behavior where 1 wei of priority fee above baseline = 0.001% improvement: - // - adjustment = currentScalingFactor + (scalingFactor - 1e18) * priorityFee - // - For 1 wei = 0.001%: (scalingFactor - 1e18) = 1e13 - // - Therefore: scalingFactor = 1e18 + 1e13 for standard sensitivity - uint256 scalingFactor = bound(priorityScalingFactor, 1e18 + 1e12, 1e18 + 1e13); - priorityFeeWei = uint64(bound(priorityFeeWei, 0, 100000 wei)); - - vm.fee(1 gwei); - vm.txGasPrice(1 gwei + priorityFeeWei); - - // Create price curve that scales from priceCurveScaling up to 1e18 or neutral - uint256[] memory priceCurve = new uint256[](1); - priceCurve[0] = (100 << 240) | 1.2e18; - - uint256 auctionStartBlock = block.number; - vm.roll(auctionStartBlock + blocksPassed); - - // Setup and execute order - tokenIn.mint(address(swapper), inputAmount); - tokenOut.mint(address(fillContract), type(uint256).max / 2); - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - - HybridOrder memory order = createBasicOrder( - inputAmount, - outputMinAmount, - scalingFactor, - priceCurve, - auctionStartBlock, - uint256(keccak256(abi.encodePacked(inputAmount, outputMinAmount, blocksPassed))) - ); - - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - fillContract.execute(signedOrder); - - uint256 actualOutput = tokenOut.balanceOf(swapper); - assertGe(actualOutput, outputMinAmount, "Output below minAmount"); - - assertEq(tokenIn.balanceOf(address(fillContract)), inputAmount, "Input should be exact in exact-in mode"); - } - - /// @dev Fuzz test to verify input amounts never violate maxAmount in exact-out mode - function testFuzz_ExactOut_InputNeverExceedsMax( - uint128 inputMaxAmount, - uint128 outputAmount, - uint256 priceCurveScaling, - uint64 priorityFeeWei, - uint8 blocksPassed - ) public { - // Bound parameters to reasonable ranges - vm.assume(inputMaxAmount > 0 && inputMaxAmount < type(uint128).max / 2); - vm.assume(outputAmount > 0 && outputAmount < type(uint128).max / 2); - - uint256 scalingFactor = bound(priceCurveScaling, 1e18 - 1e13, 1e18 - 1e12); - vm.assume(blocksPassed < 100); - - // IMPORTANT: scalingFactor must be very close to 1e18 to avoid underflow - // To match PriorityOrder behavior where 1 wei of priority fee above baseline = 0.001% improvement: - // - adjustment = currentScalingFactor - (1e18 - scalingFactor) * priorityFee - // - For 1 wei = 0.001%: (1e18 - scalingFactor) = 1e13 - // - Therefore: scalingFactor = 1e18 - 1e13 for standard sensitivity - // upper bound of improvement is cutting the maxInput in half - priorityFeeWei = uint64(bound(priorityFeeWei, 0, 50000 wei)); - - vm.fee(1 gwei); - vm.txGasPrice(1 gwei + priorityFeeWei); - - // Create price curve - uint256[] memory priceCurve = new uint256[](1); - priceCurve[0] = (100 << 240) | uint256(0.8e18); - - uint256 auctionStartBlock = block.number; - vm.roll(auctionStartBlock + blocksPassed); - - // Setup and execute order - tokenIn.mint(address(swapper), inputMaxAmount); - tokenOut.mint(address(fillContract), type(uint256).max / 2); - tokenIn.forceApprove(swapper, address(permit2), inputMaxAmount); - - HybridOrder memory order = createBasicOrder( - inputMaxAmount, - outputAmount, - scalingFactor, - priceCurve, - auctionStartBlock, - uint256(keccak256(abi.encodePacked(inputMaxAmount, outputAmount, blocksPassed))) - ); - - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - fillContract.execute(signedOrder); - - uint256 actualInput = tokenIn.balanceOf(address(fillContract)); - assertLe(actualInput, inputMaxAmount, "Input exceeds maxAmount"); - - assertEq(tokenOut.balanceOf(swapper), outputAmount, "Output should be exact in exact-out mode"); - } - - /// @dev Fuzz test at edge case: scaling factor exactly at 1e18 (neutral) - function testFuzz_NeutralScaling_RespectsConstraints( - uint128 inputAmount, - uint128 outputAmount, - uint64 priorityFeeWei, - uint8 blocksPassed - ) public { - vm.assume(inputAmount > 0 && inputAmount < type(uint128).max / 2); - vm.assume(outputAmount > 0 && outputAmount < type(uint128).max / 2); - vm.assume(blocksPassed < 100); - priorityFeeWei = uint64(bound(priorityFeeWei, 0, 1 gwei)); - - uint256 scalingFactor = 1e18; // Neutral scaling - uint256[] memory priceCurve = new uint256[](1); - priceCurve[0] = (100 << 240) | uint256(1e18); - - uint256 auctionStartBlock = block.number; - vm.roll(auctionStartBlock + blocksPassed); - - // Setup tokens - tokenIn.mint(address(swapper), inputAmount); - tokenOut.mint(address(fillContract), type(uint128).max); - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - - HybridOrder memory order = createBasicOrder( - inputAmount, - outputAmount, - scalingFactor, - priceCurve, - auctionStartBlock, - uint256(keccak256(abi.encodePacked(inputAmount, outputAmount, blocksPassed))) - ); - - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - fillContract.execute(signedOrder); - - assertEq(tokenOut.balanceOf(swapper), outputAmount, "Output should equal minAmount with neutral scaling"); - assertEq( - tokenIn.balanceOf(address(fillContract)), inputAmount, "Input should equal amount with neutral scaling" - ); - } - - /// @dev Helper to execute an order - function _executeOrder( - uint256 inputAmount, - uint256 outputAmount, - uint256 scalingFactor, - uint256[] memory priceCurve, - uint256 auctionStartBlock, - uint256 nonce - ) internal { - HybridOrder memory order = createBasicOrder( - inputAmount, outputAmount, scalingFactor, priceCurve, auctionStartBlock, nonce - ); - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - fillContract.execute(signedOrder); - } - - function _neutralPriceCurve() internal pure returns (uint256[] memory priceCurve) { - priceCurve = new uint256[](1); - priceCurve[0] = (100 << 240) | uint256(1e18); - } - - /* ================================================ - * EXCLUSIVITY TESTS - * ================================================ */ - - /// @notice Fuzz exclusivity outcomes based on target, end, and fill blocks. - function testFuzz_ExclusivityWindow( - uint32 targetBlockSeed, - uint32 endBlockSeed, - uint32 fillBlockSeed, - uint16 overrideBpsSeed, - bool useExclusiveFiller - ) public { - uint256 baseBlock = 1000; - vm.roll(baseBlock); - - uint256 targetBlock = bound(uint256(targetBlockSeed), 0, baseBlock + 20); - uint256 exclusivityEndBlock = bound(uint256(endBlockSeed), 0, baseBlock + 20); - uint256 fillBlock = bound(uint256(fillBlockSeed), 0, baseBlock + 20); - uint256 exclusivityOverrideBps = bound(uint256(overrideBpsSeed), 0, 10_000); - - uint256 inputAmount = 100e18; - uint256 outputMinAmount = 95e18; - uint256[] memory emptyCurve = new uint256[](0); - - tokenIn.forceApprove(swapper, address(permit2), type(uint256).max); - - SignedOrder memory signedOrder = _createAndCosignOrder( - inputAmount, - outputMinAmount, - emptyCurve, - targetBlock, - targetBlock, - exclusivityEndBlock, - exclusivityOverrideBps, - 1 - ); - - vm.roll(fillBlock); - - if (exclusivityEndBlock != 0 && targetBlock != 0 && exclusivityEndBlock < targetBlock) { - vm.expectRevert(HybridAuctionResolver.InvalidExclusivityEndBlock.selector); - if (useExclusiveFiller) { - vm.prank(EXCLUSIVE_FILLER, EXCLUSIVE_FILLER); - } - fillContract.execute(signedOrder); - return; - } - - if (targetBlock != 0 && fillBlock < targetBlock) { - vm.expectRevert(HybridAuctionResolver.InvalidAuctionBlock.selector); - if (useExclusiveFiller) { - vm.prank(EXCLUSIVE_FILLER, EXCLUSIVE_FILLER); - } - fillContract.execute(signedOrder); - return; - } - - bool exclusivityActive = exclusivityEndBlock != 0 && fillBlock <= exclusivityEndBlock; - if (exclusivityActive && !useExclusiveFiller && exclusivityOverrideBps == 0) { - vm.expectRevert(ExclusivityLib.NoExclusiveOverride.selector); - fillContract.execute(signedOrder); - return; - } - - uint256 swapperBalanceBefore = tokenOut.balanceOf(swapper); - if (useExclusiveFiller) { - vm.prank(EXCLUSIVE_FILLER, EXCLUSIVE_FILLER); - } - fillContract.execute(signedOrder); - - uint256 expectedOutput = outputMinAmount; - if (exclusivityActive && !useExclusiveFiller && exclusivityOverrideBps > 0) { - expectedOutput = outputMinAmount.mulDivUp(10_000 + exclusivityOverrideBps, 10_000); - } - - assertEq(tokenOut.balanceOf(swapper), swapperBalanceBefore + expectedOutput); - } - - /// @notice Test that strict exclusivity (0 bps) reverts for non-exclusive filler - function test_StrictExclusivity_InvalidCaller_Reverts() public { - uint256 inputAmount = 100e18; - uint256 outputMinAmount = 95e18; - uint256[] memory priceCurve = _neutralPriceCurve(); - - tokenIn.forceApprove(swapper, address(permit2), type(uint256).max); - - HybridInput memory input = HybridInput({token: tokenIn, maxAmount: inputAmount}); - - HybridOutput[] memory outputs = new HybridOutput[](1); - outputs[0] = HybridOutput({token: address(tokenOut), minAmount: outputMinAmount, recipient: swapper}); - - address exclusiveFiller = EXCLUSIVE_FILLER; - HybridCosignerData memory cosignerData = HybridCosignerData({ - auctionTargetBlock: block.number, - supplementalPriceCurve: new uint256[](0), - exclusiveFiller: exclusiveFiller, - exclusivityOverrideBps: 0, // Strict exclusivity - exclusivityEndBlock: block.number - }); - - HybridOrder memory order = HybridOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 1000) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(resolver), - cosigner: cosigner, - input: input, - outputs: outputs, - auctionStartBlock: block.number, - baselinePriorityFee: 0, - scalingFactor: NEUTRAL_SCALING_FACTOR, - priceCurve: priceCurve, - cosignerData: cosignerData, - cosignature: bytes("") - }); - order.cosignature = cosignOrder(order.hash(), cosignerData); - - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - - // fillContract is not the exclusive filler, should revert - vm.expectRevert(ExclusivityLib.NoExclusiveOverride.selector); - fillContract.execute(signedOrder); - } - - /// @notice Test that non-exclusive filler pays override amount - function test_ExclusivityOverride_AppliesCorrectly() public { - uint256 inputAmount = 100e18; - uint256 outputMinAmount = 95e18; - uint256 exclusivityOverrideBps = 100; // 1% - uint256[] memory priceCurve = _neutralPriceCurve(); - - tokenIn.forceApprove(swapper, address(permit2), type(uint256).max); - - HybridInput memory input = HybridInput({token: tokenIn, maxAmount: inputAmount}); - - HybridOutput[] memory outputs = new HybridOutput[](1); - outputs[0] = HybridOutput({token: address(tokenOut), minAmount: outputMinAmount, recipient: swapper}); - - address exclusiveFiller = EXCLUSIVE_FILLER; - HybridCosignerData memory cosignerData = HybridCosignerData({ - auctionTargetBlock: block.number, - supplementalPriceCurve: new uint256[](0), - exclusiveFiller: exclusiveFiller, - exclusivityOverrideBps: exclusivityOverrideBps, - exclusivityEndBlock: block.number - }); - - HybridOrder memory order = HybridOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 1000) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(resolver), - cosigner: cosigner, - input: input, - outputs: outputs, - auctionStartBlock: block.number, - baselinePriorityFee: 0, - scalingFactor: NEUTRAL_SCALING_FACTOR, - priceCurve: priceCurve, - cosignerData: cosignerData, - cosignature: bytes("") - }); - order.cosignature = cosignOrder(order.hash(), cosignerData); - - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - - uint256 swapperBalanceBefore = tokenOut.balanceOf(swapper); - fillContract.execute(signedOrder); - - // Swapper should receive output + 1% override - uint256 expectedOutput = outputMinAmount * (10000 + exclusivityOverrideBps) / 10000; - assertEq(tokenIn.balanceOf(swapper), 1000e18 - inputAmount); - assertEq(tokenOut.balanceOf(swapper), swapperBalanceBefore + expectedOutput); - } - - /// @notice Test that exclusivity expires after auction target block - function test_ExclusivityExpired_AnyoneCanFill() public { - uint256 inputAmount = 100e18; - uint256 outputMinAmount = 95e18; - uint256[] memory priceCurve = _neutralPriceCurve(); - - tokenIn.forceApprove(swapper, address(permit2), type(uint256).max); - - HybridInput memory input = HybridInput({token: tokenIn, maxAmount: inputAmount}); - - HybridOutput[] memory outputs = new HybridOutput[](1); - outputs[0] = HybridOutput({token: address(tokenOut), minAmount: outputMinAmount, recipient: swapper}); - - address exclusiveFiller = EXCLUSIVE_FILLER; - uint256 exclusivityEndBlock = block.number + 10; - - HybridCosignerData memory cosignerData = HybridCosignerData({ - auctionTargetBlock: exclusivityEndBlock, - supplementalPriceCurve: new uint256[](0), - exclusiveFiller: exclusiveFiller, - exclusivityOverrideBps: 0, // Strict during period - exclusivityEndBlock: exclusivityEndBlock - }); - - HybridOrder memory order = HybridOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 1000) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(resolver), - cosigner: cosigner, - input: input, - outputs: outputs, - auctionStartBlock: exclusivityEndBlock, - baselinePriorityFee: 0, - scalingFactor: NEUTRAL_SCALING_FACTOR, - priceCurve: priceCurve, - cosignerData: cosignerData, - cosignature: bytes("") - }); - order.cosignature = cosignOrder(order.hash(), cosignerData); - - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - - // Advance past exclusivity period - vm.roll(exclusivityEndBlock + 1); - - uint256 swapperBalanceBefore = tokenOut.balanceOf(swapper); - fillContract.execute(signedOrder); - - // Should fill without override since exclusivity expired - assertEq(tokenIn.balanceOf(swapper), 1000e18 - inputAmount); - assertEq(tokenOut.balanceOf(swapper), swapperBalanceBefore + outputMinAmount); - } - - /// @notice Test that exclusivity end before auction target block reverts - function test_ExclusivityEndBeforeTargetBlock_Reverts() public { - uint256 inputAmount = 100e18; - uint256 outputMinAmount = 95e18; - uint256[] memory priceCurve = _neutralPriceCurve(); - uint256 targetBlock = block.number + 1; - uint256 exclusivityEndBlock = block.number; - - tokenIn.forceApprove(swapper, address(permit2), type(uint256).max); - - SignedOrder memory signedOrder = _createAndCosignOrder( - inputAmount, outputMinAmount, priceCurve, targetBlock, targetBlock, exclusivityEndBlock, 0, 1 - ); - - vm.roll(targetBlock); - vm.expectRevert(HybridAuctionResolver.InvalidExclusivityEndBlock.selector); - fillContract.execute(signedOrder); - } - - /// @notice Test that exclusivity is ignored when end block is unset - function test_ExclusivityUnset_IgnoresExclusiveFiller() public { - uint256 inputAmount = 100e18; - uint256 outputMinAmount = 95e18; - uint256[] memory priceCurve = _neutralPriceCurve(); - uint256 targetBlock = block.number; - - tokenIn.forceApprove(swapper, address(permit2), type(uint256).max); - - SignedOrder memory signedOrder = - _createAndCosignOrder(inputAmount, outputMinAmount, priceCurve, targetBlock, targetBlock, 0, 0, 1); - - uint256 swapperBalanceBefore = tokenOut.balanceOf(swapper); - fillContract.execute(signedOrder); - - assertEq(tokenIn.balanceOf(swapper), 1000e18 - inputAmount); - assertEq(tokenOut.balanceOf(swapper), swapperBalanceBefore + outputMinAmount); - } - - /// @notice Test that strict exclusivity is enforced across a multi-block window for hybrid orders - function test_Exclusivity_MultiBlockWindow_Hybrid_Enforced() public { - uint256 inputAmount = 100e18; - uint256 outputMinAmount = 95e18; - uint256[] memory priceCurve = _neutralPriceCurve(); - uint256 startBlock = block.number; - uint256 exclusivityEndBlock = startBlock + 3; - - tokenIn.forceApprove(swapper, address(permit2), type(uint256).max); - - SignedOrder memory signedOrderNonExclusive = _createAndCosignOrder( - inputAmount, outputMinAmount, priceCurve, startBlock, startBlock, exclusivityEndBlock, 0, 1 - ); - SignedOrder memory signedOrderExclusive = _createAndCosignOrder( - inputAmount, outputMinAmount, priceCurve, startBlock, startBlock, exclusivityEndBlock, 0, 2 - ); - - vm.expectRevert(ExclusivityLib.NoExclusiveOverride.selector); - fillContract.execute(signedOrderNonExclusive); - - vm.roll(startBlock + 2); - vm.expectRevert(ExclusivityLib.NoExclusiveOverride.selector); - fillContract.execute(signedOrderNonExclusive); - - uint256 swapperBalanceBefore = tokenOut.balanceOf(swapper); - vm.prank(EXCLUSIVE_FILLER, EXCLUSIVE_FILLER); - fillContract.execute(signedOrderExclusive); - - assertEq(tokenIn.balanceOf(swapper), 1000e18 - inputAmount); - assertEq(tokenOut.balanceOf(swapper), swapperBalanceBefore + outputMinAmount); - } - - /// @notice Test that exclusivity opens after the end block in a multi-block window - function test_Exclusivity_MultiBlockWindow_OpensAfterEnd() public { - uint256 inputAmount = 100e18; - uint256 outputMinAmount = 95e18; - uint256[] memory priceCurve = _neutralPriceCurve(); - uint256 startBlock = block.number; - uint256 exclusivityEndBlock = startBlock + 3; - - tokenIn.forceApprove(swapper, address(permit2), type(uint256).max); - - SignedOrder memory signedOrder = _createAndCosignOrder( - inputAmount, outputMinAmount, priceCurve, startBlock, startBlock, exclusivityEndBlock, 0, 1 - ); - - vm.expectRevert(ExclusivityLib.NoExclusiveOverride.selector); - fillContract.execute(signedOrder); - - vm.roll(exclusivityEndBlock + 1); - uint256 swapperBalanceBefore = tokenOut.balanceOf(swapper); - fillContract.execute(signedOrder); - - assertEq(tokenIn.balanceOf(swapper), 1000e18 - inputAmount); - assertEq(tokenOut.balanceOf(swapper), swapperBalanceBefore + outputMinAmount); - } - - /// @notice Test that priority-only orders enforce exclusivity when end block is set - function test_PriorityOnly_ExclusivityEndBlock_Enforced() public { - uint256 inputAmount = 100e18; - uint256 outputMinAmount = 95e18; - uint256[] memory priceCurve = new uint256[](0); - uint256 targetBlock = block.number; - uint256 exclusivityEndBlock = block.number + 5; - - tokenIn.forceApprove(swapper, address(permit2), type(uint256).max); - - SignedOrder memory signedOrderNonExclusive = _createAndCosignOrder( - inputAmount, outputMinAmount, priceCurve, targetBlock, targetBlock, exclusivityEndBlock, 0, 1 - ); - - vm.expectRevert(ExclusivityLib.NoExclusiveOverride.selector); - fillContract.execute(signedOrderNonExclusive); - - SignedOrder memory signedOrderExclusive = _createAndCosignOrder( - inputAmount, outputMinAmount, priceCurve, targetBlock, targetBlock, exclusivityEndBlock, 0, 2 - ); - - uint256 swapperBalanceBefore = tokenOut.balanceOf(swapper); - vm.prank(EXCLUSIVE_FILLER, EXCLUSIVE_FILLER); - fillContract.execute(signedOrderExclusive); - - assertEq(tokenIn.balanceOf(swapper), 1000e18 - inputAmount); - assertEq(tokenOut.balanceOf(swapper), swapperBalanceBefore + outputMinAmount); - } - - /// @notice Test that strict exclusivity is enforced across a multi-block window for priority-only orders - function test_PriorityOnly_MultiBlockWindow_Exclusivity_Enforced() public { - uint256 inputAmount = 100e18; - uint256 outputMinAmount = 95e18; - uint256 priorityScalingFactor = 1.001e18; - uint256 startBlock = block.number; - uint256 exclusivityEndBlock = startBlock + 3; - - tokenIn.forceApprove(swapper, address(permit2), type(uint256).max); - - SignedOrder memory signedOrderNonExclusive = _createAndCosignPriorityOrder( - inputAmount, outputMinAmount, priorityScalingFactor, startBlock, startBlock, exclusivityEndBlock, 0, 1 - ); - SignedOrder memory signedOrderExclusive = _createAndCosignPriorityOrder( - inputAmount, outputMinAmount, priorityScalingFactor, startBlock, startBlock, exclusivityEndBlock, 0, 2 - ); - - vm.expectRevert(ExclusivityLib.NoExclusiveOverride.selector); - fillContract.execute(signedOrderNonExclusive); - - vm.roll(startBlock + 2); - vm.expectRevert(ExclusivityLib.NoExclusiveOverride.selector); - fillContract.execute(signedOrderNonExclusive); - - uint256 swapperBalanceBefore = tokenOut.balanceOf(swapper); - vm.prank(EXCLUSIVE_FILLER, EXCLUSIVE_FILLER); - fillContract.execute(signedOrderExclusive); - - assertEq(tokenIn.balanceOf(swapper), 1000e18 - inputAmount); - assertGe(tokenOut.balanceOf(swapper), swapperBalanceBefore + outputMinAmount); - } - - /// @notice Test that strict exclusivity only applies at auctionTargetBlock - function test_StrictExclusivity_OnlyAtTargetBlock() public { - uint256 inputAmount = 100e18; - uint256 outputMinAmount = 95e18; - uint256[] memory priceCurve = _neutralPriceCurve(); - uint256 targetBlock = block.number + 5; - - tokenIn.forceApprove(swapper, address(permit2), type(uint256).max); - - SignedOrder memory signedOrderAtTarget = _createAndCosignOrder( - inputAmount, outputMinAmount, priceCurve, targetBlock, targetBlock, targetBlock, 0, 1 - ); - SignedOrder memory signedOrderAfterTarget = _createAndCosignOrder( - inputAmount, outputMinAmount, priceCurve, targetBlock, targetBlock, targetBlock, 0, 2 - ); - - vm.roll(targetBlock); - vm.expectRevert(ExclusivityLib.NoExclusiveOverride.selector); - fillContract.execute(signedOrderAtTarget); - - vm.roll(targetBlock + 1); - uint256 swapperBalanceBefore = tokenOut.balanceOf(swapper); - fillContract.execute(signedOrderAfterTarget); - - assertEq(tokenIn.balanceOf(swapper), 1000e18 - inputAmount); - assertEq(tokenOut.balanceOf(swapper), swapperBalanceBefore + outputMinAmount); - } - - /// @notice Test that exclusive filler can fill at auctionTargetBlock - function test_StrictExclusivity_ExclusiveFiller_CanFillAtTargetBlock() public { - uint256 inputAmount = 100e18; - uint256 outputMinAmount = 95e18; - uint256[] memory priceCurve = _neutralPriceCurve(); - uint256 targetBlock = block.number + 5; - - tokenIn.forceApprove(swapper, address(permit2), type(uint256).max); - - SignedOrder memory signedOrder = _createAndCosignOrder( - inputAmount, outputMinAmount, priceCurve, targetBlock, targetBlock, targetBlock, 0, 1 - ); - - vm.roll(targetBlock); - uint256 swapperBalanceBefore = tokenOut.balanceOf(swapper); - - vm.prank(EXCLUSIVE_FILLER, EXCLUSIVE_FILLER); - fillContract.execute(signedOrder); - - assertEq(tokenIn.balanceOf(swapper), 1000e18 - inputAmount); - assertEq(tokenOut.balanceOf(swapper), swapperBalanceBefore + outputMinAmount); - } - - /// @notice Test that override only applies at auctionTargetBlock - function test_ExclusivityOverride_OnlyAtTargetBlock() public { - uint256 inputAmount = 100e18; - uint256 outputMinAmount = 95e18; - uint256 exclusivityOverrideBps = 150; // 1.5% - uint256[] memory priceCurve = _neutralPriceCurve(); - uint256 targetBlock = block.number + 5; - - tokenIn.forceApprove(swapper, address(permit2), type(uint256).max); - - SignedOrder memory signedOrderAtTarget = _createAndCosignOrder( - inputAmount, outputMinAmount, priceCurve, targetBlock, targetBlock, targetBlock, exclusivityOverrideBps, 1 - ); - SignedOrder memory signedOrderAfterTarget = _createAndCosignOrder( - inputAmount, outputMinAmount, priceCurve, targetBlock, targetBlock, targetBlock, exclusivityOverrideBps, 2 - ); - - vm.roll(targetBlock); - uint256 swapperBalanceBefore = tokenOut.balanceOf(swapper); - fillContract.execute(signedOrderAtTarget); - - uint256 expectedOutput = outputMinAmount * (10000 + exclusivityOverrideBps) / 10000; - assertEq(tokenOut.balanceOf(swapper), swapperBalanceBefore + expectedOutput); - - vm.roll(targetBlock + 1); - uint256 swapperBalanceAfterTarget = tokenOut.balanceOf(swapper); - fillContract.execute(signedOrderAfterTarget); - - assertEq(tokenIn.balanceOf(swapper), 1000e18 - (inputAmount * 2)); - assertEq(tokenOut.balanceOf(swapper), swapperBalanceAfterTarget + outputMinAmount); - } - - /// @notice Test that address(0) exclusiveFiller allows anyone to fill - function test_NoExclusivity_AnyoneCanFill() public { - uint256 inputAmount = 100e18; - uint256 outputMinAmount = 95e18; - uint256[] memory priceCurve = _neutralPriceCurve(); - - tokenIn.forceApprove(swapper, address(permit2), type(uint256).max); - - HybridInput memory input = HybridInput({token: tokenIn, maxAmount: inputAmount}); - - HybridOutput[] memory outputs = new HybridOutput[](1); - outputs[0] = HybridOutput({token: address(tokenOut), minAmount: outputMinAmount, recipient: swapper}); - - HybridCosignerData memory cosignerData = HybridCosignerData({ - auctionTargetBlock: block.number, - supplementalPriceCurve: new uint256[](0), - exclusiveFiller: address(0), // No exclusivity - exclusivityOverrideBps: 0, - exclusivityEndBlock: 0 - }); - - HybridOrder memory order = HybridOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 1000) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(resolver), - cosigner: cosigner, - input: input, - outputs: outputs, - auctionStartBlock: block.number, - baselinePriorityFee: 0, - scalingFactor: NEUTRAL_SCALING_FACTOR, - priceCurve: priceCurve, - cosignerData: cosignerData, - cosignature: bytes("") - }); - order.cosignature = cosignOrder(order.hash(), cosignerData); - - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - - uint256 swapperBalanceBefore = tokenOut.balanceOf(swapper); - fillContract.execute(signedOrder); - - assertEq(tokenIn.balanceOf(swapper), 1000e18 - inputAmount); - assertEq(tokenOut.balanceOf(swapper), swapperBalanceBefore + outputMinAmount); - } - - /// @notice Test that priority-only orders ignore exclusivity when end block is unset - function test_PriorityOnly_IgnoresExclusivity_WhenEndBlockUnset() public { - uint256 inputAmount = 100e18; - uint256 outputMinAmount = 95e18; - - tokenIn.forceApprove(swapper, address(permit2), type(uint256).max); - - HybridInput memory input = HybridInput({token: tokenIn, maxAmount: inputAmount}); - - HybridOutput[] memory outputs = new HybridOutput[](1); - outputs[0] = HybridOutput({token: address(tokenOut), minAmount: outputMinAmount, recipient: swapper}); - - HybridCosignerData memory cosignerData = HybridCosignerData({ - auctionTargetBlock: 0, - supplementalPriceCurve: new uint256[](0), - exclusiveFiller: EXCLUSIVE_FILLER, - exclusivityOverrideBps: 0, - exclusivityEndBlock: 0 - }); - - HybridOrder memory order = HybridOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 1000) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(resolver), - cosigner: cosigner, - input: input, - outputs: outputs, - auctionStartBlock: block.number, - baselinePriorityFee: 0, - scalingFactor: NEUTRAL_SCALING_FACTOR, - priceCurve: new uint256[](0), - cosignerData: cosignerData, - cosignature: bytes("") - }); - order.cosignature = cosignOrder(order.hash(), cosignerData); - - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - - uint256 swapperBalanceBefore = tokenOut.balanceOf(swapper); - fillContract.execute(signedOrder); - - assertEq(tokenIn.balanceOf(swapper), 1000e18 - inputAmount); - assertEq(tokenOut.balanceOf(swapper), swapperBalanceBefore + outputMinAmount); - } - - /// @notice Test exclusivity with multiple outputs - function test_ExclusivityOverride_MultipleOutputs() public { - tokenIn.forceApprove(swapper, address(permit2), type(uint256).max); - - HybridOutput[] memory outputs = new HybridOutput[](2); - outputs[0] = HybridOutput({token: address(tokenOut), minAmount: 50e18, recipient: swapper}); - outputs[1] = HybridOutput({token: address(tokenOut2), minAmount: 45e18, recipient: swapper}); - - HybridCosignerData memory cosignerData = HybridCosignerData({ - auctionTargetBlock: block.number, - supplementalPriceCurve: new uint256[](0), - exclusiveFiller: EXCLUSIVE_FILLER, - exclusivityOverrideBps: 200, // 2% - exclusivityEndBlock: block.number - }); - - HybridOrder memory order = HybridOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 1000) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(resolver), - cosigner: cosigner, - input: HybridInput({token: tokenIn, maxAmount: 100e18}), - outputs: outputs, - auctionStartBlock: block.number, - baselinePriorityFee: 0, - scalingFactor: NEUTRAL_SCALING_FACTOR, - priceCurve: _neutralPriceCurve(), - cosignerData: cosignerData, - cosignature: bytes("") - }); - order.cosignature = cosignOrder(order.hash(), cosignerData); - - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - - uint256 swapperBalance1Before = tokenOut.balanceOf(swapper); - uint256 swapperBalance2Before = tokenOut2.balanceOf(swapper); - fillContract.execute(signedOrder); - - // Both outputs should have override applied (2% = 200 bps) - assertEq(tokenIn.balanceOf(swapper), 1000e18 - 100e18); - assertEq(tokenOut.balanceOf(swapper), swapperBalance1Before + (50e18 * 10200 / 10000)); - assertEq(tokenOut2.balanceOf(swapper), swapperBalance2Before + (45e18 * 10200 / 10000)); - } - - /// @notice Test that pure priority-only order with STRICT exclusivity still allows any filler when end block is unset - /// @dev Unlike Dutch orders which revert with NoExclusiveOverride, pure PGA should succeed when exclusivity is unset - function test_PriorityOnly_StrictExclusivity_StillFillable_WhenEndBlockUnset() public { - uint256 inputAmount = 100e18; - uint256 outputMinAmount = 95e18; - - tokenIn.forceApprove(swapper, address(permit2), type(uint256).max); - - HybridInput memory input = HybridInput({token: tokenIn, maxAmount: inputAmount}); - - HybridOutput[] memory outputs = new HybridOutput[](1); - outputs[0] = HybridOutput({token: address(tokenOut), minAmount: outputMinAmount, recipient: swapper}); - - // Set strict exclusivity (overrideBps = 0) which would revert for Dutch orders - HybridCosignerData memory cosignerData = HybridCosignerData({ - auctionTargetBlock: block.number, - supplementalPriceCurve: new uint256[](0), - exclusiveFiller: EXCLUSIVE_FILLER, - exclusivityOverrideBps: 0, // Strict exclusivity - exclusivityEndBlock: 0 - }); - - HybridOrder memory order = HybridOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 1000) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(resolver), - cosigner: cosigner, - input: input, - outputs: outputs, - auctionStartBlock: block.number, - baselinePriorityFee: 0, - scalingFactor: NEUTRAL_SCALING_FACTOR, - priceCurve: new uint256[](0), // Empty = pure priority-only - cosignerData: cosignerData, - cosignature: bytes("") - }); - order.cosignature = cosignOrder(order.hash(), cosignerData); - - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - - // Should NOT revert even though fillContract is not the exclusive filler - // because pure priority-only orders skip exclusivity when end block is unset - uint256 swapperBalanceBefore = tokenOut.balanceOf(swapper); - fillContract.execute(signedOrder); - - assertEq(tokenIn.balanceOf(swapper), 1000e18 - inputAmount); - assertEq(tokenOut.balanceOf(swapper), swapperBalanceBefore + outputMinAmount); - } - - /// @notice Test that pure priority-only order ignores exclusivity with supplemental curve when end block is unset - /// @dev Supplemental curve on empty base curve results in empty effective curve - function test_PriorityOnly_WithSupplementalCurve_IgnoresExclusivity_WhenEndBlockUnset() public { - uint256 inputAmount = 100e18; - uint256 outputMinAmount = 95e18; - - tokenIn.forceApprove(swapper, address(permit2), type(uint256).max); - - HybridInput memory input = HybridInput({token: tokenIn, maxAmount: inputAmount}); - - HybridOutput[] memory outputs = new HybridOutput[](1); - outputs[0] = HybridOutput({token: address(tokenOut), minAmount: outputMinAmount, recipient: swapper}); - - // Cosigner provides supplemental curve, but base curve is empty - uint256[] memory supplementalCurve = new uint256[](1); - supplementalCurve[0] = uint256(PriceCurveElement.unwrap(PriceCurveLib.create(10, 1.1e18))); - - HybridCosignerData memory cosignerData = HybridCosignerData({ - auctionTargetBlock: block.number, - supplementalPriceCurve: supplementalCurve, // Non-empty supplemental - exclusiveFiller: EXCLUSIVE_FILLER, - exclusivityOverrideBps: 0, // Strict exclusivity - exclusivityEndBlock: 0 - }); - - HybridOrder memory order = HybridOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 1000) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(resolver), - cosigner: cosigner, - input: input, - outputs: outputs, - auctionStartBlock: block.number, - baselinePriorityFee: 0, - scalingFactor: NEUTRAL_SCALING_FACTOR, - priceCurve: new uint256[](0), // Empty base curve - cosignerData: cosignerData, - cosignature: bytes("") - }); - order.cosignature = cosignOrder(order.hash(), cosignerData); - - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - - // Should NOT revert - supplemental curve on empty base = empty effective curve - // so exclusivity is skipped when end block is unset - uint256 swapperBalanceBefore = tokenOut.balanceOf(swapper); - fillContract.execute(signedOrder); - - assertEq(tokenIn.balanceOf(swapper), 1000e18 - inputAmount); - assertEq(tokenOut.balanceOf(swapper), swapperBalanceBefore + outputMinAmount); - } - - /// @notice Test that pure priority-only order with non-zero target block ignores exclusivity when end block is unset - /// @dev At exactly auctionTargetBlock, Dutch orders would enforce exclusivity, but PGA should not if exclusivity is unset - function test_PriorityOnly_NonZeroTargetBlock_IgnoresExclusivity_WhenEndBlockUnset() public { - uint256 inputAmount = 100e18; - uint256 outputMinAmount = 95e18; - uint256 targetBlock = block.number + 5; - - tokenIn.forceApprove(swapper, address(permit2), type(uint256).max); - - HybridInput memory input = HybridInput({token: tokenIn, maxAmount: inputAmount}); - - HybridOutput[] memory outputs = new HybridOutput[](1); - outputs[0] = HybridOutput({token: address(tokenOut), minAmount: outputMinAmount, recipient: swapper}); - - HybridCosignerData memory cosignerData = HybridCosignerData({ - auctionTargetBlock: targetBlock, - supplementalPriceCurve: new uint256[](0), - exclusiveFiller: EXCLUSIVE_FILLER, - exclusivityOverrideBps: 0, // Strict exclusivity - exclusivityEndBlock: 0 - }); - - HybridOrder memory order = HybridOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 1000) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(resolver), - cosigner: cosigner, - input: input, - outputs: outputs, - auctionStartBlock: targetBlock, - baselinePriorityFee: 0, - scalingFactor: NEUTRAL_SCALING_FACTOR, - priceCurve: new uint256[](0), // Empty = pure priority-only - cosignerData: cosignerData, - cosignature: bytes("") - }); - order.cosignature = cosignOrder(order.hash(), cosignerData); - - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - - // Roll to exactly the target block - vm.roll(targetBlock); - - // Should NOT revert even at exactly targetBlock - // For Dutch orders, this would be within exclusivity period - // But for pure PGA, exclusivity is skipped when end block is unset - uint256 swapperBalanceBefore = tokenOut.balanceOf(swapper); - fillContract.execute(signedOrder); - - assertEq(tokenIn.balanceOf(swapper), 1000e18 - inputAmount); - assertEq(tokenOut.balanceOf(swapper), swapperBalanceBefore + outputMinAmount); - } - - /// @notice Test that fixed-price order (no curve, neutral scaling) ignores exclusivity when end block is unset - /// @dev Fixed-price is a degenerate case that also has empty curve - function test_FixedPrice_IgnoresExclusivity_WhenEndBlockUnset() public { - uint256 inputAmount = 100e18; - uint256 outputMinAmount = 95e18; - - tokenIn.forceApprove(swapper, address(permit2), type(uint256).max); - - HybridInput memory input = HybridInput({token: tokenIn, maxAmount: inputAmount}); - - HybridOutput[] memory outputs = new HybridOutput[](1); - outputs[0] = HybridOutput({token: address(tokenOut), minAmount: outputMinAmount, recipient: swapper}); - - HybridCosignerData memory cosignerData = HybridCosignerData({ - auctionTargetBlock: 0, // No target block - supplementalPriceCurve: new uint256[](0), - exclusiveFiller: EXCLUSIVE_FILLER, - exclusivityOverrideBps: 0, // Strict exclusivity - exclusivityEndBlock: 0 - }); - - HybridOrder memory order = HybridOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 1000) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(resolver), - cosigner: cosigner, - input: input, - outputs: outputs, - auctionStartBlock: 0, - baselinePriorityFee: 0, - scalingFactor: NEUTRAL_SCALING_FACTOR, // Neutral = no priority fee impact - priceCurve: new uint256[](0), // Empty = no Dutch decay - cosignerData: cosignerData, - cosignature: bytes("") - }); - order.cosignature = cosignOrder(order.hash(), cosignerData); - - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - - // Should NOT revert - fixed-price orders also skip exclusivity when end block is unset - uint256 swapperBalanceBefore = tokenOut.balanceOf(swapper); - fillContract.execute(signedOrder); - - assertEq(tokenIn.balanceOf(swapper), 1000e18 - inputAmount); - assertEq(tokenOut.balanceOf(swapper), swapperBalanceBefore + outputMinAmount); - } - - /// @notice Test pure priority-only with actual priority fee scaling ignores exclusivity when end block is unset - /// @dev Verifies the fix works when scalingFactor != 1e18 (actual PGA behavior) - function test_PriorityOnly_WithPriorityScaling_IgnoresExclusivity_WhenEndBlockUnset() public { - uint256 inputAmount = 100e18; - uint256 outputMinAmount = 95e18; - uint256 priorityScalingFactor = 1.001e18; // Scaling factor for priority fee - - tokenIn.forceApprove(swapper, address(permit2), type(uint256).max); - - HybridInput memory input = HybridInput({token: tokenIn, maxAmount: inputAmount}); - - HybridOutput[] memory outputs = new HybridOutput[](1); - outputs[0] = HybridOutput({token: address(tokenOut), minAmount: outputMinAmount, recipient: swapper}); - - HybridCosignerData memory cosignerData = HybridCosignerData({ - auctionTargetBlock: 0, - supplementalPriceCurve: new uint256[](0), - exclusiveFiller: EXCLUSIVE_FILLER, - exclusivityOverrideBps: 0, // Strict exclusivity - exclusivityEndBlock: 0 - }); - - HybridOrder memory order = HybridOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 1000) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(resolver), - cosigner: cosigner, - input: input, - outputs: outputs, - auctionStartBlock: 0, - baselinePriorityFee: 0, - scalingFactor: priorityScalingFactor, // Non-neutral = actual PGA - priceCurve: new uint256[](0), // Empty = pure priority-only - cosignerData: cosignerData, - cosignature: bytes("") - }); - order.cosignature = cosignOrder(order.hash(), cosignerData); - - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - - // Should NOT revert - pure PGA skips exclusivity when end block is unset - uint256 swapperBalanceBefore = tokenOut.balanceOf(swapper); - fillContract.execute(signedOrder); - - assertEq(tokenIn.balanceOf(swapper), 1000e18 - inputAmount); - // Output should be at least minAmount (may be higher due to priority fee scaling) - assertGe(tokenOut.balanceOf(swapper), swapperBalanceBefore + outputMinAmount); - } -} diff --git a/test/v4/resolvers/PriorityAuctionResolver.t.sol b/test/v4/resolvers/PriorityAuctionResolver.t.sol deleted file mode 100644 index f75a80b5..00000000 --- a/test/v4/resolvers/PriorityAuctionResolver.t.sol +++ /dev/null @@ -1,767 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {Test} from "forge-std/Test.sol"; -import {IPermit2} from "permit2/src/interfaces/IPermit2.sol"; -import {DeployPermit2} from "../../util/DeployPermit2.sol"; -import {PermitSignature} from "../../util/PermitSignature.sol"; -import {OrderInfo} from "../../../src/v4/base/ReactorStructs.sol"; -import {SignedOrder} from "../../../src/base/ReactorStructs.sol"; -import {ReactorEvents} from "../../../src/base/ReactorEvents.sol"; -import {Reactor} from "../../../src/v4/Reactor.sol"; -import {PriorityAuctionResolver} from "../../../src/v4/resolvers/PriorityAuctionResolver.sol"; -import {PriorityOrder, PriorityOrderLib} from "../../../src/v4/lib/PriorityOrderLib.sol"; -import {PriorityInput, PriorityOutput, PriorityCosignerData} from "../../../src/lib/PriorityOrderLib.sol"; -import {PriorityFeeLib} from "../../../src/lib/PriorityFeeLib.sol"; -import {CosignerLib} from "../../../src/lib/CosignerLib.sol"; -import {OrderInfoBuilder} from "../util/OrderInfoBuilder.sol"; -import {MockERC20} from "../../util/mock/MockERC20.sol"; -import {MockFillContract} from "../util/mock/MockFillContract.sol"; -import {MockFeeController} from "../../util/mock/MockFeeController.sol"; -import {TokenTransferHook} from "../../../src/v4/hooks/TokenTransferHook.sol"; - -contract PriorityAuctionResolverTest is ReactorEvents, Test, PermitSignature, DeployPermit2 { - using OrderInfoBuilder for OrderInfo; - using PriorityOrderLib for PriorityOrder; - using PriorityFeeLib for PriorityInput; - using PriorityFeeLib for PriorityOutput; - using PriorityFeeLib for PriorityOutput[]; - - uint256 constant ONE = 10 ** 18; - uint256 constant COSIGNER_PRIVATE_KEY = 0x99999999; - address internal constant PROTOCOL_FEE_OWNER = address(1); - - MockERC20 tokenIn; - MockERC20 tokenOut; - MockERC20 tokenOut2; - MockFillContract fillContract; - IPermit2 permit2; - TokenTransferHook tokenTransferHook; - MockFeeController feeController; - address feeRecipient; - Reactor reactor; - PriorityAuctionResolver resolver; - uint256 swapperPrivateKey; - address swapper; - address cosigner; - - function setUp() public { - tokenIn = new MockERC20("Input", "IN", 18); - tokenOut = new MockERC20("Output", "OUT", 18); - tokenOut2 = new MockERC20("Output2", "OUT2", 18); - swapperPrivateKey = 0x12341234; - swapper = vm.addr(swapperPrivateKey); - cosigner = vm.addr(COSIGNER_PRIVATE_KEY); - permit2 = IPermit2(deployPermit2()); - feeRecipient = makeAddr("feeRecipient"); - - feeController = new MockFeeController(feeRecipient); - reactor = new Reactor(PROTOCOL_FEE_OWNER, permit2); - resolver = new PriorityAuctionResolver(permit2); - tokenTransferHook = new TokenTransferHook(permit2, reactor); - - fillContract = new MockFillContract(address(reactor)); - - // Provide tokens for tests - tokenIn.mint(address(swapper), ONE * 100); - tokenOut.mint(address(fillContract), ONE * 100); - - // Provide ETH to fill contract for native transfers - vm.deal(address(fillContract), type(uint256).max); - } - - /// @dev Create and sign a PriorityOrder - function createAndSignOrder(PriorityOrder memory order) - internal - view - returns (SignedOrder memory signedOrder, bytes32 orderHash) - { - // Use the new witness hash that includes resolver and full order - orderHash = order.witnessHash(address(resolver)); - - // Sign the order with swapper's key - bytes memory sig = signOrder(swapperPrivateKey, address(permit2), order); - - // Encode the order data for the resolver - bytes memory orderData = abi.encode(order); - - // Wrap with resolver address - bytes memory encodedOrder = abi.encode(address(resolver), orderData); - - signedOrder = SignedOrder(encodedOrder, sig); - } - - /// @dev Helper to cosign an order - function cosignOrder(bytes32 orderHash, PriorityCosignerData memory cosignerData) - internal - view - returns (bytes memory cosignature) - { - bytes32 msgHash = keccak256(abi.encodePacked(orderHash, block.chainid, abi.encode(cosignerData))); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(COSIGNER_PRIVATE_KEY, msgHash); - cosignature = bytes.concat(r, s, bytes1(v)); - } - - /// @dev Test a basic order when output priority fee is non zero - function testExecuteWithOutputPriorityFee() public { - uint256 priorityFee = 100 wei; - vm.txGasPrice(priorityFee); - - uint256 inputAmount = 1 ether; - uint256 outputAmount = 1 ether; - uint256 inputMpsPerPriorityFeeWei = 0; - uint256 outputMpsPerPriorityFeeWei = 1; // exact input - uint256 deadline = block.timestamp + 1000; - - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - - PriorityOutput[] memory outputs = new PriorityOutput[](1); - outputs[0] = PriorityOutput({ - token: address(tokenOut), - amount: outputAmount, - mpsPerPriorityFeeWei: outputMpsPerPriorityFeeWei, - recipient: swapper - }); - - uint256 scaledOutputAmount = outputs[0].scale(priorityFee).amount; - - PriorityCosignerData memory cosignerData = PriorityCosignerData({auctionTargetBlock: block.number}); - - PriorityOrder memory order = PriorityOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(deadline) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(resolver), - cosigner: cosigner, - auctionStartBlock: block.number, - baselinePriorityFeeWei: 0, - input: PriorityInput({ - token: tokenIn, amount: inputAmount, mpsPerPriorityFeeWei: inputMpsPerPriorityFeeWei - }), - outputs: outputs, - cosignerData: cosignerData, - cosignature: bytes("") - }); - order.cosignature = cosignOrder(order.hash(), cosignerData); - - (SignedOrder memory signedOrder, bytes32 orderHash) = createAndSignOrder(order); - - uint256 swapperInputBalanceStart = tokenIn.balanceOf(address(swapper)); - uint256 swapperOutputBalanceStart = tokenOut.balanceOf(address(swapper)); - - vm.expectEmit(true, true, true, true, address(reactor)); - emit Fill(orderHash, address(fillContract), swapper, order.info.nonce); - - fillContract.execute(signedOrder); - vm.snapshotGasLastCall("Reactor_PriorityOutputFee"); - - assertEq(tokenOut.balanceOf(address(swapper)), swapperOutputBalanceStart + scaledOutputAmount); - assertEq(tokenIn.balanceOf(address(swapper)), swapperInputBalanceStart - inputAmount); - } - - /// @dev Test with baseline priority fee - function testExecuteWithOutputPriorityFeeAndBaselinePriorityFee() public { - uint256 baselinePriorityFeeWei = 1 gwei; - uint256 priorityFee = baselinePriorityFeeWei + 100 wei; - vm.txGasPrice(priorityFee); - - uint256 inputAmount = 1 ether; - uint256 outputAmount = 1 ether; - uint256 inputMpsPerPriorityFeeWei = 0; - uint256 outputMpsPerPriorityFeeWei = 1; - uint256 deadline = block.timestamp + 1000; - - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - - PriorityOutput[] memory outputs = new PriorityOutput[](1); - outputs[0] = PriorityOutput({ - token: address(tokenOut), - amount: outputAmount, - mpsPerPriorityFeeWei: outputMpsPerPriorityFeeWei, - recipient: swapper - }); - - // Should only scale by the difference - uint256 scaledOutputAmount = outputs[0].scale(priorityFee - baselinePriorityFeeWei).amount; - - PriorityCosignerData memory cosignerData = PriorityCosignerData({auctionTargetBlock: block.number}); - - PriorityOrder memory order = PriorityOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(deadline) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(resolver), - cosigner: cosigner, - auctionStartBlock: block.number, - baselinePriorityFeeWei: baselinePriorityFeeWei, - input: PriorityInput({ - token: tokenIn, amount: inputAmount, mpsPerPriorityFeeWei: inputMpsPerPriorityFeeWei - }), - outputs: outputs, - cosignerData: cosignerData, - cosignature: bytes("") - }); - order.cosignature = cosignOrder(order.hash(), cosignerData); - - (SignedOrder memory signedOrder, bytes32 orderHash) = createAndSignOrder(order); - - uint256 swapperInputBalanceStart = tokenIn.balanceOf(address(swapper)); - uint256 swapperOutputBalanceStart = tokenOut.balanceOf(address(swapper)); - - vm.expectEmit(true, true, true, true, address(reactor)); - emit Fill(orderHash, address(fillContract), swapper, order.info.nonce); - - fillContract.execute(signedOrder); - vm.snapshotGasLastCall("Reactor_PriorityOutputFeeWithBaseline"); - - assertEq(tokenOut.balanceOf(address(swapper)), swapperOutputBalanceStart + scaledOutputAmount); - assertEq(tokenIn.balanceOf(address(swapper)), swapperInputBalanceStart - inputAmount); - } - - /// @dev Test when priority fee is less than baseline (no scaling) - function testExecuteWithOutputPriorityFeeLessThanBaseline() public { - uint256 baselinePriorityFeeWei = 1 gwei; - uint256 priorityFee = baselinePriorityFeeWei - 1; - vm.txGasPrice(priorityFee); - - uint256 inputAmount = 1 ether; - uint256 outputAmount = 1 ether; - uint256 inputMpsPerPriorityFeeWei = 0; - uint256 outputMpsPerPriorityFeeWei = 1; - uint256 deadline = block.timestamp + 1000; - - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - - PriorityOutput[] memory outputs = new PriorityOutput[](1); - outputs[0] = PriorityOutput({ - token: address(tokenOut), - amount: outputAmount, - mpsPerPriorityFeeWei: outputMpsPerPriorityFeeWei, - recipient: swapper - }); - - PriorityCosignerData memory cosignerData = PriorityCosignerData({auctionTargetBlock: block.number}); - - PriorityOrder memory order = PriorityOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(deadline) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(resolver), - cosigner: cosigner, - auctionStartBlock: block.number, - baselinePriorityFeeWei: baselinePriorityFeeWei, - input: PriorityInput({ - token: tokenIn, amount: inputAmount, mpsPerPriorityFeeWei: inputMpsPerPriorityFeeWei - }), - outputs: outputs, - cosignerData: cosignerData, - cosignature: bytes("") - }); - order.cosignature = cosignOrder(order.hash(), cosignerData); - - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - - uint256 swapperInputBalanceStart = tokenIn.balanceOf(address(swapper)); - uint256 swapperOutputBalanceStart = tokenOut.balanceOf(address(swapper)); - - fillContract.execute(signedOrder); - - // No scaling should be applied since priority fee < baseline - assertEq(tokenOut.balanceOf(address(swapper)), swapperOutputBalanceStart + outputAmount); - assertEq(tokenIn.balanceOf(address(swapper)), swapperInputBalanceStart - inputAmount); - } - - /// @dev Test with input priority fee scaling - function testExecuteWithInputPriorityFee() public { - uint256 priorityFee = 100 wei; - vm.txGasPrice(priorityFee); - - uint256 inputAmount = 1 ether; - uint256 outputAmount = 1 ether; - uint256 inputMpsPerPriorityFeeWei = 1; // exact output - uint256 outputMpsPerPriorityFeeWei = 0; - uint256 deadline = block.timestamp + 1000; - - tokenIn.forceApprove(swapper, address(permit2), inputAmount * 2); - - PriorityInput memory input = - PriorityInput({token: tokenIn, amount: inputAmount, mpsPerPriorityFeeWei: inputMpsPerPriorityFeeWei}); - - PriorityOutput[] memory outputs = new PriorityOutput[](1); - outputs[0] = PriorityOutput({ - token: address(tokenOut), - amount: outputAmount, - mpsPerPriorityFeeWei: outputMpsPerPriorityFeeWei, - recipient: swapper - }); - - uint256 scaledInputAmount = input.scale(priorityFee).amount; - - PriorityCosignerData memory cosignerData = PriorityCosignerData({auctionTargetBlock: block.number}); - - PriorityOrder memory order = PriorityOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(deadline) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(resolver), - cosigner: cosigner, - auctionStartBlock: block.number, - baselinePriorityFeeWei: 0, - input: input, - outputs: outputs, - cosignerData: cosignerData, - cosignature: bytes("") - }); - order.cosignature = cosignOrder(order.hash(), cosignerData); - - (SignedOrder memory signedOrder, bytes32 orderHash) = createAndSignOrder(order); - - uint256 swapperInputBalanceStart = tokenIn.balanceOf(address(swapper)); - uint256 swapperOutputBalanceStart = tokenOut.balanceOf(address(swapper)); - - vm.expectEmit(true, true, true, true, address(reactor)); - emit Fill(orderHash, address(fillContract), swapper, order.info.nonce); - - fillContract.execute(signedOrder); - vm.snapshotGasLastCall("Reactor_PriorityInputFee"); - - assertEq(tokenIn.balanceOf(address(swapper)), swapperInputBalanceStart - scaledInputAmount); - assertEq(tokenOut.balanceOf(address(swapper)), swapperOutputBalanceStart + outputAmount); - } - - /// @dev Test cosigner override of auction start block - function testExecuteWithOverrideAuctionStartBlock() public { - PriorityOutput[] memory outputs = new PriorityOutput[](1); - outputs[0] = PriorityOutput({token: address(tokenOut), amount: 0, mpsPerPriorityFeeWei: 0, recipient: swapper}); - - PriorityCosignerData memory cosignerData = PriorityCosignerData({auctionTargetBlock: block.number + 5}); - - PriorityOrder memory order = PriorityOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 1000) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(resolver), - cosigner: cosigner, - auctionStartBlock: block.number + 10, - baselinePriorityFeeWei: 0, - input: PriorityInput({token: tokenIn, amount: 0, mpsPerPriorityFeeWei: 0}), - outputs: outputs, - cosignerData: cosignerData, - cosignature: bytes("") - }); - order.cosignature = cosignOrder(order.hash(), cosignerData); - - (SignedOrder memory signedOrder, bytes32 orderHash) = createAndSignOrder(order); - - vm.roll(block.number + 5); - - vm.expectEmit(true, true, true, true, address(reactor)); - emit Fill(orderHash, address(fillContract), swapper, order.info.nonce); - - fillContract.execute(signedOrder); - vm.snapshotGasLastCall("Reactor_OverrideAuctionTargetBlock"); - } - - /// @dev Test execution after auction start block - function testExecuteAfterAuctionStartBlock() public { - PriorityOutput[] memory outputs = new PriorityOutput[](1); - outputs[0] = PriorityOutput({token: address(tokenOut), amount: 0, mpsPerPriorityFeeWei: 0, recipient: swapper}); - - PriorityCosignerData memory cosignerData = PriorityCosignerData({auctionTargetBlock: block.number}); - - PriorityOrder memory order = PriorityOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 1000) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(resolver), - cosigner: cosigner, - auctionStartBlock: block.number, - baselinePriorityFeeWei: 0, - input: PriorityInput({token: tokenIn, amount: 0, mpsPerPriorityFeeWei: 0}), - outputs: outputs, - cosignerData: cosignerData, - cosignature: bytes("") - }); - order.cosignature = cosignOrder(order.hash(), cosignerData); - - (SignedOrder memory signedOrder, bytes32 orderHash) = createAndSignOrder(order); - - vm.roll(block.number + 1); - - vm.expectEmit(true, true, true, true, address(reactor)); - emit Fill(orderHash, address(fillContract), swapper, order.info.nonce); - - fillContract.execute(signedOrder); - } - - /// @dev Test with invalid cosigner auctionTargetBlock still works at auctionStartBlock - function testExecuteInvalidCosignerAuctionTargetBlock() public { - PriorityOutput[] memory outputs = new PriorityOutput[](1); - outputs[0] = PriorityOutput({token: address(tokenOut), amount: 0, mpsPerPriorityFeeWei: 0, recipient: swapper}); - - PriorityCosignerData memory cosignerData = PriorityCosignerData({auctionTargetBlock: block.number + 1}); - - PriorityOrder memory order = PriorityOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 1000) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(resolver), - cosigner: cosigner, - auctionStartBlock: block.number, - baselinePriorityFeeWei: 0, - input: PriorityInput({token: tokenIn, amount: 0, mpsPerPriorityFeeWei: 0}), - outputs: outputs, - cosignerData: cosignerData, - cosignature: bytes("") - }); - order.cosignature = cosignOrder(order.hash(), cosignerData); - - (SignedOrder memory signedOrder, bytes32 orderHash) = createAndSignOrder(order); - - vm.expectEmit(true, true, true, true, address(reactor)); - emit Fill(orderHash, address(fillContract), swapper, order.info.nonce); - - fillContract.execute(signedOrder); - } - - /// @dev Test execution after auctionStartBlock with invalid cosignature - function testExecuteAfterAuctionStartBlockWithInvalidCosignature() public { - address wrongCosigner = makeAddr("wrongCosigner"); - - PriorityOutput[] memory outputs = new PriorityOutput[](1); - outputs[0] = PriorityOutput({token: address(tokenOut), amount: 0, mpsPerPriorityFeeWei: 0, recipient: swapper}); - - PriorityCosignerData memory cosignerData = PriorityCosignerData({auctionTargetBlock: block.number}); - - PriorityOrder memory order = PriorityOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 1000) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(resolver), - cosigner: wrongCosigner, - auctionStartBlock: block.number + 1, - baselinePriorityFeeWei: 0, - input: PriorityInput({token: tokenIn, amount: 0, mpsPerPriorityFeeWei: 0}), - outputs: outputs, - cosignerData: cosignerData, - cosignature: bytes.concat(keccak256("invalidSignature"), keccak256("invalidSignature"), hex"33") - }); - - (SignedOrder memory signedOrder, bytes32 orderHash) = createAndSignOrder(order); - - vm.roll(block.number + 1); - - vm.expectEmit(true, true, true, true, address(reactor)); - emit Fill(orderHash, address(fillContract), swapper, order.info.nonce); - - fillContract.execute(signedOrder); - } - - /// @dev Test revert when both input and output scale with priority fee - function testRevertsWithInputOutputScaling() public { - uint256 mpsPerPriorityFeeWei = 1; - - PriorityOutput[] memory outputs = new PriorityOutput[](1); - outputs[0] = PriorityOutput({ - token: address(tokenOut), amount: 0, mpsPerPriorityFeeWei: mpsPerPriorityFeeWei, recipient: swapper - }); - - PriorityCosignerData memory cosignerData = PriorityCosignerData({auctionTargetBlock: block.number}); - - PriorityOrder memory order = PriorityOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 1000) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(resolver), - cosigner: cosigner, - auctionStartBlock: block.number, - baselinePriorityFeeWei: 0, - input: PriorityInput({token: tokenIn, amount: 0, mpsPerPriorityFeeWei: mpsPerPriorityFeeWei}), - outputs: outputs, - cosignerData: cosignerData, - cosignature: bytes("") - }); - order.cosignature = cosignOrder(order.hash(), cosignerData); - - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - - vm.expectRevert(PriorityAuctionResolver.InputOutputScaling.selector); - fillContract.execute(signedOrder); - } - - /// @dev Test revert before auction start block - function testRevertsBeforeAuctionStartBlock() public { - PriorityOutput[] memory outputs = new PriorityOutput[](1); - outputs[0] = PriorityOutput({token: address(tokenOut), amount: 0, mpsPerPriorityFeeWei: 0, recipient: swapper}); - - PriorityCosignerData memory cosignerData = PriorityCosignerData({auctionTargetBlock: block.number + 1}); - - PriorityOrder memory order = PriorityOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 1000) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(resolver), - cosigner: cosigner, - auctionStartBlock: block.number + 1, - baselinePriorityFeeWei: 0, - input: PriorityInput({token: tokenIn, amount: 0, mpsPerPriorityFeeWei: 0}), - outputs: outputs, - cosignerData: cosignerData, - cosignature: bytes("") - }); - order.cosignature = cosignOrder(order.hash(), cosignerData); - - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - - vm.expectRevert(PriorityAuctionResolver.OrderNotFillable.selector); - fillContract.execute(signedOrder); - } - - /// @dev Test revert before cosigned auction target block - function testRevertsBeforeCosignedAuctionTargetBlock() public { - PriorityOutput[] memory outputs = new PriorityOutput[](1); - outputs[0] = PriorityOutput({token: address(tokenOut), amount: 0, mpsPerPriorityFeeWei: 0, recipient: swapper}); - - PriorityCosignerData memory cosignerData = PriorityCosignerData({auctionTargetBlock: block.number + 1}); - - PriorityOrder memory order = PriorityOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 1000) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(resolver), - cosigner: cosigner, - auctionStartBlock: block.number + 2, - baselinePriorityFeeWei: 0, - input: PriorityInput({token: tokenIn, amount: 0, mpsPerPriorityFeeWei: 0}), - outputs: outputs, - cosignerData: cosignerData, - cosignature: bytes("") - }); - order.cosignature = cosignOrder(order.hash(), cosignerData); - - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - - vm.expectRevert(PriorityAuctionResolver.OrderNotFillable.selector); - fillContract.execute(signedOrder); - } - - /// @dev Test revert with wrong cosigner - function testRevertsWrongCosigner() public { - address wrongCosigner = makeAddr("wrongCosigner"); - - PriorityOutput[] memory outputs = new PriorityOutput[](1); - outputs[0] = PriorityOutput({token: address(tokenOut), amount: 0, mpsPerPriorityFeeWei: 0, recipient: swapper}); - - PriorityCosignerData memory cosignerData = PriorityCosignerData({auctionTargetBlock: block.number}); - - PriorityOrder memory order = PriorityOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 1000) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(resolver), - cosigner: wrongCosigner, - auctionStartBlock: block.number + 1, - baselinePriorityFeeWei: 0, - input: PriorityInput({token: tokenIn, amount: 0, mpsPerPriorityFeeWei: 0}), - outputs: outputs, - cosignerData: cosignerData, - cosignature: bytes("") - }); - order.cosignature = cosignOrder(order.hash(), cosignerData); - - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - - vm.expectRevert(CosignerLib.InvalidCosignature.selector); - fillContract.execute(signedOrder); - } - - /// @dev Test revert with invalid cosignature - function testRevertsInvalidCosignature() public { - PriorityOutput[] memory outputs = new PriorityOutput[](1); - outputs[0] = PriorityOutput({token: address(tokenOut), amount: 0, mpsPerPriorityFeeWei: 0, recipient: swapper}); - - PriorityCosignerData memory cosignerData = PriorityCosignerData({auctionTargetBlock: block.number}); - - PriorityOrder memory order = PriorityOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 1000) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(resolver), - cosigner: cosigner, - auctionStartBlock: block.number + 1, - baselinePriorityFeeWei: 0, - input: PriorityInput({token: tokenIn, amount: 0, mpsPerPriorityFeeWei: 0}), - outputs: outputs, - cosignerData: cosignerData, - cosignature: bytes.concat(keccak256("invalidSignature"), keccak256("invalidSignature"), hex"33") - }); - - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - - vm.expectRevert(CosignerLib.InvalidCosignature.selector); - fillContract.execute(signedOrder); - } - - /// @dev Test revert with invalid chain ID in cosignature - function testRevertsInvalidChainIdCosignature() public { - uint256 invalidChainId = 0; - - PriorityOutput[] memory outputs = new PriorityOutput[](1); - outputs[0] = PriorityOutput({token: address(tokenOut), amount: 0, mpsPerPriorityFeeWei: 0, recipient: swapper}); - - PriorityCosignerData memory cosignerData = PriorityCosignerData({auctionTargetBlock: block.number}); - - PriorityOrder memory order = PriorityOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 1000) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(resolver), - cosigner: cosigner, - auctionStartBlock: block.number + 1, - baselinePriorityFeeWei: 0, - input: PriorityInput({token: tokenIn, amount: 0, mpsPerPriorityFeeWei: 0}), - outputs: outputs, - cosignerData: cosignerData, - cosignature: bytes("") - }); - - // Sign with invalid chain ID - bytes32 msgHash = keccak256(abi.encodePacked(order.hash(), invalidChainId, abi.encode(cosignerData))); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(COSIGNER_PRIVATE_KEY, msgHash); - order.cosignature = bytes.concat(r, s, bytes1(v)); - - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - - vm.expectRevert(CosignerLib.InvalidCosignature.selector); - fillContract.execute(signedOrder); - } - - /// @dev Test revert when tx gas price is below base fee - function testRevertsInvalidTxGasPrice() public { - vm.txGasPrice(0); - vm.fee(1); - - PriorityOutput[] memory outputs = new PriorityOutput[](1); - outputs[0] = PriorityOutput({token: address(tokenOut), amount: 0, mpsPerPriorityFeeWei: 0, recipient: swapper}); - - PriorityCosignerData memory cosignerData = PriorityCosignerData({auctionTargetBlock: block.number}); - - PriorityOrder memory order = PriorityOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 1000) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(resolver), - cosigner: cosigner, - auctionStartBlock: block.number, - baselinePriorityFeeWei: 0, - input: PriorityInput({token: tokenIn, amount: 0, mpsPerPriorityFeeWei: 0}), - outputs: outputs, - cosignerData: cosignerData, - cosignature: bytes("") - }); - order.cosignature = cosignOrder(order.hash(), cosignerData); - - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - - vm.expectRevert(PriorityAuctionResolver.InvalidGasPrice.selector); - fillContract.execute(signedOrder); - } - - /// @dev Test revert when nonce is already used - function testCheckPermit2Nonce() public { - // Mark nonce as used - uint256 nonce = 0; - uint256 wordPos = uint248(nonce >> 8); - uint256 bitPos = uint8(nonce); - uint256 bit = 1 << bitPos; - - vm.prank(swapper); - permit2.invalidateUnorderedNonces(wordPos, bit); - - PriorityOutput[] memory outputs = new PriorityOutput[](1); - outputs[0] = PriorityOutput({token: address(tokenOut), amount: 0, mpsPerPriorityFeeWei: 0, recipient: swapper}); - - PriorityCosignerData memory cosignerData = PriorityCosignerData({auctionTargetBlock: block.number}); - - PriorityOrder memory order = PriorityOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(block.timestamp + 1000) - .withNonce(nonce), - cosigner: cosigner, - auctionStartBlock: block.number, - baselinePriorityFeeWei: 0, - input: PriorityInput({token: tokenIn, amount: 0, mpsPerPriorityFeeWei: 0}), - outputs: outputs, - cosignerData: cosignerData, - cosignature: bytes("") - }); - - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - - vm.expectRevert(PriorityAuctionResolver.OrderAlreadyFilled.selector); - fillContract.execute(signedOrder); - } - - /// @dev Test multiple outputs with priority fee scaling - function testExecuteMultipleOutputsWithPriorityFee() public { - uint256 priorityFee = 100 wei; - vm.txGasPrice(priorityFee); - - uint256 inputAmount = 1 ether; - uint256 outputAmount1 = 0.5 ether; - uint256 outputAmount2 = 0.3 ether; - uint256 deadline = block.timestamp + 1000; - - tokenIn.forceApprove(swapper, address(permit2), inputAmount); - // Mint enough tokens to cover scaled amounts - tokenOut.mint(address(fillContract), outputAmount1 * 2); - tokenOut2.mint(address(fillContract), outputAmount2 * 2); - - PriorityOutput[] memory outputs = new PriorityOutput[](2); - outputs[0] = PriorityOutput({ - token: address(tokenOut), amount: outputAmount1, mpsPerPriorityFeeWei: 1, recipient: swapper - }); - outputs[1] = PriorityOutput({ - token: address(tokenOut2), amount: outputAmount2, mpsPerPriorityFeeWei: 2, recipient: swapper - }); - - uint256 scaledOutputAmount1 = outputs[0].scale(priorityFee).amount; - uint256 scaledOutputAmount2 = outputs[1].scale(priorityFee).amount; - - PriorityCosignerData memory cosignerData = PriorityCosignerData({auctionTargetBlock: block.number}); - - PriorityOrder memory order = PriorityOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(deadline) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(resolver), - cosigner: cosigner, - auctionStartBlock: block.number, - baselinePriorityFeeWei: 0, - input: PriorityInput({token: tokenIn, amount: inputAmount, mpsPerPriorityFeeWei: 0}), - outputs: outputs, - cosignerData: cosignerData, - cosignature: bytes("") - }); - order.cosignature = cosignOrder(order.hash(), cosignerData); - - (SignedOrder memory signedOrder, bytes32 orderHash) = createAndSignOrder(order); - - uint256 swapperInputBalanceStart = tokenIn.balanceOf(address(swapper)); - uint256 swapperOutputBalanceStart = tokenOut.balanceOf(address(swapper)); - uint256 swapperOutput2BalanceStart = tokenOut2.balanceOf(address(swapper)); - - vm.expectEmit(true, true, true, true, address(reactor)); - emit Fill(orderHash, address(fillContract), swapper, order.info.nonce); - - fillContract.execute(signedOrder); - - assertEq(tokenIn.balanceOf(address(swapper)), swapperInputBalanceStart - inputAmount); - assertEq(tokenOut.balanceOf(address(swapper)), swapperOutputBalanceStart + scaledOutputAmount1); - assertEq(tokenOut2.balanceOf(address(swapper)), swapperOutput2BalanceStart + scaledOutputAmount2); - } - - /// @dev Test permit2 nonce check - function testExecuteSignatureReplay() public { - uint256 inputAmount = 0.1 ether; - uint256 outputAmount = 0.1 ether; - uint256 deadline = block.timestamp + 1000; - - tokenIn.forceApprove(swapper, address(permit2), inputAmount * 2); - - PriorityOutput[] memory outputs = new PriorityOutput[](1); - outputs[0] = PriorityOutput({ - token: address(tokenOut), amount: outputAmount, mpsPerPriorityFeeWei: 0, recipient: swapper - }); - - PriorityCosignerData memory cosignerData = PriorityCosignerData({auctionTargetBlock: block.number}); - - PriorityOrder memory order = PriorityOrder({ - info: OrderInfoBuilder.init(address(reactor)).withSwapper(swapper).withDeadline(deadline) - .withPreExecutionHook(tokenTransferHook).withAuctionResolver(resolver), - cosigner: cosigner, - auctionStartBlock: block.number, - baselinePriorityFeeWei: 0, - input: PriorityInput({token: tokenIn, amount: inputAmount, mpsPerPriorityFeeWei: 0}), - outputs: outputs, - cosignerData: cosignerData, - cosignature: bytes("") - }); - order.cosignature = cosignOrder(order.hash(), cosignerData); - - (SignedOrder memory signedOrder,) = createAndSignOrder(order); - - // Execute once successfully - fillContract.execute(signedOrder); - - vm.expectRevert(PriorityAuctionResolver.OrderAlreadyFilled.selector); - fillContract.execute(signedOrder); - } -} diff --git a/test/v4/util/OrderInfoBuilder.sol b/test/v4/util/OrderInfoBuilder.sol deleted file mode 100644 index c118a3d9..00000000 --- a/test/v4/util/OrderInfoBuilder.sol +++ /dev/null @@ -1,83 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {OrderInfo} from "../../../src/v4/base/ReactorStructs.sol"; -import {IReactor} from "../../../src/v4/interfaces/IReactor.sol"; -import {IPreExecutionHook, IPostExecutionHook} from "../../../src/v4/interfaces/IHook.sol"; -import {IAuctionResolver} from "../../../src/v4/interfaces/IAuctionResolver.sol"; - -library OrderInfoBuilder { - function init(address reactor) internal view returns (OrderInfo memory) { - return OrderInfo({ - reactor: IReactor(reactor), - swapper: address(0), - nonce: 0, - deadline: block.timestamp + 100, - preExecutionHook: IPreExecutionHook(address(0)), - preExecutionHookData: bytes(""), - postExecutionHook: IPostExecutionHook(address(0)), - postExecutionHookData: bytes(""), - auctionResolver: IAuctionResolver(address(0)) - }); - } - - function withSwapper(OrderInfo memory info, address _swapper) internal pure returns (OrderInfo memory) { - info.swapper = _swapper; - return info; - } - - function withNonce(OrderInfo memory info, uint256 _nonce) internal pure returns (OrderInfo memory) { - info.nonce = _nonce; - return info; - } - - function withDeadline(OrderInfo memory info, uint256 _deadline) internal pure returns (OrderInfo memory) { - info.deadline = _deadline; - return info; - } - - function withPreExecutionHook(OrderInfo memory info, IPreExecutionHook _preExecutionHook) - internal - pure - returns (OrderInfo memory) - { - info.preExecutionHook = _preExecutionHook; - return info; - } - - function withPreExecutionHookData(OrderInfo memory info, bytes memory _preExecutionHookData) - internal - pure - returns (OrderInfo memory) - { - info.preExecutionHookData = _preExecutionHookData; - return info; - } - - function withPostExecutionHook(OrderInfo memory info, IPostExecutionHook _postExecutionHook) - internal - pure - returns (OrderInfo memory) - { - info.postExecutionHook = _postExecutionHook; - return info; - } - - function withPostExecutionHookData(OrderInfo memory info, bytes memory _postExecutionHookData) - internal - pure - returns (OrderInfo memory) - { - info.postExecutionHookData = _postExecutionHookData; - return info; - } - - function withAuctionResolver(OrderInfo memory info, IAuctionResolver _auctionResolver) - internal - pure - returns (OrderInfo memory) - { - info.auctionResolver = _auctionResolver; - return info; - } -} diff --git a/test/v4/util/mock/MockAuctionResolver.sol b/test/v4/util/mock/MockAuctionResolver.sol deleted file mode 100644 index 4f57cff3..00000000 --- a/test/v4/util/mock/MockAuctionResolver.sol +++ /dev/null @@ -1,74 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {IAuctionResolver} from "../../../../src/v4/interfaces/IAuctionResolver.sol"; -import {ResolvedOrder} from "../../../../src/v4/interfaces/IAuctionResolver.sol"; -import {SignedOrder} from "../../../../src/base/ReactorStructs.sol"; -import {MockOrder, MockOrderLib} from "./MockOrderLib.sol"; -import "forge-std/console2.sol"; - -/// @notice Simple auction resolver for testing UnifiedReactor basic functionality -contract MockAuctionResolver is IAuctionResolver { - using MockOrderLib for MockOrder; - - /// @inheritdoc IAuctionResolver - function resolve(SignedOrder calldata signedOrder) external view override returns (ResolvedOrder memory) { - MockOrder memory mockOrder = abi.decode(signedOrder.order, (MockOrder)); - - return ResolvedOrder({ - info: mockOrder.info, - input: mockOrder.input, - outputs: mockOrder.outputs, - sig: signedOrder.sig, - hash: mockOrder.witnessHash(address(this)), // Witness hash that includes resolver and full order - auctionResolver: address(this), - witnessTypeString: MockOrderLib.PERMIT2_ORDER_TYPE - }); - } - - /// @inheritdoc IAuctionResolver - function getPermit2OrderType() external pure override returns (string memory) { - return MockOrderLib.PERMIT2_ORDER_TYPE; - } -} - -/// @notice Simple auction resolver for testing UnifiedReactor basic functionality -contract MaliciousAuctionResolver is IAuctionResolver { - using MockOrderLib for MockOrder; - - address attacker; - - constructor() { - attacker = msg.sender; - } - - /// @inheritdoc IAuctionResolver - function resolve(SignedOrder calldata signedOrder) external view override returns (ResolvedOrder memory) { - console2.log("MaliciousAuctionResolver: resolve called"); - MockOrder memory mockOrder = abi.decode(signedOrder.order, (MockOrder)); - // @audit for the attack, we return the original order hash the user signed on - bytes32 originalHash = mockOrder.hash(); - console2.logBytes32(originalHash); - console2.log("Original auction resolver:", address(mockOrder.info.auctionResolver)); - console2.log("Replacing with:", address(this)); - - // @audit but here, we modify order data. This modifies the hash but it is not recalculated by the Reactor so it is exploitable. - mockOrder.info.auctionResolver = this; - mockOrder.outputs[0].recipient = attacker; - - return ResolvedOrder({ - info: mockOrder.info, - input: mockOrder.input, - outputs: mockOrder.outputs, - sig: signedOrder.sig, - hash: mockOrder.witnessHash(address(this)), // Witness hash (will fail signature verification) - auctionResolver: address(this), - witnessTypeString: MockOrderLib.PERMIT2_ORDER_TYPE - }); - } - - /// @inheritdoc IAuctionResolver - function getPermit2OrderType() external pure override returns (string memory) { - return MockOrderLib.PERMIT2_ORDER_TYPE; - } -} diff --git a/test/v4/util/mock/MockFeeController.sol b/test/v4/util/mock/MockFeeController.sol deleted file mode 100644 index d63842fe..00000000 --- a/test/v4/util/mock/MockFeeController.sol +++ /dev/null @@ -1,64 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {Owned} from "solmate/src/auth/Owned.sol"; -import {ResolvedOrder} from "../../../../src/v4/base/ReactorStructs.sol"; -import {OutputToken} from "../../../../src/base/ReactorStructs.sol"; -import {IProtocolFeeController} from "../../../../src/v4/interfaces/IProtocolFeeController.sol"; -import {ERC20} from "solmate/src/tokens/ERC20.sol"; - -/// @notice Mock protocol fee controller -contract MockFeeController is IProtocolFeeController, Owned(msg.sender) { - uint256 private constant BPS = 10000; - address public immutable feeRecipient; - - constructor(address _feeRecipient) { - feeRecipient = _feeRecipient; - } - - mapping(ERC20 tokenIn => mapping(address tokenOut => uint256)) public fees; - - /// @inheritdoc IProtocolFeeController - function getFeeOutputs(ResolvedOrder memory order) external view override returns (OutputToken[] memory result) { - result = new OutputToken[](order.outputs.length); - - // use max size for now, one fee per output as overestimate - ERC20 tokenIn = order.input.token; - uint256 feeCount; - - for (uint256 j = 0; j < order.outputs.length; j++) { - address outputToken = order.outputs[j].token; - uint256 fee = fees[tokenIn][outputToken]; - if (fee != 0) { - uint256 feeAmount = order.outputs[j].amount * fee / BPS; - - // check if token already has fee - bool found; - for (uint256 k = 0; k < feeCount; k++) { - OutputToken memory feeOutput = result[k]; - if (feeOutput.token == outputToken) { - found = true; - feeOutput.amount += feeAmount; - } - } - - if (!found && feeAmount > 0) { - result[feeCount] = OutputToken({token: outputToken, amount: feeAmount, recipient: feeRecipient}); - feeCount++; - } - } - } - - assembly { - // update array size to the actual number of unique fee outputs pairs - // since the array was initialized with an upper bound of the total number of outputs - // note: this leaves a few unused memory slots, but free memory pointer - // still points to the next fresh piece of memory - mstore(result, feeCount) - } - } - - function setFee(ERC20 tokenIn, address tokenOut, uint256 fee) external onlyOwner { - fees[tokenIn][tokenOut] = fee; - } -} diff --git a/test/v4/util/mock/MockFeeControllerDuplicates.sol b/test/v4/util/mock/MockFeeControllerDuplicates.sol deleted file mode 100644 index 7192155e..00000000 --- a/test/v4/util/mock/MockFeeControllerDuplicates.sol +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {Owned} from "solmate/src/auth/Owned.sol"; -import {ResolvedOrder} from "../../../../src/v4/base/ReactorStructs.sol"; -import {OutputToken} from "../../../../src/base/ReactorStructs.sol"; -import {IProtocolFeeController} from "../../../../src/v4/interfaces/IProtocolFeeController.sol"; -import {ERC20} from "solmate/src/tokens/ERC20.sol"; - -/// @notice Mock protocol fee controller that returns duplicate fee outputs -contract MockFeeControllerDuplicates is IProtocolFeeController, Owned(msg.sender) { - uint256 private constant BPS = 10000; - address public immutable feeRecipient; - - constructor(address _feeRecipient) { - feeRecipient = _feeRecipient; - } - - mapping(ERC20 tokenIn => mapping(address tokenOut => uint256)) public fees; - - /// @inheritdoc IProtocolFeeController - function getFeeOutputs(ResolvedOrder memory order) external view override returns (OutputToken[] memory result) { - result = new OutputToken[](2); - - ERC20 tokenIn = order.input.token; - address outputToken = order.outputs[0].token; - uint256 fee = fees[tokenIn][outputToken]; - uint256 feeAmount = order.outputs[0].amount * fee / BPS; - - // Return duplicate fee outputs for the same token - result[0] = OutputToken({token: outputToken, amount: feeAmount, recipient: feeRecipient}); - result[1] = OutputToken({token: outputToken, amount: feeAmount, recipient: feeRecipient}); - } - - function setFee(ERC20 tokenIn, address tokenOut, uint256 fee) external onlyOwner { - fees[tokenIn][tokenOut] = fee; - } -} diff --git a/test/v4/util/mock/MockFeeControllerInputAndOutputFees.sol b/test/v4/util/mock/MockFeeControllerInputAndOutputFees.sol deleted file mode 100644 index 3b49c97a..00000000 --- a/test/v4/util/mock/MockFeeControllerInputAndOutputFees.sol +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {Owned} from "solmate/src/auth/Owned.sol"; -import {ResolvedOrder} from "../../../../src/v4/base/ReactorStructs.sol"; -import {OutputToken} from "../../../../src/base/ReactorStructs.sol"; -import {IProtocolFeeController} from "../../../../src/v4/interfaces/IProtocolFeeController.sol"; -import {ERC20} from "solmate/src/tokens/ERC20.sol"; - -/// @notice Mock protocol fee controller taking fee on both input and output tokens -contract MockFeeControllerInputAndOutputFees is IProtocolFeeController, Owned(msg.sender) { - uint256 private constant BPS = 10000; - address public immutable feeRecipient; - - constructor(address _feeRecipient) { - feeRecipient = _feeRecipient; - } - - mapping(ERC20 token => uint256) public fees; - - /// @inheritdoc IProtocolFeeController - function getFeeOutputs(ResolvedOrder memory order) external view override returns (OutputToken[] memory result) { - result = new OutputToken[](2); - - uint256 inputFee = fees[order.input.token]; - uint256 inputFeeAmount = order.input.amount * inputFee / BPS; - result[0] = OutputToken({token: address(order.input.token), amount: inputFeeAmount, recipient: feeRecipient}); - - uint256 outputFee = fees[ERC20(order.outputs[0].token)]; - uint256 outputFeeAmount = order.outputs[0].amount * outputFee / BPS; - result[1] = - OutputToken({token: address(order.outputs[0].token), amount: outputFeeAmount, recipient: feeRecipient}); - } - - function setFee(ERC20 token, uint256 fee) external onlyOwner { - fees[token] = fee; - } -} diff --git a/test/v4/util/mock/MockFeeControllerInputFees.sol b/test/v4/util/mock/MockFeeControllerInputFees.sol deleted file mode 100644 index 20922bb8..00000000 --- a/test/v4/util/mock/MockFeeControllerInputFees.sol +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {Owned} from "solmate/src/auth/Owned.sol"; -import {ResolvedOrder} from "../../../../src/v4/base/ReactorStructs.sol"; -import {OutputToken} from "../../../../src/base/ReactorStructs.sol"; -import {IProtocolFeeController} from "../../../../src/v4/interfaces/IProtocolFeeController.sol"; -import {ERC20} from "solmate/src/tokens/ERC20.sol"; - -/// @notice Mock protocol fee controller taking fee on input tokens -contract MockFeeControllerInputFees is IProtocolFeeController, Owned(msg.sender) { - uint256 private constant BPS = 10000; - address public immutable feeRecipient; - - constructor(address _feeRecipient) { - feeRecipient = _feeRecipient; - } - - mapping(ERC20 tokenIn => uint256) public fees; - - /// @inheritdoc IProtocolFeeController - function getFeeOutputs(ResolvedOrder memory order) external view override returns (OutputToken[] memory result) { - result = new OutputToken[](1); - - uint256 fee = fees[order.input.token]; - uint256 feeAmount = order.input.amount * fee / BPS; - result[0] = OutputToken({token: address(order.input.token), amount: feeAmount, recipient: feeRecipient}); - } - - function setFee(ERC20 tokenIn, uint256 fee) external onlyOwner { - fees[tokenIn] = fee; - } -} diff --git a/test/v4/util/mock/MockFeeControllerZeroFee.sol b/test/v4/util/mock/MockFeeControllerZeroFee.sol deleted file mode 100644 index 845cc620..00000000 --- a/test/v4/util/mock/MockFeeControllerZeroFee.sol +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {Owned} from "solmate/src/auth/Owned.sol"; -import {ResolvedOrder} from "../../../../src/v4/base/ReactorStructs.sol"; -import {OutputToken} from "../../../../src/base/ReactorStructs.sol"; -import {IProtocolFeeController} from "../../../../src/v4/interfaces/IProtocolFeeController.sol"; -import {ERC20} from "solmate/src/tokens/ERC20.sol"; - -/// @notice Mock protocol fee controller that returns a fee output with token address(0) -contract MockFeeControllerZeroFee is IProtocolFeeController, Owned(msg.sender) { - uint256 private constant BPS = 10000; - address public immutable feeRecipient; - - constructor(address _feeRecipient) { - feeRecipient = _feeRecipient; - } - - mapping(ERC20 tokenIn => mapping(address tokenOut => uint256)) public fees; - - /// @inheritdoc IProtocolFeeController - function getFeeOutputs(ResolvedOrder memory order) external view override returns (OutputToken[] memory result) { - result = new OutputToken[](1); - - ERC20 tokenIn = order.input.token; - address outputToken = order.outputs[0].token; - uint256 fee = fees[tokenIn][outputToken]; - uint256 feeAmount = order.outputs[0].amount * fee / BPS; - - // Return fee output with address(0) token - result[0] = OutputToken({token: address(0), amount: feeAmount, recipient: feeRecipient}); - } - - function setFee(ERC20 tokenIn, address tokenOut, uint256 fee) external onlyOwner { - fees[tokenIn][tokenOut] = fee; - } -} diff --git a/test/v4/util/mock/MockFillContract.sol b/test/v4/util/mock/MockFillContract.sol deleted file mode 100644 index 5eae5e7c..00000000 --- a/test/v4/util/mock/MockFillContract.sol +++ /dev/null @@ -1,51 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {ERC20} from "solmate/src/tokens/ERC20.sol"; -import {CurrencyLibrary} from "../../../../src/lib/CurrencyLibrary.sol"; -import {ResolvedOrder} from "../../../../src/v4/base/ReactorStructs.sol"; -import {OutputToken, SignedOrder} from "../../../../src/base/ReactorStructs.sol"; -import {Reactor} from "../../../../src/v4/Reactor.sol"; -import {IReactorCallback} from "../../../../src/v4/interfaces/IReactorCallback.sol"; - -contract MockFillContract is IReactorCallback { - using CurrencyLibrary for address; - - Reactor immutable reactor; - - constructor(address _reactor) { - reactor = Reactor(payable(_reactor)); - } - - /// @notice assume that we already have all output tokens - function execute(SignedOrder calldata order) external { - reactor.executeWithCallback(order, hex""); - } - - /// @notice assume that we already have all output tokens - function executeWithCallback(SignedOrder calldata order, bytes calldata callbackData) external { - reactor.executeWithCallback(order, callbackData); - } - - /// @notice assume that we already have all output tokens - function executeBatch(SignedOrder[] calldata orders) external { - reactor.executeBatchWithCallback(orders, hex""); - } - - /// @notice assume that we already have all output tokens - function reactorCallback(ResolvedOrder[] memory resolvedOrders, bytes memory) external { - for (uint256 i = 0; i < resolvedOrders.length; i++) { - for (uint256 j = 0; j < resolvedOrders[i].outputs.length; j++) { - OutputToken memory output = resolvedOrders[i].outputs[j]; - if (output.token.isNative()) { - CurrencyLibrary.transferNative(address(reactor), output.amount); - } else { - ERC20(output.token).approve(address(reactor), type(uint256).max); - } - } - } - } - - /// @notice Allow the contract to receive ETH - receive() external payable {} -} diff --git a/test/v4/util/mock/MockOrderLib.sol b/test/v4/util/mock/MockOrderLib.sol deleted file mode 100644 index 835e0e15..00000000 --- a/test/v4/util/mock/MockOrderLib.sol +++ /dev/null @@ -1,85 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {OrderInfo} from "../../../../src/v4/base/ReactorStructs.sol"; -import {InputToken, OutputToken} from "../../../../src/base/ReactorStructs.sol"; -import {OrderInfoLib} from "../../../../src/v4/lib/OrderInfoLib.sol"; - -/// @dev Mock order struct for basic UnifiedReactor testing -struct MockOrder { - // generic order information - OrderInfo info; - // The token that the swapper will provide when settling the order - InputToken input; - // The tokens that must be received to satisfy the order - OutputToken[] outputs; -} - -/// @notice helpers for handling mock order objects -library MockOrderLib { - using OrderInfoLib for OrderInfo; - - bytes private constant OUTPUT_TOKEN_TYPE = "OutputToken(address token,uint256 amount,address recipient)"; - bytes32 private constant OUTPUT_TOKEN_TYPE_HASH = keccak256(OUTPUT_TOKEN_TYPE); - - bytes internal constant ORDER_TYPE = abi.encodePacked( - "MockOrder(", - "OrderInfo info,", - "address inputToken,", - "uint256 inputAmount,", - "OutputToken[] outputs)", - OrderInfoLib.ORDER_INFO_TYPE, - OUTPUT_TOKEN_TYPE - ); - bytes32 internal constant ORDER_TYPE_HASH = keccak256(ORDER_TYPE); - - // Witness wrapper that includes the resolver address for security - bytes internal constant MOCK_ORDER_WITNESS_TYPE = - abi.encodePacked("MockOrderWitness(", "address resolver,", "MockOrder order)"); - - bytes32 internal constant MOCK_ORDER_WITNESS_TYPE_HASH = - keccak256(abi.encodePacked(MOCK_ORDER_WITNESS_TYPE, ORDER_TYPE)); - - string private constant TOKEN_PERMISSIONS_TYPE = "TokenPermissions(address token,uint256 amount)"; - string internal constant PERMIT2_ORDER_TYPE = string( - abi.encodePacked("MockOrderWitness witness)", MOCK_ORDER_WITNESS_TYPE, ORDER_TYPE, TOKEN_PERMISSIONS_TYPE) - ); - - /// @notice returns the hash of an output token struct - function hash(OutputToken memory output) private pure returns (bytes32) { - return keccak256(abi.encode(OUTPUT_TOKEN_TYPE_HASH, output.token, output.amount, output.recipient)); - } - - /// @notice returns the hash of an output token struct array - function hash(OutputToken[] memory outputs) private pure returns (bytes32) { - unchecked { - bytes memory packedHashes = new bytes(32 * outputs.length); - - for (uint256 i = 0; i < outputs.length; i++) { - bytes32 outputHash = hash(outputs[i]); - assembly { - mstore(add(add(packedHashes, 0x20), mul(i, 0x20)), outputHash) - } - } - - return keccak256(packedHashes); - } - } - - /// @notice hash the given order - /// @param order the order to hash - /// @return the eip-712 order hash - function hash(MockOrder memory order) internal pure returns (bytes32) { - return keccak256( - abi.encode(ORDER_TYPE_HASH, order.info.hash(), order.input.token, order.input.amount, hash(order.outputs)) - ); - } - - /// @notice Compute the witness hash that includes the resolver address - /// @param order the MockOrder - /// @param resolver the auction resolver address - /// @return witness hash that binds the order to the resolver - function witnessHash(MockOrder memory order, address resolver) internal pure returns (bytes32) { - return keccak256(abi.encode(MOCK_ORDER_WITNESS_TYPE_HASH, resolver, hash(order))); - } -} diff --git a/test/v4/util/mock/MockPostExecutionHook.sol b/test/v4/util/mock/MockPostExecutionHook.sol deleted file mode 100644 index 9b5171d5..00000000 --- a/test/v4/util/mock/MockPostExecutionHook.sol +++ /dev/null @@ -1,56 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {IPostExecutionHook} from "../../../../src/v4/interfaces/IHook.sol"; -import {ResolvedOrder} from "../../../../src/v4/base/ReactorStructs.sol"; - -contract MockPostExecutionHook is IPostExecutionHook { - error MockPostExecutionError(); - - bool public shouldRevert = false; - - // State tracking for testing - uint256 public postExecutionCounter; - mapping(address => uint256) public fillerExecutions; - mapping(address => uint256) public swapperExecutions; - - // Last order data for verification in tests - address public lastFiller; - address public lastSwapper; - bytes32 public lastOrderHash; - uint256 public lastInputAmount; - uint256 public lastOutputAmount; - - function setShouldRevert(bool _shouldRevert) external { - shouldRevert = _shouldRevert; - } - - /// @inheritdoc IPostExecutionHook - function postExecutionHook(address filler, ResolvedOrder calldata resolvedOrder) external override { - if (shouldRevert) { - revert MockPostExecutionError(); - } - - // Track state modifications - postExecutionCounter++; - fillerExecutions[filler]++; - swapperExecutions[resolvedOrder.info.swapper]++; - - // Store last order data for test verification - lastFiller = filler; - lastSwapper = resolvedOrder.info.swapper; - lastOrderHash = resolvedOrder.hash; - lastInputAmount = resolvedOrder.input.amount; - lastOutputAmount = resolvedOrder.outputs.length > 0 ? resolvedOrder.outputs[0].amount : 0; - } - - // Helper functions for testing - function reset() external { - postExecutionCounter = 0; - lastFiller = address(0); - lastSwapper = address(0); - lastOrderHash = bytes32(0); - lastInputAmount = 0; - lastOutputAmount = 0; - } -} diff --git a/test/v4/util/mock/MockPreExecutionHook.sol b/test/v4/util/mock/MockPreExecutionHook.sol deleted file mode 100644 index 6a86bc33..00000000 --- a/test/v4/util/mock/MockPreExecutionHook.sol +++ /dev/null @@ -1,63 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {IPreExecutionHook} from "../../../../src/v4/interfaces/IHook.sol"; -import {IReactor} from "../../../../src/v4/interfaces/IReactor.sol"; -import {ResolvedOrder} from "../../../../src/v4/base/ReactorStructs.sol"; -import {IPermit2} from "permit2/src/interfaces/IPermit2.sol"; -import {IPreExecutionHook} from "../../../../src/v4/interfaces/IHook.sol"; -import {TokenTransferLib} from "../../../../src/v4/lib/TokenTransferLib.sol"; - -contract MockPreExecutionHook is IPreExecutionHook { - IPermit2 public permit2; - IReactor public reactor; - - error MockPreExecutionError(); - - bool public isValid = true; - mapping(address => bool) public invalidFillers; // true means invalid - - // State tracking for testing state modifications - uint256 public preExecutionCounter; - mapping(address => uint256) public fillerExecutions; - - modifier onlyReactor() { - require(msg.sender == address(reactor)); - _; - } - - constructor(IPermit2 _permit2, IReactor _reactor) { - permit2 = _permit2; - reactor = _reactor; - } - - function preExecutionHook(address filler, ResolvedOrder calldata resolvedOrder) external override onlyReactor { - _beforeTokenTransfer(filler, resolvedOrder); - TokenTransferLib.signatureTransferInputTokens(permit2, resolvedOrder, filler); - } - - function setValid(bool _valid) external { - isValid = _valid; - } - - function setFillerValid(address filler, bool valid) external { - invalidFillers[filler] = !valid; // If valid is true, set invalidity to false - } - - /// @notice Override the before hook to add custom validation - function _beforeTokenTransfer(address filler, ResolvedOrder calldata) internal { - // First check global validity - if (!isValid) { - revert MockPreExecutionError(); - } - - // Check filler-specific validity (reverts if filler is marked as invalid) - if (invalidFillers[filler]) { - revert MockPreExecutionError(); - } - - // Track state modifications (demonstrating non-view capability) - preExecutionCounter++; - fillerExecutions[filler]++; - } -}