1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
use chia_consensus::opcodes::SEND_MESSAGE;
use chia_protocol::Bytes32;
use chia_sdk_types::Condition;
use clvm_traits::FromClvm;
use clvmr::{Allocator, NodePtr};
use num_bigint::BigInt;
use crate::{DriverError, Facts, Spend, VaultMessage, parse_delegated_spend, parse_vault_message};
#[derive(Debug, Clone)]
pub struct VaultSpendSummary {
pub child: Option<VaultOutput>,
pub drop_coins: Vec<DropCoin>,
pub messages: Vec<VaultMessage>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct VaultOutput {
pub custody_hash: Bytes32,
pub amount: u64,
}
impl VaultOutput {
pub fn new(custody_hash: Bytes32, amount: u64) -> Self {
Self {
custody_hash,
amount,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct DropCoin {
pub puzzle_hash: Bytes32,
pub amount: u64,
}
impl DropCoin {
pub fn new(puzzle_hash: Bytes32, amount: u64) -> Self {
Self {
puzzle_hash,
amount,
}
}
}
pub fn parse_vault_delegated_spend(
facts: &mut Facts,
allocator: &mut Allocator,
delegated_spend: Spend,
) -> Result<VaultSpendSummary, DriverError> {
let conditions = parse_delegated_spend(allocator, delegated_spend)?;
let mut child = None;
let mut drop_coins = Vec::new();
let mut messages = Vec::new();
for condition in conditions {
match condition {
Condition::AssertPuzzleAnnouncement(condition) => {
facts.assert_puzzle_announcement(condition.announcement_id);
}
Condition::AssertConcurrentSpend(condition) => {
facts.assert_spend(condition.coin_id);
}
Condition::AssertBeforeSecondsAbsolute(condition) => {
facts.update_actual_expiration_time(condition.seconds);
}
Condition::ReserveFee(condition) => {
facts.add_reserved_fees(condition.amount);
}
Condition::CreateCoin(condition) => {
// If a child of a singleton (the vault in this case) is odd, due to the way the singleton
// puzzle works, we know that it will be the new coin for this singleton. And because this
// parsing is running at the delegated spend layer (i.e., inner puzzle), we know that the
// puzzle hash is actually the custody hash, rather than the singleton's full puzzle hash.
//
// Note that if a CREATE_COIN condition with an odd child is not included in the delegated
// spend, we assume that the singleton is being melted. This is technically only true if
// the special (puzzle specific) MELT_SINGLETON condition is included, but if it's not, then
// the transaction is invalid anyways.
if condition.amount % 2 == 1 {
child = Some(VaultOutput {
custody_hash: condition.puzzle_hash,
amount: condition.amount,
});
} else {
drop_coins.push(DropCoin {
puzzle_hash: condition.puzzle_hash,
amount: condition.amount,
});
}
}
Condition::SendMessage(condition) => {
// If the vault sends a message to a coin, we must first validate the message's format, and then ensure
// that the corresponding coin spend is revealed and matches the delegated puzzle hash in the message.
// These coins are considered "linked", meaning that their conditions should also be treated as fact if
// they can be validated to be impossible to circumvent.
let vault_message = parse_vault_message(allocator, condition)?;
messages.push(vault_message);
}
Condition::Other(condition) => {
let (opcode, _) = <(BigInt, NodePtr)>::from_clvm(allocator, condition)?;
// If an unparseable opcode matches the SEND_MESSAGE opcode, we return an error.
// This is to make sure that we don't accidentally let a valid message slip through the cracks.
// If this happened, we wouldn't enforce the spend to be revealed, thus clear signing would be insecure.
if opcode == BigInt::from(SEND_MESSAGE) {
return Err(DriverError::InvalidVaultMessageFormat);
}
}
_ => {}
}
}
Ok(VaultSpendSummary {
child,
drop_coins,
messages,
})
}