Skip to content

Commit 6b5dae9

Browse files
fix(bridge-contracts): fix memo transaction hash encoding (#1428)
## Summary Use the correct encoding functions to populate memo field in the bridge's conversion logic from rollup withdrawal events to sequencer actions. ## Background We were using `ethers::H256::to_string()` to get the string for the `rollup_transaction_hash` value, which returns a shortened version of the hash. the string `0x1234...0x1234` is instead of the full hash (i.e. you would expect it to give you something like `0x1234567890123456789012345678901234567890123456789012345678901234`). ## Changes - Use the correct ## Breaking Changelist - The previously invalid memo data will now be populated with the correct information. This doesn't actually break anything because we also aren't actually using these hashes anywhere. ## Related Issues Link any issues that are related, prefer full github links. closes #1427, #1471 --------- Co-authored-by: Richard Janis Goldschmidt <github@aberrat.io>
1 parent 8bc06f1 commit 6b5dae9

File tree

6 files changed

+133
-28
lines changed

6 files changed

+133
-28
lines changed

crates/astria-bridge-contracts/src/lib.rs

+25-4
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ use astria_withdrawer::{
2525
SequencerWithdrawalFilter,
2626
};
2727
use ethers::{
28+
self,
29+
abi::AbiEncode,
2830
contract::EthEvent,
2931
providers::Middleware,
3032
types::{
@@ -378,10 +380,16 @@ where
378380
.ok_or_else(|| GetWithdrawalActionsError::log_without_block_number(&log))?
379381
.as_u64();
380382

381-
let rollup_withdrawal_event_id = log
383+
let transaction_hash = log
382384
.transaction_hash
383385
.ok_or_else(|| GetWithdrawalActionsError::log_without_transaction_hash(&log))?
384-
.to_string();
386+
.encode_hex();
387+
let event_index = log
388+
.log_index
389+
.ok_or_else(|| GetWithdrawalActionsError::log_without_log_index(&log))?
390+
.encode_hex();
391+
392+
let rollup_withdrawal_event_id = format!("{transaction_hash}.{event_index}");
385393

386394
let event = decode_log::<Ics20WithdrawalFilter>(log)
387395
.map_err(GetWithdrawalActionsError::decode_log)?;
@@ -436,10 +444,16 @@ where
436444
.ok_or_else(|| GetWithdrawalActionsError::log_without_block_number(&log))?
437445
.as_u64();
438446

439-
let rollup_withdrawal_event_id = log
447+
let transaction_hash = log
440448
.transaction_hash
441449
.ok_or_else(|| GetWithdrawalActionsError::log_without_transaction_hash(&log))?
442-
.to_string();
450+
.encode_hex();
451+
let event_index = log
452+
.log_index
453+
.ok_or_else(|| GetWithdrawalActionsError::log_without_log_index(&log))?
454+
.encode_hex();
455+
456+
let rollup_withdrawal_event_id = format!("{transaction_hash}.{event_index}");
443457

444458
let event = decode_log::<SequencerWithdrawalFilter>(log)
445459
.map_err(GetWithdrawalActionsError::decode_log)?;
@@ -502,6 +516,11 @@ impl GetWithdrawalActionsError {
502516
fn log_without_transaction_hash(_log: &Log) -> Self {
503517
Self(GetWithdrawalActionsErrorKind::LogWithoutTransactionHash)
504518
}
519+
520+
// XXX: Somehow identify the log?
521+
fn log_without_log_index(_log: &Log) -> Self {
522+
Self(GetWithdrawalActionsErrorKind::LogWithoutLogIndex)
523+
}
505524
}
506525

507526
#[derive(Debug, thiserror::Error)]
@@ -518,6 +537,8 @@ enum GetWithdrawalActionsErrorKind {
518537
LogWithoutBlockNumber,
519538
#[error("log did not contain a transaction hash")]
520539
LogWithoutTransactionHash,
540+
#[error("log did not contain a log index")]
541+
LogWithoutLogIndex,
521542
#[error(transparent)]
522543
CalculateWithdrawalAmount(CalculateWithdrawalAmountError),
523544
}

crates/astria-bridge-withdrawer/tests/blackbox/helpers/mod.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@ pub use self::{
1313
test_bridge_withdrawer::{
1414
assert_actions_eq,
1515
default_sequencer_address,
16-
make_bridge_unlock_action,
17-
make_ics20_withdrawal_action,
16+
make_erc20_bridge_unlock_action,
17+
make_erc20_ics20_withdrawal_action,
18+
make_native_bridge_unlock_action,
19+
make_native_ics20_withdrawal_action,
1820
TestBridgeWithdrawerConfig,
1921
},
2022
};

crates/astria-bridge-withdrawer/tests/blackbox/helpers/test_bridge_withdrawer.rs

+65-5
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@ use astria_core::{
2828
},
2929
},
3030
};
31-
use ethers::types::TransactionReceipt;
31+
use ethers::{
32+
abi::AbiEncode,
33+
types::TransactionReceipt,
34+
};
3235
use futures::Future;
3336
use ibc_types::core::{
3437
channel::ChannelId,
@@ -427,13 +430,66 @@ impl From<Ics20Withdrawal> for SubsetOfIcs20Withdrawal {
427430
}
428431

429432
#[must_use]
430-
pub fn make_bridge_unlock_action(receipt: &TransactionReceipt) -> Action {
433+
pub fn make_native_bridge_unlock_action(receipt: &TransactionReceipt) -> Action {
434+
let denom = default_native_asset();
435+
let rollup_transaction_hash = receipt.transaction_hash.encode_hex();
436+
let event_index = receipt.logs[0].log_index.unwrap().encode_hex();
437+
438+
let inner = BridgeUnlockAction {
439+
to: default_sequencer_address(),
440+
amount: 1_000_000u128,
441+
rollup_block_number: receipt.block_number.unwrap().as_u64(),
442+
rollup_withdrawal_event_id: format!("{rollup_transaction_hash}.{event_index}"),
443+
memo: String::new(),
444+
fee_asset: denom,
445+
bridge_address: default_bridge_address(),
446+
};
447+
Action::BridgeUnlock(inner)
448+
}
449+
450+
#[must_use]
451+
pub fn make_native_ics20_withdrawal_action(receipt: &TransactionReceipt) -> Action {
452+
let timeout_height = IbcHeight::new(u64::MAX, u64::MAX).unwrap();
453+
let timeout_time = make_ibc_timeout_time();
454+
let denom = default_ibc_asset();
455+
let rollup_transaction_hash = receipt.transaction_hash.encode_hex();
456+
let event_index = receipt.logs[0].log_index.unwrap().encode_hex();
457+
458+
let inner = Ics20Withdrawal {
459+
denom: denom.clone(),
460+
destination_chain_address: default_sequencer_address().to_string(),
461+
return_address: default_bridge_address(),
462+
amount: 1_000_000u128,
463+
memo: serde_json::to_string(&Ics20WithdrawalFromRollup {
464+
memo: "nootwashere".to_string(),
465+
rollup_return_address: receipt.from.to_string(),
466+
rollup_block_number: receipt.block_number.unwrap().as_u64(),
467+
rollup_withdrawal_event_id: format!("{rollup_transaction_hash}.{event_index}"),
468+
})
469+
.unwrap(),
470+
fee_asset: denom,
471+
timeout_height,
472+
timeout_time,
473+
source_channel: "channel-0".parse().unwrap(),
474+
bridge_address: Some(default_bridge_address()),
475+
use_compat_address: false,
476+
};
477+
478+
Action::Ics20Withdrawal(inner)
479+
}
480+
481+
#[must_use]
482+
pub fn make_erc20_bridge_unlock_action(receipt: &TransactionReceipt) -> Action {
431483
let denom = default_native_asset();
484+
let rollup_transaction_hash = receipt.transaction_hash.encode_hex();
485+
// use the second event because the erc20 transfer also emits an event
486+
let event_index = receipt.logs[1].log_index.unwrap().encode_hex();
487+
432488
let inner = BridgeUnlockAction {
433489
to: default_sequencer_address(),
434490
amount: 1_000_000u128,
435491
rollup_block_number: receipt.block_number.unwrap().as_u64(),
436-
rollup_withdrawal_event_id: receipt.transaction_hash.to_string(),
492+
rollup_withdrawal_event_id: format!("{rollup_transaction_hash}.{event_index}"),
437493
memo: String::new(),
438494
fee_asset: denom,
439495
bridge_address: default_bridge_address(),
@@ -442,10 +498,14 @@ pub fn make_bridge_unlock_action(receipt: &TransactionReceipt) -> Action {
442498
}
443499

444500
#[must_use]
445-
pub fn make_ics20_withdrawal_action(receipt: &TransactionReceipt) -> Action {
501+
pub fn make_erc20_ics20_withdrawal_action(receipt: &TransactionReceipt) -> Action {
446502
let timeout_height = IbcHeight::new(u64::MAX, u64::MAX).unwrap();
447503
let timeout_time = make_ibc_timeout_time();
448504
let denom = default_ibc_asset();
505+
let rollup_transaction_hash = receipt.transaction_hash.encode_hex();
506+
// use the second event because the erc20 transfer also emits an event
507+
let event_index = receipt.logs[1].log_index.unwrap().encode_hex();
508+
449509
let inner = Ics20Withdrawal {
450510
denom: denom.clone(),
451511
destination_chain_address: default_sequencer_address().to_string(),
@@ -455,7 +515,7 @@ pub fn make_ics20_withdrawal_action(receipt: &TransactionReceipt) -> Action {
455515
memo: "nootwashere".to_string(),
456516
rollup_return_address: receipt.from.to_string(),
457517
rollup_block_number: receipt.block_number.unwrap().as_u64(),
458-
rollup_withdrawal_event_id: receipt.transaction_hash.to_string(),
518+
rollup_withdrawal_event_id: format!("{rollup_transaction_hash}.{event_index}"),
459519
})
460520
.unwrap(),
461521
fee_asset: denom,

crates/astria-bridge-withdrawer/tests/blackbox/main.rs

+35-13
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ use astria_core::protocol::transaction::v1alpha1::Action;
22
use helpers::{
33
assert_actions_eq,
44
default_sequencer_address,
5-
make_bridge_unlock_action,
6-
make_ics20_withdrawal_action,
5+
make_erc20_bridge_unlock_action,
6+
make_erc20_ics20_withdrawal_action,
7+
make_native_bridge_unlock_action,
8+
make_native_ics20_withdrawal_action,
79
signed_tx_from_request,
810
TestBridgeWithdrawerConfig,
911
};
@@ -39,7 +41,7 @@ async fn native_sequencer_withdraw_success() {
3941
)
4042
.await;
4143

42-
assert_contract_receipt_action_matches_broadcast_action::<BridgeUnlock>(
44+
assert_contract_receipt_action_matches_broadcast_action::<BridgeUnlock, NativeAsset>(
4345
&broadcast_guard.received_requests().await,
4446
&receipt,
4547
);
@@ -76,7 +78,7 @@ async fn native_ics20_withdraw_success() {
7678
)
7779
.await;
7880

79-
assert_contract_receipt_action_matches_broadcast_action::<Ics20>(
81+
assert_contract_receipt_action_matches_broadcast_action::<Ics20, NativeAsset>(
8082
&broadcast_guard.received_requests().await,
8183
&receipt,
8284
);
@@ -119,7 +121,7 @@ async fn erc20_sequencer_withdraw_success() {
119121
)
120122
.await;
121123

122-
assert_contract_receipt_action_matches_broadcast_action::<BridgeUnlock>(
124+
assert_contract_receipt_action_matches_broadcast_action::<BridgeUnlock, Erc20>(
123125
&broadcast_guard.received_requests().await,
124126
&receipt,
125127
);
@@ -162,34 +164,54 @@ async fn erc20_ics20_withdraw_success() {
162164
)
163165
.await;
164166

165-
assert_contract_receipt_action_matches_broadcast_action::<Ics20>(
167+
assert_contract_receipt_action_matches_broadcast_action::<Ics20, Erc20>(
166168
&broadcast_guard.received_requests().await,
167169
&receipt,
168170
);
169171
}
170172

171-
trait ActionFromReceipt {
173+
trait ActionFromReceipt<TAsset> {
172174
fn action_from_receipt(receipt: &ethers::types::TransactionReceipt) -> Action;
173175
}
174176

177+
struct NativeAsset;
178+
struct Erc20;
179+
175180
struct BridgeUnlock;
176-
impl ActionFromReceipt for BridgeUnlock {
181+
impl ActionFromReceipt<NativeAsset> for BridgeUnlock {
182+
#[track_caller]
183+
fn action_from_receipt(receipt: &ethers::types::TransactionReceipt) -> Action {
184+
make_native_bridge_unlock_action(receipt)
185+
}
186+
}
187+
188+
impl ActionFromReceipt<Erc20> for BridgeUnlock {
177189
#[track_caller]
178190
fn action_from_receipt(receipt: &ethers::types::TransactionReceipt) -> Action {
179-
make_bridge_unlock_action(receipt)
191+
make_erc20_bridge_unlock_action(receipt)
180192
}
181193
}
182194

183195
struct Ics20;
184-
impl ActionFromReceipt for Ics20 {
196+
impl ActionFromReceipt<NativeAsset> for Ics20 {
197+
#[track_caller]
198+
fn action_from_receipt(receipt: &ethers::types::TransactionReceipt) -> Action {
199+
make_native_ics20_withdrawal_action(receipt)
200+
}
201+
}
202+
203+
impl ActionFromReceipt<Erc20> for Ics20 {
185204
#[track_caller]
186205
fn action_from_receipt(receipt: &ethers::types::TransactionReceipt) -> Action {
187-
make_ics20_withdrawal_action(receipt)
206+
make_erc20_ics20_withdrawal_action(receipt)
188207
}
189208
}
190209

191210
#[track_caller]
192-
fn assert_contract_receipt_action_matches_broadcast_action<T: ActionFromReceipt>(
211+
fn assert_contract_receipt_action_matches_broadcast_action<
212+
TAction: ActionFromReceipt<TAsset>,
213+
TAsset,
214+
>(
193215
received_broadcasts: &[wiremock::Request],
194216
receipt: &ethers::types::TransactionReceipt,
195217
) {
@@ -201,6 +223,6 @@ fn assert_contract_receipt_action_matches_broadcast_action<T: ActionFromReceipt>
201223
.first()
202224
.expect("the signed transaction should contain at least one action");
203225

204-
let expected = T::action_from_receipt(receipt);
226+
let expected = TAction::action_from_receipt(receipt);
205227
assert_actions_eq(&expected, actual);
206228
}

crates/astria-sequencer/src/bridge/bridge_unlock_action.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ impl ActionHandler for BridgeUnlockAction {
3535
"rollup withdrawal event id must be non-empty",
3636
);
3737
ensure!(
38-
self.rollup_withdrawal_event_id.len() <= 64,
39-
"rollup withdrawal event id must not be more than 64 bytes",
38+
self.rollup_withdrawal_event_id.len() <= 256,
39+
"rollup withdrawal event id must not be more than 256 bytes",
4040
);
4141
ensure!(
4242
self.rollup_block_number > 0,

crates/astria-sequencer/src/ibc/ics20_withdrawal.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -164,8 +164,8 @@ impl ActionHandler for action::Ics20Withdrawal {
164164
"rollup withdrawal event id must be non-empty",
165165
);
166166
ensure!(
167-
parsed_bridge_memo.rollup_withdrawal_event_id.len() <= 64,
168-
"rollup withdrawal event id must be no more than 64 bytes",
167+
parsed_bridge_memo.rollup_withdrawal_event_id.len() <= 256,
168+
"rollup withdrawal event id must be no more than 256 bytes",
169169
);
170170
ensure!(
171171
parsed_bridge_memo.rollup_block_number != 0,

0 commit comments

Comments
 (0)