diff --git a/Cargo.lock b/Cargo.lock index 6ebd67a51c6..4eba2f71847 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3218,6 +3218,7 @@ name = "near-test-contracts" version = "0.0.0" dependencies = [ "once_cell", + "wat", ] [[package]] diff --git a/core/primitives-core/src/config.rs b/core/primitives-core/src/config.rs index eb8d332dc2f..93e9d562dc7 100644 --- a/core/primitives-core/src/config.rs +++ b/core/primitives-core/src/config.rs @@ -81,6 +81,9 @@ pub struct VMLimitConfig { pub max_promises_per_function_call_action: u64, /// Max number of input data dependencies pub max_number_input_data_dependencies: u64, + /// If present, stores max number of functions in one contract + #[serde(skip_serializing_if = "Option::is_none")] + pub max_functions_number_per_contract: Option, } impl Default for VMConfig { @@ -118,6 +121,7 @@ impl VMConfig { } } +// TODO #4649: remove default impl and create impls with explicitly stated purposes impl Default for VMLimitConfig { fn default() -> Self { Self { @@ -162,6 +166,7 @@ impl Default for VMLimitConfig { max_promises_per_function_call_action: 1024, // Unlikely to hit it for normal development. max_number_input_data_dependencies: 128, + max_functions_number_per_contract: None, } } } diff --git a/core/primitives/Cargo.toml b/core/primitives/Cargo.toml index 8215f1fa163..eada3436488 100644 --- a/core/primitives/Cargo.toml +++ b/core/primitives/Cargo.toml @@ -45,7 +45,8 @@ protocol_feature_block_header_v3 = [] protocol_feature_alt_bn128 = ["near-primitives-core/protocol_feature_alt_bn128", "near-vm-errors/protocol_feature_alt_bn128"] protocol_feature_chunk_only_producers = ["protocol_feature_block_header_v3"] protocol_feature_routing_exchange_algorithm = ["near-primitives-core/protocol_feature_routing_exchange_algorithm"] -nightly_protocol_features = ["nightly_protocol", "protocol_feature_block_header_v3", "protocol_feature_alt_bn128", "protocol_feature_chunk_only_producers", "protocol_feature_routing_exchange_algorithm"] +protocol_feature_limit_contract_functions_number = [] +nightly_protocol_features = ["nightly_protocol", "protocol_feature_block_header_v3", "protocol_feature_alt_bn128", "protocol_feature_chunk_only_producers", "protocol_feature_routing_exchange_algorithm", "protocol_feature_limit_contract_functions_number"] nightly_protocol = [] [dev-dependencies] diff --git a/core/primitives/src/runtime/config_store.rs b/core/primitives/src/runtime/config_store.rs index 95a16949fe1..ae56662b423 100644 --- a/core/primitives/src/runtime/config_store.rs +++ b/core/primitives/src/runtime/config_store.rs @@ -19,6 +19,8 @@ static CONFIGS: &[(ProtocolVersion, &[u8])] = &[ (0, include_config!("29.json")), (42, include_config!("42.json")), (48, include_config!("48.json")), + #[cfg(feature = "protocol_feature_limit_contract_functions_number")] + (123, include_config!("123.json")), ]; /// Stores runtime config for each protocol version where it was updated. @@ -109,6 +111,8 @@ mod tests { "3VBfW1GkXwKNiThPhrtjm2qGupYv5oEEZWapduXkd2gY", "BdCfuR4Gb5qgr2nhxUgGyDHesuhZg3Az5D3sEwQdQCvC", "2AUtULBkjrfzTepo6zFFMp4ShtiKgjpoUjoyRXLpcxiw", + #[cfg(feature = "protocol_feature_limit_contract_functions_number")] + "2NGvpASXyKxPcqJDsJNgi9U4qiRt7Bww5Q4GzLjcGT6m", ]; let actual_hashes = CONFIGS .iter() diff --git a/core/primitives/src/version.rs b/core/primitives/src/version.rs index de71564d3be..843295e4b8f 100644 --- a/core/primitives/src/version.rs +++ b/core/primitives/src/version.rs @@ -124,6 +124,10 @@ pub enum ProtocolFeature { ChunkOnlyProducers, #[cfg(feature = "protocol_feature_routing_exchange_algorithm")] RoutingExchangeAlgorithm, + /// Limit number of wasm functions in one contract. See + /// for more details. + #[cfg(feature = "protocol_feature_limit_contract_functions_number")] + LimitContractFunctionsNumber, } /// Current latest stable version of the protocol. @@ -134,7 +138,7 @@ pub const PROTOCOL_VERSION: ProtocolVersion = 48; /// Current latest nightly version of the protocol. #[cfg(feature = "nightly_protocol")] -pub const PROTOCOL_VERSION: ProtocolVersion = 122; +pub const PROTOCOL_VERSION: ProtocolVersion = 123; impl ProtocolFeature { pub const fn protocol_version(self) -> ProtocolVersion { @@ -167,6 +171,8 @@ impl ProtocolFeature { ProtocolFeature::ChunkOnlyProducers => 115, #[cfg(feature = "protocol_feature_routing_exchange_algorithm")] ProtocolFeature::RoutingExchangeAlgorithm => 117, + #[cfg(feature = "protocol_feature_limit_contract_functions_number")] + ProtocolFeature::LimitContractFunctionsNumber => 123, } } } diff --git a/integration-tests/Cargo.toml b/integration-tests/Cargo.toml index ffef17d152e..dd13d98a386 100644 --- a/integration-tests/Cargo.toml +++ b/integration-tests/Cargo.toml @@ -60,6 +60,7 @@ protocol_feature_alt_bn128 = [ ] protocol_feature_block_header_v3 = ["near-primitives/protocol_feature_block_header_v3", "near-chain/protocol_feature_block_header_v3", "near-store/protocol_feature_block_header_v3"] protocol_feature_chunk_only_producers = ["near-client/protocol_feature_chunk_only_producers"] -nightly_protocol_features = ["nearcore/nightly_protocol_features", "protocol_feature_alt_bn128", "protocol_feature_block_header_v3"] +protocol_feature_limit_contract_functions_number = ["near-primitives/protocol_feature_limit_contract_functions_number", "near-vm-runner/protocol_feature_limit_contract_functions_number"] +nightly_protocol_features = ["nearcore/nightly_protocol_features", "protocol_feature_alt_bn128", "protocol_feature_block_header_v3", "protocol_feature_limit_contract_functions_number"] nightly_protocol = ["nearcore/nightly_protocol"] sandbox = ["near-network/sandbox", "near-chain/sandbox", "node-runtime/sandbox", "near-client/sandbox"] diff --git a/integration-tests/tests/client/process_blocks.rs b/integration-tests/tests/client/process_blocks.rs index 4a0aecc8dde..1b5318ac77c 100644 --- a/integration-tests/tests/client/process_blocks.rs +++ b/integration-tests/tests/client/process_blocks.rs @@ -41,6 +41,7 @@ use near_primitives::errors::TxExecutionError; use near_primitives::hash::{hash, CryptoHash}; use near_primitives::merkle::verify_hash; use near_primitives::receipt::DelayedReceiptIndices; +use near_primitives::runtime::config_store::RuntimeConfigStore; use near_primitives::shard_layout::ShardUId; #[cfg(not(feature = "protocol_feature_block_header_v3"))] use near_primitives::sharding::ShardChunkHeaderV2; @@ -57,6 +58,8 @@ use near_primitives::types::validator_stake::ValidatorStake; use near_primitives::types::{AccountId, BlockHeight, EpochId, NumBlocks, ProtocolVersion}; use near_primitives::utils::to_timestamp; use near_primitives::validator_signer::{InMemoryValidatorSigner, ValidatorSigner}; +#[cfg(feature = "protocol_feature_limit_contract_functions_number")] +use near_primitives::version::ProtocolFeature; use near_primitives::version::PROTOCOL_VERSION; use near_primitives::views::{ BlockHeaderView, FinalExecutionStatus, QueryRequest, QueryResponseKind, @@ -3191,6 +3194,113 @@ fn test_validator_stake_host_function() { } } +// Check that we can't call a contract exceeding functions number limit after upgrade. +#[test] +fn test_limit_contract_functions_number_upgrade() { + let functions_number_limit: u32 = 10_000; + + #[cfg(feature = "protocol_feature_limit_contract_functions_number")] + let old_protocol_version = ProtocolFeature::LimitContractFunctionsNumber.protocol_version() - 1; + #[cfg(not(feature = "protocol_feature_limit_contract_functions_number"))] + let old_protocol_version = PROTOCOL_VERSION - 1; + + let new_protocol_version = old_protocol_version + 1; + + // Prepare TestEnv with a contract at the old protocol version. + let mut env = { + let epoch_length = 5; + let mut genesis = + Genesis::test(vec!["test0".parse().unwrap(), "test1".parse().unwrap()], 1); + genesis.config.epoch_length = epoch_length; + genesis.config.protocol_version = old_protocol_version; + let chain_genesis = ChainGenesis::from(&genesis); + let runtimes: Vec> = + vec![Arc::new(nearcore::NightshadeRuntime::test_with_runtime_config_store( + Path::new("."), + create_test_store(), + &genesis, + RuntimeConfigStore::new(None), + ))]; + let mut env = TestEnv::builder(chain_genesis).runtime_adapters(runtimes).build(); + + deploy_test_contract( + &mut env, + "test0".parse().unwrap(), + &near_test_contracts::many_functions_contract(functions_number_limit + 10), + epoch_length, + 1, + ); + env + }; + + let signer = InMemorySigner::from_seed("test0".parse().unwrap(), KeyType::ED25519, "test0"); + let tx = Transaction { + signer_id: "test0".parse().unwrap(), + receiver_id: "test0".parse().unwrap(), + public_key: signer.public_key(), + actions: vec![Action::FunctionCall(FunctionCallAction { + method_name: "main".to_string(), + args: Vec::new(), + gas: 100_000_000_000_000, + deposit: 0, + })], + + nonce: 0, + block_hash: CryptoHash::default(), + }; + + // Run the transaction & get tx outcome. + let old_outcome = { + let tip = env.clients[0].chain.head().unwrap(); + let signed_transaction = + Transaction { nonce: 10, block_hash: tip.last_block_hash, ..tx.clone() }.sign(&signer); + let tx_hash = signed_transaction.get_hash(); + env.clients[0].process_tx(signed_transaction, false, false); + for i in 0..3 { + env.produce_block(0, tip.height + i + 1); + } + env.clients[0].chain.get_final_transaction_result(&tx_hash).unwrap() + }; + + // Move to the new protocol version. + { + let tip = env.clients[0].chain.head().unwrap(); + let epoch_id = env.clients[0] + .runtime_adapter + .get_epoch_id_from_prev_block(&tip.last_block_hash) + .unwrap(); + let block_producer = + env.clients[0].runtime_adapter.get_block_producer(&epoch_id, tip.height).unwrap(); + let mut block = env.clients[0].produce_block(tip.height + 1).unwrap().unwrap(); + set_block_protocol_version(&mut block, block_producer.clone(), new_protocol_version); + let (_, res) = env.clients[0].process_block(block.clone(), Provenance::NONE); + assert!(res.is_ok()); + } + + // Re-run the transaction & get tx outcome. + let new_outcome = { + let tip = env.clients[0].chain.head().unwrap(); + let signed_transaction = + Transaction { nonce: 11, block_hash: tip.last_block_hash, ..tx.clone() }.sign(&signer); + let tx_hash = signed_transaction.get_hash(); + env.clients[0].process_tx(signed_transaction, false, false); + for i in 0..3 { + env.produce_block(0, tip.height + i + 1); + } + env.clients[0].chain.get_final_transaction_result(&tx_hash).unwrap() + }; + + assert!(matches!(old_outcome.status, FinalExecutionStatus::SuccessValue(_))); + if cfg!(feature = "protocol_feature_limit_contract_functions_number") { + assert!(matches!( + new_outcome.status, + FinalExecutionStatus::Failure(TxExecutionError::ActionError(_)) + )); + } else { + assert!(matches!(new_outcome.status, FinalExecutionStatus::SuccessValue(_))); + } +} + mod access_key_nonce_range_tests { use super::*; use near_client::test_utils::create_chunk_with_transactions; diff --git a/nearcore/Cargo.toml b/nearcore/Cargo.toml index 868c7a80bbf..12268818f26 100644 --- a/nearcore/Cargo.toml +++ b/nearcore/Cargo.toml @@ -72,7 +72,8 @@ protocol_feature_alt_bn128 = ["near-primitives/protocol_feature_alt_bn128", "nod protocol_feature_block_header_v3 = ["near-epoch-manager/protocol_feature_block_header_v3", "near-store/protocol_feature_block_header_v3", "near-primitives/protocol_feature_block_header_v3", "near-chain/protocol_feature_block_header_v3", "near-client/protocol_feature_block_header_v3"] protocol_feature_chunk_only_producers = ["protocol_feature_block_header_v3", "near-chain-configs/protocol_feature_chunk_only_producers", "near-epoch-manager/protocol_feature_chunk_only_producers", "near-chain/protocol_feature_chunk_only_producers", "near-client/protocol_feature_chunk_only_producers", "node-runtime/protocol_feature_chunk_only_producers", "near-rosetta-rpc/protocol_feature_chunk_only_producers"] protocol_feature_routing_exchange_algorithm = ["near-primitives/protocol_feature_routing_exchange_algorithm", "near-chain/protocol_feature_routing_exchange_algorithm", "near-network/protocol_feature_routing_exchange_algorithm", "near-client/protocol_feature_routing_exchange_algorithm", "near-jsonrpc/protocol_feature_routing_exchange_algorithm"] -nightly_protocol_features = ["nightly_protocol", "near-primitives/nightly_protocol_features", "near-client/nightly_protocol_features", "near-epoch-manager/nightly_protocol_features", "near-store/nightly_protocol_features", "protocol_feature_block_header_v3", "protocol_feature_alt_bn128", "protocol_feature_chunk_only_producers", "protocol_feature_routing_exchange_algorithm"] +protocol_feature_limit_contract_functions_number = ["near-primitives/protocol_feature_limit_contract_functions_number", "near-vm-runner/protocol_feature_limit_contract_functions_number"] +nightly_protocol_features = ["nightly_protocol", "near-primitives/nightly_protocol_features", "near-client/nightly_protocol_features", "near-epoch-manager/nightly_protocol_features", "near-store/nightly_protocol_features", "protocol_feature_block_header_v3", "protocol_feature_alt_bn128", "protocol_feature_chunk_only_producers", "protocol_feature_routing_exchange_algorithm", "protocol_feature_limit_contract_functions_number"] nightly_protocol = ["near-primitives/nightly_protocol", "near-jsonrpc/nightly_protocol"] # Force usage of a specific wasm vm irrespective of protocol version. diff --git a/nearcore/res/runtime_configs/123.json b/nearcore/res/runtime_configs/123.json new file mode 100644 index 00000000000..c61df7c6cdf --- /dev/null +++ b/nearcore/res/runtime_configs/123.json @@ -0,0 +1,184 @@ +{ + "storage_amount_per_byte": "10000000000000000000", + "transaction_costs": { + "action_receipt_creation_config": { + "send_sir": 108059500000, + "send_not_sir": 108059500000, + "execution": 108059500000 + }, + "data_receipt_creation_config": { + "base_cost": { + "send_sir": 36486732312, + "send_not_sir": 36486732312, + "execution": 36486732312 + }, + "cost_per_byte": { + "send_sir": 17212011, + "send_not_sir": 17212011, + "execution": 17212011 + } + }, + "action_creation_config": { + "create_account_cost": { + "send_sir": 99607375000, + "send_not_sir": 99607375000, + "execution": 99607375000 + }, + "deploy_contract_cost": { + "send_sir": 184765750000, + "send_not_sir": 184765750000, + "execution": 184765750000 + }, + "deploy_contract_cost_per_byte": { + "send_sir": 6812999, + "send_not_sir": 6812999, + "execution": 6812999 + }, + "function_call_cost": { + "send_sir": 2319861500000, + "send_not_sir": 2319861500000, + "execution": 2319861500000 + }, + "function_call_cost_per_byte": { + "send_sir": 2235934, + "send_not_sir": 2235934, + "execution": 2235934 + }, + "transfer_cost": { + "send_sir": 115123062500, + "send_not_sir": 115123062500, + "execution": 115123062500 + }, + "stake_cost": { + "send_sir": 141715687500, + "send_not_sir": 141715687500, + "execution": 102217625000 + }, + "add_key_cost": { + "full_access_cost": { + "send_sir": 101765125000, + "send_not_sir": 101765125000, + "execution": 101765125000 + }, + "function_call_cost": { + "send_sir": 102217625000, + "send_not_sir": 102217625000, + "execution": 102217625000 + }, + "function_call_cost_per_byte": { + "send_sir": 1925331, + "send_not_sir": 1925331, + "execution": 1925331 + } + }, + "delete_key_cost": { + "send_sir": 94946625000, + "send_not_sir": 94946625000, + "execution": 94946625000 + }, + "delete_account_cost": { + "send_sir": 147489000000, + "send_not_sir": 147489000000, + "execution": 147489000000 + } + }, + "storage_usage_config": { + "num_bytes_account": 100, + "num_extra_bytes_record": 40 + }, + "burnt_gas_reward": [ + 3, + 10 + ], + "pessimistic_gas_price_inflation_ratio": [ + 103, + 100 + ] + }, + "wasm_config": { + "ext_costs": { + "base": 264768111, + "contract_compile_base": 35445963, + "contract_compile_bytes": 216750, + "read_memory_base": 2609863200, + "read_memory_byte": 3801333, + "write_memory_base": 2803794861, + "write_memory_byte": 2723772, + "read_register_base": 2517165186, + "read_register_byte": 98562, + "write_register_base": 2865522486, + "write_register_byte": 3801564, + "utf8_decoding_base": 3111779061, + "utf8_decoding_byte": 291580479, + "utf16_decoding_base": 3543313050, + "utf16_decoding_byte": 163577493, + "sha256_base": 4540970250, + "sha256_byte": 24117351, + "keccak256_base": 5879491275, + "keccak256_byte": 21471105, + "keccak512_base": 5811388236, + "keccak512_byte": 36649701, + "ripemd160_base": 853675086, + "ripemd160_block": 680107584, + "ecrecover_base": 278821988457, + "log_base": 3543313050, + "log_byte": 13198791, + "storage_write_base": 64196736000, + "storage_write_key_byte": 70482867, + "storage_write_value_byte": 31018539, + "storage_write_evicted_byte": 32117307, + "storage_read_base": 56356845750, + "storage_read_key_byte": 30952533, + "storage_read_value_byte": 5611005, + "storage_remove_base": 53473030500, + "storage_remove_key_byte": 38220384, + "storage_remove_ret_value_byte": 11531556, + "storage_has_key_base": 54039896625, + "storage_has_key_byte": 30790845, + "storage_iter_create_prefix_base": 0, + "storage_iter_create_prefix_byte": 0, + "storage_iter_create_range_base": 0, + "storage_iter_create_from_byte": 0, + "storage_iter_create_to_byte": 0, + "storage_iter_next_base": 0, + "storage_iter_next_key_byte": 0, + "storage_iter_next_value_byte": 0, + "touching_trie_node": 16101955926, + "promise_and_base": 1465013400, + "promise_and_per_promise": 5452176, + "promise_return": 560152386, + "validator_stake_base": 911834726400, + "validator_total_stake_base": 911834726400 + }, + "grow_mem_cost": 1, + "regular_op_cost": 3856371, + "limit_config": { + "max_gas_burnt": 200000000000000, + "max_gas_burnt_view": 200000000000000, + "max_stack_height": 16384, + "initial_memory_pages": 1024, + "max_memory_pages": 2048, + "registers_memory_limit": 1073741824, + "max_register_size": 104857600, + "max_number_registers": 100, + "max_number_logs": 100, + "max_total_log_length": 16384, + "max_total_prepaid_gas": 300000000000000, + "max_actions_per_receipt": 100, + "max_number_bytes_method_names": 2000, + "max_length_method_name": 256, + "max_arguments_length": 4194304, + "max_length_returned_data": 4194304, + "max_contract_size": 4194304, + "max_length_storage_key": 4194304, + "max_length_storage_value": 4194304, + "max_promises_per_function_call_action": 1024, + "max_number_input_data_dependencies": 128, + "max_functions_number_per_contract": 10000 + } + }, + "account_creation_config": { + "min_allowed_top_level_account_length": 32, + "registrar_account_id": "registrar" + } +} diff --git a/nearcore/src/runtime/mod.rs b/nearcore/src/runtime/mod.rs index 170116b0601..b7ae5b55d78 100644 --- a/nearcore/src/runtime/mod.rs +++ b/nearcore/src/runtime/mod.rs @@ -146,7 +146,12 @@ pub struct NightshadeRuntime { } impl NightshadeRuntime { - pub fn test(home_dir: &Path, store: Arc, genesis: &Genesis) -> Self { + pub fn test_with_runtime_config_store( + home_dir: &Path, + store: Arc, + genesis: &Genesis, + runtime_config_store: RuntimeConfigStore, + ) -> Self { Self::new( home_dir, store, @@ -154,10 +159,14 @@ impl NightshadeRuntime { TrackedConfig::new_empty(), None, None, - RuntimeConfigStore::test(), + runtime_config_store, ) } + pub fn test(home_dir: &Path, store: Arc, genesis: &Genesis) -> Self { + Self::test_with_runtime_config_store(home_dir, store, genesis, RuntimeConfigStore::test()) + } + pub fn with_config( home_dir: &Path, store: Arc, diff --git a/runtime/near-test-contracts/Cargo.toml b/runtime/near-test-contracts/Cargo.toml index 001114fdb21..5d8f370ca72 100644 --- a/runtime/near-test-contracts/Cargo.toml +++ b/runtime/near-test-contracts/Cargo.toml @@ -8,3 +8,4 @@ license = "Apache-2.0" [dependencies] once_cell = "1" +wat = "1.0.40" diff --git a/runtime/near-test-contracts/src/lib.rs b/runtime/near-test-contracts/src/lib.rs index ad7af57b938..7ab5a87771c 100644 --- a/runtime/near-test-contracts/src/lib.rs +++ b/runtime/near-test-contracts/src/lib.rs @@ -1,8 +1,8 @@ #![doc = include_str!("../README.md")] -use std::path::Path; - use once_cell::sync::OnceCell; +use std::fmt::Write; +use std::path::Path; pub fn rs_contract() -> &'static [u8] { static CONTRACT: OnceCell> = OnceCell::new(); @@ -41,3 +41,26 @@ fn smoke_test() { assert!(!ts_contract().is_empty()); assert!(!tiny_contract().is_empty()); } + +pub fn many_functions_contract(function_count: u32) -> Vec { + let mut functions = String::new(); + for i in 0..function_count { + writeln!( + &mut functions, + "(func + i32.const {} + drop + return)", + i + ) + .unwrap(); + } + + let code = format!( + r#"(module + (export "main" (func 0)) + {})"#, + functions + ); + wat::parse_str(code).unwrap() +} diff --git a/runtime/near-vm-errors/src/lib.rs b/runtime/near-vm-errors/src/lib.rs index 5ac656f6f4b..f6faa0cf19e 100644 --- a/runtime/near-vm-errors/src/lib.rs +++ b/runtime/near-vm-errors/src/lib.rs @@ -143,6 +143,8 @@ pub enum PrepareError { Instantiate, /// Error creating memory. Memory, + /// Contract contains too many functions. + TooManyFunctions, } #[derive( @@ -290,7 +292,8 @@ impl fmt::Display for PrepareError { GasInstrumentation => write!(f, "Gas instrumentation failed."), StackHeightInstrumentation => write!(f, "Stack instrumentation failed."), Instantiate => write!(f, "Error happened during instantiation."), - Memory => write!(f, "Error creating memory"), + Memory => write!(f, "Error creating memory."), + TooManyFunctions => write!(f, "Too many functions in contract."), } } } diff --git a/runtime/near-vm-runner/Cargo.toml b/runtime/near-vm-runner/Cargo.toml index 1d3927eba5c..510dc8c262e 100644 --- a/runtime/near-vm-runner/Cargo.toml +++ b/runtime/near-vm-runner/Cargo.toml @@ -72,6 +72,8 @@ protocol_feature_alt_bn128 = [ "near-primitives/protocol_feature_alt_bn128", "near-vm-errors/protocol_feature_alt_bn128" ] +protocol_feature_limit_contract_functions_number = ["near-primitives/protocol_feature_limit_contract_functions_number"] +nightly_protocol = ["near-primitives/nightly_protocol"] [package.metadata.cargo-udeps.ignore] # `no_cache` feature leads to an unused `cached` crate diff --git a/runtime/near-vm-runner/src/prepare.rs b/runtime/near-vm-runner/src/prepare.rs index 68bda346524..f626bc396b8 100644 --- a/runtime/near-vm-runner/src/prepare.rs +++ b/runtime/near-vm-runner/src/prepare.rs @@ -137,6 +137,19 @@ impl<'a> ContractModule<'a> { Ok(Self { module, config }) } + fn validate_functions_number(self) -> Result { + #[cfg(feature = "protocol_feature_limit_contract_functions_number")] + if let Some(max_functions_number) = + self.config.limit_config.max_functions_number_per_contract + { + let functions_number = self.module.functions_space() as u64; + if functions_number > max_functions_number { + return Err(PrepareError::TooManyFunctions); + } + } + Ok(self) + } + fn into_wasm_code(self) -> Result, PrepareError> { elements::serialize(self.module).map_err(|_| PrepareError::Serialization) } @@ -150,6 +163,7 @@ impl<'a> ContractModule<'a> { /// - module doesn't define an internal memory instance, /// - imported memory (if any) doesn't reserve more memory than permitted by the `config`, /// - all imported functions from the external environment matches defined by `env` module, +/// - functions number does not exceed limit specified in VMConfig, /// /// The preprocessing includes injecting code for gas metering and metering the height of stack. pub fn prepare_contract(original_code: &[u8], config: &VMConfig) -> Result, PrepareError> { @@ -159,6 +173,7 @@ pub fn prepare_contract(original_code: &[u8], config: &VMConfig) -> Result (Option, Option) { + let mut fake_external = MockedExternal::new(); + let context = create_context(vec![]); + let runtime_config_store = RuntimeConfigStore::new(None); + let runtime_config = runtime_config_store.get_config(protocol_version); + let config = &runtime_config.wasm_config; + let fees = &runtime_config.transaction_costs; + + let promise_results = vec![]; + + let code = ContractCode::new(code.to_vec(), None); + run_vm( + &code, + method_name, + &mut fake_external, + context, + &config, + fees, + &promise_results, + vm_kind, + protocol_version, + None, + ) +} + fn make_simple_contract_call_vm( code: &[u8], method_name: &str, diff --git a/runtime/near-vm-runner/src/tests/contract_preload.rs b/runtime/near-vm-runner/src/tests/contract_preload.rs index 8a93347cd30..d9de0e85375 100644 --- a/runtime/near-vm-runner/src/tests/contract_preload.rs +++ b/runtime/near-vm-runner/src/tests/contract_preload.rs @@ -1,17 +1,18 @@ -use crate::{run_vm, ContractCallPrepareRequest, ContractCaller, VMError, VMKind}; +use std::collections::HashMap; +use std::sync::{Arc, Mutex}; +use std::thread::sleep; +use std::time::Duration; + use near_primitives::contract::ContractCode; use near_primitives::runtime::fees::RuntimeFeesConfig; +use near_primitives::types::CompiledContractCache; +use near_vm_errors::VMError::FunctionCallError; +use near_vm_logic::mocks::mock_external::MockedExternal; use near_vm_logic::{ProtocolVersion, VMConfig, VMContext, VMOutcome}; use crate::cache::precompile_contract_vm; use crate::errors::ContractPrecompilatonResult; -use near_primitives::types::CompiledContractCache; -use near_vm_errors::VMError::FunctionCallError; -use near_vm_logic::mocks::mock_external::MockedExternal; -use std::collections::HashMap; -use std::sync::{Arc, Mutex}; -use std::thread::sleep; -use std::time::Duration; +use crate::{run_vm, ContractCallPrepareRequest, ContractCaller, VMError, VMKind}; fn default_vm_context() -> VMContext { return VMContext { diff --git a/runtime/near-vm-runner/src/tests/error_cases.rs b/runtime/near-vm-runner/src/tests/error_cases.rs index e8acf612815..7e7d108dfe9 100644 --- a/runtime/near-vm-runner/src/tests/error_cases.rs +++ b/runtime/near-vm-runner/src/tests/error_cases.rs @@ -658,7 +658,7 @@ fn test_initializer_no_gas() { &some_initializer_contract(), "hello", 0, - vm_kind + vm_kind, ), ( Some(vm_outcome_with_gas(0)), @@ -734,7 +734,7 @@ fn test_external_call_ok() { fn test_external_call_error() { with_vm_variants(|vm_kind: VMKind| { assert_eq!( - make_simple_contract_call_with_gas_vm(&external_call_contract(), "hello", 100, vm_kind), + make_simple_contract_call_with_gas_vm(&external_call_contract(), "hello", 100, vm_kind,), ( Some(vm_outcome_with_gas(100)), Some(VMError::FunctionCallError(FunctionCallError::HostError( diff --git a/runtime/near-vm-runner/src/tests/invalid_contracts.rs b/runtime/near-vm-runner/src/tests/invalid_contracts.rs index f6aedebd2cf..d4734b30a39 100644 --- a/runtime/near-vm-runner/src/tests/invalid_contracts.rs +++ b/runtime/near-vm-runner/src/tests/invalid_contracts.rs @@ -1,6 +1,15 @@ +#[cfg(feature = "protocol_feature_limit_contract_functions_number")] +use near_primitives::version::ProtocolFeature; +#[cfg(not(feature = "protocol_feature_limit_contract_functions_number"))] +use near_primitives::version::PROTOCOL_VERSION; use near_vm_errors::{CompilationError, FunctionCallError, PrepareError, VMError}; -use crate::tests::{make_simple_contract_call_vm, with_vm_variants}; +use assert_matches::assert_matches; + +use crate::tests::{ + make_simple_contract_call_vm, make_simple_contract_call_with_protocol_version_vm, + with_vm_variants, +}; use crate::VMKind; fn initializer_wrong_signature_contract() -> Vec { @@ -142,3 +151,55 @@ fn test_evil_function_index() { ); }); } + +#[test] +fn test_limit_contract_functions_number() { + with_vm_variants(|vm_kind: VMKind| { + #[cfg(feature = "protocol_feature_limit_contract_functions_number")] + let old_protocol_version = + ProtocolFeature::LimitContractFunctionsNumber.protocol_version() - 1; + #[cfg(not(feature = "protocol_feature_limit_contract_functions_number"))] + let old_protocol_version = PROTOCOL_VERSION - 1; + + let new_protocol_version = old_protocol_version + 1; + + let functions_number_limit: u32 = 10_000; + let method_name = "main"; + + let code = near_test_contracts::many_functions_contract(functions_number_limit + 10); + let (_, err) = make_simple_contract_call_with_protocol_version_vm( + &code, + method_name, + old_protocol_version, + vm_kind, + ); + assert_eq!(err, None); + + let code = near_test_contracts::many_functions_contract(functions_number_limit - 10); + let (_, err) = make_simple_contract_call_with_protocol_version_vm( + &code, + method_name, + new_protocol_version, + vm_kind, + ); + assert_eq!(err, None); + + let code = near_test_contracts::many_functions_contract(functions_number_limit + 10); + let (_, err) = make_simple_contract_call_with_protocol_version_vm( + &code, + method_name, + new_protocol_version, + vm_kind, + ); + if cfg!(feature = "protocol_feature_limit_contract_functions_number") { + assert_matches!( + err, + Some(VMError::FunctionCallError(FunctionCallError::CompilationError( + CompilationError::PrepareError(PrepareError::TooManyFunctions) + ))) + ); + } else { + assert_eq!(err, None); + } + }); +}