nativetest: migrate Oracle contract tests to neotest

This commit is contained in:
Anna Shaleva 2021-12-10 19:10:43 +03:00
parent 0e1f85b2bf
commit df6a7d4258
6 changed files with 366 additions and 266 deletions

View file

@ -0,0 +1,205 @@
package native_test
import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"math"
"math/big"
"path/filepath"
"strings"
"testing"
"github.com/nspcc-dev/neo-go/pkg/core/native"
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"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/io"
"github.com/nspcc-dev/neo-go/pkg/neotest"
"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/stackitem"
"github.com/stretchr/testify/require"
)
func newOracleClient(t *testing.T) *neotest.ContractInvoker {
return newNativeClient(t, nativenames.Oracle)
}
func TestGetSetPrice(t *testing.T) {
testGetSet(t, newOracleClient(t), "Price", native.DefaultOracleRequestPrice, 1, math.MaxInt64)
}
// getOracleContractState reads pre-compiled oracle contract generated by
// TestGenerateOracleContract and returns its state.
func getOracleContractState(t *testing.T, sender util.Uint160, id int32) *state.Contract {
var (
oracleContractNEFPath = filepath.Join("..", "..", "test_data", "oracle_contract", "oracle.nef")
oracleContractManifestPath = filepath.Join("..", "..", "test_data", "oracle_contract", "oracle.manifest.json")
)
errNotFound := errors.New("auto-generated oracle contract is not found, use TestGenerateOracleContract to regenerate")
neBytes, err := ioutil.ReadFile(oracleContractNEFPath)
require.NoError(t, err, fmt.Errorf("nef: %w", errNotFound))
ne, err := nef.FileFromBytes(neBytes)
require.NoError(t, err)
mBytes, err := ioutil.ReadFile(oracleContractManifestPath)
require.NoError(t, err, fmt.Errorf("manifest: %w", errNotFound))
m := &manifest.Manifest{}
err = json.Unmarshal(mBytes, m)
require.NoError(t, err)
return &state.Contract{
ContractBase: state.ContractBase{
NEF: ne,
Hash: state.CreateContractHash(sender, ne.Checksum, m.Name),
Manifest: *m,
ID: id,
},
}
}
func putOracleRequest(t *testing.T, oracleInvoker *neotest.ContractInvoker,
url string, filter *string, cb string, userData []byte, gas int64, errStr ...string) {
var filtItem interface{}
if filter != nil {
filtItem = *filter
}
if len(errStr) == 0 {
oracleInvoker.Invoke(t, stackitem.Null{}, "requestURL", url, filtItem, cb, userData, gas)
return
}
oracleInvoker.InvokeFail(t, errStr[0], "requestURL", url, filtItem, cb, userData, gas)
}
func TestOracle_Request(t *testing.T) {
oracleCommitteeInvoker := newOracleClient(t)
e := oracleCommitteeInvoker.Executor
managementCommitteeInvoker := e.CommitteeInvoker(e.NativeHash(t, nativenames.Management))
designationCommitteeInvoker := e.CommitteeInvoker(e.NativeHash(t, nativenames.Designation))
gasCommitteeInvoker := e.CommitteeInvoker(e.NativeHash(t, nativenames.Gas))
cs := getOracleContractState(t, e.Validator.ScriptHash(), 1)
nBytes, err := cs.NEF.Bytes()
require.NoError(t, err)
mBytes, err := json.Marshal(cs.Manifest)
require.NoError(t, err)
expected, err := cs.ToStackItem()
require.NoError(t, err)
managementCommitteeInvoker.Invoke(t, expected, "deploy", nBytes, mBytes)
helperValidatorInvoker := e.ValidatorInvoker(cs.Hash)
gasForResponse := int64(2000_1234)
var filter = "flt"
userData := []byte("custom info")
putOracleRequest(t, helperValidatorInvoker, "url", &filter, "handle", userData, gasForResponse)
// Designate single Oracle node.
oracleNode := e.NewAccount(t)
designationCommitteeInvoker.Invoke(t, stackitem.Null{}, "designateAsRole", int(noderoles.Oracle), []interface{}{oracleNode.(neotest.SingleSigner).Account().PrivateKey().PublicKey().Bytes()})
err = oracleNode.(neotest.SingleSigner).Account().ConvertMultisig(1, []*keys.PublicKey{oracleNode.(neotest.SingleSigner).Account().PrivateKey().PublicKey()})
require.NoError(t, err)
oracleNodeMulti := neotest.NewMultiSigner(oracleNode.(neotest.SingleSigner).Account())
gasCommitteeInvoker.Invoke(t, true, "transfer", gasCommitteeInvoker.CommitteeHash, oracleNodeMulti.ScriptHash(), 100_0000_0000, nil)
// Finish.
prepareResponseTx := func(t *testing.T, requestID uint64) *transaction.Transaction {
w := io.NewBufBinWriter()
emit.AppCall(w.BinWriter, oracleCommitteeInvoker.Hash, "finish", callflag.All)
require.NoError(t, w.Err)
script := w.Bytes()
tx := transaction.New(script, 1000_0000)
tx.Nonce = neotest.Nonce()
tx.ValidUntilBlock = e.Chain.BlockHeight() + 1
tx.Attributes = []transaction.Attribute{{
Type: transaction.OracleResponseT,
Value: &transaction.OracleResponse{
ID: requestID,
Code: transaction.Success,
Result: []byte{4, 8, 15, 16, 23, 42},
},
}}
tx.Signers = []transaction.Signer{
{
Account: oracleNodeMulti.ScriptHash(),
Scopes: transaction.None,
},
{
Account: oracleCommitteeInvoker.Hash,
Scopes: transaction.None,
},
}
tx.NetworkFee = 1000_1234
tx.Scripts = []transaction.Witness{
{
InvocationScript: oracleNodeMulti.SignHashable(uint32(e.Chain.GetConfig().Magic), tx),
VerificationScript: oracleNodeMulti.Script(),
},
{
InvocationScript: []byte{},
VerificationScript: []byte{},
},
}
return tx
}
tx := prepareResponseTx(t, 0)
e.AddNewBlock(t, tx)
e.CheckHalt(t, tx.Hash(), stackitem.Null{})
// Ensure that callback was called.
si := e.Chain.GetStorageItem(cs.ID, []byte("lastOracleResponse"))
require.NotNil(t, si)
actual, err := stackitem.Deserialize(si)
require.NoError(t, err)
require.Equal(t, stackitem.NewArray([]stackitem.Item{
stackitem.NewByteArray([]byte("url")),
stackitem.NewByteArray(userData),
stackitem.NewBigInteger(big.NewInt(int64(tx.Attributes[0].Value.(*transaction.OracleResponse).Code))),
stackitem.NewByteArray(tx.Attributes[0].Value.(*transaction.OracleResponse).Result),
}), actual)
// Check that processed request is removed. We can't access GetRequestInternal directly,
// but adding response to this request should fail due to invalid request error.
tx = prepareResponseTx(t, 0)
err = e.Chain.VerifyTx(tx)
require.Error(t, err)
require.True(t, strings.Contains(err.Error(), "oracle tx points to invalid request"))
t.Run("ErrorOnFinish", func(t *testing.T) {
putOracleRequest(t, helperValidatorInvoker, "url", nil, "handle", []byte{1, 2}, gasForResponse)
tx := prepareResponseTx(t, 1)
e.AddNewBlock(t, tx)
e.CheckFault(t, tx.Hash(), "ABORT")
// Check that processed request is cleaned up even if callback failed. We can't
// access GetRequestInternal directly, but adding response to this request
// should fail due to invalid request error.
tx = prepareResponseTx(t, 1)
err = e.Chain.VerifyTx(tx)
require.Error(t, err)
require.True(t, strings.Contains(err.Error(), "oracle tx points to invalid request"))
})
t.Run("BadRequest", 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")
})
t.Run("non-UTF8 filter", func(t *testing.T) {
var f = "\xff"
putOracleRequest(t, helperValidatorInvoker, "url", &f, "", []byte{1, 2}, gasForResponse, "invalid value: not UTF-8")
})
t.Run("not enough gas", func(t *testing.T) {
putOracleRequest(t, helperValidatorInvoker, "url", nil, "", nil, 1000, "not enough gas for response")
})
t.Run("disallowed callback", func(t *testing.T) {
putOracleRequest(t, helperValidatorInvoker, "url", nil, "_deploy", nil, 1000_0000, "disallowed callback method (starts with '_')")
})
})
}

