diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fc1973267..bb8c1be848 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,10 +30,12 @@ and this project adheres to - cosmwasm-std: Make inner values of `CanonicalAddr` and `Binary` private and add constructor for `Binary`. ([#1876]) - cosmwasm-vm: Make inner value of `Size` private and add constructor. ([#1876]) +- cosmwasm-vm: Reduce gas values by a factor of 1000. ([#1884]) [#1874]: https://github.com/CosmWasm/cosmwasm/pull/1874 [#1876]: https://github.com/CosmWasm/cosmwasm/pull/1876 [#1879]: https://github.com/CosmWasm/cosmwasm/pull/1879 +[#1884]: https://github.com/CosmWasm/cosmwasm/pull/1884 [#1898]: https://github.com/CosmWasm/cosmwasm/pull/1898 [#1902]: https://github.com/CosmWasm/cosmwasm/pull/1902 diff --git a/contracts/cyberpunk/tests/integration.rs b/contracts/cyberpunk/tests/integration.rs index 539a53eb2a..da39c22e25 100644 --- a/contracts/cyberpunk/tests/integration.rs +++ b/contracts/cyberpunk/tests/integration.rs @@ -31,7 +31,7 @@ static WASM: &[u8] = include_bytes!("../target/wasm32-unknown-unknown/release/cy #[test] fn execute_argon2() { - let mut deps = mock_instance_with_gas_limit(WASM, 100_000_000_000_000); + let mut deps = mock_instance_with_gas_limit(WASM, 100_000_000_000); let init_info = mock_info("admin", &[]); let init_res: Response = instantiate(&mut deps, mock_env(), init_info, Empty {}).unwrap(); @@ -51,7 +51,7 @@ fn execute_argon2() { let gas_used = gas_before - deps.get_gas_left(); // Note: the exact gas usage depends on the Rust version used to compile Wasm, // which we only fix when using rust-optimizer, not integration tests. - let expected = 8635688250000; // +/- 20% + let expected = 8635688250; // +/- 20% assert!(gas_used > expected * 80 / 100, "Gas used: {gas_used}"); assert!(gas_used < expected * 120 / 100, "Gas used: {gas_used}"); } @@ -60,7 +60,7 @@ fn execute_argon2() { // cargo integration-test debug_works -- --nocapture #[test] fn debug_works() { - let mut deps = mock_instance_with_gas_limit(WASM, 100_000_000_000_000); + let mut deps = mock_instance_with_gas_limit(WASM, 100_000_000_000); let _res: Response = instantiate(&mut deps, mock_env(), mock_info("admin", &[]), Empty {}).unwrap(); @@ -89,7 +89,7 @@ fn debug_works() { // cargo integration-test debug_timing -- --nocapture #[test] fn debug_timing() { - let mut deps = mock_instance_with_gas_limit(WASM, 100_000_000_000_000); + let mut deps = mock_instance_with_gas_limit(WASM, 100_000_000_000); let _res: Response = instantiate(&mut deps, mock_env(), mock_info("admin", &[]), Empty {}).unwrap(); @@ -115,7 +115,7 @@ fn debug_timing() { #[test] fn debug_file() { - let mut deps = mock_instance_with_gas_limit(WASM, 100_000_000_000_000); + let mut deps = mock_instance_with_gas_limit(WASM, 100_000_000_000); let _res: Response = instantiate(&mut deps, mock_env(), mock_info("admin", &[]), Empty {}).unwrap(); diff --git a/contracts/hackatom/tests/integration.rs b/contracts/hackatom/tests/integration.rs index 890309ffd6..1a73fe88f3 100644 --- a/contracts/hackatom/tests/integration.rs +++ b/contracts/hackatom/tests/integration.rs @@ -405,7 +405,7 @@ fn execute_allocate_large_memory() { // Gas consumption is relatively small // Note: the exact gas usage depends on the Rust version used to compile Wasm, // which we only fix when using rust-optimizer, not integration tests. - assert_approx_eq!(gas_used, 4413600000, "0.2"); + assert_approx_eq!(gas_used, 4413600, "0.2"); let used = deps.memory_pages(); assert_eq!(used, pages_before + 48, "Memory used: {used} pages"); pages_before += 48; @@ -424,7 +424,7 @@ fn execute_allocate_large_memory() { // Gas consumption is relatively small // Note: the exact gas usage depends on the Rust version used to compile Wasm, // which we only fix when using rust-optimizer, not integration tests. - let expected = 4859700000; // +/- 20% + let expected = 4859700; // +/- 20% assert!(gas_used > expected * 80 / 100, "Gas used: {gas_used}"); assert!(gas_used < expected * 120 / 100, "Gas used: {gas_used}"); let used = deps.memory_pages(); diff --git a/contracts/queue/tests/integration.rs b/contracts/queue/tests/integration.rs index b0a86ae1d5..c791881e44 100644 --- a/contracts/queue/tests/integration.rs +++ b/contracts/queue/tests/integration.rs @@ -36,7 +36,7 @@ static WASM: &[u8] = include_bytes!("../target/wasm32-unknown-unknown/release/qu /// Instantiates a contract with no elements fn create_contract() -> (Instance, MessageInfo) { - let gas_limit = 1_000_000_000_000; // ~1ms, enough for many executions within one instance + let gas_limit = 1_000_000_000; // ~1ms, enough for many executions within one instance let mut deps = mock_instance_with_gas_limit(WASM, gas_limit); let creator = String::from("creator"); let info = mock_info(&creator, &[]); diff --git a/docs/GAS.md b/docs/GAS.md index 73eb260345..4798b14fd5 100644 --- a/docs/GAS.md +++ b/docs/GAS.md @@ -19,14 +19,15 @@ while ago and can be adjusted when necessary. ## CosmWasm gas pricing For CosmWasm gas, the target gas consumption is 1 Teragas (10^12 gas) per -millisecond. This idea is [inspired by NEAR][neargas] and we encourage you to -read their excellent docs on that topic. +second. This idea is [inspired by NEAR][neargas] and we encourage you to read +their excellent docs on that topic. In order to meet this target, we execute Argon2 in a test contract ([#1120]). This is a CPU and memory intense job that does not call out into the host. At a constant gas cost per operation of 1 (pre CosmWasm 1.0), this consumed 96837752 gas and took 15ms on our CI system. The ideal cost per operation for this system -is `10**12 / (96837752 / 15)`: 154898. This is rounded to 150000 for simplicity. +is `10**12 / (96837752 / (15 / 1000))`: 154. This is rounded to 150 for +simplicity. Each machine is different, we know that. But the above target helps us in multiple ways: @@ -45,23 +46,39 @@ multiple ways: ## Gas overflow potential -CosmWasm gas aims for 1 Teragas/millisecond, i.e. the uint64 range exceeds after -18 million seconds (5 hours)1. Assuming a max supported block +CosmWasm gas aims for 1 Teragas/second, i.e. the uint64 range exceeds after 18 +million seconds (5000 hours)1. Assuming a max supported block execution time of 30 seconds, the gas price has to be over-priced by a factor of -614 (614 Teragas/millisecond) in order to exceed the uint64 range2. +614891 (614891 Teragas/second) in order to exceed the uint64 range2. Since serious over or underpricing is considered a bug, using uint64 for gas measurements is considered safe. -Cosmos SDK gas uses values that are smaller by a factor of 150_000, so those -don't overflow as well. Since no Cosmos SDK gas values are processed inside of -this repository, this is not our main concern. However, it's good to know that -we can safely pass them in uint64 fields, as long as the full range is -supported. This is the case for the C API as well as -[JSON numbers](https://www.json.org/) as long as both sides support integers in -their JSON implementation. Go and Rust do that while many other implementations -don't support integers, and convert them to IEEE-754 doubles, which has a safe -integer range up to about 53 bit (e.g. JavaScript and jq). +Cosmos SDK gas uses values that are smaller by a factor of 150, so those don't +overflow as well. Since no Cosmos SDK gas values are processed inside of this +repository, this is not our main concern. However, it's good to know that we can +safely pass them in uint64 fields, as long as the full range is supported. This +is the case for the C API as well as [JSON numbers](https://www.json.org/) as +long as both sides support integers in their JSON implementation. Go and Rust do +that while many other implementations don't support integers, and convert them +to IEEE-754 doubles, which has a safe integer range up to about 53 bit (e.g. +JavaScript and jq). -1 Python3: `(2**64-1)/1000 / 10**12` +1 Python3: `(2**64-1) / 10**12` -2 Python3: `((2**64-1)/1000/30) / 10**122` +2 Python3: `((2**64-1)/30) / 10**12` + +## CosmWasm 1.x -> 2.0 changes + +In all versions before 2.0, the gas values were bigger by a factor of 1000. +There is no need to have them this big and in order to reduce the risk of +overflow, the gas values were lowered in [#1599]. Here is a breakdown of what +this change entails: + +| | CosmWasm 1.x | CosmWasm 2.x | +| -------------------------- | --------------------- | --------------------- | +| Cost target | 1 Teragas/millisecond | 1 Teragas/second | +| Exceeds uint64 range after | 5 hours | 5124 hours (213 days) | +| Cost per Wasm op | 150_000 | 150 | +| Multiplier | 140_000_000 | 140_000 | + +[#1599]: https://github.com/CosmWasm/cosmwasm/pull/1599 diff --git a/packages/vm/benches/main.rs b/packages/vm/benches/main.rs index 16ce2c23cd..7bdd75ad51 100644 --- a/packages/vm/benches/main.rs +++ b/packages/vm/benches/main.rs @@ -17,12 +17,12 @@ use cosmwasm_vm::{ // Instance const DEFAULT_MEMORY_LIMIT: Size = Size::mebi(64); -const DEFAULT_GAS_LIMIT: u64 = 1_000_000_000_000; // ~1ms +const DEFAULT_GAS_LIMIT: u64 = 1_000_000_000; // ~1ms const DEFAULT_INSTANCE_OPTIONS: InstanceOptions = InstanceOptions { gas_limit: DEFAULT_GAS_LIMIT, print_debug: false, }; -const HIGH_GAS_LIMIT: u64 = 20_000_000_000_000_000; // ~20s, allows many calls on one instance +const HIGH_GAS_LIMIT: u64 = 20_000_000_000_000; // ~20s, allows many calls on one instance // Cache const MEMORY_CACHE_SIZE: Size = Size::mebi(200); diff --git a/packages/vm/examples/multi_threaded_cache.rs b/packages/vm/examples/multi_threaded_cache.rs index 0b42e0dd9c..1cc5b98131 100644 --- a/packages/vm/examples/multi_threaded_cache.rs +++ b/packages/vm/examples/multi_threaded_cache.rs @@ -11,7 +11,7 @@ use cosmwasm_vm::{ // Instance const DEFAULT_MEMORY_LIMIT: Size = Size::mebi(64); -const DEFAULT_GAS_LIMIT: u64 = 400_000 * 150_000; +const DEFAULT_GAS_LIMIT: u64 = 400_000 * 150; const DEFAULT_INSTANCE_OPTIONS: InstanceOptions = InstanceOptions { gas_limit: DEFAULT_GAS_LIMIT, print_debug: false, diff --git a/packages/vm/src/cache.rs b/packages/vm/src/cache.rs index 1014e7dd21..b4d1d9ea8e 100644 --- a/packages/vm/src/cache.rs +++ b/packages/vm/src/cache.rs @@ -528,7 +528,7 @@ mod tests { use std::io::Write; use tempfile::TempDir; - const TESTING_GAS_LIMIT: u64 = 500_000_000_000; // ~0.5ms + const TESTING_GAS_LIMIT: u64 = 500_000_000; // ~0.5ms const TESTING_MEMORY_LIMIT: Size = Size::mebi(16); const TESTING_OPTIONS: InstanceOptions = InstanceOptions { gas_limit: TESTING_GAS_LIMIT, diff --git a/packages/vm/src/environment.rs b/packages/vm/src/environment.rs index d6a974cb05..ad6bac36f4 100644 --- a/packages/vm/src/environment.rs +++ b/packages/vm/src/environment.rs @@ -1,5 +1,5 @@ //! Internal details to be used by instance.rs only -use std::borrow::{Borrow, BorrowMut}; +use std::borrow::BorrowMut; use std::cell::RefCell; use std::marker::PhantomData; use std::ptr::NonNull; @@ -45,8 +45,8 @@ pub struct GasConfig { impl Default for GasConfig { fn default() -> Self { - // Target is 10^12 per millisecond (see GAS.md), i.e. 10^9 gas per µ second. - const GAS_PER_US: u64 = 1_000_000_000; + // Target is 10^12 per second (see GAS.md), i.e. 10^6 gas per µ second. + const GAS_PER_US: u64 = 1_000_000; Self { // ~154 us in crypto benchmarks secp256k1_verify_cost: 154 * GAS_PER_US, @@ -169,8 +169,7 @@ impl Environment { C: FnOnce(&ContextData) -> R, { let guard = self.data.as_ref().read().unwrap(); - let context_data = guard.borrow(); - callback(context_data) + callback(&guard) } pub fn with_gas_state(&self, callback: C) -> R @@ -469,7 +468,7 @@ mod tests { const INIT_AMOUNT: u128 = 500; const INIT_DENOM: &str = "TOKEN"; - const TESTING_GAS_LIMIT: u64 = 500_000_000_000; // ~0.5ms + const TESTING_GAS_LIMIT: u64 = 500_000_000; // ~0.5ms const DEFAULT_QUERY_GAS_LIMIT: u64 = 300_000; const TESTING_MEMORY_LIMIT: Option = Some(Size::mebi(16)); diff --git a/packages/vm/src/imports.rs b/packages/vm/src/imports.rs index f27a2750fc..745b87f2e8 100644 --- a/packages/vm/src/imports.rs +++ b/packages/vm/src/imports.rs @@ -643,7 +643,7 @@ mod tests { const INIT_AMOUNT: u128 = 500; const INIT_DENOM: &str = "TOKEN"; - const TESTING_GAS_LIMIT: u64 = 500_000_000_000; // ~0.5ms + const TESTING_GAS_LIMIT: u64 = 500_000_000; // ~0.5ms const TESTING_MEMORY_LIMIT: Option = Some(Size::mebi(16)); const ECDSA_HASH_HEX: &str = "5ae8317d34d1e595e3fa7247db80c0af4320cce1116de187f8f7e2e099c0d8d0"; diff --git a/packages/vm/src/instance.rs b/packages/vm/src/instance.rs index c4be57cdbc..b400731de8 100644 --- a/packages/vm/src/instance.rs +++ b/packages/vm/src/instance.rs @@ -535,7 +535,7 @@ mod tests { #[test] fn set_debug_handler_and_unset_debug_handler_work() { - const LIMIT: u64 = 70_000_000_000_000; + const LIMIT: u64 = 70_000_000_000; let mut instance = mock_instance_with_gas_limit(CYBERPUNK, LIMIT); // init contract @@ -868,7 +868,7 @@ mod tests { #[test] fn create_gas_report_works() { - const LIMIT: u64 = 700_000_000_000; + const LIMIT: u64 = 700_000_000; let mut instance = mock_instance_with_gas_limit(CONTRACT, LIMIT); let report1 = instance.create_gas_report(); @@ -886,7 +886,7 @@ mod tests { let report2 = instance.create_gas_report(); assert_eq!(report2.used_externally, 73); - assert_eq!(report2.used_internally, 5764950198); + assert_eq!(report2.used_internally, 5765148); assert_eq!(report2.limit, LIMIT); assert_eq!( report2.remaining, @@ -1075,7 +1075,7 @@ mod tests { .unwrap(); let init_used = orig_gas - instance.get_gas_left(); - assert_eq!(init_used, 5764950271); + assert_eq!(init_used, 5765221); } #[test] @@ -1098,7 +1098,7 @@ mod tests { .unwrap(); let execute_used = gas_before_execute - instance.get_gas_left(); - assert_eq!(execute_used, 8548903606); + assert_eq!(execute_used, 8652406); } #[test] @@ -1132,6 +1132,6 @@ mod tests { assert_eq!(answer.as_slice(), b"{\"verifier\":\"verifies\"}"); let query_used = gas_before_query - instance.get_gas_left(); - assert_eq!(query_used, 4493700006); + assert_eq!(query_used, 4493706); } } diff --git a/packages/vm/src/modules/file_system_cache.rs b/packages/vm/src/modules/file_system_cache.rs index 9008c038b5..58cac91b73 100644 --- a/packages/vm/src/modules/file_system_cache.rs +++ b/packages/vm/src/modules/file_system_cache.rs @@ -232,7 +232,7 @@ mod tests { use wasmer_middlewares::metering::set_remaining_points; const TESTING_MEMORY_LIMIT: Option = Some(Size::mebi(16)); - const TESTING_GAS_LIMIT: u64 = 500_000_000; + const TESTING_GAS_LIMIT: u64 = 500_000; const SOME_WAT: &str = r#"(module (type $t0 (func (param i32) (result i32))) diff --git a/packages/vm/src/modules/in_memory_cache.rs b/packages/vm/src/modules/in_memory_cache.rs index a61ece8c01..5b2eec52d5 100644 --- a/packages/vm/src/modules/in_memory_cache.rs +++ b/packages/vm/src/modules/in_memory_cache.rs @@ -113,7 +113,7 @@ mod tests { use wasmer_middlewares::metering::set_remaining_points; const TESTING_MEMORY_LIMIT: Option = Some(Size::mebi(16)); - const TESTING_GAS_LIMIT: u64 = 500_000_000; + const TESTING_GAS_LIMIT: u64 = 500_000; // Based on `examples/module_size.sh` const TESTING_WASM_SIZE_FACTOR: usize = 18; diff --git a/packages/vm/src/modules/pinned_memory_cache.rs b/packages/vm/src/modules/pinned_memory_cache.rs index f8fe048694..6177013b98 100644 --- a/packages/vm/src/modules/pinned_memory_cache.rs +++ b/packages/vm/src/modules/pinned_memory_cache.rs @@ -81,7 +81,7 @@ mod tests { use wasmer_middlewares::metering::set_remaining_points; const TESTING_MEMORY_LIMIT: Option = Some(Size::mebi(16)); - const TESTING_GAS_LIMIT: u64 = 500_000_000; + const TESTING_GAS_LIMIT: u64 = 500_000; #[test] fn pinned_memory_cache_run() { diff --git a/packages/vm/src/testing/instance.rs b/packages/vm/src/testing/instance.rs index ce1ba84c07..bf5c200a95 100644 --- a/packages/vm/src/testing/instance.rs +++ b/packages/vm/src/testing/instance.rs @@ -17,7 +17,7 @@ use super::storage::MockStorage; /// This gas limit is used in integration tests and should be high enough to allow a reasonable /// number of contract executions and queries on one instance. For this reason it is significatly /// higher than the limit for a single execution that we have in the production setup. -const DEFAULT_GAS_LIMIT: u64 = 500_000_000_000; // ~0.5ms +const DEFAULT_GAS_LIMIT: u64 = 500_000_000; // ~0.5ms const DEFAULT_MEMORY_LIMIT: Option = Some(Size::mebi(16)); const DEFAULT_PRINT_DEBUG: bool = true; diff --git a/packages/vm/src/testing/mock.rs b/packages/vm/src/testing/mock.rs index 15c7942eb3..d0edca6e41 100644 --- a/packages/vm/src/testing/mock.rs +++ b/packages/vm/src/testing/mock.rs @@ -8,7 +8,7 @@ use super::storage::MockStorage; use crate::{Backend, BackendApi, BackendError, BackendResult, GasInfo}; pub const MOCK_CONTRACT_ADDR: &str = "cosmos2contract"; -const GAS_COST_HUMANIZE: u64 = 44; +const GAS_COST_HUMANIZE: u64 = 44; // TODO: these seem very low const GAS_COST_CANONICALIZE: u64 = 55; /// All external requirements that can be injected for unit tests. diff --git a/packages/vm/src/wasm_backend/engine.rs b/packages/vm/src/wasm_backend/engine.rs index b5b67e660a..b5be96ee52 100644 --- a/packages/vm/src/wasm_backend/engine.rs +++ b/packages/vm/src/wasm_backend/engine.rs @@ -22,12 +22,12 @@ const MAX_WASM_PAGES: u32 = 65536; fn cost(_operator: &Operator) -> u64 { // A flat fee for each operation - // The target is 1 Teragas per millisecond (see GAS.md). + // The target is 1 Teragas per second (see GAS.md). // // In https://github.com/CosmWasm/cosmwasm/pull/1042 a profiler is developed to // identify runtime differences between different Wasm operation, but this is not yet // precise enough to derive insights from it. - 150_000 + 150 } /// Creates an engine without a compiler.