Skip to content

Commit

Permalink
Call ink! contracts from Solidity: support RLP encoding (#2345)
Browse files Browse the repository at this point in the history
* Add integration test

* Generate RLP selector callables for each message

* Cargo.lock

* WIP implementing RLP encoding entry points

* Return value encoding

* Fmt

* Cargo.lock

* Elevate DecodeDispatch trait to env to allow use in `api::decode_input`

* WIP adding in actual decoding

* Comment out println!

* Define empty EncodedScope impl

* WIP buffer

* onchain rlp return value impl

* offchain fn impl

* WIP e2e integration test

* WIP e2e integration test

* WIP raw instantiate with sandbox

* Don't use macro, use sandbox directly

* Fund accounts

* WIP e2e test

* make e2e test fail

* hook up message dispatch

* E2E test success!

* Parse encoding config

* wip: only generate RLP dispatchables when enabled

* only generate RLP message infos if enabled

* Unused import

* Cargo.lock

* Unused imports

* Errors and warnings

* Update pallet-contracts version

* Unused imports

* fmt

* Refactor integration test

* Remove stuff

* Misc

* Fix unused output warning

* merge master

* refactor: move dispatch to primitives/reflect

* fix: issues after merge -- compiles!

* fmt

---------

Co-authored-by: Peter White <[email protected]>
  • Loading branch information
ascjones and peterwht authored Jan 28, 2025
1 parent 03a8eee commit d0b003e
Show file tree
Hide file tree
Showing 29 changed files with 693 additions and 185 deletions.
27 changes: 27 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
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 @@ -44,6 +44,7 @@ pub use backend_calls::{
CallBuilder,
InstantiateBuilder,
};
pub use client_utils::ContractsRegistry;
pub use contract_results::{
CallDryRunResult,
CallResult,
Expand Down
2 changes: 2 additions & 0 deletions crates/env/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
Expand Down Expand Up @@ -66,6 +67,7 @@ ink = { path = "../ink" }
[features]
default = [ "std" ]
std = [
"alloy-rlp/std",
"blake2",
"ink/std",
"ink_allocator/std",
Expand Down
20 changes: 18 additions & 2 deletions crates/env/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ use crate::{
Environment,
Gas,
},
DecodeDispatch,
DispatchError,
Result,
};
use ink_primitives::{
Expand Down Expand Up @@ -407,9 +409,9 @@ where
/// # Errors
///
/// If the given `T` cannot be properly decoded from the expected input.
pub fn decode_input<T>() -> Result<T>
pub fn decode_input<T>() -> core::result::Result<T, DispatchError>
where
T: scale::Decode,
T: DecodeDispatch,
{
<EnvInstance as OnInstance>::on_instance(|instance| {
EnvBackend::decode_input::<T>(instance)
Expand Down Expand Up @@ -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<R>(return_flags: ReturnFlags, return_value: &R) -> !
where
R: alloy_rlp::Encodable,
{
<EnvInstance as OnInstance>::on_instance(|instance| {
EnvBackend::return_value_rlp::<R>(instance, return_flags, return_value)
})
}

/// Appends the given message to the debug message buffer.
pub fn debug_message(message: &str) {
<EnvInstance as OnInstance>::on_instance(|instance| {
Expand Down
11 changes: 9 additions & 2 deletions crates/env/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ use crate::{
CryptoHash,
HashOutput,
},
DecodeDispatch,
DispatchError,
Result,
};
use ink_primitives::{
Expand Down Expand Up @@ -105,9 +107,9 @@ pub trait EnvBackend {
/// # Errors
///
/// If the given `T` cannot be properly decoded from the expected input.
fn decode_input<T>(&mut self) -> Result<T>
fn decode_input<T>(&mut self) -> core::result::Result<T, DispatchError>
where
T: scale::Decode;
T: DecodeDispatch;

/// Returns the value back to the caller of the executed contract.
///
Expand Down Expand Up @@ -137,6 +139,11 @@ pub trait EnvBackend {
where
R: scale::Encode;

/// todo: comment
fn return_value_rlp<R>(&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
Expand Down
21 changes: 15 additions & 6 deletions crates/env/src/engine/off_chain/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ use crate::{
},
test::callee,
Clear,
DecodeDispatch,
DispatchError,
EnvBackend,
Result,
TypedEnvBackend,
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -339,9 +341,9 @@ impl EnvBackend for EnvInstance {
self.engine.clear_storage(&key.encode())
}

fn decode_input<T>(&mut self) -> Result<T>
fn decode_input<T>(&mut self) -> core::result::Result<T, DispatchError>
where
T: scale::Decode,
T: DecodeDispatch,
{
unimplemented!("the off-chain env does not implement `input`")
}
Expand All @@ -364,6 +366,13 @@ impl EnvBackend for EnvInstance {
self.engine.set_storage(&[255_u8; 32], &v[..]);
}

fn return_value_rlp<R>(&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)
}
Expand Down Expand Up @@ -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));
Expand Down
19 changes: 19 additions & 0 deletions crates/env/src/engine/on_chain/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
27 changes: 23 additions & 4 deletions crates/env/src/engine/on_chain/pallet_revive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ use crate::{
FromAddr,
LimitParamsV2,
},
dispatch::{
DecodeDispatch,
DispatchError,
},
engine::on_chain::{
EncodeScope,
EnvInstance,
Expand Down Expand Up @@ -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 = <T as FromLittleEndian>::Bytes::default();
Expand Down Expand Up @@ -261,13 +266,13 @@ impl EnvBackend for EnvInstance {
ext::clear_storage(STORAGE_FLAGS, key)
}

fn decode_input<T>(&mut self) -> Result<T>
fn decode_input<T>(&mut self) -> core::result::Result<T, DispatchError>
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<R>(&mut self, flags: ReturnFlags, return_value: &R) -> !
Expand All @@ -280,6 +285,16 @@ impl EnvBackend for EnvInstance {
ext::return_value(flags, &self.buffer[..][..len]);
}

fn return_value_rlp<R>(&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) {}
Expand Down Expand Up @@ -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.")
}
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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>(
Expand All @@ -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| {
Expand Down Expand Up @@ -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)
Expand Down
4 changes: 4 additions & 0 deletions crates/env/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,10 @@ pub use ink_primitives::{
ContractReverseReference,
},
reflect,
reflect::{
DecodeDispatch,
DispatchError,
},
types,
};
#[doc(inline)]
Expand Down
2 changes: 2 additions & 0 deletions crates/ink/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"] }
Expand All @@ -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",
Expand Down
1 change: 1 addition & 0 deletions crates/ink/codegen/src/generator/arg_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading

0 comments on commit d0b003e

Please sign in to comment.