Skip to content

Commit

Permalink
native: move contract deployment to management contract
Browse files Browse the repository at this point in the history
  • Loading branch information
roman-khimov committed Dec 13, 2020
1 parent ff789f6 commit fa5437b
Show file tree
Hide file tree
Showing 45 changed files with 1,130 additions and 901 deletions.
13 changes: 10 additions & 3 deletions cli/smartcontract/smart_contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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 != "" {
Expand Down
16 changes: 14 additions & 2 deletions cli/testdata/deploy/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand All @@ -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.
Expand Down
10 changes: 8 additions & 2 deletions examples/timer/timer.go
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -9,6 +10,7 @@ import (
)

const defaultTicks = 3
const mgmtKey = "mgmt"

var (
// ctx holds storage context for contract methods
Expand All @@ -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.")
Expand All @@ -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
}
Expand All @@ -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
}
17 changes: 13 additions & 4 deletions internal/testchain/transaction.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package testchain

import (
"encoding/json"
gio "io"

"github.com/nspcc-dev/neo-go/pkg/compiler"
Expand All @@ -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"
Expand Down Expand Up @@ -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"

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

Expand Down
4 changes: 1 addition & 3 deletions pkg/compiler/debug_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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),
},
Expand Down
31 changes: 20 additions & 11 deletions pkg/compiler/interop_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package compiler_test

import (
"errors"
"fmt"
"math/big"
"strings"
Expand All @@ -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"
Expand Down Expand Up @@ -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"
Expand All @@ -164,11 +160,24 @@ func TestAppCall(t *testing.T) {
require.NoError(t, err)

ih := hash.Hash160(inner)
require.NoError(t, ic.DAO.PutContractState(&state.Contract{
Hash: ih,
Script: inner,
Manifest: *m,
}))
var contractGetter = func(_ dao.DAO, h util.Uint160) (*state.Contract, error) {
if h.Equals(ih) {
return &state.Contract{
Hash: ih,
Script: inner,
Manifest: *m,
}, nil
} else if h.Equals(barH) {
return &state.Contract{
Hash: barH,
Script: barCtr,
Manifest: *mBar,
}, nil
}
return nil, errors.New("not found")
}

ic := interop.NewContext(trigger.Application, nil, dao.NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet, false), contractGetter, nil, nil, nil, zaptest.NewLogger(t))

t.Run("valid script", func(t *testing.T) {
src := getAppCallScript(fmt.Sprintf("%#v", ih.BytesBE()))
Expand Down
13 changes: 9 additions & 4 deletions pkg/core/blockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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))
}
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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())
Expand All @@ -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:
Expand Down
8 changes: 4 additions & 4 deletions pkg/core/blockchain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions pkg/core/blockchainer/blockchainer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
33 changes: 3 additions & 30 deletions pkg/core/dao/cacheddao.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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.
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand Down
Loading

0 comments on commit fa5437b

Please sign in to comment.