Compare commits
2 commits
master
...
fix-call-c
Author | SHA1 | Date | |
---|---|---|---|
|
dc4d5a2dc6 | ||
|
e0825e7665 |
6 changed files with 136 additions and 8 deletions
|
@ -27,6 +27,9 @@ const (
|
|||
// https://github.com/neo-project/neo/pull/2925) and #3362 (ported from
|
||||
// https://github.com/neo-project/neo/pull/3154).
|
||||
HFCockatrice // Cockatrice
|
||||
// HFDomovoi represents hard-fork introduced in #3476 (ported from
|
||||
// https://github.com/neo-project/neo/pull/3290).
|
||||
HFDomovoi // Domovoi
|
||||
// hfLast denotes the end of hardforks enum. Consider adding new hardforks
|
||||
// before hfLast.
|
||||
hfLast
|
||||
|
|
|
@ -12,13 +12,15 @@ func _() {
|
|||
_ = x[HFAspidochelone-1]
|
||||
_ = x[HFBasilisk-2]
|
||||
_ = x[HFCockatrice-4]
|
||||
_ = x[hfLast-8]
|
||||
_ = x[HFDomovoi-8]
|
||||
_ = x[hfLast-16]
|
||||
}
|
||||
|
||||
const (
|
||||
_Hardfork_name_0 = "DefaultAspidocheloneBasilisk"
|
||||
_Hardfork_name_1 = "Cockatrice"
|
||||
_Hardfork_name_2 = "hfLast"
|
||||
_Hardfork_name_2 = "Domovoi"
|
||||
_Hardfork_name_3 = "hfLast"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -33,6 +35,8 @@ func (i Hardfork) String() string {
|
|||
return _Hardfork_name_1
|
||||
case i == 8:
|
||||
return _Hardfork_name_2
|
||||
case i == 16:
|
||||
return _Hardfork_name_3
|
||||
default:
|
||||
return "Hardfork(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
|
|
|
@ -370,6 +370,7 @@ func TestNewBlockchain_InitHardforks(t *testing.T) {
|
|||
config.HFAspidochelone.String(): 0,
|
||||
config.HFBasilisk.String(): 0,
|
||||
config.HFCockatrice.String(): 0,
|
||||
config.HFDomovoi.String(): 0,
|
||||
}, bc.GetConfig().Hardforks)
|
||||
})
|
||||
t.Run("empty set", func(t *testing.T) {
|
||||
|
@ -400,13 +401,14 @@ func TestNewBlockchain_InitHardforks(t *testing.T) {
|
|||
})
|
||||
t.Run("all present", func(t *testing.T) {
|
||||
bc := newTestChainWithCustomCfg(t, func(c *config.Config) {
|
||||
c.ProtocolConfiguration.Hardforks = map[string]uint32{config.HFAspidochelone.String(): 5, config.HFBasilisk.String(): 10, config.HFCockatrice.String(): 15}
|
||||
c.ProtocolConfiguration.Hardforks = map[string]uint32{config.HFAspidochelone.String(): 5, config.HFBasilisk.String(): 10, config.HFCockatrice.String(): 15, config.HFDomovoi.String(): 20}
|
||||
require.NoError(t, c.ProtocolConfiguration.Validate())
|
||||
})
|
||||
require.Equal(t, map[string]uint32{
|
||||
config.HFAspidochelone.String(): 5,
|
||||
config.HFBasilisk.String(): 10,
|
||||
config.HFCockatrice.String(): 15,
|
||||
config.HFDomovoi.String(): 20,
|
||||
}, bc.GetConfig().Hardforks)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -271,7 +271,7 @@ func TestBlockchain_StartFromExistingDB(t *testing.T) {
|
|||
|
||||
_, _, _, err = chain.NewMultiWithCustomConfigAndStoreNoCheck(t, customConfig, cache)
|
||||
require.Error(t, err)
|
||||
require.True(t, strings.Contains(err.Error(), fmt.Sprintf("native %s: version mismatch for the latest hardfork Cockatrice (stored contract state differs from autogenerated one)", nativenames.CryptoLib)), err)
|
||||
require.True(t, strings.Contains(err.Error(), fmt.Sprintf("native %s: version mismatch for the latest hardfork Domovoi (stored contract state differs from autogenerated one)", nativenames.CryptoLib)), err)
|
||||
})
|
||||
|
||||
t.Run("good", func(t *testing.T) {
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"math/big"
|
||||
"strings"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||
"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/nativenames"
|
||||
|
@ -77,12 +78,18 @@ func callInternal(ic *interop.Context, cs *state.Contract, name string, f callfl
|
|||
if md.Safe {
|
||||
f &^= (callflag.WriteStates | callflag.AllowNotify)
|
||||
} else if ctx := ic.VM.Context(); ctx != nil && ctx.IsDeployed() {
|
||||
curr, err := ic.GetContract(ic.VM.GetCurrentScriptHash())
|
||||
if err == nil {
|
||||
if !curr.Manifest.CanCall(cs.Hash, &cs.Manifest, name) {
|
||||
return errors.New("disallowed method call")
|
||||
var mfst *manifest.Manifest
|
||||
if ic.IsHardforkEnabled(config.HFDomovoi) {
|
||||
mfst = ctx.GetManifest()
|
||||
} else {
|
||||
curr, err := ic.GetContract(ic.VM.GetCurrentScriptHash())
|
||||
if err == nil {
|
||||
mfst = &curr.Manifest
|
||||
}
|
||||
}
|
||||
if mfst != nil && !mfst.CanCall(cs.Hash, &cs.Manifest, name) {
|
||||
return errors.New("disallowed method call")
|
||||
}
|
||||
}
|
||||
return callExFromNative(ic, ic.VM.GetCurrentScriptHash(), cs, name, args, f, hasReturn, isDynamic, false)
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/internal/contracts"
|
||||
"github.com/nspcc-dev/neo-go/internal/random"
|
||||
"github.com/nspcc-dev/neo-go/pkg/compiler"
|
||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/contract"
|
||||
|
@ -173,6 +174,117 @@ func TestCall(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestSystemContractCall_Permissions(t *testing.T) {
|
||||
check := func(t *testing.T, cfg func(*config.Blockchain), shouldUpdateFail bool) {
|
||||
bc, acc := chain.NewSingleWithCustomConfig(t, cfg)
|
||||
e := neotest.NewExecutor(t, bc, acc, acc)
|
||||
|
||||
// Contract A has an unsafe method.
|
||||
srcA := `package contractA
|
||||
func RetOne() int {
|
||||
return 1
|
||||
}`
|
||||
ctrA := neotest.CompileSource(t, acc.ScriptHash(), strings.NewReader(srcA), &compiler.Options{
|
||||
NoEventsCheck: true,
|
||||
NoPermissionsCheck: true,
|
||||
Name: "contractA",
|
||||
})
|
||||
e.DeployContract(t, ctrA, nil)
|
||||
|
||||
var hashAStr string
|
||||
for i := 0; i < util.Uint160Size; i++ {
|
||||
hashAStr += fmt.Sprintf("%#x", ctrA.Hash[i])
|
||||
if i != util.Uint160Size-1 {
|
||||
hashAStr += ", "
|
||||
}
|
||||
}
|
||||
|
||||
// Contract B has a method that calls contract's A and another update method that
|
||||
// calls contract's A after B's update.
|
||||
srcB := `package contractB
|
||||
import (
|
||||
"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/native/management"
|
||||
)
|
||||
func CallRetOne() int {
|
||||
res := contract.Call(interop.Hash160{` + hashAStr + `}, "retOne", contract.All).(int)
|
||||
return res
|
||||
}
|
||||
func Update(nef []byte, manifest []byte) int {
|
||||
management.Update(nef, manifest)
|
||||
res := contract.Call(interop.Hash160{` + hashAStr + `}, "retOne", contract.All).(int)
|
||||
return res
|
||||
}`
|
||||
ctrB := neotest.CompileSource(t, acc.ScriptHash(), strings.NewReader(srcB), &compiler.Options{
|
||||
Name: "contractB",
|
||||
NoEventsCheck: true,
|
||||
NoPermissionsCheck: true,
|
||||
Permissions: []manifest.Permission{
|
||||
{
|
||||
Contract: manifest.PermissionDesc{Type: manifest.PermissionHash, Value: ctrA.Hash},
|
||||
Methods: manifest.WildStrings{Value: []string{"retOne"}},
|
||||
},
|
||||
{
|
||||
Methods: manifest.WildStrings{Value: []string{"update"}},
|
||||
},
|
||||
},
|
||||
})
|
||||
e.DeployContract(t, ctrB, nil)
|
||||
ctrBInvoker := e.ValidatorInvoker(ctrB.Hash)
|
||||
|
||||
// ctrBUpdated differs from ctrB in that it has no permission to call retOne method of ctrA
|
||||
ctrBUpdated := neotest.CompileSource(t, acc.ScriptHash(), strings.NewReader(srcB), &compiler.Options{
|
||||
Name: "contractB",
|
||||
NoEventsCheck: true,
|
||||
NoPermissionsCheck: true,
|
||||
Permissions: []manifest.Permission{
|
||||
{
|
||||
Contract: manifest.PermissionDesc{Type: manifest.PermissionHash, Value: ctrA.Hash},
|
||||
Methods: manifest.WildStrings{Value: []string{}},
|
||||
},
|
||||
{
|
||||
Methods: manifest.WildStrings{Value: []string{"update"}},
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
// Call to A before B update should HALT.
|
||||
ctrBInvoker.Invoke(t, stackitem.Make(1), "callRetOne")
|
||||
|
||||
// Call to A in the same context as B update should HALT.
|
||||
n, err := ctrBUpdated.NEF.Bytes()
|
||||
require.NoError(t, err)
|
||||
m, err := json.Marshal(ctrBUpdated.Manifest)
|
||||
require.NoError(t, err)
|
||||
if shouldUpdateFail {
|
||||
ctrBInvoker.InvokeFail(t, "System.Contract.Call failed: disallowed method call", "update", n, m)
|
||||
} else {
|
||||
ctrBInvoker.Invoke(t, stackitem.Make(1), "update", n, m)
|
||||
}
|
||||
|
||||
// If contract is updated, then all to A after B update should FAULT (manifest
|
||||
// is updated, no permission to call retOne method).
|
||||
if !shouldUpdateFail {
|
||||
ctrBInvoker.InvokeFail(t, "System.Contract.Call failed: disallowed method call", "callRetOne")
|
||||
}
|
||||
}
|
||||
|
||||
// Pre-Domovoi behaviour: an updated contract state is used for permissions check.
|
||||
check(t, func(cfg *config.Blockchain) {
|
||||
cfg.Hardforks = map[string]uint32{
|
||||
config.HFDomovoi.String(): 100500,
|
||||
}
|
||||
}, true)
|
||||
|
||||
// Post-Domovoi behaviour: an executing contract state is used for permissions check.
|
||||
check(t, func(cfg *config.Blockchain) {
|
||||
cfg.Hardforks = map[string]uint32{
|
||||
config.HFDomovoi.String(): 0,
|
||||
}
|
||||
}, false)
|
||||
}
|
||||
|
||||
func TestLoadToken(t *testing.T) {
|
||||
bc, acc := chain.NewSingle(t)
|
||||
e := neotest.NewExecutor(t, bc, acc, acc)
|
||||
|
|
Loading…
Reference in a new issue