Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: allow empty to field in eth_call to simulate contract deploy #546

Merged
merged 1 commit into from
Apr 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,10 @@ jobs:
ports:
- 5432:5432
env:
OASIS_CORE_VERSION: "23.0.9"
OASIS_CORE_VERSION: "23.0.10"
OASIS_NODE: ${{ github.workspace }}/oasis_core/oasis-node
OASIS_NET_RUNNER: ${{ github.workspace }}/oasis_core/oasis-net-runner
SAPPHIRE_PARATIME_VERSION: 0.7.1-testnet
SAPPHIRE_PARATIME_VERSION: 0.7.2-testnet
GATEWAY__CHAIN_ID: 23293
GATEWAY__OASIS_RPCS: true
SAPPHIRE_PARATIME: ${{ github.workspace }}/oasis_core/sapphire-paratime
Expand Down
6 changes: 3 additions & 3 deletions conf/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,13 @@ database:
max_open_conns: 0

gateway:
chain_id: 42262
chain_id: 23293
http:
host: "localhost"
port: 8545
port: 8945
ws:
host: "localhost"
port: 8546
port: 8946
monitoring:
host: "localhost"
port: 9999
Expand Down
15 changes: 15 additions & 0 deletions docker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,3 +68,18 @@ By default, the Oasis Web3 gateway and the Oasis node are configured with the
*warn* verbosity level. To increase verbosity to *debug*, you can run the
Docker container with `-e LOG__LEVEL=debug` for the Web3 gateway and
`-e OASIS_NODE_LOG_LEVEL=debug` for the Oasis node.

## Running Tests

As an alternatively to running Postgres & using `spinup-oasis-stack.sh` the
Docker containers can be used to test changes to the gateway or run tests by
bind-mounting the runtime state directory (`/serverdir/node`) into your local
filesystem.

```bash
docker run --rm -ti -p5432:5432 -p8545:8545 -p8546:8546 -v /tmp/eth-runtime-test:/serverdir/node ghcr.io/oasisprotocol/sapphire-localnet:local -test-mnemonic -n 4
```

The `oasis-web3-gateway` or `sapphire-paratime` executables could also be
bind-mounted into the container, allowing for quick tunraround time when testing
the full gateway & paratime stack together.
7 changes: 7 additions & 0 deletions docker/common/start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,13 @@ notice "Waiting for Oasis node to start..."
while [[ ! -S ${OASIS_NODE_SOCKET} ]]; do echo -n .; sleep 1; done
echo

# Fixes permissions so oasis-web3-gateway tests can be run
# While /serverdir/node is bind-mounted to /tmp/eth-runtime-test
chmod 755 /serverdir/node/net-runner/
CedarMist marked this conversation as resolved.
Show resolved Hide resolved
chmod 755 /serverdir/node/net-runner/network/
chmod 755 /serverdir/node/net-runner/network/client-0/
chmod a+rw /serverdir/node/net-runner/network/client-0/internal.sock

notice "Starting oasis-web3-gateway...\n"
${OASIS_WEB3_GATEWAY} --config ${OASIS_WEB3_GATEWAY_CONFIG_FILE} 2>1 &>/var/log/oasis-web3-gateway.log &
OASIS_WEB3_GATEWAY_PID=$!
Expand Down
24 changes: 13 additions & 11 deletions rpc/eth/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -367,11 +367,13 @@ func (api *publicAPI) Call(ctx context.Context, args utils.TransactionArgs, bloc
sender = common.Address{1}
gasPrice = []byte{1}
// This gas cap should be enough for SimulateCall an ethereum transaction
gas uint64 = 30_000_000
gas uint64 = 30_000_000
toBytes = []byte{}
)

