casper_erc20_crate/
lib.rs

1//! A library for developing ERC20 tokens for the Casper network.
2//!
3//! The main functionality is provided via the [`ERC20`] struct, and is intended to be consumed by a
4//! smart contract written to be deployed on the Casper network.
5//!
6//! To create an example ERC20 contract which uses this library, use the cargo-casper tool:
7//!
8//! ```bash
9//! cargo install cargo-casper
10//! cargo casper --erc20 <PATH TO NEW PROJECT>
11//! ```
12
13#![warn(missing_docs)]
14#![no_std]
15
16extern crate alloc;
17
18pub mod address;
19mod allowances;
20mod balances;
21pub mod constants;
22mod detail;
23pub mod entry_points;
24mod error;
25mod total_supply;
26
27use alloc::{
28    collections::BTreeMap,
29    string::{String, ToString},
30};
31
32use once_cell::unsync::OnceCell;
33
34use casper_contract::{
35    contract_api::{runtime, storage},
36    unwrap_or_revert::UnwrapOrRevert,
37};
38use casper_types::{contracts::NamedKeys, EntryPoints, Key, URef, U256};
39
40pub use address::Address;
41use constants::{
42    ALLOWANCES_KEY_NAME, BALANCES_KEY_NAME, DECIMALS_KEY_NAME, ERC20_TOKEN_CONTRACT_KEY_NAME,
43    NAME_KEY_NAME, SYMBOL_KEY_NAME, TOTAL_SUPPLY_KEY_NAME,
44};
45pub use error::Error;
46
47/// Implementation of ERC20 standard functionality.
48#[derive(Default)]
49pub struct ERC20 {
50    balances_uref: OnceCell<URef>,
51    allowances_uref: OnceCell<URef>,
52    total_supply_uref: OnceCell<URef>,
53}
54
55impl ERC20 {
56    fn new(balances_uref: URef, allowances_uref: URef, total_supply_uref: URef) -> Self {
57        Self {
58            balances_uref: balances_uref.into(),
59            allowances_uref: allowances_uref.into(),
60            total_supply_uref: total_supply_uref.into(),
61        }
62    }
63
64    fn total_supply_uref(&self) -> URef {
65        *self
66            .total_supply_uref
67            .get_or_init(total_supply::total_supply_uref)
68    }
69
70    fn read_total_supply(&self) -> U256 {
71        total_supply::read_total_supply_from(self.total_supply_uref())
72    }
73
74    fn write_total_supply(&self, total_supply: U256) {
75        total_supply::write_total_supply_to(self.total_supply_uref(), total_supply)
76    }
77
78    fn balances_uref(&self) -> URef {
79        *self.balances_uref.get_or_init(balances::get_balances_uref)
80    }
81
82    fn read_balance(&self, owner: Address) -> U256 {
83        balances::read_balance_from(self.balances_uref(), owner)
84    }
85
86    fn write_balance(&mut self, owner: Address, amount: U256) {
87        balances::write_balance_to(self.balances_uref(), owner, amount)
88    }
89
90    fn allowances_uref(&self) -> URef {
91        *self
92            .allowances_uref
93            .get_or_init(allowances::allowances_uref)
94    }
95
96    fn read_allowance(&self, owner: Address, spender: Address) -> U256 {
97        allowances::read_allowance_from(self.allowances_uref(), owner, spender)
98    }
99
100    fn write_allowance(&mut self, owner: Address, spender: Address, amount: U256) {
101        allowances::write_allowance_to(self.allowances_uref(), owner, spender, amount)
102    }
103
104    fn transfer_balance(
105        &mut self,
106        sender: Address,
107        recipient: Address,
108        amount: U256,
109    ) -> Result<(), Error> {
110        balances::transfer_balance(self.balances_uref(), sender, recipient, amount)
111    }
112
113    /// Installs the ERC20 contract with the default set of entry points.
114    ///
115    /// This should be called from within `fn call()` of your contract.
116    pub fn install(
117        name: String,
118        symbol: String,
119        decimals: u8,
120        initial_supply: U256,
121    ) -> Result<ERC20, Error> {
122        let default_entry_points = entry_points::default();
123        ERC20::install_custom(
124            name,
125            symbol,
126            decimals,
127            initial_supply,
128            ERC20_TOKEN_CONTRACT_KEY_NAME,
129            default_entry_points,
130        )
131    }
132
133    /// Returns the name of the token.
134    pub fn name(&self) -> String {
135        detail::read_from(NAME_KEY_NAME)
136    }
137
138    /// Returns the symbol of the token.
139    pub fn symbol(&self) -> String {
140        detail::read_from(SYMBOL_KEY_NAME)
141    }
142
143    /// Returns the decimals of the token.
144    pub fn decimals(&self) -> u8 {
145        detail::read_from(DECIMALS_KEY_NAME)
146    }
147
148    /// Returns the total supply of the token.
149    pub fn total_supply(&self) -> U256 {
150        self.read_total_supply()
151    }
152
153    /// Returns the balance of `owner`.
154    pub fn balance_of(&self, owner: Address) -> U256 {
155        self.read_balance(owner)
156    }
157
158    /// Transfers `amount` of tokens from the direct caller to `recipient`.
159    pub fn transfer(&mut self, recipient: Address, amount: U256) -> Result<(), Error> {
160        let sender = detail::get_immediate_caller_address()?;
161        self.transfer_balance(sender, recipient, amount)
162    }
163
164    /// Transfers `amount` of tokens from `owner` to `recipient` if the direct caller has been
165    /// previously approved to spend the specified amount on behalf of the owner.
166    pub fn transfer_from(
167        &mut self,
168        owner: Address,
169        recipient: Address,
170        amount: U256,
171    ) -> Result<(), Error> {
172        let spender = detail::get_immediate_caller_address()?;
173        if amount.is_zero() {
174            return Ok(());
175        }
176        let spender_allowance = self.read_allowance(owner, spender);
177        let new_spender_allowance = spender_allowance
178            .checked_sub(amount)
179            .ok_or(Error::InsufficientAllowance)?;
180        self.transfer_balance(owner, recipient, amount)?;
181        self.write_allowance(owner, spender, new_spender_allowance);
182        Ok(())
183    }
184
185    /// Allows specific `owner` , `spender` to transfer up to `amount` of the owner's tokens.
186    pub fn _approve(
187        &mut self,
188        owner: Address,
189        spender: Address,
190        amount: U256,
191    ) -> Result<(), Error> {
192        self.write_allowance(owner, spender, amount);
193        Ok(())
194    }
195
196    /// Allows `spender` to transfer up to `amount` of the direct caller's tokens.
197    pub fn approve(&mut self, spender: Address, amount: U256) -> Result<(), Error> {
198        let owner = detail::get_immediate_caller_address()?;
199        self._approve(owner, spender, amount)
200    }
201
202    /// Returns the amount of `owner`'s tokens allowed to be spent by `spender`.
203    pub fn allowance(&self, owner: Address, spender: Address) -> U256 {
204        self.read_allowance(owner, spender)
205    }
206
207    /// NOTE: Custom increase_allowance function added
208    pub fn increase_allowance(&mut self, spender: Address, amount: U256) -> Result<(), Error> {
209        let owner = detail::get_immediate_caller_address()?;
210        let spender_allowance = self.read_allowance(owner, spender);
211        let new_allowance = spender_allowance
212            .checked_add(amount)
213            .ok_or(Error::Overflow)?;
214        if owner != spender {
215            self.approve(spender, new_allowance)?;
216            Ok(())
217        } else {
218            Err(Error::InvalidContext)
219        }
220    }
221
222    /// NOTE: Custom decrease_allowance function added
223    pub fn decrease_allowance(&mut self, spender: Address, amount: U256) -> Result<(), Error> {
224        let owner = detail::get_immediate_caller_address()?;
225        let spender_allowance = self.read_allowance(owner, spender);
226        let new_allowance: U256 = spender_allowance
227            .checked_sub(amount)
228            .ok_or(Error::Overflow)?;
229        if new_allowance >= 0.into() && new_allowance < spender_allowance && owner != spender {
230            self.approve(spender, new_allowance)?;
231            Ok(())
232        } else {
233            Err(Error::InvalidContext)
234        }
235    }
236
237    /// Mints `amount` new tokens and adds them to `owner`'s balance and to the token total supply.
238    ///
239    /// # Security
240    ///
241    /// This offers no security whatsoever, hence it is advised to NOT expose this method through a
242    /// public entry point.
243    pub fn mint(&mut self, owner: Address, amount: U256) -> Result<(), Error> {
244        let new_balance = {
245            let balance = self.read_balance(owner);
246            balance.checked_add(amount).ok_or(Error::Overflow)?
247        };
248        let new_total_supply = {
249            let total_supply: U256 = self.read_total_supply();
250            total_supply.checked_add(amount).ok_or(Error::Overflow)?
251        };
252        self.write_balance(owner, new_balance);
253        self.write_total_supply(new_total_supply);
254        Ok(())
255    }
256
257    /// Burns (i.e. subtracts) `amount` of tokens from `owner`'s balance and from the token total
258    /// supply.
259    ///
260    /// # Security
261    ///
262    /// This offers no security whatsoever, hence it is advised to NOT expose this method through a
263    /// public entry point.
264    pub fn burn(&mut self, owner: Address, amount: U256) -> Result<(), Error> {
265        let new_balance = {
266            let balance = self.read_balance(owner);
267            balance
268                .checked_sub(amount)
269                .ok_or(Error::InsufficientBalance)?
270        };
271        let new_total_supply = {
272            let total_supply = self.read_total_supply();
273            total_supply.checked_sub(amount).ok_or(Error::Overflow)?
274        };
275        self.write_balance(owner, new_balance);
276        self.write_total_supply(new_total_supply);
277        Ok(())
278    }
279
280    /// Generating named keys
281    ///
282    /// # NOTE - 'Custom function'
283    ///
284    /// Integrate this key storing method with our ERC20 interface
285    /// So generating keys so can be used while contract creation
286    pub fn named_keys(
287        &self,
288        name: String,
289        symbol: String,
290        decimals: u8,
291        initial_supply: U256,
292    ) -> Result<BTreeMap<String, Key>, Error> {
293        let balances_uref = storage::new_dictionary(BALANCES_KEY_NAME).unwrap_or_revert();
294        let allowances_uref = storage::new_dictionary(ALLOWANCES_KEY_NAME).unwrap_or_revert();
295        // We need to hold on a RW access rights because tokens can be minted or burned.
296        let total_supply_uref = storage::new_uref(initial_supply).into_read_write();
297
298        let mut named_keys = NamedKeys::new();
299
300        let name_key = {
301            let name_uref = storage::new_uref(name).into_read();
302            Key::from(name_uref)
303        };
304
305        let symbol_key = {
306            let symbol_uref = storage::new_uref(symbol).into_read();
307            Key::from(symbol_uref)
308        };
309
310        let decimals_key = {
311            let decimals_uref = storage::new_uref(decimals).into_read();
312            Key::from(decimals_uref)
313        };
314
315        let total_supply_key = Key::from(total_supply_uref);
316
317        let balances_dictionary_key = {
318            // Sets up initial balance for the caller - either an account, or a contract.
319            let caller = detail::get_caller_address()?;
320            balances::write_balance_to(balances_uref, caller, initial_supply);
321
322            runtime::remove_key(BALANCES_KEY_NAME);
323
324            Key::from(balances_uref)
325        };
326
327        let allowances_dictionary_key = {
328            runtime::remove_key(ALLOWANCES_KEY_NAME);
329
330            Key::from(allowances_uref)
331        };
332
333        named_keys.insert(NAME_KEY_NAME.to_string(), name_key);
334        named_keys.insert(SYMBOL_KEY_NAME.to_string(), symbol_key);
335        named_keys.insert(DECIMALS_KEY_NAME.to_string(), decimals_key);
336        named_keys.insert(BALANCES_KEY_NAME.to_string(), balances_dictionary_key);
337        named_keys.insert(ALLOWANCES_KEY_NAME.to_string(), allowances_dictionary_key);
338        named_keys.insert(TOTAL_SUPPLY_KEY_NAME.to_string(), total_supply_key);
339
340        Ok(named_keys)
341    }
342
343    /// Installs the ERC20 contract with a custom set of entry points.
344    ///
345    /// # Warning
346    ///
347    /// Contract developers should use [`ERC20::install`] instead, as it will create the default set
348    /// of ERC20 entry points. Using `install_custom` with a different set of entry points might
349    /// lead to problems with integrators such as wallets, and exchanges.
350    #[doc(hidden)]
351    pub fn install_custom(
352        name: String,
353        symbol: String,
354        decimals: u8,
355        initial_supply: U256,
356        contract_key_name: &str,
357        entry_points: EntryPoints,
358    ) -> Result<ERC20, Error> {
359        let balances_uref = storage::new_dictionary(BALANCES_KEY_NAME).unwrap_or_revert();
360        let allowances_uref = storage::new_dictionary(ALLOWANCES_KEY_NAME).unwrap_or_revert();
361        // We need to hold on a RW access rights because tokens can be minted or burned.
362        let total_supply_uref = storage::new_uref(initial_supply).into_read_write();
363
364        let mut named_keys = NamedKeys::new();
365
366        let name_key = {
367            let name_uref = storage::new_uref(name).into_read();
368            Key::from(name_uref)
369        };
370
371        let symbol_key = {
372            let symbol_uref = storage::new_uref(symbol).into_read();
373            Key::from(symbol_uref)
374        };
375
376        let decimals_key = {
377            let decimals_uref = storage::new_uref(decimals).into_read();
378            Key::from(decimals_uref)
379        };
380
381        let total_supply_key = Key::from(total_supply_uref);
382
383        let balances_dictionary_key = {
384            // Sets up initial balance for the caller - either an account, or a contract.
385            let caller = detail::get_caller_address()?;
386            balances::write_balance_to(balances_uref, caller, initial_supply);
387
388            runtime::remove_key(BALANCES_KEY_NAME);
389
390            Key::from(balances_uref)
391        };
392
393        let allowances_dictionary_key = {
394            runtime::remove_key(ALLOWANCES_KEY_NAME);
395
396            Key::from(allowances_uref)
397        };
398
399        named_keys.insert(NAME_KEY_NAME.to_string(), name_key);
400        named_keys.insert(SYMBOL_KEY_NAME.to_string(), symbol_key);
401        named_keys.insert(DECIMALS_KEY_NAME.to_string(), decimals_key);
402        named_keys.insert(BALANCES_KEY_NAME.to_string(), balances_dictionary_key);
403        named_keys.insert(ALLOWANCES_KEY_NAME.to_string(), allowances_dictionary_key);
404        named_keys.insert(TOTAL_SUPPLY_KEY_NAME.to_string(), total_supply_key);
405
406        let (contract_hash, _version) =
407            storage::new_locked_contract(entry_points, Some(named_keys), None, None);
408
409        // Hash of the installed contract will be reachable through named keys.
410        runtime::put_key(contract_key_name, Key::from(contract_hash));
411
412        Ok(ERC20::new(
413            balances_uref,
414            allowances_uref,
415            total_supply_uref,
416        ))
417    }
418}