Skip to content

Rve/add create2 precompile builtin #8904

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

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions substrate/frame/revive/src/precompiles/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

mod blake2f;
mod bn128;
mod create2;
mod ecrecover;
mod identity;
mod modexp;
Expand Down Expand Up @@ -53,6 +54,7 @@ type Production<T> = (
bn128::Bn128Pairing<T>,
blake2f::Blake2F<T>,
point_eval::PointEval<T>,
create2::Create2<T>,
);

#[cfg(feature = "runtime-benchmarks")]
Expand Down
122 changes: 122 additions & 0 deletions substrate/frame/revive/src/precompiles/builtin/create2.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// This file is part of Substrate.

// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0

// 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.

use sp_runtime::DispatchError;
use sp_core::H160;

use crate::Pallet as Contracts;
use crate::{
address, limits::code, precompiles::{BuiltinAddressMatcher, Error, ExtWithInfo, Precompiles, PrimitivePrecompile}, Config
};
use crate::H256;
use sp_core::U256;
use sp_runtime::traits::Bounded;
use crate::BalanceOf;
use crate::MomentOf;
use crate::address::AddressMapper;
use core::{marker::PhantomData, num::NonZero};

pub struct Create2<T>(PhantomData<T>);

impl<T: Config> PrimitivePrecompile for Create2<T>
{
type T = T;
const MATCHER: BuiltinAddressMatcher = BuiltinAddressMatcher::Fixed(NonZero::new(11).unwrap());
const HAS_CONTRACT_INFO: bool = true;

fn call_with_info(
address: &[u8; 20],
input: Vec<u8>,
env: &mut impl ExtWithInfo<T = Self::T>,
) -> Result<Vec<u8>, Error> {

// TODO(RVE): what value to put here?
let gas_limit = frame_support::weights::Weight::MAX;

// TODO(RVE): what value to put here?
let storage_deposit_limit = crate::DepositLimit::<BalanceOf::<Self::T>>::UnsafeOnlyForDryRun;

if input.len() < 160 {
Err(DispatchError::from("invalid input length"))?;
}
let endowment: &[u8; 32] = input[0..32].try_into().map_err(|_| DispatchError::from("invalid endowment"))?;
let code_offset: &[u8; 32] = input[32..64].try_into().map_err(|_| DispatchError::from("invalid code offset length"))?;
let code_length: &[u8; 32] = input[64..96].try_into().map_err(|_| DispatchError::from("invalid code length length"))?;
let salt_offset: &[u8; 32] = input[96..128].try_into().map_err(|_| DispatchError::from("invalid salt offset length"))?;
let salt_length: &[u8; 32] = input[128..160].try_into().map_err(|_| DispatchError::from("invalid salt length length"))?;

let code_offset1: u128 = u128::from_be_bytes(code_offset[0..16].try_into().map_err(|_| DispatchError::from("invalid code offset"))?);
let code_offset2: u128 = u128::from_be_bytes(code_offset[16..32].try_into().map_err(|_| DispatchError::from("invalid code offset"))?);
let code_length1: u128 = u128::from_be_bytes(code_length[0..16].try_into().map_err(|_| DispatchError::from("invalid code length"))?);
let code_length2: u128 = u128::from_be_bytes(code_length[16..32].try_into().map_err(|_| DispatchError::from("invalid code length"))?);
println!("code_offset1: {code_offset1}, code_offset2: {code_offset2}");
println!("code_length1: {code_length1}, code_length2: {code_length2}");

let salt_offset1: u128 = u128::from_be_bytes(salt_offset[0..16].try_into().map_err(|_| DispatchError::from("invalid salt offset"))?);
let salt_offset2: u128 = u128::from_be_bytes(salt_offset[16..32].try_into().map_err(|_| DispatchError::from("invalid salt offset"))?);
let salt_length1: u128 = u128::from_be_bytes(salt_length[0..16].try_into().map_err(|_| DispatchError::from("invalid salt length"))?);
let salt_length2: u128 = u128::from_be_bytes(salt_length[16..32].try_into().map_err(|_| DispatchError::from("invalid salt length"))?);

println!("salt_offset1: {salt_offset1}, salt_offset2: {salt_offset2}");
println!("salt_length1: {salt_length1}, salt_length2: {salt_length2}");

{
assert_eq!(input.len(), salt_offset2 as usize + salt_length2 as usize, "input length does not match expected length");
}

// TODO(RVE): this could potentially panic if the offsets are out of bounds.
let code_offset = code_offset2 as usize;
let code_length = code_length2 as usize;
let salt_offset = salt_offset2 as usize;
let salt_length = salt_length2 as usize;
let code = &input[code_offset..code_offset + code_length];
let salt = &input[salt_offset..salt_offset + salt_length];

println!("salt.len(): {}", salt.len());

let caller = env.caller();
let deployer_account_id = caller.account_id().map_err(|_| DispatchError::from("caller account_id is None"))?;
let deployer = T::AddressMapper::to_address(deployer_account_id);


println!("deployer: {deployer:?}");
let salt: &[u8; 32] = salt.try_into().map_err(|_| DispatchError::from("invalid salt length"))?;

let contract_address = crate::address::create2(&deployer, code, &[], salt);

let endowment_val = u32::from_be_bytes(endowment[28..32].try_into().unwrap());
let code_hash = sp_io::hashing::keccak_256(&code);
// env.inst
let instantiate_result = env.instantiate(
// frame_system::RawOrigin::Signed(deployer_account_id.clone()).into(),
// endowment_val.into(),
gas_limit,
U256::MAX,
H256::from(code_hash),
endowment_val.into(),
vec![], // input data for constructor, if any
Some(salt),
);

// Pad the contract address to 32 bytes (left padding with zeros)
let mut padded = [0u8; 32];
let addr = contract_address.as_ref();
padded[32 - addr.len()..].copy_from_slice(addr);
Ok(padded.to_vec())
}

}
76 changes: 76 additions & 0 deletions substrate/frame/revive/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,15 @@ use frame_support::{
},
weights::{constants::WEIGHT_REF_TIME_PER_SECOND, FixedFee, IdentityFee, Weight, WeightMeter},
};
// use sp_runtime::traits::Bounded;
use num_traits::Bounded;
use frame_system::{EventRecord, Phase};
use pallet_revive_fixtures::compile_module;
use pallet_revive_uapi::{ReturnErrorCode as RuntimeReturnCode, ReturnFlags};
use pallet_transaction_payment::{ConstFeeMultiplier, Multiplier};
use pretty_assertions::{assert_eq, assert_ne};
use sp_core::U256;
//use crate::U256;
use sp_io::hashing::blake2_256;
use sp_keystore::{testing::MemoryKeystore, KeystoreExt};
use sp_runtime::{
Expand Down Expand Up @@ -230,6 +233,7 @@ impl frame_system::Config for Test {
type AccountId = AccountId32;
type Lookup = IdentityLookup<Self::AccountId>;
type AccountData = pallet_balances::AccountData<u64>;
type Hash = sp_runtime::testing::H256;
}

#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)]
Expand Down Expand Up @@ -433,6 +437,77 @@ impl Default for Origin<Test> {
}
}

