use chia_wallet_sdk::driver::{DataStore, SpendContext};
use chia_wallet_sdk::types::Conditions;
use crate::context::inner_spend;
use crate::metadata::DigDataStoreMetadata;
use crate::types::{MerkleCoinSpend, Owner};
use crate::{MerkleError, MerkleResult};
pub fn update_root(
store: &DataStore<DigDataStoreMetadata>,
owner: Owner,
new_metadata: DigDataStoreMetadata,
) -> MerkleResult<MerkleCoinSpend> {
let mut ctx = SpendContext::new();
let launcher_id = store.info.launcher_id;
let owner_puzzle_hash = store.info.owner_puzzle_hash;
let delegated_puzzles = store.info.delegated_puzzles.clone();
let hint_delegated_puzzles = !delegated_puzzles.is_empty();
let new_metadata_condition = DataStore::new_metadata_condition(&mut ctx, new_metadata)?;
let recreate_condition = DataStore::<DigDataStoreMetadata>::owner_create_coin_condition(
&mut ctx,
launcher_id,
owner_puzzle_hash,
delegated_puzzles.clone(),
hint_delegated_puzzles,
)?;
let conditions = Conditions::new()
.with(new_metadata_condition)
.with(recreate_condition);
let owner_spend = inner_spend(&mut ctx, owner, conditions)?;
let store_spend = store.clone().spend(&mut ctx, owner_spend)?;
let child =
DataStore::<DigDataStoreMetadata>::from_spend(&mut ctx, &store_spend, &delegated_puzzles)?
.ok_or(MerkleError::NotDataStore)?;
Ok(MerkleCoinSpend::new(vec![store_spend], Some(child)))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mint::mint_datastore;
use crate::required_signatures;
use crate::types::{Bytes32, DataStore, DataStoreInfo, DelegatedPuzzle};
use chia_puzzle_types::standard::StandardArgs;
use chia_wallet_sdk::driver::{Layer, StandardLayer};
use chia_wallet_sdk::prelude::{TreeHash, MAINNET_CONSTANTS};
use chia_wallet_sdk::signer::{AggSigConstants, RequiredSignature};
use chia_wallet_sdk::test::Simulator;
fn minted_store(
sim: &mut Simulator,
) -> 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 = mint_datastore(
owner.coin,
Owner::Standard(owner.pk),
Bytes32::new([0x5a; 32]),
Some("site".into()),
None,
None,
None,
None,
owner_ph,
vec![],
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 update_round_trips_a_new_root() -> anyhow::Result<()> {
let mut sim = Simulator::new();
let (owner, store) = minted_store(&mut sim)?;
let new_root = Bytes32::new([0x77; 32]);
let new_metadata = DigDataStoreMetadata {
root_hash: new_root,
label: Some("site".into()),
..Default::default()
};
let built = update_root(&store, Owner::Standard(owner.pk), new_metadata)?;
let child = built.child.clone().expect("update yields a child");
assert_eq!(child.info.metadata.root_hash, new_root, "root updated");
assert_eq!(
child.info.launcher_id, store.info.launcher_id,
"launcher id preserved"
);
assert_eq!(
child.info.owner_puzzle_hash, store.info.owner_puzzle_hash,
"owner preserved"
);
sim.spend_coins(built.coin_spends.clone(), std::slice::from_ref(&owner.sk))?;
Ok(())
}
#[test]
fn update_recreate_targets_the_original_owner_coin() -> anyhow::Result<()> {
let mut sim = Simulator::new();
let (owner, store) = minted_store(&mut sim)?;
let new_metadata = DigDataStoreMetadata {
root_hash: Bytes32::new([0x77; 32]),
label: Some("site".into()),
..Default::default()
};
let built = update_root(&store, Owner::Standard(owner.pk), new_metadata.clone())?;
let child = built.child.clone().expect("update yields a child");
let mut ctx = SpendContext::new();
let expected_layers = DataStoreInfo::new(
store.info.launcher_id,
new_metadata,
store.info.owner_puzzle_hash,
vec![],
)
.into_layers_without_delegation_layer(StandardLayer::new(owner.pk));
let expected_puzzle = expected_layers.construct_puzzle(&mut ctx)?;
let expected_coin_ph: Bytes32 = ctx.tree_hash(expected_puzzle).into();
assert_eq!(
child.coin.puzzle_hash, expected_coin_ph,
"the recreate CREATE_COIN targets the preserved owner (a wrong owner would differ)"
);
sim.spend_coins(built.coin_spends.clone(), std::slice::from_ref(&owner.sk))?;
Ok(())
}
#[test]
fn update_preserves_the_delegation_set() -> anyhow::Result<()> {
let mut sim = Simulator::new();
let owner = sim.bls(1_000_000);
let owner_ph: Bytes32 = StandardArgs::curry_tree_hash(owner.pk).into();
let delegated_puzzles = vec![
DelegatedPuzzle::Admin(TreeHash::new([0x11; 32])),
DelegatedPuzzle::Writer(TreeHash::new([0x22; 32])),
];
let built = mint_datastore(
owner.coin,
Owner::Standard(owner.pk),
Bytes32::new([0x5a; 32]),
None,
None,
None,
None,
None,
owner_ph,
delegated_puzzles.clone(),
0,
)?;
sim.spend_coins(built.coin_spends.clone(), std::slice::from_ref(&owner.sk))?;
let store = built.child.expect("mint yields a child");
assert_eq!(
store.info.delegated_puzzles, delegated_puzzles,
"the mint anchors the delegation set (test precondition)"
);
let updated = update_root(
&store,
Owner::Standard(owner.pk),
DigDataStoreMetadata {
root_hash: Bytes32::new([0x77; 32]),
..Default::default()
},
)?;
let child = updated.child.expect("update yields a child");
assert_eq!(
child.info.delegated_puzzles, delegated_puzzles,
"the delegation set survives the update"
);
sim.spend_coins(updated.coin_spends.clone(), std::slice::from_ref(&owner.sk))?;
Ok(())
}
#[test]
fn update_requires_a_single_agg_sig_me() -> anyhow::Result<()> {
let mut sim = Simulator::new();
let (owner, store) = minted_store(&mut sim)?;
let built = update_root(
&store,
Owner::Standard(owner.pk),
DigDataStoreMetadata {
root_hash: Bytes32::new([0x01; 32]),
..Default::default()
},
)?;
let constants = AggSigConstants::from(&*MAINNET_CONSTANTS);
let required = 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(())
}
#[test]
fn update_replaces_metadata_wholesale() -> anyhow::Result<()> {
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 = mint_datastore(
owner.coin,
Owner::Standard(owner.pk),
Bytes32::new([0x5a; 32]),
None,
None,
None,
Some(Bytes32::new([0xcc; 32])),
None,
owner_ph,
vec![],
0,
)?;
sim.spend_coins(built.coin_spends.clone(), std::slice::from_ref(&owner.sk))?;
let store = built.child.expect("mint yields a child");
assert_eq!(
store.info.metadata.program_hash,
Some(Bytes32::new([0xcc; 32]))
);
let updated = update_root(
&store,
Owner::Standard(owner.pk),
DigDataStoreMetadata {
root_hash: Bytes32::new([0x99; 32]),
..Default::default()
},
)?;
let child = updated.child.expect("update yields a child");
assert_eq!(
child.info.metadata.program_hash, None,
"omitted program_hash is dropped (wholesale replacement)"
);
Ok(())
}
}