use dig_merkle::{melt, mint_datastore_with_kind, update_root, StoreKind};
use crate::error::DigStoreResult;
use crate::size::SizeBucket;
use crate::types::{Bytes32, Coin, DataStore, DigDataStoreMetadata, MerkleCoinSpend};
pub use dig_merkle::Owner as StoreOwner;
#[derive(Debug, Clone)]
pub struct CreateStoreParams {
pub root_hash: Bytes32,
pub size: SizeBucket,
pub label: Option<String>,
pub description: Option<String>,
pub program_hash: Option<Bytes32>,
pub fee: u64,
}
pub fn create_store(
parent_coin: Coin,
owner: StoreOwner,
owner_puzzle_hash: Bytes32,
params: CreateStoreParams,
) -> DigStoreResult<MerkleCoinSpend> {
Ok(mint_datastore_with_kind(
StoreKind::File,
parent_coin,
owner,
params.root_hash,
params.label,
params.description,
None, params.program_hash,
Some(params.size),
owner_puzzle_hash,
Vec::new(), params.fee,
)?)
}
pub fn modify_store(
store: &DataStore<DigDataStoreMetadata>,
owner: StoreOwner,
new_root: Bytes32,
) -> DigStoreResult<MerkleCoinSpend> {
let new_metadata = DigDataStoreMetadata {
root_hash: new_root,
..store.info.metadata.clone()
};
Ok(update_root(store, owner, new_metadata)?)
}
pub fn melt_store(
store: &DataStore<DigDataStoreMetadata>,
owner: StoreOwner,
) -> DigStoreResult<MerkleCoinSpend> {
Ok(melt(store, owner)?)
}
#[cfg(test)]
mod tests {
use super::*;
use chia_puzzle_types::standard::StandardArgs;
use chia_wallet_sdk::test::Simulator;
fn minted_store(
sim: &mut Simulator,
size: SizeBucket,
) -> anyhow::Result<(
chia_wallet_sdk::test::BlsPairWithCoin,
DataStore<DigDataStoreMetadata>,
)> {
let owner = sim.bls(1_000_000);
let owner_ph: Bytes32 = StandardArgs::curry_tree_hash(owner.pk).into();
let built = create_store(
owner.coin,
StoreOwner::Standard(owner.pk),
owner_ph,
CreateStoreParams {
root_hash: Bytes32::new([0x5a; 32]),
size,
label: Some("docs".into()),
description: None,
program_hash: None,
fee: 0,
},
)?;
sim.spend_coins(built.coin_spends.clone(), std::slice::from_ref(&owner.sk))?;
Ok((owner, built.child.expect("mint yields a child")))
}
#[test]
fn create_store_anchors_root_and_size() -> anyhow::Result<()> {
let mut sim = Simulator::new();
let size = SizeBucket::from_exponent(7).unwrap();
let (_owner, store) = minted_store(&mut sim, size)?;
assert_eq!(store.info.metadata.root_hash, Bytes32::new([0x5a; 32]));
assert_eq!(store.info.metadata.size_bucket, Some(size));
assert_eq!(store.info.metadata.label, Some("docs".into()));
Ok(())
}
#[test]
fn modify_store_updates_root_and_preserves_metadata() -> anyhow::Result<()> {
let mut sim = Simulator::new();
let size = SizeBucket::from_exponent(5).unwrap();
let (owner, store) = minted_store(&mut sim, size)?;
let new_root = Bytes32::new([0x77; 32]);
let built = modify_store(&store, StoreOwner::Standard(owner.pk), new_root)?;
let child = built.child.clone().expect("modify yields a child");
assert_eq!(child.info.metadata.root_hash, new_root);
assert_eq!(
child.info.metadata.size_bucket,
Some(size),
"the anchored size bucket is preserved across a modify"
);
assert_eq!(child.info.metadata.label, Some("docs".into()));
assert_eq!(child.info.launcher_id, store.info.launcher_id);
sim.spend_coins(built.coin_spends.clone(), std::slice::from_ref(&owner.sk))?;
Ok(())
}
#[test]
fn melt_store_closes_the_store() -> anyhow::Result<()> {
let mut sim = Simulator::new();
let size = SizeBucket::from_exponent(3).unwrap();
let (owner, store) = minted_store(&mut sim, size)?;
let built = melt_store(&store, StoreOwner::Standard(owner.pk))?;
assert!(built.child.is_none(), "a melt leaves no successor");
sim.spend_coins(built.coin_spends.clone(), std::slice::from_ref(&owner.sk))?;
Ok(())
}
#[test]
fn create_store_requires_a_single_owner_signature() -> anyhow::Result<()> {
use chia_wallet_sdk::prelude::MAINNET_CONSTANTS;
use chia_wallet_sdk::signer::{AggSigConstants, RequiredSignature};
let mut sim = Simulator::new();
let owner = sim.bls(1_000_000);
let owner_ph: Bytes32 = StandardArgs::curry_tree_hash(owner.pk).into();
let built = create_store(
owner.coin,
StoreOwner::Standard(owner.pk),
owner_ph,
CreateStoreParams {
root_hash: Bytes32::new([0x01; 32]),
size: SizeBucket::from_exponent(0).unwrap(),
label: None,
description: None,
program_hash: None,
fee: 0,
},
)?;
let constants = AggSigConstants::from(&*MAINNET_CONSTANTS);
let required = dig_merkle::required_signatures(&built.coin_spends, &constants)?;
assert_eq!(required.len(), 1, "one AGG_SIG_ME expected");
match &required[0] {
RequiredSignature::Bls(bls) => assert_eq!(bls.public_key, owner.pk),
RequiredSignature::Secp(_) => panic!("standard owner uses a BLS key"),
}
Ok(())
}
}