#[test]
fn create2_precompile_works() {
use sp_core::H160;
use crate::precompiles::Precompile;
use alloy_core::sol_types::SolInterface;
use precompiles::{INoInfo, NoInfo};

// let create2_precompile_addr = H160(NoInfo::<Test>::MATCHER.base_address());
let create2_precompile_addr = H160::from_low_u64_be(0x0B); // hardcoded 11 in create2.rs

let (code, code_hash) = compile_module("dummy").unwrap();
println!("code.len(): {}", code.len());

let value = [0u8; 32];
let mut code_offset = [0u8; 32];
code_offset[31] = 160;
let mut code_length = [0u8; 32];
let code_len = code.len() as u64;
code_length[24..32].copy_from_slice(&code_len.to_be_bytes());
let mut salt_offset = [0u8; 32];
salt_offset[24..32].copy_from_slice(&(code_len+160).to_be_bytes());
let mut salt_length = [0u8; 32];
salt_length[31] = 32;
let salt = [42u8; 32];

let mut input = Vec::new();
input.extend_from_slice(&value);
input.extend_from_slice(&code_offset);
input.extend_from_slice(&code_length);
input.extend_from_slice(&salt_offset);
input.extend_from_slice(&salt_length);
input.extend_from_slice(&code);
input.extend_from_slice(&salt);


let deployer = <Test as Config>::AddressMapper::to_address(&ALICE);
let contract_address_expected = create2(&deployer, code.as_slice(), &[], &salt);

println!("contract_address_expected: {:?}", contract_address_expected.as_bytes());

ExtBuilder::default().existential_deposit(1).build().execute_with(|| {
let _ = <Test as Config>::Currency::set_balance(&ALICE, 1_000_000);

assert_ok!(Contracts::upload_code(
RuntimeOrigin::signed(ALICE),
code.clone(),
deposit_limit::<Test>(),
));

// Call the precompile directly
let result = builder::bare_call(create2_precompile_addr)
.data(input.clone())
.build();
println!("result: {result:?}");

let result_exec = result.result.clone();
assert_ok!(result_exec);
let result_result = result.result.clone().unwrap();

let contract_address = &result_result.data[12..];
assert_eq!(contract_address.len(), contract_address_expected.as_bytes().len());
assert_eq!(contract_address, contract_address_expected.as_bytes());

assert!(!result_result.did_revert());

// TODO: check if result it OK
// TODO: output contains the expected address
// TODO: check if contract deployed at the right address
});
}

#[test]
fn calling_plain_account_is_balance_transfer() {
ExtBuilder::default().build().execute_with(|| {
Expand Down Expand Up @@ -566,6 +641,7 @@ fn create1_address_from_extrinsic() {
});
}


#[test]
fn deposit_event_max_value_limit() {
let (wasm, _code_hash) = compile_module("event_size").unwrap();
Expand Down
Loading