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

Compare Environment types against the node #1377

Merged
merged 24 commits into from
Oct 25, 2023
Merged
Show file tree
Hide file tree
Changes from 4 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]

### Added
- Compare `Environment` types against the node - [#1377](https://github.com/paritytech/cargo-contract/pull/1377)
- Adds workflow for publishing docker images for the verifiable builds - [#1267](https://github.com/paritytech/cargo-contract/pull/1267)
- Detect `INK_STATIC_BUFFER_SIZE` env var - [#1310](https://github.com/paritytech/cargo-contract/pull/1310)
- Add `verify` command - [#1306](https://github.com/paritytech/cargo-contract/pull/1306)
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

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

11 changes: 9 additions & 2 deletions crates/extrinsics/src/call.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ use super::{
Missing,
TokenMetadata,
};
use crate::extrinsic_opts::ExtrinsicOpts;
use crate::{
check_env_types,
extrinsic_opts::ExtrinsicOpts,
};

use anyhow::{
anyhow,
Expand Down Expand Up @@ -234,6 +237,7 @@ impl CallExec {
/// Returns the dry run simulation result of type [`ContractExecResult`], which
/// includes information about the simulated call, or an error in case of failure.
pub async fn call_dry_run(&self) -> Result<ContractExecResult<Balance, ()>> {
check_env_types(self.client(), self.transcoder())?;
SkymanOne marked this conversation as resolved.
Show resolved Hide resolved
let storage_deposit_limit = self
.opts
.storage_deposit_limit()
Expand Down Expand Up @@ -265,7 +269,10 @@ impl CallExec {
) -> Result<DisplayEvents, ErrorVariant> {
// use user specified values where provided, otherwise estimate
let gas_limit = match gas_limit {
Some(gas_limit) => gas_limit,
Some(gas_limit) => {
check_env_types(self.client(), self.transcoder())?;
gas_limit
}
None => self.estimate_gas().await?,
};
tracing::debug!("calling contract {:?}", self.contract);
Expand Down
11 changes: 9 additions & 2 deletions crates/extrinsics/src/instantiate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ use super::{
StorageDeposit,
TokenMetadata,
};
use crate::extrinsic_opts::ExtrinsicOpts;
use crate::{
check_env_types,
extrinsic_opts::ExtrinsicOpts,
};
use anyhow::{
anyhow,
Context,
Expand Down Expand Up @@ -341,6 +344,7 @@ impl InstantiateExec {
) -> Result<
ContractInstantiateResult<<DefaultConfig as Config>::AccountId, Balance, ()>,
> {
check_env_types(self.client(), self.transcoder())?;
let storage_deposit_limit = self.args.storage_deposit_limit;
let call_request = InstantiateRequest {
origin: account_id(&self.signer),
Expand Down Expand Up @@ -434,7 +438,10 @@ impl InstantiateExec {
) -> Result<InstantiateExecResult, ErrorVariant> {
// use user specified values where provided, otherwise estimate
let gas_limit = match gas_limit {
Some(gas_limit) => gas_limit,
Some(gas_limit) => {
check_env_types(self.client(), self.transcoder())?;
gas_limit
}
None => self.estimate_gas().await?,
};
match self.args.code.clone() {
Expand Down
12 changes: 12 additions & 0 deletions crates/extrinsics/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ mod upload;
mod integration_tests;

use colored::Colorize;
use contract_transcode::compare_node_env_with_contract;
use subxt::utils::AccountId32;

use anyhow::{
Expand Down Expand Up @@ -370,6 +371,17 @@ pub async fn fetch_contract_info(
}
}

fn check_env_types<T>(
client: &OnlineClient<T>,
transcoder: &ContractMessageTranscoder,
) -> Result<()>
where
T: Config,
{
compare_node_env_with_contract(client.metadata().types(), transcoder)
.map_err(|e| anyhow!("Verification of types match failed: {:?}", e))
}

#[derive(serde::Serialize)]
pub struct ContractInfo {
trie_id: String,
Expand Down
Binary file not shown.
Binary file not shown.
2 changes: 1 addition & 1 deletion crates/extrinsics/src/runtime_api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
#![allow(clippy::too_many_arguments)]

#[subxt::subxt(
runtime_metadata_path = "src/runtime_api/contracts_runtime.scale",
runtime_metadata_path = "src/runtime_api/metadata.scale",
substitute_type(
path = "sp_weights::weight_v2::Weight",
with = "::subxt::utils::Static<::sp_weights::Weight>"
Expand Down
16 changes: 15 additions & 1 deletion crates/extrinsics/src/upload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,12 @@ use super::{
TokenMetadata,
WasmCode,
};
use crate::extrinsic_opts::ExtrinsicOpts;
use crate::{
check_env_types,
extrinsic_opts::ExtrinsicOpts,
};
use anyhow::Result;
use contract_transcode::ContractMessageTranscoder;
use core::marker::PhantomData;
use pallet_contracts_primitives::CodeUploadResult;
use scale::Encode;
Expand Down Expand Up @@ -99,6 +103,7 @@ impl UploadCommandBuilder<state::ExtrinsicOptions> {
/// execution.
pub async fn done(self) -> Result<UploadExec> {
let artifacts = self.opts.extrinsic_opts.contract_artifacts()?;
let transcoder = artifacts.contract_transcoder()?;
let signer = self.opts.extrinsic_opts.signer()?;

let artifacts_path = artifacts.artifact_path().to_path_buf();
Expand All @@ -123,6 +128,7 @@ impl UploadCommandBuilder<state::ExtrinsicOptions> {
code,
signer,
token_metadata,
transcoder,
})
}
}
Expand All @@ -134,6 +140,7 @@ pub struct UploadExec {
code: WasmCode,
signer: Keypair,
token_metadata: TokenMetadata,
transcoder: ContractMessageTranscoder,
}

impl UploadExec {
Expand All @@ -144,6 +151,7 @@ impl UploadExec {
/// then sends the request using the provided URL. This operation does not modify
/// the state of the blockchain.
pub async fn upload_code_rpc(&self) -> Result<CodeUploadResult<CodeHash, Balance>> {
check_env_types(self.client(), self.transcoder())?;
SkymanOne marked this conversation as resolved.
Show resolved Hide resolved
let storage_deposit_limit = self
.opts
.storage_deposit_limit()
Expand All @@ -166,6 +174,7 @@ impl UploadExec {
/// The function handles the necessary interactions with the blockchain's runtime
/// API to ensure the successful upload of the code.
pub async fn upload_code(&self) -> Result<UploadResult, ErrorVariant> {
check_env_types(self.client(), self.transcoder())?;
let storage_deposit_limit = self
.opts
.compact_storage_deposit_limit(&self.token_metadata)?;
Expand Down Expand Up @@ -211,6 +220,11 @@ impl UploadExec {
pub fn token_metadata(&self) -> &TokenMetadata {
&self.token_metadata
}

/// Returns the contract message transcoder.
pub fn transcoder(&self) -> &ContractMessageTranscoder {
&self.transcoder
}
}

/// A struct that encodes RPC parameters required for a call to upload a new code.
Expand Down
1 change: 1 addition & 0 deletions crates/transcode/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ serde = { version = "1.0.189", default-features = false, features = ["derive"] }
serde_json = "1.0.107"
thiserror = "1.0.50"
strsim = "0.10.0"
subxt = "0.32.1"
ascjones marked this conversation as resolved.
Show resolved Hide resolved

[dev-dependencies]
assert_matches = "1.5.0"
Expand Down
89 changes: 89 additions & 0 deletions crates/transcode/src/env_check.rs
ascjones marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
use scale_info::{
form::PortableForm,
Field,
PortableRegistry,
TypeDef,
};

use anyhow::{
Context,
Result,
};

use crate::ContractMessageTranscoder;

#[derive(Debug, Clone)]
pub enum EnvCheckError {
CheckFailed(String),
RegistryError(String),
}
ascjones marked this conversation as resolved.
Show resolved Hide resolved

impl From<anyhow::Error> for EnvCheckError {
fn from(value: anyhow::Error) -> Self {
Self::RegistryError(value.to_string())
}
}

fn get_node_env_fields(registry: &PortableRegistry) -> Result<Vec<Field<PortableForm>>> {
let env_type = registry
.types
.iter()
.find(|t| t.ty.path.segments == ["pallet_contracts", "Environment"])
.context("The node does not contain `Environment` type. Are you using correct `pallet-contracts` version?")?;

if let TypeDef::Composite(composite) = &env_type.ty.type_def {
Ok(composite.fields.clone())
} else {
anyhow::bail!("`Environment` type definition is in the wrong format");
}
}

pub fn resolve_type_definition(
ascjones marked this conversation as resolved.
Show resolved Hide resolved
registry: &PortableRegistry,
id: u32,
) -> Result<TypeDef<PortableForm>> {
let tt = registry
.resolve(id)
.context("Type is not present in registry")?;
if tt.type_params.is_empty() {
if let TypeDef::Composite(comp) = &tt.type_def {
println!("Resolve type definition: {:#?}", tt);
ascjones marked this conversation as resolved.
Show resolved Hide resolved
let tt_id = comp
.fields
.get(0)
ascjones marked this conversation as resolved.
Show resolved Hide resolved
.context("Incorrect format of a field")?
.ty
.id;
return resolve_type_definition(registry, tt_id)
}
Ok(tt.type_def.clone())
} else {
let param_id = tt
.type_params
.get(0)
.context("type param is not present")?
.ty
.context("concrete type is not present")?
.id;
resolve_type_definition(registry, param_id)
}
}

pub fn compare_node_env_with_contract(
ascjones marked this conversation as resolved.
Show resolved Hide resolved
ascjones marked this conversation as resolved.
Show resolved Hide resolved
registry: &PortableRegistry,
transcoder: &ContractMessageTranscoder,
) -> Result<(), EnvCheckError> {
let env_fields = get_node_env_fields(registry)?;
for field in env_fields {
let field_name = field.name.context("Field does not have a name")?;
if &field_name == "hasher" {
continue
}
let field_def = resolve_type_definition(registry, field.ty.id)?;
let checked = transcoder.compare_type(&field_name, field_def, registry)?;
if !checked {
return Err(EnvCheckError::CheckFailed(field_name))
}
}
Ok(())
}
40 changes: 40 additions & 0 deletions crates/transcode/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,13 +101,17 @@
mod account_id;
mod decode;
mod encode;
mod env_check;
pub mod env_types;
mod scon;
mod transcoder;
mod util;

use crate::env_check::resolve_type_definition;

pub use self::{
account_id::AccountId32,
env_check::compare_node_env_with_contract,
scon::{
Hex,
Map,
Expand Down Expand Up @@ -141,6 +145,8 @@ use scale_info::{
PortableForm,
},
Field,
PortableRegistry,
TypeDef,
};
use std::{
cmp::Ordering,
Expand Down Expand Up @@ -273,6 +279,40 @@ impl ContractMessageTranscoder {
fn messages(&self) -> impl Iterator<Item = &MessageSpec<PortableForm>> {
self.metadata.spec().messages().iter()
}
/// Compares the contract's environment type with a provided type definition.
pub fn compare_type(
ascjones marked this conversation as resolved.
Show resolved Hide resolved
&self,
type_name: &str,
type_def: TypeDef<PortableForm>,
node_registry: &PortableRegistry,
) -> Result<bool> {
let contract_registry = self.metadata.registry();
let tt_id = match type_name {
"account_id" => self.metadata.spec().environment().account_id().ty().id,
"balance" => self.metadata.spec().environment().balance().ty().id,
"hash" => self.metadata.spec().environment().hash().ty().id,
"timestamp" => self.metadata.spec().environment().timestamp().ty().id,
"block_number" => self.metadata.spec().environment().block_number().ty().id,
_ => anyhow::bail!("Trying to resolve unknown environment type"),
};
let tt_def = resolve_type_definition(contract_registry, tt_id)?;
if let TypeDef::Array(node_arr) = &type_def {
let node_arr_type =
resolve_type_definition(node_registry, node_arr.type_param.id)?;
if let TypeDef::Array(contract_arr) = &tt_def {
if node_arr.len != contract_arr.len {
anyhow::bail!("Mismatch in array lengths");
}
let contract_arr_type = resolve_type_definition(
contract_registry,
contract_arr.type_param.id,
)?;
return Ok(contract_arr_type == node_arr_type)
}
}
println!("Node {:?}\nContract: {:?}", type_def, tt_def);
ascjones marked this conversation as resolved.
Show resolved Hide resolved
Ok(type_def == tt_def)
}

fn find_message_spec(&self, name: &str) -> Option<&MessageSpec<PortableForm>> {
self.messages().find(|msg| msg.label() == &name.to_string())
Expand Down