Skip to content

Commit 207f6b7

Browse files
authored
Refactor and rename fil_token state layer (#20)
* rename fil_token -> fil_fungible_token * fungible_token: rename some public structs * panic in load_state if state tree missing * use a shared-memory blockstore for testing * streamline balance and allowance increase/decreases into single 'change_by' functions * replace lt < 0 with is_negative * cleanup
1 parent e110035 commit 207f6b7

File tree

10 files changed

+273
-292
lines changed

10 files changed

+273
-292
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@
33
members = [
44
"fvm_dispatch",
55
"fvm_dispatch_tools",
6-
"fil_token",
6+
"fil_fungible_token",
77
]

fil_token/Cargo.toml renamed to fil_fungible_token/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[package]
2-
name = "fil_token"
2+
name = "fil_fungible_token"
33
version = "0.1.0"
44
edition = "2021"
55

@@ -12,6 +12,7 @@ fvm_ipld_amt = { version = "0.4.2", features = ["go-interop"] }
1212
fvm_ipld_encoding = "0.2.2"
1313
fvm_sdk = { version = "1.0.0" }
1414
fvm_shared = { version = "0.8.0" }
15+
num-traits = { version = "0.2.15" }
1516
serde = { version = "1.0.136", features = ["derive"] }
1617
serde_tuple = { version = "0.5.0" }
1718
thiserror = { version = "1.0.31" }
File renamed without changes.

fil_token/src/blockstore.rs renamed to fil_fungible_token/src/blockstore.rs

Lines changed: 36 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
1-
//! Blockstore implementation is borrowed from https://github.com/filecoin-project/builtin-actors/blob/6df845dcdf9872beb6e871205eb34dcc8f7550b5/runtime/src/runtime/actor_blockstore.rs
2-
//! This impl will likely be made redundant if low-level SDKs export blockstore implementations
3-
use std::convert::TryFrom;
4-
51
use anyhow::{anyhow, Result};
62
use cid::multihash::Code;
73
use cid::Cid;
84
use fvm_ipld_blockstore::Block;
95
use fvm_sdk::ipld;
6+
use std::cell::RefCell;
7+
use std::collections::HashMap;
8+
use std::convert::TryFrom;
9+
use std::rc::Rc;
1010

1111
/// A blockstore that delegates to IPLD syscalls.
1212
#[derive(Default, Debug, Copy, Clone)]
1313
pub struct Blockstore;
1414

15+
/// Blockstore implementation is borrowed from https://github.com/filecoin-project/builtin-actors/blob/6df845dcdf9872beb6e871205eb34dcc8f7550b5/runtime/src/runtime/actor_blockstore.rs
16+
/// This impl will likely be made redundant if low-level SDKs export blockstore implementations
1517
impl fvm_ipld_blockstore::Blockstore for Blockstore {
1618
fn get(&self, cid: &Cid) -> Result<Option<Vec<u8>>> {
1719
// If this fails, the _CID_ is invalid. I.e., we have a bug.
@@ -42,31 +44,51 @@ impl fvm_ipld_blockstore::Blockstore for Blockstore {
4244
}
4345
}
4446

45-
// TODO: put this somewhere more appropriate when a tests folder exists
46-
/// An in-memory blockstore impl taken from filecoin-project/ref-fvm
47+
/// An in-memory blockstore impl that shares underlying memory when cloned
48+
///
49+
/// This is useful in tests to simulate a blockstore which pipes syscalls to the fvm_ipld_blockstore
4750
#[derive(Debug, Default, Clone)]
48-
pub struct MemoryBlockstore {
49-
blocks: RefCell<HashMap<Cid, Vec<u8>>>,
51+
pub struct SharedMemoryBlockstore {
52+
blocks: Rc<RefCell<HashMap<Cid, Vec<u8>>>>,
5053
}
5154

52-
use std::{cell::RefCell, collections::HashMap};
53-
impl MemoryBlockstore {
55+
impl SharedMemoryBlockstore {
5456
pub fn new() -> Self {
5557
Self::default()
5658
}
5759
}
5860

59-
impl fvm_ipld_blockstore::Blockstore for MemoryBlockstore {
61+
impl fvm_ipld_blockstore::Blockstore for SharedMemoryBlockstore {
6062
fn has(&self, k: &Cid) -> Result<bool> {
61-
Ok(self.blocks.borrow().contains_key(k))
63+
Ok(RefCell::borrow(&self.blocks).contains_key(k))
6264
}
6365

6466
fn get(&self, k: &Cid) -> Result<Option<Vec<u8>>> {
65-
Ok(self.blocks.borrow().get(k).cloned())
67+
Ok(RefCell::borrow(&self.blocks).get(k).cloned())
6668
}
6769

6870
fn put_keyed(&self, k: &Cid, block: &[u8]) -> Result<()> {
69-
self.blocks.borrow_mut().insert(*k, block.into());
71+
RefCell::borrow_mut(&self.blocks).insert(*k, block.into());
7072
Ok(())
7173
}
7274
}
75+
76+
#[cfg(test)]
77+
mod test {
78+
use fvm_ipld_blockstore::Blockstore;
79+
use fvm_ipld_encoding::CborStore;
80+
use fvm_shared::bigint::{bigint_ser::BigIntDe, BigInt};
81+
82+
use super::*;
83+
84+
#[test]
85+
fn it_shares_memory_under_clone() {
86+
let bs = SharedMemoryBlockstore::new();
87+
let a_number = BigIntDe(BigInt::from(123));
88+
let cid = bs.put_cbor(&a_number, Code::Blake2b256).unwrap();
89+
90+
let bs_cloned = bs.clone();
91+
assert_eq!(bs.blocks, bs_cloned.blocks);
92+
assert_eq!(bs.get(&cid).unwrap(), bs_cloned.get(&cid).unwrap())
93+
}
94+
}
File renamed without changes.

fil_token/src/token/mod.rs renamed to fil_fungible_token/src/token/mod.rs

Lines changed: 68 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ pub mod errors;
22
pub mod receiver;
33
mod state;
44
mod types;
5+
use std::ops::Neg;
6+
57
use self::state::TokenState;
68
pub use self::types::*;
79

@@ -10,30 +12,33 @@ use anyhow::Ok;
1012
use anyhow::Result;
1113
use cid::Cid;
1214
use fvm_ipld_blockstore::Blockstore as IpldStore;
13-
use fvm_shared::bigint::Zero;
1415
use fvm_shared::econ::TokenAmount;
1516
use fvm_shared::ActorID;
17+
use num_traits::Signed;
1618

1719
/// Library functions that implement core FRC-??? standards
1820
///
1921
/// Holds injectable services to access/interface with IPLD/FVM layer.
20-
pub struct TokenHelper<BS>
22+
pub struct Token<BS>
2123
where
2224
BS: IpldStore + Clone,
2325
{
24-
/// Injected blockstore
26+
/// Injected blockstore. The blockstore must reference the same underlying storage under Clone
2527
bs: BS,
2628
/// Root of the token state tree
27-
token_state: Cid,
29+
state_cid: Cid,
2830
}
2931

30-
impl<BS> TokenHelper<BS>
32+
impl<BS> Token<BS>
3133
where
3234
BS: IpldStore + Clone,
3335
{
3436
/// Instantiate a token helper with access to a blockstore and runtime
3537
pub fn new(bs: BS, token_state: Cid) -> Self {
36-
Self { bs, token_state }
38+
Self {
39+
bs,
40+
state_cid: token_state,
41+
}
3742
}
3843

3944
/// Constructs the token state tree and saves it at a CID
@@ -43,22 +48,27 @@ where
4348
}
4449

4550
/// Helper function that loads the root of the state tree related to token-accounting
46-
fn load_state(&self) -> Result<TokenState> {
47-
TokenState::load(&self.bs, &self.token_state)
51+
///
52+
/// Actors can't usefully recover if state wasn't initialized (failure to call `init_state`) in
53+
/// the constructor so this method panics if the state tree if missing
54+
fn load_state(&self) -> TokenState {
55+
TokenState::load(&self.bs, &self.state_cid).unwrap()
4856
}
4957

5058
/// Mints the specified value of tokens into an account
5159
///
52-
/// If the total supply or account balance overflows, this method returns an error. The mint
53-
/// amount must be non-negative or the method returns an error.
60+
/// The mint amount must be non-negative or the method returns an error
5461
pub fn mint(&self, initial_holder: ActorID, value: TokenAmount) -> Result<()> {
55-
if value.lt(&TokenAmount::zero()) {
62+
if value.is_negative() {
5663
bail!("value of mint was negative {}", value);
5764
}
5865

5966
// Increase the balance of the actor and increase total supply
60-
let mut state = self.load_state()?;
61-
state.increase_balance(&self.bs, initial_holder, &value)?;
67+
let mut state = self.load_state();
68+
state.change_balance_by(&self.bs, initial_holder, &value)?;
69+
70+
// TODO: invoke the receiver hook on the initial_holder
71+
6272
state.increase_supply(&value)?;
6373

6474
// Commit the state atomically if supply and balance increased
@@ -72,7 +82,7 @@ where
7282
/// This equals the sum of `balance_of` called on all addresses. This equals sum of all
7383
/// successful `mint` calls minus the sum of all successful `burn`/`burn_from` calls
7484
pub fn total_supply(&self) -> TokenAmount {
75-
let state = self.load_state().unwrap();
85+
let state = self.load_state();
7686
state.supply
7787
}
7888

@@ -81,7 +91,7 @@ where
8191
/// Accounts that have never received transfers implicitly have a zero-balance
8292
pub fn balance_of(&self, holder: ActorID) -> Result<TokenAmount> {
8393
// Load the HAMT holding balances
84-
let state = self.load_state()?;
94+
let state = self.load_state();
8595
state.get_balance(&self.bs, holder)
8696
}
8797

@@ -90,27 +100,27 @@ where
90100
/// The allowance is the amount that the spender can transfer or burn out of the owner's account
91101
/// via the `transfer_from` and `burn_from` methods.
92102
pub fn allowance(&self, owner: ActorID, spender: ActorID) -> Result<TokenAmount> {
93-
let state = self.load_state()?;
103+
let state = self.load_state();
94104
let allowance = state.get_allowance_between(&self.bs, owner, spender)?;
95105
Ok(allowance)
96106
}
97107

98108
/// Increase the allowance that a spender controls of the owner's balance by the requested delta
99109
///
100-
/// Returns an error if requested delta is negative or there are errors in (de)sereliazation of
110+
/// Returns an error if requested delta is negative or there are errors in (de)serialization of
101111
/// state. Else returns the new allowance.
102112
pub fn increase_allowance(
103113
&self,
104114
owner: ActorID,
105115
spender: ActorID,
106116
delta: TokenAmount,
107117
) -> Result<TokenAmount> {
108-
if delta.lt(&TokenAmount::zero()) {
118+
if delta.is_negative() {
109119
bail!("value of delta was negative {}", delta);
110120
}
111121

112-
let mut state = self.load_state()?;
113-
let new_amount = state.increase_allowance(&self.bs, owner, spender, &delta)?;
122+
let mut state = self.load_state();
123+
let new_amount = state.change_allowance_by(&self.bs, owner, spender, &delta)?;
114124
state.save(&self.bs)?;
115125

116126
Ok(new_amount)
@@ -119,29 +129,28 @@ where
119129
/// Decrease the allowance that a spender controls of the owner's balance by the requested delta
120130
///
121131
/// If the resulting allowance would be negative, the allowance between owner and spender is set
122-
/// to zero. If resulting allowance is zero, the entry is removed from the state map. Returns an
123-
/// error if either the spender or owner address is unresolvable. Returns an error if requested
124-
/// delta is negative. Else returns the new allowance
132+
/// to zero. Returns an error if either the spender or owner address is unresolvable. Returns an
133+
/// error if requested delta is negative. Else returns the new allowance
125134
pub fn decrease_allowance(
126135
&self,
127136
owner: ActorID,
128137
spender: ActorID,
129138
delta: TokenAmount,
130139
) -> Result<TokenAmount> {
131-
if delta.lt(&TokenAmount::zero()) {
140+
if delta.is_negative() {
132141
bail!("value of delta was negative {}", delta);
133142
}
134143

135-
let mut state = self.load_state()?;
136-
let new_allowance = state.decrease_allowance(&self.bs, owner, spender, &delta)?;
144+
let mut state = self.load_state();
145+
let new_allowance = state.change_allowance_by(&self.bs, owner, spender, &delta.neg())?;
137146
state.save(&self.bs)?;
138147

139148
Ok(new_allowance)
140149
}
141150

142151
/// Sets the allowance between owner and spender to 0
143152
pub fn revoke_allowance(&self, owner: ActorID, spender: ActorID) -> Result<()> {
144-
let mut state = self.load_state()?;
153+
let mut state = self.load_state();
145154
state.revoke_allowance(&self.bs, owner, spender)?;
146155
state.save(&self.bs)?;
147156
Ok(())
@@ -157,37 +166,37 @@ where
157166
/// - The target's balance MUST decrease by the requested value
158167
/// - The total_supply MUST decrease by the requested value
159168
///
160-
/// ## Operator equals target address
161-
/// If the operator is the targeted address, they are implicitly approved to burn an unlimited
169+
/// ## Spender equals owner address
170+
/// If the spender is the targeted address, they are implicitly approved to burn an unlimited
162171
/// amount of tokens (up to their balance)
163172
///
164-
/// ## Operator burning on behalf of target address
165-
/// If the operator is burning on behalf of the target token holder the following preconditions
173+
/// ## Spender burning on behalf of owner address
174+
/// If the spender is burning on behalf of the owner the following preconditions
166175
/// must be met on top of the general burn conditions:
167-
/// - The operator MUST have an allowance not less than the requested value
176+
/// - The spender MUST have an allowance not less than the requested value
168177
/// In addition to the general postconditions:
169-
/// - The target-operator allowance MUST decrease by the requested value
178+
/// - The target-spender allowance MUST decrease by the requested value
170179
///
171-
/// If the burn operation would result in a negative balance for the targeted address, the burn
172-
/// is discarded and this method returns an error
180+
/// If the burn operation would result in a negative balance for the owner, the burn is
181+
/// discarded and this method returns an error
173182
pub fn burn(
174183
&self,
175184
spender: ActorID,
176185
owner: ActorID,
177186
value: TokenAmount,
178187
) -> Result<TokenAmount> {
179-
if value.lt(&TokenAmount::zero()) {
180-
bail!("Cannot burn a negative amount");
188+
if value.is_negative() {
189+
bail!("cannot burn a negative amount");
181190
}
182191

183-
let mut state = self.load_state()?;
192+
let mut state = self.load_state();
184193

185194
if spender != owner {
186195
// attempt to use allowance and return early if not enough
187196
state.attempt_use_allowance(&self.bs, spender, owner, &value)?;
188197
}
189198
// attempt to burn the requested amount
190-
let new_amount = state.decrease_balance(&self.bs, owner, &value)?;
199+
let new_amount = state.change_balance_by(&self.bs, owner, &value.neg())?;
191200

192201
// if both succeeded, atomically commit the transaction
193202
state.save(&self.bs)?;
@@ -208,42 +217,43 @@ where
208217
/// - The senders's balance MUST decrease by the requested value
209218
/// - The receiver's balance MUST increase by the requested value
210219
///
211-
/// ## Operator equals target address
212-
/// If the operator is the 'from' address, they are implicitly approved to transfer an unlimited
220+
/// ## Spender equals owner address
221+
/// If the spender is the owner address, they are implicitly approved to transfer an unlimited
213222
/// amount of tokens (up to their balance)
214223
///
215-
/// ## Operator transferring on behalf of target address
216-
/// If the operator is transferring on behalf of the target token holder the following preconditions
224+
/// ## Spender transferring on behalf of owner address
225+
/// If the spender is transferring on behalf of the target token holder the following preconditions
217226
/// must be met on top of the general burn conditions:
218-
/// - The operator MUST have an allowance not less than the requested value
227+
/// - The spender MUST have an allowance not less than the requested value
219228
/// In addition to the general postconditions:
220-
/// - The from-operator allowance MUST decrease by the requested value
229+
/// - The owner-spender allowance MUST decrease by the requested value
221230
pub fn transfer(
222231
&self,
223-
operator: ActorID,
224-
from: ActorID,
225-
to: ActorID,
232+
spender: ActorID,
233+
owner: ActorID,
234+
receiver: ActorID,
226235
value: TokenAmount,
227236
) -> Result<()> {
228-
if value.lt(&TokenAmount::zero()) {
229-
bail!("Cannot transfer a negative amount");
237+
if value.is_negative() {
238+
bail!("cannot transfer a negative amount");
230239
}
231240

232-
let mut state = self.load_state()?;
241+
let mut state = self.load_state();
233242

234-
if operator != from {
243+
if spender != owner {
235244
// attempt to use allowance and return early if not enough
236-
state.attempt_use_allowance(&self.bs, operator, from, &value)?;
245+
state.attempt_use_allowance(&self.bs, spender, owner, &value)?;
237246
}
238247

248+
// attempt to credit the receiver
249+
state.change_balance_by(&self.bs, receiver, &value)?;
250+
// attempt to debit from the sender
251+
state.change_balance_by(&self.bs, owner, &value.neg())?;
252+
239253
// call the receiver hook
240254
// FIXME: use fvm_dispatch to make a standard runtime call to the receiver
241255
// - ensure the hook did not abort
242-
243-
// attempt to debit from the sender
244-
state.decrease_balance(&self.bs, from, &value)?;
245-
// attempt to credit the receiver
246-
state.increase_balance(&self.bs, to, &value)?;
256+
// - receiver hook should see the new balances...
247257

248258
// if all succeeded, atomically commit the transaction
249259
state.save(&self.bs)?;

0 commit comments

Comments
 (0)