From 126b36e5dc335e2b9979d4f6c33423631754e60b Mon Sep 17 00:00:00 2001 From: Jernej Kos Date: Mon, 18 Dec 2023 10:06:08 +0100 Subject: [PATCH] runtime-sdk: Refactor state handling The Context is now only used to serve read-only environment state (e.g. information about the current block) and provide the Runtime type for static dispatch. All mutable state (including storage) has been moved into a new type, State, which supports nesting and transactions (similar to the previous CurrentStore which only provided this for storage). This allows many things to be simplified and a better integration with the EVM's substates (which now no longer require hacks to handle events and messages). --- .../src/module_derive/method_handler.rs | 14 +- .../src/module_derive/migration_handler.rs | 2 +- runtime-sdk-macros/src/module_derive/mod.rs | 28 +- runtime-sdk/modules/contracts/src/abi/mod.rs | 4 +- .../modules/contracts/src/abi/oasis/crypto.rs | 12 +- .../modules/contracts/src/abi/oasis/env.rs | 4 +- .../modules/contracts/src/abi/oasis/test.rs | 134 +- runtime-sdk/modules/contracts/src/code.rs | 7 +- runtime-sdk/modules/contracts/src/lib.rs | 124 +- runtime-sdk/modules/contracts/src/results.rs | 87 +- runtime-sdk/modules/contracts/src/store.rs | 22 +- runtime-sdk/modules/contracts/src/test.rs | 240 +-- runtime-sdk/modules/contracts/src/types.rs | 8 +- runtime-sdk/modules/evm/src/backend.rs | 150 +- runtime-sdk/modules/evm/src/lib.rs | 211 ++- runtime-sdk/modules/evm/src/mock.rs | 18 +- runtime-sdk/modules/evm/src/precompile/gas.rs | 17 +- .../modules/evm/src/precompile/subcall.rs | 37 +- .../modules/evm/src/precompile/testing.rs | 7 +- runtime-sdk/modules/evm/src/signed_call.rs | 6 +- runtime-sdk/modules/evm/src/state.rs | 30 +- runtime-sdk/modules/evm/src/test.rs | 321 ++-- runtime-sdk/src/callformat.rs | 27 +- runtime-sdk/src/context.rs | 1250 +------------ runtime-sdk/src/crypto/random.rs | 59 +- runtime-sdk/src/dispatcher.rs | 382 ++-- runtime-sdk/src/lib.rs | 7 +- runtime-sdk/src/module.rs | 98 +- runtime-sdk/src/modules/accounts/mod.rs | 301 ++-- runtime-sdk/src/modules/accounts/test.rs | 243 ++- runtime-sdk/src/modules/consensus/mod.rs | 166 +- runtime-sdk/src/modules/consensus/test.rs | 609 +++---- .../src/modules/consensus_accounts/mod.rs | 263 +-- .../src/modules/consensus_accounts/state.rs | 25 +- .../src/modules/consensus_accounts/test.rs | 374 ++-- runtime-sdk/src/modules/core/mod.rs | 329 ++-- runtime-sdk/src/modules/core/test.rs | 1016 +++++------ runtime-sdk/src/modules/rewards/mod.rs | 17 +- runtime-sdk/src/runtime.rs | 15 +- runtime-sdk/src/state.rs | 1590 +++++++++++++++++ runtime-sdk/src/storage/current.rs | 512 ------ runtime-sdk/src/storage/mod.rs | 2 - runtime-sdk/src/subcall.rs | 122 +- runtime-sdk/src/testing/mock.rs | 49 +- runtime-sdk/src/types/transaction.rs | 9 +- .../runtimes/benchmarking/src/runtime/mod.rs | 21 +- tests/runtimes/simple-consensus/src/lib.rs | 2 +- .../runtimes/simple-keyvalue/src/keyvalue.rs | 152 +- tests/runtimes/simple-keyvalue/src/lib.rs | 2 +- 49 files changed, 4426 insertions(+), 4699 deletions(-) create mode 100644 runtime-sdk/src/state.rs delete mode 100644 runtime-sdk/src/storage/current.rs diff --git a/runtime-sdk-macros/src/module_derive/method_handler.rs b/runtime-sdk-macros/src/module_derive/method_handler.rs index c8082e4c92..9e1183fd78 100644 --- a/runtime-sdk-macros/src/module_derive/method_handler.rs +++ b/runtime-sdk-macros/src/module_derive/method_handler.rs @@ -111,7 +111,7 @@ impl super::Deriver for DeriveMethodHandler { if h.attrs.is_internal { quote! { |ctx, body| { - if !ctx.is_internal() { + if !sdk::state::CurrentState::with_env(|env| env.is_internal()) { return Err(sdk::modules::core::Error::Forbidden.into()); } Self::#ident(ctx, body) @@ -128,8 +128,8 @@ impl super::Deriver for DeriveMethodHandler { quote! {} } else { quote! { - fn dispatch_call( - ctx: &mut C, + fn dispatch_call( + ctx: &C, method: &str, body: cbor::Value, ) -> DispatchResult { @@ -146,7 +146,7 @@ impl super::Deriver for DeriveMethodHandler { let query_parameters_impl = { quote! { - fn query_parameters(_ctx: &mut C, _args: ()) -> Result<::Parameters, ::Error> { + fn query_parameters(_ctx: &C, _args: ()) -> Result<::Parameters, ::Error> { Ok(Self::params()) } } @@ -158,7 +158,7 @@ impl super::Deriver for DeriveMethodHandler { if handler_names.is_empty() { quote! { fn dispatch_query( - ctx: &mut C, + ctx: &C, method: &str, args: cbor::Value, ) -> DispatchResult> { @@ -171,7 +171,7 @@ impl super::Deriver for DeriveMethodHandler { } else { quote! { fn dispatch_query( - ctx: &mut C, + ctx: &C, method: &str, args: cbor::Value, ) -> DispatchResult> { @@ -196,7 +196,7 @@ impl super::Deriver for DeriveMethodHandler { } else { quote! { fn dispatch_message_result( - ctx: &mut C, + ctx: &C, handler_name: &str, result: MessageResult, ) -> DispatchResult { diff --git a/runtime-sdk-macros/src/module_derive/migration_handler.rs b/runtime-sdk-macros/src/module_derive/migration_handler.rs index e86742a556..9cd6182375 100644 --- a/runtime-sdk-macros/src/module_derive/migration_handler.rs +++ b/runtime-sdk-macros/src/module_derive/migration_handler.rs @@ -101,7 +101,7 @@ impl super::Deriver for DeriveMigrationHandler { #genesis_ty fn init_or_migrate( - _ctx: &mut C, + _ctx: &C, meta: &mut sdk::modules::core::types::Metadata, genesis: Self::Genesis, ) -> bool { diff --git a/runtime-sdk-macros/src/module_derive/mod.rs b/runtime-sdk-macros/src/module_derive/mod.rs index 9721d507ac..277d0bcf55 100644 --- a/runtime-sdk-macros/src/module_derive/mod.rs +++ b/runtime-sdk-macros/src/module_derive/mod.rs @@ -120,7 +120,7 @@ mod tests { #[automatically_derived] impl sdk::module::MethodHandler for MyModule { fn dispatch_query( - ctx: &mut C, + ctx: &C, method: &str, args: cbor::Value, ) -> DispatchResult> @@ -135,7 +135,7 @@ mod tests { } #[automatically_derived] impl MyModule { - fn query_parameters(_ctx: &mut C, _args: ()) -> Result<::Parameters, ::Error> { + fn query_parameters(_ctx: &C, _args: ()) -> Result<::Parameters, ::Error> { Ok(Self::params()) } } @@ -190,8 +190,8 @@ mod tests { _ => module::DispatchResult::Unhandled(body), } } - fn dispatch_call( - ctx: &mut C, + fn dispatch_call( + ctx: &C, method: &str, body: cbor::Value, ) -> DispatchResult { @@ -201,7 +201,7 @@ mod tests { module::dispatch_call(ctx, body, Self::my_other_call) } "my_module.MyInternalCall" => module::dispatch_call(ctx, body, |ctx, body| { - if !ctx.is_internal() { + if !sdk::state::CurrentState::with_env(|env| env.is_internal()) { return Err(sdk::modules::core::Error::Forbidden.into()); } Self::my_internal_call(ctx, body) @@ -210,7 +210,7 @@ mod tests { } } fn dispatch_query( - ctx: &mut C, + ctx: &C, method: &str, args: cbor::Value, ) -> DispatchResult> @@ -241,7 +241,7 @@ mod tests { } #[automatically_derived] impl MyModule { - fn query_parameters(_ctx: &mut C, _args: ()) -> Result<::Parameters, ::Error> { + fn query_parameters(_ctx: &C, _args: ()) -> Result<::Parameters, ::Error> { Ok(Self::params()) } #[handler(prefetch = "my_module.MyCall")] @@ -281,7 +281,7 @@ mod tests { #[automatically_derived] impl sdk::module::MethodHandler for MyModule { fn dispatch_query( - ctx: &mut C, + ctx: &C, method: &str, args: cbor::Value, ) -> DispatchResult> @@ -322,7 +322,7 @@ mod tests { #[automatically_derived] impl MyModule { fn query_parameters( - _ctx: &mut C, + _ctx: &C, _args: (), ) -> Result<::Parameters, ::Error> { @@ -359,7 +359,7 @@ mod tests { #[automatically_derived] impl sdk::module::MethodHandler for MyModule { fn dispatch_query( - ctx: &mut C, + ctx: &C, method: &str, args: cbor::Value, ) -> DispatchResult> @@ -381,7 +381,7 @@ mod tests { } #[automatically_derived] impl MyModule { - fn query_parameters(_ctx: &mut C, _args: ()) -> Result<::Parameters, ::Error> { + fn query_parameters(_ctx: &C, _args: ()) -> Result<::Parameters, ::Error> { Ok(Self::params()) } #[handler(query = "my_module.MyMC")] @@ -438,7 +438,7 @@ mod tests { impl sdk::module::MigrationHandler for MyModule { type Genesis = Genesis; fn init_or_migrate( - _ctx: &mut C, + _ctx: &C, meta: &mut sdk::modules::core::types::Metadata, genesis: Self::Genesis, ) -> bool { @@ -482,7 +482,7 @@ mod tests { #[automatically_derived] impl sdk::module::MethodHandler for MyModule { fn dispatch_query( - ctx: &mut C, + ctx: &C, method: &str, args: cbor::Value, ) -> DispatchResult> @@ -504,7 +504,7 @@ mod tests { } #[automatically_derived] impl MyModule { - fn query_parameters(_ctx: &mut C, _args: ()) -> Result<::Parameters, ::Error> { + fn query_parameters(_ctx: &C, _args: ()) -> Result<::Parameters, ::Error> { Ok(Self::params()) } #[handler(query = "my_module.MyMC")] diff --git a/runtime-sdk/modules/contracts/src/abi/mod.rs b/runtime-sdk/modules/contracts/src/abi/mod.rs index e65e70350f..ac0233ba38 100644 --- a/runtime-sdk/modules/contracts/src/abi/mod.rs +++ b/runtime-sdk/modules/contracts/src/abi/mod.rs @@ -93,7 +93,7 @@ pub struct Info { /// Execution context. pub struct ExecutionContext<'ctx, C: Context> { /// Transaction context. - pub tx_context: &'ctx mut C, + pub tx_context: &'ctx C, /// Contracts module parameters. pub params: &'ctx Parameters, @@ -127,7 +127,7 @@ impl<'ctx, C: Context> ExecutionContext<'ctx, C> { caller_address: Address, read_only: bool, call_format: CallFormat, - tx_context: &'ctx mut C, + tx_context: &'ctx C, ) -> Self { Self { tx_context, diff --git a/runtime-sdk/modules/contracts/src/abi/oasis/crypto.rs b/runtime-sdk/modules/contracts/src/abi/oasis/crypto.rs index f7416fcab5..2d4498ae5a 100644 --- a/runtime-sdk/modules/contracts/src/abi/oasis/crypto.rs +++ b/runtime-sdk/modules/contracts/src/abi/oasis/crypto.rs @@ -3,7 +3,7 @@ use std::convert::TryInto; use oasis_contract_sdk_crypto as crypto; use oasis_contract_sdk_types::crypto::SignatureKind; -use oasis_runtime_sdk::{context::Context, crypto::signature}; +use oasis_runtime_sdk::{context::Context, crypto::signature, state::CurrentState}; use super::{memory::Region, OasisV1}; use crate::{ @@ -145,10 +145,12 @@ impl OasisV1 { let pers = Region::from_arg((pers_ptr, pers_len)) .as_slice(&memory) .map_err(|_| wasm3::Trap::Abort)?; - let mut rng = ec.tx_context.rng(pers).map_err(|e| { - ec.aborted = Some(e.into()); - wasm3::Trap::Abort - })?; + let mut rng = CurrentState::with(|state| state.rng().fork(ec.tx_context, pers)) + .map_err(|e| { + ec.aborted = Some(Error::ExecutionFailed(e.into())); + wasm3::Trap::Abort + })?; + let output = Region::from_arg((dst_ptr, num_bytes)) .as_slice_mut(&mut memory) .map_err(|_| wasm3::Trap::Abort)?; diff --git a/runtime-sdk/modules/contracts/src/abi/oasis/env.rs b/runtime-sdk/modules/contracts/src/abi/oasis/env.rs index b0ef649390..4b7d59e1b0 100644 --- a/runtime-sdk/modules/contracts/src/abi/oasis/env.rs +++ b/runtime-sdk/modules/contracts/src/abi/oasis/env.rs @@ -102,7 +102,7 @@ impl OasisV1 { } /// Perform environment query dispatch. -fn dispatch_query(ctx: &mut C, query: QueryRequest) -> QueryResponse { +fn dispatch_query(ctx: &C, query: QueryRequest) -> QueryResponse { match query { // Information about the current runtime block. QueryRequest::BlockInfo => QueryResponse::BlockInfo { @@ -124,7 +124,7 @@ fn dispatch_query(ctx: &mut C, query: QueryRequest) -> /// Perform accounts API query dispatch. fn dispatch_accounts_query( - _ctx: &mut C, + _ctx: &C, query: AccountsQuery, ) -> QueryResponse { match query { diff --git a/runtime-sdk/modules/contracts/src/abi/oasis/test.rs b/runtime-sdk/modules/contracts/src/abi/oasis/test.rs index f8346c026e..b30ea4786f 100644 --- a/runtime-sdk/modules/contracts/src/abi/oasis/test.rs +++ b/runtime-sdk/modules/contracts/src/abi/oasis/test.rs @@ -1,12 +1,13 @@ //! Tests for Oasis ABIs. use oasis_runtime_sdk::{ - context::{self, BatchContext, TxContext}, + context::Context, core::common::crypto::hash::Hash, error::Error as _, modules, modules::core, + state::CurrentState, testing::mock, - types::address::Address, + types::{address::Address, transaction::CallFormat}, }; use crate::{abi, types, wasm, Config, Error, Parameters}; @@ -28,7 +29,7 @@ impl core::Config for CoreConfig {} #[test] fn test_validate_and_transform() { - fn test(_ctx: C, params: &Parameters) { + fn test(_ctx: C, params: &Parameters) { // Non-WASM code. let code = Vec::new(); let result = wasm::validate_and_transform::(&code, types::ABI::OasisV1, params); @@ -301,11 +302,8 @@ fn test_validate_and_transform() { } let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); - let params = Parameters::default(); - ctx.with_tx(mock::transaction().into(), |ctx, _| { - test::(ctx, ¶ms); - }); + let ctx = mock.create_ctx(); + test::(ctx, &Parameters::default()); } fn run_contract_with_defaults( @@ -315,7 +313,7 @@ fn run_contract_with_defaults( call_data: cbor::Value, ) -> Result { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx_for_runtime::(context::Mode::ExecuteTx, true); + let ctx = mock.create_ctx_for_runtime::(true); let params = Parameters::default(); core::Module::::init(core::Genesis { @@ -328,68 +326,62 @@ fn run_contract_with_defaults( let mut tx = mock::transaction(); tx.auth_info.fee.gas = gas_limit; - ctx.with_tx(tx.into(), |mut ctx, _| -> Result { - fn transform( - _ctx: &mut C, - code: &[u8], - params: &Parameters, - ) -> (Vec, abi::Info) { - wasm::validate_and_transform::(code, types::ABI::OasisV1, params) - .unwrap() - } - let (code, abi_info) = transform(&mut ctx, code, ¶ms); - - let code_info = types::Code { - id: 1.into(), - hash: Hash::empty_hash(), - abi: types::ABI::OasisV1, - abi_sv: abi_info.abi_sv, - uploader: Address::default(), - instantiate_policy: types::Policy::Everyone, - }; - let call = types::Instantiate { - code_id: code_info.id, - upgrades_policy: types::Policy::Everyone, - data: cbor::to_vec(instantiate_data), - tokens: vec![], - }; - let instance_info = types::Instance { - id: 1.into(), - code_id: 1.into(), - creator: Address::default(), - upgrades_policy: call.upgrades_policy, - }; - - // Instantiate the contract. - let contract = wasm::Contract { - code_info: &code_info, - code: &code, - instance_info: &instance_info, - }; - let mut exec_ctx = abi::ExecutionContext::new( - ¶ms, - &code_info, - &instance_info, - gas_limit, - Default::default(), - ctx.is_read_only(), - ctx.tx_call_format(), - &mut ctx, - ); - wasm::instantiate::(&mut exec_ctx, &contract, &call).inner?; - - // Call the contract. - let call = types::Call { - id: 1.into(), - data: cbor::to_vec(call_data), - tokens: vec![], - }; - let result = wasm::call::(&mut exec_ctx, &contract, &call).inner?; - let result: cbor::Value = - cbor::from_slice(&result.data).map_err(|err| Error::ExecutionFailed(err.into()))?; - - Ok(result) - }) + fn transform(_ctx: &C, code: &[u8], params: &Parameters) -> (Vec, abi::Info) { + wasm::validate_and_transform::(code, types::ABI::OasisV1, params) + .unwrap() + } + let (code, abi_info) = transform(&ctx, code, ¶ms); + + let code_info = types::Code { + id: 1.into(), + hash: Hash::empty_hash(), + abi: types::ABI::OasisV1, + abi_sv: abi_info.abi_sv, + uploader: Address::default(), + instantiate_policy: types::Policy::Everyone, + }; + let call = types::Instantiate { + code_id: code_info.id, + upgrades_policy: types::Policy::Everyone, + data: cbor::to_vec(instantiate_data), + tokens: vec![], + }; + let instance_info = types::Instance { + id: 1.into(), + code_id: 1.into(), + creator: Address::default(), + upgrades_policy: call.upgrades_policy, + }; + + // Instantiate the contract. + let contract = wasm::Contract { + code_info: &code_info, + code: &code, + instance_info: &instance_info, + }; + let mut exec_ctx = abi::ExecutionContext::new( + ¶ms, + &code_info, + &instance_info, + gas_limit, + Default::default(), + false, + CallFormat::Plain, + &ctx, + ); + wasm::instantiate::(&mut exec_ctx, &contract, &call).inner?; + + // Call the contract. + let call = types::Call { + id: 1.into(), + data: cbor::to_vec(call_data), + tokens: vec![], + }; + let result = wasm::call::(&mut exec_ctx, &contract, &call).inner?; + let result: cbor::Value = + cbor::from_slice(&result.data).map_err(|err| Error::ExecutionFailed(err.into()))?; + + Ok(result) } #[test] diff --git a/runtime-sdk/modules/contracts/src/code.rs b/runtime-sdk/modules/contracts/src/code.rs index c03d32e04d..13607eea11 100644 --- a/runtime-sdk/modules/contracts/src/code.rs +++ b/runtime-sdk/modules/contracts/src/code.rs @@ -9,7 +9,8 @@ use once_cell::sync::Lazy; use oasis_runtime_sdk::{ core::common::crypto::hash::Hash, - storage::{self, CurrentStore, Store}, + state::CurrentState, + storage::{self, Store}, }; use crate::{state, types, Config, Error, Module, MODULE_NAME}; @@ -27,7 +28,7 @@ impl Module { } // TODO: Support local untrusted cache to avoid storage queries. - let code = CurrentStore::with(|store| { + let code = CurrentState::with_store(|store| { let mut store = storage::PrefixStore::new(store, &MODULE_NAME); let code_store = storage::PrefixStore::new(&mut store, &state::CODE); code_store @@ -60,7 +61,7 @@ impl Module { encoder.write_all(code).unwrap(); drop(encoder); // Make sure data is flushed. - CurrentStore::with(|store| { + CurrentState::with_store(|store| { let mut store = storage::PrefixStore::new(store, &MODULE_NAME); let mut code_store = storage::PrefixStore::new(&mut store, &state::CODE); code_store.insert(&code_info.id.to_storage_key(), &output); diff --git a/runtime-sdk/modules/contracts/src/lib.rs b/runtime-sdk/modules/contracts/src/lib.rs index 29023184f4..65973d298b 100644 --- a/runtime-sdk/modules/contracts/src/lib.rs +++ b/runtime-sdk/modules/contracts/src/lib.rs @@ -13,15 +13,17 @@ use thiserror::Error; use oasis_contract_sdk_types::storage::StoreKind; use oasis_runtime_sdk::{ self as sdk, - context::{Context, TxContext}, + context::Context, core::common::crypto::hash::Hash, handler, migration, module, module::Module as _, modules, modules::{accounts::API as _, core::API as _}, runtime::Runtime, - sdk_derive, storage, - storage::{CurrentStore, Store}, + sdk_derive, + state::CurrentState, + storage, + storage::Store, types::transaction::CallFormat, }; @@ -364,7 +366,7 @@ pub struct Module { impl Module { /// Loads code information for the specified code identifier. fn load_code_info(code_id: types::CodeId) -> Result { - CurrentStore::with(|store| { + CurrentState::with_store(|store| { let mut store = storage::PrefixStore::new(store, &MODULE_NAME); let code_info_store = storage::TypedStore::new(storage::PrefixStore::new(&mut store, &state::CODE_INFO)); @@ -378,7 +380,7 @@ impl Module { /// Stores specified code information. fn store_code_info(code_info: types::Code) -> Result<(), Error> { - CurrentStore::with(|store| { + CurrentState::with_store(|store| { let mut store = storage::PrefixStore::new(store, &MODULE_NAME); let mut code_info_store = storage::TypedStore::new(storage::PrefixStore::new(&mut store, &state::CODE_INFO)); @@ -390,7 +392,7 @@ impl Module { /// Loads specified instance information. fn load_instance_info(instance_id: types::InstanceId) -> Result { - CurrentStore::with(|store| { + CurrentState::with_store(|store| { let mut store = storage::PrefixStore::new(store, &MODULE_NAME); let instance_info_store = storage::TypedStore::new(storage::PrefixStore::new( &mut store, @@ -406,7 +408,7 @@ impl Module { /// Stores specified instance information. fn store_instance_info(instance_info: types::Instance) -> Result<(), Error> { - CurrentStore::with(|store| { + CurrentState::with_store(|store| { let mut store = storage::PrefixStore::new(store, &MODULE_NAME); let mut instance_info_store = storage::TypedStore::new(storage::PrefixStore::new( &mut store, @@ -434,12 +436,12 @@ impl Module { } #[handler(call = "contracts.Upload")] - pub fn tx_upload( - ctx: &mut C, + pub fn tx_upload( + ctx: &C, body: types::Upload, ) -> Result { let params = Self::params(); - let uploader = ctx.tx_caller_address(); + let uploader = CurrentState::with_env(|env| env.tx_caller_address()); // Validate code size. let code_size: u32 = body @@ -452,9 +454,8 @@ impl Module { } // Account for base gas. - ::Core::use_tx_gas(ctx, params.gas_costs.tx_upload)?; + ::Core::use_tx_gas(params.gas_costs.tx_upload)?; ::Core::use_tx_gas( - ctx, params .gas_costs .tx_upload_per_byte @@ -472,7 +473,6 @@ impl Module { // Account for extra gas needed after decompression. let plain_code_size: u32 = code.len().try_into().unwrap(); ::Core::use_tx_gas( - ctx, params .gas_costs .tx_upload_per_byte @@ -498,7 +498,6 @@ impl Module { return Err(Error::CodeTooLarge(inst_code_size, params.max_code_size)); } ::Core::use_tx_gas( - ctx, params .gas_costs .tx_upload_per_byte @@ -506,7 +505,7 @@ impl Module { )?; // Assign next identifier. - let id = CurrentStore::with(|store| { + let id = CurrentState::with_store(|store| { let mut store = storage::PrefixStore::new(store, &MODULE_NAME); let mut tstore = storage::TypedStore::new(&mut store); let id: types::CodeId = tstore.get(state::NEXT_CODE_IDENTIFIER).unwrap_or_default(); @@ -530,14 +529,14 @@ impl Module { } #[handler(call = "contracts.Instantiate")] - pub fn tx_instantiate( - ctx: &mut C, + pub fn tx_instantiate( + ctx: &C, body: types::Instantiate, ) -> Result { let params = Self::params(); - let creator = ctx.tx_caller_address(); + let creator = CurrentState::with_env(|env| env.tx_caller_address()); - ::Core::use_tx_gas(ctx, params.gas_costs.tx_instantiate)?; + ::Core::use_tx_gas(params.gas_costs.tx_instantiate)?; if !ctx.should_execute_contracts() { // Only fast checks are allowed. @@ -546,11 +545,11 @@ impl Module { // Load code information, enforce instantiation policy and load the code. let code_info = Self::load_code_info(body.code_id)?; - code_info.instantiate_policy.enforce(ctx)?; + code_info.instantiate_policy.enforce(&creator)?; let code = Self::load_code(&code_info)?; // Assign next identifier. - let id = CurrentStore::with(|store| { + let id = CurrentState::with_store(|store| { let mut store = storage::PrefixStore::new(store, &MODULE_NAME); let mut tstore = storage::TypedStore::new(&mut store); let id: types::InstanceId = tstore @@ -571,7 +570,7 @@ impl Module { // Transfer any attached tokens. for tokens in &body.tokens { - Cfg::Accounts::transfer(ctx, creator, instance_info.address(), tokens) + Cfg::Accounts::transfer(creator, instance_info.address(), tokens) .map_err(|_| Error::InsufficientCallerBalance)? } // Run instantiation function. @@ -584,10 +583,10 @@ impl Module { ¶ms, &code_info, &instance_info, - ::Core::remaining_tx_gas(ctx), - ctx.tx_caller_address(), - ctx.is_read_only(), - ctx.tx_call_format(), + ::Core::remaining_tx_gas(), + creator, + CurrentState::with_env(|env| env.is_read_only()), + CurrentState::with_env(|env| env.tx_call_format()), ctx, ); let result = wasm::instantiate::(&mut exec_ctx, &contract, &body); @@ -598,14 +597,11 @@ impl Module { } #[handler(call = "contracts.Call", allow_interactive)] - pub fn tx_call( - ctx: &mut C, - body: types::Call, - ) -> Result { + pub fn tx_call(ctx: &C, body: types::Call) -> Result { let params = Self::params(); - let caller = ctx.tx_caller_address(); + let caller = CurrentState::with_env(|env| env.tx_caller_address()); - ::Core::use_tx_gas(ctx, params.gas_costs.tx_call)?; + ::Core::use_tx_gas(params.gas_costs.tx_call)?; if !ctx.should_execute_contracts() { // Only fast checks are allowed. @@ -619,7 +615,7 @@ impl Module { // Transfer any attached tokens. for tokens in &body.tokens { - Cfg::Accounts::transfer(ctx, caller, instance_info.address(), tokens) + Cfg::Accounts::transfer(caller, instance_info.address(), tokens) .map_err(|_| Error::InsufficientCallerBalance)? } // Run call function. @@ -632,10 +628,10 @@ impl Module { ¶ms, &code_info, &instance_info, - ::Core::remaining_tx_gas(ctx), - ctx.tx_caller_address(), - ctx.is_read_only(), - ctx.tx_call_format(), + ::Core::remaining_tx_gas(), + caller, + CurrentState::with_env(|env| env.is_read_only()), + CurrentState::with_env(|env| env.tx_call_format()), ctx, ); let result = wasm::call::(&mut exec_ctx, &contract, &body); @@ -646,21 +642,22 @@ impl Module { } #[handler(call = "contracts.ChangeUpgradePolicy")] - pub fn tx_change_upgrade_policy( - ctx: &mut C, + pub fn tx_change_upgrade_policy( + ctx: &C, body: types::ChangeUpgradePolicy, ) -> Result<(), Error> { let params = Self::params(); + let caller = CurrentState::with_env(|env| env.tx_caller_address()); - ::Core::use_tx_gas(ctx, params.gas_costs.tx_change_upgrade_policy)?; + ::Core::use_tx_gas(params.gas_costs.tx_change_upgrade_policy)?; - if ctx.is_check_only() { + if CurrentState::with_env(|env| env.is_check_only()) { return Ok(()); } // Load instance information. let mut instance_info = Self::load_instance_info(body.id)?; - instance_info.upgrades_policy.enforce(ctx)?; + instance_info.upgrades_policy.enforce(&caller)?; // Change upgrade policy. instance_info.upgrades_policy = body.upgrades_policy; @@ -670,11 +667,11 @@ impl Module { } #[handler(call = "contracts.Upgrade")] - pub fn tx_upgrade(ctx: &mut C, body: types::Upgrade) -> Result<(), Error> { + pub fn tx_upgrade(ctx: &C, body: types::Upgrade) -> Result<(), Error> { let params = Self::params(); - let caller = ctx.tx_caller_address(); + let caller = CurrentState::with_env(|env| env.tx_caller_address()); - ::Core::use_tx_gas(ctx, params.gas_costs.tx_upgrade)?; + ::Core::use_tx_gas(params.gas_costs.tx_upgrade)?; if !ctx.should_execute_contracts() { // Only fast checks are allowed. @@ -683,7 +680,7 @@ impl Module { // Load instance information and code. let mut instance_info = Self::load_instance_info(body.id)?; - instance_info.upgrades_policy.enforce(ctx)?; + instance_info.upgrades_policy.enforce(&caller)?; if instance_info.code_id == body.code_id { return Err(Error::CodeAlreadyUpgraded(body.code_id.as_u64())); } @@ -692,7 +689,7 @@ impl Module { // Transfer any attached tokens. for tokens in &body.tokens { - Cfg::Accounts::transfer(ctx, caller, instance_info.address(), tokens) + Cfg::Accounts::transfer(caller, instance_info.address(), tokens) .map_err(|_| Error::InsufficientCallerBalance)? } // Run pre-upgrade function on the previous contract. @@ -705,10 +702,10 @@ impl Module { ¶ms, &code_info, &instance_info, - ::Core::remaining_tx_gas(ctx), - ctx.tx_caller_address(), - ctx.is_read_only(), - ctx.tx_call_format(), + ::Core::remaining_tx_gas(), + caller, + CurrentState::with_env(|env| env.is_read_only()), + CurrentState::with_env(|env| env.tx_call_format()), ctx, ); // Pre-upgrade invocation must succeed for the upgrade to proceed. @@ -731,10 +728,10 @@ impl Module { ¶ms, &code_info, &instance_info, - ::Core::remaining_tx_gas(ctx), - ctx.tx_caller_address(), - ctx.is_read_only(), - ctx.tx_call_format(), + ::Core::remaining_tx_gas(), + caller, + CurrentState::with_env(|env| env.is_read_only()), + CurrentState::with_env(|env| env.tx_call_format()), ctx, ); @@ -745,16 +742,13 @@ impl Module { } #[handler(query = "contracts.Code")] - pub fn query_code( - _ctx: &mut C, - args: types::CodeQuery, - ) -> Result { + pub fn query_code(_ctx: &C, args: types::CodeQuery) -> Result { Self::load_code_info(args.id) } #[handler(query = "contracts.CodeStorage")] pub fn query_code_storage( - _ctx: &mut C, + _ctx: &C, args: types::CodeStorageQuery, ) -> Result { let code_info = Self::load_code_info(args.id)?; @@ -765,7 +759,7 @@ impl Module { #[handler(query = "contracts.Instance")] pub fn query_instance( - _ctx: &mut C, + _ctx: &C, args: types::InstanceQuery, ) -> Result { Self::load_instance_info(args.id) @@ -773,7 +767,7 @@ impl Module { #[handler(query = "contracts.InstanceStorage")] pub fn query_instance_storage( - ctx: &mut C, + ctx: &C, args: types::InstanceStorageQuery, ) -> Result { let instance_info = Self::load_instance_info(args.id)?; @@ -787,7 +781,7 @@ impl Module { #[handler(query = "contracts.InstanceRawStorage", expensive)] pub fn query_instance_raw_storage( - ctx: &mut C, + ctx: &C, args: types::InstanceRawStorageQuery, ) -> Result { let cfg: LocalConfig = ctx.local_config(MODULE_NAME).unwrap_or_default(); @@ -825,7 +819,7 @@ impl Module { #[handler(query = "contracts.PublicKey")] pub fn query_public_key( - _ctx: &mut C, + _ctx: &C, _args: types::PublicKeyQuery, ) -> Result { Err(Error::Unsupported) @@ -833,7 +827,7 @@ impl Module { #[handler(query = "contracts.Custom", expensive)] pub fn query_custom( - ctx: &mut C, + ctx: &C, args: types::CustomQuery, ) -> Result { let params = Self::params(); diff --git a/runtime-sdk/modules/contracts/src/results.rs b/runtime-sdk/modules/contracts/src/results.rs index cf3954fd17..1b6330fb91 100644 --- a/runtime-sdk/modules/contracts/src/results.rs +++ b/runtime-sdk/modules/contracts/src/results.rs @@ -7,10 +7,11 @@ use oasis_contract_sdk_types::{ ExecutionOk, }; use oasis_runtime_sdk::{ - context::TxContext, + context::Context, event::etag_for_event, modules::core::API as _, runtime::Runtime, + state::CurrentState, subcall::{self, SubcallInfo}, types::transaction::CallerAddress, }; @@ -22,62 +23,60 @@ use crate::{ }; /// Process an execution result by performing gas accounting and returning the inner result. -pub(crate) fn process_execution_result( - ctx: &mut C, +pub(crate) fn process_execution_result( + _ctx: &C, result: ExecutionResult, ) -> Result { // The following call should never fail as we accounted for all the gas in advance. - ::Core::use_tx_gas(ctx, result.gas_used)?; + ::Core::use_tx_gas(result.gas_used)?; result.inner } /// Process a successful execution result. -pub(crate) fn process_execution_success( - ctx: &mut C, +pub(crate) fn process_execution_success( + ctx: &C, params: &Parameters, contract: &wasm::Contract<'_>, result: ExecutionOk, ) -> Result, Error> { // Process events. - process_events(ctx, contract, result.events)?; + process_events(contract, result.events)?; // Process subcalls. let result = process_subcalls::(ctx, params, contract, result.messages, result.data)?; Ok(result) } -fn process_events( - ctx: &mut C, - contract: &wasm::Contract<'_>, - events: Vec, -) -> Result<(), Error> { +fn process_events(contract: &wasm::Contract<'_>, events: Vec) -> Result<(), Error> { // Transform contract events into tags using the SDK scheme. - for event in events { - ctx.emit_etag(etag_for_event( - &if event.module.is_empty() { - format!("{}.{}", MODULE_NAME, contract.code_info.id.as_u64()) - } else { - format!( - "{}.{}.{}", - MODULE_NAME, - contract.code_info.id.as_u64(), - event.module, - ) - }, - event.code, - cbor::to_value(ContractEvent { - id: contract.instance_info.id, - data: event.data, - }), - )); - } + CurrentState::with(|state| { + for event in events { + state.emit_event_raw(etag_for_event( + &if event.module.is_empty() { + format!("{}.{}", MODULE_NAME, contract.code_info.id.as_u64()) + } else { + format!( + "{}.{}.{}", + MODULE_NAME, + contract.code_info.id.as_u64(), + event.module, + ) + }, + event.code, + cbor::to_value(ContractEvent { + id: contract.instance_info.id, + data: event.data, + }), + )); + } + }); Ok(()) } -fn process_subcalls( - ctx: &mut C, +fn process_subcalls( + ctx: &C, params: &Parameters, contract: &wasm::Contract<'_>, messages: Vec, @@ -89,7 +88,6 @@ fn process_subcalls( // Charge gas for each emitted message. ::Core::use_tx_gas( - ctx, params .gas_costs .subcall_dispatch @@ -109,8 +107,8 @@ fn process_subcalls( } // Properly propagate original call format and read-only flag. - let orig_call_format = ctx.tx_call_format(); - let orig_read_only = ctx.is_read_only(); + let (orig_call_format, orig_read_only) = + CurrentState::with_env(|env| (env.tx_call_format(), env.is_read_only())); // Process emitted messages recursively. for msg in messages { @@ -124,7 +122,7 @@ fn process_subcalls( max_gas, } => { // Compute the amount of gas that can be used. - let remaining_gas = ::Core::remaining_tx_gas(ctx); + let remaining_gas = ::Core::remaining_tx_gas(); let max_gas = max_gas.unwrap_or(remaining_gas); let max_gas = if max_gas > remaining_gas { remaining_gas @@ -146,16 +144,7 @@ fn process_subcalls( // Use any gas that was used inside the child context. This should never fail as we // preconfigured the amount of available gas. - ::Core::use_tx_gas(ctx, result.gas_used)?; - - // Forward any emitted event tags. - ctx.emit_etags(result.state.events); - - // Forward any emitted runtime messages. - for (msg, hook) in result.state.messages { - // This should never fail as child context has the right limits configured. - ctx.emit_message(msg, hook)?; - } + ::Core::use_tx_gas(result.gas_used)?; // Process replies based on filtering criteria. let result = result.call_result; @@ -173,8 +162,8 @@ fn process_subcalls( params, contract.code_info, contract.instance_info, - ::Core::remaining_tx_gas(ctx), - ctx.tx_caller_address(), + ::Core::remaining_tx_gas(), + CurrentState::with_env(|env| env.tx_caller_address()), orig_read_only, orig_call_format, ctx, diff --git a/runtime-sdk/modules/contracts/src/store.rs b/runtime-sdk/modules/contracts/src/store.rs index 5bdee268db..18a3a7ded5 100644 --- a/runtime-sdk/modules/contracts/src/store.rs +++ b/runtime-sdk/modules/contracts/src/store.rs @@ -4,7 +4,8 @@ use oasis_runtime_sdk::{ context::Context, dispatcher, keymanager::{self, StateKey}, - storage::{self, CurrentStore, Store}, + state::CurrentState, + storage::{self, Store}, subcall, }; @@ -22,7 +23,7 @@ const CONTEXT_KEY_CONFIDENTIAL_STORE_INSTANCE_COUNT: &str = "contracts.Confident /// manager are available. In others, an error will be returned describing the /// particular key manager failure. pub fn with_instance_store( - ctx: &mut C, + ctx: &C, instance_info: &types::Instance, store_kind: StoreKind, f: F, @@ -41,12 +42,15 @@ where 0 }; let instance_count: Option = if let StoreKind::Confidential = store_kind { - let cnt = *ctx - .value(CONTEXT_KEY_CONFIDENTIAL_STORE_INSTANCE_COUNT) - .or_default(); - ctx.value(CONTEXT_KEY_CONFIDENTIAL_STORE_INSTANCE_COUNT) - .set(cnt + 1); - Some(cnt) + CurrentState::with(|state| { + let cnt = *state + .block_value(CONTEXT_KEY_CONFIDENTIAL_STORE_INSTANCE_COUNT) + .or_default(); + state + .block_value(CONTEXT_KEY_CONFIDENTIAL_STORE_INSTANCE_COUNT) + .set(cnt + 1); + Some(cnt) + }) } else { None }; @@ -97,7 +101,7 @@ pub fn with_instance_raw_store( where F: FnOnce(&mut dyn Store) -> R, { - CurrentStore::with(|store| { + CurrentState::with_store(|store| { let store = storage::PrefixStore::new(store, &MODULE_NAME); let instance_prefix = instance_info.id.to_storage_key(); let contract_state = storage::PrefixStore::new( diff --git a/runtime-sdk/modules/contracts/src/test.rs b/runtime-sdk/modules/contracts/src/test.rs index 472b044ff1..778a7b93cd 100644 --- a/runtime-sdk/modules/contracts/src/test.rs +++ b/runtime-sdk/modules/contracts/src/test.rs @@ -2,7 +2,6 @@ use std::{collections::BTreeMap, io::Write}; use oasis_runtime_sdk::{ - context, error::Error, event::IntoTags, module, @@ -10,12 +9,13 @@ use oasis_runtime_sdk::{ accounts::{self, Module as Accounts, API as _}, core::{self, Module as Core}, }, + state::{self, CurrentState, Options, TransactionResult}, testing::{keys, mock}, types::{ token::{BaseUnits, Denomination}, transaction, }, - BatchContext, Context, Runtime, Version, + Context, Runtime, Version, }; use crate::{types, types::StoreKind, Config, Genesis}; @@ -33,7 +33,7 @@ impl Config for ContractsConfig { type Contracts = crate::Module; -fn upload_hello_contract(ctx: &mut C) -> types::CodeId { +fn upload_hello_contract(ctx: &C) -> types::CodeId { // Compress contract code. let mut code = Vec::with_capacity(HELLO_CONTRACT_CODE.len() << 3); let mut encoder = snap::write::FrameEncoder::new(&mut code); @@ -65,21 +65,17 @@ fn upload_hello_contract(ctx: &mut C) -> types::CodeId { ..Default::default() }, }; - ctx.with_tx(tx.into(), |mut tx_ctx, call| { - let code_id = Contracts::tx_upload(&mut tx_ctx, cbor::from_value(call.body).unwrap()) + let call = tx.call.clone(); + CurrentState::with_transaction_opts(Options::new().with_tx(tx.into()), || { + let code_id = Contracts::tx_upload(ctx, cbor::from_value(call.body).unwrap()) .expect("upload should succeed") .id; - tx_ctx.commit(); - - code_id + TransactionResult::Commit(code_id) }) } -fn deploy_hello_contract( - ctx: &mut C, - tokens: Vec, -) -> types::InstanceId { +fn deploy_hello_contract(ctx: &C, tokens: Vec) -> types::InstanceId { // Upload the contract. upload_hello_contract(ctx); @@ -115,22 +111,20 @@ fn deploy_hello_contract( ..Default::default() }, }; - ctx.with_tx(tx.into(), |mut tx_ctx, call| { - let instance_id = - Contracts::tx_instantiate(&mut tx_ctx, cbor::from_value(call.body).unwrap()) - .expect("instantiate should succeed") - .id; - - tx_ctx.commit(); + let call = tx.call.clone(); + CurrentState::with_transaction_opts(Options::new().with_tx(tx.into()), || { + let instance_id = Contracts::tx_instantiate(ctx, cbor::from_value(call.body).unwrap()) + .expect("instantiate should succeed") + .id; - instance_id + TransactionResult::Commit(instance_id) }) } #[test] fn test_hello_contract_call() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx_for_runtime::(context::Mode::ExecuteTx, true); + let ctx = mock.create_ctx_for_runtime::(true); Core::::init(core::Genesis { parameters: core::Parameters { @@ -163,7 +157,7 @@ fn test_hello_contract_call() { }); let instance_id = - deploy_hello_contract(&mut ctx, vec![BaseUnits::new(1_000, Denomination::NATIVE)]); + deploy_hello_contract(&ctx, vec![BaseUnits::new(1_000, Denomination::NATIVE)]); // Check caller account balances. let bals = Accounts::get_balances(keys::alice::address()).expect("get_balances should succeed"); @@ -223,8 +217,9 @@ fn test_hello_contract_call() { ..Default::default() }, }; - ctx.with_tx(tx.into(), |mut tx_ctx, call| { - let result = Contracts::tx_call(&mut tx_ctx, cbor::from_value(call.body).unwrap()) + let call = tx.call.clone(); + CurrentState::with_transaction_opts(Options::new().with_tx(tx.into()), || { + let result = Contracts::tx_call(&ctx, cbor::from_value(call.body).unwrap()) .expect("call should succeed"); let result: cbor::Value = @@ -266,13 +261,10 @@ fn test_hello_contract_call() { "there should only be one denomination" ); - let state = tx_ctx.commit(); - let tags = state.events.into_tags(); + let tags = CurrentState::with(|state| state.take_events().into_tags()); + let messages = CurrentState::with(|state| state.take_messages()); // Make sure no runtime messages got emitted. - assert!( - state.messages.is_empty(), - "no runtime messages should be emitted" - ); + assert!(messages.is_empty(), "no runtime messages should be emitted"); // Make sure a contract event was emitted and is properly formatted. assert_eq!(tags.len(), 2, "two events should have been emitted"); assert_eq!(tags[0].key, b"accounts\x00\x00\x00\x01"); // accounts.Transfer (code = 1) event @@ -320,8 +312,9 @@ fn test_hello_contract_call() { ..Default::default() }, }; - ctx.with_tx(tx.into(), |mut tx_ctx, call| { - let result = Contracts::tx_call(&mut tx_ctx, cbor::from_value(call.body).unwrap()) + let call = tx.call.clone(); + CurrentState::with_transaction_opts(Options::new().with_tx(tx.into()), || { + let result = Contracts::tx_call(&ctx, cbor::from_value(call.body).unwrap()) .expect("call should succeed"); let result: cbor::Value = @@ -362,38 +355,36 @@ fn test_hello_contract_call() { 1, "there should only be one denomination" ); - - tx_ctx.commit(); }); // Test instance query. - let result = Contracts::query_instance(&mut ctx, types::InstanceQuery { id: instance_id }) + let result = Contracts::query_instance(&ctx, types::InstanceQuery { id: instance_id }) .expect("instance query should succeed"); assert_eq!(result.id, instance_id); assert_eq!(result.code_id, 0.into()); assert_eq!(result.creator, keys::alice::address()); // Test code query. - let result = Contracts::query_code(&mut ctx, types::CodeQuery { id: result.code_id }) + let result = Contracts::query_code(&ctx, types::CodeQuery { id: result.code_id }) .expect("code query should succeed"); assert_eq!(result.id, 0.into()); assert_eq!(result.abi, types::ABI::OasisV1); // Test code storage query. - let result = Contracts::query_code_storage(&mut ctx, types::CodeStorageQuery { id: 0.into() }) + let result = Contracts::query_code_storage(&ctx, types::CodeStorageQuery { id: 0.into() }) .expect("code storage query should succeed"); // Stored code is the original code plus some injected gas billing calls. assert!(result.code.len() >= HELLO_CONTRACT_CODE.len()); // Invalid code queries should fail. - Contracts::query_code(&mut ctx, types::CodeQuery { id: 9999.into() }) + Contracts::query_code(&ctx, types::CodeQuery { id: 9999.into() }) .expect_err("invalid code query should fail"); - Contracts::query_code_storage(&mut ctx, types::CodeStorageQuery { id: 9999.into() }) + Contracts::query_code_storage(&ctx, types::CodeStorageQuery { id: 9999.into() }) .expect_err("invalid code storage query should fail"); // Test storage query for the counter key. let result = Contracts::query_instance_storage( - &mut ctx, + &ctx, types::InstanceStorageQuery { id: instance_id, key: b"counter".to_vec(), @@ -407,7 +398,7 @@ fn test_hello_contract_call() { // Test raw public storage query. let result = Contracts::query_instance_raw_storage( - &mut ctx, + &ctx, types::InstanceRawStorageQuery { id: instance_id, store_kind: StoreKind::Public, @@ -473,14 +464,14 @@ fn test_hello_contract_call() { ..Default::default() }, }; - ctx.with_tx(tx.into(), |mut tx_ctx, call| { - let _result = Contracts::tx_call(&mut tx_ctx, cbor::from_value(call.body).unwrap()) + let call = tx.call.clone(); + CurrentState::with_transaction_opts(Options::new().with_tx(tx.into()), || { + Contracts::tx_call(&ctx, cbor::from_value(call.body).unwrap()) .expect("call should succeed"); - tx_ctx.commit(); }); } let result = Contracts::query_instance_raw_storage( - &mut ctx, + &ctx, types::InstanceRawStorageQuery { id: instance_id, store_kind: StoreKind::Public, @@ -512,7 +503,7 @@ fn test_hello_contract_call() { ); } let result = Contracts::query_instance_raw_storage( - &mut ctx, + &ctx, types::InstanceRawStorageQuery { id: instance_id, store_kind: StoreKind::Confidential, @@ -560,14 +551,14 @@ fn test_hello_contract_call() { ..Default::default() }, }; - ctx.with_tx(tx.into(), |mut tx_ctx, call| { - let _result = Contracts::tx_call(&mut tx_ctx, cbor::from_value(call.body).unwrap()) + let call = tx.call.clone(); + CurrentState::with_transaction_opts(Options::new().with_tx(tx.into()), || { + Contracts::tx_call(&ctx, cbor::from_value(call.body).unwrap()) .expect("call should succeed"); - tx_ctx.commit(); }); } let result = Contracts::query_instance_raw_storage( - &mut ctx, + &ctx, types::InstanceRawStorageQuery { id: instance_id, store_kind: StoreKind::Public, @@ -582,7 +573,7 @@ fn test_hello_contract_call() { "raw storage query should be limited by default limit 100" ); let result = Contracts::query_instance_raw_storage( - &mut ctx, + &ctx, types::InstanceRawStorageQuery { id: instance_id, store_kind: StoreKind::Public, @@ -597,7 +588,7 @@ fn test_hello_contract_call() { "raw storage query should be limited by default limit 100, even if requested limit is higher" ); let result = Contracts::query_instance_raw_storage( - &mut ctx, + &ctx, types::InstanceRawStorageQuery { id: instance_id, store_kind: StoreKind::Public, @@ -612,7 +603,7 @@ fn test_hello_contract_call() { "raw storage should contain 10 elements" ); let result = Contracts::query_instance_raw_storage( - &mut ctx, + &ctx, types::InstanceRawStorageQuery { id: instance_id, store_kind: StoreKind::Public, @@ -658,17 +649,22 @@ fn test_hello_contract_call() { ..Default::default() }, }; - ctx.with_tx(invalid_tx.clone().into(), |mut tx_ctx, call| { - Contracts::tx_call(&mut tx_ctx, cbor::from_value(call.body).unwrap()) + let call = invalid_tx.call.clone(); + CurrentState::with_transaction_opts(Options::new().with_tx(invalid_tx.clone().into()), || { + Contracts::tx_call(&ctx, cbor::from_value(call.body).unwrap()) .expect_err("invalid call should fail"); }); - ctx.with_child(context::Mode::CheckTx, |mut check_ctx| { - check_ctx.with_tx(invalid_tx.into(), |mut tx_ctx, call| { - Contracts::tx_call(&mut tx_ctx, cbor::from_value(call.body).unwrap()) + let call = invalid_tx.call.clone(); + CurrentState::with_transaction_opts( + Options::new() + .with_mode(state::Mode::Check) + .with_tx(invalid_tx.clone().into()), + || { + Contracts::tx_call(&ctx, cbor::from_value(call.body).unwrap()) .expect("invalid call should succeed check-tx"); - }); - }) + }, + ); } struct CoreConfig; @@ -724,11 +720,11 @@ fn test_hello_contract_subcalls_overflow() { use cbor::cbor_map; let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx_for_runtime::(context::Mode::ExecuteTx, true); + let ctx = mock.create_ctx_for_runtime::(true); - ContractRuntime::migrate(&mut ctx); + ContractRuntime::migrate(&ctx); - let instance_id = deploy_hello_contract(&mut ctx, vec![]); + let instance_id = deploy_hello_contract(&ctx, vec![]); // And finally call a method. let tx = transaction::Transaction { @@ -760,8 +756,9 @@ fn test_hello_contract_subcalls_overflow() { ..Default::default() }, }; - ctx.with_tx(tx.into(), |mut tx_ctx, call| { - let result = Contracts::tx_call(&mut tx_ctx, cbor::from_value(call.body).unwrap()) + let call = tx.call.clone(); + CurrentState::with_transaction_opts(Options::new().with_tx(tx.into()), || { + let result = Contracts::tx_call(&ctx, cbor::from_value(call.body).unwrap()) .expect_err("call should fail"); assert_eq!(result.module_name(), "contracts.0"); @@ -778,11 +775,11 @@ fn test_hello_contract_subcalls() { use cbor::cbor_map; let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx_for_runtime::(context::Mode::ExecuteTx, true); + let ctx = mock.create_ctx_for_runtime::(true); - ContractRuntime::migrate(&mut ctx); + ContractRuntime::migrate(&ctx); - let instance_id = deploy_hello_contract(&mut ctx, vec![]); + let instance_id = deploy_hello_contract(&ctx, vec![]); // And finally call a method. let tx = transaction::Transaction { @@ -814,8 +811,9 @@ fn test_hello_contract_subcalls() { ..Default::default() }, }; - ctx.with_tx(tx.clone().into(), |mut tx_ctx, call| { - let result = Contracts::tx_call(&mut tx_ctx, cbor::from_value(call.body).unwrap()) + let call = tx.call.clone(); + CurrentState::with_transaction_opts(Options::new().with_tx(tx.clone().into()), || { + let result = Contracts::tx_call(&ctx, cbor::from_value(call.body).unwrap()) .expect("call should succeed"); let result: cbor::Value = @@ -831,13 +829,12 @@ fn test_hello_contract_subcalls() { }); // Gas estimation should work. - let mut ctx = mock.create_ctx_for_runtime::(context::Mode::CheckTx, true); let args = core::types::EstimateGasQuery { caller: None, tx, propagate_failures: true, }; - ::Core::query_estimate_gas(&mut ctx, args) + ::Core::query_estimate_gas(&ctx, args) .expect("query_estimate_gas should succeed"); } @@ -850,11 +847,11 @@ fn test_hello_contract_query() { mock.runtime_header.timestamp = 1629117379; mock.epoch = 42; - let mut ctx = mock.create_ctx_for_runtime::(context::Mode::ExecuteTx, true); + let ctx = mock.create_ctx_for_runtime::(true); - ContractRuntime::migrate(&mut ctx); + ContractRuntime::migrate(&ctx); - let instance_id = deploy_hello_contract(&mut ctx, vec![]); + let instance_id = deploy_hello_contract(&ctx, vec![]); // Call the query_block_info method. let tx = transaction::Transaction { @@ -882,8 +879,9 @@ fn test_hello_contract_query() { ..Default::default() }, }; - ctx.with_tx(tx.into(), |mut tx_ctx, call| { - let result = Contracts::tx_call(&mut tx_ctx, cbor::from_value(call.body).unwrap()) + let call = tx.call.clone(); + CurrentState::with_transaction_opts(Options::new().with_tx(tx.into()), || { + let result = Contracts::tx_call(&ctx, cbor::from_value(call.body).unwrap()) .expect("call should succeed"); let result: cbor::Value = @@ -924,8 +922,9 @@ fn test_hello_contract_query() { ..Default::default() }, }; - ctx.with_tx(tx.into(), |mut tx_ctx, call| { - let result = Contracts::tx_call(&mut tx_ctx, cbor::from_value(call.body).unwrap()) + let call = tx.call.clone(); + CurrentState::with_transaction_opts(Options::new().with_tx(tx.into()), || { + let result = Contracts::tx_call(&ctx, cbor::from_value(call.body).unwrap()) .expect("call should succeed"); let result: cbor::Value = @@ -966,8 +965,9 @@ fn test_hello_contract_query() { ..Default::default() }, }; - ctx.with_tx(tx.into(), |mut tx_ctx, call| { - let result = Contracts::tx_call(&mut tx_ctx, cbor::from_value(call.body).unwrap()) + let call = tx.call.clone(); + CurrentState::with_transaction_opts(Options::new().with_tx(tx.into()), || { + let result = Contracts::tx_call(&ctx, cbor::from_value(call.body).unwrap()) .expect("call should succeed"); let result: cbor::Value = @@ -986,12 +986,12 @@ fn test_hello_contract_query() { #[test] fn test_hello_contract_upgrade() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx_for_runtime::(context::Mode::ExecuteTx, true); + let ctx = mock.create_ctx_for_runtime::(true); - ContractRuntime::migrate(&mut ctx); + ContractRuntime::migrate(&ctx); - let instance_id = deploy_hello_contract(&mut ctx, vec![]); - let code_2 = upload_hello_contract(&mut ctx); + let instance_id = deploy_hello_contract(&ctx, vec![]); + let code_2 = upload_hello_contract(&ctx); // Call the upgrade method. let tx = transaction::Transaction { @@ -1020,23 +1020,22 @@ fn test_hello_contract_upgrade() { ..Default::default() }, }; - ctx.with_tx(tx.into(), |mut tx_ctx, call| { - Contracts::tx_upgrade(&mut tx_ctx, cbor::from_value(call.body).unwrap()) + let call = tx.call.clone(); + CurrentState::with_transaction_opts(Options::new().with_tx(tx.into()), || { + Contracts::tx_upgrade(&ctx, cbor::from_value(call.body).unwrap()) .expect("upgrade should succeed"); - - tx_ctx.commit(); }); } #[test] fn test_hello_contract_upgrade_fail_policy() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx_for_runtime::(context::Mode::ExecuteTx, true); + let ctx = mock.create_ctx_for_runtime::(true); - ContractRuntime::migrate(&mut ctx); + ContractRuntime::migrate(&ctx); - let instance_id = deploy_hello_contract(&mut ctx, vec![]); - let code_2 = upload_hello_contract(&mut ctx); + let instance_id = deploy_hello_contract(&ctx, vec![]); + let code_2 = upload_hello_contract(&ctx); // Make Bob call the upgrade method which should fail as he is not authorized. let tx = transaction::Transaction { @@ -1065,8 +1064,9 @@ fn test_hello_contract_upgrade_fail_policy() { ..Default::default() }, }; - ctx.with_tx(tx.into(), |mut tx_ctx, call| { - let result = Contracts::tx_upgrade(&mut tx_ctx, cbor::from_value(call.body).unwrap()) + let call = tx.call.clone(); + CurrentState::with_transaction_opts(Options::new().with_tx(tx.into()), || { + let result = Contracts::tx_upgrade(&ctx, cbor::from_value(call.body).unwrap()) .expect_err("upgrade should fail"); assert_eq!(result.module_name(), "contracts"); @@ -1078,12 +1078,12 @@ fn test_hello_contract_upgrade_fail_policy() { #[test] fn test_hello_contract_upgrade_fail_pre() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx_for_runtime::(context::Mode::ExecuteTx, true); + let ctx = mock.create_ctx_for_runtime::(true); - ContractRuntime::migrate(&mut ctx); + ContractRuntime::migrate(&ctx); - let instance_id = deploy_hello_contract(&mut ctx, vec![]); - let code_2 = upload_hello_contract(&mut ctx); + let instance_id = deploy_hello_contract(&ctx, vec![]); + let code_2 = upload_hello_contract(&ctx); // Call the upgrade handler with a request that should cause a failure in pre-upgrade. let tx = transaction::Transaction { @@ -1112,8 +1112,9 @@ fn test_hello_contract_upgrade_fail_pre() { ..Default::default() }, }; - ctx.with_tx(tx.into(), |mut tx_ctx, call| { - let result = Contracts::tx_upgrade(&mut tx_ctx, cbor::from_value(call.body).unwrap()) + let call = tx.call.clone(); + CurrentState::with_transaction_opts(Options::new().with_tx(tx.into()), || { + let result = Contracts::tx_upgrade(&ctx, cbor::from_value(call.body).unwrap()) .expect_err("upgrade should fail"); assert_eq!(result.module_name(), "contracts.0"); @@ -1128,12 +1129,12 @@ fn test_hello_contract_upgrade_fail_pre() { #[test] fn test_hello_contract_upgrade_fail_post() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx_for_runtime::(context::Mode::ExecuteTx, true); + let ctx = mock.create_ctx_for_runtime::(true); - ContractRuntime::migrate(&mut ctx); + ContractRuntime::migrate(&ctx); - let instance_id = deploy_hello_contract(&mut ctx, vec![]); - let code_2 = upload_hello_contract(&mut ctx); + let instance_id = deploy_hello_contract(&ctx, vec![]); + let code_2 = upload_hello_contract(&ctx); // Call the upgrade handler with a request that should cause a failure in post-upgrade. let tx = transaction::Transaction { @@ -1162,8 +1163,9 @@ fn test_hello_contract_upgrade_fail_post() { ..Default::default() }, }; - ctx.with_tx(tx.into(), |mut tx_ctx, call| { - let result = Contracts::tx_upgrade(&mut tx_ctx, cbor::from_value(call.body).unwrap()) + let call = tx.call.clone(); + CurrentState::with_transaction_opts(Options::new().with_tx(tx.into()), || { + let result = Contracts::tx_upgrade(&ctx, cbor::from_value(call.body).unwrap()) .expect_err("upgrade should fail"); assert_eq!(result.module_name(), "contracts.1"); // Note the new code id. @@ -1178,11 +1180,11 @@ fn test_hello_contract_upgrade_fail_post() { #[test] fn test_hello_contract_change_upgrade_policy() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx_for_runtime::(context::Mode::ExecuteTx, true); + let ctx = mock.create_ctx_for_runtime::(true); - ContractRuntime::migrate(&mut ctx); + ContractRuntime::migrate(&ctx); - let instance_id = deploy_hello_contract(&mut ctx, vec![]); + let instance_id = deploy_hello_contract(&ctx, vec![]); // Call the upgrade method. let tx = transaction::Transaction { @@ -1209,22 +1211,21 @@ fn test_hello_contract_change_upgrade_policy() { ..Default::default() }, }; - ctx.with_tx(tx.into(), |mut tx_ctx, call| { - Contracts::tx_change_upgrade_policy(&mut tx_ctx, cbor::from_value(call.body).unwrap()) + let call = tx.call.clone(); + CurrentState::with_transaction_opts(Options::new().with_tx(tx.into()), || { + Contracts::tx_change_upgrade_policy(&ctx, cbor::from_value(call.body).unwrap()) .expect("upgrade should succeed"); - - tx_ctx.commit(); }); } #[test] fn test_hello_contract_change_upgrade_policy_fail() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx_for_runtime::(context::Mode::ExecuteTx, true); + let ctx = mock.create_ctx_for_runtime::(true); - ContractRuntime::migrate(&mut ctx); + ContractRuntime::migrate(&ctx); - let instance_id = deploy_hello_contract(&mut ctx, vec![]); + let instance_id = deploy_hello_contract(&ctx, vec![]); // Make Bob call the change upgrade policy method which should fail as he is not authorized. let tx = transaction::Transaction { @@ -1251,9 +1252,10 @@ fn test_hello_contract_change_upgrade_policy_fail() { ..Default::default() }, }; - ctx.with_tx(tx.into(), |mut tx_ctx, call| { + let call = tx.call.clone(); + CurrentState::with_transaction_opts(Options::new().with_tx(tx.into()), || { let result = - Contracts::tx_change_upgrade_policy(&mut tx_ctx, cbor::from_value(call.body).unwrap()) + Contracts::tx_change_upgrade_policy(&ctx, cbor::from_value(call.body).unwrap()) .expect_err("change upgrade policy should fail"); assert_eq!(result.module_name(), "contracts"); diff --git a/runtime-sdk/modules/contracts/src/types.rs b/runtime-sdk/modules/contracts/src/types.rs index a5be637391..b5fa464cf4 100644 --- a/runtime-sdk/modules/contracts/src/types.rs +++ b/runtime-sdk/modules/contracts/src/types.rs @@ -1,7 +1,6 @@ //! Contracts module types. pub use oasis_contract_sdk_types::{CodeId, InstanceId}; use oasis_runtime_sdk::{ - context::TxContext, core::common::crypto::hash::Hash, types::{address::Address, token}, }; @@ -22,14 +21,13 @@ pub enum Policy { } impl Policy { - /// Enforce the given policy by returning an error if the policy is not satisfied by the passed - /// transaction context. - pub fn enforce(&self, ctx: &C) -> Result<(), Error> { + /// Enforce the given policy by returning an error if the policy is not satisfied. + pub fn enforce(&self, caller: &Address) -> Result<(), Error> { match self { // Nobody is allowed to perform the action. Policy::Nobody => Err(Error::Forbidden), // Only the given caller is allowed to perform the action. - Policy::Address(address) if address == &ctx.tx_caller_address() => Ok(()), + Policy::Address(address) if address == caller => Ok(()), Policy::Address(_) => Err(Error::Forbidden), // Anyone is allowed to perform the action. Policy::Everyone => Ok(()), diff --git a/runtime-sdk/modules/evm/src/backend.rs b/runtime-sdk/modules/evm/src/backend.rs index 2450ba1210..9f02636f7f 100644 --- a/runtime-sdk/modules/evm/src/backend.rs +++ b/runtime-sdk/modules/evm/src/backend.rs @@ -1,5 +1,4 @@ use std::{ - cell::RefCell, collections::{btree_map::Entry, BTreeMap, BTreeSet}, marker::PhantomData, mem, @@ -13,13 +12,13 @@ use evm::{ use primitive_types::{H160, H256, U256}; use oasis_runtime_sdk::{ - context::{self, TxContext}, + context::Context, core::common::crypto::hash::Hash, modules::{ accounts::API as _, core::{self, API as _}, }, - storage::CurrentStore, + state::CurrentState, subcall, types::token, Runtime, @@ -44,19 +43,17 @@ pub struct Vicinity { } /// Backend for the evm crate that enables the use of our storage. -pub struct OasisBackend<'ctx, C: TxContext, Cfg: Config> { +pub struct OasisBackend<'ctx, C: Context, Cfg: Config> { vicinity: Vicinity, - ctx: RefCell<&'ctx mut C>, - pending_state: RefCell, + ctx: &'ctx C, _cfg: PhantomData, } -impl<'ctx, C: TxContext, Cfg: Config> OasisBackend<'ctx, C, Cfg> { - pub fn new(ctx: &'ctx mut C, vicinity: Vicinity) -> Self { +impl<'ctx, C: Context, Cfg: Config> OasisBackend<'ctx, C, Cfg> { + pub fn new(ctx: &'ctx C, vicinity: Vicinity) -> Self { Self { vicinity, - ctx: RefCell::new(ctx), - pending_state: RefCell::new(context::State::default()), + ctx, _cfg: PhantomData, } } @@ -69,9 +66,6 @@ pub(crate) trait EVMBackendExt { fn random_bytes(&self, num_bytes: u64, pers: &[u8]) -> Vec; /// Perform a subcall. - /// - /// Note that `state` from the resulting `SubcallResult` is replaced with a default value as - /// it needs to be stored in substate. fn subcall( &self, info: subcall::SubcallInfo, @@ -93,16 +87,22 @@ impl EVMBackendExt for &T { } } -impl<'ctx, C: TxContext, Cfg: Config> EVMBackendExt for OasisBackend<'ctx, C, Cfg> { +impl<'ctx, C: Context, Cfg: Config> EVMBackendExt for OasisBackend<'ctx, C, Cfg> { fn random_bytes(&self, num_bytes: u64, pers: &[u8]) -> Vec { // Refuse to generate more than 1 KiB in one go. // EVM memory gas is checked only before and after calls, so we won't // see the quadratic memory cost until after this call uses its time. let num_bytes = num_bytes.min(RNG_MAX_BYTES) as usize; - let mut ctx = self.ctx.borrow_mut(); - let mut rng = ctx.rng(pers).expect("unable to access RNG"); let mut rand_bytes = vec![0u8; num_bytes]; - rand_core::RngCore::try_fill_bytes(&mut rng, &mut rand_bytes).expect("RNG is inoperable"); + CurrentState::with(|state| { + let mut rng = state + .rng() + .fork(self.ctx, pers) + .expect("unable to access RNG"); + rand_core::RngCore::try_fill_bytes(&mut rng, &mut rand_bytes) + .expect("RNG is inoperable"); + }); + rand_bytes } @@ -111,23 +111,14 @@ impl<'ctx, C: TxContext, Cfg: Config> EVMBackendExt for OasisBackend<'ctx, C, Cf info: subcall::SubcallInfo, validator: V, ) -> Result { - let mut ctx = self.ctx.borrow_mut(); - - // Execute the subcall. - let mut result = subcall::call(&mut ctx, info, validator)?; - // Store state after subcall execution for substate handling. - self.pending_state - .borrow_mut() - .merge_from(mem::take(&mut result.state)); - - Ok(result) + subcall::call(self.ctx, info, validator) } } /// Oasis-specific substate implementation for the EVM stack executor. /// /// The substate is used to track nested transactional state that can be either be committed or -/// reverted. This is similar to `Context` in the SDK. +/// reverted. This is similar to `State` in the SDK. /// /// See the `evm` crate for details. struct OasisStackSubstate<'config> { @@ -135,7 +126,6 @@ struct OasisStackSubstate<'config> { parent: Option>>, logs: Vec, deletes: BTreeSet, - state: context::State, origin_nonce_incremented: bool, } @@ -146,7 +136,6 @@ impl<'config> OasisStackSubstate<'config> { parent: None, logs: Vec::new(), deletes: BTreeSet::new(), - state: context::State::default(), origin_nonce_incremented: false, } } @@ -165,7 +154,6 @@ impl<'config> OasisStackSubstate<'config> { parent: None, logs: Vec::new(), deletes: BTreeSet::new(), - state: context::State::default(), origin_nonce_incremented: false, }; mem::swap(&mut entering, self); @@ -180,7 +168,6 @@ impl<'config> OasisStackSubstate<'config> { self.metadata.swallow_commit(exited.metadata)?; self.logs.append(&mut exited.logs); self.deletes.append(&mut exited.deletes); - self.state.merge_from(exited.state); self.origin_nonce_incremented |= exited.origin_nonce_incremented; Ok(()) @@ -243,13 +230,13 @@ impl<'config> OasisStackSubstate<'config> { /// and exposes it through accessors to the EVM stack executor. /// /// See the `evm` crate for details. -pub struct OasisStackState<'ctx, 'backend, 'config, C: TxContext, Cfg: Config> { +pub struct OasisStackState<'ctx, 'backend, 'config, C: Context, Cfg: Config> { backend: &'backend OasisBackend<'ctx, C, Cfg>, substate: OasisStackSubstate<'config>, original_storage: BTreeMap<(types::H160, types::H256), types::H256>, } -impl<'ctx, 'backend, 'config, C: TxContext, Cfg: Config> +impl<'ctx, 'backend, 'config, C: Context, Cfg: Config> OasisStackState<'ctx, 'backend, 'config, C, Cfg> { /// Create a new Oasis-specific state for the EVM stack executor. @@ -267,7 +254,7 @@ impl<'ctx, 'backend, 'config, C: TxContext, Cfg: Config> /// Applies any final state by emitting SDK events/messages. /// /// Note that storage has already been committed to the top-level current store. - pub fn apply(mut self) -> Result<(), crate::Error> { + pub fn apply(self) -> Result<(), crate::Error> { // Abort if SELFDESTRUCT was used. if !self.substate.deletes.is_empty() { return Err(crate::Error::ExecutionFailed( @@ -275,35 +262,22 @@ impl<'ctx, 'backend, 'config, C: TxContext, Cfg: Config> )); } - // Merge from top-level pending state. - self.substate - .state - .merge_from(mem::take(&mut self.backend.pending_state.borrow_mut())); - - let mut ctx = self.backend.ctx.borrow_mut(); - - // Forward SDK events. - ctx.emit_etags(self.substate.state.events); - - // Forward any emitted runtime messages. - for (msg, hook) in self.substate.state.messages { - ctx.emit_message(msg, hook)?; - } - // Emit logs as events. - for log in self.substate.logs { - ctx.emit_event(crate::Event::Log { - address: log.address.into(), - topics: log.topics.iter().map(|&topic| topic.into()).collect(), - data: log.data, - }); - } + CurrentState::with(|state| { + for log in self.substate.logs { + state.emit_event(crate::Event::Log { + address: log.address.into(), + topics: log.topics.iter().map(|&topic| topic.into()).collect(), + data: log.data, + }); + } + }); Ok(()) } } -impl<'ctx, 'backend, 'config, C: TxContext, Cfg: Config> Backend +impl<'ctx, 'backend, 'config, C: Context, Cfg: Config> Backend for OasisStackState<'ctx, 'backend, 'config, C, Cfg> { fn gas_price(&self) -> U256 { @@ -315,7 +289,7 @@ impl<'ctx, 'backend, 'config, C: TxContext, Cfg: Config> Backend } fn block_hash(&self, number: U256) -> H256 { - CurrentStore::with(|store| { + CurrentState::with_store(|store| { let block_hashes = state::block_hashes(store); if let Some(hash) = block_hashes.get::<_, Hash>(&number.low_u64().to_be_bytes()) { @@ -327,7 +301,7 @@ impl<'ctx, 'backend, 'config, C: TxContext, Cfg: Config> Backend } fn block_number(&self) -> U256 { - self.backend.ctx.borrow().runtime_header().round.into() + self.backend.ctx.runtime_header().round.into() } fn block_coinbase(&self) -> H160 { @@ -336,7 +310,7 @@ impl<'ctx, 'backend, 'config, C: TxContext, Cfg: Config> Backend } fn block_timestamp(&self) -> U256 { - self.backend.ctx.borrow().runtime_header().timestamp.into() + self.backend.ctx.runtime_header().timestamp.into() } fn block_difficulty(&self) -> U256 { @@ -349,16 +323,13 @@ impl<'ctx, 'backend, 'config, C: TxContext, Cfg: Config> Backend } fn block_gas_limit(&self) -> U256 { - ::Core::max_batch_gas(&mut self.backend.ctx.borrow_mut()).into() + ::Core::max_batch_gas().into() } fn block_base_fee_per_gas(&self) -> U256 { - ::Core::min_gas_price( - &self.backend.ctx.borrow_mut(), - &Cfg::TOKEN_DENOMINATION, - ) - .unwrap_or_default() - .into() + ::Core::min_gas_price(self.backend.ctx, &Cfg::TOKEN_DENOMINATION) + .unwrap_or_default() + .into() } fn chain_id(&self) -> U256 { @@ -381,10 +352,9 @@ impl<'ctx, 'backend, 'config, C: TxContext, Cfg: Config> Backend // If this is the caller's address, the caller nonce has not yet been incremented based on // the EVM semantics and this is not a simulation context, return the nonce decremented by // one to cancel out the SDK nonce changes. - let ctx = self.backend.ctx.borrow_mut(); if address == self.origin() && !self.substate.origin_nonce_incremented - && !ctx.is_simulation() + && !CurrentState::with_env(|env| env.is_simulation()) { nonce = nonce.saturating_sub(1); } @@ -396,7 +366,7 @@ impl<'ctx, 'backend, 'config, C: TxContext, Cfg: Config> Backend } fn code(&self, address: H160) -> Vec { - CurrentStore::with(|store| { + CurrentState::with_store(|store| { let store = state::codes(store); store.get(address).unwrap_or_default() }) @@ -406,10 +376,10 @@ impl<'ctx, 'backend, 'config, C: TxContext, Cfg: Config> Backend let address: types::H160 = address.into(); let key: types::H256 = key.into(); - let mut ctx = self.backend.ctx.borrow_mut(); - let res: types::H256 = state::with_storage::(*ctx, &address, |store| { - store.get(key).unwrap_or_default() - }); + let res: types::H256 = + state::with_storage::(self.backend.ctx, &address, |store| { + store.get(key).unwrap_or_default() + }); res.into() } @@ -424,7 +394,7 @@ impl<'ctx, 'backend, 'config, C: TxContext, Cfg: Config> Backend } } -impl<'ctx, 'backend, 'config, C: TxContext, Cfg: Config> StackState<'config> +impl<'ctx, 'backend, 'config, C: Context, Cfg: Config> StackState<'config> for OasisStackState<'ctx, 'backend, 'config, C, Cfg> { fn metadata(&self) -> &StackSubstateMetadata<'config> { @@ -436,16 +406,15 @@ impl<'ctx, 'backend, 'config, C: TxContext, Cfg: Config> StackState<'config> } fn enter(&mut self, gas_limit: u64, is_static: bool) { - self.substate.state = mem::take(&mut self.backend.pending_state.borrow_mut()); self.substate.enter(gas_limit, is_static); - CurrentStore::start_transaction(); + CurrentState::start_transaction(); } fn exit_commit(&mut self) -> Result<(), ExitError> { self.substate.exit_commit()?; - CurrentStore::commit_transaction(); + CurrentState::commit_transaction(); Ok(()) } @@ -453,7 +422,7 @@ impl<'ctx, 'backend, 'config, C: TxContext, Cfg: Config> StackState<'config> fn exit_revert(&mut self) -> Result<(), ExitError> { self.substate.exit_revert()?; - CurrentStore::rollback_transaction(); + CurrentState::rollback_transaction(); Ok(()) } @@ -461,7 +430,7 @@ impl<'ctx, 'backend, 'config, C: TxContext, Cfg: Config> StackState<'config> fn exit_discard(&mut self) -> Result<(), ExitError> { self.substate.exit_discard()?; - CurrentStore::rollback_transaction(); + CurrentState::rollback_transaction(); Ok(()) } @@ -487,12 +456,10 @@ impl<'ctx, 'backend, 'config, C: TxContext, Cfg: Config> StackState<'config> } fn inc_nonce(&mut self, address: H160) -> Result<(), ExitError> { - let ctx = self.backend.ctx.borrow_mut(); - // Do not increment the origin nonce as that has already been handled by the SDK. But do // record that the nonce should be incremented based on EVM semantics so we can adjust any // results from the `basic` method. - if address == self.origin() && !ctx.is_simulation() { + if address == self.origin() && !CurrentState::with_env(|env| env.is_simulation()) { self.substate.origin_nonce_incremented = true; return Ok(()); } @@ -503,17 +470,16 @@ impl<'ctx, 'backend, 'config, C: TxContext, Cfg: Config> StackState<'config> } fn set_storage(&mut self, address: H160, key: H256, value: H256) { - let mut ctx = self.backend.ctx.borrow_mut(); - let address: types::H160 = address.into(); let key: types::H256 = key.into(); let value: types::H256 = value.into(); // We cache the current value if this is the first time we modify it in the transaction. if let Entry::Vacant(e) = self.original_storage.entry((address, key)) { - let original = state::with_storage::(*ctx, &address, |store| { - store.get(key).unwrap_or_default() - }); + let original = + state::with_storage::(self.backend.ctx, &address, |store| { + store.get(key).unwrap_or_default() + }); // No need to cache if same value. if original != value { e.insert(original); @@ -521,11 +487,11 @@ impl<'ctx, 'backend, 'config, C: TxContext, Cfg: Config> StackState<'config> } if value == types::H256::default() { - state::with_storage::(*ctx, &address, |store| { + state::with_storage::(self.backend.ctx, &address, |store| { store.remove(key); }); } else { - state::with_storage::(*ctx, &address, |store| { + state::with_storage::(self.backend.ctx, &address, |store| { store.insert(key, value); }); } @@ -547,7 +513,7 @@ impl<'ctx, 'backend, 'config, C: TxContext, Cfg: Config> StackState<'config> } fn set_code(&mut self, address: H160, code: Vec) { - CurrentStore::with(|store| { + CurrentState::with_store(|store| { let mut store = state::codes(store); store.insert(address, code); }); diff --git a/runtime-sdk/modules/evm/src/lib.rs b/runtime-sdk/modules/evm/src/lib.rs index 96d1ecf1ea..ae92b1811c 100644 --- a/runtime-sdk/modules/evm/src/lib.rs +++ b/runtime-sdk/modules/evm/src/lib.rs @@ -21,7 +21,7 @@ use thiserror::Error; use oasis_runtime_sdk::{ callformat, - context::{BatchContext, Context, TransactionWithMeta, TxContext}, + context::Context, handler, migration, module::{self, Module as _}, modules::{ @@ -31,7 +31,7 @@ use oasis_runtime_sdk::{ }, runtime::Runtime, sdk_derive, - storage::CurrentStore, + state::{CurrentState, Mode, Options, TransactionResult, TransactionWithMeta}, types::{ address::{self, Address}, token, transaction, @@ -265,12 +265,11 @@ pub enum Event { pub trait API { /// Perform an Ethereum CREATE transaction. /// Returns 160-bit address of created contract. - fn create(ctx: &mut C, value: U256, init_code: Vec) - -> Result, Error>; + fn create(ctx: &C, value: U256, init_code: Vec) -> Result, Error>; /// Perform an Ethereum CALL transaction. - fn call( - ctx: &mut C, + fn call( + ctx: &C, address: H160, value: U256, data: Vec, @@ -279,44 +278,42 @@ pub trait API { /// Peek into EVM storage. /// Returns 256-bit value stored at given contract address and index (slot) /// in the storage. - fn get_storage(ctx: &mut C, address: H160, index: H256) -> Result, Error>; + fn get_storage(ctx: &C, address: H160, index: H256) -> Result, Error>; /// Peek into EVM code storage. /// Returns EVM bytecode of contract at given address. - fn get_code(ctx: &mut C, address: H160) -> Result, Error>; + fn get_code(ctx: &C, address: H160) -> Result, Error>; /// Get EVM account balance. - fn get_balance(ctx: &mut C, address: H160) -> Result; + fn get_balance(ctx: &C, address: H160) -> Result; /// Simulate an Ethereum CALL. /// /// If the EVM is confidential, it may accept _signed queries_, which are formatted as /// an either a [`sdk::types::transaction::Call`] or [`types::SignedCallDataPack`] encoded /// and packed into the `data` field of the [`types::SimulateCallQuery`]. - fn simulate_call( - ctx: &mut C, - call: types::SimulateCallQuery, - ) -> Result, Error>; + fn simulate_call(ctx: &C, call: types::SimulateCallQuery) + -> Result, Error>; } impl API for Module { - fn create( - ctx: &mut C, - value: U256, - init_code: Vec, - ) -> Result, Error> { - let caller = Self::derive_caller(ctx)?; + fn create(ctx: &C, value: U256, init_code: Vec) -> Result, Error> { + let caller = Self::derive_caller()?; if !ctx.should_execute_contracts() { // Only fast checks are allowed. return Ok(vec![]); } + let (tx_call_format, tx_index, is_simulation) = CurrentState::with_env(|env| { + (env.tx_call_format(), env.tx_index(), env.is_simulation()) + }); + // Create output (the contract address) does not need to be encrypted because it's // trivially computable by anyone who can observe the create tx and receipt status. // Therefore, we don't need the `tx_metadata` or to encode the result. let (init_code, _tx_metadata) = - Self::decode_call_data(ctx, init_code, ctx.tx_call_format(), ctx.tx_index(), true)? + Self::decode_call_data(ctx, init_code, tx_call_format, tx_index, true)? .expect("processing always proceeds"); Self::do_evm( @@ -338,26 +335,29 @@ impl API for Module { }, // If in simulation, this must be EstimateGas query. // Use estimate mode if not doing binary search for exact gas costs. - ctx.is_simulation() - && ::Core::estimate_gas_search_max_iters(ctx) == 0, + is_simulation && ::Core::estimate_gas_search_max_iters(ctx) == 0, ) } - fn call( - ctx: &mut C, + fn call( + ctx: &C, address: H160, value: U256, data: Vec, ) -> Result, Error> { - let caller = Self::derive_caller(ctx)?; + let caller = Self::derive_caller()?; if !ctx.should_execute_contracts() { // Only fast checks are allowed. return Ok(vec![]); } + let (tx_call_format, tx_index, is_simulation) = CurrentState::with_env(|env| { + (env.tx_call_format(), env.tx_index(), env.is_simulation()) + }); + let (data, tx_metadata) = - Self::decode_call_data(ctx, data, ctx.tx_call_format(), ctx.tx_index(), true)? + Self::decode_call_data(ctx, data, tx_call_format, tx_index, true)? .expect("processing always proceeds"); let evm_result = Self::do_evm( @@ -375,33 +375,32 @@ impl API for Module { }, // If in simulation, this must be EstimateGas query. // Use estimate mode if not doing binary search for exact gas costs. - ctx.is_simulation() - && ::Core::estimate_gas_search_max_iters(ctx) == 0, + is_simulation && ::Core::estimate_gas_search_max_iters(ctx) == 0, ); Self::encode_evm_result(ctx, evm_result, tx_metadata) } - fn get_storage(_ctx: &mut C, address: H160, index: H256) -> Result, Error> { + fn get_storage(_ctx: &C, address: H160, index: H256) -> Result, Error> { state::with_public_storage(&address, |store| { let result: H256 = store.get(index).unwrap_or_default(); Ok(result.as_bytes().to_vec()) }) } - fn get_code(_ctx: &mut C, address: H160) -> Result, Error> { - CurrentStore::with(|store| { + fn get_code(_ctx: &C, address: H160) -> Result, Error> { + CurrentState::with_store(|store| { let codes = state::codes(store); Ok(codes.get(address).unwrap_or_default()) }) } - fn get_balance(_ctx: &mut C, address: H160) -> Result { + fn get_balance(_ctx: &C, address: H160) -> Result { let address = Cfg::map_address(address.into()); Ok(Cfg::Accounts::get_balance(address, Cfg::TOKEN_DENOMINATION).unwrap_or_default()) } fn simulate_call( - ctx: &mut C, + ctx: &C, call: types::SimulateCallQuery, ) -> Result, Error> { let ( @@ -416,63 +415,65 @@ impl API for Module { tx_metadata, ) = Self::decode_simulate_call_query(ctx, call)?; - let evm_result = ctx.with_simulation(|mut sctx| { - let call_tx = transaction::Transaction { - version: 1, - call: transaction::Call { - format: transaction::CallFormat::Plain, - method: "evm.Call".to_owned(), - body: cbor::to_value(types::Call { - address, - value, - data: data.clone(), - }), - ..Default::default() + let call_tx = transaction::Transaction { + version: 1, + call: transaction::Call { + format: transaction::CallFormat::Plain, + method: "evm.Call".to_owned(), + body: cbor::to_value(types::Call { + address, + value, + data: data.clone(), + }), + ..Default::default() + }, + auth_info: transaction::AuthInfo { + signer_info: vec![], + fee: transaction::Fee { + amount: token::BaseUnits::new( + gas_price + .checked_mul(U256::from(gas_limit)) + .ok_or(Error::FeeOverflow)? + .as_u128(), + Cfg::TOKEN_DENOMINATION, + ), + gas: gas_limit, + consensus_messages: 0, }, - auth_info: transaction::AuthInfo { - signer_info: vec![], - fee: transaction::Fee { - amount: token::BaseUnits::new( - gas_price - .checked_mul(U256::from(gas_limit)) - .ok_or(Error::FeeOverflow)? - .as_u128(), - Cfg::TOKEN_DENOMINATION, - ), - gas: gas_limit, - consensus_messages: 0, + ..Default::default() + }, + }; + let evm_result = CurrentState::with_transaction_opts( + Options::new() + .with_tx(TransactionWithMeta::internal(call_tx)) + .with_mode(Mode::Simulate), + || { + let result = Self::do_evm( + caller, + ctx, + |exec, gas_limit| { + exec.transact_call( + caller.into(), + address.into(), + value.into(), + data, + gas_limit, + vec![], + ) }, - ..Default::default() - }, - }; - sctx.with_tx( - TransactionWithMeta::internal(call_tx), - |mut txctx, _call| { - Self::do_evm( - caller, - &mut txctx, - |exec, gas_limit| { - exec.transact_call( - caller.into(), - address.into(), - value.into(), - data, - gas_limit, - vec![], - ) - }, - // Simulate call is never called from EstimateGas. - false, - ) - }, - ) - }); + // Simulate call is never called from EstimateGas. + false, + ); + + TransactionResult::Rollback(result) + }, + ); Self::encode_evm_result(ctx, evm_result, tx_metadata) } } impl Module { - fn do_evm(source: H160, ctx: &mut C, f: F, estimate_gas: bool) -> Result, Error> + fn do_evm(source: H160, ctx: &C, f: F, estimate_gas: bool) -> Result, Error> where F: FnOnce( &mut StackExecutor< @@ -483,12 +484,13 @@ impl Module { >, u64, ) -> (evm::ExitReason, Vec), - C: TxContext, + C: Context, { - let is_query = ctx.is_check_only() || ctx.is_simulation(); + let is_query = CurrentState::with_env(|env| !env.is_execute()); let cfg = Cfg::evm_config(estimate_gas); - let gas_limit: u64 = ::Core::remaining_tx_gas(ctx); - let gas_price: primitive_types::U256 = ctx.tx_auth_info().fee.gas_price().into(); + let gas_limit: u64 = ::Core::remaining_tx_gas(); + let gas_price: primitive_types::U256 = + CurrentState::with_env(|env| env.tx_auth_info().fee.gas_price().into()); let vicinity = backend::Vicinity { gas_price, @@ -522,29 +524,26 @@ impl Module { _ => unreachable!("already handled above"), }; - ::Core::use_tx_gas(ctx, gas_used)?; - Cfg::Accounts::set_refund_unused_tx_fee(ctx, Cfg::REFUND_UNUSED_FEE); + ::Core::use_tx_gas(gas_used)?; + Cfg::Accounts::set_refund_unused_tx_fee(Cfg::REFUND_UNUSED_FEE); return Err(err); } }; // Apply can fail in case of unsupported actions. if let Err(err) = executor.into_state().apply() { - ::Core::use_tx_gas(ctx, gas_used)?; + ::Core::use_tx_gas(gas_used)?; return Err(err); // Do not refund unused fee. }; - ::Core::use_tx_gas(ctx, gas_used)?; - Cfg::Accounts::set_refund_unused_tx_fee(ctx, Cfg::REFUND_UNUSED_FEE); + ::Core::use_tx_gas(gas_used)?; + Cfg::Accounts::set_refund_unused_tx_fee(Cfg::REFUND_UNUSED_FEE); Ok(exit_value) } - fn derive_caller(ctx: &C) -> Result - where - C: TxContext, - { - derive_caller::from_tx_auth_info(ctx.tx_auth_info()) + fn derive_caller() -> Result { + CurrentState::with_env(|env| derive_caller::from_tx_auth_info(env.tx_auth_info())) } /// Returns the decrypted call data or `None` if this transaction is simulated in @@ -674,33 +673,33 @@ impl Module { } #[handler(call = "evm.Create")] - fn tx_create(ctx: &mut C, body: types::Create) -> Result, Error> { + fn tx_create(ctx: &C, body: types::Create) -> Result, Error> { Self::create(ctx, body.value, body.init_code) } #[handler(call = "evm.Call")] - fn tx_call(ctx: &mut C, body: types::Call) -> Result, Error> { + fn tx_call(ctx: &C, body: types::Call) -> Result, Error> { Self::call(ctx, body.address, body.value, body.data) } #[handler(query = "evm.Storage")] - fn query_storage(ctx: &mut C, body: types::StorageQuery) -> Result, Error> { + fn query_storage(ctx: &C, body: types::StorageQuery) -> Result, Error> { Self::get_storage(ctx, body.address, body.index) } #[handler(query = "evm.Code")] - fn query_code(ctx: &mut C, body: types::CodeQuery) -> Result, Error> { + fn query_code(ctx: &C, body: types::CodeQuery) -> Result, Error> { Self::get_code(ctx, body.address) } #[handler(query = "evm.Balance")] - fn query_balance(ctx: &mut C, body: types::BalanceQuery) -> Result { + fn query_balance(ctx: &C, body: types::BalanceQuery) -> Result { Self::get_balance(ctx, body.address) } #[handler(query = "evm.SimulateCall", expensive, allow_private_km)] fn query_simulate_call( - ctx: &mut C, + ctx: &C, body: types::SimulateCallQuery, ) -> Result, Error> { let cfg: LocalConfig = ctx.local_config(MODULE_NAME).unwrap_or_default(); @@ -715,7 +714,7 @@ impl Module { impl module::TransactionHandler for Module { fn decode_tx( - _ctx: &mut C, + _ctx: &C, scheme: &str, body: &[u8], ) -> Result, CoreError> { @@ -730,8 +729,8 @@ impl module::TransactionHandler for Module { } impl module::BlockHandler for Module { - fn end_block(ctx: &mut C) { - CurrentStore::with(|store| { + fn end_block(ctx: &C) { + CurrentState::with_store(|store| { // Update the list of historic block hashes. let block_number = ctx.runtime_header().round; let block_hash = ctx.runtime_header().encoded_hash(); diff --git a/runtime-sdk/modules/evm/src/mock.rs b/runtime-sdk/modules/evm/src/mock.rs index e1dfe9799e..ec0fbc56d0 100644 --- a/runtime-sdk/modules/evm/src/mock.rs +++ b/runtime-sdk/modules/evm/src/mock.rs @@ -9,7 +9,7 @@ use oasis_runtime_sdk::{ module, testing::mock::{CallOptions, Signer}, types::{address::SignatureAddressSpec, transaction}, - BatchContext, + Context, }; use crate::{ @@ -29,14 +29,14 @@ impl EvmSigner { /// Dispatch a call to the given EVM contract method. pub fn call_evm( &mut self, - ctx: &mut C, + ctx: &C, address: H160, name: &str, param_types: &[ethabi::ParamType], params: &[ethabi::Token], ) -> dispatcher::DispatchResult where - C: BatchContext, + C: Context, { self.call_evm_opts(ctx, address, name, param_types, params, Default::default()) } @@ -44,7 +44,7 @@ impl EvmSigner { /// Dispatch a call to the given EVM contract method with the given options. pub fn call_evm_opts( &mut self, - ctx: &mut C, + ctx: &C, address: H160, name: &str, param_types: &[ethabi::ParamType], @@ -52,7 +52,7 @@ impl EvmSigner { opts: CallOptions, ) -> dispatcher::DispatchResult where - C: BatchContext, + C: Context, { let data = [ ethabi::short_signature(name, param_types).to_vec(), @@ -80,14 +80,14 @@ impl EvmSigner { /// Dispatch a query to the given EVM contract method. pub fn query_evm( &self, - ctx: &mut C, + ctx: &C, address: H160, name: &str, param_types: &[ethabi::ParamType], params: &[ethabi::Token], ) -> Result, RuntimeError> where - C: BatchContext, + C: Context, { self.query_evm_opts(ctx, address, name, param_types, params, Default::default()) } @@ -95,7 +95,7 @@ impl EvmSigner { /// Dispatch a query to the given EVM contract method. pub fn query_evm_opts( &self, - ctx: &mut C, + ctx: &C, address: H160, name: &str, param_types: &[ethabi::ParamType], @@ -103,7 +103,7 @@ impl EvmSigner { opts: QueryOptions, ) -> Result, RuntimeError> where - C: BatchContext, + C: Context, { let mut data = [ ethabi::short_signature(name, param_types).to_vec(), diff --git a/runtime-sdk/modules/evm/src/precompile/gas.rs b/runtime-sdk/modules/evm/src/precompile/gas.rs index 7d7970c431..3eb42d64d7 100644 --- a/runtime-sdk/modules/evm/src/precompile/gas.rs +++ b/runtime-sdk/modules/evm/src/precompile/gas.rs @@ -64,7 +64,6 @@ mod test { }; use ethabi::{ParamType, Token}; use oasis_runtime_sdk::{ - context, modules::core::Event, testing::{keys, mock::Mock}, }; @@ -97,18 +96,17 @@ mod test { // Test use gas in contract. let mut mock = Mock::default(); - let mut ctx = mock.create_ctx_for_runtime::(context::Mode::ExecuteTx, false); + let ctx = mock.create_ctx_for_runtime::(false); let mut signer = EvmSigner::new(0, keys::dave::sigspec()); // Create contract. - let contract_address = - init_and_deploy_contract(&mut ctx, &mut signer, TEST_CONTRACT_CODE_HEX); + let contract_address = init_and_deploy_contract(&ctx, &mut signer, TEST_CONTRACT_CODE_HEX); let expected_gas_used = 22_659; // Call into the test contract. let dispatch_result = - signer.call_evm(&mut ctx, contract_address.into(), "test_gas_used", &[], &[]); + signer.call_evm(&ctx, contract_address.into(), "test_gas_used", &[], &[]); assert!( dispatch_result.result.is_success(), "test gas used should succeed" @@ -161,18 +159,17 @@ mod test { // Test gas padding. let mut mock = Mock::default(); - let mut ctx = mock.create_ctx_for_runtime::(context::Mode::ExecuteTx, false); + let ctx = mock.create_ctx_for_runtime::(false); let mut signer = EvmSigner::new(0, keys::dave::sigspec()); // Create contract. - let contract_address = - init_and_deploy_contract(&mut ctx, &mut signer, TEST_CONTRACT_CODE_HEX); + let contract_address = init_and_deploy_contract(&ctx, &mut signer, TEST_CONTRACT_CODE_HEX); let expected_gas = 41_359; // Call into the test contract path for `if param > 10`. let dispatch_result = signer.call_evm( - &mut ctx, + &ctx, contract_address.into(), "test_pad_gas", &[ParamType::Uint(128)], @@ -194,7 +191,7 @@ mod test { // Call into the test contract path `if param < 10`. let dispatch_result = signer.call_evm( - &mut ctx, + &ctx, contract_address.into(), "test_pad_gas", &[ParamType::Uint(128)], diff --git a/runtime-sdk/modules/evm/src/precompile/subcall.rs b/runtime-sdk/modules/evm/src/precompile/subcall.rs index 39b34e1be5..ee09c27b5b 100644 --- a/runtime-sdk/modules/evm/src/precompile/subcall.rs +++ b/runtime-sdk/modules/evm/src/precompile/subcall.rs @@ -120,7 +120,6 @@ mod test { use ethabi::{ParamType, Token}; use oasis_runtime_sdk::{ - context, module::{self, Module as _}, modules::accounts, testing::{ @@ -151,16 +150,15 @@ mod test { #[test] fn test_subcall_dispatch() { let mut mock = Mock::default(); - let mut ctx = mock.create_ctx_for_runtime::(context::Mode::ExecuteTx, false); + let ctx = mock.create_ctx_for_runtime::(false); let mut signer = EvmSigner::new(0, keys::dave::sigspec()); // Create contract. - let contract_address = - init_and_deploy_contract(&mut ctx, &mut signer, TEST_CONTRACT_CODE_HEX); + let contract_address = init_and_deploy_contract(&ctx, &mut signer, TEST_CONTRACT_CODE_HEX); // Call into the test contract. let dispatch_result = signer.call_evm( - &mut ctx, + &ctx, contract_address.into(), "test", &[ @@ -182,7 +180,7 @@ mod test { // Transfer some tokens to the contract. let dispatch_result = signer.call( - &mut ctx, + &ctx, "accounts.Transfer", accounts::types::Transfer { to: TestConfig::map_address(contract_address.into()), @@ -196,7 +194,7 @@ mod test { // Call into test contract again. let dispatch_result = signer.call_evm_opts( - &mut ctx, + &ctx, contract_address.into(), "test", &[ @@ -258,16 +256,15 @@ mod test { #[test] fn test_require_regular_call() { let mut mock = Mock::default(); - let mut ctx = mock.create_ctx_for_runtime::(context::Mode::ExecuteTx, false); + let ctx = mock.create_ctx_for_runtime::(false); let mut signer = EvmSigner::new(0, keys::dave::sigspec()); // Create contract. - let contract_address = - init_and_deploy_contract(&mut ctx, &mut signer, TEST_CONTRACT_CODE_HEX); + let contract_address = init_and_deploy_contract(&ctx, &mut signer, TEST_CONTRACT_CODE_HEX); // Call into the test contract. let dispatch_result = signer.call_evm( - &mut ctx, + &ctx, contract_address.into(), "test_delegatecall", &[ @@ -299,16 +296,15 @@ mod test { #[test] fn test_no_reentrance() { let mut mock = Mock::default(); - let mut ctx = mock.create_ctx_for_runtime::(context::Mode::ExecuteTx, false); + let ctx = mock.create_ctx_for_runtime::(false); let mut signer = EvmSigner::new(0, keys::dave::sigspec()); // Create contract. - let contract_address = - init_and_deploy_contract(&mut ctx, &mut signer, TEST_CONTRACT_CODE_HEX); + let contract_address = init_and_deploy_contract(&ctx, &mut signer, TEST_CONTRACT_CODE_HEX); // Call into the test contract. let dispatch_result = signer.call_evm( - &mut ctx, + &ctx, contract_address.into(), "test", &[ @@ -352,12 +348,11 @@ mod test { #[test] fn test_gas_accounting() { let mut mock = Mock::default(); - let mut ctx = mock.create_ctx_for_runtime::(context::Mode::ExecuteTx, false); + let ctx = mock.create_ctx_for_runtime::(false); let mut signer = EvmSigner::new(0, keys::dave::sigspec()); // Create contract. - let contract_address = - init_and_deploy_contract(&mut ctx, &mut signer, TEST_CONTRACT_CODE_HEX); + let contract_address = init_and_deploy_contract(&ctx, &mut signer, TEST_CONTRACT_CODE_HEX); // Make transfers more expensive so we can test an out-of-gas condition. accounts::Module::set_params(accounts::Parameters { @@ -369,7 +364,7 @@ mod test { // First try a call with enough gas. let dispatch_result = signer.call_evm_opts( - &mut ctx, + &ctx, contract_address.into(), "test", &[ @@ -398,7 +393,7 @@ mod test { // Then lower the amount such that the inner call would fail, but the rest of execution // can still continue (e.g. to trigger the revert). let dispatch_result = signer.call_evm_opts( - &mut ctx, + &ctx, contract_address.into(), "test", &[ @@ -449,7 +444,7 @@ mod test { // Then raise the amount such that the inner call would succeed but the rest of the // execution would fail. let dispatch_result = signer.call_evm_opts( - &mut ctx, + &ctx, contract_address.into(), "test_spin", // Version that spins, wasting gas, after the subcall. &[ diff --git a/runtime-sdk/modules/evm/src/precompile/testing.rs b/runtime-sdk/modules/evm/src/precompile/testing.rs index b9717f5f67..0e5f1690d3 100644 --- a/runtime-sdk/modules/evm/src/precompile/testing.rs +++ b/runtime-sdk/modules/evm/src/precompile/testing.rs @@ -5,12 +5,13 @@ use evm::{ pub use primitive_types::{H160, H256}; use oasis_runtime_sdk::{ + context, module::{self}, modules::{accounts, accounts::Module, core, core::Error}, subcall, testing::keys, types::token::{self, Denomination}, - BatchContext, Runtime, Version, + Runtime, Version, }; use crate::{ @@ -240,8 +241,8 @@ impl Runtime for TestRuntime { } #[cfg(any(test, feature = "test"))] -pub fn init_and_deploy_contract( - ctx: &mut C, +pub fn init_and_deploy_contract( + ctx: &C, signer: &mut EvmSigner, bytecode: &str, ) -> H160 { diff --git a/runtime-sdk/modules/evm/src/signed_call.rs b/runtime-sdk/modules/evm/src/signed_call.rs index 6f81e7ab4d..9af3527b1f 100644 --- a/runtime-sdk/modules/evm/src/signed_call.rs +++ b/runtime-sdk/modules/evm/src/signed_call.rs @@ -6,7 +6,7 @@ use sha3::{Digest as _, Keccak256}; use oasis_runtime_sdk::{ context::Context, core::common::crypto::hash::Hash, modules::accounts::API as _, - storage::CurrentStore, + state::CurrentState, }; use crate::{ @@ -49,7 +49,7 @@ pub(crate) fn verify( return Err(Error::InvalidSignedSimulateCall("stale nonce")); } - let base_block_hash = CurrentStore::with(|store| { + let base_block_hash = CurrentState::with_store(|store| { let block_hashes = state::block_hashes(store); match block_hashes.get::<_, Hash>(&leash.block_number.to_be_bytes()) { Some(hash) => Ok(hash), @@ -201,7 +201,7 @@ mod test { } fn setup_block(leash: &Leash) { - CurrentStore::with(|store| { + CurrentState::with_store(|store| { let mut block_hashes = state::block_hashes(store); block_hashes.insert::<_, Hash>( &leash.block_number.to_be_bytes(), diff --git a/runtime-sdk/modules/evm/src/state.rs b/runtime-sdk/modules/evm/src/state.rs index 41f3a3e0ba..71d35fe4be 100644 --- a/runtime-sdk/modules/evm/src/state.rs +++ b/runtime-sdk/modules/evm/src/state.rs @@ -1,6 +1,7 @@ use oasis_runtime_sdk::{ context::Context, - storage::{ConfidentialStore, CurrentStore, HashedStore, PrefixStore, Store, TypedStore}, + state::CurrentState, + storage::{ConfidentialStore, HashedStore, PrefixStore, Store, TypedStore}, }; use crate::{types::H160, Config}; @@ -24,7 +25,7 @@ pub const BLOCK_HASH_WINDOW_SIZE: u64 = 256; /// Run closure with the store of the provided contract address. Based on configuration this will /// be either confidential or public storage. -pub fn with_storage(ctx: &mut C, address: &H160, f: F) -> R +pub fn with_storage(ctx: &C, address: &H160, f: F) -> R where Cfg: Config, C: Context, @@ -42,7 +43,7 @@ pub fn with_public_storage(address: &H160, f: F) -> R where F: FnOnce(&mut TypedStore<&mut dyn Store>) -> R, { - CurrentStore::with(|store| { + CurrentState::with_store(|store| { let mut store = HashedStore::<_, blake3::Hasher>::new(contract_storage(store, STORAGES, address)); let mut store = TypedStore::new(&mut store as &mut dyn Store); @@ -51,7 +52,7 @@ where } /// Run closure with the confidential store of the provided contract address. -pub fn with_confidential_storage<'a, C, F, R>(ctx: &'a mut C, address: &'a H160, f: F) -> R +pub fn with_confidential_storage<'a, C, F, R>(ctx: &'a C, address: &'a H160, f: F) -> R where C: Context, F: FnOnce(&mut TypedStore<&mut dyn Store>) -> R, @@ -70,26 +71,27 @@ where // These values are used to derive the confidential store nonce: let round = ctx.runtime_header().round; - let instance_count: usize = { - // One Context is used per tx batch, so the instance count will monotonically increase. - let cnt = *ctx - .value(CONTEXT_KEY_CONFIDENTIAL_STORE_INSTANCE_COUNT) + let instance_count: usize = CurrentState::with(|state| { + // One state is used per tx batch, so the instance count will monotonically increase. + let cnt = *state + .block_value(CONTEXT_KEY_CONFIDENTIAL_STORE_INSTANCE_COUNT) .or_default(); - ctx.value(CONTEXT_KEY_CONFIDENTIAL_STORE_INSTANCE_COUNT) + state + .block_value(CONTEXT_KEY_CONFIDENTIAL_STORE_INSTANCE_COUNT) .set(cnt + 1); cnt - }; - let mode = ctx.mode(); + }); - CurrentStore::with(|store| { - let contract_storages = contract_storage(store, CONFIDENTIAL_STORAGES, address); + CurrentState::with(|state| { + let mode = state.env().mode() as u8; + let contract_storages = contract_storage(state.store(), CONFIDENTIAL_STORAGES, address); let mut confidential_storages = ConfidentialStore::new_with_key( contract_storages, confidential_key.0, &[ round.to_le_bytes().as_slice(), instance_count.to_le_bytes().as_slice(), - &[mode as u8], + &[mode], ], ); let mut store = TypedStore::new(&mut confidential_storages as &mut dyn Store); diff --git a/runtime-sdk/modules/evm/src/test.rs b/runtime-sdk/modules/evm/src/test.rs index cb58ff4079..881932301e 100644 --- a/runtime-sdk/modules/evm/src/test.rs +++ b/runtime-sdk/modules/evm/src/test.rs @@ -6,7 +6,7 @@ use sha3::Digest as _; use uint::hex::FromHex; use oasis_runtime_sdk::{ - callformat, context, + callformat, crypto::{self, signature::secp256k1}, error::Error as _, module::{self, InvariantHandler as _, TransactionHandler as _}, @@ -14,7 +14,7 @@ use oasis_runtime_sdk::{ accounts::{self, Module as Accounts, ADDRESS_FEE_ACCUMULATOR, API as _}, core::{self, Module as Core}, }, - storage::{current::TransactionResult, CurrentStore}, + state::{self, CurrentState, Mode, Options, TransactionResult}, testing::{keys, mock, mock::CallOptions}, types::{ address::{Address, SignatureAddressSpec}, @@ -22,7 +22,7 @@ use oasis_runtime_sdk::{ transaction, transaction::Fee, }, - BatchContext, Context, Runtime, Version, + Runtime, Version, }; use crate::{ @@ -108,7 +108,7 @@ fn test_evm_caller_addr_derivation() { fn do_test_evm_calls(force_plain: bool) { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx_for_runtime::(context::Mode::ExecuteTx, true); + let ctx = mock.create_ctx_for_runtime::(true); let client_keypair = oasis_runtime_sdk::core::common::crypto::mrae::deoxysii::generate_key_pair(); @@ -215,16 +215,18 @@ fn do_test_evm_calls(force_plain: bool) { }, }; // Run authentication handler to simulate nonce increments. - Accounts::authenticate_tx(&mut ctx, &create_tx).unwrap(); + Accounts::authenticate_tx(&ctx, &create_tx).unwrap(); - let erc20_addr = ctx.with_tx(create_tx.into(), |mut tx_ctx, call| { - let addr = H160::from_slice( - &EVMModule::::tx_create(&mut tx_ctx, cbor::from_value(call.body).unwrap()).unwrap(), - ); - EVMModule::::check_invariants(&mut tx_ctx).expect("invariants should hold"); - tx_ctx.commit(); - addr - }); + let call = create_tx.call.clone(); + let erc20_addr = + CurrentState::with_transaction_opts(Options::new().with_tx(create_tx.into()), || { + let addr = H160::from_slice( + &EVMModule::::tx_create(&ctx, cbor::from_value(call.body).unwrap()).unwrap(), + ); + EVMModule::::check_invariants(&ctx).expect("invariants should hold"); + + TransactionResult::Commit(addr) + }); // Test the Call transaction. let name_method: Vec = Vec::from_hex("06fdde03".to_owned() + &"0".repeat(64 - 8)).unwrap(); @@ -254,24 +256,24 @@ fn do_test_evm_calls(force_plain: bool) { }, }; // Run authentication handler to simulate nonce increments. - Accounts::authenticate_tx(&mut ctx, &call_name_tx).unwrap(); - - let erc20_name = ctx.with_tx(call_name_tx.into(), |mut tx_ctx, call| { - let name: Vec = cbor::from_value( - decode_result!( - tx_ctx, - EVMModule::::tx_call(&mut tx_ctx, cbor::from_value(call.body).unwrap()) + Accounts::authenticate_tx(&ctx, &call_name_tx).unwrap(); + + let call = call_name_tx.call.clone(); + let erc20_name = + CurrentState::with_transaction_opts(Options::new().with_tx(call_name_tx.into()), || { + let name: Vec = cbor::from_value( + decode_result!( + ctx, + EVMModule::::tx_call(&ctx, cbor::from_value(call.body).unwrap()) + ) + .unwrap(), ) - .unwrap(), - ) - .unwrap(); - - EVMModule::::check_invariants(&mut tx_ctx).expect("invariants should hold"); + .unwrap(); - tx_ctx.commit(); + EVMModule::::check_invariants(&ctx).expect("invariants should hold"); - name - }); + TransactionResult::Commit(name) + }); assert_eq!(erc20_name.len(), 96); assert_eq!(erc20_name[63], 0x04); // Name is 4 bytes long. assert_eq!(erc20_name[64..68], vec![0x54, 0x65, 0x73, 0x74]); // "Test". @@ -298,7 +300,7 @@ fn test_c10l_evm_calls_plain() { fn test_c10l_evm_balance_transfer() { crypto::signature::context::set_chain_context(Default::default(), "test"); let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); + let ctx = mock.create_ctx(); Core::::init(core::Genesis { parameters: core::Parameters { @@ -347,21 +349,17 @@ fn test_c10l_evm_balance_transfer() { }, }; // Run authentication handler to simulate nonce increments. - Accounts::authenticate_tx(&mut ctx, &transfer_tx).unwrap(); + Accounts::authenticate_tx(&ctx, &transfer_tx).unwrap(); - ctx.with_tx(transfer_tx.into(), |mut tx_ctx, call| { - EVMModule::::tx_call( - &mut tx_ctx, - cbor::from_value(call.body).unwrap(), - ) - .unwrap(); - EVMModule::::check_invariants(&mut tx_ctx) - .expect("invariants should hold"); - tx_ctx.commit(); + let call = transfer_tx.call.clone(); + CurrentState::with_transaction_opts(Options::new().with_tx(transfer_tx.into()), || { + EVMModule::::tx_call(&ctx, cbor::from_value(call.body).unwrap()) + .unwrap(); + EVMModule::::check_invariants(&ctx).expect("invariants should hold"); }); let recipient_balance = EVMModule::::query_balance( - &mut ctx, + &ctx, types::BalanceQuery { address: recipient.into(), }, @@ -375,10 +373,7 @@ fn test_c10l_enc_call_identity_decoded() { // Calls sent using the Oasis encrypted envelope format (not inner-enveloped) // should not be decoded: let mut mock = mock::Mock::default(); - let ctx = mock.create_ctx_for_runtime::>( - context::Mode::ExecuteTx, - true, - ); + let ctx = mock.create_ctx_for_runtime::>(true); let data = vec![1, 2, 3, 4, 5]; let (decoded_data, metadata) = EVMModule::::decode_call_data( &ctx, @@ -443,7 +438,7 @@ impl Runtime for EVMRuntime { fn do_test_evm_runtime() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx_for_runtime::>(context::Mode::ExecuteTx, true); + let ctx = mock.create_ctx_for_runtime::>(true); let client_keypair = oasis_runtime_sdk::core::common::crypto::mrae::deoxysii::generate_key_pair(); @@ -493,7 +488,7 @@ fn do_test_evm_runtime() { }; } - EVMRuntime::::migrate(&mut ctx); + EVMRuntime::::migrate(&ctx); let erc20 = load_erc20(); @@ -523,16 +518,18 @@ fn do_test_evm_runtime() { }, }; // Run authentication handler to simulate nonce increments. - as Runtime>::Modules::authenticate_tx(&mut ctx, &create_tx).unwrap(); + as Runtime>::Modules::authenticate_tx(&ctx, &create_tx).unwrap(); - let erc20_addr = ctx.with_tx(create_tx.into(), |mut tx_ctx, call| { - let addr = H160::from_slice( - &EVMModule::::tx_create(&mut tx_ctx, cbor::from_value(call.body).unwrap()).unwrap(), - ); - EVMModule::::check_invariants(&mut tx_ctx).expect("invariants should hold"); - tx_ctx.commit(); - addr - }); + let call = create_tx.call.clone(); + let erc20_addr = + CurrentState::with_transaction_opts(Options::new().with_tx(create_tx.into()), || { + let addr = H160::from_slice( + &EVMModule::::tx_create(&ctx, cbor::from_value(call.body).unwrap()).unwrap(), + ); + EVMModule::::check_invariants(&ctx).expect("invariants should hold"); + + TransactionResult::Commit(addr) + }); // Make sure the derived address matches the expected value. If this fails it likely indicates // a problem with nonce increment semantics between the SDK and EVM. @@ -570,20 +567,28 @@ fn do_test_evm_runtime() { }, }; // Run authentication handler to simulate nonce increments. - as Runtime>::Modules::authenticate_tx(&mut ctx, &out_of_gas_create).unwrap(); - - ctx.with_tx(out_of_gas_create.clone().into(), |mut tx_ctx, call| { - assert!(!decode_result!( - tx_ctx, - EVMModule::::tx_create(&mut tx_ctx, cbor::from_value(call.body).unwrap()) - ) - .is_success()); - }); + as Runtime>::Modules::authenticate_tx(&ctx, &out_of_gas_create).unwrap(); + + let call = out_of_gas_create.call.clone(); + CurrentState::with_transaction_opts( + Options::new().with_tx(out_of_gas_create.clone().into()), + || { + assert!(!decode_result!( + ctx, + EVMModule::::tx_create(&ctx, cbor::from_value(call.body).unwrap()) + ) + .is_success()); + }, + ); // CheckTx should not fail. - ctx.with_child(context::Mode::CheckTx, |mut check_ctx| { - check_ctx.with_tx(out_of_gas_create.into(), |mut tx_ctx, call| { - let rsp = EVMModule::::tx_create(&mut tx_ctx, cbor::from_value(call.body).unwrap()) + let call = out_of_gas_create.call.clone(); + CurrentState::with_transaction_opts( + Options::new() + .with_mode(state::Mode::Check) + .with_tx(out_of_gas_create.clone().into()), + || { + let rsp = EVMModule::::tx_create(&ctx, cbor::from_value(call.body).unwrap()) .expect("call should succeed with empty result"); assert_eq!( @@ -591,8 +596,8 @@ fn do_test_evm_runtime() { Vec::::new(), "check tx should return an empty response" ); - }); - }); + }, + ); // Test the Call transaction. let name_method: Vec = Vec::from_hex("06fdde03".to_owned() + &"0".repeat(64 - 8)).unwrap(); @@ -622,51 +627,48 @@ fn do_test_evm_runtime() { }, }; // Run authentication handler to simulate nonce increments. - as Runtime>::Modules::authenticate_tx(&mut ctx, &call_name_tx).unwrap(); + as Runtime>::Modules::authenticate_tx(&ctx, &call_name_tx).unwrap(); // Test transaction call in simulate mode. - CurrentStore::with_transaction(|| { - ctx.with_simulation(|mut sim_ctx| { - let erc20_name = sim_ctx.with_tx(call_name_tx.clone().into(), |mut tx_ctx, call| { - let name: Vec = cbor::from_value( - decode_result!( - tx_ctx, - EVMModule::::tx_call(&mut tx_ctx, cbor::from_value(call.body).unwrap()) - ) - .unwrap(), + let call = call_name_tx.call.clone(); + CurrentState::with_transaction_opts( + Options::new() + .with_mode(Mode::Simulate) + .with_tx(call_name_tx.clone().into()), + || { + let erc20_name: Vec = cbor::from_value( + decode_result!( + ctx, + EVMModule::::tx_call(&ctx, cbor::from_value(call.body).unwrap()) ) - .unwrap(); - - EVMModule::::check_invariants(&mut tx_ctx).expect("invariants should hold"); + .unwrap(), + ) + .unwrap(); - tx_ctx.commit(); + EVMModule::::check_invariants(&ctx).expect("invariants should hold"); - name - }); assert_eq!(erc20_name.len(), 96); assert_eq!(erc20_name[63], 0x04); // Name is 4 bytes long. assert_eq!(erc20_name[64..68], vec![0x54, 0x65, 0x73, 0x74]); // "Test". - }); - - TransactionResult::Rollback(()) // Ignore simulation results. - }); + }, + ); - let erc20_name = ctx.with_tx(call_name_tx.clone().into(), |mut tx_ctx, call| { - let name: Vec = cbor::from_value( - decode_result!( - tx_ctx, - EVMModule::::tx_call(&mut tx_ctx, cbor::from_value(call.body).unwrap()) + let call = call_name_tx.call.clone(); + let erc20_name = + CurrentState::with_transaction_opts(Options::new().with_tx(call_name_tx.into()), || { + let name: Vec = cbor::from_value( + decode_result!( + ctx, + EVMModule::::tx_call(&ctx, cbor::from_value(call.body).unwrap()) + ) + .unwrap(), ) - .unwrap(), - ) - .unwrap(); - - EVMModule::::check_invariants(&mut tx_ctx).expect("invariants should hold"); + .unwrap(); - tx_ctx.commit(); + EVMModule::::check_invariants(&ctx).expect("invariants should hold"); - name - }); + TransactionResult::Commit(name) + }); assert_eq!(erc20_name.len(), 96); assert_eq!(erc20_name[63], 0x04); // Name is 4 bytes long. assert_eq!(erc20_name[64..68], vec![0x54, 0x65, 0x73, 0x74]); // "Test". @@ -707,24 +709,26 @@ fn do_test_evm_runtime() { }, }; // Run authentication handler to simulate nonce increments. - as Runtime>::Modules::authenticate_tx(&mut ctx, &call_transfer_tx).unwrap(); - - let transfer_ret = ctx.with_tx(call_transfer_tx.clone().into(), |mut tx_ctx, call| { - let ret: Vec = cbor::from_value( - decode_result!( - tx_ctx, - EVMModule::::tx_call(&mut tx_ctx, cbor::from_value(call.body).unwrap()) + as Runtime>::Modules::authenticate_tx(&ctx, &call_transfer_tx).unwrap(); + + let call = call_transfer_tx.call.clone(); + let transfer_ret = CurrentState::with_transaction_opts( + Options::new().with_tx(call_transfer_tx.into()), + || { + let ret: Vec = cbor::from_value( + decode_result!( + ctx, + EVMModule::::tx_call(&ctx, cbor::from_value(call.body).unwrap()) + ) + .unwrap(), ) - .unwrap(), - ) - .unwrap(); + .unwrap(); - EVMModule::::check_invariants(&mut tx_ctx).expect("invariants should hold"); + EVMModule::::check_invariants(&ctx).expect("invariants should hold"); - tx_ctx.commit(); - - ret - }); + TransactionResult::Commit(ret) + }, + ); assert_eq!( transfer_ret, Vec::::from_hex("0".repeat(64 - 1) + &"1".to_owned()).unwrap() @@ -756,29 +760,37 @@ fn do_test_evm_runtime() { ..Default::default() }, }; - as Runtime>::Modules::authenticate_tx(&mut ctx, &out_of_gas_tx).unwrap(); - - ctx.with_tx(out_of_gas_tx.clone().into(), |mut tx_ctx, call| { - assert!(!decode_result!( - tx_ctx, - EVMModule::::tx_call(&mut tx_ctx, cbor::from_value(call.body).unwrap()) - ) - .is_success()); - }); + as Runtime>::Modules::authenticate_tx(&ctx, &out_of_gas_tx).unwrap(); + + let call = out_of_gas_tx.call.clone(); + CurrentState::with_transaction_opts( + Options::new().with_tx(out_of_gas_tx.clone().into()), + || { + assert!(!decode_result!( + ctx, + EVMModule::::tx_call(&ctx, cbor::from_value(call.body).unwrap()) + ) + .is_success()); + }, + ); // CheckTx should not fail. - ctx.with_child(context::Mode::CheckTx, |mut check_ctx| { - check_ctx.with_tx(out_of_gas_tx.into(), |mut tx_ctx, call| { - let rsp = EVMModule::::tx_call(&mut tx_ctx, cbor::from_value(call.body).unwrap()) + let call = out_of_gas_tx.call.clone(); + CurrentState::with_transaction_opts( + Options::new() + .with_mode(state::Mode::Check) + .with_tx(out_of_gas_tx.clone().into()), + || { + let rsp = EVMModule::::tx_call(&ctx, cbor::from_value(call.body).unwrap()) .expect("call should succeed with empty result"); assert_eq!( rsp, Vec::::new(), "check tx should return an empty response" - ) - }); - }); + ); + }, + ); } #[test] @@ -795,20 +807,17 @@ fn test_c10l_evm_runtime() { #[test] fn test_c10l_queries() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx_for_runtime::>( - context::Mode::ExecuteTx, - true, - ); + let ctx = mock.create_ctx_for_runtime::>(true); let mut signer = EvmSigner::new(0, keys::dave::sigspec()); - EVMRuntime::::migrate(&mut ctx); + EVMRuntime::::migrate(&ctx); static QUERY_CONTRACT_CODE_HEX: &str = include_str!("../../../../tests/e2e/contracts/query/query.hex"); // Create contract. let dispatch_result = signer.call( - &mut ctx, + &ctx, "evm.Create", types::Create { value: 0.into(), @@ -819,12 +828,11 @@ fn test_c10l_queries() { let result: Vec = cbor::from_value(result).unwrap(); let contract_address = H160::from_slice(&result); - let mut ctx = mock - .create_ctx_for_runtime::>(context::Mode::CheckTx, true); + let ctx = mock.create_ctx_for_runtime::>(true); // Call the `test` method on the contract via a query. let result = signer - .query_evm(&mut ctx, contract_address, "test", &[], &[]) + .query_evm(&ctx, contract_address, "test", &[], &[]) .expect("query should succeed"); let mut result = @@ -836,7 +844,7 @@ fn test_c10l_queries() { // Test call with confidential envelope. let result = signer .query_evm_opts( - &mut ctx, + &ctx, contract_address, "test", &[], @@ -858,15 +866,13 @@ fn test_c10l_queries() { #[test] fn test_fee_refunds() { let mut mock = mock::Mock::default(); - let mut ctx = - mock.create_ctx_for_runtime::>(context::Mode::ExecuteTx, true); + let ctx = mock.create_ctx_for_runtime::>(true); let mut signer = EvmSigner::new(0, keys::dave::sigspec()); - EVMRuntime::::migrate(&mut ctx); + EVMRuntime::::migrate(&ctx); // Give Dave some tokens. Accounts::mint( - &mut ctx, keys::dave::address(), &token::BaseUnits(1_000_000_000, Denomination::NATIVE), ) @@ -874,7 +880,7 @@ fn test_fee_refunds() { // Create contract. let dispatch_result = signer.call( - &mut ctx, + &ctx, "evm.Create", types::Create { value: 0.into(), @@ -887,7 +893,7 @@ fn test_fee_refunds() { // Call the `name` method on the contract. let dispatch_result = signer.call_evm_opts( - &mut ctx, + &ctx, contract_address, "name", &[], @@ -936,7 +942,7 @@ fn test_fee_refunds() { // Call the `transfer` method on the contract with invalid parameters so it reverts. let dispatch_result = signer.call_evm_opts( - &mut ctx, + &ctx, contract_address, "transfer", &[ParamType::Address, ParamType::Uint(256)], @@ -992,15 +998,13 @@ fn test_fee_refunds() { #[test] fn test_return_value_limits() { let mut mock = mock::Mock::default(); - let mut ctx = - mock.create_ctx_for_runtime::>(context::Mode::ExecuteTx, true); + let ctx = mock.create_ctx_for_runtime::>(true); let mut signer = EvmSigner::new(0, keys::dave::sigspec()); - EVMRuntime::::migrate(&mut ctx); + EVMRuntime::::migrate(&ctx); // Give Dave some tokens. Accounts::mint( - &mut ctx, keys::dave::address(), &token::BaseUnits(1_000_000_000, Denomination::NATIVE), ) @@ -1011,7 +1015,7 @@ fn test_return_value_limits() { // Create contract. let dispatch_result = signer.call( - &mut ctx, + &ctx, "evm.Create", types::Create { value: 0.into(), @@ -1024,7 +1028,7 @@ fn test_return_value_limits() { // Call the `testSuccess` method on the contract. let dispatch_result = signer.call_evm_opts( - &mut ctx, + &ctx, contract_address, "testSuccess", &[], @@ -1045,7 +1049,7 @@ fn test_return_value_limits() { // Call the `testRevert` method on the contract. let dispatch_result = signer.call_evm_opts( - &mut ctx, + &ctx, contract_address, "testRevert", &[], @@ -1075,12 +1079,11 @@ fn test_return_value_limits() { } // Make sure that in query context, the return value is not trimmed. - let mut ctx = - mock.create_ctx_for_runtime::>(context::Mode::CheckTx, true); + let ctx = mock.create_ctx_for_runtime::>(true); let result = signer .query_evm_opts( - &mut ctx, + &ctx, contract_address, "testSuccess", &[], diff --git a/runtime-sdk/src/callformat.rs b/runtime-sdk/src/callformat.rs index f738da2353..ebad3110f8 100644 --- a/runtime-sdk/src/callformat.rs +++ b/runtime-sdk/src/callformat.rs @@ -12,6 +12,7 @@ use crate::{ crypto::signature::context::get_chain_context_for, keymanager, module, modules::core::Error, + state::CurrentState, types::{ self, transaction::{Call, CallFormat, CallResult}, @@ -116,7 +117,7 @@ pub fn decode_call_ex( // If we are only doing checks, this is the most that we can do as in this case we may // be unable to access the key manager. - if !assume_km_reachable && (ctx.is_check_only() || ctx.is_simulation()) { + if !assume_km_reachable && CurrentState::with_env(|env| !env.is_execute()) { return Ok(None); } @@ -266,18 +267,20 @@ pub fn encrypt_result_x25519_deoxysii( sk: x25519::PrivateKey, index: usize, ) -> cbor::Value { - // Generate nonce for the output as Round (8 bytes) || Index (4 bytes) || 00 00 00. let mut nonce = Vec::with_capacity(deoxysii::NONCE_SIZE); - nonce - .write_u64::(ctx.runtime_header().round) - .unwrap(); - nonce - .write_u32::(index.try_into().unwrap()) - .unwrap(); - nonce.extend(&[0, 0, 0]); - if ctx.is_simulation() { - // Randomize the lower-order bytes of the nonce to facilitate private queries. - OsRng.fill_bytes(&mut nonce[deoxysii::NONCE_SIZE - 3..]); + if CurrentState::with_env(|env| env.is_execute()) { + // In execution mode generate nonce for the output as Round (8 bytes) || Index (4 bytes) || 00 00 00. + nonce + .write_u64::(ctx.runtime_header().round) + .unwrap(); + nonce + .write_u32::(index.try_into().unwrap()) + .unwrap(); + nonce.extend(&[0, 0, 0]); + } else { + // In non-execution mode randomize the nonce to facilitate private queries. + nonce.resize(deoxysii::NONCE_SIZE, 0); + OsRng.fill_bytes(&mut nonce); } let nonce = nonce.try_into().unwrap(); let result = cbor::to_vec(result); diff --git a/runtime-sdk/src/context.rs b/runtime-sdk/src/context.rs index fe6e9ece20..ceea66c26d 100644 --- a/runtime-sdk/src/context.rs +++ b/runtime-sdk/src/context.rs @@ -1,85 +1,23 @@ //! Execution context. -use std::{ - any::Any, - collections::btree_map::{BTreeMap, Entry}, - fmt, - marker::PhantomData, - ops::{Deref, DerefMut}, -}; +use std::{collections::btree_map::BTreeMap, marker::PhantomData}; use slog::{self, o}; use oasis_core_runtime::{ - common::{crypto::hash::Hash, logger::get_logger, namespace::Namespace}, + common::{logger::get_logger, namespace::Namespace}, consensus, consensus::roothash, protocol::HostInfo, }; use crate::{ - crypto::random::{LeafRng, RootRng}, - event::{Event, EventTag, EventTags}, history, keymanager::KeyManager, module::MethodHandler as _, - modules::core::Error, runtime, - types::{address::Address, message::MessageEventHookInvocation, transaction}, + state::{self, CurrentState}, }; -/// Transaction execution mode. -#[derive(Clone, Copy, Debug, PartialEq, Eq)] -#[repr(u8)] -pub enum Mode { - ExecuteTx, - CheckTx, - SimulateTx, - PreScheduleTx, -} - -const MODE_CHECK_TX: &str = "check_tx"; -const MODE_EXECUTE_TX: &str = "execute_tx"; -const MODE_SIMULATE_TX: &str = "simulate_tx"; -const MODE_PRE_SCHEDULE_TX: &str = "pre_schedule_tx"; - -impl fmt::Display for Mode { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(self.into()) - } -} - -impl From<&Mode> for &'static str { - fn from(m: &Mode) -> Self { - match m { - Mode::CheckTx => MODE_CHECK_TX, - Mode::ExecuteTx => MODE_EXECUTE_TX, - Mode::SimulateTx => MODE_SIMULATE_TX, - Mode::PreScheduleTx => MODE_PRE_SCHEDULE_TX, - } - } -} - -/// State after applying the context. -#[derive(Clone, Debug, Default)] -pub struct State { - /// Emitted event tags. - pub events: EventTags, - /// Emitted messages to consensus layer. - pub messages: Vec<(roothash::Message, MessageEventHookInvocation)>, -} - -impl State { - /// Merge a different state into this state. - pub fn merge_from(&mut self, other: State) { - for (key, event) in other.events { - let events = self.events.entry(key).or_default(); - events.extend(event); - } - - self.messages.extend(other.messages); - } -} - /// Local configuration key the value of which determines whether expensive queries should be /// allowed or not, and also whether smart contracts should be simulated for `core.EstimateGas`. /// DEPRECATED and superseded by LOCAL_CONFIG_ESTIMATE_GAS_BY_SIMULATING_CONTRACTS and LOCAL_CONFIG_ALLOWED_QUERIES. @@ -101,33 +39,18 @@ pub trait Context { /// Runtime that the context is being invoked in. type Runtime: runtime::Runtime; + /// Clone this context. + fn clone(&self) -> Self; + /// Returns a logger. fn get_logger(&self, module: &'static str) -> slog::Logger; - /// Context mode. - fn mode(&self) -> Mode; - - /// Whether the transaction is just being checked for validity. - fn is_check_only(&self) -> bool { - self.mode() == Mode::CheckTx || self.mode() == Mode::PreScheduleTx - } - - /// Whether the transaction is just being checked for validity before being scheduled. - fn is_pre_schedule(&self) -> bool { - self.mode() == Mode::PreScheduleTx - } - - /// Whether the transaction is just being simulated. - fn is_simulation(&self) -> bool { - self.mode() == Mode::SimulateTx - } - /// Whether smart contracts should be executed in this context. fn should_execute_contracts(&self) -> bool { - match self.mode() { + match CurrentState::with_env(|env| env.mode()) { // When actually executing a transaction, we always run contracts. - Mode::ExecuteTx => true, - Mode::SimulateTx => { + state::Mode::Execute => true, + state::Mode::Simulate => { // Backwards compatibility for the deprecated `allow_expensive_queries`. if let Some(allow_expensive_queries) = self.local_config::(LOCAL_CONFIG_ALLOW_EXPENSIVE_QUERIES) @@ -147,7 +70,7 @@ pub trait Context { .unwrap_or_default() } // When just checking a transaction, we always want to be fast and skip contracts. - Mode::CheckTx | Mode::PreScheduleTx => false, + state::Mode::Check | state::Mode::PreSchedule => false, } } @@ -197,7 +120,7 @@ pub trait Context { where T: cbor::Decode, { - if self.mode() == Mode::ExecuteTx { + if CurrentState::with_env(|env| env.is_execute()) { return None; } @@ -246,333 +169,23 @@ pub trait Context { /// Current epoch. fn epoch(&self) -> consensus::beacon::EpochTime; - /// Emits an event by transforming it into a tag and emitting a tag. - fn emit_event(&mut self, event: E); - - /// Emits a tag. - fn emit_etag(&mut self, etag: EventTag); - - /// Emits event tags. - fn emit_etags(&mut self, etags: EventTags); - - /// Return any emitted tags and runtime messages. It consumes the transaction context. - /// - /// # Storage - /// - /// This does not commit any storage transaction. - fn commit(self) -> State; - - /// Rollback any changes made by this context. This method only needs to be called explicitly - /// in case you want to retrieve possibly emitted unconditional events. Simply dropping the - /// context without calling `commit` will also result in a rollback. - /// - /// # Storage - /// - /// This does not rollback any storage transaction. - fn rollback(self) -> EventTags; - - /// Fetches a value entry associated with the context. - fn value(&mut self, key: &'static str) -> ContextValue<'_, V>; - - /// Number of consensus messages that can still be emitted. - fn remaining_messages(&self) -> u32; - - /// Set an upper limit on the number of consensus messages that can be emitted in this context. - /// Note that the limit can only be decreased and calling this function will return an error - /// in case the passed `max_messages` is higher than the current limit. - fn limit_max_messages(&mut self, max_messages: u32) -> Result<(), Error>; - - /// Executes a function in a child context with the given mode. - /// - /// The context collects its own messages and starts with an empty set of context values. - /// - /// # Storage - /// - /// This does not start a new storage transaction. Start a transaction and explicitly commit or - /// rollback if you want to discard storage side effects. - fn with_child(&mut self, mode: Mode, f: F) -> Rs - where - F: FnOnce(RuntimeBatchContext<'_, Self::Runtime>) -> Rs; - - /// Executes a function in a simulation context. - /// - /// The simulation context collects its own messages and starts with an empty set of context - /// values. - /// - /// # Storage - /// - /// This does not start a new storage transaction. Start a transaction and explicitly commit or - /// rollback if you want to discard storage side effects. - fn with_simulation(&mut self, f: F) -> Rs - where - F: FnOnce(RuntimeBatchContext<'_, Self::Runtime>) -> Rs, - { - self.with_child(Mode::SimulateTx, f) - } - - /// Returns a random number generator, if it is available, with optional personalization. - fn rng(&mut self, pers: &[u8]) -> Result; -} - -impl<'a, 'b, C: Context> Context for std::cell::RefMut<'a, &'b mut C> { - type Runtime = C::Runtime; - - fn get_logger(&self, module: &'static str) -> slog::Logger { - self.deref().get_logger(module) - } - - fn mode(&self) -> Mode { - self.deref().mode() - } - - fn host_info(&self) -> &HostInfo { - self.deref().host_info() - } - - fn key_manager(&self) -> Option<&dyn KeyManager> { - self.deref().key_manager() - } - - fn runtime_header(&self) -> &roothash::Header { - self.deref().runtime_header() - } - - fn runtime_round_results(&self) -> &roothash::RoundResults { - self.deref().runtime_round_results() - } - - fn consensus_state(&self) -> &consensus::state::ConsensusState { - self.deref().consensus_state() - } - - fn history(&self) -> &dyn history::HistoryHost { - self.deref().history() - } - - fn epoch(&self) -> consensus::beacon::EpochTime { - self.deref().epoch() - } - - fn emit_event(&mut self, event: E) { - self.deref_mut().emit_event(event); - } - - fn emit_etag(&mut self, etag: EventTag) { - self.deref_mut().emit_etag(etag); - } - - fn emit_etags(&mut self, etags: EventTags) { - self.deref_mut().emit_etags(etags); - } - - fn commit(self) -> State { - unimplemented!() - } - - fn rollback(self) -> EventTags { - unimplemented!() - } - - fn value(&mut self, key: &'static str) -> ContextValue<'_, V> { - self.deref_mut().value(key) - } - - fn remaining_messages(&self) -> u32 { - self.deref().remaining_messages() - } - - fn limit_max_messages(&mut self, max_messages: u32) -> Result<(), Error> { - self.deref_mut().limit_max_messages(max_messages) - } - - fn with_child(&mut self, mode: Mode, f: F) -> Rs - where - F: FnOnce(RuntimeBatchContext<'_, Self::Runtime>) -> Rs, - { - self.deref_mut().with_child(mode, f) - } - - fn rng(&mut self, pers: &[u8]) -> Result { - self.deref_mut().rng(pers) - } -} - -/// Decoded transaction with additional metadata. -#[derive(Clone)] -pub struct TransactionWithMeta { - /// Decoded transaction. - pub tx: transaction::Transaction, - /// Transaction size. - pub tx_size: u32, - /// Transaction index within the batch. - pub tx_index: usize, - /// Transaction hash. - pub tx_hash: Hash, -} - -impl TransactionWithMeta { - /// Create transaction with metadata for an internally generated transaction. - /// - /// Internally generated transactions have zero size, index and hash. - pub fn internal(tx: transaction::Transaction) -> Self { - Self { - tx, - tx_size: 0, - tx_index: 0, - tx_hash: Default::default(), - } - } -} - -#[cfg(any(test, feature = "test"))] -impl From for TransactionWithMeta { - fn from(tx: transaction::Transaction) -> Self { - Self::internal(tx) // For use in tests. - } -} - -/// Runtime SDK batch-wide context. -pub trait BatchContext: Context { - /// Executes a function in a per-transaction context. - fn with_tx(&mut self, tx: TransactionWithMeta, f: F) -> Rs - where - F: FnOnce(RuntimeTxContext<'_, '_, ::Runtime>, transaction::Call) -> Rs; - - /// Emit consensus messages. - fn emit_messages( - &mut self, - msgs: Vec<(roothash::Message, MessageEventHookInvocation)>, - ) -> Result<(), Error>; -} - -/// Runtime SDK transaction context. -pub trait TxContext: Context { - /// The index of the transaction in the batch. - fn tx_index(&self) -> usize; - - /// Transaction size in bytes. - fn tx_size(&self) -> u32; - - /// Transaction authentication information. - fn tx_auth_info(&self) -> &transaction::AuthInfo; - - /// The transaction's call format. - fn tx_call_format(&self) -> transaction::CallFormat; - - /// Whether the call is read-only and must not make any storage modifications. - fn is_read_only(&self) -> bool; - - /// Whether the transaction is internally generated (e.g. by another module in the SDK as a - /// subcall to a different module). - fn is_internal(&self) -> bool; - - /// Mark this context as part of an internally generated transaction (e.g. a subcall). - fn internal(self) -> Self; - - /// Authenticated address of the caller. - /// - /// In case there are multiple signers of a transaction, this will return the address - /// corresponding to the first signer. - fn tx_caller_address(&self) -> Address { - self.tx_auth_info().signer_info[0].address_spec.address() - } - - /// Fetches an entry pointing to a value associated with the transaction. - fn tx_value(&mut self, key: &'static str) -> ContextValue<'_, V>; - - /// Emit a consensus message. - fn emit_message( - &mut self, - msg: roothash::Message, - hook: MessageEventHookInvocation, - ) -> Result<(), Error>; - - /// Similar as `emit_event` but the event will persist even in case the transaction that owns - /// this context fails. - fn emit_unconditional_event(&mut self, event: E); -} - -impl<'a, 'b, C: TxContext> TxContext for std::cell::RefMut<'a, &'b mut C> { - fn tx_index(&self) -> usize { - self.deref().tx_index() - } - - fn tx_size(&self) -> u32 { - self.deref().tx_size() - } - - fn tx_auth_info(&self) -> &transaction::AuthInfo { - self.deref().tx_auth_info() - } - - fn tx_call_format(&self) -> transaction::CallFormat { - self.deref().tx_call_format() - } - - fn is_read_only(&self) -> bool { - self.deref().is_read_only() - } - - fn is_internal(&self) -> bool { - self.deref().is_internal() - } - - fn internal(self) -> Self { - unimplemented!() - } - - fn tx_caller_address(&self) -> Address { - self.deref().tx_caller_address() - } - - fn tx_value(&mut self, key: &'static str) -> ContextValue<'_, V> { - self.deref_mut().tx_value(key) - } - - fn emit_message( - &mut self, - msg: roothash::Message, - hook: MessageEventHookInvocation, - ) -> Result<(), Error> { - self.deref_mut().emit_message(msg, hook) - } - - fn emit_unconditional_event(&mut self, event: E) { - self.deref_mut().emit_unconditional_event(event) - } + /// Maximum number of consensus messages that the runtime can emit in this block. + fn max_messages(&self) -> u32; } /// Dispatch context for the whole batch. pub struct RuntimeBatchContext<'a, R: runtime::Runtime> { - mode: Mode, - host_info: &'a HostInfo, key_manager: Option>, runtime_header: &'a roothash::Header, runtime_round_results: &'a roothash::RoundResults, - // TODO: linked consensus layer block consensus_state: &'a consensus::state::ConsensusState, history: &'a dyn history::HistoryHost, epoch: consensus::beacon::EpochTime, logger: slog::Logger, - /// Whether this context is part of an existing transaction (e.g. a subcall). - internal: bool, - - /// Block emitted event tags. Events are aggregated by tag key, the value - /// is a list of all emitted event values. - block_etags: EventTags, - /// Maximum number of messages that can be emitted. max_messages: u32, - /// Emitted messages. - messages: Vec<(roothash::Message, MessageEventHookInvocation)>, - - /// Per-context values. - values: BTreeMap<&'static str, Box>, - - /// A reference to the root RNG. - rng: &'a RootRng, _runtime: PhantomData, } @@ -581,7 +194,6 @@ impl<'a, R: runtime::Runtime> RuntimeBatchContext<'a, R> { /// Create a new dispatch context. #[allow(clippy::too_many_arguments)] pub fn new( - mode: Mode, host_info: &'a HostInfo, key_manager: Option>, runtime_header: &'a roothash::Header, @@ -589,11 +201,9 @@ impl<'a, R: runtime::Runtime> RuntimeBatchContext<'a, R> { consensus_state: &'a consensus::state::ConsensusState, history: &'a dyn history::HistoryHost, epoch: consensus::beacon::EpochTime, - rng: &'a RootRng, max_messages: u32, ) -> Self { Self { - mode, host_info, runtime_header, runtime_round_results, @@ -601,296 +211,35 @@ impl<'a, R: runtime::Runtime> RuntimeBatchContext<'a, R> { history, epoch, key_manager, - logger: get_logger("runtime-sdk") - .new(o!("ctx" => "dispatch", "mode" => Into::<&'static str>::into(&mode))), - internal: false, - block_etags: EventTags::new(), + logger: get_logger("runtime-sdk"), max_messages, - messages: Vec::new(), - values: BTreeMap::new(), - rng, _runtime: PhantomData, } } - - /// Executes a function in a child context in pre-schedule mode. - /// - /// The context collects its own messages and starts with an empty set of context values. - /// - /// # Random Number Generator - /// - /// The pre-schedule context has the random number generator disabled and any attempts to obtain - /// a leaf RNG will result in an error. - /// - /// # Storage - /// - /// This does not start a new storage transaction. Start a transaction and explicitly commit or - /// rollback if you want to discard storage side effects. - pub(crate) fn with_pre_schedule(&mut self, f: F) -> Rs - where - F: FnOnce(RuntimeBatchContext<'_, ::Runtime>) -> Rs, - { - // Use an invalid RNG as its use is not allowed in pre-schedule context. - let rng = RootRng::invalid(); - - let child_ctx = RuntimeBatchContext { - mode: Mode::PreScheduleTx, - host_info: self.host_info, - key_manager: self.key_manager.clone(), - runtime_header: self.runtime_header, - runtime_round_results: self.runtime_round_results, - consensus_state: self.consensus_state, - history: self.history, - epoch: self.epoch, - logger: self.logger.clone(), - internal: self.internal, - block_etags: EventTags::new(), - max_messages: self.remaining_messages(), - messages: Vec::new(), - values: BTreeMap::new(), - rng: &rng, - _runtime: PhantomData, - }; - f(child_ctx) - } } impl<'a, R: runtime::Runtime> Context for RuntimeBatchContext<'a, R> { type Runtime = R; - fn get_logger(&self, module: &'static str) -> slog::Logger { - self.logger.new(o!("sdk_module" => module)) - } - - fn mode(&self) -> Mode { - self.mode - } - - fn host_info(&self) -> &HostInfo { - self.host_info - } - - fn key_manager(&self) -> Option<&dyn KeyManager> { - self.key_manager.as_ref().map(Box::as_ref) - } - - fn runtime_header(&self) -> &roothash::Header { - self.runtime_header - } - - fn runtime_round_results(&self) -> &roothash::RoundResults { - self.runtime_round_results - } - - fn consensus_state(&self) -> &consensus::state::ConsensusState { - self.consensus_state - } - - fn history(&self) -> &dyn history::HistoryHost { - self.history - } - - fn epoch(&self) -> consensus::beacon::EpochTime { - self.epoch - } - - fn emit_event(&mut self, event: E) { - let etag = event.into_event_tag(); - let tag = self.block_etags.entry(etag.key).or_default(); - tag.push(etag.value); - } - - fn emit_etag(&mut self, etag: EventTag) { - let tag = self.block_etags.entry(etag.key).or_default(); - tag.push(etag.value); - } - - fn emit_etags(&mut self, etags: EventTags) { - for (key, val) in etags { - let tag = self.block_etags.entry(key).or_default(); - tag.extend(val) - } - } - - fn commit(self) -> State { - State { - events: self.block_etags, - messages: self.messages, - } - } - - fn rollback(self) -> EventTags { - EventTags::new() - } - - fn value(&mut self, key: &'static str) -> ContextValue<'_, V> { - ContextValue::new(self.values.entry(key)) - } - - fn remaining_messages(&self) -> u32 { - self.max_messages.saturating_sub(self.messages.len() as u32) - } - - fn limit_max_messages(&mut self, max_messages: u32) -> Result<(), Error> { - if max_messages > self.max_messages { - return Err(Error::OutOfMessageSlots); - } - - self.max_messages = max_messages; - Ok(()) - } - - fn with_child(&mut self, mode: Mode, f: F) -> Rs - where - F: FnOnce(RuntimeBatchContext<'_, Self::Runtime>) -> Rs, - { - // Update RNG state to include entering this subcontext. - self.rng.append_subcontext(); - - let child_ctx = RuntimeBatchContext { - mode, + fn clone(&self) -> Self { + Self { host_info: self.host_info, - key_manager: self.key_manager.clone(), runtime_header: self.runtime_header, runtime_round_results: self.runtime_round_results, consensus_state: self.consensus_state, history: self.history, epoch: self.epoch, - logger: self - .logger - .new(o!("ctx" => "dispatch", "mode" => Into::<&'static str>::into(&mode))), - internal: self.internal, - block_etags: EventTags::new(), - max_messages: match mode { - Mode::SimulateTx => self.max_messages, - _ => self.remaining_messages(), - }, - messages: Vec::new(), - values: BTreeMap::new(), - rng: self.rng, - _runtime: PhantomData, - }; - f(child_ctx) - } - - fn rng(&mut self, pers: &[u8]) -> Result { - self.rng.fork(self, pers) - } -} - -impl<'a, R: runtime::Runtime> BatchContext for RuntimeBatchContext<'a, R> { - fn with_tx(&mut self, tm: TransactionWithMeta, f: F) -> Rs - where - F: FnOnce(RuntimeTxContext<'_, '_, ::Runtime>, transaction::Call) -> Rs, - { - // Update RNG state to include entering this transaction context. - self.rng.append_tx(tm.tx_hash); - - let tx_ctx = RuntimeTxContext { - mode: self.mode, - host_info: self.host_info, key_manager: self.key_manager.clone(), - runtime_header: self.runtime_header, - runtime_round_results: self.runtime_round_results, - consensus_state: self.consensus_state, - history: self.history, - epoch: self.epoch, - logger: self - .logger - .new(o!("ctx" => "transaction", "mode" => Into::<&'static str>::into(&self.mode))), - tx_index: tm.tx_index, - tx_size: tm.tx_size, - tx_auth_info: tm.tx.auth_info, - tx_call_format: tm.tx.call.format, - read_only: tm.tx.call.read_only, - internal: self.internal, - etags: BTreeMap::new(), - etags_unconditional: BTreeMap::new(), - max_messages: self.remaining_messages(), - messages: Vec::new(), - values: &mut self.values, - tx_values: BTreeMap::new(), - rng: self.rng, + logger: get_logger("runtime-sdk"), + max_messages: self.max_messages, _runtime: PhantomData, - }; - f(tx_ctx, tm.tx.call) - } - - fn emit_messages( - &mut self, - msgs: Vec<(roothash::Message, MessageEventHookInvocation)>, - ) -> Result<(), Error> { - if self.messages.len() + msgs.len() > self.max_messages as usize { - return Err(Error::OutOfMessageSlots); } - - self.messages.extend(msgs); - - Ok(()) } -} - -/// Per-transaction/method dispatch sub-context. -pub struct RuntimeTxContext<'round, 'store, R: runtime::Runtime> { - mode: Mode, - - host_info: &'round HostInfo, - key_manager: Option>, - runtime_header: &'round roothash::Header, - runtime_round_results: &'round roothash::RoundResults, - consensus_state: &'round consensus::state::ConsensusState, - history: &'round dyn history::HistoryHost, - epoch: consensus::beacon::EpochTime, - // TODO: linked consensus layer block - logger: slog::Logger, - - /// The index of the transaction in the block. - tx_index: usize, - /// Transaction size. - tx_size: u32, - /// Transaction authentication info. - tx_auth_info: transaction::AuthInfo, - /// The transaction call format (as received, before decoding by the dispatcher). - tx_call_format: transaction::CallFormat, - /// Whether the call is read-only and must not make any storage modifications. - read_only: bool, - /// Whether this context is part of an existing transaction (e.g. a subcall). - internal: bool, - - /// Emitted event tags. Events are aggregated by tag key, the value - /// is a list of all emitted event values. - etags: EventTags, - /// Emitted unconditional event tags. - etags_unconditional: EventTags, - - /// Maximum number of messages that can be emitted. - max_messages: u32, - /// Emitted messages and respective event hooks. - messages: Vec<(roothash::Message, MessageEventHookInvocation)>, - - /// Per-context values. - values: &'store mut BTreeMap<&'static str, Box>, - - /// Per-transaction values. - tx_values: BTreeMap<&'static str, Box>, - - /// The RNG associated with the context. - rng: &'round RootRng, - - _runtime: PhantomData, -} - -impl<'round, 'store, R: runtime::Runtime> Context for RuntimeTxContext<'round, 'store, R> { - type Runtime = R; fn get_logger(&self, module: &'static str) -> slog::Logger { self.logger.new(o!("sdk_module" => module)) } - fn mode(&self) -> Mode { - self.mode - } - fn host_info(&self) -> &HostInfo { self.host_info } @@ -919,566 +268,7 @@ impl<'round, 'store, R: runtime::Runtime> Context for RuntimeTxContext<'round, ' self.epoch } - fn emit_event(&mut self, event: E) { - let etag = event.into_event_tag(); - let tag = self.etags.entry(etag.key).or_default(); - tag.push(etag.value); - } - - fn emit_etag(&mut self, etag: EventTag) { - let tag = self.etags.entry(etag.key).or_default(); - tag.push(etag.value); - } - - fn emit_etags(&mut self, etags: EventTags) { - for (key, val) in etags { - let tag = self.etags.entry(key).or_default(); - tag.extend(val) - } - } - - fn commit(mut self) -> State { - // Merge unconditional events into regular events on success. - for (key, val) in self.etags_unconditional { - let tag = self.etags.entry(key).or_default(); - tag.extend(val) - } - - State { - events: self.etags, - messages: self.messages, - } - } - - fn rollback(self) -> EventTags { - self.etags_unconditional - } - - fn value(&mut self, key: &'static str) -> ContextValue<'_, V> { - ContextValue::new(self.values.entry(key)) - } - - fn remaining_messages(&self) -> u32 { - self.max_messages.saturating_sub(self.messages.len() as u32) - } - - fn limit_max_messages(&mut self, max_messages: u32) -> Result<(), Error> { - if max_messages > self.max_messages { - return Err(Error::OutOfMessageSlots); - } - - self.max_messages = max_messages; - Ok(()) - } - - fn with_child(&mut self, mode: Mode, f: F) -> Rs - where - F: FnOnce(RuntimeBatchContext<'_, Self::Runtime>) -> Rs, - { - // Update RNG state to include entering this subcontext. - self.rng.append_subcontext(); - - let child_ctx = RuntimeBatchContext { - mode, - host_info: self.host_info, - key_manager: self.key_manager.clone(), - runtime_header: self.runtime_header, - runtime_round_results: self.runtime_round_results, - consensus_state: self.consensus_state, - history: self.history, - epoch: self.epoch, - logger: self - .logger - .new(o!("ctx" => "dispatch", "mode" => Into::<&'static str>::into(&mode))), - internal: self.internal, - block_etags: EventTags::new(), - max_messages: match mode { - Mode::SimulateTx => self.max_messages, - _ => self.remaining_messages(), - }, - messages: Vec::new(), - values: BTreeMap::new(), - rng: self.rng, - _runtime: PhantomData, - }; - f(child_ctx) - } - - fn rng(&mut self, pers: &[u8]) -> Result { - self.rng.fork(self, pers) - } -} - -impl TxContext for RuntimeTxContext<'_, '_, R> { - fn tx_index(&self) -> usize { - self.tx_index - } - - fn tx_size(&self) -> u32 { - self.tx_size - } - - fn tx_call_format(&self) -> transaction::CallFormat { - self.tx_call_format - } - - fn tx_auth_info(&self) -> &transaction::AuthInfo { - &self.tx_auth_info - } - - fn is_read_only(&self) -> bool { - self.read_only - } - - fn is_internal(&self) -> bool { - self.internal - } - - fn internal(mut self) -> Self { - self.internal = true; - self - } - - fn tx_value(&mut self, key: &'static str) -> ContextValue<'_, V> { - ContextValue::new(self.tx_values.entry(key)) - } - - fn emit_message( - &mut self, - msg: roothash::Message, - hook: MessageEventHookInvocation, - ) -> Result<(), Error> { - // Check against maximum number of messages that can be emitted per round. - if self.messages.len() >= self.max_messages as usize { - return Err(Error::OutOfMessageSlots); - } - - self.messages.push((msg, hook)); - - Ok(()) - } - - fn emit_unconditional_event(&mut self, event: E) { - let etag = event.into_event_tag(); - let tag = self.etags_unconditional.entry(etag.key).or_default(); - tag.push(etag.value); - } -} - -/// A per-context arbitrary value. -pub struct ContextValue<'a, V> { - inner: Entry<'a, &'static str, Box>, - _value: PhantomData, -} - -impl<'a, V: Any> ContextValue<'a, V> { - fn new(inner: Entry<'a, &'static str, Box>) -> Self { - Self { - inner, - _value: PhantomData, - } - } - - /// Gets a reference to the specified per-context value. - /// - /// # Panics - /// - /// Panics if the retrieved type is not the type that was stored. - pub fn get(self) -> Option<&'a V> { - match self.inner { - Entry::Occupied(oe) => Some( - oe.into_mut() - .downcast_ref() - .expect("type should stay the same"), - ), - _ => None, - } - } - - /// Gets a mutable reference to the specified per-context value. - /// - /// # Panics - /// - /// Panics if the retrieved type is not the type that was stored. - pub fn get_mut(&mut self) -> Option<&mut V> { - match &mut self.inner { - Entry::Occupied(oe) => Some( - oe.get_mut() - .downcast_mut() - .expect("type should stay the same"), - ), - _ => None, - } - } - - /// Sets the context value, returning a mutable reference to the set value. - /// - /// # Panics - /// - /// Panics if the retrieved type is not the type that was stored. - pub fn set(self, value: V) -> &'a mut V { - let value = Box::new(value); - match self.inner { - Entry::Occupied(mut oe) => { - oe.insert(value); - oe.into_mut() - } - Entry::Vacant(ve) => ve.insert(value), - } - .downcast_mut() - .expect("type should stay the same") - } - - /// Takes the context value, if it exists. - /// - /// # Panics - /// - /// Panics if the retrieved type is not the type that was stored. - pub fn take(self) -> Option { - match self.inner { - Entry::Occupied(oe) => { - Some(*oe.remove().downcast().expect("type should stay the same")) - } - Entry::Vacant(_) => None, - } - } -} - -impl<'a, V: Any + Default> ContextValue<'a, V> { - /// Retrieves the existing value or inserts and returns the default. - /// - /// # Panics - /// - /// Panics if the retrieved type is not the type that was stored. - pub fn or_default(self) -> &'a mut V { - match self.inner { - Entry::Occupied(oe) => oe.into_mut(), - Entry::Vacant(ve) => ve.insert(Box::::default()), - } - .downcast_mut() - .expect("type should stay the same") - } -} - -#[cfg(test)] -#[allow(clippy::many_single_char_names)] -mod test { - use oasis_core_runtime::{common::versioned::Versioned, consensus::staking}; - - use super::*; - use crate::testing::{mock, mock::Mock}; - - #[test] - fn test_value() { - let mut mock = Mock::default(); - let mut ctx = mock.create_ctx(); - - let x = ctx.value::("module.TestKey").get(); - assert_eq!(x, None); - - ctx.value::("module.TestKey").set(42); - - let y = ctx.value::("module.TestKey").get(); - assert_eq!(y, Some(&42u64)); - - let z = ctx.value::("module.TestKey").take(); - assert_eq!(z, Some(42u64)); - - let y = ctx.value::("module.TestKey").get(); - assert_eq!(y, None); - } - - #[test] - #[should_panic] - fn test_value_type_change() { - let mut mock = Mock::default(); - let mut ctx = mock.create_ctx(); - - ctx.value::("module.TestKey").or_default(); - ctx.value::("module.TestKey").get(); - } - - #[test] - fn test_value_child_context_no_propagation() { - let mut mock = Mock::default(); - let mut ctx = mock.create_ctx(); - - ctx.value("module.TestKey").set(42u64); - - ctx.with_tx(mock::transaction().into(), |mut tx_ctx, _call| { - // Create a child context inside a transaction context and make sure that it starts - // with an empty set of values and that modifications are not propagated. - tx_ctx.with_child(tx_ctx.mode(), |mut child_ctx| { - let v = child_ctx.value::("module.TestKey").get(); - assert!(v.is_none(), "values should not propagate to child context"); - - child_ctx.value("module.TestKey").set(48u64); - }); - - let v = tx_ctx.value::("module.TestKey").get(); - assert_eq!( - v, - Some(&42u64), - "values should not propagate outside child context" - ); - }); - } - - #[test] - fn test_value_tx_context() { - let mut mock = Mock::default(); - let mut ctx = mock.create_ctx(); - - ctx.value("module.TestKey").set(42u64); - - let tx = transaction::Transaction { - version: 1, - call: transaction::Call { - format: transaction::CallFormat::Plain, - method: "test".to_owned(), - ..Default::default() - }, - auth_info: transaction::AuthInfo { - signer_info: vec![], - fee: transaction::Fee { - amount: Default::default(), - gas: 1000, - consensus_messages: 0, - }, - ..Default::default() - }, - }; - ctx.with_tx(tx.clone().into(), |mut tx_ctx, _call| { - let mut y = tx_ctx.value::("module.TestKey"); - let y = y.get_mut().unwrap(); - assert_eq!(*y, 42); - *y = 48; - - let a = tx_ctx.tx_value::("module.TestTxKey").get(); - assert_eq!(a, None); - tx_ctx.tx_value::("module.TestTxKey").set(65); - - let b = tx_ctx.tx_value::("module.TestTxKey").get(); - assert_eq!(b, Some(&65)); - - let c = tx_ctx.tx_value::("module.TestTakeTxKey").or_default(); - *c = 67; - let d = tx_ctx.tx_value::("module.TestTakeTxKey").take(); - assert_eq!(d, Some(67)); - let e = tx_ctx.tx_value::("module.TestTakeTxKey").get(); - assert_eq!(e, None); - }); - - let x = ctx.value::("module.TestKey").get(); - assert_eq!(x, Some(&48)); - - ctx.with_tx(tx.into(), |mut tx_ctx, _call| { - let z = tx_ctx.value::("module.TestKey").take(); - assert_eq!(z, Some(48)); - - let a = tx_ctx.tx_value::("module.TestTxKey").get(); - assert_eq!(a, None); - }); - - let y = ctx.value::("module.TestKey").get(); - assert_eq!(y, None); - } - - #[test] - #[should_panic] - fn test_value_tx_context_type_change() { - let mut mock = Mock::default(); - let mut ctx = mock.create_ctx(); - - let x = ctx.value::("module.TestKey").set(0); - *x = 42; - - let tx = transaction::Transaction { - version: 1, - call: transaction::Call { - format: transaction::CallFormat::Plain, - method: "test".to_owned(), - ..Default::default() - }, - auth_info: transaction::AuthInfo { - signer_info: vec![], - fee: transaction::Fee { - amount: Default::default(), - gas: 1000, - consensus_messages: 0, - }, - ..Default::default() - }, - }; - ctx.with_tx(tx.into(), |mut tx_ctx, _call| { - // Changing the type of a key should result in a panic. - tx_ctx.value::>("module.TestKey").get(); - }); - } - - #[test] - fn test_ctx_message_slots() { - let mut mock = Mock::default(); - let max_messages = mock.max_messages; - let mut ctx = mock.create_ctx(); - - let mut messages = Vec::with_capacity(max_messages as usize); - for _ in 0..max_messages { - messages.push(( - roothash::Message::Staking(Versioned::new( - 0, - roothash::StakingMessage::Transfer(staking::Transfer::default()), - )), - MessageEventHookInvocation::new("test".to_string(), ""), - )) - } - - // Emitting messages should work. - ctx.emit_messages(messages.clone()) - .expect("message emitting should work"); - - assert_eq!(ctx.remaining_messages(), 0); - - // Emitting more messages should fail. - ctx.emit_messages(messages) - .expect_err("message emitting should fail"); - - assert_eq!(ctx.remaining_messages(), 0); - } - - #[test] - fn test_tx_ctx_message_slots() { - let mut mock = Mock::default(); - let max_messages = mock.max_messages; - let mut ctx = mock.create_ctx(); - - ctx.with_tx(mock::transaction().into(), |mut tx_ctx, _call| { - for i in 0..max_messages { - assert_eq!(tx_ctx.remaining_messages(), max_messages - i); - - tx_ctx - .emit_message( - roothash::Message::Staking(Versioned::new( - 0, - roothash::StakingMessage::Transfer(staking::Transfer::default()), - )), - MessageEventHookInvocation::new("test".to_string(), ""), - ) - .expect("message should be emitted"); - - assert_eq!(tx_ctx.remaining_messages(), max_messages - i - 1); - } - - // Another message should error. - tx_ctx - .emit_message( - roothash::Message::Staking(Versioned::new( - 0, - roothash::StakingMessage::Transfer(staking::Transfer::default()), - )), - MessageEventHookInvocation::new("test".to_string(), ""), - ) - .expect_err("message emitting should fail"); - - assert_eq!(tx_ctx.remaining_messages(), 0); - }); - } - - #[test] - fn test_ctx_message_slot_limits() { - let mut mock = Mock::default(); - let max_messages = mock.max_messages; - let mut ctx = mock.create_ctx(); - - // Increasing the limit should fail. - assert_eq!(ctx.remaining_messages(), max_messages); - ctx.limit_max_messages(max_messages * 2) - .expect_err("increasing the max message limit should fail"); - assert_eq!(ctx.remaining_messages(), max_messages); - - // Limiting to a single message should work. - ctx.limit_max_messages(1) - .expect("limiting max_messages should work"); - assert_eq!(ctx.remaining_messages(), 1); - - let messages = vec![( - roothash::Message::Staking(Versioned::new( - 0, - roothash::StakingMessage::Transfer(staking::Transfer::default()), - )), - MessageEventHookInvocation::new("test".to_string(), ""), - )]; - - // Emitting messages should work. - ctx.emit_messages(messages.clone()) - .expect("emitting a message should work"); - assert_eq!(ctx.remaining_messages(), 0); - - // Emitting more messages should fail (we set the limit to a single message). - ctx.emit_messages(messages.clone()) - .expect_err("emitting a message should fail"); - assert_eq!(ctx.remaining_messages(), 0); - - // Also in transaction contexts. - ctx.with_tx(mock::transaction().into(), |mut tx_ctx, _call| { - tx_ctx - .emit_message(messages[0].0.clone(), messages[0].1.clone()) - .expect_err("emitting a message should fail"); - assert_eq!(tx_ctx.remaining_messages(), 0); - }); - - // Also in child contexts. - ctx.with_child(Mode::ExecuteTx, |mut child_ctx| { - child_ctx - .emit_messages(messages.clone()) - .expect_err("emitting a message should fail"); - assert_eq!(child_ctx.remaining_messages(), 0); - }); - } - - #[test] - fn test_tx_ctx_message_slot_limits() { - let mut mock = Mock::default(); - let mut ctx = mock.create_ctx(); - - let messages = vec![( - roothash::Message::Staking(Versioned::new( - 0, - roothash::StakingMessage::Transfer(staking::Transfer::default()), - )), - MessageEventHookInvocation::new("test".to_string(), ""), - )]; - - ctx.with_tx(mock::transaction().into(), |mut tx_ctx, _call| { - tx_ctx.limit_max_messages(1).unwrap(); - - tx_ctx.with_child(tx_ctx.mode(), |mut child_ctx| { - child_ctx - .emit_messages(messages.clone()) - .expect("emitting a message should work"); - - child_ctx - .emit_messages(messages.clone()) - .expect_err("emitting another message should fail"); - }); - }); - } - - #[test] - fn test_tx_ctx_metadata() { - let mut mock = Mock::default(); - let mut ctx = mock.create_ctx(); - ctx.with_tx( - TransactionWithMeta { - tx: mock::transaction(), - tx_size: 888, - tx_index: 42, - tx_hash: Default::default(), - }, - |tx_ctx, _call| { - assert_eq!(tx_ctx.tx_index(), 42); - assert_eq!(tx_ctx.tx_size(), 888); - }, - ); + fn max_messages(&self) -> u32 { + self.max_messages } } diff --git a/runtime-sdk/src/crypto/random.rs b/runtime-sdk/src/crypto/random.rs index 0f6e16af12..29cc3632fe 100644 --- a/runtime-sdk/src/crypto/random.rs +++ b/runtime-sdk/src/crypto/random.rs @@ -8,7 +8,9 @@ use schnorrkel::keys::{ExpansionMode, Keypair, MiniSecretKey}; use oasis_core_runtime::common::crypto::hash::Hash; -use crate::{context::Context, dispatcher, keymanager::KeyManagerError, modules::core::Error}; +use crate::{ + context::Context, dispatcher, keymanager::KeyManagerError, modules::core::Error, state::Mode, +}; /// RNG domain separation context. const RNG_CONTEXT: &[u8] = b"oasis-runtime-sdk/crypto: rng v1"; @@ -18,6 +20,7 @@ const VRF_KEY_CONTEXT: &[u8] = b"oasis-runtime-sdk/crypto: root vrf key v1"; /// A root RNG that can be used to derive domain-separated leaf RNGs. pub struct RootRng { inner: RefCell, + mode: Mode, valid: bool, } @@ -30,12 +33,13 @@ struct Inner { impl RootRng { /// Create a new root RNG. - pub fn new() -> Self { + pub fn new(mode: Mode) -> Self { Self { inner: RefCell::new(Inner { transcript: Transcript::new(RNG_CONTEXT), rng: None, }), + mode, valid: true, } } @@ -47,11 +51,12 @@ impl RootRng { transcript: Transcript::new(&[]), rng: None, }), + mode: Mode::Simulate, // Use a "safe" mode even though it will never be used. valid: false, } } - fn derive_root_vrf_key(ctx: &C) -> Result { + fn derive_root_vrf_key(ctx: &C, mode: Mode) -> Result { let km = ctx .key_manager() .ok_or(Error::Abort(dispatcher::Error::KeyManagerFailure( @@ -60,7 +65,7 @@ impl RootRng { let round_header_hash = ctx.runtime_header().encoded_hash(); let key_id = crate::keymanager::get_key_pair_id([ VRF_KEY_CONTEXT, - &[ctx.mode() as u8], + &[mode as u8], round_header_hash.as_ref(), ]); let km_kp = km @@ -128,7 +133,7 @@ impl RootRng { // Ensure the RNG is initialized and initialize it if not. if inner.rng.is_none() { // Derive the root VRF key for the current block. - let root_vrf_key = Self::derive_root_vrf_key(ctx)?; + let root_vrf_key = Self::derive_root_vrf_key(ctx, self.mode)?; // Initialize the root RNG. let rng = root_vrf_key @@ -148,12 +153,6 @@ impl RootRng { } } -impl Default for RootRng { - fn default() -> Self { - Self::new() - } -} - /// A leaf RNG. pub struct LeafRng(TranscriptRng); @@ -181,15 +180,15 @@ impl CryptoRng for LeafRng {} mod test { use super::*; - use crate::{context::Mode, testing::mock}; + use crate::{state::Mode, testing::mock}; #[test] fn test_rng_basic() { let mut mock = mock::Mock::default(); - let ctx = mock.create_ctx_for_runtime::(Mode::ExecuteTx, true); + let ctx = mock.create_ctx_for_runtime::(true); // Create first root RNG. - let root_rng = RootRng::new(); + let root_rng = RootRng::new(Mode::Execute); let mut leaf_rng = root_rng.fork(&ctx, &[]).expect("rng fork should work"); let mut bytes1 = [0u8; 32]; @@ -202,7 +201,7 @@ mod test { assert_ne!(bytes1, bytes1_1, "rng should apply domain separation"); // Create second root RNG using the same context so the ephemeral key is shared. - let root_rng = RootRng::new(); + let root_rng = RootRng::new(Mode::Execute); let mut leaf_rng = root_rng.fork(&ctx, &[]).expect("rng fork should work"); let mut bytes2 = [0u8; 32]; @@ -218,7 +217,7 @@ mod test { assert_eq!(bytes1_1, bytes2_1, "rng should be deterministic"); // Create third root RNG using the same context, but with different personalization. - let root_rng = RootRng::new(); + let root_rng = RootRng::new(Mode::Execute); let mut leaf_rng = root_rng .fork(&ctx, b"domsep") @@ -229,7 +228,7 @@ mod test { assert_ne!(bytes2, bytes3, "rng should apply domain separation"); // Create another root RNG using the same context, but with different history. - let root_rng = RootRng::new(); + let root_rng = RootRng::new(Mode::Execute); root_rng .append_tx("0000000000000000000000000000000000000000000000000000000000000001".into()); @@ -240,7 +239,7 @@ mod test { assert_ne!(bytes2, bytes4, "rng should apply domain separation"); // Create another root RNG using the same context, but with different history. - let root_rng = RootRng::new(); + let root_rng = RootRng::new(Mode::Execute); root_rng .append_tx("0000000000000000000000000000000000000000000000000000000000000002".into()); @@ -251,7 +250,7 @@ mod test { assert_ne!(bytes4, bytes5, "rng should apply domain separation"); // Create another root RNG using the same context, but with same history as four. - let root_rng = RootRng::new(); + let root_rng = RootRng::new(Mode::Execute); root_rng .append_tx("0000000000000000000000000000000000000000000000000000000000000001".into()); @@ -262,7 +261,7 @@ mod test { assert_eq!(bytes4, bytes6, "rng should be deterministic"); // Create another root RNG using the same context, but with different history. - let root_rng = RootRng::new(); + let root_rng = RootRng::new(Mode::Execute); root_rng .append_tx("0000000000000000000000000000000000000000000000000000000000000001".into()); root_rng @@ -275,7 +274,7 @@ mod test { assert_ne!(bytes4, bytes7, "rng should apply domain separation"); // Create another root RNG using the same context, but with different init point. - let root_rng = RootRng::new(); + let root_rng = RootRng::new(Mode::Execute); root_rng .append_tx("0000000000000000000000000000000000000000000000000000000000000001".into()); let _ = root_rng.fork(&ctx, &[]).expect("rng fork should work"); // Force init. @@ -293,9 +292,9 @@ mod test { #[test] fn test_rng_fail_nonconfidential() { let mut mock = mock::Mock::default(); - let ctx = mock.create_ctx_for_runtime::(Mode::ExecuteTx, false); + let ctx = mock.create_ctx_for_runtime::(false); - let root_rng = RootRng::new(); + let root_rng = RootRng::new(Mode::Execute); assert!( root_rng.fork(&ctx, &[]).is_err(), "rng fork should fail on non-confidential runtimes" @@ -305,17 +304,17 @@ mod test { #[test] fn test_rng_local_entropy() { let mut mock = mock::Mock::default(); - let ctx = mock.create_ctx_for_runtime::(Mode::ExecuteTx, true); + let ctx = mock.create_ctx_for_runtime::(true); // Create first root RNG. - let root_rng = RootRng::new(); + let root_rng = RootRng::new(Mode::Execute); let mut leaf_rng = root_rng.fork(&ctx, &[]).expect("rng fork should work"); let mut bytes1 = [0u8; 32]; leaf_rng.fill_bytes(&mut bytes1); // Create second root RNG using the same context, but mix in local entropy. - let root_rng = RootRng::new(); + let root_rng = RootRng::new(Mode::Execute); root_rng.append_local_entropy(); let mut leaf_rng = root_rng.fork(&ctx, &[]).expect("rng fork should work"); @@ -328,10 +327,10 @@ mod test { #[test] fn test_rng_parent_fork_propagation() { let mut mock = mock::Mock::default(); - let ctx = mock.create_ctx_for_runtime::(Mode::ExecuteTx, true); + let ctx = mock.create_ctx_for_runtime::(true); // Create first root RNG. - let root_rng = RootRng::new(); + let root_rng = RootRng::new(Mode::Execute); let mut leaf_rng = root_rng.fork(&ctx, b"a").expect("rng fork should work"); let mut bytes1 = [0u8; 32]; @@ -342,7 +341,7 @@ mod test { leaf_rng.fill_bytes(&mut bytes1_1); // Create second root RNG. - let root_rng = RootRng::new(); + let root_rng = RootRng::new(Mode::Execute); let mut leaf_rng = root_rng.fork(&ctx, b"b").expect("rng fork should work"); let mut bytes2 = [0u8; 32]; @@ -361,7 +360,7 @@ mod test { #[test] fn test_rng_invalid() { let mut mock = mock::Mock::default(); - let ctx = mock.create_ctx_for_runtime::(Mode::ExecuteTx, true); + let ctx = mock.create_ctx_for_runtime::(true); let root_rng = RootRng::invalid(); assert!( diff --git a/runtime-sdk/src/dispatcher.rs b/runtime-sdk/src/dispatcher.rs index 870be3ac9c..f686c330fe 100644 --- a/runtime-sdk/src/dispatcher.rs +++ b/runtime-sdk/src/dispatcher.rs @@ -27,8 +27,7 @@ use oasis_core_runtime::{ use crate::{ callformat, - context::{BatchContext, Context, Mode, RuntimeBatchContext, TransactionWithMeta, TxContext}, - crypto::random::RootRng, + context::{Context, RuntimeBatchContext}, error::{Error as _, RuntimeError}, event::IntoTags, keymanager::{KeyManagerClient, KeyManagerError}, @@ -38,7 +37,8 @@ use crate::{ runtime::Runtime, schedule_control::ScheduleControlHost, sender::SenderMeta, - storage::{self, current::TransactionResult, CurrentStore, Prefix}, + state::{self, CurrentState, Mode, TransactionResult, TransactionWithMeta}, + storage::{self, Prefix}, types, types::transaction::{AuthProof, Transaction}, }; @@ -155,7 +155,7 @@ impl Dispatcher { /// Decode a runtime transaction. pub fn decode_tx( - ctx: &mut C, + ctx: &C, tx: &[u8], ) -> Result { // Perform any checks before decoding. @@ -186,8 +186,8 @@ impl Dispatcher { /// Run the dispatch steps inside a transaction context. This includes the before call hooks, /// the call itself and after call hooks. The after call hooks are called regardless if the call /// succeeds or not. - pub fn dispatch_tx_call( - ctx: &mut C, + pub fn dispatch_tx_call( + ctx: &C, call: types::transaction::Call, opts: &DispatchOptions<'_>, ) -> (module::CallResult, callformat::Metadata) { @@ -206,7 +206,7 @@ impl Dispatcher { }; // Make sure that a read-only call did not result in any modifications. - if read_only && CurrentStore::has_pending_updates() { + if read_only && CurrentState::with(|state| state.has_pending_store_updates()) { return ( modules::core::Error::ReadOnlyTransaction.into_call_result(), metadata, @@ -216,8 +216,8 @@ impl Dispatcher { (result, metadata) } - fn _dispatch_tx_call( - ctx: &mut C, + fn _dispatch_tx_call( + ctx: &C, call: types::transaction::Call, opts: &DispatchOptions<'_>, ) -> (module::CallResult, callformat::Metadata) { @@ -226,8 +226,7 @@ impl Dispatcher { } // Decode call based on specified call format. - let (call, call_format_metadata) = match callformat::decode_call(ctx, call, ctx.tx_index()) - { + let (call, call_format_metadata) = match callformat::decode_call(ctx, call, opts.tx_index) { Ok(Some(result)) => result, Ok(None) => { return ( @@ -259,8 +258,8 @@ impl Dispatcher { } /// Dispatch a runtime transaction in the given context with the provided options. - pub fn dispatch_tx_opts( - ctx: &mut C, + pub fn dispatch_tx_opts( + ctx: &C, tx: types::transaction::Transaction, opts: &DispatchOptions<'_>, ) -> Result { @@ -272,64 +271,55 @@ impl Dispatcher { } let tx_auth_info = tx.auth_info.clone(); let is_read_only = tx.call.read_only; + let call = tx.call.clone(); // TODO: Avoid clone. + + let result = CurrentState::with_transaction_opts( + state::Options::new().with_tx(TransactionWithMeta { + data: tx, + size: opts.tx_size, + index: opts.tx_index, + hash: opts.tx_hash, + }), + || { + let (result, call_format_metadata) = Self::dispatch_tx_call(ctx, call, opts); + if !result.is_success() || is_read_only { + // Retrieve unconditional events. + let events = CurrentState::with(|state| state.take_unconditional_events()); + + return TransactionResult::Rollback(DispatchResult::new( + result, + events.into_tags(), + call_format_metadata, + )); + } - let (result, messages) = CurrentStore::with_transaction(|| { - ctx.with_tx( - TransactionWithMeta { - tx, - tx_size: opts.tx_size, - tx_index: opts.tx_index, - tx_hash: opts.tx_hash, - }, - |mut ctx, call| { - let (result, call_format_metadata) = - Self::dispatch_tx_call(&mut ctx, call, opts); - if !result.is_success() || is_read_only { - // Retrieve unconditional events by doing an explicit rollback. - let etags = ctx.rollback(); - - return TransactionResult::Rollback(( - DispatchResult::new(result, etags.into_tags(), call_format_metadata), - Vec::new(), - )); - } - - // Load priority. - let priority = R::Core::take_priority(&mut ctx); - // Load sender metadata. - let sender_metadata = R::Core::take_sender_meta(&mut ctx); - - if ctx.is_check_only() { - // Rollback state during checks. - ctx.rollback(); - - TransactionResult::Rollback(( - DispatchResult { - result, - tags: Vec::new(), - priority, - sender_metadata, - call_format_metadata, - }, - Vec::new(), - )) - } else { - // Commit store and return emitted tags and messages. - let state = ctx.commit(); - TransactionResult::Commit(( - DispatchResult { - result, - tags: state.events.into_tags(), - priority, - sender_metadata, - call_format_metadata, - }, - state.messages, - )) - } - }, - ) - }); + // Load priority. + let priority = R::Core::take_priority(); + // Load sender metadata. + let sender_metadata = R::Core::take_sender_meta(); + + if CurrentState::with_env(|env| env.is_check_only()) { + TransactionResult::Rollback(DispatchResult { + result, + tags: Vec::new(), + priority, + sender_metadata, + call_format_metadata, + }) + } else { + // Merge normal and unconditional events. + let tags = CurrentState::with(|state| state.take_all_events().into_tags()); + + TransactionResult::Commit(DispatchResult { + result, + tags, + priority, + sender_metadata, + call_format_metadata, + }) + } + }, + ); // Run after dispatch hooks. R::Modules::after_dispatch_tx(ctx, &tx_auth_info, &result.result); @@ -339,18 +329,12 @@ impl Dispatcher { return Err(err); } - // Forward any emitted messages if we are not in check tx context. - if !ctx.is_check_only() { - ctx.emit_messages(messages) - .expect("per-tx context has already enforced the limits"); - } - Ok(result) } /// Dispatch a runtime transaction in the given context. - pub fn dispatch_tx( - ctx: &mut C, + pub fn dispatch_tx( + ctx: &C, tx_size: u32, tx: types::transaction::Transaction, tx_index: usize, @@ -367,14 +351,12 @@ impl Dispatcher { } /// Check whether the given transaction is valid. - pub fn check_tx( - ctx: &mut C, + pub fn check_tx( + ctx: &C, tx_size: u32, tx: Transaction, ) -> Result { - let dispatch = ctx.with_child(Mode::CheckTx, |mut ctx| { - Self::dispatch_tx(&mut ctx, tx_size, tx, usize::MAX) - })?; + let dispatch = Self::dispatch_tx(ctx, tx_size, tx, usize::MAX)?; match dispatch.result { module::CallResult::Ok(_) => Ok(CheckTxResult { error: Default::default(), @@ -404,8 +386,8 @@ impl Dispatcher { } /// Execute the given transaction, returning unserialized results. - pub fn execute_tx_opts( - ctx: &mut C, + pub fn execute_tx_opts( + ctx: &C, tx: Transaction, opts: &DispatchOptions<'_>, ) -> Result<(types::transaction::CallResult, Tags), Error> { @@ -420,8 +402,8 @@ impl Dispatcher { } /// Execute the given transaction. - pub fn execute_tx( - ctx: &mut C, + pub fn execute_tx( + ctx: &C, tx_size: u32, tx_hash: Hash, tx: Transaction, @@ -455,10 +437,10 @@ impl Dispatcher { } } - fn handle_last_round_messages(ctx: &mut C) -> Result<(), modules::core::Error> { + fn handle_last_round_messages(ctx: &C) -> Result<(), modules::core::Error> { let message_events = ctx.runtime_round_results().messages.clone(); - let mut handlers = CurrentStore::with(|store| { + let mut handlers = CurrentState::with_store(|store| { let store = storage::TypedStore::new(storage::PrefixStore::new( store, &modules::core::MODULE_NAME, @@ -502,7 +484,7 @@ impl Dispatcher { .map(|(idx, h)| (idx as u32, h)) .collect(); - CurrentStore::with(|store| { + CurrentState::with_store(|store| { let mut store = storage::TypedStore::new(storage::PrefixStore::new( store, &modules::core::MODULE_NAME, @@ -512,15 +494,15 @@ impl Dispatcher { } /// Process the given runtime query. - pub fn dispatch_query( - ctx: &mut C, + pub fn dispatch_query( + ctx: &C, method: &str, args: Vec, ) -> Result, RuntimeError> { let args = cbor::from_slice(&args) .map_err(|err| modules::core::Error::InvalidArgument(err.into()))?; - CurrentStore::with_transaction(|| { + CurrentState::with_transaction(|| { // Catch any panics that occur during query dispatch. let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { // Perform state migrations if required. @@ -549,7 +531,7 @@ impl Dispatcher { f: F, ) -> Result where - F: FnOnce(&mut RuntimeBatchContext<'_, R>) -> Result, RuntimeError>, + F: FnOnce(&RuntimeBatchContext<'_, R>) -> Result, RuntimeError>, { // Prepare dispatch context. let key_manager = self @@ -558,11 +540,9 @@ impl Dispatcher { // NOTE: We are explicitly allowing private key operations during execution. .map(|mgr| mgr.with_private_context()); let history = self.consensus_verifier.clone(); - let rng = RootRng::new(); let root = storage::MKVSStore::new(&mut rt_ctx.runtime_state); - let mut ctx = RuntimeBatchContext::<'_, R>::new( - Mode::ExecuteTx, + let ctx = RuntimeBatchContext::<'_, R>::new( &self.host_info, key_manager, rt_ctx.header, @@ -570,34 +550,37 @@ impl Dispatcher { &rt_ctx.consensus_state, &history, rt_ctx.epoch, - &rng, rt_ctx.max_messages, ); - CurrentStore::enter(root, || { + CurrentState::enter_opts(state::Options::new().with_mode(Mode::Execute), root, || { // Perform state migrations if required. - R::migrate(&mut ctx); + R::migrate(&ctx); // Handle last round message results. - Self::handle_last_round_messages(&mut ctx)?; + Self::handle_last_round_messages(&ctx)?; // Run begin block hooks. - R::Modules::begin_block(&mut ctx); + R::Modules::begin_block(&ctx); - let results = f(&mut ctx)?; + let results = f(&ctx)?; // Run end block hooks. - R::Modules::end_block(&mut ctx); + R::Modules::end_block(&ctx); - // Commit the context and retrieve the emitted messages. - let state = ctx.commit(); - let (messages, handlers) = state.messages.into_iter().unzip(); + // Process any emitted messages and block-level events. + let (messages, handlers, block_tags) = CurrentState::with(|state| { + let (messages, handlers) = state.take_messages().into_iter().unzip(); + let block_tags = state.take_all_events().into_tags(); + + (messages, handlers, block_tags) + }); Self::save_emitted_message_handlers(handlers); Ok(ExecuteBatchResult { results, messages, - block_tags: state.events.into_tags(), + block_tags, tx_reject_hashes: vec![], in_msgs_count: 0, // TODO: Support processing incoming messages. }) @@ -639,7 +622,7 @@ impl transaction::dispatcher::Dispatcher for Dispatche } } if prefetch_enabled { - CurrentStore::with(|store| { + CurrentState::with_store(|store| { store.prefetch_prefixes(prefixes.into_iter().collect(), R::PREFETCH_LIMIT); }) } @@ -682,7 +665,7 @@ impl transaction::dispatcher::Dispatcher for Dispatche for raw_tx in batch.drain(..) { // If we don't have enough gas for processing even the cheapest transaction // we are done. Same if we reached the runtime-imposed maximum tx count. - let remaining_gas = R::Core::remaining_batch_gas(ctx); + let remaining_gas = R::Core::remaining_batch_gas(); if remaining_gas < cfg.min_remaining_gas || new_batch.len() >= cfg.max_tx_count { @@ -708,7 +691,11 @@ impl transaction::dispatcher::Dispatcher for Dispatche continue; } // Same if we don't have enough consensus message slots. - if tx.auth_info.fee.consensus_messages > ctx.remaining_messages() { + let remaining_messages = CurrentState::with(|state| { + ctx.max_messages() + .saturating_sub(state.emitted_messages_count() as u32) + }); + if tx.auth_info.fee.consensus_messages > remaining_messages { continue; } @@ -717,10 +704,11 @@ impl transaction::dispatcher::Dispatcher for Dispatche // First run the transaction in check tx mode in a separate subcontext. If // that fails, skip and (sometimes) reject transaction. - let skip = CurrentStore::with_transaction(|| { - let result = ctx.with_pre_schedule(|mut ctx| -> Result<_, Error> { + let skip = CurrentState::with_transaction_opts( + state::Options::new().with_mode(Mode::PreSchedule), + || -> Result<_, Error> { // First authenticate the transaction to get any nonce related errors. - match R::Modules::authenticate_tx(&mut ctx, &tx) { + match R::Modules::authenticate_tx(ctx, &tx) { Err(modules::core::Error::FutureNonce) => { // Only skip transaction as it may become valid in the future. return Ok(true); @@ -731,7 +719,7 @@ impl transaction::dispatcher::Dispatcher for Dispatche Ok(_) => { // Run additional checks on the transaction. let check_result = Self::dispatch_tx_opts( - &mut ctx, + ctx, tx.clone(), &DispatchOptions { tx_size, @@ -751,10 +739,8 @@ impl transaction::dispatcher::Dispatcher for Dispatche // Skip and reject the transaction. tx_reject_hashes.push(tx_hash); Ok(true) - }); - - TransactionResult::Rollback(result) // Always rollback storage changes. - })?; + }, + )?; if skip { continue; } @@ -805,11 +791,9 @@ impl transaction::dispatcher::Dispatcher for Dispatche // Prepare dispatch context. let key_manager = self.key_manager.as_ref().map(|mgr| mgr.with_context()); let history = self.consensus_verifier.clone(); - let rng = RootRng::new(); let root = storage::MKVSStore::new(&mut rt_ctx.runtime_state); - let mut ctx = RuntimeBatchContext::<'_, R>::new( - Mode::CheckTx, + let ctx = RuntimeBatchContext::<'_, R>::new( &self.host_info, key_manager, rt_ctx.header, @@ -817,53 +801,56 @@ impl transaction::dispatcher::Dispatcher for Dispatche &rt_ctx.consensus_state, &history, rt_ctx.epoch, - &rng, rt_ctx.max_messages, ); - CurrentStore::enter(root, || { - // Perform state migrations if required. - R::migrate(&mut ctx); - - // Prefetch. - let mut txs: Vec> = Vec::with_capacity(batch.len()); - let mut prefixes: BTreeSet = BTreeSet::new(); - for tx in batch.iter() { - let tx_size = tx.len().try_into().map_err(|_| { - Error::MalformedTransactionInBatch(anyhow!("transaction too large")) - })?; - let res = match Self::decode_tx(&mut ctx, tx) { - Ok(tx) => { - if prefetch_enabled { - Self::prefetch_tx(&mut prefixes, tx.clone()).map(|_| (tx_size, tx)) - } else { - Ok((tx_size, tx)) + CurrentState::enter_opts( + state::Options::new().with_mode(state::Mode::Check), + root, + || { + // Perform state migrations if required. + R::migrate(&ctx); + + // Prefetch. + let mut txs: Vec> = Vec::with_capacity(batch.len()); + let mut prefixes: BTreeSet = BTreeSet::new(); + for tx in batch.iter() { + let tx_size = tx.len().try_into().map_err(|_| { + Error::MalformedTransactionInBatch(anyhow!("transaction too large")) + })?; + let res = match Self::decode_tx(&ctx, tx) { + Ok(tx) => { + if prefetch_enabled { + Self::prefetch_tx(&mut prefixes, tx.clone()).map(|_| (tx_size, tx)) + } else { + Ok((tx_size, tx)) + } } - } - Err(err) => Err(err.into()), - }; - txs.push(res); - } - if prefetch_enabled { - CurrentStore::with(|store| { - store.prefetch_prefixes(prefixes.into_iter().collect(), R::PREFETCH_LIMIT); - }); - } + Err(err) => Err(err.into()), + }; + txs.push(res); + } + if prefetch_enabled { + CurrentState::with_store(|store| { + store.prefetch_prefixes(prefixes.into_iter().collect(), R::PREFETCH_LIMIT); + }); + } - // Check the batch. - let mut results = Vec::with_capacity(batch.len()); - for tx in txs.into_iter() { - match tx { - Ok((tx_size, tx)) => results.push(Self::check_tx(&mut ctx, tx_size, tx)?), - Err(err) => results.push(CheckTxResult { - error: err, - meta: None, - }), + // Check the batch. + let mut results = Vec::with_capacity(batch.len()); + for tx in txs.into_iter() { + match tx { + Ok((tx_size, tx)) => results.push(Self::check_tx(&ctx, tx_size, tx)?), + Err(err) => results.push(CheckTxResult { + error: err, + meta: None, + }), + } } - } - Ok(results) - }) + Ok(results) + }, + ) } fn set_abort_batch_flag(&mut self, _abort_batch: Arc) { @@ -899,17 +886,11 @@ impl transaction::dispatcher::Dispatcher for Dispatche } }); - // Initialize the root RNG. For queries which don't need to be deterministic as they are - // node-local we mix in local (private) entropy. - let rng = RootRng::new(); - rng.append_local_entropy(); - // Prepare dispatch context. let history = self.consensus_verifier.clone(); let root = storage::MKVSStore::new(&mut rt_ctx.runtime_state); - let mut ctx = RuntimeBatchContext::<'_, R>::new( - Mode::CheckTx, + let ctx = RuntimeBatchContext::<'_, R>::new( &self.host_info, key_manager, rt_ctx.header, @@ -917,11 +898,16 @@ impl transaction::dispatcher::Dispatcher for Dispatche &rt_ctx.consensus_state, &history, rt_ctx.epoch, - &rng, rt_ctx.max_messages, ); - CurrentStore::enter(root, || Self::dispatch_query(&mut ctx, method, args)) + CurrentState::enter_opts( + state::Options::new() + .with_mode(state::Mode::Check) + .with_rng_local_entropy(), // Mix in local (private) entropy for queries. + root, + || Self::dispatch_query(&ctx, method, args), + ) } } @@ -933,7 +919,8 @@ mod test { module::Module, modules::core, sdk_derive, - storage::{CurrentStore, Store}, + state::{CurrentState, Options}, + storage::Store, testing::{configmap, keys, mock::Mock}, types::{token, transaction}, Version, @@ -964,34 +951,34 @@ mod test { type Genesis = (); #[handler(call = "alphabet.ReadOnly")] - fn read_only(_ctx: &mut C, _args: ()) -> Result { - CurrentStore::with(|store| { + fn read_only(_ctx: &C, _args: ()) -> Result { + CurrentState::with_store(|store| { let _ = store.get(b"key"); // Read something and ignore result. }); Ok(42) } #[handler(call = "alphabet.NotReadOnly")] - fn not_read_only(_ctx: &mut C, _args: ()) -> Result { - CurrentStore::with(|store| { + fn not_read_only(_ctx: &C, _args: ()) -> Result { + CurrentState::with_store(|store| { store.insert(b"key", b"value"); }); Ok(10) } #[handler(call = "alphabet.Aborting")] - fn aborting(_ctx: &mut C, _args: ()) -> Result<(), AlphabetError> { + fn aborting(_ctx: &C, _args: ()) -> Result<(), AlphabetError> { // Use a deeply nested abort to make sure this is handled correctly. Err(AlphabetError::Core(core::Error::Abort(Error::Aborted))) } #[handler(query = "alphabet.Alpha")] - fn alpha(_ctx: &mut C, _args: ()) -> Result<(), AlphabetError> { + fn alpha(_ctx: &C, _args: ()) -> Result<(), AlphabetError> { Ok(()) } #[handler(query = "alphabet.Omega", expensive)] - fn expensive(_ctx: &mut C, _args: ()) -> Result<(), AlphabetError> { + fn expensive(_ctx: &C, _args: ()) -> Result<(), AlphabetError> { // Nothing actually expensive here. We're just pretending for testing purposes. Ok(()) } @@ -1034,7 +1021,7 @@ mod test { #[test] fn test_allowed_queries_defaults() { let mut mock = Mock::with_local_config(BTreeMap::new()); - let mut ctx = mock.create_ctx_for_runtime::(Mode::CheckTx, false); + let mut ctx = mock.create_ctx_for_runtime::(false); Dispatcher::::dispatch_query( &mut ctx, @@ -1063,28 +1050,31 @@ mod test { ], }; let mut mock = Mock::with_local_config(local_config); - // For queries, oasis-core always generates a `CheckTx` context; test with that. - let mut ctx = mock.create_ctx_for_runtime::(Mode::CheckTx, false); + let mut ctx = mock.create_ctx_for_runtime::(false); - Dispatcher::::dispatch_query( - &mut ctx, - "alphabet.Alpha", - cbor::to_vec(().into_cbor_value()), - ) - .expect_err("alphabet.Alpha is a disallowed query"); + CurrentState::with_transaction_opts(Options::new().with_mode(state::Mode::Check), || { + Dispatcher::::dispatch_query( + &mut ctx, + "alphabet.Alpha", + cbor::to_vec(().into_cbor_value()), + ) + .expect_err("alphabet.Alpha is a disallowed query"); - Dispatcher::::dispatch_query( - &mut ctx, - "alphabet.Omega", - cbor::to_vec(().into_cbor_value()), - ) - .expect("alphabet.Omega is an expensive query and expensive queries are allowed"); + Dispatcher::::dispatch_query( + &mut ctx, + "alphabet.Omega", + cbor::to_vec(().into_cbor_value()), + ) + .expect("alphabet.Omega is an expensive query and expensive queries are allowed"); + + TransactionResult::Rollback(()) + }); } #[test] fn test_dispatch_read_only_call() { let mut mock = Mock::default(); - let mut ctx = mock.create_ctx_for_runtime::(Mode::ExecuteTx, false); + let mut ctx = mock.create_ctx_for_runtime::(false); AlphabetRuntime::migrate(&mut ctx); @@ -1140,7 +1130,7 @@ mod test { #[test] fn test_dispatch_abort_forwarding() { let mut mock = Mock::default(); - let mut ctx = mock.create_ctx_for_runtime::(Mode::ExecuteTx, false); + let mut ctx = mock.create_ctx_for_runtime::(false); AlphabetRuntime::migrate(&mut ctx); diff --git a/runtime-sdk/src/lib.rs b/runtime-sdk/src/lib.rs index d95969ad2a..93e767db28 100644 --- a/runtime-sdk/src/lib.rs +++ b/runtime-sdk/src/lib.rs @@ -16,16 +16,15 @@ pub mod modules; pub mod runtime; pub mod schedule_control; pub mod sender; +pub mod state; pub mod storage; pub mod subcall; pub mod testing; pub mod types; pub use crate::{ - context::{BatchContext, Context, TxContext}, - core::common::version::Version, - module::Module, - runtime::Runtime, + context::Context, core::common::version::Version, module::Module, runtime::Runtime, + state::CurrentState, }; // Re-export the appropriate version of the oasis-core-runtime library. diff --git a/runtime-sdk/src/module.rs b/runtime-sdk/src/module.rs index 0be8b5202b..e3f230d3e6 100644 --- a/runtime-sdk/src/module.rs +++ b/runtime-sdk/src/module.rs @@ -8,13 +8,14 @@ use cbor::Encode as _; use impl_trait_for_tuples::impl_for_tuples; use crate::{ - context::{Context, TxContext}, + context::Context, dispatcher, error, error::Error as _, event, modules, modules::core::types::{MethodHandlerInfo, ModuleInfo}, + state::CurrentState, storage, - storage::{CurrentStore, Prefix}, + storage::Prefix, types::{ message::MessageResult, transaction::{self, AuthInfo, Call, Transaction, UnverifiedTransaction}, @@ -111,16 +112,16 @@ impl From for transaction::CallResult { /// A convenience function for dispatching method calls. pub fn dispatch_call( - ctx: &mut C, + ctx: &C, body: cbor::Value, f: F, ) -> DispatchResult where - C: TxContext, + C: Context, B: cbor::Decode, R: cbor::Encode, E: error::Error, - F: FnOnce(&mut C, B) -> Result, + F: FnOnce(&C, B) -> Result, { DispatchResult::Handled((|| { let args = match cbor::from_value(body) @@ -139,7 +140,7 @@ where /// A convenience function for dispatching queries. pub fn dispatch_query( - ctx: &mut C, + ctx: &C, body: cbor::Value, f: F, ) -> DispatchResult> @@ -149,7 +150,7 @@ where R: cbor::Encode, E: error::Error, error::RuntimeError: From, - F: FnOnce(&mut C, B) -> Result, + F: FnOnce(&C, B) -> Result, { DispatchResult::Handled((|| { let args = cbor::from_value(body).map_err(|err| -> error::RuntimeError { @@ -173,8 +174,8 @@ pub trait MethodHandler { } /// Dispatch a call. - fn dispatch_call( - _ctx: &mut C, + fn dispatch_call( + _ctx: &C, _method: &str, body: cbor::Value, ) -> DispatchResult { @@ -184,7 +185,7 @@ pub trait MethodHandler { /// Dispatch a query. fn dispatch_query( - _ctx: &mut C, + _ctx: &C, _method: &str, args: cbor::Value, ) -> DispatchResult> { @@ -194,7 +195,7 @@ pub trait MethodHandler { /// Dispatch a message result. fn dispatch_message_result( - _ctx: &mut C, + _ctx: &C, _handler_name: &str, result: MessageResult, ) -> DispatchResult { @@ -245,8 +246,8 @@ impl MethodHandler for Tuple { DispatchResult::Unhandled(body) } - fn dispatch_call( - ctx: &mut C, + fn dispatch_call( + ctx: &C, method: &str, body: cbor::Value, ) -> DispatchResult { @@ -262,7 +263,7 @@ impl MethodHandler for Tuple { } fn dispatch_query( - ctx: &mut C, + ctx: &C, method: &str, args: cbor::Value, ) -> DispatchResult> { @@ -278,7 +279,7 @@ impl MethodHandler for Tuple { } fn dispatch_message_result( - ctx: &mut C, + ctx: &C, handler_name: &str, result: MessageResult, ) -> DispatchResult { @@ -325,7 +326,7 @@ impl MethodHandler for Tuple { pub trait TransactionHandler { /// Judge if a raw transaction is good enough to undergo decoding. /// This takes place before even decoding the transaction. - fn approve_raw_tx(_ctx: &mut C, _tx: &[u8]) -> Result<(), modules::core::Error> { + fn approve_raw_tx(_ctx: &C, _tx: &[u8]) -> Result<(), modules::core::Error> { // Default implementation doesn't do any checks. Ok(()) } @@ -333,7 +334,7 @@ pub trait TransactionHandler { /// Judge if an unverified transaction is good enough to undergo verification. /// This takes place before even verifying signatures. fn approve_unverified_tx( - _ctx: &mut C, + _ctx: &C, _utx: &UnverifiedTransaction, ) -> Result<(), modules::core::Error> { // Default implementation doesn't do any checks. @@ -348,7 +349,7 @@ pub trait TransactionHandler { /// Returns Ok(Some(_)) if the module is in charge of the encoding scheme identified by _scheme /// or Ok(None) otherwise. fn decode_tx( - _ctx: &mut C, + _ctx: &C, _scheme: &str, _body: &[u8], ) -> Result, modules::core::Error> { @@ -360,7 +361,7 @@ pub trait TransactionHandler { /// /// Note that any signatures have already been verified. fn authenticate_tx( - _ctx: &mut C, + _ctx: &C, _tx: &Transaction, ) -> Result<(), modules::core::Error> { // Default implementation accepts all transactions. @@ -371,10 +372,7 @@ pub trait TransactionHandler { /// /// At this point call format has not yet been decoded so peeking into the call may not be /// possible in case the call is encrypted. - fn before_handle_call( - _ctx: &mut C, - _call: &Call, - ) -> Result<(), modules::core::Error> { + fn before_handle_call(_ctx: &C, _call: &Call) -> Result<(), modules::core::Error> { // Default implementation doesn't do anything. Ok(()) } @@ -382,8 +380,8 @@ pub trait TransactionHandler { /// Perform any action after call, within the transaction context. /// /// If an error is returned the transaction call fails and updates are rolled back. - fn after_handle_call( - _ctx: &mut C, + fn after_handle_call( + _ctx: &C, result: CallResult, ) -> Result { // Default implementation doesn't do anything. @@ -391,20 +389,20 @@ pub trait TransactionHandler { } /// Perform any action after dispatching the transaction, in batch context. - fn after_dispatch_tx(_ctx: &mut C, _tx_auth_info: &AuthInfo, _result: &CallResult) { + fn after_dispatch_tx(_ctx: &C, _tx_auth_info: &AuthInfo, _result: &CallResult) { // Default implementation doesn't do anything. } } #[impl_for_tuples(30)] impl TransactionHandler for Tuple { - fn approve_raw_tx(ctx: &mut C, tx: &[u8]) -> Result<(), modules::core::Error> { + fn approve_raw_tx(ctx: &C, tx: &[u8]) -> Result<(), modules::core::Error> { for_tuples!( #( Tuple::approve_raw_tx(ctx, tx)?; )* ); Ok(()) } fn approve_unverified_tx( - ctx: &mut C, + ctx: &C, utx: &UnverifiedTransaction, ) -> Result<(), modules::core::Error> { for_tuples!( #( Tuple::approve_unverified_tx(ctx, utx)?; )* ); @@ -412,7 +410,7 @@ impl TransactionHandler for Tuple { } fn decode_tx( - ctx: &mut C, + ctx: &C, scheme: &str, body: &[u8], ) -> Result, modules::core::Error> { @@ -425,24 +423,18 @@ impl TransactionHandler for Tuple { Ok(None) } - fn authenticate_tx( - ctx: &mut C, - tx: &Transaction, - ) -> Result<(), modules::core::Error> { + fn authenticate_tx(ctx: &C, tx: &Transaction) -> Result<(), modules::core::Error> { for_tuples!( #( Tuple::authenticate_tx(ctx, tx)?; )* ); Ok(()) } - fn before_handle_call( - ctx: &mut C, - call: &Call, - ) -> Result<(), modules::core::Error> { + fn before_handle_call(ctx: &C, call: &Call) -> Result<(), modules::core::Error> { for_tuples!( #( Tuple::before_handle_call(ctx, call)?; )* ); Ok(()) } - fn after_handle_call( - ctx: &mut C, + fn after_handle_call( + ctx: &C, mut result: CallResult, ) -> Result { for_tuples!( #( @@ -451,7 +443,7 @@ impl TransactionHandler for Tuple { Ok(result) } - fn after_dispatch_tx(ctx: &mut C, tx_auth_info: &AuthInfo, result: &CallResult) { + fn after_dispatch_tx(ctx: &C, tx_auth_info: &AuthInfo, result: &CallResult) { for_tuples!( #( Tuple::after_dispatch_tx(ctx, tx_auth_info, result); )* ); } } @@ -468,7 +460,7 @@ pub trait MigrationHandler { /// /// Should return true in case metadata has been changed. fn init_or_migrate( - _ctx: &mut C, + _ctx: &C, _meta: &mut modules::core::types::Metadata, _genesis: Self::Genesis, ) -> bool { @@ -483,7 +475,7 @@ impl MigrationHandler for Tuple { for_tuples!( type Genesis = ( #( Tuple::Genesis ),* ); ); fn init_or_migrate( - ctx: &mut C, + ctx: &C, meta: &mut modules::core::types::Metadata, genesis: Self::Genesis, ) -> bool { @@ -497,24 +489,24 @@ impl MigrationHandler for Tuple { pub trait BlockHandler { /// Perform any common actions at the start of the block (before any transactions have been /// executed). - fn begin_block(_ctx: &mut C) { + fn begin_block(_ctx: &C) { // Default implementation doesn't do anything. } /// Perform any common actions at the end of the block (after all transactions have been /// executed). - fn end_block(_ctx: &mut C) { + fn end_block(_ctx: &C) { // Default implementation doesn't do anything. } } #[impl_for_tuples(30)] impl BlockHandler for Tuple { - fn begin_block(ctx: &mut C) { + fn begin_block(ctx: &C) { for_tuples!( #( Tuple::begin_block(ctx); )* ); } - fn end_block(ctx: &mut C) { + fn end_block(ctx: &C) { for_tuples!( #( Tuple::end_block(ctx); )* ); } } @@ -522,7 +514,7 @@ impl BlockHandler for Tuple { /// Invariant handler. pub trait InvariantHandler { /// Check invariants. - fn check_invariants(_ctx: &mut C) -> Result<(), modules::core::Error> { + fn check_invariants(_ctx: &C) -> Result<(), modules::core::Error> { // Default implementation doesn't do anything. Ok(()) } @@ -531,7 +523,7 @@ pub trait InvariantHandler { #[impl_for_tuples(30)] impl InvariantHandler for Tuple { /// Check the invariants in all modules in the tuple. - fn check_invariants(ctx: &mut C) -> Result<(), modules::core::Error> { + fn check_invariants(ctx: &C) -> Result<(), modules::core::Error> { for_tuples!( #( Tuple::check_invariants(ctx)?; )* ); Ok(()) } @@ -540,11 +532,11 @@ impl InvariantHandler for Tuple { /// Info handler. pub trait ModuleInfoHandler { /// Reports info about the module (or modules, if `Self` is a tuple). - fn module_info(_ctx: &mut C) -> BTreeMap; + fn module_info(_ctx: &C) -> BTreeMap; } impl ModuleInfoHandler for M { - fn module_info(_ctx: &mut C) -> BTreeMap { + fn module_info(_ctx: &C) -> BTreeMap { let mut info = BTreeMap::new(); info.insert( Self::NAME.to_string(), @@ -561,7 +553,7 @@ impl ModuleInfoHandler for M { #[impl_for_tuples(30)] impl ModuleInfoHandler for Tuple { #[allow(clippy::let_and_return)] - fn module_info(ctx: &mut C) -> BTreeMap { + fn module_info(ctx: &C) -> BTreeMap { let mut merged = BTreeMap::new(); for_tuples!( #( merged.extend(Tuple::module_info(ctx)); @@ -589,7 +581,7 @@ pub trait Module { /// Return the module's parameters. fn params() -> Self::Parameters { - CurrentStore::with(|store| { + CurrentState::with_store(|store| { let store = storage::PrefixStore::new(store, &Self::NAME); let store = storage::TypedStore::new(store); store.get(Self::Parameters::STORE_KEY).unwrap_or_default() @@ -598,7 +590,7 @@ pub trait Module { /// Set the module's parameters. fn set_params(params: Self::Parameters) { - CurrentStore::with(|store| { + CurrentState::with_store(|store| { let store = storage::PrefixStore::new(store, &Self::NAME); let mut store = storage::TypedStore::new(store); store.insert(Self::Parameters::STORE_KEY, params); diff --git a/runtime-sdk/src/modules/accounts/mod.rs b/runtime-sdk/src/modules/accounts/mod.rs index 37a6fd86de..2443e38088 100644 --- a/runtime-sdk/src/modules/accounts/mod.rs +++ b/runtime-sdk/src/modules/accounts/mod.rs @@ -10,7 +10,7 @@ use once_cell::sync::Lazy; use thiserror::Error; use crate::{ - context::{Context, TxContext}, + context::Context, core::common::quantity::Quantity, handler, migration, module, module::{Module as _, Parameters as _}, @@ -19,8 +19,9 @@ use crate::{ runtime::Runtime, sdk_derive, sender::SenderMeta, + state::CurrentState, storage, - storage::{CurrentStore, Prefix}, + storage::Prefix, types::{ address::{Address, SignatureAddressSpec}, token, @@ -141,22 +142,16 @@ pub struct Genesis { /// Interface that can be called from other modules. pub trait API { /// Transfer an amount from one account to the other. - fn transfer( - ctx: &mut C, - from: Address, - to: Address, - amount: &token::BaseUnits, - ) -> Result<(), Error>; + fn transfer(from: Address, to: Address, amount: &token::BaseUnits) -> Result<(), Error>; /// Transfer an amount from one account to the other without emitting an event. fn transfer_silent(from: Address, to: Address, amount: &token::BaseUnits) -> Result<(), Error>; /// Mint new tokens, increasing the total supply. - fn mint(ctx: &mut C, to: Address, amount: &token::BaseUnits) -> Result<(), Error>; + fn mint(to: Address, amount: &token::BaseUnits) -> Result<(), Error>; /// Burn existing tokens, decreasing the total supply. - fn burn(ctx: &mut C, from: Address, amount: &token::BaseUnits) - -> Result<(), Error>; + fn burn(from: Address, amount: &token::BaseUnits) -> Result<(), Error>; /// Sets an account's nonce. fn set_nonce(address: Address, nonce: u64); @@ -209,32 +204,28 @@ pub trait API { ) -> Result; /// Moves the amount into the per-transaction fee accumulator. - fn charge_tx_fee( - ctx: &mut C, - from: Address, - amount: &token::BaseUnits, - ) -> Result<(), modules::core::Error>; + fn charge_tx_fee(from: Address, amount: &token::BaseUnits) -> Result<(), modules::core::Error>; /// Indicates that the unused portion of the transaction fee should be refunded after the /// transaction completes (even in case it fails). - fn set_refund_unused_tx_fee(ctx: &mut C, refund: bool); + fn set_refund_unused_tx_fee(refund: bool); /// Take the flag indicating that the unused portion of the transaction fee should be refunded /// after the transaction completes is set. /// /// After calling this method the flag is reset to `false`. - fn take_refund_unused_tx_fee(ctx: &mut C) -> bool; + fn take_refund_unused_tx_fee() -> bool; /// Check transaction signer account nonces. /// Return payer address. fn check_signer_nonces( - ctx: &mut C, + ctx: &C, tx_auth_info: &AuthInfo, ) -> Result; /// Update transaction signer account nonces. fn update_signer_nonces( - ctx: &mut C, + ctx: &C, tx_auth_info: &AuthInfo, ) -> Result<(), modules::core::Error>; } @@ -290,7 +281,7 @@ impl Module { return Ok(()); } - CurrentStore::with(|store| { + CurrentState::with_store(|store| { let store = storage::PrefixStore::new(store, &MODULE_NAME); let balances = storage::PrefixStore::new(store, &state::BALANCES); let mut account = storage::TypedStore::new(storage::PrefixStore::new(balances, &addr)); @@ -310,7 +301,7 @@ impl Module { return Ok(()); } - CurrentStore::with(|store| { + CurrentState::with_store(|store| { let store = storage::PrefixStore::new(store, &MODULE_NAME); let balances = storage::PrefixStore::new(store, &state::BALANCES); let mut account = storage::TypedStore::new(storage::PrefixStore::new(balances, &addr)); @@ -330,7 +321,7 @@ impl Module { return Ok(()); } - CurrentStore::with(|store| { + CurrentState::with_store(|store| { let store = storage::PrefixStore::new(store, &MODULE_NAME); let mut total_supplies = storage::TypedStore::new(storage::PrefixStore::new(store, &state::TOTAL_SUPPLY)); @@ -352,7 +343,7 @@ impl Module { return Ok(()); } - CurrentStore::with(|store| { + CurrentState::with_store(|store| { let store = storage::PrefixStore::new(store, &MODULE_NAME); let mut total_supplies = storage::TypedStore::new(storage::PrefixStore::new(store, &state::TOTAL_SUPPLY)); @@ -370,7 +361,7 @@ impl Module { /// Get all balances. fn get_all_balances() -> Result>, Error> { - CurrentStore::with(|store| { + CurrentState::with_store(|store| { let store = storage::PrefixStore::new(store, &MODULE_NAME); let balances = storage::TypedStore::new(storage::PrefixStore::new(store, &state::BALANCES)); @@ -408,23 +399,20 @@ const CONTEXT_KEY_TX_FEE_REFUND_UNUSED: &str = "accounts.TxRefundUnusedFee"; const CONTEXT_KEY_FEE_MANAGER: &str = "accounts.FeeManager"; impl API for Module { - fn transfer( - ctx: &mut C, - from: Address, - to: Address, - amount: &token::BaseUnits, - ) -> Result<(), Error> { - if ctx.is_check_only() || amount.amount() == 0 { + fn transfer(from: Address, to: Address, amount: &token::BaseUnits) -> Result<(), Error> { + if CurrentState::with_env(|env| env.is_check_only()) || amount.amount() == 0 { return Ok(()); } Self::transfer_silent(from, to, amount)?; // Emit a transfer event. - ctx.emit_event(Event::Transfer { - from, - to, - amount: amount.clone(), + CurrentState::with(|state| { + state.emit_event(Event::Transfer { + from, + to, + amount: amount.clone(), + }) }); Ok(()) @@ -439,8 +427,8 @@ impl API for Module { Ok(()) } - fn mint(ctx: &mut C, to: Address, amount: &token::BaseUnits) -> Result<(), Error> { - if ctx.is_check_only() || amount.amount() == 0 { + fn mint(to: Address, amount: &token::BaseUnits) -> Result<(), Error> { + if CurrentState::with_env(|env| env.is_check_only()) || amount.amount() == 0 { return Ok(()); } @@ -451,20 +439,18 @@ impl API for Module { Self::inc_total_supply(amount)?; // Emit a mint event. - ctx.emit_event(Event::Mint { - owner: to, - amount: amount.clone(), + CurrentState::with(|state| { + state.emit_event(Event::Mint { + owner: to, + amount: amount.clone(), + }); }); Ok(()) } - fn burn( - ctx: &mut C, - from: Address, - amount: &token::BaseUnits, - ) -> Result<(), Error> { - if ctx.is_check_only() || amount.amount() == 0 { + fn burn(from: Address, amount: &token::BaseUnits) -> Result<(), Error> { + if CurrentState::with_env(|env| env.is_check_only()) || amount.amount() == 0 { return Ok(()); } @@ -476,16 +462,18 @@ impl API for Module { .expect("target account had enough balance so total supply should not underflow"); // Emit a burn event. - ctx.emit_event(Event::Burn { - owner: from, - amount: amount.clone(), + CurrentState::with(|state| { + state.emit_event(Event::Burn { + owner: from, + amount: amount.clone(), + }); }); Ok(()) } fn set_nonce(address: Address, nonce: u64) { - CurrentStore::with(|store| { + CurrentState::with_store(|store| { let store = storage::PrefixStore::new(store, &MODULE_NAME); let mut accounts = storage::TypedStore::new(storage::PrefixStore::new(store, &state::ACCOUNTS)); @@ -496,7 +484,7 @@ impl API for Module { } fn get_nonce(address: Address) -> Result { - CurrentStore::with(|store| { + CurrentState::with_store(|store| { let store = storage::PrefixStore::new(store, &MODULE_NAME); let accounts = storage::TypedStore::new(storage::PrefixStore::new(store, &state::ACCOUNTS)); @@ -506,7 +494,7 @@ impl API for Module { } fn inc_nonce(address: Address) { - CurrentStore::with(|store| { + CurrentState::with_store(|store| { let store = storage::PrefixStore::new(store, &MODULE_NAME); let mut accounts = storage::TypedStore::new(storage::PrefixStore::new(store, &state::ACCOUNTS)); @@ -517,7 +505,7 @@ impl API for Module { } fn set_balance(address: Address, amount: &token::BaseUnits) { - CurrentStore::with(|store| { + CurrentState::with_store(|store| { let store = storage::PrefixStore::new(store, &MODULE_NAME); let balances = storage::PrefixStore::new(store, &state::BALANCES); let mut account = @@ -527,7 +515,7 @@ impl API for Module { } fn get_balance(address: Address, denomination: token::Denomination) -> Result { - CurrentStore::with(|store| { + CurrentState::with_store(|store| { let store = storage::PrefixStore::new(store, &MODULE_NAME); let balances = storage::PrefixStore::new(store, &state::BALANCES); let account = storage::TypedStore::new(storage::PrefixStore::new(balances, &address)); @@ -537,7 +525,7 @@ impl API for Module { } fn get_balances(address: Address) -> Result { - CurrentStore::with(|store| { + CurrentState::with_store(|store| { let store = storage::PrefixStore::new(store, &MODULE_NAME); let balances = storage::PrefixStore::new(store, &state::BALANCES); let account = storage::TypedStore::new(storage::PrefixStore::new(balances, &address)); @@ -549,7 +537,7 @@ impl API for Module { } fn get_addresses(denomination: token::Denomination) -> Result, Error> { - CurrentStore::with(|store| { + CurrentState::with_store(|store| { let store = storage::PrefixStore::new(store, &MODULE_NAME); let balances: BTreeMap = storage::TypedStore::new(storage::PrefixStore::new(store, &state::BALANCES)) @@ -565,7 +553,7 @@ impl API for Module { } fn get_total_supplies() -> Result, Error> { - CurrentStore::with(|store| { + CurrentState::with_store(|store| { let store = storage::PrefixStore::new(store, &MODULE_NAME); let ts = storage::TypedStore::new(storage::PrefixStore::new(store, &state::TOTAL_SUPPLY)); @@ -575,7 +563,7 @@ impl API for Module { } fn set_total_supply(amount: &token::BaseUnits) { - CurrentStore::with(|store| { + CurrentState::with_store(|store| { let store = storage::PrefixStore::new(store, &MODULE_NAME); let mut total_supplies = storage::TypedStore::new(storage::PrefixStore::new(store, &state::TOTAL_SUPPLY)); @@ -593,53 +581,59 @@ impl API for Module { .ok_or(Error::NotFound) } - fn charge_tx_fee( - ctx: &mut C, - from: Address, - amount: &token::BaseUnits, - ) -> Result<(), modules::core::Error> { - if ctx.is_simulation() { + fn charge_tx_fee(from: Address, amount: &token::BaseUnits) -> Result<(), modules::core::Error> { + if CurrentState::with_env(|env| env.is_simulation()) { return Ok(()); } Self::sub_amount(from, amount).map_err(|_| modules::core::Error::InsufficientFeeBalance)?; - ctx.value::(CONTEXT_KEY_FEE_MANAGER) - .or_default() - .record_fee(from, amount); + CurrentState::with(|state| { + state + .block_value::(CONTEXT_KEY_FEE_MANAGER) + .or_default() + .record_fee(from, amount); + }); Ok(()) } - fn set_refund_unused_tx_fee(ctx: &mut C, refund: bool) { - if ctx.is_simulation() { - return; - } + fn set_refund_unused_tx_fee(refund: bool) { + CurrentState::with(|state| { + if state.env().is_simulation() { + return; + } - ctx.value(CONTEXT_KEY_TX_FEE_REFUND_UNUSED).set(refund); + state + .block_value(CONTEXT_KEY_TX_FEE_REFUND_UNUSED) + .set(refund); + }); } - fn take_refund_unused_tx_fee(ctx: &mut C) -> bool { - if ctx.is_simulation() { - return false; - } + fn take_refund_unused_tx_fee() -> bool { + CurrentState::with(|state| { + if state.env().is_simulation() { + return false; + } - ctx.value(CONTEXT_KEY_TX_FEE_REFUND_UNUSED) - .take() - .unwrap_or(false) + state + .block_value(CONTEXT_KEY_TX_FEE_REFUND_UNUSED) + .take() + .unwrap_or(false) + }) } fn check_signer_nonces( - ctx: &mut C, + _ctx: &C, auth_info: &AuthInfo, ) -> Result { - let is_pre_schedule = ctx.is_pre_schedule(); - let is_check_only = ctx.is_check_only(); + let is_pre_schedule = CurrentState::with_env(|env| env.is_pre_schedule()); + let is_check_only = CurrentState::with_env(|env| env.is_check_only()); // TODO: Optimize the check/update pair so that the accounts are // fetched only once. let params = Self::params(); - let sender = CurrentStore::with(|store| { + let sender = CurrentState::with_store(|store| { // Fetch information about each signer. let mut store = storage::PrefixStore::new(store, &MODULE_NAME); let accounts = @@ -699,17 +693,17 @@ impl API for Module { let sender = sender.expect("at least one signer is always present"); let sender_address = sender.address; if is_check_only { - ::Core::set_sender_meta(ctx, sender); + ::Core::set_sender_meta(sender); } Ok(sender_address) } fn update_signer_nonces( - _ctx: &mut C, + _ctx: &C, auth_info: &AuthInfo, ) -> Result<(), modules::core::Error> { - CurrentStore::with(|store| { + CurrentState::with_store(|store| { // Fetch information about each signer. let mut store = storage::PrefixStore::new(store, &MODULE_NAME); let mut accounts = @@ -740,7 +734,7 @@ impl Module { #[migration(init)] pub fn init(genesis: Genesis) { - CurrentStore::with(|store| { + CurrentState::with_store(|store| { // Create accounts. let mut store = storage::PrefixStore::new(store, &MODULE_NAME); let mut accounts = @@ -825,7 +819,7 @@ impl Module { } #[handler(call = "accounts.Transfer")] - fn tx_transfer(ctx: &mut C, body: types::Transfer) -> Result<(), Error> { + fn tx_transfer(_ctx: &C, body: types::Transfer) -> Result<(), Error> { let params = Self::params(); // Reject transfers when they are disabled. @@ -833,21 +827,22 @@ impl Module { return Err(Error::Forbidden); } - ::Core::use_tx_gas(ctx, params.gas_costs.tx_transfer)?; + ::Core::use_tx_gas(params.gas_costs.tx_transfer)?; - Self::transfer(ctx, ctx.tx_caller_address(), body.to, &body.amount)?; + let tx_caller_address = CurrentState::with_env(|env| env.tx_caller_address()); + Self::transfer(tx_caller_address, body.to, &body.amount)?; Ok(()) } #[handler(query = "accounts.Nonce")] - fn query_nonce(_ctx: &mut C, args: types::NonceQuery) -> Result { + fn query_nonce(_ctx: &C, args: types::NonceQuery) -> Result { Self::get_nonce(args.address) } #[handler(query = "accounts.Addresses", expensive)] fn query_addresses( - _ctx: &mut C, + _ctx: &C, args: types::AddressesQuery, ) -> Result, Error> { Self::get_addresses(args.denomination) @@ -855,7 +850,7 @@ impl Module { #[handler(query = "accounts.Balances")] fn query_balances( - _ctx: &mut C, + _ctx: &C, args: types::BalancesQuery, ) -> Result { Self::get_balances(args.address) @@ -863,7 +858,7 @@ impl Module { #[handler(query = "accounts.DenominationInfo")] fn query_denomination_info( - _ctx: &mut C, + _ctx: &C, args: types::DenominationInfoQuery, ) -> Result { Self::get_denomination_info(&args.denomination) @@ -871,10 +866,7 @@ impl Module { } impl module::TransactionHandler for Module { - fn authenticate_tx( - ctx: &mut C, - tx: &Transaction, - ) -> Result<(), modules::core::Error> { + fn authenticate_tx(ctx: &C, tx: &Transaction) -> Result<(), modules::core::Error> { // Check whether the transaction is currently valid. let round = ctx.runtime_header().round; if let Some(not_before) = tx.auth_info.not_before { @@ -895,7 +887,7 @@ impl module::TransactionHandler for Module { // Charge the specified amount of fees. if !tx.auth_info.fee.amount.amount().is_zero() { - if ctx.is_check_only() { + if CurrentState::with_env(|env| env.is_check_only()) { // Do not update balances during transaction checks. In case of checks, only do it // after all the other checks have already passed as otherwise retrying the // transaction will not be possible. @@ -903,76 +895,77 @@ impl module::TransactionHandler for Module { .map_err(|_| modules::core::Error::InsufficientFeeBalance)?; } else { // Actually perform the move. - Self::charge_tx_fee(ctx, payer, &tx.auth_info.fee.amount)?; + Self::charge_tx_fee(payer, &tx.auth_info.fee.amount)?; } let gas_price = tx.auth_info.fee.gas_price(); // Set transaction priority. - ::Core::set_priority( - ctx, - gas_price.try_into().unwrap_or(u64::MAX), - ); + ::Core::set_priority(gas_price.try_into().unwrap_or(u64::MAX)); } // Do not update nonces early during transaction checks. In case of checks, only do it after // all the other checks have already passed as otherwise retrying the transaction will not // be possible. - if !ctx.is_check_only() { + if !CurrentState::with_env(|env| env.is_check_only()) { Self::update_signer_nonces(ctx, &tx.auth_info)?; } Ok(()) } - fn after_handle_call( - ctx: &mut C, + fn after_handle_call( + _ctx: &C, result: module::CallResult, ) -> Result { // Check whether unused part of the fee should be refunded. - let refund_fee = if Self::take_refund_unused_tx_fee(ctx) { - let remaining_gas = ::Core::remaining_tx_gas(ctx); - let gas_price = ctx.tx_auth_info().fee.gas_price(); + let refund_fee = if Self::take_refund_unused_tx_fee() { + let remaining_gas = ::Core::remaining_tx_gas(); + let gas_price = CurrentState::with_env(|env| env.tx_auth_info().fee.gas_price()); gas_price.saturating_mul(remaining_gas.into()) } else { 0 }; - let mgr = ctx - .value::(CONTEXT_KEY_FEE_MANAGER) - .or_default(); - - // Update the per-tx fee accumulator. State must be updated in `after_dispatch_tx` as - // otherwise any state updates may be reverted in case call result is a failure. - mgr.record_refund(refund_fee); - - // Emit event for paid fee. - let tx_fee = mgr.tx_fee().cloned().unwrap_or_default(); - if tx_fee.amount() > 0 { - ctx.emit_unconditional_event(Event::Transfer { - from: tx_fee.payer(), - to: *ADDRESS_FEE_ACCUMULATOR, - amount: token::BaseUnits::new(tx_fee.amount(), tx_fee.denomination()), - }); - } + CurrentState::with(|state| { + let mgr = state + .block_value::(CONTEXT_KEY_FEE_MANAGER) + .or_default(); + + // Update the per-tx fee accumulator. State must be updated in `after_dispatch_tx` as + // otherwise any state updates may be reverted in case call result is a failure. + mgr.record_refund(refund_fee); + + // Emit event for paid fee. + let tx_fee = mgr.tx_fee().cloned().unwrap_or_default(); + if tx_fee.amount() > 0 { + state.emit_unconditional_event(Event::Transfer { + from: tx_fee.payer(), + to: *ADDRESS_FEE_ACCUMULATOR, + amount: token::BaseUnits::new(tx_fee.amount(), tx_fee.denomination()), + }); + } + }); Ok(result) } fn after_dispatch_tx( - ctx: &mut C, + ctx: &C, tx_auth_info: &AuthInfo, result: &module::CallResult, ) { // Move transaction fees into the per-block fee accumulator. - let mgr = ctx - .value::(CONTEXT_KEY_FEE_MANAGER) - .or_default(); - let fee_updates = mgr.commit_tx(); + let fee_updates = CurrentState::with(|state| { + let mgr = state + .block_value::(CONTEXT_KEY_FEE_MANAGER) + .or_default(); + mgr.commit_tx() + }); // Refund any fees. This needs to happen after tx dispatch to ensure state is updated. Self::add_amount(fee_updates.payer, &fee_updates.refund).unwrap(); - if !ctx.is_check_only() { + if !CurrentState::with_env(|env| env.is_check_only()) { // Do nothing further outside transaction checks. return; } @@ -992,7 +985,7 @@ impl module::TransactionHandler for Module { } impl module::BlockHandler for Module { - fn end_block(ctx: &mut C) { + fn end_block(ctx: &C) { // Determine the fees that are available for disbursement from the last block. let mut previous_fees = Self::get_balances(*ADDRESS_FEE_ACCUMULATOR) .expect("get_balances must succeed") @@ -1045,10 +1038,12 @@ impl module::BlockHandler for Module { .expect("add_amount must succeed for fee disbursement"); // Emit transfer event for fee disbursement. - ctx.emit_event(Event::Transfer { - from: *ADDRESS_FEE_ACCUMULATOR, - to: address, - amount: amount.clone(), + CurrentState::with(|state| { + state.emit_event(Event::Transfer { + from: *ADDRESS_FEE_ACCUMULATOR, + to: address, + amount: amount.clone(), + }); }); } } @@ -1065,19 +1060,25 @@ impl module::BlockHandler for Module { .expect("add_amount must succeed for transfer to common pool"); // Emit transfer event for fee disbursement. - ctx.emit_event(Event::Transfer { - from: *ADDRESS_FEE_ACCUMULATOR, - to: *ADDRESS_COMMON_POOL, - amount, - }) + CurrentState::with(|state| { + state.emit_event(Event::Transfer { + from: *ADDRESS_FEE_ACCUMULATOR, + to: *ADDRESS_COMMON_POOL, + amount, + }) + }); } // Fees for the active block should be transferred to the fee accumulator address. - let mgr = ctx - .value::(CONTEXT_KEY_FEE_MANAGER) - .take() - .unwrap_or_default(); - for (denom, amount) in mgr.commit_block().into_iter() { + let block_fees = CurrentState::with(|state| { + let mgr = state + .block_value::(CONTEXT_KEY_FEE_MANAGER) + .take() + .unwrap_or_default(); + mgr.commit_block().into_iter() + }); + + for (denom, amount) in block_fees { Self::add_amount( *ADDRESS_FEE_ACCUMULATOR, &token::BaseUnits::new(amount, denom), @@ -1089,7 +1090,7 @@ impl module::BlockHandler for Module { impl module::InvariantHandler for Module { /// Check invariants. - fn check_invariants(_ctx: &mut C) -> Result<(), CoreError> { + fn check_invariants(_ctx: &C) -> Result<(), CoreError> { // All account balances should sum up to the total supply for their // corresponding denominations. diff --git a/runtime-sdk/src/modules/accounts/test.rs b/runtime-sdk/src/modules/accounts/test.rs index f9cf911f87..9f19f56cad 100644 --- a/runtime-sdk/src/modules/accounts/test.rs +++ b/runtime-sdk/src/modules/accounts/test.rs @@ -7,14 +7,16 @@ use std::{ use anyhow::anyhow; use crate::{ - context::{self, BatchContext, Context, TxContext}, + context::Context, handler, module::{self, BlockHandler, InvariantHandler, MethodHandler, Module, TransactionHandler}, modules::{ core, core::{Error as CoreError, Module as Core, API as _}, }, - sdk_derive, subcall, + sdk_derive, + state::{self, CurrentState, Options}, + subcall, testing::{keys, mock}, types::{ address::Address, @@ -81,11 +83,11 @@ impl TestModule { type Genesis = (); #[handler(call = "test.RefundFee")] - fn refund_fee(ctx: &mut C, fail: bool) -> Result<(), CoreError> { + fn refund_fee(_ctx: &C, fail: bool) -> Result<(), CoreError> { // Use some gas. - ::Core::use_tx_gas(ctx, 10_000)?; + ::Core::use_tx_gas(10_000)?; // Ask the runtime to refund the rest (even on failures). - Accounts::set_refund_unused_tx_fee(ctx, true); + Accounts::set_refund_unused_tx_fee(true); if fail { Err(CoreError::Forbidden) @@ -95,11 +97,11 @@ impl TestModule { } #[handler(call = "test.Subcall")] - fn subcall(ctx: &mut C, _args: ()) -> Result<(), CoreError> { + fn subcall(ctx: &C, _args: ()) -> Result<(), CoreError> { // Use some gas. - ::Core::use_tx_gas(ctx, 1_000)?; + ::Core::use_tx_gas(1_000)?; - let max_gas = ::Core::remaining_tx_gas(ctx); + let max_gas = ::Core::remaining_tx_gas(); let result = subcall::call( ctx, subcall::SubcallInfo { @@ -113,7 +115,7 @@ impl TestModule { )?; // Propagate gas use. - ::Core::use_tx_gas(ctx, result.gas_used)?; + ::Core::use_tx_gas(result.gas_used)?; Ok(()) } @@ -260,7 +262,7 @@ fn test_init_2() { #[test] fn test_api_tx_transfer_disabled() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); + let ctx = mock.create_ctx(); Accounts::init(Genesis { balances: { @@ -310,12 +312,13 @@ fn test_api_tx_transfer_disabled() { ..Default::default() }, }; + let call = tx.call.clone(); // Try to transfer. - ctx.with_tx(tx.into(), |mut tx_ctx, call| { + CurrentState::with_transaction_opts(Options::new().with_tx(tx.into()), || { assert!( matches!( - Accounts::tx_transfer(&mut tx_ctx, cbor::from_value(call.body).unwrap()), + Accounts::tx_transfer(&ctx, cbor::from_value(call.body).unwrap()), Err(Error::Forbidden), ), "transfers are forbidden", @@ -325,8 +328,7 @@ fn test_api_tx_transfer_disabled() { #[test] fn test_prefetch() { - let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); + let _mock = mock::Mock::default(); let auth_info = transaction::AuthInfo { signer_info: vec![transaction::SignerInfo::new_sigspec( @@ -354,8 +356,10 @@ fn test_prefetch() { }, auth_info: auth_info.clone(), }; + let call = tx.call.clone(); + // Transfer tokens from one account to the other and check balances. - ctx.with_tx(tx.into(), |mut _tx_ctx, call| { + CurrentState::with_transaction_opts(Options::new().with_tx(tx.into()), || { let mut prefixes = BTreeSet::new(); let result = Accounts::prefetch(&mut prefixes, &call.method, call.body, &auth_info) .ok_or(anyhow!("dispatch failure")) @@ -370,7 +374,7 @@ fn test_prefetch() { }); } -pub(crate) fn init_accounts(_ctx: &mut C) { +pub(crate) fn init_accounts(_ctx: &C) { Accounts::init(Genesis { balances: { let mut balances = BTreeMap::new(); @@ -402,14 +406,13 @@ pub(crate) fn init_accounts(_ctx: &mut C) { #[test] fn test_api_transfer() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); + let ctx = mock.create_ctx(); - init_accounts(&mut ctx); + init_accounts(&ctx); // Transfer tokens from one account to the other and check balances. - ctx.with_tx(mock::transaction().into(), |mut tx_ctx, _call| { + CurrentState::with_transaction_opts(Options::new().with_tx(mock::transaction().into()), || { Accounts::transfer( - &mut tx_ctx, keys::alice::address(), keys::bob::address(), &BaseUnits::new(1_000, Denomination::NATIVE), @@ -417,7 +420,6 @@ fn test_api_transfer() { .expect("transfer should succeed"); let result = Accounts::transfer( - &mut tx_ctx, keys::alice::address(), keys::bob::address(), &BaseUnits::new(1_000_000, Denomination::NATIVE), @@ -465,9 +467,9 @@ fn test_api_transfer() { #[test] fn test_authenticate_tx() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); + let ctx = mock.create_ctx(); - init_accounts(&mut ctx); + init_accounts(&ctx); let mut tx = transaction::Transaction { version: 1, @@ -495,7 +497,7 @@ fn test_authenticate_tx() { }; // Should succeed with enough funds to pay for fees. - Accounts::authenticate_tx(&mut ctx, &tx).expect("transaction authentication should succeed"); + Accounts::authenticate_tx(&ctx, &tx).expect("transaction authentication should succeed"); // Check source account balances. let bals = Accounts::get_balances(keys::alice::address()).expect("get_balances should succeed"); assert_eq!( @@ -512,26 +514,26 @@ fn test_authenticate_tx() { let nonce = Accounts::get_nonce(keys::alice::address()).expect("get_nonce should succeed"); assert_eq!(nonce, 1, "nonce should be incremented"); // Check priority. - let priority = core::Module::::take_priority(&mut ctx); + let priority = core::Module::::take_priority(); assert_eq!(priority, 1, "priority should be equal to gas price"); // Should fail with an invalid nonce. - let result = Accounts::authenticate_tx(&mut ctx, &tx); + let result = Accounts::authenticate_tx(&ctx, &tx); assert!(matches!(result, Err(core::Error::InvalidNonce))); // Should fail when there's not enough balance to pay fees. tx.auth_info.signer_info[0].nonce = nonce; tx.auth_info.fee.amount = BaseUnits::new(1_100_000, Denomination::NATIVE); - let result = Accounts::authenticate_tx(&mut ctx, &tx); + let result = Accounts::authenticate_tx(&ctx, &tx); assert!(matches!(result, Err(core::Error::InsufficientFeeBalance))); } #[test] fn test_tx_transfer() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); + let ctx = mock.create_ctx(); - init_accounts(&mut ctx); + init_accounts(&ctx); let tx = transaction::Transaction { version: 1, @@ -557,10 +559,11 @@ fn test_tx_transfer() { ..Default::default() }, }; + let call = tx.call.clone(); // Transfer tokens from one account to the other and check balances. - ctx.with_tx(tx.into(), |mut tx_ctx, call| { - Accounts::tx_transfer(&mut tx_ctx, cbor::from_value(call.body).unwrap()) + CurrentState::with_transaction_opts(Options::new().with_tx(tx.into()), || { + Accounts::tx_transfer(&ctx, cbor::from_value(call.body).unwrap()) .expect("transfer should succeed"); // Check source account balances. @@ -603,9 +606,9 @@ fn test_fee_disbursement() { keys::charlie::pk_ed25519().into(), ]; - let mut ctx = mock.create_ctx(); + let ctx = mock.create_ctx(); - init_accounts(&mut ctx); + init_accounts(&ctx); let tx = transaction::Transaction { version: 1, @@ -634,25 +637,24 @@ fn test_fee_disbursement() { }; // Authenticate transaction, fees should be moved to accumulator. - Accounts::authenticate_tx(&mut ctx, &tx).expect("transaction authentication should succeed"); - ctx.with_tx(tx.clone().into(), |mut tx_ctx, _call| { + Accounts::authenticate_tx(&ctx, &tx).expect("transaction authentication should succeed"); + CurrentState::with_transaction_opts(Options::new().with_tx(tx.clone().into()), || { // Run after call tx handler. Accounts::after_handle_call( - &mut tx_ctx, + &ctx, module::CallResult::Ok(cbor::Value::Simple(cbor::SimpleValue::NullValue)), ) .expect("after_handle_call should succeed"); - tx_ctx.commit() }); // Run after dispatch hooks. Accounts::after_dispatch_tx( - &mut ctx, + &ctx, &tx.auth_info, &module::CallResult::Ok(cbor::Value::Simple(cbor::SimpleValue::NullValue)), ); // Run end block handler. - Accounts::end_block(&mut ctx); + Accounts::end_block(&ctx); // Check source account balances. let bals = Accounts::get_balances(keys::alice::address()).expect("get_balances should succeed"); @@ -679,7 +681,7 @@ fn test_fee_disbursement() { ); // Simulate another block happening. - Accounts::end_block(&mut ctx); + Accounts::end_block(&ctx); // Fees should be removed from the fee accumulator address. let bals = @@ -718,13 +720,13 @@ fn test_fee_disbursement() { #[test] fn test_query_addresses() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); + let ctx = mock.create_ctx(); let dn = Denomination::NATIVE; let d1: Denomination = "den1".parse().unwrap(); let accs = Accounts::query_addresses( - &mut ctx, + &ctx, AddressesQuery { denomination: dn.clone(), }, @@ -761,25 +763,23 @@ fn test_query_addresses() { Accounts::init(gen); - ctx.with_tx(mock::transaction().into(), |mut tx_ctx, _call| { - let accs = Accounts::query_addresses(&mut tx_ctx, AddressesQuery { denomination: d1 }) - .expect("query accounts should succeed"); - assert_eq!(accs.len(), 2, "there should be two addresses"); - assert_eq!( - accs, - Vec::from_iter([keys::bob::address(), keys::alice::address()]), - "addresses should be correct" - ); + let accs = Accounts::query_addresses(&ctx, AddressesQuery { denomination: d1 }) + .expect("query accounts should succeed"); + assert_eq!(accs.len(), 2, "there should be two addresses"); + assert_eq!( + accs, + Vec::from_iter([keys::bob::address(), keys::alice::address()]), + "addresses should be correct" + ); - let accs = Accounts::query_addresses(&mut tx_ctx, AddressesQuery { denomination: dn }) - .expect("query accounts should succeed"); - assert_eq!(accs.len(), 1, "there should be one address"); - assert_eq!( - accs, - Vec::from_iter([keys::alice::address()]), - "addresses should be correct" - ); - }); + let accs = Accounts::query_addresses(&ctx, AddressesQuery { denomination: dn }) + .expect("query accounts should succeed"); + assert_eq!(accs.len(), 1, "there should be one address"); + assert_eq!( + accs, + Vec::from_iter([keys::alice::address()]), + "addresses should be correct" + ); } #[test] @@ -952,12 +952,12 @@ fn test_get_all_balances_and_total_supplies_more() { #[test] fn test_check_invariants_basic() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); + let ctx = mock.create_ctx(); - init_accounts(&mut ctx); + init_accounts(&ctx); assert!( - Accounts::check_invariants(&mut ctx).is_ok(), + Accounts::check_invariants(&ctx).is_ok(), "invariants check should succeed" ); } @@ -965,7 +965,7 @@ fn test_check_invariants_basic() { #[test] fn test_check_invariants_more() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); + let ctx = mock.create_ctx(); let dn = Denomination::NATIVE; let d1: Denomination = "den1".parse().unwrap(); @@ -1009,7 +1009,7 @@ fn test_check_invariants_more() { Accounts::init(gen); assert!( - Accounts::check_invariants(&mut ctx).is_ok(), + Accounts::check_invariants(&ctx).is_ok(), "initial inv chk should succeed" ); @@ -1018,7 +1018,7 @@ fn test_check_invariants_more() { "giving Charlie money should succeed" ); assert!( - Accounts::check_invariants(&mut ctx).is_err(), + Accounts::check_invariants(&ctx).is_err(), "inv chk 1 should fail" ); @@ -1027,7 +1027,7 @@ fn test_check_invariants_more() { "increasing total supply should succeed" ); assert!( - Accounts::check_invariants(&mut ctx).is_ok(), + Accounts::check_invariants(&ctx).is_ok(), "inv chk 2 should succeed" ); @@ -1038,7 +1038,7 @@ fn test_check_invariants_more() { "giving Charlie more money should succeed" ); assert!( - Accounts::check_invariants(&mut ctx).is_err(), + Accounts::check_invariants(&ctx).is_err(), "inv chk 3 should fail" ); @@ -1047,7 +1047,7 @@ fn test_check_invariants_more() { "increasing total supply should succeed" ); assert!( - Accounts::check_invariants(&mut ctx).is_ok(), + Accounts::check_invariants(&ctx).is_ok(), "inv chk 4 should succeed" ); @@ -1058,7 +1058,7 @@ fn test_check_invariants_more() { "increasing total supply should succeed" ); assert!( - Accounts::check_invariants(&mut ctx).is_err(), + Accounts::check_invariants(&ctx).is_err(), "inv chk 5 should fail" ); @@ -1067,7 +1067,7 @@ fn test_check_invariants_more() { "giving Charlie more money should succeed" ); assert!( - Accounts::check_invariants(&mut ctx).is_ok(), + Accounts::check_invariants(&ctx).is_ok(), "inv chk 6 should succeed" ); } @@ -1075,44 +1075,43 @@ fn test_check_invariants_more() { #[test] fn test_fee_manager_normal() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); + let ctx = mock.create_ctx(); - init_accounts(&mut ctx); + init_accounts(&ctx); // Check that Accounts::charge_tx_fee works. - ctx.with_tx(mock::transaction().into(), |mut tx_ctx, _call| { - Accounts::charge_tx_fee( - &mut tx_ctx, - keys::alice::address(), - &BaseUnits::new(1_000, Denomination::NATIVE), - ) - .expect("charge tx fee should succeed"); + Accounts::charge_tx_fee( + keys::alice::address(), + &BaseUnits::new(1_000, Denomination::NATIVE), + ) + .expect("charge tx fee should succeed"); - let ab = Accounts::get_balance(keys::alice::address(), Denomination::NATIVE) - .expect("get_balance should succeed"); - assert_eq!(ab, 999_000, "balance in source account should be correct"); + let ab = Accounts::get_balance(keys::alice::address(), Denomination::NATIVE) + .expect("get_balance should succeed"); + assert_eq!(ab, 999_000, "balance in source account should be correct"); - // Setting the refund request should have no effect. - Accounts::set_refund_unused_tx_fee(&mut tx_ctx, true); + // Setting the refund request should have no effect. + Accounts::set_refund_unused_tx_fee(true); - let ab = Accounts::get_balance(keys::alice::address(), Denomination::NATIVE) - .expect("get_balance should succeed"); - assert_eq!(ab, 999_000, "balance in source account should be correct"); - }); + let ab = Accounts::get_balance(keys::alice::address(), Denomination::NATIVE) + .expect("get_balance should succeed"); + assert_eq!(ab, 999_000, "balance in source account should be correct"); } #[test] fn test_fee_manager_sim() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); + let ctx = mock.create_ctx(); - init_accounts(&mut ctx); + init_accounts(&ctx); // Check that Accounts::charge_tx_fee doesn't do anything in simulation mode. - ctx.with_simulation(|mut sctx| { - sctx.with_tx(mock::transaction().into(), |mut tx_ctx, _call| { + CurrentState::with_transaction_opts( + state::Options::new() + .with_mode(state::Mode::Simulate) + .with_tx(mock::transaction().into()), + || { Accounts::charge_tx_fee( - &mut tx_ctx, keys::alice::address(), &BaseUnits::new(1_000, Denomination::NATIVE), ) @@ -1121,16 +1120,16 @@ fn test_fee_manager_sim() { let ab = Accounts::get_balance(keys::alice::address(), Denomination::NATIVE) .expect("get_balance should succeed"); assert_eq!(ab, 1_000_000, "balance in source account should be correct"); - }); - }); + }, + ); } #[test] fn test_get_set_nonce() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); + let ctx = mock.create_ctx(); - init_accounts(&mut ctx); + init_accounts(&ctx); let nonce = Accounts::get_nonce(keys::alice::address()).unwrap(); assert_eq!(nonce, 0); @@ -1144,9 +1143,9 @@ fn test_get_set_nonce() { #[test] fn test_get_set_balance() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); + let ctx = mock.create_ctx(); - init_accounts(&mut ctx); + init_accounts(&ctx); let balance = Accounts::get_balance(keys::alice::address(), Denomination::NATIVE).unwrap(); assert_eq!(balance, 1_000_000); @@ -1163,9 +1162,9 @@ fn test_get_set_balance() { #[test] fn test_get_set_total_supply() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); + let ctx = mock.create_ctx(); - init_accounts(&mut ctx); + init_accounts(&ctx); let ts = Accounts::get_total_supplies().expect("get_total_supplies should succeed"); assert_eq!( @@ -1206,12 +1205,12 @@ fn test_get_set_total_supply() { #[test] fn test_query_denomination_info() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); + let ctx = mock.create_ctx(); - init_accounts(&mut ctx); + init_accounts(&ctx); let di = Accounts::query_denomination_info( - &mut ctx, + &ctx, DenominationInfoQuery { denomination: Denomination::NATIVE, }, @@ -1221,7 +1220,7 @@ fn test_query_denomination_info() { // Query for missing info should fail. Accounts::query_denomination_info( - &mut ctx, + &ctx, DenominationInfoQuery { denomination: "MISSING".parse().unwrap(), }, @@ -1232,9 +1231,9 @@ fn test_query_denomination_info() { #[test] fn test_transaction_expiry() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); + let ctx = mock.create_ctx(); - init_accounts(&mut ctx); + init_accounts(&ctx); let mut tx = transaction::Transaction { version: 1, @@ -1264,37 +1263,37 @@ fn test_transaction_expiry() { }; // Authenticate transaction, should be expired. - let err = Accounts::authenticate_tx(&mut ctx, &tx).expect_err("tx should be expired (early)"); + let err = Accounts::authenticate_tx(&ctx, &tx).expect_err("tx should be expired (early)"); assert!(matches!(err, core::Error::ExpiredTransaction)); // Move the round forward. mock.runtime_header.round = 15; // Authenticate transaction, should succeed. - let mut ctx = mock.create_ctx(); - Accounts::authenticate_tx(&mut ctx, &tx).expect("tx should be valid"); + let ctx = mock.create_ctx(); + Accounts::authenticate_tx(&ctx, &tx).expect("tx should be valid"); // Move the round forward and also update the transaction nonce. mock.runtime_header.round = 50; tx.auth_info.signer_info[0].nonce = 1; // Authenticate transaction, should be expired. - let mut ctx = mock.create_ctx(); - let err = Accounts::authenticate_tx(&mut ctx, &tx).expect_err("tx should be expired"); + let ctx = mock.create_ctx(); + let err = Accounts::authenticate_tx(&ctx, &tx).expect_err("tx should be expired"); assert!(matches!(err, core::Error::ExpiredTransaction)); } #[test] fn test_fee_disbursement_2() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx_for_runtime::(context::Mode::ExecuteTx, false); + let ctx = mock.create_ctx_for_runtime::(false); let mut signer = mock::Signer::new(0, keys::alice::sigspec()); - TestRuntime::migrate(&mut ctx); + TestRuntime::migrate(&ctx); // Do a simple transfer. let dispatch_result = signer.call_opts( - &mut ctx, + &ctx, "accounts.Transfer", Transfer { to: keys::bob::address(), @@ -1348,15 +1347,15 @@ fn test_fee_disbursement_2() { #[test] fn test_fee_refund() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx_for_runtime::(context::Mode::ExecuteTx, false); + let ctx = mock.create_ctx_for_runtime::(false); let mut signer = mock::Signer::new(0, keys::alice::sigspec()); - TestRuntime::migrate(&mut ctx); + TestRuntime::migrate(&ctx); // Test refund on success and failure. for fail in [false, true] { let dispatch_result = signer.call_opts( - &mut ctx, + &ctx, "test.RefundFee", fail, mock::CallOptions { @@ -1404,14 +1403,14 @@ fn test_fee_refund() { #[test] fn test_fee_refund_subcall() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx_for_runtime::(context::Mode::ExecuteTx, false); + let ctx = mock.create_ctx_for_runtime::(false); let mut signer = mock::Signer::new(0, keys::alice::sigspec()); - TestRuntime::migrate(&mut ctx); + TestRuntime::migrate(&ctx); // Make sure that having a subcall that refunds fees does not affect the transaction. let dispatch_result = signer.call_opts( - &mut ctx, + &ctx, "test.Subcall", (), mock::CallOptions { diff --git a/runtime-sdk/src/modules/consensus/mod.rs b/runtime-sdk/src/modules/consensus/mod.rs index 1b6daae6f0..ca2c1a6e73 100644 --- a/runtime-sdk/src/modules/consensus/mod.rs +++ b/runtime-sdk/src/modules/consensus/mod.rs @@ -24,13 +24,14 @@ use oasis_core_runtime::{ }; use crate::{ - context::{Context, TxContext}, + context::Context, core::common::crypto::hash::Hash, history, migration, module, module::{Module as _, Parameters as _}, modules, modules::core::API as _, sdk_derive, + state::CurrentState, types::{ address::{Address, SignatureAddressSpec}, message::MessageEventHookInvocation, @@ -155,42 +156,42 @@ pub enum Error { /// Interface that can be called from other modules. pub trait API { /// Transfer an amount from the runtime account. - fn transfer( - ctx: &mut C, + fn transfer( + ctx: &C, to: Address, amount: &token::BaseUnits, hook: MessageEventHookInvocation, ) -> Result<(), Error>; /// Withdraw an amount into the runtime account. - fn withdraw( - ctx: &mut C, + fn withdraw( + ctx: &C, from: Address, amount: &token::BaseUnits, hook: MessageEventHookInvocation, ) -> Result<(), Error>; /// Escrow an amount of the runtime account funds. - fn escrow( - ctx: &mut C, + fn escrow( + ctx: &C, to: Address, amount: &token::BaseUnits, hook: MessageEventHookInvocation, ) -> Result<(), Error>; /// Reclaim an amount of runtime staked shares. - fn reclaim_escrow( - ctx: &mut C, + fn reclaim_escrow( + ctx: &C, from: Address, amount: u128, hook: MessageEventHookInvocation, ) -> Result<(), Error>; /// Returns consensus token denomination. - fn consensus_denomination(ctx: &mut C) -> Result; + fn consensus_denomination() -> Result; /// Ensures transaction signer is consensus compatible. - fn ensure_compatible_tx_signer(ctx: &C) -> Result<(), Error>; + fn ensure_compatible_tx_signer() -> Result<(), Error>; /// Query consensus account info. fn account(ctx: &C, addr: Address) -> Result; @@ -203,10 +204,10 @@ pub trait API { ) -> Result; /// Convert runtime amount to consensus amount, scaling as needed. - fn amount_from_consensus(ctx: &mut C, amount: u128) -> Result; + fn amount_from_consensus(ctx: &C, amount: u128) -> Result; /// Convert consensus amount to runtime amount, scaling as needed. - fn amount_to_consensus(ctx: &mut C, amount: u128) -> Result; + fn amount_to_consensus(ctx: &C, amount: u128) -> Result; /// Determine consensus height corresponding to the given epoch transition. This query may be /// expensive in case the epoch is far back. @@ -223,11 +224,8 @@ pub trait API { pub struct Module; impl Module { - fn ensure_consensus_denomination( - ctx: &mut C, - denomination: &token::Denomination, - ) -> Result<(), Error> { - if denomination != &Self::consensus_denomination(ctx)? { + fn ensure_consensus_denomination(denomination: &token::Denomination) -> Result<(), Error> { + if denomination != &Self::consensus_denomination()? { return Err(Error::InvalidDenomination); } @@ -257,12 +255,12 @@ impl Module { } #[handler(call = "consensus.RoundRoot", internal)] - fn internal_round_root( - ctx: &mut C, + fn internal_round_root( + ctx: &C, body: types::RoundRootBody, ) -> Result, Error> { let params = Self::params(); - ::Core::use_tx_gas(ctx, params.gas_costs.round_root)?; + ::Core::use_tx_gas(params.gas_costs.round_root)?; Ok( Self::round_roots(ctx, body.runtime_id, body.round)?.map(|rr| match body.kind { @@ -274,108 +272,120 @@ impl Module { } impl API for Module { - fn transfer( - ctx: &mut C, + fn transfer( + ctx: &C, to: Address, amount: &token::BaseUnits, hook: MessageEventHookInvocation, ) -> Result<(), Error> { - Self::ensure_consensus_denomination(ctx, amount.denomination())?; + Self::ensure_consensus_denomination(amount.denomination())?; let amount = Self::amount_to_consensus(ctx, amount.amount())?; - ctx.emit_message( - Message::Staking(Versioned::new( - 0, - StakingMessage::Transfer(staking::Transfer { - to: to.into(), - amount: amount.into(), - }), - )), - hook, - )?; + CurrentState::with(|state| { + state.emit_message( + ctx, + Message::Staking(Versioned::new( + 0, + StakingMessage::Transfer(staking::Transfer { + to: to.into(), + amount: amount.into(), + }), + )), + hook, + ) + })?; Ok(()) } - fn withdraw( - ctx: &mut C, + fn withdraw( + ctx: &C, from: Address, amount: &token::BaseUnits, hook: MessageEventHookInvocation, ) -> Result<(), Error> { - Self::ensure_consensus_denomination(ctx, amount.denomination())?; + Self::ensure_consensus_denomination(amount.denomination())?; let amount = Self::amount_to_consensus(ctx, amount.amount())?; - ctx.emit_message( - Message::Staking(Versioned::new( - 0, - StakingMessage::Withdraw(staking::Withdraw { - from: from.into(), - amount: amount.into(), - }), - )), - hook, - )?; + CurrentState::with(|state| { + state.emit_message( + ctx, + Message::Staking(Versioned::new( + 0, + StakingMessage::Withdraw(staking::Withdraw { + from: from.into(), + amount: amount.into(), + }), + )), + hook, + ) + })?; Ok(()) } - fn escrow( - ctx: &mut C, + fn escrow( + ctx: &C, to: Address, amount: &token::BaseUnits, hook: MessageEventHookInvocation, ) -> Result<(), Error> { - Self::ensure_consensus_denomination(ctx, amount.denomination())?; + Self::ensure_consensus_denomination(amount.denomination())?; let amount = Self::amount_to_consensus(ctx, amount.amount())?; if amount < Self::params().min_delegate_amount { return Err(Error::UnderMinDelegationAmount); } - ctx.emit_message( - Message::Staking(Versioned::new( - 0, - StakingMessage::AddEscrow(staking::Escrow { - account: to.into(), - amount: amount.into(), - }), - )), - hook, - )?; + CurrentState::with(|state| { + state.emit_message( + ctx, + Message::Staking(Versioned::new( + 0, + StakingMessage::AddEscrow(staking::Escrow { + account: to.into(), + amount: amount.into(), + }), + )), + hook, + ) + })?; Ok(()) } - fn reclaim_escrow( - ctx: &mut C, + fn reclaim_escrow( + ctx: &C, from: Address, shares: u128, hook: MessageEventHookInvocation, ) -> Result<(), Error> { - ctx.emit_message( - Message::Staking(Versioned::new( - 0, - StakingMessage::ReclaimEscrow(staking::ReclaimEscrow { - account: from.into(), - shares: shares.into(), - }), - )), - hook, - )?; + CurrentState::with(|state| { + state.emit_message( + ctx, + Message::Staking(Versioned::new( + 0, + StakingMessage::ReclaimEscrow(staking::ReclaimEscrow { + account: from.into(), + shares: shares.into(), + }), + )), + hook, + ) + })?; Ok(()) } - fn consensus_denomination(_ctx: &mut C) -> Result { + fn consensus_denomination() -> Result { Ok(Self::params().consensus_denomination) } - fn ensure_compatible_tx_signer(ctx: &C) -> Result<(), Error> { - match ctx.tx_auth_info().signer_info[0].address_spec { + fn ensure_compatible_tx_signer() -> Result<(), Error> { + CurrentState::with_env(|env| match env.tx_auth_info().signer_info[0].address_spec { AddressSpec::Signature(SignatureAddressSpec::Ed25519(_)) => Ok(()), _ => Err(Error::ConsensusIncompatibleSigner), - } + }) } fn account(ctx: &C, addr: Address) -> Result { @@ -396,14 +406,14 @@ impl API for Module { .map_err(Error::InternalStateError) } - fn amount_from_consensus(_ctx: &mut C, amount: u128) -> Result { + fn amount_from_consensus(_ctx: &C, amount: u128) -> Result { let scaling_factor = Self::params().consensus_scaling_factor; amount .checked_mul(scaling_factor.into()) .ok_or(Error::AmountNotRepresentable) } - fn amount_to_consensus(_ctx: &mut C, amount: u128) -> Result { + fn amount_to_consensus(_ctx: &C, amount: u128) -> Result { let scaling_factor = Self::params().consensus_scaling_factor; let scaled = amount .checked_div(scaling_factor.into()) diff --git a/runtime-sdk/src/modules/consensus/test.rs b/runtime-sdk/src/modules/consensus/test.rs index 6df9c7df80..e99b86b7a9 100644 --- a/runtime-sdk/src/modules/consensus/test.rs +++ b/runtime-sdk/src/modules/consensus/test.rs @@ -9,9 +9,9 @@ use oasis_core_runtime::{ }; use crate::{ - context::{BatchContext, Context}, module::Module as _, modules::consensus::Module as Consensus, + state::CurrentState, testing::{keys, mock}, types::{ message::MessageEventHookInvocation, @@ -24,383 +24,361 @@ use super::{Error, Genesis, Parameters, API as _}; #[test] fn test_api_transfer_invalid_denomination() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); - - ctx.with_tx(mock::transaction().into(), |mut tx_ctx, _call| { - let hook_name = "test_event_handler"; - let amount = BaseUnits::new(1_000, Denomination::NATIVE); - - assert!(Consensus::transfer( - &mut tx_ctx, - keys::alice::address(), - &amount, - MessageEventHookInvocation::new(hook_name.to_string(), 0), - ) - .is_err()); - }); + let ctx = mock.create_ctx(); + + let hook_name = "test_event_handler"; + let amount = BaseUnits::new(1_000, Denomination::NATIVE); + + assert!(Consensus::transfer( + &ctx, + keys::alice::address(), + &amount, + MessageEventHookInvocation::new(hook_name.to_string(), 0), + ) + .is_err()); } #[test] fn test_api_transfer() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); - - ctx.with_tx(mock::transaction().into(), |mut tx_ctx, _call| { - let hook_name = "test_event_handler"; - let amount = BaseUnits::new(1_000, Denomination::from_str("TEST").unwrap()); - Consensus::transfer( - &mut tx_ctx, - keys::alice::address(), - &amount, - MessageEventHookInvocation::new(hook_name.to_string(), 0), - ) - .expect("transfer should succeed"); - - let state = tx_ctx.commit(); - assert_eq!(1, state.messages.len(), "one message should be emitted"); - let (msg, hook) = state.messages.first().unwrap(); - - assert_eq!( - &Message::Staking(Versioned::new( - 0, - StakingMessage::Transfer(staking::Transfer { - to: keys::alice::address().into(), - amount: amount.amount().into(), - }) - )), - msg, - "emitted message should match" - ); - - assert_eq!( - hook_name.to_string(), - hook.hook_name, - "emitted hook should match" - ) - }); + let ctx = mock.create_ctx(); + + let hook_name = "test_event_handler"; + let amount = BaseUnits::new(1_000, Denomination::from_str("TEST").unwrap()); + Consensus::transfer( + &ctx, + keys::alice::address(), + &amount, + MessageEventHookInvocation::new(hook_name.to_string(), 0), + ) + .expect("transfer should succeed"); + + let messages = CurrentState::with(|state| state.take_messages()); + assert_eq!(1, messages.len(), "one message should be emitted"); + let (msg, hook) = messages.first().unwrap(); + + assert_eq!( + &Message::Staking(Versioned::new( + 0, + StakingMessage::Transfer(staking::Transfer { + to: keys::alice::address().into(), + amount: amount.amount().into(), + }) + )), + msg, + "emitted message should match" + ); + + assert_eq!( + hook_name.to_string(), + hook.hook_name, + "emitted hook should match" + ); } #[test] fn test_api_transfer_scaling_unrepresentable() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); + let ctx = mock.create_ctx(); Consensus::set_params(Parameters { consensus_scaling_factor: 1_000, // Everything is multiplied by 1000. ..Default::default() }); - ctx.with_tx(mock::transaction().into(), |mut tx_ctx, _call| { - let hook_name = "test_event_handler"; - // Amount is not representable as it must be in multiples of 1000. - let amount = BaseUnits::new(500, Denomination::from_str("TEST").unwrap()); - - assert!(Consensus::transfer( - &mut tx_ctx, - keys::alice::address(), - &amount, - MessageEventHookInvocation::new(hook_name.to_string(), 0), - ) - .is_err()); - }); + let hook_name = "test_event_handler"; + // Amount is not representable as it must be in multiples of 1000. + let amount = BaseUnits::new(500, Denomination::from_str("TEST").unwrap()); + + assert!(Consensus::transfer( + &ctx, + keys::alice::address(), + &amount, + MessageEventHookInvocation::new(hook_name.to_string(), 0), + ) + .is_err()); } #[test] fn test_api_transfer_scaling() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); + let ctx = mock.create_ctx(); Consensus::set_params(Parameters { consensus_scaling_factor: 1_000, // Everything is multiplied by 1000. ..Default::default() }); - ctx.with_tx(mock::transaction().into(), |mut tx_ctx, _call| { - let hook_name = "test_event_handler"; - let amount = BaseUnits::new(1_000, Denomination::from_str("TEST").unwrap()); - Consensus::transfer( - &mut tx_ctx, - keys::alice::address(), - &amount, - MessageEventHookInvocation::new(hook_name.to_string(), 0), - ) - .expect("transfer should succeed"); - - let state = tx_ctx.commit(); - assert_eq!(1, state.messages.len(), "one message should be emitted"); - let (msg, hook) = state.messages.first().unwrap(); - - assert_eq!( - &Message::Staking(Versioned::new( - 0, - StakingMessage::Transfer(staking::Transfer { - to: keys::alice::address().into(), - // Amount should be properly scaled. - amount: 1u128.into(), - }) - )), - msg, - "emitted message should match" - ); - - assert_eq!( - hook_name.to_string(), - hook.hook_name, - "emitted hook should match" - ) - }); + let hook_name = "test_event_handler"; + let amount = BaseUnits::new(1_000, Denomination::from_str("TEST").unwrap()); + Consensus::transfer( + &ctx, + keys::alice::address(), + &amount, + MessageEventHookInvocation::new(hook_name.to_string(), 0), + ) + .expect("transfer should succeed"); + + let messages = CurrentState::with(|state| state.take_messages()); + assert_eq!(1, messages.len(), "one message should be emitted"); + let (msg, hook) = messages.first().unwrap(); + + assert_eq!( + &Message::Staking(Versioned::new( + 0, + StakingMessage::Transfer(staking::Transfer { + to: keys::alice::address().into(), + // Amount should be properly scaled. + amount: 1u128.into(), + }) + )), + msg, + "emitted message should match" + ); + + assert_eq!( + hook_name.to_string(), + hook.hook_name, + "emitted hook should match" + ); } #[test] fn test_api_withdraw() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); - - ctx.with_tx(mock::transaction().into(), |mut tx_ctx, _call| { - let hook_name = "test_event_handler"; - let amount = BaseUnits::new(1_000, Denomination::from_str("TEST").unwrap()); - Consensus::withdraw( - &mut tx_ctx, - keys::alice::address(), - &amount, - MessageEventHookInvocation::new(hook_name.to_string(), 0), - ) - .expect("withdraw should succeed"); - - let state = tx_ctx.commit(); - assert_eq!(1, state.messages.len(), "one message should be emitted"); - let (msg, hook) = state.messages.first().unwrap(); - - assert_eq!( - &Message::Staking(Versioned::new( - 0, - StakingMessage::Withdraw(staking::Withdraw { - from: keys::alice::address().into(), - amount: amount.amount().into(), - }) - )), - msg, - "emitted message should match" - ); - - assert_eq!( - hook_name.to_string(), - hook.hook_name, - "emitted hook should match" - ) - }); + let ctx = mock.create_ctx(); + + let hook_name = "test_event_handler"; + let amount = BaseUnits::new(1_000, Denomination::from_str("TEST").unwrap()); + Consensus::withdraw( + &ctx, + keys::alice::address(), + &amount, + MessageEventHookInvocation::new(hook_name.to_string(), 0), + ) + .expect("withdraw should succeed"); + + let messages = CurrentState::with(|state| state.take_messages()); + assert_eq!(1, messages.len(), "one message should be emitted"); + let (msg, hook) = messages.first().unwrap(); + + assert_eq!( + &Message::Staking(Versioned::new( + 0, + StakingMessage::Withdraw(staking::Withdraw { + from: keys::alice::address().into(), + amount: amount.amount().into(), + }) + )), + msg, + "emitted message should match" + ); + + assert_eq!( + hook_name.to_string(), + hook.hook_name, + "emitted hook should match" + ); } #[test] fn test_api_withdraw_scaling() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); + let ctx = mock.create_ctx(); Consensus::set_params(Parameters { consensus_scaling_factor: 1_000, // Everything is multiplied by 1000. ..Default::default() }); - ctx.with_tx(mock::transaction().into(), |mut tx_ctx, _call| { - let hook_name = "test_event_handler"; - let amount = BaseUnits::new(1_000, Denomination::from_str("TEST").unwrap()); - Consensus::withdraw( - &mut tx_ctx, - keys::alice::address(), - &amount, - MessageEventHookInvocation::new(hook_name.to_string(), 0), - ) - .expect("withdraw should succeed"); - - let state = tx_ctx.commit(); - assert_eq!(1, state.messages.len(), "one message should be emitted"); - let (msg, hook) = state.messages.first().unwrap(); - - assert_eq!( - &Message::Staking(Versioned::new( - 0, - StakingMessage::Withdraw(staking::Withdraw { - from: keys::alice::address().into(), - amount: 1u128.into(), - }) - )), - msg, - "emitted message should match" - ); - - assert_eq!( - hook_name.to_string(), - hook.hook_name, - "emitted hook should match" - ) - }); + let hook_name = "test_event_handler"; + let amount = BaseUnits::new(1_000, Denomination::from_str("TEST").unwrap()); + Consensus::withdraw( + &ctx, + keys::alice::address(), + &amount, + MessageEventHookInvocation::new(hook_name.to_string(), 0), + ) + .expect("withdraw should succeed"); + + let messages = CurrentState::with(|state| state.take_messages()); + assert_eq!(1, messages.len(), "one message should be emitted"); + let (msg, hook) = messages.first().unwrap(); + + assert_eq!( + &Message::Staking(Versioned::new( + 0, + StakingMessage::Withdraw(staking::Withdraw { + from: keys::alice::address().into(), + amount: 1u128.into(), + }) + )), + msg, + "emitted message should match" + ); + + assert_eq!( + hook_name.to_string(), + hook.hook_name, + "emitted hook should match" + ); } #[test] fn test_api_escrow() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); - - ctx.with_tx(mock::transaction().into(), |mut tx_ctx, _call| { - let hook_name = "test_event_handler"; - let amount = BaseUnits::new(1_000, Denomination::from_str("TEST").unwrap()); - Consensus::escrow( - &mut tx_ctx, - keys::alice::address(), - &amount, - MessageEventHookInvocation::new(hook_name.to_string(), 0), - ) - .expect("escrow should succeed"); - - let state = tx_ctx.commit(); - assert_eq!(1, state.messages.len(), "one message should be emitted"); - let (msg, hook) = state.messages.first().unwrap(); - - assert_eq!( - &Message::Staking(Versioned::new( - 0, - StakingMessage::AddEscrow(staking::Escrow { - account: keys::alice::address().into(), - amount: amount.amount().into(), - }) - )), - msg, - "emitted message should match" - ); - - assert_eq!( - hook_name.to_string(), - hook.hook_name, - "emitted hook should match" - ) - }); + let ctx = mock.create_ctx(); + + let hook_name = "test_event_handler"; + let amount = BaseUnits::new(1_000, Denomination::from_str("TEST").unwrap()); + Consensus::escrow( + &ctx, + keys::alice::address(), + &amount, + MessageEventHookInvocation::new(hook_name.to_string(), 0), + ) + .expect("escrow should succeed"); + + let messages = CurrentState::with(|state| state.take_messages()); + assert_eq!(1, messages.len(), "one message should be emitted"); + let (msg, hook) = messages.first().unwrap(); + + assert_eq!( + &Message::Staking(Versioned::new( + 0, + StakingMessage::AddEscrow(staking::Escrow { + account: keys::alice::address().into(), + amount: amount.amount().into(), + }) + )), + msg, + "emitted message should match" + ); + + assert_eq!( + hook_name.to_string(), + hook.hook_name, + "emitted hook should match" + ); } #[test] fn test_api_escrow_min_delegate_amount() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); + let ctx = mock.create_ctx(); Consensus::set_params(Parameters { min_delegate_amount: 10, ..Default::default() }); - ctx.with_tx(mock::transaction().into(), |mut tx_ctx, _call| { - let hook_name = "test_event_handler"; - let amount = BaseUnits::new(5, Denomination::from_str("TEST").unwrap()); - let result = Consensus::escrow( - &mut tx_ctx, - keys::alice::address(), - &amount, - MessageEventHookInvocation::new(hook_name.to_string(), 0), - ); - - assert!(matches!(result, Err(Error::UnderMinDelegationAmount))); - }); + let hook_name = "test_event_handler"; + let amount = BaseUnits::new(5, Denomination::from_str("TEST").unwrap()); + let result = Consensus::escrow( + &ctx, + keys::alice::address(), + &amount, + MessageEventHookInvocation::new(hook_name.to_string(), 0), + ); - ctx.with_tx(mock::transaction().into(), |mut tx_ctx, _call| { - let hook_name = "test_event_handler"; - let amount = BaseUnits::new(15, Denomination::from_str("TEST").unwrap()); - let result = Consensus::escrow( - &mut tx_ctx, - keys::alice::address(), - &amount, - MessageEventHookInvocation::new(hook_name.to_string(), 0), - ); - - assert!(result.is_ok()); - }); + assert!(matches!(result, Err(Error::UnderMinDelegationAmount))); + + let hook_name = "test_event_handler"; + let amount = BaseUnits::new(15, Denomination::from_str("TEST").unwrap()); + let result = Consensus::escrow( + &ctx, + keys::alice::address(), + &amount, + MessageEventHookInvocation::new(hook_name.to_string(), 0), + ); + + assert!(result.is_ok()); } #[test] fn test_api_escrow_scaling() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); + let ctx = mock.create_ctx(); Consensus::set_params(Parameters { consensus_scaling_factor: 1_000, // Everything is multiplied by 1000. ..Default::default() }); - ctx.with_tx(mock::transaction().into(), |mut tx_ctx, _call| { - let hook_name = "test_event_handler"; - let amount = BaseUnits::new(1_000, Denomination::from_str("TEST").unwrap()); - Consensus::escrow( - &mut tx_ctx, - keys::alice::address(), - &amount, - MessageEventHookInvocation::new(hook_name.to_string(), 0), - ) - .expect("escrow should succeed"); - - let state = tx_ctx.commit(); - assert_eq!(1, state.messages.len(), "one message should be emitted"); - let (msg, hook) = state.messages.first().unwrap(); - - assert_eq!( - &Message::Staking(Versioned::new( - 0, - StakingMessage::AddEscrow(staking::Escrow { - account: keys::alice::address().into(), - amount: 1u128.into(), - }) - )), - msg, - "emitted message should match" - ); - - assert_eq!( - hook_name.to_string(), - hook.hook_name, - "emitted hook should match" - ) - }); + let hook_name = "test_event_handler"; + let amount = BaseUnits::new(1_000, Denomination::from_str("TEST").unwrap()); + Consensus::escrow( + &ctx, + keys::alice::address(), + &amount, + MessageEventHookInvocation::new(hook_name.to_string(), 0), + ) + .expect("escrow should succeed"); + + let messages = CurrentState::with(|state| state.take_messages()); + assert_eq!(1, messages.len(), "one message should be emitted"); + let (msg, hook) = messages.first().unwrap(); + + assert_eq!( + &Message::Staking(Versioned::new( + 0, + StakingMessage::AddEscrow(staking::Escrow { + account: keys::alice::address().into(), + amount: 1u128.into(), + }) + )), + msg, + "emitted message should match" + ); + + assert_eq!( + hook_name.to_string(), + hook.hook_name, + "emitted hook should match" + ); } #[test] fn test_api_reclaim_escrow() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); + let ctx = mock.create_ctx(); Consensus::set_params(Parameters { consensus_scaling_factor: 1_000, // NOTE: Should be ignored for share amounts. ..Default::default() }); - ctx.with_tx(mock::transaction().into(), |mut tx_ctx, _call| { - let hook_name = "test_event_handler"; - let amount = 1_000u128; - Consensus::reclaim_escrow( - &mut tx_ctx, - keys::alice::address(), - amount, - MessageEventHookInvocation::new(hook_name.to_string(), 0), - ) - .expect("reclaim escrow should succeed"); - - let state = tx_ctx.commit(); - assert_eq!(1, state.messages.len(), "one message should be emitted"); - let (msg, hook) = state.messages.first().unwrap(); - - assert_eq!( - &Message::Staking(Versioned::new( - 0, - StakingMessage::ReclaimEscrow(staking::ReclaimEscrow { - account: keys::alice::address().into(), - shares: amount.into(), - }) - )), - msg, - "emitted message should match" - ); - - assert_eq!( - hook_name.to_string(), - hook.hook_name, - "emitted hook should match" - ) - }); + let hook_name = "test_event_handler"; + let amount = 1_000u128; + Consensus::reclaim_escrow( + &ctx, + keys::alice::address(), + amount, + MessageEventHookInvocation::new(hook_name.to_string(), 0), + ) + .expect("reclaim escrow should succeed"); + + let messages = CurrentState::with(|state| state.take_messages()); + assert_eq!(1, messages.len(), "one message should be emitted"); + let (msg, hook) = messages.first().unwrap(); + + assert_eq!( + &Message::Staking(Versioned::new( + 0, + StakingMessage::ReclaimEscrow(staking::ReclaimEscrow { + account: keys::alice::address().into(), + shares: amount.into(), + }) + )), + msg, + "emitted message should match" + ); + + assert_eq!( + hook_name.to_string(), + hook.hook_name, + "emitted hook should match" + ); } #[test] @@ -421,7 +399,7 @@ fn test_api_account() { #[test] fn test_api_scaling() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); + let ctx = mock.create_ctx(); Consensus::set_params(Parameters { consensus_scaling_factor: 1_000, // Everything is multiplied by 1000. @@ -429,32 +407,29 @@ fn test_api_scaling() { }); // Not representable. - Consensus::amount_to_consensus(&mut ctx, 100).unwrap_err(); - Consensus::amount_to_consensus(&mut ctx, 1100).unwrap_err(); - Consensus::amount_to_consensus(&mut ctx, 2500).unwrap_err(); - Consensus::amount_to_consensus(&mut ctx, 2500).unwrap_err(); - Consensus::amount_to_consensus(&mut ctx, 1_000_250).unwrap_err(); - Consensus::amount_to_consensus(&mut ctx, 1_000_001).unwrap_err(); + Consensus::amount_to_consensus(&ctx, 100).unwrap_err(); + Consensus::amount_to_consensus(&ctx, 1100).unwrap_err(); + Consensus::amount_to_consensus(&ctx, 2500).unwrap_err(); + Consensus::amount_to_consensus(&ctx, 2500).unwrap_err(); + Consensus::amount_to_consensus(&ctx, 1_000_250).unwrap_err(); + Consensus::amount_to_consensus(&ctx, 1_000_001).unwrap_err(); // Scaling. - assert_eq!(Consensus::amount_to_consensus(&mut ctx, 0).unwrap(), 0); - assert_eq!(Consensus::amount_to_consensus(&mut ctx, 1000).unwrap(), 1); - assert_eq!(Consensus::amount_to_consensus(&mut ctx, 2000).unwrap(), 2); + assert_eq!(Consensus::amount_to_consensus(&ctx, 0).unwrap(), 0); + assert_eq!(Consensus::amount_to_consensus(&ctx, 1000).unwrap(), 1); + assert_eq!(Consensus::amount_to_consensus(&ctx, 2000).unwrap(), 2); assert_eq!( - Consensus::amount_to_consensus(&mut ctx, 1_000_000).unwrap(), + Consensus::amount_to_consensus(&ctx, 1_000_000).unwrap(), 1000 ); assert_eq!( - Consensus::amount_to_consensus(&mut ctx, 1_234_000).unwrap(), + Consensus::amount_to_consensus(&ctx, 1_234_000).unwrap(), 1234 ); - assert_eq!(Consensus::amount_from_consensus(&mut ctx, 0).unwrap(), 0); - assert_eq!(Consensus::amount_from_consensus(&mut ctx, 1).unwrap(), 1000); + assert_eq!(Consensus::amount_from_consensus(&ctx, 0).unwrap(), 0); + assert_eq!(Consensus::amount_from_consensus(&ctx, 1).unwrap(), 1000); + assert_eq!(Consensus::amount_from_consensus(&ctx, 10).unwrap(), 10_000); assert_eq!( - Consensus::amount_from_consensus(&mut ctx, 10).unwrap(), - 10_000 - ); - assert_eq!( - Consensus::amount_from_consensus(&mut ctx, 1000).unwrap(), + Consensus::amount_from_consensus(&ctx, 1000).unwrap(), 1_000_000 ); } @@ -462,7 +437,7 @@ fn test_api_scaling() { #[test] fn test_query_parameters() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); + let ctx = mock.create_ctx(); let params = Parameters { gas_costs: Default::default(), @@ -472,7 +447,7 @@ fn test_query_parameters() { }; Consensus::set_params(params.clone()); - let queried_params = Consensus::query_parameters(&mut ctx, ()).unwrap(); + let queried_params = Consensus::query_parameters(&ctx, ()).unwrap(); assert_eq!(queried_params, params); } diff --git a/runtime-sdk/src/modules/consensus_accounts/mod.rs b/runtime-sdk/src/modules/consensus_accounts/mod.rs index faf5015b73..4cb9119be1 100644 --- a/runtime-sdk/src/modules/consensus_accounts/mod.rs +++ b/runtime-sdk/src/modules/consensus_accounts/mod.rs @@ -18,12 +18,13 @@ use oasis_core_runtime::{ use oasis_runtime_sdk_macros::{handler, sdk_derive}; use crate::{ - context::{Context, TxContext}, + context::Context, error, migration, module, module::Module as _, modules, modules::core::{Error as CoreError, API as _}, runtime::Runtime, + state::CurrentState, storage::Prefix, types::{ address::Address, @@ -169,8 +170,8 @@ pub trait API { /// /// * `nonce`: A caller-provided sequence number that will help identify the success/fail events. /// When called from a deposit transaction, we use the signer nonce. - fn deposit( - ctx: &mut C, + fn deposit( + ctx: &C, from: Address, nonce: u64, to: Address, @@ -183,8 +184,8 @@ pub trait API { /// /// * `nonce`: A caller-provided sequence number that will help identify the success/fail events. /// When called from a withdraw transaction, we use the signer nonce. - fn withdraw( - ctx: &mut C, + fn withdraw( + ctx: &C, from: Address, nonce: u64, to: Address, @@ -197,8 +198,8 @@ pub trait API { /// /// * `nonce`: A caller-provided sequence number that will help identify the success/fail events. /// When called from a delegate transaction, we use the signer nonce. - fn delegate( - ctx: &mut C, + fn delegate( + ctx: &C, from: Address, nonce: u64, to: Address, @@ -213,8 +214,8 @@ pub trait API { /// /// * `nonce`: A caller-provided sequence number that will help identify the success/fail events. /// When called from an undelegate transaction, we use the signer nonce. - fn undelegate( - ctx: &mut C, + fn undelegate( + ctx: &C, from: Address, nonce: u64, to: Address, @@ -244,8 +245,8 @@ const CONSENSUS_UNDELEGATE_HANDLER: &str = "consensus.Undelegate"; impl API for Module { - fn deposit( - ctx: &mut C, + fn deposit( + ctx: &C, from: Address, nonce: u64, to: Address, @@ -275,8 +276,8 @@ impl API Ok(()) } - fn withdraw( - ctx: &mut C, + fn withdraw( + ctx: &C, from: Address, nonce: u64, to: Address, @@ -298,20 +299,20 @@ impl API ), )?; - if ctx.is_check_only() { + if CurrentState::with_env(|env| env.is_check_only()) { return Ok(()); } // Transfer the given amount to the module's withdrawal account to make sure the tokens // remain available until actually withdrawn. - Accounts::transfer(ctx, from, *ADDRESS_PENDING_WITHDRAWAL, &amount) + Accounts::transfer(from, *ADDRESS_PENDING_WITHDRAWAL, &amount) .map_err(|_| Error::InsufficientBalance)?; Ok(()) } - fn delegate( - ctx: &mut C, + fn delegate( + ctx: &C, from: Address, nonce: u64, to: Address, @@ -334,20 +335,20 @@ impl API ), )?; - if ctx.is_check_only() { + if CurrentState::with_env(|env| env.is_check_only()) { return Ok(()); } // Transfer the given amount to the module's delegation account to make sure the tokens // remain available until actually delegated. - Accounts::transfer(ctx, from, *ADDRESS_PENDING_DELEGATION, &amount) + Accounts::transfer(from, *ADDRESS_PENDING_DELEGATION, &amount) .map_err(|_| Error::InsufficientBalance)?; Ok(()) } - fn undelegate( - ctx: &mut C, + fn undelegate( + ctx: &C, from: Address, nonce: u64, to: Address, @@ -396,17 +397,17 @@ impl /// Deposit in the runtime. #[handler(call = "consensus.Deposit")] - fn tx_deposit(ctx: &mut C, body: types::Deposit) -> Result<(), Error> { + fn tx_deposit(ctx: &C, body: types::Deposit) -> Result<(), Error> { let params = Self::params(); - ::Core::use_tx_gas(ctx, params.gas_costs.tx_deposit)?; + ::Core::use_tx_gas(params.gas_costs.tx_deposit)?; // Check whether deposit is allowed. if params.disable_deposit { return Err(Error::Forbidden); } - let signer = &ctx.tx_auth_info().signer_info[0]; - Consensus::ensure_compatible_tx_signer(ctx)?; + let signer = CurrentState::with_env(|env| env.tx_auth_info().signer_info[0].clone()); + Consensus::ensure_compatible_tx_signer()?; let address = signer.address_spec.address(); let nonce = signer.nonce; @@ -434,9 +435,9 @@ impl } #[handler(call = "consensus.Withdraw")] - fn tx_withdraw(ctx: &mut C, body: types::Withdraw) -> Result<(), Error> { + fn tx_withdraw(ctx: &C, body: types::Withdraw) -> Result<(), Error> { let params = Self::params(); - ::Core::use_tx_gas(ctx, params.gas_costs.tx_withdraw)?; + ::Core::use_tx_gas(params.gas_costs.tx_withdraw)?; // Check whether withdraw is allowed. if params.disable_withdraw { @@ -444,26 +445,26 @@ impl } // Signer. - let signer = &ctx.tx_auth_info().signer_info[0]; if body.to.is_none() { // If no `to` field is specified, i.e. withdrawing to the transaction sender's account, // only allow the consensus-compatible single-Ed25519-key signer type. Otherwise, the // tokens would get stuck in an account that you can't sign for on the consensus layer. - Consensus::ensure_compatible_tx_signer(ctx)?; + Consensus::ensure_compatible_tx_signer()?; } + let signer = CurrentState::with_env(|env| env.tx_auth_info().signer_info[0].clone()); let address = signer.address_spec.address(); let nonce = signer.nonce; Self::withdraw(ctx, address, nonce, body.to.unwrap_or(address), body.amount) } #[handler(call = "consensus.Delegate")] - fn tx_delegate(ctx: &mut C, body: types::Delegate) -> Result<(), Error> { + fn tx_delegate(ctx: &C, body: types::Delegate) -> Result<(), Error> { let params = Self::params(); - ::Core::use_tx_gas(ctx, params.gas_costs.tx_delegate)?; + ::Core::use_tx_gas(params.gas_costs.tx_delegate)?; let store_receipt = body.receipt > 0; if store_receipt { - ::Core::use_tx_gas(ctx, params.gas_costs.store_receipt)?; + ::Core::use_tx_gas(params.gas_costs.store_receipt)?; } // Check whether delegate is allowed. @@ -471,12 +472,12 @@ impl return Err(Error::Forbidden); } // Make sure receipts can only be requested internally (e.g. via subcalls). - if store_receipt && !ctx.is_internal() { + if store_receipt && !CurrentState::with_env(|env| env.is_internal()) { return Err(Error::InvalidArgument); } // Signer. - let signer = &ctx.tx_auth_info().signer_info[0]; + let signer = CurrentState::with_env(|env| env.tx_auth_info().signer_info[0].clone()); let from = signer.address_spec.address(); let nonce = if store_receipt { body.receipt // Use receipt identifier as the nonce. @@ -487,12 +488,12 @@ impl } #[handler(call = "consensus.Undelegate")] - fn tx_undelegate(ctx: &mut C, body: types::Undelegate) -> Result<(), Error> { + fn tx_undelegate(ctx: &C, body: types::Undelegate) -> Result<(), Error> { let params = Self::params(); - ::Core::use_tx_gas(ctx, params.gas_costs.tx_undelegate)?; + ::Core::use_tx_gas(params.gas_costs.tx_undelegate)?; let store_receipt = body.receipt > 0; if store_receipt { - ::Core::use_tx_gas(ctx, params.gas_costs.store_receipt)?; + ::Core::use_tx_gas(params.gas_costs.store_receipt)?; } // Check whether undelegate is allowed. @@ -500,12 +501,12 @@ impl return Err(Error::Forbidden); } // Make sure receipts can only be requested internally (e.g. via subcalls). - if store_receipt && !ctx.is_internal() { + if store_receipt && !CurrentState::with_env(|env| env.is_internal()) { return Err(Error::InvalidArgument); } // Signer. - let signer = &ctx.tx_auth_info().signer_info[0]; + let signer = CurrentState::with_env(|env| env.tx_auth_info().signer_info[0].clone()); let to = signer.address_spec.address(); let nonce = if store_receipt { body.receipt // Use receipt identifer as the nonce. @@ -516,19 +517,19 @@ impl } #[handler(call = "consensus.TakeReceipt", internal)] - fn internal_take_receipt( - ctx: &mut C, + fn internal_take_receipt( + _ctx: &C, body: types::TakeReceipt, ) -> Result, Error> { let params = Self::params(); - ::Core::use_tx_gas(ctx, params.gas_costs.take_receipt)?; + ::Core::use_tx_gas(params.gas_costs.take_receipt)?; if !body.kind.is_valid() { return Err(Error::InvalidArgument); } Ok(state::take_receipt( - ctx.tx_caller_address(), + CurrentState::with_env(|env| env.tx_caller_address()), body.kind, body.id, )) @@ -536,10 +537,10 @@ impl #[handler(query = "consensus.Balance")] fn query_balance( - ctx: &mut C, + _ctx: &C, args: types::BalanceQuery, ) -> Result { - let denomination = Consensus::consensus_denomination(ctx)?; + let denomination = Consensus::consensus_denomination()?; let balances = Accounts::get_balances(args.address).map_err(|_| Error::InvalidArgument)?; let balance = balances .balances @@ -551,7 +552,7 @@ impl #[handler(query = "consensus.Account")] fn query_consensus_account( - ctx: &mut C, + ctx: &C, args: types::ConsensusAccountQuery, ) -> Result { Consensus::account(ctx, args.address).map_err(|_| Error::InvalidArgument) @@ -559,7 +560,7 @@ impl #[handler(query = "consensus.Delegation")] fn query_delegation( - _ctx: &mut C, + _ctx: &C, args: types::DelegationQuery, ) -> Result { state::get_delegation(args.from, args.to) @@ -567,7 +568,7 @@ impl #[handler(query = "consensus.Delegations")] fn query_delegations( - _ctx: &mut C, + _ctx: &C, args: types::DelegationsQuery, ) -> Result, Error> { state::get_delegations(args.from) @@ -575,7 +576,7 @@ impl #[handler(query = "consensus.Undelegations")] fn query_undelegations( - _ctx: &mut C, + _ctx: &C, args: types::UndelegationsQuery, ) -> Result, Error> { state::get_undelegations(args.to) @@ -583,14 +584,13 @@ impl #[handler(message_result = CONSENSUS_TRANSFER_HANDLER)] fn message_result_transfer( - ctx: &mut C, + ctx: &C, me: MessageEvent, context: types::ConsensusTransferContext, ) { if !me.is_success() { // Transfer out failed, refund the balance. Accounts::transfer( - ctx, *ADDRESS_PENDING_WITHDRAWAL, context.address, &context.amount, @@ -598,76 +598,79 @@ impl .expect("should have enough balance"); // Emit withdraw failed event. - ctx.emit_event(Event::Withdraw { - from: context.address, - nonce: context.nonce, - to: context.to, - amount: context.amount.clone(), - error: Some(me.into()), + CurrentState::with(|state| { + state.emit_event(Event::Withdraw { + from: context.address, + nonce: context.nonce, + to: context.to, + amount: context.amount.clone(), + error: Some(me.into()), + }); }); return; } // Burn the withdrawn tokens. - Accounts::burn(ctx, *ADDRESS_PENDING_WITHDRAWAL, &context.amount) + Accounts::burn(*ADDRESS_PENDING_WITHDRAWAL, &context.amount) .expect("should have enough balance"); // Emit withdraw successful event. - ctx.emit_event(Event::Withdraw { - from: context.address, - nonce: context.nonce, - to: context.to, - amount: context.amount.clone(), - error: None, + CurrentState::with(|state| { + state.emit_event(Event::Withdraw { + from: context.address, + nonce: context.nonce, + to: context.to, + amount: context.amount.clone(), + error: None, + }); }); } #[handler(message_result = CONSENSUS_WITHDRAW_HANDLER)] fn message_result_withdraw( - ctx: &mut C, + ctx: &C, me: MessageEvent, context: types::ConsensusWithdrawContext, ) { if !me.is_success() { // Transfer in failed, emit deposit failed event. - ctx.emit_event(Event::Deposit { - from: context.from, - nonce: context.nonce, - to: context.address, - amount: context.amount.clone(), - error: Some(me.into()), + CurrentState::with(|state| { + state.emit_event(Event::Deposit { + from: context.from, + nonce: context.nonce, + to: context.address, + amount: context.amount.clone(), + error: Some(me.into()), + }); }); return; } // Update runtime state. - Accounts::mint(ctx, context.address, &context.amount).unwrap(); + Accounts::mint(context.address, &context.amount).unwrap(); // Emit deposit successful event. - ctx.emit_event(Event::Deposit { - from: context.from, - nonce: context.nonce, - to: context.address, - amount: context.amount.clone(), - error: None, + CurrentState::with(|state| { + state.emit_event(Event::Deposit { + from: context.from, + nonce: context.nonce, + to: context.address, + amount: context.amount.clone(), + error: None, + }); }); } #[handler(message_result = CONSENSUS_DELEGATE_HANDLER)] fn message_result_delegate( - ctx: &mut C, + ctx: &C, me: MessageEvent, context: types::ConsensusDelegateContext, ) { if !me.is_success() { // Delegation failed, refund the balance. - Accounts::transfer( - ctx, - *ADDRESS_PENDING_DELEGATION, - context.from, - &context.amount, - ) - .expect("should have enough balance"); + Accounts::transfer(*ADDRESS_PENDING_DELEGATION, context.from, &context.amount) + .expect("should have enough balance"); // Store receipt if requested. if context.receipt { @@ -683,18 +686,20 @@ impl } // Emit delegation failed event. - ctx.emit_event(Event::Delegate { - from: context.from, - nonce: context.nonce, - to: context.to, - amount: context.amount, - error: Some(me.into()), + CurrentState::with(|state| { + state.emit_event(Event::Delegate { + from: context.from, + nonce: context.nonce, + to: context.to, + amount: context.amount, + error: Some(me.into()), + }); }); return; } // Burn the delegated tokens. - Accounts::burn(ctx, *ADDRESS_PENDING_DELEGATION, &context.amount) + Accounts::burn(*ADDRESS_PENDING_DELEGATION, &context.amount) .expect("should have enough balance"); // Record delegation. @@ -720,18 +725,20 @@ impl } // Emit delegation successful event. - ctx.emit_event(Event::Delegate { - from: context.from, - nonce: context.nonce, - to: context.to, - amount: context.amount, - error: None, + CurrentState::with(|state| { + state.emit_event(Event::Delegate { + from: context.from, + nonce: context.nonce, + to: context.to, + amount: context.amount, + error: None, + }); }); } #[handler(message_result = CONSENSUS_UNDELEGATE_HANDLER)] fn message_result_undelegate( - ctx: &mut C, + ctx: &C, me: MessageEvent, context: types::ConsensusUndelegateContext, ) { @@ -753,13 +760,15 @@ impl } // Emit undelegation failed event. - ctx.emit_event(Event::UndelegateStart { - from: context.from, - nonce: context.nonce, - to: context.to, - shares: context.shares, - debond_end_time: EPOCH_INVALID, - error: Some(me.into()), + CurrentState::with(|state| { + state.emit_event(Event::UndelegateStart { + from: context.from, + nonce: context.nonce, + to: context.to, + shares: context.shares, + debond_end_time: EPOCH_INVALID, + error: Some(me.into()), + }); }); return; } @@ -802,13 +811,15 @@ impl } // Emit undelegation started event. - ctx.emit_event(Event::UndelegateStart { - from: context.from, - nonce: context.nonce, - to: context.to, - shares: context.shares, - debond_end_time: result.debond_end_time, - error: None, + CurrentState::with(|state| { + state.emit_event(Event::UndelegateStart { + from: context.from, + nonce: context.nonce, + to: context.to, + shares: context.shares, + debond_end_time: result.debond_end_time, + error: None, + }); }); } } @@ -821,9 +832,9 @@ impl impl module::BlockHandler for Module { - fn end_block(ctx: &mut C) { + fn end_block(ctx: &C) { // Only do work in case the epoch has changed since the last processed block. - if !::Core::has_epoch_changed(ctx) { + if !::Core::has_epoch_changed() { return; } @@ -836,7 +847,7 @@ impl modul lru::LruCache::new(NonZeroUsize::new(128).unwrap()); let own_address = Address::from_runtime_id(ctx.runtime_id()); - let denomination = Consensus::consensus_denomination(ctx).unwrap(); + let denomination = Consensus::consensus_denomination().unwrap(); let qd = state::get_queued_undelegations(ctx.epoch()).unwrap(); for ud in qd { let udi = state::take_undelegation(&ud).unwrap(); @@ -899,7 +910,7 @@ impl modul let amount = token::BaseUnits::new(raw_amount, denomination.clone()); // Mint the given number of tokens. - Accounts::mint(ctx, ud.to, &amount).unwrap(); + Accounts::mint(ud.to, &amount).unwrap(); // Store receipt if requested. if udi.receipt > 0 { @@ -915,11 +926,13 @@ impl modul } // Emit undelegation done event. - ctx.emit_event(Event::UndelegateDone { - from: ud.from, - to: ud.to, - shares: udi.shares, - amount, + CurrentState::with(|state| { + state.emit_event(Event::UndelegateDone { + from: ud.from, + to: ud.to, + shares: udi.shares, + amount, + }); }); } } @@ -929,12 +942,12 @@ impl modul for Module { /// Check invariants. - fn check_invariants(ctx: &mut C) -> Result<(), CoreError> { + fn check_invariants(ctx: &C) -> Result<(), CoreError> { // Total supply of the designated consensus layer token denomination // should be less than or equal to the balance of the runtime's general // account in the consensus layer. - let den = Consensus::consensus_denomination(ctx).unwrap(); + let den = Consensus::consensus_denomination().unwrap(); #[allow(clippy::or_fun_call)] let ts = Accounts::get_total_supplies().or(Err(CoreError::InvariantViolation( "unable to get total supplies".to_string(), diff --git a/runtime-sdk/src/modules/consensus_accounts/state.rs b/runtime-sdk/src/modules/consensus_accounts/state.rs index d3feed5f46..7ac892aae1 100644 --- a/runtime-sdk/src/modules/consensus_accounts/state.rs +++ b/runtime-sdk/src/modules/consensus_accounts/state.rs @@ -7,7 +7,8 @@ use std::{ use oasis_core_runtime::consensus::beacon::EpochTime; use crate::{ - storage::{self, CurrentStore, Store}, + state::CurrentState, + storage::{self, Store}, types::address::Address, }; @@ -27,7 +28,7 @@ pub const RECEIPTS: &[u8] = &[0x04]; /// The given shares are added to any existing delegation that may exist for the same (from, to) /// address pair. If no delegation exists a new one is created. pub fn add_delegation(from: Address, to: Address, shares: u128) -> Result<(), Error> { - CurrentStore::with(|store| { + CurrentState::with_store(|store| { let store = storage::PrefixStore::new(store, &MODULE_NAME); let delegations = storage::PrefixStore::new(store, &DELEGATIONS); let mut account = storage::TypedStore::new(storage::PrefixStore::new(delegations, &from)); @@ -46,7 +47,7 @@ pub fn add_delegation(from: Address, to: Address, shares: u128) -> Result<(), Er /// Subtract delegation from a given (from, to) pair. pub fn sub_delegation(from: Address, to: Address, shares: u128) -> Result<(), Error> { - CurrentStore::with(|store| { + CurrentState::with_store(|store| { let store = storage::PrefixStore::new(store, &MODULE_NAME); let delegations = storage::PrefixStore::new(store, &DELEGATIONS); let mut account = storage::TypedStore::new(storage::PrefixStore::new(delegations, &from)); @@ -72,7 +73,7 @@ pub fn sub_delegation(from: Address, to: Address, shares: u128) -> Result<(), Er /// In case no delegation exists for the given (from, to) address pair, an all-zero delegation /// metadata are returned. pub fn get_delegation(from: Address, to: Address) -> Result { - CurrentStore::with(|store| { + CurrentState::with_store(|store| { let store = storage::PrefixStore::new(store, &MODULE_NAME); let delegations = storage::PrefixStore::new(store, &DELEGATIONS); let account = storage::TypedStore::new(storage::PrefixStore::new(delegations, &from)); @@ -82,7 +83,7 @@ pub fn get_delegation(from: Address, to: Address) -> Result Result, Error> { - CurrentStore::with(|store| { + CurrentState::with_store(|store| { let store = storage::PrefixStore::new(store, &MODULE_NAME); let delegations = storage::PrefixStore::new(store, &DELEGATIONS); let account = storage::TypedStore::new(storage::PrefixStore::new(delegations, &from)); @@ -125,7 +126,7 @@ impl TryFrom<&[u8]> for AddressPair { /// Return the number of delegated shares for each destination escrow account. pub fn get_delegations_by_destination() -> Result, Error> { - CurrentStore::with(|store| { + CurrentState::with_store(|store| { let store = storage::PrefixStore::new(store, &MODULE_NAME); let delegations = storage::TypedStore::new(storage::PrefixStore::new(store, &DELEGATIONS)); @@ -153,7 +154,7 @@ pub fn add_undelegation( shares: u128, receipt: u64, ) -> Result { - CurrentStore::with(|mut root_store| { + CurrentState::with_store(|mut root_store| { let store = storage::PrefixStore::new(&mut root_store, &MODULE_NAME); let undelegations = storage::PrefixStore::new(store, &UNDELEGATIONS); let account = storage::PrefixStore::new(undelegations, &to); @@ -192,7 +193,7 @@ fn queue_entry_key(from: Address, to: Address, epoch: EpochTime) -> Vec { /// /// In case the undelegation doesn't exist, returns a default-constructed DelegationInfo. pub fn take_undelegation(ud: &Undelegation) -> Result { - CurrentStore::with(|mut root_store| { + CurrentState::with_store(|mut root_store| { // Get and remove undelegation metadata. let store = storage::PrefixStore::new(&mut root_store, &MODULE_NAME); let undelegations = storage::PrefixStore::new(store, &UNDELEGATIONS); @@ -232,7 +233,7 @@ impl TryFrom<&[u8]> for AddressWithEpoch { /// Retrieve all undelegation metadata to a given address. pub fn get_undelegations(to: Address) -> Result, Error> { - CurrentStore::with(|store| { + CurrentState::with_store(|store| { let store = storage::PrefixStore::new(store, &MODULE_NAME); let undelegations = storage::PrefixStore::new(store, &UNDELEGATIONS); let account = storage::TypedStore::new(storage::PrefixStore::new(undelegations, &to)); @@ -278,7 +279,7 @@ impl<'a> TryFrom<&'a [u8]> for Undelegation { /// Retrieve all queued undelegations for epochs earlier than or equal to the passed epoch. pub fn get_queued_undelegations(epoch: EpochTime) -> Result, Error> { - CurrentStore::with(|store| { + CurrentState::with_store(|store| { let store = storage::PrefixStore::new(store, &MODULE_NAME); let queue = storage::TypedStore::new(storage::PrefixStore::new(store, &UNDELEGATION_QUEUE)); @@ -292,7 +293,7 @@ pub fn get_queued_undelegations(epoch: EpochTime) -> Result, E /// Store the given receipt. pub fn set_receipt(owner: Address, kind: types::ReceiptKind, id: u64, receipt: types::Receipt) { - CurrentStore::with(|store| { + CurrentState::with_store(|store| { let store = storage::PrefixStore::new(store, &MODULE_NAME); let receipts = storage::PrefixStore::new(store, &RECEIPTS); let of_owner = storage::PrefixStore::new(receipts, &owner); @@ -305,7 +306,7 @@ pub fn set_receipt(owner: Address, kind: types::ReceiptKind, id: u64, receipt: t /// Remove the given receipt from storage if it exists and return it, otherwise return `None`. pub fn take_receipt(owner: Address, kind: types::ReceiptKind, id: u64) -> Option { - CurrentStore::with(|store| { + CurrentState::with_store(|store| { let store = storage::PrefixStore::new(store, &MODULE_NAME); let receipts = storage::PrefixStore::new(store, &RECEIPTS); let of_owner = storage::PrefixStore::new(receipts, &owner); diff --git a/runtime-sdk/src/modules/consensus_accounts/test.rs b/runtime-sdk/src/modules/consensus_accounts/test.rs index 048457a0e9..5740dbc77c 100644 --- a/runtime-sdk/src/modules/consensus_accounts/test.rs +++ b/runtime-sdk/src/modules/consensus_accounts/test.rs @@ -16,7 +16,7 @@ use oasis_core_runtime::{ }; use crate::{ - context::BatchContext, + context::Context, event::IntoTags, history, module::{BlockHandler, MethodHandler, MigrationHandler}, @@ -24,6 +24,7 @@ use crate::{ accounts::{Genesis as AccountsGenesis, Module as Accounts, API}, consensus::{Error as ConsensusError, Module as Consensus}, }, + state::{CurrentState, Options, TransactionResult}, testing::{ keys, mock::{self, EmptyRuntime}, @@ -40,7 +41,7 @@ use super::{ Module, *, }; -fn init_accounts_ex(ctx: &mut C, address: Address) { +fn init_accounts_ex(ctx: &C, address: Address) { let denom: Denomination = Denomination::from_str("TEST").unwrap(); let mut meta = Default::default(); let genesis = Default::default(); @@ -70,22 +71,22 @@ fn init_accounts_ex(ctx: &mut C, address: Address) { Module::::init_or_migrate(ctx, &mut meta, genesis); } -fn init_accounts(ctx: &mut C) { +fn init_accounts(ctx: &C) { init_accounts_ex(ctx, keys::alice::address()); } #[test] fn test_init() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); - init_accounts(&mut ctx); + let ctx = mock.create_ctx(); + init_accounts(&ctx); } #[test] fn test_api_deposit_invalid_denomination() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); - init_accounts(&mut ctx); + let ctx = mock.create_ctx(); + init_accounts(&ctx); let tx = transaction::Transaction { version: 1, @@ -114,12 +115,11 @@ fn test_api_deposit_invalid_denomination() { }, }; - ctx.with_tx(tx.into(), |mut tx_ctx, call| { - let result = Module::::tx_deposit( - &mut tx_ctx, - cbor::from_value(call.body).unwrap(), - ) - .unwrap_err(); + let call = tx.call.clone(); + CurrentState::with_transaction_opts(Options::new().with_tx(tx.into()), || { + let result = + Module::::tx_deposit(&ctx, cbor::from_value(call.body).unwrap()) + .unwrap_err(); assert!(matches!( result, Error::Consensus(ConsensusError::InvalidDenomination) @@ -130,8 +130,8 @@ fn test_api_deposit_invalid_denomination() { #[test] fn test_api_deposit_incompatible_signer() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); - init_accounts(&mut ctx); + let ctx = mock.create_ctx(); + init_accounts(&ctx); let tx = transaction::Transaction { version: 1, @@ -159,13 +159,12 @@ fn test_api_deposit_incompatible_signer() { ..Default::default() }, }; + let call = tx.call.clone(); - ctx.with_tx(tx.into(), |mut tx_ctx, call| { - let result = Module::::tx_deposit( - &mut tx_ctx, - cbor::from_value(call.body).unwrap(), - ) - .unwrap_err(); + CurrentState::with_transaction_opts(Options::new().with_tx(tx.into()), || { + let result = + Module::::tx_deposit(&ctx, cbor::from_value(call.body).unwrap()) + .unwrap_err(); assert!(matches!( result, Error::Consensus(ConsensusError::ConsensusIncompatibleSigner) @@ -177,8 +176,8 @@ fn test_api_deposit_incompatible_signer() { fn test_api_deposit() { let denom: Denomination = Denomination::from_str("TEST").unwrap(); let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); - init_accounts(&mut ctx); + let ctx = mock.create_ctx(); + init_accounts(&ctx); let nonce = 123; let tx = transaction::Transaction { @@ -207,17 +206,15 @@ fn test_api_deposit() { ..Default::default() }, }; + let call = tx.call.clone(); - let hook = ctx.with_tx(tx.into(), |mut tx_ctx, call| { - Module::::tx_deposit( - &mut tx_ctx, - cbor::from_value(call.body).unwrap(), - ) - .expect("deposit tx should succeed"); + let hook = CurrentState::with_transaction_opts(Options::new().with_tx(tx.into()), || { + Module::::tx_deposit(&ctx, cbor::from_value(call.body).unwrap()) + .expect("deposit tx should succeed"); - let mut state = tx_ctx.commit(); - assert_eq!(1, state.messages.len(), "one message should be emitted"); - let (msg, hook) = state.messages.pop().unwrap(); + let mut messages = CurrentState::with(|state| state.take_messages()); + assert_eq!(1, messages.len(), "one message should be emitted"); + let (msg, hook) = messages.pop().unwrap(); assert_eq!( Message::Staking(Versioned::new( @@ -237,13 +234,13 @@ fn test_api_deposit() { "emitted hook should match" ); - hook + TransactionResult::Commit(hook) }); // Simulate the message being processed and make sure withdrawal is successfully completed. let me = Default::default(); Module::::message_result_withdraw( - &mut ctx, + &ctx, me, cbor::from_value(hook.payload).unwrap(), ); @@ -259,8 +256,7 @@ fn test_api_deposit() { ); // Make sure events were emitted. - let state = ctx.commit(); - let tags = state.events.into_tags(); + let tags = CurrentState::with(|state| state.take_events().into_tags()); assert_eq!(tags.len(), 2, "deposit and mint events should be emitted"); assert_eq!(tags[0].key, b"accounts\x00\x00\x00\x03"); // accounts.Mint (code = 3) event assert_eq!(tags[1].key, b"consensus_accounts\x00\x00\x00\x01"); // consensus_accounts.Deposit (code = 1) event @@ -290,8 +286,8 @@ fn test_api_deposit() { #[test] fn test_api_withdraw_invalid_denomination() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); - init_accounts(&mut ctx); + let ctx = mock.create_ctx(); + init_accounts(&ctx); let tx = transaction::Transaction { version: 1, @@ -319,13 +315,12 @@ fn test_api_withdraw_invalid_denomination() { ..Default::default() }, }; + let call = tx.call.clone(); - ctx.with_tx(tx.into(), |mut tx_ctx, call| { - let result = Module::::tx_withdraw( - &mut tx_ctx, - cbor::from_value(call.body).unwrap(), - ) - .unwrap_err(); + CurrentState::with_transaction_opts(Options::new().with_tx(tx.into()), || { + let result = + Module::::tx_withdraw(&ctx, cbor::from_value(call.body).unwrap()) + .unwrap_err(); assert!(matches!( result, Error::Consensus(ConsensusError::InvalidDenomination) @@ -336,8 +331,8 @@ fn test_api_withdraw_invalid_denomination() { #[test] fn test_api_withdraw_insufficient_balance() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); - init_accounts(&mut ctx); + let ctx = mock.create_ctx(); + init_accounts(&ctx); let tx = transaction::Transaction { version: 1, @@ -365,13 +360,12 @@ fn test_api_withdraw_insufficient_balance() { ..Default::default() }, }; + let call = tx.call.clone(); - ctx.with_tx(tx.into(), |mut tx_ctx, call| { - let result = Module::::tx_withdraw( - &mut tx_ctx, - cbor::from_value(call.body).unwrap(), - ) - .unwrap_err(); + CurrentState::with_transaction_opts(Options::new().with_tx(tx.into()), || { + let result = + Module::::tx_withdraw(&ctx, cbor::from_value(call.body).unwrap()) + .unwrap_err(); assert!(matches!(result, Error::InsufficientBalance)); }); } @@ -379,8 +373,8 @@ fn test_api_withdraw_insufficient_balance() { #[test] fn test_api_withdraw_incompatible_signer() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); - init_accounts(&mut ctx); + let ctx = mock.create_ctx(); + init_accounts(&ctx); let tx = transaction::Transaction { version: 1, @@ -406,13 +400,12 @@ fn test_api_withdraw_incompatible_signer() { ..Default::default() }, }; + let call = tx.call.clone(); - ctx.with_tx(tx.into(), |mut tx_ctx, call| { - let result = Module::::tx_withdraw( - &mut tx_ctx, - cbor::from_value(call.body).unwrap(), - ) - .unwrap_err(); + CurrentState::with_transaction_opts(Options::new().with_tx(tx.into()), || { + let result = + Module::::tx_withdraw(&ctx, cbor::from_value(call.body).unwrap()) + .unwrap_err(); assert!(matches!( result, Error::Consensus(ConsensusError::ConsensusIncompatibleSigner) @@ -425,8 +418,8 @@ fn test_api_withdraw(signer_sigspec: SignatureAddressSpec) { let denom: Denomination = Denomination::from_str("TEST").unwrap(); let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); - init_accounts_ex(&mut ctx, signer_address); + let ctx = mock.create_ctx(); + init_accounts_ex(&ctx, signer_address); let nonce = 123; let tx = transaction::Transaction { @@ -452,17 +445,16 @@ fn test_api_withdraw(signer_sigspec: SignatureAddressSpec) { ..Default::default() }, }; + let call = tx.call.clone(); - let hook = ctx.with_tx(tx.into(), |mut tx_ctx, call| { - Module::::tx_withdraw( - &mut tx_ctx, - cbor::from_value(call.body).unwrap(), - ) - .expect("withdraw tx should succeed"); + let hook = CurrentState::with_transaction_opts(Options::new().with_tx(tx.into()), || { + Module::::tx_withdraw(&ctx, cbor::from_value(call.body).unwrap()) + .expect("withdraw tx should succeed"); - let mut state = tx_ctx.commit(); - assert_eq!(1, state.messages.len(), "one message should be emitted"); - let (msg, hook) = state.messages.pop().unwrap(); + CurrentState::with(|state| state.take_all_events()); // Clear events. + let mut messages = CurrentState::with(|state| state.take_messages()); + assert_eq!(1, messages.len(), "one message should be emitted"); + let (msg, hook) = messages.pop().unwrap(); assert_eq!( Message::Staking(Versioned::new( @@ -482,7 +474,7 @@ fn test_api_withdraw(signer_sigspec: SignatureAddressSpec) { "emitted hook should match" ); - hook + TransactionResult::Commit(hook) }); // Make sure that withdrawn balance is in the module's pending withdrawal account. @@ -495,7 +487,7 @@ fn test_api_withdraw(signer_sigspec: SignatureAddressSpec) { // Simulate the message being processed and make sure withdrawal is successfully completed. let me = Default::default(); Module::::message_result_transfer( - &mut ctx, + &ctx, me, cbor::from_value(hook.payload).unwrap(), ); @@ -513,8 +505,7 @@ fn test_api_withdraw(signer_sigspec: SignatureAddressSpec) { ); // Make sure events were emitted. - let state = ctx.commit(); - let tags = state.events.into_tags(); + let tags = CurrentState::with(|state| state.take_events().into_tags()); assert_eq!(tags.len(), 2, "withdraw and burn events should be emitted"); assert_eq!(tags[0].key, b"accounts\x00\x00\x00\x02"); // accounts.Burn (code = 2) event assert_eq!(tags[1].key, b"consensus_accounts\x00\x00\x00\x02"); // consensus_accounts.Withdraw (code = 2) event @@ -554,8 +545,8 @@ fn test_api_withdraw_secp256k1() { fn test_api_withdraw_handler_failure() { let denom: Denomination = Denomination::from_str("TEST").unwrap(); let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); - init_accounts(&mut ctx); + let ctx = mock.create_ctx(); + init_accounts(&ctx); let nonce = 123; let tx = transaction::Transaction { @@ -584,17 +575,15 @@ fn test_api_withdraw_handler_failure() { ..Default::default() }, }; + let call = tx.call.clone(); - let hook = ctx.with_tx(tx.into(), |mut tx_ctx, call| { - Module::::tx_withdraw( - &mut tx_ctx, - cbor::from_value(call.body).unwrap(), - ) - .expect("withdraw tx should succeed"); + let hook = CurrentState::with_transaction_opts(Options::new().with_tx(tx.into()), || { + Module::::tx_withdraw(&ctx, cbor::from_value(call.body).unwrap()) + .expect("withdraw tx should succeed"); - let mut state = tx_ctx.commit(); - assert_eq!(1, state.messages.len(), "one message should be emitted"); - let (msg, hook) = state.messages.pop().unwrap(); + let mut messages = CurrentState::with(|state| state.take_messages()); + assert_eq!(1, messages.len(), "one message should be emitted"); + let (msg, hook) = messages.pop().unwrap(); assert_eq!( Message::Staking(Versioned::new( @@ -614,7 +603,7 @@ fn test_api_withdraw_handler_failure() { "emitted hook should match" ); - hook + TransactionResult::Commit(hook) }); // Make sure that withdrawn balance is in the module's pending withdrawal account. @@ -632,7 +621,7 @@ fn test_api_withdraw_handler_failure() { result: None, }; Module::::message_result_transfer( - &mut ctx, + &ctx, me, cbor::from_value(hook.payload).unwrap(), ); @@ -650,8 +639,7 @@ fn test_api_withdraw_handler_failure() { ); // Make sure events were emitted. - let state = ctx.commit(); - let tags = state.events.into_tags(); + let tags = CurrentState::with(|state| state.take_events().into_tags()); assert_eq!( tags.len(), 2, @@ -691,8 +679,8 @@ fn test_api_withdraw_handler_failure() { fn test_consensus_withdraw_handler() { let denom: Denomination = Denomination::from_str("TEST").unwrap(); let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); - init_accounts(&mut ctx); + let ctx = mock.create_ctx(); + init_accounts(&ctx); // Simulate successful event. let me = Default::default(); @@ -702,14 +690,14 @@ fn test_consensus_withdraw_handler() { address: keys::alice::address(), amount: BaseUnits::new(1, denom.clone()), }; - Module::::message_result_withdraw(&mut ctx, me, h_ctx); + Module::::message_result_withdraw(&ctx, me, h_ctx); // Ensure runtime balance is updated. let bals = Accounts::get_balances(keys::alice::address()).unwrap(); assert_eq!(bals.balances[&denom], 1_001, "alice balance deposited in") } -fn perform_delegation(ctx: &mut C, success: bool) -> u64 { +fn perform_delegation(ctx: &C, success: bool) -> u64 { let denom: Denomination = Denomination::from_str("TEST").unwrap(); let nonce = 123; let tx = transaction::Transaction { @@ -737,17 +725,16 @@ fn perform_delegation(ctx: &mut C, success: bool) -> u64 { ..Default::default() }, }; + let call = tx.call.clone(); - let hook = ctx.with_tx(tx.into(), |mut tx_ctx, call| { - Module::::tx_delegate( - &mut tx_ctx, - cbor::from_value(call.body).unwrap(), - ) - .expect("delegate tx should succeed"); + let hook = CurrentState::with_transaction_opts(Options::new().with_tx(tx.into()), || { + Module::::tx_delegate(ctx, cbor::from_value(call.body).unwrap()) + .expect("delegate tx should succeed"); - let mut state = tx_ctx.commit(); - assert_eq!(1, state.messages.len(), "one message should be emitted"); - let (msg, hook) = state.messages.pop().unwrap(); + CurrentState::with(|state| state.take_all_events()); // Clear events. + let mut messages = CurrentState::with(|state| state.take_messages()); + assert_eq!(1, messages.len(), "one message should be emitted"); + let (msg, hook) = messages.pop().unwrap(); assert_eq!( Message::Staking(Versioned::new( @@ -767,7 +754,7 @@ fn perform_delegation(ctx: &mut C, success: bool) -> u64 { "emitted hook should match" ); - hook + TransactionResult::Commit(hook) }); // Make sure that delegated balance is in the module's pending delegations account. @@ -811,10 +798,10 @@ fn perform_delegation(ctx: &mut C, success: bool) -> u64 { fn test_api_delegate() { let denom: Denomination = Denomination::from_str("TEST").unwrap(); let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); - init_accounts(&mut ctx); + let ctx = mock.create_ctx(); + init_accounts(&ctx); - let nonce = perform_delegation(&mut ctx, true); + let nonce = perform_delegation(&ctx, true); // Ensure runtime balance is updated. let balance = Accounts::get_balance(*ADDRESS_PENDING_DELEGATION, denom.clone()).unwrap(); @@ -829,8 +816,7 @@ fn test_api_delegate() { ); // Make sure events were emitted. - let state = ctx.commit(); - let tags = state.events.into_tags(); + let tags = CurrentState::with(|state| state.take_events().into_tags()); assert_eq!(tags.len(), 2, "delegate and burn events should be emitted"); assert_eq!(tags[0].key, b"accounts\x00\x00\x00\x02"); // accounts.Burn (code = 2) event assert_eq!(tags[1].key, b"consensus_accounts\x00\x00\x00\x03"); // consensus_accounts.Delegate (code = 3) event @@ -856,9 +842,9 @@ fn test_api_delegate() { assert_eq!(event.error, None); // Test delegation queries. - let mut ctx = mock.create_ctx(); + let ctx = mock.create_ctx(); let di = Module::::query_delegation( - &mut ctx, + &ctx, types::DelegationQuery { from: keys::alice::address(), to: keys::bob::address(), @@ -868,7 +854,7 @@ fn test_api_delegate() { assert_eq!(di.shares, 1_000); let dis = Module::::query_delegations( - &mut ctx, + &ctx, types::DelegationsQuery { from: keys::alice::address(), }, @@ -882,8 +868,8 @@ fn test_api_delegate() { fn test_api_delegate_insufficient_balance() { let denom: Denomination = Denomination::from_str("TEST").unwrap(); let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); - init_accounts(&mut ctx); + let ctx = mock.create_ctx(); + init_accounts(&ctx); let tx = transaction::Transaction { version: 1, @@ -910,13 +896,12 @@ fn test_api_delegate_insufficient_balance() { ..Default::default() }, }; + let call = tx.call.clone(); - ctx.with_tx(tx.into(), |mut tx_ctx, call| { - let result = Module::::tx_delegate( - &mut tx_ctx, - cbor::from_value(call.body).unwrap(), - ) - .unwrap_err(); + CurrentState::with_transaction_opts(Options::new().with_tx(tx.into()), || { + let result = + Module::::tx_delegate(&ctx, cbor::from_value(call.body).unwrap()) + .unwrap_err(); assert!(matches!(result, Error::InsufficientBalance)); }); } @@ -925,10 +910,10 @@ fn test_api_delegate_insufficient_balance() { fn test_api_delegate_fail() { let denom: Denomination = Denomination::from_str("TEST").unwrap(); let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); - init_accounts(&mut ctx); + let ctx = mock.create_ctx(); + init_accounts(&ctx); - perform_delegation(&mut ctx, false); + perform_delegation(&ctx, false); // Ensure runtime balance is updated. let balance = Accounts::get_balance(*ADDRESS_PENDING_DELEGATION, denom.clone()).unwrap(); @@ -949,8 +934,7 @@ fn test_api_delegate_fail() { ); // Make sure events were emitted. - let state = ctx.commit(); - let tags = state.events.into_tags(); + let tags = CurrentState::with(|state| state.take_events().into_tags()); assert_eq!(tags.len(), 2, "delegate and burn events should be emitted"); assert_eq!(tags[0].key, b"accounts\x00\x00\x00\x01"); // accounts.Transfer (code = 1) event assert_eq!(tags[1].key, b"consensus_accounts\x00\x00\x00\x03"); // consensus_accounts.Delegate (code = 3) event @@ -960,8 +944,8 @@ fn test_api_delegate_fail() { fn test_api_delegate_receipt_not_internal() { let denom: Denomination = Denomination::from_str("TEST").unwrap(); let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); - init_accounts(&mut ctx); + let ctx = mock.create_ctx(); + init_accounts(&ctx); let tx = transaction::Transaction { version: 1, @@ -988,21 +972,17 @@ fn test_api_delegate_receipt_not_internal() { ..Default::default() }, }; + let call = tx.call.clone(); - ctx.with_tx(tx.into(), |mut tx_ctx, call| { - let result = Module::::tx_delegate( - &mut tx_ctx, - cbor::from_value(call.body).unwrap(), - ) - .unwrap_err(); + CurrentState::with_transaction_opts(Options::new().with_tx(tx.into()), || { + let result = + Module::::tx_delegate(&ctx, cbor::from_value(call.body).unwrap()) + .unwrap_err(); assert!(matches!(result, Error::InvalidArgument)); }); } -fn perform_undelegation( - ctx: &mut C, - success: Option, -) -> (u64, Option) { +fn perform_undelegation(ctx: &C, success: Option) -> (u64, Option) { let rt_address = Address::from_runtime_id(ctx.runtime_id()); let nonce = 123; let tx = transaction::Transaction { @@ -1030,17 +1010,16 @@ fn perform_undelegation( ..Default::default() }, }; + let call = tx.call.clone(); - let hook = ctx.with_tx(tx.into(), |mut tx_ctx, call| { - Module::::tx_undelegate( - &mut tx_ctx, - cbor::from_value(call.body).unwrap(), - ) - .expect("undelegate tx should succeed"); + let hook = CurrentState::with_transaction_opts(Options::new().with_tx(tx.into()), || { + Module::::tx_undelegate(ctx, cbor::from_value(call.body).unwrap()) + .expect("undelegate tx should succeed"); - let mut state = tx_ctx.commit(); - assert_eq!(1, state.messages.len(), "one message should be emitted"); - let (msg, hook) = state.messages.pop().unwrap(); + CurrentState::with(|state| state.take_all_events()); // Clear events. + let mut messages = CurrentState::with(|state| state.take_messages()); + assert_eq!(1, messages.len(), "one message should be emitted"); + let (msg, hook) = messages.pop().unwrap(); assert_eq!( Message::Staking(Versioned::new( @@ -1060,7 +1039,7 @@ fn perform_undelegation( "emitted hook should match" ); - hook + TransactionResult::Commit(hook) }); // Make sure the delegation was updated to remove shares. @@ -1184,20 +1163,17 @@ struct UndelegateDoneEvent { fn test_api_undelegate() { let denom: Denomination = Denomination::from_str("TEST").unwrap(); let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); - init_accounts(&mut ctx); + let ctx = mock.create_ctx(); + init_accounts(&ctx); - perform_delegation(&mut ctx, true); + perform_delegation(&ctx, true); + CurrentState::with(|state| state.take_all_events()); // Clear events. - ctx.commit(); - let mut ctx = mock.create_ctx(); - - let (nonce, _) = perform_undelegation(&mut ctx, Some(true)); + let (nonce, _) = perform_undelegation(&ctx, Some(true)); let rt_address = Address::from_runtime_id(ctx.runtime_id()); // Make sure events were emitted. - let state = ctx.commit(); - let tags = state.events.into_tags(); + let tags = CurrentState::with(|state| state.take_events().into_tags()); assert_eq!(tags.len(), 1, "undelegate start event should be emitted"); assert_eq!(tags[0].key, b"consensus_accounts\x00\x00\x00\x04"); // consensus_accounts.UndelegateStart (code = 4) event @@ -1216,13 +1192,12 @@ fn test_api_undelegate() { for epoch in 1..=13 { mock.epoch = epoch; - let mut ctx = mock.create_ctx(); - ::Core::begin_block(&mut ctx); - Module::::end_block(&mut ctx); + let ctx = mock.create_ctx(); + ::Core::begin_block(&ctx); + Module::::end_block(&ctx); // Make sure nothing changes. - let state = ctx.commit(); - let tags = state.events.into_tags(); + let tags = CurrentState::with(|state| state.take_events().into_tags()); assert_eq!(tags.len(), 0, "no events should be emitted"); } @@ -1240,13 +1215,12 @@ fn test_api_undelegate() { })], }); - let mut ctx = mock.create_ctx(); - ::Core::begin_block(&mut ctx); - Module::::end_block(&mut ctx); + let ctx = mock.create_ctx(); + ::Core::begin_block(&ctx); + Module::::end_block(&ctx); // Make sure events were emitted. - let state = ctx.commit(); - let tags = state.events.into_tags(); + let tags = CurrentState::with(|state| state.take_events().into_tags()); assert_eq!( tags.len(), 2, @@ -1279,8 +1253,8 @@ fn test_api_undelegate() { #[test] fn test_api_undelegate_insufficient_balance() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); - init_accounts(&mut ctx); + let ctx = mock.create_ctx(); + init_accounts(&ctx); let tx = transaction::Transaction { version: 1, @@ -1307,10 +1281,11 @@ fn test_api_undelegate_insufficient_balance() { ..Default::default() }, }; + let call = tx.call.clone(); - ctx.with_tx(tx.into(), |mut tx_ctx, call| { + CurrentState::with_transaction_opts(Options::new().with_tx(tx.into()), || { let result = Module::::tx_undelegate( - &mut tx_ctx, + &ctx, cbor::from_value(call.body).unwrap(), ) .unwrap_err(); @@ -1321,19 +1296,16 @@ fn test_api_undelegate_insufficient_balance() { #[test] fn test_api_undelegate_fail() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); - init_accounts(&mut ctx); - - perform_delegation(&mut ctx, true); + let ctx = mock.create_ctx(); + init_accounts(&ctx); - ctx.commit(); - let mut ctx = mock.create_ctx(); + perform_delegation(&ctx, true); + CurrentState::with(|state| state.take_all_events()); // Clear events. - let (nonce, _) = perform_undelegation(&mut ctx, Some(false)); + let (nonce, _) = perform_undelegation(&ctx, Some(false)); // Make sure events were emitted. - let state = ctx.commit(); - let tags = state.events.into_tags(); + let tags = CurrentState::with(|state| state.take_events().into_tags()); assert_eq!(tags.len(), 1, "undelegate start event should be emitted"); assert_eq!(tags[0].key, b"consensus_accounts\x00\x00\x00\x04"); // consensus_accounts.UndelegateStart (code = 4) event @@ -1352,8 +1324,8 @@ fn test_api_undelegate_fail() { #[test] fn test_api_undelegate_receipt_not_internal() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); - init_accounts(&mut ctx); + let ctx = mock.create_ctx(); + init_accounts(&ctx); let tx = transaction::Transaction { version: 1, @@ -1380,10 +1352,11 @@ fn test_api_undelegate_receipt_not_internal() { ..Default::default() }, }; + let call = tx.call.clone(); - ctx.with_tx(tx.into(), |mut tx_ctx, call| { + CurrentState::with_transaction_opts(Options::new().with_tx(tx.into()), || { let result = Module::::tx_undelegate( - &mut tx_ctx, + &ctx, cbor::from_value(call.body).unwrap(), ) .unwrap_err(); @@ -1395,13 +1368,11 @@ fn test_api_undelegate_receipt_not_internal() { fn test_api_undelegate_suspension() { let denom: Denomination = Denomination::from_str("TEST").unwrap(); let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); - init_accounts(&mut ctx); + let ctx = mock.create_ctx(); + init_accounts(&ctx); - perform_delegation(&mut ctx, true); - - ctx.commit(); - let mut ctx = mock.create_ctx(); + perform_delegation(&ctx, true); + CurrentState::with(|state| state.take_all_events()); // Clear events. // Simulate the following scenario: // @@ -1412,12 +1383,11 @@ fn test_api_undelegate_suspension() { // * Runtime resumes, undelegate results processed. // - let (nonce, hook_payload) = perform_undelegation(&mut ctx, None); // Do not process undelegation results. + let (nonce, hook_payload) = perform_undelegation(&ctx, None); // Do not process undelegation results. let rt_address = Address::from_runtime_id(ctx.runtime_id()); // Make sure no events were emitted. - let state = ctx.commit(); - let tags = state.events.into_tags(); + let tags = CurrentState::with(|state| state.take_events().into_tags()); assert!(tags.is_empty(), "no events should be emitted"); // Simulate the runtime resuming and processing both undelegate results and the debonding period @@ -1436,7 +1406,7 @@ fn test_api_undelegate_suspension() { })], }); - let mut ctx = mock.create_ctx(); + let ctx = mock.create_ctx(); // Process undelegation message result. let me = MessageEvent { @@ -1453,18 +1423,17 @@ fn test_api_undelegate_suspension() { })), }; Module::::message_result_undelegate( - &mut ctx, + &ctx, me, cbor::from_value(hook_payload.unwrap()).unwrap(), ); // Process block. - ::Core::begin_block(&mut ctx); - Module::::end_block(&mut ctx); + ::Core::begin_block(&ctx); + Module::::end_block(&ctx); // Make sure events were emitted. - let state = ctx.commit(); - let tags = state.events.into_tags(); + let tags = CurrentState::with(|state| state.take_events().into_tags()); assert_eq!( tags.len(), 3, @@ -1508,8 +1477,7 @@ fn test_api_undelegate_suspension() { #[test] fn test_prefetch() { - let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); + let _mock = mock::Mock::default(); let auth_info = transaction::AuthInfo { signer_info: vec![transaction::SignerInfo::new_sigspec( @@ -1540,8 +1508,9 @@ fn test_prefetch() { }, auth_info: auth_info.clone(), }; + let call = tx.call.clone(); // Withdraw should result in one prefix getting prefetched. - ctx.with_tx(tx.into(), |mut _tx_ctx, call| { + CurrentState::with_transaction_opts(Options::new().with_tx(tx.into()), || { let mut prefixes = BTreeSet::new(); let result = Module::::prefetch( &mut prefixes, @@ -1572,8 +1541,9 @@ fn test_prefetch() { }, auth_info: auth_info.clone(), }; + let call = tx.call.clone(); // Deposit should result in zero prefixes. - ctx.with_tx(tx.into(), |mut _tx_ctx, call| { + CurrentState::with_transaction_opts(Options::new().with_tx(tx.into()), || { let mut prefixes = BTreeSet::new(); let result = Module::::prefetch( &mut prefixes, diff --git a/runtime-sdk/src/modules/core/mod.rs b/runtime-sdk/src/modules/core/mod.rs index d9b1262481..c08d01b238 100644 --- a/runtime-sdk/src/modules/core/mod.rs +++ b/runtime-sdk/src/modules/core/mod.rs @@ -11,7 +11,7 @@ use thiserror::Error; use crate::{ callformat, - context::{BatchContext, Context, TransactionWithMeta, TxContext}, + context::Context, core::consensus::beacon::EpochTime, dispatcher, error::Error as SDKError, @@ -21,7 +21,8 @@ use crate::{ ModuleInfoHandler as _, }, sender::SenderMeta, - storage::{self, current::TransactionResult, CurrentStore}, + state::{CurrentState, Mode, Options, TransactionWithMeta}, + storage::{self}, types::{ token::{self, Denomination}, transaction::{ @@ -302,49 +303,49 @@ pub trait API { /// Attempt to use gas. If the gas specified would cause either total used to exceed /// its limit, fails with Error::OutOfGas or Error::BatchOutOfGas, and neither gas usage is /// increased. - fn use_batch_gas(ctx: &mut C, gas: u64) -> Result<(), Error>; + fn use_batch_gas(gas: u64) -> Result<(), Error>; /// Attempt to use gas. If the gas specified would cause either total used to exceed /// its limit, fails with Error::OutOfGas or Error::BatchOutOfGas, and neither gas usage is /// increased. - fn use_tx_gas(ctx: &mut C, gas: u64) -> Result<(), Error>; + fn use_tx_gas(gas: u64) -> Result<(), Error>; /// Returns the remaining batch-wide gas. - fn remaining_batch_gas(ctx: &mut C) -> u64; + fn remaining_batch_gas() -> u64; /// Returns the total batch-wide gas used. - fn used_batch_gas(ctx: &mut C) -> u64; + fn used_batch_gas() -> u64; /// Return the remaining tx-wide gas. - fn remaining_tx_gas(ctx: &mut C) -> u64; + fn remaining_tx_gas() -> u64; /// Return the used tx-wide gas. - fn used_tx_gas(ctx: &mut C) -> u64; + fn used_tx_gas() -> u64; /// Configured maximum amount of gas that can be used in a batch. - fn max_batch_gas(ctx: &mut C) -> u64; + fn max_batch_gas() -> u64; /// Configured minimum gas price. fn min_gas_price(ctx: &C, denom: &token::Denomination) -> Option; /// Sets the transaction priority to the provided amount. - fn set_priority(ctx: &mut C, priority: u64); + fn set_priority(priority: u64); /// Takes and returns the stored transaction priority. - fn take_priority(ctx: &mut C) -> u64; + fn take_priority() -> u64; /// Set transaction sender metadata. - fn set_sender_meta(ctx: &mut C, meta: SenderMeta); + fn set_sender_meta(meta: SenderMeta); /// Takes and returns the stored transaction sender metadata. - fn take_sender_meta(ctx: &mut C) -> SenderMeta; + fn take_sender_meta() -> SenderMeta; /// Returns the configured max iterations in the binary search for the estimate /// gas. fn estimate_gas_search_max_iters(ctx: &C) -> u64; /// Check whether the epoch has changed since last processed block. - fn has_epoch_changed(ctx: &mut C) -> bool; + fn has_epoch_changed() -> bool; } /// Genesis state for the accounts module. @@ -432,13 +433,13 @@ const CONTEXT_KEY_EPOCH_CHANGED: &str = "core.EpochChanged"; impl API for Module { type Config = Cfg; - fn use_batch_gas(ctx: &mut C, gas: u64) -> Result<(), Error> { - // Do not enforce batch limits for check-tx. - if ctx.is_check_only() { + fn use_batch_gas(gas: u64) -> Result<(), Error> { + // Do not enforce batch limits for checks. + if CurrentState::with_env(|env| env.is_check_only()) { return Ok(()); } let batch_gas_limit = Self::params().max_batch_gas; - let batch_gas_used = Self::used_batch_gas(ctx); + let batch_gas_used = Self::used_batch_gas(); // NOTE: Going over the batch limit should trigger an abort as the scheduler should never // allow scheduling past the batch limit but a malicious proposer might include too // many transactions. Make sure to vote for failure in this case. @@ -449,15 +450,22 @@ impl API for Module { return Err(Error::Abort(dispatcher::Error::BatchOutOfGas)); } - ctx.value::(CONTEXT_KEY_GAS_USED) - .set(batch_new_gas_used); + CurrentState::with(|state| { + state + .block_value::(CONTEXT_KEY_GAS_USED) + .set(batch_new_gas_used); + }); Ok(()) } - fn use_tx_gas(ctx: &mut C, gas: u64) -> Result<(), Error> { - let gas_limit = ctx.tx_auth_info().fee.gas; - let gas_used = ctx.tx_value::(CONTEXT_KEY_GAS_USED).or_default(); + fn use_tx_gas(gas: u64) -> Result<(), Error> { + let (gas_limit, gas_used) = CurrentState::with(|state| { + ( + state.env().tx_auth_info().fee.gas, + *state.local_value::(CONTEXT_KEY_GAS_USED).or_default(), + ) + }); let new_gas_used = { let sum = gas_used.checked_add(gas).ok_or(Error::GasOverflow)?; if sum > gas_limit { @@ -466,39 +474,48 @@ impl API for Module { sum }; - Self::use_batch_gas(ctx, gas)?; + Self::use_batch_gas(gas)?; - *ctx.tx_value::(CONTEXT_KEY_GAS_USED).or_default() = new_gas_used; + CurrentState::with(|state| { + *state.local_value::(CONTEXT_KEY_GAS_USED).or_default() = new_gas_used; + }); Ok(()) } - fn remaining_batch_gas(ctx: &mut C) -> u64 { + fn remaining_batch_gas() -> u64 { let batch_gas_limit = Self::params().max_batch_gas; - batch_gas_limit.saturating_sub(Self::used_batch_gas(ctx)) + batch_gas_limit.saturating_sub(Self::used_batch_gas()) } - fn used_batch_gas(ctx: &mut C) -> u64 { - ctx.value::(CONTEXT_KEY_GAS_USED) - .get() - .cloned() - .unwrap_or_default() + fn used_batch_gas() -> u64 { + CurrentState::with(|state| { + state + .block_value::(CONTEXT_KEY_GAS_USED) + .get() + .cloned() + .unwrap_or_default() + }) } - fn remaining_tx_gas(ctx: &mut C) -> u64 { - let gas_limit = ctx.tx_auth_info().fee.gas; - let gas_used = ctx.tx_value::(CONTEXT_KEY_GAS_USED).or_default(); - let remaining_tx = gas_limit.saturating_sub(*gas_used); + fn remaining_tx_gas() -> u64 { + let (gas_limit, gas_used) = CurrentState::with(|state| { + ( + state.env().tx_auth_info().fee.gas, + *state.local_value::(CONTEXT_KEY_GAS_USED).or_default(), + ) + }); + let remaining_tx = gas_limit.saturating_sub(gas_used); // Also check remaining batch gas limit and return the minimum of the two. - let remaining_batch = Self::remaining_batch_gas(ctx); + let remaining_batch = Self::remaining_batch_gas(); std::cmp::min(remaining_tx, remaining_batch) } - fn used_tx_gas(ctx: &mut C) -> u64 { - *ctx.tx_value::(CONTEXT_KEY_GAS_USED).or_default() + fn used_tx_gas() -> u64 { + CurrentState::with(|state| *state.local_value::(CONTEXT_KEY_GAS_USED).or_default()) } - fn max_batch_gas(_ctx: &mut C) -> u64 { + fn max_batch_gas() -> u64 { Self::params().max_batch_gas } @@ -506,24 +523,36 @@ impl API for Module { Self::min_gas_prices(ctx).get(denom).copied() } - fn set_priority(ctx: &mut C, priority: u64) { - ctx.value::(CONTEXT_KEY_PRIORITY).set(priority); + fn set_priority(priority: u64) { + CurrentState::with(|state| { + state.block_value::(CONTEXT_KEY_PRIORITY).set(priority); + }) } - fn take_priority(ctx: &mut C) -> u64 { - ctx.value::(CONTEXT_KEY_PRIORITY) - .take() - .unwrap_or_default() + fn take_priority() -> u64 { + CurrentState::with(|state| { + state + .block_value::(CONTEXT_KEY_PRIORITY) + .take() + .unwrap_or_default() + }) } - fn set_sender_meta(ctx: &mut C, meta: SenderMeta) { - ctx.value::(CONTEXT_KEY_SENDER_META).set(meta); + fn set_sender_meta(meta: SenderMeta) { + CurrentState::with(|state| { + state + .block_value::(CONTEXT_KEY_SENDER_META) + .set(meta); + }); } - fn take_sender_meta(ctx: &mut C) -> SenderMeta { - ctx.value::(CONTEXT_KEY_SENDER_META) - .take() - .unwrap_or_default() + fn take_sender_meta() -> SenderMeta { + CurrentState::with(|state| { + state + .block_value::(CONTEXT_KEY_SENDER_META) + .take() + .unwrap_or_default() + }) } fn estimate_gas_search_max_iters(ctx: &C) -> u64 { @@ -533,8 +562,13 @@ impl API for Module { .unwrap_or(Cfg::DEFAULT_LOCAL_ESTIMATE_GAS_SEARCH_MAX_ITERS) } - fn has_epoch_changed(ctx: &mut C) -> bool { - *ctx.value(CONTEXT_KEY_EPOCH_CHANGED).get().unwrap_or(&false) + fn has_epoch_changed() -> bool { + CurrentState::with(|state| { + *state + .block_value(CONTEXT_KEY_EPOCH_CHANGED) + .get() + .unwrap_or(&false) + }) } } @@ -558,7 +592,7 @@ impl Module { /// succeed. #[handler(query = "core.EstimateGas", allow_private_km)] pub fn query_estimate_gas( - ctx: &mut C, + ctx: &C, mut args: types::EstimateGasQuery, ) -> Result { let mut extra_gas = 0; @@ -590,7 +624,7 @@ impl Module { }; args.tx.auth_info.fee.amount = token::BaseUnits::new(u64::MAX.into(), token::Denomination::NATIVE); - args.tx.auth_info.fee.consensus_messages = ctx.remaining_messages(); + args.tx.auth_info.fee.consensus_messages = ctx.max_messages(); // Estimate transaction size. Since the transaction given to us is not signed, we need to // estimate how large each of the auth proofs would be. let auth_proofs: Result<_, Error> = args @@ -652,45 +686,41 @@ impl Module { .unwrap_or(&0); // Simulates transaction with a specific gas limit. - let mut simulate = |tx: &transaction::Transaction, gas: u64, report_failure: bool| { + let simulate = |tx: &transaction::Transaction, gas: u64, report_failure: bool| { let mut tx = tx.clone(); tx.auth_info.fee.gas = gas; - - CurrentStore::with_transaction(|| { - let result = ctx.with_simulation(|mut sim_ctx| { - sim_ctx.with_tx( - TransactionWithMeta { - tx, - tx_size, - tx_index: 0, - tx_hash: Default::default(), - }, - |mut tx_ctx, call| { - let (result, _) = - dispatcher::Dispatcher::::dispatch_tx_call( - &mut tx_ctx, - call, - &Default::default(), - ); - if !result.is_success() && report_failure { - // Report failure. - let err: TxSimulationFailure = result.try_into().unwrap(); // Guaranteed to be a Failed CallResult. - return Err(Error::TxSimulationFailed(err)); - } - // Don't report success or failure. If the call fails, we still report - // how much gas it uses while it fails. - let gas_used = *tx_ctx.value::(CONTEXT_KEY_GAS_USED).or_default(); - if result.is_success() { - Ok(gas_used) - } else { - Ok(gas_used.saturating_add(extra_gas_fail).clamp(0, gas)) - } - }, - ) - }); - - TransactionResult::Rollback(result) // Always rollback storage changes. - }) + let call = tx.call.clone(); // TODO: Avoid clone. + + CurrentState::with_transaction_opts( + Options::new() + .with_mode(Mode::Simulate) + .with_tx(TransactionWithMeta { + data: tx, + size: tx_size, + index: 0, + hash: Default::default(), + }), + || { + let (result, _) = dispatcher::Dispatcher::::dispatch_tx_call( + ctx, + call, + &Default::default(), + ); + if !result.is_success() && report_failure { + // Report failure. + let err: TxSimulationFailure = result.try_into().unwrap(); // Guaranteed to be a Failed CallResult. + return Err(Error::TxSimulationFailed(err)); + } + // Don't report success or failure. If the call fails, we still report + // how much gas it uses while it fails. + let gas_used = Self::used_batch_gas(); + if result.is_success() { + Ok(gas_used) + } else { + Ok(gas_used.saturating_add(extra_gas_fail).clamp(0, gas)) + } + }, + ) }; // Do a binary search for exact gas limit. @@ -798,14 +828,14 @@ impl Module { /// Check invariants of all modules in the runtime. #[handler(query = "core.CheckInvariants", expensive)] - fn query_check_invariants(ctx: &mut C, _args: ()) -> Result<(), Error> { + fn query_check_invariants(ctx: &C, _args: ()) -> Result<(), Error> { ::Modules::check_invariants(ctx) } /// Retrieve the public key for encrypting call data. #[handler(query = "core.CallDataPublicKey")] fn query_calldata_public_key( - ctx: &mut C, + ctx: &C, _args: (), ) -> Result { let key_manager = ctx @@ -827,7 +857,7 @@ impl Module { /// Query the minimum gas price. #[handler(query = "core.MinGasPrice")] fn query_min_gas_price( - ctx: &mut C, + ctx: &C, _args: (), ) -> Result, Error> { let mut mgp = Self::min_gas_prices(ctx); @@ -845,10 +875,7 @@ impl Module { /// Return basic information about the module and the containing runtime. #[handler(query = "core.RuntimeInfo")] - fn query_runtime_info( - ctx: &mut C, - _args: (), - ) -> Result { + fn query_runtime_info(ctx: &C, _args: ()) -> Result { Ok(RuntimeInfoResponse { runtime_version: ::VERSION, state_version: ::STATE_VERSION, @@ -863,14 +890,14 @@ impl Module { /// This query is allowed access to private key manager state. #[handler(query = "core.ExecuteReadOnlyTx", expensive, allow_private_km)] fn query_execute_read_only_tx( - ctx: &mut C, + ctx: &C, args: types::ExecuteReadOnlyTxQuery, ) -> Result { if !Cfg::ALLOW_INTERACTIVE_READ_ONLY_TRANSACTIONS { return Err(Error::Forbidden); } - ctx.with_simulation(|mut sim_ctx| { + CurrentState::with_transaction_opts(Options::new().with_mode(Mode::Simulate), || { // TODO: Use separate batch gas limit for query execution. // Decode transaction and verify signature. @@ -879,7 +906,7 @@ impl Module { .len() .try_into() .map_err(|_| Error::OversizedTransaction)?; - let tx = dispatcher::Dispatcher::::decode_tx(&mut sim_ctx, &args.tx)?; + let tx = dispatcher::Dispatcher::::decode_tx(ctx, &args.tx)?; // Only read-only transactions are allowed in interactive queries. if !tx.call.read_only { @@ -896,7 +923,7 @@ impl Module { // Execute transaction. let (result, _) = dispatcher::Dispatcher::::execute_tx_opts( - &mut sim_ctx, + ctx, tx, &dispatcher::DispatchOptions { tx_size, @@ -920,7 +947,7 @@ impl Module { fn min_gas_prices(_ctx: &C) -> BTreeMap { let params = Self::params(); if params.dynamic_min_gas_price.enabled { - CurrentStore::with(|store| { + CurrentState::with_store(|store| { let store = storage::TypedStore::new(storage::PrefixStore::new(store, &MODULE_NAME)); store @@ -949,14 +976,14 @@ impl Module { .unwrap_or_default() } - fn enforce_min_gas_price(ctx: &C, call: &Call) -> Result<(), Error> { + fn enforce_min_gas_price(ctx: &C, call: &Call) -> Result<(), Error> { // If the method is exempt from min gas price requirements, checks always pass. #[allow(clippy::borrow_interior_mutable_const)] if Cfg::MIN_GAS_PRICE_EXEMPT_METHODS.contains(call.method.as_str()) { return Ok(()); } - let fee = ctx.tx_auth_info().fee.clone(); + let fee = CurrentState::with_env(|env| env.tx_auth_info().fee.clone()); let denom = fee.amount.denomination(); match Self::min_gas_price(ctx, denom) { @@ -965,7 +992,7 @@ impl Module { // Otherwise, allow overrides during local checks. Some(min_gas_price) => { - if ctx.is_check_only() { + if CurrentState::with_env(|env| env.is_check_only()) { let local_mgp = Self::get_local_min_gas_price(ctx, denom); // Reject during local checks. @@ -985,7 +1012,7 @@ impl Module { } impl module::TransactionHandler for Module { - fn approve_raw_tx(_ctx: &mut C, tx: &[u8]) -> Result<(), Error> { + fn approve_raw_tx(_ctx: &C, tx: &[u8]) -> Result<(), Error> { let params = Self::params(); if tx.len() > params.max_tx_size.try_into().unwrap() { return Err(Error::OversizedTransaction); @@ -994,7 +1021,7 @@ impl module::TransactionHandler for Module { } fn approve_unverified_tx( - _ctx: &mut C, + _ctx: &C, utx: &UnverifiedTransaction, ) -> Result<(), Error> { let params = Self::params(); @@ -1011,20 +1038,19 @@ impl module::TransactionHandler for Module { Ok(()) } - fn before_handle_call(ctx: &mut C, call: &Call) -> Result<(), Error> { + fn before_handle_call(ctx: &C, call: &Call) -> Result<(), Error> { // Ensure that specified gas limit is not greater than batch gas limit. let params = Self::params(); - let gas = ctx.tx_auth_info().fee.gas; - if gas > params.max_batch_gas { + let fee = CurrentState::with_env(|env| env.tx_auth_info().fee.clone()); + if fee.gas > params.max_batch_gas { return Err(Error::GasOverflow); } - - // Attempt to limit the maximum number of consensus messages. - let consensus_messages = ctx.tx_auth_info().fee.consensus_messages; - ctx.limit_max_messages(consensus_messages)?; + if fee.consensus_messages > ctx.max_messages() { + return Err(Error::OutOfMessageSlots); + } // Skip additional checks/gas payment for internally generated transactions. - if ctx.is_internal() { + if CurrentState::with_env(|env| env.is_internal()) { return Ok(()); } @@ -1032,60 +1058,62 @@ impl module::TransactionHandler for Module { Self::enforce_min_gas_price(ctx, call)?; // Charge gas for transaction size. + let tx_size = CurrentState::with_env(|env| env.tx_size()); Self::use_tx_gas( - ctx, params .gas_costs .tx_byte - .checked_mul(ctx.tx_size().into()) + .checked_mul(tx_size.into()) .ok_or(Error::GasOverflow)?, )?; // Charge gas for signature verification. - let mut num_signature: u64 = 0; - let mut num_multisig_signer: u64 = 0; - for si in &ctx.tx_auth_info().signer_info { - match &si.address_spec { - AddressSpec::Signature(_) => { - num_signature = num_signature.checked_add(1).ok_or(Error::GasOverflow)?; - } - AddressSpec::Multisig(config) => { - num_multisig_signer = num_multisig_signer - .checked_add(config.signers.len() as u64) - .ok_or(Error::GasOverflow)?; + let total = CurrentState::with_env(|env| { + let mut num_signature: u64 = 0; + let mut num_multisig_signer: u64 = 0; + for si in &env.tx_auth_info().signer_info { + match &si.address_spec { + AddressSpec::Signature(_) => { + num_signature = num_signature.checked_add(1)?; + } + AddressSpec::Multisig(config) => { + num_multisig_signer = + num_multisig_signer.checked_add(config.signers.len() as u64)?; + } + AddressSpec::Internal(_) => {} } - AddressSpec::Internal(_) => {} } - } - let total = (|| { + let signature_cost = num_signature.checked_mul(params.gas_costs.auth_signature)?; let multisig_signer_cost = num_multisig_signer.checked_mul(params.gas_costs.auth_multisig_signer)?; let sum = signature_cost.checked_add(multisig_signer_cost)?; Some(sum) - })() + }) .ok_or(Error::GasOverflow)?; - Self::use_tx_gas(ctx, total)?; + Self::use_tx_gas(total)?; // Charge gas for callformat. match call.format { CallFormat::Plain => {} // No additional gas required. CallFormat::EncryptedX25519DeoxysII => { - Self::use_tx_gas(ctx, params.gas_costs.callformat_x25519_deoxysii)? + Self::use_tx_gas(params.gas_costs.callformat_x25519_deoxysii)? } } Ok(()) } - fn after_handle_call( - ctx: &mut C, + fn after_handle_call( + _ctx: &C, result: module::CallResult, ) -> Result { // Emit gas used event (if this is not an internally generated call). - if Cfg::EMIT_GAS_USED_EVENTS && !ctx.is_internal() { - let used_gas = Self::used_tx_gas(ctx); - ctx.emit_unconditional_event(Event::GasUsed { amount: used_gas }); + if Cfg::EMIT_GAS_USED_EVENTS && !CurrentState::with_env(|env| env.is_internal()) { + let used_gas = Self::used_tx_gas(); + CurrentState::with(|state| { + state.emit_unconditional_event(Event::GasUsed { amount: used_gas }); + }); } Ok(result) @@ -1127,12 +1155,12 @@ fn min_gas_price_update( } impl module::BlockHandler for Module { - fn begin_block(ctx: &mut C) { - CurrentStore::with(|store| { + fn begin_block(ctx: &C) { + CurrentState::with(|state| { let epoch = ctx.epoch(); // Load previous epoch. - let mut store = storage::PrefixStore::new(store, &MODULE_NAME); + let mut store = storage::PrefixStore::new(state.store(), &MODULE_NAME); let mut tstore = storage::TypedStore::new(&mut store); let previous_epoch: EpochTime = tstore.get(state::LAST_EPOCH).unwrap_or_default(); if epoch != previous_epoch { @@ -1140,12 +1168,13 @@ impl module::BlockHandler for Module { } // Set the epoch changed key as needed. - ctx.value(CONTEXT_KEY_EPOCH_CHANGED) + state + .block_value(CONTEXT_KEY_EPOCH_CHANGED) .set(epoch != previous_epoch); }); } - fn end_block(ctx: &mut C) { + fn end_block(ctx: &C) { let params = Self::params(); if !params.dynamic_min_gas_price.enabled { return; @@ -1155,8 +1184,8 @@ impl module::BlockHandler for Module { // // Adjust the min gas price for each block based on the gas used in the previous block and the desired target // gas usage set by `target_block_gas_usage_percentage`. - let gas_used = Self::used_batch_gas(ctx) as u128; - let max_batch_gas = Self::max_batch_gas(ctx) as u128; + let gas_used = Self::used_batch_gas() as u128; + let max_batch_gas = Self::max_batch_gas() as u128; let target_gas_used = max_batch_gas.saturating_mul( params .dynamic_min_gas_price @@ -1185,7 +1214,7 @@ impl module::BlockHandler for Module { }); // Update min prices. - CurrentStore::with(|store| { + CurrentState::with_store(|store| { let mut store = storage::PrefixStore::new(store, &MODULE_NAME); let mut tstore = storage::TypedStore::new(&mut store); tstore.insert(state::DYNAMIC_MIN_GAS_PRICE, mgp); diff --git a/runtime-sdk/src/modules/core/test.rs b/runtime-sdk/src/modules/core/test.rs index f3325eaaf1..4441506814 100644 --- a/runtime-sdk/src/modules/core/test.rs +++ b/runtime-sdk/src/modules/core/test.rs @@ -3,7 +3,7 @@ use std::collections::{BTreeMap, BTreeSet}; use once_cell::unsync::Lazy; use crate::{ - context::{BatchContext, Context, Mode, TxContext}, + context::Context, core::common::version::Version, crypto::multisig, error::Error, @@ -14,6 +14,7 @@ use crate::{ runtime::Runtime, sdk_derive, sender::SenderMeta, + state::{self, CurrentState, Options}, testing::{configmap, keys, mock}, types::{address::Address, token, transaction, transaction::CallerAddress}, }; @@ -26,8 +27,8 @@ type Core = super::Module; fn test_use_gas() { const MAX_GAS: u64 = 1000; const BLOCK_MAX_GAS: u64 = 3 * MAX_GAS + 2; - let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); + let _mock = mock::Mock::default(); + Core::set_params(Parameters { max_batch_gas: BLOCK_MAX_GAS, max_tx_size: 32 * 1024, @@ -42,89 +43,84 @@ fn test_use_gas() { dynamic_min_gas_price: Default::default(), }); - assert_eq!(Core::max_batch_gas(&mut ctx), BLOCK_MAX_GAS); + assert_eq!(Core::max_batch_gas(), BLOCK_MAX_GAS); - Core::use_batch_gas(&mut ctx, 1).expect("using batch gas under limit should succeed"); - assert_eq!(Core::remaining_batch_gas(&mut ctx), BLOCK_MAX_GAS - 1); + Core::use_batch_gas(1).expect("using batch gas under limit should succeed"); + assert_eq!(Core::remaining_batch_gas(), BLOCK_MAX_GAS - 1); let mut tx = mock::transaction(); tx.auth_info.fee.gas = MAX_GAS; - ctx.with_tx(tx.clone().into(), |mut tx_ctx, _call| { - Core::use_tx_gas(&mut tx_ctx, MAX_GAS).expect("using gas under limit should succeed"); - assert_eq!( - Core::remaining_batch_gas(&mut tx_ctx), - BLOCK_MAX_GAS - 1 - MAX_GAS - ); - assert_eq!(Core::remaining_tx_gas(&mut tx_ctx), 0); - assert_eq!(Core::used_tx_gas(&mut tx_ctx), MAX_GAS); + CurrentState::with_transaction_opts(Options::new().with_tx(tx.clone().into()), || { + Core::use_tx_gas(MAX_GAS).expect("using gas under limit should succeed"); + assert_eq!(Core::remaining_batch_gas(), BLOCK_MAX_GAS - 1 - MAX_GAS); + assert_eq!(Core::remaining_tx_gas(), 0); + assert_eq!(Core::used_tx_gas(), MAX_GAS); }); - ctx.with_tx(tx.clone().into(), |mut tx_ctx, _call| { - Core::use_tx_gas(&mut tx_ctx, MAX_GAS) - .expect("gas across separate transactions shouldn't accumulate"); - assert_eq!( - Core::remaining_batch_gas(&mut tx_ctx), - BLOCK_MAX_GAS - 1 - 2 * MAX_GAS - ); - assert_eq!(Core::remaining_tx_gas(&mut tx_ctx), 0); - assert_eq!(Core::used_tx_gas(&mut tx_ctx), MAX_GAS); + CurrentState::with_transaction_opts(Options::new().with_tx(tx.clone().into()), || { + Core::use_tx_gas(MAX_GAS).expect("gas across separate transactions shouldn't accumulate"); + assert_eq!(Core::remaining_batch_gas(), BLOCK_MAX_GAS - 1 - 2 * MAX_GAS); + assert_eq!(Core::remaining_tx_gas(), 0); + assert_eq!(Core::used_tx_gas(), MAX_GAS); }); - ctx.with_tx(tx.clone().into(), |mut tx_ctx, _call| { - Core::use_tx_gas(&mut tx_ctx, MAX_GAS).unwrap(); - Core::use_tx_gas(&mut tx_ctx, 1).expect_err("gas in same transaction should accumulate"); - assert_eq!(Core::remaining_tx_gas(&mut tx_ctx), 0); - assert_eq!(Core::used_tx_gas(&mut tx_ctx), MAX_GAS); + CurrentState::with_transaction_opts(Options::new().with_tx(tx.clone().into()), || { + Core::use_tx_gas(MAX_GAS).unwrap(); + Core::use_tx_gas(1).expect_err("gas in same transaction should accumulate"); + assert_eq!(Core::remaining_tx_gas(), 0); + assert_eq!(Core::used_tx_gas(), MAX_GAS); }); - assert_eq!( - Core::remaining_batch_gas(&mut ctx), - BLOCK_MAX_GAS - 1 - 3 * MAX_GAS - ); - assert_eq!(Core::max_batch_gas(&mut ctx), BLOCK_MAX_GAS); + assert_eq!(Core::remaining_batch_gas(), BLOCK_MAX_GAS - 1 - 3 * MAX_GAS); + assert_eq!(Core::max_batch_gas(), BLOCK_MAX_GAS); - ctx.with_tx(tx.clone().into(), |mut tx_ctx, _call| { - Core::use_tx_gas(&mut tx_ctx, 1).unwrap(); + CurrentState::with_transaction_opts(Options::new().with_tx(tx.clone().into()), || { + Core::use_tx_gas(1).unwrap(); assert_eq!( - Core::remaining_batch_gas(&mut tx_ctx), + Core::remaining_batch_gas(), BLOCK_MAX_GAS - 1 - 3 * MAX_GAS - 1 ); assert_eq!( - Core::remaining_tx_gas(&mut tx_ctx), + Core::remaining_tx_gas(), 0, "remaining tx gas should take batch limit into account" ); - assert_eq!(Core::used_tx_gas(&mut tx_ctx), 1); - Core::use_tx_gas(&mut tx_ctx, u64::MAX).expect_err("overflow should cause error"); - assert_eq!(Core::used_tx_gas(&mut tx_ctx), 1); + assert_eq!(Core::used_tx_gas(), 1); + Core::use_tx_gas(u64::MAX).expect_err("overflow should cause error"); + assert_eq!(Core::used_tx_gas(), 1); }); let mut big_tx = tx.clone(); big_tx.auth_info.fee.gas = u64::MAX; - ctx.with_tx(big_tx.into(), |mut tx_ctx, _call| { - Core::use_tx_gas(&mut tx_ctx, u64::MAX).expect_err("batch overflow should cause error"); + CurrentState::with_transaction_opts(Options::new().with_tx(tx.clone().into()), || { + Core::use_tx_gas(u64::MAX).expect_err("batch overflow should cause error"); }); - ctx.with_tx(tx.clone().into(), |mut tx_ctx, _call| { - Core::use_tx_gas(&mut tx_ctx, 1).expect_err("batch gas should accumulate"); + CurrentState::with_transaction_opts(Options::new().with_tx(tx.clone().into()), || { + Core::use_tx_gas(1).expect_err("batch gas should accumulate"); }); - Core::use_batch_gas(&mut ctx, 1).expect_err("batch gas should accumulate outside tx"); + Core::use_batch_gas(1).expect_err("batch gas should accumulate outside tx"); - let mut ctx = mock.create_check_ctx(); let mut big_tx = tx; big_tx.auth_info.fee.gas = u64::MAX; - ctx.with_tx(big_tx.into(), |mut tx_ctx, _call| { - Core::use_tx_gas(&mut tx_ctx, u64::MAX) - .expect("batch overflow should not happen in check-tx"); - }); + + CurrentState::with_transaction_opts( + state::Options::new() + .with_mode(state::Mode::Check) + .with_tx(big_tx.into()), + || { + Core::use_tx_gas(u64::MAX).expect("batch overflow should not happen in check-tx"); + }, + ); } #[test] fn test_query_min_gas_price() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); + let ctx = mock.create_ctx(); + Core::set_params(Parameters { max_batch_gas: 10000, max_tx_size: 32 * 1024, @@ -141,15 +137,15 @@ fn test_query_min_gas_price() { }); assert_eq!( - Core::min_gas_price(&mut ctx, &token::Denomination::NATIVE), + Core::min_gas_price(&ctx, &token::Denomination::NATIVE), Some(123) ); assert_eq!( - Core::min_gas_price(&mut ctx, &"SMALLER".parse().unwrap()), + Core::min_gas_price(&ctx, &"SMALLER".parse().unwrap()), Some(1000) ); - let mgp = Core::query_min_gas_price(&mut ctx, ()).expect("query_min_gas_price should succeed"); + let mgp = Core::query_min_gas_price(&ctx, ()).expect("query_min_gas_price should succeed"); assert!(mgp.len() == 2); assert!(mgp.contains_key(&token::Denomination::NATIVE)); assert!(*mgp.get(&token::Denomination::NATIVE).unwrap() == 123); @@ -172,15 +168,15 @@ fn test_query_min_gas_price() { } assert_eq!( - super::Module::::min_gas_price(&mut ctx, &token::Denomination::NATIVE), + super::Module::::min_gas_price(&ctx, &token::Denomination::NATIVE), Some(123) ); assert_eq!( - super::Module::::min_gas_price(&mut ctx, &"SMALLER".parse().unwrap()), + super::Module::::min_gas_price(&ctx, &"SMALLER".parse().unwrap()), Some(1000) ); - let mgp = super::Module::::query_min_gas_price(&mut ctx, ()) + let mgp = super::Module::::query_min_gas_price(&ctx, ()) .expect("query_min_gas_price should succeed"); assert!(mgp.len() == 2); assert!(mgp.contains_key(&token::Denomination::NATIVE)); @@ -220,65 +216,67 @@ impl GasWasterModule { type Genesis = (); #[handler(call = Self::METHOD_WASTE_GAS)] - fn waste_gas( - ctx: &mut C, + fn waste_gas( + _ctx: &C, _args: (), ) -> Result<(), ::Error> { - ::Core::use_tx_gas(ctx, Self::CALL_GAS)?; + ::Core::use_tx_gas(Self::CALL_GAS)?; Ok(()) } #[handler(call = Self::METHOD_WASTE_GAS_AND_FAIL)] - fn fail( - ctx: &mut C, + fn fail( + _ctx: &C, _args: (), ) -> Result<(), ::Error> { - ::Core::use_tx_gas(ctx, Self::CALL_GAS)?; + ::Core::use_tx_gas(Self::CALL_GAS)?; Err(::Error::Forbidden) } #[handler(call = Self::METHOD_WASTE_GAS_AND_FAIL_EXTRA)] - fn fail_extra( - ctx: &mut C, + fn fail_extra( + _ctx: &C, _args: (), ) -> Result<(), ::Error> { - ::Core::use_tx_gas(ctx, Self::CALL_GAS)?; + ::Core::use_tx_gas(Self::CALL_GAS)?; Err(::Error::Forbidden) } #[handler(call = Self::METHOD_WASTE_GAS_HUGE)] - fn waste_gas_huge( - ctx: &mut C, + fn waste_gas_huge( + _ctx: &C, _args: (), ) -> Result<(), ::Error> { - ::Core::use_tx_gas(ctx, Self::CALL_GAS_HUGE)?; + ::Core::use_tx_gas(Self::CALL_GAS_HUGE)?; Ok(()) } #[handler(call = Self::METHOD_WASTE_GAS_CALLER)] - fn waste_gas_caller( - ctx: &mut C, + fn waste_gas_caller( + _ctx: &C, _args: (), ) -> Result<(), ::Error> { // Uses a different amount of gas based on the caller. + let caller = CurrentState::with_env(|env| env.tx_caller_address()); let addr_zero = Address::default(); let addr_ethzero = CallerAddress::EthAddress([0u8; 20]).address(); - if ctx.tx_caller_address() == addr_zero { - ::Core::use_tx_gas(ctx, Self::CALL_GAS_CALLER_ADDR_ZERO)?; - } else if ctx.tx_caller_address() == addr_ethzero { - ::Core::use_tx_gas(ctx, Self::CALL_GAS_CALLER_ADDR_ETHZERO)?; + if caller == addr_zero { + ::Core::use_tx_gas(Self::CALL_GAS_CALLER_ADDR_ZERO)?; + } else if caller == addr_ethzero { + ::Core::use_tx_gas(Self::CALL_GAS_CALLER_ADDR_ETHZERO)?; } Ok(()) } #[handler(call = Self::METHOD_SPECIFIC_GAS_REQUIRED)] - fn specific_gas_required( - ctx: &mut C, + fn specific_gas_required( + ctx: &C, _args: (), ) -> Result<(), ::Error> { // Fails with an error if less than X gas was specified. (doesn't fail with out-of-gas). - if ctx.tx_auth_info().fee.gas < Self::CALL_GAS_SPECIFIC { + let gas_limit = CurrentState::with_env(|env| env.tx_auth_info().fee.gas); + if gas_limit < Self::CALL_GAS_SPECIFIC { Err(::Error::Forbidden) } else { Ok(()) @@ -286,12 +284,13 @@ impl GasWasterModule { } #[handler(call = Self::METHOD_SPECIFIC_GAS_REQUIRED_HUGE)] - fn specific_gas_required_huge( - ctx: &mut C, + fn specific_gas_required_huge( + ctx: &C, _args: (), ) -> Result<(), ::Error> { // Fails with an error if less than X gas was specified. (doesn't fail with out-of-gas). - if ctx.tx_auth_info().fee.gas < Self::CALL_GAS_SPECIFIC_HUGE { + let gas_limit = CurrentState::with_env(|env| env.tx_auth_info().fee.gas); + if gas_limit < Self::CALL_GAS_SPECIFIC_HUGE { Err(::Error::Forbidden) } else { Ok(()) @@ -362,9 +361,9 @@ fn test_reject_txs() { // The gas waster runtime doesn't implement any authenticate_tx handler, // so it should accept all transactions. let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx_for_runtime::(Mode::CheckTx, false); + let ctx = mock.create_ctx_for_runtime::(false); - GasWasterRuntime::migrate(&mut ctx); + GasWasterRuntime::migrate(&ctx); let tx = transaction::Transaction { version: 1, @@ -396,7 +395,7 @@ fn test_reject_txs() { }, }; - Core::authenticate_tx(&mut ctx, &tx).expect("authenticate should pass if all modules accept"); + Core::authenticate_tx(&ctx, &tx).expect("authenticate should pass if all modules accept"); } #[test] @@ -465,316 +464,321 @@ fn test_query_estimate_gas() { let tx_caller_gas_addr_ethzero = GasWasterRuntime::AUTH_SIGNATURE_GAS + GasWasterModule::CALL_GAS_CALLER_ADDR_ETHZERO; - // Test happy-path execution with default settings. - { - let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx_for_runtime::(Mode::CheckTx, false); - GasWasterRuntime::migrate(&mut ctx); - - // Test estimation with caller derived from the transaction. - let args = types::EstimateGasQuery { - caller: None, - tx: tx.clone(), - propagate_failures: false, - }; - let est = - Core::query_estimate_gas(&mut ctx, args).expect("query_estimate_gas should succeed"); - assert_eq!(est, tx_reference_gas, "estimated gas should be correct"); - - // Test estimation with specified caller. - let args = types::EstimateGasQuery { - caller: Some(CallerAddress::Address(keys::alice::address())), - tx: tx.clone(), - propagate_failures: false, - }; - let est = - Core::query_estimate_gas(&mut ctx, args).expect("query_estimate_gas should succeed"); - assert_eq!( - est, tx_nomultisig_reference_gas, - "estimated gas should be correct" - ); - } + CurrentState::init_local_fallback(); - // Test extra gas estimation. - { - let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx_for_runtime::(Mode::CheckTx, false); - GasWasterRuntime::migrate(&mut ctx); - - // Test estimation on failure. - let args = types::EstimateGasQuery { - caller: None, - tx: tx_fail_extra.clone(), - propagate_failures: false, - }; - let est = - Core::query_estimate_gas(&mut ctx, args).expect("query_estimate_gas should succeed"); - assert_eq!( - est, - tx_reference_gas + GasWasterModule::CALL_GAS_EXTRA, - "estimated gas should be correct" - ); - } + CurrentState::with_transaction_opts(Options::new().with_mode(state::Mode::Check), || { + // Test happy-path execution with default settings. + { + let mut mock = mock::Mock::default(); + let ctx = mock.create_ctx_for_runtime::(false); + GasWasterRuntime::migrate(&ctx); + + // Test estimation with caller derived from the transaction. + let args = types::EstimateGasQuery { + caller: None, + tx: tx.clone(), + propagate_failures: false, + }; + let est = + Core::query_estimate_gas(&ctx, args).expect("query_estimate_gas should succeed"); + assert_eq!(est, tx_reference_gas, "estimated gas should be correct"); + + // Test estimation with specified caller. + let args = types::EstimateGasQuery { + caller: Some(CallerAddress::Address(keys::alice::address())), + tx: tx.clone(), + propagate_failures: false, + }; + let est = + Core::query_estimate_gas(&ctx, args).expect("query_estimate_gas should succeed"); + assert_eq!( + est, tx_nomultisig_reference_gas, + "estimated gas should be correct" + ); + } - // Test expensive estimates. - { - let max_estimated_gas = tx_reference_gas - 1; - let local_config = configmap! { - "core" => configmap! { - "max_estimated_gas" => max_estimated_gas, - }, - }; - let mut mock = mock::Mock::with_local_config(local_config); - let mut ctx = mock.create_ctx_for_runtime::(Mode::CheckTx, false); - GasWasterRuntime::migrate(&mut ctx); - - // Test with limited max_estimated_gas. - let args = types::EstimateGasQuery { - caller: None, - tx: tx.clone(), - propagate_failures: false, - }; - let est = Core::query_estimate_gas(&mut ctx, args) - .expect("query_estimate_gas should succeed even with limited max_estimated_gas"); - assert!( - est <= max_estimated_gas, - "estimated gas should be at most max_estimated_gas={}, was {}", - max_estimated_gas, - est - ); + // Test extra gas estimation. + { + let mut mock = mock::Mock::default(); + let ctx = mock.create_ctx_for_runtime::(false); + GasWasterRuntime::migrate(&ctx); + + // Test estimation on failure. + let args = types::EstimateGasQuery { + caller: None, + tx: tx_fail_extra.clone(), + propagate_failures: false, + }; + let est = + Core::query_estimate_gas(&ctx, args).expect("query_estimate_gas should succeed"); + assert_eq!( + est, + tx_reference_gas + GasWasterModule::CALL_GAS_EXTRA, + "estimated gas should be correct" + ); + } - // Test with limited max_estimated_gas and propagate failures enabled. - let args = types::EstimateGasQuery { - caller: None, - tx: tx.clone(), - propagate_failures: true, - }; - let result = Core::query_estimate_gas(&mut ctx, args).expect_err( + // Test expensive estimates. + { + let max_estimated_gas = tx_reference_gas - 1; + let local_config = configmap! { + "core" => configmap! { + "max_estimated_gas" => max_estimated_gas, + }, + }; + let mut mock = mock::Mock::with_local_config(local_config); + let ctx = mock.create_ctx_for_runtime::(false); + GasWasterRuntime::migrate(&ctx); + + // Test with limited max_estimated_gas. + let args = types::EstimateGasQuery { + caller: None, + tx: tx.clone(), + propagate_failures: false, + }; + let est = Core::query_estimate_gas(&ctx, args) + .expect("query_estimate_gas should succeed even with limited max_estimated_gas"); + assert!( + est <= max_estimated_gas, + "estimated gas should be at most max_estimated_gas={}, was {}", + max_estimated_gas, + est + ); + + // Test with limited max_estimated_gas and propagate failures enabled. + let args = types::EstimateGasQuery { + caller: None, + tx: tx.clone(), + propagate_failures: true, + }; + let result = Core::query_estimate_gas(&ctx, args).expect_err( "query_estimate_gas should fail with limited max_estimated_gas and propagate failures enabled", ); - assert_eq!(result.module_name(), "core"); - assert_eq!(result.code(), 12); - assert_eq!( - result.to_string(), - format!( - "out of gas (limit: {} wanted: {})", - max_estimated_gas, tx_reference_gas - ) - ); - } + assert_eq!(result.module_name(), "core"); + assert_eq!(result.code(), 12); + assert_eq!( + result.to_string(), + format!( + "out of gas (limit: {} wanted: {})", + max_estimated_gas, tx_reference_gas + ) + ); + } - // Test transactions that fail. - { - let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx_for_runtime::(Mode::CheckTx, false); - GasWasterRuntime::migrate(&mut ctx); - - // Test with propagate failures disabled. - let args = types::EstimateGasQuery { - caller: None, - tx: tx_fail.clone(), - propagate_failures: false, - }; - let est = Core::query_estimate_gas(&mut ctx, args) - .expect("query_estimate_gas should succeed even with a transaction that fails"); - assert_eq!(est, tx_reference_gas, "estimated gas should be correct"); - - // Test with propagate failures enabled. - let args = types::EstimateGasQuery { - caller: None, - tx: tx_fail.clone(), - propagate_failures: true, - }; - let result = Core::query_estimate_gas(&mut ctx, args) + // Test transactions that fail. + { + let mut mock = mock::Mock::default(); + let ctx = mock.create_ctx_for_runtime::(false); + GasWasterRuntime::migrate(&ctx); + + // Test with propagate failures disabled. + let args = types::EstimateGasQuery { + caller: None, + tx: tx_fail.clone(), + propagate_failures: false, + }; + let est = Core::query_estimate_gas(&ctx, args) + .expect("query_estimate_gas should succeed even with a transaction that fails"); + assert_eq!(est, tx_reference_gas, "estimated gas should be correct"); + + // Test with propagate failures enabled. + let args = types::EstimateGasQuery { + caller: None, + tx: tx_fail.clone(), + propagate_failures: true, + }; + let result = Core::query_estimate_gas(&ctx, args) .expect_err("query_estimate_gas should fail with a transaction that fails and propagate failures enabled"); - assert_eq!(result.module_name(), "core"); - assert_eq!(result.code(), 22); - assert_eq!(result.to_string(), "forbidden by node policy",); - } + assert_eq!(result.module_name(), "core"); + assert_eq!(result.code(), 22); + assert_eq!(result.to_string(), "forbidden by node policy",); + } - // Test binary search of expensive transactions. - { - let local_config = configmap! { - "core" => configmap! { - "estimate_gas_search_max_iters" => 64, - }, - }; - let mut mock = mock::Mock::with_local_config(local_config); - let mut ctx = mock.create_ctx_for_runtime::(Mode::CheckTx, false); - GasWasterRuntime::migrate(&mut ctx); - - // Test tx estimation. - let args = types::EstimateGasQuery { - caller: None, - tx: tx.clone(), - propagate_failures: false, - }; - let est = - Core::query_estimate_gas(&mut ctx, args).expect("query_estimate_gas should succeed"); - assert_eq!(est, tx_reference_gas, "estimated gas should be correct"); - - // Test tx estimation with propagate failures enabled. - let args = types::EstimateGasQuery { - caller: None, - tx, - propagate_failures: true, - }; - let est = - Core::query_estimate_gas(&mut ctx, args).expect("query_estimate_gas should succeed"); - assert_eq!(est, tx_reference_gas, "estimated gas should be correct"); - - // Test a failing transaction with propagate failures disabled. - let args = types::EstimateGasQuery { - caller: None, - tx: tx_fail.clone(), - propagate_failures: false, - }; - let est = Core::query_estimate_gas(&mut ctx, args) - .expect("query_estimate_gas should succeed even with a transaction that fails"); - assert_eq!(est, tx_reference_gas, "estimated gas should be correct"); - - // Test a failing transaction with propagate failures enabled. - let args = types::EstimateGasQuery { - caller: None, - tx: tx_fail, - propagate_failures: true, - }; - let result = Core::query_estimate_gas(&mut ctx, args) + // Test binary search of expensive transactions. + { + let local_config = configmap! { + "core" => configmap! { + "estimate_gas_search_max_iters" => 64, + }, + }; + let mut mock = mock::Mock::with_local_config(local_config); + let ctx = mock.create_ctx_for_runtime::(false); + GasWasterRuntime::migrate(&ctx); + + // Test tx estimation. + let args = types::EstimateGasQuery { + caller: None, + tx: tx.clone(), + propagate_failures: false, + }; + let est = + Core::query_estimate_gas(&ctx, args).expect("query_estimate_gas should succeed"); + assert_eq!(est, tx_reference_gas, "estimated gas should be correct"); + + // Test tx estimation with propagate failures enabled. + let args = types::EstimateGasQuery { + caller: None, + tx, + propagate_failures: true, + }; + let est = + Core::query_estimate_gas(&ctx, args).expect("query_estimate_gas should succeed"); + assert_eq!(est, tx_reference_gas, "estimated gas should be correct"); + + // Test a failing transaction with propagate failures disabled. + let args = types::EstimateGasQuery { + caller: None, + tx: tx_fail.clone(), + propagate_failures: false, + }; + let est = Core::query_estimate_gas(&ctx, args) + .expect("query_estimate_gas should succeed even with a transaction that fails"); + assert_eq!(est, tx_reference_gas, "estimated gas should be correct"); + + // Test a failing transaction with propagate failures enabled. + let args = types::EstimateGasQuery { + caller: None, + tx: tx_fail, + propagate_failures: true, + }; + let result = Core::query_estimate_gas(&ctx, args) .expect_err("query_estimate_gas should fail with a transaction that fails and propagate failures enabled"); - assert_eq!(result.module_name(), "core"); - assert_eq!(result.code(), 22); - assert_eq!(result.to_string(), "forbidden by node policy",); - - // Test huge tx estimation. - let args = types::EstimateGasQuery { - caller: None, - tx: tx_huge.clone(), - propagate_failures: false, - }; - let est = - Core::query_estimate_gas(&mut ctx, args).expect("query_estimate_gas should succeed"); - assert_eq!( - est, tx_huge_reference_gas, - "estimated gas should be correct" - ); - - // Test huge tx estimation with propagate failures enabled. - let args = types::EstimateGasQuery { - caller: None, - tx: tx_huge, - propagate_failures: true, - }; - let est = - Core::query_estimate_gas(&mut ctx, args).expect("query_estimate_gas should succeed"); - assert_eq!( - est, tx_huge_reference_gas, - "estimated gas should be correct" - ); - - // Test a transaction that requires specific amount of gas, but doesn't fail with out-of-gas. - let args = types::EstimateGasQuery { - caller: None, - tx: tx_specific_gas.clone(), - propagate_failures: false, - }; - let est = - Core::query_estimate_gas(&mut ctx, args).expect("query_estimate_gas should succeed"); - assert_eq!( - est, tx_specific_gas_reference_gas, - "estimated gas should be correct" - ); - - // Test a transaction that requires specific amount of gas, with propagate failures enabled. - let args = types::EstimateGasQuery { - caller: None, - tx: tx_specific_gas, - propagate_failures: true, - }; - let est = - Core::query_estimate_gas(&mut ctx, args).expect("query_estimate_gas should succeed"); - assert_eq!( - est, tx_specific_gas_reference_gas, - "estimated gas should be correct" - ); - - // Test a transaction that requires specific huge amount of gas, but doesn't fail with out-of-gas. - let args = types::EstimateGasQuery { - caller: None, - tx: tx_specific_gas_huge.clone(), - propagate_failures: false, - }; - let est = - Core::query_estimate_gas(&mut ctx, args).expect("query_estimate_gas should succeed"); - assert_eq!( - est, tx_specific_gas_huge_reference_gas, - "estimated gas should be correct" - ); - - // Test a transaction that requires specific amount of gas, with propagate failures enabled. - let args = types::EstimateGasQuery { - caller: None, - tx: tx_specific_gas_huge, - propagate_failures: true, - }; - let est = - Core::query_estimate_gas(&mut ctx, args).expect("query_estimate_gas should succeed"); - assert_eq!( - est, tx_specific_gas_huge_reference_gas, - "estimated gas should be correct" - ); - } - - // Test confidential estimation that should zeroize the caller. - { - let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx_for_runtime::(Mode::CheckTx, true); - GasWasterRuntime::migrate(&mut ctx); - - // Test estimation with caller derived from the transaction. - let args = types::EstimateGasQuery { - caller: None, - tx: tx_caller_specific.clone(), - propagate_failures: false, - }; - let est = - Core::query_estimate_gas(&mut ctx, args).expect("query_estimate_gas should succeed"); - assert_eq!( - est, tx_caller_gas_addr_zero, - "estimated gas should be correct" - ); - - // Test estimation with specified caller. - let args = types::EstimateGasQuery { - caller: Some(CallerAddress::Address(keys::alice::address())), - tx: tx_caller_specific.clone(), - propagate_failures: false, - }; - let est = - Core::query_estimate_gas(&mut ctx, args).expect("query_estimate_gas should succeed"); - assert_eq!( - est, tx_caller_gas_addr_zero, - "estimated gas should be correct" - ); + assert_eq!(result.module_name(), "core"); + assert_eq!(result.code(), 22); + assert_eq!(result.to_string(), "forbidden by node policy",); + + // Test huge tx estimation. + let args = types::EstimateGasQuery { + caller: None, + tx: tx_huge.clone(), + propagate_failures: false, + }; + let est = + Core::query_estimate_gas(&ctx, args).expect("query_estimate_gas should succeed"); + assert_eq!( + est, tx_huge_reference_gas, + "estimated gas should be correct" + ); + + // Test huge tx estimation with propagate failures enabled. + let args = types::EstimateGasQuery { + caller: None, + tx: tx_huge, + propagate_failures: true, + }; + let est = + Core::query_estimate_gas(&ctx, args).expect("query_estimate_gas should succeed"); + assert_eq!( + est, tx_huge_reference_gas, + "estimated gas should be correct" + ); + + // Test a transaction that requires specific amount of gas, but doesn't fail with out-of-gas. + let args = types::EstimateGasQuery { + caller: None, + tx: tx_specific_gas.clone(), + propagate_failures: false, + }; + let est = + Core::query_estimate_gas(&ctx, args).expect("query_estimate_gas should succeed"); + assert_eq!( + est, tx_specific_gas_reference_gas, + "estimated gas should be correct" + ); + + // Test a transaction that requires specific amount of gas, with propagate failures enabled. + let args = types::EstimateGasQuery { + caller: None, + tx: tx_specific_gas, + propagate_failures: true, + }; + let est = + Core::query_estimate_gas(&ctx, args).expect("query_estimate_gas should succeed"); + assert_eq!( + est, tx_specific_gas_reference_gas, + "estimated gas should be correct" + ); + + // Test a transaction that requires specific huge amount of gas, but doesn't fail with out-of-gas. + let args = types::EstimateGasQuery { + caller: None, + tx: tx_specific_gas_huge.clone(), + propagate_failures: false, + }; + let est = + Core::query_estimate_gas(&ctx, args).expect("query_estimate_gas should succeed"); + assert_eq!( + est, tx_specific_gas_huge_reference_gas, + "estimated gas should be correct" + ); + + // Test a transaction that requires specific amount of gas, with propagate failures enabled. + let args = types::EstimateGasQuery { + caller: None, + tx: tx_specific_gas_huge, + propagate_failures: true, + }; + let est = + Core::query_estimate_gas(&ctx, args).expect("query_estimate_gas should succeed"); + assert_eq!( + est, tx_specific_gas_huge_reference_gas, + "estimated gas should be correct" + ); + } - // Test estimation with specified caller (eth address). - let args = types::EstimateGasQuery { - caller: Some(CallerAddress::EthAddress([42u8; 20])), - tx: tx_caller_specific.clone(), - propagate_failures: false, - }; - let est = - Core::query_estimate_gas(&mut ctx, args).expect("query_estimate_gas should succeed"); - assert_eq!( - est, tx_caller_gas_addr_ethzero, - "estimated gas should be correct" - ); - } + // Test confidential estimation that should zeroize the caller. + { + let mut mock = mock::Mock::default(); + let ctx = mock.create_ctx_for_runtime::(true); + GasWasterRuntime::migrate(&ctx); + + // Test estimation with caller derived from the transaction. + let args = types::EstimateGasQuery { + caller: None, + tx: tx_caller_specific.clone(), + propagate_failures: false, + }; + let est = + Core::query_estimate_gas(&ctx, args).expect("query_estimate_gas should succeed"); + assert_eq!( + est, tx_caller_gas_addr_zero, + "estimated gas should be correct" + ); + + // Test estimation with specified caller. + let args = types::EstimateGasQuery { + caller: Some(CallerAddress::Address(keys::alice::address())), + tx: tx_caller_specific.clone(), + propagate_failures: false, + }; + let est = + Core::query_estimate_gas(&ctx, args).expect("query_estimate_gas should succeed"); + assert_eq!( + est, tx_caller_gas_addr_zero, + "estimated gas should be correct" + ); + + // Test estimation with specified caller (eth address). + let args = types::EstimateGasQuery { + caller: Some(CallerAddress::EthAddress([42u8; 20])), + tx: tx_caller_specific.clone(), + propagate_failures: false, + }; + let est = + Core::query_estimate_gas(&ctx, args).expect("query_estimate_gas should succeed"); + assert_eq!( + est, tx_caller_gas_addr_ethzero, + "estimated gas should be correct" + ); + } + }); } #[test] fn test_approve_unverified_tx() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); + let ctx = mock.create_ctx(); + Core::set_params(Parameters { max_batch_gas: u64::MAX, max_tx_size: 32 * 1024, @@ -788,9 +792,10 @@ fn test_approve_unverified_tx() { }, dynamic_min_gas_price: Default::default(), }); + let dummy_bytes = b"you look, you die".to_vec(); Core::approve_unverified_tx( - &mut ctx, + &ctx, &transaction::UnverifiedTransaction( dummy_bytes.clone(), vec![ @@ -800,8 +805,9 @@ fn test_approve_unverified_tx() { ), ) .expect("at max"); + Core::approve_unverified_tx( - &mut ctx, + &ctx, &transaction::UnverifiedTransaction( dummy_bytes.clone(), vec![ @@ -812,8 +818,9 @@ fn test_approve_unverified_tx() { ), ) .expect_err("too many authentication slots"); + Core::approve_unverified_tx( - &mut ctx, + &ctx, &transaction::UnverifiedTransaction( dummy_bytes.clone(), vec![ @@ -827,43 +834,32 @@ fn test_approve_unverified_tx() { #[test] fn test_set_priority() { - let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); + let _mock = mock::Mock::default(); - assert_eq!( - 0, - Core::take_priority(&mut ctx), - "default priority should be 0" - ); + assert_eq!(0, Core::take_priority(), "default priority should be 0"); - Core::set_priority(&mut ctx, 1); - Core::set_priority(&mut ctx, 11); + Core::set_priority(1); + Core::set_priority(11); - let tx = mock::transaction(); - ctx.with_tx(tx.into(), |mut tx_ctx, _call| { - Core::set_priority(&mut tx_ctx, 10); + CurrentState::with_transaction(|| { + Core::set_priority(10); }); - assert_eq!( - 10, - Core::take_priority(&mut ctx), - "setting priority should work" - ); + assert_eq!(10, Core::take_priority(), "setting priority should work"); } #[test] fn test_set_sender_meta() { - let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); + let _mock = mock::Mock::default(); let sender_meta = SenderMeta { address: keys::alice::address(), tx_nonce: 42, state_nonce: 43, }; - Core::set_sender_meta(&mut ctx, sender_meta.clone()); + Core::set_sender_meta(sender_meta.clone()); - let taken_sender_meta = Core::take_sender_meta(&mut ctx); + let taken_sender_meta = Core::take_sender_meta(); assert_eq!( taken_sender_meta, sender_meta, "setting sender metadata should work" @@ -873,7 +869,7 @@ fn test_set_sender_meta() { #[test] fn test_min_gas_price() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx_for_runtime::(Mode::CheckTx, false); + let ctx = mock.create_ctx_for_runtime::(false); Core::set_params(Parameters { max_batch_gas: u64::MAX, @@ -924,15 +920,16 @@ fn test_min_gas_price() { ..Default::default() }, }; + let call = tx.call.clone(); - ctx.with_tx(tx.clone().into(), |mut tx_ctx, call| { - Core::before_handle_call(&mut tx_ctx, &call).expect_err("gas price should be too low"); + CurrentState::with_transaction_opts(Options::new().with_tx(tx.clone().into()), || { + Core::before_handle_call(&ctx, &call).expect_err("gas price should be too low"); }); tx.auth_info.fee.amount = token::BaseUnits::new(100000, token::Denomination::NATIVE); - ctx.with_tx(tx.clone().into(), |mut tx_ctx, call| { - Core::before_handle_call(&mut tx_ctx, &call).expect("gas price should be ok"); + CurrentState::with_transaction_opts(Options::new().with_tx(tx.clone().into()), || { + Core::before_handle_call(&ctx, &call).expect("gas price should be ok"); }); // Test local override. @@ -952,108 +949,60 @@ fn test_min_gas_price() { once_cell::unsync::Lazy::new(|| BTreeSet::from(["exempt.Method"])); } - ctx.with_tx(tx.clone().into(), |mut tx_ctx, call| { - super::Module::::before_handle_call(&mut tx_ctx, &call) - .expect_err("gas price should be too low"); - }); - - tx.auth_info.fee.amount = token::BaseUnits::new(1_000_000, token::Denomination::NATIVE); - - ctx.with_tx(tx.clone().into(), |mut tx_ctx, call| { - super::Module::::before_handle_call(&mut tx_ctx, &call) - .expect("gas price should be ok"); - }); - - tx.auth_info.fee.amount = token::BaseUnits::new(1_000, "SMALLER".parse().unwrap()); - - ctx.with_tx(tx.clone().into(), |mut tx_ctx, call| { - super::Module::::before_handle_call(&mut tx_ctx, &call) - .expect_err("gas price should be too low"); - }); - - tx.auth_info.fee.amount = token::BaseUnits::new(10_000, "SMALLER".parse().unwrap()); - - ctx.with_tx(tx.clone().into(), |mut tx_ctx, call| { - super::Module::::before_handle_call(&mut tx_ctx, &call) - .expect("gas price should be ok"); - }); - - // Test exempt methods. - tx.call.method = "exempt.Method".into(); - tx.auth_info.fee.amount = token::BaseUnits::new(100_000, token::Denomination::NATIVE); + CurrentState::with_transaction_opts( + state::Options::new().with_mode(state::Mode::Check), + || { + CurrentState::with_transaction_opts(Options::new().with_tx(tx.clone().into()), || { + super::Module::::before_handle_call(&ctx, &call) + .expect_err("gas price should be too low"); + }); - ctx.with_tx(tx.clone().into(), |mut tx_ctx, call| { - super::Module::::before_handle_call(&mut tx_ctx, &call) - .expect("method should be gas price exempt"); - }); + tx.auth_info.fee.amount = token::BaseUnits::new(1_000_000, token::Denomination::NATIVE); - tx.auth_info.fee.amount = token::BaseUnits::new(0, token::Denomination::NATIVE); + CurrentState::with_transaction_opts(Options::new().with_tx(tx.clone().into()), || { + super::Module::::before_handle_call(&ctx, &call) + .expect("gas price should be ok"); + }); - ctx.with_tx(tx.into(), |mut tx_ctx, call| { - super::Module::::before_handle_call(&mut tx_ctx, &call) - .expect("method should be gas price exempt"); - }); -} + tx.auth_info.fee.amount = token::BaseUnits::new(1_000, "SMALLER".parse().unwrap()); -#[test] -fn test_emit_events() { - let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); + CurrentState::with_transaction_opts(Options::new().with_tx(tx.clone().into()), || { + super::Module::::before_handle_call(&ctx, &call) + .expect_err("gas price should be too low"); + }); - #[derive(Debug, Default, PartialEq, Eq, cbor::Encode, cbor::Decode)] - struct TestEvent { - i: u64, - } + tx.auth_info.fee.amount = token::BaseUnits::new(10_000, "SMALLER".parse().unwrap()); - impl crate::event::Event for TestEvent { - fn module_name() -> &'static str { - "testevent" - } - fn code(&self) -> u32 { - 0 - } - } + CurrentState::with_transaction_opts(Options::new().with_tx(tx.clone().into()), || { + super::Module::::before_handle_call(&ctx, &call) + .expect("gas price should be ok"); + }); - ctx.emit_event(TestEvent { i: 42 }); - let etags = ctx.with_tx(mock::transaction().into(), |mut ctx, _| { - ctx.emit_event(TestEvent { i: 2 }); - ctx.emit_event(TestEvent { i: 3 }); - ctx.emit_event(TestEvent { i: 1 }); + // Test exempt methods. + tx.call.method = "exempt.Method".into(); + tx.auth_info.fee.amount = token::BaseUnits::new(100_000, token::Denomination::NATIVE); + let call = tx.call.clone(); - let state = ctx.commit(); - let tags = state.events.clone().into_tags(); - assert_eq!(tags.len(), 1, "1 emitted tag expected"); + CurrentState::with_transaction_opts(Options::new().with_tx(tx.clone().into()), || { + super::Module::::before_handle_call(&ctx, &call) + .expect("method should be gas price exempt"); + }); - let events: Vec = cbor::from_slice(&tags[0].value).unwrap(); - assert_eq!(events.len(), 3, "3 emitted events expected"); - assert_eq!(TestEvent { i: 2 }, events[0], "expected events emitted"); - assert_eq!(TestEvent { i: 3 }, events[1], "expected events emitted"); - assert_eq!(TestEvent { i: 1 }, events[2], "expected events emitted"); + tx.auth_info.fee.amount = token::BaseUnits::new(0, token::Denomination::NATIVE); - state.events - }); - // Forward tx emitted etags. - ctx.emit_etags(etags); - // Emit one more event. - ctx.emit_event(TestEvent { i: 0 }); - - let state = ctx.commit(); - let tags = state.events.into_tags(); - assert_eq!(tags.len(), 1, "1 emitted tag expected"); - - let events: Vec = cbor::from_slice(&tags[0].value).unwrap(); - assert_eq!(events.len(), 5, "5 emitted events expected"); - assert_eq!(TestEvent { i: 42 }, events[0], "expected events emitted"); - assert_eq!(TestEvent { i: 2 }, events[1], "expected events emitted"); - assert_eq!(TestEvent { i: 3 }, events[2], "expected events emitted"); - assert_eq!(TestEvent { i: 1 }, events[3], "expected events emitted"); - assert_eq!(TestEvent { i: 0 }, events[4], "expected events emitted"); + CurrentState::with_transaction_opts(Options::new().with_tx(tx.clone().into()), || { + super::Module::::before_handle_call(&ctx, &call) + .expect("method should be gas price exempt"); + }); + }, + ); } #[test] fn test_gas_used_events() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx(); + let ctx = mock.create_ctx(); + Core::set_params(Parameters { max_batch_gas: 1_000_000, max_tx_size: 32 * 1024, @@ -1071,33 +1020,21 @@ fn test_gas_used_events() { let mut tx = mock::transaction(); tx.auth_info.fee.gas = 100_000; - let etags = ctx.with_tx(tx.into(), |mut tx_ctx, _call| { - Core::use_tx_gas(&mut tx_ctx, 10).expect("using gas under limit should succeed"); - assert_eq!(Core::used_tx_gas(&mut tx_ctx), 10); + CurrentState::with_transaction_opts(Options::new().with_tx(tx.into()), || { + Core::use_tx_gas(10).expect("using gas under limit should succeed"); + assert_eq!(Core::used_tx_gas(), 10); Core::after_handle_call( - &mut tx_ctx, + &ctx, module::CallResult::Ok(cbor::Value::Simple(cbor::SimpleValue::NullValue)), ) .expect("after_handle_call should succeed"); - let state = tx_ctx.commit(); - let tags = state.events.clone().into_tags(); + let tags = CurrentState::with(|state| state.take_all_events().into_tags()); assert_eq!(tags.len(), 1, "1 emitted tag expected"); let expected = cbor::to_vec(vec![Event::GasUsed { amount: 10 }]); assert_eq!(tags[0].value, expected, "expected events emitted"); - - state.events }); - // Forward tx emitted etags. - ctx.emit_etags(etags); - - let state = ctx.commit(); - let tags = state.events.into_tags(); - assert_eq!(tags.len(), 1, "1 emitted tags expected"); - - let expected = cbor::to_vec(vec![Event::GasUsed { amount: 10 }]); - assert_eq!(tags[0].value, expected, "expected events emitted"); } /// Constructs a BTreeMap using a `btreemap! { key => value, ... }` syntax. @@ -1119,7 +1056,7 @@ fn test_module_info() { use types::{MethodHandlerInfo, MethodHandlerKind}; let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx_for_runtime::(Mode::CheckTx, false); + let ctx = mock.create_ctx_for_runtime::(false); // Set bogus params on the core module; we want to see them reflected in response to the `runtime_info()` query. let core_params = Parameters { @@ -1130,7 +1067,7 @@ fn test_module_info() { }; Core::set_params(core_params.clone()); - let info = Core::query_runtime_info(&mut ctx, ()).unwrap(); + let info = Core::query_runtime_info(&ctx, ()).unwrap(); assert_eq!( info, types::RuntimeInfoResponse { @@ -1209,7 +1146,7 @@ fn test_min_gas_price_update() { #[test] fn test_dynamic_min_gas_price() { let mut mock = mock::Mock::default(); - let mut ctx = mock.create_ctx_for_runtime::(Mode::CheckTx, false); + let ctx = mock.create_ctx_for_runtime::(false); let denom: token::Denomination = "SMALLER".parse().unwrap(); Core::set_params(Parameters { @@ -1265,49 +1202,58 @@ fn test_dynamic_min_gas_price() { ..Default::default() }, }; + let call = tx.call.clone(); assert_eq!( - Core::min_gas_price(&mut ctx, &token::Denomination::NATIVE), + Core::min_gas_price(&ctx, &token::Denomination::NATIVE), Some(1000) ); - assert_eq!(Core::min_gas_price(&mut ctx, &denom), Some(100)); + assert_eq!(Core::min_gas_price(&ctx, &denom), Some(100)); // Simulate some full blocks (with max gas usage). for round in 0..=10 { mock.runtime_header.round = round; - let mut ctx = mock.create_ctx(); - Core::begin_block(&mut ctx); + let ctx = mock.create_ctx(); + CurrentState::with_transaction(|| { + // Simulate a new block starting by starting with fresh per-block values. + CurrentState::with(|state| state.hide_block_values()); - for _ in 0..909 { - // Each tx uses 11 gas, this makes it 9999/10_000 block gas used. - ctx.with_tx(tx.clone().into(), |mut tx_ctx, call| { - Core::before_handle_call(&mut tx_ctx, &call).expect("gas price should be ok"); - }); - } + Core::begin_block(&ctx); - Core::end_block(&mut ctx); + for _ in 0..909 { + // Each tx uses 11 gas, this makes it 9999/10_000 block gas used. + CurrentState::with_transaction_opts( + Options::new().with_tx(tx.clone().into()), + || { + Core::before_handle_call(&ctx, &call).expect("gas price should be ok"); + }, + ); + } + + Core::end_block(&ctx); + }); } - let mut ctx = mock.create_ctx(); + let ctx = mock.create_ctx(); assert_eq!( - Core::min_gas_price(&mut ctx, &token::Denomination::NATIVE), + Core::min_gas_price(&ctx, &token::Denomination::NATIVE), Some(3598) // Gas price should increase. ); - assert_eq!(Core::min_gas_price(&mut ctx, &denom), Some(350)); + assert_eq!(Core::min_gas_price(&ctx, &denom), Some(350)); // Simulate some empty blocks. for round in 10..=100 { mock.runtime_header.round = round; - let mut ctx = mock.create_ctx(); - Core::begin_block(&mut ctx); - Core::end_block(&mut ctx); + let ctx = mock.create_ctx(); + Core::begin_block(&ctx); + Core::end_block(&ctx); } - let mut ctx = mock.create_ctx(); + let ctx = mock.create_ctx(); assert_eq!( - Core::min_gas_price(&mut ctx, &token::Denomination::NATIVE), + Core::min_gas_price(&ctx, &token::Denomination::NATIVE), Some(1000) // Gas price should decrease to the configured min gas price. ); - assert_eq!(Core::min_gas_price(&mut ctx, &denom), Some(100)); + assert_eq!(Core::min_gas_price(&ctx, &denom), Some(100)); } diff --git a/runtime-sdk/src/modules/rewards/mod.rs b/runtime-sdk/src/modules/rewards/mod.rs index bbcae5ed5f..e993572b64 100644 --- a/runtime-sdk/src/modules/rewards/mod.rs +++ b/runtime-sdk/src/modules/rewards/mod.rs @@ -13,7 +13,8 @@ use crate::{ modules::{self, core::API as _}, runtime::Runtime, sdk_derive, - storage::{self, CurrentStore, Store}, + state::CurrentState, + storage::{self, Store}, types::address::{Address, SignatureAddressSpec}, }; @@ -112,7 +113,7 @@ impl Module { #[migration(from = 1)] fn migrate_v1_to_v2() { - CurrentStore::with(|store| { + CurrentState::with_store(|store| { // Version 2 removes the LAST_EPOCH storage state which was at 0x01. let mut store = storage::PrefixStore::new(store, &MODULE_NAME); store.remove(&[0x01]); @@ -123,11 +124,11 @@ impl Module { impl module::TransactionHandler for Module {} impl module::BlockHandler for Module { - fn end_block(ctx: &mut C) { + fn end_block(ctx: &C) { let epoch = ctx.epoch(); // Load rewards accumulator for the current epoch. - let mut rewards: types::EpochRewards = CurrentStore::with(|store| { + let mut rewards: types::EpochRewards = CurrentState::with_store(|store| { let store = storage::PrefixStore::new(store, &MODULE_NAME); let epochs = storage::TypedStore::new(storage::PrefixStore::new(store, &state::REWARDS)); @@ -147,8 +148,8 @@ impl module::BlockHandler for Module } // Disburse any rewards for previous epochs when the epoch changes. - if ::Core::has_epoch_changed(ctx) { - let epoch_rewards = CurrentStore::with(|store| { + if ::Core::has_epoch_changed() { + let epoch_rewards = CurrentState::with_store(|store| { let store = storage::PrefixStore::new(store, &MODULE_NAME); let mut epochs = storage::TypedStore::new(storage::PrefixStore::new(store, &state::REWARDS)); @@ -179,7 +180,7 @@ impl module::BlockHandler for Module params.participation_threshold_numerator, params.participation_threshold_denominator, ) { - match Accounts::transfer(ctx, *ADDRESS_REWARD_POOL, address, &reward) { + match Accounts::transfer(*ADDRESS_REWARD_POOL, address, &reward) { Ok(_) => {} Err(modules::accounts::Error::InsufficientBalance) => { // Since rewards are the same for the whole epoch, if there is not @@ -194,7 +195,7 @@ impl module::BlockHandler for Module } // Update rewards for current epoch. - CurrentStore::with(|store| { + CurrentState::with_store(|store| { let store = storage::PrefixStore::new(store, &MODULE_NAME); let mut epochs = storage::TypedStore::new(storage::PrefixStore::new(store, &state::REWARDS)); diff --git a/runtime-sdk/src/runtime.rs b/runtime-sdk/src/runtime.rs index 00dfe8422d..3546122507 100644 --- a/runtime-sdk/src/runtime.rs +++ b/runtime-sdk/src/runtime.rs @@ -20,7 +20,8 @@ use crate::{ TransactionHandler, }, modules, - storage::{self, CurrentStore}, + state::CurrentState, + storage::{self}, }; /// A runtime. @@ -64,7 +65,7 @@ pub trait Runtime { /// Perform runtime-specific state migration. This method is only called when the recorded /// state version does not match `STATE_VERSION`. - fn migrate_state(_ctx: &mut C) { + fn migrate_state(_ctx: &C) { // Default implementation doesn't perform any migration. } @@ -90,8 +91,8 @@ pub trait Runtime { } /// Perform state migrations if required. - fn migrate(ctx: &mut C) { - let mut metadata = CurrentStore::with(|store| { + fn migrate(ctx: &C) { + let mut metadata = CurrentState::with_store(|store| { let store = storage::TypedStore::new(storage::PrefixStore::new( store, &modules::core::MODULE_NAME, @@ -113,7 +114,9 @@ pub trait Runtime { .get(modules::core::types::VERSION_GLOBAL_KEY) .copied() .unwrap_or_default(); - if global_version != Self::STATE_VERSION && !ctx.is_check_only() { + if global_version != Self::STATE_VERSION + && !CurrentState::with_env(|env| env.is_check_only()) + { assert!( // There should either be no state, or it should be the previous version. global_version == 0 || global_version == Self::STATE_VERSION - 1, @@ -134,7 +137,7 @@ pub trait Runtime { // If there are any changes, update metadata. if has_changes { - CurrentStore::with(|store| { + CurrentState::with_store(|store| { let mut store = storage::TypedStore::new(storage::PrefixStore::new( store, &modules::core::MODULE_NAME, diff --git a/runtime-sdk/src/state.rs b/runtime-sdk/src/state.rs new file mode 100644 index 0000000000..cb97b1798d --- /dev/null +++ b/runtime-sdk/src/state.rs @@ -0,0 +1,1590 @@ +use std::{ + any::Any, + cell::RefCell, + collections::btree_map::{BTreeMap, Entry}, + fmt, + marker::PhantomData, + mem, +}; + +use oasis_core_runtime::{common::crypto::hash::Hash, consensus::roothash, storage::mkvs}; + +use crate::{ + context::Context, + crypto::random::RootRng, + event::{Event, EventTag, EventTags}, + modules::core::Error, + storage::{MKVSStore, NestedStore, OverlayStore, Store}, + types::{address::Address, message::MessageEventHookInvocation, transaction}, +}; + +/// Execution mode. +#[derive(Clone, Copy, Default, Debug, PartialEq, Eq)] +#[repr(u8)] +pub enum Mode { + /// Actually execute transactions during block production. + #[default] + Execute, + /// Check that transactions are valid for local acceptance into the transaction pool. + Check, + /// Simulate transaction outcomes (e.g. for gas estimation). + Simulate, + /// Check that transactions are still valid before scheduling. + PreSchedule, +} + +const MODE_CHECK: &str = "check"; +const MODE_EXECUTE: &str = "execute"; +const MODE_SIMULATE: &str = "simulate"; +const MODE_PRE_SCHEDULE: &str = "pre_schedule"; + +impl fmt::Display for Mode { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.into()) + } +} + +impl From<&Mode> for &'static str { + fn from(m: &Mode) -> Self { + match m { + Mode::Check => MODE_CHECK, + Mode::Execute => MODE_EXECUTE, + Mode::Simulate => MODE_SIMULATE, + Mode::PreSchedule => MODE_PRE_SCHEDULE, + } + } +} + +/// Information about the execution environment. +#[derive(Clone, Default, Debug)] +pub struct Environment { + mode: Mode, + tx: Option, + internal: bool, +} + +impl Environment { + /// Execution mode. + pub fn mode(&self) -> Mode { + self.mode + } + + /// Whether the execution mode is such that only checks should be performed. + pub fn is_check_only(&self) -> bool { + matches!(self.mode, Mode::Check | Mode::PreSchedule) + } + + /// Whether the execution mode is `Mode::PreSchedule`. + pub fn is_pre_schedule(&self) -> bool { + matches!(self.mode, Mode::PreSchedule) + } + + /// Whether the execution mode is `Mode::Simulate`. + pub fn is_simulation(&self) -> bool { + matches!(self.mode, Mode::Simulate) + } + + /// Whether the execution mode is `Mode::Execute`. + pub fn is_execute(&self) -> bool { + matches!(self.mode, Mode::Execute) + } + + /// Whether there is an active transaction in the current environment. + pub fn is_transaction(&self) -> bool { + self.tx.is_some() + } + + /// An active transaction's index (order) within the block. + /// + /// # Panics + /// + /// This method will panic if called outside a transaction environment. + pub fn tx_index(&self) -> usize { + self.tx + .as_ref() + .map(|tx| tx.index) + .expect("only in transaction environment") + } + + /// An active transaction's size in bytes. + /// + /// # Panics + /// + /// This method will panic if called outside a transaction environment. + pub fn tx_size(&self) -> u32 { + self.tx + .as_ref() + .map(|tx| tx.size) + .expect("only in transaction environment") + } + + /// An active transaction's authentication information. + /// + /// # Panics + /// + /// This method will panic if called outside a transaction environment. + pub fn tx_auth_info(&self) -> &transaction::AuthInfo { + self.tx + .as_ref() + .map(|tx| &tx.data.auth_info) + .expect("only in transaction environment") + } + + /// An active transaction's call format. + /// + /// # Panics + /// + /// This method will panic if called outside a transaction environment. + pub fn tx_call_format(&self) -> transaction::CallFormat { + self.tx + .as_ref() + .map(|tx| tx.data.call.format) + .expect("only in transaction environment") + } + + /// An active transaction's read only flag. + /// + /// # Panics + /// + /// This method will panic if called outside a transaction environment. + pub fn is_read_only(&self) -> bool { + self.tx + .as_ref() + .map(|tx| tx.data.call.read_only) + .expect("only in transaction environment") + } + + /// Whether the current execution environment is part of an internal subcall. + pub fn is_internal(&self) -> bool { + self.internal + } + + /// Authenticated address of the caller. + /// + /// In case there are multiple signers of a transaction, this will return the address + /// corresponding to the first signer. If there are no signers, it returns the default address. + /// + /// # Panics + /// + /// This method will panic if called outside a transaction environment. + pub fn tx_caller_address(&self) -> Address { + self.tx_auth_info() + .signer_info + .first() + .map(|si| si.address_spec.address()) + .unwrap_or_default() + } +} + +/// Decoded transaction with additional metadata. +#[derive(Clone, Debug)] +pub struct TransactionWithMeta { + /// Decoded transaction. + pub data: transaction::Transaction, + /// Transaction size. + pub size: u32, + /// Transaction index within the batch. + pub index: usize, + /// Transaction hash. + pub hash: Hash, +} + +impl TransactionWithMeta { + /// Create transaction with metadata for an internally generated transaction. + /// + /// Internally generated transactions have zero size, index and hash. + pub fn internal(tx: transaction::Transaction) -> Self { + Self { + data: tx, + size: 0, + index: 0, + hash: Default::default(), + } + } +} + +#[cfg(any(test, feature = "test"))] +impl From for TransactionWithMeta { + fn from(tx: transaction::Transaction) -> Self { + Self::internal(tx) // For use in tests. + } +} + +/// Environment modification options. +#[derive(Clone, Default, Debug)] +pub struct Options { + pub mode: Option, + pub tx: Option, + pub internal: Option, + pub rng_local_entropy: bool, +} + +impl Options { + /// Create options with default values. + pub fn new() -> Self { + Self::default() + } + + /// Change the execution mode of the environment. + pub fn with_mode(self, mode: Mode) -> Self { + Self { + mode: Some(mode), + ..self + } + } + + /// Change the active transaction of the environment. + pub fn with_tx(self, tx: TransactionWithMeta) -> Self { + Self { + tx: Some(tx), + ..self + } + } + + /// Change the internal flag of the environment. + pub fn with_internal(self, internal: bool) -> Self { + Self { + internal: Some(internal), + ..self + } + } + + /// Request for local entropy to be mixed into the current RNG. + /// + /// # Determinisim + /// + /// Using this method will result in non-deterministic behavior as the node's local entropy is + /// mixed into the RNG. As such, this method should only be used in cases where non-determinism + /// is not problematic (e.g. local queries). + pub fn with_rng_local_entropy(self) -> Self { + Self { + rng_local_entropy: true, + ..self + } + } +} + +/// Mutable block state of a runtime. +/// +/// The state includes storage, emitted events, messages to consensus layer, etc. States can be +/// nested via `open`, `commit` and `rollback` methods which behave like transactions. +pub struct State { + parent: Option>, + store: Option>>, + + events: EventTags, + unconditional_events: EventTags, + messages: Vec<(roothash::Message, MessageEventHookInvocation)>, + + block_values: BTreeMap<&'static str, Box>, + hidden_block_values: Option>>, + local_values: BTreeMap<&'static str, Box>, + + rng: Option, + hidden_rng: Option, + env: Environment, + + always_rollback: bool, +} + +impl State { + /// Initialize the state with the given options. + fn init(&mut self, opts: Options) { + if let Some(mode) = opts.mode { + // Change mode. + self.env.mode = mode; + // If we have enabled pre-schedule or simulation mode, always rollback state and hide + // block values to prevent leaking them. + if matches!(mode, Mode::PreSchedule | Mode::Simulate) { + self.always_rollback = true; + self.hide_block_values(); + } + } + + if let Some(tx) = opts.tx { + // Change RNG state. + self.rng.as_mut().unwrap().append_tx(tx.hash); + // Change tx metadata. + self.env.tx = Some(tx); + } + + if let Some(internal) = opts.internal { + self.env.internal = internal; + if internal { + self.hide_block_values(); + } + } + + if opts.rng_local_entropy { + // Append local entropy to RNG state. + self.rng.as_mut().unwrap().append_local_entropy(); + } + + if !matches!(self.env.mode, Mode::PreSchedule) { + // Record opening a child state in the RNG. + self.rng.as_mut().unwrap().append_subcontext(); + } else { + // Use an invalid RNG as its use is not allowed in pre-schedule context. + self.disable_rng(); + } + } + + /// Open a child state after which self will point to the child state. + pub fn open(&mut self) { + let mut parent = Self { + parent: None, + store: None, + events: EventTags::new(), + unconditional_events: EventTags::new(), + messages: Vec::new(), + block_values: BTreeMap::new(), + hidden_block_values: None, + local_values: BTreeMap::new(), + rng: None, + hidden_rng: None, + env: self.env.clone(), + always_rollback: false, + }; + mem::swap(&mut parent, self); + + // Wrap parent store to create an overlay child store. + self.store = parent + .store + .take() + .map(|pstore| OverlayStore::new(Box::new(pstore) as Box)); + + // Take block values map. We will put it back after commit/rollback. + mem::swap(&mut parent.block_values, &mut self.block_values); + // Take RNG. We will put it back after commit/rollback. + mem::swap(&mut parent.rng, &mut self.rng); + + self.parent = Some(Box::new(parent)); + } + + fn convert_store(store: Box) -> OverlayStore> { + let raw = Box::into_raw(store); + unsafe { + // SAFETY: This is safe because we always wrap child stores into OverlayStore. + *Box::from_raw(raw as *mut OverlayStore>) + } + } + + /// Commit the current state and return to its parent state. + /// + /// # Panics + /// + /// This method will panic when attempting to commit the root state. + pub fn commit(&mut self) { + if self.always_rollback { + self.rollback(); + } else { + self._commit(); + } + } + + fn _commit(&mut self) { + let mut child = *self.parent.take().expect("cannot commit on root state"); + mem::swap(&mut child, self); + + // Commit storage. + self.store = child + .store + .take() + .map(|cstore| Self::convert_store(cstore.commit())); + + // Propagate messages. + self.messages.extend(child.messages); + + // Propagate events. + for (key, event) in child.events { + let events = self.events.entry(key).or_default(); + events.extend(event); + } + for (key, event) in child.unconditional_events { + let events = self.unconditional_events.entry(key).or_default(); + events.extend(event); + } + + // Put back per-block values. + if let Some(mut block_values) = child.hidden_block_values { + mem::swap(&mut block_values, &mut self.block_values); // Block values were hidden. + } else { + mem::swap(&mut child.block_values, &mut self.block_values); + } + // Always drop local values. + + // Put back RNG. + if child.hidden_rng.is_some() { + mem::swap(&mut child.hidden_rng, &mut self.rng); // RNG was hidden. + } else { + mem::swap(&mut child.rng, &mut self.rng); + } + } + + /// Rollback the current state and return to its parent state. + /// + /// # Panics + /// + /// This method will panic when attempting to rollback the root state. + pub fn rollback(&mut self) { + let mut child = *self.parent.take().expect("cannot rollback on root state"); + mem::swap(&mut child, self); + + // Rollback storage. + self.store = child + .store + .take() + .map(|cstore| Self::convert_store(cstore.rollback())); + + // Always put back per-block values. + if let Some(mut block_values) = child.hidden_block_values { + mem::swap(&mut block_values, &mut self.block_values); // Block values were hidden. + } else { + mem::swap(&mut child.block_values, &mut self.block_values); + } + // Always drop local values. + + // Always put back RNG. + if child.hidden_rng.is_some() { + mem::swap(&mut child.hidden_rng, &mut self.rng); // RNG was hidden. + } else { + mem::swap(&mut child.rng, &mut self.rng); + } + } + + /// Fetches a block state value entry. + /// + /// Block values live as long as the root `State` and are propagated to child states. They are + /// not affected by state rollbacks. If you need state-scoped values, use local values. + pub fn block_value(&mut self, key: &'static str) -> StateValue<'_, V> { + StateValue::new(self.block_values.entry(key)) + } + + /// Fetches a local state value entry. + /// + /// Local values only live as long as the current `State`, are dropped upon exiting to parent + /// state and child states start with an empty set. If you need longer-lived values, use block + /// values. + pub fn local_value(&mut self, key: &'static str) -> StateValue<'_, V> { + StateValue::new(self.local_values.entry(key)) + } + + /// Hides block values from the current state which will have an empty set of values after this + /// method returns. Hidden values will be restored upon exit to parent state. + pub fn hide_block_values(&mut self) { + if self.parent.is_none() { + // Allowing hiding on root state would prevent those values from ever being recovered. + panic!("cannot hide block values on root state"); + } + if self.hidden_block_values.is_some() { + return; // Parent block values already hidden. + } + + self.hidden_block_values = Some(mem::take(&mut self.block_values)); + } + + /// Emitted messages count returns the number of messages emitted so far. + pub fn emitted_messages_count(&self) -> usize { + self.messages.len() + + self + .parent + .as_ref() + .map(|p| p.emitted_messages_count()) + .unwrap_or_default() + } + + /// Maximum number of messages that can be emitted. + pub fn emitted_messages_max(&self, ctx: &C) -> u32 { + if self.env.is_transaction() { + self.env.tx_auth_info().fee.consensus_messages + } else { + ctx.max_messages() + } + } + + /// Queue a message to be emitted by the runtime for consensus layer to process. + pub fn emit_message( + &mut self, + ctx: &C, + msg: roothash::Message, + hook: MessageEventHookInvocation, + ) -> Result<(), Error> { + // Check against maximum number of messages that can be emitted per round. + if self.emitted_messages_count() >= self.emitted_messages_max(ctx) as usize { + return Err(Error::OutOfMessageSlots); + } + + self.messages.push((msg, hook)); + + Ok(()) + } + + /// Take all messages accumulated in the current state. + pub fn take_messages(&mut self) -> Vec<(roothash::Message, MessageEventHookInvocation)> { + mem::take(&mut self.messages) + } + + /// Emit an event. + pub fn emit_event(&mut self, event: E) { + self.emit_event_raw(event.into_event_tag()); + } + + /// Emit a raw event. + pub fn emit_event_raw(&mut self, etag: EventTag) { + let events = self.events.entry(etag.key).or_default(); + events.push(etag.value); + } + + /// Emit an unconditional event. + /// + /// The only difference to regular events is that these are handled as a separate set. + pub fn emit_unconditional_event(&mut self, event: E) { + let etag = event.into_event_tag(); + let events = self.unconditional_events.entry(etag.key).or_default(); + events.push(etag.value); + } + + /// Take all regular events accumulated in the current state. + pub fn take_events(&mut self) -> EventTags { + mem::take(&mut self.events) + } + + /// Take all unconditional events accumulated in the current state. + pub fn take_unconditional_events(&mut self) -> EventTags { + mem::take(&mut self.unconditional_events) + } + + /// Take all events accumulated in the current state and return the merged set. + pub fn take_all_events(&mut self) -> EventTags { + let mut events = self.take_events(); + let unconditional_events = self.take_unconditional_events(); + + for (key, val) in unconditional_events { + let tag = events.entry(key).or_default(); + tag.extend(val) + } + + events + } + + /// Store associated with the state. + /// + /// # Panics + /// + /// This method will panic if no store exists. + pub fn store(&mut self) -> &mut dyn Store { + self.store.as_mut().unwrap() + } + + /// Whether the store associated with the state has any pending updates. + pub fn has_pending_store_updates(&self) -> bool { + self.store + .as_ref() + .map(|store| store.has_pending_updates()) + .unwrap_or_default() + } + + /// Random number generator. + pub fn rng(&mut self) -> &mut RootRng { + self.rng.as_mut().unwrap() + } + + /// Disables the RNG by replacing the instance with an invalid RNG. + fn disable_rng(&mut self) { + if self.parent.is_none() { + // Allowing hiding on root state would prevent the RNG from ever being recovered. + panic!("cannot hide the RNG on root state"); + } + if self.hidden_rng.is_some() { + return; // Parent RNG already hidden. + } + + self.hidden_rng = mem::replace(&mut self.rng, Some(RootRng::invalid())); + } + + /// Environment information. + pub fn env(&self) -> &Environment { + &self.env + } + + /// Returns the nesting level of the current state. + pub fn level(&self) -> usize { + if let Some(ref parent) = self.parent { + parent.level() + 1 + } else { + 0 + } + } +} + +thread_local! { + static CURRENT: RefCell> = RefCell::new(Vec::new()); +} + +struct CurrentStateGuard; + +impl Drop for CurrentStateGuard { + fn drop(&mut self) { + CURRENT.with(|c| { + let root = c.borrow_mut().pop().expect("must have current state"); + // Commit root state as it has been wrapped in an overlay. + let store = root + .store + .expect("must not have open child states after exiting root state"); + store.commit(); + }); + } +} + +struct TransactionGuard(usize); + +impl Drop for TransactionGuard { + fn drop(&mut self) { + let level = CurrentState::with(|state| state.level()); + + // If transaction hasn't been either committed or reverted, rollback. + if level == self.0 { + CurrentState::rollback_transaction(); + } + } +} + +/// Result of a transaction helper closure. +pub enum TransactionResult { + Commit(T), + Rollback(T), +} + +impl From<()> for TransactionResult<()> { + fn from(_: ()) -> TransactionResult<()> { + TransactionResult::Commit(()) + } +} + +impl From> for TransactionResult> { + fn from(v: Result) -> TransactionResult> { + match v { + Ok(_) => TransactionResult::Commit(v), + Err(_) => TransactionResult::Rollback(v), + } + } +} + +/// State attached to the current thread. +pub struct CurrentState; + +impl CurrentState { + /// Attach a new state to the current thread and enter the state's context. + /// + /// The passed store is used as the root store. + /// + /// # Panics + /// + /// This method will panic if called from within a `CurrentState::with` block. + pub fn enter(root: S, f: F) -> R + where + S: Store, + F: FnOnce() -> R, + { + Self::enter_opts( + Options { + mode: Some(Default::default()), // Make sure there is a default mode. + ..Options::default() + }, + root, + f, + ) + } + + /// Attach a new state to the current thread and enter the state's context. + /// + /// The passed store is used as the root store. + /// + /// # Panics + /// + /// This method will panic if called from within a `CurrentState::with` block or if the mode + /// has not been explicitly set in `opts`. + pub fn enter_opts(opts: Options, mut root: S, f: F) -> R + where + S: Store, + F: FnOnce() -> R, + { + let root = unsafe { + // SAFETY: Keeping the root store is safe as it can only be accessed from the current + // thread while we are running inside `CurrentState::enter` where we are holding a + // mutable reference on it. + std::mem::transmute::<_, &mut (dyn Store + 'static)>(&mut root as &mut dyn Store) + }; + // Initialize the root state. + let mode = opts + .mode + .expect("mode must be explicitly set on root state"); + let mut root = State { + parent: None, + store: Some(OverlayStore::new(Box::new(root) as Box)), + events: EventTags::new(), + unconditional_events: EventTags::new(), + messages: Vec::new(), + block_values: BTreeMap::new(), + hidden_block_values: None, + local_values: BTreeMap::new(), + rng: Some(RootRng::new(mode)), + hidden_rng: None, + env: Default::default(), + always_rollback: false, + }; + // Apply options to allow customization of the root state. + root.init(opts); + + CURRENT.with(|c| { + c.try_borrow_mut() + .expect("must not re-enter from with block") + .push(root) + }); + let _guard = CurrentStateGuard; // Ensure current state is popped once we return. + + f() + } + + /// Create an empty baseline state for the current thread. + /// + /// This should only be used in tests to have state always available. + /// + /// # Panics + /// + /// This method will panic if any states have been attached to the local thread or if called + /// within a `CurrentState::with` block. + #[doc(hidden)] + pub(crate) fn init_local_fallback() { + thread_local! { + static BASE_STATE_INIT: RefCell = RefCell::new(false); + } + + BASE_STATE_INIT.with(|initialized| { + // Initialize once per thread. + if *initialized.borrow() { + return; + } + *initialized.borrow_mut() = true; + + let root = mkvs::OverlayTree::new( + mkvs::Tree::builder() + .with_root_type(mkvs::RootType::State) + .build(Box::new(mkvs::sync::NoopReadSyncer)), + ); + let root = MKVSStore::new(root); + + // Initialize the root state. + let root = State { + parent: None, + store: Some(OverlayStore::new(Box::new(root) as Box)), + events: EventTags::new(), + unconditional_events: EventTags::new(), + messages: Vec::new(), + block_values: BTreeMap::new(), + hidden_block_values: None, + local_values: BTreeMap::new(), + rng: Some(RootRng::new(Default::default())), + hidden_rng: None, + env: Default::default(), + always_rollback: false, + }; + + CURRENT.with(|c| { + let mut current = c + .try_borrow_mut() + .expect("must not re-enter from with block"); + assert!( + current.is_empty(), + "must have no prior states attached to local thread" + ); + + current.push(root); + }); + }); + } + + /// Run a closure with the currently active state. + /// + /// # Panics + /// + /// This method will panic if called outside `CurrentState::enter` or if any transaction methods + /// are called from the closure. + pub fn with(f: F) -> R + where + F: FnOnce(&mut State) -> R, + { + CURRENT.with(|c| { + let mut current_ref = c.try_borrow_mut().expect("must not re-enter with"); + let current = current_ref.last_mut().expect("must enter context"); + + f(current) + }) + } + + /// Run a closure with the store of the currently active state. + /// + /// # Panics + /// + /// This method will panic if called outside `CurrentState::enter` or if any transaction methods + /// are called from the closure. + pub fn with_store(f: F) -> R + where + F: FnOnce(&mut dyn Store) -> R, + { + Self::with(|state| f(state.store())) + } + + /// Run a closure with the environment of the currently active state. + /// + /// # Panics + /// + /// This method will panic if called outside `CurrentState::enter` or if any transaction methods + /// are called from the closure. + pub fn with_env(f: F) -> R + where + F: FnOnce(&Environment) -> R, + { + Self::with(|state| f(state.env())) + } + + /// Start a new transaction by opening a new child state. + /// + /// # Panics + /// + /// This method will panic if called outside `CurrentState::enter` or if called within a + /// `CurrentState::with` block. + pub fn start_transaction() { + Self::with(|state| state.open()); + } + + /// Commit a previously started transaction. + /// + /// # Panics + /// + /// This method will panic if called outside `CurrentState::enter`, if there is no currently + /// open transaction (started via `CurrentState::start_transaction`) or if called within a + /// `CurrentState::with` block. + pub fn commit_transaction() { + Self::with(|state| state.commit()); + } + + /// Rollback a previously started transaction. + /// + /// # Panics + /// + /// This method will panic if called outside `CurrentState::enter`, if there is no currently + /// open transaction (started via `CurrentState::start_transaction`) or if called within a + /// `CurrentState::with` block. + pub fn rollback_transaction() { + Self::with(|state| state.rollback()); + } + + /// Run a closure within a state transaction. + /// + /// If the closure returns `TransactionResult::Commit(R)` then the child state is committed, + /// otherwise the child state is rolled back. + pub fn with_transaction(f: F) -> R + where + F: FnOnce() -> Rs, + Rs: Into>, + { + Self::with_transaction_opts(Options::default(), f) + } + + /// Run a closure within a state transaction, allowing the caller to customize state. + /// + /// If the closure returns `TransactionResult::Commit(R)` then the child state is committed, + /// otherwise the child state is rolled back. + pub fn with_transaction_opts(opts: Options, f: F) -> R + where + F: FnOnce() -> Rs, + Rs: Into>, + { + let level = Self::with(|state| { + state.open(); + state.init(opts); + state.level() + }); + let _guard = TransactionGuard(level); // Ensure transaction is always closed. + + match f().into() { + TransactionResult::Commit(result) => { + Self::commit_transaction(); + result + } + TransactionResult::Rollback(result) => { + Self::rollback_transaction(); + result + } + } + } +} + +/// A per-state arbitrary value. +pub struct StateValue<'a, V> { + inner: Entry<'a, &'static str, Box>, + _value: PhantomData, +} + +impl<'a, V: Any> StateValue<'a, V> { + fn new(inner: Entry<'a, &'static str, Box>) -> Self { + Self { + inner, + _value: PhantomData, + } + } + + /// Gets a reference to the specified per-state value. + /// + /// # Panics + /// + /// Panics if the retrieved type is not the type that was stored. + pub fn get(self) -> Option<&'a V> { + match self.inner { + Entry::Occupied(oe) => Some( + oe.into_mut() + .downcast_ref() + .expect("type should stay the same"), + ), + _ => None, + } + } + + /// Gets a mutable reference to the specified per-state value. + /// + /// # Panics + /// + /// Panics if the retrieved type is not the type that was stored. + pub fn get_mut(&mut self) -> Option<&mut V> { + match &mut self.inner { + Entry::Occupied(oe) => Some( + oe.get_mut() + .downcast_mut() + .expect("type should stay the same"), + ), + _ => None, + } + } + + /// Sets the context value, returning a mutable reference to the set value. + /// + /// # Panics + /// + /// Panics if the retrieved type is not the type that was stored. + pub fn set(self, value: V) -> &'a mut V { + let value = Box::new(value); + match self.inner { + Entry::Occupied(mut oe) => { + oe.insert(value); + oe.into_mut() + } + Entry::Vacant(ve) => ve.insert(value), + } + .downcast_mut() + .expect("type should stay the same") + } + + /// Takes the context value, if it exists. + /// + /// # Panics + /// + /// Panics if the retrieved type is not the type that was stored. + pub fn take(self) -> Option { + match self.inner { + Entry::Occupied(oe) => { + Some(*oe.remove().downcast().expect("type should stay the same")) + } + Entry::Vacant(_) => None, + } + } +} + +impl<'a, V: Any + Default> StateValue<'a, V> { + /// Retrieves the existing value or inserts and returns the default. + /// + /// # Panics + /// + /// Panics if the retrieved type is not the type that was stored. + pub fn or_default(self) -> &'a mut V { + match self.inner { + Entry::Occupied(oe) => oe.into_mut(), + Entry::Vacant(ve) => ve.insert(Box::::default()), + } + .downcast_mut() + .expect("type should stay the same") + } +} + +#[cfg(test)] +mod test { + use oasis_core_runtime::{ + common::versioned::Versioned, + consensus::{roothash, staking}, + storage::mkvs, + }; + + use super::{CurrentState, Mode, Options, TransactionResult, TransactionWithMeta}; + use crate::{ + modules::core::Event, + storage::{MKVSStore, Store}, + testing::mock::{self, Mock}, + types::message::MessageEventHookInvocation, + }; + + #[test] + fn test_value() { + CurrentState::init_local_fallback(); + + CurrentState::with(|state| { + let x = state.block_value::("module.TestKey").get(); + assert_eq!(x, None); + + state.block_value::("module.TestKey").set(42); + + let y = state.block_value::("module.TestKey").get(); + assert_eq!(y, Some(&42u64)); + + let z = state.block_value::("module.TestKey").take(); + assert_eq!(z, Some(42u64)); + + let y = state.block_value::("module.TestKey").get(); + assert_eq!(y, None); + }); + } + + #[test] + #[should_panic] + fn test_value_type_change_block_value() { + CurrentState::init_local_fallback(); + + CurrentState::with(|state| { + state.block_value::("module.TestKey").or_default(); + state.block_value::("module.TestKey").get(); + }); + } + + #[test] + #[should_panic] + fn test_value_type_change_local_value() { + CurrentState::init_local_fallback(); + + CurrentState::with(|state| { + state.local_value::("module.TestKey").or_default(); + state.local_value::("module.TestKey").get(); + }); + } + + #[test] + fn test_value_hidden_block_values() { + CurrentState::init_local_fallback(); + + CurrentState::with(|state| { + state.block_value("module.TestKey").set(42u64); + + state.open(); + state.hide_block_values(); + + let v = state.block_value::("module.TestKey").get(); + assert!(v.is_none(), "block values should not propagate when hidden"); + + state.block_value("module.TestKey").set(48u64); + + state.commit(); + + let v = state.block_value::("module.TestKey").get(); + assert_eq!( + v, + Some(&42u64), + "block values should not propagate when hidden" + ); + }); + } + + #[test] + fn test_value_local() { + CurrentState::init_local_fallback(); + + CurrentState::with(|state| { + state.block_value("module.TestKey").set(42u64); + + state.open(); + + let mut y = state.block_value::("module.TestKey"); + let y = y.get_mut().unwrap(); + assert_eq!(*y, 42); + *y = 48; + + let a = state.local_value::("module.TestTxKey").get(); + assert_eq!(a, None); + state.local_value::("module.TestTxKey").set(65); + + let b = state.local_value::("module.TestTxKey").get(); + assert_eq!(b, Some(&65)); + + let c = state + .local_value::("module.TestTakeTxKey") + .or_default(); + *c = 67; + let d = state.local_value::("module.TestTakeTxKey").take(); + assert_eq!(d, Some(67)); + let e = state.local_value::("module.TestTakeTxKey").get(); + assert_eq!(e, None); + + state.rollback(); // Block values are always propagated. + + let x = state.block_value::("module.TestKey").get(); + assert_eq!(x, Some(&48)); + + state.open(); + + let z = state.block_value::("module.TestKey").take(); + assert_eq!(z, Some(48)); + + let a = state.local_value::("module.TestTxKey").get(); + assert_eq!(a, None, "local values should not be propagated"); + + state.rollback(); // Block values are always propagated. + + let y = state.block_value::("module.TestKey").get(); + assert_eq!(y, None); + }); + } + + #[test] + fn test_emit_messages() { + let mut mock = Mock::default(); // Also creates local fallback state. + let max_messages = mock.max_messages as usize; + let ctx = mock.create_ctx(); + + CurrentState::with(|state| { + state.open(); + + state + .emit_message( + &ctx, + roothash::Message::Staking(Versioned::new( + 0, + roothash::StakingMessage::Transfer(staking::Transfer::default()), + )), + MessageEventHookInvocation::new("test".to_string(), ""), + ) + .expect("message emission should succeed"); + assert_eq!(state.emitted_messages_count(), 1); + assert_eq!(state.emitted_messages_max(&ctx), max_messages as u32); + + state.open(); // Start child state. + + state + .emit_message( + &ctx, + roothash::Message::Staking(Versioned::new( + 0, + roothash::StakingMessage::Transfer(staking::Transfer::default()), + )), + MessageEventHookInvocation::new("test".to_string(), ""), + ) + .expect("message emission should succeed"); + assert_eq!(state.emitted_messages_count(), 2); + assert_eq!(state.emitted_messages_max(&ctx), max_messages as u32); + + state.rollback(); // Rollback. + + assert_eq!( + state.emitted_messages_count(), + 1, + "emitted message should have been rolled back" + ); + + state.open(); // Start child state. + + state + .emit_message( + &ctx, + roothash::Message::Staking(Versioned::new( + 0, + roothash::StakingMessage::Transfer(staking::Transfer::default()), + )), + MessageEventHookInvocation::new("test".to_string(), ""), + ) + .expect("message emission should succeed"); + assert_eq!(state.emitted_messages_count(), 2); + + state.commit(); // Commit. + + assert_eq!( + state.emitted_messages_count(), + 2, + "emitted message should have been committed" + ); + + // Emit some more messages. + for _ in 0..max_messages - 2 { + state + .emit_message( + &ctx, + roothash::Message::Staking(Versioned::new( + 0, + roothash::StakingMessage::Transfer(staking::Transfer::default()), + )), + MessageEventHookInvocation::new("test".to_string(), ""), + ) + .expect("message emission should succeed"); + } + assert_eq!(state.emitted_messages_count(), max_messages); + + // Emitting one more message should be rejected. + state + .emit_message( + &ctx, + roothash::Message::Staking(Versioned::new( + 0, + roothash::StakingMessage::Transfer(staking::Transfer::default()), + )), + MessageEventHookInvocation::new("test".to_string(), ""), + ) + .expect_err("message emission should fail due to out of slots"); + assert_eq!(state.emitted_messages_count(), max_messages); + + state.rollback(); // Rollback. + + assert_eq!(state.emitted_messages_count(), 0); + }); + + // Change the maximum amount of messages. + let mut tx = mock::transaction(); + tx.auth_info.fee.consensus_messages = 1; // Limit amount of messages. + CurrentState::with_transaction_opts(Options::new().with_tx(tx.into()), || { + CurrentState::with(|state| { + assert_eq!(state.emitted_messages_max(&ctx), 1); + }); + }); + } + + #[test] + fn test_emit_events() { + CurrentState::init_local_fallback(); + + CurrentState::with(|state| { + state.open(); + + state.open(); + + state.emit_event(Event::GasUsed { amount: 41 }); + state.emit_event(Event::GasUsed { amount: 42 }); + state.emit_event(Event::GasUsed { amount: 43 }); + + state.emit_unconditional_event(Event::GasUsed { amount: 10 }); + + state.commit(); + + let events = state.take_events(); + assert_eq!(events.len(), 1, "events should have been propagated"); + let event_key = b"core\x00\x00\x00\x01".to_vec(); + assert_eq!(events[&event_key].len(), 3); + + let events = state.take_unconditional_events(); + assert_eq!( + events.len(), + 1, + "unconditional events should have been propagated" + ); + let event_key = b"core\x00\x00\x00\x01".to_vec(); + assert_eq!(events[&event_key].len(), 1); + + state.emit_event(Event::GasUsed { amount: 41 }); + state.emit_event(Event::GasUsed { amount: 42 }); + state.emit_event(Event::GasUsed { amount: 43 }); + + state.emit_unconditional_event(Event::GasUsed { amount: 20 }); + + state.rollback(); + + let events = state.take_events(); + assert_eq!(events.len(), 0, "events should not have been propagated"); + + let events = state.take_unconditional_events(); + assert_eq!( + events.len(), + 0, + "unconditional events should not have been propagated" + ); + }); + } + + fn test_store_basic() { + CurrentState::start_transaction(); + + assert!( + !CurrentState::with(|state| state.has_pending_store_updates()), + "should not have pending updates" + ); + + CurrentState::with_store(|store| { + store.insert(b"test", b"value"); + }); + + assert!( + CurrentState::with(|state| state.has_pending_store_updates()), + "should have pending updates after insert" + ); + + // Transaction helper. + CurrentState::with_transaction(|| { + assert!( + !CurrentState::with(|state| state.has_pending_store_updates()), + "should not have pending updates" + ); + + CurrentState::with_store(|store| { + store.insert(b"test", b"b0rken"); + }); + + assert!( + CurrentState::with(|state| state.has_pending_store_updates()), + "should have pending updates after insert" + ); + + TransactionResult::Rollback(()) + }); + + // Transaction helper with options. + CurrentState::with_transaction_opts( + Options::new() + .with_mode(Mode::Check) + .with_internal(true) + .with_tx(TransactionWithMeta { + data: mock::transaction(), + size: 888, + index: 42, + hash: Default::default(), + }), + || { + CurrentState::with_env(|env| { + assert!(env.is_check_only(), "environment should be updated"); + assert!(env.is_internal(), "environment should be updated"); + assert!(env.is_transaction(), "environment should be updated"); + assert_eq!(env.tx_index(), 42, "environment should be updated"); + assert_eq!(env.tx_size(), 888, "environment should be updated"); + }); + + CurrentState::with_transaction(|| { + // Check environment propagation. + CurrentState::with_env(|env| { + assert!(env.is_check_only(), "environment should propagate"); + assert!(env.is_internal(), "environment should propagate"); + assert!(env.is_transaction(), "environment should propagate"); + assert_eq!(env.tx_index(), 42, "environment should propagate"); + assert_eq!(env.tx_size(), 888, "environment should propagate"); + }); + + TransactionResult::Rollback(()) + }); + + TransactionResult::Rollback(()) + }, + ); + + CurrentState::with_env(|env| { + assert!(!env.is_transaction(), "environment should not leak"); + }); + + // Nested entering, but with a different store. + let unrelated = mkvs::OverlayTree::new( + mkvs::Tree::builder() + .with_root_type(mkvs::RootType::State) + .build(Box::new(mkvs::sync::NoopReadSyncer)), + ); + let mut unrelated = MKVSStore::new(unrelated); + + CurrentState::enter(&mut unrelated, || { + CurrentState::start_transaction(); + + CurrentState::with_store(|store| { + store.insert(b"test", b"should not touch the original root"); + }); + + CurrentState::commit_transaction(); + }); + + CurrentState::with_store(|store| { + store.insert(b"another", b"value 2"); + }); + + CurrentState::commit_transaction(); + } + + #[test] + fn test_basic() { + let root = mkvs::OverlayTree::new( + mkvs::Tree::builder() + .with_root_type(mkvs::RootType::State) + .build(Box::new(mkvs::sync::NoopReadSyncer)), + ); + let mut root = MKVSStore::new(root); + + CurrentState::enter(&mut root, || { + test_store_basic(); + }); + + let value = root.get(b"test").unwrap(); + assert_eq!(value, b"value"); + } + + #[test] + fn test_local_fallback() { + // Initialize the local fallback store. + CurrentState::init_local_fallback(); + CurrentState::init_local_fallback(); // Should be no-op. + + // Test the basic store -- note, no need to enter as fallback current store is available. + test_store_basic(); + + CurrentState::with_store(|store| { + let value = store.get(b"test").unwrap(); + assert_eq!(value, b"value"); + }); + + // It should be possible to override the fallback by entering explicitly. + let root = mkvs::OverlayTree::new( + mkvs::Tree::builder() + .with_root_type(mkvs::RootType::State) + .build(Box::new(mkvs::sync::NoopReadSyncer)), + ); + let mut root = MKVSStore::new(root); + + CurrentState::enter(&mut root, || { + CurrentState::with_store(|store| { + assert!(store.get(b"test").is_none(), "store should be empty"); + store.insert(b"unrelated", b"unrelated"); + }); + + test_store_basic(); + }); + + let value = root.get(b"test").unwrap(); + assert_eq!(value, b"value"); + let value = root.get(b"unrelated").unwrap(); + assert_eq!(value, b"unrelated"); + + // Changes should not leak to fallback store. + CurrentState::with_store(|store| { + assert!(store.get(b"unrelated").is_none(), "changes should not leak"); + }); + } + + #[test] + #[should_panic(expected = "must enter context")] + fn test_fail_not_entered() { + test_store_basic(); // Should panic due to no current store being available. + } + + #[test] + #[should_panic(expected = "must not re-enter with")] + fn test_fail_reenter_with() { + CurrentState::init_local_fallback(); + + CurrentState::with(|_| { + CurrentState::with(|_| { + // Should panic. + }); + }); + } + + #[test] + #[should_panic(expected = "must not re-enter with")] + fn test_fail_reenter_with_start_transaction() { + CurrentState::init_local_fallback(); + + CurrentState::with(|_| { + CurrentState::start_transaction(); // Should panic. + }); + } + + #[test] + #[should_panic(expected = "must not re-enter with")] + fn test_fail_reenter_with_commit_transaction() { + CurrentState::init_local_fallback(); + + CurrentState::with(|_| { + CurrentState::commit_transaction(); // Should panic. + }); + } + + #[test] + #[should_panic(expected = "must not re-enter with")] + fn test_fail_reenter_with_rollback_transaction() { + CurrentState::init_local_fallback(); + + CurrentState::with(|_| { + CurrentState::rollback_transaction(); // Should panic. + }); + } + + #[test] + #[should_panic(expected = "must not re-enter from with block")] + fn test_fail_reenter_with_enter() { + CurrentState::init_local_fallback(); + + CurrentState::with(|_| { + let unrelated = mkvs::OverlayTree::new( + mkvs::Tree::builder() + .with_root_type(mkvs::RootType::State) + .build(Box::new(mkvs::sync::NoopReadSyncer)), + ); + let mut unrelated = MKVSStore::new(unrelated); + + CurrentState::enter(&mut unrelated, || { + // Should panic. + }); + }); + } + + #[test] + #[should_panic(expected = "must not re-enter from with block")] + fn test_fail_local_fallback_within_with() { + let root = mkvs::OverlayTree::new( + mkvs::Tree::builder() + .with_root_type(mkvs::RootType::State) + .build(Box::new(mkvs::sync::NoopReadSyncer)), + ); + let mut root = MKVSStore::new(root); + + CurrentState::enter(&mut root, || { + CurrentState::with(|_| { + CurrentState::init_local_fallback(); // Should panic. + }) + }); + } + + #[test] + #[should_panic(expected = "must have no prior states attached to local thread")] + fn test_fail_local_fallback_within_enter() { + let root = mkvs::OverlayTree::new( + mkvs::Tree::builder() + .with_root_type(mkvs::RootType::State) + .build(Box::new(mkvs::sync::NoopReadSyncer)), + ); + let mut root = MKVSStore::new(root); + + CurrentState::enter(&mut root, || { + CurrentState::init_local_fallback(); // Should panic. + }); + } + + #[test] + #[should_panic(expected = "cannot commit on root state")] + fn test_fail_commit_transaction_must_exist() { + CurrentState::init_local_fallback(); + + CurrentState::commit_transaction(); // Should panic. + } + + #[test] + #[should_panic(expected = "cannot rollback on root state")] + fn test_fail_rollback_transaction_must_exist() { + CurrentState::init_local_fallback(); + + CurrentState::rollback_transaction(); // Should panic. + } +} diff --git a/runtime-sdk/src/storage/current.rs b/runtime-sdk/src/storage/current.rs deleted file mode 100644 index 4857289d73..0000000000 --- a/runtime-sdk/src/storage/current.rs +++ /dev/null @@ -1,512 +0,0 @@ -//! A store attached to the current thread. -use std::cell::RefCell; - -use oasis_core_runtime::storage::mkvs; - -use crate::storage::{MKVSStore, NestedStore, OverlayStore, Store}; - -thread_local! { - static CURRENT: RefCell> = RefCell::new(Vec::new()); -} - -struct CurrentStoreGuard; - -impl Drop for CurrentStoreGuard { - fn drop(&mut self) { - CURRENT.with(|c| c.borrow_mut().pop()); - } -} - -struct TransactionGuard(usize); - -impl Drop for TransactionGuard { - fn drop(&mut self) { - let level = CURRENT.with(|c| { - let mut current_ref = c.borrow_mut(); - let current = current_ref.last_mut().expect("must enter context"); - current.transactions.len() - }); - - // If transaction hasn't been either committed or reverted, rollback. - if level == self.0 { - CurrentStore::rollback_transaction(); - } - } -} - -/// Result of a transaction helper closure. -pub enum TransactionResult { - Commit(T), - Rollback(T), -} - -/// A store attached to the current thread. -pub struct CurrentStore { - store: *mut dyn Store, - #[allow(clippy::vec_box)] // Must be boxed to survive the vector extending, moving elements. - transactions: Vec>>, -} - -impl CurrentStore { - /// Attach a new store to the current thread and enter the store's context. - /// - /// The passed store is used as the root store. - pub fn enter(mut root: S, f: F) -> R - where - S: Store, - F: FnOnce() -> R, - { - // Initialize the root store. - let current = CurrentStore { - store: unsafe { - // Keeping the root store is safe as it can only be accessed from the current thread - // while we are running inside `CurrentStore::enter` where we are holding a mutable - // reference on it. - std::mem::transmute::<_, *mut (dyn Store + 'static)>(&mut root as &mut dyn Store) - }, - transactions: vec![], - }; - - CURRENT.with(|c| { - c.try_borrow_mut() - .expect("must not re-enter from with block") - .push(current) - }); - let _guard = CurrentStoreGuard; // Ensure current store is popped once we return. - - f() - } - - /// Create an empty baseline store for the current thread. - /// - /// This should only be used in tests to have a store always available. - /// - /// # Panics - /// - /// This method will panic if any stores have been attached to the local thread or if called - /// within a `CurrentStore::with` block. - #[doc(hidden)] - pub(crate) fn init_local_fallback() { - thread_local! { - static BASE_STORE: RefCell>> = { - let root = mkvs::OverlayTree::new( - mkvs::Tree::builder() - .with_root_type(mkvs::RootType::State) - .build(Box::new(mkvs::sync::NoopReadSyncer)), - ); - let root = MKVSStore::new(root); - - RefCell::new(root) - }; - - static BASE_STORE_INIT: RefCell = RefCell::new(false); - } - - BASE_STORE_INIT.with(|initialized| { - // Initialize once per thread. - if *initialized.borrow() { - return; - } - *initialized.borrow_mut() = true; - - let store = BASE_STORE.with(|bs| bs.as_ptr()); - let base = CurrentStore { - store: store as *mut dyn Store, - transactions: vec![], - }; - - CURRENT.with(|c| { - let mut current = c - .try_borrow_mut() - .expect("must not re-enter from with block"); - assert!( - current.is_empty(), - "must have no prior stores attached to local thread" - ); - - current.push(base); - }); - }); - } - - /// Start a new transaction by overlaying a store over the current store. - /// - /// # Panics - /// - /// This method will panic if called outside `CurrentStore::enter` or if called within a - /// `CurrentStore::with` block. - pub fn start_transaction() -> usize { - CURRENT.with(|c| { - let mut current_ref = c - .try_borrow_mut() - .expect("must not re-enter from with block"); - let current = current_ref.last_mut().expect("must enter context"); - // Dereferencing the store is safe because we ensure it always points to a valid store - // while we are inside the storage context. - let store = unsafe { &mut *current.store }; - - // Create a new overlay for the transaction and replace the active store. - let overlay = Box::new(OverlayStore::new(store)); - // Ensure the overlay is not dropped prematurely. - current.transactions.push(overlay); - current.store = &mut **current.transactions.last_mut().unwrap(); - - current.transactions.len() - }) - } - - /// Commit a previously started transaction. - /// - /// # Panics - /// - /// This method will panic if called outside `CurrentStore::enter`, if there is no currently - /// open transaction (started via `CurrentStore::start_transaction`) or if called within a - /// `CurrentStore::with` block. - pub fn commit_transaction() { - CURRENT.with(|c| { - let mut current_ref = c - .try_borrow_mut() - .expect("must not re-enter from with block"); - let current = current_ref.last_mut().expect("must enter context"); - - let store = current - .transactions - .pop() - .expect("transaction must have been opened"); - current.store = store.commit(); - }); - } - - /// Rollback a previously started transaction. - /// - /// # Panics - /// - /// This method will panic if called outside `CurrentStore::enter`, if there is no currently - /// open transaction (started via `CurrentStore::start_transaction`) or if called within a - /// `CurrentStore::with` block. - pub fn rollback_transaction() { - CURRENT.with(|c| { - let mut current_ref = c - .try_borrow_mut() - .expect("must not re-enter from with block"); - let current = current_ref.last_mut().expect("must enter context"); - - let store = current - .transactions - .pop() - .expect("transaction must have been opened"); - current.store = store.rollback(); - }); - } - - /// Whether there are any store updates pending to be committed in the current transaction. - /// - /// If there is no current transaction, the method returns `true`. - /// - /// # Panics - /// - /// This method will panic if called outside `CurrentStore::enter` or if called within a - /// `CurrentStore::with` block. - pub fn has_pending_updates() -> bool { - CURRENT.with(|c| { - let mut current_ref = c - .try_borrow_mut() - .expect("must not re-enter from with block"); - let current = current_ref.last_mut().expect("must enter context"); - - current - .transactions - .last() - .map(|store| store.has_pending_updates()) - .unwrap_or(true) // If no transaction is opened, assume modifications are there. - }) - } - - /// Run a closure with the currently active store. - /// - /// # Panics - /// - /// This method will panic if called outside `CurrentStore::enter` or if any transaction methods - /// are called from the closure. - pub fn with(f: F) -> R - where - F: FnOnce(&mut dyn Store) -> R, - { - CURRENT.with(|c| { - let mut current_ref = c.try_borrow_mut().expect("must not re-enter with"); - let current = current_ref.last_mut().expect("must enter context"); - - // Dereferencing the store is safe because we ensure it always points to a valid store - // while we are inside the storage context. - let store = unsafe { &mut *current.store }; - - f(store) - }) - } - - /// Run a closure within a storage transaction. - /// - /// If the closure returns `TransactionResult::Commit(R)` then the transaction is committed, - /// otherwise the transaction is rolled back. - pub fn with_transaction(f: F) -> R - where - F: FnOnce() -> TransactionResult, - { - let level = Self::start_transaction(); - let _guard = TransactionGuard(level); // Ensure transaction is always closed. - - match f() { - TransactionResult::Commit(result) => { - Self::commit_transaction(); - result - } - TransactionResult::Rollback(result) => { - Self::rollback_transaction(); - result - } - } - } -} - -#[cfg(test)] -mod test { - use oasis_core_runtime::storage::mkvs; - - use super::{CurrentStore, TransactionResult}; - use crate::storage::{MKVSStore, Store}; - - fn test_store_basic() { - CurrentStore::start_transaction(); - - assert!( - !CurrentStore::has_pending_updates(), - "should not have pending updates" - ); - - CurrentStore::with(|store| { - store.insert(b"test", b"value"); - }); - - assert!( - CurrentStore::has_pending_updates(), - "should have pending updates after insert" - ); - - // Transaction helper. - CurrentStore::with_transaction(|| { - assert!( - !CurrentStore::has_pending_updates(), - "should not have pending updates" - ); - - CurrentStore::with(|store| { - store.insert(b"test", b"b0rken"); - }); - - assert!( - CurrentStore::has_pending_updates(), - "should have pending updates after insert" - ); - - TransactionResult::Rollback(()) - }); - - // Nested entering, but with a different store. - let unrelated = mkvs::OverlayTree::new( - mkvs::Tree::builder() - .with_root_type(mkvs::RootType::State) - .build(Box::new(mkvs::sync::NoopReadSyncer)), - ); - let mut unrelated = MKVSStore::new(unrelated); - - CurrentStore::enter(&mut unrelated, || { - CurrentStore::start_transaction(); - - CurrentStore::with(|store| { - store.insert(b"test", b"should not touch the original root"); - }); - - CurrentStore::commit_transaction(); - }); - - CurrentStore::with(|store| { - store.insert(b"another", b"value 2"); - }); - - CurrentStore::commit_transaction(); - } - - #[test] - fn test_basic() { - let root = mkvs::OverlayTree::new( - mkvs::Tree::builder() - .with_root_type(mkvs::RootType::State) - .build(Box::new(mkvs::sync::NoopReadSyncer)), - ); - let mut root = MKVSStore::new(root); - - CurrentStore::enter(&mut root, || { - test_store_basic(); - }); - - let value = root.get(b"test").unwrap(); - assert_eq!(value, b"value"); - } - - #[test] - fn test_local_fallback() { - // Initialize the local fallback store. - CurrentStore::init_local_fallback(); - CurrentStore::init_local_fallback(); // Should be no-op. - - // Test the basic store -- note, no need to enter as fallback current store is available. - test_store_basic(); - - CurrentStore::with(|store| { - let value = store.get(b"test").unwrap(); - assert_eq!(value, b"value"); - }); - - // It should be possible to override the fallback by entering explicitly. - let root = mkvs::OverlayTree::new( - mkvs::Tree::builder() - .with_root_type(mkvs::RootType::State) - .build(Box::new(mkvs::sync::NoopReadSyncer)), - ); - let mut root = MKVSStore::new(root); - - CurrentStore::enter(&mut root, || { - CurrentStore::with(|store| { - assert!(store.get(b"test").is_none(), "store should be empty"); - store.insert(b"unrelated", b"unrelated"); - }); - - test_store_basic(); - }); - - let value = root.get(b"test").unwrap(); - assert_eq!(value, b"value"); - let value = root.get(b"unrelated").unwrap(); - assert_eq!(value, b"unrelated"); - - // Changes should not leak to fallback store. - CurrentStore::with(|store| { - assert!(store.get(b"unrelated").is_none(), "changes should not leak"); - }); - } - - #[test] - #[should_panic(expected = "must enter context")] - fn test_fail_not_entered() { - test_store_basic(); // Should panic due to no current store being available. - } - - #[test] - #[should_panic(expected = "must not re-enter with")] - fn test_fail_reenter_with() { - CurrentStore::init_local_fallback(); - - CurrentStore::with(|_| { - CurrentStore::with(|_| { - // Should panic. - }); - }); - } - - #[test] - #[should_panic(expected = "must not re-enter from with block")] - fn test_fail_reenter_with_start_transaction() { - CurrentStore::init_local_fallback(); - - CurrentStore::with(|_| { - CurrentStore::start_transaction(); // Should panic. - }); - } - - #[test] - #[should_panic(expected = "must not re-enter from with block")] - fn test_fail_reenter_with_commit_transaction() { - CurrentStore::init_local_fallback(); - - CurrentStore::with(|_| { - CurrentStore::commit_transaction(); // Should panic. - }); - } - - #[test] - #[should_panic(expected = "must not re-enter from with block")] - fn test_fail_reenter_with_rollback_transaction() { - CurrentStore::init_local_fallback(); - - CurrentStore::with(|_| { - CurrentStore::rollback_transaction(); // Should panic. - }); - } - - #[test] - #[should_panic(expected = "must not re-enter from with block")] - fn test_fail_reenter_with_enter() { - CurrentStore::init_local_fallback(); - - CurrentStore::with(|_| { - let unrelated = mkvs::OverlayTree::new( - mkvs::Tree::builder() - .with_root_type(mkvs::RootType::State) - .build(Box::new(mkvs::sync::NoopReadSyncer)), - ); - let mut unrelated = MKVSStore::new(unrelated); - - CurrentStore::enter(&mut unrelated, || { - // Should panic. - }); - }); - } - - #[test] - #[should_panic(expected = "must not re-enter from with block")] - fn test_fail_local_fallback_within_with() { - let root = mkvs::OverlayTree::new( - mkvs::Tree::builder() - .with_root_type(mkvs::RootType::State) - .build(Box::new(mkvs::sync::NoopReadSyncer)), - ); - let mut root = MKVSStore::new(root); - - CurrentStore::enter(&mut root, || { - CurrentStore::with(|_| { - CurrentStore::init_local_fallback(); // Should panic. - }) - }); - } - - #[test] - #[should_panic(expected = "must have no prior stores attached to local thread")] - fn test_fail_local_fallback_within_enter() { - let root = mkvs::OverlayTree::new( - mkvs::Tree::builder() - .with_root_type(mkvs::RootType::State) - .build(Box::new(mkvs::sync::NoopReadSyncer)), - ); - let mut root = MKVSStore::new(root); - - CurrentStore::enter(&mut root, || { - CurrentStore::init_local_fallback(); // Should panic. - }); - } - - #[test] - #[should_panic(expected = "transaction must have been opened")] - fn test_fail_commit_transaction_must_exist() { - CurrentStore::init_local_fallback(); - - CurrentStore::commit_transaction(); // Should panic. - } - - #[test] - #[should_panic(expected = "transaction must have been opened")] - fn test_fail_rollback_transaction_must_exist() { - CurrentStore::init_local_fallback(); - - CurrentStore::rollback_transaction(); // Should panic. - } -} diff --git a/runtime-sdk/src/storage/mod.rs b/runtime-sdk/src/storage/mod.rs index f03058d987..efc029dc35 100644 --- a/runtime-sdk/src/storage/mod.rs +++ b/runtime-sdk/src/storage/mod.rs @@ -2,7 +2,6 @@ use oasis_core_runtime::storage::mkvs::Iterator; pub mod confidential; -pub mod current; mod hashed; mod mkvs; mod overlay; @@ -89,7 +88,6 @@ impl Store for Box { } pub use confidential::{ConfidentialStore, Error as ConfidentialStoreError}; -pub use current::CurrentStore; pub use hashed::HashedStore; pub use mkvs::MKVSStore; pub use overlay::OverlayStore; diff --git a/runtime-sdk/src/subcall.rs b/runtime-sdk/src/subcall.rs index 3ed6f752ae..51e5def30a 100644 --- a/runtime-sdk/src/subcall.rs +++ b/runtime-sdk/src/subcall.rs @@ -2,12 +2,12 @@ use std::cell::RefCell; use crate::{ - context::{BatchContext, Context, State, TransactionWithMeta, TxContext}, + context::Context, dispatcher, module::CallResult, modules::core::{Error, API as _}, runtime::Runtime, - storage::{current::TransactionResult, CurrentStore}, + state::{CurrentState, Options, TransactionResult, TransactionWithMeta}, types::{token, transaction, transaction::CallerAddress}, }; @@ -49,8 +49,6 @@ pub struct SubcallInfo { /// Result of dispatching a subcall. #[derive(Debug)] pub struct SubcallResult { - /// State after applying the subcall context. - pub state: State, /// Result of the subcall. pub call_result: CallResult, /// Gas used by the subcall. @@ -101,13 +99,13 @@ impl Drop for SubcallStackGuard { } /// The current subcall depth. -pub fn get_current_subcall_depth(_ctx: &mut C) -> u16 { +pub fn get_current_subcall_depth(_ctx: &C) -> u16 { SUBCALL_STACK.with(|ss| ss.borrow().depth()) } /// Perform a subcall. -pub fn call( - ctx: &mut C, +pub fn call( + ctx: &C, info: SubcallInfo, validator: V, ) -> Result { @@ -135,73 +133,63 @@ pub fn call( })?; let _guard = SubcallStackGuard; // Ensure subcall is popped from stack. - // Calculate how many consensus messages the child call can emit. - let remaining_messages = ctx.remaining_messages(); - - // Execute a transaction in a child context. - let (call_result, gas, state) = ctx.with_child(ctx.mode(), |mut ctx| { - // Generate an internal transaction. - let tx = transaction::Transaction { - version: transaction::LATEST_TRANSACTION_VERSION, - call: transaction::Call { - format: transaction::CallFormat::Plain, - method: info.method, - body: info.body, - ..Default::default() - }, - auth_info: transaction::AuthInfo { - signer_info: vec![transaction::SignerInfo { - // The call is being performed on the caller's behalf. - address_spec: transaction::AddressSpec::Internal(info.caller), - nonce: 0, - }], - fee: transaction::Fee { - amount: token::BaseUnits::new(0, token::Denomination::NATIVE), - // Limit gas usage inside the child context to the allocated maximum. - gas: info.max_gas, - consensus_messages: remaining_messages, - }, - ..Default::default() + // Generate an internal transaction. + let tx = transaction::Transaction { + version: transaction::LATEST_TRANSACTION_VERSION, + call: transaction::Call { + format: transaction::CallFormat::Plain, + method: info.method, + body: info.body, + ..Default::default() + }, + auth_info: transaction::AuthInfo { + signer_info: vec![transaction::SignerInfo { + // The call is being performed on the caller's behalf. + address_spec: transaction::AddressSpec::Internal(info.caller), + nonce: 0, + }], + fee: transaction::Fee { + amount: token::BaseUnits::new(0, token::Denomination::NATIVE), + // Limit gas usage inside the child context to the allocated maximum. + gas: info.max_gas, + // Propagate consensus message limit. + consensus_messages: CurrentState::with(|state| state.emitted_messages_max(ctx)), }, - }; - - let result = CurrentStore::with_transaction(|| { - ctx.with_tx(TransactionWithMeta::internal(tx), |ctx, call| { - // Mark this sub-context as internal as it belongs to an existing transaction. - let mut ctx = ctx.internal(); - - // Dispatch the call. - let (result, _) = dispatcher::Dispatcher::::dispatch_tx_call( - &mut ctx, - call, - &Default::default(), - ); - // Retrieve remaining gas. - let gas = ::Core::remaining_tx_gas(&mut ctx); - - // Commit store and return emitted tags and messages on successful dispatch, - // otherwise revert state and ignore any emitted events/messages. - if result.is_success() { - let state = ctx.commit(); - TransactionResult::Commit((result, gas, state)) - } else { - // Ignore tags/messages on failure. - TransactionResult::Rollback((result, gas, Default::default())) - } - }) - }); - - // Commit. Note that if child context didn't commit, this is basically a no-op. - ctx.commit(); + ..Default::default() + }, + }; + let call = tx.call.clone(); // TODO: Avoid clone. - result - }); + // Execute a transaction in a child context. + let (call_result, gas) = CurrentState::with_transaction_opts( + Options::new() + .with_internal(true) + .with_tx(TransactionWithMeta::internal(tx)), + || { + // Dispatch the call. + let (result, _) = dispatcher::Dispatcher::::dispatch_tx_call( + &ctx.clone(), // Must clone to avoid infinite type. + call, + &Default::default(), + ); + // Retrieve remaining gas. + let gas = ::Core::remaining_tx_gas(); + + // Commit store and return emitted tags and messages on successful dispatch, + // otherwise revert state and ignore any emitted events/messages. + if result.is_success() { + TransactionResult::Commit((result, gas)) + } else { + // Ignore tags/messages on failure. + TransactionResult::Rollback((result, gas)) + } + }, + ); // Compute the amount of gas used. let gas_used = info.max_gas.saturating_sub(gas); Ok(SubcallResult { - state, call_result, gas_used, }) diff --git a/runtime-sdk/src/testing/mock.rs b/runtime-sdk/src/testing/mock.rs index 0bc93790f1..6c774d0ca8 100644 --- a/runtime-sdk/src/testing/mock.rs +++ b/runtime-sdk/src/testing/mock.rs @@ -10,8 +10,7 @@ use oasis_core_runtime::{ }; use crate::{ - context::{BatchContext, Mode, RuntimeBatchContext}, - crypto::random::RootRng, + context::{Context, RuntimeBatchContext}, dispatcher, error::RuntimeError, history, @@ -19,7 +18,8 @@ use crate::{ module::MigrationHandler, modules, runtime::Runtime, - storage::{CurrentStore, MKVSStore}, + state::{self, CurrentState, TransactionResult}, + storage::MKVSStore, testing::{configmap, keymanager::MockKeyManagerClient}, types::{address::SignatureAddressSpec, transaction}, }; @@ -67,7 +67,6 @@ pub struct Mock { pub consensus_state: ConsensusState, pub history: Box, pub epoch: beacon::EpochTime, - pub rng: RootRng, pub max_messages: u32, } @@ -75,21 +74,15 @@ pub struct Mock { impl Mock { /// Create a new mock dispatch context. pub fn create_ctx(&mut self) -> RuntimeBatchContext<'_, EmptyRuntime> { - self.create_ctx_for_runtime(Mode::ExecuteTx, false) - } - - pub fn create_check_ctx(&mut self) -> RuntimeBatchContext<'_, EmptyRuntime> { - self.create_ctx_for_runtime(Mode::CheckTx, false) + self.create_ctx_for_runtime(false) } /// Create a new mock dispatch context. pub fn create_ctx_for_runtime( &mut self, - mode: Mode, confidential: bool, ) -> RuntimeBatchContext<'_, R> { RuntimeBatchContext::new( - mode, &self.host_info, if confidential { Some(Box::new(MockKeyManagerClient::new()) as Box) @@ -101,16 +94,15 @@ impl Mock { &self.consensus_state, &self.history, self.epoch, - &self.rng, self.max_messages, ) } /// Create an instance with the given local configuration. pub fn with_local_config(local_config: BTreeMap) -> Self { - // Ensure a current store is always available during tests. Note that one can always use a - // different store by calling CurrentStore::enter explicitly. - CurrentStore::init_local_fallback(); + // Ensure a current state is always available during tests. Note that one can always use a + // different store by calling `CurrentState::enter` explicitly. + CurrentState::init_local_fallback(); let consensus_tree = mkvs::Tree::builder() .with_root_type(mkvs::RootType::State) @@ -129,7 +121,6 @@ impl Mock { consensus_state: ConsensusState::new(1, consensus_tree), history: Box::new(EmptyHistory), epoch: 1, - rng: RootRng::new(), max_messages: 32, } } @@ -217,9 +208,9 @@ impl Signer { } /// Dispatch a call to the given method. - pub fn call(&mut self, ctx: &mut C, method: &str, body: B) -> dispatcher::DispatchResult + pub fn call(&mut self, ctx: &C, method: &str, body: B) -> dispatcher::DispatchResult where - C: BatchContext, + C: Context, B: cbor::Encode, { self.call_opts(ctx, method, body, Default::default()) @@ -228,13 +219,13 @@ impl Signer { /// Dispatch a call to the given method with the given options. pub fn call_opts( &mut self, - ctx: &mut C, + ctx: &C, method: &str, body: B, opts: CallOptions, ) -> dispatcher::DispatchResult where - C: BatchContext, + C: Context, B: cbor::Encode, { let tx = transaction::Transaction { @@ -265,14 +256,24 @@ impl Signer { } /// Dispatch a query to the given method. - pub fn query(&self, ctx: &mut C, method: &str, args: A) -> Result + pub fn query(&self, ctx: &C, method: &str, args: A) -> Result where - C: BatchContext, + C: Context, A: cbor::Encode, R: cbor::Decode, { - let result = - dispatcher::Dispatcher::::dispatch_query(ctx, method, cbor::to_vec(args))?; + let result = CurrentState::with_transaction_opts( + state::Options::new().with_mode(state::Mode::Check), + || { + let result = dispatcher::Dispatcher::::dispatch_query( + ctx, + method, + cbor::to_vec(args), + ); + + TransactionResult::Rollback(result) + }, + )?; Ok(cbor::from_slice(&result).expect("result should decode correctly")) } } diff --git a/runtime-sdk/src/types/transaction.rs b/runtime-sdk/src/types/transaction.rs index f508532144..6810116cf5 100644 --- a/runtime-sdk/src/types/transaction.rs +++ b/runtime-sdk/src/types/transaction.rs @@ -110,22 +110,17 @@ impl Transaction { } /// Format used for encoding the call (and output) information. -#[derive(Clone, Copy, Debug, PartialEq, Eq, cbor::Encode, cbor::Decode)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, cbor::Encode, cbor::Decode)] #[repr(u8)] #[cbor(with_default)] pub enum CallFormat { /// Plain text call data. + #[default] Plain = 0, /// Encrypted call data using X25519 for key exchange and Deoxys-II for symmetric encryption. EncryptedX25519DeoxysII = 1, } -impl Default for CallFormat { - fn default() -> Self { - Self::Plain - } -} - /// Method call. #[derive(Clone, Debug, cbor::Encode, cbor::Decode)] pub struct Call { diff --git a/tests/runtimes/benchmarking/src/runtime/mod.rs b/tests/runtimes/benchmarking/src/runtime/mod.rs index d15f311c37..88e746bdff 100644 --- a/tests/runtimes/benchmarking/src/runtime/mod.rs +++ b/tests/runtimes/benchmarking/src/runtime/mod.rs @@ -4,11 +4,12 @@ use thiserror::Error; use oasis_runtime_sdk::{ self as sdk, - context::{Context, TxContext}, + context::Context, error, module, module::{CallResult, Module as _}, modules, modules::{accounts, core}, + state::CurrentState, storage::Prefix, types::transaction::AuthInfo, }; @@ -58,23 +59,25 @@ pub struct Module { // Impls. impl Module { - fn tx_accounts_mint(ctx: &mut C, body: types::AccountsMint) -> Result<(), Error> { + fn tx_accounts_mint(_ctx: &C, body: types::AccountsMint) -> Result<(), Error> { // XXX: no gas costs atm. - Accounts::mint(ctx, ctx.tx_caller_address(), &body.amount)?; + let caller = CurrentState::with_env(|env| env.tx_caller_address()); + Accounts::mint(caller, &body.amount)?; Ok(()) } } impl Module { - fn tx_accounts_transfer( - ctx: &mut C, + fn tx_accounts_transfer( + _ctx: &C, body: types::AccountsTransfer, ) -> Result<(), Error> { // XXX: no gas costs atm. - Accounts::transfer(ctx, ctx.tx_caller_address(), body.to, &body.amount)?; + let caller = CurrentState::with_env(|env| env.tx_caller_address()); + Accounts::transfer(caller, body.to, &body.amount)?; Ok(()) } @@ -166,8 +169,8 @@ impl module::MethodHandler for Module( - ctx: &mut C, + fn dispatch_call( + ctx: &C, method: &str, body: cbor::Value, ) -> module::DispatchResult { @@ -185,7 +188,7 @@ impl module::MigrationHandler for Module( - _ctx: &mut C, + _ctx: &C, meta: &mut modules::core::types::Metadata, genesis: Self::Genesis, ) -> bool { diff --git a/tests/runtimes/simple-consensus/src/lib.rs b/tests/runtimes/simple-consensus/src/lib.rs index b95b581c0a..ec44eec70d 100644 --- a/tests/runtimes/simple-consensus/src/lib.rs +++ b/tests/runtimes/simple-consensus/src/lib.rs @@ -83,7 +83,7 @@ impl sdk::Runtime for Runtime { ) } - fn migrate_state(_ctx: &mut C) { + fn migrate_state(_ctx: &C) { // Make sure that there are no spurious state migration invocations. panic!("state migration called when it shouldn't be"); } diff --git a/tests/runtimes/simple-keyvalue/src/keyvalue.rs b/tests/runtimes/simple-keyvalue/src/keyvalue.rs index 40afa1dd06..4b865b1b3d 100644 --- a/tests/runtimes/simple-keyvalue/src/keyvalue.rs +++ b/tests/runtimes/simple-keyvalue/src/keyvalue.rs @@ -6,7 +6,7 @@ use anyhow::{self, Context as _}; use oasis_runtime_sdk::{ self as sdk, - context::{Context, TxContext}, + context::Context, core::common::crypto::hash::Hash, error::RuntimeError, keymanager::{get_key_pair_id, KeyPair, KeyPairId}, @@ -16,7 +16,8 @@ use oasis_runtime_sdk::{ core::{Error as CoreError, API as _}, }, runtime::Runtime, - storage::{ConfidentialStore, CurrentStore, PrefixStore, Store, TypedStore}, + state::CurrentState, + storage::{ConfidentialStore, PrefixStore, Store, TypedStore}, types::{address, transaction}, }; @@ -102,7 +103,7 @@ impl sdk::module::Module for Module { impl sdk::module::TransactionHandler for Module { fn decode_tx( - _ctx: &mut C, + _ctx: &C, scheme: &str, body: &[u8], ) -> Result, CoreError> { @@ -160,8 +161,8 @@ impl sdk::module::BlockHandler for Module {} impl sdk::module::InvariantHandler for Module {} impl sdk::module::MethodHandler for Module { - fn dispatch_call( - ctx: &mut C, + fn dispatch_call( + ctx: &C, method: &str, body: cbor::Value, ) -> sdk::module::DispatchResult { @@ -183,7 +184,7 @@ impl sdk::module::MethodHandler for Module { } fn dispatch_query( - ctx: &mut C, + ctx: &C, method: &str, args: cbor::Value, ) -> sdk::module::DispatchResult> { @@ -197,21 +198,18 @@ impl sdk::module::MethodHandler for Module { // Actual implementation of this runtime's externally-callable methods. impl Module { /// Insert given keyvalue into storage. - fn tx_insert(ctx: &mut C, body: types::KeyValue) -> Result<(), Error> { + fn tx_insert(_ctx: &C, body: types::KeyValue) -> Result<(), Error> { let params = Self::params(); - if ctx.is_simulation() { - ::Core::use_tx_gas( - ctx, - max( - params.gas_costs.insert_absent, - params.gas_costs.insert_existing, - ), - )?; + if CurrentState::with_env(|env| env.is_simulation()) { + ::Core::use_tx_gas(max( + params.gas_costs.insert_absent, + params.gas_costs.insert_existing, + ))?; return Ok(()); } - let cost = CurrentStore::with(|store| { + let cost = CurrentState::with_store(|store| { let mut store = sdk::storage::PrefixStore::new(store, &MODULE_NAME); let ts = sdk::storage::TypedStore::new(&mut store); match ts.get::<_, Vec>(body.key.as_slice()) { @@ -219,39 +217,37 @@ impl Module { Some(_) => params.gas_costs.insert_existing, } }); - ::Core::use_tx_gas(ctx, cost)?; + ::Core::use_tx_gas(cost)?; - if ctx.is_check_only() { + if CurrentState::with_env(|env| env.is_check_only()) { return Ok(()); } let bc = body.clone(); - CurrentStore::with(|store| { - let mut store = sdk::storage::PrefixStore::new(store, &MODULE_NAME); + CurrentState::with(|state| { + let mut store = sdk::storage::PrefixStore::new(state.store(), &MODULE_NAME); let mut ts = sdk::storage::TypedStore::new(&mut store); ts.insert(&body.key, body.value); + + state.emit_event(Event::Insert { kv: bc }); }); - ctx.emit_event(Event::Insert { kv: bc }); Ok(()) } /// Remove keyvalue from storage using given key. - fn tx_remove(ctx: &mut C, body: types::Key) -> Result<(), Error> { + fn tx_remove(_ctx: &C, body: types::Key) -> Result<(), Error> { let params = Self::params(); - if ctx.is_simulation() { - ::Core::use_tx_gas( - ctx, - max( - params.gas_costs.remove_absent, - params.gas_costs.remove_existing, - ), - )?; + if CurrentState::with_env(|env| env.is_simulation()) { + ::Core::use_tx_gas(max( + params.gas_costs.remove_absent, + params.gas_costs.remove_existing, + ))?; return Ok(()); } - let cost = CurrentStore::with(|store| { + let cost = CurrentState::with_store(|store| { let mut store = sdk::storage::PrefixStore::new(store, &MODULE_NAME); let ts = sdk::storage::TypedStore::new(&mut store); match ts.get::<_, Vec>(body.key.as_slice()) { @@ -259,24 +255,26 @@ impl Module { Some(_) => params.gas_costs.remove_existing, } }); - ::Core::use_tx_gas(ctx, cost)?; + ::Core::use_tx_gas(cost)?; - if ctx.is_check_only() { + if CurrentState::with_env(|env| env.is_check_only()) { return Ok(()); } let bc = body.clone(); - CurrentStore::with(|store| { - let mut store = sdk::storage::PrefixStore::new(store, &MODULE_NAME); + CurrentState::with(|state| { + let mut store = sdk::storage::PrefixStore::new(state.store(), &MODULE_NAME); let mut ts = sdk::storage::TypedStore::new(&mut store); ts.remove(&body.key); + + state.emit_event(Event::Remove { key: bc }); }); - ctx.emit_event(Event::Remove { key: bc }); + Ok(()) } - fn tx_getcreatekey(ctx: &mut C, body: types::Key) -> Result<(), Error> { - if ctx.is_check_only() || ctx.is_simulation() { + fn tx_getcreatekey(ctx: &C, body: types::Key) -> Result<(), Error> { + if CurrentState::with_env(|env| !env.is_execute()) { return Ok(()); } @@ -303,7 +301,7 @@ impl Module { where F: FnOnce(&mut TypedStore>>) -> R, { - CurrentStore::with(|store| { + CurrentState::with_store(|store| { let inner_store = PrefixStore::new(store, &MODULE_NAME); let confidential_store = ConfidentialStore::new_with_key( inner_store, @@ -318,10 +316,10 @@ impl Module { /// Fetch keyvalue from confidential storage using given key. fn tx_confidential_get( - ctx: &mut C, + ctx: &C, body: types::ConfidentialKey, ) -> Result { - if ctx.is_check_only() || ctx.is_simulation() { + if CurrentState::with_env(|env| !env.is_execute()) { return Ok(types::KeyValue { key: Vec::new(), value: Vec::new(), @@ -340,24 +338,21 @@ impl Module { } /// Insert given keyvalue into confidential storage. - fn tx_confidential_insert( - ctx: &mut C, + fn tx_confidential_insert( + ctx: &C, body: types::ConfidentialKeyValue, ) -> Result<(), Error> { - if ctx.is_check_only() { + if CurrentState::with_env(|env| env.is_check_only()) { return Ok(()); } let params = Self::params(); - if ctx.is_simulation() { - ::Core::use_tx_gas( - ctx, - max( - params.gas_costs.confidential_insert_absent, - params.gas_costs.confidential_insert_existing, - ), - )?; + if CurrentState::with_env(|env| env.is_simulation()) { + ::Core::use_tx_gas(max( + params.gas_costs.confidential_insert_absent, + params.gas_costs.confidential_insert_existing, + ))?; return Ok(()); } @@ -368,41 +363,41 @@ impl Module { Some(_) => params.gas_costs.confidential_insert_existing, } }); - ::Core::use_tx_gas(ctx, cost)?; + ::Core::use_tx_gas(cost)?; // Recreate store and ts after we get ctx back Self::with_confidential_store(key_pair, |ts| { ts.insert(&body.key, body.value.clone()); }); - ctx.emit_event(Event::Insert { - kv: types::KeyValue { - key: body.key, - value: body.value, - }, + CurrentState::with(|state| { + state.emit_event(Event::Insert { + kv: types::KeyValue { + key: body.key, + value: body.value, + }, + }); }); + Ok(()) } /// Remove keyvalue from confidential storage using given key. - fn tx_confidential_remove( - ctx: &mut C, + fn tx_confidential_remove( + ctx: &C, body: types::ConfidentialKey, ) -> Result<(), Error> { - if ctx.is_check_only() { + if CurrentState::with_env(|env| env.is_check_only()) { return Ok(()); } let params = Self::params(); - if ctx.is_simulation() { - ::Core::use_tx_gas( - ctx, - max( - params.gas_costs.confidential_remove_absent, - params.gas_costs.confidential_remove_existing, - ), - )?; + if CurrentState::with_env(|env| env.is_simulation()) { + ::Core::use_tx_gas(max( + params.gas_costs.confidential_remove_absent, + params.gas_costs.confidential_remove_existing, + ))?; return Ok(()); } @@ -413,24 +408,27 @@ impl Module { Some(_) => params.gas_costs.confidential_remove_existing, } }); - ::Core::use_tx_gas(ctx, cost)?; + ::Core::use_tx_gas(cost)?; // Recreate store and ts after we get ctx back Self::with_confidential_store(key_pair, |ts| { ts.remove(&body.key); }); - ctx.emit_event(Event::Remove { - key: types::Key { - key: body.key.clone(), - }, + CurrentState::with(|state| { + state.emit_event(Event::Remove { + key: types::Key { + key: body.key.clone(), + }, + }); }); + Ok(()) } /// Fetch keyvalue from storage using given key. - fn query_get(_ctx: &mut C, body: types::Key) -> Result { - let v: Vec = CurrentStore::with(|store| { + fn query_get(_ctx: &C, body: types::Key) -> Result { + let v: Vec = CurrentState::with_store(|store| { let mut store = sdk::storage::PrefixStore::new(store, &MODULE_NAME); let ts = sdk::storage::TypedStore::new(&mut store); ts.get(body.key.clone()).ok_or(Error::InvalidArgument) @@ -447,7 +445,7 @@ impl sdk::module::MigrationHandler for Module { type Genesis = Genesis; fn init_or_migrate( - _ctx: &mut C, + _ctx: &C, meta: &mut sdk::modules::core::types::Metadata, genesis: Self::Genesis, ) -> bool { diff --git a/tests/runtimes/simple-keyvalue/src/lib.rs b/tests/runtimes/simple-keyvalue/src/lib.rs index ddd2ea7750..91fcb9680e 100644 --- a/tests/runtimes/simple-keyvalue/src/lib.rs +++ b/tests/runtimes/simple-keyvalue/src/lib.rs @@ -146,7 +146,7 @@ impl sdk::Runtime for Runtime { ) } - fn migrate_state(_ctx: &mut C) { + fn migrate_state(_ctx: &C) { // Fetch current parameters. type Rewards = modules::rewards::Module; let mut params = Rewards::params();