2020-03-25 10:00:11 +00:00
|
|
|
package native
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
2021-04-01 15:16:43 +00:00
|
|
|
"fmt"
|
2020-11-19 10:00:46 +00:00
|
|
|
"math"
|
2020-03-25 10:00:11 +00:00
|
|
|
"math/big"
|
|
|
|
|
2024-03-27 17:48:14 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/config"
|
2020-08-03 11:31:42 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
2020-03-25 10:00:11 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
2020-11-19 15:01:42 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/interop/contract"
|
2020-05-19 07:19:05 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/interop/runtime"
|
2020-03-25 10:00:11 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
2020-06-04 18:11:27 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
|
2020-03-25 10:00:11 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
2020-12-29 10:45:49 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
2020-03-25 10:00:11 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
2020-06-03 12:55:06 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
2020-03-25 10:00:11 +00:00
|
|
|
)
|
|
|
|
|
2020-04-23 18:28:37 +00:00
|
|
|
// prefixAccount is the standard prefix used to store account data.
|
|
|
|
const prefixAccount = 20
|
|
|
|
|
2022-04-20 18:30:09 +00:00
|
|
|
// makeAccountKey creates a key from the account script hash.
|
2020-04-23 18:28:37 +00:00
|
|
|
func makeAccountKey(h util.Uint160) []byte {
|
2020-12-13 15:26:35 +00:00
|
|
|
return makeUint160Key(prefixAccount, h)
|
2020-04-23 18:28:37 +00:00
|
|
|
}
|
|
|
|
|
2022-04-20 18:30:09 +00:00
|
|
|
// nep17TokenNative represents a NEP-17 token contract.
|
2020-11-19 15:01:42 +00:00
|
|
|
type nep17TokenNative struct {
|
2020-04-22 20:00:18 +00:00
|
|
|
interop.ContractMD
|
2021-04-01 15:16:43 +00:00
|
|
|
symbol string
|
|
|
|
decimals int64
|
|
|
|
factor int64
|
2022-05-13 11:49:41 +00:00
|
|
|
incBalance func(*interop.Context, util.Uint160, *state.StorageItem, *big.Int, *big.Int) (func(), error)
|
2021-04-01 15:16:43 +00:00
|
|
|
balFromBytes func(item *state.StorageItem) (*big.Int, error)
|
2020-03-25 10:00:11 +00:00
|
|
|
}
|
|
|
|
|
2020-04-22 20:00:18 +00:00
|
|
|
// totalSupplyKey is the key used to store totalSupply value.
|
|
|
|
var totalSupplyKey = []byte{11}
|
|
|
|
|
2020-11-19 15:01:42 +00:00
|
|
|
func (c *nep17TokenNative) Metadata() *interop.ContractMD {
|
2020-03-25 10:00:11 +00:00
|
|
|
return &c.ContractMD
|
|
|
|
}
|
|
|
|
|
2021-01-15 21:17:31 +00:00
|
|
|
func newNEP17Native(name string, id int32) *nep17TokenNative {
|
2024-03-27 17:48:14 +00:00
|
|
|
n := &nep17TokenNative{ContractMD: *interop.NewContractMD(name, id, func(m *manifest.Manifest) {
|
|
|
|
m.SupportedStandards = []string{manifest.NEP17StandardName}
|
|
|
|
})}
|
2020-03-25 10:00:11 +00:00
|
|
|
|
2020-11-25 09:36:38 +00:00
|
|
|
desc := newDescriptor("symbol", smartcontract.StringType)
|
2020-12-29 10:45:49 +00:00
|
|
|
md := newMethodAndPrice(n.Symbol, 0, callflag.NoneFlag)
|
2020-12-08 10:27:41 +00:00
|
|
|
n.AddMethod(md, desc)
|
2020-03-25 10:00:11 +00:00
|
|
|
|
|
|
|
desc = newDescriptor("decimals", smartcontract.IntegerType)
|
2020-12-29 10:45:49 +00:00
|
|
|
md = newMethodAndPrice(n.Decimals, 0, callflag.NoneFlag)
|
2020-12-08 10:27:41 +00:00
|
|
|
n.AddMethod(md, desc)
|
2020-03-25 10:00:11 +00:00
|
|
|
|
2020-04-22 20:00:18 +00:00
|
|
|
desc = newDescriptor("totalSupply", smartcontract.IntegerType)
|
2021-03-05 10:30:16 +00:00
|
|
|
md = newMethodAndPrice(n.TotalSupply, 1<<15, callflag.ReadStates)
|
2020-12-08 10:27:41 +00:00
|
|
|
n.AddMethod(md, desc)
|
2020-04-22 20:00:18 +00:00
|
|
|
|
2020-03-25 10:00:11 +00:00
|
|
|
desc = newDescriptor("balanceOf", smartcontract.IntegerType,
|
2021-01-12 15:06:27 +00:00
|
|
|
manifest.NewParameter("account", smartcontract.Hash160Type))
|
2021-03-05 10:30:16 +00:00
|
|
|
md = newMethodAndPrice(n.balanceOf, 1<<15, callflag.ReadStates)
|
2020-12-08 10:27:41 +00:00
|
|
|
n.AddMethod(md, desc)
|
2020-03-25 10:00:11 +00:00
|
|
|
|
2021-01-12 15:06:27 +00:00
|
|
|
transferParams := []manifest.Parameter{
|
|
|
|
manifest.NewParameter("from", smartcontract.Hash160Type),
|
|
|
|
manifest.NewParameter("to", smartcontract.Hash160Type),
|
2020-12-22 14:30:45 +00:00
|
|
|
manifest.NewParameter("amount", smartcontract.IntegerType),
|
2021-01-12 15:06:27 +00:00
|
|
|
}
|
|
|
|
desc = newDescriptor("transfer", smartcontract.BoolType,
|
|
|
|
append(transferParams, manifest.NewParameter("data", smartcontract.AnyType))...,
|
2020-03-25 10:00:11 +00:00
|
|
|
)
|
2021-03-05 10:30:16 +00:00
|
|
|
md = newMethodAndPrice(n.Transfer, 1<<17, callflag.States|callflag.AllowCall|callflag.AllowNotify)
|
2021-03-05 11:53:46 +00:00
|
|
|
md.StorageFee = 50
|
2020-12-08 10:27:41 +00:00
|
|
|
n.AddMethod(md, desc)
|
2020-03-25 10:00:11 +00:00
|
|
|
|
2024-03-27 17:48:14 +00:00
|
|
|
eDesc := newEventDescriptor("Transfer", transferParams...)
|
|
|
|
eMD := newEvent(eDesc)
|
|
|
|
n.AddEvent(eMD)
|
2020-03-25 10:00:11 +00:00
|
|
|
|
|
|
|
return n
|
|
|
|
}
|
|
|
|
|
2020-11-19 15:01:42 +00:00
|
|
|
func (c *nep17TokenNative) Initialize(_ *interop.Context) error {
|
2020-03-25 10:00:11 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-11-19 15:01:42 +00:00
|
|
|
func (c *nep17TokenNative) Symbol(_ *interop.Context, _ []stackitem.Item) stackitem.Item {
|
2020-06-03 12:55:06 +00:00
|
|
|
return stackitem.NewByteArray([]byte(c.symbol))
|
2020-03-25 10:00:11 +00:00
|
|
|
}
|
|
|
|
|
2020-11-19 15:01:42 +00:00
|
|
|
func (c *nep17TokenNative) Decimals(_ *interop.Context, _ []stackitem.Item) stackitem.Item {
|
2020-06-03 12:55:06 +00:00
|
|
|
return stackitem.NewBigInteger(big.NewInt(c.decimals))
|
2020-03-25 10:00:11 +00:00
|
|
|
}
|
|
|
|
|
2020-11-19 15:01:42 +00:00
|
|
|
func (c *nep17TokenNative) TotalSupply(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
2021-08-02 21:19:23 +00:00
|
|
|
_, supply := c.getTotalSupply(ic.DAO)
|
|
|
|
return stackitem.NewBigInteger(supply)
|
2020-04-22 20:00:18 +00:00
|
|
|
}
|
|
|
|
|
2022-02-16 15:04:47 +00:00
|
|
|
func (c *nep17TokenNative) getTotalSupply(d *dao.Simple) (state.StorageItem, *big.Int) {
|
2021-02-09 09:26:25 +00:00
|
|
|
si := d.GetStorageItem(c.ID, totalSupplyKey)
|
2020-04-22 20:00:18 +00:00
|
|
|
if si == nil {
|
2021-08-02 21:19:23 +00:00
|
|
|
si = []byte{}
|
2020-04-22 20:00:18 +00:00
|
|
|
}
|
2021-08-02 21:19:23 +00:00
|
|
|
return si, bigint.FromBytes(si)
|
2020-04-22 20:00:18 +00:00
|
|
|
}
|
|
|
|
|
2022-02-16 15:04:47 +00:00
|
|
|
func (c *nep17TokenNative) saveTotalSupply(d *dao.Simple, si state.StorageItem, supply *big.Int) {
|
2022-05-31 20:10:56 +00:00
|
|
|
d.PutBigInt(c.ID, totalSupplyKey, supply)
|
2020-04-22 20:00:18 +00:00
|
|
|
}
|
|
|
|
|
2020-11-19 15:01:42 +00:00
|
|
|
func (c *nep17TokenNative) Transfer(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
2020-03-25 10:00:11 +00:00
|
|
|
from := toUint160(args[0])
|
|
|
|
to := toUint160(args[1])
|
|
|
|
amount := toBigInt(args[2])
|
2020-11-19 15:01:42 +00:00
|
|
|
err := c.TransferInternal(ic, from, to, amount, args[3])
|
2020-06-03 12:55:06 +00:00
|
|
|
return stackitem.NewBool(err == nil)
|
2020-03-25 10:00:11 +00:00
|
|
|
}
|
|
|
|
|
2020-06-03 12:55:06 +00:00
|
|
|
func addrToStackItem(u *util.Uint160) stackitem.Item {
|
2020-03-25 10:00:11 +00:00
|
|
|
if u == nil {
|
2020-06-03 12:55:06 +00:00
|
|
|
return stackitem.Null{}
|
2020-03-25 10:00:11 +00:00
|
|
|
}
|
2020-06-03 12:55:06 +00:00
|
|
|
return stackitem.NewByteArray(u.BytesBE())
|
2020-03-25 10:00:11 +00:00
|
|
|
}
|
|
|
|
|
2020-11-30 10:05:42 +00:00
|
|
|
func (c *nep17TokenNative) postTransfer(ic *interop.Context, from, to *util.Uint160, amount *big.Int,
|
2022-05-13 11:49:41 +00:00
|
|
|
data stackitem.Item, callOnPayment bool, postCalls ...func()) {
|
|
|
|
var skipPostCalls bool
|
|
|
|
defer func() {
|
|
|
|
if skipPostCalls {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
for _, f := range postCalls {
|
|
|
|
if f != nil {
|
|
|
|
f()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
2020-11-19 15:01:42 +00:00
|
|
|
c.emitTransfer(ic, from, to, amount)
|
2020-11-30 10:05:42 +00:00
|
|
|
if to == nil || !callOnPayment {
|
2020-11-19 15:01:42 +00:00
|
|
|
return
|
|
|
|
}
|
2020-12-13 15:26:35 +00:00
|
|
|
cs, err := ic.GetContract(*to)
|
2020-11-19 15:01:42 +00:00
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
fromArg := stackitem.Item(stackitem.Null{})
|
|
|
|
if from != nil {
|
|
|
|
fromArg = stackitem.NewByteArray((*from).BytesBE())
|
|
|
|
}
|
|
|
|
args := []stackitem.Item{
|
|
|
|
fromArg,
|
|
|
|
stackitem.NewBigInteger(amount),
|
|
|
|
data,
|
|
|
|
}
|
2021-02-05 13:09:51 +00:00
|
|
|
if err := contract.CallFromNative(ic, c.Hash, cs, manifest.MethodOnNEP17Payment, args, false); err != nil {
|
2022-05-13 11:49:41 +00:00
|
|
|
skipPostCalls = true
|
2020-11-19 15:01:42 +00:00
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *nep17TokenNative) emitTransfer(ic *interop.Context, from, to *util.Uint160, amount *big.Int) {
|
2022-05-16 08:19:15 +00:00
|
|
|
ic.AddNotification(c.Hash, "Transfer", stackitem.NewArray([]stackitem.Item{
|
|
|
|
addrToStackItem(from),
|
|
|
|
addrToStackItem(to),
|
|
|
|
stackitem.NewBigInteger(amount),
|
|
|
|
}))
|
2020-03-25 10:00:11 +00:00
|
|
|
}
|
|
|
|
|
2022-04-20 18:30:09 +00:00
|
|
|
// updateAccBalance adds the specified amount to the acc's balance. If requiredBalance
|
|
|
|
// is set and amount is 0, the acc's balance is checked against requiredBalance.
|
2022-05-13 11:49:41 +00:00
|
|
|
func (c *nep17TokenNative) updateAccBalance(ic *interop.Context, acc util.Uint160, amount *big.Int, requiredBalance *big.Int) (func(), error) {
|
2020-06-23 19:20:17 +00:00
|
|
|
key := makeAccountKey(acc)
|
2021-02-09 09:26:25 +00:00
|
|
|
si := ic.DAO.GetStorageItem(c.ID, key)
|
2020-06-23 19:20:17 +00:00
|
|
|
if si == nil {
|
core: allow transfer 0 GAS/NEO with zero balance
This commit fixes states diff at 131795 block of mainnet.
Transaction:
```
NEO-GO-VM > loadbase64 DAAQDBSPsxdYh6cITC3gUKI4oWmYxJs49gwUj7MXWIenCEwt4FCiOKFpmMSbOPYUwB8MCHRyYW5zZmVyDBT1Y+pAvCg9TQ4FxI6jBbPyoHNA70FifVtSOQwAEQwUj7MXWIenCEwt4FCiOKFpmMSbOPYMFL1Mb4Fqp6gHiEwzM6xSc8fLS+RpFMAfDAh0cmFuc2ZlcgwU9WPqQLwoPU0OBcSOowWz8qBzQO9BYn1bUjk=
READY: loaded 176 instructions
NEO-GO-VM 0 > ops
INDEX OPCODE PARAMETER
0 PUSHDATA1 ("") <<
2 PUSH0
3 PUSHDATA1 8fb3175887a7084c2de050a238a16998c49b38f6
25 PUSHDATA1 8fb3175887a7084c2de050a238a16998c49b38f6
47 PUSH4
48 PACK
49 PUSH15
50 PUSHDATA1 7472616e73666572 ("transfer")
60 PUSHDATA1 f563ea40bc283d4d0e05c48ea305b3f2a07340ef // NEO token
82 SYSCALL System.Contract.Call (627d5b52)
87 ASSERT
88 PUSHDATA1 ("")
90 PUSH1
91 PUSHDATA1 8fb3175887a7084c2de050a238a16998c49b38f6
113 PUSHDATA1 bd4c6f816aa7a807884c3333ac5273c7cb4be469
135 PUSH4
136 PACK
137 PUSH15
138 PUSHDATA1 7472616e73666572 ("transfer")
148 PUSHDATA1 f563ea40bc283d4d0e05c48ea305b3f2a07340ef // NEO token
170 SYSCALL System.Contract.Call (627d5b52)
175 ASSERT
```
Go's applog:
```
{
"id" : 1,
"result" : {
"txid" : "0x97d2ccb01467b22c73a2cb95f7af298f3a5bd8c849d7044371898b8efecdaabd",
"executions" : [
{
"exception" : "at instruction 87 (ASSERT): ASSERT failed",
"stack" : [],
"gasconsumed" : "4988995",
"notifications" : [],
"trigger" : "Application",
"vmstate" : "FAULT"
}
]
},
"jsonrpc" : "2.0"
}
```
C#'s applog:
```
{
"jsonrpc" : "2.0",
"result" : {
"executions" : [
{
"stack" : [],
"notifications" : [
{
"contract" : "0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5",
"state" : {
"type" : "Array",
"value" : [
{
"type" : "ByteString",
"value" : "j7MXWIenCEwt4FCiOKFpmMSbOPY="
},
{
"type" : "ByteString",
"value" : "j7MXWIenCEwt4FCiOKFpmMSbOPY="
},
{
"value" : "0",
"type" : "Integer"
}
]
},
"eventname" : "Transfer"
},
{
"contract" : "0xd2a4cff31913016155e38e474a2c06d08be276cf",
"state" : {
"value" : [
{
"type" : "Any"
},
{
"type" : "ByteString",
"value" : "vUxvgWqnqAeITDMzrFJzx8tL5Gk="
},
{
"value" : "2490",
"type" : "Integer"
}
],
"type" : "Array"
},
"eventname" : "Transfer"
},
{
"contract" : "0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5",
"state" : {
"value" : [
{
"value" : "vUxvgWqnqAeITDMzrFJzx8tL5Gk=",
"type" : "ByteString"
},
{
"value" : "j7MXWIenCEwt4FCiOKFpmMSbOPY=",
"type" : "ByteString"
},
{
"value" : "1",
"type" : "Integer"
}
],
"type" : "Array"
},
"eventname" : "Transfer"
}
],
"vmstate" : "HALT",
"gasconsumed" : "9977990",
"trigger" : "Application",
"exception" : null
}
],
"txid" : "0x97d2ccb01467b22c73a2cb95f7af298f3a5bd8c849d7044371898b8efecdaabd"
},
"id" : 1
}
```
2021-09-10 14:18:09 +00:00
|
|
|
if amount.Sign() < 0 {
|
2022-05-13 11:49:41 +00:00
|
|
|
return nil, errors.New("insufficient funds")
|
2020-06-23 19:20:17 +00:00
|
|
|
}
|
2023-11-07 16:37:37 +00:00
|
|
|
if requiredBalance != nil && requiredBalance.Sign() > 0 {
|
|
|
|
return nil, errors.New("insufficient funds")
|
|
|
|
}
|
core: allow transfer 0 GAS/NEO with zero balance
This commit fixes states diff at 131795 block of mainnet.
Transaction:
```
NEO-GO-VM > loadbase64 DAAQDBSPsxdYh6cITC3gUKI4oWmYxJs49gwUj7MXWIenCEwt4FCiOKFpmMSbOPYUwB8MCHRyYW5zZmVyDBT1Y+pAvCg9TQ4FxI6jBbPyoHNA70FifVtSOQwAEQwUj7MXWIenCEwt4FCiOKFpmMSbOPYMFL1Mb4Fqp6gHiEwzM6xSc8fLS+RpFMAfDAh0cmFuc2ZlcgwU9WPqQLwoPU0OBcSOowWz8qBzQO9BYn1bUjk=
READY: loaded 176 instructions
NEO-GO-VM 0 > ops
INDEX OPCODE PARAMETER
0 PUSHDATA1 ("") <<
2 PUSH0
3 PUSHDATA1 8fb3175887a7084c2de050a238a16998c49b38f6
25 PUSHDATA1 8fb3175887a7084c2de050a238a16998c49b38f6
47 PUSH4
48 PACK
49 PUSH15
50 PUSHDATA1 7472616e73666572 ("transfer")
60 PUSHDATA1 f563ea40bc283d4d0e05c48ea305b3f2a07340ef // NEO token
82 SYSCALL System.Contract.Call (627d5b52)
87 ASSERT
88 PUSHDATA1 ("")
90 PUSH1
91 PUSHDATA1 8fb3175887a7084c2de050a238a16998c49b38f6
113 PUSHDATA1 bd4c6f816aa7a807884c3333ac5273c7cb4be469
135 PUSH4
136 PACK
137 PUSH15
138 PUSHDATA1 7472616e73666572 ("transfer")
148 PUSHDATA1 f563ea40bc283d4d0e05c48ea305b3f2a07340ef // NEO token
170 SYSCALL System.Contract.Call (627d5b52)
175 ASSERT
```
Go's applog:
```
{
"id" : 1,
"result" : {
"txid" : "0x97d2ccb01467b22c73a2cb95f7af298f3a5bd8c849d7044371898b8efecdaabd",
"executions" : [
{
"exception" : "at instruction 87 (ASSERT): ASSERT failed",
"stack" : [],
"gasconsumed" : "4988995",
"notifications" : [],
"trigger" : "Application",
"vmstate" : "FAULT"
}
]
},
"jsonrpc" : "2.0"
}
```
C#'s applog:
```
{
"jsonrpc" : "2.0",
"result" : {
"executions" : [
{
"stack" : [],
"notifications" : [
{
"contract" : "0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5",
"state" : {
"type" : "Array",
"value" : [
{
"type" : "ByteString",
"value" : "j7MXWIenCEwt4FCiOKFpmMSbOPY="
},
{
"type" : "ByteString",
"value" : "j7MXWIenCEwt4FCiOKFpmMSbOPY="
},
{
"value" : "0",
"type" : "Integer"
}
]
},
"eventname" : "Transfer"
},
{
"contract" : "0xd2a4cff31913016155e38e474a2c06d08be276cf",
"state" : {
"value" : [
{
"type" : "Any"
},
{
"type" : "ByteString",
"value" : "vUxvgWqnqAeITDMzrFJzx8tL5Gk="
},
{
"value" : "2490",
"type" : "Integer"
}
],
"type" : "Array"
},
"eventname" : "Transfer"
},
{
"contract" : "0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5",
"state" : {
"value" : [
{
"value" : "vUxvgWqnqAeITDMzrFJzx8tL5Gk=",
"type" : "ByteString"
},
{
"value" : "j7MXWIenCEwt4FCiOKFpmMSbOPY=",
"type" : "ByteString"
},
{
"value" : "1",
"type" : "Integer"
}
],
"type" : "Array"
},
"eventname" : "Transfer"
}
],
"vmstate" : "HALT",
"gasconsumed" : "9977990",
"trigger" : "Application",
"exception" : null
}
],
"txid" : "0x97d2ccb01467b22c73a2cb95f7af298f3a5bd8c849d7044371898b8efecdaabd"
},
"id" : 1
}
```
2021-09-10 14:18:09 +00:00
|
|
|
if amount.Sign() == 0 {
|
2022-04-20 18:30:09 +00:00
|
|
|
// it's OK to transfer 0 if the balance is 0, no need to put si to the storage
|
2022-05-13 11:49:41 +00:00
|
|
|
return nil, nil
|
core: allow transfer 0 GAS/NEO with zero balance
This commit fixes states diff at 131795 block of mainnet.
Transaction:
```
NEO-GO-VM > loadbase64 DAAQDBSPsxdYh6cITC3gUKI4oWmYxJs49gwUj7MXWIenCEwt4FCiOKFpmMSbOPYUwB8MCHRyYW5zZmVyDBT1Y+pAvCg9TQ4FxI6jBbPyoHNA70FifVtSOQwAEQwUj7MXWIenCEwt4FCiOKFpmMSbOPYMFL1Mb4Fqp6gHiEwzM6xSc8fLS+RpFMAfDAh0cmFuc2ZlcgwU9WPqQLwoPU0OBcSOowWz8qBzQO9BYn1bUjk=
READY: loaded 176 instructions
NEO-GO-VM 0 > ops
INDEX OPCODE PARAMETER
0 PUSHDATA1 ("") <<
2 PUSH0
3 PUSHDATA1 8fb3175887a7084c2de050a238a16998c49b38f6
25 PUSHDATA1 8fb3175887a7084c2de050a238a16998c49b38f6
47 PUSH4
48 PACK
49 PUSH15
50 PUSHDATA1 7472616e73666572 ("transfer")
60 PUSHDATA1 f563ea40bc283d4d0e05c48ea305b3f2a07340ef // NEO token
82 SYSCALL System.Contract.Call (627d5b52)
87 ASSERT
88 PUSHDATA1 ("")
90 PUSH1
91 PUSHDATA1 8fb3175887a7084c2de050a238a16998c49b38f6
113 PUSHDATA1 bd4c6f816aa7a807884c3333ac5273c7cb4be469
135 PUSH4
136 PACK
137 PUSH15
138 PUSHDATA1 7472616e73666572 ("transfer")
148 PUSHDATA1 f563ea40bc283d4d0e05c48ea305b3f2a07340ef // NEO token
170 SYSCALL System.Contract.Call (627d5b52)
175 ASSERT
```
Go's applog:
```
{
"id" : 1,
"result" : {
"txid" : "0x97d2ccb01467b22c73a2cb95f7af298f3a5bd8c849d7044371898b8efecdaabd",
"executions" : [
{
"exception" : "at instruction 87 (ASSERT): ASSERT failed",
"stack" : [],
"gasconsumed" : "4988995",
"notifications" : [],
"trigger" : "Application",
"vmstate" : "FAULT"
}
]
},
"jsonrpc" : "2.0"
}
```
C#'s applog:
```
{
"jsonrpc" : "2.0",
"result" : {
"executions" : [
{
"stack" : [],
"notifications" : [
{
"contract" : "0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5",
"state" : {
"type" : "Array",
"value" : [
{
"type" : "ByteString",
"value" : "j7MXWIenCEwt4FCiOKFpmMSbOPY="
},
{
"type" : "ByteString",
"value" : "j7MXWIenCEwt4FCiOKFpmMSbOPY="
},
{
"value" : "0",
"type" : "Integer"
}
]
},
"eventname" : "Transfer"
},
{
"contract" : "0xd2a4cff31913016155e38e474a2c06d08be276cf",
"state" : {
"value" : [
{
"type" : "Any"
},
{
"type" : "ByteString",
"value" : "vUxvgWqnqAeITDMzrFJzx8tL5Gk="
},
{
"value" : "2490",
"type" : "Integer"
}
],
"type" : "Array"
},
"eventname" : "Transfer"
},
{
"contract" : "0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5",
"state" : {
"value" : [
{
"value" : "vUxvgWqnqAeITDMzrFJzx8tL5Gk=",
"type" : "ByteString"
},
{
"value" : "j7MXWIenCEwt4FCiOKFpmMSbOPY=",
"type" : "ByteString"
},
{
"value" : "1",
"type" : "Integer"
}
],
"type" : "Array"
},
"eventname" : "Transfer"
}
],
"vmstate" : "HALT",
"gasconsumed" : "9977990",
"trigger" : "Application",
"exception" : null
}
],
"txid" : "0x97d2ccb01467b22c73a2cb95f7af298f3a5bd8c849d7044371898b8efecdaabd"
},
"id" : 1
}
```
2021-09-10 14:18:09 +00:00
|
|
|
}
|
2021-03-05 14:06:54 +00:00
|
|
|
si = state.StorageItem{}
|
2020-06-23 19:20:17 +00:00
|
|
|
}
|
|
|
|
|
2022-05-13 11:49:41 +00:00
|
|
|
postF, err := c.incBalance(ic, acc, &si, amount, requiredBalance)
|
2020-06-23 19:20:17 +00:00
|
|
|
if err != nil {
|
2021-08-02 20:16:12 +00:00
|
|
|
if si != nil && amount.Sign() <= 0 {
|
2022-02-16 14:48:15 +00:00
|
|
|
ic.DAO.PutStorageItem(c.ID, key, si)
|
2021-07-01 15:44:00 +00:00
|
|
|
}
|
2022-05-13 11:49:41 +00:00
|
|
|
return nil, err
|
2020-06-23 19:20:17 +00:00
|
|
|
}
|
2021-03-05 14:06:54 +00:00
|
|
|
if si == nil {
|
2022-02-16 14:48:15 +00:00
|
|
|
ic.DAO.DeleteStorageItem(c.ID, key)
|
2020-06-23 19:20:17 +00:00
|
|
|
} else {
|
2022-02-16 14:48:15 +00:00
|
|
|
ic.DAO.PutStorageItem(c.ID, key, si)
|
2020-06-23 19:20:17 +00:00
|
|
|
}
|
2022-05-13 11:49:41 +00:00
|
|
|
return postF, nil
|
2020-06-23 19:20:17 +00:00
|
|
|
}
|
|
|
|
|
2022-04-20 18:30:09 +00:00
|
|
|
// TransferInternal transfers NEO across accounts.
|
2020-11-19 15:01:42 +00:00
|
|
|
func (c *nep17TokenNative) TransferInternal(ic *interop.Context, from, to util.Uint160, amount *big.Int, data stackitem.Item) error {
|
2022-05-13 11:49:41 +00:00
|
|
|
var postF1, postF2 func()
|
|
|
|
|
2020-03-25 10:00:11 +00:00
|
|
|
if amount.Sign() == -1 {
|
|
|
|
return errors.New("negative amount")
|
|
|
|
}
|
|
|
|
|
2020-08-07 13:58:09 +00:00
|
|
|
caller := ic.VM.GetCallingScriptHash()
|
2020-07-15 19:52:35 +00:00
|
|
|
if caller.Equals(util.Uint160{}) || !from.Equals(caller) {
|
|
|
|
ok, err := runtime.CheckHashedWitness(ic, from)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
} else if !ok {
|
|
|
|
return errors.New("invalid signature")
|
|
|
|
}
|
2020-05-19 07:19:05 +00:00
|
|
|
}
|
2020-03-25 10:00:11 +00:00
|
|
|
isEmpty := from.Equals(to) || amount.Sign() == 0
|
|
|
|
inc := amount
|
|
|
|
if isEmpty {
|
|
|
|
inc = big.NewInt(0)
|
2020-05-27 19:51:44 +00:00
|
|
|
} else {
|
|
|
|
inc = new(big.Int).Neg(inc)
|
2020-03-25 10:00:11 +00:00
|
|
|
}
|
2022-05-13 11:49:41 +00:00
|
|
|
|
|
|
|
postF1, err := c.updateAccBalance(ic, from, inc, amount)
|
|
|
|
if err != nil {
|
2020-03-25 10:00:11 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if !isEmpty {
|
2022-05-13 11:49:41 +00:00
|
|
|
postF2, err = c.updateAccBalance(ic, to, amount, nil)
|
|
|
|
if err != nil {
|
2020-03-25 10:00:11 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-13 11:49:41 +00:00
|
|
|
c.postTransfer(ic, &from, &to, amount, data, true, postF1, postF2)
|
2020-03-25 10:00:11 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-11-19 15:01:42 +00:00
|
|
|
func (c *nep17TokenNative) balanceOf(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
2020-03-25 10:00:11 +00:00
|
|
|
h := toUint160(args[0])
|
2021-07-25 12:00:44 +00:00
|
|
|
return stackitem.NewBigInteger(c.balanceOfInternal(ic.DAO, h))
|
|
|
|
}
|
|
|
|
|
2022-02-16 15:04:47 +00:00
|
|
|
func (c *nep17TokenNative) balanceOfInternal(d *dao.Simple, h util.Uint160) *big.Int {
|
2021-04-01 15:16:43 +00:00
|
|
|
key := makeAccountKey(h)
|
2021-07-25 12:00:44 +00:00
|
|
|
si := d.GetStorageItem(c.ID, key)
|
2021-04-01 15:16:43 +00:00
|
|
|
if si == nil {
|
2021-07-25 12:00:44 +00:00
|
|
|
return big.NewInt(0)
|
2021-04-01 15:16:43 +00:00
|
|
|
}
|
|
|
|
balance, err := c.balFromBytes(&si)
|
2020-03-25 10:00:11 +00:00
|
|
|
if err != nil {
|
2021-04-01 15:16:43 +00:00
|
|
|
panic(fmt.Errorf("can not deserialize balance state: %w", err))
|
2020-03-25 10:00:11 +00:00
|
|
|
}
|
2021-07-25 12:00:44 +00:00
|
|
|
return balance
|
2020-03-25 10:00:11 +00:00
|
|
|
}
|
|
|
|
|
2020-11-30 10:05:42 +00:00
|
|
|
func (c *nep17TokenNative) mint(ic *interop.Context, h util.Uint160, amount *big.Int, callOnPayment bool) {
|
2020-06-04 12:41:54 +00:00
|
|
|
if amount.Sign() == 0 {
|
|
|
|
return
|
|
|
|
}
|
2022-05-13 11:49:41 +00:00
|
|
|
postF := c.addTokens(ic, h, amount)
|
|
|
|
c.postTransfer(ic, nil, &h, amount, stackitem.Null{}, callOnPayment, postF)
|
2020-03-25 10:00:11 +00:00
|
|
|
}
|
|
|
|
|
2020-11-19 15:01:42 +00:00
|
|
|
func (c *nep17TokenNative) burn(ic *interop.Context, h util.Uint160, amount *big.Int) {
|
2020-06-04 12:41:54 +00:00
|
|
|
if amount.Sign() == 0 {
|
|
|
|
return
|
|
|
|
}
|
2021-12-02 12:40:43 +00:00
|
|
|
amount.Neg(amount)
|
2022-05-13 11:49:41 +00:00
|
|
|
postF := c.addTokens(ic, h, amount)
|
2021-12-02 12:40:43 +00:00
|
|
|
amount.Neg(amount)
|
2022-05-13 11:49:41 +00:00
|
|
|
c.postTransfer(ic, &h, nil, amount, stackitem.Null{}, false, postF)
|
2020-03-25 10:00:11 +00:00
|
|
|
}
|
|
|
|
|
2022-05-13 11:49:41 +00:00
|
|
|
func (c *nep17TokenNative) addTokens(ic *interop.Context, h util.Uint160, amount *big.Int) func() {
|
2020-05-19 15:49:03 +00:00
|
|
|
if amount.Sign() == 0 {
|
2022-05-13 11:49:41 +00:00
|
|
|
return nil
|
2020-03-25 10:00:11 +00:00
|
|
|
}
|
|
|
|
|
2020-04-23 18:28:37 +00:00
|
|
|
key := makeAccountKey(h)
|
2021-02-09 09:26:25 +00:00
|
|
|
si := ic.DAO.GetStorageItem(c.ID, key)
|
2020-04-23 18:28:37 +00:00
|
|
|
if si == nil {
|
2021-03-05 14:06:54 +00:00
|
|
|
si = state.StorageItem{}
|
2020-03-25 10:00:11 +00:00
|
|
|
}
|
2022-05-13 11:49:41 +00:00
|
|
|
postF, err := c.incBalance(ic, h, &si, amount, nil)
|
|
|
|
if err != nil {
|
2020-03-25 10:00:11 +00:00
|
|
|
panic(err)
|
|
|
|
}
|
2021-03-05 14:06:54 +00:00
|
|
|
if si == nil {
|
2022-02-16 14:48:15 +00:00
|
|
|
ic.DAO.DeleteStorageItem(c.ID, key)
|
2021-02-12 20:23:24 +00:00
|
|
|
} else {
|
2022-02-16 14:48:15 +00:00
|
|
|
ic.DAO.PutStorageItem(c.ID, key, si)
|
2020-03-25 10:00:11 +00:00
|
|
|
}
|
|
|
|
|
2021-08-02 21:19:23 +00:00
|
|
|
buf, supply := c.getTotalSupply(ic.DAO)
|
2020-04-22 20:00:18 +00:00
|
|
|
supply.Add(supply, amount)
|
2022-02-16 14:48:15 +00:00
|
|
|
c.saveTotalSupply(ic.DAO, buf, supply)
|
2022-05-13 11:49:41 +00:00
|
|
|
return postF
|
2020-03-25 10:00:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func newDescriptor(name string, ret smartcontract.ParamType, ps ...manifest.Parameter) *manifest.Method {
|
2021-09-09 14:03:11 +00:00
|
|
|
if len(ps) == 0 {
|
|
|
|
ps = []manifest.Parameter{}
|
|
|
|
}
|
2020-03-25 10:00:11 +00:00
|
|
|
return &manifest.Method{
|
|
|
|
Name: name,
|
|
|
|
Parameters: ps,
|
|
|
|
ReturnType: ret,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-17 11:47:19 +00:00
|
|
|
// newMethodAndPrice builds method with the provided descriptor and ActiveFrom/ActiveTill hardfork
|
|
|
|
// values consequently specified via activations. [config.HFDefault] specfied as ActiveFrom is treated
|
|
|
|
// as active starting from the genesis block.
|
|
|
|
func newMethodAndPrice(f interop.Method, cpuFee int64, flags callflag.CallFlag, activations ...config.Hardfork) *interop.MethodAndPrice {
|
2024-03-27 17:48:14 +00:00
|
|
|
md := &interop.MethodAndPrice{
|
|
|
|
HFSpecificMethodAndPrice: interop.HFSpecificMethodAndPrice{
|
|
|
|
Func: f,
|
|
|
|
CPUFee: cpuFee,
|
|
|
|
RequiredFlags: flags,
|
|
|
|
},
|
2020-03-25 10:00:11 +00:00
|
|
|
}
|
2024-05-17 11:47:19 +00:00
|
|
|
if len(activations) > 0 {
|
|
|
|
if activations[0] != config.HFDefault {
|
|
|
|
md.ActiveFrom = &activations[0]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if len(activations) > 1 {
|
|
|
|
md.ActiveTill = &activations[1]
|
2024-03-27 17:48:14 +00:00
|
|
|
}
|
|
|
|
return md
|
|
|
|
}
|
|
|
|
|
|
|
|
func newEventDescriptor(name string, ps ...manifest.Parameter) *manifest.Event {
|
|
|
|
if len(ps) == 0 {
|
|
|
|
ps = []manifest.Parameter{}
|
|
|
|
}
|
|
|
|
return &manifest.Event{
|
|
|
|
Name: name,
|
|
|
|
Parameters: ps,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-17 11:47:19 +00:00
|
|
|
// newEvent builds event with the provided descriptor and ActiveFrom/ActiveTill hardfork
|
|
|
|
// values consequently specified via activations.
|
|
|
|
func newEvent(desc *manifest.Event, activations ...config.Hardfork) interop.Event {
|
2024-03-27 17:48:14 +00:00
|
|
|
md := interop.Event{
|
|
|
|
HFSpecificEvent: interop.HFSpecificEvent{
|
|
|
|
MD: desc,
|
|
|
|
},
|
|
|
|
}
|
2024-05-17 11:47:19 +00:00
|
|
|
if len(activations) > 0 {
|
|
|
|
md.ActiveFrom = &activations[0]
|
|
|
|
}
|
|
|
|
if len(activations) > 1 {
|
|
|
|
md.ActiveTill = &activations[1]
|
2024-03-27 17:48:14 +00:00
|
|
|
}
|
|
|
|
return md
|
2020-03-25 10:00:11 +00:00
|
|
|
}
|
|
|
|
|
2020-06-03 12:55:06 +00:00
|
|
|
func toBigInt(s stackitem.Item) *big.Int {
|
2020-03-25 10:00:11 +00:00
|
|
|
bi, err := s.TryInteger()
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
return bi
|
|
|
|
}
|
|
|
|
|
2020-06-03 12:55:06 +00:00
|
|
|
func toUint160(s stackitem.Item) util.Uint160 {
|
2020-03-25 10:00:11 +00:00
|
|
|
buf, err := s.TryBytes()
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
u, err := util.Uint160DecodeBytesBE(buf)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
return u
|
|
|
|
}
|
2020-05-08 17:54:24 +00:00
|
|
|
|
2023-09-20 13:33:04 +00:00
|
|
|
func toUint64(s stackitem.Item) uint64 {
|
2020-11-19 10:00:46 +00:00
|
|
|
bigInt := toBigInt(s)
|
2021-07-19 12:15:14 +00:00
|
|
|
if !bigInt.IsUint64() {
|
2023-09-20 13:33:04 +00:00
|
|
|
panic("bigint is not a uint64")
|
2020-11-19 10:00:46 +00:00
|
|
|
}
|
2023-09-20 13:33:04 +00:00
|
|
|
return bigInt.Uint64()
|
|
|
|
}
|
|
|
|
|
|
|
|
func toUint32(s stackitem.Item) uint32 {
|
|
|
|
uint64Value := toUint64(s)
|
2021-07-19 12:15:14 +00:00
|
|
|
if uint64Value > math.MaxUint32 {
|
2020-11-19 10:00:46 +00:00
|
|
|
panic("bigint does not fit into uint32")
|
|
|
|
}
|
2021-07-19 12:15:14 +00:00
|
|
|
return uint32(uint64Value)
|
2020-11-19 10:00:46 +00:00
|
|
|
}
|
2022-03-01 10:10:54 +00:00
|
|
|
|
2023-09-20 13:33:04 +00:00
|
|
|
func toUint8(s stackitem.Item) uint8 {
|
|
|
|
uint64Value := toUint64(s)
|
|
|
|
if uint64Value > math.MaxUint8 {
|
|
|
|
panic("bigint does not fit into uint8")
|
|
|
|
}
|
|
|
|
return uint8(uint64Value)
|
|
|
|
}
|
|
|
|
|
2022-03-01 10:10:54 +00:00
|
|
|
func toInt64(s stackitem.Item) int64 {
|
|
|
|
bigInt := toBigInt(s)
|
|
|
|
if !bigInt.IsInt64() {
|
|
|
|
panic("bigint is not an uint64")
|
|
|
|
}
|
|
|
|
return bigInt.Int64()
|
|
|
|
}
|