-
Notifications
You must be signed in to change notification settings - Fork 18
/
lib.rs
221 lines (199 loc) · 6.83 KB
/
lib.rs
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
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
//! An implementation of a simple fungible token.
#![cfg_attr(not(feature = "std"), no_std)]
use parity_scale_codec::{Decode, Encode};
use scale_info::TypeInfo;
use serde::{Deserialize, Serialize};
use sp_runtime::transaction_validity::TransactionPriority;
use sp_std::prelude::*;
use tuxedo_core::{
dynamic_typing::{DynamicallyTypedData, UtxoData},
ensure,
traits::Cash,
types::Transaction,
SimpleConstraintChecker, Verifier,
};
#[cfg(test)]
mod tests;
impl<const ID: u8> Cash for Coin<ID> {
fn value(&self) -> u128 {
self.0
}
const ID: u8 = ID;
}
// use log::info;
/// The main constraint checker for the money piece. Allows spending and minting tokens.
#[derive(
Serialize,
Deserialize,
PartialEq,
Eq,
PartialOrd,
Ord,
Clone,
Encode,
Decode,
Hash,
Debug,
TypeInfo,
)]
pub enum MoneyConstraintChecker<const ID: u8> {
/// A typical spend transaction where some coins are consumed and others are created.
/// Input value must exceed output value. The difference is burned and reflected in the
/// transaction's priority.
Spend,
/// A mint transaction that creates no coins out of the void. In a real-world chain,
/// this should be protected somehow, or not included at all. For now it is publicly
/// available. I'm adding it to explore multiple validation paths in a single piece.
Mint,
}
/// A single coin in the fungible money system.
/// A new-type wrapper around a `u128` value.
#[derive(
Serialize,
Deserialize,
PartialEq,
Eq,
PartialOrd,
Ord,
Clone,
Encode,
Decode,
Hash,
Debug,
TypeInfo,
)]
pub struct Coin<const ID: u8>(pub u128);
impl<const ID: u8> Coin<ID> {
pub fn new(amt: u128) -> Self {
Coin(amt)
}
/// Create a mint transaction for a single Coin.
pub fn mint<V, OV, OC>(amt: u128, v: V) -> Transaction<OV, OC>
where
V: Verifier,
OV: Verifier + From<V>,
OC: tuxedo_core::ConstraintChecker<OV> + From<MoneyConstraintChecker<ID>>,
{
Transaction {
inputs: vec![],
peeks: vec![],
outputs: vec![(Self::new(amt), v).into()],
checker: MoneyConstraintChecker::Mint.into(),
}
}
}
impl<const ID: u8> UtxoData for Coin<ID> {
const TYPE_ID: [u8; 4] = [b'c', b'o', b'i', ID];
}
/// Errors that can occur when checking money transactions.
#[derive(
Serialize,
Deserialize,
PartialEq,
Eq,
PartialOrd,
Ord,
Clone,
Encode,
Decode,
Hash,
Debug,
TypeInfo,
)]
pub enum ConstraintCheckerError {
/// Dynamic typing issue.
/// This error doesn't discriminate between badly typed inputs and outputs.
BadlyTyped,
/// The transaction attempts to consume inputs while minting. This is not allowed.
MintingWithInputs,
/// The transaction attempts to mint zero coins. This is not allowed.
MintingNothing,
/// The transaction attempts to spend without consuming any inputs.
/// Either the output value will exceed the input value, or if there are no outputs,
/// it is a waste of processing power, so it is not allowed.
SpendingNothing,
/// The value of the spent input coins is less than the value of the newly created
/// output coins. This would lead to money creation and is not allowed.
OutputsExceedInputs,
/// The value consumed or created by this transaction overflows the value type.
/// This could lead to problems like https://bitcointalk.org/index.php?topic=823.0
ValueOverflow,
/// The transaction attempted to create a coin with zero value. This is not allowed
/// because it wastes state space.
ZeroValueCoin,
}
impl<const ID: u8> SimpleConstraintChecker for MoneyConstraintChecker<ID> {
type Error = ConstraintCheckerError;
fn check(
&self,
input_data: &[DynamicallyTypedData],
_peeks: &[DynamicallyTypedData],
output_data: &[DynamicallyTypedData],
) -> Result<TransactionPriority, Self::Error> {
match &self {
Self::Spend => {
// Check that we are consuming at least one input
ensure!(
!input_data.is_empty(),
ConstraintCheckerError::SpendingNothing
);
let mut total_input_value: u128 = 0;
let mut total_output_value: u128 = 0;
// Check that sum of input values < output values
for input in input_data {
let utxo_value = input
.extract::<Coin<ID>>()
.map_err(|_| ConstraintCheckerError::BadlyTyped)?
.0;
total_input_value = total_input_value
.checked_add(utxo_value)
.ok_or(ConstraintCheckerError::ValueOverflow)?;
}
for utxo in output_data {
let utxo_value = utxo
.extract::<Coin<ID>>()
.map_err(|_| ConstraintCheckerError::BadlyTyped)?
.0;
ensure!(utxo_value > 0, ConstraintCheckerError::ZeroValueCoin);
total_output_value = total_output_value
.checked_add(utxo_value)
.ok_or(ConstraintCheckerError::ValueOverflow)?;
}
ensure!(
total_output_value <= total_input_value,
ConstraintCheckerError::OutputsExceedInputs
);
// Priority is based on how many token are burned
// Type stuff is kinda ugly. Maybe division would be better?
let burned = total_input_value - total_output_value;
Ok(if burned < u64::max_value() as u128 {
burned as u64
} else {
u64::max_value()
})
}
Self::Mint => {
// Make sure there are no inputs being consumed
ensure!(
input_data.is_empty(),
ConstraintCheckerError::MintingWithInputs
);
// Make sure there is at least one output being minted
ensure!(
!output_data.is_empty(),
ConstraintCheckerError::MintingNothing
);
// Make sure the outputs are the right type
for utxo in output_data {
let utxo_value = utxo
.extract::<Coin<ID>>()
.map_err(|_| ConstraintCheckerError::BadlyTyped)?
.0;
ensure!(utxo_value > 0, ConstraintCheckerError::ZeroValueCoin);
}
// No priority for minting
Ok(0)
}
}
}
}