-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
a07e731
commit 4173286
Showing
10 changed files
with
435 additions
and
41 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
use rand::Rng; | ||
|
||
use crate::address::Address; | ||
use crate::network::ThorNode; | ||
use crate::rlp::Bytes; | ||
use crate::transactions::{Clause, Reserved, Transaction}; | ||
use crate::U256; | ||
|
||
#[derive(Clone, Debug, Eq, PartialEq, Default)] | ||
struct TransactionTemplate { | ||
block_ref: Option<u64>, | ||
expiration: Option<u32>, | ||
clauses: Vec<Clause>, | ||
gas_price_coef: Option<u8>, | ||
gas: Option<u64>, | ||
depends_on: Option<U256>, | ||
nonce: Option<u64>, | ||
delegated: bool, | ||
} | ||
|
||
/// Transaction builder allows to create and prepare transactions | ||
/// with minimal developers efforts. | ||
#[derive(Clone, Debug)] | ||
pub struct TransactionBuilder { | ||
node: ThorNode, | ||
template: TransactionTemplate, | ||
} | ||
|
||
impl TransactionBuilder { | ||
pub fn new(node: ThorNode) -> Self { | ||
//! Create a new builder. | ||
Self { | ||
node, | ||
template: TransactionTemplate::default(), | ||
} | ||
} | ||
pub const fn delegated(mut self) -> Self { | ||
//! Make a transaction delegated. | ||
self.template.delegated = true; | ||
self | ||
} | ||
pub const fn nonce(mut self, nonce: u64) -> Self { | ||
//! Set a nonce for transaction. | ||
self.template.nonce = Some(nonce); | ||
self | ||
} | ||
pub const fn depends_on(mut self, depends_on: U256) -> Self { | ||
//! Mark a transaction as dependent on another one. | ||
self.template.depends_on = Some(depends_on); | ||
self | ||
} | ||
pub const fn gas(mut self, gas: u64) -> Self { | ||
//! Set maximal gas amount for transaction. | ||
self.template.gas = Some(gas); | ||
self | ||
} | ||
pub const fn gas_price_coef(mut self, gas_price_coef: u8) -> Self { | ||
//! Set gas price coefficient for transaction. | ||
self.template.gas_price_coef = Some(gas_price_coef); | ||
self | ||
} | ||
pub const fn expiration(mut self, expiration: u32) -> Self { | ||
//! Set expiration for transaction in blocks, starting from `block_ref`. | ||
self.template.expiration = Some(expiration); | ||
self | ||
} | ||
pub const fn block_ref(mut self, block_ref: u64) -> Self { | ||
//! Set block_ref for transaction to count `expiration` from. | ||
self.template.block_ref = Some(block_ref); | ||
self | ||
} | ||
pub fn add_transfer<T: Into<U256>>(self, recipient: Address, value: T) -> Self { | ||
//! Add a simple transfer to clauses. | ||
self.add_clause(Clause { | ||
to: Some(recipient), | ||
value: value.into(), | ||
data: Bytes::new(), | ||
}) | ||
} | ||
pub fn add_contract_create(self, contract_bytes: Bytes) -> Self { | ||
//! Add a contract creation clause. | ||
self.add_clause(Clause { | ||
to: None, | ||
value: U256::zero(), | ||
data: contract_bytes, | ||
}) | ||
} | ||
pub fn add_contract_call(self, contract_address: Address, call_bytes: Bytes) -> Self { | ||
//! Add a contract method call clause. | ||
self.add_clause(Clause { | ||
to: Some(contract_address), | ||
value: U256::zero(), | ||
data: call_bytes, | ||
}) | ||
} | ||
pub fn add_clause(mut self, clause: Clause) -> Self { | ||
//! Add an arbitrary, user-provided clause. | ||
self.template.clauses.push(clause); | ||
self | ||
} | ||
|
||
pub async fn build(&self) -> Result<Transaction, TransactionBuilderError> { | ||
//! Prepare a `Transaction`. This may perform a network request | ||
//! to identify appropriate parameters. | ||
if self.template.clauses.is_empty() { | ||
return Err(TransactionBuilderError::EmptyTransaction); | ||
} | ||
let block_ref = match self.template.block_ref { | ||
Some(r) => r, | ||
None => self | ||
.node | ||
.fetch_best_block() | ||
.await | ||
.map_err(|_| TransactionBuilderError::NetworkError)? | ||
.0 | ||
.block_ref(), | ||
}; | ||
let mut tx = Transaction { | ||
chain_tag: self.node.chain_tag, | ||
block_ref, | ||
expiration: self.template.expiration.unwrap_or(128), | ||
clauses: self.template.clauses.clone(), | ||
gas_price_coef: self.template.gas_price_coef.unwrap_or(0), | ||
gas: self.template.gas.unwrap_or(0), | ||
depends_on: self.template.depends_on, | ||
nonce: self.template.nonce.unwrap_or_else(|| { | ||
let mut rng = rand::thread_rng(); | ||
rng.gen::<u64>() | ||
}), | ||
reserved: if self.template.delegated { | ||
Some(Reserved::new_delegated()) | ||
} else { | ||
None | ||
}, | ||
signature: None, | ||
}; | ||
if self.template.gas.is_some() { | ||
Ok(tx) | ||
} else if tx.clauses.iter().all(|clause| clause.data.is_empty()) { | ||
tx.gas = tx.intrinsic_gas(); | ||
Ok(tx) | ||
} else { | ||
Err(TransactionBuilderError::CannotEstimateGas) | ||
} | ||
} | ||
} | ||
|
||
/// Transaction creation errors | ||
#[derive(Clone, Debug, Eq, PartialEq)] | ||
pub enum TransactionBuilderError { | ||
/// Network error (failed to fetch data from node) | ||
NetworkError, | ||
/// No clauses provided | ||
EmptyTransaction, | ||
/// Transaction clauses involve contract interaction, and gas was not provided. | ||
CannotEstimateGas, | ||
} | ||
|
||
impl std::error::Error for TransactionBuilderError {} | ||
impl std::fmt::Display for TransactionBuilderError { | ||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||
match self { | ||
Self::NetworkError => f.write_str("Failed to retrieve data from network"), | ||
Self::EmptyTransaction => f.write_str("Cannot build an empty transaction - make sure to add at least one clause first."), | ||
Self::CannotEstimateGas => f.write_str("Transaction clauses involve contract interaction, please provide gas amount explicitly."), | ||
} | ||
} | ||
} |
Oops, something went wrong.