diff --git a/crates/astria-sequencer/src/accounts/action.rs b/crates/astria-sequencer/src/accounts/action.rs index 60cbf3c150..8d15d1a606 100644 --- a/crates/astria-sequencer/src/accounts/action.rs +++ b/crates/astria-sequencer/src/accounts/action.rs @@ -6,7 +6,7 @@ use anyhow::{ use astria_core::{ primitive::v1::ADDRESS_LEN, protocol::transaction::v1alpha1::action::TransferAction, - Protobuf, + Protobuf as _, }; use cnidarium::{ StateRead, diff --git a/crates/astria-sequencer/src/app/mod.rs b/crates/astria-sequencer/src/app/mod.rs index 13ec8393c4..be42077ca1 100644 --- a/crates/astria-sequencer/src/app/mod.rs +++ b/crates/astria-sequencer/src/app/mod.rs @@ -3,6 +3,8 @@ pub(crate) mod test_utils; #[cfg(test)] mod tests_app; #[cfg(test)] +mod tests_block_fees; +#[cfg(test)] mod tests_breaking_changes; #[cfg(test)] mod tests_execute_transaction; diff --git a/crates/astria-sequencer/src/app/snapshots/astria_sequencer__app__tests_breaking_changes__app_finalize_block_snapshot.snap b/crates/astria-sequencer/src/app/snapshots/astria_sequencer__app__tests_breaking_changes__app_finalize_block_snapshot.snap index 7d4b7f970c..4647bcb04f 100644 --- a/crates/astria-sequencer/src/app/snapshots/astria_sequencer__app__tests_breaking_changes__app_finalize_block_snapshot.snap +++ b/crates/astria-sequencer/src/app/snapshots/astria_sequencer__app__tests_breaking_changes__app_finalize_block_snapshot.snap @@ -3,36 +3,36 @@ source: crates/astria-sequencer/src/app/tests_breaking_changes.rs expression: app.app_hash.as_bytes() --- [ - 72, - 87, - 29, - 138, - 91, - 221, - 71, + 36, 66, - 219, - 212, - 171, - 126, - 223, - 176, - 198, - 154, - 10, - 234, - 253, - 99, - 213, - 78, - 92, - 228, - 89, - 204, - 197, + 30, + 16, + 122, + 103, + 62, + 67, + 145, + 180, + 247, + 139, + 229, + 200, + 86, 193, - 90, - 57, - 205, - 2 + 28, + 71, + 235, + 32, + 134, + 143, + 239, + 229, + 8, + 86, + 190, + 164, + 81, + 230, + 24, + 149 ] diff --git a/crates/astria-sequencer/src/app/tests_block_fees.rs b/crates/astria-sequencer/src/app/tests_block_fees.rs new file mode 100644 index 0000000000..4801e661ea --- /dev/null +++ b/crates/astria-sequencer/src/app/tests_block_fees.rs @@ -0,0 +1,335 @@ +use std::sync::Arc; + +use astria_core::{ + primitive::v1::RollupId, + protocol::transaction::v1alpha1::{ + action::{ + BridgeLockAction, + BridgeSudoChangeAction, + InitBridgeAccountAction, + SequenceAction, + TransferAction, + }, + TransactionParams, + UnsignedTransaction, + }, + sequencerblock::v1alpha1::block::Deposit, +}; +use cnidarium::StateDelta; +use tendermint::abci::EventAttributeIndexExt as _; + +use crate::{ + accounts::{ + StateReadExt as _, + StateWriteExt as _, + }, + app::test_utils::{ + get_alice_signing_key, + get_bridge_signing_key, + initialize_app, + BOB_ADDRESS, + }, + assets::StateReadExt as _, + bridge::{ + get_deposit_byte_len, + StateWriteExt as _, + }, + sequence::{ + calculate_fee_from_state, + StateWriteExt as _, + }, + test_utils::{ + astria_address, + astria_address_from_hex_string, + nria, + }, +}; + +#[tokio::test] +async fn transaction_execution_records_fee_event() { + let mut app = initialize_app(None, vec![]).await; + + // transfer funds from Alice to Bob + let alice = get_alice_signing_key(); + let bob_address = astria_address_from_hex_string(BOB_ADDRESS); + let value = 333_333; + let tx = UnsignedTransaction { + params: TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + actions: vec![ + TransferAction { + to: bob_address, + amount: value, + asset: nria().into(), + fee_asset: nria().into(), + } + .into(), + ], + }; + + let signed_tx = Arc::new(tx.into_signed(&alice)); + + let events = app.execute_transaction(signed_tx).await.unwrap(); + let transfer_fee = app.state.get_transfer_base_fee().await.unwrap(); + let event = events.first().unwrap(); + assert_eq!(event.kind, "tx.fees"); + assert_eq!( + event.attributes[0], + ("asset", nria().to_string()).index().into() + ); + assert_eq!( + event.attributes[1], + ("feeAmount", transfer_fee.to_string()).index().into() + ); + assert_eq!( + event.attributes[2], + ( + "actionType", + "astria.protocol.transactions.v1alpha1.TransferAction" + ) + .index() + .into() + ); +} + +#[tokio::test] +async fn ensure_correct_block_fees_transfer() { + let mut app = initialize_app(None, vec![]).await; + let mut state_tx = StateDelta::new(app.state.clone()); + let transfer_base_fee = 1; + state_tx.put_transfer_base_fee(transfer_base_fee).unwrap(); + app.apply(state_tx); + + let alice = get_alice_signing_key(); + let bob_address = astria_address_from_hex_string(BOB_ADDRESS); + let actions = vec![ + TransferAction { + to: bob_address, + amount: 1000, + asset: nria().into(), + fee_asset: nria().into(), + } + .into(), + ]; + + let tx = UnsignedTransaction { + params: TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + actions, + }; + let signed_tx = Arc::new(tx.into_signed(&alice)); + app.execute_transaction(signed_tx).await.unwrap(); + + let total_block_fees: u128 = app + .state + .get_block_fees() + .await + .unwrap() + .into_iter() + .map(|(_, fee)| fee) + .sum(); + assert_eq!(total_block_fees, transfer_base_fee); +} + +#[tokio::test] +async fn ensure_correct_block_fees_sequence() { + let mut app = initialize_app(None, vec![]).await; + let mut state_tx = StateDelta::new(app.state.clone()); + state_tx.put_sequence_action_base_fee(1); + state_tx.put_sequence_action_byte_cost_multiplier(1); + app.apply(state_tx); + + let alice = get_alice_signing_key(); + let data = b"hello world".to_vec(); + + let actions = vec![ + SequenceAction { + rollup_id: RollupId::from_unhashed_bytes(b"testchainid"), + data: data.clone().into(), + fee_asset: nria().into(), + } + .into(), + ]; + + let tx = UnsignedTransaction { + params: TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + actions, + }; + let signed_tx = Arc::new(tx.into_signed(&alice)); + app.execute_transaction(signed_tx).await.unwrap(); + + let total_block_fees: u128 = app + .state + .get_block_fees() + .await + .unwrap() + .into_iter() + .map(|(_, fee)| fee) + .sum(); + let expected_fees = calculate_fee_from_state(&data, &app.state).await.unwrap(); + assert_eq!(total_block_fees, expected_fees); +} + +#[tokio::test] +async fn ensure_correct_block_fees_init_bridge_acct() { + let mut app = initialize_app(None, vec![]).await; + let mut state_tx = StateDelta::new(app.state.clone()); + let init_bridge_account_base_fee = 1; + state_tx.put_init_bridge_account_base_fee(init_bridge_account_base_fee); + app.apply(state_tx); + + let alice = get_alice_signing_key(); + + let actions = vec![ + InitBridgeAccountAction { + rollup_id: RollupId::from_unhashed_bytes(b"testchainid"), + asset: nria().into(), + fee_asset: nria().into(), + sudo_address: None, + withdrawer_address: None, + } + .into(), + ]; + + let tx = UnsignedTransaction { + params: TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + actions, + }; + let signed_tx = Arc::new(tx.into_signed(&alice)); + app.execute_transaction(signed_tx).await.unwrap(); + + let total_block_fees: u128 = app + .state + .get_block_fees() + .await + .unwrap() + .into_iter() + .map(|(_, fee)| fee) + .sum(); + assert_eq!(total_block_fees, init_bridge_account_base_fee); +} + +#[tokio::test] +async fn ensure_correct_block_fees_bridge_lock() { + let alice = get_alice_signing_key(); + let bridge = get_bridge_signing_key(); + let bridge_address = astria_address(&bridge.address_bytes()); + let rollup_id = RollupId::from_unhashed_bytes(b"testchainid"); + + let mut app = initialize_app(None, vec![]).await; + let mut state_tx = StateDelta::new(app.state.clone()); + + let transfer_base_fee = 1; + let bridge_lock_byte_cost_multiplier = 1; + + state_tx.put_transfer_base_fee(transfer_base_fee).unwrap(); + state_tx.put_bridge_lock_byte_cost_multiplier(bridge_lock_byte_cost_multiplier); + state_tx.put_bridge_account_rollup_id(bridge_address, &rollup_id); + state_tx + .put_bridge_account_ibc_asset(bridge_address, nria()) + .unwrap(); + app.apply(state_tx); + + let actions = vec![ + BridgeLockAction { + to: bridge_address, + amount: 1, + asset: nria().into(), + fee_asset: nria().into(), + destination_chain_address: rollup_id.to_string(), + } + .into(), + ]; + + let tx = UnsignedTransaction { + params: TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + actions, + }; + let signed_tx = Arc::new(tx.into_signed(&alice)); + app.execute_transaction(signed_tx).await.unwrap(); + + let test_deposit = Deposit::new( + bridge_address, + rollup_id, + 1, + nria().into(), + rollup_id.to_string(), + ); + + let total_block_fees: u128 = app + .state + .get_block_fees() + .await + .unwrap() + .into_iter() + .map(|(_, fee)| fee) + .sum(); + let expected_fees = transfer_base_fee + + (get_deposit_byte_len(&test_deposit) * bridge_lock_byte_cost_multiplier); + assert_eq!(total_block_fees, expected_fees); +} + +#[tokio::test] +async fn ensure_correct_block_fees_bridge_sudo_change() { + let alice = get_alice_signing_key(); + let alice_address = astria_address(&alice.address_bytes()); + let bridge = get_bridge_signing_key(); + let bridge_address = astria_address(&bridge.address_bytes()); + + let mut app = initialize_app(None, vec![]).await; + let mut state_tx = StateDelta::new(app.state.clone()); + + let sudo_change_base_fee = 1; + state_tx.put_bridge_sudo_change_base_fee(sudo_change_base_fee); + state_tx.put_bridge_account_sudo_address(bridge_address, alice_address); + state_tx + .increase_balance(bridge_address, nria(), 1) + .await + .unwrap(); + app.apply(state_tx); + + let actions = vec![ + BridgeSudoChangeAction { + bridge_address, + new_sudo_address: None, + new_withdrawer_address: None, + fee_asset: nria().into(), + } + .into(), + ]; + + let tx = UnsignedTransaction { + params: TransactionParams::builder() + .nonce(0) + .chain_id("test") + .build(), + actions, + }; + let signed_tx = Arc::new(tx.into_signed(&alice)); + app.execute_transaction(signed_tx).await.unwrap(); + + let total_block_fees: u128 = app + .state + .get_block_fees() + .await + .unwrap() + .into_iter() + .map(|(_, fee)| fee) + .sum(); + assert_eq!(total_block_fees, sudo_change_base_fee); +} + +// TODO(https://github.com/astriaorg/astria/issues/1382): Add test to ensure correct block fees for ICS20 withdrawal diff --git a/crates/astria-sequencer/src/app/tests_execute_transaction.rs b/crates/astria-sequencer/src/app/tests_execute_transaction.rs index 6a39904305..42c2704b57 100644 --- a/crates/astria-sequencer/src/app/tests_execute_transaction.rs +++ b/crates/astria-sequencer/src/app/tests_execute_transaction.rs @@ -30,7 +30,6 @@ use astria_core::{ use bytes::Bytes; use cnidarium::StateDelta; use penumbra_ibc::params::IBCParameters; -use tendermint::abci::EventAttributeIndexExt as _; use crate::{ accounts::StateReadExt as _, @@ -1052,52 +1051,3 @@ async fn app_execute_transaction_bridge_lock_unlock_action_ok() { "bridge should've transferred out whole balance" ); } - -#[tokio::test] -async fn transaction_execution_records_fee_event() { - let mut app = initialize_app(None, vec![]).await; - - // transfer funds from Alice to Bob - let alice = get_alice_signing_key(); - let bob_address = astria_address_from_hex_string(BOB_ADDRESS); - let value = 333_333; - let tx = UnsignedTransaction { - params: TransactionParams::builder() - .nonce(0) - .chain_id("test") - .build(), - actions: vec![ - TransferAction { - to: bob_address, - amount: value, - asset: nria().into(), - fee_asset: nria().into(), - } - .into(), - ], - }; - - let signed_tx = Arc::new(tx.into_signed(&alice)); - - let events = app.execute_transaction(signed_tx).await.unwrap(); - let transfer_fee = app.state.get_transfer_base_fee().await.unwrap(); - let event = events.first().unwrap(); - assert_eq!(event.kind, "tx.fees"); - assert_eq!( - event.attributes[0], - ("asset", nria().to_string()).index().into() - ); - assert_eq!( - event.attributes[1], - ("feeAmount", transfer_fee.to_string()).index().into() - ); - assert_eq!( - event.attributes[2], - ( - "actionType", - "astria.protocol.transactions.v1alpha1.TransferAction" - ) - .index() - .into() - ); -} diff --git a/crates/astria-sequencer/src/bridge/bridge_lock_action.rs b/crates/astria-sequencer/src/bridge/bridge_lock_action.rs index 880298fb5f..b2375aae24 100644 --- a/crates/astria-sequencer/src/bridge/bridge_lock_action.rs +++ b/crates/astria-sequencer/src/bridge/bridge_lock_action.rs @@ -9,6 +9,7 @@ use astria_core::{ TransferAction, }, sequencerblock::v1alpha1::block::Deposit, + Protobuf as _, }; use cnidarium::StateWrite; @@ -23,6 +24,7 @@ use crate::{ }, address::StateReadExt as _, app::ActionHandler, + assets::StateWriteExt as _, bridge::{ StateReadExt as _, StateWriteExt as _, @@ -125,7 +127,10 @@ impl ActionHandler for BridgeLockAction { .await .context("failed to get byte cost multiplier")?; let fee = byte_cost_multiplier.saturating_mul(get_deposit_byte_len(&deposit)); - + state + .get_and_increase_block_fees(&self.fee_asset, fee, Self::full_name()) + .await + .context("failed to add to block fees")?; state .decrease_balance(from, &self.fee_asset, fee) .await @@ -156,8 +161,7 @@ mod tests { use super::*; use crate::{ - address::StateWriteExt, - assets::StateWriteExt as _, + address::StateWriteExt as _, test_utils::{ assert_anyhow_error, astria_address, diff --git a/crates/astria-sequencer/src/bridge/bridge_sudo_change_action.rs b/crates/astria-sequencer/src/bridge/bridge_sudo_change_action.rs index 483adf9af5..1ebe568d53 100644 --- a/crates/astria-sequencer/src/bridge/bridge_sudo_change_action.rs +++ b/crates/astria-sequencer/src/bridge/bridge_sudo_change_action.rs @@ -3,14 +3,20 @@ use anyhow::{ Context as _, Result, }; -use astria_core::protocol::transaction::v1alpha1::action::BridgeSudoChangeAction; +use astria_core::{ + protocol::transaction::v1alpha1::action::BridgeSudoChangeAction, + Protobuf as _, +}; use cnidarium::StateWrite; use crate::{ accounts::StateWriteExt as _, address::StateReadExt as _, app::ActionHandler, - assets::StateReadExt as _, + assets::{ + StateReadExt as _, + StateWriteExt as _, + }, bridge::state_ext::{ StateReadExt as _, StateWriteExt as _, @@ -73,6 +79,10 @@ impl ActionHandler for BridgeSudoChangeAction { .get_bridge_sudo_change_base_fee() .await .context("failed to get bridge sudo change fee")?; + state + .get_and_increase_block_fees(&self.fee_asset, fee, Self::full_name()) + .await + .context("failed to add to block fees")?; state .decrease_balance(self.bridge_address, &self.fee_asset, fee) .await @@ -98,7 +108,6 @@ mod tests { use super::*; use crate::{ address::StateWriteExt as _, - assets::StateWriteExt as _, test_utils::{ astria_address, ASTRIA_PREFIX, diff --git a/crates/astria-sequencer/src/bridge/init_bridge_account_action.rs b/crates/astria-sequencer/src/bridge/init_bridge_account_action.rs index 0dbbd07c78..bb1ae84d25 100644 --- a/crates/astria-sequencer/src/bridge/init_bridge_account_action.rs +++ b/crates/astria-sequencer/src/bridge/init_bridge_account_action.rs @@ -7,6 +7,7 @@ use anyhow::{ use astria_core::{ primitive::v1::Address, protocol::transaction::v1alpha1::action::InitBridgeAccountAction, + Protobuf as _, }; use cnidarium::StateWrite; @@ -17,7 +18,10 @@ use crate::{ }, address::StateReadExt as _, app::ActionHandler, - assets::StateReadExt as _, + assets::{ + StateReadExt as _, + StateWriteExt as _, + }, bridge::state_ext::{ StateReadExt as _, StateWriteExt as _, @@ -98,7 +102,10 @@ impl ActionHandler for InitBridgeAccountAction { from, self.withdrawer_address.map_or(from, Address::bytes), ); - + state + .get_and_increase_block_fees(&self.fee_asset, fee, Self::full_name()) + .await + .context("failed to get and increase block fees")?; state .decrease_balance(from, &self.fee_asset, fee) .await diff --git a/crates/astria-sequencer/src/ibc/ics20_withdrawal.rs b/crates/astria-sequencer/src/ibc/ics20_withdrawal.rs index 477519df59..314d44c60f 100644 --- a/crates/astria-sequencer/src/ibc/ics20_withdrawal.rs +++ b/crates/astria-sequencer/src/ibc/ics20_withdrawal.rs @@ -10,6 +10,7 @@ use astria_core::{ Address, }, protocol::transaction::v1alpha1::action, + Protobuf as _, }; use cnidarium::{ StateRead, @@ -33,6 +34,7 @@ use crate::{ }, address::StateReadExt as _, app::ActionHandler, + assets::StateWriteExt as _, bridge::StateReadExt as _, ibc::{ StateReadExt as _, @@ -144,6 +146,11 @@ impl ActionHandler for action::Ics20Withdrawal { .context("packet failed send check")? }; + state + .get_and_increase_block_fees(self.fee_asset(), fee, Self::full_name()) + .await + .context("failed to get and increase block fees")?; + state .decrease_balance(withdrawal_target, self.denom(), self.amount()) .await