1pub mod error;
2use super::arg_handling;
3use crate::{
4 cli::{FieldsContainer, FieldsContainerError},
5 types::InitiatorAddrAndSecretKey,
6};
7use alloc::collections::BTreeMap;
8use alloc::collections::BTreeSet;
9use alloc::vec::Vec;
10use casper_types::{
11 bytesrepr::{Bytes, ToBytes},
12 system::auction::{DelegatorKind, Reservation},
13 AddressableEntityHash, CLValueError, Digest, EntityVersion, EntityVersionKey, InitiatorAddr,
14 PackageHash, PricingMode, PublicKey, RuntimeArgs, SecretKey, TimeDiff, Timestamp,
15 TransactionArgs, TransactionEntryPoint, TransactionInvocationTarget, TransactionRuntimeParams,
16 TransactionScheduling, TransactionTarget, TransactionV1, TransactionV1Payload, TransferTarget,
17 URef, U512,
18};
19use core::marker::PhantomData;
20pub use error::TransactionV1BuilderError;
21
22#[derive(Debug)]
66pub struct TransactionV1Builder<'a> {
67 args: TransactionArgs,
69 target: TransactionTarget,
71 scheduling: TransactionScheduling,
73 entry_point: TransactionEntryPoint,
75 chain_name: Option<String>,
77 timestamp: Timestamp,
79 ttl: TimeDiff,
81 pricing_mode: PricingMode,
83 initiator_addr: Option<InitiatorAddr>,
85 #[cfg(not(test))]
87 secret_key: Option<&'a SecretKey>,
88 #[cfg(test)]
90 secret_key: Option<SecretKey>,
91 _phantom_data: PhantomData<&'a ()>,
93}
94
95impl<'a> TransactionV1Builder<'a> {
96 pub const DEFAULT_TTL: TimeDiff = TimeDiff::from_millis(30 * 60 * 1_000);
98 pub const DEFAULT_PRICING_MODE: PricingMode = PricingMode::Fixed {
100 gas_price_tolerance: 5,
101 additional_computation_factor: 0,
102 };
103 pub const DEFAULT_SCHEDULING: TransactionScheduling = TransactionScheduling::Standard;
105
106 pub(crate) fn new() -> Self {
141 #[cfg(any(feature = "std-fs-io", test))]
142 let timestamp = Timestamp::now();
143 #[cfg(not(any(feature = "std-fs-io", test)))]
144 let timestamp = Timestamp::zero();
145
146 TransactionV1Builder {
147 args: TransactionArgs::Named(RuntimeArgs::new()),
148 entry_point: TransactionEntryPoint::Transfer,
149 target: TransactionTarget::Native,
150 scheduling: TransactionScheduling::Standard,
151 chain_name: None,
152 timestamp,
153 ttl: Self::DEFAULT_TTL,
154 pricing_mode: Self::DEFAULT_PRICING_MODE,
155 initiator_addr: None,
156 secret_key: None,
157 _phantom_data: PhantomData,
158 }
159 }
160
161 pub fn new_transfer<A: Into<U512>, T: Into<TransferTarget>>(
163 amount: A,
164 maybe_source: Option<URef>,
165 target: T,
166 maybe_id: Option<u64>,
167 ) -> Result<Self, CLValueError> {
168 let args = arg_handling::new_transfer_args(amount, maybe_source, target, maybe_id)?;
169 let mut builder = TransactionV1Builder::new();
170 builder.args = TransactionArgs::Named(args);
171 builder.target = TransactionTarget::Native;
172 builder.entry_point = TransactionEntryPoint::Transfer;
173 builder.scheduling = Self::DEFAULT_SCHEDULING;
174 Ok(builder)
175 }
176
177 pub fn new_add_bid<A: Into<U512>>(
179 public_key: PublicKey,
180 delegation_rate: u8,
181 amount: A,
182 minimum_delegation_amount: Option<u64>,
183 maximum_delegation_amount: Option<u64>,
184 reserved_slots: Option<u32>,
185 ) -> Result<Self, CLValueError> {
186 let args = arg_handling::new_add_bid_args(
187 public_key,
188 delegation_rate,
189 amount,
190 minimum_delegation_amount,
191 maximum_delegation_amount,
192 reserved_slots,
193 )?;
194 let mut builder = TransactionV1Builder::new();
195 builder.args = TransactionArgs::Named(args);
196 builder.target = TransactionTarget::Native;
197 builder.entry_point = TransactionEntryPoint::AddBid;
198 builder.scheduling = Self::DEFAULT_SCHEDULING;
199 Ok(builder)
200 }
201
202 pub fn new_withdraw_bid<A: Into<U512>>(
205 public_key: PublicKey,
206 amount: A,
207 ) -> Result<Self, CLValueError> {
208 let args = arg_handling::new_withdraw_bid_args(public_key, amount)?;
209 let mut builder = TransactionV1Builder::new();
210 builder.args = TransactionArgs::Named(args);
211 builder.target = TransactionTarget::Native;
212 builder.entry_point = TransactionEntryPoint::WithdrawBid;
213 builder.scheduling = Self::DEFAULT_SCHEDULING;
214 Ok(builder)
215 }
216
217 pub fn new_delegate<A: Into<U512>>(
219 delegator: PublicKey,
220 validator: PublicKey,
221 amount: A,
222 ) -> Result<Self, CLValueError> {
223 let args = arg_handling::new_delegate_args(delegator, validator, amount)?;
224 let mut builder = TransactionV1Builder::new();
225 builder.args = TransactionArgs::Named(args);
226 builder.target = TransactionTarget::Native;
227 builder.entry_point = TransactionEntryPoint::Delegate;
228 builder.scheduling = Self::DEFAULT_SCHEDULING;
229 Ok(builder)
230 }
231
232 pub fn new_undelegate<A: Into<U512>>(
234 delegator: PublicKey,
235 validator: PublicKey,
236 amount: A,
237 ) -> Result<Self, CLValueError> {
238 let args = arg_handling::new_undelegate_args(delegator, validator, amount)?;
239 let mut builder = TransactionV1Builder::new();
240 builder.args = TransactionArgs::Named(args);
241 builder.target = TransactionTarget::Native;
242 builder.entry_point = TransactionEntryPoint::Undelegate;
243 builder.scheduling = Self::DEFAULT_SCHEDULING;
244 Ok(builder)
245 }
246
247 pub fn new_redelegate<A: Into<U512>>(
249 delegator: PublicKey,
250 validator: PublicKey,
251 amount: A,
252 new_validator: PublicKey,
253 ) -> Result<Self, CLValueError> {
254 let args = arg_handling::new_redelegate_args(delegator, validator, amount, new_validator)?;
255 let mut builder = TransactionV1Builder::new();
256 builder.args = TransactionArgs::Named(args);
257 builder.target = TransactionTarget::Native;
258 builder.entry_point = TransactionEntryPoint::Redelegate;
259 builder.scheduling = Self::DEFAULT_SCHEDULING;
260 Ok(builder)
261 }
262
263 pub fn new_activate_bid(validator: PublicKey) -> Result<Self, CLValueError> {
266 let args = arg_handling::new_activate_bid_args(validator)?;
267 let mut builder = TransactionV1Builder::new();
268 builder.args = TransactionArgs::Named(args);
269 builder.target = TransactionTarget::Native;
270 builder.entry_point = TransactionEntryPoint::ActivateBid;
271 builder.scheduling = Self::DEFAULT_SCHEDULING;
272 Ok(builder)
273 }
274
275 pub fn new_change_bid_public_key(
278 public_key: PublicKey,
279 new_public_key: PublicKey,
280 ) -> Result<Self, CLValueError> {
281 let args = arg_handling::new_change_bid_public_key_args(public_key, new_public_key)?;
282 let mut builder = TransactionV1Builder::new();
283 builder.args = TransactionArgs::Named(args);
284 builder.target = TransactionTarget::Native;
285 builder.entry_point = TransactionEntryPoint::ChangeBidPublicKey;
286 builder.scheduling = Self::DEFAULT_SCHEDULING;
287 Ok(builder)
288 }
289
290 pub fn new_add_reservations(reservations: Vec<Reservation>) -> Result<Self, CLValueError> {
293 let args = arg_handling::new_add_reservations_args(reservations)?;
294 let mut builder = TransactionV1Builder::new();
295 builder.args = TransactionArgs::Named(args);
296 builder.target = TransactionTarget::Native;
297 builder.entry_point = TransactionEntryPoint::AddReservations;
298 builder.scheduling = Self::DEFAULT_SCHEDULING;
299 Ok(builder)
300 }
301
302 pub fn new_cancel_reservations(
305 validator: PublicKey,
306 delegators: Vec<DelegatorKind>,
307 ) -> Result<Self, CLValueError> {
308 let args = arg_handling::new_cancel_reservations_args(validator, delegators)?;
309 let mut builder = TransactionV1Builder::new();
310 builder.args = TransactionArgs::Named(args);
311 builder.target = TransactionTarget::Native;
312 builder.entry_point = TransactionEntryPoint::CancelReservations;
313 builder.scheduling = Self::DEFAULT_SCHEDULING;
314 Ok(builder)
315 }
316
317 fn new_targeting_stored<E: Into<String>>(
318 id: TransactionInvocationTarget,
319 entry_point: E,
320 runtime: TransactionRuntimeParams,
321 ) -> Self {
322 let target = TransactionTarget::Stored { id, runtime };
323 let mut builder = TransactionV1Builder::new();
324 builder.args = TransactionArgs::Named(RuntimeArgs::new());
325 builder.target = target;
326 builder.entry_point = TransactionEntryPoint::Custom(entry_point.into());
327 builder.scheduling = Self::DEFAULT_SCHEDULING;
328 builder
329 }
330
331 pub fn new_targeting_invocable_entity<E: Into<String>>(
334 hash: AddressableEntityHash,
335 entry_point: E,
336 runtime: TransactionRuntimeParams,
337 ) -> Self {
338 let id = TransactionInvocationTarget::new_invocable_entity(hash);
339 Self::new_targeting_stored(id, entry_point, runtime)
340 }
341
342 pub fn new_targeting_invocable_entity_via_alias<A: Into<String>, E: Into<String>>(
345 alias: A,
346 entry_point: E,
347 runtime: TransactionRuntimeParams,
348 ) -> Self {
349 let id = TransactionInvocationTarget::new_invocable_entity_alias(alias.into());
350 Self::new_targeting_stored(id, entry_point, runtime)
351 }
352
353 pub fn new_targeting_package<E: Into<String>>(
356 hash: PackageHash,
357 version: Option<EntityVersion>,
358 entry_point: E,
359 runtime: TransactionRuntimeParams,
360 ) -> Self {
361 #[allow(deprecated)]
362 let id = TransactionInvocationTarget::new_package(hash, version);
363 Self::new_targeting_stored(id, entry_point, runtime)
364 }
365
366 pub fn new_targeting_package_with_version_key<E: Into<String>>(
369 hash: PackageHash,
370 version: Option<EntityVersionKey>,
371 entry_point: E,
372 runtime: TransactionRuntimeParams,
373 ) -> Self {
374 let id = if let Some(version) = version {
375 TransactionInvocationTarget::new_package_with_major(hash, Some(version.entity_version()), Some(version.protocol_version_major()))
376 } else {
377 TransactionInvocationTarget::new_package_with_major(hash, None, None)
378 };
379 Self::new_targeting_stored(id, entry_point, runtime)
380 }
381
382 pub fn new_targeting_package_via_alias<A: Into<String>, E: Into<String>>(
385 alias: A,
386 version: Option<EntityVersion>,
387 entry_point: E,
388 runtime: TransactionRuntimeParams,
389 ) -> Self {
390 #[allow(deprecated)]
391 let id = TransactionInvocationTarget::new_package_alias(alias.into(), version);
392 Self::new_targeting_stored(id, entry_point, runtime)
393 }
394
395 pub fn new_targeting_package_via_alias_with_version_key<A: Into<String>, E: Into<String>>(
398 alias: A,
399 version: Option<EntityVersionKey>,
400 entry_point: E,
401 runtime: TransactionRuntimeParams,
402 ) -> Self {
403 let id = if let Some(version) = version {
404 TransactionInvocationTarget::new_package_alias_with_major(
405 alias.into(),
406 Some(version.entity_version()),
407 Some(version.protocol_version_major()),
408 )
409 } else {
410 TransactionInvocationTarget::new_package_alias_with_major(alias.into(), None, None)
411 };
412 Self::new_targeting_stored(id, entry_point, runtime)
413 }
414
415 pub fn new_session(
418 is_install_upgrade: bool,
419 module_bytes: Bytes,
420 runtime: TransactionRuntimeParams,
421 ) -> Self {
422 let target = TransactionTarget::Session {
423 is_install_upgrade,
424 module_bytes,
425 runtime,
426 };
427 let mut builder = TransactionV1Builder::new();
428 builder.args = TransactionArgs::Named(RuntimeArgs::new());
429 builder.target = target;
430 builder.entry_point = TransactionEntryPoint::Call;
431 builder.scheduling = Self::DEFAULT_SCHEDULING;
432 builder
433 }
434
435 pub fn with_chain_name<C: Into<String>>(mut self, chain_name: C) -> Self {
439 self.chain_name = Some(chain_name.into());
440 self
441 }
442
443 pub fn with_timestamp(mut self, timestamp: Timestamp) -> Self {
447 self.timestamp = timestamp;
448 self
449 }
450
451 pub fn with_ttl(mut self, ttl: TimeDiff) -> Self {
455 self.ttl = ttl;
456 self
457 }
458
459 pub fn with_pricing_mode(mut self, pricing_mode: PricingMode) -> Self {
463 self.pricing_mode = pricing_mode;
464 self
465 }
466
467 pub fn with_initiator_addr<I: Into<InitiatorAddr>>(mut self, initiator_addr: I) -> Self {
472 self.initiator_addr = Some(initiator_addr.into());
473 self
474 }
475
476 pub fn with_secret_key(mut self, secret_key: &'a SecretKey) -> Self {
481 #[cfg(not(test))]
482 {
483 self.secret_key = Some(secret_key);
484 }
485 #[cfg(test)]
486 {
487 self.secret_key = Some(
488 SecretKey::from_der(secret_key.to_der().expect("should der-encode"))
489 .expect("should der-decode"),
490 );
491 }
492 self
493 }
494
495 pub fn with_runtime_args(mut self, args: RuntimeArgs) -> Self {
500 self.args = TransactionArgs::Named(args);
501 self
502 }
503
504 pub fn with_chunked_args(mut self, args: Bytes) -> Self {
506 self.args = TransactionArgs::Bytesrepr(args);
507 self
508 }
509
510 pub fn with_entry_point(mut self, entry_point: TransactionEntryPoint) -> Self {
512 self.entry_point = entry_point;
513 self
514 }
515
516 pub fn build(self) -> Result<TransactionV1, TransactionV1BuilderError> {
520 self.do_build()
521 }
522
523 fn build_transaction_inner(
524 chain_name: String,
525 timestamp: Timestamp,
526 ttl: TimeDiff,
527 pricing_mode: PricingMode,
528 fields: BTreeMap<u16, Bytes>,
529 initiator_addr_and_secret_key: InitiatorAddrAndSecretKey,
530 ) -> TransactionV1 {
531 let initiator_addr = initiator_addr_and_secret_key.initiator_addr();
532 let transaction_v1_payload = TransactionV1Payload::new(
533 chain_name,
534 timestamp,
535 ttl,
536 pricing_mode,
537 initiator_addr,
538 fields,
539 );
540 let hash = Digest::hash(
541 transaction_v1_payload
542 .to_bytes()
543 .unwrap_or_else(|error| panic!("should serialize body: {}", error)),
544 );
545 let mut transaction =
546 TransactionV1::new(hash.into(), transaction_v1_payload, BTreeSet::new());
547
548 if let Some(secret_key) = initiator_addr_and_secret_key.secret_key() {
549 transaction.sign(secret_key);
550 }
551 transaction
552 }
553
554 fn do_build(self) -> Result<TransactionV1, TransactionV1BuilderError> {
555 let initiator_addr_and_secret_key = match (self.initiator_addr, &self.secret_key) {
556 (Some(initiator_addr), Some(secret_key)) => InitiatorAddrAndSecretKey::Both {
557 initiator_addr,
558 secret_key,
559 },
560 (Some(initiator_addr), None) => {
561 InitiatorAddrAndSecretKey::InitiatorAddr(initiator_addr)
562 }
563 (None, Some(secret_key)) => InitiatorAddrAndSecretKey::SecretKey(secret_key),
564 (None, None) => return Err(TransactionV1BuilderError::MissingInitiatorAddr),
565 };
566
567 let chain_name = self
568 .chain_name
569 .ok_or(TransactionV1BuilderError::MissingChainName)?;
570
571 let container =
572 FieldsContainer::new(self.args, self.target, self.entry_point, self.scheduling)
573 .to_map()
574 .map_err(|err| match err {
575 FieldsContainerError::CouldNotSerializeField { field_index } => {
576 TransactionV1BuilderError::CouldNotSerializeField { field_index }
577 }
578 })?;
579
580 let transaction = Self::build_transaction_inner(
581 chain_name,
582 self.timestamp,
583 self.ttl,
584 self.pricing_mode,
585 container,
586 initiator_addr_and_secret_key,
587 );
588
589 Ok(transaction)
590 }
591}