*: introduce stable contract hashes

Follow neo-project/neo#2044.
This commit is contained in:
Roman Khimov 2020-11-18 23:10:48 +03:00
parent c5e39dfabf
commit 1cf1fe5d74
32 changed files with 320 additions and 304 deletions

View file

@ -10,9 +10,7 @@ import (
"testing"
"github.com/nspcc-dev/neo-go/pkg/config"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
"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/stretchr/testify/require"
@ -58,7 +56,7 @@ func TestComlileAndInvokeFunction(t *testing.T) {
res := new(result.Invoke)
require.NoError(t, json.Unmarshal(e.Out.Bytes(), res))
require.Equal(t, vm.HaltState.String(), res.State)
require.Equal(t, vm.HaltState.String(), res.State, res.FaultException)
require.Len(t, res.Stack, 1)
require.Equal(t, []byte("on create|sub create"), res.Stack[0].Value())
@ -77,8 +75,6 @@ func TestComlileAndInvokeFunction(t *testing.T) {
rawNef, err := ioutil.ReadFile(nefName)
require.NoError(t, err)
realNef, err := nef.FileFromBytes(rawNef)
require.NoError(t, err)
rawManifest, err := ioutil.ReadFile(manifestName)
require.NoError(t, err)
@ -87,7 +83,7 @@ func TestComlileAndInvokeFunction(t *testing.T) {
"--rpc-endpoint", "http://"+e.RPC.Addr,
"--wallet", validatorWallet, "--address", validatorAddr,
h.StringLE(), "update",
"bytes:"+hex.EncodeToString(realNef.Script),
"bytes:"+hex.EncodeToString(rawNef),
"bytes:"+hex.EncodeToString(rawManifest),
)
e.checkTxPersisted(t, "Sent invocation transaction ")
@ -95,7 +91,7 @@ func TestComlileAndInvokeFunction(t *testing.T) {
e.In.WriteString("one\r")
e.Run(t, "neo-go", "contract", "testinvokefunction",
"--rpc-endpoint", "http://"+e.RPC.Addr,
hash.Hash160(realNef.Script).StringLE(), "getValue")
h.StringLE(), "getValue")
res := new(result.Invoke)
require.NoError(t, json.Unmarshal(e.Out.Bytes(), res))

View file

@ -15,6 +15,7 @@ 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/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/rpc/request"
@ -734,13 +735,18 @@ func contractDeploy(ctx *cli.Context) error {
if err != nil {
return err
}
sender, err := address.StringToUint160(acc.Address)
if err != nil {
return cli.NewExitError(err, 1)
}
f, err := ioutil.ReadFile(in)
if err != nil {
return cli.NewExitError(err, 1)
}
// Check the file.
nefFile, err := nef.FileFromBytes(f)
if err != nil {
return cli.NewExitError(fmt.Errorf("failed to restore .nef file: %w", err), 1)
return cli.NewExitError(fmt.Errorf("failed to read .nef file: %w", err), 1)
}
manifestBytes, err := ioutil.ReadFile(manifestFile)
@ -761,7 +767,7 @@ func contractDeploy(ctx *cli.Context) error {
return err
}
txScript, err := request.CreateDeploymentScript(nefFile.Script, m)
txScript, err := request.CreateDeploymentScript(f, m)
if err != nil {
return cli.NewExitError(fmt.Errorf("failed to create deployment script: %w", err), 1)
}
@ -775,7 +781,8 @@ func contractDeploy(ctx *cli.Context) error {
if err != nil {
return cli.NewExitError(fmt.Errorf("failed to push invocation tx: %w", err), 1)
}
fmt.Fprintf(ctx.App.Writer, "Contract: %s\n", nefFile.Header.ScriptHash.StringLE())
hash := state.CreateContractHash(sender, nefFile.Script)
fmt.Fprintf(ctx.App.Writer, "Contract: %s\n", hash.StringLE())
fmt.Fprintln(ctx.App.Writer, txHash.StringLE())
return nil
}

View file

@ -437,7 +437,7 @@ func importDeployed(ctx *cli.Context) error {
if md == nil {
return cli.NewExitError("contract has no `verify` method", 1)
}
acc.Address = address.Uint160ToString(cs.ScriptHash())
acc.Address = address.Uint160ToString(cs.Hash)
acc.Contract.Script = cs.Script
acc.Contract.Parameters = acc.Contract.Parameters[:0]
for _, p := range md.Parameters {

View file

@ -5,13 +5,16 @@ import (
gio "io"
"github.com/nspcc-dev/neo-go/pkg/compiler"
"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/blockchainer"
"github.com/nspcc-dev/neo-go/pkg/core/fee"
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
"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/io"
"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"
@ -46,28 +49,46 @@ 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, r gio.Reader) (*transaction.Transaction, []byte, error) {
func NewDeployTx(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"
avm, di, err := compiler.CompileWithDebugInfo(name, r)
if err != nil {
return nil, nil, err
return nil, util.Uint160{}, err
}
ne, err := nef.NewFile(avm)
if err != nil {
return nil, util.Uint160{}, err
}
neb, err := ne.Bytes()
if err != nil {
return nil, util.Uint160{}, err
}
w := io.NewBufBinWriter()
m, err := di.ConvertToManifest(name, nil)
if err != nil {
return nil, nil, err
return nil, util.Uint160{}, err
}
bs, err := json.Marshal(m)
if err != nil {
return nil, nil, err
return nil, util.Uint160{}, err
}
w := io.NewBufBinWriter()
emit.Bytes(w.BinWriter, bs)
emit.Bytes(w.BinWriter, avm)
emit.Bytes(w.BinWriter, neb)
emit.Syscall(w.BinWriter, interopnames.SystemContractCreate)
if w.Err != nil {
return nil, nil, err
return nil, util.Uint160{}, w.Err
}
return transaction.New(Network(), w.Bytes(), 100*native.GASFactor), avm, nil
txScript := w.Bytes()
tx := transaction.New(Network(), txScript, 100*native.GASFactor)
tx.Signers = []transaction.Signer{{Account: sender}}
h := state.CreateContractHash(tx.Sender(), avm)
return tx, h, nil
}
// SignTx signs provided transactions with validator keys.

View file

@ -107,6 +107,7 @@ func TestAppCall(t *testing.T) {
ih := hash.Hash160(inner)
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: ih,
Script: inner,
Manifest: *m,
}))

View file

@ -940,7 +940,7 @@ func TestVerifyHashAgainstScript(t *testing.T) {
gas := bc.contracts.Policy.GetMaxVerificationGas(ic.DAO)
t.Run("Contract", func(t *testing.T) {
t.Run("Missing", func(t *testing.T) {
newH := cs.ScriptHash()
newH := cs.Hash
newH[0] = ^newH[0]
w := &transaction.Witness{InvocationScript: []byte{byte(opcode.PUSH4)}}
_, err := bc.verifyHashAgainstScript(newH, w, ic, gas)
@ -948,17 +948,17 @@ func TestVerifyHashAgainstScript(t *testing.T) {
})
t.Run("Invalid", func(t *testing.T) {
w := &transaction.Witness{InvocationScript: []byte{byte(opcode.PUSH4)}}
_, err := bc.verifyHashAgainstScript(csInvalid.ScriptHash(), w, ic, gas)
_, err := bc.verifyHashAgainstScript(csInvalid.Hash, w, ic, gas)
require.True(t, errors.Is(err, ErrInvalidVerificationContract))
})
t.Run("ValidSignature", func(t *testing.T) {
w := &transaction.Witness{InvocationScript: []byte{byte(opcode.PUSH4)}}
_, err := bc.verifyHashAgainstScript(cs.ScriptHash(), w, ic, gas)
_, err := bc.verifyHashAgainstScript(cs.Hash, w, ic, gas)
require.NoError(t, err)
})
t.Run("InvalidSignature", func(t *testing.T) {
w := &transaction.Witness{InvocationScript: []byte{byte(opcode.PUSH3)}}
_, err := bc.verifyHashAgainstScript(cs.ScriptHash(), w, ic, gas)
_, err := bc.verifyHashAgainstScript(cs.Hash, w, ic, gas)
require.True(t, errors.Is(err, ErrVerificationFailed))
})
})
@ -1071,7 +1071,7 @@ func TestIsTxStillRelevant(t *testing.T) {
currentHeight := blockchain.GetHeight()
return currentHeight < %d
}`, bc.BlockHeight()+2) // deploy + next block
txDeploy, avm, err := testchain.NewDeployTx("TestVerify", strings.NewReader(src))
txDeploy, h, err := testchain.NewDeployTx("TestVerify", neoOwner, strings.NewReader(src))
require.NoError(t, err)
txDeploy.ValidUntilBlock = bc.BlockHeight() + 1
addSigners(txDeploy)
@ -1080,7 +1080,7 @@ func TestIsTxStillRelevant(t *testing.T) {
tx := newTx(t)
tx.Signers = append(tx.Signers, transaction.Signer{
Account: hash.Hash160(avm),
Account: h,
Scopes: transaction.None,
})
tx.NetworkFee += 1_000_000

View file

@ -42,7 +42,7 @@ func (cd *Cached) GetContractState(hash util.Uint160) (*state.Contract, error) {
// PutContractState puts given contract state into the given store.
func (cd *Cached) PutContractState(cs *state.Contract) error {
cd.contracts[cs.ScriptHash()] = cs
cd.contracts[cs.Hash] = cs
return cd.DAO.PutContractState(cs)
}

View file

@ -27,6 +27,7 @@ func TestCachedDaoContracts(t *testing.T) {
cs := &state.Contract{
ID: 123,
Hash: sh,
Script: script,
Manifest: *m,
}

View file

@ -136,20 +136,23 @@ func (dao *Simple) GetContractState(hash util.Uint160) (*state.Contract, error)
if err != nil {
return nil, err
}
if contract.ScriptHash() != hash {
return nil, fmt.Errorf("found script hash is not equal to expected")
}
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.ScriptHash().BytesBE())
key := storage.AppendPrefix(storage.STContract, cs.Hash.BytesBE())
if err := dao.Put(cs, key); err != nil {
return err
}
return dao.putContractScriptHash(cs)
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.
@ -173,16 +176,6 @@ func (dao *Simple) GetAndUpdateNextContractID() (int32, error) {
return id, dao.Store.Put(key, data)
}
// putContractScriptHash puts given contract script hash into the given store.
// It's a private method because it should be used after PutContractState to keep
// ID-Hash pair always up-to-date.
func (dao *Simple) putContractScriptHash(cs *state.Contract) error {
key := make([]byte, 5)
key[0] = byte(storage.STContractID)
binary.LittleEndian.PutUint32(key[1:], uint32(cs.ID))
return dao.Store.Put(key, cs.ScriptHash().BytesBE())
}
// 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) {

View file

@ -10,6 +10,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/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"
@ -44,24 +45,26 @@ func (t *TestSerializable) DecodeBinary(reader *io.BinReader) {
func TestPutAndGetContractState(t *testing.T) {
dao := NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, false)
contractState := &state.Contract{Script: []byte{}}
hash := contractState.ScriptHash()
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(hash)
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)
contractState := &state.Contract{Script: []byte{}}
hash := contractState.ScriptHash()
script := []byte{}
h := hash.Hash160(script)
contractState := &state.Contract{Hash: h, Script: script}
err := dao.PutContractState(contractState)
require.NoError(t, err)
err = dao.DeleteContractState(hash)
err = dao.DeleteContractState(h)
require.NoError(t, err)
gotContractState, err := dao.GetContractState(hash)
gotContractState, err := dao.GetContractState(h)
require.Error(t, err)
require.Nil(t, gotContractState)
}

View file

@ -272,10 +272,9 @@ func initBasicChain(t *testing.T, bc *Blockchain) {
require.NoError(t, err)
// Push some contract into the chain.
txDeploy, avm := newDeployTx(t, prefix+"test_contract.go", "Rubl")
txDeploy, cHash := newDeployTx(t, priv0ScriptHash, prefix+"test_contract.go", "Rubl")
txDeploy.Nonce = getNextNonce()
txDeploy.ValidUntilBlock = validUntilBlock
txDeploy.Signers = []transaction.Signer{{Account: priv0ScriptHash}}
require.NoError(t, addNetworkFee(bc, txDeploy, acc0))
require.NoError(t, acc0.SignTx(txDeploy))
b = bc.newBlock(txDeploy)
@ -285,7 +284,7 @@ func initBasicChain(t *testing.T, bc *Blockchain) {
// Now invoke this contract.
script := io.NewBufBinWriter()
emit.AppCallWithOperationAndArgs(script.BinWriter, hash.Hash160(avm), "putValue", "testkey", "testvalue")
emit.AppCallWithOperationAndArgs(script.BinWriter, cHash, "putValue", "testkey", "testvalue")
txInv := transaction.New(testchain.Network(), script.Bytes(), 1*native.GASFactor)
txInv.Nonce = getNextNonce()
@ -314,16 +313,15 @@ func initBasicChain(t *testing.T, bc *Blockchain) {
b = bc.newBlock(txNeo0to1)
require.NoError(t, bc.AddBlock(b))
sh := hash.Hash160(avm)
w := io.NewBufBinWriter()
emit.AppCallWithOperationAndArgs(w.BinWriter, sh, "init")
emit.AppCallWithOperationAndArgs(w.BinWriter, cHash, "init")
initTx := transaction.New(testchain.Network(), w.Bytes(), 1*native.GASFactor)
initTx.Nonce = getNextNonce()
initTx.ValidUntilBlock = validUntilBlock
initTx.Signers = []transaction.Signer{{Account: priv0ScriptHash}}
require.NoError(t, addNetworkFee(bc, initTx, acc0))
require.NoError(t, acc0.SignTx(initTx))
transferTx := newNEP17Transfer(sh, sh, priv0.GetScriptHash(), 1000)
transferTx := newNEP17Transfer(cHash, cHash, priv0.GetScriptHash(), 1000)
transferTx.Nonce = getNextNonce()
transferTx.ValidUntilBlock = validUntilBlock
transferTx.Signers = []transaction.Signer{
@ -341,7 +339,7 @@ func initBasicChain(t *testing.T, bc *Blockchain) {
require.NoError(t, bc.AddBlock(b))
t.Logf("recieveRublesTx: %v", transferTx.Hash().StringLE())
transferTx = newNEP17Transfer(sh, priv0.GetScriptHash(), priv1.GetScriptHash(), 123)
transferTx = newNEP17Transfer(cHash, priv0.GetScriptHash(), priv1.GetScriptHash(), 123)
transferTx.Nonce = getNextNonce()
transferTx.ValidUntilBlock = validUntilBlock
transferTx.Signers = []transaction.Signer{
@ -360,10 +358,9 @@ func initBasicChain(t *testing.T, bc *Blockchain) {
t.Logf("sendRublesTx: %v", transferTx.Hash().StringLE())
// Push verification contract into the chain.
txDeploy2, _ := newDeployTx(t, prefix+"verification_contract.go", "Verify")
txDeploy2, _ := newDeployTx(t, priv0ScriptHash, prefix+"verification_contract.go", "Verify")
txDeploy2.Nonce = getNextNonce()
txDeploy2.ValidUntilBlock = validUntilBlock
txDeploy2.Signers = []transaction.Signer{{Account: priv0ScriptHash}}
require.NoError(t, addNetworkFee(bc, txDeploy2, acc0))
require.NoError(t, acc0.SignTx(txDeploy2))
b = bc.newBlock(txDeploy2)
@ -379,15 +376,13 @@ func newNEP17Transfer(sc, from, to util.Uint160, amount int64, additionalArgs ..
return transaction.New(testchain.Network(), script, 10000000)
}
func newDeployTx(t *testing.T, name, ctrName string) (*transaction.Transaction, []byte) {
func newDeployTx(t *testing.T, sender util.Uint160, name, ctrName string) (*transaction.Transaction, util.Uint160) {
c, err := ioutil.ReadFile(name)
require.NoError(t, err)
tx, avm, err := testchain.NewDeployTx(ctrName, bytes.NewReader(c))
tx, h, err := testchain.NewDeployTx(ctrName, sender, bytes.NewReader(c))
require.NoError(t, err)
t.Logf("contractHash (%s): %s", name, hash.Hash160(avm).StringLE())
t.Logf("contractScript: %x", avm)
return tx, avm
t.Logf("contractHash (%s): %s", name, h.StringLE())
return tx, h
}
func addSigners(txs ...*transaction.Transaction) {

View file

@ -29,7 +29,7 @@ func (s *MethodCallback) ArgCount() int {
func (s *MethodCallback) LoadContext(v *vm.VM, args []stackitem.Item) {
v.Estack().PushVal(args)
v.Estack().PushVal(s.method.Name)
v.Estack().PushVal(s.contract.ScriptHash().BytesBE())
v.Estack().PushVal(s.contract.Hash.BytesBE())
}
// CreateFromMethod creates callback for a contract method.

View file

@ -67,12 +67,11 @@ func CallExInternal(ic *interop.Context, cs *state.Contract,
return fmt.Errorf("invalid argument count: %d (expected %d)", len(args), len(md.Parameters))
}
u := cs.ScriptHash()
ic.VM.Invocations[u]++
ic.VM.LoadScriptWithHash(cs.Script, u, ic.VM.Context().GetCallFlags()&f)
ic.VM.Invocations[cs.Hash]++
ic.VM.LoadScriptWithHash(cs.Script, cs.Hash, ic.VM.Context().GetCallFlags()&f)
var isNative bool
for i := range ic.Natives {
if ic.Natives[i].Metadata().Hash.Equals(u) {
if ic.Natives[i].Metadata().Hash.Equals(cs.Hash) {
isNative = true
break
}

View file

@ -6,15 +6,16 @@ import (
"encoding/json"
"errors"
"fmt"
"math"
"sort"
"github.com/mr-tron/base58"
"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/crypto/hash"
"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"
)
@ -60,39 +61,59 @@ func storageFind(ic *interop.Context) error {
return nil
}
// createContractStateFromVM pops all contract state elements from the VM
// evaluation stack, does a lot of checks and returns Contract if it
// succeeds.
func createContractStateFromVM(ic *interop.Context) (*state.Contract, error) {
script := ic.VM.Estack().Pop().Bytes()
if len(script) > MaxContractScriptSize {
return nil, errors.New("the script is too big")
// 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)
}
manifestBytes := ic.VM.Estack().Pop().Bytes()
if len(manifestBytes) > manifest.MaxManifestSize {
return nil, errors.New("manifest is too big")
if err := checkNonEmpty(manifestBytes, manifest.MaxManifestSize); err != nil {
return nil, nil, fmt.Errorf("invalid manifest: %w", err)
}
if !ic.VM.AddGas(int64(StoragePrice * (len(script) + len(manifestBytes)))) {
return nil, errGasLimitExceeded
if !v.AddGas(int64(StoragePrice * (len(nefBytes) + len(manifestBytes)))) {
return nil, nil, errGasLimitExceeded
}
var m manifest.Manifest
err := json.Unmarshal(manifestBytes, &m)
if err != nil {
return nil, fmt.Errorf("unable to retrieve manifest from stack: %w", err)
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
}
return &state.Contract{
Script: script,
Manifest: m,
}, nil
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 {
newcontract, err := createContractStateFromVM(ic)
neff, manif, err := getNefAndManifestFromVM(ic.VM)
if err != nil {
return err
}
contract, err := ic.DAO.GetContractState(newcontract.ScriptHash())
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")
}
@ -100,10 +121,15 @@ func contractCreate(ic *interop.Context) error {
if err != nil {
return err
}
newcontract.ID = id
if !newcontract.Manifest.IsValid(newcontract.ScriptHash()) {
if !manif.IsValid(h) {
return errors.New("failed to check contract script hash against manifest")
}
newcontract := &state.Contract{
ID: id,
Hash: h,
Script: neff.Script,
Manifest: *manif,
}
if err := ic.DAO.PutContractState(newcontract); err != nil {
return err
}
@ -129,64 +155,33 @@ func checkNonEmpty(b []byte, max int) error {
// 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")
}
script := ic.VM.Estack().Pop().BytesOrNil()
manifestBytes := ic.VM.Estack().Pop().BytesOrNil()
if script == nil && manifestBytes == nil {
return errors.New("both script and manifest are nil")
// if NEF was provided, update the contract script
if neff != nil {
contract.Script = neff.Script
}
if err := checkNonEmpty(script, MaxContractScriptSize); err != nil {
return fmt.Errorf("invalid script size: %w", err)
}
if err := checkNonEmpty(manifestBytes, manifest.MaxManifestSize); err != nil {
return fmt.Errorf("invalid manifest size: %w", err)
}
if !ic.VM.AddGas(int64(StoragePrice * (len(script) + len(manifestBytes)))) {
return errGasLimitExceeded
}
// if script was provided, update the old contract script and Manifest.ABI hash
if script != nil {
newHash := hash.Hash160(script)
if newHash.Equals(contract.ScriptHash()) {
return errors.New("the script is the same")
} else if _, err := ic.DAO.GetContractState(newHash); err == nil {
return errors.New("contract already exists")
}
oldHash := contract.ScriptHash()
// re-write existing contract variable, as we need it to be up-to-date during manifest update
contract = &state.Contract{
ID: contract.ID,
Script: script,
Manifest: contract.Manifest,
}
if err := ic.DAO.PutContractState(contract); err != nil {
return fmt.Errorf("failed to update script: %w", err)
}
if err := ic.DAO.DeleteContractState(oldHash); err != nil {
return fmt.Errorf("failed to update script: %w", err)
}
}
// if manifest was provided, update the old contract manifest and check associated
// storage items if needed
if manifestBytes != nil {
var newManifest manifest.Manifest
err := json.Unmarshal(manifestBytes, &newManifest)
if err != nil {
return fmt.Errorf("unable to retrieve manifest from stack: %w", err)
}
// we don't have to perform `GetContractState` one more time as it's already up-to-date
contract.Manifest = newManifest
if !contract.Manifest.IsValid(contract.ScriptHash()) {
// 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")
}
if err := ic.DAO.PutContractState(contract); err != nil {
return fmt.Errorf("failed to update manifest: %w", err)
}
}
contract.UpdateCounter++
if err := ic.DAO.PutContractState(contract); err != nil {
return fmt.Errorf("failed to update contract: %w", err)
}
return callDeploy(ic, contract, true)
}

View file

@ -16,6 +16,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/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/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
@ -296,6 +297,7 @@ func createVMAndContractState(t *testing.T) (*vm.VM, *state.Contract, *interop.C
m := manifest.NewManifest("Test")
contractState := &state.Contract{
Script: script,
Hash: hash.Hash160(script),
Manifest: *m,
ID: 123,
}

View file

@ -105,6 +105,9 @@ func contractToStackItem(cs *state.Contract) (stackitem.Item, error) {
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

View file

@ -8,6 +8,7 @@ import (
"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"
@ -21,6 +22,7 @@ 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"
@ -204,7 +206,7 @@ func TestContractIsStandard(t *testing.T) {
require.NoError(t, err)
pub := priv.PublicKey()
err = ic.DAO.PutContractState(&state.Contract{ID: 42, Script: pub.GetVerificationScript()})
err = ic.DAO.PutContractState(&state.Contract{ID: 42, Hash: pub.GetScriptHash(), Script: pub.GetVerificationScript()})
require.NoError(t, err)
v.Estack().PushVal(pub.GetScriptHash().BytesBE())
@ -213,7 +215,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, Script: script}))
require.NoError(t, ic.DAO.PutContractState(&state.Contract{ID: 24, Hash: hash.Hash160(script), Script: script}))
v.Estack().PushVal(crypto.Hash160(script).BytesBE())
require.NoError(t, contractIsStandard(ic))
@ -319,7 +321,7 @@ func TestBlockchainGetContractState(t *testing.T) {
require.NoError(t, ic.DAO.PutContractState(cs))
t.Run("positive", func(t *testing.T) {
v.Estack().PushVal(cs.ScriptHash().BytesBE())
v.Estack().PushVal(cs.Hash.BytesBE())
require.NoError(t, bcGetContract(ic))
actual := v.Estack().Pop().Item()
@ -502,6 +504,7 @@ func getTestContractState() (*state.Contract, *state.Contract) {
}
cs := &state.Contract{
Script: script,
Hash: h,
Manifest: *m,
ID: 42,
}
@ -519,6 +522,7 @@ func getTestContractState() (*state.Contract, *state.Contract) {
return cs, &state.Contract{
Script: currScript,
Hash: hash.Hash160(currScript),
Manifest: *m,
ID: 123,
}
@ -654,13 +658,31 @@ func TestContractCreate(t *testing.T) {
v.GasLimit = -1
defer bc.Close()
// nef.NewFile() cares about version a lot.
config.Version = "0.90.0-test"
putArgsOnStack := func() {
manifest, err := json.Marshal(cs.Manifest)
require.NoError(t, err)
ne, err := nef.NewFile(cs.Script)
require.NoError(t, err)
neb, err := ne.Bytes()
require.NoError(t, err)
v.Estack().PushVal(manifest)
v.Estack().PushVal(cs.Script)
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)
var sender = util.Uint160{1, 2, 3}
ic.Tx.Signers = append(ic.Tx.Signers, transaction.Signer{Account: sender})
cs.ID = 0
cs.Hash = state.CreateContractHash(sender, cs.Script)
t.Run("positive", func(t *testing.T) {
putArgsOnStack()
@ -670,8 +692,6 @@ func TestContractCreate(t *testing.T) {
})
t.Run("contract already exists", func(t *testing.T) {
cs.Script = cs.Script[:len(cs.Script)-1]
require.NoError(t, ic.DAO.PutContractState(cs))
putArgsOnStack()
require.Error(t, contractCreate(ic))
@ -685,9 +705,12 @@ func compareContractStates(t *testing.T, expected *state.Contract, actual stacki
expectedManifest, err := json.Marshal(expected.Manifest)
require.NoError(t, err)
require.Equal(t, 2, len(act))
require.Equal(t, expected.Script, act[0].Value().([]byte))
require.Equal(t, expectedManifest, act[1].Value().([]byte))
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) {
@ -697,12 +720,19 @@ func TestContractUpdate(t *testing.T) {
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.ScriptHash(), smartcontract.All)
v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.Hash, smartcontract.All)
putArgsOnStack(stackitem.Null{}, stackitem.Null{})
require.Error(t, contractUpdate(ic))
})
@ -710,19 +740,20 @@ func TestContractUpdate(t *testing.T) {
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.ScriptHash(), smartcontract.All)
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.ScriptHash(), smartcontract.All)
v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.Hash, smartcontract.All)
putArgsOnStack(stackitem.Null{}, make([]byte, manifest.MaxManifestSize+1))
require.Error(t, contractUpdate(ic))
})
@ -730,70 +761,43 @@ func TestContractUpdate(t *testing.T) {
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.ScriptHash(), smartcontract.All)
v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.Hash, smartcontract.All)
putArgsOnStack([]byte{1}, []byte{2})
require.Error(t, contractUpdate(ic))
})
t.Run("update script, the same script", func(t *testing.T) {
require.NoError(t, ic.DAO.PutContractState(cs))
v.GasLimit = -1
v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.ScriptHash(), smartcontract.All)
putArgsOnStack(cs.Script, stackitem.Null{})
require.Error(t, contractUpdate(ic))
})
t.Run("update script, already exists", func(t *testing.T) {
require.NoError(t, ic.DAO.PutContractState(cs))
duplicateScript := []byte{byte(opcode.PUSHDATA4)}
require.NoError(t, ic.DAO.PutContractState(&state.Contract{
ID: 95,
Script: duplicateScript,
Manifest: manifest.Manifest{
ABI: manifest.ABI{},
},
}))
v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.ScriptHash(), smartcontract.All)
putArgsOnStack(duplicateScript, stackitem.Null{})
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.ScriptHash(), smartcontract.All)
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.ScriptHash(), smartcontract.All)
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 new scripthash
actual, err := ic.DAO.GetContractState(hash.Hash160(newScript))
// updated contract should have the same scripthash
actual, err := ic.DAO.GetContractState(cs.Hash)
require.NoError(t, err)
expected := &state.Contract{
ID: cs.ID,
Script: newScript,
Manifest: cs.Manifest,
ID: cs.ID,
UpdateCounter: 1,
Hash: cs.Hash,
Script: newScript,
Manifest: cs.Manifest,
}
_ = expected.ScriptHash()
require.Equal(t, expected, actual)
// old contract should be deleted
_, err = ic.DAO.GetContractState(cs.ScriptHash())
require.Error(t, err)
})
t.Run("update manifest, bad manifest", func(t *testing.T) {
require.NoError(t, ic.DAO.PutContractState(cs))
v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.ScriptHash(), smartcontract.All)
v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.Hash, smartcontract.All)
putArgsOnStack(stackitem.Null{}, []byte{1, 2, 3})
require.Error(t, contractUpdate(ic))
@ -808,30 +812,31 @@ func TestContractUpdate(t *testing.T) {
require.NoError(t, err)
t.Run("empty script", func(t *testing.T) {
v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.ScriptHash(), smartcontract.All)
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.ScriptHash(), smartcontract.All)
v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.Hash, smartcontract.All)
putArgsOnStack(stackitem.Null{}, manifestBytes)
require.NoError(t, contractUpdate(ic))
// updated contract should have new scripthash
actual, err := ic.DAO.GetContractState(cs.ScriptHash())
// updated contract should have old scripthash
actual, err := ic.DAO.GetContractState(cs.Hash)
require.NoError(t, err)
expected := &state.Contract{
ID: cs.ID,
Script: cs.Script,
Manifest: *manifest,
ID: cs.ID,
UpdateCounter: 2,
Hash: cs.Hash,
Script: cs.Script,
Manifest: *manifest,
}
_ = expected.ScriptHash()
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.ScriptHash(), smartcontract.All)
v.LoadScriptWithHash([]byte{byte(opcode.RET)}, cs.Hash, smartcontract.All)
newScript := []byte{12, 13, 14}
newManifest := manifest.Manifest{
ABI: manifest.ABI{},
@ -844,19 +849,16 @@ func TestContractUpdate(t *testing.T) {
require.NoError(t, contractUpdate(ic))
// updated contract should have new script and manifest
actual, err := ic.DAO.GetContractState(hash.Hash160(newScript))
actual, err := ic.DAO.GetContractState(cs.Hash)
require.NoError(t, err)
expected := &state.Contract{
ID: cs.ID,
Script: newScript,
Manifest: newManifest,
ID: cs.ID,
UpdateCounter: 3,
Hash: cs.Hash,
Script: newScript,
Manifest: newManifest,
}
_ = expected.ScriptHash()
require.Equal(t, expected, actual)
// old contract should be deleted
_, err = ic.DAO.GetContractState(cs.ScriptHash())
require.Error(t, err)
})
}
@ -871,28 +873,37 @@ func TestContractCreateDeploy(t *testing.T) {
rawManifest, err := json.Marshal(cs.Manifest)
require.NoError(t, err)
v.Estack().PushVal(rawManifest)
v.Estack().PushVal(cs.Script)
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())
v.LoadScriptWithFlags(currCs.Script, smartcontract.All)
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, nil)
require.NoError(t, err)
require.NoError(t, v.Run())
require.Equal(t, "create", v.Estack().Pop().String())
v.LoadScriptWithFlags(cs.Script, smartcontract.All)
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,
}
@ -900,7 +911,7 @@ func TestContractCreateDeploy(t *testing.T) {
require.NoError(t, contractUpdate(ic))
require.NoError(t, v.Run())
v.LoadScriptWithFlags(currCs.Script, smartcontract.All)
v.LoadScriptWithHash(currCs.Script, cs.Hash, smartcontract.All)
err = contract.CallExInternal(ic, newCs, "getValue", nil, smartcontract.All, vm.EnsureNotEmpty, nil)
require.NoError(t, err)
require.NoError(t, v.Run())
@ -1219,6 +1230,7 @@ func TestRuntimeCheckWitness(t *testing.T) {
contractScriptHash := hash.Hash160(contractScript)
contractState := &state.Contract{
ID: 15,
Hash: contractScriptHash,
Script: contractScript,
Manifest: manifest.Manifest{
Groups: []manifest.Group{{PublicKey: pk.PublicKey()}},

View file

@ -20,6 +20,7 @@ func Deploy(ic *interop.Context) error {
cs := &state.Contract{
ID: md.ContractID,
Hash: md.Hash,
Script: md.Script,
Manifest: md.Manifest,
}

View file

@ -149,6 +149,7 @@ func TestNativeContract_Invoke(t *testing.T) {
err := chain.dao.PutContractState(&state.Contract{
Script: tn.meta.Script,
Hash: tn.meta.Hash,
Manifest: tn.meta.Manifest,
})
require.NoError(t, err)
@ -221,6 +222,7 @@ func TestNativeContract_InvokeOtherContract(t *testing.T) {
chain.registerNative(tn)
err := chain.dao.PutContractState(&state.Contract{
Hash: tn.meta.Hash,
Script: tn.meta.Script,
Manifest: tn.meta.Manifest,
})
@ -230,7 +232,7 @@ func TestNativeContract_InvokeOtherContract(t *testing.T) {
require.NoError(t, chain.dao.PutContractState(cs))
t.Run("non-native, no return", func(t *testing.T) {
res, err := invokeContractMethod(chain, testSumPrice*4+10000, tn.Metadata().Hash, "callOtherContractNoReturn", cs.ScriptHash(), "justReturn", []interface{}{})
res, err := invokeContractMethod(chain, testSumPrice*4+10000, tn.Metadata().Hash, "callOtherContractNoReturn", cs.Hash, "justReturn", []interface{}{})
require.NoError(t, err)
checkResult(t, res, stackitem.Null{}) // simple call is done with EnsureNotEmpty
})

View file

@ -313,7 +313,7 @@ func TestNEO_TransferOnPayment(t *testing.T) {
require.NoError(t, bc.dao.PutContractState(cs))
const amount = 2
tx := transferTokenFromMultisigAccount(t, bc, cs.ScriptHash(), bc.contracts.NEO.Hash, amount)
tx := transferTokenFromMultisigAccount(t, bc, cs.Hash, bc.contracts.NEO.Hash, amount)
aer, err := bc.GetAppExecResults(tx.Hash(), trigger.Application)
require.NoError(t, err)
require.Equal(t, vm.HaltState, aer[0].VMState)

View file

@ -12,6 +12,7 @@ import (
"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"
"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"
@ -81,8 +82,10 @@ func getOracleContractState(h util.Uint160) *state.Contract {
perm.Methods.Add("request")
m.Permissions = append(m.Permissions, *perm)
script := w.Bytes()
return &state.Contract{
Script: w.Bytes(),
Script: script,
Hash: hash.Hash160(script),
Manifest: *m,
ID: 42,
}
@ -111,7 +114,7 @@ func TestOracle_Request(t *testing.T) {
gasForResponse := int64(2000_1234)
var filter = "flt"
userData := []byte("custom info")
txHash := putOracleRequest(t, cs.ScriptHash(), bc, "url", &filter, userData, gasForResponse)
txHash := putOracleRequest(t, cs.Hash, bc, "url", &filter, userData, gasForResponse)
req, err := orc.GetRequestInternal(bc.dao, 1)
require.NotNil(t, req)
@ -119,7 +122,7 @@ func TestOracle_Request(t *testing.T) {
require.Equal(t, txHash, req.OriginalTxID)
require.Equal(t, "url", req.URL)
require.Equal(t, filter, *req.Filter)
require.Equal(t, cs.ScriptHash(), req.CallbackContract)
require.Equal(t, cs.Hash, req.CallbackContract)
require.Equal(t, "handle", req.CallbackMethod)
require.Equal(t, uint64(gasForResponse), req.GasForResponse)
@ -189,7 +192,7 @@ func TestOracle_Request(t *testing.T) {
t.Run("ErrorOnFinish", func(t *testing.T) {
const reqID = 2
putOracleRequest(t, cs.ScriptHash(), bc, "url", nil, []byte{1, 2}, gasForResponse)
putOracleRequest(t, cs.Hash, bc, "url", nil, []byte{1, 2}, gasForResponse)
_, err := orc.GetRequestInternal(bc.dao, reqID) // ensure ID is 2
require.NoError(t, err)
@ -221,14 +224,14 @@ func TestOracle_Request(t *testing.T) {
require.Equal(t, vm.FaultState, aer[0].VMState)
}
t.Run("non-UTF8 url", func(t *testing.T) {
doBadRequest(t, cs.ScriptHash(), "\xff", nil, []byte{1, 2}, gasForResponse)
doBadRequest(t, cs.Hash, "\xff", nil, []byte{1, 2}, gasForResponse)
})
t.Run("non-UTF8 filter", func(t *testing.T) {
var f = "\xff"
doBadRequest(t, cs.ScriptHash(), "url", &f, []byte{1, 2}, gasForResponse)
doBadRequest(t, cs.Hash, "url", &f, []byte{1, 2}, gasForResponse)
})
t.Run("not enough gas", func(t *testing.T) {
doBadRequest(t, cs.ScriptHash(), "url", nil, nil, 1000)
doBadRequest(t, cs.Hash, "url", nil, nil, 1000)
})
})
}

View file

@ -1,80 +1,50 @@
package state
import (
"encoding/json"
"errors"
"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"
)
// Contract holds information about a smart contract in the NEO blockchain.
type Contract struct {
ID int32
Script []byte
Manifest manifest.Manifest
scriptHash util.Uint160
ID int32 `json:"id"`
UpdateCounter uint16 `json:"updatecounter"`
Hash util.Uint160 `json:"hash"`
Script []byte `json:"script"`
Manifest manifest.Manifest `json:"manifest"`
}
// 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)
cs.createHash()
}
// 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)
}
// ScriptHash returns a contract script hash.
func (cs *Contract) ScriptHash() util.Uint160 {
if cs.scriptHash.Equals(util.Uint160{}) {
cs.createHash()
// CreateContractHash creates deployed contract hash from transaction sender
// and contract script.
func CreateContractHash(sender util.Uint160, script []byte) util.Uint160 {
w := io.NewBufBinWriter()
emit.Opcodes(w.BinWriter, opcode.ABORT)
emit.Bytes(w.BinWriter, sender.BytesBE())
emit.Bytes(w.BinWriter, script)
if w.Err != nil {
panic(w.Err)
}
return cs.scriptHash
}
// createHash creates contract script hash.
func (cs *Contract) createHash() {
cs.scriptHash = hash.Hash160(cs.Script)
}
type contractJSON struct {
ID int32 `json:"id"`
Script []byte `json:"script"`
Manifest *manifest.Manifest `json:"manifest"`
ScriptHash util.Uint160 `json:"hash"`
}
// MarshalJSON implements json.Marshaler.
func (cs *Contract) MarshalJSON() ([]byte, error) {
return json.Marshal(&contractJSON{
ID: cs.ID,
Script: cs.Script,
Manifest: &cs.Manifest,
ScriptHash: cs.ScriptHash(),
})
}
// UnmarshalJSON implements json.Unmarshaler.
func (cs *Contract) UnmarshalJSON(data []byte) error {
var cj contractJSON
if err := json.Unmarshal(data, &cj); err != nil {
return err
} else if cj.Manifest == nil {
return errors.New("empty manifest")
}
cs.ID = cj.ID
cs.Script = cj.Script
cs.Manifest = *cj.Manifest
cs.createHash()
return nil
return hash.Hash160(w.Bytes())
}

View file

@ -7,7 +7,8 @@ import (
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/stretchr/testify/assert"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/stretchr/testify/require"
)
func TestEncodeDecodeContractState(t *testing.T) {
@ -30,21 +31,30 @@ func TestEncodeDecodeContractState(t *testing.T) {
ReturnType: smartcontract.BoolType,
}}
contract := &Contract{
ID: 123,
Script: script,
Manifest: *m,
ID: 123,
UpdateCounter: 42,
Hash: h,
Script: script,
Manifest: *m,
}
assert.Equal(t, h, contract.ScriptHash())
t.Run("Serializable", func(t *testing.T) {
contractDecoded := new(Contract)
testserdes.EncodeDecodeBinary(t, contract, contractDecoded)
assert.Equal(t, contract.ScriptHash(), contractDecoded.ScriptHash())
})
t.Run("JSON", func(t *testing.T) {
contractDecoded := new(Contract)
testserdes.MarshalUnmarshalJSON(t, contract, contractDecoded)
assert.Equal(t, contract.ScriptHash(), contractDecoded.ScriptHash())
})
}
func TestCreateContractHash(t *testing.T) {
var script = []byte{1, 2, 3}
var sender util.Uint160
var err error
require.Equal(t, "b4b7417195feca1cdb5a99504ab641d8c220ae99", CreateContractHash(sender, script).StringLE())
sender, err = util.Uint160DecodeStringLE("a400ff00ff00ff00ff00ff00ff00ff00ff00ff01")
require.NoError(t, err)
require.Equal(t, "e56e4ee87f89a70e9138432c387ad49f2ee5b55f", CreateContractHash(sender, script).StringLE())
}

View file

@ -121,12 +121,12 @@ func (c *Client) Init() error {
if err != nil {
return fmt.Errorf("failed to get NEO contract scripthash: %w", err)
}
c.cache.nativeHashes["neo"] = neoContractHash.ScriptHash()
c.cache.nativeHashes["neo"] = neoContractHash.Hash
gasContractHash, err := c.GetContractStateByAddressOrName("gas")
if err != nil {
return fmt.Errorf("failed to get GAS contract scripthash: %w", err)
}
c.cache.nativeHashes["gas"] = gasContractHash.ScriptHash()
c.cache.nativeHashes["gas"] = gasContractHash.Hash
c.initDone = true
return nil
}

View file

@ -124,8 +124,12 @@ func (c *Client) CreateNEP17MultiTransferTx(acc *wallet.Account, gas int64, reci
recipients[i].Address, recipients[i].Amount, nil)
emit.Opcodes(w.BinWriter, opcode.ASSERT)
}
accAddr, err := address.StringToUint160(acc.Address)
if err != nil {
return nil, fmt.Errorf("bad account address: %v", err)
}
return c.CreateTxFromScript(w.Bytes(), acc, -1, gas, transaction.Signer{
Account: acc.Contract.ScriptHash(),
Account: accAddr,
Scopes: transaction.CalledByEntry,
})
}

View file

@ -622,6 +622,6 @@ func (c *Client) GetNativeContractHash(name string) (util.Uint160, error) {
if err != nil {
return util.Uint160{}, err
}
c.cache.nativeHashes[lowercasedName] = cs.ScriptHash()
return cs.ScriptHash(), nil
c.cache.nativeHashes[lowercasedName] = cs.Hash
return cs.Hash, nil
}

View file

@ -19,6 +19,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/block"
"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"
"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/rpc/request"
@ -335,10 +336,10 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
m := manifest.NewManifest("Test")
cs := &state.Contract{
ID: 0,
Hash: hash.Hash160(script),
Script: script,
Manifest: *m,
}
_ = cs.ScriptHash()
return cs
},
},
@ -356,10 +357,10 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
m := manifest.NewManifest("Test")
cs := &state.Contract{
ID: 0,
Hash: hash.Hash160(script),
Script: script,
Manifest: *m,
}
_ = cs.ScriptHash()
return cs
},
},
@ -377,10 +378,10 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
m := manifest.NewManifest("Test")
cs := &state.Contract{
ID: 0,
Hash: hash.Hash160(script),
Script: script,
Manifest: *m,
}
_ = cs.ScriptHash()
return cs
},
},

View file

@ -56,11 +56,11 @@ type rpcTestCase struct {
check func(t *testing.T, e *executor, result interface{})
}
const testContractHash = "55b692ecc09f240355e042c6c07e8f3fe57546b1"
const deploymentTxHash = "99e40e5d169eb9a2b6faebc6fc596c050cf3f8a70ad25de8f44309bc8ccbfbfb"
const testContractHash = "743ed26f78e29ecd595535b74a943b1f9ccbc444"
const deploymentTxHash = "8e848d367d6194c0ddc12310e0509c0bb3bf716b0bcd9c10508b4bc9c954408d"
const genesisBlockHash = "a496577895eb8c227bb866dc44f99f21c0cf06417ca8f2a877cc5d761a50dac0"
const verifyContractHash = "c1213693b22cb0454a436d6e0bd76b8c0a3bfdf7"
const verifyContractHash = "a2eb22340979804cb10cc1add0b8822c201f4d8a"
const verifyContractAVM = "570300412d51083021700c14aa8acf859d4fe402b34e673f2156821796a488ebdb30716813cedb2869db289740"
const testVerifyContractAVM = "VwcADBQBDAMOBQYMDQIODw0DDgcJAAAAANswcGgRVUH4J+yMIaonBwAAABFADBQNDwMCCQACAQMHAwQFAgEADgYMCdswcWkRVUH4J+yMIaonBwAAABJAE0A="
@ -142,7 +142,7 @@ var rpcTestCases = map[string][]rpcTestCase{
check: func(t *testing.T, e *executor, cs interface{}) {
res, ok := cs.(*state.Contract)
require.True(t, ok)
assert.Equal(t, testContractHash, res.ScriptHash().StringLE())
assert.Equal(t, testContractHash, res.Hash.StringLE())
},
},
{
@ -1345,7 +1345,7 @@ func checkNep17Balances(t *testing.T, e *executor, acc interface{}) {
},
{
Asset: e.chain.UtilityTokenHash(),
Amount: "80009744770",
Amount: "80009690770",
LastUpdated: 7,
}},
Address: testchain.PrivateKeyByID(0).GetScriptHash().StringLE(),

Binary file not shown.

View file

@ -7,6 +7,7 @@ import (
"os"
"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/util"
"github.com/nspcc-dev/neo-go/pkg/vm"
)
@ -181,8 +182,9 @@ func (w *Wallet) Close() {
// GetAccount returns account corresponding to the provided scripthash.
func (w *Wallet) GetAccount(h util.Uint160) *Account {
addr := address.Uint160ToString(h)
for _, acc := range w.Accounts {
if c := acc.Contract; c != nil && h.Equals(c.ScriptHash()) {
if acc.Address == addr {
return acc
}
}

View file

@ -167,6 +167,7 @@ func TestWallet_GetAccount(t *testing.T) {
}
for _, acc := range accounts {
acc.Address = address.Uint160ToString(acc.Contract.ScriptHash())
wallet.AddAccount(acc)
}

View file

@ -18,7 +18,6 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/native"
"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"
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
@ -75,18 +74,13 @@ func main() {
handleError("can't tranfser GAS", err)
lastBlock = addBlock(bc, lastBlock, valScript, txMoveNeo, txMoveGas)
tx, avm, err := testchain.NewDeployTx("DumpContract", strings.NewReader(contract))
tx, contractHash, err := testchain.NewDeployTx("DumpContract", h, strings.NewReader(contract))
handleError("can't create deploy tx", err)
tx.Signers = []transaction.Signer{{
Account: h,
Scopes: transaction.CalledByEntry,
}}
tx.NetworkFee = 10_000_000
tx.ValidUntilBlock = bc.BlockHeight() + 1
handleError("can't sign deploy tx", acc.SignTx(tx))
lastBlock = addBlock(bc, lastBlock, valScript, tx)
contractHash := hash.Hash160(avm)
key := make([]byte, 10)
value := make([]byte, 10)
nonce := uint32(0)