Skip to main content

contract_transcode/
lib.rs

1// Copyright (C) Use Ink (UK) Ltd.
2// This file is part of cargo-contract.
3//
4// cargo-contract is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// cargo-contract is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with cargo-contract.  If not, see <http://www.gnu.org/licenses/>.
16
17//! For interacting with contracts from the command line, arguments need to be
18//! "transcoded" from the string representation to the SCALE encoded representation.
19//!
20//! e.g. `"false" -> 0x00`
21//!
22//! And for displaying SCALE encoded data from events and RPC responses, it must be
23//! "transcoded" in the other direction from the SCALE encoded representation to a
24//! human-readable string.
25//!
26//! e.g. `0x00 -> "false"`
27//!
28//! Transcoding depends on [`scale-info`](https://github.com/paritytech/scale-info/) metadata in
29//! order to dynamically determine the expected types.
30//!
31//! # Encoding
32//!
33//! First the string is parsed into an intermediate [`Value`]:
34//!
35//! `"false" -> Value::Bool(false)`
36//!
37//! This value is then matched with the metadata for the expected type in that context.
38//! e.g. the [flipper](https://github.com/use-ink/ink/blob/master/examples/flipper/lib.rs) contract
39//! accepts a `bool` argument to its `new` constructor, which will be reflected in the
40//! contract metadata as [`scale_info::TypeDefPrimitive::Bool`].
41//!
42//! ```no_compile
43//! #[ink(constructor)]
44//! pub fn new(init_value: bool) -> Self {
45//!     Self { value: init_value }
46//! }
47//! ```
48//!
49//! The parsed `Value::Bool(false)` argument value is then matched with the
50//! [`scale_info::TypeDefPrimitive::Bool`] type metadata, and then the value can be safely
51//! encoded as a `bool`, resulting in `0x00`, which can then be appended as data to the
52//! message to invoke the constructor.
53//!
54//! # Decoding
55//!
56//! First the type of the SCALE encoded data is determined from the metadata. e.g. the
57//! return type of a message when it is invoked as a "dry run" over RPC:
58//!
59//! ```no_compile
60//! #[ink(message)]
61//! pub fn get(&self) -> bool {
62//!     self.value
63//! }
64//! ```
65//!
66//! The metadata will define the return type as [`scale_info::TypeDefPrimitive::Bool`], so
67//! that when the raw data is received it can be decoded into the correct [`Value`], which
68//! is then converted to a string for displaying to the user:
69//!
70//! `0x00 -> Value::Bool(false) -> "false"`
71//!
72//! # SCALE Object Notation (SCON)
73//!
74//! Complex types can be represented as strings using `SCON` for human-computer
75//! interaction. It is intended to be similar to Rust syntax for instantiating types. e.g.
76//!
77//! `Foo { a: false, b: [0, 1, 2], c: "bar", d: (0, 1) }`
78//!
79//! This string could be parsed into a [`Value::Map`] and together with
80//! [`scale_info::TypeDefComposite`] metadata could be transcoded into SCALE encoded
81//! bytes.
82//!
83//! As with the example for the primitive `bool` above, this works in the other direction
84//! for decoding SCALE encoded bytes and converting them into a human readable string.
85//!
86//! # Example
87//! ```no_run
88//! # use contract_metadata::ContractMetadata;
89//! # use contract_transcode::ContractMessageTranscoder;
90//! # use std::{path::Path, fs::File};
91//! let metadata_path = Path::new("/path/to/contract.json");
92//! let transcoder = ContractMessageTranscoder::load(metadata_path).unwrap();
93//!
94//! let constructor = "new";
95//! let args = ["foo", "bar"];
96//! let data = transcoder.encode(&constructor, &args).unwrap();
97//!
98//! println!("Encoded constructor data {:?}", data);
99//! ```
100
101mod account_id;
102mod decode;
103mod encode;
104pub mod env_types;
105mod scon;
106mod transcoder;
107mod util;
108
109pub use self::{
110    account_id::AccountId32,
111    scon::{
112        Hex,
113        Map,
114        Tuple,
115        Value,
116    },
117    transcoder::{
118        Transcoder,
119        TranscoderBuilder,
120    },
121};
122
123use anyhow::{
124    Context,
125    Result,
126};
127pub use ink_metadata;
128use ink_metadata::{
129    ConstructorSpec,
130    InkProject,
131    MessageSpec,
132};
133use itertools::Itertools;
134use regex::Regex;
135use scale::{
136    Compact,
137    Decode,
138    Input,
139};
140use scale_info::{
141    Field,
142    PortableRegistry,
143    TypeDef,
144    TypeDefPrimitive,
145    form::{
146        Form,
147        PortableForm,
148    },
149};
150use std::{
151    cmp::Ordering,
152    fmt::Debug,
153    path::Path,
154};
155
156/// Encode strings to SCALE encoded smart contract calls.
157/// Decode SCALE encoded smart contract events and return values into `Value` objects.
158pub struct ContractMessageTranscoder {
159    metadata: InkProject,
160    transcoder: Transcoder,
161}
162
163/// Find strings from an iterable of `possible_values` similar to a given value `v`
164/// Returns a Vec of all possible values that exceed a similarity threshold
165/// sorted by ascending similarity, most similar comes last
166/// Extracted from https://github.com/clap-rs/clap/blob/v4.3.4/clap_builder/src/parser/features/suggestions.rs#L11-L26
167fn did_you_mean<T, I>(v: &str, possible_values: I) -> Vec<String>
168where
169    T: AsRef<str>,
170    I: IntoIterator<Item = T>,
171{
172    let mut candidates: Vec<(f64, String)> = possible_values
173        .into_iter()
174        .map(|pv| (strsim::jaro(v, pv.as_ref()), pv.as_ref().to_owned()))
175        .filter(|(confidence, _)| *confidence > 0.7)
176        .collect();
177    candidates.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(Ordering::Equal));
178    candidates.into_iter().map(|(_, pv)| pv).collect()
179}
180
181/// Parse an argument with type hints from the contract metadata.
182///
183/// This function enables intuitive argument parsing by using type information
184/// from the contract metadata. For String types, it accepts unquoted strings
185/// directly, while maintaining backward compatibility with SCON quoted strings.
186///
187/// # Examples
188///
189/// For a String parameter:
190/// - `Alice` → parsed as string "Alice" (new behavior)
191/// - `"Alice"` → parsed as string "Alice" (backward compatible)
192/// - `my string` → parsed as string "my string" (with spaces)
193///
194/// For other types, standard SCON parsing applies:
195/// - `true` → boolean
196/// - `42` → integer
197/// - `[1, 2, 3]` → array
198fn parse_with_type_hint(
199    arg: &str,
200    type_id: u32,
201    registry: &PortableRegistry,
202) -> Result<Value> {
203    // Get the expected type from metadata
204    let ty = registry
205        .resolve(type_id)
206        .ok_or_else(|| anyhow::anyhow!("Type {type_id} not found in registry"))?;
207    // For String types, provide special handling
208    if matches!(&ty.type_def, TypeDef::Primitive(TypeDefPrimitive::Str)) {
209        // If the argument is already properly quoted for SCON, parse it normally
210        if arg.starts_with('"') && arg.ends_with('"') {
211            return scon::parse_value(arg);
212        }
213        // Otherwise, treat the entire argument as a plain string.
214        // This allows users to pass strings without explicit quotes.
215        return Ok(Value::String(arg.to_string()));
216    }
217    // For all other types, use standard SCON parsing
218    scon::parse_value(arg)
219}
220
221impl ContractMessageTranscoder {
222    pub fn new(metadata: InkProject) -> Self {
223        let transcoder = TranscoderBuilder::new(metadata.registry())
224            .with_default_custom_type_transcoders()
225            .done();
226        Self {
227            metadata,
228            transcoder,
229        }
230    }
231
232    /// Attempt to create a [`ContractMessageTranscoder`] from the metadata file at the
233    /// given path.
234    pub fn load<P>(metadata_path: P) -> Result<Self>
235    where
236        P: AsRef<Path>,
237    {
238        let path = metadata_path.as_ref();
239        let metadata: contract_metadata::ContractMetadata =
240            contract_metadata::ContractMetadata::load(&metadata_path)?;
241        let ink_metadata = serde_json::from_value(serde_json::Value::Object(
242            metadata.abi,
243        ))
244        .context(format!(
245            "Failed to deserialize ink project metadata from file {}",
246            path.display()
247        ))?;
248
249        Ok(Self::new(ink_metadata))
250    }
251
252    pub fn encode<I, S>(&self, name: &str, args: I) -> Result<Vec<u8>>
253    where
254        I: IntoIterator<Item = S>,
255        S: AsRef<str> + Debug,
256    {
257        let (selector, spec_args) = match (
258            self.find_constructor_spec(name),
259            self.find_message_spec(name),
260        ) {
261            (Some(c), None) => (c.selector(), c.args()),
262            (None, Some(m)) => (m.selector(), m.args()),
263            (Some(_), Some(_)) => {
264                return Err(anyhow::anyhow!(
265                    "Invalid metadata: both a constructor and message found with name '{name}'"
266                ));
267            }
268            (None, None) => {
269                let constructors = self.constructors().map(|c| c.label());
270                let messages = self.messages().map(|c| c.label());
271                let possible_values: Vec<_> = constructors.chain(messages).collect();
272                let help_txt = did_you_mean(name, possible_values.clone())
273                    .first()
274                    .map(|suggestion| format!("Did you mean '{suggestion}'?"))
275                    .unwrap_or_else(|| {
276                        format!("Should be one of: {}", possible_values.iter().join(", "))
277                    });
278
279                return Err(anyhow::anyhow!(
280                    "No constructor or message with the name '{name}' found.\n{help_txt}",
281                ));
282            }
283        };
284
285        let args: Vec<_> = args.into_iter().collect();
286        if spec_args.len() != args.len() {
287            anyhow::bail!(
288                "Invalid number of input arguments: expected {}, {} provided",
289                spec_args.len(),
290                args.len()
291            )
292        }
293
294        let mut encoded = selector.to_bytes().to_vec();
295        for (spec, arg) in spec_args.iter().zip(args) {
296            assert_not_shortened_hex(arg.as_ref());
297
298            // Use context-aware parsing with type hints from metadata.
299            // This allows intuitive string arguments without explicit quotes.
300            let value = parse_with_type_hint(
301                arg.as_ref(),
302                spec.ty().ty().id,
303                self.metadata.registry(),
304            )?;
305
306            self.transcoder.encode(
307                self.metadata.registry(),
308                spec.ty().ty().id,
309                &value,
310                &mut encoded,
311            )?;
312        }
313        Ok(encoded)
314    }
315
316    pub fn decode(&self, type_id: u32, input: &mut &[u8]) -> Result<Value> {
317        self.transcoder
318            .decode(self.metadata.registry(), type_id, input)
319    }
320
321    pub fn metadata(&self) -> &InkProject {
322        &self.metadata
323    }
324
325    fn constructors(&self) -> impl Iterator<Item = &ConstructorSpec<PortableForm>> {
326        self.metadata.spec().constructors().iter()
327    }
328
329    fn messages(&self) -> impl Iterator<Item = &MessageSpec<PortableForm>> {
330        self.metadata.spec().messages().iter()
331    }
332
333    fn find_message_spec(&self, name: &str) -> Option<&MessageSpec<PortableForm>> {
334        self.messages().find(|msg| msg.label() == &name.to_string())
335    }
336
337    fn find_constructor_spec(
338        &self,
339        name: &str,
340    ) -> Option<&ConstructorSpec<PortableForm>> {
341        self.constructors()
342            .find(|msg| msg.label() == &name.to_string())
343    }
344
345    pub fn decode_contract_event<Hash>(
346        &self,
347        event_sig_topic: &Hash,
348        data: &mut &[u8],
349    ) -> Result<Value>
350    where
351        Hash: AsRef<[u8]>,
352    {
353        // data is an encoded `Vec<u8>` so is prepended with its length `Compact<u32>`,
354        // which we ignore because the structure of the event data is known for
355        // decoding.
356        let _len = <Compact<u32>>::decode(data)?;
357        let event_spec = self
358            .metadata
359            .spec()
360            .events()
361            .iter()
362            .find(|event| {
363                if let Some(sig_topic) = event.signature_topic() {
364                    sig_topic.as_bytes() == event_sig_topic.as_ref()
365                } else {
366                    false
367                }
368            })
369            .ok_or_else(|| {
370                anyhow::anyhow!(
371                    "Event with signature topic {} not found in contract metadata",
372                    hex::encode(event_sig_topic)
373                )
374            })?;
375        tracing::debug!("Decoding contract event '{}'", event_spec.label());
376
377        let mut args = Vec::new();
378        for arg in event_spec.args() {
379            let name = arg.label().to_string();
380            let value = self.decode(arg.ty().ty().id, data)?;
381            args.push((Value::String(name), value));
382        }
383
384        Self::validate_length(data, event_spec.label(), &args)?;
385
386        let name = event_spec.label().to_string();
387        let map = Map::new(Some(&name), args.into_iter().collect());
388
389        Ok(Value::Map(map))
390    }
391
392    pub fn decode_contract_message(&self, data: &mut &[u8]) -> Result<Value> {
393        let mut msg_selector = [0u8; 4];
394        data.read(&mut msg_selector)?;
395        let msg_spec = self
396            .messages()
397            .find(|x| msg_selector == x.selector().to_bytes())
398            .ok_or_else(|| {
399                anyhow::anyhow!(
400                    "Message with selector {} not found in contract metadata",
401                    hex::encode_upper(msg_selector)
402                )
403            })?;
404        tracing::debug!("Decoding contract message '{}'", msg_spec.label());
405
406        let mut args = Vec::new();
407        for arg in msg_spec.args() {
408            let name = arg.label().to_string();
409            let value = self.decode(arg.ty().ty().id, data)?;
410            args.push((Value::String(name), value));
411        }
412
413        Self::validate_length(data, msg_spec.label(), &args)?;
414
415        let name = msg_spec.label().to_string();
416        let map = Map::new(Some(&name), args.into_iter().collect());
417
418        Ok(Value::Map(map))
419    }
420
421    pub fn decode_contract_constructor(&self, data: &mut &[u8]) -> Result<Value> {
422        let mut msg_selector = [0u8; 4];
423        data.read(&mut msg_selector)?;
424        let msg_spec = self
425            .constructors()
426            .find(|x| msg_selector == x.selector().to_bytes())
427            .ok_or_else(|| {
428                anyhow::anyhow!(
429                    "Constructor with selector {} not found in contract metadata",
430                    hex::encode_upper(msg_selector)
431                )
432            })?;
433        tracing::debug!("Decoding contract constructor '{}'", msg_spec.label());
434
435        let mut args = Vec::new();
436        for arg in msg_spec.args() {
437            let name = arg.label().to_string();
438            let value = self.decode(arg.ty().ty().id, data)?;
439            args.push((Value::String(name), value));
440        }
441
442        Self::validate_length(data, msg_spec.label(), &args)?;
443
444        let name = msg_spec.label().to_string();
445        let map = Map::new(Some(&name), args.into_iter().collect());
446
447        Ok(Value::Map(map))
448    }
449
450    pub fn decode_constructor_return(
451        &self,
452        name: &str,
453        data: &mut &[u8],
454    ) -> Result<Value> {
455        let ctor_spec = self.find_constructor_spec(name).ok_or_else(|| {
456            anyhow::anyhow!("Failed to find constructor spec with name '{name}'")
457        })?;
458        let return_ty = ctor_spec.return_type().ret_type();
459        self.decode(return_ty.ty().id, data)
460    }
461
462    pub fn decode_message_return(&self, name: &str, data: &mut &[u8]) -> Result<Value> {
463        let msg_spec = self.find_message_spec(name).ok_or_else(|| {
464            anyhow::anyhow!("Failed to find message spec with name '{name}'")
465        })?;
466        let return_ty = msg_spec.return_type().ret_type();
467        self.decode(return_ty.ty().id, data)
468    }
469
470    /// Checks if buffer empty, otherwise returns am error
471    fn validate_length(data: &[u8], label: &str, args: &[(Value, Value)]) -> Result<()> {
472        if !data.is_empty() {
473            let arg_list_string: String =
474                args.iter().fold(format!("`{label}`"), |init, arg| {
475                    format!("{}, `{}`", init, arg.0)
476                });
477            let encoded_bytes = hex::encode_upper(data);
478            return Err(anyhow::anyhow!(
479                "input length was longer than expected by {} byte(s).\nManaged to decode {} but `{}` bytes were left unread",
480                data.len(),
481                arg_list_string,
482                encoded_bytes
483            ));
484        }
485        Ok(())
486    }
487}
488
489// Assert that `arg` is not in a shortened format a la `0xbc3f…f58a`.
490fn assert_not_shortened_hex(arg: &str) {
491    let re = Regex::new(r"^0x[a-fA-F0-9]+…[a-fA-F0-9]+$").unwrap();
492    if re.is_match(arg) {
493        panic!(
494            "Error: You are attempting to transcode a shortened hex value: `{arg:?}`.\n\
495                This would result in a different return value than the un-shortened hex value.\n\
496                You likely called `to_string()` on e.g. `H160` and got a shortened output."
497        );
498    }
499}
500
501impl TryFrom<contract_metadata::ContractMetadata> for ContractMessageTranscoder {
502    type Error = anyhow::Error;
503
504    fn try_from(
505        metadata: contract_metadata::ContractMetadata,
506    ) -> Result<Self, Self::Error> {
507        Ok(Self::new(serde_json::from_value(
508            serde_json::Value::Object(metadata.abi),
509        )?))
510    }
511}
512
513#[derive(Debug)]
514pub enum CompositeTypeFields {
515    Named(Vec<CompositeTypeNamedField>),
516    Unnamed(Vec<Field<PortableForm>>),
517    NoFields,
518}
519
520#[derive(Debug)]
521pub struct CompositeTypeNamedField {
522    name: <PortableForm as Form>::String,
523    field: Field<PortableForm>,
524}
525
526impl CompositeTypeNamedField {
527    pub fn name(&self) -> &str {
528        &self.name
529    }
530
531    pub fn field(&self) -> &Field<PortableForm> {
532        &self.field
533    }
534}
535
536impl CompositeTypeFields {
537    pub fn from_fields(fields: &[Field<PortableForm>]) -> Result<Self> {
538        if fields.iter().next().is_none() {
539            Ok(Self::NoFields)
540        } else if fields.iter().all(|f| f.name.is_some()) {
541            let fields = fields
542                .iter()
543                .map(|field| {
544                    CompositeTypeNamedField {
545                        name: field
546                            .name
547                            .as_ref()
548                            .expect("All fields have a name; qed")
549                            .to_owned(),
550                        field: field.clone(),
551                    }
552                })
553                .collect();
554            Ok(Self::Named(fields))
555        } else if fields.iter().all(|f| f.name.is_none()) {
556            Ok(Self::Unnamed(fields.to_vec()))
557        } else {
558            Err(anyhow::anyhow!(
559                "Struct fields should either be all named or all unnamed"
560            ))
561        }
562    }
563}
564
565#[cfg(test)]
566// For contract re-exports and `cfg`s.
567#[allow(unused_imports, unexpected_cfgs)]
568mod tests {
569    use super::*;
570    use crate::scon::Hex;
571    use ink_env::{
572        DefaultEnvironment,
573        Environment,
574    };
575    use primitive_types::H256;
576    use scale::Encode;
577    use scon::Value;
578    use std::str::FromStr;
579
580    #[allow(clippy::extra_unused_lifetimes, non_local_definitions)]
581    #[ink::contract]
582    pub mod transcode {
583        #[ink(storage)]
584        pub struct Transcode {
585            value: bool,
586        }
587
588        #[ink(event)]
589        pub struct Event1 {
590            #[ink(topic)]
591            name: Hash,
592            #[ink(topic)]
593            from: AccountId,
594        }
595
596        impl Transcode {
597            #[ink(constructor)]
598            pub fn new(init_value: bool) -> Self {
599                Self { value: init_value }
600            }
601
602            #[ink(constructor)]
603            pub fn default() -> Self {
604                Self::new(Default::default())
605            }
606
607            #[ink(message)]
608            pub fn flip(&mut self) {
609                self.value = !self.value;
610            }
611
612            #[ink(message)]
613            pub fn get(&self) -> bool {
614                self.value
615            }
616
617            #[ink(message)]
618            pub fn get_complex(
619                &self,
620            ) -> (u32, ink::H160, ink::H256, ink::U256, AccountId) {
621                (
622                    32u32,
623                    self.env().address(),
624                    self.env().own_code_hash(),
625                    //self.env().transferred_value()
626                    ink::U256::one(),
627                    AccountId::from([0x17; 32]),
628                )
629            }
630            //pub fn get_complex(&mut self) -> (ink::H160, Hash, ink::H256, ink::U256) {
631            //(self.env().address(), [0xABu8; 32].into(),
632            /*
633            pub fn get_complex(&self) -> (ink::H160, ink::H256, ink::U256) {
634                (self.env().address(),
635                self.env().own_code_hash(), self.env().transferred_value())
636            }
637             */
638
639            #[ink(message)]
640            pub fn set_account_id(&self, account_id: AccountId) {
641                let _ = account_id;
642            }
643
644            #[ink(message)]
645            pub fn set_account_ids_vec(&self, account_ids: Vec<AccountId>) {
646                let _ = account_ids;
647            }
648
649            #[ink(message)]
650            pub fn primitive_vec_args(&self, args: Vec<u32>) {
651                let _ = args;
652            }
653
654            #[ink(message)]
655            pub fn uint_args(
656                &self,
657                _u8: u8,
658                _u16: u16,
659                _u32: u32,
660                _u64: u64,
661                _u128: u128,
662            ) {
663            }
664
665            #[ink(message)]
666            pub fn uint_array_args(&self, arr: [u8; 4]) {
667                let _ = arr;
668            }
669
670            #[ink(message)]
671            pub fn h160(&self, addr: ink::H160) {
672                let _ = addr;
673            }
674
675            #[ink(message)]
676            pub fn set_name(&self, _name: String) {}
677        }
678    }
679
680    fn generate_metadata() -> InkProject {
681        unsafe extern "Rust" {
682            fn __ink_generate_metadata() -> InkProject;
683        }
684
685        unsafe { __ink_generate_metadata() }
686    }
687
688    #[test]
689    fn encode_single_primitive_arg() -> Result<()> {
690        let metadata = generate_metadata();
691        let transcoder = ContractMessageTranscoder::new(metadata);
692
693        let encoded = transcoder.encode("new", ["true"])?;
694        // encoded args follow the 4 byte selector
695        let encoded_args = &encoded[4..];
696
697        assert_eq!(true.encode(), encoded_args);
698        Ok(())
699    }
700
701    #[test]
702    fn encode_misspelled_arg() {
703        let metadata = generate_metadata();
704        let transcoder = ContractMessageTranscoder::new(metadata);
705        assert_eq!(
706            transcoder.encode("fip", ["true"]).unwrap_err().to_string(),
707            "No constructor or message with the name 'fip' found.\nDid you mean 'flip'?"
708        );
709    }
710
711    #[test]
712    fn encode_mismatching_args_length() {
713        let metadata = generate_metadata();
714        let transcoder = ContractMessageTranscoder::new(metadata);
715
716        let result: Result<Vec<u8>> = transcoder.encode("new", Vec::<&str>::new());
717        assert!(result.is_err(), "Should return an error");
718        assert_eq!(
719            result.unwrap_err().to_string(),
720            "Invalid number of input arguments: expected 1, 0 provided"
721        );
722
723        let result: Result<Vec<u8>> = transcoder.encode("new", ["true", "false"]);
724        assert!(result.is_err(), "Should return an error");
725        assert_eq!(
726            result.unwrap_err().to_string(),
727            "Invalid number of input arguments: expected 1, 2 provided"
728        );
729    }
730
731    #[test]
732    fn encode_account_id_custom_ss58_encoding() -> Result<()> {
733        let metadata = generate_metadata();
734        let transcoder = ContractMessageTranscoder::new(metadata);
735
736        let encoded = transcoder.encode(
737            "set_account_id",
738            ["5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"],
739        )?;
740
741        // encoded args follow the 4 byte selector
742        let encoded_args = &encoded[4..];
743
744        let expected =
745            AccountId32::from_str("5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY")
746                .unwrap();
747        assert_eq!(expected.encode(), encoded_args);
748        Ok(())
749    }
750
751    #[test]
752    fn encode_account_ids_vec_args() -> Result<()> {
753        let metadata = generate_metadata();
754        let transcoder = ContractMessageTranscoder::new(metadata);
755
756        let encoded = transcoder.encode(
757            "set_account_ids_vec",
758            ["[5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY, 5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty]"],
759        )?;
760
761        // encoded args follow the 4 byte selector
762        let encoded_args = &encoded[4..];
763
764        let expected = vec![
765            AccountId32::from_str("5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY")
766                .unwrap(),
767            AccountId32::from_str("5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty")
768                .unwrap(),
769        ];
770        assert_eq!(expected.encode(), encoded_args);
771        Ok(())
772    }
773
774    #[test]
775    fn encode_primitive_vec_args() -> Result<()> {
776        let metadata = generate_metadata();
777        let transcoder = ContractMessageTranscoder::new(metadata);
778
779        let encoded = transcoder.encode("primitive_vec_args", ["[1, 2]"])?;
780
781        // encoded args follow the 4 byte selector
782        let encoded_args = &encoded[4..];
783
784        let expected = vec![1, 2];
785        assert_eq!(expected.encode(), encoded_args);
786        Ok(())
787    }
788
789    #[test]
790    fn encode_uint_hex_literals() -> Result<()> {
791        let metadata = generate_metadata();
792        let transcoder = ContractMessageTranscoder::new(metadata);
793
794        let encoded = transcoder.encode(
795            "uint_args",
796            [
797                "0x00",
798                "0xDEAD",
799                "0xDEADBEEF",
800                "0xDEADBEEF12345678",
801                "0xDEADBEEF0123456789ABCDEF01234567",
802            ],
803        )?;
804
805        // encoded args follow the 4 byte selector
806        let encoded_args = &encoded[4..];
807
808        let expected = (
809            0x00u8,
810            0xDEADu16,
811            0xDEADBEEFu32,
812            0xDEADBEEF12345678u64,
813            0xDEADBEEF0123456789ABCDEF01234567u128,
814        );
815        assert_eq!(expected.encode(), encoded_args);
816        Ok(())
817    }
818
819    #[test]
820    fn encode_uint_arr_hex_literals() -> Result<()> {
821        let metadata = generate_metadata();
822        let transcoder = ContractMessageTranscoder::new(metadata);
823
824        let encoded =
825            transcoder.encode("uint_array_args", ["[0xDE, 0xAD, 0xBE, 0xEF]"])?;
826
827        // encoded args follow the 4 byte selector
828        let encoded_args = &encoded[4..];
829
830        let expected: [u8; 4] = [0xDE, 0xAD, 0xBE, 0xEF];
831        assert_eq!(expected.encode(), encoded_args);
832        Ok(())
833    }
834
835    #[test]
836    #[should_panic(
837        expected = "Error: You are attempting to transcode a shortened hex value: `\"0xbc3f…f58a\"`"
838    )]
839    fn encode_must_panic_on_shortened_hex() {
840        let metadata = generate_metadata();
841        let transcoder = ContractMessageTranscoder::new(metadata);
842
843        let _encoded = transcoder.encode("h160", ["0xbc3f…f58a"]);
844    }
845
846    #[test]
847    fn decode_complex_return() {
848        let metadata = generate_metadata();
849        let transcoder = ContractMessageTranscoder::new(metadata);
850
851        let addr: ink::H160 = ink::H160::from([0x42; 20]);
852        let account_id: AccountId32 = AccountId32::from([0x13; 32]);
853        let _hash: [u8; 32] = [0xAB; 32];
854        let h256: ink::H256 = ink::H256::from([0x17; 32]);
855        // todo let value: ink::U256 = ink::U256::MAX;
856        let value: ink::U256 = ink::U256::one();
857
858        let encoded = Result::<
859            (u32, ink::H160, ink::H256, ink::U256, AccountId32),
860            ink::primitives::LangError,
861        >::Ok((32, addr, h256, value, account_id))
862        .encode();
863
864        let decoded = transcoder
865            .decode_message_return("get_complex", &mut &encoded[..])
866            .unwrap_or_else(|e| panic!("Error decoding return value {e}"));
867
868        let expected = Value::Tuple(Tuple::new(
869            "Ok".into(),
870            [
871                Value::Tuple(Tuple::new(
872                    None,
873                    [
874                        Value::UInt(32),
875                        Value::Hex(Hex::from_str("0x4242424242424242424242424242424242424242").unwrap()),
876                        Value::Hex(Hex::from_str("0x1717171717171717171717171717171717171717171717171717171717171717").unwrap()),
877                        Value::Literal("1".to_string()),
878                        Value::Literal("5CViS5pKamF1VbJ9tmQKPNDpLpJaBCfpPw2m49UzQ8zgiDGT".to_string()),
879                    ]
880                    .into_iter().collect()
881                ))
882            ]
883            .into_iter().collect(),
884        ));
885        assert_eq!(expected, decoded);
886    }
887
888    #[test]
889    fn decode_primitive_return() {
890        let metadata = generate_metadata();
891        let transcoder = ContractMessageTranscoder::new(metadata);
892
893        let encoded = Result::<bool, ink::primitives::LangError>::Ok(true).encode();
894        let decoded = transcoder
895            .decode_message_return("get", &mut &encoded[..])
896            .unwrap_or_else(|e| panic!("Error decoding return value {e}"));
897
898        let expected = Value::Tuple(Tuple::new(
899            "Ok".into(),
900            [Value::Bool(true)].into_iter().collect(),
901        ));
902        assert_eq!(expected, decoded);
903    }
904
905    #[test]
906    fn decode_lang_error() {
907        use ink::primitives::LangError;
908
909        let metadata = generate_metadata();
910        let transcoder = ContractMessageTranscoder::new(metadata);
911
912        let encoded =
913            Result::<bool, LangError>::Err(LangError::CouldNotReadInput).encode();
914        let decoded = transcoder
915            .decode_message_return("get", &mut &encoded[..])
916            .unwrap_or_else(|e| panic!("Error decoding return value {e}"));
917
918        let expected = Value::Tuple(Tuple::new(
919            "Err".into(),
920            [Value::Tuple(Tuple::new(
921                Some("CouldNotReadInput"),
922                Vec::new(),
923            ))]
924            .to_vec(),
925        ));
926        assert_eq!(expected, decoded);
927    }
928
929    #[test]
930    fn decode_contract_event() -> Result<()> {
931        let metadata = generate_metadata();
932        let transcoder = ContractMessageTranscoder::new(metadata);
933
934        let signature_topic: <DefaultEnvironment as Environment>::Hash =
935            <transcode::Event1 as ink::env::Event>::SIGNATURE_TOPIC
936                .unwrap()
937                .into();
938        // raw encoded event
939        let encoded = ([0u32; 8], [1u32; 8]).encode();
940        // encode again as a Vec<u8> which has a len prefix.
941        let encoded_bytes = encoded.encode();
942        let _ = transcoder
943            .decode_contract_event(&signature_topic, &mut &encoded_bytes[..])?;
944
945        // todo assert is missing
946
947        Ok(())
948    }
949
950    #[test]
951    fn decode_hash_as_hex_encoded_string() -> Result<()> {
952        let metadata = generate_metadata();
953        let transcoder = ContractMessageTranscoder::new(metadata);
954
955        let hash = [
956            52u8, 40, 235, 225, 70, 245, 184, 36, 21, 218, 130, 114, 75, 207, 117, 240,
957            83, 118, 135, 56, 220, 172, 95, 131, 171, 125, 130, 167, 10, 15, 242, 222,
958        ];
959        let signature_topic: <DefaultEnvironment as Environment>::Hash =
960            <transcode::Event1 as ink::env::Event>::SIGNATURE_TOPIC
961                .unwrap()
962                .into();
963        // raw encoded event with event index prefix
964        let encoded = (hash, [0u32; 8]).encode();
965        // encode again as a Vec<u8> which has a len prefix.
966        let encoded_bytes = encoded.encode();
967        let decoded = transcoder
968            .decode_contract_event(&signature_topic, &mut &encoded_bytes[..])?;
969
970        if let Value::Map(ref map) = decoded {
971            let name_field = &map[&Value::String("name".into())];
972            if let Value::Hex(hex) = name_field {
973                assert_eq!(
974                    &Hex::from_str(
975                        "0x3428ebe146f5b82415da82724bcf75f053768738dcac5f83ab7d82a70a0ff2de"
976                    )?,
977                    hex
978                );
979                Ok(())
980            } else {
981                Err(anyhow::anyhow!(
982                    "Expected a name field hash encoded as Hex value, was {name_field:?}"
983                ))
984            }
985        } else {
986            Err(anyhow::anyhow!(
987                "Expected a Value::Map for the decoded event"
988            ))
989        }
990    }
991
992    #[test]
993    fn decode_contract_message() -> Result<()> {
994        let metadata = generate_metadata();
995        let transcoder = ContractMessageTranscoder::new(metadata);
996
997        let encoded_bytes = hex::decode("633aa551").unwrap();
998        let _ = transcoder.decode_contract_message(&mut &encoded_bytes[..])?;
999
1000        Ok(())
1001    }
1002
1003    #[test]
1004    #[should_panic(
1005        expected = "input length was longer than expected by 1 byte(s).\nManaged to decode `flip` but `00` bytes were left unread"
1006    )]
1007    fn fail_decode_input_with_extra_bytes() {
1008        let metadata = generate_metadata();
1009        let transcoder = ContractMessageTranscoder::new(metadata);
1010
1011        let encoded_bytes = hex::decode("633aa55100").unwrap();
1012        let _ = transcoder
1013            .decode_contract_message(&mut &encoded_bytes[..])
1014            .unwrap();
1015    }
1016
1017    #[test]
1018    #[should_panic(
1019        expected = "input length was longer than expected by 2 byte(s).\nManaged to decode `Event1`, `name`, `from` but `0C10` bytes were left unread"
1020    )]
1021    fn fail_decode_contract_event_with_extra_bytes() {
1022        let metadata = generate_metadata();
1023        let transcoder = ContractMessageTranscoder::new(metadata);
1024
1025        let signature_topic: H256 =
1026            <transcode::Event1 as ink::env::Event>::SIGNATURE_TOPIC
1027                .unwrap()
1028                .into();
1029        // raw encoded event with event index prefix
1030        let encoded = ([0u32; 8], [1u32; 8], [12u8, 16u8]).encode();
1031        // encode again as a Vec<u8> which has a len prefix.
1032        let encoded_bytes = encoded.encode();
1033        let _ = transcoder
1034            .decode_contract_event(&signature_topic, &mut &encoded_bytes[..])
1035            .unwrap();
1036    }
1037
1038    #[test]
1039    fn encode_string_quoted_traditional() -> Result<()> {
1040        // Traditional SCON format with explicit quotes
1041        let metadata = generate_metadata();
1042        let transcoder = ContractMessageTranscoder::new(metadata);
1043
1044        let encoded = transcoder.encode("set_name", [r#""Alice""#])?;
1045        let encoded_args = &encoded[4..];
1046
1047        let expected = "Alice".to_string().encode();
1048        assert_eq!(expected, encoded_args);
1049        Ok(())
1050    }
1051
1052    #[test]
1053    fn encode_string_unquoted_simple() -> Result<()> {
1054        // Simple unquoted string - should work after fix
1055        let metadata = generate_metadata();
1056        let transcoder = ContractMessageTranscoder::new(metadata);
1057
1058        let encoded = transcoder.encode("set_name", ["Alice"])?;
1059        let encoded_args = &encoded[4..];
1060
1061        let expected = "Alice".to_string().encode();
1062        assert_eq!(expected, encoded_args);
1063        Ok(())
1064    }
1065
1066    #[test]
1067    fn encode_string_with_spaces() -> Result<()> {
1068        // String with spaces - simulating bash: --args "my string"
1069        // Bash passes "my string" (with the quotes stripped) to the program
1070        let metadata = generate_metadata();
1071        let transcoder = ContractMessageTranscoder::new(metadata);
1072
1073        let encoded = transcoder.encode("set_name", ["my string"])?;
1074        let encoded_args = &encoded[4..];
1075
1076        let expected = "my string".to_string().encode();
1077        assert_eq!(expected, encoded_args);
1078        Ok(())
1079    }
1080
1081    #[test]
1082    fn encode_string_with_special_chars() -> Result<()> {
1083        // String with special characters
1084        let metadata = generate_metadata();
1085        let transcoder = ContractMessageTranscoder::new(metadata);
1086
1087        let test_string = "Hello, World! 123 @#$%";
1088        let encoded = transcoder.encode("set_name", [test_string])?;
1089        let encoded_args = &encoded[4..];
1090
1091        let expected = test_string.to_string().encode();
1092        assert_eq!(expected, encoded_args);
1093        Ok(())
1094    }
1095
1096    #[test]
1097    fn encode_empty_string() -> Result<()> {
1098        // Empty string
1099        let metadata = generate_metadata();
1100        let transcoder = ContractMessageTranscoder::new(metadata);
1101
1102        let encoded = transcoder.encode("set_name", [""])?;
1103        let encoded_args = &encoded[4..];
1104
1105        let expected = "".to_string().encode();
1106        assert_eq!(expected, encoded_args);
1107        Ok(())
1108    }
1109
1110    #[test]
1111    fn encode_string_numbers_as_string() -> Result<()> {
1112        // Numbers that should be treated as strings
1113        let metadata = generate_metadata();
1114        let transcoder = ContractMessageTranscoder::new(metadata);
1115
1116        let encoded = transcoder.encode("set_name", ["12345"])?;
1117        let encoded_args = &encoded[4..];
1118
1119        // Should encode as string "12345", not integer 12345
1120        let expected = "12345".to_string().encode();
1121        assert_eq!(expected, encoded_args);
1122        Ok(())
1123    }
1124}