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

feat: restricted contract deployment #766

Merged
merged 4 commits into from
Feb 21, 2024
Merged
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
6 changes: 3 additions & 3 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 integration-tests/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "runtime-integration-tests"
version = "1.18.0"
version = "1.18.1"
description = "Integration tests"
authors = ["GalacticCouncil"]
edition = "2021"
Expand Down
38 changes: 38 additions & 0 deletions integration-tests/src/call_filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -267,3 +267,41 @@ fn calling_orml_xcm_extrinsic_should_be_filtered_by_call_filter() {
assert!(!hydradx_runtime::CallFilter::contains(&call));
});
}

#[test]
fn create_contract_from_evm_pallet_should_be_filtered_by_call_filter() {
use sp_core::{H160, H256, U256};

TestNet::reset();

Hydra::execute_with(|| {
// the values here don't need to make sense, all we need is a valid Call
let call = hydradx_runtime::RuntimeCall::EVM(pallet_evm::Call::create {
source: H160::default(),
init: vec![0, 1, 1, 0],
value: U256::zero(),
gas_limit: 1000000,
max_fee_per_gas: U256::from(100000u64),
max_priority_fee_per_gas: None,
nonce: None,
access_list: [].into(),
});

assert!(!hydradx_runtime::CallFilter::contains(&call));

// the values here don't need to make sense, all we need is a valid Call
let call = hydradx_runtime::RuntimeCall::EVM(pallet_evm::Call::create2 {
source: H160::default(),
init: vec![0, 1, 1, 0],
salt: H256::zero(),
value: U256::zero(),
gas_limit: 1000000,
max_fee_per_gas: U256::from(100000u64),
max_priority_fee_per_gas: None,
nonce: None,
access_list: [].into(),
});

assert!(!hydradx_runtime::CallFilter::contains(&call));
});
}
53 changes: 53 additions & 0 deletions integration-tests/src/evm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -985,6 +985,59 @@ mod currency_precompile {
}
}

mod contract_deployment {
use super::*;
use frame_support::assert_noop;
use pretty_assertions::assert_eq;

#[test]
fn create_contract_from_runtime_rpc_should_be_rejected_if_address_is_not_whitelisted() {
TestNet::reset();

Hydra::execute_with(|| {
assert_noop!(
hydradx_runtime::Runtime::create(
evm_address(),
vec![0, 1, 1, 0],
U256::zero(),
U256::from(100000u64),
None,
None,
None,
false,
None,
),
pallet_evm_accounts::Error::<hydradx_runtime::Runtime>::AddressNotWhitelisted
);
});
}

#[test]
fn create_contract_from_runtime_rpc_should_be_accepted_if_address_is_whitelisted() {
TestNet::reset();

Hydra::execute_with(|| {
let evm_address = EVMAccounts::evm_address(&Into::<AccountId>::into(ALICE));
assert_ok!(EVMAccounts::add_contract_deployer(
hydradx_runtime::RuntimeOrigin::root(),
evm_address
));

assert_ok!(hydradx_runtime::Runtime::create(
evm_address,
vec![0, 1, 1, 0],
U256::zero(),
U256::from(100000u64),
None,
None,
None,
false,
None,
));
});
}
}

