Skip to content

Commit

Permalink
Use named tuple for receipt and add functionality to decode receipt logs
Browse files Browse the repository at this point in the history
  • Loading branch information
danhper committed Aug 5, 2024
1 parent b2873f3 commit 4999ae1
Show file tree
Hide file tree
Showing 9 changed files with 254 additions and 134 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Changelog

## Not released

### Features

* Decode logs returned by `Transaction.getReceipt()` when available in ABI

### Other changes

* Drop `Receipt` type and use `NamedTuple` instead

## v0.1.2 (2024-08-04)

### Features
Expand Down
42 changes: 15 additions & 27 deletions docs/src/builtin_methods.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,40 +110,28 @@ Returns the receipt of the transaction.
The function will wait for the transaction for up to `timeout` seconds, or 30 seconds by default.

```javascript
>> tx = Transaction(0xad8a96e212d8d95187bdbb06b44f63ab1a0718a4b4d9086cbd229b6bffc43089)
>> tx = Transaction(0xfb89e2333b81f2751eedaf2aeffb787917d42ea6ea7c5afd4d45371f3f1b8079)
>> tx.getReceipt()
TransactionReceipt { tx_hash: 0xad8a96e212d8d95187bdbb06b44f63ab1a0718a4b4d9086cbd229b6bffc43089, block_hash: 0xec8704cc21a047e95287adad403138739b7b79fae7fd15aa14f1d315aae1db1f, block_number: 20387117, status: true, gas_used: 34742, gas_price: 3552818949 }
Receipt { txHash: 0xfb89e2333b81f2751eedaf2aeffb787917d42ea6ea7c5afd4d45371f3f1b8079, blockHash: 0xd82cbdd9aba2827815d8db2e0665b1f54e6decc4f59042e53344f6562301e55b, blockNumber: 18735365, status: true, gasUsed: 54017, gasPrice: 71885095452, logs: [Log { address: 0xe07F9D810a48ab5c3c914BA3cA53AF14E4491e8A, topics: [0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef, 0x00000000000000000000000035641673a0ce64f644ad2f395be19668a06a5616, 0x0000000000000000000000009748a9de5a2d81e96c2070f7f0d1d128bbb4d3c4], data: 0x00000000000000000000000000000000000000000000007b1638669932a6793d, args: Transfer { from: 0x35641673A0Ce64F644Ad2f395be19668A06A5616, to: 0x9748a9dE5A2D81e96C2070f7F0D1d128BbB4d3c4, value: 2270550663541970860349 } }] }
```

## `Receipt` methods
The receipt is a named tuple with the following fields:

### `Receipt.status -> bool`
- `txHash` (`bytes32`): the hash of the transaction.
- `blockHash` (`bytes32`): the hash of the block containing the transaction.
- `blockNumber` (`uint256`): the number of the block containing the transaction.
- `status` (`bool`): the status of the transaction.
- `gasUsed` (`uint256`): the amount of gas used by the transaction.
- `gasPrice` (`uint256`): the gas price of the transaction.
- `logs` (`NamedTuple[]`): the logs of the transaction.

Returns the status of the transaction.
Logs is an array of named tuples with the following fields:

### `Receipt.tx_hash -> bytes32`
- `address` (`address`): the address of the contract that emitted the log.
- `topics` (`bytes32[]`): the topics of the log.
- `data` (`bytes`): the data of the log.
- `args` (`NamedTuple`): the decoded arguments of the log, if the event is known (present in one of the loaded ABIs).

Returns the hash of the transaction.

### `Receipt.gas_used -> uint256`

Returns the amount of gas used by the transaction.

### `Receipt.effective_gas_price -> uint256`

Returns the gas price of the transaction.

### `Receipt.block_number -> uint256`

Returns the block number of the transaction.

### `Receipt.block_hash -> uint256`

Returns the block hash of the transaction.

### `Receipt.logs -> array`

Returns the logs of the transaction.

