use neo_devpack::neo_storage;
use neo_devpack::prelude::*;
use neo_devpack::serde::{Deserialize, Serialize};
#[neo_event]
pub struct TransferEvent {
pub from: NeoByteString,
pub to: NeoByteString,
pub amount: NeoInteger,
}
#[neo_contract]
pub struct TokenContract {
name: NeoString,
symbol: NeoString,
decimals: NeoInteger,
total_supply: NeoInteger,
}
#[derive(Default, Serialize, Deserialize)]
#[neo_storage]
pub struct TokenStorage {
balances: NeoMap<NeoByteString, NeoInteger>,
allowances: NeoMap<NeoByteString, NeoMap<NeoByteString, NeoInteger>>,
}
impl TokenStorage {
}
impl TokenContract {
pub fn new(
name: NeoString,
symbol: NeoString,
decimals: NeoInteger,
total_supply: NeoInteger,
) -> Self {
Self {
name,
symbol,
decimals,
total_supply,
}
}
#[neo_method]
pub fn name(&self) -> NeoResult<NeoString> {
Ok(self.name.clone())
}
#[neo_method]
pub fn symbol(&self) -> NeoResult<NeoString> {
Ok(self.symbol.clone())
}
#[neo_method]
pub fn decimals(&self) -> NeoResult<NeoInteger> {
Ok(self.decimals.clone())
}
#[neo_method]
pub fn total_supply(&self) -> NeoResult<NeoInteger> {
Ok(self.total_supply.clone())
}
#[neo_method]
pub fn balance_of(&self, account: &NeoByteString) -> NeoResult<NeoInteger> {
let storage = TokenStorage::load(&NeoRuntime::get_storage_context()?)?;
Ok(storage
.balances
.get(account)
.cloned()
.unwrap_or(NeoInteger::zero()))
}
#[neo_method]
pub fn transfer(&mut self, to: &NeoByteString, amount: NeoInteger) -> NeoResult<NeoBoolean> {
if amount <= NeoInteger::zero() {
return Ok(NeoBoolean::FALSE);
}
let from = NeoRuntime::get_calling_script_hash()?;
let balance = self.balance_of(&from)?;
if balance < amount {
return Ok(NeoBoolean::FALSE);
}
let mut storage = TokenStorage::load(&NeoRuntime::get_storage_context()?)?;
let new_from_balance = balance - amount.clone();
storage.balances.insert(from.clone(), new_from_balance);
let to_balance = storage
.balances
.get(to)
.cloned()
.unwrap_or(NeoInteger::zero());
let new_to_balance = to_balance + amount.clone();
storage.balances.insert(to.clone(), new_to_balance);
storage.save(&NeoRuntime::get_storage_context()?)?;
let event = TransferEvent {
from,
to: to.clone(),
amount: amount.clone(),
};
event.emit()?;
Ok(NeoBoolean::TRUE)
}
#[neo_method]
pub fn approve(
&mut self,
spender: &NeoByteString,
amount: NeoInteger,
) -> NeoResult<NeoBoolean> {
if amount < NeoInteger::zero() {
return Ok(NeoBoolean::FALSE);
}
let owner = NeoRuntime::get_calling_script_hash()?;
let mut storage = TokenStorage::load(&NeoRuntime::get_storage_context()?)?;
let mut owner_allowances = storage
.allowances
.get(&owner)
.cloned()
.unwrap_or(NeoMap::new());
owner_allowances.insert(spender.clone(), amount.clone());
storage.allowances.insert(owner, owner_allowances);
storage.save(&NeoRuntime::get_storage_context()?)?;
Ok(NeoBoolean::TRUE)
}
#[neo_method]
pub fn allowance(
&self,
owner: &NeoByteString,
spender: &NeoByteString,
) -> NeoResult<NeoInteger> {
let storage = TokenStorage::load(&NeoRuntime::get_storage_context()?)?;
if let Some(owner_allowances) = storage.allowances.get(owner) {
Ok(owner_allowances
.get(spender)
.cloned()
.unwrap_or(NeoInteger::zero()))
} else {
Ok(NeoInteger::zero())
}
}
#[neo_method]
pub fn transfer_from(
&mut self,
from: &NeoByteString,
to: &NeoByteString,
amount: NeoInteger,
) -> NeoResult<NeoBoolean> {
if amount <= NeoInteger::zero() {
return Ok(NeoBoolean::FALSE);
}
let spender = NeoRuntime::get_calling_script_hash()?;
let allowance = self.allowance(from, &spender)?;
if allowance < amount {
return Ok(NeoBoolean::FALSE);
}
let balance = self.balance_of(from)?;
if balance < amount {
return Ok(NeoBoolean::FALSE);
}
let mut storage = TokenStorage::load(&NeoRuntime::get_storage_context()?)?;
let new_from_balance = balance - amount.clone();
storage.balances.insert(from.clone(), new_from_balance);
let to_balance = storage
.balances
.get(to)
.cloned()
.unwrap_or(NeoInteger::zero());
let new_to_balance = to_balance + amount.clone();
storage.balances.insert(to.clone(), new_to_balance);
let mut owner_allowances = storage
.allowances
.get(from)
.cloned()
.unwrap_or(NeoMap::new());
let new_allowance = allowance - amount.clone();
owner_allowances.insert(spender, new_allowance);
storage.allowances.insert(from.clone(), owner_allowances);
storage.save(&NeoRuntime::get_storage_context()?)?;
let event = TransferEvent {
from: from.clone(),
to: to.clone(),
amount: amount.clone(),
};
event.emit()?;
Ok(NeoBoolean::TRUE)
}
}
pub fn deploy_contract() -> NeoResult<()> {
let total_supply = NeoInteger::new(1000000); let _contract = TokenContract::new(
NeoString::from_str("Neo Token"),
NeoString::from_str("NEO"),
NeoInteger::new(8), total_supply.clone(),
);
let deployer = NeoRuntime::get_calling_script_hash()?;
let mut storage = TokenStorage::load(&NeoRuntime::get_storage_context()?)?;
storage.balances.insert(deployer, total_supply.clone());
storage.save(&NeoRuntime::get_storage_context()?)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_token_creation() {
let contract = TokenContract::new(
NeoString::from_str("Test Token"),
NeoString::from_str("TEST"),
NeoInteger::new(8),
NeoInteger::new(1000000),
);
assert_eq!(contract.name().unwrap().as_str(), "Test Token");
assert_eq!(contract.symbol().unwrap().as_str(), "TEST");
assert_eq!(contract.decimals().unwrap().as_i32_saturating(), 8);
assert_eq!(
contract.total_supply().unwrap().as_i32_saturating(),
1000000
);
}
#[test]
fn test_token_operations() {
let mut contract = TokenContract::new(
NeoString::from_str("Test Token"),
NeoString::from_str("TEST"),
NeoInteger::new(8),
NeoInteger::new(1000000),
);
let account1 = NeoByteString::from_slice(b"account1");
let account2 = NeoByteString::from_slice(b"account2");
assert_eq!(
contract.balance_of(&account1).unwrap().as_i32_saturating(),
0
);
let transfer_result = contract.transfer(&account2, NeoInteger::new(100));
assert_eq!(transfer_result.unwrap().as_bool(), false);
}
}
pub fn main() -> NeoResult<()> {
let contract = TokenContract::new(
NeoString::from_str("MyToken"),
NeoString::from_str("MTK"),
NeoInteger::new(8),
NeoInteger::new(1000000),
);
let account = NeoByteString::from_slice(b"account1");
let balance = contract.balance_of(&account)?;
let event = NeoString::from_str("TokenInitialized");
let state = NeoArray::from_vec(vec![
NeoValue::String(NeoString::from_str("Token contract initialized")),
NeoValue::Integer(balance),
]);
NeoRuntime::notify(&event, &state)?;
Ok(())
}