#[test]
fn dispatch_should_work_with_remark() {
TestNet::reset();
Expand Down
2 changes: 1 addition & 1 deletion pallets/evm-accounts/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "pallet-evm-accounts"
version = "1.0.0"
version = "1.1.0"
authors = ['GalacticCouncil']
edition = "2021"
license = "Apache-2.0"
Expand Down
13 changes: 11 additions & 2 deletions pallets/evm-accounts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@

## Overview

The pallet allows users to bind their Substrate account to the EVM address.
The pallet allows users to bind their Substrate account to the EVM address and to grant a permission to deploy smart contracts.
The purpose of this pallet is to make interaction with the EVM easier.
Binding an address is not necessary for interacting with the EVM.

### Binding
Without binding, we are unable to get the original Substrate address from the EVM address inside
of the EVM. Inside of the EVM, we have access only to the EVM address (first 20 bytes of a Substrate account).
In this case we create and use a truncated version of the original Substrate address that called the EVM.
Expand All @@ -20,6 +21,14 @@ The original and truncated address are two different Substrate addresses.
With binding, we store the last 12 bytes of the Substrate address. Then we can get the original
Substrate address by concatenating these 12 bytes stored in the storage to the EVM address.

### Smart contract deployment
This pallet also allows granting a permission to deploy smart contracts.
`ControllerOrigin` can add this permission to EVM addresses.
The list of whitelisted accounts is stored in the storage of this pallet.

### Dispatchable Functions

* `bind_evm_address` - Binds a Substrate address to EVM address.
* `bind_evm_address` - Binds a Substrate address to EVM address.
* `add_contract_deployer` - Adds a permission to deploy smart contracts.
* `remove_contract_deployer` - Removes a permission of whitelisted address to deploy smart contracts.
* `renounce_contract_deployer` - Renounce caller's permission to deploy smart contracts.
41 changes: 39 additions & 2 deletions pallets/evm-accounts/src/benchmarking.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (C) 2020-2022 Intergalactic, Limited (GIB).
// Copyright (C) 2020-2024 Intergalactic, Limited (GIB).
// SPDX-License-Identifier: Apache-2.0

// Licensed under the Apache License, Version 2.0 (the "License");
Expand All @@ -16,6 +16,7 @@
#![cfg(feature = "runtime-benchmarks")]

use super::*;
use crate::Pallet as EVMAccounts;

use frame_benchmarking::{account, benchmarks};
use frame_system::RawOrigin;
Expand All @@ -33,9 +34,45 @@ benchmarks! {

}: _(RawOrigin::Signed(user.clone()))
verify {
let evm_address = Pallet::<T>::evm_address(&user);
assert!(AccountExtension::<T>::contains_key(evm_address));
}

add_contract_deployer {
let user: T::AccountId = account("user", 0, 1);
let evm_address = Pallet::<T>::evm_address(&user);
assert!(!ContractDeployer::<T>::contains_key(evm_address));

}: _(RawOrigin::Root, evm_address)
verify {
assert!(ContractDeployer::<T>::contains_key(evm_address));
}

remove_contract_deployer {
let user: T::AccountId = account("user", 0, 1);
let evm_address = Pallet::<T>::evm_address(&user);

EVMAccounts::<T>::add_contract_deployer(RawOrigin::Root.into(), evm_address)?;

assert!(ContractDeployer::<T>::contains_key(evm_address));

}: _(RawOrigin::Root, evm_address)
verify {
assert!(!ContractDeployer::<T>::contains_key(evm_address));
}

renounce_contract_deployer {
let user: T::AccountId = account("user", 0, 1);
let evm_address = Pallet::<T>::evm_address(&user);

EVMAccounts::<T>::add_contract_deployer(RawOrigin::Root.into(), evm_address)?;
EVMAccounts::<T>::bind_evm_address(RawOrigin::Signed(user.clone()).into())?;

assert!(ContractDeployer::<T>::contains_key(evm_address));

}: _(RawOrigin::Signed(user))
verify {
assert!(!ContractDeployer::<T>::contains_key(evm_address));
}

impl_benchmark_test_suite!(Pallet, crate::mock::ExtBuilder::default().build(), crate::mock::Test);
}
86 changes: 85 additions & 1 deletion pallets/evm-accounts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,11 @@
//!
//! ## Overview
//!
//! The pallet allows users to bind their Substrate account to the EVM address.
//! The pallet allows users to bind their Substrate account to the EVM address and to grant a permission to deploy smart contracts.
//! The purpose of this pallet is to make interaction with the EVM easier.
//! Binding an address is not necessary for interacting with the EVM.
//!
//! ### Binding
//! Without binding, we are unable to get the original Substrate address from the EVM address inside
//! of the EVM. Inside of the EVM, we have access only to the EVM address (first 20 bytes of a Substrate account).
//! In this case we create and use a truncated version of the original Substrate address that called the EVM.
Expand All @@ -35,9 +36,17 @@
//! With binding, we store the last 12 bytes of the Substrate address. Then we can get the original
//! Substrate address by concatenating these 12 bytes stored in the storage to the EVM address.
//!
//! ### Smart contract deployment
//! This pallet also allows granting a permission to deploy smart contracts.
//! `ControllerOrigin` can add this permission to EVM addresses.
//! The list of whitelisted accounts is stored in the storage of this pallet.
//!
//! ### Dispatchable Functions
//!
//! * `bind_evm_address` - Binds a Substrate address to EVM address.
//! * `add_contract_deployer` - Adds a permission to deploy smart contracts.
//! * `remove_contract_deployer` - Removes a permission of whitelisted address to deploy smart contracts.
//! * `renounce_contract_deployer` - Renounce caller's permission to deploy smart contracts.

#![cfg_attr(not(feature = "std"), no_std)]

