Skip to content

Commit

Permalink
Checksum address output in log_address event and broadcast files (fou…
Browse files Browse the repository at this point in the history
…ndry-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 <[email protected]>
  • Loading branch information
tgfukuda and tgfukuda authored Sep 10, 2022
1 parent 0a16e13 commit 1d1e182
Show file tree
Hide file tree
Showing 4 changed files with 233 additions and 8 deletions.
208 changes: 206 additions & 2 deletions cli/src/cmd/forge/script/sequence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::cmd::forge::{init::get_commit_hash, script::verify::VerifyBundle};
use cast::{executor::inspector::DEFAULT_CREATE2_DEPLOYER, CallKind};
use ethers::{
abi::{Abi, Address},
core::utils::to_checksum,
prelude::{artifacts::Libraries, ArtifactId, NameOrAddress, TransactionReceipt, TxHash},
types::transaction::eip2718::TypedTransaction,
};
Expand All @@ -28,6 +29,7 @@ const DRY_RUN_DIR: &str = "dry-run";
#[derive(Deserialize, Serialize, Clone)]
pub struct ScriptSequence {
pub transactions: VecDeque<TransactionWithMetadata>,
#[serde(serialize_with = "wrapper::serialize_receipts")]
pub receipts: Vec<TransactionReceipt>,
pub libraries: Vec<String>,
pub pending: Vec<TxHash>,
Expand Down Expand Up @@ -254,20 +256,21 @@ impl Drop for ScriptSequence {
pub struct AdditionalContract {
#[serde(rename = "transactionType")]
pub opcode: CallKind,
#[serde(serialize_with = "wrapper::serialize_addr")]
pub address: Address,
#[serde(with = "hex")]
pub init_code: Vec<u8>,
}

#[derive(Deserialize, Serialize, Clone, Default)]
#[derive(Serialize, Deserialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct TransactionWithMetadata {
pub hash: Option<TxHash>,
#[serde(rename = "transactionType")]
pub opcode: CallKind,
#[serde(default = "default_string")]
pub contract_name: Option<String>,
#[serde(default = "default_address")]
#[serde(default = "default_address", serialize_with = "wrapper::serialize_opt_addr")]
pub contract_address: Option<Address>,
#[serde(default = "default_string")]
pub function: Option<String>,
Expand Down Expand Up @@ -441,6 +444,207 @@ fn sig_to_file_name(sig: &str) -> String {
sig.to_string()
}

// wrapper for modifying ethers-rs type serialization
mod wrapper {
use super::*;
use ethers::types::{Bloom, Bytes, Log, H256, U256, U64};

pub fn serialize_addr<S>(addr: &Address, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&to_checksum(addr, None))
}

pub fn serialize_opt_addr<S>(opt: &Option<Address>, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match opt {
Some(addr) => serialize_addr(addr, serializer),
None => serializer.serialize_none(),
}
}

pub fn serialize_vec_with_wrapped<S, T, WrappedType>(
vec: &[T],
serializer: S,
) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
T: Clone,
WrappedType: serde::Serialize + From<T>,
{
serializer.collect_seq(vec.iter().cloned().map(WrappedType::from))
}

// copied from https://github.com/gakonst/ethers-rs
#[derive(Serialize, Deserialize)]
struct WrappedLog {
/// H160. the contract that emitted the log
#[serde(serialize_with = "serialize_addr")]
pub address: Address,

/// topics: Array of 0 to 4 32 Bytes of indexed log arguments.
/// (In solidity: The first topic is the hash of the signature of the event
/// (e.g. `Deposit(address,bytes32,uint256)`), except you declared the event
/// with the anonymous specifier.)
pub topics: Vec<H256>,

/// Data
pub data: Bytes,

/// Block Hash
#[serde(rename = "blockHash")]
#[serde(skip_serializing_if = "Option::is_none")]
pub block_hash: Option<H256>,

/// Block Number
#[serde(rename = "blockNumber")]
#[serde(skip_serializing_if = "Option::is_none")]
pub block_number: Option<U64>,

/// Transaction Hash
#[serde(rename = "transactionHash")]
#[serde(skip_serializing_if = "Option::is_none")]
pub transaction_hash: Option<H256>,

/// Transaction Index
#[serde(rename = "transactionIndex")]
#[serde(skip_serializing_if = "Option::is_none")]
pub transaction_index: Option<U64>,

/// Integer of the log index position in the block. None if it's a pending log.
#[serde(rename = "logIndex")]
#[serde(skip_serializing_if = "Option::is_none")]
pub log_index: Option<U256>,

/// Integer of the transactions index position log was created from.
/// None when it's a pending log.
#[serde(rename = "transactionLogIndex")]
#[serde(skip_serializing_if = "Option::is_none")]
pub transaction_log_index: Option<U256>,

/// Log Type
#[serde(rename = "logType")]
#[serde(skip_serializing_if = "Option::is_none")]
pub log_type: Option<String>,

/// True when the log was removed, due to a chain reorganization.
/// false if it's a valid log.
#[serde(skip_serializing_if = "Option::is_none")]
pub removed: Option<bool>,
}
impl From<Log> for WrappedLog {
fn from(log: Log) -> Self {
Self {
address: log.address,
topics: log.topics,
data: log.data,
block_hash: log.block_hash,
block_number: log.block_number,
transaction_hash: log.transaction_hash,
transaction_index: log.transaction_index,
log_index: log.log_index,
transaction_log_index: log.transaction_log_index,
log_type: log.log_type,
removed: log.removed,
}
}
}

fn serialize_logs<S: serde::Serializer>(
logs: &[Log],
serializer: S,
) -> Result<S::Ok, S::Error> {
serialize_vec_with_wrapped::<S, Log, WrappedLog>(logs, serializer)
}

// "Receipt" of an executed transaction: details of its execution.
// copied from https://github.com/gakonst/ethers-rs
#[derive(Default, Clone, Serialize, Deserialize)]
pub struct WrappedTransactionReceipt {
/// Transaction hash.
#[serde(rename = "transactionHash")]
pub transaction_hash: H256,
/// Index within the block.
#[serde(rename = "transactionIndex")]
pub transaction_index: U64,
/// Hash of the block this transaction was included within.
#[serde(rename = "blockHash")]
pub block_hash: Option<H256>,
/// Number of the block this transaction was included within.
#[serde(rename = "blockNumber")]
pub block_number: Option<U64>,
/// address of the sender.
#[serde(serialize_with = "serialize_addr")]
pub from: Address,
// address of the receiver. null when its a contract creation transaction.
#[serde(serialize_with = "serialize_opt_addr")]
pub to: Option<Address>,
/// Cumulative gas used within the block after this was executed.
#[serde(rename = "cumulativeGasUsed")]
pub cumulative_gas_used: U256,
/// Gas used by this transaction alone.
///
/// Gas used is `None` if the the client is running in light client mode.
#[serde(rename = "gasUsed")]
pub gas_used: Option<U256>,
/// Contract address created, or `None` if not a deployment.
#[serde(rename = "contractAddress", serialize_with = "serialize_opt_addr")]
pub contract_address: Option<Address>,
/// Logs generated within this transaction.
#[serde(serialize_with = "serialize_logs")]
pub logs: Vec<Log>,
/// Status: either 1 (success) or 0 (failure). Only present after activation of [EIP-658](https://eips.ethereum.org/EIPS/eip-658)
pub status: Option<U64>,
/// State root. Only present before activation of [EIP-658](https://eips.ethereum.org/EIPS/eip-658)
#[serde(default, skip_serializing_if = "Option::is_none")]
pub root: Option<H256>,
/// Logs bloom
#[serde(rename = "logsBloom")]
pub logs_bloom: Bloom,
/// Transaction type, Some(1) for AccessList transaction, None for Legacy
#[serde(rename = "type", default, skip_serializing_if = "Option::is_none")]
pub transaction_type: Option<U64>,
/// The price paid post-execution by the transaction (i.e. base fee + priority fee).
/// Both fields in 1559-style transactions are *maximums* (max fee + max priority fee), the
/// amount that's actually paid by users can only be determined post-execution
#[serde(rename = "effectiveGasPrice", default, skip_serializing_if = "Option::is_none")]
pub effective_gas_price: Option<U256>,
}
impl From<TransactionReceipt> for WrappedTransactionReceipt {
fn from(receipt: TransactionReceipt) -> Self {
Self {
transaction_hash: receipt.transaction_hash,
transaction_index: receipt.transaction_index,
block_hash: receipt.block_hash,
block_number: receipt.block_number,
from: receipt.from,
to: receipt.to,
cumulative_gas_used: receipt.cumulative_gas_used,
gas_used: receipt.gas_used,
contract_address: receipt.contract_address,
logs: receipt.logs,
status: receipt.status,
root: receipt.root,
logs_bloom: receipt.logs_bloom,
transaction_type: receipt.transaction_type,
effective_gas_price: receipt.effective_gas_price,
}
}
}

pub fn serialize_receipts<S: serde::Serializer>(
receipts: &[TransactionReceipt],
serializer: S,
) -> Result<S::Ok, S::Error> {
serialize_vec_with_wrapped::<S, TransactionReceipt, wrapper::WrappedTransactionReceipt>(
receipts, serializer,
)
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down
6 changes: 3 additions & 3 deletions evm/src/trace/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use crate::{
pub use decoder::{CallTraceDecoder, CallTraceDecoderBuilder};
use ethers::{
abi::{ethereum_types::BigEndianHash, Address, RawLog},
core::utils::to_checksum,
types::{GethDebugTracingOptions, GethTrace, StructLog, H256, U256},
};
use foundry_common::contracts::{ContractsByAddress, ContractsByArtifact};
Expand Down Expand Up @@ -450,12 +451,11 @@ impl Default for CallTrace {

impl fmt::Display for CallTrace {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let address =
if f.alternate() { format!("{:?}", self.address) } else { format!("{}", self.address) };
let address = to_checksum(&self.address, None);
if self.created() {
write!(
f,
"[{}] {}{} {}@{:?}",
"[{}] {}{} {}@{}",
self.gas_cost,
Paint::yellow(CALL),
Paint::yellow("new"),
Expand Down
7 changes: 5 additions & 2 deletions evm/src/trace/utils.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
//! utilities used within tracing
use crate::decode;
use ethers::abi::{Abi, Address, Function, Token};
use ethers::{
abi::{Abi, Address, Function, Token},
core::utils::to_checksum,
};
use foundry_utils::format_token;
use std::collections::HashMap;

Expand All @@ -13,7 +16,7 @@ pub fn label(token: &Token, labels: &HashMap<Address, String>) -> String {
match token {
Token::Address(addr) => {
if let Some(label) = labels.get(addr) {
format!("{}: [{:?}]", label, addr)
format!("{}: [{}]", label, to_checksum(addr, None))
} else {
format_token(token)
}
Expand Down
20 changes: 19 additions & 1 deletion utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use ethers_core::{
Event, Function, HumanReadableParser, ParamType, RawLog, Token,
},
types::*,
utils::to_checksum,
};
use ethers_etherscan::Client;
use ethers_providers::{Middleware, Provider, ProviderError};
Expand Down Expand Up @@ -498,7 +499,7 @@ pub fn format_tokens(tokens: &[Token]) -> impl Iterator<Item = String> + '_ {
// Gets pretty print strings for tokens
pub fn format_token(param: &Token) -> String {
match param {
Token::Address(addr) => format!("{:?}", addr),
Token::Address(addr) => to_checksum(addr, None),
Token::FixedBytes(bytes) => format!("0x{}", hex::encode(bytes)),
Token::Bytes(bytes) => format!("0x{}", hex::encode(bytes)),
Token::Int(num) => format!("{}", I256::from_raw(*num)),
Expand Down Expand Up @@ -856,4 +857,21 @@ mod tests {
assert_eq!(parsed.params[2].name, "param2");
assert_eq!(parsed.params[2].value, Token::Address(param2.into()));
}

#[test]
fn test_format_token_addr() {
// copied from testcases in https://github.com/ethereum/EIPs/blob/master/EIPS/eip-55.md
let eip55 = "0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed";
assert_eq!(
format_token(&Token::Address(Address::from_str(&eip55.to_lowercase()).unwrap())),
eip55.to_string()
);

// copied from testcases in https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1191.md
let eip1191 = "0xFb6916095cA1Df60bb79ce92cE3EA74c37c5d359";
assert_ne!(
format_token(&Token::Address(Address::from_str(&eip1191.to_lowercase()).unwrap())),
eip1191.to_string()
);
}
}

0 comments on commit 1d1e182

Please sign in to comment.