mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2025-05-03 23:02:27 +00:00
compiler: enforce runtime.Notify parameters cast
If notification parameters type can be defined in a compile time then enforce parameter cast to the desired type got from manifest.
This commit is contained in:
parent
80f71a4e6e
commit
554e48e7b7
5 changed files with 266 additions and 13 deletions
|
@ -4,6 +4,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
|
@ -22,6 +23,7 @@ import (
|
|||
cinterop "github.com/nspcc-dev/neo-go/pkg/interop"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neotest/chain"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||
|
@ -548,3 +550,145 @@ func TestCallWithVersion(t *testing.T) {
|
|||
c.InvokeFail(t, "contract version mismatch", "callWithVersion", policyH.BytesBE(), 1, "getExecFeeFactor")
|
||||
})
|
||||
}
|
||||
|
||||
func TestForcedNotifyArgumentsConversion(t *testing.T) {
|
||||
const methodWithEllipsis = "withEllipsis"
|
||||
const methodWithoutEllipsis = "withoutEllipsis"
|
||||
check := func(t *testing.T, method string, targetSCParamTypes []smartcontract.ParamType, expectedVMParamTypes []stackitem.Type, noEventsCheck bool) {
|
||||
bc, acc := chain.NewSingle(t)
|
||||
e := neotest.NewExecutor(t, bc, acc, acc)
|
||||
src := `package foo
|
||||
import "github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
||||
const arg4 = 4 // Const value.
|
||||
func WithoutEllipsis() {
|
||||
var arg0 int // Default value.
|
||||
var arg1 int = 1 // Initialized value.
|
||||
arg2 := 2 // Short decl.
|
||||
var arg3 int
|
||||
arg3 = 3 // Declare first, change value afterwards.
|
||||
runtime.Notify("withoutEllipsis", arg0, arg1, arg2, arg3, arg4, 5, f(6)) // The fifth argument is basic literal.
|
||||
}
|
||||
func WithEllipsis() {
|
||||
arg := []interface{}{0, 1, f(2), 3, 4, 5, 6}
|
||||
runtime.Notify("withEllipsis", arg...)
|
||||
}
|
||||
func f(i int) int {
|
||||
return i
|
||||
}`
|
||||
count := len(targetSCParamTypes)
|
||||
if count != len(expectedVMParamTypes) {
|
||||
t.Fatalf("parameters count mismatch: %d vs %d", count, len(expectedVMParamTypes))
|
||||
}
|
||||
scParams := make([]manifest.Parameter, len(targetSCParamTypes))
|
||||
vmParams := make([]stackitem.Item, len(expectedVMParamTypes))
|
||||
for i := range scParams {
|
||||
scParams[i] = manifest.Parameter{
|
||||
Name: strconv.Itoa(i),
|
||||
Type: targetSCParamTypes[i],
|
||||
}
|
||||
defaultValue := stackitem.NewBigInteger(big.NewInt(int64(i)))
|
||||
var (
|
||||
val stackitem.Item
|
||||
err error
|
||||
)
|
||||
if expectedVMParamTypes[i] == stackitem.IntegerT {
|
||||
val = defaultValue
|
||||
} else {
|
||||
val, err = defaultValue.Convert(expectedVMParamTypes[i]) // exactly the same conversion should be emitted by compiler and performed by the contract code.
|
||||
require.NoError(t, err)
|
||||
}
|
||||
vmParams[i] = val
|
||||
}
|
||||
ctr := neotest.CompileSource(t, e.CommitteeHash, strings.NewReader(src), &compiler.Options{
|
||||
Name: "Helper",
|
||||
ContractEvents: []manifest.Event{
|
||||
{
|
||||
Name: methodWithoutEllipsis,
|
||||
Parameters: scParams,
|
||||
},
|
||||
{
|
||||
Name: methodWithEllipsis,
|
||||
Parameters: scParams,
|
||||
},
|
||||
},
|
||||
NoEventsCheck: noEventsCheck,
|
||||
})
|
||||
e.DeployContract(t, ctr, nil)
|
||||
c := e.CommitteeInvoker(ctr.Hash)
|
||||
|
||||
t.Run(method, func(t *testing.T) {
|
||||
h := c.Invoke(t, stackitem.Null{}, method)
|
||||
aer := c.GetTxExecResult(t, h)
|
||||
require.Equal(t, 1, len(aer.Events))
|
||||
require.Equal(t, stackitem.NewArray(vmParams), aer.Events[0].Item)
|
||||
})
|
||||
}
|
||||
checkSingleType := func(t *testing.T, method string, targetSCEventType smartcontract.ParamType, expectedVMType stackitem.Type, noEventsCheck ...bool) {
|
||||
count := 7
|
||||
scParams := make([]smartcontract.ParamType, count)
|
||||
vmParams := make([]stackitem.Type, count)
|
||||
for i := range scParams {
|
||||
scParams[i] = targetSCEventType
|
||||
vmParams[i] = expectedVMType
|
||||
}
|
||||
var noEvents bool
|
||||
if len(noEventsCheck) > 0 {
|
||||
noEvents = noEventsCheck[0]
|
||||
}
|
||||
check(t, method, scParams, vmParams, noEvents)
|
||||
}
|
||||
|
||||
t.Run("good, single type, default values", func(t *testing.T) {
|
||||
checkSingleType(t, methodWithoutEllipsis, smartcontract.IntegerType, stackitem.IntegerT)
|
||||
})
|
||||
t.Run("good, single type, conversion to BooleanT", func(t *testing.T) {
|
||||
checkSingleType(t, methodWithoutEllipsis, smartcontract.BoolType, stackitem.BooleanT)
|
||||
})
|
||||
t.Run("good, single type, Hash160Type->ByteArray", func(t *testing.T) {
|
||||
checkSingleType(t, methodWithoutEllipsis, smartcontract.Hash160Type, stackitem.ByteArrayT)
|
||||
})
|
||||
t.Run("good, single type, Hash256Type->ByteArray", func(t *testing.T) {
|
||||
checkSingleType(t, methodWithoutEllipsis, smartcontract.Hash256Type, stackitem.ByteArrayT)
|
||||
})
|
||||
t.Run("good, single type, Signature->ByteArray", func(t *testing.T) {
|
||||
checkSingleType(t, methodWithoutEllipsis, smartcontract.SignatureType, stackitem.ByteArrayT)
|
||||
})
|
||||
t.Run("good, single type, String->ByteArray", func(t *testing.T) {
|
||||
checkSingleType(t, methodWithoutEllipsis, smartcontract.StringType, stackitem.ByteArrayT) // Special case, runtime.Notify will convert any Buffer to ByteArray.
|
||||
})
|
||||
t.Run("good, single type, PublicKeyType->ByteArray", func(t *testing.T) {
|
||||
checkSingleType(t, methodWithoutEllipsis, smartcontract.PublicKeyType, stackitem.ByteArrayT)
|
||||
})
|
||||
t.Run("good, single type, AnyType->do not change initial type", func(t *testing.T) {
|
||||
checkSingleType(t, methodWithoutEllipsis, smartcontract.AnyType, stackitem.IntegerT) // Special case, compiler should leave the type "as is" and do not emit conversion code.
|
||||
})
|
||||
// Test for InteropInterface->... is missing, because we don't enforce conversion to stackitem.InteropInterface,
|
||||
// but compiler still checks these notifications against expected manifest.
|
||||
t.Run("good, multiple types, check the conversion order", func(t *testing.T) {
|
||||
check(t, methodWithoutEllipsis, []smartcontract.ParamType{
|
||||
smartcontract.IntegerType,
|
||||
smartcontract.BoolType,
|
||||
smartcontract.ByteArrayType,
|
||||
smartcontract.PublicKeyType,
|
||||
smartcontract.Hash160Type,
|
||||
smartcontract.AnyType, // leave initial type
|
||||
smartcontract.StringType,
|
||||
}, []stackitem.Type{
|
||||
stackitem.IntegerT,
|
||||
stackitem.BooleanT,
|
||||
stackitem.ByteArrayT,
|
||||
stackitem.ByteArrayT,
|
||||
stackitem.ByteArrayT,
|
||||
stackitem.IntegerT, // leave initial type
|
||||
stackitem.ByteArrayT,
|
||||
}, false)
|
||||
})
|
||||
t.Run("with ellipsis, do not emit conversion code", func(t *testing.T) {
|
||||
checkSingleType(t, methodWithEllipsis, smartcontract.IntegerType, stackitem.IntegerT)
|
||||
checkSingleType(t, methodWithEllipsis, smartcontract.BoolType, stackitem.IntegerT)
|
||||
checkSingleType(t, methodWithEllipsis, smartcontract.ByteArrayType, stackitem.IntegerT)
|
||||
})
|
||||
t.Run("no events check => no conversion code", func(t *testing.T) {
|
||||
checkSingleType(t, methodWithoutEllipsis, smartcontract.PublicKeyType, stackitem.IntegerT, true)
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue