pub mod error;
use super::arg_handling;
use crate::{
cli::{FieldsContainer, FieldsContainerError},
types::InitiatorAddrAndSecretKey,
};
use alloc::collections::BTreeMap;
use alloc::collections::BTreeSet;
use alloc::vec::Vec;
use casper_types::contracts::ProtocolVersionMajor;
use casper_types::{
bytesrepr::{Bytes, ToBytes},
system::auction::{DelegatorKind, Reservation},
AddressableEntityHash, CLValueError, Digest, EntityVersion, InitiatorAddr, PackageHash,
PricingMode, PublicKey, RuntimeArgs, SecretKey, TimeDiff, Timestamp, TransactionArgs,
TransactionEntryPoint, TransactionInvocationTarget, TransactionRuntimeParams,
TransactionScheduling, TransactionTarget, TransactionV1, TransactionV1Payload, TransferTarget,
URef, U512,
};
use core::marker::PhantomData;
pub use error::TransactionV1BuilderError;
#[derive(Debug)]
pub struct TransactionV1Builder<'a> {
args: TransactionArgs,
target: TransactionTarget,
scheduling: TransactionScheduling,
entry_point: TransactionEntryPoint,
chain_name: Option<String>,
timestamp: Timestamp,
ttl: TimeDiff,
pricing_mode: PricingMode,
initiator_addr: Option<InitiatorAddr>,
#[cfg(not(test))]
secret_key: Option<&'a SecretKey>,
#[cfg(test)]
secret_key: Option<SecretKey>,
_phantom_data: PhantomData<&'a ()>,
}
impl<'a> TransactionV1Builder<'a> {
pub const DEFAULT_TTL: TimeDiff = TimeDiff::from_millis(30 * 60 * 1_000);
pub const DEFAULT_PRICING_MODE: PricingMode = PricingMode::Fixed {
gas_price_tolerance: 5,
additional_computation_factor: 0,
};
pub const DEFAULT_SCHEDULING: TransactionScheduling = TransactionScheduling::Standard;
pub(crate) fn new() -> Self {
#[cfg(any(feature = "std-fs-io", test))]
let timestamp = Timestamp::now();
#[cfg(not(any(feature = "std-fs-io", test)))]
let timestamp = Timestamp::zero();
TransactionV1Builder {
args: TransactionArgs::Named(RuntimeArgs::new()),
entry_point: TransactionEntryPoint::Transfer,
target: TransactionTarget::Native,
scheduling: TransactionScheduling::Standard,
chain_name: None,
timestamp,
ttl: Self::DEFAULT_TTL,
pricing_mode: Self::DEFAULT_PRICING_MODE,
initiator_addr: None,
secret_key: None,
_phantom_data: PhantomData,
}
}
pub fn new_transfer<A: Into<U512>, T: Into<TransferTarget>>(
amount: A,
maybe_source: Option<URef>,
target: T,
maybe_id: Option<u64>,
) -> Result<Self, CLValueError> {
let args = arg_handling::new_transfer_args(amount, maybe_source, target, maybe_id)?;
let mut builder = TransactionV1Builder::new();
builder.args = TransactionArgs::Named(args);
builder.target = TransactionTarget::Native;
builder.entry_point = TransactionEntryPoint::Transfer;
builder.scheduling = Self::DEFAULT_SCHEDULING;
Ok(builder)
}
pub fn new_add_bid<A: Into<U512>>(
public_key: PublicKey,
delegation_rate: u8,
amount: A,
minimum_delegation_amount: Option<u64>,
maximum_delegation_amount: Option<u64>,
reserved_slots: Option<u32>,
) -> Result<Self, CLValueError> {
let args = arg_handling::new_add_bid_args(
public_key,
delegation_rate,
amount,
minimum_delegation_amount,
maximum_delegation_amount,
reserved_slots,
)?;
let mut builder = TransactionV1Builder::new();
builder.args = TransactionArgs::Named(args);
builder.target = TransactionTarget::Native;
builder.entry_point = TransactionEntryPoint::AddBid;
builder.scheduling = Self::DEFAULT_SCHEDULING;
Ok(builder)
}
pub fn new_withdraw_bid<A: Into<U512>>(
public_key: PublicKey,
amount: A,
) -> Result<Self, CLValueError> {
let args = arg_handling::new_withdraw_bid_args(public_key, amount)?;
let mut builder = TransactionV1Builder::new();
builder.args = TransactionArgs::Named(args);
builder.target = TransactionTarget::Native;
builder.entry_point = TransactionEntryPoint::WithdrawBid;
builder.scheduling = Self::DEFAULT_SCHEDULING;
Ok(builder)
}
pub fn new_delegate<A: Into<U512>>(
delegator: PublicKey,
validator: PublicKey,
amount: A,
) -> Result<Self, CLValueError> {
let args = arg_handling::new_delegate_args(delegator, validator, amount)?;
let mut builder = TransactionV1Builder::new();
builder.args = TransactionArgs::Named(args);
builder.target = TransactionTarget::Native;
builder.entry_point = TransactionEntryPoint::Delegate;
builder.scheduling = Self::DEFAULT_SCHEDULING;
Ok(builder)
}
pub fn new_undelegate<A: Into<U512>>(
delegator: PublicKey,
validator: PublicKey,
amount: A,
) -> Result<Self, CLValueError> {
let args = arg_handling::new_undelegate_args(delegator, validator, amount)?;
let mut builder = TransactionV1Builder::new();
builder.args = TransactionArgs::Named(args);
builder.target = TransactionTarget::Native;
builder.entry_point = TransactionEntryPoint::Undelegate;
builder.scheduling = Self::DEFAULT_SCHEDULING;
Ok(builder)
}
pub fn new_redelegate<A: Into<U512>>(
delegator: PublicKey,
validator: PublicKey,
amount: A,
new_validator: PublicKey,
) -> Result<Self, CLValueError> {
let args = arg_handling::new_redelegate_args(delegator, validator, amount, new_validator)?;
let mut builder = TransactionV1Builder::new();
builder.args = TransactionArgs::Named(args);
builder.target = TransactionTarget::Native;
builder.entry_point = TransactionEntryPoint::Redelegate;
builder.scheduling = Self::DEFAULT_SCHEDULING;
Ok(builder)
}
pub fn new_activate_bid(validator: PublicKey) -> Result<Self, CLValueError> {
let args = arg_handling::new_activate_bid_args(validator)?;
let mut builder = TransactionV1Builder::new();
builder.args = TransactionArgs::Named(args);
builder.target = TransactionTarget::Native;
builder.entry_point = TransactionEntryPoint::ActivateBid;
builder.scheduling = Self::DEFAULT_SCHEDULING;
Ok(builder)
}
pub fn new_change_bid_public_key(
public_key: PublicKey,
new_public_key: PublicKey,
) -> Result<Self, CLValueError> {
let args = arg_handling::new_change_bid_public_key_args(public_key, new_public_key)?;
let mut builder = TransactionV1Builder::new();
builder.args = TransactionArgs::Named(args);
builder.target = TransactionTarget::Native;
builder.entry_point = TransactionEntryPoint::ChangeBidPublicKey;
builder.scheduling = Self::DEFAULT_SCHEDULING;
Ok(builder)
}
pub fn new_add_reservations(reservations: Vec<Reservation>) -> Result<Self, CLValueError> {
let args = arg_handling::new_add_reservations_args(reservations)?;
let mut builder = TransactionV1Builder::new();
builder.args = TransactionArgs::Named(args);
builder.target = TransactionTarget::Native;
builder.entry_point = TransactionEntryPoint::AddReservations;
builder.scheduling = Self::DEFAULT_SCHEDULING;
Ok(builder)
}
pub fn new_cancel_reservations(
validator: PublicKey,
delegators: Vec<DelegatorKind>,
) -> Result<Self, CLValueError> {
let args = arg_handling::new_cancel_reservations_args(validator, delegators)?;
let mut builder = TransactionV1Builder::new();
builder.args = TransactionArgs::Named(args);
builder.target = TransactionTarget::Native;
builder.entry_point = TransactionEntryPoint::CancelReservations;
builder.scheduling = Self::DEFAULT_SCHEDULING;
Ok(builder)
}
fn new_targeting_stored<E: Into<String>>(
id: TransactionInvocationTarget,
entry_point: E,
runtime: TransactionRuntimeParams,
) -> Self {
let target = TransactionTarget::Stored { id, runtime };
let mut builder = TransactionV1Builder::new();
builder.args = TransactionArgs::Named(RuntimeArgs::new());
builder.target = target;
builder.entry_point = TransactionEntryPoint::Custom(entry_point.into());
builder.scheduling = Self::DEFAULT_SCHEDULING;
builder
}
pub fn new_targeting_invocable_entity<E: Into<String>>(
hash: AddressableEntityHash,
entry_point: E,
runtime: TransactionRuntimeParams,
) -> Self {
let id = TransactionInvocationTarget::new_invocable_entity(hash);
Self::new_targeting_stored(id, entry_point, runtime)
}
pub fn new_targeting_invocable_entity_via_alias<A: Into<String>, E: Into<String>>(
alias: A,
entry_point: E,
runtime: TransactionRuntimeParams,
) -> Self {
let id = TransactionInvocationTarget::new_invocable_entity_alias(alias.into());
Self::new_targeting_stored(id, entry_point, runtime)
}
pub fn new_targeting_package<E: Into<String>>(
hash: PackageHash,
version: Option<EntityVersion>,
entry_point: E,
runtime: TransactionRuntimeParams,
) -> Self {
#[allow(deprecated)]
let id = TransactionInvocationTarget::new_package(hash, version);
Self::new_targeting_stored(id, entry_point, runtime)
}
pub fn new_targeting_package_with_version_key<E: Into<String>>(
hash: PackageHash,
version: Option<EntityVersion>,
protocol_version_major: Option<ProtocolVersionMajor>,
entry_point: E,
runtime: TransactionRuntimeParams,
) -> Self {
let id = TransactionInvocationTarget::new_package_with_major(
hash,
version,
protocol_version_major,
);
Self::new_targeting_stored(id, entry_point, runtime)
}
pub fn new_targeting_package_via_alias<A: Into<String>, E: Into<String>>(
alias: A,
version: Option<EntityVersion>,
entry_point: E,
runtime: TransactionRuntimeParams,
) -> Self {
#[allow(deprecated)]
let id = TransactionInvocationTarget::new_package_alias(alias.into(), version);
Self::new_targeting_stored(id, entry_point, runtime)
}
pub fn new_targeting_package_via_alias_with_version_key<A: Into<String>, E: Into<String>>(
alias: A,
version: Option<EntityVersion>,
protocol_version_major: Option<ProtocolVersionMajor>,
entry_point: E,
runtime: TransactionRuntimeParams,
) -> Self {
let id = TransactionInvocationTarget::new_package_alias_with_major(
alias.into(),
version,
protocol_version_major,
);
Self::new_targeting_stored(id, entry_point, runtime)
}
pub fn new_session(
is_install_upgrade: bool,
module_bytes: Bytes,
runtime: TransactionRuntimeParams,
) -> Self {
let target = TransactionTarget::Session {
is_install_upgrade,
module_bytes,
runtime,
};
let mut builder = TransactionV1Builder::new();
builder.args = TransactionArgs::Named(RuntimeArgs::new());
builder.target = target;
builder.entry_point = TransactionEntryPoint::Call;
builder.scheduling = Self::DEFAULT_SCHEDULING;
builder
}
pub fn with_chain_name<C: Into<String>>(mut self, chain_name: C) -> Self {
self.chain_name = Some(chain_name.into());
self
}
pub fn with_timestamp(mut self, timestamp: Timestamp) -> Self {
self.timestamp = timestamp;
self
}
pub fn with_ttl(mut self, ttl: TimeDiff) -> Self {
self.ttl = ttl;
self
}
pub fn with_pricing_mode(mut self, pricing_mode: PricingMode) -> Self {
self.pricing_mode = pricing_mode;
self
}
pub fn with_initiator_addr<I: Into<InitiatorAddr>>(mut self, initiator_addr: I) -> Self {
self.initiator_addr = Some(initiator_addr.into());
self
}
pub fn with_secret_key(mut self, secret_key: &'a SecretKey) -> Self {
#[cfg(not(test))]
{
self.secret_key = Some(secret_key);
}
#[cfg(test)]
{
self.secret_key = Some(
SecretKey::from_der(secret_key.to_der().expect("should der-encode"))
.expect("should der-decode"),
);
}
self
}
pub fn with_runtime_args(mut self, args: RuntimeArgs) -> Self {
self.args = TransactionArgs::Named(args);
self
}
pub fn with_chunked_args(mut self, args: Bytes) -> Self {
self.args = TransactionArgs::Bytesrepr(args);
self
}
pub fn with_entry_point(mut self, entry_point: TransactionEntryPoint) -> Self {
self.entry_point = entry_point;
self
}
pub fn build(self) -> Result<TransactionV1, TransactionV1BuilderError> {
self.do_build()
}
fn build_transaction_inner(
chain_name: String,
timestamp: Timestamp,
ttl: TimeDiff,
pricing_mode: PricingMode,
fields: BTreeMap<u16, Bytes>,
initiator_addr_and_secret_key: InitiatorAddrAndSecretKey,
) -> TransactionV1 {
let initiator_addr = initiator_addr_and_secret_key.initiator_addr();
let transaction_v1_payload = TransactionV1Payload::new(
chain_name,
timestamp,
ttl,
pricing_mode,
initiator_addr,
fields,
);
let hash = Digest::hash(
transaction_v1_payload
.to_bytes()
.unwrap_or_else(|error| panic!("should serialize body: {}", error)),
);
let mut transaction =
TransactionV1::new(hash.into(), transaction_v1_payload, BTreeSet::new());
if let Some(secret_key) = initiator_addr_and_secret_key.secret_key() {
transaction.sign(secret_key);
}
transaction
}
fn do_build(self) -> Result<TransactionV1, TransactionV1BuilderError> {
let initiator_addr_and_secret_key = match (self.initiator_addr, &self.secret_key) {
(Some(initiator_addr), Some(secret_key)) => InitiatorAddrAndSecretKey::Both {
initiator_addr,
secret_key,
},
(Some(initiator_addr), None) => {
InitiatorAddrAndSecretKey::InitiatorAddr(initiator_addr)
}
(None, Some(secret_key)) => InitiatorAddrAndSecretKey::SecretKey(secret_key),
(None, None) => return Err(TransactionV1BuilderError::MissingInitiatorAddr),
};
let chain_name = self
.chain_name
.ok_or(TransactionV1BuilderError::MissingChainName)?;
let container =
FieldsContainer::new(self.args, self.target, self.entry_point, self.scheduling)
.to_map()
.map_err(|err| match err {
FieldsContainerError::CouldNotSerializeField { field_index } => {
TransactionV1BuilderError::CouldNotSerializeField { field_index }
}
})?;
let transaction = Self::build_transaction_inner(
chain_name,
self.timestamp,
self.ttl,
self.pricing_mode,
container,
initiator_addr_and_secret_key,
);
Ok(transaction)
}
}