Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: improvements and testing for RLP ink! contracts #2375

Open
wants to merge 28 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
1981ce4
feat(generator): add DECODE and RETURN const to trait message generat…
peterwht Jan 15, 2025
e73a039
test: compile tests for RLP and ALL encoding -- wip
peterwht Jan 18, 2025
227aefe
feat(generator): wip RLP encoding for trait messages
peterwht Jan 18, 2025
17b9c95
fix(generator): don't double generate message with encoding ALL when …
peterwht Jan 18, 2025
f77d68e
feat(primitives): AccountId derives RlpEncodable
peterwht Jan 23, 2025
dea8dce
test(integration): RLP cross-contract call ink! <> ink!; wip
peterwht Jan 23, 2025
019ad69
build(deps): use consistent workspace import format
peterwht Jan 28, 2025
4c3f21c
test(integration): update RLP test to use revive
peterwht Jan 28, 2025
0144497
test(integration): refactor RLP test to use OriginFor. Cleanup commen…
peterwht Jan 28, 2025
f16844a
test(integration): rlp cross-contract test uses revive -- fails with …
peterwht Jan 28, 2025
01e4f46
feat: invoke contract uses DecodeDispatch return -- wip
peterwht Jan 29, 2025
9e16702
Revert "feat: invoke contract uses DecodeDispatch return -- wip"
peterwht Jan 30, 2025
ae5f44a
Merge branch 'master' into peter/rlp
peterwht Jan 30, 2025
bf08b28
fix(codegen): expected function signature of rlp_return_value
peterwht Feb 8, 2025
2a042dc
test(e2e): setup hardhat-revive for solidity testing
peterwht Feb 8, 2025
1a49c84
test(e2e): wip .sol calling ink! contract
peterwht Feb 8, 2025
b38b608
merge master
peterwht Feb 10, 2025
31f7667
feat(e2e): add node `url` to `Client`
peterwht Feb 10, 2025
5e971e4
test(e2e): solidity calls ink!, setup eth-rpc, setup, and run hardhat…
peterwht Feb 11, 2025
ba88473
chore(spellcheck): include alith
peterwht Feb 11, 2025
cabfcd9
test(e2e): verify value after flip from sol
peterwht Feb 11, 2025
2fbd8df
test(e2e): refactor to e2e_tests.rs file
peterwht Feb 11, 2025
e69d711
test(e2e): refactor hardhat command + general cleanup
peterwht Feb 11, 2025
379d03c
merge master
peterwht Feb 11, 2025
5eea25b
chore: remove comment
peterwht Feb 11, 2025
1daeb6d
style(deps): match base branch whitespace
peterwht Feb 11, 2025
9c6258b
style(tests): add new line to UI test
peterwht Feb 11, 2025
c2364c4
chore: address review; add whitespace, fix version, initial TODOs
peterwht Feb 15, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

just a temporary fix for compilation. Added TODO in #2401

// 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use contract::Contract;
peterwht marked this conversation as resolved.
Show resolved Hide resolved

#[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(),
);
}
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
Loading