core: process NEP5 transfers emitted by Native.onPersist

All GAS is minted/burnt during `onPersist` call.
This commit is contained in:
Evgenii Stratonikov 2020-06-18 16:34:56 +03:00
parent c9df5d3aed
commit 8a0b2be285
2 changed files with 68 additions and 40 deletions

View file

@ -578,6 +578,9 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
} else if _, err := systemInterop.DAO.Persist(); err != nil { } else if _, err := systemInterop.DAO.Persist(); err != nil {
return errors.Wrap(err, "can't persist `onPersist` changes") return errors.Wrap(err, "can't persist `onPersist` changes")
} }
for i := range systemInterop.Notifications {
bc.handleNotification(&systemInterop.Notifications[i], cache, block, block.Hash())
}
aer := &state.AppExecResult{ aer := &state.AppExecResult{
TxHash: block.Hash(), // application logs can be retrieved by block hash TxHash: block.Hash(), // application logs can be retrieved by block hash
Trigger: trigger.System, Trigger: trigger.System,
@ -612,42 +615,8 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
if err != nil { if err != nil {
return errors.Wrap(err, "failed to persist invocation results") return errors.Wrap(err, "failed to persist invocation results")
} }
for _, note := range systemInterop.Notifications { for i := range systemInterop.Notifications {
arr, ok := note.Item.Value().([]stackitem.Item) bc.handleNotification(&systemInterop.Notifications[i], cache, block, tx.Hash())
if !ok || len(arr) != 4 {
continue
}
op, ok := arr[0].Value().([]byte)
if !ok || (string(op) != "transfer" && string(op) != "Transfer") {
continue
}
var from []byte
fromValue := arr[1].Value()
// we don't have `from` set when we are minting tokens
if fromValue != nil {
from, ok = fromValue.([]byte)
if !ok {
continue
}
}
var to []byte
toValue := arr[2].Value()
// we don't have `to` set when we are burning tokens
if toValue != nil {
to, ok = toValue.([]byte)
if !ok {
continue
}
}
amount, ok := arr[3].Value().(*big.Int)
if !ok {
bs, ok := arr[3].Value().([]byte)
if !ok {
continue
}
amount = bigint.FromBytes(bs)
}
bc.processNEP5Transfer(cache, tx, block, note.ScriptHash, from, to, amount.Int64())
} }
} else { } else {
bc.log.Warn("contract invocation failed", bc.log.Warn("contract invocation failed",
@ -695,6 +664,44 @@ func (bc *Blockchain) storeBlock(block *block.Block) error {
return nil return nil
} }
func (bc *Blockchain) handleNotification(note *state.NotificationEvent, d *dao.Cached, b *block.Block, h util.Uint256) {
arr, ok := note.Item.Value().([]stackitem.Item)
if !ok || len(arr) != 4 {
return
}
op, ok := arr[0].Value().([]byte)
if !ok || (string(op) != "transfer" && string(op) != "Transfer") {
return
}
var from []byte
fromValue := arr[1].Value()
// we don't have `from` set when we are minting tokens
if fromValue != nil {
from, ok = fromValue.([]byte)
if !ok {
return
}
}
var to []byte
toValue := arr[2].Value()
// we don't have `to` set when we are burning tokens
if toValue != nil {
to, ok = toValue.([]byte)
if !ok {
return
}
}
amount, ok := arr[3].Value().(*big.Int)
if !ok {
bs, ok := arr[3].Value().([]byte)
if !ok {
return
}
amount = bigint.FromBytes(bs)
}
bc.processNEP5Transfer(d, h, b, note.ScriptHash, from, to, amount.Int64())
}
func parseUint160(addr []byte) util.Uint160 { func parseUint160(addr []byte) util.Uint160 {
if u, err := util.Uint160DecodeBytesBE(addr); err == nil { if u, err := util.Uint160DecodeBytesBE(addr); err == nil {
return u return u
@ -702,7 +709,7 @@ func parseUint160(addr []byte) util.Uint160 {
return util.Uint160{} return util.Uint160{}
} }
func (bc *Blockchain) processNEP5Transfer(cache *dao.Cached, tx *transaction.Transaction, b *block.Block, sc util.Uint160, from, to []byte, amount int64) { func (bc *Blockchain) processNEP5Transfer(cache *dao.Cached, h util.Uint256, b *block.Block, sc util.Uint160, from, to []byte, amount int64) {
toAddr := parseUint160(to) toAddr := parseUint160(to)
fromAddr := parseUint160(from) fromAddr := parseUint160(from)
transfer := &state.NEP5Transfer{ transfer := &state.NEP5Transfer{
@ -711,7 +718,7 @@ func (bc *Blockchain) processNEP5Transfer(cache *dao.Cached, tx *transaction.Tra
To: toAddr, To: toAddr,
Block: b.Index, Block: b.Index,
Timestamp: b.Timestamp, Timestamp: b.Timestamp,
Tx: tx.Hash(), Tx: h,
} }
if !fromAddr.Equals(util.Uint160{}) { if !fromAddr.Equals(util.Uint160{}) {
balances, err := cache.GetNEP5Balances(fromAddr) balances, err := cache.GetNEP5Balances(fromAddr)

View file

@ -148,8 +148,8 @@ var rpcTestCases = map[string][]rpcTestCase{
}, },
{ {
Asset: e.chain.UtilityTokenHash(), Asset: e.chain.UtilityTokenHash(),
Amount: "1023.99976000", Amount: "923.96934740",
LastUpdated: 4, LastUpdated: 6,
}}, }},
Address: testchain.PrivateKeyByID(0).GetScriptHash().StringLE(), Address: testchain.PrivateKeyByID(0).GetScriptHash().StringLE(),
} }
@ -256,6 +256,27 @@ var rpcTestCases = map[string][]rpcTestCase{
}, },
Address: testchain.PrivateKeyByID(0).Address(), Address: testchain.PrivateKeyByID(0).Address(),
} }
// take burned gas into account
u := testchain.PrivateKeyByID(0).GetScriptHash()
for i := 0; i <= int(e.chain.BlockHeight()); i++ {
h := e.chain.GetHeaderHash(i)
b, err := e.chain.GetBlock(h)
require.NoError(t, err)
for j := range b.Transactions {
if u.Equals(b.Transactions[j].Sender) {
amount := b.Transactions[j].SystemFee + b.Transactions[j].NetworkFee
expected.Sent = append(expected.Sent, result.NEP5Transfer{
Timestamp: b.Timestamp,
Asset: e.chain.UtilityTokenHash(),
Address: "", // burn has empty receiver
Amount: amountToString(int64(amount), 8),
Index: b.Index,
TxHash: b.Hash(),
})
}
}
}
require.Equal(t, expected.Address, res.Address) require.Equal(t, expected.Address, res.Address)
require.ElementsMatch(t, expected.Sent, res.Sent) require.ElementsMatch(t, expected.Sent, res.Sent)
require.ElementsMatch(t, expected.Received, res.Received) require.ElementsMatch(t, expected.Received, res.Received)