Skip to content

Conversation

@james-a-morris
Copy link
Contributor

This PR creates a new CCTP Adapter that can be added to chain adapters to support bridging USDC on L1 -> L2 via the CCTP bridge. This PR also modifies the following chain adapters (both solidity code & deploy script code):

  1. Optimism (closes ACX-1795)
  2. Polygon (closes ACX-1792)
  3. Arbitrum (closes ACX-1794)
  4. Base (closes ACX-1793)

This commit creates a new CCTP Adapter that can be added to chain adapters to support bridging USDC on L1 -> L2 via the CCTP bridge
This hubpool mock is specfically designed to test chain-adapters through a delegate call. It allows for relaying messages and tokens.
@james-a-morris james-a-morris changed the base branch from master to cctp-stage December 29, 2023 15:17
@james-a-morris james-a-morris marked this pull request as draft December 29, 2023 15:21
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 -- I only have one question about variable storage, since it seems like we shouldn't need to store these in the proxy, itself, and we can keep them in the bytecode.

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.

Added some comments about storage slots and also doubled up matt's comment about constant state variables

* @notice The official USDC contract address on this chain.
* @dev Posted officially here: https://developers.circle.com/stablecoins/docs/usdc-on-main-networks
*/
IERC20 public l2Usdc;
Copy link
Member

Choose a reason for hiding this comment

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

if we're adding a new storage variable to a base contract like this one we need to decrement this contract's __gap variable. This contract is a base contract for Optimism/Base/Boba_SpokePool's. Decrement the __gap for each 32 byte slot used up: https://docs.openzeppelin.com/upgrades-plugins/1.x/writing-upgradeable#storage-gaps

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've decremented the __gap by 2 slots to include space for the CircleCCTPAdapter's recipientCircleDomainId, usdcToken, and cctpTokenMessenger.

This is because: recipientCircleDomainId is 4 bytes, and usdcToken & cctpTokenMessenger should be 20 bytes each. So in total, 44 bytes fit in 2 slots.

