Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 37 additions & 11 deletions contracts/chain-adapters/Arbitrum_Adapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,16 @@ interface ArbitrumL1ERC20GatewayLike {
bytes calldata _data
) external payable returns (bytes memory);

function outboundTransferCustomRefund(
address _token,
address _refundTo,
address _to,
uint256 _amount,
uint256 _maxGas,
uint256 _gasPriceBid,
bytes calldata _data
) external payable returns (bytes memory);

function getGateway(address _token) external view returns (address);
}

Expand Down Expand Up @@ -127,17 +137,33 @@ contract Arbitrum_Adapter is AdapterInterface {
// maxSubmissionCost to use when creating an L2 retryable ticket: https://github.com/OffchainLabs/arbitrum/blob/e98d14873dd77513b569771f47b5e05b72402c5e/packages/arb-bridge-peripherals/contracts/tokenbridge/ethereum/gateway/L1GatewayRouter.sol#L232
bytes memory data = abi.encode(l2MaxSubmissionCost, "");

// Note: outboundTransfer() will ultimately create a retryable ticket and set this contract's address as the
// refund address. This means that the excess ETH to pay for the L2 transaction will be sent to the aliased
// contract address on L2 and lost.
l1ERC20GatewayRouter.outboundTransfer{ value: requiredL1CallValue }(
l1Token,
to,
amount,
l2GasLimit,
l2GasPrice,
data
);
// Note: Legacy routers don't have the outboundTransferCustomRefund method, so default to using
// outboundTransfer(). Legacy routers are used for the following tokens that are currently enabled:
// - DAI
if (l1Token == 0x6B175474E89094C44Da98b954EedeAC495271d0F) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is DAI the only token this applies to? Or just the only token we currently support that this applies to?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The latter, DAI is the only token we currently support that this applies to, I'll clarify the comment

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good!

// Note: outboundTransfer() will ultimately create a retryable ticket and set this contract's address as the
// refund address. This means that the excess ETH to pay for the L2 transaction will be sent to the aliased
// contract address on L2, which we'd have to retrieve via a custom adapter
// (i.e. the Arbitrum_RescueAdapter).
l1ERC20GatewayRouter.outboundTransfer{ value: requiredL1CallValue }(
l1Token,
to,
amount,
l2GasLimit,
l2GasPrice,
data
);
} else {
l1ERC20GatewayRouter.outboundTransferCustomRefund{ value: requiredL1CallValue }(
l1Token,
l2RefundL2Address,
to,
amount,
l2GasLimit,
l2GasPrice,
data
);
}

emit TokensRelayed(l1Token, l2Token, amount, to);
}
Expand Down
12 changes: 12 additions & 0 deletions contracts/test/ArbitrumMocks.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@
pragma solidity ^0.8.0;

contract ArbitrumMockErc20GatewayRouter {
function outboundTransferCustomRefund(
address,
address,
address,
uint256,
uint256,
uint256,
bytes calldata _data
) external payable returns (bytes memory) {
return _data;
}

function outboundTransfer(
address,
address,
Expand Down
5 changes: 3 additions & 2 deletions test/chain-adapters/Arbitrum_Adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,15 @@ describe("Arbitrum Chain Adapter", function () {
);

// The correct functions should have been called on the arbitrum contracts.
expect(l1ERC20GatewayRouter.outboundTransfer).to.have.been.calledOnce; // One token transfer over the canonical bridge.
expect(l1ERC20GatewayRouter.outboundTransferCustomRefund).to.have.been.calledOnce; // One token transfer over the canonical bridge.

// Adapter should have approved gateway to spend its ERC20.
expect(await dai.allowance(hubPool.address, gatewayAddress)).to.equal(tokensSendToL2);

const message = defaultAbiCoder.encode(["uint256", "bytes"], [consts.sampleL2MaxSubmissionCost, "0x"]);
expect(l1ERC20GatewayRouter.outboundTransfer).to.have.been.calledWith(
expect(l1ERC20GatewayRouter.outboundTransferCustomRefund).to.have.been.calledWith(
dai.address,
owner.address,
mockSpoke.address,
tokensSendToL2,
consts.sampleL2Gas,
Expand Down