OrderPayments.sol
1 // SPDX-FileCopyrightText: 2024 Mass Labs 2 // 3 // SPDX-License-Identifier: GPL-3.0-or-later 4 5 pragma solidity ^0.8.19; 6 7 import {ERC20} from "openzeppelin/contracts/token/ERC20/ERC20.sol"; 8 import {EfficientHashLib} from "solady/utils/EfficientHashLib.sol"; 9 10 address constant ETH = address(0); 11 12 /// used as salt for creating an OrderPayment Contract 13 struct OrderPaymentBinding { 14 uint256 chainId; 15 uint256 shopId; 16 uint256 orderId; 17 /// Merchant (or escrow) account receiving the payment 18 address payable receivingAddress; 19 } 20 21 /// @title A contract for an order with functions that sweeps ERC20's and Eth from the payment address to the receiving address 22 /// @notice ERC20 sweeps can fail depending on the ERC20 implementation 23 contract OrderPayment { 24 address payable receivingAddress; 25 26 constructor(address payable _receivingAddress) { 27 receivingAddress = _receivingAddress; 28 } 29 30 /// @param hookCallData an optional call data that is passed to the receiving address after the sweep 31 /// the hook call data can be anything and it is up to the receiving contract to validate it 32 function sweep(ERC20 token, bytes calldata hookCallData) public { 33 if (address(token) == ETH) { 34 sweepEth(hookCallData); 35 } else { 36 sweepErc20(token, hookCallData); 37 } 38 } 39 40 function sweepEth(bytes calldata hookCallData) public { 41 uint256 balance = address(this).balance; 42 (bool success,) = receivingAddress.call{value: balance}(hookCallData); 43 require(success, "Failed to send Ether"); 44 } 45 46 function sweepErc20(ERC20 token, bytes calldata hookCallData) public { 47 uint256 balance = token.balanceOf(address(this)); 48 bool success = token.transfer(receivingAddress, balance); 49 require(success, "Failed to transfer ERC20 tokens"); 50 if (hookCallData.length > 0) { 51 (success, ) = receivingAddress.call(hookCallData); 52 require(success, "Failed to call hook"); 53 } 54 } 55 } 56 57 /// @title Creates an OrderPayment instances. 58 contract OrderPaymentsFactory { 59 function getSalt( 60 OrderPaymentBinding calldata binding 61 ) public pure returns (bytes32) { 62 bytes memory encodedAbi = abi.encode(binding); 63 return EfficientHashLib.hash(encodedAbi); 64 } 65 66 function getBytecodeHash( 67 address receivingAddress 68 ) public pure returns (bytes32) { 69 bytes memory bytecode = type(OrderPayment).creationCode; 70 return EfficientHashLib.hash(abi.encodePacked(bytecode, abi.encode(receivingAddress))); 71 } 72 73 function getOrderPaymentAddress( 74 OrderPaymentBinding calldata binding 75 ) public view returns (address) { 76 bytes32 hash = EfficientHashLib.hash( 77 abi.encodePacked( 78 bytes1(0xff), 79 address(this), 80 getSalt(binding), // salt 81 bytes32(getBytecodeHash(binding.receivingAddress)) 82 ) 83 ); 84 85 return address(uint160(uint256(hash))); 86 } 87 88 function deployOrderPayment ( 89 OrderPaymentBinding calldata binding 90 ) public { 91 new OrderPayment{salt: getSalt(binding)}(binding.receivingAddress); 92 } 93 }