mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-11-29 23:33:37 +00:00
7589733017
And include some node-specific configurations there with backwards compatibility. Note that in the future we'll remove Ledger's fields from the ProtocolConfiguration and it'll be possible to access them in Blockchain directly (not via .Ledger). The other option tried was using two configuration types separately, but that incurs more changes to the codebase, single structure that behaves almost like the old one is better for backwards compatibility. Fixes #2676.
438 lines
15 KiB
Go
438 lines
15 KiB
Go
package contracts
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"testing"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/config"
|
|
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
|
|
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
|
"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/nef"
|
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// TestGenerateHelperContracts generates contract states that are used in tests.
|
|
// See generateOracleContract and generateManagementHelperContracts comments for
|
|
// details.
|
|
func TestGenerateHelperContracts(t *testing.T) {
|
|
const saveState = false
|
|
|
|
generateOracleContract(t, saveState)
|
|
generateManagementHelperContracts(t, saveState)
|
|
|
|
require.False(t, saveState)
|
|
}
|
|
|
|
// generateOracleContract generates a helper contract that is able to call
|
|
// the native Oracle contract and has callback method. It uses testchain to define
|
|
// Oracle and StdLib native hashes and saves the generated NEF and manifest to `oracle_contract` folder.
|
|
// Set `saveState` flag to true and run the test to rewrite NEF and manifest files.
|
|
func generateOracleContract(t *testing.T, saveState bool) {
|
|
ctr := neotest.CompileFile(t, util.Uint160{}, oracleContractModPath, oracleContractYAMLPath)
|
|
|
|
// Write NEF file.
|
|
bytes, err := ctr.NEF.Bytes()
|
|
require.NoError(t, err)
|
|
if saveState {
|
|
err = os.WriteFile(oracleContractNEFPath, bytes, os.ModePerm)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// Write manifest file.
|
|
mData, err := json.Marshal(ctr.Manifest)
|
|
require.NoError(t, err)
|
|
if saveState {
|
|
err = os.WriteFile(oracleContractManifestPath, mData, os.ModePerm)
|
|
require.NoError(t, err)
|
|
}
|
|
}
|
|
|
|
// generateManagementHelperContracts generates 2 helper contracts, second of which is
|
|
// allowed to call the first. It uses testchain to define Management and StdLib
|
|
// native hashes and saves the generated NEF and manifest to `management_contract` folder.
|
|
// Set `saveState` flag to true and run the test to rewrite NEF and manifest files.
|
|
func generateManagementHelperContracts(t *testing.T, saveState bool) {
|
|
bc, validator, committee := chain.NewMultiWithCustomConfig(t, func(c *config.Blockchain) {
|
|
c.P2PSigExtensions = true
|
|
})
|
|
e := neotest.NewExecutor(t, bc, validator, committee)
|
|
|
|
mgmtHash := e.NativeHash(t, nativenames.Management)
|
|
stdHash := e.NativeHash(t, nativenames.StdLib)
|
|
neoHash := e.NativeHash(t, nativenames.Neo)
|
|
singleChainValidatorAcc := e.Validator.(neotest.MultiSigner).Single(2).Account() // priv0
|
|
require.NoError(t, singleChainValidatorAcc.ConvertMultisig(1, keys.PublicKeys{singleChainValidatorAcc.PublicKey()}))
|
|
singleChainValidatorHash := singleChainValidatorAcc.Contract.ScriptHash()
|
|
|
|
w := io.NewBufBinWriter()
|
|
emit.Opcodes(w.BinWriter, opcode.ABORT)
|
|
addOff := w.Len()
|
|
emit.Opcodes(w.BinWriter, opcode.ADD, opcode.RET)
|
|
addMultiOff := w.Len()
|
|
emit.Opcodes(w.BinWriter, opcode.ADD, opcode.ADD, opcode.RET)
|
|
ret7Off := w.Len()
|
|
emit.Opcodes(w.BinWriter, opcode.PUSH7, opcode.RET)
|
|
dropOff := w.Len()
|
|
emit.Opcodes(w.BinWriter, opcode.DROP, opcode.RET)
|
|
initOff := w.Len()
|
|
emit.Opcodes(w.BinWriter, opcode.INITSSLOT, 1, opcode.PUSH3, opcode.STSFLD0, opcode.RET)
|
|
add3Off := w.Len()
|
|
emit.Opcodes(w.BinWriter, opcode.LDSFLD0, opcode.ADD, opcode.RET)
|
|
invalidRetOff := w.Len()
|
|
emit.Opcodes(w.BinWriter, opcode.PUSH1, opcode.PUSH2, opcode.RET)
|
|
justRetOff := w.Len()
|
|
emit.Opcodes(w.BinWriter, opcode.RET)
|
|
verifyOff := w.Len()
|
|
emit.Opcodes(w.BinWriter, opcode.LDSFLD0, opcode.SUB,
|
|
opcode.CONVERT, opcode.Opcode(stackitem.BooleanT), opcode.RET)
|
|
deployOff := w.Len()
|
|
emit.Opcodes(w.BinWriter, opcode.SWAP, opcode.JMPIF, 2+8+1+1+1+1+39+3)
|
|
emit.String(w.BinWriter, "create") // 8 bytes
|
|
emit.Int(w.BinWriter, 2) // 1 byte
|
|
emit.Opcodes(w.BinWriter, opcode.PACK) // 1 byte
|
|
emit.Int(w.BinWriter, 1) // 1 byte (args count for `serialize`)
|
|
emit.Opcodes(w.BinWriter, opcode.PACK) // 1 byte (pack args into array for `serialize`)
|
|
emit.AppCallNoArgs(w.BinWriter, stdHash, "serialize", callflag.All) // 39 bytes
|
|
emit.Opcodes(w.BinWriter, opcode.CALL, 3+8+1+1+1+1+39+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.Int(w.BinWriter, 1) // 1 byte (args count for `serialize`)
|
|
emit.Opcodes(w.BinWriter, opcode.PACK) // 1 byte (pack args into array for `serialize`)
|
|
emit.AppCallNoArgs(w.BinWriter, stdHash, "serialize", callflag.All) // 39 bytes
|
|
emit.Opcodes(w.BinWriter, opcode.CALL, 3, opcode.RET)
|
|
putValOff := w.Len()
|
|
emit.String(w.BinWriter, "initial")
|
|
emit.Syscall(w.BinWriter, interopnames.SystemStorageGetContext)
|
|
emit.Syscall(w.BinWriter, interopnames.SystemStoragePut)
|
|
emit.Opcodes(w.BinWriter, opcode.RET)
|
|
getValOff := w.Len()
|
|
emit.String(w.BinWriter, "initial")
|
|
emit.Syscall(w.BinWriter, interopnames.SystemStorageGetContext)
|
|
emit.Syscall(w.BinWriter, interopnames.SystemStorageGet)
|
|
emit.Opcodes(w.BinWriter, opcode.RET)
|
|
delValOff := w.Len()
|
|
emit.Syscall(w.BinWriter, interopnames.SystemStorageGetContext)
|
|
emit.Syscall(w.BinWriter, interopnames.SystemStorageDelete)
|
|
emit.Opcodes(w.BinWriter, opcode.RET)
|
|
onNEP17PaymentOff := w.Len()
|
|
emit.Syscall(w.BinWriter, interopnames.SystemRuntimeGetCallingScriptHash)
|
|
emit.Int(w.BinWriter, 4)
|
|
emit.Opcodes(w.BinWriter, opcode.PACK)
|
|
emit.String(w.BinWriter, "LastPayment")
|
|
emit.Syscall(w.BinWriter, interopnames.SystemRuntimeNotify)
|
|
emit.Opcodes(w.BinWriter, opcode.RET)
|
|
onNEP11PaymentOff := w.Len()
|
|
emit.Syscall(w.BinWriter, interopnames.SystemRuntimeGetCallingScriptHash)
|
|
emit.Int(w.BinWriter, 5)
|
|
emit.Opcodes(w.BinWriter, opcode.PACK)
|
|
emit.String(w.BinWriter, "LostPayment")
|
|
emit.Syscall(w.BinWriter, interopnames.SystemRuntimeNotify)
|
|
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()
|
|
emit.Int(w.BinWriter, 2)
|
|
emit.Opcodes(w.BinWriter, opcode.PACK)
|
|
emit.AppCallNoArgs(w.BinWriter, mgmtHash, "update", callflag.All)
|
|
emit.Opcodes(w.BinWriter, opcode.DROP)
|
|
emit.Opcodes(w.BinWriter, opcode.RET)
|
|
destroyOff := w.Len()
|
|
emit.AppCall(w.BinWriter, mgmtHash, "destroy", callflag.All)
|
|
emit.Opcodes(w.BinWriter, opcode.DROP)
|
|
emit.Opcodes(w.BinWriter, opcode.RET)
|
|
invalidStack1Off := w.Len()
|
|
emit.Opcodes(w.BinWriter, opcode.NEWARRAY0, opcode.DUP, opcode.DUP, opcode.APPEND) // recursive array
|
|
emit.Opcodes(w.BinWriter, opcode.RET)
|
|
invalidStack2Off := w.Len()
|
|
emit.Syscall(w.BinWriter, interopnames.SystemStorageGetReadOnlyContext) // interop item
|
|
emit.Opcodes(w.BinWriter, opcode.RET)
|
|
callT0Off := w.Len()
|
|
emit.Opcodes(w.BinWriter, opcode.CALLT, 0, 0, opcode.PUSH1, opcode.ADD, opcode.RET)
|
|
callT1Off := w.Len()
|
|
emit.Opcodes(w.BinWriter, opcode.CALLT, 1, 0, opcode.RET)
|
|
callT2Off := w.Len()
|
|
emit.Opcodes(w.BinWriter, opcode.CALLT, 0, 0, opcode.RET)
|
|
burnGasOff := w.Len()
|
|
emit.Syscall(w.BinWriter, interopnames.SystemRuntimeBurnGas)
|
|
emit.Opcodes(w.BinWriter, opcode.RET)
|
|
invocCounterOff := w.Len()
|
|
emit.Syscall(w.BinWriter, interopnames.SystemRuntimeGetInvocationCounter)
|
|
emit.Opcodes(w.BinWriter, opcode.RET)
|
|
|
|
script := w.Bytes()
|
|
m := manifest.NewManifest("TestMain")
|
|
m.ABI.Methods = []manifest.Method{
|
|
{
|
|
Name: "add",
|
|
Offset: addOff,
|
|
Parameters: []manifest.Parameter{
|
|
manifest.NewParameter("addend1", smartcontract.IntegerType),
|
|
manifest.NewParameter("addend2", smartcontract.IntegerType),
|
|
},
|
|
ReturnType: smartcontract.IntegerType,
|
|
},
|
|
{
|
|
Name: "add",
|
|
Offset: addMultiOff,
|
|
Parameters: []manifest.Parameter{
|
|
manifest.NewParameter("addend1", smartcontract.IntegerType),
|
|
manifest.NewParameter("addend2", smartcontract.IntegerType),
|
|
manifest.NewParameter("addend3", smartcontract.IntegerType),
|
|
},
|
|
ReturnType: smartcontract.IntegerType,
|
|
},
|
|
{
|
|
Name: "ret7",
|
|
Offset: ret7Off,
|
|
Parameters: []manifest.Parameter{},
|
|
ReturnType: smartcontract.IntegerType,
|
|
},
|
|
{
|
|
Name: "drop",
|
|
Offset: dropOff,
|
|
ReturnType: smartcontract.VoidType,
|
|
},
|
|
{
|
|
Name: manifest.MethodInit,
|
|
Offset: initOff,
|
|
ReturnType: smartcontract.VoidType,
|
|
},
|
|
{
|
|
Name: "add3",
|
|
Offset: add3Off,
|
|
Parameters: []manifest.Parameter{
|
|
manifest.NewParameter("addend", smartcontract.IntegerType),
|
|
},
|
|
ReturnType: smartcontract.IntegerType,
|
|
},
|
|
{
|
|
Name: "invalidReturn",
|
|
Offset: invalidRetOff,
|
|
ReturnType: smartcontract.IntegerType,
|
|
},
|
|
{
|
|
Name: "justReturn",
|
|
Offset: justRetOff,
|
|
ReturnType: smartcontract.VoidType,
|
|
},
|
|
{
|
|
Name: manifest.MethodVerify,
|
|
Offset: verifyOff,
|
|
ReturnType: smartcontract.BoolType,
|
|
},
|
|
{
|
|
Name: manifest.MethodDeploy,
|
|
Offset: deployOff,
|
|
Parameters: []manifest.Parameter{
|
|
manifest.NewParameter("data", smartcontract.AnyType),
|
|
manifest.NewParameter("isUpdate", smartcontract.BoolType),
|
|
},
|
|
ReturnType: smartcontract.VoidType,
|
|
},
|
|
{
|
|
Name: "getValue",
|
|
Offset: getValOff,
|
|
ReturnType: smartcontract.StringType,
|
|
},
|
|
{
|
|
Name: "putValue",
|
|
Offset: putValOff,
|
|
Parameters: []manifest.Parameter{
|
|
manifest.NewParameter("value", smartcontract.StringType),
|
|
},
|
|
ReturnType: smartcontract.VoidType,
|
|
},
|
|
{
|
|
Name: "delValue",
|
|
Offset: delValOff,
|
|
Parameters: []manifest.Parameter{
|
|
manifest.NewParameter("key", smartcontract.StringType),
|
|
},
|
|
ReturnType: smartcontract.VoidType,
|
|
},
|
|
{
|
|
Name: manifest.MethodOnNEP11Payment,
|
|
Offset: onNEP11PaymentOff,
|
|
Parameters: []manifest.Parameter{
|
|
manifest.NewParameter("from", smartcontract.Hash160Type),
|
|
manifest.NewParameter("amount", smartcontract.IntegerType),
|
|
manifest.NewParameter("tokenid", smartcontract.ByteArrayType),
|
|
manifest.NewParameter("data", smartcontract.AnyType),
|
|
},
|
|
ReturnType: smartcontract.VoidType,
|
|
},
|
|
{
|
|
Name: manifest.MethodOnNEP17Payment,
|
|
Offset: onNEP17PaymentOff,
|
|
Parameters: []manifest.Parameter{
|
|
manifest.NewParameter("from", smartcontract.Hash160Type),
|
|
manifest.NewParameter("amount", smartcontract.IntegerType),
|
|
manifest.NewParameter("data", smartcontract.AnyType),
|
|
},
|
|
ReturnType: smartcontract.VoidType,
|
|
},
|
|
{
|
|
Name: "update",
|
|
Offset: updateOff,
|
|
Parameters: []manifest.Parameter{
|
|
manifest.NewParameter("nef", smartcontract.ByteArrayType),
|
|
manifest.NewParameter("manifest", smartcontract.ByteArrayType),
|
|
},
|
|
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",
|
|
Offset: destroyOff,
|
|
ReturnType: smartcontract.VoidType,
|
|
},
|
|
{
|
|
Name: "invalidStack1",
|
|
Offset: invalidStack1Off,
|
|
ReturnType: smartcontract.AnyType,
|
|
},
|
|
{
|
|
Name: "invalidStack2",
|
|
Offset: invalidStack2Off,
|
|
ReturnType: smartcontract.AnyType,
|
|
},
|
|
{
|
|
Name: "callT0",
|
|
Offset: callT0Off,
|
|
Parameters: []manifest.Parameter{
|
|
manifest.NewParameter("address", smartcontract.Hash160Type),
|
|
},
|
|
ReturnType: smartcontract.IntegerType,
|
|
},
|
|
{
|
|
Name: "callT1",
|
|
Offset: callT1Off,
|
|
ReturnType: smartcontract.IntegerType,
|
|
},
|
|
{
|
|
Name: "callT2",
|
|
Offset: callT2Off,
|
|
ReturnType: smartcontract.IntegerType,
|
|
},
|
|
{
|
|
Name: "burnGas",
|
|
Offset: burnGasOff,
|
|
Parameters: []manifest.Parameter{
|
|
manifest.NewParameter("amount", smartcontract.IntegerType),
|
|
},
|
|
ReturnType: smartcontract.VoidType,
|
|
},
|
|
{
|
|
Name: "invocCounter",
|
|
Offset: invocCounterOff,
|
|
ReturnType: smartcontract.IntegerType,
|
|
},
|
|
}
|
|
m.Permissions = make([]manifest.Permission, 2)
|
|
m.Permissions[0].Contract.Type = manifest.PermissionHash
|
|
m.Permissions[0].Contract.Value = neoHash
|
|
m.Permissions[0].Methods.Add("balanceOf")
|
|
|
|
m.Permissions[1].Contract.Type = manifest.PermissionHash
|
|
m.Permissions[1].Contract.Value = util.Uint160{}
|
|
m.Permissions[1].Methods.Add("method")
|
|
|
|
// Generate NEF file.
|
|
ne, err := nef.NewFile(script)
|
|
require.NoError(t, err)
|
|
ne.Tokens = []nef.MethodToken{
|
|
{
|
|
Hash: neoHash,
|
|
Method: "balanceOf",
|
|
ParamCount: 1,
|
|
HasReturn: true,
|
|
CallFlag: callflag.ReadStates,
|
|
},
|
|
{
|
|
Hash: util.Uint160{},
|
|
Method: "method",
|
|
HasReturn: true,
|
|
CallFlag: callflag.ReadStates,
|
|
},
|
|
}
|
|
ne.Checksum = ne.CalculateChecksum()
|
|
|
|
// Write first NEF file.
|
|
bytes, err := ne.Bytes()
|
|
require.NoError(t, err)
|
|
if saveState {
|
|
err = os.WriteFile(helper1ContractNEFPath, bytes, os.ModePerm)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// Write first manifest file.
|
|
mData, err := json.Marshal(m)
|
|
require.NoError(t, err)
|
|
if saveState {
|
|
err = os.WriteFile(helper1ContractManifestPath, mData, os.ModePerm)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// Create hash of the first contract assuming that sender is single-chain validator.
|
|
h := state.CreateContractHash(singleChainValidatorHash, ne.Checksum, m.Name)
|
|
|
|
currScript := []byte{byte(opcode.RET)}
|
|
m = manifest.NewManifest("TestAux")
|
|
m.ABI.Methods = []manifest.Method{
|
|
{
|
|
Name: "simpleMethod",
|
|
Offset: 0,
|
|
ReturnType: smartcontract.VoidType,
|
|
},
|
|
}
|
|
perm := manifest.NewPermission(manifest.PermissionHash, h)
|
|
perm.Methods.Add("add")
|
|
perm.Methods.Add("drop")
|
|
perm.Methods.Add("add3")
|
|
perm.Methods.Add("invalidReturn")
|
|
perm.Methods.Add("justReturn")
|
|
perm.Methods.Add("getValue")
|
|
m.Permissions = append(m.Permissions, *perm)
|
|
ne, err = nef.NewFile(currScript)
|
|
require.NoError(t, err)
|
|
|
|
// Write second NEF file.
|
|
bytes, err = ne.Bytes()
|
|
require.NoError(t, err)
|
|
if saveState {
|
|
err = os.WriteFile(helper2ContractNEFPath, bytes, os.ModePerm)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// Write second manifest file.
|
|
mData, err = json.Marshal(m)
|
|
require.NoError(t, err)
|
|
if saveState {
|
|
err = os.WriteFile(helper2ContractManifestPath, mData, os.ModePerm)
|
|
require.NoError(t, err)
|
|
}
|
|
}
|