manifest: add Safe flag to method descriptor

`interop.Contex.AddMethod` sets `Safe` flag for native
contracts. This allows not to forget to change manifest
when changing call flags.
Also fixed invalid `Safe` flags for `Notary` and `Designate` contracts.
This commit is contained in:
Evgenii Stratonikov 2020-12-08 13:27:41 +03:00
parent fb13acab94
commit b7e86fa6a3
13 changed files with 70 additions and 85 deletions

View file

@ -251,9 +251,6 @@ func _deploy(isUpdate bool) {}
Trusts: manifest.WildUint160s{
Value: []util.Uint160{},
},
SafeMethods: manifest.WildStrings{
Value: []string{},
},
Extra: nil,
}
require.ElementsMatch(t, expected.ABI.Methods, actual.ABI.Methods)
@ -261,7 +258,6 @@ func _deploy(isUpdate bool) {}
require.Equal(t, expected.Groups, actual.Groups)
require.Equal(t, expected.Permissions, actual.Permissions)
require.Equal(t, expected.Trusts, actual.Trusts)
require.Equal(t, expected.SafeMethods, actual.SafeMethods)
require.Equal(t, expected.Extra, actual.Extra)
})
}

View file

@ -120,13 +120,11 @@ func NewContractMD(name string) *ContractMD {
}
// AddMethod adds new method to a native contract.
func (c *ContractMD) AddMethod(md *MethodAndPrice, desc *manifest.Method, safe bool) {
func (c *ContractMD) AddMethod(md *MethodAndPrice, desc *manifest.Method) {
c.Manifest.ABI.Methods = append(c.Manifest.ABI.Methods, *desc)
md.MD = desc
desc.Safe = (md.RequiredFlags & smartcontract.AllowModifyStates) == 0
c.Methods[desc.Name] = *md
if safe {
c.Manifest.SafeMethods.Add(desc.Name)
}
}
// AddEvent adds new event to a native contract.

View file

@ -46,8 +46,13 @@ func callExInternal(ic *interop.Context, h []byte, name string, args []stackitem
if strings.HasPrefix(name, "_") {
return errors.New("invalid method name (starts with '_')")
}
ctx := ic.VM.Context()
if ctx != nil && ctx.IsDeployed() {
md := cs.Manifest.ABI.GetMethod(name)
if md == nil {
return errors.New("method not found")
}
if md.Safe {
f &^= smartcontract.AllowModifyStates
} else if ctx := ic.VM.Context(); ctx != nil && ctx.IsDeployed() {
curr, err := ic.DAO.GetContractState(ic.VM.GetCurrentScriptHash())
if err == nil {
if !curr.Manifest.CanCall(u, &cs.Manifest, name) {

View file

@ -79,21 +79,21 @@ func newDesignate(p2pSigExtensionsEnabled bool) *Designate {
manifest.NewParameter("role", smartcontract.IntegerType),
manifest.NewParameter("index", smartcontract.IntegerType))
md := newMethodAndPrice(s.getDesignatedByRole, 1000000, smartcontract.AllowStates)
s.AddMethod(md, desc, false)
s.AddMethod(md, desc)
desc = newDescriptor("designateAsRole", smartcontract.VoidType,
manifest.NewParameter("role", smartcontract.IntegerType),
manifest.NewParameter("nodes", smartcontract.ArrayType))
md = newMethodAndPrice(s.designateAsRole, 0, smartcontract.AllowModifyStates)
s.AddMethod(md, desc, false)
s.AddMethod(md, desc)
desc = newDescriptor("onPersist", smartcontract.VoidType)
md = newMethodAndPrice(getOnPersistWrapper(onPersistBase), 0, smartcontract.AllowModifyStates)
s.AddMethod(md, desc, false)
s.AddMethod(md, desc)
desc = newDescriptor("postPersist", smartcontract.VoidType)
md = newMethodAndPrice(getOnPersistWrapper(postPersistBase), 0, smartcontract.AllowModifyStates)
s.AddMethod(md, desc, false)
s.AddMethod(md, desc)
return s
}

View file

@ -121,44 +121,44 @@ func newNEO() *NEO {
manifest.NewParameter("account", smartcontract.Hash160Type),
manifest.NewParameter("end", smartcontract.IntegerType))
md := newMethodAndPrice(n.unclaimedGas, 3000000, smartcontract.AllowStates)
n.AddMethod(md, desc, true)
n.AddMethod(md, desc)
desc = newDescriptor("registerCandidate", smartcontract.BoolType,
manifest.NewParameter("pubkey", smartcontract.PublicKeyType))
md = newMethodAndPrice(n.registerCandidate, 5000000, smartcontract.AllowModifyStates)
n.AddMethod(md, desc, false)
n.AddMethod(md, desc)
desc = newDescriptor("unregisterCandidate", smartcontract.BoolType,
manifest.NewParameter("pubkey", smartcontract.PublicKeyType))
md = newMethodAndPrice(n.unregisterCandidate, 5000000, smartcontract.AllowModifyStates)
n.AddMethod(md, desc, false)
n.AddMethod(md, desc)
desc = newDescriptor("vote", smartcontract.BoolType,
manifest.NewParameter("account", smartcontract.Hash160Type),
manifest.NewParameter("pubkey", smartcontract.PublicKeyType))
md = newMethodAndPrice(n.vote, 5000000, smartcontract.AllowModifyStates)
n.AddMethod(md, desc, false)
n.AddMethod(md, desc)
desc = newDescriptor("getCandidates", smartcontract.ArrayType)
md = newMethodAndPrice(n.getCandidatesCall, 100000000, smartcontract.AllowStates)
n.AddMethod(md, desc, true)
n.AddMethod(md, desc)
desc = newDescriptor("getСommittee", smartcontract.ArrayType)
md = newMethodAndPrice(n.getCommittee, 100000000, smartcontract.AllowStates)
n.AddMethod(md, desc, true)
n.AddMethod(md, desc)
desc = newDescriptor("getNextBlockValidators", smartcontract.ArrayType)
md = newMethodAndPrice(n.getNextBlockValidators, 100000000, smartcontract.AllowStates)
n.AddMethod(md, desc, true)
n.AddMethod(md, desc)
desc = newDescriptor("getGasPerBlock", smartcontract.IntegerType)
md = newMethodAndPrice(n.getGASPerBlock, 100_0000, smartcontract.AllowStates)
n.AddMethod(md, desc, false)
n.AddMethod(md, desc)
desc = newDescriptor("setGasPerBlock", smartcontract.BoolType,
manifest.NewParameter("gasPerBlock", smartcontract.IntegerType))
md = newMethodAndPrice(n.setGASPerBlock, 500_0000, smartcontract.AllowModifyStates)
n.AddMethod(md, desc, false)
n.AddMethod(md, desc)
return n
}

View file

@ -56,20 +56,20 @@ func newNEP17Native(name string) *nep17TokenNative {
desc := newDescriptor("symbol", smartcontract.StringType)
md := newMethodAndPrice(n.Symbol, 0, smartcontract.NoneFlag)
n.AddMethod(md, desc, true)
n.AddMethod(md, desc)
desc = newDescriptor("decimals", smartcontract.IntegerType)
md = newMethodAndPrice(n.Decimals, 0, smartcontract.NoneFlag)
n.AddMethod(md, desc, true)
n.AddMethod(md, desc)
desc = newDescriptor("totalSupply", smartcontract.IntegerType)
md = newMethodAndPrice(n.TotalSupply, 1000000, smartcontract.AllowStates)
n.AddMethod(md, desc, true)
n.AddMethod(md, desc)
desc = newDescriptor("balanceOf", smartcontract.IntegerType,
manifest.NewParameter("account", smartcontract.Hash160Type))
md = newMethodAndPrice(n.balanceOf, 1000000, smartcontract.AllowStates)
n.AddMethod(md, desc, true)
n.AddMethod(md, desc)
transferParams := []manifest.Parameter{
manifest.NewParameter("from", smartcontract.Hash160Type),
@ -80,15 +80,15 @@ func newNEP17Native(name string) *nep17TokenNative {
append(transferParams, manifest.NewParameter("data", smartcontract.AnyType))...,
)
md = newMethodAndPrice(n.Transfer, 8000000, smartcontract.AllowModifyStates)
n.AddMethod(md, desc, false)
n.AddMethod(md, desc)
desc = newDescriptor("onPersist", smartcontract.VoidType)
md = newMethodAndPrice(getOnPersistWrapper(onPersistBase), 0, smartcontract.AllowModifyStates)
n.AddMethod(md, desc, false)
n.AddMethod(md, desc)
desc = newDescriptor("postPersist", smartcontract.VoidType)
md = newMethodAndPrice(getOnPersistWrapper(postPersistBase), 0, smartcontract.AllowModifyStates)
n.AddMethod(md, desc, false)
n.AddMethod(md, desc)
n.AddEvent("Transfer", transferParams...)

View file

@ -46,42 +46,42 @@ func newNotary() *Notary {
manifest.NewParameter("amount", smartcontract.IntegerType),
manifest.NewParameter("data", smartcontract.AnyType))
md := newMethodAndPrice(n.onPayment, 100_0000, smartcontract.AllowModifyStates)
n.AddMethod(md, desc, true)
n.AddMethod(md, desc)
desc = newDescriptor("lockDepositUntil", smartcontract.BoolType,
manifest.NewParameter("address", smartcontract.Hash160Type),
manifest.NewParameter("till", smartcontract.IntegerType))
md = newMethodAndPrice(n.lockDepositUntil, 100_0000, smartcontract.AllowModifyStates)
n.AddMethod(md, desc, true)
n.AddMethod(md, desc)
desc = newDescriptor("withdraw", smartcontract.BoolType,
manifest.NewParameter("from", smartcontract.Hash160Type),
manifest.NewParameter("to", smartcontract.Hash160Type))
md = newMethodAndPrice(n.withdraw, 100_0000, smartcontract.AllowModifyStates)
n.AddMethod(md, desc, true)
n.AddMethod(md, desc)
desc = newDescriptor("balanceOf", smartcontract.IntegerType,
manifest.NewParameter("addr", smartcontract.Hash160Type))
md = newMethodAndPrice(n.balanceOf, 100_0000, smartcontract.AllowStates)
n.AddMethod(md, desc, true)
n.AddMethod(md, desc)
desc = newDescriptor("expirationOf", smartcontract.IntegerType,
manifest.NewParameter("addr", smartcontract.Hash160Type))
md = newMethodAndPrice(n.expirationOf, 100_0000, smartcontract.AllowStates)
n.AddMethod(md, desc, true)
n.AddMethod(md, desc)
desc = newDescriptor("verify", smartcontract.BoolType,
manifest.NewParameter("signature", smartcontract.SignatureType))
md = newMethodAndPrice(n.verify, 100_0000, smartcontract.AllowStates)
n.AddMethod(md, desc, false)
n.AddMethod(md, desc)
desc = newDescriptor("onPersist", smartcontract.VoidType)
md = newMethodAndPrice(getOnPersistWrapper(n.OnPersist), 0, smartcontract.AllowModifyStates)
n.AddMethod(md, desc, false)
n.AddMethod(md, desc)
desc = newDescriptor("postPersist", smartcontract.VoidType)
md = newMethodAndPrice(getOnPersistWrapper(postPersistBase), 0, smartcontract.AllowModifyStates)
n.AddMethod(md, desc, false)
n.AddMethod(md, desc)
return n
}

View file

@ -111,24 +111,24 @@ func newOracle() *Oracle {
manifest.NewParameter("userData", smartcontract.AnyType),
manifest.NewParameter("gasForResponse", smartcontract.IntegerType))
md := newMethodAndPrice(o.request, oracleRequestPrice, smartcontract.AllowModifyStates)
o.AddMethod(md, desc, false)
o.AddMethod(md, desc)
desc = newDescriptor("finish", smartcontract.VoidType)
md = newMethodAndPrice(o.finish, 0, smartcontract.AllowModifyStates)
o.AddMethod(md, desc, false)
o.AddMethod(md, desc)
desc = newDescriptor("verify", smartcontract.BoolType)
md = newMethodAndPrice(o.verify, 100_0000, smartcontract.NoneFlag)
o.AddMethod(md, desc, false)
o.AddMethod(md, desc)
pp := chainOnPersist(postPersistBase, o.PostPersist)
desc = newDescriptor("postPersist", smartcontract.VoidType)
md = newMethodAndPrice(getOnPersistWrapper(pp), 0, smartcontract.AllowModifyStates)
o.AddMethod(md, desc, false)
o.AddMethod(md, desc)
desc = newDescriptor("onPersist", smartcontract.VoidType)
md = newMethodAndPrice(getOnPersistWrapper(onPersistBase), 0, smartcontract.AllowModifyStates)
o.AddMethod(md, desc, false)
o.AddMethod(md, desc)
o.AddEvent("OracleRequest", manifest.NewParameter("Id", smartcontract.IntegerType),
manifest.NewParameter("RequestContract", smartcontract.Hash160Type),

View file

@ -77,62 +77,62 @@ func newPolicy() *Policy {
desc := newDescriptor("getMaxTransactionsPerBlock", smartcontract.IntegerType)
md := newMethodAndPrice(p.getMaxTransactionsPerBlock, 1000000, smartcontract.AllowStates)
p.AddMethod(md, desc, true)
p.AddMethod(md, desc)
desc = newDescriptor("getMaxBlockSize", smartcontract.IntegerType)
md = newMethodAndPrice(p.getMaxBlockSize, 1000000, smartcontract.AllowStates)
p.AddMethod(md, desc, true)
p.AddMethod(md, desc)
desc = newDescriptor("getFeePerByte", smartcontract.IntegerType)
md = newMethodAndPrice(p.getFeePerByte, 1000000, smartcontract.AllowStates)
p.AddMethod(md, desc, true)
p.AddMethod(md, desc)
desc = newDescriptor("isBlocked", smartcontract.BoolType,
manifest.NewParameter("account", smartcontract.Hash160Type))
md = newMethodAndPrice(p.isBlocked, 1000000, smartcontract.AllowStates)
p.AddMethod(md, desc, true)
p.AddMethod(md, desc)
desc = newDescriptor("getMaxBlockSystemFee", smartcontract.IntegerType)
md = newMethodAndPrice(p.getMaxBlockSystemFee, 1000000, smartcontract.AllowStates)
p.AddMethod(md, desc, true)
p.AddMethod(md, desc)
desc = newDescriptor("setMaxBlockSize", smartcontract.BoolType,
manifest.NewParameter("value", smartcontract.IntegerType))
md = newMethodAndPrice(p.setMaxBlockSize, 3000000, smartcontract.AllowModifyStates)
p.AddMethod(md, desc, false)
p.AddMethod(md, desc)
desc = newDescriptor("setMaxTransactionsPerBlock", smartcontract.BoolType,
manifest.NewParameter("value", smartcontract.IntegerType))
md = newMethodAndPrice(p.setMaxTransactionsPerBlock, 3000000, smartcontract.AllowModifyStates)
p.AddMethod(md, desc, false)
p.AddMethod(md, desc)
desc = newDescriptor("setFeePerByte", smartcontract.BoolType,
manifest.NewParameter("value", smartcontract.IntegerType))
md = newMethodAndPrice(p.setFeePerByte, 3000000, smartcontract.AllowModifyStates)
p.AddMethod(md, desc, false)
p.AddMethod(md, desc)
desc = newDescriptor("setMaxBlockSystemFee", smartcontract.BoolType,
manifest.NewParameter("value", smartcontract.IntegerType))
md = newMethodAndPrice(p.setMaxBlockSystemFee, 3000000, smartcontract.AllowModifyStates)
p.AddMethod(md, desc, false)
p.AddMethod(md, desc)
desc = newDescriptor("blockAccount", smartcontract.BoolType,
manifest.NewParameter("account", smartcontract.Hash160Type))
md = newMethodAndPrice(p.blockAccount, 3000000, smartcontract.AllowModifyStates)
p.AddMethod(md, desc, false)
p.AddMethod(md, desc)
desc = newDescriptor("unblockAccount", smartcontract.BoolType,
manifest.NewParameter("account", smartcontract.Hash160Type))
md = newMethodAndPrice(p.unblockAccount, 3000000, smartcontract.AllowModifyStates)
p.AddMethod(md, desc, false)
p.AddMethod(md, desc)
desc = newDescriptor("onPersist", smartcontract.VoidType)
md = newMethodAndPrice(getOnPersistWrapper(p.OnPersist), 0, smartcontract.AllowModifyStates)
p.AddMethod(md, desc, false)
p.AddMethod(md, desc)
desc = newDescriptor("postPersist", smartcontract.VoidType)
md = newMethodAndPrice(getOnPersistWrapper(postPersistBase), 0, smartcontract.AllowModifyStates)
p.AddMethod(md, desc, false)
p.AddMethod(md, desc)
return p
}

View file

@ -66,13 +66,14 @@ func newTestNative() *testNative {
manifest.NewParameter("addend2", smartcontract.IntegerType),
},
ReturnType: smartcontract.IntegerType,
Safe: true,
}
md := &interop.MethodAndPrice{
Func: tn.sum,
Price: testSumPrice,
RequiredFlags: smartcontract.NoneFlag,
}
tn.meta.AddMethod(md, desc, true)
tn.meta.AddMethod(md, desc)
desc = &manifest.Method{
Name: "callOtherContractNoReturn",
@ -82,16 +83,17 @@ func newTestNative() *testNative {
manifest.NewParameter("arg", smartcontract.ArrayType),
},
ReturnType: smartcontract.VoidType,
Safe: true,
}
md = &interop.MethodAndPrice{
Func: tn.callOtherContractNoReturn,
Price: testSumPrice,
RequiredFlags: smartcontract.NoneFlag}
tn.meta.AddMethod(md, desc, true)
tn.meta.AddMethod(md, desc)
desc = &manifest.Method{Name: "onPersist", ReturnType: smartcontract.BoolType}
md = &interop.MethodAndPrice{Func: tn.OnPersist, RequiredFlags: smartcontract.AllowModifyStates}
tn.meta.AddMethod(md, desc, false)
tn.meta.AddMethod(md, desc)
return tn
}

View file

@ -49,8 +49,6 @@ type Manifest struct {
SupportedStandards []string `json:"supportedstandards"`
// Trusts is a set of hashes to a which contract trusts.
Trusts WildUint160s `json:"trusts"`
// SafeMethods is a set of names of safe methods.
SafeMethods WildStrings `json:"safemethods"`
// Extra is an implementation-defined user data.
Extra interface{} `json:"extra"`
}
@ -67,7 +65,6 @@ func NewManifest(name string) *Manifest {
SupportedStandards: []string{},
}
m.Trusts.Restrict()
m.SafeMethods.Restrict()
return m
}
@ -101,10 +98,6 @@ func (a *ABI) GetEvent(name string) *Event {
// CanCall returns true is current contract is allowed to call
// method of another contract with specified hash.
func (m *Manifest) CanCall(hash util.Uint160, toCall *Manifest, method string) bool {
// this if is not present in the original code but should probably be here
if toCall.SafeMethods.Contains(method) {
return true
}
for i := range m.Permissions {
if m.Permissions[i].IsAllowed(hash, toCall, method) {
return true

View file

@ -13,33 +13,33 @@ import (
// https://github.com/neo-project/neo/blob/master/tests/neo.UnitTests/SmartContract/Manifest/UT_ContractManifest.cs#L10
func TestManifest_MarshalJSON(t *testing.T) {
t.Run("default", func(t *testing.T) {
s := `{"groups":[],"supportedstandards":[],"name":"Test","abi":{"methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"safemethods":[],"extra":null}`
s := `{"groups":[],"supportedstandards":[],"name":"Test","abi":{"methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":null}`
m := testUnmarshalMarshalManifest(t, s)
require.Equal(t, DefaultManifest("Test"), m)
})
t.Run("permissions", func(t *testing.T) {
s := `{"groups":[],"supportedstandards":[],"name":"Test","abi":{"methods":[],"events":[]},"permissions":[{"contract":"0x0000000000000000000000000000000000000000","methods":["method1","method2"]}],"trusts":[],"safemethods":[],"extra":null}`
s := `{"groups":[],"supportedstandards":[],"name":"Test","abi":{"methods":[],"events":[]},"permissions":[{"contract":"0x0000000000000000000000000000000000000000","methods":["method1","method2"]}],"trusts":[],"extra":null}`
testUnmarshalMarshalManifest(t, s)
})
t.Run("safe methods", func(t *testing.T) {
s := `{"groups":[],"supportedstandards":[],"name":"Test","abi":{"methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"safemethods":["balanceOf"],"extra":null}`
s := `{"groups":[],"supportedstandards":[],"name":"Test","abi":{"methods":[{"name":"safeMet","offset":123,"parameters":[],"returntype":"Integer","safe":true}],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":null}`
testUnmarshalMarshalManifest(t, s)
})
t.Run("trust", func(t *testing.T) {
s := `{"groups":[],"supportedstandards":[],"name":"Test","abi":{"methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":["0x0000000000000000000000000000000000000001"],"safemethods":[],"extra":null}`
s := `{"groups":[],"supportedstandards":[],"name":"Test","abi":{"methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":["0x0000000000000000000000000000000000000001"],"extra":null}`
testUnmarshalMarshalManifest(t, s)
})
t.Run("groups", func(t *testing.T) {
s := `{"groups":[{"pubkey":"03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c","signature":"QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQQ=="}],"supportedstandards":[],"name":"Test","abi":{"methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"safemethods":[],"extra":null}`
s := `{"groups":[{"pubkey":"03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c","signature":"QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQQ=="}],"supportedstandards":[],"name":"Test","abi":{"methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":null}`
testUnmarshalMarshalManifest(t, s)
})
t.Run("extra", func(t *testing.T) {
s := `{"groups":[],"supportedstandards":[],"name":"Test","abi":{"methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"safemethods":[],"extra":{"key":"value"}}`
s := `{"groups":[],"supportedstandards":[],"name":"Test","abi":{"methods":[],"events":[]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":{"key":"value"}}`
testUnmarshalMarshalManifest(t, s)
})
}
@ -57,19 +57,9 @@ func testUnmarshalMarshalManifest(t *testing.T, s string) *Manifest {
}
func TestManifest_CanCall(t *testing.T) {
t.Run("safe methods", func(t *testing.T) {
man1 := NewManifest("Test1")
man2 := DefaultManifest("Test2")
require.False(t, man1.CanCall(util.Uint160{}, man2, "method1"))
man2.SafeMethods.Add("method1")
require.True(t, man1.CanCall(util.Uint160{}, man2, "method1"))
})
t.Run("wildcard permission", func(t *testing.T) {
man1 := DefaultManifest("Test1")
man2 := DefaultManifest("Test2")
require.True(t, man1.CanCall(util.Uint160{}, man2, "method1"))
})
}
func TestPermission_IsAllowed(t *testing.T) {

View file

@ -41,6 +41,7 @@ type Method struct {
Offset int `json:"offset"`
Parameters []Parameter `json:"parameters"`
ReturnType smartcontract.ParamType `json:"returntype"`
Safe bool `json:"safe"`
}
// NewParameter returns new parameter of specified name and type.