## `Contract` static methods

Expand Down
3 changes: 2 additions & 1 deletion src/interpreter/builtins/receipt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use lazy_static::lazy_static;

use crate::interpreter::{
functions::{AsyncMethod, FunctionDef, FunctionParam},
utils::receipt_to_value,
Env, Type, Value,
};

Expand All @@ -31,7 +32,7 @@ fn wait_for_receipt<'a>(
.with_timeout(Some(std::time::Duration::from_secs(timeout)))
.get_receipt()
.await?;
Ok(Value::TransactionReceipt(receipt.into()))
receipt_to_value(env, receipt)
}
.boxed()
}
Expand Down
8 changes: 3 additions & 5 deletions src/interpreter/builtins/repl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use crate::{
functions::{
AsyncMethod, AsyncProperty, FunctionDef, FunctionParam, SyncMethod, SyncProperty,
},
ContractInfo, Env, Type, Value,
Env, Type, Value,
},
loaders,
};
Expand Down Expand Up @@ -99,8 +99,7 @@ fn load_abi(env: &mut Env, _receiver: &Value, args: &[Value]) -> Result<Value> {
_ => bail!("loadAbi: invalid arguments"),
};
let abi = loaders::file::load_abi(filepath, key)?;
let contract_info = ContractInfo(name.to_string(), abi);
env.set_type(name, Type::Contract(contract_info.clone()));
env.add_contract(name, abi);
Ok(Value::Null)
}

Expand All @@ -116,8 +115,7 @@ fn fetch_abi<'a>(
let etherscan_config = env.config.get_etherscan_config(chain_id)?;
let abi =
loaders::etherscan::load_abi(etherscan_config, &address.to_string()).await?;
let contract_info = ContractInfo(name.to_string(), abi);
env.set_type(name, Type::Contract(contract_info.clone()));
let contract_info = env.add_contract(name, abi);
Ok(Value::Contract(contract_info, *address))
}
_ => bail!("fetchAbi: invalid arguments"),
Expand Down
28 changes: 26 additions & 2 deletions src/interpreter/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ use url::Url;

use alloy::{
eips::BlockId,
json_abi::{Event, JsonAbi},
network::{AnyNetwork, Ethereum, EthereumWallet, NetworkWallet, TxSigner},
primitives::Address,
primitives::{Address, B256},
providers::{Provider, ProviderBuilder},
signers::{ledger::HDPath, Signature},
transports::http::{Client, Http},
Expand All @@ -19,7 +20,7 @@ use coins_ledger::{transports::LedgerAsync, Ledger};

use crate::{interpreter::Config, vendor::ledger_signer::LedgerSigner};

use super::{evaluate_expression, types::Type, Value};
use super::{evaluate_expression, types::Type, ContractInfo, Value};

pub struct Env {
variables: Vec<HashMap<String, Value>>,
Expand All @@ -28,6 +29,7 @@ pub struct Env {
wallet: Option<EthereumWallet>,
ledger: Option<Arc<Mutex<Ledger>>>,
block_id: BlockId,
events: HashMap<B256, Event>,
pub config: Config,
}

Expand All @@ -44,6 +46,7 @@ impl Env {
wallet: None,
ledger: None,
block_id: BlockId::latest(),
events: HashMap::new(),
config,
}
}
Expand All @@ -64,6 +67,27 @@ impl Env {
self.config.debug
}

pub fn get_event(&self, selector: &B256) -> Option<&Event> {
self.events.get(selector)
}

pub fn add_contract(&mut self, name: &str, abi: JsonAbi) -> ContractInfo {
for event in abi.events() {
self.register_event(event.clone());
}
let contract_info = ContractInfo(name.to_string(), abi);
self.set_type(name, Type::Contract(contract_info.clone()));
contract_info
}

pub fn list_events(&mut self) -> Vec<&Event> {
self.events.values().collect()
}

pub fn register_event(&mut self, event: Event) {
self.events.insert(event.selector(), event);
}

pub fn set_block(&mut self, block: BlockId) {
self.block_id = block;
}
Expand Down
8 changes: 1 addition & 7 deletions src/interpreter/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,7 @@ pub fn load_builtins(env: &mut Env) {
pub fn load_project(env: &mut Env, project: &Project) -> Result<()> {
for contract_name in project.contract_names().iter() {
let contract = project.get_contract(contract_name);
env.set_type(
contract_name,
Type::Contract(super::types::ContractInfo(
contract_name.clone(),
contract.clone(),
)),
);
env.add_contract(contract_name, contract.clone());
}
Ok(())
}
Expand Down
81 changes: 0 additions & 81 deletions src/interpreter/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ use alloy::{
dyn_abi::DynSolType,
json_abi::JsonAbi,
primitives::{Address, B256, I256, U160, U256},
rpc::types::{Log, TransactionReceipt},
};
use anyhow::{bail, Result};
use indexmap::IndexMap;
Expand Down Expand Up @@ -69,85 +68,6 @@ impl ContractInfo {
}
}

#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct Receipt {
tx_hash: B256,
block_hash: B256,
block_number: u64,
status: bool,
gas_used: u128,
effective_gas_price: u128,
logs: Vec<Log>,
}

