diff --git a/.circleci/config.yml b/.circleci/config.yml index ddc969c61d..acd3756e6f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -61,7 +61,7 @@ workflows: - contract_hackatom - contract_ibc_reflect - contract_ibc_reflect_send - - contract_floaty + # - contract_floaty # This contract needs nightly Rust to compile - contract_queue - contract_reflect - contract_staking @@ -1030,13 +1030,14 @@ jobs: name: Build development contracts command: | echo "Building all contracts under ./contracts" + export GLOBIGNORE="./contracts/floaty/" docker run --volumes-from with_code cosmwasm/rust-optimizer:0.12.13 ./contracts/*/ - run: name: Check development contracts command: | echo "Checking all contracts under ./artifacts" docker run --volumes-from with_code rust:1.67.0 \ - /bin/bash -e -c 'export GLOBIGNORE="artifacts/floaty.wasm"; cd ./code; cargo run --bin cosmwasm-check artifacts/*.wasm' + /bin/bash -e -c 'cd ./code; cargo run --bin cosmwasm-check artifacts/*.wasm' docker cp with_code:/code/artifacts . - run: name: Publish artifacts on GitHub diff --git a/CHANGELOG.md b/CHANGELOG.md index 93b8860e4a..31018a206d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ and this project adheres to `to_{vec,binary}` in favor of `to_json_{vec,binary}` and `from_{slice,binary}` in favor of `from_json`. ([#1886]) - cosmwasm-std: Add `SignedDecimal` and `SignedDecimal256` ([#1807]). +- cosmwasm-vm: Allow float operations with NaN canonicalization ([#1864]). [#1854]: https://github.com/CosmWasm/cosmwasm/pull/1854 [#1861]: https://github.com/CosmWasm/cosmwasm/pull/1861 @@ -31,6 +32,7 @@ and this project adheres to [#1870]: https://github.com/CosmWasm/cosmwasm/pull/1870 [#1886]: https://github.com/CosmWasm/cosmwasm/pull/1886 [#1807]: https://github.com/CosmWasm/cosmwasm/pull/1807 +[#1864]: https://github.com/CosmWasm/cosmwasm/pull/1864 ## [1.4.0] - 2023-09-04 diff --git a/contracts/floaty/.vscode/settings.json b/contracts/floaty/.vscode/settings.json new file mode 100644 index 0000000000..11fee5dc5b --- /dev/null +++ b/contracts/floaty/.vscode/settings.json @@ -0,0 +1,6 @@ +{ + "rust-analyzer.cargo.target": "wasm32-unknown-unknown", + "rust-analyzer.cargo.extraEnv": { + "RUSTFLAGS": "-C target-feature=+nontrapping-fptoint" + } +} diff --git a/contracts/floaty/Cargo.lock b/contracts/floaty/Cargo.lock index 37e0fc6474..2e0272a2a5 100644 --- a/contracts/floaty/Cargo.lock +++ b/contracts/floaty/Cargo.lock @@ -689,9 +689,9 @@ dependencies = [ "cosmwasm-schema", "cosmwasm-std", "cosmwasm-vm", + "rand_chacha", "schemars", "serde", - "thiserror", ] [[package]] @@ -1034,6 +1034,12 @@ dependencies = [ "spki", ] +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -1060,9 +1066,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.56" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" +checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" dependencies = [ "unicode-ident", ] @@ -1089,13 +1095,23 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.9" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + [[package]] name = "rand_core" version = "0.5.1" diff --git a/contracts/floaty/Cargo.toml b/contracts/floaty/Cargo.toml index 0aee91625c..e78bf00175 100644 --- a/contracts/floaty/Cargo.toml +++ b/contracts/floaty/Cargo.toml @@ -35,7 +35,7 @@ cosmwasm-schema = { path = "../../packages/schema" } cosmwasm-std = { path = "../../packages/std" } schemars = "0.8.3" serde = { version = "1.0.103", default-features = false, features = ["derive"] } -thiserror = "1.0.26" +rand_chacha = { version = "0.3.1", default-features = false } [dev-dependencies] cosmwasm-vm = { path = "../../packages/vm", default-features = false, features = ["iterator"] } diff --git a/contracts/floaty/README.md b/contracts/floaty/README.md new file mode 100644 index 0000000000..9212056517 --- /dev/null +++ b/contracts/floaty/README.md @@ -0,0 +1,13 @@ +# Floaty Contract + +This contract contains all WebAssembly floating point instructions. It is used +for testing the floating point support. + +In order to compile it, you need a nightly version of Rust and enable the +`nontrapping-fptoint` target-feature. This allows the usage of +[some more conversion instructions](https://github.com/WebAssembly/spec/blob/main/proposals/nontrapping-float-to-int-conversion/Overview.md). +To do this, run: + +```sh +RUSTFLAGS="-C link-arg=-s -C target-feature=+nontrapping-fptoint" cargo wasm +``` diff --git a/contracts/floaty/schema/floaty.json b/contracts/floaty/schema/floaty.json index 62f697a59f..cd80c14339 100644 --- a/contracts/floaty/schema/floaty.json +++ b/contracts/floaty/schema/floaty.json @@ -5,53 +5,50 @@ "instantiate": { "$schema": "http://json-schema.org/draft-07/schema#", "title": "InstantiateMsg", - "type": "object", - "required": [ - "beneficiary", - "verifier" - ], - "properties": { - "beneficiary": { - "type": "string" - }, - "verifier": { - "type": "string" - } - }, - "additionalProperties": false + "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", + "type": "object" }, - "execute": { + "execute": null, + "query": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "ExecuteMsg", + "title": "QueryMsg", "oneOf": [ { - "description": "Releasing all funds in the contract to the beneficiary. This is the only \"proper\" action of this demo contract.", + "description": "Returns valid random arguments for the given instruction", "type": "object", "required": [ - "release" + "random_args_for" ], "properties": { - "release": { + "random_args_for": { "type": "object", + "required": [ + "instruction", + "seed" + ], + "properties": { + "instruction": { + "type": "string" + }, + "seed": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, "additionalProperties": false } }, "additionalProperties": false - } - ] - }, - "query": { - "$schema": "http://json-schema.org/draft-07/schema#", - "title": "QueryMsg", - "oneOf": [ + }, { - "description": "returns a human-readable representation of the verifier use to ensure query path works in integration tests", + "description": "Returns a list of all instructions", "type": "object", "required": [ - "verifier" + "instructions" ], "properties": { - "verifier": { + "instructions": { "type": "object", "additionalProperties": false } @@ -59,19 +56,26 @@ "additionalProperties": false }, { - "description": "This returns cosmwasm_std::AllBalanceResponse to demo use of the querier", + "description": "Runs the given instruction with the given arguments and returns the result", "type": "object", "required": [ - "other_balance" + "run" ], "properties": { - "other_balance": { + "run": { "type": "object", "required": [ - "address" + "args", + "instruction" ], "properties": { - "address": { + "args": { + "type": "array", + "items": { + "$ref": "#/definitions/Value" + } + }, + "instruction": { "type": "string" } }, @@ -80,62 +84,212 @@ }, "additionalProperties": false } - ] + ], + "definitions": { + "Value": { + "oneOf": [ + { + "type": "object", + "required": [ + "u32" + ], + "properties": { + "u32": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "u64" + ], + "properties": { + "u64": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "f32" + ], + "properties": { + "f32": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "f64" + ], + "properties": { + "f64": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + ] + } + } }, "migrate": null, "sudo": null, "responses": { - "other_balance": { + "instructions": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "AllBalanceResponse", - "type": "object", - "required": [ - "amount" - ], - "properties": { - "amount": { - "description": "Returns all non-zero coins held by this account.", - "type": "array", - "items": { - "$ref": "#/definitions/Coin" - } - } + "title": "Array_of_String", + "type": "array", + "items": { + "type": "string" + } + }, + "random_args_for": { + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_Value", + "type": "array", + "items": { + "$ref": "#/definitions/Value" }, "definitions": { - "Coin": { - "type": "object", - "required": [ - "amount", - "denom" - ], - "properties": { - "amount": { - "$ref": "#/definitions/Uint128" + "Value": { + "oneOf": [ + { + "type": "object", + "required": [ + "u32" + ], + "properties": { + "u32": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "u64" + ], + "properties": { + "u64": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false }, - "denom": { - "type": "string" + { + "type": "object", + "required": [ + "f32" + ], + "properties": { + "f32": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "f64" + ], + "properties": { + "f64": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false } - } - }, - "Uint128": { - "description": "A thin wrapper around u128 that is using strings for JSON encoding/decoding, such that the full u128 range can be used for clients that convert JSON numbers to floats, like JavaScript and jq.\n\n# Examples\n\nUse `from` to create instances of this and `u128` to get the value out:\n\n``` # use cosmwasm_std::Uint128; let a = Uint128::from(123u128); assert_eq!(a.u128(), 123);\n\nlet b = Uint128::from(42u64); assert_eq!(b.u128(), 42);\n\nlet c = Uint128::from(70u32); assert_eq!(c.u128(), 70); ```", - "type": "string" + ] } } }, - "verifier": { + "run": { "$schema": "http://json-schema.org/draft-07/schema#", - "title": "VerifierResponse", - "type": "object", - "required": [ - "verifier" - ], - "properties": { - "verifier": { - "type": "string" + "title": "Value", + "oneOf": [ + { + "type": "object", + "required": [ + "u32" + ], + "properties": { + "u32": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "u64" + ], + "properties": { + "u64": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "f32" + ], + "properties": { + "f32": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "f64" + ], + "properties": { + "f64": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false } - }, - "additionalProperties": false + ] } } } diff --git a/contracts/floaty/schema/raw/execute.json b/contracts/floaty/schema/raw/execute.json index 76967fd17b..bcd478d67e 100644 --- a/contracts/floaty/schema/raw/execute.json +++ b/contracts/floaty/schema/raw/execute.json @@ -1,20 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "title": "ExecuteMsg", - "oneOf": [ - { - "description": "Releasing all funds in the contract to the beneficiary. This is the only \"proper\" action of this demo contract.", - "type": "object", - "required": [ - "release" - ], - "properties": { - "release": { - "type": "object", - "additionalProperties": false - } - }, - "additionalProperties": false - } - ] + "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", + "type": "object" } diff --git a/contracts/floaty/schema/raw/instantiate.json b/contracts/floaty/schema/raw/instantiate.json index 8639103d34..5f6dfaf43c 100644 --- a/contracts/floaty/schema/raw/instantiate.json +++ b/contracts/floaty/schema/raw/instantiate.json @@ -1,18 +1,6 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "title": "InstantiateMsg", - "type": "object", - "required": [ - "beneficiary", - "verifier" - ], - "properties": { - "beneficiary": { - "type": "string" - }, - "verifier": { - "type": "string" - } - }, - "additionalProperties": false + "description": "An empty struct that serves as a placeholder in different places, such as contracts that don't set a custom message.\n\nIt is designed to be expressable in correct JSON and JSON Schema but contains no meaningful data. Previously we used enums without cases, but those cannot represented as valid JSON Schema (https://github.com/CosmWasm/cosmwasm/issues/451)", + "type": "object" } diff --git a/contracts/floaty/schema/raw/query.json b/contracts/floaty/schema/raw/query.json index b96c3dfb8a..376f327931 100644 --- a/contracts/floaty/schema/raw/query.json +++ b/contracts/floaty/schema/raw/query.json @@ -3,13 +3,41 @@ "title": "QueryMsg", "oneOf": [ { - "description": "returns a human-readable representation of the verifier use to ensure query path works in integration tests", + "description": "Returns valid random arguments for the given instruction", "type": "object", "required": [ - "verifier" + "random_args_for" ], "properties": { - "verifier": { + "random_args_for": { + "type": "object", + "required": [ + "instruction", + "seed" + ], + "properties": { + "instruction": { + "type": "string" + }, + "seed": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + { + "description": "Returns a list of all instructions", + "type": "object", + "required": [ + "instructions" + ], + "properties": { + "instructions": { "type": "object", "additionalProperties": false } @@ -17,19 +45,26 @@ "additionalProperties": false }, { - "description": "This returns cosmwasm_std::AllBalanceResponse to demo use of the querier", + "description": "Runs the given instruction with the given arguments and returns the result", "type": "object", "required": [ - "other_balance" + "run" ], "properties": { - "other_balance": { + "run": { "type": "object", "required": [ - "address" + "args", + "instruction" ], "properties": { - "address": { + "args": { + "type": "array", + "items": { + "$ref": "#/definitions/Value" + } + }, + "instruction": { "type": "string" } }, @@ -38,5 +73,67 @@ }, "additionalProperties": false } - ] + ], + "definitions": { + "Value": { + "oneOf": [ + { + "type": "object", + "required": [ + "u32" + ], + "properties": { + "u32": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "u64" + ], + "properties": { + "u64": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "f32" + ], + "properties": { + "f32": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "f64" + ], + "properties": { + "f64": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + ] + } + } } diff --git a/contracts/floaty/schema/raw/response_to_instructions.json b/contracts/floaty/schema/raw/response_to_instructions.json new file mode 100644 index 0000000000..4290cb1a21 --- /dev/null +++ b/contracts/floaty/schema/raw/response_to_instructions.json @@ -0,0 +1,8 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_String", + "type": "array", + "items": { + "type": "string" + } +} diff --git a/contracts/floaty/schema/raw/response_to_random_args_for.json b/contracts/floaty/schema/raw/response_to_random_args_for.json new file mode 100644 index 0000000000..4b9c97bed9 --- /dev/null +++ b/contracts/floaty/schema/raw/response_to_random_args_for.json @@ -0,0 +1,70 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Array_of_Value", + "type": "array", + "items": { + "$ref": "#/definitions/Value" + }, + "definitions": { + "Value": { + "oneOf": [ + { + "type": "object", + "required": [ + "u32" + ], + "properties": { + "u32": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "u64" + ], + "properties": { + "u64": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "f32" + ], + "properties": { + "f32": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "f64" + ], + "properties": { + "f64": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + ] + } + } +} diff --git a/contracts/floaty/schema/raw/response_to_run.json b/contracts/floaty/schema/raw/response_to_run.json new file mode 100644 index 0000000000..08c5730abf --- /dev/null +++ b/contracts/floaty/schema/raw/response_to_run.json @@ -0,0 +1,62 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Value", + "oneOf": [ + { + "type": "object", + "required": [ + "u32" + ], + "properties": { + "u32": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "u64" + ], + "properties": { + "u64": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "f32" + ], + "properties": { + "f32": { + "type": "integer", + "format": "uint32", + "minimum": 0.0 + } + }, + "additionalProperties": false + }, + { + "type": "object", + "required": [ + "f64" + ], + "properties": { + "f64": { + "type": "integer", + "format": "uint64", + "minimum": 0.0 + } + }, + "additionalProperties": false + } + ] +} diff --git a/contracts/floaty/src/bin/schema.rs b/contracts/floaty/src/bin/schema.rs index b0a8a02b01..6a19141a53 100644 --- a/contracts/floaty/src/bin/schema.rs +++ b/contracts/floaty/src/bin/schema.rs @@ -1,11 +1,11 @@ use cosmwasm_schema::write_api; -use floaty::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; +use cosmwasm_std::Empty; +use floaty::msg::QueryMsg; fn main() { write_api! { - instantiate: InstantiateMsg, + instantiate: Empty, query: QueryMsg, - execute: ExecuteMsg, } } diff --git a/contracts/floaty/src/contract.rs b/contracts/floaty/src/contract.rs index 157dcf5765..99b0def3b2 100644 --- a/contracts/floaty/src/contract.rs +++ b/contracts/floaty/src/contract.rs @@ -1,91 +1,47 @@ use cosmwasm_std::{ - entry_point, from_json, to_json_binary, to_json_vec, AllBalanceResponse, BankMsg, Deps, - DepsMut, Env, Event, MessageInfo, QueryResponse, Response, StdError, StdResult, + entry_point, from_slice, to_binary, to_vec, AllBalanceResponse, BankMsg, Deps, DepsMut, Env, + Event, MessageInfo, QueryResponse, Response, StdError, StdResult, }; +use rand_chacha::rand_core::SeedableRng; -use crate::errors::HackError; -use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg, VerifierResponse}; -use crate::state::{State, CONFIG_KEY}; +#[cfg(target_arch = "wasm32")] +use crate::instructions::run_instruction; +use crate::{ + instructions::{random_args_for, Value, FLOAT_INSTRUCTIONS}, + msg::QueryMsg, +}; #[entry_point] pub fn instantiate( - deps: DepsMut, + _deps: DepsMut, _env: Env, - info: MessageInfo, - msg: InstantiateMsg, -) -> Result { - deps.api.debug("here we go 🚀"); - - deps.storage.set( - CONFIG_KEY, - &to_json_vec(&State { - verifier: deps.api.addr_validate(&msg.verifier)?, - beneficiary: deps.api.addr_validate(&msg.beneficiary)?, - funder: info.sender, - })?, - ); - - // This adds some unrelated event attribute for testing purposes - Ok(Response::new().add_attribute("Let the", "hacking begin")) + _info: MessageInfo, + _msg: Empty, +) -> Result { + Ok(Response::default()) } #[entry_point] -pub fn execute( - deps: DepsMut, - env: Env, - info: MessageInfo, - _msg: ExecuteMsg, -) -> Result { - let data = deps - .storage - .get(CONFIG_KEY) - .ok_or_else(|| StdError::not_found("State"))?; - let state: State = from_json(data)?; - - if info.sender == state.verifier { - let to_addr = state.beneficiary; - let balance = deps.querier.query_all_balances(env.contract.address)?; - - let mut fl = balance[0].amount.u128() as f64; - fl *= 0.3; - - let resp = Response::new() - .add_attribute("action", "release") - .add_attribute("destination", to_addr.clone()) - .add_attribute("foo", fl.to_string()) - .add_event(Event::new("hackatom").add_attribute("action", "release")) - .add_message(BankMsg::Send { - to_address: to_addr.into(), - amount: balance, - }) - .set_data([0xF0, 0x0B, 0xAA]); - Ok(resp) - } else { - Err(HackError::Unauthorized {}) - } -} - -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { +pub fn query(_deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { - QueryMsg::Verifier {} => to_json_binary(&query_verifier(deps)?), - QueryMsg::OtherBalance { address } => to_json_binary(&query_other_balance(deps, address)?), + QueryMsg::RandomArgsFor { instruction, seed } => { + let mut rng = rand_chacha::ChaChaRng::seed_from_u64(seed); + to_binary(&random_args_for(&instruction, &mut rng)) + } + QueryMsg::Instructions {} => to_binary(&FLOAT_INSTRUCTIONS.to_vec()), + QueryMsg::Run { instruction, args } => to_binary(&query_run(&instruction, args)?), } } -fn query_verifier(deps: Deps) -> StdResult { - let data = deps - .storage - .get(CONFIG_KEY) - .ok_or_else(|| StdError::not_found("State"))?; - let state: State = from_json(data)?; - Ok(VerifierResponse { - verifier: state.verifier.into(), - }) -} +fn query_run(instruction: &str, args: Vec) -> StdResult { + #[cfg(not(target_arch = "wasm32"))] + panic!(); -fn query_other_balance(deps: Deps, address: String) -> StdResult { - let amount = deps.querier.query_all_balances(address)?; - Ok(AllBalanceResponse { amount }) + #[cfg(target_arch = "wasm32")] + { + let result = run_instruction(instruction, &args); + Ok(result) + } } #[cfg(test)] @@ -124,7 +80,7 @@ mod tests { // it worked, let's check the state let data = deps.storage.get(CONFIG_KEY).expect("no data stored"); - let state: State = from_json(data).unwrap(); + let state: State = from_slice(&data).unwrap(); assert_eq!(state, expected_state); } @@ -246,7 +202,7 @@ mod tests { // state should not change let data = deps.storage.get(CONFIG_KEY).expect("no data stored"); - let state: State = from_json(data).unwrap(); + let state: State = from_slice(&data).unwrap(); assert_eq!( state, State { diff --git a/contracts/floaty/src/errors.rs b/contracts/floaty/src/errors.rs deleted file mode 100644 index 7b46b00b63..0000000000 --- a/contracts/floaty/src/errors.rs +++ /dev/null @@ -1,12 +0,0 @@ -use cosmwasm_std::StdError; -use thiserror::Error; - -#[derive(Error, Debug, PartialEq)] -pub enum HackError { - #[error("{0}")] - /// this is needed so we can use `bucket.load(...)?` and have it auto-converted to the custom error - Std(#[from] StdError), - // this is whatever we want - #[error("Unauthorized")] - Unauthorized {}, -} diff --git a/contracts/floaty/src/floats.rs b/contracts/floaty/src/floats.rs new file mode 100644 index 0000000000..ccbb06ade5 --- /dev/null +++ b/contracts/floaty/src/floats.rs @@ -0,0 +1,212 @@ +use rand_chacha::rand_core::RngCore; + +pub const INF_32: u32 = 0x7f800000; +pub const NEG_INF_32: u32 = 0xff800000; +pub const INF_64: u64 = 0x7ff0000000000000; +pub const NEG_INF_64: u64 = 0xfff0000000000000; + +const EXPONENT_MASK_32: u32 = 0x7f800000; +const EXPONENT_MASK_64: u64 = 0x7ff0000000000000; +const SIGN_MASK_32: u32 = 0x80000000; +const SIGN_MASK_64: u64 = 0x8000000000000000; +const MANTISSA_MASK_32: u32 = 0x007fffff; +const MANTISSA_MASK_64: u64 = 0x000fffffffffffff; + +/// Returns a random `f32`. +/// +/// We want to cover all classes of floats: NaNs, subnormals, infinities, and normal floats. +/// Because of that, we don't just generate a random `u32` and convert it to an `f32` +/// (that would make e.g. infinities highly unlikely) +/// Instead, we give each of these classes a probability of 25% and +/// then generate a random pattern within that class +pub fn random_f32(rng: &mut impl RngCore) -> f32 { + let decider = rng.next_u32(); + let bits = match decider % 4 { + 0 => { + // 25% chance of being a NaN + random_nan_32(rng) + } + 1 => { + // 25% chance of being a subnormal + random_subnormal_32(rng) + } + 2 => { + // 25% chance of being an infinite + if decider % 2 == 0 { + INF_32 + } else { + NEG_INF_32 + } + } + 3 => { + // 25% chance of being a random bit pattern + rng.next_u32() + } + _ => unreachable!(), + }; + f32::from_bits(bits) +} + +/// Returns a random `f64`. +/// +/// See [`random_f32`] for more details. +pub fn random_f64(rng: &mut impl RngCore) -> f64 { + let decider = rng.next_u64(); + let bits = match decider % 4 { + 0 => { + // 25% chance of being a NaN + random_nan_64(rng) + } + 1 => { + // 25% chance of being a subnormal + random_subnormal_64(rng) + } + 2 => { + // 25% chance of being an infinite + if decider % 2 == 0 { + INF_64 + } else { + NEG_INF_64 + } + } + 3 => { + // 25% chance of being a random bit pattern + rng.next_u64() + } + _ => unreachable!(), + }; + f64::from_bits(bits) +} + +/// Returns bits for a random NaN +pub fn random_nan_32(rng: &mut impl RngCore) -> u32 { + let mut rnd = rng.next_u32(); + if rnd == 0 { + // we don't want to return an infinity, so we just set the last bit to 1 + rnd = 1; + } + // Set the exponent to all 1s and remaining bits random + EXPONENT_MASK_32 | rnd +} + +/// Returns bits for a random NaN +pub fn random_nan_64(rng: &mut impl RngCore) -> u64 { + let mut rnd = rng.next_u64(); + if rnd == 0 { + // we don't want to return an infinity, so we just set the last bit to 1 + rnd = 1; + } + // Set the exponent to all 1s and remaining bits random + EXPONENT_MASK_64 | rnd +} + +/// Returns bits for a random subnormal +pub fn random_subnormal_32(rng: &mut impl RngCore) -> u32 { + // Set the exponent to all 0s and remaining bits random + let res = rng.next_u32() & (SIGN_MASK_32 | MANTISSA_MASK_32); + + if res == 0 { + // we don't want to return a zero, so we just return a fixed subnormal + SIGN_MASK_32 | MANTISSA_MASK_32 + } else { + res + } +} + +/// Returns bits for a random subnormal +pub fn random_subnormal_64(rng: &mut impl RngCore) -> u64 { + // Set the exponent to all 0s and remaining bits random + let res = rng.next_u64() & (SIGN_MASK_64 | MANTISSA_MASK_64); + + if res == 0 { + // we don't want to return a zero, so we just return a fixed subnormal + SIGN_MASK_64 | MANTISSA_MASK_64 + } else { + res + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Class { + Normal, + Subnormal, + Zero, + Infinite, + NaN, +} + +pub trait Classifier { + fn classify(&self) -> Class; +} + +impl Classifier for u32 { + fn classify(&self) -> Class { + let exponent = self & EXPONENT_MASK_32; + let mantissa = self & MANTISSA_MASK_32; + + match (exponent, mantissa) { + (0, 0) => Class::Zero, + (0, _) => Class::Subnormal, + (EXPONENT_MASK_32, 0) => Class::Infinite, + (EXPONENT_MASK_32, _) => Class::NaN, + _ => Class::Normal, + } + } +} + +impl Classifier for u64 { + fn classify(&self) -> Class { + let exponent = self & EXPONENT_MASK_64; + let mantissa = self & MANTISSA_MASK_64; + + match (exponent, mantissa) { + (0, 0) => Class::Zero, + (0, _) => Class::Subnormal, + (EXPONENT_MASK_64, 0) => Class::Infinite, + (EXPONENT_MASK_64, _) => Class::NaN, + _ => Class::Normal, + } + } +} + +#[cfg(test)] +mod tests { + use rand_chacha::rand_core::SeedableRng; + + use super::*; + + #[test] + fn test_constants() { + assert_eq!(INF_32, f32::INFINITY.to_bits()); + assert_eq!(NEG_INF_32, f32::NEG_INFINITY.to_bits()); + assert_eq!(INF_64, f64::INFINITY.to_bits()); + assert_eq!(NEG_INF_64, f64::NEG_INFINITY.to_bits()); + } + + #[test] + fn test_classify() { + // for 32-bit floats + assert_eq!((-0f32).to_bits().classify(), Class::Zero); + assert_eq!(0u32.classify(), Class::Zero); + assert_eq!(1f32.to_bits().classify(), Class::Normal); + assert_eq!(INF_32.classify(), Class::Infinite); + assert_eq!(NEG_INF_32.classify(), Class::Infinite); + + // for 64-bit floats + assert_eq!((-0f64).to_bits().classify(), Class::Zero); + assert_eq!(0u64.classify(), Class::Zero); + assert_eq!(1f64.to_bits().classify(), Class::Normal); + assert_eq!(INF_64.classify(), Class::Infinite); + assert_eq!(NEG_INF_64.classify(), Class::Infinite); + + // random floats + let mut rng = rand_chacha::ChaChaRng::seed_from_u64(123456); + for _ in 0..1000 { + assert_eq!(random_subnormal_32(&mut rng).classify(), Class::Subnormal); + assert_eq!(random_nan_32(&mut rng).classify(), Class::NaN); + + assert_eq!(random_subnormal_64(&mut rng).classify(), Class::Subnormal); + assert_eq!(random_nan_64(&mut rng).classify(), Class::NaN); + } + } +} diff --git a/contracts/floaty/src/instructions.rs b/contracts/floaty/src/instructions.rs new file mode 100644 index 0000000000..d76fa28b19 --- /dev/null +++ b/contracts/floaty/src/instructions.rs @@ -0,0 +1,551 @@ +use cosmwasm_schema::cw_serde; +use rand_chacha::rand_core::RngCore; + +use crate::floats::{random_f32, random_f64}; + +/// Not intended for direct usage +macro_rules! run_instr { + ($instr:expr, $input:expr, $input_ty:ty, $return_ty:ty) => {{ + let input: $input_ty = $input; + let ret: $return_ty; + unsafe { + core::arch::asm!("local.get {0}", $instr, "local.set {1}", in(local) input, out(local) ret) + }; + ret + }}; + ($instr:expr, $input1:expr, $input1_ty:ty, $input2:expr, $input2_ty:ty, $return_ty:ty) => {{ + let input1: $input1_ty = $input1; + let input2: $input2_ty = $input2; + let ret: $return_ty; + unsafe { + core::arch::asm!("local.get {0}", "local.get {1}", $instr, "local.set {2}", in(local) input1, in(local) input2, out(local) ret) + }; + ret + }}; +} +pub(crate) use run_instr; + +/// Helper to run a single WebAssembly instruction in a type-safe way +macro_rules! run { + ("f32.eq", $input1:expr, $input2:expr) => { + $crate::instructions::run_instr!("f32.eq", $input1, f32, $input2, f32, u32) + }; + ("f32.ne", $input1:expr, $input2:expr) => { + $crate::instructions::run_instr!("f32.ne", $input1, f32, $input2, f32, u32) + }; + ("f32.lt", $input1:expr, $input2:expr) => { + $crate::instructions::run_instr!("f32.lt", $input1, f32, $input2, f32, u32) + }; + ("f32.gt", $input1:expr, $input2:expr) => { + $crate::instructions::run_instr!("f32.gt", $input1, f32, $input2, f32, u32) + }; + ("f32.le", $input1:expr, $input2:expr) => { + $crate::instructions::run_instr!("f32.le", $input1, f32, $input2, f32, u32) + }; + ("f32.ge", $input1:expr, $input2:expr) => { + $crate::instructions::run_instr!("f32.ge", $input1, f32, $input2, f32, u32) + }; + ("f64.eq", $input1:expr, $input2:expr) => { + $crate::instructions::run_instr!("f64.eq", $input1, f64, $input2, f64, u32) + }; + ("f64.ne", $input1:expr, $input2:expr) => { + $crate::instructions::run_instr!("f64.ne", $input1, f64, $input2, f64, u32) + }; + ("f64.lt", $input1:expr, $input2:expr) => { + $crate::instructions::run_instr!("f64.lt", $input1, f64, $input2, f64, u32) + }; + ("f64.gt", $input1:expr, $input2:expr) => { + $crate::instructions::run_instr!("f64.gt", $input1, f64, $input2, f64, u32) + }; + ("f64.le", $input1:expr, $input2:expr) => { + $crate::instructions::run_instr!("f64.le", $input1, f64, $input2, f64, u32) + }; + ("f64.ge", $input1:expr, $input2:expr) => { + $crate::instructions::run_instr!("f64.ge", $input1, f64, $input2, f64, u32) + }; + // + ("f32.abs", $input:expr) => { + $crate::instructions::run_instr!("f32.abs", $input, f32, f32) + }; + ("f32.neg", $input:expr) => { + $crate::instructions::run_instr!("f32.neg", $input, f32, f32) + }; + ("f32.ceil", $input:expr) => { + $crate::instructions::run_instr!("f32.ceil", $input, f32, f32) + }; + ("f32.floor", $input:expr) => { + $crate::instructions::run_instr!("f32.floor", $input, f32, f32) + }; + ("f32.trunc", $input:expr) => { + $crate::instructions::run_instr!("f32.trunc", $input, f32, f32) + }; + ("f32.nearest", $input:expr) => { + $crate::instructions::run_instr!("f32.nearest", $input, f32, f32) + }; + ("f32.sqrt", $input:expr) => { + $crate::instructions::run_instr!("f32.sqrt", $input, f32, f32) + }; + ("f32.add", $input1:expr, $input2:expr) => { + $crate::instructions::run_instr!("f32.add", $input1, f32, $input2, f32, f32) + }; + ("f32.sub", $input1:expr, $input2:expr) => { + $crate::instructions::run_instr!("f32.sub", $input1, f32, $input2, f32, f32) + }; + ("f32.mul", $input1:expr, $input2:expr) => { + $crate::instructions::run_instr!("f32.mul", $input1, f32, $input2, f32, f32) + }; + ("f32.div", $input1:expr, $input2:expr) => { + $crate::instructions::run_instr!("f32.div", $input1, f32, $input2, f32, f32) + }; + ("f32.min", $input1:expr, $input2:expr) => { + $crate::instructions::run_instr!("f32.min", $input1, f32, $input2, f32, f32) + }; + ("f32.max", $input1:expr, $input2:expr) => { + $crate::instructions::run_instr!("f32.max", $input1, f32, $input2, f32, f32) + }; + ("f32.copysign", $input1:expr, $input2:expr) => { + $crate::instructions::run_instr!("f32.copysign", $input1, f32, $input2, f32, f32) + }; + ("f64.abs", $input:expr) => { + $crate::instructions::run_instr!("f64.abs", $input, f64, f64) + }; + ("f64.neg", $input:expr) => { + $crate::instructions::run_instr!("f64.neg", $input, f64, f64) + }; + ("f64.ceil", $input:expr) => { + $crate::instructions::run_instr!("f64.ceil", $input, f64, f64) + }; + ("f64.floor", $input:expr) => { + $crate::instructions::run_instr!("f64.floor", $input, f64, f64) + }; + ("f64.trunc", $input:expr) => { + $crate::instructions::run_instr!("f64.trunc", $input, f64, f64) + }; + ("f64.nearest", $input:expr) => { + $crate::instructions::run_instr!("f64.nearest", $input, f64, f64) + }; + ("f64.sqrt", $input:expr) => { + $crate::instructions::run_instr!("f64.sqrt", $input, f64, f64) + }; + ("f64.add", $input1:expr, $input2:expr) => { + $crate::instructions::run_instr!("f64.add", $input1, f64, $input2, f64, f64) + }; + ("f64.sub", $input1:expr, $input2:expr) => { + $crate::instructions::run_instr!("f64.sub", $input1, f64, $input2, f64, f64) + }; + ("f64.mul", $input1:expr, $input2:expr) => { + $crate::instructions::run_instr!("f64.mul", $input1, f64, $input2, f64, f64) + }; + ("f64.div", $input1:expr, $input2:expr) => { + $crate::instructions::run_instr!("f64.div", $input1, f64, $input2, f64, f64) + }; + ("f64.min", $input1:expr, $input2:expr) => { + $crate::instructions::run_instr!("f64.min", $input1, f64, $input2, f64, f64) + }; + ("f64.max", $input1:expr, $input2:expr) => { + $crate::instructions::run_instr!("f64.max", $input1, f64, $input2, f64, f64) + }; + ("f64.copysign", $input1:expr, $input2:expr) => { + $crate::instructions::run_instr!("f64.copysign", $input1, f64, $input2, f64, f64) + }; + // + ("i32.trunc_f32_s", $input:expr) => { + $crate::instructions::run_instr!("i32.trunc_f32_s", $input, f32, i32) + }; + ("i32.trunc_f32_u", $input:expr) => { + $crate::instructions::run_instr!("i32.trunc_f32_u", $input, f32, u32) + }; + ("i32.trunc_f64_s", $input:expr) => { + $crate::instructions::run_instr!("i32.trunc_f64_s", $input, f64, i32) + }; + ("i32.trunc_f64_u", $input:expr) => { + $crate::instructions::run_instr!("i32.trunc_f64_u", $input, f64, u32) + }; + // + ("i64.trunc_f32_s", $input:expr) => { + $crate::instructions::run_instr!("i64.trunc_f32_s", $input, f32, i64) + }; + ("i64.trunc_f32_u", $input:expr) => { + $crate::instructions::run_instr!("i64.trunc_f32_u", $input, f32, u64) + }; + ("i64.trunc_f64_s", $input:expr) => { + $crate::instructions::run_instr!("i64.trunc_f64_s", $input, f64, i64) + }; + ("i64.trunc_f64_u", $input:expr) => { + $crate::instructions::run_instr!("i64.trunc_f64_u", $input, f64, u64) + }; + // + ("f32.convert_i32_s", $input:expr) => { + $crate::instructions::run_instr!("f32.convert_i32_s", $input, i32, f32) + }; + ("f32.convert_i32_u", $input:expr) => { + $crate::instructions::run_instr!("f32.convert_i32_u", $input, u32, f32) + }; + ("f32.convert_i64_s", $input:expr) => { + $crate::instructions::run_instr!("f32.convert_i64_s", $input, i64, f32) + }; + ("f32.convert_i64_u", $input:expr) => { + $crate::instructions::run_instr!("f32.convert_i64_u", $input, u64, f32) + }; + ("f32.demote_f64", $input:expr) => { + $crate::instructions::run_instr!("f32.demote_f64", $input, f64, f32) + }; + ("f64.convert_i32_s", $input:expr) => { + $crate::instructions::run_instr!("f64.convert_i32_s", $input, i32, f64) + }; + ("f64.convert_i32_u", $input:expr) => { + $crate::instructions::run_instr!("f64.convert_i32_u", $input, u32, f64) + }; + ("f64.convert_i64_s", $input:expr) => { + $crate::instructions::run_instr!("f64.convert_i64_s", $input, i64, f64) + }; + ("f64.convert_i64_u", $input:expr) => { + $crate::instructions::run_instr!("f64.convert_i64_u", $input, u64, f64) + }; + ("f64.promote_f32", $input:expr) => { + $crate::instructions::run_instr!("f64.promote_f32", $input, f32, f64) + }; + // + ("i32.reinterpret_f32", $input:expr) => { + $crate::instructions::run_instr!("i32.reinterpret_f32", $input, f32, i32) + }; + ("i64.reinterpret_f64", $input:expr) => { + $crate::instructions::run_instr!("i64.reinterpret_f64", $input, f64, i64) + }; + ("f32.reinterpret_i32", $input:expr) => { + $crate::instructions::run_instr!("f32.reinterpret_i32", $input, u32, f32) + }; + ("f64.reinterpret_i64", $input:expr) => { + $crate::instructions::run_instr!("f64.reinterpret_i64", $input, u64, f64) + }; + // + ("i32.trunc_sat_f32_s", $input:expr) => { + $crate::instructions::run_instr!("i32.trunc_sat_f32_s", $input, f32, i32) + }; + ("i32.trunc_sat_f32_u", $input:expr) => { + $crate::instructions::run_instr!("i32.trunc_sat_f32_u", $input, f32, u32) + }; + ("i32.trunc_sat_f64_s", $input:expr) => { + $crate::instructions::run_instr!("i32.trunc_sat_f64_s", $input, f64, i32) + }; + ("i32.trunc_sat_f64_u", $input:expr) => { + $crate::instructions::run_instr!("i32.trunc_sat_f64_u", $input, f64, u32) + }; + ("i64.trunc_sat_f32_s", $input:expr) => { + $crate::instructions::run_instr!("i64.trunc_sat_f32_s", $input, f32, i64) + }; + ("i64.trunc_sat_f32_u", $input:expr) => { + $crate::instructions::run_instr!("i64.trunc_sat_f32_u", $input, f32, u64) + }; + ("i64.trunc_sat_f64_s", $input:expr) => { + $crate::instructions::run_instr!("i64.trunc_sat_f64_s", $input, f64, i64) + }; + ("i64.trunc_sat_f64_u", $input:expr) => { + $crate::instructions::run_instr!("i64.trunc_sat_f64_u", $input, f64, u64) + }; +} +pub(crate) use run; + +#[cw_serde] +pub enum Value { + U32(u32), + U64(u64), + F32(u32), + F64(u64), +} + +impl Value { + pub fn u32(&self) -> u32 { + match self { + Self::U32(x) => *x, + v => panic!("expected u32, got {:?}", v), + } + } + + pub fn u64(&self) -> u64 { + match self { + Self::U64(x) => *x, + v => panic!("expected u64, got {:?}", v), + } + } + + pub fn f32(&self) -> f32 { + match self { + Self::F32(x) => f32::from_bits(*x), + v => panic!("expected f32, got {:?}", v), + } + } + + pub fn f64(&self) -> f64 { + match self { + Self::F64(x) => f64::from_bits(*x), + v => panic!("expected f64, got {:?}", v), + } + } +} + +/// Runs the given instruction with random inputs +#[cfg(target_arch = "wasm32")] +pub fn run_instruction(instr: &str, args: &[Value]) -> Value { + use Value::*; + + let arg1 = || args.get(0).unwrap(); + let arg2 = || args.get(0).unwrap(); + + match instr { + "f32.eq" => U32(run!("f32.eq", arg1().f32(), arg2().f32())), + "f32.ne" => U32(run!("f32.ne", arg1().f32(), arg2().f32())), + "f32.lt" => U32(run!("f32.lt", arg1().f32(), arg2().f32())), + "f32.gt" => U32(run!("f32.gt", arg1().f32(), arg2().f32())), + "f32.le" => U32(run!("f32.le", arg1().f32(), arg2().f32())), + "f32.ge" => U32(run!("f32.ge", arg1().f32(), arg2().f32())), + "f64.eq" => U32(run!("f64.eq", arg1().f64(), arg2().f64())), + "f64.ne" => U32(run!("f64.ne", arg1().f64(), arg2().f64())), + "f64.lt" => U32(run!("f64.lt", arg1().f64(), arg2().f64())), + "f64.gt" => U32(run!("f64.gt", arg1().f64(), arg2().f64())), + "f64.le" => U32(run!("f64.le", arg1().f64(), arg2().f64())), + "f64.ge" => U32(run!("f64.ge", arg1().f64(), arg2().f64())), + // + "f32.abs" => U32(run!("f32.abs", arg1().f32()).to_bits()), + "f32.neg" => U32(run!("f32.neg", arg1().f32()).to_bits()), + "f32.ceil" => U32(run!("f32.ceil", arg1().f32()).to_bits()), + "f32.floor" => U32(run!("f32.floor", arg1().f32()).to_bits()), + "f32.trunc" => U32(run!("f32.trunc", arg1().f32()).to_bits()), + "f32.nearest" => U32(run!("f32.nearest", arg1().f32()).to_bits()), + "f32.sqrt" => U32(run!("f32.sqrt", arg1().f32()).to_bits()), + "f32.add" => U32(run!("f32.add", arg1().f32(), arg2().f32()).to_bits()), + "f32.sub" => U32(run!("f32.sub", arg1().f32(), arg2().f32()).to_bits()), + "f32.mul" => U32(run!("f32.mul", arg1().f32(), arg2().f32()).to_bits()), + "f32.div" => U32(run!("f32.div", arg1().f32(), arg2().f32()).to_bits()), + "f32.min" => U32(run!("f32.min", arg1().f32(), arg2().f32()).to_bits()), + "f32.max" => U32(run!("f32.max", arg1().f32(), arg2().f32()).to_bits()), + "f32.copysign" => U32(run!("f32.copysign", arg1().f32(), arg2().f32()).to_bits()), + "f64.abs" => U64(run!("f64.abs", arg1().f64()).to_bits()), + "f64.neg" => U64(run!("f64.neg", arg1().f64()).to_bits()), + "f64.ceil" => U64(run!("f64.ceil", arg1().f64()).to_bits()), + "f64.floor" => U64(run!("f64.floor", arg1().f64()).to_bits()), + "f64.trunc" => U64(run!("f64.trunc", arg1().f64()).to_bits()), + "f64.nearest" => U64(run!("f64.nearest", arg1().f64()).to_bits()), + "f64.sqrt" => U64(run!("f64.sqrt", arg1().f64()).to_bits()), + "f64.add" => U64(run!("f64.add", arg1().f64(), arg2().f64()).to_bits()), + "f64.sub" => U64(run!("f64.sub", arg1().f64(), arg2().f64()).to_bits()), + "f64.mul" => U64(run!("f64.mul", arg1().f64(), arg2().f64()).to_bits()), + "f64.div" => U64(run!("f64.div", arg1().f64(), arg2().f64()).to_bits()), + "f64.min" => U64(run!("f64.min", arg1().f64(), arg2().f64()).to_bits()), + "f64.max" => U64(run!("f64.max", arg1().f64(), arg2().f64()).to_bits()), + "f64.copysign" => U64(run!("f64.copysign", arg1().f64(), arg2().f64()).to_bits()), + // + "i32.trunc_f32_s" => U32(run!("i32.trunc_f32_s", arg1().f32()) as u32), + "i32.trunc_f32_u" => U32(run!("i32.trunc_f32_u", arg1().f32())), + "i32.trunc_f64_s" => U32(run!("i32.trunc_f64_s", arg1().f64()) as u32), + "i32.trunc_f64_u" => U32(run!("i32.trunc_f64_u", arg1().f64())), + // + "i64.trunc_f32_s" => U64(run!("i64.trunc_f32_s", arg1().f32()) as u64), + "i64.trunc_f32_u" => U64(run!("i64.trunc_f32_u", arg1().f32())), + "i64.trunc_f64_s" => U64(run!("i64.trunc_f64_s", arg1().f64()) as u64), + "i64.trunc_f64_u" => U64(run!("i64.trunc_f64_u", arg1().f64())), + // + "f32.convert_i32_s" => U32(run!("f32.convert_i32_s", arg1().u32() as i32).to_bits()), + "f32.convert_i32_u" => U32(run!("f32.convert_i32_u", arg1().u32()).to_bits()), + "f32.convert_i64_s" => U32(run!("f32.convert_i64_s", arg1().u64() as i64).to_bits()), + "f32.convert_i64_u" => U32(run!("f32.convert_i64_u", arg1().u64()).to_bits()), + "f32.demote_f64" => U32(run!("f32.demote_f64", arg1().f64()).to_bits()), + "f64.convert_i32_s" => U64(run!("f64.convert_i32_s", arg1().u32() as i32).to_bits()), + "f64.convert_i32_u" => U64(run!("f64.convert_i32_u", arg1().u32()).to_bits()), + "f64.convert_i64_s" => U64(run!("f64.convert_i64_s", arg1().u64() as i64).to_bits()), + "f64.convert_i64_u" => U64(run!("f64.convert_i64_u", arg1().u64()).to_bits()), + "f64.promote_f32" => U64(run!("f64.promote_f32", arg1().f32()).to_bits()), + // + "i32.reinterpret_f32" => U32(run!("i32.reinterpret_f32", arg1().f32()) as u32), + "i64.reinterpret_f64" => U64(run!("i64.reinterpret_f64", arg1().f64()) as u64), + "f32.reinterpret_i32" => U32(run!("f32.reinterpret_i32", arg1().u32()).to_bits() as u32), + "f64.reinterpret_i64" => U64(run!("f64.reinterpret_i64", arg1().u64()).to_bits() as u64), + // + "i32.trunc_sat_f32_s" => U32(run!("i32.trunc_sat_f32_s", arg1().f32()) as u32), + "i32.trunc_sat_f32_u" => U32(run!("i32.trunc_sat_f32_u", arg1().f32()) as u32), + "i32.trunc_sat_f64_s" => U32(run!("i32.trunc_sat_f64_s", arg1().f64()) as u32), + "i32.trunc_sat_f64_u" => U32(run!("i32.trunc_sat_f64_u", arg1().f64()) as u32), + "i64.trunc_sat_f32_s" => U64(run!("i64.trunc_sat_f32_s", arg1().f32()) as u64), + "i64.trunc_sat_f32_u" => U64(run!("i64.trunc_sat_f32_u", arg1().f32()) as u64), + "i64.trunc_sat_f64_s" => U64(run!("i64.trunc_sat_f64_s", arg1().f64()) as u64), + "i64.trunc_sat_f64_u" => U64(run!("i64.trunc_sat_f64_u", arg1().f64()) as u64), + _ => panic!("unknown instruction: {}", instr), + } +} + +pub fn random_args_for(instr: &str, rng: &mut impl RngCore) -> Vec { + let a = random_f32(rng); + let b = random_f32(rng); + let c = random_f64(rng); + let d = random_f64(rng); + let e = rng.next_u32(); + let f = rng.next_u64(); + + use Value::*; + + let f32x2 = vec![F32(a.to_bits()), F32(b.to_bits())]; + let f64x2 = vec![F64(c.to_bits()), F64(d.to_bits())]; + let f32 = vec![F32(a.to_bits())]; + let f64 = vec![F64(c.to_bits())]; + let u32 = vec![U32(e)]; + let u64 = vec![U64(f)]; + + match instr { + "f32.eq" => f32x2, + "f32.ne" => f32x2, + "f32.lt" => f32x2, + "f32.gt" => f32x2, + "f32.le" => f32x2, + "f32.ge" => f32x2, + "f64.eq" => f64x2, + "f64.ne" => f64x2, + "f64.lt" => f64x2, + "f64.gt" => f64x2, + "f64.le" => f64x2, + "f64.ge" => f64x2, + // + "f32.abs" => f32, + "f32.neg" => f32, + "f32.ceil" => f32, + "f32.floor" => f32, + "f32.trunc" => f32, + "f32.nearest" => f32, + "f32.sqrt" => f32, + "f32.add" => f32x2, + "f32.sub" => f32x2, + "f32.mul" => f32x2, + "f32.div" => f32x2, + "f32.min" => f32x2, + "f32.max" => f32x2, + "f32.copysign" => f32x2, + "f64.abs" => f64, + "f64.neg" => f64, + "f64.ceil" => f64, + "f64.floor" => f64, + "f64.trunc" => f64, + "f64.nearest" => f64, + "f64.sqrt" => f64, + "f64.add" => f64x2, + "f64.sub" => f64x2, + "f64.mul" => f64x2, + "f64.div" => f64x2, + "f64.min" => f64x2, + "f64.max" => f64x2, + "f64.copysign" => f64x2, + // + "i32.trunc_f32_s" => f32, + "i32.trunc_f32_u" => f32, + "i32.trunc_f64_s" => f64, + "i32.trunc_f64_u" => f64, + // + "i64.trunc_f32_s" => f32, + "i64.trunc_f32_u" => f32, + "i64.trunc_f64_s" => f64, + "i64.trunc_f64_u" => f64, + // + "f32.convert_i32_s" => u32, + "f32.convert_i32_u" => u32, + "f32.convert_i64_s" => u64, + "f32.convert_i64_u" => u64, + "f32.demote_f64" => f64, + "f64.convert_i32_s" => u32, + "f64.convert_i32_u" => u32, + "f64.convert_i64_s" => u64, + "f64.convert_i64_u" => u64, + "f64.promote_f32" => f32, + // + "i32.reinterpret_f32" => f32, + "i64.reinterpret_f64" => f64, + "f32.reinterpret_i32" => u32, + "f64.reinterpret_i64" => u64, + // + "i32.trunc_sat_f32_s" => f32, + "i32.trunc_sat_f32_u" => f32, + "i32.trunc_sat_f64_s" => f64, + "i32.trunc_sat_f64_u" => f64, + "i64.trunc_sat_f32_s" => f32, + "i64.trunc_sat_f32_u" => f32, + "i64.trunc_sat_f64_s" => f64, + "i64.trunc_sat_f64_u" => f64, + _ => panic!("unknown instruction: {}", instr), + } +} + +pub const FLOAT_INSTRUCTIONS: [&'static str; 70] = [ + "f32.eq", + "f32.ne", + "f32.lt", + "f32.gt", + "f32.le", + "f32.ge", + "f64.eq", + "f64.ne", + "f64.lt", + "f64.gt", + "f64.le", + "f64.ge", + // + "f32.abs", + "f32.neg", + "f32.ceil", + "f32.floor", + "f32.trunc", + "f32.nearest", + "f32.sqrt", + "f32.add", + "f32.sub", + "f32.mul", + "f32.div", + "f32.min", + "f32.max", + "f32.copysign", + "f64.abs", + "f64.neg", + "f64.ceil", + "f64.floor", + "f64.trunc", + "f64.nearest", + "f64.sqrt", + "f64.add", + "f64.sub", + "f64.mul", + "f64.div", + "f64.min", + "f64.max", + "f64.copysign", + // + "i32.trunc_f32_s", + "i32.trunc_f32_u", + "i32.trunc_f64_s", + "i32.trunc_f64_u", + // + "i64.trunc_f32_s", + "i64.trunc_f32_u", + "i64.trunc_f64_s", + "i64.trunc_f64_u", + // + "f32.convert_i32_s", + "f32.convert_i32_u", + "f32.convert_i64_s", + "f32.convert_i64_u", + "f32.demote_f64", + "f64.convert_i32_s", + "f64.convert_i32_u", + "f64.convert_i64_s", + "f64.convert_i64_u", + "f64.promote_f32", + // + "i32.reinterpret_f32", + "i64.reinterpret_f64", + "f32.reinterpret_i32", + "f64.reinterpret_i64", + // + "i32.trunc_sat_f32_s", + "i32.trunc_sat_f32_u", + "i32.trunc_sat_f64_s", + "i32.trunc_sat_f64_u", + "i64.trunc_sat_f32_s", + "i64.trunc_sat_f32_u", + "i64.trunc_sat_f64_s", + "i64.trunc_sat_f64_u", +]; diff --git a/contracts/floaty/src/lib.rs b/contracts/floaty/src/lib.rs index 0573cc96c3..ba67016d30 100644 --- a/contracts/floaty/src/lib.rs +++ b/contracts/floaty/src/lib.rs @@ -1,4 +1,7 @@ +#![cfg_attr(target_arch = "wasm32", feature(asm_experimental_arch))] + pub mod contract; -mod errors; +pub(crate) mod floats; +mod instructions; pub mod msg; pub mod state; diff --git a/contracts/floaty/src/msg.rs b/contracts/floaty/src/msg.rs index caa8fc4e3e..6baa9a1efe 100644 --- a/contracts/floaty/src/msg.rs +++ b/contracts/floaty/src/msg.rs @@ -1,30 +1,26 @@ use cosmwasm_schema::{cw_serde, QueryResponses}; -#[cw_serde] -pub struct InstantiateMsg { - pub verifier: String, - pub beneficiary: String, -} +use crate::instructions::Value; #[cw_serde] -pub enum ExecuteMsg { - /// Releasing all funds in the contract to the beneficiary. This is the only "proper" action of this demo contract. - Release {}, +pub enum ValueType { + Float, + Int, } #[cw_serde] #[derive(QueryResponses)] pub enum QueryMsg { - /// returns a human-readable representation of the verifier - /// use to ensure query path works in integration tests - #[returns(VerifierResponse)] - Verifier {}, - /// This returns cosmwasm_std::AllBalanceResponse to demo use of the querier - #[returns(cosmwasm_std::AllBalanceResponse)] - OtherBalance { address: String }, -} - -#[cw_serde] -pub struct VerifierResponse { - pub verifier: String, + /// Returns valid random arguments for the given instruction + #[returns(Vec)] + RandomArgsFor { instruction: String, seed: u64 }, + /// Returns a list of all instructions + #[returns(Vec)] + Instructions {}, + /// Runs the given instruction with the given arguments and returns the result + #[returns(Value)] + Run { + instruction: String, + args: Vec, + }, } diff --git a/contracts/floaty/tests/integration.rs b/contracts/floaty/tests/integration.rs index 5d3de04e58..593ae1d1e0 100644 --- a/contracts/floaty/tests/integration.rs +++ b/contracts/floaty/tests/integration.rs @@ -6,7 +6,6 @@ static WASM: &[u8] = include_bytes!("../target/wasm32-unknown-unknown/release/fl // static WASM: &[u8] = include_bytes!("../contract.wasm"); #[test] -#[should_panic(expected = "Float operator detected")] -fn instantiate_fails() { - let mut _deps = mock_instance(WASM, &[]); +fn validation_succeeds() { + mock_instance(WASM, &[]); } diff --git a/packages/check/tests/cosmwasm_check_tests.rs b/packages/check/tests/cosmwasm_check_tests.rs index 0c4301d683..751ac2ae0e 100644 --- a/packages/check/tests/cosmwasm_check_tests.rs +++ b/packages/check/tests/cosmwasm_check_tests.rs @@ -27,16 +27,13 @@ fn invalid_contract_check() -> Result<(), Box> { } #[test] -fn invalid_contract_check_float_operator() -> Result<(), Box> { +fn valid_contract_check_float_operator() -> Result<(), Box> { let mut cmd = Command::cargo_bin("cosmwasm-check")?; cmd.arg("../vm/testdata/floaty.wasm"); cmd.assert() - .failure() - .stdout(predicate::str::contains("Float operator detected")) - .stdout(predicate::str::contains( - "The use of floats is not supported", - )); + .success() + .stdout(predicate::str::contains("pass")); Ok(()) } diff --git a/packages/vm/src/calls.rs b/packages/vm/src/calls.rs index dc24a11a6f..3f3037b8e0 100644 --- a/packages/vm/src/calls.rs +++ b/packages/vm/src/calls.rs @@ -590,11 +590,15 @@ where #[cfg(test)] mod tests { use super::*; - use crate::testing::{mock_env, mock_info, mock_instance}; - use cosmwasm_std::{coins, Empty}; + use crate::testing::{ + mock_env, mock_info, mock_instance, mock_instance_with_options, MockInstanceOptions, + }; + use cosmwasm_std::{coins, from_json, to_json_string, Empty}; + use sha2::{Digest, Sha256}; static CONTRACT: &[u8] = include_bytes!("../testdata/hackatom.wasm"); static CYBERPUNK: &[u8] = include_bytes!("../testdata/cyberpunk.wasm"); + static FLOATY2: &[u8] = include_bytes!("../testdata/floaty_2.0.wasm"); #[test] fn call_instantiate_works() { @@ -734,6 +738,86 @@ mod tests { assert_eq!(query_response.as_slice(), b"{\"verifier\":\"verifies\"}"); } + #[test] + fn float_instrs_are_deterministic() { + #[derive(Debug, serde::Serialize, serde::Deserialize)] + #[serde(rename_all = "snake_case")] + pub enum Value { + U32(u32), + U64(u64), + F32(u32), + F64(u64), + } + + let mut instance = mock_instance_with_options( + FLOATY2, + MockInstanceOptions { + gas_limit: u64::MAX, + memory_limit: None, + ..Default::default() + }, + ); + + // init + let info = mock_info("creator", &[]); + call_instantiate::<_, _, _, Empty>(&mut instance, &mock_env(), &info, br#"{}"#) + .unwrap() + .unwrap(); + + // query instructions + let msg = br#"{"instructions":{}}"#; + let contract_result = call_query(&mut instance, &mock_env(), msg) + .unwrap() + .unwrap(); + let instructions: Vec = from_json(&contract_result).unwrap(); + // little sanity check + assert_eq!(instructions.len(), 70); + + const RUNS_PER_INSTRUCTION: u64 = 150; + let mut hasher = Sha256::new(); + for instr in &instructions { + for seed in 0..RUNS_PER_INSTRUCTION { + // query some input values for the instruction + let args: Vec = from_json( + &call_query( + &mut instance, + &mock_env(), + format!( + r#"{{"random_args_for":{{ "instruction": "{instr}", "seed": {seed}}}}}"# + ) + .as_bytes(), + ) + .unwrap() + .unwrap(), + ) + .unwrap(); + + // build the run message + let args = to_json_string(&args).unwrap(); + let msg: String = format!( + r#"{{"run":{{ + "instruction": "{instr}", + "args": {args} + }}}}"# + ); + // run the instruction + // this might throw a runtime error (e.g. if the instruction traps) + let result = match call_query(&mut instance, &mock_env(), msg.as_bytes()) { + Ok(ContractResult::Ok(r)) => format!("{:?}", from_json::(&r).unwrap()), + Err(VmError::RuntimeErr { msg, .. }) => msg, + e => panic!("unexpected error: {e:?}"), + }; + // add the result to the hash + hasher.update(format!("{instr}{seed}{result}").as_bytes()); + } + } + let hash = Digest::finalize(hasher); + assert_eq!( + hex::encode(hash.as_slice()), + "95f70fa6451176ab04a9594417a047a1e4d8e2ff809609b8f81099496bee2393" + ); + } + #[cfg(feature = "stargate")] mod ibc { use super::*; diff --git a/packages/vm/src/wasm_backend/compile.rs b/packages/vm/src/wasm_backend/compile.rs index 38fd4966d2..d06a1ae055 100644 --- a/packages/vm/src/wasm_backend/compile.rs +++ b/packages/vm/src/wasm_backend/compile.rs @@ -16,9 +16,8 @@ mod tests { static CONTRACT: &[u8] = include_bytes!("../../testdata/floaty.wasm"); #[test] - fn contract_with_floats_fails_check() { + fn contract_with_floats_passes_check() { let engine = make_compiling_engine(None); - let err = compile(&engine, CONTRACT).unwrap_err(); - assert!(err.to_string().contains("Float operator detected:")); + assert!(compile(&engine, CONTRACT).is_ok()); } } diff --git a/packages/vm/src/wasm_backend/engine.rs b/packages/vm/src/wasm_backend/engine.rs index 0761e5968a..25eceee93c 100644 --- a/packages/vm/src/wasm_backend/engine.rs +++ b/packages/vm/src/wasm_backend/engine.rs @@ -54,6 +54,7 @@ pub fn make_compiling_engine(memory_limit: Option) -> Engine { #[cfg(not(feature = "cranelift"))] let mut compiler = Singlepass::default(); + compiler.canonicalize_nans(true); compiler.push_middleware(deterministic); compiler.push_middleware(metering); let mut engine = Engine::from(compiler); diff --git a/packages/vm/src/wasm_backend/gatekeeper.rs b/packages/vm/src/wasm_backend/gatekeeper.rs index bc9c770c94..fbc2ab8b02 100644 --- a/packages/vm/src/wasm_backend/gatekeeper.rs +++ b/packages/vm/src/wasm_backend/gatekeeper.rs @@ -57,7 +57,7 @@ impl Gatekeeper { impl Default for Gatekeeper { fn default() -> Self { Self::new(GatekeeperConfig { - allow_floats: false, + allow_floats: true, allow_feature_bulk_memory_operations: false, allow_feature_reference_types: false, allow_feature_simd: false, @@ -727,7 +727,7 @@ mod tests { } #[test] - fn parser_floats_are_not_supported() { + fn parser_floats_are_supported() { let wasm = wat::parse_str( r#" (module @@ -744,10 +744,7 @@ mod tests { compiler.push_middleware(deterministic); let store = Store::new(compiler); let result = Module::new(&store, wasm); - assert!(result - .unwrap_err() - .to_string() - .contains("Float operator detected:")); + assert!(result.is_ok()); } #[test] diff --git a/packages/vm/testdata/floaty_2.0.wasm b/packages/vm/testdata/floaty_2.0.wasm new file mode 100755 index 0000000000..3dd07a913e Binary files /dev/null and b/packages/vm/testdata/floaty_2.0.wasm differ