// Copyright 2018-2020 Parity Technologies (UK) Ltd. // This file is part of cargo-contract. // // cargo-contract is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // cargo-contract is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with cargo-contract. If not, see . //! For interacting with contracts from the command line, arguments need to be "transcoded" from //! the string representation to the SCALE encoded representation. //! //! e.g. `"false" -> 0x00` //! //! And for displaying SCALE encoded data from events and RPC responses, it must be "transcoded" //! in the other direction from the SCALE encoded representation to a human readable string. //! //! e.g. `0x00 -> "false"` //! //! Transcoding depends on [`scale-info`](https://github.com/paritytech/scale-info/) metadata in //! order to dynamically determine the expected types. //! //! # Encoding //! //! First the string is parsed into an intermediate [`Value`]: //! //! `"false" -> Value::Bool(false)` //! //! This value is then matched with the metadata for the expected type in that context. e.g. the //! [flipper](https://github.com/paritytech/ink/blob/master/examples/flipper/lib.rs) contract //! accepts a `bool` argument to its `new` constructor, which will be reflected in the contract //! metadata as [`scale_info::TypeDefPrimitive::Bool`]. //! //! ```no_compile //! #[ink(constructor)] //! pub fn new(init_value: bool) -> Self { //! Self { value: init_value } //! } //! ``` //! //! The parsed `Value::Bool(false)` argument value is then matched with the //! [`scale_info::TypeDefPrimitive::Bool`] type metadata, and then the value can be safely encoded //! as a `bool`, resulting in `0x00`, which can then be appended as data to the message to invoke //! the constructor. //! //! # Decoding //! //! First the type of the SCALE encoded data is determined from the metadata. e.g. the return type //! of a message when it is invoked as a "dry run" over RPC: //! //! ```no_compile //! #[ink(message)] //! pub fn get(&self) -> bool { //! self.value //! } //! ``` //! //! The metadata will define the return type as [`scale_info::TypeDefPrimitive::Bool`], so that when //! the raw data is received it can be decoded into the correct [`Value`], which is then converted //! to a string for displaying to the user: //! //! `0x00 -> Value::Bool(false) -> "false"` //! //! # SCALE Object Notation (SCON) //! //! Complex types can be represented as strings using `SCON` for human-computer interaction. It is //! intended to be similar to Rust syntax for instantiating types. e.g. //! //! `Foo { a: false, b: [0, 1, 2], c: "bar", d: (0, 1) }` //! //! This string could be parsed into a [`Value::Map`] and together with //! [`scale_info::TypeDefComposite`] metadata could be transcoded into SCALE encoded bytes. //! //! As with the example for the primitive `bool` above, this works in the other direction for //! decoding SCALE encoded bytes and converting them into a human readable string. mod decode; mod encode; pub mod env_types; mod scon; mod transcoder; pub use self::{ scon::{ Map, Value, }, transcoder::{ Transcoder, TranscoderBuilder, }, }; use anyhow::Result; use ink_metadata::{ ConstructorSpec, InkProject, MessageSpec, }; use scale::{ Compact, Decode, Input, }; use scale_info::{ form::{ Form, PortableForm, }, Field, }; use std::fmt::Debug; /// Encode strings to SCALE encoded smart contract calls. /// Decode SCALE encoded smart contract events and return values into `Value` objects. pub struct ContractMessageTranscoder<'a> { metadata: &'a InkProject, transcoder: Transcoder<'a>, } impl<'a> ContractMessageTranscoder<'a> { pub fn new(metadata: &'a InkProject) -> Self { let transcoder = TranscoderBuilder::new(metadata.registry()) .register_custom_type_transcoder::<::AccountId, _>(env_types::AccountId) .register_custom_type_decoder::<::Hash, _>(env_types::Hash) .done(); Self { metadata, transcoder, } } pub fn encode(&self, name: &str, args: I) -> Result> where I: IntoIterator, S: AsRef + Debug, { let (selector, spec_args) = match ( self.find_constructor_spec(name), self.find_message_spec(name), ) { (Some(c), None) => (c.selector(), c.args()), (None, Some(m)) => (m.selector(), m.args()), (Some(_), Some(_)) => { return Err(anyhow::anyhow!( "Invalid metadata: both a constructor and message found with name '{}'", name )) } (None, None) => { return Err(anyhow::anyhow!( "No constructor or message with the name '{}' found", name )) } }; let mut encoded = selector.to_bytes().to_vec(); for (spec, arg) in spec_args.iter().zip(args) { let value = scon::parse_value(arg.as_ref())?; self.transcoder .encode(spec.ty().ty().id(), &value, &mut encoded)?; } Ok(encoded) } fn constructors(&self) -> impl Iterator> { self.metadata.spec().constructors().iter() } fn messages(&self) -> impl Iterator> { self.metadata.spec().messages().iter() } fn find_message_spec(&self, name: &str) -> Option<&MessageSpec> { self.messages().find(|msg| msg.label() == &name.to_string()) } fn find_constructor_spec( &self, name: &str, ) -> Option<&ConstructorSpec> { self.constructors() .find(|msg| msg.label() == &name.to_string()) } pub fn decode_contract_event(&self, data: &mut &[u8]) -> Result { // data is an encoded `Vec` so is prepended with its length `Compact`, which we // ignore because the structure of the event data is known for decoding. let _len = >::decode(data)?; let variant_index = data.read_byte()?; let event_spec = self .metadata .spec() .events() .get(variant_index as usize) .ok_or_else(|| { anyhow::anyhow!( "Event variant {} not found in contract metadata", variant_index ) })?; log::debug!("Decoding contract event '{}'", event_spec.label()); let mut args = Vec::new(); for arg in event_spec.args() { let name = arg.label().to_string(); let value = self.transcoder.decode(arg.ty().ty().id(), data)?; args.push((Value::String(name), value)); } let name = event_spec.label().to_string(); let map = Map::new(Some(&name), args.into_iter().collect()); Ok(Value::Map(map)) } pub fn decode_contract_message(&self, data: &mut &[u8]) -> Result { let mut msg_selector = [0u8; 4]; data.read(&mut msg_selector)?; let msg_spec = self .messages() .find(|x| msg_selector == x.selector().to_bytes()) .ok_or_else(|| { anyhow::anyhow!( "Message with selector {} not found in contract metadata", hex::encode(&msg_selector) ) })?; log::debug!("Decoding contract message '{}'", msg_spec.label()); let mut args = Vec::new(); for arg in msg_spec.args() { let name = arg.label().to_string(); let value = self.transcoder.decode(arg.ty().ty().id(), data)?; args.push((Value::String(name), value)); } let name = msg_spec.label().to_string(); let map = Map::new(Some(&name), args.into_iter().collect()); Ok(Value::Map(map)) } pub fn decode_contract_constructor(&self, data: &mut &[u8]) -> Result { let mut msg_selector = [0u8; 4]; data.read(&mut msg_selector)?; let msg_spec = self .constructors() .find(|x| msg_selector == x.selector().to_bytes()) .ok_or_else(|| { anyhow::anyhow!( "Constructor with selector {} not found in contract metadata", hex::encode(&msg_selector) ) })?; log::debug!("Decoding contract constructor '{}'", msg_spec.label()); let mut args = Vec::new(); for arg in msg_spec.args() { let name = arg.label().to_string(); let value = self.transcoder.decode(arg.ty().ty().id(), data)?; args.push((Value::String(name), value)); } let name = msg_spec.label().to_string(); let map = Map::new(Some(&name), args.into_iter().collect()); Ok(Value::Map(map)) } pub fn decode_return(&self, name: &str, data: &mut &[u8]) -> Result { let msg_spec = self.find_message_spec(name).ok_or_else(|| { anyhow::anyhow!("Failed to find message spec with name '{}'", name) })?; if let Some(return_ty) = msg_spec.return_type().opt_type() { self.transcoder.decode(return_ty.ty().id(), data) } else { Ok(Value::Unit) } } } #[derive(Debug)] pub enum CompositeTypeFields { Named(Vec), Unnamed(Vec>), NoFields, } #[derive(Debug)] pub struct CompositeTypeNamedField { name: ::String, field: Field, } impl CompositeTypeNamedField { pub fn name(&self) -> &str { &self.name } pub fn field(&self) -> &Field { &self.field } } impl CompositeTypeFields { pub fn from_fields(fields: &[Field]) -> Result { if fields.iter().next().is_none() { Ok(Self::NoFields) } else if fields.iter().all(|f| f.name().is_some()) { let fields = fields .iter() .map(|field| { CompositeTypeNamedField { name: field .name() .expect("All fields have a name; qed") .to_owned(), field: field.clone(), } }) .collect(); Ok(Self::Named(fields)) } else if fields.iter().all(|f| f.name().is_none()) { Ok(Self::Unnamed(fields.to_vec())) } else { Err(anyhow::anyhow!( "Struct fields should either be all named or all unnamed" )) } } } #[cfg(test)] mod tests { use super::*; use scale::Encode; use scon::Value; use std::str::FromStr; use crate::cmd::extrinsics::transcode::scon::Hex; use ink_lang as ink; #[ink::contract] pub mod transcode { #[ink(storage)] pub struct Transcode { value: bool, } #[ink(event)] pub struct Event1 { #[ink(topic)] name: Hash, #[ink(topic)] from: AccountId, } impl Transcode { #[ink(constructor)] pub fn new(init_value: bool) -> Self { Self { value: init_value } } #[ink(constructor)] pub fn default() -> Self { Self::new(Default::default()) } #[ink(message)] pub fn flip(&mut self) { self.value = !self.value; } #[ink(message)] pub fn get(&self) -> bool { self.value } #[ink(message)] pub fn set_account_id(&self, account_id: AccountId) { let _ = account_id; } #[ink(message)] pub fn set_account_ids_vec(&self, account_ids: Vec) { let _ = account_ids; } #[ink(message)] pub fn primitive_vec_args(&self, args: Vec) { let _ = args; } #[ink(message)] pub fn uint_args( &self, _u8: u8, _u16: u16, _u32: u32, _u64: u64, _u128: u128, ) { } #[ink(message)] pub fn uint_array_args(&self, arr: [u8; 4]) { let _ = arr; } } } fn generate_metadata() -> ink_metadata::InkProject { extern "Rust" { fn __ink_generate_metadata() -> ink_metadata::MetadataVersioned; } let metadata_versioned = unsafe { __ink_generate_metadata() }; if let ink_metadata::MetadataVersioned::V3(ink_project) = metadata_versioned { ink_project } else { panic!("Expected metadata V3"); } } #[test] fn encode_single_primitive_arg() -> Result<()> { let metadata = generate_metadata(); let transcoder = ContractMessageTranscoder::new(&metadata); let encoded = transcoder.encode("new", &["true"])?; // encoded args follow the 4 byte selector let encoded_args = &encoded[4..]; assert_eq!(true.encode(), encoded_args); Ok(()) } #[test] fn encode_account_id_custom_ss58_encoding() -> Result<()> { let metadata = generate_metadata(); let transcoder = ContractMessageTranscoder::new(&metadata); let encoded = transcoder.encode( "set_account_id", &["5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"], )?; // encoded args follow the 4 byte selector let encoded_args = &encoded[4..]; let expected = sp_core::crypto::AccountId32::from_str( "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", ) .unwrap(); assert_eq!(expected.encode(), encoded_args); Ok(()) } #[test] fn encode_account_ids_vec_args() -> Result<()> { let metadata = generate_metadata(); let transcoder = ContractMessageTranscoder::new(&metadata); let encoded = transcoder.encode( "set_account_ids_vec", &["[5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY, 5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty]"], )?; // encoded args follow the 4 byte selector let encoded_args = &encoded[4..]; let expected = vec![ sp_core::crypto::AccountId32::from_str( "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY", ) .unwrap(), sp_core::crypto::AccountId32::from_str( "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty", ) .unwrap(), ]; assert_eq!(expected.encode(), encoded_args); Ok(()) } #[test] fn encode_primitive_vec_args() -> Result<()> { let metadata = generate_metadata(); let transcoder = ContractMessageTranscoder::new(&metadata); let encoded = transcoder.encode("primitive_vec_args", &["[1, 2]"])?; // encoded args follow the 4 byte selector let encoded_args = &encoded[4..]; let expected = vec![1, 2]; assert_eq!(expected.encode(), encoded_args); Ok(()) } #[test] fn encode_uint_hex_literals() -> Result<()> { let metadata = generate_metadata(); let transcoder = ContractMessageTranscoder::new(&metadata); let encoded = transcoder.encode( "uint_args", &[ "0x00", "0xDEAD", "0xDEADBEEF", "0xDEADBEEF12345678", "0xDEADBEEF0123456789ABCDEF01234567", ], )?; // encoded args follow the 4 byte selector let encoded_args = &encoded[4..]; let expected = ( 0x00u8, 0xDEADu16, 0xDEADBEEFu32, 0xDEADBEEF12345678u64, 0xDEADBEEF0123456789ABCDEF01234567u128, ); assert_eq!(expected.encode(), encoded_args); Ok(()) } #[test] fn encode_uint_arr_hex_literals() -> Result<()> { let metadata = generate_metadata(); let transcoder = ContractMessageTranscoder::new(&metadata); let encoded = transcoder.encode("uint_array_args", &["[0xDE, 0xAD, 0xBE, 0xEF]"])?; // encoded args follow the 4 byte selector let encoded_args = &encoded[4..]; let expected: [u8; 4] = [0xDE, 0xAD, 0xBE, 0xEF]; assert_eq!(expected.encode(), encoded_args); Ok(()) } #[test] fn decode_primitive_return() -> Result<()> { let metadata = generate_metadata(); let transcoder = ContractMessageTranscoder::new(&metadata); let encoded = true.encode(); let decoded = transcoder.decode_return("get", &mut &encoded[..])?; assert_eq!(Value::Bool(true), decoded); Ok(()) } #[test] fn decode_contract_event() -> Result<()> { let metadata = generate_metadata(); let transcoder = ContractMessageTranscoder::new(&metadata); // raw encoded event with event index prefix let encoded = (0u8, [0u32; 32], [1u32; 32]).encode(); // encode again as a Vec which has a len prefix. let encoded_bytes = encoded.encode(); let _ = transcoder.decode_contract_event(&mut &encoded_bytes[..])?; Ok(()) } #[test] fn decode_hash_as_hex_encoded_string() -> Result<()> { let metadata = generate_metadata(); let transcoder = ContractMessageTranscoder::new(&metadata); let hash = [ 52u8, 40, 235, 225, 70, 245, 184, 36, 21, 218, 130, 114, 75, 207, 117, 240, 83, 118, 135, 56, 220, 172, 95, 131, 171, 125, 130, 167, 10, 15, 242, 222, ]; // raw encoded event with event index prefix let encoded = (0u8, hash, [0u32; 32]).encode(); // encode again as a Vec which has a len prefix. let encoded_bytes = encoded.encode(); let decoded = transcoder.decode_contract_event(&mut &encoded_bytes[..])?; if let Value::Map(ref map) = decoded { let name_field = &map[&Value::String("name".into())]; if let Value::Hex(hex) = name_field { assert_eq!(&Hex::from_str("0x3428ebe146f5b82415da82724bcf75f053768738dcac5f83ab7d82a70a0ff2de")?, hex); Ok(()) } else { Err(anyhow::anyhow!( "Expected a name field hash encoded as Hex value, was {:?}", name_field )) } } else { Err(anyhow::anyhow!( "Expected a Value::Map for the decoded event" )) } } #[test] fn decode_contract_message() -> Result<()> { let metadata = generate_metadata(); let transcoder = ContractMessageTranscoder::new(&metadata); let encoded_bytes = hex::decode("633aa551").unwrap(); let _ = transcoder.decode_contract_message(&mut &encoded_bytes[..])?; Ok(()) } }