Merge pull request #1479 from nspcc-dev/examples/deploy

examples: add _deploy usage examples
This commit is contained in:
Roman Khimov 2020-10-14 12:32:21 +03:00 committed by GitHub
commit 7808ab2dde
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 203 additions and 9 deletions

View file

@ -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 {

View file

@ -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
View 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
View file

@ -0,0 +1,4 @@
hasstorage: true
ispayable: false
supportedstandards: []
events: []

View file

@ -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)

View file

@ -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))
})
}

View file

@ -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))
}

View file

@ -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 {

View file

@ -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
} }

View file

@ -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())

View file

@ -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.

View file

@ -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")