forked from TrueCloudLab/neoneo-go
commit
509cdec981
10 changed files with 80 additions and 77 deletions
|
@ -21,8 +21,10 @@ var (
|
||||||
helper2ContractNEFPath = filepath.Join("management_helper", "management_helper2.nef")
|
helper2ContractNEFPath = filepath.Join("management_helper", "management_helper2.nef")
|
||||||
helper2ContractManifestPath = filepath.Join("management_helper", "management_helper2.manifest.json")
|
helper2ContractManifestPath = filepath.Join("management_helper", "management_helper2.manifest.json")
|
||||||
|
|
||||||
oracleContractNEFPath = filepath.Join("oracle_contract", "oracle.nef")
|
oracleContractModPath = "oracle_contract"
|
||||||
oracleContractManifestPath = filepath.Join("oracle_contract", "oracle.manifest.json")
|
oracleContractYAMLPath = filepath.Join(oracleContractModPath, "oracle.yml")
|
||||||
|
oracleContractNEFPath = filepath.Join(oracleContractModPath, "oracle.nef")
|
||||||
|
oracleContractManifestPath = filepath.Join(oracleContractModPath, "oracle.manifest.json")
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetTestContractState reads 2 pre-compiled contracts generated by
|
// GetTestContractState reads 2 pre-compiled contracts generated by
|
||||||
|
|
|
@ -41,81 +41,10 @@ func TestGenerateHelperContracts(t *testing.T) {
|
||||||
// Oracle and StdLib native hashes and saves the generated NEF and manifest to `oracle_contract` folder.
|
// 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.
|
// Set `saveState` flag to true and run the test to rewrite NEF and manifest files.
|
||||||
func generateOracleContract(t *testing.T, saveState bool) {
|
func generateOracleContract(t *testing.T, saveState bool) {
|
||||||
bc, validator, committee := chain.NewMultiWithCustomConfig(t, func(c *config.ProtocolConfiguration) {
|
ctr := neotest.CompileFile(t, util.Uint160{}, oracleContractModPath, oracleContractYAMLPath)
|
||||||
c.P2PSigExtensions = true
|
|
||||||
})
|
|
||||||
e := neotest.NewExecutor(t, bc, validator, committee)
|
|
||||||
|
|
||||||
oracleHash := e.NativeHash(t, nativenames.Oracle)
|
|
||||||
stdHash := e.NativeHash(t, nativenames.StdLib)
|
|
||||||
|
|
||||||
w := io.NewBufBinWriter()
|
|
||||||
emit.Int(w.BinWriter, 5)
|
|
||||||
emit.Opcodes(w.BinWriter, opcode.PACK)
|
|
||||||
emit.Int(w.BinWriter, int64(callflag.All))
|
|
||||||
emit.String(w.BinWriter, "request")
|
|
||||||
emit.Bytes(w.BinWriter, oracleHash.BytesBE())
|
|
||||||
emit.Syscall(w.BinWriter, interopnames.SystemContractCall)
|
|
||||||
emit.Opcodes(w.BinWriter, opcode.DROP)
|
|
||||||
emit.Opcodes(w.BinWriter, opcode.RET)
|
|
||||||
|
|
||||||
// `handle` method aborts if len(userData) == 2 and does NOT perform witness checks
|
|
||||||
// for the sake of contract code simplicity (the contract is used in multiple testchains).
|
|
||||||
offset := w.Len()
|
|
||||||
|
|
||||||
emit.Opcodes(w.BinWriter, opcode.OVER)
|
|
||||||
emit.Opcodes(w.BinWriter, opcode.SIZE)
|
|
||||||
emit.Int(w.BinWriter, 2)
|
|
||||||
emit.Instruction(w.BinWriter, opcode.JMPNE, []byte{3})
|
|
||||||
emit.Opcodes(w.BinWriter, opcode.ABORT)
|
|
||||||
emit.Int(w.BinWriter, 4) // url, userData, code, result
|
|
||||||
emit.Opcodes(w.BinWriter, opcode.PACK)
|
|
||||||
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.String(w.BinWriter, "lastOracleResponse")
|
|
||||||
emit.Syscall(w.BinWriter, interopnames.SystemStorageGetContext)
|
|
||||||
emit.Syscall(w.BinWriter, interopnames.SystemStoragePut)
|
|
||||||
emit.Opcodes(w.BinWriter, opcode.RET)
|
|
||||||
|
|
||||||
m := manifest.NewManifest("TestOracle")
|
|
||||||
m.ABI.Methods = []manifest.Method{
|
|
||||||
{
|
|
||||||
Name: "requestURL",
|
|
||||||
Offset: 0,
|
|
||||||
Parameters: []manifest.Parameter{
|
|
||||||
manifest.NewParameter("url", smartcontract.StringType),
|
|
||||||
manifest.NewParameter("filter", smartcontract.StringType),
|
|
||||||
manifest.NewParameter("callback", smartcontract.StringType),
|
|
||||||
manifest.NewParameter("userData", smartcontract.AnyType),
|
|
||||||
manifest.NewParameter("gasForResponse", smartcontract.IntegerType),
|
|
||||||
},
|
|
||||||
ReturnType: smartcontract.VoidType,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "handle",
|
|
||||||
Offset: offset,
|
|
||||||
Parameters: []manifest.Parameter{
|
|
||||||
manifest.NewParameter("url", smartcontract.StringType),
|
|
||||||
manifest.NewParameter("userData", smartcontract.AnyType),
|
|
||||||
manifest.NewParameter("code", smartcontract.IntegerType),
|
|
||||||
manifest.NewParameter("result", smartcontract.ByteArrayType),
|
|
||||||
},
|
|
||||||
ReturnType: smartcontract.VoidType,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
perm := manifest.NewPermission(manifest.PermissionHash, oracleHash)
|
|
||||||
perm.Methods.Add("request")
|
|
||||||
m.Permissions = append(m.Permissions, *perm)
|
|
||||||
|
|
||||||
// Generate NEF file.
|
|
||||||
script := w.Bytes()
|
|
||||||
ne, err := nef.NewFile(script)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
// Write NEF file.
|
// Write NEF file.
|
||||||
bytes, err := ne.Bytes()
|
bytes, err := ctr.NEF.Bytes()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
if saveState {
|
if saveState {
|
||||||
err = os.WriteFile(oracleContractNEFPath, bytes, os.ModePerm)
|
err = os.WriteFile(oracleContractNEFPath, bytes, os.ModePerm)
|
||||||
|
@ -123,7 +52,7 @@ func generateOracleContract(t *testing.T, saveState bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write manifest file.
|
// Write manifest file.
|
||||||
mData, err := json.Marshal(m)
|
mData, err := json.Marshal(ctr.Manifest)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
if saveState {
|
if saveState {
|
||||||
err = os.WriteFile(oracleContractManifestPath, mData, os.ModePerm)
|
err = os.WriteFile(oracleContractManifestPath, mData, os.ModePerm)
|
||||||
|
|
5
internal/contracts/oracle_contract/go.mod
Normal file
5
internal/contracts/oracle_contract/go.mod
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
module github.com/nspcc-dev/neo-go/examples/oracle
|
||||||
|
|
||||||
|
go 1.17
|
||||||
|
|
||||||
|
require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20220809123759-3094d3e0c14b
|
2
internal/contracts/oracle_contract/go.sum
Normal file
2
internal/contracts/oracle_contract/go.sum
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20220809123759-3094d3e0c14b h1:J7QZNmnO84esVuPbBo88fwAG4XVnDjlSTiO1ewLNCkQ=
|
||||||
|
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20220809123759-3094d3e0c14b/go.mod h1:23bBw0v6pBYcrWs8CBEEDIEDJNbcFoIh8pGGcf2Vv8s=
|
42
internal/contracts/oracle_contract/oracle.go
Normal file
42
internal/contracts/oracle_contract/oracle.go
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
package oraclecontract
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/native/oracle"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/native/std"
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RequestURL accepts a complete set of parameters to make an oracle request and
|
||||||
|
// performs it.
|
||||||
|
func RequestURL(url string, filter []byte, callback string, userData interface{}, gasForResponse int) {
|
||||||
|
oracle.Request(url, filter, callback, userData, gasForResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle is a response handler that writes response data to the storage.
|
||||||
|
func Handle(url string, data interface{}, code int, res []byte) {
|
||||||
|
// ABORT if len(data) == 2, some tests use this feature.
|
||||||
|
if data != nil && len(data.(string)) == 2 {
|
||||||
|
util.Abort()
|
||||||
|
}
|
||||||
|
params := []interface{}{url, data, code, res}
|
||||||
|
storage.Put(storage.GetContext(), "lastOracleResponse", std.Serialize(params))
|
||||||
|
}
|
||||||
|
|
||||||
|
// HandleRecursive invokes oracle.finish again to test Oracle reentrance.
|
||||||
|
func HandleRecursive(url string, data interface{}, code int, res []byte) {
|
||||||
|
// Regular safety check.
|
||||||
|
callingHash := runtime.GetCallingScriptHash()
|
||||||
|
if !callingHash.Equals(oracle.Hash) {
|
||||||
|
panic("not called from oracle contract")
|
||||||
|
}
|
||||||
|
|
||||||
|
runtime.Notify("Invocation")
|
||||||
|
if runtime.GetInvocationCounter() == 1 {
|
||||||
|
// We provide no wrapper for finish in interops, it's not usually needed.
|
||||||
|
contract.Call(interop.Hash160(oracle.Hash), "finish", contract.All)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1 +1 @@
|
||||||
{"name":"TestOracle","abi":{"methods":[{"name":"requestURL","offset":0,"parameters":[{"name":"url","type":"String"},{"name":"filter","type":"String"},{"name":"callback","type":"String"},{"name":"userData","type":"Any"},{"name":"gasForResponse","type":"Integer"}],"returntype":"Void","safe":false},{"name":"handle","offset":41,"parameters":[{"name":"url","type":"String"},{"name":"userData","type":"Any"},{"name":"code","type":"Integer"},{"name":"result","type":"ByteArray"}],"returntype":"Void","safe":false}],"events":[]},"features":{},"groups":[],"permissions":[{"contract":"0xfe924b7cfe89ddd271abaf7210a80a7e11178758","methods":["request"]}],"supportedstandards":[],"trusts":[],"extra":null}
|
{"name":"Oracle test","abi":{"methods":[{"name":"handle","offset":14,"parameters":[{"name":"url","type":"String"},{"name":"data","type":"Any"},{"name":"code","type":"Integer"},{"name":"res","type":"ByteArray"}],"returntype":"Void","safe":false},{"name":"handleRecursive","offset":89,"parameters":[{"name":"url","type":"String"},{"name":"data","type":"Any"},{"name":"code","type":"Integer"},{"name":"res","type":"ByteArray"}],"returntype":"Void","safe":false},{"name":"requestURL","offset":0,"parameters":[{"name":"url","type":"String"},{"name":"filter","type":"ByteArray"},{"name":"callback","type":"String"},{"name":"userData","type":"Any"},{"name":"gasForResponse","type":"Integer"}],"returntype":"Void","safe":false}],"events":[{"name":"Invocation","parameters":null}]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":["request","finish"]}],"supportedstandards":[],"trusts":[],"extra":null}
|
Binary file not shown.
7
internal/contracts/oracle_contract/oracle.yml
Normal file
7
internal/contracts/oracle_contract/oracle.yml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
name: "Oracle test"
|
||||||
|
sourceurl: https://github.com/nspcc-dev/neo-go/
|
||||||
|
supportedstandards: []
|
||||||
|
events:
|
||||||
|
- name: Invocation
|
||||||
|
permissions:
|
||||||
|
- methods: ["request", "finish"]
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
"github.com/nspcc-dev/neo-go/pkg/neotest"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
@ -151,6 +152,15 @@ func TestOracle_Request(t *testing.T) {
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
require.True(t, strings.Contains(err.Error(), "oracle tx points to invalid request"))
|
require.True(t, strings.Contains(err.Error(), "oracle tx points to invalid request"))
|
||||||
})
|
})
|
||||||
|
t.Run("Reentrant", func(t *testing.T) {
|
||||||
|
putOracleRequest(t, helperValidatorInvoker, "url", nil, "handleRecursive", []byte{}, gasForResponse)
|
||||||
|
tx := prepareResponseTx(t, 2)
|
||||||
|
e.AddNewBlock(t, tx)
|
||||||
|
e.CheckFault(t, tx.Hash(), "Oracle.finish called from non-entry script")
|
||||||
|
aer, err := e.Chain.GetAppExecResults(tx.Hash(), trigger.Application)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 2, len(aer[0].Events)) // OracleResponse + Invocation
|
||||||
|
})
|
||||||
t.Run("BadRequest", func(t *testing.T) {
|
t.Run("BadRequest", func(t *testing.T) {
|
||||||
t.Run("non-UTF8 url", func(t *testing.T) {
|
t.Run("non-UTF8 url", func(t *testing.T) {
|
||||||
putOracleRequest(t, helperValidatorInvoker, "\xff", nil, "", []byte{1, 2}, gasForResponse, "invalid value: not UTF-8")
|
putOracleRequest(t, helperValidatorInvoker, "\xff", nil, "", []byte{1, 2}, gasForResponse, "invalid value: not UTF-8")
|
||||||
|
|
|
@ -276,6 +276,12 @@ func (o *Oracle) finish(ic *interop.Context, _ []stackitem.Item) stackitem.Item
|
||||||
|
|
||||||
// FinishInternal processes an oracle response.
|
// FinishInternal processes an oracle response.
|
||||||
func (o *Oracle) FinishInternal(ic *interop.Context) error {
|
func (o *Oracle) FinishInternal(ic *interop.Context) error {
|
||||||
|
if ic.VM.Istack().Len() != 2 {
|
||||||
|
return errors.New("Oracle.finish called from non-entry script")
|
||||||
|
}
|
||||||
|
if ic.Invocations[o.Hash] != 1 {
|
||||||
|
return errors.New("Oracle.finish called multiple times")
|
||||||
|
}
|
||||||
resp := getResponse(ic.Tx)
|
resp := getResponse(ic.Tx)
|
||||||
if resp == nil {
|
if resp == nil {
|
||||||
return ErrResponseNotFound
|
return ErrResponseNotFound
|
||||||
|
|
Loading…
Reference in a new issue