-
Notifications
You must be signed in to change notification settings - Fork 54
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
Permissionless token pools #652
Conversation
# Conflicts: # contracts/src/v0.8/ccip/offRamp/EVM2EVMOffRamp.sol # contracts/src/v0.8/ccip/onRamp/EVM2EVMOnRamp.sol # contracts/src/v0.8/ccip/pools/TokenPool.sol # contracts/src/v0.8/ccip/test/StructFactory.sol # contracts/src/v0.8/ccip/test/applications/ImmutableExample.t.sol # contracts/src/v0.8/ccip/test/e2e/End2End.t.sol # contracts/src/v0.8/ccip/test/offRamp/EVM2EVMOffRamp.t.sol # contracts/src/v0.8/ccip/test/offRamp/EVM2EVMOffRampSetup.t.sol # contracts/src/v0.8/ccip/test/onRamp/EVM2EVMOnRamp.t.sol # contracts/src/v0.8/ccip/test/pools/BurnMintTokenPool.t.sol # contracts/src/v0.8/ccip/test/pools/LockReleaseTokenPool.t.sol # contracts/src/v0.8/ccip/test/pools/USDCTokenPool.t.sol
LCOV of commit
|
LCOV of commit
|
Go solidity wrappers are out-of-date, regenerate them via the |
61fe9f4
to
eb57271
Compare
LCOV of commit
|
|
||
newMessage.sourceTokenData[i] = tokenData; | ||
// Since this is an EVM2EVM message, the pool address should be exactly 32 bytes | ||
if (poolReturnData.destPoolAddress.length != 32) revert InvalidAddress(poolReturnData.destPoolAddress); |
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.
question: do we need this validation here - since we already verify it in the offramp?
I'm thinking removing this validation would contribute 1 step further to breaking away from EVM2EVM specific logic
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.
Any validation prior to sending is a win. If this is wrong, and the tx goes through, funds will be stuck in limbo due to the failure on the offRamp. Technically the sending side rate limits also offer no security value, but they are there to make sure people don't get surprised on the destination.
re breaking away: as long as this is still an EVM2EVM ramp, this check should be here. When we go for EVM2Any this can be revisited.
|
||
// We determined that the pool address is a valid EVM address, but that does not mean the code at this | ||
// address is actually a (compatible) pool contract. If there is no contract it could not possibly be a pool. | ||
if (pool.code.length == 0) { |
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.
question: isn't this check redundant with the one performed in _callWithExactGasSafeReturnData
?
ccip/contracts/src/v0.8/shared/call/CallWithExactGas.sol
Lines 87 to 90 in eb57271
if iszero(extcodesize(target)) { | |
mstore(0x0, NO_CONTRACT_SIG) | |
revert(0x0, 0x4) | |
} |
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.
Technically no, since it would throw a NO_CONTRACT_SIG
error there. I did it this way to throw a consistent error in the address parsing (see _validateEVMAddress errors) which we then catch in _trialExecute
. We could make trialExecute also catch NO_CONTRACT_SIG though. I had considered both solutions, but had not benchmarked gas cost. Let me see if it's worth it
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.
It saves 282 gas in the happy path, while the revert case costs ~3k more
core/services/ocr2/plugins/ccip/internal/ccipdata/v1_5_0/onramp.go
Outdated
Show resolved
Hide resolved
destTokenAmounts[i].token = address(pool.getToken()); | ||
destTokenAmounts[i].amount = sourceTokenAmount; | ||
// If the call was successful, the returnData should be the local token address. | ||
destTokenAmounts[i].token = _validateEVMAddress(returnData); | ||
} | ||
_rateLimitValue(destTokenAmounts, IPriceRegistry(s_dynamicConfig.priceRegistry)); |
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.
question: since pools are now considered hostile, is there any risk of performing this state update after the releaseOrMint
calls on the pools?
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.
Given the very protected external call, the validation of its contents and the reentrancy protection I think we're good here. Please let me know if you have any specific action that doesn't look safe!
@@ -6,41 +6,55 @@ import {IERC20} from "../../../vendor/openzeppelin-solidity/v4.8.3/contracts/tok | |||
// Shared public interface for multiple pool types. | |||
// Each pool type handles a different child token model (lock/unlock, mint/burn.) | |||
interface IPool { | |||
struct SourceTokenData { | |||
bytes sourcePoolAddress; | |||
bytes destPoolAddress; |
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.
suggestion: what do you think of the alternative option to pass the destToken
instead of the pool? This way we can validate more strictly by querying getPool(destToken)
via the registry on the dest chain - and we can always be sure that the pool represents the token correctly
In this case the mapping on the src chain would have to change to token => chainSelector => token address (bytes)
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.
Adds an additional 5.8k gas for a single msg token and increases the size of the contract. Probably not worth it
LCOV of commit
|
c899782
to
f51b7ae
Compare
LCOV of commit
|
f51b7ae
to
52d559a
Compare
LCOV of commit
|
|
||
/// @notice Get all configured destination tokens | ||
/// @return destTokens Array of configured destination tokens | ||
function getDestinationTokens() external view returns (IERC20[] memory destTokens) { |
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.
@RensR Justin's PR gets all tokens supported by an offramp and filters out ones missing price getter config in job spec, so it is still requiring a trusted source for a list of supported tokens
sourcePoolAddress: abi.encode(sourcePool), | ||
destPoolAddress: poolReturnData.destPoolAddress, | ||
extraData: poolReturnData.destPoolData | ||
}) |
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.
while this is readable, I think this encoding adds ~224 bytes of extra calldata per token, that can add up. If no way to slim it down, we should account for it in tokenTransferBytesOverhead
in billing calc.
Won't block on this, oK to mark it as todo and address in followup PR.
} | ||
/// @inheritdoc IEVM2AnyOnRampClient | ||
function getSupportedTokens(uint64 /*destChainSelector*/ ) external view returns (address[] memory) { | ||
return ITokenAdminRegistry(s_dynamicConfig.tokenAdminRegistry).getAllConfiguredTokens(); |
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.
TODO here?
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.
There's a spike ticket for this in the current sprint 👍🏼
This PR is mainly focussed on the CCIP side of self serve token pools. Some additional contracts had to be written/changed, but in review, please focus on the CCIP & token pool side of self serve, not on the TokenAdminRegistry. Considerations - Token pools are now considered hostile - This goes for both in- and output - Source side - Call to `sourcePool.lockOrBurn` from the onRamp - Does not strictly have to be through a callWithExactGas call, as CCIP is not paying for gas used here - Same for return data bomb - It returns the destPool and optional extraData - DestPool has to be checked for length to mitigate the risk of return data bombs through this field - Since the onRamp is EVM2EVM, we can assume the address is an abi.encoded(address) and therefore 32 bytes - ExtraData is checked like it was before - Dest side - We used to make a call to `getToken` - Since this would be a hostile call, and require the full callWithExactGas protection, we opted to fold this getter into the `releaseOrMint` call. This saves a significant amount of gas, and makes sure we only have to do a single call to the hostile contract. - We call `releaseOrMint` on the `pool` provided on the source side - The pool data is considered hostile, as it came from an untrusted source - We need to ensure the address is valid and it contains a contract - The releaseOrMint call was already done through callWithExactGas This brings the number of onchain calls to token pools to 2 per transferred token: one on the source chain and one on the destination. This is the absolute minimum possible. Reviews for this PR should focus on - Hostile token pool interactions - DoS - Unusual reverts - Gas bombs - Return data bombs - The changes to CCIP and their impact on the security and availability of the protocol Please do not focus on - The TokenAdminRegistry, except for things that would impact the changed CCIP design # TODO Onchain: - Add the tag to the return data and check for it (next PR) - Use default price for tokens that don't have a config (next PR) Offchain - Add 1.4 lane logic to integration test? --------- Co-authored-by: Anindita Ghosh <[email protected]>
This PR is mainly focussed on the CCIP side of self serve token pools. Some additional contracts had to be written/changed, but in review, please focus on the CCIP & token pool side of self serve, not on the TokenAdminRegistry.
Considerations
sourcePool.lockOrBurn
from the onRampgetToken
releaseOrMint
call. This saves a significant amount of gas, and makes sure we only have to do a single call to the hostile contract.releaseOrMint
on thepool
provided on the source sideThis brings the number of onchain calls to token pools to 2 per transferred token: one on the source chain and one on the destination. This is the absolute minimum possible.
Reviews for this PR should focus on
Please do not focus on
TODO
Onchain:
Offchain