diff --git a/cli/multisig_test.go b/cli/multisig_test.go index c1829ec9f..9672537eb 100644 --- a/cli/multisig_test.go +++ b/cli/multisig_test.go @@ -93,7 +93,7 @@ func TestSignMultisigTx(t *testing.T) { e.Chain.GoverningTokenHash().StringLE(), "transfer", "bytes:"+multisigHash.StringBE(), "bytes:"+priv.GetScriptHash().StringBE(), - "int:1", + "int:1", "bytes:", "--", strings.Join([]string{multisigHash.StringLE(), ":", "Global"}, "")) e.In.WriteString("pass\r") diff --git a/cli/testdata/verify.go b/cli/testdata/verify.go index 467e1fd59..6a3ed7b6a 100644 --- a/cli/testdata/verify.go +++ b/cli/testdata/verify.go @@ -3,3 +3,6 @@ package testdata func Verify() bool { return true } + +func OnPayment(from []byte, amount int, data interface{}) { +} diff --git a/cli/testdata/verify.manifest.json b/cli/testdata/verify.manifest.json index 10f37f908..518bcd896 100755 --- a/cli/testdata/verify.manifest.json +++ b/cli/testdata/verify.manifest.json @@ -1 +1 @@ -{"name":"Verify","abi":{"hash":"0x8dff9f223e4622961f410c015dd37052a59892bb","methods":[{"name":"verify","offset":0,"parameters":[],"returntype":"Boolean"}],"events":[]},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":[],"trusts":[],"safemethods":[],"extra":null} \ No newline at end of file +{"name":"verify","abi":{"hash":"0xbf214a7551e50d6fbe0bef05271719325d9fc1ef","methods":[{"name":"verify","offset":0,"parameters":[],"returntype":"Boolean"},{"name":"onPayment","offset":5,"parameters":[{"name":"from","type":"ByteArray"},{"name":"amount","type":"Integer"},{"name":"data","type":"Any"}],"returntype":"Void"}],"events":[]},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":[],"trusts":[],"safemethods":[],"extra":null} \ No newline at end of file diff --git a/cli/testdata/verify.nef b/cli/testdata/verify.nef index 65f2011fb..309796612 100755 Binary files a/cli/testdata/verify.nef and b/cli/testdata/verify.nef differ diff --git a/examples/token-sale/token_sale.go b/examples/token-sale/token_sale.go index a69cb34a2..e48510516 100644 --- a/examples/token-sale/token_sale.go +++ b/examples/token-sale/token_sale.go @@ -133,14 +133,6 @@ func checkOwnerWitness() bool { return false } -// Name returns the token name -func Name() interface{} { - if trigger != runtime.Application { - return false - } - return token.Name -} - // Decimals returns the token decimals func Decimals() interface{} { if trigger != runtime.Application { diff --git a/examples/token-sale/token_sale.yml b/examples/token-sale/token_sale.yml index 56f3d4d9b..2d6181dca 100644 --- a/examples/token-sale/token_sale.yml +++ b/examples/token-sale/token_sale.yml @@ -1,3 +1,3 @@ name: "My awesome token" -supportedstandards: ["NEP-5"] +supportedstandards: ["NEP-17"] events: [] diff --git a/examples/token/nep5/nep5.go b/examples/token/nep5/nep5.go index f5b911953..8aeb8760c 100644 --- a/examples/token/nep5/nep5.go +++ b/examples/token/nep5/nep5.go @@ -44,7 +44,7 @@ func (t Token) BalanceOf(ctx storage.Context, holder []byte) int { } // Transfer token from one user to another -func (t Token) Transfer(ctx storage.Context, from []byte, to []byte, amount int) bool { +func (t Token) Transfer(ctx storage.Context, from []byte, to []byte, amount int, data interface{}) bool { amountFrom := t.CanTransfer(ctx, from, to, amount) if amountFrom == -1 { return false diff --git a/examples/token/token.go b/examples/token/token.go index 09107d702..bf0e187bd 100644 --- a/examples/token/token.go +++ b/examples/token/token.go @@ -32,11 +32,6 @@ func init() { ctx = storage.GetContext() } -// Name returns the token name -func Name() string { - return token.Name -} - // Symbol returns the token symbol func Symbol() string { return token.Symbol @@ -58,8 +53,8 @@ func BalanceOf(holder interop.Hash160) interface{} { } // Transfer token from one user to another -func Transfer(from interop.Hash160, to interop.Hash160, amount int) bool { - return token.Transfer(ctx, from, to, amount) +func Transfer(from interop.Hash160, to interop.Hash160, amount int, data interface{}) bool { + return token.Transfer(ctx, from, to, amount, data) } // Mint initial supply of tokens diff --git a/examples/token/token.yml b/examples/token/token.yml index b6ee7f862..cea35644b 100644 --- a/examples/token/token.yml +++ b/examples/token/token.yml @@ -1,7 +1,7 @@ name: "Awesome NEO Token" -supportedstandards: ["NEP-5"] +supportedstandards: ["NEP-17"] events: - - name: transfer + - name: Transfer parameters: - name: from type: ByteString diff --git a/pkg/consensus/consensus_test.go b/pkg/consensus/consensus_test.go index d269ad35a..3fa50fb62 100644 --- a/pkg/consensus/consensus_test.go +++ b/pkg/consensus/consensus_test.go @@ -54,10 +54,10 @@ func initServiceNextConsensus(t *testing.T, newAcc *wallet.Account, offset uint3 // Transfer funds to new validator. w := io.NewBufBinWriter() emit.AppCallWithOperationAndArgs(w.BinWriter, bc.GoverningTokenHash(), "transfer", - acc.Contract.ScriptHash().BytesBE(), newPriv.GetScriptHash().BytesBE(), int64(native.NEOTotalSupply)) + acc.Contract.ScriptHash().BytesBE(), newPriv.GetScriptHash().BytesBE(), int64(native.NEOTotalSupply), nil) emit.Opcodes(w.BinWriter, opcode.ASSERT) emit.AppCallWithOperationAndArgs(w.BinWriter, bc.UtilityTokenHash(), "transfer", - acc.Contract.ScriptHash().BytesBE(), newPriv.GetScriptHash().BytesBE(), int64(1_000_000_000)) + acc.Contract.ScriptHash().BytesBE(), newPriv.GetScriptHash().BytesBE(), int64(1_000_000_000), nil) emit.Opcodes(w.BinWriter, opcode.ASSERT) require.NoError(t, w.Err) diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 689874354..f60944887 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -758,7 +758,7 @@ func (bc *Blockchain) runPersist(script []byte, block *block.Block, cache *dao.C } func (bc *Blockchain) handleNotification(note *state.NotificationEvent, d *dao.Cached, b *block.Block, h util.Uint256) { - if note.Name != "transfer" && note.Name != "Transfer" { + if note.Name != "Transfer" { return } arr, ok := note.Item.Value().([]stackitem.Item) diff --git a/pkg/core/blockchain_test.go b/pkg/core/blockchain_test.go index 15276ddcf..c287f7678 100644 --- a/pkg/core/blockchain_test.go +++ b/pkg/core/blockchain_test.go @@ -265,12 +265,12 @@ func TestVerifyTx(t *testing.T) { amount = 1_000_000_000 } emit.AppCallWithOperationAndArgs(w.BinWriter, sc, "transfer", - neoOwner, a.Contract.ScriptHash(), amount) + neoOwner, a.Contract.ScriptHash(), amount, nil) emit.Opcodes(w.BinWriter, opcode.ASSERT) } } emit.AppCallWithOperationAndArgs(w.BinWriter, gasHash, "transfer", - neoOwner, testchain.CommitteeScriptHash(), int64(1_000_000_000)) + neoOwner, testchain.CommitteeScriptHash(), int64(1_000_000_000), nil) emit.Opcodes(w.BinWriter, opcode.ASSERT) require.NoError(t, w.Err) diff --git a/pkg/core/helper_test.go b/pkg/core/helper_test.go index 4fd78e494..814815b12 100644 --- a/pkg/core/helper_test.go +++ b/pkg/core/helper_test.go @@ -244,7 +244,7 @@ func TestCreateBasicChain(t *testing.T) { require.NoError(t, err) // Push some contract into the chain. - txDeploy, avm := newDeployTx(t, prefix+"test_contract.go") + txDeploy, avm := newDeployTx(t, prefix+"test_contract.go", "Rubl") txDeploy.Nonce = getNextNonce() txDeploy.ValidUntilBlock = validUntilBlock txDeploy.Signers = []transaction.Signer{{Account: priv0ScriptHash}} @@ -332,7 +332,7 @@ func TestCreateBasicChain(t *testing.T) { t.Logf("sendRublesTx: %v", transferTx.Hash().StringLE()) // Push verification contract into the chain. - txDeploy2, _ := newDeployTx(t, prefix+"verification_contract.go") + txDeploy2, _ := newDeployTx(t, prefix+"verification_contract.go", "Verify") txDeploy2.Nonce = getNextNonce() txDeploy2.ValidUntilBlock = validUntilBlock txDeploy2.Signers = []transaction.Signer{{Account: priv0ScriptHash}} @@ -382,14 +382,14 @@ func TestCreateBasicChain(t *testing.T) { func newNEP5Transfer(sc, from, to util.Uint160, amount int64) *transaction.Transaction { w := io.NewBufBinWriter() - emit.AppCallWithOperationAndArgs(w.BinWriter, sc, "transfer", from, to, amount) + emit.AppCallWithOperationAndArgs(w.BinWriter, sc, "transfer", from, to, amount, nil) emit.Opcodes(w.BinWriter, opcode.ASSERT) script := w.Bytes() return transaction.New(testchain.Network(), script, 10000000) } -func newDeployTx(t *testing.T, name string) (*transaction.Transaction, []byte) { +func newDeployTx(t *testing.T, name, ctrName string) (*transaction.Transaction, []byte) { c, err := ioutil.ReadFile(name) require.NoError(t, err) avm, di, err := compiler.CompileWithDebugInfo(name, bytes.NewReader(c)) @@ -398,7 +398,7 @@ func newDeployTx(t *testing.T, name string) (*transaction.Transaction, []byte) { t.Logf("contractScript: %x", avm) script := io.NewBufBinWriter() - m, err := di.ConvertToManifest("Test", nil) + m, err := di.ConvertToManifest(ctrName, nil) require.NoError(t, err) bs, err := json.Marshal(m) require.NoError(t, err) diff --git a/pkg/core/interop_system.go b/pkg/core/interop_system.go index c24dcd974..31822df6a 100644 --- a/pkg/core/interop_system.go +++ b/pkg/core/interop_system.go @@ -299,16 +299,7 @@ func runtimeLog(ic *interop.Context) error { // runtimeGetTime returns timestamp of the block being verified, or the latest // one in the blockchain if no block is given to Context. func runtimeGetTime(ic *interop.Context) error { - var header *block.Header - if ic.Block == nil { - var err error - header, err = ic.Chain.GetHeader(ic.Chain.CurrentBlockHash()) - if err != nil { - return err - } - } else { - header = ic.Block.Header() - } + header := ic.Block.Header() ic.VM.Estack().PushVal(header.Timestamp) return nil } diff --git a/pkg/core/interop_system_test.go b/pkg/core/interop_system_test.go index a382afbda..f714b855f 100644 --- a/pkg/core/interop_system_test.go +++ b/pkg/core/interop_system_test.go @@ -408,6 +408,13 @@ func getTestContractState() (*state.Contract, *state.Contract) { emit.String(w.BinWriter, "initial") emit.Syscall(w.BinWriter, interopnames.SystemStorageGetContext) emit.Syscall(w.BinWriter, interopnames.SystemStorageGet) + emit.Opcodes(w.BinWriter, opcode.RET) + onPaymentOff := w.Len() + emit.Int(w.BinWriter, 3) + emit.Opcodes(w.BinWriter, opcode.PACK) + emit.String(w.BinWriter, "LastPayment") + emit.Syscall(w.BinWriter, interopnames.SystemRuntimeNotify) + emit.Opcodes(w.BinWriter, opcode.RET) script := w.Bytes() h := hash.Hash160(script) @@ -482,6 +489,16 @@ func getTestContractState() (*state.Contract, *state.Contract) { }, ReturnType: smartcontract.VoidType, }, + { + Name: "onPayment", + Offset: onPaymentOff, + Parameters: []manifest.Parameter{ + manifest.NewParameter("from", smartcontract.Hash160Type), + manifest.NewParameter("amount", smartcontract.IntegerType), + manifest.NewParameter("data", smartcontract.AnyType), + }, + ReturnType: smartcontract.VoidType, + }, } cs := &state.Contract{ Script: script, diff --git a/pkg/core/native/native_gas.go b/pkg/core/native/native_gas.go index 2913ed45e..baaed669f 100644 --- a/pkg/core/native/native_gas.go +++ b/pkg/core/native/native_gas.go @@ -13,7 +13,7 @@ import ( // GAS represents GAS native contract. type GAS struct { - nep5TokenNative + nep17TokenNative NEO *NEO } @@ -27,15 +27,15 @@ const initialGAS = 30000000 // newGAS returns GAS native contract. func newGAS() *GAS { g := &GAS{} - nep5 := newNEP5Native(gasName) - nep5.symbol = "gas" - nep5.decimals = 8 - nep5.factor = GASFactor - nep5.onPersist = chainOnPersist(nep5.OnPersist, g.OnPersist) - nep5.incBalance = g.increaseBalance - nep5.ContractID = gasContractID + nep17 := newNEP17Native(gasName) + nep17.symbol = "gas" + nep17.decimals = 8 + nep17.factor = GASFactor + nep17.onPersist = chainOnPersist(nep17.OnPersist, g.OnPersist) + nep17.incBalance = g.increaseBalance + nep17.ContractID = gasContractID - g.nep5TokenNative = *nep5 + g.nep17TokenNative = *nep17 onp := g.Methods["onPersist"] onp.Func = getOnPersistWrapper(g.onPersist) @@ -65,10 +65,10 @@ func (g *GAS) increaseBalance(_ *interop.Context, _ util.Uint160, si *state.Stor // Initialize initializes GAS contract. func (g *GAS) Initialize(ic *interop.Context) error { - if err := g.nep5TokenNative.Initialize(ic); err != nil { + if err := g.nep17TokenNative.Initialize(ic); err != nil { return err } - if g.nep5TokenNative.getTotalSupply(ic.DAO).Sign() != 0 { + if g.nep17TokenNative.getTotalSupply(ic.DAO).Sign() != 0 { return errors.New("already initialized") } h, err := getStandbyValidatorsHash(ic) diff --git a/pkg/core/native/native_neo.go b/pkg/core/native/native_neo.go index 05031838d..76004775b 100644 --- a/pkg/core/native/native_neo.go +++ b/pkg/core/native/native_neo.go @@ -28,7 +28,7 @@ import ( // NEO represents NEO native contract. type NEO struct { - nep5TokenNative + nep17TokenNative GAS *GAS // gasPerBlock represents current value of generated gas per block. @@ -93,16 +93,16 @@ func makeValidatorKey(key *keys.PublicKey) []byte { // newNEO returns NEO native contract. func newNEO() *NEO { n := &NEO{} - nep5 := newNEP5Native(neoName) - nep5.symbol = "neo" - nep5.decimals = 0 - nep5.factor = 1 - nep5.onPersist = chainOnPersist(nep5.OnPersist, n.OnPersist) - nep5.postPersist = chainOnPersist(nep5.postPersist, n.PostPersist) - nep5.incBalance = n.increaseBalance - nep5.ContractID = neoContractID + nep17 := newNEP17Native(neoName) + nep17.symbol = "neo" + nep17.decimals = 0 + nep17.factor = 1 + nep17.onPersist = chainOnPersist(nep17.OnPersist, n.OnPersist) + nep17.postPersist = chainOnPersist(nep17.postPersist, n.PostPersist) + nep17.incBalance = n.increaseBalance + nep17.ContractID = neoContractID - n.nep5TokenNative = *nep5 + n.nep17TokenNative = *nep17 n.votesChanged.Store(true) n.nextValidators.Store(keys.PublicKeys(nil)) n.validators.Store(keys.PublicKeys(nil)) @@ -165,11 +165,11 @@ func newNEO() *NEO { // Initialize initializes NEO contract. func (n *NEO) Initialize(ic *interop.Context) error { - if err := n.nep5TokenNative.Initialize(ic); err != nil { + if err := n.nep17TokenNative.Initialize(ic); err != nil { return err } - if n.nep5TokenNative.getTotalSupply(ic.DAO).Sign() != 0 { + if n.nep17TokenNative.getTotalSupply(ic.DAO).Sign() != 0 { return errors.New("already initialized") } diff --git a/pkg/core/native/native_nep5.go b/pkg/core/native/native_nep17.go similarity index 72% rename from pkg/core/native/native_nep5.go rename to pkg/core/native/native_nep17.go index 76ecb5d7c..bfc588171 100644 --- a/pkg/core/native/native_nep5.go +++ b/pkg/core/native/native_nep17.go @@ -7,6 +7,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/dao" "github.com/nspcc-dev/neo-go/pkg/core/interop" + "github.com/nspcc-dev/neo-go/pkg/core/interop/contract" "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" @@ -14,6 +15,7 @@ import ( "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" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) @@ -28,8 +30,8 @@ func makeAccountKey(h util.Uint160) []byte { return k } -// nep5TokenNative represents NEP-5 token contract. -type nep5TokenNative struct { +// nep17TokenNative represents NEP-17 token contract. +type nep17TokenNative struct { interop.ContractMD symbol string decimals int64 @@ -42,15 +44,15 @@ type nep5TokenNative struct { // totalSupplyKey is the key used to store totalSupply value. var totalSupplyKey = []byte{11} -func (c *nep5TokenNative) Metadata() *interop.ContractMD { +func (c *nep17TokenNative) Metadata() *interop.ContractMD { return &c.ContractMD } -var _ interop.Contract = (*nep5TokenNative)(nil) +var _ interop.Contract = (*nep17TokenNative)(nil) -func newNEP5Native(name string) *nep5TokenNative { - n := &nep5TokenNative{ContractMD: *interop.NewContractMD(name)} - n.Manifest.SupportedStandards = []string{manifest.NEP5StandardName} +func newNEP17Native(name string) *nep17TokenNative { + n := &nep17TokenNative{ContractMD: *interop.NewContractMD(name)} + n.Manifest.SupportedStandards = []string{manifest.NEP17StandardName} desc := newDescriptor("name", smartcontract.StringType) md := newMethodAndPrice(nameMethod(name), 0, smartcontract.NoneFlag) @@ -77,6 +79,7 @@ func newNEP5Native(name string) *nep5TokenNative { manifest.NewParameter("from", smartcontract.Hash160Type), manifest.NewParameter("to", smartcontract.Hash160Type), manifest.NewParameter("amount", smartcontract.IntegerType), + manifest.NewParameter("data", smartcontract.AnyType), ) md = newMethodAndPrice(n.Transfer, 8000000, smartcontract.AllowModifyStates) n.AddMethod(md, desc, false) @@ -94,23 +97,23 @@ func newNEP5Native(name string) *nep5TokenNative { return n } -func (c *nep5TokenNative) Initialize(_ *interop.Context) error { +func (c *nep17TokenNative) Initialize(_ *interop.Context) error { return nil } -func (c *nep5TokenNative) Symbol(_ *interop.Context, _ []stackitem.Item) stackitem.Item { +func (c *nep17TokenNative) Symbol(_ *interop.Context, _ []stackitem.Item) stackitem.Item { return stackitem.NewByteArray([]byte(c.symbol)) } -func (c *nep5TokenNative) Decimals(_ *interop.Context, _ []stackitem.Item) stackitem.Item { +func (c *nep17TokenNative) 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 { +func (c *nep17TokenNative) TotalSupply(ic *interop.Context, _ []stackitem.Item) stackitem.Item { return stackitem.NewBigInteger(c.getTotalSupply(ic.DAO)) } -func (c *nep5TokenNative) getTotalSupply(d dao.DAO) *big.Int { +func (c *nep17TokenNative) getTotalSupply(d dao.DAO) *big.Int { si := d.GetStorageItem(c.ContractID, totalSupplyKey) if si == nil { return big.NewInt(0) @@ -118,16 +121,16 @@ func (c *nep5TokenNative) getTotalSupply(d dao.DAO) *big.Int { return bigint.FromBytes(si.Value) } -func (c *nep5TokenNative) saveTotalSupply(d dao.DAO, supply *big.Int) error { +func (c *nep17TokenNative) saveTotalSupply(d dao.DAO, supply *big.Int) error { si := &state.StorageItem{Value: bigint.ToBytes(supply)} return d.PutStorageItem(c.ContractID, totalSupplyKey, si) } -func (c *nep5TokenNative) Transfer(ic *interop.Context, args []stackitem.Item) stackitem.Item { +func (c *nep17TokenNative) Transfer(ic *interop.Context, args []stackitem.Item) stackitem.Item { from := toUint160(args[0]) to := toUint160(args[1]) amount := toBigInt(args[2]) - err := c.TransferInternal(ic, from, to, amount) + err := c.TransferInternal(ic, from, to, amount, args[3]) return stackitem.NewBool(err == nil) } @@ -138,7 +141,31 @@ func addrToStackItem(u *util.Uint160) stackitem.Item { return stackitem.NewByteArray(u.BytesBE()) } -func (c *nep5TokenNative) emitTransfer(ic *interop.Context, from, to *util.Uint160, amount *big.Int) { +func (c *nep17TokenNative) postTransfer(ic *interop.Context, from, to *util.Uint160, amount *big.Int, data stackitem.Item) { + c.emitTransfer(ic, from, to, amount) + if to == nil { + return + } + cs, err := ic.DAO.GetContractState(*to) + 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, + } + if err := contract.CallExInternal(ic, cs, manifest.MethodOnPayment, args, smartcontract.All, vm.EnsureIsEmpty); err != nil { + panic(err) + } +} + +func (c *nep17TokenNative) emitTransfer(ic *interop.Context, from, to *util.Uint160, amount *big.Int) { ne := state.NotificationEvent{ ScriptHash: c.Hash, Name: "Transfer", @@ -151,7 +178,7 @@ func (c *nep5TokenNative) emitTransfer(ic *interop.Context, from, to *util.Uint1 ic.Notifications = append(ic.Notifications, ne) } -func (c *nep5TokenNative) updateAccBalance(ic *interop.Context, acc util.Uint160, amount *big.Int) error { +func (c *nep17TokenNative) updateAccBalance(ic *interop.Context, acc util.Uint160, amount *big.Int) error { key := makeAccountKey(acc) si := ic.DAO.GetStorageItem(c.ContractID, key) if si == nil { @@ -174,7 +201,7 @@ func (c *nep5TokenNative) updateAccBalance(ic *interop.Context, acc util.Uint160 } // TransferInternal transfers NEO between accounts. -func (c *nep5TokenNative) TransferInternal(ic *interop.Context, from, to util.Uint160, amount *big.Int) error { +func (c *nep17TokenNative) TransferInternal(ic *interop.Context, from, to util.Uint160, amount *big.Int, data stackitem.Item) error { if amount.Sign() == -1 { return errors.New("negative amount") } @@ -205,11 +232,11 @@ func (c *nep5TokenNative) TransferInternal(ic *interop.Context, from, to util.Ui } } - c.emitTransfer(ic, &from, &to, amount) + c.postTransfer(ic, &from, &to, amount, data) return nil } -func (c *nep5TokenNative) balanceOf(ic *interop.Context, args []stackitem.Item) stackitem.Item { +func (c *nep17TokenNative) balanceOf(ic *interop.Context, args []stackitem.Item) stackitem.Item { h := toUint160(args[0]) bs, err := ic.DAO.GetNEP5Balances(h) if err != nil { @@ -219,23 +246,23 @@ func (c *nep5TokenNative) balanceOf(ic *interop.Context, args []stackitem.Item) return stackitem.NewBigInteger(&balance) } -func (c *nep5TokenNative) mint(ic *interop.Context, h util.Uint160, amount *big.Int) { +func (c *nep17TokenNative) 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) + c.postTransfer(ic, nil, &h, amount, stackitem.Null{}) } -func (c *nep5TokenNative) burn(ic *interop.Context, h util.Uint160, amount *big.Int) { +func (c *nep17TokenNative) burn(ic *interop.Context, h util.Uint160, amount *big.Int) { if amount.Sign() == 0 { return } c.addTokens(ic, h, new(big.Int).Neg(amount)) - c.emitTransfer(ic, &h, nil, amount) + c.postTransfer(ic, &h, nil, amount, stackitem.Null{}) } -func (c *nep5TokenNative) addTokens(ic *interop.Context, h util.Uint160, amount *big.Int) { +func (c *nep17TokenNative) addTokens(ic *interop.Context, h util.Uint160, amount *big.Int) { if amount.Sign() == 0 { return } @@ -260,7 +287,7 @@ func (c *nep5TokenNative) addTokens(ic *interop.Context, h util.Uint160, amount } } -func (c *nep5TokenNative) OnPersist(ic *interop.Context) error { +func (c *nep17TokenNative) OnPersist(ic *interop.Context) error { if ic.Trigger != trigger.OnPersist { return errors.New("onPersist must be triggerred by system") } diff --git a/pkg/core/native/oracle.go b/pkg/core/native/oracle.go index 313a78805..d94f6a693 100644 --- a/pkg/core/native/oracle.go +++ b/pkg/core/native/oracle.go @@ -125,6 +125,13 @@ func newOracle() *Oracle { md = newMethodAndPrice(o.verify, 100_0000, smartcontract.NoneFlag) o.AddMethod(md, desc, false) + desc = newDescriptor("onPayment", smartcontract.VoidType, + manifest.NewParameter("from", smartcontract.Hash160Type), + manifest.NewParameter("amount", smartcontract.IntegerType), + manifest.NewParameter("data", smartcontract.AnyType)) + md = newMethodAndPrice(o.onPayment, 0, smartcontract.NoneFlag) + o.AddMethod(md, desc, false) + pp := chainOnPersist(postPersistBase, o.PostPersist) desc = newDescriptor("postPersist", smartcontract.VoidType) md = newMethodAndPrice(getOnPersistWrapper(pp), 0, smartcontract.AllowModifyStates) @@ -299,6 +306,7 @@ func (o *Oracle) RequestInternal(ic *interop.Context, url string, filter *string if !ic.VM.AddGas(gas.Int64()) { return ErrNotEnoughGas } + callingHash := ic.VM.GetCallingScriptHash() o.GAS.mint(ic, o.Hash, gas) si := ic.DAO.GetStorageItem(o.ContractID, prefixRequestID) id := binary.LittleEndian.Uint64(si.Value) + 1 @@ -344,7 +352,7 @@ func (o *Oracle) RequestInternal(ic *interop.Context, url string, filter *string GasForResponse: gas.Uint64(), URL: url, Filter: filter, - CallbackContract: ic.VM.GetCallingScriptHash(), + CallbackContract: callingHash, CallbackMethod: cb, UserData: data, } @@ -402,6 +410,14 @@ func (o *Oracle) verify(ic *interop.Context, _ []stackitem.Item) stackitem.Item return stackitem.NewBool(ic.Tx.HasAttribute(transaction.OracleResponseT)) } +func (o *Oracle) onPayment(ic *interop.Context, _ []stackitem.Item) stackitem.Item { + // FIXME when calling native transfer directly, context is not provided. + if h := ic.VM.GetCallingScriptHash(); h != o.Hash && h != o.GAS.Hash { + panic("only GAS can be accepted") + } + return stackitem.Null{} +} + func (o *Oracle) getOriginalTxID(d dao.DAO, tx *transaction.Transaction) util.Uint256 { for i := range tx.Attributes { if tx.Attributes[i].Type == transaction.OracleResponseT { diff --git a/pkg/core/native_neo_test.go b/pkg/core/native_neo_test.go index 4ae1d4b5e..977b514cc 100644 --- a/pkg/core/native_neo_test.go +++ b/pkg/core/native_neo_test.go @@ -16,6 +16,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/stretchr/testify/require" ) @@ -76,11 +77,11 @@ func TestNEO_Vote(t *testing.T) { w := io.NewBufBinWriter() emit.AppCallWithOperationAndArgs(w.BinWriter, bc.contracts.NEO.Hash, "transfer", neoOwner.BytesBE(), to.BytesBE(), - big.NewInt(int64(sz-i)*1000000).Int64()) + big.NewInt(int64(sz-i)*1000000).Int64(), nil) emit.Opcodes(w.BinWriter, opcode.ASSERT) emit.AppCallWithOperationAndArgs(w.BinWriter, bc.contracts.GAS.Hash, "transfer", neoOwner.BytesBE(), to.BytesBE(), - int64(1_000_000_000)) + int64(1_000_000_000), nil) emit.Opcodes(w.BinWriter, opcode.ASSERT) require.NoError(t, w.Err) tx := transaction.New(netmode.UnitTestNet, w.Bytes(), 1000_000_000) @@ -141,7 +142,7 @@ func TestNEO_Vote(t *testing.T) { gasBalance[i] = bc.GetUtilityTokenBalance(h) neoBalance[i], _ = bc.GetGoverningTokenBalance(h) emit.AppCallWithOperationAndArgs(w.BinWriter, bc.contracts.NEO.Hash, "transfer", - h.BytesBE(), h.BytesBE(), int64(1)) + h.BytesBE(), h.BytesBE(), int64(1), nil) emit.Opcodes(w.BinWriter, opcode.ASSERT) require.NoError(t, w.Err) tx := transaction.New(netmode.UnitTestNet, w.Bytes(), 0) @@ -303,3 +304,31 @@ func TestNEO_CommitteeBountyOnPersist(t *testing.T) { checkBalances() } } + +func TestNEO_TransferOnPayment(t *testing.T) { + bc := newTestChain(t) + defer bc.Close() + + cs, _ := getTestContractState() + require.NoError(t, bc.dao.PutContractState(cs)) + + const amount = 2 + tx := newNEP5Transfer(bc.contracts.NEO.Hash, neoOwner, cs.ScriptHash(), amount) + tx.SystemFee += 1_000_000 + tx.NetworkFee = 10_000_000 + tx.ValidUntilBlock = bc.BlockHeight() + 1 + addSigners(tx) + require.NoError(t, signTx(bc, tx)) + require.NoError(t, bc.AddBlock(bc.newBlock(tx))) + + aer, err := bc.GetAppExecResults(tx.Hash(), trigger.Application) + require.NoError(t, err) + require.Equal(t, vm.HaltState, aer[0].VMState) + require.Len(t, aer[0].Events, 3) // transfer + auto GAS claim + onPayment + + e := aer[0].Events[2] + require.Equal(t, "LastPayment", e.Name) + arr := e.Item.Value().([]stackitem.Item) + require.Equal(t, neoOwner.BytesBE(), arr[0].Value()) + require.Equal(t, big.NewInt(amount), arr[1].Value()) +} diff --git a/pkg/rpc/client/nep5.go b/pkg/rpc/client/nep5.go index ea8db995a..9c5a08cf1 100644 --- a/pkg/rpc/client/nep5.go +++ b/pkg/rpc/client/nep5.go @@ -37,20 +37,6 @@ func (c *Client) NEP5Decimals(tokenHash util.Uint160) (int64, error) { return topIntFromStack(result.Stack) } -// NEP5Name invokes `name` NEP5 method on a specified contract. -func (c *Client) NEP5Name(tokenHash util.Uint160) (string, error) { - result, err := c.InvokeFunction(tokenHash, "name", []smartcontract.Parameter{}, nil) - if err != nil { - return "", err - } - err = getInvocationError(result) - if err != nil { - return "", fmt.Errorf("failed to get NEP5 name: %w", err) - } - - return topStringFromStack(result.Stack) -} - // NEP5Symbol invokes `symbol` NEP5 method on a specified contract. func (c *Client) NEP5Symbol(tokenHash util.Uint160) (string, error) { result, err := c.InvokeFunction(tokenHash, "symbol", []smartcontract.Parameter{}, nil) @@ -98,7 +84,7 @@ func (c *Client) NEP5BalanceOf(tokenHash, acc util.Uint160) (int64, error) { // NEP5TokenInfo returns full NEP5 token info. func (c *Client) NEP5TokenInfo(tokenHash util.Uint160) (*wallet.Token, error) { - name, err := c.NEP5Name(tokenHash) + cs, err := c.GetContractStateByHash(tokenHash) if err != nil { return nil, err } @@ -110,7 +96,7 @@ func (c *Client) NEP5TokenInfo(tokenHash util.Uint160) (*wallet.Token, error) { if err != nil { return nil, err } - return wallet.NewToken(tokenHash, name, symbol, decimals), nil + return wallet.NewToken(tokenHash, cs.Manifest.Name, symbol, decimals), nil } // CreateNEP5TransferTx creates an invocation transaction for the 'transfer' @@ -135,7 +121,7 @@ func (c *Client) CreateNEP5MultiTransferTx(acc *wallet.Account, gas int64, recip w := io.NewBufBinWriter() for i := range recipients { emit.AppCallWithOperationAndArgs(w.BinWriter, recipients[i].Token, "transfer", from, - recipients[i].Address, recipients[i].Amount) + recipients[i].Address, recipients[i].Amount, nil) emit.Opcodes(w.BinWriter, opcode.ASSERT) } return c.CreateTxFromScript(w.Bytes(), acc, -1, gas, transaction.Signer{ diff --git a/pkg/rpc/server/client_test.go b/pkg/rpc/server/client_test.go index 711db62a5..07a3cf5ed 100644 --- a/pkg/rpc/server/client_test.go +++ b/pkg/rpc/server/client_test.go @@ -41,11 +41,6 @@ func TestClient_NEP5(t *testing.T) { require.NoError(t, err) require.EqualValues(t, 1_000_000, s) }) - t.Run("Name", func(t *testing.T) { - name, err := c.NEP5Name(h) - require.NoError(t, err) - require.Equal(t, "Rubl", name) - }) t.Run("Symbol", func(t *testing.T) { sym, err := c.NEP5Symbol(h) require.NoError(t, err) diff --git a/pkg/rpc/server/server.go b/pkg/rpc/server/server.go index 382e28079..fb958318f 100644 --- a/pkg/rpc/server/server.go +++ b/pkg/rpc/server/server.go @@ -1095,7 +1095,7 @@ func (s *Server) invokeFunction(reqParams request.Params) (interface{}, *respons return nil, response.NewInternalServerError("can't create invocation script", err) } tx.Script = script - return s.runScriptInVM(script, tx), nil + return s.runScriptInVM(script, tx) } // invokescript implements the `invokescript` RPC call. @@ -1121,16 +1121,27 @@ func (s *Server) invokescript(reqParams request.Params) (interface{}, *response. tx.Signers = []transaction.Signer{{Account: util.Uint160{}, Scopes: transaction.None}} } tx.Script = script - return s.runScriptInVM(script, tx), nil + return s.runScriptInVM(script, tx) } // runScriptInVM runs given script in a new test VM and returns the invocation // result. -func (s *Server) runScriptInVM(script []byte, tx *transaction.Transaction) *result.Invoke { - vm := s.chain.GetTestVM(tx, nil) +func (s *Server) runScriptInVM(script []byte, tx *transaction.Transaction) (*result.Invoke, *response.Error) { + // When transfering funds, script execution does no auto GAS claim, + // because it depends on persisting tx height. + // This is why we provide block here. + b := block.New(s.network, s.stateRootEnabled) + b.Index = s.chain.BlockHeight() + 1 + hdr, err := s.chain.GetHeader(s.chain.GetHeaderHash(int(s.chain.BlockHeight()))) + if err != nil { + return nil, response.NewInternalServerError("can't get last block", err) + } + b.Timestamp = hdr.Timestamp + uint64(s.chain.GetConfig().SecondsPerBlock*int(time.Second/time.Millisecond)) + + vm := s.chain.GetTestVM(tx, b) vm.GasLimit = int64(s.config.MaxGasInvoke) vm.LoadScriptWithFlags(script, smartcontract.All) - err := vm.Run() + err = vm.Run() var faultException string if err != nil { faultException = err.Error() @@ -1142,7 +1153,7 @@ func (s *Server) runScriptInVM(script []byte, tx *transaction.Transaction) *resu Stack: vm.Estack().ToArray(), FaultException: faultException, } - return result + return result, nil } // submitBlock broadcasts a raw block over the NEO network. diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index 4456efa06..2ddf37a25 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -56,8 +56,8 @@ type rpcTestCase struct { check func(t *testing.T, e *executor, result interface{}) } -const testContractHash = "b0fda4dd46b8e5d207e86e774a4a133c6db69ee7" -const deploymentTxHash = "59f7b22b90e26f883a56b916c1580e3ee4f13caded686353cd77577e6194c173" +const testContractHash = "55b692ecc09f240355e042c6c07e8f3fe57546b1" +const deploymentTxHash = "99e40e5d169eb9a2b6faebc6fc596c050cf3f8a70ad25de8f44309bc8ccbfbfb" const genesisBlockHash = "a496577895eb8c227bb866dc44f99f21c0cf06417ca8f2a877cc5d761a50dac0" const verifyContractHash = "c1213693b22cb0454a436d6e0bd76b8c0a3bfdf7" @@ -1345,7 +1345,7 @@ func checkNep5Balances(t *testing.T, e *executor, acc interface{}) { }, { Asset: e.chain.UtilityTokenHash(), - Amount: "80009641770", + Amount: "80009744770", LastUpdated: 7, }}, Address: testchain.PrivateKeyByID(0).GetScriptHash().StringLE(), diff --git a/pkg/rpc/server/testdata/test_contract.go b/pkg/rpc/server/testdata/test_contract.go index 38ccd62f0..60b7909a4 100644 --- a/pkg/rpc/server/testdata/test_contract.go +++ b/pkg/rpc/server/testdata/test_contract.go @@ -15,11 +15,11 @@ func Init() bool { h := runtime.GetExecutingScriptHash() amount := totalSupply storage.Put(ctx, h, amount) - runtime.Notify("transfer", []byte{}, h, amount) + runtime.Notify("Transfer", []byte{}, h, amount) return true } -func Transfer(from, to []byte, amount int) bool { +func Transfer(from, to []byte, amount int, data interface{}) bool { ctx := storage.GetContext() if len(from) != 20 { runtime.Log("invalid 'from' address") @@ -54,7 +54,7 @@ func Transfer(from, to []byte, amount int) bool { toBalance += amount storage.Put(ctx, to, toBalance) - runtime.Notify("transfer", from, to, amount) + runtime.Notify("Transfer", from, to, amount) return true } @@ -74,10 +74,6 @@ func BalanceOf(addr []byte) int { return amount } -func Name() string { - return "Rubl" -} - func Symbol() string { return "RUB" } diff --git a/pkg/rpc/server/testdata/testblocks.acc b/pkg/rpc/server/testdata/testblocks.acc index 722d3c9a8..1a29ce8cc 100644 Binary files a/pkg/rpc/server/testdata/testblocks.acc and b/pkg/rpc/server/testdata/testblocks.acc differ diff --git a/pkg/smartcontract/manifest/manifest.go b/pkg/smartcontract/manifest/manifest.go index fa72ea16b..89de8dbd4 100644 --- a/pkg/smartcontract/manifest/manifest.go +++ b/pkg/smartcontract/manifest/manifest.go @@ -20,10 +20,13 @@ const ( // MethodVerify is a name for default verification method. MethodVerify = "verify" - // NEP5StandardName represents the name of NEP5 smartcontract standard. - NEP5StandardName = "NEP-5" + // MethodOnPayment is name of the method which is called when contract receives funds. + MethodOnPayment = "onPayment" + // NEP10StandardName represents the name of NEP10 smartcontract standard. NEP10StandardName = "NEP-10" + // NEP17StandardName represents the name of NEP17 smartcontract standard. + NEP17StandardName = "NEP-17" ) // ABI represents a contract application binary interface.