Skip to content

Commit

Permalink
Merge pull request #105 from movementlabsxyz/andygolay/no-bcs-for-vecu8
Browse files Browse the repository at this point in the history
Native bridge: Modify param serialization to make bridge transfer IDs match on Move and Solidity sides in both L1 -> L2 and L2 -> L1 directions
  • Loading branch information
0xmovses authored Dec 2, 2024
2 parents beafb1f + 0da24a0 commit 903d4b3
Show file tree
Hide file tree
Showing 6 changed files with 427 additions and 96 deletions.
153 changes: 114 additions & 39 deletions aptos-move/e2e-move-tests/src/tests/bridge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,66 @@ struct NativeBridgeTransferInitiatedEvent {
nonce: u64,
}

#[derive(Debug, Serialize, Deserialize, Eq, PartialEq)]
struct NativeBridgeTransferCompletedEvent {
bridge_transfer_id: Vec<u8>,
initiator: Vec<u8>,
recipient: AccountAddress,
amount: u64,
nonce: u64,
}

fn hex_to_bytes(input: Vec<u8>) -> Vec<u8> {
let mut result = Vec::new();
assert!(input.len() % 2 == 0, "Input length must be even for valid hex");

let mut i = 0;
while i < input.len() {
let high_nibble = ascii_hex_to_u8(input[i]);
let low_nibble = ascii_hex_to_u8(input[i + 1]);
let byte = (high_nibble << 4) | low_nibble;
result.push(byte);
i += 2;
}

result
}

fn ascii_hex_to_u8(ch: u8) -> u8 {
match ch {
b'0'..=b'9' => ch - b'0',
b'A'..=b'F' => ch - b'A' + 10,
b'a'..=b'f' => ch - b'a' + 10,
_ => panic!("Invalid hex character: {}", ch),
}
}

fn normalize_to_32_bytes(value: Vec<u8>) -> Vec<u8> {
let mut meaningful = Vec::new();
let mut i = 0;

// Remove trailing zeroes
while i < value.len() {
if value[i] != 0 {
meaningful.push(value[i]);
}
i += 1;
}

let mut result = Vec::with_capacity(32);
let padding_length = 32 - meaningful.len();

// Pad with zeros on the left
for _ in 0..padding_length {
result.push(0);
}

// Append the meaningful bytes
result.extend_from_slice(&meaningful);

result
}

