Skip to content

Commit

Permalink
Merge pull request #1884 from CosmWasm/1599-gas-factor
Browse files Browse the repository at this point in the history
[2.0] Reduce gas factor
  • Loading branch information
chipshort authored Nov 7, 2023
2 parents 576ba88 + 2d20050 commit 79b1571
Show file tree
Hide file tree
Showing 17 changed files with 67 additions and 49 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
10 changes: 5 additions & 5 deletions contracts/cyberpunk/tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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}");
}
Expand All @@ -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();
Expand Down Expand Up @@ -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();
Expand All @@ -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();
Expand Down
4 changes: 2 additions & 2 deletions contracts/hackatom/tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion contracts/queue/tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<MockApi, MockStorage, MockQuerier>, 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, &[]);
Expand Down
51 changes: 34 additions & 17 deletions docs/GAS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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)<sup>1</sup>. Assuming a max supported block
CosmWasm gas aims for 1 Teragas/second, i.e. the uint64 range exceeds after 18
million seconds (5000 hours)<sup>1</sup>. 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 range<sup>2</sup>.
614891 (614891 Teragas/second) in order to exceed the uint64 range<sup>2</sup>.
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).

<sup>1</sup> Python3: `(2**64-1)/1000 / 10**12`
<sup>1</sup> Python3: `(2**64-1) / 10**12`

<sup>2</sup> Python3: `((2**64-1)/1000/30) / 10**122`
<sup>2</sup> 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
4 changes: 2 additions & 2 deletions packages/vm/benches/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion packages/vm/examples/multi_threaded_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion packages/vm/src/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
11 changes: 5 additions & 6 deletions packages/vm/src/environment.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -169,8 +169,7 @@ impl<A: BackendApi, S: Storage, Q: Querier> Environment<A, S, Q> {
C: FnOnce(&ContextData<S, Q>) -> R,
{
let guard = self.data.as_ref().read().unwrap();
let context_data = guard.borrow();
callback(context_data)
callback(&guard)
}

pub fn with_gas_state<C, R>(&self, callback: C) -> R
Expand Down Expand Up @@ -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<Size> = Some(Size::mebi(16));

Expand Down
2 changes: 1 addition & 1 deletion packages/vm/src/imports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Size> = Some(Size::mebi(16));

const ECDSA_HASH_HEX: &str = "5ae8317d34d1e595e3fa7247db80c0af4320cce1116de187f8f7e2e099c0d8d0";
Expand Down
12 changes: 6 additions & 6 deletions packages/vm/src/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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();
Expand All @@ -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,
Expand Down Expand Up @@ -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]
Expand All @@ -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]
Expand Down Expand Up @@ -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);
}
}
2 changes: 1 addition & 1 deletion packages/vm/src/modules/file_system_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ mod tests {
use wasmer_middlewares::metering::set_remaining_points;

const TESTING_MEMORY_LIMIT: Option<Size> = 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)))
Expand Down
2 changes: 1 addition & 1 deletion packages/vm/src/modules/in_memory_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ mod tests {
use wasmer_middlewares::metering::set_remaining_points;

const TESTING_MEMORY_LIMIT: Option<Size> = 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;

Expand Down
2 changes: 1 addition & 1 deletion packages/vm/src/modules/pinned_memory_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ mod tests {
use wasmer_middlewares::metering::set_remaining_points;

const TESTING_MEMORY_LIMIT: Option<Size> = 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() {
Expand Down
2 changes: 1 addition & 1 deletion packages/vm/src/testing/instance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Size> = Some(Size::mebi(16));
const DEFAULT_PRINT_DEBUG: bool = true;

Expand Down
2 changes: 1 addition & 1 deletion packages/vm/src/testing/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
4 changes: 2 additions & 2 deletions packages/vm/src/wasm_backend/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit 79b1571

Please sign in to comment.