using System.Numerics;
using Neo;
using Neo.SmartContract.Framework;
using Neo.SmartContract.Framework.Attributes;
using Neo.SmartContract.Framework.Services;
using Neo.Sol.Runtime;
namespace Neo.Sol.Examples;
[DisplayName("ERC20Token")]
[ManifestExtra("Description", "ERC20-compatible token using EVM runtime")]
[ManifestExtra("Author", "Neo Project")]
[ContractPermission("*", "*")]
public class ERC20Token : SmartContract
{
private static readonly EvmRuntime runtime = Evm.CreateRuntime();
private static readonly BigInteger TOTAL_SUPPLY_SLOT = 0;
private static readonly BigInteger BALANCES_SLOT = 1; private static readonly BigInteger ALLOWANCES_SLOT = 2;
private const string NAME = "Neo DevPack for Solidity Token";
private const string SYMBOL = "NST";
private const byte DECIMALS = 18;
[DisplayName("_deploy")]
public static void Deploy(object data, bool update)
{
if (update) return;
var deployData = (object[])data;
var owner = (UInt160)deployData[0];
var initialSupply = (BigInteger)deployData[1];
runtime.Require(owner.IsValid && !owner.IsZero, "Invalid owner address");
runtime.Require(initialSupply > 0, "Initial supply must be positive");
runtime.Storage.Store(TOTAL_SUPPLY_SLOT, initialSupply);
var balanceSlot = StorageManager.CalculateMappingElementSlot(BALANCES_SLOT, owner.ToArray());
runtime.Storage.Store(balanceSlot, initialSupply);
var contractInfo = new ContractInfo
{
Name = NAME,
Version = "1.0.0",
Description = "ERC20-compatible token",
Owner = owner,
IsActive = true,
CreatedAt = runtime.Now
};
runtime.Registry.RegisterContract(runtime.ContractAddress, contractInfo);
StandardEvents.EmitTransfer(runtime.Events, UInt160.Zero, owner, initialSupply);
}
[DisplayName("name")]
[Safe]
public static string Name() => NAME;
[DisplayName("symbol")]
[Safe]
public static string Symbol() => SYMBOL;
[DisplayName("decimals")]
[Safe]
public static byte Decimals() => DECIMALS;
[DisplayName("totalSupply")]
[Safe]
public static BigInteger TotalSupply()
{
return runtime.Storage.LoadBigInteger(TOTAL_SUPPLY_SLOT);
}
[DisplayName("balanceOf")]
[Safe]
public static BigInteger BalanceOf(UInt160 account)
{
runtime.Require(account.IsValid, "Invalid account address");
var balanceSlot = StorageManager.CalculateMappingElementSlot(BALANCES_SLOT, account.ToArray());
return runtime.Storage.LoadBigInteger(balanceSlot);
}
[DisplayName("transfer")]
public static bool Transfer(UInt160 to, BigInteger amount)
{
var from = runtime.Msg.Sender;
return TransferInternal(from, to, amount);
}
[DisplayName("transferFrom")]
public static bool TransferFrom(UInt160 from, UInt160 to, BigInteger amount)
{
var spender = runtime.Msg.Sender;
var allowance = AllowanceInternal(from, spender);
runtime.Require(allowance >= amount, "Transfer amount exceeds allowance");
var newAllowance = allowance - amount;
SetAllowanceInternal(from, spender, newAllowance);
StandardEvents.EmitApproval(runtime.Events, from, spender, newAllowance);
return TransferInternal(from, to, amount);
}
[DisplayName("approve")]
public static bool Approve(UInt160 spender, BigInteger amount)
{
var owner = runtime.Msg.Sender;
runtime.Require(owner.IsValid, "Invalid owner address");
runtime.Require(spender.IsValid, "Invalid spender address");
runtime.Require(amount >= 0, "Approval amount cannot be negative");
SetAllowanceInternal(owner, spender, amount);
StandardEvents.EmitApproval(runtime.Events, owner, spender, amount);
return true;
}
[DisplayName("allowance")]
[Safe]
public static BigInteger Allowance(UInt160 owner, UInt160 spender)
{
return AllowanceInternal(owner, spender);
}
[DisplayName("increaseAllowance")]
public static bool IncreaseAllowance(UInt160 spender, BigInteger addedValue)
{
var owner = runtime.Msg.Sender;
var currentAllowance = AllowanceInternal(owner, spender);
var newAllowance = currentAllowance + addedValue;
return Approve(spender, newAllowance);
}
[DisplayName("decreaseAllowance")]
public static bool DecreaseAllowance(UInt160 spender, BigInteger subtractedValue)
{
var owner = runtime.Msg.Sender;
var currentAllowance = AllowanceInternal(owner, spender);
runtime.Require(currentAllowance >= subtractedValue, "Decreased allowance below zero");
var newAllowance = currentAllowance - subtractedValue;
return Approve(spender, newAllowance);
}
[DisplayName("mint")]
public static bool Mint(UInt160 to, BigInteger amount)
{
var contractInfo = runtime.Registry.GetContractInfo(runtime.ContractAddress);
runtime.Require(contractInfo != null && contractInfo.Owner.Equals(runtime.Msg.Sender),
"Only owner can mint tokens");
runtime.Require(to.IsValid && !to.IsZero, "Invalid recipient address");
runtime.Require(amount > 0, "Mint amount must be positive");
var totalSupply = TotalSupply();
var newTotalSupply = totalSupply + amount;
runtime.Storage.Store(TOTAL_SUPPLY_SLOT, newTotalSupply);
var balanceSlot = StorageManager.CalculateMappingElementSlot(BALANCES_SLOT, to.ToArray());
var currentBalance = runtime.Storage.LoadBigInteger(balanceSlot);
var newBalance = currentBalance + amount;
runtime.Storage.Store(balanceSlot, newBalance);
StandardEvents.EmitTransfer(runtime.Events, UInt160.Zero, to, amount);
return true;
}
[DisplayName("burn")]
public static bool Burn(BigInteger amount)
{
var from = runtime.Msg.Sender;
runtime.Require(amount > 0, "Burn amount must be positive");
var balanceSlot = StorageManager.CalculateMappingElementSlot(BALANCES_SLOT, from.ToArray());
var currentBalance = runtime.Storage.LoadBigInteger(balanceSlot);
runtime.Require(currentBalance >= amount, "Burn amount exceeds balance");
var newBalance = currentBalance - amount;
runtime.Storage.Store(balanceSlot, newBalance);
var totalSupply = TotalSupply();
var newTotalSupply = totalSupply - amount;
runtime.Storage.Store(TOTAL_SUPPLY_SLOT, newTotalSupply);
StandardEvents.EmitTransfer(runtime.Events, from, UInt160.Zero, amount);
return true;
}
private static bool TransferInternal(UInt160 from, UInt160 to, BigInteger amount)
{
runtime.Require(from.IsValid, "Invalid from address");
runtime.Require(to.IsValid && !to.IsZero, "Invalid to address");
runtime.Require(amount > 0, "Transfer amount must be positive");
var fromBalanceSlot = StorageManager.CalculateMappingElementSlot(BALANCES_SLOT, from.ToArray());
var toBalanceSlot = StorageManager.CalculateMappingElementSlot(BALANCES_SLOT, to.ToArray());
var fromBalance = runtime.Storage.LoadBigInteger(fromBalanceSlot);
runtime.Require(fromBalance >= amount, "Transfer amount exceeds balance");
var newFromBalance = fromBalance - amount;
runtime.Storage.Store(fromBalanceSlot, newFromBalance);
var toBalance = runtime.Storage.LoadBigInteger(toBalanceSlot);
var newToBalance = toBalance + amount;
runtime.Storage.Store(toBalanceSlot, newToBalance);
StandardEvents.EmitTransfer(runtime.Events, from, to, amount);
return true;
}
private static BigInteger AllowanceInternal(UInt160 owner, UInt160 spender)
{
runtime.Require(owner.IsValid, "Invalid owner address");
runtime.Require(spender.IsValid, "Invalid spender address");
var ownerSlot = StorageManager.CalculateMappingElementSlot(ALLOWANCES_SLOT, owner.ToArray());
var allowanceSlot = StorageManager.CalculateMappingElementSlot(ownerSlot, spender.ToArray());
return runtime.Storage.LoadBigInteger(allowanceSlot);
}
private static void SetAllowanceInternal(UInt160 owner, UInt160 spender, BigInteger amount)
{
var ownerSlot = StorageManager.CalculateMappingElementSlot(ALLOWANCES_SLOT, owner.ToArray());
var allowanceSlot = StorageManager.CalculateMappingElementSlot(ownerSlot, spender.ToArray());
runtime.Storage.Store(allowanceSlot, amount);
}
[DisplayName("getStats")]
[Safe]
public static object GetStats()
{
var stats = runtime.GetStats();
return new object[]
{
stats.MemoryStats.TotalSize,
stats.StorageStats.CachedSlots,
stats.RegistryStats.TotalContracts,
stats.GasUsed
};
}
}