Skip to content

Commit

Permalink
Customize origin for contract deployment (#1)
Browse files Browse the repository at this point in the history
* add support for contract deploy filter

* add tests to pallet-ethereum

* point to moondance evm fork

* improve comments

* fix comment in mock

* add error comment

* update evm branch
  • Loading branch information
Agusrodri authored and tmpolaczyk committed Aug 6, 2024
1 parent 17d110f commit 002ef30
Show file tree
Hide file tree
Showing 12 changed files with 459 additions and 22 deletions.
14 changes: 5 additions & 9 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ derive_more = "0.99"
environmental = { version = "1.1.4", default-features = false }
ethereum = { version = "0.15.0", default-features = false }
ethereum-types = { version = "0.14.1", default-features = false }
evm = { version = "0.41.1", default-features = false }
evm = { git = "https://github.com/moondance-labs/evm", branch = "release-v041", default-features = false }
futures = "0.3.30"
hash-db = { version = "0.16.0", default-features = false }
hex = { version = "0.4.3", default-features = false, features = ["alloc"] }
Expand Down
8 changes: 7 additions & 1 deletion frame/ethereum/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

//! Test utilities

use core::str::FromStr;
use ethereum::{TransactionAction, TransactionSignature};
use rlp::RlpStream;
// Substrate
Expand All @@ -32,7 +33,7 @@ use sp_runtime::{
AccountId32, BuildStorage,
};
// Frontier
use pallet_evm::{AddressMapping, EnsureAddressTruncated, FeeCalculator};
use pallet_evm::{AddressMapping, EnsureAddressTruncated, EnsureAllowedCreateAddress, FeeCalculator};

use super::*;
use crate::IntermediateStateRoot;
Expand Down Expand Up @@ -156,6 +157,9 @@ impl AddressMapping<AccountId32> for HashedAddressMapping {

parameter_types! {
pub SuicideQuickClearLimit: u32 = 0;
// Alice is allowed to create contracts via CREATE and CALL(CREATE)
pub AllowedAddressesCreate: Vec<H160> = vec![H160::from_str("0x1a642f0e3c3af545e7acbd38b07251b3990914f1").expect("alice address")];
pub AllowedAddressesCreateInner: Vec<H160> = vec![H160::from_str("0x1a642f0e3c3af545e7acbd38b07251b3990914f1").expect("alice address")];
}

impl pallet_evm::Config for Test {
Expand All @@ -164,6 +168,8 @@ impl pallet_evm::Config for Test {
type WeightPerGas = WeightPerGas;
type BlockHashMapping = crate::EthereumBlockHashMapping<Self>;
type CallOrigin = EnsureAddressTruncated;
type CreateOrigin = EnsureAllowedCreateAddress<AllowedAddressesCreate>;
type CreateInnerOrigin = EnsureAllowedCreateAddress<AllowedAddressesCreateInner>;
type WithdrawOrigin = EnsureAddressTruncated;
type AddressMapping = HashedAddressMapping;
type Currency = Balances;
Expand Down
151 changes: 150 additions & 1 deletion frame/ethereum/src/tests/legacy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ use super::*;
use evm::{ExitReason, ExitRevert, ExitSucceed};
use fp_ethereum::{TransactionData, ValidatedTransaction};
use frame_support::{
dispatch::{DispatchClass, GetDispatchInfo},
dispatch::{DispatchClass, GetDispatchInfo, Pays, PostDispatchInfo},
weights::Weight,
};
use pallet_evm::AddressMapping;
use sp_runtime::{DispatchError, DispatchErrorWithPostInfo, ModuleError};

fn legacy_erc20_creation_unsigned_transaction() -> LegacyUnsignedTransaction {
LegacyUnsignedTransaction {
Expand All @@ -41,6 +42,21 @@ fn legacy_erc20_creation_transaction(account: &AccountInfo) -> Transaction {
legacy_erc20_creation_unsigned_transaction().sign(&account.private_key)
}

fn legacy_foo_bar_contract_creation_unsigned_transaction() -> LegacyUnsignedTransaction {
LegacyUnsignedTransaction {
nonce: U256::zero(),
gas_price: U256::from(1),
gas_limit: U256::from(0x100000),
action: ethereum::TransactionAction::Create,
value: U256::zero(),
input: hex::decode(FOO_BAR_CONTRACT_CREATOR_BYTECODE.trim_end()).unwrap(),
}
}

fn legacy_foo_bar_contract_creation_transaction(account: &AccountInfo) -> Transaction {
legacy_foo_bar_contract_creation_unsigned_transaction().sign(&account.private_key)
}

#[test]
fn transaction_should_increment_nonce() {
let (pairs, mut ext) = new_test_ext(1);
Expand Down Expand Up @@ -273,6 +289,139 @@ fn transaction_should_generate_correct_gas_used() {
});
}

#[test]
fn contract_creation_succeeds_with_allowed_address() {
let (pairs, mut ext) = new_test_ext(1);
let alice = &pairs[0];

ext.execute_with(|| {
// Alice can deploy contracts
let t = LegacyUnsignedTransaction {
nonce: U256::zero(),
gas_price: U256::from(1),
gas_limit: U256::from(0x100000),
action: ethereum::TransactionAction::Create,
value: U256::zero(),
input: hex::decode(TEST_CONTRACT_CODE).unwrap(),
}
.sign(&alice.private_key);
assert_ok!(Ethereum::execute(alice.address, &t, None,));
});
}

#[test]
fn contract_creation_fails_with_not_allowed_address() {
let (pairs, mut ext) = new_test_ext(2);
let bob = &pairs[1];

ext.execute_with(|| {
// Bob can't deploy contracts
let t = LegacyUnsignedTransaction {
nonce: U256::zero(),
gas_price: U256::from(1),
gas_limit: U256::from(0x100000),
action: ethereum::TransactionAction::Create,
value: U256::zero(),
input: hex::decode(TEST_CONTRACT_CODE).unwrap(),
}
.sign(&bob.private_key);

let result = Ethereum::execute(bob.address, &t, None,);
assert!(result.is_err());

// Note: assert_err! macro doesn't work here because we receive 'None' as
// 'actual_weight' using assert_err instead of Some(Weight::default()),
// but the error is the same "CreateOriginNotAllowed".
assert_eq!(result, Err(DispatchErrorWithPostInfo {
post_info: PostDispatchInfo{
actual_weight: Some(Weight::default()),
pays_fee: Pays::Yes
},
error: DispatchError::Module(ModuleError{
index: 3,
error: [13,0,0,0],
message: Some("CreateOriginNotAllowed")
})
}));
});
}

#[test]
fn inner_contract_creation_succeeds_with_allowed_address() {
let (pairs, mut ext) = new_test_ext(1);
let alice = &pairs[0];

ext.execute_with(|| {
let t = legacy_foo_bar_contract_creation_transaction(alice);
assert_ok!(Ethereum::execute(alice.address, &t, None,));

let contract_address = hex::decode("32dcab0ef3fb2de2fce1d2e0799d36239671f04a").unwrap();
let new_bar = hex::decode("2fc11060").unwrap();

// Alice is allowed to deploy contracts via inner calls.
let new_bar_inner_creation_tx = LegacyUnsignedTransaction {
nonce: U256::from(1),
gas_price: U256::from(1),
gas_limit: U256::from(0x100000),
action: TransactionAction::Call(H160::from_slice(&contract_address)),
value: U256::zero(),
input: new_bar,
}
.sign(&alice.private_key);

let (_, _, info) = Ethereum::execute(alice.address, &new_bar_inner_creation_tx, None).unwrap();

assert!(Ethereum::execute(alice.address, &new_bar_inner_creation_tx, None).is_ok());
match info {
CallOrCreateInfo::Call(info) => {
assert_eq!(
info.exit_reason,
ExitReason::Succeed(ExitSucceed::Returned)
);
}
CallOrCreateInfo::Create(_) => panic!("expected call info"),
}
});
}

#[test]
fn inner_contract_creation_reverts_with_not_allowed_address() {
let (pairs, mut ext) = new_test_ext(2);
let alice = &pairs[0];
let bob = &pairs[1];

ext.execute_with(|| {
let t = legacy_foo_bar_contract_creation_transaction(alice);
assert_ok!(Ethereum::execute(alice.address, &t, None,));

let contract_address = hex::decode("32dcab0ef3fb2de2fce1d2e0799d36239671f04a").unwrap();
let new_bar = hex::decode("2fc11060").unwrap();

// Bob is not allowed to deploy contracts via inner calls.
let new_bar_inner_creation_tx = LegacyUnsignedTransaction {
nonce: U256::from(1),
gas_price: U256::from(1),
gas_limit: U256::from(0x100000),
action: TransactionAction::Call(H160::from_slice(&contract_address)),
value: U256::zero(),
input: new_bar,
}
.sign(&bob.private_key);

let (_, _, info) = Ethereum::execute(bob.address, &new_bar_inner_creation_tx, None).unwrap();

match info {
CallOrCreateInfo::Call(info) => {
assert_eq!(
info.exit_reason,
ExitReason::Revert(ExitRevert::Reverted)
);
}
CallOrCreateInfo::Create(_) => panic!("expected call info"),
}
});
}

#[test]
fn call_should_handle_errors() {
let (pairs, mut ext) = new_test_ext(1);
Expand Down
23 changes: 23 additions & 0 deletions frame/ethereum/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,26 @@ pub const ERC20_CONTRACT_BYTECODE: &str = include_str!("./res/erc20_contract_byt
// }
// }
pub const TEST_CONTRACT_CODE: &str = "608060405234801561001057600080fd5b50610129806100206000396000f3fe6080604052348015600f57600080fd5b506004361060325760003560e01c8063c2985578146037578063febb0f7e146055575b600080fd5b603d605d565b60405180821515815260200191505060405180910390f35b605b6066565b005b60006001905090565b600060bc576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260358152602001806100bf6035913960400191505060405180910390fd5b56fe766572795f6c6f6e675f6572726f725f6d73675f746861745f77655f6578706563745f746f5f62655f7472696d6d65645f61776179a26469706673582212207af96dd688d3a3adc999c619e6073d5b6056c72c79ace04a90ea4835a77d179364736f6c634300060c0033";

// pragma solidity ^0.8.2;
// contract Foo {
// function newBar() // 2fc11060
// public
// returns(Bar newContract)
// {
// Bar b = new Bar();
// return b;
// }
//}

// contract Bar {
// function getNumber()
// public
// pure
// returns (uint32 number)
// {
// return 10;
// }
//}
pub const FOO_BAR_CONTRACT_CREATOR_BYTECODE: &str =
include_str!("./res/foo_bar_contract_creator.txt");
1 change: 1 addition & 0 deletions frame/ethereum/src/tests/res/foo_bar_contract_creator.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
6080604052348015600e575f80fd5b506102208061001c5f395ff3fe608060405234801561000f575f80fd5b5060043610610029575f3560e01c80632fc110601461002d575b5f80fd5b61003561004b565b6040516100429190610102565b60405180910390f35b5f806040516100599061007c565b604051809103905ff080158015610072573d5f803e3d5ffd5b5090508091505090565b60cf8061011c83390190565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f819050919050565b5f6100ca6100c56100c084610088565b6100a7565b610088565b9050919050565b5f6100db826100b0565b9050919050565b5f6100ec826100d1565b9050919050565b6100fc816100e2565b82525050565b5f6020820190506101155f8301846100f3565b9291505056fe6080604052348015600e575f80fd5b5060b580601a5f395ff3fe6080604052348015600e575f80fd5b50600436106026575f3560e01c8063f2c9ecd814602a575b5f80fd5b60306044565b604051603b91906068565b60405180910390f35b5f600a905090565b5f63ffffffff82169050919050565b606281604c565b82525050565b5f60208201905060795f830184605b565b9291505056fea2646970667358221220d061ca6930ff12673467107984883595e5f7ee04e63392aed46124a4bd3c4b4f64736f6c63430008190033a2646970667358221220f849f845d87941120b198faa30df2975ad373cbacfb60c64a7f2c0a2a36b502564736f6c63430008190033
50 changes: 45 additions & 5 deletions frame/evm/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ pub mod runner;
mod tests;
pub mod weights;

use alloc::{collections::btree_map::BTreeMap, vec::Vec};
use alloc::{borrow::Cow, collections::btree_map::BTreeMap, vec::Vec};
use core::cmp::min;
pub use evm::{
Config as EvmConfig, Context, ExitError, ExitFatal, ExitReason, ExitRevert, ExitSucceed,
Expand Down Expand Up @@ -139,6 +139,13 @@ pub mod pallet {

/// Allow the origin to call on behalf of given address.
type CallOrigin: EnsureAddressOrigin<Self::RuntimeOrigin>;

/// Allow the source address to deploy contracts directly via CREATE calls.
type CreateOrigin: EnsureCreateOrigin<Self>;

/// Allow the source address to deploy contracts via CALL(CREATE) calls.
type CreateInnerOrigin: EnsureCreateOrigin<Self>;

/// Allow the origin to withdraw on behalf of given address.
type WithdrawOrigin: EnsureAddressOrigin<Self::RuntimeOrigin, Success = Self::AccountId>;

Expand Down Expand Up @@ -506,6 +513,8 @@ pub mod pallet {
TransactionMustComeFromEOA,
/// Undefined error.
Undefined,
/// Address not allowed to deploy contracts either via CREATE or CALL(CREATE).
CreateOriginNotAllowed,
}

impl<T> From<TransactionValidationError> for Error<T> {
Expand Down Expand Up @@ -559,7 +568,7 @@ pub mod pallet {
account.balance.unique_saturated_into(),
);

Pallet::<T>::create_account(*address, account.code.clone());
let _ = Pallet::<T>::create_account(*address, account.code.clone(), None);

for (index, value) in &account.storage {
<AccountStorages<T>>::insert(address, index, value);
Expand Down Expand Up @@ -719,6 +728,30 @@ where
}
}

pub trait EnsureCreateOrigin<T> {
fn check_create_origin(address: &H160) -> Result<(), Error<T>>;
}

pub struct EnsureAllowedCreateAddress<AddressGetter>(core::marker::PhantomData<AddressGetter>);

impl<AddressGetter, T: Config> EnsureCreateOrigin<T> for EnsureAllowedCreateAddress<AddressGetter>
where
AddressGetter: Get<Vec<H160>>,
{
fn check_create_origin(address: &H160) -> Result<(), Error<T>> {
if !AddressGetter::get().contains(address){
return Err(Error::<T>::CreateOriginNotAllowed);
}
Ok(())
}
}

impl<T> EnsureCreateOrigin<T> for () {
fn check_create_origin(_address: &H160) -> Result<(), Error<T>> {
Ok(())
}
}

/// Trait to be implemented for evm address mapping.
pub trait AddressMapping<A> {
fn into_account_id(address: H160) -> A;
Expand Down Expand Up @@ -850,17 +883,23 @@ impl<T: Config> Pallet<T> {
}

/// Create an account.
pub fn create_account(address: H160, code: Vec<u8>) {
pub fn create_account(address: H160, code: Vec<u8>, caller: Option<H160>) -> Result<(), ExitError>{
if let Some(caller_address) = caller {
T::CreateInnerOrigin::check_create_origin(&caller_address).map_err(|e| {
let error: &'static str = e.into();
ExitError::Other(Cow::Borrowed(error))
})?;
}
if <Suicided<T>>::contains_key(address) {
// This branch should never trigger, because when Suicided
// contains an address, then its nonce will be at least one,
// which causes CreateCollision error in EVM, but we add it
// here for safeguard.
return;
return Ok(());
}

if code.is_empty() {
return;
return Ok(());
}

if !<AccountCodes<T>>::contains_key(address) {
Expand All @@ -873,6 +912,7 @@ impl<T: Config> Pallet<T> {
<AccountCodesMetadata<T>>::insert(address, meta);

<AccountCodes<T>>::insert(address, code);
Ok(())
}

/// Get the account metadata (hash and size) from storage if it exists,
Expand Down
Loading

0 comments on commit 002ef30

Please sign in to comment.