-
Notifications
You must be signed in to change notification settings - Fork 17
Expand file tree
/
Copy pathmod.rs
More file actions
285 lines (250 loc) · 10 KB
/
Copy pathmod.rs
File metadata and controls
285 lines (250 loc) · 10 KB
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
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
pub mod receiver;
mod state;
mod types;
use self::state::{StateError, TokenState};
pub use self::types::*;
use cid::Cid;
use fvm_ipld_blockstore::Blockstore as IpldStore;
use fvm_shared::econ::TokenAmount;
use fvm_shared::ActorID;
use num_traits::Signed;
use std::ops::Neg;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum TokenError {
#[error("error in underlying state {0}")]
State(#[from] StateError),
#[error("invalid negative: {0}")]
InvalidNegative(String),
}
type Result<T> = std::result::Result<T, TokenError>;
/// Library functions that implement core FRC-??? standards
///
/// Holds injectable services to access/interface with IPLD/FVM layer.
pub struct Token<BS>
where
BS: IpldStore + Clone,
{
/// Injected blockstore. The blockstore must reference the same underlying storage under Clone
bs: BS,
/// Root of the token state tree
state_cid: Cid,
}
impl<BS> Token<BS>
where
BS: IpldStore + Clone,
{
/// Instantiate a token helper with access to a blockstore and runtime
pub fn new(bs: BS, token_state: Cid) -> Self {
Self {
bs,
state_cid: token_state,
}
}
/// Constructs the token state tree and saves it at a CID
pub fn init_state(&self) -> Result<Cid> {
let init_state = TokenState::new(&self.bs)?;
Ok(init_state.save(&self.bs)?)
}
/// Helper function that loads the root of the state tree related to token-accounting
///
/// Actors can't usefully recover if state wasn't initialized (failure to call `init_state`) in
/// the constructor so this method panics if the state tree if missing
fn load_state(&self) -> TokenState {
TokenState::load(&self.bs, &self.state_cid).unwrap()
}
/// Mints the specified value of tokens into an account
///
/// The mint amount must be non-negative or the method returns an error
pub fn mint(&self, initial_holder: ActorID, value: TokenAmount) -> Result<()> {
if value.is_negative() {
return Err(TokenError::InvalidNegative(format!(
"mint amount {} cannot be negative",
value
)));
}
// Increase the balance of the actor and increase total supply
let mut state = self.load_state();
state.change_balance_by(&self.bs, initial_holder, &value)?;
// TODO: invoke the receiver hook on the initial_holder
state.increase_supply(&value)?;
// Commit the state atomically if supply and balance increased
state.save(&self.bs)?;
Ok(())
}
/// Gets the total number of tokens in existence
///
/// This equals the sum of `balance_of` called on all addresses. This equals sum of all
/// successful `mint` calls minus the sum of all successful `burn`/`burn_from` calls
pub fn total_supply(&self) -> TokenAmount {
let state = self.load_state();
state.supply
}
/// Returns the balance associated with a particular address
///
/// Accounts that have never received transfers implicitly have a zero-balance
pub fn balance_of(&self, holder: ActorID) -> Result<TokenAmount> {
// Load the HAMT holding balances
let state = self.load_state();
Ok(state.get_balance(&self.bs, holder)?)
}
/// Gets the allowance between owner and spender
///
/// The allowance is the amount that the spender can transfer or burn out of the owner's account
/// via the `transfer_from` and `burn_from` methods.
pub fn allowance(&self, owner: ActorID, spender: ActorID) -> Result<TokenAmount> {
let state = self.load_state();
let allowance = state.get_allowance_between(&self.bs, owner, spender)?;
Ok(allowance)
}
/// Increase the allowance that a spender controls of the owner's balance by the requested delta
///
/// Returns an error if requested delta is negative or there are errors in (de)serialization of
/// state. Else returns the new allowance.
pub fn increase_allowance(
&self,
owner: ActorID,
spender: ActorID,
delta: TokenAmount,
) -> Result<TokenAmount> {
if delta.is_negative() {
return Err(TokenError::InvalidNegative(format!(
"increase allowance delta {} cannot be negative",
delta
)));
}
let mut state = self.load_state();
let new_amount = state.change_allowance_by(&self.bs, owner, spender, &delta)?;
state.save(&self.bs)?;
Ok(new_amount)
}
/// Decrease the allowance that a spender controls of the owner's balance by the requested delta
///
/// If the resulting allowance would be negative, the allowance between owner and spender is set
/// to zero. Returns an error if either the spender or owner address is unresolvable. Returns an
/// error if requested delta is negative. Else returns the new allowance
pub fn decrease_allowance(
&self,
owner: ActorID,
spender: ActorID,
delta: TokenAmount,
) -> Result<TokenAmount> {
if delta.is_negative() {
return Err(TokenError::InvalidNegative(format!(
"decrease allowance delta {} cannot be negative",
delta
)));
}
let mut state = self.load_state();
let new_allowance = state.change_allowance_by(&self.bs, owner, spender, &delta.neg())?;
state.save(&self.bs)?;
Ok(new_allowance)
}
/// Sets the allowance between owner and spender to 0
pub fn revoke_allowance(&self, owner: ActorID, spender: ActorID) -> Result<()> {
let mut state = self.load_state();
state.revoke_allowance(&self.bs, owner, spender)?;
state.save(&self.bs)?;
Ok(())
}
/// Burns an amount of token from the specified address, decreasing total token supply
///
/// ## For all burn operations
/// - The requested value MUST be non-negative
/// - The requested value MUST NOT exceed the target's balance
///
/// Upon successful burn
/// - The target's balance MUST decrease by the requested value
/// - The total_supply MUST decrease by the requested value
///
/// ## Spender equals owner address
/// If the spender is the targeted address, they are implicitly approved to burn an unlimited
/// amount of tokens (up to their balance)
///
/// ## Spender burning on behalf of owner address
/// If the spender is burning on behalf of the owner the following preconditions
/// must be met on top of the general burn conditions:
/// - The spender MUST have an allowance not less than the requested value
/// In addition to the general postconditions:
/// - The target-spender allowance MUST decrease by the requested value
///
/// If the burn operation would result in a negative balance for the owner, the burn is
/// discarded and this method returns an error
pub fn burn(
&self,
spender: ActorID,
owner: ActorID,
value: TokenAmount,
) -> Result<TokenAmount> {
if value.is_negative() {
return Err(TokenError::InvalidNegative(format!(
"burn amount {} cannot be negative",
value
)));
}
let mut state = self.load_state();
if spender != owner {
// attempt to use allowance and return early if not enough
state.attempt_use_allowance(&self.bs, spender, owner, &value)?;
}
// attempt to burn the requested amount
let new_amount = state.change_balance_by(&self.bs, owner, &value.neg())?;
// if both succeeded, atomically commit the transaction
state.save(&self.bs)?;
Ok(new_amount)
}
/// Transfers an amount from one actor to another
///
/// ## For all transfer operations
///
/// - The requested value MUST be non-negative
/// - The requested value MUST NOT exceed the sender's balance
/// - The receiver actor MUST implement a method called `tokens_received`, corresponding to the
/// interface specified for FRC-XXX token receivers
/// - The receiver's `tokens_received` hook MUST NOT abort
///
/// Upon successful transfer:
/// - The senders's balance MUST decrease by the requested value
/// - The receiver's balance MUST increase by the requested value
///
/// ## Spender equals owner address
/// If the spender is the owner address, they are implicitly approved to transfer an unlimited
/// amount of tokens (up to their balance)
///
/// ## Spender transferring on behalf of owner address
/// If the spender is transferring on behalf of the target token holder the following preconditions
/// must be met on top of the general burn conditions:
/// - The spender MUST have an allowance not less than the requested value
/// In addition to the general postconditions:
/// - The owner-spender allowance MUST decrease by the requested value
pub fn transfer(
&self,
spender: ActorID,
owner: ActorID,
receiver: ActorID,
value: TokenAmount,
) -> Result<()> {
if value.is_negative() {
return Err(TokenError::InvalidNegative(format!(
"transfer amount {} cannot be negative",
value
)));
}
let mut state = self.load_state();
if spender != owner {
// attempt to use allowance and return early if not enough
state.attempt_use_allowance(&self.bs, spender, owner, &value)?;
}
// attempt to credit the receiver
state.change_balance_by(&self.bs, receiver, &value)?;
// attempt to debit from the sender
state.change_balance_by(&self.bs, owner, &value.neg())?;
// call the receiver hook
// FIXME: use fvm_dispatch to make a standard runtime call to the receiver
// - ensure the hook did not abort
// - receiver hook should see the new balances...
// if all succeeded, atomically commit the transaction
state.save(&self.bs)?;
Ok(())
}
}