Skip to content

Commit

Permalink
Merge pull request #184 from teonite/contract-verification-command-no…
Browse files Browse the repository at this point in the history
…de-2.0

Interaction with Casper smart-contract source code verification service for feat-2.0 branch
  • Loading branch information
zajko authored Oct 28, 2024
2 parents aef6def + 8df82e9 commit 08bf74d
Show file tree
Hide file tree
Showing 17 changed files with 579 additions and 80 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/target
Cargo.lock
*casper_keygen_test*
.idea/
.envrc
.direnv/
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,12 @@ std-fs-io = ["casper-types/std-fs-io"]
[dependencies]
async-trait = { version = "0.1.74", default-features = false, optional = true }
base16 = "0.2.1"
base64 = { version = "0.22.1", default-features = false }
bytes = { version = "1.6.0", default-features = false }
casper-types = { version = "5.0.0", features = ["std", "json-schema"] }
clap = { version = "~4.4", features = ["cargo", "deprecated"], optional = true }
clap_complete = { version = "~4.4", default-features = false, optional = true }
flate2 = "1.0.30"
hex-buffer-serde = "0.4.0"
humantime = "2.1.0"
itertools = "0.12.0"
Expand All @@ -47,6 +50,7 @@ schemars = "0.8.18"
serde = { version = "1", default-features = false, features = ["derive"] }
serde-map-to-array = "1.1.1"
serde_json = { version = "1", features = ["preserve_order"] }
tar = { version = "0.4.41", default-features = false }
thiserror = "1"
tokio = { version = "1.39.3", features = ["macros", "rt", "sync", "time"] }
uint = "0.9.5"
Expand Down
23 changes: 23 additions & 0 deletions lib/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ use crate::{
},
SuccessResponse,
};

#[cfg(feature = "std-fs-io")]
use crate::verification_types::VerificationDetails;
#[cfg(doc)]
use crate::{Account, Block, Error, StoredValue, Transfer};
#[cfg(doc)]
Expand Down Expand Up @@ -614,3 +617,23 @@ pub async fn get_era_info(
.await
.map_err(CliError::from)
}

