native: add additional parameters to deploy

1. Management contract has 2 overloads of `deploy` method.
2. Normal contracts should have `_deploy` with 2 parameters.
This commit is contained in:
Evgeniy Stratonikov 2021-01-28 16:31:50 +03:00
parent 849385a533
commit c1cc7e6f9d
10 changed files with 149 additions and 32 deletions

View file

@ -13,7 +13,7 @@ var key = "key"
const mgmtKey = "mgmt" const mgmtKey = "mgmt"
func _deploy(isUpdate bool) { func _deploy(data interface{}, isUpdate bool) {
var value string var value string
ctx := storage.GetContext() ctx := storage.GetContext()

View file

@ -4,7 +4,7 @@ import "github.com/nspcc-dev/neo-go/pkg/interop/storage"
var Key = "sub" var Key = "sub"
func _deploy(isUpdate bool) { func _deploy(data interface{}, isUpdate bool) {
ctx := storage.GetContext() ctx := storage.GetContext()
value := "sub create" value := "sub create"
if isUpdate { if isUpdate {

View file

@ -16,7 +16,7 @@ func init() {
trigger = runtime.GetTrigger() trigger = runtime.GetTrigger()
} }
func _deploy(isUpdate bool) { func _deploy(_ interface{}, isUpdate bool) {
if isUpdate { if isUpdate {
Log("_deploy method called before contract update") Log("_deploy method called before contract update")
return return

View file

@ -25,7 +25,7 @@ func init() {
ctx = storage.GetContext() ctx = storage.GetContext()
} }
func _deploy(isUpdate bool) { func _deploy(_ interface{}, isUpdate bool) {
if isUpdate { if isUpdate {
ticksLeft := storage.Get(ctx, ticksKey).(int) + 1 ticksLeft := storage.Get(ctx, ticksKey).(int) + 1
storage.Put(ctx, ticksKey, ticksLeft) storage.Put(ctx, ticksKey, ticksLeft)

View file

@ -336,11 +336,11 @@ func (c *codegen) convertInitFuncs(f *ast.File, pkg *types.Package, seenBefore b
func isDeployFunc(decl *ast.FuncDecl) bool { func isDeployFunc(decl *ast.FuncDecl) bool {
if decl.Name.Name != "_deploy" || decl.Recv != nil || if decl.Name.Name != "_deploy" || decl.Recv != nil ||
decl.Type.Params.NumFields() != 1 || decl.Type.Params.NumFields() != 2 ||
decl.Type.Results.NumFields() != 0 { decl.Type.Results.NumFields() != 0 {
return false return false
} }
typ, ok := decl.Type.Params.List[0].Type.(*ast.Ident) typ, ok := decl.Type.Params.List[1].Type.(*ast.Ident)
return ok && typ.Name == "bool" return ok && typ.Name == "bool"
} }
@ -1869,7 +1869,7 @@ func (c *codegen) compile(info *buildInfo, pkg *loader.PackageInfo) error {
hasDeploy := deployLocals > -1 hasDeploy := deployLocals > -1
if hasDeploy { if hasDeploy {
emit.Instruction(c.prog.BinWriter, opcode.INITSLOT, []byte{byte(deployLocals), 1}) emit.Instruction(c.prog.BinWriter, opcode.INITSLOT, []byte{byte(deployLocals), 2})
c.convertDeployFuncs() c.convertDeployFuncs()
c.deployEndOffset = c.prog.Len() c.deployEndOffset = c.prog.Len()
emit.Opcodes(c.prog.BinWriter, opcode.RET) emit.Opcodes(c.prog.BinWriter, opcode.RET)

View file

@ -150,11 +150,18 @@ func (c *codegen) emitDebugInfo(contract []byte) *DebugInfo {
Start: uint16(c.initEndOffset + 1), Start: uint16(c.initEndOffset + 1),
End: uint16(c.deployEndOffset), End: uint16(c.deployEndOffset),
}, },
Parameters: []DebugParam{{ Parameters: []DebugParam{
Name: "isUpdate", {
Type: "Boolean", Name: "data",
TypeSC: smartcontract.BoolType, Type: "Any",
}}, TypeSC: smartcontract.AnyType,
},
{
Name: "isUpdate",
Type: "Boolean",
TypeSC: smartcontract.BoolType,
},
},
ReturnType: "Void", ReturnType: "Void",
ReturnTypeSC: smartcontract.VoidType, ReturnTypeSC: smartcontract.VoidType,
SeqPoints: c.sequencePoints[manifest.MethodDeploy], SeqPoints: c.sequencePoints[manifest.MethodDeploy],

View file

@ -53,7 +53,7 @@ func MethodParams(addr interop.Hash160, h interop.Hash256,
type MyStruct struct {} type MyStruct struct {}
func (ms MyStruct) MethodOnStruct() { } func (ms MyStruct) MethodOnStruct() { }
func (ms *MyStruct) MethodOnPointerToStruct() { } func (ms *MyStruct) MethodOnPointerToStruct() { }
func _deploy(isUpdate bool) {} func _deploy(data interface{}, isUpdate bool) {}
` `
info, err := getBuildInfo("foo.go", src) info, err := getBuildInfo("foo.go", src)
@ -100,11 +100,18 @@ func _deploy(isUpdate bool) {}
t.Run("param types", func(t *testing.T) { t.Run("param types", func(t *testing.T) {
paramTypes := map[string][]DebugParam{ paramTypes := map[string][]DebugParam{
"_deploy": {{ "_deploy": {
Name: "isUpdate", {
Type: "Boolean", Name: "data",
TypeSC: smartcontract.BoolType, Type: "Any",
}}, TypeSC: smartcontract.AnyType,
},
{
Name: "isUpdate",
Type: "Boolean",
TypeSC: smartcontract.BoolType,
},
},
"MethodInt": {{ "MethodInt": {{
Name: "a", Name: "a",
Type: "ByteString", Type: "ByteString",
@ -160,6 +167,7 @@ func _deploy(isUpdate bool) {}
Name: "_deploy", Name: "_deploy",
Offset: 0, Offset: 0,
Parameters: []manifest.Parameter{ Parameters: []manifest.Parameter{
manifest.NewParameter("data", smartcontract.AnyType),
manifest.NewParameter("isUpdate", smartcontract.BoolType), manifest.NewParameter("isUpdate", smartcontract.BoolType),
}, },
ReturnType: smartcontract.VoidType, ReturnType: smartcontract.VoidType,

View file

@ -479,10 +479,16 @@ func getTestContractState(bc *Blockchain) (*state.Contract, *state.Contract) {
emit.Opcodes(w.BinWriter, opcode.LDSFLD0, opcode.SUB, emit.Opcodes(w.BinWriter, opcode.LDSFLD0, opcode.SUB,
opcode.CONVERT, opcode.Opcode(stackitem.BooleanT), opcode.RET) opcode.CONVERT, opcode.Opcode(stackitem.BooleanT), opcode.RET)
deployOff := w.Len() deployOff := w.Len()
emit.Opcodes(w.BinWriter, opcode.JMPIF, 2+8+3) emit.Opcodes(w.BinWriter, opcode.SWAP, opcode.JMPIF, 2+8+1+1+5+3)
emit.String(w.BinWriter, "create") emit.String(w.BinWriter, "create") // 8 bytes
emit.Opcodes(w.BinWriter, opcode.CALL, 3+8+3, opcode.RET) emit.Int(w.BinWriter, 2) // 1 byte
emit.String(w.BinWriter, "update") emit.Opcodes(w.BinWriter, opcode.PACK) // 1 byte
emit.Syscall(w.BinWriter, interopnames.SystemBinarySerialize) // 5 bytes
emit.Opcodes(w.BinWriter, opcode.CALL, 3+8+1+1+5+3, opcode.RET)
emit.String(w.BinWriter, "update") // 8 bytes
emit.Int(w.BinWriter, 2) // 1 byte
emit.Opcodes(w.BinWriter, opcode.PACK) // 1 byte
emit.Syscall(w.BinWriter, interopnames.SystemBinarySerialize) // 5 bytes
emit.Opcodes(w.BinWriter, opcode.CALL, 3, opcode.RET) emit.Opcodes(w.BinWriter, opcode.CALL, 3, opcode.RET)
putValOff := w.Len() putValOff := w.Len()
emit.String(w.BinWriter, "initial") emit.String(w.BinWriter, "initial")
@ -501,6 +507,9 @@ func getTestContractState(bc *Blockchain) (*state.Contract, *state.Contract) {
emit.String(w.BinWriter, "LastPayment") emit.String(w.BinWriter, "LastPayment")
emit.Syscall(w.BinWriter, interopnames.SystemRuntimeNotify) emit.Syscall(w.BinWriter, interopnames.SystemRuntimeNotify)
emit.Opcodes(w.BinWriter, opcode.RET) emit.Opcodes(w.BinWriter, opcode.RET)
update3Off := w.Len()
emit.Int(w.BinWriter, 3)
emit.Opcodes(w.BinWriter, opcode.JMP, 2+1)
updateOff := w.Len() updateOff := w.Len()
emit.Int(w.BinWriter, 2) emit.Int(w.BinWriter, 2)
emit.Opcodes(w.BinWriter, opcode.PACK) emit.Opcodes(w.BinWriter, opcode.PACK)
@ -588,6 +597,7 @@ func getTestContractState(bc *Blockchain) (*state.Contract, *state.Contract) {
Name: manifest.MethodDeploy, Name: manifest.MethodDeploy,
Offset: deployOff, Offset: deployOff,
Parameters: []manifest.Parameter{ Parameters: []manifest.Parameter{
manifest.NewParameter("data", smartcontract.AnyType),
manifest.NewParameter("isUpdate", smartcontract.BoolType), manifest.NewParameter("isUpdate", smartcontract.BoolType),
}, },
ReturnType: smartcontract.VoidType, ReturnType: smartcontract.VoidType,
@ -624,6 +634,16 @@ func getTestContractState(bc *Blockchain) (*state.Contract, *state.Contract) {
}, },
ReturnType: smartcontract.VoidType, ReturnType: smartcontract.VoidType,
}, },
{
Name: "update",
Offset: update3Off,
Parameters: []manifest.Parameter{
manifest.NewParameter("nef", smartcontract.ByteArrayType),
manifest.NewParameter("manifest", smartcontract.ByteArrayType),
manifest.NewParameter("data", smartcontract.AnyType),
},
ReturnType: smartcontract.VoidType,
},
{ {
Name: "destroy", Name: "destroy",
Offset: destroyOff, Offset: destroyOff,

View file

@ -77,12 +77,26 @@ func newManagement() *Management {
md = newMethodAndPrice(m.deploy, 0, callflag.WriteStates|callflag.AllowNotify) md = newMethodAndPrice(m.deploy, 0, callflag.WriteStates|callflag.AllowNotify)
m.AddMethod(md, desc) m.AddMethod(md, desc)
desc = newDescriptor("deploy", smartcontract.ArrayType,
manifest.NewParameter("script", smartcontract.ByteArrayType),
manifest.NewParameter("manifest", smartcontract.ByteArrayType),
manifest.NewParameter("data", smartcontract.AnyType))
md = newMethodAndPrice(m.deployWithData, 0, callflag.WriteStates|callflag.AllowNotify)
m.AddMethod(md, desc)
desc = newDescriptor("update", smartcontract.VoidType, desc = newDescriptor("update", smartcontract.VoidType,
manifest.NewParameter("script", smartcontract.ByteArrayType), manifest.NewParameter("script", smartcontract.ByteArrayType),
manifest.NewParameter("manifest", smartcontract.ByteArrayType)) manifest.NewParameter("manifest", smartcontract.ByteArrayType))
md = newMethodAndPrice(m.update, 0, callflag.WriteStates|callflag.AllowNotify) md = newMethodAndPrice(m.update, 0, callflag.WriteStates|callflag.AllowNotify)
m.AddMethod(md, desc) m.AddMethod(md, desc)
desc = newDescriptor("update", smartcontract.VoidType,
manifest.NewParameter("script", smartcontract.ByteArrayType),
manifest.NewParameter("manifest", smartcontract.ByteArrayType),
manifest.NewParameter("data", smartcontract.AnyType))
md = newMethodAndPrice(m.updateWithData, 0, callflag.WriteStates|callflag.AllowNotify)
m.AddMethod(md, desc)
desc = newDescriptor("destroy", smartcontract.VoidType) desc = newDescriptor("destroy", smartcontract.VoidType)
md = newMethodAndPrice(m.destroy, 1000000, callflag.WriteStates|callflag.AllowNotify) md = newMethodAndPrice(m.destroy, 1000000, callflag.WriteStates|callflag.AllowNotify)
m.AddMethod(md, desc) m.AddMethod(md, desc)
@ -204,9 +218,14 @@ func (m *Management) getNefAndManifestFromItems(ic *interop.Context, args []stac
return resNef, resManifest, nil return resNef, resManifest, nil
} }
// deploy is an implementation of public deploy method, it's run under // deploy is an implementation of public 2-argument deploy method.
// VM protections, so it's OK for it to panic instead of returning errors.
func (m *Management) deploy(ic *interop.Context, args []stackitem.Item) stackitem.Item { func (m *Management) deploy(ic *interop.Context, args []stackitem.Item) stackitem.Item {
return m.deployWithData(ic, append(args, stackitem.Null{}))
}
// deployWithData is an implementation of public 3-argument deploy method.
// It's run under VM protections, so it's OK for it to panic instead of returning errors.
func (m *Management) deployWithData(ic *interop.Context, args []stackitem.Item) stackitem.Item {
neff, manif, err := m.getNefAndManifestFromItems(ic, args, true) neff, manif, err := m.getNefAndManifestFromItems(ic, args, true)
if err != nil { if err != nil {
panic(err) panic(err)
@ -224,7 +243,7 @@ func (m *Management) deploy(ic *interop.Context, args []stackitem.Item) stackite
if err != nil { if err != nil {
panic(err) panic(err)
} }
m.callDeploy(ic, newcontract, false) m.callDeploy(ic, newcontract, args[2], false)
m.emitNotification(ic, contractDeployNotificationName, newcontract.Hash) m.emitNotification(ic, contractDeployNotificationName, newcontract.Hash)
return contractToStack(newcontract) return contractToStack(newcontract)
} }
@ -266,9 +285,13 @@ func (m *Management) Deploy(d dao.DAO, sender util.Uint160, neff *nef.File, mani
return newcontract, nil return newcontract, nil
} }
func (m *Management) update(ic *interop.Context, args []stackitem.Item) stackitem.Item {
return m.updateWithData(ic, append(args, stackitem.Null{}))
}
// update is an implementation of public update method, it's run under // update is an implementation of public update method, it's run under
// VM protections, so it's OK for it to panic instead of returning errors. // VM protections, so it's OK for it to panic instead of returning errors.
func (m *Management) update(ic *interop.Context, args []stackitem.Item) stackitem.Item { func (m *Management) updateWithData(ic *interop.Context, args []stackitem.Item) stackitem.Item {
neff, manif, err := m.getNefAndManifestFromItems(ic, args, false) neff, manif, err := m.getNefAndManifestFromItems(ic, args, false)
if err != nil { if err != nil {
panic(err) panic(err)
@ -280,7 +303,7 @@ func (m *Management) update(ic *interop.Context, args []stackitem.Item) stackite
if err != nil { if err != nil {
panic(err) panic(err)
} }
m.callDeploy(ic, contract, true) m.callDeploy(ic, contract, args[2], true)
m.emitNotification(ic, contractUpdateNotificationName, contract.Hash) m.emitNotification(ic, contractUpdateNotificationName, contract.Hash)
return stackitem.Null{} return stackitem.Null{}
} }
@ -378,11 +401,11 @@ func (m *Management) setMinimumDeploymentFee(ic *interop.Context, args []stackit
return stackitem.Null{} return stackitem.Null{}
} }
func (m *Management) callDeploy(ic *interop.Context, cs *state.Contract, isUpdate bool) { func (m *Management) callDeploy(ic *interop.Context, cs *state.Contract, data stackitem.Item, isUpdate bool) {
md := cs.Manifest.ABI.GetMethod(manifest.MethodDeploy, 1) md := cs.Manifest.ABI.GetMethod(manifest.MethodDeploy, 2)
if md != nil { if md != nil {
err := contract.CallFromNative(ic, m.Hash, cs, manifest.MethodDeploy, err := contract.CallFromNative(ic, m.Hash, cs, manifest.MethodDeploy,
[]stackitem.Item{stackitem.NewBool(isUpdate)}, false) []stackitem.Item{data, stackitem.NewBool(isUpdate)}, false)
if err != nil { if err != nil {
panic(err) panic(err)
} }

View file

@ -71,6 +71,57 @@ func TestStartFromHeight(t *testing.T) {
checkContractState(t, bc2, cs1.Hash, cs1) checkContractState(t, bc2, cs1.Hash, cs1)
} }
func TestContractDeployAndUpdateWithParameter(t *testing.T) {
bc := newTestChain(t)
defer bc.Close()
// nef.NewFile() cares about version a lot.
config.Version = "0.90.0-test"
mgmtHash := bc.ManagementContractHash()
cs1, _ := getTestContractState(bc)
cs1.Manifest.Permissions = []manifest.Permission{*manifest.NewPermission(manifest.PermissionWildcard)}
cs1.ID = 1
cs1.Hash = state.CreateContractHash(testchain.MultisigScriptHash(), cs1.NEF.Checksum, cs1.Manifest.Name)
manif1, err := json.Marshal(cs1.Manifest)
require.NoError(t, err)
nef1b, err := cs1.NEF.Bytes()
require.NoError(t, err)
aer, err := invokeContractMethod(bc, 11_00000000, mgmtHash, "deploy", nef1b, manif1, int64(42))
require.NoError(t, err)
require.Equal(t, vm.HaltState, aer.VMState)
t.Run("_deploy called", func(t *testing.T) {
res, err := invokeContractMethod(bc, 1_00000000, cs1.Hash, "getValue")
require.NoError(t, err)
require.Equal(t, 1, len(res.Stack))
item, err := stackitem.DeserializeItem(res.Stack[0].Value().([]byte))
require.NoError(t, err)
expected := []stackitem.Item{stackitem.Make("create"), stackitem.Make(42)}
require.Equal(t, stackitem.NewArray(expected), item)
})
cs1.NEF.Script = append(cs1.NEF.Script, byte(opcode.RET))
cs1.NEF.Checksum = cs1.NEF.CalculateChecksum()
nef1b, err = cs1.NEF.Bytes()
require.NoError(t, err)
cs1.UpdateCounter++
aer, err = invokeContractMethod(bc, 10_00000000, cs1.Hash, "update", nef1b, nil, "new data")
require.NoError(t, err)
require.Equal(t, vm.HaltState, aer.VMState)
t.Run("_deploy called", func(t *testing.T) {
res, err := invokeContractMethod(bc, 1_00000000, cs1.Hash, "getValue")
require.NoError(t, err)
require.Equal(t, 1, len(res.Stack))
item, err := stackitem.DeserializeItem(res.Stack[0].Value().([]byte))
require.NoError(t, err)
expected := []stackitem.Item{stackitem.Make("update"), stackitem.Make("new data")}
require.Equal(t, stackitem.NewArray(expected), item)
})
}
func TestContractDeploy(t *testing.T) { func TestContractDeploy(t *testing.T) {
bc := newTestChain(t) bc := newTestChain(t)
defer bc.Close() defer bc.Close()
@ -166,7 +217,10 @@ func TestContractDeploy(t *testing.T) {
res, err := invokeContractMethod(bc, 1_00000000, cs1.Hash, "getValue") res, err := invokeContractMethod(bc, 1_00000000, cs1.Hash, "getValue")
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 1, len(res.Stack)) require.Equal(t, 1, len(res.Stack))
require.Equal(t, []byte("create"), res.Stack[0].Value()) item, err := stackitem.DeserializeItem(res.Stack[0].Value().([]byte))
require.NoError(t, err)
expected := []stackitem.Item{stackitem.Make("create"), stackitem.Null{}}
require.Equal(t, stackitem.NewArray(expected), item)
}) })
t.Run("get after deploy", func(t *testing.T) { t.Run("get after deploy", func(t *testing.T) {
checkContractState(t, bc, cs1.Hash, cs1) checkContractState(t, bc, cs1.Hash, cs1)
@ -198,6 +252,7 @@ func TestContractDeploy(t *testing.T) {
Name: manifest.MethodDeploy, Name: manifest.MethodDeploy,
Offset: 0, Offset: 0,
Parameters: []manifest.Parameter{ Parameters: []manifest.Parameter{
manifest.NewParameter("data", smartcontract.AnyType),
manifest.NewParameter("isUpdate", smartcontract.BoolType), manifest.NewParameter("isUpdate", smartcontract.BoolType),
}, },
ReturnType: smartcontract.VoidType, ReturnType: smartcontract.VoidType,
@ -226,6 +281,7 @@ func TestContractDeploy(t *testing.T) {
Name: manifest.MethodDeploy, Name: manifest.MethodDeploy,
Offset: 0, Offset: 0,
Parameters: []manifest.Parameter{ Parameters: []manifest.Parameter{
manifest.NewParameter("data", smartcontract.AnyType),
manifest.NewParameter("isUpdate", smartcontract.BoolType), manifest.NewParameter("isUpdate", smartcontract.BoolType),
}, },
ReturnType: smartcontract.ArrayType, ReturnType: smartcontract.ArrayType,
@ -346,7 +402,10 @@ func TestContractUpdate(t *testing.T) {
res, err := invokeContractMethod(bc, 1_00000000, cs1.Hash, "getValue") res, err := invokeContractMethod(bc, 1_00000000, cs1.Hash, "getValue")
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, 1, len(res.Stack)) require.Equal(t, 1, len(res.Stack))
require.Equal(t, []byte("update"), res.Stack[0].Value()) item, err := stackitem.DeserializeItem(res.Stack[0].Value().([]byte))
require.NoError(t, err)
expected := []stackitem.Item{stackitem.Make("update"), stackitem.Null{}}
require.Equal(t, stackitem.NewArray(expected), item)
}) })
t.Run("check contract", func(t *testing.T) { t.Run("check contract", func(t *testing.T) {
checkContractState(t, bc, cs1.Hash, cs1) checkContractState(t, bc, cs1.Hash, cs1)