github.com/ethereum-optimism/optimism@v1.7.2/packages/contracts-bedrock/test/periphery/TransferOnion.t.sol (about) 1 // SPDX-License-Identifier: MIT 2 pragma solidity 0.8.15; 3 4 // Testing utilities 5 import { Test } from "forge-std/Test.sol"; 6 import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 7 8 // Target contract 9 import { TransferOnion } from "src/periphery/TransferOnion.sol"; 10 11 /// @title TransferOnionTest 12 /// @notice Test coverage of TransferOnion 13 contract TransferOnionTest is Test { 14 /// @notice TransferOnion 15 TransferOnion internal onion; 16 17 /// @notice Token constructor argument 18 address internal _token; 19 20 /// @notice Sender constructor argument 21 address internal _sender; 22 23 /// @notice Sets up addresses, deploys contracts and funds the owner. 24 function setUp() public { 25 ERC20 token = new ERC20("Token", "TKN"); 26 _token = address(token); 27 _sender = makeAddr("sender"); 28 } 29 30 /// @notice Deploy the TransferOnion with a dummy shell. 31 function _deploy() public { 32 _deploy(bytes32(0)); 33 } 34 35 /// @notice Deploy the TransferOnion with a specific shell. 36 function _deploy(bytes32 _shell) public { 37 onion = new TransferOnion({ _token: ERC20(_token), _sender: _sender, _shell: _shell }); 38 } 39 40 /// @notice Build the onion data. 41 function _onionize(TransferOnion.Layer[] memory _layers) 42 public 43 pure 44 returns (bytes32, TransferOnion.Layer[] memory) 45 { 46 uint256 length = _layers.length; 47 bytes32 hash = bytes32(0); 48 for (uint256 i; i < length; i++) { 49 TransferOnion.Layer memory layer = _layers[i]; 50 _layers[i].shell = hash; 51 hash = keccak256(abi.encode(layer.recipient, layer.amount, hash)); 52 } 53 return (hash, _layers); 54 } 55 56 /// @notice The constructor sets the variables as expected. 57 function test_constructor_succeeds() external { 58 _deploy(); 59 60 assertEq(address(onion.TOKEN()), _token); 61 assertEq(onion.SENDER(), _sender); 62 assertEq(onion.shell(), bytes32(0)); 63 } 64 65 /// @notice Tests unwrapping the onion. 66 function test_unwrap_succeeds() external { 67 // Commit to transferring tiny amounts of tokens 68 TransferOnion.Layer[] memory _layers = new TransferOnion.Layer[](2); 69 _layers[0] = TransferOnion.Layer(address(1), 1, bytes32(0)); 70 _layers[1] = TransferOnion.Layer(address(2), 2, bytes32(0)); 71 72 // Build the onion shell 73 (bytes32 shell, TransferOnion.Layer[] memory layers) = _onionize(_layers); 74 _deploy(shell); 75 76 assertEq(onion.shell(), shell); 77 78 address token = address(onion.TOKEN()); 79 address sender = onion.SENDER(); 80 81 // give 3 units of token to sender 82 deal(token, onion.SENDER(), 3); 83 vm.prank(sender); 84 ERC20(token).approve(address(onion), 3); 85 86 // To build the inputs, to `peel`, need to reverse the list 87 TransferOnion.Layer[] memory inputs = new TransferOnion.Layer[](2); 88 int256 length = int256(layers.length); 89 for (int256 i = length - 1; i >= 0; i--) { 90 uint256 ui = uint256(i); 91 uint256 revidx = uint256(length) - ui - 1; 92 TransferOnion.Layer memory layer = layers[ui]; 93 inputs[revidx] = layer; 94 } 95 96 // The accounts have no balance 97 assertEq(ERC20(_token).balanceOf(address(1)), 0); 98 assertEq(ERC20(_token).balanceOf(address(2)), 0); 99 100 onion.peel(inputs); 101 102 // Now the accounts have the expected balance 103 assertEq(ERC20(_token).balanceOf(address(1)), 1); 104 assertEq(ERC20(_token).balanceOf(address(2)), 2); 105 } 106 }