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/2925) and #3362 (ported from
|
||||||
// https://github.com/neo-project/neo/pull/3154).
|
// https://github.com/neo-project/neo/pull/3154).
|
||||||
HFCockatrice // Cockatrice
|
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
|
// hfLast denotes the end of hardforks enum. Consider adding new hardforks
|
||||||
// before hfLast.
|
// before hfLast.
|
||||||
hfLast
|
hfLast
|
||||||
|
|
|
@ -12,13 +12,15 @@ func _() {
|
||||||
_ = x[HFAspidochelone-1]
|
_ = x[HFAspidochelone-1]
|
||||||
_ = x[HFBasilisk-2]
|
_ = x[HFBasilisk-2]
|
||||||
_ = x[HFCockatrice-4]
|
_ = x[HFCockatrice-4]
|
||||||
_ = x[hfLast-8]
|
_ = x[HFDomovoi-8]
|
||||||
|
_ = x[hfLast-16]
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
_Hardfork_name_0 = "DefaultAspidocheloneBasilisk"
|
_Hardfork_name_0 = "DefaultAspidocheloneBasilisk"
|
||||||
_Hardfork_name_1 = "Cockatrice"
|
_Hardfork_name_1 = "Cockatrice"
|
||||||
_Hardfork_name_2 = "hfLast"
|
_Hardfork_name_2 = "Domovoi"
|
||||||
|
_Hardfork_name_3 = "hfLast"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -33,6 +35,8 @@ func (i Hardfork) String() string {
|
||||||
return _Hardfork_name_1
|
return _Hardfork_name_1
|
||||||
case i == 8:
|
case i == 8:
|
||||||
return _Hardfork_name_2
|
return _Hardfork_name_2
|
||||||
|
case i == 16:
|
||||||
|
return _Hardfork_name_3
|
||||||
default:
|
default:
|
||||||
return "Hardfork(" + strconv.FormatInt(int64(i), 10) + ")"
|
return "Hardfork(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
}
|
}
|
||||||
|
|
|
@ -370,6 +370,7 @@ func TestNewBlockchain_InitHardforks(t *testing.T) {
|
||||||
config.HFAspidochelone.String(): 0,
|
config.HFAspidochelone.String(): 0,
|
||||||
config.HFBasilisk.String(): 0,
|
config.HFBasilisk.String(): 0,
|
||||||
config.HFCockatrice.String(): 0,
|
config.HFCockatrice.String(): 0,
|
||||||
|
config.HFDomovoi.String(): 0,
|
||||||
}, bc.GetConfig().Hardforks)
|
}, bc.GetConfig().Hardforks)
|
||||||
})
|
})
|
||||||
t.Run("empty set", func(t *testing.T) {
|
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) {
|
t.Run("all present", func(t *testing.T) {
|
||||||
bc := newTestChainWithCustomCfg(t, func(c *config.Config) {
|
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.NoError(t, c.ProtocolConfiguration.Validate())
|
||||||
})
|
})
|
||||||
require.Equal(t, map[string]uint32{
|
require.Equal(t, map[string]uint32{
|
||||||
config.HFAspidochelone.String(): 5,
|
config.HFAspidochelone.String(): 5,
|
||||||
config.HFBasilisk.String(): 10,
|
config.HFBasilisk.String(): 10,
|
||||||
config.HFCockatrice.String(): 15,
|
config.HFCockatrice.String(): 15,
|
||||||
|
config.HFDomovoi.String(): 20,
|
||||||
}, bc.GetConfig().Hardforks)
|
}, bc.GetConfig().Hardforks)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -271,7 +271,7 @@ func TestBlockchain_StartFromExistingDB(t *testing.T) {
|
||||||
|
|
||||||
_, _, _, err = chain.NewMultiWithCustomConfigAndStoreNoCheck(t, customConfig, cache)
|
_, _, _, err = chain.NewMultiWithCustomConfigAndStoreNoCheck(t, customConfig, cache)
|
||||||
require.Error(t, err)
|
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) {
|
t.Run("good", func(t *testing.T) {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"math/big"
|
"math/big"
|
||||||
"strings"
|
"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/dao"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
"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 {
|
if md.Safe {
|
||||||
f &^= (callflag.WriteStates | callflag.AllowNotify)
|
f &^= (callflag.WriteStates | callflag.AllowNotify)
|
||||||
} else if ctx := ic.VM.Context(); ctx != nil && ctx.IsDeployed() {
|
} else if ctx := ic.VM.Context(); ctx != nil && ctx.IsDeployed() {
|
||||||
curr, err := ic.GetContract(ic.VM.GetCurrentScriptHash())
|
var mfst *manifest.Manifest
|
||||||
if err == nil {
|
if ic.IsHardforkEnabled(config.HFDomovoi) {
|
||||||
if !curr.Manifest.CanCall(cs.Hash, &cs.Manifest, name) {
|
mfst = ctx.GetManifest()
|
||||||
return errors.New("disallowed method call")
|
} 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)
|
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/contracts"
|
||||||
"github.com/nspcc-dev/neo-go/internal/random"
|
"github.com/nspcc-dev/neo-go/internal/random"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/compiler"
|
"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/block"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
"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/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) {
|
func TestLoadToken(t *testing.T) {
|
||||||
bc, acc := chain.NewSingle(t)
|
bc, acc := chain.NewSingle(t)
|
||||||
e := neotest.NewExecutor(t, bc, acc, acc)
|
e := neotest.NewExecutor(t, bc, acc, acc)
|
||||||
|
|
Loading…
Reference in a new issue