forked from TrueCloudLab/neoneo-go
Merge pull request #690 from nspcc-dev/feature/getapplicationlog
rpc: implement getapplicationlog RPC
This commit is contained in:
commit
252a9f2f31
29 changed files with 1661 additions and 1027 deletions
|
@ -296,10 +296,10 @@ func initSmartContract(ctx *cli.Context) error {
|
||||||
// TODO: Fix the missing neo-go.yml file with the `init` command when the package manager is in place.
|
// TODO: Fix the missing neo-go.yml file with the `init` command when the package manager is in place.
|
||||||
if !ctx.Bool("skip-details") {
|
if !ctx.Bool("skip-details") {
|
||||||
details := parseContractDetails()
|
details := parseContractDetails()
|
||||||
details.ReturnType = request.ByteArray
|
details.ReturnType = smartcontract.ByteArrayType
|
||||||
details.Parameters = make([]request.StackParamType, 2)
|
details.Parameters = make([]smartcontract.ParamType, 2)
|
||||||
details.Parameters[0] = request.String
|
details.Parameters[0] = smartcontract.StringType
|
||||||
details.Parameters[1] = request.Array
|
details.Parameters[1] = smartcontract.ArrayType
|
||||||
|
|
||||||
project := &ProjectConfig{Contract: details}
|
project := &ProjectConfig{Contract: details}
|
||||||
b, err := yaml.Marshal(project)
|
b, err := yaml.Marshal(project)
|
||||||
|
|
|
@ -36,7 +36,7 @@ which would yield the response:
|
||||||
| Method | Implemented |
|
| Method | Implemented |
|
||||||
| ------- | ------------|
|
| ------- | ------------|
|
||||||
| `getaccountstate` | Yes |
|
| `getaccountstate` | Yes |
|
||||||
| `getapplicationlog` | No (#500) |
|
| `getapplicationlog` | Yes |
|
||||||
| `getassetstate` | Yes |
|
| `getassetstate` | Yes |
|
||||||
| `getbestblockhash` | Yes |
|
| `getbestblockhash` | Yes |
|
||||||
| `getblock` | Yes |
|
| `getblock` | Yes |
|
||||||
|
|
|
@ -880,6 +880,12 @@ func (bc *Blockchain) GetTransaction(hash util.Uint256) (*transaction.Transactio
|
||||||
return bc.dao.GetTransaction(hash)
|
return bc.dao.GetTransaction(hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetAppExecResult returns application execution result by the given
|
||||||
|
// tx hash.
|
||||||
|
func (bc *Blockchain) GetAppExecResult(hash util.Uint256) (*state.AppExecResult, error) {
|
||||||
|
return bc.dao.GetAppExecResult(hash)
|
||||||
|
}
|
||||||
|
|
||||||
// GetStorageItem returns an item from storage.
|
// GetStorageItem returns an item from storage.
|
||||||
func (bc *Blockchain) GetStorageItem(scripthash util.Uint160, key []byte) *state.StorageItem {
|
func (bc *Blockchain) GetStorageItem(scripthash util.Uint160, key []byte) *state.StorageItem {
|
||||||
return bc.dao.GetStorageItem(scripthash, key)
|
return bc.dao.GetStorageItem(scripthash, key)
|
||||||
|
@ -1717,6 +1723,6 @@ func (bc *Blockchain) secondsPerBlock() int {
|
||||||
return bc.config.SecondsPerBlock
|
return bc.config.SecondsPerBlock
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bc *Blockchain) newInteropContext(trigger byte, s storage.Store, block *block.Block, tx *transaction.Transaction) *interopContext {
|
func (bc *Blockchain) newInteropContext(trigger trigger.Type, s storage.Store, block *block.Block, tx *transaction.Transaction) *interopContext {
|
||||||
return newInteropContext(trigger, bc, s, block, tx, bc.log)
|
return newInteropContext(trigger, bc, s, block, tx, bc.log)
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ type Blockchainer interface {
|
||||||
HasTransaction(util.Uint256) bool
|
HasTransaction(util.Uint256) bool
|
||||||
GetAssetState(util.Uint256) *state.Asset
|
GetAssetState(util.Uint256) *state.Asset
|
||||||
GetAccountState(util.Uint160) *state.Account
|
GetAccountState(util.Uint160) *state.Account
|
||||||
|
GetAppExecResult(util.Uint256) (*state.AppExecResult, error)
|
||||||
GetValidators(txes ...*transaction.Transaction) ([]*keys.PublicKey, error)
|
GetValidators(txes ...*transaction.Transaction) ([]*keys.PublicKey, error)
|
||||||
GetScriptHashesForVerifying(*transaction.Transaction) ([]util.Uint160, error)
|
GetScriptHashesForVerifying(*transaction.Transaction) ([]util.Uint160, error)
|
||||||
GetStorageItem(scripthash util.Uint160, key []byte) *state.StorageItem
|
GetStorageItem(scripthash util.Uint160, key []byte) *state.StorageItem
|
||||||
|
|
|
@ -14,13 +14,14 @@ import (
|
||||||
"github.com/CityOfZion/neo-go/pkg/core/state"
|
"github.com/CityOfZion/neo-go/pkg/core/state"
|
||||||
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
"github.com/CityOfZion/neo-go/pkg/core/storage"
|
||||||
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/smartcontract/trigger"
|
||||||
"github.com/CityOfZion/neo-go/pkg/vm"
|
"github.com/CityOfZion/neo-go/pkg/vm"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
type interopContext struct {
|
type interopContext struct {
|
||||||
bc Blockchainer
|
bc Blockchainer
|
||||||
trigger byte
|
trigger trigger.Type
|
||||||
block *block.Block
|
block *block.Block
|
||||||
tx *transaction.Transaction
|
tx *transaction.Transaction
|
||||||
dao *cachedDao
|
dao *cachedDao
|
||||||
|
@ -28,7 +29,7 @@ type interopContext struct {
|
||||||
log *zap.Logger
|
log *zap.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func newInteropContext(trigger byte, bc Blockchainer, s storage.Store, block *block.Block, tx *transaction.Transaction, log *zap.Logger) *interopContext {
|
func newInteropContext(trigger trigger.Type, bc Blockchainer, s storage.Store, block *block.Block, tx *transaction.Transaction, log *zap.Logger) *interopContext {
|
||||||
dao := newCachedDao(s)
|
dao := newCachedDao(s)
|
||||||
nes := make([]state.NotificationEvent, 0)
|
nes := make([]state.NotificationEvent, 0)
|
||||||
return &interopContext{bc, trigger, block, tx, dao, nes, log}
|
return &interopContext{bc, trigger, block, tx, dao, nes, log}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/CityOfZion/neo-go/pkg/io"
|
"github.com/CityOfZion/neo-go/pkg/io"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/smartcontract/trigger"
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
"github.com/CityOfZion/neo-go/pkg/vm"
|
"github.com/CityOfZion/neo-go/pkg/vm"
|
||||||
)
|
)
|
||||||
|
@ -17,7 +18,7 @@ type NotificationEvent struct {
|
||||||
// all resulting notifications, state, stack and other metadata.
|
// all resulting notifications, state, stack and other metadata.
|
||||||
type AppExecResult struct {
|
type AppExecResult struct {
|
||||||
TxHash util.Uint256
|
TxHash util.Uint256
|
||||||
Trigger byte
|
Trigger trigger.Type
|
||||||
VMState string
|
VMState string
|
||||||
GasConsumed util.Fixed8
|
GasConsumed util.Fixed8
|
||||||
Stack string // JSON
|
Stack string // JSON
|
||||||
|
@ -39,7 +40,7 @@ func (ne *NotificationEvent) DecodeBinary(r *io.BinReader) {
|
||||||
// EncodeBinary implements the Serializable interface.
|
// EncodeBinary implements the Serializable interface.
|
||||||
func (aer *AppExecResult) EncodeBinary(w *io.BinWriter) {
|
func (aer *AppExecResult) EncodeBinary(w *io.BinWriter) {
|
||||||
w.WriteBytes(aer.TxHash[:])
|
w.WriteBytes(aer.TxHash[:])
|
||||||
w.WriteB(aer.Trigger)
|
w.WriteB(byte(aer.Trigger))
|
||||||
w.WriteString(aer.VMState)
|
w.WriteString(aer.VMState)
|
||||||
aer.GasConsumed.EncodeBinary(w)
|
aer.GasConsumed.EncodeBinary(w)
|
||||||
w.WriteString(aer.Stack)
|
w.WriteString(aer.Stack)
|
||||||
|
@ -49,7 +50,7 @@ func (aer *AppExecResult) EncodeBinary(w *io.BinWriter) {
|
||||||
// DecodeBinary implements the Serializable interface.
|
// DecodeBinary implements the Serializable interface.
|
||||||
func (aer *AppExecResult) DecodeBinary(r *io.BinReader) {
|
func (aer *AppExecResult) DecodeBinary(r *io.BinReader) {
|
||||||
r.ReadBytes(aer.TxHash[:])
|
r.ReadBytes(aer.TxHash[:])
|
||||||
aer.Trigger = r.ReadB()
|
aer.Trigger = trigger.Type(r.ReadB())
|
||||||
aer.VMState = r.ReadString()
|
aer.VMState = r.ReadString()
|
||||||
aer.GasConsumed.DecodeBinary(r)
|
aer.GasConsumed.DecodeBinary(r)
|
||||||
aer.Stack = r.ReadString()
|
aer.Stack = r.ReadString()
|
||||||
|
|
|
@ -67,6 +67,9 @@ func (chain *testChain) Close() {
|
||||||
func (chain testChain) HeaderHeight() uint32 {
|
func (chain testChain) HeaderHeight() uint32 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
func (chain testChain) GetAppExecResult(hash util.Uint256) (*state.AppExecResult, error) {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
func (chain testChain) GetBlock(hash util.Uint256) (*block.Block, error) {
|
func (chain testChain) GetBlock(hash util.Uint256) (*block.Block, error) {
|
||||||
panic("TODO")
|
panic("TODO")
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/encoding/address"
|
"github.com/CityOfZion/neo-go/pkg/encoding/address"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/smartcontract"
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
@ -24,8 +25,8 @@ type (
|
||||||
// FuncParam represents a function argument parameter used in the
|
// FuncParam represents a function argument parameter used in the
|
||||||
// invokefunction RPC method.
|
// invokefunction RPC method.
|
||||||
FuncParam struct {
|
FuncParam struct {
|
||||||
Type StackParamType `json:"type"`
|
Type smartcontract.ParamType `json:"type"`
|
||||||
Value Param `json:"value"`
|
Value Param `json:"value"`
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/encoding/address"
|
"github.com/CityOfZion/neo-go/pkg/encoding/address"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/smartcontract"
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
@ -41,7 +42,7 @@ func TestParam_UnmarshalJSON(t *testing.T) {
|
||||||
{
|
{
|
||||||
Type: FuncParamT,
|
Type: FuncParamT,
|
||||||
Value: FuncParam{
|
Value: FuncParam{
|
||||||
Type: String,
|
Type: smartcontract.StringType,
|
||||||
Value: Param{
|
Value: Param{
|
||||||
Type: StringT,
|
Type: StringT,
|
||||||
Value: "jajaja",
|
Value: "jajaja",
|
||||||
|
@ -146,7 +147,7 @@ func TestParamGetUint160FromAddress(t *testing.T) {
|
||||||
|
|
||||||
func TestParamGetFuncParam(t *testing.T) {
|
func TestParamGetFuncParam(t *testing.T) {
|
||||||
fp := FuncParam{
|
fp := FuncParam{
|
||||||
Type: String,
|
Type: smartcontract.StringType,
|
||||||
Value: Param{
|
Value: Param{
|
||||||
Type: StringT,
|
Type: StringT,
|
||||||
Value: "jajaja",
|
Value: "jajaja",
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package request
|
package request
|
||||||
|
|
||||||
|
import "github.com/CityOfZion/neo-go/pkg/smartcontract"
|
||||||
|
|
||||||
// ContractDetails contains contract metadata.
|
// ContractDetails contains contract metadata.
|
||||||
type ContractDetails struct {
|
type ContractDetails struct {
|
||||||
Author string
|
Author string
|
||||||
|
@ -10,6 +12,6 @@ type ContractDetails struct {
|
||||||
HasStorage bool
|
HasStorage bool
|
||||||
HasDynamicInvocation bool
|
HasDynamicInvocation bool
|
||||||
IsPayable bool
|
IsPayable bool
|
||||||
ReturnType StackParamType
|
ReturnType smartcontract.ParamType
|
||||||
Parameters []StackParamType
|
Parameters []smartcontract.ParamType
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,291 +0,0 @@
|
||||||
package request
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"encoding/hex"
|
|
||||||
"encoding/json"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
// StackParamType represents different types of stack values.
|
|
||||||
type StackParamType int
|
|
||||||
|
|
||||||
// All possible StackParamType values are listed here.
|
|
||||||
const (
|
|
||||||
Unknown StackParamType = -1
|
|
||||||
Signature StackParamType = 0x00
|
|
||||||
Boolean StackParamType = 0x01
|
|
||||||
Integer StackParamType = 0x02
|
|
||||||
Hash160 StackParamType = 0x03
|
|
||||||
Hash256 StackParamType = 0x04
|
|
||||||
ByteArray StackParamType = 0x05
|
|
||||||
PublicKey StackParamType = 0x06
|
|
||||||
String StackParamType = 0x07
|
|
||||||
Array StackParamType = 0x10
|
|
||||||
InteropInterface StackParamType = 0xf0
|
|
||||||
Void StackParamType = 0xff
|
|
||||||
)
|
|
||||||
|
|
||||||
// String implements the stringer interface.
|
|
||||||
func (t StackParamType) String() string {
|
|
||||||
switch t {
|
|
||||||
case Signature:
|
|
||||||
return "Signature"
|
|
||||||
case Boolean:
|
|
||||||
return "Boolean"
|
|
||||||
case Integer:
|
|
||||||
return "Integer"
|
|
||||||
case Hash160:
|
|
||||||
return "Hash160"
|
|
||||||
case Hash256:
|
|
||||||
return "Hash256"
|
|
||||||
case ByteArray:
|
|
||||||
return "ByteArray"
|
|
||||||
case PublicKey:
|
|
||||||
return "PublicKey"
|
|
||||||
case String:
|
|
||||||
return "String"
|
|
||||||
case Array:
|
|
||||||
return "Array"
|
|
||||||
case InteropInterface:
|
|
||||||
return "InteropInterface"
|
|
||||||
case Void:
|
|
||||||
return "Void"
|
|
||||||
default:
|
|
||||||
return "Unknown"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// StackParamTypeFromString converts string into the StackParamType.
|
|
||||||
func StackParamTypeFromString(s string) (StackParamType, error) {
|
|
||||||
switch s {
|
|
||||||
case "Signature":
|
|
||||||
return Signature, nil
|
|
||||||
case "Boolean":
|
|
||||||
return Boolean, nil
|
|
||||||
case "Integer":
|
|
||||||
return Integer, nil
|
|
||||||
case "Hash160":
|
|
||||||
return Hash160, nil
|
|
||||||
case "Hash256":
|
|
||||||
return Hash256, nil
|
|
||||||
case "ByteArray":
|
|
||||||
return ByteArray, nil
|
|
||||||
case "PublicKey":
|
|
||||||
return PublicKey, nil
|
|
||||||
case "String":
|
|
||||||
return String, nil
|
|
||||||
case "Array":
|
|
||||||
return Array, nil
|
|
||||||
case "InteropInterface":
|
|
||||||
return InteropInterface, nil
|
|
||||||
case "Void":
|
|
||||||
return Void, nil
|
|
||||||
default:
|
|
||||||
return Unknown, errors.Errorf("unknown stack parameter type: %s", s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON implements the json.Marshaler interface.
|
|
||||||
func (t *StackParamType) MarshalJSON() ([]byte, error) {
|
|
||||||
return []byte(`"` + t.String() + `"`), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON sets StackParamType from JSON-encoded data.
|
|
||||||
func (t *StackParamType) UnmarshalJSON(data []byte) (err error) {
|
|
||||||
var (
|
|
||||||
s = string(data)
|
|
||||||
l = len(s)
|
|
||||||
)
|
|
||||||
if l < 2 || s[0] != '"' || s[l-1] != '"' {
|
|
||||||
*t = Unknown
|
|
||||||
return errors.Errorf("invalid type: %s", s)
|
|
||||||
}
|
|
||||||
*t, err = StackParamTypeFromString(s[1 : l-1])
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalYAML implements the YAML Marshaler interface.
|
|
||||||
func (t *StackParamType) MarshalYAML() (interface{}, error) {
|
|
||||||
return t.String(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalYAML implements the YAML Unmarshaler interface.
|
|
||||||
func (t *StackParamType) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|
||||||
var name string
|
|
||||||
|
|
||||||
err := unmarshal(&name)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*t, err = StackParamTypeFromString(name)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// StackParam represent a stack parameter.
|
|
||||||
type StackParam struct {
|
|
||||||
Type StackParamType `json:"type"`
|
|
||||||
Value interface{} `json:"value"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type rawStackParam struct {
|
|
||||||
Type StackParamType `json:"type"`
|
|
||||||
Value json.RawMessage `json:"value"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON implements Unmarshaler interface.
|
|
||||||
func (p *StackParam) UnmarshalJSON(data []byte) (err error) {
|
|
||||||
var (
|
|
||||||
r rawStackParam
|
|
||||||
i int64
|
|
||||||
s string
|
|
||||||
b []byte
|
|
||||||
)
|
|
||||||
|
|
||||||
if err = json.Unmarshal(data, &r); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch p.Type = r.Type; r.Type {
|
|
||||||
case ByteArray:
|
|
||||||
if err = json.Unmarshal(r.Value, &s); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if b, err = hex.DecodeString(s); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.Value = b
|
|
||||||
case String:
|
|
||||||
if err = json.Unmarshal(r.Value, &s); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.Value = s
|
|
||||||
case Integer:
|
|
||||||
if err = json.Unmarshal(r.Value, &i); err == nil {
|
|
||||||
p.Value = i
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// sometimes integer comes as string
|
|
||||||
if err = json.Unmarshal(r.Value, &s); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if i, err = strconv.ParseInt(s, 10, 64); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.Value = i
|
|
||||||
case Array:
|
|
||||||
// https://github.com/neo-project/neo/blob/3d59ecca5a8deb057bdad94b3028a6d5e25ac088/neo/Network/RPC/RpcServer.cs#L67
|
|
||||||
var rs []StackParam
|
|
||||||
if err = json.Unmarshal(r.Value, &rs); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.Value = rs
|
|
||||||
case Hash160:
|
|
||||||
var h util.Uint160
|
|
||||||
if err = json.Unmarshal(r.Value, &h); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.Value = h
|
|
||||||
case Hash256:
|
|
||||||
var h util.Uint256
|
|
||||||
if err = json.Unmarshal(r.Value, &h); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
p.Value = h
|
|
||||||
default:
|
|
||||||
return errors.New("not implemented")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// StackParams is an array of StackParam (TODO: drop it?).
|
|
||||||
type StackParams []StackParam
|
|
||||||
|
|
||||||
// TryParseArray converts an array of StackParam into an array of more appropriate things.
|
|
||||||
func (p StackParams) TryParseArray(vals ...interface{}) error {
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
i int
|
|
||||||
par StackParam
|
|
||||||
)
|
|
||||||
if len(p) != len(vals) {
|
|
||||||
return errors.New("receiver array doesn't fit the StackParams length")
|
|
||||||
}
|
|
||||||
for i, par = range p {
|
|
||||||
if err = par.TryParse(vals[i]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// TryParse converts one StackParam into something more appropriate.
|
|
||||||
func (p StackParam) TryParse(dest interface{}) error {
|
|
||||||
var (
|
|
||||||
err error
|
|
||||||
ok bool
|
|
||||||
data []byte
|
|
||||||
)
|
|
||||||
switch p.Type {
|
|
||||||
case ByteArray:
|
|
||||||
if data, ok = p.Value.([]byte); !ok {
|
|
||||||
return errors.Errorf("failed to cast %s to []byte", p.Value)
|
|
||||||
}
|
|
||||||
switch dest := dest.(type) {
|
|
||||||
case *util.Uint160:
|
|
||||||
if *dest, err = util.Uint160DecodeBytesBE(data); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
case *[]byte:
|
|
||||||
*dest = data
|
|
||||||
return nil
|
|
||||||
case *util.Uint256:
|
|
||||||
if *dest, err = util.Uint256DecodeBytesLE(data); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
case *int64, *int32, *int16, *int8, *int, *uint64, *uint32, *uint16, *uint8, *uint:
|
|
||||||
i := bytesToUint64(data)
|
|
||||||
switch dest := dest.(type) {
|
|
||||||
case *int64:
|
|
||||||
*dest = int64(i)
|
|
||||||
case *int32:
|
|
||||||
*dest = int32(i)
|
|
||||||
case *int16:
|
|
||||||
*dest = int16(i)
|
|
||||||
case *int8:
|
|
||||||
*dest = int8(i)
|
|
||||||
case *int:
|
|
||||||
*dest = int(i)
|
|
||||||
case *uint64:
|
|
||||||
*dest = i
|
|
||||||
case *uint32:
|
|
||||||
*dest = uint32(i)
|
|
||||||
case *uint16:
|
|
||||||
*dest = uint16(i)
|
|
||||||
case *uint8:
|
|
||||||
*dest = uint8(i)
|
|
||||||
case *uint:
|
|
||||||
*dest = uint(i)
|
|
||||||
}
|
|
||||||
case *string:
|
|
||||||
*dest = string(data)
|
|
||||||
return nil
|
|
||||||
default:
|
|
||||||
return errors.Errorf("cannot cast stackparam of type %s to type %s", p.Type, dest)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return errors.New("cannot define stackparam type")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func bytesToUint64(b []byte) uint64 {
|
|
||||||
data := make([]byte, 8)
|
|
||||||
copy(data[8-len(b):], util.ArrayReverse(b))
|
|
||||||
return binary.BigEndian.Uint64(data)
|
|
||||||
}
|
|
|
@ -1,200 +0,0 @@
|
||||||
package request
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
var testCases = []struct {
|
|
||||||
input string
|
|
||||||
result StackParam
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
input: `{"type":"Integer","value":12345}`,
|
|
||||||
result: StackParam{Type: Integer, Value: int64(12345)},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: `{"type":"Integer","value":"12345"}`,
|
|
||||||
result: StackParam{Type: Integer, Value: int64(12345)},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: `{"type":"ByteArray","value":"010203"}`,
|
|
||||||
result: StackParam{Type: ByteArray, Value: []byte{0x01, 0x02, 0x03}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: `{"type":"String","value":"Some string"}`,
|
|
||||||
result: StackParam{Type: String, Value: "Some string"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: `{"type":"Array","value":[
|
|
||||||
{"type": "String", "value": "str 1"},
|
|
||||||
{"type": "Integer", "value": 2}]}`,
|
|
||||||
result: StackParam{
|
|
||||||
Type: Array,
|
|
||||||
Value: []StackParam{
|
|
||||||
{Type: String, Value: "str 1"},
|
|
||||||
{Type: Integer, Value: int64(2)},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: `{"type": "Hash160", "value": "0bcd2978634d961c24f5aea0802297ff128724d6"}`,
|
|
||||||
result: StackParam{
|
|
||||||
Type: Hash160,
|
|
||||||
Value: util.Uint160{
|
|
||||||
0x0b, 0xcd, 0x29, 0x78, 0x63, 0x4d, 0x96, 0x1c, 0x24, 0xf5,
|
|
||||||
0xae, 0xa0, 0x80, 0x22, 0x97, 0xff, 0x12, 0x87, 0x24, 0xd6,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: `{"type": "Hash256", "value": "f037308fa0ab18155bccfc08485468c112409ea5064595699e98c545f245f32d"}`,
|
|
||||||
result: StackParam{
|
|
||||||
Type: Hash256,
|
|
||||||
Value: util.Uint256{
|
|
||||||
0x2d, 0xf3, 0x45, 0xf2, 0x45, 0xc5, 0x98, 0x9e,
|
|
||||||
0x69, 0x95, 0x45, 0x06, 0xa5, 0x9e, 0x40, 0x12,
|
|
||||||
0xc1, 0x68, 0x54, 0x48, 0x08, 0xfc, 0xcc, 0x5b,
|
|
||||||
0x15, 0x18, 0xab, 0xa0, 0x8f, 0x30, 0x37, 0xf0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var errorCases = []string{
|
|
||||||
`{"type": "ByteArray","value":`, // incorrect JSON
|
|
||||||
`{"type": "ByteArray","value":1}`, // incorrect Value
|
|
||||||
`{"type": "ByteArray","value":"12zz"}`, // incorrect ByteArray value
|
|
||||||
`{"type": "String","value":`, // incorrect JSON
|
|
||||||
`{"type": "String","value":1}`, // incorrect Value
|
|
||||||
`{"type": "Integer","value": "nn"}`, // incorrect Integer value
|
|
||||||
`{"type": "Integer","value": []}`, // incorrect Integer value
|
|
||||||
`{"type": "Array","value": 123}`, // incorrect Array value
|
|
||||||
`{"type": "Hash160","value": "0bcd"}`, // incorrect Uint160 value
|
|
||||||
`{"type": "Hash256","value": "0bcd"}`, // incorrect Uint256 value
|
|
||||||
`{"type": "Stringg","value": ""}`, // incorrect type
|
|
||||||
`{"type": {},"value": ""}`, // incorrect value
|
|
||||||
|
|
||||||
`{"type": "InteropInterface","value": ""}`, // ununmarshable type
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStackParam_UnmarshalJSON(t *testing.T) {
|
|
||||||
var s StackParam
|
|
||||||
for _, tc := range testCases {
|
|
||||||
assert.NoError(t, json.Unmarshal([]byte(tc.input), &s))
|
|
||||||
assert.Equal(t, s, tc.result)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, input := range errorCases {
|
|
||||||
assert.Error(t, json.Unmarshal([]byte(input), &s))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var tryParseTestCases = []struct {
|
|
||||||
input interface{}
|
|
||||||
expected interface{}
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
input: []byte{
|
|
||||||
0x0b, 0xcd, 0x29, 0x78, 0x63, 0x4d, 0x96, 0x1c, 0x24, 0xf5,
|
|
||||||
0xae, 0xa0, 0x80, 0x22, 0x97, 0xff, 0x12, 0x87, 0x24, 0xd6,
|
|
||||||
},
|
|
||||||
expected: util.Uint160{
|
|
||||||
0x0b, 0xcd, 0x29, 0x78, 0x63, 0x4d, 0x96, 0x1c, 0x24, 0xf5,
|
|
||||||
0xae, 0xa0, 0x80, 0x22, 0x97, 0xff, 0x12, 0x87, 0x24, 0xd6,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: []byte{
|
|
||||||
0xf0, 0x37, 0x30, 0x8f, 0xa0, 0xab, 0x18, 0x15,
|
|
||||||
0x5b, 0xcc, 0xfc, 0x08, 0x48, 0x54, 0x68, 0xc1,
|
|
||||||
0x12, 0x40, 0x9e, 0xa5, 0x06, 0x45, 0x95, 0x69,
|
|
||||||
0x9e, 0x98, 0xc5, 0x45, 0xf2, 0x45, 0xf3, 0x2d,
|
|
||||||
},
|
|
||||||
expected: util.Uint256{
|
|
||||||
0x2d, 0xf3, 0x45, 0xf2, 0x45, 0xc5, 0x98, 0x9e,
|
|
||||||
0x69, 0x95, 0x45, 0x06, 0xa5, 0x9e, 0x40, 0x12,
|
|
||||||
0xc1, 0x68, 0x54, 0x48, 0x08, 0xfc, 0xcc, 0x5b,
|
|
||||||
0x15, 0x18, 0xab, 0xa0, 0x8f, 0x30, 0x37, 0xf0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: []byte{0, 1, 2, 3, 4, 9, 8, 6},
|
|
||||||
expected: []byte{0, 1, 2, 3, 4, 9, 8, 6},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: []byte{0x63, 0x78, 0x29, 0xcd, 0x0b},
|
|
||||||
expected: int64(50686687331),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
input: []byte("this is a test string"),
|
|
||||||
expected: "this is a test string",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStackParam_TryParse(t *testing.T) {
|
|
||||||
for _, tc := range tryParseTestCases {
|
|
||||||
t.Run(reflect.TypeOf(tc.expected).String(), func(t *testing.T) {
|
|
||||||
input := StackParam{
|
|
||||||
Type: ByteArray,
|
|
||||||
Value: tc.input,
|
|
||||||
}
|
|
||||||
|
|
||||||
val := reflect.New(reflect.TypeOf(tc.expected))
|
|
||||||
assert.NoError(t, input.TryParse(val.Interface()))
|
|
||||||
assert.Equal(t, tc.expected, val.Elem().Interface())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("[]Uint160", func(t *testing.T) {
|
|
||||||
exp1 := util.Uint160{1, 2, 3, 4, 5}
|
|
||||||
exp2 := util.Uint160{9, 8, 7, 6, 5}
|
|
||||||
|
|
||||||
params := StackParams{
|
|
||||||
{
|
|
||||||
Type: ByteArray,
|
|
||||||
Value: exp1.BytesBE(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Type: ByteArray,
|
|
||||||
Value: exp2.BytesBE(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var out1, out2 util.Uint160
|
|
||||||
|
|
||||||
assert.NoError(t, params.TryParseArray(&out1, &out2))
|
|
||||||
assert.Equal(t, exp1, out1)
|
|
||||||
assert.Equal(t, exp2, out2)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestStackParamType_String(t *testing.T) {
|
|
||||||
types := []StackParamType{
|
|
||||||
Signature,
|
|
||||||
Boolean,
|
|
||||||
Integer,
|
|
||||||
Hash160,
|
|
||||||
Hash256,
|
|
||||||
ByteArray,
|
|
||||||
PublicKey,
|
|
||||||
String,
|
|
||||||
Array,
|
|
||||||
InteropInterface,
|
|
||||||
Void,
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, exp := range types {
|
|
||||||
actual, err := StackParamTypeFromString(exp.String())
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, exp, actual)
|
|
||||||
}
|
|
||||||
|
|
||||||
actual, err := StackParamTypeFromString(Unknown.String())
|
|
||||||
assert.Error(t, err)
|
|
||||||
assert.Equal(t, Unknown, actual)
|
|
||||||
}
|
|
|
@ -148,31 +148,31 @@ func expandArrayIntoScript(script *io.BinWriter, slice []Param) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
switch fp.Type {
|
switch fp.Type {
|
||||||
case ByteArray, Signature:
|
case smartcontract.ByteArrayType, smartcontract.SignatureType:
|
||||||
str, err := fp.Value.GetBytesHex()
|
str, err := fp.Value.GetBytesHex()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
emit.Bytes(script, str)
|
emit.Bytes(script, str)
|
||||||
case String:
|
case smartcontract.StringType:
|
||||||
str, err := fp.Value.GetString()
|
str, err := fp.Value.GetString()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
emit.String(script, str)
|
emit.String(script, str)
|
||||||
case Hash160:
|
case smartcontract.Hash160Type:
|
||||||
hash, err := fp.Value.GetUint160FromHex()
|
hash, err := fp.Value.GetUint160FromHex()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
emit.Bytes(script, hash.BytesBE())
|
emit.Bytes(script, hash.BytesBE())
|
||||||
case Hash256:
|
case smartcontract.Hash256Type:
|
||||||
hash, err := fp.Value.GetUint256()
|
hash, err := fp.Value.GetUint256()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
emit.Bytes(script, hash.BytesBE())
|
emit.Bytes(script, hash.BytesBE())
|
||||||
case PublicKey:
|
case smartcontract.PublicKeyType:
|
||||||
str, err := fp.Value.GetString()
|
str, err := fp.Value.GetString()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -182,13 +182,13 @@ func expandArrayIntoScript(script *io.BinWriter, slice []Param) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
emit.Bytes(script, key.Bytes())
|
emit.Bytes(script, key.Bytes())
|
||||||
case Integer:
|
case smartcontract.IntegerType:
|
||||||
val, err := fp.Value.GetInt()
|
val, err := fp.Value.GetInt()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
emit.Int(script, int64(val))
|
emit.Int(script, int64(val))
|
||||||
case Boolean:
|
case smartcontract.BoolType:
|
||||||
str, err := fp.Value.GetString()
|
str, err := fp.Value.GetString()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/smartcontract"
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
@ -29,31 +30,31 @@ func TestInvocationScriptCreationGood(t *testing.T) {
|
||||||
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{}}},
|
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{}}},
|
||||||
script: "00c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
script: "00c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
||||||
}, {
|
}, {
|
||||||
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: ByteArray, Value: Param{Type: StringT, Value: "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}},
|
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.ByteArrayType, Value: Param{Type: StringT, Value: "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}},
|
||||||
script: "1450befd26fdf6e4d957c11e078b24ebce6291456f51c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
script: "1450befd26fdf6e4d957c11e078b24ebce6291456f51c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
||||||
}, {
|
}, {
|
||||||
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Signature, Value: Param{Type: StringT, Value: "4edf5005771de04619235d5a4c7a9a11bb78e008541f1da7725f654c33380a3c87e2959a025da706d7255cb3a3fa07ebe9c6559d0d9e6213c68049168eb1056f"}}}}}},
|
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.SignatureType, Value: Param{Type: StringT, Value: "4edf5005771de04619235d5a4c7a9a11bb78e008541f1da7725f654c33380a3c87e2959a025da706d7255cb3a3fa07ebe9c6559d0d9e6213c68049168eb1056f"}}}}}},
|
||||||
script: "404edf5005771de04619235d5a4c7a9a11bb78e008541f1da7725f654c33380a3c87e2959a025da706d7255cb3a3fa07ebe9c6559d0d9e6213c68049168eb1056f51c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
script: "404edf5005771de04619235d5a4c7a9a11bb78e008541f1da7725f654c33380a3c87e2959a025da706d7255cb3a3fa07ebe9c6559d0d9e6213c68049168eb1056f51c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
||||||
}, {
|
}, {
|
||||||
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: String, Value: Param{Type: StringT, Value: "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}},
|
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.StringType, Value: Param{Type: StringT, Value: "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}},
|
||||||
script: "283530626566643236666466366534643935376331316530373862323465626365363239313435366651c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
script: "283530626566643236666466366534643935376331316530373862323465626365363239313435366651c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
||||||
}, {
|
}, {
|
||||||
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Hash160, Value: Param{Type: StringT, Value: "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}},
|
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.Hash160Type, Value: Param{Type: StringT, Value: "50befd26fdf6e4d957c11e078b24ebce6291456f"}}}}}},
|
||||||
script: "146f459162ceeb248b071ec157d9e4f6fd26fdbe5051c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
script: "146f459162ceeb248b071ec157d9e4f6fd26fdbe5051c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
||||||
}, {
|
}, {
|
||||||
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Hash256, Value: Param{Type: StringT, Value: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"}}}}}},
|
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.Hash256Type, Value: Param{Type: StringT, Value: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7"}}}}}},
|
||||||
script: "20e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c6051c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
script: "20e72d286979ee6cb1b7e65dfddfb2e384100b8d148e7758de42e4168b71792c6051c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
||||||
}, {
|
}, {
|
||||||
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: PublicKey, Value: Param{Type: StringT, Value: "03c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c1"}}}}}},
|
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.PublicKeyType, Value: Param{Type: StringT, Value: "03c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c1"}}}}}},
|
||||||
script: "2103c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c151c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
script: "2103c089d7122b840a4935234e82e26ae5efd0c2acb627239dc9f207311337b6f2c151c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
||||||
}, {
|
}, {
|
||||||
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Integer, Value: Param{Type: NumberT, Value: 42}}}}}},
|
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.IntegerType, Value: Param{Type: NumberT, Value: 42}}}}}},
|
||||||
script: "012a51c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
script: "012a51c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
||||||
}, {
|
}, {
|
||||||
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Boolean, Value: Param{Type: StringT, Value: "true"}}}}}},
|
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.BoolType, Value: Param{Type: StringT, Value: "true"}}}}}},
|
||||||
script: "5151c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
script: "5151c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
||||||
}, {
|
}, {
|
||||||
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Boolean, Value: Param{Type: StringT, Value: "false"}}}}}},
|
ps: Params{{Type: StringT, Value: "a"}, {Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.BoolType, Value: Param{Type: StringT, Value: "false"}}}}}},
|
||||||
script: "0051c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
script: "0051c10161676f459162ceeb248b071ec157d9e4f6fd26fdbe50",
|
||||||
}}
|
}}
|
||||||
for _, ps := range paramScripts {
|
for _, ps := range paramScripts {
|
||||||
|
@ -70,17 +71,17 @@ func TestInvocationScriptCreationBad(t *testing.T) {
|
||||||
{{Type: NumberT, Value: "qwerty"}},
|
{{Type: NumberT, Value: "qwerty"}},
|
||||||
{{Type: ArrayT, Value: 42}},
|
{{Type: ArrayT, Value: 42}},
|
||||||
{{Type: ArrayT, Value: []Param{{Type: NumberT, Value: 42}}}},
|
{{Type: ArrayT, Value: []Param{{Type: NumberT, Value: 42}}}},
|
||||||
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: ByteArray, Value: Param{Type: StringT, Value: "qwerty"}}}}}},
|
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.ByteArrayType, Value: Param{Type: StringT, Value: "qwerty"}}}}}},
|
||||||
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Signature, Value: Param{Type: StringT, Value: "qwerty"}}}}}},
|
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.SignatureType, Value: Param{Type: StringT, Value: "qwerty"}}}}}},
|
||||||
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: String, Value: Param{Type: NumberT, Value: 42}}}}}},
|
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.StringType, Value: Param{Type: NumberT, Value: 42}}}}}},
|
||||||
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Hash160, Value: Param{Type: StringT, Value: "qwerty"}}}}}},
|
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.Hash160Type, Value: Param{Type: StringT, Value: "qwerty"}}}}}},
|
||||||
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Hash256, Value: Param{Type: StringT, Value: "qwerty"}}}}}},
|
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.Hash256Type, Value: Param{Type: StringT, Value: "qwerty"}}}}}},
|
||||||
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: PublicKey, Value: Param{Type: NumberT, Value: 42}}}}}},
|
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.PublicKeyType, Value: Param{Type: NumberT, Value: 42}}}}}},
|
||||||
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: PublicKey, Value: Param{Type: StringT, Value: "qwerty"}}}}}},
|
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.PublicKeyType, Value: Param{Type: StringT, Value: "qwerty"}}}}}},
|
||||||
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Integer, Value: Param{Type: StringT, Value: "qwerty"}}}}}},
|
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.IntegerType, Value: Param{Type: StringT, Value: "qwerty"}}}}}},
|
||||||
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Boolean, Value: Param{Type: NumberT, Value: 42}}}}}},
|
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.BoolType, Value: Param{Type: NumberT, Value: 42}}}}}},
|
||||||
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Boolean, Value: Param{Type: StringT, Value: "qwerty"}}}}}},
|
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.BoolType, Value: Param{Type: StringT, Value: "qwerty"}}}}}},
|
||||||
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: Unknown, Value: Param{}}}}}},
|
{{Type: ArrayT, Value: []Param{{Type: FuncParamT, Value: FuncParam{Type: smartcontract.UnknownType, Value: Param{}}}}}},
|
||||||
}
|
}
|
||||||
for _, ps := range testParams {
|
for _, ps := range testParams {
|
||||||
_, err := CreateFunctionInvocationScript(contract, ps)
|
_, err := CreateFunctionInvocationScript(contract, ps)
|
||||||
|
|
60
pkg/rpc/response/result/application_log.go
Normal file
60
pkg/rpc/response/result/application_log.go
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
package result
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/core/state"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/smartcontract"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ApplicationLog wrapper used for the representation of the
|
||||||
|
// state.AppExecResult based on the specific tx on the RPC Server.
|
||||||
|
type ApplicationLog struct {
|
||||||
|
TxHash util.Uint256 `json:"txid"`
|
||||||
|
Executions []Execution `json:"executions"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execution response wrapper
|
||||||
|
type Execution struct {
|
||||||
|
Trigger string `json:"trigger"`
|
||||||
|
ScriptHash util.Uint160 `json:"contract"`
|
||||||
|
VMState string `json:"vmstate"`
|
||||||
|
GasConsumed util.Fixed8 `json:"gas_consumed"`
|
||||||
|
Stack json.RawMessage `json:"stack"`
|
||||||
|
Events []NotificationEvent `json:"notifications"`
|
||||||
|
}
|
||||||
|
|
||||||
|
//NotificationEvent response wrapper
|
||||||
|
type NotificationEvent struct {
|
||||||
|
Contract util.Uint160 `json:"contract"`
|
||||||
|
Item smartcontract.Parameter `json:"state"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewApplicationLog creates a new ApplicationLog wrapper.
|
||||||
|
func NewApplicationLog(appExecRes *state.AppExecResult, scriptHash util.Uint160) ApplicationLog {
|
||||||
|
events := make([]NotificationEvent, 0, len(appExecRes.Events))
|
||||||
|
for _, e := range appExecRes.Events {
|
||||||
|
item := e.Item.ToContractParameter()
|
||||||
|
events = append(events, NotificationEvent{
|
||||||
|
Contract: e.ScriptHash,
|
||||||
|
Item: item,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
triggerString := appExecRes.Trigger.String()
|
||||||
|
|
||||||
|
executions := []Execution{{
|
||||||
|
Trigger: triggerString,
|
||||||
|
ScriptHash: scriptHash,
|
||||||
|
VMState: appExecRes.VMState,
|
||||||
|
GasConsumed: appExecRes.GasConsumed,
|
||||||
|
Stack: json.RawMessage(appExecRes.Stack),
|
||||||
|
Events: events,
|
||||||
|
}}
|
||||||
|
|
||||||
|
return ApplicationLog{
|
||||||
|
TxHash: appExecRes.TxHash,
|
||||||
|
Executions: executions,
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,8 +3,8 @@ package response
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/rpc/request"
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/rpc/response/result"
|
"github.com/CityOfZion/neo-go/pkg/rpc/response/result"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/smartcontract"
|
||||||
"github.com/CityOfZion/neo-go/pkg/vm"
|
"github.com/CityOfZion/neo-go/pkg/vm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ type InvokeResult struct {
|
||||||
State vm.State `json:"state"`
|
State vm.State `json:"state"`
|
||||||
GasConsumed string `json:"gas_consumed"`
|
GasConsumed string `json:"gas_consumed"`
|
||||||
Script string `json:"script"`
|
Script string `json:"script"`
|
||||||
Stack []request.StackParam
|
Stack []smartcontract.Parameter
|
||||||
}
|
}
|
||||||
|
|
||||||
// Header is a generic JSON-RPC 2.0 response header (ID and JSON-RPC version).
|
// Header is a generic JSON-RPC 2.0 response header (ID and JSON-RPC version).
|
||||||
|
|
|
@ -4,6 +4,13 @@ import "github.com/prometheus/client_golang/prometheus"
|
||||||
|
|
||||||
// Metrics used in monitoring service.
|
// Metrics used in monitoring service.
|
||||||
var (
|
var (
|
||||||
|
getapplicationlogCalled = prometheus.NewCounter(
|
||||||
|
prometheus.CounterOpts{
|
||||||
|
Help: "Number of calls to getapplicationlog rpc endpoint",
|
||||||
|
Name: "getapplicationlog_called",
|
||||||
|
Namespace: "neogo",
|
||||||
|
},
|
||||||
|
)
|
||||||
getbestblockhashCalled = prometheus.NewCounter(
|
getbestblockhashCalled = prometheus.NewCounter(
|
||||||
prometheus.CounterOpts{
|
prometheus.CounterOpts{
|
||||||
Help: "Number of calls to getbestblockhash rpc endpoint",
|
Help: "Number of calls to getbestblockhash rpc endpoint",
|
||||||
|
@ -143,6 +150,7 @@ var (
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
prometheus.MustRegister(
|
prometheus.MustRegister(
|
||||||
|
getapplicationlogCalled,
|
||||||
getbestblockhashCalled,
|
getbestblockhashCalled,
|
||||||
getbestblockCalled,
|
getbestblockCalled,
|
||||||
getblockcountCalled,
|
getblockcountCalled,
|
||||||
|
|
|
@ -12,6 +12,7 @@ import (
|
||||||
"github.com/CityOfZion/neo-go/pkg/core"
|
"github.com/CityOfZion/neo-go/pkg/core"
|
||||||
"github.com/CityOfZion/neo-go/pkg/core/state"
|
"github.com/CityOfZion/neo-go/pkg/core/state"
|
||||||
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
"github.com/CityOfZion/neo-go/pkg/core/transaction"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
|
||||||
"github.com/CityOfZion/neo-go/pkg/encoding/address"
|
"github.com/CityOfZion/neo-go/pkg/encoding/address"
|
||||||
"github.com/CityOfZion/neo-go/pkg/io"
|
"github.com/CityOfZion/neo-go/pkg/io"
|
||||||
"github.com/CityOfZion/neo-go/pkg/network"
|
"github.com/CityOfZion/neo-go/pkg/network"
|
||||||
|
@ -114,6 +115,10 @@ func (s *Server) methodHandler(w http.ResponseWriter, req *request.In, reqParams
|
||||||
|
|
||||||
Methods:
|
Methods:
|
||||||
switch req.Method {
|
switch req.Method {
|
||||||
|
case "getapplicationlog":
|
||||||
|
getapplicationlogCalled.Inc()
|
||||||
|
results, resultsErr = s.getApplicationLog(reqParams)
|
||||||
|
|
||||||
case "getbestblockhash":
|
case "getbestblockhash":
|
||||||
getbestblockhashCalled.Inc()
|
getbestblockhashCalled.Inc()
|
||||||
results = "0x" + s.chain.CurrentBlockHash().StringLE()
|
results = "0x" + s.chain.CurrentBlockHash().StringLE()
|
||||||
|
@ -284,6 +289,39 @@ Methods:
|
||||||
s.WriteResponse(req, w, results)
|
s.WriteResponse(req, w, results)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getApplicationLog returns the contract log based on the specified txid.
|
||||||
|
func (s *Server) getApplicationLog(reqParams request.Params) (interface{}, error) {
|
||||||
|
param, ok := reqParams.Value(0)
|
||||||
|
if !ok {
|
||||||
|
return nil, response.ErrInvalidParams
|
||||||
|
}
|
||||||
|
|
||||||
|
txHash, err := param.GetUint256()
|
||||||
|
if err != nil {
|
||||||
|
return nil, response.ErrInvalidParams
|
||||||
|
}
|
||||||
|
|
||||||
|
appExecResult, err := s.chain.GetAppExecResult(txHash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, response.NewRPCError("Unknown transaction", "", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
tx, _, err := s.chain.GetTransaction(txHash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, response.NewRPCError("Error while getting transaction", "", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
var scriptHash util.Uint160
|
||||||
|
switch t := tx.Data.(type) {
|
||||||
|
case *transaction.InvocationTX:
|
||||||
|
scriptHash = hash.Hash160(t.Script)
|
||||||
|
default:
|
||||||
|
return nil, response.NewRPCError("Invalid transaction type", "", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.NewApplicationLog(appExecResult, scriptHash), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Server) getStorage(ps request.Params) (interface{}, error) {
|
func (s *Server) getStorage(ps request.Params) (interface{}, error) {
|
||||||
param, ok := ps.Value(0)
|
param, ok := ps.Value(0)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
|
@ -40,6 +40,44 @@ type rpcTestCase struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
var rpcTestCases = map[string][]rpcTestCase{
|
var rpcTestCases = map[string][]rpcTestCase{
|
||||||
|
"getapplicationlog": {
|
||||||
|
{
|
||||||
|
name: "positive",
|
||||||
|
params: `["d5cf936296de912aa4d051531bd8d25c7a58fb68fc7f87c8d3e6e85475187c08"]`,
|
||||||
|
result: func(e *executor) interface{} { return &result.ApplicationLog{} },
|
||||||
|
check: func(t *testing.T, e *executor, acc interface{}) {
|
||||||
|
res, ok := acc.(*result.ApplicationLog)
|
||||||
|
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
expectedTxHash := util.Uint256{0x8, 0x7c, 0x18, 0x75, 0x54, 0xe8, 0xe6, 0xd3, 0xc8, 0x87, 0x7f, 0xfc, 0x68, 0xfb, 0x58, 0x7a, 0x5c, 0xd2, 0xd8, 0x1b, 0x53, 0x51, 0xd0, 0xa4, 0x2a, 0x91, 0xde, 0x96, 0x62, 0x93, 0xcf, 0xd5}
|
||||||
|
assert.Equal(t, expectedTxHash, res.TxHash)
|
||||||
|
assert.Equal(t, 1, len(res.Executions))
|
||||||
|
assert.Equal(t, "Application", res.Executions[0].Trigger)
|
||||||
|
assert.Equal(t, "HALT", res.Executions[0].VMState)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no params",
|
||||||
|
params: `[]`,
|
||||||
|
fail: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid address",
|
||||||
|
params: `["notahash"]`,
|
||||||
|
fail: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid tx hash",
|
||||||
|
params: `["d24cc1d52b5c0216cbf3835bb5bac8ccf32639fa1ab6627ec4e2b9f33f7ec02f"]`,
|
||||||
|
fail: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid tx type",
|
||||||
|
params: `["f9adfde059810f37b3d0686d67f6b29034e0c669537df7e59b40c14a0508b9ed"]`,
|
||||||
|
fail: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
"getaccountstate": {
|
"getaccountstate": {
|
||||||
{
|
{
|
||||||
name: "positive",
|
name: "positive",
|
||||||
|
|
|
@ -1,33 +1,16 @@
|
||||||
package smartcontract
|
package smartcontract
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/binary"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"math/bits"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/encoding/address"
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/io"
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
)
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
// ParamType represents the Type of the contract parameter.
|
|
||||||
type ParamType byte
|
|
||||||
|
|
||||||
// A list of supported smart contract parameter types.
|
|
||||||
const (
|
|
||||||
SignatureType ParamType = iota
|
|
||||||
BoolType
|
|
||||||
IntegerType
|
|
||||||
Hash160Type
|
|
||||||
Hash256Type
|
|
||||||
ByteArrayType
|
|
||||||
PublicKeyType
|
|
||||||
StringType
|
|
||||||
ArrayType
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// PropertyState represents contract properties (flags).
|
// PropertyState represents contract properties (flags).
|
||||||
|
@ -49,62 +32,6 @@ type Parameter struct {
|
||||||
Value interface{} `json:"value"`
|
Value interface{} `json:"value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pt ParamType) String() string {
|
|
||||||
switch pt {
|
|
||||||
case SignatureType:
|
|
||||||
return "Signature"
|
|
||||||
case BoolType:
|
|
||||||
return "Boolean"
|
|
||||||
case IntegerType:
|
|
||||||
return "Integer"
|
|
||||||
case Hash160Type:
|
|
||||||
return "Hash160"
|
|
||||||
case Hash256Type:
|
|
||||||
return "Hash256"
|
|
||||||
case ByteArrayType:
|
|
||||||
return "ByteArray"
|
|
||||||
case PublicKeyType:
|
|
||||||
return "PublicKey"
|
|
||||||
case StringType:
|
|
||||||
return "String"
|
|
||||||
case ArrayType:
|
|
||||||
return "Array"
|
|
||||||
default:
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalJSON implements the json.Marshaler interface.
|
|
||||||
func (pt ParamType) MarshalJSON() ([]byte, error) {
|
|
||||||
return []byte(`"` + pt.String() + `"`), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON implements json.Unmarshaler interface.
|
|
||||||
func (pt *ParamType) UnmarshalJSON(data []byte) error {
|
|
||||||
var s string
|
|
||||||
if err := json.Unmarshal(data, &s); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
p, err := parseParamType(s)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
*pt = p
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodeBinary implements io.Serializable interface.
|
|
||||||
func (pt ParamType) EncodeBinary(w *io.BinWriter) {
|
|
||||||
w.WriteB(byte(pt))
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecodeBinary implements io.Serializable interface.
|
|
||||||
func (pt *ParamType) DecodeBinary(r *io.BinReader) {
|
|
||||||
*pt = ParamType(r.ReadB())
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewParameter returns a Parameter with proper initialized Value
|
// NewParameter returns a Parameter with proper initialized Value
|
||||||
// of the given ParamType.
|
// of the given ParamType.
|
||||||
func NewParameter(t ParamType) Parameter {
|
func NewParameter(t ParamType) Parameter {
|
||||||
|
@ -114,149 +41,278 @@ func NewParameter(t ParamType) Parameter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseParamType is a user-friendly string to ParamType converter, it's
|
type rawParameter struct {
|
||||||
// case-insensitive and makes the following conversions:
|
Type ParamType `json:"type"`
|
||||||
// signature -> SignatureType
|
Value json.RawMessage `json:"value"`
|
||||||
// bool -> BoolType
|
|
||||||
// int -> IntegerType
|
|
||||||
// hash160 -> Hash160Type
|
|
||||||
// hash256 -> Hash256Type
|
|
||||||
// bytes -> ByteArrayType
|
|
||||||
// key -> PublicKeyType
|
|
||||||
// string -> StringType
|
|
||||||
// anything else generates an error.
|
|
||||||
func parseParamType(typ string) (ParamType, error) {
|
|
||||||
switch strings.ToLower(typ) {
|
|
||||||
case "signature":
|
|
||||||
return SignatureType, nil
|
|
||||||
case "bool":
|
|
||||||
return BoolType, nil
|
|
||||||
case "int":
|
|
||||||
return IntegerType, nil
|
|
||||||
case "hash160":
|
|
||||||
return Hash160Type, nil
|
|
||||||
case "hash256":
|
|
||||||
return Hash256Type, nil
|
|
||||||
case "bytes", "bytearray":
|
|
||||||
return ByteArrayType, nil
|
|
||||||
case "key":
|
|
||||||
return PublicKeyType, nil
|
|
||||||
case "string":
|
|
||||||
return StringType, nil
|
|
||||||
default:
|
|
||||||
// We deliberately don't support array here.
|
|
||||||
return 0, errors.New("wrong or unsupported parameter type")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// adjustValToType is a value type-checker and converter.
|
type keyValuePair struct {
|
||||||
func adjustValToType(typ ParamType, val string) (interface{}, error) {
|
Key rawParameter `json:"key"`
|
||||||
switch typ {
|
Value rawParameter `json:"value"`
|
||||||
case SignatureType:
|
}
|
||||||
b, err := hex.DecodeString(val)
|
|
||||||
if err != nil {
|
type rawKeyValuePair struct {
|
||||||
return nil, err
|
Key json.RawMessage `json:"key"`
|
||||||
|
Value json.RawMessage `json:"value"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements Marshaler interface.
|
||||||
|
func (p *Parameter) MarshalJSON() ([]byte, error) {
|
||||||
|
var (
|
||||||
|
resultRawValue json.RawMessage
|
||||||
|
resultErr error
|
||||||
|
)
|
||||||
|
switch p.Type {
|
||||||
|
case BoolType, IntegerType, StringType, Hash256Type, Hash160Type:
|
||||||
|
resultRawValue, resultErr = json.Marshal(p.Value)
|
||||||
|
case PublicKeyType, ByteArrayType, SignatureType:
|
||||||
|
resultRawValue, resultErr = json.Marshal(hex.EncodeToString(p.Value.([]byte)))
|
||||||
|
case ArrayType:
|
||||||
|
var value = make([]rawParameter, 0)
|
||||||
|
for _, parameter := range p.Value.([]Parameter) {
|
||||||
|
rawValue, err := json.Marshal(parameter.Value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
value = append(value, rawParameter{
|
||||||
|
Type: parameter.Type,
|
||||||
|
Value: rawValue,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
if len(b) != 64 {
|
resultRawValue, resultErr = json.Marshal(value)
|
||||||
return nil, errors.New("not a signature")
|
case MapType:
|
||||||
|
var value []keyValuePair
|
||||||
|
for key, val := range p.Value.(map[Parameter]Parameter) {
|
||||||
|
rawKey, err := json.Marshal(key.Value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rawValue, err := json.Marshal(val.Value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
value = append(value, keyValuePair{
|
||||||
|
Key: rawParameter{
|
||||||
|
Type: key.Type,
|
||||||
|
Value: rawKey,
|
||||||
|
},
|
||||||
|
Value: rawParameter{
|
||||||
|
Type: val.Type,
|
||||||
|
Value: rawValue,
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return val, nil
|
resultRawValue, resultErr = json.Marshal(value)
|
||||||
|
default:
|
||||||
|
resultErr = errors.Errorf("Marshaller for type %s not implemented", p.Type)
|
||||||
|
}
|
||||||
|
if resultErr != nil {
|
||||||
|
return nil, resultErr
|
||||||
|
}
|
||||||
|
return json.Marshal(rawParameter{
|
||||||
|
Type: p.Type,
|
||||||
|
Value: resultRawValue,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements Unmarshaler interface.
|
||||||
|
func (p *Parameter) UnmarshalJSON(data []byte) (err error) {
|
||||||
|
var (
|
||||||
|
r rawParameter
|
||||||
|
i int64
|
||||||
|
s string
|
||||||
|
b []byte
|
||||||
|
boolean bool
|
||||||
|
)
|
||||||
|
if err = json.Unmarshal(data, &r); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch p.Type = r.Type; r.Type {
|
||||||
case BoolType:
|
case BoolType:
|
||||||
switch val {
|
if err = json.Unmarshal(r.Value, &boolean); err != nil {
|
||||||
case "true":
|
return
|
||||||
return true, nil
|
|
||||||
case "false":
|
|
||||||
return false, nil
|
|
||||||
default:
|
|
||||||
return nil, errors.New("invalid boolean value")
|
|
||||||
}
|
}
|
||||||
case IntegerType:
|
p.Value = boolean
|
||||||
return strconv.Atoi(val)
|
case ByteArrayType, PublicKeyType:
|
||||||
case Hash160Type:
|
if err = json.Unmarshal(r.Value, &s); err != nil {
|
||||||
u, err := address.StringToUint160(val)
|
return
|
||||||
if err == nil {
|
|
||||||
return hex.EncodeToString(u.BytesBE()), nil
|
|
||||||
}
|
}
|
||||||
b, err := hex.DecodeString(val)
|
if b, err = hex.DecodeString(s); err != nil {
|
||||||
if err != nil {
|
return
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
if len(b) != 20 {
|
p.Value = b
|
||||||
return nil, errors.New("not a hash160")
|
|
||||||
}
|
|
||||||
return val, nil
|
|
||||||
case Hash256Type:
|
|
||||||
b, err := hex.DecodeString(val)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if len(b) != 32 {
|
|
||||||
return nil, errors.New("not a hash256")
|
|
||||||
}
|
|
||||||
return val, nil
|
|
||||||
case ByteArrayType:
|
|
||||||
_, err := hex.DecodeString(val)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return val, nil
|
|
||||||
case PublicKeyType:
|
|
||||||
_, err := keys.NewPublicKeyFromString(val)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return val, nil
|
|
||||||
case StringType:
|
case StringType:
|
||||||
return val, nil
|
if err = json.Unmarshal(r.Value, &s); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.Value = s
|
||||||
|
case IntegerType:
|
||||||
|
if err = json.Unmarshal(r.Value, &i); err == nil {
|
||||||
|
p.Value = i
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// sometimes integer comes as string
|
||||||
|
if err = json.Unmarshal(r.Value, &s); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if i, err = strconv.ParseInt(s, 10, 64); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.Value = i
|
||||||
|
case ArrayType:
|
||||||
|
// https://github.com/neo-project/neo/blob/3d59ecca5a8deb057bdad94b3028a6d5e25ac088/neo/Network/RPC/RpcServer.cs#L67
|
||||||
|
var rs []Parameter
|
||||||
|
if err = json.Unmarshal(r.Value, &rs); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.Value = rs
|
||||||
|
case MapType:
|
||||||
|
var rawMap []rawKeyValuePair
|
||||||
|
if err = json.Unmarshal(r.Value, &rawMap); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rs := make(map[Parameter]Parameter)
|
||||||
|
for _, p := range rawMap {
|
||||||
|
var key, value Parameter
|
||||||
|
if err = json.Unmarshal(p.Key, &key); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err = json.Unmarshal(p.Value, &value); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
rs[key] = value
|
||||||
|
}
|
||||||
|
p.Value = rs
|
||||||
|
case Hash160Type:
|
||||||
|
var h util.Uint160
|
||||||
|
if err = json.Unmarshal(r.Value, &h); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.Value = h
|
||||||
|
case Hash256Type:
|
||||||
|
var h util.Uint256
|
||||||
|
if err = json.Unmarshal(r.Value, &h); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p.Value = h
|
||||||
default:
|
default:
|
||||||
return nil, errors.New("unsupported parameter type")
|
return errors.Errorf("Unmarshaller for type %s not implemented", p.Type)
|
||||||
}
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// inferParamType tries to infer the value type from its contents. It returns
|
// Params is an array of Parameter (TODO: drop it?).
|
||||||
// IntegerType for anything that looks like decimal integer (can be converted
|
type Params []Parameter
|
||||||
// with strconv.Atoi), BoolType for true and false values, Hash160Type for
|
|
||||||
// addresses and hex strings encoding 20 bytes long values, PublicKeyType for
|
|
||||||
// valid hex-encoded public keys, Hash256Type for hex-encoded 32 bytes values,
|
|
||||||
// SignatureType for hex-encoded 64 bytes values, ByteArrayType for any other
|
|
||||||
// valid hex-encoded values and StringType for anything else.
|
|
||||||
func inferParamType(val string) ParamType {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
_, err = strconv.Atoi(val)
|
// TryParseArray converts an array of Parameter into an array of more appropriate things.
|
||||||
if err == nil {
|
func (p Params) TryParseArray(vals ...interface{}) error {
|
||||||
return IntegerType
|
var (
|
||||||
|
err error
|
||||||
|
i int
|
||||||
|
par Parameter
|
||||||
|
)
|
||||||
|
if len(p) != len(vals) {
|
||||||
|
return errors.New("receiver array doesn't fit the Params length")
|
||||||
}
|
}
|
||||||
|
for i, par = range p {
|
||||||
if val == "true" || val == "false" {
|
if err = par.TryParse(vals[i]); err != nil {
|
||||||
return BoolType
|
return err
|
||||||
}
|
|
||||||
|
|
||||||
_, err = address.StringToUint160(val)
|
|
||||||
if err == nil {
|
|
||||||
return Hash160Type
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = keys.NewPublicKeyFromString(val)
|
|
||||||
if err == nil {
|
|
||||||
return PublicKeyType
|
|
||||||
}
|
|
||||||
|
|
||||||
unhexed, err := hex.DecodeString(val)
|
|
||||||
if err == nil {
|
|
||||||
switch len(unhexed) {
|
|
||||||
case 20:
|
|
||||||
return Hash160Type
|
|
||||||
case 32:
|
|
||||||
return Hash256Type
|
|
||||||
case 64:
|
|
||||||
return SignatureType
|
|
||||||
default:
|
|
||||||
return ByteArrayType
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Anything can be a string.
|
return nil
|
||||||
return StringType
|
}
|
||||||
|
|
||||||
|
// TryParse converts one Parameter into something more appropriate.
|
||||||
|
func (p Parameter) TryParse(dest interface{}) error {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
ok bool
|
||||||
|
data []byte
|
||||||
|
)
|
||||||
|
switch p.Type {
|
||||||
|
case ByteArrayType:
|
||||||
|
if data, ok = p.Value.([]byte); !ok {
|
||||||
|
return errors.Errorf("failed to cast %s to []byte", p.Value)
|
||||||
|
}
|
||||||
|
switch dest := dest.(type) {
|
||||||
|
case *util.Uint160:
|
||||||
|
if *dest, err = util.Uint160DecodeBytesBE(data); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
case *[]byte:
|
||||||
|
*dest = data
|
||||||
|
return nil
|
||||||
|
case *util.Uint256:
|
||||||
|
if *dest, err = util.Uint256DecodeBytesLE(data); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
case *int64, *int32, *int16, *int8, *int, *uint64, *uint32, *uint16, *uint8, *uint:
|
||||||
|
var size int
|
||||||
|
switch dest.(type) {
|
||||||
|
case *int64, *uint64:
|
||||||
|
size = 64
|
||||||
|
case *int32, *uint32:
|
||||||
|
size = 32
|
||||||
|
case *int16, *uint16:
|
||||||
|
size = 16
|
||||||
|
case *int8, *uint8:
|
||||||
|
size = 8
|
||||||
|
case *int, *uint:
|
||||||
|
size = bits.UintSize
|
||||||
|
}
|
||||||
|
|
||||||
|
i, err := bytesToUint64(data, size)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch dest := dest.(type) {
|
||||||
|
case *int64:
|
||||||
|
*dest = int64(i)
|
||||||
|
case *int32:
|
||||||
|
*dest = int32(i)
|
||||||
|
case *int16:
|
||||||
|
*dest = int16(i)
|
||||||
|
case *int8:
|
||||||
|
*dest = int8(i)
|
||||||
|
case *int:
|
||||||
|
*dest = int(i)
|
||||||
|
case *uint64:
|
||||||
|
*dest = i
|
||||||
|
case *uint32:
|
||||||
|
*dest = uint32(i)
|
||||||
|
case *uint16:
|
||||||
|
*dest = uint16(i)
|
||||||
|
case *uint8:
|
||||||
|
*dest = uint8(i)
|
||||||
|
case *uint:
|
||||||
|
*dest = uint(i)
|
||||||
|
}
|
||||||
|
case *string:
|
||||||
|
*dest = string(data)
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return errors.Errorf("cannot cast param of type %s to type %s", p.Type, dest)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return errors.New("cannot define param type")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func bytesToUint64(b []byte, size int) (uint64, error) {
|
||||||
|
var length = size / 8
|
||||||
|
if len(b) > length {
|
||||||
|
return 0, errors.Errorf("input doesn't fit into %d bits", size)
|
||||||
|
}
|
||||||
|
if len(b) < length {
|
||||||
|
data := make([]byte, length)
|
||||||
|
copy(data, b)
|
||||||
|
return binary.LittleEndian.Uint64(data), nil
|
||||||
|
}
|
||||||
|
return binary.LittleEndian.Uint64(b), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewParameterFromString returns a new Parameter initialized from the given
|
// NewParameterFromString returns a new Parameter initialized from the given
|
||||||
|
@ -282,10 +338,14 @@ func NewParameterFromString(in string) (*Parameter, error) {
|
||||||
}
|
}
|
||||||
if char == ':' && !escaped && !hadType {
|
if char == ':' && !escaped && !hadType {
|
||||||
typStr := buf.String()
|
typStr := buf.String()
|
||||||
res.Type, err = parseParamType(typStr)
|
res.Type, err = ParseParamType(typStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
// We currently do not support following types:
|
||||||
|
if res.Type == ArrayType || res.Type == MapType || res.Type == InteropInterfaceType || res.Type == VoidType {
|
||||||
|
return nil, errors.Errorf("Unsupported contract parameter type: %s", res.Type)
|
||||||
|
}
|
||||||
buf.Reset()
|
buf.Reset()
|
||||||
hadType = true
|
hadType = true
|
||||||
continue
|
continue
|
||||||
|
@ -309,20 +369,4 @@ func NewParameterFromString(in string) (*Parameter, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContextItem represents a transaction context item.
|
|
||||||
type ContextItem struct {
|
|
||||||
Script util.Uint160
|
|
||||||
Parameters []Parameter
|
|
||||||
Signatures []Signature
|
|
||||||
}
|
|
||||||
|
|
||||||
// Signature represents a transaction signature.
|
|
||||||
type Signature struct {
|
|
||||||
Data []byte
|
|
||||||
PublicKey []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParameterContext holds the parameter context.
|
|
||||||
type ParameterContext struct{}
|
|
|
@ -1,283 +1,357 @@
|
||||||
package smartcontract
|
package smartcontract
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"math"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParseParamType(t *testing.T) {
|
var marshalJSONTestCases = []struct {
|
||||||
var inouts = []struct {
|
input Parameter
|
||||||
in string
|
result string
|
||||||
out ParamType
|
}{
|
||||||
err bool
|
{
|
||||||
}{{
|
input: Parameter{Type: IntegerType, Value: int64(12345)},
|
||||||
in: "signature",
|
result: `{"type":"Integer","value":12345}`,
|
||||||
out: SignatureType,
|
},
|
||||||
}, {
|
{
|
||||||
in: "Signature",
|
input: Parameter{Type: StringType, Value: "Some string"},
|
||||||
out: SignatureType,
|
result: `{"type":"String","value":"Some string"}`,
|
||||||
}, {
|
},
|
||||||
in: "SiGnAtUrE",
|
{
|
||||||
out: SignatureType,
|
input: Parameter{Type: BoolType, Value: true},
|
||||||
}, {
|
result: `{"type":"Boolean","value":true}`,
|
||||||
in: "bool",
|
},
|
||||||
out: BoolType,
|
{
|
||||||
}, {
|
input: Parameter{Type: ByteArrayType, Value: []byte{0x01, 0x02, 0x03}},
|
||||||
in: "int",
|
result: `{"type":"ByteArray","value":"010203"}`,
|
||||||
out: IntegerType,
|
},
|
||||||
}, {
|
{
|
||||||
in: "hash160",
|
input: Parameter{
|
||||||
out: Hash160Type,
|
Type: PublicKeyType,
|
||||||
}, {
|
Value: []byte{0x03, 0xb3, 0xbf, 0x15, 0x02, 0xfb, 0xdc, 0x05, 0x44, 0x9b, 0x50, 0x6a, 0xaf, 0x04, 0x57, 0x97, 0x24, 0x02, 0x4b, 0x06, 0x54, 0x2e, 0x49, 0x26, 0x2b, 0xfa, 0xa3, 0xf7, 0x0e, 0x20, 0x00, 0x40, 0xa9},
|
||||||
in: "hash256",
|
},
|
||||||
out: Hash256Type,
|
result: `{"type":"PublicKey","value":"03b3bf1502fbdc05449b506aaf04579724024b06542e49262bfaa3f70e200040a9"}`,
|
||||||
}, {
|
},
|
||||||
in: "bytes",
|
{
|
||||||
out: ByteArrayType,
|
input: Parameter{
|
||||||
}, {
|
Type: ArrayType,
|
||||||
in: "key",
|
Value: []Parameter{
|
||||||
out: PublicKeyType,
|
{Type: StringType, Value: "str 1"},
|
||||||
}, {
|
{Type: IntegerType, Value: int64(2)},
|
||||||
in: "string",
|
},
|
||||||
out: StringType,
|
},
|
||||||
}, {
|
result: `{"type":"Array","value":[{"type":"String","value":"str 1"},{"type":"Integer","value":2}]}`,
|
||||||
in: "array",
|
},
|
||||||
err: true,
|
{
|
||||||
}, {
|
input: Parameter{
|
||||||
in: "qwerty",
|
Type: MapType,
|
||||||
err: true,
|
Value: map[Parameter]Parameter{
|
||||||
}}
|
{Type: StringType, Value: "key1"}: {Type: IntegerType, Value: 1},
|
||||||
for _, inout := range inouts {
|
{Type: StringType, Value: "key2"}: {Type: StringType, Value: "two"},
|
||||||
out, err := parseParamType(inout.in)
|
},
|
||||||
if inout.err {
|
},
|
||||||
assert.NotNil(t, err, "should error on '%s' input", inout.in)
|
result: `{"type":"Map","value":[{"key":{"type":"String","value":"key1"},"value":{"type":"Integer","value":1}},{"key":{"type":"String","value":"key2"},"value":{"type":"String","value":"two"}}]}`,
|
||||||
} else {
|
},
|
||||||
assert.Nil(t, err, "shouldn't error on '%s' input", inout.in)
|
{
|
||||||
assert.Equal(t, inout.out, out, "bad output for '%s' input", inout.in)
|
input: Parameter{
|
||||||
}
|
Type: MapType,
|
||||||
|
Value: map[Parameter]Parameter{
|
||||||
|
{Type: StringType, Value: "key1"}: {Type: ArrayType, Value: []Parameter{
|
||||||
|
{Type: StringType, Value: "str 1"},
|
||||||
|
{Type: IntegerType, Value: int64(2)},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
result: `{"type":"Map","value":[{"key":{"type":"String","value":"key1"},"value":{"type":"Array","value":[{"type":"String","value":"str 1"},{"type":"Integer","value":2}]}}]}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: Parameter{
|
||||||
|
Type: Hash160Type,
|
||||||
|
Value: util.Uint160{
|
||||||
|
0x0b, 0xcd, 0x29, 0x78, 0x63, 0x4d, 0x96, 0x1c, 0x24, 0xf5,
|
||||||
|
0xae, 0xa0, 0x80, 0x22, 0x97, 0xff, 0x12, 0x87, 0x24, 0xd6,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
result: `{"type":"Hash160","value":"0x0bcd2978634d961c24f5aea0802297ff128724d6"}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: Parameter{
|
||||||
|
Type: Hash256Type,
|
||||||
|
Value: util.Uint256{
|
||||||
|
0x2d, 0xf3, 0x45, 0xf2, 0x45, 0xc5, 0x98, 0x9e,
|
||||||
|
0x69, 0x95, 0x45, 0x06, 0xa5, 0x9e, 0x40, 0x12,
|
||||||
|
0xc1, 0x68, 0x54, 0x48, 0x08, 0xfc, 0xcc, 0x5b,
|
||||||
|
0x15, 0x18, 0xab, 0xa0, 0x8f, 0x30, 0x37, 0xf0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
result: `{"type":"Hash256","value":"0xf037308fa0ab18155bccfc08485468c112409ea5064595699e98c545f245f32d"}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var marshalJSONErrorCases = []Parameter{
|
||||||
|
{
|
||||||
|
Type: UnknownType,
|
||||||
|
Value: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: InteropInterfaceType,
|
||||||
|
Value: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: IntegerType,
|
||||||
|
Value: math.Inf(1),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParam_MarshalJSON(t *testing.T) {
|
||||||
|
for _, tc := range marshalJSONTestCases {
|
||||||
|
res, err := json.Marshal(&tc.input)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
var actual, expected Parameter
|
||||||
|
assert.NoError(t, json.Unmarshal(res, &actual))
|
||||||
|
assert.NoError(t, json.Unmarshal([]byte(tc.result), &expected))
|
||||||
|
|
||||||
|
assert.Equal(t, expected, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, input := range marshalJSONErrorCases {
|
||||||
|
_, err := json.Marshal(&input)
|
||||||
|
assert.Error(t, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInferParamType(t *testing.T) {
|
var unmarshalJSONTestCases = []struct {
|
||||||
var inouts = []struct {
|
input string
|
||||||
in string
|
result Parameter
|
||||||
out ParamType
|
}{
|
||||||
}{{
|
{
|
||||||
in: "42",
|
input: `{"type":"Bool","value":true}`,
|
||||||
out: IntegerType,
|
result: Parameter{Type: BoolType, Value: true},
|
||||||
}, {
|
},
|
||||||
in: "-42",
|
{
|
||||||
out: IntegerType,
|
input: `{"type":"Integer","value":12345}`,
|
||||||
}, {
|
result: Parameter{Type: IntegerType, Value: int64(12345)},
|
||||||
in: "0",
|
},
|
||||||
out: IntegerType,
|
{
|
||||||
}, {
|
input: `{"type":"Integer","value":"12345"}`,
|
||||||
in: "2e10",
|
result: Parameter{Type: IntegerType, Value: int64(12345)},
|
||||||
out: ByteArrayType,
|
},
|
||||||
}, {
|
{
|
||||||
in: "true",
|
input: `{"type":"ByteArray","value":"010203"}`,
|
||||||
out: BoolType,
|
result: Parameter{Type: ByteArrayType, Value: []byte{0x01, 0x02, 0x03}},
|
||||||
}, {
|
},
|
||||||
in: "false",
|
{
|
||||||
out: BoolType,
|
input: `{"type":"String","value":"Some string"}`,
|
||||||
}, {
|
result: Parameter{Type: StringType, Value: "Some string"},
|
||||||
in: "truee",
|
},
|
||||||
out: StringType,
|
{
|
||||||
}, {
|
input: `{"type":"Array","value":[
|
||||||
in: "AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y",
|
{"type": "String", "value": "str 1"},
|
||||||
out: Hash160Type,
|
{"type": "Integer", "value": 2}]}`,
|
||||||
}, {
|
result: Parameter{
|
||||||
in: "ZK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y",
|
Type: ArrayType,
|
||||||
out: StringType,
|
Value: []Parameter{
|
||||||
}, {
|
{Type: StringType, Value: "str 1"},
|
||||||
in: "50befd26fdf6e4d957c11e078b24ebce6291456f",
|
{Type: IntegerType, Value: int64(2)},
|
||||||
out: Hash160Type,
|
},
|
||||||
}, {
|
},
|
||||||
in: "03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c",
|
},
|
||||||
out: PublicKeyType,
|
{
|
||||||
}, {
|
input: `{"type": "Hash160", "value": "0bcd2978634d961c24f5aea0802297ff128724d6"}`,
|
||||||
in: "30b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c",
|
result: Parameter{
|
||||||
out: ByteArrayType,
|
Type: Hash160Type,
|
||||||
}, {
|
Value: util.Uint160{
|
||||||
in: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7",
|
0x0b, 0xcd, 0x29, 0x78, 0x63, 0x4d, 0x96, 0x1c, 0x24, 0xf5,
|
||||||
out: Hash256Type,
|
0xae, 0xa0, 0x80, 0x22, 0x97, 0xff, 0x12, 0x87, 0x24, 0xd6,
|
||||||
}, {
|
},
|
||||||
in: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7da",
|
},
|
||||||
out: ByteArrayType,
|
},
|
||||||
}, {
|
{
|
||||||
in: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b",
|
input: `{"type": "Hash256", "value": "f037308fa0ab18155bccfc08485468c112409ea5064595699e98c545f245f32d"}`,
|
||||||
out: SignatureType,
|
result: Parameter{
|
||||||
}, {
|
Type: Hash256Type,
|
||||||
in: "qwerty",
|
Value: util.Uint256{
|
||||||
out: StringType,
|
0x2d, 0xf3, 0x45, 0xf2, 0x45, 0xc5, 0x98, 0x9e,
|
||||||
}, {
|
0x69, 0x95, 0x45, 0x06, 0xa5, 0x9e, 0x40, 0x12,
|
||||||
in: "ab",
|
0xc1, 0x68, 0x54, 0x48, 0x08, 0xfc, 0xcc, 0x5b,
|
||||||
out: ByteArrayType,
|
0x15, 0x18, 0xab, 0xa0, 0x8f, 0x30, 0x37, 0xf0,
|
||||||
}, {
|
},
|
||||||
in: "az",
|
},
|
||||||
out: StringType,
|
},
|
||||||
}, {
|
{
|
||||||
in: "bad",
|
input: `{"type":"Map","value":[{"key":{"type":"String","value":"key1"},"value":{"type":"Integer","value":1}},{"key":{"type":"String","value":"key2"},"value":{"type":"String","value":"two"}}]}`,
|
||||||
out: StringType,
|
result: Parameter{
|
||||||
}, {
|
Type: MapType,
|
||||||
in: "фыва",
|
Value: map[Parameter]Parameter{
|
||||||
out: StringType,
|
{Type: StringType, Value: "key1"}: {Type: IntegerType, Value: int64(1)},
|
||||||
}, {
|
{Type: StringType, Value: "key2"}: {Type: StringType, Value: "two"},
|
||||||
in: "dead",
|
},
|
||||||
out: ByteArrayType,
|
},
|
||||||
}}
|
},
|
||||||
for _, inout := range inouts {
|
{
|
||||||
out := inferParamType(inout.in)
|
input: `{"type":"Map","value":[{"key":{"type":"String","value":"key1"},"value":{"type":"Array","value":[{"type":"String","value":"str 1"},{"type":"Integer","value":2}]}}]}`,
|
||||||
assert.Equal(t, inout.out, out, "bad output for '%s' input", inout.in)
|
result: Parameter{
|
||||||
|
Type: MapType,
|
||||||
|
Value: map[Parameter]Parameter{
|
||||||
|
{Type: StringType, Value: "key1"}: {Type: ArrayType, Value: []Parameter{
|
||||||
|
{Type: StringType, Value: "str 1"},
|
||||||
|
{Type: IntegerType, Value: int64(2)},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
result: Parameter{
|
||||||
|
Type: PublicKeyType,
|
||||||
|
Value: []byte{0x03, 0xb3, 0xbf, 0x15, 0x02, 0xfb, 0xdc, 0x05, 0x44, 0x9b, 0x50, 0x6a, 0xaf, 0x04, 0x57, 0x97, 0x24, 0x02, 0x4b, 0x06, 0x54, 0x2e, 0x49, 0x26, 0x2b, 0xfa, 0xa3, 0xf7, 0x0e, 0x20, 0x00, 0x40, 0xa9},
|
||||||
|
},
|
||||||
|
input: `{"type":"PublicKey","value":"03b3bf1502fbdc05449b506aaf04579724024b06542e49262bfaa3f70e200040a9"}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var unmarshalJSONErrorCases = []string{
|
||||||
|
`{"type": "ByteArray","value":`, // incorrect JSON
|
||||||
|
`{"type": "ByteArray","value":1}`, // incorrect Value
|
||||||
|
`{"type": "ByteArray","value":"12zz"}`, // incorrect ByteArray value
|
||||||
|
`{"type": "String","value":`, // incorrect JSON
|
||||||
|
`{"type": "String","value":1}`, // incorrect Value
|
||||||
|
`{"type": "Integer","value": "nn"}`, // incorrect Integer value
|
||||||
|
`{"type": "Integer","value": []}`, // incorrect Integer value
|
||||||
|
`{"type": "Array","value": 123}`, // incorrect Array value
|
||||||
|
`{"type": "Hash160","value": "0bcd"}`, // incorrect Uint160 value
|
||||||
|
`{"type": "Hash256","value": "0bcd"}`, // incorrect Uint256 value
|
||||||
|
`{"type": "Stringg","value": ""}`, // incorrect type
|
||||||
|
`{"type": {},"value": ""}`, // incorrect value
|
||||||
|
`{"type": "Boolean","value": qwerty}`, // incorrect Bool value
|
||||||
|
`{"type": "Boolean","value": ""}`, // incorrect Bool value
|
||||||
|
`{"type": "Map","value": ["key": {}]}`, // incorrect Map value
|
||||||
|
`{"type": "Map","value": ["key": {"type":"String", "value":"qwer"}, "value": {"type":"Boolean"}]}`, // incorrect Map Value value
|
||||||
|
`{"type": "Map","value": ["key": {"type":"String"}, "value": {"type":"Boolean", "value":true}]}`, // incorrect Map Key value
|
||||||
|
|
||||||
|
`{"type": "InteropInterface","value": ""}`, // ununmarshable type
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParam_UnmarshalJSON(t *testing.T) {
|
||||||
|
var s Parameter
|
||||||
|
for _, tc := range unmarshalJSONTestCases {
|
||||||
|
assert.NoError(t, json.Unmarshal([]byte(tc.input), &s))
|
||||||
|
assert.Equal(t, s, tc.result)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, input := range unmarshalJSONErrorCases {
|
||||||
|
assert.Error(t, json.Unmarshal([]byte(input), &s))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAdjustValToType(t *testing.T) {
|
var tryParseTestCases = []struct {
|
||||||
var inouts = []struct {
|
input interface{}
|
||||||
typ ParamType
|
expected interface{}
|
||||||
val string
|
}{
|
||||||
out interface{}
|
{
|
||||||
err bool
|
input: []byte{
|
||||||
}{{
|
0x0b, 0xcd, 0x29, 0x78, 0x63, 0x4d, 0x96, 0x1c, 0x24, 0xf5,
|
||||||
typ: SignatureType,
|
0xae, 0xa0, 0x80, 0x22, 0x97, 0xff, 0x12, 0x87, 0x24, 0xd6,
|
||||||
val: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b",
|
},
|
||||||
out: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b",
|
expected: util.Uint160{
|
||||||
}, {
|
0x0b, 0xcd, 0x29, 0x78, 0x63, 0x4d, 0x96, 0x1c, 0x24, 0xf5,
|
||||||
typ: SignatureType,
|
0xae, 0xa0, 0x80, 0x22, 0x97, 0xff, 0x12, 0x87, 0x24, 0xd6,
|
||||||
val: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c",
|
},
|
||||||
err: true,
|
},
|
||||||
}, {
|
{
|
||||||
typ: SignatureType,
|
input: []byte{
|
||||||
val: "qwerty",
|
0xf0, 0x37, 0x30, 0x8f, 0xa0, 0xab, 0x18, 0x15,
|
||||||
err: true,
|
0x5b, 0xcc, 0xfc, 0x08, 0x48, 0x54, 0x68, 0xc1,
|
||||||
}, {
|
0x12, 0x40, 0x9e, 0xa5, 0x06, 0x45, 0x95, 0x69,
|
||||||
typ: BoolType,
|
0x9e, 0x98, 0xc5, 0x45, 0xf2, 0x45, 0xf3, 0x2d,
|
||||||
val: "false",
|
},
|
||||||
out: false,
|
expected: util.Uint256{
|
||||||
}, {
|
0x2d, 0xf3, 0x45, 0xf2, 0x45, 0xc5, 0x98, 0x9e,
|
||||||
typ: BoolType,
|
0x69, 0x95, 0x45, 0x06, 0xa5, 0x9e, 0x40, 0x12,
|
||||||
val: "true",
|
0xc1, 0x68, 0x54, 0x48, 0x08, 0xfc, 0xcc, 0x5b,
|
||||||
out: true,
|
0x15, 0x18, 0xab, 0xa0, 0x8f, 0x30, 0x37, 0xf0,
|
||||||
}, {
|
},
|
||||||
typ: BoolType,
|
},
|
||||||
val: "qwerty",
|
{
|
||||||
err: true,
|
input: []byte{0, 1, 2, 3, 4, 9, 8, 6},
|
||||||
}, {
|
expected: []byte{0, 1, 2, 3, 4, 9, 8, 6},
|
||||||
typ: BoolType,
|
},
|
||||||
val: "42",
|
{
|
||||||
err: true,
|
input: []byte{0x63, 0x78, 0x29, 0xcd, 0x0b},
|
||||||
}, {
|
expected: int64(50686687331),
|
||||||
typ: BoolType,
|
},
|
||||||
val: "0",
|
{
|
||||||
err: true,
|
input: []byte("this is a test string"),
|
||||||
}, {
|
expected: "this is a test string",
|
||||||
typ: IntegerType,
|
},
|
||||||
val: "0",
|
}
|
||||||
out: 0,
|
|
||||||
}, {
|
|
||||||
typ: IntegerType,
|
|
||||||
val: "42",
|
|
||||||
out: 42,
|
|
||||||
}, {
|
|
||||||
typ: IntegerType,
|
|
||||||
val: "-42",
|
|
||||||
out: -42,
|
|
||||||
}, {
|
|
||||||
typ: IntegerType,
|
|
||||||
val: "q",
|
|
||||||
err: true,
|
|
||||||
}, {
|
|
||||||
typ: Hash160Type,
|
|
||||||
val: "AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y",
|
|
||||||
out: "23ba2703c53263e8d6e522dc32203339dcd8eee9",
|
|
||||||
}, {
|
|
||||||
typ: Hash160Type,
|
|
||||||
val: "50befd26fdf6e4d957c11e078b24ebce6291456f",
|
|
||||||
out: "50befd26fdf6e4d957c11e078b24ebce6291456f",
|
|
||||||
}, {
|
|
||||||
typ: Hash160Type,
|
|
||||||
val: "befd26fdf6e4d957c11e078b24ebce6291456f",
|
|
||||||
err: true,
|
|
||||||
}, {
|
|
||||||
typ: Hash160Type,
|
|
||||||
val: "q",
|
|
||||||
err: true,
|
|
||||||
}, {
|
|
||||||
typ: Hash256Type,
|
|
||||||
val: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7",
|
|
||||||
out: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7",
|
|
||||||
}, {
|
|
||||||
typ: Hash256Type,
|
|
||||||
val: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282d",
|
|
||||||
err: true,
|
|
||||||
}, {
|
|
||||||
typ: Hash256Type,
|
|
||||||
val: "q",
|
|
||||||
err: true,
|
|
||||||
}, {
|
|
||||||
typ: ByteArrayType,
|
|
||||||
val: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282d",
|
|
||||||
out: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282d",
|
|
||||||
}, {
|
|
||||||
typ: ByteArrayType,
|
|
||||||
val: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7",
|
|
||||||
out: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7",
|
|
||||||
}, {
|
|
||||||
typ: ByteArrayType,
|
|
||||||
val: "50befd26fdf6e4d957c11e078b24ebce6291456f",
|
|
||||||
out: "50befd26fdf6e4d957c11e078b24ebce6291456f",
|
|
||||||
}, {
|
|
||||||
typ: ByteArrayType,
|
|
||||||
val: "AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y",
|
|
||||||
err: true,
|
|
||||||
}, {
|
|
||||||
typ: ByteArrayType,
|
|
||||||
val: "q",
|
|
||||||
err: true,
|
|
||||||
}, {
|
|
||||||
typ: ByteArrayType,
|
|
||||||
val: "ab",
|
|
||||||
out: "ab",
|
|
||||||
}, {
|
|
||||||
typ: PublicKeyType,
|
|
||||||
val: "03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c",
|
|
||||||
out: "03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c",
|
|
||||||
}, {
|
|
||||||
typ: PublicKeyType,
|
|
||||||
val: "01b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c",
|
|
||||||
err: true,
|
|
||||||
}, {
|
|
||||||
typ: PublicKeyType,
|
|
||||||
val: "q",
|
|
||||||
err: true,
|
|
||||||
}, {
|
|
||||||
typ: StringType,
|
|
||||||
val: "q",
|
|
||||||
out: "q",
|
|
||||||
}, {
|
|
||||||
typ: StringType,
|
|
||||||
val: "dead",
|
|
||||||
out: "dead",
|
|
||||||
}, {
|
|
||||||
typ: StringType,
|
|
||||||
val: "йцукен",
|
|
||||||
out: "йцукен",
|
|
||||||
}, {
|
|
||||||
typ: ArrayType,
|
|
||||||
val: "",
|
|
||||||
err: true,
|
|
||||||
}}
|
|
||||||
|
|
||||||
for _, inout := range inouts {
|
func TestParam_TryParse(t *testing.T) {
|
||||||
out, err := adjustValToType(inout.typ, inout.val)
|
for _, tc := range tryParseTestCases {
|
||||||
if inout.err {
|
t.Run(reflect.TypeOf(tc.expected).String(), func(t *testing.T) {
|
||||||
assert.NotNil(t, err, "should error on '%s/%s' input", inout.typ, inout.val)
|
input := Parameter{
|
||||||
} else {
|
Type: ByteArrayType,
|
||||||
assert.Nil(t, err, "shouldn't error on '%s/%s' input", inout.typ, inout.val)
|
Value: tc.input,
|
||||||
assert.Equal(t, inout.out, out, "bad output for '%s/%s' input", inout.typ, inout.val)
|
}
|
||||||
}
|
|
||||||
|
val := reflect.New(reflect.TypeOf(tc.expected))
|
||||||
|
assert.NoError(t, input.TryParse(val.Interface()))
|
||||||
|
assert.Equal(t, tc.expected, val.Elem().Interface())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
t.Run("[]Uint160", func(t *testing.T) {
|
||||||
|
exp1 := util.Uint160{1, 2, 3, 4, 5}
|
||||||
|
exp2 := util.Uint160{9, 8, 7, 6, 5}
|
||||||
|
|
||||||
|
params := Params{
|
||||||
|
{
|
||||||
|
Type: ByteArrayType,
|
||||||
|
Value: exp1.BytesBE(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: ByteArrayType,
|
||||||
|
Value: exp2.BytesBE(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var out1, out2 util.Uint160
|
||||||
|
|
||||||
|
assert.NoError(t, params.TryParseArray(&out1, &out2))
|
||||||
|
assert.Equal(t, exp1, out1)
|
||||||
|
assert.Equal(t, exp2, out2)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParamType_String(t *testing.T) {
|
||||||
|
types := []ParamType{
|
||||||
|
SignatureType,
|
||||||
|
BoolType,
|
||||||
|
IntegerType,
|
||||||
|
Hash160Type,
|
||||||
|
Hash256Type,
|
||||||
|
ByteArrayType,
|
||||||
|
PublicKeyType,
|
||||||
|
StringType,
|
||||||
|
ArrayType,
|
||||||
|
InteropInterfaceType,
|
||||||
|
MapType,
|
||||||
|
VoidType,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, exp := range types {
|
||||||
|
actual, err := ParseParamType(exp.String())
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, exp, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
actual, err := ParseParamType(UnknownType.String())
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Equal(t, UnknownType, actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestNewParameterFromString(t *testing.T) {
|
func TestNewParameterFromString(t *testing.T) {
|
||||||
|
@ -330,6 +404,12 @@ func TestNewParameterFromString(t *testing.T) {
|
||||||
}, {
|
}, {
|
||||||
in: `bool:asdf`,
|
in: `bool:asdf`,
|
||||||
err: true,
|
err: true,
|
||||||
|
}, {
|
||||||
|
in: `InteropInterface:123`,
|
||||||
|
err: true,
|
||||||
|
}, {
|
||||||
|
in: `Map:[]`,
|
||||||
|
err: true,
|
||||||
}}
|
}}
|
||||||
for _, inout := range inouts {
|
for _, inout := range inouts {
|
||||||
out, err := NewParameterFromString(inout.in)
|
out, err := NewParameterFromString(inout.in)
|
||||||
|
|
269
pkg/smartcontract/param_type.go
Normal file
269
pkg/smartcontract/param_type.go
Normal file
|
@ -0,0 +1,269 @@
|
||||||
|
package smartcontract
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/crypto/keys"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/encoding/address"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/io"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ParamType represents the Type of the smart contract parameter.
|
||||||
|
type ParamType int
|
||||||
|
|
||||||
|
// A list of supported smart contract parameter types.
|
||||||
|
const (
|
||||||
|
UnknownType ParamType = -1
|
||||||
|
SignatureType ParamType = 0x00
|
||||||
|
BoolType ParamType = 0x01
|
||||||
|
IntegerType ParamType = 0x02
|
||||||
|
Hash160Type ParamType = 0x03
|
||||||
|
Hash256Type ParamType = 0x04
|
||||||
|
ByteArrayType ParamType = 0x05
|
||||||
|
PublicKeyType ParamType = 0x06
|
||||||
|
StringType ParamType = 0x07
|
||||||
|
ArrayType ParamType = 0x10
|
||||||
|
MapType ParamType = 0x12
|
||||||
|
InteropInterfaceType ParamType = 0xf0
|
||||||
|
VoidType ParamType = 0xff
|
||||||
|
)
|
||||||
|
|
||||||
|
// String implements the stringer interface.
|
||||||
|
func (pt ParamType) String() string {
|
||||||
|
switch pt {
|
||||||
|
case SignatureType:
|
||||||
|
return "Signature"
|
||||||
|
case BoolType:
|
||||||
|
return "Boolean"
|
||||||
|
case IntegerType:
|
||||||
|
return "Integer"
|
||||||
|
case Hash160Type:
|
||||||
|
return "Hash160"
|
||||||
|
case Hash256Type:
|
||||||
|
return "Hash256"
|
||||||
|
case ByteArrayType:
|
||||||
|
return "ByteArray"
|
||||||
|
case PublicKeyType:
|
||||||
|
return "PublicKey"
|
||||||
|
case StringType:
|
||||||
|
return "String"
|
||||||
|
case ArrayType:
|
||||||
|
return "Array"
|
||||||
|
case MapType:
|
||||||
|
return "Map"
|
||||||
|
case InteropInterfaceType:
|
||||||
|
return "InteropInterface"
|
||||||
|
case VoidType:
|
||||||
|
return "Void"
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements the json.Marshaler interface.
|
||||||
|
func (pt ParamType) MarshalJSON() ([]byte, error) {
|
||||||
|
return []byte(`"` + pt.String() + `"`), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements json.Unmarshaler interface.
|
||||||
|
func (pt *ParamType) UnmarshalJSON(data []byte) error {
|
||||||
|
var s string
|
||||||
|
if err := json.Unmarshal(data, &s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
p, err := ParseParamType(s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*pt = p
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalYAML implements the YAML Marshaler interface.
|
||||||
|
func (pt *ParamType) MarshalYAML() (interface{}, error) {
|
||||||
|
return pt.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalYAML implements the YAML Unmarshaler interface.
|
||||||
|
func (pt *ParamType) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||||
|
var name string
|
||||||
|
|
||||||
|
err := unmarshal(&name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
*pt, err = ParseParamType(name)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodeBinary implements io.Serializable interface.
|
||||||
|
func (pt ParamType) EncodeBinary(w *io.BinWriter) {
|
||||||
|
w.WriteB(byte(pt))
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecodeBinary implements io.Serializable interface.
|
||||||
|
func (pt *ParamType) DecodeBinary(r *io.BinReader) {
|
||||||
|
*pt = ParamType(r.ReadB())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseParamType is a user-friendly string to ParamType converter, it's
|
||||||
|
// case-insensitive and makes the following conversions:
|
||||||
|
// signature -> SignatureType
|
||||||
|
// bool, boolean -> BoolType
|
||||||
|
// int, integer -> IntegerType
|
||||||
|
// hash160 -> Hash160Type
|
||||||
|
// hash256 -> Hash256Type
|
||||||
|
// bytes, bytearray -> ByteArrayType
|
||||||
|
// key, publickey -> PublicKeyType
|
||||||
|
// string -> StringType
|
||||||
|
// array -> ArrayType
|
||||||
|
// map -> MapType
|
||||||
|
// interopinterface -> InteropInterfaceType
|
||||||
|
// void -> VoidType
|
||||||
|
// anything else generates an error.
|
||||||
|
func ParseParamType(typ string) (ParamType, error) {
|
||||||
|
switch strings.ToLower(typ) {
|
||||||
|
case "signature":
|
||||||
|
return SignatureType, nil
|
||||||
|
case "bool", "boolean":
|
||||||
|
return BoolType, nil
|
||||||
|
case "int", "integer":
|
||||||
|
return IntegerType, nil
|
||||||
|
case "hash160":
|
||||||
|
return Hash160Type, nil
|
||||||
|
case "hash256":
|
||||||
|
return Hash256Type, nil
|
||||||
|
case "bytes", "bytearray":
|
||||||
|
return ByteArrayType, nil
|
||||||
|
case "key", "publickey":
|
||||||
|
return PublicKeyType, nil
|
||||||
|
case "string":
|
||||||
|
return StringType, nil
|
||||||
|
case "array":
|
||||||
|
return ArrayType, nil
|
||||||
|
case "map":
|
||||||
|
return MapType, nil
|
||||||
|
case "interopinterface":
|
||||||
|
return InteropInterfaceType, nil
|
||||||
|
case "void":
|
||||||
|
return VoidType, nil
|
||||||
|
default:
|
||||||
|
return UnknownType, errors.Errorf("Unknown contract parameter type: %s", typ)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// adjustValToType is a value type-checker and converter.
|
||||||
|
func adjustValToType(typ ParamType, val string) (interface{}, error) {
|
||||||
|
switch typ {
|
||||||
|
case SignatureType:
|
||||||
|
b, err := hex.DecodeString(val)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(b) != 64 {
|
||||||
|
return nil, errors.New("not a signature")
|
||||||
|
}
|
||||||
|
return val, nil
|
||||||
|
case BoolType:
|
||||||
|
switch val {
|
||||||
|
case "true":
|
||||||
|
return true, nil
|
||||||
|
case "false":
|
||||||
|
return false, nil
|
||||||
|
default:
|
||||||
|
return nil, errors.New("invalid boolean value")
|
||||||
|
}
|
||||||
|
case IntegerType:
|
||||||
|
return strconv.Atoi(val)
|
||||||
|
case Hash160Type:
|
||||||
|
u, err := address.StringToUint160(val)
|
||||||
|
if err == nil {
|
||||||
|
return hex.EncodeToString(u.BytesBE()), nil
|
||||||
|
}
|
||||||
|
b, err := hex.DecodeString(val)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(b) != 20 {
|
||||||
|
return nil, errors.New("not a hash160")
|
||||||
|
}
|
||||||
|
return val, nil
|
||||||
|
case Hash256Type:
|
||||||
|
b, err := hex.DecodeString(val)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(b) != 32 {
|
||||||
|
return nil, errors.New("not a hash256")
|
||||||
|
}
|
||||||
|
return val, nil
|
||||||
|
case ByteArrayType:
|
||||||
|
_, err := hex.DecodeString(val)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return val, nil
|
||||||
|
case PublicKeyType:
|
||||||
|
_, err := keys.NewPublicKeyFromString(val)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return val, nil
|
||||||
|
case StringType:
|
||||||
|
return val, nil
|
||||||
|
default:
|
||||||
|
return nil, errors.New("unsupported parameter type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// inferParamType tries to infer the value type from its contents. It returns
|
||||||
|
// IntegerType for anything that looks like decimal integer (can be converted
|
||||||
|
// with strconv.Atoi), BoolType for true and false values, Hash160Type for
|
||||||
|
// addresses and hex strings encoding 20 bytes long values, PublicKeyType for
|
||||||
|
// valid hex-encoded public keys, Hash256Type for hex-encoded 32 bytes values,
|
||||||
|
// SignatureType for hex-encoded 64 bytes values, ByteArrayType for any other
|
||||||
|
// valid hex-encoded values and StringType for anything else.
|
||||||
|
func inferParamType(val string) ParamType {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
_, err = strconv.Atoi(val)
|
||||||
|
if err == nil {
|
||||||
|
return IntegerType
|
||||||
|
}
|
||||||
|
|
||||||
|
if val == "true" || val == "false" {
|
||||||
|
return BoolType
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = address.StringToUint160(val)
|
||||||
|
if err == nil {
|
||||||
|
return Hash160Type
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = keys.NewPublicKeyFromString(val)
|
||||||
|
if err == nil {
|
||||||
|
return PublicKeyType
|
||||||
|
}
|
||||||
|
|
||||||
|
unhexed, err := hex.DecodeString(val)
|
||||||
|
if err == nil {
|
||||||
|
switch len(unhexed) {
|
||||||
|
case 20:
|
||||||
|
return Hash160Type
|
||||||
|
case 32:
|
||||||
|
return Hash256Type
|
||||||
|
case 64:
|
||||||
|
return SignatureType
|
||||||
|
default:
|
||||||
|
return ByteArrayType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Anything can be a string.
|
||||||
|
return StringType
|
||||||
|
}
|
298
pkg/smartcontract/param_type_test.go
Normal file
298
pkg/smartcontract/param_type_test.go
Normal file
|
@ -0,0 +1,298 @@
|
||||||
|
package smartcontract
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseParamType(t *testing.T) {
|
||||||
|
var inouts = []struct {
|
||||||
|
in string
|
||||||
|
out ParamType
|
||||||
|
err bool
|
||||||
|
}{{
|
||||||
|
in: "signature",
|
||||||
|
out: SignatureType,
|
||||||
|
}, {
|
||||||
|
in: "Signature",
|
||||||
|
out: SignatureType,
|
||||||
|
}, {
|
||||||
|
in: "SiGnAtUrE",
|
||||||
|
out: SignatureType,
|
||||||
|
}, {
|
||||||
|
in: "bool",
|
||||||
|
out: BoolType,
|
||||||
|
}, {
|
||||||
|
in: "int",
|
||||||
|
out: IntegerType,
|
||||||
|
}, {
|
||||||
|
in: "hash160",
|
||||||
|
out: Hash160Type,
|
||||||
|
}, {
|
||||||
|
in: "hash256",
|
||||||
|
out: Hash256Type,
|
||||||
|
}, {
|
||||||
|
in: "bytes",
|
||||||
|
out: ByteArrayType,
|
||||||
|
}, {
|
||||||
|
in: "key",
|
||||||
|
out: PublicKeyType,
|
||||||
|
}, {
|
||||||
|
in: "string",
|
||||||
|
out: StringType,
|
||||||
|
}, {
|
||||||
|
in: "array",
|
||||||
|
out: ArrayType,
|
||||||
|
}, {
|
||||||
|
in: "map",
|
||||||
|
out: MapType,
|
||||||
|
}, {
|
||||||
|
in: "interopinterface",
|
||||||
|
out: InteropInterfaceType,
|
||||||
|
}, {
|
||||||
|
in: "void",
|
||||||
|
out: VoidType,
|
||||||
|
}, {
|
||||||
|
in: "qwerty",
|
||||||
|
err: true,
|
||||||
|
}}
|
||||||
|
for _, inout := range inouts {
|
||||||
|
out, err := ParseParamType(inout.in)
|
||||||
|
if inout.err {
|
||||||
|
assert.NotNil(t, err, "should error on '%s' input", inout.in)
|
||||||
|
} else {
|
||||||
|
assert.Nil(t, err, "shouldn't error on '%s' input", inout.in)
|
||||||
|
assert.Equal(t, inout.out, out, "bad output for '%s' input", inout.in)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInferParamType(t *testing.T) {
|
||||||
|
var inouts = []struct {
|
||||||
|
in string
|
||||||
|
out ParamType
|
||||||
|
}{{
|
||||||
|
in: "42",
|
||||||
|
out: IntegerType,
|
||||||
|
}, {
|
||||||
|
in: "-42",
|
||||||
|
out: IntegerType,
|
||||||
|
}, {
|
||||||
|
in: "0",
|
||||||
|
out: IntegerType,
|
||||||
|
}, {
|
||||||
|
in: "2e10",
|
||||||
|
out: ByteArrayType,
|
||||||
|
}, {
|
||||||
|
in: "true",
|
||||||
|
out: BoolType,
|
||||||
|
}, {
|
||||||
|
in: "false",
|
||||||
|
out: BoolType,
|
||||||
|
}, {
|
||||||
|
in: "truee",
|
||||||
|
out: StringType,
|
||||||
|
}, {
|
||||||
|
in: "AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y",
|
||||||
|
out: Hash160Type,
|
||||||
|
}, {
|
||||||
|
in: "ZK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y",
|
||||||
|
out: StringType,
|
||||||
|
}, {
|
||||||
|
in: "50befd26fdf6e4d957c11e078b24ebce6291456f",
|
||||||
|
out: Hash160Type,
|
||||||
|
}, {
|
||||||
|
in: "03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c",
|
||||||
|
out: PublicKeyType,
|
||||||
|
}, {
|
||||||
|
in: "30b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c",
|
||||||
|
out: ByteArrayType,
|
||||||
|
}, {
|
||||||
|
in: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7",
|
||||||
|
out: Hash256Type,
|
||||||
|
}, {
|
||||||
|
in: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7da",
|
||||||
|
out: ByteArrayType,
|
||||||
|
}, {
|
||||||
|
in: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b",
|
||||||
|
out: SignatureType,
|
||||||
|
}, {
|
||||||
|
in: "qwerty",
|
||||||
|
out: StringType,
|
||||||
|
}, {
|
||||||
|
in: "ab",
|
||||||
|
out: ByteArrayType,
|
||||||
|
}, {
|
||||||
|
in: "az",
|
||||||
|
out: StringType,
|
||||||
|
}, {
|
||||||
|
in: "bad",
|
||||||
|
out: StringType,
|
||||||
|
}, {
|
||||||
|
in: "фыва",
|
||||||
|
out: StringType,
|
||||||
|
}, {
|
||||||
|
in: "dead",
|
||||||
|
out: ByteArrayType,
|
||||||
|
}}
|
||||||
|
for _, inout := range inouts {
|
||||||
|
out := inferParamType(inout.in)
|
||||||
|
assert.Equal(t, inout.out, out, "bad output for '%s' input", inout.in)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAdjustValToType(t *testing.T) {
|
||||||
|
var inouts = []struct {
|
||||||
|
typ ParamType
|
||||||
|
val string
|
||||||
|
out interface{}
|
||||||
|
err bool
|
||||||
|
}{{
|
||||||
|
typ: SignatureType,
|
||||||
|
val: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b",
|
||||||
|
out: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c9b",
|
||||||
|
}, {
|
||||||
|
typ: SignatureType,
|
||||||
|
val: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7c56f33fc6ecfcd0c225c4ab356fee59390af8560be0e930faebe74a6daff7c",
|
||||||
|
err: true,
|
||||||
|
}, {
|
||||||
|
typ: SignatureType,
|
||||||
|
val: "qwerty",
|
||||||
|
err: true,
|
||||||
|
}, {
|
||||||
|
typ: BoolType,
|
||||||
|
val: "false",
|
||||||
|
out: false,
|
||||||
|
}, {
|
||||||
|
typ: BoolType,
|
||||||
|
val: "true",
|
||||||
|
out: true,
|
||||||
|
}, {
|
||||||
|
typ: BoolType,
|
||||||
|
val: "qwerty",
|
||||||
|
err: true,
|
||||||
|
}, {
|
||||||
|
typ: BoolType,
|
||||||
|
val: "42",
|
||||||
|
err: true,
|
||||||
|
}, {
|
||||||
|
typ: BoolType,
|
||||||
|
val: "0",
|
||||||
|
err: true,
|
||||||
|
}, {
|
||||||
|
typ: IntegerType,
|
||||||
|
val: "0",
|
||||||
|
out: 0,
|
||||||
|
}, {
|
||||||
|
typ: IntegerType,
|
||||||
|
val: "42",
|
||||||
|
out: 42,
|
||||||
|
}, {
|
||||||
|
typ: IntegerType,
|
||||||
|
val: "-42",
|
||||||
|
out: -42,
|
||||||
|
}, {
|
||||||
|
typ: IntegerType,
|
||||||
|
val: "q",
|
||||||
|
err: true,
|
||||||
|
}, {
|
||||||
|
typ: Hash160Type,
|
||||||
|
val: "AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y",
|
||||||
|
out: "23ba2703c53263e8d6e522dc32203339dcd8eee9",
|
||||||
|
}, {
|
||||||
|
typ: Hash160Type,
|
||||||
|
val: "50befd26fdf6e4d957c11e078b24ebce6291456f",
|
||||||
|
out: "50befd26fdf6e4d957c11e078b24ebce6291456f",
|
||||||
|
}, {
|
||||||
|
typ: Hash160Type,
|
||||||
|
val: "befd26fdf6e4d957c11e078b24ebce6291456f",
|
||||||
|
err: true,
|
||||||
|
}, {
|
||||||
|
typ: Hash160Type,
|
||||||
|
val: "q",
|
||||||
|
err: true,
|
||||||
|
}, {
|
||||||
|
typ: Hash256Type,
|
||||||
|
val: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7",
|
||||||
|
out: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7",
|
||||||
|
}, {
|
||||||
|
typ: Hash256Type,
|
||||||
|
val: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282d",
|
||||||
|
err: true,
|
||||||
|
}, {
|
||||||
|
typ: Hash256Type,
|
||||||
|
val: "q",
|
||||||
|
err: true,
|
||||||
|
}, {
|
||||||
|
typ: ByteArrayType,
|
||||||
|
val: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282d",
|
||||||
|
out: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282d",
|
||||||
|
}, {
|
||||||
|
typ: ByteArrayType,
|
||||||
|
val: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7",
|
||||||
|
out: "602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7",
|
||||||
|
}, {
|
||||||
|
typ: ByteArrayType,
|
||||||
|
val: "50befd26fdf6e4d957c11e078b24ebce6291456f",
|
||||||
|
out: "50befd26fdf6e4d957c11e078b24ebce6291456f",
|
||||||
|
}, {
|
||||||
|
typ: ByteArrayType,
|
||||||
|
val: "AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y",
|
||||||
|
err: true,
|
||||||
|
}, {
|
||||||
|
typ: ByteArrayType,
|
||||||
|
val: "q",
|
||||||
|
err: true,
|
||||||
|
}, {
|
||||||
|
typ: ByteArrayType,
|
||||||
|
val: "ab",
|
||||||
|
out: "ab",
|
||||||
|
}, {
|
||||||
|
typ: PublicKeyType,
|
||||||
|
val: "03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c",
|
||||||
|
out: "03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c",
|
||||||
|
}, {
|
||||||
|
typ: PublicKeyType,
|
||||||
|
val: "01b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c",
|
||||||
|
err: true,
|
||||||
|
}, {
|
||||||
|
typ: PublicKeyType,
|
||||||
|
val: "q",
|
||||||
|
err: true,
|
||||||
|
}, {
|
||||||
|
typ: StringType,
|
||||||
|
val: "q",
|
||||||
|
out: "q",
|
||||||
|
}, {
|
||||||
|
typ: StringType,
|
||||||
|
val: "dead",
|
||||||
|
out: "dead",
|
||||||
|
}, {
|
||||||
|
typ: StringType,
|
||||||
|
val: "йцукен",
|
||||||
|
out: "йцукен",
|
||||||
|
}, {
|
||||||
|
typ: ArrayType,
|
||||||
|
val: "",
|
||||||
|
err: true,
|
||||||
|
}, {
|
||||||
|
typ: MapType,
|
||||||
|
val: "[]",
|
||||||
|
err: true,
|
||||||
|
}, {
|
||||||
|
typ: InteropInterfaceType,
|
||||||
|
val: "",
|
||||||
|
err: true,
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, inout := range inouts {
|
||||||
|
out, err := adjustValToType(inout.typ, inout.val)
|
||||||
|
if inout.err {
|
||||||
|
assert.NotNil(t, err, "should error on '%s/%s' input", inout.typ, inout.val)
|
||||||
|
} else {
|
||||||
|
assert.Nil(t, err, "shouldn't error on '%s/%s' input", inout.typ, inout.val)
|
||||||
|
assert.Equal(t, inout.out, out, "bad output for '%s/%s' input", inout.typ, inout.val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,13 +1,18 @@
|
||||||
package trigger
|
package trigger
|
||||||
|
|
||||||
// Trigger typed used in C# reference node: https://github.com/neo-project/neo/blob/c64748ecbac3baeb8045b16af0d518398a6ced24/neo/SmartContract/TriggerType.cs#L3
|
//go:generate stringer -type=Type
|
||||||
|
|
||||||
|
// Type represents trigger type used in C# reference node: https://github.com/neo-project/neo/blob/c64748ecbac3baeb8045b16af0d518398a6ced24/neo/SmartContract/TriggerType.cs#L3
|
||||||
|
type Type byte
|
||||||
|
|
||||||
|
// Viable list of supported trigger type constants.
|
||||||
const (
|
const (
|
||||||
// The verification trigger indicates that the contract is being invoked as a verification function.
|
// The verification trigger indicates that the contract is being invoked as a verification function.
|
||||||
// The verification function can accept multiple parameters, and should return a boolean value that indicates the validity of the transaction or block.
|
// The verification function can accept multiple parameters, and should return a boolean value that indicates the validity of the transaction or block.
|
||||||
// The entry point of the contract will be invoked if the contract is triggered by Verification:
|
// The entry point of the contract will be invoked if the contract is triggered by Verification:
|
||||||
// main(...);
|
// main(...);
|
||||||
// The entry point of the contract must be able to handle this type of invocation.
|
// The entry point of the contract must be able to handle this type of invocation.
|
||||||
Verification = 0x00
|
Verification Type = 0x00
|
||||||
|
|
||||||
// The verificationR trigger indicates that the contract is being invoked as a verification function because it is specified as a target of an output of the transaction.
|
// The verificationR trigger indicates that the contract is being invoked as a verification function because it is specified as a target of an output of the transaction.
|
||||||
// The verification function accepts no parameter, and should return a boolean value that indicates the validity of the transaction.
|
// The verification function accepts no parameter, and should return a boolean value that indicates the validity of the transaction.
|
||||||
|
@ -16,14 +21,14 @@ const (
|
||||||
// The receiving function should have the following signature:
|
// The receiving function should have the following signature:
|
||||||
// public bool receiving()
|
// public bool receiving()
|
||||||
// The receiving function will be invoked automatically when a contract is receiving assets from a transfer.
|
// The receiving function will be invoked automatically when a contract is receiving assets from a transfer.
|
||||||
VerificationR = 0x01
|
VerificationR Type = 0x01
|
||||||
|
|
||||||
// The application trigger indicates that the contract is being invoked as an application function.
|
// The application trigger indicates that the contract is being invoked as an application function.
|
||||||
// The application function can accept multiple parameters, change the states of the blockchain, and return any type of value.
|
// The application function can accept multiple parameters, change the states of the blockchain, and return any type of value.
|
||||||
// The contract can have any form of entry point, but we recommend that all contracts should have the following entry point:
|
// The contract can have any form of entry point, but we recommend that all contracts should have the following entry point:
|
||||||
// public byte[] main(string operation, params object[] args)
|
// public byte[] main(string operation, params object[] args)
|
||||||
// The functions can be invoked by creating an InvocationTransaction.
|
// The functions can be invoked by creating an InvocationTransaction.
|
||||||
Application = 0x10
|
Application Type = 0x10
|
||||||
|
|
||||||
// The ApplicationR trigger indicates that the default function received of the contract is being invoked because it is specified as a target of an output of the transaction.
|
// The ApplicationR trigger indicates that the default function received of the contract is being invoked because it is specified as a target of an output of the transaction.
|
||||||
// The received function accepts no parameter, changes the states of the blockchain, and returns any type of value.
|
// The received function accepts no parameter, changes the states of the blockchain, and returns any type of value.
|
||||||
|
@ -32,5 +37,5 @@ const (
|
||||||
// The received function should have the following signature:
|
// The received function should have the following signature:
|
||||||
// public byte[] received()
|
// public byte[] received()
|
||||||
// The received function will be invoked automatically when a contract is receiving assets from a transfer.
|
// The received function will be invoked automatically when a contract is receiving assets from a transfer.
|
||||||
ApplicationR = 0x11
|
ApplicationR Type = 0x11
|
||||||
)
|
)
|
||||||
|
|
37
pkg/smartcontract/trigger/trigger_type_string.go
Normal file
37
pkg/smartcontract/trigger/trigger_type_string.go
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
// Code generated by "stringer -type=Type"; DO NOT EDIT.
|
||||||
|
|
||||||
|
package trigger
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
func _() {
|
||||||
|
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||||
|
// Re-run the stringer command to generate them again.
|
||||||
|
var x [1]struct{}
|
||||||
|
_ = x[Verification-0]
|
||||||
|
_ = x[VerificationR-1]
|
||||||
|
_ = x[Application-16]
|
||||||
|
_ = x[ApplicationR-17]
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
_Type_name_0 = "VerificationVerificationR"
|
||||||
|
_Type_name_1 = "ApplicationApplicationR"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_Type_index_0 = [...]uint8{0, 12, 25}
|
||||||
|
_Type_index_1 = [...]uint8{0, 11, 23}
|
||||||
|
)
|
||||||
|
|
||||||
|
func (i Type) String() string {
|
||||||
|
switch {
|
||||||
|
case i <= 1:
|
||||||
|
return _Type_name_0[_Type_index_0[i]:_Type_index_0[i+1]]
|
||||||
|
case 16 <= i && i <= 17:
|
||||||
|
i -= 16
|
||||||
|
return _Type_name_1[_Type_index_1[i]:_Type_index_1[i+1]]
|
||||||
|
default:
|
||||||
|
return "Type(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
|
}
|
||||||
|
}
|
43
pkg/smartcontract/trigger/trigger_type_test.go
Normal file
43
pkg/smartcontract/trigger/trigger_type_test.go
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
package trigger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStringer(t *testing.T) {
|
||||||
|
tests := map[Type]string{
|
||||||
|
Application: "Application",
|
||||||
|
ApplicationR: "ApplicationR",
|
||||||
|
Verification: "Verification",
|
||||||
|
VerificationR: "VerificationR",
|
||||||
|
}
|
||||||
|
for o, s := range tests {
|
||||||
|
assert.Equal(t, s, o.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodeBynary(t *testing.T) {
|
||||||
|
tests := map[Type]byte{
|
||||||
|
Verification: 0x00,
|
||||||
|
VerificationR: 0x01,
|
||||||
|
Application: 0x10,
|
||||||
|
ApplicationR: 0x11,
|
||||||
|
}
|
||||||
|
for o, b := range tests {
|
||||||
|
assert.Equal(t, b, byte(o))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecodeBynary(t *testing.T) {
|
||||||
|
tests := map[Type]byte{
|
||||||
|
Verification: 0x00,
|
||||||
|
VerificationR: 0x01,
|
||||||
|
Application: 0x10,
|
||||||
|
ApplicationR: 0x11,
|
||||||
|
}
|
||||||
|
for o, b := range tests {
|
||||||
|
assert.Equal(t, o, Type(b))
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
|
"github.com/CityOfZion/neo-go/pkg/crypto/hash"
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/smartcontract"
|
||||||
"github.com/CityOfZion/neo-go/pkg/util"
|
"github.com/CityOfZion/neo-go/pkg/util"
|
||||||
"github.com/CityOfZion/neo-go/pkg/vm/opcode"
|
"github.com/CityOfZion/neo-go/pkg/vm/opcode"
|
||||||
)
|
)
|
||||||
|
@ -169,6 +170,11 @@ func (c *Context) Dup() StackItem {
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToContractParameter implements StackItem interface.
|
||||||
|
func (c *Context) ToContractParameter() smartcontract.Parameter {
|
||||||
|
panic("Not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
func (c *Context) atBreakPoint() bool {
|
func (c *Context) atBreakPoint() bool {
|
||||||
for _, n := range c.breakPoints {
|
for _, n := range c.breakPoints {
|
||||||
if n == c.ip {
|
if n == c.ip {
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"math/big"
|
"math/big"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/smartcontract"
|
||||||
"github.com/CityOfZion/neo-go/pkg/vm/emit"
|
"github.com/CityOfZion/neo-go/pkg/vm/emit"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -17,6 +18,8 @@ type StackItem interface {
|
||||||
Value() interface{}
|
Value() interface{}
|
||||||
// Dup duplicates current StackItem.
|
// Dup duplicates current StackItem.
|
||||||
Dup() StackItem
|
Dup() StackItem
|
||||||
|
// ToContractParameter converts StackItem to smartcontract.Parameter
|
||||||
|
ToContractParameter() smartcontract.Parameter
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeStackItem(v interface{}) StackItem {
|
func makeStackItem(v interface{}) StackItem {
|
||||||
|
@ -115,6 +118,19 @@ func (i *StructItem) Dup() StackItem {
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToContractParameter implements StackItem interface.
|
||||||
|
func (i *StructItem) ToContractParameter() smartcontract.Parameter {
|
||||||
|
var value []smartcontract.Parameter
|
||||||
|
for _, stackItem := range i.value {
|
||||||
|
parameter := stackItem.ToContractParameter()
|
||||||
|
value = append(value, parameter)
|
||||||
|
}
|
||||||
|
return smartcontract.Parameter{
|
||||||
|
Type: smartcontract.ArrayType,
|
||||||
|
Value: value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Clone returns a Struct with all Struct fields copied by value.
|
// Clone returns a Struct with all Struct fields copied by value.
|
||||||
// Array fields are still copied by reference.
|
// Array fields are still copied by reference.
|
||||||
func (i *StructItem) Clone() *StructItem {
|
func (i *StructItem) Clone() *StructItem {
|
||||||
|
@ -162,6 +178,14 @@ func (i *BigIntegerItem) Dup() StackItem {
|
||||||
return &BigIntegerItem{n.Set(i.value)}
|
return &BigIntegerItem{n.Set(i.value)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToContractParameter implements StackItem interface.
|
||||||
|
func (i *BigIntegerItem) ToContractParameter() smartcontract.Parameter {
|
||||||
|
return smartcontract.Parameter{
|
||||||
|
Type: smartcontract.IntegerType,
|
||||||
|
Value: i.value.Int64(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MarshalJSON implements the json.Marshaler interface.
|
// MarshalJSON implements the json.Marshaler interface.
|
||||||
func (i *BigIntegerItem) MarshalJSON() ([]byte, error) {
|
func (i *BigIntegerItem) MarshalJSON() ([]byte, error) {
|
||||||
return json.Marshal(i.value)
|
return json.Marshal(i.value)
|
||||||
|
@ -198,6 +222,14 @@ func (i *BoolItem) Dup() StackItem {
|
||||||
return &BoolItem{i.value}
|
return &BoolItem{i.value}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToContractParameter implements StackItem interface.
|
||||||
|
func (i *BoolItem) ToContractParameter() smartcontract.Parameter {
|
||||||
|
return smartcontract.Parameter{
|
||||||
|
Type: smartcontract.BoolType,
|
||||||
|
Value: i.value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ByteArrayItem represents a byte array on the stack.
|
// ByteArrayItem represents a byte array on the stack.
|
||||||
type ByteArrayItem struct {
|
type ByteArrayItem struct {
|
||||||
value []byte
|
value []byte
|
||||||
|
@ -231,6 +263,14 @@ func (i *ByteArrayItem) Dup() StackItem {
|
||||||
return &ByteArrayItem{a}
|
return &ByteArrayItem{a}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToContractParameter implements StackItem interface.
|
||||||
|
func (i *ByteArrayItem) ToContractParameter() smartcontract.Parameter {
|
||||||
|
return smartcontract.Parameter{
|
||||||
|
Type: smartcontract.ByteArrayType,
|
||||||
|
Value: i.value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ArrayItem represents a new ArrayItem object.
|
// ArrayItem represents a new ArrayItem object.
|
||||||
type ArrayItem struct {
|
type ArrayItem struct {
|
||||||
value []StackItem
|
value []StackItem
|
||||||
|
@ -263,6 +303,19 @@ func (i *ArrayItem) Dup() StackItem {
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToContractParameter implements StackItem interface.
|
||||||
|
func (i *ArrayItem) ToContractParameter() smartcontract.Parameter {
|
||||||
|
var value []smartcontract.Parameter
|
||||||
|
for _, stackItem := range i.value {
|
||||||
|
parameter := stackItem.ToContractParameter()
|
||||||
|
value = append(value, parameter)
|
||||||
|
}
|
||||||
|
return smartcontract.Parameter{
|
||||||
|
Type: smartcontract.ArrayType,
|
||||||
|
Value: value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MapItem represents Map object.
|
// MapItem represents Map object.
|
||||||
type MapItem struct {
|
type MapItem struct {
|
||||||
value map[interface{}]StackItem
|
value map[interface{}]StackItem
|
||||||
|
@ -280,7 +333,6 @@ func (i *MapItem) Value() interface{} {
|
||||||
return i.value
|
return i.value
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON implements the json.Marshaler interface.
|
|
||||||
func (i *MapItem) String() string {
|
func (i *MapItem) String() string {
|
||||||
return "Map"
|
return "Map"
|
||||||
}
|
}
|
||||||
|
@ -297,6 +349,23 @@ func (i *MapItem) Dup() StackItem {
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToContractParameter implements StackItem interface.
|
||||||
|
func (i *MapItem) ToContractParameter() smartcontract.Parameter {
|
||||||
|
value := make(map[smartcontract.Parameter]smartcontract.Parameter)
|
||||||
|
for key, val := range i.value {
|
||||||
|
pValue := val.ToContractParameter()
|
||||||
|
pKey := fromMapKey(key).ToContractParameter()
|
||||||
|
if pKey.Type == smartcontract.ByteArrayType {
|
||||||
|
pKey.Value = string(pKey.Value.([]byte))
|
||||||
|
}
|
||||||
|
value[pKey] = pValue
|
||||||
|
}
|
||||||
|
return smartcontract.Parameter{
|
||||||
|
Type: smartcontract.MapType,
|
||||||
|
Value: value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Add adds key-value pair to the map.
|
// Add adds key-value pair to the map.
|
||||||
func (i *MapItem) Add(key, value StackItem) {
|
func (i *MapItem) Add(key, value StackItem) {
|
||||||
i.value[toMapKey(key)] = value
|
i.value[toMapKey(key)] = value
|
||||||
|
@ -316,6 +385,20 @@ func toMapKey(key StackItem) interface{} {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fromMapKey converts map key to StackItem
|
||||||
|
func fromMapKey(key interface{}) StackItem {
|
||||||
|
switch t := key.(type) {
|
||||||
|
case bool:
|
||||||
|
return &BoolItem{value: t}
|
||||||
|
case int64:
|
||||||
|
return &BigIntegerItem{value: big.NewInt(t)}
|
||||||
|
case string:
|
||||||
|
return &ByteArrayItem{value: []byte(t)}
|
||||||
|
default:
|
||||||
|
panic("wrong key type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// InteropItem represents interop data on the stack.
|
// InteropItem represents interop data on the stack.
|
||||||
type InteropItem struct {
|
type InteropItem struct {
|
||||||
value interface{}
|
value interface{}
|
||||||
|
@ -344,6 +427,14 @@ func (i *InteropItem) Dup() StackItem {
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ToContractParameter implements StackItem interface.
|
||||||
|
func (i *InteropItem) ToContractParameter() smartcontract.Parameter {
|
||||||
|
return smartcontract.Parameter{
|
||||||
|
Type: smartcontract.InteropInterfaceType,
|
||||||
|
Value: nil,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MarshalJSON implements the json.Marshaler interface.
|
// MarshalJSON implements the json.Marshaler interface.
|
||||||
func (i *InteropItem) MarshalJSON() ([]byte, error) {
|
func (i *InteropItem) MarshalJSON() ([]byte, error) {
|
||||||
return json.Marshal(i.value)
|
return json.Marshal(i.value)
|
||||||
|
|
91
pkg/vm/stack_item_test.go
Normal file
91
pkg/vm/stack_item_test.go
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
package vm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/CityOfZion/neo-go/pkg/smartcontract"
|
||||||
|
)
|
||||||
|
|
||||||
|
var toContractParameterTestCases = []struct {
|
||||||
|
input StackItem
|
||||||
|
result smartcontract.Parameter
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
input: NewStructItem([]StackItem{
|
||||||
|
NewBigIntegerItem(1),
|
||||||
|
NewBoolItem(true),
|
||||||
|
}),
|
||||||
|
result: smartcontract.Parameter{Type: smartcontract.ArrayType, Value: []smartcontract.Parameter{
|
||||||
|
{Type: smartcontract.IntegerType, Value: int64(1)},
|
||||||
|
{Type: smartcontract.BoolType, Value: true},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: NewBoolItem(false),
|
||||||
|
result: smartcontract.Parameter{Type: smartcontract.BoolType, Value: false},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: NewByteArrayItem([]byte{0x01, 0x02, 0x03}),
|
||||||
|
result: smartcontract.Parameter{Type: smartcontract.ByteArrayType, Value: []byte{0x01, 0x02, 0x03}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: NewArrayItem([]StackItem{NewBigIntegerItem(2), NewBoolItem(true)}),
|
||||||
|
result: smartcontract.Parameter{Type: smartcontract.ArrayType, Value: []smartcontract.Parameter{
|
||||||
|
{Type: smartcontract.IntegerType, Value: int64(2)},
|
||||||
|
{Type: smartcontract.BoolType, Value: true},
|
||||||
|
}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: NewInteropItem(nil),
|
||||||
|
result: smartcontract.Parameter{Type: smartcontract.InteropInterfaceType, Value: nil},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: &MapItem{value: map[interface{}]StackItem{
|
||||||
|
toMapKey(NewBigIntegerItem(1)): NewBoolItem(true),
|
||||||
|
toMapKey(NewByteArrayItem([]byte("qwerty"))): NewBigIntegerItem(3),
|
||||||
|
toMapKey(NewBoolItem(true)): NewBoolItem(false),
|
||||||
|
}},
|
||||||
|
result: smartcontract.Parameter{
|
||||||
|
Type: smartcontract.MapType,
|
||||||
|
Value: map[smartcontract.Parameter]smartcontract.Parameter{
|
||||||
|
{Type: smartcontract.IntegerType, Value: int64(1)}: {Type: smartcontract.BoolType, Value: true},
|
||||||
|
{Type: smartcontract.ByteArrayType, Value: "qwerty"}: {Type: smartcontract.IntegerType, Value: int64(3)},
|
||||||
|
{Type: smartcontract.BoolType, Value: true}: {Type: smartcontract.BoolType, Value: false},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestToContractParameter(t *testing.T) {
|
||||||
|
for _, tc := range toContractParameterTestCases {
|
||||||
|
res := tc.input.ToContractParameter()
|
||||||
|
assert.Equal(t, res, tc.result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var fromMapKeyTestCases = []struct {
|
||||||
|
input interface{}
|
||||||
|
result StackItem
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
input: true,
|
||||||
|
result: NewBoolItem(true),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: int64(4),
|
||||||
|
result: NewBigIntegerItem(4),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "qwerty",
|
||||||
|
result: NewByteArrayItem([]byte("qwerty")),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFromMapKey(t *testing.T) {
|
||||||
|
for _, tc := range fromMapKeyTestCases {
|
||||||
|
res := fromMapKey(tc.input)
|
||||||
|
assert.Equal(t, res, tc.result)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue