casper_client/types/
deploy.rs

1use std::fmt::{self, Display, Formatter};
2
3use itertools::Itertools;
4use serde::{Deserialize, Serialize};
5
6use casper_hashing::Digest;
7use casper_types::{
8    bytesrepr::{self, ToBytes},
9    crypto, PublicKey, SecretKey, Signature, URef, U512,
10};
11
12use crate::{
13    types::{ExecutableDeployItem, TimeDiff, Timestamp},
14    Error, TransferTarget,
15};
16
17/// The maximum permissible size in bytes of a Deploy when serialized via `ToBytes`.
18///
19/// Note: this should be kept in sync with the value of `[deploys.max_deploy_size]` in the
20/// production chainspec.
21pub const MAX_SERIALIZED_SIZE_OF_DEPLOY: u32 = 1_024 * 1_024;
22
23/// A cryptographic hash uniquely identifying a [`Deploy`].
24#[derive(
25    Copy, Clone, Default, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize, Deserialize, Debug,
26)]
27#[serde(deny_unknown_fields)]
28pub struct DeployHash(Digest);
29
30impl DeployHash {
31    /// Returns a new `DeployHash`.
32    pub fn new(digest: Digest) -> Self {
33        DeployHash(digest)
34    }
35
36    /// Returns a copy of the wrapped `Digest`.
37    pub fn inner(&self) -> Digest {
38        self.0
39    }
40}
41
42impl Display for DeployHash {
43    fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
44        write!(formatter, "{}", self.0)
45    }
46}
47
48impl ToBytes for DeployHash {
49    fn write_bytes(&self, buffer: &mut Vec<u8>) -> Result<(), bytesrepr::Error> {
50        self.0.write_bytes(buffer)
51    }
52
53    fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
54        self.0.to_bytes()
55    }
56
57    fn serialized_length(&self) -> usize {
58        self.0.serialized_length()
59    }
60}
61
62/// The header portion of a [`Deploy`].
63#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize, Deserialize, Debug)]
64#[serde(deny_unknown_fields)]
65pub struct DeployHeader {
66    account: PublicKey,
67    timestamp: Timestamp,
68    ttl: TimeDiff,
69    gas_price: u64,
70    body_hash: Digest,
71    dependencies: Vec<DeployHash>,
72    chain_name: String,
73}
74
75impl DeployHeader {
76    /// Returns the account within which the deploy will be run.
77    pub fn account(&self) -> &PublicKey {
78        &self.account
79    }
80
81    /// Returns the deploy creation timestamp.
82    pub fn timestamp(&self) -> Timestamp {
83        self.timestamp
84    }
85
86    /// Returns the duration for which the deploy will stay valid.
87    pub fn ttl(&self) -> TimeDiff {
88        self.ttl
89    }
90
91    /// Returns the price per gas unit for this deploy.
92    pub fn gas_price(&self) -> u64 {
93        self.gas_price
94    }
95
96    /// Returns the hash of the body of this deploy.
97    pub fn body_hash(&self) -> Digest {
98        self.body_hash
99    }
100
101    /// Returns the list of other deploys that have to be run before this one.
102    pub fn dependencies(&self) -> impl Iterator<Item = &DeployHash> {
103        self.dependencies.iter()
104    }
105
106    /// Returns the chain name of the network the deploy is supposed to be run on.
107    pub fn chain_name(&self) -> &str {
108        &self.chain_name
109    }
110}
111
112impl Display for DeployHeader {
113    fn fmt(&self, formatter: &mut Formatter) -> fmt::Result {
114        write!(
115            formatter,
116            "deploy header {{ account {}, timestamp {}, ttl {}, body hash {}, chain name {} }}",
117            self.account, self.timestamp, self.ttl, self.body_hash, self.chain_name,
118        )
119    }
120}
121
122impl ToBytes for DeployHeader {
123    fn write_bytes(&self, buffer: &mut Vec<u8>) -> Result<(), bytesrepr::Error> {
124        self.account.write_bytes(buffer)?;
125        self.timestamp.write_bytes(buffer)?;
126        self.ttl.write_bytes(buffer)?;
127        self.gas_price.write_bytes(buffer)?;
128        self.body_hash.write_bytes(buffer)?;
129        self.dependencies.write_bytes(buffer)?;
130        self.chain_name.write_bytes(buffer)
131    }
132
133    fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
134        let mut buffer = vec![];
135        self.write_bytes(&mut buffer)?;
136        Ok(buffer)
137    }
138
139    fn serialized_length(&self) -> usize {
140        self.account.serialized_length()
141            + self.timestamp.serialized_length()
142            + self.ttl.serialized_length()
143            + self.gas_price.serialized_length()
144            + self.body_hash.serialized_length()
145            + self.dependencies.serialized_length()
146            + self.chain_name.serialized_length()
147    }
148}
149
150/// The signature of a deploy and the public key of the signer.
151#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize, Deserialize, Debug)]
152#[serde(deny_unknown_fields)]
153pub struct Approval {
154    signer: PublicKey,
155    signature: Signature,
156}
157
158impl Approval {
159    /// Returns the public key.
160    pub fn signer(&self) -> &PublicKey {
161        &self.signer
162    }
163
164    /// Returns the signature.
165    pub fn signature(&self) -> &Signature {
166        &self.signature
167    }
168}
169
170impl ToBytes for Approval {
171    fn write_bytes(&self, buffer: &mut Vec<u8>) -> Result<(), bytesrepr::Error> {
172        self.signer.write_bytes(buffer)?;
173        self.signature.write_bytes(buffer)
174    }
175
176    fn to_bytes(&self) -> Result<Vec<u8>, bytesrepr::Error> {
177        let mut buffer = vec![];
178        self.write_bytes(&mut buffer)?;
179        Ok(buffer)
180    }
181
182    fn serialized_length(&self) -> usize {
183        self.signer.serialized_length() + self.signature.serialized_length()
184    }
185}
186
187/// A signed item sent to the network used to request execution of Wasm.
188///
189/// Note that constructing a `Deploy` is done via the [`DeployBuilder`].
190#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Serialize, Deserialize, Debug)]
191#[serde(deny_unknown_fields)]
192pub struct Deploy {
193    hash: DeployHash,
194    header: DeployHeader,
195    payment: ExecutableDeployItem,
196    session: ExecutableDeployItem,
197    approvals: Vec<Approval>,
198}
199
200impl Deploy {
201    /// The default time-to-live for `Deploy`s, i.e. 30 minutes.
202    pub const DEFAULT_TTL: TimeDiff = TimeDiff::from_millis(30 * 60 * 1_000);
203    /// The default gas price for `Deploy`s, i.e. `1`.
204    pub const DEFAULT_GAS_PRICE: u64 = 1;
205
206    /// Constructs a new signed `Deploy`.
207    #[allow(clippy::too_many_arguments)]
208    fn new(
209        timestamp: Timestamp,
210        ttl: TimeDiff,
211        gas_price: u64,
212        dependencies: Vec<DeployHash>,
213        chain_name: String,
214        payment: ExecutableDeployItem,
215        session: ExecutableDeployItem,
216        secret_key: &SecretKey,
217        account: Option<PublicKey>,
218    ) -> Deploy {
219        let serialized_body = serialize_body(&payment, &session);
220        let body_hash = Digest::hash(serialized_body);
221
222        let account = account.unwrap_or_else(|| PublicKey::from(secret_key));
223
224        // Remove duplicates.
225        let dependencies = dependencies.into_iter().unique().collect();
226        let header = DeployHeader {
227            account,
228            timestamp,
229            ttl,
230            gas_price,
231            body_hash,
232            dependencies,
233            chain_name,
234        };
235        let serialized_header = header
236            .to_bytes()
237            .unwrap_or_else(|error| panic!("should serialize deploy header: {}", error));
238        let hash = DeployHash(Digest::hash(serialized_header));
239
240        let mut deploy = Deploy {
241            hash,
242            header,
243            payment,
244            session,
245            approvals: vec![],
246        };
247
248        deploy.sign(secret_key);
249        deploy
250    }
251
252    /// Adds a signature of this deploy's hash to its approvals.
253    pub fn sign(&mut self, secret_key: &SecretKey) {
254        let signer = PublicKey::from(secret_key);
255        let signature = crypto::sign(self.hash.0, secret_key, &signer);
256        let approval = Approval { signer, signature };
257        self.approvals.push(approval);
258    }
259
260    /// Returns `Ok` if the serialized size of the deploy is not greater than `max_deploy_size`.
261    pub fn is_valid_size(&self, max_deploy_size: u32) -> Result<(), Error> {
262        let deploy_size = self.header.serialized_length()
263            + self.hash.serialized_length()
264            + self.payment.serialized_length()
265            + self.session.serialized_length()
266            + self.approvals.serialized_length();
267        if deploy_size > max_deploy_size as usize {
268            return Err(Error::DeploySizeTooLarge {
269                max_deploy_size,
270                actual_deploy_size: deploy_size,
271            });
272        }
273        Ok(())
274    }
275
276    /// Returns the hash uniquely identifying this deploy.
277    pub fn id(&self) -> &DeployHash {
278        &self.hash
279    }
280
281    /// Returns the header portion of the deploy.
282    pub fn header(&self) -> &DeployHeader {
283        &self.header
284    }
285
286    /// Returns the payment code of the deploy.
287    pub fn payment(&self) -> &ExecutableDeployItem {
288        &self.payment
289    }
290
291    /// Returns the session code of the deploy.
292    pub fn session(&self) -> &ExecutableDeployItem {
293        &self.session
294    }
295
296    /// Returns the approvals; the public keys and signatures of the signatories of the deploy.
297    pub fn approvals(&self) -> &[Approval] {
298        &self.approvals
299    }
300}
301
302impl Display for Deploy {
303    fn fmt(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
304        write!(
305            formatter,
306            "deploy {{ {}, account {}, timestamp {}, ttl {}, body hash {}, chain name {} }}",
307            self.hash,
308            self.header.account,
309            self.header.timestamp,
310            self.header.ttl,
311            self.header.body_hash,
312            self.header.chain_name
313        )
314    }
315}
316
317fn serialize_body(payment: &ExecutableDeployItem, session: &ExecutableDeployItem) -> Vec<u8> {
318    let mut buffer = payment
319        .to_bytes()
320        .unwrap_or_else(|error| panic!("should serialize payment code: {}", error));
321    buffer.extend(
322        session
323            .to_bytes()
324            .unwrap_or_else(|error| panic!("should serialize session code: {}", error)),
325    );
326    buffer
327}
328
329/// A builder for constructing a [`Deploy`].
330pub struct DeployBuilder<'a> {
331    account: Option<PublicKey>,
332    timestamp: Timestamp,
333    ttl: TimeDiff,
334    gas_price: u64,
335    dependencies: Vec<DeployHash>,
336    chain_name: String,
337    payment: Option<ExecutableDeployItem>,
338    session: ExecutableDeployItem,
339    secret_key: &'a SecretKey,
340}
341
342impl<'a> DeployBuilder<'a> {
343    /// Returns a new `DeployBuilder`.
344    ///
345    /// # Note
346    ///
347    /// Before calling [`build`](Self::build), you must ensure that payment code is provided by
348    /// either calling [`with_standard_payment`](Self::with_standard_payment) or
349    /// [`with_payment`](Self::with_payment).
350    pub fn new<C: Into<String>>(
351        chain_name: C,
352        session: ExecutableDeployItem,
353        secret_key: &'a SecretKey,
354    ) -> Self {
355        DeployBuilder {
356            account: None,
357            timestamp: Timestamp::now(),
358            ttl: Deploy::DEFAULT_TTL,
359            gas_price: Deploy::DEFAULT_GAS_PRICE,
360            dependencies: vec![],
361            chain_name: chain_name.into(),
362            payment: None,
363            session,
364            secret_key,
365        }
366    }
367
368    /// Returns a new `DeployBuilder` with session code suitable for a transfer.
369    ///
370    /// If `maybe_source` is None, the account's main purse is used as the source of the transfer.
371    ///
372    /// # Note
373    ///
374    /// Before calling [`build`](Self::build), you must ensure that payment code is provided by
375    /// either calling [`with_standard_payment`](Self::with_standard_payment) or
376    /// [`with_payment`](Self::with_payment).
377    pub fn new_transfer<C: Into<String>, A: Into<U512>>(
378        chain_name: C,
379        amount: A,
380        maybe_source: Option<URef>,
381        target: TransferTarget,
382        maybe_transfer_id: Option<u64>,
383        secret_key: &'a SecretKey,
384    ) -> Self {
385        let session =
386            ExecutableDeployItem::new_transfer(amount, maybe_source, target, maybe_transfer_id);
387        DeployBuilder::new(chain_name, session, secret_key)
388    }
389
390    /// Sets the `payment` in the `Deploy` to a standard payment with the given amount.
391    pub fn with_standard_payment<A: Into<U512>>(mut self, amount: A) -> Self {
392        self.payment = Some(ExecutableDeployItem::new_standard_payment(amount));
393        self
394    }
395
396    /// Sets the `payment` in the `Deploy`.
397    pub fn with_payment(mut self, payment: ExecutableDeployItem) -> Self {
398        self.payment = Some(payment);
399        self
400    }
401
402    /// Sets the `account` in the `Deploy`.
403    ///
404    /// If not provided, the public key derived from the secret key used in the `DeployBuilder` will
405    /// be used as the `account` in the `Deploy`.
406    pub fn with_account(mut self, account: PublicKey) -> Self {
407        self.account = Some(account);
408        self
409    }
410
411    /// Sets the `timestamp` in the `Deploy`.
412    ///
413    /// If not provided, the timestamp will be set to the time when the `DeployBuilder` was
414    /// constructed.
415    pub fn with_timestamp(mut self, timestamp: Timestamp) -> Self {
416        self.timestamp = timestamp;
417        self
418    }
419
420    /// Sets the `ttl` (time-to-live) in the `Deploy`.
421    ///
422    /// If not provided, the ttl will be set to [`Deploy::DEFAULT_TTL`].
423    pub fn with_ttl(mut self, ttl: TimeDiff) -> Self {
424        self.ttl = ttl;
425        self
426    }
427
428    /// Returns the new `Deploy`, or an error if neither
429    /// [`with_standard_payment`](Self::with_standard_payment) nor
430    /// [`with_payment`](Self::with_payment) were previously called.
431    pub fn build(self) -> Result<Deploy, Error> {
432        let payment = self.payment.ok_or(Error::DeployMissingPaymentCode)?;
433        let deploy = Deploy::new(
434            self.timestamp,
435            self.ttl,
436            self.gas_price,
437            self.dependencies,
438            self.chain_name,
439            payment,
440            self.session,
441            self.secret_key,
442            self.account,
443        );
444        Ok(deploy)
445    }
446}