github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/core/native/native_nep17.go (about)

     1  package native
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"math"
     7  	"math/big"
     8  
     9  	"github.com/nspcc-dev/neo-go/pkg/config"
    10  	"github.com/nspcc-dev/neo-go/pkg/core/dao"
    11  	"github.com/nspcc-dev/neo-go/pkg/core/interop"
    12  	"github.com/nspcc-dev/neo-go/pkg/core/interop/contract"
    13  	"github.com/nspcc-dev/neo-go/pkg/core/interop/runtime"
    14  	"github.com/nspcc-dev/neo-go/pkg/core/state"
    15  	"github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
    16  	"github.com/nspcc-dev/neo-go/pkg/smartcontract"
    17  	"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
    18  	"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
    19  	"github.com/nspcc-dev/neo-go/pkg/util"
    20  	"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
    21  )
    22  
    23  // prefixAccount is the standard prefix used to store account data.
    24  const prefixAccount = 20
    25  
    26  // makeAccountKey creates a key from the account script hash.
    27  func makeAccountKey(h util.Uint160) []byte {
    28  	return makeUint160Key(prefixAccount, h)
    29  }
    30  
    31  // nep17TokenNative represents a NEP-17 token contract.
    32  type nep17TokenNative struct {
    33  	interop.ContractMD
    34  	symbol       string
    35  	decimals     int64
    36  	factor       int64
    37  	incBalance   func(*interop.Context, util.Uint160, *state.StorageItem, *big.Int, *big.Int) (func(), error)
    38  	balFromBytes func(item *state.StorageItem) (*big.Int, error)
    39  }
    40  
    41  // totalSupplyKey is the key used to store totalSupply value.
    42  var totalSupplyKey = []byte{11}
    43  
    44  func (c *nep17TokenNative) Metadata() *interop.ContractMD {
    45  	return &c.ContractMD
    46  }
    47  
    48  func newNEP17Native(name string, id int32) *nep17TokenNative {
    49  	n := &nep17TokenNative{ContractMD: *interop.NewContractMD(name, id, func(m *manifest.Manifest) {
    50  		m.SupportedStandards = []string{manifest.NEP17StandardName}
    51  	})}
    52  
    53  	desc := newDescriptor("symbol", smartcontract.StringType)
    54  	md := newMethodAndPrice(n.Symbol, 0, callflag.NoneFlag)
    55  	n.AddMethod(md, desc)
    56  
    57  	desc = newDescriptor("decimals", smartcontract.IntegerType)
    58  	md = newMethodAndPrice(n.Decimals, 0, callflag.NoneFlag)
    59  	n.AddMethod(md, desc)
    60  
    61  	desc = newDescriptor("totalSupply", smartcontract.IntegerType)
    62  	md = newMethodAndPrice(n.TotalSupply, 1<<15, callflag.ReadStates)
    63  	n.AddMethod(md, desc)
    64  
    65  	desc = newDescriptor("balanceOf", smartcontract.IntegerType,
    66  		manifest.NewParameter("account", smartcontract.Hash160Type))
    67  	md = newMethodAndPrice(n.balanceOf, 1<<15, callflag.ReadStates)
    68  	n.AddMethod(md, desc)
    69  
    70  	transferParams := []manifest.Parameter{
    71  		manifest.NewParameter("from", smartcontract.Hash160Type),
    72  		manifest.NewParameter("to", smartcontract.Hash160Type),
    73  		manifest.NewParameter("amount", smartcontract.IntegerType),
    74  	}
    75  	desc = newDescriptor("transfer", smartcontract.BoolType,
    76  		append(transferParams, manifest.NewParameter("data", smartcontract.AnyType))...,
    77  	)
    78  	md = newMethodAndPrice(n.Transfer, 1<<17, callflag.States|callflag.AllowCall|callflag.AllowNotify)
    79  	md.StorageFee = 50
    80  	n.AddMethod(md, desc)
    81  
    82  	eDesc := newEventDescriptor("Transfer", transferParams...)
    83  	eMD := newEvent(eDesc)
    84  	n.AddEvent(eMD)
    85  
    86  	return n
    87  }
    88  
    89  func (c *nep17TokenNative) Initialize(_ *interop.Context) error {
    90  	return nil
    91  }
    92  
    93  func (c *nep17TokenNative) Symbol(_ *interop.Context, _ []stackitem.Item) stackitem.Item {
    94  	return stackitem.NewByteArray([]byte(c.symbol))
    95  }
    96  
    97  func (c *nep17TokenNative) Decimals(_ *interop.Context, _ []stackitem.Item) stackitem.Item {
    98  	return stackitem.NewBigInteger(big.NewInt(c.decimals))
    99  }
   100  
   101  func (c *nep17TokenNative) TotalSupply(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
   102  	_, supply := c.getTotalSupply(ic.DAO)
   103  	return stackitem.NewBigInteger(supply)
   104  }
   105  
   106  func (c *nep17TokenNative) getTotalSupply(d *dao.Simple) (state.StorageItem, *big.Int) {
   107  	si := d.GetStorageItem(c.ID, totalSupplyKey)
   108  	if si == nil {
   109  		si = []byte{}
   110  	}
   111  	return si, bigint.FromBytes(si)
   112  }
   113  
   114  func (c *nep17TokenNative) saveTotalSupply(d *dao.Simple, si state.StorageItem, supply *big.Int) {
   115  	d.PutBigInt(c.ID, totalSupplyKey, supply)
   116  }
   117  
   118  func (c *nep17TokenNative) Transfer(ic *interop.Context, args []stackitem.Item) stackitem.Item {
   119  	from := toUint160(args[0])
   120  	to := toUint160(args[1])
   121  	amount := toBigInt(args[2])
   122  	err := c.TransferInternal(ic, from, to, amount, args[3])
   123  	return stackitem.NewBool(err == nil)
   124  }
   125  
   126  func addrToStackItem(u *util.Uint160) stackitem.Item {
   127  	if u == nil {
   128  		return stackitem.Null{}
   129  	}
   130  	return stackitem.NewByteArray(u.BytesBE())
   131  }
   132  
   133  func (c *nep17TokenNative) postTransfer(ic *interop.Context, from, to *util.Uint160, amount *big.Int,
   134  	data stackitem.Item, callOnPayment bool, postCalls ...func()) {
   135  	var skipPostCalls bool
   136  	defer func() {
   137  		if skipPostCalls {
   138  			return
   139  		}
   140  		for _, f := range postCalls {
   141  			if f != nil {
   142  				f()
   143  			}
   144  		}
   145  	}()
   146  	c.emitTransfer(ic, from, to, amount)
   147  	if to == nil || !callOnPayment {
   148  		return
   149  	}
   150  	cs, err := ic.GetContract(*to)
   151  	if err != nil {
   152  		return
   153  	}
   154  
   155  	fromArg := stackitem.Item(stackitem.Null{})
   156  	if from != nil {
   157  		fromArg = stackitem.NewByteArray((*from).BytesBE())
   158  	}
   159  	args := []stackitem.Item{
   160  		fromArg,
   161  		stackitem.NewBigInteger(amount),
   162  		data,
   163  	}
   164  	if err := contract.CallFromNative(ic, c.Hash, cs, manifest.MethodOnNEP17Payment, args, false); err != nil {
   165  		skipPostCalls = true
   166  		panic(err)
   167  	}
   168  }
   169  
   170  func (c *nep17TokenNative) emitTransfer(ic *interop.Context, from, to *util.Uint160, amount *big.Int) {
   171  	ic.AddNotification(c.Hash, "Transfer", stackitem.NewArray([]stackitem.Item{
   172  		addrToStackItem(from),
   173  		addrToStackItem(to),
   174  		stackitem.NewBigInteger(amount),
   175  	}))
   176  }
   177  
   178  // updateAccBalance adds the specified amount to the acc's balance. If requiredBalance
   179  // is set and amount is 0, the acc's balance is checked against requiredBalance.
   180  func (c *nep17TokenNative) updateAccBalance(ic *interop.Context, acc util.Uint160, amount *big.Int, requiredBalance *big.Int) (func(), error) {
   181  	key := makeAccountKey(acc)
   182  	si := ic.DAO.GetStorageItem(c.ID, key)
   183  	if si == nil {
   184  		if amount.Sign() < 0 {
   185  			return nil, errors.New("insufficient funds")
   186  		}
   187  		if requiredBalance != nil && requiredBalance.Sign() > 0 {
   188  			return nil, errors.New("insufficient funds")
   189  		}
   190  		if amount.Sign() == 0 {
   191  			// it's OK to transfer 0 if the balance is 0, no need to put si to the storage
   192  			return nil, nil
   193  		}
   194  		si = state.StorageItem{}
   195  	}
   196  
   197  	postF, err := c.incBalance(ic, acc, &si, amount, requiredBalance)
   198  	if err != nil {
   199  		if si != nil && amount.Sign() <= 0 {
   200  			ic.DAO.PutStorageItem(c.ID, key, si)
   201  		}
   202  		return nil, err
   203  	}
   204  	if si == nil {
   205  		ic.DAO.DeleteStorageItem(c.ID, key)
   206  	} else {
   207  		ic.DAO.PutStorageItem(c.ID, key, si)
   208  	}
   209  	return postF, nil
   210  }
   211  
   212  // TransferInternal transfers NEO across accounts.
   213  func (c *nep17TokenNative) TransferInternal(ic *interop.Context, from, to util.Uint160, amount *big.Int, data stackitem.Item) error {
   214  	var postF1, postF2 func()
   215  
   216  	if amount.Sign() == -1 {
   217  		return errors.New("negative amount")
   218  	}
   219  
   220  	caller := ic.VM.GetCallingScriptHash()
   221  	if caller.Equals(util.Uint160{}) || !from.Equals(caller) {
   222  		ok, err := runtime.CheckHashedWitness(ic, from)
   223  		if err != nil {
   224  			return err
   225  		} else if !ok {
   226  			return errors.New("invalid signature")
   227  		}
   228  	}
   229  	isEmpty := from.Equals(to) || amount.Sign() == 0
   230  	inc := amount
   231  	if isEmpty {
   232  		inc = big.NewInt(0)
   233  	} else {
   234  		inc = new(big.Int).Neg(inc)
   235  	}
   236  
   237  	postF1, err := c.updateAccBalance(ic, from, inc, amount)
   238  	if err != nil {
   239  		return err
   240  	}
   241  
   242  	if !isEmpty {
   243  		postF2, err = c.updateAccBalance(ic, to, amount, nil)
   244  		if err != nil {
   245  			return err
   246  		}
   247  	}
   248  
   249  	c.postTransfer(ic, &from, &to, amount, data, true, postF1, postF2)
   250  	return nil
   251  }
   252  
   253  func (c *nep17TokenNative) balanceOf(ic *interop.Context, args []stackitem.Item) stackitem.Item {
   254  	h := toUint160(args[0])
   255  	return stackitem.NewBigInteger(c.balanceOfInternal(ic.DAO, h))
   256  }
   257  
   258  func (c *nep17TokenNative) balanceOfInternal(d *dao.Simple, h util.Uint160) *big.Int {
   259  	key := makeAccountKey(h)
   260  	si := d.GetStorageItem(c.ID, key)
   261  	if si == nil {
   262  		return big.NewInt(0)
   263  	}
   264  	balance, err := c.balFromBytes(&si)
   265  	if err != nil {
   266  		panic(fmt.Errorf("can not deserialize balance state: %w", err))
   267  	}
   268  	return balance
   269  }
   270  
   271  func (c *nep17TokenNative) mint(ic *interop.Context, h util.Uint160, amount *big.Int, callOnPayment bool) {
   272  	if amount.Sign() == 0 {
   273  		return
   274  	}
   275  	postF := c.addTokens(ic, h, amount)
   276  	c.postTransfer(ic, nil, &h, amount, stackitem.Null{}, callOnPayment, postF)
   277  }
   278  
   279  func (c *nep17TokenNative) burn(ic *interop.Context, h util.Uint160, amount *big.Int) {
   280  	if amount.Sign() == 0 {
   281  		return
   282  	}
   283  	amount.Neg(amount)
   284  	postF := c.addTokens(ic, h, amount)
   285  	amount.Neg(amount)
   286  	c.postTransfer(ic, &h, nil, amount, stackitem.Null{}, false, postF)
   287  }
   288  
   289  func (c *nep17TokenNative) addTokens(ic *interop.Context, h util.Uint160, amount *big.Int) func() {
   290  	if amount.Sign() == 0 {
   291  		return nil
   292  	}
   293  
   294  	key := makeAccountKey(h)
   295  	si := ic.DAO.GetStorageItem(c.ID, key)
   296  	if si == nil {
   297  		si = state.StorageItem{}
   298  	}
   299  	postF, err := c.incBalance(ic, h, &si, amount, nil)
   300  	if err != nil {
   301  		panic(err)
   302  	}
   303  	if si == nil {
   304  		ic.DAO.DeleteStorageItem(c.ID, key)
   305  	} else {
   306  		ic.DAO.PutStorageItem(c.ID, key, si)
   307  	}
   308  
   309  	buf, supply := c.getTotalSupply(ic.DAO)
   310  	supply.Add(supply, amount)
   311  	c.saveTotalSupply(ic.DAO, buf, supply)
   312  	return postF
   313  }
   314  
   315  func newDescriptor(name string, ret smartcontract.ParamType, ps ...manifest.Parameter) *manifest.Method {
   316  	if len(ps) == 0 {
   317  		ps = []manifest.Parameter{}
   318  	}
   319  	return &manifest.Method{
   320  		Name:       name,
   321  		Parameters: ps,
   322  		ReturnType: ret,
   323  	}
   324  }
   325  
   326  func newMethodAndPrice(f interop.Method, cpuFee int64, flags callflag.CallFlag, activeFrom ...config.Hardfork) *interop.MethodAndPrice {
   327  	md := &interop.MethodAndPrice{
   328  		HFSpecificMethodAndPrice: interop.HFSpecificMethodAndPrice{
   329  			Func:          f,
   330  			CPUFee:        cpuFee,
   331  			RequiredFlags: flags,
   332  		},
   333  	}
   334  	if len(activeFrom) != 0 {
   335  		md.ActiveFrom = &activeFrom[0]
   336  	}
   337  	return md
   338  }
   339  
   340  func newEventDescriptor(name string, ps ...manifest.Parameter) *manifest.Event {
   341  	if len(ps) == 0 {
   342  		ps = []manifest.Parameter{}
   343  	}
   344  	return &manifest.Event{
   345  		Name:       name,
   346  		Parameters: ps,
   347  	}
   348  }
   349  
   350  func newEvent(desc *manifest.Event, activeFrom ...config.Hardfork) interop.Event {
   351  	md := interop.Event{
   352  		HFSpecificEvent: interop.HFSpecificEvent{
   353  			MD: desc,
   354  		},
   355  	}
   356  	if len(activeFrom) != 0 {
   357  		md.ActiveFrom = &activeFrom[0]
   358  	}
   359  	return md
   360  }
   361  
   362  func toBigInt(s stackitem.Item) *big.Int {
   363  	bi, err := s.TryInteger()
   364  	if err != nil {
   365  		panic(err)
   366  	}
   367  	return bi
   368  }
   369  
   370  func toUint160(s stackitem.Item) util.Uint160 {
   371  	buf, err := s.TryBytes()
   372  	if err != nil {
   373  		panic(err)
   374  	}
   375  	u, err := util.Uint160DecodeBytesBE(buf)
   376  	if err != nil {
   377  		panic(err)
   378  	}
   379  	return u
   380  }
   381  
   382  func toUint64(s stackitem.Item) uint64 {
   383  	bigInt := toBigInt(s)
   384  	if !bigInt.IsUint64() {
   385  		panic("bigint is not a uint64")
   386  	}
   387  	return bigInt.Uint64()
   388  }
   389  
   390  func toUint32(s stackitem.Item) uint32 {
   391  	uint64Value := toUint64(s)
   392  	if uint64Value > math.MaxUint32 {
   393  		panic("bigint does not fit into uint32")
   394  	}
   395  	return uint32(uint64Value)
   396  }
   397  
   398  func toUint8(s stackitem.Item) uint8 {
   399  	uint64Value := toUint64(s)
   400  	if uint64Value > math.MaxUint8 {
   401  		panic("bigint does not fit into uint8")
   402  	}
   403  	return uint8(uint64Value)
   404  }
   405  
   406  func toInt64(s stackitem.Item) int64 {
   407  	bigInt := toBigInt(s)
   408  	if !bigInt.IsInt64() {
   409  		panic("bigint is not an uint64")
   410  	}
   411  	return bigInt.Int64()
   412  }