-
Notifications
You must be signed in to change notification settings - Fork 105
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
SNO-612: Extend sendToken for Polkadot-native assets #927
Conversation
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## polkadot-assets-on-ethereum #927 +/- ##
==============================================================
Coverage ? 72.43%
==============================================================
Files ? 41
Lines ? 1810
Branches ? 92
==============================================================
Hits ? 1311
Misses ? 473
Partials ? 26
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
contracts/src/Gateway.sol
Outdated
struct RegisterTokenParams { | ||
/// @dev The ID of the agent to register the token for | ||
bytes32 agentID; | ||
/// @dev The name of the new token | ||
string name; | ||
/// @dev The short symbol of the new token | ||
string symbol; | ||
/// @dev The number of decimal places allowed | ||
uint8 decimals; | ||
} | ||
|
||
// @dev Register a new fungible Polkadot token for an agent | ||
function registerToken(bytes calldata data) external onlySelf { | ||
CoreStorage.Layout storage $ = CoreStorage.layout(); | ||
|
||
RegisterTokenParams memory params = abi.decode(data, (RegisterTokenParams)); | ||
|
||
address agent = $.agents[params.agentID]; | ||
if (agent == address(0)) { | ||
revert AgentDoesNotExist(); | ||
} | ||
|
||
bytes memory call = abi.encodeCall(AgentExecutor.registerToken, (params.name, params.symbol, params.decimals)); | ||
bytes memory response = _invokeOnAgent(agent, call); | ||
|
||
address token = address(bytes20(response)); | ||
$.ownerAgentIDs[token] = params.agentID; | ||
} |
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.
The gateway already has an Command.AgentExecute
command for sending arbitrary messages to Agents. Lets use that rather than adding special-purpose commands for higher-level application stuff.
I understand we do need to keep a token registry in the gateway. So you could make the agent call back into the gateway to update the registry.
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.
Oops, my mistake, your approach here is correct. My memory was foggy as I just got back from leave.
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.
Actually, yes, we should be reusing using 'Command.AgentExecute' instead of creating a new toplevel 'Command.RegisterToken'.
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.
The call back into the gateway opens a door to attackers: they can change the agentID for any token. This won't let them control the token (as they're not the owner recorded in the token contract), but it can be used to grief legitimate owners by changing the token's agentID so that calls to the token fail.
So we'll need some kind of authorisation when updating the token registry. It might be enough to require that the token hasn't been registered before.
contracts/src/Gateway.sol
Outdated
@@ -410,6 +415,35 @@ contract Gateway is IGateway, IInitializable { | |||
_transferNativeFromAgent(agent, payable(params.recipient), params.amount); | |||
} | |||
|
|||
struct RegisterTokenParams { |
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.
When registering a foreign token, these params also need to include a unique and stable ID for the original asset on the Polkadot side.
This ID will be used later on for Polkadot->Ethereum transfers, to lookup the address of the erc20 contract of the wrapped token.
On BridgeHub, I suggest reusing the code below to produce this ID.
let agent_id = T::AgentHashedDescription::convert_location(&location) |
The (name, symbol, decimals) metadata can't be used for this purpose. That's just metadata and has no guarantee of uniqueness.
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.
Agree! Arbitrary transact also depends on the mapping here(asset_id <-> location) should be a runtime storage somewhere to support dynamic fee, currently we only support DOT
to be paid as the fee of BuyExecution
.
f6fdd41
to
b340c95
Compare
ebe5178
to
48f4717
Compare
@@ -412,13 +412,14 @@ contract Gateway is IGateway, IInitializable { | |||
} | |||
|
|||
// @dev Register a new fungible Polkadot token for an agent | |||
function setTokenOwnerAgentID(address token, bytes32 agentID) external { | |||
function registerToken(bytes32 tokenID, address token, bytes32 agentID) 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.
I've thought of a way we can prevent malicious parachains from using Transact
to call back into gateway via an agent.
In AgentExecutor.sol, we make sure the destination of the transact is not the gateway contract. If it the destination is the gateway, we disallow the transact.
What we still need for this registerToken
is a guard to ensure it's only being called by a registered agent. We may need to introduce another storage field which allows to perform this lookup. Unfortunately the storage field below only allows us to lookup by agentID:
mapping(bytes32 agentID => address) agents; |
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 receive the relevant agentID
as the 3rd parameter here, so we can check that msg.sender == agents[agentID]
. This means that only a registered agent can call registerToken
and that they must register tokens for themselves. It's slightly stricter than just "registered agents only", but seems reasonable as the intention is for agents to register their own tokens anyway.
Move registerToken to gateway command so that the return value can be set in the gateway's storage.
Register, mint, then transfer.
b7e3511
to
9a216bb
Compare
Closing, will reopen if it becomes relevant. |
Stacking this on #926. I'm not sure about initiating burn within
Assets
, given the need to invoke an agent. Would it be more appropriate to burn inGateway
? (where we invoke agents & register Polkadot tokens) Should we rather move Polkadot token registration intoAssets
as well?SNO-612