diff --git a/crates/abi/decoder/src/calldata.rs b/crates/abi/decoder/src/calldata.rs index 0e0385019..432d45651 100644 --- a/crates/abi/decoder/src/calldata.rs +++ b/crates/abi/decoder/src/calldata.rs @@ -8,7 +8,6 @@ use crate::{Cursor, Decoder}; /// Its main usage is by the `svm-sdk` crate for decoding the binary `calldata` into a Rust native values. pub struct CallData { cursor: Cursor, - decoder: Decoder, } @@ -17,7 +16,6 @@ impl CallData { pub fn new(bytes: &[u8]) -> Self { Self { cursor: Cursor::new(bytes), - decoder: Decoder::new(), } } diff --git a/crates/codec/build.sh b/crates/codec/build.sh index 7f637bf1e..b8aa7c4f3 100755 --- a/crates/codec/build.sh +++ b/crates/codec/build.sh @@ -1,2 +1,2 @@ set -e -cargo +nightly build --release --target wasm32-unknown-unknown \ No newline at end of file +cargo +nightly build --release --target wasm32-unknown-unknown \ No newline at end of file diff --git a/crates/codec/examples/test.js b/crates/codec/examples/test.js index 1858af7fa..73700c47e 100644 --- a/crates/codec/examples/test.js +++ b/crates/codec/examples/test.js @@ -124,9 +124,9 @@ function generateAddress(s) { return repeatString(s, 20); } -function encodeCallData(instance, object) { +function encodeInput(instance, object) { const buf = wasmNewBuffer(instance, object); - const result = instanceCall(instance, "wasm_encode_calldata", buf); + const result = instanceCall(instance, "wasm_encode_inputdata", buf); const encoded = loadWasmBufferDataAsJson(instance, result); @@ -136,9 +136,9 @@ function encodeCallData(instance, object) { return encoded; } -function decodeCallData(instance, encodedData) { +function decodeInput(instance, encodedData) { const buf = wasmNewBuffer(instance, encodedData); - const result = instanceCall(instance, "wasm_decode_calldata", buf); + const result = instanceCall(instance, "wasm_decode_inputdata", buf); const json = loadWasmBufferDataAsJson(instance, result); wasmBufferFree(instance, buf); @@ -163,58 +163,58 @@ function binToString(array) { return result; } -describe("Encode Calldata", function () { - function testCallData(instance, abi, data) { +describe("Encode InputData", function () { + function testInputData(instance, abi, data) { const calldata = { abi: abi, data: data, }; - let encoded = encodeCallData(instance, calldata); - let decoded = decodeCallData(instance, encoded); + let encoded = encodeInput(instance, calldata); + let decoded = decodeInput(instance, encoded); assert.deepStrictEqual(decoded, calldata); } it("i8", function () { return compileWasmCodec().then((instance) => { - testCallData(instance, ["i8"], [-10]); + testInputData(instance, ["i8"], [-10]); }); }); it("u8", function () { return compileWasmCodec().then((instance) => { - testCallData(instance, ["u8"], [10]); + testInputData(instance, ["u8"], [10]); }); }); it("i16", function () { return compileWasmCodec().then((instance) => { - testCallData(instance, ["i16"], [-10]); + testInputData(instance, ["i16"], [-10]); }); }); it("u16", function () { return compileWasmCodec().then((instance) => { - testCallData(instance, ["u16"], [10]); + testInputData(instance, ["u16"], [10]); }); }); it("i32", function () { return compileWasmCodec().then((instance) => { - testCallData(instance, ["i32"], [-10]); + testInputData(instance, ["i32"], [-10]); }); }); it("u32", function () { return compileWasmCodec().then((instance) => { - testCallData(instance, ["u32"], [10]); + testInputData(instance, ["u32"], [10]); }); }); it("amount", function () { return compileWasmCodec().then((instance) => { - testCallData(instance, ["amount"], [10]); + testInputData(instance, ["amount"], [10]); }); }); @@ -226,8 +226,8 @@ describe("Encode Calldata", function () { data: [addr], }; - let encoded = encodeCallData(instance, object); - let decoded = decodeCallData(instance, encoded); + let encoded = encodeInput(instance, object); + let decoded = decodeInput(instance, encoded); assert.deepStrictEqual(decoded, { abi: ["address"], @@ -246,8 +246,8 @@ describe("Encode Calldata", function () { data: [[addr1, addr2]], }; - let encoded = encodeCallData(instance, object); - let decoded = decodeCallData(instance, encoded); + let encoded = encodeInput(instance, object); + let decoded = decodeInput(instance, encoded); assert.deepStrictEqual(decoded, { abi: [["address"]], @@ -312,10 +312,7 @@ describe("Deploy Template", function () { const result = instanceCall(instance, "wasm_encode_deploy", buf); const error = loadWasmBufferError(instance, result); - assert.strictEqual( - error, - 'A non-optional field was missing (`name`).' - ); + assert.strictEqual(error, "A non-optional field was missing (`name`)."); wasmBufferFree(instance, buf); wasmBufferFree(instance, result); @@ -369,7 +366,7 @@ describe("Spawn Account", function () { data: [10, 20], }; - let calldata = encodeCallData(instance, object); + let calldata = encodeInput(instance, object); const bytes = encodeSpawn(instance, template, name, calldata["data"]); const json = decodeSpawn(instance, bytes); @@ -398,7 +395,7 @@ describe("Spawn Account", function () { const error = loadWasmBufferError(instance, result); assert.strictEqual( error, - 'The value of a specific field is invalid (`template`).' + "The value of a specific field is invalid (`template`)." ); wasmBufferFree(instance, buf); @@ -408,11 +405,12 @@ describe("Spawn Account", function () { }); describe("Call Account", function () { - function encodeCall(instance, target, calldata) { + function encodeCall(instance, target, verifydata, calldata) { let tx = { version: 0, target: target, func_name: "do_something", + verifydata: verifydata, calldata: calldata, }; @@ -446,19 +444,32 @@ describe("Call Account", function () { return compileWasmCodec().then((instance) => { const target = generateAddress("1020304050"); - const object = { + let verifydata = encodeInput(instance, { + abi: ["bool", "i8"], + data: [true, 5], + }); + + let calldata = encodeInput(instance, { abi: ["i32", "i64"], data: [10, 20], - }; + }); - let calldata = encodeCallData(instance, object); - const bytes = encodeCall(instance, target, calldata["data"]); + const bytes = encodeCall( + instance, + target, + verifydata["data"], + calldata["data"] + ); const json = decodeCall(instance, bytes); assert.deepStrictEqual(json, { version: 0, target: target, func_name: "do_something", + verifydata: { + abi: ["bool", "i8"], + data: [true, 5], + }, calldata: { abi: ["i32", "i64"], data: [10, 20], @@ -476,7 +487,7 @@ describe("Call Account", function () { const error = loadWasmBufferError(instance, result); assert.strictEqual( error, - 'The value of a specific field is invalid (`target`).' + "The value of a specific field is invalid (`target`)." ); wasmBufferFree(instance, buf); diff --git a/crates/codec/src/api/builder/call.rs b/crates/codec/src/api/builder/call.rs index 05bfe11d8..012536ddb 100644 --- a/crates/codec/src/api/builder/call.rs +++ b/crates/codec/src/api/builder/call.rs @@ -9,7 +9,7 @@ pub struct CallBuilder { version: Option, target: Option
, func_name: Option, - // verifydata: Option>, + verifydata: Option>, calldata: Option>, } @@ -26,14 +26,14 @@ pub struct CallBuilder { /// let target = Address::of("@target").into(); /// /// let func_name = "do_work"; -/// // let verifydata = vec![0x10, 0x20, 0x30]; +/// let verifydata = vec![0x10, 0x20, 0x30]; /// let calldata = vec![0x10, 0x20, 0x30]; /// /// let bytes = CallBuilder::new() /// .with_version(0) /// .with_target(&target) /// .with_func(func_name) -/// // .with_verifydata(&verifydata) +/// .with_verifydata(&verifydata) /// .with_calldata(&calldata) /// .build(); /// @@ -43,7 +43,7 @@ pub struct CallBuilder { /// version: 0, /// target, /// func_name: func_name.to_string(), -/// // verifydata, +/// verifydata, /// calldata, /// }; /// @@ -58,7 +58,7 @@ impl CallBuilder { version: None, target: None, func_name: None, - // verifydata: None, + verifydata: None, calldata: None, } } @@ -78,10 +78,10 @@ impl CallBuilder { self } - // pub fn with_verifydata(mut self, verifydata: &[u8]) -> Self { - // self.verifydata = Some(verifydata.to_vec()); - // self - // } + pub fn with_verifydata(mut self, verifydata: &[u8]) -> Self { + self.verifydata = Some(verifydata.to_vec()); + self + } pub fn with_calldata(mut self, calldata: &[u8]) -> Self { self.calldata = Some(calldata.to_vec()); @@ -93,10 +93,10 @@ impl CallBuilder { let target = self.target.unwrap(); let func_name = self.func_name.unwrap(); - // let verifydata = match self.verifydata { - // None => vec![], - // Some(verifydata) => verifydata.to_vec(), - // }; + let verifydata = match self.verifydata { + None => vec![], + Some(verifydata) => verifydata.to_vec(), + }; let calldata = match self.calldata { None => vec![], @@ -107,7 +107,7 @@ impl CallBuilder { version, target, func_name, - // verifydata, + verifydata, calldata, }; diff --git a/crates/codec/src/api/json/call.rs b/crates/codec/src/api/json/call.rs index d7ef046e3..22ea128f2 100644 --- a/crates/codec/src/api/json/call.rs +++ b/crates/codec/src/api/json/call.rs @@ -5,7 +5,7 @@ use std::io::Cursor; use svm_types::Transaction; -use super::calldata::{decode_raw_calldata, DecodedCallData}; +use super::inputdata::{decode_raw_input, DecodedInputData}; use super::serde_types::*; use crate::api::json::{JsonError, JsonSerdeUtils}; @@ -16,10 +16,8 @@ use crate::api::json::{JsonError, JsonSerdeUtils}; /// "version": 0, // number /// "target": "A2FB...", // string /// "func_name": "do_work", // string -/// "verifydata": "", // string -/// "calldata": { -/// ... -/// } +/// "verifydata": {"abi": [], "data": []}, +/// "calldata": {"abi": [], "data": []}, /// } /// /// The `calldata` field can be both encoded and user-friendly form. @@ -61,13 +59,8 @@ pub fn encode_call_raw(json: &str) -> Result, JsonError> { /// ``` pub fn decode_call(json: &str) -> Result { let encoded_call = EncodedData::from_json_str(json)?; - let tx = { - let mut cursor = Cursor::new(&encoded_call.data.0[..]); - crate::call::decode_call(&mut cursor).unwrap() - }; - - // let verifydata = json::bytes_to_str(&tx.verifydata); - // let verifydata = json::decode_calldata(&json!({ "calldata": verifydata }))?; + let mut cursor = Cursor::new(&encoded_call.data.0[..]); + let tx = crate::call::decode_call(&mut cursor).unwrap(); Ok(DecodedCall::from(tx).to_json()) } @@ -77,7 +70,7 @@ struct DecodedCall { version: u16, target: AddressWrapper, func_name: String, - // verifydata: String, + verifydata: EncodedOrDecodedCalldata, calldata: EncodedOrDecodedCalldata, } @@ -86,10 +79,12 @@ impl JsonSerdeUtils for DecodedCall {} impl From for Transaction { fn from(decoded: DecodedCall) -> Self { let target = decoded.target.into(); + Transaction { version: decoded.version, func_name: decoded.func_name, target, + verifydata: decoded.verifydata.encode(), calldata: decoded.calldata.encode(), } } @@ -101,8 +96,12 @@ impl From for DecodedCall { version: tx.version, target: AddressWrapper::from(&tx.target), func_name: tx.func_name.clone(), + verifydata: EncodedOrDecodedCalldata::Decoded( + DecodedInputData::new(&decode_raw_input(tx.verifydata()).unwrap().to_string()) + .unwrap(), + ), calldata: EncodedOrDecodedCalldata::Decoded( - DecodedCallData::new(&decode_raw_calldata(&tx.calldata).unwrap().to_string()) + DecodedInputData::new(&decode_raw_input(tx.calldata()).unwrap().to_string()) .unwrap(), ), } @@ -115,7 +114,7 @@ impl From for DecodedCall { #[serde(untagged)] pub(crate) enum EncodedOrDecodedCalldata { Encoded(HexBlob>), - Decoded(DecodedCallData), + Decoded(DecodedInputData), } impl EncodedOrDecodedCalldata { @@ -202,7 +201,7 @@ mod tests { #[test] fn json_call_missing_calldata() { - let verifydata = json::encode_calldata( + let verifydata = json::encode_inputdata( &json!({ "abi": ["bool", "i8"], "data": [true, 3], @@ -215,7 +214,7 @@ mod tests { "version": 0, "target": "10203040506070809000A0B0C0D0E0F0ABCDEFFF", "func_name": "do_something", - "verifydata": verifydata["calldata"] + "verifydata": verifydata["data"] }) .to_string(); @@ -230,7 +229,7 @@ mod tests { #[test] fn json_call_valid() { - let calldata = json::encode_calldata( + let calldata = json::encode_inputdata( &json!({ "abi": ["i32", "i64"], "data": [10, 20], @@ -239,11 +238,20 @@ mod tests { ) .unwrap(); + let verifydata = json::encode_inputdata( + &json!({ + "abi": ["bool", "i8"], + "data": [true, 3], + }) + .to_string(), + ) + .unwrap(); + let json = json!({ "version": 0, "target": "10203040506070809000A0B0C0D0E0F0ABCDEFFF", "func_name": "do_something", - // "verifydata": verifydata["calldata"], + "verifydata": verifydata["data"], "calldata": calldata["data"], }) .to_string(); @@ -257,10 +265,10 @@ mod tests { "version": 0, "target": "10203040506070809000A0B0C0D0E0F0ABCDEFFF", "func_name": "do_something", - // "verifydata": { - // "abi": ["bool", "i8"], - // "data": [true, 3] - // }, + "verifydata": { + "abi": ["bool", "i8"], + "data": [true, 3] + }, "calldata": { "abi": ["i32", "i64"], "data": [10, 20] diff --git a/crates/codec/src/api/json/calldata.rs b/crates/codec/src/api/json/inputdata.rs similarity index 95% rename from crates/codec/src/api/json/calldata.rs rename to crates/codec/src/api/json/inputdata.rs index 6d36ef28f..f22653ada 100644 --- a/crates/codec/src/api/json/calldata.rs +++ b/crates/codec/src/api/json/inputdata.rs @@ -13,7 +13,7 @@ use super::serde_types::{AddressWrapper, EncodedData, HexBlob}; use super::JsonSerdeUtils; use crate::api::json::JsonError; -/// Given a `Calldata` JSON, encodes it into a binary `Calldata` +/// Given an `Input Data` JSON, encodes it into a binary `Input Data` /// and returns the result wrapped with a JSON. /// /// ```json @@ -21,19 +21,19 @@ use crate::api::json::JsonError; /// "data": "FFC103..." /// } /// ``` -pub fn encode_calldata(json: &str) -> Result { - let decoded = DecodedCallData::new(json)?; +pub fn encode_inputdata(json: &str) -> Result { + let decoded = DecodedInputData::new(json)?; let calldata = HexBlob(decoded.encode().unwrap()); Ok(EncodedData { data: calldata }.to_json()) } -pub fn decode_raw_calldata(data: &[u8]) -> Result { +pub fn decode_raw_input(data: &[u8]) -> Result { let calldata = CallData::new(data); Ok(calldata_to_json(calldata)) } /// Given a binary `Calldata` (wrapped within a JSON), decodes it into a JSON -pub fn decode_calldata(json: &str) -> Result { +pub fn decode_inputdata(json: &str) -> Result { let encoded = EncodedData::from_json_str(json)?; let calldata = CallData::new(&encoded.data.0); Ok(calldata_to_json(calldata)) @@ -41,12 +41,12 @@ pub fn decode_calldata(json: &str) -> Result { #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] -pub(crate) struct DecodedCallData { +pub(crate) struct DecodedInputData { abi: Vec, data: Vec, } -impl DecodedCallData { +impl DecodedInputData { pub fn new(json: &str) -> Result { let decoded = Self::from_json_str(json)?; @@ -85,7 +85,7 @@ impl DecodedCallData { } } -impl JsonSerdeUtils for DecodedCallData {} +impl JsonSerdeUtils for DecodedInputData {} fn calldata_to_json(mut calldata: CallData) -> Json { let mut abi = vec![]; @@ -326,8 +326,8 @@ mod tests { ($abi:expr, $data:expr) => {{ let json = json!({"abi": $abi, "data": $data }); - let encoded = encode_calldata(&json.to_string()).unwrap(); - let decoded = decode_calldata(&encoded.to_string()).unwrap(); + let encoded = encode_inputdata(&json.to_string()).unwrap(); + let decoded = decode_inputdata(&encoded.to_string()).unwrap(); assert_eq!(decoded, json); }} diff --git a/crates/codec/src/api/json/mod.rs b/crates/codec/src/api/json/mod.rs index 517f5b114..52874b725 100644 --- a/crates/codec/src/api/json/mod.rs +++ b/crates/codec/src/api/json/mod.rs @@ -1,18 +1,18 @@ //! JSON API mod call; -mod calldata; mod deploy; mod error; +mod inputdata; mod receipt; mod spawn; pub(crate) mod serde_types; pub use call::{decode_call, encode_call, encode_call_raw}; -pub use calldata::{decode_calldata, encode_calldata}; pub use deploy::deploy_template; pub use error::JsonError; +pub use inputdata::{decode_inputdata, encode_inputdata}; pub use receipt::decode_receipt; pub use spawn::{decode_spawn, encode_spawn}; diff --git a/crates/codec/src/api/json/spawn.rs b/crates/codec/src/api/json/spawn.rs index d8679d06d..f57dcd94a 100644 --- a/crates/codec/src/api/json/spawn.rs +++ b/crates/codec/src/api/json/spawn.rs @@ -6,7 +6,7 @@ use std::io::Cursor; use svm_types::{Account, SpawnAccount}; use super::call::EncodedOrDecodedCalldata; -use super::calldata::DecodedCallData; +use super::inputdata::DecodedInputData; use super::serde_types::{EncodedData, TemplateAddrWrapper}; use super::{JsonError, JsonSerdeUtils}; use crate::spawn; @@ -56,7 +56,7 @@ impl JsonSerdeUtils for DecodedSpawn {} impl From for DecodedSpawn { fn from(spawn: SpawnAccount) -> Self { let template_addr = TemplateAddrWrapper(spawn.template_addr().clone()); - let decoded_calldata = super::calldata::decode_raw_calldata(&spawn.calldata).unwrap(); + let decoded_calldata = super::inputdata::decode_raw_input(&spawn.calldata).unwrap(); Self { version: spawn.version, @@ -64,7 +64,7 @@ impl From for DecodedSpawn { template_addr, ctor_name: spawn.ctor_name, calldata: EncodedOrDecodedCalldata::Decoded( - DecodedCallData::new(&decoded_calldata.to_string()) + DecodedInputData::new(&decoded_calldata.to_string()) .expect("Invalid JSON immediately after serialization"), ), } @@ -177,7 +177,7 @@ mod tests { #[test] fn json_spawn_valid() { - let calldata = json::encode_calldata( + let calldata = json::encode_inputdata( &json!({ "abi": ["i32", "i64"], "data": [10, 20] diff --git a/crates/codec/src/api/wasm/call.rs b/crates/codec/src/api/wasm/call.rs index ef0390aa7..06d89eab4 100644 --- a/crates/codec/src/api/wasm/call.rs +++ b/crates/codec/src/api/wasm/call.rs @@ -38,13 +38,16 @@ mod test { fn wasm_call_valid() { let target = "1122334455667788990011223344556677889900"; - // let verifydata = api::json::encode_calldata(&json!({ - // "abi": ["bool", "i8"], - // "data": [true, 3] - // })) - // .unwrap(); + let verifydata = api::json::encode_inputdata( + &json!({ + "abi": ["bool", "i8"], + "data": [true, 3] + }) + .to_string(), + ) + .unwrap(); - let calldata = api::json::encode_calldata( + let calldata = api::json::encode_inputdata( &json!({ "abi": ["i32", "i64"], "data": [10, 20] @@ -57,7 +60,7 @@ mod test { "version": 1, "target": target, "func_name": "do_something", - // "verifydata": verifydata["calldata"], + "verifydata": verifydata["data"], "calldata": calldata["data"] }); @@ -88,10 +91,10 @@ mod test { "version": 1, "target": target, "func_name": "do_something", - // "verifydata": { - // "abi": ["bool", "i8"], - // "data": [true, 3], - // }, + "verifydata": { + "abi": ["bool", "i8"], + "data": [true, 3], + }, "calldata": { "abi": ["i32", "i64"], "data": [10, 20], diff --git a/crates/codec/src/api/wasm/calldata.rs b/crates/codec/src/api/wasm/inputdata.rs similarity index 68% rename from crates/codec/src/api/wasm/calldata.rs rename to crates/codec/src/api/wasm/inputdata.rs index 81f94d389..a45aa10c2 100644 --- a/crates/codec/src/api/wasm/calldata.rs +++ b/crates/codec/src/api/wasm/inputdata.rs @@ -1,22 +1,21 @@ use super::wasm_buf_apply; use crate::{api, api::json::JsonError}; -/// Given an offset to a Wasm buffer holding the data to be encoded -/// to a binary `Calldata`, encodes it and returns an offset to the encoded -/// binary `Calldata` (wrapped within a JSON). -pub fn encode_calldata(offset: usize) -> Result { +/// Given an offset to a Wasm buffer holding the data to be encoded, +/// encodes it and returns an offset to the encoded binary `Input Data` (wrapped within a JSON). +pub fn encode_inputdata(offset: usize) -> Result { wasm_buf_apply(offset, |json: &str| { - let json = api::json::encode_calldata(json)?; + let json = api::json::encode_inputdata(json)?; Ok(api::json::to_bytes(&json)) }) } -/// Given an offset to a Wasm buffer holding a binary `Calldata`, -/// decodes it and returns an offset to be decoded `Calldata` (wrapped within a JSON) -pub fn decode_calldata(offset: usize) -> Result { +/// Given an offset to a Wasm buffer holding a binary `Input Data`, +/// decodes it and returns an offset to be decoded `Input Data` (wrapped within a JSON) +pub fn decode_inputdata(offset: usize) -> Result { wasm_buf_apply(offset, |json: &str| { - let json = api::json::decode_calldata(json)?; + let json = api::json::decode_inputdata(json)?; Ok(api::json::to_bytes(&json)) }) @@ -43,7 +42,7 @@ mod test { } #[test] - fn wasm_encode_calldata_valid() { + fn wasm_encode_inputdata_valid() { let json = r#"{ "abi": ["i32", "address"], "data": [10, "102030405060708090A011121314151617181920"] @@ -51,13 +50,13 @@ mod test { // encode let json_buf = to_wasm_buffer(json.as_bytes()); - let calldata = encode_calldata(json_buf).unwrap(); - let data = wasm_buffer_data(calldata); + let inputdata = encode_inputdata(json_buf).unwrap(); + let data = wasm_buffer_data(inputdata); assert_eq!(data[0], BUF_OK_MARKER); // decode let data_buf = to_wasm_buffer(&data[1..]); - let res_buf = decode_calldata(data_buf).unwrap(); + let res_buf = decode_inputdata(data_buf).unwrap(); assert_eq!( wasm_buf_as_json(res_buf), @@ -68,17 +67,17 @@ mod test { ); free(json_buf); - free(calldata); + free(inputdata); free(data_buf); free(res_buf); } #[test] - fn wasm_encode_calldata_invalid_json() { + fn wasm_encode_inputdata_invalid_json() { let json = "{"; let json_buf = to_wasm_buffer(json.as_bytes()); - let error_buf = encode_calldata(json_buf).unwrap(); + let error_buf = encode_inputdata(json_buf).unwrap(); let error = unsafe { error_as_string(error_buf) }; @@ -89,11 +88,11 @@ mod test { } #[test] - fn wasm_decode_calldata_invalid_json() { + fn wasm_decode_inputdata_invalid_json() { let json = "{"; let json_buf = to_wasm_buffer(json.as_bytes()); - let error_buf = decode_calldata(json_buf).unwrap(); + let error_buf = decode_inputdata(json_buf).unwrap(); let error = unsafe { error_as_string(error_buf) }; diff --git a/crates/codec/src/api/wasm/mod.rs b/crates/codec/src/api/wasm/mod.rs index fd8d002d4..505750ff7 100644 --- a/crates/codec/src/api/wasm/mod.rs +++ b/crates/codec/src/api/wasm/mod.rs @@ -1,16 +1,16 @@ //! WASM API mod call; -mod calldata; mod deploy; mod error; +mod inputdata; mod receipt; mod spawn; pub use call::{decode_call, encode_call}; -pub use calldata::{decode_calldata, encode_calldata}; pub use deploy::encode_deploy; pub use error::{error_as_string, into_error_buffer}; +pub use inputdata::{decode_inputdata, encode_inputdata}; pub use receipt::decode_receipt; pub use spawn::{decode_spawn, encode_spawn}; diff --git a/crates/codec/src/api/wasm/spawn.rs b/crates/codec/src/api/wasm/spawn.rs index 6f7e4dc12..0def7a515 100644 --- a/crates/codec/src/api/wasm/spawn.rs +++ b/crates/codec/src/api/wasm/spawn.rs @@ -36,7 +36,7 @@ mod test { fn wasm_spawn_valid() { let template_addr = "1122334455667788990011223344556677889900"; - let calldata = json::encode_calldata( + let calldata = json::encode_inputdata( &json!({ "abi": ["i32", "i64"], "data": [10, 20] diff --git a/crates/codec/src/call.rs b/crates/codec/src/call.rs index d93025cae..d4834e19c 100644 --- a/crates/codec/src/call.rs +++ b/crates/codec/src/call.rs @@ -20,19 +20,19 @@ use svm_types::{Address, Transaction}; use std::io::Cursor; -use crate::{calldata, version}; +use crate::{inputdata, version}; use crate::{Field, ParseError, ReadExt, WriteExt}; -/// Encodes a raw [`Transaction`] +/// Encodes a binary [`Transaction`] pub fn encode_call(tx: &Transaction, w: &mut Vec) { encode_version(tx, w); encode_target(tx, w); encode_func(tx, w); - // encode_verifydata(tx, w); + encode_verifydata(tx, w); encode_calldata(tx, w); } -/// Parsing a raw [`Transaction`]. +/// Parsing a binary [`Transaction`]. /// /// Returns the parsed transaction as [`Transaction`] struct. /// On failure, returns `ParseError` @@ -40,14 +40,14 @@ pub fn decode_call(cursor: &mut Cursor<&[u8]>) -> Result) { } fn encode_func(tx: &Transaction, w: &mut Vec) { - let func = tx.function(); + let func = tx.func_name(); w.write_string(func); } -// fn encode_verifydata(tx: &Transaction, w: &mut Vec) { -// let verifydata = &tx.verifydata; - -// calldata::encode_calldata(verifydata, w) -// } +fn encode_verifydata(tx: &Transaction, w: &mut Vec) { + let verifydata = tx.verifydata(); + inputdata::encode_inputdata(verifydata, w) +} fn encode_calldata(tx: &Transaction, w: &mut Vec) { let calldata = tx.calldata(); - calldata::encode_calldata(calldata, w) + inputdata::encode_inputdata(calldata, w) } /// Decoders @@ -115,7 +114,7 @@ mod tests { version: 0, target: Address::of("@target").into(), func_name: "do_work".to_string(), - // verifydata: vec![0x10, 0x0, 0x30], + verifydata: vec![0xAA, 0xBB, 0xCC], calldata: vec![0x10, 0x0, 0x30], }; diff --git a/crates/codec/src/calldata.rs b/crates/codec/src/calldata.rs deleted file mode 100644 index 17aa9b014..000000000 --- a/crates/codec/src/calldata.rs +++ /dev/null @@ -1,25 +0,0 @@ -use std::io::Cursor; - -use crate::{Field, ParseError, ReadExt, WriteExt}; - -pub fn encode_calldata(calldata: &[u8], w: &mut Vec) { - let length = calldata.len(); - - assert!(length <= std::u8::MAX as usize); - - w.write_byte(length as u8); - w.write_bytes(calldata); -} - -pub fn decode_calldata<'a>(cursor: &mut Cursor<&[u8]>) -> Result, ParseError> { - match cursor.read_byte() { - Err(..) => Err(ParseError::NotEnoughBytes(Field::CallDataLength)), - Ok(byte) => { - let length = byte as usize; - - cursor - .read_bytes(length) - .map_err(|_| ParseError::NotEnoughBytes(Field::CallData)) - } - } -} diff --git a/crates/codec/src/field.rs b/crates/codec/src/field.rs index 469c853d8..b320c0d41 100644 --- a/crates/codec/src/field.rs +++ b/crates/codec/src/field.rs @@ -28,8 +28,8 @@ pub enum Field { Address, TemplateAddr, TargetAddr, - CallDataLength, - CallData, + InputDataLength, + InputData, LayoutKind, LayoutCount, LayoutFirstVarId, diff --git a/crates/codec/src/inputdata.rs b/crates/codec/src/inputdata.rs new file mode 100644 index 000000000..466e7b830 --- /dev/null +++ b/crates/codec/src/inputdata.rs @@ -0,0 +1,25 @@ +use std::io::Cursor; + +use crate::{Field, ParseError, ReadExt, WriteExt}; + +pub fn encode_inputdata(data: &[u8], w: &mut Vec) { + let length = data.len(); + + assert!(length <= std::u8::MAX as usize); + + w.write_byte(length as u8); + w.write_bytes(data); +} + +pub fn decode_inputdata<'a>(cursor: &mut Cursor<&[u8]>) -> Result, ParseError> { + match cursor.read_byte() { + Err(..) => Err(ParseError::NotEnoughBytes(Field::InputDataLength)), + Ok(byte) => { + let length = byte as usize; + + cursor + .read_bytes(length) + .map_err(|_| ParseError::NotEnoughBytes(Field::InputData)) + } + } +} diff --git a/crates/codec/src/lib.rs b/crates/codec/src/lib.rs index 3d47e9021..e6a390c13 100644 --- a/crates/codec/src/lib.rs +++ b/crates/codec/src/lib.rs @@ -12,9 +12,9 @@ #![allow(unreachable_code)] #![feature(vec_into_raw_parts)] -mod calldata; mod ext; mod field; +mod inputdata; mod section; mod version; @@ -60,7 +60,8 @@ pub use error::ParseError; /// /// /// WASM Buffer `Data` for Success result: -/// ``` +/// +/// ```text /// +------------------------------------------------+ /// | OK_MAKER = 1 (1 byte) | SVM binary transaction | /// +------------------------------------------------+ @@ -68,7 +69,8 @@ pub use error::ParseError; /// /// /// WASM Buffer `Data` for Error result: -/// ``` +// +/// ```text /// +------------------------------------------------+ /// | ERR_MAKER = 0 (1 byte) | UTF-8 String (error) | /// +------------------------------------------------+ @@ -194,25 +196,25 @@ pub extern "C" fn wasm_buffer_data(offset: i32) -> i32 { data_offset as _ } -/// ## Calldata +/// ## Input Data (i.e `CallData/VerifyData`) /// /// Reads the WASM buffer given at parameter `offset` containing a JSON value. -/// Encodes the `Calldata, and returns a pointer to a new WASM buffer holding the encoded `Calldata`. +/// Encodes the `Input Data`, and returns a pointer to a new WASM buffer holding the encoded `Input Data`. /// If the encoding fails, the returned WASM buffer will contain a String containing the error message. #[no_mangle] #[cfg(target_arch = "wasm32")] -pub extern "C" fn wasm_encode_calldata(offset: i32) -> i32 { - wasm_func_call!(encode_calldata, offset) +pub extern "C" fn wasm_encode_inputdata(offset: i32) -> i32 { + wasm_func_call!(encode_inputdata, offset) } -/// Decodes the encoded `Calldata` given as a WASM buffer (parameter `offset`). +/// Decodes the encoded `Input Data` given as a WASM buffer (parameter `offset`). /// -/// Returns a pointer to a new WASM buffer holding the decoded `Calldata`. +/// Returns a pointer to a new WASM buffer holding the decoded `Input Data`. /// If the decoding fails, the returned WASM buffer will contain a String containing the error message. #[no_mangle] #[cfg(target_arch = "wasm32")] -pub extern "C" fn wasm_decode_calldata(offset: i32) -> i32 { - wasm_func_call!(decode_calldata, offset) +pub extern "C" fn wasm_decode_inputdata(offset: i32) -> i32 { + wasm_func_call!(decode_inputdata, offset) } /// Decodes the encoded `Receipt` given as a WASM buffer (parameter `offset`). diff --git a/crates/codec/src/spawn.rs b/crates/codec/src/spawn.rs index 94a0cd0b8..e21b061f1 100644 --- a/crates/codec/src/spawn.rs +++ b/crates/codec/src/spawn.rs @@ -20,7 +20,7 @@ use std::io::Cursor; use svm_types::{Account, SpawnAccount, TemplateAddr}; -use crate::{calldata, version}; +use crate::{inputdata, version}; use crate::{Field, ParseError, ReadExt, WriteExt}; /// Encodes a binary [`SpawnAccount`] transaction. @@ -82,7 +82,7 @@ fn encode_ctor(spawn: &SpawnAccount, w: &mut Vec) { fn encode_ctor_calldata(spawn: &SpawnAccount, w: &mut Vec) { let calldata = &*spawn.calldata; - calldata::encode_calldata(calldata, w); + inputdata::encode_inputdata(calldata, w); } /// Decoders @@ -115,7 +115,7 @@ fn decode_ctor(cursor: &mut Cursor<&[u8]>) -> Result { } fn decode_ctor_calldata(cursor: &mut Cursor<&[u8]>) -> Result, ParseError> { - calldata::decode_calldata(cursor) + inputdata::decode_inputdata(cursor) } #[cfg(test)] diff --git a/crates/program/src/error.rs b/crates/program/src/error.rs index 51aebf795..70a4bbcd7 100644 --- a/crates/program/src/error.rs +++ b/crates/program/src/error.rs @@ -7,19 +7,27 @@ use std::fmt; pub enum ProgramError { /// Invalid wasm InvalidWasm, + /// No valid `svm_alloc` function found. - FunctionNotFound { - /// The name of the WebAssembly function that wasn't found. - func_name: String, - }, + FunctionNotFound(String), + /// Floats not allowed FloatsNotAllowed, + /// Too many function imports TooManyFunctionImports, + /// Function index is too large FunctionIndexTooLarge, + /// Wasm has no `code` section MissingCodeSection, + + /// Invalid Export Kind + InvalidExportKind, + + /// Invalid Export Function Signature + InvalidExportFunctionSignature(String), } impl fmt::Display for ProgramError { diff --git a/crates/program/src/program.rs b/crates/program/src/program.rs index b215807e0..5171e8348 100644 --- a/crates/program/src/program.rs +++ b/crates/program/src/program.rs @@ -1,5 +1,6 @@ use indexmap::IndexMap; -use parity_wasm::elements::{CodeSection, Module}; + +use parity_wasm::elements as pwasm; use crate::{ validate_no_floats, Exports, FuncIndex, Function, Imports, Instruction, ProgramError, @@ -33,7 +34,7 @@ pub struct Program { } impl Program { - /// Reads a Wasm program and constructs a `Program` struct + /// Reads a Wasm program and constructs a [`Program`] struct pub fn new(wasm_module: &[u8], validate_exports: bool) -> Result { let module = read_module(wasm_module)?; @@ -124,13 +125,13 @@ impl Program { } } -fn read_module(wasm: &[u8]) -> Result { +fn read_module(wasm: &[u8]) -> Result { let module = parity_wasm::deserialize_buffer(wasm); module.map_err(|_| ProgramError::InvalidWasm) } -fn read_code(module: &Module) -> Result { +fn read_code(module: &pwasm::Module) -> Result { match module.code_section() { Some(code) => Ok(code.clone()), None => Err(ProgramError::MissingCodeSection), @@ -162,53 +163,164 @@ fn count_functions_in_program(program: &Program) -> u64 { Counter::default().visit(program).unwrap() } -/// Checks whether `wasm_module` exports a well-defined `svm_alloc` function. -/// `svm_alloc` is required by SVM for all WASM code and must have a `I32 -> -/// I32` type signature. -fn module_validate_exports(wasm_module: &Module) -> Result<(), ProgramError> { - use parity_wasm::elements::{ExportSection, FunctionSection, Type, TypeSection, ValueType}; +/// Checks whether `wasm_module` exports a well-defined `svm_alloc` and `svm_verify` functions. +/// Both are required by SVM to exist in each `Template`. - let empty_export_section = ExportSection::with_entries(vec![]); - let empty_function_sig_section = FunctionSection::with_entries(vec![]); +/// * `svm_alloc` must have a `I32 -> I32` type signature. +/// - The input param is for the amount of bytes to allocate. +/// - The output is the `offset` pointing to the allocated memory first cell. +/// +/// * `svm_verify` must have a `() -> I32` type signature. +/// - The input is `()` since it is passed using the `Verify Data` mechanism. +/// - The output is the `offset` pointing to the `returndata` first cell. +/// +fn module_validate_exports(module: &pwasm::Module) -> Result<(), ProgramError> { + use pwasm::{ExportSection, FunctionSection, TypeSection}; + + let empty_function_section = FunctionSection::with_entries(vec![]); let empty_type_section = TypeSection::with_types(vec![]); + let empty_export_section = ExportSection::with_entries(vec![]); - let module_functions = wasm_module - .function_section() - .unwrap_or(&empty_function_sig_section) - .entries(); - let module_types = wasm_module - .type_section() - .unwrap_or(&empty_type_section) - .types(); - let module_exports = wasm_module - .export_section() - .unwrap_or(&empty_export_section) - .entries(); - - for entry in module_exports.iter() { - if entry.field() != "svm_alloc" { - continue; - } + let module_functions = module_functions(module, &empty_function_section); + let module_types = module_types(module, &empty_type_section); + let module_exports = module_exports(module, &empty_export_section); + let import_count = module_functions_import_count(module); + + let mut seen_alloc = false; + let mut seen_verify = false; - let type_signature = { - let function = if let parity_wasm::elements::Internal::Function(i) = entry.internal() { - module_functions[*i as usize] - } else { - // We don't care about anything but functions right now. - continue; - }; - &module_types[function.type_ref() as usize] - }; - - #[allow(irrefutable_let_patterns)] - if let Type::Function(f) = type_signature { - if f.params() == &[ValueType::I32] && f.results() == &[ValueType::I32] { - return Ok(()); + for export in module_exports.iter() { + match export.field() { + "svm_alloc" => { + seen_alloc = true; + validate_export_alloc(export, import_count, module_functions, module_types)?; } + "svm_verify" => { + seen_verify = true; + svm_verify_validate(export, import_count, &module_functions, &module_types)?; + } + _ => (), } } - Err(ProgramError::FunctionNotFound { - func_name: "svm_alloc".to_string(), - }) + if !seen_alloc { + return Err(ProgramError::FunctionNotFound("svm_alloc".to_string())); + } + + if !seen_verify { + return Err(ProgramError::FunctionNotFound("svm_verify".to_string())); + } + + Ok(()) +} + +fn validate_export_alloc( + export: &pwasm::ExportEntry, + import_count: usize, + module_funcs: &[pwasm::Func], + module_types: &[pwasm::Type], +) -> Result<(), ProgramError> { + use pwasm::ValueType; + + validate_func_signature( + "svm_alloc", + export, + import_count, + module_funcs, + module_types, + &[ValueType::I32], + &[ValueType::I32], + ) +} + +fn svm_verify_validate( + export: &pwasm::ExportEntry, + import_count: usize, + module_funcs: &[pwasm::Func], + module_types: &[pwasm::Type], +) -> Result<(), ProgramError> { + use pwasm::ValueType; + + validate_func_signature( + "svm_verify", + export, + import_count, + module_funcs, + module_types, + &[], + &[ValueType::I32], + ) +} + +fn validate_func_signature( + func_name: &str, + export: &pwasm::ExportEntry, + import_count: usize, + module_funcs: &[pwasm::Func], + module_types: &[pwasm::Type], + expected_params: &[pwasm::ValueType], + expected_results: &[pwasm::ValueType], +) -> Result<(), ProgramError> { + let func_sig = export_func_signature(export, import_count, &module_funcs, &module_types)?; + + #[allow(irrefutable_let_patterns)] + if let pwasm::Type::Function(f) = func_sig { + if f.params() == expected_params && f.results() == expected_results { + Ok(()) + } else { + Err(ProgramError::InvalidExportFunctionSignature( + func_name.to_string(), + )) + } + } else { + unreachable!() + } +} + +fn export_func_signature<'p>( + entry: &'p pwasm::ExportEntry, + import_count: usize, + module_functions: &'p [pwasm::Func], + module_types: &'p [pwasm::Type], +) -> Result<&'p pwasm::Type, ProgramError> { + if let pwasm::Internal::Function(global) = entry.internal() { + let global = *global as usize; + debug_assert!(global >= import_count); + + let local = global - import_count; + let func = &module_functions[local]; + let type_ref = func.type_ref() as usize; + let sig = &module_types[type_ref]; + + Ok(sig) + } else { + Err(ProgramError::InvalidExportKind) + } +} + +fn module_functions<'p>( + module: &'p pwasm::Module, + default: &'p pwasm::FunctionSection, +) -> &'p [pwasm::Func] { + module.function_section().unwrap_or(default).entries() +} + +fn module_types<'p>( + module: &'p pwasm::Module, + default: &'p pwasm::TypeSection, +) -> &'p [pwasm::Type] { + module.type_section().unwrap_or(default).types() +} + +fn module_exports<'p>( + module: &'p pwasm::Module, + default: &'p pwasm::ExportSection, +) -> &'p [pwasm::ExportEntry] { + module.export_section().unwrap_or(default).entries() +} + +fn module_functions_import_count(module: &pwasm::Module) -> usize { + use pwasm::ImportCountType; + + module.import_count(ImportCountType::Function) } diff --git a/crates/runtime-ffi/src/api.rs b/crates/runtime-ffi/src/api.rs index 0587570c2..267ccd87f 100644 --- a/crates/runtime-ffi/src/api.rs +++ b/crates/runtime-ffi/src/api.rs @@ -23,6 +23,7 @@ static MESSAGE_TYPE: Type = Type::Str("Tx Message"); static CONTEXT_TYPE: Type = Type::Str("Tx Context"); static DEPLOY_RECEIPT_TYPE: Type = Type::Str("Deploy Receipt"); static SPAWN_RECEIPT_TYPE: Type = Type::Str("Spawn Receipt"); +static VERIFY_RECEIPT_TYPE: Type = Type::Str("Verify Receipt"); static CALL_RECEIPT_TYPE: Type = Type::Str("Call Receipt"); static SVM_RESOURCE_TYPE: Type = Type::of::(); @@ -472,6 +473,86 @@ pub unsafe extern "C" fn svm_spawn( }) } +/// Calls `verify` on an Account. +/// The inputs `envelope`, `message` and `context` should be the same ones +/// passed later to `svm_call`.(in case the `verify` succeeds). +/// +/// Returns the Receipt of the execution via the `receipt` parameter. +/// +/// # Examples +/// +/// ```rust, no_run +/// use std::ffi::c_void; +/// +/// use svm_runtime_ffi::*; +/// +/// let mut runtime = std::ptr::null_mut(); +/// let mut error = svm_byte_array::default(); +/// +/// let res = unsafe { svm_memory_runtime_create(&mut runtime, &mut error) }; +/// assert!(res.is_ok()); +/// +/// let mut receipt = svm_byte_array::default(); +/// let envelope = svm_byte_array::default(); +/// let message = svm_byte_array::default(); +/// let context = svm_byte_array::default(); +/// +/// let _res = unsafe { +/// svm_verify( +/// &mut receipt, +/// runtime, +/// envelope, +/// message, +/// context, +/// &mut error) +/// }; +/// ``` +/// +#[must_use] +#[no_mangle] +pub unsafe extern "C" fn svm_verify( + receipt: *mut svm_byte_array, + runtime: *mut c_void, + envelope: svm_byte_array, + message: svm_byte_array, + context: svm_byte_array, + error: *mut svm_byte_array, +) -> svm_result_t { + catch_unwind_with_err(&mut *error, svm_result_t::SVM_FAILURE, || { + debug!("`svm_verify` start"); + + let runtime = RuntimeRef::as_native(runtime); + let message = message.as_slice(); + + let envelope = decode_envelope(envelope); + if let Err(e) = envelope { + raw_io_error(e, &mut *error); + return svm_result_t::SVM_FAILURE; + } + + let context = decode_context(context); + if let Err(e) = context { + raw_io_error(e, &mut *error); + return svm_result_t::SVM_FAILURE; + } + + let envelope = envelope.unwrap(); + let context = context.unwrap(); + let rust_receipt = runtime.verify(&envelope, &message, &context); + let receipt_bytes = receipt::encode_call(&rust_receipt); + + // Returns encoded `CallReceipt` as `svm_byte_array`. + // + // # Notes: + // + // Should call later `svm_receipt_destroy` + data_to_svm_byte_array(VERIFY_RECEIPT_TYPE, &mut *receipt, receipt_bytes); + + debug!("`svm_verify` returns `SVM_SUCCESS`"); + svm_result_t::SVM_SUCCESS + }) +} + /// `Call Account` transaction. /// Returns the Receipt of the execution via the `receipt` parameter. /// diff --git a/crates/runtime-ffi/src/lib.rs b/crates/runtime-ffi/src/lib.rs index 3f4ea7f39..3a470b496 100644 --- a/crates/runtime-ffi/src/lib.rs +++ b/crates/runtime-ffi/src/lib.rs @@ -47,9 +47,10 @@ pub use api::{ svm_validate_call, // Transactions Execution - svm_call, svm_deploy, svm_spawn, + svm_verify, + svm_call, // Destroy svm_runtime_destroy, diff --git a/crates/runtime/src/error.rs b/crates/runtime/src/error.rs index 7fb0817de..c05cb4ed9 100644 --- a/crates/runtime/src/error.rs +++ b/crates/runtime/src/error.rs @@ -10,10 +10,12 @@ pub enum ValidateError { /// An unexpected condition was found when decoding from the SVM ABI. #[error("{0}")] Parse(#[from] ParseError), + /// The given smWasm is invalid or it uses specific features which are not /// allowed by SVM. #[error("{0}")] Program(#[from] ProgramError), + /// The given smWasm code is valid, but it doesn't pass the requirements to /// run in fixed-gas mode. #[error("{0}")] diff --git a/crates/runtime/src/func_env.rs b/crates/runtime/src/func_env.rs index 6b46b8975..2f9e04923 100644 --- a/crates/runtime/src/func_env.rs +++ b/crates/runtime/src/func_env.rs @@ -31,15 +31,15 @@ impl FuncEnv { storage: AccountStorage, envelope: &Envelope, context: &Context, - template_addr: &TemplateAddr, - target_addr: &Address, + template_addr: TemplateAddr, + target_addr: Address, ) -> Self { let inner = Inner::new(storage); Self { inner: Rc::new(RefCell::new(inner)), - template_addr: template_addr.clone(), - target_addr: target_addr.clone(), + template_addr: template_addr, + target_addr: target_addr, envelope: envelope.clone(), context: context.clone(), } @@ -51,8 +51,8 @@ impl FuncEnv { storage: AccountStorage, envelope: &Envelope, context: &Context, - template_addr: &TemplateAddr, - target_addr: &Address, + template_addr: TemplateAddr, + target_addr: Address, ) -> Self { let func_env = Self::new(storage, envelope, context, template_addr, target_addr); func_env.borrow_mut().set_memory(memory); diff --git a/crates/runtime/src/runtime/call.rs b/crates/runtime/src/runtime/call.rs index b36b43fe6..e748b6f4e 100644 --- a/crates/runtime/src/runtime/call.rs +++ b/crates/runtime/src/runtime/call.rs @@ -5,12 +5,11 @@ use svm_types::{Address, Context, Envelope, Gas, State, TemplateAddr}; #[derive(Debug, Clone, PartialEq)] pub struct Call<'a> { pub func_name: &'a str, - pub calldata: &'a [u8], - pub target_addr: &'a Address, - pub target_template: &'a TemplateAddr, + pub func_input: &'a [u8], + pub target: Address, + pub template: TemplateAddr, pub state: &'a State, - pub gas_used: Gas, - pub gas_left: Gas, + pub gas_limit: Gas, pub within_spawn: bool, pub context: &'a Context, pub envelope: &'a Envelope, diff --git a/crates/runtime/src/runtime/default.rs b/crates/runtime/src/runtime/default.rs index 792e2ef7d..525bc94ad 100644 --- a/crates/runtime/src/runtime/default.rs +++ b/crates/runtime/src/runtime/default.rs @@ -11,7 +11,7 @@ use svm_program::Program; use svm_storage::account::AccountStorage; use svm_types::{ Address, CallReceipt, Context, DeployReceipt, Envelope, Gas, GasMode, OOGError, ReceiptLog, - RuntimeError, SectionKind, SpawnReceipt, State, Template, TemplateAddr, + RuntimeError, SectionKind, SpawnReceipt, State, Template, TemplateAddr, Transaction, }; use super::{Call, Failure, Function, Outcome}; @@ -116,23 +116,21 @@ where fn call_ctor( &mut self, spawn: &ExtSpawn, - target: &Address, - gas_used: Gas, + target: Address, gas_left: Gas, envelope: &Envelope, context: &Context, ) -> SpawnReceipt { - let template_addr = spawn.template_addr(); + let template = spawn.template_addr().clone(); let call = Call { func_name: spawn.ctor_name(), - calldata: spawn.ctor_data(), + func_input: spawn.ctor_data(), state: &State::zeros(), - target_template: template_addr, - target_addr: target, + template, + target: target.clone(), within_spawn: true, - gas_used, - gas_left, + gas_limit: gas_left, envelope, context, }; @@ -140,7 +138,7 @@ where let receipt = self.exec_call::<(), ()>(&call); // TODO: move the `into_spawn_receipt` to a `From / TryFrom` - svm_types::into_spawn_receipt(receipt, target) + svm_types::into_spawn_receipt(receipt, &target) } fn exec_call(&mut self, call: &Call) -> CallReceipt { @@ -155,17 +153,16 @@ where Rets: WasmTypeList, F: Fn(&FuncEnv, Outcome>) -> R, { - match self.account_template(call.target_addr) { + match self.account_template(&call.target) { Ok(template) => { - let storage = - self.open_storage(call.target_addr, call.state, template.fixed_layout()); + let storage = self.open_storage(&call.target, call.state, template.fixed_layout()); let mut env = FuncEnv::new( storage, call.envelope, call.context, - call.target_template, - call.target_addr, + call.template.clone(), + call.target.clone(), ); let store = crate::wasm_store::new_store(); @@ -192,17 +189,17 @@ where { self.validate_call(call, template, func_env)?; - let module = self.compile_template(store, func_env, &template, call.gas_left)?; + let module = self.compile_template(store, func_env, &template, call.gas_limit)?; let instance = self.instantiate(func_env, &module, import_object)?; self.set_memory(func_env, &instance); let func = self.func::(&instance, func_env, call.func_name)?; - let mut out = if call.calldata.len() > 0 { - self.call_with_alloc(&instance, func_env, call.calldata, &func, &[])? + let mut out = if call.func_input.len() > 0 { + self.call_with_alloc(&instance, func_env, call.func_input, &func, &[])? } else { - self.call(&instance, func_env, &func, &[])? + self.wasmer_call(&instance, func_env, &func, &[])? }; let logs = out.take_logs(); @@ -244,7 +241,7 @@ where let wasm_ptr = out.returns(); self.set_calldata(env, calldata, wasm_ptr); - self.call(instance, env, func, params) + self.wasmer_call(instance, env, func, params) } fn call_alloc(&self, instance: &Instance, env: &FuncEnv, size: usize) -> Result> { @@ -259,7 +256,7 @@ where let func = func.unwrap(); let params: [wasmer::Val; 1] = [(size as i32).into()]; - let out = self.call(instance, env, &func, ¶ms)?; + let out = self.wasmer_call(instance, env, &func, ¶ms)?; let out = out.map(|rets| { let ret = &rets[0]; let offset = ret.i32().unwrap() as u32; @@ -270,7 +267,7 @@ where Ok(out) } - fn call( + fn wasmer_call( &self, instance: &Instance, env: &FuncEnv, @@ -477,6 +474,11 @@ where template: &Template, env: &FuncEnv, ) -> std::result::Result<(), Failure> { + // TODO: validate there is enough gas for running the `Transaction`. + // * verify + // * call + // * other factors + let spawning = call.within_spawn; let ctor = template.is_ctor(call.func_name); @@ -497,6 +499,34 @@ where Ok(()) } + fn build_call<'a>( + &self, + tx: &'a Transaction, + envelope: &'a Envelope, + context: &'a Context, + func_name: &'a str, + func_input: &'a [u8], + ) -> Call<'a> { + let target = tx.target(); + let template = self.env.resolve_template_addr(target); + + if let Some(template) = template { + Call { + func_name, + func_input, + target: target.clone(), + template, + state: context.state(), + gas_limit: envelope.gas_limit(), + within_spawn: false, + envelope, + context, + } + } else { + unreachable!("Should have failed earlier when doing `validate_call`"); + } + } + /// Errors #[inline] @@ -705,24 +735,25 @@ where match gas_left { Ok(gas_left) => { let account = ExtAccount::new(spawn.account(), &spawner); - let addr = self.env.compute_account_addr(&spawn); + let target = self.env.compute_account_addr(&spawn); - self.env.store_account(&account, &addr); - let gas_used = payload_price.into(); - - self.call_ctor(&spawn, &addr, gas_used, gas_left, envelope, context) + self.env.store_account(&account, &target); + self.call_ctor(&spawn, target, gas_left, envelope, context) } Err(..) => SpawnReceipt::new_oog(Vec::new()), } } - fn verify( - &self, - _envelope: &Envelope, - _message: &[u8], - _context: &Context, - ) -> std::result::Result { - todo!("https://github.com/spacemeshos/svm/issues/248") + fn verify(&mut self, envelope: &Envelope, message: &[u8], context: &Context) -> CallReceipt { + let tx = self + .env + .parse_call(message) + .expect("Should have called `validate_call` first"); + + let call = self.build_call(&tx, envelope, context, "svm_verify", tx.verifydata()); + + // TODO: override the `call.gas_limit` with `VERIFY_MAX_GAS` + self.exec_call::<(), ()>(&call) } fn call(&mut self, envelope: &Envelope, message: &[u8], context: &Context) -> CallReceipt { @@ -731,25 +762,7 @@ where .parse_call(message) .expect("Should have called `validate_call` first"); - let template = self.env.resolve_template_addr(tx.target()); - - if let Some(template) = template { - let call = Call { - func_name: tx.function(), - calldata: tx.calldata(), - target_addr: tx.target(), - target_template: &template, - state: context.state(), - gas_used: Gas::with(0), - gas_left: envelope.gas_limit(), - within_spawn: false, - envelope, - context, - }; - - self.exec_call::<(), ()>(&call) - } else { - unreachable!("Should have failed earlier when doing `validate_call`"); - } + let call = self.build_call(&tx, envelope, context, tx.func_name(), tx.calldata()); + self.exec_call::<(), ()>(&call) } } diff --git a/crates/runtime/src/runtime/mod.rs b/crates/runtime/src/runtime/mod.rs index 7b079c93c..9d62c85d7 100644 --- a/crates/runtime/src/runtime/mod.rs +++ b/crates/runtime/src/runtime/mod.rs @@ -21,7 +21,7 @@ pub use rocksdb::create_rocksdb_runtime; pub use config::Config; pub use default::DefaultRuntime; -use svm_types::{CallReceipt, Context, DeployReceipt, Envelope, RuntimeError, SpawnReceipt}; +use svm_types::{CallReceipt, Context, DeployReceipt, Envelope, SpawnReceipt}; use crate::error::ValidateError; @@ -49,12 +49,7 @@ pub trait Runtime { fn spawn(&mut self, envelope: &Envelope, message: &[u8], context: &Context) -> SpawnReceipt; /// Verifies a [`Transaction`](svm_types::Transaction) before execution. - fn verify( - &self, - envelope: &Envelope, - message: &[u8], - context: &Context, - ) -> Result; + fn verify(&mut self, envelope: &Envelope, message: &[u8], context: &Context) -> CallReceipt; /// Executes a [`Transaction`](svm_types::Transaction) and returns its output [`CallReceipt`]. /// diff --git a/crates/runtime/tests/runtime_tests.rs b/crates/runtime/tests/runtime_tests.rs index c1b1c57f4..3074cd7c3 100644 --- a/crates/runtime/tests/runtime_tests.rs +++ b/crates/runtime/tests/runtime_tests.rs @@ -24,7 +24,83 @@ fn memory_runtime_validate_deploy_not_enough_bytes() { } #[test] -fn memory_runtime_validate_deploy_invalid_wasm() { +fn memory_runtime_validate_deploy_missing_svm_verify_export() { + let runtime = testing::create_memory_runtime(); + + let message = testing::build_deploy( + 0, + "My Template", + FixedLayout::default(), + &[], + include_str!("wasm/missing_svm_verify.wast").into(), + ); + + let error = ProgramError::FunctionNotFound("svm_verify".to_string()); + let expected = Err(ValidateError::Program(error)); + + let actual = runtime.validate_deploy(&message); + assert_eq!(expected, actual); +} + +#[test] +fn memory_runtime_validate_deploy_missing_svm_alloc_export() { + let runtime = testing::create_memory_runtime(); + + let message = testing::build_deploy( + 0, + "My Template", + FixedLayout::default(), + &[], + include_str!("wasm/missing_svm_alloc.wast").into(), + ); + + let error = ProgramError::FunctionNotFound("svm_alloc".to_string()); + let expected = Err(ValidateError::Program(error)); + + let actual = runtime.validate_deploy(&message); + assert_eq!(expected, actual); +} + +#[test] +fn memory_runtime_validate_deploy_svm_alloc_export_invalid_signature() { + let runtime = testing::create_memory_runtime(); + + let message = testing::build_deploy( + 0, + "My Template", + FixedLayout::default(), + &[], + include_str!("wasm/svm_alloc_invalid_sig.wast").into(), + ); + + let error = ProgramError::InvalidExportFunctionSignature("svm_alloc".to_string()); + let expected = Err(ValidateError::Program(error)); + + let actual = runtime.validate_deploy(&message); + assert_eq!(expected, actual); +} + +#[test] +fn memory_runtime_validate_deploy_svm_verify_export_invalid_signature() { + let runtime = testing::create_memory_runtime(); + + let message = testing::build_deploy( + 0, + "My Template", + FixedLayout::default(), + &[], + include_str!("wasm/svm_verify_invalid_sig.wast").into(), + ); + + let error = ProgramError::InvalidExportFunctionSignature("svm_verify".to_string()); + let expected = Err(ValidateError::Program(error)); + + let actual = runtime.validate_deploy(&message); + assert_eq!(expected, actual); +} + +#[test] +fn memory_runtime_validate_deploy_floats_not_allowed() { let runtime = testing::create_memory_runtime(); // An invalid Wasm (has floats) @@ -43,6 +119,22 @@ fn memory_runtime_validate_deploy_invalid_wasm() { assert_eq!(expected, actual); } +#[test] +fn memory_runtime_validate_deploy_ok() { + let runtime = testing::create_memory_runtime(); + + let message = testing::build_deploy( + 0, + "My Template", + FixedLayout::default(), + &[], + include_bytes!("wasm/runtime_calldata.wasm")[..].into(), + ); + + let result = runtime.validate_deploy(&message); + assert!(result.is_ok()); +} + #[test] fn memory_runtime_validate_spawn_missing_template_addr() { let runtime = testing::create_memory_runtime(); diff --git a/crates/runtime/tests/vmcalls_tests.rs b/crates/runtime/tests/vmcalls_tests.rs index f7581dc06..f1b5bc5ec 100644 --- a/crates/runtime/tests/vmcalls_tests.rs +++ b/crates/runtime/tests/vmcalls_tests.rs @@ -115,14 +115,14 @@ fn vmcalls_empty_wasm() { #[test] fn vmcalls_get32_set32() { let template_addr = TemplateAddr::repeat(0xAB); - let account_addr = Address::repeat(0xCD); + let target_addr = Address::repeat(0xCD); let layout: FixedLayout = vec![4, 2].into(); let store = wasmer_store(); - let storage = testing::blank_storage(&account_addr, &layout); + let storage = testing::blank_storage(&target_addr, &layout); let envelope = Envelope::default(); let context = Context::default(); - let func_env = FuncEnv::new(storage, &envelope, &context, &template_addr, &account_addr); + let func_env = FuncEnv::new(storage, &envelope, &context, template_addr, target_addr); let import_object = imports! { "svm" => { @@ -150,14 +150,14 @@ fn vmcalls_get32_set32() { #[test] fn vmcalls_get64_set64() { let template_addr = TemplateAddr::repeat(0xAB); - let account_addr = Address::repeat(0xCD); + let target_addr = Address::repeat(0xCD); let layout: FixedLayout = vec![4, 2].into(); let store = wasmer_store(); - let storage = testing::blank_storage(&account_addr, &layout); + let storage = testing::blank_storage(&target_addr, &layout); let envelope = Envelope::default(); let context = Context::default(); - let func_env = FuncEnv::new(storage, &envelope, &context, &template_addr, &account_addr); + let func_env = FuncEnv::new(storage, &envelope, &context, template_addr, target_addr); let import_object = imports! { "svm" => { @@ -185,12 +185,12 @@ fn vmcalls_get64_set64() { #[test] fn vmcalls_load160() { let template_addr = TemplateAddr::repeat(0xAB); - let account_addr = Address::repeat(0xCD); + let target_addr = Address::repeat(0xCD); let layout: FixedLayout = vec![20].into(); let store = wasmer_store(); let memory = wasmer_memory(&store); - let storage = testing::blank_storage(&account_addr, &layout); + let storage = testing::blank_storage(&target_addr, &layout); let envelope = Envelope::default(); let context = Context::default(); let func_env = FuncEnv::new_with_memory( @@ -198,8 +198,8 @@ fn vmcalls_load160() { storage, &envelope, &context, - &template_addr, - &account_addr, + template_addr, + target_addr.clone(), ); let import_object = imports! { @@ -218,7 +218,7 @@ fn vmcalls_load160() { { let storage = &mut func_env.borrow_mut().storage; - storage.write_var(Id(0), account_addr.as_slice().to_vec()); + storage.write_var(Id(0), target_addr.as_slice().to_vec()); } let func: NativeFunc<(u32, u32)> = instance.exports.get_native_function("load").unwrap(); @@ -230,18 +230,18 @@ fn vmcalls_load160() { let view = &memory.view::()[ptr as usize..(ptr as usize + 20)]; let bytes: Vec = view.iter().map(|cell| cell.get()).collect(); - assert_eq!(account_addr, Address::from(&bytes[..])); + assert_eq!(target_addr, Address::from(&bytes[..])); } #[test] fn vmcalls_store160() { let template_addr = TemplateAddr::repeat(0xAB); - let account_addr = Address::repeat(0xCD); + let target_addr = Address::repeat(0xCD); let layout: FixedLayout = vec![20].into(); let store = wasmer_store(); let memory = wasmer_memory(&store); - let storage = testing::blank_storage(&account_addr, &layout); + let storage = testing::blank_storage(&target_addr, &layout); let envelope = Envelope::default(); let context = Context::default(); let func_env = FuncEnv::new_with_memory( @@ -249,8 +249,8 @@ fn vmcalls_store160() { storage, &envelope, &context, - &template_addr, - &account_addr, + template_addr, + target_addr.clone(), ); let import_object = imports! { @@ -267,7 +267,7 @@ fn vmcalls_store160() { include_str!("wasm/load160_store160.wast").into(), ); - for (cell, byte) in memory.view::().iter().zip(account_addr.as_slice()) { + for (cell, byte) in memory.view::().iter().zip(target_addr.as_slice()) { cell.set(*byte); } @@ -277,18 +277,18 @@ fn vmcalls_store160() { func.call(var_id, ptr).expect("function has failed"); - assert_storage!(func_env, 0 => account_addr.as_slice()); + assert_storage!(func_env, 0 => target_addr.as_slice()); } #[test] fn vmcalls_log() { let template_addr = TemplateAddr::repeat(0xAB); - let account_addr = Address::repeat(0xCD); + let target_addr = Address::repeat(0xCD); let layout = FixedLayout::default(); let store = wasmer_store(); let memory = wasmer_memory(&store); - let storage = testing::blank_storage(&account_addr, &layout); + let storage = testing::blank_storage(&target_addr, &layout); let envelope = Envelope::default(); let context = Context::default(); let func_env = FuncEnv::new_with_memory( @@ -296,8 +296,8 @@ fn vmcalls_log() { storage, &envelope, &context, - &template_addr, - &account_addr, + template_addr, + target_addr, ); let import_object = imports! { diff --git a/crates/runtime/tests/wasm/calldata/Cargo.lock b/crates/runtime/tests/wasm/calldata/Cargo.lock index 1eb95d227..baa06ff7b 100644 --- a/crates/runtime/tests/wasm/calldata/Cargo.lock +++ b/crates/runtime/tests/wasm/calldata/Cargo.lock @@ -147,7 +147,7 @@ version = "0.0.0" [[package]] name = "svm-runtime-examples-calldata" -version = "0.1.0" +version = "0.0.0" dependencies = [ "svm-sdk", ] @@ -187,7 +187,7 @@ dependencies = [ [[package]] name = "svm-sdk-macros" -version = "0.1.0" +version = "0.0.0" dependencies = [ "proc-macro2", "quote", diff --git a/crates/runtime/tests/wasm/missing_svm_alloc.wast b/crates/runtime/tests/wasm/missing_svm_alloc.wast new file mode 100644 index 000000000..151695482 --- /dev/null +++ b/crates/runtime/tests/wasm/missing_svm_alloc.wast @@ -0,0 +1,4 @@ +(module + (export "svm_verify" (func $svm_verify)) + (func $svm_verify (result i32) + (i32.const 0))) \ No newline at end of file diff --git a/crates/runtime/tests/wasm/missing_svm_verify.wast b/crates/runtime/tests/wasm/missing_svm_verify.wast new file mode 100644 index 000000000..961ded034 --- /dev/null +++ b/crates/runtime/tests/wasm/missing_svm_verify.wast @@ -0,0 +1,4 @@ +(module + (export "svm_alloc" (func $svm_alloc)) + (func $svm_alloc (param i32) (result i32) + (i32.const 0))) \ No newline at end of file diff --git a/crates/runtime/tests/wasm/runtime_calldata.wasm b/crates/runtime/tests/wasm/runtime_calldata.wasm index d832a946f..028774fca 100755 Binary files a/crates/runtime/tests/wasm/runtime_calldata.wasm and b/crates/runtime/tests/wasm/runtime_calldata.wasm differ diff --git a/crates/runtime/tests/wasm/svm_alloc_invalid_sig.wast b/crates/runtime/tests/wasm/svm_alloc_invalid_sig.wast new file mode 100644 index 000000000..1ca0dddc7 --- /dev/null +++ b/crates/runtime/tests/wasm/svm_alloc_invalid_sig.wast @@ -0,0 +1,11 @@ +(module + (export "svm_alloc" (func $svm_alloc)) + (export "svm_verify" (func $svm_verify)) + + ;; Valid `svm_verify` signature + (func $svm_verify (result i32) + (i32.const 0)) + + ;; Invalid `svm_alloc` signature + (func $svm_alloc (result i32) + (i32.const 0))) \ No newline at end of file diff --git a/crates/runtime/tests/wasm/svm_verify_invalid_sig.wast b/crates/runtime/tests/wasm/svm_verify_invalid_sig.wast new file mode 100644 index 000000000..845de8859 --- /dev/null +++ b/crates/runtime/tests/wasm/svm_verify_invalid_sig.wast @@ -0,0 +1,11 @@ +(module + (export "svm_alloc" (func $svm_alloc)) + (export "svm_verify" (func $svm_verify)) + + ;; Invalid `svm_verify` signature + (func $svm_verify (param i32) (result i32) + (i32.const 0)) + + ;; Valid `svm_alloc` signature + (func $svm_alloc (param i32) (result i32) + (i32.const 0))) \ No newline at end of file diff --git a/crates/sdk/macros/Cargo.toml b/crates/sdk/macros/Cargo.toml index f030c8e34..c0e5077e5 100644 --- a/crates/sdk/macros/Cargo.toml +++ b/crates/sdk/macros/Cargo.toml @@ -31,7 +31,7 @@ lazy_static = "1.4.0" trybuild = { version = "1.0", features = ["diff"] } [features] -refault = ["mock", "dynamic-alloc"] +default = ["mock", "dynamic-alloc"] meta = [] ffi = ["svm-sdk-host/ffi", "svm-sdk-storage/ffi"] mock = ["svm-sdk-host/mock", "svm-sdk-storage/mock"] diff --git a/crates/sdk/macros/src/template.rs b/crates/sdk/macros/src/template.rs index 8dae7ce20..c3792edf5 100644 --- a/crates/sdk/macros/src/template.rs +++ b/crates/sdk/macros/src/template.rs @@ -57,14 +57,13 @@ pub fn expand(_args: TokenStream, input: TokenStream) -> Result<(TokenStream, Te let structs = expand_structs(&template)?; let functions = expand_functions(&template)?; - let alloc_func = alloc_func_ast(); + let verify_export = export_verify_ast(); + let alloc_export = export_alloc_ast(); let ast = quote! { - // #(#imports)* + #verify_export - // #(#aliases)* - - #alloc_func + #alloc_export #structs @@ -266,15 +265,29 @@ fn expand_functions(template: &Template) -> Result { Ok(ast) } -fn alloc_func_ast() -> TokenStream { +fn export_alloc_ast() -> TokenStream { quote! { + // using the `Allocator` of `svm_sdk` extern crate svm_sdk; #[no_mangle] pub extern "C" fn svm_alloc(size: u32) -> u32 { let ptr = svm_sdk::alloc(size as usize); - ptr.offset() as u32 } } } + +fn export_verify_ast() -> TokenStream { + quote! { + #[no_mangle] + pub extern "C" fn svm_verify() -> u32 { + // TODO: + // This is a temporary stub + // + // The Template Author will have to come up with + // his own implementation for `svm_verify` + 0 + } + } +} diff --git a/crates/sdk/types/Cargo.toml b/crates/sdk/types/Cargo.toml index 4e4e1e6c8..ee585256d 100644 --- a/crates/sdk/types/Cargo.toml +++ b/crates/sdk/types/Cargo.toml @@ -15,6 +15,6 @@ svm-sdk-std = { path = "../std", default-features = false } [features] default = [] -debug = ["svm-sdk-std/debug"] +debug = ["svm-sdk-std/debug"] static-alloc = ["svm-sdk-std/static-alloc"] dynamic-alloc = ["svm-sdk-std/dynamic-alloc"] \ No newline at end of file diff --git a/crates/types/src/transaction/mod.rs b/crates/types/src/transaction/mod.rs index ef04df4bf..960508f5d 100644 --- a/crates/types/src/transaction/mod.rs +++ b/crates/types/src/transaction/mod.rs @@ -24,10 +24,9 @@ pub struct Transaction { /// Function's name to execute pub func_name: String, - // TODO: - // Transaction's `VerifyData` - // See issue: https://github.com/spacemeshos/svm/issues/248 - // pub verifydata: Vec, + /// Transaction's `VerifyData` + pub verifydata: Vec, + /// Transaction's `CallData` pub calldata: Vec, } @@ -39,16 +38,14 @@ impl Transaction { } #[doc(hidden)] - pub fn function(&self) -> &str { + pub fn func_name(&self) -> &str { &self.func_name } - // TODO: - // See issue: https://github.com/spacemeshos/svm/issues/248 - // #[doc(hidden)] - // pub fn verifydata(&self) -> &[u8] { - // &self.verifydata - // } + #[doc(hidden)] + pub fn verifydata(&self) -> &[u8] { + &self.verifydata + } #[doc(hidden)] pub fn calldata(&self) -> &[u8] { @@ -58,17 +55,15 @@ impl Transaction { impl fmt::Debug for Transaction { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - // let verifydata = self.verifydata.iter().take(4).collect::>(); + let verifydata = self.verifydata.iter().take(4).collect::>(); let calldata = self.calldata.iter().take(4).collect::>(); f.debug_struct("Transaction") .field("version", &self.version) .field("target", self.target()) - // TODO: - // See issue: https://github.com/spacemeshos/svm/issues/248 - // .field("verifydata", &verifydata) + .field("verifydata", &verifydata) .field("calldata", &calldata) - .field("function", &self.function()) + .field("function", &self.func_name()) .finish() } }