Signed-off-by: james-a-morris <jaamorris@cs.stonybrook.edu>
@james-a-morris james-a-morris force-pushed the james/acx-1791-modify-chain-adapters-to-include-the-cctp-bridge branch from 28ee6a6 to 51f7a04 Compare January 5, 2024 00:15
@james-a-morris james-a-morris marked this pull request as ready for review January 5, 2024 00:19
*/
function _transferUsdc(address to, uint256 amount) internal {
// Require that the CCTP bridge is enabled
require(_isCCTPEnabled(), "CCTP bridge is disabled");
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 of the SpokePools, we first check _isCCTPEnabled() before calling into here so the check here is redundant and adds gas. Can we remove this check and add a note to devs that this function will revert if cctpTokenMessenger is set incorrectly?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Although I'd prefer to have a readable failure - I agree that calling {null}.depositForBurn() is going to revert for us. I've removed the require() statement and added a dev warning: 7fb1f5b

import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "../external/interfaces/CCTPInterfaces.sol";

abstract contract CircleCCTPAdapter {
Copy link
Member

Choose a reason for hiding this comment

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

Nit: prefer naming this to CircleCCTP_SpokePool since its extended by SpokePools and not used as an L1 "Adapter" just because this would be confuinsg with the contracts in chain-adapters

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hmm agreed but could we use CircleCCTPConnector or something similar? The CircleCCTPAdapter is currently used by the chain adapters & the spoke pools since the logic is the same

/// @dev This function delegates the call to the adapter contract to execute the balance check.
/// @param l2Token The address of the L2 token.
/// @param user The user address whose balance is being queried.
function balanceOf(address l2Token, address user) external {
Copy link
Member

Choose a reason for hiding this comment

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

why not just implement a more generalized function that takes in bytes memory l2CallData so that we can test with any arbitrary function? I think balanceOf is an interesting check but I wonder if there are additional test cases we'd want to send with state-modifyin xchain functions

Copy link
Contributor Author

@james-a-morris james-a-morris Jan 5, 2024

Choose a reason for hiding this comment

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

Absolutely. I wrote this mock to include something related to arbitrary messaging but mostly for token exchange. I later tested the messaging with a hub pool deployment.

However, since we're including this in the repo, I'll update it to include arbitrary messaging.

Done: 42bdc15

L1_ADDRESS_MAP[chainId].weth,
L1_ADDRESS_MAP[chainId].optimismCrossDomainMessenger,
L1_ADDRESS_MAP[chainId].optimismStandardBridge,
L1_ADDRESS_MAP[chainId].l1UsdcAddress,
Copy link
Member

Choose a reason for hiding this comment

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

nit: suggest commenting out the cctpTokenMessenger line and adding in a zeroAddress for production so we don't accidentally deploy this with full cctp support yet

This comment applies to all deploy scripts as well

Copy link
Contributor Author

@james-a-morris james-a-morris Jan 5, 2024

Choose a reason for hiding this comment

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

Good call - per the sync today I've set all spoke/adapters cctpTokenMessenger contract to ZERO_ADDRESS.

Done here: 9a25b3b

"" // _data. We don't need to send any data for the bridging action.
);
// If the l2TokenAddress is UDSC, we need to use the CCTP bridge.
if (_isCCTPEnabledForToken(l2TokenAddress)) {
Copy link
Member

Choose a reason for hiding this comment

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

WDYT about calling _isCCTPEnabled() && l2TokenAddress == usdcToken? I think its more intuitive what's going on here rather than hiding the second part of that check behind an internal 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.

I think it's a good call. I'm not sure how much additional readability it would give for someone familiar with the code, but it does bring the boolean check directly into where the bridging is called.

As a tradeoff - this change requires propagating the _isCCTPEnabled() && l2TokenAddress == usdcToken 8 times.

Commit: 318afa2

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.

This makes a lot of sense, I left some suggestions

Signed-off-by: james-a-morris <jaamorris@cs.stonybrook.edu>
Signed-off-by: james-a-morris <jaamorris@cs.stonybrook.edu>
Signed-off-by: james-a-morris <jaamorris@cs.stonybrook.edu>
Signed-off-by: james-a-morris <jaamorris@cs.stonybrook.edu>
Signed-off-by: james-a-morris <jaamorris@cs.stonybrook.edu>
@james-a-morris james-a-morris force-pushed the james/acx-1791-modify-chain-adapters-to-include-the-cctp-bridge branch from 42bdc15 to 7b40144 Compare January 5, 2024 20:50
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.

I like this design a lot -- just a few small comments!

// affecting the storage layout of child contracts. Decrement the size of __gap whenever state variables
// are added. This is at bottom of contract to make sure its always at the end of storage.
uint256[1000] private __gap;
uint256[998] private __gap;
Copy link
Contributor

Choose a reason for hiding this comment

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

What is this change? Did we add variables to the OVM SpokePool that would move storage slots?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My understanding is that since this class' inherited class was updated (the CircleCCTPAdapter) we'd need to modify this. In both cases, we didn't have any upgrade errors as outlined here: https://docs.openzeppelin.com/contracts/3.x/upgradeable#:~:text=You%20may%20notice%20that%20every,storage%20compatibility%20with%20existing%20deployments.

Copy link
Member

Choose a reason for hiding this comment

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

If the variables are constant or immutable then they don't take slots and this shouldn't be decremented

* @dev This identifier is assigned by Circle and is not related to a chain ID.
* @dev Official domain list can be found here: https://developers.circle.com/stablecoins/docs/supported-domains
*/
uint32 public constant circleDomainId = 3;
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: since this is only used to pass up through the constructor stack, I would suggest making it a private variable and name it ARBITRUM_CIRCLE_DOMAIN_ID (I think it's good to include the chain name so it's very clear that the constant is chain specific). It will be exposed via the recipientCircleDomainId in the parent class, so we don't really gain anything from exposing with a public function here.

Same for the other adapters.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated here: dd5e3f8

@james-a-morris james-a-morris requested a review from mrice32 January 6, 2024 22:18
@james-a-morris james-a-morris changed the base branch from cctp-stage to master January 6, 2024 23:29
@mrice32 mrice32 force-pushed the james/acx-1791-modify-chain-adapters-to-include-the-cctp-bridge branch from 1be19b4 to fad4f92 Compare January 8, 2024 02:40
Signed-off-by: Matt Rice <matthewcrice32@gmail.com>
@mrice32 mrice32 force-pushed the james/acx-1791-modify-chain-adapters-to-include-the-cctp-bridge branch from fad4f92 to 5036e8d Compare January 8, 2024 02:42
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.

LGTM!

@mrice32 mrice32 merged commit cd04662 into master Jan 8, 2024
@mrice32 mrice32 deleted the james/acx-1791-modify-chain-adapters-to-include-the-cctp-bridge branch January 8, 2024 03:23
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.

5 participants