View file

@ -1,262 +0,0 @@
package core
import (
"errors"
"math"
"math/big"
"testing"
"github.com/nspcc-dev/neo-go/internal/testchain"
"github.com/nspcc-dev/neo-go/pkg/core/block"
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
"github.com/nspcc-dev/neo-go/pkg/core/native"
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/io"
"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/smartcontract/trigger"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm"
"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"
)
// getTestContractState returns test contract which uses oracles.
func getOracleContractState(h util.Uint160, stdHash util.Uint160) *state.Contract {
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, h.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
offset := w.Len()
emit.Bytes(w.BinWriter, neoOwner.BytesBE())
emit.Syscall(w.BinWriter, interopnames.SystemRuntimeCheckWitness)
emit.Instruction(w.BinWriter, opcode.JMPIF, []byte{3})
emit.Opcodes(w.BinWriter, opcode.ABORT)
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, h)
perm.Methods.Add("request")
m.Permissions = append(m.Permissions, *perm)
script := w.Bytes()
ne, err := nef.NewFile(script)
if err != nil {
panic(err)
}
return &state.Contract{
ContractBase: state.ContractBase{
NEF: *ne,
Hash: hash.Hash160(script),
Manifest: *m,
ID: 42,
},
}
}
func putOracleRequest(t *testing.T, h util.Uint160, bc *Blockchain,
url string, filter *string, cb string, userData []byte, gas int64) util.Uint256 {
var filtItem interface{}
if filter != nil {
filtItem = *filter
}
res, err := invokeContractMethod(bc, gas+50_000_000+5_000_000, h, "requestURL",
url, filtItem, cb, userData, gas)
require.NoError(t, err)
return res.Container
}
func TestOracle_Request(t *testing.T) {
bc := newTestChain(t)
orc := bc.contracts.Oracle
cs := getOracleContractState(orc.Hash, bc.contracts.Std.Hash)
require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs))
gasForResponse := int64(2000_1234)
var filter = "flt"
userData := []byte("custom info")
txHash := putOracleRequest(t, cs.Hash, bc, "url", &filter, "handle", userData, gasForResponse)
req, err := orc.GetRequestInternal(bc.dao, 0)
require.NotNil(t, req)
require.NoError(t, err)
require.Equal(t, txHash, req.OriginalTxID)
require.Equal(t, "url", req.URL)
require.Equal(t, filter, *req.Filter)
require.Equal(t, cs.Hash, req.CallbackContract)
require.Equal(t, "handle", req.CallbackMethod)
require.Equal(t, uint64(gasForResponse), req.GasForResponse)
idList, err := orc.GetIDListInternal(bc.dao, "url")
require.NoError(t, err)
require.Equal(t, &native.IDList{0}, idList)
// Finish.
priv, err := keys.NewPrivateKey()
require.NoError(t, err)
pub := priv.PublicKey()
tx := transaction.New([]byte{byte(opcode.PUSH1)}, 0)
bl := block.New(bc.config.StateRootInHeader)
bl.Index = bc.BlockHeight() + 1
setSigner(tx, testchain.CommitteeScriptHash())
ic := bc.newInteropContext(trigger.Application, bc.dao, bl, tx)
ic.SpawnVM()
ic.VM.LoadScript([]byte{byte(opcode.RET)})
err = bc.contracts.Designate.DesignateAsRole(ic, noderoles.Oracle, keys.PublicKeys{pub})
require.NoError(t, err)
tx = transaction.New(orc.GetOracleResponseScript(), 0)
ic.Tx = tx
ic.Block = bc.newBlock(tx)
err = orc.FinishInternal(ic)
require.True(t, errors.Is(err, native.ErrResponseNotFound), "got: %v", err)
resp := &transaction.OracleResponse{
ID: 12,
Code: transaction.Success,
Result: []byte{4, 8, 15, 16, 23, 42},
}
tx.Attributes = []transaction.Attribute{{
Type: transaction.OracleResponseT,
Value: resp,
}}
err = orc.FinishInternal(ic)
require.True(t, errors.Is(err, native.ErrRequestNotFound), "got: %v", err)
// We need to ensure that callback is called thus, executing full script is necessary.
resp.ID = 0
ic.VM.LoadScriptWithFlags(tx.Script, callflag.All)
require.NoError(t, ic.VM.Run())
si := ic.DAO.GetStorageItem(cs.ID, []byte("lastOracleResponse"))
require.NotNil(t, si)
item, err := stackitem.Deserialize(si)
require.NoError(t, err)
arr, ok := item.Value().([]stackitem.Item)
require.True(t, ok)
require.Equal(t, []byte("url"), arr[0].Value())
require.Equal(t, userData, arr[1].Value())
require.Equal(t, big.NewInt(int64(resp.Code)), arr[2].Value())
require.Equal(t, resp.Result, arr[3].Value())
// Check that processed request is removed during `postPersist`.
_, err = orc.GetRequestInternal(ic.DAO, 0)
require.NoError(t, err)
require.NoError(t, orc.PostPersist(ic))
_, err = orc.GetRequestInternal(ic.DAO, 0)
require.Error(t, err)
t.Run("ErrorOnFinish", func(t *testing.T) {
const reqID = 1
putOracleRequest(t, cs.Hash, bc, "url", nil, "handle", []byte{1, 2}, gasForResponse)
_, err := orc.GetRequestInternal(bc.dao, reqID) // ensure ID is 1
require.NoError(t, err)
tx = transaction.New(orc.GetOracleResponseScript(), 0)
tx.Attributes = []transaction.Attribute{{
Type: transaction.OracleResponseT,
Value: &transaction.OracleResponse{
ID: reqID,
Code: transaction.Success,
Result: []byte{4, 8, 15, 16, 23, 42},
},
}}
ic := bc.newInteropContext(trigger.Application, bc.dao, bc.newBlock(tx), tx)
ic.VM = ic.SpawnVM()
ic.VM.LoadScriptWithFlags(tx.Script, callflag.All)
require.Error(t, ic.VM.Run())
// Request is cleaned up even if callback failed.
require.NoError(t, orc.PostPersist(ic))
_, err = orc.GetRequestInternal(ic.DAO, reqID)
require.Error(t, err)
})
t.Run("BadRequest", func(t *testing.T) {
var doBadRequest = func(t *testing.T, h util.Uint160, url string, filter *string, cb string, userData []byte, gas int64) {
txHash := putOracleRequest(t, h, bc, url, filter, cb, userData, gas)
aer, err := bc.GetAppExecResults(txHash, trigger.Application)
require.NoError(t, err)
require.Equal(t, 1, len(aer))
require.Equal(t, vm.FaultState, aer[0].VMState)
}
t.Run("non-UTF8 url", func(t *testing.T) {
doBadRequest(t, cs.Hash, "\xff", nil, "", []byte{1, 2}, gasForResponse)
})
t.Run("non-UTF8 filter", func(t *testing.T) {
var f = "\xff"
doBadRequest(t, cs.Hash, "url", &f, "", []byte{1, 2}, gasForResponse)
})
t.Run("not enough gas", func(t *testing.T) {
doBadRequest(t, cs.Hash, "url", nil, "", nil, 1000)
})
t.Run("disallowed callback", func(t *testing.T) {
doBadRequest(t, cs.Hash, "url", nil, "_deploy", nil, 1000_0000)
})
})
}
func TestGetSetPrice(t *testing.T) {
bc := newTestChain(t)
testGetSet(t, bc, bc.contracts.Oracle.Hash, "Price",
native.DefaultOracleRequestPrice, 1, math.MaxInt64)
}

