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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
use std::collections::VecDeque;
use chia_protocol::{Bytes32, Coin};
use chia_puzzles::SETTLEMENT_PAYMENT_HASH;
use chia_sdk_types::{Condition, conditions::CreateCoin};
use crate::{
BURN_PUZZLE_HASH, Cat, DriverError, Facts, Nft, ParsedAsset, ParsedMemos, RevealedCoinSpend,
RevealedP2Puzzle, Reveals, SpendContext, parse_memos,
};
#[derive(Debug, Clone)]
pub struct ParsedChild {
pub asset: ParsedAsset,
pub memos: ParsedMemos,
pub transfer_type: TransferType,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TransferType {
Sent,
Burned,
Offered,
OfferPreSplit(OfferPreSplitInfo),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OfferPreSplitInfo {
pub launcher_id: Bytes32,
pub nonce: usize,
pub fixed_conditions: Vec<Condition>,
pub settlement_amount: u64,
}
pub fn parse_children(
reveals: &mut Reveals,
facts: &mut Facts,
ctx: &mut SpendContext,
asset: &ParsedAsset,
spend: RevealedCoinSpend,
conditions: &[Condition],
is_claw_back: bool,
) -> Result<Vec<ParsedChild>, DriverError> {
// Now, we should be able to assume that the conditions will be output if the transaction is valid.
let mut children = Vec::new();
// We should parse CAT children up front, so that we can hydrate their details when adding children later.
let mut cats = if matches!(asset, ParsedAsset::Cat(_)) {
if let Some(cats) = Cat::parse_children(ctx, spend.coin, spend.puzzle, spend.solution)? {
VecDeque::from(cats)
} else {
return Err(DriverError::MissingChild);
}
} else {
VecDeque::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);
}
// We shouldn't allow a claw back spend to say when the transaction will expire.
// Otherwise, it could pretend that it's impossible for the clawback to expire
// before the transaction expires, which would be a security vulnerability.
Condition::AssertBeforeSecondsAbsolute(condition) if !is_claw_back => {
facts.update_actual_expiration_time(condition.seconds);
}
Condition::ReserveFee(condition) => {
facts.add_reserved_fees(condition.amount);
}
Condition::CreateCoin(condition) => {
match &asset {
// All XCH and bulletin children are considered to be XCH.
// However, ephemeral bulletin children are hydrated later based on the spend.
ParsedAsset::Xch(_) | ParsedAsset::Bulletin(_) => {
let memos = parse_memos(reveals, ctx, *condition, false);
let transfer_type =
calculate_transfer_type(reveals, &memos, condition.amount);
children.push(ParsedChild {
asset: ParsedAsset::Xch(Coin::new(
spend.coin.coin_id(),
condition.puzzle_hash,
condition.amount,
)),
memos,
transfer_type,
});
}
// For NFTs, even amount children are XCH coins. If the amount is odd, it's the
// next NFT singleton coin in the lineage.
ParsedAsset::Nft(_) => {
if condition.amount % 2 == 1 {
let Some(nft) =
Nft::parse_child(ctx, spend.coin, spend.puzzle, spend.solution)?
else {
return Err(DriverError::MissingChild);
};
let memos = parse_memos(reveals, ctx, *condition, true);
let transfer_type =
calculate_transfer_type(reveals, &memos, condition.amount);
children.push(ParsedChild {
asset: ParsedAsset::Nft(nft),
memos,
transfer_type,
});
} else {
let memos = parse_memos(reveals, ctx, *condition, false);
let transfer_type =
calculate_transfer_type(reveals, &memos, condition.amount);
children.push(ParsedChild {
asset: ParsedAsset::Xch(Coin::new(
spend.coin.coin_id(),
condition.puzzle_hash,
condition.amount,
)),
memos,
transfer_type,
});
}
}
// CATs never output anything other than CAT children.
ParsedAsset::Cat(parent) => {
let cat = cats.pop_front().ok_or(DriverError::MissingChild)?;
// This prevents an attack where someone tricks you into spending a CAT, sending it
// back to you, and wrapping it in a revocation layer that they control.
if cat.info.hidden_puzzle_hash != parent.info.hidden_puzzle_hash {
return Err(DriverError::RevocableChild);
}
let memos = parse_memos(
reveals,
ctx,
CreateCoin::new(
cat.info.p2_puzzle_hash,
condition.amount,
condition.memos,
),
true,
);
let transfer_type =
calculate_transfer_type(reveals, &memos, condition.amount);
children.push(ParsedChild {
asset: ParsedAsset::Cat(cat),
memos,
transfer_type,
});
}
}
}
_ => {}
}
}
if !cats.is_empty() {
return Err(DriverError::MissingChild);
}
Ok(children)
}
fn calculate_transfer_type(
reveals: &Reveals,
memos: &ParsedMemos,
input_amount: u64,
) -> TransferType {
if memos.p2_puzzle_hash == BURN_PUZZLE_HASH {
TransferType::Burned
} else if memos.p2_puzzle_hash == SETTLEMENT_PAYMENT_HASH.into() {
TransferType::Offered
} else if memos.clawback.is_none()
&& let Some(RevealedP2Puzzle::P2ConditionsOrSingleton(reveal)) =
reveals.p2_puzzle(memos.p2_puzzle_hash.into())
&& let Some(fixed_conditions) = &memos.fixed_conditions
{
let mut reserved_fee = 0;
for condition in fixed_conditions {
if let Condition::ReserveFee(condition) = condition {
reserved_fee += condition.amount;
}
}
TransferType::OfferPreSplit(OfferPreSplitInfo {
launcher_id: reveal.launcher_id,
nonce: reveal.nonce,
fixed_conditions: fixed_conditions.clone(),
settlement_amount: input_amount.saturating_sub(reserved_fee),
})
} else {
TransferType::Sent
}
}