risc0_steel/
account.rs

1// Copyright 2025 RISC Zero, Inc.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Types related to account queries.
16use crate::{EvmFactory, GuestEvmEnv, StateAccount};
17use alloy_primitives::{Address, Bytes, B256, U256};
18use anyhow::Result;
19
20/// Information about an Ethereum account.
21///
22/// This struct contains all the essential data that makes up an Ethereum account's state,
23/// including its balance, nonce, storage, and code information.
24#[derive(Debug, Clone, Eq)]
25pub struct AccountInfo {
26    /// The number of transactions sent from this account (also used as replay protection).
27    pub nonce: u64,
28    /// The account's current balance in Wei.
29    pub balance: U256,
30    /// The Keccak-256 hash of the root node of the account's storage trie.
31    pub storage_root: B256,
32    /// The Keccak-256 hash of the account's code.
33    /// For non-contract accounts (EOAs), this will be the hash of empty bytes.
34    pub code_hash: B256,
35    /// The actual bytecode of the account.
36    /// This is `None` when the code hasn't been loaded.
37    pub code: Option<Bytes>,
38}
39
40impl PartialEq for AccountInfo {
41    fn eq(&self, other: &Self) -> bool {
42        self.nonce == other.nonce
43            && self.balance == other.balance
44            && self.storage_root == other.storage_root
45            && self.code_hash == other.code_hash
46    }
47}
48
49impl From<StateAccount> for AccountInfo {
50    fn from(account: StateAccount) -> Self {
51        Self {
52            nonce: account.nonce,
53            balance: account.balance,
54            storage_root: account.storage_root,
55            code_hash: account.code_hash,
56            code: None,
57        }
58    }
59}
60
61/// Represents an EVM account query.
62///
63/// ### Usage
64/// - **Preflight calls on the Host:** To prepare the account query on the host environment and
65///   build the necessary proof, use [Account::preflight].
66/// - **Calls in the Guest:** To initialize the account query in the guest, use [Account::new].
67///
68/// ### Examples
69/// ```rust,no_run
70/// # use risc0_steel::{Account, ethereum::{ETH_MAINNET_CHAIN_SPEC, EthEvmEnv}};
71/// # use alloy_primitives::address;
72/// # #[tokio::main(flavor = "current_thread")]
73/// # async fn main() -> anyhow::Result<()> {
74/// let account_address = address!("F977814e90dA44bFA03b6295A0616a897441aceC");
75///
76/// // Host:
77/// let url = "https://ethereum-rpc.publicnode.com".parse()?;
78/// let mut env = EthEvmEnv::builder().rpc(url).chain_spec(&ETH_MAINNET_CHAIN_SPEC).build().await?;
79/// let account = Account::preflight(account_address, &mut env);
80/// let info = account.bytecode(true).info().await?;
81///
82/// let evm_input = env.into_input().await?;
83///
84/// // Guest:
85/// let env = evm_input.into_env(&ETH_MAINNET_CHAIN_SPEC);
86/// let account = Account::new(account_address, &env);
87/// let info = account.bytecode(true).info();
88///
89/// # Ok(())
90/// # }
91/// ```
92pub struct Account<E> {
93    address: Address,
94    env: E,
95    code: bool,
96}
97
98impl<E> Account<E> {
99    /// Sets whether to fetch the bytecode for this account.
100    ///
101    /// If set to `true`, the bytecode will be fetched when calling [Account::info].
102    pub fn bytecode(mut self, code: bool) -> Self {
103        self.code = code;
104        self
105    }
106}
107
108impl<'a, F: EvmFactory> Account<&'a GuestEvmEnv<F>> {
109    /// Constructor for querying an Ethereum account in the guest.
110    pub fn new(address: Address, env: &'a GuestEvmEnv<F>) -> Self {
111        Self {
112            address,
113            env,
114            code: false,
115        }
116    }
117
118    /// Attempts to get the [AccountInfo] for the corresponding account and returns an error if the
119    /// query fails.
120    ///
121    /// In general, it's recommended to use [Account::info] unless explicit error handling is
122    /// required.
123    pub fn try_info(self) -> Result<AccountInfo> {
124        let db = self.env.db();
125        let account = db.account(self.address).unwrap_or_default();
126        let mut info = AccountInfo::from(account);
127        if self.code && info.code.is_none() {
128            let code = db.code_by_hash(info.code_hash);
129            info.code = Some(code.clone());
130        }
131
132        Ok(info)
133    }
134
135    /// Gets the [AccountInfo] for the corresponding account and panics on failure.
136    ///
137    /// A convenience wrapper for [Account::try_info], panicking if the query fails. Useful when
138    /// success is expected.
139    pub fn info(self) -> AccountInfo {
140        self.try_info().unwrap()
141    }
142}
143
144#[cfg(feature = "host")]
145mod host {
146    use super::*;
147    use crate::host::{db::ProviderDb, HostEvmEnv};
148    use alloy::{network::Network, providers::Provider};
149    use anyhow::{ensure, Context};
150    use revm::Database as RevmDatabase;
151
152    impl<'a, N, P, F, C> Account<&'a mut HostEvmEnv<ProviderDb<N, P>, F, C>>
153    where
154        N: Network,
155        P: Provider<N>,
156        ProviderDb<N, P>: Send + 'static,
157        F: EvmFactory,
158    {
159        /// Constructor for preflighting queries to an Ethereum account on the host.
160        ///
161        /// Initializes the environment for querying account information, fetching necessary data
162        /// via the [Provider], and generating a storage proof for any accessed elements using
163        /// [EvmEnv::into_input].
164        ///
165        /// [EvmEnv::into_input]: crate::EvmEnv::into_input
166        /// [EvmEnv]: crate::EvmEnv
167        pub fn preflight(
168            address: Address,
169            env: &'a mut HostEvmEnv<ProviderDb<N, P>, F, C>,
170        ) -> Self {
171            Self {
172                address,
173                env,
174                code: false,
175            }
176        }
177
178        /// Gets the [AccountInfo] for the corresponding account using an [EvmEnv] constructed with
179        /// [Account::preflight].
180        ///
181        /// [EvmEnv]: crate::EvmEnv
182        pub async fn info(self) -> Result<AccountInfo> {
183            log::info!("Executing preflight querying account {}", &self.address);
184
185            let account = self
186                .env
187                .db_mut()
188                .state_account(self.address)
189                .await
190                .context("failed to get state account information")?;
191            let mut info = AccountInfo::from(account);
192            if self.code && info.code.is_none() {
193                // basic must always be called first
194                let basic = self
195                    .env
196                    .spawn_with_db(move |db| db.basic(self.address))
197                    .await
198                    .context("failed to get basic account information")?
199                    .unwrap_or_default();
200                ensure!(basic.code_hash == account.code_hash, "code_hash mismatch");
201
202                let code = self
203                    .env
204                    .spawn_with_db(move |db| db.code_by_hash(info.code_hash))
205                    .await
206                    .context("failed to get account code by its hash")?;
207                info.code = Some(code.original_bytes());
208            }
209
210            Ok(info)
211        }
212    }
213}