-
Notifications
You must be signed in to change notification settings - Fork 75
feat(CCTP): ✨ modify adapters to include CCTP bridging of UDSC #391
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(CCTP): ✨ modify adapters to include CCTP bridging of UDSC #391
Conversation
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.
There was a problem hiding this 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.
There was a problem hiding this 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
contracts/Ovm_SpokePool.sol
Outdated
| * @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; |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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>
28ee6a6 to
51f7a04
Compare
| */ | ||
| function _transferUsdc(address to, uint256 amount) internal { | ||
| // Require that the CCTP bridge is enabled | ||
| require(_isCCTPEnabled(), "CCTP bridge is disabled"); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
contracts/Arbitrum_SpokePool.sol
Outdated
| "" // _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)) { |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
There was a problem hiding this 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>
42bdc15 to
7b40144
Compare
There was a problem hiding this 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!
contracts/Ovm_SpokePool.sol
Outdated
| // 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; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated here: dd5e3f8
Signed-off-by: Matt Rice <matthewcrice32@gmail.com>
1be19b4 to
fad4f92
Compare
Signed-off-by: Matt Rice <matthewcrice32@gmail.com>
fad4f92 to
5036e8d
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM!
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):