Skip to content

Commit 528d6c7

Browse files
committed
custom error enums for state and token methods
1 parent 207f6b7 commit 528d6c7

File tree

3 files changed

+92
-135
lines changed

3 files changed

+92
-135
lines changed

fil_fungible_token/src/token/errors.rs

Lines changed: 0 additions & 71 deletions
This file was deleted.

fil_fungible_token/src/token/mod.rs

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,27 @@
1-
pub mod errors;
21
pub mod receiver;
32
mod state;
43
mod types;
5-
use std::ops::Neg;
64

7-
use self::state::TokenState;
5+
use self::state::{StateError, TokenState};
86
pub use self::types::*;
97

10-
use anyhow::bail;
11-
use anyhow::Ok;
12-
use anyhow::Result;
138
use cid::Cid;
149
use fvm_ipld_blockstore::Blockstore as IpldStore;
1510
use fvm_shared::econ::TokenAmount;
1611
use fvm_shared::ActorID;
1712
use num_traits::Signed;
13+
use std::ops::Neg;
14+
use thiserror::Error;
15+
16+
#[derive(Error, Debug)]
17+
pub enum TokenError {
18+
#[error("error in underlying state {0}")]
19+
State(#[from] StateError),
20+
#[error("invalid negative: {0}")]
21+
InvalidNegative(String),
22+
}
23+
24+
type Result<T> = std::result::Result<T, TokenError>;
1825

1926
/// Library functions that implement core FRC-??? standards
2027
///
@@ -44,7 +51,7 @@ where
4451
/// Constructs the token state tree and saves it at a CID
4552
pub fn init_state(&self) -> Result<Cid> {
4653
let init_state = TokenState::new(&self.bs)?;
47-
init_state.save(&self.bs)
54+
Ok(init_state.save(&self.bs)?)
4855
}
4956

5057
/// Helper function that loads the root of the state tree related to token-accounting
@@ -60,7 +67,10 @@ where
6067
/// The mint amount must be non-negative or the method returns an error
6168
pub fn mint(&self, initial_holder: ActorID, value: TokenAmount) -> Result<()> {
6269
if value.is_negative() {
63-
bail!("value of mint was negative {}", value);
70+
return Err(TokenError::InvalidNegative(format!(
71+
"mint amount {} cannot be negative",
72+
value
73+
)));
6474
}
6575

6676
// Increase the balance of the actor and increase total supply
@@ -92,7 +102,7 @@ where
92102
pub fn balance_of(&self, holder: ActorID) -> Result<TokenAmount> {
93103
// Load the HAMT holding balances
94104
let state = self.load_state();
95-
state.get_balance(&self.bs, holder)
105+
Ok(state.get_balance(&self.bs, holder)?)
96106
}
97107

98108
/// Gets the allowance between owner and spender
@@ -116,7 +126,10 @@ where
116126
delta: TokenAmount,
117127
) -> Result<TokenAmount> {
118128
if delta.is_negative() {
119-
bail!("value of delta was negative {}", delta);
129+
return Err(TokenError::InvalidNegative(format!(
130+
"increase allowance delta {} cannot be negative",
131+
delta
132+
)));
120133
}
121134

122135
let mut state = self.load_state();
@@ -138,7 +151,10 @@ where
138151
delta: TokenAmount,
139152
) -> Result<TokenAmount> {
140153
if delta.is_negative() {
141-
bail!("value of delta was negative {}", delta);
154+
return Err(TokenError::InvalidNegative(format!(
155+
"decrease allowance delta {} cannot be negative",
156+
delta
157+
)));
142158
}
143159

144160
let mut state = self.load_state();
@@ -186,7 +202,10 @@ where
186202
value: TokenAmount,
187203
) -> Result<TokenAmount> {
188204
if value.is_negative() {
189-
bail!("cannot burn a negative amount");
205+
return Err(TokenError::InvalidNegative(format!(
206+
"burn amount {} cannot be negative",
207+
value
208+
)));
190209
}
191210

192211
let mut state = self.load_state();
@@ -235,7 +254,10 @@ where
235254
value: TokenAmount,
236255
) -> Result<()> {
237256
if value.is_negative() {
238-
bail!("cannot transfer a negative amount");
257+
return Err(TokenError::InvalidNegative(format!(
258+
"transfer amount {} cannot be negative",
259+
value
260+
)));
239261
}
240262

241263
let mut state = self.load_state();

fil_fungible_token/src/token/state.rs

Lines changed: 57 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,52 @@
1-
use anyhow::anyhow;
2-
use anyhow::bail;
3-
use anyhow::Result;
41
use cid::multihash::Code;
52
use cid::Cid;
6-
73
use fvm_ipld_blockstore::Block;
84
use fvm_ipld_blockstore::Blockstore as IpldStore;
95
use fvm_ipld_encoding::tuple::*;
106
use fvm_ipld_encoding::Cbor;
117
use fvm_ipld_encoding::CborStore;
128
use fvm_ipld_encoding::DAG_CBOR;
9+
use fvm_ipld_hamt::Error as HamtError;
1310
use fvm_ipld_hamt::Hamt;
1411
use fvm_shared::bigint::bigint_ser;
1512
use fvm_shared::bigint::bigint_ser::BigIntDe;
1613
use fvm_shared::bigint::Zero;
1714
use fvm_shared::econ::TokenAmount;
1815
use fvm_shared::ActorID;
1916
use num_traits::Signed;
17+
use thiserror::Error;
2018

2119
const HAMT_BIT_WIDTH: u32 = 5;
2220

21+
#[derive(Error, Debug)]
22+
pub enum StateError {
23+
#[error("ipld hamt error: {0}")]
24+
IpldHamt(#[from] HamtError),
25+
#[error("missing state at cid: {0}")]
26+
MissingState(Cid),
27+
#[error("underlying serialization error: {0}")]
28+
Serialization(String),
29+
#[error(
30+
"negative balance caused by subtracting {delta:?} from {owner:?}'s balance of {balance:?}"
31+
)]
32+
NegativeBalance {
33+
owner: ActorID,
34+
balance: TokenAmount,
35+
delta: TokenAmount,
36+
},
37+
#[error(
38+
"{spender:?} attempted to utilise {delta:?} of allowance {allowance:?} set by {owner:?}"
39+
)]
40+
InsufficentAllowance {
41+
owner: ActorID,
42+
spender: ActorID,
43+
allowance: TokenAmount,
44+
delta: TokenAmount,
45+
},
46+
}
47+
48+
type Result<T> = std::result::Result<T, StateError>;
49+
2350
/// Token state IPLD structure
2451
#[derive(Serialize_tuple, Deserialize_tuple, PartialEq, Clone, Debug)]
2552
pub struct TokenState {
@@ -37,9 +64,8 @@ pub struct TokenState {
3764
///
3865
/// This is a simple wrapper of state and in general does not account for token protocol level
3966
/// checks such as ensuring necessary approvals are enforced during transfers. This is left for the
40-
/// caller to handle. However, some invariants such as enforcing non-negative balances, allowances
41-
/// and total supply are enforced. Furthermore, this layer returns errors if any of the underlying
42-
/// arithmetic overflows.
67+
/// caller to handle. However, some invariants such as non-negative balances, allowances and total
68+
/// supply are enforced.
4369
///
4470
/// Some methods on TokenState require the caller to pass in a blockstore implementing the Clone
4571
/// trait. It is assumed that when cloning the blockstore implementation does a "shallow-clone"
@@ -63,24 +89,24 @@ impl TokenState {
6389
// Load the actor state from the state tree.
6490
match bs.get_cbor::<Self>(cid) {
6591
Ok(Some(state)) => Ok(state),
66-
Ok(None) => Err(anyhow!("no state at this cid {:?}", cid)),
67-
Err(err) => Err(anyhow!("failed to get state: {}", err)),
92+
Ok(None) => Err(StateError::MissingState(*cid)),
93+
Err(err) => Err(StateError::Serialization(err.to_string())),
6894
}
6995
}
7096

7197
/// Saves the current state to the blockstore, returning the cid
7298
pub fn save<BS: IpldStore>(&self, bs: &BS) -> Result<Cid> {
7399
let serialized = match fvm_ipld_encoding::to_vec(self) {
74100
Ok(s) => s,
75-
Err(err) => return Err(anyhow!("failed to serialize state: {:?}", err)),
101+
Err(err) => return Err(StateError::Serialization(err.to_string())),
76102
};
77103
let block = Block {
78104
codec: DAG_CBOR,
79105
data: serialized,
80106
};
81107
let cid = match bs.put(Code::Blake2b256, &block) {
82108
Ok(cid) => cid,
83-
Err(err) => return Err(anyhow!("failed to store initial state: {:}", err)),
109+
Err(err) => return Err(StateError::Serialization(err.to_string())),
84110
};
85111
Ok(cid)
86112
}
@@ -126,11 +152,11 @@ impl TokenState {
126152

127153
// if the new_balance is negative, return an error
128154
if new_balance.is_negative() {
129-
bail!(
130-
"resulting balance was negative adding {:?} to {:?}",
131-
delta,
132-
balance.unwrap_or(&BigIntDe(TokenAmount::zero())).0
133-
);
155+
return Err(StateError::NegativeBalance {
156+
balance: new_balance,
157+
delta: delta.clone(),
158+
owner,
159+
});
134160
}
135161

136162
balance_map.set(owner, BigIntDe(new_balance.clone()))?;
@@ -144,30 +170,19 @@ impl TokenState {
144170
&self,
145171
bs: &BS,
146172
) -> Result<Hamt<BS, BigIntDe, ActorID>> {
147-
match Hamt::<BS, BigIntDe, ActorID>::load_with_bit_width(
173+
Ok(Hamt::<BS, BigIntDe, ActorID>::load_with_bit_width(
148174
&self.balances,
149175
(*bs).clone(),
150176
HAMT_BIT_WIDTH,
151-
) {
152-
Ok(map) => Ok(map),
153-
Err(err) => return Err(anyhow!("failed to load balances hamt: {:?}", err)),
154-
}
177+
)?)
155178
}
156179

157180
/// Increase the total supply by the specified value
158181
///
159-
/// The requested amount must be non-negative.
160-
/// Returns an error if the total supply overflows, else returns the new total supply
161-
pub fn increase_supply(&mut self, value: &TokenAmount) -> Result<TokenAmount> {
162-
let new_supply = self.supply.checked_add(value).ok_or_else(|| {
163-
anyhow!(
164-
"Overflow when adding {} to the total_supply of {}",
165-
value,
166-
self.supply
167-
)
168-
})?;
169-
self.supply = new_supply.clone();
170-
Ok(new_supply)
182+
/// The requested amount must be non-negative. Returns the new total supply
183+
pub fn increase_supply(&mut self, value: &TokenAmount) -> Result<&TokenAmount> {
184+
self.supply += value;
185+
Ok(&self.supply)
171186
}
172187

173188
/// Get the allowance that an owner has approved for a spender
@@ -296,25 +311,17 @@ impl TokenState {
296311
return Ok(current_allowance);
297312
}
298313

299-
let new_allowance = current_allowance.checked_sub(value).ok_or_else(|| {
300-
anyhow!(
301-
"Overflow when subtracting {} from {}'s allowance of {}",
302-
value,
303-
owner,
304-
current_allowance
305-
)
306-
})?;
307-
308-
if new_allowance.is_negative() {
309-
return Err(anyhow!(
310-
"Attempted to use {} of {}'s tokens from {}'s allowance of {}",
311-
value,
314+
if current_allowance.lt(value) {
315+
return Err(StateError::InsufficentAllowance {
312316
owner,
313317
spender,
314-
current_allowance
315-
));
318+
allowance: current_allowance,
319+
delta: value.clone(),
320+
});
316321
}
317322

323+
let new_allowance = current_allowance - value;
324+
318325
// TODO: helper function to set a new allowance and flush hamts
319326
let owner_allowances = self.get_owner_allowance_map(bs, owner)?;
320327
// to reach here, allowance must have been previously non zero; so safe to assume the map exists
@@ -353,12 +360,11 @@ impl TokenState {
353360
///
354361
/// Gets a HAMT with CIDs linking to other HAMTs
355362
fn get_allowances_map<BS: IpldStore + Clone>(&self, bs: &BS) -> Result<Hamt<BS, Cid, ActorID>> {
356-
Hamt::<BS, Cid, ActorID>::load_with_bit_width(
363+
Ok(Hamt::<BS, Cid, ActorID>::load_with_bit_width(
357364
&self.allowances,
358365
(*bs).clone(),
359366
HAMT_BIT_WIDTH,
360-
)
361-
.map_err(|e| anyhow!("failed to load base allowances map {}", e))
367+
)?)
362368
}
363369
}
364370

0 commit comments

Comments
 (0)