-
Notifications
You must be signed in to change notification settings - Fork 104
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
Contracts V2 #1300
base: v2
Are you sure you want to change the base?
Contracts V2 #1300
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## v2 #1300 +/- ##
=====================================
Coverage ? 60.22%
=====================================
Files ? 23
Lines ? 714
Branches ? 120
=====================================
Hits ? 430
Misses ? 275
Partials ? 9
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
contracts/src/v2/Calls.sol
Outdated
_ensureOutboundMessagingEnabled(); | ||
|
||
// Wrap provided ether and transfer to AssetHub agent | ||
address assetHubAgent = Functions.ensureAgent(Constants.ASSET_HUB_AGENT_ID); |
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.
Since we know agent of asset-hub do exists in production. Is this check nessessary?
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.
Yeah the check is not necessary, I'll add a TODO.
contracts/src/v2/Calls.sol
Outdated
bytes memory payload = | ||
SubstrateTypes.encodePayloadV2(ticket.origin, ticket.assets, ticket.xcm); |
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.
Cool, A lot more simple now!
With ticket.xcm
does that mean for V2 there is no need to construct xcm on chain any more?
UI will build the xcm and it's the responsibility of relayer to validate the xcm and check the relay is profitable or not.
On BH we just decode the xcm with
VersionedXcm::<()>::decode_with_depth_limit(
MAX_XCM_DECODE_DEPTH,
&mut data,
)
and forward it to AH transparently.
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.
Yeah, exactly.
Though for registration of native tokens, we need to control the XCM and so we need to build it on chain. See more details in 149c3e4.
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.
Though for registration of native tokens, control the XCM and so we need to build it on chain
Still not sure fully understand why ticket.assets
is required here, why not just build the xcm off chain?
@vgeddes I've removed the MessageConverter
completely in yrong/polkadot-sdk#3 which I'd assume is not in use any more, correct me if I'm wrong.
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.
Still not sure fully understand why ticket.assets is required here, why not just build the xcm off chain?
When the message gets to BH, we convert the payload.assets
to instructions like ReserveAssetDeposited,etc, and then append the instructions in payload.xcm
.
Or in other words, BH builds final XCM using:
finalXCM = concat(
assets_to_xcm(payload.asssets),
UniversalOrigin(Ethereum)
DescendOrigin(payload.origin)
payload.xcm
)
We can't trust the user to build the entire xcm offchain as they could mint any assets they want. Rather, they can only supply a fragment of the xcm.
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.
We can't trust the user to build the entire xcm offchain as they could mint any assets they want.
Yeah, that make sense.
Instead of convert the assets to xcm, can we just do some basic check on BH to verify the xcm is constructed correctly based upon the assets? e.g.
ensure!(xcm contains DescendOrigin(payload.origin))
ensure!(xcm contains ReserveAssetDeposited(asset)|WithdrawAsset(asset)) // Depends on the transfer type is ENA or PNA
...
So the point here is on chain we do validate rather than construct.
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.
Btw: for transfer I'm not sure DescendOrigin(userOrigin)
make sense. For PNA we need to burn the asset locked in Ethereum sovereign on AH. IIUC that's why we change origin to UniversalOrigin(GlobalConsensus(network))
before WithdrawAsset
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.
So the point here is on chain we do validate rather than construct.
The issue with this approach is that the validation needs to happen on the Ethereum side in order to do this right. The actual locking/unlocking/minting/burning of tokens occurs on the Ethereum side in solidity, which must match the Assets section of the XCM. If the validation happens on BH or in a relayer, and we reject the message, it is too late because we have already changed the state on the Ethereum side. This is why it is better to build the assets section of the XCM on BH to match what was done on the Ethereum side.
Related comment here: yrong/polkadot-sdk#3 (comment)
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 is too late because we have already changed the state on the Ethereum side
Just curious what will happen in this case. Is this something acceptable assuming we will implement error handing mechanism any way? The message with errored xcm will get trapped on BH and we allow end user to edit/repair it?
Something like we allow user to add fee for message pending from Substrate to Ethereum direction, assuming there is no relayer who will pick it up if it's not profitable.
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.
Just curious what will happen in this case. Is this something acceptable assuming we will implement error handing mechanism any way?
I think if the message is queued and if the message is profitable, than once submitted to BH we should not fail for any reason (such as validating the xcm for example) and we should try to always push the assets to AH where they can get trapped if the user passes invalid xcm. And we should trust that we constrain the origin to stop users from doing things that they cannot within the xcm, instead of validating.
/// @param leafProof A message proof used to verify that the message is in the merkle tree committed by the OutboundQueue pallet | ||
/// @param headerProof A proof that the commitment is included in parachain header that was finalized by BEEFY. | ||
/// @param rewardAddress Account on BH to credit delivery rewards | ||
function v2_submit( |
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.
should it also be in IGateway.sol? Meanwhile we may need another function isRelayed(nonce)
to check if the nonce has been relayed.
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 catch!
contracts/src/v2/Types.sol
Outdated
|
||
struct Command { | ||
uint8 kind; | ||
uint256 gas; |
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.
Gas is uint64
.
|
||
function testGetSet() public { | ||
bitmap.set(384); | ||
assertEq(bitmap.get(384), true); |
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.
Maybe test the bits around the set bit to make sure they remain unset.
assertEq(bitmap.get(384), true); | |
assertEq(bitmap.get(383), false); | |
assertEq(bitmap.get(384), true); | |
assertEq(bitmap.get(385), false); |
contracts/src/v2/Calls.sol
Outdated
bytes memory payload = | ||
SubstrateTypes.encodePayloadV2(ticket.origin, ticket.assets, ticket.xcm); |
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.
So the point here is on chain we do validate rather than construct.
The issue with this approach is that the validation needs to happen on the Ethereum side in order to do this right. The actual locking/unlocking/minting/burning of tokens occurs on the Ethereum side in solidity, which must match the Assets section of the XCM. If the validation happens on BH or in a relayer, and we reject the message, it is too late because we have already changed the state on the Ethereum side. This is why it is better to build the assets section of the XCM on BH to match what was done on the Ethereum side.
Related comment here: yrong/polkadot-sdk#3 (comment)
|
||
// Register Ethereum-native token on AHK, using `xcmFeeAHP` and `xcmFeeAHK` | ||
// of `msg.value` to pay for execution on AHP and AHK respectively. | ||
function registerTokenOnKusama(address token, uint128 xcmFeeAHP, uint128 xcmFeeAHK) |
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.
Maybe we should introduce a Network Enum here as a destination parameter, instead of having 2 registers methods. The network can simply contain the Polkadot networks.
https://github.com/paritytech/polkadot-sdk/blob/master/polkadot/xcm/src/v4/junction.rs/#L125
contracts/src/v2/Calls.sol
Outdated
error TokenNotRegistered(); | ||
error InvalidAsset(); | ||
|
||
address public constant WETH_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; |
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.
Hardcoded WETH_ADDRESS?
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.
yeah, why would it need to be mutable?
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.
Oh, I see, for Ethereum testnets, WETH will have a different address. got it. 👍
contracts/src/Gateway.sol
Outdated
revert ChannelDoesNotExist(); | ||
} | ||
// See docs for `IGateway.sendMessage` | ||
function sendMessage(bytes calldata xcm, bytes[] calldata assets) external payable { |
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 we should add a MultiAddress parameter that can be the asset claimer. If provided then we can use this account with the SetClaimer
XCM instruction. If it is zero/empty address then we SetClaimer to the origin(msg.sender).
contracts/src/Gateway.sol
Outdated
|
||
emit IGateway.OutboundMessageAccepted(channelID, channel.outboundNonce, messageID, ticket.payload); | ||
emit IGateway.InboundMessageDispatched(message.nonce, success, rewardAddress); |
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.
Would it be better to store the delivery status on chain? e.g:
mapping(uint64 nonce => DeliveryStatus) public nonces;
struct DeliveryStatus {
uint64 blockNumber;
bool result;
}
With the blockNumber
it's easy for relayer to filter this event efficiently when submit the delivery proof.
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.
That's a good point Ron. Though there are some simplifying assumptions which I think allow us to save 20000 gas by not storing that status on-chain.
The plan I had in mind was that the relayer who calls v2_submit
is also responsible for sending delivery proof back to BH. Since this relayer witnessed the result for v2_submit
it can generate a proof for InboundMessageDispatched
, and there isn't a need for additional metadata on-chain.
For other cases when the delivery proof could not be sent back immediately, we could create a command in snowbridge-relay
that queries for InboundMessageDispatched(rewardAddress=X)
against some public ethereum node or event indexer, and sends the delivery proofs for the matching events.
I will make a rewardAddress
an indexed event parameter, which allows this query to be much faster to execute.
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.
import {OperatingMode} from "./../types/Common.sol"; | ||
|
||
// Inbound message from a Polkadot parachain (via BridgeHub) | ||
struct InboundMessage { |
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.
would suggest to rename as InboundMessageV2
or conflict error when generate go bindings.
> mage build
2024/10/28 15:53:26 INFO: Skipped ssz generated object: relays/beacon/state/beacon_encoding.go
2024/10/28 15:53:26 INFO: Skipped ssz generated object: relays/beacon/state/beacon_deneb_encoding.go
# github.com/snowfork/snowbridge/relayer/contracts
contracts/gateway.go:47:6: InboundMessage redeclared in this block
contracts/gateway.go:40:6: other declaration of InboundMessage
Error: running "go build -o build/snowbridge-relay main.go" failed with exit code 1
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.
af374a6 FYI
) external payable; | ||
|
||
// Check if an inbound message was previously accepted and dispatched | ||
function v2_isDispatched(uint64 nonce) external returns (bool); |
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.
Should add view
keyword here.
|
||
// Register Ethereum-native token on AHK, using `xcmFeeAHP` and `xcmFeeAHK` | ||
// of `msg.value` to pay for execution on AHP and AHK respectively. | ||
function v2_registerTokenOnKusama( |
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.
Just a thought on registration while thinking about our Security model. Kusama token registration can be built as a separate contract on top of the gateway.
The implementation of a contract called KusamaRegistration, that can build the registration Transact call onchain, and then submit it to the gateway.
On BH we would decend origin to that Location so the origin would be { parents:2 interior: X2(Ethreum{chain_id:1}, KusamaRegistration)}
.
This origin would be forwarded to AHK using AliasOrigin, and can be used secure the register extrinsic on the foreignAssets pallet on AHK. This is currently how we secure token registration now on AHP, using the gateway contracts origin (Snowbridge Sovereign).
Upgrade would be just deploying a new version of the KusamaRegistration contract and then updating the origin which is allowed to call register on AHK. And it would be separate from the Gateway.
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.
Nice!
I was wondering if it would be feasible to add Transact support in this release, given that all the building blocks are there?
@@ -59,7 +56,9 @@ contract UpgradeShell is Script { | |||
assetHubReserveTransferFee: mDot(100), // 0.1 DOT | |||
exchangeRate: ud60x18(0.0024e18), | |||
multiplier: ud60x18(1.33e18), | |||
rescueOperator: 0x4B8a782D4F03ffcB7CE1e95C5cfe5BFCb2C8e967 | |||
rescueOperator: 0x4B8a782D4F03ffcB7CE1e95C5cfe5BFCb2C8e967, |
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.
Probably don't need this anymore, since we have already dropped the operator on mainnet?
ParaID constant ASSET_HUB_PARA_ID = ParaID.wrap(1000); | ||
bytes32 constant ASSET_HUB_AGENT_ID = | ||
0x81c5ab2571199e3188135178f3c2c8e2d268be1313d029b30f534fa579b69b79; | ||
|
||
ParaID constant BRIDGE_HUB_PARA_ID = ParaID.wrap(1002); |
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.
Nice! Seems like these para IDs are the same on all the environments now anyway.
try Gateway(this).createChannel{gas: maxDispatchGas}(message.params) {} | ||
catch { | ||
} else if (message.command == CommandV1.CreateChannel) { | ||
success = false; |
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.
So we essentially disable creating new channels?
UpdateChannelParams memory params = abi.decode(data, (UpdateChannelParams)); | ||
// @dev Get token address by tokenID | ||
function tokenAddressOf(bytes32 tokenID) external view returns (address) { | ||
return CallsV1.tokenAddressOf(tokenID); |
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.
How come this is constrained to v1? Would it not stay the same for v2?
emit ChannelUpdated(params.channelID); | ||
/// @dev Create an agent for a consensus system on Polkadot | ||
function v1_handleCreateAgent(bytes calldata data) external onlySelf { | ||
HandlersV1.createAgent(data); |
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 is there an extra layer of functions for these commands? Wouldn't it be fine to use HandlersV1.createAgent(data);
directly in https://github.com/Snowfork/snowbridge/pull/1300/files#diff-74470ba8f35b42dbc8805739e92708c0696343e0b1819feb3e7a5ebb77cc623cR168?
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.
try / catch can only catch errors from external function calls.
function _submitOutbound(Ticket memory ticket) internal { | ||
ChannelID channelID = ticket.dest.into(); | ||
Channel storage channel = _ensureChannel(channelID); | ||
uint256 public constant DISPATCH_OVERHEAD_GAS_V2 = 32_000; |
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.
How do you calculate this? Comment about it might be useful.
@@ -16,16 +16,7 @@ contract RococoGatewayV2 is Gateway { | |||
bytes32 bridgeHubAgentID, |
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: File should probably be for Westend now that Rococo is deprecated.
* Internal functions | ||
*/ | ||
|
||
// Convert foreign currency to native currency (ROC/KSM/DOT -> ETH) |
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.
// Convert foreign currency to native currency (ROC/KSM/DOT -> ETH) | |
// Convert foreign currency to native currency (WND/PAS/KSM/DOT -> ETH) |
InboundMessageV2 calldata message, | ||
bytes32[] calldata leafProof, | ||
Verification.Proof calldata headerProof, | ||
bytes32 rewardAddress |
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.
Won't we just just derive the account from the origin
on BH?
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's different. The origin in InboundMessage is derived from account on source chain, while the rewardAddress is account of the relayer for accepting reward.
Tasks:
IGateway
intoIGatewayV1
andIGatewayV2