Skip to content

Commit

Permalink
Add E2E test to verify behavior on revert
Browse files Browse the repository at this point in the history
Signed-off-by: Peter Broadhurst <[email protected]>
  • Loading branch information
peterbroadhurst committed Dec 21, 2023
1 parent c808a7c commit d67a3b7
Show file tree
Hide file tree
Showing 8 changed files with 156 additions and 5 deletions.
2 changes: 1 addition & 1 deletion internal/blockchain/ethereum/ethereum.go
Original file line number Diff line number Diff line change
Expand Up @@ -1000,7 +1000,7 @@ func (e *Ethereum) GenerateFFI(ctx context.Context, generationRequest *fftypes.F
if err != nil {
return nil, i18n.WrapError(ctx, err, coremsgs.MsgFFIGenerationFailed, "unable to deserialize JSON as ABI")
}
if len(*input.ABI) == 0 {
if input.ABI == nil || len(*input.ABI) == 0 {
return nil, i18n.NewError(ctx, coremsgs.MsgFFIGenerationFailed, "ABI is empty")
}
return ffi2abi.ConvertABIToFFI(ctx, generationRequest.Namespace, generationRequest.Name, generationRequest.Version, generationRequest.Description, input.ABI)
Expand Down
1 change: 1 addition & 0 deletions test/data/contracts/reverter/reverter.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"contracts":{"reverter.sol:Reverter":{"abi":[{"inputs":[],"name":"goBang","outputs":[],"stateMutability":"pure","type":"function"}],"bin":"608060405234801561001057600080fd5b5061011b806100206000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063060846fb14602d575b600080fd5b60336035565b005b6040517f08c379a000000000000000000000000000000000000000000000000000000000815260040160659060c7565b60405180910390fd5b600082825260208201905092915050565b7f42616e6721000000000000000000000000000000000000000000000000000000600082015250565b600060b3600583606e565b915060bc82607f565b602082019050919050565b6000602082019050818103600083015260de8160a8565b905091905056fea26469706673582212204c5a121fa1ad563532a26d368380482d34f0eee629e860671f518ec7af2fc2c064736f6c63430008170033"}},"version":"0.8.23+commit.f704f362.Darwin.appleclang"}
9 changes: 9 additions & 0 deletions test/data/contracts/reverter/reverter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: Apache-2.0

pragma solidity >=0.6.0 <0.9.0;

contract Reverter {
function goBang() pure public {
revert("Bang!");
}
}
5 changes: 5 additions & 0 deletions test/e2e/client/restclient.go
Original file line number Diff line number Diff line change
Expand Up @@ -824,15 +824,20 @@ func (client *FireFlyClient) DeleteContractListener(t *testing.T, id *fftypes.UU
func (client *FireFlyClient) InvokeContractMethod(t *testing.T, req *core.ContractCallRequest, expectedStatus ...int) (interface{}, error) {
var res interface{}
path := client.namespaced(urlContractInvoke)
var errResult fftypes.RESTError
resp, err := client.Client.R().
SetBody(req).
SetResult(&res).
SetError(&errResult).
Post(path)
require.NoError(t, err)
if len(expectedStatus) == 0 {
expectedStatus = []int{202}
}
require.Equal(t, expectedStatus[0], resp.StatusCode(), "POST %s [%d]: %s", path, resp.StatusCode(), resp.String())
if err == nil && errResult.Error != "" {
return res, fmt.Errorf(errResult.Error)
}
return res, err
}

Expand Down
14 changes: 14 additions & 0 deletions test/e2e/e2e.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,3 +256,17 @@ func VerifyAllOperationsSucceeded(t *testing.T, clients []*client.FireFlyClient,

assert.Fail(t, pending)
}

func VerifyOperationsAlreadyMarkedFailed(t *testing.T, clients []*client.FireFlyClient, startTime time.Time) {
// Note we do NOT wait in this function - use this function when failure should already have been recorded,
// and the work has been done in FF Core to ensure that is reflected in the operation cache.
pending := ""
for _, client := range clients {
for _, op := range client.GetOperations(t, startTime) {
if op.Status != core.OpStatusFailed {
pending += fmt.Sprintf("Operation '%s' (%s) on '%s' status=%s\n", op.ID, op.Type, client.Client.BaseURL, op.Status)
}
}
}
assert.Empty(t, pending, pending)
}
96 changes: 96 additions & 0 deletions test/e2e/gateway/ethereum_revert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
// Copyright © 2023 Kaleido, Inc.
//
// SPDX-License-Identifier: Apache-2.0
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package gateway

import (
"encoding/json"
"fmt"

"github.com/go-resty/resty/v2"
"github.com/hyperledger/firefly-common/pkg/fftypes"
"github.com/hyperledger/firefly/pkg/core"
"github.com/hyperledger/firefly/test/e2e"
"github.com/hyperledger/firefly/test/e2e/client"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/suite"
)

type EthereumRevertTestSuite struct {
suite.Suite
testState *testState
contractAddress string
ethClient *resty.Client
ethIdentity string
abi *fftypes.JSONAny
}

func (suite *EthereumRevertTestSuite) SetupSuite() {
suite.testState = beforeE2ETest(suite.T())
stack := e2e.ReadStack(suite.T())
stackState := e2e.ReadStackState(suite.T())
suite.ethClient = client.NewResty(suite.T())
suite.ethClient.SetBaseURL(fmt.Sprintf("http://localhost:%d", stack.Members[0].ExposedConnectorPort))
account := stackState.Accounts[0].(map[string]interface{})
suite.ethIdentity = account["address"].(string)
suite.abi, suite.contractAddress = deployTestContractFromCompiledJSON(suite.T(), stack.Name, "reverter/reverter.json")
}

func (suite *EthereumRevertTestSuite) BeforeTest(suiteName, testName string) {
suite.testState = beforeE2ETest(suite.T())
}

func (suite *EthereumRevertTestSuite) AfterTest(suiteName, testName string) {
// Important part of the test - the status of the operation must go to Failed - immediately.
// We should not encounter an "Initialized" status
e2e.VerifyOperationsAlreadyMarkedFailed(suite.T(), []*client.FireFlyClient{suite.testState.client1}, suite.testState.startTime)
}

func (suite *EthereumRevertTestSuite) TestRevertTransitionsToFailed() {
defer suite.testState.Done()

type generateInput struct {
ABI *fftypes.JSONAny `json:"abi"`
}
inputBytes, err := json.Marshal(&generateInput{ABI: suite.abi})
assert.NoError(suite.T(), err)

ffi := suite.testState.client1.GenerateFFIFromABI(suite.T(), &fftypes.FFIGenerationRequest{
Input: fftypes.JSONAnyPtrBytes(inputBytes),
})
assert.NoError(suite.T(), err)
var goBang *fftypes.FFIMethod
for _, m := range ffi.Methods {
if m.Name == "goBang" {
goBang = m
}
}
assert.NotNil(suite.T(), goBang)

location := map[string]interface{}{
"address": suite.contractAddress,
}
locationBytes, _ := json.Marshal(location)
invokeContractRequest := &core.ContractCallRequest{
Location: fftypes.JSONAnyPtrBytes(locationBytes),
Method: goBang,
Input: map[string]interface{}{},
}

// Check we get the revert error all the way back through the API on the invoke, due to the gas estimation
_, err = suite.testState.client1.InvokeContractMethod(suite.T(), invokeContractRequest, 500)
assert.Regexp(suite.T(), "FF10111.*Bang!", err)
}
33 changes: 29 additions & 4 deletions test/e2e/gateway/ethereum_simplestorage.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package gateway
import (
"encoding/json"
"fmt"
"os"
"os/exec"
"testing"

Expand Down Expand Up @@ -93,16 +94,40 @@ func simpleStorageFFIGet() *fftypes.FFIMethod {
}
}

func deploySimpleStorageContract(t *testing.T, stackName, contract string) string {
func deployTestContractFromCompiledJSON(t *testing.T, stackName, contract string) (*fftypes.JSONAny, string) {
path := "../../data/contracts/" + contract
out, err := exec.Command("ff", "deploy", "ethereum", stackName, path).Output()
require.NoError(t, err)
var stderr []byte
if err != nil {
stderr = err.(*exec.ExitError).Stderr
}
require.NoError(t, err, fmt.Sprintf("ff deploy failed: %s", stderr))
var output map[string]interface{}
err = json.Unmarshal(out, &output)
require.NoError(t, err)
address := output["address"].(string)
t.Logf("Contract address: %s", address)
return address

type solcJSON struct {
Contracts map[string]struct {
ABI *fftypes.JSONAny `json:"abi"`
} `json:"contracts"`
}
b, err := os.ReadFile(path)
assert.NoError(t, err)
var contractJSON solcJSON
err = json.Unmarshal(b, &contractJSON)
assert.NoError(t, err)

var abiBytes *fftypes.JSONAny
for _, contract := range contractJSON.Contracts {
abiBytes = contract.ABI
if abiBytes != nil {
break
}
}
assert.NotNil(t, abiBytes)
return abiBytes, address
}

type EthereumSimpleStorageTestSuite struct {
Expand All @@ -122,7 +147,7 @@ func (suite *EthereumSimpleStorageTestSuite) SetupSuite() {
suite.ethClient.SetBaseURL(fmt.Sprintf("http://localhost:%d", stack.Members[0].ExposedConnectorPort))
account := stackState.Accounts[0].(map[string]interface{})
suite.ethIdentity = account["address"].(string)
suite.contractAddress = deploySimpleStorageContract(suite.T(), stack.Name, "simplestorage/simple_storage.json")
_, suite.contractAddress = deployTestContractFromCompiledJSON(suite.T(), stack.Name, "simplestorage/simple_storage.json")

res, err := suite.testState.client1.CreateFFI(suite.T(), simpleStorageFFI(), false)
suite.interfaceID = res.ID
Expand Down
1 change: 1 addition & 0 deletions test/e2e/runners/ethereum_gateway_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,6 @@ func TestEthereumGatewayE2ESuite(t *testing.T) {
suite.Run(t, new(gateway.TokensTestSuite))
suite.Run(t, new(gateway.EthereumCouponTestSuite))
suite.Run(t, new(gateway.EthereumSimpleStorageTestSuite))
suite.Run(t, new(gateway.EthereumRevertTestSuite))
suite.Run(t, new(gateway.TokensOnlyTestSuite))
}

0 comments on commit d67a3b7

Please sign in to comment.