/// Verifies the smart contract code against the one installed
/// by deploy or transaction with given hash.
#[cfg(feature = "std-fs-io")]
pub async fn verify_contract(
hash_str: &str,
verification_url_base_path: &str,
verification_project_path: Option<&str>,
verbosity_level: u64,
) -> Result<VerificationDetails, CliError> {
let verbosity = parse::verbosity(verbosity_level);
crate::verify_contract(
hash_str,
verification_url_base_path,
verification_project_path,
verbosity,
)
.await
.map_err(CliError::from)
}
44 changes: 22 additions & 22 deletions lib/cli/json_args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,59 +59,59 @@ fn write_json_to_bytesrepr(
.as_i64()
.and_then(|value| i32::try_from(value).ok())
.ok_or(ErrorDetails::CannotParseToI32)?;
value.write_bytes(output)?
value.write_bytes(output)?;
}
(&CLType::I64, Value::Number(number)) => {
let value = number.as_i64().ok_or(ErrorDetails::CannotParseToI64)?;
value.write_bytes(output)?
value.write_bytes(output)?;
}
(&CLType::U8, Value::Number(number)) => {
let value = number
.as_u64()
.and_then(|value| u8::try_from(value).ok())
.ok_or(ErrorDetails::CannotParseToU8)?;
value.write_bytes(output)?
value.write_bytes(output)?;
}
(&CLType::U32, Value::Number(number)) => {
let value = number
.as_u64()
.and_then(|value| u32::try_from(value).ok())
.ok_or(ErrorDetails::CannotParseToU32)?;
value.write_bytes(output)?
value.write_bytes(output)?;
}
(&CLType::U64, Value::Number(number)) => {
let value = number.as_u64().ok_or(ErrorDetails::CannotParseToU64)?;
value.write_bytes(output)?
value.write_bytes(output)?;
}
(&CLType::U128, Value::String(string)) => {
let value = U128::from_dec_str(string)?;
value.write_bytes(output)?
value.write_bytes(output)?;
}
(&CLType::U128, Value::Number(number)) => {
let value = number.as_u64().ok_or(ErrorDetails::CannotParseToU64)?;
U128::from(value).write_bytes(output)?
U128::from(value).write_bytes(output)?;
}
(&CLType::U256, Value::String(string)) => {
let value = U256::from_dec_str(string)?;
value.write_bytes(output)?
value.write_bytes(output)?;
}
(&CLType::U256, Value::Number(number)) => {
let value = number.as_u64().ok_or(ErrorDetails::CannotParseToU64)?;
U256::from(value).write_bytes(output)?
U256::from(value).write_bytes(output)?;
}
(&CLType::U512, Value::String(string)) => {
let value = U512::from_dec_str(string)?;
value.write_bytes(output)?
value.write_bytes(output)?;
}
(&CLType::U512, Value::Number(number)) => {
let value = number.as_u64().ok_or(ErrorDetails::CannotParseToU64)?;
U512::from(value).write_bytes(output)?
U512::from(value).write_bytes(output)?;
}
(&CLType::Unit, Value::Null) => (),
(&CLType::String, Value::String(string)) => string.write_bytes(output)?,
(&CLType::Key, Value::String(string)) => {
let value = Key::from_formatted_str(string)?;
value.write_bytes(output)?
value.write_bytes(output)?;
}
(&CLType::Key, Value::Object(map)) => {
// This is an alternative JSON representation of a `Key`, e.g. if calling
Expand Down Expand Up @@ -141,22 +141,22 @@ fn write_json_to_bytesrepr(
Key::ChainspecRegistry if mapped_variant == "ChainspecRegistry" => {}
_ => return Err(ErrorDetails::KeyObjectHasInvalidVariant),
}
value.write_bytes(output)?
value.write_bytes(output)?;
}
(&CLType::URef, Value::String(string)) => {
let value = URef::from_formatted_str(string)?;
value.write_bytes(output)?
value.write_bytes(output)?;
}
(&CLType::PublicKey, Value::String(string)) => {
let value = PublicKey::from_hex(string)?;
value.write_bytes(output)?
value.write_bytes(output)?;
}
(CLType::Option(ref _inner_cl_type), Value::Null) => {
output.push(OPTION_NONE_TAG);
}
(CLType::Option(ref inner_cl_type), _) => {
output.push(OPTION_SOME_TAG);
write_json_to_bytesrepr(inner_cl_type, json_value, output)?
write_json_to_bytesrepr(inner_cl_type, json_value, output)?;
}
(CLType::List(ref inner_cl_type), Value::Array(vec)) => {
(vec.len() as u32).write_bytes(output)?;
Expand Down Expand Up @@ -209,11 +209,11 @@ fn write_json_to_bytesrepr(
match map.iter().next() {
Some((key, value)) if key.to_ascii_lowercase() == "ok" => {
output.push(RESULT_OK_TAG);
write_json_to_bytesrepr(ok, value, output)?
write_json_to_bytesrepr(ok, value, output)?;
}
Some((key, value)) if key.to_ascii_lowercase() == "err" => {
output.push(RESULT_ERR_TAG);
write_json_to_bytesrepr(err, value, output)?
write_json_to_bytesrepr(err, value, output)?;
}
_ => return Err(ErrorDetails::ResultObjectHasInvalidVariant),
}
Expand Down Expand Up @@ -243,7 +243,7 @@ fn write_json_to_bytesrepr(
_ => return Err(ErrorDetails::MapTypeNotValidAsObject(*key_type.clone())),
};
(map.len() as u32).write_bytes(output)?;
for (key_as_str, value) in map.iter() {
for (key_as_str, value) in map {
let key = match **key_type {
CLType::I32 => json!(i32::from_str(key_as_str)?),
CLType::I64 => json!(i64::from_str(key_as_str)?),
Expand Down Expand Up @@ -294,7 +294,7 @@ fn write_json_to_bytesrepr(
actual: vec.len(),
});
}
write_json_to_bytesrepr(&inner_cl_types[0], &vec[0], output)?
write_json_to_bytesrepr(&inner_cl_types[0], &vec[0], output)?;
}
(CLType::Tuple2(ref inner_cl_types), Value::Array(vec)) => {
if vec.len() != inner_cl_types.len() {
Expand All @@ -304,7 +304,7 @@ fn write_json_to_bytesrepr(
});
}
write_json_to_bytesrepr(&inner_cl_types[0], &vec[0], output)?;
write_json_to_bytesrepr(&inner_cl_types[1], &vec[1], output)?
write_json_to_bytesrepr(&inner_cl_types[1], &vec[1], output)?;
}
(CLType::Tuple3(ref inner_cl_types), Value::Array(vec)) => {
if vec.len() != inner_cl_types.len() {
Expand All @@ -315,7 +315,7 @@ fn write_json_to_bytesrepr(
}
write_json_to_bytesrepr(&inner_cl_types[0], &vec[0], output)?;
write_json_to_bytesrepr(&inner_cl_types[1], &vec[1], output)?;
write_json_to_bytesrepr(&inner_cl_types[2], &vec[2], output)?
write_json_to_bytesrepr(&inner_cl_types[2], &vec[2], output)?;
}
_ => return Err(ErrorDetails::IncompatibleType),
};
Expand Down
8 changes: 8 additions & 0 deletions lib/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,14 @@ pub enum Error {
/// Underlying error.
error: std::str::Utf8Error,
},

/// Failed to verify contract.
#[error("contract verification failed")]
ContractVerificationFailed,

/// Failed to construct HTTP client.
#[error("failed to construct HTTP client")]
FailedToConstructHttpClient,
}

