diff --git a/Cargo.lock b/Cargo.lock index 9699e71c3f..07e0a7870c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -65,6 +65,28 @@ version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" +[[package]] +name = "alloy-rlp" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6c1d995bff8d011f7cd6c81820d51825e6e06d6db73914c1630ecf544d83d6" +dependencies = [ + "alloy-rlp-derive", + "arrayvec 0.7.6", + "bytes", +] + +[[package]] +name = "alloy-rlp-derive" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a40e1ef334153322fd878d07e86af7a529bcb86b2439525920a88eba87bcf943" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.93", +] + [[package]] name = "android-tzdata" version = "0.1.1" @@ -3073,6 +3095,7 @@ checksum = "8e04e2fd2b8188ea827b32ef11de88377086d690286ab35747ef7f9bf3ccb590" name = "ink" version = "6.0.0-alpha" dependencies = [ + "alloy-rlp", "derive_more 1.0.0", "ink_env", "ink_ir", @@ -3192,6 +3215,7 @@ dependencies = [ name = "ink_env" version = "6.0.0-alpha" dependencies = [ + "alloy-rlp", "blake2", "cfg-if", "const_env", @@ -3231,6 +3255,7 @@ dependencies = [ "itertools 0.14.0", "proc-macro2", "quote", + "sha3 0.10.8", "syn 2.0.93", ] @@ -3282,12 +3307,14 @@ dependencies = [ name = "ink_primitives" version = "6.0.0-alpha" dependencies = [ + "alloy-rlp", "cfg-if", "derive_more 1.0.0", "ink", "ink_env", "ink_prelude", "num-traits", + "pallet-revive-uapi", "parity-scale-codec", "primitive-types", "scale-decode", diff --git a/Cargo.toml b/Cargo.toml index 35b3c47d37..33d5d0a566 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,6 +55,7 @@ proc-macro2 = { version = "1" } quickcheck = { version = "1" } quickcheck_macros = { version = "1" } quote = { version = "1" } +alloy-rlp = { version = "0.3.9", default-features = false } scale = { package = "parity-scale-codec", version = "3.6.12", default-features = false, features = ["derive"] } scale-decode = { version = "0.14.0", default-features = false } scale-encode = { version = "0.8.0", default-features = false } diff --git a/crates/e2e/src/lib.rs b/crates/e2e/src/lib.rs index d6a2daad8a..087fbee916 100644 --- a/crates/e2e/src/lib.rs +++ b/crates/e2e/src/lib.rs @@ -44,6 +44,7 @@ pub use backend_calls::{ CallBuilder, InstantiateBuilder, }; +pub use client_utils::ContractsRegistry; pub use contract_results::{ CallDryRunResult, CallResult, diff --git a/crates/env/Cargo.toml b/crates/env/Cargo.toml index a0888828b8..b0be46249a 100644 --- a/crates/env/Cargo.toml +++ b/crates/env/Cargo.toml @@ -23,6 +23,7 @@ ink_primitives = { workspace = true } ink_macro = { workspace = true } pallet-revive-uapi = { workspace = true } +alloy-rlp = { workspace = true } scale = { workspace = true, features = ["max-encoded-len"] } derive_more = { workspace = true, features = ["from", "display"] } num-traits = { workspace = true, features = ["i128"] } @@ -66,6 +67,7 @@ ink = { path = "../ink" } [features] default = [ "std" ] std = [ + "alloy-rlp/std", "blake2", "ink/std", "ink_allocator/std", diff --git a/crates/env/src/api.rs b/crates/env/src/api.rs index 0f02e5d798..1ce5601b2a 100644 --- a/crates/env/src/api.rs +++ b/crates/env/src/api.rs @@ -41,6 +41,8 @@ use crate::{ Environment, Gas, }, + DecodeDispatch, + DispatchError, Result, }; use ink_primitives::{ @@ -407,9 +409,9 @@ where /// # Errors /// /// If the given `T` cannot be properly decoded from the expected input. -pub fn decode_input() -> Result +pub fn decode_input() -> core::result::Result where - T: scale::Decode, + T: DecodeDispatch, { ::on_instance(|instance| { EnvBackend::decode_input::(instance) @@ -447,6 +449,20 @@ where }) } +/// Returns the *RLP encoded* value back to the caller of the executed contract. +/// +/// # Note +/// +/// This function stops the execution of the contract immediately. +pub fn return_value_rlp(return_flags: ReturnFlags, return_value: &R) -> ! +where + R: alloy_rlp::Encodable, +{ + ::on_instance(|instance| { + EnvBackend::return_value_rlp::(instance, return_flags, return_value) + }) +} + /// Appends the given message to the debug message buffer. pub fn debug_message(message: &str) { ::on_instance(|instance| { diff --git a/crates/env/src/backend.rs b/crates/env/src/backend.rs index 1f6d811482..2fea8f7aa7 100644 --- a/crates/env/src/backend.rs +++ b/crates/env/src/backend.rs @@ -27,6 +27,8 @@ use crate::{ CryptoHash, HashOutput, }, + DecodeDispatch, + DispatchError, Result, }; use ink_primitives::{ @@ -105,9 +107,9 @@ pub trait EnvBackend { /// # Errors /// /// If the given `T` cannot be properly decoded from the expected input. - fn decode_input(&mut self) -> Result + fn decode_input(&mut self) -> core::result::Result where - T: scale::Decode; + T: DecodeDispatch; /// Returns the value back to the caller of the executed contract. /// @@ -137,6 +139,11 @@ pub trait EnvBackend { where R: scale::Encode; + /// todo: comment + fn return_value_rlp(&mut self, flags: ReturnFlags, return_value: &R) -> ! + where + R: alloy_rlp::Encodable; + /// Emit a custom debug message. /// /// The message is appended to the debug buffer which is then supplied to the calling diff --git a/crates/env/src/engine/off_chain/impls.rs b/crates/env/src/engine/off_chain/impls.rs index ad01d00bdd..847630b2c8 100644 --- a/crates/env/src/engine/off_chain/impls.rs +++ b/crates/env/src/engine/off_chain/impls.rs @@ -37,6 +37,8 @@ use crate::{ }, test::callee, Clear, + DecodeDispatch, + DispatchError, EnvBackend, Result, TypedEnvBackend, @@ -81,8 +83,8 @@ where >::Type as crate::reflect::ContractMessageDecoder >::Type - as scale::Decode - >::decode(&mut &input[..]) + as DecodeDispatch + >::decode_dispatch(&mut &input[..]) .unwrap_or_else(|e| panic!("Failed to decode constructor call: {:?}", e)); crate::reflect::ExecuteDispatchable::execute_dispatchable(dispatch) @@ -339,9 +341,9 @@ impl EnvBackend for EnvInstance { self.engine.clear_storage(&key.encode()) } - fn decode_input(&mut self) -> Result + fn decode_input(&mut self) -> core::result::Result where - T: scale::Decode, + T: DecodeDispatch, { unimplemented!("the off-chain env does not implement `input`") } @@ -364,6 +366,13 @@ impl EnvBackend for EnvInstance { self.engine.set_storage(&[255_u8; 32], &v[..]); } + fn return_value_rlp(&mut self, _flags: ReturnFlags, _return_value: &R) -> ! + where + R: alloy_rlp::Encodable, + { + unimplemented!("the off-chain env does not implement `return_value_rlp`") + } + fn debug_message(&mut self, message: &str) { self.engine.debug_message(message) } @@ -669,8 +678,8 @@ impl TypedEnvBackend for EnvInstance { >::Type as crate::reflect::ContractConstructorDecoder >::Type - as scale::Decode - >::decode(&mut &input[..]) + as DecodeDispatch + >::decode_dispatch(&mut &input[..]) .unwrap_or_else(|e| panic!("Failed to decode constructor call: {:?}", e)); crate::reflect::ExecuteDispatchable::execute_dispatchable(dispatch) .unwrap_or_else(|e| panic!("Constructor call failed: {:?}", e)); diff --git a/crates/env/src/engine/on_chain/buffer.rs b/crates/env/src/engine/on_chain/buffer.rs index b9444c05e8..acb1f7b138 100644 --- a/crates/env/src/engine/on_chain/buffer.rs +++ b/crates/env/src/engine/on_chain/buffer.rs @@ -106,6 +106,25 @@ impl scale::Output for EncodeScope<'_> { } } +unsafe impl<'a> alloy_rlp::bytes::BufMut for EncodeScope<'a> { + fn remaining_mut(&self) -> usize { + self.capacity() - self.len() + } + unsafe fn advance_mut(&mut self, cnt: usize) { + debug_assert!( + self.len().checked_add(cnt).unwrap() <= self.capacity(), + "encode scope buffer overflowed. capacity is {} but last write index is {}", + self.capacity(), + self.len().checked_add(cnt).unwrap(), + ); + self.len = self.len.checked_add(cnt).unwrap() + } + + fn chunk_mut(&mut self) -> &mut alloy_rlp::bytes::buf::UninitSlice { + alloy_rlp::bytes::buf::UninitSlice::new(&mut self.buffer[self.len..]) + } +} + /// Scoped access to an underlying bytes buffer. /// /// # Note diff --git a/crates/env/src/engine/on_chain/pallet_revive.rs b/crates/env/src/engine/on_chain/pallet_revive.rs index c96100f210..dfb09d25ba 100644 --- a/crates/env/src/engine/on_chain/pallet_revive.rs +++ b/crates/env/src/engine/on_chain/pallet_revive.rs @@ -22,6 +22,10 @@ use crate::{ FromAddr, LimitParamsV2, }, + dispatch::{ + DecodeDispatch, + DispatchError, + }, engine::on_chain::{ EncodeScope, EnvInstance, @@ -186,6 +190,7 @@ impl EnvInstance { T: FromLittleEndian, { let mut scope = self.scoped_buffer(); + // TODO: check unwrap let u256: &mut [u8; 32] = scope.take(32).try_into().unwrap(); ext_fn(u256); let mut result = ::Bytes::default(); @@ -261,13 +266,13 @@ impl EnvBackend for EnvInstance { ext::clear_storage(STORAGE_FLAGS, key) } - fn decode_input(&mut self) -> Result + fn decode_input(&mut self) -> core::result::Result where - T: scale::Decode, + T: DecodeDispatch, { let full_scope = &mut self.scoped_buffer().take_rest(); ext::call_data_copy(full_scope, 0); - scale::Decode::decode(&mut &full_scope[..]).map_err(Into::into) + DecodeDispatch::decode_dispatch(&mut &full_scope[..]) } fn return_value(&mut self, flags: ReturnFlags, return_value: &R) -> ! @@ -280,6 +285,16 @@ impl EnvBackend for EnvInstance { ext::return_value(flags, &self.buffer[..][..len]); } + fn return_value_rlp(&mut self, flags: ReturnFlags, return_value: &R) -> ! + where + R: alloy_rlp::Encodable, + { + let mut scope = EncodeScope::from(&mut self.buffer[..]); + return_value.encode(&mut scope); + let len = scope.len(); + ext::return_value(flags, &self.buffer[..][..len]); + } + #[cfg(not(feature = "ink-debug"))] /// A no-op. Enable the `ink-debug` feature for debug messages. fn debug_message(&mut self, _content: &str) {} @@ -386,6 +401,7 @@ impl TypedEnvBackend for EnvInstance { let h160: &mut [u8; 20] = scope.take(20).try_into().unwrap(); ext::caller(h160); + // TODO: check decode scale::Decode::decode(&mut &h160[..]) .expect("The executed contract must have a caller with a valid account id.") } @@ -491,6 +507,7 @@ impl TypedEnvBackend for EnvInstance { enc_callee, ref_time_limit, proof_size_limit, + // TODO: cleanup comment? //enc_storage_limit, storage_deposit_limit.as_deref(), enc_transferred_value, @@ -594,6 +611,7 @@ impl TypedEnvBackend for EnvInstance { Some(out_return_value), salt, ); + // TODO: clean comment? //let foo: () = instantiate_result; crate::engine::decode_instantiate_result::<_, ContractRef, RetType>( @@ -617,7 +635,7 @@ impl TypedEnvBackend for EnvInstance { E: Environment, { let mut scope = self.scoped_buffer(); - /* + /* TODO: remove comment? let ref_time_limit = params.ref_time_limit(); let proof_size_limit = params.proof_size_limit(); let storage_deposit_limit = params.storage_deposit_limit().map(|limit| { @@ -660,6 +678,7 @@ impl TypedEnvBackend for EnvInstance { ); match call_result { Ok(()) => { + // TODO: clean comments? // no need to decode, is () //let decoded = scale::DecodeAll::decode_all(&mut &output[..])?; //Ok(decoded) diff --git a/crates/env/src/lib.rs b/crates/env/src/lib.rs index ce6274466e..e7a54a1c78 100644 --- a/crates/env/src/lib.rs +++ b/crates/env/src/lib.rs @@ -126,6 +126,10 @@ pub use ink_primitives::{ ContractReverseReference, }, reflect, + reflect::{ + DecodeDispatch, + DispatchError, + }, types, }; #[doc(inline)] diff --git a/crates/ink/Cargo.toml b/crates/ink/Cargo.toml index fe26e88245..63f2addbe0 100644 --- a/crates/ink/Cargo.toml +++ b/crates/ink/Cargo.toml @@ -23,6 +23,7 @@ ink_metadata = { workspace = true, optional = true } ink_prelude = { workspace = true } ink_macro = { workspace = true } pallet-revive-uapi = { workspace = true } +alloy-rlp = { workspace = true } scale = { workspace = true } scale-info = { workspace = true, default-features = false, features = ["derive"], optional = true } derive_more = { workspace = true, features = ["from"] } @@ -41,6 +42,7 @@ trybuild = { workspace = true, features = ["diff"] } [features] default = [ "std" ] std = [ + "alloy-rlp/std", "ink_env/std", "ink_macro/std", "ink_metadata/std", diff --git a/crates/ink/codegen/src/generator/arg_list.rs b/crates/ink/codegen/src/generator/arg_list.rs index 0fe0891a5a..a373b5817e 100644 --- a/crates/ink/codegen/src/generator/arg_list.rs +++ b/crates/ink/codegen/src/generator/arg_list.rs @@ -66,6 +66,7 @@ pub fn input_message_idents(inputs: ir::InputsIter) -> Vec<&syn::Ident> { /// Returns a tuple type representing the types yielded by the input types. pub fn input_types_tuple(inputs: ir::InputsIter) -> TokenStream2 { let input_types = input_types(inputs); + // println!("input_types_tuple {}", input_types.len()); if input_types.len() != 1 { // Pack all types into a tuple if they are not exactly 1. // This results in `()` for zero input types. diff --git a/crates/ink/codegen/src/generator/dispatch.rs b/crates/ink/codegen/src/generator/dispatch.rs index 093ae82446..b172efde44 100644 --- a/crates/ink/codegen/src/generator/dispatch.rs +++ b/crates/ink/codegen/src/generator/dispatch.rs @@ -130,7 +130,7 @@ impl Dispatch<'_> { .flat_map(|item_impl| { iter::repeat(item_impl.trait_path()).zip(item_impl.iter_messages()) }) - .map(|(trait_path, message)| { + .flat_map(|(trait_path, message)| { let span = message.span(); let id = if let Some(trait_path) = trait_path { let local_id = message.local_id().hex_padded_suffixed(); @@ -152,7 +152,28 @@ impl Dispatch<'_> { #id ) }; - MessageDispatchable { message, id } + + let mut message_dispatchables = Vec::new(); + + if self.contract.config().abi_encoding().is_scale() { + message_dispatchables.push(MessageDispatchable { message, id }); + } + + if self.contract.config().abi_encoding().is_rlp() { + // todo: handle traits + let composed_selector = message + .composed_rlp_selector(); + + let rlp_selector = composed_selector + .into_be_u32() + .hex_padded_suffixed(); + let rlp_id = quote_spanned!(span=> + #rlp_selector + ); + message_dispatchables.push(MessageDispatchable { message, id: rlp_id }); + } + + message_dispatchables }) .collect::>() } @@ -239,13 +260,15 @@ impl Dispatch<'_> { .impls() .filter(|item_impl| item_impl.trait_path().is_none()) .flat_map(|item_impl| item_impl.iter_messages()) - .map(|message| { + .flat_map(|message| { let message_span = message.span(); let message_ident = message.ident(); let payable = message.is_payable(); let mutates = message.receiver().is_ref_mut(); let selector_id = message.composed_selector().into_be_u32().hex_padded_suffixed(); let selector_bytes = message.composed_selector().hex_lits(); + let rlp_selector_id = message.composed_rlp_selector().into_be_u32().hex_padded_suffixed(); + let rlp_selector_bytes = message.composed_rlp_selector().hex_lits(); let cfg_attrs = message.get_cfg_attrs(message_span); let output_tuple_type = message .output() @@ -254,23 +277,102 @@ impl Dispatch<'_> { let input_bindings = generator::input_bindings(message.inputs()); let input_tuple_type = generator::input_types_tuple(message.inputs()); let input_tuple_bindings = generator::input_bindings_tuple(message.inputs()); - quote_spanned!(message_span=> - #( #cfg_attrs )* - impl ::ink::reflect::DispatchableMessageInfo<#selector_id> for #storage_ident { - type Input = #input_tuple_type; - type Output = #output_tuple_type; - type Storage = #storage_ident; - const CALLABLE: fn(&mut Self::Storage, Self::Input) -> Self::Output = - |storage, #input_tuple_bindings| { - #storage_ident::#message_ident( storage #( , #input_bindings )* ) - }; - const SELECTOR: [::core::primitive::u8; 4usize] = [ #( #selector_bytes ),* ]; - const PAYABLE: ::core::primitive::bool = #payable; - const MUTATES: ::core::primitive::bool = #mutates; - const LABEL: &'static ::core::primitive::str = ::core::stringify!(#message_ident); + let mut message_infos = Vec::new(); + + if self.contract.config().abi_encoding().is_scale() { + message_infos.push(quote_spanned!(message_span=> + #( #cfg_attrs )* + impl ::ink::reflect::DispatchableMessageInfo<#selector_id> for #storage_ident { + type Input = #input_tuple_type; + type Output = #output_tuple_type; + type Storage = #storage_ident; + + const CALLABLE: fn(&mut Self::Storage, Self::Input) -> Self::Output = + |storage, #input_tuple_bindings| { + #storage_ident::#message_ident( storage #( , #input_bindings )* ) + }; + const DECODE: fn(&mut &[::core::primitive::u8]) -> ::core::result::Result = + |input| { + ::decode(input) + .map_err(|_| ::ink::env::DispatchError::InvalidParameters) + }; + const RETURN: fn(::ink::env::ReturnFlags, Self::Output) -> ! = + |flags, output| { + ::ink::env::return_value::<::ink::MessageResult::>( + flags, + // Currently no `LangError`s are raised at this level of the + // dispatch logic so `Ok` is always returned to the caller. + &::ink::MessageResult::Ok(output), + ) + }; + const SELECTOR: [::core::primitive::u8; 4usize] = [ #( #selector_bytes ),* ]; + const PAYABLE: ::core::primitive::bool = #payable; + const MUTATES: ::core::primitive::bool = #mutates; + const LABEL: &'static ::core::primitive::str = ::core::stringify!(#message_ident); + const ENCODING: ::ink::reflect::Encoding = ::ink::reflect::Encoding::Scale; + } + )) + } + + if self.contract.config().abi_encoding().is_rlp() { + // todo: refactor and figure out if there is a bug with the message.inputs() iterator + let input_types_len = generator::input_types(message.inputs()).len(); + // println!("LEN {}, input_types_len {}, {}", message.inputs().len(), input_types_len, input_tuple_type.to_string()); + let rlp_decode = if input_types_len == 0 { + quote! { + |_input| { + ::core::result::Result::Ok(()) // todo: should we decode `RlpUnit` instead, e.g. what if some data... + }; } - ) + } else { + quote! { + |input| { + ::decode(input) + .map_err(|_| ::ink::env::DispatchError::InvalidParameters) + }; + } + }; + let rlp_return_value = message + .output() + .map(|_| quote! { + |flags, output| { + ::ink::env::return_value_rlp::(flags, &output) + }; + }) + .unwrap_or_else(|| quote! { + |flags, _output| { + ::ink::env::return_value_rlp::<::ink::reflect::RlpUnit>( + flags, + &::ink::reflect::RlpUnit {} + ) + }; + }); + + message_infos.push(quote_spanned!(message_span=> + #( #cfg_attrs )* + impl ::ink::reflect::DispatchableMessageInfo<#rlp_selector_id> for #storage_ident { + type Input = #input_tuple_type; + type Output = #output_tuple_type; + type Storage = #storage_ident; + + const CALLABLE: fn(&mut Self::Storage, Self::Input) -> Self::Output = + |storage, #input_tuple_bindings| { + #storage_ident::#message_ident( storage #( , #input_bindings )* ) + }; + const DECODE: fn(&mut &[::core::primitive::u8]) -> ::core::result::Result = + #rlp_decode + const RETURN: fn(::ink::env::ReturnFlags, Self::Output) -> ! = + #rlp_return_value + const SELECTOR: [::core::primitive::u8; 4usize] = [ #( #rlp_selector_bytes ),* ]; + const PAYABLE: ::core::primitive::bool = #payable; + const MUTATES: ::core::primitive::bool = #mutates; + const LABEL: &'static ::core::primitive::str = ::core::stringify!(#message_ident); + const ENCODING: ::ink::reflect::Encoding = ::ink::reflect::Encoding::Rlp; + } + )) + } + message_infos }); let trait_message_infos = self .contract @@ -288,6 +390,7 @@ impl Dispatch<'_> { }) .flatten() .map(|((trait_ident, trait_path), message)| { + // todo: trait message RLP encoding let message_span = message.span(); let message_ident = message.ident(); let mutates = message.receiver().is_ref_mut(); @@ -329,6 +432,7 @@ impl Dispatch<'_> { const PAYABLE: ::core::primitive::bool = #payable; const MUTATES: ::core::primitive::bool = #mutates; const LABEL: &'static ::core::primitive::str = #label; + const ENCODING: ::ink::reflect::Encoding = ::ink::reflect::Encoding::Scale; } ) }); @@ -417,12 +521,10 @@ impl Dispatch<'_> { .unwrap_or_else(|error| ::core::panic!("{}", error)) } - //::ink::env::debug_println!("before decode"); let dispatchable = match ::ink::env::decode_input::< <#storage_ident as ::ink::reflect::ContractMessageDecoder>::Type, >() { ::core::result::Result::Ok(decoded_dispatchable) => { - //::ink::env::debug_println!("in ok"); decoded_dispatchable } ::core::result::Result::Err(_decoding_error) => { @@ -515,14 +617,18 @@ impl Dispatch<'_> { let constructor_span = item.constructor.span(); let constructor_ident = constructor_variant_ident(index); let const_ident = format_ident!("CONSTRUCTOR_{}", index); - let constructor_input = expand_constructor_input(constructor_span, storage_ident, item.id.clone()); + let constructor_input = expand_constructor_input( + constructor_span, + storage_ident, + item.id.clone(), + ); let cfg_attrs = item.constructor.get_cfg_attrs(constructor_span); quote_spanned!(constructor_span=> #( #cfg_attrs )* #const_ident => { ::core::result::Result::Ok(Self::#constructor_ident( <#constructor_input as ::ink::scale::Decode>::decode(input) - .map_err(|_| ::ink::reflect::DispatchError::InvalidParameters)? + .map_err(|_| ::ink::env::DispatchError::InvalidParameters)? )) } ) @@ -542,13 +648,13 @@ impl Dispatch<'_> { quote! { ::core::result::Result::Ok(Self::#constructor_ident( <#constructor_input as ::ink::scale::Decode>::decode(input) - .map_err(|_| ::ink::reflect::DispatchError::InvalidParameters)? + .map_err(|_| ::ink::env::DispatchError::InvalidParameters)? )) } } None => { quote! { - ::core::result::Result::Err(::ink::reflect::DispatchError::UnknownSelector) + ::core::result::Result::Err(::ink::env::DispatchError::UnknownSelector) } } }; @@ -632,17 +738,14 @@ impl Dispatch<'_> { #( #constructors_variants ),* } - impl ::ink::reflect::DecodeDispatch for __ink_ConstructorDecoder { - fn decode_dispatch(input: &mut I) - -> ::core::result::Result - where - I: ::ink::scale::Input, - { + impl ::ink::env::DecodeDispatch for __ink_ConstructorDecoder { + fn decode_dispatch(input: &mut &[::core::primitive::u8]) + -> ::core::result::Result { #( #constructor_selector )* match <[::core::primitive::u8; 4usize] as ::ink::scale::Decode>::decode(input) - .map_err(|_| ::ink::reflect::DispatchError::InvalidSelector)? + .map_err(|_| ::ink::env::DispatchError::InvalidSelector)? { #( #constructor_match , )* _invalid => #possibly_wildcard_selector_constructor @@ -650,19 +753,9 @@ impl Dispatch<'_> { } } - impl ::ink::scale::Decode for __ink_ConstructorDecoder { - fn decode(input: &mut I) -> ::core::result::Result - where - I: ::ink::scale::Input, - { - ::decode_dispatch(input) - .map_err(::core::convert::Into::into) - } - } - impl ::ink::reflect::ExecuteDispatchable for __ink_ConstructorDecoder { #[allow(clippy::nonminimal_bool, dead_code)] - fn execute_dispatchable(self) -> ::core::result::Result<(), ::ink::reflect::DispatchError> { + fn execute_dispatchable(self) -> ::core::result::Result<(), ::ink::env::DispatchError> { match self { #( #constructor_execute ),* } @@ -738,13 +831,12 @@ impl Dispatch<'_> { let const_ident = format_ident!("MESSAGE_{}", index); let message_span = item.message.span(); let cfg_attrs = item.message.get_cfg_attrs(message_span); - let message_input = expand_message_input(message_span, storage_ident, item.id.clone()); + let id = item.id.clone(); quote_spanned!(message_span=> #( #cfg_attrs )* #const_ident => { ::core::result::Result::Ok(Self::#message_ident( - <#message_input as ::ink::scale::Decode>::decode(input) - .map_err(|_| ::ink::reflect::DispatchError::InvalidParameters)? + <#storage_ident as ::ink::reflect::DispatchableMessageInfo< #id >>::DECODE(input)? )) } ) @@ -759,13 +851,13 @@ impl Dispatch<'_> { quote! { ::core::result::Result::Ok(Self::#message_ident( <#message_input as ::ink::scale::Decode>::decode(input) - .map_err(|_| ::ink::reflect::DispatchError::InvalidParameters)? + .map_err(|_| ::ink::env::DispatchError::InvalidParameters)? )) } } None => { quote! { - ::core::result::Result::Err(::ink::reflect::DispatchError::UnknownSelector) + ::core::result::Result::Err(::ink::env::DispatchError::UnknownSelector) } } }; @@ -781,6 +873,9 @@ impl Dispatch<'_> { let message_callable = quote_spanned!(message_span=> <#storage_ident as ::ink::reflect::DispatchableMessageInfo< #id >>::CALLABLE ); + let message_return = quote_spanned!(message_span=> + <#storage_ident as ::ink::reflect::DispatchableMessageInfo< #id >>::RETURN + ); let message_output = quote_spanned!(message_span=> <#storage_ident as ::ink::reflect::DispatchableMessageInfo< #id >>::Output ); @@ -798,22 +893,6 @@ impl Dispatch<'_> { #( #cfg_attrs )* Self::#message_ident(input) => { if #any_message_accepts_payment && #deny_payment { - - /* - let bar = ::ink::reflect::DispatchError::UnknownSelector; - ::ink::env::debug_print!("bar: {:?}", bar); - //let foo = format_args!("{}", bar); - //let s = ::alloc::fmt::format(format_args!("{}", bar)); - let s = ::ink::env::format!("{}", ::ink::reflect::DispatchError::UnknownSelector); - if s.eq("encountered unknown selector") { - ink::env::debug_print!("TRUE"); - } else { - ink::env::debug_print!("FALSE"); - } - */ - - //let foo = Err(::ink::reflect::DispatchError::UnknownSelector); - //foo? ::ink::codegen::deny_payment::< <#storage_ident as ::ink::env::ContractEnv>::Env>()?; } @@ -833,12 +912,7 @@ impl Dispatch<'_> { push_contract(contract, #mutates_storage); } - ::ink::env::return_value::<::ink::MessageResult::<#message_output>>( - flag, - // Currently no `LangError`s are raised at this level of the - // dispatch logic so `Ok` is always returned to the caller. - &::ink::MessageResult::Ok(result), - ); + #message_return(flag, result); #[cfg(feature = "std")] return ::core::result::Result::Ok(()); @@ -859,17 +933,14 @@ impl Dispatch<'_> { #( #message_variants ),* } - impl ::ink::reflect::DecodeDispatch for __ink_MessageDecoder { - fn decode_dispatch(input: &mut I) - -> ::core::result::Result - where - I: ::ink::scale::Input, - { + impl ::ink::env::DecodeDispatch for __ink_MessageDecoder { + fn decode_dispatch(input: &mut &[::core::primitive::u8]) + -> ::core::result::Result { #( #message_selector )* match <[::core::primitive::u8; 4usize] as ::ink::scale::Decode>::decode(input) - .map_err(|_| ::ink::reflect::DispatchError::InvalidSelector)? + .map_err(|_| ::ink::env::DispatchError::InvalidSelector)? { #( #message_match , )* _invalid => #possibly_wildcard_selector_message @@ -877,16 +948,6 @@ impl Dispatch<'_> { } } - impl ::ink::scale::Decode for __ink_MessageDecoder { - fn decode(input: &mut I) -> ::core::result::Result - where - I: ::ink::scale::Input, - { - ::decode_dispatch(input) - .map_err(::core::convert::Into::into) - } - } - fn push_contract(contract: ::core::mem::ManuallyDrop<#storage_ident>, mutates: bool) { if mutates { ::ink::env::set_contract_storage::<::ink::primitives::Key, #storage_ident>( @@ -900,7 +961,7 @@ impl Dispatch<'_> { #[allow(clippy::nonminimal_bool, clippy::let_unit_value, dead_code)] fn execute_dispatchable( self - ) -> ::core::result::Result<(), ::ink::reflect::DispatchError> { + ) -> ::core::result::Result<(), ::ink::env::DispatchError> { let key = <#storage_ident as ::ink::storage::traits::StorageKey>::KEY; let mut contract: ::core::mem::ManuallyDrop<#storage_ident> = ::core::mem::ManuallyDrop::new( diff --git a/crates/ink/ir/Cargo.toml b/crates/ink/ir/Cargo.toml index ef9bf8a22c..86eb2a0f21 100644 --- a/crates/ink/ir/Cargo.toml +++ b/crates/ink/ir/Cargo.toml @@ -26,6 +26,7 @@ either = { workspace = true } blake2 = { workspace = true } impl-serde = { workspace = true } ink_prelude = { workspace = true } +sha3 = { workspace = true } [features] default = [ "std" ] diff --git a/crates/ink/ir/src/ir/config.rs b/crates/ink/ir/src/ir/config.rs index 3942071262..ce159a773d 100644 --- a/crates/ink/ir/src/ir/config.rs +++ b/crates/ink/ir/src/ir/config.rs @@ -29,6 +29,8 @@ pub struct Config { /// be used to change the underlying environmental types of an ink! smart /// contract. env: Option, + /// todo: docs + abi_encoding: AbiEncoding, /// The set of attributes that can be passed to call builder in the codegen. whitelisted_attributes: WhitelistedAttributes, } @@ -39,6 +41,7 @@ impl TryFrom for Config { fn try_from(args: ast::AttributeArgs) -> Result { let mut env: Option<(Environment, ast::MetaNameValue)> = None; let mut whitelisted_attributes = WhitelistedAttributes::default(); + let mut abi_encoding: Option<(AbiEncoding, ast::MetaNameValue)> = None; for arg in args.into_iter() { if arg.name().is_ident("env") { @@ -56,6 +59,37 @@ impl TryFrom for Config { "expected a path value for `env` ink! configuration argument", )); } + } else if arg.name().is_ident("abi_encoding") { + if let Some((_, ast)) = abi_encoding { + return Err(duplicate_config_err( + ast, + arg, + "abi_encoding", + "contract", + )); + } + let encoding = arg + .name_value() + .zip(arg.value().and_then(ast::MetaValue::as_string)); + if let Some((name_value, path)) = encoding { + let encoding = match path.as_str() { + "scale" => AbiEncoding::Scale, + "rlp" => AbiEncoding::Rlp, + "all" => AbiEncoding::All, + _ => { + return Err(format_err_spanned!( + arg, + "expected one of `scale`, `rlp` or `all` for `abi_encoding` ink! configuration argument", + )); + } + }; + abi_encoding = Some((encoding, name_value.clone())) + } else { + return Err(format_err_spanned!( + arg, + "expected a string value for `abi_encoding` ink! configuration argument", + )); + } } else if arg.name().is_ident("keep_attr") { if let Some(name_value) = arg.name_value() { whitelisted_attributes.parse_arg_value(name_value)?; @@ -74,6 +108,8 @@ impl TryFrom for Config { } Ok(Config { env: env.map(|(value, _)| value), + abi_encoding: abi_encoding + .map_or(AbiEncoding::default(), |(encoding, _)| encoding), whitelisted_attributes, }) } @@ -91,6 +127,10 @@ impl Config { .unwrap_or(Environment::default().path) } + pub fn abi_encoding(&self) -> &AbiEncoding { + &self.abi_encoding + } + /// Return set of attributes that can be passed to call builder in the codegen. pub fn whitelisted_attributes(&self) -> &WhitelistedAttributes { &self.whitelisted_attributes @@ -112,6 +152,29 @@ impl Default for Environment { } } +/// Which format is used for ABI encoding. +#[derive(Debug, Clone, PartialEq, Eq, Default)] + +pub enum AbiEncoding { + /// Scale codec, the default. + #[default] + Scale, + /// RLP codec, useful for compatibility with Solidity contracts. + Rlp, + /// Support both Scale and RLP encoding for each contract entry point. + All, +} + +impl AbiEncoding { + pub fn is_rlp(&self) -> bool { + matches!(self, Self::Rlp | Self::All) + } + + pub fn is_scale(&self) -> bool { + matches!(self, Self::Scale | Self::All) + } +} + #[cfg(test)] mod tests { use super::*; @@ -145,6 +208,7 @@ mod tests { path: syn::parse_quote! { ::my::env::Types }, }), whitelisted_attributes: Default::default(), + abi_encoding: Default::default(), }), ) } @@ -195,6 +259,7 @@ mod tests { }, Ok(Config { env: None, + abi_encoding: Default::default(), whitelisted_attributes: attrs, }), ) diff --git a/crates/ink/ir/src/ir/item_impl/callable.rs b/crates/ink/ir/src/ir/item_impl/callable.rs index 9391c6809d..1fc222c339 100644 --- a/crates/ink/ir/src/ir/item_impl/callable.rs +++ b/crates/ink/ir/src/ir/item_impl/callable.rs @@ -48,6 +48,8 @@ pub struct CallableWithSelector<'a, C> { /// The composed selector computed by the associated implementation block /// and the given callable. composed_selector: ir::Selector, + /// The composed selector for the callable with RLP encoding. + composed_rlp_selector: ir::Selector, /// The parent implementation block. item_impl: &'a ir::ItemImpl, /// The actual callable. @@ -69,6 +71,7 @@ where pub(super) fn new(item_impl: &'a ir::ItemImpl, callable: &'a C) -> Self { Self { composed_selector: compose_selector(item_impl, callable), + composed_rlp_selector: compose_selector_rlp(item_impl, callable), item_impl, callable, } @@ -81,6 +84,11 @@ impl<'a, C> CallableWithSelector<'a, C> { self.composed_selector } + /// Returns the composed selector of the ink! callable with RLP encoding. + pub fn composed_rlp_selector(&self) -> ir::Selector { + self.composed_rlp_selector + } + /// Returns a shared reference to the underlying callable. pub fn callable(&self) -> &'a C { self.callable @@ -324,13 +332,34 @@ where if let Some(selector) = callable.user_provided_selector() { return *selector } + let preimage = compose_selector_preimage(item_impl, callable); + ir::Selector::compute(&preimage) +} + +/// Returns the composed selector of the ink! callable. +fn compose_selector_rlp(item_impl: &ir::ItemImpl, callable: &C) -> ir::Selector +where + C: Callable, +{ + // todo: handle user provided RLP selector... + if let Some(selector) = callable.user_provided_selector() { + return *selector + } + let preimage = compose_selector_preimage(item_impl, callable); + ir::Selector::compute_keccak(&preimage) +} + +fn compose_selector_preimage(item_impl: &ir::ItemImpl, callable: &C) -> Vec +where + C: Callable, +{ let callable_ident = callable.ident().to_string().into_bytes(); let namespace_bytes = item_impl .namespace() .map(|namespace| namespace.as_bytes().to_vec()) .unwrap_or_default(); let separator = &b"::"[..]; - let joined = match item_impl.trait_path() { + match item_impl.trait_path() { None => { // Inherent implementation block: if namespace_bytes.is_empty() { @@ -362,8 +391,7 @@ where [namespace_bytes, path_bytes, callable_ident].join(separator) } } - }; - ir::Selector::compute(&joined) + } } /// Ensures that common invariants of externally callable ink! entities are met. diff --git a/crates/ink/ir/src/ir/mod.rs b/crates/ink/ir/src/ir/mod.rs index a6f076470e..5e3baaa735 100644 --- a/crates/ink/ir/src/ir/mod.rs +++ b/crates/ink/ir/src/ir/mod.rs @@ -26,6 +26,7 @@ mod item; mod item_impl; mod item_mod; mod selector; +mod sha3; mod storage_item; mod trait_def; pub mod utils; diff --git a/crates/ink/ir/src/ir/selector.rs b/crates/ink/ir/src/ir/selector.rs index 3ed8761a1e..79f9b3cff2 100644 --- a/crates/ink/ir/src/ir/selector.rs +++ b/crates/ink/ir/src/ir/selector.rs @@ -12,7 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -use super::blake2::blake2b_256; +use super::{ + blake2::blake2b_256, + sha3::keccak_256, +}; use crate::literal::HexLiteral; use proc_macro2::TokenStream as TokenStream2; use std::marker::PhantomData; @@ -71,6 +74,13 @@ impl Selector { Self::from([output[0], output[1], output[2], output[3]]) } + /// Computes the Keccak 256-bit based selector from the given input bytes. + pub fn compute_keccak(input: &[u8]) -> Self { + let mut output = [0; 32]; + keccak_256(input, &mut output); + Self::from([output[0], output[1], output[2], output[3]]) + } + /// # Note /// /// - `trait_prefix` is `None` when computing the selector of ink! constructors and diff --git a/crates/ink/ir/src/ir/sha3.rs b/crates/ink/ir/src/ir/sha3.rs new file mode 100644 index 0000000000..9e995506e6 --- /dev/null +++ b/crates/ink/ir/src/ir/sha3.rs @@ -0,0 +1,79 @@ +// Copyright (C) Use Ink (UK) Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/// Computes the KECCAK 256-bit hash for the given input and stores it in output. +pub fn keccak_256(input: &[u8], output: &mut [u8; 32]) { + use sha3::{ + digest::generic_array::GenericArray, + Digest as _, + }; + let mut hasher = sha3::Keccak256::new(); + hasher.update(input); + hasher.finalize_into(<&mut GenericArray>::from(&mut output[..])); +} + +// /// Computes the BLAKE2b-256 bit hash of a string or byte string literal. +// /// +// /// # Note +// /// +// /// This is mainly used for analysis and codegen of the `blake2x256!` macro. +// #[derive(Debug)] +// pub struct Blake2x256Macro { +// hash: [u8; 32], +// input: syn::Lit, +// } + +// impl Blake2x256Macro { +// /// Returns the underlying selector. +// pub fn hash(&self) -> [u8; 32] { +// self.hash +// } +// +// /// Returns the literal input of the BLAKE-2b hash. +// pub fn input(&self) -> &syn::Lit { +// &self.input +// } +// } +// +// impl TryFrom for Blake2x256Macro { +// type Error = syn::Error; +// +// fn try_from(input: TokenStream2) -> Result { +// let input_span = input.span(); +// let lit = syn::parse2::(input).map_err(|error| { +// format_err!( +// input_span, +// "expected string or byte string literal as input: {}", +// error +// ) +// })?; +// let input_bytes = match lit { +// syn::Lit::Str(ref lit_str) => lit_str.value().into_bytes(), +// syn::Lit::ByteStr(ref byte_str) => byte_str.value(), +// invalid => { +// return Err(format_err!( +// invalid.span(), +// "expected string or byte string literal as input. found {:?}", +// invalid, +// )) +// } +// }; +// let mut output = [0u8; 32]; +// blake2b_256(&input_bytes, &mut output); +// Ok(Self { +// hash: output, +// input: lit, +// }) +// } +// } diff --git a/crates/ink/src/codegen/dispatch/execution.rs b/crates/ink/src/codegen/dispatch/execution.rs index 4662995122..95428d55d3 100644 --- a/crates/ink/src/codegen/dispatch/execution.rs +++ b/crates/ink/src/codegen/dispatch/execution.rs @@ -12,8 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::reflect::DispatchError; -use ink_env::Environment; +use ink_env::{ + DispatchError, + Environment, +}; use ink_primitives::U256; /// Returns `Ok` if the caller did not transfer additional value to the callee. diff --git a/crates/ink/src/lib.rs b/crates/ink/src/lib.rs index ad89bd09d6..d4efd0564b 100644 --- a/crates/ink/src/lib.rs +++ b/crates/ink/src/lib.rs @@ -36,6 +36,7 @@ mod contract_ref; mod env_access; mod message_builder; +pub use alloy_rlp as rlp; pub use ink_env as env; #[cfg(feature = "std")] pub use ink_metadata as metadata; diff --git a/crates/ink/tests/ui/contract/pass/message-selector.rs b/crates/ink/tests/ui/contract/pass/message-selector.rs index 3a9212c4ea..e39af1435e 100644 --- a/crates/ink/tests/ui/contract/pass/message-selector.rs +++ b/crates/ink/tests/ui/contract/pass/message-selector.rs @@ -1,8 +1,8 @@ #![allow(unexpected_cfgs)] use contract::Contract; - #[ink::contract] + mod contract { #[ink(storage)] pub struct Contract {} diff --git a/crates/primitives/Cargo.toml b/crates/primitives/Cargo.toml index b98872eb78..6c288f3999 100644 --- a/crates/primitives/Cargo.toml +++ b/crates/primitives/Cargo.toml @@ -17,9 +17,11 @@ include = ["/Cargo.toml", "src/**/*.rs", "/README.md", "/LICENSE"] [dependencies] derive_more = { workspace = true, features = ["from", "display"] } ink_prelude = { workspace = true } +alloy-rlp = { workspace = true, features = ["derive"] } scale = { workspace = true, features = ["max-encoded-len"] } scale-decode = { workspace = true, features = ["derive"] } scale-encode = { workspace = true, features = ["derive"], optional = true } +pallet-revive-uapi.workspace = true primitive-types = { version = "0.13.1", default-features = false, features = ["codec"]} scale-info = { workspace = true, features = ["derive"], optional = true } xxhash-rust = { workspace = true, features = ["const_xxh32"] } @@ -34,6 +36,7 @@ ink_env = { workspace = true, default-features = false } [features] default = [ "std" ] std = [ + "alloy-rlp/std", "ink/std", "ink_env/std", "ink_prelude/std", diff --git a/crates/primitives/src/lib.rs b/crates/primitives/src/lib.rs index 0ce6a21b50..73a1b5686c 100644 --- a/crates/primitives/src/lib.rs +++ b/crates/primitives/src/lib.rs @@ -38,6 +38,10 @@ pub use self::{ Key, KeyComposer, }, + reflect::{ + DecodeDispatch, + DispatchError, + }, types::{ AccountId, Clear, diff --git a/crates/primitives/src/reflect/dispatch.rs b/crates/primitives/src/reflect/dispatch.rs index f1b498d978..abb736fed5 100644 --- a/crates/primitives/src/reflect/dispatch.rs +++ b/crates/primitives/src/reflect/dispatch.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use core::fmt::Display; +use pallet_revive_uapi::ReturnFlags; /// Stores various information of the respective dispatchable ink! message. /// @@ -111,6 +111,14 @@ pub trait DispatchableMessageInfo { /// `&mut self` to `&self` with our current dispatch codegen architecture. const CALLABLE: fn(&mut Self::Storage, Self::Input) -> Self::Output; + /// closure for decoding + const DECODE: fn( + &mut &[::core::primitive::u8], + ) -> Result; + + /// closure for returning per encoding todo: docs + const RETURN: fn(ReturnFlags, Self::Output) -> !; + /// Yields `true` if the dispatchable ink! message mutates the ink! storage. const MUTATES: bool; /// Yields `true` if the dispatchable ink! message is payable. @@ -119,6 +127,8 @@ pub trait DispatchableMessageInfo { const SELECTOR: [u8; 4]; /// The label of the dispatchable ink! message. const LABEL: &'static str; + /// The encoding of input and output data for the message + const ENCODING: Encoding; } /// Stores various information of the respective dispatchable ink! constructor. @@ -215,6 +225,17 @@ pub trait DispatchableConstructorInfo { const LABEL: &'static str; } +/// todo: comment +pub enum Encoding { + Scale, + Rlp, +} + +/// Custom unit type for RLP encodable messages which return `()`. +/// This is because ['alloy_rlp::Encodable`] is not implemented for the build-in `()` type +#[derive(alloy_rlp::RlpEncodable)] +pub struct RlpUnit {} + mod private { /// Seals the implementation of `ConstructorReturnType`. pub trait Sealed {} @@ -378,7 +399,7 @@ impl ConstructorOutput for ConstructorOutputValue> { /// ``` pub trait ContractMessageDecoder { /// The ink! smart contract message decoder type. - type Type: scale::Decode + ExecuteDispatchable; + type Type: DecodeDispatch + ExecuteDispatchable; } /// Generated type used to decode all dispatchable ink! constructors of the ink! smart @@ -500,7 +521,7 @@ pub enum DispatchError { PaidUnpayableMessage, } -impl Display for DispatchError { +impl core::fmt::Display for DispatchError { fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { write!(f, "{}", self.as_str()) } @@ -509,7 +530,7 @@ impl Display for DispatchError { impl DispatchError { /// Returns a string representation of the error. #[inline] - pub fn as_str(&self) -> &'static str { + fn as_str(&self) -> &'static str { match self { Self::InvalidSelector => "unable to decode selector", Self::UnknownSelector => "encountered unknown selector", @@ -546,77 +567,8 @@ impl From for scale::Error { /// /// # Usage /// -/// ``` -/// # use ink::reflect::{ContractMessageDecoder, DecodeDispatch, DispatchError}; -/// # use ink::selector_bytes; -/// # use scale::Encode; -/// -/// #[ink::contract] -/// pub mod contract { -/// #[ink(storage)] -/// pub struct Contract {} -/// -/// impl Contract { -/// #[ink(constructor)] -/// pub fn constructor() -> Self { Self {} } -/// -/// #[ink(message)] -/// pub fn message(&self, input_1: bool, input_2: i32) {} -/// } -/// } -/// -/// use contract::Contract; -/// -/// fn main() { -/// // Valid call to `message`: -/// { -/// let mut input_bytes = Vec::new(); -/// input_bytes.extend(selector_bytes!("message")); -/// input_bytes.extend(true.encode()); -/// input_bytes.extend(42i32.encode()); -/// assert!( -/// <::Type as DecodeDispatch>::decode_dispatch( -/// &mut &input_bytes[..]).is_ok() -/// ); -/// } -/// // Invalid call with invalid selector (or empty input). -/// { -/// let mut input_bytes = Vec::new(); -/// assert_eq!( -/// <::Type -/// as DecodeDispatch>::decode_dispatch(&mut &input_bytes[..]) -/// # .map(|_| ()) -/// .unwrap_err(), -/// DispatchError::InvalidSelector, -/// ); -/// } -/// // Invalid call to `message` with unknown selector. -/// { -/// let mut input_bytes = Vec::new(); -/// input_bytes.extend(selector_bytes!("unknown_selector")); -/// assert_eq!( -/// <::Type -/// as DecodeDispatch>::decode_dispatch(&mut &input_bytes[..]) -/// # .map(|_| ()) -/// .unwrap_err(), -/// DispatchError::UnknownSelector, -/// ); -/// } -/// // Invalid call to `message` with invalid (or missing) parameters. -/// { -/// let mut input_bytes = Vec::new(); -/// input_bytes.extend(selector_bytes!("message")); -/// assert_eq!( -/// <::Type -/// as DecodeDispatch>::decode_dispatch(&mut &input_bytes[..]) -/// # .map(|_| ()) -/// .unwrap_err(), -/// DispatchError::InvalidParameters, -/// ); -/// } -/// } -/// ``` -pub trait DecodeDispatch: scale::Decode { - /// Decodes an ink! dispatch input into a known selector and its expected parameters. - fn decode_dispatch(input: &mut I) -> Result; +/// todo: prev doc test used a contract instance, it was in the `ink!` crate. +pub trait DecodeDispatch: Sized { + /// todo: docs + fn decode_dispatch(input: &mut &[u8]) -> Result; } diff --git a/crates/primitives/src/reflect/mod.rs b/crates/primitives/src/reflect/mod.rs index 79e7fc596e..5842de9d76 100644 --- a/crates/primitives/src/reflect/mod.rs +++ b/crates/primitives/src/reflect/mod.rs @@ -38,7 +38,9 @@ pub use self::{ DispatchError, DispatchableConstructorInfo, DispatchableMessageInfo, + Encoding, ExecuteDispatchable, + RlpUnit, }, trait_def::{ TraitDefinitionRegistry, diff --git a/integration-tests/public/rlp/Cargo.toml b/integration-tests/public/rlp/Cargo.toml new file mode 100755 index 0000000000..6676641218 --- /dev/null +++ b/integration-tests/public/rlp/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "rlp" +version = "5.0.0" +authors = ["Use Ink "] +edition = "2021" +publish = false + +[dependencies] +ink = { path = "../../../crates/ink", default-features = false } + +[dev-dependencies] +ink_e2e = { path = "../../../crates/e2e", features = ["sandbox"] } +ink_sandbox = { path = "../../../crates/e2e/sandbox" } +pallet-contracts = { version = "38.0.0", default-features = false } +sha3 = { version = "0.10" } + +[lib] +path = "lib.rs" + +[features] +default = ["std"] +std = [ + "ink/std", +] +ink-as-dependency = [] +e2e-tests = [] diff --git a/integration-tests/public/rlp/e2e_tests.rs b/integration-tests/public/rlp/e2e_tests.rs new file mode 100644 index 0000000000..4baf8e2732 --- /dev/null +++ b/integration-tests/public/rlp/e2e_tests.rs @@ -0,0 +1,131 @@ +use super::rlp::*; +use ink_e2e::{ + ContractsRegistry, + Keypair, +}; +use ink_sandbox::{ + api::balance_api::BalanceAPI, + AccountId32, +}; +use pallet_contracts::ExecReturnValue; + +#[test] +fn call_rlp_encoded_message() { + let built_contracts = ::ink_e2e::build_root_and_contract_dependencies(); + let contracts = ContractsRegistry::new(built_contracts); + + let mut sandbox = ink_e2e::DefaultSandbox::default(); + let caller = ink_e2e::alice(); + + sandbox + .mint_into( + &caller.public_key().0.into(), + 1_000_000_000_000_000u128.into(), + ) + .unwrap_or_else(|_| panic!("Failed to mint tokens")); + + // given + let constructor = RlpRef::new(false); + let params = constructor + .endowment(0u32.into()) + .code_hash(ink::primitives::Clear::CLEAR_HASH) + .salt_bytes(Vec::new()) + .params(); + let exec_input = params.exec_input(); + + let code = contracts.load_code("rlp"); + let contract_account_id = + ::deploy_contract( + &mut sandbox, + code, + 0, + ink::scale::Encode::encode(&exec_input), + vec![0u8], + caller.public_key().0.into(), + ::default_gas_limit(), + None, + ) + .result + .expect("sandbox deploy contract failed") + .account_id; + + let mut contract = ContractSandbox { + sandbox, + contract_account_id, + }; + + // set value + contract.call("set_value", true, caller.clone()); + + // get value + let value: bool = + contract.call_with_return_value("get_value", Vec::::new(), caller); + + assert!(value, "value should have been set to true"); +} + +struct ContractSandbox { + sandbox: ink_e2e::DefaultSandbox, + contract_account_id: AccountId32, +} + +impl ContractSandbox { + fn call_with_return_value( + &mut self, + message: &str, + args: Args, + caller: Keypair, + ) -> Ret + where + Args: ink::rlp::Encodable, + Ret: ink::rlp::Decodable, + { + let result = self.call(message, args, caller); + ink::rlp::Decodable::decode(&mut &result[..]).expect("decode failed") + } + + fn call(&mut self, message: &str, args: Args, caller: Keypair) -> Vec + where + Args: ink::rlp::Encodable, + { + let mut data = keccak_selector(message.as_bytes()); + let mut args_buf = Vec::new(); + ink::rlp::Encodable::encode(&args, &mut args_buf); + data.append(&mut args_buf); + + let result = self.call_raw(data, caller); + assert!(!result.did_revert(), "'{message}' failed {:?}", result); + result.data + } + + fn call_raw(&mut self, data: Vec, caller: Keypair) -> ExecReturnValue { + let result = + + ::call_contract( + &mut self.sandbox, + self.contract_account_id.clone(), + 0, + data, + caller.public_key().0.into(), + ::default_gas_limit(), + None, + pallet_contracts::Determinism::Enforced, + ) + .result + .expect("sandbox call contract failed"); + + result + } +} + +fn keccak_selector(input: &[u8]) -> Vec { + let mut output = [0; 32]; + use sha3::{ + digest::generic_array::GenericArray, + Digest as _, + }; + let mut hasher = sha3::Keccak256::new(); + hasher.update(input); + hasher.finalize_into(<&mut GenericArray>::from(&mut output[..])); + vec![output[0], output[1], output[2], output[3]] +} diff --git a/integration-tests/public/rlp/lib.rs b/integration-tests/public/rlp/lib.rs new file mode 100755 index 0000000000..2a0a12126b --- /dev/null +++ b/integration-tests/public/rlp/lib.rs @@ -0,0 +1,34 @@ +//! A smart contract to test using RLP encoding for EVM compatibility. + +#![cfg_attr(not(feature = "std"), no_std, no_main)] + +#[ink::contract(abi_encoding = "rlp")] +mod rlp { + #[ink(storage)] + #[derive(Default)] + pub struct Rlp { + value: bool, + } + + impl Rlp { + #[ink(constructor)] + pub fn new(value: bool) -> Self { + Self { value } + } + + /// Set the value + #[ink(message)] + pub fn set_value(&mut self, value: bool) { + self.value = value; + } + + /// Set the value + #[ink(message)] + pub fn get_value(&self) -> bool { + self.value + } + } +} + +#[cfg(all(test, feature = "e2e-tests"))] +mod e2e_tests;