Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add example for encoding and decoding raw transactions #164

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ This repository contains the following examples:
- [x] [Transfer ERC20 token](./examples/transactions/examples/transfer_erc20.rs)
- [x] [Transfer ETH](./examples/transactions/examples/transfer_eth.rs)
- [x] [Sign and send a raw transaction](./examples/transactions/examples/send_raw_transaction.rs)
- [x] [Encode and decode a raw transaction](./examples/transactions/examples/encode_and_decode_raw_transaction.rs)
- [x] [Send EIP-1559 transaction](./examples/transactions/examples/send_eip1559_transaction.rs)
- [x] [Send legacy transaction](./examples/transactions/examples/send_legacy_transaction.rs)
- [x] [Send EIP-4844 transaction](./examples/transactions/examples/send_eip4844_transaction.rs)
Expand Down
102 changes: 102 additions & 0 deletions examples/transactions/examples/encode_and_decode_raw_transaction.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
//! Example of encoding and decoding raw transactions.

use alloy::primitives::keccak256;
use alloy::primitives::private::alloy_rlp::Decodable;
use alloy::primitives::private::alloy_rlp::Encodable;
use alloy::primitives::FixedBytes;
use alloy::primitives::TxKind;
use alloy::primitives::U256;
use alloy::providers::WalletProvider;
use alloy::{
consensus::TxEnvelope,
consensus::{SignableTransaction, TxEip1559},
hex,
network::{EthereumWallet, TransactionBuilder},
primitives::Address,
providers::{Provider, ProviderBuilder},
rpc::types::TransactionRequest,
};
zerosnacks marked this conversation as resolved.
Show resolved Hide resolved
use eyre::Result;

fn build_unsigned_tx(chain_id: u64, to_address: Address) -> TxEip1559 {
let mut tx = TxEip1559::default();
tx.chain_id = chain_id;
tx.nonce = 0;
tx.gas_limit = 21_000;
tx.max_fee_per_gas = 20_000_000_000;
tx.max_priority_fee_per_gas = 1_000_000_000;
tx.to = TxKind::Call(to_address); // Change this to `TxKind::Create` if you'd like to deploy a contract instead
tx.value = U256::from(100);

tx
}
Copy link
Member

Choose a reason for hiding this comment

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

We can use the builder properties of TransactionRequest here

// Build a transaction to send 100 wei from Alice to Bob.
// The `from` field is automatically filled to the first signer's address (Alice).
let tx = TransactionRequest::default()
.with_to(bob)
.with_nonce(0)
.with_chain_id(provider.get_chain_id().await?)
.with_value(U256::from(100))
.with_gas_limit(21_000)
.with_max_priority_fee_per_gas(1_000_000_000)
.with_max_fee_per_gas(20_000_000_000);

Copy link
Author

@junha-ahn junha-ahn Dec 9, 2024

Choose a reason for hiding this comment

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

I couldn't find the RLP-encoding-method in the TransactionRequest struct.

So Thats why I use TxEip1559.

however, I changed the coding style

fn build_unsigned_tx(chain_id: u64, to_address: Address) -> TxEip1559 {
    TxEip1559 {
        chain_id,
        nonce: 0,
        gas_limit: 21_000,
        max_fee_per_gas: 20_000_000_000,
        max_priority_fee_per_gas: 1_000_000_000,
        to: TxKind::Call(to_address), // Change this to `TxKind::Create` if you'd like to deploy a contract instead
        value: U256::from(100),
        ..Default::default()
    }
}

Copy link
Member

Choose a reason for hiding this comment

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

I couldn't find the RLP-encoding-method in the TransactionRequest struct.

there is no RLP-encoding method becasue TransactionRequest cannot be RLP encoded. you should use build to construct and sign a transaction, converting TransactionRequest into TransactionEnvelope


fn unsigned_tx_to_bytes(tx: TxEip1559) -> Vec<u8> {
tx.encoded_for_signing() // To use this, have to import "alloy::primitives::private::alloy_rlp::Encodable"
}

fn bytes_to_unsigned_tx(bytes: Vec<u8>) -> TxEip1559 {
let mut slice = &bytes.as_slice()[1..];
TxEip1559::decode(&mut slice).unwrap() // To use this, have to import "alloy::primitives::private::alloy_rlp::Decodable"
}

async fn sign_tx(tx: TxEip1559, wallet: EthereumWallet) -> TxEnvelope {
let tx_request: TransactionRequest = tx.into();

tx_request.build(&wallet).await.unwrap()
}

fn signed_tx_to_bytes(signed_tx: TxEnvelope) -> Vec<u8> {
let mut encoded = Vec::new();
signed_tx.encode(&mut encoded);
let encoded = &encoded[2..];
encoded.into()
}

fn bytes_to_signed_tx(bytes: Vec<u8>) -> TxEnvelope {
let mut slice = bytes.as_slice();
TxEnvelope::decode(&mut slice).unwrap()
}

fn get_tx_hash_from_signed_tx_bytes(signed_tx_bytes: Vec<u8>) -> FixedBytes<32> {
format!("0x{}", hex::encode(keccak256(signed_tx_bytes.clone())))
.parse::<FixedBytes<32>>()
.unwrap()
}
Copy link
Member

@zerosnacks zerosnacks Dec 9, 2024

Choose a reason for hiding this comment

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

this can all be inlined for clarity, using ? with eyre for conciseness

Copy link
Author

@junha-ahn junha-ahn Dec 9, 2024

Choose a reason for hiding this comment

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

fn bytes_to_unsigned_tx(bytes: Vec<u8>) -> eyre::Result<TxEip1559> {
    let mut slice = &bytes.as_slice()[1..];
    Ok(TxEip1559::decode(&mut slice)?) // To use this, have to import "alloy::primitives::private::alloy_rlp::Decodable"
}

If I will change like above, then I have to handle all Result enum in the main function

image

I think, its not conciseness

zerosnacks marked this conversation as resolved.
Show resolved Hide resolved

#[tokio::main]
async fn main() -> Result<()> {
// Spin up a local Anvil node.
// Ensure `anvil` is available in $PATH.
let provider = ProviderBuilder::new().on_anvil_with_wallet();

// Create two users, Alice and Bob.
let accounts = provider.get_accounts().await?;
let alice = accounts[0];
let bob = accounts[1];

// 1. Build a transaction to send 100 wei from Alice to Bob.
let tx: TxEip1559 = build_unsigned_tx(provider.get_chain_id().await?, bob);

// 2. Encode the unsigned transaction to rlp bytes.
let unsigned_tx_bytes = unsigned_tx_to_bytes(tx.clone());
assert_eq!(tx, bytes_to_unsigned_tx(unsigned_tx_bytes));

// 3. Sign the transaction using the wallet.
let signed_tx = sign_tx(tx, provider.wallet().clone()).await;

// 4. Encode the signed transaction to rlp bytes.
let signed_tx_bytes = signed_tx_to_bytes(signed_tx.clone());
assert_eq!(signed_tx, bytes_to_signed_tx(signed_tx_bytes.clone()));

// 5. Send the raw transaction and retrieve the transaction receipt.
let receipt = provider.send_tx_envelope(signed_tx).await?.get_receipt().await?;
assert_eq!(receipt.from, alice);
assert_eq!(receipt.to, Some(bob));

// 6. Comapre the transaction hash from the signed transaction with the receipt's transaction hash.
let tx_hash = get_tx_hash_from_signed_tx_bytes(signed_tx_bytes.clone());
assert_eq!(tx_hash, receipt.transaction_hash);

Ok(())
}
Loading