Skip to content

Commit d309678

Browse files
committed
Separate Mint from Spend
1 parent ec9de09 commit d309678

File tree

2 files changed

+119
-102
lines changed

2 files changed

+119
-102
lines changed

wardrobe/money/src/lib.rs

+107-87
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,28 @@ impl<const ID: u8> Cash for Coin<ID> {
2626
const ID: u8 = ID;
2727
}
2828

29-
// use log::info;
29+
#[derive(
30+
Serialize,
31+
Deserialize,
32+
PartialEq,
33+
Eq,
34+
PartialOrd,
35+
Ord,
36+
Clone,
37+
Encode,
38+
Decode,
39+
Hash,
40+
Debug,
41+
TypeInfo,
42+
)]
43+
/// A typical spend transaction where some coins are consumed and others are created.
44+
/// Input value must exceed output value. The difference is burned and reflected in the
45+
/// transaction's priority.
46+
///
47+
/// This is the primary way to move money around the economy. Allows managing your own
48+
/// coins as well as paying others.
49+
pub struct Spend<const ID: u8>;
3050

31-
/// The main constraint checker for the money piece. Allows spending and minting tokens.
3251
#[derive(
3352
Serialize,
3453
Deserialize,
@@ -43,16 +62,9 @@ impl<const ID: u8> Cash for Coin<ID> {
4362
Debug,
4463
TypeInfo,
4564
)]
46-
pub enum MoneyConstraintChecker<const ID: u8> {
47-
/// A typical spend transaction where some coins are consumed and others are created.
48-
/// Input value must exceed output value. The difference is burned and reflected in the
49-
/// transaction's priority.
50-
Spend,
51-
/// A mint transaction that creates no coins out of the void. In a real-world chain,
52-
/// this should be protected somehow, or not included at all. For now it is publicly
53-
/// available. I'm adding it to explore multiple validation paths in a single piece.
54-
Mint,
55-
}
65+
/// A mint transaction that creates no coins out of the void. In a real-world chain,
66+
/// this should be protected somehow, or not included at all.
67+
pub struct Mint<const ID: u8>;
5668

5769
/// A single coin in the fungible money system.
5870
/// A new-type wrapper around a `u128` value.
@@ -82,13 +94,15 @@ impl<const ID: u8> Coin<ID> {
8294
where
8395
V: Verifier,
8496
OV: Verifier + From<V>,
85-
OC: tuxedo_core::ConstraintChecker<OV> + From<MoneyConstraintChecker<ID>>,
97+
// FIXME https://github.com/Off-Narrative-Labs/Tuxedo/issues/177
98+
// Do these bounds respect nested piece aggregation?
99+
OC: tuxedo_core::ConstraintChecker<OV> + From<Mint<ID>>,
86100
{
87101
Transaction {
88102
inputs: vec![],
89103
peeks: vec![],
90104
outputs: vec![(Self::new(amt), v).into()],
91-
checker: MoneyConstraintChecker::Mint.into(),
105+
checker: Mint.into(),
92106
}
93107
}
94108
}
@@ -135,7 +149,7 @@ pub enum ConstraintCheckerError {
135149
ZeroValueCoin,
136150
}
137151

