Skip to content

Conversation

@noot
Copy link
Contributor

@noot noot commented Nov 8, 2023

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

  • implement a Denom type, which represents the base (smallest) denomination of an arbitrary asset
  • implement an Id type, 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, the Id is just the hash of the base denomination.
  • update UnsignedTransaction to have an optional fee_asset field; if this is unset, the native asset is used for fee payment
  • update Transfer to have an optional asset field; if this is unset, the native asset is the asset transferred
  • update the sequencer app to have a NATIVE_ASSET global
  • update the genesis state to have a native_asset_base_denomination field. the native asset should be set in the genesis file, and stored in (non-consensus) storage for retrieval upon startups. then the NATIVE_ASSET global is set upon startup by reading from storage.

Testing

unit tests, ran it locally

Breaking Changelist

  • UnsignedTransaction and Transfer now have an extra (optional) field.

Related Issues

closes #559

@github-actions github-actions bot added proto pertaining to the Astria Protobuf spec sequencer pertaining to the astria-sequencer crate labels Nov 8, 2023
@github-actions github-actions bot added sequencer-relayer pertaining to the astria-sequencer-relayer crate composer pertaining to composer labels Nov 9, 2023
@noot noot marked this pull request as ready for review November 9, 2023 23:47
}

#[must_use]
pub fn id(&self) -> Id {
Copy link
Contributor

@SuperFluffy SuperFluffy Nov 20, 2023

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]);
Copy link
Contributor

@SuperFluffy SuperFluffy Nov 20, 2023

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.

Copy link
Contributor Author

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,
Copy link
Contributor

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,
Copy link
Contributor

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?;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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),
Copy link
Contributor

@SuperFluffy SuperFluffy Nov 20, 2023

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,
Copy link
Contributor

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)
Copy link
Contributor

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)
Copy link
Contributor

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)
Copy link
Contributor

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)
Copy link
Contributor

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 {
Copy link
Contributor

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?

Copy link
Contributor Author

@noot noot Nov 20, 2023

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] {
Copy link
Contributor

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>()
Copy link
Contributor

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;
Copy link
Contributor

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

Copy link
Contributor

@SuperFluffy SuperFluffy left a 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

composer pertaining to composer proto pertaining to the Astria Protobuf spec sequencer pertaining to the astria-sequencer crate sequencer-relayer pertaining to the astria-sequencer-relayer crate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

sequencer/IBC: support multiple token types and assets for fee payment

3 participants