mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-11-30 09:33:36 +00:00
0fa4c49735
After native contracts are deployed, single persist script is created and executed at the start of every block persisting.
327 lines
8.9 KiB
Go
327 lines
8.9 KiB
Go
package native
|
|
|
|
import (
|
|
"errors"
|
|
"math/big"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
|
"github.com/nspcc-dev/neo-go/pkg/core/interop/runtime"
|
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
|
"github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
|
)
|
|
|
|
// prefixAccount is the standard prefix used to store account data.
|
|
const prefixAccount = 20
|
|
|
|
// makeAccountKey creates a key from account script hash.
|
|
func makeAccountKey(h util.Uint160) []byte {
|
|
k := make([]byte, util.Uint160Size+1)
|
|
k[0] = prefixAccount
|
|
copy(k[1:], h.BytesBE())
|
|
return k
|
|
}
|
|
|
|
// nep5TokenNative represents NEP-5 token contract.
|
|
type nep5TokenNative struct {
|
|
interop.ContractMD
|
|
name string
|
|
symbol string
|
|
decimals int64
|
|
factor int64
|
|
onPersist func(*interop.Context) error
|
|
incBalance func(*interop.Context, util.Uint160, *state.StorageItem, *big.Int) error
|
|
}
|
|
|
|
// totalSupplyKey is the key used to store totalSupply value.
|
|
var totalSupplyKey = []byte{11}
|
|
|
|
func (c *nep5TokenNative) Metadata() *interop.ContractMD {
|
|
return &c.ContractMD
|
|
}
|
|
|
|
var _ interop.Contract = (*nep5TokenNative)(nil)
|
|
|
|
func newNEP5Native(name string) *nep5TokenNative {
|
|
n := &nep5TokenNative{ContractMD: *interop.NewContractMD(name)}
|
|
|
|
desc := newDescriptor("name", smartcontract.StringType)
|
|
md := newMethodAndPrice(n.Name, 1, smartcontract.NoneFlag)
|
|
n.AddMethod(md, desc, true)
|
|
|
|
desc = newDescriptor("symbol", smartcontract.StringType)
|
|
md = newMethodAndPrice(n.Symbol, 1, smartcontract.NoneFlag)
|
|
n.AddMethod(md, desc, true)
|
|
|
|
desc = newDescriptor("decimals", smartcontract.IntegerType)
|
|
md = newMethodAndPrice(n.Decimals, 1, smartcontract.NoneFlag)
|
|
n.AddMethod(md, desc, true)
|
|
|
|
desc = newDescriptor("totalSupply", smartcontract.IntegerType)
|
|
md = newMethodAndPrice(n.TotalSupply, 1, smartcontract.AllowStates)
|
|
n.AddMethod(md, desc, true)
|
|
|
|
desc = newDescriptor("balanceOf", smartcontract.IntegerType,
|
|
manifest.NewParameter("account", smartcontract.Hash160Type))
|
|
md = newMethodAndPrice(n.balanceOf, 1, smartcontract.AllowStates)
|
|
n.AddMethod(md, desc, true)
|
|
|
|
desc = newDescriptor("transfer", smartcontract.BoolType,
|
|
manifest.NewParameter("from", smartcontract.Hash160Type),
|
|
manifest.NewParameter("to", smartcontract.Hash160Type),
|
|
manifest.NewParameter("amount", smartcontract.IntegerType),
|
|
)
|
|
md = newMethodAndPrice(n.Transfer, 1, smartcontract.AllowModifyStates)
|
|
n.AddMethod(md, desc, false)
|
|
|
|
desc = newDescriptor("onPersist", smartcontract.BoolType)
|
|
md = newMethodAndPrice(getOnPersistWrapper(n.OnPersist), 0, smartcontract.AllowModifyStates)
|
|
n.AddMethod(md, desc, false)
|
|
|
|
n.AddEvent("Transfer", desc.Parameters...)
|
|
|
|
return n
|
|
}
|
|
|
|
func (c *nep5TokenNative) Initialize(_ *interop.Context) error {
|
|
return nil
|
|
}
|
|
|
|
func (c *nep5TokenNative) Name(_ *interop.Context, _ []stackitem.Item) stackitem.Item {
|
|
return stackitem.NewByteArray([]byte(c.name))
|
|
}
|
|
|
|
func (c *nep5TokenNative) Symbol(_ *interop.Context, _ []stackitem.Item) stackitem.Item {
|
|
return stackitem.NewByteArray([]byte(c.symbol))
|
|
}
|
|
|
|
func (c *nep5TokenNative) Decimals(_ *interop.Context, _ []stackitem.Item) stackitem.Item {
|
|
return stackitem.NewBigInteger(big.NewInt(c.decimals))
|
|
}
|
|
|
|
func (c *nep5TokenNative) TotalSupply(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
|
return stackitem.NewBigInteger(c.getTotalSupply(ic))
|
|
}
|
|
|
|
func (c *nep5TokenNative) getTotalSupply(ic *interop.Context) *big.Int {
|
|
si := ic.DAO.GetStorageItem(c.Hash, totalSupplyKey)
|
|
if si == nil {
|
|
return big.NewInt(0)
|
|
}
|
|
return bigint.FromBytes(si.Value)
|
|
}
|
|
|
|
func (c *nep5TokenNative) saveTotalSupply(ic *interop.Context, supply *big.Int) error {
|
|
si := &state.StorageItem{Value: bigint.ToBytes(supply)}
|
|
return ic.DAO.PutStorageItem(c.Hash, totalSupplyKey, si)
|
|
}
|
|
|
|
func (c *nep5TokenNative) Transfer(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
from := toUint160(args[0])
|
|
to := toUint160(args[1])
|
|
amount := toBigInt(args[2])
|
|
err := c.transfer(ic, from, to, amount)
|
|
return stackitem.NewBool(err == nil)
|
|
}
|
|
|
|
func addrToStackItem(u *util.Uint160) stackitem.Item {
|
|
if u == nil {
|
|
return stackitem.Null{}
|
|
}
|
|
return stackitem.NewByteArray(u.BytesBE())
|
|
}
|
|
|
|
func (c *nep5TokenNative) emitTransfer(ic *interop.Context, from, to *util.Uint160, amount *big.Int) {
|
|
ne := state.NotificationEvent{
|
|
ScriptHash: c.Hash,
|
|
Item: stackitem.NewArray([]stackitem.Item{
|
|
stackitem.NewByteArray([]byte("Transfer")),
|
|
addrToStackItem(from),
|
|
addrToStackItem(to),
|
|
stackitem.NewBigInteger(amount),
|
|
}),
|
|
}
|
|
ic.Notifications = append(ic.Notifications, ne)
|
|
}
|
|
|
|
func (c *nep5TokenNative) transfer(ic *interop.Context, from, to util.Uint160, amount *big.Int) error {
|
|
if amount.Sign() == -1 {
|
|
return errors.New("negative amount")
|
|
}
|
|
|
|
ok, err := runtime.CheckHashedWitness(ic, nep5ScriptHash{
|
|
callingScriptHash: c.Hash,
|
|
entryScriptHash: c.Hash,
|
|
currentScriptHash: c.Hash,
|
|
}, from)
|
|
if err != nil {
|
|
return err
|
|
} else if !ok {
|
|
return errors.New("invalid signature")
|
|
}
|
|
|
|
keyFrom := makeAccountKey(from)
|
|
siFrom := ic.DAO.GetStorageItem(c.Hash, keyFrom)
|
|
if siFrom == nil {
|
|
return errors.New("insufficient funds")
|
|
}
|
|
|
|
isEmpty := from.Equals(to) || amount.Sign() == 0
|
|
inc := amount
|
|
if isEmpty {
|
|
inc = big.NewInt(0)
|
|
} else {
|
|
inc = new(big.Int).Neg(inc)
|
|
}
|
|
if err := c.incBalance(ic, from, siFrom, inc); err != nil {
|
|
return err
|
|
}
|
|
if err := ic.DAO.PutStorageItem(c.Hash, keyFrom, siFrom); err != nil {
|
|
return err
|
|
}
|
|
|
|
if !isEmpty {
|
|
keyTo := makeAccountKey(to)
|
|
siTo := ic.DAO.GetStorageItem(c.Hash, keyTo)
|
|
if siTo == nil {
|
|
siTo = new(state.StorageItem)
|
|
}
|
|
if err := c.incBalance(ic, to, siTo, amount); err != nil {
|
|
return err
|
|
}
|
|
if err := ic.DAO.PutStorageItem(c.Hash, keyTo, siTo); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
c.emitTransfer(ic, &from, &to, amount)
|
|
return nil
|
|
}
|
|
|
|
func (c *nep5TokenNative) balanceOf(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
|
h := toUint160(args[0])
|
|
bs, err := ic.DAO.GetNEP5Balances(h)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
balance := bs.Trackers[c.Hash].Balance
|
|
return stackitem.NewBigInteger(big.NewInt(balance))
|
|
}
|
|
|
|
func (c *nep5TokenNative) mint(ic *interop.Context, h util.Uint160, amount *big.Int) {
|
|
if amount.Sign() == 0 {
|
|
return
|
|
}
|
|
c.addTokens(ic, h, amount)
|
|
c.emitTransfer(ic, nil, &h, amount)
|
|
}
|
|
|
|
func (c *nep5TokenNative) burn(ic *interop.Context, h util.Uint160, amount *big.Int) {
|
|
if amount.Sign() == 0 {
|
|
return
|
|
}
|
|
amount = new(big.Int).Neg(amount)
|
|
c.addTokens(ic, h, amount)
|
|
c.emitTransfer(ic, &h, nil, amount)
|
|
}
|
|
|
|
func (c *nep5TokenNative) addTokens(ic *interop.Context, h util.Uint160, amount *big.Int) {
|
|
if amount.Sign() == 0 {
|
|
return
|
|
}
|
|
|
|
key := makeAccountKey(h)
|
|
si := ic.DAO.GetStorageItem(c.Hash, key)
|
|
if si == nil {
|
|
si = new(state.StorageItem)
|
|
}
|
|
if err := c.incBalance(ic, h, si, amount); err != nil {
|
|
panic(err)
|
|
}
|
|
if err := ic.DAO.PutStorageItem(c.Hash, key, si); err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
supply := c.getTotalSupply(ic)
|
|
supply.Add(supply, amount)
|
|
err := c.saveTotalSupply(ic, supply)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
func (c *nep5TokenNative) OnPersist(ic *interop.Context) error {
|
|
if ic.Trigger != trigger.System {
|
|
return errors.New("onPersist should be triggerred by system")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func newDescriptor(name string, ret smartcontract.ParamType, ps ...manifest.Parameter) *manifest.Method {
|
|
return &manifest.Method{
|
|
Name: name,
|
|
Parameters: ps,
|
|
ReturnType: ret,
|
|
}
|
|
}
|
|
|
|
func newMethodAndPrice(f interop.Method, price int64, flags smartcontract.CallFlag) *interop.MethodAndPrice {
|
|
return &interop.MethodAndPrice{
|
|
Func: f,
|
|
Price: price,
|
|
RequiredFlags: flags,
|
|
}
|
|
}
|
|
|
|
func toBigInt(s stackitem.Item) *big.Int {
|
|
bi, err := s.TryInteger()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return bi
|
|
}
|
|
|
|
func toUint160(s stackitem.Item) util.Uint160 {
|
|
buf, err := s.TryBytes()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
u, err := util.Uint160DecodeBytesBE(buf)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return u
|
|
}
|
|
|
|
// scriptHash is an auxiliary structure which implements ScriptHashGetter
|
|
// interface over NEP5 native contract and is used for runtime.CheckHashedWitness
|
|
type nep5ScriptHash struct {
|
|
callingScriptHash util.Uint160
|
|
entryScriptHash util.Uint160
|
|
currentScriptHash util.Uint160
|
|
}
|
|
|
|
// GetCallingScriptHash implements ScriptHashGetter interface
|
|
func (s nep5ScriptHash) GetCallingScriptHash() util.Uint160 {
|
|
return s.callingScriptHash
|
|
}
|
|
|
|
// GetEntryScriptHash implements ScriptHashGetter interface
|
|
func (s nep5ScriptHash) GetEntryScriptHash() util.Uint160 {
|
|
return s.entryScriptHash
|
|
}
|
|
|
|
// GetCurrentScriptHash implements ScriptHashGetter interface
|
|
func (s nep5ScriptHash) GetCurrentScriptHash() util.Uint160 {
|
|
return s.currentScriptHash
|
|
}
|
|
|
|
func getOnPersistWrapper(f func(ic *interop.Context) error) interop.Method {
|
|
return func(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
|
return stackitem.NewBool(f(ic) == nil)
|
|
}
|
|
}
|