erc20/
lib.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2
3use ink_lang as ink;
4
5#[ink::contract]
6mod erc20 {
7    use ink_storage::{
8        traits::SpreadAllocate,
9        Mapping,
10    };
11
12    /// A simple ERC-20 contract.
13    #[ink(storage)]
14    #[derive(SpreadAllocate)]
15    pub struct Erc20 {
16        /// Total token supply.
17        total_supply: Balance,
18        /// Mapping from owner to number of owned token.
19        balances: Mapping<AccountId, Balance>,
20        /// Mapping of the token amount which an account is allowed to withdraw
21        /// from another account.
22        allowances: Mapping<(AccountId, AccountId), Balance>,
23    }
24
25    /// Event emitted when a token transfer occurs.
26    #[ink(event)]
27    pub struct Transfer {
28        #[ink(topic)]
29        from: Option<AccountId>,
30        #[ink(topic)]
31        to: Option<AccountId>,
32        value: Balance,
33    }
34
35    /// Event emitted when an approval occurs that `spender` is allowed to withdraw
36    /// up to the amount of `value` tokens from `owner`.
37    #[ink(event)]
38    pub struct Approval {
39        #[ink(topic)]
40        owner: AccountId,
41        #[ink(topic)]
42        spender: AccountId,
43        value: Balance,
44    }
45
46    /// The ERC-20 error types.
47    #[derive(Debug, PartialEq, Eq, scale::Encode, scale::Decode)]
48    #[cfg_attr(feature = "std", derive(scale_info::TypeInfo))]
49    pub enum Error {
50        /// Returned if not enough balance to fulfill a request is available.
51        InsufficientBalance,
52        /// Returned if not enough allowance to fulfill a request is available.
53        InsufficientAllowance,
54    }
55
56    /// The ERC-20 result type.
57    pub type Result<T> = core::result::Result<T, Error>;
58
59    impl Erc20 {
60        /// Creates a new ERC-20 contract with the specified initial supply.
61        #[ink(constructor)]
62        pub fn new(initial_supply: Balance) -> Self {
63            // This call is required in order to correctly initialize the
64            // `Mapping`s of our contract.
65            ink_lang::utils::initialize_contract(|contract| {
66                Self::new_init(contract, initial_supply)
67            })
68        }
69
70        /// Default initializes the ERC-20 contract with the specified initial supply.
71        fn new_init(&mut self, initial_supply: Balance) {
72            let caller = Self::env().caller();
73            self.balances.insert(&caller, &initial_supply);
74            self.total_supply = initial_supply;
75            Self::env().emit_event(Transfer {
76                from: None,
77                to: Some(caller),
78                value: initial_supply,
79            });
80        }
81
82        /// Returns the total token supply.
83        #[ink(message)]
84        pub fn total_supply(&self) -> Balance {
85            self.total_supply
86        }
87
88        /// Returns the account balance for the specified `owner`.
89        ///
90        /// Returns `0` if the account is non-existent.
91        #[ink(message)]
92        pub fn balance_of(&self, owner: AccountId) -> Balance {
93            self.balance_of_impl(&owner)
94        }
95
96        /// Returns the account balance for the specified `owner`.
97        ///
98        /// Returns `0` if the account is non-existent.
99        ///
100        /// # Note
101        ///
102        /// Prefer to call this method over `balance_of` since this
103        /// works using references which are more efficient in Wasm.
104        #[inline]
105        fn balance_of_impl(&self, owner: &AccountId) -> Balance {
106            self.balances.get(owner).unwrap_or_default()
107        }
108
109        /// Returns the amount which `spender` is still allowed to withdraw from `owner`.
110        ///
111        /// Returns `0` if no allowance has been set.
112        #[ink(message)]
113        pub fn allowance(&self, owner: AccountId, spender: AccountId) -> Balance {
114            self.allowance_impl(&owner, &spender)
115        }
116
117        /// Returns the amount which `spender` is still allowed to withdraw from `owner`.
118        ///
119        /// Returns `0` if no allowance has been set.
120        ///
121        /// # Note
122        ///
123        /// Prefer to call this method over `allowance` since this
124        /// works using references which are more efficient in Wasm.
125        #[inline]
126        fn allowance_impl(&self, owner: &AccountId, spender: &AccountId) -> Balance {
127            self.allowances.get((owner, spender)).unwrap_or_default()
128        }
129
130        /// Transfers `value` amount of tokens from the caller's account to account `to`.
131        ///
132        /// On success a `Transfer` event is emitted.
133        ///
134        /// # Errors
135        ///
136        /// Returns `InsufficientBalance` error if there are not enough tokens on
137        /// the caller's account balance.
138        #[ink(message)]
139        pub fn transfer(&mut self, to: AccountId, value: Balance) -> Result<()> {
140            let from = self.env().caller();
141            self.transfer_from_to(&from, &to, value)
142        }
143
144        /// Allows `spender` to withdraw from the caller's account multiple times, up to
145        /// the `value` amount.
146        ///
147        /// If this function is called again it overwrites the current allowance with `value`.
148        ///
149        /// An `Approval` event is emitted.
150        #[ink(message)]
151        pub fn approve(&mut self, spender: AccountId, value: Balance) -> Result<()> {
152            let owner = self.env().caller();
153            self.allowances.insert((&owner, &spender), &value);
154            self.env().emit_event(Approval {
155                owner,
156                spender,
157                value,
158            });
159            Ok(())
160        }
161
162        /// Transfers `value` tokens on the behalf of `from` to the account `to`.
163        ///
164        /// This can be used to allow a contract to transfer tokens on ones behalf and/or
165        /// to charge fees in sub-currencies, for example.
166        ///
167        /// On success a `Transfer` event is emitted.
168        ///
169        /// # Errors
170        ///
171        /// Returns `InsufficientAllowance` error if there are not enough tokens allowed
172        /// for the caller to withdraw from `from`.
173        ///
174        /// Returns `InsufficientBalance` error if there are not enough tokens on
175        /// the account balance of `from`.
176        #[ink(message)]
177        pub fn transfer_from(
178            &mut self,
179            from: AccountId,
180            to: AccountId,
181            value: Balance,
182        ) -> Result<()> {
183            let caller = self.env().caller();
184            let allowance = self.allowance_impl(&from, &caller);
185            if allowance < value {
186                return Err(Error::InsufficientAllowance)
187            }
188            self.transfer_from_to(&from, &to, value)?;
189            self.allowances
190                .insert((&from, &caller), &(allowance - value));
191            Ok(())
192        }
193
194        /// Transfers `value` amount of tokens from the caller's account to account `to`.
195        ///
196        /// On success a `Transfer` event is emitted.
197        ///
198        /// # Errors
199        ///
200        /// Returns `InsufficientBalance` error if there are not enough tokens on
201        /// the caller's account balance.
202        fn transfer_from_to(
203            &mut self,
204            from: &AccountId,
205            to: &AccountId,
206            value: Balance,
207        ) -> Result<()> {
208            let from_balance = self.balance_of_impl(from);
209            if from_balance < value {
210                return Err(Error::InsufficientBalance)
211            }
212
213            self.balances.insert(from, &(from_balance - value));
214            let to_balance = self.balance_of_impl(to);
215            self.balances.insert(to, &(to_balance + value));
216            self.env().emit_event(Transfer {
217                from: Some(*from),
218                to: Some(*to),
219                value,
220            });
221            Ok(())
222        }
223    }
224
225    #[cfg(test)]
226    mod tests {
227        use super::*;
228
229        use ink_env::Clear;
230        use ink_lang as ink;
231
232        type Event = <Erc20 as ::ink_lang::reflect::ContractEventBase>::Type;
233
234        fn assert_transfer_event(
235            event: &ink_env::test::EmittedEvent,
236            expected_from: Option<AccountId>,
237            expected_to: Option<AccountId>,
238            expected_value: Balance,
239        ) {
240            let decoded_event = <Event as scale::Decode>::decode(&mut &event.data[..])
241                .expect("encountered invalid contract event data buffer");
242            if let Event::Transfer(Transfer { from, to, value }) = decoded_event {
243                assert_eq!(from, expected_from, "encountered invalid Transfer.from");
244                assert_eq!(to, expected_to, "encountered invalid Transfer.to");
245                assert_eq!(value, expected_value, "encountered invalid Trasfer.value");
246            } else {
247                panic!("encountered unexpected event kind: expected a Transfer event")
248            }
249            let expected_topics = vec![
250                encoded_into_hash(&PrefixedValue {
251                    value: b"Erc20::Transfer",
252                    prefix: b"",
253                }),
254                encoded_into_hash(&PrefixedValue {
255                    prefix: b"Erc20::Transfer::from",
256                    value: &expected_from,
257                }),
258                encoded_into_hash(&PrefixedValue {
259                    prefix: b"Erc20::Transfer::to",
260                    value: &expected_to,
261                }),
262                encoded_into_hash(&PrefixedValue {
263                    prefix: b"Erc20::Transfer::value",
264                    value: &expected_value,
265                }),
266            ];
267
268            let topics = event.topics.clone();
269            for (n, (actual_topic, expected_topic)) in
270                topics.iter().zip(expected_topics).enumerate()
271            {
272                let mut topic_hash = Hash::clear();
273                let len = actual_topic.len();
274                topic_hash.as_mut()[0..len].copy_from_slice(&actual_topic[0..len]);
275
276                assert_eq!(
277                    topic_hash, expected_topic,
278                    "encountered invalid topic at {}",
279                    n
280                );
281            }
282        }
283
284        /// The default constructor does its job.
285        #[ink::test]
286        fn new_works() {
287            // Constructor works.
288            let _erc20 = Erc20::new(100);
289
290            // Transfer event triggered during initial construction.
291            let emitted_events = ink_env::test::recorded_events().collect::<Vec<_>>();
292            assert_eq!(1, emitted_events.len());
293
294            assert_transfer_event(
295                &emitted_events[0],
296                None,
297                Some(AccountId::from([0x01; 32])),
298                100,
299            );
300        }
301
302        /// The total supply was applied.
303        #[ink::test]
304        fn total_supply_works() {
305            // Constructor works.
306            let erc20 = Erc20::new(100);
307            // Transfer event triggered during initial construction.
308            let emitted_events = ink_env::test::recorded_events().collect::<Vec<_>>();
309            assert_transfer_event(
310                &emitted_events[0],
311                None,
312                Some(AccountId::from([0x01; 32])),
313                100,
314            );
315            // Get the token total supply.
316            assert_eq!(erc20.total_supply(), 100);
317        }
318
319        /// Get the actual balance of an account.
320        #[ink::test]
321        fn balance_of_works() {
322            // Constructor works
323            let erc20 = Erc20::new(100);
324            // Transfer event triggered during initial construction
325            let emitted_events = ink_env::test::recorded_events().collect::<Vec<_>>();
326            assert_transfer_event(
327                &emitted_events[0],
328                None,
329                Some(AccountId::from([0x01; 32])),
330                100,
331            );
332            let accounts =
333                ink_env::test::default_accounts::<ink_env::DefaultEnvironment>();
334            // Alice owns all the tokens on contract instantiation
335            assert_eq!(erc20.balance_of(accounts.alice), 100);
336            // Bob does not owns tokens
337            assert_eq!(erc20.balance_of(accounts.bob), 0);
338        }
339
340        #[ink::test]
341        fn transfer_works() {
342            // Constructor works.
343            let mut erc20 = Erc20::new(100);
344            // Transfer event triggered during initial construction.
345            let accounts =
346                ink_env::test::default_accounts::<ink_env::DefaultEnvironment>();
347
348            assert_eq!(erc20.balance_of(accounts.bob), 0);
349            // Alice transfers 10 tokens to Bob.
350            assert_eq!(erc20.transfer(accounts.bob, 10), Ok(()));
351            // Bob owns 10 tokens.
352            assert_eq!(erc20.balance_of(accounts.bob), 10);
353
354            let emitted_events = ink_env::test::recorded_events().collect::<Vec<_>>();
355            assert_eq!(emitted_events.len(), 2);
356            // Check first transfer event related to ERC-20 instantiation.
357            assert_transfer_event(
358                &emitted_events[0],
359                None,
360                Some(AccountId::from([0x01; 32])),
361                100,
362            );
363            // Check the second transfer event relating to the actual trasfer.
364            assert_transfer_event(
365                &emitted_events[1],
366                Some(AccountId::from([0x01; 32])),
367                Some(AccountId::from([0x02; 32])),
368                10,
369            );
370        }
371
372        #[ink::test]
373        fn invalid_transfer_should_fail() {
374            // Constructor works.
375            let mut erc20 = Erc20::new(100);
376            let accounts =
377                ink_env::test::default_accounts::<ink_env::DefaultEnvironment>();
378
379            assert_eq!(erc20.balance_of(accounts.bob), 0);
380
381            // Set the contract as callee and Bob as caller.
382            let contract = ink_env::account_id::<ink_env::DefaultEnvironment>();
383            ink_env::test::set_callee::<ink_env::DefaultEnvironment>(contract);
384            ink_env::test::set_caller::<ink_env::DefaultEnvironment>(accounts.bob);
385
386            // Bob fails to transfers 10 tokens to Eve.
387            assert_eq!(
388                erc20.transfer(accounts.eve, 10),
389                Err(Error::InsufficientBalance)
390            );
391            // Alice owns all the tokens.
392            assert_eq!(erc20.balance_of(accounts.alice), 100);
393            assert_eq!(erc20.balance_of(accounts.bob), 0);
394            assert_eq!(erc20.balance_of(accounts.eve), 0);
395
396            // Transfer event triggered during initial construction.
397            let emitted_events = ink_env::test::recorded_events().collect::<Vec<_>>();
398            assert_eq!(emitted_events.len(), 1);
399            assert_transfer_event(
400                &emitted_events[0],
401                None,
402                Some(AccountId::from([0x01; 32])),
403                100,
404            );
405        }
406
407        #[ink::test]
408        fn transfer_from_works() {
409            // Constructor works.
410            let mut erc20 = Erc20::new(100);
411            // Transfer event triggered during initial construction.
412            let accounts =
413                ink_env::test::default_accounts::<ink_env::DefaultEnvironment>();
414
415            // Bob fails to transfer tokens owned by Alice.
416            assert_eq!(
417                erc20.transfer_from(accounts.alice, accounts.eve, 10),
418                Err(Error::InsufficientAllowance)
419            );
420            // Alice approves Bob for token transfers on her behalf.
421            assert_eq!(erc20.approve(accounts.bob, 10), Ok(()));
422
423            // The approve event takes place.
424            assert_eq!(ink_env::test::recorded_events().count(), 2);
425
426            // Set the contract as callee and Bob as caller.
427            let contract = ink_env::account_id::<ink_env::DefaultEnvironment>();
428            ink_env::test::set_callee::<ink_env::DefaultEnvironment>(contract);
429            ink_env::test::set_caller::<ink_env::DefaultEnvironment>(accounts.bob);
430
431            // Bob transfers tokens from Alice to Eve.
432            assert_eq!(
433                erc20.transfer_from(accounts.alice, accounts.eve, 10),
434                Ok(())
435            );
436            // Eve owns tokens.
437            assert_eq!(erc20.balance_of(accounts.eve), 10);
438
439            // Check all transfer events that happened during the previous calls:
440            let emitted_events = ink_env::test::recorded_events().collect::<Vec<_>>();
441            assert_eq!(emitted_events.len(), 3);
442            assert_transfer_event(
443                &emitted_events[0],
444                None,
445                Some(AccountId::from([0x01; 32])),
446                100,
447            );
448            // The second event `emitted_events[1]` is an Approve event that we skip checking.
449            assert_transfer_event(
450                &emitted_events[2],
451                Some(AccountId::from([0x01; 32])),
452                Some(AccountId::from([0x05; 32])),
453                10,
454            );
455        }
456
457        #[ink::test]
458        fn allowance_must_not_change_on_failed_transfer() {
459            let mut erc20 = Erc20::new(100);
460            let accounts =
461                ink_env::test::default_accounts::<ink_env::DefaultEnvironment>();
462
463            // Alice approves Bob for token transfers on her behalf.
464            let alice_balance = erc20.balance_of(accounts.alice);
465            let initial_allowance = alice_balance + 2;
466            assert_eq!(erc20.approve(accounts.bob, initial_allowance), Ok(()));
467
468            // Get contract address.
469            let callee = ink_env::account_id::<ink_env::DefaultEnvironment>();
470            ink_env::test::set_callee::<ink_env::DefaultEnvironment>(callee);
471            ink_env::test::set_caller::<ink_env::DefaultEnvironment>(accounts.bob);
472
473            // Bob tries to transfer tokens from Alice to Eve.
474            let emitted_events_before = ink_env::test::recorded_events().count();
475            assert_eq!(
476                erc20.transfer_from(accounts.alice, accounts.eve, alice_balance + 1),
477                Err(Error::InsufficientBalance)
478            );
479            // Allowance must have stayed the same
480            assert_eq!(
481                erc20.allowance(accounts.alice, accounts.bob),
482                initial_allowance
483            );
484            // No more events must have been emitted
485            assert_eq!(
486                emitted_events_before,
487                ink_env::test::recorded_events().count()
488            )
489        }
490
491        /// For calculating the event topic hash.
492        struct PrefixedValue<'a, 'b, T> {
493            pub prefix: &'a [u8],
494            pub value: &'b T,
495        }
496
497        impl<X> scale::Encode for PrefixedValue<'_, '_, X>
498        where
499            X: scale::Encode,
500        {
501            #[inline]
502            fn size_hint(&self) -> usize {
503                self.prefix.size_hint() + self.value.size_hint()
504            }
505
506            #[inline]
507            fn encode_to<T: scale::Output + ?Sized>(&self, dest: &mut T) {
508                self.prefix.encode_to(dest);
509                self.value.encode_to(dest);
510            }
511        }
512
513        fn encoded_into_hash<T>(entity: &T) -> Hash
514        where
515            T: scale::Encode,
516        {
517            use ink_env::{
518                hash::{
519                    Blake2x256,
520                    CryptoHash,
521                    HashOutput,
522                },
523                Clear,
524            };
525            let mut result = Hash::clear();
526            let len_result = result.as_ref().len();
527            let encoded = entity.encode();
528            let len_encoded = encoded.len();
529            if len_encoded <= len_result {
530                result.as_mut()[..len_encoded].copy_from_slice(&encoded);
531                return result
532            }
533            let mut hash_output =
534                <<Blake2x256 as HashOutput>::Type as Default>::default();
535            <Blake2x256 as CryptoHash>::hash(&encoded, &mut hash_output);
536            let copy_len = core::cmp::min(hash_output.len(), len_result);
537            result.as_mut()[0..copy_len].copy_from_slice(&hash_output[0..copy_len]);
538            result
539        }
540    }
541}