/ test / OrderPayments.t.sol
OrderPayments.t.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 {Test} from "forge-std/Test.sol";
  8  import {ERC20Mock, ERC20} from "openzeppelin/contracts/mocks/token/ERC20Mock.sol";
  9  
 10  import {OrderPaymentsFactory, OrderPaymentBinding, OrderPayment, ETH} from "../src/OrderPayments.sol";
 11  
 12  contract OrderPaymentsFactoryTest is Test {
 13      OrderPaymentsFactory factory;
 14      ERC20Mock mockToken;
 15      address payable merchant;
 16      address payable customer;
 17  
 18      OrderPaymentBinding binding;
 19  
 20      function setUp() public {
 21          factory = new OrderPaymentsFactory();
 22          mockToken = new ERC20Mock();
 23          merchant = payable(makeAddr("merchant"));
 24          customer = payable(makeAddr("customer"));
 25  
 26          binding = OrderPaymentBinding({
 27              chainId: 1,
 28              shopId: 123,
 29              orderId: 456,
 30              receivingAddress: merchant
 31          });
 32      }
 33  
 34      function test_getSalt() public view {
 35          bytes32 salt = factory.getSalt(binding);
 36          bytes32 expectedSalt = keccak256(abi.encode(binding));
 37          assertEq(salt, expectedSalt);
 38      }
 39  
 40      function test_getSalt_DifferentBindingsProduceDifferentSalts() public view {
 41          OrderPaymentBinding memory binding2 = OrderPaymentBinding({
 42              chainId: 1,
 43              shopId: 123,
 44              orderId: 457, // different order ID
 45              receivingAddress: merchant
 46          });
 47  
 48          bytes32 salt1 = factory.getSalt(binding);
 49          bytes32 salt2 = factory.getSalt(binding2);
 50          assertTrue(salt1 != salt2);
 51      }
 52  
 53      function test_getOrderPaymentAddress() public {
 54          address predictedAddress = factory.getOrderPaymentAddress(binding);
 55  
 56          // Deploy the contract and verify the address matches
 57          factory.deployOrderPayment(binding);
 58  
 59          // The predicted address should match the actual deployed address
 60          assertTrue(predictedAddress.code.length > 0);
 61      }
 62  
 63      function test_deployOrderPayment() public {
 64          address predictedAddress = factory.getOrderPaymentAddress(binding);
 65  
 66          // Deploy the contract
 67          factory.deployOrderPayment(binding);
 68  
 69          // Verify the contract was deployed
 70          assertTrue(predictedAddress.code.length > 0);
 71  
 72          // Verify the contract is an OrderPayment instance
 73          OrderPayment orderPayment = OrderPayment(predictedAddress);
 74          // Contract should exist and be callable
 75          vm.expectCall(predictedAddress, abi.encodeWithSelector(OrderPayment.sweepEth.selector));
 76          orderPayment.sweepEth("");
 77      }
 78  
 79      function test_deployOrderPayment_CannotDeployTwice() public {
 80          // Deploy once
 81          factory.deployOrderPayment(binding);
 82  
 83          // Try to deploy again - should revert
 84          vm.expectRevert();
 85          factory.deployOrderPayment(binding);
 86      }
 87  
 88      function test_deployOrderPayment_DifferentBindingsDeployToDifferentAddresses() public {
 89          OrderPaymentBinding memory binding2 = OrderPaymentBinding({
 90              chainId: 1,
 91              shopId: 123,
 92              orderId: 457,
 93              receivingAddress: merchant
 94          });
 95  
 96          address addr1 = factory.getOrderPaymentAddress(binding);
 97          address addr2 = factory.getOrderPaymentAddress(binding2);
 98  
 99          assertTrue(addr1 != addr2);
100  
101          factory.deployOrderPayment(binding);
102          factory.deployOrderPayment(binding2);
103  
104          assertTrue(addr1.code.length > 0);
105          assertTrue(addr2.code.length > 0);
106      }
107  }
108  
109  contract OrderPaymentTest is Test {
110      OrderPayment orderPayment;
111      ERC20Mock mockToken;
112      address payable merchant;
113      address payable customer;
114  
115      function setUp() public {
116          merchant = payable(makeAddr("merchant"));
117          customer = payable(makeAddr("customer"));
118          orderPayment = new OrderPayment(merchant);
119          mockToken = new ERC20Mock();
120  
121          // Fund the customer for testing
122          vm.deal(customer, 10 ether);
123          mockToken.mint(customer, 1000 ether);
124      }
125  
126      function test_constructor() public {
127          OrderPayment newOrderPayment = new OrderPayment(merchant);
128          // Contract should be deployed successfully
129          assertTrue(address(newOrderPayment) != address(0));
130      }
131  
132      function test_sweepEth() public {
133          uint256 amount = 1 ether;
134  
135          // Fund the order payment contract
136          vm.deal(address(orderPayment), amount);
137  
138          uint256 merchantBalanceBefore = merchant.balance;
139          uint256 contractBalanceBefore = address(orderPayment).balance;
140  
141          assertEq(contractBalanceBefore, amount);
142  
143          // Sweep ETH
144          orderPayment.sweepEth("");
145  
146          uint256 merchantBalanceAfter = merchant.balance;
147          uint256 contractBalanceAfter = address(orderPayment).balance;
148  
149          assertEq(contractBalanceAfter, 0);
150          assertEq(merchantBalanceAfter, merchantBalanceBefore + amount);
151      }
152  
153      function test_sweepEth_EmptyContract() public {
154          uint256 merchantBalanceBefore = merchant.balance;
155  
156          // Contract has no ETH
157          assertEq(address(orderPayment).balance, 0);
158  
159          // Sweep should not revert but also not change balances
160          orderPayment.sweepEth("");
161  
162          assertEq(address(orderPayment).balance, 0);
163          assertEq(merchant.balance, merchantBalanceBefore);
164      }
165  
166      function test_sweepERC20() public {
167          uint256 amount = 100 ether;
168  
169          // Transfer tokens to the order payment contract
170          vm.prank(customer);
171          bool success = mockToken.transfer(address(orderPayment), amount);
172          assertTrue(success);
173  
174          uint256 merchantBalanceBefore = mockToken.balanceOf(merchant);
175          uint256 contractBalanceBefore = mockToken.balanceOf(address(orderPayment));
176  
177          assertEq(contractBalanceBefore, amount);
178  
179          // Sweep ERC20
180          orderPayment.sweepErc20(mockToken, "");
181  
182          uint256 merchantBalanceAfter = mockToken.balanceOf(merchant);
183          uint256 contractBalanceAfter = mockToken.balanceOf(address(orderPayment));
184  
185          assertEq(contractBalanceAfter, 0);
186          assertEq(merchantBalanceAfter, merchantBalanceBefore + amount);
187      }
188  
189      function test_sweepERC20_EmptyContract() public {
190          uint256 merchantBalanceBefore = mockToken.balanceOf(merchant);
191  
192          // Contract has no tokens
193          assertEq(mockToken.balanceOf(address(orderPayment)), 0);
194  
195          // Sweep should not revert but also not change balances
196          orderPayment.sweepErc20(mockToken, "");
197  
198          assertEq(mockToken.balanceOf(address(orderPayment)), 0);
199          assertEq(mockToken.balanceOf(merchant), merchantBalanceBefore);
200      }
201  
202      function test_sweep_WithETHAddress() public {
203          uint256 amount = 2 ether;
204  
205          // Fund the contract with ETH
206          vm.deal(address(orderPayment), amount);
207  
208          uint256 merchantBalanceBefore = merchant.balance;
209  
210          // Create a mock ERC20 with ETH address (address(0))
211          ERC20 ethToken = ERC20(ETH);
212  
213          // This should call sweepEth internally
214          orderPayment.sweep(ethToken, "");
215  
216          uint256 merchantBalanceAfter = merchant.balance;
217  
218          assertEq(address(orderPayment).balance, 0);
219          assertEq(merchantBalanceAfter, merchantBalanceBefore + amount);
220      }
221  
222      function test_sweepERC20_CanBeCalledByAnyone() public {
223          uint256 amount = 100 ether;
224  
225          // Transfer tokens to the order payment contract
226          vm.prank(customer);
227          assertTrue(mockToken.transfer(address(orderPayment), amount));
228  
229          // Random address calls sweep
230          address randomUser = makeAddr("random");
231          vm.prank(randomUser);
232          orderPayment.sweepErc20(mockToken, "");
233  
234          // Tokens should still go to merchant
235          assertEq(mockToken.balanceOf(merchant), amount);
236          assertEq(mockToken.balanceOf(address(orderPayment)), 0);
237      }
238  
239      function test_sweepEth_CanBeCalledByAnyone() public {
240          uint256 amount = 1 ether;
241  
242          // Fund the order payment contract
243          vm.deal(address(orderPayment), amount);
244  
245          // Random address calls sweep
246          address randomUser = makeAddr("random");
247          vm.prank(randomUser);
248          orderPayment.sweepEth("");
249  
250          // ETH should still go to merchant
251          assertEq(merchant.balance, amount);
252          assertEq(address(orderPayment).balance, 0);
253      }
254  
255      function test_fuzz_sweepEth(uint256 amount) public {
256          // Bound the amount to reasonable values
257          amount = bound(amount, 0, 100 ether);
258  
259          // Fund the contract
260          vm.deal(address(orderPayment), amount);
261  
262          uint256 merchantBalanceBefore = merchant.balance;
263  
264          orderPayment.sweepEth("");
265  
266          assertEq(address(orderPayment).balance, 0);
267          assertEq(merchant.balance, merchantBalanceBefore + amount);
268      }
269  
270      function test_fuzz_sweepERC20(uint256 amount) public {
271          // Bound the amount to reasonable values
272          amount = bound(amount, 0, type(uint128).max);
273  
274          // Mint tokens to customer and transfer to contract
275          mockToken.mint(customer, amount);
276          vm.prank(customer);
277          assertTrue(mockToken.transfer(address(orderPayment), amount));
278  
279          uint256 merchantBalanceBefore = mockToken.balanceOf(merchant);
280  
281          orderPayment.sweepErc20(mockToken, "");
282  
283          assertEq(mockToken.balanceOf(address(orderPayment)), 0);
284          assertEq(mockToken.balanceOf(merchant), merchantBalanceBefore + amount);
285      }
286  }
287  
288  contract OrderPaymentsIntegrationTest is Test {
289      OrderPaymentsFactory factory;
290      ERC20Mock mockToken;
291      address payable merchant;
292      address payable customer;
293  
294      function setUp() public {
295          factory = new OrderPaymentsFactory();
296          mockToken = new ERC20Mock();
297          merchant = payable(makeAddr("merchant"));
298          customer = payable(makeAddr("customer"));
299  
300          // Fund the customer
301          vm.deal(customer, 10 ether);
302          mockToken.mint(customer, 1000 ether);
303      }
304  
305      function test_fullWorkflow() public {
306          // Create binding
307          OrderPaymentBinding memory binding = OrderPaymentBinding({
308              chainId: 1,
309              shopId: 123,
310              orderId: 456,
311              receivingAddress: merchant
312          });
313  
314          // Get predicted address
315          address predictedAddress = factory.getOrderPaymentAddress(binding);
316  
317          // Customer sends ETH and tokens to the predicted address
318          vm.startPrank(customer);
319          payable(predictedAddress).transfer(1 ether);
320          assertTrue(mockToken.transfer(predictedAddress, 100 ether));
321          vm.stopPrank();
322  
323          // Verify funds are at the predicted address
324          assertEq(predictedAddress.balance, 1 ether);
325          assertEq(mockToken.balanceOf(predictedAddress), 100 ether);
326  
327          // Deploy the contract
328          factory.deployOrderPayment(binding);
329  
330          // Get the order payment instance
331          OrderPayment orderPayment = OrderPayment(predictedAddress);
332  
333          // Sweep funds
334          uint256 merchantEthBefore = merchant.balance;
335          uint256 merchantTokensBefore = mockToken.balanceOf(merchant);
336  
337          orderPayment.sweepEth("");
338          orderPayment.sweepErc20(mockToken, "");
339  
340          // Verify funds were swept to merchant
341          assertEq(merchant.balance, merchantEthBefore + 1 ether);
342          assertEq(mockToken.balanceOf(merchant), merchantTokensBefore + 100 ether);
343          assertEq(predictedAddress.balance, 0);
344          assertEq(mockToken.balanceOf(predictedAddress), 0);
345      }
346  
347      function test_multipleOrders() public {
348          // Create multiple bindings
349          OrderPaymentBinding memory binding1 = OrderPaymentBinding({
350              chainId: 1,
351              shopId: 123,
352              orderId: 456,
353              receivingAddress: merchant
354          });
355  
356          OrderPaymentBinding memory binding2 = OrderPaymentBinding({
357              chainId: 1,
358              shopId: 123,
359              orderId: 457,
360              receivingAddress: merchant
361          });
362  
363          // Get predicted addresses
364          address addr1 = factory.getOrderPaymentAddress(binding1);
365          address addr2 = factory.getOrderPaymentAddress(binding2);
366  
367          // Verify addresses are different
368          assertTrue(addr1 != addr2);
369  
370          // Send different amounts to each
371          vm.startPrank(customer);
372          payable(addr1).transfer(1 ether);
373          payable(addr2).transfer(2 ether);
374          assertTrue(mockToken.transfer(addr1, 100 ether));
375          assertTrue(mockToken.transfer(addr2, 200 ether));
376          vm.stopPrank();
377  
378          // Deploy contracts
379          factory.deployOrderPayment(binding1);
380          factory.deployOrderPayment(binding2);
381  
382          // Sweep funds from both
383          uint256 merchantEthBefore = merchant.balance;
384          uint256 merchantTokensBefore = mockToken.balanceOf(merchant);
385  
386          OrderPayment(addr1).sweepEth("");
387          OrderPayment(addr1).sweepErc20(mockToken, "");
388          OrderPayment(addr2).sweepEth("");
389          OrderPayment(addr2).sweepErc20(mockToken, "");
390  
391          // Verify total funds were swept
392          assertEq(merchant.balance, merchantEthBefore + 3 ether);
393          assertEq(mockToken.balanceOf(merchant), merchantTokensBefore + 300 ether);
394      }
395  }