if args.To == nil {
return []byte{}, errors.New("to address not specified")
// When simulating a contract deploy the To address may not be specified
if args.To != nil {
toBytes = args.To.Bytes()
}
if args.GasPrice != nil {
gasPrice = args.GasPrice.ToInt().Bytes()
Expand All @@ -396,14 +398,14 @@ func (api *publicAPI) Call(ctx context.Context, args utils.TransactionArgs, bloc
}

res, err := evm.NewV1(api.client).SimulateCall(
ctx,
round,
gasPrice,
gas,
sender.Bytes(),
args.To.Bytes(),
amount,
input,
ctx, // context
round, // round
gasPrice, // gasPrice
gas, // gasLimit
sender.Bytes(), // caller
toBytes, // address
amount, // value
input, // data
)
if err != nil {
return nil, api.handleCallFailure(ctx, logger, err)
Expand Down
1 change: 1 addition & 0 deletions tests/rpc/contracts/evm_constructor_require.hex
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
6080604052348015600e575f80fd5b5060405162461bcd60e51b815260206004820152601360248201527f4578616d706c654572726f725265717569726500000000000000000000000000604482015260640160405180910390fdfe
1 change: 1 addition & 0 deletions tests/rpc/contracts/evm_constructor_revert.hex
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
6080604052348015600e575f80fd5b5060405163547796ff60e01b815260206004820152601260248201527122bc30b6b83632a1bab9ba37b6a2b93937b960711b604482015260640160405180910390fdfe
125 changes: 125 additions & 0 deletions tests/rpc/tx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
_ "embed"
"math/big"
"reflect"
"strings"
"testing"
"time"
Expand Down Expand Up @@ -71,6 +72,130 @@ func waitTransaction(ctx context.Context, ec *ethclient.Client, txhash common.Ha
}
}

/*
contract ExampleRequire {
constructor () {
require(false, "ExampleErrorRequire");
}
}
*/
//go:embed contracts/evm_constructor_require.hex
var evmConstructorRequireHex string

/*
contract ExampleCustomError {
error Example(string x);
constructor () {
revert Example("ExampleCustomError");
}
}
*/
//go:embed contracts/evm_constructor_revert.hex
var evmConstructorRevertHex string

// extractDataFromUnexportedError extracts the "Data" field from *rpc.jsonError
// that is not exported using reflection.
func extractDataFromUnexportedError(err error) string {
if err == nil {
return ""
}

val := reflect.ValueOf(err)
if val.Kind() == reflect.Ptr && !val.IsNil() {
// Assuming jsonError is a struct
errVal := val.Elem()

// Check if the struct has a field named "Data".
dataField := errVal.FieldByName("Data")
if dataField.IsValid() && dataField.CanInterface() {
// Assuming the data field is a string
return dataField.Interface().(string)
}
}

return ""
}

func skipIfNotSapphire(t *testing.T, ec *ethclient.Client) {
chainID, err := ec.ChainID(context.Background())
require.Nil(t, err, "get chainid")

switch chainID.Uint64() {
case 0x5afd, 0x5afe, 0x5aff: // For sapphire chain IDs, do nothing
default:
t.Skip("Skipping contract constructor tests on non-Sapphire chain")
}
}

// Verifies that require(false,"...") in constructor works as expected.
func testContractConstructorError(t *testing.T, contractCode string, expectedError string) {
ec := localClient(t, false)
skipIfNotSapphire(t, ec)

t.Logf("compiled contract: %s", contractCode)
code := common.FromHex(strings.TrimSpace(contractCode))

callMsg := ethereum.CallMsg{
From: tests.TestKey1.EthAddress,
Gas: 0,
Data: code,
}

// Estimate contract creation gas requirements.
gasCost, err := ec.EstimateGas(context.Background(), callMsg)
require.Nil(t, err, "estimate gas failed")
require.Greater(t, gasCost, uint64(0), "gas estimate must be positive")
t.Logf("gas estimate for contract creation: %d", gasCost)

// Evaluate the contract creation via `eth_call`.
callMsg.Gas = 250000 // gasCost
result, err := ec.CallContract(context.Background(), callMsg, nil)
require.NotNil(t, err, "constructor expected to throw error")
require.Equal(t, expectedError, extractDataFromUnexportedError(err))
require.Nil(t, result)
}

func TestContractConstructorRequire(t *testing.T) {
testContractConstructorError(t, evmConstructorRequireHex, "0x08c379a0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000134578616d706c654572726f725265717569726500000000000000000000000000")
}

func TestContractConstructorRevert(t *testing.T) {
testContractConstructorError(t, evmConstructorRevertHex, "0x547796ff000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000124578616d706c65437573746f6d4572726f720000000000000000000000000000")
}

// Verify contract creation can be simulated and returns an address.
func TestContractCreateInCall(t *testing.T) {
ec := localClient(t, false)
skipIfNotSapphire(t, ec)

t.Logf("compiled contract: %s", evmSolTestCompiledHex)
code := common.FromHex(strings.TrimSpace(evmSolTestCompiledHex))

gasPrice, err := ec.SuggestGasPrice(context.Background())
require.Nil(t, err, "get gasPrice failed")

callMsg := ethereum.CallMsg{
From: tests.TestKey1.EthAddress,
Gas: 0,
GasPrice: gasPrice,
Data: code,
}

// Estimate contract creation gas requirements.
gasCost, err := ec.EstimateGas(context.Background(), callMsg)
require.NoError(t, err, "estimate gas failed")
require.Greater(t, gasCost, uint64(0), "gas estimate must be positive")
t.Logf("gas estimate for contract creation: %d", gasCost)

// Evaluate the contract creation via `eth_call`.
callMsg.Gas = 250000 // gasCost
result, err := ec.CallContract(context.Background(), callMsg, nil)
require.Nil(t, err, "call contract")

require.NotNil(t, result)
require.Equal(t, len(result), 20)
}

func testContractCreation(t *testing.T, value *big.Int) uint64 {
ec := localClient(t, false)

Expand Down
8 changes: 6 additions & 2 deletions tests/tools/spinup-oasis-stack.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,12 @@ function paratime_ver {
export FIXTURE_FILE="${OASIS_NODE_DATADIR}/fixture.json"
export STAKING_GENESIS_FILE="$(dirname "$0")/staking_genesis.json"

rm -rf "$OASIS_NODE_DATADIR"
mkdir -p "$OASIS_NODE_DATADIR"
rm -rf "$OASIS_NODE_DATADIR/net-runner"
rm -rf "$OASIS_NODE_DATADIR/net-runner.log"
rm -rf "$OASIS_NODE_DATADIR/fixture.json"
# When $OASIS_NODE_DATADIR is bind-mounted, below fails, but the above succeed
CedarMist marked this conversation as resolved.
Show resolved Hide resolved
rm -rf "$OASIS_NODE_DATADIR" || true
mkdir -p "$OASIS_NODE_DATADIR" || true

# Prepare configuration for oasis-node (fixture).
${OASIS_NET_RUNNER} dump-fixture \
Expand Down
Loading