diff --git a/cli/candidate_test.go b/cli/candidate_test.go index 3018754d9..262e05e5a 100644 --- a/cli/candidate_test.go +++ b/cli/candidate_test.go @@ -20,8 +20,8 @@ func TestRegisterCandidate(t *testing.T) { "--rpc-endpoint", "http://"+e.RPC.Addr, "--wallet", validatorWallet, "--from", validatorAddr, - "neo:"+validatorPriv.Address()+":10", - "gas:"+validatorPriv.Address()+":100") + "NEO:"+validatorPriv.Address()+":10", + "GAS:"+validatorPriv.Address()+":100") e.checkTxPersisted(t) e.In.WriteString("one\r") diff --git a/cli/multisig_test.go b/cli/multisig_test.go index 7694d0c73..e3a9fd304 100644 --- a/cli/multisig_test.go +++ b/cli/multisig_test.go @@ -55,8 +55,8 @@ func TestSignMultisigTx(t *testing.T) { "--rpc-endpoint", "http://"+e.RPC.Addr, "--wallet", validatorWallet, "--from", validatorAddr, - "neo:"+multisigAddr+":4", - "gas:"+multisigAddr+":1") + "NEO:"+multisigAddr+":4", + "GAS:"+multisigAddr+":1") e.checkTxPersisted(t) // Sign and transfer funds to another account. @@ -69,7 +69,7 @@ func TestSignMultisigTx(t *testing.T) { e.Run(t, "neo-go", "wallet", "nep17", "transfer", "--rpc-endpoint", "http://"+e.RPC.Addr, "--wallet", wallet1Path, "--from", multisigAddr, - "--to", priv.Address(), "--token", "neo", "--amount", "1", + "--to", priv.Address(), "--token", "NEO", "--amount", "1", "--out", txPath) e.In.WriteString("pass\r") diff --git a/cli/nep17_test.go b/cli/nep17_test.go index a5ca20494..35c60e253 100644 --- a/cli/nep17_test.go +++ b/cli/nep17_test.go @@ -9,6 +9,7 @@ import ( "strings" "testing" + "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/encoding/fixedn" "github.com/nspcc-dev/neo-go/pkg/wallet" @@ -28,13 +29,13 @@ func TestNEP17Balance(t *testing.T) { b, index := e.Chain.GetGoverningTokenBalance(validatorHash) checkResult := func(t *testing.T) { e.checkNextLine(t, "^\\s*Account\\s+"+validatorAddr) - e.checkNextLine(t, "^\\s*NEO:\\s+NEO \\("+e.Chain.GoverningTokenHash().StringLE()+"\\)") + e.checkNextLine(t, "^\\s*NEO:\\s+NeoToken \\("+e.Chain.GoverningTokenHash().StringLE()+"\\)") e.checkNextLine(t, "^\\s*Amount\\s*:\\s*"+b.String()) e.checkNextLine(t, "^\\s*Updated\\s*:\\s*"+strconv.FormatUint(uint64(index), 10)) e.checkEOF(t) } t.Run("Alias", func(t *testing.T) { - e.Run(t, append(cmd, "--token", "neo")...) + e.Run(t, append(cmd, "--token", "NEO")...) checkResult(t) }) t.Run("Hash", func(t *testing.T) { @@ -43,9 +44,9 @@ func TestNEP17Balance(t *testing.T) { }) }) t.Run("GAS", func(t *testing.T) { - e.Run(t, append(cmd, "--token", "gas")...) + e.Run(t, append(cmd, "--token", "GAS")...) e.checkNextLine(t, "^\\s*Account\\s+"+validatorAddr) - e.checkNextLine(t, "^\\s*GAS:\\s+GAS \\("+e.Chain.UtilityTokenHash().StringLE()+"\\)") + e.checkNextLine(t, "^\\s*GAS:\\s+GasToken \\("+e.Chain.UtilityTokenHash().StringLE()+"\\)") b := e.Chain.GetUtilityTokenBalance(validatorHash) e.checkNextLine(t, "^\\s*Amount\\s*:\\s*"+fixedn.Fixed8(b.Int64()).String()) }) @@ -54,7 +55,7 @@ func TestNEP17Balance(t *testing.T) { addr1, err := address.StringToUint160("NbTiM6h8r99kpRtb428XcsUk1TzKed2gTc") require.NoError(t, err) e.checkNextLine(t, "^Account "+address.Uint160ToString(addr1)) - e.checkNextLine(t, "^\\s*GAS:\\s+GAS \\("+e.Chain.UtilityTokenHash().StringLE()+"\\)") + e.checkNextLine(t, "^\\s*GAS:\\s+GasToken \\("+e.Chain.UtilityTokenHash().StringLE()+"\\)") balance := e.Chain.GetUtilityTokenBalance(addr1) e.checkNextLine(t, "^\\s*Amount\\s*:\\s*"+fixedn.Fixed8(balance.Int64()).String()) e.checkNextLine(t, "^\\s*Updated:") @@ -72,13 +73,13 @@ func TestNEP17Balance(t *testing.T) { for i := 0; i < 2; i++ { line := e.getNextLine(t) if strings.Contains(line, "GAS") { - e.checkLine(t, line, "^\\s*GAS:\\s+GAS \\("+e.Chain.UtilityTokenHash().StringLE()+"\\)") + e.checkLine(t, line, "^\\s*GAS:\\s+GasToken \\("+e.Chain.UtilityTokenHash().StringLE()+"\\)") balance = e.Chain.GetUtilityTokenBalance(addr3) e.checkNextLine(t, "^\\s*Amount\\s*:\\s*"+fixedn.Fixed8(balance.Int64()).String()) e.checkNextLine(t, "^\\s*Updated:") } else { balance, index := e.Chain.GetGoverningTokenBalance(validatorHash) - e.checkLine(t, line, "^\\s*NEO:\\s+NEO \\("+e.Chain.GoverningTokenHash().StringLE()+"\\)") + e.checkLine(t, line, "^\\s*NEO:\\s+NeoToken \\("+e.Chain.GoverningTokenHash().StringLE()+"\\)") e.checkNextLine(t, "^\\s*Amount\\s*:\\s*"+balance.String()) e.checkNextLine(t, "^\\s*Updated\\s*:\\s*"+strconv.FormatUint(uint64(index), 10)) } @@ -112,7 +113,7 @@ func TestNEP17Transfer(t *testing.T) { "--wallet", validatorWallet, "--from", validatorAddr, "--to", w.Accounts[0].Address, - "--token", "neo", + "--token", "NEO", "--amount", "1", } @@ -137,14 +138,14 @@ func TestNEP17MultiTransfer(t *testing.T) { e := newExecutor(t, true) defer e.Close(t) - neoContractHash, err := e.Chain.GetNativeContractScriptHash("neo") + neoContractHash, err := e.Chain.GetNativeContractScriptHash(nativenames.Neo) require.NoError(t, err) args := []string{ "neo-go", "wallet", "nep17", "multitransfer", "--rpc-endpoint", "http://" + e.RPC.Addr, "--wallet", validatorWallet, "--from", validatorAddr, - "neo:" + privs[0].Address() + ":42", + "NEO:" + privs[0].Address() + ":42", "GAS:" + privs[1].Address() + ":7", neoContractHash.StringLE() + ":" + privs[2].Address() + ":13", } @@ -169,9 +170,9 @@ func TestNEP17ImportToken(t *testing.T) { walletPath := path.Join(tmpDir, "walletForImport.json") defer os.Remove(walletPath) - neoContractHash, err := e.Chain.GetNativeContractScriptHash("neo") + neoContractHash, err := e.Chain.GetNativeContractScriptHash(nativenames.Neo) require.NoError(t, err) - gasContractHash, err := e.Chain.GetNativeContractScriptHash("gas") + gasContractHash, err := e.Chain.GetNativeContractScriptHash(nativenames.Gas) require.NoError(t, err) e.Run(t, "neo-go", "wallet", "init", "--wallet", walletPath) e.Run(t, "neo-go", "wallet", "nep17", "import", @@ -185,8 +186,8 @@ func TestNEP17ImportToken(t *testing.T) { t.Run("Info", func(t *testing.T) { checkGASInfo := func(t *testing.T) { - e.checkNextLine(t, "^Name:\\s*GAS") - e.checkNextLine(t, "^Symbol:\\s*gas") + e.checkNextLine(t, "^Name:\\s*GasToken") + e.checkNextLine(t, "^Symbol:\\s*GAS") e.checkNextLine(t, "^Hash:\\s*"+gasContractHash.StringLE()) e.checkNextLine(t, "^Decimals:\\s*8") e.checkNextLine(t, "^Address:\\s*"+address.Uint160ToString(gasContractHash)) @@ -202,8 +203,8 @@ func TestNEP17ImportToken(t *testing.T) { checkGASInfo(t) _, err := e.Out.ReadString('\n') require.NoError(t, err) - e.checkNextLine(t, "^Name:\\s*NEO") - e.checkNextLine(t, "^Symbol:\\s*neo") + e.checkNextLine(t, "^Name:\\s*NeoToken") + e.checkNextLine(t, "^Symbol:\\s*NEO") e.checkNextLine(t, "^Hash:\\s*"+neoContractHash.StringLE()) e.checkNextLine(t, "^Decimals:\\s*0") e.checkNextLine(t, "^Address:\\s*"+address.Uint160ToString(neoContractHash)) diff --git a/cli/smartcontract/smart_contract.go b/cli/smartcontract/smart_contract.go index dc9205953..bb130466c 100644 --- a/cli/smartcontract/smart_contract.go +++ b/cli/smartcontract/smart_contract.go @@ -16,17 +16,19 @@ import ( "github.com/nspcc-dev/neo-go/cli/options" "github.com/nspcc-dev/neo-go/cli/paramcontext" "github.com/nspcc-dev/neo-go/pkg/compiler" + "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/encoding/fixedn" - "github.com/nspcc-dev/neo-go/pkg/rpc/request" + "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/rpc/response/result" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/smartcontract/nef" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm" + "github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/urfave/cli" "gopkg.in/yaml.v2" @@ -770,10 +772,16 @@ func contractDeploy(ctx *cli.Context) error { return err } - txScript, err := request.CreateDeploymentScript(&nefFile, m) + mgmtHash, err := c.GetNativeContractHash(nativenames.Management) if err != nil { - return cli.NewExitError(fmt.Errorf("failed to create deployment script: %w", err), 1) + return cli.NewExitError(fmt.Errorf("failed to get management contract's hash: %w", err), 1) } + buf := io.NewBufBinWriter() + emit.AppCallWithOperationAndArgs(buf.BinWriter, mgmtHash, "deploy", f, manifestBytes) + if buf.Err != nil { + return cli.NewExitError(fmt.Errorf("failed to create deployment script: %w", buf.Err), 1) + } + txScript := buf.Bytes() // It doesn't require any signers. invRes, err := c.InvokeScript(txScript, nil) if err == nil && invRes.FaultException != "" { diff --git a/cli/testdata/chain50x2.acc b/cli/testdata/chain50x2.acc index 6c10dabc4..c8f6d3982 100644 Binary files a/cli/testdata/chain50x2.acc and b/cli/testdata/chain50x2.acc differ diff --git a/cli/testdata/deploy/main.go b/cli/testdata/deploy/main.go index 6182751c9..525d932d9 100644 --- a/cli/testdata/deploy/main.go +++ b/cli/testdata/deploy/main.go @@ -2,18 +2,28 @@ package deploy import ( "github.com/nspcc-dev/neo-go/cli/testdata/deploy/sub" + "github.com/nspcc-dev/neo-go/pkg/interop" "github.com/nspcc-dev/neo-go/pkg/interop/contract" + "github.com/nspcc-dev/neo-go/pkg/interop/runtime" "github.com/nspcc-dev/neo-go/pkg/interop/storage" ) var key = "key" +const mgmtKey = "mgmt" + func _deploy(isUpdate bool) { + var value string + ctx := storage.GetContext() - value := "on create" if isUpdate { value = "on update" + } else { + value = "on create" + sh := runtime.GetCallingScriptHash() + storage.Put(ctx, mgmtKey, sh) } + storage.Put(ctx, key, value) } @@ -24,7 +34,9 @@ func Fail() { // Update updates contract with the new one. func Update(script, manifest []byte) { - contract.Update(script, manifest) + ctx := storage.GetReadOnlyContext() + mgmt := storage.Get(ctx, mgmtKey).(interop.Hash160) + contract.Call(mgmt, "update", script, manifest) } // GetValue returns stored value. diff --git a/cli/wallet/nep17.go b/cli/wallet/nep17.go index dd9006bd8..80269f06e 100644 --- a/cli/wallet/nep17.go +++ b/cli/wallet/nep17.go @@ -197,7 +197,7 @@ func getNEP17Balance(ctx *cli.Context) error { } tokenSymbol = "UNKNOWN" } - fmt.Fprintf(ctx.App.Writer, "%s: %s (%s)\n", strings.ToUpper(tokenSymbol), tokenName, asset.StringLE()) + fmt.Fprintf(ctx.App.Writer, "%s: %s (%s)\n", tokenSymbol, tokenName, asset.StringLE()) fmt.Fprintf(ctx.App.Writer, "\tAmount : %s\n", balances.Balances[i].Amount) fmt.Fprintf(ctx.App.Writer, "\tUpdated: %d\n", balances.Balances[i].LastUpdated) } @@ -228,7 +228,7 @@ func getMatchingTokenAux(ctx *cli.Context, get func(i int) *wallet.Token, n int, var count int for i := 0; i < n; i++ { t := get(i) - if t != nil && (t.Name == name || t.Symbol == name || t.Address() == name || t.Hash.StringLE() == name) { + if t != nil && (t.Hash.StringLE() == name || t.Address() == name || t.Symbol == name || t.Name == name) { if count == 1 { printTokenInfo(ctx, token) printTokenInfo(ctx, t) diff --git a/cli/wallet/validator.go b/cli/wallet/validator.go index 3efd6c4ec..3d8a337a0 100644 --- a/cli/wallet/validator.go +++ b/cli/wallet/validator.go @@ -6,6 +6,7 @@ import ( "github.com/nspcc-dev/neo-go/cli/flags" "github.com/nspcc-dev/neo-go/cli/input" "github.com/nspcc-dev/neo-go/cli/options" + "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/encoding/address" @@ -99,7 +100,7 @@ func handleCandidate(ctx *cli.Context, method string) error { } gas := flags.Fixed8FromContext(ctx, "gas") - neoContractHash, err := c.GetNativeContractHash("neo") + neoContractHash, err := c.GetNativeContractHash(nativenames.Neo) if err != nil { return err } @@ -161,7 +162,7 @@ func handleVote(ctx *cli.Context) error { } gas := flags.Fixed8FromContext(ctx, "gas") - neoContractHash, err := c.GetNativeContractHash("neo") + neoContractHash, err := c.GetNativeContractHash(nativenames.Neo) if err != nil { return cli.NewExitError(err, 1) } diff --git a/cli/wallet/wallet.go b/cli/wallet/wallet.go index 31ce122d4..5b593f678 100644 --- a/cli/wallet/wallet.go +++ b/cli/wallet/wallet.go @@ -10,6 +10,7 @@ import ( "github.com/nspcc-dev/neo-go/cli/flags" "github.com/nspcc-dev/neo-go/cli/input" "github.com/nspcc-dev/neo-go/cli/options" + "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" @@ -233,7 +234,7 @@ func claimGas(ctx *cli.Context) error { return cli.NewExitError(err, 1) } - neoContractHash, err := c.GetNativeContractHash("neo") + neoContractHash, err := c.GetNativeContractHash(nativenames.Neo) if err != nil { return cli.NewExitError(err, 1) } diff --git a/cli/wallet_test.go b/cli/wallet_test.go index 986adf4ac..6fbb2a9cc 100644 --- a/cli/wallet_test.go +++ b/cli/wallet_test.go @@ -182,8 +182,8 @@ func TestClaimGas(t *testing.T) { "--rpc-endpoint", "http://" + e.RPC.Addr, "--wallet", validatorWallet, "--from", validatorAddr, - "neo:" + w.Accounts[0].Address + ":1000", - "gas:" + w.Accounts[0].Address + ":1000", // for tx send + "NEO:" + w.Accounts[0].Address + ":1000", + "GAS:" + w.Accounts[0].Address + ":1000", // for tx send } e.In.WriteString("one\r") e.Run(t, args...) @@ -261,8 +261,8 @@ func TestImportDeployed(t *testing.T) { e.Run(t, "neo-go", "wallet", "nep17", "multitransfer", "--rpc-endpoint", "http://"+e.RPC.Addr, "--wallet", validatorWallet, "--from", validatorAddr, - "neo:"+contractAddr+":10", - "gas:"+contractAddr+":10") + "NEO:"+contractAddr+":10", + "GAS:"+contractAddr+":10") e.checkTxPersisted(t) privTo, err := keys.NewPrivateKey() @@ -272,7 +272,7 @@ func TestImportDeployed(t *testing.T) { e.Run(t, "neo-go", "wallet", "nep17", "transfer", "--rpc-endpoint", "http://"+e.RPC.Addr, "--wallet", walletPath, "--from", contractAddr, - "--to", privTo.Address(), "--token", "neo", "--amount", "1") + "--to", privTo.Address(), "--token", "NEO", "--amount", "1") e.checkTxPersisted(t) b, _ := e.Chain.GetGoverningTokenBalance(h) diff --git a/examples/timer/timer.go b/examples/timer/timer.go index 0b0839df2..1d58a20fe 100644 --- a/examples/timer/timer.go +++ b/examples/timer/timer.go @@ -1,6 +1,7 @@ package timer import ( + "github.com/nspcc-dev/neo-go/pkg/interop" "github.com/nspcc-dev/neo-go/pkg/interop/binary" "github.com/nspcc-dev/neo-go/pkg/interop/contract" "github.com/nspcc-dev/neo-go/pkg/interop/runtime" @@ -9,6 +10,7 @@ import ( ) const defaultTicks = 3 +const mgmtKey = "mgmt" var ( // ctx holds storage context for contract methods @@ -30,6 +32,8 @@ func _deploy(isUpdate bool) { runtime.Log("One more tick is added.") return } + sh := runtime.GetCallingScriptHash() + storage.Put(ctx, mgmtKey, sh) storage.Put(ctx, ticksKey, defaultTicks) i := binary.Itoa(defaultTicks, 10) runtime.Log("Timer set to " + i + " ticks.") @@ -41,7 +45,8 @@ func Migrate(script []byte, manifest []byte) bool { runtime.Log("Only owner is allowed to update the contract.") return false } - contract.Update(script, manifest) + mgmt := storage.Get(ctx, mgmtKey).(interop.Hash160) + contract.Call(mgmt, "update", script, manifest) runtime.Log("Contract updated.") return true } @@ -67,7 +72,8 @@ func SelfDestroy() bool { runtime.Log("Only owner or the contract itself are allowed to destroy the contract.") return false } - contract.Destroy() + mgmt := storage.Get(ctx, mgmtKey).(interop.Hash160) + contract.Call(mgmt, "destroy") runtime.Log("Destroyed.") return true } diff --git a/internal/testchain/transaction.go b/internal/testchain/transaction.go index 10e71529e..044f82d82 100644 --- a/internal/testchain/transaction.go +++ b/internal/testchain/transaction.go @@ -1,6 +1,7 @@ package testchain import ( + "encoding/json" gio "io" "github.com/nspcc-dev/neo-go/pkg/compiler" @@ -12,7 +13,6 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/io" - "github.com/nspcc-dev/neo-go/pkg/rpc/request" "github.com/nspcc-dev/neo-go/pkg/smartcontract/nef" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/emit" @@ -48,7 +48,7 @@ func NewTransferFromOwner(bc blockchainer.Blockchainer, contractHash, to util.Ui } // NewDeployTx returns new deployment tx for contract with name with Go code read from r. -func NewDeployTx(name string, sender util.Uint160, r gio.Reader) (*transaction.Transaction, util.Uint160, error) { +func NewDeployTx(bc blockchainer.Blockchainer, name string, sender util.Uint160, r gio.Reader) (*transaction.Transaction, util.Uint160, error) { // nef.NewFile() cares about version a lot. config.Version = "0.90.0-test" @@ -67,12 +67,21 @@ func NewDeployTx(name string, sender util.Uint160, r gio.Reader) (*transaction.T return nil, util.Uint160{}, err } - txScript, err := request.CreateDeploymentScript(ne, m) + rawManifest, err := json.Marshal(m) if err != nil { return nil, util.Uint160{}, err } + neb, err := ne.Bytes() + if err != nil { + return nil, util.Uint160{}, err + } + buf := io.NewBufBinWriter() + emit.AppCallWithOperationAndArgs(buf.BinWriter, bc.ManagementContractHash(), "deploy", neb, rawManifest) + if buf.Err != nil { + return nil, util.Uint160{}, buf.Err + } - tx := transaction.New(Network(), txScript, 100*native.GASFactor) + tx := transaction.New(Network(), buf.Bytes(), 100*native.GASFactor) tx.Signers = []transaction.Signer{{Account: sender}} h := state.CreateContractHash(tx.Sender(), avm) diff --git a/pkg/compiler/debug_test.go b/pkg/compiler/debug_test.go index fcced5851..506142f6d 100644 --- a/pkg/compiler/debug_test.go +++ b/pkg/compiler/debug_test.go @@ -17,7 +17,6 @@ func TestCodeGen_DebugInfo(t *testing.T) { import "github.com/nspcc-dev/neo-go/pkg/interop" import "github.com/nspcc-dev/neo-go/pkg/interop/storage" import "github.com/nspcc-dev/neo-go/pkg/interop/blockchain" - import "github.com/nspcc-dev/neo-go/pkg/interop/contract" func Main(op string) bool { var s string _ = s @@ -47,7 +46,7 @@ func MethodStruct() struct{} { return struct{}{} } func unexportedMethod() int { return 1 } func MethodParams(addr interop.Hash160, h interop.Hash256, sig interop.Signature, pub interop.PublicKey, - inter interop.Interface, ctr contract.Contract, + inter interop.Interface, ctx storage.Context, tx blockchain.Transaction) bool { return true } @@ -238,7 +237,6 @@ func _deploy(isUpdate bool) {} manifest.NewParameter("sig", smartcontract.SignatureType), manifest.NewParameter("pub", smartcontract.PublicKeyType), manifest.NewParameter("inter", smartcontract.InteropInterfaceType), - manifest.NewParameter("ctr", smartcontract.ArrayType), manifest.NewParameter("ctx", smartcontract.InteropInterfaceType), manifest.NewParameter("tx", smartcontract.ArrayType), }, diff --git a/pkg/compiler/interop_test.go b/pkg/compiler/interop_test.go index eb81b717b..ee618b999 100644 --- a/pkg/compiler/interop_test.go +++ b/pkg/compiler/interop_test.go @@ -1,6 +1,7 @@ package compiler_test import ( + "errors" "fmt" "math/big" "strings" @@ -18,6 +19,7 @@ import ( cinterop "github.com/nspcc-dev/neo-go/pkg/interop" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "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/opcode" "github.com/stretchr/testify/require" @@ -132,12 +134,6 @@ func TestAppCall(t *testing.T) { require.NoError(t, err) barH := hash.Hash160(barCtr) - ic := interop.NewContext(trigger.Application, nil, dao.NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, false), nil, nil, nil, zaptest.NewLogger(t)) - require.NoError(t, ic.DAO.PutContractState(&state.Contract{ - Hash: barH, - Script: barCtr, - Manifest: *mBar, - })) srcInner := `package foo import "github.com/nspcc-dev/neo-go/pkg/interop/contract" @@ -164,11 +160,24 @@ func TestAppCall(t *testing.T) { require.NoError(t, err) ih := hash.Hash160(inner) - require.NoError(t, ic.DAO.PutContractState(&state.Contract{ - Hash: ih, - Script: inner, - Manifest: *m, - })) + var contractGetter = func(_ dao.DAO, h util.Uint160) (*state.Contract, error) { + if h.Equals(ih) { + return &state.Contract{ + Hash: ih, + Script: inner, + Manifest: *m, + }, nil + } else if h.Equals(barH) { + return &state.Contract{ + Hash: barH, + Script: barCtr, + Manifest: *mBar, + }, nil + } + return nil, errors.New("not found") + } + + ic := interop.NewContext(trigger.Application, nil, dao.NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, false), contractGetter, nil, nil, nil, zaptest.NewLogger(t)) t.Run("valid script", func(t *testing.T) { src := getAppCallScript(fmt.Sprintf("%#v", ih.BytesBE())) diff --git a/pkg/compiler/syscall.go b/pkg/compiler/syscall.go index 964564582..abc5bebe0 100644 --- a/pkg/compiler/syscall.go +++ b/pkg/compiler/syscall.go @@ -16,7 +16,6 @@ var syscalls = map[string]map[string]string{ }, "blockchain": { "GetBlock": interopnames.SystemBlockchainGetBlock, - "GetContract": interopnames.SystemBlockchainGetContract, "GetHeight": interopnames.SystemBlockchainGetHeight, "GetTransaction": interopnames.SystemBlockchainGetTransaction, "GetTransactionFromBlock": interopnames.SystemBlockchainGetTransactionFromBlock, @@ -25,12 +24,9 @@ var syscalls = map[string]map[string]string{ "contract": { "Call": interopnames.SystemContractCall, "CallEx": interopnames.SystemContractCallEx, - "Create": interopnames.SystemContractCreate, "CreateStandardAccount": interopnames.SystemContractCreateStandardAccount, - "Destroy": interopnames.SystemContractDestroy, "IsStandard": interopnames.SystemContractIsStandard, "GetCallFlags": interopnames.SystemContractGetCallFlags, - "Update": interopnames.SystemContractUpdate, }, "crypto": { "ECDsaSecp256k1Verify": interopnames.NeoCryptoVerifyWithECDsaSecp256k1, diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index a762830f1..443f8609c 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -600,18 +600,16 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error } writeBuf.Reset() - if block.Index > 0 { - aer, err := bc.runPersist(bc.contracts.GetPersistScript(), block, cache, trigger.OnPersist) - if err != nil { - return fmt.Errorf("onPersist failed: %w", err) - } - appExecResults = append(appExecResults, aer) - err = cache.PutAppExecResult(aer, writeBuf) - if err != nil { - return fmt.Errorf("failed to store onPersist exec result: %w", err) - } - writeBuf.Reset() + aer, err := bc.runPersist(bc.contracts.GetPersistScript(), block, cache, trigger.OnPersist) + if err != nil { + return fmt.Errorf("onPersist failed: %w", err) } + appExecResults = append(appExecResults, aer) + err = cache.PutAppExecResult(aer, writeBuf) + if err != nil { + return fmt.Errorf("failed to store onPersist exec result: %w", err) + } + writeBuf.Reset() for _, tx := range block.Transactions { if err := cache.StoreAsTransaction(tx, block.Index, writeBuf); err != nil { @@ -673,7 +671,7 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error } } - aer, err := bc.runPersist(bc.contracts.GetPostPersistScript(), block, cache, trigger.PostPersist) + aer, err = bc.runPersist(bc.contracts.GetPostPersistScript(), block, cache, trigger.PostPersist) if err != nil { return fmt.Errorf("postPersist failed: %w", err) } @@ -726,21 +724,6 @@ func (bc *Blockchain) storeBlock(block *block.Block, txpool *mempool.Pool) error bc.lock.Unlock() return err } - if err := bc.contracts.Policy.OnPersistEnd(bc.dao); err != nil { - bc.lock.Unlock() - return fmt.Errorf("failed to call OnPersistEnd for Policy native contract: %w", err) - } - if bc.P2PSigExtensionsEnabled() { - err := bc.contracts.Notary.OnPersistEnd(bc.dao) - if err != nil { - bc.lock.Unlock() - return fmt.Errorf("failed to call OnPersistEnd for Notary native contract: %w", err) - } - } - if err := bc.contracts.Designate.OnPersistEnd(bc.dao); err != nil { - bc.lock.Unlock() - return err - } bc.dao.MPT.Flush() // Every persist cycle we also compact our in-memory MPT. persistedHeight := atomic.LoadUint32(&bc.persistedHeight) @@ -843,7 +826,7 @@ func (bc *Blockchain) processNEP17Transfer(cache *dao.Cached, h util.Uint256, b if nativeContract != nil { id = nativeContract.Metadata().ContractID } else { - assetContract, err := cache.GetContractState(sc) + assetContract, err := bc.contracts.Management.GetContract(cache, sc) if err != nil { return } @@ -1143,7 +1126,7 @@ func (bc *Blockchain) HeaderHeight() uint32 { // GetContractState returns contract by its script hash. func (bc *Blockchain) GetContractState(hash util.Uint160) *state.Contract { - contract, err := bc.dao.GetContractState(hash) + contract, err := bc.contracts.Management.GetContract(bc.dao, hash) if contract == nil && err != storage.ErrKeyNotFound { bc.log.Warn("failed to get contract state", zap.Error(err)) } @@ -1665,7 +1648,7 @@ func (bc *Blockchain) initVerificationVM(ic *interop.Context, hash util.Uint160, } v.LoadScriptWithFlags(witness.VerificationScript, smartcontract.NoneFlag) } else { - cs, err := ic.DAO.GetContractState(hash) + cs, err := ic.GetContract(hash) if err != nil { return ErrUnknownVerificationContract } @@ -1674,10 +1657,10 @@ func (bc *Blockchain) initVerificationVM(ic *interop.Context, hash util.Uint160, return ErrInvalidVerificationContract } initMD := cs.Manifest.ABI.GetMethod(manifest.MethodInit) - v.LoadScriptWithHash(cs.Script, hash, smartcontract.ReadStates|smartcontract.AllowCall) + v.LoadScriptWithHash(cs.Script, hash, smartcontract.ReadStates) v.Jump(v.Context(), md.Offset) - if cs.ID < 0 { + if cs.ID <= 0 { w := io.NewBufBinWriter() emit.Opcodes(w.BinWriter, opcode.DEPTH, opcode.PACK) emit.String(w.BinWriter, manifest.MethodVerify) @@ -1788,6 +1771,11 @@ func (bc *Blockchain) UtilityTokenHash() util.Uint160 { return bc.contracts.GAS.Hash } +// ManagementContractHash returns management contract's hash. +func (bc *Blockchain) ManagementContractHash() util.Uint160 { + return bc.contracts.Management.Hash +} + func hashAndIndexToBytes(h util.Uint256, index uint32) []byte { buf := io.NewBufBinWriter() buf.WriteBytes(h.BytesLE()) @@ -1796,7 +1784,7 @@ func hashAndIndexToBytes(h util.Uint256, index uint32) []byte { } func (bc *Blockchain) newInteropContext(trigger trigger.Type, d dao.DAO, block *block.Block, tx *transaction.Transaction) *interop.Context { - ic := interop.NewContext(trigger, bc, d, bc.contracts.Contracts, block, tx, bc.log) + ic := interop.NewContext(trigger, bc, d, bc.contracts.Management.GetContract, bc.contracts.Contracts, block, tx, bc.log) ic.Functions = [][]interop.Function{systemInterops, neoInterops} switch { case tx != nil: diff --git a/pkg/core/blockchain_test.go b/pkg/core/blockchain_test.go index d63e0a803..d8b892ffb 100644 --- a/pkg/core/blockchain_test.go +++ b/pkg/core/blockchain_test.go @@ -540,7 +540,6 @@ func TestVerifyTx(t *testing.T) { ic.SpawnVM() ic.VM.LoadScript([]byte{byte(opcode.RET)}) require.NoError(t, bc.contracts.Designate.DesignateAsRole(ic, native.RoleOracle, oraclePubs)) - require.NoError(t, bc.contracts.Designate.OnPersistEnd(ic.DAO)) _, err = ic.DAO.Persist() require.NoError(t, err) @@ -713,11 +712,18 @@ func TestVerifyTx(t *testing.T) { require.True(t, errors.Is(bc.VerifyTx(tx), ErrHasConflicts)) }) t.Run("attribute on-chain conflict", func(t *testing.T) { - b, err := bc.GetBlock(bc.GetHeaderHash(0)) - require.NoError(t, err) - conflictsHash := b.Transactions[0].Hash() - tx := getConflictsTx(conflictsHash) - require.Error(t, bc.VerifyTx(tx)) + tx := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 0) + tx.ValidUntilBlock = 4242 + tx.Signers = []transaction.Signer{{ + Account: testchain.MultisigScriptHash(), + Scopes: transaction.None, + }} + require.NoError(t, testchain.SignTx(bc, tx)) + b := bc.newBlock(tx) + + require.NoError(t, bc.AddBlock(b)) + txConflict := getConflictsTx(tx.Hash()) + require.Error(t, bc.VerifyTx(txConflict)) }) t.Run("positive", func(t *testing.T) { tx := getConflictsTx(random.Uint256()) @@ -740,7 +746,6 @@ func TestVerifyTx(t *testing.T) { ic.SpawnVM() ic.VM.LoadScript([]byte{byte(opcode.RET)}) require.NoError(t, bc.contracts.Designate.DesignateAsRole(ic, native.RoleP2PNotary, keys.PublicKeys{notary.PrivateKey().PublicKey()})) - require.NoError(t, bc.contracts.Designate.OnPersistEnd(ic.DAO)) _, err = ic.DAO.Persist() require.NoError(t, err) getNotaryAssistedTx := func(signaturesCount uint8, serviceFee int64) *transaction.Transaction { @@ -1023,10 +1028,10 @@ func TestVerifyHashAgainstScript(t *testing.T) { bc := newTestChain(t) defer bc.Close() - cs, csInvalid := getTestContractState() + cs, csInvalid := getTestContractState(bc) ic := bc.newInteropContext(trigger.Verification, bc.dao, nil, nil) - require.NoError(t, ic.DAO.PutContractState(cs)) - require.NoError(t, ic.DAO.PutContractState(csInvalid)) + require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs)) + require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, csInvalid)) gas := bc.contracts.Policy.GetMaxVerificationGas(ic.DAO) t.Run("Contract", func(t *testing.T) { @@ -1162,7 +1167,7 @@ func TestIsTxStillRelevant(t *testing.T) { currentHeight := blockchain.GetHeight() return currentHeight < %d }`, bc.BlockHeight()+2) // deploy + next block - txDeploy, h, err := testchain.NewDeployTx("TestVerify", neoOwner, strings.NewReader(src)) + txDeploy, h, err := testchain.NewDeployTx(bc, "TestVerify", neoOwner, strings.NewReader(src)) require.NoError(t, err) txDeploy.ValidUntilBlock = bc.BlockHeight() + 1 addSigners(txDeploy) diff --git a/pkg/core/blockchainer/blockchainer.go b/pkg/core/blockchainer/blockchainer.go index bcbc2c780..0bbd90fe8 100644 --- a/pkg/core/blockchainer/blockchainer.go +++ b/pkg/core/blockchainer/blockchainer.go @@ -58,6 +58,7 @@ type Blockchainer interface { GetTestVM(tx *transaction.Transaction, b *block.Block) *vm.VM GetTransaction(util.Uint256) (*transaction.Transaction, uint32, error) mempool.Feer // fee interface + ManagementContractHash() util.Uint160 PoolTx(t *transaction.Transaction, pools ...*mempool.Pool) error PoolTxWithData(t *transaction.Transaction, data interface{}, mp *mempool.Pool, feer mempool.Feer, verificationFunction func(bc Blockchainer, t *transaction.Transaction, data interface{}) error) error RegisterPostBlock(f func(Blockchainer, *mempool.Pool, *block.Block)) diff --git a/pkg/core/dao/cacheddao.go b/pkg/core/dao/cacheddao.go index 037dc64a5..32e5019b5 100644 --- a/pkg/core/dao/cacheddao.go +++ b/pkg/core/dao/cacheddao.go @@ -9,11 +9,10 @@ import ( ) // Cached is a data access object that mimics DAO, but has a write cache -// for accounts and read cache for contracts. These are the most frequently used +// for accounts and NEP17 transfer data. These are the most frequently used // objects in the storeBlock(). type Cached struct { DAO - contracts map[util.Uint160]*state.Contract balances map[util.Uint160]*state.NEP17Balances transfers map[util.Uint160]map[uint32]*state.NEP17TransferLog @@ -22,34 +21,9 @@ type Cached struct { // NewCached returns new Cached wrapping around given backing store. func NewCached(d DAO) *Cached { - ctrs := make(map[util.Uint160]*state.Contract) balances := make(map[util.Uint160]*state.NEP17Balances) transfers := make(map[util.Uint160]map[uint32]*state.NEP17TransferLog) - return &Cached{d.GetWrapped(), ctrs, balances, transfers, false} -} - -// GetContractState returns contract state from cache or underlying store. -func (cd *Cached) GetContractState(hash util.Uint160) (*state.Contract, error) { - if cd.contracts[hash] != nil { - return cd.contracts[hash], nil - } - cs, err := cd.DAO.GetContractState(hash) - if err == nil { - cd.contracts[hash] = cs - } - return cs, err -} - -// PutContractState puts given contract state into the given store. -func (cd *Cached) PutContractState(cs *state.Contract) error { - cd.contracts[cs.Hash] = cs - return cd.DAO.PutContractState(cs) -} - -// DeleteContractState deletes given contract state in cache and backing store. -func (cd *Cached) DeleteContractState(hash util.Uint160) error { - cd.contracts[hash] = nil - return cd.DAO.DeleteContractState(hash) + return &Cached{d.GetWrapped(), balances, transfers, false} } // GetNEP17Balances retrieves NEP17Balances for the acc. @@ -105,7 +79,7 @@ func (cd *Cached) Persist() (int, error) { // If the lower DAO is Cached, we only need to flush the MemCached DB. // This actually breaks DAO interface incapsulation, but for our current // usage scenario it should be good enough if cd doesn't modify object - // caches (accounts/contracts/etc) in any way. + // caches (accounts/transfer data) in any way. if ok { if cd.dropNEP17Cache { lowerCache.balances = make(map[util.Uint160]*state.NEP17Balances) @@ -145,7 +119,6 @@ func (cd *Cached) Persist() (int, error) { // GetWrapped implements DAO interface. func (cd *Cached) GetWrapped() DAO { return &Cached{cd.DAO.GetWrapped(), - cd.contracts, cd.balances, cd.transfers, false, diff --git a/pkg/core/dao/cacheddao_test.go b/pkg/core/dao/cacheddao_test.go index f38987e85..ee8211ade 100644 --- a/pkg/core/dao/cacheddao_test.go +++ b/pkg/core/dao/cacheddao_test.go @@ -7,51 +7,10 @@ import ( "github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/storage" - "github.com/nspcc-dev/neo-go/pkg/crypto/hash" - "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func TestCachedDaoContracts(t *testing.T) { - store := storage.NewMemoryStore() - pdao := NewSimple(store, netmode.UnitTestNet, false) - dao := NewCached(pdao) - - script := []byte{0xde, 0xad, 0xbe, 0xef} - sh := hash.Hash160(script) - _, err := dao.GetContractState(sh) - require.NotNil(t, err) - - m := manifest.NewManifest("Test") - - cs := &state.Contract{ - ID: 123, - Hash: sh, - Script: script, - Manifest: *m, - } - - require.NoError(t, dao.PutContractState(cs)) - cs2, err := dao.GetContractState(sh) - require.Nil(t, err) - require.Equal(t, cs, cs2) - - _, err = dao.Persist() - require.Nil(t, err) - dao2 := NewCached(pdao) - cs2, err = dao2.GetContractState(sh) - require.Nil(t, err) - require.Equal(t, cs, cs2) - - require.NoError(t, dao.DeleteContractState(sh)) - cs2, err = dao2.GetContractState(sh) - require.Nil(t, err) - require.Equal(t, cs, cs2) - _, err = dao.GetContractState(sh) - require.NotNil(t, err) -} - func TestCachedCachedDao(t *testing.T) { store := storage.NewMemoryStore() // Persistent DAO to check for backing storage. diff --git a/pkg/core/dao/dao.go b/pkg/core/dao/dao.go index b1fb35a7d..40911e227 100644 --- a/pkg/core/dao/dao.go +++ b/pkg/core/dao/dao.go @@ -33,13 +33,12 @@ type DAO interface { AppendAppExecResult(aer *state.AppExecResult, buf *io.BufBinWriter) error AppendNEP17Transfer(acc util.Uint160, index uint32, tr *state.NEP17Transfer) (bool, error) DeleteBlock(h util.Uint256, buf *io.BufBinWriter) error - DeleteContractState(hash util.Uint160) error + DeleteContractID(id int32) error DeleteStorageItem(id int32, key []byte) error GetAndDecode(entity io.Serializable, key []byte) error GetAppExecResults(hash util.Uint256, trig trigger.Type) ([]state.AppExecResult, error) GetBatch() *storage.MemBatch GetBlock(hash util.Uint256) (*block.Block, error) - GetContractState(hash util.Uint160) (*state.Contract, error) GetContractScriptHash(id int32) (util.Uint160, error) GetCurrentBlockHeight() (uint32, error) GetCurrentHeaderHeight() (i uint32, h util.Uint256, err error) @@ -47,7 +46,6 @@ type DAO interface { GetHeaderHashes() ([]util.Uint256, error) GetNEP17Balances(acc util.Uint160) (*state.NEP17Balances, error) GetNEP17TransferLog(acc util.Uint160, index uint32) (*state.NEP17TransferLog, error) - GetAndUpdateNextContractID() (int32, error) GetStateRoot(height uint32) (*state.MPTRootState, error) PutStateRoot(root *state.MPTRootState) error GetStorageItem(id int32, key []byte) *state.StorageItem @@ -59,7 +57,7 @@ type DAO interface { HasTransaction(hash util.Uint256) error Persist() (int, error) PutAppExecResult(aer *state.AppExecResult, buf *io.BufBinWriter) error - PutContractState(cs *state.Contract) error + PutContractID(id int32, hash util.Uint160) error PutCurrentHeader(hashAndIndex []byte) error PutNEP17Balances(acc util.Uint160, bs *state.NEP17Balances) error PutNEP17TransferLog(acc util.Uint160, index uint32, lg *state.NEP17TransferLog) error @@ -125,72 +123,32 @@ func (dao *Simple) putWithBuffer(entity io.Serializable, key []byte, buf *io.Buf return dao.Store.Put(key, buf.Bytes()) } -// -- start contracts. - -// GetContractState returns contract state as recorded in the given -// store by the given script hash. -func (dao *Simple) GetContractState(hash util.Uint160) (*state.Contract, error) { - contract := &state.Contract{} - key := storage.AppendPrefix(storage.STContract, hash.BytesBE()) - err := dao.GetAndDecode(contract, key) - if err != nil { - return nil, err - } - - return contract, nil -} - -// PutContractState puts given contract state into the given store. -func (dao *Simple) PutContractState(cs *state.Contract) error { - key := storage.AppendPrefix(storage.STContract, cs.Hash.BytesBE()) - if err := dao.Put(cs, key); err != nil { - return err - } - if cs.UpdateCounter != 0 { // Update. - return nil - } - key = key[:5] - key[0] = byte(storage.STContractID) - binary.LittleEndian.PutUint32(key[1:], uint32(cs.ID)) - return dao.Store.Put(key, cs.Hash.BytesBE()) -} - -// DeleteContractState deletes given contract state in the given store. -func (dao *Simple) DeleteContractState(hash util.Uint160) error { - key := storage.AppendPrefix(storage.STContract, hash.BytesBE()) - return dao.Store.Delete(key) -} - -// GetAndUpdateNextContractID returns id for the next contract and increases stored ID. -func (dao *Simple) GetAndUpdateNextContractID() (int32, error) { - var id int32 - key := storage.SYSContractID.Bytes() - data, err := dao.Store.Get(key) - if err == nil { - id = int32(binary.LittleEndian.Uint32(data)) - } else if err != storage.ErrKeyNotFound { - return 0, err - } - data = make([]byte, 4) - binary.LittleEndian.PutUint32(data, uint32(id+1)) - return id, dao.Store.Put(key, data) -} - -// GetContractScriptHash returns script hash of the contract with the specified ID. -// Contract with the script hash may be destroyed. -func (dao *Simple) GetContractScriptHash(id int32) (util.Uint160, error) { +func makeContractIDKey(id int32) []byte { key := make([]byte, 5) key[0] = byte(storage.STContractID) binary.LittleEndian.PutUint32(key[1:], uint32(id)) - data := &util.Uint160{} - if err := dao.GetAndDecode(data, key); err != nil { + return key +} + +// DeleteContractID deletes contract's id to hash mapping. +func (dao *Simple) DeleteContractID(id int32) error { + return dao.Store.Delete(makeContractIDKey(id)) +} + +// PutContractID adds a mapping from contract's ID to its hash. +func (dao *Simple) PutContractID(id int32, hash util.Uint160) error { + return dao.Store.Put(makeContractIDKey(id), hash.BytesBE()) +} + +// GetContractScriptHash retrieves contract's hash given its ID. +func (dao *Simple) GetContractScriptHash(id int32) (util.Uint160, error) { + var data = new(util.Uint160) + if err := dao.GetAndDecode(data, makeContractIDKey(id)); err != nil { return *data, err } return *data, nil } -// -- end contracts. - // -- start nep17 balances. // GetNEP17Balances retrieves nep17 balances from the cache. diff --git a/pkg/core/dao/dao_test.go b/pkg/core/dao/dao_test.go index a1a022eea..97b1ddb54 100644 --- a/pkg/core/dao/dao_test.go +++ b/pkg/core/dao/dao_test.go @@ -10,7 +10,6 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/storage" "github.com/nspcc-dev/neo-go/pkg/core/transaction" - "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" @@ -43,45 +42,6 @@ func (t *TestSerializable) DecodeBinary(reader *io.BinReader) { t.field = reader.ReadString() } -func TestPutAndGetContractState(t *testing.T) { - dao := NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, false) - script := []byte{} - h := hash.Hash160(script) - contractState := &state.Contract{Hash: h, Script: script} - err := dao.PutContractState(contractState) - require.NoError(t, err) - gotContractState, err := dao.GetContractState(contractState.Hash) - require.NoError(t, err) - require.Equal(t, contractState, gotContractState) -} - -func TestDeleteContractState(t *testing.T) { - dao := NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, false) - script := []byte{} - h := hash.Hash160(script) - contractState := &state.Contract{Hash: h, Script: script} - err := dao.PutContractState(contractState) - require.NoError(t, err) - err = dao.DeleteContractState(h) - require.NoError(t, err) - gotContractState, err := dao.GetContractState(h) - require.Error(t, err) - require.Nil(t, gotContractState) -} - -func TestSimple_GetAndUpdateNextContractID(t *testing.T) { - dao := NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, false) - id, err := dao.GetAndUpdateNextContractID() - require.NoError(t, err) - require.EqualValues(t, 0, id) - id, err = dao.GetAndUpdateNextContractID() - require.NoError(t, err) - require.EqualValues(t, 1, id) - id, err = dao.GetAndUpdateNextContractID() - require.NoError(t, err) - require.EqualValues(t, 2, id) -} - func TestPutGetAppExecResult(t *testing.T) { dao := NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, false) hash := random.Uint256() diff --git a/pkg/core/gas_price.go b/pkg/core/gas_price.go index e8641bc1a..b5ead6426 100644 --- a/pkg/core/gas_price.go +++ b/pkg/core/gas_price.go @@ -6,9 +6,6 @@ import ( "github.com/nspcc-dev/neo-go/pkg/vm/opcode" ) -// StoragePrice is a price for storing 1 byte of storage. -const StoragePrice = 100000 - // getPrice returns a price for executing op with the provided parameter. func getPrice(v *vm.VM, op opcode.Opcode, parameter []byte) int64 { return fee.Opcode(op) diff --git a/pkg/core/helper_test.go b/pkg/core/helper_test.go index 653c9d2a4..319094690 100644 --- a/pkg/core/helper_test.go +++ b/pkg/core/helper_test.go @@ -213,6 +213,7 @@ func TestCreateBasicChain(t *testing.T) { bw := io.NewBufBinWriter() txSendRaw.EncodeBinary(bw.BinWriter) t.Logf("sendrawtransaction: %s", hex.EncodeToString(bw.Bytes())) + require.False(t, saveChain) } func initBasicChain(t *testing.T, bc *Blockchain) { @@ -271,7 +272,7 @@ func initBasicChain(t *testing.T, bc *Blockchain) { acc0 := wallet.NewAccountFromPrivateKey(priv0) // Push some contract into the chain. - txDeploy, cHash := newDeployTx(t, priv0ScriptHash, prefix+"test_contract.go", "Rubl") + txDeploy, cHash := newDeployTx(t, bc, priv0ScriptHash, prefix+"test_contract.go", "Rubl") txDeploy.Nonce = getNextNonce() txDeploy.ValidUntilBlock = validUntilBlock require.NoError(t, addNetworkFee(bc, txDeploy, acc0)) @@ -357,7 +358,7 @@ func initBasicChain(t *testing.T, bc *Blockchain) { t.Logf("sendRublesTx: %v", transferTx.Hash().StringLE()) // Push verification contract into the chain. - txDeploy2, _ := newDeployTx(t, priv0ScriptHash, prefix+"verification_contract.go", "Verify") + txDeploy2, _ := newDeployTx(t, bc, priv0ScriptHash, prefix+"verification_contract.go", "Verify") txDeploy2.Nonce = getNextNonce() txDeploy2.ValidUntilBlock = validUntilBlock require.NoError(t, addNetworkFee(bc, txDeploy2, acc0)) @@ -375,10 +376,10 @@ func newNEP17Transfer(sc, from, to util.Uint160, amount int64, additionalArgs .. return transaction.New(testchain.Network(), script, 10000000) } -func newDeployTx(t *testing.T, sender util.Uint160, name, ctrName string) (*transaction.Transaction, util.Uint160) { +func newDeployTx(t *testing.T, bc *Blockchain, sender util.Uint160, name, ctrName string) (*transaction.Transaction, util.Uint160) { c, err := ioutil.ReadFile(name) require.NoError(t, err) - tx, h, err := testchain.NewDeployTx(ctrName, sender, bytes.NewReader(c)) + tx, h, err := testchain.NewDeployTx(bc, ctrName, sender, bytes.NewReader(c)) require.NoError(t, err) t.Logf("contractHash (%s): %s", name, h.StringLE()) return tx, h diff --git a/pkg/core/interop/callback/method.go b/pkg/core/interop/callback/method.go index ada8e2d41..4c6764a89 100644 --- a/pkg/core/interop/callback/method.go +++ b/pkg/core/interop/callback/method.go @@ -2,6 +2,7 @@ package callback import ( "errors" + "fmt" "strings" "github.com/nspcc-dev/neo-go/pkg/core/interop" @@ -39,15 +40,15 @@ func CreateFromMethod(ic *interop.Context) error { if err != nil { return err } - cs, err := ic.DAO.GetContractState(h) + cs, err := ic.GetContract(h) if err != nil { - return errors.New("contract not found") + return fmt.Errorf("contract not found: %w", err) } method := string(ic.VM.Estack().Pop().Bytes()) if strings.HasPrefix(method, "_") { return errors.New("invalid method name") } - currCs, err := ic.DAO.GetContractState(ic.VM.GetCurrentScriptHash()) + currCs, err := ic.GetContract(ic.VM.GetCurrentScriptHash()) if err == nil && !currCs.Manifest.CanCall(h, &cs.Manifest, method) { return errors.New("method call is not allowed") } diff --git a/pkg/core/interop/context.go b/pkg/core/interop/context.go index 0f06e96d9..a489dbf69 100644 --- a/pkg/core/interop/context.go +++ b/pkg/core/interop/context.go @@ -37,10 +37,13 @@ type Context struct { Log *zap.Logger VM *vm.VM Functions [][]Function + getContract func(dao.DAO, util.Uint160) (*state.Contract, error) } // NewContext returns new interop context. -func NewContext(trigger trigger.Type, bc blockchainer.Blockchainer, d dao.DAO, natives []Contract, block *block.Block, tx *transaction.Transaction, log *zap.Logger) *Context { +func NewContext(trigger trigger.Type, bc blockchainer.Blockchainer, d dao.DAO, + getContract func(dao.DAO, util.Uint160) (*state.Contract, error), natives []Contract, + block *block.Block, tx *transaction.Transaction, log *zap.Logger) *Context { dao := dao.NewCached(d) nes := make([]state.NotificationEvent, 0) return &Context{ @@ -53,7 +56,8 @@ func NewContext(trigger trigger.Type, bc blockchainer.Blockchainer, d dao.DAO, n Notifications: nes, Log: log, // Functions is a slice of slices of interops sorted by ID. - Functions: [][]Function{}, + Functions: [][]Function{}, + getContract: getContract, } } @@ -89,6 +93,8 @@ type MethodAndPrice struct { type Contract interface { Initialize(*Context) error Metadata() *ContractMD + OnPersist(*Context) error + PostPersist(*Context) error } // ContractMD represents native contract instance. @@ -110,7 +116,7 @@ func NewContractMD(name string) *ContractMD { w := io.NewBufBinWriter() emit.String(w.BinWriter, c.Name) - emit.Syscall(w.BinWriter, interopnames.NeoNativeCall) + emit.Syscall(w.BinWriter, interopnames.SystemContractCallNative) c.Script = w.Bytes() c.Hash = hash.Hash160(c.Script) @@ -140,6 +146,11 @@ func Sort(fs []Function) { sort.Slice(fs, func(i, j int) bool { return fs[i].ID < fs[j].ID }) } +// GetContract returns contract by its hash in current interop context. +func (ic *Context) GetContract(hash util.Uint160) (*state.Contract, error) { + return ic.getContract(ic.DAO, hash) +} + // GetFunction returns metadata for interop with the specified id. func (ic *Context) GetFunction(id uint32) *Function { for _, slice := range ic.Functions { diff --git a/pkg/core/interop/contract/call.go b/pkg/core/interop/contract/call.go index 6c6943c67..c828b6f54 100644 --- a/pkg/core/interop/contract/call.go +++ b/pkg/core/interop/contract/call.go @@ -39,9 +39,9 @@ func callExInternal(ic *interop.Context, h []byte, name string, args []stackitem if err != nil { return errors.New("invalid contract hash") } - cs, err := ic.DAO.GetContractState(u) + cs, err := ic.GetContract(u) if err != nil { - return errors.New("contract not found") + return fmt.Errorf("contract not found: %w", err) } if strings.HasPrefix(name, "_") { return errors.New("invalid method name (starts with '_')") @@ -53,7 +53,7 @@ func callExInternal(ic *interop.Context, h []byte, name string, args []stackitem if md.Safe { f &^= smartcontract.WriteStates } else if ctx := ic.VM.Context(); ctx != nil && ctx.IsDeployed() { - curr, err := ic.DAO.GetContractState(ic.VM.GetCurrentScriptHash()) + curr, err := ic.GetContract(ic.VM.GetCurrentScriptHash()) if err == nil { if !curr.Manifest.CanCall(u, &cs.Manifest, name) { return errors.New("disallowed method call") diff --git a/pkg/core/interop/interopnames/names.go b/pkg/core/interop/interopnames/names.go index c3021014d..a96b8dee1 100644 --- a/pkg/core/interop/interopnames/names.go +++ b/pkg/core/interop/interopnames/names.go @@ -11,7 +11,6 @@ const ( SystemBinaryItoa = "System.Binary.Itoa" SystemBinarySerialize = "System.Binary.Serialize" SystemBlockchainGetBlock = "System.Blockchain.GetBlock" - SystemBlockchainGetContract = "System.Blockchain.GetContract" SystemBlockchainGetHeight = "System.Blockchain.GetHeight" SystemBlockchainGetTransaction = "System.Blockchain.GetTransaction" SystemBlockchainGetTransactionFromBlock = "System.Blockchain.GetTransactionFromBlock" @@ -22,12 +21,12 @@ const ( SystemCallbackInvoke = "System.Callback.Invoke" SystemContractCall = "System.Contract.Call" SystemContractCallEx = "System.Contract.CallEx" - SystemContractCreate = "System.Contract.Create" + SystemContractCallNative = "System.Contract.CallNative" SystemContractCreateStandardAccount = "System.Contract.CreateStandardAccount" - SystemContractDestroy = "System.Contract.Destroy" SystemContractIsStandard = "System.Contract.IsStandard" SystemContractGetCallFlags = "System.Contract.GetCallFlags" - SystemContractUpdate = "System.Contract.Update" + SystemContractNativeOnPersist = "System.Contract.NativeOnPersist" + SystemContractNativePostPersist = "System.Contract.NativePostPersist" SystemEnumeratorConcat = "System.Enumerator.Concat" SystemEnumeratorCreate = "System.Enumerator.Create" SystemEnumeratorNext = "System.Enumerator.Next" @@ -66,8 +65,6 @@ const ( NeoCryptoCheckMultisigWithECDsaSecp256k1 = "Neo.Crypto.CheckMultisigWithECDsaSecp256k1" NeoCryptoSHA256 = "Neo.Crypto.SHA256" NeoCryptoRIPEMD160 = "Neo.Crypto.RIPEMD160" - NeoNativeCall = "Neo.Native.Call" - NeoNativeDeploy = "Neo.Native.Deploy" ) var names = []string{ @@ -80,7 +77,6 @@ var names = []string{ SystemBinaryItoa, SystemBinarySerialize, SystemBlockchainGetBlock, - SystemBlockchainGetContract, SystemBlockchainGetHeight, SystemBlockchainGetTransaction, SystemBlockchainGetTransactionFromBlock, @@ -91,12 +87,12 @@ var names = []string{ SystemCallbackInvoke, SystemContractCall, SystemContractCallEx, - SystemContractCreate, + SystemContractCallNative, SystemContractCreateStandardAccount, - SystemContractDestroy, SystemContractIsStandard, SystemContractGetCallFlags, - SystemContractUpdate, + SystemContractNativeOnPersist, + SystemContractNativePostPersist, SystemEnumeratorConcat, SystemEnumeratorCreate, SystemEnumeratorNext, @@ -135,6 +131,4 @@ var names = []string{ NeoCryptoCheckMultisigWithECDsaSecp256k1, NeoCryptoSHA256, NeoCryptoRIPEMD160, - NeoNativeCall, - NeoNativeDeploy, } diff --git a/pkg/core/interop/runtime/witness.go b/pkg/core/interop/runtime/witness.go index bdee68beb..5123128a0 100644 --- a/pkg/core/interop/runtime/witness.go +++ b/pkg/core/interop/runtime/witness.go @@ -5,7 +5,6 @@ import ( "errors" "fmt" - "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/transaction" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" @@ -22,13 +21,13 @@ func CheckHashedWitness(ic *interop.Context, hash util.Uint160) (bool, error) { return true, nil } if tx, ok := ic.Container.(*transaction.Transaction); ok { - return checkScope(ic.DAO, tx, ic.VM, hash) + return checkScope(ic, tx, ic.VM, hash) } return false, errors.New("script container is not a transaction") } -func checkScope(d dao.DAO, tx *transaction.Transaction, v *vm.VM, hash util.Uint160) (bool, error) { +func checkScope(ic *interop.Context, tx *transaction.Transaction, v *vm.VM, hash util.Uint160) (bool, error) { for _, c := range tx.Signers { if c.Account == hash { if c.Scopes == transaction.Global { @@ -57,9 +56,9 @@ func checkScope(d dao.DAO, tx *transaction.Transaction, v *vm.VM, hash util.Uint if !v.Context().GetCallFlags().Has(smartcontract.ReadStates) { return false, errors.New("missing ReadStates call flag") } - cs, err := d.GetContractState(callingScriptHash) + cs, err := ic.GetContract(callingScriptHash) if err != nil { - return false, err + return false, fmt.Errorf("unable to find calling script: %w", err) } // check if the current group is the required one for _, allowedGroup := range c.AllowedGroups { diff --git a/pkg/core/interop_neo.go b/pkg/core/interop_neo.go index 2971d57b3..adf7d3f6a 100644 --- a/pkg/core/interop_neo.go +++ b/pkg/core/interop_neo.go @@ -2,33 +2,15 @@ package core import ( "bytes" - "encoding/json" "errors" "fmt" - "math" "sort" "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/state" - "github.com/nspcc-dev/neo-go/pkg/smartcontract" - "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" - "github.com/nspcc-dev/neo-go/pkg/smartcontract/nef" "github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) -const ( - // MaxContractDescriptionLen is the maximum length for contract description. - MaxContractDescriptionLen = 65536 - // MaxContractScriptSize is the maximum script size for a contract. - MaxContractScriptSize = 1024 * 1024 - // MaxContractParametersNum is the maximum number of parameters for a contract. - MaxContractParametersNum = 252 - // MaxContractStringLen is the maximum length for contract metadata strings. - MaxContractStringLen = 252 -) - var errGasLimitExceeded = errors.New("gas limit exceeded") // storageFind finds stored key-value pair. @@ -58,136 +40,3 @@ func storageFind(ic *interop.Context) error { return nil } - -// getNefAndManifestFromVM pops NEF and manifest from the VM's evaluation stack, -// does a lot of checks and returns deserialized structures if succeeds. -func getNefAndManifestFromVM(v *vm.VM) (*nef.File, *manifest.Manifest, error) { - // Always pop both elements. - nefBytes := v.Estack().Pop().BytesOrNil() - manifestBytes := v.Estack().Pop().BytesOrNil() - - if err := checkNonEmpty(nefBytes, math.MaxInt32); err != nil { // Upper limits are checked during NEF deserialization. - return nil, nil, fmt.Errorf("invalid NEF file: %w", err) - } - if err := checkNonEmpty(manifestBytes, manifest.MaxManifestSize); err != nil { - return nil, nil, fmt.Errorf("invalid manifest: %w", err) - } - - if !v.AddGas(int64(StoragePrice * (len(nefBytes) + len(manifestBytes)))) { - return nil, nil, errGasLimitExceeded - } - var resManifest *manifest.Manifest - var resNef *nef.File - if nefBytes != nil { - nf, err := nef.FileFromBytes(nefBytes) - if err != nil { - return nil, nil, fmt.Errorf("invalid NEF file: %w", err) - } - resNef = &nf - } - if manifestBytes != nil { - resManifest = new(manifest.Manifest) - err := json.Unmarshal(manifestBytes, resManifest) - if err != nil { - return nil, nil, fmt.Errorf("invalid manifest: %w", err) - } - } - return resNef, resManifest, nil -} - -// contractCreate creates a contract. -func contractCreate(ic *interop.Context) error { - neff, manif, err := getNefAndManifestFromVM(ic.VM) - if err != nil { - return err - } - if neff == nil { - return errors.New("no valid NEF provided") - } - if manif == nil { - return errors.New("no valid manifest provided") - } - if ic.Tx == nil { - return errors.New("no transaction provided") - } - h := state.CreateContractHash(ic.Tx.Sender(), neff.Script) - contract, err := ic.DAO.GetContractState(h) - if contract != nil && err == nil { - return errors.New("contract already exists") - } - if !manif.IsValid(h) { - return errors.New("failed to check contract script hash against manifest") - } - id, err := ic.DAO.GetAndUpdateNextContractID() - if err != nil { - return err - } - newcontract := &state.Contract{ - ID: id, - Hash: h, - Script: neff.Script, - Manifest: *manif, - } - if err := ic.DAO.PutContractState(newcontract); err != nil { - return err - } - cs, err := contractToStackItem(newcontract) - if err != nil { - return fmt.Errorf("cannot convert contract to stack item: %w", err) - } - ic.VM.Estack().PushVal(cs) - return callDeploy(ic, newcontract, false) -} - -func checkNonEmpty(b []byte, max int) error { - if b != nil { - if l := len(b); l == 0 { - return errors.New("empty") - } else if l > max { - return fmt.Errorf("len is %d (max %d)", l, max) - } - } - return nil -} - -// contractUpdate migrates a contract. This method assumes that Manifest and Script -// of the contract can be updated independently. -func contractUpdate(ic *interop.Context) error { - neff, manif, err := getNefAndManifestFromVM(ic.VM) - if err != nil { - return err - } - if neff == nil && manif == nil { - return errors.New("both NEF and manifest are nil") - } - contract, _ := ic.DAO.GetContractState(ic.VM.GetCurrentScriptHash()) - if contract == nil { - return errors.New("contract doesn't exist") - } - // if NEF was provided, update the contract script - if neff != nil { - contract.Script = neff.Script - } - // if manifest was provided, update the contract manifest - if manif != nil { - contract.Manifest = *manif - if !contract.Manifest.IsValid(contract.Hash) { - return errors.New("failed to check contract script hash against new manifest") - } - } - contract.UpdateCounter++ - - if err := ic.DAO.PutContractState(contract); err != nil { - return fmt.Errorf("failed to update contract: %w", err) - } - return callDeploy(ic, contract, true) -} - -func callDeploy(ic *interop.Context, cs *state.Contract, isUpdate bool) error { - md := cs.Manifest.ABI.GetMethod(manifest.MethodDeploy) - if md != nil { - return contract.CallExInternal(ic, cs, manifest.MethodDeploy, - []stackitem.Item{stackitem.NewBool(isUpdate)}, smartcontract.All, vm.EnsureIsEmpty) - } - return nil -} diff --git a/pkg/core/interop_neo_test.go b/pkg/core/interop_neo_test.go index 5366e4d91..eeb29a22c 100644 --- a/pkg/core/interop_neo_test.go +++ b/pkg/core/interop_neo_test.go @@ -49,7 +49,7 @@ func TestStorageFind(t *testing.T) { }, } - require.NoError(t, context.DAO.PutContractState(contractState)) + require.NoError(t, chain.contracts.Management.PutContractState(chain.dao, contractState)) id := contractState.ID diff --git a/pkg/core/interop_system.go b/pkg/core/interop_system.go index 50e2f377b..e455f482c 100644 --- a/pkg/core/interop_system.go +++ b/pkg/core/interop_system.go @@ -2,7 +2,6 @@ package core import ( "crypto/elliptic" - "encoding/json" "errors" "fmt" "math" @@ -12,6 +11,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/blockchainer" "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/native" "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" @@ -93,41 +93,6 @@ func bcGetBlock(ic *interop.Context) error { return nil } -// contractToStackItem converts state.Contract to stackitem.Item -func contractToStackItem(cs *state.Contract) (stackitem.Item, error) { - manifest, err := json.Marshal(cs.Manifest) - if err != nil { - return nil, err - } - return stackitem.NewArray([]stackitem.Item{ - stackitem.Make(cs.ID), - stackitem.Make(cs.UpdateCounter), - stackitem.NewByteArray(cs.Hash.BytesBE()), - stackitem.NewByteArray(cs.Script), - stackitem.NewByteArray(manifest), - }), nil -} - -// bcGetContract returns contract. -func bcGetContract(ic *interop.Context) error { - hashbytes := ic.VM.Estack().Pop().Bytes() - hash, err := util.Uint160DecodeBytesBE(hashbytes) - if err != nil { - return err - } - cs, err := ic.DAO.GetContractState(hash) - if err != nil { - ic.VM.Estack().PushVal(stackitem.Null{}) - } else { - item, err := contractToStackItem(cs) - if err != nil { - return err - } - ic.VM.Estack().PushVal(item) - } - return nil -} - // bcGetHeight returns blockchain height. func bcGetHeight(ic *interop.Context) error { ic.VM.Estack().PushVal(ic.Chain.BlockHeight()) @@ -274,7 +239,7 @@ func storageGetReadOnlyContext(ic *interop.Context) error { // storageGetContextInternal is internal version of storageGetContext and // storageGetReadOnlyContext which allows to specify ReadOnly context flag. func storageGetContextInternal(ic *interop.Context, isReadOnly bool) error { - contract, err := ic.DAO.GetContractState(ic.VM.GetCurrentScriptHash()) + contract, err := ic.GetContract(ic.VM.GetCurrentScriptHash()) if err != nil { return err } @@ -311,7 +276,7 @@ func putWithContextAndFlags(ic *interop.Context, stc *StorageContext, key []byte sizeInc = (len(si.Value)-1)/4 + 1 + len(value) - len(si.Value) } } - if !ic.VM.AddGas(int64(sizeInc) * StoragePrice) { + if !ic.VM.AddGas(int64(sizeInc) * native.StoragePrice) { return errGasLimitExceeded } si.Value = value @@ -363,27 +328,6 @@ func storageContextAsReadOnly(ic *interop.Context) error { return nil } -// contractDestroy destroys a contract. -func contractDestroy(ic *interop.Context) error { - hash := ic.VM.GetCurrentScriptHash() - cs, err := ic.DAO.GetContractState(hash) - if err != nil { - return nil - } - err = ic.DAO.DeleteContractState(hash) - if err != nil { - return err - } - siMap, err := ic.DAO.GetStorageItems(cs.ID) - if err != nil { - return err - } - for k := range siMap { - _ = ic.DAO.DeleteStorageItem(cs.ID, []byte(k)) - } - return nil -} - // contractIsStandard checks if contract is standard (sig or multisig) contract. func contractIsStandard(ic *interop.Context) error { h := ic.VM.Estack().Pop().Bytes() @@ -392,7 +336,7 @@ func contractIsStandard(ic *interop.Context) error { return err } var result bool - cs, _ := ic.DAO.GetContractState(u) + cs, _ := ic.GetContract(u) if cs != nil { result = vm.IsStandardContract(cs.Script) } else { diff --git a/pkg/core/interop_system_test.go b/pkg/core/interop_system_test.go index faef05bb6..19781c46e 100644 --- a/pkg/core/interop_system_test.go +++ b/pkg/core/interop_system_test.go @@ -1,20 +1,19 @@ package core import ( - "encoding/json" "errors" "math/big" "testing" "github.com/nspcc-dev/dbft/crypto" "github.com/nspcc-dev/neo-go/internal/random" - "github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/interop/callback" "github.com/nspcc-dev/neo-go/pkg/core/interop/contract" "github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames" "github.com/nspcc-dev/neo-go/pkg/core/interop/runtime" + "github.com/nspcc-dev/neo-go/pkg/core/native" "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/crypto/hash" @@ -22,7 +21,6 @@ import ( "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" - "github.com/nspcc-dev/neo-go/pkg/smartcontract/nef" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm/emit" @@ -228,7 +226,7 @@ func TestContractIsStandard(t *testing.T) { require.NoError(t, err) pub := priv.PublicKey() - err = ic.DAO.PutContractState(&state.Contract{ID: 42, Hash: pub.GetScriptHash(), Script: pub.GetVerificationScript()}) + err = chain.contracts.Management.PutContractState(ic.DAO, &state.Contract{ID: 42, Hash: pub.GetScriptHash(), Script: pub.GetVerificationScript()}) require.NoError(t, err) v.Estack().PushVal(pub.GetScriptHash().BytesBE()) @@ -237,7 +235,7 @@ func TestContractIsStandard(t *testing.T) { }) t.Run("contract stored, false", func(t *testing.T) { script := []byte{byte(opcode.PUSHT)} - require.NoError(t, ic.DAO.PutContractState(&state.Contract{ID: 24, Hash: hash.Hash160(script), Script: script})) + require.NoError(t, chain.contracts.Management.PutContractState(ic.DAO, &state.Contract{ID: 24, Hash: hash.Hash160(script), Script: script})) v.Estack().PushVal(crypto.Hash160(script).BytesBE()) require.NoError(t, contractIsStandard(ic)) @@ -266,25 +264,74 @@ func TestContractCreateAccount(t *testing.T) { }) } -func TestBlockchainGetContractState(t *testing.T) { - v, cs, ic, bc := createVMAndContractState(t) - defer bc.Close() - require.NoError(t, ic.DAO.PutContractState(cs)) +func TestRuntimeGasLeft(t *testing.T) { + v, ic, chain := createVM(t) + defer chain.Close() - t.Run("positive", func(t *testing.T) { - v.Estack().PushVal(cs.Hash.BytesBE()) - require.NoError(t, bcGetContract(ic)) + v.GasLimit = 100 + v.AddGas(58) + require.NoError(t, runtime.GasLeft(ic)) + require.EqualValues(t, 42, v.Estack().Pop().BigInt().Int64()) +} - actual := v.Estack().Pop().Item() - compareContractStates(t, cs, actual) +func TestRuntimeGetNotifications(t *testing.T) { + v, ic, chain := createVM(t) + defer chain.Close() + + ic.Notifications = []state.NotificationEvent{ + {ScriptHash: util.Uint160{1}, Name: "Event1", Item: stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray([]byte{11})})}, + {ScriptHash: util.Uint160{2}, Name: "Event2", Item: stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray([]byte{22})})}, + {ScriptHash: util.Uint160{1}, Name: "Event1", Item: stackitem.NewArray([]stackitem.Item{stackitem.NewByteArray([]byte{33})})}, + } + + t.Run("NoFilter", func(t *testing.T) { + v.Estack().PushVal(stackitem.Null{}) + require.NoError(t, runtime.GetNotifications(ic)) + + arr := v.Estack().Pop().Array() + require.Equal(t, len(ic.Notifications), len(arr)) + for i := range arr { + elem := arr[i].Value().([]stackitem.Item) + require.Equal(t, ic.Notifications[i].ScriptHash.BytesBE(), elem[0].Value()) + name, err := stackitem.ToString(elem[1]) + require.NoError(t, err) + require.Equal(t, ic.Notifications[i].Name, name) + require.Equal(t, ic.Notifications[i].Item, elem[2]) + } }) - t.Run("uncknown contract state", func(t *testing.T) { - v.Estack().PushVal(util.Uint160{1, 2, 3}.BytesBE()) - require.NoError(t, bcGetContract(ic)) + t.Run("WithFilter", func(t *testing.T) { + h := util.Uint160{2}.BytesBE() + v.Estack().PushVal(h) + require.NoError(t, runtime.GetNotifications(ic)) - actual := v.Estack().Pop().Item() - require.Equal(t, stackitem.Null{}, actual) + arr := v.Estack().Pop().Array() + require.Equal(t, 1, len(arr)) + elem := arr[0].Value().([]stackitem.Item) + require.Equal(t, h, elem[0].Value()) + name, err := stackitem.ToString(elem[1]) + require.NoError(t, err) + require.Equal(t, ic.Notifications[1].Name, name) + require.Equal(t, ic.Notifications[1].Item, elem[2]) + }) +} + +func TestRuntimeGetInvocationCounter(t *testing.T) { + v, ic, chain := createVM(t) + defer chain.Close() + + ic.VM.Invocations[hash.Hash160([]byte{2})] = 42 + + t.Run("No invocations", func(t *testing.T) { + v.LoadScript([]byte{1}) + // do not return an error in this case. + require.NoError(t, runtime.GetInvocationCounter(ic)) + require.EqualValues(t, 1, v.Estack().Pop().BigInt().Int64()) + }) + t.Run("NonZero", func(t *testing.T) { + v.LoadScript([]byte{2}) + require.NoError(t, runtime.GetInvocationCounter(ic)) + require.EqualValues(t, 42, v.Estack().Pop().BigInt().Int64()) }) } @@ -292,7 +339,7 @@ func TestStoragePut(t *testing.T) { _, cs, ic, bc := createVMAndContractState(t) defer bc.Close() - require.NoError(t, ic.DAO.PutContractState(cs)) + require.NoError(t, bc.contracts.Management.PutContractState(ic.DAO, cs)) initVM := func(t *testing.T, key, value []byte, gas int64) { v := ic.SpawnVM() @@ -304,23 +351,23 @@ func TestStoragePut(t *testing.T) { } t.Run("create, not enough gas", func(t *testing.T) { - initVM(t, []byte{1}, []byte{2, 3}, 2*StoragePrice) + initVM(t, []byte{1}, []byte{2, 3}, 2*native.StoragePrice) err := storagePut(ic) require.True(t, errors.Is(err, errGasLimitExceeded), "got: %v", err) }) - initVM(t, []byte{4}, []byte{5, 6}, 3*StoragePrice) + initVM(t, []byte{4}, []byte{5, 6}, 3*native.StoragePrice) require.NoError(t, storagePut(ic)) t.Run("update", func(t *testing.T) { t.Run("not enough gas", func(t *testing.T) { - initVM(t, []byte{4}, []byte{5, 6, 7, 8}, StoragePrice) + initVM(t, []byte{4}, []byte{5, 6, 7, 8}, native.StoragePrice) err := storagePut(ic) require.True(t, errors.Is(err, errGasLimitExceeded), "got: %v", err) }) - initVM(t, []byte{4}, []byte{5, 6, 7, 8}, 3*StoragePrice) + initVM(t, []byte{4}, []byte{5, 6, 7, 8}, 3*native.StoragePrice) require.NoError(t, storagePut(ic)) - initVM(t, []byte{4}, []byte{5, 6}, StoragePrice) + initVM(t, []byte{4}, []byte{5, 6}, native.StoragePrice) require.NoError(t, storagePut(ic)) }) @@ -365,7 +412,7 @@ func TestStorageDelete(t *testing.T) { v, cs, ic, bc := createVMAndContractState(t) defer bc.Close() - require.NoError(t, ic.DAO.PutContractState(cs)) + require.NoError(t, bc.contracts.Management.PutContractState(ic.DAO, cs)) v.LoadScriptWithHash(cs.Script, cs.Hash, smartcontract.All) put := func(key, value string, flag int) { v.Estack().PushVal(flag) @@ -403,7 +450,9 @@ func TestStorageDelete(t *testing.T) { } // getTestContractState returns 2 contracts second of which is allowed to call the first. -func getTestContractState() (*state.Contract, *state.Contract) { +func getTestContractState(bc *Blockchain) (*state.Contract, *state.Contract) { + mgmtHash := bc.ManagementContractHash() + w := io.NewBufBinWriter() emit.Opcodes(w.BinWriter, opcode.ABORT) addOff := w.Len() @@ -446,6 +495,17 @@ func getTestContractState() (*state.Contract, *state.Contract) { emit.String(w.BinWriter, "LastPayment") emit.Syscall(w.BinWriter, interopnames.SystemRuntimeNotify) emit.Opcodes(w.BinWriter, opcode.RET) + updateOff := w.Len() + emit.Int(w.BinWriter, 2) + emit.Opcodes(w.BinWriter, opcode.PACK) + emit.String(w.BinWriter, "update") + emit.AppCall(w.BinWriter, mgmtHash) + emit.Opcodes(w.BinWriter, opcode.RET) + destroyOff := w.Len() + emit.Opcodes(w.BinWriter, opcode.NEWARRAY0) + emit.String(w.BinWriter, "destroy") + emit.AppCall(w.BinWriter, mgmtHash) + emit.Opcodes(w.BinWriter, opcode.RET) script := w.Bytes() h := hash.Hash160(script) @@ -530,6 +590,20 @@ func getTestContractState() (*state.Contract, *state.Contract) { }, ReturnType: smartcontract.VoidType, }, + { + Name: "update", + Offset: updateOff, + Parameters: []manifest.Parameter{ + manifest.NewParameter("nef", smartcontract.ByteArrayType), + manifest.NewParameter("manifest", smartcontract.ByteArrayType), + }, + ReturnType: smartcontract.VoidType, + }, + { + Name: "destroy", + Offset: destroyOff, + ReturnType: smartcontract.VoidType, + }, } cs := &state.Contract{ Script: script, @@ -579,9 +653,9 @@ func TestContractCall(t *testing.T) { _, ic, bc := createVM(t) defer bc.Close() - cs, currCs := getTestContractState() - require.NoError(t, ic.DAO.PutContractState(cs)) - require.NoError(t, ic.DAO.PutContractState(currCs)) + cs, currCs := getTestContractState(bc) + require.NoError(t, bc.contracts.Management.PutContractState(ic.DAO, cs)) + require.NoError(t, bc.contracts.Management.PutContractState(ic.DAO, currCs)) currScript := currCs.Script h := hash.Hash160(cs.Script) @@ -682,325 +756,6 @@ func TestContractCall(t *testing.T) { }) } -func TestContractCreate(t *testing.T) { - v, cs, ic, bc := createVMAndContractState(t) - v.GasLimit = -1 - defer bc.Close() - - // nef.NewFile() cares about version a lot. - config.Version = "0.90.0-test" - - ne, err := nef.NewFile(cs.Script) - require.NoError(t, err) - neb, err := ne.Bytes() - require.NoError(t, err) - priv, err := keys.NewPrivateKey() - require.NoError(t, err) - sender := util.Uint160{1, 2, 3} - h := state.CreateContractHash(sender, ne.Script) - sig := priv.Sign(h.BytesBE()) - cs.Manifest.Groups = []manifest.Group{{ - PublicKey: priv.PublicKey(), - Signature: sig, - }} - m, err := json.Marshal(cs.Manifest) - require.NoError(t, err) - putArgsOnStack := func() { - v.Estack().PushVal(m) - v.Estack().PushVal(neb) - } - - t.Run("no tx", func(t *testing.T) { - putArgsOnStack() - - require.Error(t, contractCreate(ic)) - }) - - ic.Tx = transaction.New(netmode.UnitTestNet, []byte{1}, 0) - ic.Tx.Signers = append(ic.Tx.Signers, transaction.Signer{Account: sender}) - cs.ID = 0 - cs.Hash = state.CreateContractHash(sender, cs.Script) - - t.Run("missing NEF", func(t *testing.T) { - v.Estack().PushVal(m) - v.Estack().PushVal(stackitem.Null{}) - require.Error(t, contractCreate(ic)) - }) - t.Run("missing manifest", func(t *testing.T) { - v.Estack().PushVal(stackitem.Null{}) - v.Estack().PushVal(neb) - require.Error(t, contractCreate(ic)) - }) - t.Run("invalid manifest (empty)", func(t *testing.T) { - v.Estack().PushVal([]byte{}) - v.Estack().PushVal(neb) - require.Error(t, contractCreate(ic)) - }) - - t.Run("invalid manifest (group signature)", func(t *testing.T) { - cs.Manifest.Groups[0].Signature = make([]byte, 11) - rawManif, err := json.Marshal(cs.Manifest) - require.NoError(t, err) - v.Estack().PushVal(rawManif) - v.Estack().PushVal(neb) - require.Error(t, contractCreate(ic)) - }) - - cs.Manifest.Groups[0].Signature = sig - t.Run("positive", func(t *testing.T) { - putArgsOnStack() - - require.NoError(t, contractCreate(ic)) - actual := v.Estack().Pop().Item() - compareContractStates(t, cs, actual) - }) - - t.Run("contract already exists", func(t *testing.T) { - putArgsOnStack() - - require.Error(t, contractCreate(ic)) - }) -} - -func compareContractStates(t *testing.T, expected *state.Contract, actual stackitem.Item) { - act, ok := actual.Value().([]stackitem.Item) - require.True(t, ok) - - expectedManifest, err := json.Marshal(expected.Manifest) - require.NoError(t, err) - - require.Equal(t, 5, len(act)) - require.Equal(t, expected.ID, int32(act[0].Value().(*big.Int).Int64())) - require.Equal(t, expected.UpdateCounter, uint16(act[1].Value().(*big.Int).Int64())) - require.Equal(t, expected.Hash.BytesBE(), act[2].Value().([]byte)) - require.Equal(t, expected.Script, act[3].Value().([]byte)) - require.Equal(t, expectedManifest, act[4].Value().([]byte)) -} - -func TestContractUpdate(t *testing.T) { - v, cs, ic, bc := createVMAndContractState(t) - defer bc.Close() - v.GasLimit = -1 - - putArgsOnStack := func(script, manifest interface{}) { - v.Estack().PushVal(manifest) - b, ok := script.([]byte) - if ok { - ne, err := nef.NewFile(b) - require.NoError(t, err) - script, err = ne.Bytes() - require.NoError(t, err) - } - v.Estack().PushVal(script) - } - - t.Run("no args", func(t *testing.T) { - require.NoError(t, ic.DAO.PutContractState(cs)) - v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.Hash, smartcontract.All) - putArgsOnStack(stackitem.Null{}, stackitem.Null{}) - require.Error(t, contractUpdate(ic)) - }) - - t.Run("no contract", func(t *testing.T) { - require.NoError(t, ic.DAO.PutContractState(cs)) - v.LoadScriptWithHash([]byte{byte(opcode.RET)}, util.Uint160{8, 9, 7}, smartcontract.All) - putArgsOnStack([]byte{1}, stackitem.Null{}) - require.Error(t, contractUpdate(ic)) - }) - - t.Run("too large script", func(t *testing.T) { - require.NoError(t, ic.DAO.PutContractState(cs)) - v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.Hash, smartcontract.All) - putArgsOnStack(make([]byte, MaxContractScriptSize+1), stackitem.Null{}) - require.Error(t, contractUpdate(ic)) - }) - - t.Run("too large manifest", func(t *testing.T) { - require.NoError(t, ic.DAO.PutContractState(cs)) - v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.Hash, smartcontract.All) - putArgsOnStack(stackitem.Null{}, make([]byte, manifest.MaxManifestSize+1)) - require.Error(t, contractUpdate(ic)) - }) - - t.Run("gas limit exceeded", func(t *testing.T) { - require.NoError(t, ic.DAO.PutContractState(cs)) - v.GasLimit = 0 - v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.Hash, smartcontract.All) - putArgsOnStack([]byte{1}, []byte{2}) - require.Error(t, contractUpdate(ic)) - }) - - v.GasLimit = -1 - t.Run("update script, positive", func(t *testing.T) { - require.NoError(t, ic.DAO.PutContractState(cs)) - t.Run("empty manifest", func(t *testing.T) { - v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.Hash, smartcontract.All) - newScript := []byte{9, 8, 7, 6, 5} - putArgsOnStack(newScript, []byte{}) - require.Error(t, contractUpdate(ic)) - }) - - v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.Hash, smartcontract.All) - newScript := []byte{9, 8, 7, 6, 5} - putArgsOnStack(newScript, stackitem.Null{}) - - require.NoError(t, contractUpdate(ic)) - - // updated contract should have the same scripthash - actual, err := ic.DAO.GetContractState(cs.Hash) - require.NoError(t, err) - expected := &state.Contract{ - ID: cs.ID, - UpdateCounter: 1, - Hash: cs.Hash, - Script: newScript, - Manifest: cs.Manifest, - } - require.Equal(t, expected, actual) - }) - - t.Run("update manifest, bad manifest", func(t *testing.T) { - require.NoError(t, ic.DAO.PutContractState(cs)) - v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.Hash, smartcontract.All) - putArgsOnStack(stackitem.Null{}, []byte{1, 2, 3}) - - require.Error(t, contractUpdate(ic)) - }) - - t.Run("update manifest, positive", func(t *testing.T) { - require.NoError(t, ic.DAO.PutContractState(cs)) - manifest := &manifest.Manifest{ - ABI: manifest.ABI{}, - } - manifestBytes, err := json.Marshal(manifest) - require.NoError(t, err) - - t.Run("empty script", func(t *testing.T) { - v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.Hash, smartcontract.All) - putArgsOnStack([]byte{}, manifestBytes) - require.Error(t, contractUpdate(ic)) - }) - - v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.Hash, smartcontract.All) - putArgsOnStack(stackitem.Null{}, manifestBytes) - require.NoError(t, contractUpdate(ic)) - - // updated contract should have old scripthash - actual, err := ic.DAO.GetContractState(cs.Hash) - require.NoError(t, err) - expected := &state.Contract{ - ID: cs.ID, - UpdateCounter: 2, - Hash: cs.Hash, - Script: cs.Script, - Manifest: *manifest, - } - require.Equal(t, expected, actual) - }) - - t.Run("update both script and manifest", func(t *testing.T) { - require.NoError(t, ic.DAO.PutContractState(cs)) - v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.Hash, smartcontract.All) - newScript := []byte{12, 13, 14} - newManifest := manifest.Manifest{ - ABI: manifest.ABI{}, - } - newManifestBytes, err := json.Marshal(newManifest) - require.NoError(t, err) - - putArgsOnStack(newScript, newManifestBytes) - - require.NoError(t, contractUpdate(ic)) - - // updated contract should have new script and manifest - actual, err := ic.DAO.GetContractState(cs.Hash) - require.NoError(t, err) - expected := &state.Contract{ - ID: cs.ID, - UpdateCounter: 3, - Hash: cs.Hash, - Script: newScript, - Manifest: newManifest, - } - require.Equal(t, expected, actual) - }) -} - -func TestContractDestroy(t *testing.T) { - v, cs, ic, bc := createVMAndContractState(t) - defer bc.Close() - - v.LoadScriptWithHash(cs.Script, cs.Hash, smartcontract.All) - require.NoError(t, contractDestroy(ic)) // silent error when contract is missing - require.NoError(t, ic.DAO.PutContractState(cs)) - - v.Estack().PushVal("value") - v.Estack().PushVal("key") - require.NoError(t, storageGetContext(ic)) - require.NoError(t, storagePut(ic)) - require.NotNil(t, ic.DAO.GetStorageItem(cs.ID, []byte("key"))) - require.NoError(t, contractDestroy(ic)) - require.Nil(t, ic.DAO.GetStorageItem(cs.ID, []byte("key"))) - require.Error(t, storageGetContext(ic)) -} - -// TestContractCreateDeploy checks that `_deploy` method was called -// during contract creation or update. -func TestContractCreateDeploy(t *testing.T) { - v, ic, bc := createVM(t) - defer bc.Close() - v.GasLimit = -1 - - putArgs := func(cs *state.Contract) { - rawManifest, err := json.Marshal(cs.Manifest) - require.NoError(t, err) - v.Estack().PushVal(rawManifest) - ne, err := nef.NewFile(cs.Script) - require.NoError(t, err) - b, err := ne.Bytes() - require.NoError(t, err) - v.Estack().PushVal(b) - } - cs, currCs := getTestContractState() - - ic.Tx = transaction.New(netmode.UnitTestNet, []byte{1}, 0) - var sender = util.Uint160{1, 2, 3} - ic.Tx.Signers = append(ic.Tx.Signers, transaction.Signer{Account: sender}) - v.LoadScriptWithFlags([]byte{byte(opcode.RET)}, smartcontract.All) - putArgs(cs) - require.NoError(t, contractCreate(ic)) - require.NoError(t, ic.VM.Run()) - - cs.Hash = state.CreateContractHash(sender, cs.Script) - v.LoadScriptWithHash(currCs.Script, cs.Hash, smartcontract.All) - err := contract.CallExInternal(ic, cs, "getValue", nil, smartcontract.All, vm.EnsureNotEmpty) - require.NoError(t, err) - require.NoError(t, v.Run()) - require.Equal(t, "create", v.Estack().Pop().String()) - - v.LoadScriptWithHash(cs.Script, cs.Hash, smartcontract.All) - md := cs.Manifest.ABI.GetMethod("justReturn") - v.Jump(v.Context(), md.Offset) - - t.Run("Update", func(t *testing.T) { - newCs := &state.Contract{ - ID: cs.ID, - Hash: cs.Hash, - Script: append(cs.Script, byte(opcode.RET)), - Manifest: cs.Manifest, - } - putArgs(newCs) - require.NoError(t, contractUpdate(ic)) - require.NoError(t, v.Run()) - - v.LoadScriptWithHash(currCs.Script, cs.Hash, smartcontract.All) - err = contract.CallExInternal(ic, newCs, "getValue", nil, smartcontract.All, vm.EnsureNotEmpty) - require.NoError(t, err) - require.NoError(t, v.Run()) - require.Equal(t, "update", v.Estack().Pop().String()) - }) -} - func TestContractGetCallFlags(t *testing.T) { v, ic, bc := createVM(t) defer bc.Close() @@ -1049,12 +804,12 @@ func TestMethodCallback(t *testing.T) { _, ic, bc := createVM(t) defer bc.Close() - cs, currCs := getTestContractState() - require.NoError(t, ic.DAO.PutContractState(cs)) - require.NoError(t, ic.DAO.PutContractState(currCs)) + cs, currCs := getTestContractState(bc) + require.NoError(t, bc.contracts.Management.PutContractState(ic.DAO, cs)) + require.NoError(t, bc.contracts.Management.PutContractState(ic.DAO, currCs)) ic.Functions = append(ic.Functions, systemInterops) - rawHash := hash.Hash160(cs.Script).BytesBE() + rawHash := cs.Hash.BytesBE() t.Run("Invalid", func(t *testing.T) { runInvalid := func(args ...interface{}) func(t *testing.T) { @@ -1318,7 +1073,7 @@ func TestRuntimeCheckWitness(t *testing.T) { Groups: []manifest.Group{{PublicKey: pk.PublicKey()}}, }, } - require.NoError(t, ic.DAO.PutContractState(contractState)) + require.NoError(t, bc.contracts.Management.PutContractState(ic.DAO, contractState)) loadScriptWithHashAndFlags(ic, contractScript, contractScriptHash, smartcontract.All) ic.VM.LoadScriptWithHash([]byte{0x1}, random.Uint160(), smartcontract.ReadStates) ic.Container = tx diff --git a/pkg/core/interops.go b/pkg/core/interops.go index 83ac95824..8558e4797 100644 --- a/pkg/core/interops.go +++ b/pkg/core/interops.go @@ -43,8 +43,6 @@ var systemInterops = []interop.Function{ {Name: interopnames.SystemBinarySerialize, Func: binary.Serialize, Price: 100000, ParamCount: 1}, {Name: interopnames.SystemBlockchainGetBlock, Func: bcGetBlock, Price: 2500000, RequiredFlags: smartcontract.ReadStates, ParamCount: 1}, - {Name: interopnames.SystemBlockchainGetContract, Func: bcGetContract, Price: 1000000, - RequiredFlags: smartcontract.ReadStates, ParamCount: 1}, {Name: interopnames.SystemBlockchainGetHeight, Func: bcGetHeight, Price: 400, RequiredFlags: smartcontract.ReadStates}, {Name: interopnames.SystemBlockchainGetTransaction, Func: bcGetTransaction, Price: 1000000, @@ -61,14 +59,12 @@ var systemInterops = []interop.Function{ RequiredFlags: smartcontract.AllowCall, ParamCount: 3, DisallowCallback: true}, {Name: interopnames.SystemContractCallEx, Func: contract.CallEx, Price: 1000000, RequiredFlags: smartcontract.AllowCall, ParamCount: 4, DisallowCallback: true}, - {Name: interopnames.SystemContractCreate, Func: contractCreate, Price: 0, - RequiredFlags: smartcontract.WriteStates, ParamCount: 2, DisallowCallback: true}, + {Name: interopnames.SystemContractCallNative, Func: native.Call, Price: 0, ParamCount: 1, DisallowCallback: true}, {Name: interopnames.SystemContractCreateStandardAccount, Func: contractCreateStandardAccount, Price: 10000, ParamCount: 1, DisallowCallback: true}, - {Name: interopnames.SystemContractDestroy, Func: contractDestroy, Price: 1000000, RequiredFlags: smartcontract.WriteStates, DisallowCallback: true}, {Name: interopnames.SystemContractIsStandard, Func: contractIsStandard, Price: 30000, RequiredFlags: smartcontract.ReadStates, ParamCount: 1}, {Name: interopnames.SystemContractGetCallFlags, Func: contractGetCallFlags, Price: 30000, DisallowCallback: true}, - {Name: interopnames.SystemContractUpdate, Func: contractUpdate, Price: 0, - RequiredFlags: smartcontract.WriteStates, ParamCount: 2, DisallowCallback: true}, + {Name: interopnames.SystemContractNativeOnPersist, Func: native.OnPersist, Price: 0, DisallowCallback: true}, + {Name: interopnames.SystemContractNativePostPersist, Func: native.PostPersist, Price: 0, DisallowCallback: true}, {Name: interopnames.SystemEnumeratorConcat, Func: enumerator.Concat, Price: 400, ParamCount: 2, DisallowCallback: true}, {Name: interopnames.SystemEnumeratorCreate, Func: enumerator.Create, Price: 400, ParamCount: 1, DisallowCallback: true}, {Name: interopnames.SystemEnumeratorNext, Func: enumerator.Next, Price: 1000000, ParamCount: 1, DisallowCallback: true}, @@ -96,7 +92,7 @@ var systemInterops = []interop.Function{ {Name: interopnames.SystemRuntimeNotify, Func: runtime.Notify, Price: 1000000, RequiredFlags: smartcontract.AllowNotify, ParamCount: 2, DisallowCallback: true}, {Name: interopnames.SystemRuntimePlatform, Func: runtime.Platform, Price: 250}, - {Name: interopnames.SystemStorageDelete, Func: storageDelete, Price: StoragePrice, + {Name: interopnames.SystemStorageDelete, Func: storageDelete, Price: native.StoragePrice, RequiredFlags: smartcontract.WriteStates, ParamCount: 2, DisallowCallback: true}, {Name: interopnames.SystemStorageFind, Func: storageFind, Price: 1000000, RequiredFlags: smartcontract.ReadStates, ParamCount: 2, DisallowCallback: true}, @@ -123,8 +119,6 @@ var neoInterops = []interop.Function{ {Name: interopnames.NeoCryptoCheckMultisigWithECDsaSecp256k1, Func: crypto.ECDSASecp256k1CheckMultisig, Price: 0, ParamCount: 3}, {Name: interopnames.NeoCryptoSHA256, Func: crypto.Sha256, Price: 1000000, ParamCount: 1}, {Name: interopnames.NeoCryptoRIPEMD160, Func: crypto.RipeMD160, Price: 1000000, ParamCount: 1}, - {Name: interopnames.NeoNativeCall, Func: native.Call, Price: 0, RequiredFlags: smartcontract.AllowCall, ParamCount: 1, DisallowCallback: true}, - {Name: interopnames.NeoNativeDeploy, Func: native.Deploy, Price: 0, RequiredFlags: smartcontract.WriteStates, DisallowCallback: true}, } // initIDinInteropsSlice initializes IDs from names in one given diff --git a/pkg/core/native/contract.go b/pkg/core/native/contract.go index 29f7e58b7..7dad9d2e8 100644 --- a/pkg/core/native/contract.go +++ b/pkg/core/native/contract.go @@ -1,15 +1,13 @@ package native import ( - "errors" "strings" "github.com/nspcc-dev/neo-go/pkg/core/interop" + "github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames" "github.com/nspcc-dev/neo-go/pkg/io" - "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/emit" - "github.com/nspcc-dev/neo-go/pkg/vm/opcode" ) // reservedContractID represents the upper bound of the reserved IDs for native contracts. @@ -17,13 +15,14 @@ const reservedContractID = -100 // Contracts is a set of registered native contracts. type Contracts struct { - NEO *NEO - GAS *GAS - Policy *Policy - Oracle *Oracle - Designate *Designate - Notary *Notary - Contracts []interop.Contract + Management *Management + NEO *NEO + GAS *GAS + Policy *Policy + Oracle *Oracle + Designate *Designate + Notary *Notary + Contracts []interop.Contract // persistScript is vm script which executes "onPersist" method of every native contract. persistScript []byte // postPersistScript is vm script which executes "postPersist" method of every native contract. @@ -56,15 +55,19 @@ func (cs *Contracts) ByName(name string) interop.Contract { func NewContracts(p2pSigExtensionsEnabled bool) *Contracts { cs := new(Contracts) + mgmt := newManagement() + cs.Management = mgmt + cs.Contracts = append(cs.Contracts, mgmt) + gas := newGAS() neo := newNEO() neo.GAS = gas gas.NEO = neo cs.GAS = gas - cs.Contracts = append(cs.Contracts, gas) cs.NEO = neo cs.Contracts = append(cs.Contracts, neo) + cs.Contracts = append(cs.Contracts, gas) policy := newPolicy() cs.Policy = policy @@ -93,62 +96,24 @@ func NewContracts(p2pSigExtensionsEnabled bool) *Contracts { return cs } -// GetPersistScript returns VM script calling "onPersist" method of every native contract. +// GetPersistScript returns VM script calling "onPersist" syscall for native contracts. func (cs *Contracts) GetPersistScript() []byte { if cs.persistScript != nil { return cs.persistScript } w := io.NewBufBinWriter() - for i := range cs.Contracts { - md := cs.Contracts[i].Metadata() - // Not every contract is persisted: - // https://github.com/neo-project/neo/blob/master/src/neo/Ledger/Blockchain.cs#L90 - if md.ContractID == policyContractID || md.ContractID == oracleContractID || md.ContractID == designateContractID { - continue - } - emit.Int(w.BinWriter, 0) - emit.Opcodes(w.BinWriter, opcode.NEWARRAY) - emit.String(w.BinWriter, "onPersist") - emit.AppCall(w.BinWriter, md.Hash) - emit.Opcodes(w.BinWriter, opcode.DROP) - } + emit.Syscall(w.BinWriter, interopnames.SystemContractNativeOnPersist) cs.persistScript = w.Bytes() return cs.persistScript } -// GetPostPersistScript returns VM script calling "postPersist" method of some native contracts. +// GetPostPersistScript returns VM script calling "postPersist" syscall for native contracts. func (cs *Contracts) GetPostPersistScript() []byte { if cs.postPersistScript != nil { return cs.postPersistScript } w := io.NewBufBinWriter() - for i := range cs.Contracts { - md := cs.Contracts[i].Metadata() - // Not every contract is persisted: - // https://github.com/neo-project/neo/blob/master/src/neo/Ledger/Blockchain.cs#L103 - if md.ContractID == policyContractID || md.ContractID == gasContractID || md.ContractID == designateContractID || md.ContractID == notaryContractID { - continue - } - emit.Int(w.BinWriter, 0) - emit.Opcodes(w.BinWriter, opcode.NEWARRAY) - emit.String(w.BinWriter, "postPersist") - emit.AppCall(w.BinWriter, md.Hash) - emit.Opcodes(w.BinWriter, opcode.DROP) - } + emit.Syscall(w.BinWriter, interopnames.SystemContractNativePostPersist) cs.postPersistScript = w.Bytes() return cs.postPersistScript } - -func postPersistBase(ic *interop.Context) error { - if ic.Trigger != trigger.PostPersist { - return errors.New("postPersist must be trigered by system") - } - return nil -} - -func onPersistBase(ic *interop.Context) error { - if ic.Trigger != trigger.OnPersist { - return errors.New("onPersist must be trigered by system") - } - return nil -} diff --git a/pkg/core/native/designate.go b/pkg/core/native/designate.go index bc4f4f4f0..ecfbdbd77 100644 --- a/pkg/core/native/designate.go +++ b/pkg/core/native/designate.go @@ -10,6 +10,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/runtime" + "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" @@ -40,7 +41,6 @@ type oraclesData struct { const ( designateContractID = -5 - designateName = "Designation" // maxNodeCount is the maximum number of nodes to set the role for. maxNodeCount = 32 @@ -71,7 +71,7 @@ func (s *Designate) isValidRole(r Role) bool { } func newDesignate(p2pSigExtensionsEnabled bool) *Designate { - s := &Designate{ContractMD: *interop.NewContractMD(designateName)} + s := &Designate{ContractMD: *interop.NewContractMD(nativenames.Designation)} s.ContractID = designateContractID s.p2pSigExtensionsEnabled = p2pSigExtensionsEnabled @@ -87,14 +87,6 @@ func newDesignate(p2pSigExtensionsEnabled bool) *Designate { md = newMethodAndPrice(s.designateAsRole, 0, smartcontract.WriteStates) s.AddMethod(md, desc) - desc = newDescriptor("onPersist", smartcontract.VoidType) - md = newMethodAndPrice(getOnPersistWrapper(onPersistBase), 0, smartcontract.WriteStates) - s.AddMethod(md, desc) - - desc = newDescriptor("postPersist", smartcontract.VoidType) - md = newMethodAndPrice(getOnPersistWrapper(postPersistBase), 0, smartcontract.WriteStates) - s.AddMethod(md, desc) - return s } @@ -103,13 +95,18 @@ func (s *Designate) Initialize(ic *interop.Context) error { return nil } -// OnPersistEnd updates cached values if they've been changed. -func (s *Designate) OnPersistEnd(d dao.DAO) error { +// OnPersist implements Contract interface. +func (s *Designate) OnPersist(ic *interop.Context) error { + return nil +} + +// PostPersist implements Contract interface. +func (s *Designate) PostPersist(ic *interop.Context) error { if !s.rolesChanged() { return nil } - nodeKeys, height, err := s.GetDesignatedByRole(d, RoleOracle, math.MaxUint32) + nodeKeys, height, err := s.GetDesignatedByRole(ic.DAO, RoleOracle, math.MaxUint32) if err != nil { return err } diff --git a/pkg/core/native/interop.go b/pkg/core/native/interop.go index 4fbcf2ffa..78737da08 100644 --- a/pkg/core/native/interop.go +++ b/pkg/core/native/interop.go @@ -5,35 +5,10 @@ import ( "fmt" "github.com/nspcc-dev/neo-go/pkg/core/interop" - "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" ) -// Deploy deploys native contract. -func Deploy(ic *interop.Context) error { - if ic.Block == nil || ic.Block.Index != 0 { - return errors.New("native contracts can be deployed only at 0 block") - } - - for _, native := range ic.Natives { - md := native.Metadata() - - cs := &state.Contract{ - ID: md.ContractID, - Hash: md.Hash, - Script: md.Script, - Manifest: md.Manifest, - } - if err := ic.DAO.PutContractState(cs); err != nil { - return err - } - if err := native.Initialize(ic); err != nil { - return fmt.Errorf("initializing %s native contract: %w", md.Name, err) - } - } - return nil -} - // Call calls specified native contract method. func Call(ic *interop.Context) error { name := ic.VM.Estack().Pop().String() @@ -70,3 +45,31 @@ func Call(ic *interop.Context) error { } return nil } + +// OnPersist calls OnPersist methods for all native contracts. +func OnPersist(ic *interop.Context) error { + if ic.Trigger != trigger.OnPersist { + return errors.New("onPersist must be trigered by system") + } + for _, c := range ic.Natives { + err := c.OnPersist(ic) + if err != nil { + return err + } + } + return nil +} + +// PostPersist calls PostPersist methods for all native contracts. +func PostPersist(ic *interop.Context) error { + if ic.Trigger != trigger.PostPersist { + return errors.New("postPersist must be trigered by system") + } + for _, c := range ic.Natives { + err := c.PostPersist(ic) + if err != nil { + return err + } + } + return nil +} diff --git a/pkg/core/native/management.go b/pkg/core/native/management.go new file mode 100644 index 000000000..04631c5e8 --- /dev/null +++ b/pkg/core/native/management.go @@ -0,0 +1,377 @@ +package native + +import ( + "encoding/json" + "errors" + "fmt" + "math" + "math/big" + + "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/native/nativenames" + "github.com/nspcc-dev/neo-go/pkg/core/state" + "github.com/nspcc-dev/neo-go/pkg/encoding/bigint" + "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/nef" + "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" +) + +// Management is contract-managing native contract. +type Management struct { + interop.ContractMD +} + +// StoragePrice is the price to pay for 1 byte of storage. +const StoragePrice = 100000 + +const ( + prefixContract = 8 +) + +var errGasLimitExceeded = errors.New("gas limit exceeded") +var keyNextAvailableID = []byte{15} + +// makeContractKey creates a key from account script hash. +func makeContractKey(h util.Uint160) []byte { + return makeUint160Key(prefixContract, h) +} + +// newManagement creates new Management native contract. +func newManagement() *Management { + var m = &Management{ContractMD: *interop.NewContractMD(nativenames.Management)} + + desc := newDescriptor("getContract", smartcontract.ArrayType, + manifest.NewParameter("hash", smartcontract.Hash160Type)) + md := newMethodAndPrice(m.getContract, 1000000, smartcontract.ReadStates) + m.AddMethod(md, desc) + + desc = newDescriptor("deploy", smartcontract.ArrayType, + manifest.NewParameter("script", smartcontract.ByteArrayType), + manifest.NewParameter("manifest", smartcontract.ByteArrayType)) + md = newMethodAndPrice(m.deploy, 0, smartcontract.WriteStates) + m.AddMethod(md, desc) + + desc = newDescriptor("update", smartcontract.VoidType, + manifest.NewParameter("script", smartcontract.ByteArrayType), + manifest.NewParameter("manifest", smartcontract.ByteArrayType)) + md = newMethodAndPrice(m.update, 0, smartcontract.WriteStates) + m.AddMethod(md, desc) + + desc = newDescriptor("destroy", smartcontract.VoidType) + md = newMethodAndPrice(m.destroy, 10000000, smartcontract.WriteStates) + m.AddMethod(md, desc) + + return m +} + +// getContract is an implementation of public getContract method, it's run under +// VM protections, so it's OK for it to panic instead of returning errors. +func (m *Management) getContract(ic *interop.Context, args []stackitem.Item) stackitem.Item { + hashBytes, err := args[0].TryBytes() + if err != nil { + panic(err) + } + hash, err := util.Uint160DecodeBytesBE(hashBytes) + if err != nil { + panic(err) + } + ctr, err := m.GetContract(ic.DAO, hash) + if err != nil { + panic(err) + } + return contractToStack(ctr) +} + +// GetContract returns contract with given hash from given DAO. +func (m *Management) GetContract(d dao.DAO, hash util.Uint160) (*state.Contract, error) { + contract := new(state.Contract) + key := makeContractKey(hash) + err := getSerializableFromDAO(m.ContractID, d, key, contract) + if err != nil { + return nil, err + } + return contract, nil +} + +func getLimitedSlice(arg stackitem.Item, max int) ([]byte, error) { + _, isNull := arg.(stackitem.Null) + if isNull { + return nil, nil + } + b, err := arg.TryBytes() + if err != nil { + return nil, err + } + l := len(b) + if l == 0 { + return nil, errors.New("empty") + } else if l > max { + return nil, fmt.Errorf("len is %d (max %d)", l, max) + } + + return b, nil +} + +// getNefAndManifestFromItems converts input arguments into NEF and manifest +// adding appropriate deployment GAS price and sanitizing inputs. +func getNefAndManifestFromItems(args []stackitem.Item, v *vm.VM) (*nef.File, *manifest.Manifest, error) { + nefBytes, err := getLimitedSlice(args[0], math.MaxInt32) // Upper limits are checked during NEF deserialization. + if err != nil { + return nil, nil, fmt.Errorf("invalid NEF file: %w", err) + } + manifestBytes, err := getLimitedSlice(args[1], manifest.MaxManifestSize) + if err != nil { + return nil, nil, fmt.Errorf("invalid manifest: %w", err) + } + + if !v.AddGas(int64(StoragePrice * (len(nefBytes) + len(manifestBytes)))) { + return nil, nil, errGasLimitExceeded + } + var resManifest *manifest.Manifest + var resNef *nef.File + if nefBytes != nil { + nf, err := nef.FileFromBytes(nefBytes) + if err != nil { + return nil, nil, fmt.Errorf("invalid NEF file: %w", err) + } + resNef = &nf + } + if manifestBytes != nil { + resManifest = new(manifest.Manifest) + err := json.Unmarshal(manifestBytes, resManifest) + if err != nil { + return nil, nil, fmt.Errorf("invalid manifest: %w", err) + } + } + return resNef, resManifest, nil +} + +// deploy is an implementation of public deploy method, it's run under +// VM protections, so it's OK for it to panic instead of returning errors. +func (m *Management) deploy(ic *interop.Context, args []stackitem.Item) stackitem.Item { + neff, manif, err := getNefAndManifestFromItems(args, ic.VM) + if err != nil { + panic(err) + } + if neff == nil { + panic(errors.New("no valid NEF provided")) + } + if manif == nil { + panic(errors.New("no valid manifest provided")) + } + if ic.Tx == nil { + panic(errors.New("no transaction provided")) + } + newcontract, err := m.Deploy(ic.DAO, ic.Tx.Sender(), neff, manif) + if err != nil { + panic(err) + } + callDeploy(ic, newcontract, false) + return contractToStack(newcontract) + +} + +// Deploy creates contract's hash/ID and saves new contract into the given DAO. +// It doesn't run _deploy method. +func (m *Management) Deploy(d dao.DAO, sender util.Uint160, neff *nef.File, manif *manifest.Manifest) (*state.Contract, error) { + h := state.CreateContractHash(sender, neff.Script) + key := makeContractKey(h) + si := d.GetStorageItem(m.ContractID, key) + if si != nil { + return nil, errors.New("contract already exists") + } + id, err := m.getNextContractID(d) + if err != nil { + return nil, err + } + if !manif.IsValid(h) { + return nil, errors.New("invalid manifest for this contract") + } + newcontract := &state.Contract{ + ID: id, + Hash: h, + Script: neff.Script, + Manifest: *manif, + } + err = m.PutContractState(d, newcontract) + if err != nil { + return nil, err + } + return newcontract, nil +} + +// update is an implementation of public update method, it's run under +// VM protections, so it's OK for it to panic instead of returning errors. +func (m *Management) update(ic *interop.Context, args []stackitem.Item) stackitem.Item { + neff, manif, err := getNefAndManifestFromItems(args, ic.VM) + if err != nil { + panic(err) + } + if neff == nil && manif == nil { + panic(errors.New("both NEF and manifest are nil")) + } + contract, err := m.Update(ic.DAO, ic.VM.GetCallingScriptHash(), neff, manif) + if err != nil { + panic(err) + } + callDeploy(ic, contract, true) + return stackitem.Null{} +} + +// Update updates contract's script and/or manifest in the given DAO. +// It doesn't run _deploy method. +func (m *Management) Update(d dao.DAO, hash util.Uint160, neff *nef.File, manif *manifest.Manifest) (*state.Contract, error) { + contract, err := m.GetContract(d, hash) + if err != nil { + return nil, errors.New("contract doesn't exist") + } + // if NEF was provided, update the contract script + if neff != nil { + contract.Script = neff.Script + } + // if manifest was provided, update the contract manifest + if manif != nil { + contract.Manifest = *manif + if !contract.Manifest.IsValid(contract.Hash) { + return nil, errors.New("invalid manifest for this contract") + } + } + contract.UpdateCounter++ + err = m.PutContractState(d, contract) + if err != nil { + return nil, err + } + return contract, nil +} + +// destroy is an implementation of destroy update method, it's run under +// VM protections, so it's OK for it to panic instead of returning errors. +func (m *Management) destroy(ic *interop.Context, sis []stackitem.Item) stackitem.Item { + hash := ic.VM.GetCallingScriptHash() + err := m.Destroy(ic.DAO, hash) + if err != nil { + panic(err) + } + return stackitem.Null{} +} + +// Destroy drops given contract from DAO along with its storage. +func (m *Management) Destroy(d dao.DAO, hash util.Uint160) error { + contract, err := m.GetContract(d, hash) + if err != nil { + return err + } + key := makeContractKey(hash) + err = d.DeleteStorageItem(m.ContractID, key) + if err != nil { + return err + } + err = d.DeleteContractID(contract.ID) + if err != nil { + return err + } + siMap, err := d.GetStorageItems(contract.ID) + if err != nil { + return err + } + for k := range siMap { + err := d.DeleteStorageItem(contract.ID, []byte(k)) + if err != nil { + return err + } + } + return nil +} + +func callDeploy(ic *interop.Context, cs *state.Contract, isUpdate bool) { + md := cs.Manifest.ABI.GetMethod(manifest.MethodDeploy) + if md != nil { + err := contract.CallExInternal(ic, cs, manifest.MethodDeploy, + []stackitem.Item{stackitem.NewBool(isUpdate)}, smartcontract.All, vm.EnsureIsEmpty) + if err != nil { + panic(err) + } + } +} + +func contractToStack(cs *state.Contract) stackitem.Item { + si, err := cs.ToStackItem() + if err != nil { + panic(fmt.Errorf("contract to stack item: %w", err)) + } + return si +} + +// Metadata implements Contract interface. +func (m *Management) Metadata() *interop.ContractMD { + return &m.ContractMD +} + +// OnPersist implements Contract interface. +func (m *Management) OnPersist(ic *interop.Context) error { + if ic.Block.Index != 0 { // We're only deploying at 0 at the moment. + return nil + } + + for _, native := range ic.Natives { + md := native.Metadata() + + cs := &state.Contract{ + ID: md.ContractID, + Hash: md.Hash, + Script: md.Script, + Manifest: md.Manifest, + } + err := m.PutContractState(ic.DAO, cs) + if err != nil { + return err + } + if err := native.Initialize(ic); err != nil { + return fmt.Errorf("initializing %s native contract: %w", md.Name, err) + } + } + + return nil +} + +// PostPersist implements Contract interface. +func (m *Management) PostPersist(_ *interop.Context) error { + return nil +} + +// Initialize implements Contract interface. +func (m *Management) Initialize(_ *interop.Context) error { + return nil +} + +// PutContractState saves given contract state into given DAO. +func (m *Management) PutContractState(d dao.DAO, cs *state.Contract) error { + key := makeContractKey(cs.Hash) + if err := putSerializableToDAO(m.ContractID, d, key, cs); err != nil { + return err + } + if cs.UpdateCounter != 0 { // Update. + return nil + } + return d.PutContractID(cs.ID, cs.Hash) +} + +func (m *Management) getNextContractID(d dao.DAO) (int32, error) { + var id = big.NewInt(1) + si := d.GetStorageItem(m.ContractID, keyNextAvailableID) + if si != nil { + id = bigint.FromBytes(si.Value) + } else { + si = new(state.StorageItem) + si.Value = make([]byte, 0, 2) + } + ret := int32(id.Int64()) + id.Add(id, big.NewInt(1)) + si.Value = bigint.ToPreallocatedBytes(id, si.Value) + return ret, d.PutStorageItem(m.ContractID, keyNextAvailableID, si) +} diff --git a/pkg/core/native/management_test.go b/pkg/core/native/management_test.go new file mode 100644 index 000000000..e708654a4 --- /dev/null +++ b/pkg/core/native/management_test.go @@ -0,0 +1,63 @@ +package native + +import ( + "testing" + + "github.com/nspcc-dev/neo-go/pkg/config/netmode" + "github.com/nspcc-dev/neo-go/pkg/core/dao" + "github.com/nspcc-dev/neo-go/pkg/core/state" + "github.com/nspcc-dev/neo-go/pkg/core/storage" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/nef" + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/stretchr/testify/require" +) + +func TestDeployGetUpdateDestroyContract(t *testing.T) { + mgmt := newManagement() + d := dao.NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, false) + script := []byte{1} + sender := util.Uint160{1, 2, 3} + h := state.CreateContractHash(sender, script) + + ne, err := nef.NewFile(script) + require.NoError(t, err) + manif := manifest.NewManifest("Test") + require.NoError(t, err) + + contract, err := mgmt.Deploy(d, sender, ne, manif) + require.NoError(t, err) + require.Equal(t, int32(1), contract.ID) + require.Equal(t, uint16(0), contract.UpdateCounter) + require.Equal(t, h, contract.Hash) + require.Equal(t, script, contract.Script) + require.Equal(t, *manif, contract.Manifest) + + // Double deploy. + _, err = mgmt.Deploy(d, sender, ne, manif) + require.Error(t, err) + + // Different sender. + sender2 := util.Uint160{3, 2, 1} + contract2, err := mgmt.Deploy(d, sender2, ne, manif) + require.NoError(t, err) + require.Equal(t, int32(2), contract2.ID) + require.Equal(t, uint16(0), contract2.UpdateCounter) + require.Equal(t, state.CreateContractHash(sender2, script), contract2.Hash) + require.Equal(t, script, contract2.Script) + require.Equal(t, *manif, contract2.Manifest) + + refContract, err := mgmt.GetContract(d, h) + require.NoError(t, err) + require.Equal(t, contract, refContract) + + upContract, err := mgmt.Update(d, h, ne, manif) + refContract.UpdateCounter++ + require.NoError(t, err) + require.Equal(t, refContract, upContract) + + err = mgmt.Destroy(d, h) + require.NoError(t, err) + _, err = mgmt.GetContract(d, h) + require.Error(t, err) +} diff --git a/pkg/core/native/native_gas.go b/pkg/core/native/native_gas.go index 8965f0236..8883f3ada 100644 --- a/pkg/core/native/native_gas.go +++ b/pkg/core/native/native_gas.go @@ -5,6 +5,7 @@ import ( "math/big" "github.com/nspcc-dev/neo-go/pkg/core/interop" + "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/smartcontract" @@ -17,7 +18,6 @@ type GAS struct { NEO *NEO } -const gasName = "GAS" const gasContractID = -2 // GASFactor is a divisor for finding GAS integral value. @@ -27,20 +27,15 @@ const initialGAS = 30000000 // newGAS returns GAS native contract. func newGAS() *GAS { g := &GAS{} - nep17 := newNEP17Native(gasName) - nep17.symbol = "gas" + nep17 := newNEP17Native(nativenames.Gas) + nep17.symbol = "GAS" nep17.decimals = 8 nep17.factor = GASFactor - nep17.onPersist = chainOnPersist(onPersistBase, g.OnPersist) nep17.incBalance = g.increaseBalance nep17.ContractID = gasContractID g.nep17TokenNative = *nep17 - onp := g.Methods["onPersist"] - onp.Func = getOnPersistWrapper(g.onPersist) - g.Methods["onPersist"] = onp - return g } @@ -98,6 +93,11 @@ func (g *GAS) OnPersist(ic *interop.Context) error { return nil } +// PostPersist implements Contract interface. +func (g *GAS) PostPersist(ic *interop.Context) error { + return nil +} + func getStandbyValidatorsHash(ic *interop.Context) (util.Uint160, error) { s, err := smartcontract.CreateDefaultMultiSigRedeemScript(ic.Chain.GetStandByValidators()) if err != nil { @@ -105,16 +105,3 @@ func getStandbyValidatorsHash(ic *interop.Context) (util.Uint160, error) { } return hash.Hash160(s), nil } - -func chainOnPersist(fs ...func(*interop.Context) error) func(*interop.Context) error { - return func(ic *interop.Context) error { - for i := range fs { - if fs[i] != nil { - if err := fs[i](ic); err != nil { - return err - } - } - } - return nil - } -} diff --git a/pkg/core/native/native_neo.go b/pkg/core/native/native_neo.go index 40800dc1a..739264680 100644 --- a/pkg/core/native/native_neo.go +++ b/pkg/core/native/native_neo.go @@ -14,6 +14,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/runtime" + "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/storage" "github.com/nspcc-dev/neo-go/pkg/crypto/hash" @@ -49,7 +50,6 @@ type NEO struct { } const ( - neoName = "NEO" neoContractID = -1 // NEOTotalSupply is the total amount of NEO in the system. NEOTotalSupply = 100000000 @@ -93,12 +93,10 @@ func makeValidatorKey(key *keys.PublicKey) []byte { // newNEO returns NEO native contract. func newNEO() *NEO { n := &NEO{} - nep17 := newNEP17Native(neoName) - nep17.symbol = "neo" + nep17 := newNEP17Native(nativenames.Neo) + nep17.symbol = "NEO" nep17.decimals = 0 nep17.factor = 1 - nep17.onPersist = chainOnPersist(onPersistBase, n.OnPersist) - nep17.postPersist = chainOnPersist(nep17.postPersist, n.PostPersist) nep17.incBalance = n.increaseBalance nep17.ContractID = neoContractID @@ -109,14 +107,6 @@ func newNEO() *NEO { n.committee.Store(keysWithVotes(nil)) n.committeeHash.Store(util.Uint160{}) - onp := n.Methods["onPersist"] - onp.Func = getOnPersistWrapper(n.onPersist) - n.Methods["onPersist"] = onp - - pp := n.Methods["postPersist"] - pp.Func = getOnPersistWrapper(n.postPersist) - n.Methods["postPersist"] = pp - desc := newDescriptor("unclaimedGas", smartcontract.IntegerType, manifest.NewParameter("account", smartcontract.Hash160Type), manifest.NewParameter("end", smartcontract.IntegerType)) @@ -330,7 +320,14 @@ func (n *NEO) PostPersist(ic *interop.Context) error { } } - n.OnPersistEnd(ic.DAO) + if n.gasPerBlockChanged.Load().(bool) { + gr, err := n.getSortedGASRecordFromDAO(ic.DAO) + if err != nil { + panic(err) + } + n.gasPerBlock.Store(gr) + n.gasPerBlockChanged.Store(false) + } return nil } @@ -357,18 +354,6 @@ func (n *NEO) getGASPerVote(d dao.DAO, key []byte, index ...uint32) []big.Int { return reward } -// OnPersistEnd updates cached values if they've been changed. -func (n *NEO) OnPersistEnd(d dao.DAO) { - if n.gasPerBlockChanged.Load().(bool) { - gr, err := n.getSortedGASRecordFromDAO(d) - if err != nil { - panic(err) - } - n.gasPerBlock.Store(gr) - n.gasPerBlockChanged.Store(false) - } -} - func (n *NEO) increaseBalance(ic *interop.Context, h util.Uint160, si *state.StorageItem, amount *big.Int) error { acc, err := state.NEOBalanceStateFromBytes(si.Value) if err != nil { diff --git a/pkg/core/native/native_nep17.go b/pkg/core/native/native_nep17.go index 8f42dc8da..9a2e8b1ca 100644 --- a/pkg/core/native/native_nep17.go +++ b/pkg/core/native/native_nep17.go @@ -2,7 +2,6 @@ package native import ( "errors" - "fmt" "math" "math/big" @@ -24,21 +23,16 @@ const prefixAccount = 20 // makeAccountKey creates a key from account script hash. func makeAccountKey(h util.Uint160) []byte { - k := make([]byte, util.Uint160Size+1) - k[0] = prefixAccount - copy(k[1:], h.BytesBE()) - return k + return makeUint160Key(prefixAccount, h) } // nep17TokenNative represents NEP-17 token contract. type nep17TokenNative struct { interop.ContractMD - symbol string - decimals int64 - factor int64 - onPersist func(*interop.Context) error - postPersist func(*interop.Context) error - incBalance func(*interop.Context, util.Uint160, *state.StorageItem, *big.Int) error + symbol string + decimals int64 + factor int64 + incBalance func(*interop.Context, util.Uint160, *state.StorageItem, *big.Int) error } // totalSupplyKey is the key used to store totalSupply value. @@ -48,8 +42,6 @@ func (c *nep17TokenNative) Metadata() *interop.ContractMD { return &c.ContractMD } -var _ interop.Contract = (*nep17TokenNative)(nil) - func newNEP17Native(name string) *nep17TokenNative { n := &nep17TokenNative{ContractMD: *interop.NewContractMD(name)} n.Manifest.SupportedStandards = []string{manifest.NEP17StandardName} @@ -82,14 +74,6 @@ func newNEP17Native(name string) *nep17TokenNative { md = newMethodAndPrice(n.Transfer, 8000000, smartcontract.WriteStates|smartcontract.AllowCall|smartcontract.AllowNotify) n.AddMethod(md, desc) - desc = newDescriptor("onPersist", smartcontract.VoidType) - md = newMethodAndPrice(getOnPersistWrapper(onPersistBase), 0, smartcontract.WriteStates) - n.AddMethod(md, desc) - - desc = newDescriptor("postPersist", smartcontract.VoidType) - md = newMethodAndPrice(getOnPersistWrapper(postPersistBase), 0, smartcontract.WriteStates) - n.AddMethod(md, desc) - n.AddEvent("Transfer", transferParams...) return n @@ -145,7 +129,7 @@ func (c *nep17TokenNative) postTransfer(ic *interop.Context, from, to *util.Uint if to == nil || !callOnPayment { return } - cs, err := ic.DAO.GetContractState(*to) + cs, err := ic.GetContract(*to) if err != nil { return } @@ -333,13 +317,3 @@ func toUint32(s stackitem.Item) uint32 { } return uint32(int64Value) } - -func getOnPersistWrapper(f func(ic *interop.Context) error) interop.Method { - return func(ic *interop.Context, _ []stackitem.Item) stackitem.Item { - err := f(ic) - if err != nil { - panic(fmt.Errorf("OnPersist for native contract: %w", err)) - } - return stackitem.Null{} - } -} diff --git a/pkg/core/native/nativenames/names.go b/pkg/core/native/nativenames/names.go new file mode 100644 index 000000000..301f5e17d --- /dev/null +++ b/pkg/core/native/nativenames/names.go @@ -0,0 +1,12 @@ +package nativenames + +// Names of all native contracts. +const ( + Management = "ManagementContract" + Neo = "NeoToken" + Gas = "GasToken" + Policy = "PolicyContract" + Oracle = "OracleContract" + Designation = "DesignationContract" + Notary = "Notary" +) diff --git a/pkg/core/native/notary.go b/pkg/core/native/notary.go index 3f29bca37..110355688 100644 --- a/pkg/core/native/notary.go +++ b/pkg/core/native/notary.go @@ -11,6 +11,7 @@ import ( "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/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/storage" "github.com/nspcc-dev/neo-go/pkg/core/transaction" @@ -37,7 +38,6 @@ type Notary struct { } const ( - notaryName = "Notary" notaryContractID = reservedContractID - 1 // NotaryVerificationPrice is the price of `verify` Notary method. NotaryVerificationPrice = 100_0000 @@ -52,7 +52,7 @@ var maxNotValidBeforeDeltaKey = []byte{10} // newNotary returns Notary native contract. func newNotary() *Notary { - n := &Notary{ContractMD: *interop.NewContractMD(notaryName)} + n := &Notary{ContractMD: *interop.NewContractMD(nativenames.Notary)} n.ContractID = notaryContractID desc := newDescriptor("onPayment", smartcontract.VoidType, @@ -98,14 +98,6 @@ func newNotary() *Notary { md = newMethodAndPrice(n.setMaxNotValidBeforeDelta, 300_0000, smartcontract.WriteStates) n.AddMethod(md, desc) - desc = newDescriptor("onPersist", smartcontract.VoidType) - md = newMethodAndPrice(getOnPersistWrapper(n.OnPersist), 0, smartcontract.WriteStates) - n.AddMethod(md, desc) - - desc = newDescriptor("postPersist", smartcontract.VoidType) - md = newMethodAndPrice(getOnPersistWrapper(postPersistBase), 0, smartcontract.WriteStates) - n.AddMethod(md, desc) - return n } @@ -166,15 +158,15 @@ func (n *Notary) OnPersist(ic *interop.Context) error { return nil } -// OnPersistEnd updates cached Policy values if they've been changed -func (n *Notary) OnPersistEnd(dao dao.DAO) error { +// PostPersist implements Contract interface. +func (n *Notary) PostPersist(ic *interop.Context) error { + n.lock.Lock() + defer n.lock.Unlock() if n.isValid { return nil } - n.lock.Lock() - defer n.lock.Unlock() - n.maxNotValidBeforeDelta = getUint32WithKey(n.ContractID, dao, maxNotValidBeforeDeltaKey, defaultMaxNotValidBeforeDelta) + n.maxNotValidBeforeDelta = getUint32WithKey(n.ContractID, ic.DAO, maxNotValidBeforeDeltaKey, defaultMaxNotValidBeforeDelta) n.isValid = true return nil } @@ -279,7 +271,7 @@ func (n *Notary) withdraw(ic *interop.Context, args []stackitem.Item) stackitem. if ic.Chain.BlockHeight() < deposit.Till { return stackitem.NewBool(false) } - cs, err := ic.DAO.GetContractState(n.GAS.Hash) + cs, err := ic.GetContract(n.GAS.Hash) if err != nil { panic(fmt.Errorf("failed to get GAS contract state: %w", err)) } diff --git a/pkg/core/native/oracle.go b/pkg/core/native/oracle.go index 92ff3f57c..49b23ada7 100644 --- a/pkg/core/native/oracle.go +++ b/pkg/core/native/oracle.go @@ -11,6 +11,7 @@ import ( "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/interopnames" + "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/storage" "github.com/nspcc-dev/neo-go/pkg/core/transaction" @@ -36,11 +37,7 @@ type Oracle struct { } const ( - oracleContractID = -4 - oracleName = "Oracle" -) - -const ( + oracleContractID = -4 maxURLLength = 256 maxFilterLength = 128 maxCallbackLength = 32 @@ -58,8 +55,8 @@ var ( func init() { w := io.NewBufBinWriter() - emit.String(w.BinWriter, oracleName) - emit.Syscall(w.BinWriter, interopnames.NeoNativeCall) + emit.String(w.BinWriter, nativenames.Oracle) + emit.Syscall(w.BinWriter, interopnames.SystemContractCallNative) oracleInvokeScript = w.Bytes() h := hash.Hash160(oracleInvokeScript) @@ -102,7 +99,7 @@ func GetOracleResponseScript() []byte { } func newOracle() *Oracle { - o := &Oracle{ContractMD: *interop.NewContractMD(oracleName)} + o := &Oracle{ContractMD: *interop.NewContractMD(nativenames.Oracle)} o.ContractID = oracleContractID desc := newDescriptor("request", smartcontract.VoidType, @@ -122,15 +119,6 @@ func newOracle() *Oracle { md = newMethodAndPrice(o.verify, 100_0000, smartcontract.NoneFlag) o.AddMethod(md, desc) - pp := chainOnPersist(postPersistBase, o.PostPersist) - desc = newDescriptor("postPersist", smartcontract.VoidType) - md = newMethodAndPrice(getOnPersistWrapper(pp), 0, smartcontract.WriteStates) - o.AddMethod(md, desc) - - desc = newDescriptor("onPersist", smartcontract.VoidType) - md = newMethodAndPrice(getOnPersistWrapper(onPersistBase), 0, smartcontract.WriteStates) - o.AddMethod(md, desc) - o.AddEvent("OracleRequest", manifest.NewParameter("Id", smartcontract.IntegerType), manifest.NewParameter("RequestContract", smartcontract.Hash160Type), manifest.NewParameter("Url", smartcontract.StringType), @@ -141,6 +129,11 @@ func newOracle() *Oracle { return o } +// OnPersist implements Contract interface. +func (o *Oracle) OnPersist(ic *interop.Context) error { + return nil +} + // PostPersist represents `postPersist` method. func (o *Oracle) PostPersist(ic *interop.Context) error { var nodes keys.PublicKeys @@ -255,7 +248,7 @@ func (o *Oracle) FinishInternal(ic *interop.Context) error { stackitem.Make(resp.Code), stackitem.Make(resp.Result), } - cs, err := ic.DAO.GetContractState(req.CallbackContract) + cs, err := ic.GetContract(req.CallbackContract) if err != nil { return err } @@ -311,7 +304,7 @@ func (o *Oracle) RequestInternal(ic *interop.Context, url string, filter *string } // Should be executed from contract. - _, err := ic.DAO.GetContractState(ic.VM.GetCallingScriptHash()) + _, err := ic.GetContract(ic.VM.GetCallingScriptHash()) if err != nil { return err } diff --git a/pkg/core/native/policy.go b/pkg/core/native/policy.go index 79a7897d9..192aa7728 100644 --- a/pkg/core/native/policy.go +++ b/pkg/core/native/policy.go @@ -9,6 +9,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/block" "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/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/network/payload" @@ -19,7 +20,6 @@ import ( ) const ( - policyName = "Policy" policyContractID = -3 defaultMaxBlockSize = 1024 * 256 @@ -69,7 +69,7 @@ var _ interop.Contract = (*Policy)(nil) // newPolicy returns Policy native contract. func newPolicy() *Policy { - p := &Policy{ContractMD: *interop.NewContractMD(policyName)} + p := &Policy{ContractMD: *interop.NewContractMD(nativenames.Policy)} p.ContractID = policyContractID @@ -124,13 +124,6 @@ func newPolicy() *Policy { md = newMethodAndPrice(p.unblockAccount, 3000000, smartcontract.WriteStates) p.AddMethod(md, desc) - desc = newDescriptor("onPersist", smartcontract.VoidType) - md = newMethodAndPrice(getOnPersistWrapper(p.OnPersist), 0, smartcontract.WriteStates) - p.AddMethod(md, desc) - - desc = newDescriptor("postPersist", smartcontract.VoidType) - md = newMethodAndPrice(getOnPersistWrapper(postPersistBase), 0, smartcontract.WriteStates) - p.AddMethod(md, desc) return p } @@ -157,22 +150,22 @@ func (p *Policy) OnPersist(ic *interop.Context) error { return nil } -// OnPersistEnd updates cached Policy values if they've been changed -func (p *Policy) OnPersistEnd(dao dao.DAO) error { +// PostPersist implements Contract interface. +func (p *Policy) PostPersist(ic *interop.Context) error { + p.lock.Lock() + defer p.lock.Unlock() if p.isValid { return nil } - p.lock.Lock() - defer p.lock.Unlock() - p.maxTransactionsPerBlock = getUint32WithKey(p.ContractID, dao, maxTransactionsPerBlockKey, defaultMaxTransactionsPerBlock) - p.maxBlockSize = getUint32WithKey(p.ContractID, dao, maxBlockSizeKey, defaultMaxBlockSize) - p.feePerByte = getInt64WithKey(p.ContractID, dao, feePerByteKey, defaultFeePerByte) - p.maxBlockSystemFee = getInt64WithKey(p.ContractID, dao, maxBlockSystemFeeKey, defaultMaxBlockSystemFee) + p.maxTransactionsPerBlock = getUint32WithKey(p.ContractID, ic.DAO, maxTransactionsPerBlockKey, defaultMaxTransactionsPerBlock) + p.maxBlockSize = getUint32WithKey(p.ContractID, ic.DAO, maxBlockSizeKey, defaultMaxBlockSize) + p.feePerByte = getInt64WithKey(p.ContractID, ic.DAO, feePerByteKey, defaultFeePerByte) + p.maxBlockSystemFee = getInt64WithKey(p.ContractID, ic.DAO, maxBlockSystemFeeKey, defaultMaxBlockSystemFee) p.maxVerificationGas = defaultMaxVerificationGas p.blockedAccounts = make([]util.Uint160, 0) - siMap, err := dao.GetStorageItemsWithPrefix(p.ContractID, []byte{blockedAccountPrefix}) + siMap, err := ic.DAO.GetStorageItemsWithPrefix(p.ContractID, []byte{blockedAccountPrefix}) if err != nil { return fmt.Errorf("failed to get blocked accounts from storage: %w", err) } diff --git a/pkg/core/native/util.go b/pkg/core/native/util.go index 38a17e58a..d70d88799 100644 --- a/pkg/core/native/util.go +++ b/pkg/core/native/util.go @@ -9,6 +9,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/storage" "github.com/nspcc-dev/neo-go/pkg/io" + "github.com/nspcc-dev/neo-go/pkg/util" ) func getSerializableFromDAO(id int32, d dao.DAO, key []byte, item io.Serializable) error { @@ -71,3 +72,11 @@ func checkValidators(ic *interop.Context) (bool, error) { } return runtime.CheckHashedWitness(ic, prevBlock.NextConsensus) } + +// makeUint160Key creates a key from account script hash. +func makeUint160Key(prefix byte, h util.Uint160) []byte { + k := make([]byte, util.Uint160Size+1) + k[0] = prefix + copy(k[1:], h.BytesBE()) + return k +} diff --git a/pkg/core/native_contract_test.go b/pkg/core/native_contract_test.go index 97b70cad1..074a0c7ce 100644 --- a/pkg/core/native_contract_test.go +++ b/pkg/core/native_contract_test.go @@ -1,6 +1,7 @@ package core import ( + "errors" "math/big" "testing" @@ -33,18 +34,19 @@ func (tn *testNative) Metadata() *interop.ContractMD { return &tn.meta } -func (tn *testNative) OnPersist(ic *interop.Context, _ []stackitem.Item) stackitem.Item { - if ic.Trigger != trigger.OnPersist { - panic("invalid trigger") - } +func (tn *testNative) OnPersist(ic *interop.Context) error { select { case tn.blocks <- ic.Block.Index: - return stackitem.NewBool(true) + return nil default: - return stackitem.NewBool(false) + return errors.New("can't send index") } } +func (tn *testNative) PostPersist(ic *interop.Context) error { + return nil +} + var _ interop.Contract = (*testNative)(nil) // registerNative registers native contract in the blockchain. @@ -106,10 +108,6 @@ func newTestNative() *testNative { RequiredFlags: smartcontract.NoneFlag} tn.meta.AddMethod(md, desc) - desc = &manifest.Method{Name: "onPersist", ReturnType: smartcontract.BoolType} - md = &interop.MethodAndPrice{Func: tn.OnPersist, RequiredFlags: smartcontract.WriteStates} - tn.meta.AddMethod(md, desc) - return tn } @@ -138,12 +136,11 @@ func toUint160(item stackitem.Item) util.Uint160 { } func (tn *testNative) call(ic *interop.Context, args []stackitem.Item, checkReturn vm.CheckReturnState) { - h := toUint160(args[0]) - bs, err := args[1].TryBytes() + cs, err := ic.GetContract(toUint160(args[0])) if err != nil { panic(err) } - cs, err := ic.DAO.GetContractState(h) + bs, err := args[1].TryBytes() if err != nil { panic(err) } @@ -171,7 +168,8 @@ func TestNativeContract_Invoke(t *testing.T) { tn := newTestNative() chain.registerNative(tn) - err := chain.dao.PutContractState(&state.Contract{ + err := chain.contracts.Management.PutContractState(chain.dao, &state.Contract{ + ID: 1, Script: tn.meta.Script, Hash: tn.meta.Hash, Manifest: tn.meta.Manifest, @@ -205,7 +203,8 @@ func TestNativeContract_InvokeInternal(t *testing.T) { tn := newTestNative() chain.registerNative(tn) - err := chain.dao.PutContractState(&state.Contract{ + err := chain.contracts.Management.PutContractState(chain.dao, &state.Contract{ + ID: 1, Script: tn.meta.Script, Manifest: tn.meta.Manifest, }) @@ -245,25 +244,36 @@ func TestNativeContract_InvokeOtherContract(t *testing.T) { tn := newTestNative() chain.registerNative(tn) - err := chain.dao.PutContractState(&state.Contract{ + err := chain.contracts.Management.PutContractState(chain.dao, &state.Contract{ + ID: 1, Hash: tn.meta.Hash, Script: tn.meta.Script, Manifest: tn.meta.Manifest, }) require.NoError(t, err) - cs, _ := getTestContractState() - require.NoError(t, chain.dao.PutContractState(cs)) + var drainTN = func(t *testing.T) { + select { + case <-tn.blocks: + default: + require.Fail(t, "testNative didn't send us block") + } + } + + cs, _ := getTestContractState(chain) + require.NoError(t, chain.contracts.Management.PutContractState(chain.dao, cs)) t.Run("non-native, no return", func(t *testing.T) { res, err := invokeContractMethod(chain, testSumPrice*4+10000, tn.Metadata().Hash, "callOtherContractNoReturn", cs.Hash, "justReturn", []interface{}{}) require.NoError(t, err) + drainTN(t) checkResult(t, res, stackitem.Null{}) // simple call is done with EnsureNotEmpty }) t.Run("non-native, with return", func(t *testing.T) { res, err := invokeContractMethod(chain, testSumPrice*4+10000, tn.Metadata().Hash, "callOtherContractWithReturn", cs.Hash, "ret7", []interface{}{}) require.NoError(t, err) + drainTN(t) checkResult(t, res, stackitem.Make(8)) }) } diff --git a/pkg/core/native_designate_test.go b/pkg/core/native_designate_test.go index d318c8772..b381a923f 100644 --- a/pkg/core/native_designate_test.go +++ b/pkg/core/native_designate_test.go @@ -134,7 +134,6 @@ func TestDesignate_DesignateAsRole(t *testing.T) { setSigner(tx, testchain.CommitteeScriptHash()) err = des.DesignateAsRole(ic, native.RoleOracle, keys.PublicKeys{pub}) require.NoError(t, err) - require.NoError(t, des.OnPersistEnd(ic.DAO)) pubs, index, err = des.GetDesignatedByRole(ic.DAO, native.RoleOracle, bl.Index+1) require.NoError(t, err) @@ -152,7 +151,6 @@ func TestDesignate_DesignateAsRole(t *testing.T) { pub1 := priv.PublicKey() err = des.DesignateAsRole(ic, native.RoleStateValidator, keys.PublicKeys{pub1}) require.NoError(t, err) - require.NoError(t, des.OnPersistEnd(ic.DAO)) pubs, index, err = des.GetDesignatedByRole(ic.DAO, native.RoleOracle, 255) require.NoError(t, err) @@ -172,7 +170,6 @@ func TestDesignate_DesignateAsRole(t *testing.T) { err = des.DesignateAsRole(ic, native.RoleP2PNotary, keys.PublicKeys{pub1}) require.NoError(t, err) - require.NoError(t, des.OnPersistEnd(ic.DAO)) pubs, index, err = des.GetDesignatedByRole(ic.DAO, native.RoleP2PNotary, 255) require.NoError(t, err) diff --git a/pkg/core/native_management_test.go b/pkg/core/native_management_test.go new file mode 100644 index 000000000..43204e451 --- /dev/null +++ b/pkg/core/native_management_test.go @@ -0,0 +1,362 @@ +package core + +import ( + "encoding/json" + "math/big" + "testing" + + "github.com/nspcc-dev/neo-go/internal/testchain" + "github.com/nspcc-dev/neo-go/pkg/config" + "github.com/nspcc-dev/neo-go/pkg/core/state" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/nef" + "github.com/nspcc-dev/neo-go/pkg/vm" + "github.com/nspcc-dev/neo-go/pkg/vm/opcode" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" + "github.com/stretchr/testify/require" +) + +func TestContractDeploy(t *testing.T) { + bc := newTestChain(t) + defer bc.Close() + + // nef.NewFile() cares about version a lot. + config.Version = "0.90.0-test" + mgmtHash := bc.ManagementContractHash() + cs1, _ := getTestContractState(bc) + cs1.ID = 1 + cs1.Hash = state.CreateContractHash(testchain.MultisigScriptHash(), cs1.Script) + manif1, err := json.Marshal(cs1.Manifest) + require.NoError(t, err) + nef1, err := nef.NewFile(cs1.Script) + require.NoError(t, err) + nef1b, err := nef1.Bytes() + require.NoError(t, err) + + t.Run("no NEF", func(t *testing.T) { + res, err := invokeContractMethod(bc, 10_00000000, mgmtHash, "deploy", nil, manif1) + require.NoError(t, err) + checkFAULTState(t, res) + }) + t.Run("no manifest", func(t *testing.T) { + res, err := invokeContractMethod(bc, 10_00000000, mgmtHash, "deploy", nef1b, nil) + require.NoError(t, err) + checkFAULTState(t, res) + }) + t.Run("int for NEF", func(t *testing.T) { + res, err := invokeContractMethod(bc, 10_00000000, mgmtHash, "deploy", int64(1), manif1) + require.NoError(t, err) + checkFAULTState(t, res) + }) + t.Run("zero-length NEF", func(t *testing.T) { + res, err := invokeContractMethod(bc, 10_00000000, mgmtHash, "deploy", []byte{}, manif1) + require.NoError(t, err) + checkFAULTState(t, res) + }) + t.Run("array for NEF", func(t *testing.T) { + res, err := invokeContractMethod(bc, 10_00000000, mgmtHash, "deploy", []interface{}{int64(1)}, manif1) + require.NoError(t, err) + checkFAULTState(t, res) + }) + t.Run("int for manifest", func(t *testing.T) { + res, err := invokeContractMethod(bc, 10_00000000, mgmtHash, "deploy", nef1b, int64(1)) + require.NoError(t, err) + checkFAULTState(t, res) + }) + t.Run("zero-length manifest", func(t *testing.T) { + res, err := invokeContractMethod(bc, 10_00000000, mgmtHash, "deploy", nef1b, []byte{}) + require.NoError(t, err) + checkFAULTState(t, res) + }) + t.Run("too long manifest", func(t *testing.T) { + res, err := invokeContractMethod(bc, 100_00000000, mgmtHash, "deploy", nef1b, append(manif1, make([]byte, manifest.MaxManifestSize)...)) + require.NoError(t, err) + checkFAULTState(t, res) + }) + t.Run("array for manifest", func(t *testing.T) { + res, err := invokeContractMethod(bc, 10_00000000, mgmtHash, "deploy", nef1b, []interface{}{int64(1)}) + require.NoError(t, err) + checkFAULTState(t, res) + }) + t.Run("invalid manifest", func(t *testing.T) { + pkey, err := keys.NewPrivateKey() + require.NoError(t, err) + + var badManifest = cs1.Manifest + badManifest.Groups = []manifest.Group{manifest.Group{PublicKey: pkey.PublicKey(), Signature: make([]byte, 64)}} + manifB, err := json.Marshal(badManifest) + require.NoError(t, err) + + res, err := invokeContractMethod(bc, 10_00000000, mgmtHash, "deploy", nef1b, manifB) + require.NoError(t, err) + checkFAULTState(t, res) + }) + t.Run("not enough GAS", func(t *testing.T) { + res, err := invokeContractMethod(bc, 1_00000000, mgmtHash, "deploy", nef1b, manif1) + require.NoError(t, err) + checkFAULTState(t, res) + }) + t.Run("positive", func(t *testing.T) { + res, err := invokeContractMethod(bc, 10_00000000, mgmtHash, "deploy", nef1b, manif1) + require.NoError(t, err) + require.Equal(t, vm.HaltState, res.VMState) + require.Equal(t, 1, len(res.Stack)) + compareContractStates(t, cs1, res.Stack[0]) + t.Run("_deploy called", func(t *testing.T) { + res, err := invokeContractMethod(bc, 1_00000000, cs1.Hash, "getValue") + require.NoError(t, err) + require.Equal(t, 1, len(res.Stack)) + require.Equal(t, []byte("create"), res.Stack[0].Value()) + }) + }) + t.Run("contract already exists", func(t *testing.T) { + res, err := invokeContractMethod(bc, 10_00000000, mgmtHash, "deploy", nef1b, manif1) + require.NoError(t, err) + checkFAULTState(t, res) + }) + t.Run("failed _deploy", func(t *testing.T) { + deployScript := []byte{byte(opcode.ABORT)} + m := manifest.NewManifest("TestDeployAbort") + m.ABI.Methods = []manifest.Method{ + { + Name: manifest.MethodDeploy, + Offset: 0, + Parameters: []manifest.Parameter{ + manifest.NewParameter("isUpdate", smartcontract.BoolType), + }, + ReturnType: smartcontract.VoidType, + }, + } + nefD, err := nef.NewFile(deployScript) + require.NoError(t, err) + nefDb, err := nefD.Bytes() + require.NoError(t, err) + manifD, err := json.Marshal(m) + require.NoError(t, err) + res, err := invokeContractMethod(bc, 10_00000000, mgmtHash, "deploy", nefDb, manifD) + require.NoError(t, err) + checkFAULTState(t, res) + }) + t.Run("bad _deploy", func(t *testing.T) { // invalid _deploy signature + deployScript := []byte{byte(opcode.RET)} + m := manifest.NewManifest("TestBadDeploy") + m.ABI.Methods = []manifest.Method{ + { + Name: manifest.MethodDeploy, + Offset: 0, + Parameters: []manifest.Parameter{ + manifest.NewParameter("isUpdate", smartcontract.BoolType), + manifest.NewParameter("param", smartcontract.IntegerType), + }, + ReturnType: smartcontract.VoidType, + }, + } + nefD, err := nef.NewFile(deployScript) + require.NoError(t, err) + nefDb, err := nefD.Bytes() + require.NoError(t, err) + manifD, err := json.Marshal(m) + require.NoError(t, err) + res, err := invokeContractMethod(bc, 10_00000000, mgmtHash, "deploy", nefDb, manifD) + require.NoError(t, err) + checkFAULTState(t, res) + }) +} + +func TestContractUpdate(t *testing.T) { + bc := newTestChain(t) + defer bc.Close() + + // nef.NewFile() cares about version a lot. + config.Version = "0.90.0-test" + mgmtHash := bc.ManagementContractHash() + cs1, _ := getTestContractState(bc) + // Allow calling management contract. + cs1.Manifest.Permissions = []manifest.Permission{*manifest.NewPermission(manifest.PermissionWildcard)} + err := bc.contracts.Management.PutContractState(bc.dao, cs1) + require.NoError(t, err) + manif1, err := json.Marshal(cs1.Manifest) + require.NoError(t, err) + nef1, err := nef.NewFile(cs1.Script) + require.NoError(t, err) + nef1b, err := nef1.Bytes() + require.NoError(t, err) + + t.Run("no contract", func(t *testing.T) { + res, err := invokeContractMethod(bc, 10_00000000, mgmtHash, "update", nef1b, manif1) + require.NoError(t, err) + checkFAULTState(t, res) + }) + t.Run("zero-length NEF", func(t *testing.T) { + res, err := invokeContractMethod(bc, 10_00000000, cs1.Hash, "update", []byte{}, manif1) + require.NoError(t, err) + checkFAULTState(t, res) + }) + t.Run("zero-length manifest", func(t *testing.T) { + res, err := invokeContractMethod(bc, 10_00000000, cs1.Hash, "update", nef1b, []byte{}) + require.NoError(t, err) + checkFAULTState(t, res) + }) + t.Run("not enough GAS", func(t *testing.T) { + res, err := invokeContractMethod(bc, 1_00000000, cs1.Hash, "update", nef1b, manif1) + require.NoError(t, err) + checkFAULTState(t, res) + }) + t.Run("no real params", func(t *testing.T) { + res, err := invokeContractMethod(bc, 10_00000000, cs1.Hash, "update", nil, nil) + require.NoError(t, err) + checkFAULTState(t, res) + }) + t.Run("invalid manifest", func(t *testing.T) { + pkey, err := keys.NewPrivateKey() + require.NoError(t, err) + + var badManifest = cs1.Manifest + badManifest.Groups = []manifest.Group{manifest.Group{PublicKey: pkey.PublicKey(), Signature: make([]byte, 64)}} + manifB, err := json.Marshal(badManifest) + require.NoError(t, err) + + res, err := invokeContractMethod(bc, 10_00000000, cs1.Hash, "update", nef1b, manifB) + require.NoError(t, err) + checkFAULTState(t, res) + }) + + cs1.Script = append(cs1.Script, byte(opcode.RET)) + nef1, err = nef.NewFile(cs1.Script) + require.NoError(t, err) + nef1b, err = nef1.Bytes() + require.NoError(t, err) + cs1.UpdateCounter++ + + t.Run("update script, positive", func(t *testing.T) { + res, err := invokeContractMethod(bc, 10_00000000, cs1.Hash, "update", nef1b, nil) + require.NoError(t, err) + require.Equal(t, vm.HaltState, res.VMState) + t.Run("_deploy called", func(t *testing.T) { + res, err := invokeContractMethod(bc, 1_00000000, cs1.Hash, "getValue") + require.NoError(t, err) + require.Equal(t, 1, len(res.Stack)) + require.Equal(t, []byte("update"), res.Stack[0].Value()) + }) + t.Run("check contract", func(t *testing.T) { + res, err := invokeContractMethod(bc, 1_00000000, mgmtHash, "getContract", cs1.Hash.BytesBE()) + require.NoError(t, err) + require.Equal(t, 1, len(res.Stack)) + compareContractStates(t, cs1, res.Stack[0]) + }) + }) + + cs1.Manifest.Extra = "update me" + manif1, err = json.Marshal(cs1.Manifest) + require.NoError(t, err) + cs1.UpdateCounter++ + + t.Run("update manifest, positive", func(t *testing.T) { + res, err := invokeContractMethod(bc, 10_00000000, cs1.Hash, "update", nil, manif1) + require.NoError(t, err) + require.Equal(t, vm.HaltState, res.VMState) + t.Run("check contract", func(t *testing.T) { + res, err := invokeContractMethod(bc, 1_00000000, mgmtHash, "getContract", cs1.Hash.BytesBE()) + require.NoError(t, err) + require.Equal(t, 1, len(res.Stack)) + compareContractStates(t, cs1, res.Stack[0]) + }) + }) + + cs1.Script = append(cs1.Script, byte(opcode.ABORT)) + nef1, err = nef.NewFile(cs1.Script) + require.NoError(t, err) + nef1b, err = nef1.Bytes() + require.NoError(t, err) + cs1.Manifest.Extra = "update me once more" + manif1, err = json.Marshal(cs1.Manifest) + require.NoError(t, err) + cs1.UpdateCounter++ + + t.Run("update both script and manifest", func(t *testing.T) { + res, err := invokeContractMethod(bc, 10_00000000, cs1.Hash, "update", nef1b, manif1) + require.NoError(t, err) + require.Equal(t, vm.HaltState, res.VMState) + t.Run("check contract", func(t *testing.T) { + res, err := invokeContractMethod(bc, 1_00000000, mgmtHash, "getContract", cs1.Hash.BytesBE()) + require.NoError(t, err) + require.Equal(t, 1, len(res.Stack)) + compareContractStates(t, cs1, res.Stack[0]) + }) + }) +} + +func TestGetContract(t *testing.T) { + bc := newTestChain(t) + defer bc.Close() + + mgmtHash := bc.ManagementContractHash() + cs1, _ := getTestContractState(bc) + err := bc.contracts.Management.PutContractState(bc.dao, cs1) + require.NoError(t, err) + + t.Run("bad parameter type", func(t *testing.T) { + res, err := invokeContractMethod(bc, 1_00000000, mgmtHash, "getContract", []interface{}{int64(1)}) + require.NoError(t, err) + checkFAULTState(t, res) + }) + t.Run("not a hash", func(t *testing.T) { + res, err := invokeContractMethod(bc, 1_00000000, mgmtHash, "getContract", []byte{1, 2, 3}) + require.NoError(t, err) + checkFAULTState(t, res) + }) + t.Run("positive", func(t *testing.T) { + res, err := invokeContractMethod(bc, 1_00000000, mgmtHash, "getContract", cs1.Hash.BytesBE()) + require.NoError(t, err) + require.Equal(t, 1, len(res.Stack)) + compareContractStates(t, cs1, res.Stack[0]) + }) +} + +func TestContractDestroy(t *testing.T) { + bc := newTestChain(t) + defer bc.Close() + + mgmtHash := bc.ManagementContractHash() + cs1, _ := getTestContractState(bc) + // Allow calling management contract. + cs1.Manifest.Permissions = []manifest.Permission{*manifest.NewPermission(manifest.PermissionWildcard)} + err := bc.contracts.Management.PutContractState(bc.dao, cs1) + require.NoError(t, err) + err = bc.dao.PutStorageItem(cs1.ID, []byte{1, 2, 3}, &state.StorageItem{Value: []byte{3, 2, 1}}) + require.NoError(t, err) + + t.Run("no contract", func(t *testing.T) { + res, err := invokeContractMethod(bc, 1_00000000, mgmtHash, "destroy") + require.NoError(t, err) + checkFAULTState(t, res) + }) + t.Run("positive", func(t *testing.T) { + res, err := invokeContractMethod(bc, 1_00000000, cs1.Hash, "destroy") + require.NoError(t, err) + require.Equal(t, vm.HaltState, res.VMState) + t.Run("check contract", func(t *testing.T) { + res, err := invokeContractMethod(bc, 1_00000000, mgmtHash, "getContract", cs1.Hash.BytesBE()) + require.NoError(t, err) + checkFAULTState(t, res) + }) + + }) +} + +func compareContractStates(t *testing.T, expected *state.Contract, actual stackitem.Item) { + act, ok := actual.Value().([]stackitem.Item) + require.True(t, ok) + + expectedManifest, err := json.Marshal(expected.Manifest) + require.NoError(t, err) + + require.Equal(t, 5, len(act)) + require.Equal(t, expected.ID, int32(act[0].Value().(*big.Int).Int64())) + require.Equal(t, expected.UpdateCounter, uint16(act[1].Value().(*big.Int).Int64())) + require.Equal(t, expected.Hash.BytesBE(), act[2].Value().([]byte)) + require.Equal(t, expected.Script, act[3].Value().([]byte)) + require.Equal(t, expectedManifest, act[4].Value().([]byte)) +} diff --git a/pkg/core/native_neo_test.go b/pkg/core/native_neo_test.go index 0e2da1511..ee8be24e7 100644 --- a/pkg/core/native_neo_test.go +++ b/pkg/core/native_neo_test.go @@ -225,7 +225,6 @@ func TestNEO_SetGasPerBlock(t *testing.T) { ok, err := neo.SetGASPerBlock(ic, 10, big.NewInt(native.GASFactor*2)) require.NoError(t, err) require.True(t, ok) - neo.OnPersistEnd(ic.DAO) _, err = ic.DAO.Persist() require.NoError(t, err) @@ -242,7 +241,6 @@ func TestNEO_SetGasPerBlock(t *testing.T) { }) }) - neo.OnPersistEnd(ic.DAO) g := neo.GetGASPerBlock(ic.DAO, 9) require.EqualValues(t, 5*native.GASFactor, g.Int64()) @@ -309,8 +307,8 @@ func TestNEO_TransferOnPayment(t *testing.T) { bc := newTestChain(t) defer bc.Close() - cs, _ := getTestContractState() - require.NoError(t, bc.dao.PutContractState(cs)) + cs, _ := getTestContractState(bc) + require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs)) const amount = 2 tx := transferTokenFromMultisigAccount(t, bc, cs.Hash, bc.contracts.NEO.Hash, amount) diff --git a/pkg/core/native_oracle_test.go b/pkg/core/native_oracle_test.go index 7cea35cea..80555bb15 100644 --- a/pkg/core/native_oracle_test.go +++ b/pkg/core/native_oracle_test.go @@ -109,7 +109,7 @@ func TestOracle_Request(t *testing.T) { orc := bc.contracts.Oracle cs := getOracleContractState(orc.Hash) - require.NoError(t, bc.dao.PutContractState(cs)) + require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs)) gasForResponse := int64(2000_1234) var filter = "flt" @@ -144,7 +144,6 @@ func TestOracle_Request(t *testing.T) { ic.VM.LoadScript([]byte{byte(opcode.RET)}) err = bc.contracts.Designate.DesignateAsRole(ic, native.RoleOracle, keys.PublicKeys{pub}) require.NoError(t, err) - require.NoError(t, bc.contracts.Designate.OnPersistEnd(ic.DAO)) tx = transaction.New(netmode.UnitTestNet, native.GetOracleResponseScript(), 0) ic.Tx = tx diff --git a/pkg/core/state/coin.go b/pkg/core/state/coin.go deleted file mode 100644 index 650d3f7fa..000000000 --- a/pkg/core/state/coin.go +++ /dev/null @@ -1,12 +0,0 @@ -package state - -// Coin represents the state of a coin. -type Coin uint8 - -// Viable Coin constants. -const ( - CoinConfirmed Coin = 0 - CoinSpent Coin = 1 << 1 - CoinClaimed Coin = 1 << 2 - CoinFrozen Coin = 1 << 5 -) diff --git a/pkg/core/state/contract.go b/pkg/core/state/contract.go index f616d4d79..743713fce 100644 --- a/pkg/core/state/contract.go +++ b/pkg/core/state/contract.go @@ -1,12 +1,18 @@ package state import ( + "encoding/json" + "errors" + "math" + "math/big" + "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/util" "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" ) // Contract holds information about a smart contract in the NEO blockchain. @@ -19,21 +25,81 @@ type Contract struct { } // DecodeBinary implements Serializable interface. -func (cs *Contract) DecodeBinary(br *io.BinReader) { - cs.ID = int32(br.ReadU32LE()) - cs.UpdateCounter = br.ReadU16LE() - cs.Hash.DecodeBinary(br) - cs.Script = br.ReadVarBytes() - cs.Manifest.DecodeBinary(br) +func (c *Contract) DecodeBinary(r *io.BinReader) { + si := stackitem.DecodeBinaryStackItem(r) + if r.Err != nil { + return + } + r.Err = c.FromStackItem(si) } // EncodeBinary implements Serializable interface. -func (cs *Contract) EncodeBinary(bw *io.BinWriter) { - bw.WriteU32LE(uint32(cs.ID)) - bw.WriteU16LE(cs.UpdateCounter) - cs.Hash.EncodeBinary(bw) - bw.WriteVarBytes(cs.Script) - cs.Manifest.EncodeBinary(bw) +func (c *Contract) EncodeBinary(w *io.BinWriter) { + si, err := c.ToStackItem() + if err != nil { + w.Err = err + return + } + stackitem.EncodeBinaryStackItem(si, w) +} + +// ToStackItem converts state.Contract to stackitem.Item +func (c *Contract) ToStackItem() (stackitem.Item, error) { + manifest, err := json.Marshal(c.Manifest) + if err != nil { + return nil, err + } + return stackitem.NewArray([]stackitem.Item{ + stackitem.Make(c.ID), + stackitem.Make(c.UpdateCounter), + stackitem.NewByteArray(c.Hash.BytesBE()), + stackitem.NewByteArray(c.Script), + stackitem.NewByteArray(manifest), + }), nil +} + +// FromStackItem fills Contract's data from given stack itemized contract +// representation. +func (c *Contract) FromStackItem(item stackitem.Item) error { + arr, ok := item.Value().([]stackitem.Item) + if !ok { + return errors.New("not an array") + } + bi, ok := arr[0].Value().(*big.Int) + if !ok { + return errors.New("ID is not an integer") + } + if !bi.IsInt64() || bi.Int64() > math.MaxInt32 || bi.Int64() < math.MinInt32 { + return errors.New("ID not in int32 range") + } + c.ID = int32(bi.Int64()) + bi, ok = arr[1].Value().(*big.Int) + if !ok { + return errors.New("UpdateCounter is not an integer") + } + if !bi.IsInt64() || bi.Int64() > math.MaxUint16 || bi.Int64() < 0 { + return errors.New("UpdateCounter not in uint16 range") + } + c.UpdateCounter = uint16(bi.Int64()) + bytes, err := arr[2].TryBytes() + if err != nil { + return err + } + c.Hash, err = util.Uint160DecodeBytesBE(bytes) + if err != nil { + return err + } + bytes, err = arr[3].TryBytes() + if err != nil { + return err + } + c.Script = make([]byte, len(bytes)) + copy(c.Script, bytes) + bytes, err = arr[4].TryBytes() + if err != nil { + return err + } + return json.Unmarshal(bytes, &c.Manifest) } // CreateContractHash creates deployed contract hash from transaction sender diff --git a/pkg/core/state/contract_test.go b/pkg/core/state/contract_test.go index 5283b5acc..73d4e5ecf 100644 --- a/pkg/core/state/contract_test.go +++ b/pkg/core/state/contract_test.go @@ -1,6 +1,8 @@ package state import ( + "encoding/json" + "math" "testing" "github.com/nspcc-dev/neo-go/internal/testserdes" @@ -8,6 +10,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/stretchr/testify/require" ) @@ -58,3 +61,41 @@ func TestCreateContractHash(t *testing.T) { require.NoError(t, err) require.Equal(t, "e56e4ee87f89a70e9138432c387ad49f2ee5b55f", CreateContractHash(sender, script).StringLE()) } + +func TestContractFromStackItem(t *testing.T) { + var ( + id = stackitem.Make(42) + counter = stackitem.Make(11) + chash = stackitem.Make(util.Uint160{1, 2, 3}.BytesBE()) + script = stackitem.Make([]byte{0, 9, 8}) + manifest = manifest.DefaultManifest("stack item") + manifestB, _ = json.Marshal(manifest) + manifItem = stackitem.Make(manifestB) + + badCases = []struct { + name string + item stackitem.Item + }{ + {"not an array", stackitem.Make(1)}, + {"id is not a number", stackitem.Make([]stackitem.Item{manifItem, counter, chash, script, manifItem})}, + {"id is out of range", stackitem.Make([]stackitem.Item{stackitem.Make(math.MaxUint32), counter, chash, script, manifItem})}, + {"counter is not a number", stackitem.Make([]stackitem.Item{id, manifItem, chash, script, manifItem})}, + {"counter is out of range", stackitem.Make([]stackitem.Item{id, stackitem.Make(100500), chash, script, manifItem})}, + {"hash is not a byte string", stackitem.Make([]stackitem.Item{id, counter, stackitem.NewArray(nil), script, manifItem})}, + {"hash is not a hash", stackitem.Make([]stackitem.Item{id, counter, stackitem.Make([]byte{1, 2, 3}), script, manifItem})}, + {"script is not a byte string", stackitem.Make([]stackitem.Item{id, counter, chash, stackitem.NewArray(nil), manifItem})}, + {"manifest is not a byte string", stackitem.Make([]stackitem.Item{id, counter, chash, script, stackitem.NewArray(nil)})}, + {"manifest is not correct", stackitem.Make([]stackitem.Item{id, counter, chash, script, stackitem.Make(100500)})}, + } + ) + for _, cs := range badCases { + t.Run(cs.name, func(t *testing.T) { + var c = new(Contract) + err := c.FromStackItem(cs.item) + require.Error(t, err) + }) + } + var c = new(Contract) + err := c.FromStackItem(stackitem.Make([]stackitem.Item{id, counter, chash, script, manifItem})) + require.NoError(t, err) +} diff --git a/pkg/core/storage/store.go b/pkg/core/storage/store.go index 4e099498c..485d45104 100644 --- a/pkg/core/storage/store.go +++ b/pkg/core/storage/store.go @@ -13,7 +13,6 @@ const ( DataMPT KeyPrefix = 0x03 STAccount KeyPrefix = 0x40 STNotification KeyPrefix = 0x4d - STContract KeyPrefix = 0x50 STContractID KeyPrefix = 0x51 STStorage KeyPrefix = 0x70 STNEP17Transfers KeyPrefix = 0x72 @@ -21,7 +20,6 @@ const ( IXHeaderHashList KeyPrefix = 0x80 SYSCurrentBlock KeyPrefix = 0xc0 SYSCurrentHeader KeyPrefix = 0xc1 - SYSContractID KeyPrefix = 0xc2 SYSVersion KeyPrefix = 0xf0 ) diff --git a/pkg/core/storage/store_test.go b/pkg/core/storage/store_test.go index 9b1e579e0..c20904bbd 100644 --- a/pkg/core/storage/store_test.go +++ b/pkg/core/storage/store_test.go @@ -11,7 +11,6 @@ var ( DataBlock, DataTransaction, STAccount, - STContract, STStorage, IXHeaderHashList, SYSCurrentBlock, @@ -23,7 +22,6 @@ var ( 0x01, 0x02, 0x40, - 0x50, 0x70, 0x80, 0xc0, diff --git a/pkg/core/util.go b/pkg/core/util.go index eb85640b6..283c9d050 100644 --- a/pkg/core/util.go +++ b/pkg/core/util.go @@ -5,16 +5,12 @@ import ( "time" "github.com/nspcc-dev/neo-go/pkg/config" - "github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/core/block" - "github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" - "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/util" - "github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" ) @@ -56,10 +52,8 @@ func createGenesisBlock(cfg config.ProtocolConfiguration) (*block.Block, error) } b := &block.Block{ - Base: base, - Transactions: []*transaction.Transaction{ - deployNativeContracts(cfg.Magic), - }, + Base: base, + Transactions: []*transaction.Transaction{}, ConsensusData: block.ConsensusData{ PrimaryIndex: 0, Nonce: 2083236893, @@ -70,27 +64,6 @@ func createGenesisBlock(cfg config.ProtocolConfiguration) (*block.Block, error) return b, nil } -func deployNativeContracts(magic netmode.Magic) *transaction.Transaction { - buf := io.NewBufBinWriter() - emit.Syscall(buf.BinWriter, interopnames.NeoNativeDeploy) - script := buf.Bytes() - tx := transaction.New(magic, script, 0) - tx.Nonce = 0 - tx.Signers = []transaction.Signer{ - { - Account: hash.Hash160([]byte{byte(opcode.PUSH1)}), - Scopes: transaction.None, - }, - } - tx.Scripts = []transaction.Witness{ - { - InvocationScript: []byte{}, - VerificationScript: []byte{byte(opcode.PUSH1)}, - }, - } - return tx -} - func validatorsFromConfig(cfg config.ProtocolConfiguration) ([]*keys.PublicKey, error) { vs, err := committeeFromConfig(cfg) if err != nil { diff --git a/pkg/core/util_test.go b/pkg/core/util_test.go index dae606d64..ceec4237a 100644 --- a/pkg/core/util_test.go +++ b/pkg/core/util_test.go @@ -17,7 +17,7 @@ func TestGenesisBlockMainNet(t *testing.T) { block, err := createGenesisBlock(cfg.ProtocolConfiguration) require.NoError(t, err) - expect := "ecaee33262f1bc7c7c28f2b25b54a5d61d50670871f45c0c6fe755a40cbde4a8" + expect := "00c6803707b564153d444bfcdf3a13325fc96dda55cc8a740bbd543a1d752fda" assert.Equal(t, expect, block.Hash().StringLE()) } diff --git a/pkg/interop/blockchain/blockchain.go b/pkg/interop/blockchain/blockchain.go index a8d8e1659..fffd667fa 100644 --- a/pkg/interop/blockchain/blockchain.go +++ b/pkg/interop/blockchain/blockchain.go @@ -5,7 +5,6 @@ package blockchain import ( "github.com/nspcc-dev/neo-go/pkg/interop" - "github.com/nspcc-dev/neo-go/pkg/interop/contract" ) // Transaction represents a NEO transaction. It's similar to Transaction class @@ -95,11 +94,3 @@ func GetTransactionFromBlock(heightOrHash interface{}, index int) interop.Hash25 func GetTransactionHeight(hash interop.Hash256) int { return 0 } - -// GetContract returns contract found by the given script hash (160 bit in BE -// format represented as a slice of 20 bytes). Refer to the `contract` package -// for details on how to use the returned structure. This function uses -// `System.Blockchain.GetContract` syscall. -func GetContract(scriptHash interop.Hash160) *contract.Contract { - return &contract.Contract{} -} diff --git a/pkg/interop/contract/contract.go b/pkg/interop/contract/contract.go index cd8ab67e3..a42be36c5 100644 --- a/pkg/interop/contract/contract.go +++ b/pkg/interop/contract/contract.go @@ -5,15 +5,6 @@ package contract import "github.com/nspcc-dev/neo-go/pkg/interop" -// Contract represents a Neo contract and is used in interop functions. It's -// a data structure that you can manipulate with using functions from -// this package. It's similar in function to the Contract class in the Neo .net -// framework. -type Contract struct { - Script []byte - Manifest []byte -} - // CallFlag specifies valid call flags. type CallFlag byte @@ -30,30 +21,6 @@ const ( NoneFlag CallFlag = 0 ) -// Create creates a new contract using a set of input parameters: -// script contract's bytecode (limited in length by 1M) -// manifest contract's manifest (limited in length by 2 KiB) -// It returns this new created Contract when successful (and fails transaction -// if not). It uses `System.Contract.Create` syscall. -func Create(script []byte, manifest []byte) *Contract { - return &Contract{} -} - -// Update updates script and manifest of the calling contract (that is the one that calls Update) -// to the new ones. Its parameters have exactly the same semantics as for -// Create. The old contract will be deleted by this call, if it has any storage -// associated it will be migrated to the new contract. New contract is returned. -// This function uses `System.Contract.Update` syscall. -func Update(script []byte, manifest []byte) { - return -} - -// Destroy deletes calling contract (the one that calls Destroy) from the -// blockchain, so it's only possible to do that from the contract itself and -// not by any outside code. When contract is deleted all associated storage -// items are deleted too. This function uses `System.Contract.Destroy` syscall. -func Destroy() {} - // IsStandard checks if contract with provided hash is a standard signature/multisig contract. // This function uses `System.Contract.IsStandard` syscall. func IsStandard(h interop.Hash160) bool { diff --git a/pkg/network/helper_test.go b/pkg/network/helper_test.go index 346266f62..764e0efe5 100644 --- a/pkg/network/helper_test.go +++ b/pkg/network/helper_test.go @@ -264,6 +264,9 @@ func (chain *testChain) GetUtilityTokenBalance(uint160 util.Uint160) *big.Int { } panic("TODO") } +func (chain testChain) ManagementContractHash() util.Uint160 { + panic("TODO") +} func (chain *testChain) PoolTx(tx *transaction.Transaction, _ ...*mempool.Pool) error { return chain.poolTx(tx) diff --git a/pkg/rpc/client/client.go b/pkg/rpc/client/client.go index 628f342a2..bccd0d4a5 100644 --- a/pkg/rpc/client/client.go +++ b/pkg/rpc/client/client.go @@ -12,6 +12,7 @@ import ( "time" "github.com/nspcc-dev/neo-go/pkg/config/netmode" + "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/rpc/request" "github.com/nspcc-dev/neo-go/pkg/rpc/response" "github.com/nspcc-dev/neo-go/pkg/util" @@ -117,16 +118,21 @@ func (c *Client) Init() error { } c.network = version.Magic c.stateRootInHeader = version.StateRootInHeader - neoContractHash, err := c.GetContractStateByAddressOrName("neo") + neoContractHash, err := c.GetContractStateByAddressOrName(nativenames.Neo) if err != nil { return fmt.Errorf("failed to get NEO contract scripthash: %w", err) } - c.cache.nativeHashes["neo"] = neoContractHash.Hash - gasContractHash, err := c.GetContractStateByAddressOrName("gas") + c.cache.nativeHashes[nativenames.Neo] = neoContractHash.Hash + gasContractHash, err := c.GetContractStateByAddressOrName(nativenames.Gas) if err != nil { return fmt.Errorf("failed to get GAS contract scripthash: %w", err) } - c.cache.nativeHashes["gas"] = gasContractHash.Hash + c.cache.nativeHashes[nativenames.Gas] = gasContractHash.Hash + policyContractHash, err := c.GetContractStateByAddressOrName(nativenames.Policy) + if err != nil { + return fmt.Errorf("failed to get Policy contract scripthash: %w", err) + } + c.cache.nativeHashes[nativenames.Policy] = policyContractHash.Hash c.initDone = true return nil } diff --git a/pkg/rpc/client/policy.go b/pkg/rpc/client/policy.go index efffcd492..6c89baf68 100644 --- a/pkg/rpc/client/policy.go +++ b/pkg/rpc/client/policy.go @@ -3,14 +3,12 @@ package client import ( "fmt" + "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" ) -// PolicyContractHash represents a hash of native Policy contract. -var PolicyContractHash, _ = util.Uint160DecodeStringBE("e9ff4ca7cc252e1dfddb26315869cd79505906ce") - // GetMaxTransactionsPerBlock invokes `getMaxTransactionsPerBlock` method on a // native Policy contract. func (c *Client) GetMaxTransactionsPerBlock() (int64, error) { @@ -28,7 +26,10 @@ func (c *Client) GetFeePerByte() (int64, error) { } func (c *Client) invokeNativePolicyMethod(operation string) (int64, error) { - result, err := c.InvokeFunction(PolicyContractHash, operation, []smartcontract.Parameter{}, nil) + if !c.initDone { + return 0, errNetworkNotInitialized + } + result, err := c.InvokeFunction(c.cache.nativeHashes[nativenames.Policy], operation, []smartcontract.Parameter{}, nil) if err != nil { return 0, err } @@ -42,7 +43,10 @@ func (c *Client) invokeNativePolicyMethod(operation string) (int64, error) { // IsBlocked invokes `isBlocked` method on native Policy contract. func (c *Client) IsBlocked(hash util.Uint160) (bool, error) { - result, err := c.InvokeFunction(PolicyContractHash, "isBlocked", []smartcontract.Parameter{{ + if !c.initDone { + return false, errNetworkNotInitialized + } + result, err := c.InvokeFunction(c.cache.nativeHashes[nativenames.Policy], "isBlocked", []smartcontract.Parameter{{ Type: smartcontract.Hash160Type, Value: hash, }}, nil) diff --git a/pkg/rpc/client/rpc.go b/pkg/rpc/client/rpc.go index 65da7f66f..3f4b59c6d 100644 --- a/pkg/rpc/client/rpc.go +++ b/pkg/rpc/client/rpc.go @@ -4,7 +4,6 @@ import ( "encoding/hex" "errors" "fmt" - "strings" "github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/core" @@ -612,10 +611,9 @@ func (c *Client) StateRootInHeader() bool { return c.stateRootInHeader } -// GetNativeContractHash returns native contract hash by its name. It is not case-sensitive. +// GetNativeContractHash returns native contract hash by its name. func (c *Client) GetNativeContractHash(name string) (util.Uint160, error) { - lowercasedName := strings.ToLower(name) - hash, ok := c.cache.nativeHashes[lowercasedName] + hash, ok := c.cache.nativeHashes[name] if ok { return hash, nil } @@ -623,6 +621,6 @@ func (c *Client) GetNativeContractHash(name string) (util.Uint160, error) { if err != nil { return util.Uint160{}, err } - c.cache.nativeHashes[lowercasedName] = cs.Hash + c.cache.nativeHashes[name] = cs.Hash return cs.Hash, nil } diff --git a/pkg/rpc/client/rpc_test.go b/pkg/rpc/client/rpc_test.go index 3065538e5..589dfec16 100644 --- a/pkg/rpc/client/rpc_test.go +++ b/pkg/rpc/client/rpc_test.go @@ -1463,10 +1463,12 @@ func wrapInitResponse(r *request.In, resp string) string { response = resp } switch name { - case "neo": + case "NeoToken": response = `{"id":1,"jsonrpc":"2.0","result":{"id":-1,"script":"DANORU9Ba2d4Cw==","manifest":{"name":"NEO","abi":{"hash":"0xde5f57d430d3dece511cf975a8d37848cb9e0525","methods":[{"name":"name","offset":0,"parameters":null,"returntype":"String"},{"name":"symbol","offset":0,"parameters":null,"returntype":"String"},{"name":"decimals","offset":0,"parameters":null,"returntype":"Integer"},{"name":"totalSupply","offset":0,"parameters":null,"returntype":"Integer"},{"name":"balanceOf","offset":0,"parameters":[{"name":"account","type":"Hash160"}],"returntype":"Integer"},{"name":"transfer","offset":0,"parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"}],"returntype":"Boolean"},{"name":"onPersist","offset":0,"parameters":null,"returntype":"Void"},{"name":"postPersist","offset":0,"parameters":null,"returntype":"Void"},{"name":"unclaimedGas","offset":0,"parameters":[{"name":"account","type":"Hash160"},{"name":"end","type":"Integer"}],"returntype":"Integer"},{"name":"registerCandidate","offset":0,"parameters":[{"name":"pubkey","type":"PublicKey"}],"returntype":"Boolean"},{"name":"unregisterCandidate","offset":0,"parameters":[{"name":"pubkey","type":"PublicKey"}],"returntype":"Boolean"},{"name":"vote","offset":0,"parameters":[{"name":"account","type":"Hash160"},{"name":"pubkey","type":"PublicKey"}],"returntype":"Boolean"},{"name":"getCandidates","offset":0,"parameters":null,"returntype":"Array"},{"name":"getŠ”ommittee","offset":0,"parameters":null,"returntype":"Array"},{"name":"getNextBlockValidators","offset":0,"parameters":null,"returntype":"Array"},{"name":"getGasPerBlock","offset":0,"parameters":null,"returntype":"Integer"},{"name":"setGasPerBlock","offset":0,"parameters":[{"name":"gasPerBlock","type":"Integer"}],"returntype":"Boolean"}],"events":[{"name":"Transfer","parameters":null}]},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":["NEP-5"],"trusts":[],"safemethods":["name","symbol","decimals","totalSupply","balanceOf","unclaimedGas","getCandidates","getŠ”ommittee","getNextBlockValidators"],"extra":null},"hash":"0xde5f57d430d3dece511cf975a8d37848cb9e0525"}}` - case "gas": + case "GasToken": response = `{"id":1,"jsonrpc":"2.0","result":{"id":-2,"script":"DANHQVNBa2d4Cw==","manifest":{"name":"GAS","abi":{"hash":"0x668e0c1f9d7b70a99dd9e06eadd4c784d641afbc","methods":[{"name":"name","offset":0,"parameters":null,"returntype":"String"},{"name":"symbol","offset":0,"parameters":null,"returntype":"String"},{"name":"decimals","offset":0,"parameters":null,"returntype":"Integer"},{"name":"totalSupply","offset":0,"parameters":null,"returntype":"Integer"},{"name":"balanceOf","offset":0,"parameters":[{"name":"account","type":"Hash160"}],"returntype":"Integer"},{"name":"transfer","offset":0,"parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"}],"returntype":"Boolean"},{"name":"onPersist","offset":0,"parameters":null,"returntype":"Void"},{"name":"postPersist","offset":0,"parameters":null,"returntype":"Void"}],"events":[{"name":"Transfer","parameters":null}]},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":["NEP-5"],"trusts":[],"safemethods":["name","symbol","decimals","totalSupply","balanceOf"],"extra":null},"hash":"0x668e0c1f9d7b70a99dd9e06eadd4c784d641afbc"}}` + case "PolicyContract": + response = `{"id":1,"jsonrpc":"2.0","result":{"id":-3,"updatecounter":0,"hash":"0xac593e6183643940a9193f87c64ccf55ef19c529","script":"DAZQb2xpY3lBGvd7Zw==","manifest":{"name":"Policy","abi":{"methods":[{"name":"getMaxTransactionsPerBlock","offset":0,"parameters":null,"returntype":"Integer"},{"name":"getMaxBlockSize","offset":0,"parameters":null,"returntype":"Integer"},{"name":"getFeePerByte","offset":0,"parameters":null,"returntype":"Integer"},{"name":"isBlocked","offset":0,"parameters":[{"name":"account","type":"Hash160"}],"returntype":"Boolean"},{"name":"getMaxBlockSystemFee","offset":0,"parameters":null,"returntype":"Integer"},{"name":"setMaxBlockSize","offset":0,"parameters":[{"name":"value","type":"Integer"}],"returntype":"Boolean"},{"name":"setMaxTransactionsPerBlock","offset":0,"parameters":[{"name":"value","type":"Integer"}],"returntype":"Boolean"},{"name":"setFeePerByte","offset":0,"parameters":[{"name":"value","type":"Integer"}],"returntype":"Boolean"},{"name":"setMaxBlockSystemFee","offset":0,"parameters":[{"name":"value","type":"Integer"}],"returntype":"Boolean"},{"name":"blockAccount","offset":0,"parameters":[{"name":"account","type":"Hash160"}],"returntype":"Boolean"},{"name":"unblockAccount","offset":0,"parameters":[{"name":"account","type":"Hash160"}],"returntype":"Boolean"}],"events":[]},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":[],"trusts":[],"safemethods":["getMaxTransactionsPerBlock","getMaxBlockSize","getFeePerByte","isBlocked","getMaxBlockSystemFee"],"extra":null}}}` default: response = resp } @@ -1555,3 +1557,34 @@ func TestGetNetwork(t *testing.T) { require.Equal(t, netmode.UnitTestNet, c.GetNetwork()) }) } + +func TestUninitedClient(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + r := request.NewRequest() + err := r.DecodeData(req.Body) + require.NoErrorf(t, err, "Cannot decode request body: %s", req.Body) + // request handler already have `getversion` response wrapper + requestHandler(t, r.In, w, "") + })) + defer srv.Close() + endpoint := srv.URL + opts := Options{} + + c, err := New(context.TODO(), endpoint, opts) + require.NoError(t, err) + + _, err = c.GetBlockByIndex(0) + require.Error(t, err) + _, err = c.GetBlockByIndexVerbose(0) + require.Error(t, err) + _, err = c.GetBlockHeader(util.Uint256{}) + require.Error(t, err) + _, err = c.GetRawTransaction(util.Uint256{}) + require.Error(t, err) + _, err = c.GetRawTransactionVerbose(util.Uint256{}) + require.Error(t, err) + _, err = c.IsBlocked(util.Uint160{}) + require.Error(t, err) + _, err = c.GetFeePerByte() + require.Error(t, err) +} diff --git a/pkg/rpc/request/txBuilder.go b/pkg/rpc/request/txBuilder.go index a405d7de1..0c5f58336 100644 --- a/pkg/rpc/request/txBuilder.go +++ b/pkg/rpc/request/txBuilder.go @@ -1,40 +1,18 @@ package request import ( - "encoding/json" "errors" "fmt" "strconv" - "github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/smartcontract" - "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" - "github.com/nspcc-dev/neo-go/pkg/smartcontract/nef" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/vm/opcode" ) -// CreateDeploymentScript returns a script that deploys given smart contract -// with its metadata. -func CreateDeploymentScript(ne *nef.File, manif *manifest.Manifest) ([]byte, error) { - script := io.NewBufBinWriter() - rawManifest, err := json.Marshal(manif) - if err != nil { - return nil, err - } - neb, err := ne.Bytes() - if err != nil { - return nil, err - } - emit.Bytes(script.BinWriter, rawManifest) - emit.Bytes(script.BinWriter, neb) - emit.Syscall(script.BinWriter, interopnames.SystemContractCreate) - return script.Bytes(), nil -} - // expandArrayIntoScript pushes all FuncParam parameters from the given array // into the given buffer in reverse order. func expandArrayIntoScript(script *io.BinWriter, slice []Param) error { diff --git a/pkg/rpc/server/client_test.go b/pkg/rpc/server/client_test.go index 086fc4a98..500f2c3e6 100644 --- a/pkg/rpc/server/client_test.go +++ b/pkg/rpc/server/client_test.go @@ -7,6 +7,7 @@ import ( "github.com/nspcc-dev/neo-go/internal/testchain" "github.com/nspcc-dev/neo-go/pkg/core/fee" + "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" @@ -266,7 +267,7 @@ func TestCreateNEP17TransferTx(t *testing.T) { priv := testchain.PrivateKeyByID(0) acc := wallet.NewAccountFromPrivateKey(priv) - gasContractHash, err := c.GetNativeContractHash("gas") + gasContractHash, err := c.GetNativeContractHash(nativenames.Gas) require.NoError(t, err) tx, err := c.CreateNEP17TransferTx(acc, util.Uint160{}, gasContractHash, 1000, 0) diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index 8f9e2cc08..030a96e22 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -57,8 +57,8 @@ type rpcTestCase struct { } const testContractHash = "743ed26f78e29ecd595535b74a943b1f9ccbc444" -const deploymentTxHash = "9ecf1273fe0d8868cc024c8270b569a12edd7ea9d675c88554b937134efb03f8" -const genesisBlockHash = "a496577895eb8c227bb866dc44f99f21c0cf06417ca8f2a877cc5d761a50dac0" +const deploymentTxHash = "37644146394ad76ddb9431d10b724a3cad5f8b249abdaed0b086fcd761756951" +const genesisBlockHash = "0542f4350c6e236d0509bcd98188b0034bfbecc1a0c7fcdb8e4295310d468b70" const verifyContractHash = "a2eb22340979804cb10cc1add0b8822c201f4d8a" const verifyContractAVM = "570300412d51083021700c14aa8acf859d4fe402b34e673f2156821796a488ebdb30716813cedb2869db289740" @@ -89,8 +89,9 @@ var rpcTestCases = map[string][]rpcTestCase{ res, ok := acc.(*result.ApplicationLog) require.True(t, ok) assert.Equal(t, genesisBlockHash, res.Container.StringLE()) - assert.Equal(t, 1, len(res.Executions)) - assert.Equal(t, trigger.PostPersist, res.Executions[0].Trigger) // no onPersist for genesis block + assert.Equal(t, 2, len(res.Executions)) + assert.Equal(t, trigger.OnPersist, res.Executions[0].Trigger) + assert.Equal(t, trigger.PostPersist, res.Executions[1].Trigger) assert.Equal(t, vm.HaltState, res.Executions[0].VMState) }, }, @@ -103,7 +104,7 @@ var rpcTestCases = map[string][]rpcTestCase{ require.True(t, ok) assert.Equal(t, genesisBlockHash, res.Container.StringLE()) assert.Equal(t, 1, len(res.Executions)) - assert.Equal(t, trigger.PostPersist, res.Executions[0].Trigger) // no onPersist for genesis block + assert.Equal(t, trigger.PostPersist, res.Executions[0].Trigger) assert.Equal(t, vm.HaltState, res.Executions[0].VMState) }, }, @@ -115,7 +116,9 @@ var rpcTestCases = map[string][]rpcTestCase{ res, ok := acc.(*result.ApplicationLog) require.True(t, ok) assert.Equal(t, genesisBlockHash, res.Container.StringLE()) - assert.Equal(t, 0, len(res.Executions)) // no onPersist for genesis block + assert.Equal(t, 1, len(res.Executions)) + assert.Equal(t, trigger.OnPersist, res.Executions[0].Trigger) + assert.Equal(t, vm.HaltState, res.Executions[0].VMState) }, }, { @@ -167,7 +170,7 @@ var rpcTestCases = map[string][]rpcTestCase{ }, { name: "positive, native by name", - params: `["Policy"]`, + params: `["PolicyContract"]`, result: func(e *executor) interface{} { return &state.Contract{} }, check: func(t *testing.T, e *executor, cs interface{}) { res, ok := cs.(*state.Contract) @@ -1075,7 +1078,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] }) t.Run("getrawtransaction", func(t *testing.T) { - block, _ := chain.GetBlock(chain.GetHeaderHash(0)) + block, _ := chain.GetBlock(chain.GetHeaderHash(1)) tx := block.Transactions[0] rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "getrawtransaction", "params": ["%s"]}"`, tx.Hash().StringLE()) body := doRPCCall(rpc, httpSrv.URL, t) @@ -1090,7 +1093,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] }) t.Run("getrawtransaction 2 arguments", func(t *testing.T) { - block, _ := chain.GetBlock(chain.GetHeaderHash(0)) + block, _ := chain.GetBlock(chain.GetHeaderHash(1)) tx := block.Transactions[0] rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "getrawtransaction", "params": ["%s", 0]}"`, tx.Hash().StringLE()) body := doRPCCall(rpc, httpSrv.URL, t) @@ -1105,7 +1108,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] }) t.Run("getrawtransaction 2 arguments, verbose", func(t *testing.T) { - block, _ := chain.GetBlock(chain.GetHeaderHash(0)) + block, _ := chain.GetBlock(chain.GetHeaderHash(1)) TXHash := block.Transactions[0].Hash() _ = block.Transactions[0].Size() rpc := fmt.Sprintf(`{"jsonrpc": "2.0", "id": 1, "method": "getrawtransaction", "params": ["%s", 1]}"`, TXHash.StringLE()) @@ -1116,7 +1119,7 @@ func testRPCProtocol(t *testing.T, doRPCCall func(string, string, *testing.T) [] require.NoErrorf(t, err, "could not parse response: %s", txOut) assert.Equal(t, *block.Transactions[0], actual.Transaction) - assert.Equal(t, 9, actual.Confirmations) + assert.Equal(t, 8, actual.Confirmations) assert.Equal(t, TXHash, actual.Transaction.Hash()) }) @@ -1344,7 +1347,7 @@ func checkNep17Balances(t *testing.T, e *executor, acc interface{}) { }, { Asset: e.chain.UtilityTokenHash(), - Amount: "80009698770", + Amount: "80009634770", LastUpdated: 7, }}, Address: testchain.PrivateKeyByID(0).GetScriptHash().StringLE(), diff --git a/pkg/rpc/server/testdata/testblocks.acc b/pkg/rpc/server/testdata/testblocks.acc index 444446512..3cbc31749 100644 Binary files a/pkg/rpc/server/testdata/testblocks.acc and b/pkg/rpc/server/testdata/testblocks.acc differ diff --git a/pkg/smartcontract/nef/nef.go b/pkg/smartcontract/nef/nef.go index af2a42024..9d1088af8 100644 --- a/pkg/smartcontract/nef/nef.go +++ b/pkg/smartcontract/nef/nef.go @@ -29,7 +29,7 @@ const ( // Magic is a magic File header constant. Magic uint32 = 0x3346454E // MaxScriptLength is the maximum allowed contract script length. - MaxScriptLength = 1024 * 1024 + MaxScriptLength = 512 * 1024 // compilerFieldSize is the length of `Compiler` and `Version` File header fields in bytes. compilerFieldSize = 32 ) diff --git a/scripts/gendump/main.go b/scripts/gendump/main.go index 3c4f90496..3a8cfc35b 100644 --- a/scripts/gendump/main.go +++ b/scripts/gendump/main.go @@ -74,7 +74,7 @@ func main() { handleError("can't tranfser GAS", err) lastBlock = addBlock(bc, lastBlock, valScript, txMoveNeo, txMoveGas) - tx, contractHash, err := testchain.NewDeployTx("DumpContract", h, strings.NewReader(contract)) + tx, contractHash, err := testchain.NewDeployTx(bc, "DumpContract", h, strings.NewReader(contract)) handleError("can't create deploy tx", err) tx.NetworkFee = 10_000_000 tx.ValidUntilBlock = bc.BlockHeight() + 1