forked from TrueCloudLab/neoneo-go
Merge pull request #1479 from nspcc-dev/examples/deploy
examples: add _deploy usage examples
This commit is contained in:
commit
7808ab2dde
12 changed files with 203 additions and 9 deletions
|
@ -55,6 +55,10 @@ var (
|
||||||
Name: "out",
|
Name: "out",
|
||||||
Usage: "file to put JSON transaction to",
|
Usage: "file to put JSON transaction to",
|
||||||
}
|
}
|
||||||
|
forceFlag = cli.StringFlag{
|
||||||
|
Name: "force",
|
||||||
|
Usage: "force-push the transaction in case of bad VM state after test script invocation",
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -110,6 +114,7 @@ func NewCommands() []cli.Command {
|
||||||
addressFlag,
|
addressFlag,
|
||||||
gasFlag,
|
gasFlag,
|
||||||
outFlag,
|
outFlag,
|
||||||
|
forceFlag,
|
||||||
}
|
}
|
||||||
invokeFunctionFlags = append(invokeFunctionFlags, options.RPC...)
|
invokeFunctionFlags = append(invokeFunctionFlags, options.RPC...)
|
||||||
return []cli.Command{{
|
return []cli.Command{{
|
||||||
|
@ -487,6 +492,13 @@ func invokeInternal(ctx *cli.Context, signAndPush bool) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return cli.NewExitError(err, 1)
|
return cli.NewExitError(err, 1)
|
||||||
}
|
}
|
||||||
|
if signAndPush && resp.State != "HALT" {
|
||||||
|
errText := fmt.Sprintf("Warning: %s VM state returned from the RPC node: %s\n", resp.State, resp.FaultException)
|
||||||
|
if ctx.String("force") == "" {
|
||||||
|
return cli.NewExitError(errText+". Use --force flag to send the transaction anyway.", 1)
|
||||||
|
}
|
||||||
|
fmt.Fprintln(ctx.App.Writer, errText+". Sending transaction...")
|
||||||
|
}
|
||||||
if out := ctx.String("out"); out != "" {
|
if out := ctx.String("out"); out != "" {
|
||||||
script, err := hex.DecodeString(resp.Script)
|
script, err := hex.DecodeString(resp.Script)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -16,6 +16,14 @@ func init() {
|
||||||
trigger = runtime.GetTrigger()
|
trigger = runtime.GetTrigger()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _deploy(isUpdate bool) {
|
||||||
|
if isUpdate {
|
||||||
|
Log("_deploy method called before contract update")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
Log("_deploy method called before contract creation")
|
||||||
|
}
|
||||||
|
|
||||||
// CheckWitness checks owner's witness
|
// CheckWitness checks owner's witness
|
||||||
func CheckWitness() bool {
|
func CheckWitness() bool {
|
||||||
// Log owner upon Verification trigger
|
// Log owner upon Verification trigger
|
||||||
|
|
96
examples/timer/timer.go
Normal file
96
examples/timer/timer.go
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
package timer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/engine"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
const defaultTicks = 3
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ctx holds storage context for contract methods
|
||||||
|
ctx storage.Context
|
||||||
|
// Check if the invoker of the contract is the specified owner
|
||||||
|
owner = util.FromAddress("NULwe3UAHckN2fzNdcVg31tDiaYtMDwANt")
|
||||||
|
// ticksKey is a storage key for ticks counter
|
||||||
|
ticksKey = []byte("ticks")
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
ctx = storage.GetContext()
|
||||||
|
}
|
||||||
|
|
||||||
|
func _deploy(isUpdate bool) {
|
||||||
|
if isUpdate {
|
||||||
|
ticksLeft := storage.Get(ctx, ticksKey).(int) + 1
|
||||||
|
storage.Put(ctx, ticksKey, ticksLeft)
|
||||||
|
runtime.Log("One more tick is added.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
storage.Put(ctx, ticksKey, defaultTicks)
|
||||||
|
runtime.Log("Timer set to " + itoa(defaultTicks) + " ticks.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Migrate migrates the contract.
|
||||||
|
func Migrate(script []byte, manifest []byte) bool {
|
||||||
|
if !runtime.CheckWitness(owner) {
|
||||||
|
runtime.Log("Only owner is allowed to update the contract.")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
contract.Update(script, manifest)
|
||||||
|
runtime.Log("Contract updated.")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tick decrement ticks count and checks whether the timer is fired.
|
||||||
|
func Tick() bool {
|
||||||
|
runtime.Log("Tick-tock.")
|
||||||
|
ticksLeft := storage.Get(ctx, ticksKey)
|
||||||
|
ticksLeft = ticksLeft.(int) - 1
|
||||||
|
if ticksLeft == 0 {
|
||||||
|
runtime.Log("Fired!")
|
||||||
|
return engine.AppCall(runtime.GetExecutingScriptHash(), "selfDestroy").(bool)
|
||||||
|
}
|
||||||
|
storage.Put(ctx, ticksKey, ticksLeft)
|
||||||
|
runtime.Log(itoa(ticksLeft.(int)) + " ticks left.")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// SelfDestroy destroys the contract.
|
||||||
|
func SelfDestroy() bool {
|
||||||
|
if !(runtime.CheckWitness(owner) || runtime.CheckWitness(runtime.GetExecutingScriptHash())) {
|
||||||
|
runtime.Log("Only owner or the contract itself are allowed to destroy the contract.")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
contract.Destroy()
|
||||||
|
runtime.Log("Destroyed.")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// itoa converts int to string
|
||||||
|
func itoa(i int) string {
|
||||||
|
digits := "0123456789"
|
||||||
|
var (
|
||||||
|
res string
|
||||||
|
isNegative bool
|
||||||
|
)
|
||||||
|
if i < 0 {
|
||||||
|
i = -i
|
||||||
|
isNegative = true
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
r := i % 10
|
||||||
|
res = digits[r:r+1] + res
|
||||||
|
i = i / 10
|
||||||
|
if i == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if isNegative {
|
||||||
|
res = "-" + res
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
4
examples/timer/timer.yml
Normal file
4
examples/timer/timer.yml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
hasstorage: true
|
||||||
|
ispayable: false
|
||||||
|
supportedstandards: []
|
||||||
|
events: []
|
|
@ -105,6 +105,11 @@ func (c *funcScope) analyzeVoidCalls(node ast.Node) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case *ast.TypeAssertExpr:
|
||||||
|
ce, ok := n.X.(*ast.CallExpr)
|
||||||
|
if ok {
|
||||||
|
c.voidCalls[ce] = false
|
||||||
|
}
|
||||||
case *ast.BinaryExpr:
|
case *ast.BinaryExpr:
|
||||||
return false
|
return false
|
||||||
case *ast.RangeStmt:
|
case *ast.RangeStmt:
|
||||||
|
@ -112,6 +117,11 @@ func (c *funcScope) analyzeVoidCalls(node ast.Node) bool {
|
||||||
if ok {
|
if ok {
|
||||||
c.voidCalls[ce] = false
|
c.voidCalls[ce] = false
|
||||||
}
|
}
|
||||||
|
case *ast.UnaryExpr:
|
||||||
|
ce, ok := n.X.(*ast.CallExpr)
|
||||||
|
if ok {
|
||||||
|
c.voidCalls[ce] = false
|
||||||
|
}
|
||||||
case *ast.IfStmt:
|
case *ast.IfStmt:
|
||||||
// we can't just return `false`, because we still need to process body
|
// we can't just return `false`, because we still need to process body
|
||||||
ce, ok := n.Cond.(*ast.CallExpr)
|
ce, ok := n.Cond.(*ast.CallExpr)
|
||||||
|
|
|
@ -121,3 +121,32 @@ func TestInitIF(t *testing.T) {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCallExpIF(t *testing.T) {
|
||||||
|
t.Run("Call", func(t *testing.T) {
|
||||||
|
src := `package foo
|
||||||
|
func someFunc() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
func Main() int {
|
||||||
|
if someFunc() {
|
||||||
|
return 5
|
||||||
|
}
|
||||||
|
return 6
|
||||||
|
}`
|
||||||
|
eval(t, src, big.NewInt(5))
|
||||||
|
})
|
||||||
|
t.Run("CallWithUnaryExpression", func(t *testing.T) {
|
||||||
|
src := `package foo
|
||||||
|
func someFunc() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
func Main() int {
|
||||||
|
if !someFunc() {
|
||||||
|
return 5
|
||||||
|
}
|
||||||
|
return 6
|
||||||
|
}`
|
||||||
|
eval(t, src, big.NewInt(5))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -123,3 +123,18 @@ func TestNamedReturn(t *testing.T) {
|
||||||
t.Run("EmptyReturn", runCase("", big.NewInt(1), big.NewInt(2)))
|
t.Run("EmptyReturn", runCase("", big.NewInt(1), big.NewInt(2)))
|
||||||
t.Run("AnotherVariable", runCase("b, c", big.NewInt(2), big.NewInt(3)))
|
t.Run("AnotherVariable", runCase("b, c", big.NewInt(2), big.NewInt(3)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTypeAssertReturn(t *testing.T) {
|
||||||
|
src := `
|
||||||
|
package main
|
||||||
|
|
||||||
|
func foo() interface{} {
|
||||||
|
return 5
|
||||||
|
}
|
||||||
|
|
||||||
|
func Main() int {
|
||||||
|
return foo().(int)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
eval(t, src, big.NewInt(5))
|
||||||
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -51,12 +52,12 @@ func callExInternal(ic *interop.Context, h []byte, name string, args []stackitem
|
||||||
return errors.New("disallowed method call")
|
return errors.New("disallowed method call")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return CallExInternal(ic, cs, name, args, f)
|
return CallExInternal(ic, cs, name, args, f, vm.EnsureNotEmpty)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CallExInternal calls a contract with flags and can't be invoked directly by user.
|
// CallExInternal calls a contract with flags and can't be invoked directly by user.
|
||||||
func CallExInternal(ic *interop.Context, cs *state.Contract,
|
func CallExInternal(ic *interop.Context, cs *state.Contract,
|
||||||
name string, args []stackitem.Item, f smartcontract.CallFlag) error {
|
name string, args []stackitem.Item, f smartcontract.CallFlag, checkReturn vm.CheckReturnState) error {
|
||||||
md := cs.Manifest.ABI.GetMethod(name)
|
md := cs.Manifest.ABI.GetMethod(name)
|
||||||
if md == nil {
|
if md == nil {
|
||||||
return fmt.Errorf("method '%s' not found", name)
|
return fmt.Errorf("method '%s' not found", name)
|
||||||
|
@ -86,7 +87,7 @@ func CallExInternal(ic *interop.Context, cs *state.Contract,
|
||||||
// use Jump not Call here because context was loaded in LoadScript above.
|
// use Jump not Call here because context was loaded in LoadScript above.
|
||||||
ic.VM.Jump(ic.VM.Context(), md.Offset)
|
ic.VM.Jump(ic.VM.Context(), md.Offset)
|
||||||
}
|
}
|
||||||
ic.VM.Context().CheckReturn = true
|
ic.VM.Context().CheckReturn = checkReturn
|
||||||
|
|
||||||
md = cs.Manifest.ABI.GetMethod(manifest.MethodInit)
|
md = cs.Manifest.ABI.GetMethod(manifest.MethodInit)
|
||||||
if md != nil {
|
if md != nil {
|
||||||
|
|
|
@ -203,7 +203,7 @@ func callDeploy(ic *interop.Context, cs *state.Contract, isUpdate bool) error {
|
||||||
md := cs.Manifest.ABI.GetMethod(manifest.MethodDeploy)
|
md := cs.Manifest.ABI.GetMethod(manifest.MethodDeploy)
|
||||||
if md != nil {
|
if md != nil {
|
||||||
return contract.CallExInternal(ic, cs, manifest.MethodDeploy,
|
return contract.CallExInternal(ic, cs, manifest.MethodDeploy,
|
||||||
[]stackitem.Item{stackitem.NewBool(isUpdate)}, smartcontract.All)
|
[]stackitem.Item{stackitem.NewBool(isUpdate)}, smartcontract.All, vm.EnsureIsEmpty)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -912,7 +912,7 @@ func TestContractCreateDeploy(t *testing.T) {
|
||||||
require.NoError(t, ic.VM.Run())
|
require.NoError(t, ic.VM.Run())
|
||||||
|
|
||||||
v.LoadScriptWithFlags(currCs.Script, smartcontract.All)
|
v.LoadScriptWithFlags(currCs.Script, smartcontract.All)
|
||||||
err := contract.CallExInternal(ic, cs, "getValue", nil, smartcontract.All)
|
err := contract.CallExInternal(ic, cs, "getValue", nil, smartcontract.All, vm.EnsureNotEmpty)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NoError(t, v.Run())
|
require.NoError(t, v.Run())
|
||||||
require.Equal(t, "create", v.Estack().Pop().String())
|
require.Equal(t, "create", v.Estack().Pop().String())
|
||||||
|
@ -933,7 +933,7 @@ func TestContractCreateDeploy(t *testing.T) {
|
||||||
require.NoError(t, v.Run())
|
require.NoError(t, v.Run())
|
||||||
|
|
||||||
v.LoadScriptWithFlags(currCs.Script, smartcontract.All)
|
v.LoadScriptWithFlags(currCs.Script, smartcontract.All)
|
||||||
err = contract.CallExInternal(ic, newCs, "getValue", nil, smartcontract.All)
|
err = contract.CallExInternal(ic, newCs, "getValue", nil, smartcontract.All, vm.EnsureNotEmpty)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.NoError(t, v.Run())
|
require.NoError(t, v.Run())
|
||||||
require.Equal(t, "update", v.Estack().Pop().String())
|
require.Equal(t, "update", v.Estack().Pop().String())
|
||||||
|
|
|
@ -46,9 +46,22 @@ type Context struct {
|
||||||
callFlag smartcontract.CallFlag
|
callFlag smartcontract.CallFlag
|
||||||
|
|
||||||
// CheckReturn specifies if amount of return values needs to be checked.
|
// CheckReturn specifies if amount of return values needs to be checked.
|
||||||
CheckReturn bool
|
CheckReturn CheckReturnState
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CheckReturnState represents possible states of stack after opcode.RET was processed.
|
||||||
|
type CheckReturnState byte
|
||||||
|
|
||||||
|
const (
|
||||||
|
// NoCheck performs no return values check.
|
||||||
|
NoCheck CheckReturnState = 0
|
||||||
|
// EnsureIsEmpty checks that stack is empty and panics if not.
|
||||||
|
EnsureIsEmpty CheckReturnState = 1
|
||||||
|
// EnsureNotEmpty checks that stack contains not more than 1 element and panics if not.
|
||||||
|
// It pushes stackitem.Null on stack in case if there's no elements.
|
||||||
|
EnsureNotEmpty CheckReturnState = 2
|
||||||
|
)
|
||||||
|
|
||||||
var errNoInstParam = errors.New("failed to read instruction parameter")
|
var errNoInstParam = errors.New("failed to read instruction parameter")
|
||||||
|
|
||||||
// NewContext returns a new Context object.
|
// NewContext returns a new Context object.
|
||||||
|
|
10
pkg/vm/vm.go
10
pkg/vm/vm.go
|
@ -1409,7 +1409,13 @@ func (v *VM) unloadContext(ctx *Context) {
|
||||||
if ctx.static != nil && currCtx != nil && ctx.static != currCtx.static {
|
if ctx.static != nil && currCtx != nil && ctx.static != currCtx.static {
|
||||||
ctx.static.Clear()
|
ctx.static.Clear()
|
||||||
}
|
}
|
||||||
if ctx.CheckReturn {
|
switch ctx.CheckReturn {
|
||||||
|
case NoCheck:
|
||||||
|
case EnsureIsEmpty:
|
||||||
|
if currCtx != nil && ctx.estack.len != 0 {
|
||||||
|
panic("return value amount is > 0")
|
||||||
|
}
|
||||||
|
case EnsureNotEmpty:
|
||||||
if currCtx != nil && ctx.estack.len == 0 {
|
if currCtx != nil && ctx.estack.len == 0 {
|
||||||
currCtx.estack.PushVal(stackitem.Null{})
|
currCtx.estack.PushVal(stackitem.Null{})
|
||||||
} else if ctx.estack.len > 1 {
|
} else if ctx.estack.len > 1 {
|
||||||
|
@ -1471,7 +1477,7 @@ func (v *VM) Call(ctx *Context, offset int) {
|
||||||
// package.
|
// package.
|
||||||
func (v *VM) call(ctx *Context, offset int) {
|
func (v *VM) call(ctx *Context, offset int) {
|
||||||
newCtx := ctx.Copy()
|
newCtx := ctx.Copy()
|
||||||
newCtx.CheckReturn = false
|
newCtx.CheckReturn = NoCheck
|
||||||
newCtx.local = nil
|
newCtx.local = nil
|
||||||
newCtx.arguments = nil
|
newCtx.arguments = nil
|
||||||
newCtx.tryStack = NewStack("exception")
|
newCtx.tryStack = NewStack("exception")
|
||||||
|
|
Loading…
Reference in a new issue