-
Notifications
You must be signed in to change notification settings - Fork 95
feat(sequencer): implement support for arbitrary assets #568
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
Conversation
| } | ||
|
|
||
| #[must_use] | ||
| pub fn id(&self) -> 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.
Can you add little doc strings to this that also explain what these things are (i.e. hash of the base denom, what it's used for).
|
|
||
| /// Asset ID, which is the hash of the denomination trace. | ||
| #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] | ||
| pub struct Id([u8; 32]); |
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.
Is there an advantage of having this be a self-standing type as opposed to leaving this as an implementation detail fo the Denom type?
One need not match every proto type with a "native" type.
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, there will be an IbcAsset type also which will use this, so I'd like to keep it named
| pub nonce: u32, | ||
| pub actions: Vec<Action>, | ||
| /// asset to use for fee payment. | ||
| pub fee_asset_id: asset::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.
See above, not opposed to types, just wondering if leaving this as a bare [u8; 32] would be fine.
| // to cover both the fee and the amount transferred | ||
| if fee_asset_id == &transfer_asset_id { | ||
| ensure!( | ||
| from_fee_balance >= self.amount + TRANSFER_FEE, |
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.
Can we use let Some(from_fee_balance) = self.amount.checked_add(TRANSFER_FEE) else {}; here? These things are usually fine, but I'd like us to be extra cautious here because the worst case scenario (however unlikely) is bad.
| "insufficient funds for fee payment" | ||
| ); | ||
|
|
||
| let from_transfer_balance = state.get_account_balance(from, &transfer_asset_id).await?; |
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.
| let from_transfer_balance = state.get_account_balance(from, &transfer_asset_id).await?; | |
| let from_transfer_balance = state.get_account_balance(from, &transfer_asset_id) | |
| .await | |
| .context("failed getting account balance while checking that enough funds are available")?; |
| .put_account_balance( | ||
| from, | ||
| &transfer_asset_id, | ||
| from_balance - (self.amount + TRANSFER_FEE), |
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.
As above, can we use checked math for all of this? I.e. checked_sub + checked_add.
Can put that into a utility function to make the purpose clear.
| &self, | ||
| _state: &S, | ||
| _from: Address, | ||
| _fee_asset_id: &asset::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.
Isn't Id: Copy? Could just _fee_asset_id: asset::Id
| ) | ||
| .context("failed updating `from` account balance")?; | ||
| state | ||
| .put_account_balance(self.to, &transfer_asset_id, to_balance + self.amount) |
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.
checked math?
| // otherwise, just transfer the transfer asset and deduct fee from fee asset balance | ||
| // later | ||
| state | ||
| .put_account_balance(from, &transfer_asset_id, from_balance - self.amount) |
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.
checked math?
| .put_account_balance(from, &transfer_asset_id, from_balance - self.amount) | ||
| .context("failed updating `from` account balance")?; | ||
| state | ||
| .put_account_balance(self.to, &transfer_asset_id, to_balance + self.amount) |
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.
checked math?
| .await | ||
| .context("failed getting `from` account balance for fee payment")?; | ||
| state | ||
| .put_account_balance(from, fee_asset_id, from_fee_balance - TRANSFER_FEE) |
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.
checked math?
| state | ||
| .put_account_balance(self.to, &transfer_asset_id, to_balance + self.amount) | ||
| .context("failed updating `to` account balance")?; | ||
| } else { |
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 any one of the steps in this else block fails, don't we need some kind of rollback mechanism to stay consistent?
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 is in a StateDelta, so the state changes will not be committed if this fails. see this part of deliver_tx:
let mut state_tx = self
.state
.try_begin_transaction()
.expect("state Arc should be present and unique");
transaction::execute(&signed_tx, &mut state_tx)
.await
.context("failed executing transaction")?;
state_tx.apply();
if the tx fails to execute, the changes aren't applied to the StateDelta.
| } | ||
|
|
||
| #[must_use] | ||
| pub fn as_bytes(&self) -> &[u8; 32] { |
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.
You could add AsRef for this.
| format!( | ||
| "{}/balance/{}", | ||
| storage_key(&address.encode_hex::<String>()), | ||
| asset.as_bytes().encode_hex::<String>() |
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.
See my comment above: hex::ToHex is implemented for all T: AsRef<[u8]>, so you could just impl AsRef<[u8]> for Id to make this a bit more elegant
| @@ -0,0 +1,21 @@ | |||
| use std::sync::OnceLock; | |||
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. This makes sequencer need msrv 1.70, but it's taken care of in #582
SuperFluffy
left a 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.
Looks good. The fact that the native asset is encoded now I like.
Other than some requests for error context, please also have a look at this comment (can't link it; it gives you the file, which is confusing, quoting; need grep to find it):
If any one of the steps in this else block fails, don't we need some kind of rollback mechanism to stay consistent?
Also, I would very much like to go overboard with checked math for the fee calculations as a safety precaution.
Summary
This PR implements support for arbitrary assets for fee payments and balance/transfers inside the sequencer. Currently, only one asset exists (the native asset, RIA), but after IBC is implemented we can potentially have many arbitrary assets on the sequencer chain.
Background
We need to support multiple assets for IBC, and also support fee payments in different token types (probably ibc-TIA)
Changes
Denomtype, which represents the base (smallest) denomination of an arbitrary assetIdtype, which is a hash of the denomination "trace" (see https://github.com/cosmos/ibc-go/blob/main/docs/architecture/adr-001-coin-source-tracing.md for details) in this PR, since there is no IBC, theIdis just the hash of the base denomination.UnsignedTransactionto have an optionalfee_assetfield; if this is unset, the native asset is used for fee paymentTransferto have an optionalassetfield; if this is unset, the native asset is the asset transferredNATIVE_ASSETglobalnative_asset_base_denominationfield. the native asset should be set in the genesis file, and stored in (non-consensus) storage for retrieval upon startups. then theNATIVE_ASSETglobal is set upon startup by reading from storage.Testing
unit tests, ran it locally
Breaking Changelist
UnsignedTransactionandTransfernow have an extra (optional) field.Related Issues
closes #559