#189 Predeployed Contracts functionality (#218)

* Added a possibility to predeploy contracts to the genesis block
* Added some default contracts to predeploy.
This commit is contained in:
Nikita Khateev
2024-06-12 09:35:05 +04:00
committed by GitHub
parent b080293cdb
commit 26c49797cb
16 changed files with 1390 additions and 26 deletions
+2
View File
@@ -7941,6 +7941,7 @@ dependencies = [
"frame-benchmarking",
"frame-benchmarking-cli",
"futures",
"hex",
"hex-literal",
"jsonrpsee",
"log",
@@ -7967,6 +7968,7 @@ dependencies = [
"sc-transaction-pool",
"sc-transaction-pool-api",
"serde",
"serde_derive",
"serde_json",
"sp-api",
"sp-block-builder",
+2
View File
@@ -13,6 +13,7 @@ repository = "https://github.com/OpenZeppelin/polkadot-runtime-template"
clap = { version = "4.5.3", features = [ "derive" ] }
color-print = "0.3.4"
futures = "0.3.28"
hex = "0.4.3"
hex-literal = "0.4.1"
jsonrpsee = { version = "0.22", features = [ "server" ] }
log = { version = "0.4.20", default-features = false }
@@ -22,6 +23,7 @@ parity-scale-codec = { version = "3.0.0", default-features = false, features = [
] }
scale-info = { version = "2.10.0", default-features = false }
serde = { version = "1.0.188", default-features = false }
serde_derive = { version = "1.0.188", default-features = false }
serde_json = "1.0.108"
smallvec = "1.11.0"
File diff suppressed because one or more lines are too long
+6
View File
@@ -0,0 +1,6 @@
[
{
"address": "0x81ead4918134AE386dbd04346216E20AB8F822C4",
"filename": "Entrypoint.json"
}
]
+2
View File
@@ -12,11 +12,13 @@ version = "0.1.2"
[dependencies]
clap = { workspace = true }
futures = { workspace = true }
hex = { workspace = true }
hex-literal = { workspace = true }
jsonrpsee = { workspace = true, features = [ "server" ] }
log = { workspace = true }
parity-scale-codec = { workspace = true }
serde = { workspace = true, features = [ "derive" ] }
serde_derive = { workspace = true }
serde_json = { workspace = true }
# Local
+42 -18
View File
@@ -3,6 +3,7 @@ use std::collections::BTreeMap;
use cumulus_primitives_core::ParaId;
use fp_evm::GenesisAccount;
use hex_literal::hex;
use log::error;
use parachain_template_runtime::{
constants::currency::EXISTENTIAL_DEPOSIT, AccountId, AuraId,
OpenZeppelinPrecompiles as Precompiles, Runtime, Signature,
@@ -13,6 +14,8 @@ use serde::{Deserialize, Serialize};
use sp_core::{ecdsa, Pair, Public, H160};
use sp_runtime::traits::{IdentifyAccount, Verify};
use crate::contracts::{parse_contracts, ContractsPath};
/// Specialized `ChainSpec` for the normal parachain runtime.
pub type ChainSpec =
sc_service::GenericChainSpec<parachain_template_runtime::RuntimeGenesisConfig, Extensions>;
@@ -70,7 +73,7 @@ pub fn template_session_keys(keys: AuraId) -> parachain_template_runtime::Sessio
parachain_template_runtime::SessionKeys { aura: keys }
}
pub fn development_config() -> ChainSpec {
pub fn development_config(contracts_path: ContractsPath) -> ChainSpec {
// Give your base currency a unit name and decimal places
let mut properties = sc_chain_spec::Properties::new();
properties.insert("tokenSymbol".into(), "UNIT".into());
@@ -118,11 +121,12 @@ pub fn development_config() -> ChainSpec {
],
get_account_id_from_seed::<ecdsa::Public>("Alice"),
1000.into(),
contracts_path,
))
.build()
}
pub fn local_testnet_config() -> ChainSpec {
pub fn local_testnet_config(contracts_path: ContractsPath) -> ChainSpec {
// Give your base currency a unit name and decimal places
let mut properties = sc_chain_spec::Properties::new();
properties.insert("tokenSymbol".into(), "UNIT".into());
@@ -167,6 +171,7 @@ pub fn local_testnet_config() -> ChainSpec {
],
get_account_id_from_seed::<ecdsa::Public>("Alice"),
1000.into(),
contracts_path,
))
.with_protocol_id("template-local")
.with_properties(properties)
@@ -178,7 +183,41 @@ fn testnet_genesis(
endowed_accounts: Vec<AccountId>,
root: AccountId,
id: ParaId,
contracts_path: ContractsPath,
) -> serde_json::Value {
let contracts = parse_contracts(contracts_path)
.map_err(|e| error!("Error while parsing contracts: {e:?}"))
.unwrap_or_default();
let precompiles = Precompiles::<Runtime>::used_addresses()
.map(|addr| {
(
addr,
GenesisAccount {
nonce: Default::default(),
balance: Default::default(),
storage: Default::default(),
// bytecode to revert without returning data
// (PUSH1 0x00 PUSH1 0x00 REVERT)
code: vec![0x60, 0x00, 0x60, 0x00, 0xFD],
},
)
})
.into_iter();
let accounts: BTreeMap<H160, GenesisAccount> = contracts
.into_iter()
.map(|(address, contract)| {
(
address,
GenesisAccount {
code: contract.bytecode(),
nonce: Default::default(),
balance: Default::default(),
storage: Default::default(),
},
)
})
.chain(precompiles)
.collect();
serde_json::json!({
"balances": {
"balances": endowed_accounts.iter().cloned().map(|k| (k, 1u64 << 60)).collect::<Vec<_>>(),
@@ -207,22 +246,7 @@ fn testnet_genesis(
"chainId": 9999
},
"evm": {
"accounts": Precompiles::<Runtime>::used_addresses()
.map(|addr| {
(
addr,
GenesisAccount {
nonce: Default::default(),
balance: Default::default(),
storage: Default::default(),
// bytecode to revert without returning data
// (PUSH1 0x00 PUSH1 0x00 REVERT)
code: vec![0x60, 0x00, 0x60, 0x00, 0xFD],
},
)
})
.into_iter()
.collect::<BTreeMap<H160, GenesisAccount>>(),
"accounts": accounts
},
"polkadotXcm": {
"safeXcmVersion": Some(SAFE_XCM_VERSION),
+47 -2
View File
@@ -1,12 +1,15 @@
use std::path::PathBuf;
use crate::eth::EthConfiguration;
use sc_cli::{ChainSpec, Result};
use sc_network::config::NetworkConfiguration;
use crate::{contracts::ContractsPath, eth::EthConfiguration};
/// Sub-commands supported by the collator.
#[derive(Debug, clap::Subcommand)]
pub enum Subcommand {
/// Build a chain specification.
BuildSpec(sc_cli::BuildSpecCmd),
BuildSpec(ExtendedBuildSpecCmd),
/// Validate blocks.
CheckBlock(sc_cli::CheckBlockCmd),
@@ -47,6 +50,48 @@ pub enum Subcommand {
TryRuntime,
}
impl Subcommand {
pub fn contract_directory(&self) -> ContractsPath {
match self {
Self::BuildSpec(cmd) =>
match (cmd.no_predeployed_contracts, cmd.predeployed_contracts.clone()) {
(true, _) => ContractsPath::None,
(false, None) => ContractsPath::Default,
(false, Some(path)) => ContractsPath::Some(path),
},
_ => ContractsPath::None,
}
}
}
#[derive(Debug, Clone, clap::Parser)]
pub struct ExtendedBuildSpecCmd {
#[clap(flatten)]
pub cmd: sc_cli::BuildSpecCmd,
/// Path to a directory that contains precompiled contracts.
/// A directory should contain file `contracts.json`.
/// It should contain a JSON array of objects with "address" and "filename" files in it. See the example in `evm-template/contracts/contracts.json`
/// If you don't specify any contract, only Entrypoint contract will be deployed at the address `0x81ead4918134AE386dbd04346216E20AB8F822C4`
#[arg(long, default_value = None)]
pub predeployed_contracts: Option<String>,
/// Set this to true to if you do not want any contracts to be deployed
#[arg(long, default_value = "false")]
pub no_predeployed_contracts: bool,
}
impl ExtendedBuildSpecCmd {
/// Run the build-spec command
pub fn run(
&self,
spec: Box<dyn ChainSpec>,
network_config: NetworkConfiguration,
) -> Result<()> {
self.cmd.run(spec, network_config)
}
}
const AFTER_HELP_EXAMPLE: &str = color_print::cstr!(
r#"<bold><underline>Examples:</></>
<bold>parachain-template-node build-spec --disable-default-bootnode > plain-parachain-chainspec.json</>
+23 -6
View File
@@ -14,15 +14,19 @@ use sp_runtime::traits::AccountIdConversion;
use crate::{
chain_spec,
cli::{Cli, RelayChainCli, Subcommand},
cli::{Cli, ExtendedBuildSpecCmd, RelayChainCli, Subcommand},
contracts::ContractsPath,
service::new_partial,
};
fn load_spec(id: &str) -> std::result::Result<Box<dyn ChainSpec>, String> {
fn load_spec(
id: &str,
contracts_path: ContractsPath,
) -> std::result::Result<Box<dyn ChainSpec>, String> {
Ok(match id {
"dev" => Box::new(chain_spec::development_config()),
"template-rococo" => Box::new(chain_spec::local_testnet_config()),
"" | "local" => Box::new(chain_spec::local_testnet_config()),
"dev" => Box::new(chain_spec::development_config(contracts_path)),
"template-rococo" => Box::new(chain_spec::local_testnet_config(contracts_path)),
"" | "local" => Box::new(chain_spec::local_testnet_config(contracts_path)),
path => Box::new(chain_spec::ChainSpec::from_json_file(std::path::PathBuf::from(path))?),
})
}
@@ -58,7 +62,10 @@ impl SubstrateCli for Cli {
}
fn load_spec(&self, id: &str) -> std::result::Result<Box<dyn sc_service::ChainSpec>, String> {
load_spec(id)
load_spec(
id,
self.subcommand.as_ref().map(Subcommand::contract_directory).unwrap_or_default(),
)
}
}
@@ -389,3 +396,13 @@ impl CliConfiguration<Self> for RelayChainCli {
self.base.base.node_name()
}
}
impl CliConfiguration for ExtendedBuildSpecCmd {
fn shared_params(&self) -> &SharedParams {
self.cmd.shared_params()
}
fn node_key_params(&self) -> Option<&sc_cli::NodeKeyParams> {
self.cmd.node_key_params()
}
}
+64
View File
@@ -0,0 +1,64 @@
/// This part was easy to implement and may be useful in future.
use serde::Deserialize;
#[derive(Deserialize)]
#[serde(tag = "type")]
#[serde(rename_all = "lowercase")]
pub enum AbiItem {
Error(Error),
Function(Function),
Constructor,
Event(Event),
Receive(Receive),
Default(Function),
}
#[derive(Deserialize)]
#[allow(dead_code)]
pub struct Error {
name: String,
inputs: Vec<Input>,
}
#[derive(Deserialize)]
#[allow(dead_code)]
pub struct Receive {
#[serde(rename = "stateMutability")]
state_mutability: StateMutability,
}
#[derive(Deserialize)]
#[allow(dead_code)]
pub struct Event {
name: String,
inputs: Vec<Input>,
anonymous: bool,
}
#[derive(Deserialize)]
#[allow(dead_code)]
pub struct Function {
name: String,
inputs: Vec<Input>,
outputs: Vec<Input>,
#[serde(rename = "stateMutability")]
state_mutability: StateMutability,
}
#[derive(Deserialize)]
#[allow(dead_code)]
pub struct Input {
name: String,
r#type: String,
components: Option<Vec<Input>>,
indexed: Option<bool>,
}
#[derive(Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum StateMutability {
Pure,
View,
Nonpayable,
Payable,
}
File diff suppressed because one or more lines are too long
+15
View File
@@ -0,0 +1,15 @@
use serde::Deserialize;
use super::{abi::AbiItem, deserialize_bytecode};
#[derive(Deserialize)]
pub struct ForgeContract {
pub abi: Vec<AbiItem>,
pub bytecode: ForgeBytecode,
}
#[derive(Deserialize)]
pub struct ForgeBytecode {
#[serde(deserialize_with = "deserialize_bytecode")]
pub object: Vec<u8>,
}
@@ -0,0 +1,10 @@
use serde::Deserialize;
use super::{abi::AbiItem, deserialize_bytecode};
#[derive(Deserialize)]
pub struct HardhatContract {
pub abi: Vec<AbiItem>,
#[serde(deserialize_with = "deserialize_bytecode")]
pub bytecode: Vec<u8>,
}
+102
View File
@@ -0,0 +1,102 @@
use std::{
collections::HashMap,
fs::File,
io::{self, BufReader},
path::Path,
};
use serde::{de, Deserialize};
use sp_core::H160;
use self::{
defaults::{entrypoint_address, entrypoint_contract},
forge::ForgeContract,
hardhat::HardhatContract,
};
mod abi;
mod defaults;
mod forge;
mod hardhat;
#[derive(Deserialize)]
pub struct ContractMetadata {
pub address: H160,
pub filename: String,
}
#[derive(Deserialize)]
#[serde(untagged)]
pub enum Contract {
Hardhat(HardhatContract),
Forge(ForgeContract),
}
impl Contract {
pub fn bytecode(self) -> Vec<u8> {
match self {
Self::Hardhat(c) => c.bytecode,
Self::Forge(c) => c.bytecode.object,
}
}
}
#[derive(Debug)]
pub enum ContractParsingError {
MetadataReadError(io::Error),
MetadataParseError(serde_json::Error),
}
#[derive(Default)]
pub enum ContractsPath {
None,
#[default]
Default,
Some(String),
}
pub fn parse_contracts(
path: ContractsPath,
) -> Result<HashMap<H160, Contract>, ContractParsingError> {
let mut res = HashMap::new();
let path = match path {
ContractsPath::None => return Ok(res),
ContractsPath::Default => {
res.insert(entrypoint_address(), entrypoint_contract());
return Ok(res);
}
ContractsPath::Some(path) => path,
};
let path = Path::new(&path);
let metadata_path = path.join(Path::new("contracts.json"));
let metadata_file =
File::open(metadata_path.as_path()).map_err(ContractParsingError::MetadataReadError)?;
let metadata_reader = BufReader::new(metadata_file);
let metadatas: Vec<ContractMetadata> = serde_json::from_reader(metadata_reader)
.map_err(ContractParsingError::MetadataParseError)?;
for metadata in metadatas {
let ContractMetadata { filename, address } = metadata;
let contract_path = path.join(Path::new(&filename));
let Ok(contract_file) = File::open(contract_path.as_path()).map_err(|e| {
println!("File {filename} can't be opened, skipping: {e:?}");
}) else {
continue;
};
let contract_reader = BufReader::new(contract_file);
let Ok(contract) = serde_json::from_reader(contract_reader).map_err(|e| {
println!("File {filename} can't be parsed, skipping: {e:?}");
}) else {
continue;
};
res.insert(address, contract);
}
Ok(res)
}
fn deserialize_bytecode<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
where
D: de::Deserializer<'de>,
{
let s: &str = de::Deserialize::deserialize(deserializer)?;
hex::decode(&s[2..]).map_err(|e| de::Error::custom(e.to_string()))
}
+1
View File
@@ -7,6 +7,7 @@ mod chain_spec;
mod service;
mod cli;
mod command;
mod contracts;
mod eth;
mod rpc;