native: move contract deployment to management contract

See neo-project/neo#2119.
This commit is contained in:
Roman Khimov 2020-12-13 18:26:35 +03:00
parent ad3547783d
commit ab12eee346
47 changed files with 1135 additions and 918 deletions

View file

@ -20,13 +20,14 @@ import (
"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 +771,16 @@ func contractDeploy(ctx *cli.Context) error {
return err
}
txScript, err := request.CreateDeploymentScript(&nefFile, m)
mgmtHash, err := c.GetNativeContractHash("Neo Contract 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 != "" {

View file

@ -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.

View file

@ -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
}

View file

@ -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)

View file

@ -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),
},

View file

@ -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{
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()))

View file

@ -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,

View file

@ -841,7 +841,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
}
@ -1141,7 +1141,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))
}
@ -1663,7 +1663,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
}
@ -1786,6 +1786,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())
@ -1794,7 +1799,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:

View file

@ -1030,10 +1030,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) {
@ -1169,7 +1169,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)

View file

@ -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))

View file

@ -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,

View file

@ -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.

View file

@ -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(1)
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.

View file

@ -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, 1, id)
id, err = dao.GetAndUpdateNextContractID()
require.NoError(t, err)
require.EqualValues(t, 2, id)
id, err = dao.GetAndUpdateNextContractID()
require.NoError(t, err)
require.EqualValues(t, 3, id)
}
func TestPutGetAppExecResult(t *testing.T) {
dao := NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, false)
hash := random.Uint256()

View file

@ -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)

View file

@ -271,7 +271,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 +357,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 +375,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

View file

@ -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")
}

View file

@ -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{
@ -54,6 +57,7 @@ func NewContext(trigger trigger.Type, bc blockchainer.Blockchainer, d dao.DAO, n
Log: log,
// Functions is a slice of slices of interops sorted by ID.
Functions: [][]Function{},
getContract: getContract,
}
}
@ -142,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 {

View file

@ -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")

View file

@ -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,14 +21,11 @@ const (
SystemCallbackInvoke = "System.Callback.Invoke"
SystemContractCall = "System.Contract.Call"
SystemContractCallEx = "System.Contract.CallEx"
SystemContractCreate = "System.Contract.Create"
SystemContractCreateStandardAccount = "System.Contract.CreateStandardAccount"
SystemContractDestroy = "System.Contract.Destroy"
SystemContractIsStandard = "System.Contract.IsStandard"
SystemContractGetCallFlags = "System.Contract.GetCallFlags"
SystemContractNativeOnPersist = "System.Contract.NativeOnPersist"
SystemContractNativePostPersist = "System.Contract.NativePostPersist"
SystemContractUpdate = "System.Contract.Update"
SystemEnumeratorConcat = "System.Enumerator.Concat"
SystemEnumeratorCreate = "System.Enumerator.Create"
SystemEnumeratorNext = "System.Enumerator.Next"
@ -81,7 +77,6 @@ var names = []string{
SystemBinaryItoa,
SystemBinarySerialize,
SystemBlockchainGetBlock,
SystemBlockchainGetContract,
SystemBlockchainGetHeight,
SystemBlockchainGetTransaction,
SystemBlockchainGetTransactionFromBlock,
@ -92,14 +87,11 @@ var names = []string{
SystemCallbackInvoke,
SystemContractCall,
SystemContractCallEx,
SystemContractCreate,
SystemContractCreateStandardAccount,
SystemContractDestroy,
SystemContractIsStandard,
SystemContractGetCallFlags,
SystemContractNativeOnPersist,
SystemContractNativePostPersist,
SystemContractUpdate,
SystemEnumeratorConcat,
SystemEnumeratorCreate,
SystemEnumeratorNext,

View file

@ -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 {

View file

@ -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
}

View file

@ -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

View file

@ -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 {

View file

@ -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 = 1
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

View file

@ -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,16 +59,11 @@ 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.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.SystemContractNativeOnPersist, Func: native.OnPersist, Price: 0, DisallowCallback: true},
{Name: interopnames.SystemContractNativePostPersist, Func: native.PostPersist, Price: 0, DisallowCallback: true},
{Name: interopnames.SystemContractUpdate, Func: contractUpdate, Price: 0,
RequiredFlags: smartcontract.WriteStates, ParamCount: 2, 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},
@ -98,7 +91,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},

View file

@ -1,10 +1,23 @@
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/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.
@ -12,19 +25,288 @@ type Management struct {
interop.ContractMD
}
// StoragePrice is the price to pay for 1 byte of storage.
const StoragePrice = 100000
const (
managementName = "Neo Contract Management"
prefixContract = 8
prefixNextAvailableId = 15
)
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(managementName)}
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
@ -45,7 +327,8 @@ func (m *Management) OnPersist(ic *interop.Context) error {
Script: md.Script,
Manifest: md.Manifest,
}
if err := ic.DAO.PutContractState(cs); err != nil {
err := m.PutContractState(ic.DAO, cs)
if err != nil {
return err
}
if err := native.Initialize(ic); err != nil {
@ -65,3 +348,30 @@ func (m *Management) PostPersist(_ *interop.Context) error {
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)
}

View file

@ -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)
}

View file

@ -23,10 +23,7 @@ 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.
@ -132,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
}

View file

@ -276,7 +276,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))
}

View file

@ -251,7 +251,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
}
@ -307,7 +307,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
}

View file

@ -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
}

View file

@ -136,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)
}
@ -169,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,
@ -203,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,
})
@ -243,7 +244,8 @@ 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,
@ -258,8 +260,8 @@ func TestNativeContract_InvokeOtherContract(t *testing.T) {
}
}
cs, _ := getTestContractState()
require.NoError(t, chain.dao.PutContractState(cs))
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{}{})

View file

@ -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))
}

View file

@ -309,8 +309,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)

View file

@ -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"

View file

@ -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

View file

@ -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)
}

View file

@ -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
)

View file

@ -11,7 +11,6 @@ var (
DataBlock,
DataTransaction,
STAccount,
STContract,
STStorage,
IXHeaderHashList,
SYSCurrentBlock,
@ -23,7 +22,6 @@ var (
0x01,
0x02,
0x40,
0x50,
0x70,
0x80,
0xc0,

View file

@ -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{}
}

View file

@ -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 {

View file

@ -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)

View file

@ -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 {

View file

@ -57,7 +57,7 @@ type rpcTestCase struct {
}
const testContractHash = "743ed26f78e29ecd595535b74a943b1f9ccbc444"
const deploymentTxHash = "a72dfaebf9543964d74e803723dae6a86196e0915ae9d76b3cc57c3b2e3e8c49"
const deploymentTxHash = "8ec2d061ecb22115f1e25a8bf79e536134d0cd7f0ac31b89cc40960a21dc8171"
const genesisBlockHash = "0542f4350c6e236d0509bcd98188b0034bfbecc1a0c7fcdb8e4295310d468b70"
const verifyContractHash = "a2eb22340979804cb10cc1add0b8822c201f4d8a"
@ -1347,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(),

Binary file not shown.

View file

@ -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