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
12pub 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 pub const DEFAULT_TTL: TimeDiff = TimeDiff::from_millis(30 * 60 * 1_000);
28 pub const DEFAULT_GAS_PRICE: u64 = 1;
30
31 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 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 pub fn with_account(mut self, account: PublicKey) -> Self {
87 self.account = Some(account);
88 self
89 }
90
91 pub fn with_gas_price(mut self, gas_price: u64) -> Self {
96 self.gas_price = gas_price;
97 self
98 }
99
100 pub fn with_secret_key(mut self, secret_key: &'a SecretKey) -> Self {
105 self.secret_key = Some(secret_key);
106 self
107 }
108
109 pub fn with_payment(mut self, payment: ExecutableDeployItem) -> Self {
111 self.payment = Some(payment);
112 self
113 }
114
115 pub fn with_timestamp(mut self, timestamp: Timestamp) -> Self {
120 self.timestamp = timestamp;
121 self
122 }
123
124 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 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}