neoneo-go/pkg/smartcontract/manifest/manifest.go
Anna Shaleva 2581146a01 smartcontract: fix wildcard trusts deserialization
If manifest trusts presented as Null stackitem, then they should be
treated as wildcard, not as restricted.

It's not the same problem as described and fixed in
https://github.com/neo-project/neo/pull/2901 and
https://github.com/neo-project/neo/pull/2892, although these
two PRs are related.

Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
2023-09-25 11:53:47 +03:00

275 lines
7.9 KiB
Go

package manifest
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"math"
ojson "github.com/nspcc-dev/go-ordered-json"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
)
const (
// MaxManifestSize is the max length for a valid contract manifest.
MaxManifestSize = math.MaxUint16
// NEP11StandardName represents the name of NEP-11 smartcontract standard.
NEP11StandardName = "NEP-11"
// NEP17StandardName represents the name of NEP-17 smartcontract standard.
NEP17StandardName = "NEP-17"
// NEP11Payable represents the name of contract interface which can receive NEP-11 tokens.
NEP11Payable = "NEP-11-Payable"
// NEP17Payable represents the name of contract interface which can receive NEP-17 tokens.
NEP17Payable = "NEP-17-Payable"
)
// Manifest represens contract metadata.
type Manifest struct {
// Name is a contract's name.
Name string `json:"name"`
// ABI is a contract's ABI.
ABI ABI `json:"abi"`
// Features is a set of contract features. Currently unused.
Features json.RawMessage `json:"features"`
// Groups is a set of groups to which a contract belongs.
Groups []Group `json:"groups"`
Permissions []Permission `json:"permissions"`
// SupportedStandards is a list of standards supported by the contract.
SupportedStandards []string `json:"supportedstandards"`
// Trusts is a set of hashes to a which contract trusts.
Trusts WildPermissionDescs `json:"trusts"`
// Extra is an implementation-defined user data.
Extra json.RawMessage `json:"extra"`
}
// NewManifest returns a new manifest with necessary fields initialized.
func NewManifest(name string) *Manifest {
m := &Manifest{
Name: name,
ABI: ABI{
Methods: []Method{},
Events: []Event{},
},
Features: json.RawMessage("{}"),
Groups: []Group{},
Permissions: []Permission{},
SupportedStandards: []string{},
Extra: json.RawMessage("null"),
}
m.Trusts.Restrict()
return m
}
// DefaultManifest returns the default contract manifest.
func DefaultManifest(name string) *Manifest {
m := NewManifest(name)
m.Permissions = []Permission{*NewPermission(PermissionWildcard)}
return m
}
// CanCall returns true if the current contract is allowed to call
// the method of another contract with the specified hash.
func (m *Manifest) CanCall(hash util.Uint160, toCall *Manifest, method string) bool {
for i := range m.Permissions {
if m.Permissions[i].IsAllowed(hash, toCall, method) {
return true
}
}
return false
}
// IsValid checks manifest internal consistency and correctness, one of the
// checks is for group signature correctness, contract hash is passed for it.
// If hash is empty, then hash-related checks are omitted.
func (m *Manifest) IsValid(hash util.Uint160) error {
var err error
if m.Name == "" {
return errors.New("no name")
}
for i := range m.SupportedStandards {
if m.SupportedStandards[i] == "" {
return errors.New("invalid nameless supported standard")
}
}
if len(m.SupportedStandards) > 1 {
names := make([]string, len(m.SupportedStandards))
copy(names, m.SupportedStandards)
if stringsHaveDups(names) {
return errors.New("duplicate supported standards")
}
}
err = m.ABI.IsValid()
if err != nil {
return fmt.Errorf("ABI: %w", err)
}
err = Groups(m.Groups).AreValid(hash)
if err != nil {
return err
}
if len(m.Trusts.Value) > 1 {
hashes := make([]PermissionDesc, len(m.Trusts.Value))
copy(hashes, m.Trusts.Value)
if permissionDescsHaveDups(hashes) {
return errors.New("duplicate trusted contracts")
}
}
return Permissions(m.Permissions).AreValid()
}
// IsStandardSupported denotes whether the specified standard is supported by the contract.
func (m *Manifest) IsStandardSupported(standard string) bool {
for _, st := range m.SupportedStandards {
if st == standard {
return true
}
}
return false
}
// ToStackItem converts Manifest to stackitem.Item.
func (m *Manifest) ToStackItem() (stackitem.Item, error) {
groups := make([]stackitem.Item, len(m.Groups))
for i := range m.Groups {
groups[i] = m.Groups[i].ToStackItem()
}
supportedStandards := make([]stackitem.Item, len(m.SupportedStandards))
for i := range m.SupportedStandards {
supportedStandards[i] = stackitem.Make(m.SupportedStandards[i])
}
abi := m.ABI.ToStackItem()
permissions := make([]stackitem.Item, len(m.Permissions))
for i := range m.Permissions {
permissions[i] = m.Permissions[i].ToStackItem()
}
trusts := stackitem.Item(stackitem.Null{})
if !m.Trusts.IsWildcard() {
tItems := make([]stackitem.Item, len(m.Trusts.Value))
for i, v := range m.Trusts.Value {
tItems[i] = v.ToStackItem()
}
trusts = stackitem.Make(tItems)
}
extra := extraToStackItem(m.Extra)
return stackitem.NewStruct([]stackitem.Item{
stackitem.Make(m.Name),
stackitem.Make(groups),
stackitem.NewMap(),
stackitem.Make(supportedStandards),
abi,
stackitem.Make(permissions),
trusts,
extra,
}), nil
}
// extraToStackItem removes indentation from `Extra` field in JSON and
// converts it to a byte-array stack item.
func extraToStackItem(rawExtra []byte) stackitem.Item {
extra := stackitem.Make("null")
if rawExtra == nil || string(rawExtra) == "null" {
return extra
}
d := ojson.NewDecoder(bytes.NewReader(rawExtra))
// The result is put directly in the database and affects state-root calculation,
// thus use ordered map to stay compatible with C# implementation.
d.UseOrderedObject()
// Prevent accidental precision loss.
d.UseNumber()
var obj any
// The error can't really occur because `json.RawMessage` is already a valid json.
_ = d.Decode(&obj)
res, _ := ojson.Marshal(obj)
return stackitem.NewByteArray(res)
}
// FromStackItem converts stackitem.Item to Manifest.
func (m *Manifest) FromStackItem(item stackitem.Item) error {
var err error
if item.Type() != stackitem.StructT {
return errors.New("invalid Manifest stackitem type")
}
str := item.Value().([]stackitem.Item)
if len(str) != 8 {
return errors.New("invalid stackitem length")
}
m.Name, err = stackitem.ToString(str[0])
if err != nil {
return err
}
if str[1].Type() != stackitem.ArrayT {
return errors.New("invalid Groups stackitem type")
}
groups := str[1].Value().([]stackitem.Item)
m.Groups = make([]Group, len(groups))
for i := range groups {
group := new(Group)
err := group.FromStackItem(groups[i])
if err != nil {
return err
}
m.Groups[i] = *group
}
if str[2].Type() != stackitem.MapT || str[2].(*stackitem.Map).Len() != 0 {
return errors.New("invalid Features stackitem")
}
m.Features = json.RawMessage("{}")
if str[3].Type() != stackitem.ArrayT {
return errors.New("invalid SupportedStandards stackitem type")
}
supportedStandards := str[3].Value().([]stackitem.Item)
m.SupportedStandards = make([]string, len(supportedStandards))
for i := range supportedStandards {
m.SupportedStandards[i], err = stackitem.ToString(supportedStandards[i])
if err != nil {
return err
}
}
abi := new(ABI)
if err := abi.FromStackItem(str[4]); err != nil {
return err
}
m.ABI = *abi
if str[5].Type() != stackitem.ArrayT {
return errors.New("invalid Permissions stackitem type")
}
permissions := str[5].Value().([]stackitem.Item)
m.Permissions = make([]Permission, len(permissions))
for i := range permissions {
p := new(Permission)
if err := p.FromStackItem(permissions[i]); err != nil {
return err
}
m.Permissions[i] = *p
}
if _, ok := str[6].(stackitem.Null); ok {
m.Trusts = WildPermissionDescs{Value: nil} // wildcard by default
} else {
if str[6].Type() != stackitem.ArrayT {
return errors.New("invalid Trusts stackitem type")
}
trusts := str[6].Value().([]stackitem.Item)
m.Trusts = WildPermissionDescs{Value: make([]PermissionDesc, len(trusts))}
for i := range trusts {
v := new(PermissionDesc)
err = v.FromStackItem(trusts[i])
if err != nil {
return err
}
m.Trusts.Value[i] = *v
}
}
extra, err := str[7].TryBytes()
if err != nil {
return err
}
m.Extra = extra
return nil
}