Expand Down Expand Up @@ -88,6 +97,9 @@ pub mod pallet {
#[pallet::constant]
type FeeMultiplier: Get<u32>;

/// Origin that can whitelist addresses for smart contract deployment.
type ControllerOrigin: EnsureOrigin<Self::RuntimeOrigin>;

/// Weight information for extrinsic in this pallet.
type WeightInfo: WeightInfo;
}
Expand All @@ -97,11 +109,19 @@ pub mod pallet {
#[pallet::getter(fn account)]
pub(super) type AccountExtension<T: Config> = StorageMap<_, Blake2_128Concat, EvmAddress, AccountIdLast12Bytes>;

/// Whitelisted addresses that are allowed to deploy smart contracts.
#[pallet::storage]
pub(super) type ContractDeployer<T: Config> = StorageMap<_, Blake2_128Concat, EvmAddress, ()>;

#[pallet::event]
#[pallet::generate_deposit(pub(crate) fn deposit_event)]
pub enum Event<T: Config> {
/// Binding was created.
Bound { account: T::AccountId, address: EvmAddress },
/// Deployer was added.
DeployerAdded { who: EvmAddress },
/// Deployer was removed.
DeployerRemoved { who: EvmAddress },
}

#[pallet::error]
Expand All @@ -113,6 +133,8 @@ pub mod pallet {
AddressAlreadyBound,
/// Bound address cannot be used
BoundAddressCannotBeUsed,
/// Address not whitelisted
AddressNotWhitelisted,
}

#[pallet::hooks]
Expand Down Expand Up @@ -179,6 +201,64 @@ pub mod pallet {

Ok(())
}

/// Adds an EVM address to the list of addresses that are allowed to deploy smart contracts.
///
/// Parameters:
/// - `origin`: Substrate account whitelisting an address. Must be `ControllerOrigin`.
/// - `address`: EVM address that is whitelisted
///
/// Emits `DeployerAdded` event when successful.
#[pallet::call_index(1)]
#[pallet::weight(<T as Config>::WeightInfo::add_contract_deployer())]
pub fn add_contract_deployer(origin: OriginFor<T>, address: EvmAddress) -> DispatchResult {
T::ControllerOrigin::ensure_origin(origin.clone())?;

<ContractDeployer<T>>::insert(address, ());

Self::deposit_event(Event::DeployerAdded { who: address });

Ok(())
}

/// Removes an EVM address from the list of addresses that are allowed to deploy smart contracts.
///
/// Parameters:
/// - `origin`: Substrate account removing the EVM address from the whitelist. Must be `ControllerOrigin`.
/// - `address`: EVM address that is removed from the whitelist
///
/// Emits `DeployerRemoved` event when successful.
#[pallet::call_index(2)]
#[pallet::weight(<T as Config>::WeightInfo::remove_contract_deployer())]
pub fn remove_contract_deployer(origin: OriginFor<T>, address: EvmAddress) -> DispatchResult {
T::ControllerOrigin::ensure_origin(origin.clone())?;

<ContractDeployer<T>>::remove(address);

Self::deposit_event(Event::DeployerRemoved { who: address });

Ok(())
}

/// Removes the account's EVM address from the list of addresses that are allowed to deploy smart contracts.
/// Based on the best practices, this extrinsic can be called by any whitelisted account to renounce their own permission.
///
/// Parameters:
/// - `origin`: Substrate account removing their EVM address from the whitelist.
///
/// Emits `DeployerRemoved` event when successful.
#[pallet::call_index(3)]
#[pallet::weight(<T as Config>::WeightInfo::renounce_contract_deployer())]
pub fn renounce_contract_deployer(origin: OriginFor<T>) -> DispatchResult {
let who = ensure_signed(origin.clone())?;
let address = Self::evm_address(&who);

<ContractDeployer<T>>::remove(address);

Self::deposit_event(Event::DeployerRemoved { who: address });

Ok(())
}
}
}

Expand Down Expand Up @@ -216,4 +296,8 @@ where
pub fn account_id(evm_address: EvmAddress) -> T::AccountId {
Self::bound_account_id(evm_address).unwrap_or_else(|| Self::truncated_account_id(evm_address))
}

pub fn can_deploy_contracts(evm_address: EvmAddress) -> bool {
ContractDeployer::<T>::contains_key(evm_address)
}
}
2 changes: 2 additions & 0 deletions pallets/evm-accounts/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use frame_support::sp_runtime::{
BuildStorage, MultiSignature,
};
use frame_support::traits::Everything;
use frame_system::EnsureRoot;
use orml_traits::parameter_type_with_key;
pub use sp_core::{H160, H256};
use std::cell::RefCell;
Expand Down Expand Up @@ -59,6 +60,7 @@ impl Config for Test {
type RuntimeEvent = RuntimeEvent;
type FeeMultiplier = sp_core::ConstU32<10>;
type EvmNonceProvider = EvmNonceProviderMock;
type ControllerOrigin = EnsureRoot<AccountId>;
type WeightInfo = ();
}

Expand Down
Loading
Loading