mod decode;
mod encode;
pub mod env_types;
mod scon;
mod transcoder;
mod util;
pub use self::{
scon::{
Hex,
Map,
Tuple,
Value,
},
transcoder::{
Transcoder,
TranscoderBuilder,
},
};
use anyhow::{
Context,
Result,
};
use ink_metadata::{
ConstructorSpec,
InkProject,
MessageSpec,
};
use scale::{
Compact,
Decode,
Input,
};
use scale_info::{
form::{
Form,
PortableForm,
},
Field,
};
use std::{
fmt::Debug,
path::Path,
};
pub struct ContractMessageTranscoder {
metadata: InkProject,
transcoder: Transcoder,
}
impl ContractMessageTranscoder {
pub fn new(metadata: InkProject) -> Self {
let transcoder = TranscoderBuilder::new(metadata.registry())
.register_custom_type_transcoder::<<ink_env::DefaultEnvironment as ink_env::Environment>::AccountId, _>(env_types::AccountId)
.register_custom_type_decoder::<<ink_env::DefaultEnvironment as ink_env::Environment>::Hash, _>(env_types::Hash)
.done();
Self {
metadata,
transcoder,
}
}
pub fn load<P>(metadata_path: P) -> Result<Self>
where
P: AsRef<Path>,
{
let path = metadata_path.as_ref();
let metadata: contract_metadata::ContractMetadata =
contract_metadata::ContractMetadata::load(&metadata_path)?;
let ink_metadata = serde_json::from_value(serde_json::Value::Object(
metadata.abi,
))
.context(format!(
"Failed to deserialize ink project metadata from file {}",
path.display()
))?;
Ok(Self::new(ink_metadata))
}
pub fn encode<I, S>(&self, name: &str, args: I) -> Result<Vec<u8>>
where
I: IntoIterator<Item = S>,
S: AsRef<str> + 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(
self.metadata.registry(),
spec.ty().ty().id(),
&value,
&mut encoded,
)?;
}
Ok(encoded)
}
pub fn decode(&self, type_id: u32, input: &mut &[u8]) -> Result<Value> {
self.transcoder
.decode(self.metadata.registry(), type_id, input)
}
fn constructors(&self) -> impl Iterator<Item = &ConstructorSpec<PortableForm>> {
self.metadata.spec().constructors().iter()
}
fn messages(&self) -> impl Iterator<Item = &MessageSpec<PortableForm>> {
self.metadata.spec().messages().iter()
}
fn find_message_spec(&self, name: &str) -> Option<&MessageSpec<PortableForm>> {
self.messages().find(|msg| msg.label() == &name.to_string())
}
fn find_constructor_spec(
&self,
name: &str,
) -> Option<&ConstructorSpec<PortableForm>> {
self.constructors()
.find(|msg| msg.label() == &name.to_string())
}
pub fn decode_contract_event(&self, data: &mut &[u8]) -> Result<Value> {
let _len = <Compact<u32>>::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
)
})?;
tracing::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.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<Value> {
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)
)
})?;
tracing::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.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<Value> {
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)
)
})?;
tracing::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.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<Value> {
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.decode(return_ty.ty().id(), data)
} else {
Ok(Value::Unit)
}
}
}
impl TryFrom<contract_metadata::ContractMetadata> for ContractMessageTranscoder {
type Error = anyhow::Error;
fn try_from(
metadata: contract_metadata::ContractMetadata,
) -> Result<Self, Self::Error> {
Ok(Self::new(serde_json::from_value(
serde_json::Value::Object(metadata.abi),
)?))
}
}
#[derive(Debug)]
pub enum CompositeTypeFields {
Named(Vec<CompositeTypeNamedField>),
Unnamed(Vec<Field<PortableForm>>),
NoFields,
}
#[derive(Debug)]
pub struct CompositeTypeNamedField {
name: <PortableForm as Form>::String,
field: Field<PortableForm>,
}
impl CompositeTypeNamedField {
pub fn name(&self) -> &str {
&self.name
}
pub fn field(&self) -> &Field<PortableForm> {
&self.field
}
}
impl CompositeTypeFields {
pub fn from_fields(fields: &[Field<PortableForm>]) -> Result<Self> {
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::scon::Hex;
#[allow(clippy::extra_unused_lifetimes)]
#[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<AccountId>) {
let _ = account_ids;
}
#[ink(message)]
pub fn primitive_vec_args(&self, args: Vec<u32>) {
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() -> InkProject {
extern "Rust" {
fn __ink_generate_metadata() -> InkProject;
}
unsafe { __ink_generate_metadata() }
}
#[test]
fn encode_single_primitive_arg() -> Result<()> {
let metadata = generate_metadata();
let transcoder = ContractMessageTranscoder::new(metadata);
let encoded = transcoder.encode("new", ["true"])?;
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"],
)?;
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]"],
)?;
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]"])?;
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",
],
)?;
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]"])?;
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() {
let metadata = generate_metadata();
let transcoder = ContractMessageTranscoder::new(metadata);
let encoded = Result::<bool, ink::primitives::LangError>::Ok(true).encode();
let decoded = transcoder
.decode_return("get", &mut &encoded[..])
.unwrap_or_else(|e| panic!("Error decoding return value {}", e));
let expected = Value::Tuple(Tuple::new(
"Ok".into(),
[Value::Bool(true)].into_iter().collect(),
));
assert_eq!(expected, decoded);
}
#[test]
fn decode_contract_event() -> Result<()> {
let metadata = generate_metadata();
let transcoder = ContractMessageTranscoder::new(metadata);
let encoded = (0u8, [0u32; 32], [1u32; 32]).encode();
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,
];
let encoded = (0u8, hash, [0u32; 32]).encode();
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(())
}
}