diff --git a/.github/workflows/deploy.py b/.github/workflows/deploy.py index 1c5eecfb6..20d7ef6a1 100644 --- a/.github/workflows/deploy.py +++ b/.github/workflows/deploy.py @@ -105,16 +105,19 @@ def run_subprocess(command): @cli.command(name="run_tests") @click.option('--github_sha') @click.option('--neon_test_branch') -def run_tests(github_sha, neon_test_branch): +@click.option('--base_ref_branch') +def run_tests(github_sha, neon_test_branch, base_ref_branch): os.environ["EVM_LOADER_IMAGE"] = f"{IMAGE_NAME}:{github_sha}" - if neon_test_branch in GithubClient.get_branches_list(NEON_TESTS_ENDPOINT) \ + if GithubClient.is_branch_exist(NEON_TESTS_ENDPOINT, neon_test_branch) \ and neon_test_branch not in ('master', 'develop'): neon_test_image_tag = neon_test_branch + elif re.match(VERSION_BRANCH_TEMPLATE, base_ref_branch): # PR to version branch + neon_test_image_tag = base_ref_branch else: neon_test_image_tag = 'latest' os.environ["NEON_TESTS_IMAGE"] = f"{NEON_TEST_IMAGE_NAME}:{neon_test_image_tag}" - + click.echo(f"NEON_TESTS_IMAGE: {os.environ['NEON_TESTS_IMAGE']}") project_name = f"neon-evm-{github_sha}" stop_containers(project_name) diff --git a/.github/workflows/github_api_client.py b/.github/workflows/github_api_client.py index 198a4892a..3aa3fd850 100644 --- a/.github/workflows/github_api_client.py +++ b/.github/workflows/github_api_client.py @@ -42,6 +42,15 @@ def get_branches_list(endpoint): proxy_branches_obj = requests.get( f"{endpoint}/branches?per_page=100").json() return [item["name"] for item in proxy_branches_obj] + @staticmethod + def is_branch_exist(endpoint, branch): + if branch: + response = requests.get(f"{endpoint}/branches/{branch}") + if response.status_code == 200: + click.echo(f"The branch {branch} exist in the {endpoint} repository") + return True + else: + return False def get_proxy_run_info(self, id): response = requests.get( diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index 0418a9cf7..e1e58d6e3 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -64,7 +64,7 @@ jobs: elif [[ "${{ steps.tag_creation.outputs.base_branch }}" != "" ]]; then # tag creation tag=${{ steps.tag_creation.outputs.base_branch }} - elif [[ "${{ github.head_ref }}" != "" ]]; then # pr to feature or version branch + elif [[ "${{ github.head_ref }}" != "" ]]; then # pr to feature or version branch or develop tag=${{ github.head_ref }} else tag='develop' @@ -75,7 +75,8 @@ jobs: run: | python3 ./.github/workflows/deploy.py run_tests \ --github_sha=${GITHUB_SHA} \ - --neon_test_branch=${{ steps.neon_test_branch.outputs.value }} + --neon_test_branch=${{ steps.neon_test_branch.outputs.value }} \ + --base_ref_branch=${{ github.base_ref }} trigger-proxy-tests: runs-on: trigger-runner needs: diff --git a/Dockerfile b/Dockerfile index 04bf9c5c8..75f4656c4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -29,7 +29,7 @@ RUN cargo fmt --check && \ # Add neon_test_invoke_program to the genesis -FROM neonlabsorg/neon_test_invoke_program:develop AS neon_test_invoke_program +FROM neonlabsorg/neon_test_programs:latest AS neon_test_programs # Define solana-image that contains utility FROM builder AS base @@ -40,8 +40,8 @@ COPY --from=evm-loader-builder /opt/neon-evm/evm_loader/target/deploy/evm_loader COPY --from=evm-loader-builder /opt/neon-evm/evm_loader/target/deploy/evm_loader-dump.txt /opt/ COPY --from=evm-loader-builder /opt/neon-evm/evm_loader/target/release/neon-cli /opt/ COPY --from=evm-loader-builder /opt/neon-evm/evm_loader/target/release/neon-api /opt/ -COPY --from=neon_test_invoke_program /opt/neon_test_invoke_program.so /opt/ -COPY --from=neon_test_invoke_program /opt/neon_test_invoke_program-keypair.json /opt/ + +COPY --from=neon_test_programs /opt/deploy/ /opt/deploy/ COPY ci/wait-for-solana.sh \ ci/wait-for-neon.sh \ diff --git a/ci/solana-run-neon.sh b/ci/solana-run-neon.sh index 8f58a1122..0deda3f77 100755 --- a/ci/solana-run-neon.sh +++ b/ci/solana-run-neon.sh @@ -12,9 +12,6 @@ EVM_LOADER_PATH=${NEON_BIN}/evm_loader.so METAPLEX=metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s METAPLEX_PATH=${NEON_BIN}/metaplex.so -TEST_INVOKE_PROGRAM_ID_KEYPAIR=${NEON_BIN}/neon_test_invoke_program-keypair.json -TEST_INVOKE=$(solana address -k ${TEST_INVOKE_PROGRAM_ID_KEYPAIR}) -TEST_INVOKE_PATH=${NEON_BIN}/neon_test_invoke_program.so VALIDATOR_ARGS=( --reset @@ -23,9 +20,16 @@ VALIDATOR_ARGS=( --ticks-per-slot 16 --upgradeable-program ${EVM_LOADER} ${EVM_LOADER_PATH} ${EVM_LOADER_AUTHORITY_KEYPAIR} --bpf-program ${METAPLEX} ${METAPLEX_PATH} - --bpf-program ${TEST_INVOKE} ${TEST_INVOKE_PATH} ) +LIST_OF_TEST_PROGRAMS=("test_invoke_program" "counter" "cross_program_invocation" "transfer_sol" "transfer_tokens") + +for program in "${LIST_OF_TEST_PROGRAMS[@]}"; do + keypair="${NEON_BIN}/deploy/${program}/${program}-keypair.json" + address=$(solana address -k $keypair) + VALIDATOR_ARGS+=(--bpf-program $address ${NEON_BIN}/deploy/$program/$program.so) +done + if [[ -n $GEYSER_PLUGIN_CONFIG ]]; then echo "Using geyser plugin with config: $GEYSER_PLUGIN_CONFIG" VALIDATOR_ARGS+=(--geyser-plugin-config $GEYSER_PLUGIN_CONFIG) diff --git a/evm_loader/api/src/api_server/handlers/emulate.rs b/evm_loader/api/src/api_server/handlers/emulate.rs index 6d5015e15..cbb03472a 100644 --- a/evm_loader/api/src/api_server/handlers/emulate.rs +++ b/evm_loader/api/src/api_server/handlers/emulate.rs @@ -1,5 +1,6 @@ use actix_request_identifier::RequestId; use actix_web::{http::StatusCode, post, web::Json, Responder}; +use neon_lib::tracing::tracers::TracerTypeEnum; use std::convert::Into; use tracing::info; @@ -26,8 +27,14 @@ pub async fn emulate( }; process_result( - &EmulateCommand::execute(&rpc, state.config.evm_loader, emulate_request.body, None) - .await - .map_err(Into::into), + &EmulateCommand::execute( + &rpc, + state.config.evm_loader, + emulate_request.body, + None::, + ) + .await + .map(|(response, _)| response) + .map_err(Into::into), ) } diff --git a/evm_loader/cli/src/main.rs b/evm_loader/cli/src/main.rs index 85009b452..cb8253b92 100644 --- a/evm_loader/cli/src/main.rs +++ b/evm_loader/cli/src/main.rs @@ -30,6 +30,7 @@ use crate::build_info::get_build_info; use evm_loader::types::Address; use neon_lib::errors::NeonError; use neon_lib::rpc::{CallDbClient, RpcEnum}; +use neon_lib::tracing::tracers::TracerTypeEnum; use neon_lib::types::TracerDb; use solana_clap_utils::keypair::signer_from_path; use solana_sdk::signature::Signer; @@ -45,9 +46,9 @@ async fn run(options: &ArgMatches<'_>) -> NeonCliResult { let rpc = build_rpc(options, config).await?; let request = read_tx_from_stdin()?; - emulate::execute(&rpc, config.evm_loader, request, None) + emulate::execute(&rpc, config.evm_loader, request, None::) .await - .map(|result| json!(result)) + .map(|(result, _)| json!(result)) } ("trace", Some(_)) => { let rpc = build_rpc(options, config).await?; @@ -90,12 +91,12 @@ async fn run(options: &ArgMatches<'_>) -> NeonCliResult { .map(|result| json!(result)) } ("cancel-trx", Some(params)) => { - let rpc_client = config.build_solana_rpc_client(); + let rpc_client = config.build_clone_solana_rpc_client(); let signer = build_signer(config)?; let storage_account = pubkey_of(params, "storage_account").expect("storage_account parse error"); - cancel_trx::execute(&rpc_client, &*signer, config.evm_loader, &storage_account) + cancel_trx::execute(rpc_client, &*signer, config.evm_loader, &storage_account) .await .map(|result| json!(result)) } diff --git a/evm_loader/lib/Cargo.toml b/evm_loader/lib/Cargo.toml index 12536fbc1..54fd59438 100644 --- a/evm_loader/lib/Cargo.toml +++ b/evm_loader/lib/Cargo.toml @@ -9,7 +9,7 @@ edition = "2021" thiserror = "1.0" anyhow = "1.0" bincode = "1.3.1" -evm-loader = { path = "../program", default-features = false, features = ["log", "async-trait", "serde_json"] } +evm-loader = { path = "../program", default-features = false, features = ["log", "async-trait"] } solana-sdk = "=1.16.23" solana-client = "=1.16.23" solana-clap-utils = "=1.16.23" diff --git a/evm_loader/lib/src/account_storage.rs b/evm_loader/lib/src/account_storage.rs index 8d096bdc0..49e23dfb1 100644 --- a/evm_loader/lib/src/account_storage.rs +++ b/evm_loader/lib/src/account_storage.rs @@ -1,18 +1,21 @@ use async_trait::async_trait; use evm_loader::account::legacy::{ - LegacyEtherData, LegacyStorageData, TAG_ACCOUNT_CONTRACT_DEPRECATED, - TAG_STORAGE_CELL_DEPRECATED, + LegacyEtherData, LegacyStorageData, ACCOUNT_PREFIX_LEN_BEFORE_REVISION, + TAG_ACCOUNT_BALANCE_BEFORE_REVISION, TAG_ACCOUNT_CONTRACT_BEFORE_REVISION, + TAG_ACCOUNT_CONTRACT_DEPRECATED, TAG_STORAGE_CELL_BEFORE_REVISION, TAG_STORAGE_CELL_DEPRECATED, }; -use evm_loader::account::{TAG_ACCOUNT_CONTRACT, TAG_STORAGE_CELL}; +use evm_loader::account::{ACCOUNT_PREFIX_LEN, TAG_ACCOUNT_CONTRACT, TAG_STORAGE_CELL}; use evm_loader::account_storage::find_slot_hash; use evm_loader::types::Address; use solana_sdk::rent::Rent; use solana_sdk::system_program; -use solana_sdk::sysvar::{slot_hashes, Sysvar}; -use std::collections::{BTreeMap, HashSet}; -use std::{cell::RefCell, collections::HashMap, convert::TryInto, rc::Rc}; +use solana_sdk::sysvar::slot_hashes; +use std::collections::{BTreeMap, HashMap, HashSet}; +use std::{cell::RefCell, convert::TryInto, rc::Rc}; +use crate::account_update::update_account; use crate::solana_emulator::get_solana_emulator; +use crate::NeonResult; use crate::{rpc::Rpc, NeonError}; use ethnum::U256; use evm_loader::{ @@ -58,6 +61,7 @@ pub struct EmulatorAccountStorage<'rpc, T: Rpc> { chains: Vec, block_number: u64, block_timestamp: i64, + rent: Rent, state_overrides: Option, } @@ -86,6 +90,14 @@ impl<'rpc, T: Rpc + BuildConfigSimulator> EmulatorAccountStorage<'rpc, T> { Some(chains) => chains, }; + let rent_account = rpc + .get_account(&solana_sdk::sysvar::rent::id()) + .await? + .value + .ok_or(NeonError::AccountNotFound(solana_sdk::sysvar::rent::id()))?; + let rent = bincode::deserialize::(&rent_account.data)?; + info!("Rent: {rent:?}"); + Ok(Self { accounts: RefCell::new(HashMap::new()), program_id, @@ -95,6 +107,7 @@ impl<'rpc, T: Rpc + BuildConfigSimulator> EmulatorAccountStorage<'rpc, T> { block_number, block_timestamp, state_overrides, + rent, }) } @@ -169,9 +182,13 @@ impl EmulatorAccountStorage<'_, T> { address: Address, chain_id: u64, is_writable: bool, - ) -> client_error::Result<(Pubkey, Option, Option)> { + ) -> NeonResult<(Pubkey, Option, Option)> { let (pubkey, _) = address.find_balance_address(self.program_id(), chain_id); - let account = self.use_account(pubkey, is_writable).await?; + let mut account = self.use_account(pubkey, is_writable).await?; + + if let Some(account) = &mut account { + update_account(&self.program_id, &pubkey, account)?; + } let legacy_account = if account.is_none() && (chain_id == self.default_chain_id()) { let (legacy_pubkey, _) = address.find_solana_address(self.program_id()); @@ -187,9 +204,13 @@ impl EmulatorAccountStorage<'_, T> { &self, address: Address, is_writable: bool, - ) -> client_error::Result<(Pubkey, Option)> { + ) -> NeonResult<(Pubkey, Option)> { let (pubkey, _) = address.find_solana_address(self.program_id()); - let account = self.use_account(pubkey, is_writable).await?; + let mut account = self.use_account(pubkey, is_writable).await?; + + if let Some(account) = &mut account { + update_account(&self.program_id, &pubkey, account)?; + } Ok((pubkey, account)) } @@ -199,22 +220,24 @@ impl EmulatorAccountStorage<'_, T> { address: Address, index: U256, is_writable: bool, - ) -> client_error::Result<(Pubkey, Option)> { + ) -> NeonResult<(Pubkey, Option)> { let (base, _) = address.find_solana_address(self.program_id()); let cell_address = StorageCellAddress::new(self.program_id(), &base, &index); - let account = self + let mut account = self .use_account(*cell_address.pubkey(), is_writable) .await?; + if let Some(account) = &mut account { + update_account(&self.program_id, cell_address.pubkey(), account)?; + } + Ok((*cell_address.pubkey(), account)) } pub async fn apply_actions(&mut self, actions: Vec) -> Result<(), NeonError> { info!("apply_actions"); - let rent = Rent::get()?; - let mut new_balance_accounts = HashSet::new(); for action in actions { @@ -262,12 +285,13 @@ impl EmulatorAccountStorage<'_, T> { let empty_size = StorageCell::required_account_size(0); let gas = if account.is_none() { - rent.minimum_balance(cell_size) + self.rent.minimum_balance(cell_size) } else { let existing_value = self.storage(address, index).await; if existing_value == [0_u8; 32] { - rent.minimum_balance(cell_size) - .saturating_sub(rent.minimum_balance(empty_size)) + self.rent + .minimum_balance(cell_size) + .saturating_sub(self.rent.minimum_balance(empty_size)) } else { 0 } @@ -294,7 +318,7 @@ impl EmulatorAccountStorage<'_, T> { self.use_contract_account(address, true).await?; let space = ContractAccount::required_account_size(&code); - self.gas = self.gas.saturating_add(rent.minimum_balance(space)); + self.gas = self.gas.saturating_add(self.rent.minimum_balance(space)); } Action::EvmSelfDestruct { address } => { info!("selfdestruct {address}"); @@ -320,7 +344,8 @@ impl EmulatorAccountStorage<'_, T> { } self.gas = self.gas.saturating_add( - rent.minimum_balance(BalanceAccount::required_account_size()) + self.rent + .minimum_balance(BalanceAccount::required_account_size()) .saturating_mul(new_balance_accounts.len() as u64), ); @@ -331,8 +356,6 @@ impl EmulatorAccountStorage<'_, T> { let mut accounts = self.accounts.borrow_mut(); let mut additional_balances = Vec::new(); - let rent = Rent::get()?; - for (key, account) in accounts.iter_mut() { let Some(account_data) = account.data.as_mut() else { continue; @@ -361,10 +384,33 @@ impl EmulatorAccountStorage<'_, T> { if (legacy_data.code_size > 0) || (legacy_data.generation > 0) { // This is a contract, we need additional gas for conversion - let lamports = rent.minimum_balance(BalanceAccount::required_account_size()); + let lamports = self + .rent + .minimum_balance(BalanceAccount::required_account_size()); self.gas = self.gas.saturating_add(lamports); } } + + if matches!( + tag, + TAG_ACCOUNT_BALANCE_BEFORE_REVISION + | TAG_ACCOUNT_CONTRACT_BEFORE_REVISION + | TAG_STORAGE_CELL_BEFORE_REVISION + ) { + const PREFIX_BEFORE: usize = ACCOUNT_PREFIX_LEN_BEFORE_REVISION; + const PREFIX_AFTER: usize = ACCOUNT_PREFIX_LEN; + const EXPANSION_LEN: usize = PREFIX_AFTER - PREFIX_BEFORE; + + account.is_writable = true; + account.is_legacy = true; + + let lamports = self + .rent + .minimum_balance(EXPANSION_LEN) + .saturating_sub(self.rent.minimum_balance(0)); + + self.gas = self.gas.saturating_add(lamports); + } } for a in additional_balances { @@ -533,6 +579,16 @@ impl AccountStorage for EmulatorAccountStorage<'_, T> { self.block_timestamp.try_into().unwrap() } + fn rent(&self) -> &Rent { + &self.rent + } + + fn return_data(&self) -> Option<(Pubkey, Vec)> { + info!("return_data"); + // TODO: implement return_data() method with SyncedAccountStorage implementation + unimplemented!(); + } + async fn block_hash(&self, slot: u64) -> [u8; 32] { info!("block_hash {slot}"); @@ -742,7 +798,6 @@ impl AccountStorage for EmulatorAccountStorage<'_, T> { ) -> evm_loader::error::Result<()> { let instruction = Instruction::new_with_bytes(*program_id, instruction_data, meta.to_vec()); let solana_emulator = get_solana_emulator().await; - //let solana_emulator = self.solana_emulator.lock().expect("Lock solana_emulator"); solana_emulator .emulate_solana_call(self, &instruction, accounts, seeds) .await diff --git a/evm_loader/lib/src/account_update.rs b/evm_loader/lib/src/account_update.rs new file mode 100644 index 000000000..a51dda7b2 --- /dev/null +++ b/evm_loader/lib/src/account_update.rs @@ -0,0 +1,48 @@ +use evm_loader::account::{ + legacy::{ + ACCOUNT_PREFIX_LEN_BEFORE_REVISION, TAG_ACCOUNT_BALANCE_BEFORE_REVISION, + TAG_ACCOUNT_CONTRACT_BEFORE_REVISION, TAG_STORAGE_CELL_BEFORE_REVISION, + }, + ACCOUNT_PREFIX_LEN, TAG_ACCOUNT_BALANCE, TAG_ACCOUNT_CONTRACT, TAG_STORAGE_CELL, +}; +use solana_sdk::{account::Account, pubkey::Pubkey}; + +use crate::{account_storage::account_info, NeonResult}; + +fn from_before_revision(account: &mut Account, new_tag: u8) { + const PREFIX_BEFORE: usize = ACCOUNT_PREFIX_LEN_BEFORE_REVISION; + const PREFIX_AFTER: usize = ACCOUNT_PREFIX_LEN; + + let data: &mut Vec = &mut account.data; + + assert!(data.len() > PREFIX_BEFORE); + let data_len = data.len() - PREFIX_BEFORE; + + let required_len = data.len() + PREFIX_AFTER - PREFIX_BEFORE; + data.resize(required_len, 0); + + data.copy_within(PREFIX_BEFORE..(PREFIX_BEFORE + data_len), PREFIX_AFTER); + data[0] = new_tag; +} + +pub fn update_account(program_id: &Pubkey, key: &Pubkey, account: &mut Account) -> NeonResult<()> { + let tag = { + let info = account_info(key, account); + evm_loader::account::tag(program_id, &info)? + }; + + match tag { + TAG_ACCOUNT_BALANCE_BEFORE_REVISION => { + from_before_revision(account, TAG_ACCOUNT_BALANCE); + } + TAG_ACCOUNT_CONTRACT_BEFORE_REVISION => { + from_before_revision(account, TAG_ACCOUNT_CONTRACT); + } + TAG_STORAGE_CELL_BEFORE_REVISION => { + from_before_revision(account, TAG_STORAGE_CELL); + } + _ => {} + } + + Ok(()) +} diff --git a/evm_loader/lib/src/commands/cancel_trx.rs b/evm_loader/lib/src/commands/cancel_trx.rs index 22e288c41..f68ee6441 100644 --- a/evm_loader/lib/src/commands/cancel_trx.rs +++ b/evm_loader/lib/src/commands/cancel_trx.rs @@ -2,7 +2,6 @@ use evm_loader::account::StateAccount; use log::info; use serde::{Deserialize, Serialize}; -use solana_client::nonblocking::rpc_client::RpcClient; use solana_sdk::{ instruction::{AccountMeta, Instruction}, pubkey::Pubkey, @@ -10,7 +9,9 @@ use solana_sdk::{ signer::Signer, }; -use crate::{account_storage::account_info, commands::send_transaction, NeonResult}; +use crate::{ + account_storage::account_info, commands::send_transaction, rpc::CloneRpcClient, NeonResult, +}; #[derive(Debug, Serialize, Deserialize)] pub struct CancelTrxReturn { @@ -18,20 +19,23 @@ pub struct CancelTrxReturn { } pub async fn execute( - rpc_client: &RpcClient, + rpc_client: CloneRpcClient, signer: &dyn Signer, - evm_loader: Pubkey, + program_id: Pubkey, storage_account: &Pubkey, ) -> NeonResult { let mut acc = rpc_client.get_account(storage_account).await?; let storage_info = account_info(storage_account, &mut acc); - let storage = StateAccount::from_account(&evm_loader, storage_info)?; + let storage = StateAccount::from_account(&program_id, storage_info)?; let operator = &signer.pubkey(); + let default_chain_id = + crate::commands::get_config::read_default_chain_id(&rpc_client, program_id).await?; + let chain_id = storage.trx().chain_id().unwrap_or(default_chain_id); + let origin = storage.trx_origin(); - let chain_id: u64 = storage.trx_chain_id(); - let (origin_pubkey, _) = origin.find_balance_address(&evm_loader, chain_id); + let (origin_pubkey, _) = origin.find_balance_address(&program_id, chain_id); let mut accounts_meta: Vec = vec![ AccountMeta::new(*storage_account, false), // State account @@ -39,23 +43,19 @@ pub async fn execute( AccountMeta::new(origin_pubkey, false), ]; - for blocked_account_meta in storage.blocked_accounts().iter() { - if blocked_account_meta.is_writable { - accounts_meta.push(AccountMeta::new(blocked_account_meta.key, false)); - } else { - accounts_meta.push(AccountMeta::new_readonly(blocked_account_meta.key, false)); - } - } - for meta in &accounts_meta { + for key in storage.accounts() { + let meta = AccountMeta::new(*key, true); info!("\t{:?}", meta); + + accounts_meta.push(meta); } let cancel_with_nonce_instruction = - Instruction::new_with_bincode(evm_loader, &(0x37_u8, storage.trx_hash()), accounts_meta); + Instruction::new_with_bincode(program_id, &(0x37_u8, storage.trx().hash()), accounts_meta); let instructions = vec![cancel_with_nonce_instruction]; - let signature = send_transaction(rpc_client, signer, &instructions).await?; + let signature = send_transaction(&rpc_client, signer, &instructions).await?; Ok(CancelTrxReturn { transaction: signature, diff --git a/evm_loader/lib/src/commands/emulate.rs b/evm_loader/lib/src/commands/emulate.rs index 57e5fb7dd..621800826 100644 --- a/evm_loader/lib/src/commands/emulate.rs +++ b/evm_loader/lib/src/commands/emulate.rs @@ -1,12 +1,15 @@ use evm_loader::account::ContractAccount; +use evm_loader::account_storage::AccountStorage; use evm_loader::error::build_revert_message; use log::{debug, info}; use serde::{Deserialize, Serialize}; +use serde_json::Value; use solana_sdk::entrypoint::MAX_PERMITTED_DATA_INCREASE; use solana_sdk::pubkey::Pubkey; use crate::commands::get_config::BuildConfigSimulator; use crate::rpc::Rpc; +use crate::tracing::tracers::Tracer; use crate::types::{EmulateRequest, TxParams}; use crate::{ account_storage::{EmulatorAccountStorage, SolanaAccount}, @@ -14,7 +17,6 @@ use crate::{ errors::NeonError, NeonResult, }; -use evm_loader::evm::tracing::TracerType; use evm_loader::{ config::{EVM_STEPS_MIN, PAYMENT_TO_TREASURE}, evm::{ExitStatus, Machine}, @@ -56,12 +58,12 @@ impl EmulateResponse { } } -pub async fn execute( +pub async fn execute( rpc: &(impl Rpc + BuildConfigSimulator), program_id: Pubkey, emulate_request: EmulateRequest, - tracer: Option, -) -> NeonResult { + tracer: Option, +) -> NeonResult<(EmulateResponse, Option)> { let block_overrides = emulate_request .trace_config .as_ref() @@ -86,12 +88,12 @@ pub async fn execute( emulate_trx(emulate_request.tx, &mut storage, step_limit, tracer).await } -async fn emulate_trx( +async fn emulate_trx( tx_params: TxParams, storage: &mut EmulatorAccountStorage<'_, impl Rpc>, step_limit: u64, - tracer: Option, -) -> NeonResult { + tracer: Option, +) -> NeonResult<(EmulateResponse, Option)> { info!("tx_params: {:?}", tx_params); let (origin, tx) = tx_params.into_transaction(storage).await; @@ -99,23 +101,22 @@ async fn emulate_trx( info!("origin: {:?}", origin); info!("tx: {:?}", tx); - let (exit_status, actions, steps_executed, execute_status) = { - let mut backend = EmulatorState::new(storage); - let mut evm = match Machine::new(tx, origin, &mut backend, tracer).await { - Ok(evm) => evm, - Err(e) => return Ok(EmulateResponse::revert(e)), - }; + let chain_id = tx.chain_id().unwrap_or_else(|| storage.default_chain_id()); + storage.use_balance_account(origin, chain_id, true).await?; - let (result, steps_executed) = evm.execute(step_limit, &mut backend).await?; - if result == ExitStatus::StepLimit { - return Err(NeonError::TooManySteps); - } + let mut backend = EmulatorState::new(storage); + let mut evm = match Machine::new(&tx, origin, &mut backend, tracer).await { + Ok(evm) => evm, + Err(e) => return Ok((EmulateResponse::revert(e), None)), + }; - let execute_status = backend.execute_status.clone(); - let actions = backend.into_actions(); + let (exit_status, steps_executed, tracer) = evm.execute(step_limit, &mut backend).await?; + if exit_status == ExitStatus::StepLimit { + return Err(NeonError::TooManySteps); + } - (result, actions, steps_executed, execute_status) - }; + let execute_status = backend.execute_status.clone(); + let actions = backend.into_actions(); storage.apply_actions(actions.clone()).await?; storage.mark_legacy_accounts().await?; @@ -135,17 +136,20 @@ async fn emulate_trx( let solana_accounts = storage.accounts.borrow().values().cloned().collect(); - Ok(EmulateResponse { - exit_status: exit_status.to_string(), - external_solana_calls: execute_status.external_solana_calls, - reverts_before_solana_calls: execute_status.reverts_before_solana_calls, - reverts_after_solana_calls: execute_status.reverts_after_solana_calls, - steps_executed, - used_gas, - solana_accounts, - result: exit_status.into_result().unwrap_or_default(), - iterations, - }) + Ok(( + EmulateResponse { + exit_status: exit_status.to_string(), + external_solana_calls: execute_status.external_solana_calls, + reverts_before_solana_calls: execute_status.reverts_before_solana_calls, + reverts_after_solana_calls: execute_status.reverts_after_solana_calls, + steps_executed, + used_gas, + solana_accounts, + result: exit_status.into_result().unwrap_or_default(), + iterations, + }, + tracer.map(|tracer| tracer.into_traces()), + )) } fn realloc_iterations(actions: &[Action]) -> u64 { diff --git a/evm_loader/lib/src/commands/get_balance.rs b/evm_loader/lib/src/commands/get_balance.rs index e9d6561a9..4879dd1c9 100644 --- a/evm_loader/lib/src/commands/get_balance.rs +++ b/evm_loader/lib/src/commands/get_balance.rs @@ -4,7 +4,10 @@ use evm_loader::account::BalanceAccount; use serde::{Deserialize, Serialize}; use solana_sdk::{account::Account, pubkey::Pubkey}; -use crate::{account_storage::account_info, rpc::Rpc, types::BalanceAddress, NeonResult}; +use crate::{ + account_storage::account_info, account_update::update_account, rpc::Rpc, types::BalanceAddress, + NeonResult, +}; use serde_with::{serde_as, DisplayFromStr}; @@ -48,6 +51,8 @@ fn read_account( ) -> NeonResult { let solana_address = address.find_pubkey(program_id); + update_account(program_id, &solana_address, &mut account)?; + let account_info = account_info(&solana_address, &mut account); let balance_account = BalanceAccount::from_account(program_id, account_info)?; diff --git a/evm_loader/lib/src/commands/get_config.rs b/evm_loader/lib/src/commands/get_config.rs index 47d19baf2..d10c8821e 100644 --- a/evm_loader/lib/src/commands/get_config.rs +++ b/evm_loader/lib/src/commands/get_config.rs @@ -344,6 +344,18 @@ pub async fn read_chains( simulator.get_chains().await } +pub async fn read_default_chain_id( + rpc: &impl BuildConfigSimulator, + program_id: Pubkey, +) -> NeonResult { + let mut simulator = rpc.build_config_simulator(program_id).await?; + + let chains = simulator.get_chains().await?; + let default_chain = chains.iter().find(|chain| chain.name == "neon").unwrap(); + + Ok(default_chain.id) +} + impl CloneRpcClient { async fn get_account_with_sol(&self) -> ClientResult { let r = self diff --git a/evm_loader/lib/src/commands/get_contract.rs b/evm_loader/lib/src/commands/get_contract.rs index 6f1a3d324..938a1a4c6 100644 --- a/evm_loader/lib/src/commands/get_contract.rs +++ b/evm_loader/lib/src/commands/get_contract.rs @@ -5,7 +5,7 @@ use evm_loader::{ use serde::{Deserialize, Serialize}; use solana_sdk::{account::Account, pubkey::Pubkey}; -use crate::{account_storage::account_info, rpc::Rpc, NeonResult}; +use crate::{account_storage::account_info, account_update::update_account, rpc::Rpc, NeonResult}; use serde_with::{hex::Hex, serde_as, DisplayFromStr}; @@ -51,6 +51,8 @@ fn read_account( return Ok(GetContractResponse::empty(solana_address)); }; + update_account(program_id, &solana_address, &mut account)?; + let account_info = account_info(&solana_address, &mut account); let (chain_id, code) = if let Ok(contract) = ContractAccount::from_account(program_id, account_info.clone()) { diff --git a/evm_loader/lib/src/commands/get_holder.rs b/evm_loader/lib/src/commands/get_holder.rs index 1ad6aff13..aa1cddb25 100644 --- a/evm_loader/lib/src/commands/get_holder.rs +++ b/evm_loader/lib/src/commands/get_holder.rs @@ -1,4 +1,3 @@ -use ethnum::U256; use evm_loader::account::{ legacy::{ LegacyFinalizedData, LegacyHolderData, TAG_HOLDER_DEPRECATED, @@ -24,14 +23,6 @@ pub enum Status { Finalized, } -#[serde_as] -#[derive(Debug, Default, Serialize)] -pub struct AccountMeta { - pub is_writable: bool, - #[serde_as(as = "DisplayFromStr")] - pub key: Pubkey, -} - #[serde_as] #[skip_serializing_none] #[derive(Debug, Default, Serialize)] @@ -45,11 +36,8 @@ pub struct GetHolderResponse { pub tx: Option<[u8; 32]>, pub chain_id: Option, - pub gas_price: Option, - pub gas_limit: Option, - pub gas_used: Option, - - pub accounts: Option>, + #[serde_as(as = "Option>")] + pub accounts: Option>, } impl GetHolderResponse { @@ -114,24 +102,14 @@ pub fn read_holder(program_id: &Pubkey, info: AccountInfo) -> NeonResult { let state = StateAccount::from_account(program_id, info)?; - let accounts = state - .blocked_accounts() - .iter() - .map(|a| AccountMeta { - is_writable: a.is_writable, - key: a.key, - }) - .collect(); + let accounts = state.accounts().copied().collect(); Ok(GetHolderResponse { status: Status::Active, len: Some(data_len), owner: Some(state.owner()), - tx: Some(state.trx_hash()), - chain_id: Some(state.trx_chain_id()), - gas_limit: Some(state.trx_gas_limit()), - gas_price: Some(state.trx_gas_price()), - gas_used: Some(state.gas_used()), + tx: Some(state.trx().hash()), + chain_id: state.trx().chain_id(), accounts: Some(accounts), }) } diff --git a/evm_loader/lib/src/commands/trace.rs b/evm_loader/lib/src/commands/trace.rs index b6f8251fb..96fdc5c97 100644 --- a/evm_loader/lib/src/commands/trace.rs +++ b/evm_loader/lib/src/commands/trace.rs @@ -1,5 +1,3 @@ -use std::rc::Rc; - use serde_json::Value; use solana_sdk::pubkey::Pubkey; @@ -22,13 +20,10 @@ pub async fn trace_transaction( let tracer = new_tracer(&trace_config)?; - let emulation_tracer = Some(Rc::clone(&tracer)); - let r = super::emulate::execute(rpc, program_id, emulate_request, emulation_tracer).await?; + let (r, traces) = + super::emulate::execute(rpc, program_id, emulate_request, Some(tracer)).await?; - let mut traces = Rc::try_unwrap(tracer) - .expect("There is must be only one reference") - .into_inner() - .into_traces(); + let mut traces = traces.expect("traces should not be None"); traces["gas"] = r.used_gas.into(); Ok(traces) diff --git a/evm_loader/lib/src/emulator_state.rs b/evm_loader/lib/src/emulator_state.rs index 0be9b3ff1..5ebfe29e2 100644 --- a/evm_loader/lib/src/emulator_state.rs +++ b/evm_loader/lib/src/emulator_state.rs @@ -12,6 +12,7 @@ use evm_loader::executor::{ use evm_loader::types::Address; use solana_sdk::instruction::Instruction; use solana_sdk::pubkey::Pubkey; +use solana_sdk::rent::Rent; #[derive(Default, Clone)] pub struct ExecuteStatus { @@ -136,6 +137,14 @@ impl<'a, B: AccountStorage> Database for EmulatorState<'a, B> { self.inner_state.external_account(address).await } + fn rent(&self) -> &Rent { + self.inner_state.rent() + } + + fn return_data(&self) -> Option<(Pubkey, Vec)> { + self.inner_state.return_data() + } + async fn map_solana_account(&self, address: &Pubkey, action: F) -> R where F: FnOnce(&solana_sdk::account_info::AccountInfo) -> R, diff --git a/evm_loader/lib/src/lib.rs b/evm_loader/lib/src/lib.rs index 3cab72221..d42f55212 100644 --- a/evm_loader/lib/src/lib.rs +++ b/evm_loader/lib/src/lib.rs @@ -1,4 +1,5 @@ pub mod account_storage; +pub mod account_update; pub mod build_info; pub mod build_info_common; pub mod commands; @@ -7,7 +8,6 @@ pub mod emulator_state; pub mod errors; pub mod rpc; pub mod solana_emulator; -pub mod syscall_stubs; pub mod tracing; pub mod types; diff --git a/evm_loader/lib/src/solana_emulator.rs b/evm_loader/lib/src/solana_emulator.rs index 38ba77393..13822faf6 100644 --- a/evm_loader/lib/src/solana_emulator.rs +++ b/evm_loader/lib/src/solana_emulator.rs @@ -6,7 +6,6 @@ use std::collections::BTreeMap; use tokio::sync::{Mutex, MutexGuard, OnceCell}; use crate::rpc::Rpc; -use crate::syscall_stubs; use crate::NeonError; use evm_loader::{account_storage::AccountStorage, executor::OwnedAccountInfo}; use solana_program_test::{processor, ProgramTest, ProgramTestContext}; @@ -31,17 +30,6 @@ pub struct SolanaEmulator { static SOLANA_EMULATOR: OnceCell> = OnceCell::const_new(); const SEEDS_PUBKEY: Pubkey = pubkey!("Seeds11111111111111111111111111111111111111"); -macro_rules! processor_with_original_stubs { - ($process_instruction:expr) => { - processor!(|program_id, accounts, instruction_data| { - let use_original_stubs_saved = syscall_stubs::use_original_stubs_for_thread(true); - let result = $process_instruction(program_id, accounts, instruction_data); - syscall_stubs::use_original_stubs_for_thread(use_original_stubs_saved); - result - }) - }; -} - pub async fn get_solana_emulator() -> MutexGuard<'static, SolanaEmulator> { SOLANA_EMULATOR .get() @@ -59,9 +47,7 @@ pub async fn init_solana_emulator( let emulator = SolanaEmulator::new(program_id, rpc_client) .await .expect("Initialize SolanaEmulator"); - syscall_stubs::setup_emulator_syscall_stubs(rpc_client) - .await - .expect("Setup emulator syscall stubs"); + Mutex::new(emulator) }) .await @@ -121,7 +107,7 @@ impl SolanaEmulator { program_test.add_program( "evm_loader", program_id, - processor_with_original_stubs!(process_emulator_instruction), + processor!(process_emulator_instruction), ); // Disable features (get known feature list and disable by actual value) diff --git a/evm_loader/lib/src/syscall_stubs.rs b/evm_loader/lib/src/syscall_stubs.rs deleted file mode 100644 index 85f6d8fd0..000000000 --- a/evm_loader/lib/src/syscall_stubs.rs +++ /dev/null @@ -1,165 +0,0 @@ -use log::info; -use solana_sdk::{program_error::ProgramError, program_stubs::SyscallStubs, sysvar::rent::Rent}; -use std::cell::RefCell; - -use crate::{errors::NeonError, rpc::Rpc}; - -pub struct DefaultStubs; -impl SyscallStubs for DefaultStubs {} - -thread_local! { - static USE_ORIGINAL_STUBS: RefCell = RefCell::new(false); -} -pub fn use_original_stubs_for_thread(new: bool) -> bool { - USE_ORIGINAL_STUBS.with(|invoke_context| invoke_context.replace(new)) -} -pub fn is_original_stubs_for_thread() -> bool { - USE_ORIGINAL_STUBS.with(|invoke_context| *invoke_context.borrow()) -} - -pub struct EmulatorStubs { - rent: Rent, - original_stubs: Box, -} - -impl EmulatorStubs { - pub async fn new( - rpc: &impl Rpc, - original_stubs: Box, - ) -> Result, NeonError> { - let rent_pubkey = solana_sdk::sysvar::rent::id(); - let data = rpc - .get_account(&rent_pubkey) - .await? - .value - .map(|a| a.data) - .unwrap_or_default(); - let rent = bincode::deserialize(&data).map_err(|_| ProgramError::InvalidArgument)?; - - Ok(Box::new(Self { - rent, - original_stubs, - })) - } -} - -impl SyscallStubs for EmulatorStubs { - fn sol_get_rent_sysvar(&self, pointer: *mut u8) -> u64 { - if is_original_stubs_for_thread() { - self.original_stubs.sol_get_rent_sysvar(pointer) - } else { - unsafe { - #[allow(clippy::cast_ptr_alignment)] - let rent = pointer.cast::(); - *rent = self.rent; - } - 0 - } - } - - fn sol_log(&self, message: &str) { - if is_original_stubs_for_thread() { - self.original_stubs.sol_log(message) - } else { - info!("{}", message); - } - } - - fn sol_log_data(&self, fields: &[&[u8]]) { - if is_original_stubs_for_thread() { - self.original_stubs.sol_log_data(fields) - } else { - let mut messages: Vec = Vec::new(); - - for f in fields { - if let Ok(str) = String::from_utf8(f.to_vec()) { - messages.push(str); - } else { - messages.push(hex::encode(f)); - } - } - - info!("Program Data: {}", messages.join(" ")); - } - } - - fn sol_get_clock_sysvar(&self, _var_addr: *mut u8) -> u64 { - if is_original_stubs_for_thread() { - self.original_stubs.sol_get_clock_sysvar(_var_addr) - } else { - DefaultStubs {}.sol_get_clock_sysvar(_var_addr) - } - } - - fn sol_get_epoch_schedule_sysvar(&self, _var_addr: *mut u8) -> u64 { - if is_original_stubs_for_thread() { - self.original_stubs.sol_get_epoch_schedule_sysvar(_var_addr) - } else { - DefaultStubs {}.sol_get_epoch_schedule_sysvar(_var_addr) - } - } - - fn sol_get_fees_sysvar(&self, _var_addr: *mut u8) -> u64 { - if is_original_stubs_for_thread() { - self.original_stubs.sol_get_fees_sysvar(_var_addr) - } else { - DefaultStubs {}.sol_get_fees_sysvar(_var_addr) - } - } - - fn sol_get_return_data(&self) -> Option<(solana_sdk::pubkey::Pubkey, Vec)> { - if is_original_stubs_for_thread() { - self.original_stubs.sol_get_return_data() - } else { - DefaultStubs {}.sol_get_return_data() - } - } - - fn sol_get_stack_height(&self) -> u64 { - if is_original_stubs_for_thread() { - self.original_stubs.sol_get_stack_height() - } else { - DefaultStubs {}.sol_get_stack_height() - } - } - - fn sol_invoke_signed( - &self, - _instruction: &solana_sdk::instruction::Instruction, - _account_infos: &[solana_sdk::account_info::AccountInfo], - _signers_seeds: &[&[&[u8]]], - ) -> solana_sdk::entrypoint::ProgramResult { - if is_original_stubs_for_thread() { - self.original_stubs - .sol_invoke_signed(_instruction, _account_infos, _signers_seeds) - } else { - DefaultStubs {}.sol_invoke_signed(_instruction, _account_infos, _signers_seeds) - } - } - - fn sol_log_compute_units(&self) { - if is_original_stubs_for_thread() { - self.original_stubs.sol_log_compute_units() - } else { - DefaultStubs {}.sol_log_compute_units() - } - } - - fn sol_set_return_data(&self, _data: &[u8]) { - if is_original_stubs_for_thread() { - self.original_stubs.sol_set_return_data(_data) - } else { - DefaultStubs {}.sol_set_return_data(_data) - } - } -} - -pub async fn setup_emulator_syscall_stubs(rpc: &impl Rpc) -> Result<(), NeonError> { - use solana_sdk::program_stubs::set_syscall_stubs; - - let original_stubs = set_syscall_stubs(Box::new(DefaultStubs {})); - let syscall_stubs = EmulatorStubs::new(rpc, original_stubs).await?; - set_syscall_stubs(syscall_stubs); - - Ok(()) -} diff --git a/evm_loader/lib/src/tracing/tracers/mod.rs b/evm_loader/lib/src/tracing/tracers/mod.rs index e0dd86756..0f54f6391 100644 --- a/evm_loader/lib/src/tracing/tracers/mod.rs +++ b/evm_loader/lib/src/tracing/tracers/mod.rs @@ -1,21 +1,45 @@ use crate::tracing::tracers::struct_logger::StructLogger; use crate::tracing::TraceConfig; -use evm_loader::evm::tracing::TracerType; -use std::cell::RefCell; -use std::rc::Rc; +use evm_loader::evm::database::Database; +use evm_loader::evm::tracing::{Event, EventListener}; +use serde_json::Value; pub mod struct_logger; -pub fn new_tracer(trace_config: &TraceConfig) -> evm_loader::error::Result { - Ok(Rc::new(RefCell::new( - match trace_config.tracer.as_deref() { - None | Some("") => Box::new(StructLogger::new(trace_config)), - _ => { - return Err(evm_loader::error::Error::Custom(format!( - "Unsupported tracer: {:?}", - trace_config.tracer - ))) +pub enum TracerTypeEnum { + StructLogger(StructLogger), +} + +impl EventListener for TracerTypeEnum { + fn event(&mut self, executor_state: &impl Database, event: Event) { + match self { + TracerTypeEnum::StructLogger(struct_logger) => { + struct_logger.event(executor_state, event) } - }, - ))) + } + } +} + +pub trait Tracer: EventListener { + fn into_traces(self) -> Value; +} + +impl Tracer for TracerTypeEnum { + fn into_traces(self) -> Value { + match self { + TracerTypeEnum::StructLogger(struct_logger) => struct_logger.into_traces(), + } + } +} + +pub fn new_tracer(trace_config: &TraceConfig) -> evm_loader::error::Result { + match trace_config.tracer.as_deref() { + None | Some("") => Ok(TracerTypeEnum::StructLogger(StructLogger::new( + trace_config, + ))), + _ => Err(evm_loader::error::Error::Custom(format!( + "Unsupported tracer: {:?}", + trace_config.tracer + ))), + } } diff --git a/evm_loader/lib/src/tracing/tracers/struct_logger.rs b/evm_loader/lib/src/tracing/tracers/struct_logger.rs index a842557f6..e8d23c922 100644 --- a/evm_loader/lib/src/tracing/tracers/struct_logger.rs +++ b/evm_loader/lib/src/tracing/tracers/struct_logger.rs @@ -1,11 +1,13 @@ use std::collections::BTreeMap; use ethnum::U256; +use evm_loader::evm::database::Database; use evm_loader::evm::ExitStatus; use serde::Serialize; use serde_json::Value; use web3::types::Bytes; +use crate::tracing::tracers::Tracer; use crate::tracing::TraceConfig; use evm_loader::evm::opcode_table::OPNAMES; use evm_loader::evm::tracing::{Event, EventListener}; @@ -138,7 +140,7 @@ impl StructLogger { } impl EventListener for StructLogger { - fn event(&mut self, event: Event) { + fn event(&mut self, _executor_state: &impl Database, event: Event) { match event { Event::BeginVM { .. } => { self.depth += 1; @@ -203,8 +205,10 @@ impl EventListener for StructLogger { } }; } +} - fn into_traces(self: Box) -> Value { +impl Tracer for StructLogger { + fn into_traces(self) -> Value { let exit_status = self.exit_status.expect("Emulation is not completed"); let result = StructLoggerResult { gas: 0, diff --git a/evm_loader/program/Cargo.toml b/evm_loader/program/Cargo.toml index 575384cec..35518d8aa 100644 --- a/evm_loader/program/Cargo.toml +++ b/evm_loader/program/Cargo.toml @@ -51,7 +51,6 @@ borsh = "0.9" bincode = "1" serde_bytes = "0.11.12" serde = { version = "1.0.186", default-features = false, features = ["derive", "rc"] } -serde_json = { version = "1.0.107", features = ["preserve_order"], optional = true } ethnum = { version = "1.4", default-features = false, features = ["serde"] } cfg-if = { version = "1.0" } log = { version = "0.4", default-features = false, optional = true } @@ -62,11 +61,12 @@ async-trait = { version = "0.1.73", optional = true } version = "0.2.7" features = ["is_sync"] + [lib] crate-type = ["cdylib", "lib"] [dev-dependencies] -serde_json = "1.0.105" +serde_json = { version = "1.0.107", features = ["preserve_order"] } solana-program-test = "=1.16.23" solana-sdk = "=1.16.23" tokio = { version = "1.0", features = ["full"] } diff --git a/evm_loader/program/config/testnet.toml b/evm_loader/program/config/testnet.toml index 8ea5d890e..8e22e1371 100644 --- a/evm_loader/program/config/testnet.toml +++ b/evm_loader/program/config/testnet.toml @@ -229,6 +229,9 @@ operators_whitelist = [ "AuikYUkrP9bRCxPq99YpEkFCgWLS9KM2oe3sCPkTCEwr", "AyEE2tf4AezMxtBYXoWgoK1PwMDMsPfDahQRtZvU8BLc", "B5Gefd2yR3nBi4eFDtp3grmVsRq6sw4UYmGVZG6vrda3", + "7P1VpfLJNo1rMJbHmz2P6U34ygkRM5UNogknFUXP2b1k", + "8HzCjhBNP3rs7SydUrZAiQGEoqXHNtpNPE475zzHmzba", + "CRJ7MFYvMjXysVDkifFmiS8jmpDMS5qZRwyu3EN3Rfav", ] [chain.neon] diff --git a/evm_loader/program/src/account/ether_balance.rs b/evm_loader/program/src/account/ether_balance.rs index d84dd3925..dd316d95e 100644 --- a/evm_loader/program/src/account/ether_balance.rs +++ b/evm_loader/program/src/account/ether_balance.rs @@ -11,7 +11,7 @@ use crate::{ types::Address, }; use ethnum::U256; -use solana_program::{account_info::AccountInfo, pubkey::Pubkey, system_program}; +use solana_program::{account_info::AccountInfo, pubkey::Pubkey, rent::Rent, system_program}; use super::{AccountsDB, ACCOUNT_PREFIX_LEN, ACCOUNT_SEED_VERSION, TAG_ACCOUNT_BALANCE}; @@ -46,6 +46,7 @@ impl<'a> BalanceAccount<'a> { chain_id: u64, accounts: &AccountsDB<'a>, keys: Option<&KeysCache>, + rent: &Rent, ) -> Result { let (pubkey, bump_seed) = keys.map_or_else( || address.find_balance_address(&crate::ID, chain_id), @@ -93,6 +94,7 @@ impl<'a> BalanceAccount<'a> { &account, program_seeds, ACCOUNT_PREFIX_LEN + size_of::
(), + rent, )?; super::set_tag(&crate::ID, &account, TAG_ACCOUNT_BALANCE)?; diff --git a/evm_loader/program/src/account/ether_contract.rs b/evm_loader/program/src/account/ether_contract.rs index 4eb41c658..64c30a91b 100644 --- a/evm_loader/program/src/account/ether_contract.rs +++ b/evm_loader/program/src/account/ether_contract.rs @@ -78,7 +78,7 @@ impl<'a> ContractAccount<'a> { if system_program::check_id(info.owner) { let seeds: &[&[u8]] = &[&[ACCOUNT_SEED_VERSION], address.as_bytes(), &[bump_seed]]; let space = required_size.min(MAX_PERMITTED_DATA_INCREASE); - system.create_pda_account(&crate::ID, operator, info, seeds, space)?; + system.create_pda_account(&crate::ID, operator, info, seeds, space, rent)?; } else if crate::check_id(info.owner) { super::validate_tag(&crate::ID, info, TAG_EMPTY)?; diff --git a/evm_loader/program/src/account/ether_storage.rs b/evm_loader/program/src/account/ether_storage.rs index f047b4da9..1e6b1dc97 100644 --- a/evm_loader/program/src/account/ether_storage.rs +++ b/evm_loader/program/src/account/ether_storage.rs @@ -97,6 +97,7 @@ impl<'a> StorageCell<'a> { allocate_cells: usize, accounts: &AccountsDB<'a>, signer_seeds: &[&[u8]], + rent: &Rent, ) -> Result { let base_account = accounts.get(&address.base); let cell_account = accounts.get(&address.pubkey); @@ -114,6 +115,7 @@ impl<'a> StorageCell<'a> { cell_account, address.seed(), space, + rent, )?; super::set_tag(&crate::ID, cell_account, TAG_STORAGE_CELL)?; @@ -200,7 +202,7 @@ impl<'a> StorageCell<'a> { Ok(()) } - pub fn sync_lamports(&mut self, rent: Rent, accounts: &AccountsDB<'a>) -> Result<()> { + pub fn sync_lamports(&mut self, rent: &Rent, accounts: &AccountsDB<'a>) -> Result<()> { let original_data_len = unsafe { self.account.original_data_len() }; if original_data_len == self.account.data_len() { return Ok(()); diff --git a/evm_loader/program/src/account/holder.rs b/evm_loader/program/src/account/holder.rs index cb12fc852..2d015555d 100644 --- a/evm_loader/program/src/account/holder.rs +++ b/evm_loader/program/src/account/holder.rs @@ -7,7 +7,7 @@ use crate::account::TAG_STATE_FINALIZED; use crate::error::{Error, Result}; use crate::types::Transaction; -use super::{Operator, ACCOUNT_PREFIX_LEN, TAG_EMPTY, TAG_HOLDER}; +use super::{Operator, HOLDER_PREFIX_LEN, TAG_EMPTY, TAG_HOLDER}; /// Ethereum holder data account #[repr(C, packed)] @@ -21,7 +21,7 @@ pub struct Holder<'a> { account: AccountInfo<'a>, } -const HEADER_OFFSET: usize = ACCOUNT_PREFIX_LEN; +const HEADER_OFFSET: usize = HOLDER_PREFIX_LEN; const BUFFER_OFFSET: usize = HEADER_OFFSET + size_of::
(); impl<'a> Holder<'a> { diff --git a/evm_loader/program/src/account/legacy/mod.rs b/evm_loader/program/src/account/legacy/mod.rs index cf730df67..2a92e275c 100644 --- a/evm_loader/program/src/account/legacy/mod.rs +++ b/evm_loader/program/src/account/legacy/mod.rs @@ -1,6 +1,7 @@ mod legacy_ether; mod legacy_holder; mod legacy_storage_cell; +mod update; pub use legacy_ether::LegacyEtherData; pub use legacy_holder::LegacyFinalizedData; @@ -12,6 +13,8 @@ use solana_program::{account_info::AccountInfo, rent::Rent, sysvar::Sysvar}; use super::Holder; use super::StateFinalizedAccount; +use super::TAG_ACCOUNT_BALANCE; +use super::TAG_ACCOUNT_CONTRACT; use super::TAG_HOLDER; use super::TAG_STATE_FINALIZED; use super::{AccountsDB, ContractAccount, TAG_STORAGE_CELL}; @@ -21,19 +24,26 @@ use crate::{ error::Result, }; +// First version pub const TAG_STATE_DEPRECATED: u8 = 22; pub const TAG_STATE_FINALIZED_DEPRECATED: u8 = 31; pub const TAG_HOLDER_DEPRECATED: u8 = 51; pub const TAG_ACCOUNT_CONTRACT_DEPRECATED: u8 = 12; pub const TAG_STORAGE_CELL_DEPRECATED: u8 = 42; +// Before account revision (Holder and Finalized remain the same) +pub const TAG_STATE_BEFORE_REVISION: u8 = 23; +pub const TAG_ACCOUNT_BALANCE_BEFORE_REVISION: u8 = 60; +pub const TAG_ACCOUNT_CONTRACT_BEFORE_REVISION: u8 = 70; +pub const TAG_STORAGE_CELL_BEFORE_REVISION: u8 = 43; -fn reduce_account_size(account: &AccountInfo, required_len: usize) -> Result { +pub const ACCOUNT_PREFIX_LEN_BEFORE_REVISION: usize = 2; + +fn reduce_account_size(account: &AccountInfo, required_len: usize, rent: &Rent) -> Result { assert!(account.data_len() > required_len); account.realloc(required_len, false)?; // Return excessive lamports to the operator - let rent = Rent::get()?; let minimum_balance = rent.minimum_balance(account.data_len()); if account.lamports() > minimum_balance { let value = account.lamports() - minimum_balance; @@ -55,10 +65,11 @@ fn kill_account(account: &AccountInfo) -> Result { Ok(value) } -fn update_ether_account( +fn update_ether_account_from_v1( legacy_data: &LegacyEtherData, db: &AccountsDB, keys: &KeysCache, + rent: &Rent, ) -> Result { let pubkey = keys.contract(&crate::ID, legacy_data.address); let account = db.get(&pubkey); @@ -75,7 +86,7 @@ fn update_ether_account( // Make account smaller let required_len = ContractAccount::required_account_size(&code); - lamports_collected += reduce_account_size(account, required_len)?; + lamports_collected += reduce_account_size(account, required_len, rent)?; // Fill it with new data account.try_borrow_mut_data()?.fill(0); @@ -89,8 +100,6 @@ fn update_ether_account( Some(keys), )?; contract.set_storage_multiple_values(0, &storage); - - super::set_block(&crate::ID, account, legacy_data.rw_blocked)?; } else { // This is not contract. Just kill it. lamports_collected += kill_account(account)?; @@ -103,20 +112,20 @@ fn update_ether_account( crate::config::DEFAULT_CHAIN_ID, db, Some(keys), + rent, )?; balance.mint(legacy_data.balance)?; balance.increment_nonce_by(legacy_data.trx_count)?; - - super::set_block(&crate::ID, db.get(balance.pubkey()), legacy_data.rw_blocked)?; } Ok(lamports_collected) } -fn update_storage_account( +fn update_storage_account_from_v1( legacy_data: &LegacyStorageData, db: &AccountsDB, keys: &KeysCache, + rent: &Rent, ) -> Result { let mut lamports_collected = 0_u64; @@ -137,7 +146,7 @@ fn update_storage_account( // Make account smaller let required_len = StorageCell::required_account_size(cells.len()); - lamports_collected += reduce_account_size(&cell_account, required_len)?; + lamports_collected += reduce_account_size(&cell_account, required_len, rent)?; // Fill it with new data cell_account.try_borrow_mut_data()?.fill(0); @@ -155,7 +164,6 @@ pub fn update_holder_account(account: &AccountInfo) -> Result { let legacy_data = LegacyHolderData::from_account(&crate::ID, account)?; super::set_tag(&crate::ID, account, TAG_HOLDER)?; - super::set_block(&crate::ID, account, false)?; let mut holder = Holder::from_account(&crate::ID, account.clone())?; holder.update(|data| { @@ -170,7 +178,6 @@ pub fn update_holder_account(account: &AccountInfo) -> Result { let legacy_data = LegacyFinalizedData::from_account(&crate::ID, account)?; super::set_tag(&crate::ID, account, TAG_STATE_FINALIZED)?; - super::set_block(&crate::ID, account, false)?; let mut state = StateFinalizedAccount::from_account(&crate::ID, account.clone())?; state.update(|data| { @@ -190,6 +197,10 @@ pub fn update_legacy_accounts(accounts: &AccountsDB) -> Result { let mut lamports_collected = 0_u64; let mut legacy_storage = Vec::with_capacity(accounts.accounts_len()); + let rent = Rent::get()?; + let op = accounts.operator(); + let system = accounts.system(); + for account in accounts { if !crate::check_id(account.owner) { continue; @@ -203,18 +214,28 @@ pub fn update_legacy_accounts(accounts: &AccountsDB) -> Result { match tag { LegacyEtherData::TAG => { let legacy_data = LegacyEtherData::from_account(&crate::ID, account)?; - lamports_collected += update_ether_account(&legacy_data, accounts, &keys)?; + lamports_collected += + update_ether_account_from_v1(&legacy_data, accounts, &keys, &rent)?; } LegacyStorageData::TAG => { let legacy_data = LegacyStorageData::from_account(&crate::ID, account)?; legacy_storage.push(legacy_data); } + TAG_ACCOUNT_BALANCE_BEFORE_REVISION => { + update::from_before_revision(account, TAG_ACCOUNT_BALANCE, op, system, &rent)?; + } + TAG_ACCOUNT_CONTRACT_BEFORE_REVISION => { + update::from_before_revision(account, TAG_ACCOUNT_CONTRACT, op, system, &rent)?; + } + TAG_STORAGE_CELL_BEFORE_REVISION => { + update::from_before_revision(account, TAG_STORAGE_CELL, op, system, &rent)?; + } _ => {} } } for data in legacy_storage { - lamports_collected += update_storage_account(&data, accounts, &keys)?; + lamports_collected += update_storage_account_from_v1(&data, accounts, &keys, &rent)?; } Ok(lamports_collected) @@ -229,5 +250,9 @@ pub fn is_legacy_tag(tag: u8) -> bool { | TAG_HOLDER_DEPRECATED | TAG_STATE_FINALIZED_DEPRECATED | TAG_STATE_DEPRECATED + | TAG_STATE_BEFORE_REVISION + | TAG_ACCOUNT_BALANCE_BEFORE_REVISION + | TAG_ACCOUNT_CONTRACT_BEFORE_REVISION + | TAG_STORAGE_CELL_BEFORE_REVISION ) } diff --git a/evm_loader/program/src/account/legacy/update.rs b/evm_loader/program/src/account/legacy/update.rs new file mode 100644 index 000000000..27e9ff817 --- /dev/null +++ b/evm_loader/program/src/account/legacy/update.rs @@ -0,0 +1,37 @@ +use crate::account::legacy::ACCOUNT_PREFIX_LEN_BEFORE_REVISION; +use solana_program::account_info::AccountInfo; +use solana_program::rent::Rent; + +use crate::account::program::System; +use crate::account::Operator; +use crate::account::ACCOUNT_PREFIX_LEN; +use crate::error::Result; + +pub fn from_before_revision<'a>( + account: &AccountInfo<'a>, + new_tag: u8, + operator: &Operator<'a>, + system: &System<'a>, + rent: &Rent, +) -> Result<()> { + const PREFIX_BEFORE: usize = ACCOUNT_PREFIX_LEN_BEFORE_REVISION; + const PREFIX_AFTER: usize = ACCOUNT_PREFIX_LEN; + + assert!(account.data_len() > PREFIX_BEFORE); + let data_len = account.data_len() - PREFIX_BEFORE; + + let required_len = account.data_len() + PREFIX_AFTER - PREFIX_BEFORE; + account.realloc(required_len, false)?; + + let minimum_balance = rent.minimum_balance(required_len); + if account.lamports() < minimum_balance { + let required_lamports = minimum_balance - account.lamports(); + system.transfer(operator, account, required_lamports)?; + } + + let mut account_data = account.try_borrow_mut_data()?; + account_data.copy_within(PREFIX_BEFORE..(PREFIX_BEFORE + data_len), PREFIX_AFTER); + account_data[0] = new_tag; + + Ok(()) +} diff --git a/evm_loader/program/src/account/mod.rs b/evm_loader/program/src/account/mod.rs index 8ba611a8c..0be5568cd 100644 --- a/evm_loader/program/src/account/mod.rs +++ b/evm_loader/program/src/account/mod.rs @@ -6,13 +6,13 @@ use std::cell::{Ref, RefMut}; pub use crate::config::ACCOUNT_SEED_VERSION; -pub use ether_balance::BalanceAccount; -pub use ether_contract::{AllocateResult, ContractAccount}; +pub use ether_balance::{BalanceAccount, Header as BalanceHeader}; +pub use ether_contract::{AllocateResult, ContractAccount, Header as ContractHeader}; pub use ether_storage::{StorageCell, StorageCellAddress}; pub use holder::Holder; pub use incinerator::Incinerator; pub use operator::Operator; -pub use state::StateAccount; +pub use state::{AccountsStatus, StateAccount}; pub use state_finalized::StateFinalizedAccount; pub use treasury::{MainTreasury, Treasury}; @@ -32,15 +32,16 @@ pub mod token; mod treasury; pub const TAG_EMPTY: u8 = 0; -pub const TAG_STATE: u8 = 23; +pub const TAG_STATE: u8 = 24; pub const TAG_STATE_FINALIZED: u8 = 32; pub const TAG_HOLDER: u8 = 52; -pub const TAG_ACCOUNT_BALANCE: u8 = 60; -pub const TAG_ACCOUNT_CONTRACT: u8 = 70; -pub const TAG_STORAGE_CELL: u8 = 43; +pub const TAG_ACCOUNT_BALANCE: u8 = 61; +pub const TAG_ACCOUNT_CONTRACT: u8 = 71; +pub const TAG_STORAGE_CELL: u8 = 44; -const ACCOUNT_PREFIX_LEN: usize = 2; +pub const ACCOUNT_PREFIX_LEN: usize = 1/*tag*/ + 4/*revision*/; +pub const HOLDER_PREFIX_LEN: usize = 1/*tag*/ + 1/*reserved*/; #[inline] fn section<'r, T>(account: &'r AccountInfo<'_>, offset: usize) -> Ref<'r, T> { @@ -106,7 +107,7 @@ pub fn validate_tag(program_id: &Pubkey, info: &AccountInfo, tag: u8) -> Result< } } -pub fn is_blocked(program_id: &Pubkey, info: &AccountInfo) -> Result { +pub fn revision(program_id: &Pubkey, info: &AccountInfo) -> Result { if info.owner != program_id { return Err(Error::AccountInvalidOwner(*info.key, *program_id)); } @@ -116,43 +117,27 @@ pub fn is_blocked(program_id: &Pubkey, info: &AccountInfo) -> Result { return Err(Error::AccountInvalidData(*info.key)); } - if legacy::is_legacy_tag(data[0]) { - return Err(Error::AccountLegacy(*info.key)); - } - - Ok(data[1] == 1) + let buffer = arrayref::array_ref![data, 1, 4]; + Ok(u32::from_le_bytes(*buffer)) } -#[inline] -fn set_block(program_id: &Pubkey, info: &AccountInfo, block: bool) -> Result<()> { - assert_eq!(info.owner, program_id); +pub fn increment_revision(program_id: &Pubkey, info: &AccountInfo) -> Result<()> { + if info.owner != program_id { + return Err(Error::AccountInvalidOwner(*info.key, *program_id)); + } let mut data = info.try_borrow_mut_data()?; if data.len() < ACCOUNT_PREFIX_LEN { return Err(Error::AccountInvalidData(*info.key)); } - if legacy::is_legacy_tag(data[0]) { - return Err(Error::AccountLegacy(*info.key)); - } - - if block && (data[1] != 0) { - return Err(Error::AccountBlocked(*info.key)); - } - - data[1] = block.into(); + let buffer = arrayref::array_mut_ref![data, 1, 4]; + let revision = u32::from_le_bytes(*buffer); + *buffer = (revision + 1).to_le_bytes(); Ok(()) } -pub fn block(program_id: &Pubkey, info: &AccountInfo) -> Result<()> { - set_block(program_id, info, true) -} - -pub fn unblock(program_id: &Pubkey, info: &AccountInfo) -> Result<()> { - set_block(program_id, info, false) -} - /// # Safety /// *Permanently delete all data* in the account. Transfer lamports to the operator. pub unsafe fn delete(account: &AccountInfo, operator: &Operator) { diff --git a/evm_loader/program/src/account/program.rs b/evm_loader/program/src/account/program.rs index 756d01220..0acdd48f0 100644 --- a/evm_loader/program/src/account/program.rs +++ b/evm_loader/program/src/account/program.rs @@ -3,7 +3,7 @@ use solana_program::account_info::AccountInfo; use solana_program::program::{invoke_signed_unchecked, invoke_unchecked}; use solana_program::program_error::ProgramError; use solana_program::pubkey::Pubkey; -use solana_program::{rent::Rent, system_instruction, sysvar::Sysvar}; +use solana_program::{rent::Rent, system_instruction}; use std::convert::From; use std::ops::Deref; @@ -31,8 +31,8 @@ impl<'a> System<'a> { new_account: &AccountInfo<'a>, new_account_seeds: &[&[u8]], space: usize, + rent: &Rent, ) -> Result<(), ProgramError> { - let rent = Rent::get()?; let minimum_balance = rent.minimum_balance(space).max(1); if new_account.lamports() > 0 { @@ -81,8 +81,9 @@ impl<'a> System<'a> { new_account: &AccountInfo<'a>, seed: &str, space: usize, + rent: &Rent, ) -> Result<(), ProgramError> { - let minimum_balance = Rent::get()?.minimum_balance(space).max(1); + let minimum_balance = rent.minimum_balance(space).max(1); if new_account.lamports() > 0 { let required_lamports = minimum_balance.saturating_sub(new_account.lamports()); diff --git a/evm_loader/program/src/account/state.rs b/evm_loader/program/src/account/state.rs index 53d22e382..f4e7b147f 100644 --- a/evm_loader/program/src/account/state.rs +++ b/evm_loader/program/src/account/state.rs @@ -1,66 +1,72 @@ use std::cell::{Ref, RefMut}; +use std::collections::BTreeMap; use std::mem::size_of; -use crate::config::GAS_LIMIT_MULTIPLIER_NO_CHAINID; +use crate::account_storage::AccountStorage; +use crate::config::DEFAULT_CHAIN_ID; use crate::error::{Error, Result}; use crate::types::{Address, Transaction}; use ethnum::U256; -use solana_program::clock::Clock; -use solana_program::program_error::ProgramError; -use solana_program::sysvar::Sysvar; +use serde::{Deserialize, Serialize}; use solana_program::{account_info::AccountInfo, pubkey::Pubkey}; use super::{ - AccountsDB, BalanceAccount, Operator, ACCOUNT_PREFIX_LEN, TAG_EMPTY, TAG_HOLDER, TAG_STATE, - TAG_STATE_FINALIZED, + revision, AccountsDB, BalanceAccount, Holder, StateFinalizedAccount, HOLDER_PREFIX_LEN, + TAG_HOLDER, TAG_STATE, TAG_STATE_FINALIZED, }; +#[derive(PartialEq, Eq)] +pub enum AccountsStatus { + Ok, + RevisionChanged, +} + /// Storage data account to store execution metainfo between steps for iterative execution -#[repr(C, packed)] -pub struct Header { +#[derive(Serialize, Deserialize)] +struct Data { pub owner: Pubkey, - pub transaction_hash: [u8; 32], + pub transaction: Transaction, /// Ethereum transaction caller address pub origin: Address, - /// Ethereum transaction chain_id - pub chain_id: u64, - /// Ethereum transaction gas limit - pub gas_limit: U256, - /// Ethereum transaction gas price - pub gas_price: U256, + /// Stored accounts + pub revisions: BTreeMap, /// Ethereum transaction gas used and paid + #[serde(with = "ethnum::serde::bytes::le")] pub gas_used: U256, - /// Operator public key - pub operator: Pubkey, - /// Starting slot for this operator - pub slot: u64, - /// Stored accounts length - pub accounts_len: usize, - /// Stored EVM State length - pub evm_state_len: usize, - /// Stored EVM Machine length - pub evm_machine_len: usize, } #[repr(C, packed)] -pub struct BlockedAccount { - pub is_writable: bool, - pub blocked: bool, - pub key: Pubkey, +struct Header { + pub evm_state_len: usize, + pub evm_machine_len: usize, + pub data_len: usize, } pub struct StateAccount<'a> { account: AccountInfo<'a>, + data: Data, } -const HEADER_OFFSET: usize = ACCOUNT_PREFIX_LEN; -const BLOCKED_ACCOUNTS_OFFSET: usize = HEADER_OFFSET + size_of::
(); +const HEADER_OFFSET: usize = HOLDER_PREFIX_LEN; +const BUFFER_OFFSET: usize = HEADER_OFFSET + size_of::
(); impl<'a> StateAccount<'a> { pub fn from_account(program_id: &Pubkey, account: AccountInfo<'a>) -> Result { super::validate_tag(program_id, &account, TAG_STATE)?; - Ok(Self { account }) + let (offset, len) = { + let header = super::section::
(&account, HEADER_OFFSET); + let offset = BUFFER_OFFSET + header.evm_state_len + header.evm_machine_len; + (offset, header.data_len) + }; + + let data = { + let account_data = account.try_borrow_data()?; + let buffer = &account_data[offset..(offset + len)]; + bincode::deserialize(buffer)? + }; + + Ok(Self { account, data }) } pub fn new( @@ -68,108 +74,101 @@ impl<'a> StateAccount<'a> { info: AccountInfo<'a>, accounts: &AccountsDB<'a>, origin: Address, - trx: &Transaction, + transaction: Transaction, ) -> Result { - let tag = super::tag(program_id, &info)?; - if matches!(tag, TAG_HOLDER | TAG_STATE_FINALIZED) { - super::set_tag(program_id, &info, TAG_STATE)?; - } - - let mut state = Self::from_account(program_id, info)?; - state.validate_owner(accounts.operator())?; - - if (tag == TAG_STATE_FINALIZED) && (state.trx_hash() == trx.hash) { - return Err(Error::StorageAccountFinalized); - } - - // Set header + let owner = match super::tag(program_id, &info)? { + TAG_HOLDER => { + let holder = Holder::from_account(program_id, info.clone())?; + holder.validate_owner(accounts.operator())?; + holder.owner() + } + TAG_STATE_FINALIZED => { + let finalized = StateFinalizedAccount::from_account(program_id, info.clone())?; + finalized.validate_owner(accounts.operator())?; + finalized.validate_trx(&transaction)?; + finalized.owner() + } + tag => return Err(Error::AccountInvalidTag(*info.key, tag)), + }; + + // todo: get revision from account + let revisions = accounts + .into_iter() + .map(|account| { + let revision = revision(program_id, account).unwrap_or(0); + (*account.key, revision) + }) + .collect(); + + let data = Data { + owner, + transaction, + origin, + revisions, + gas_used: U256::ZERO, + }; + + super::set_tag(program_id, &info, TAG_STATE)?; { - let mut header = state.header_mut(); - header.transaction_hash = trx.hash(); - header.origin = origin; - header.chain_id = trx.chain_id().unwrap_or(crate::config::DEFAULT_CHAIN_ID); - header.gas_limit = trx.gas_limit(); - header.gas_price = trx.gas_price(); - header.gas_used = U256::ZERO; - header.operator = accounts.operator_key(); - header.slot = Clock::get()?.slot; - header.accounts_len = accounts.accounts_len(); - header.evm_machine_len = 0; + // Set header + let mut header = super::section_mut::
(&info, HEADER_OFFSET); header.evm_state_len = 0; - } - // Block accounts - for (block, account) in state.blocked_accounts_mut().iter_mut().zip(accounts) { - block.is_writable = account.is_writable; - block.key = *account.key; - if (account.owner == program_id) && !account.data_is_empty() { - super::block(program_id, account)?; - block.blocked = true; - } else { - block.blocked = false; - } + header.evm_machine_len = 0; + header.data_len = 0; } - Ok(state) + Ok(Self { + account: info, + data, + }) } pub fn restore( program_id: &Pubkey, info: AccountInfo<'a>, accounts: &AccountsDB, - is_canceling: bool, - ) -> Result { + ) -> Result<(Self, AccountsStatus)> { let mut state = Self::from_account(program_id, info)?; - - if state.blocked_accounts_len() != accounts.accounts_len() { - return Err(ProgramError::NotEnoughAccountKeys.into()); - } - - // Check blocked accounts - for (block, account) in state.blocked_accounts().iter().zip(accounts) { - if &block.key != account.key { - return Err(Error::AccountInvalidKey(*account.key, block.key)); - } - - if block.is_writable && !account.is_writable { - return Err(Error::AccountNotWritable(*account.key)); - } - - if !is_canceling && (account.owner == program_id) && !block.blocked { - if super::is_blocked(program_id, account)? { - return Err(Error::AccountCreatedByAnotherTransaction(*account.key)); - } - - super::validate_tag(program_id, account, TAG_EMPTY) - .map_err(|_| Error::AccountCreatedByAnotherTransaction(*account.key))?; + let mut status = AccountsStatus::Ok; + + for account in accounts { + let account_revision = revision(program_id, account).unwrap_or(0); + let stored_revision = state + .data + .revisions + .entry(*account.key) + .or_insert(account_revision); + + if stored_revision != &account_revision { + status = AccountsStatus::RevisionChanged; + *stored_revision = account_revision; } } - state.update_current_operator(&accounts.operator); - - Ok(state) + Ok((state, status)) } - pub fn finalize(self, program_id: &Pubkey, accounts: &AccountsDB) -> Result<()> { + pub fn finalize(self, program_id: &Pubkey) -> Result<()> { debug_print!("Finalize Storage {}", self.account.key); - // Unblock accounts - for (block, account) in self.blocked_accounts().iter().zip(accounts) { - if &block.key != account.key { - return Err(Error::AccountInvalidKey(*account.key, block.key)); - } - - if !block.blocked { - continue; - } - - super::unblock(program_id, account)?; - } + let owner = self.owner(); + let trx_hash = self.trx().hash(); // Change tag to finalized let account = self.account.clone(); std::mem::drop(self); - super::set_tag(account.owner, &account, TAG_STATE_FINALIZED) + super::set_tag(account.owner, &account, TAG_STATE_FINALIZED)?; + StateFinalizedAccount::from_account(program_id, account)?.update(|f| { + f.owner = owner; + f.transaction_hash = trx_hash; + }); + + Ok(()) + } + + pub fn accounts(&self) -> impl Iterator { + self.data.revisions.keys() } #[inline] @@ -184,62 +183,16 @@ impl<'a> StateAccount<'a> { super::section_mut(&self.account, HEADER_OFFSET) } - #[inline] - #[must_use] - fn blocked_accounts_len(&self) -> usize { - self.header().accounts_len - } - - #[inline] - #[must_use] - pub fn blocked_accounts(&self) -> Ref<[BlockedAccount]> { - let accounts_len = self.blocked_accounts_len(); - let accounts_len_bytes = accounts_len * size_of::(); - - let data = self.account.data.borrow(); - Ref::map(data, |d| { - let bytes = &d[BLOCKED_ACCOUNTS_OFFSET..][..accounts_len_bytes]; - - unsafe { - let ptr = bytes.as_ptr().cast(); - std::slice::from_raw_parts(ptr, accounts_len) - } - }) - } - - #[inline] - #[must_use] - fn blocked_accounts_mut(&mut self) -> RefMut<[BlockedAccount]> { - let accounts_len = self.blocked_accounts_len(); - let accounts_len_bytes = accounts_len * size_of::(); - - let data = self.account.data.borrow_mut(); - RefMut::map(data, |d| { - let bytes: &mut [u8] = &mut d[BLOCKED_ACCOUNTS_OFFSET..][..accounts_len_bytes]; - - unsafe { - let ptr = bytes.as_mut_ptr().cast(); - std::slice::from_raw_parts_mut(ptr, accounts_len) - } - }) - } - #[must_use] pub fn buffer(&self) -> Ref<[u8]> { - let accounts_len_bytes = self.blocked_accounts_len() * size_of::(); - let buffer_offset = BLOCKED_ACCOUNTS_OFFSET + accounts_len_bytes; - let data = self.account.data.borrow(); - Ref::map(data, |d| &d[buffer_offset..]) + Ref::map(data, |d| &d[BUFFER_OFFSET..]) } #[must_use] pub fn buffer_mut(&mut self) -> RefMut<[u8]> { - let accounts_len_bytes = self.blocked_accounts_len() * size_of::(); - let buffer_offset = BLOCKED_ACCOUNTS_OFFSET + accounts_len_bytes; - let data = self.account.data.borrow_mut(); - RefMut::map(data, |d| &mut d[buffer_offset..]) + RefMut::map(data, |d| &mut d[BUFFER_OFFSET..]) } #[must_use] @@ -254,69 +207,56 @@ impl<'a> StateAccount<'a> { header.evm_machine_len = evm_machine_len; } - #[must_use] - pub fn owner(&self) -> Pubkey { - self.header().owner - } + pub fn save_data(&mut self) -> Result<()> { + let (evm_state_len, evm_machine_len) = self.buffer_variables(); + let offset = BUFFER_OFFSET + evm_state_len + evm_machine_len; - fn validate_owner(&self, operator: &Operator) -> Result<()> { - let owner = self.owner(); - let operator = *operator.key; + let data_len: usize = { + let mut data = self.account.data.borrow_mut(); + let buffer = &mut data[offset..]; - if owner != operator { - return Err(Error::HolderInvalidOwner(owner, operator)); - } + let mut cursor = std::io::Cursor::new(buffer); + bincode::serialize_into(&mut cursor, &self.data)?; - Ok(()) - } + cursor.position().try_into()? + }; - fn update_current_operator(&mut self, operator: &Operator) { - let mut header = self.header_mut(); - header.operator = *operator.key; - } + self.header_mut().data_len = data_len; - #[must_use] - pub fn trx_hash(&self) -> [u8; 32] { - self.header().transaction_hash + Ok(()) } #[must_use] - pub fn trx_origin(&self) -> Address { - self.header().origin + pub fn owner(&self) -> Pubkey { + self.data.owner } #[must_use] - pub fn trx_chain_id(&self) -> u64 { - self.header().chain_id + pub fn trx(&self) -> &Transaction { + &self.data.transaction } #[must_use] - pub fn trx_gas_price(&self) -> U256 { - self.header().gas_price + pub fn trx_origin(&self) -> Address { + self.data.origin } #[must_use] - pub fn trx_gas_limit(&self) -> U256 { - self.header().gas_limit - } - - pub fn gas_limit_in_tokens(&self) -> Result { - let header = self.header(); - header - .gas_limit - .checked_mul(header.gas_price) - .ok_or(Error::IntegerOverflow) + pub fn trx_chain_id(&self, backend: &impl AccountStorage) -> u64 { + self.data + .transaction + .chain_id() + .unwrap_or_else(|| backend.default_chain_id()) } #[must_use] pub fn gas_used(&self) -> U256 { - self.header().gas_used + self.data.gas_used } #[must_use] pub fn gas_available(&self) -> U256 { - let header = self.header(); - header.gas_limit.saturating_sub(header.gas_used) + self.trx().gas_limit().saturating_sub(self.gas_used()) } pub fn consume_gas(&mut self, amount: U256, receiver: &mut BalanceAccount) -> Result<()> { @@ -324,39 +264,33 @@ impl<'a> StateAccount<'a> { return Ok(()); } - let mut header = self.header_mut(); - - if receiver.chain_id() != header.chain_id { + let trx_chain_id = self.trx().chain_id().unwrap_or(DEFAULT_CHAIN_ID); + if receiver.chain_id() != trx_chain_id { return Err(Error::GasReceiverInvalidChainId); } - let total_gas_used = header.gas_used.saturating_add(amount); - let gas_limit = header.gas_limit; + let total_gas_used = self.data.gas_used.saturating_add(amount); + let gas_limit = self.trx().gas_limit(); if total_gas_used > gas_limit { return Err(Error::OutOfGas(gas_limit, total_gas_used)); } - header.gas_used = total_gas_used; + self.data.gas_used = total_gas_used; let tokens = amount - .checked_mul(header.gas_price) + .checked_mul(self.trx().gas_price()) .ok_or(Error::IntegerOverflow)?; receiver.mint(tokens) } pub fn refund_unused_gas(&mut self, origin: &mut BalanceAccount) -> Result<()> { - assert!(origin.chain_id() == self.trx_chain_id()); + let trx_chain_id = self.trx().chain_id().unwrap_or(DEFAULT_CHAIN_ID); + + assert!(origin.chain_id() == trx_chain_id); assert!(origin.address() == self.trx_origin()); let unused_gas = self.gas_available(); self.consume_gas(unused_gas, origin) } - - pub fn use_gas_limit_multiplier(&mut self) { - let mut header = self.header_mut(); - - let gas_multiplier = U256::from(GAS_LIMIT_MULTIPLIER_NO_CHAINID); - header.gas_limit = header.gas_limit.saturating_mul(gas_multiplier); - } } diff --git a/evm_loader/program/src/account/state_finalized.rs b/evm_loader/program/src/account/state_finalized.rs index 5e73c9801..03e07d43e 100644 --- a/evm_loader/program/src/account/state_finalized.rs +++ b/evm_loader/program/src/account/state_finalized.rs @@ -1,7 +1,10 @@ use std::cell::{Ref, RefMut}; -use super::{ACCOUNT_PREFIX_LEN, TAG_STATE_FINALIZED}; -use crate::error::Result; +use super::{Operator, HOLDER_PREFIX_LEN, TAG_STATE_FINALIZED}; +use crate::{ + error::{Error, Result}, + types::Transaction, +}; use solana_program::{account_info::AccountInfo, pubkey::Pubkey}; /// Storage data account to store execution metainfo between steps for iterative execution @@ -15,7 +18,7 @@ pub struct StateFinalizedAccount<'a> { account: AccountInfo<'a>, } -const HEADER_OFFSET: usize = ACCOUNT_PREFIX_LEN; +const HEADER_OFFSET: usize = HOLDER_PREFIX_LEN; impl<'a> StateFinalizedAccount<'a> { pub fn from_account(program_id: &Pubkey, account: AccountInfo<'a>) -> Result { @@ -52,4 +55,20 @@ impl<'a> StateFinalizedAccount<'a> { pub fn trx_hash(&self) -> [u8; 32] { self.header().transaction_hash } + + pub fn validate_owner(&self, operator: &Operator) -> Result<()> { + if &self.owner() != operator.key { + return Err(Error::HolderInvalidOwner(self.owner(), *operator.key)); + } + + Ok(()) + } + + pub fn validate_trx(&self, transaction: &Transaction) -> Result<()> { + if self.trx_hash() == transaction.hash { + return Err(Error::StorageAccountFinalized); + } + + Ok(()) + } } diff --git a/evm_loader/program/src/account_storage/apply.rs b/evm_loader/program/src/account_storage/apply.rs index 5ca25e02a..cd755e577 100644 --- a/evm_loader/program/src/account_storage/apply.rs +++ b/evm_loader/program/src/account_storage/apply.rs @@ -1,15 +1,15 @@ +use std::collections::{HashMap, HashSet}; + use ethnum::U256; use solana_program::account_info::AccountInfo; use solana_program::instruction::Instruction; use solana_program::program::{invoke_signed_unchecked, invoke_unchecked}; -use solana_program::rent::Rent; +use solana_program::pubkey::Pubkey; use solana_program::system_program; -use solana_program::sysvar::Sysvar; -use std::collections::HashMap; -use crate::account::BalanceAccount; +use crate::account::{increment_revision, BalanceAccount}; use crate::account::{AllocateResult, ContractAccount, StorageCell}; -use crate::account_storage::ProgramAccountStorage; +use crate::account_storage::{AccountStorage, ProgramAccountStorage}; use crate::config::{ ACCOUNT_SEED_VERSION, PAYMENT_TO_TREASURE, STORAGE_ENTRIES_IN_CONTRACT_ACCOUNT, }; @@ -47,14 +47,12 @@ impl<'a> ProgramAccountStorage<'a> { pub fn allocate(&mut self, actions: &[Action]) -> Result { let mut total_result = AllocateResult::Ready; - let rent = Rent::get()?; - for action in actions { if let Action::EvmSetCode { address, code, .. } = action { let result = ContractAccount::allocate( *address, code, - &rent, + &self.rent, &self.accounts, Some(&self.keys), )?; @@ -71,6 +69,7 @@ impl<'a> ProgramAccountStorage<'a> { pub fn apply_state_change(&mut self, actions: Vec) -> Result<()> { debug_print!("Applies begin"); + let mut modified_accounts: HashSet = HashSet::with_capacity(64); let mut storage = HashMap::with_capacity(16); for action in actions { @@ -84,6 +83,9 @@ impl<'a> ProgramAccountStorage<'a> { let mut source = self.balance_account(source, chain_id)?; let mut target = self.create_balance_account(target, chain_id)?; source.transfer(&mut target, value)?; + + modified_accounts.insert(*source.pubkey()); + modified_accounts.insert(*target.pubkey()); } Action::Burn { source, @@ -92,6 +94,8 @@ impl<'a> ProgramAccountStorage<'a> { } => { let mut account = self.create_balance_account(source, chain_id)?; account.burn(value)?; + + modified_accounts.insert(*account.pubkey()); } Action::EvmSetStorage { address, @@ -106,13 +110,15 @@ impl<'a> ProgramAccountStorage<'a> { Action::EvmIncrementNonce { address, chain_id } => { let mut account = self.create_balance_account(address, chain_id)?; account.increment_nonce()?; + + // Nonce increment is not count as account modification } Action::EvmSetCode { address, chain_id, code, } => { - ContractAccount::init( + let account = ContractAccount::init( address, chain_id, 0, @@ -120,6 +126,8 @@ impl<'a> ProgramAccountStorage<'a> { &self.accounts, Some(&self.keys), )?; + + modified_accounts.insert(*account.pubkey()); } Action::EvmSelfDestruct { address: _ } => { // EIP-6780: SELFDESTRUCT only in the same transaction @@ -168,25 +176,38 @@ impl<'a> ProgramAccountStorage<'a> { } } - self.apply_storage(storage)?; + self.apply_storage(storage, &mut modified_accounts)?; + + for pubkey in modified_accounts { + let account = self.accounts.get(&pubkey); + increment_revision(self.program_id(), account)?; + } + debug_print!("Applies done"); Ok(()) } - fn apply_storage(&mut self, storage: HashMap>) -> Result<()> { + fn apply_storage( + &mut self, + storage: HashMap>, + modified_accounts: &mut HashSet, + ) -> Result<()> { const STATIC_STORAGE_LIMIT: U256 = U256::new(STORAGE_ENTRIES_IN_CONTRACT_ACCOUNT as u128); - let rent = Rent::get()?; - for (address, storage) in storage { - let mut contract = self.contract_account(address)?; + let mut contract: Option = None; let mut infinite_values: HashMap> = HashMap::with_capacity(storage.len()); for (index, value) in storage { if index < STATIC_STORAGE_LIMIT { + let contract = contract.get_or_insert_with(|| { + self.contract_account(address) + .expect("contract already created") + }); + // Static Storage - Write into contract account let index: usize = index.as_usize(); contract.set_storage_value(index, &value); @@ -202,16 +223,23 @@ impl<'a> ProgramAccountStorage<'a> { } } + if let Some(contract) = contract { + modified_accounts.insert(*contract.pubkey()); + } + for (index, values) in infinite_values { let cell_address = self.keys.storage_cell_address(&crate::ID, address, index); let account = self.accounts.get(cell_address.pubkey()); + modified_accounts.insert(*account.key); + if system_program::check_id(account.owner) { let (_, bump) = self.keys.contract_with_bump_seed(&crate::ID, address); let sign: &[&[u8]] = &[&[ACCOUNT_SEED_VERSION], address.as_bytes(), &[bump]]; let len = values.len(); - let mut storage = StorageCell::create(cell_address, len, &self.accounts, sign)?; + let mut storage = + StorageCell::create(cell_address, len, &self.accounts, sign, &self.rent)?; let mut cells = storage.cells_mut(); assert_eq!(cells.len(), len); @@ -225,7 +253,7 @@ impl<'a> ProgramAccountStorage<'a> { storage.update(subindex, &value)?; } - storage.sync_lamports(rent, &self.accounts)?; + storage.sync_lamports(&self.rent, &self.accounts)?; }; } } diff --git a/evm_loader/program/src/account_storage/backend.rs b/evm_loader/program/src/account_storage/backend.rs index ad9bde9e6..13e48a008 100644 --- a/evm_loader/program/src/account_storage/backend.rs +++ b/evm_loader/program/src/account_storage/backend.rs @@ -6,7 +6,7 @@ use crate::types::Address; use ethnum::U256; use solana_program::account_info::AccountInfo; use solana_program::instruction::AccountMeta; -use solana_program::{pubkey::Pubkey, sysvar::slot_hashes}; +use solana_program::{pubkey::Pubkey, rent::Rent, sysvar::slot_hashes}; use std::collections::BTreeMap; use std::convert::TryInto; @@ -30,6 +30,14 @@ impl<'a> AccountStorage for ProgramAccountStorage<'a> { .expect("Timestamp is positive") } + fn rent(&self) -> &Rent { + &self.rent + } + + fn return_data(&self) -> Option<(Pubkey, Vec)> { + solana_program::program::get_return_data() + } + fn block_hash(&self, slot: u64) -> [u8; 32] { let slot_hashes_account = self.accounts.get(&slot_hashes::ID); let slot_hashes_data = slot_hashes_account.data.borrow(); diff --git a/evm_loader/program/src/account_storage/base.rs b/evm_loader/program/src/account_storage/base.rs index 3169d66a1..df43c2ae4 100644 --- a/evm_loader/program/src/account_storage/base.rs +++ b/evm_loader/program/src/account_storage/base.rs @@ -4,18 +4,18 @@ use crate::account::{ use crate::account_storage::ProgramAccountStorage; use crate::config::DEFAULT_CHAIN_ID; use crate::error::Result; -use crate::types::Address; +use crate::types::{Address, Transaction}; use ethnum::U256; -use solana_program::clock::Clock; -use solana_program::system_program; -use solana_program::sysvar::Sysvar; +use solana_program::{clock::Clock, rent::Rent, system_program, sysvar::Sysvar}; use super::keys_cache::KeysCache; +use super::AccountStorage; impl<'a> ProgramAccountStorage<'a> { pub fn new(accounts: AccountsDB<'a>) -> Result { Ok(Self { clock: Clock::get()?, + rent: Rent::get()?, accounts, keys: KeysCache::new(), }) @@ -88,6 +88,23 @@ impl<'a> ProgramAccountStorage<'a> { address: Address, chain_id: u64, ) -> Result> { - BalanceAccount::create(address, chain_id, &self.accounts, Some(&self.keys)) + BalanceAccount::create( + address, + chain_id, + &self.accounts, + Some(&self.keys), + &self.rent, + ) + } + + pub fn origin( + &self, + address: Address, + transaction: &Transaction, + ) -> Result> { + let chain_id = transaction + .chain_id() + .unwrap_or_else(|| self.default_chain_id()); + self.create_balance_account(address, chain_id) } } diff --git a/evm_loader/program/src/account_storage/mod.rs b/evm_loader/program/src/account_storage/mod.rs index 135b7e092..2d152af6e 100644 --- a/evm_loader/program/src/account_storage/mod.rs +++ b/evm_loader/program/src/account_storage/mod.rs @@ -5,6 +5,7 @@ use ethnum::U256; use maybe_async::maybe_async; use solana_program::{ account_info::AccountInfo, instruction::AccountMeta, instruction::Instruction, pubkey::Pubkey, + rent::Rent, }; use std::collections::BTreeMap; #[cfg(target_os = "solana")] @@ -28,6 +29,7 @@ pub use keys_cache::KeysCache; #[cfg(target_os = "solana")] pub struct ProgramAccountStorage<'a> { clock: Clock, + rent: Rent, accounts: AccountsDB<'a>, keys: keys_cache::KeysCache, } @@ -48,6 +50,12 @@ pub trait AccountStorage { /// Get block hash async fn block_hash(&self, number: u64) -> [u8; 32]; + /// Get rent info + fn rent(&self) -> &Rent; + + /// Get return data from Solana + fn return_data(&self) -> Option<(Pubkey, Vec)>; + /// Get account nonce async fn nonce(&self, address: Address, chain_id: u64) -> u64; /// Get account balance diff --git a/evm_loader/program/src/account_storage/synced.rs b/evm_loader/program/src/account_storage/synced.rs index 30381e7f1..017aec824 100644 --- a/evm_loader/program/src/account_storage/synced.rs +++ b/evm_loader/program/src/account_storage/synced.rs @@ -2,9 +2,7 @@ use ethnum::U256; use solana_program::account_info::AccountInfo; use solana_program::instruction::Instruction; use solana_program::program::{invoke_signed_unchecked, invoke_unchecked}; -use solana_program::rent::Rent; use solana_program::system_program; -use solana_program::sysvar::Sysvar; use crate::account::{AllocateResult, ContractAccount, StorageCell}; use crate::account_storage::SyncedAccountStorage; @@ -14,9 +12,13 @@ use crate::types::Address; impl<'a> SyncedAccountStorage for crate::account_storage::ProgramAccountStorage<'a> { fn set_code(&mut self, address: Address, chain_id: u64, code: Vec) -> Result<()> { - let rent = Rent::get()?; - let result = - ContractAccount::allocate(address, &code, &rent, &self.accounts, Some(&self.keys))?; + let result = ContractAccount::allocate( + address, + &code, + &self.rent, + &self.accounts, + Some(&self.keys), + )?; if result != AllocateResult::Ready { return Err(crate::error::Error::AccountSpaceAllocationFailure); @@ -36,7 +38,6 @@ impl<'a> SyncedAccountStorage for crate::account_storage::ProgramAccountStorage< fn set_storage(&mut self, address: Address, index: U256, value: [u8; 32]) -> Result<()> { const STATIC_STORAGE_LIMIT: U256 = U256::new(STORAGE_ENTRIES_IN_CONTRACT_ACCOUNT as u128); - let rent = Rent::get()?; if index < STATIC_STORAGE_LIMIT { // Static Storage - Write into contract account @@ -51,7 +52,8 @@ impl<'a> SyncedAccountStorage for crate::account_storage::ProgramAccountStorage< let (_, bump) = self.keys.contract_with_bump_seed(&crate::ID, address); let sign: &[&[u8]] = &[&[ACCOUNT_SEED_VERSION], address.as_bytes(), &[bump]]; - let mut storage = StorageCell::create(cell_address, 1, &self.accounts, sign)?; + let mut storage = + StorageCell::create(cell_address, 1, &self.accounts, sign, &self.rent)?; let mut cells = storage.cells_mut(); assert_eq!(cells.len(), 1); @@ -61,7 +63,7 @@ impl<'a> SyncedAccountStorage for crate::account_storage::ProgramAccountStorage< let mut storage = StorageCell::from_account(&crate::ID, account.clone())?; storage.update((index & 0xFF).as_u8(), &value)?; - storage.sync_lamports(rent, &self.accounts)?; + storage.sync_lamports(&self.rent, &self.accounts)?; }; } diff --git a/evm_loader/program/src/debug.rs b/evm_loader/program/src/debug.rs index a23d4429d..e7fe5153f 100644 --- a/evm_loader/program/src/debug.rs +++ b/evm_loader/program/src/debug.rs @@ -15,3 +15,34 @@ macro_rules! debug_print { macro_rules! debug_print { ($( $args:expr ),*) => {}; } + +#[cfg(target_os = "solana")] +macro_rules! log_msg { + ($($arg:tt)*) => (solana_program::msg!($($arg)*)); +} + +#[cfg(not(target_os = "solana"))] +macro_rules! log_msg { + ($($arg:tt)*) => (log::info!($($arg)*)); +} + +#[inline] +pub fn log_data(data: &[&[u8]]) { + #[cfg(target_os = "solana")] + solana_program::log::sol_log_data(data); + + #[cfg(not(target_os = "solana"))] + { + let mut messages: Vec = Vec::new(); + + for f in data { + if let Ok(str) = String::from_utf8(f.to_vec()) { + messages.push(str); + } else { + messages.push(hex::encode(f)); + } + } + + log::info!("Program Data: {}", messages.join(" ")); + } +} diff --git a/evm_loader/program/src/entrypoint.rs b/evm_loader/program/src/entrypoint.rs index e029ee510..5d84c72fa 100644 --- a/evm_loader/program/src/entrypoint.rs +++ b/evm_loader/program/src/entrypoint.rs @@ -49,7 +49,7 @@ fn process_instruction<'a>( instruction::config_get_version::process(program_id, accounts, instruction) } _ => { - solana_program::msg!("Emergency image: all instructions are rejected"); + log_msg!("Emergency image: all instructions are rejected"); Err(ProgramError::InvalidInstructionData.into()) } } @@ -125,9 +125,6 @@ fn process_instruction<'a>( instruction::create_main_treasury::process(program_id, accounts, instruction) .map_err(Error::from) } - EvmInstruction::AccountBlockAdd => { - instruction::account_block_add::process(program_id, accounts, instruction) - } EvmInstruction::AccountCreateBalance => { instruction::account_create_balance::process(program_id, accounts, instruction) } diff --git a/evm_loader/program/src/error.rs b/evm_loader/program/src/error.rs index c21401eb5..a474b3058 100644 --- a/evm_loader/program/src/error.rs +++ b/evm_loader/program/src/error.rs @@ -187,7 +187,7 @@ pub type Result = std::result::Result; impl From for ProgramError { fn from(e: Error) -> Self { - solana_program::msg!("{}", e); + log_msg!("{}", e); match e { Error::ProgramError(e) => e, _ => Self::Custom(0), @@ -275,18 +275,18 @@ fn format_revert_panic(msg: &[u8]) -> Option { pub fn print_revert_message(msg: &[u8]) { if msg.is_empty() { - return solana_program::msg!("Revert"); + return log_msg!("Revert"); } if let Some(reason) = format_revert_error(msg) { - return solana_program::msg!("Revert: Error(\"{}\")", reason); + return log_msg!("Revert: Error(\"{}\")", reason); } if let Some(reason) = format_revert_panic(msg) { - return solana_program::msg!("Revert: Panic({:#x})", reason); + return log_msg!("Revert: Panic({:#x})", reason); } - solana_program::msg!("Revert: {}", hex::encode(msg)); + log_msg!("Revert: {}", hex::encode(msg)); } #[must_use] diff --git a/evm_loader/program/src/evm/database.rs b/evm_loader/program/src/evm/database.rs index 5b75afeb3..1f175fd17 100644 --- a/evm_loader/program/src/evm/database.rs +++ b/evm_loader/program/src/evm/database.rs @@ -2,7 +2,9 @@ use super::{Buffer, Context}; use crate::{error::Result, executor::OwnedAccountInfo, types::Address}; use ethnum::U256; use maybe_async::maybe_async; -use solana_program::{account_info::AccountInfo, instruction::Instruction, pubkey::Pubkey}; +use solana_program::{ + account_info::AccountInfo, instruction::Instruction, pubkey::Pubkey, rent::Rent, +}; #[maybe_async(?Send)] pub trait Database { @@ -39,6 +41,8 @@ pub trait Database { async fn block_hash(&self, number: U256) -> Result<[u8; 32]>; fn block_number(&self) -> Result; fn block_timestamp(&self) -> Result; + fn rent(&self) -> &Rent; + fn return_data(&self) -> Option<(Pubkey, Vec)>; async fn external_account(&self, address: Pubkey) -> Result; async fn map_solana_account(&self, address: &Pubkey, action: F) -> R @@ -274,6 +278,14 @@ mod tests { unimplemented!(); } + fn rent(&self) -> &Rent { + unimplemented!(); + } + + fn return_data(&self) -> Option<(Pubkey, Vec)> { + unimplemented!(); + } + async fn map_solana_account(&self, address: &Pubkey, action: F) -> R where F: FnOnce(&AccountInfo) -> R, diff --git a/evm_loader/program/src/evm/mod.rs b/evm_loader/program/src/evm/mod.rs index f84010139..d26a722cf 100644 --- a/evm_loader/program/src/evm/mod.rs +++ b/evm_loader/program/src/evm/mod.rs @@ -7,13 +7,14 @@ use std::{fmt::Display, marker::PhantomData, ops::Range}; use ethnum::U256; use maybe_async::maybe_async; use serde::{Deserialize, Serialize}; -use solana_program::log::sol_log_data; pub use buffer::Buffer; -#[cfg(not(target_os = "solana"))] -use crate::evm::tracing::TracerTypeOpt; +use crate::evm::tracing::EventListener; +#[cfg(target_os = "solana")] +use crate::evm::tracing::NoopEventListener; use crate::{ + debug::log_data, error::{build_revert_message, Error, Result}, evm::{opcode::Action, precompile::is_precompile_address}, types::{Address, Transaction}, @@ -28,43 +29,43 @@ mod opcode; pub mod opcode_table; mod precompile; mod stack; -#[cfg(not(target_os = "solana"))] pub mod tracing; mod utils; macro_rules! tracing_event { - ($self:ident, $x:expr) => { + ($self:ident, $backend:ident, $x:expr) => { #[cfg(not(target_os = "solana"))] - if let Some(tracer) = &$self.tracer { - tracer.borrow_mut().event($x); + if let Some(tracer) = &mut $self.tracer { + tracer.event($backend, $x); } }; - ($self:ident, $condition:expr, $x:expr) => { + ($self:ident, $backend:ident, $condition:expr, $x:expr) => { #[cfg(not(target_os = "solana"))] - if let Some(tracer) = &$self.tracer { + if let Some(tracer) = &mut $self.tracer { if $condition { - tracer.borrow_mut().event($x); + tracer.event($backend, $x); } } }; } macro_rules! trace_end_step { - ($self:ident, $return_data:expr) => { + ($self:ident, $backend:ident, $return_data:expr) => { #[cfg(not(target_os = "solana"))] - if let Some(tracer) = &$self.tracer { - tracer - .borrow_mut() - .event(crate::evm::tracing::Event::EndStep { + if let Some(tracer) = &mut $self.tracer { + tracer.event( + $backend, + crate::evm::tracing::Event::EndStep { gas_used: 0_u64, return_data: $return_data, - }) + }, + ) } }; - ($self:ident, $condition:expr; $return_data_getter:expr) => { + ($self:ident, $backend:ident, $condition:expr; $return_data_getter:expr) => { #[cfg(not(target_os = "solana"))] if $condition { - trace_end_step!($self, $return_data_getter) + trace_end_step!($self, $backend, $return_data_getter) } }; } @@ -134,7 +135,7 @@ pub struct Context { #[derive(Serialize, Deserialize)] #[serde(bound = "B: Database")] -pub struct Machine { +pub struct Machine { origin: Address, chain_id: u64, context: Context, @@ -161,12 +162,12 @@ pub struct Machine { #[serde(skip)] phantom: PhantomData<*const B>, - #[cfg(not(target_os = "solana"))] #[serde(skip)] - tracer: TracerTypeOpt, + tracer: Option, } -impl Machine { +#[cfg(target_os = "solana")] +impl Machine { pub fn serialize_into(&self, buffer: &mut [u8]) -> Result { let mut cursor = std::io::Cursor::new(buffer); @@ -175,7 +176,6 @@ impl Machine { cursor.position().try_into().map_err(Error::from) } - #[cfg(target_os = "solana")] pub fn deserialize_from(buffer: &[u8], backend: &B) -> Result { fn reinit_buffer(buffer: &mut Buffer, backend: &B) { if let Some((key, range)) = buffer.uninit_data() { @@ -184,7 +184,10 @@ impl Machine { } } - fn reinit_machine(mut machine: &mut Machine, backend: &B) { + fn reinit_machine( + mut machine: &mut Machine, + backend: &B, + ) { loop { reinit_buffer(&mut machine.call_data, backend); reinit_buffer(&mut machine.execution_code, backend); @@ -202,30 +205,18 @@ impl Machine { Ok(evm) } +} +impl Machine { #[maybe_async] pub async fn new( - trx: Transaction, + trx: &Transaction, origin: Address, backend: &mut B, - #[cfg(not(target_os = "solana"))] tracer: TracerTypeOpt, + tracer: Option, ) -> Result { let trx_chain_id = trx.chain_id().unwrap_or_else(|| backend.default_chain_id()); - if !backend.is_valid_chain_id(trx_chain_id) { - return Err(Error::InvalidChainId(trx_chain_id)); - } - - let nonce = backend.nonce(origin, trx_chain_id).await?; - - if nonce == u64::MAX { - return Err(Error::NonceOverflow(origin)); - } - - if nonce != trx.nonce() { - return Err(Error::InvalidTransactionNonce(origin, nonce, trx.nonce())); - } - if backend.balance(origin, trx_chain_id).await? < trx.value() { return Err(Error::InsufficientBalance( origin, @@ -234,49 +225,26 @@ impl Machine { )); } - // TODO may be remove. This requires additional account - // Never actually happens, or at least should not - // if backend.code_size(origin).await? != 0 { - // return Err(Error::SenderHasDeployedCode(origin)); - // } - if trx.target().is_some() { - Self::new_call( - trx_chain_id, - trx, - origin, - backend, - #[cfg(not(target_os = "solana"))] - tracer, - ) - .await + Self::new_call(trx_chain_id, trx, origin, backend, tracer).await } else { - Self::new_create( - trx_chain_id, - trx, - origin, - backend, - #[cfg(not(target_os = "solana"))] - tracer, - ) - .await + Self::new_create(trx_chain_id, trx, origin, backend, tracer).await } } #[maybe_async] async fn new_call( chain_id: u64, - trx: Transaction, + trx: &Transaction, origin: Address, backend: &mut B, - #[cfg(not(target_os = "solana"))] tracer: TracerTypeOpt, + tracer: Option, ) -> Result { assert!(trx.target().is_some()); let target = trx.target().unwrap(); - sol_log_data(&[b"ENTER", b"CALL", target.as_bytes()]); + log_data(&[b"ENTER", b"CALL", target.as_bytes()]); - backend.increment_nonce(origin, chain_id)?; backend.snapshot(); backend @@ -298,7 +266,7 @@ impl Machine { gas_price: trx.gas_price(), gas_limit: trx.gas_limit(), execution_code, - call_data: trx.into_call_data(), + call_data: Buffer::from_slice(trx.call_data()), return_data: Buffer::empty(), return_range: 0..0, stack: Stack::new(), @@ -308,7 +276,6 @@ impl Machine { reason: Reason::Call, parent: None, phantom: PhantomData, - #[cfg(not(target_os = "solana"))] tracer, }) } @@ -316,22 +283,21 @@ impl Machine { #[maybe_async] async fn new_create( chain_id: u64, - trx: Transaction, + trx: &Transaction, origin: Address, backend: &mut B, - #[cfg(not(target_os = "solana"))] tracer: TracerTypeOpt, + tracer: Option, ) -> Result { assert!(trx.target().is_none()); let target = Address::from_create(&origin, trx.nonce()); - sol_log_data(&[b"ENTER", b"CREATE", target.as_bytes()]); + log_data(&[b"ENTER", b"CREATE", target.as_bytes()]); if (backend.nonce(target, chain_id).await? != 0) || (backend.code_size(target).await? != 0) { return Err(Error::DeployToExistingAccount(target, origin)); } - backend.increment_nonce(origin, chain_id)?; backend.snapshot(); backend.increment_nonce(target, chain_id)?; @@ -358,17 +324,20 @@ impl Machine { pc: 0_usize, is_static: false, reason: Reason::Create, - execution_code: trx.into_call_data(), + execution_code: Buffer::from_slice(trx.call_data()), call_data: Buffer::empty(), parent: None, phantom: PhantomData, - #[cfg(not(target_os = "solana"))] tracer, }) } #[maybe_async] - pub async fn execute(&mut self, step_limit: u64, backend: &mut B) -> Result<(ExitStatus, u64)> { + pub async fn execute( + &mut self, + step_limit: u64, + backend: &mut B, + ) -> Result<(ExitStatus, u64, Option)> { assert!(self.execution_code.is_initialized()); assert!(self.call_data.is_initialized()); assert!(self.return_data.is_initialized()); @@ -377,6 +346,7 @@ impl Machine { tracing_event!( self, + backend, tracing::Event::BeginVM { context: self.context, code: self.execution_code.to_vec() @@ -399,6 +369,7 @@ impl Machine { tracing_event!( self, + backend, tracing::Event::BeginStep { opcode, pc: self.pc, @@ -415,7 +386,7 @@ impl Machine { } }; - trace_end_step!(self, opcode_result != Action::Noop; match &opcode_result { + trace_end_step!(self, backend, opcode_result != Action::Noop; match &opcode_result { Action::Return(value) | Action::Revert(value) => Some(value.clone()), _ => None, }); @@ -434,12 +405,13 @@ impl Machine { tracing_event!( self, + backend, tracing::Event::EndVM { status: status.clone() } ); - Ok((status, step)) + Ok((status, step, self.tracer.take())) } fn fork( @@ -468,8 +440,7 @@ impl Machine { reason, parent: None, phantom: PhantomData, - #[cfg(not(target_os = "solana"))] - tracer: self.tracer.clone(), + tracer: self.tracer.take(), }; core::mem::swap(self, &mut other); @@ -482,6 +453,8 @@ impl Machine { let mut other = *self.parent.take().unwrap(); core::mem::swap(self, &mut other); + self.tracer = other.tracer.take(); + other } } diff --git a/evm_loader/program/src/evm/opcode.rs b/evm_loader/program/src/evm/opcode.rs index fc22c43b4..1ed917531 100644 --- a/evm_loader/program/src/evm/opcode.rs +++ b/evm_loader/program/src/evm/opcode.rs @@ -1,13 +1,14 @@ /// use ethnum::{I256, U256}; use maybe_async::maybe_async; -use solana_program::log::sol_log_data; use super::{ database::{Database, DatabaseExt}, tracing_event, Context, Machine, Reason, }; +use crate::evm::tracing::EventListener; use crate::{ + debug::log_data, error::{Error, Result}, evm::{trace_end_step, Buffer}, types::Address, @@ -25,7 +26,7 @@ pub enum Action { } #[allow(clippy::unused_async)] -impl Machine { +impl Machine { /// Unknown instruction #[maybe_async] pub async fn opcode_unknown(&mut self, _backend: &mut B) -> Result { @@ -799,7 +800,11 @@ impl Machine { let index = self.stack.pop_u256()?; let value = backend.storage(self.context.contract, index).await?; - tracing_event!(self, super::tracing::Event::StorageAccess { index, value }); + tracing_event!( + self, + backend, + super::tracing::Event::StorageAccess { index, value } + ); self.stack.push_array(&value)?; @@ -816,7 +821,11 @@ impl Machine { let index = self.stack.pop_u256()?; let value = *self.stack.pop_array()?; - tracing_event!(self, super::tracing::Event::StorageAccess { index, value }); + tracing_event!( + self, + backend, + super::tracing::Event::StorageAccess { index, value } + ); backend.set_storage(self.context.contract, index, value)?; @@ -984,11 +993,11 @@ impl Machine { let address = self.context.contract.as_bytes(); match N { - 0 => sol_log_data(&[b"LOG0", address, &[0], data]), - 1 => sol_log_data(&[b"LOG1", address, &[1], &topics[0], data]), - 2 => sol_log_data(&[b"LOG2", address, &[2], &topics[0], &topics[1], data]), - 3 => sol_log_data(&[b"LOG3", address, &[3], &topics[0], &topics[1], &topics[2], data]), - 4 => sol_log_data(&[b"LOG4", address, &[4], &topics[0], &topics[1], &topics[2], &topics[3], data]), + 0 => log_data(&[b"LOG0", address, &[0], data]), + 1 => log_data(&[b"LOG1", address, &[1], &topics[0], data]), + 2 => log_data(&[b"LOG2", address, &[2], &topics[0], &topics[1], data]), + 3 => log_data(&[b"LOG3", address, &[3], &topics[0], &topics[1], &topics[2], data]), + 4 => log_data(&[b"LOG4", address, &[4], &topics[0], &topics[1], &topics[2], &topics[3], data]), _ => unreachable!(), } @@ -1073,6 +1082,7 @@ impl Machine { tracing_event!( self, + backend, super::tracing::Event::BeginVM { context, code: init_code.to_vec() @@ -1089,7 +1099,7 @@ impl Machine { ); backend.snapshot(); - sol_log_data(&[b"ENTER", b"CREATE", address.as_bytes()]); + log_data(&[b"ENTER", b"CREATE", address.as_bytes()]); if (backend.nonce(address, chain_id).await? != 0) || (backend.code_size(address).await? != 0) @@ -1133,6 +1143,7 @@ impl Machine { tracing_event!( self, + backend, super::tracing::Event::BeginVM { context, code: code.to_vec() @@ -1149,7 +1160,7 @@ impl Machine { ); backend.snapshot(); - sol_log_data(&[b"ENTER", b"CALL", address.as_bytes()]); + log_data(&[b"ENTER", b"CALL", address.as_bytes()]); if self.is_static && (value != U256::ZERO) { return Err(Error::StaticModeViolation(self.context.caller)); @@ -1188,6 +1199,7 @@ impl Machine { tracing_event!( self, + backend, super::tracing::Event::BeginVM { context, code: code.to_vec() @@ -1204,7 +1216,7 @@ impl Machine { ); backend.snapshot(); - sol_log_data(&[b"ENTER", b"CALLCODE", address.as_bytes()]); + log_data(&[b"ENTER", b"CALLCODE", address.as_bytes()]); if backend.balance(self.context.caller, chain_id).await? < value { return Err(Error::InsufficientBalance( @@ -1241,6 +1253,7 @@ impl Machine { tracing_event!( self, + backend, super::tracing::Event::BeginVM { context, code: code.to_vec() @@ -1257,7 +1270,7 @@ impl Machine { ); backend.snapshot(); - sol_log_data(&[b"ENTER", b"DELEGATECALL", address.as_bytes()]); + log_data(&[b"ENTER", b"DELEGATECALL", address.as_bytes()]); self.opcode_call_precompile_impl(backend, &address).await } @@ -1290,6 +1303,7 @@ impl Machine { tracing_event!( self, + backend, super::tracing::Event::BeginVM { context, code: code.to_vec() @@ -1308,7 +1322,7 @@ impl Machine { backend.snapshot(); - sol_log_data(&[b"ENTER", b"STATICCALL", address.as_bytes()]); + log_data(&[b"ENTER", b"STATICCALL", address.as_bytes()]); self.opcode_call_precompile_impl(backend, &address).await } @@ -1361,15 +1375,16 @@ impl Machine { } backend.commit_snapshot(); - sol_log_data(&[b"EXIT", b"RETURN"]); + log_data(&[b"EXIT", b"RETURN"]); if self.parent.is_none() { return Ok(Action::Return(return_data)); } - trace_end_step!(self, Some(return_data.clone())); + trace_end_step!(self, backend, Some(return_data.clone())); tracing_event!( self, + backend, super::tracing::Event::EndVM { status: super::ExitStatus::Return(return_data.clone()) } @@ -1409,16 +1424,17 @@ impl Machine { return_data: Vec, backend: &mut B, ) -> Result { - sol_log_data(&[b"EXIT", b"REVERT", &return_data]); + log_data(&[b"EXIT", b"REVERT", &return_data]); backend.revert_snapshot(); if self.parent.is_none() { return Ok(Action::Revert(return_data)); } - trace_end_step!(self, Some(return_data.clone())); + trace_end_step!(self, backend, Some(return_data.clone())); tracing_event!( self, + backend, super::tracing::Event::EndVM { status: super::ExitStatus::Revert(return_data.clone()) } @@ -1466,15 +1482,16 @@ impl Machine { backend.selfdestruct(self.context.contract)?; backend.commit_snapshot(); - sol_log_data(&[b"EXIT", b"SELFDESTRUCT"]); + log_data(&[b"EXIT", b"SELFDESTRUCT"]); if self.parent.is_none() { return Ok(Action::Suicide); } - trace_end_step!(self, None); + trace_end_step!(self, backend, None); tracing_event!( self, + backend, super::tracing::Event::EndVM { status: super::ExitStatus::Suicide } @@ -1498,15 +1515,16 @@ impl Machine { #[maybe_async] pub async fn opcode_stop(&mut self, backend: &mut B) -> Result { backend.commit_snapshot(); - sol_log_data(&[b"EXIT", b"STOP"]); + log_data(&[b"EXIT", b"STOP"]); if self.parent.is_none() { return Ok(Action::Stop); } - trace_end_step!(self, None); + trace_end_step!(self, backend, None); tracing_event!( self, + backend, super::tracing::Event::EndVM { status: super::ExitStatus::Stop } diff --git a/evm_loader/program/src/evm/opcode_table.rs b/evm_loader/program/src/evm/opcode_table.rs index cc169efc2..2f954da47 100644 --- a/evm_loader/program/src/evm/opcode_table.rs +++ b/evm_loader/program/src/evm/opcode_table.rs @@ -1,16 +1,17 @@ use crate::error::Result; +use crate::evm::tracing::EventListener; use super::{database::Database, opcode::Action, Machine}; macro_rules! opcode_table { ($( $opcode:literal, $opname:literal, $op:path;)*) => { #[cfg(target_os = "solana")] - type OpCode = fn(&mut Machine, &mut B) -> Result; + type OpCode = fn(&mut Machine, &mut B) -> Result; #[cfg(target_os = "solana")] - impl Machine { - const OPCODES: [OpCode; 256] = { - let mut opcodes: [OpCode; 256] = [Self::opcode_unknown; 256]; + impl Machine { + const OPCODES: [OpCode; 256] = { + let mut opcodes: [OpCode; 256] = [Self::opcode_unknown; 256]; $(opcodes[$opcode as usize] = $op;)* @@ -25,7 +26,7 @@ macro_rules! opcode_table { } #[cfg(not(target_os = "solana"))] - impl Machine { + impl Machine { pub async fn execute_opcode(&mut self, backend: &mut B, opcode: u8) -> Result { match opcode { $($opcode => $op(self, backend).await,)* diff --git a/evm_loader/program/src/evm/precompile/mod.rs b/evm_loader/program/src/evm/precompile/mod.rs index 16bb5666c..c22b65a8b 100644 --- a/evm_loader/program/src/evm/precompile/mod.rs +++ b/evm_loader/program/src/evm/precompile/mod.rs @@ -1,3 +1,4 @@ +use crate::evm::tracing::EventListener; use crate::evm::{database::Database, Machine}; use crate::types::Address; @@ -56,7 +57,7 @@ pub fn is_precompile_address(address: &Address) -> bool { || *address == SYSTEM_ACCOUNT_BLAKE2F } -impl Machine { +impl Machine { #[must_use] pub fn precompile(address: &Address, data: &[u8]) -> Option> { match *address { diff --git a/evm_loader/program/src/evm/tracing.rs b/evm_loader/program/src/evm/tracing.rs index d9386f6ee..8f7776206 100644 --- a/evm_loader/program/src/evm/tracing.rs +++ b/evm_loader/program/src/evm/tracing.rs @@ -1,19 +1,16 @@ -use std::cell::RefCell; -use std::fmt::Debug; -use std::rc::Rc; - +use super::{Context, ExitStatus}; +use crate::evm::database::Database; use ethnum::U256; -use serde_json::Value; -use super::{Context, ExitStatus}; +pub struct NoopEventListener; -pub trait EventListener: Debug { - fn event(&mut self, event: Event); - fn into_traces(self: Box) -> Value; +pub trait EventListener { + fn event(&mut self, executor_state: &impl Database, event: Event); } -pub type TracerType = Rc>>; -pub type TracerTypeOpt = Option; +impl EventListener for NoopEventListener { + fn event(&mut self, _executor_state: &impl Database, _event: Event) {} +} /// Trace event pub enum Event { diff --git a/evm_loader/program/src/executor/action.rs b/evm_loader/program/src/executor/action.rs index 91e9ab87f..456848627 100644 --- a/evm_loader/program/src/executor/action.rs +++ b/evm_loader/program/src/executor/action.rs @@ -2,7 +2,7 @@ use ethnum::U256; use serde::{Deserialize, Serialize}; use solana_program::{instruction::AccountMeta, pubkey::Pubkey}; -use crate::types::Address; +use crate::types::{serde::bytes_32, Address}; #[derive(Debug, Clone, Serialize, Deserialize)] pub enum Action { @@ -32,7 +32,7 @@ pub enum Action { address: Address, #[serde(with = "ethnum::serde::bytes::le")] index: U256, - #[serde(with = "serde_bytes_32")] + #[serde(with = "bytes_32")] value: [u8; 32], }, EvmIncrementNonce { @@ -81,108 +81,3 @@ pub fn filter_selfdestruct(actions: Vec) -> Vec { }) .collect() } - -mod serde_bytes_32 { - pub fn serialize(value: &[u8; 32], serializer: S) -> Result - where - S: serde::ser::Serializer, - { - if serializer.is_human_readable() { - serializer.serialize_str(&hex::encode(value)) - } else { - serializer.serialize_bytes(value) - } - } - - pub fn deserialize<'de, D>(deserializer: D) -> Result<[u8; 32], D::Error> - where - D: serde::Deserializer<'de>, - { - struct BytesVisitor; - - impl<'de> serde::de::Visitor<'de> for BytesVisitor { - type Value = [u8; 32]; - - fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - f.write_str("[u8; 32]") - } - - fn visit_str(self, value: &str) -> Result - where - E: serde::de::Error, - { - use serde::de::Unexpected::Str; - - let value = hex::decode(value) - .map_err(|_| serde::de::Error::invalid_value(Str(value), &self))?; - - let value_len = value.len(); - value - .try_into() - .map_err(|_| serde::de::Error::invalid_length(value_len, &self)) - } - - fn visit_bytes(self, value: &[u8]) -> Result - where - E: serde::de::Error, - { - value - .try_into() - .map_err(|_| serde::de::Error::invalid_length(value.len(), &self)) - } - - fn visit_seq(self, mut seq: S) -> Result - where - S: serde::de::SeqAccess<'de>, - { - let mut bytes = Vec::with_capacity(32); - while let Some(b) = seq.next_element()? { - bytes.push(b); - } - bytes - .try_into() - .map_err(|_| serde::de::Error::custom("Invalid [u8; 32] value")) - } - } - - if deserializer.is_human_readable() { - deserializer.deserialize_str(BytesVisitor) - } else { - deserializer.deserialize_bytes(BytesVisitor) - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn roundtrip_bincode() { - let action = Action::EvmSetStorage { - address: Address::default(), - index: U256::from_le_bytes([ - 255, 46, 185, 41, 144, 201, 3, 36, 227, 18, 148, 147, 106, 131, 110, 6, 229, 235, - 44, 154, 71, 124, 159, 144, 47, 119, 77, 5, 154, 49, 23, 54, - ]), - value: Default::default(), - }; - let serialized = bincode::serialize(&action).unwrap(); - let _deserialized: Action = bincode::deserialize(&serialized).unwrap(); - } - - #[cfg(not(target_os = "solana"))] - #[test] - fn roundtrip_json() { - let action = Action::EvmSetStorage { - address: Address::default(), - index: U256::from_le_bytes([ - 255, 46, 185, 41, 144, 201, 3, 36, 227, 18, 148, 147, 106, 131, 110, 6, 229, 235, - 44, 154, 71, 124, 159, 144, 47, 119, 77, 5, 154, 49, 23, 54, - ]), - value: Default::default(), - }; - let serialized = serde_json::to_string(&action).unwrap(); - let _deserialized: Action = serde_json::from_str(&serialized).unwrap(); - } -} diff --git a/evm_loader/program/src/executor/precompile_extension/metaplex.rs b/evm_loader/program/src/executor/precompile_extension/metaplex.rs index b649160ed..f19ac88a1 100644 --- a/evm_loader/program/src/executor/precompile_extension/metaplex.rs +++ b/evm_loader/program/src/executor/precompile_extension/metaplex.rs @@ -7,7 +7,7 @@ use mpl_token_metadata::state::{ Creator, Metadata, TokenMetadataAccount, TokenStandard, CREATE_FEE, MAX_MASTER_EDITION_LEN, MAX_METADATA_LEN, }; -use solana_program::{pubkey::Pubkey, rent::Rent, sysvar::Sysvar}; +use solana_program::pubkey::Pubkey; use crate::{ account::ACCOUNT_SEED_VERSION, @@ -191,9 +191,7 @@ fn create_metadata( None, // Collection Details ); - let rent = Rent::get()?; - let fee = rent.minimum_balance(MAX_METADATA_LEN) + CREATE_FEE; - + let fee = state.rent().minimum_balance(MAX_METADATA_LEN) + CREATE_FEE; state.queue_external_instruction(instruction, vec![seeds], fee, true)?; Ok(metadata_pubkey.to_bytes().to_vec()) @@ -228,9 +226,7 @@ fn create_master_edition( max_supply, ); - let rent = Rent::get()?; - let fee = rent.minimum_balance(MAX_MASTER_EDITION_LEN) + CREATE_FEE; - + let fee = state.rent().minimum_balance(MAX_MASTER_EDITION_LEN) + CREATE_FEE; state.queue_external_instruction(instruction, vec![seeds], fee, true)?; Ok(edition_pubkey.to_bytes().to_vec()) diff --git a/evm_loader/program/src/executor/precompile_extension/mod.rs b/evm_loader/program/src/executor/precompile_extension/mod.rs index 08cad3d7c..041fc8d4c 100644 --- a/evm_loader/program/src/executor/precompile_extension/mod.rs +++ b/evm_loader/program/src/executor/precompile_extension/mod.rs @@ -4,7 +4,7 @@ use crate::{ types::Address, }; use maybe_async::maybe_async; -use solana_program::{pubkey::Pubkey, rent::Rent, system_instruction, sysvar::Sysvar}; +use solana_program::{pubkey::Pubkey, system_instruction}; use super::OwnedAccountInfo; @@ -82,8 +82,7 @@ pub fn create_account( owner: &Pubkey, seeds: Vec>, ) -> Result<()> { - let rent = Rent::get()?; - let minimum_balance = rent.minimum_balance(space); + let minimum_balance = state.rent().minimum_balance(space); let required_lamports = minimum_balance.saturating_sub(account.lamports); diff --git a/evm_loader/program/src/executor/precompile_extension/neon_token.rs b/evm_loader/program/src/executor/precompile_extension/neon_token.rs index 86e9f370f..7265bc9e8 100644 --- a/evm_loader/program/src/executor/precompile_extension/neon_token.rs +++ b/evm_loader/program/src/executor/precompile_extension/neon_token.rs @@ -3,9 +3,7 @@ use std::convert::TryInto; use arrayref::array_ref; use ethnum::U256; use maybe_async::maybe_async; -use solana_program::{ - account_info::IntoAccountInfo, program_pack::Pack, pubkey::Pubkey, rent::Rent, sysvar::Sysvar, -}; +use solana_program::{account_info::IntoAccountInfo, program_pack::Pack, pubkey::Pubkey}; use spl_associated_token_account::get_associated_token_address; use crate::{ @@ -116,8 +114,7 @@ async fn withdraw( &spl_token::ID, ); - let rent = Rent::get()?; - let fee = rent.minimum_balance(spl_token::state::Account::LEN); + let fee = state.rent().minimum_balance(spl_token::state::Account::LEN); state.queue_external_instruction(create_associated, vec![], fee, true)?; } diff --git a/evm_loader/program/src/executor/state.rs b/evm_loader/program/src/executor/state.rs index 00eafcab6..6bf387444 100644 --- a/evm_loader/program/src/executor/state.rs +++ b/evm_loader/program/src/executor/state.rs @@ -5,6 +5,7 @@ use ethnum::{AsU256, U256}; use maybe_async::maybe_async; use solana_program::instruction::Instruction; use solana_program::pubkey::Pubkey; +use solana_program::rent::Rent; use crate::account_storage::AccountStorage; use crate::error::{Error, Result}; @@ -377,10 +378,16 @@ impl<'a, B: AccountStorage> Database for ExecutorState<'a, B> { data, meta, &mut accounts, + self.rent(), )?; } program_id if mpl_token_metadata::check_id(program_id) => { - crate::external_programs::metaplex::emulate(data, meta, &mut accounts)?; + crate::external_programs::metaplex::emulate( + data, + meta, + &mut accounts, + self.rent(), + )?; } _ => { return Err(Error::Custom(format!( @@ -394,6 +401,14 @@ impl<'a, B: AccountStorage> Database for ExecutorState<'a, B> { Ok(accounts[&address].clone()) } + fn rent(&self) -> &Rent { + self.backend.rent() + } + + fn return_data(&self) -> Option<(Pubkey, Vec)> { + self.backend.return_data() + } + async fn map_solana_account(&self, address: &Pubkey, action: F) -> R where F: FnOnce(&solana_program::account_info::AccountInfo) -> R, @@ -415,8 +430,7 @@ impl<'a, B: AccountStorage> Database for ExecutorState<'a, B> { if self.stack.is_empty() { // sanity check - assert_eq!(self.actions.len(), 1); - assert!(matches!(self.actions[0], Action::EvmIncrementNonce { .. })); + assert!(self.actions.is_empty()); } } diff --git a/evm_loader/program/src/executor/synced_state.rs b/evm_loader/program/src/executor/synced_state.rs index f768afc14..e165d2439 100644 --- a/evm_loader/program/src/executor/synced_state.rs +++ b/evm_loader/program/src/executor/synced_state.rs @@ -2,6 +2,7 @@ use ethnum::{AsU256, U256}; use maybe_async::maybe_async; use solana_program::instruction::Instruction; use solana_program::pubkey::Pubkey; +use solana_program::rent::Rent; use crate::account_storage::{AccountStorage, SyncedAccountStorage}; use crate::error::{Error, Result}; @@ -166,6 +167,14 @@ impl<'a, B: AccountStorage + SyncedAccountStorage> Database for SyncedExecutorSt return Ok(account); } + fn rent(&self) -> &Rent { + self.backend.rent() + } + + fn return_data(&self) -> Option<(Pubkey, Vec)> { + self.backend.return_data() + } + async fn map_solana_account(&self, address: &Pubkey, action: F) -> R where F: FnOnce(&solana_program::account_info::AccountInfo) -> R, diff --git a/evm_loader/program/src/external_programs/metaplex.rs b/evm_loader/program/src/external_programs/metaplex.rs index 0f2c513ad..a9fe278b8 100644 --- a/evm_loader/program/src/external_programs/metaplex.rs +++ b/evm_loader/program/src/external_programs/metaplex.rs @@ -6,7 +6,6 @@ use solana_program::account_info::IntoAccountInfo; use solana_program::instruction::AccountMeta; use solana_program::program_option::COption; use solana_program::rent::Rent; -use solana_program::sysvar::Sysvar; use spl_token::state::Mint; use std::collections::BTreeMap; @@ -26,13 +25,14 @@ pub fn emulate( instruction: &[u8], meta: &[AccountMeta], accounts: &mut BTreeMap, + rent: &Rent, ) -> ProgramResult { match MetadataInstruction::try_from_slice(instruction)? { MetadataInstruction::CreateMetadataAccountV3(args) => { - create_metadata_accounts_v3(meta, accounts, &args) + create_metadata_accounts_v3(meta, accounts, &args, rent) } MetadataInstruction::CreateMasterEditionV3(args) => { - create_master_edition_v3(meta, accounts, &args) + create_master_edition_v3(meta, accounts, &args, rent) } _ => Err!(ProgramError::InvalidInstructionData; "Unknown Metaplex instruction"), } @@ -42,6 +42,7 @@ fn create_metadata_accounts_v3( meta: &[AccountMeta], accounts: &mut BTreeMap, args: &CreateMetadataAccountArgsV3, + rent: &Rent, ) -> ProgramResult { let metadata_account_key = &meta[0].pubkey; let mint_key = &meta[1].pubkey; @@ -52,8 +53,6 @@ fn create_metadata_accounts_v3( // let _rent_key = &meta[6].pubkey; let mut metadata: Metadata = { - let rent = Rent::get()?; - let metadata_account = accounts.get_mut(metadata_account_key).unwrap(); metadata_account.data.resize(MAX_METADATA_LEN, 0); metadata_account.owner = mpl_token_metadata::ID; @@ -123,6 +122,7 @@ fn create_master_edition_v3( meta: &[AccountMeta], accounts: &mut BTreeMap, args: &CreateMasterEditionArgs, + rent: &Rent, ) -> ProgramResult { let edition_account_key = &meta[0].pubkey; let mint_key = &meta[1].pubkey; @@ -160,8 +160,6 @@ fn create_master_edition_v3( } { - let rent = Rent::get()?; - let edition_account = accounts.get_mut(edition_account_key).unwrap(); edition_account.data.resize(MAX_MASTER_EDITION_LEN, 0); edition_account.owner = mpl_token_metadata::ID; diff --git a/evm_loader/program/src/external_programs/spl_associated_token.rs b/evm_loader/program/src/external_programs/spl_associated_token.rs index 9ef2ae843..0491cc903 100644 --- a/evm_loader/program/src/external_programs/spl_associated_token.rs +++ b/evm_loader/program/src/external_programs/spl_associated_token.rs @@ -4,7 +4,7 @@ use crate::executor::OwnedAccountInfo; use borsh::BorshDeserialize; use solana_program::{ entrypoint::ProgramResult, instruction::AccountMeta, program_error::ProgramError, - program_pack::Pack, pubkey::Pubkey, rent::Rent, sysvar::Sysvar, + program_pack::Pack, pubkey::Pubkey, rent::Rent, }; use spl_associated_token_account::instruction::AssociatedTokenAccountInstruction; @@ -12,6 +12,7 @@ pub fn emulate( instruction: &[u8], meta: &[AccountMeta], accounts: &mut BTreeMap, + rent: &Rent, ) -> ProgramResult { let instruction = if instruction.is_empty() { AssociatedTokenAccountInstruction::Create @@ -33,8 +34,6 @@ pub fn emulate( let required_lamports = { let associated_token_account = &accounts[associated_token_account_key]; - - let rent = Rent::get()?; rent.minimum_balance(spl_token::state::Account::LEN) .max(1) .saturating_sub(associated_token_account.lamports) diff --git a/evm_loader/program/src/instruction/account_block_add.rs b/evm_loader/program/src/instruction/account_block_add.rs deleted file mode 100644 index ab2f5254b..000000000 --- a/evm_loader/program/src/instruction/account_block_add.rs +++ /dev/null @@ -1,50 +0,0 @@ -use crate::error::Result; -use solana_program::instruction::TRANSACTION_LEVEL_STACK_HEIGHT; -use solana_program::{account_info::AccountInfo, pubkey::Pubkey}; - -pub fn process<'a>( - _program_id: &'a Pubkey, - _accounts: &'a [AccountInfo<'a>], - _instruction: &[u8], -) -> Result<()> { - let stack_height = solana_program::instruction::get_stack_height(); - assert_eq!(stack_height, TRANSACTION_LEVEL_STACK_HEIGHT); - - solana_program::msg!("Instruction: Block Accounts"); - - todo!(); - - // let mut state = State::from_account(program_id, &accounts[0])?; - // let operator = Operator::from_account(&accounts[1])?; - - // if &state.owner != operator.key { - // return Err(Error::HolderInvalidOwner(state.owner, *operator.key)); - // } - - // let mut blocked_accounts = state.read_blocked_accounts()?; - // let mut blocked_keys: BTreeSet = blocked_accounts.iter().map(|a| a.key).collect(); - - // for account_info in &accounts[2..] { - // if blocked_keys.contains(account_info.key) { - // continue; - // } - - // let mut meta = BlockedAccountMeta { - // key: *account_info.key, - // exists: false, - // is_writable: account_info.is_writable, - // }; - - // if let Ok(mut account) = EthereumAccount::from_account(program_id, account_info) { - // account.check_blocked()?; - // account.rw_blocked = true; - - // meta.exists = true; - // } - - // blocked_accounts.push(meta); - // blocked_keys.insert(*account_info.key); - // } - - // state.update_blocked_accounts(blocked_accounts.into_iter()) -} diff --git a/evm_loader/program/src/instruction/account_create_balance.rs b/evm_loader/program/src/instruction/account_create_balance.rs index 878eb4c78..8693310ab 100644 --- a/evm_loader/program/src/instruction/account_create_balance.rs +++ b/evm_loader/program/src/instruction/account_create_balance.rs @@ -1,5 +1,5 @@ use arrayref::array_ref; -use solana_program::{account_info::AccountInfo, pubkey::Pubkey}; +use solana_program::{account_info::AccountInfo, pubkey::Pubkey, rent::Rent, sysvar::Sysvar}; use crate::account::{program, AccountsDB, BalanceAccount, Operator}; use crate::config::{CHAIN_ID_LIST, DEFAULT_CHAIN_ID}; @@ -11,7 +11,7 @@ pub fn process<'a>( accounts: &'a [AccountInfo<'a>], instruction: &[u8], ) -> Result<()> { - solana_program::msg!("Instruction: Create Balance Account"); + log_msg!("Instruction: Create Balance Account"); let operator = unsafe { Operator::from_account_not_whitelisted(&accounts[0]) }?; let system = program::System::from_account(&accounts[1])?; @@ -28,7 +28,7 @@ pub fn process<'a>( .binary_search_by_key(&chain_id, |c| c.0) .map_err(|_| Error::InvalidChainId(chain_id))?; - solana_program::msg!("Address: {}, ChainID: {}", address, chain_id); + log_msg!("Address: {}, ChainID: {}", address, chain_id); let mut excessive_lamports = 0; if chain_id == DEFAULT_CHAIN_ID { @@ -36,7 +36,8 @@ pub fn process<'a>( excessive_lamports += crate::account::legacy::update_legacy_accounts(&accounts_db)?; }; - BalanceAccount::create(address, chain_id, &accounts_db, None)?; + let rent = Rent::get()?; + BalanceAccount::create(address, chain_id, &accounts_db, None, &rent)?; **accounts_db.operator().try_borrow_mut_lamports()? += excessive_lamports; diff --git a/evm_loader/program/src/instruction/account_holder_create.rs b/evm_loader/program/src/instruction/account_holder_create.rs index 7509b9d1c..ac6687d2b 100644 --- a/evm_loader/program/src/instruction/account_holder_create.rs +++ b/evm_loader/program/src/instruction/account_holder_create.rs @@ -8,7 +8,7 @@ pub fn process<'a>( accounts: &'a [AccountInfo<'a>], instruction: &[u8], ) -> Result<()> { - solana_program::msg!("Instruction: Create Holder Account"); + log_msg!("Instruction: Create Holder Account"); let holder = accounts[0].clone(); let operator = unsafe { Operator::from_account_not_whitelisted(&accounts[1]) }?; diff --git a/evm_loader/program/src/instruction/account_holder_delete.rs b/evm_loader/program/src/instruction/account_holder_delete.rs index 8873f2642..6920b803b 100644 --- a/evm_loader/program/src/instruction/account_holder_delete.rs +++ b/evm_loader/program/src/instruction/account_holder_delete.rs @@ -7,7 +7,7 @@ pub fn process<'a>( accounts: &'a [AccountInfo<'a>], _instruction: &[u8], ) -> Result<()> { - solana_program::msg!("Instruction: Delete Holder Account"); + log_msg!("Instruction: Delete Holder Account"); let holder_info = accounts[0].clone(); let operator = unsafe { Operator::from_account_not_whitelisted(&accounts[1]) }?; diff --git a/evm_loader/program/src/instruction/account_holder_write.rs b/evm_loader/program/src/instruction/account_holder_write.rs index 47cca15eb..e9a5e8781 100644 --- a/evm_loader/program/src/instruction/account_holder_write.rs +++ b/evm_loader/program/src/instruction/account_holder_write.rs @@ -1,4 +1,5 @@ use crate::account::{Holder, Operator}; +use crate::debug::log_data; use crate::error::Result; use arrayref::array_ref; use solana_program::{account_info::AccountInfo, pubkey::Pubkey}; @@ -8,7 +9,7 @@ pub fn process<'a>( accounts: &'a [AccountInfo<'a>], instruction: &[u8], ) -> Result<()> { - solana_program::msg!("Instruction: Write To Holder"); + log_msg!("Instruction: Write To Holder"); let transaction_hash = *array_ref![instruction, 0, 32]; let offset = usize::from_le_bytes(*array_ref![instruction, 32, 8]); @@ -23,7 +24,7 @@ pub fn process<'a>( holder.validate_owner(&operator)?; holder.update_transaction_hash(transaction_hash); - solana_program::log::sol_log_data(&[b"HASH", &transaction_hash]); + log_data(&[b"HASH", &transaction_hash]); holder.write(offset, data)?; diff --git a/evm_loader/program/src/instruction/collect_treasury.rs b/evm_loader/program/src/instruction/collect_treasury.rs index bc950d378..af7547e26 100644 --- a/evm_loader/program/src/instruction/collect_treasury.rs +++ b/evm_loader/program/src/instruction/collect_treasury.rs @@ -13,7 +13,7 @@ pub fn process<'a>( accounts: &'a [AccountInfo<'a>], instruction: &[u8], ) -> ProgramResult { - solana_program::msg!("Instruction: Collect treasury"); + log_msg!("Instruction: Collect treasury"); let treasury_index = u32::from_le_bytes(*array_ref![instruction, 0, 4]); diff --git a/evm_loader/program/src/instruction/config_get_chain_count.rs b/evm_loader/program/src/instruction/config_get_chain_count.rs index bc932f2e9..d917b85a6 100644 --- a/evm_loader/program/src/instruction/config_get_chain_count.rs +++ b/evm_loader/program/src/instruction/config_get_chain_count.rs @@ -7,7 +7,7 @@ pub fn process<'a>( _accounts: &'a [AccountInfo<'a>], _instruction: &[u8], ) -> Result<()> { - solana_program::msg!("Instruction: Config Get Chain Count"); + log_msg!("Instruction: Config Get Chain Count"); let count = crate::config::CHAIN_ID_LIST.len(); diff --git a/evm_loader/program/src/instruction/config_get_chain_info.rs b/evm_loader/program/src/instruction/config_get_chain_info.rs index 13b1a397e..157539c8e 100644 --- a/evm_loader/program/src/instruction/config_get_chain_info.rs +++ b/evm_loader/program/src/instruction/config_get_chain_info.rs @@ -7,7 +7,7 @@ pub fn process<'a>( _accounts: &'a [AccountInfo<'a>], instruction: &[u8], ) -> Result<()> { - solana_program::msg!("Instruction: Config Get Chain Info"); + log_msg!("Instruction: Config Get Chain Info"); let bytes = instruction.try_into()?; let index = usize::from_le_bytes(bytes); diff --git a/evm_loader/program/src/instruction/config_get_environment.rs b/evm_loader/program/src/instruction/config_get_environment.rs index e4c8513f7..8a9e08074 100644 --- a/evm_loader/program/src/instruction/config_get_environment.rs +++ b/evm_loader/program/src/instruction/config_get_environment.rs @@ -7,7 +7,7 @@ pub fn process<'a>( _accounts: &'a [AccountInfo<'a>], _instruction: &[u8], ) -> Result<()> { - solana_program::msg!("Instruction: Config Get Environment"); + log_msg!("Instruction: Config Get Environment"); let environment: &str = if cfg!(feature = "mainnet") { "mainnet" diff --git a/evm_loader/program/src/instruction/config_get_property_by_index.rs b/evm_loader/program/src/instruction/config_get_property_by_index.rs index 19c6d6e31..ac9f62627 100644 --- a/evm_loader/program/src/instruction/config_get_property_by_index.rs +++ b/evm_loader/program/src/instruction/config_get_property_by_index.rs @@ -7,7 +7,7 @@ pub fn process<'a>( _accounts: &'a [AccountInfo<'a>], instruction: &[u8], ) -> Result<()> { - solana_program::msg!("Instruction: Config Get Property by Index"); + log_msg!("Instruction: Config Get Property by Index"); let bytes = instruction.try_into()?; let index = usize::from_le_bytes(bytes); diff --git a/evm_loader/program/src/instruction/config_get_property_by_name.rs b/evm_loader/program/src/instruction/config_get_property_by_name.rs index 3937c7c69..e4397b2e9 100644 --- a/evm_loader/program/src/instruction/config_get_property_by_name.rs +++ b/evm_loader/program/src/instruction/config_get_property_by_name.rs @@ -7,7 +7,7 @@ pub fn process<'a>( _accounts: &'a [AccountInfo<'a>], instruction: &[u8], ) -> Result<()> { - solana_program::msg!("Instruction: Config Get Property by Name"); + log_msg!("Instruction: Config Get Property by Name"); let requested_property = std::str::from_utf8(instruction)?; diff --git a/evm_loader/program/src/instruction/config_get_property_count.rs b/evm_loader/program/src/instruction/config_get_property_count.rs index c246b6b0a..2732f22f2 100644 --- a/evm_loader/program/src/instruction/config_get_property_count.rs +++ b/evm_loader/program/src/instruction/config_get_property_count.rs @@ -7,7 +7,7 @@ pub fn process<'a>( _accounts: &'a [AccountInfo<'a>], _instruction: &[u8], ) -> Result<()> { - solana_program::msg!("Instruction: Config Get Property Count"); + log_msg!("Instruction: Config Get Property Count"); let count = crate::config::PARAMETERS.len(); diff --git a/evm_loader/program/src/instruction/config_get_status.rs b/evm_loader/program/src/instruction/config_get_status.rs index 42c5b9af0..158e2c46d 100644 --- a/evm_loader/program/src/instruction/config_get_status.rs +++ b/evm_loader/program/src/instruction/config_get_status.rs @@ -7,7 +7,7 @@ pub fn process<'a>( _accounts: &'a [AccountInfo<'a>], _instruction: &[u8], ) -> Result<()> { - solana_program::msg!("Instruction: Config Get Status"); + log_msg!("Instruction: Config Get Status"); if cfg!(feature = "emergency") { solana_program::program::set_return_data(&[0]); diff --git a/evm_loader/program/src/instruction/config_get_version.rs b/evm_loader/program/src/instruction/config_get_version.rs index 3d16b2b52..9d0b0dc1a 100644 --- a/evm_loader/program/src/instruction/config_get_version.rs +++ b/evm_loader/program/src/instruction/config_get_version.rs @@ -10,7 +10,7 @@ pub fn process<'a>( _accounts: &'a [AccountInfo<'a>], _instruction: &[u8], ) -> Result<()> { - solana_program::msg!("Instruction: Config Get Version"); + log_msg!("Instruction: Config Get Version"); let version = std::str::from_utf8(&NEON_PKG_VERSION)?; let revision = std::str::from_utf8(&NEON_REVISION)?; diff --git a/evm_loader/program/src/instruction/create_main_treasury.rs b/evm_loader/program/src/instruction/create_main_treasury.rs index 86c5f7972..1378551ad 100644 --- a/evm_loader/program/src/instruction/create_main_treasury.rs +++ b/evm_loader/program/src/instruction/create_main_treasury.rs @@ -6,10 +6,11 @@ use crate::{ use solana_program::{ account_info::AccountInfo, bpf_loader_upgradeable::{self, UpgradeableLoaderState}, - msg, program_pack::Pack, pubkey::Pubkey, + rent::Rent, system_program, + sysvar::Sysvar, }; struct Accounts<'a> { @@ -69,7 +70,7 @@ pub fn process<'a>( accounts: &'a [AccountInfo<'a>], _instruction: &[u8], ) -> Result<()> { - msg!("Instruction: Create Main Treasury"); + log_msg!("Instruction: Create Main Treasury"); let accounts = Accounts::from_slice(accounts)?; let (expected_key, bump_seed) = MainTreasury::address(program_id); @@ -120,6 +121,7 @@ pub fn process<'a>( accounts.main_treasury, &[TREASURY_POOL_SEED.as_bytes(), &[bump_seed]], spl_token::state::Account::LEN, + &Rent::get()?, )?; accounts.token_program.create_account( diff --git a/evm_loader/program/src/instruction/mod.rs b/evm_loader/program/src/instruction/mod.rs index 33ebfb407..a46695e0d 100644 --- a/evm_loader/program/src/instruction/mod.rs +++ b/evm_loader/program/src/instruction/mod.rs @@ -157,9 +157,6 @@ pub enum EvmInstruction { /// None CreateMainTreasury, - /// Block additional accounts - AccountBlockAdd, - /// Create a User Balance account /// /// Accounts: @@ -220,7 +217,6 @@ impl EvmInstruction { 0x25 => Self::HolderDelete, // 37 0x26 => Self::HolderWrite, // 38 0x29 => Self::CreateMainTreasury, // 41 - 0x2B => Self::AccountBlockAdd, // 43 0x30 => Self::AccountCreateBalance, // 48 0x31 => Self::Deposit, // 49 @@ -246,7 +242,6 @@ impl EvmInstruction { } } -pub mod account_block_add; pub mod account_create_balance; pub mod account_holder_create; pub mod account_holder_delete; diff --git a/evm_loader/program/src/instruction/neon_tokens_deposit.rs b/evm_loader/program/src/instruction/neon_tokens_deposit.rs index b0ecb80fc..bfab10115 100644 --- a/evm_loader/program/src/instruction/neon_tokens_deposit.rs +++ b/evm_loader/program/src/instruction/neon_tokens_deposit.rs @@ -1,7 +1,7 @@ use arrayref::array_ref; use ethnum::U256; use solana_program::program::invoke_signed; -use solana_program::{account_info::AccountInfo, pubkey::Pubkey}; +use solana_program::{account_info::AccountInfo, pubkey::Pubkey, rent::Rent, sysvar::Sysvar}; use spl_associated_token_account::get_associated_token_address; use crate::account::{program, token, AccountsDB, BalanceAccount, Operator, ACCOUNT_SEED_VERSION}; @@ -42,7 +42,7 @@ pub fn process<'a>( accounts: &'a [AccountInfo<'a>], instruction: &[u8], ) -> Result<()> { - solana_program::msg!("Instruction: Deposit"); + log_msg!("Instruction: Deposit"); let parsed_accounts = Accounts::from_slice(accounts)?; @@ -162,7 +162,9 @@ fn execute(program_id: &Pubkey, accounts: Accounts, address: Address, chain_id: excessive_lamports += crate::account::legacy::update_legacy_accounts(&accounts_db)?; } - let mut balance_account = BalanceAccount::create(address, chain_id, &accounts_db, None)?; + let rent = Rent::get()?; + + let mut balance_account = BalanceAccount::create(address, chain_id, &accounts_db, None, &rent)?; balance_account.mint(deposit)?; **accounts_db.operator().try_borrow_mut_lamports()? += excessive_lamports; diff --git a/evm_loader/program/src/instruction/transaction_cancel.rs b/evm_loader/program/src/instruction/transaction_cancel.rs index 21360e828..489685363 100644 --- a/evm_loader/program/src/instruction/transaction_cancel.rs +++ b/evm_loader/program/src/instruction/transaction_cancel.rs @@ -1,4 +1,6 @@ use crate::account::{AccountsDB, BalanceAccount, Operator, StateAccount}; +use crate::config::DEFAULT_CHAIN_ID; +use crate::debug::log_data; use crate::error::{Error, Result}; use crate::gasometer::{CANCEL_TRX_COST, LAST_ITERATION_COST}; use arrayref::array_ref; @@ -10,7 +12,7 @@ pub fn process<'a>( accounts: &'a [AccountInfo<'a>], instruction: &[u8], ) -> Result<()> { - solana_program::msg!("Instruction: Cancel Transaction"); + log_msg!("Instruction: Cancel Transaction"); let transaction_hash = array_ref![instruction, 0, 32]; @@ -18,20 +20,20 @@ pub fn process<'a>( let operator = Operator::from_account(&accounts[1])?; let operator_balance = BalanceAccount::from_account(program_id, accounts[2].clone())?; - solana_program::log::sol_log_data(&[b"HASH", transaction_hash]); - solana_program::log::sol_log_data(&[b"MINER", operator_balance.address().as_bytes()]); + log_data(&[b"HASH", transaction_hash]); + log_data(&[b"MINER", operator_balance.address().as_bytes()]); let accounts_db = AccountsDB::new(&accounts[3..], operator, Some(operator_balance), None, None); - let storage = StateAccount::restore(program_id, storage_info, &accounts_db, true)?; + let (storage, _) = StateAccount::restore(program_id, storage_info, &accounts_db)?; validate(&storage, transaction_hash)?; execute(program_id, accounts_db, storage) } fn validate(storage: &StateAccount, transaction_hash: &[u8; 32]) -> Result<()> { - if &storage.trx_hash() != transaction_hash { + if &storage.trx().hash() != transaction_hash { return Err(Error::HolderInvalidHash( - storage.trx_hash(), + storage.trx().hash(), *transaction_hash, )); } @@ -44,9 +46,11 @@ fn execute<'a>( mut accounts: AccountsDB<'a>, mut storage: StateAccount<'a>, ) -> Result<()> { + let trx_chain_id = storage.trx().chain_id().unwrap_or(DEFAULT_CHAIN_ID); + let used_gas = U256::ZERO; let total_used_gas = storage.gas_used(); - solana_program::log::sol_log_data(&[ + log_data(&[ b"GAS", &used_gas.to_le_bytes(), &total_used_gas.to_le_bytes(), @@ -56,15 +60,13 @@ fn execute<'a>( let _ = storage.consume_gas(gas, accounts.operator_balance()); // ignore error let origin = storage.trx_origin(); - let (origin_pubkey, _) = origin.find_balance_address(program_id, storage.trx_chain_id()); + let (origin_pubkey, _) = origin.find_balance_address(program_id, trx_chain_id); { let origin_info = accounts.get(&origin_pubkey).clone(); let mut account = BalanceAccount::from_account(program_id, origin_info)?; - account.increment_nonce()?; - storage.refund_unused_gas(&mut account)?; } - storage.finalize(program_id, &accounts) + storage.finalize(program_id) } diff --git a/evm_loader/program/src/instruction/transaction_execute.rs b/evm_loader/program/src/instruction/transaction_execute.rs index fe661b485..781411d83 100644 --- a/evm_loader/program/src/instruction/transaction_execute.rs +++ b/evm_loader/program/src/instruction/transaction_execute.rs @@ -1,28 +1,14 @@ -use solana_program::pubkey::Pubkey; - use crate::account::{AccountsDB, AllocateResult}; use crate::account_storage::ProgramAccountStorage; +use crate::debug::log_data; use crate::error::{Error, Result}; +use crate::evm::tracing::NoopEventListener; use crate::evm::Machine; use crate::executor::{ExecutorState, SyncedExecutorState}; use crate::gasometer::Gasometer; use crate::instruction::transaction_step::log_return_value; use crate::types::{Address, Transaction}; -pub fn validate(program_id: &Pubkey, accounts: &AccountsDB) -> Result<()> { - for account in accounts { - if account.owner != program_id { - continue; - } - - if crate::account::is_blocked(program_id, account)? { - return Err(Error::AccountBlocked(*account.key)); - } - } - - Ok(()) -} - pub fn execute( accounts: AccountsDB<'_>, mut gasometer: Gasometer, @@ -35,11 +21,15 @@ pub fn execute( let mut account_storage = ProgramAccountStorage::new(accounts)?; + trx.validate(origin, &account_storage)?; + + account_storage.origin(origin, &trx)?.increment_nonce()?; + let (exit_reason, apply_state) = { let mut backend = ExecutorState::new(&account_storage); - let mut evm = Machine::new(trx, origin, &mut backend)?; - let (result, _) = evm.execute(u64::MAX, &mut backend)?; + let mut evm = Machine::new(&trx, origin, &mut backend, None::)?; + let (result, _, _) = evm.execute(u64::MAX, &mut backend)?; let actions = backend.into_actions(); @@ -60,7 +50,7 @@ pub fn execute( return Err(Error::OutOfGas(gas_limit, used_gas)); } - solana_program::log::sol_log_data(&[b"GAS", &used_gas.to_le_bytes(), &used_gas.to_le_bytes()]); + log_data(&[b"GAS", &used_gas.to_le_bytes(), &used_gas.to_le_bytes()]); let gas_cost = used_gas.saturating_mul(gas_price); account_storage.transfer_gas_payment(origin, chain_id, gas_cost)?; @@ -82,11 +72,15 @@ pub fn execute_with_solana_call( let mut account_storage = ProgramAccountStorage::new(accounts)?; + trx.validate(origin, &account_storage)?; + + account_storage.origin(origin, &trx)?.increment_nonce()?; + let exit_reason = { let mut backend = SyncedExecutorState::new(&mut account_storage); - let mut evm = Machine::new(trx, origin, &mut backend)?; - let (result, _) = evm.execute(u64::MAX, &mut backend)?; + let mut evm = Machine::new(&trx, origin, &mut backend, None::)?; + let (result, _, _) = evm.execute(u64::MAX, &mut backend)?; result }; @@ -99,7 +93,7 @@ pub fn execute_with_solana_call( return Err(Error::OutOfGas(gas_limit, used_gas)); } - solana_program::log::sol_log_data(&[b"GAS", &used_gas.to_le_bytes(), &used_gas.to_le_bytes()]); + log_data(&[b"GAS", &used_gas.to_le_bytes(), &used_gas.to_le_bytes()]); let gas_cost = used_gas.saturating_mul(gas_price); account_storage.transfer_gas_payment(origin, chain_id, gas_cost)?; diff --git a/evm_loader/program/src/instruction/transaction_execute_from_account.rs b/evm_loader/program/src/instruction/transaction_execute_from_account.rs index 5b28216f6..7d7ad6c2d 100644 --- a/evm_loader/program/src/instruction/transaction_execute_from_account.rs +++ b/evm_loader/program/src/instruction/transaction_execute_from_account.rs @@ -1,4 +1,5 @@ use crate::account::{program, AccountsDB, BalanceAccount, Holder, Operator, Treasury}; +use crate::debug::log_data; use crate::error::Result; use crate::gasometer::Gasometer; use crate::types::Transaction; @@ -12,7 +13,7 @@ pub fn process<'a>( accounts: &'a [AccountInfo<'a>], instruction: &[u8], ) -> Result<()> { - solana_program::msg!("Instruction: Execute Transaction from Account"); + log_msg!("Instruction: Execute Transaction from Account"); let treasury_index = u32::from_le_bytes(*array_ref![instruction, 0, 4]); @@ -29,8 +30,8 @@ pub fn process<'a>( let origin = trx.recover_caller_address()?; - solana_program::log::sol_log_data(&[b"HASH", &trx.hash()]); - solana_program::log::sol_log_data(&[b"MINER", operator_balance.address().as_bytes()]); + log_data(&[b"HASH", &trx.hash()]); + log_data(&[b"MINER", operator_balance.address().as_bytes()]); let accounts_db = AccountsDB::new( &accounts[5..], @@ -45,6 +46,5 @@ pub fn process<'a>( gasometer.record_address_lookup_table(accounts); gasometer.record_write_to_holder(&trx); - super::transaction_execute::validate(program_id, &accounts_db)?; super::transaction_execute::execute(accounts_db, gasometer, trx, origin) } diff --git a/evm_loader/program/src/instruction/transaction_execute_from_account_solana_call.rs b/evm_loader/program/src/instruction/transaction_execute_from_account_solana_call.rs index 272d9f527..95fa8019a 100644 --- a/evm_loader/program/src/instruction/transaction_execute_from_account_solana_call.rs +++ b/evm_loader/program/src/instruction/transaction_execute_from_account_solana_call.rs @@ -1,4 +1,5 @@ use crate::account::{program, AccountsDB, BalanceAccount, Holder, Operator, Treasury}; +use crate::debug::log_data; use crate::error::Result; use crate::gasometer::Gasometer; use crate::types::Transaction; @@ -12,7 +13,7 @@ pub fn process<'a>( accounts: &'a [AccountInfo<'a>], instruction: &[u8], ) -> Result<()> { - solana_program::msg!("Instruction: Execute Transaction from Account with Solana call"); + log_msg!("Instruction: Execute Transaction from Account with Solana call"); let treasury_index = u32::from_le_bytes(*array_ref![instruction, 0, 4]); @@ -29,8 +30,8 @@ pub fn process<'a>( let origin = trx.recover_caller_address()?; - solana_program::log::sol_log_data(&[b"HASH", &trx.hash()]); - solana_program::log::sol_log_data(&[b"MINER", operator_balance.address().as_bytes()]); + log_data(&[b"HASH", &trx.hash()]); + log_data(&[b"MINER", operator_balance.address().as_bytes()]); let accounts_db = AccountsDB::new( &accounts[5..], @@ -45,6 +46,5 @@ pub fn process<'a>( gasometer.record_address_lookup_table(accounts); gasometer.record_write_to_holder(&trx); - super::transaction_execute::validate(program_id, &accounts_db)?; super::transaction_execute::execute_with_solana_call(accounts_db, gasometer, trx, origin) } diff --git a/evm_loader/program/src/instruction/transaction_execute_from_instruction.rs b/evm_loader/program/src/instruction/transaction_execute_from_instruction.rs index ffe964728..b51d593d8 100644 --- a/evm_loader/program/src/instruction/transaction_execute_from_instruction.rs +++ b/evm_loader/program/src/instruction/transaction_execute_from_instruction.rs @@ -1,4 +1,5 @@ use crate::account::{program, AccountsDB, BalanceAccount, Operator, Treasury}; +use crate::debug::log_data; use crate::error::Result; use crate::gasometer::Gasometer; use crate::types::Transaction; @@ -12,7 +13,7 @@ pub fn process<'a>( accounts: &'a [AccountInfo<'a>], instruction: &[u8], ) -> Result<()> { - solana_program::msg!("Instruction: Execute Transaction from Instruction"); + log_msg!("Instruction: Execute Transaction from Instruction"); let treasury_index = u32::from_le_bytes(*array_ref![instruction, 0, 4]); let messsage = &instruction[4..]; @@ -25,8 +26,8 @@ pub fn process<'a>( let trx = Transaction::from_rlp(messsage)?; let origin = trx.recover_caller_address()?; - solana_program::log::sol_log_data(&[b"HASH", &trx.hash()]); - solana_program::log::sol_log_data(&[b"MINER", operator_balance.address().as_bytes()]); + log_data(&[b"HASH", &trx.hash()]); + log_data(&[b"MINER", operator_balance.address().as_bytes()]); let accounts_db = AccountsDB::new( &accounts[4..], @@ -40,6 +41,5 @@ pub fn process<'a>( gasometer.record_solana_transaction_cost(); gasometer.record_address_lookup_table(accounts); - super::transaction_execute::validate(program_id, &accounts_db)?; super::transaction_execute::execute(accounts_db, gasometer, trx, origin) } diff --git a/evm_loader/program/src/instruction/transaction_execute_from_instruction_solana_call.rs b/evm_loader/program/src/instruction/transaction_execute_from_instruction_solana_call.rs index b756808c9..3bae7620f 100644 --- a/evm_loader/program/src/instruction/transaction_execute_from_instruction_solana_call.rs +++ b/evm_loader/program/src/instruction/transaction_execute_from_instruction_solana_call.rs @@ -1,4 +1,5 @@ use crate::account::{program, AccountsDB, BalanceAccount, Operator, Treasury}; +use crate::debug::log_data; use crate::error::Result; use crate::gasometer::Gasometer; use crate::types::Transaction; @@ -12,7 +13,7 @@ pub fn process<'a>( accounts: &'a [AccountInfo<'a>], instruction: &[u8], ) -> Result<()> { - solana_program::msg!("Instruction: Execute Transaction from Instruction with Solana call"); + log_msg!("Instruction: Execute Transaction from Instruction with Solana call"); let treasury_index = u32::from_le_bytes(*array_ref![instruction, 0, 4]); let messsage = &instruction[4..]; @@ -25,8 +26,8 @@ pub fn process<'a>( let trx = Transaction::from_rlp(messsage)?; let origin = trx.recover_caller_address()?; - solana_program::log::sol_log_data(&[b"HASH", &trx.hash()]); - solana_program::log::sol_log_data(&[b"MINER", operator_balance.address().as_bytes()]); + log_data(&[b"HASH", &trx.hash()]); + log_data(&[b"MINER", operator_balance.address().as_bytes()]); let accounts_db = AccountsDB::new( &accounts[4..], @@ -40,6 +41,5 @@ pub fn process<'a>( gasometer.record_solana_transaction_cost(); gasometer.record_address_lookup_table(accounts); - super::transaction_execute::validate(program_id, &accounts_db)?; super::transaction_execute::execute_with_solana_call(accounts_db, gasometer, trx, origin) } diff --git a/evm_loader/program/src/instruction/transaction_step.rs b/evm_loader/program/src/instruction/transaction_step.rs index c8b20a3af..aa377f6db 100644 --- a/evm_loader/program/src/instruction/transaction_step.rs +++ b/evm_loader/program/src/instruction/transaction_step.rs @@ -1,36 +1,48 @@ use crate::account::{AccountsDB, AllocateResult, StateAccount}; use crate::account_storage::{AccountStorage, ProgramAccountStorage}; use crate::config::{EVM_STEPS_LAST_ITERATION_MAX, EVM_STEPS_MIN}; +use crate::debug::log_data; use crate::error::{Error, Result}; +use crate::evm::tracing::NoopEventListener; use crate::evm::{ExitStatus, Machine}; use crate::executor::{Action, ExecutorState}; use crate::gasometer::Gasometer; -use crate::types::{Address, Transaction}; type EvmBackend<'a, 'r> = ExecutorState<'r, ProgramAccountStorage<'a>>; -type Evm<'a, 'r> = Machine>; +type Evm<'a, 'r> = Machine, NoopEventListener>; pub fn do_begin<'a>( accounts: AccountsDB<'a>, mut storage: StateAccount<'a>, gasometer: Gasometer, - trx: Transaction, - origin: Address, ) -> Result<()> { debug_print!("do_begin"); - let accounts = ProgramAccountStorage::new(accounts)?; + let account_storage = ProgramAccountStorage::new(accounts)?; + + let origin = storage.trx_origin(); + + storage.trx().validate(origin, &account_storage)?; - let mut backend = ExecutorState::new(&accounts); - let evm = Machine::new(trx, origin, &mut backend)?; + // Increment origin nonce in the first iteration + // This allows us to run multiple iterative transactions from the same sender in parallel + // These transactions are guaranteed to start in a correct sequence + // BUT they finalize in an undefined order + let mut origin_account = account_storage.origin(origin, storage.trx())?; + origin_account.increment_nonce()?; // Burn `gas_limit` tokens from the origin account // Later we will mint them to the operator - let mut origin_balance = accounts.create_balance_account(origin, storage.trx_chain_id())?; - origin_balance.burn(storage.gas_limit_in_tokens()?)?; + // Remaining tokens are returned to the origin in the last iteration + let gas_limit_in_tokens = storage.trx().gas_limit_in_tokens()?; + origin_account.burn(gas_limit_in_tokens)?; + + // Initialize EVM and serialize it to the Holder + let mut backend = ExecutorState::new(&account_storage); + let evm = Machine::new(storage.trx(), origin, &mut backend, None)?; serialize_evm_state(&mut storage, &backend, &evm)?; - finalize(0, storage, accounts, None, gasometer) + finalize(0, storage, account_storage, None, gasometer) } pub fn do_continue<'a>( @@ -38,32 +50,35 @@ pub fn do_continue<'a>( accounts: AccountsDB<'a>, mut storage: StateAccount<'a>, gasometer: Gasometer, + reset: bool, ) -> Result<()> { debug_print!("do_continue"); - if (step_count < EVM_STEPS_MIN) && (storage.trx_gas_price() > 0) { + if (step_count < EVM_STEPS_MIN) && (storage.trx().gas_price() > 0) { return Err(Error::Custom(format!( "Step limit {step_count} below minimum {EVM_STEPS_MIN}" ))); } let account_storage = ProgramAccountStorage::new(accounts)?; - let (mut backend, mut evm) = deserialize_evm_state(&storage, &account_storage)?; + let (mut backend, mut evm) = if reset { + let mut backend = ExecutorState::new(&account_storage); + let evm = Machine::new(storage.trx(), storage.trx_origin(), &mut backend, None)?; + (backend, evm) + } else { + deserialize_evm_state(&storage, &account_storage)? + }; - let (result, steps_executed) = { - match backend.exit_status() { - Some(status) => (status.clone(), 0_u64), - None => evm.execute(step_count, &mut backend)?, - } + let (result, steps_executed, _) = match backend.exit_status() { + Some(status) => (status.clone(), 0_u64, None), + None => evm.execute(step_count, &mut backend)?, }; if (result != ExitStatus::StepLimit) && (steps_executed > 0) { backend.set_exit_status(result.clone()); } - if steps_executed > 0 { - serialize_evm_state(&mut storage, &backend, &evm)?; - } + serialize_evm_state(&mut storage, &backend, &evm)?; let results = match result { ExitStatus::StepLimit => None, @@ -102,7 +117,7 @@ fn finalize<'a>( let used_gas = gasometer.used_gas(); let total_used_gas = gasometer.used_gas_total(); - solana_program::log::sol_log_data(&[ + log_data(&[ b"GAS", &used_gas.to_le_bytes(), &total_used_gas.to_le_bytes(), @@ -113,18 +128,18 @@ fn finalize<'a>( if let Some(status) = status { log_return_value(&status); - let mut origin = accounts.balance_account(storage.trx_origin(), storage.trx_chain_id())?; + let mut origin = accounts.origin(storage.trx_origin(), storage.trx())?; storage.refund_unused_gas(&mut origin)?; - storage.finalize(accounts.program_id(), accounts.db())?; + storage.finalize(accounts.program_id())?; + } else { + storage.save_data()?; } Ok(()) } pub fn log_return_value(status: &ExitStatus) { - use solana_program::log::sol_log_data; - let code: u8 = match status { ExitStatus::Stop => 0x11, ExitStatus::Return(_) => 0x12, @@ -133,12 +148,12 @@ pub fn log_return_value(status: &ExitStatus) { ExitStatus::StepLimit => unreachable!(), }; - solana_program::msg!("exit_status={:#04X}", code); // Tests compatibility + log_msg!("exit_status={:#04X}", code); // Tests compatibility if let ExitStatus::Revert(msg) = status { crate::error::print_revert_message(msg); } - sol_log_data(&[b"RETURN", &[code]]); + log_data(&[b"RETURN", &[code]]); } fn serialize_evm_state( diff --git a/evm_loader/program/src/instruction/transaction_step_from_account.rs b/evm_loader/program/src/instruction/transaction_step_from_account.rs index 2697a430b..4cc712ba7 100644 --- a/evm_loader/program/src/instruction/transaction_step_from_account.rs +++ b/evm_loader/program/src/instruction/transaction_step_from_account.rs @@ -1,9 +1,9 @@ use crate::account::legacy::{TAG_HOLDER_DEPRECATED, TAG_STATE_FINALIZED_DEPRECATED}; use crate::account::{ - program, AccountsDB, BalanceAccount, Holder, Operator, StateAccount, Treasury, TAG_HOLDER, - TAG_STATE, TAG_STATE_FINALIZED, + program, AccountsDB, AccountsStatus, BalanceAccount, Holder, Operator, StateAccount, Treasury, + TAG_HOLDER, TAG_STATE, TAG_STATE_FINALIZED, }; - +use crate::debug::log_data; use crate::error::{Error, Result}; use crate::gasometer::Gasometer; use crate::instruction::transaction_step::{do_begin, do_continue}; @@ -17,7 +17,7 @@ pub fn process<'a>( accounts: &'a [AccountInfo<'a>], instruction: &[u8], ) -> Result<()> { - solana_program::msg!("Instruction: Begin or Continue Transaction from Account"); + log_msg!("Instruction: Begin or Continue Transaction from Account"); process_inner(program_id, accounts, instruction, false) } @@ -57,7 +57,7 @@ pub fn process_inner<'a>( match tag { TAG_HOLDER | TAG_HOLDER_DEPRECATED => { - let trx = { + let mut trx = { let holder = Holder::from_account(program_id, holder_or_storage.clone())?; holder.validate_owner(accounts_db.operator())?; @@ -69,8 +69,13 @@ pub fn process_inner<'a>( trx }; - solana_program::log::sol_log_data(&[b"HASH", &trx.hash]); - solana_program::log::sol_log_data(&[b"MINER", miner_address.as_bytes()]); + log_data(&[b"HASH", &trx.hash]); + log_data(&[b"MINER", miner_address.as_bytes()]); + + if increase_gas_limit { + assert!(trx.chain_id().is_none()); + trx.use_gas_limit_multiplier(); + } let origin = trx.recover_caller_address()?; @@ -82,32 +87,28 @@ pub fn process_inner<'a>( excessive_lamports += crate::account::legacy::update_legacy_accounts(&accounts_db)?; gasometer.refund_lamports(excessive_lamports); - let mut storage = StateAccount::new( + let storage = StateAccount::new( program_id, holder_or_storage.clone(), &accounts_db, origin, - &trx, + trx, )?; - if increase_gas_limit { - assert!(trx.chain_id().is_none()); - storage.use_gas_limit_multiplier(); - } - - do_begin(accounts_db, storage, gasometer, trx, origin) + do_begin(accounts_db, storage, gasometer) } TAG_STATE => { - let storage = - StateAccount::restore(program_id, holder_or_storage.clone(), &accounts_db, false)?; + let (storage, accounts_status) = + StateAccount::restore(program_id, holder_or_storage.clone(), &accounts_db)?; - solana_program::log::sol_log_data(&[b"HASH", &storage.trx_hash()]); - solana_program::log::sol_log_data(&[b"MINER", miner_address.as_bytes()]); + log_data(&[b"HASH", &storage.trx().hash()]); + log_data(&[b"MINER", miner_address.as_bytes()]); let mut gasometer = Gasometer::new(storage.gas_used(), accounts_db.operator())?; gasometer.record_solana_transaction_cost(); - do_continue(step_count, accounts_db, storage, gasometer) + let reset = accounts_status != AccountsStatus::Ok; + do_continue(step_count, accounts_db, storage, gasometer, reset) } TAG_STATE_FINALIZED | TAG_STATE_FINALIZED_DEPRECATED => Err(Error::StorageAccountFinalized), _ => Err(Error::AccountInvalidTag(*holder_or_storage.key, TAG_HOLDER)), diff --git a/evm_loader/program/src/instruction/transaction_step_from_account_no_chainid.rs b/evm_loader/program/src/instruction/transaction_step_from_account_no_chainid.rs index 041ac6b42..f911b998a 100644 --- a/evm_loader/program/src/instruction/transaction_step_from_account_no_chainid.rs +++ b/evm_loader/program/src/instruction/transaction_step_from_account_no_chainid.rs @@ -6,7 +6,7 @@ pub fn process<'a>( accounts: &'a [AccountInfo<'a>], instruction: &[u8], ) -> Result<()> { - solana_program::msg!("Instruction: Begin or Continue Transaction from Account Without ChainId"); + log_msg!("Instruction: Begin or Continue Transaction from Account Without ChainId"); super::transaction_step_from_account::process_inner(program_id, accounts, instruction, true) } diff --git a/evm_loader/program/src/instruction/transaction_step_from_instruction.rs b/evm_loader/program/src/instruction/transaction_step_from_instruction.rs index 656d8915d..adc2b7ae6 100644 --- a/evm_loader/program/src/instruction/transaction_step_from_instruction.rs +++ b/evm_loader/program/src/instruction/transaction_step_from_instruction.rs @@ -1,8 +1,9 @@ use crate::account::legacy::{TAG_HOLDER_DEPRECATED, TAG_STATE_FINALIZED_DEPRECATED}; use crate::account::{ - program, AccountsDB, BalanceAccount, Operator, StateAccount, Treasury, TAG_HOLDER, TAG_STATE, - TAG_STATE_FINALIZED, + program, AccountsDB, AccountsStatus, BalanceAccount, Operator, StateAccount, Treasury, + TAG_HOLDER, TAG_STATE, TAG_STATE_FINALIZED, }; +use crate::debug::log_data; use crate::error::{Error, Result}; use crate::gasometer::Gasometer; use crate::instruction::transaction_step::{do_begin, do_continue}; @@ -16,7 +17,7 @@ pub fn process<'a>( accounts: &'a [AccountInfo<'a>], instruction: &[u8], ) -> Result<()> { - solana_program::msg!("Instruction: Begin or Continue Transaction from Instruction"); + log_msg!("Instruction: Begin or Continue Transaction from Instruction"); let treasury_index = u32::from_le_bytes(*array_ref![instruction, 0, 4]); let step_count = u64::from(u32::from_le_bytes(*array_ref![instruction, 4, 4])); @@ -52,8 +53,8 @@ pub fn process<'a>( let trx = Transaction::from_rlp(message)?; let origin = trx.recover_caller_address()?; - solana_program::log::sol_log_data(&[b"HASH", &trx.hash()]); - solana_program::log::sol_log_data(&[b"MINER", miner_address.as_bytes()]); + log_data(&[b"HASH", &trx.hash()]); + log_data(&[b"MINER", miner_address.as_bytes()]); let mut gasometer = Gasometer::new(U256::ZERO, accounts_db.operator())?; gasometer.record_solana_transaction_cost(); @@ -62,20 +63,22 @@ pub fn process<'a>( excessive_lamports += crate::account::legacy::update_legacy_accounts(&accounts_db)?; gasometer.refund_lamports(excessive_lamports); - let storage = StateAccount::new(program_id, storage_info, &accounts_db, origin, &trx)?; + let storage = StateAccount::new(program_id, storage_info, &accounts_db, origin, trx)?; - do_begin(accounts_db, storage, gasometer, trx, origin) + do_begin(accounts_db, storage, gasometer) } TAG_STATE => { - let storage = StateAccount::restore(program_id, storage_info, &accounts_db, false)?; + let (storage, accounts_status) = + StateAccount::restore(program_id, storage_info, &accounts_db)?; - solana_program::log::sol_log_data(&[b"HASH", &storage.trx_hash()]); - solana_program::log::sol_log_data(&[b"MINER", miner_address.as_bytes()]); + log_data(&[b"HASH", &storage.trx().hash()]); + log_data(&[b"MINER", miner_address.as_bytes()]); let mut gasometer = Gasometer::new(storage.gas_used(), accounts_db.operator())?; gasometer.record_solana_transaction_cost(); - do_continue(step_count, accounts_db, storage, gasometer) + let reset = accounts_status != AccountsStatus::Ok; + do_continue(step_count, accounts_db, storage, gasometer, reset) } _ => Err(Error::AccountInvalidTag(*storage_info.key, TAG_HOLDER)), }?; diff --git a/evm_loader/program/src/types/mod.rs b/evm_loader/program/src/types/mod.rs index 04afe313f..756a5a409 100644 --- a/evm_loader/program/src/types/mod.rs +++ b/evm_loader/program/src/types/mod.rs @@ -6,4 +6,5 @@ pub use transaction::Transaction; pub use transaction::TransactionPayload; mod address; +pub mod serde; mod transaction; diff --git a/evm_loader/program/src/types/serde.rs b/evm_loader/program/src/types/serde.rs new file mode 100644 index 000000000..eb94cfbbe --- /dev/null +++ b/evm_loader/program/src/types/serde.rs @@ -0,0 +1,130 @@ +// use ethnum::U256; + +// serde_with::serde_conv!( +// pub U256AsWords, +// U256, +// |value: &U256| { value.into_words() }, +// |words: (u128, u128)| -> Result<_, std::convert::Infallible> { Ok(U256::from_words(words.0, words.1)) } +// ); + +pub mod option_u256 { + use std::fmt::{self, Formatter}; + + use ethnum::U256; + use serde::{de::Visitor, Deserializer, Serializer}; + + pub fn serialize(value: &Option, serializer: S) -> Result + where + S: Serializer, + { + if let Some(value) = value { + serializer.serialize_bytes(&value.to_le_bytes()) + } else { + serializer.serialize_bytes(&[]) + } + } + + struct BytesVisitor; + + impl<'de> Visitor<'de> for BytesVisitor { + type Value = Option; + + fn expecting(&self, f: &mut Formatter) -> fmt::Result { + f.write_str(concat!("32 bytes in little endian")) + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: serde::de::Error, + { + if v.is_empty() { + return Ok(None); + } + + let bytes = v + .try_into() + .map_err(|_| E::invalid_length(v.len(), &self))?; + + Ok(Some(U256::from_le_bytes(bytes))) + } + } + + #[doc(hidden)] + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + deserializer.deserialize_bytes(BytesVisitor) + } +} + +pub mod bytes_32 { + pub fn serialize(value: &[u8; 32], serializer: S) -> Result + where + S: serde::ser::Serializer, + { + if serializer.is_human_readable() { + serializer.serialize_str(&hex::encode(value)) + } else { + serializer.serialize_bytes(value) + } + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result<[u8; 32], D::Error> + where + D: serde::Deserializer<'de>, + { + struct BytesVisitor; + + impl<'de> serde::de::Visitor<'de> for BytesVisitor { + type Value = [u8; 32]; + + fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.write_str("[u8; 32]") + } + + fn visit_str(self, value: &str) -> Result + where + E: serde::de::Error, + { + use serde::de::Unexpected::Str; + + let value = hex::decode(value) + .map_err(|_| serde::de::Error::invalid_value(Str(value), &self))?; + + let value_len = value.len(); + value + .try_into() + .map_err(|_| serde::de::Error::invalid_length(value_len, &self)) + } + + fn visit_bytes(self, value: &[u8]) -> Result + where + E: serde::de::Error, + { + value + .try_into() + .map_err(|_| serde::de::Error::invalid_length(value.len(), &self)) + } + + fn visit_seq(self, mut seq: S) -> Result + where + S: serde::de::SeqAccess<'de>, + { + let mut bytes = Vec::with_capacity(32); + while let Some(b) = seq.next_element()? { + bytes.push(b); + } + bytes + .try_into() + .map_err(|_| serde::de::Error::custom("Invalid [u8; 32] value")) + } + } + + if deserializer.is_human_readable() { + deserializer.deserialize_str(BytesVisitor) + } else { + deserializer.deserialize_bytes(BytesVisitor) + } + } +} diff --git a/evm_loader/program/src/types/transaction.rs b/evm_loader/program/src/types/transaction.rs index bae39cf34..c3d82173e 100644 --- a/evm_loader/program/src/types/transaction.rs +++ b/evm_loader/program/src/types/transaction.rs @@ -1,13 +1,22 @@ use ethnum::U256; +use maybe_async::maybe_async; +use serde::{Deserialize, Serialize}; use std::convert::TryInto; -use crate::error::Error; +use crate::{ + account_storage::AccountStorage, config::GAS_LIMIT_MULTIPLIER_NO_CHAINID, error::Error, +}; -use super::Address; +use super::{ + serde::{bytes_32, option_u256}, + Address, +}; #[repr(transparent)] -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] -pub struct StorageKey([u8; 32]); +#[derive( + Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize, +)] +pub struct StorageKey(#[serde(with = "bytes_32")] [u8; 32]); impl rlp::Decodable for StorageKey { fn decode(rlp: &rlp::Rlp) -> Result { @@ -67,17 +76,25 @@ impl TransactionEnvelope { } } -#[derive(Debug)] +#[derive(Debug, Serialize, Deserialize)] pub struct LegacyTx { pub nonce: u64, + #[serde(with = "ethnum::serde::bytes::le")] pub gas_price: U256, + #[serde(with = "ethnum::serde::bytes::le")] pub gas_limit: U256, pub target: Option
, + #[serde(with = "ethnum::serde::bytes::le")] pub value: U256, + #[serde(with = "serde_bytes")] pub call_data: Vec, + #[serde(with = "ethnum::serde::bytes::le")] pub v: U256, + #[serde(with = "ethnum::serde::bytes::le")] pub r: U256, + #[serde(with = "ethnum::serde::bytes::le")] pub s: U256, + #[serde(with = "option_u256")] pub chain_id: Option, pub recovery_id: u8, } @@ -148,16 +165,23 @@ impl rlp::Decodable for LegacyTx { } } -#[derive(Debug)] +#[derive(Debug, Serialize, Deserialize)] pub struct AccessListTx { pub nonce: u64, + #[serde(with = "ethnum::serde::bytes::le")] pub gas_price: U256, + #[serde(with = "ethnum::serde::bytes::le")] pub gas_limit: U256, pub target: Option
, + #[serde(with = "ethnum::serde::bytes::le")] pub value: U256, + #[serde(with = "serde_bytes")] pub call_data: Vec, + #[serde(with = "ethnum::serde::bytes::le")] pub r: U256, + #[serde(with = "ethnum::serde::bytes::le")] pub s: U256, + #[serde(with = "ethnum::serde::bytes::le")] pub chain_id: U256, pub recovery_id: u8, pub access_list: Vec<(Address, Vec)>, @@ -245,17 +269,19 @@ impl rlp::Decodable for AccessListTx { // TODO: Will be added as a part of EIP-1559 // struct DynamicFeeTx {} -#[derive(Debug)] +#[derive(Debug, Serialize, Deserialize)] pub enum TransactionPayload { Legacy(LegacyTx), AccessList(AccessListTx), } -#[derive(Debug)] +#[derive(Debug, Serialize, Deserialize)] pub struct Transaction { pub transaction: TransactionPayload, pub byte_len: usize, + #[serde(with = "bytes_32")] pub hash: [u8; 32], + #[serde(with = "bytes_32")] pub signed_hash: [u8; 32], } @@ -491,6 +517,12 @@ impl Transaction { } } + pub fn gas_limit_in_tokens(&self) -> Result { + self.gas_price() + .checked_mul(self.gas_limit()) + .ok_or(Error::IntegerOverflow) + } + #[must_use] pub fn target(&self) -> Option
{ match self.transaction { @@ -515,16 +547,6 @@ impl Transaction { } } - #[must_use] - pub fn into_call_data(self) -> crate::evm::Buffer { - match self.transaction { - TransactionPayload::Legacy(LegacyTx { call_data, .. }) - | TransactionPayload::AccessList(AccessListTx { call_data, .. }) => { - crate::evm::Buffer::from_vec(call_data) - } - } - } - #[must_use] pub fn r(&self) -> U256 { match self.transaction { @@ -582,6 +604,40 @@ impl Transaction { TransactionPayload::Legacy(_) => None, } } + + pub fn use_gas_limit_multiplier(&mut self) { + let gas_multiplier = U256::from(GAS_LIMIT_MULTIPLIER_NO_CHAINID); + + match &mut self.transaction { + TransactionPayload::AccessList(AccessListTx { gas_limit, .. }) + | TransactionPayload::Legacy(LegacyTx { gas_limit, .. }) => { + *gas_limit = gas_limit.saturating_mul(gas_multiplier); + } + } + } + + #[maybe_async] + pub async fn validate( + &self, + origin: Address, + backend: &impl AccountStorage, + ) -> Result<(), crate::error::Error> { + let chain_id = self + .chain_id() + .unwrap_or_else(|| backend.default_chain_id()); + + if !backend.is_valid_chain_id(chain_id) { + return Err(Error::InvalidChainId(chain_id)); + } + + let origin_nonce = backend.nonce(origin, chain_id).await; + if origin_nonce != self.nonce() { + let error = Error::InvalidTransactionNonce(origin, origin_nonce, self.nonce()); + return Err(error); + } + + Ok(()) + } } #[inline]