mirror of
https://github.com/pezkuwichain/pezkuwi-runtime-templates.git
synced 2026-04-21 23:47:56 +00:00
* Added a possibility to predeploy contracts to the genesis block * Added some default contracts to predeploy.
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
** xref:guides/async_backing.adoc[Async Backing]
|
||||
* EVM Template Guides
|
||||
** xref:guides/contract_migration.adoc[Contract Migration]
|
||||
** xref:guides/predeployed_contracts.adoc[Predeployed Contracts]
|
||||
* Runtimes
|
||||
** xref:runtimes/generic.adoc[Generic Runtime]
|
||||
* Runtime Descriptions
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
:source-highlighter: highlight.js
|
||||
:highlightjs-languages: rust
|
||||
:github-icon: pass:[<svg class="icon"><use href="#github-icon"/></svg>]
|
||||
|
||||
= Predeployed Contracts:
|
||||
|
||||
To enable some bleeding-edge features that EVM provides (like pay gas with any tokens), we have developed an ability to deploy contracts in genesis config. Initially it was done to deploy Entrypoint contract only, but we have supported this functionality for any contract with some limitations.
|
||||
|
||||
== How to use:
|
||||
|
||||
=== Step 1: Compiling your contracts
|
||||
|
||||
Currently we are supporting contracts that are compiled with Forge or Hardhat. If you want any other tool to be supported, consider creating an issue and attach and example of compiled contract.
|
||||
|
||||
Here are compilation guides:
|
||||
|
||||
* link:https://hardhat.org/hardhat-runner/docs/guides/compile-contracts[Hardhat]
|
||||
* link:https://book.getfoundry.sh/reference/forge/forge-build[Forge]
|
||||
|
||||
When you have compiled the contracts, take the resulting JSON for your contract and save into a single directory for the future deployment.
|
||||
|
||||
=== Step 2: Creating the configuration
|
||||
|
||||
In the directory where you have saved the contracts, create a file name "contracts.json". In this file you should store an array of contract metadata, that contains filename of the artifact and the address where you want this contract to be deployed to. So, it should like like this:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"address": "0x81ead4918134AE386dbd04346216E20AB8F822C4",
|
||||
"filename": "Entrypoint.json"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
You can take as an example a file in our EVM template in path `contracts/contracts.json`.
|
||||
|
||||
=== Step 3: Building the chainspec
|
||||
|
||||
During the step when you generate a chainspec pass the parameter `--predeployed-contracts` with a path to the directory where you have stored the contract artifacts and the configuration:
|
||||
|
||||
```bash
|
||||
./target/release/parachain-template-node build-spec --disable-default-bootnode --predeployed-contracts=<path_to_dir> > plain-parachain-chainspec.json
|
||||
```
|
||||
|
||||
== Exclude any contracts from genesis config
|
||||
|
||||
If you do not want any contract to be predeployed, you can use the `--no-predeployed-contracts` option when you are generating a plain chainspec. With this flag set you will receive a pristine chainspec without any additional smartcontracts deployed.
|
||||
|
||||
== Limitations
|
||||
|
||||
* Contructors are not executed at the moment. So if your contract needs any initialization, consider deploying it as usual.
|
||||
Generated
+2
@@ -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",
|
||||
|
||||
@@ -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
@@ -0,0 +1,6 @@
|
||||
[
|
||||
{
|
||||
"address": "0x81ead4918134AE386dbd04346216E20AB8F822C4",
|
||||
"filename": "Entrypoint.json"
|
||||
}
|
||||
]
|
||||
@@ -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
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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</>
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
@@ -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>,
|
||||
}
|
||||
@@ -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()))
|
||||
}
|
||||
@@ -7,6 +7,7 @@ mod chain_spec;
|
||||
mod service;
|
||||
mod cli;
|
||||
mod command;
|
||||
mod contracts;
|
||||
mod eth;
|
||||
mod rpc;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user