diff --git a/.github/workflows/rust-checks.yml b/.github/workflows/rust-checks.yml index 5359683..c7e1823 100644 --- a/.github/workflows/rust-checks.yml +++ b/.github/workflows/rust-checks.yml @@ -6,8 +6,6 @@ on: branches: - main pull_request: - branches: - - main workflow_dispatch: concurrency: diff --git a/Cargo.lock b/Cargo.lock index 1e99f0c..d1060fd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -938,7 +938,7 @@ checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" [[package]] name = "drink" -version = "0.1.2" +version = "0.1.3" dependencies = [ "contract-transcode", "frame-support", @@ -950,11 +950,12 @@ dependencies = [ "parity-scale-codec", "scale-info", "thiserror", + "wat", ] [[package]] name = "drink-cli" -version = "0.1.2" +version = "0.1.3" dependencies = [ "anyhow", "clap", @@ -1929,6 +1930,12 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "leb128" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" + [[package]] name = "libc" version = "0.2.146" @@ -4272,6 +4279,15 @@ version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" +[[package]] +name = "wasm-encoder" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ba64e81215916eaeb48fee292f29401d69235d62d8b8fd92a7b2844ec5ae5f7" +dependencies = [ + "leb128", +] + [[package]] name = "wasm-instrument" version = "0.4.0" @@ -4504,6 +4520,27 @@ dependencies = [ "wasmparser", ] +[[package]] +name = "wast" +version = "64.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a259b226fd6910225aa7baeba82f9d9933b6d00f2ce1b49b80fa4214328237cc" +dependencies = [ + "leb128", + "memchr", + "unicode-width", + "wasm-encoder", +] + +[[package]] +name = "wat" +version = "1.0.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53253d920ab413fca1c7dc2161d601c79b4fdf631d0ba51dd4343bf9b556c3f6" +dependencies = [ + "wast", +] + [[package]] name = "which" version = "4.4.0" diff --git a/Cargo.toml b/Cargo.toml index 8b0f7ac..58ad37d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ homepage = "https://github.com/Cardinal-Cryptography/drink" license = "Apache-2.0" readme = "README.md" repository = "https://github.com/Cardinal-Cryptography/drink" -version = "0.1.2" +version = "0.1.3" [workspace.dependencies] anyhow = { version = "1.0.71" } @@ -30,6 +30,7 @@ parity-scale-codec = { version = "3.6.0" } ratatui = { version = "0.21.0" } scale-info = { version = "2.5.0" } thiserror = { version = "1.0.40" } +wat = { version = "1.0.71" } # Substrate dependencies @@ -43,4 +44,4 @@ sp-core = { version = "22.0.0" } # Local dependencies -drink = { version = "0.1.2", path = "drink" } +drink = { version = "0.1.3", path = "drink" } diff --git a/drink/Cargo.toml b/drink/Cargo.toml index 47b325a..f6c60f8 100644 --- a/drink/Cargo.toml +++ b/drink/Cargo.toml @@ -21,5 +21,8 @@ parity-scale-codec = { workspace = true } scale-info = { workspace = true } thiserror = { workspace = true } +[dev-dependencies] +wat = { workspace = true } + [features] session = ["contract-transcode"] diff --git a/drink/src/contract_api.rs b/drink/src/contract_api.rs index 1a62d6d..b0c5235 100644 --- a/drink/src/contract_api.rs +++ b/drink/src/contract_api.rs @@ -140,3 +140,121 @@ pub fn decode_debug_buffer(buffer: &[u8]) -> Vec { let decoded = buffer.iter().map(|b| *b as char).collect::(); decoded.split('\n').map(|s| s.to_string()).collect() } + +#[cfg(test)] +mod tests { + use frame_support::sp_runtime::traits::Hash; + use pallet_contracts::Origin; + + use super::*; + use crate::{ + chain_api::ChainApi, minimal::RuntimeEvent, MinimalRuntime, DEFAULT_ACTOR, + DEFAULT_GAS_LIMIT, + }; + + fn compile_module(contract_name: &str) -> Vec { + let path = [ + std::env::var("CARGO_MANIFEST_DIR") + .as_deref() + .unwrap_or("drink"), + "/test-resources/", + contract_name, + ".wat", + ] + .concat(); + wat::parse_file(path).expect("Failed to parse wat file") + } + + #[test] + fn can_upload_code() { + let mut sandbox = Sandbox::::new().unwrap(); + let wasm_binary = compile_module("dummy"); + let hash = <::Hashing>::hash(&wasm_binary); + + let result = sandbox.upload_contract(wasm_binary, DEFAULT_ACTOR, None); + + assert!(result.is_ok()); + assert_eq!(hash, result.unwrap().code_hash); + } + + #[test] + fn can_deploy_contract() { + let mut sandbox = Sandbox::::new().unwrap(); + let wasm_binary = compile_module("dummy"); + + let events_before = sandbox.get_current_block_events(); + assert!(events_before.is_empty()); + + let result = sandbox.deploy_contract( + wasm_binary, + 0, + vec![], + vec![], + DEFAULT_ACTOR, + DEFAULT_GAS_LIMIT, + None, + ); + assert!(result.result.is_ok()); + assert!(!result.result.unwrap().result.did_revert()); + + let events = result.events.expect("Drink should collect events"); + let instantiation_event = events.last().expect("There should be an event"); + assert!(matches!( + instantiation_event.event, + RuntimeEvent::Contracts(pallet_contracts::Event::::Instantiated { .. }) + )); + } + + #[test] + fn can_call_contract() { + let mut sandbox = Sandbox::::new().unwrap(); + let wasm_binary = compile_module("dummy"); + + let result = sandbox.deploy_contract( + wasm_binary, + 0, + vec![], + vec![], + DEFAULT_ACTOR, + DEFAULT_GAS_LIMIT, + None, + ); + + let contract_address = result + .result + .expect("Contract should be deployed") + .account_id; + + sandbox.reset_current_block_events(); + + let result = sandbox.call_contract( + contract_address.clone(), + 0, + vec![], + DEFAULT_ACTOR, + DEFAULT_GAS_LIMIT, + None, + ); + assert!(result.result.is_ok()); + assert!(!result.result.unwrap().did_revert()); + + let events = result.events.expect("Drink should collect events"); + assert!(events.len() == 2); + + assert_eq!( + events[0].event, + RuntimeEvent::Contracts(pallet_contracts::Event::::ContractEmitted { + contract: contract_address.clone(), + data: vec![0, 0, 0, 0], + }) + ); + + assert_eq!( + events[1].event, + RuntimeEvent::Contracts(pallet_contracts::Event::::Called { + contract: contract_address, + caller: Origin::Signed(DEFAULT_ACTOR), + }), + ); + } +} diff --git a/drink/src/runtime/minimal.rs b/drink/src/runtime/minimal.rs index b5e8388..198f78c 100644 --- a/drink/src/runtime/minimal.rs +++ b/drink/src/runtime/minimal.rs @@ -12,6 +12,12 @@ use frame_support::{ }; use pallet_contracts::{DefaultAddressGenerator, Frame, Schedule}; +// Re-export all pallets. +pub use frame_system; +pub use pallet_balances; +pub use pallet_contracts; +pub use pallet_timestamp; + type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( diff --git a/drink/test-resources/dummy.wat b/drink/test-resources/dummy.wat new file mode 100644 index 0000000..23eff87 --- /dev/null +++ b/drink/test-resources/dummy.wat @@ -0,0 +1,25 @@ +;; Dummy contract emitting a dummy event. +(module + (import "seal0" "seal_deposit_event" (func $seal_deposit_event (param i32 i32 i32 i32))) + (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) + (import "env" "memory" (memory 1 1)) + + (func (export "deploy")) + + (func (export "call") + ;; emit dummy event + (call $seal_deposit_event + (i32.const 0) ;; The topics buffer + (i32.const 0) ;; The topics buffer's length + (i32.const 8) ;; The data buffer + (i32.const 4) ;; The data buffer's length + ) + + ;; exit with success + (call $seal_return + (i32.const 0) ;; flags + (i32.const 0) ;; returned value + (i32.const 4) ;; length of returned value + ) + ) +)