wip
This commit is contained in:
parent
712ae21615
commit
2d3e76a894
|
@ -7,7 +7,7 @@
|
|||
use anyhow::{anyhow, Result};
|
||||
use minicbor::decode;
|
||||
|
||||
use crate::{tx::plutus::BuildParams, utils::v2a};
|
||||
use crate::{cardano_types::{address::Address, DatumOrHash, Output, Utxo}, tx::plutus::BuildParams, utils::v2a};
|
||||
use blockfrost::{BlockfrostAPI, Pagination};
|
||||
use blockfrost_openapi::models::{
|
||||
asset_history_inner::Action, tx_content_output_amount_inner::TxContentOutputAmountInner,
|
||||
|
@ -15,15 +15,12 @@ use blockfrost_openapi::models::{
|
|||
use pallas_addresses::{Network, ShelleyAddress, ShelleyDelegationPart, ShelleyPaymentPart};
|
||||
use pallas_codec::{
|
||||
minicbor as cbor,
|
||||
utils::{CborWrap, NonEmptyKeyValuePairs},
|
||||
};
|
||||
use pallas_primitives::{
|
||||
conway::{
|
||||
AssetName, DatumOption, PolicyId, PostAlonzoTransactionOutput, PseudoDatumOption,
|
||||
ScriptRef, TransactionInput, TransactionOutput, Tx, Value,
|
||||
},
|
||||
PlutusScript,
|
||||
utils::NonEmptyKeyValuePairs,
|
||||
};
|
||||
use pallas_primitives::conway::{
|
||||
AssetName, PolicyId, PostAlonzoTransactionOutput,
|
||||
TransactionInput, TransactionOutput, Tx, Value,
|
||||
};
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use uplc::{tx::ResolvedInput, PlutusData};
|
||||
|
||||
|
@ -147,20 +144,16 @@ impl Blockfrost {
|
|||
plutus_data_from_inline(&data)
|
||||
}
|
||||
|
||||
pub async fn resolve_datum_option(
|
||||
pub async fn resolve_datum(
|
||||
&self,
|
||||
datum_hash: &Option<String>,
|
||||
inline_datum: &Option<String>,
|
||||
) -> Result<Option<DatumOption>> {
|
||||
) -> Result<Option<DatumOrHash>> {
|
||||
if let Some(inline_datum) = inline_datum {
|
||||
Ok(Some(PseudoDatumOption::Data(CborWrap(
|
||||
plutus_data_from_inline(inline_datum)?,
|
||||
))))
|
||||
Ok(Some(DatumOrHash::Data(plutus_data_from_inline(inline_datum)?)))
|
||||
} else {
|
||||
if let Some(datum_hash) = datum_hash {
|
||||
Ok(Some(PseudoDatumOption::Data(CborWrap(
|
||||
self.plutus_data_from_hash(&datum_hash).await?,
|
||||
))))
|
||||
Ok(Some(DatumOrHash::Data(self.plutus_data_from_hash(&datum_hash).await?,)))
|
||||
} else {
|
||||
Ok(None)
|
||||
}
|
||||
|
@ -273,7 +266,7 @@ impl Cardano for Blockfrost {
|
|||
(&pp).into()
|
||||
}
|
||||
|
||||
async fn resolve_many(&self, inputs: Vec<TransactionInput>) -> Vec<ResolvedInput> {
|
||||
async fn resolve_many(&self, inputs: Vec<TransactionInput>) -> Vec<Utxo> {
|
||||
let mut resolved = vec![];
|
||||
for i in inputs {
|
||||
if let Ok(r) = self.resolve(i).await {
|
||||
|
@ -283,7 +276,7 @@ impl Cardano for Blockfrost {
|
|||
resolved
|
||||
}
|
||||
|
||||
async fn resolve(&self, input: TransactionInput) -> Result<ResolvedInput> {
|
||||
async fn resolve(&self, input: TransactionInput) -> Result<Utxo> {
|
||||
let utxo = self
|
||||
.api
|
||||
.transactions_utxos(hex::encode(input.transaction_id).as_str())
|
||||
|
@ -299,23 +292,23 @@ impl Cardano for Blockfrost {
|
|||
output.output_index, input.index as i32,
|
||||
"somehow resolved the wrong ouput",
|
||||
);
|
||||
let datum_option = self
|
||||
.resolve_datum_option(&output.data_hash, &output.inline_datum)
|
||||
let datum = self
|
||||
.resolve_datum(&output.data_hash, &output.inline_datum)
|
||||
.await
|
||||
.expect("Something went wrong");
|
||||
?;
|
||||
// let script_ref = self.resolve_script(&output.reference_script_hash).await.expect("Something went wrong");
|
||||
|
||||
// FIXME!!!!
|
||||
let script_ref = None;
|
||||
|
||||
Ok(ResolvedInput {
|
||||
Ok(Utxo {
|
||||
input: input.clone(),
|
||||
output: TransactionOutput::PostAlonzo(PostAlonzoTransactionOutput {
|
||||
address: from_bech32(&output.address).into(),
|
||||
output: Output {
|
||||
address: Address::from_bech32(&output.address)?,
|
||||
value: from_tx_content_output_amounts(&output.amount[..]),
|
||||
datum_option,
|
||||
datum,
|
||||
script_ref,
|
||||
}),
|
||||
},
|
||||
})
|
||||
} else {
|
||||
Err(anyhow!("No output found"))
|
||||
|
|
|
@ -2,6 +2,7 @@ use anyhow::Result;
|
|||
use pallas_addresses::{Network, ShelleyPaymentPart};
|
||||
use pallas_primitives::conway::Tx;
|
||||
use uplc::{tx::ResolvedInput, TransactionInput};
|
||||
use crate::cardano_types::{Utxo};
|
||||
|
||||
use crate::tx::plutus::BuildParams;
|
||||
|
||||
|
@ -11,11 +12,11 @@ pub trait Cardano {
|
|||
fn resolve_many(
|
||||
&self,
|
||||
inputs: Vec<TransactionInput>,
|
||||
) -> impl std::future::Future<Output = Vec<ResolvedInput>> + Send;
|
||||
) -> impl std::future::Future<Output = Vec<Utxo>> + Send;
|
||||
fn resolve(
|
||||
&self,
|
||||
input: TransactionInput,
|
||||
) -> impl std::future::Future<Output = Result<ResolvedInput>> + Send;
|
||||
) -> impl std::future::Future<Output = Result<Utxo>> + Send;
|
||||
fn transaction_by_hash(
|
||||
&self,
|
||||
tx_hash: &str,
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
use pallas_addresses;
|
||||
use pallas_codec::utils::CborWrap;
|
||||
use pallas_crypto::hash::Hash;
|
||||
use pallas_primitives::{
|
||||
conway::{DatumOption, NativeScript, PostAlonzoTransactionOutput, PseudoScript, PseudoTransactionOutput, Value},
|
||||
PlutusScript,
|
||||
};
|
||||
use uplc::PlutusData;
|
||||
|
||||
pub mod address;
|
||||
use address::Address;
|
||||
|
||||
/// This is a sad file: yet another set of wrapping.
|
||||
///
|
||||
/// The pallas types are too confusing.
|
||||
/// This is surely a consequence of supporting all eras.
|
||||
/// At least in part.
|
||||
/// Another part may also be that this support has simply grown,
|
||||
/// rather than be rationalised.
|
||||
|
||||
|
||||
///
|
||||
/// Script.
|
||||
/// There is no support for native scripts,
|
||||
/// and we only really care about V3.
|
||||
pub enum Script {
|
||||
V1(Vec<u8>),
|
||||
V2(Vec<u8>),
|
||||
V3(Vec<u8>),
|
||||
}
|
||||
|
||||
impl Into<PseudoScript<NativeScript>> for Script {
|
||||
fn into(self) -> PseudoScript<NativeScript> {
|
||||
match self {
|
||||
Script::V1(items) => PseudoScript::PlutusV1Script(PlutusScript(items.into())),
|
||||
Script::V2(items) => PseudoScript::PlutusV2Script(PlutusScript(items.into())),
|
||||
Script::V3(items) => PseudoScript::PlutusV3Script(PlutusScript(items.into())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<CborWrap<PseudoScript<NativeScript>>> for Script {
|
||||
fn into(self) -> CborWrap<PseudoScript<NativeScript>> {
|
||||
CborWrap(self.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Datum
|
||||
/// We are not being too clever with this.
|
||||
/// We make use of rust's `Optional` for the no datum case.
|
||||
/// We are yet to distinguish cases where we need the data,
|
||||
/// in place of the hash.
|
||||
/// It should be documented.
|
||||
pub enum DatumOrHash {
|
||||
Hash(Hash<32>),// Check this
|
||||
Data(PlutusData),
|
||||
}
|
||||
|
||||
impl Into<DatumOption> for DatumOrHash {
|
||||
fn into(self) -> DatumOption {
|
||||
match self {
|
||||
DatumOrHash::Hash(hash) => DatumOption::Hash(hash),
|
||||
DatumOrHash::Data(plutus_data) => DatumOption::Data(CborWrap(plutus_data)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Output
|
||||
pub struct Output {
|
||||
pub address: Address,
|
||||
pub value: Value,
|
||||
pub datum: Option<DatumOrHash>,
|
||||
pub script_ref: Option<Script>,
|
||||
}
|
||||
|
||||
impl Into<PostAlonzoTransactionOutput> for Output {
|
||||
fn into(self) -> PostAlonzoTransactionOutput {
|
||||
let address : pallas_addresses::ShelleyAddress = self.address.into();
|
||||
PostAlonzoTransactionOutput {
|
||||
address: address.to_vec().into(),
|
||||
value: self.value,
|
||||
datum_option: self.datum.map(|x| x.into()),
|
||||
script_ref: self.script_ref.map(|x| x.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<pallas_primitives::conway::PseudoTransactionOutput<PostAlonzoTransactionOutput>> for Output {
|
||||
fn into(self) -> PseudoTransactionOutput<PostAlonzoTransactionOutput> {
|
||||
PseudoTransactionOutput::PostAlonzo(self.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Input
|
||||
/// No change from pallas primitives here.
|
||||
pub type Input = pallas_primitives::TransactionInput;
|
||||
|
||||
/// Utxo
|
||||
/// Aka resolved input.
|
||||
pub struct Utxo {
|
||||
pub input: Input,
|
||||
pub output: Output,
|
||||
}
|
||||
|
||||
impl Into<uplc::tx::ResolvedInput> for Utxo {
|
||||
fn into(self) -> uplc::tx::ResolvedInput {
|
||||
uplc::tx::ResolvedInput {
|
||||
input: self.input,
|
||||
output: self.output.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
/// Seems like quite a lot of code just to pullout `from_bech32` on address
|
||||
/// This is mostly extracted from private function of pallas_addresses
|
||||
use anyhow::{anyhow, Result};
|
||||
use pallas_addresses::{self, Network};
|
||||
use pallas_crypto::hash::Hash;
|
||||
use pallas_primitives::NetworkId;
|
||||
|
||||
use crate::utils::v2a;
|
||||
|
||||
/// Network
|
||||
|
||||
pub fn network_from_id(id: NetworkId) -> Network {
|
||||
match id {
|
||||
NetworkId::Testnet => Network::Testnet,
|
||||
NetworkId::Mainnet => Network::Mainnet,
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_network(header: u8) -> Network {
|
||||
let masked = header & 0b0000_1111;
|
||||
|
||||
match masked {
|
||||
0b_0000_0000 => Network::Testnet,
|
||||
0b_0000_0001 => Network::Mainnet,
|
||||
_ => Network::Other(masked),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Credential
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Credential {
|
||||
Key(Hash<28>),
|
||||
Script(Hash<28>),
|
||||
}
|
||||
|
||||
impl Into<pallas_addresses::ShelleyPaymentPart> for Credential {
|
||||
fn into(self) -> pallas_addresses::ShelleyPaymentPart {
|
||||
match self {
|
||||
Credential::Key(hash) => pallas_addresses::ShelleyPaymentPart::Key(hash.into()),
|
||||
Credential::Script(hash) => pallas_addresses::ShelleyPaymentPart::Script(hash.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<pallas_addresses::ShelleyDelegationPart> for Credential {
|
||||
fn into(self) -> pallas_addresses::ShelleyDelegationPart {
|
||||
match self {
|
||||
Credential::Key(hash) => pallas_addresses::ShelleyDelegationPart::Key(hash.into()),
|
||||
Credential::Script(hash) => {
|
||||
pallas_addresses::ShelleyDelegationPart::Script(hash.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Address
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Address {
|
||||
pub network: Network,
|
||||
pub payment: Credential,
|
||||
pub delegation: Option<Credential>,
|
||||
}
|
||||
|
||||
impl Address {
|
||||
|
||||
pub fn new(network: Network, payment: Credential, delegation: Option<Credential>) -> Self {
|
||||
Self { network, payment, delegation }
|
||||
}
|
||||
|
||||
pub fn from_bech32(s : &str) -> Result<Self> {
|
||||
let (_, bytes) = bech32::decode(s)?;
|
||||
let x = Address::try_from(bytes)?;
|
||||
Ok(x)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Vec<u8>> for Address {
|
||||
fn try_from(bytes : Vec<u8>) -> std::result::Result<Self, Self::Error> {
|
||||
let header = *bytes.first().expect("Missing bytes");
|
||||
let network = parse_network(header);
|
||||
let payment : Hash<28> = v2a(bytes[1..=28].to_vec())?.into();
|
||||
let delegation = v2a(bytes[28..].to_vec());
|
||||
match header & 0b1111_0000 {
|
||||
0b0000_0000 => Ok(Address::new(network, Credential::Key(payment), Some(Credential::Key(delegation?.into())))),
|
||||
0b0001_0000 => Ok(Address::new(network, Credential::Script(payment),Some(Credential::Key(delegation?.into())))),
|
||||
0b0010_0000 => Ok(Address::new(network, Credential::Key(payment), Some(Credential::Script(delegation?.into())))),
|
||||
0b0011_0000 => Ok(Address::new(network, Credential::Script(payment), Some(Credential::Script(delegation?.into())))),
|
||||
0b0110_0000 => Ok(Address::new(network, Credential::Key(payment), None)),
|
||||
0b0111_0000 => Ok(Address::new(network, Credential::Script(payment), None)),
|
||||
_ => Err(anyhow!("Header not supported")),
|
||||
}
|
||||
}
|
||||
|
||||
type Error = anyhow::Error;
|
||||
}
|
||||
|
||||
impl Into<pallas_addresses::ShelleyAddress> for Address {
|
||||
fn into(self) -> pallas_addresses::ShelleyAddress {
|
||||
let network = self.network;
|
||||
pallas_addresses::ShelleyAddress::new(
|
||||
network,
|
||||
self.payment.into(),
|
||||
self.delegation
|
||||
.map_or(pallas_addresses::ShelleyDelegationPart::Null, |x| x.into()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn parse_bech32() {
|
||||
let testnet = "addr_test1vqpymjsk49r6p9f2f6d3gm04ther0ka7ts8jsv8dukg4nvgcdjxy9";
|
||||
let addr0 = Address::from_bech32(testnet).unwrap();
|
||||
let mainnet = "addr1vypymjsk49r6p9f2f6d3gm04ther0ka7ts8jsv8dukg4nvgr9x6tq";
|
||||
let addr1 = Address::from_bech32(mainnet).unwrap();
|
||||
assert_eq!(addr0.delegation,addr1.delegation, "oops");
|
||||
let sa0 : pallas_addresses::ShelleyAddress = addr0.into();
|
||||
assert_eq!(sa0.to_bech32().unwrap(), testnet, "oops");
|
||||
let sa1 : pallas_addresses::ShelleyAddress = addr1.into();
|
||||
assert_eq!(sa1.to_bech32().unwrap(), mainnet, "oops");
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
use clap::Args;
|
||||
use pallas_addresses::Address;
|
||||
use pallas_addresses::{Address, ShelleyAddress};
|
||||
use tokio::runtime::Runtime;
|
||||
|
||||
use crate::{
|
||||
|
@ -20,7 +20,7 @@ pub struct Params {
|
|||
}
|
||||
|
||||
pub fn handle(params: Params) {
|
||||
let outputs = params
|
||||
let to = params
|
||||
.to
|
||||
.iter()
|
||||
.map(|x| parse_to(x))
|
||||
|
@ -28,7 +28,7 @@ pub fn handle(params: Params) {
|
|||
let env = get_env();
|
||||
let tx_ctx = from_env(&env);
|
||||
let rt = Runtime::new().expect("Failed to create Tokio runtime");
|
||||
let tx = rt.block_on(async { send(tx_ctx, vec![]).await });
|
||||
let tx = rt.block_on(async { send(tx_ctx, to).await });
|
||||
println!("{:?}", tx);
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ pub mod cardano;
|
|||
pub mod cmd;
|
||||
pub mod data;
|
||||
pub mod env;
|
||||
pub mod pallas_extra;
|
||||
pub mod tx;
|
||||
pub mod utils;
|
||||
pub mod wallet;
|
||||
pub mod cardano_types;
|
||||
|
|
|
@ -1,397 +0,0 @@
|
|||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
// SOURCE : github.com/cardanoSolutions/zhuli
|
||||
|
||||
use pallas_addresses::{Network, ShelleyAddress, ShelleyDelegationPart, ShelleyPaymentPart};
|
||||
use pallas_codec::{
|
||||
minicbor as cbor,
|
||||
utils::{NonEmptyKeyValuePairs, NonEmptySet, Set},
|
||||
};
|
||||
use pallas_crypto::hash::{Hash, Hasher};
|
||||
use pallas_primitives::{
|
||||
conway::{
|
||||
AssetName, Constr, ExUnits, Language, Multiasset, NetworkId, PlutusData,
|
||||
PostAlonzoTransactionOutput, PseudoTransactionOutput, RedeemerTag, RedeemersKey,
|
||||
RedeemersValue, TransactionBody, TransactionInput, TransactionOutput, Tx, Value,
|
||||
WitnessSet,
|
||||
},
|
||||
MaybeIndefArray,
|
||||
};
|
||||
use std::{cmp::Ordering, str::FromStr};
|
||||
use uplc::tx::{eval_phase_two, ResolvedInput, SlotConfig};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct BuildParams {
|
||||
pub fee_constant: u64,
|
||||
pub fee_coefficient: u64,
|
||||
pub price_mem: f64,
|
||||
pub price_steps: f64,
|
||||
}
|
||||
|
||||
pub struct OutputReference(pub TransactionInput);
|
||||
|
||||
impl FromStr for OutputReference {
|
||||
type Err = String;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match &s.split('#').collect::<Vec<_>>()[..] {
|
||||
[tx_id_str, ix_str] => {
|
||||
let transaction_id: Hash<32> = tx_id_str
|
||||
.parse()
|
||||
.map_err(|e| format!("failed to decode transaction id from hex: {e:?}"))?;
|
||||
let index: u64 = ix_str
|
||||
.parse()
|
||||
.map_err(|e| format!("failed to decode output index: {e:?}"))?;
|
||||
Ok(OutputReference(TransactionInput {
|
||||
transaction_id,
|
||||
index,
|
||||
}))
|
||||
}
|
||||
_ => Err("malformed output reference: expected a hex-encode string and an index separated by '#'".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Redeemer {}
|
||||
|
||||
impl Redeemer {
|
||||
pub fn mint(index: u32, data: PlutusData, ex_units: ExUnits) -> (RedeemersKey, RedeemersValue) {
|
||||
(
|
||||
RedeemersKey {
|
||||
tag: RedeemerTag::Mint,
|
||||
index,
|
||||
},
|
||||
RedeemersValue { data, ex_units },
|
||||
)
|
||||
}
|
||||
|
||||
pub fn spend(
|
||||
(inputs, target): (&[TransactionInput], &TransactionInput),
|
||||
data: PlutusData,
|
||||
ex_units: ExUnits,
|
||||
) -> (RedeemersKey, RedeemersValue) {
|
||||
(
|
||||
RedeemersKey {
|
||||
tag: RedeemerTag::Spend,
|
||||
index: inputs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, i)| *i == target)
|
||||
.unwrap()
|
||||
.0 as u32,
|
||||
},
|
||||
RedeemersValue { data, ex_units },
|
||||
)
|
||||
}
|
||||
|
||||
pub fn publish(
|
||||
index: u32,
|
||||
data: PlutusData,
|
||||
ex_units: ExUnits,
|
||||
) -> (RedeemersKey, RedeemersValue) {
|
||||
(
|
||||
RedeemersKey {
|
||||
tag: RedeemerTag::Cert,
|
||||
index,
|
||||
},
|
||||
RedeemersValue { data, ex_units },
|
||||
)
|
||||
}
|
||||
|
||||
pub fn vote(index: u32, data: PlutusData, ex_units: ExUnits) -> (RedeemersKey, RedeemersValue) {
|
||||
(
|
||||
RedeemersKey {
|
||||
tag: RedeemerTag::Vote,
|
||||
index,
|
||||
},
|
||||
RedeemersValue { data, ex_units },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn void() -> PlutusData {
|
||||
PlutusData::Constr(Constr {
|
||||
tag: 121,
|
||||
any_constructor: None,
|
||||
fields: MaybeIndefArray::Indef(vec![]),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn from_network(network: Network) -> NetworkId {
|
||||
match network {
|
||||
Network::Mainnet => NetworkId::Mainnet,
|
||||
_ => NetworkId::Testnet,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn non_empty_set<T>(set: Vec<T>) -> Option<NonEmptySet<T>>
|
||||
where
|
||||
T: std::fmt::Debug,
|
||||
{
|
||||
if set.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(NonEmptySet::try_from(set).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn non_empty_pairs<K, V>(pairs: Vec<(K, V)>) -> Option<NonEmptyKeyValuePairs<K, V>>
|
||||
where
|
||||
V: Clone,
|
||||
K: Clone,
|
||||
{
|
||||
if pairs.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(NonEmptyKeyValuePairs::Def(pairs))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_outputs(outputs: Vec<PostAlonzoTransactionOutput>) -> Vec<TransactionOutput> {
|
||||
outputs
|
||||
.into_iter()
|
||||
.map(PseudoTransactionOutput::PostAlonzo)
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn singleton_assets<T: Clone>(
|
||||
validator_hash: Hash<28>,
|
||||
assets: &[(AssetName, T)],
|
||||
) -> Multiasset<T> {
|
||||
NonEmptyKeyValuePairs::Def(vec![(
|
||||
validator_hash,
|
||||
NonEmptyKeyValuePairs::Def(assets.to_vec()),
|
||||
)])
|
||||
}
|
||||
|
||||
pub fn from_validator(validator: &[u8], network_id: Network) -> (Hash<28>, ShelleyAddress) {
|
||||
let validator_hash = Hasher::<224>::hash_tagged(validator, 3);
|
||||
let validator_address = ShelleyAddress::new(
|
||||
network_id,
|
||||
ShelleyPaymentPart::script_hash(validator_hash),
|
||||
ShelleyDelegationPart::script_hash(validator_hash),
|
||||
);
|
||||
|
||||
(validator_hash, validator_address)
|
||||
}
|
||||
|
||||
pub fn value_subtract_lovelace(value: Value, lovelace: u64) -> Option<Value> {
|
||||
match value {
|
||||
Value::Coin(total) if total > lovelace => Some(Value::Coin(total - lovelace)),
|
||||
Value::Multiasset(total, assets) if total > lovelace => {
|
||||
Some(Value::Multiasset(total - lovelace, assets))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn value_add_lovelace(value: Value, lovelace: u64) -> Value {
|
||||
match value {
|
||||
Value::Coin(total) => Value::Coin(total + lovelace),
|
||||
Value::Multiasset(total, assets) => Value::Multiasset(total + lovelace, assets),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn lovelace_of(value: &Value) -> u64 {
|
||||
match value {
|
||||
Value::Coin(lovelace) | Value::Multiasset(lovelace, _) => *lovelace,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_min_value_output<F>(per_byte: u64, build: F) -> PostAlonzoTransactionOutput
|
||||
where
|
||||
F: Fn(u64) -> PostAlonzoTransactionOutput,
|
||||
{
|
||||
let value = build(1);
|
||||
let mut buffer: Vec<u8> = Vec::new();
|
||||
cbor::encode(&value, &mut buffer).unwrap();
|
||||
// NOTE: 160 overhead as per the spec + 4 bytes for actual final lovelace value.
|
||||
// Technically, the final value could need 8 more additional bytes if the resulting
|
||||
// value was larger than 4_294_967_295 lovelaces, which would realistically never be
|
||||
// the case.
|
||||
build((buffer.len() as u64 + 164) * per_byte)
|
||||
}
|
||||
|
||||
pub fn total_execution_cost(params: &BuildParams, redeemers: &[ExUnits]) -> u64 {
|
||||
redeemers.iter().fold(0, |acc, ex_units| {
|
||||
acc + ((params.price_mem * ex_units.mem as f64).ceil() as u64)
|
||||
+ ((params.price_steps * ex_units.steps as f64).ceil() as u64)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn script_integrity_hash(
|
||||
redeemers: Option<&NonEmptyKeyValuePairs<RedeemersKey, RedeemersValue>>,
|
||||
datums: Option<&NonEmptyKeyValuePairs<Hash<32>, PlutusData>>,
|
||||
language_views: &[(Language, &[i64])],
|
||||
) -> Option<Hash<32>> {
|
||||
if redeemers.is_none() && language_views.is_empty() && datums.is_none() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let mut preimage: Vec<u8> = Vec::new();
|
||||
if let Some(redeemers) = redeemers {
|
||||
cbor::encode(redeemers, &mut preimage).unwrap();
|
||||
}
|
||||
|
||||
if let Some(datums) = datums {
|
||||
cbor::encode(datums, &mut preimage).unwrap();
|
||||
}
|
||||
|
||||
// NOTE: This doesn't work for PlutusV1, but I don't care.
|
||||
if !language_views.is_empty() {
|
||||
let mut views = language_views.to_vec();
|
||||
// TODO: Derive an Ord instance in Pallas.
|
||||
views.sort_by(|(a, _), (b, _)| match (a, b) {
|
||||
(Language::PlutusV3, Language::PlutusV3) => Ordering::Equal,
|
||||
(Language::PlutusV3, _) => Ordering::Greater,
|
||||
(_, Language::PlutusV3) => Ordering::Less,
|
||||
|
||||
(Language::PlutusV2, Language::PlutusV2) => Ordering::Equal,
|
||||
(Language::PlutusV2, _) => Ordering::Greater,
|
||||
(_, Language::PlutusV2) => Ordering::Less,
|
||||
|
||||
(Language::PlutusV1, Language::PlutusV1) => Ordering::Equal,
|
||||
});
|
||||
cbor::encode(NonEmptyKeyValuePairs::Def(views), &mut preimage).unwrap()
|
||||
}
|
||||
|
||||
Some(Hasher::<256>::hash(&preimage))
|
||||
}
|
||||
|
||||
pub fn default_transaction_body() -> TransactionBody {
|
||||
TransactionBody {
|
||||
auxiliary_data_hash: None,
|
||||
certificates: None,
|
||||
collateral: None,
|
||||
collateral_return: None,
|
||||
donation: None,
|
||||
fee: 0,
|
||||
inputs: Set::from(vec![]),
|
||||
mint: None,
|
||||
network_id: None,
|
||||
outputs: vec![],
|
||||
proposal_procedures: None,
|
||||
reference_inputs: None,
|
||||
required_signers: None,
|
||||
script_data_hash: None,
|
||||
total_collateral: None,
|
||||
treasury_value: None,
|
||||
ttl: None,
|
||||
validity_interval_start: None,
|
||||
voting_procedures: None,
|
||||
withdrawals: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn default_witness_set() -> WitnessSet {
|
||||
WitnessSet {
|
||||
bootstrap_witness: None,
|
||||
native_script: None,
|
||||
plutus_data: None,
|
||||
plutus_v1_script: None,
|
||||
plutus_v2_script: None,
|
||||
plutus_v3_script: None,
|
||||
redeemer: None,
|
||||
vkeywitness: None,
|
||||
}
|
||||
}
|
||||
|
||||
// Build a transaction by repeatedly executing some building logic with different fee and execution
|
||||
// units settings. Stops when a fixed point is reached. The final transaction has corresponding
|
||||
// fees and execution units.
|
||||
pub fn build_transaction<F>(params: &BuildParams, resolved_inputs: &[ResolvedInput], with: F) -> Tx
|
||||
where
|
||||
F: Fn(u64, &[ExUnits]) -> Tx,
|
||||
{
|
||||
let empty_ex_units = || {
|
||||
vec![
|
||||
ExUnits { mem: 0, steps: 0 },
|
||||
ExUnits { mem: 0, steps: 0 },
|
||||
ExUnits { mem: 0, steps: 0 },
|
||||
ExUnits { mem: 0, steps: 0 },
|
||||
]
|
||||
};
|
||||
|
||||
let mut fee = 0;
|
||||
let mut ex_units = empty_ex_units();
|
||||
let mut tx;
|
||||
let mut attempts = 0;
|
||||
loop {
|
||||
tx = with(fee, &ex_units[..]);
|
||||
|
||||
// Convert to minted_tx...
|
||||
let mut serialized_tx = Vec::new();
|
||||
cbor::encode(&tx, &mut serialized_tx).unwrap();
|
||||
|
||||
let mut calculated_ex_units = if resolved_inputs.is_empty() {
|
||||
empty_ex_units()
|
||||
} else {
|
||||
// Compute execution units
|
||||
let minted_tx = cbor::decode(&serialized_tx).unwrap();
|
||||
eval_phase_two(
|
||||
&minted_tx,
|
||||
resolved_inputs,
|
||||
None,
|
||||
None,
|
||||
&SlotConfig::default(),
|
||||
false,
|
||||
|_| (),
|
||||
)
|
||||
.expect("script evaluation failed")
|
||||
.into_iter()
|
||||
.map(|r| r.0.ex_units)
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
calculated_ex_units.extend(empty_ex_units());
|
||||
|
||||
attempts += 1;
|
||||
|
||||
let estimated_fee = {
|
||||
// NOTE: This is a best effort to estimate the number of signatories since signatures
|
||||
// will add an overhead to the fee. Yet, if inputs are locked by native scripts each
|
||||
// requiring multiple signatories, this will unfortunately fall short.
|
||||
//
|
||||
// For similar reasons, it will also over-estimate fees by a small margin for every
|
||||
// script-locked inputs that do not require signatories.
|
||||
//
|
||||
// This is however *acceptable* in our context.
|
||||
let num_signatories = tx.transaction_body.inputs.len()
|
||||
+ tx.transaction_body
|
||||
.required_signers
|
||||
.as_ref()
|
||||
.map(|xs| xs.len())
|
||||
.unwrap_or(0);
|
||||
|
||||
params.fee_constant
|
||||
+ params.fee_coefficient
|
||||
* (5 + ex_units.len() * 16 + num_signatories * 102 + serialized_tx.len()) as u64
|
||||
+ total_execution_cost(params, &ex_units)
|
||||
};
|
||||
|
||||
// Check if we've reached a fixed point, or start over.
|
||||
if fee >= estimated_fee
|
||||
&& calculated_ex_units
|
||||
.iter()
|
||||
.zip(ex_units)
|
||||
.all(|(l, r)| l.eq(&r))
|
||||
{
|
||||
break;
|
||||
} else if attempts >= 3 {
|
||||
panic!("failed to build transaction: did not converge after three attempts.");
|
||||
} else {
|
||||
ex_units = calculated_ex_units;
|
||||
fee = estimated_fee;
|
||||
}
|
||||
}
|
||||
tx
|
||||
}
|
||||
|
||||
pub fn expect_post_alonzo(output: &TransactionOutput) -> &PostAlonzoTransactionOutput {
|
||||
if let TransactionOutput::PostAlonzo(ref o) = output {
|
||||
o
|
||||
} else {
|
||||
panic!("expected PostAlonzo output but got a legacy one.")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
use pallas_codec::utils::CborWrap;
|
||||
use pallas_primitives::conway::{DatumOption, NativeScript, PostAlonzoTransactionOutput, PseudoScript, Value};
|
||||
|
||||
/// Address needs to be a Vec<u8>
|
||||
///
|
||||
pub fn output(
|
||||
address: Vec<u8>,
|
||||
value: Value,
|
||||
datum_option: Option<DatumOption>,
|
||||
script_ref: PseudoScript<NativeScript>,
|
||||
) -> PostAlonzoTransactionOutput
|
||||
{
|
||||
PostAlonzoTransactionOutput {
|
||||
address: address.into(),
|
||||
value,
|
||||
datum_option,
|
||||
script_ref : Some(CborWrap(script_ref.into())),
|
||||
}
|
||||
}
|
|
@ -19,7 +19,7 @@ use super::{
|
|||
},
|
||||
};
|
||||
|
||||
pub async fn send<T>(ctx: TxContext<T>, outputs: Vec<(Address, u64)>) -> Result<String>
|
||||
pub async fn send<T>(ctx: TxContext<T>, target: Vec<(Address, u64)>) -> Result<String>
|
||||
where
|
||||
T: Cardano,
|
||||
{
|
||||
|
@ -35,15 +35,15 @@ where
|
|||
.map(|u| u.input.clone())
|
||||
.collect::<Vec<TransactionInput>>();
|
||||
let x = expect_post_alonzo(&available_utxos[0].output);
|
||||
let mut outputs = vec![];
|
||||
// let mut outputs = outputs.iter().map(|(addr, amount)| {
|
||||
// TransactionOutput::PostAlonzoTransactionOutput {
|
||||
// address: addr,
|
||||
// value: Value::Coin(amount * 1_000_000) ,
|
||||
// datum_option: None,
|
||||
// script_ref: None,
|
||||
// }
|
||||
// }).collect::<Vec<TransactionOutput>>();
|
||||
let mut outputs = target
|
||||
.iter()
|
||||
.map(|tup| PostAlonzoTransactionOutput {
|
||||
address: tup.0.to_vec().into(),
|
||||
value: Value::Coin(tup.1 * 1_000_000),
|
||||
datum_option: None,
|
||||
script_ref: None,
|
||||
})
|
||||
.collect::<Vec<PostAlonzoTransactionOutput>>();
|
||||
outputs.push(
|
||||
// Change
|
||||
PostAlonzoTransactionOutput {
|
||||
|
@ -73,7 +73,7 @@ where
|
|||
);
|
||||
ctx.wallet.sign(&mut tx);
|
||||
let mut serialized_tx = Vec::new();
|
||||
println!("{:#?}", tx);
|
||||
println!("{:?}", tx);
|
||||
encode(&tx, &mut serialized_tx).unwrap();
|
||||
let tx_hash = ctx.cardano.submit(serialized_tx).await?;
|
||||
Ok(tx_hash)
|
||||
|
|
Loading…
Reference in New Issue