use self::events::{Deposit, Withdrawal};
use crate::erc20::Erc20;
use odra::{
contract_env,
types::{event::OdraEvent, Address, U256},
UnwrapOrRevert
};
#[odra::module(events = [Deposit, Withdrawal])]
pub struct WrappedNativeToken {
erc20: Erc20
}
#[odra::module]
impl WrappedNativeToken {
#[odra(init)]
pub fn init(&mut self) {
let metadata = contract_env::native_token_metadata();
let symbol = format!("W{}", metadata.symbol);
let name = format!("Wrapped {}", metadata.name);
self.erc20.init(symbol, name, metadata.decimals, &None);
}
#[odra(payable)]
pub fn deposit(&mut self) {
let caller = contract_env::caller();
let amount = contract_env::attached_value().to_u256().unwrap_or_revert();
self.erc20.mint(&caller, &amount);
Deposit {
account: caller,
value: amount
}
.emit();
}
pub fn withdraw(&mut self, amount: &U256) {
let caller = contract_env::caller();
self.erc20.burn(&caller, amount);
let balance = amount.to_balance().unwrap_or_revert();
contract_env::transfer_tokens(&caller, balance);
Withdrawal {
account: caller,
value: *amount
}
.emit()
}
pub fn allowance(&self, owner: &Address, spender: &Address) -> U256 {
self.erc20.allowance(owner, spender)
}
pub fn balance_of(&self, address: &Address) -> U256 {
self.erc20.balance_of(address)
}
pub fn total_supply(&self) -> U256 {
self.erc20.total_supply()
}
pub fn decimals(&self) -> u8 {
self.erc20.decimals()
}
pub fn symbol(&self) -> String {
self.erc20.symbol()
}
pub fn name(&self) -> String {
self.erc20.name()
}
pub fn approve(&mut self, spender: &Address, amount: &U256) {
self.erc20.approve(spender, amount)
}
pub fn transfer_from(&mut self, owner: &Address, recipient: &Address, amount: &U256) {
self.erc20.transfer_from(owner, recipient, amount)
}
pub fn transfer(&mut self, recipient: &Address, amount: &U256) {
self.erc20.transfer(recipient, amount)
}
}
pub mod events {
use odra::{
types::{Address, U256},
Event
};
#[derive(Event, Debug, Eq, PartialEq)]
pub struct Deposit {
pub account: Address,
pub value: U256
}
#[derive(Event, Debug, Eq, PartialEq)]
pub struct Withdrawal {
pub account: Address,
pub value: U256
}
}
#[cfg(test)]
mod tests {
use odra::{
assert_events, test_env,
types::{Address, Balance, OdraError, VmError, U256}
};
use crate::{
erc20::events::Transfer,
wrapped_native::{
events::{Deposit, Withdrawal},
WrappedNativeTokenDeployer, WrappedNativeTokenRef
}
};
fn setup() -> (WrappedNativeTokenRef, Address, Balance, Address, Balance) {
let token: WrappedNativeTokenRef = WrappedNativeTokenDeployer::init();
let account_1 = test_env::get_account(0);
let account_1_balance = test_env::token_balance(account_1);
let account_2 = test_env::get_account(1);
let account_2_balance = test_env::token_balance(account_2);
(
token,
account_1,
account_1_balance,
account_2,
account_2_balance
)
}
#[test]
fn test_init() {
let (token, _, _, _, _) = setup();
let metadata = test_env::native_token_metadata();
assert_eq!(token.total_supply(), U256::zero());
assert_eq!(token.name(), format!("Wrapped {}", metadata.name));
assert_eq!(token.symbol(), format!("W{}", metadata.symbol));
assert_eq!(token.decimals(), metadata.decimals);
}
#[test]
fn test_deposit() {
let (token, account, account_balance, _, _) = setup();
let deposit_amount = 1_000u32;
token.with_tokens(deposit_amount).deposit();
let gas_used = test_env::last_call_contract_gas_used();
assert_eq!(
account_balance - gas_used - deposit_amount,
test_env::token_balance(account)
);
assert_eq!(token.balance_of(&account), deposit_amount.into());
assert_events!(
token,
Transfer {
from: None,
to: Some(account),
amount: deposit_amount.into()
},
Deposit {
account,
value: deposit_amount.into()
}
);
}
#[test]
fn test_minting() {
let (token, account_1, _, account_2, _) = setup();
let deposit_amount = 1_000u32;
test_env::set_caller(account_1);
token.with_tokens(deposit_amount).deposit();
test_env::set_caller(account_2);
token.with_tokens(deposit_amount).deposit();
assert_eq!(
token.total_supply(),
(deposit_amount + deposit_amount).into()
);
assert_events!(token, Transfer, Deposit, Transfer, Deposit);
}
#[test]
fn test_deposit_amount_exceeding_account_balance() {
test_env::assert_exception(OdraError::VmError(VmError::BalanceExceeded), || {
let (token, _, balance, _, _) = setup();
token.with_tokens(balance + Balance::one()).deposit();
});
}
#[test]
fn test_withdrawal() {
let (mut token, account, _, _, _) = setup();
let deposit_amount: Balance = 3_000.into();
token.with_tokens(deposit_amount).deposit();
let account_balance = test_env::token_balance(account);
let withdrawal_amount: U256 = 1_000.into();
token.withdraw(&withdrawal_amount);
let gas_used = test_env::last_call_contract_gas_used();
assert_eq!(
account_balance - gas_used + withdrawal_amount.to_balance().unwrap(),
test_env::token_balance(account)
);
assert_eq!(
token.balance_of(&account),
deposit_amount.to_u256().unwrap() - withdrawal_amount
);
assert_events!(
token,
Transfer {
from: Some(account),
to: None,
amount: withdrawal_amount
},
Withdrawal {
account,
value: withdrawal_amount
}
);
}
#[test]
fn test_withdrawal_amount_exceeding_account_deposit() {
test_env::assert_exception(crate::erc20::errors::Error::InsufficientBalance, || {
let (mut token, _, _, _, _) = setup();
token.withdraw(&U256::one());
});
}
}