138-
impl<const ID: u8> SimpleConstraintChecker for MoneyConstraintChecker<ID> {
152+
impl<const ID: u8> SimpleConstraintChecker for Spend<ID> {
139153
type Error = ConstraintCheckerError;
140154

141155
fn check(
@@ -144,78 +158,84 @@ impl<const ID: u8> SimpleConstraintChecker for MoneyConstraintChecker<ID> {
144158
_peeks: &[DynamicallyTypedData],
145159
output_data: &[DynamicallyTypedData],
146160
) -> Result<TransactionPriority, Self::Error> {
147-
match &self {
148-
Self::Spend => {
149-
// Check that we are consuming at least one input
150-
ensure!(
151-
!input_data.is_empty(),
152-
ConstraintCheckerError::SpendingNothing
153-
);
154-
155-
let mut total_input_value: u128 = 0;
156-
let mut total_output_value: u128 = 0;
157-
158-
// Check that sum of input values < output values
159-
for input in input_data {
160-
let utxo_value = input
161-
.extract::<Coin<ID>>()
162-
.map_err(|_| ConstraintCheckerError::BadlyTyped)?
163-
.0;
164-
total_input_value = total_input_value
165-
.checked_add(utxo_value)
166-
.ok_or(ConstraintCheckerError::ValueOverflow)?;
167-
}
168-
169-
for utxo in output_data {
170-
let utxo_value = utxo
171-
.extract::<Coin<ID>>()
172-
.map_err(|_| ConstraintCheckerError::BadlyTyped)?
173-
.0;
174-
ensure!(utxo_value > 0, ConstraintCheckerError::ZeroValueCoin);
175-
total_output_value = total_output_value
176-
.checked_add(utxo_value)
177-
.ok_or(ConstraintCheckerError::ValueOverflow)?;
178-
}
179-
180-
ensure!(
181-
total_output_value <= total_input_value,
182-
ConstraintCheckerError::OutputsExceedInputs
183-
);
184-
185-
// Priority is based on how many token are burned
186-
// Type stuff is kinda ugly. Maybe division would be better?
187-
let burned = total_input_value - total_output_value;
188-
Ok(if burned < u64::max_value() as u128 {
189-
burned as u64
190-
} else {
191-
u64::max_value()
192-
})
193-
}
194-
Self::Mint => {
195-
// Make sure there are no inputs being consumed
196-
ensure!(
197-
input_data.is_empty(),
198-
ConstraintCheckerError::MintingWithInputs
199-
);
200-
201-
// Make sure there is at least one output being minted
202-
ensure!(
203-
!output_data.is_empty(),
204-
ConstraintCheckerError::MintingNothing
205-
);
206-
207-
// Make sure the outputs are the right type
208-
for utxo in output_data {
209-
let utxo_value = utxo
210-
.extract::<Coin<ID>>()
211-
.map_err(|_| ConstraintCheckerError::BadlyTyped)?
212-
.0;
213-
ensure!(utxo_value > 0, ConstraintCheckerError::ZeroValueCoin);
214-
}
215-
216-
// No priority for minting
217-
Ok(0)
218-
}
161+
// Check that we are consuming at least one input
162+
ensure!(
163+
!input_data.is_empty(),
164+
ConstraintCheckerError::SpendingNothing
165+
);
166+
167+
let mut total_input_value: u128 = 0;
168+
let mut total_output_value: u128 = 0;
169+
170+
// Check that sum of input values < output values
171+
for input in input_data {
172+
let utxo_value = input
173+
.extract::<Coin<ID>>()
174+
.map_err(|_| ConstraintCheckerError::BadlyTyped)?
175+
.0;
176+
total_input_value = total_input_value
177+
.checked_add(utxo_value)
178+
.ok_or(ConstraintCheckerError::ValueOverflow)?;
219179
}
180+
181+
for utxo in output_data {
182+
let utxo_value = utxo
183+
.extract::<Coin<ID>>()
184+
.map_err(|_| ConstraintCheckerError::BadlyTyped)?
185+
.0;
186+
ensure!(utxo_value > 0, ConstraintCheckerError::ZeroValueCoin);
187+
total_output_value = total_output_value
188+
.checked_add(utxo_value)
189+
.ok_or(ConstraintCheckerError::ValueOverflow)?;
190+
}
191+
192+
ensure!(
193+
total_output_value <= total_input_value,
194+
ConstraintCheckerError::OutputsExceedInputs
195+
);
196+
197+
// Priority is based on how many token are burned
198+
// Type stuff is kinda ugly. Maybe division would be better?
199+
let burned = total_input_value - total_output_value;
200+
Ok(if burned < u64::max_value() as u128 {
201+
burned as u64
202+
} else {
203+
u64::max_value()
204+
})
205+
}
206+
}
207+
208+
impl<const ID: u8> SimpleConstraintChecker for Mint<ID> {
209+
type Error = ConstraintCheckerError;
210+
211+
fn check(
212+
&self,
213+
input_data: &[DynamicallyTypedData],
214+
_peeks: &[DynamicallyTypedData],
215+
output_data: &[DynamicallyTypedData],
216+
) -> Result<TransactionPriority, Self::Error> {
217+
// Make sure there are no inputs being consumed
218+
ensure!(
219+
input_data.is_empty(),
220+
ConstraintCheckerError::MintingWithInputs
221+
);
222+
223+
// Make sure there is at least one output being minted
224+
ensure!(
225+
!output_data.is_empty(),
226+
ConstraintCheckerError::MintingNothing
227+
);
228+
229+
// Make sure the outputs are the right type
230+
for utxo in output_data {
231+
let utxo_value = utxo
232+
.extract::<Coin<ID>>()
233+
.map_err(|_| ConstraintCheckerError::BadlyTyped)?
234+
.0;
235+
ensure!(utxo_value > 0, ConstraintCheckerError::ZeroValueCoin);
236+
}
237+
238+
// No priority for minting
239+
Ok(0)
220240
}
221241
}

wardrobe/money/src/tests.rs

+12-15
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ fn spend_valid_transaction_work() {
1010
let expected_priority = 1u64;
1111

1212
assert_eq!(
13-
MoneyConstraintChecker::<0>::Spend.check(&input_data, &[], &output_data),
13+
Spend::<0>.check(&input_data, &[], &output_data),
1414
Ok(expected_priority),
1515
);
1616
}
@@ -25,7 +25,7 @@ fn spend_with_zero_value_output_fails() {
2525
]; // total 11
2626

2727
assert_eq!(
28-
MoneyConstraintChecker::<0>::Spend.check(&input_data, &[], &output_data),
28+
Spend::<0>.check(&input_data, &[], &output_data),
2929
Err(ConstraintCheckerError::ZeroValueCoin),
3030
);
3131
}
@@ -37,7 +37,7 @@ fn spend_no_outputs_is_a_burn() {
3737
let expected_priority = 12u64;
3838

3939
assert_eq!(
40-
MoneyConstraintChecker::<0>::Spend.check(&input_data, &[], &output_data),
40+
Spend::<0>.check(&input_data, &[], &output_data),
4141
Ok(expected_priority),
4242
);
4343
}
@@ -48,7 +48,7 @@ fn spend_no_inputs_fails() {
4848
let output_data = vec![Coin::<0>(10).into(), Coin::<0>(1).into()];
4949

5050
assert_eq!(
51-
MoneyConstraintChecker::<0>::Spend.check(&input_data, &[], &output_data),
51+
Spend::<0>.check(&input_data, &[], &output_data),
5252
Err(ConstraintCheckerError::SpendingNothing),
5353
);
5454
}
@@ -59,7 +59,7 @@ fn spend_wrong_input_type_fails() {
5959
let output_data = vec![Coin::<0>(10).into(), Coin::<0>(1).into()];
6060

6161
assert_eq!(
62-
MoneyConstraintChecker::<0>::Spend.check(&input_data, &[], &output_data),
62+
Spend::<0>.check(&input_data, &[], &output_data),
6363
Err(ConstraintCheckerError::BadlyTyped),
6464
);
6565
}
@@ -70,7 +70,7 @@ fn spend_wrong_output_type_fails() {
7070
let output_data = vec![Bogus.into()];
7171

7272
assert_eq!(
73-
MoneyConstraintChecker::<0>::Spend.check(&input_data, &[], &output_data),
73+
Spend::<0>.check(&input_data, &[], &output_data),
7474
Err(ConstraintCheckerError::BadlyTyped),
7575
);
7676
}
@@ -81,7 +81,7 @@ fn spend_output_value_exceeds_input_value_fails() {
8181
let output_data = vec![Coin::<0>(5).into(), Coin::<0>(7).into()]; // total 12
8282

8383
assert_eq!(
84-
MoneyConstraintChecker::<0>::Spend.check(&input_data, &[], &output_data),
84+
Spend::<0>.check(&input_data, &[], &output_data),
8585
Err(ConstraintCheckerError::OutputsExceedInputs),
8686
);
8787
}
@@ -91,10 +91,7 @@ fn mint_valid_transaction_works() {
9191
let input_data = vec![];
9292
let output_data = vec![Coin::<0>(10).into(), Coin::<0>(1).into()];
9393

94-
assert_eq!(
95-
MoneyConstraintChecker::<0>::Mint.check(&input_data, &[], &output_data),
96-
Ok(0),
97-
);
94+
assert_eq!(Mint::<0>.check(&input_data, &[], &output_data), Ok(0),);
9895
}
9996

10097
#[test]
@@ -103,7 +100,7 @@ fn mint_with_zero_value_output_fails() {
103100
let output_data = vec![Coin::<0>(0).into()];
104101

105102
assert_eq!(
106-
MoneyConstraintChecker::<0>::Mint.check(&input_data, &[], &output_data),
103+
Mint::<0>.check(&input_data, &[], &output_data),
107104
Err(ConstraintCheckerError::ZeroValueCoin),
108105
);
109106
}
@@ -114,7 +111,7 @@ fn mint_with_inputs_fails() {
114111
let output_data = vec![Coin::<0>(10).into(), Coin::<0>(1).into()];
115112

116113
assert_eq!(
117-
MoneyConstraintChecker::<0>::Mint.check(&input_data, &[], &output_data),
114+
Mint::<0>.check(&input_data, &[], &output_data),
118115
Err(ConstraintCheckerError::MintingWithInputs),
119116
);
120117
}
@@ -125,7 +122,7 @@ fn mint_with_no_outputs_fails() {
125122
let output_data = vec![];
126123

127124
assert_eq!(
128-
MoneyConstraintChecker::<0>::Mint.check(&input_data, &[], &output_data),
125+
Mint::<0>.check(&input_data, &[], &output_data),
129126
Err(ConstraintCheckerError::MintingNothing),
130127
);
131128
}
@@ -136,7 +133,7 @@ fn mint_wrong_output_type_fails() {
136133
let output_data = vec![Coin::<0>(10).into(), Bogus.into()];
137134

138135
assert_eq!(
139-
MoneyConstraintChecker::<0>::Mint.check(&input_data, &[], &output_data),
136+
Mint::<0>.check(&input_data, &[], &output_data),
140137
Err(ConstraintCheckerError::BadlyTyped),
141138
);
142139
}

0 commit comments

Comments
 (0)