#![cfg_attr(not(feature = "std"), no_std)] #[ink::contract] mod erc20 { use ink::storage::Mapping; /// A simple ERC-20 contract. #[ink(storage)] #[derive(Default)] pub struct Erc20 { /// Total token supply. total_supply: Balance, /// Mapping from owner to number of owned token. balances: Mapping, /// Mapping of the token amount which an account is allowed to withdraw /// from another account. allowances: Mapping<(AccountId, AccountId), Balance>, } /// Event emitted when a token transfer occurs. #[ink(event)] pub struct Transfer { #[ink(topic)] from: Option, #[ink(topic)] to: Option, value: Balance, } /// Event emitted when an approval occurs that `spender` is allowed to withdraw /// up to the amount of `value` tokens from `owner`. #[ink(event)] pub struct Approval { #[ink(topic)] owner: AccountId, #[ink(topic)] spender: AccountId, value: Balance, } /// The ERC-20 error types. #[derive(Debug, PartialEq, Eq, scale::Encode, scale::Decode)] #[cfg_attr(feature = "std", derive(scale_info::TypeInfo))] pub enum Error { /// Returned if not enough balance to fulfill a request is available. InsufficientBalance, /// Returned if not enough allowance to fulfill a request is available. InsufficientAllowance, } /// The ERC-20 result type. pub type Result = core::result::Result; impl Erc20 { /// Creates a new ERC-20 contract with the specified initial supply. #[ink(constructor)] pub fn new(total_supply: Balance) -> Self { let mut balances = Mapping::default(); let caller = Self::env().caller(); balances.insert(&caller, &total_supply); Self::env().emit_event(Transfer { from: None, to: Some(caller), value: total_supply, }); Self { total_supply, balances, allowances: Default::default(), } } /// Returns the total token supply. #[ink(message)] pub fn total_supply(&self) -> Balance { self.total_supply } /// Returns the account balance for the specified `owner`. /// /// Returns `0` if the account is non-existent. #[ink(message)] pub fn balance_of(&self, owner: AccountId) -> Balance { self.balance_of_impl(&owner) } /// Returns the account balance for the specified `owner`. /// /// Returns `0` if the account is non-existent. /// /// # Note /// /// Prefer to call this method over `balance_of` since this /// works using references which are more efficient in Wasm. #[inline] fn balance_of_impl(&self, owner: &AccountId) -> Balance { self.balances.get(owner).unwrap_or_default() } /// Returns the amount which `spender` is still allowed to withdraw from `owner`. /// /// Returns `0` if no allowance has been set. #[ink(message)] pub fn allowance(&self, owner: AccountId, spender: AccountId) -> Balance { self.allowance_impl(&owner, &spender) } /// Returns the amount which `spender` is still allowed to withdraw from `owner`. /// /// Returns `0` if no allowance has been set. /// /// # Note /// /// Prefer to call this method over `allowance` since this /// works using references which are more efficient in Wasm. #[inline] fn allowance_impl(&self, owner: &AccountId, spender: &AccountId) -> Balance { self.allowances.get((owner, spender)).unwrap_or_default() } /// Transfers `value` amount of tokens from the caller's account to account `to`. /// /// On success a `Transfer` event is emitted. /// /// # Errors /// /// Returns `InsufficientBalance` error if there are not enough tokens on /// the caller's account balance. #[ink(message)] pub fn transfer(&mut self, to: AccountId, value: Balance) -> Result<()> { let from = self.env().caller(); self.transfer_from_to(&from, &to, value) } /// Allows `spender` to withdraw from the caller's account multiple times, up to /// the `value` amount. /// /// If this function is called again it overwrites the current allowance with `value`. /// /// An `Approval` event is emitted. #[ink(message)] pub fn approve(&mut self, spender: AccountId, value: Balance) -> Result<()> { let owner = self.env().caller(); self.allowances.insert((&owner, &spender), &value); self.env().emit_event(Approval { owner, spender, value, }); Ok(()) } /// Transfers `value` tokens on the behalf of `from` to the account `to`. /// /// This can be used to allow a contract to transfer tokens on ones behalf and/or /// to charge fees in sub-currencies, for example. /// /// On success a `Transfer` event is emitted. /// /// # Errors /// /// Returns `InsufficientAllowance` error if there are not enough tokens allowed /// for the caller to withdraw from `from`. /// /// Returns `InsufficientBalance` error if there are not enough tokens on /// the account balance of `from`. #[ink(message)] pub fn transfer_from( &mut self, from: AccountId, to: AccountId, value: Balance, ) -> Result<()> { let caller = self.env().caller(); let allowance = self.allowance_impl(&from, &caller); if allowance < value { return Err(Error::InsufficientAllowance) } self.transfer_from_to(&from, &to, value)?; self.allowances .insert((&from, &caller), &(allowance - value)); Ok(()) } /// Transfers `value` amount of tokens from the caller's account to account `to`. /// /// On success a `Transfer` event is emitted. /// /// # Errors /// /// Returns `InsufficientBalance` error if there are not enough tokens on /// the caller's account balance. fn transfer_from_to( &mut self, from: &AccountId, to: &AccountId, value: Balance, ) -> Result<()> { let from_balance = self.balance_of_impl(from); if from_balance < value { return Err(Error::InsufficientBalance) } self.balances.insert(from, &(from_balance - value)); let to_balance = self.balance_of_impl(to); self.balances.insert(to, &(to_balance + value)); self.env().emit_event(Transfer { from: Some(*from), to: Some(*to), value, }); Ok(()) } } #[cfg(test)] mod tests { use super::*; use ink::primitives::Clear; type Event = ::Type; fn assert_transfer_event( event: &ink::env::test::EmittedEvent, expected_from: Option, expected_to: Option, expected_value: Balance, ) { let decoded_event = ::decode(&mut &event.data[..]) .expect("encountered invalid contract event data buffer"); if let Event::Transfer(Transfer { from, to, value }) = decoded_event { assert_eq!(from, expected_from, "encountered invalid Transfer.from"); assert_eq!(to, expected_to, "encountered invalid Transfer.to"); assert_eq!(value, expected_value, "encountered invalid Trasfer.value"); } else { panic!("encountered unexpected event kind: expected a Transfer event") } let expected_topics = vec![ encoded_into_hash(&PrefixedValue { value: b"Erc20::Transfer", prefix: b"", }), encoded_into_hash(&PrefixedValue { prefix: b"Erc20::Transfer::from", value: &expected_from, }), encoded_into_hash(&PrefixedValue { prefix: b"Erc20::Transfer::to", value: &expected_to, }), encoded_into_hash(&PrefixedValue { prefix: b"Erc20::Transfer::value", value: &expected_value, }), ]; let topics = event.topics.clone(); for (n, (actual_topic, expected_topic)) in topics.iter().zip(expected_topics).enumerate() { let mut topic_hash = Hash::clear(); let len = actual_topic.len(); topic_hash.as_mut()[0..len].copy_from_slice(&actual_topic[0..len]); assert_eq!( topic_hash, expected_topic, "encountered invalid topic at {}", n ); } } /// The default constructor does its job. #[ink::test] fn new_works() { // Constructor works. let _erc20 = Erc20::new(100); // Transfer event triggered during initial construction. let emitted_events = ink::env::test::recorded_events().collect::>(); assert_eq!(1, emitted_events.len()); assert_transfer_event( &emitted_events[0], None, Some(AccountId::from([0x01; 32])), 100, ); } /// The total supply was applied. #[ink::test] fn total_supply_works() { // Constructor works. let erc20 = Erc20::new(100); // Transfer event triggered during initial construction. let emitted_events = ink::env::test::recorded_events().collect::>(); assert_transfer_event( &emitted_events[0], None, Some(AccountId::from([0x01; 32])), 100, ); // Get the token total supply. assert_eq!(erc20.total_supply(), 100); } /// Get the actual balance of an account. #[ink::test] fn balance_of_works() { // Constructor works let erc20 = Erc20::new(100); // Transfer event triggered during initial construction let emitted_events = ink::env::test::recorded_events().collect::>(); assert_transfer_event( &emitted_events[0], None, Some(AccountId::from([0x01; 32])), 100, ); let accounts = ink::env::test::default_accounts::(); // Alice owns all the tokens on contract instantiation assert_eq!(erc20.balance_of(accounts.alice), 100); // Bob does not owns tokens assert_eq!(erc20.balance_of(accounts.bob), 0); } #[ink::test] fn transfer_works() { // Constructor works. let mut erc20 = Erc20::new(100); // Transfer event triggered during initial construction. let accounts = ink::env::test::default_accounts::(); assert_eq!(erc20.balance_of(accounts.bob), 0); // Alice transfers 10 tokens to Bob. assert_eq!(erc20.transfer(accounts.bob, 10), Ok(())); // Bob owns 10 tokens. assert_eq!(erc20.balance_of(accounts.bob), 10); let emitted_events = ink::env::test::recorded_events().collect::>(); assert_eq!(emitted_events.len(), 2); // Check first transfer event related to ERC-20 instantiation. assert_transfer_event( &emitted_events[0], None, Some(AccountId::from([0x01; 32])), 100, ); // Check the second transfer event relating to the actual trasfer. assert_transfer_event( &emitted_events[1], Some(AccountId::from([0x01; 32])), Some(AccountId::from([0x02; 32])), 10, ); } #[ink::test] fn invalid_transfer_should_fail() { // Constructor works. let mut erc20 = Erc20::new(100); let accounts = ink::env::test::default_accounts::(); assert_eq!(erc20.balance_of(accounts.bob), 0); // Set the contract as callee and Bob as caller. let contract = ink::env::account_id::(); ink::env::test::set_callee::(contract); ink::env::test::set_caller::(accounts.bob); // Bob fails to transfers 10 tokens to Eve. assert_eq!( erc20.transfer(accounts.eve, 10), Err(Error::InsufficientBalance) ); // Alice owns all the tokens. assert_eq!(erc20.balance_of(accounts.alice), 100); assert_eq!(erc20.balance_of(accounts.bob), 0); assert_eq!(erc20.balance_of(accounts.eve), 0); // Transfer event triggered during initial construction. let emitted_events = ink::env::test::recorded_events().collect::>(); assert_eq!(emitted_events.len(), 1); assert_transfer_event( &emitted_events[0], None, Some(AccountId::from([0x01; 32])), 100, ); } #[ink::test] fn transfer_from_works() { // Constructor works. let mut erc20 = Erc20::new(100); // Transfer event triggered during initial construction. let accounts = ink::env::test::default_accounts::(); // Bob fails to transfer tokens owned by Alice. assert_eq!( erc20.transfer_from(accounts.alice, accounts.eve, 10), Err(Error::InsufficientAllowance) ); // Alice approves Bob for token transfers on her behalf. assert_eq!(erc20.approve(accounts.bob, 10), Ok(())); // The approve event takes place. assert_eq!(ink::env::test::recorded_events().count(), 2); // Set the contract as callee and Bob as caller. let contract = ink::env::account_id::(); ink::env::test::set_callee::(contract); ink::env::test::set_caller::(accounts.bob); // Bob transfers tokens from Alice to Eve. assert_eq!( erc20.transfer_from(accounts.alice, accounts.eve, 10), Ok(()) ); // Eve owns tokens. assert_eq!(erc20.balance_of(accounts.eve), 10); // Check all transfer events that happened during the previous calls: let emitted_events = ink::env::test::recorded_events().collect::>(); assert_eq!(emitted_events.len(), 3); assert_transfer_event( &emitted_events[0], None, Some(AccountId::from([0x01; 32])), 100, ); // The second event `emitted_events[1]` is an Approve event that we skip checking. assert_transfer_event( &emitted_events[2], Some(AccountId::from([0x01; 32])), Some(AccountId::from([0x05; 32])), 10, ); } #[ink::test] fn allowance_must_not_change_on_failed_transfer() { let mut erc20 = Erc20::new(100); let accounts = ink::env::test::default_accounts::(); // Alice approves Bob for token transfers on her behalf. let alice_balance = erc20.balance_of(accounts.alice); let initial_allowance = alice_balance + 2; assert_eq!(erc20.approve(accounts.bob, initial_allowance), Ok(())); // Get contract address. let callee = ink::env::account_id::(); ink::env::test::set_callee::(callee); ink::env::test::set_caller::(accounts.bob); // Bob tries to transfer tokens from Alice to Eve. let emitted_events_before = ink::env::test::recorded_events().count(); assert_eq!( erc20.transfer_from(accounts.alice, accounts.eve, alice_balance + 1), Err(Error::InsufficientBalance) ); // Allowance must have stayed the same assert_eq!( erc20.allowance(accounts.alice, accounts.bob), initial_allowance ); // No more events must have been emitted assert_eq!( emitted_events_before, ink::env::test::recorded_events().count() ) } /// For calculating the event topic hash. struct PrefixedValue<'a, 'b, T> { pub prefix: &'a [u8], pub value: &'b T, } impl scale::Encode for PrefixedValue<'_, '_, X> where X: scale::Encode, { #[inline] fn size_hint(&self) -> usize { self.prefix.size_hint() + self.value.size_hint() } #[inline] fn encode_to(&self, dest: &mut T) { self.prefix.encode_to(dest); self.value.encode_to(dest); } } fn encoded_into_hash(entity: &T) -> Hash where T: scale::Encode, { use ink::{ env::hash::{ Blake2x256, CryptoHash, HashOutput, }, primitives::Clear, }; let mut result = Hash::clear(); let len_result = result.as_ref().len(); let encoded = entity.encode(); let len_encoded = encoded.len(); if len_encoded <= len_result { result.as_mut()[..len_encoded].copy_from_slice(&encoded); return result } let mut hash_output = <::Type as Default>::default(); ::hash(&encoded, &mut hash_output); let copy_len = core::cmp::min(hash_output.len(), len_result); result.as_mut()[0..copy_len].copy_from_slice(&hash_output[0..copy_len]); result } } }