neo-go/pkg/core/transaction/witness_scope.go
Anna Shaleva 9f69522ff5 neorpc: adjust SignerWithWitness scopes parsing
Ensure that Scopes can be properly parsed not only from the string
representation, but also from a single byte. transaction.Signer
is not affected (checked against the C# implementation), only
RPC-related signer scopes are allowed to be unmarshalled from byte.

Close #3059.

Signed-off-by: Anna Shaleva <shaleva.ann@nspcc.ru>
2023-07-20 10:33:48 +03:00

122 lines
3.8 KiB
Go

package transaction
//go:generate stringer -type=WitnessScope -linecomment -output=witness_scope_string.go
import (
"encoding/json"
"errors"
"fmt"
"strings"
)
// WitnessScope represents set of witness flags for Transaction signer.
type WitnessScope byte
const (
// None specifies that no contract was witnessed. Only sign the transaction.
None WitnessScope = 0
// CalledByEntry witness is only valid in entry script and ones directly called by it.
// No params is needed, as the witness/permission/signature given on first invocation will
// automatically expire if entering deeper internal invokes. This can be default safe
// choice for native NEO/GAS (previously used on Neo 2 as "attach" mode).
CalledByEntry WitnessScope = 0x01
// CustomContracts define custom hash for contract-specific.
CustomContracts WitnessScope = 0x10
// CustomGroups define custom pubkey for group members.
CustomGroups WitnessScope = 0x20
// Rules is a set of conditions with boolean operators.
Rules WitnessScope = 0x40 // WitnessRules
// Global allows this witness in all contexts (default Neo2 behavior).
// This cannot be combined with other flags.
Global WitnessScope = 0x80
)
// ScopesFromByte converts byte to a set of WitnessScopes and performs validity
// check.
func ScopesFromByte(b byte) (WitnessScope, error) {
var res = WitnessScope(b)
if (res&Global != 0) && (res&(None|CalledByEntry|CustomContracts|CustomGroups|Rules) != 0) {
return 0, errors.New("Global scope can not be combined with other scopes")
}
if res&^(None|CalledByEntry|CustomContracts|CustomGroups|Rules|Global) != 0 {
return 0, fmt.Errorf("invalid scope %d", res)
}
return res, nil
}
// ScopesFromString converts string of comma-separated scopes to a set of scopes
// (case-sensitive). String can combine several scopes, e.g. be any of: 'Global',
// 'CalledByEntry,CustomGroups' etc. In case of an empty string an error will be
// returned.
func ScopesFromString(s string) (WitnessScope, error) {
var result WitnessScope
scopes := strings.Split(s, ",")
for i, scope := range scopes {
scopes[i] = strings.TrimSpace(scope)
}
dict := map[string]WitnessScope{
Global.String(): Global,
CalledByEntry.String(): CalledByEntry,
CustomContracts.String(): CustomContracts,
CustomGroups.String(): CustomGroups,
Rules.String(): Rules,
None.String(): None,
}
var isGlobal bool
for _, scopeStr := range scopes {
scope, ok := dict[scopeStr]
if !ok {
return result, fmt.Errorf("invalid witness scope: %v", scopeStr)
}
if isGlobal && !(scope == Global) {
return result, fmt.Errorf("Global scope can not be combined with other scopes")
}
result |= scope
if scope == Global {
isGlobal = true
}
}
return result, nil
}
func appendScopeString(str string, scopes WitnessScope, scope WitnessScope) string {
if scopes&scope != 0 {
if len(str) != 0 {
str += ", "
}
str += scope.String()
}
return str
}
// scopesToString converts witness scope to it's string representation. It uses
// `, ` to separate scope names.
func scopesToString(scopes WitnessScope) string {
if scopes&Global != 0 || scopes == None {
return scopes.String()
}
var res string
res = appendScopeString(res, scopes, CalledByEntry)
res = appendScopeString(res, scopes, CustomContracts)
res = appendScopeString(res, scopes, CustomGroups)
res = appendScopeString(res, scopes, Rules)
return res
}
// MarshalJSON implements the json.Marshaler interface.
func (s WitnessScope) MarshalJSON() ([]byte, error) {
return []byte(`"` + scopesToString(s) + `"`), nil
}
// UnmarshalJSON implements the json.Unmarshaler interface.
func (s *WitnessScope) UnmarshalJSON(data []byte) error {
var js string
if err := json.Unmarshal(data, &js); err != nil {
return err
}
scopes, err := ScopesFromString(js)
if err != nil {
return err
}
*s = scopes
return nil
}