diff --git a/script/Zenith.s.sol b/script/Zenith.s.sol index 6c4f3bf..3e4d2e7 100644 --- a/script/Zenith.s.sol +++ b/script/Zenith.s.sol @@ -1,11 +1,14 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.24; -import {Script} from "forge-std/Script.sol"; +// deploy contracts import {Zenith} from "../src/Zenith.sol"; -import {Passage, RollupPassage} from "../src/Passage.sol"; import {Transactor} from "../src/Transact.sol"; -import {HostOrders, RollupOrders} from "../src/Orders.sol"; +import {HostOrders, RollupOrders} from "../src/orders/Orders.sol"; +import {Passage} from "../src/passage/Passage.sol"; +import {RollupPassage} from "../src/passage/RollupPassage.sol"; +// utils +import {Script} from "forge-std/Script.sol"; contract ZenithScript is Script { // deploy: diff --git a/src/Transact.sol b/src/Transact.sol index 0faedd3..2cca0e7 100644 --- a/src/Transact.sol +++ b/src/Transact.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.24; -import {Passage} from "./Passage.sol"; +import {Passage} from "./passage/Passage.sol"; /// @notice A contract deployed to Host chain that enables transactions from L1 to be sent on an L2. contract Transactor { diff --git a/src/UsesPermit2.sol b/src/UsesPermit2.sol new file mode 100644 index 0000000..1244fac --- /dev/null +++ b/src/UsesPermit2.sol @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import {IOrders} from "./orders/IOrders.sol"; +import {ISignatureTransfer} from "permit2/src/interfaces/ISignatureTransfer.sol"; + +abstract contract UsesPermit2 { + /// @param permit - the permit2 single token transfer details. includes a `deadline` and an unordered `nonce`. + /// @param signer - the signer of the permit2 info; the owner of the tokens. + /// @param signature - the signature over the permit + witness. + struct Permit2 { + ISignatureTransfer.PermitTransferFrom permit; + address owner; + bytes signature; + } + + /// @param permit - the permit2 batch token transfer details. includes a `deadline` and an unordered `nonce`. + /// @param signer - the signer of the permit2 info; the owner of the tokens. + /// @param signature - the signature over the permit + witness. + struct Permit2Batch { + ISignatureTransfer.PermitBatchTransferFrom permit; + address owner; + bytes signature; + } + + /// @notice Struct to hold the pre-hashed witness field and the witness type string. + struct Witness { + bytes32 witnessHash; + string witnessTypeString; + } + + /// @notice The Permit2 contract address. + address immutable permit2Contract; + + constructor(address _permit2) { + permit2Contract = _permit2; + } +} diff --git a/src/interfaces/IOrders.sol b/src/orders/IOrders.sol similarity index 100% rename from src/interfaces/IOrders.sol rename to src/orders/IOrders.sol diff --git a/src/orders/OrderDestination.sol b/src/orders/OrderDestination.sol new file mode 100644 index 0000000..556ef31 --- /dev/null +++ b/src/orders/OrderDestination.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import {OrdersPermit2} from "./OrdersPermit2.sol"; +import {IOrders} from "./IOrders.sol"; +import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; + +/// @notice Contract capable of processing fulfillment of intent-based Orders. +abstract contract OrderDestination is IOrders, OrdersPermit2 { + /// @notice Emitted when Order Outputs are sent to their recipients. + /// @dev NOTE that here, Output.chainId denotes the *origin* chainId. + event Filled(Output[] outputs); + + /// @notice Fill any number of Order(s), by transferring their Output(s). + /// @dev Filler may aggregate multiple Outputs with the same (`chainId`, `recipient`, `token`) into a single Output with the summed `amount`. + /// @dev NOTE that here, Output.chainId denotes the *origin* chainId. + /// @param outputs - The Outputs to be transferred. + /// @custom:emits Filled + function fill(Output[] memory outputs) external payable { + // transfer outputs + _transferOutputs(outputs); + + // emit + emit Filled(outputs); + } + + /// @notice Fill any number of Order(s), by transferring their Output(s) via permit2 signed batch transfer. + /// @dev Can only provide ERC20 tokens as Outputs. + /// @dev Filler may aggregate multiple Outputs with the same (`chainId`, `recipient`, `token`) into a single Output with the summed `amount`. + /// @dev the permit2 signer is the Filler providing the Outputs. + /// @dev the permit2 `permitted` tokens MUST match provided Outputs. + /// @dev Filler MUST submit `fill` and `intitiate` within an atomic bundle. + /// @dev NOTE that here, Output.chainId denotes the *origin* chainId. + /// @param outputs - The Outputs to be transferred. signed over via permit2 witness. + /// @param permit2 - the permit2 details, signer, and signature. + /// @custom:emits Filled + function fillPermit2(Output[] memory outputs, OrdersPermit2.Permit2Batch calldata permit2) external { + // transfer all tokens to the Output recipients via permit2 (includes check on nonce & deadline) + _permitWitnessTransferFrom( + outputWitness(outputs), _fillTransferDetails(outputs, permit2.permit.permitted), permit2 + ); + + // emit + emit Filled(outputs); + } + + /// @notice Transfer the Order Outputs to their recipients. + function _transferOutputs(Output[] memory outputs) internal { + uint256 value = msg.value; + for (uint256 i; i < outputs.length; i++) { + if (outputs[i].token == address(0)) { + // this line should underflow if there's an attempt to spend more ETH than is attached to the transaction + value -= outputs[i].amount; + payable(outputs[i].recipient).transfer(outputs[i].amount); + } else { + IERC20(outputs[i].token).transferFrom(msg.sender, outputs[i].recipient, outputs[i].amount); + } + } + } +} diff --git a/src/Orders.sol b/src/orders/OrderOrigin.sol similarity index 62% rename from src/Orders.sol rename to src/orders/OrderOrigin.sol index 31862c7..8628150 100644 --- a/src/Orders.sol +++ b/src/orders/OrderOrigin.sol @@ -1,64 +1,10 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.24; -import {OrdersPermit2, UsesPermit2} from "./permit2/UsesPermit2.sol"; -import {IOrders} from "./interfaces/IOrders.sol"; +import {OrdersPermit2} from "./OrdersPermit2.sol"; +import {IOrders} from "./IOrders.sol"; import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; -/// @notice Contract capable of processing fulfillment of intent-based Orders. -abstract contract OrderDestination is IOrders, OrdersPermit2 { - /// @notice Emitted when Order Outputs are sent to their recipients. - /// @dev NOTE that here, Output.chainId denotes the *origin* chainId. - event Filled(Output[] outputs); - - /// @notice Fill any number of Order(s), by transferring their Output(s). - /// @dev Filler may aggregate multiple Outputs with the same (`chainId`, `recipient`, `token`) into a single Output with the summed `amount`. - /// @dev NOTE that here, Output.chainId denotes the *origin* chainId. - /// @param outputs - The Outputs to be transferred. - /// @custom:emits Filled - function fill(Output[] memory outputs) external payable { - // transfer outputs - _transferOutputs(outputs); - - // emit - emit Filled(outputs); - } - - /// @notice Fill any number of Order(s), by transferring their Output(s) via permit2 signed batch transfer. - /// @dev Can only provide ERC20 tokens as Outputs. - /// @dev Filler may aggregate multiple Outputs with the same (`chainId`, `recipient`, `token`) into a single Output with the summed `amount`. - /// @dev the permit2 signer is the Filler providing the Outputs. - /// @dev the permit2 `permitted` tokens MUST match provided Outputs. - /// @dev Filler MUST submit `fill` and `intitiate` within an atomic bundle. - /// @dev NOTE that here, Output.chainId denotes the *origin* chainId. - /// @param outputs - The Outputs to be transferred. signed over via permit2 witness. - /// @param permit2 - the permit2 details, signer, and signature. - /// @custom:emits Filled - function fillPermit2(Output[] memory outputs, OrdersPermit2.Permit2Batch calldata permit2) external { - // transfer all tokens to the Output recipients via permit2 (includes check on nonce & deadline) - _permitWitnessTransferFrom( - outputWitness(outputs), _fillTransferDetails(outputs, permit2.permit.permitted), permit2 - ); - - // emit - emit Filled(outputs); - } - - /// @notice Transfer the Order Outputs to their recipients. - function _transferOutputs(Output[] memory outputs) internal { - uint256 value = msg.value; - for (uint256 i; i < outputs.length; i++) { - if (outputs[i].token == address(0)) { - // this line should underflow if there's an attempt to spend more ETH than is attached to the transaction - value -= outputs[i].amount; - payable(outputs[i].recipient).transfer(outputs[i].amount); - } else { - IERC20(outputs[i].token).transferFrom(msg.sender, outputs[i].recipient, outputs[i].amount); - } - } - } -} - /// @notice Contract capable of registering initiation of intent-based Orders. abstract contract OrderOrigin is IOrders, OrdersPermit2 { /// @notice Thrown when an Order is submitted with a deadline that has passed. @@ -154,11 +100,3 @@ abstract contract OrderOrigin is IOrders, OrdersPermit2 { } } } - -contract HostOrders is OrderDestination { - constructor(address _permit2) UsesPermit2(_permit2) {} -} - -contract RollupOrders is OrderOrigin, OrderDestination { - constructor(address _permit2) UsesPermit2(_permit2) {} -} diff --git a/src/orders/Orders.sol b/src/orders/Orders.sol new file mode 100644 index 0000000..7bf3968 --- /dev/null +++ b/src/orders/Orders.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import {OrderDestination} from "./OrderDestination.sol"; +import {OrderOrigin} from "./OrderOrigin.sol"; +import {UsesPermit2} from "../UsesPermit2.sol"; + +contract HostOrders is OrderDestination { + constructor(address _permit2) UsesPermit2(_permit2) {} +} + +contract RollupOrders is OrderOrigin, OrderDestination { + constructor(address _permit2) UsesPermit2(_permit2) {} +} diff --git a/src/permit2/UsesPermit2.sol b/src/orders/OrdersPermit2.sol similarity index 52% rename from src/permit2/UsesPermit2.sol rename to src/orders/OrdersPermit2.sol index 8cd1e7f..3b951cd 100644 --- a/src/permit2/UsesPermit2.sol +++ b/src/orders/OrdersPermit2.sol @@ -1,41 +1,9 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.24; +import {IOrders} from "./IOrders.sol"; +import {UsesPermit2} from "../UsesPermit2.sol"; import {ISignatureTransfer} from "permit2/src/interfaces/ISignatureTransfer.sol"; -import {IOrders} from "../interfaces/IOrders.sol"; - -abstract contract UsesPermit2 { - /// @param permit - the permit2 single token transfer details. includes a `deadline` and an unordered `nonce`. - /// @param signer - the signer of the permit2 info; the owner of the tokens. - /// @param signature - the signature over the permit + witness. - struct Permit2 { - ISignatureTransfer.PermitTransferFrom permit; - address owner; - bytes signature; - } - - /// @param permit - the permit2 batch token transfer details. includes a `deadline` and an unordered `nonce`. - /// @param signer - the signer of the permit2 info; the owner of the tokens. - /// @param signature - the signature over the permit + witness. - struct Permit2Batch { - ISignatureTransfer.PermitBatchTransferFrom permit; - address owner; - bytes signature; - } - - /// @notice Struct to hold the pre-hashed witness field and the witness type string. - struct Witness { - bytes32 witnessHash; - string witnessTypeString; - } - - /// @notice The Permit2 contract address. - address immutable permit2Contract; - - constructor(address _permit2) { - permit2Contract = _permit2; - } -} abstract contract OrdersPermit2 is UsesPermit2 { string constant _OUTPUT_WITNESS_TYPESTRING = @@ -128,72 +96,3 @@ abstract contract OrdersPermit2 is UsesPermit2 { } } } - -abstract contract PassagePermit2 is UsesPermit2 { - string constant _ENTER_WITNESS_TYPESTRING = - "EnterWitness witness)EnterWitness(uint256 rollupChainId,address rollupRecipient)TokenPermissions(address token,uint256 amount)"; - - bytes32 constant _ENTER_WITNESS_TYPEHASH = keccak256("EnterWitness(uint256 rollupChainId,address rollupRecipient)"); - - string constant _EXIT_WITNESS_TYPESTRING = - "ExitWitness witness)ExitWitness(address hostRecipient)TokenPermissions(address token,uint256 amount)"; - - bytes32 constant _EXIT_WITNESS_TYPEHASH = keccak256("ExitWitness(address hostRecipient)"); - - /// @notice Struct to hash Enter witness data into a 32-byte witness field, in an EIP-712 compliant way. - struct EnterWitness { - uint256 rollupChainId; - address rollupRecipient; - } - - /// @notice Struct to hash Exit witness data into a 32-byte witness field, in an EIP-712 compliant way. - struct ExitWitness { - address hostRecipient; - } - - /// @notice Encode & hash the rollupChainId and rollupRecipient for use as a permit2 witness. - /// @return _witness - the hashed witness and its typestring. - function enterWitness(uint256 rollupChainId, address rollupRecipient) - public - pure - returns (Witness memory _witness) - { - _witness.witnessHash = - keccak256(abi.encode(_ENTER_WITNESS_TYPEHASH, EnterWitness(rollupChainId, rollupRecipient))); - _witness.witnessTypeString = _ENTER_WITNESS_TYPESTRING; - } - - /// @notice Hash the hostRecipient for use as a permit2 witness. - /// @return _witness - the hashed witness and its typestring. - function exitWitness(address hostRecipient) public pure returns (Witness memory _witness) { - _witness.witnessHash = keccak256(abi.encode(_EXIT_WITNESS_TYPEHASH, ExitWitness(hostRecipient))); - _witness.witnessTypeString = _EXIT_WITNESS_TYPESTRING; - } - - /// @notice Transfer tokens using permit2. - /// @param _witness - the hashed witness and its typestring. - /// @param permit2 - the Permit2 information. - function _permitWitnessTransferFrom(Witness memory _witness, Permit2 calldata permit2) internal { - ISignatureTransfer(permit2Contract).permitWitnessTransferFrom( - permit2.permit, - _selfTransferDetails(permit2.permit.permitted.amount), - permit2.owner, - _witness.witnessHash, - _witness.witnessTypeString, - permit2.signature - ); - } - - /// @notice Construct TransferDetails transferring a balance to this contract, for passing to permit2. - /// @dev always transfers the full amount to address(this). - /// @param amount - the amount to transfer to this contract. - /// @return transferDetails - the SignatureTransferDetails generated. - function _selfTransferDetails(uint256 amount) - internal - view - returns (ISignatureTransfer.SignatureTransferDetails memory transferDetails) - { - transferDetails.to = address(this); - transferDetails.requestedAmount = amount; - } -} diff --git a/src/Passage.sol b/src/passage/Passage.sol similarity index 68% rename from src/Passage.sol rename to src/passage/Passage.sol index a1faded..c39f803 100644 --- a/src/Passage.sol +++ b/src/passage/Passage.sol @@ -1,9 +1,9 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.24; -import {PassagePermit2, UsesPermit2} from "./permit2/UsesPermit2.sol"; +import {PassagePermit2} from "./PassagePermit2.sol"; +import {UsesPermit2} from "../UsesPermit2.sol"; import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; -import {ERC20Burnable} from "openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Burnable.sol"; /// @notice A contract deployed to Host chain that allows tokens to enter the rollup. contract Passage is PassagePermit2 { @@ -145,67 +145,3 @@ contract Passage is PassagePermit2 { emit EnterConfigured(token, _canEnter); } } - -/// @notice Enables tokens to Exit the rollup. -contract RollupPassage is PassagePermit2 { - /// @notice Emitted when native Ether exits the rollup. - /// @param hostRecipient - The *requested* recipient of tokens on the host chain. - /// @param amount - The amount of Ether exiting the rollup. - event Exit(address indexed hostRecipient, uint256 amount); - - /// @notice Emitted when ERC20 tokens exit the rollup. - /// @param hostRecipient - The *requested* recipient of tokens on the host chain. - /// @param token - The token exiting the rollup. - /// @param amount - The amount of ERC20s exiting the rollup. - event ExitToken(address indexed hostRecipient, address indexed token, uint256 amount); - - constructor(address _permit2) UsesPermit2(_permit2) {} - - /// @notice Allows native Ether to exit the rollup by being sent directly to the contract. - fallback() external payable { - exit(msg.sender); - } - - /// @notice Allows native Ether to exit the rollup by being sent directly to the contract. - receive() external payable { - exit(msg.sender); - } - - /// @notice Allows native Ether to exit the rollup. - /// @param hostRecipient - The *requested* recipient of tokens on the host chain. - /// @custom:emits Exit indicating the amount of Ether that was locked on the rollup & the requested host recipient. - function exit(address hostRecipient) public payable { - if (msg.value == 0) return; - emit Exit(hostRecipient, msg.value); - } - - /// @notice Allows ERC20 tokens to exit the rollup. - /// @param hostRecipient - The *requested* recipient of tokens on the host chain. - /// @param token - The rollup address of the token exiting the rollup. - /// @param amount - The amount of tokens exiting the rollup. - /// @custom:emits ExitToken - function exitToken(address hostRecipient, address token, uint256 amount) external { - // transfer tokens to this contract - IERC20(token).transferFrom(msg.sender, address(this), amount); - // burn and emit - _exitToken(hostRecipient, token, amount); - } - - /// @notice Allows ERC20 tokens to exit the rollup. - /// @param hostRecipient - The *requested* recipient of tokens on the host chain. - /// @param permit2 - The Permit2 information, including token & amount. - /// @custom:emits ExitToken - function exitTokenPermit2(address hostRecipient, PassagePermit2.Permit2 calldata permit2) external { - // transfer tokens to this contract - _permitWitnessTransferFrom(exitWitness(hostRecipient), permit2); - // burn and emit - _exitToken(hostRecipient, permit2.permit.permitted.token, permit2.permit.permitted.amount); - } - - /// @notice Shared functionality for tokens exiting rollup. - function _exitToken(address hostRecipient, address token, uint256 amount) internal { - if (amount == 0) return; - ERC20Burnable(token).burn(amount); - emit ExitToken(hostRecipient, token, amount); - } -} diff --git a/src/passage/PassagePermit2.sol b/src/passage/PassagePermit2.sol new file mode 100644 index 0000000..c99cb78 --- /dev/null +++ b/src/passage/PassagePermit2.sol @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import {UsesPermit2} from "../UsesPermit2.sol"; +import {ISignatureTransfer} from "permit2/src/interfaces/ISignatureTransfer.sol"; + +abstract contract PassagePermit2 is UsesPermit2 { + string constant _ENTER_WITNESS_TYPESTRING = + "EnterWitness witness)EnterWitness(uint256 rollupChainId,address rollupRecipient)TokenPermissions(address token,uint256 amount)"; + + bytes32 constant _ENTER_WITNESS_TYPEHASH = keccak256("EnterWitness(uint256 rollupChainId,address rollupRecipient)"); + + string constant _EXIT_WITNESS_TYPESTRING = + "ExitWitness witness)ExitWitness(address hostRecipient)TokenPermissions(address token,uint256 amount)"; + + bytes32 constant _EXIT_WITNESS_TYPEHASH = keccak256("ExitWitness(address hostRecipient)"); + + /// @notice Struct to hash Enter witness data into a 32-byte witness field, in an EIP-712 compliant way. + struct EnterWitness { + uint256 rollupChainId; + address rollupRecipient; + } + + /// @notice Struct to hash Exit witness data into a 32-byte witness field, in an EIP-712 compliant way. + struct ExitWitness { + address hostRecipient; + } + + /// @notice Encode & hash the rollupChainId and rollupRecipient for use as a permit2 witness. + /// @return _witness - the hashed witness and its typestring. + function enterWitness(uint256 rollupChainId, address rollupRecipient) + public + pure + returns (Witness memory _witness) + { + _witness.witnessHash = + keccak256(abi.encode(_ENTER_WITNESS_TYPEHASH, EnterWitness(rollupChainId, rollupRecipient))); + _witness.witnessTypeString = _ENTER_WITNESS_TYPESTRING; + } + + /// @notice Hash the hostRecipient for use as a permit2 witness. + /// @return _witness - the hashed witness and its typestring. + function exitWitness(address hostRecipient) public pure returns (Witness memory _witness) { + _witness.witnessHash = keccak256(abi.encode(_EXIT_WITNESS_TYPEHASH, ExitWitness(hostRecipient))); + _witness.witnessTypeString = _EXIT_WITNESS_TYPESTRING; + } + + /// @notice Transfer tokens using permit2. + /// @param _witness - the hashed witness and its typestring. + /// @param permit2 - the Permit2 information. + function _permitWitnessTransferFrom(Witness memory _witness, Permit2 calldata permit2) internal { + ISignatureTransfer(permit2Contract).permitWitnessTransferFrom( + permit2.permit, + _selfTransferDetails(permit2.permit.permitted.amount), + permit2.owner, + _witness.witnessHash, + _witness.witnessTypeString, + permit2.signature + ); + } + + /// @notice Construct TransferDetails transferring a balance to this contract, for passing to permit2. + /// @dev always transfers the full amount to address(this). + /// @param amount - the amount to transfer to this contract. + /// @return transferDetails - the SignatureTransferDetails generated. + function _selfTransferDetails(uint256 amount) + internal + view + returns (ISignatureTransfer.SignatureTransferDetails memory transferDetails) + { + transferDetails.to = address(this); + transferDetails.requestedAmount = amount; + } +} diff --git a/src/passage/RollupPassage.sol b/src/passage/RollupPassage.sol new file mode 100644 index 0000000..18fd0cd --- /dev/null +++ b/src/passage/RollupPassage.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.24; + +import {PassagePermit2} from "./PassagePermit2.sol"; +import {UsesPermit2} from "../UsesPermit2.sol"; +import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; +import {ERC20Burnable} from "openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Burnable.sol"; + +/// @notice Enables tokens to Exit the rollup. +contract RollupPassage is PassagePermit2 { + /// @notice Emitted when native Ether exits the rollup. + /// @param hostRecipient - The *requested* recipient of tokens on the host chain. + /// @param amount - The amount of Ether exiting the rollup. + event Exit(address indexed hostRecipient, uint256 amount); + + /// @notice Emitted when ERC20 tokens exit the rollup. + /// @param hostRecipient - The *requested* recipient of tokens on the host chain. + /// @param token - The token exiting the rollup. + /// @param amount - The amount of ERC20s exiting the rollup. + event ExitToken(address indexed hostRecipient, address indexed token, uint256 amount); + + constructor(address _permit2) UsesPermit2(_permit2) {} + + /// @notice Allows native Ether to exit the rollup by being sent directly to the contract. + fallback() external payable { + exit(msg.sender); + } + + /// @notice Allows native Ether to exit the rollup by being sent directly to the contract. + receive() external payable { + exit(msg.sender); + } + + /// @notice Allows native Ether to exit the rollup. + /// @param hostRecipient - The *requested* recipient of tokens on the host chain. + /// @custom:emits Exit indicating the amount of Ether that was locked on the rollup & the requested host recipient. + function exit(address hostRecipient) public payable { + if (msg.value == 0) return; + emit Exit(hostRecipient, msg.value); + } + + /// @notice Allows ERC20 tokens to exit the rollup. + /// @param hostRecipient - The *requested* recipient of tokens on the host chain. + /// @param token - The rollup address of the token exiting the rollup. + /// @param amount - The amount of tokens exiting the rollup. + /// @custom:emits ExitToken + function exitToken(address hostRecipient, address token, uint256 amount) external { + // transfer tokens to this contract + IERC20(token).transferFrom(msg.sender, address(this), amount); + // burn and emit + _exitToken(hostRecipient, token, amount); + } + + /// @notice Allows ERC20 tokens to exit the rollup. + /// @param hostRecipient - The *requested* recipient of tokens on the host chain. + /// @param permit2 - The Permit2 information, including token & amount. + /// @custom:emits ExitToken + function exitTokenPermit2(address hostRecipient, PassagePermit2.Permit2 calldata permit2) external { + // transfer tokens to this contract + _permitWitnessTransferFrom(exitWitness(hostRecipient), permit2); + // burn and emit + _exitToken(hostRecipient, permit2.permit.permitted.token, permit2.permit.permitted.amount); + } + + /// @notice Shared functionality for tokens exiting rollup. + function _exitToken(address hostRecipient, address token, uint256 amount) internal { + if (amount == 0) return; + ERC20Burnable(token).burn(amount); + emit ExitToken(hostRecipient, token, amount); + } +} diff --git a/test/Helpers.t.sol b/test/Helpers.t.sol index ef5e73d..1e784a0 100644 --- a/test/Helpers.t.sol +++ b/test/Helpers.t.sol @@ -1,13 +1,14 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.24; +// system contracts import {Zenith} from "../src/Zenith.sol"; -import {UsesPermit2} from "../src/permit2/UsesPermit2.sol"; - -import {Test, console2} from "forge-std/Test.sol"; +import {UsesPermit2} from "../src/UsesPermit2.sol"; +// deps import {ERC20} from "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; import {ERC20Burnable} from "openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Burnable.sol"; import {ISignatureTransfer} from "permit2/src/interfaces/ISignatureTransfer.sol"; +import {Test, console2} from "forge-std/Test.sol"; contract TestERC20 is ERC20Burnable { constructor(string memory name_, string memory symbol_) ERC20(name_, symbol_) {} diff --git a/test/Orders.t.sol b/test/Orders.t.sol index 00a4046..762344a 100644 --- a/test/Orders.t.sol +++ b/test/Orders.t.sol @@ -1,11 +1,14 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.24; -import {Test, console2} from "forge-std/Test.sol"; +// test contracts +import {IOrders} from "../src/orders/IOrders.sol"; +import {RollupOrders} from "../src/orders/Orders.sol"; +import {OrderOrigin} from "../src/orders/OrderOrigin.sol"; +// utils import {TestERC20} from "./Helpers.t.sol"; import {ERC20} from "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; -import {RollupOrders, OrderOrigin} from "../src/Orders.sol"; -import {IOrders} from "../src/interfaces/IOrders.sol"; +import {Test, console2} from "forge-std/Test.sol"; contract OrdersTest is Test { RollupOrders public target; diff --git a/test/Passage.t.sol b/test/Passage.t.sol index 3f61c02..25dff6d 100644 --- a/test/Passage.t.sol +++ b/test/Passage.t.sol @@ -1,11 +1,14 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.24; -import {Test, console2} from "forge-std/Test.sol"; -import {Passage, RollupPassage} from "../src/Passage.sol"; +// test contracts +import {Passage} from "../src/passage/Passage.sol"; +import {RollupPassage} from "../src/passage/RollupPassage.sol"; +// utils import {TestERC20} from "./Helpers.t.sol"; import {ERC20} from "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; import {ERC20Burnable} from "openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Burnable.sol"; +import {Test, console2} from "forge-std/Test.sol"; contract PassageTest is Test { Passage public target; diff --git a/test/Permit2Orders.t.sol b/test/Permit2Orders.t.sol index 4d9d04e..40cf6e5 100644 --- a/test/Permit2Orders.t.sol +++ b/test/Permit2Orders.t.sol @@ -2,15 +2,12 @@ pragma solidity ^0.8.24; // test contracts -import {RollupOrders} from "../src/Orders.sol"; -import {IOrders} from "../src/interfaces/IOrders.sol"; -import {UsesPermit2} from "../src/permit2/UsesPermit2.sol"; - -// Permit2 deps -import {ISignatureTransfer} from "permit2/src/interfaces/ISignatureTransfer.sol"; - -// other test utils +import {RollupOrders} from "../src/orders/Orders.sol"; +import {IOrders} from "../src/orders/IOrders.sol"; +import {UsesPermit2} from "../src/UsesPermit2.sol"; +// utils import {Permit2Helpers, IBatchPermit, TestERC20} from "./Helpers.t.sol"; +import {ISignatureTransfer} from "permit2/src/interfaces/ISignatureTransfer.sol"; import {ERC20} from "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; import {Test, console2} from "forge-std/Test.sol"; diff --git a/test/Permit2Passage.t.sol b/test/Permit2Passage.t.sol index 6fd6df0..acf3f3a 100644 --- a/test/Permit2Passage.t.sol +++ b/test/Permit2Passage.t.sol @@ -2,14 +2,12 @@ pragma solidity ^0.8.24; // test contracts -import {Passage, RollupPassage} from "../src/Passage.sol"; -import {UsesPermit2} from "../src/permit2/UsesPermit2.sol"; - -// Permit2 deps -import {ISignatureTransfer} from "permit2/src/interfaces/ISignatureTransfer.sol"; - -// other test utils +import {Passage} from "../src/passage/Passage.sol"; +import {RollupPassage} from "../src/passage/RollupPassage.sol"; +import {UsesPermit2} from "../src/UsesPermit2.sol"; +// utils import {Permit2Helpers, ISinglePermit, TestERC20} from "./Helpers.t.sol"; +import {ISignatureTransfer} from "permit2/src/interfaces/ISignatureTransfer.sol"; import {ERC20} from "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; import {ERC20Burnable} from "openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Burnable.sol"; import {Test, console2} from "forge-std/Test.sol"; diff --git a/test/Transact.t.sol b/test/Transact.t.sol index 673f72c..3537db7 100644 --- a/test/Transact.t.sol +++ b/test/Transact.t.sol @@ -1,9 +1,11 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.24; -import {Test, console2} from "forge-std/Test.sol"; -import {Passage} from "../src/Passage.sol"; +// test contracts import {Transactor} from "../src/Transact.sol"; +import {Passage} from "../src/passage/Passage.sol"; +// utils +import {Test, console2} from "forge-std/Test.sol"; contract TransactTest is Test { Passage public passage; diff --git a/test/Zenith.t.sol b/test/Zenith.t.sol index b917d28..7b0f38f 100644 --- a/test/Zenith.t.sol +++ b/test/Zenith.t.sol @@ -1,8 +1,10 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.24; -import {Test, console2} from "forge-std/Test.sol"; +// test contracts import {Zenith} from "../src/Zenith.sol"; +// utils +import {Test, console2} from "forge-std/Test.sol"; contract ZenithTest is Test { Zenith public target;