odra_modules/
erc20.rs

1//! ERC20 token standard implementation.
2use crate::erc20::errors::Error;
3use crate::erc20::events::*;
4use odra::casper_types::U256;
5use odra::prelude::*;
6
7/// ERC20 token module
8#[odra::module(events = [Approval, Transfer], errors = Error)]
9pub struct Erc20 {
10    decimals: Var<u8>,
11    symbol: Var<String>,
12    name: Var<String>,
13    total_supply: Var<U256>,
14    balances: Mapping<Address, U256>,
15    allowances: Mapping<(Address, Address), U256>
16}
17
18#[odra::module]
19impl Erc20 {
20    /// Initializes the contract with the given metadata and initial supply.
21    pub fn init(
22        &mut self,
23        symbol: String,
24        name: String,
25        decimals: u8,
26        initial_supply: Option<U256>
27    ) {
28        let caller = self.env().caller();
29        self.symbol.set(symbol);
30        self.name.set(name);
31        self.decimals.set(decimals);
32
33        if let Some(initial_supply) = initial_supply {
34            self.total_supply.set(initial_supply);
35            self.balances.set(&caller, initial_supply);
36
37            if !initial_supply.is_zero() {
38                self.env().emit_event(Transfer {
39                    from: None,
40                    to: Some(caller),
41                    amount: initial_supply
42                });
43            }
44        }
45    }
46
47    /// Transfers tokens from the caller to the recipient.
48    pub fn transfer(&mut self, recipient: &Address, amount: &U256) {
49        let caller = self.env().caller();
50        self.raw_transfer(&caller, recipient, amount);
51    }
52
53    /// Transfers tokens from the owner to the recipient using the spender's allowance.
54    pub fn transfer_from(&mut self, owner: &Address, recipient: &Address, amount: &U256) {
55        let spender = self.env().caller();
56
57        self.spend_allowance(owner, &spender, amount);
58        self.raw_transfer(owner, recipient, amount);
59    }
60
61    /// Approves the spender to spend the given amount of tokens on behalf of the caller.
62    pub fn approve(&mut self, spender: &Address, amount: &U256) {
63        let owner = self.env().caller();
64
65        self.allowances.set(&(owner, *spender), *amount);
66        self.env().emit_event(Approval {
67            owner,
68            spender: *spender,
69            value: *amount
70        });
71    }
72
73    /// Returns the name of the token.
74    pub fn name(&self) -> String {
75        self.name.get_or_revert_with(Error::NameNotSet)
76    }
77
78    /// Returns the symbol of the token.
79    pub fn symbol(&self) -> String {
80        self.symbol.get_or_revert_with(Error::SymbolNotSet)
81    }
82
83    /// Returns the number of decimals the token uses.
84    pub fn decimals(&self) -> u8 {
85        self.decimals.get_or_revert_with(Error::DecimalsNotSet)
86    }
87
88    /// Returns the total supply of the token.
89    pub fn total_supply(&self) -> U256 {
90        self.total_supply.get_or_default()
91    }
92
93    /// Returns the balance of the given address.
94    pub fn balance_of(&self, address: &Address) -> U256 {
95        self.balances.get_or_default(address)
96    }
97
98    /// Returns the amount of tokens the owner has allowed the spender to spend.
99    pub fn allowance(&self, owner: &Address, spender: &Address) -> U256 {
100        self.allowances.get_or_default(&(*owner, *spender))
101    }
102
103    /// Mints new tokens and assigns them to the given address.
104    pub fn mint(&mut self, address: &Address, amount: &U256) {
105        self.total_supply.add(*amount);
106        self.balances.add(address, *amount);
107
108        self.env().emit_event(Transfer {
109            from: None,
110            to: Some(*address),
111            amount: *amount
112        });
113    }
114
115    /// Burns the given amount of tokens from the given address.
116    pub fn burn(&mut self, address: &Address, amount: &U256) {
117        if self.balance_of(address) < *amount {
118            self.env().revert(Error::InsufficientBalance);
119        }
120        self.total_supply.subtract(*amount);
121        self.balances.subtract(address, *amount);
122
123        self.env().emit_event(Transfer {
124            from: Some(*address),
125            to: None,
126            amount: *amount
127        });
128    }
129}
130
131impl Erc20 {
132    fn raw_transfer(&mut self, owner: &Address, recipient: &Address, amount: &U256) {
133        if *amount > self.balances.get_or_default(owner) {
134            self.env().revert(Error::InsufficientBalance)
135        }
136
137        self.balances.subtract(owner, *amount);
138        self.balances.add(recipient, *amount);
139
140        self.env().emit_event(Transfer {
141            from: Some(*owner),
142            to: Some(*recipient),
143            amount: *amount
144        });
145    }
146
147    fn spend_allowance(&mut self, owner: &Address, spender: &Address, amount: &U256) {
148        let allowance = self.allowances.get_or_default(&(*owner, *spender));
149        if allowance < *amount {
150            self.env().revert(Error::InsufficientAllowance)
151        }
152        self.allowances.subtract(&(*owner, *spender), *amount);
153
154        self.env().emit_event(Approval {
155            owner: *owner,
156            spender: *spender,
157            value: allowance - *amount
158        });
159    }
160}
161
162/// ERC20 Events
163pub mod events {
164    use odra::casper_event_standard;
165    use odra::casper_types::U256;
166    use odra::prelude::*;
167
168    /// Transfer event
169    #[odra::event]
170    pub struct Transfer {
171        /// Sender of the tokens.
172        pub from: Option<Address>,
173        /// Recipient of the tokens.
174        pub to: Option<Address>,
175        /// Amount of tokens transferred.
176        pub amount: U256
177    }
178
179    /// Approval event
180    #[odra::event]
181    pub struct Approval {
182        /// Owner of the tokens.
183        pub owner: Address,
184        /// Spender of the tokens.
185        pub spender: Address,
186        /// Amount of tokens approved.
187        pub value: U256
188    }
189}
190
191/// ERC20 Errors
192pub mod errors {
193    use odra::prelude::*;
194
195    /// ERC20 errors
196    #[odra::odra_error]
197    pub enum Error {
198        /// Insufficient balance
199        InsufficientBalance = 30_000,
200        /// Insufficient allowance
201        InsufficientAllowance = 30_001,
202        /// Name not set
203        NameNotSet = 30_002,
204        /// Symbol not set
205        SymbolNotSet = 30_003,
206        /// Decimals not set
207        DecimalsNotSet = 30_004
208    }
209}
210
211#[cfg(test)]
212mod tests {
213    use super::{
214        errors::Error,
215        events::{Approval, Transfer},
216        Erc20, Erc20HostRef, Erc20InitArgs
217    };
218    use odra::{
219        casper_types::U256,
220        host::{Deployer, HostEnv},
221        prelude::*
222    };
223
224    const NAME: &str = "Plascoin";
225    const SYMBOL: &str = "PLS";
226    const DECIMALS: u8 = 10;
227    const INITIAL_SUPPLY: u32 = 10_000;
228
229    fn setup() -> (HostEnv, Erc20HostRef) {
230        let env = odra_test::env();
231        (
232            env.clone(),
233            Erc20::deploy(
234                &env,
235                Erc20InitArgs {
236                    symbol: SYMBOL.to_string(),
237                    name: NAME.to_string(),
238                    decimals: DECIMALS,
239                    initial_supply: Some(INITIAL_SUPPLY.into())
240                }
241            )
242        )
243    }
244
245    #[test]
246    fn initialization() {
247        // When deploy a contract with the initial supply.
248        let (env, erc20) = setup();
249
250        // Then the contract has the metadata set.
251        assert_eq!(erc20.symbol(), SYMBOL.to_string());
252        assert_eq!(erc20.name(), NAME.to_string());
253        assert_eq!(erc20.decimals(), DECIMALS);
254
255        // Then the total supply is updated.
256        assert_eq!(erc20.total_supply(), INITIAL_SUPPLY.into());
257
258        // Then a Transfer event was emitted.
259        assert!(env.emitted_event(
260            &erc20,
261            Transfer {
262                from: None,
263                to: Some(env.get_account(0)),
264                amount: INITIAL_SUPPLY.into()
265            }
266        ));
267    }
268
269    #[test]
270    fn transfer_works() {
271        // Given a new contract.
272        let (env, mut erc20) = setup();
273
274        // When transfer tokens to a recipient.
275        let sender = env.get_account(0);
276        let recipient = env.get_account(1);
277        let amount = 1_000.into();
278        erc20.transfer(&recipient, &amount);
279
280        // Then the sender balance is deducted.
281        assert_eq!(
282            erc20.balance_of(&sender),
283            U256::from(INITIAL_SUPPLY) - amount
284        );
285
286        // Then the recipient balance is updated.
287        assert_eq!(erc20.balance_of(&recipient), amount);
288
289        // Then Transfer event was emitted.
290        assert!(env.emitted_event(
291            &erc20,
292            Transfer {
293                from: Some(sender),
294                to: Some(recipient),
295                amount
296            }
297        ));
298    }
299
300    #[test]
301    fn transfer_error() {
302        // Given a new contract.
303        let (env, mut erc20) = setup();
304
305        // When the transfer amount exceeds the sender balance.
306        let recipient = env.get_account(1);
307        let amount = U256::from(INITIAL_SUPPLY) + U256::one();
308
309        // Then an error occurs.
310        assert!(erc20.try_transfer(&recipient, &amount).is_err());
311    }
312
313    #[test]
314    fn transfer_from_and_approval_work() {
315        let (env, mut erc20) = setup();
316
317        let (owner, recipient, spender) =
318            (env.get_account(0), env.get_account(1), env.get_account(2));
319        let approved_amount = 3_000.into();
320        let transfer_amount = 1_000.into();
321
322        assert_eq!(erc20.balance_of(&owner), U256::from(INITIAL_SUPPLY));
323
324        // Owner approves Spender.
325        erc20.approve(&spender, &approved_amount);
326
327        // Allowance was recorded.
328        assert_eq!(erc20.allowance(&owner, &spender), approved_amount);
329        assert!(env.emitted_event(
330            &erc20,
331            Approval {
332                owner,
333                spender,
334                value: approved_amount
335            }
336        ));
337
338        // Spender transfers tokens from Owner to Recipient.
339        env.set_caller(spender);
340        erc20.transfer_from(&owner, &recipient, &transfer_amount);
341
342        // Tokens are transferred and allowance decremented.
343        assert_eq!(
344            erc20.balance_of(&owner),
345            U256::from(INITIAL_SUPPLY) - transfer_amount
346        );
347        assert_eq!(erc20.balance_of(&recipient), transfer_amount);
348        assert!(env.emitted_event(
349            &erc20,
350            Approval {
351                owner,
352                spender,
353                value: approved_amount - transfer_amount
354            }
355        ));
356        assert!(env.emitted_event(
357            &erc20,
358            Transfer {
359                from: Some(owner),
360                to: Some(recipient),
361                amount: transfer_amount
362            }
363        ));
364    }
365
366    #[test]
367    fn transfer_from_error() {
368        // Given a new instance.
369        let (env, mut erc20) = setup();
370
371        // When the spender's allowance is zero.
372        let (owner, spender, recipient) =
373            (env.get_account(0), env.get_account(1), env.get_account(2));
374        let amount = 1_000.into();
375        env.set_caller(spender);
376
377        // Then transfer fails.
378        assert_eq!(
379            erc20.try_transfer_from(&owner, &recipient, &amount),
380            Err(Error::InsufficientAllowance.into())
381        );
382    }
383}