Skip to content

Commit

Permalink
feat: first version
Browse files Browse the repository at this point in the history
  • Loading branch information
georgeroman committed Nov 14, 2024
1 parent 714fd28 commit e2dc41f
Show file tree
Hide file tree
Showing 8 changed files with 214 additions and 57 deletions.
19 changes: 0 additions & 19 deletions script/Counter.s.sol

This file was deleted.

14 changes: 0 additions & 14 deletions src/Counter.sol

This file was deleted.

95 changes: 95 additions & 0 deletions src/OpReceiverProxy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.28;

import {IHyperlaneMailbox} from "./interfaces/IHyperlaneMailbox.sol";

import {Utils} from "./Utils.sol";

contract OpReceiverProxy is Utils {
// Errors

error Unauthorized();

// Constants

address public constant OP_GATEWAY =
0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1;

uint32 public immutable SENDER_CHAIN_ID;
address public immutable HYPERLANE_MAILBOX;

// Public fields

address public opSenderProxy;
mapping(uint256 => bool) public idWasUsed;
mapping(address => uint256) public balances;

// Constructor

constructor(
uint32 _senderChainId,
address _hyperlaneMailbox,
address _opSenderProxy
) {
SENDER_CHAIN_ID = _senderChainId;
HYPERLANE_MAILBOX = _hyperlaneMailbox;

opSenderProxy = _opSenderProxy;
}

// Public methods

function handle(
uint32 senderChainId,
bytes32 senderAddress,
bytes calldata data
) external {
// Only `HYPERLANE_MAILBOX` is authorized to call this method
if (msg.sender != HYPERLANE_MAILBOX) {
revert Unauthorized();
}

// The sender chain must be `SENDER_CHAIN_ID`
if (senderChainId != SENDER_CHAIN_ID) {
revert Unauthorized();
}

// The sender address must be `opSenderProxy`
if (senderAddress != bytes32(uint256(uint160(opSenderProxy)))) {
revert Unauthorized();
}

(uint256 id, address to, uint256 amount) = abi.decode(
data,
(uint256, address, uint256)
);

// If the parsed id was not already used then we simply forward the received funds to the parsed recipient
if (!idWasUsed[id]) {
// TODO: Also charge fees
_send(to, amount);

// Mark the id as being used
idWasUsed[id] = true;
}
}

receive() external payable {}

fallback() external payable {
// Only `OP_GATEWAY` is authorized to call this
if (msg.sender != OP_GATEWAY) {
revert Unauthorized();
}

(uint256 id, address to) = abi.decode(msg.data, (uint256, address));

// If the parsed id was not already used then we simply forward the received funds to the parsed recipient
if (!idWasUsed[id]) {
_send(to, msg.value);

// Mark the id as being used
idWasUsed[id] = true;
}
}
}
66 changes: 66 additions & 0 deletions src/OpSenderProxy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.28;

import {IHyperlaneMailbox} from "./interfaces/IHyperlaneMailbox.sol";
import {IOpBridge} from "./interfaces/IOpBridge.sol";

import {Utils} from "./Utils.sol";

contract OpSenderProxy is Utils {
// Constants

IOpBridge public constant OP_BRIDGE =
IOpBridge(0x4200000000000000000000000000000000000010);

uint32 public immutable RECEIVER_CHAIN_ID;
address public immutable HYPERLANE_MAILBOX;

// Public fields

address public opReceiverProxy;
uint256 public nextId;

// Constructor

constructor(
uint32 _receiverChainId,
address _hyperlaneMailbox,
address _opReceiverProxy
) {
RECEIVER_CHAIN_ID = _receiverChainId;
HYPERLANE_MAILBOX = _hyperlaneMailbox;

opReceiverProxy = _opReceiverProxy;
}

// Public methods

function withdraw(uint256 amount, address to) external payable {
// Associate the withdrawal to a unique id
uint256 id = nextId++;

// Endoce the data to be passed to the receiver chain
bytes memory data = abi.encode(id, to, amount);

// Trigger a canonical withdrawal to the `opReceiverProxy` contract
OP_BRIDGE.withdrawTo(
0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000,
opReceiverProxy,
amount,
0,
data
);

// Trigger a Hyperlane cross-chain message to the `opReceiverProxy` contract
IHyperlaneMailbox(HYPERLANE_MAILBOX).dispatch{
value: IHyperlaneMailbox(HYPERLANE_MAILBOX).quoteDispatch(
RECEIVER_CHAIN_ID,
bytes32(uint256(uint160(opReceiverProxy))),
data
)
}(RECEIVER_CHAIN_ID, bytes32(uint256(uint160(opReceiverProxy))), data);

// Refund any ETH leftover back to the caller
_send(msg.sender, address(this).balance);
}
}
25 changes: 25 additions & 0 deletions src/Utils.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.28;

contract Utils {
// Errors

error NativeTransferFailed();

// Internal methods

function _send(address to, uint256 value) internal {
bool success;
assembly {
// Save gas by avoiding copying the return data to memory.
// Provide at most 100k gas to the internal call, which is
// more than enough to cover common use-cases of logic for
// receiving native tokens (eg. SCW payable fallbacks).
success := call(100000, to, value, 0, 0, 0, 0)
}

if (!success) {
revert NativeTransferFailed();
}
}
}
16 changes: 16 additions & 0 deletions src/interfaces/IHyperlaneMailbox.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.28;

interface IHyperlaneMailbox {
function dispatch(
uint32 receiverChainId,
bytes32 receiverAddress,
bytes calldata data
) external payable returns (bytes32 id);

function quoteDispatch(
uint32 receiverChainId,
bytes32 receiverAddress,
bytes calldata data
) external returns (uint256 fee);
}
12 changes: 12 additions & 0 deletions src/interfaces/IOpBridge.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.28;

interface IOpBridge {
function withdrawTo(
address l2Token,
address to,
uint256 amount,
uint32 minGasLimit,
bytes calldata extraData
) external payable;
}
24 changes: 0 additions & 24 deletions test/Counter.t.sol

This file was deleted.

0 comments on commit e2dc41f

Please sign in to comment.