Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add Arbitrum forwarder contracts #610

Closed
wants to merge 14 commits into from

Conversation

bmzig
Copy link
Contributor

@bmzig bmzig commented Sep 17, 2024

This contains L2 forwarder contracts based on the interface #609, under the assumption that the L2 on which it is deployed is Arbitrum. It mostly follows the same format as the Arbitrum_CustomGasToken_Adapter and Arbitrum_Adapter contracts.

address _l3SpokePool,
address _crossDomainAdmin
)
CircleCCTPAdapter(_l2Usdc, _cctpTokenMessenger, CircleDomainIds.UNINITIALIZED)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is speculation on how CCTP would work on L3s. We may just want to remove this on the forwarders.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't see a reason it would work differently on L3s, so it seems reasonable just to leave this here as uninitialized and use it if/when we need it.

Maybe we've talked about this before, but is there a reason we shouldn't allow the domain id to be passed in rather than hardcoded, so it can be enabled without changing the code?

Signed-off-by: bennett <[email protected]>
@bmzig
Copy link
Contributor Author

bmzig commented Sep 18, 2024

There are some refactors I need to do to hopefully cut down a lot on the code duplication, but I think this is the general idea.

Copy link
Contributor

@mrice32 mrice32 left a comment

Choose a reason for hiding this comment

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

Looks great! A few comments

address _l3SpokePool,
address _crossDomainAdmin
)
CircleCCTPAdapter(_l2Usdc, _cctpTokenMessenger, CircleDomainIds.UNINITIALIZED)
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't see a reason it would work differently on L3s, so it seems reasonable just to leave this here as uninitialized and use it if/when we need it.

Maybe we've talked about this before, but is there a reason we shouldn't allow the domain id to be passed in rather than hardcoded, so it can be enabled without changing the code?

*/

