#![allow(clippy::field_reassign_with_default)]
use std::{
array::TryFromSliceError,
collections::{BTreeSet, HashMap},
error::Error as StdError,
fmt::{self, Debug, Display, Formatter},
};
use datasize::DataSize;
use derive_more::Display;
use itertools::Itertools;
use num_traits::Zero;
use once_cell::sync::Lazy;
#[cfg(test)]
use rand::{Rng, RngCore};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use tracing::{info, warn};
#[cfg(test)]
use casper_execution_engine::core::engine_state::MAX_PAYMENT;
use casper_execution_engine::core::engine_state::{
executable_deploy_item::ExecutableDeployItem, DeployItem,
};
use casper_hashing::Digest;
#[cfg(test)]
use casper_types::bytesrepr::Bytes;
use casper_types::{
bytesrepr::{self, FromBytes, ToBytes},
runtime_args,
system::standard_payment::ARG_AMOUNT,
ExecutionResult, Motes, PublicKey, RuntimeArgs, SecretKey, Signature, U512,
};
use super::{BlockHash, Item, Tag, TimeDiff, Timestamp};
#[cfg(test)]
use crate::testing::TestRng;
use crate::{
components::block_proposer::DeployInfo, crypto, crypto::AsymmetricKeyExt,
rpcs::docs::DocExample, types::chainspec::DeployConfig, utils::DisplayIter,
};
static DEPLOY: Lazy<Deploy> = Lazy::new(|| {
let payment_args = runtime_args! {
"amount" => 1000
};
let payment = ExecutableDeployItem::StoredContractByName {
name: String::from("casper-example"),
entry_point: String::from("example-entry-point"),
args: payment_args,
};
let session_args = runtime_args! {
"amount" => 1000
};
let session = ExecutableDeployItem::Transfer { args: session_args };
let serialized_body = serialize_body(&payment, &session);
let body_hash = Digest::hash(&serialized_body);
let secret_key = SecretKey::doc_example();
let header = DeployHeader {
account: PublicKey::from(secret_key),
timestamp: *Timestamp::doc_example(),
ttl: TimeDiff::from(3_600_000),
gas_price: 1,
body_hash,
dependencies: vec![DeployHash::new(Digest::from([1u8; Digest::LENGTH]))],
chain_name: String::from("casper-example"),
};
let serialized_header = serialize_header(&header);
let hash = DeployHash::new(Digest::hash(&serialized_header));
let mut approvals = BTreeSet::new();
let approval = Approval::create(&hash, secret_key);
approvals.insert(approval);
Deploy {
hash,
header,
payment,
session,
approvals,
is_valid: None,
}
});
#[derive(Clone, DataSize, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Error, Serialize)]
pub enum DeployConfigurationFailure {
#[error("invalid chain name: expected {expected}, got {got}")]
InvalidChainName {
expected: String,
got: String,
},
#[error("{got} dependencies exceeds limit of {max_dependencies}")]
ExcessiveDependencies {
max_dependencies: u8,
got: usize,
},
#[error("deploy size too large: {0}")]
ExcessiveSize(#[from] ExcessiveSizeError),
#[error("time-to-live of {got} exceeds limit of {max_ttl}")]
ExcessiveTimeToLive {
max_ttl: TimeDiff,
got: TimeDiff,
},
#[error("the provided body hash does not match the actual hash of the body")]
InvalidBodyHash,
#[error("the provided hash does not match the actual hash of the deploy")]
InvalidDeployHash,
#[error("the deploy has no approvals")]
EmptyApprovals,
#[error("the approval at index {index} is invalid: {error_msg}")]
InvalidApproval {
index: usize,
error_msg: String,
},
#[error("serialized session code runtime args of {got} exceeds limit of {max_length}")]
ExcessiveSessionArgsLength {
max_length: usize,
got: usize,
},
#[error("serialized payment code runtime args of {got} exceeds limit of {max_length}")]
ExcessivePaymentArgsLength {
max_length: usize,
got: usize,
},
#[error("missing payment 'amount' runtime argument ")]
MissingPaymentAmount,
#[error("failed to parse payment 'amount' as U512")]
FailedToParsePaymentAmount,
#[error("payment amount of {got} exceeds the block gas limit of {block_gas_limit}")]
ExceededBlockGasLimit {
block_gas_limit: u64,
got: U512,
},
#[error("missing transfer 'amount' runtime argument")]
MissingTransferAmount,
#[error("failed to parse transfer 'amount' as U512")]
FailedToParseTransferAmount,
#[error("insufficient transfer amount; minimum: {minimum} attempted: {attempted}")]
InsufficientTransferAmount {
minimum: U512,
attempted: U512,
},
#[error("number of associated keys {got} exceeds the maximum {max_associated_keys}")]
ExcessiveApprovals {
got: u32,
max_associated_keys: u32,
},
}
#[derive(Clone, DataSize, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Error, Serialize)]
#[error("deploy size of {actual_deploy_size} bytes exceeds limit of {max_deploy_size}")]
pub struct ExcessiveSizeError {
pub max_deploy_size: u32,
pub actual_deploy_size: usize,
}
#[derive(Debug, Error)]
pub enum Error {
#[error("encoding to JSON: {0}")]
EncodeToJson(#[from] serde_json::Error),
#[error("decoding from JSON: {0}")]
DecodeFromJson(Box<dyn StdError>),
#[error("invalid payment: missing \"amount\" arg")]
InvalidPayment,
}
impl From<base16::DecodeError> for Error {
fn from(error: base16::DecodeError) -> Self {
Error::DecodeFromJson(Box::new(error))
}
}
impl From<TryFromSliceError> for Error {
fn from(error: TryFromSliceError) -> Self {
Error::DecodeFromJson(Box::new(error))
}
}
#[derive(
Copy,
Clone,
DataSize,
Ord,
PartialOrd,
Eq,
PartialEq,
Hash,
Serialize,
Deserialize,
Debug,
Default,
JsonSchema,
)]
#[serde(deny_unknown_fields)]
#[schemars(with = "String", description = "Hex-encoded deploy hash.")]
pub struct DeployHash(#[schemars(skip)] Digest);
impl DeployHash {
pub fn new(hash: Digest) -> Self {
DeployHash(hash)
}
pub fn inner(&self) -> &Digest {
&self.0
}
#[cfg(test)]
pub fn random(rng: &mut TestRng) -> Self {
let hash = rng.gen::<[u8; Digest::LENGTH]>().into();
DeployHash(hash)
}
}
impl From<DeployHash> for casper_types::DeployHash {
fn from(deploy_hash: DeployHash) -> casper_types::DeployHash {
casper_types::DeployHash::new(deploy_hash.inner().value())
}
}
impl From<DeployHash> for Digest {
fn from(deploy_hash: DeployHash) -> Self {
deploy_hash.0
}
}
impl Display for DeployHash {
fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
write!(formatter, "deploy-hash({})", self.0,)
}
}
impl From<Digest> for DeployHash {
fn from(digest: Digest) -> Self {
Self(digest)
}
}
impl AsRef<[u8]> for DeployHash {
fn as_ref(&self) -> &[u8] {
self.0.as_ref()
}
}
impl ToBytes for DeployHash {
fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
self.0.to_bytes()
}
fn serialized_length(&self) -> usize {
self.0.serialized_length()
}
}
impl FromBytes for DeployHash {
fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
Digest::from_bytes(bytes).map(|(inner, remainder)| (DeployHash(inner), remainder))
}
}
#[derive(
Copy,
Clone,
DataSize,
Ord,
PartialOrd,
Eq,
PartialEq,
Hash,
Serialize,
Deserialize,
Debug,
Display,
JsonSchema,
)]
#[serde(deny_unknown_fields)]
pub enum DeployOrTransferHash {
#[display(fmt = "deploy {}", _0)]
Deploy(DeployHash),
#[display(fmt = "transfer {}", _0)]
Transfer(DeployHash),
}
impl DeployOrTransferHash {
pub fn deploy_hash(&self) -> &DeployHash {
match self {
DeployOrTransferHash::Deploy(hash) | DeployOrTransferHash::Transfer(hash) => hash,
}
}
pub fn is_transfer(&self) -> bool {
matches!(self, DeployOrTransferHash::Transfer(_))
}
}
impl From<DeployOrTransferHash> for DeployHash {
fn from(dt_hash: DeployOrTransferHash) -> DeployHash {
match dt_hash {
DeployOrTransferHash::Deploy(hash) => hash,
DeployOrTransferHash::Transfer(hash) => hash,
}
}
}
#[derive(
Clone, DataSize, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize, Deserialize, Debug, JsonSchema,
)]
#[serde(deny_unknown_fields)]
pub struct DeployHeader {
account: PublicKey,
timestamp: Timestamp,
ttl: TimeDiff,
gas_price: u64,
body_hash: Digest,
dependencies: Vec<DeployHash>,
chain_name: String,
}
impl DeployHeader {
pub fn account(&self) -> &PublicKey {
&self.account
}
pub fn timestamp(&self) -> Timestamp {
self.timestamp
}
pub fn ttl(&self) -> TimeDiff {
self.ttl
}
pub fn expired(&self, current_instant: Timestamp) -> bool {
let lifespan = self.timestamp + self.ttl;
lifespan < current_instant
}
pub fn gas_price(&self) -> u64 {
self.gas_price
}
pub fn body_hash(&self) -> &Digest {
&self.body_hash
}
pub fn dependencies(&self) -> &Vec<DeployHash> {
&self.dependencies
}
pub fn chain_name(&self) -> &str {
&self.chain_name
}
pub fn is_valid(&self, deploy_config: &DeployConfig, current_timestamp: Timestamp) -> bool {
let ttl_valid = self.ttl() <= deploy_config.max_ttl;
let timestamp_valid = self.timestamp() <= current_timestamp;
let not_expired = !self.expired(current_timestamp);
let num_deps_valid = self.dependencies().len() <= deploy_config.max_dependencies as usize;
ttl_valid && timestamp_valid && not_expired && num_deps_valid
}
}
impl DeployHeader {
pub fn expires(&self) -> Timestamp {
self.timestamp + self.ttl
}
}
impl ToBytes for DeployHeader {
fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
let mut buffer = bytesrepr::allocate_buffer(self)?;
buffer.extend(self.account.to_bytes()?);
buffer.extend(self.timestamp.to_bytes()?);
buffer.extend(self.ttl.to_bytes()?);
buffer.extend(self.gas_price.to_bytes()?);
buffer.extend(self.body_hash.to_bytes()?);
buffer.extend(self.dependencies.to_bytes()?);
buffer.extend(self.chain_name.to_bytes()?);
Ok(buffer)
}
fn serialized_length(&self) -> usize {
self.account.serialized_length()
+ self.timestamp.serialized_length()
+ self.ttl.serialized_length()
+ self.gas_price.serialized_length()
+ self.body_hash.serialized_length()
+ self.dependencies.serialized_length()
+ self.chain_name.serialized_length()
}
}
impl FromBytes for DeployHeader {
fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
let (account, remainder) = PublicKey::from_bytes(bytes)?;
let (timestamp, remainder) = Timestamp::from_bytes(remainder)?;
let (ttl, remainder) = TimeDiff::from_bytes(remainder)?;
let (gas_price, remainder) = u64::from_bytes(remainder)?;
let (body_hash, remainder) = Digest::from_bytes(remainder)?;
let (dependencies, remainder) = Vec::<DeployHash>::from_bytes(remainder)?;
let (chain_name, remainder) = String::from_bytes(remainder)?;
let deploy_header = DeployHeader {
account,
timestamp,
ttl,
gas_price,
body_hash,
dependencies,
chain_name,
};
Ok((deploy_header, remainder))
}
}
impl Display for DeployHeader {
fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
write!(
formatter,
"deploy-header[account: {}, timestamp: {}, ttl: {}, gas_price: {}, body_hash: {}, dependencies: [{}], chain_name: {}]",
self.account,
self.timestamp,
self.ttl,
self.gas_price,
self.body_hash,
DisplayIter::new(self.dependencies.iter()),
self.chain_name,
)
}
}
#[derive(
Clone, DataSize, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize, Deserialize, Debug, JsonSchema,
)]
#[serde(deny_unknown_fields)]
pub struct Approval {
signer: PublicKey,
signature: Signature,
}
impl Approval {
pub fn create(hash: &DeployHash, secret_key: &SecretKey) -> Self {
let signer = PublicKey::from(secret_key);
let signature = crypto::sign(hash, secret_key, &signer);
Self { signer, signature }
}
pub fn signer(&self) -> &PublicKey {
&self.signer
}
pub fn signature(&self) -> &Signature {
&self.signature
}
}
#[cfg(test)]
impl Approval {
pub fn random(rng: &mut TestRng) -> Self {
Self {
signer: PublicKey::random(rng),
signature: Signature::ed25519([0; Signature::ED25519_LENGTH]).unwrap(),
}
}
}
impl Display for Approval {
fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
write!(formatter, "approval({})", self.signer)
}
}
impl ToBytes for Approval {
fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
let mut buffer = bytesrepr::allocate_buffer(self)?;
buffer.extend(self.signer.to_bytes()?);
buffer.extend(self.signature.to_bytes()?);
Ok(buffer)
}
fn serialized_length(&self) -> usize {
self.signer.serialized_length() + self.signature.serialized_length()
}
}
impl FromBytes for Approval {
fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
let (signer, remainder) = PublicKey::from_bytes(bytes)?;
let (signature, remainder) = Signature::from_bytes(remainder)?;
let approval = Approval { signer, signature };
Ok((approval, remainder))
}
}
#[derive(Clone, DataSize, Debug, PartialOrd, Ord, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct DeployWithApprovals {
deploy_hash: DeployHash,
approvals: BTreeSet<Approval>,
}
impl DeployWithApprovals {
pub fn new(deploy_hash: DeployHash, approvals: BTreeSet<Approval>) -> Self {
Self {
deploy_hash,
approvals,
}
}
pub fn deploy_hash(&self) -> &DeployHash {
&self.deploy_hash
}
pub fn approvals(&self) -> &BTreeSet<Approval> {
&self.approvals
}
}
#[derive(DataSize, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct FinalizedApprovals(BTreeSet<Approval>);
impl FinalizedApprovals {
pub fn new(approvals: BTreeSet<Approval>) -> Self {
Self(approvals)
}
}
impl AsRef<BTreeSet<Approval>> for FinalizedApprovals {
fn as_ref(&self) -> &BTreeSet<Approval> {
&self.0
}
}
#[derive(
Clone, DataSize, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize, Deserialize, Debug, JsonSchema,
)]
#[serde(deny_unknown_fields)]
pub struct Deploy {
hash: DeployHash,
header: DeployHeader,
payment: ExecutableDeployItem,
session: ExecutableDeployItem,
approvals: BTreeSet<Approval>,
#[serde(skip)]
is_valid: Option<Result<(), DeployConfigurationFailure>>,
}
impl Deploy {
#[allow(clippy::too_many_arguments)]
pub fn new(
timestamp: Timestamp,
ttl: TimeDiff,
gas_price: u64,
dependencies: Vec<DeployHash>,
chain_name: String,
payment: ExecutableDeployItem,
session: ExecutableDeployItem,
secret_key: &SecretKey,
account: Option<PublicKey>,
) -> Deploy {
let serialized_body = serialize_body(&payment, &session);
let body_hash = Digest::hash(&serialized_body);
let account = account.unwrap_or_else(|| PublicKey::from(secret_key));
let dependencies = dependencies.into_iter().unique().collect();
let header = DeployHeader {
account,
timestamp,
ttl,
gas_price,
body_hash,
dependencies,
chain_name,
};
let serialized_header = serialize_header(&header);
let hash = DeployHash::new(Digest::hash(&serialized_header));
let mut deploy = Deploy {
hash,
header,
payment,
session,
approvals: BTreeSet::new(),
is_valid: None,
};
deploy.sign(secret_key);
deploy
}
pub fn sign(&mut self, secret_key: &SecretKey) {
let approval = Approval::create(&self.hash, secret_key);
self.approvals.insert(approval);
}
pub fn id(&self) -> &DeployHash {
&self.hash
}
pub fn header(&self) -> &DeployHeader {
&self.header
}
pub fn take_header(self) -> DeployHeader {
self.header
}
pub fn payment(&self) -> &ExecutableDeployItem {
&self.payment
}
pub fn session(&self) -> &ExecutableDeployItem {
&self.session
}
pub fn approvals(&self) -> &BTreeSet<Approval> {
&self.approvals
}
pub fn deploy_or_transfer_hash(&self) -> DeployOrTransferHash {
if self.session.is_transfer() {
DeployOrTransferHash::Transfer(self.hash)
} else {
DeployOrTransferHash::Deploy(self.hash)
}
}
pub fn deploy_info(&self) -> Result<DeployInfo, Error> {
let header = self.header().clone();
let size = self.serialized_length();
let payment_amount = if self.session().is_transfer() {
Motes::zero()
} else {
let payment_item = self.payment().clone();
let value = payment_item
.args()
.get(ARG_AMOUNT)
.ok_or(Error::InvalidPayment)?;
let value = value
.clone()
.into_t::<U512>()
.map_err(|_| Error::InvalidPayment)?;
Motes::new(value)
};
Ok(DeployInfo {
header,
payment_amount,
size,
})
}
pub fn is_valid_size(&self, max_deploy_size: u32) -> Result<(), ExcessiveSizeError> {
let deploy_size = self.serialized_length();
if deploy_size > max_deploy_size as usize {
return Err(ExcessiveSizeError {
max_deploy_size,
actual_deploy_size: deploy_size,
});
}
Ok(())
}
pub fn is_valid(&mut self) -> Result<(), DeployConfigurationFailure> {
match self.is_valid.as_ref() {
None => {
let validity = validate_deploy(self);
self.is_valid = Some(validity.clone());
validity
}
Some(validity) => validity.clone(),
}
}
pub fn is_config_compliant(
&self,
chain_name: &str,
config: &DeployConfig,
max_associated_keys: u32,
) -> Result<(), DeployConfigurationFailure> {
self.is_valid_size(config.max_deploy_size)?;
let header = self.header();
if header.chain_name() != chain_name {
info!(
deploy_hash = %self.id(),
deploy_header = %header,
chain_name = %header.chain_name(),
"invalid chain identifier"
);
return Err(DeployConfigurationFailure::InvalidChainName {
expected: chain_name.to_string(),
got: header.chain_name().to_string(),
});
}
if header.dependencies().len() > config.max_dependencies as usize {
info!(
deploy_hash = %self.id(),
deploy_header = %header,
max_dependencies = %config.max_dependencies,
"deploy dependency ceiling exceeded"
);
return Err(DeployConfigurationFailure::ExcessiveDependencies {
max_dependencies: config.max_dependencies,
got: header.dependencies().len(),
});
}
if header.ttl() > config.max_ttl {
info!(
deploy_hash = %self.id(),
deploy_header = %header,
max_ttl = %config.max_ttl,
"deploy ttl excessive"
);
return Err(DeployConfigurationFailure::ExcessiveTimeToLive {
max_ttl: config.max_ttl,
got: header.ttl(),
});
}
if self.approvals.len() > max_associated_keys as usize {
info!(
deploy_hash = %self.id(),
number_of_associated_keys = %self.approvals.len(),
max_associated_keys = %max_associated_keys,
"number of associated keys exceeds the maximum limit"
);
return Err(DeployConfigurationFailure::ExcessiveApprovals {
got: self.approvals.len() as u32,
max_associated_keys,
});
}
if !self.session().is_transfer() {
let value = self
.payment()
.args()
.get(ARG_AMOUNT)
.ok_or(DeployConfigurationFailure::MissingPaymentAmount)?;
let payment_amount = value
.clone()
.into_t::<U512>()
.map_err(|_| DeployConfigurationFailure::FailedToParsePaymentAmount)?;
if payment_amount > U512::from(config.block_gas_limit) {
info!(
amount = %payment_amount,
block_gas_limit = %config.block_gas_limit, "payment amount exceeds block gas limit"
);
return Err(DeployConfigurationFailure::ExceededBlockGasLimit {
block_gas_limit: config.block_gas_limit,
got: payment_amount,
});
}
}
let payment_args_length = self.payment().args().serialized_length();
if payment_args_length > config.payment_args_max_length as usize {
info!(
payment_args_length,
payment_args_max_length = config.payment_args_max_length,
"payment args excessive"
);
return Err(DeployConfigurationFailure::ExcessivePaymentArgsLength {
max_length: config.payment_args_max_length as usize,
got: payment_args_length,
});
}
let session_args_length = self.session().args().serialized_length();
if session_args_length > config.session_args_max_length as usize {
info!(
session_args_length,
session_args_max_length = config.session_args_max_length,
"session args excessive"
);
return Err(DeployConfigurationFailure::ExcessiveSessionArgsLength {
max_length: config.session_args_max_length as usize,
got: session_args_length,
});
}
if self.session().is_transfer() {
let item = self.session().clone();
let attempted = item
.args()
.get(ARG_AMOUNT)
.ok_or_else(|| {
info!("missing transfer 'amount' runtime argument");
DeployConfigurationFailure::MissingTransferAmount
})?
.clone()
.into_t::<U512>()
.map_err(|_| {
info!("failed to parse transfer 'amount' runtime argument as a U512");
DeployConfigurationFailure::FailedToParseTransferAmount
})?;
let minimum = U512::from(config.native_transfer_minimum_motes);
if attempted < minimum {
info!(
minimum = %config.native_transfer_minimum_motes,
amount = %attempted,
"insufficient transfer amount"
);
return Err(DeployConfigurationFailure::InsufficientTransferAmount {
minimum,
attempted,
});
}
}
Ok(())
}
}
#[derive(DataSize, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct DeployWithFinalizedApprovals {
deploy: Deploy,
finalized_approvals: Option<FinalizedApprovals>,
}
impl DeployWithFinalizedApprovals {
pub fn new(deploy: Deploy, finalized_approvals: Option<FinalizedApprovals>) -> Self {
Self {
deploy,
finalized_approvals,
}
}
pub fn into_naive(self) -> Deploy {
let mut deploy = self.deploy;
if let Some(finalized_approvals) = self.finalized_approvals {
deploy.approvals = finalized_approvals.0;
}
deploy
}
pub fn discard_finalized_approvals(self) -> Deploy {
self.deploy
}
#[cfg(test)]
pub(crate) fn original_approvals(&self) -> &BTreeSet<Approval> {
self.deploy.approvals()
}
#[cfg(test)]
pub(crate) fn finalized_approvals(&self) -> Option<&FinalizedApprovals> {
self.finalized_approvals.as_ref()
}
}
#[cfg(test)]
impl Deploy {
pub fn random(rng: &mut TestRng) -> Self {
let timestamp = Timestamp::random(rng);
let ttl = TimeDiff::from(rng.gen_range(60_000..3_600_000));
Deploy::random_with_timestamp_and_ttl(rng, timestamp, ttl)
}
pub fn random_with_timestamp_and_ttl(
rng: &mut TestRng,
timestamp: Timestamp,
ttl: TimeDiff,
) -> Self {
let gas_price = rng.gen_range(1..100);
let dependencies = vec![
DeployHash::new(Digest::hash(rng.next_u64().to_le_bytes())),
DeployHash::new(Digest::hash(rng.next_u64().to_le_bytes())),
DeployHash::new(Digest::hash(rng.next_u64().to_le_bytes())),
];
let chain_name = String::from("casper-example");
let payment_args = runtime_args! {
"amount" => U512::from(10),
};
let payment = ExecutableDeployItem::StoredContractByName {
name: String::from("casper-example"),
entry_point: String::from("example-entry-point"),
args: payment_args,
};
let session = rng.gen();
let secret_key = SecretKey::random(rng);
Deploy::new(
timestamp,
ttl,
gas_price,
dependencies,
chain_name,
payment,
session,
&secret_key,
None,
)
}
pub(crate) fn invalidate(&mut self) {
self.header.chain_name.clear();
}
pub(crate) fn random_valid_native_transfer(rng: &mut TestRng) -> Self {
let deploy = Self::random(rng);
let transfer_args = runtime_args! {
"amount" => *MAX_PAYMENT,
"source" => PublicKey::random(rng).to_account_hash(),
"target" => PublicKey::random(rng).to_account_hash(),
};
let payment_args = runtime_args! {
"amount" => U512::from(10),
};
let session = ExecutableDeployItem::Transfer {
args: transfer_args,
};
let payment = ExecutableDeployItem::ModuleBytes {
module_bytes: Bytes::new(),
args: payment_args,
};
let secret_key = SecretKey::random(rng);
Deploy::new(
Timestamp::now(),
deploy.header.ttl,
deploy.header.gas_price,
deploy.header.dependencies,
deploy.header.chain_name,
payment,
session,
&secret_key,
None,
)
}
pub(crate) fn random_valid_native_transfer_without_deps(rng: &mut TestRng) -> Self {
let deploy = Self::random(rng);
let transfer_args = runtime_args! {
"amount" => *MAX_PAYMENT,
"source" => PublicKey::random(rng).to_account_hash(),
"target" => PublicKey::random(rng).to_account_hash(),
};
let payment_args = runtime_args! {
"amount" => U512::from(10),
};
let session = ExecutableDeployItem::Transfer {
args: transfer_args,
};
let payment = ExecutableDeployItem::ModuleBytes {
module_bytes: Bytes::new(),
args: payment_args,
};
let secret_key = SecretKey::random(rng);
Deploy::new(
Timestamp::now(),
deploy.header.ttl,
deploy.header.gas_price,
vec![],
deploy.header.chain_name,
payment,
session,
&secret_key,
None,
)
}
pub(crate) fn random_without_payment_amount(rng: &mut TestRng) -> Self {
let payment = ExecutableDeployItem::ModuleBytes {
module_bytes: Bytes::new(),
args: RuntimeArgs::default(),
};
Self::random_transfer_with_payment(rng, payment)
}
pub(crate) fn random_with_mangled_payment_amount(rng: &mut TestRng) -> Self {
let payment_args = runtime_args! {
"amount" => "invalid-argument"
};
let payment = ExecutableDeployItem::ModuleBytes {
module_bytes: Bytes::new(),
args: payment_args,
};
Self::random_transfer_with_payment(rng, payment)
}
pub(crate) fn random_with_valid_custom_payment_contract_by_name(rng: &mut TestRng) -> Self {
let payment = ExecutableDeployItem::StoredContractByName {
name: "Test".to_string(),
entry_point: "call".to_string(),
args: Default::default(),
};
Self::random_transfer_with_payment(rng, payment)
}
pub(crate) fn random_with_missing_payment_contract_by_hash(rng: &mut TestRng) -> Self {
let payment = ExecutableDeployItem::StoredContractByHash {
hash: [19; 32].into(),
entry_point: "call".to_string(),
args: Default::default(),
};
Self::random_transfer_with_payment(rng, payment)
}
pub(crate) fn random_with_missing_entry_point_in_payment_contract(rng: &mut TestRng) -> Self {
let payment = ExecutableDeployItem::StoredContractByHash {
hash: [19; 32].into(),
entry_point: "non-existent-entry-point".to_string(),
args: Default::default(),
};
Self::random_transfer_with_payment(rng, payment)
}
pub(crate) fn random_with_valid_custom_payment_package_by_name(rng: &mut TestRng) -> Self {
let payment = ExecutableDeployItem::StoredVersionedContractByName {
name: "Test".to_string(),
version: None,
entry_point: "call".to_string(),
args: Default::default(),
};
Self::random_transfer_with_payment(rng, payment)
}
pub(crate) fn random_with_missing_payment_package_by_hash(rng: &mut TestRng) -> Self {
let payment = ExecutableDeployItem::StoredVersionedContractByHash {
hash: Default::default(),
version: None,
entry_point: "call".to_string(),
args: Default::default(),
};
Self::random_transfer_with_payment(rng, payment)
}
pub(crate) fn random_with_nonexistent_contract_version_in_payment_package(
rng: &mut TestRng,
) -> Self {
let payment = ExecutableDeployItem::StoredVersionedContractByHash {
hash: [19; 32].into(),
version: Some(6u32),
entry_point: "non-existent-entry-point".to_string(),
args: Default::default(),
};
Self::random_transfer_with_payment(rng, payment)
}
pub(crate) fn random_with_valid_session_contract_by_name(rng: &mut TestRng) -> Self {
let session = ExecutableDeployItem::StoredContractByName {
name: "Test".to_string(),
entry_point: "call".to_string(),
args: Default::default(),
};
Self::random_transfer_with_session(rng, session)
}
pub(crate) fn random_with_missing_session_contract_by_hash(rng: &mut TestRng) -> Self {
let session = ExecutableDeployItem::StoredContractByHash {
hash: Default::default(),
entry_point: "call".to_string(),
args: Default::default(),
};
Self::random_transfer_with_session(rng, session)
}
pub(crate) fn random_with_missing_entry_point_in_session_contract(rng: &mut TestRng) -> Self {
let session = ExecutableDeployItem::StoredContractByHash {
hash: [19; 32].into(),
entry_point: "non-existent-entry-point".to_string(),
args: Default::default(),
};
Self::random_transfer_with_session(rng, session)
}
pub(crate) fn random_with_valid_session_package_by_name(rng: &mut TestRng) -> Self {
let session = ExecutableDeployItem::StoredVersionedContractByName {
name: "Test".to_string(),
version: None,
entry_point: "call".to_string(),
args: Default::default(),
};
Self::random_transfer_with_session(rng, session)
}
pub(crate) fn random_with_missing_session_package_by_hash(rng: &mut TestRng) -> Self {
let session = ExecutableDeployItem::StoredVersionedContractByHash {
hash: Default::default(),
version: None,
entry_point: "call".to_string(),
args: Default::default(),
};
Self::random_transfer_with_session(rng, session)
}
pub(crate) fn random_with_nonexistent_contract_version_in_session_package(
rng: &mut TestRng,
) -> Self {
let session = ExecutableDeployItem::StoredVersionedContractByHash {
hash: [19; 32].into(),
version: Some(6u32),
entry_point: "non-existent-entry-point".to_string(),
args: Default::default(),
};
Self::random_transfer_with_session(rng, session)
}
pub(crate) fn random_without_transfer_target(rng: &mut TestRng) -> Self {
let transfer_args = runtime_args! {
"amount" => *MAX_PAYMENT,
"source" => PublicKey::random(rng).to_account_hash(),
};
let session = ExecutableDeployItem::Transfer {
args: transfer_args,
};
Self::random_transfer_with_session(rng, session)
}
pub(crate) fn random_without_transfer_amount(rng: &mut TestRng) -> Self {
let transfer_args = runtime_args! {
"source" => PublicKey::random(rng).to_account_hash(),
"target" => PublicKey::random(rng).to_account_hash(),
};
let session = ExecutableDeployItem::Transfer {
args: transfer_args,
};
Self::random_transfer_with_session(rng, session)
}
pub(crate) fn random_with_mangled_transfer_amount(rng: &mut TestRng) -> Self {
let transfer_args = runtime_args! {
"amount" => "mangled-transfer-amount",
"source" => PublicKey::random(rng).to_account_hash(),
"target" => PublicKey::random(rng).to_account_hash(),
};
let session = ExecutableDeployItem::Transfer {
args: transfer_args,
};
Self::random_transfer_with_session(rng, session)
}
pub(crate) fn random_with_empty_session_module_bytes(rng: &mut TestRng) -> Self {
let session = ExecutableDeployItem::ModuleBytes {
module_bytes: Bytes::new(),
args: Default::default(),
};
Self::random_transfer_with_session(rng, session)
}
pub(crate) fn random_expired_deploy(rng: &mut TestRng) -> Self {
let deploy = Self::random_valid_native_transfer(rng);
let secret_key = SecretKey::random(rng);
Deploy::new(
Timestamp::zero(),
TimeDiff::from_seconds(1u32),
deploy.header.gas_price,
deploy.header.dependencies,
deploy.header.chain_name,
deploy.payment,
deploy.session,
&secret_key,
None,
)
}
pub(crate) fn random_with_native_transfer_in_payment_logic(rng: &mut TestRng) -> Self {
let transfer_args = runtime_args! {
"amount" => *MAX_PAYMENT,
"source" => PublicKey::random(rng).to_account_hash(),
"target" => PublicKey::random(rng).to_account_hash(),
};
let payment = ExecutableDeployItem::Transfer {
args: transfer_args,
};
Self::random_transfer_with_payment(rng, payment)
}
fn random_transfer_with_payment(rng: &mut TestRng, payment: ExecutableDeployItem) -> Self {
let deploy = Self::random_valid_native_transfer(rng);
let secret_key = SecretKey::random(rng);
Deploy::new(
deploy.header.timestamp,
deploy.header.ttl,
deploy.header.gas_price,
deploy.header.dependencies,
deploy.header.chain_name,
payment,
deploy.session,
&secret_key,
None,
)
}
fn random_transfer_with_session(rng: &mut TestRng, session: ExecutableDeployItem) -> Self {
let deploy = Self::random_valid_native_transfer(rng);
let secret_key = SecretKey::random(rng);
Deploy::new(
deploy.header.timestamp,
deploy.header.ttl,
deploy.header.gas_price,
deploy.header.dependencies,
deploy.header.chain_name,
deploy.payment,
session,
&secret_key,
None,
)
}
}
impl DocExample for Deploy {
fn doc_example() -> &'static Self {
&*DEPLOY
}
}
fn serialize_header(header: &DeployHeader) -> Vec<u8> {
header
.to_bytes()
.unwrap_or_else(|error| panic!("should serialize deploy header: {}", error))
}
fn serialize_body(payment: &ExecutableDeployItem, session: &ExecutableDeployItem) -> Vec<u8> {
let mut buffer = payment
.to_bytes()
.unwrap_or_else(|error| panic!("should serialize payment code: {}", error));
buffer.extend(
session
.to_bytes()
.unwrap_or_else(|error| panic!("should serialize session code: {}", error)),
);
buffer
}
fn validate_deploy(deploy: &Deploy) -> Result<(), DeployConfigurationFailure> {
if deploy.approvals.is_empty() {
warn!(?deploy, "deploy has no approvals");
return Err(DeployConfigurationFailure::EmptyApprovals);
}
let serialized_body = serialize_body(&deploy.payment, &deploy.session);
let body_hash = Digest::hash(&serialized_body);
if body_hash != deploy.header.body_hash {
warn!(?deploy, ?body_hash, "invalid deploy body hash");
return Err(DeployConfigurationFailure::InvalidBodyHash);
}
let serialized_header = serialize_header(&deploy.header);
let hash = DeployHash::new(Digest::hash(&serialized_header));
if hash != deploy.hash {
warn!(?deploy, ?hash, "invalid deploy hash");
return Err(DeployConfigurationFailure::InvalidDeployHash);
}
for (index, approval) in deploy.approvals.iter().enumerate() {
if let Err(error) = crypto::verify(&deploy.hash, &approval.signature, &approval.signer) {
warn!(?deploy, "failed to verify approval {}: {}", index, error);
return Err(DeployConfigurationFailure::InvalidApproval {
index,
error_msg: error.to_string(),
});
}
}
Ok(())
}
impl Item for Deploy {
type Id = DeployHash;
const TAG: Tag = Tag::Deploy;
const ID_IS_COMPLETE_ITEM: bool = false;
fn id(&self) -> Self::Id {
*self.id()
}
}
impl Display for Deploy {
fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
write!(
formatter,
"deploy[{}, {}, payment_code: {}, session_code: {}, approvals: {}]",
self.hash,
self.header,
self.payment,
self.session,
DisplayIter::new(self.approvals.iter())
)
}
}
impl From<Deploy> for DeployItem {
fn from(deploy: Deploy) -> Self {
let address = deploy.header().account().to_account_hash();
let authorization_keys = deploy
.approvals()
.iter()
.map(|approval| approval.signer().to_account_hash())
.collect();
DeployItem::new(
address,
deploy.session().clone(),
deploy.payment().clone(),
deploy.header().gas_price(),
authorization_keys,
casper_types::DeployHash::new(deploy.id().inner().value()),
)
}
}
#[derive(Clone, Default, Serialize, Deserialize, Debug, PartialEq, Eq)]
pub struct DeployMetadata {
pub execution_results: HashMap<BlockHash, ExecutionResult>,
}
impl ToBytes for Deploy {
fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
let mut buffer = bytesrepr::allocate_buffer(self)?;
buffer.extend(self.header.to_bytes()?);
buffer.extend(self.hash.to_bytes()?);
buffer.extend(self.payment.to_bytes()?);
buffer.extend(self.session.to_bytes()?);
buffer.extend(self.approvals.to_bytes()?);
Ok(buffer)
}
fn serialized_length(&self) -> usize {
self.header.serialized_length()
+ self.hash.serialized_length()
+ self.payment.serialized_length()
+ self.session.serialized_length()
+ self.approvals.serialized_length()
}
}
impl FromBytes for Deploy {
fn from_bytes(bytes: &[u8]) -> Result<(Self, &[u8]), bytesrepr::Error> {
let (header, remainder) = DeployHeader::from_bytes(bytes)?;
let (hash, remainder) = DeployHash::from_bytes(remainder)?;
let (payment, remainder) = ExecutableDeployItem::from_bytes(remainder)?;
let (session, remainder) = ExecutableDeployItem::from_bytes(remainder)?;
let (approvals, remainder) = BTreeSet::<Approval>::from_bytes(remainder)?;
let maybe_valid_deploy = Deploy {
header,
hash,
payment,
session,
approvals,
is_valid: None,
};
Ok((maybe_valid_deploy, remainder))
}
}
#[cfg(test)]
mod tests {
use std::{iter, time::Duration};
use casper_execution_engine::core::engine_state::MAX_PAYMENT_AMOUNT;
use casper_types::{bytesrepr::Bytes, CLValue};
use super::*;
use crate::crypto::AsymmetricKeyExt;
const DEFAULT_MAX_ASSOCIATED_KEYS: u32 = 100;
#[test]
fn json_roundtrip() {
let mut rng = crate::new_rng();
let deploy = Deploy::random(&mut rng);
let json_string = serde_json::to_string_pretty(&deploy).unwrap();
let decoded = serde_json::from_str(&json_string).unwrap();
assert_eq!(deploy, decoded);
}
#[test]
fn bincode_roundtrip() {
let mut rng = crate::new_rng();
let deploy = Deploy::random(&mut rng);
let serialized = bincode::serialize(&deploy).unwrap();
let deserialized = bincode::deserialize(&serialized).unwrap();
assert_eq!(deploy, deserialized);
}
#[test]
fn bytesrepr_roundtrip() {
let mut rng = crate::new_rng();
let hash = DeployHash(rng.gen::<[u8; Digest::LENGTH]>().into());
bytesrepr::test_serialization_roundtrip(&hash);
let deploy = Deploy::random(&mut rng);
bytesrepr::test_serialization_roundtrip(deploy.header());
bytesrepr::test_serialization_roundtrip(&deploy);
}
fn create_deploy(
rng: &mut TestRng,
ttl: TimeDiff,
dependency_count: usize,
chain_name: &str,
) -> Deploy {
let secret_key = SecretKey::random(rng);
let dependencies = iter::repeat_with(|| DeployHash::random(rng))
.take(dependency_count)
.collect();
let transfer_args = {
let mut transfer_args = RuntimeArgs::new();
let value =
CLValue::from_t(U512::from(MAX_PAYMENT_AMOUNT)).expect("should create CLValue");
transfer_args.insert_cl_value(ARG_AMOUNT, value);
transfer_args
};
Deploy::new(
Timestamp::now(),
ttl,
1,
dependencies,
chain_name.to_string(),
ExecutableDeployItem::ModuleBytes {
module_bytes: Bytes::new(),
args: RuntimeArgs::new(),
},
ExecutableDeployItem::Transfer {
args: transfer_args,
},
&secret_key,
None,
)
}
#[test]
fn is_valid() {
let mut rng = crate::new_rng();
let mut deploy = create_deploy(&mut rng, DeployConfig::default().max_ttl, 0, "net-1");
assert_eq!(deploy.is_valid, None, "is valid should initially be None");
deploy.is_valid().expect("should be valid");
assert_eq!(deploy.is_valid, Some(Ok(())), "is valid should be true");
}
fn check_is_not_valid(mut invalid_deploy: Deploy, expected_error: DeployConfigurationFailure) {
assert!(
invalid_deploy.is_valid.is_none(),
"is valid should initially be None"
);
let actual_error = invalid_deploy.is_valid().unwrap_err();
match expected_error {
DeployConfigurationFailure::InvalidApproval {
index: expected_index,
..
} => match actual_error {
DeployConfigurationFailure::InvalidApproval {
index: actual_index,
..
} => {
assert_eq!(actual_index, expected_index);
}
_ => panic!("expected {}, got: {}", expected_error, actual_error),
},
_ => {
assert_eq!(actual_error, expected_error,);
}
}
assert_eq!(
invalid_deploy.is_valid,
Some(Err(actual_error)),
"is valid should now be Some"
);
}
#[test]
fn not_valid_due_to_invalid_body_hash() {
let mut rng = crate::new_rng();
let mut deploy = create_deploy(&mut rng, DeployConfig::default().max_ttl, 0, "net-1");
deploy.session = ExecutableDeployItem::Transfer {
args: runtime_args! {
"amount" => 1
},
};
check_is_not_valid(deploy, DeployConfigurationFailure::InvalidBodyHash);
}
#[test]
fn not_valid_due_to_invalid_deploy_hash() {
let mut rng = crate::new_rng();
let mut deploy = create_deploy(&mut rng, DeployConfig::default().max_ttl, 0, "net-1");
deploy.header.gas_price = 2;
check_is_not_valid(deploy, DeployConfigurationFailure::InvalidDeployHash);
}
#[test]
fn not_valid_due_to_empty_approvals() {
let mut rng = crate::new_rng();
let mut deploy = create_deploy(&mut rng, DeployConfig::default().max_ttl, 0, "net-1");
deploy.approvals = BTreeSet::new();
assert!(deploy.approvals.is_empty());
check_is_not_valid(deploy, DeployConfigurationFailure::EmptyApprovals)
}
#[test]
fn not_valid_due_to_invalid_approval() {
let mut rng = crate::new_rng();
let mut deploy = create_deploy(&mut rng, DeployConfig::default().max_ttl, 0, "net-1");
let deploy2 = Deploy::random(&mut rng);
deploy.approvals.extend(deploy2.approvals.clone());
let expected_index = deploy
.approvals
.iter()
.enumerate()
.find(|(_, approval)| deploy2.approvals.contains(approval))
.map(|(index, _)| index)
.unwrap();
check_is_not_valid(
deploy,
DeployConfigurationFailure::InvalidApproval {
index: expected_index,
error_msg: String::new(), },
);
}
#[test]
fn is_acceptable() {
let mut rng = crate::new_rng();
let chain_name = "net-1";
let deploy_config = DeployConfig::default();
let deploy = create_deploy(
&mut rng,
deploy_config.max_ttl,
deploy_config.max_dependencies.into(),
chain_name,
);
deploy
.is_config_compliant(chain_name, &deploy_config, DEFAULT_MAX_ASSOCIATED_KEYS)
.expect("should be acceptable");
}
#[test]
fn not_acceptable_due_to_invalid_chain_name() {
let mut rng = crate::new_rng();
let expected_chain_name = "net-1";
let wrong_chain_name = "net-2".to_string();
let deploy_config = DeployConfig::default();
let deploy = create_deploy(
&mut rng,
deploy_config.max_ttl,
deploy_config.max_dependencies.into(),
&wrong_chain_name,
);
let expected_error = DeployConfigurationFailure::InvalidChainName {
expected: expected_chain_name.to_string(),
got: wrong_chain_name,
};
assert_eq!(
deploy.is_config_compliant(
expected_chain_name,
&deploy_config,
DEFAULT_MAX_ASSOCIATED_KEYS
),
Err(expected_error)
);
assert!(
deploy.is_valid.is_none(),
"deploy should not have run expensive `is_valid` call"
);
}
#[test]
fn not_acceptable_due_to_excessive_dependencies() {
let mut rng = crate::new_rng();
let chain_name = "net-1";
let deploy_config = DeployConfig::default();
let dependency_count = usize::from(deploy_config.max_dependencies + 1);
let deploy = create_deploy(
&mut rng,
deploy_config.max_ttl,
dependency_count,
chain_name,
);
let expected_error = DeployConfigurationFailure::ExcessiveDependencies {
max_dependencies: deploy_config.max_dependencies,
got: dependency_count,
};
assert_eq!(
deploy.is_config_compliant(chain_name, &deploy_config, DEFAULT_MAX_ASSOCIATED_KEYS),
Err(expected_error)
);
assert!(
deploy.is_valid.is_none(),
"deploy should not have run expensive `is_valid` call"
);
}
#[test]
fn not_acceptable_due_to_excessive_ttl() {
let mut rng = crate::new_rng();
let chain_name = "net-1";
let deploy_config = DeployConfig::default();
let ttl = deploy_config.max_ttl + TimeDiff::from(Duration::from_secs(1));
let deploy = create_deploy(
&mut rng,
ttl,
deploy_config.max_dependencies.into(),
chain_name,
);
let expected_error = DeployConfigurationFailure::ExcessiveTimeToLive {
max_ttl: deploy_config.max_ttl,
got: ttl,
};
assert_eq!(
deploy.is_config_compliant(chain_name, &deploy_config, DEFAULT_MAX_ASSOCIATED_KEYS),
Err(expected_error)
);
assert!(
deploy.is_valid.is_none(),
"deploy should not have run expensive `is_valid` call"
);
}
#[test]
fn not_acceptable_due_to_missing_payment_amount() {
let mut rng = crate::new_rng();
let chain_name = "net-1";
let deploy_config = DeployConfig::default();
let payment = ExecutableDeployItem::ModuleBytes {
module_bytes: Bytes::new(),
args: RuntimeArgs::default(),
};
let session = ExecutableDeployItem::StoredContractByName {
name: "".to_string(),
entry_point: "".to_string(),
args: Default::default(),
};
let mut deploy = create_deploy(
&mut rng,
deploy_config.max_ttl,
deploy_config.max_dependencies.into(),
chain_name,
);
deploy.payment = payment;
deploy.session = session;
assert_eq!(
deploy.is_config_compliant(chain_name, &deploy_config, DEFAULT_MAX_ASSOCIATED_KEYS),
Err(DeployConfigurationFailure::MissingPaymentAmount)
);
assert!(
deploy.is_valid.is_none(),
"deploy should not have run expensive `is_valid` call"
);
}
#[test]
fn not_acceptable_due_to_mangled_payment_amount() {
let mut rng = crate::new_rng();
let chain_name = "net-1";
let deploy_config = DeployConfig::default();
let payment = ExecutableDeployItem::ModuleBytes {
module_bytes: Bytes::new(),
args: runtime_args! {
"amount" => "mangled-amount"
},
};
let session = ExecutableDeployItem::StoredContractByName {
name: "".to_string(),
entry_point: "".to_string(),
args: Default::default(),
};
let mut deploy = create_deploy(
&mut rng,
deploy_config.max_ttl,
deploy_config.max_dependencies.into(),
chain_name,
);
deploy.payment = payment;
deploy.session = session;
assert_eq!(
deploy.is_config_compliant(chain_name, &deploy_config, DEFAULT_MAX_ASSOCIATED_KEYS),
Err(DeployConfigurationFailure::FailedToParsePaymentAmount)
);
assert!(
deploy.is_valid.is_none(),
"deploy should not have run expensive `is_valid` call"
);
}
#[test]
fn not_acceptable_due_to_excessive_payment_amount() {
let mut rng = crate::new_rng();
let chain_name = "net-1";
let deploy_config = DeployConfig::default();
let amount = U512::from(deploy_config.block_gas_limit + 1);
let payment = ExecutableDeployItem::ModuleBytes {
module_bytes: Bytes::new(),
args: runtime_args! {
"amount" => amount
},
};
let session = ExecutableDeployItem::StoredContractByName {
name: "".to_string(),
entry_point: "".to_string(),
args: Default::default(),
};
let mut deploy = create_deploy(
&mut rng,
deploy_config.max_ttl,
deploy_config.max_dependencies.into(),
chain_name,
);
deploy.payment = payment;
deploy.session = session;
let expected_error = DeployConfigurationFailure::ExceededBlockGasLimit {
block_gas_limit: deploy_config.block_gas_limit,
got: amount,
};
assert_eq!(
deploy.is_config_compliant(chain_name, &deploy_config, DEFAULT_MAX_ASSOCIATED_KEYS),
Err(expected_error)
);
assert!(
deploy.is_valid.is_none(),
"deploy should not have run expensive `is_valid` call"
);
}
#[test]
fn transfer_acceptable_regardless_of_excessive_payment_amount() {
let mut rng = crate::new_rng();
let secret_key = SecretKey::random(&mut rng);
let chain_name = "net-1";
let deploy_config = DeployConfig::default();
let amount = U512::from(deploy_config.block_gas_limit + 1);
let payment = ExecutableDeployItem::ModuleBytes {
module_bytes: Bytes::new(),
args: runtime_args! {
"amount" => amount
},
};
let transfer_args = {
let mut transfer_args = RuntimeArgs::new();
let value =
CLValue::from_t(U512::from(MAX_PAYMENT_AMOUNT)).expect("should create CLValue");
transfer_args.insert_cl_value(ARG_AMOUNT, value);
transfer_args
};
let deploy = Deploy::new(
Timestamp::now(),
deploy_config.max_ttl,
1,
vec![],
chain_name.to_string(),
payment,
ExecutableDeployItem::Transfer {
args: transfer_args,
},
&secret_key,
None,
);
assert_eq!(
Ok(()),
deploy.is_config_compliant(chain_name, &deploy_config, DEFAULT_MAX_ASSOCIATED_KEYS)
)
}
#[test]
fn not_acceptable_due_to_excessive_approvals() {
let mut rng = crate::new_rng();
let chain_name = "net-1";
let deploy_config = DeployConfig::default();
let deploy = create_deploy(
&mut rng,
deploy_config.max_ttl,
deploy_config.max_dependencies as usize,
chain_name,
);
let max_associated_keys = (deploy.approvals.len() - 1) as u32;
assert_eq!(
Err(DeployConfigurationFailure::ExcessiveApprovals {
got: deploy.approvals.len() as u32,
max_associated_keys: (deploy.approvals.len() - 1) as u32
}),
deploy.is_config_compliant(chain_name, &deploy_config, max_associated_keys)
)
}
#[test]
fn not_acceptable_due_to_missing_transfer_amount() {
let mut rng = crate::new_rng();
let chain_name = "net-1";
let deploy_config = DeployConfig::default();
let mut deploy = create_deploy(
&mut rng,
deploy_config.max_ttl,
deploy_config.max_dependencies as usize,
chain_name,
);
let transfer_args = RuntimeArgs::default();
let session = ExecutableDeployItem::Transfer {
args: transfer_args,
};
deploy.session = session;
assert_eq!(
Err(DeployConfigurationFailure::MissingTransferAmount),
deploy.is_config_compliant(chain_name, &deploy_config, DEFAULT_MAX_ASSOCIATED_KEYS)
)
}
#[test]
fn not_acceptable_due_to_mangled_transfer_amount() {
let mut rng = crate::new_rng();
let chain_name = "net-1";
let deploy_config = DeployConfig::default();
let mut deploy = create_deploy(
&mut rng,
deploy_config.max_ttl,
deploy_config.max_dependencies as usize,
chain_name,
);
let transfer_args = runtime_args! {
"amount" => "mangled-amount",
"source" => PublicKey::random(&mut rng).to_account_hash(),
"target" => PublicKey::random(&mut rng).to_account_hash(),
};
let session = ExecutableDeployItem::Transfer {
args: transfer_args,
};
deploy.session = session;
assert_eq!(
Err(DeployConfigurationFailure::FailedToParseTransferAmount),
deploy.is_config_compliant(chain_name, &deploy_config, DEFAULT_MAX_ASSOCIATED_KEYS)
)
}
#[test]
fn not_acceptable_due_to_insufficient_transfer_amount() {
let mut rng = crate::new_rng();
let chain_name = "net-1";
let deploy_config = DeployConfig::default();
let mut deploy = create_deploy(
&mut rng,
deploy_config.max_ttl,
deploy_config.max_dependencies as usize,
chain_name,
);
let amount = deploy_config.native_transfer_minimum_motes - 1;
let insufficient_amount = U512::from(amount);
let transfer_args = runtime_args! {
"amount" => insufficient_amount,
"source" => PublicKey::random(&mut rng).to_account_hash(),
"target" => PublicKey::random(&mut rng).to_account_hash(),
};
let session = ExecutableDeployItem::Transfer {
args: transfer_args,
};
deploy.session = session;
assert_eq!(
Err(DeployConfigurationFailure::InsufficientTransferAmount {
minimum: U512::from(deploy_config.native_transfer_minimum_motes),
attempted: insufficient_amount,
}),
deploy.is_config_compliant(chain_name, &deploy_config, DEFAULT_MAX_ASSOCIATED_KEYS)
)
}
}