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

Call ink! contracts from Solidity: support RLP encoding #2345

Draft
wants to merge 42 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
46a62f7
Add integration test
ascjones Nov 6, 2024
54d1aa1
Generate RLP selector callables for each message
ascjones Nov 8, 2024
ccd2feb
Merge branch 'master' into aj/rlp
ascjones Nov 8, 2024
3c9a3e2
Cargo.lock
ascjones Nov 8, 2024
4e0ee32
WIP implementing RLP encoding entry points
ascjones Nov 13, 2024
e423a11
Return value encoding
ascjones Nov 14, 2024
e2911d0
Fmt
ascjones Nov 14, 2024
dd749e4
Merge branch 'master' into aj/rlp
ascjones Nov 14, 2024
e5e576e
Cargo.lock
ascjones Nov 14, 2024
e43da98
Elevate DecodeDispatch trait to env to allow use in `api::decode_input`
ascjones Nov 15, 2024
6352abd
WIP adding in actual decoding
ascjones Nov 15, 2024
4c742fb
Merge branch 'master' into aj/rlp
ascjones Nov 15, 2024
f5e7804
Merge branch 'master' into aj/rlp
ascjones Nov 18, 2024
3f3860d
Comment out println!
ascjones Nov 18, 2024
b7b012f
Define empty EncodedScope impl
ascjones Nov 18, 2024
95a823c
WIP buffer
ascjones Nov 19, 2024
5461316
onchain rlp return value impl
ascjones Nov 19, 2024
3752f8c
offchain fn impl
ascjones Nov 20, 2024
e0d834c
WIP e2e integration test
ascjones Nov 20, 2024
d86f5f7
WIP e2e integration test
ascjones Nov 27, 2024
d0a8c0c
WIP raw instantiate with sandbox
ascjones Nov 27, 2024
77fd8b1
Don't use macro, use sandbox directly
ascjones Nov 28, 2024
1731070
Fund accounts
ascjones Nov 28, 2024
07fb467
WIP e2e test
ascjones Nov 29, 2024
241445f
make e2e test fail
ascjones Dec 4, 2024
a38d63a
hook up message dispatch
ascjones Dec 4, 2024
6e19fcd
E2E test success!
ascjones Dec 4, 2024
7681aa7
Parse encoding config
ascjones Dec 10, 2024
c508d95
wip: only generate RLP dispatchables when enabled
ascjones Dec 10, 2024
917fc5e
only generate RLP message infos if enabled
ascjones Dec 11, 2024
7e792e0
Unused import
ascjones Dec 11, 2024
5e18085
Merge branch 'master' into aj/rlp
ascjones Dec 11, 2024
6b8c165
Cargo.lock
ascjones Dec 11, 2024
3ba2197
Unused imports
ascjones Dec 11, 2024
ea20e21
Errors and warnings
ascjones Dec 11, 2024
38453bb
Update pallet-contracts version
ascjones Dec 11, 2024
3d8d8ea
Unused imports
ascjones Dec 12, 2024
47e2592
fmt
ascjones Dec 12, 2024
37352a9
Refactor integration test
ascjones Dec 12, 2024
f57a377
Remove stuff
ascjones Dec 12, 2024
a1fbb03
Misc
ascjones Dec 12, 2024
65108d7
Fix unused output warning
ascjones Dec 12, 2024
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
26 changes: 26 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 @@ -56,6 +56,7 @@ quickcheck = { version = "1" }
quickcheck_macros = { version = "1" }
quote = { version = "1" }
rlibc = { 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 @@ -43,6 +43,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 }
pallet-contracts-uapi = { 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 @@ -70,6 +71,7 @@ ink = { path = "../ink" }
[features]
default = [ "std" ]
std = [
"alloy-rlp/std",
"blake2",
"ink_allocator/std",
"ink_prelude/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 @@ -33,6 +33,7 @@ use crate::{
FromAccountId,
LimitParamsV2,
},
dispatch::DecodeDispatch,
engine::{
EnvInstance,
OnInstance,
Expand All @@ -43,6 +44,7 @@ use crate::{
HashOutput,
},
types::Gas,
DispatchError,
Environment,
Result,
};
Expand Down Expand Up @@ -498,9 +500,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 All @@ -521,6 +523,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 @@ -32,6 +32,8 @@ use crate::{
CryptoHash,
HashOutput,
},
DecodeDispatch,
DispatchError,
Environment,
Result,
};
Expand Down Expand Up @@ -108,9 +110,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 All @@ -125,6 +127,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
2 changes: 1 addition & 1 deletion crates/env/src/call/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ pub mod utils {
#[cfg(not(feature = "revive"))]
pub use self::{
call_builder::CallV1,
create_builder::LimitParamsV1
create_builder::LimitParamsV1,
};
pub use self::{
call_builder::{
Expand Down
80 changes: 80 additions & 0 deletions crates/env/src/dispatch.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// 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.

/// An error that can occur during dispatch of ink! dispatchables.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum DispatchError {
/// Failed to decode into a valid dispatch selector.
InvalidSelector,
/// The decoded selector is not known to the dispatch decoder.
UnknownSelector,
/// Failed to decode the parameters for the selected dispatchable.
InvalidParameters,
/// Failed to read execution input for the dispatchable.
CouldNotReadInput,
/// Invalidly paid an unpayable dispatchable.
PaidUnpayableMessage,
}

impl core::fmt::Display for DispatchError {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(f, "{}", self.as_str())
}
}

impl DispatchError {
/// Returns a string representation of the error.
#[inline]
fn as_str(&self) -> &'static str {
match self {
Self::InvalidSelector => "unable to decode selector",
Self::UnknownSelector => "encountered unknown selector",
Self::InvalidParameters => "unable to decode input",
Self::CouldNotReadInput => "could not read input",
Self::PaidUnpayableMessage => "paid an unpayable message",
}
}
}

impl From<DispatchError> for scale::Error {
#[inline]
fn from(error: DispatchError) -> Self {
Self::from(error.as_str())
}
}

/// Decodes an ink! dispatch input into a known selector and its expected parameters.
///
/// # Note
///
/// This trait is automatically implemented for ink! message and constructor decoders.
///
/// # Errors
///
/// Returns an error if any of the decode steps failed:
///
/// - `InvalidSelector`: The first four bytes could not properly decoded into the
/// selector.
/// - `UnknownSelector`: The decoded selector did not match any of the expected ones.
/// - `InvalidParameters`: Failed to decoded the parameters for the selected dispatchable.
///
/// The other dispatch errors are handled by other structures usually.
///
/// # Usage
///
/// 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<Self, DispatchError>;
}
13 changes: 11 additions & 2 deletions crates/env/src/engine/off_chain/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ use crate::{
Sha2x256,
},
Clear,
DecodeDispatch,
DispatchError,
EnvBackend,
Environment,
Result,
Expand Down Expand Up @@ -236,9 +238,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 @@ -250,6 +252,13 @@ impl EnvBackend for EnvInstance {
unimplemented!("the off-chain env does not implement `return_value`")
}

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
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<'a> scale::Output for EncodeScope<'a> {
}
}

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
22 changes: 19 additions & 3 deletions crates/env/src/engine/on_chain/impls/pallet_contracts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ use crate::{
LimitParamsV1,
LimitParamsV2,
},
dispatch::{
DecodeDispatch,
DispatchError,
},
engine::{
on_chain::{
EncodeScope,
Expand Down Expand Up @@ -269,11 +273,13 @@ impl EnvBackend for EnvInstance {
ext::clear_storage_v1(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,
{
self.get_property::<T>(ext::input)
let full_scope = &mut self.scoped_buffer().take_rest();
ext::input(full_scope);
DecodeDispatch::decode_dispatch(&mut &full_scope[..])
}

fn return_value<R>(&mut self, flags: ReturnFlags, return_value: &R) -> !
Expand All @@ -286,6 +292,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
5 changes: 5 additions & 0 deletions crates/env/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ pub mod event;
pub mod hash;
mod types;

mod dispatch;
#[cfg(test)]
mod tests;

Expand Down Expand Up @@ -127,6 +128,10 @@ pub use self::{
ContractEnv,
ContractReference,
},
dispatch::{
DecodeDispatch,
DispatchError,
},
error::{
Error,
Result,
Expand Down
Loading
Loading