Skip to content

Commit

Permalink
core: decouple native contracts from interop service
Browse files Browse the repository at this point in the history
Closes #1191.
  • Loading branch information
AnnaShaleva committed Jul 29, 2020
1 parent 5cf4481 commit 598f692
Show file tree
Hide file tree
Showing 14 changed files with 112 additions and 84 deletions.
2 changes: 2 additions & 0 deletions pkg/core/helper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,10 @@ func TestCreateBasicChain(t *testing.T) {

gasHash := bc.contracts.GAS.Hash
neoHash := bc.contracts.NEO.Hash
policyHash := bc.contracts.Policy.Hash
t.Logf("native GAS hash: %v", gasHash)
t.Logf("native NEO hash: %v", neoHash)
t.Logf("native Policy hash: %v", policyHash)

priv0 := testchain.PrivateKeyByID(0)
priv0ScriptHash := priv0.GetScriptHash()
Expand Down
22 changes: 11 additions & 11 deletions pkg/core/interop/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,25 +82,25 @@ type Contract interface {

// ContractMD represents native contract instance.
type ContractMD struct {
Manifest manifest.Manifest
ServiceName string
ServiceID uint32
ContractID int32
Script []byte
Hash util.Uint160
Methods map[string]MethodAndPrice
Manifest manifest.Manifest
Name string
ContractID int32
Script []byte
Hash util.Uint160
Methods map[string]MethodAndPrice
}

// NewContractMD returns Contract with the specified list of methods.
func NewContractMD(name string) *ContractMD {
c := &ContractMD{
ServiceName: name,
ServiceID: emit.InteropNameToID([]byte(name)),
Methods: make(map[string]MethodAndPrice),
Name: name,
Methods: make(map[string]MethodAndPrice),
}

w := io.NewBufBinWriter()
emit.Syscall(w.BinWriter, c.ServiceName)
emit.String(w.BinWriter, c.Name)
emit.Syscall(w.BinWriter, "Neo.Native.Call")

c.Script = w.Bytes()
c.Hash = hash.Hash160(c.Script)
c.Manifest = *manifest.DefaultManifest(c.Hash)
Expand Down
4 changes: 1 addition & 3 deletions pkg/core/interops.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,6 @@ func SpawnVM(ic *interop.Context) *vm.VM {
vm := vm.NewWithTrigger(ic.Trigger)
vm.RegisterInteropGetter(getSystemInterop(ic))
vm.RegisterInteropGetter(getNeoInterop(ic))
if ic.Chain != nil {
vm.RegisterInteropGetter(ic.Chain.(*Blockchain).contracts.GetNativeInterop(ic))
}
ic.ScriptGetter = vm
return vm
}
Expand Down Expand Up @@ -129,6 +126,7 @@ var neoInterops = []interop.Function{
{Name: "Neo.Crypto.CheckMultisigWithECDsaSecp256k1", Func: crypto.ECDSASecp256k1CheckMultisig, Price: 0},
{Name: "Neo.Crypto.SHA256", Func: crypto.Sha256, Price: 1000000},
{Name: "Neo.Crypto.RIPEMD160", Func: crypto.RipeMD160, Price: 1000000},
{Name: "Neo.Native.Call", Func: native.Call, Price: 0},
{Name: "Neo.Native.Deploy", Func: native.Deploy, Price: 0, RequiredFlags: smartcontract.AllowModifyStates},
}

Expand Down
51 changes: 0 additions & 51 deletions pkg/core/native/contract.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
package native

import (
"fmt"

"github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/io"
"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/vm/opcode"
"github.com/pkg/errors"
)

// Contracts is a set of registered native contracts.
Expand All @@ -32,16 +28,6 @@ func (cs *Contracts) ByHash(h util.Uint160) interop.Contract {
return nil
}

// ByID returns native contract with the specified id.
func (cs *Contracts) ByID(id uint32) interop.Contract {
for _, ctr := range cs.Contracts {
if ctr.Metadata().ServiceID == id {
return ctr
}
}
return nil
}

// NewContracts returns new set of native contracts with new GAS, NEO and Policy
// contracts.
func NewContracts() *Contracts {
Expand Down Expand Up @@ -79,40 +65,3 @@ func (cs *Contracts) GetPersistScript() []byte {
cs.persistScript = w.Bytes()
return cs.persistScript
}

// GetNativeInterop returns an interop getter for a given set of contracts.
func (cs *Contracts) GetNativeInterop(ic *interop.Context) func(uint32) *vm.InteropFuncPrice {
return func(id uint32) *vm.InteropFuncPrice {
if c := cs.ByID(id); c != nil {
return &vm.InteropFuncPrice{
Func: getNativeInterop(ic, c),
}
}
return nil
}
}

// getNativeInterop returns native contract interop.
func getNativeInterop(ic *interop.Context, c interop.Contract) func(v *vm.VM) error {
return func(v *vm.VM) error {
h := v.GetCurrentScriptHash()
if !h.Equals(c.Metadata().Hash) {
return errors.New("invalid hash")
}
name := string(v.Estack().Pop().Bytes())
args := v.Estack().Pop().Array()
m, ok := c.Metadata().Methods[name]
if !ok {
return fmt.Errorf("method %s not found", name)
}
if !v.Context().GetCallFlags().Has(m.RequiredFlags) {
return errors.New("missing call flags")
}
if !v.AddGas(m.Price) {
return errors.New("gas limit exceeded")
}
result := m.Func(ic, args)
v.Estack().PushVal(result)
return nil
}
}
36 changes: 35 additions & 1 deletion pkg/core/native/interop.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,42 @@ func Deploy(ic *interop.Context, _ *vm.VM) error {
return err
}
if err := native.Initialize(ic); err != nil {
return fmt.Errorf("initializing %s native contract: %v", md.ServiceName, err)
return fmt.Errorf("initializing %s native contract: %v", md.Name, err)
}
}
return nil
}

// Call calls specified native contract method.
func Call(ic *interop.Context, v *vm.VM) error {
name := string(v.Estack().Pop().Bytes())
var c interop.Contract
for _, ctr := range ic.Natives {
if ctr.Metadata().Name == name {
c = ctr
break
}
}
if c == nil {
return fmt.Errorf("native contract %s not found", name)
}
h := v.GetCurrentScriptHash()
if !h.Equals(c.Metadata().Hash) {
return errors.New("it is not allowed to use Neo.Native.Call directly to call native contracts. System.Contract.Call should be used")
}
operation := string(v.Estack().Pop().Bytes())
args := v.Estack().Pop().Array()
m, ok := c.Metadata().Methods[operation]
if !ok {
return fmt.Errorf("method %s not found", operation)
}
if !v.Context().GetCallFlags().Has(m.RequiredFlags) {
return errors.New("missing call flags")
}
if !v.AddGas(m.Price) {
return errors.New("gas limit exceeded")
}
result := m.Func(ic, args)
v.Estack().PushVal(result)
return nil
}
5 changes: 2 additions & 3 deletions pkg/core/native/native_gas.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ type GAS struct {
NEO *NEO
}

const gasSyscallName = "Neo.Native.Tokens.GAS"
const gasName = "GAS"
const gasContractID = -2

// GASFactor is a divisor for finding GAS integral value.
Expand All @@ -29,8 +29,7 @@ const initialGAS = 30000000
// NewGAS returns GAS native contract.
func NewGAS() *GAS {
g := &GAS{}
nep5 := newNEP5Native(gasSyscallName)
nep5.name = "GAS"
nep5 := newNEP5Native(gasName)
nep5.symbol = "gas"
nep5.decimals = 8
nep5.factor = GASFactor
Expand Down
7 changes: 3 additions & 4 deletions pkg/core/native/native_neo.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ type keyWithVotes struct {
}

const (
neoSyscallName = "Neo.Native.Tokens.NEO"
neoContractID = -1
neoName = "NEO"
neoContractID = -1
// NEOTotalSupply is the total amount of NEO in the system.
NEOTotalSupply = 100000000
// prefixValidator is a prefix used to store validator's data.
Expand Down Expand Up @@ -68,8 +68,7 @@ func makeValidatorKey(key *keys.PublicKey) []byte {
// NewNEO returns NEO native contract.
func NewNEO() *NEO {
n := &NEO{}
nep5 := newNEP5Native(neoSyscallName)
nep5.name = "NEO"
nep5 := newNEP5Native(neoName)
nep5.symbol = "neo"
nep5.decimals = 0
nep5.factor = 1
Expand Down
3 changes: 1 addition & 2 deletions pkg/core/native/native_nep5.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ func makeAccountKey(h util.Uint160) []byte {
// nep5TokenNative represents NEP-5 token contract.
type nep5TokenNative struct {
interop.ContractMD
name string
symbol string
decimals int64
factor int64
Expand Down Expand Up @@ -92,7 +91,7 @@ func (c *nep5TokenNative) Initialize(_ *interop.Context) error {
}

func (c *nep5TokenNative) Name(_ *interop.Context, _ []stackitem.Item) stackitem.Item {
return stackitem.NewByteArray([]byte(c.name))
return stackitem.NewByteArray([]byte(c.ContractMD.Name))
}

func (c *nep5TokenNative) Symbol(_ *interop.Context, _ []stackitem.Item) stackitem.Item {
Expand Down
6 changes: 3 additions & 3 deletions pkg/core/native/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ import (
)

const (
policySyscallName = "Neo.Native.Policy"
policyContractID = -3
policyName = "Policy"
policyContractID = -3

defaultMaxBlockSize = 1024 * 256
defaultMaxTransactionsPerBlock = 512
Expand Down Expand Up @@ -59,7 +59,7 @@ var _ interop.Contract = (*Policy)(nil)

// newPolicy returns Policy native contract.
func newPolicy() *Policy {
p := &Policy{ContractMD: *interop.NewContractMD(policySyscallName)}
p := &Policy{ContractMD: *interop.NewContractMD(policyName)}

p.ContractID = policyContractID
p.Manifest.Features |= smartcontract.HasStorage
Expand Down
50 changes: 49 additions & 1 deletion pkg/core/native_contract_test.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
package core

import (
"math/big"
"testing"

"github.com/stretchr/testify/require"

"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/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/storage"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/stretchr/testify/require"
)

type testNative struct {
Expand Down Expand Up @@ -139,3 +146,44 @@ func TestNativeContract_Invoke(t *testing.T) {
require.Fail(t, "onPersist wasn't called")
}
}

func TestNativeContract_InvokeInternal(t *testing.T) {
chain := newTestChain(t)
defer chain.Close()

tn := newTestNative()
chain.registerNative(tn)

err := chain.dao.PutContractState(&state.Contract{
Script: tn.meta.Script,
Manifest: tn.meta.Manifest,
})
require.NoError(t, err)

v := vm.New()
v.GasLimit = -1
ic := chain.newInteropContext(trigger.Application,
dao.NewSimple(storage.NewMemoryStore(), netmode.UnitTestNet), nil, nil)

t.Run("fail, bad current script hash", func(t *testing.T) {
v.LoadScriptWithHash([]byte{1}, tn.Metadata().Hash, smartcontract.All)
v.Estack().PushVal(stackitem.NewArray([]stackitem.Item{stackitem.NewBigInteger(big.NewInt(14)), stackitem.NewBigInteger(big.NewInt(28))}))
v.Estack().PushVal("sum")
v.Estack().PushVal(tn.Metadata().Name)

// it's prohibited to call natives directly
require.NoError(t, native.Call(ic, v))
})

t.Run("success", func(t *testing.T) {
v.LoadScriptWithHash([]byte{1}, tn.Metadata().Hash, smartcontract.All)
v.Estack().PushVal(stackitem.NewArray([]stackitem.Item{stackitem.NewBigInteger(big.NewInt(14)), stackitem.NewBigInteger(big.NewInt(28))}))
v.Estack().PushVal("sum")
v.Estack().PushVal(tn.Metadata().Name)

require.NoError(t, native.Call(ic, v))

value := v.Estack().Pop().BigInt()
require.Equal(t, int64(42), value.Int64())
})
}
4 changes: 2 additions & 2 deletions pkg/rpc/client/nep5.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ import (

var (
// NeoContractHash is a hash of the NEO native contract.
NeoContractHash, _ = util.Uint160DecodeStringLE("9bde8f209c88dd0e7ca3bf0af0f476cdd8207789")
NeoContractHash, _ = util.Uint160DecodeStringBE("25059ecb4878d3a875f91c51ceded330d4575fde")
// GasContractHash is a hash of the GAS native contract.
GasContractHash, _ = util.Uint160DecodeStringLE("8c23f196d8a1bfd103a9dcb1f9ccf0c611377d3b")
GasContractHash, _ = util.Uint160DecodeStringBE("bcaf41d684c7d4ad6ee0d99da9707b9d1f0c8e66")
)

// NEP5Decimals invokes `decimals` NEP5 method on a specified contract.
Expand Down
4 changes: 2 additions & 2 deletions pkg/rpc/client/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import (
"github.com/pkg/errors"
)

// PolicyContractHash represents BE hash of native Policy contract.
var PolicyContractHash = util.Uint160{154, 97, 164, 110, 236, 151, 184, 147, 6, 215, 206, 129, 241, 91, 70, 32, 145, 208, 9, 50}
// PolicyContractHash represents a hash of native Policy contract.
var PolicyContractHash, _ = util.Uint160DecodeStringBE("e9ff4ca7cc252e1dfddb26315869cd79505906ce")

// GetMaxTransactionsPerBlock invokes `getMaxTransactionsPerBlock` method on a
// native Policy contract.
Expand Down
2 changes: 1 addition & 1 deletion pkg/rpc/server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ type rpcTestCase struct {
}

const testContractHash = "36c3b0c85d98607db00b711885ec3e411d9b1672"
const deploymentTxHash = "dcf4fe429ec84947361c86c2192b14641be7f0c6e2bdf8d150fad731160ed386"
const deploymentTxHash = "ef4209bc06e1d8412995c645a8497d3e2c9a05ca52236de94297c6db9c3e94d0"

var rpcTestCases = map[string][]rpcTestCase{
"getapplicationlog": {
Expand Down
Binary file modified pkg/rpc/server/testdata/testblocks.acc
Binary file not shown.

0 comments on commit 598f692

Please sign in to comment.