Skip to content

Commit 619baf9

Browse files
tgfukudaiFrostizz
authored andcommitted
Checksum address output in log_address event and broadcast files (foundry-rs#3108)
* checksumming log addr * fix lint * checksum addresses in broadcast's json file without transaction request metadata * add comments * add test for format_token in the case of address * fix lint Co-authored-by: tgfukuda <luktiger793@gmail.com>
1 parent 3f699f2 commit 619baf9

File tree

4 files changed

+233
-8
lines changed

4 files changed

+233
-8
lines changed

cli/src/cmd/forge/script/sequence.rs

Lines changed: 206 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::cmd::forge::{init::get_commit_hash, script::verify::VerifyBundle};
33
use cast::{executor::inspector::DEFAULT_CREATE2_DEPLOYER, CallKind};
44
use ethers::{
55
abi::{Abi, Address},
6+
core::utils::to_checksum,
67
prelude::{artifacts::Libraries, ArtifactId, NameOrAddress, TransactionReceipt, TxHash},
78
types::transaction::eip2718::TypedTransaction,
89
};
@@ -28,6 +29,7 @@ const DRY_RUN_DIR: &str = "dry-run";
2829
#[derive(Deserialize, Serialize, Clone)]
2930
pub struct ScriptSequence {
3031
pub transactions: VecDeque<TransactionWithMetadata>,
32+
#[serde(serialize_with = "wrapper::serialize_receipts")]
3133
pub receipts: Vec<TransactionReceipt>,
3234
pub libraries: Vec<String>,
3335
pub pending: Vec<TxHash>,
@@ -254,20 +256,21 @@ impl Drop for ScriptSequence {
254256
pub struct AdditionalContract {
255257
#[serde(rename = "transactionType")]
256258
pub opcode: CallKind,
259+
#[serde(serialize_with = "wrapper::serialize_addr")]
257260
pub address: Address,
258261
#[serde(with = "hex")]
259262
pub init_code: Vec<u8>,
260263
}
261264

262-
#[derive(Deserialize, Serialize, Clone, Default)]
265+
#[derive(Serialize, Deserialize, Clone, Default)]
263266
#[serde(rename_all = "camelCase")]
264267
pub struct TransactionWithMetadata {
265268
pub hash: Option<TxHash>,
266269
#[serde(rename = "transactionType")]
267270
pub opcode: CallKind,
268271
#[serde(default = "default_string")]
269272
pub contract_name: Option<String>,
270-
#[serde(default = "default_address")]
273+
#[serde(default = "default_address", serialize_with = "wrapper::serialize_opt_addr")]
271274
pub contract_address: Option<Address>,
272275
#[serde(default = "default_string")]
273276
pub function: Option<String>,
@@ -441,6 +444,207 @@ fn sig_to_file_name(sig: &str) -> String {
441444
sig.to_string()
442445
}
443446

447+
// wrapper for modifying ethers-rs type serialization
448+
mod wrapper {
449+
use super::*;
450+
use ethers::types::{Bloom, Bytes, Log, H256, U256, U64};
451+
452+
pub fn serialize_addr<S>(addr: &Address, serializer: S) -> Result<S::Ok, S::Error>
453+
where
454+
S: serde::Serializer,
455+
{
456+
serializer.serialize_str(&to_checksum(addr, None))
457+
}
458+
459+
pub fn serialize_opt_addr<S>(opt: &Option<Address>, serializer: S) -> Result<S::Ok, S::Error>
460+
where
461+
S: serde::Serializer,
462+
{
463+
match opt {
464+
Some(addr) => serialize_addr(addr, serializer),
465+
None => serializer.serialize_none(),
466+
}
467+
}
468+
469+
pub fn serialize_vec_with_wrapped<S, T, WrappedType>(
470+
vec: &[T],
471+
serializer: S,
472+
) -> Result<S::Ok, S::Error>
473+
where
474+
S: serde::Serializer,
475+
T: Clone,
476+
WrappedType: serde::Serialize + From<T>,
477+
{
478+
serializer.collect_seq(vec.iter().cloned().map(WrappedType::from))
479+
}
480+
481+
// copied from https://github.com/gakonst/ethers-rs
482+
#[derive(Serialize, Deserialize)]
483+
struct WrappedLog {
484+
/// H160. the contract that emitted the log
485+
#[serde(serialize_with = "serialize_addr")]
486+
pub address: Address,
487+
488+
/// topics: Array of 0 to 4 32 Bytes of indexed log arguments.
489+
/// (In solidity: The first topic is the hash of the signature of the event
490+
/// (e.g. `Deposit(address,bytes32,uint256)`), except you declared the event
491+
/// with the anonymous specifier.)
492+
pub topics: Vec<H256>,
493+
494+
/// Data
495+
pub data: Bytes,
496+
497+
/// Block Hash
498+
#[serde(rename = "blockHash")]
499+
#[serde(skip_serializing_if = "Option::is_none")]
500+
pub block_hash: Option<H256>,
501+
502+
/// Block Number
503+
#[serde(rename = "blockNumber")]
504+
#[serde(skip_serializing_if = "Option::is_none")]
505+
pub block_number: Option<U64>,
506+
507+
/// Transaction Hash
508+
#[serde(rename = "transactionHash")]
509+
#[serde(skip_serializing_if = "Option::is_none")]
510+
pub transaction_hash: Option<H256>,
511+
512+
/// Transaction Index
513+
#[serde(rename = "transactionIndex")]
514+
#[serde(skip_serializing_if = "Option::is_none")]
515+
pub transaction_index: Option<U64>,
516+
517+
/// Integer of the log index position in the block. None if it's a pending log.
518+
#[serde(rename = "logIndex")]
519+
#[serde(skip_serializing_if = "Option::is_none")]
520+
pub log_index: Option<U256>,
521+
522+
/// Integer of the transactions index position log was created from.
523+
/// None when it's a pending log.
524+
#[serde(rename = "transactionLogIndex")]
525+
#[serde(skip_serializing_if = "Option::is_none")]
526+
pub transaction_log_index: Option<U256>,
527+
528+
/// Log Type
529+
#[serde(rename = "logType")]
530+
#[serde(skip_serializing_if = "Option::is_none")]
531+
pub log_type: Option<String>,
532+
533+
/// True when the log was removed, due to a chain reorganization.
534+
/// false if it's a valid log.
535+
#[serde(skip_serializing_if = "Option::is_none")]
536+
pub removed: Option<bool>,
537+
}
538+
impl From<Log> for WrappedLog {
539+
fn from(log: Log) -> Self {
540+
Self {
541+
address: log.address,
542+
topics: log.topics,
543+
data: log.data,
544+
block_hash: log.block_hash,
545+
block_number: log.block_number,
546+
transaction_hash: log.transaction_hash,
547+
transaction_index: log.transaction_index,
548+
log_index: log.log_index,
549+
transaction_log_index: log.transaction_log_index,
550+
log_type: log.log_type,
551+
removed: log.removed,
552+
}
553+
}
554+
}
555+
556+
fn serialize_logs<S: serde::Serializer>(
557+
logs: &[Log],
558+
serializer: S,
559+
) -> Result<S::Ok, S::Error> {
560+
serialize_vec_with_wrapped::<S, Log, WrappedLog>(logs, serializer)
561+
}
562+
563+
// "Receipt" of an executed transaction: details of its execution.
564+
// copied from https://github.com/gakonst/ethers-rs
565+
#[derive(Default, Clone, Serialize, Deserialize)]
566+
pub struct WrappedTransactionReceipt {
567+
/// Transaction hash.
568+
#[serde(rename = "transactionHash")]
569+
pub transaction_hash: H256,
570+
/// Index within the block.
571+
#[serde(rename = "transactionIndex")]
572+
pub transaction_index: U64,
573+
/// Hash of the block this transaction was included within.
574+
#[serde(rename = "blockHash")]
575+
pub block_hash: Option<H256>,
576+
/// Number of the block this transaction was included within.
577+
#[serde(rename = "blockNumber")]
578+
pub block_number: Option<U64>,
579+
/// address of the sender.
580+
#[serde(serialize_with = "serialize_addr")]
581+
pub from: Address,
582+
// address of the receiver. null when its a contract creation transaction.
583+
#[serde(serialize_with = "serialize_opt_addr")]
584+
pub to: Option<Address>,
585+
/// Cumulative gas used within the block after this was executed.
586+
#[serde(rename = "cumulativeGasUsed")]
587+
pub cumulative_gas_used: U256,
588+
/// Gas used by this transaction alone.
589+
///
590+
/// Gas used is `None` if the the client is running in light client mode.
591+
#[serde(rename = "gasUsed")]
592+
pub gas_used: Option<U256>,
593+
/// Contract address created, or `None` if not a deployment.
594+
#[serde(rename = "contractAddress", serialize_with = "serialize_opt_addr")]
595+
pub contract_address: Option<Address>,
596+
/// Logs generated within this transaction.
597+
#[serde(serialize_with = "serialize_logs")]
598+
pub logs: Vec<Log>,
599+
/// Status: either 1 (success) or 0 (failure). Only present after activation of [EIP-658](https://eips.ethereum.org/EIPS/eip-658)
600+
pub status: Option<U64>,
601+
/// State root. Only present before activation of [EIP-658](https://eips.ethereum.org/EIPS/eip-658)
602+
#[serde(default, skip_serializing_if = "Option::is_none")]
603+
pub root: Option<H256>,
604+
/// Logs bloom
605+
#[serde(rename = "logsBloom")]
606+
pub logs_bloom: Bloom,
607+
/// Transaction type, Some(1) for AccessList transaction, None for Legacy
608+
#[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
609+
pub transaction_type: Option<U64>,
610+
/// The price paid post-execution by the transaction (i.e. base fee + priority fee).
611+
/// Both fields in 1559-style transactions are *maximums* (max fee + max priority fee), the
612+
/// amount that's actually paid by users can only be determined post-execution
613+
#[serde(rename = "effectiveGasPrice", default, skip_serializing_if = "Option::is_none")]
614+
pub effective_gas_price: Option<U256>,
615+
}
616+
impl From<TransactionReceipt> for WrappedTransactionReceipt {
617+
fn from(receipt: TransactionReceipt) -> Self {
618+
Self {
619+
transaction_hash: receipt.transaction_hash,
620+
transaction_index: receipt.transaction_index,
621+
block_hash: receipt.block_hash,
622+
block_number: receipt.block_number,
623+
from: receipt.from,
624+
to: receipt.to,
625+
cumulative_gas_used: receipt.cumulative_gas_used,
626+
gas_used: receipt.gas_used,
627+
contract_address: receipt.contract_address,
628+
logs: receipt.logs,
629+
status: receipt.status,
630+
root: receipt.root,
631+
logs_bloom: receipt.logs_bloom,
632+
transaction_type: receipt.transaction_type,
633+
effective_gas_price: receipt.effective_gas_price,
634+
}
635+
}
636+
}
637+
638+
pub fn serialize_receipts<S: serde::Serializer>(
639+
receipts: &[TransactionReceipt],
640+
serializer: S,
641+
) -> Result<S::Ok, S::Error> {
642+
serialize_vec_with_wrapped::<S, TransactionReceipt, wrapper::WrappedTransactionReceipt>(
643+
receipts, serializer,
644+
)
645+
}
646+
}
647+
444648
#[cfg(test)]
445649
mod tests {
446650
use super::*;

evm/src/trace/mod.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use crate::{
1414
pub use decoder::{CallTraceDecoder, CallTraceDecoderBuilder};
1515
use ethers::{
1616
abi::{ethereum_types::BigEndianHash, Address, RawLog},
17+
core::utils::to_checksum,
1718
types::{GethDebugTracingOptions, GethTrace, StructLog, H256, U256},
1819
};
1920
use foundry_common::contracts::{ContractsByAddress, ContractsByArtifact};
@@ -450,12 +451,11 @@ impl Default for CallTrace {
450451

451452
impl fmt::Display for CallTrace {
452453
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
453-
let address =
454-
if f.alternate() { format!("{:?}", self.address) } else { format!("{}", self.address) };
454+
let address = to_checksum(&self.address, None);
455455
if self.created() {
456456
write!(
457457
f,
458-
"[{}] {}{} {}@{:?}",
458+
"[{}] {}{} {}@{}",
459459
self.gas_cost,
460460
Paint::yellow(CALL),
461461
Paint::yellow("new"),

evm/src/trace/utils.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
//! utilities used within tracing
22
33
use crate::decode;
4-
use ethers::abi::{Abi, Address, Function, Token};
4+
use ethers::{
5+
abi::{Abi, Address, Function, Token},
6+
core::utils::to_checksum,
7+
};
58
use foundry_utils::format_token;
69
use std::collections::HashMap;
710

@@ -13,7 +16,7 @@ pub fn label(token: &Token, labels: &HashMap<Address, String>) -> String {
1316
match token {
1417
Token::Address(addr) => {
1518
if let Some(label) = labels.get(addr) {
16-
format!("{}: [{:?}]", label, addr)
19+
format!("{}: [{}]", label, to_checksum(addr, None))
1720
} else {
1821
format_token(token)
1922
}

utils/src/lib.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use ethers_core::{
66
Event, Function, HumanReadableParser, ParamType, RawLog, Token,
77
},
88
types::*,
9+
utils::to_checksum,
910
};
1011
use ethers_etherscan::Client;
1112
use ethers_providers::{Middleware, Provider, ProviderError};
@@ -498,7 +499,7 @@ pub fn format_tokens(tokens: &[Token]) -> impl Iterator<Item = String> + '_ {
498499
// Gets pretty print strings for tokens
499500
pub fn format_token(param: &Token) -> String {
500501
match param {
501-
Token::Address(addr) => format!("{:?}", addr),
502+
Token::Address(addr) => to_checksum(addr, None),
502503
Token::FixedBytes(bytes) => format!("0x{}", hex::encode(bytes)),
503504
Token::Bytes(bytes) => format!("0x{}", hex::encode(bytes)),
504505
Token::Int(num) => format!("{}", I256::from_raw(*num)),
@@ -856,4 +857,21 @@ mod tests {
856857
assert_eq!(parsed.params[2].name, "param2");
857858
assert_eq!(parsed.params[2].value, Token::Address(param2.into()));
858859
}
860+
861+
#[test]
862+
fn test_format_token_addr() {
863+
// copied from testcases in https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md
864+
let eip55 = "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed";
865+
assert_eq!(
866+
format_token(&Token::Address(Address::from_str(&eip55.to_lowercase()).unwrap())),
867+
eip55.to_string()
868+
);
869+
870+
// copied from testcases in https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1191.md
871+
let eip1191 = "0xFb6916095cA1Df60bb79ce92cE3EA74c37c5d359";
872+
assert_ne!(
873+
format_token(&Token::Address(Address::from_str(&eip1191.to_lowercase()).unwrap())),
874+
eip1191.to_string()
875+
);
876+
}
859877
}

0 commit comments

Comments
 (0)