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"
|
|
|
|
|
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
|
|
|
|
|
|
|
|
// makeAccountKey creates a key from account script hash.
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2020-11-19 15:01:42 +00:00
|
|
|
// nep17TokenNative represents NEP-17 token contract.
|
|
|
|
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
|
|
|
|
incBalance func(*interop.Context, util.Uint160, *state.StorageItem, *big.Int) error
|
|
|
|
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 {
|
|
|
|
n := &nep17TokenNative{ContractMD: *interop.NewContractMD(name, id)}
|
2020-11-19 15:01:42 +00:00
|
|
|
n.Manifest.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
|
|
|
|
2021-01-12 15:06:27 +00:00
|
|
|
n.AddEvent("Transfer", transferParams...)
|
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 {
|
2020-08-03 11:31:42 +00:00
|
|
|
return stackitem.NewBigInteger(c.getTotalSupply(ic.DAO))
|
2020-04-22 20:00:18 +00:00
|
|
|
}
|
|
|
|
|
2020-11-19 15:01:42 +00:00
|
|
|
func (c *nep17TokenNative) getTotalSupply(d dao.DAO) *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 {
|
|
|
|
return big.NewInt(0)
|
|
|
|
}
|
2021-03-05 14:06:54 +00:00
|
|
|
return bigint.FromBytes(si)
|
2020-04-22 20:00:18 +00:00
|
|
|
}
|
|
|
|
|
2020-11-19 15:01:42 +00:00
|
|
|
func (c *nep17TokenNative) saveTotalSupply(d dao.DAO, supply *big.Int) error {
|
2021-03-05 14:06:54 +00:00
|
|
|
return d.PutStorageItem(c.ID, totalSupplyKey, bigint.ToBytes(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,
|
|
|
|
data stackitem.Item, callOnPayment bool) {
|
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 {
|
2020-11-19 15:01:42 +00:00
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *nep17TokenNative) emitTransfer(ic *interop.Context, from, to *util.Uint160, amount *big.Int) {
|
2020-03-25 10:00:11 +00:00
|
|
|
ne := state.NotificationEvent{
|
|
|
|
ScriptHash: c.Hash,
|
2020-06-29 08:25:32 +00:00
|
|
|
Name: "Transfer",
|
2020-06-03 12:55:06 +00:00
|
|
|
Item: stackitem.NewArray([]stackitem.Item{
|
2020-03-25 10:00:11 +00:00
|
|
|
addrToStackItem(from),
|
|
|
|
addrToStackItem(to),
|
2020-06-03 12:55:06 +00:00
|
|
|
stackitem.NewBigInteger(amount),
|
2020-03-25 10:00:11 +00:00
|
|
|
}),
|
|
|
|
}
|
|
|
|
ic.Notifications = append(ic.Notifications, ne)
|
|
|
|
}
|
|
|
|
|
core: do not allow NEP17 roundtrip in case of insufficient funds
NEP17 roundtrip is prohibited if from account doesn't have enough funds.
This commit fixes states diff in block 92057 where account
NfuwpaQ1A2xaeVbxWe8FRtaRgaMa8yF3YM initiates two NEO roundtrips with
amount exceeding the account's balance:
block 92057: value mismatch for key +////xTbYWBH3r5qhRKZAPFPHabKfb2vhQ==: QQMhAkwBIQOZZwEA vs QQMhAkwBIQN/ZwEA
block 92057: value mismatch for key +v///ws=: kqlddcitCg== vs tphddcitCg==
block 92057: value mismatch for key +v///xTbYWBH3r5qhRKZAPFPHabKfb2vhQ==: QQEhBUWyDu0W vs QQEhBWmhDu0W
C#'s applog (contains False and False on stack for both transfers):
```
{
"id" : 1,
"jsonrpc" : "2.0",
"result" : {
"executions" : [
{
"gasconsumed" : "11955500",
"exception" : null,
"stack" : [
{
"value" : false,
"type" : "Boolean"
},
{
"value" : false,
"type" : "Boolean"
}
],
"vmstate" : "HALT",
"trigger" : "Application",
"notifications" : []
}
],
"txid" : "0x8e73a7e9a566a514813907272ad65fc965002c3b098eacc5bdda529af19d7688"
}
}
```
Go's applog (both transfers succeeded and GAS minted):
```
{
"result" : {
"executions" : [
{
"gasconsumed" : "11955500",
"trigger" : "Application",
"stack" : [
{
"type" : "Boolean",
"value" : true
},
{
"type" : "Boolean",
"value" : true
}
],
"vmstate" : "HALT",
"notifications" : [
{
"eventname" : "Transfer",
"contract" : "0xd2a4cff31913016155e38e474a2c06d08be276cf",
"state" : {
"value" : [
{
"type" : "Any"
},
{
"value" : "22FgR96+aoUSmQDxTx2myn29r4U=",
"type" : "ByteString"
},
{
"value" : "4316",
"type" : "Integer"
}
],
"type" : "Array"
}
},
{
"state" : {
"type" : "Array",
"value" : [
{
"value" : "22FgR96+aoUSmQDxTx2myn29r4U=",
"type" : "ByteString"
},
{
"type" : "ByteString",
"value" : "22FgR96+aoUSmQDxTx2myn29r4U="
},
{
"type" : "Integer",
"value" : "1111111111"
}
]
},
"contract" : "0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5",
"eventname" : "Transfer"
},
{
"contract" : "0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5",
"state" : {
"type" : "Array",
"value" : [
{
"value" : "22FgR96+aoUSmQDxTx2myn29r4U=",
"type" : "ByteString"
},
{
"type" : "ByteString",
"value" : "22FgR96+aoUSmQDxTx2myn29r4U="
},
{
"type" : "Integer",
"value" : "1111111"
}
]
},
"eventname" : "Transfer"
}
]
}
],
"txid" : "0x8e73a7e9a566a514813907272ad65fc965002c3b098eacc5bdda529af19d7688"
},
"id" : 1,
"jsonrpc" : "2.0"
}
```
2021-06-09 08:58:58 +00:00
|
|
|
// updateAccBalance adds specified amount to the acc's balance. If requiredBalance
|
|
|
|
// is set and amount is 0, then acc's balance is checked against requiredBalance.
|
|
|
|
func (c *nep17TokenNative) updateAccBalance(ic *interop.Context, acc util.Uint160, amount *big.Int, requiredBalance *big.Int) 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 {
|
|
|
|
if amount.Sign() <= 0 {
|
|
|
|
return errors.New("insufficient funds")
|
|
|
|
}
|
2021-03-05 14:06:54 +00:00
|
|
|
si = state.StorageItem{}
|
core: do not allow NEP17 roundtrip in case of insufficient funds
NEP17 roundtrip is prohibited if from account doesn't have enough funds.
This commit fixes states diff in block 92057 where account
NfuwpaQ1A2xaeVbxWe8FRtaRgaMa8yF3YM initiates two NEO roundtrips with
amount exceeding the account's balance:
block 92057: value mismatch for key +////xTbYWBH3r5qhRKZAPFPHabKfb2vhQ==: QQMhAkwBIQOZZwEA vs QQMhAkwBIQN/ZwEA
block 92057: value mismatch for key +v///ws=: kqlddcitCg== vs tphddcitCg==
block 92057: value mismatch for key +v///xTbYWBH3r5qhRKZAPFPHabKfb2vhQ==: QQEhBUWyDu0W vs QQEhBWmhDu0W
C#'s applog (contains False and False on stack for both transfers):
```
{
"id" : 1,
"jsonrpc" : "2.0",
"result" : {
"executions" : [
{
"gasconsumed" : "11955500",
"exception" : null,
"stack" : [
{
"value" : false,
"type" : "Boolean"
},
{
"value" : false,
"type" : "Boolean"
}
],
"vmstate" : "HALT",
"trigger" : "Application",
"notifications" : []
}
],
"txid" : "0x8e73a7e9a566a514813907272ad65fc965002c3b098eacc5bdda529af19d7688"
}
}
```
Go's applog (both transfers succeeded and GAS minted):
```
{
"result" : {
"executions" : [
{
"gasconsumed" : "11955500",
"trigger" : "Application",
"stack" : [
{
"type" : "Boolean",
"value" : true
},
{
"type" : "Boolean",
"value" : true
}
],
"vmstate" : "HALT",
"notifications" : [
{
"eventname" : "Transfer",
"contract" : "0xd2a4cff31913016155e38e474a2c06d08be276cf",
"state" : {
"value" : [
{
"type" : "Any"
},
{
"value" : "22FgR96+aoUSmQDxTx2myn29r4U=",
"type" : "ByteString"
},
{
"value" : "4316",
"type" : "Integer"
}
],
"type" : "Array"
}
},
{
"state" : {
"type" : "Array",
"value" : [
{
"value" : "22FgR96+aoUSmQDxTx2myn29r4U=",
"type" : "ByteString"
},
{
"type" : "ByteString",
"value" : "22FgR96+aoUSmQDxTx2myn29r4U="
},
{
"type" : "Integer",
"value" : "1111111111"
}
]
},
"contract" : "0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5",
"eventname" : "Transfer"
},
{
"contract" : "0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5",
"state" : {
"type" : "Array",
"value" : [
{
"value" : "22FgR96+aoUSmQDxTx2myn29r4U=",
"type" : "ByteString"
},
{
"type" : "ByteString",
"value" : "22FgR96+aoUSmQDxTx2myn29r4U="
},
{
"type" : "Integer",
"value" : "1111111"
}
]
},
"eventname" : "Transfer"
}
]
}
],
"txid" : "0x8e73a7e9a566a514813907272ad65fc965002c3b098eacc5bdda529af19d7688"
},
"id" : 1,
"jsonrpc" : "2.0"
}
```
2021-06-09 08:58:58 +00:00
|
|
|
} else if amount.Sign() == 0 && requiredBalance != nil {
|
2021-07-01 15:46:23 +00:00
|
|
|
// If amount == 0 then it's either a round trip or an empty transfer. In
|
|
|
|
// case of a round trip account's balance may still be less than actual
|
core: do not allow NEP17 roundtrip in case of insufficient funds
NEP17 roundtrip is prohibited if from account doesn't have enough funds.
This commit fixes states diff in block 92057 where account
NfuwpaQ1A2xaeVbxWe8FRtaRgaMa8yF3YM initiates two NEO roundtrips with
amount exceeding the account's balance:
block 92057: value mismatch for key +////xTbYWBH3r5qhRKZAPFPHabKfb2vhQ==: QQMhAkwBIQOZZwEA vs QQMhAkwBIQN/ZwEA
block 92057: value mismatch for key +v///ws=: kqlddcitCg== vs tphddcitCg==
block 92057: value mismatch for key +v///xTbYWBH3r5qhRKZAPFPHabKfb2vhQ==: QQEhBUWyDu0W vs QQEhBWmhDu0W
C#'s applog (contains False and False on stack for both transfers):
```
{
"id" : 1,
"jsonrpc" : "2.0",
"result" : {
"executions" : [
{
"gasconsumed" : "11955500",
"exception" : null,
"stack" : [
{
"value" : false,
"type" : "Boolean"
},
{
"value" : false,
"type" : "Boolean"
}
],
"vmstate" : "HALT",
"trigger" : "Application",
"notifications" : []
}
],
"txid" : "0x8e73a7e9a566a514813907272ad65fc965002c3b098eacc5bdda529af19d7688"
}
}
```
Go's applog (both transfers succeeded and GAS minted):
```
{
"result" : {
"executions" : [
{
"gasconsumed" : "11955500",
"trigger" : "Application",
"stack" : [
{
"type" : "Boolean",
"value" : true
},
{
"type" : "Boolean",
"value" : true
}
],
"vmstate" : "HALT",
"notifications" : [
{
"eventname" : "Transfer",
"contract" : "0xd2a4cff31913016155e38e474a2c06d08be276cf",
"state" : {
"value" : [
{
"type" : "Any"
},
{
"value" : "22FgR96+aoUSmQDxTx2myn29r4U=",
"type" : "ByteString"
},
{
"value" : "4316",
"type" : "Integer"
}
],
"type" : "Array"
}
},
{
"state" : {
"type" : "Array",
"value" : [
{
"value" : "22FgR96+aoUSmQDxTx2myn29r4U=",
"type" : "ByteString"
},
{
"type" : "ByteString",
"value" : "22FgR96+aoUSmQDxTx2myn29r4U="
},
{
"type" : "Integer",
"value" : "1111111111"
}
]
},
"contract" : "0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5",
"eventname" : "Transfer"
},
{
"contract" : "0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5",
"state" : {
"type" : "Array",
"value" : [
{
"value" : "22FgR96+aoUSmQDxTx2myn29r4U=",
"type" : "ByteString"
},
{
"type" : "ByteString",
"value" : "22FgR96+aoUSmQDxTx2myn29r4U="
},
{
"type" : "Integer",
"value" : "1111111"
}
]
},
"eventname" : "Transfer"
}
]
}
],
"txid" : "0x8e73a7e9a566a514813907272ad65fc965002c3b098eacc5bdda529af19d7688"
},
"id" : 1,
"jsonrpc" : "2.0"
}
```
2021-06-09 08:58:58 +00:00
|
|
|
// transfer's amount, so we need to check it. Other cases are handled by
|
|
|
|
// `incBalance` method.
|
|
|
|
balance, err := c.balFromBytes(&si)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("failed to deserialise balance: %w", err)
|
|
|
|
}
|
|
|
|
if balance.Cmp(requiredBalance) < 0 {
|
|
|
|
// Firstly, need to put it back to storage as it affects dumps.
|
|
|
|
err = ic.DAO.PutStorageItem(c.ID, key, si)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
// Finally, return an error.
|
|
|
|
return errors.New("insufficient funds")
|
|
|
|
}
|
2020-06-23 19:20:17 +00:00
|
|
|
}
|
|
|
|
|
2021-03-05 14:06:54 +00:00
|
|
|
err := c.incBalance(ic, acc, &si, amount)
|
2020-06-23 19:20:17 +00:00
|
|
|
if err != nil {
|
2021-07-01 15:44:00 +00:00
|
|
|
if si != nil && amount.Sign() < 0 {
|
|
|
|
_ = ic.DAO.PutStorageItem(c.ID, key, si)
|
|
|
|
}
|
2020-06-23 19:20:17 +00:00
|
|
|
return err
|
|
|
|
}
|
2021-03-05 14:06:54 +00:00
|
|
|
if si == nil {
|
2021-02-09 09:26:25 +00:00
|
|
|
err = ic.DAO.DeleteStorageItem(c.ID, key)
|
2020-06-23 19:20:17 +00:00
|
|
|
} else {
|
2021-02-09 09:26:25 +00:00
|
|
|
err = ic.DAO.PutStorageItem(c.ID, key, si)
|
2020-06-23 19:20:17 +00:00
|
|
|
}
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2020-08-03 13:24:22 +00:00
|
|
|
// TransferInternal transfers NEO between 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 {
|
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
|
|
|
}
|
core: do not allow NEP17 roundtrip in case of insufficient funds
NEP17 roundtrip is prohibited if from account doesn't have enough funds.
This commit fixes states diff in block 92057 where account
NfuwpaQ1A2xaeVbxWe8FRtaRgaMa8yF3YM initiates two NEO roundtrips with
amount exceeding the account's balance:
block 92057: value mismatch for key +////xTbYWBH3r5qhRKZAPFPHabKfb2vhQ==: QQMhAkwBIQOZZwEA vs QQMhAkwBIQN/ZwEA
block 92057: value mismatch for key +v///ws=: kqlddcitCg== vs tphddcitCg==
block 92057: value mismatch for key +v///xTbYWBH3r5qhRKZAPFPHabKfb2vhQ==: QQEhBUWyDu0W vs QQEhBWmhDu0W
C#'s applog (contains False and False on stack for both transfers):
```
{
"id" : 1,
"jsonrpc" : "2.0",
"result" : {
"executions" : [
{
"gasconsumed" : "11955500",
"exception" : null,
"stack" : [
{
"value" : false,
"type" : "Boolean"
},
{
"value" : false,
"type" : "Boolean"
}
],
"vmstate" : "HALT",
"trigger" : "Application",
"notifications" : []
}
],
"txid" : "0x8e73a7e9a566a514813907272ad65fc965002c3b098eacc5bdda529af19d7688"
}
}
```
Go's applog (both transfers succeeded and GAS minted):
```
{
"result" : {
"executions" : [
{
"gasconsumed" : "11955500",
"trigger" : "Application",
"stack" : [
{
"type" : "Boolean",
"value" : true
},
{
"type" : "Boolean",
"value" : true
}
],
"vmstate" : "HALT",
"notifications" : [
{
"eventname" : "Transfer",
"contract" : "0xd2a4cff31913016155e38e474a2c06d08be276cf",
"state" : {
"value" : [
{
"type" : "Any"
},
{
"value" : "22FgR96+aoUSmQDxTx2myn29r4U=",
"type" : "ByteString"
},
{
"value" : "4316",
"type" : "Integer"
}
],
"type" : "Array"
}
},
{
"state" : {
"type" : "Array",
"value" : [
{
"value" : "22FgR96+aoUSmQDxTx2myn29r4U=",
"type" : "ByteString"
},
{
"type" : "ByteString",
"value" : "22FgR96+aoUSmQDxTx2myn29r4U="
},
{
"type" : "Integer",
"value" : "1111111111"
}
]
},
"contract" : "0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5",
"eventname" : "Transfer"
},
{
"contract" : "0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5",
"state" : {
"type" : "Array",
"value" : [
{
"value" : "22FgR96+aoUSmQDxTx2myn29r4U=",
"type" : "ByteString"
},
{
"type" : "ByteString",
"value" : "22FgR96+aoUSmQDxTx2myn29r4U="
},
{
"type" : "Integer",
"value" : "1111111"
}
]
},
"eventname" : "Transfer"
}
]
}
],
"txid" : "0x8e73a7e9a566a514813907272ad65fc965002c3b098eacc5bdda529af19d7688"
},
"id" : 1,
"jsonrpc" : "2.0"
}
```
2021-06-09 08:58:58 +00:00
|
|
|
if err := c.updateAccBalance(ic, from, inc, amount); err != nil {
|
2020-03-25 10:00:11 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if !isEmpty {
|
core: do not allow NEP17 roundtrip in case of insufficient funds
NEP17 roundtrip is prohibited if from account doesn't have enough funds.
This commit fixes states diff in block 92057 where account
NfuwpaQ1A2xaeVbxWe8FRtaRgaMa8yF3YM initiates two NEO roundtrips with
amount exceeding the account's balance:
block 92057: value mismatch for key +////xTbYWBH3r5qhRKZAPFPHabKfb2vhQ==: QQMhAkwBIQOZZwEA vs QQMhAkwBIQN/ZwEA
block 92057: value mismatch for key +v///ws=: kqlddcitCg== vs tphddcitCg==
block 92057: value mismatch for key +v///xTbYWBH3r5qhRKZAPFPHabKfb2vhQ==: QQEhBUWyDu0W vs QQEhBWmhDu0W
C#'s applog (contains False and False on stack for both transfers):
```
{
"id" : 1,
"jsonrpc" : "2.0",
"result" : {
"executions" : [
{
"gasconsumed" : "11955500",
"exception" : null,
"stack" : [
{
"value" : false,
"type" : "Boolean"
},
{
"value" : false,
"type" : "Boolean"
}
],
"vmstate" : "HALT",
"trigger" : "Application",
"notifications" : []
}
],
"txid" : "0x8e73a7e9a566a514813907272ad65fc965002c3b098eacc5bdda529af19d7688"
}
}
```
Go's applog (both transfers succeeded and GAS minted):
```
{
"result" : {
"executions" : [
{
"gasconsumed" : "11955500",
"trigger" : "Application",
"stack" : [
{
"type" : "Boolean",
"value" : true
},
{
"type" : "Boolean",
"value" : true
}
],
"vmstate" : "HALT",
"notifications" : [
{
"eventname" : "Transfer",
"contract" : "0xd2a4cff31913016155e38e474a2c06d08be276cf",
"state" : {
"value" : [
{
"type" : "Any"
},
{
"value" : "22FgR96+aoUSmQDxTx2myn29r4U=",
"type" : "ByteString"
},
{
"value" : "4316",
"type" : "Integer"
}
],
"type" : "Array"
}
},
{
"state" : {
"type" : "Array",
"value" : [
{
"value" : "22FgR96+aoUSmQDxTx2myn29r4U=",
"type" : "ByteString"
},
{
"type" : "ByteString",
"value" : "22FgR96+aoUSmQDxTx2myn29r4U="
},
{
"type" : "Integer",
"value" : "1111111111"
}
]
},
"contract" : "0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5",
"eventname" : "Transfer"
},
{
"contract" : "0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5",
"state" : {
"type" : "Array",
"value" : [
{
"value" : "22FgR96+aoUSmQDxTx2myn29r4U=",
"type" : "ByteString"
},
{
"type" : "ByteString",
"value" : "22FgR96+aoUSmQDxTx2myn29r4U="
},
{
"type" : "Integer",
"value" : "1111111"
}
]
},
"eventname" : "Transfer"
}
]
}
],
"txid" : "0x8e73a7e9a566a514813907272ad65fc965002c3b098eacc5bdda529af19d7688"
},
"id" : 1,
"jsonrpc" : "2.0"
}
```
2021-06-09 08:58:58 +00:00
|
|
|
if err := c.updateAccBalance(ic, to, amount, nil); err != nil {
|
2020-03-25 10:00:11 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-11-30 10:05:42 +00:00
|
|
|
c.postTransfer(ic, &from, &to, amount, data, true)
|
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))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *nep17TokenNative) balanceOfInternal(d dao.DAO, 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
|
|
|
|
}
|
2020-03-25 10:00:11 +00:00
|
|
|
c.addTokens(ic, h, amount)
|
2020-11-30 10:05:42 +00:00
|
|
|
c.postTransfer(ic, nil, &h, amount, stackitem.Null{}, callOnPayment)
|
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
|
|
|
|
}
|
2020-06-18 13:19:07 +00:00
|
|
|
c.addTokens(ic, h, new(big.Int).Neg(amount))
|
2020-11-30 10:05:42 +00:00
|
|
|
c.postTransfer(ic, &h, nil, amount, stackitem.Null{}, false)
|
2020-03-25 10:00:11 +00:00
|
|
|
}
|
|
|
|
|
2020-11-19 15:01:42 +00:00
|
|
|
func (c *nep17TokenNative) addTokens(ic *interop.Context, h util.Uint160, amount *big.Int) {
|
2020-05-19 15:49:03 +00:00
|
|
|
if amount.Sign() == 0 {
|
2020-03-25 10:00:11 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
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
|
|
|
}
|
2021-03-05 14:06:54 +00:00
|
|
|
if err := c.incBalance(ic, h, &si, amount); err != nil {
|
2020-03-25 10:00:11 +00:00
|
|
|
panic(err)
|
|
|
|
}
|
2021-02-12 20:23:24 +00:00
|
|
|
var err error
|
2021-03-05 14:06:54 +00:00
|
|
|
if si == nil {
|
2021-02-12 20:23:24 +00:00
|
|
|
err = ic.DAO.DeleteStorageItem(c.ID, key)
|
|
|
|
} else {
|
|
|
|
err = ic.DAO.PutStorageItem(c.ID, key, si)
|
|
|
|
}
|
|
|
|
if err != nil {
|
2020-03-25 10:00:11 +00:00
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
2020-08-03 11:31:42 +00:00
|
|
|
supply := c.getTotalSupply(ic.DAO)
|
2020-04-22 20:00:18 +00:00
|
|
|
supply.Add(supply, amount)
|
2021-02-12 20:23:24 +00:00
|
|
|
err = c.saveTotalSupply(ic.DAO, supply)
|
2020-04-22 20:00:18 +00:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2020-03-25 10:00:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func newDescriptor(name string, ret smartcontract.ParamType, ps ...manifest.Parameter) *manifest.Method {
|
|
|
|
return &manifest.Method{
|
|
|
|
Name: name,
|
|
|
|
Parameters: ps,
|
|
|
|
ReturnType: ret,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-05 10:30:16 +00:00
|
|
|
func newMethodAndPrice(f interop.Method, cpuFee int64, flags callflag.CallFlag) *interop.MethodAndPrice {
|
2020-04-22 20:00:18 +00:00
|
|
|
return &interop.MethodAndPrice{
|
2020-03-25 10:00:11 +00:00
|
|
|
Func: f,
|
2021-03-05 10:30:16 +00:00
|
|
|
CPUFee: cpuFee,
|
2020-03-25 10:00:11 +00:00
|
|
|
RequiredFlags: flags,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
2020-11-19 10:00:46 +00:00
|
|
|
func toUint32(s stackitem.Item) uint32 {
|
|
|
|
bigInt := toBigInt(s)
|
2021-07-19 12:15:14 +00:00
|
|
|
if !bigInt.IsUint64() {
|
|
|
|
panic("bigint is not an uint64")
|
2020-11-19 10:00:46 +00:00
|
|
|
}
|
2021-07-19 12:15:14 +00:00
|
|
|
uint64Value := bigInt.Uint64()
|
|
|
|
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
|
|
|
}
|