Skip to content

Commit

Permalink
Change types system to support any utxo-based blockchain by the library
Browse files Browse the repository at this point in the history
  • Loading branch information
gostkin committed Mar 21, 2023
1 parent 0d4297e commit 3185156
Show file tree
Hide file tree
Showing 37 changed files with 5,469 additions and 319 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
@@ -1,6 +1,7 @@
[workspace]
members = [
"blockchain-source",
"cardano-utils",
"core",
"deps",
"fraos",
Expand Down
3 changes: 2 additions & 1 deletion blockchain-source/src/source.rs
@@ -1,7 +1,8 @@
use crate::cardano::Point;
use anyhow::Result;
use async_trait::async_trait;
use dcspark_core::{BlockId, BlockNumber, TransactionId};
use dcspark_core::tx::TransactionId;
use dcspark_core::{BlockId, BlockNumber};

/// Trait that defines how a we are to handle a source of event.
///
Expand Down
18 changes: 18 additions & 0 deletions cardano-utils/Cargo.toml
@@ -0,0 +1,18 @@
[package]
name = "cardano-utils"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
deps = { version = "0.1.0", path = "../deps" }
dcspark-core = { version = "0.1.0", path = "../core" }

anyhow = { version = "1" }
bech32 = { version = "0.9" }
cardano-multiplatform-lib = { git = "https://github.com/dcSpark/cardano-multiplatform-lib.git", branch = "egostkin/utxo-selection-changes", commit = "a41264b88c9b48ac890c2d046fe916f224fccabb" }
cryptoxide = "0.4.2"
hex = { version = "0.4" }
rand = "0.8.5"
serde = { version = "1.0.144", features = ["derive", "rc" ] }
112 changes: 112 additions & 0 deletions cardano-utils/src/cip14.rs
@@ -0,0 +1,112 @@
use anyhow::{anyhow, Context as _, Result};
use bech32::ToBase32;
use cryptoxide::hashing::blake2b::Blake2b;
use dcspark_core::{AssetName, PolicyId, TokenId};

const HRP: &str = "asset";

pub fn fingerprint(policy: &PolicyId, name: &AssetName) -> Result<TokenId> {
let mut buf = vec![0u8; 28 + name.as_ref().len() / 2];
hex::decode_to_slice(policy.as_ref(), &mut buf[..28])
.with_context(|| anyhow!("Failed to decode PolicyId: {policy}"))?;
hex::decode_to_slice(name.as_ref(), &mut buf[28..])
.with_context(|| anyhow!("Failed to decode AssetName: {name}"))?;

let b2b = Blake2b::<{ 20 * 8 }>::new().update(&buf);

let mut out = [0; 20];
b2b.finalize_at(&mut out);

bech32::encode(HRP, out.to_base32(), bech32::Variant::Bech32)
// this probably can't fail
.context("Couldn't compute bech32 asset fingerprint")
.map(TokenId::new)
}

#[cfg(test)]
mod tests {
use super::*;

struct TestVector {
policy_id: PolicyId,
asset_name: AssetName,
asset_fingerprint: TokenId,
}

const TESTS: &[TestVector] = &[
TestVector {
policy_id: PolicyId::new_static(
"7eae28af2208be856f7a119668ae52a49b73725e326dc16579dcc373",
),
asset_name: AssetName::new_static(""),
asset_fingerprint: TokenId::new_static("asset1rjklcrnsdzqp65wjgrg55sy9723kw09mlgvlc3"),
},
TestVector {
policy_id: PolicyId::new_static(
"7eae28af2208be856f7a119668ae52a49b73725e326dc16579dcc37e",
),
asset_name: AssetName::new_static(""),
asset_fingerprint: TokenId::new_static("asset1nl0puwxmhas8fawxp8nx4e2q3wekg969n2auw3"),
},
TestVector {
policy_id: PolicyId::new_static(
"1e349c9bdea19fd6c147626a5260bc44b71635f398b67c59881df209",
),
asset_name: AssetName::new_static(""),
asset_fingerprint: TokenId::new_static("asset1uyuxku60yqe57nusqzjx38aan3f2wq6s93f6ea"),
},
TestVector {
policy_id: PolicyId::new_static(
"7eae28af2208be856f7a119668ae52a49b73725e326dc16579dcc373",
),
asset_name: AssetName::new_static("504154415445"),
asset_fingerprint: TokenId::new_static("asset13n25uv0yaf5kus35fm2k86cqy60z58d9xmde92"),
},
TestVector {
policy_id: PolicyId::new_static(
"1e349c9bdea19fd6c147626a5260bc44b71635f398b67c59881df209",
),
asset_name: AssetName::new_static("504154415445"),
asset_fingerprint: TokenId::new_static("asset1hv4p5tv2a837mzqrst04d0dcptdjmluqvdx9k3"),
},
TestVector {
policy_id: PolicyId::new_static(
"1e349c9bdea19fd6c147626a5260bc44b71635f398b67c59881df209",
),
asset_name: AssetName::new_static(
"7eae28af2208be856f7a119668ae52a49b73725e326dc16579dcc373",
),
asset_fingerprint: TokenId::new_static("asset1aqrdypg669jgazruv5ah07nuyqe0wxjhe2el6f"),
},
TestVector {
policy_id: PolicyId::new_static(
"7eae28af2208be856f7a119668ae52a49b73725e326dc16579dcc373",
),
asset_name: AssetName::new_static(
"1e349c9bdea19fd6c147626a5260bc44b71635f398b67c59881df209",
),
asset_fingerprint: TokenId::new_static("asset17jd78wukhtrnmjh3fngzasxm8rck0l2r4hhyyt"),
},
TestVector {
policy_id: PolicyId::new_static(
"7eae28af2208be856f7a119668ae52a49b73725e326dc16579dcc373",
),
asset_name: AssetName::new_static(
"0000000000000000000000000000000000000000000000000000000000000000",
),
asset_fingerprint: TokenId::new_static("asset1pkpwyknlvul7az0xx8czhl60pyel45rpje4z8w"),
},
];

#[test]
fn test_vectors() {
for (index, test) in TESTS.iter().enumerate() {
let computed = fingerprint(&test.policy_id, &test.asset_name).unwrap();

assert_eq!(
computed, test.asset_fingerprint,
"Failed to run the vector test {index}"
)
}
}
}
118 changes: 118 additions & 0 deletions cardano-utils/src/conversion.rs
@@ -0,0 +1,118 @@
use cardano_multiplatform_lib::ledger::common::value::{BigNum, Coin};
use cardano_multiplatform_lib::{MultiAsset, PolicyID};
use dcspark_core::tx::TransactionAsset;
use dcspark_core::{AssetName, PolicyId, Regulated, TokenId, Value};
use deps::bigdecimal::ToPrimitive;
use std::collections::HashMap;
use anyhow::anyhow;

pub fn value_to_csl_coin(value: &Value<Regulated>) -> anyhow::Result<Coin> {
Ok(
cardano_multiplatform_lib::ledger::common::value::Coin::from(
value
.to_u64()
.ok_or_else(|| anyhow!("Can't convert input balance to u64"))?,
),
)
}

pub fn csl_coin_to_value(value: &Coin) -> anyhow::Result<Value<Regulated>> {
Ok(Value::new(deps::bigdecimal::BigDecimal::from(u64::from(
*value,
))))
}

pub fn multiasset_iter<F>(
value: &cardano_multiplatform_lib::ledger::common::value::Value,
mut f: F,
) -> anyhow::Result<()>
where
F: FnMut(
&PolicyID,
&cardano_multiplatform_lib::AssetName,
&Option<BigNum>,
) -> anyhow::Result<()>,
{
if let Some(ma) = &value.multiasset() {
let policy_ids = ma.keys();
for policy_id_index in 0..policy_ids.len() {
let policy_id = policy_ids.get(policy_id_index);
let assets = if let Some(assets) = ma.get(&policy_id) {
assets
} else {
continue;
};
let asset_names = assets.keys();
for asset_name_index in 0..asset_names.len() {
let asset_name = asset_names.get(asset_name_index);
let quantity = assets.get(&asset_name);
f(&policy_id, &asset_name, &quantity)?;
}
}
}
Ok(())
}

pub fn tokens_to_csl_value(
coin: &Value<Regulated>,
assets: &HashMap<TokenId, TransactionAsset>,
) -> anyhow::Result<cardano_multiplatform_lib::ledger::common::value::Value> {
let coin = value_to_csl_coin(coin)?;
let mut value = cardano_multiplatform_lib::ledger::common::value::Value::new(&coin);
if !assets.is_empty() {
let mut multi_assets = MultiAsset::new();
for (_, asset) in assets.iter() {
let policy_id =
PolicyID::from_bytes(hex::decode(asset.policy_id.to_string()).map_err(|err| {
anyhow!("Failed to decode the policy id: hex error {err}")
})?)
.map_err(|error| {
anyhow!("Failed to decode the policy id: {error}")
})?;
let asset_name = cardano_multiplatform_lib::AssetName::new(
hex::decode(asset.asset_name.as_ref()).map_err(|err| {
anyhow!("Failed to decode the asset name {err}")
})?,
).map_err(|err| anyhow!("can't decode asset_name {err}"))?;

let value =
BigNum::from_str(&asset.quantity.truncate().to_string()).map_err(|error| {
anyhow!(
"Value {value} ({fingerprint}) was not within the boundaries: {error}",
value = asset.quantity.truncate(),
fingerprint = asset.fingerprint,
)
})?;

multi_assets.set_asset(&policy_id, &asset_name, &value);
}

value.set_multiasset(&multi_assets);
}

Ok(value)
}

pub fn csl_value_to_tokens(
value: &cardano_multiplatform_lib::ledger::common::value::Value,
) -> anyhow::Result<(Value<Regulated>, HashMap<TokenId, TransactionAsset>)> {
let coin = csl_coin_to_value(&value.coin())?;
let mut tokens = HashMap::<TokenId, TransactionAsset>::new();
multiasset_iter(value, |policy_id, asset_name, quantity| {
let policy_id = PolicyId::new(hex::encode(policy_id.to_bytes()));
let asset_name = AssetName::new(hex::encode(asset_name.to_bytes()));
let fingerprint = crate::cip14::fingerprint(&policy_id, &asset_name).map_err(|err| {
anyhow!("Can't create fingerprint {err}")
})?;
let quantity = quantity.ok_or_else(|| anyhow!("not found asset quantity"))?;
let asset = TransactionAsset {
policy_id,
asset_name,
fingerprint: fingerprint.clone(),
quantity: csl_coin_to_value(&quantity)?,
};
tokens.insert(fingerprint, asset);
Ok(())
})?;
Ok((coin, tokens))
}
4 changes: 4 additions & 0 deletions cardano-utils/src/lib.rs
@@ -0,0 +1,4 @@
pub mod cip14;
pub mod conversion;
pub mod multisig_plan;
pub mod network_id;
73 changes: 73 additions & 0 deletions cardano-utils/src/multisig_plan.rs
@@ -0,0 +1,73 @@
use anyhow::{anyhow, Context as _};
use cardano_multiplatform_lib::address::{Address, EnterpriseAddress, StakeCredential};
use cardano_multiplatform_lib::crypto::{Ed25519KeyHash, ScriptHash};
use cardano_multiplatform_lib::{NativeScript, NativeScripts, ScriptNOfK, ScriptPubkey};
use deps::serde_json;
use serde::{Deserialize, Deserializer};
use std::path::Path;

#[derive(Debug, Clone, Deserialize)]
pub struct MultisigPlan {
pub quorum: u32,
pub keys: Vec<Hash>,
}

impl MultisigPlan {
pub fn load(path: impl AsRef<Path>) -> anyhow::Result<Self> {
let path = path.as_ref();
let file = std::fs::File::open(path)
.with_context(|| anyhow!("failed to read multisig plan from {}", path.display()))?;
serde_json::from_reader(file)
.with_context(|| anyhow!("failed to read multisig plan from {}", path.display()))
}

pub fn hash(&self) -> ScriptHash {
let script = self.to_script().get(0).hash().to_bytes();

ScriptHash::from_bytes(script)
.map_err(|error| anyhow!("Invalid hash {}", error))
.expect("Script should be valid all the time already")
}

pub fn address(&self, network_id: u8) -> Address {
let native_bytes = self.hash();

let address = StakeCredential::from_scripthash(&native_bytes);
EnterpriseAddress::new(network_id, &address).to_address()
}

pub fn to_script(&self) -> NativeScripts {
let keys = {
let mut scripts = NativeScripts::new();

for key in self.keys.iter().map(|k| &k.0) {
scripts.add(&NativeScript::new_script_pubkey(&ScriptPubkey::new(key)));
}

scripts
};

let mut scripts = NativeScripts::new();
let script = ScriptNOfK::new(self.quorum, &keys);
let script = NativeScript::new_script_n_of_k(&script);
scripts.add(&script);

scripts
}
}

#[derive(Debug, Clone, Deserialize)]
pub struct Hash(#[serde(deserialize_with = "deserialize_key_hash")] pub Ed25519KeyHash);

fn deserialize_key_hash<'de, D>(deserializer: D) -> Result<Ed25519KeyHash, D::Error>
where
D: Deserializer<'de>,
D::Error: serde::de::Error,
{
use serde::de::Error as _;

let bytes = String::deserialize(deserializer)?;
let bytes = hex::decode(bytes).map_err(D::Error::custom)?;

Ed25519KeyHash::from_bytes(bytes).map_err(D::Error::custom)
}

0 comments on commit 3185156

Please sign in to comment.