From a2330c7ce42eb74e8be21875dc8baf02e79ffae8 Mon Sep 17 00:00:00 2001 From: Alex Roan Date: Fri, 28 Feb 2025 14:05:24 +0000 Subject: [PATCH] Domain hash calculated correctly --- Cargo.lock | 213 ++++++++++++++++++++++++++++++++++++++++++++++- safe/Cargo.toml | 3 + safe/src/main.rs | 131 +++++++++++++++++++++++++++-- 3 files changed, 338 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ddc3e1e9..03f487e0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -48,7 +48,7 @@ version = "0.4.3" dependencies = [ "crossbeam-channel", "cyfrin-foundry-compilers", - "derive_more", + "derive_more 0.99.18", "eyre", "ignore", "lazy-regex", @@ -152,7 +152,7 @@ dependencies = [ "bytes", "cfg-if", "const-hex", - "derive_more", + "derive_more 0.99.18", "getrandom", "hex-literal", "itoa", @@ -579,6 +579,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" +dependencies = [ + "num-traits", +] + [[package]] name = "ciborium" version = "0.2.2" @@ -1022,6 +1031,26 @@ dependencies = [ "syn 2.0.72", ] +[[package]] +name = "derive_more" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.72", +] + [[package]] name = "digest" version = "0.9.0" @@ -1189,6 +1218,81 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "ethabi" +version = "18.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7413c5f74cc903ea37386a8965a936cbeb334bd270862fdece542c1b2dcbc898" +dependencies = [ + "ethereum-types", + "hex", + "once_cell", + "regex", + "serde", + "serde_json", + "sha3", + "thiserror", + "uint", +] + +[[package]] +name = "ethbloom" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c22d4b5885b6aa2fe5e8b9329fb8d232bf739e434e6b87347c63bdd00c120f60" +dependencies = [ + "crunchy", + "fixed-hash", + "impl-codec", + "impl-rlp", + "impl-serde", + "scale-info", + "tiny-keccak", +] + +[[package]] +name = "ethereum-types" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02d215cbf040552efcbe99a38372fe80ab9d00268e20012b79fcd0f073edd8ee" +dependencies = [ + "ethbloom", + "fixed-hash", + "impl-codec", + "impl-rlp", + "impl-serde", + "primitive-types", + "scale-info", + "uint", +] + +[[package]] +name = "ethers-core" +version = "2.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82d80cc6ad30b14a48ab786523af33b37f28a8623fc06afd55324816ef18fb1f" +dependencies = [ + "arrayvec", + "bytes", + "chrono", + "const-hex", + "elliptic-curve", + "ethabi", + "generic-array", + "k256", + "num_enum", + "open-fastrlp", + "rand", + "rlp", + "serde", + "serde_json", + "strum 0.26.3", + "tempfile", + "thiserror", + "tiny-keccak", + "unicode-xid", +] + [[package]] name = "eyre" version = "0.6.12" @@ -1824,6 +1928,24 @@ dependencies = [ "parity-scale-codec", ] +[[package]] +name = "impl-rlp" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" +dependencies = [ + "rlp", +] + +[[package]] +name = "impl-serde" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc88fc67028ae3db0c853baa36269d398d5f45b6982f95549ff5def78c935cd" +dependencies = [ + "serde", +] + [[package]] name = "impl-trait-for-tuples" version = "0.2.2" @@ -1964,6 +2086,15 @@ dependencies = [ "sha2", ] +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + [[package]] name = "keccak-asm" version = "0.1.1" @@ -2263,6 +2394,7 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ + "proc-macro-crate", "proc-macro2", "quote", "syn 2.0.72", @@ -2295,6 +2427,31 @@ version = "11.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" +[[package]] +name = "open-fastrlp" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "786393f80485445794f6043fd3138854dd109cc6c4bd1a6383db304c9ce9b9ce" +dependencies = [ + "arrayvec", + "auto_impl", + "bytes", + "ethereum-types", + "open-fastrlp-derive", +] + +[[package]] +name = "open-fastrlp-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "003b2be5c6c53c1cfeb0a238b8a1c3915cd410feb684457a36c10038f764bb1c" +dependencies = [ + "bytes", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "openssl-probe" version = "0.1.5" @@ -2586,6 +2743,9 @@ checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" dependencies = [ "fixed-hash", "impl-codec", + "impl-rlp", + "impl-serde", + "scale-info", "uint", ] @@ -2923,9 +3083,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" dependencies = [ "bytes", + "rlp-derive", "rustc-hex", ] +[[package]] +name = "rlp-derive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33d7b2abe0c340d8797fe2907d3f20d3b5ea5908683618bfe80df7f621f672a" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "ruint" version = "1.12.3" @@ -3094,6 +3266,9 @@ name = "safe" version = "0.1.0" dependencies = [ "clap", + "ethers-core", + "hex", + "semver 1.0.23", ] [[package]] @@ -3105,6 +3280,30 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "scale-info" +version = "2.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346a3b32eba2640d17a9cb5927056b08f3de90f65b72fe09402c2ad07d684d0b" +dependencies = [ + "cfg-if", + "derive_more 1.0.0", + "parity-scale-codec", + "scale-info-derive", +] + +[[package]] +name = "scale-info-derive" +version = "2.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6630024bf739e2179b91fb424b28898baf819414262c5d376677dbff1fe7ebf" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.72", +] + [[package]] name = "scc" version = "2.1.6" @@ -3357,6 +3556,16 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest 0.10.7", + "keccak", +] + [[package]] name = "sha3-asm" version = "0.1.1" diff --git a/safe/Cargo.toml b/safe/Cargo.toml index df950430..7fae02b5 100644 --- a/safe/Cargo.toml +++ b/safe/Cargo.toml @@ -5,3 +5,6 @@ edition = "2021" [dependencies] clap = { version = "4.5.1", features = ["derive"] } +ethers-core = "2.0.13" +hex = "0.4.3" +semver = "1.0.22" diff --git a/safe/src/main.rs b/safe/src/main.rs index dd61f4df..091cff0a 100644 --- a/safe/src/main.rs +++ b/safe/src/main.rs @@ -1,5 +1,11 @@ use clap::{Parser, ValueEnum}; -use std::collections::HashMap; +use ethers_core::{ + abi::{encode, Token}, + types::{Address, U256}, + utils::keccak256, +}; +use semver::Version; +use std::str::FromStr; // Type hash constants const DOMAIN_SEPARATOR_TYPEHASH: &str = @@ -131,6 +137,80 @@ fn validate_ethereum_address(address: &str) -> Result<(), Box Result> { + // Parse the version string + let version = Version::parse(version)?; + let clean_version = Version::new(version.major, version.minor, version.patch); + let version_1_2_0 = Version::new(1, 2, 0); + + // Parse the address + let safe_address = Address::from_str(address)?; + + // Choose the appropriate typehash and encode parameters based on version + let encoded = if clean_version <= version_1_2_0 { + // Legacy format without chainId + encode(&[ + Token::FixedBytes(hex::decode(&DOMAIN_SEPARATOR_TYPEHASH_OLD[2..])?.try_into()?), + Token::Address(safe_address), + ]) + } else { + // New format with chainId + encode(&[ + Token::FixedBytes(hex::decode(&DOMAIN_SEPARATOR_TYPEHASH[2..])?.try_into()?), + Token::Uint(U256::from(chain_id)), + Token::Address(safe_address), + ]) + }; + + let hash = keccak256(encoded); + Ok(format!("0x{}", hex::encode(hash))) +} + +/// Helper function to calculate the message hash based on transaction parameters +fn calculate_message_hash( + to: &str, + value: &str, + data: &str, + operation: &str, + safe_tx_gas: &str, + base_gas: &str, + gas_price: &str, + gas_token: &str, + refund_receiver: &str, + nonce: u64, + version: &str, +) -> Result> { + // TODO: Implement message hash calculation + Ok("0x0000000000000000000000000000000000000000000000000000000000000000".to_string()) +} + +/// Helper function to calculate the final safe transaction hash +fn calculate_final_hash( + domain_hash: &str, + message_hash: &str, +) -> Result> { + // TODO: Implement final hash calculation + Ok("0x0000000000000000000000000000000000000000000000000000000000000000".to_string()) +} + +/// Helper function to format the hash output +fn format_hash_output( + domain_hash: &str, + message_hash: &str, + safe_tx_hash: &str, +) -> Result<(), Box> { + // TODO: Implement proper formatting of the hashes + println!("Domain hash: {}", domain_hash); + println!("Message hash: {}", message_hash); + println!("Safe transaction hash: {}", safe_tx_hash); + Ok(()) +} + fn calculate_safe_hashes(args: Args) -> Result<(), Box> { // Validate Ethereum addresses validate_ethereum_address(&args.address)?; @@ -138,12 +218,30 @@ fn calculate_safe_hashes(args: Args) -> Result<(), Box> { validate_ethereum_address(&args.gas_token)?; validate_ethereum_address(&args.refund_receiver)?; - // TODO: Implement the actual hash calculation logic - // This will include: - // 1. Calculate domain hash based on chain ID and verifying contract - // 2. Calculate message hash based on transaction parameters - // 3. Calculate final safe transaction hash - // 4. Print results in the same format as the bash script + // Calculate domain hash + let domain_hash = + calculate_domain_hash(args.network.chain_id(), &args.address, &args.safe_version)?; + + // Calculate message hash + let message_hash = calculate_message_hash( + &args.to, + &args.value, + &args.data, + &args.operation, + &args.safe_tx_gas, + &args.base_gas, + &args.gas_price, + &args.gas_token, + &args.refund_receiver, + args.nonce, + &args.safe_version, + )?; + + // Calculate final safe transaction hash + let safe_tx_hash = calculate_final_hash(&domain_hash, &message_hash)?; + + // Format and print the results + format_hash_output(&domain_hash, &message_hash, &safe_tx_hash)?; Ok(()) } @@ -156,3 +254,22 @@ fn main() { std::process::exit(1); } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_calculate_domain_hash() -> Result<(), Box> { + let chain_id = Network::Ethereum.chain_id(); + let address = "0x1c694Fc3006D81ff4a56F97E1b99529066a23725"; + let version = "1.3.0"; // Default version from Args struct + + let domain_hash = calculate_domain_hash(chain_id, address, version)?; + assert_eq!( + domain_hash, + "0x1655e94a9bcc5a957daa1acae692b4c22e7aaf146b4deb9194f8221d2f09d8c3" + ); + Ok(()) + } +}