diff --git a/contracts/extensions/NativeOrderFactory.sol b/contracts/extensions/NativeOrderFactory.sol index e7c120d9..20f38308 100644 --- a/contracts/extensions/NativeOrderFactory.sol +++ b/contracts/extensions/NativeOrderFactory.sol @@ -12,21 +12,9 @@ import { Errors } from "../libraries/Errors.sol"; import { EIP712Alien } from "../utils/EIP712Alien.sol"; import { OrderLib } from "../OrderLib.sol"; import { NativeOrderImpl } from "./NativeOrderImpl.sol"; +import { NativeOrderFactoryBase } from "./NativeOrderFactoryBase.sol"; -contract NativeOrderFactory is Ownable, EIP712Alien { - using Clones for address; - using AddressLib for Address; - using SafeERC20 for IERC20; - using OrderLib for IOrderMixin.Order; - - event NativeOrderCreated(address maker, bytes32 orderHash, address clone, uint256 value); - - error OrderReceiverShouldBeSetCorrectly(address receiver); - error OrderMakerShouldBeMsgSender(address expected, address actual); - error OrderMakingAmountShouldBeEqualToMsgValue(uint256 expected, uint256 actual); - - address public immutable IMPLEMENTATION; - +contract NativeOrderFactory is NativeOrderFactoryBase { constructor( IWETH weth, address limitOrderProtocol, @@ -48,30 +36,4 @@ contract NativeOrderFactory is Ownable, EIP712Alien { version )); } - - function create(IOrderMixin.Order calldata makerOrder) external payable returns (address clone) { - // Validate main order parameters - if (makerOrder.maker.get() != msg.sender) revert OrderMakerShouldBeMsgSender(msg.sender, makerOrder.maker.get()); - address receiver = makerOrder.receiver.get(); - if (receiver == address(0) || receiver == address(this)) revert OrderReceiverShouldBeSetCorrectly(receiver); - if (msg.value != makerOrder.makingAmount) revert OrderMakingAmountShouldBeEqualToMsgValue(makerOrder.makingAmount, msg.value); - - bytes32 makerOrderHash = makerOrder.hash(_domainSeparatorV4()); - clone = IMPLEMENTATION.cloneDeterministic(makerOrderHash); - NativeOrderImpl(payable(clone)).depositAndApprove{ value: msg.value }(); - - IOrderMixin.Order memory order = makerOrder; - order.maker = Address.wrap(uint160(clone)); - bytes32 orderHash = order.hashMemory(_domainSeparatorV4()); - emit NativeOrderCreated(msg.sender, orderHash, clone, msg.value); - } - - function rescueFunds(address token, address to, uint256 amount) external onlyOwner { - if (token == address(0)) { - (bool success, ) = payable(to).call{ value: amount }(""); - if (!success) revert Errors.ETHTransferFailed(); - } else { - IERC20(token).safeTransfer(to, amount); - } - } } diff --git a/contracts/extensions/NativeOrderFactoryBase.sol b/contracts/extensions/NativeOrderFactoryBase.sol new file mode 100644 index 00000000..bbdbcf41 --- /dev/null +++ b/contracts/extensions/NativeOrderFactoryBase.sol @@ -0,0 +1,59 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.30; + +import { Clones } from "@openzeppelin/contracts/proxy/Clones.sol"; +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +import { Address, AddressLib } from "@1inch/solidity-utils/contracts/libraries/AddressLib.sol"; +import { SafeERC20, IERC20, IWETH } from "@1inch/solidity-utils/contracts/libraries/SafeERC20.sol"; + +import { IOrderMixin } from "../interfaces/IOrderMixin.sol"; +import { Errors } from "../libraries/Errors.sol"; +import { EIP712Alien } from "../utils/EIP712Alien.sol"; +import { OrderLib } from "../OrderLib.sol"; +import { NativeOrderImpl } from "./NativeOrderImpl.sol"; + +abstract contract NativeOrderFactoryBase is Ownable, EIP712Alien { + using Clones for address; + using AddressLib for Address; + using SafeERC20 for IERC20; + using OrderLib for IOrderMixin.Order; + + event NativeOrderCreated(address maker, bytes32 orderHash, address clone, uint256 value); + + error OrderReceiverShouldBeSetCorrectly(address receiver); + error OrderMakerShouldBeMsgSender(address expected, address actual); + error OrderMakingAmountShouldBeEqualToMsgValue(uint256 expected, uint256 actual); + + address public immutable IMPLEMENTATION; + + function create(IOrderMixin.Order calldata makerOrder) external payable returns (address clone) { + // Validate main order parameters + if (makerOrder.maker.get() != msg.sender) revert OrderMakerShouldBeMsgSender(msg.sender, makerOrder.maker.get()); + address receiver = makerOrder.receiver.get(); + if (receiver == address(0) || receiver == address(this)) revert OrderReceiverShouldBeSetCorrectly(receiver); + if (msg.value != makerOrder.makingAmount) revert OrderMakingAmountShouldBeEqualToMsgValue(makerOrder.makingAmount, msg.value); + + bytes32 makerOrderHash = makerOrder.hash(_domainSeparatorV4()); + clone = _deployNativeOrder(IMPLEMENTATION, makerOrderHash); + NativeOrderImpl(payable(clone)).depositAndApprove{ value: msg.value }(); + + IOrderMixin.Order memory order = makerOrder; + order.maker = Address.wrap(uint160(clone)); + bytes32 orderHash = order.hashMemory(_domainSeparatorV4()); + emit NativeOrderCreated(msg.sender, orderHash, clone, msg.value); + } + + function rescueFunds(address token, address to, uint256 amount) external onlyOwner { + if (token == address(0)) { + (bool success, ) = payable(to).call{ value: amount }(""); + if (!success) revert Errors.ETHTransferFailed(); + } else { + IERC20(token).safeTransfer(to, amount); + } + } + + function _deployNativeOrder(address implementation, bytes32 salt) internal virtual returns (address) { + return implementation.cloneDeterministic(salt); + } +} diff --git a/contracts/extensions/NativeOrderImpl.sol b/contracts/extensions/NativeOrderImpl.sol index 231a0687..3e2f3ee4 100644 --- a/contracts/extensions/NativeOrderImpl.sol +++ b/contracts/extensions/NativeOrderImpl.sol @@ -38,7 +38,7 @@ contract NativeOrderImpl is IERC1271, EIP712Alien, OnlyWethReceiver { IWETH private immutable _WETH; address private immutable _LOP; address private immutable _IMPLEMENTATION = address(this); - address private immutable _FACTORY; + address internal immutable _FACTORY; IERC20 private immutable _ACCESS_TOKEN; uint256 private immutable _CANCELLATION_DELAY; @@ -167,7 +167,7 @@ contract NativeOrderImpl is IERC1271, EIP712Alien, OnlyWethReceiver { return order.hashMemory(_domainSeparatorV4()); } - function _calcCloneAddress(IOrderMixin.Order calldata makerOrder) private view returns(address) { + function _calcCloneAddress(IOrderMixin.Order calldata makerOrder) internal virtual view returns(address) { bytes32 makerOrderHash = makerOrder.hash(_domainSeparatorV4()); return _IMPLEMENTATION.predictDeterministicAddress(makerOrderHash, _FACTORY); } diff --git a/contracts/extensions/ZkSync/MinimalProxyZkSync.sol b/contracts/extensions/ZkSync/MinimalProxyZkSync.sol new file mode 100644 index 00000000..83c34756 --- /dev/null +++ b/contracts/extensions/ZkSync/MinimalProxyZkSync.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.30; + +/// @custom:security-contact security@1inch.io +contract MinimalProxyZkSync { + address private immutable _IMPLEMENTATION; + + constructor(address implementation) payable { + _IMPLEMENTATION = implementation; + } + + // solhint-disable-next-line no-complex-fallback + fallback() external payable { + address _impl = _IMPLEMENTATION; + assembly ("memory-safe") { + calldatacopy(0, 0, calldatasize()) + let result := delegatecall(gas(), _impl, 0, calldatasize(), 0, 0) + returndatacopy(0, 0, returndatasize()) + switch result + case 0 { + revert(0, returndatasize()) + } + default { + return(0, returndatasize()) + } + } + } +} diff --git a/contracts/extensions/ZkSync/NativeOrderFactoryZkSync.sol b/contracts/extensions/ZkSync/NativeOrderFactoryZkSync.sol new file mode 100644 index 00000000..1af8eb1b --- /dev/null +++ b/contracts/extensions/ZkSync/NativeOrderFactoryZkSync.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.30; + +import { Clones } from "@openzeppelin/contracts/proxy/Clones.sol"; +import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; +import { Address, AddressLib } from "@1inch/solidity-utils/contracts/libraries/AddressLib.sol"; +import { SafeERC20, IERC20, IWETH } from "@1inch/solidity-utils/contracts/libraries/SafeERC20.sol"; + +import { IOrderMixin } from "../../interfaces/IOrderMixin.sol"; +import { Errors } from "../../libraries/Errors.sol"; +import { EIP712Alien } from "../../utils/EIP712Alien.sol"; +import { OrderLib } from "../../OrderLib.sol"; +import { NativeOrderImplZkSync } from "./NativeOrderImplZkSync.sol"; +import { MinimalProxyZkSync } from "./MinimalProxyZkSync.sol"; +import { ZkSyncLib } from "./ZkSyncLib.sol"; +import { NativeOrderFactoryBase } from "../NativeOrderFactoryBase.sol"; + +contract NativeOrderFactoryZkSync is NativeOrderFactoryBase { + using Clones for address; + using AddressLib for Address; + using SafeERC20 for IERC20; + using OrderLib for IOrderMixin.Order; + + bytes32 public immutable IMPLEMENTATION_INPUT_HASH; + bytes32 private immutable _PROXY_IMPL_BYTECODE_HASH; + + constructor( + IWETH weth, + address limitOrderProtocol, + IERC20 accessToken, + uint256 cancellationDelay, // Recommended 60 seconds delay after order expiration for rewardable cancellation + string memory name, + string memory version + ) + Ownable(msg.sender) + EIP712Alien(limitOrderProtocol, name, version) + { + IMPLEMENTATION = address(new NativeOrderImplZkSync( + weth, + address(this), + limitOrderProtocol, + accessToken, + cancellationDelay, + name, + version + )); + IMPLEMENTATION_INPUT_HASH = keccak256(abi.encode(IMPLEMENTATION)); + + MinimalProxyZkSync proxyImpl = new MinimalProxyZkSync(IMPLEMENTATION); + bytes32 bytecodeHash; + assembly ("memory-safe") { + bytecodeHash := extcodehash(proxyImpl) + } + _PROXY_IMPL_BYTECODE_HASH = bytecodeHash; + } + + function addressOfNativeOrder(IOrderMixin.Order calldata makerOrder) external view returns (address) { + bytes32 makerOrderHash = makerOrder.hash(_domainSeparatorV4()); + return ZkSyncLib.computeAddressZkSync(makerOrderHash, _PROXY_IMPL_BYTECODE_HASH, address(this), IMPLEMENTATION_INPUT_HASH); + } + + function _deployNativeOrder(address implementation, bytes32 salt) internal override virtual returns (address) { + return address(new MinimalProxyZkSync{salt: salt}(implementation)); + } +} diff --git a/contracts/extensions/ZkSync/NativeOrderImplZkSync.sol b/contracts/extensions/ZkSync/NativeOrderImplZkSync.sol new file mode 100644 index 00000000..f8c5a2ae --- /dev/null +++ b/contracts/extensions/ZkSync/NativeOrderImplZkSync.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.30; + +import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; +import { Clones } from "@openzeppelin/contracts/proxy/Clones.sol"; +import { IERC1271 } from "@openzeppelin/contracts/interfaces/IERC1271.sol"; +import { Address, AddressLib } from "@1inch/solidity-utils/contracts/libraries/AddressLib.sol"; +import { SafeERC20, IERC20, IWETH } from "@1inch/solidity-utils/contracts/libraries/SafeERC20.sol"; +import { OnlyWethReceiver } from "@1inch/solidity-utils/contracts/mixins/OnlyWethReceiver.sol"; + +import { MakerTraits, MakerTraitsLib } from "../../libraries/MakerTraitsLib.sol"; +import { OrderLib, IOrderMixin } from "../../OrderLib.sol"; +import { NativeOrderImpl } from "../NativeOrderImpl.sol"; +import { ZkSyncLib } from "./ZkSyncLib.sol"; + +contract NativeOrderImplZkSync is NativeOrderImpl { + using MakerTraitsLib for MakerTraits; + using OrderLib for IOrderMixin.Order; + + bytes32 private immutable _INPUT_HASH; + + constructor( + IWETH weth, + address nativeOrderFactory, + address limitOrderProtocol, + IERC20 accessToken, + uint256 cancellationDelay, // Recommended 60 seconds delay after order expiration for rewardable cancellation + string memory name, + string memory version + ) + NativeOrderImpl(weth, nativeOrderFactory, limitOrderProtocol, accessToken, cancellationDelay, name, version) + { + _INPUT_HASH = keccak256(abi.encode(address(this))); + } + + function _calcCloneAddress(IOrderMixin.Order calldata makerOrder) internal override view returns(address) { + bytes32 makerOrderHash = makerOrder.hash(_domainSeparatorV4()); + bytes32 bytecodeHash; + assembly ("memory-safe") { + bytecodeHash := extcodehash(address()) + } + // return _IMPLEMENTATION.predictDeterministicAddress(makerOrderHash, _FACTORY); + return ZkSyncLib.computeAddressZkSync(makerOrderHash, bytecodeHash, _FACTORY, _INPUT_HASH); + } +} diff --git a/contracts/extensions/ZkSync/ZkSyncLib.sol b/contracts/extensions/ZkSync/ZkSyncLib.sol new file mode 100644 index 00000000..5ec41215 --- /dev/null +++ b/contracts/extensions/ZkSync/ZkSyncLib.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +/** + * @title Library for ZkSync contracts. + * @custom:security-contact security@1inch.io + */ +library ZkSyncLib { + // keccak256("zksyncCreate2") + bytes32 constant private _CREATE2_PREFIX = 0x2020dba91b30cc0006188af794c2fb30dd8520db7e2c088b7fc7c103c00ca494; + + /** + * @notice Returns the address of the contract deployed with CREATE2. + * @param salt The salt used for the deployment. + * @param bytecodeHash The hash of the bytecode. + * @param deployer The address of the deployer. + * @param inputHash The hash of the input. + * @return addr The computed address. + */ + function computeAddressZkSync( + bytes32 salt, + bytes32 bytecodeHash, + address deployer, + bytes32 inputHash + ) internal pure returns (address addr) { + assembly ("memory-safe") { + let ptr := mload(0x40) + mstore(ptr, _CREATE2_PREFIX) + mstore(add(ptr, 0x20), deployer) + mstore(add(ptr, 0x40), salt) + mstore(add(ptr, 0x60), bytecodeHash) + mstore(add(ptr, 0x80), inputHash) + addr := and(keccak256(ptr, 0xa0), 0xffffffffffffffffffffffffffffffffffffffff) + } + } +}