impl From<ToBytesError> for Error {
Expand Down
54 changes: 54 additions & 0 deletions lib/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,12 @@ pub mod rpcs;
pub mod types;
mod validation;
mod verbosity;
mod verification;
mod verification_types;

#[cfg(any(feature = "std-fs-io", test))]
use std::{
env::current_dir,
fs,
io::{Cursor, Read, Write},
path::Path,
Expand All @@ -65,6 +68,8 @@ use casper_types::{
#[cfg(any(feature = "std-fs-io", test))]
use casper_types::{SecretKey, TransactionV1};

#[cfg(any(feature = "std-fs-io", test))]
use base64::{engine::general_purpose::STANDARD, Engine};
pub use error::Error;
use json_rpc::JsonRpcCall;
pub use json_rpc::{JsonRpcId, SuccessResponse};
Expand Down Expand Up @@ -112,6 +117,9 @@ use rpcs::{
};
pub use validation::ValidateResponseError;
pub use verbosity::Verbosity;
pub use verification::{build_archive, send_verification_request};
#[cfg(any(feature = "std-fs-io", test))]
use verification_types::VerificationDetails;

/// The maximum permissible size in bytes of a Deploy when serialized via `ToBytes`.
///
Expand Down Expand Up @@ -749,3 +757,49 @@ pub async fn get_era_info(
.send_request(GET_ERA_INFO_METHOD, params)
.await
}

/// Verifies the smart contract code against the one deployed at given deploy or transaction hash.
#[cfg(any(feature = "std-fs-io", test))]
pub async fn verify_contract(
hash_str: &str,
verification_url_base_path: &str,
project_path: Option<&str>,
verbosity: Verbosity,
) -> Result<VerificationDetails, Error> {
if verbosity == Verbosity::Medium || verbosity == Verbosity::High {
println!("Hash: {hash_str}");
println!("Verification service base path: {verification_url_base_path}",);
}

let project_path = match project_path {
Some(path) => Path::new(path).to_path_buf(),
None => match current_dir() {
Ok(path) => path,
Err(error) => {
eprintln!("Cannot get current directory: {error}");
return Err(Error::ContractVerificationFailed);
}
},
};

let archive = match build_archive(&project_path) {
Ok(archive) => {
if verbosity == Verbosity::Medium || verbosity == Verbosity::High {
println!("Created project archive (size: {})", archive.len());
}
archive
}
Err(error) => {
eprintln!("Cannot create project archive: {error}");
return Err(Error::ContractVerificationFailed);
}
};

send_verification_request(
hash_str,
verification_url_base_path,
STANDARD.encode(&archive),
verbosity,
)
.await
}
2 changes: 1 addition & 1 deletion lib/rpcs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ pub(crate) mod v2_0_0;
pub use v2_0_0::{
get_account::AccountIdentifier,
get_dictionary_item::DictionaryItemIdentifier,
get_entity::{EntityIdentifier, EntityOrAccount},
get_entity::{AddressableEntity, EntityIdentifier, EntityOrAccount},
get_reward::EraIdentifier,
query_balance::PurseIdentifier,
query_global_state::GlobalStateIdentifier,
Expand Down
17 changes: 16 additions & 1 deletion lib/rpcs/v2_0_0/get_entity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,26 @@ pub enum EntityIdentifier {
EntityAddr(EntityAddr),
}

/// An addressable entity with named keys and entry points.
/// Represents an entity that is addressable within the Casper system.
/// This struct holds the entity, its associated named keys, and its
/// entry points for interaction.
///
/// # Fields
///
/// * `entity` - The addressable entity which could be a contract, account, or other entity types in Casper.
/// * `named_keys` - A collection of named keys that are associated with the entity. Named keys
/// are a mapping from string identifiers to keys (e.g., contracts, URefs, etc.).
/// * `entry_points` - A list of entry points representing methods or functions that the entity exposes.
/// Entry points define the public interface of a contract or other executable object.
#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
pub struct AddressableEntity {
/// The core addressable entity in Casper (account, contract, etc.).
pub entity: CasperTypesAddressableEntity,

/// The named keys associated with the entity, mapping identifiers to actual keys.
pub named_keys: NamedKeys,

/// A collection of entry points for the entity, defining its public interface.
pub entry_points: Vec<EntryPointValue>,
}

Expand Down
6 changes: 3 additions & 3 deletions lib/rpcs/v2_0_0/get_transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,9 @@ impl GetTransactionParams {
#[derive(Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
#[serde(deny_unknown_fields)]
pub struct ExecutionInfo {
pub(crate) block_hash: BlockHash,
pub(crate) block_height: u64,
pub(crate) execution_result: Option<ExecutionResult>,
pub block_hash: BlockHash,
pub block_height: u64,
pub execution_result: Option<ExecutionResult>,
}

/// Result for "info_get_transaction" RPC response.
Expand Down
Loading

0 comments on commit 08bf74d

Please sign in to comment.