casper_client/cli/
deploy_builder.rs

1mod error;
2
3use casper_types::{
4    bytesrepr::ToBytes, Deploy, DeployHash, DeployHeader, Digest, ExecutableDeployItem,
5    InitiatorAddr, PublicKey, SecretKey, TimeDiff, Timestamp, TransferTarget, URef, U512,
6};
7pub use error::DeployBuilderError;
8use itertools::Itertools;
9
10use crate::types::InitiatorAddrAndSecretKey;
11
12/// A builder for constructing a [`Deploy`].
13pub struct DeployBuilder<'a> {
14    account: Option<PublicKey>,
15    secret_key: Option<&'a SecretKey>,
16    timestamp: Timestamp,
17    ttl: TimeDiff,
18    gas_price: u64,
19    dependencies: Vec<DeployHash>,
20    chain_name: String,
21    payment: Option<ExecutableDeployItem>,
22    session: ExecutableDeployItem,
23}
24
25impl<'a> DeployBuilder<'a> {
26    /// The default time-to-live for `Deploy`s, i.e. 30 minutes.
27    pub const DEFAULT_TTL: TimeDiff = TimeDiff::from_millis(30 * 60 * 1_000);
28    /// The default gas price for `Deploy`s, i.e. `1`.
29    pub const DEFAULT_GAS_PRICE: u64 = 1;
30
31    /// Returns a new `DeployBuilder`.
32    ///
33    /// # Note
34    ///
35    /// Before calling [`build`](Self::build), you must ensure
36    ///   * that an account is provided by either calling [`with_account`](Self::with_account) or
37    ///     [`with_secret_key`](Self::with_secret_key)
38    ///   * that payment code is provided by calling
39    ///     [`with_payment`](Self::with_payment)
40    pub fn new<C: Into<String>>(chain_name: C, session: ExecutableDeployItem) -> Self {
41        #[cfg(any(feature = "std-fs-io", test))]
42        let timestamp = Timestamp::now();
43        #[cfg(not(any(feature = "std-fs-io", test)))]
44        let timestamp = Timestamp::zero();
45
46        DeployBuilder {
47            account: None,
48            secret_key: None,
49            timestamp,
50            ttl: Self::DEFAULT_TTL,
51            gas_price: Self::DEFAULT_GAS_PRICE,
52            dependencies: vec![],
53            chain_name: chain_name.into(),
54            payment: None,
55            session,
56        }
57    }
58
59    /// Returns a new `DeployBuilder` with session code suitable for a transfer.
60    ///
61    /// If `maybe_source` is None, the account's main purse is used as the source of the transfer.
62    ///
63    /// # Note
64    ///
65    /// Before calling [`build`](Self::build), you must ensure
66    ///   * that an account is provided by either calling [`with_account`](Self::with_account) or
67    ///     [`with_secret_key`](Self::with_secret_key)
68    ///   * that payment code is provided by calling
69    ///     [`with_payment`](Self::with_payment)
70    pub fn new_transfer<C: Into<String>, A: Into<U512>, T: Into<TransferTarget>>(
71        chain_name: C,
72        amount: A,
73        maybe_source: Option<URef>,
74        target: T,
75        maybe_transfer_id: Option<u64>,
76    ) -> Self {
77        let session =
78            ExecutableDeployItem::new_transfer(amount, maybe_source, target, maybe_transfer_id);
79        DeployBuilder::new(chain_name, session)
80    }
81
82    /// Sets the `account` in the `Deploy`.
83    ///
84    /// If not provided, the public key derived from the secret key used in the `DeployBuilder` will
85    /// be used as the `account` in the `Deploy`.
86    pub fn with_account(mut self, account: PublicKey) -> Self {
87        self.account = Some(account);
88        self
89    }
90
91    /// Sets the gas price in the `Deploy` to the provided amount.
92    ///
93    /// If not provided, the `Deploy` will use `DEFAULT_GAS_PRICE` (1) as the gas price for the
94    /// `deploy`
95    pub fn with_gas_price(mut self, gas_price: u64) -> Self {
96        self.gas_price = gas_price;
97        self
98    }
99
100    /// Sets the secret key used to sign the `Deploy` on calling [`build`](Self::build).
101    ///
102    /// If not provided, the `Deploy` can still be built, but will be unsigned and will be invalid
103    /// until subsequently signed.
104    pub fn with_secret_key(mut self, secret_key: &'a SecretKey) -> Self {
105        self.secret_key = Some(secret_key);
106        self
107    }
108
109    /// Sets the `payment` in the `Deploy`.
110    pub fn with_payment(mut self, payment: ExecutableDeployItem) -> Self {
111        self.payment = Some(payment);
112        self
113    }
114
115    /// Sets the `timestamp` in the `Deploy`.
116    ///
117    /// If not provided, the timestamp will be set to the time when the `DeployBuilder` was
118    /// constructed.
119    pub fn with_timestamp(mut self, timestamp: Timestamp) -> Self {
120        self.timestamp = timestamp;
121        self
122    }
123
124    /// Sets the `ttl` (time-to-live) in the `Deploy`.
125    ///
126    /// If not provided, the ttl will be set to [`Self::DEFAULT_TTL`].
127    pub fn with_ttl(mut self, ttl: TimeDiff) -> Self {
128        self.ttl = ttl;
129        self
130    }
131
132    #[allow(clippy::too_many_arguments)]
133    fn build_deploy_inner(
134        timestamp: Timestamp,
135        ttl: TimeDiff,
136        gas_price: u64,
137        dependencies: Vec<DeployHash>,
138        chain_name: String,
139        payment: ExecutableDeployItem,
140        session: ExecutableDeployItem,
141        initiator_addr_and_secret_key: InitiatorAddrAndSecretKey,
142    ) -> Deploy {
143        let serialized_body = serialize_body(&payment, &session);
144        let body_hash = Digest::hash(serialized_body);
145
146        let account = match initiator_addr_and_secret_key.initiator_addr() {
147            InitiatorAddr::PublicKey(public_key) => public_key,
148            InitiatorAddr::AccountHash(_) => unreachable!(),
149        };
150
151        let dependencies = dependencies.into_iter().unique().collect();
152        let header = DeployHeader::new(
153            account,
154            timestamp,
155            ttl,
156            gas_price,
157            body_hash,
158            dependencies,
159            chain_name,
160        );
161        let serialized_header = serialize_header(&header);
162        let hash = DeployHash::new(Digest::hash(serialized_header));
163
164        let mut deploy = Deploy::new(hash, header, payment, session);
165
166        if let Some(secret_key) = initiator_addr_and_secret_key.secret_key() {
167            deploy.sign(secret_key);
168        }
169        deploy
170    }
171
172    /// Returns the new `Deploy`, or an error if
173    /// [`with_payment`](Self::with_payment) wasn't previously called.
174    pub fn build(self) -> Result<Deploy, DeployBuilderError> {
175        let initiator_addr_and_secret_key = match (self.account, self.secret_key) {
176            (Some(account), Some(secret_key)) => InitiatorAddrAndSecretKey::Both {
177                initiator_addr: InitiatorAddr::PublicKey(account),
178                secret_key,
179            },
180            (Some(account), None) => {
181                InitiatorAddrAndSecretKey::InitiatorAddr(InitiatorAddr::PublicKey(account))
182            }
183            (None, Some(secret_key)) => InitiatorAddrAndSecretKey::SecretKey(secret_key),
184            (None, None) => return Err(DeployBuilderError::DeployMissingSessionAccount),
185        };
186
187        let payment = self
188            .payment
189            .ok_or(DeployBuilderError::DeployMissingPaymentCode)?;
190        let deploy = Self::build_deploy_inner(
191            self.timestamp,
192            self.ttl,
193            self.gas_price,
194            self.dependencies,
195            self.chain_name,
196            payment,
197            self.session,
198            initiator_addr_and_secret_key,
199        );
200        Ok(deploy)
201    }
202}
203
204fn serialize_header(header: &DeployHeader) -> Vec<u8> {
205    header
206        .to_bytes()
207        .unwrap_or_else(|error| panic!("should serialize deploy header: {}", error))
208}
209
210fn serialize_body(payment: &ExecutableDeployItem, session: &ExecutableDeployItem) -> Vec<u8> {
211    let mut buffer = Vec::with_capacity(payment.serialized_length() + session.serialized_length());
212    payment
213        .write_bytes(&mut buffer)
214        .unwrap_or_else(|error| panic!("should serialize payment code: {}", error));
215    session
216        .write_bytes(&mut buffer)
217        .unwrap_or_else(|error| panic!("should serialize session code: {}", error));
218    buffer
219}