/ src / OrderPayments.sol
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  }