Skip to content

Commit

Permalink
feat: improvements and testing for RLP ink! contracts (#2375)
Browse files Browse the repository at this point in the history
* feat(generator): add DECODE and RETURN const to trait message generation for tests to build

* test: compile tests for RLP and ALL encoding -- wip

* feat(generator): wip RLP encoding for trait messages

* fix(generator): don't double generate message with encoding ALL when user provided

* feat(primitives): AccountId derives RlpEncodable

* test(integration): RLP cross-contract call ink! <> ink!; wip

* build(deps): use consistent workspace import format

* test(integration): update RLP test to use revive

* test(integration): refactor RLP test to use OriginFor. Cleanup comments and unused imports

* test(integration): rlp cross-contract test uses revive -- fails with DecodeError

* feat: invoke contract uses DecodeDispatch return -- wip

* Revert "feat: invoke contract uses DecodeDispatch return -- wip"

This reverts commit 01e4f46.

* fix(codegen): expected function signature of rlp_return_value

* test(e2e): setup hardhat-revive for solidity testing

* test(e2e): wip .sol calling ink! contract

* merge master

* feat(e2e): add node `url` to `Client`

* test(e2e): solidity calls ink!, setup eth-rpc, setup, and run hardhat script

* chore(spellcheck): include alith

* test(e2e): verify value after flip from sol

* test(e2e): refactor to e2e_tests.rs file

* test(e2e): refactor hardhat command + general cleanup

* chore: remove comment

* style(deps): match base branch whitespace

* style(tests): add new line to UI test

* chore: address review; add whitespace, fix version, initial TODOs
  • Loading branch information
peterwht authored Feb 17, 2025
1 parent 39157b3 commit b798c8d
Show file tree
Hide file tree
Showing 28 changed files with 4,985 additions and 69 deletions.
1 change: 1 addition & 0 deletions .config/cargo_spellcheck.dic
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
110

ABI
alith
AST
BLAKE2
BLAKE2b
Expand Down
4 changes: 2 additions & 2 deletions crates/e2e/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ ink_e2e_macro = { workspace = true, default-features = true }
ink = { workspace = true, default-features = true }
ink_env = { workspace = true, default-features = true }
ink_primitives = { workspace = true, default-features = true }
ink_sandbox = { version = "=6.0.0-alpha", path = "./sandbox", optional = true }
ink_sandbox = { version = "=6.0.0-alpha", path = "./sandbox", optional = true }

cargo_metadata = { workspace = true }
contract-build = { workspace = true }
Expand All @@ -36,7 +36,7 @@ tracing-subscriber = { workspace = true, features = ["env-filter"] }
scale = { workspace = true }
subxt = { workspace = true }
subxt-metadata = { workspace = true, optional = true }
subxt-signer = { workspace = true, features = ["subxt", "sr25519"] }
subxt-signer = { workspace = true, features = ["subxt", "sr25519", "unstable-eth"] }
thiserror = { workspace = true }
which = { workspace = true }

Expand Down
1 change: 1 addition & 0 deletions crates/e2e/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ use ink_primitives::{
H160,
H256,
};
pub use sp_weights::Weight;
use std::{
cell::RefCell,
sync::Once,
Expand Down
8 changes: 5 additions & 3 deletions crates/e2e/src/subxt_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,9 @@ where
C: subxt::Config,
E: Environment,
{
api: ReviveApi<C, E>,
contracts: ContractsRegistry,
// TODO (@peterwht): make private once call builder supports RLP
pub api: ReviveApi<C, E>,
pub contracts: ContractsRegistry,
url: String,
}

Expand Down Expand Up @@ -143,8 +144,9 @@ where
})
}

