curve_casper_erc20/
lib.rs

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