#[test]
// A bridge is initiated with said amount to recipient on the destination chain
// A relayer confirms that the initiate bridge transfer is successful and validates the details
Expand All @@ -228,9 +288,9 @@ fn test_native_bridge_initiate() {
native_bridge_feature(&mut harness);
run_mint_burn_caps_native(&mut harness);

let initiator = harness.new_account_at(AccountAddress::from_hex_literal("0xCAFE").unwrap());
let recipient = b"32Be343B94f860124dC4fEe278FDCBD38C102D88".to_vec();
let amount = 1_000_000; // 0.1
let initiator = harness.new_account_at(AccountAddress::from_hex_literal("0x726563697069656e740000000000000000000000000000000000000000000000").unwrap());
let recipient = b"5B38Da6a701c568545dCfcB03FcB875f56beddC4".to_vec();
let amount = 100; // 0.1

let original_balance = harness.read_aptos_balance(initiator.address());
let gas_used = harness.evaluate_entry_function_gas(&initiator,
Expand Down Expand Up @@ -258,11 +318,20 @@ fn test_native_bridge_initiate() {

let mut combined_bytes = Vec::new();

// Append serialized values to `combined_bytes`
// Append serialized values to `combined_bytes` in the same way as in the Move function
// Serialize initiator as BCS bytes
combined_bytes.extend(bcs::to_bytes(&initiator).expect("Failed to serialize initiator"));
combined_bytes.extend(bcs::to_bytes(&recipient).expect("Failed to serialize recipient"));
combined_bytes.extend(bcs::to_bytes(&amount).expect("Failed to serialize amount"));
combined_bytes.extend(bcs::to_bytes(&nonce).expect("Failed to serialize nonce"));

// Convert recipient from hex to bytes
let recipient_bytes = hex::decode(String::from_utf8(recipient).expect("Invalid UTF-8 recipient"))
.expect("Failed to decode recipient hex");
combined_bytes.extend(recipient_bytes);

// Pad amount and nonce to 32 bytes
let amount_bytes = normalize_to_32_bytes(bcs::to_bytes(&amount).expect("Failed to serialize amount"));
let nonce_bytes = normalize_to_32_bytes(bcs::to_bytes(&nonce).expect("Failed to serialize nonce"));
combined_bytes.extend(amount_bytes);
combined_bytes.extend(nonce_bytes);

// Compute keccak256 hash using tiny-keccak
let mut hasher = Keccak::v256();
Expand All @@ -284,42 +353,24 @@ fn test_native_bridge_complete() {
native_bridge_feature(&mut harness);
run_mint_burn_caps_native(&mut harness);

let initiator = harness.new_account_at(AccountAddress::from_hex_literal("0xCAFE").unwrap());
let recipient = b"32Be343B94f860124dC4fEe278FDCBD38C102D88".to_vec();
let amount = 1_000_000; // 0.1

let original_balance = harness.read_aptos_balance(initiator.address());
let gas_used = harness.evaluate_entry_function_gas(&initiator,
str::parse("0x1::native_bridge::initiate_bridge_transfer").unwrap(),
vec![],
vec![
MoveValue::vector_u8(recipient.clone()).simple_serialize().unwrap(),
MoveValue::U64(amount).simple_serialize().unwrap(),
],);

let gas_used = gas_used * harness.default_gas_unit_price;
let new_balance = harness.read_aptos_balance(initiator.address());
assert_eq!(original_balance - amount - gas_used, new_balance);

let events = harness.get_events();
let bridge_transfer_initiated_event_tag = TypeTag::from_str("0x1::native_bridge::BridgeTransferInitiatedEvent").unwrap();
let bridge_transfer_initiated_event = events.iter().find(|element| element.type_tag() == &bridge_transfer_initiated_event_tag).unwrap();
let bridge_transfer_initiated_event = bcs::from_bytes::<NativeBridgeTransferInitiatedEvent>(bridge_transfer_initiated_event.event_data()).unwrap();
let relayer = harness.new_account_at(AccountAddress::from_hex_literal("0x1").unwrap());

let bridge_transfer_id = bridge_transfer_initiated_event.bridge_transfer_id;
let initiator = bridge_transfer_initiated_event.initiator;
let recipient = bridge_transfer_initiated_event.recipient;
let amount = bridge_transfer_initiated_event.amount;
let nonce = bridge_transfer_initiated_event.nonce;
let initiator = b"5B38Da6a701c568545dCfcB03FcB875f56beddC4".to_vec();
let recipient = harness.new_account_at(AccountAddress::from_hex_literal("0x726563697069656e740000000000000000000000000000000000000000000000").unwrap());
let amount = 100; // 0.1
let nonce = 1;

let mut combined_bytes = Vec::new();

// Append serialized values to `combined_bytes`
combined_bytes.extend(bcs::to_bytes(&initiator).expect("Failed to serialize initiator"));
combined_bytes.extend(bcs::to_bytes(&recipient).expect("Failed to serialize recipient"));
combined_bytes.extend(bcs::to_bytes(&amount).expect("Failed to serialize amount"));
combined_bytes.extend(bcs::to_bytes(&nonce).expect("Failed to serialize nonce"));


// Convert recipient from hex to bytes
let initiator_bytes = hex::decode(String::from_utf8(initiator.clone()).expect("Invalid UTF-8 recipient"))
.expect("Failed to decode recipient hex");
combined_bytes.extend(initiator_bytes);
combined_bytes.extend(bcs::to_bytes(&recipient.address()).expect("Failed to serialize recipient"));
combined_bytes.extend(normalize_to_32_bytes(bcs::to_bytes(&amount).expect("Failed to serialize amount")));
combined_bytes.extend(normalize_to_32_bytes(bcs::to_bytes(&nonce).expect("Failed to serialize nonce")));
// Compute keccak256 hash using tiny-keccak
let mut hasher = Keccak::v256();
hasher.update(&combined_bytes);
Expand All @@ -328,7 +379,31 @@ fn test_native_bridge_complete() {
hasher.finalize(&mut hash);

// Compare the computed hash to `bridge_transfer_id`
assert!(bridge_transfer_id == hash.to_vec());
let original_balance = harness.read_aptos_balance(relayer.address());
let gas_used = harness.evaluate_entry_function_gas(&relayer,
str::parse("0x1::native_bridge::complete_bridge_transfer").unwrap(),
vec![],
vec![
MoveValue::vector_u8(hash.to_vec()).simple_serialize().unwrap(),
MoveValue::vector_u8(initiator.clone()).simple_serialize().unwrap(),
MoveValue::Address(*recipient.address()).simple_serialize().unwrap(),
MoveValue::U64(amount).simple_serialize().unwrap(),
MoveValue::U64(nonce).simple_serialize().unwrap(),
],);

let gas_used = gas_used * harness.default_gas_unit_price;
let new_balance = harness.read_aptos_balance(relayer.address());
assert_eq!(original_balance - gas_used, new_balance);

let events = harness.get_events();
let bridge_transfer_completed_event_tag = TypeTag::from_str("0x1::native_bridge::BridgeTransferCompletedEvent").unwrap();
let bridge_transfer_completed_event = events.iter().find(|element| element.type_tag() == &bridge_transfer_completed_event_tag).unwrap();
let bridge_transfer_completed_event = bcs::from_bytes::<NativeBridgeTransferCompletedEvent>(bridge_transfer_completed_event.event_data()).unwrap();

let bridge_transfer_id = bridge_transfer_completed_event.bridge_transfer_id;

assert_eq!(bridge_transfer_id, hash.to_vec());

}

#[test]
Expand Down
29 changes: 29 additions & 0 deletions aptos-move/framework/aptos-framework/doc/atomic_bridge.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
- [Constants](#@Constants_0)
- [Function `ethereum_address`](#0x1_ethereum_ethereum_address)
- [Function `ethereum_address_no_eip55`](#0x1_ethereum_ethereum_address_no_eip55)
- [Function `get_inner_ethereum_address`](#0x1_ethereum_get_inner_ethereum_address)
- [Function `to_lowercase`](#0x1_ethereum_to_lowercase)
- [Function `to_eip55_checksumed_address`](#0x1_ethereum_to_eip55_checksumed_address)
- [Function `get_inner`](#0x1_ethereum_get_inner)
Expand Down Expand Up @@ -150,6 +151,34 @@ Returns a new <code><a href="atomic_bridge.md#0x1_ethereum_EthereumAddress">Ethe



</details>

<a id="0x1_ethereum_get_inner_ethereum_address"></a>

## Function `get_inner_ethereum_address`

Gets the inner vector of an <code><a href="atomic_bridge.md#0x1_ethereum_EthereumAddress">EthereumAddress</a></code>.

@param ethereum_address A 40-byte vector of unsigned 8-bit integers (hexadecimal format).
@return The vector<u8> inner value of the EthereumAddress


<pre><code><b>public</b> <b>fun</b> <a href="atomic_bridge.md#0x1_ethereum_get_inner_ethereum_address">get_inner_ethereum_address</a>(ethereum_address: <a href="atomic_bridge.md#0x1_ethereum_EthereumAddress">ethereum::EthereumAddress</a>): <a href="../../aptos-stdlib/../move-stdlib/doc/vector.md#0x1_vector">vector</a>&lt;u8&gt;
</code></pre>



<details>
<summary>Implementation</summary>


<pre><code><b>public</b> <b>fun</b> <a href="atomic_bridge.md#0x1_ethereum_get_inner_ethereum_address">get_inner_ethereum_address</a>(ethereum_address: <a href="atomic_bridge.md#0x1_ethereum_EthereumAddress">EthereumAddress</a>): <a href="../../aptos-stdlib/../move-stdlib/doc/vector.md#0x1_vector">vector</a>&lt;u8&gt; {
ethereum_address.inner
}
</code></pre>



</details>

<a id="0x1_ethereum_to_lowercase"></a>
Expand Down
Loading

0 comments on commit 903d4b3

Please sign in to comment.