Skip to content

Commit

Permalink
feat(sequencer)!: implement bridge unlock action and derestrict trans…
Browse files Browse the repository at this point in the history
…fers (#1034)

## Summary
Implements bridge accounts' `BridgeUnlock` action and allows bridge
accounts to transfer assets like normal.

## Background

We need a `BridgeUnlock` action to help with tracking/proving that
withdraws for rollup specific events happened. This PR implements the
ground work for that by adding a new action with a `memo:bytes` field
that is currently unused.

We also decided that it's unnecessary to have restrictions on the bridge
account in terms of normal transfers in/out of the account. The bridge
account is already a privileged actor and having the restrictions on
transfer would make normal account maintenance (like gas funding) messy.

## Changes
- implement `BridgeUnlockAction` for transferring out a bridge's bridged
asset
- derestricted the transfer function to let bridge account also act like
normal accounts

## Testing
unit tests

## Breaking Changelist
- one new action is added: `BridgeUnlock`
- bridge account now can transfer assets in/out like normal sequencer
accounts

## Related Issues
closes #983
  • Loading branch information
Lilyjjo authored May 14, 2024
1 parent a393f3a commit dbd3aec
Show file tree
Hide file tree
Showing 14 changed files with 574 additions and 94 deletions.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

115 changes: 115 additions & 0 deletions crates/astria-core/src/protocol/transaction/v1alpha1/action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ pub enum Action {
FeeAssetChange(FeeAssetChangeAction),
InitBridgeAccount(InitBridgeAccountAction),
BridgeLock(BridgeLockAction),
BridgeUnlock(BridgeUnlockAction),
}

impl Action {
Expand All @@ -54,6 +55,7 @@ impl Action {
Action::FeeAssetChange(act) => Value::FeeAssetChangeAction(act.into_raw()),
Action::InitBridgeAccount(act) => Value::InitBridgeAccountAction(act.into_raw()),
Action::BridgeLock(act) => Value::BridgeLockAction(act.into_raw()),
Action::BridgeUnlock(act) => Value::BridgeUnlockAction(act.into_raw()),
};
raw::Action {
value: Some(kind),
Expand All @@ -77,6 +79,7 @@ impl Action {
Action::FeeAssetChange(act) => Value::FeeAssetChangeAction(act.to_raw()),
Action::InitBridgeAccount(act) => Value::InitBridgeAccountAction(act.to_raw()),
Action::BridgeLock(act) => Value::BridgeLockAction(act.to_raw()),
Action::BridgeUnlock(act) => Value::BridgeUnlockAction(act.to_raw()),
};
raw::Action {
value: Some(kind),
Expand Down Expand Up @@ -134,6 +137,9 @@ impl Action {
Value::BridgeLockAction(act) => Self::BridgeLock(
BridgeLockAction::try_from_raw(act).map_err(ActionError::bridge_lock)?,
),
Value::BridgeUnlockAction(act) => Self::BridgeUnlock(
BridgeUnlockAction::try_from_raw(act).map_err(ActionError::bridge_unlock)?,
),
};
Ok(action)
}
Expand Down Expand Up @@ -215,6 +221,12 @@ impl From<BridgeLockAction> for Action {
}
}

impl From<BridgeUnlockAction> for Action {
fn from(value: BridgeUnlockAction) -> Self {
Self::BridgeUnlock(value)
}
}

#[allow(clippy::module_name_repetitions)]
#[derive(Debug, thiserror::Error)]
#[error(transparent)]
Expand Down Expand Up @@ -268,6 +280,10 @@ impl ActionError {
fn bridge_lock(inner: BridgeLockActionError) -> Self {
Self(ActionErrorKind::BridgeLock(inner))
}

fn bridge_unlock(inner: BridgeUnlockActionError) -> Self {
Self(ActionErrorKind::BridgeUnlock(inner))
}
}

#[derive(Debug, thiserror::Error)]
Expand Down Expand Up @@ -296,6 +312,8 @@ enum ActionErrorKind {
InitBridgeAccount(#[source] InitBridgeAccountActionError),
#[error("bridge lock action was not valid")]
BridgeLock(#[source] BridgeLockActionError),
#[error("bridge unlock action was not valid")]
BridgeUnlock(#[source] BridgeUnlockActionError),
}

#[derive(Debug, thiserror::Error)]
Expand Down Expand Up @@ -1269,3 +1287,100 @@ enum BridgeLockActionErrorKind {
#[error("the `fee_asset_id` field was invalid")]
InvalidFeeAssetId(#[source] asset::IncorrectAssetIdLength),
}

#[allow(clippy::module_name_repetitions)]
#[derive(Debug, Clone)]
pub struct BridgeUnlockAction {
pub to: Address,
pub amount: u128,
// asset to use for fee payment.
pub fee_asset_id: asset::Id,
// memo for double spend protection.
pub memo: Vec<u8>,
}

impl BridgeUnlockAction {
#[must_use]
pub fn into_raw(self) -> raw::BridgeUnlockAction {
raw::BridgeUnlockAction {
to: Some(self.to.to_raw()),
amount: Some(self.amount.into()),
fee_asset_id: self.fee_asset_id.as_ref().to_vec(),
memo: self.memo,
}
}

#[must_use]
pub fn to_raw(&self) -> raw::BridgeUnlockAction {
raw::BridgeUnlockAction {
to: Some(self.to.to_raw()),
amount: Some(self.amount.into()),
fee_asset_id: self.fee_asset_id.as_ref().to_vec(),
memo: self.memo.clone(),
}
}

/// Convert from a raw, unchecked protobuf [`raw::BridgeUnlockAction`].
///
/// # Errors
///
/// - if the `to` field is not set
/// - if the `to` field is invalid
/// - if the `amount` field is invalid
/// - if the `fee_asset_id` field is invalid
pub fn try_from_raw(proto: raw::BridgeUnlockAction) -> Result<Self, BridgeUnlockActionError> {
let Some(to) = proto.to else {
return Err(BridgeUnlockActionError::field_not_set("to"));
};
let to = Address::try_from_raw(&to).map_err(BridgeUnlockActionError::invalid_address)?;
let amount = proto
.amount
.ok_or(BridgeUnlockActionError::missing_amount())?;
let fee_asset_id = asset::Id::try_from_slice(&proto.fee_asset_id)
.map_err(BridgeUnlockActionError::invalid_fee_asset_id)?;
Ok(Self {
to,
amount: amount.into(),
fee_asset_id,
memo: proto.memo,
})
}
}

#[derive(Debug, thiserror::Error)]
#[error(transparent)]
pub struct BridgeUnlockActionError(BridgeUnlockActionErrorKind);

impl BridgeUnlockActionError {
#[must_use]
fn field_not_set(field: &'static str) -> Self {
Self(BridgeUnlockActionErrorKind::FieldNotSet(field))
}

#[must_use]
fn invalid_address(err: IncorrectAddressLength) -> Self {
Self(BridgeUnlockActionErrorKind::InvalidAddress(err))
}

#[must_use]
fn missing_amount() -> Self {
Self(BridgeUnlockActionErrorKind::MissingAmount)
}

#[must_use]
fn invalid_fee_asset_id(err: asset::IncorrectAssetIdLength) -> Self {
Self(BridgeUnlockActionErrorKind::InvalidFeeAssetId(err))
}
}

#[derive(Debug, thiserror::Error)]
enum BridgeUnlockActionErrorKind {
#[error("the expected field in the raw source type was not set: `{0}`")]
FieldNotSet(&'static str),
#[error("the `to` field was invalid")]
InvalidAddress(#[source] IncorrectAddressLength),
#[error("the `amount` field was not set")]
MissingAmount,
#[error("the `fee_asset_id` field was invalid")]
InvalidFeeAssetId(#[source] asset::IncorrectAssetIdLength),
}
12 changes: 0 additions & 12 deletions crates/astria-sequencer/src/accounts/action.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ use crate::{
StateReadExt,
StateWriteExt,
},
bridge::state_ext::StateReadExt as _,
state_ext::{
StateReadExt as _,
StateWriteExt as _,
Expand Down Expand Up @@ -86,17 +85,6 @@ impl ActionHandler for TransferAction {
state: &S,
from: Address,
) -> Result<()> {
// ensure the recipient is not a bridge account,
// as the explicit `BridgeLockAction` should be used to transfer to a bridge account.
ensure!(
state
.get_bridge_account_rollup_id(&self.to)
.await
.context("failed to get bridge account rollup ID from state")?
.is_none(),
"cannot send transfer to bridge account",
);

transfer_check_stateful(self, state, from)
.await
.context("stateful transfer check failed")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,38 +1,39 @@
---
source: crates/astria-sequencer/src/app/tests_breaking_changes.rs
assertion_line: 274
expression: app.app_hash.as_bytes()
---
[
191,
74,
54,
30,
232,
210,
6,
194,
12,
154,
179,
5,
152,
196,
250,
125,
255,
173,
93,
58,
245,
0,
43,
100,
173,
142,
245,
56,
145,
105,
40,
101,
30,
224,
10,
195,
119,
124,
207,
182,
132,
175,
9,
191,
70,
2,
181,
214,
161,
226,
13,
171,
20,
255
104,
213,
200,
146,
180,
192,
229,
97
]
11 changes: 11 additions & 0 deletions crates/astria-sequencer/src/app/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,17 @@ pub(crate) fn get_alice_signing_key_and_address() -> (SigningKey, Address) {
(alice_signing_key, alice)
}

pub(crate) fn get_bridge_signing_key_and_address() -> (SigningKey, Address) {
let bridge_secret_bytes: [u8; 32] =
hex::decode("db4982e01f3eba9e74ac35422fcd49aa2b47c3c535345c7e7da5220fe3a0ce79")
.unwrap()
.try_into()
.unwrap();
let bridge_signing_key = SigningKey::from(bridge_secret_bytes);
let bridge = Address::from_verification_key(bridge_signing_key.verification_key());
(bridge_signing_key, bridge)
}

pub(crate) fn default_genesis_accounts() -> Vec<Account> {
vec![
Account {
Expand Down
Loading

0 comments on commit dbd3aec

Please sign in to comment.