From 8d170a1eb85f77e9c0669448bc49d70cf596b08c Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Fri, 22 Jul 2022 12:14:54 +0300 Subject: [PATCH] native: add Oracle.finish reentrancy test --- internal/contracts/oracle_contract/oracle.go | 18 ++++++++++++++++++ .../oracle_contract/oracle.manifest.json | 2 +- internal/contracts/oracle_contract/oracle.nef | Bin 233 -> 467 bytes internal/contracts/oracle_contract/oracle.yml | 3 ++- pkg/core/native/native_test/oracle_test.go | 10 ++++++++++ 5 files changed, 31 insertions(+), 2 deletions(-) diff --git a/internal/contracts/oracle_contract/oracle.go b/internal/contracts/oracle_contract/oracle.go index ba81c4005..da7ebe990 100644 --- a/internal/contracts/oracle_contract/oracle.go +++ b/internal/contracts/oracle_contract/oracle.go @@ -1,8 +1,11 @@ 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" ) @@ -22,3 +25,18 @@ func Handle(url string, data interface{}, code int, res []byte) { 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) + } +} diff --git a/internal/contracts/oracle_contract/oracle.manifest.json b/internal/contracts/oracle_contract/oracle.manifest.json index 36fb0f38b..e4c102428 100644 --- a/internal/contracts/oracle_contract/oracle.manifest.json +++ b/internal/contracts/oracle_contract/oracle.manifest.json @@ -1 +1 @@ -{"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":"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":[]},"features":{},"groups":[],"permissions":[{"contract":"*","methods":["request"]}],"supportedstandards":[],"trusts":[],"extra":null} \ No newline at end of file +{"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} \ No newline at end of file diff --git a/internal/contracts/oracle_contract/oracle.nef b/internal/contracts/oracle_contract/oracle.nef index c7f91865fa387344ca8f5d2617b228ec9fc8a553..c088eb37555262b83817a87cb79cbc51ccfe020f 100644 GIT binary patch delta 251 zcmaFKc$s-ZH}hXd#)&v-7GGV?NvGvu>F9FuCJgIpbM_6KhU0A|2pg8%>k delta 16 Ycmcc2{E~4(H)G_)zDSlSwL6Xh06MJ)K>z>% diff --git a/internal/contracts/oracle_contract/oracle.yml b/internal/contracts/oracle_contract/oracle.yml index 1266786f9..e2d84080a 100644 --- a/internal/contracts/oracle_contract/oracle.yml +++ b/internal/contracts/oracle_contract/oracle.yml @@ -2,5 +2,6 @@ name: "Oracle test" sourceurl: https://github.com/nspcc-dev/neo-go/ supportedstandards: [] events: + - name: Invocation permissions: - - methods: ["request"] + - methods: ["request", "finish"] diff --git a/pkg/core/native/native_test/oracle_test.go b/pkg/core/native/native_test/oracle_test.go index bc51d6de9..bc32eadde 100644 --- a/pkg/core/native/native_test/oracle_test.go +++ b/pkg/core/native/native_test/oracle_test.go @@ -15,6 +15,7 @@ import ( "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/neotest" + "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/stretchr/testify/require" ) @@ -151,6 +152,15 @@ func TestOracle_Request(t *testing.T) { require.Error(t, err) 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(), "") + 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("non-UTF8 url", func(t *testing.T) { putOracleRequest(t, helperValidatorInvoker, "\xff", nil, "", []byte{1, 2}, gasForResponse, "invalid value: not UTF-8")