// solhint-disable-next-line contract-name-camelcase
contract Arbitrum_CustomGasToken_L2_Forwarder is ArbitrumForwarderInterface, CircleCCTPAdapter {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is the code in this contract identical to the custom gas arbitrum adapter?

Thoughts on sharing the code via a library or inheritance?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There was a big refactor here ea2b6ca which should share code via inheritance.

*/

// solhint-disable-next-line contract-name-camelcase
contract Arbitrum_L2_Forwarder is ArbitrumForwarderInterface, CircleCCTPAdapter {
Copy link
Contributor

Choose a reason for hiding this comment

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

Same question here, but for the regular arbitrum adapter

*/

// solhint-disable-next-line contract-name-camelcase
contract Ovm_CustomGasToken_L2_Forwarder is Arbitrum_CustomGasToken_L2_Forwarder {
Copy link
Contributor

Choose a reason for hiding this comment

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

+1, great job on keeping these super simple

@bmzig bmzig marked this pull request as ready for review September 20, 2024 16:53
@@ -7,7 +7,8 @@ import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "../external/interfaces/CCTPInterfaces.sol";
import "../libraries/CircleCCTPAdapter.sol";
import { ArbitrumInboxLike as ArbitrumL1InboxLike, ArbitrumL1ERC20GatewayLike } from "../interfaces/ArbitrumBridgeInterfaces.sol";
Copy link
Contributor

Choose a reason for hiding this comment

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

This change was only just introduced back in this (not yer merged) PR: https://github.com/across-protocol/contracts/pull/609/files#diff-9986d5b9b57b18f73b5c629869f6d8a6cfdbfa978964dd76de7f4f37d4b5d561

Is it worth backporting the change to that PR, or is the intermediate step necessary?

IERC20(l1Token).safeIncreaseAllowance(erc20Gateway, amount);

// `outboundTransfer` expects that the caller includes a bytes message as the last param that includes the
// 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
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems like a dead link now. I think the correct reference for that contract is: https://github.com/OffchainLabs/arbitrum-classic/blob/551a39b381dcea81e03e7599fcb01fddff4fe96c/packages/arb-bridge-peripherals/contracts/tokenbridge/ethereum/gateway/L1GatewayRouter.sol#L240

We are however referencing their legacy contracts though. They seem to be maintaining compatibility moving forward but I wonder if we should try to reference the current implementations instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nice catch 05dfc6e

Comment on lines 99 to 104
function _applyL1ToL2Alias(address l1Address) internal pure returns (address l2Address) {
// Allows overflows as explained above.
unchecked {
l2Address = address(uint160(l1Address) + uint160(0x1111000000000000000000000000000000001111));
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

We have this in the SpokePool as well. Should we factor it out to an "Arbitrum Utils" library for inlining at build time?

Copy link
Member

Choose a reason for hiding this comment

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

Same comment as paul. If we add it as an internal library function

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Refactored: 05dfc6e

*/

// solhint-disable-next-line contract-name-camelcase
contract Ovm_L2_Forwarder is Arbitrum_L2_Forwarder {
Copy link
Contributor

@pxrl pxrl Sep 24, 2024

Choose a reason for hiding this comment

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

I keep getting lost in these naming conventions (i.e. L2 forwarder, l3 adapter, ...) and it gets a bit tricker when chain names are overlaid like this. Is there a concrete naming scheme to be followed? wdyt about building a lexicon that explains how to interpret the names?

Copy link
Member

Choose a reason for hiding this comment

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

I think we should stick with the Adapter{base} naming scheme:

  • chainId makes it clear which chain the contract should be deployed on, or L1 if omitted
  • Adapter already is intuitively understood by us as something that sends message to a higher level
  • Base contracts are meant to be inherited

@bmzig bmzig marked this pull request as draft September 24, 2024 13:54
Copy link
Member

@nicholaspai nicholaspai left a comment

Choose a reason for hiding this comment

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

I really like the inheritance tree of this PR but I think the names are clunky. Can we think of a better naming scheme? There are too many underscores for one.

_l1Usdc,
_cctpTokenMessenger,
_circleDomainId,
_l2MaxSubmissionCost,
Copy link
Member

Choose a reason for hiding this comment

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

Note for future work: anytime you make changes to existing contract code where its understood that you're refactoring, you should leave a comment saying something like "I changed this code here"

import { Arbitrum_CustomGasToken_AdapterBase, FunderInterface } from "./Arbitrum_CustomGasToken_AdapterBase.sol";

/**
* @notice Contract containing logic to send messages from Arbitrum to an AVM L3.
Copy link
Member

Choose a reason for hiding this comment

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

In all contracts, its helpful to note which layer this contract is supposed to be deployed on. In this case, this contract is supposed to be deployed on L2, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, it is L2: 05dfc6e

*/

// solhint-disable-next-line contract-name-camelcase
contract Arbitrum_CustomGasToken_L2_Forwarder is Arbitrum_CustomGasToken_AdapterBase, ForwarderBase {
Copy link
Member

Choose a reason for hiding this comment

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

alternative naming option:

  • Arbitrum_CustomGasToken_L2Adapter

Makes it more clear this behaves like an Adapter, but its on L1. Also I think the forwarder logic maybe is implied if this extends ForwarderBase

Copy link
Contributor Author

Choose a reason for hiding this comment

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


/**
* @notice Constructs new L2 Forwarder.
* @dev We normally cannot define a constructor for proxies, but this is an exception since all
Copy link
Member

Choose a reason for hiding this comment

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

I would note here in a @dev note that the cross domain admin will be sit upon initilization of the ForwarderBase, which should be the HubPool

Copy link
Contributor Author

Choose a reason for hiding this comment

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

/**
* @notice Contract containing logic to send messages from Arbitrum to an AVM L3.
* @dev This contract is very similar to Arbitrum_CustomGasToken_Adapter. It is meant to bridge
* tokens and send messages over a bridge which uses a custom gas token, except this contract assumes
Copy link
Member

Choose a reason for hiding this comment

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

I would also note that this contract is designed to be controlled by a cross domain admin which should be the HubPool

Copy link
Contributor Author

Choose a reason for hiding this comment

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

address,
uint256 amount
) external payable override {
_relayTokens(l2Token, address(0), amount, l3SpokePool);
Copy link
Member

Choose a reason for hiding this comment

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

you should comment why address(0) can be hardcoded here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

*/

// solhint-disable-next-line contract-name-camelcase
contract Arbitrum_L2_Forwarder is ForwarderBase, Arbitrum_AdapterBase {
Copy link
Member

Choose a reason for hiding this comment

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

alternative name could be Arbitrum_L2Adapter for similar reasons to here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

*/

// solhint-disable-next-line contract-name-camelcase
contract Ovm_CustomGasToken_L2_Forwarder is Arbitrum_CustomGasToken_L2_Forwarder {
Copy link
Member

Choose a reason for hiding this comment

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

alternative name would be Ovm_CustomGasToken_L2_Adapter

Copy link
Contributor Author

Choose a reason for hiding this comment

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

* @notice Contract containing logic to send messages from an OVM L2 to an AVM L3.
* @dev This contract is very similar to Arbitrum_CustomGasToken_Adapter. It is meant to bridge
* tokens and send messages over a bridge which uses a custom gas token, except this contract makes
* the assumption that it is deployed on an OpStack L2.
Copy link
Member

Choose a reason for hiding this comment

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

Can you explain a bit more how this differs from Arbitrum_L2 equivalent? Specifically comment on how the onlyFromCrossDomainAdmin-esque function has changed

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@bmzig
Copy link
Contributor Author

bmzig commented Sep 27, 2024

Closing in favor of #625 since we no longer wish to inherit specific adapter code, but instead delegatecall external contracts which contain bridging logic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants