diff --git a/Cargo.lock b/Cargo.lock index a380386..d45c9ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5582,6 +5582,7 @@ dependencies = [ "sp-core", "sp-genesis-builder", "sp-inherents", + "sp-io", "sp-offchain", "sp-runtime", "sp-session", diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 006fff7..afa15de 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -61,6 +61,9 @@ frame-system-benchmarking = { workspace = true, optional = true } [build-dependencies] substrate-wasm-builder = { workspace = true, optional = true } +[dev-dependencies] +sp-io = { workspace = true } + [features] default = ["std"] std = [ diff --git a/runtime/src/IDemo.sol b/runtime/src/IDemo.sol new file mode 100644 index 0000000..40bd496 --- /dev/null +++ b/runtime/src/IDemo.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +/// @dev The on-chain address of the Demo precompile. +address constant DEMO_PRECOMPILE_ADDRESS = address(0xB0000); + +/// @title Interface for the Demo precompile +/// @notice A simple precompile demonstrating basic functionality. +/// @dev Documentation: +/// @dev - ink! Contract calling this interface: https://github.com/use-ink/ink-examples/precompile-demo +interface IDemo { + /// @notice Estimates the `Weight` required to execute a given XCM message. + /// @param mode The value `0` causes the `echo` function to revert. + /// @return If `mode > 0`, the input `message` is echoed back to the caller. + function echo(uint8 mode, bytes message) external view returns (bytes); +} \ No newline at end of file diff --git a/runtime/src/demo_precompile.rs b/runtime/src/demo_precompile.rs new file mode 100644 index 0000000..5b7d6e5 --- /dev/null +++ b/runtime/src/demo_precompile.rs @@ -0,0 +1,174 @@ +// Ensure we're `no_std` when compiling for Wasm. +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate alloc; + +use alloc::vec::Vec; +use core::{marker::PhantomData, num::NonZero}; +use pallet_revive::precompiles::{ + alloy::{self, sol_types::SolValue}, + AddressMatcher, Error, Ext, Precompile, +}; + +alloy::sol!("src/IDemo.sol"); +use IDemo::IDemoCalls; + +const LOG_TARGET: &str = "custom::precompile"; + +pub struct DemoPrecompile(PhantomData); + +impl Precompile for DemoPrecompile +where + Runtime: pallet_revive::Config, +{ + type T = Runtime; + const MATCHER: AddressMatcher = AddressMatcher::Fixed(NonZero::new(11).unwrap()); + const HAS_CONTRACT_INFO: bool = false; + type Interface = IDemo::IDemoCalls; + + fn call( + _address: &[u8; 20], + input: &Self::Interface, + env: &mut impl Ext, + ) -> Result, Error> { + let origin = env.caller(); + log::debug!(target: LOG_TARGET, "got a call from origin {origin:?}"); + + match input { + IDemoCalls::echo(IDemo::echoCall { mode, message }) => { + if *mode == 0 { + log::error!(target: LOG_TARGET, "reverting"); + return Err(Error::Revert("mode was set to 0".into())) + } + + log::debug!(target: LOG_TARGET, "echoing message back {message:?}"); + Ok(message.abi_encode()) + }, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use frame_support::{derive_impl, parameter_types, weights::Weight}; + use pallet_revive::{ + precompiles::{ + alloy::{ + hex, + primitives::Bytes, + sol_types::{SolInterface, SolValue}, + }, + H160, + }, + ExecConfig, U256, + }; + use sp_runtime::{traits::IdentityLookup, AccountId32, BuildStorage}; + + pub type AccountId = AccountId32; + pub type Balance = u128; + type Block = frame_system::mocking::MockBlock; + + pub const ALICE: AccountId32 = AccountId::new([0u8; 32]); + const CUSTOM_INITIAL_BALANCE: u128 = 100_000_000_000u128; + + parameter_types! { + pub const MinimumPeriod: u64 = 1; + } + + impl pallet_timestamp::Config for Test { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = MinimumPeriod; + type WeightInfo = (); + } + + frame_support::construct_runtime!( + pub enum Test + { + System: frame_system, + Balances: pallet_balances, + Revive: pallet_revive, + Timestamp: pallet_timestamp, + } + ); + + #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] + impl frame_system::Config for Test { + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Block = Block; + type AccountData = pallet_balances::AccountData; + } + + parameter_types! { + pub ExistentialDeposit: Balance = 1; + } + + #[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] + impl pallet_balances::Config for Test { + type Balance = Balance; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + } + + #[derive_impl(pallet_revive::config_preludes::TestDefaultConfig)] + impl pallet_revive::Config for Test { + type AddressMapper = pallet_revive::AccountId32Mapper; + type Balance = Balance; + type Currency = Balances; + type Precompiles = (DemoPrecompile,); + type Time = Timestamp; + type UploadOrigin = frame_system::EnsureSigned; + type InstantiateOrigin = frame_system::EnsureSigned; + } + + pub fn new_test_ext_with_balances( + balances: Vec<(AccountId, Balance)>, + ) -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); + + pallet_balances::GenesisConfig:: { balances, ..Default::default() } + .assimilate_storage(&mut t) + .unwrap(); + + pallet_revive::GenesisConfig:: { mapped_accounts: vec![ALICE], ..Default::default() } + .assimilate_storage(&mut t) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext + } + + #[test] + fn foobar() { + let balances = vec![(ALICE, CUSTOM_INITIAL_BALANCE)]; + new_test_ext_with_balances(balances).execute_with(|| { + let precompile_addr = H160::from( + hex::const_decode_to_array(b"00000000000000000000000000000000000B0000").unwrap(), + ); + + let mut msg: Vec = Vec::new(); + msg.push(123); + let echo_params = IDemo::echoCall { mode: 1, message: Bytes::from(msg) }; + let call = IDemo::IDemoCalls::echo(echo_params); + let encoded_call = call.abi_encode(); + + let result = pallet_revive::Pallet::::bare_call( + RuntimeOrigin::signed(ALICE), + precompile_addr, + U256::zero(), + Weight::MAX, + u128::MAX, + encoded_call, + ExecConfig::new_substrate_tx(), + ); + assert!(result.result.is_ok()); + let data = result.result.unwrap().data; + let bytes = Bytes::abi_decode_validate(&data).unwrap(); + let vec: Vec = bytes.0.into(); + assert_eq!(vec, vec![123]); + }); + } +} diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 74afd70..643919b 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -7,6 +7,7 @@ include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); mod assets_config; +mod demo_precompile; mod revive_config; extern crate alloc; diff --git a/runtime/src/revive_config.rs b/runtime/src/revive_config.rs index 175d8bb..1f5883d 100644 --- a/runtime/src/revive_config.rs +++ b/runtime/src/revive_config.rs @@ -1,6 +1,6 @@ use crate::{ - Address, Balance, Balances, EthExtraImpl, Perbill, Runtime, RuntimeCall, RuntimeEvent, - RuntimeHoldReason, RuntimeOrigin, Signature, Timestamp, + demo_precompile::DemoPrecompile, Address, Balance, Balances, EthExtraImpl, Perbill, Runtime, + RuntimeCall, RuntimeEvent, RuntimeHoldReason, RuntimeOrigin, Signature, Timestamp, }; use frame_support::{ parameter_types, @@ -38,7 +38,7 @@ impl pallet_revive::Config for Runtime { type DepositPerChildTrieItem = DepositPerChildTrieItem; type DepositPerByte = DepositPerByte; type WeightInfo = pallet_revive::weights::SubstrateWeight; - type Precompiles = (ERC20, ()>,); + type Precompiles = (ERC20, ()>, DemoPrecompile); type AddressMapper = pallet_revive::AccountId32Mapper; type RuntimeMemory = ConstU32<{ 128 * 1024 * 1024 }>; type PVFMemory = ConstU32<{ 512 * 1024 * 1024 }>;