From cbbbf9bd944b5c674be430d3dea47099e5793223 Mon Sep 17 00:00:00 2001 From: Denis Date: Tue, 24 Oct 2023 14:19:58 +0400 Subject: [PATCH] Add ResolverMock --- contracts/Settlement.sol | 266 ----------------- contracts/interfaces/IResolver.sol | 13 +- contracts/libraries/DynamicSuffix.sol | 42 --- contracts/libraries/FusionDetails.sol | 349 ----------------------- contracts/libraries/TokensAndAmounts.sol | 25 -- contracts/mocks/FusionDetailsMock.sol | 29 -- contracts/mocks/ResolverMock.sol | 126 +++++--- contracts/mocks/SettlementMock.sol | 15 - test/FusionDetailsMock.js | 54 ---- test/Settlement.js | 195 +++++++++---- 10 files changed, 228 insertions(+), 886 deletions(-) delete mode 100644 contracts/Settlement.sol delete mode 100644 contracts/libraries/DynamicSuffix.sol delete mode 100644 contracts/libraries/FusionDetails.sol delete mode 100644 contracts/libraries/TokensAndAmounts.sol delete mode 100644 contracts/mocks/FusionDetailsMock.sol delete mode 100644 contracts/mocks/SettlementMock.sol delete mode 100644 test/FusionDetailsMock.js diff --git a/contracts/Settlement.sol b/contracts/Settlement.sol deleted file mode 100644 index e5c70d8c..00000000 --- a/contracts/Settlement.sol +++ /dev/null @@ -1,266 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.19; - -import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; -import "@1inch/limit-order-protocol-contract/contracts/interfaces/IOrderMixin.sol"; -import "@1inch/solidity-utils/contracts/libraries/SafeERC20.sol"; -import "./interfaces/ISettlement.sol"; -import "./interfaces/IResolver.sol"; -import "./libraries/DynamicSuffix.sol"; -import "./libraries/FusionDetails.sol"; -import "./FeeBankCharger.sol"; - -/** - * @title Settlement contract - * @notice Contract to execute limit orders settlement, created by Fusion mode. - */ -contract Settlement is ISettlement, FeeBankCharger { - using SafeERC20 for IERC20; - using DynamicSuffix for bytes; - using AddressLib for Address; - using FusionDetails for bytes; - - error AccessDenied(); - error ResolverIsNotWhitelisted(); - error WrongInteractionTarget(); - error IncorrectSelector(); - error FusionDetailsMismatch(); - error ResolveFailed(); - - // Flag to indicate that the order is the last one in the chain. No interaction will be invoked after its processing. - bytes1 private constant _FINALIZE_INTERACTION = 0x01; - uint256 private constant _ORDER_FEE_BASE_POINTS = 1e15; - uint256 private constant _BASE_POINTS = 10_000_000; // 100% - uint256 private constant _TAKING_FEE_BASE = 1e9; - uint256 private constant _TAKING_FEE_RATIO_OFFSET = 160; - uint256 private constant _RESOLVER_ADDRESS_BYTES_SIZE = 10; - - IOrderMixin private immutable _limitOrderProtocol; - - - /// @dev Modifier to check if the account is the contract itself. - /// @param account The account to check. - modifier onlyThis(address account) { - if (account != address(this)) revert AccessDenied(); - _; - } - - /// @dev Modifier to check if the caller is the limit order protocol contract. - modifier onlyLimitOrderProtocol { - if (msg.sender != address(_limitOrderProtocol)) revert AccessDenied(); - _; - } - - /** - * @notice Initializes the contract. - * @param limitOrderProtocol The limit order protocol contract. - * @param token The token to charge protocol fees in. - */ - constructor(IOrderMixin limitOrderProtocol, IERC20 token) - FeeBankCharger(token) - { - _limitOrderProtocol = limitOrderProtocol; - } - - /** - * @notice Settles the order - * @param data The order to settle with settlement parameters. - * @return Returns a boolean value indicating the success of the function. - */ - function settleOrders(bytes calldata data) public virtual returns(bool) { - _settleOrder(data, msg.sender, 0, msg.data[:0], IERC20(address(0)), 0); - return true; - } - - /** - * @notice Allows a taker to interact with the order after making amount transfered to taker, - * but before taking amount transfered to maker. - * @dev Calls the resolver contract and approves the token to the limit order protocol. - * Layout of extra data parameter: - * byte1 finalize interaction flag - * byte [M] fusion details (variable length, M) - * byte [N] arbitrary data (variable length, N) - * byte32 resolver address - * byte32 resolverFee - * (byte32,byte32) [L] tokensAndAmounts bytes - * byte32 tokensAndAmounts array length in bytes (the last 32 bytes of calldata) - * @param order The limit order being filled, which caused the interaction. - * @param /extension/ The order extension data. - * @param /orderHash/ The order hash. - * @param taker The taker address. - * @param /makingAmount/ The making amount. - * @param takingAmount The taking amount. - * @param /remainingMakingAmount/ The remaining making amount. - * @param extraData Filling order supplemental data. In the order of layout: - * FINALIZE_INTERACTION flag, {FusionDetails} data, resolver, resolver fee, tokensAndAmounts array. See {DynamicSuffix} for details. - */ - function takerInteraction( - IOrderMixin.Order calldata order, - bytes calldata /* extension */, - bytes32 /* orderHash */, - address taker, - uint256 makingAmount, - uint256 takingAmount, - uint256 /* remainingMakingAmount */, - bytes calldata extraData - ) public override onlyThis(taker) onlyLimitOrderProtocol { - (DynamicSuffix.Data calldata suffix, bytes calldata tokensAndAmounts, bytes calldata args) = extraData.decodeSuffix(); - - bytes calldata fusionDetails = args[1:]; - fusionDetails = fusionDetails[:fusionDetails.detailsLength()]; - - uint256 offeredTakingAmount = takingAmount * (_BASE_POINTS + fusionDetails.rateBump()) / _BASE_POINTS; - Address takingFeeData = fusionDetails.takingFeeData(); - uint256 takingFeeAmount = offeredTakingAmount * takingFeeData.getUint32(_TAKING_FEE_RATIO_OFFSET) / _TAKING_FEE_BASE; - - args = args[1 + fusionDetails.length:]; // remove fusion details - - IERC20 token = IERC20(order.takerAsset.get()); - - address resolver = suffix.resolver.get(); - uint256 resolverFee = suffix.resolverFee + (_ORDER_FEE_BASE_POINTS * fusionDetails.resolverFee() * makingAmount + order.makingAmount - 1) / order.makingAmount; - unchecked { - if (extraData[0] == _FINALIZE_INTERACTION) { - bytes memory allTokensAndAmounts = new bytes(tokensAndAmounts.length + 0x40); - assembly ("memory-safe") { - let ptr := add(allTokensAndAmounts, 0x20) - calldatacopy(ptr, tokensAndAmounts.offset, tokensAndAmounts.length) - ptr := add(ptr, tokensAndAmounts.length) - mstore(ptr, token) - mstore(add(ptr, 0x20), add(offeredTakingAmount, takingFeeAmount)) - } - - _chargeFee(resolver, resolverFee); - uint256 resolversLength = uint8(args[args.length - 1]); - bool success = IResolver(resolver).resolveOrders(allTokensAndAmounts, args[:args.length - 1 - resolversLength * _RESOLVER_ADDRESS_BYTES_SIZE]); - if (!success) revert ResolveFailed(); - } else { - _settleOrder(args, resolver, resolverFee, tokensAndAmounts, token, offeredTakingAmount + takingFeeAmount); - } - } - - if (takingFeeAmount > 0) { - token.safeTransfer(takingFeeData.get(), takingFeeAmount); - } - token.forceApprove(address(_limitOrderProtocol), offeredTakingAmount); - } - - struct FillOrderToArgs { - IOrderMixin.Order order; - bytes32 r; - bytes32 vs; - uint256 amount; - TakerTraits takerTraits; - address target; - bytes interaction; - } - - struct FillContractOrderArgs { - IOrderMixin.Order order; - bytes signature; - uint256 amount; - TakerTraits takerTraits; - address target; - bytes interaction; - } - - /** - * @notice Fetches the interaction from calldata. - * @dev Based on the selector determines calldata type and fetches the interaction. - * @param data The data to process. - * @return interaction Returns the interaction data. - */ - function _getInteraction(bytes calldata data) internal pure returns(bytes calldata interaction) { - bytes4 selector = bytes4(data); - if (selector == IOrderMixin.fillOrderArgs.selector) { - FillOrderToArgs calldata args; - assembly ("memory-safe") { - args := add(data.offset, 4) - } - interaction = args.interaction; - } - else if (selector == IOrderMixin.fillContractOrderArgs.selector) { - FillContractOrderArgs calldata args; - assembly ("memory-safe") { - args := add(data.offset, 4) - } - interaction = args.interaction; - } - else { - revert IncorrectSelector(); - } - } - - /** - * @notice Settles a fusion limit order. - * @dev Extracts interaction and fusion details from arguments, checks the resolver and executes . Also calculates the resolver fee. - * @param args The calldata with fill order args, fusion details and dynamic suffix. - * @param resolver The resolver address. The address is checked against the whitelist, interaction is invoked on it, and fees are charged. - * @param resolverFee The accumulated resolver fee. - * @param tokensAndAmounts The tokens and their respective amounts (from previous recursion steps). - * @param token The taker token. Appended to tokensAndAmounts. - * @param newAmount The taker amount. Appended to tokensAndAmounts. - */ - function _settleOrder( - bytes calldata args, - address resolver, - uint256 resolverFee, - bytes calldata tokensAndAmounts, - IERC20 token, - uint256 newAmount - ) private { - bytes calldata interaction = _getInteraction(args); - bytes calldata fusionDetails = interaction[21:]; - fusionDetails = fusionDetails[:fusionDetails.detailsLength()]; - - if (address(bytes20(interaction)) != address(this)) revert WrongInteractionTarget(); - // salt is the first word in Order struct, and we validate that lower 160 bits of salt are hash of fusionDetails - if (uint256(fusionDetails.computeHash(args)) & type(uint160).max != uint256(bytes32(args[4:])) & type(uint160).max) revert FusionDetailsMismatch(); - if (!fusionDetails.checkResolver(resolver, args)) revert ResolverIsNotWhitelisted(); - - uint256 suffixLength; - unchecked { - suffixLength = DynamicSuffix._STATIC_DATA_SIZE + - tokensAndAmounts.length + - (address(token) != address(0) ? 0x60 : 0x20); - } - IOrderMixin limitOrderProtocol = _limitOrderProtocol; - - assembly ("memory-safe") { - let resolversBytesSize := add(1, mul(_RESOLVER_ADDRESS_BYTES_SIZE, byte(0, calldataload(sub(add(args.offset, args.length), 1))))) - let interactionOffset := sub(interaction.offset, args.offset) - - // Copy calldata and patch interaction.length - let ptr := mload(0x40) - calldatacopy(ptr, args.offset, args.length) - mstore(add(ptr, sub(interactionOffset, 0x20)), add(add(interaction.length, suffixLength), resolversBytesSize)) - - let offset := add(add(ptr, interactionOffset), interaction.length) - // Append resolvers - calldatacopy(offset, sub(add(args.offset, args.length), resolversBytesSize), resolversBytesSize) - offset := add(offset, resolversBytesSize) - // Append suffix fields - mstore(offset, resolver) - mstore(add(offset, 0x20), resolverFee) - calldatacopy(add(offset, 0x40), tokensAndAmounts.offset, tokensAndAmounts.length) - - let pointer := add(offset, add(0x40, tokensAndAmounts.length)) - switch token - case 0 { - mstore(pointer, 0) - } - default { - mstore(pointer, token) - mstore(add(pointer, 0x20), newAmount) - mstore(add(pointer, 0x40), add(tokensAndAmounts.length, 0x40)) - } - - // Call fillOrderTo - if iszero(call(gas(), limitOrderProtocol, 0, ptr, add(add(args.length, suffixLength), resolversBytesSize), 0, 0)) { - returndatacopy(ptr, 0, returndatasize()) - revert(ptr, returndatasize()) - } - } - } -} diff --git a/contracts/interfaces/IResolver.sol b/contracts/interfaces/IResolver.sol index 17f5fcc5..9f0abc95 100644 --- a/contracts/interfaces/IResolver.sol +++ b/contracts/interfaces/IResolver.sol @@ -2,6 +2,17 @@ pragma solidity 0.8.19; +import "@1inch/limit-order-protocol-contract/contracts/interfaces/IOrderMixin.sol"; + interface IResolver { - function resolveOrders(bytes calldata tokensAndAmounts, bytes calldata data) external returns(bool); + function takerInteraction( + IOrderMixin.Order calldata order, + bytes calldata extension, + bytes32 orderHash , + address taker, + uint256 makingAmount, + uint256 takingAmount, + uint256 remainingMakingAmount, + bytes calldata extraData + ) external; } diff --git a/contracts/libraries/DynamicSuffix.sol b/contracts/libraries/DynamicSuffix.sol deleted file mode 100644 index 6ba712cf..00000000 --- a/contracts/libraries/DynamicSuffix.sol +++ /dev/null @@ -1,42 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.19; - -import "@1inch/solidity-utils/contracts/libraries/AddressLib.sol"; - -/// @title Library to parse DynamicSuffix from calldata -library DynamicSuffix { - struct Data { - Address resolver; - uint256 resolverFee; - } - - uint256 internal constant _STATIC_DATA_SIZE = 0x40; - - /** - * @notice Decodes calldata passed to settlement function and returns suffix (resolver, resolverFee) and tokensAndAmounts array. - * @dev The function reads only the tail of the calldata, as a dynamic suffix. The calldata before the suffix can be arbitrary, - * and is loaded into args return parameter. - * The dynamic suffix structure is: - * byte32 resolver address - * byte32 resolverFee - * (byte32,byte32) [M] tokensAndAmounts bytes - * byte32 tokensAndAmounts array length in bytes (the last 32 bytes of calldata) - * @param cd Calldata passed to settlement function. - * @return suffix Dynamic suffix (resolver, resolverFee). - * resolverFee is the accumulated fee paid by the resolver for a sequence of fills. - * @return tokensAndAmounts calldata containing tokensAndAmounts. - * @return args calldata containing fusion details. - */ - function decodeSuffix(bytes calldata cd) internal pure returns(Data calldata suffix, bytes calldata tokensAndAmounts, bytes calldata args) { - assembly ("memory-safe") { - let lengthOffset := sub(add(cd.offset, cd.length), 0x20) // length is stored in the last 32 bytes of calldata - tokensAndAmounts.length := calldataload(lengthOffset) // loads tokensAndAmounts array length in bytes - tokensAndAmounts.offset := sub(lengthOffset, tokensAndAmounts.length) // loads tokensAndAmounts array - - suffix := sub(tokensAndAmounts.offset, _STATIC_DATA_SIZE) // loads suffix (resolver, resolverFee) struct - args.offset := cd.offset // loads calldata without suffix into args - args.length := sub(suffix, args.offset) - } - } -} diff --git a/contracts/libraries/FusionDetails.sol b/contracts/libraries/FusionDetails.sol deleted file mode 100644 index a8b3b38d..00000000 --- a/contracts/libraries/FusionDetails.sol +++ /dev/null @@ -1,349 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.19; - -import "@1inch/solidity-utils/contracts/libraries/AddressLib.sol"; - -/// @title Library to parse FusionDetails from calldata -/// @dev Placed in the end of the order interactions data -/// Last byte contains flags and lengths, can have up to 15 resolvers and 7 points -/// Fusion order `interaction` prefix structure: -/// struct Data { -/// bytes1 flags; -/// bytes4 startTime; -/// bytes2 auctionDelay; -/// bytes3 auctionDuration; -/// bytes3 initialRateBump; -/// bytes4 resolverFee; -/// bytes2 publicTimeDelay; -/// (bytes1,bytes2)[N] resolversIndicesAndTimeDeltas; -/// (bytes3,bytes2)[M] pointsAndTimeDeltas; -/// bytes24? takingFeeData; optional, present only if flags has _HAS_TAKING_FEE_FLAG -/// } -library FusionDetails { - uint256 private constant _HAS_TAKING_FEE_FLAG = 0x80; - uint256 private constant _TAKING_FEE_FLAG_BIT_SHIFT = 7; - uint256 private constant _RESOLVERS_LENGTH_MASK = 0x78; - uint256 private constant _RESOLVERS_LENGTH_BIT_SHIFT = 3; - uint256 private constant _POINTS_LENGTH_MASK = 0x07; - - uint256 private constant _TAKING_FEE_DATA_BYTES_SIZE = 24; - uint256 private constant _TAKING_FEE_DATA_BIT_SHIFT = 64; // 256 - _TAKING_FEE_DATA_BYTES_SIZE * 8 - - uint256 private constant _START_TIME_BYTES_OFFSET = 1; - // uint256 private constant _START_TIME_BYTES_SIZE = 4; - uint256 private constant _START_TIME_BIT_SHIFT = 224; // 256 - _START_TIME_BYTES_SIZE * 8 - - uint256 private constant _AUCTION_DELAY_BYTES_OFFSET = 5; // _START_TIME_BYTES_OFFSET + _START_TIME_BYTES_SIZE - // uint256 private constant _AUCTION_DELAY_BYTES_SIZE = 2; - uint256 private constant _AUCTION_DELAY_BIT_SHIFT = 240; // 256 - _AUCTION_DELAY_BYTES_SIZE * 8 - - uint256 private constant _AUCTION_DURATION_BYTES_OFFSET = 7; // _AUCTION_DELAY_BYTES_OFFSET + _AUCTION_DELAY_BYTES_SIZE - // uint256 private constant _AUCTION_DURATION_BYTES_SIZE = 3; - uint256 private constant _AUCTION_DURATION_BIT_SHIFT = 232; // 256 - _AUCTION_DURATION_BYTES_SIZE * 8 - - uint256 private constant _INITIAL_RATE_BUMP_BYTES_OFFSET = 10; // _AUCTION_DURATION_BYTES_OFFSET + _AUCTION_DURATION_BYTES_SIZE - // uint256 private constant _INITIAL_RATE_BUMP_BYTES_SIZE = 3; - uint256 private constant _INITIAL_RATE_BUMP_BIT_SHIFT = 232; // 256 - _INITIAL_RATE_BUMP_BYTES_SIZE * 8 - - uint256 private constant _RESOLVER_FEE_BYTES_OFFSET = 13; // _INITIAL_RATE_BUMP_BYTES_OFFSET + _INITIAL_RATE_BUMP_BYTES_SIZE - // uint256 private constant _RESOLVER_FEE_BYTES_SIZE = 4; - uint256 private constant _RESOLVER_FEE_BIT_SHIFT = 224; // 256 - _RESOLVER_FEE_BYTES_SIZE * 8 - - uint256 private constant _PUBLIC_TIME_DELAY_BYTES_OFFSET = 17; // _RESOLVER_FEE_BYTES_OFFSET + _RESOLVER_FEE_BYTES_SIZE - // uint256 private constant _PUBLIC_TIME_DELAY_BYTES_SIZE = 2; - uint256 private constant _PUBLIC_TIME_DELAY_BIT_SHIFT = 240; // 256 - _PUBLIC_TIME_DELAY_BYTES_SIZE * 8 - - uint256 private constant _RESOLVERS_LIST_BYTES_OFFSET = 19; // _PUBLIC_TIME_DELAY_BYTES_OFFSET + _PUBLIC_TIME_DELAY_BYTES_SIZE - - uint256 private constant _AUCTION_POINT_BUMP_BYTES_SIZE = 3; - uint256 private constant _AUCTION_POINT_DELTA_BYTES_SIZE = 2; - uint256 private constant _AUCTION_POINT_BYTES_SIZE = 5; // _AUCTION_POINT_BUMP_BYTES_SIZE + _AUCTION_POINT_DELTA_BYTES_SIZE; - uint256 private constant _AUCTION_POINT_BUMP_BIT_SHIFT = 232; // 256 - _AUCTION_POINT_BUMP_BYTES_SIZE * 8; - uint256 private constant _AUCTION_POINT_DELTA_BIT_SHIFT = 216; // 256 - (_AUCTION_POINT_DELTA_BYTES_SIZE + _AUCTION_POINT_BUMP_BYTES_SIZE) * 8; - uint256 private constant _AUCTION_POINT_DELTA_MASK = 0xffff; - - uint256 private constant _RESOLVER_INDEX_BYTES_SIZE = 1; - uint256 private constant _RESOLVER_DELTA_BYTES_SIZE = 2; - uint256 private constant _RESOLVER_ADDRESS_BYTES_SIZE = 10; - uint256 private constant _RESOLVER_ADDRESS_MASK = 0xffffffffffffffffffff; - uint256 private constant _RESOLVER_BYTES_SIZE = 3; // _RESOLVER_DELTA_BYTES_SIZE + _RESOLVER_INDEX_BYTES_SIZE; - uint256 private constant _RESOLVER_DELTA_BIT_SHIFT = 240; // 256 - _RESOLVER_DELTA_BYTES_SIZE * 8; - uint256 private constant _RESOLVER_ADDRESS_BIT_SHIFT = 176; // 256 - _RESOLVER_ADDRESS_BYTES_SIZE * 8; - - error InvalidDetailsLength(); - error InvalidWhitelistStructure(); - - /** - * @notice Calculates fusion details calldata length passed to settlement function. - * @param details Fusion details. - * @return len Fusion details calldata length. - */ - function detailsLength(bytes calldata details) internal pure returns (uint256 len) { - assembly ("memory-safe") { - let flags := byte(0, calldataload(details.offset)) - let resolversCount := shr(_RESOLVERS_LENGTH_BIT_SHIFT, and(flags, _RESOLVERS_LENGTH_MASK)) - let pointsCount := and(flags, _POINTS_LENGTH_MASK) - // length = resolver list offset + (resolvers count * resolversSize + points count * point size + taking fee flag * taking fee size) - len := add( - _RESOLVERS_LIST_BYTES_OFFSET, - add( - add( - mul(resolversCount, _RESOLVER_BYTES_SIZE), - mul(pointsCount, _AUCTION_POINT_BYTES_SIZE) - ), - mul(_TAKING_FEE_DATA_BYTES_SIZE, shr(_TAKING_FEE_FLAG_BIT_SHIFT, flags)) - ) - ) - } - - if (details.length < len) revert InvalidDetailsLength(); - } - - /** - * @notice Decodes fusion details calldata and returns fee recipient address and fee amount. - * @dev The taking fee data structure (24 bytes) is: - * bytes4 taking fee - * bytes20 taking fee recipient - * @param details Fusion details calldata. - * @return data Returns taking fee and taking fee recipient address, or `address(0)` if there is no fee. - */ - function takingFeeData(bytes calldata details) internal pure returns (Address data) { - assembly ("memory-safe") { - if and(_HAS_TAKING_FEE_FLAG, byte(0, calldataload(details.offset))) { - let ptr := sub(add(details.offset, details.length), _TAKING_FEE_DATA_BYTES_SIZE) // offset + length - taking fee data size - data := shr(_TAKING_FEE_DATA_BIT_SHIFT, calldataload(ptr)) - } - } - } - - /** - * @notice Computes a Keccak-256 hash over the fusion details data. - * The computation is done by reconstructing the data and hashing it. - * The reconstruction involves: - * - The first part of the fusion details data up to the resolvers list - * - For each resolver, delta is combined with a resolver address from the interaction data - * - The rest of the details data including the auction points and optionally the taking fee and recipient - * @dev The calldata structure for hash function is: - * bytes1 flags; - * bytes4 startTime; - * bytes2 auctionDelay; - * bytes3 auctionDuration; - * bytes3 initialRateBump; - * bytes4 resolverFee; - * bytes2 publicTimeDelay; - * (bytes10,bytes2)[N] resolverAddress (first 10 bytes), resolverDelta; - * (bytes3,bytes2) [M] pointsAndTimeDeltas; - * bytes24? takingFeeData; only present if flags has _HAS_TAKING_FEE_FLAG - * @param details The fusion details calldata - * @param interaction The interaction calldata containing the resolver address - * @return detailsHash The computed Keccak-256 hash over the reconstructed data - */ - function computeHash(bytes calldata details, bytes calldata interaction) internal pure returns (bytes32 detailsHash) { - bytes4 invalidWhitelistStructureError = InvalidWhitelistStructure.selector; - assembly ("memory-safe") { - let flags := byte(0, calldataload(details.offset)) - let resolversCount := shr(_RESOLVERS_LENGTH_BIT_SHIFT, and(flags, _RESOLVERS_LENGTH_MASK)) - let pointsCount := and(flags, _POINTS_LENGTH_MASK) - // pointer to the first resolver address in the list - let addressPtr := sub(add(interaction.offset, interaction.length), 1) - let arraySize := byte(0, calldataload(addressPtr)) - addressPtr := sub(addressPtr, mul(_RESOLVER_ADDRESS_BYTES_SIZE, arraySize)) - // addressPtr = offset + length - 1 - resolver index * resolver address size - if lt(addressPtr, interaction.offset) { - mstore(0, invalidWhitelistStructureError) - revert(0, 4) - } - - let ptr := mload(0x40) - let reconstructed := ptr - calldatacopy(ptr, details.offset, _RESOLVERS_LIST_BYTES_OFFSET) - ptr := add(ptr, _RESOLVERS_LIST_BYTES_OFFSET) - - let cdPtr := add(details.offset, _RESOLVERS_LIST_BYTES_OFFSET) - // for each resolver - for { let cdEnd := add(cdPtr, mul(_RESOLVER_BYTES_SIZE, resolversCount)) } lt(cdPtr, cdEnd) {} { - let resolverIndex := byte(0, calldataload(cdPtr)) - if iszero(lt(resolverIndex, arraySize)) { - mstore(0, invalidWhitelistStructureError) - revert(0, 4) - } - cdPtr := add(cdPtr, _RESOLVER_INDEX_BYTES_SIZE) - let deltaRaw := calldataload(cdPtr) - cdPtr := add(cdPtr, _RESOLVER_DELTA_BYTES_SIZE) - let resolverRaw := calldataload(add(addressPtr, mul(resolverIndex, _RESOLVER_ADDRESS_BYTES_SIZE))) - - mstore(ptr, resolverRaw) - ptr := add(ptr, _RESOLVER_ADDRESS_BYTES_SIZE) - mstore(ptr, deltaRaw) - ptr := add(ptr, _RESOLVER_DELTA_BYTES_SIZE) - } - let takingFeeAndRecipientLength := mul(_TAKING_FEE_DATA_BYTES_SIZE, shr(_TAKING_FEE_FLAG_BIT_SHIFT, flags)) - let pointsAndtakingFeeInfoLength := add(mul(pointsCount, _AUCTION_POINT_BYTES_SIZE), takingFeeAndRecipientLength) - calldatacopy(ptr, cdPtr, pointsAndtakingFeeInfoLength) - ptr := add(ptr, pointsAndtakingFeeInfoLength) - mstore(0x40, ptr) - - let len := sub(ptr, reconstructed) - detailsHash := keccak256(reconstructed, len) - } - } - - /** - * @notice Checks whether a given resolver is valid at the current timestamp. - * - * A resolver is considered valid if the current timestamp is greater than - * the sum of the auction start time and the public time delay. - * - * If the resolver is not valid at this point, the function iterates over the resolvers list. - * If a resolver's address matches the provided address and the current timestamp is greater - * than its start time, the resolver is considered valid. - * - * @param details The calldata representing the fusion details - * @param resolver The address of the resolver to be checked - * @param interaction The calldata representing the interactions to be used for callback addresses - * @return valid Returns true if the resolver is valid at the current timestamp, false otherwise - */ - function checkResolver(bytes calldata details, address resolver, bytes calldata interaction) internal view returns (bool valid) { - bytes4 invalidWhitelistStructureError = InvalidWhitelistStructure.selector; - assembly ("memory-safe") { - let flags := byte(0, calldataload(details.offset)) - let resolversCount := shr(_RESOLVERS_LENGTH_BIT_SHIFT, and(flags, _RESOLVERS_LENGTH_MASK)) - - // Check public time limit - let startTime := shr(_START_TIME_BIT_SHIFT, calldataload(add(details.offset, _START_TIME_BYTES_OFFSET))) - let publicTimeDelay := shr(_PUBLIC_TIME_DELAY_BIT_SHIFT, calldataload(add(details.offset, _PUBLIC_TIME_DELAY_BYTES_OFFSET))) - valid := gt(timestamp(), add(startTime, publicTimeDelay)) - - // Check resolvers and corresponding time limits - if iszero(valid) { - let resolverTimeStart := startTime - let ptr := add(details.offset, _RESOLVERS_LIST_BYTES_OFFSET) // moves pointer to the start of the resolvers list - let addressPtr := sub(add(interaction.offset, interaction.length), 1) // moves address pointer to the resolvers array length - let arraySize := byte(0, calldataload(addressPtr)) - addressPtr := sub(addressPtr, mul(_RESOLVER_ADDRESS_BYTES_SIZE, arraySize)) // moves address pointer to the first resolver address - if lt(addressPtr, interaction.offset) { - mstore(0, invalidWhitelistStructureError) - revert(0, 4) - } - - for { let end := add(ptr, mul(_RESOLVER_BYTES_SIZE, resolversCount)) } lt(ptr, end) { } { - let resolverIndex := byte(0, calldataload(ptr)) // gets resolver's index - if iszero(lt(resolverIndex, arraySize)) { - mstore(0, invalidWhitelistStructureError) - revert(0, 4) - } - ptr := add(ptr, _RESOLVER_INDEX_BYTES_SIZE) // moves pointer to the resolver's delta - resolverTimeStart := add(resolverTimeStart, shr(_RESOLVER_DELTA_BIT_SHIFT, calldataload(ptr))) // gets resolver's time limit - ptr := add(ptr, _RESOLVER_DELTA_BYTES_SIZE) // moves pointer to the next resolver - - // gets resolver's address and checks if it matches the provided address - let account := shr(_RESOLVER_ADDRESS_BIT_SHIFT, calldataload(add(addressPtr, mul(resolverIndex, _RESOLVER_ADDRESS_BYTES_SIZE)))) - if eq(account, and(resolver, _RESOLVER_ADDRESS_MASK)) { - valid := gt(timestamp(), resolverTimeStart) - break - } - } - } - } - } - - /** - * @notice Calculates and returns the rate bump for an auction at the current time. - * - * If the current time is before the auction's start, the initial rate bump is returned. - * If the current time is after the auction's finish, 0 is returned, meaning there is no rate bump after the auction. - * If the current time is within the auction duration, the rate bump is calculated based on a linear interpolation - * between the auction's points, each having a specific rate bump and delay time. - * - * @param details Calldata containing fusion details - * @return ret Returns the rate bump at the current timestamp - */ - function rateBump(bytes calldata details) internal view returns (uint256 ret) { - uint256 startBump; - uint256 auctionStartTime; - uint256 auctionFinishTime; - assembly ("memory-safe") { - startBump := shr(_INITIAL_RATE_BUMP_BIT_SHIFT, calldataload(add(details.offset, _INITIAL_RATE_BUMP_BYTES_OFFSET))) - auctionStartTime := add( - shr(_START_TIME_BIT_SHIFT, calldataload(add(details.offset, _START_TIME_BYTES_OFFSET))), - shr(_AUCTION_DELAY_BIT_SHIFT, calldataload(add(details.offset, _AUCTION_DELAY_BYTES_OFFSET))) - ) - auctionFinishTime := add(auctionStartTime, shr(_AUCTION_DURATION_BIT_SHIFT, calldataload(add(details.offset, _AUCTION_DURATION_BYTES_OFFSET)))) - } - - if (block.timestamp <= auctionStartTime) { - return startBump; - } else if (block.timestamp >= auctionFinishTime) { - return 0; // Means 0% bump - } - - assembly ("memory-safe") { - function linearInterpolation(t1, t2, v1, v2, t) -> v { - // v = ((t - t1) * v2 + (t2 - t) * v1) / (t2 - t1) - v := div( - add(mul(sub(t, t1), v2), mul(sub(t2, t), v1)), - sub(t2, t1) - ) - } - - // move ptr to the first point - let ptr := add(details.offset, _RESOLVERS_LIST_BYTES_OFFSET) - let pointsCount - { - let flags := byte(0, calldataload(details.offset)) - let resolversCount := shr(_RESOLVERS_LENGTH_BIT_SHIFT, and(flags, _RESOLVERS_LENGTH_MASK)) - ptr := add(ptr, mul(resolversCount, _RESOLVER_BYTES_SIZE)) - pointsCount := and(flags, _POINTS_LENGTH_MASK) - } - - // Check points sequentially - let pointTime := auctionStartTime - let prevBump := startBump - let prevPointTime := pointTime - for { let end := add(ptr, mul(_AUCTION_POINT_BYTES_SIZE, pointsCount)) } lt(ptr, end) { } { - let word := calldataload(ptr) - let bump := shr(_AUCTION_POINT_BUMP_BIT_SHIFT, word) - let delay := and(shr(_AUCTION_POINT_DELTA_BIT_SHIFT, word), _AUCTION_POINT_DELTA_MASK) - ptr := add(ptr, add(_AUCTION_POINT_BUMP_BYTES_SIZE, _AUCTION_POINT_DELTA_BYTES_SIZE)) - pointTime := add(pointTime, delay) - if gt(pointTime, timestamp()) { - // Compute linear interpolation between prevBump and bump based on the time passed: - // prevPointTime now pointTime - // prevBump ??? bump - ret := linearInterpolation( - prevPointTime, - pointTime, - prevBump, - bump, - timestamp() - ) - break - } - prevPointTime := pointTime - prevBump := bump - } - - if iszero(ret) { - ret := linearInterpolation( - prevPointTime, - auctionFinishTime, - prevBump, - 0, - timestamp() - ) - } - } - } - - /** - * @notice Decodes fusion details calldata and returns the fee paid by a resolver for filling the order. - * @param details Fusion details calldata. - * @return fee Fee paid by a resolver for filling the order. - */ - function resolverFee(bytes calldata details) internal pure returns (uint256 fee) { - assembly ("memory-safe") { - fee := shr(_RESOLVER_FEE_BIT_SHIFT, calldataload(add(details.offset, _RESOLVER_FEE_BYTES_OFFSET))) - } - } -} diff --git a/contracts/libraries/TokensAndAmounts.sol b/contracts/libraries/TokensAndAmounts.sol deleted file mode 100644 index 7ec0e0bb..00000000 --- a/contracts/libraries/TokensAndAmounts.sol +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.19; - -import "@1inch/solidity-utils/contracts/libraries/AddressLib.sol"; - -/// @title Library to parse TokensAndAmounts array from calldata -library TokensAndAmounts { - struct Data { - Address token; - uint256 amount; - } - - /** - * @notice Decodes calldata passed to settlement function and returns an array of structs representing token addresses and amounts. - * @param cd Calldata passed to settlement function. - * @return decoded Array of structs representing token addresses and amounts. - */ - function decode(bytes calldata cd) internal pure returns(Data[] calldata decoded) { - assembly ("memory-safe") { - decoded.offset := cd.offset - decoded.length := div(cd.length, 0x40) - } - } -} diff --git a/contracts/mocks/FusionDetailsMock.sol b/contracts/mocks/FusionDetailsMock.sol deleted file mode 100644 index d0f22544..00000000 --- a/contracts/mocks/FusionDetailsMock.sol +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.19; - -import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import "@1inch/solidity-utils/contracts/libraries/AddressLib.sol"; - -import "../libraries/FusionDetails.sol"; - -contract FusionDetailsMock { - using AddressLib for Address; - using FusionDetails for bytes; - - function parse(bytes calldata details, address resolver, bytes calldata whitelist) external view returns( - uint256 detailsLength, - address takingFeeReceiver, - uint256 takingFeeAmount, - uint256 bump, - uint256 resolverFee, - bool isValidResolver - ) { - detailsLength = details.detailsLength(); - takingFeeReceiver = details.takingFeeData().get(); - takingFeeAmount = details.takingFeeData().getUint32(160); - bump = details.rateBump(); - resolverFee = details.resolverFee(); - isValidResolver = details.checkResolver(resolver, whitelist); - } -} diff --git a/contracts/mocks/ResolverMock.sol b/contracts/mocks/ResolverMock.sol index 2b424faf..7aa4a194 100644 --- a/contracts/mocks/ResolverMock.sol +++ b/contracts/mocks/ResolverMock.sol @@ -2,77 +2,113 @@ pragma solidity 0.8.19; +import "@openzeppelin/contracts/interfaces/IERC1271.sol"; import "../interfaces/IResolver.sol"; import "../interfaces/ISettlement.sol"; -import "../libraries/TokensAndAmounts.sol"; import "@1inch/solidity-utils/contracts/libraries/SafeERC20.sol"; +import "@1inch/solidity-utils/contracts/libraries/ECDSA.sol"; +import "@1inch/limit-order-protocol-contract/contracts/interfaces/IOrderMixin.sol"; -contract ResolverMock is IResolver { +contract ResolverMock is IResolver, IERC1271 { error OnlyOwner(); error OnlySettlement(); + error OnlyLOP(); error FailedExternalCall(uint256 index, bytes reason); - using TokensAndAmounts for bytes; using SafeERC20 for IERC20; using AddressLib for Address; - ISettlement private immutable _settlement; - address private immutable _limitOrderProtocol; + bytes1 private constant _FINALIZE_INTERACTION = 0x01; + uint256 private constant _RESOLVER_ADDRESS_BYTES_SIZE = 10; + uint256 private constant _BASE_POINTS = 10_000_000; // 100% + uint256 private constant _TAKING_FEE_BASE = 1e9; + uint256 private constant _TAKING_FEE_RATIO_OFFSET = 160; + + + address private immutable _settlementExtension; + IOrderMixin private immutable _lopv4; address private immutable _owner; - constructor(ISettlement settlement, address limitOrderProtocol) { - _settlement = settlement; - _limitOrderProtocol = limitOrderProtocol; + constructor(address settlementExtension, IOrderMixin limitOrderProtocol) { + _settlementExtension = settlementExtension; + _lopv4 = limitOrderProtocol; _owner = msg.sender; } - function settleOrders(bytes calldata data) public { - if (msg.sender != _owner) revert OnlyOwner(); - _settlement.settleOrders(data); + function isValidSignature(bytes32 hash, bytes calldata signature) external view returns (bytes4 magicValue) { + if (ECDSA.recoverOrIsValidSignature(_owner, hash, signature)) { + return IERC1271.isValidSignature.selector; + } + return 0xFFFFFFFF; } - /// @dev High byte of `packing` contains number of permits, each 2 bits from lowest contains length of permit (index in [92,120,148] array) - function settleOrdersWithPermits(bytes calldata data, uint256 packing, bytes calldata packedPermits) external { - _performPermits(packing, packedPermits); - settleOrders(data); + function approve(IERC20 token, address to) external { + if (msg.sender != _owner) revert OnlyOwner(); + token.forceApprove(to, type(uint256).max); } - function resolveOrders(bytes calldata tokensAndAmounts, bytes calldata data) external returns(bool) { - if (msg.sender != address(_settlement)) revert OnlySettlement(); + function settleOrders(bytes calldata data) public { + if (msg.sender != _owner) revert OnlyOwner(); + _settleOrders(data); + } - if (data.length > 0) { - (Address[] memory targets, bytes[] memory calldatas) = abi.decode(data, (Address[], bytes[])); - for (uint256 i = 0; i < targets.length; ++i) { - // solhint-disable-next-line avoid-low-level-calls - (bool success, bytes memory reason) = targets[i].get().call(calldatas[i]); - if (!success) revert FailedExternalCall(i, reason); - } - } + function _settleOrders(bytes calldata data) internal { + (bool success, bytes memory reason) = address(_lopv4).call(data); // abi.encodeWithSelector(_lopv4.fillOrderArgs.selector, data) + if (!success) revert FailedExternalCall(0, reason); + } - TokensAndAmounts.Data[] calldata items = tokensAndAmounts.decode(); - for (uint256 i = 0; i < items.length; i++) { - IERC20(items[i].token.get()).safeTransfer(msg.sender, items[i].amount); - } + /// @dev High byte of `packing` contains number of permits, each 2 bits from lowest contains length of permit (index in [92,120,148] array) + // function settleOrdersWithPermits(bytes calldata data, uint256 packing, bytes calldata packedPermits) external { + // _performPermits(packing, packedPermits); + // settleOrders(data); + // } - return true; - } + function takerInteraction( + IOrderMixin.Order calldata /* order */, + bytes calldata /* extension */, + bytes32 /* orderHash */, + address /* taker */, + uint256 /* makingAmount */, + uint256 /* takingAmount */, + uint256 /* remainingMakingAmount */, + bytes calldata extraData + ) public { + if (msg.sender != address(_lopv4)) revert OnlyLOP(); + // if (address(uint160(uint256(bytes32(extension)))) != _settlementExtension) revert OnlySettlement(); - function _performPermits(uint256 packing, bytes calldata packedPermits) private { unchecked { - uint256 permitsCount = packing >> 248; - uint256 start = 0; - for (uint256 i = 0; i < permitsCount; i++) { - uint256 length = (packing >> (i << 1)) & 0x03; - if (length == 0) length = 112; - else if (length == 1) length = 140; - else if (length == 2) length = 136; - - bytes calldata permit = packedPermits[start:start + length]; - address owner = address(bytes20(permit)); - IERC20 token = IERC20(address(bytes20(permit[20:]))); - token.safePermit(owner, _limitOrderProtocol, permit[40:]); - start += length; + if (extraData[0] == _FINALIZE_INTERACTION) { + if (extraData.length > 1) { + (Address[] memory targets, bytes[] memory calldatas) = abi.decode(extraData[1:], (Address[], bytes[])); + for (uint256 i = 0; i < targets.length; ++i) { + // solhint-disable-next-line avoid-low-level-calls + (bool success, bytes memory reason) = targets[i].get().call(calldatas[i]); + if (!success) revert FailedExternalCall(i, reason); + } + } + } else { + // _lopv4.call(abi.encodeWithSelector(extraData[1:4], args[5:])); + _settleOrders(extraData[1:]); } } } + + // function _performPermits(uint256 packing, bytes calldata packedPermits) private { + // unchecked { + // uint256 permitsCount = packing >> 248; + // uint256 start = 0; + // for (uint256 i = 0; i < permitsCount; i++) { + // uint256 length = (packing >> (i << 1)) & 0x03; + // if (length == 0) length = 112; + // else if (length == 1) length = 140; + // else if (length == 2) length = 136; + + // bytes calldata permit = packedPermits[start:start + length]; + // address owner = address(bytes20(permit)); + // IERC20 token = IERC20(address(bytes20(permit[20:]))); + // token.safePermit(owner, _limitOrderProtocol, permit[40:]); + // start += length; + // } + // } + // } } diff --git a/contracts/mocks/SettlementMock.sol b/contracts/mocks/SettlementMock.sol deleted file mode 100644 index ad93d492..00000000 --- a/contracts/mocks/SettlementMock.sol +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.19; - -import "../Settlement.sol"; - -contract SettlementMock is Settlement { - constructor(IOrderMixin limitOrderProtocol, IERC20 token) - Settlement(limitOrderProtocol, token) - {} - - function decreaseAvailableCreditMock(address account, uint256 amount) external { - _chargeFee(account, amount); - } -} diff --git a/test/FusionDetailsMock.js b/test/FusionDetailsMock.js deleted file mode 100644 index 400a4d76..00000000 --- a/test/FusionDetailsMock.js +++ /dev/null @@ -1,54 +0,0 @@ -const { BigNumber } = require('ethers'); -const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); -const { expect, constants, time, deployContract } = require('@1inch/solidity-utils'); -const { buildFusions } = require('./helpers/fusionUtils'); - -describe('FusionDetailsMock', function () { - async function initContracts() { - const fusionDetailsMock = await deployContract('FusionDetailsMock', []); - return { fusionDetailsMock }; - } - - it('should return default values', async function () { - const { fusionDetailsMock } = await loadFixture(initContracts); - - const { fusions: [fusionDetails], resolvers } = await buildFusions([{}]); - const result = await fusionDetailsMock.parse(fusionDetails, constants.ZERO_ADDRESS, resolvers); - - expect(Object.assign({}, result)).to.deep.contain({ - detailsLength: BigNumber.from('19'), - takingFeeReceiver: constants.ZERO_ADDRESS, - takingFeeAmount: BigNumber.from('0'), - bump: BigNumber.from('0'), - resolverFee: BigNumber.from('0'), - isValidResolver: false, - }); - }); - - it('should return custom values', async function () { - const { fusionDetailsMock } = await loadFixture(initContracts); - - const { fusions: [fusionDetails], resolvers } = await buildFusions([{ - resolvers: [fusionDetailsMock.address], - points: [[10n, 100n], [5n, 50n]], - startTime: (await time.latest()) + 100, - auctionDuration: time.duration.hours(2), - initialRateBump: 200n, - resolverFee: 10000n, - publicTimeDelay: time.duration.hours(1), - }]); - - await time.increase(time.duration.minutes(2)); - - const result = await fusionDetailsMock.parse(fusionDetails, fusionDetailsMock.address, resolvers); - - expect(Object.assign({}, result)).to.deep.include({ - detailsLength: BigNumber.from('32'), - takingFeeReceiver: constants.ZERO_ADDRESS, - takingFeeAmount: BigNumber.from('0'), - bump: BigNumber.from('49'), - resolverFee: BigNumber.from('10000'), - isValidResolver: true, - }); - }); -}); diff --git a/test/Settlement.js b/test/Settlement.js index ffd794c9..dfd0e219 100644 --- a/test/Settlement.js +++ b/test/Settlement.js @@ -2,44 +2,43 @@ const { time, expect, ether, trim0x, timeIncreaseTo, getPermit, getPermit2, comp const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); const { ethers } = require('hardhat'); const { deploySwapTokens, getChainId } = require('./helpers/fixtures'); -const { buildOrder, signOrder, compactSignature, fillWithMakingAmount, buildMakerTraits } = require('@1inch/limit-order-protocol-contract/test/helpers/orderUtils'); -const { buildFusions } = require('./helpers/fusionUtils'); +const { buildOrder, signOrder, buildTakerTraits, buildMakerTraits } = require('@1inch/limit-order-protocol-contract/test/helpers/orderUtils'); const ORDER_FEE = 100n; const BACK_ORDER_FEE = 125n; const BASE_POINTS = ether('0.001'); // 1e15 -describe.skip('Settlement', function () { +describe('Settlement', function () { async function initContracts() { const abiCoder = ethers.utils.defaultAbiCoder; const chainId = await getChainId(); const [owner, alice, bob] = await ethers.getSigners(); - const { dai, weth, inch, swap } = await deploySwapTokens(); + const { dai, weth, inch, lopv4 } = await deploySwapTokens(); await dai.transfer(alice.address, ether('100')); await inch.mint(owner.address, ether('100')); await weth.deposit({ value: ether('1') }); await weth.connect(alice).deposit({ value: ether('1') }); - const settlement = await deployContract('SettlementMock', [swap.address, inch.address]); + const settlement = await deployContract('SettlementExtensionMock', [lopv4.address, inch.address]); const FeeBank = await ethers.getContractFactory('FeeBank'); const feeBank = FeeBank.attach(await settlement.feeBank()); const ResolverMock = await ethers.getContractFactory('ResolverMock'); - const resolver = await ResolverMock.deploy(settlement.address, swap.address); + const resolver = await ResolverMock.deploy(settlement.address, lopv4.address); await inch.approve(feeBank.address, ether('100')); await feeBank.depositFor(resolver.address, ether('100')); - await dai.approve(swap.address, ether('100')); - await dai.connect(alice).approve(swap.address, ether('100')); - await weth.approve(swap.address, ether('1')); - await weth.connect(alice).approve(swap.address, ether('1')); + await dai.approve(lopv4.address, ether('100')); + await dai.connect(alice).approve(lopv4.address, ether('100')); + await weth.approve(lopv4.address, ether('1')); + await weth.connect(alice).approve(lopv4.address, ether('1')); return { - contracts: { dai, weth, swap, settlement, feeBank, resolver }, + contracts: { dai, weth, lopv4, settlement, feeBank, resolver }, accounts: { owner, alice, bob }, others: { chainId, abiCoder }, }; @@ -56,70 +55,146 @@ describe.skip('Settlement', function () { fillingAmount = orderData.makingAmount, }) { const { - contracts: { swap, settlement, resolver }, + contracts: { lopv4, settlement, resolver }, others: { chainId }, } = dataFormFixture; - const { - fusions: [fusionDetails], - hashes: [fusionHash], - resolvers, - } = await buildFusions([singleFusionData]); - const order = buildOrder(orderData); - order.salt = fusionHash; - const { r, vs } = compactSignature(await signOrder(order, chainId, swap.address, orderSigner)); - const fillOrderToData = swap.interface.encodeFunctionData('fillOrderTo', [ - order, - r, - vs, - fillingAmount, - fillWithMakingAmount('0'), - resolver.address, - settlement.address + (isInnermostOrder ? '01' : '00') + trim0x(fusionDetails) + trim0x(additionalDataForSettlement), - ]); + + const auctionStartTime = await time.latest(); + const auctionDetails = ethers.utils.solidityPack( + ['uint32', 'uint24', 'uint24'], [auctionStartTime, time.duration.hours(1), 0], + ); + + const order = buildOrder({ + maker: alice.address, + makerAsset: dai.address, + takerAsset: weth.address, + makingAmount: ether('100'), + takingAmount: ether('0.1'), + makerTraits: buildMakerTraits(), + }, { + makingAmountData: settlementExtension.address + trim0x(auctionDetails), + takingAmountData: settlementExtension.address + trim0x(auctionDetails), + postInteraction: settlementExtension.address + trim0x(ethers.utils.solidityPack( + ['uint8', 'uint32', 'bytes10', 'uint16'], [0, auctionStartTime, '0x' + owner.address.substring(22), 0], + )), + }); + + const { r, _vs: vs } = ethers.utils.splitSignature(await signOrder(order, chainId, lopv4.address, alice)); + + await weth.approve(lopv4.address, ether('0.1')); + + const takerTraits = buildTakerTraits({ + makingAmount: true, + minReturn: ether('0.1'), + extension: order.extension, + }); + + + // const { + // fusions: [fusionDetails], + // hashes: [fusionHash], + // resolvers, + // } = await buildFusions([singleFusionData]); + // const order = buildOrder(orderData); + // order.salt = fusionHash; + // const { r, vs } = compactSignature(await signOrder(order, chainId, lopv4.address, orderSigner)); + // const fillOrderToData = lopv4.interface.encodeFunctionData('fillOrderTo', [ + // order, + // r, + // vs, + // fillingAmount, + // fillWithMakingAmount('0'), + // resolver.address, + // settlement.address + (isInnermostOrder ? '01' : '00') + trim0x(fusionDetails) + trim0x(additionalDataForSettlement), + // ]); return needAddResolvers ? fillOrderToData + trim0x(resolvers) : fillOrderToData; } it('opposite direction recursive swap', async function () { const dataFormFixture = await loadFixture(initContracts); const { - contracts: { dai, weth, settlement, resolver }, + contracts: { dai, weth, lopv4, settlement, resolver }, accounts: { owner, alice }, + others: { chainId }, } = dataFormFixture; - const fillOrderToData1 = await buildCalldataForOrder({ - orderData: { - maker: alice.address, - makerAsset: weth.address, - takerAsset: dai.address, - makingAmount: ether('0.11'), - takingAmount: ether('100'), - makerTraits: buildMakerTraits({ allowedSender: settlement.address }), - }, - singleFusionData: { resolvers: [resolver.address] }, - orderSigner: alice, - dataFormFixture, - isInnermostOrder: true, + const auctionStartTime = await time.latest(); + const auctionDetails = ethers.utils.solidityPack( + ['uint32', 'uint24', 'uint24'], [auctionStartTime, time.duration.hours(1), 0], + ); + + const order = buildOrder({ + maker: alice.address, + makerAsset: dai.address, + takerAsset: weth.address, + makingAmount: ether('100'), + takingAmount: ether('0.1'), + makerTraits: buildMakerTraits(), + }, { + makingAmountData: settlement.address + trim0x(auctionDetails), + takingAmountData: settlement.address + trim0x(auctionDetails), + postInteraction: settlement.address + trim0x(ethers.utils.solidityPack( + ['uint8', 'uint32', 'bytes10', 'uint16'], [0, auctionStartTime, '0x' + resolver.address.substring(22), 0], + )), + }); + const backOrder = buildOrder({ + maker: owner.address, + makerAsset: weth.address, + takerAsset: dai.address, + makingAmount: ether('0.11'), + takingAmount: ether('100'), + makerTraits: buildMakerTraits(), + }, { + makingAmountData: settlement.address + trim0x(auctionDetails), + takingAmountData: settlement.address + trim0x(auctionDetails), + postInteraction: settlement.address + trim0x(ethers.utils.solidityPack( + ['uint8', 'uint32', 'bytes10', 'uint16'], [0, auctionStartTime, '0x' + resolver.address.substring(22), 0], + )), }); - const fillOrderToData0 = await buildCalldataForOrder({ - orderData: { - maker: owner.address, - makerAsset: dai.address, - takerAsset: weth.address, - makingAmount: ether('100'), - takingAmount: ether('0.11'), - makerTraits: buildMakerTraits({ allowedSender: settlement.address }), - }, - singleFusionData: { resolvers: [resolver.address] }, - orderSigner: owner, - dataFormFixture, - additionalDataForSettlement: fillOrderToData1, - needAddResolvers: true, + const signature = ethers.utils.splitSignature(await signOrder(order, chainId, lopv4.address, alice)); + const signatureBackOrder = ethers.utils.splitSignature(await signOrder(backOrder, chainId, lopv4.address, owner)); + + const orderTakerTraits = buildTakerTraits({ + makingAmount: false, + minReturn: ether('90'), + extension: order.extension, + interaction: resolver.address + '01', + target: resolver.address, }); - const txn = await resolver.settleOrders(fillOrderToData0); - await expect(txn).to.changeTokenBalances(dai, [owner, alice], [ether('-100'), ether('100')]); - await expect(txn).to.changeTokenBalances(weth, [owner, alice], [ether('0.11'), ether('-0.11')]); + const orderBackTakerTraits = buildTakerTraits({ + makingAmount: false, + minReturn: ether('0.1'), + extension: backOrder.extension, + interaction: resolver.address + '00' + lopv4.interface.encodeFunctionData('fillOrderArgs', [ + order, + signature.r, + signature._vs, + ether('0.1'), + orderTakerTraits.traits, + orderTakerTraits.args, + ]).substring(2), //.substring(10), + target: resolver.address, + }); + + await resolver.approve(dai.address, lopv4.address); + await resolver.approve(weth.address, lopv4.address); + + const tx = await resolver.settleOrders( + // '0x' + + lopv4.interface.encodeFunctionData('fillOrderArgs', [ + backOrder, + signatureBackOrder.r, + signatureBackOrder._vs, + ether('100'), + orderBackTakerTraits.traits, + orderBackTakerTraits.args, + ]), //.substring(10), + ); + + await expect(tx).to.changeTokenBalances(dai, [owner, alice, resolver], [ether('100'), ether('-100'), ether('0')]); + await expect(tx).to.changeTokenBalances(weth, [owner, alice, resolver], [ether('-0.11'), ether('0.1'), ether('0.01')]); }); it('settle orders with permits, permit', async function () {