pwn_w3bridges/ink/lib.rs

533 lines
19 KiB
Rust

#![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<AccountId, Balance>,
/// 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<AccountId>,
#[ink(topic)]
to: Option<AccountId>,
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<T> = core::result::Result<T, Error>;
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 = <Erc20 as ::ink::reflect::ContractEventBase>::Type;
fn assert_transfer_event(
event: &ink::env::test::EmittedEvent,
expected_from: Option<AccountId>,
expected_to: Option<AccountId>,
expected_value: Balance,
) {
let decoded_event = <Event as scale::Decode>::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::<Vec<_>>();
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::<Vec<_>>();
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::<Vec<_>>();
assert_transfer_event(
&emitted_events[0],
None,
Some(AccountId::from([0x01; 32])),
100,
);
let accounts =
ink::env::test::default_accounts::<ink::env::DefaultEnvironment>();
// 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::<ink::env::DefaultEnvironment>();
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::<Vec<_>>();
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::<ink::env::DefaultEnvironment>();
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::DefaultEnvironment>();
ink::env::test::set_callee::<ink::env::DefaultEnvironment>(contract);
ink::env::test::set_caller::<ink::env::DefaultEnvironment>(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::<Vec<_>>();
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::<ink::env::DefaultEnvironment>();
// 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::DefaultEnvironment>();
ink::env::test::set_callee::<ink::env::DefaultEnvironment>(contract);
ink::env::test::set_caller::<ink::env::DefaultEnvironment>(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::<Vec<_>>();
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::<ink::env::DefaultEnvironment>();
// 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::DefaultEnvironment>();
ink::env::test::set_callee::<ink::env::DefaultEnvironment>(callee);
ink::env::test::set_caller::<ink::env::DefaultEnvironment>(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<X> 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<T: scale::Output + ?Sized>(&self, dest: &mut T) {
self.prefix.encode_to(dest);
self.value.encode_to(dest);
}
}
fn encoded_into_hash<T>(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 =
<<Blake2x256 as HashOutput>::Type as Default>::default();
<Blake2x256 as CryptoHash>::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
}
}
}