// TODO (@peterwht): private after call builder supports RLP
/// Executes an `instantiate_with_code` call and captures the resulting events.
async fn exec_instantiate(
pub async fn exec_instantiate(
&mut self,
signer: &Keypair,
code: Vec<u8>,
Expand Down
91 changes: 81 additions & 10 deletions crates/ink/codegen/src/generator/dispatch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,8 @@ impl Dispatch<'_> {
fn generate_dispatchable_message_infos(&self) -> TokenStream2 {
let span = self.contract.module().storage().span();
let storage_ident = self.contract.module().storage().ident();
let encoding = self.contract.config().abi_encoding();

let inherent_message_infos = self
.contract
.module()
Expand All @@ -280,7 +282,7 @@ impl Dispatch<'_> {

let mut message_infos = Vec::new();

if self.contract.config().abi_encoding().is_scale() {
if encoding.is_scale() {
message_infos.push(quote_spanned!(message_span=>
#( #cfg_attrs )*
impl ::ink::reflect::DispatchableMessageInfo<#selector_id> for #storage_ident {
Expand Down Expand Up @@ -326,7 +328,8 @@ impl Dispatch<'_> {
))
}

if self.contract.config().abi_encoding().is_rlp() {
// if encoding is `all` and the selector is user provided, we do not generate another message info
if encoding.is_rlp() && !(encoding.is_all() && message.user_provided_selector().is_some()) {
// 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());
Expand Down Expand Up @@ -373,8 +376,12 @@ impl Dispatch<'_> {
};
const DECODE: fn(&mut &[::core::primitive::u8]) -> ::core::result::Result<Self::Input, ::ink::env::DispatchError> =
#rlp_decode
#[cfg(not(feature = "std"))]
const RETURN: fn(::ink::env::ReturnFlags, Self::Output) -> ! =
#rlp_return_value
#[cfg(feature = "std")]
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;
Expand All @@ -400,7 +407,7 @@ impl Dispatch<'_> {
})
})
.flatten()
.map(|((trait_ident, trait_path), message)| {
.flat_map(|((trait_ident, trait_path), message)| {
// todo: trait message RLP encoding
let message_span = message.span();
let message_ident = message.ident();
Expand Down Expand Up @@ -428,12 +435,16 @@ impl Dispatch<'_> {
let input_tuple_bindings = generator::input_bindings_tuple(message.inputs());
let label = format!("{trait_ident}::{message_ident}");
let cfg_attrs = message.get_cfg_attrs(message_span);
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;

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| {
Expand Down Expand Up @@ -469,8 +480,68 @@ impl Dispatch<'_> {
const MUTATES: ::core::primitive::bool = #mutates;
const LABEL: &'static ::core::primitive::str = #label;
const ENCODING: ::ink::reflect::Encoding = ::ink::reflect::Encoding::Scale;
}
))
}

if encoding.is_rlp() && !(encoding.is_all() && message.user_provided_selector().is_some()) {
// 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| {
<Self::Input as ::ink::rlp::Decodable>::decode(input)
.map_err(|_| ::ink::env::DispatchError::InvalidParameters)
};
}
};
let rlp_return_value = message
.output()
.map(|_| quote! {
|flags, output| {
::ink::env::return_value_rlp::<Self::Output>(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<#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 as #trait_path>::#message_ident( storage #( , #input_bindings )* )
};
const DECODE: fn(&mut &[::core::primitive::u8]) -> ::core::result::Result<Self::Input, ::ink::env::DispatchError> =
#rlp_decode
const RETURN: fn(::ink::env::ReturnFlags, Self::Output) -> ! =
#rlp_return_value
const SELECTOR: [::core::primitive::u8; 4usize] = #selector;
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::Rlp;
}
))
}
message_infos
});
quote_spanned!(span=>
#( #inherent_message_infos )*
Expand Down
4 changes: 4 additions & 0 deletions crates/ink/ir/src/ir/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,10 @@ impl AbiEncoding {
pub fn is_scale(&self) -> bool {
matches!(self, Self::Scale | Self::All)
}

pub fn is_all(&self) -> bool {
matches!(self, Self::All)
}
}

#[cfg(test)]
Expand Down
1 change: 0 additions & 1 deletion crates/ink/ir/src/ir/item_impl/callable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,6 @@ fn compose_selector_rlp<C>(item_impl: &ir::ItemImpl, callable: &C) -> ir::Select
where
C: Callable,
{
// todo: handle user provided RLP selector...
if let Some(selector) = callable.user_provided_selector() {
return *selector
}
Expand Down
65 changes: 65 additions & 0 deletions crates/ink/tests/ui/contract/pass/message-selector-encoding-all.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use contract::Contract;

#[ink::contract(abi_encoding = "all")]
mod contract {
#[ink(storage)]
pub struct Contract {}

#[ink::trait_definition]
pub trait Messages {
#[ink(message, selector = 1)]
fn message_1(&self);
}

impl Contract {
#[ink(constructor)]
pub fn constructor() -> Self {
Self {}
}

#[ink(message, selector = 0xC0DE_CAFE)]
pub fn message_2(&self) {}

#[ink(message)]
pub fn message_3(&self) {}
}

impl Messages for Contract {
#[ink(message, selector = 1)]
fn message_1(&self) {}
}

#[ink::trait_definition]
pub trait Messages2 {
#[ink(message, selector = 0x12345678)]
fn message_4(&self);
}

impl Messages2 for Contract {
#[ink(message, selector = 0x12345678)]
fn message_4(&self) {}
}
}

fn main() {
assert_eq!(
<Contract as ::ink::reflect::DispatchableMessageInfo<1_u32>>::SELECTOR,
1_u32.to_be_bytes(),
);
assert_eq!(
<Contract as ::ink::reflect::DispatchableMessageInfo<0xC0DE_CAFE_u32>>::SELECTOR,
0xC0DE_CAFE_u32.to_be_bytes(),
);

// manually calculated "message_3"
const INHERENT_ID_RLP: u32 = 0x0cd0f0f1;
assert_eq!(
<Contract as ::ink::reflect::DispatchableMessageInfo<INHERENT_ID_RLP>>::SELECTOR,
[0x0C, 0xD0, 0xF0, 0xF1],
);

assert_eq!(
<Contract as ::ink::reflect::DispatchableMessageInfo<0x12345678_u32>>::SELECTOR,
0x12345678_u32.to_be_bytes(),
);
}
65 changes: 65 additions & 0 deletions crates/ink/tests/ui/contract/pass/message-selector-encoding-rlp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use contract::Contract;

#[ink::contract(abi_encoding = "rlp")]
mod contract {
#[ink(storage)]
pub struct Contract {}

#[ink::trait_definition]
pub trait Messages {
#[ink(message, selector = 1)]
fn message_1(&self);
}

impl Contract {
#[ink(constructor)]
pub fn constructor() -> Self {
Self {}
}

#[ink(message, selector = 0xC0DE_CAFE)]
pub fn message_2(&self) {}

#[ink(message)]
pub fn message_3(&self) {}
}

impl Messages for Contract {
#[ink(message, selector = 1)]
fn message_1(&self) {}
}

#[ink::trait_definition]
pub trait Messages2 {
#[ink(message, selector = 0x12345678)]
fn message_4(&self);
}

impl Messages2 for Contract {
#[ink(message, selector = 0x12345678)]
fn message_4(&self) {}
}
}

fn main() {
assert_eq!(
<Contract as ::ink::reflect::DispatchableMessageInfo<1_u32>>::SELECTOR,
1_u32.to_be_bytes(),
);
assert_eq!(
<Contract as ::ink::reflect::DispatchableMessageInfo<0xC0DE_CAFE_u32>>::SELECTOR,
0xC0DE_CAFE_u32.to_be_bytes(),
);

// manually calculated "message_3"
const INHERENT_ID_RLP: u32 = 0x0cd0f0f1;
assert_eq!(
<Contract as ::ink::reflect::DispatchableMessageInfo<INHERENT_ID_RLP>>::SELECTOR,
[0x0C, 0xD0, 0xF0, 0xF1],
);

assert_eq!(
<Contract as ::ink::reflect::DispatchableMessageInfo<0x12345678_u32>>::SELECTOR,
0x12345678_u32.to_be_bytes(),
);
}
2 changes: 1 addition & 1 deletion crates/primitives/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ 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
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"] }
Expand Down
2 changes: 2 additions & 0 deletions crates/primitives/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ use {
scale_info::TypeInfo,
};

use alloy_rlp::RlpDecodable;
/// The default environment `AccountId` type.
///
/// # Note
Expand All @@ -47,6 +48,7 @@ use {
PartialOrd,
Hash,
Decode,
RlpDecodable,
Encode,
MaxEncodedLen,
From,
Expand Down
Loading

0 comments on commit b798c8d

Please sign in to comment.