impl Receipt {
pub fn get(&self, field: &str) -> Result<Value> {
let result = match field {
"tx_hash" => Value::FixBytes(self.tx_hash, 32),
"block_hash" => Value::FixBytes(self.block_hash, 32),
"block_number" => Value::Uint(U256::from(self.block_number), 256),
"status" => Value::Bool(self.status),
"gas_used" => Value::Uint(U256::from(self.gas_used), 256),
"effective_gas_price" => Value::Uint(U256::from(self.effective_gas_price), 256),
"logs" => Value::Array(
self.logs.iter().map(|log| log.into()).collect(),
Box::new(Type::NamedTuple(
"Log".to_string(),
HashableIndexMap::from_iter([
("address".to_string(), Type::Address),
("topics".to_string(), Type::Array(Box::new(Type::Uint(256)))),
("data".to_string(), Type::Bytes),
]),
)),
),
_ => bail!("receipt has no field {}", field),
};
Ok(result)
}

pub fn contains_key(&self, key: &str) -> bool {
Self::keys().contains(&key.to_string())
}

pub fn keys() -> Vec<String> {
let keys = [
"tx_hash",
"block_hash",
"block_number",
"status",
"gas_used",
"effective_gas_price",
"logs",
];
keys.map(|s| s.to_string()).to_vec()
}
}

impl From<TransactionReceipt> for Receipt {
fn from(receipt: TransactionReceipt) -> Self {
let logs = receipt.inner.logs().to_vec();
Receipt {
tx_hash: receipt.transaction_hash,
block_hash: receipt.block_hash.unwrap_or_default(),
block_number: receipt.block_number.unwrap_or(0),
status: receipt.status(),
gas_used: receipt.gas_used,
effective_gas_price: receipt.effective_gas_price,
logs,
}
}
}

impl Display for Receipt {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
f,
"tx_hash: {}, block_hash: {}, block_number: {}, status: {}, gas_used: {}, gas_price: {}",
self.tx_hash, self.block_hash, self.block_number, self.status, self.gas_used, self.effective_gas_price
)
}
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum NonParametricType {
Any,
Expand Down Expand Up @@ -569,7 +489,6 @@ impl Type {
abi.functions.keys().map(|s| s.to_string()).collect()
}
Type::NamedTuple(_, fields) => fields.0.keys().map(|s| s.to_string()).collect(),
Type::TransactionReceipt => Receipt::keys(),
Type::Type(type_) => STATIC_METHODS
.get(&type_.into())
.map_or(vec![], |m| m.keys().cloned().collect()),
Expand Down
Loading

0 comments on commit 4999ae1

Please sign in to comment.