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)]
566mod tests {
567    use super::*;
568    use crate::scon::Hex;
569    use ink_env::{
570        DefaultEnvironment,
571        Environment,
572    };
573    use primitive_types::H256;
574    use scale::Encode;
575    use scon::Value;
576    use std::str::FromStr;
577
578    #[allow(clippy::extra_unused_lifetimes, unexpected_cfgs, non_local_definitions)]
579    #[ink::contract]
580    pub mod transcode {
581        #[ink(storage)]
582        pub struct Transcode {
583            value: bool,
584        }
585
586        #[ink(event)]
587        pub struct Event1 {
588            #[ink(topic)]
589            name: Hash,
590            #[ink(topic)]
591            from: AccountId,
592        }
593
594        impl Transcode {
595            #[ink(constructor)]
596            pub fn new(init_value: bool) -> Self {
597                Self { value: init_value }
598            }
599
600            #[ink(constructor)]
601            pub fn default() -> Self {
602                Self::new(Default::default())
603            }
604
605            #[ink(message)]
606            pub fn flip(&mut self) {
607                self.value = !self.value;
608            }
609
610            #[ink(message)]
611            pub fn get(&self) -> bool {
612                self.value
613            }
614
615            #[ink(message)]
616            pub fn get_complex(
617                &self,
618            ) -> (u32, ink::H160, ink::H256, ink::U256, AccountId) {
619                (
620                    32u32,
621                    self.env().address(),
622                    self.env().own_code_hash(),
623                    //self.env().transferred_value()
624                    ink::U256::one(),
625                    AccountId::from([0x17; 32]),
626                )
627            }
628            //pub fn get_complex(&mut self) -> (ink::H160, Hash, ink::H256, ink::U256) {
629            //(self.env().address(), [0xABu8; 32].into(),
630            /*
631            pub fn get_complex(&self) -> (ink::H160, ink::H256, ink::U256) {
632                (self.env().address(),
633                self.env().own_code_hash(), self.env().transferred_value())
634            }
635             */
636
637            #[ink(message)]
638            pub fn set_account_id(&self, account_id: AccountId) {
639                let _ = account_id;
640            }
641
642            #[ink(message)]
643            pub fn set_account_ids_vec(&self, account_ids: Vec<AccountId>) {
644                let _ = account_ids;
645            }
646
647            #[ink(message)]
648            pub fn primitive_vec_args(&self, args: Vec<u32>) {
649                let _ = args;
650            }
651
652            #[ink(message)]
653            pub fn uint_args(
654                &self,
655                _u8: u8,
656                _u16: u16,
657                _u32: u32,
658                _u64: u64,
659                _u128: u128,
660            ) {
661            }
662
663            #[ink(message)]
664            pub fn uint_array_args(&self, arr: [u8; 4]) {
665                let _ = arr;
666            }
667
668            #[ink(message)]
669            pub fn h160(&self, addr: ink::H160) {
670                let _ = addr;
671            }
672
673            #[ink(message)]
674            pub fn set_name(&self, _name: String) {}
675        }
676    }
677
678    fn generate_metadata() -> InkProject {
679        unsafe extern "Rust" {
680            fn __ink_generate_metadata() -> InkProject;
681        }
682
683        unsafe { __ink_generate_metadata() }
684    }
685
686    #[test]
687    fn encode_single_primitive_arg() -> Result<()> {
688        let metadata = generate_metadata();
689        let transcoder = ContractMessageTranscoder::new(metadata);
690
691        let encoded = transcoder.encode("new", ["true"])?;
692        // encoded args follow the 4 byte selector
693        let encoded_args = &encoded[4..];
694
695        assert_eq!(true.encode(), encoded_args);
696        Ok(())
697    }
698
699    #[test]
700    fn encode_misspelled_arg() {
701        let metadata = generate_metadata();
702        let transcoder = ContractMessageTranscoder::new(metadata);
703        assert_eq!(
704            transcoder.encode("fip", ["true"]).unwrap_err().to_string(),
705            "No constructor or message with the name 'fip' found.\nDid you mean 'flip'?"
706        );
707    }
708
709    #[test]
710    fn encode_mismatching_args_length() {
711        let metadata = generate_metadata();
712        let transcoder = ContractMessageTranscoder::new(metadata);
713
714        let result: Result<Vec<u8>> = transcoder.encode("new", Vec::<&str>::new());
715        assert!(result.is_err(), "Should return an error");
716        assert_eq!(
717            result.unwrap_err().to_string(),
718            "Invalid number of input arguments: expected 1, 0 provided"
719        );
720
721        let result: Result<Vec<u8>> = transcoder.encode("new", ["true", "false"]);
722        assert!(result.is_err(), "Should return an error");
723        assert_eq!(
724            result.unwrap_err().to_string(),
725            "Invalid number of input arguments: expected 1, 2 provided"
726        );
727    }
728
729    #[test]
730    fn encode_account_id_custom_ss58_encoding() -> Result<()> {
731        let metadata = generate_metadata();
732        let transcoder = ContractMessageTranscoder::new(metadata);
733
734        let encoded = transcoder.encode(
735            "set_account_id",
736            ["5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"],
737        )?;
738
739        // encoded args follow the 4 byte selector
740        let encoded_args = &encoded[4..];
741
742        let expected =
743            AccountId32::from_str("5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY")
744                .unwrap();
745        assert_eq!(expected.encode(), encoded_args);
746        Ok(())
747    }
748
749    #[test]
750    fn encode_account_ids_vec_args() -> Result<()> {
751        let metadata = generate_metadata();
752        let transcoder = ContractMessageTranscoder::new(metadata);
753
754        let encoded = transcoder.encode(
755            "set_account_ids_vec",
756            ["[5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY, 5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty]"],
757        )?;
758
759        // encoded args follow the 4 byte selector
760        let encoded_args = &encoded[4..];
761
762        let expected = vec![
763            AccountId32::from_str("5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY")
764                .unwrap(),
765            AccountId32::from_str("5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty")
766                .unwrap(),
767        ];
768        assert_eq!(expected.encode(), encoded_args);
769        Ok(())
770    }
771
772    #[test]
773    fn encode_primitive_vec_args() -> Result<()> {
774        let metadata = generate_metadata();
775        let transcoder = ContractMessageTranscoder::new(metadata);
776
777        let encoded = transcoder.encode("primitive_vec_args", ["[1, 2]"])?;
778
779        // encoded args follow the 4 byte selector
780        let encoded_args = &encoded[4..];
781
782        let expected = vec![1, 2];
783        assert_eq!(expected.encode(), encoded_args);
784        Ok(())
785    }
786
787    #[test]
788    fn encode_uint_hex_literals() -> Result<()> {
789        let metadata = generate_metadata();
790        let transcoder = ContractMessageTranscoder::new(metadata);
791
792        let encoded = transcoder.encode(
793            "uint_args",
794            [
795                "0x00",
796                "0xDEAD",
797                "0xDEADBEEF",
798                "0xDEADBEEF12345678",
799                "0xDEADBEEF0123456789ABCDEF01234567",
800            ],
801        )?;
802
803        // encoded args follow the 4 byte selector
804        let encoded_args = &encoded[4..];
805
806        let expected = (
807            0x00u8,
808            0xDEADu16,
809            0xDEADBEEFu32,
810            0xDEADBEEF12345678u64,
811            0xDEADBEEF0123456789ABCDEF01234567u128,
812        );
813        assert_eq!(expected.encode(), encoded_args);
814        Ok(())
815    }
816
817    #[test]
818    fn encode_uint_arr_hex_literals() -> Result<()> {
819        let metadata = generate_metadata();
820        let transcoder = ContractMessageTranscoder::new(metadata);
821
822        let encoded =
823            transcoder.encode("uint_array_args", ["[0xDE, 0xAD, 0xBE, 0xEF]"])?;
824
825        // encoded args follow the 4 byte selector
826        let encoded_args = &encoded[4..];
827
828        let expected: [u8; 4] = [0xDE, 0xAD, 0xBE, 0xEF];
829        assert_eq!(expected.encode(), encoded_args);
830        Ok(())
831    }
832
833    #[test]
834    #[should_panic(
835        expected = "Error: You are attempting to transcode a shortened hex value: `\"0xbc3f…f58a\"`"
836    )]
837    fn encode_must_panic_on_shortened_hex() {
838        let metadata = generate_metadata();
839        let transcoder = ContractMessageTranscoder::new(metadata);
840
841        let _encoded = transcoder.encode("h160", ["0xbc3f…f58a"]);
842    }
843
844    #[test]
845    fn decode_complex_return() {
846        let metadata = generate_metadata();
847        let transcoder = ContractMessageTranscoder::new(metadata);
848
849        let addr: ink::H160 = ink::H160::from([0x42; 20]);
850        let account_id: AccountId32 = AccountId32::from([0x13; 32]);
851        let _hash: [u8; 32] = [0xAB; 32];
852        let h256: ink::H256 = ink::H256::from([0x17; 32]);
853        // todo let value: ink::U256 = ink::U256::MAX;
854        let value: ink::U256 = ink::U256::one();
855
856        let encoded = Result::<
857            (u32, ink::H160, ink::H256, ink::U256, AccountId32),
858            ink::primitives::LangError,
859        >::Ok((32, addr, h256, value, account_id))
860        .encode();
861
862        let decoded = transcoder
863            .decode_message_return("get_complex", &mut &encoded[..])
864            .unwrap_or_else(|e| panic!("Error decoding return value {e}"));
865
866        let expected = Value::Tuple(Tuple::new(
867            "Ok".into(),
868            [
869                Value::Tuple(Tuple::new(
870                    None,
871                    [
872                        Value::UInt(32),
873                        Value::Hex(Hex::from_str("0x4242424242424242424242424242424242424242").unwrap()),
874                        Value::Hex(Hex::from_str("0x1717171717171717171717171717171717171717171717171717171717171717").unwrap()),
875                        Value::Literal("1".to_string()),
876                        Value::Literal("5CViS5pKamF1VbJ9tmQKPNDpLpJaBCfpPw2m49UzQ8zgiDGT".to_string()),
877                    ]
878                    .into_iter().collect()
879                ))
880            ]
881            .into_iter().collect(),
882        ));
883        assert_eq!(expected, decoded);
884    }
885
886    #[test]
887    fn decode_primitive_return() {
888        let metadata = generate_metadata();
889        let transcoder = ContractMessageTranscoder::new(metadata);
890
891        let encoded = Result::<bool, ink::primitives::LangError>::Ok(true).encode();
892        let decoded = transcoder
893            .decode_message_return("get", &mut &encoded[..])
894            .unwrap_or_else(|e| panic!("Error decoding return value {e}"));
895
896        let expected = Value::Tuple(Tuple::new(
897            "Ok".into(),
898            [Value::Bool(true)].into_iter().collect(),
899        ));
900        assert_eq!(expected, decoded);
901    }
902
903    #[test]
904    fn decode_lang_error() {
905        use ink::primitives::LangError;
906
907        let metadata = generate_metadata();
908        let transcoder = ContractMessageTranscoder::new(metadata);
909
910        let encoded =
911            Result::<bool, LangError>::Err(LangError::CouldNotReadInput).encode();
912        let decoded = transcoder
913            .decode_message_return("get", &mut &encoded[..])
914            .unwrap_or_else(|e| panic!("Error decoding return value {e}"));
915
916        let expected = Value::Tuple(Tuple::new(
917            "Err".into(),
918            [Value::Tuple(Tuple::new(
919                Some("CouldNotReadInput"),
920                Vec::new(),
921            ))]
922            .to_vec(),
923        ));
924        assert_eq!(expected, decoded);
925    }
926
927    #[test]
928    fn decode_contract_event() -> Result<()> {
929        let metadata = generate_metadata();
930        let transcoder = ContractMessageTranscoder::new(metadata);
931
932        let signature_topic: <DefaultEnvironment as Environment>::Hash =
933            <transcode::Event1 as ink::env::Event>::SIGNATURE_TOPIC
934                .unwrap()
935                .into();
936        // raw encoded event
937        let encoded = ([0u32; 8], [1u32; 8]).encode();
938        // encode again as a Vec<u8> which has a len prefix.
939        let encoded_bytes = encoded.encode();
940        let _ = transcoder
941            .decode_contract_event(&signature_topic, &mut &encoded_bytes[..])?;
942
943        // todo assert is missing
944
945        Ok(())
946    }
947
948    #[test]
949    fn decode_hash_as_hex_encoded_string() -> Result<()> {
950        let metadata = generate_metadata();
951        let transcoder = ContractMessageTranscoder::new(metadata);
952
953        let hash = [
954            52u8, 40, 235, 225, 70, 245, 184, 36, 21, 218, 130, 114, 75, 207, 117, 240,
955            83, 118, 135, 56, 220, 172, 95, 131, 171, 125, 130, 167, 10, 15, 242, 222,
956        ];
957        let signature_topic: <DefaultEnvironment as Environment>::Hash =
958            <transcode::Event1 as ink::env::Event>::SIGNATURE_TOPIC
959                .unwrap()
960                .into();
961        // raw encoded event with event index prefix
962        let encoded = (hash, [0u32; 8]).encode();
963        // encode again as a Vec<u8> which has a len prefix.
964        let encoded_bytes = encoded.encode();
965        let decoded = transcoder
966            .decode_contract_event(&signature_topic, &mut &encoded_bytes[..])?;
967
968        if let Value::Map(ref map) = decoded {
969            let name_field = &map[&Value::String("name".into())];
970            if let Value::Hex(hex) = name_field {
971                assert_eq!(
972                    &Hex::from_str(
973                        "0x3428ebe146f5b82415da82724bcf75f053768738dcac5f83ab7d82a70a0ff2de"
974                    )?,
975                    hex
976                );
977                Ok(())
978            } else {
979                Err(anyhow::anyhow!(
980                    "Expected a name field hash encoded as Hex value, was {name_field:?}"
981                ))
982            }
983        } else {
984            Err(anyhow::anyhow!(
985                "Expected a Value::Map for the decoded event"
986            ))
987        }
988    }
989
990    #[test]
991    fn decode_contract_message() -> Result<()> {
992        let metadata = generate_metadata();
993        let transcoder = ContractMessageTranscoder::new(metadata);
994
995        let encoded_bytes = hex::decode("633aa551").unwrap();
996        let _ = transcoder.decode_contract_message(&mut &encoded_bytes[..])?;
997
998        Ok(())
999    }
1000
1001    #[test]
1002    #[should_panic(
1003        expected = "input length was longer than expected by 1 byte(s).\nManaged to decode `flip` but `00` bytes were left unread"
1004    )]
1005    fn fail_decode_input_with_extra_bytes() {
1006        let metadata = generate_metadata();
1007        let transcoder = ContractMessageTranscoder::new(metadata);
1008
1009        let encoded_bytes = hex::decode("633aa55100").unwrap();
1010        let _ = transcoder
1011            .decode_contract_message(&mut &encoded_bytes[..])
1012            .unwrap();
1013    }
1014
1015    #[test]
1016    #[should_panic(
1017        expected = "input length was longer than expected by 2 byte(s).\nManaged to decode `Event1`, `name`, `from` but `0C10` bytes were left unread"
1018    )]
1019    fn fail_decode_contract_event_with_extra_bytes() {
1020        let metadata = generate_metadata();
1021        let transcoder = ContractMessageTranscoder::new(metadata);
1022
1023        let signature_topic: H256 =
1024            <transcode::Event1 as ink::env::Event>::SIGNATURE_TOPIC
1025                .unwrap()
1026                .into();
1027        // raw encoded event with event index prefix
1028        let encoded = ([0u32; 8], [1u32; 8], [12u8, 16u8]).encode();
1029        // encode again as a Vec<u8> which has a len prefix.
1030        let encoded_bytes = encoded.encode();
1031        let _ = transcoder
1032            .decode_contract_event(&signature_topic, &mut &encoded_bytes[..])
1033            .unwrap();
1034    }
1035
1036    #[test]
1037    fn encode_string_quoted_traditional() -> Result<()> {
1038        // Traditional SCON format with explicit quotes
1039        let metadata = generate_metadata();
1040        let transcoder = ContractMessageTranscoder::new(metadata);
1041
1042        let encoded = transcoder.encode("set_name", [r#""Alice""#])?;
1043        let encoded_args = &encoded[4..];
1044
1045        let expected = "Alice".to_string().encode();
1046        assert_eq!(expected, encoded_args);
1047        Ok(())
1048    }
1049
1050    #[test]
1051    fn encode_string_unquoted_simple() -> Result<()> {
1052        // Simple unquoted string - should work after fix
1053        let metadata = generate_metadata();
1054        let transcoder = ContractMessageTranscoder::new(metadata);
1055
1056        let encoded = transcoder.encode("set_name", ["Alice"])?;
1057        let encoded_args = &encoded[4..];
1058
1059        let expected = "Alice".to_string().encode();
1060        assert_eq!(expected, encoded_args);
1061        Ok(())
1062    }
1063
1064    #[test]
1065    fn encode_string_with_spaces() -> Result<()> {
1066        // String with spaces - simulating bash: --args "my string"
1067        // Bash passes "my string" (with the quotes stripped) to the program
1068        let metadata = generate_metadata();
1069        let transcoder = ContractMessageTranscoder::new(metadata);
1070
1071        let encoded = transcoder.encode("set_name", ["my string"])?;
1072        let encoded_args = &encoded[4..];
1073
1074        let expected = "my string".to_string().encode();
1075        assert_eq!(expected, encoded_args);
1076        Ok(())
1077    }
1078
1079    #[test]
1080    fn encode_string_with_special_chars() -> Result<()> {
1081        // String with special characters
1082        let metadata = generate_metadata();
1083        let transcoder = ContractMessageTranscoder::new(metadata);
1084
1085        let test_string = "Hello, World! 123 @#$%";
1086        let encoded = transcoder.encode("set_name", [test_string])?;
1087        let encoded_args = &encoded[4..];
1088
1089        let expected = test_string.to_string().encode();
1090        assert_eq!(expected, encoded_args);
1091        Ok(())
1092    }
1093
1094    #[test]
1095    fn encode_empty_string() -> Result<()> {
1096        // Empty string
1097        let metadata = generate_metadata();
1098        let transcoder = ContractMessageTranscoder::new(metadata);
1099
1100        let encoded = transcoder.encode("set_name", [""])?;
1101        let encoded_args = &encoded[4..];
1102
1103        let expected = "".to_string().encode();
1104        assert_eq!(expected, encoded_args);
1105        Ok(())
1106    }
1107
1108    #[test]
1109    fn encode_string_numbers_as_string() -> Result<()> {
1110        // Numbers that should be treated as strings
1111        let metadata = generate_metadata();
1112        let transcoder = ContractMessageTranscoder::new(metadata);
1113
1114        let encoded = transcoder.encode("set_name", ["12345"])?;
1115        let encoded_args = &encoded[4..];
1116
1117        // Should encode as string "12345", not integer 12345
1118        let expected = "12345".to_string().encode();
1119        assert_eq!(expected, encoded_args);
1120        Ok(())
1121    }
1122}