diff --git a/CHANGELOG.md b/CHANGELOG.md index c57148029a..122b163690 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - `lotus-shed indexes inspect-indexes` now performs a comprehensive comparison of the event index data for each message by comparing the AMT root CID from the message receipt with the root of a reconstructed AMT. Previously `inspect-indexes` simply compared event counts, comparing AMT roots confirms all the event data is byte-perfect. ([filecoin-project/lotus#12570](https://github.com/filecoin-project/lotus/pull/12570)) - Expose APIs to list the miner IDs that are currently participating in F3 via node. ([filecoin-project/lotus#12608](https://github.com/filecoin-project/lotus/pull/12608)) - Implement new `lotus f3` CLI commands to list F3 participants, dump manifest, get/list finality certificates and check the F3 status. ([filecoin-project/lotus#12617](https://github.com/filecoin-project/lotus/pull/12617), [filecoin-project/lotus#12627](https://github.com/filecoin-project/lotus/pull/12627)) +- Return a `"data"` field on the `"error"` returned from RPC when `eth_call` and `eth_estimateGas` APIs encounter `execution reverted` errors. ([filecoin-project/lotus#12553](https://github.com/filecoin-project/lotus/pull/12553)) ## Bug Fixes - Fix a bug in the `lotus-shed indexes backfill-events` command that may result in either duplicate events being backfilled where there are existing events (such an operation *should* be idempotent) or events erroneously having duplicate `logIndex` values when queried via ETH APIs. ([filecoin-project/lotus#12567](https://github.com/filecoin-project/lotus/pull/12567)) diff --git a/api/api_errors.go b/api/api_errors.go index a46cfe6c26..b6fbbaf9a9 100644 --- a/api/api_errors.go +++ b/api/api_errors.go @@ -4,9 +4,13 @@ import ( "errors" "reflect" + "golang.org/x/xerrors" + "github.com/filecoin-project/go-jsonrpc" ) +var invalidExecutionRevertedMsg = xerrors.New("invalid execution reverted error") + const ( EOutOfGas = iota + jsonrpc.FirstUserCode EActorNotFound @@ -17,6 +21,7 @@ const ( EF3ParticipationTooManyInstances EF3ParticipationTicketStartBeforeExisting EF3NotReady + EExecutionReverted ) var ( @@ -40,13 +45,15 @@ var ( // should back off and try again later. ErrF3NotReady = &errF3NotReady{} - _ error = (*ErrOutOfGas)(nil) - _ error = (*ErrActorNotFound)(nil) - _ error = (*errF3Disabled)(nil) - _ error = (*errF3ParticipationTicketInvalid)(nil) - _ error = (*errF3ParticipationTicketExpired)(nil) - _ error = (*errF3ParticipationIssuerMismatch)(nil) - _ error = (*errF3NotReady)(nil) + _ error = (*ErrOutOfGas)(nil) + _ error = (*ErrActorNotFound)(nil) + _ error = (*errF3Disabled)(nil) + _ error = (*errF3ParticipationTicketInvalid)(nil) + _ error = (*errF3ParticipationTicketExpired)(nil) + _ error = (*errF3ParticipationIssuerMismatch)(nil) + _ error = (*errF3NotReady)(nil) + _ error = (*ErrExecutionReverted)(nil) + _ jsonrpc.RPCErrorCodec = (*ErrExecutionReverted)(nil) ) func init() { @@ -59,6 +66,7 @@ func init() { RPCErrors.Register(EF3ParticipationTooManyInstances, new(*errF3ParticipationTooManyInstances)) RPCErrors.Register(EF3ParticipationTicketStartBeforeExisting, new(*errF3ParticipationTicketStartBeforeExisting)) RPCErrors.Register(EF3NotReady, new(*errF3NotReady)) + RPCErrors.Register(EExecutionReverted, new(*ErrExecutionReverted)) } func ErrorIsIn(err error, errorTypes []error) bool { @@ -110,3 +118,45 @@ func (errF3ParticipationTicketStartBeforeExisting) Error() string { type errF3NotReady struct{} func (errF3NotReady) Error() string { return "f3 isn't yet ready to participate" } + +// ErrExecutionReverted is used to return execution reverted with a reason for a revert in the `data` field. +type ErrExecutionReverted struct { + Message string + Data string +} + +// Error returns the error message. +func (e *ErrExecutionReverted) Error() string { return e.Message } + +// FromJSONRPCError converts a JSONRPCError to ErrExecutionReverted. +func (e *ErrExecutionReverted) FromJSONRPCError(jerr jsonrpc.JSONRPCError) error { + if jerr.Code != EExecutionReverted || jerr.Message == "" || jerr.Data == nil { + return invalidExecutionRevertedMsg + } + + data, ok := jerr.Data.(string) + if !ok { + return xerrors.Errorf("expected string data in execution reverted error, got %T", jerr.Data) + } + + e.Message = jerr.Message + e.Data = data + return nil +} + +// ToJSONRPCError converts ErrExecutionReverted to a JSONRPCError. +func (e *ErrExecutionReverted) ToJSONRPCError() (jsonrpc.JSONRPCError, error) { + return jsonrpc.JSONRPCError{ + Code: EExecutionReverted, + Message: e.Message, + Data: e.Data, + }, nil +} + +// NewErrExecutionReverted creates a new ErrExecutionReverted with the given reason. +func NewErrExecutionReverted(reason string) *ErrExecutionReverted { + return &ErrExecutionReverted{ + Message: "execution reverted", + Data: reason, + } +} diff --git a/go.mod b/go.mod index 4ef014996f..4c7bf4f1cd 100644 --- a/go.mod +++ b/go.mod @@ -47,7 +47,7 @@ require ( github.com/filecoin-project/go-f3 v0.7.0 github.com/filecoin-project/go-fil-commcid v0.2.0 github.com/filecoin-project/go-hamt-ipld/v3 v3.4.0 - github.com/filecoin-project/go-jsonrpc v0.6.0 + github.com/filecoin-project/go-jsonrpc v0.7.0 github.com/filecoin-project/go-padreader v0.0.1 github.com/filecoin-project/go-paramfetch v0.0.4 github.com/filecoin-project/go-state-types v0.15.0-rc1 diff --git a/go.sum b/go.sum index 0806460471..3ee43c719e 100644 --- a/go.sum +++ b/go.sum @@ -287,8 +287,8 @@ github.com/filecoin-project/go-hamt-ipld/v3 v3.0.1/go.mod h1:gXpNmr3oQx8l3o7qkGy github.com/filecoin-project/go-hamt-ipld/v3 v3.1.0/go.mod h1:bxmzgT8tmeVQA1/gvBwFmYdT8SOFUwB3ovSUfG1Ux0g= github.com/filecoin-project/go-hamt-ipld/v3 v3.4.0 h1:nYs6OPUF8KbZ3E8o9p9HJnQaE8iugjHR5WYVMcicDJc= github.com/filecoin-project/go-hamt-ipld/v3 v3.4.0/go.mod h1:s0qiHRhFyrgW0SvdQMSJFQxNa4xEIG5XvqCBZUEgcbc= -github.com/filecoin-project/go-jsonrpc v0.6.0 h1:/fFJIAN/k6EgY90m7qbyfY28woMwyseZmh2gVs5sYjY= -github.com/filecoin-project/go-jsonrpc v0.6.0/go.mod h1:/n/niXcS4ZQua6i37LcVbY1TmlJR0UIK9mDFQq2ICek= +github.com/filecoin-project/go-jsonrpc v0.7.0 h1:mqA5pIOlBODx7ascY9cJdBAYonhgbdUOIn2dyYI1YBg= +github.com/filecoin-project/go-jsonrpc v0.7.0/go.mod h1:lAUpS8BSVtKaA8+/CFUMA5dokMiSM7n0ehf8bHOFdpE= github.com/filecoin-project/go-padreader v0.0.1 h1:8h2tVy5HpoNbr2gBRr+WD6zV6VD6XHig+ynSGJg8ZOs= github.com/filecoin-project/go-padreader v0.0.1/go.mod h1:VYVPJqwpsfmtoHnAmPx6MUwmrK6HIcDqZJiuZhtmfLQ= github.com/filecoin-project/go-paramfetch v0.0.4 h1:H+Me8EL8T5+79z/KHYQQcT8NVOzYVqXIi7nhb48tdm8= diff --git a/itests/fevm_test.go b/itests/fevm_test.go index 19f9af0178..677427b858 100644 --- a/itests/fevm_test.go +++ b/itests/fevm_test.go @@ -231,33 +231,33 @@ func TestFEVMDelegateCall(t *testing.T) { ctx, cancel, client := kit.SetupFEVMTest(t) defer cancel() - //install contract Actor + // install contract Actor filenameActor := "contracts/DelegatecallActor.hex" fromAddr, actorAddr := client.EVM().DeployContractFromFilename(ctx, filenameActor) - //install contract Storage + // install contract Storage filenameStorage := "contracts/DelegatecallStorage.hex" fromAddrStorage, storageAddr := client.EVM().DeployContractFromFilename(ctx, filenameStorage) require.Equal(t, fromAddr, fromAddrStorage) - //call Contract Storage which makes a delegatecall to contract Actor - //this contract call sets the "counter" variable to 7, from default value 0 + // call Contract Storage which makes a delegatecall to contract Actor + // this contract call sets the "counter" variable to 7, from default value 0 inputDataContract := inputDataFromFrom(ctx, t, client, actorAddr) inputDataValue := inputDataFromArray([]byte{7}) inputData := append(inputDataContract, inputDataValue...) - //verify that the returned value of the call to setvars is 7 + // verify that the returned value of the call to setvars is 7 result, _, err := client.EVM().InvokeContractByFuncName(ctx, fromAddr, storageAddr, "setVars(address,uint256)", inputData) require.NoError(t, err) expectedResult, err := hex.DecodeString("0000000000000000000000000000000000000000000000000000000000000007") require.NoError(t, err) require.Equal(t, result, expectedResult) - //test the value is 7 a second way by calling the getter + // test the value is 7 a second way by calling the getter result, _, err = client.EVM().InvokeContractByFuncName(ctx, fromAddr, storageAddr, "getCounter()", []byte{}) require.NoError(t, err) require.Equal(t, result, expectedResult) - //test the value is 0 via calling the getter on the Actor contract + // test the value is 0 via calling the getter on the Actor contract result, _, err = client.EVM().InvokeContractByFuncName(ctx, fromAddr, actorAddr, "getCounter()", []byte{}) require.NoError(t, err) expectedResultActor, err := hex.DecodeString("0000000000000000000000000000000000000000000000000000000000000000") @@ -285,34 +285,34 @@ func TestFEVMDelegateCallRevert(t *testing.T) { ctx, cancel, client := kit.SetupFEVMTest(t) defer cancel() - //install contract Actor + // install contract Actor filenameActor := "contracts/DelegatecallActor.hex" fromAddr, actorAddr := client.EVM().DeployContractFromFilename(ctx, filenameActor) - //install contract Storage + // install contract Storage filenameStorage := "contracts/DelegatecallStorage.hex" fromAddrStorage, storageAddr := client.EVM().DeployContractFromFilename(ctx, filenameStorage) require.Equal(t, fromAddr, fromAddrStorage) - //call Contract Storage which makes a delegatecall to contract Actor - //this contract call sets the "counter" variable to 7, from default value 0 + // call Contract Storage which makes a delegatecall to contract Actor + // this contract call sets the "counter" variable to 7, from default value 0 inputDataContract := inputDataFromFrom(ctx, t, client, actorAddr) inputDataValue := inputDataFromArray([]byte{7}) inputData := append(inputDataContract, inputDataValue...) - //verify that the returned value of the call to setvars is 7 + // verify that the returned value of the call to setvars is 7 _, wait, err := client.EVM().InvokeContractByFuncName(ctx, fromAddr, storageAddr, "setVarsRevert(address,uint256)", inputData) require.Error(t, err) require.Equal(t, exitcode.ExitCode(33), wait.Receipt.ExitCode) - //test the value is 0 via calling the getter and was not set to 7 + // test the value is 0 via calling the getter and was not set to 7 expectedResult, err := hex.DecodeString("0000000000000000000000000000000000000000000000000000000000000000") require.NoError(t, err) result, _, err := client.EVM().InvokeContractByFuncName(ctx, fromAddr, storageAddr, "getCounter()", []byte{}) require.NoError(t, err) require.Equal(t, result, expectedResult) - //test the value is 0 via calling the getter on the Actor contract + // test the value is 0 via calling the getter on the Actor contract result, _, err = client.EVM().InvokeContractByFuncName(ctx, fromAddr, actorAddr, "getCounter()", []byte{}) require.NoError(t, err) require.Equal(t, result, expectedResult) @@ -323,11 +323,11 @@ func TestFEVMSimpleRevert(t *testing.T) { ctx, cancel, client := kit.SetupFEVMTest(t) defer cancel() - //install contract Actor + // install contract Actor filenameStorage := "contracts/DelegatecallStorage.hex" fromAddr, contractAddr := client.EVM().DeployContractFromFilename(ctx, filenameStorage) - //call revert + // call revert _, wait, err := client.EVM().InvokeContractByFuncName(ctx, fromAddr, contractAddr, "revert()", []byte{}) require.Equal(t, wait.Receipt.ExitCode, exitcode.ExitCode(33)) @@ -339,15 +339,15 @@ func TestFEVMSelfDestruct(t *testing.T) { ctx, cancel, client := kit.SetupFEVMTest(t) defer cancel() - //install contract Actor + // install contract Actor filenameStorage := "contracts/SelfDestruct.hex" fromAddr, contractAddr := client.EVM().DeployContractFromFilename(ctx, filenameStorage) - //call destroy + // call destroy _, _, err := client.EVM().InvokeContractByFuncName(ctx, fromAddr, contractAddr, "destroy()", []byte{}) require.NoError(t, err) - //call destroy a second time and also no error + // call destroy a second time and also no error _, _, err = client.EVM().InvokeContractByFuncName(ctx, fromAddr, contractAddr, "destroy()", []byte{}) require.NoError(t, err) } @@ -357,7 +357,7 @@ func TestFEVMTestApp(t *testing.T) { ctx, cancel, client := kit.SetupFEVMTest(t) defer cancel() - //install contract Actor + // install contract Actor filenameStorage := "contracts/TestApp.hex" fromAddr, contractAddr := client.EVM().DeployContractFromFilename(ctx, filenameStorage) @@ -379,11 +379,11 @@ func TestFEVMTestConstructor(t *testing.T) { ctx, cancel, client := kit.SetupFEVMTest(t) defer cancel() - //install contract Actor + // install contract Actor filenameStorage := "contracts/Constructor.hex" fromAddr, contractAddr := client.EVM().DeployContractFromFilename(ctx, filenameStorage) - //input = uint256{7}. set value and confirm tx success + // input = uint256{7}. set value and confirm tx success inputData, err := hex.DecodeString("0000000000000000000000000000000000000000000000000000000000000007") require.NoError(t, err) _, _, err = client.EVM().InvokeContractByFuncName(ctx, fromAddr, contractAddr, "new_Test(uint256)", inputData) @@ -396,11 +396,11 @@ func TestFEVMAutoSelfDestruct(t *testing.T) { ctx, cancel, client := kit.SetupFEVMTest(t) defer cancel() - //install contract Actor + // install contract Actor filenameStorage := "contracts/AutoSelfDestruct.hex" fromAddr, contractAddr := client.EVM().DeployContractFromFilename(ctx, filenameStorage) - //call destroy + // call destroy _, _, err := client.EVM().InvokeContractByFuncName(ctx, fromAddr, contractAddr, "destroy()", []byte{}) require.NoError(t, err) } @@ -413,16 +413,16 @@ func TestFEVMTestSendToContract(t *testing.T) { bal, err := client.WalletBalance(ctx, client.DefaultKey.Address) require.NoError(t, err) - //install contract TestApp + // install contract TestApp filenameStorage := "contracts/SelfDestruct.hex" fromAddr, contractAddr := client.EVM().DeployContractFromFilename(ctx, filenameStorage) - //transfer half balance to contract + // transfer half balance to contract sendAmount := big.Div(bal, big.NewInt(2)) client.EVM().TransferValueOrFail(ctx, fromAddr, contractAddr, sendAmount) - //call self destruct which should return balance + // call self destruct which should return balance _, _, err = client.EVM().InvokeContractByFuncName(ctx, fromAddr, contractAddr, "destroy()", []byte{}) require.NoError(t, err) @@ -443,7 +443,7 @@ func TestFEVMTestNotPayable(t *testing.T) { fromAddr := client.DefaultKey.Address t.Log("from - ", fromAddr) - //create contract A + // create contract A filenameStorage := "contracts/NotPayable.hex" fromAddr, contractAddr := client.EVM().DeployContractFromFilename(ctx, filenameStorage) sendAmount := big.NewInt(10_000_000) @@ -457,7 +457,7 @@ func TestFEVMSendCall(t *testing.T) { ctx, cancel, client := kit.SetupFEVMTest(t) defer cancel() - //install contract + // install contract filenameActor := "contracts/GasSendTest.hex" fromAddr, contractAddr := client.EVM().DeployContractFromFilename(ctx, filenameActor) @@ -472,12 +472,12 @@ func TestFEVMSendGasLimit(t *testing.T) { ctx, cancel, client := kit.SetupFEVMTest(t) defer cancel() - //install contract + // install contract filenameActor := "contracts/GasLimitSend.hex" fromAddr, contractAddr := client.EVM().DeployContractFromFilename(ctx, filenameActor) - //send $ to contract - //transfer 1 attoFIL to contract + // send $ to contract + // transfer 1 attoFIL to contract sendAmount := big.MustFromString("1") client.EVM().TransferValueOrFail(ctx, fromAddr, contractAddr, sendAmount) @@ -488,26 +488,26 @@ func TestFEVMSendGasLimit(t *testing.T) { // TestFEVMDelegateCall deploys the two contracts in TestFEVMDelegateCall but instead of A calling B, A calls A which should cause A to cause A in an infinite loop and should give a reasonable error func TestFEVMDelegateCallRecursiveFail(t *testing.T) { - //TODO change the gas limit of this invocation and confirm that the number of errors is + // TODO change the gas limit of this invocation and confirm that the number of errors is // different ctx, cancel, client := kit.SetupFEVMTest(t) defer cancel() - //install contract Actor + // install contract Actor filenameActor := "contracts/DelegatecallStorage.hex" fromAddr, actorAddr := client.EVM().DeployContractFromFilename(ctx, filenameActor) - //any data will do for this test that fails + // any data will do for this test that fails inputDataContract := inputDataFromFrom(ctx, t, client, actorAddr) inputDataValue := inputDataFromArray([]byte{7}) inputData := append(inputDataContract, inputDataValue...) - //verify that we run out of gas then revert. + // verify that we run out of gas then revert. _, wait, err := client.EVM().InvokeContractByFuncName(ctx, fromAddr, actorAddr, "setVarsSelf(address,uint256)", inputData) require.Error(t, err) require.Equal(t, exitcode.ExitCode(33), wait.Receipt.ExitCode) - //assert no fatal errors but still there are errors:: + // assert no fatal errors but still there are errors:: errorAny := "fatal error" require.NotContains(t, err.Error(), errorAny) } @@ -522,11 +522,11 @@ func TestFEVMTestSendValueThroughContractsAndDestroy(t *testing.T) { fromAddr := client.DefaultKey.Address t.Log("from - ", fromAddr) - //create contract A + // create contract A filenameStorage := "contracts/ValueSender.hex" fromAddr, contractAddr := client.EVM().DeployContractFromFilename(ctx, filenameStorage) - //create contract B + // create contract B ret, _, err := client.EVM().InvokeContractByFuncName(ctx, fromAddr, contractAddr, "createB()", []byte{}) require.NoError(t, err) @@ -536,7 +536,7 @@ func TestFEVMTestSendValueThroughContractsAndDestroy(t *testing.T) { require.NoError(t, err) t.Log("contractBAddress - ", contractBAddress) - //self destruct contract B + // self destruct contract B _, _, err = client.EVM().InvokeContractByFuncName(ctx, fromAddr, contractBAddress, "selfDestruct()", []byte{}) require.NoError(t, err) @@ -554,7 +554,7 @@ func TestFEVMRecursiveFuncCall(t *testing.T) { ctx, cancel, client := kit.SetupFEVMTest(t) defer cancel() - //install contract Actor + // install contract Actor filenameActor := "contracts/StackFunc.hex" fromAddr, actorAddr := client.EVM().DeployContractFromFilename(ctx, filenameActor) @@ -579,7 +579,7 @@ func TestFEVMRecursiveActorCall(t *testing.T) { ctx, cancel, client := kit.SetupFEVMTest(t) defer cancel() - //install contract Actor + // install contract Actor filenameActor := "contracts/RecCall.hex" fromAddr, actorAddr := client.EVM().DeployContractFromFilename(ctx, filenameActor) @@ -636,7 +636,7 @@ func TestFEVMRecursiveActorCallEstimate(t *testing.T) { ctx, cancel, client := kit.SetupFEVMTest(t) defer cancel() - //install contract Actor + // install contract Actor filenameActor := "contracts/ExternalRecursiveCallSimple.hex" _, actorAddr := client.EVM().DeployContractFromFilename(ctx, filenameActor) @@ -737,8 +737,8 @@ func TestFEVMDeployWithValue(t *testing.T) { ctx, cancel, client := kit.SetupFEVMTest(t) defer cancel() - //testValue is the amount sent when the contract is created - //at the end we check that the new contract has a balance of testValue + // testValue is the amount sent when the contract is created + // at the end we check that the new contract has a balance of testValue testValue := big.NewInt(20) // deploy DeployValueTest which creates NewContract @@ -747,14 +747,14 @@ func TestFEVMDeployWithValue(t *testing.T) { filenameActor := "contracts/DeployValueTest.hex" fromAddr, idAddr := client.EVM().DeployContractFromFilenameWithValue(ctx, filenameActor, testValue) - //call getNewContractBalance to find the value of NewContract + // call getNewContractBalance to find the value of NewContract ret, _, err := client.EVM().InvokeContractByFuncName(ctx, fromAddr, idAddr, "getNewContractBalance()", []byte{}) require.NoError(t, err) contractBalance, err := decodeOutputToUint64(ret) require.NoError(t, err) - //require balance of NewContract is testValue + // require balance of NewContract is testValue require.Equal(t, testValue.Uint64(), contractBalance) } @@ -762,39 +762,39 @@ func TestFEVMDestroyCreate2(t *testing.T) { ctx, cancel, client := kit.SetupFEVMTest(t) defer cancel() - //deploy create2 factory contract + // deploy create2 factory contract filename := "contracts/Create2Factory.hex" fromAddr, idAddr := client.EVM().DeployContractFromFilename(ctx, filename) - //construct salt for create2 + // construct salt for create2 salt := make([]byte, 32) _, err := rand.Read(salt) require.NoError(t, err) - //deploy contract using create2 factory + // deploy contract using create2 factory selfDestructAddress, _, err := client.EVM().InvokeContractByFuncName(ctx, fromAddr, idAddr, "deploy(bytes32)", salt) require.NoError(t, err) - //convert to filecoin actor address so we can call InvokeContractByFuncName + // convert to filecoin actor address so we can call InvokeContractByFuncName ea, err := ethtypes.CastEthAddress(selfDestructAddress[12:]) require.NoError(t, err) selfDestructAddressActor, err := ea.ToFilecoinAddress() require.NoError(t, err) - //read sender property from contract + // read sender property from contract ret, _, err := client.EVM().InvokeContractByFuncName(ctx, fromAddr, selfDestructAddressActor, "sender()", []byte{}) require.NoError(t, err) - //assert contract has correct data + // assert contract has correct data ethFromAddr := inputDataFromFrom(ctx, t, client, fromAddr) require.Equal(t, ethFromAddr, ret) - //run test() which 1.calls sefldestruct 2. verifies sender() is the correct value 3. attempts and fails to deploy via create2 + // run test() which 1.calls sefldestruct 2. verifies sender() is the correct value 3. attempts and fails to deploy via create2 testSenderAddress, _, err := client.EVM().InvokeContractByFuncName(ctx, fromAddr, idAddr, "test(address)", selfDestructAddress) require.NoError(t, err) require.Equal(t, testSenderAddress, ethFromAddr) - //read sender() but get response of 0x0 because of self destruct + // read sender() but get response of 0x0 because of self destruct senderAfterDestroy, _, err := client.EVM().InvokeContractByFuncName(ctx, fromAddr, selfDestructAddressActor, "sender()", []byte{}) require.NoError(t, err) require.Equal(t, []byte{}, senderAfterDestroy) @@ -804,11 +804,11 @@ func TestFEVMDestroyCreate2(t *testing.T) { require.NoError(t, err) require.Equal(t, newAddressSelfDestruct, selfDestructAddress) - //verify sender() property is correct + // verify sender() property is correct senderSecondCall, _, err := client.EVM().InvokeContractByFuncName(ctx, fromAddr, selfDestructAddressActor, "sender()", []byte{}) require.NoError(t, err) - //assert contract has correct data + // assert contract has correct data require.Equal(t, ethFromAddr, senderSecondCall) } @@ -880,11 +880,11 @@ func TestFEVMTestDeployOnTransfer(t *testing.T) { fromAddr := client.DefaultKey.Address t.Log("from - ", fromAddr) - //create contract A + // create contract A filenameStorage := "contracts/ValueSender.hex" fromAddr, contractAddr := client.EVM().DeployContractFromFilename(ctx, filenameStorage) - //send to some random address. + // send to some random address. params := [32]byte{} params[30] = 0xff randomAddr, err := ethtypes.CastEthAddress(params[12:]) @@ -908,7 +908,7 @@ func TestFEVMProxyUpgradeable(t *testing.T) { ctx, cancel, client := kit.SetupFEVMTest(t) defer cancel() - //install transparently upgradeable proxy + // install transparently upgradeable proxy proxyFilename := "contracts/TransparentUpgradeableProxy.hex" fromAddr, contractAddr := client.EVM().DeployContractFromFilename(ctx, proxyFilename) @@ -920,7 +920,7 @@ func TestFEVMGetBlockDifficulty(t *testing.T) { ctx, cancel, client := kit.SetupFEVMTest(t) defer cancel() - //install contract + // install contract filenameActor := "contracts/GetDifficulty.hex" fromAddr, contractAddr := client.EVM().DeployContractFromFilename(ctx, filenameActor) @@ -933,11 +933,11 @@ func TestFEVMTestCorrectChainID(t *testing.T) { ctx, cancel, client := kit.SetupFEVMTest(t) defer cancel() - //install contract + // install contract filenameActor := "contracts/Blocktest.hex" fromAddr, contractAddr := client.EVM().DeployContractFromFilename(ctx, filenameActor) - //run test + // run test _, _, err := client.EVM().InvokeContractByFuncName(ctx, fromAddr, contractAddr, "testChainID()", []byte{}) require.NoError(t, err) } @@ -946,7 +946,7 @@ func TestFEVMGetChainPropertiesBlockTimestamp(t *testing.T) { ctx, cancel, client := kit.SetupFEVMTest(t) defer cancel() - //install contract + // install contract filenameActor := "contracts/Blocktest.hex" fromAddr, contractAddr := client.EVM().DeployContractFromFilename(ctx, filenameActor) @@ -966,7 +966,7 @@ func TestFEVMGetChainPropertiesBlockNumber(t *testing.T) { ctx, cancel, client := kit.SetupFEVMTest(t) defer cancel() - //install contract + // install contract filenameActor := "contracts/Blocktest.hex" fromAddr, contractAddr := client.EVM().DeployContractFromFilename(ctx, filenameActor) @@ -986,18 +986,18 @@ func TestFEVMGetChainPropertiesBlockHash(t *testing.T) { ctx, cancel, client := kit.SetupFEVMTest(t) defer cancel() - //install contract + // install contract filenameActor := "contracts/Blocktest.hex" fromAddr, contractAddr := client.EVM().DeployContractFromFilename(ctx, filenameActor) - //block hash check + // block hash check ret, wait, err := client.EVM().InvokeContractByFuncName(ctx, fromAddr, contractAddr, "getBlockhashPrevious()", []byte{}) expectedBlockHash := hex.EncodeToString(ret) require.NoError(t, err) ethBlock := client.EVM().GetEthBlockFromWait(ctx, wait) - //in solidity we get the parent block hash because the current block hash doesnt exist at that execution context yet - //so we compare the parent hash here in the test + // in solidity we get the parent block hash because the current block hash doesnt exist at that execution context yet + // so we compare the parent hash here in the test require.Equal(t, "0x"+expectedBlockHash, ethBlock.ParentHash.String()) } @@ -1005,7 +1005,7 @@ func TestFEVMGetChainPropertiesBaseFee(t *testing.T) { ctx, cancel, client := kit.SetupFEVMTest(t) defer cancel() - //install contract + // install contract filenameActor := "contracts/Blocktest.hex" fromAddr, contractAddr := client.EVM().DeployContractFromFilename(ctx, filenameActor) @@ -1037,15 +1037,19 @@ func TestFEVMErrorParsing(t *testing.T) { "failCustom()": customError, } { sig := sig - expected := fmt.Sprintf("exit 33, revert reason: %s, vm error", expected) + expected := expected t.Run(sig, func(t *testing.T) { entryPoint := kit.CalcFuncSignature(sig) t.Run("EthCall", func(t *testing.T) { - _, err := e.EthCall(ctx, ethtypes.EthCall{ + _, err = e.EthCall(ctx, ethtypes.EthCall{ To: &contractAddrEth, Data: entryPoint, }, ethtypes.NewEthBlockNumberOrHashFromPredefined("latest")) - require.ErrorContains(t, err, expected) + require.Error(t, err) + + var dataErr *api.ErrExecutionReverted + require.ErrorAs(t, err, &dataErr, "Expected error to be ErrExecutionReverted") + require.Contains(t, dataErr.Data, expected, "Error data should contain the expected error") }) t.Run("EthEstimateGas", func(t *testing.T) { gasParams, err := json.Marshal(ethtypes.EthEstimateGasParams{Tx: ethtypes.EthCall{ @@ -1055,7 +1059,10 @@ func TestFEVMErrorParsing(t *testing.T) { require.NoError(t, err) _, err = e.EthEstimateGas(ctx, gasParams) - require.ErrorContains(t, err, expected) + require.Error(t, err) + var dataErr *api.ErrExecutionReverted + require.ErrorAs(t, err, &dataErr, "Expected error to be ErrExecutionReverted") + require.Contains(t, dataErr.Data, expected, "Error data should contain the expected error") }) }) } @@ -1436,3 +1443,99 @@ func TestEthGetBlockByNumber(t *testing.T) { require.NotNil(t, pendingBlock) require.True(t, pendingBlock.Number >= latest) } + +func TestEthCall(t *testing.T) { + ctx, cancel, client := kit.SetupFEVMTest(t) + defer cancel() + + filename := "contracts/Errors.hex" + fromAddr, contractAddr := client.EVM().DeployContractFromFilename(ctx, filename) + + divideByZeroSignature := kit.CalcFuncSignature("failDivZero()") + + _, _, err := client.EVM().InvokeContractByFuncName(ctx, fromAddr, contractAddr, "failDivZero()", []byte{}) + require.Error(t, err) + + latestBlock, err := client.EthBlockNumber(ctx) + require.NoError(t, err) + + contractAddrEth, err := ethtypes.EthAddressFromFilecoinAddress(contractAddr) + require.NoError(t, err) + + callParams := ethtypes.EthCall{ + From: nil, + To: &contractAddrEth, + Data: divideByZeroSignature, + } + + t.Run("FailedToProcessBlockParam", func(t *testing.T) { + invalidBlockNumber := latestBlock + 1000 + _, err = client.EthCall(ctx, callParams, ethtypes.NewEthBlockNumberOrHashFromNumber(invalidBlockNumber)) + require.Error(t, err) + require.Contains(t, err.Error(), "requested a future epoch (beyond 'latest')") + }) + + t.Run("DivideByZeroError", func(t *testing.T) { + _, err = client.EthCall(ctx, callParams, ethtypes.NewEthBlockNumberOrHashFromNumber(latestBlock)) + require.Error(t, err) + + var dataErr *api.ErrExecutionReverted + require.ErrorAs(t, err, &dataErr, "Expected error to be ErrExecutionReverted") + require.Contains(t, dataErr.Message, "execution reverted", "Expected 'execution reverted' message") + + // Get the error data + require.Equal(t, dataErr.Data, "DivideByZero()", "Expected error data to contain 'DivideByZero()'") + }) +} + +func TestEthEstimateGas(t *testing.T) { + ctx, cancel, client := kit.SetupFEVMTest(t) + defer cancel() + + _, ethAddr, filAddr := client.EVM().NewAccount() + + kit.SendFunds(ctx, t, client, filAddr, types.FromFil(10)) + + filename := "contracts/Errors.hex" + _, contractAddr := client.EVM().DeployContractFromFilename(ctx, filename) + + contractAddrEth, err := ethtypes.EthAddressFromFilecoinAddress(contractAddr) + require.NoError(t, err) + + testCases := []struct { + name string + function string + expectedError string + expectedErrMsg string + }{ + {"DivideByZero", "failDivZero()", "DivideByZero()", "execution reverted"}, + {"Assert", "failAssert()", "Assert()", "execution reverted"}, + {"RevertWithReason", "failRevertReason()", "Error(my reason)", "execution reverted"}, + {"RevertEmpty", "failRevertEmpty()", "", "execution reverted"}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + signature := kit.CalcFuncSignature(tc.function) + + callParams := ethtypes.EthCall{ + From: ðAddr, + To: &contractAddrEth, + Data: signature, + } + + gasParams, err := json.Marshal(ethtypes.EthEstimateGasParams{Tx: callParams}) + require.NoError(t, err, "Error marshaling gas params") + + _, err = client.EthEstimateGas(ctx, gasParams) + + if tc.expectedError != "" { + require.Error(t, err) + var dataErr *api.ErrExecutionReverted + require.ErrorAs(t, err, &dataErr, "Expected error to be ErrExecutionReverted") + require.Equal(t, tc.expectedErrMsg, dataErr.Message) + require.Contains(t, tc.expectedError, dataErr.Data) + } + }) + } +} diff --git a/node/impl/full/eth.go b/node/impl/full/eth.go index a3164c000a..2a422509b3 100644 --- a/node/impl/full/eth.go +++ b/node/impl/full/eth.go @@ -1433,9 +1433,11 @@ func (a *EthModule) applyMessage(ctx context.Context, msg *types.Message, tsk ty } if res.MsgRct.ExitCode.IsError() { - reason := parseEthRevert(res.MsgRct.Return) - return nil, xerrors.Errorf("message execution failed: exit %s, revert reason: %s, vm error: %s", res.MsgRct.ExitCode, reason, res.Error) + return nil, api.NewErrExecutionReverted( + parseEthRevert(res.MsgRct.Return), + ) } + return res, nil } @@ -1474,8 +1476,16 @@ func (a *EthModule) EthEstimateGas(ctx context.Context, p jsonrpc.RawParams) (et // information. msg.GasLimit = buildconstants.BlockGasLimit if _, err2 := a.applyMessage(ctx, msg, ts.Key()); err2 != nil { + // If err2 is an ExecutionRevertedError, return it + var ed *api.ErrExecutionReverted + if errors.As(err2, &ed) { + return ethtypes.EthUint64(0), err2 + } + + // Otherwise, return the error from applyMessage with failed to estimate gas err = err2 } + return ethtypes.EthUint64(0), xerrors.Errorf("failed to estimate gas: %w", err) } @@ -1628,7 +1638,6 @@ func (a *EthModule) EthCall(ctx context.Context, tx ethtypes.EthCall, blkParam e } if msg.To == builtintypes.EthereumAddressManagerActorAddr { - // As far as I can tell, the Eth API always returns empty on contract deployment return ethtypes.EthBytes{}, nil } else if len(invokeResult.MsgRct.Return) > 0 { return cbg.ReadByteArray(bytes.NewReader(invokeResult.MsgRct.Return), uint64(len(invokeResult.MsgRct.Return)))