Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
N
nebula
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
exchain
nebula
Commits
4809bf6c
Unverified
Commit
4809bf6c
authored
Feb 10, 2023
by
mergify[bot]
Committed by
GitHub
Feb 10, 2023
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #4855 from ethereum-optimism/feat/transfer-onion
contracts-bedrock: add TransferOnion contract
parents
fd29e1c7
05b134d4
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
213 additions
and
0 deletions
+213
-0
.gas-snapshot
packages/contracts-bedrock/.gas-snapshot
+2
-0
TransferOnion.sol
...s/contracts-bedrock/contracts/periphery/TransferOnion.sol
+88
-0
TransferOnion.t.sol
...ages/contracts-bedrock/contracts/test/TransferOnion.t.sol
+123
-0
No files found.
packages/contracts-bedrock/.gas-snapshot
View file @
4809bf6c
...
@@ -395,3 +395,5 @@ SystemConfig_Setters_TestFail:test_setBatcherHash_notOwner_reverts() (gas: 10545
...
@@ -395,3 +395,5 @@ SystemConfig_Setters_TestFail:test_setBatcherHash_notOwner_reverts() (gas: 10545
SystemConfig_Setters_TestFail:test_setGasConfig_notOwner_reverts() (gas: 10532)
SystemConfig_Setters_TestFail:test_setGasConfig_notOwner_reverts() (gas: 10532)
SystemConfig_Setters_TestFail:test_setGasLimit_notOwner_reverts() (gas: 10636)
SystemConfig_Setters_TestFail:test_setGasLimit_notOwner_reverts() (gas: 10636)
SystemConfig_Setters_TestFail:test_setUnsafeBlockSigner_notOwner_reverts() (gas: 10638)
SystemConfig_Setters_TestFail:test_setUnsafeBlockSigner_notOwner_reverts() (gas: 10638)
TransferOnionTest:test_constructor_succeeds() (gas: 564833)
TransferOnionTest:test_unwrap_succeeds() (gas: 724936)
packages/contracts-bedrock/contracts/periphery/TransferOnion.sol
0 → 100644
View file @
4809bf6c
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { ReentrancyGuard } from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
/**
* @title TransferOnion
* @notice TransferOnion is a hash onion for distributing tokens. The shell commits
* to an ordered list of the token transfers and can be permissionlessly
* unwrapped in order. The SENDER must `approve` this contract as
* `transferFrom` is used to move the token balances.
*/
contract TransferOnion is ReentrancyGuard {
using SafeERC20 for ERC20;
/**
* @notice Struct representing a layer of the onion.
*/
struct Layer {
address recipient;
uint256 amount;
bytes32 shell;
}
/**
* @notice Address of the token to distribute.
*/
ERC20 public immutable TOKEN;
/**
* @notice Address of the account to distribute tokens from.
*/
address public immutable SENDER;
/**
* @notice Current shell hash.
*/
bytes32 public shell;
/**
* @param _token Address of the token to distribute.
* @param _sender Address of the sender to distribute from.
* @param _shell Initial shell of the onion.
*/
constructor(
ERC20 _token,
address _sender,
bytes32 _shell
) {
TOKEN = _token;
SENDER = _sender;
shell = _shell;
}
/**
* @notice Peels layers from the onion and distributes tokens.
*
* @param _layers Array of onion layers to peel.
*/
function peel(Layer[] memory _layers) public nonReentrant {
bytes32 tempShell = shell;
uint256 length = _layers.length;
for (uint256 i = 0; i < length; ) {
Layer memory layer = _layers[i];
// Confirm that the onion layer is correct.
require(
keccak256(abi.encode(layer.recipient, layer.amount, layer.shell)) == tempShell,
"TransferOnion: what are you doing in my swamp?"
);
// Update the onion layer.
tempShell = layer.shell;
// Transfer the tokens.
TOKEN.safeTransferFrom(SENDER, layer.recipient, layer.amount);
// Unchecked increment to save some gas.
unchecked {
++i;
}
}
shell = tempShell;
}
}
packages/contracts-bedrock/contracts/test/TransferOnion.t.sol
0 → 100644
View file @
4809bf6c
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import { Test } from "forge-std/Test.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { TransferOnion } from "../periphery/TransferOnion.sol";
/**
* @title TransferOnionTest
* @notice Test coverage of TransferOnion
*/
contract TransferOnionTest is Test {
/**
* @notice TransferOnion
*/
TransferOnion internal onion;
/**
* @notice token constructor arg
*/
address internal _token;
/**
* @notice sender constructor arg
*/
address internal _sender;
/**
* @notice Sets up addresses, deploys contracts and funds the owner.
*/
function setUp() public {
ERC20 token = new ERC20("Token", "TKN");
_token = address(token);
_sender = makeAddr("sender");
}
/**
* @notice Deploy the TransferOnion with a dummy shell
*/
function _deploy() public {
_deploy(bytes32(0));
}
/**
* @notice Deploy the TransferOnion with a specific shell
*/
function _deploy(bytes32 _shell) public {
onion = new TransferOnion({ _token: ERC20(_token), _sender: _sender, _shell: _shell });
}
/**
* @notice Build the onion data
*/
function _onionize(TransferOnion.Layer[] memory _layers)
public
pure
returns (bytes32, TransferOnion.Layer[] memory)
{
uint256 length = _layers.length;
bytes32 hash = bytes32(0);
for (uint256 i; i < length; i++) {
TransferOnion.Layer memory layer = _layers[i];
_layers[i].shell = hash;
hash = keccak256(abi.encode(layer.recipient, layer.amount, hash));
}
return (hash, _layers);
}
/**
* @notice The constructor sets the variables as expected
*/
function test_constructor_succeeds() external {
_deploy();
assertEq(address(onion.TOKEN()), _token);
assertEq(onion.SENDER(), _sender);
assertEq(onion.shell(), bytes32(0));
}
/**
* @notice unwrap
*/
function test_unwrap_succeeds() external {
// Commit to transferring tiny amounts of tokens
TransferOnion.Layer[] memory _layers = new TransferOnion.Layer[](2);
_layers[0] = TransferOnion.Layer(address(1), 1, bytes32(0));
_layers[1] = TransferOnion.Layer(address(2), 2, bytes32(0));
// Build the onion shell
(bytes32 shell, TransferOnion.Layer[] memory layers) = _onionize(_layers);
_deploy(shell);
assertEq(onion.shell(), shell);
address token = address(onion.TOKEN());
address sender = onion.SENDER();
// give 3 units of token to sender
deal(token, onion.SENDER(), 3);
vm.prank(sender);
ERC20(token).approve(address(onion), 3);
// To build the inputs, to `peel`, need to reverse the list
TransferOnion.Layer[] memory inputs = new TransferOnion.Layer[](2);
int256 length = int256(layers.length);
for (int256 i = length - 1; i >= 0; i--) {
uint256 ui = uint256(i);
uint256 revidx = uint256(length) - ui - 1;
TransferOnion.Layer memory layer = layers[ui];
inputs[revidx] = layer;
}
// The accounts have no balance
assertEq(ERC20(_token).balanceOf(address(1)), 0);
assertEq(ERC20(_token).balanceOf(address(2)), 0);
onion.peel(inputs);
// Now the accounts have the expected balance
assertEq(ERC20(_token).balanceOf(address(1)), 1);
assertEq(ERC20(_token).balanceOf(address(2)), 2);
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment