forked from TrueCloudLab/neoneo-go
Merge pull request #1229 from nspcc-dev/interop/native_call
core: decouple native contracts from interop service
This commit is contained in:
commit
f24e707ea1
14 changed files with 110 additions and 83 deletions
|
@ -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()
|
||||
|
|
|
@ -83,8 +83,7 @@ type Contract interface {
|
|||
// ContractMD represents native contract instance.
|
||||
type ContractMD struct {
|
||||
Manifest manifest.Manifest
|
||||
ServiceName string
|
||||
ServiceID uint32
|
||||
Name string
|
||||
ContractID int32
|
||||
Script []byte
|
||||
Hash util.Uint160
|
||||
|
@ -94,13 +93,14 @@ type ContractMD struct {
|
|||
// NewContractMD returns Contract with the specified list of methods.
|
||||
func NewContractMD(name string) *ContractMD {
|
||||
c := &ContractMD{
|
||||
ServiceName: name,
|
||||
ServiceID: emit.InteropNameToID([]byte(name)),
|
||||
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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
@ -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},
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
@ -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
|
||||
|
|
|
@ -38,7 +38,7 @@ type keyWithVotes struct {
|
|||
}
|
||||
|
||||
const (
|
||||
neoSyscallName = "Neo.Native.Tokens.NEO"
|
||||
neoName = "NEO"
|
||||
neoContractID = -1
|
||||
// NEOTotalSupply is the total amount of NEO in the system.
|
||||
NEOTotalSupply = 100000000
|
||||
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
@ -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 {
|
||||
|
|
|
@ -19,7 +19,7 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
policySyscallName = "Neo.Native.Policy"
|
||||
policyName = "Policy"
|
||||
policyContractID = -3
|
||||
|
||||
defaultMaxBlockSize = 1024 * 256
|
||||
|
@ -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
|
||||
|
|
|
@ -1,15 +1,21 @@
|
|||
package core
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"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/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/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/stackitem"
|
||||
|
@ -140,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}, util.Uint160{1, 2, 3}, 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.Error(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())
|
||||
})
|
||||
}
|
||||
|
|
|
@ -23,9 +23,9 @@ type AddrAndAmount struct {
|
|||
|
||||
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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -52,7 +52,7 @@ type rpcTestCase struct {
|
|||
}
|
||||
|
||||
const testContractHash = "36c3b0c85d98607db00b711885ec3e411d9b1672"
|
||||
const deploymentTxHash = "dcf4fe429ec84947361c86c2192b14641be7f0c6e2bdf8d150fad731160ed386"
|
||||
const deploymentTxHash = "ef4209bc06e1d8412995c645a8497d3e2c9a05ca52236de94297c6db9c3e94d0"
|
||||
|
||||
var rpcTestCases = map[string][]rpcTestCase{
|
||||
"getapplicationlog": {
|
||||
|
|
BIN
pkg/rpc/server/testdata/testblocks.acc
vendored
BIN
pkg/rpc/server/testdata/testblocks.acc
vendored
Binary file not shown.
Loading…
Reference in a new issue