Skip to content

Conversation

@ruvaag
Copy link
Contributor

@ruvaag ruvaag commented Sep 5, 2023

Motivation

Resolve #4752 and implement cast decode to decode raw EIP2718 transaction payloads

Solution

  • Implement new clap subcommand Decode
  • Implement SimpleCast::decode_tx that uses ethers_core::utils::rlp to decode raw eip2718 signed txns to ethers_core::types::transaction::eip2718::TypedTransaction and Signature
  • Pretty print decoded txn details

@ruvaag
Copy link
Contributor Author

ruvaag commented Sep 5, 2023

@Evalir would really appreciate a review here. I think I'm close to getting this over the line and only struggling with the final step of the solution described above i.e. pretty-printing the decoded output.

This is what the current output looks using serde_json::to_string_pretty:

> cast decode 0x02f8f582a86a82058d8459682f008508351050808303fd84948e42f2f4101563bf679975178e880fd87d3efd4e80b884659ac74b00000000000000000000000080f0c1c49891dcfdd40b6e0f960f84e6042bcb6f000000000000000000000000b97ef9ef8734c71904d8002f8b6bc66dd9c48a6e00000000000000000000000000000000000000000000000000000000007ff4e20000000000000000000000000000000000000000000000000000000000000064c001a05d429597befe2835396206781b199122f2e8297327ed4a05483339e7a8b2022aa04c23a7f70fb29dda1b4ee342fb10a625e9b8ddc6a603fb4e170d4f6f37700cb8
Transaction: {
  "type": "0x02",
  "from": "0xddbfd6c43d6412b61dbbe463934b69f8c17d9430",
  "to": "0x8e42f2f4101563bf679975178e880fd87d3efd4e",
  "gas": "0x3fd84",
  "value": "0x0",
  "data": "0x659ac74b00000000000000000000000080f0c1c49891dcfdd40b6e0f960f84e6042bcb6f000000000000000000000000b97ef9ef8734c71904d8002f8b6bc66dd9c48a6e00000000000000000000000000000000000000000000000000000000007ff4e20000000000000000000000000000000000000000000000000000000000000064",
  "nonce": "0x58d",
  "accessList": [],
  "maxPriorityFeePerGas": "0x59682f00",
  "maxFeePerGas": "0x835105080"
}
Signature: {
  "r": "0x5d429597befe2835396206781b199122f2e8297327ed4a05483339e7a8b2022a",
  "s": "0x4c23a7f70fb29dda1b4ee342fb10a625e9b8ddc6a603fb4e170d4f6f37700cb8",
  "v": 1
}

ethers_core::types::transaction::eip2718::TypedTransaction doesn't implement fmt::Display so I'm not sure how best to print this out in the desired way. An alternative could be to pretty print thefmt::Debug outputs of TypedTransaction and Signature which would look something like this:

> cast decode 0x02f8f582a86a82058d8459682f008508351050808303fd84948e42f2f4101563bf679975178e880fd87d3efd4e80b884659ac74b00000000000000000000000080f0c1c49891dcfdd40b6e0f960f84e6042bcb6f000000000000000000000000b97ef9ef8734c71904d8002f8b6bc66dd9c48a6e00000000000000000000000000000000000000000000000000000000007ff4e20000000000000000000000000000000000000000000000000000000000000064c001a05d429597befe2835396206781b199122f2e8297327ed4a05483339e7a8b2022aa04c23a7f70fb29dda1b4ee342fb10a625e9b8ddc6a603fb4e170d4f6f37700cb8
Eip1559(
    Eip1559TransactionRequest {
        from: Some(
            0xddbfd6c43d6412b61dbbe463934b69f8c17d9430,
        ),
        to: Some(
            Address(
                0x8e42f2f4101563bf679975178e880fd87d3efd4e,
            ),
        ),
        gas: Some(
            261508,
        ),
        value: Some(
            0,
        ),
        data: Some(
            Bytes(0x659ac74b00000000000000000000000080f0c1c49891dcfdd40b6e0f960f84e6042bcb6f000000000000000000000000b97ef9ef8734c71904d8002f8b6bc66dd9c48a6e00000000000000000000000000000000000000000000000000000000007ff4e20000000000000000000000000000000000000000000000000000000000000064),
        ),
        nonce: Some(
            1421,
        ),
        access_list: AccessList(
            [],
        ),
        max_priority_fee_per_gas: Some(
            1500000000,
        ),
        max_fee_per_gas: Some(
            35250000000,
        ),
        chain_id: Some(
            43114,
        ),
    },
)
Signature {
    r: 42182739275790655586253530586324480569063940514059565326253468982835405783594,
    s: 34438775391995392764448454937167654498428095335712619789751606417593983831224,
    v: 1,
}