View file

@ -2,11 +2,14 @@ package core
import ( import (
"bytes" "bytes"
"encoding/json"
"errors" "errors"
"fmt"
gio "io" gio "io"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/url" "net/url"
"os"
"path" "path"
"path/filepath" "path/filepath"
"strings" "strings"
@ -16,20 +19,164 @@ import (
"github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/config"
"github.com/nspcc-dev/neo-go/pkg/config/netmode" "github.com/nspcc-dev/neo-go/pkg/config/netmode"
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
"github.com/nspcc-dev/neo-go/pkg/core/native/noderoles" "github.com/nspcc-dev/neo-go/pkg/core/native/noderoles"
"github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/state"
"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/io"
"github.com/nspcc-dev/neo-go/pkg/services/oracle" "github.com/nspcc-dev/neo-go/pkg/services/oracle"
"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/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/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/wallet" "github.com/nspcc-dev/neo-go/pkg/wallet"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"go.uber.org/zap/zaptest" "go.uber.org/zap/zaptest"
) )
var oracleModulePath = filepath.Join("..", "services", "oracle") var (
oracleModulePath = filepath.Join("..", "services", "oracle")
oracleContractNEFPath = filepath.Join("test_data", "oracle_contract", "oracle.nef")
oracleContractManifestPath = filepath.Join("test_data", "oracle_contract", "oracle.manifest.json")
)
// TestGenerateOracleContract generates helper contract that is able to call
// native Oracle contract and has callback method. It uses test chain to define
// Oracle and StdLib native hashes and saves generated NEF and manifest to ... folder.
// Set `saveState` flag to true and run the test to rewrite NEF and manifest files.
func TestGenerateOracleContract(t *testing.T) {
const saveState = false
bc := newTestChain(t)
oracleHash := bc.contracts.Oracle.Hash
stdHash := bc.contracts.Std.Hash
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.
bytes, err := ne.Bytes()
require.NoError(t, err)
if saveState {
err = ioutil.WriteFile(oracleContractNEFPath, bytes, os.ModePerm)
require.NoError(t, err)
}
// Write manifest file.
mData, err := json.Marshal(m)
require.NoError(t, err)
if saveState {
err = ioutil.WriteFile(oracleContractManifestPath, mData, os.ModePerm)
require.NoError(t, err)
}
require.False(t, saveState)
}
// getOracleContractState reads pre-compiled oracle contract generated by
// TestGenerateOracleContract and returns its state.
func getOracleContractState(t *testing.T, sender util.Uint160, id int32) *state.Contract {
errNotFound := errors.New("auto-generated oracle contract is not found, use TestGenerateOracleContract to regenerate")
neBytes, err := ioutil.ReadFile(oracleContractNEFPath)
require.NoError(t, err, fmt.Errorf("nef: %w", errNotFound))
ne, err := nef.FileFromBytes(neBytes)
require.NoError(t, err)
mBytes, err := ioutil.ReadFile(oracleContractManifestPath)
require.NoError(t, err, fmt.Errorf("manifest: %w", errNotFound))
m := &manifest.Manifest{}
err = json.Unmarshal(mBytes, m)
require.NoError(t, err)
return &state.Contract{
ContractBase: state.ContractBase{
NEF: ne,
Hash: state.CreateContractHash(sender, ne.Checksum, m.Name),
Manifest: *m,
ID: id,
},
}
}
func putOracleRequest(t *testing.T, h util.Uint160, bc *Blockchain,
url string, filter *string, cb string, userData []byte, gas int64) util.Uint256 {
var filtItem interface{}
if filter != nil {
filtItem = *filter
}
res, err := invokeContractMethod(bc, gas+50_000_000+5_000_000, h, "requestURL",
url, filtItem, cb, userData, gas)
require.NoError(t, err)
return res.Container
}
func getOracleConfig(t *testing.T, bc *Blockchain, w, pass string) oracle.Config { func getOracleConfig(t *testing.T, bc *Blockchain, w, pass string) oracle.Config {
return oracle.Config{ return oracle.Config{
@ -133,7 +280,7 @@ func TestOracle(t *testing.T) {
orc1.UpdateNativeContract(orcNative.NEF.Script, orcNative.GetOracleResponseScript(), orcNative.Hash, md.MD.Offset) orc1.UpdateNativeContract(orcNative.NEF.Script, orcNative.GetOracleResponseScript(), orcNative.Hash, md.MD.Offset)
orc2.UpdateNativeContract(orcNative.NEF.Script, orcNative.GetOracleResponseScript(), orcNative.Hash, md.MD.Offset) orc2.UpdateNativeContract(orcNative.NEF.Script, orcNative.GetOracleResponseScript(), orcNative.Hash, md.MD.Offset)
cs := getOracleContractState(bc.contracts.Oracle.Hash, bc.contracts.Std.Hash) cs := getOracleContractState(t, util.Uint160{}, 42)
require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs)) require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs))
putOracleRequest(t, cs.Hash, bc, "https://get.1234", nil, "handle", []byte{}, 10_000_000) putOracleRequest(t, cs.Hash, bc, "https://get.1234", nil, "handle", []byte{}, 10_000_000)
@ -301,7 +448,7 @@ func TestOracleFull(t *testing.T) {
orc.OnTransaction = func(tx *transaction.Transaction) { _ = mp.Add(tx, bc) } orc.OnTransaction = func(tx *transaction.Transaction) { _ = mp.Add(tx, bc) }
bc.SetOracle(orc) bc.SetOracle(orc)
cs := getOracleContractState(bc.contracts.Oracle.Hash, bc.contracts.Std.Hash) cs := getOracleContractState(t, util.Uint160{}, 42)
require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs)) require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs))
go bc.Run() go bc.Run()
@ -326,7 +473,7 @@ func TestNotYetRunningOracle(t *testing.T) {
orc.OnTransaction = func(tx *transaction.Transaction) { _ = mp.Add(tx, bc) } orc.OnTransaction = func(tx *transaction.Transaction) { _ = mp.Add(tx, bc) }
bc.SetOracle(orc) bc.SetOracle(orc)
cs := getOracleContractState(bc.contracts.Oracle.Hash, bc.contracts.Std.Hash) cs := getOracleContractState(t, util.Uint160{}, 42)
require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs)) require.NoError(t, bc.contracts.Management.PutContractState(bc.dao, cs))
go bc.Run() go bc.Run()

View file

@ -0,0 +1,9 @@
## Oracle helper contract
Oracle helper contract NEF and manifest files are generated automatically by
`TestGenerateOracleContract` and are used in tests. Do not modify these files manually.
To regenerate these files:
1. Open `TestGenerateOracleContract` and set `saveState` flag to `true`.
2. Run `TestGenerateOracleContract`.
3. Set `saveState` back to `false`.

View file

@ -0,0 +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}

Binary file not shown.