There's probably a third way - construct a custom template string which would replicate the desired format exactly, but I doubt if this is the best way to go since it tightly couples SimpleCast::decode_tx and TypedTransaction.

Any upstream modification of fields, new types of txns, and even potential migration of foundry away from ethers-rs itself would add an maintenance overhead. Additionally with three variants of the TypedTransaction Enum, each with multiple fields it would make the decode_tx function very bulky.

Curious to hear any thoughts and suggestions.

P.S - I'm aware the PR is missing proper comments, will add those before finalising it. Apologies if i'm missing something simple... only started with rust a few weeks back.

Copy link
Member

@mattsse mattsse left a comment

Choose a reason for hiding this comment

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

a few nits

Copy link
Member

@mattsse mattsse left a comment

Choose a reason for hiding this comment

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

thanks, last nit

Comment on lines 496 to 500
// Serialise tx and sig as json strings and reformat to print as a single json object
let mut tx = serde_json::to_string_pretty(&tx)?;
tx.truncate(tx.len() - 2);
let sig = serde_json::to_string_pretty(&sig)?[2..].to_string();
println!("{},\n{}", tx, sig);
Copy link
Member

Choose a reason for hiding this comment

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

nit: both can be serialized into to_value and then merged like this:

fn merge(a: &mut Value, b: Value) {
    match (a, b) {
        (a @ &mut Value::Object(_), Value::Object(b)) => {
            let a = a.as_object_mut().unwrap();
            for (k, v) in b {
                merge(a.entry(k).or_insert(Value::Null), v);
            }
        }
        (a, b) => *a = b,
    }
}

https://stackoverflow.com/questions/47070876/how-can-i-merge-two-json-objects-with-rust

since we know the signature has no nested objects we don't need the recursive step

Copy link
Contributor Author

Choose a reason for hiding this comment

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

super neat – thanks for pointing this out. will push a fix in just a bit

Copy link
Contributor Author

@ruvaag ruvaag Sep 6, 2023

Choose a reason for hiding this comment

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

just pushed and refactord it to be more concise:

    // Serialize tx, sig and constructed a merged json string
    let mut tx = serde_json::to_value(&tx)?;
    let tx_map = tx.as_object_mut().unwrap();

    serde_json::to_value(sig)?.as_object().unwrap().iter().for_each(|(k, v)| {
        tx_map.entry(k).or_insert(v.clone());
    });

@mattsse mattsse marked this pull request as ready for review September 5, 2023 23:20
@ruvaag ruvaag requested a review from mattsse September 6, 2023 10:48
Copy link
Member

@mattsse mattsse left a comment

Choose a reason for hiding this comment

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

lgtm

@mattsse
Copy link
Member

mattsse commented Sep 6, 2023

gas diffs unrelated

@mattsse mattsse merged commit 80c0347 into foundry-rs:master Sep 6, 2023
@ruvaag ruvaag deleted the cast-decode branch September 6, 2023 15:32
mikelodder7 pushed a commit to LIT-Protocol/foundry that referenced this pull request Sep 12, 2023
* feat: cast decode can decode raw eip2718 txns

* fix: refactor impl, reformat result, qol changes

* fix: failing doctests

* refactor: merged json output object
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Decode raw signed txs

2 participants