diff --git a/changelog.md b/changelog.md index eb19739b35..e34c863306 100644 --- a/changelog.md +++ b/changelog.md @@ -16,6 +16,8 @@ * [3349](https://github.com/zeta-chain/node/pull/3349) - implement new bitcoin rpc in zetaclient with improved performance and observability * [3379](https://github.com/zeta-chain/node/pull/3379) - add Avalanche, Arbitrum and World Chain in chain info * [3390](https://github.com/zeta-chain/node/pull/3390) - orchestrator V2: EVM observer-signer +* [3326](https://github.com/zeta-chain/node/pull/3326) - improve error messages for cctx status object + ### Fixes diff --git a/docs/openapi/openapi.swagger.yaml b/docs/openapi/openapi.swagger.yaml index b94abdb160..97b7713e7b 100644 --- a/docs/openapi/openapi.swagger.yaml +++ b/docs/openapi/openapi.swagger.yaml @@ -60377,6 +60377,11 @@ definitions: type: string format: int64 description: when the CCTX was created. only populated on new transactions. + error_message_revert: + type: string + title: |- + error_message_revert carries information about the revert outbound tx , + which is created if the first outbound tx fails zetacoreemissionsParams: type: object properties: diff --git a/docs/zetacore/status_message.md b/docs/zetacore/status_message.md new file mode 100644 index 0000000000..7ae520a494 --- /dev/null +++ b/docs/zetacore/status_message.md @@ -0,0 +1,78 @@ +# CCTX status message + +The cctx object contains a field `cctx_status` , which has the following structure +```go +type Status struct { + Status CctxStatus `protobuf:"varint,1,opt,name=status,proto3,enum=zetachain.zetacore.crosschain.CctxStatus" json:"status,omitempty"` + StatusMessage string `protobuf:"bytes,2,opt,name=status_message,json=statusMessage,proto3" json:"status_message,omitempty"` + ErrorMessage string `protobuf:"bytes,6,opt,name=error_message,json=errorMessage,proto3" json:"error_message,omitempty"` + LastUpdateTimestamp int64 `protobuf:"varint,3,opt,name=lastUpdate_timestamp,json=lastUpdateTimestamp,proto3" json:"lastUpdate_timestamp,omitempty"` + IsAbortRefunded bool `protobuf:"varint,4,opt,name=isAbortRefunded,proto3" json:"isAbortRefunded,omitempty"` + CreatedTimestamp int64 `protobuf:"varint,5,opt,name=created_timestamp,json=createdTimestamp,proto3" json:"created_timestamp,omitempty"` + ErrorMessageRevert string `protobuf:"bytes,7,opt,name=error_message_revert,json=errorMessageRevert,proto3" json:"error_message_revert,omitempty"` +} +``` + +## Status +This is the most updated status for the cctx. This can be one of the following values +- `PendingInbound` : The cctx is pending for the inbound to be finalized, this is an intermediate status used by the protocol only +- `PendingOutbound` : This means that the inbound has been finalized, and the outbound is pending +- `OutboundMined` : The outbound has been successfully mined. This is a terminal status +- `Aborted` : The cctx has been aborted. This is a terminal status +- `PendingRevert` : The the cctx failed at some step and is pending for the revert to be finalized +- `Reverted` : The cctx has been successfully reverted. This is a terminal status + +### StatusMessage +The status message provides a some details about the current status.This is primarily meant for the user to quickly understand the status of the cctx. +### LastUpdateTimestamp +The last time the status was updated +### IsAbortRefunded +This is a boolean value which is true if the cctx has been refunded after being aborted or not . +### CreatedTimestamp +The time when the cctx was created +### ErrorMessage and ErrorMessageRevert +A cctx can have a maximum of two outbound params. We can refer to the first outbound as `outbound` and the second as `revert`. +- A normal flow for a cctx is to go from `PendingOutbound` -> `OutboundMined` , which creates a single outbound +- A cctx where the outbound fails has the transition `PendingOutbound` -> `PendingRevert` -> `Reverted` , which creates two outbounds +- Any of the above two flows can abort the cctx at some point that can create either one or two outbounds + + - The `ErrorMessage` field only contains a value if the original outbound failed. It contains details about the error that caused the outbound to fail + - The `ErrorMessageRevert` field only contains a value if the revert outbound failed. It contains details about the error that caused the revert outbound to fail. + +### StatusMessage fieldsand how to interpret it +- `initiating outbound` : The inbound votes have been successfully finalized, and the protocol is starting the outbound process +- `revert failed to be processed` : The revert failed. This message also means that the initial outbound has failed. +- `outbound failed` : The outbound failed, The protocol would try to create a revert either in the same block or schedule one to be picked up by zetaclient +- `outbound failed for admin tx` : The outbound failed for an admin transaction, in this case we do not revert the cctx +- `outbound failed unable to process` : The outbound processing failed at the protocol level. When this happens, the protocol sets the cctx to aborted. +- `outbound failed but the universal contract did not revert` : The outbound/deposit failed, but the contract did not revert, + this is most likely caused by an internal error in the protocol. The CCTX is this case is aborted. Users can try connecting with the zetachain team to get a refund +- `cctx aborted through MsgAbortStuckCCTX` : The cctx was aborted manually by an admin command + + +### ErrorMessage and ErrorMessageRevert fields and how to interpret them +- The ErrorMessage and ErrorMessageRevert would contain the following fields. The protocol generates the fields tagged as internal. +``` + - type : Type of error ,Supported types are internal_error(error from the protocol), contract_call_error (error from ZEVM call) + - message [Internal]: A message from the protocol to explain the error + - error : Error message related to the call + - method: The method that was called by the protocol + - contract: The contract that his method was called on + - args:The arguments that were used for this call + - revert_reason: Revert reason from the smart contract call, if any +``` +Sample error message for a failed deposit (Note empty fields are discarded by default) + +```json +{ + "type": "contract_call_error", + "message": "contract call failed when calling EVM with data", + "method": "depositAndCall0", + "contract": "0x733aB8b06DDDEf27Eaa72294B0d7c9cEF7f12db9", + "args": "[{[]0xdFb74337c53141bf912101b0Ee770FA8e2DCB921 1337} 0x13A0c5930C028511Dc02665E7285134B6d11A5f410000000000000000 0xD28D6A0b8189305551a0A8bd247a6ECa9CE781Ca [114 101 118 101114 116]]", + "error": "execution reverted: ret 0x: evm transaction execution failed", +} +``` + +- `outbound tx failed to be executed on connected chain` : `revert tx failed to be executed on connected chain` : The outbound/revert transaction failed to be executed on the connected chain. +- `coin type [CoinType] not supported for revert when source chain is Zetachain` : The coin type is not supported for revert when the source chain is Zetachain. diff --git a/e2e/e2etests/test_bitcoin_deposit_abort_with_low_deposit_fee.go b/e2e/e2etests/test_bitcoin_deposit_abort_with_low_deposit_fee.go index 6a4d491daa..6ec1562f46 100644 --- a/e2e/e2etests/test_bitcoin_deposit_abort_with_low_deposit_fee.go +++ b/e2e/e2etests/test_bitcoin_deposit_abort_with_low_deposit_fee.go @@ -30,5 +30,5 @@ func TestBitcoinDepositAndAbortWithLowDepositFee(r *runner.E2ERunner, args []str require.Equal(r, cctx.GetCurrentOutboundParam().Amount.Uint64(), uint64(0)) // check cctx error message - require.Equal(r, cctx.CctxStatus.ErrorMessage, types.InboundStatus_INSUFFICIENT_DEPOSITOR_FEE.String()) + require.Contains(r, cctx.CctxStatus.StatusMessage, types.InboundStatus_INSUFFICIENT_DEPOSITOR_FEE.String()) } diff --git a/e2e/e2etests/test_bitcoin_deposit_and_call_revert_with_dust.go b/e2e/e2etests/test_bitcoin_deposit_and_call_revert_with_dust.go index 1d4dec34e0..bee2359da9 100644 --- a/e2e/e2etests/test_bitcoin_deposit_and_call_revert_with_dust.go +++ b/e2e/e2etests/test_bitcoin_deposit_and_call_revert_with_dust.go @@ -52,5 +52,8 @@ func TestBitcoinDepositAndCallRevertWithDust(r *runner.E2ERunner, args []string) cctx := utils.WaitCctxAbortedByInboundHash(r.Ctx, r, txHash.String(), r.CctxClient) require.True(r, cctx.GetCurrentOutboundParam().Amount.Uint64() < constant.BTCWithdrawalDustAmount) - require.True(r, strings.Contains(cctx.CctxStatus.ErrorMessage, crosschaintypes.ErrInvalidWithdrawalAmount.Error())) + require.True( + r, + strings.Contains(cctx.CctxStatus.ErrorMessageRevert, crosschaintypes.ErrInvalidWithdrawalAmount.Error()), + ) } diff --git a/pkg/errors/crosschain_error_message.go b/pkg/errors/crosschain_error_message.go new file mode 100644 index 0000000000..5ab15a4cb5 --- /dev/null +++ b/pkg/errors/crosschain_error_message.go @@ -0,0 +1,214 @@ +package errors + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/ethereum/go-ethereum/common" +) + +type Type string + +const ( + ContractCallError Type = "contract_call_error" + InternalError Type = "internal_error" +) + +// CCTXErrorMessage is used to provide a detailed error message for a cctx + +// The message and error fields together provide a view on what cause the outbound or revert to fail +// Message : Internal message from the protocol to indicate what went wrong. +// Error : This is the error from the protocol + +// Zevm specific fields. These fields are only available if the outbound or revert was involved in a ZEVM transaction, as a deposit +// Contract: Contract called +// Args: Arguments provided to the call +// Method: Contract method +// RevertReason: Reason for the revert if available +type CCTXErrorMessage struct { + Type Type `json:"type,omitempty"` + Message string `json:"message,omitempty"` + Error string `json:"error,omitempty"` + Method string `json:"method,omitempty"` + Contract string `json:"contract,omitempty"` + Args string `json:"args,omitempty"` + RevertReason string `json:"revert_reason,omitempty"` +} + +// NewCCTXErrorMessage creates a new CCTXErrorMessage struct +func NewCCTXErrorMessage(message string) CCTXErrorMessage { + return CCTXErrorMessage{ + Message: message, + Type: InternalError, + } +} + +// NewZEVMErrorMessage is s special case of NewCCTXErrorMessage which is called by ZEVM specific code. +// This creates a CCTXErrorMessage with ZEVM specific fields like Method, Contract, Args +func NewZEVMErrorMessage( + method string, + contract common.Address, + args interface{}, + message string, + err error, +) CCTXErrorMessage { + c := CCTXErrorMessage{ + Method: method, + Contract: contract.String(), + Args: fmt.Sprintf("%v", args), + Message: message, + Type: ContractCallError, + } + if err != nil { + c.Error = err.Error() + } + return c +} + +// NewCCTXErrorJSONMessage creates a new CCTXErrorMessage struct and returns it as a JSON string. +// The error field can be of the following types +// 1 - Nil : If the error is nil, we don't need to do anything +// 2 - ErrorString : If the error is a string, we add the error and Pack it into a CCTXErrorMessage struct +// 3 - CCTXErrorMessage : If the error is already a CCTXErrorMessage, we should unpack it and return the JSON string, +// 4 - If it the error is a chain of errors, we should unpack each error and add it to the CCTXErrorMessage struct error field +// 5 - If the error is a chain of errors and one of the errors is a CCTXErrorMessage, we should unpack the CCTXErrorMessage and return the JSON string and add the errors into the error field +// This function does not return an error, if the marshalling fails, it returns a string representation of the CCTXErrorMessage +func NewCCTXErrorJSONMessage(message string, newError error) string { + m := NewCCTXErrorMessage(message) + + if newError != nil { + // 1. Split the error message into parts where each part is a separate error message. Json or a simple string + errorLists := SplitErrorMessage(newError.Error()) + + for _, e := range errorLists { + // + parsed, err := ParseCCTXErrorMessage(e) + switch { + // 3. parsing failed so this is not a CCTXErrorMessage json string. Wrap the error into the CCTXErrorMessage.Error field + case err != nil: + { + m.WrapError(e) + } + // parsing succeeded, this is a CCTXErrorMessage unpack it. Wrap the error into the CCTXErrorMessage.Error field and assign the other fields + // The message fields are overwritten , it should contain only a single message describing the error. The actual error chain is added to the error field + // the method, contract, args, revert_reason fields are present only in ZEVM errors and are directly added to the CCTXErrorMessage struct + default: + { + m.Message = parsed.Message + m.Method = parsed.Method + m.Contract = parsed.Contract + m.Args = parsed.Args + m.RevertReason = parsed.RevertReason + m.Type = parsed.Type + m.WrapError(parsed.Error) + } + } + } + } + + jsonString, err := m.ToJSON() + if err != nil { + return fmt.Sprintf("json marshalling failed %s,%s", err.Error(), m.String()) + } + return jsonString +} + +// SplitErrorMessage splits the error message into parts it treats a JSON formatted error message as a single part + +// Example inputs that this function can handle +// 1. "errorString1:errorString2:errorString3" : deposit error: can't call a non-contract address, processing error: withdraw amount 100 is less than dust amount 1000: invalid withdrawal amount + +// 2. "errorString1:{jsonObject}" : deposit error: {"message":"contract call failed when calling EVM with data","error":"execution reverted: ret 0x: evm transaction execution failed", +// "method":"depositAndCall0","contract":"0x733aB8b06DDDEf27Eaa72294B0d7c9cEF7f12db9, +// args: "[{[] 0xdFb74337c53141bf912101b0Ee770FA8e2DCB921 1337} 0x13A0c5930C028511Dc02665E7285134B6d11A5f4 10000000000000000 +// 0xE83192f6301d090EFB2F38824A12E98877F66fe3 [101 53 52 55 51 50 100 55 50 55 57 99 56 97 99 100 48 97 97 101 55 57 98 51 101 100 99 101 50 55 99 99 50 57 97 49 48 51 56 100 48 102 102 +// 54 52 57 98 53 101 51 56 101 55 51 53 56 55 101 100 55 56 102 49 48 101 101 97 49 52 56 100 97 55 97 51 97 57 100 98 49 101 101 98 55 51 57 100 49 52 54 53 56 55 99 101 99 48 56 54 55]]","revert_reason":""} +func SplitErrorMessage(input string) []string { + var result []string + + // Find the JSON object boundaries + start := strings.Index(input, "{") + end := strings.LastIndex(input, "}") + + // The error chain does not have a json formatted error message so return the error as is + if start == -1 || end == -1 { + return strings.Split(input, ":") + } + + // We have a JSON formatted error message + // Extract JSON part and add to result + jsonStr := input[start : end+1] + result = append(result, jsonStr) + + // Extract errors before JSON part + prefix := strings.TrimSpace(input[:start]) + if prefix != "" && strings.HasSuffix(prefix, ":") { + prefix = strings.TrimSuffix(prefix, ":") + prefixParts := strings.Split(prefix, ":") + + if len(prefixParts) > 0 { + prefixErrors := make([]string, len(prefixParts)) + for i, part := range prefixParts { + prefixErrors[i] = strings.TrimSpace(part) + } + result = append(prefixErrors, result...) + } + } + + // Extract errors after the json part + if end+1 < len(input) { + suffix := strings.TrimSpace(input[end+1:]) + if strings.HasPrefix(suffix, ":") { + suffix = strings.TrimSpace(strings.TrimPrefix(suffix, ":")) + suffixParts := strings.Split(suffix, ":") + if suffix != "" && len(suffixParts) > 0 { + for _, part := range suffixParts { + result = append(result, strings.TrimSpace(part)) + } + } + } + } + + return result +} + +// WrapError adds a new error to the CCTXErrorMessage struct +func (e *CCTXErrorMessage) WrapError(newError string) { + existingError := e.Error + if existingError == "" { + e.Error = newError + } else { + e.Error = fmt.Sprintf("%s:%s", existingError, newError) + } +} + +// AddRevertReason adds a revert reason to the CCTXErrorMessage struct +func (e *CCTXErrorMessage) AddRevertReason(revertReason interface{}) { + e.RevertReason = fmt.Sprintf("%v", revertReason) +} + +// ToJSON marshals an CCTXErrorMessage struct into a JSON string +func (e *CCTXErrorMessage) ToJSON() (string, error) { + jsonData, err := json.Marshal(e) + if err != nil { + return "", fmt.Errorf("error marshalling CCTXErrorMessage to JSON: %v", err) + } + return string(jsonData), nil +} + +// String returns a string representation of an CCTXErrorMessage struct +func (e *CCTXErrorMessage) String() string { + return fmt.Sprintf("Message: %s, Error: %s, Method: %s, Contract: %s, Args: %s, RevertReason: %s", + e.Message, e.Error, e.Method, e.Contract, e.Args, e.RevertReason) +} + +// ParseCCTXErrorMessage parses a JSON string into an CCTXErrorMessage struct +func ParseCCTXErrorMessage(jsonData string) (CCTXErrorMessage, error) { + var evmErrorMessage CCTXErrorMessage + err := json.Unmarshal([]byte(jsonData), &evmErrorMessage) + if err != nil { + return CCTXErrorMessage{}, fmt.Errorf("error unmarshalling JSON to CCTXErrorMessage: %v", err) + } + return evmErrorMessage, nil +} diff --git a/pkg/errors/crosschain_error_message_test.go b/pkg/errors/crosschain_error_message_test.go new file mode 100644 index 0000000000..28b4728bf8 --- /dev/null +++ b/pkg/errors/crosschain_error_message_test.go @@ -0,0 +1,196 @@ +package errors_test + +import ( + "testing" + + "cosmossdk.io/errors" + "github.com/ethereum/go-ethereum/common" + "github.com/stretchr/testify/require" + cctxerror "github.com/zeta-chain/node/pkg/errors" +) + +var ( + sampleErr = errors.New("test", 9991, "error_cause") + sampleErr2 = errors.New("test", 9992, "error_cause2") +) + +func Test_NewCCTXErrorMessage(t *testing.T) { + t.Run("TestNewCCTXErrorMessage", func(t *testing.T) { + m := cctxerror.NewCCTXErrorMessage("message") + jsonString, err := m.ToJSON() + require.NoError(t, err) + require.Equal( + t, + `{"type":"internal_error","message":"message"}`, + jsonString, + ) + }) + +} + +func Test_ZEvmErrorMessage(t *testing.T) { + t.Run("TestEvmErrorMessage", func(t *testing.T) { + contractAddress := "0xE97Ac2CA30D30de65a6FE0Ab20EDC39a623c18df" + + msg := cctxerror.NewZEVMErrorMessage( + "method", + common.HexToAddress(contractAddress), + "args", + "message", + sampleErr, + ) + msg.AddRevertReason("revert_reason") + + s, err := msg.ToJSON() + require.NoError(t, err) + + require.Equal( + t, + `{"type":"contract_call_error","message":"message","error":"error_cause","method":"method","contract":"0xE97Ac2CA30D30de65a6FE0Ab20EDC39a623c18df","args":"args","revert_reason":"revert_reason"}`, + s, + ) + }) +} + +func Test_ParseCCTXErrorMessage(t *testing.T) { + t.Run("TestParseCCTXErrorMessage", func(t *testing.T) { + m := `{"message":"contract call failed when calling EVM with data","method":"depositAndCall0","contract":"0x733aB8b06DDDEf27Eaa72294B0d7c9cEF7f12db9","args":"[{[]0xdFb74337c53141bf912101b0Ee770FA8e2DCB921 1337} 0x13A0c5930C028511Dc02665E7285134B6d11A5f410000000000000000 0xD28D6A0b8189305551a0A8bd247a6ECa9CE781Ca [114 101 118 101114 116]]","error":"execution reverted: ret 0x: evm transaction execution failed","revert_reason":""}` + parsedMsg, err := cctxerror.ParseCCTXErrorMessage(m) + require.NoError(t, err) + + require.Equal(t, "contract call failed when calling EVM with data", parsedMsg.Message) + require.Equal(t, "depositAndCall0", parsedMsg.Method) + require.Equal(t, "0x733aB8b06DDDEf27Eaa72294B0d7c9cEF7f12db9", parsedMsg.Contract) + require.Equal( + t, + "[{[]0xdFb74337c53141bf912101b0Ee770FA8e2DCB921 1337} 0x13A0c5930C028511Dc02665E7285134B6d11A5f410000000000000000 0xD28D6A0b8189305551a0A8bd247a6ECa9CE781Ca [114 101 118 101114 116]]", + parsedMsg.Args, + ) + require.Equal(t, "execution reverted: ret 0x: evm transaction execution failed", parsedMsg.Error) + require.Equal(t, "", parsedMsg.RevertReason) + }) +} + +func Test_NewCCTXErrorJsonMessage(t *testing.T) { + t.Run("Pack error into CCTXErrorMessage", func(t *testing.T) { + m := cctxerror.NewCCTXErrorJSONMessage("message", sampleErr) + require.Equal( + t, + `{"type":"internal_error","message":"message","error":"error_cause"}`, + m, + ) + }) + + t.Run("do not repack into CCTXErrorMessage if older error has been formated already", func(t *testing.T) { + m := cctxerror.NewCCTXErrorJSONMessage("message", sampleErr) + require.Equal( + t, + `{"type":"internal_error","message":"message","error":"error_cause"}`, + m, + ) + errPacked := errors.New("test", 9993, m) + m2 := cctxerror.NewCCTXErrorJSONMessage("", errPacked) + require.Equal( + t, + `{"type":"internal_error","message":"message","error":"error_cause"}`, + m2, + ) + }) + + t.Run("unpack json CCTXErrorMessage and wrap other errors into the error field", func(t *testing.T) { + m := cctxerror.NewCCTXErrorJSONMessage("message", sampleErr) + require.Equal( + t, + `{"type":"internal_error","message":"message","error":"error_cause"}`, + m, + ) + errPacked := errors.Wrap(sampleErr2, m) + errPacked2 := errors.Wrap(sampleErr, errPacked.Error()) + m2 := cctxerror.NewCCTXErrorJSONMessage("", errPacked2) + require.Equal( + t, + `{"type":"internal_error","message":"message","error":"error_cause:error_cause2:error_cause"}`, + m2, + ) + }) + +} + +func Test_SplitErrorMessage(t *testing.T) { + t.Run("SplitErrorMessage", func(t *testing.T) { + m := `{"message":"message","error":"error_cause","method":"method","contract":"contract","args":"args","revert_reason":"revert_reason"}` + errorLists := cctxerror.SplitErrorMessage(m) + + require.Len(t, errorLists, 1) + require.Equal(t, m, errorLists[0]) + }) + + t.Run("SplitErrorMessage with prefix and suffix errors", func(t *testing.T) { + m := `error_cause1:{"message":"message","error":"error_cause","method":"","contract":"","args":"","revert_reason":""}: error_cause2` + errorLists := cctxerror.SplitErrorMessage(m) + + require.Len(t, errorLists, 3) + require.Equal(t, "error_cause1", errorLists[0]) + require.Equal( + t, + `{"message":"message","error":"error_cause","method":"","contract":"","args":"","revert_reason":""}`, + errorLists[1], + ) + require.Equal(t, "error_cause2", errorLists[2]) + }) + + t.Run("SplitErrorMessage with wrapped errors", func(t *testing.T) { + m := cctxerror.NewCCTXErrorJSONMessage("message", sampleErr) + require.Equal( + t, + `{"type":"internal_error","message":"message","error":"error_cause"}`, + m, + ) + errPacked := errors.Wrap(sampleErr2, m) + errPacked2 := errors.Wrap(sampleErr, errPacked.Error()) + + errorLists := cctxerror.SplitErrorMessage(errPacked2.Error()) + require.Len(t, errorLists, 3) + require.Equal( + t, + `{"type":"internal_error","message":"message","error":"error_cause"}`, + errorLists[0], + ) + require.Equal(t, "error_cause2", errorLists[1]) + require.Equal(t, "error_cause", errorLists[2]) + }) +} + +func TestCCTXErrorMessage_WrapError(t *testing.T) { + t.Run("WrapError when error field exists", func(t *testing.T) { + contractAddress := "0xE97Ac2CA30D30de65a6FE0Ab20EDC39a623c18df" + + msg := cctxerror.NewZEVMErrorMessage( + "method", + common.HexToAddress(contractAddress), + "args", + "message", + sampleErr, + ) + msg.AddRevertReason("revert_reason") + msg.WrapError("test_error") + + require.Equal(t, "error_cause:test_error", msg.Error) + }) + + t.Run("WrapError when error field does not exist", func(t *testing.T) { + contractAddress := "0xE97Ac2CA30D30de65a6FE0Ab20EDC39a623c18df" + + msg := cctxerror.NewZEVMErrorMessage( + "method", + common.HexToAddress(contractAddress), + "args", + "message", + nil, + ) + msg.AddRevertReason("revert_reason") + msg.WrapError("test_error") + + require.Equal(t, "test_error", msg.Error) + }) +} diff --git a/proto/zetachain/zetacore/crosschain/cross_chain_tx.proto b/proto/zetachain/zetacore/crosschain/cross_chain_tx.proto index 63270d4743..bd46fc8b9e 100644 --- a/proto/zetachain/zetacore/crosschain/cross_chain_tx.proto +++ b/proto/zetachain/zetacore/crosschain/cross_chain_tx.proto @@ -121,6 +121,9 @@ message Status { bool isAbortRefunded = 4; // when the CCTX was created. only populated on new transactions. int64 created_timestamp = 5; + // error_message_revert carries information about the revert outbound tx , + // which is created if the first outbound tx fails + string error_message_revert = 7; } // ProtocolContractVersion represents the version of the protocol contract used diff --git a/typescript/zetachain/zetacore/crosschain/cross_chain_tx_pb.d.ts b/typescript/zetachain/zetacore/crosschain/cross_chain_tx_pb.d.ts index 73a807ca5b..4f352b977f 100644 --- a/typescript/zetachain/zetacore/crosschain/cross_chain_tx_pb.d.ts +++ b/typescript/zetachain/zetacore/crosschain/cross_chain_tx_pb.d.ts @@ -422,6 +422,14 @@ export declare class Status extends Message { */ createdTimestamp: bigint; + /** + * error_message_revert carries information about the revert outbound tx , + * which is created if the first outbound tx fails + * + * @generated from field: string error_message_revert = 7; + */ + errorMessageRevert: string; + constructor(data?: PartialMessage); static readonly runtime: typeof proto3; diff --git a/x/crosschain/keeper/cctx_gateway_observers.go b/x/crosschain/keeper/cctx_gateway_observers.go index 9576bcfe32..da82606ba4 100644 --- a/x/crosschain/keeper/cctx_gateway_observers.go +++ b/x/crosschain/keeper/cctx_gateway_observers.go @@ -6,6 +6,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/zeta-chain/node/pkg/chains" + cctxerror "github.com/zeta-chain/node/pkg/errors" "github.com/zeta-chain/node/x/crosschain/types" ) @@ -75,7 +76,10 @@ func (c CCTXGatewayObservers) InitiateOutbound( }() if err != nil { // do not commit anything here as the CCTX should be aborted - config.CCTX.SetAbort("internal error", err.Error()) + config.CCTX.SetAbort(types.StatusMessages{ + StatusMessage: "outbound failed unable to process", + ErrorMessageOutbound: cctxerror.NewCCTXErrorJSONMessage("Unable to create outbound", err), + }) return types.CctxStatus_Aborted, err } commit() diff --git a/x/crosschain/keeper/cctx_gateway_zevm.go b/x/crosschain/keeper/cctx_gateway_zevm.go index fc300f0ebb..45eaafa9c8 100644 --- a/x/crosschain/keeper/cctx_gateway_zevm.go +++ b/x/crosschain/keeper/cctx_gateway_zevm.go @@ -1,8 +1,11 @@ package keeper import ( + "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" + cctxerror "github.com/zeta-chain/node/pkg/errors" "github.com/zeta-chain/node/x/crosschain/types" ) @@ -26,7 +29,14 @@ func (c CCTXGatewayZEVM) InitiateOutbound( // abort if CCTX inbound observation status indicates failure // this is specifically for Bitcoin inbound error 'INSUFFICIENT_DEPOSITOR_FEE' if config.CCTX.InboundParams.Status == types.InboundStatus_INSUFFICIENT_DEPOSITOR_FEE { - config.CCTX.SetAbort("observation failed", types.InboundStatus_INSUFFICIENT_DEPOSITOR_FEE.String()) + config.CCTX.SetAbort( + types.StatusMessages{ + StatusMessage: fmt.Sprintf( + "observation failed, reason %s", + types.InboundStatus_INSUFFICIENT_DEPOSITOR_FEE.String(), + ), + }, + ) return types.CctxStatus_Aborted, nil } @@ -36,9 +46,10 @@ func (c CCTXGatewayZEVM) InitiateOutbound( if err != nil && !isContractReverted { // exceptional case; internal error; should abort CCTX - config.CCTX.SetAbort( - "error during deposit that is not smart contract revert", - err.Error()) + config.CCTX.SetAbort(types.StatusMessages{ + StatusMessage: "outbound failed but the universal contract did not revert", + ErrorMessageOutbound: cctxerror.NewCCTXErrorJSONMessage("failed to deposit tokens in ZEVM", err), + }) return types.CctxStatus_Aborted, err } diff --git a/x/crosschain/keeper/cctx_gateway_zevm_test.go b/x/crosschain/keeper/cctx_gateway_zevm_test.go index 46a5dbcadb..8a526a7884 100644 --- a/x/crosschain/keeper/cctx_gateway_zevm_test.go +++ b/x/crosschain/keeper/cctx_gateway_zevm_test.go @@ -121,12 +121,10 @@ func TestKeeper_InitiateOutboundZEVM(t *testing.T) { require.Error(t, err) require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) require.Equal(t, types.CctxStatus_Aborted, newStatus) - require.True( + require.Contains( t, - strings.Contains( - cctx.CctxStatus.StatusMessage, - "error during deposit that is not smart contract revert", - ), + cctx.CctxStatus.StatusMessage, + "outbound failed but the universal contract did not revert", ) require.True(t, strings.Contains(cctx.CctxStatus.ErrorMessage, sample.ErrSample.Error())) }, diff --git a/x/crosschain/keeper/cctx_orchestrator_validate_outbound.go b/x/crosschain/keeper/cctx_orchestrator_validate_outbound.go index 6e370b4096..f3a2cbefdb 100644 --- a/x/crosschain/keeper/cctx_orchestrator_validate_outbound.go +++ b/x/crosschain/keeper/cctx_orchestrator_validate_outbound.go @@ -15,6 +15,7 @@ import ( "github.com/zeta-chain/node/pkg/chains" "github.com/zeta-chain/node/pkg/coin" + ccctxerror "github.com/zeta-chain/node/pkg/errors" "github.com/zeta-chain/node/x/crosschain/types" fungiblekeeper "github.com/zeta-chain/node/x/fungible/keeper" fungibletypes "github.com/zeta-chain/node/x/fungible/types" @@ -47,17 +48,23 @@ func (k Keeper) ValidateOutboundZEVM( if depositErr != nil && isContractReverted { tmpCtxRevert, commitRevert := ctx.CacheContext() // contract call reverted; should refund via a revert tx - err := k.processFailedOutboundOnExternalChain( + revertErr := k.processFailedOutboundOnExternalChain( tmpCtxRevert, cctx, types.CctxStatus_PendingOutbound, - depositErr.Error(), + depositErr, cctx.InboundParams.Amount, ) - if err != nil { + if revertErr != nil { + // Error here would mean the outbound tx failed and we also failed to create a revert tx. + // This is the only case where we set outbound and revert messages, + // as both the outbound and the revert failed in the same block cctx.SetAbort( - "revert failed", - fmt.Sprintf("deposit error: %s, processing error: %s", depositErr.Error(), err.Error())) + types.StatusMessages{ + StatusMessage: fmt.Sprintf("revert failed to be processed"), + ErrorMessageOutbound: ccctxerror.NewCCTXErrorJSONMessage("", depositErr), + ErrorMessageRevert: ccctxerror.NewCCTXErrorJSONMessage("", revertErr), + }) return types.CctxStatus_Aborted } @@ -110,7 +117,7 @@ func (k Keeper) ValidateOutboundObservers( // 4. Set the finalization status of the current outbound tx to executed. If a revert tx is is created, the finalization status is not set, it would get set when the revert is processed via a subsequent transaction // // This function sets CCTX status , in cases where the outbound tx is successful, but tx itself fails -// This is done because SaveSuccessfulOutbound does not set the cctx status +// This is done because HandleValidOutbound does not set the cctx status // For cases where the outbound tx is unsuccessful, the cctx status is automatically set to Aborted in the processFailedOutboundObservers function, so we can just return and error to trigger that func (k Keeper) processFailedOutboundObservers(ctx sdk.Context, cctx *types.CrossChainTx, valueReceived string) error { oldStatus := cctx.CctxStatus.Status @@ -126,7 +133,9 @@ func (k Keeper) processFailedOutboundObservers(ctx sdk.Context, cctx *types.Cros if cctx.InboundParams.CoinType == coin.CoinType_Cmd { // if the cctx is of coin type cmd or the sender chain is zeta chain, then we do not revert, the cctx is aborted cctx.GetCurrentOutboundParam().TxFinalizationStatus = types.TxFinalizationStatus_Executed - cctx.SetAbort("", "outbound failed for admin tx") + cctx.SetAbort(types.StatusMessages{ + StatusMessage: "outbound failed for admin tx", + }) } else if chains.IsZetaChain(cctx.InboundParams.SenderChainId, k.GetAuthorityKeeper().GetAdditionalChainList(ctx)) { switch cctx.InboundParams.CoinType { // Try revert if the coin-type is ZETA @@ -141,11 +150,16 @@ func (k Keeper) processFailedOutboundObservers(ctx sdk.Context, cctx *types.Cros default: { cctx.GetCurrentOutboundParam().TxFinalizationStatus = types.TxFinalizationStatus_Executed - cctx.SetAbort("", "outbound failed for non-ZETA cctx") + cctx.SetAbort(types.StatusMessages{ + StatusMessage: "outbound failed", + ErrorMessageOutbound: ccctxerror.NewCCTXErrorJSONMessage("outbound failed to be executed on connected chain", nil), + ErrorMessageRevert: ccctxerror.NewCCTXErrorJSONMessage(fmt.Sprintf("revert on ZetaChain is not supported for cctx v1 with coin type %s", cctx.InboundParams.CoinType), nil), + }) } } } else { - err := k.processFailedOutboundOnExternalChain(ctx, cctx, oldStatus, "Outbound failed, start revert", cctx.GetCurrentOutboundParam().Amount) + // We add a hardcoded message here as the error from the connected chain is not available, + err := k.processFailedOutboundOnExternalChain(ctx, cctx, oldStatus, errors.New("outbound failed to be executed on connected chain"), cctx.GetCurrentOutboundParam().Amount) if err != nil { return cosmoserrors.Wrap(err, "processFailedOutboundOnExternalChain") } @@ -160,7 +174,7 @@ func (k Keeper) processFailedOutboundOnExternalChain( ctx sdk.Context, cctx *types.CrossChainTx, oldStatus types.CctxStatus, - revertMsg string, + depositErr error, inputAmount math.Uint, ) error { switch oldStatus { @@ -213,10 +227,16 @@ func (k Keeper) processFailedOutboundOnExternalChain( return err } // Not setting the finalization status here, the required changes have been made while creating the revert tx - cctx.SetPendingRevert("", revertMsg) + cctx.SetPendingRevert(types.StatusMessages{ + StatusMessage: "outbound failed", + ErrorMessageOutbound: ccctxerror.NewCCTXErrorJSONMessage("", depositErr), + }) case types.CctxStatus_PendingRevert: cctx.GetCurrentOutboundParam().TxFinalizationStatus = types.TxFinalizationStatus_Executed - cctx.SetAbort("aborted while processing failed outbound", "outbound and revert failed") + cctx.SetAbort(types.StatusMessages{ + StatusMessage: "revert failed to be processed", + ErrorMessageRevert: ccctxerror.NewCCTXErrorJSONMessage("", depositErr), + }) } return nil } @@ -232,7 +252,7 @@ func (k Keeper) processFailedOutboundOnExternalChain( // 3. Emit an event for the successful outbound transaction if flag is provided // // This function sets CCTX status, in cases where the outbound tx is successful, but tx itself fails -// This is done because SaveSuccessfulOutbound does not set the cctx status +// This is done because HandleValidOutbound does not set the cctx status // For cases where the outbound tx is unsuccessful, the cctx status is automatically set to Aborted in the processFailedOutboundObservers function, so we can just return and error to trigger that func (k Keeper) processSuccessfulOutbound( ctx sdk.Context, @@ -243,9 +263,9 @@ func (k Keeper) processSuccessfulOutbound( oldStatus := cctx.CctxStatus.Status switch oldStatus { case types.CctxStatus_PendingRevert: - cctx.SetReverted("", "") + cctx.SetReverted() case types.CctxStatus_PendingOutbound: - cctx.SetOutboundMined("") + cctx.SetOutboundMined() default: return } @@ -273,8 +293,15 @@ func (k Keeper) processFailedZETAOutboundOnZEVM(ctx sdk.Context, cctx *types.Cro return fmt.Errorf("failed AddRevertOutbound: %s", err.Error()) } - // Trying to revert the transaction this would get set to a finalized status in the same block as this does not need a TSS singing - cctx.SetPendingRevert("", "outbound failed") + // Trying to revert the transaction, this would get set to a finalized status in the same block as this does not need a TSS singing + // The outbound failed due to majority of observer voting failure.We do not have the exact reason for the failure available on chain. + cctx.SetPendingRevert(types.StatusMessages{ + StatusMessage: "outbound failed", + ErrorMessageOutbound: ccctxerror.NewCCTXErrorJSONMessage( + "outbound failed to be executed on connected chain", + nil, + ), + }) data, err := base64.StdEncoding.DecodeString(cctx.RelayedMessage) if err != nil { return fmt.Errorf("failed decoding relayed message: %s", err.Error()) @@ -308,7 +335,8 @@ func (k Keeper) processFailedZETAOutboundOnZEVM(ctx sdk.Context, cctx *types.Cro return fmt.Errorf("failed ZETARevertAndCallContract: %s", err.Error()) } - cctx.SetReverted("", "outbound failed") + cctx.SetReverted() + if len(ctx.TxBytes()) > 0 { // add event for tendermint transaction hash format hash := tmbytes.HexBytes(tmtypes.Tx(ctx.TxBytes()).Hash()) @@ -353,7 +381,13 @@ func (k Keeper) processFailedOutboundV2(ctx sdk.Context, cctx *types.CrossChainT } // update status - cctx.SetPendingRevert("", "outbound failed") + cctx.SetPendingRevert(types.StatusMessages{ + StatusMessage: "outbound failed", + ErrorMessageOutbound: ccctxerror.NewCCTXErrorJSONMessage( + "outbound tx failed to be executed on connected chain", + nil, + ), + }) // use a temporary context to not commit any state change in case of error tmpCtx, commit := ctx.CacheContext() @@ -404,7 +438,7 @@ func (k Keeper) processFailedOutboundV2(ctx sdk.Context, cctx *types.CrossChainT commit() // tx is reverted - cctx.SetReverted("", "outbound failed") + cctx.SetReverted() // add event for tendermint transaction hash format if len(ctx.TxBytes()) > 0 { @@ -417,7 +451,13 @@ func (k Keeper) processFailedOutboundV2(ctx sdk.Context, cctx *types.CrossChainT cctx.GetCurrentOutboundParam().TxFinalizationStatus = types.TxFinalizationStatus_Executed case types.CctxStatus_PendingRevert: cctx.GetCurrentOutboundParam().TxFinalizationStatus = types.TxFinalizationStatus_Executed - cctx.SetAbort("aborted while processing failed outbound", "outbound and revert failed") + cctx.SetAbort(types.StatusMessages{ + StatusMessage: "revert failed to be processed", + ErrorMessageRevert: ccctxerror.NewCCTXErrorJSONMessage( + "revert tx failed to be executed on connected chain", + nil, + ), + }) default: return fmt.Errorf("unexpected cctx status %s", cctx.CctxStatus.Status) } diff --git a/x/crosschain/keeper/evm_deposit.go b/x/crosschain/keeper/evm_deposit.go index d6c8a77a66..037462e653 100644 --- a/x/crosschain/keeper/evm_deposit.go +++ b/x/crosschain/keeper/evm_deposit.go @@ -48,7 +48,7 @@ func (k Keeper) HandleEVMDeposit(ctx sdk.Context, cctx *types.CrossChainTx) (boo // - Return false will abort the cctx indexBytes, err := cctx.GetCCTXIndexBytes() if err != nil { - return false, err + return false, errors.Wrap(types.ErrUnableToParseCCTXIndexBytes, err.Error()) } data, err := base64.StdEncoding.DecodeString(cctx.RelayedMessage) if err != nil { diff --git a/x/crosschain/keeper/initiate_outbound.go b/x/crosschain/keeper/initiate_outbound.go index 5339cd4150..c8c42ea637 100644 --- a/x/crosschain/keeper/initiate_outbound.go +++ b/x/crosschain/keeper/initiate_outbound.go @@ -38,6 +38,6 @@ func (k Keeper) InitiateOutbound(ctx sdk.Context, config InitiateOutboundConfig) ) } - config.CCTX.SetPendingOutbound("") + config.CCTX.SetPendingOutbound(types.StatusMessages{StatusMessage: "initiating outbound"}) return cctxGateway.InitiateOutbound(ctx, config) } diff --git a/x/crosschain/keeper/initiate_outbound_test.go b/x/crosschain/keeper/initiate_outbound_test.go index 204179bea5..5b9cee84fa 100644 --- a/x/crosschain/keeper/initiate_outbound_test.go +++ b/x/crosschain/keeper/initiate_outbound_test.go @@ -76,7 +76,7 @@ func TestKeeper_InitiateOutboundZEVMDeposit(t *testing.T) { require.ErrorContains(t, err, "deposit error") require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) require.Equal(t, types.CctxStatus_Aborted, newStatus) - require.Equal(t, "deposit error", cctx.CctxStatus.ErrorMessage) + require.Contains(t, cctx.CctxStatus.ErrorMessage, "deposit error") }) t.Run( @@ -96,7 +96,7 @@ func TestKeeper_InitiateOutboundZEVMDeposit(t *testing.T) { errDeposit := fmt.Errorf("deposit failed") // Setup expected calls - // mock unsuccessful HandleEVMDeposit which reverts , i.e returns err and isContractReverted = true + // mock unsuccessful HandleEVMDeposit which reverts, i.e returns err and isContractReverted = true keepertest.MockRevertForHandleEVMDeposit(fungibleMock, receiver, amount, senderChain.ChainId, errDeposit) // mock unsuccessful GetSupportedChainFromChainID @@ -111,7 +111,7 @@ func TestKeeper_InitiateOutboundZEVMDeposit(t *testing.T) { require.Equal(t, types.CctxStatus_Aborted, newStatus) require.Contains( t, - cctx.CctxStatus.ErrorMessage, + cctx.CctxStatus.ErrorMessageRevert, "chain not supported", ) }, @@ -149,9 +149,10 @@ func TestKeeper_InitiateOutboundZEVMDeposit(t *testing.T) { require.NoError(t, err) require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) require.Equal(t, types.CctxStatus_Aborted, newStatus) + require.Contains( t, - cctx.CctxStatus.ErrorMessage, + cctx.CctxStatus.ErrorMessageRevert, "GetRevertGasLimit: foreign coin not found for sender chain", ) }) @@ -194,7 +195,7 @@ func TestKeeper_InitiateOutboundZEVMDeposit(t *testing.T) { require.Equal(t, types.CctxStatus_Aborted, newStatus) require.Contains( t, - cctx.CctxStatus.ErrorMessage, + cctx.CctxStatus.ErrorMessageRevert, "chain not supported", ) }, @@ -239,7 +240,7 @@ func TestKeeper_InitiateOutboundZEVMDeposit(t *testing.T) { require.Equal(t, types.CctxStatus_Aborted, newStatus) require.Contains( t, - cctx.CctxStatus.ErrorMessage, + cctx.CctxStatus.ErrorMessageRevert, "chain not supported", ) }, @@ -284,7 +285,7 @@ func TestKeeper_InitiateOutboundZEVMDeposit(t *testing.T) { require.NoError(t, err) require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) require.Equal(t, types.CctxStatus_Aborted, newStatus) - require.Contains(t, cctx.CctxStatus.ErrorMessage, "cannot find receiver chain nonce") + require.Contains(t, cctx.CctxStatus.ErrorMessageRevert, "cannot find receiver chain nonce") }) t.Run("unable to process zevm deposit HandleEVMDeposit revert successfully", func(t *testing.T) { @@ -321,7 +322,7 @@ func TestKeeper_InitiateOutboundZEVMDeposit(t *testing.T) { require.NoError(t, err) require.Equal(t, types.CctxStatus_PendingRevert, cctx.CctxStatus.Status) require.Equal(t, types.CctxStatus_PendingRevert, newStatus) - require.Equal(t, errDeposit.Error(), cctx.CctxStatus.ErrorMessage) + require.Contains(t, cctx.CctxStatus.ErrorMessage, errDeposit.Error()) require.Equal(t, updatedNonce, cctx.GetCurrentOutboundParam().TssNonce) }) @@ -361,7 +362,7 @@ func TestKeeper_InitiateOutboundZEVMDeposit(t *testing.T) { require.Equal(t, types.CctxStatus_Aborted, newStatus) require.Contains( t, - cctx.CctxStatus.ErrorMessage, + cctx.CctxStatus.ErrorMessageRevert, "cannot revert a revert tx", ) }, @@ -427,7 +428,7 @@ func TestKeeper_InitiateOutboundProcessCrosschainMsgPassing(t *testing.T) { require.ErrorIs(t, err, observertypes.ErrSupportedChains) require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) require.Equal(t, types.CctxStatus_Aborted, newStatus) - require.Equal(t, observertypes.ErrSupportedChains.Error(), cctx.CctxStatus.ErrorMessage) + require.Contains(t, cctx.CctxStatus.ErrorMessage, observertypes.ErrSupportedChains.Error()) }) t.Run("unable to process crosschain msg passing UpdateNonce fails", func(t *testing.T) { diff --git a/x/crosschain/keeper/msg_server_abort_stuck_cctx.go b/x/crosschain/keeper/msg_server_abort_stuck_cctx.go index 7e2505b01e..6983dcdc21 100644 --- a/x/crosschain/keeper/msg_server_abort_stuck_cctx.go +++ b/x/crosschain/keeper/msg_server_abort_stuck_cctx.go @@ -12,7 +12,7 @@ import ( const ( // AbortMessage is the message to abort a stuck CCTX - AbortMessage = "CCTX aborted with admin cmd" + AbortMessage = "cctx aborted through MsgAbortStuckCCTX" ) // AbortStuckCCTX aborts a stuck CCTX @@ -41,7 +41,10 @@ func (k msgServer) AbortStuckCCTX( } // update the status - cctx.CctxStatus.UpdateStatusAndErrorMessages(types.CctxStatus_Aborted, AbortMessage, "") + + cctx.SetAbort(types.StatusMessages{ + StatusMessage: AbortMessage, + }) // Save out outbound, // We do not need to provide the tss-pubkey as NonceToCctx is not updated / New outbound is not added diff --git a/x/crosschain/keeper/msg_server_vote_inbound_tx_test.go b/x/crosschain/keeper/msg_server_vote_inbound_tx_test.go index 67bceef663..34fabbce4a 100644 --- a/x/crosschain/keeper/msg_server_vote_inbound_tx_test.go +++ b/x/crosschain/keeper/msg_server_vote_inbound_tx_test.go @@ -306,7 +306,7 @@ func TestStatus_UpdateCctxStatus(t *testing.T) { for _, test := range tt { test := test t.Run(test.Name, func(t *testing.T) { - test.Status.UpdateStatusAndErrorMessages(test.NonErrStatus, test.Msg, "") + test.Status.UpdateStatusAndErrorMessages(test.NonErrStatus, types.StatusMessages{StatusMessage: test.Msg}) if test.IsErr { require.Equal(t, test.ErrStatus, test.Status.Status) } else { diff --git a/x/crosschain/keeper/msg_server_vote_outbound_tx.go b/x/crosschain/keeper/msg_server_vote_outbound_tx.go index a940c9c7db..9535a11131 100644 --- a/x/crosschain/keeper/msg_server_vote_outbound_tx.go +++ b/x/crosschain/keeper/msg_server_vote_outbound_tx.go @@ -10,6 +10,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + cctxerror "github.com/zeta-chain/node/pkg/errors" "github.com/zeta-chain/node/x/crosschain/types" observerkeeper "github.com/zeta-chain/node/x/observer/keeper" ) @@ -123,10 +124,13 @@ func (k msgServer) VoteOutbound( // We use the current TSS pubkey to finalize the outbound. err = k.ValidateOutboundObservers(ctx, &cctx, ballot.BallotStatus, msg.ValueReceived.String()) if err != nil { - k.SaveFailedOutbound(ctx, &cctx, err.Error(), tss.TssPubkey) + // The validate function for the outbound returns an error, which means that the outbound is invalid and should instead be aborted directly + // Irrespective of what the Ballot status is + k.HandleInvalidOutbound(ctx, &cctx, err, tss.TssPubkey) return &types.MsgVoteOutboundResponse{}, nil } - k.SaveSuccessfulOutbound(ctx, &cctx, tss.TssPubkey) + // The outbound is valid, the HandleValidOutbound function would save the required status changes + k.HandleValidOutbound(ctx, &cctx, tss.TssPubkey) return &types.MsgVoteOutboundResponse{}, nil } @@ -185,23 +189,26 @@ func percentOf(n *big.Int, percent int64) *big.Int { } /* -SaveFailedOutbound saves a failed outbound transaction.It does the following things in one function: +HandleInvalidOutbound saves an invalid outbound transaction. It does the following things in one function: 1. Change the status of the CCTX to Aborted 2. Save the outbound */ -func (k Keeper) SaveFailedOutbound(ctx sdk.Context, cctx *types.CrossChainTx, errMessage string, tssPubkey string) { - cctx.SetAbort("", errMessage) - ctx.Logger().Error(errMessage) +func (k Keeper) HandleInvalidOutbound(ctx sdk.Context, cctx *types.CrossChainTx, err error, tssPubkey string) { + cctx.SetAbort(types.StatusMessages{ + StatusMessage: "outbound failed unable to process", + ErrorMessageOutbound: cctxerror.NewCCTXErrorJSONMessage("", err), + }) + ctx.Logger().Error(err.Error()) k.SaveOutbound(ctx, cctx, tssPubkey) } -// SaveSuccessfulOutbound saves a successful outbound transaction. +// HandleValidOutbound saves a successful outbound transaction. // This function does not set the CCTX status, therefore all successful outbound transactions need // to have their status set during processing -func (k Keeper) SaveSuccessfulOutbound(ctx sdk.Context, cctx *types.CrossChainTx, tssPubkey string) { +func (k Keeper) HandleValidOutbound(ctx sdk.Context, cctx *types.CrossChainTx, tssPubkey string) { k.SaveOutbound(ctx, cctx, tssPubkey) } diff --git a/x/crosschain/keeper/msg_server_vote_outbound_tx_test.go b/x/crosschain/keeper/msg_server_vote_outbound_tx_test.go index 5c1415d2df..85b1f8c03d 100644 --- a/x/crosschain/keeper/msg_server_vote_outbound_tx_test.go +++ b/x/crosschain/keeper/msg_server_vote_outbound_tx_test.go @@ -146,7 +146,7 @@ func TestKeeper_VoteOutbound(t *testing.T) { // Successfully mock VoteOnOutboundBallot keepertest.MockVoteOnOutboundSuccessBallot(observerMock, ctx, cctx, senderChain, observer) - // Successfully mock SaveSuccessfulOutbound + // Successfully mock HandleValidOutbound expectedNumberOfOutboundParams := 1 keepertest.MockSaveOutbound(observerMock, ctx, cctx, tss, expectedNumberOfOutboundParams) @@ -452,7 +452,7 @@ func TestKeeper_VoteOutbound(t *testing.T) { // Successfully mock GetSupportedChainFromChainID keepertest.MockGetSupportedChainFromChainID(observerMock, senderChain) - //Successfully mock SaveFailedOutbound + //Successfully mock HandleInvalidOutbound expectedNumberOfOutboundParams := 1 keepertest.MockSaveOutbound(observerMock, ctx, cctx, tss, expectedNumberOfOutboundParams) @@ -592,7 +592,7 @@ func TestKeeper_SaveFailedOutbound(t *testing.T) { cctx.CctxStatus.Status = types.CctxStatus_PendingOutbound //ACT - k.SaveFailedOutbound(ctx, cctx, sample.String(), sample.Tss().TssPubkey) + k.HandleInvalidOutbound(ctx, cctx, errors.New(sample.String()), sample.Tss().TssPubkey) //ASSERT require.Equal(t, cctx.CctxStatus.Status, types.CctxStatus_Aborted) @@ -619,7 +619,7 @@ func TestKeeper_SaveFailedOutbound(t *testing.T) { cctx.CctxStatus.Status = types.CctxStatus_PendingOutbound //ACT - k.SaveFailedOutbound(ctx, cctx, sample.String(), sample.Tss().TssPubkey) + k.HandleInvalidOutbound(ctx, cctx, errors.New(sample.String()), sample.Tss().TssPubkey) //ASSERT require.Equal(t, cctx.CctxStatus.Status, types.CctxStatus_Aborted) @@ -648,7 +648,7 @@ func TestKeeper_SaveSuccessfulOutbound(t *testing.T) { cctx.CctxStatus.Status = types.CctxStatus_PendingOutbound //ACT - k.SaveSuccessfulOutbound(ctx, cctx, sample.Tss().TssPubkey) + k.HandleValidOutbound(ctx, cctx, sample.Tss().TssPubkey) //ASSERT _, found := k.GetOutboundTracker( @@ -674,7 +674,7 @@ func TestKeeper_SaveSuccessfulOutbound(t *testing.T) { cctx.CctxStatus.Status = types.CctxStatus_PendingOutbound //ACT - k.SaveSuccessfulOutbound(ctx, cctx, sample.Tss().TssPubkey) + k.HandleValidOutbound(ctx, cctx, sample.Tss().TssPubkey) //ASSERT for _, outboundParams := range cctx.OutboundParams { diff --git a/x/crosschain/types/cctx.go b/x/crosschain/types/cctx.go index 7bc9c0fca9..7cdb4d4ce8 100644 --- a/x/crosschain/types/cctx.go +++ b/x/crosschain/types/cctx.go @@ -146,6 +146,11 @@ func (m *CrossChainTx) AddRevertOutbound(gasLimit uint64) error { }, TssPubkey: m.GetCurrentOutboundParam().TssPubkey, } + + // TODO : Refactor to move CoinType field to the CCTX object directly : https://github.com/zeta-chain/node/issues/1943 + if m.InboundParams != nil { + revertTxParams.CoinType = m.InboundParams.CoinType + } // The original outbound has been finalized, the new outbound is pending m.GetCurrentOutboundParam().TxFinalizationStatus = TxFinalizationStatus_Executed m.OutboundParams = append(m.OutboundParams, revertTxParams) @@ -183,28 +188,28 @@ func (m *CrossChainTx) AddOutbound( } // SetAbort sets the CCTX status to Aborted with the given error message. -func (m CrossChainTx) SetAbort(statusMsg, errorMsg string) { - m.CctxStatus.UpdateStatusAndErrorMessages(CctxStatus_Aborted, statusMsg, errorMsg) +func (m CrossChainTx) SetAbort(messages StatusMessages) { + m.CctxStatus.UpdateStatusAndErrorMessages(CctxStatus_Aborted, messages) } // SetPendingRevert sets the CCTX status to PendingRevert with the given error message. -func (m CrossChainTx) SetPendingRevert(statusMsg, errorMsg string) { - m.CctxStatus.UpdateStatusAndErrorMessages(CctxStatus_PendingRevert, statusMsg, errorMsg) +func (m CrossChainTx) SetPendingRevert(messages StatusMessages) { + m.CctxStatus.UpdateStatusAndErrorMessages(CctxStatus_PendingRevert, messages) } // SetPendingOutbound sets the CCTX status to PendingOutbound with the given error message. -func (m CrossChainTx) SetPendingOutbound(statusMsg string) { - m.CctxStatus.UpdateStatusAndErrorMessages(CctxStatus_PendingOutbound, statusMsg, "") +func (m CrossChainTx) SetPendingOutbound(messages StatusMessages) { + m.CctxStatus.UpdateStatusAndErrorMessages(CctxStatus_PendingOutbound, messages) } // SetOutboundMined sets the CCTX status to OutboundMined with the given error message. -func (m CrossChainTx) SetOutboundMined(statusMsg string) { - m.CctxStatus.UpdateStatusAndErrorMessages(CctxStatus_OutboundMined, statusMsg, "") +func (m CrossChainTx) SetOutboundMined() { + m.CctxStatus.UpdateStatusAndErrorMessages(CctxStatus_OutboundMined, StatusMessages{}) } // SetReverted sets the CCTX status to Reverted with the given error message. -func (m CrossChainTx) SetReverted(statusMsg, errorMsg string) { - m.CctxStatus.UpdateStatusAndErrorMessages(CctxStatus_Reverted, statusMsg, errorMsg) +func (m CrossChainTx) SetReverted() { + m.CctxStatus.UpdateStatusAndErrorMessages(CctxStatus_Reverted, StatusMessages{}) } func (m CrossChainTx) GetCCTXIndexBytes() ([32]byte, error) { diff --git a/x/crosschain/types/cctx_test.go b/x/crosschain/types/cctx_test.go index 1369e2dee0..a98f7dc1ca 100644 --- a/x/crosschain/types/cctx_test.go +++ b/x/crosschain/types/cctx_test.go @@ -134,6 +134,7 @@ func Test_SetRevertOutboundValues(t *testing.T) { require.Equal(t, cctx.GetCurrentOutboundParam().CallOptions.GasLimit, uint64(100)) require.Equal(t, cctx.GetCurrentOutboundParam().TssPubkey, cctx.OutboundParams[0].TssPubkey) require.Equal(t, types.TxFinalizationStatus_Executed, cctx.OutboundParams[0].TxFinalizationStatus) + require.Equal(t, cctx.GetCurrentOutboundParam().CoinType, cctx.InboundParams.CoinType) }) t.Run("successfully set BTC revert address V1", func(t *testing.T) { @@ -151,6 +152,7 @@ func Test_SetRevertOutboundValues(t *testing.T) { require.Equal(t, cctx.GetCurrentOutboundParam().CallOptions.GasLimit, uint64(100)) require.Equal(t, cctx.GetCurrentOutboundParam().TssPubkey, cctx.OutboundParams[0].TssPubkey) require.Equal(t, types.TxFinalizationStatus_Executed, cctx.OutboundParams[0].TxFinalizationStatus) + require.Equal(t, cctx.GetCurrentOutboundParam().CoinType, cctx.InboundParams.CoinType) }) t.Run("successfully set EVM revert address V2", func(t *testing.T) { @@ -167,6 +169,7 @@ func Test_SetRevertOutboundValues(t *testing.T) { require.Equal(t, cctx.GetCurrentOutboundParam().CallOptions.GasLimit, uint64(100)) require.Equal(t, cctx.GetCurrentOutboundParam().TssPubkey, cctx.OutboundParams[0].TssPubkey) require.Equal(t, types.TxFinalizationStatus_Executed, cctx.OutboundParams[0].TxFinalizationStatus) + require.Equal(t, cctx.GetCurrentOutboundParam().CoinType, cctx.InboundParams.CoinType) }) t.Run("failed to set revert outbound values if revert outbound already exists", func(t *testing.T) { @@ -184,46 +187,66 @@ func Test_SetRevertOutboundValues(t *testing.T) { } func TestCrossChainTx_SetAbort(t *testing.T) { - cctx := sample.CrossChainTx(t, "test") - cctx.CctxStatus.Status = types.CctxStatus_PendingOutbound - cctx.SetAbort("test", "test") - require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) - require.Contains(t, cctx.CctxStatus.StatusMessage, "test") - require.Contains(t, cctx.CctxStatus.ErrorMessage, "test") + t.Run("set abort from pending revert", func(t *testing.T) { + cctx := sample.CrossChainTx(t, "test") + cctx.CctxStatus.Status = types.CctxStatus_PendingRevert + cctx.SetAbort(types.StatusMessages{ + StatusMessage: "status message", + ErrorMessageRevert: "error revert", + }) + require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) + require.Equal(t, cctx.CctxStatus.StatusMessage, "status message") + require.Equal(t, cctx.CctxStatus.ErrorMessageRevert, "error revert") + }) + + t.Run("set abort from pending outbound", func(t *testing.T) { + cctx := sample.CrossChainTx(t, "test") + cctx.CctxStatus.Status = types.CctxStatus_PendingOutbound + cctx.SetAbort(types.StatusMessages{ + StatusMessage: "status message", + ErrorMessageOutbound: "error outbound", + }) + require.Equal(t, types.CctxStatus_Aborted, cctx.CctxStatus.Status) + require.Equal(t, cctx.CctxStatus.StatusMessage, "status message") + require.Equal(t, cctx.CctxStatus.ErrorMessage, "error outbound") + }) } func TestCrossChainTx_SetPendingRevert(t *testing.T) { cctx := sample.CrossChainTx(t, "test") cctx.CctxStatus.Status = types.CctxStatus_PendingOutbound - cctx.SetPendingRevert("test", "test") + cctx.SetPendingRevert(types.StatusMessages{ + StatusMessage: "status message", + ErrorMessageOutbound: "error outbound", + }) require.Equal(t, types.CctxStatus_PendingRevert, cctx.CctxStatus.Status) - require.Contains(t, cctx.CctxStatus.StatusMessage, "test") - require.Contains(t, cctx.CctxStatus.ErrorMessage, "test") + require.Equal(t, cctx.CctxStatus.StatusMessage, "status message") + require.Equal(t, cctx.CctxStatus.ErrorMessage, "error outbound") } func TestCrossChainTx_SetPendingOutbound(t *testing.T) { cctx := sample.CrossChainTx(t, "test") cctx.CctxStatus.Status = types.CctxStatus_PendingInbound - cctx.SetPendingOutbound("test") + cctx.SetPendingOutbound(types.StatusMessages{ + StatusMessage: "status message", + }) require.Equal(t, types.CctxStatus_PendingOutbound, cctx.CctxStatus.Status) - require.Contains(t, cctx.CctxStatus.StatusMessage, "test") - require.NotContains(t, cctx.CctxStatus.ErrorMessage, "test") + require.Equal(t, cctx.CctxStatus.StatusMessage, "status message") } func TestCrossChainTx_SetOutboundMined(t *testing.T) { cctx := sample.CrossChainTx(t, "test") cctx.CctxStatus.Status = types.CctxStatus_PendingOutbound - cctx.SetOutboundMined("test") + cctx.SetOutboundMined() require.Equal(t, types.CctxStatus_OutboundMined, cctx.CctxStatus.Status) - require.Contains(t, cctx.CctxStatus.StatusMessage, "test") - require.NotContains(t, cctx.CctxStatus.ErrorMessage, "test") + require.Equal(t, cctx.CctxStatus.StatusMessage, "") } func TestCrossChainTx_SetReverted(t *testing.T) { cctx := sample.CrossChainTx(t, "test") cctx.CctxStatus.Status = types.CctxStatus_PendingRevert - cctx.SetReverted("test", "test") + cctx.SetReverted() require.Equal(t, types.CctxStatus_Reverted, cctx.CctxStatus.Status) - require.Contains(t, cctx.CctxStatus.StatusMessage, "test") - require.Contains(t, cctx.CctxStatus.ErrorMessage, "test") + require.Equal(t, cctx.CctxStatus.StatusMessage, "") + require.Equal(t, cctx.CctxStatus.ErrorMessageRevert, "") } diff --git a/x/crosschain/types/cross_chain_tx.pb.go b/x/crosschain/types/cross_chain_tx.pb.go index 32d34682a4..b08e7821c8 100644 --- a/x/crosschain/types/cross_chain_tx.pb.go +++ b/x/crosschain/types/cross_chain_tx.pb.go @@ -546,6 +546,9 @@ type Status struct { IsAbortRefunded bool `protobuf:"varint,4,opt,name=isAbortRefunded,proto3" json:"isAbortRefunded,omitempty"` // when the CCTX was created. only populated on new transactions. CreatedTimestamp int64 `protobuf:"varint,5,opt,name=created_timestamp,json=createdTimestamp,proto3" json:"created_timestamp,omitempty"` + // error_message_revert carries information about the revert outbound tx , + // which is created if the first outbound tx fails + ErrorMessageRevert string `protobuf:"bytes,7,opt,name=error_message_revert,json=errorMessageRevert,proto3" json:"error_message_revert,omitempty"` } func (m *Status) Reset() { *m = Status{} } @@ -623,6 +626,13 @@ func (m *Status) GetCreatedTimestamp() int64 { return 0 } +func (m *Status) GetErrorMessageRevert() string { + if m != nil { + return m.ErrorMessageRevert + } + return "" +} + // RevertOptions represents the options for reverting a cctx type RevertOptions struct { RevertAddress string `protobuf:"bytes,1,opt,name=revert_address,json=revertAddress,proto3" json:"revert_address,omitempty"` @@ -813,97 +823,98 @@ func init() { } var fileDescriptor_d4c1966807fb5cb2 = []byte{ - // 1438 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x57, 0xcd, 0x6e, 0x1b, 0xb7, - 0x16, 0xd6, 0xd8, 0xb2, 0x2c, 0x1d, 0xfd, 0x78, 0x4c, 0x3b, 0xce, 0xc4, 0x17, 0x51, 0x7c, 0x75, - 0xaf, 0x73, 0x15, 0xdf, 0x5a, 0x46, 0x1c, 0x20, 0x28, 0xba, 0xb3, 0x15, 0x2b, 0x51, 0x5b, 0xff, - 0x60, 0xfc, 0x03, 0x34, 0x9b, 0x29, 0x35, 0x43, 0x4b, 0x84, 0xa5, 0xa1, 0x30, 0xa4, 0x0c, 0x39, - 0xe8, 0x43, 0x74, 0xd1, 0x17, 0x28, 0xd0, 0x45, 0x9f, 0xa2, 0xeb, 0x2c, 0xb3, 0x2c, 0xba, 0x08, - 0x8a, 0xe4, 0x0d, 0xb2, 0xee, 0xa2, 0xe0, 0x9f, 0x64, 0xa5, 0xae, 0xed, 0x16, 0x5d, 0x89, 0xfc, - 0x0e, 0xcf, 0x47, 0xce, 0xe1, 0xf9, 0xce, 0xa1, 0x60, 0xf3, 0x15, 0x11, 0x38, 0xec, 0x60, 0x1a, - 0x6f, 0xa8, 0x11, 0x4b, 0xc8, 0x46, 0x98, 0x30, 0xce, 0x35, 0xa6, 0x86, 0x81, 0x1a, 0x07, 0x62, - 0x58, 0xeb, 0x27, 0x4c, 0x30, 0x74, 0x7f, 0xe4, 0x53, 0xb3, 0x3e, 0xb5, 0xb1, 0xcf, 0xf2, 0x62, - 0x9b, 0xb5, 0x99, 0x5a, 0xb9, 0x21, 0x47, 0xda, 0x69, 0xf9, 0xe1, 0x15, 0x1b, 0xf5, 0xcf, 0xda, - 0x1b, 0x21, 0x93, 0xdb, 0x30, 0x1a, 0xeb, 0x75, 0x95, 0xef, 0x66, 0xa0, 0xd8, 0x8c, 0x5b, 0x6c, - 0x10, 0x47, 0x07, 0x38, 0xc1, 0x3d, 0x8e, 0x96, 0x20, 0xc3, 0x49, 0x1c, 0x91, 0xc4, 0x73, 0x56, - 0x9c, 0x6a, 0xce, 0x37, 0x33, 0xf4, 0x10, 0xe6, 0xf4, 0xc8, 0x9c, 0x8f, 0x46, 0xde, 0xd4, 0x8a, - 0x53, 0x9d, 0xf6, 0x8b, 0x1a, 0xae, 0x4b, 0xb4, 0x19, 0xa1, 0x7f, 0x41, 0x4e, 0x0c, 0x03, 0x96, - 0xd0, 0x36, 0x8d, 0xbd, 0x69, 0x45, 0x91, 0x15, 0xc3, 0x7d, 0x35, 0x47, 0xdb, 0x90, 0x93, 0x9b, - 0x07, 0xe2, 0xa2, 0x4f, 0xbc, 0xf4, 0x8a, 0x53, 0x2d, 0x6d, 0xae, 0xd6, 0xae, 0xf8, 0xbe, 0xfe, - 0x59, 0xbb, 0xa6, 0x4e, 0x59, 0x67, 0x34, 0x3e, 0xba, 0xe8, 0x13, 0x3f, 0x1b, 0x9a, 0x11, 0x5a, - 0x84, 0x19, 0xcc, 0x39, 0x11, 0xde, 0x8c, 0x22, 0xd7, 0x13, 0xf4, 0x14, 0x32, 0xb8, 0xc7, 0x06, - 0xb1, 0xf0, 0x32, 0x12, 0xde, 0x2e, 0xbf, 0x7e, 0xfb, 0x20, 0xf5, 0xcb, 0xdb, 0x07, 0x4b, 0x21, - 0xe3, 0x3d, 0xc6, 0x79, 0x74, 0x56, 0xa3, 0x6c, 0xa3, 0x87, 0x45, 0xa7, 0x76, 0x4c, 0x63, 0xe1, - 0x9b, 0xd5, 0xe8, 0x3f, 0x50, 0x64, 0x2d, 0x4e, 0x92, 0x73, 0x12, 0x05, 0x1d, 0xcc, 0x3b, 0xde, - 0xac, 0x62, 0x2d, 0x58, 0xf0, 0x05, 0xe6, 0x1d, 0xf4, 0x29, 0x78, 0xa3, 0x45, 0x64, 0x28, 0x48, - 0x12, 0xe3, 0x6e, 0xd0, 0x21, 0xb4, 0xdd, 0x11, 0x5e, 0x76, 0xc5, 0xa9, 0xa6, 0xfd, 0x25, 0x6b, - 0xdf, 0x31, 0xe6, 0x17, 0xca, 0x8a, 0xfe, 0x0d, 0x85, 0x16, 0xee, 0x76, 0x99, 0x08, 0x68, 0x1c, - 0x91, 0xa1, 0x97, 0x53, 0xec, 0x79, 0x8d, 0x35, 0x25, 0x84, 0x36, 0xe1, 0xce, 0x29, 0x8d, 0x71, - 0x97, 0xbe, 0x22, 0x51, 0x20, 0x23, 0x60, 0x99, 0x41, 0x31, 0x2f, 0x8c, 0x8c, 0x2f, 0x89, 0xc0, - 0x86, 0x96, 0xc2, 0x92, 0x18, 0x06, 0xc6, 0x82, 0x05, 0x65, 0x71, 0xc0, 0x05, 0x16, 0x03, 0xee, - 0xe5, 0x55, 0x50, 0x9f, 0xd4, 0xae, 0x4d, 0x9a, 0xda, 0xd1, 0xb0, 0x71, 0xc9, 0xf7, 0x50, 0xb9, - 0xfa, 0x8b, 0xe2, 0x0a, 0x14, 0xad, 0xc3, 0x02, 0xe5, 0xc1, 0xe5, 0xcc, 0x0c, 0x71, 0xb7, 0xeb, - 0x15, 0x56, 0x9c, 0x6a, 0xd6, 0x77, 0x29, 0xaf, 0x4b, 0x8b, 0xba, 0xfc, 0x3a, 0xee, 0x76, 0xd1, - 0x33, 0xc8, 0x98, 0x93, 0x2c, 0xaa, 0x93, 0x7c, 0x72, 0xc3, 0x49, 0x4c, 0xf2, 0x99, 0x23, 0x18, - 0xdf, 0xca, 0xd7, 0x50, 0x92, 0x5f, 0xbb, 0x15, 0x86, 0xf2, 0x92, 0x68, 0xdc, 0x46, 0x7b, 0xb0, - 0x80, 0x5b, 0x2c, 0x11, 0x36, 0x46, 0xe6, 0xb2, 0x9d, 0x5b, 0x5d, 0xf6, 0xbc, 0x71, 0x55, 0x9c, - 0xca, 0xb1, 0x72, 0x02, 0x79, 0x79, 0xde, 0xfd, 0xbe, 0xfc, 0x54, 0x2e, 0xb3, 0xb6, 0x8d, 0x79, - 0xd0, 0xa5, 0x3d, 0xaa, 0x49, 0xd3, 0x7e, 0xb6, 0x8d, 0xf9, 0x97, 0x72, 0x8e, 0xd6, 0x60, 0x9e, - 0xf2, 0x00, 0x27, 0x2d, 0x2a, 0x12, 0x9c, 0x5c, 0xe8, 0x00, 0x4c, 0xa9, 0x00, 0xcc, 0x51, 0xbe, - 0x65, 0x71, 0xc9, 0x57, 0xf9, 0x29, 0x03, 0xa5, 0xfd, 0x81, 0xb8, 0xac, 0xa8, 0x65, 0xc8, 0x26, - 0x24, 0x24, 0xf4, 0x7c, 0xa4, 0xa9, 0xd1, 0x1c, 0x3d, 0x02, 0xd7, 0x8e, 0x75, 0x74, 0x9b, 0x56, - 0x56, 0x73, 0x16, 0xb7, 0xc2, 0x9a, 0xd0, 0xce, 0xf4, 0xdf, 0xd3, 0xce, 0x58, 0x25, 0xe9, 0xbf, - 0xa4, 0x12, 0x29, 0x6a, 0xce, 0x83, 0x98, 0xc5, 0x21, 0x51, 0xba, 0x4b, 0xfb, 0x59, 0xc1, 0xf9, - 0x9e, 0x9c, 0x4f, 0xc6, 0x2e, 0xf3, 0x51, 0xec, 0x8c, 0xb1, 0x9f, 0xd0, 0x90, 0x18, 0x6d, 0x49, - 0xe3, 0x81, 0x9c, 0xa3, 0x2a, 0xb8, 0xc6, 0xc8, 0x12, 0x2a, 0x2e, 0x82, 0x53, 0x42, 0xbc, 0xbb, - 0x6a, 0x4d, 0x49, 0xaf, 0x51, 0x70, 0x83, 0x10, 0x84, 0x20, 0xad, 0xd4, 0x99, 0x55, 0x56, 0x35, - 0xbe, 0x8d, 0xb6, 0xae, 0x13, 0x2e, 0x5c, 0x2b, 0xdc, 0x7b, 0x20, 0x8f, 0x19, 0x0c, 0x38, 0x89, - 0x54, 0x26, 0xa7, 0xfd, 0xd9, 0x36, 0xe6, 0xc7, 0x9c, 0x44, 0x68, 0x17, 0x16, 0xc8, 0xe9, 0x29, - 0x09, 0x05, 0x3d, 0x27, 0xc1, 0xf8, 0xe3, 0xee, 0xa8, 0x88, 0xde, 0x37, 0x11, 0xbd, 0xf3, 0xc7, - 0x88, 0x36, 0x65, 0x26, 0x8e, 0x3c, 0x9f, 0xdb, 0x20, 0xd4, 0x3e, 0xa6, 0xd3, 0x81, 0x5c, 0x52, - 0x9b, 0x4e, 0xac, 0xd7, 0x11, 0xbd, 0x0f, 0x20, 0xef, 0xa2, 0x3f, 0x68, 0x9d, 0x91, 0x0b, 0xa5, - 0xf7, 0x9c, 0x2f, 0x6f, 0xe7, 0x40, 0x01, 0xd7, 0x94, 0x86, 0xc2, 0x3f, 0x5d, 0x1a, 0x76, 0xa1, - 0x20, 0xa5, 0x10, 0x30, 0x2d, 0x22, 0xcf, 0x5b, 0x71, 0xaa, 0xf9, 0xcd, 0xb5, 0x1b, 0x36, 0xb8, - 0x24, 0x3b, 0x3f, 0x1f, 0x8e, 0x27, 0x9f, 0xa7, 0xb3, 0x45, 0x77, 0xb1, 0xf2, 0xfd, 0x14, 0x64, - 0x0c, 0xff, 0xd6, 0xa8, 0x96, 0x38, 0xea, 0xe8, 0x8f, 0x6e, 0x62, 0x0e, 0xc5, 0x70, 0xb2, 0x90, - 0xa0, 0x55, 0x28, 0xe9, 0x51, 0xd0, 0x23, 0x9c, 0xe3, 0x36, 0x51, 0xea, 0xca, 0xf9, 0x45, 0x8d, - 0xee, 0x6a, 0x50, 0x76, 0x01, 0x92, 0x24, 0x2c, 0x19, 0xad, 0xca, 0xe8, 0x2e, 0xa0, 0x40, 0xbb, - 0xe8, 0x31, 0x2c, 0x76, 0x31, 0x17, 0xc7, 0xfd, 0x08, 0x0b, 0x12, 0x08, 0xda, 0x23, 0x5c, 0xe0, - 0x5e, 0x5f, 0x69, 0x71, 0xda, 0x5f, 0x18, 0xdb, 0x8e, 0xac, 0x09, 0x55, 0x41, 0x16, 0x08, 0x59, - 0x7c, 0x7c, 0x72, 0x3a, 0x88, 0x23, 0x12, 0x29, 0xe1, 0xe9, 0xba, 0x71, 0x19, 0x46, 0xff, 0x87, - 0xf9, 0x30, 0x21, 0x58, 0xd6, 0xb7, 0x31, 0xf3, 0x8c, 0x62, 0x76, 0x8d, 0x61, 0x44, 0x5b, 0xf9, - 0xe0, 0x40, 0xd1, 0x27, 0xe7, 0x24, 0x11, 0xb6, 0x7e, 0xad, 0x42, 0x29, 0x51, 0x40, 0x80, 0xa3, - 0x28, 0x21, 0x9c, 0x9b, 0x4a, 0x53, 0xd4, 0xe8, 0x96, 0x06, 0xd1, 0x7f, 0xa1, 0xa4, 0x6f, 0x2c, - 0x0e, 0xb4, 0xc1, 0x94, 0x31, 0x75, 0x8f, 0xfb, 0xb1, 0xe6, 0x94, 0xd1, 0x50, 0x05, 0x73, 0xc4, - 0xa5, 0xdb, 0x78, 0x41, 0x81, 0x96, 0x6a, 0xbc, 0xa3, 0x8d, 0x99, 0xfc, 0xb2, 0x82, 0xdd, 0xd1, - 0x06, 0xed, 0x85, 0x2c, 0x70, 0x6a, 0xd9, 0x38, 0xb5, 0x67, 0x6e, 0x55, 0x7b, 0x0c, 0xbd, 0xcd, - 0xfb, 0xca, 0x6f, 0x69, 0x28, 0x8c, 0x9b, 0xcd, 0xd1, 0x10, 0x79, 0x30, 0xab, 0x22, 0xc3, 0x6c, - 0x59, 0xb5, 0x53, 0xf9, 0x44, 0xd0, 0x25, 0x41, 0x5f, 0xb6, 0x9e, 0xa0, 0x7d, 0xc8, 0xa9, 0xd6, - 0x71, 0x4a, 0x08, 0x37, 0x67, 0xd8, 0xbc, 0xfe, 0x0c, 0x1f, 0xde, 0x3e, 0x70, 0x2f, 0x70, 0xaf, - 0xfb, 0x59, 0x65, 0xe4, 0x58, 0xf1, 0xb3, 0x72, 0xdc, 0x20, 0x84, 0xa3, 0xff, 0xc1, 0x5c, 0x42, - 0xba, 0xf8, 0x82, 0x44, 0x1f, 0xe5, 0x4d, 0xc9, 0xc0, 0x36, 0x08, 0x0d, 0xc8, 0x87, 0xa1, 0x18, - 0x5a, 0x21, 0x66, 0x95, 0x4e, 0x56, 0x6f, 0xc8, 0x66, 0x93, 0xc9, 0x10, 0x8e, 0xb2, 0x1a, 0x1d, - 0x42, 0x89, 0xea, 0x7e, 0x19, 0xf4, 0x55, 0x6f, 0x51, 0x35, 0x2f, 0x7f, 0xdb, 0x26, 0xab, 0xfb, - 0x91, 0x5f, 0xa4, 0x13, 0x0f, 0xbe, 0x13, 0x98, 0x63, 0xa6, 0x61, 0x59, 0x56, 0x58, 0x99, 0xae, - 0xe6, 0x37, 0xd7, 0x6f, 0x60, 0x9d, 0x6c, 0x73, 0x7e, 0x89, 0x4d, 0xb6, 0xbd, 0x04, 0xee, 0xa9, - 0x37, 0x66, 0xc8, 0xba, 0x41, 0xc8, 0x62, 0x91, 0xe0, 0x50, 0x04, 0xe7, 0x24, 0xe1, 0x94, 0xc5, - 0xe6, 0x99, 0xf2, 0xf4, 0x86, 0x1d, 0x0e, 0x8c, 0x7f, 0xdd, 0xb8, 0x9f, 0x68, 0x6f, 0xff, 0x6e, - 0xff, 0x6a, 0x03, 0xfa, 0x6a, 0x94, 0x94, 0xb6, 0x26, 0x15, 0x6e, 0x15, 0xa0, 0x09, 0x31, 0x6d, - 0xa7, 0x65, 0x56, 0xd8, 0x44, 0x36, 0xe0, 0xda, 0x37, 0x00, 0xe3, 0xfa, 0x82, 0x10, 0x94, 0x0e, - 0x48, 0x1c, 0xd1, 0xb8, 0x6d, 0x62, 0xeb, 0xa6, 0xd0, 0x02, 0xcc, 0x19, 0xcc, 0x46, 0xc6, 0x75, - 0xd0, 0x3c, 0x14, 0xed, 0x6c, 0x97, 0xc6, 0x24, 0x72, 0xa7, 0x25, 0x64, 0xd6, 0xe9, 0x6d, 0xdd, - 0x34, 0x2a, 0x40, 0x56, 0x8f, 0x49, 0xe4, 0xce, 0xa0, 0x3c, 0xcc, 0x6e, 0xe9, 0x07, 0x8b, 0x9b, - 0x59, 0x4e, 0xff, 0xf8, 0x43, 0xd9, 0x59, 0xfb, 0x02, 0x16, 0xaf, 0x2a, 0xcc, 0xc8, 0x85, 0xc2, - 0x1e, 0x13, 0x0d, 0xfb, 0x44, 0x74, 0x53, 0xa8, 0x08, 0xb9, 0xf1, 0xd4, 0x91, 0xcc, 0x3b, 0x43, - 0x12, 0x0e, 0x24, 0xd9, 0x94, 0x21, 0xdb, 0x1e, 0xbd, 0xf9, 0x0d, 0x4b, 0x1e, 0x66, 0x0f, 0x8f, - 0xeb, 0xf5, 0x9d, 0xc3, 0x43, 0x37, 0x85, 0xca, 0xb0, 0xdc, 0xdc, 0x3b, 0x3c, 0x6e, 0x34, 0x9a, - 0xf5, 0xe6, 0xce, 0xde, 0x51, 0xf0, 0x6c, 0xe7, 0x60, 0xff, 0xb0, 0x79, 0xb4, 0xef, 0x07, 0x8d, - 0x9d, 0x1d, 0xd7, 0x31, 0x1c, 0x1b, 0x70, 0xf7, 0x4f, 0x6e, 0x07, 0x65, 0x60, 0xea, 0xe4, 0xb1, - 0x9b, 0x52, 0xbf, 0x9b, 0xd6, 0x61, 0xfb, 0xf9, 0xeb, 0x77, 0x65, 0xe7, 0xcd, 0xbb, 0xb2, 0xf3, - 0xeb, 0xbb, 0xb2, 0xf3, 0xed, 0xfb, 0x72, 0xea, 0xcd, 0xfb, 0x72, 0xea, 0xe7, 0xf7, 0xe5, 0xd4, - 0xcb, 0xf5, 0x36, 0x15, 0x9d, 0x41, 0xab, 0x16, 0xb2, 0x9e, 0xfa, 0xb3, 0xb2, 0xae, 0xff, 0xb7, - 0xc4, 0x2c, 0x22, 0x1b, 0xc3, 0xcb, 0x7f, 0x8f, 0xe4, 0xcb, 0x87, 0xb7, 0x32, 0xea, 0xf2, 0x9f, - 0xfc, 0x1e, 0x00, 0x00, 0xff, 0xff, 0xb1, 0xe2, 0x58, 0xbd, 0x4c, 0x0d, 0x00, 0x00, + // 1453 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x57, 0xcd, 0x6e, 0x1b, 0xc9, + 0x11, 0xe6, 0x48, 0x14, 0x45, 0x16, 0x7f, 0x34, 0x6a, 0xd1, 0xf2, 0x58, 0x81, 0x69, 0x85, 0x89, + 0x1c, 0x5a, 0x89, 0xa8, 0x58, 0x06, 0x8c, 0x20, 0x37, 0x89, 0x16, 0x6d, 0x26, 0xd1, 0x0f, 0x46, + 0x3f, 0x40, 0x7c, 0x99, 0x34, 0x67, 0x5a, 0x64, 0x43, 0xe4, 0x34, 0x31, 0xdd, 0x14, 0x28, 0x23, + 0x0f, 0xb1, 0x87, 0x7d, 0x84, 0x3d, 0xec, 0x53, 0xec, 0xd9, 0x47, 0x03, 0x7b, 0x59, 0xec, 0xc1, + 0x58, 0xd8, 0x6f, 0xe0, 0xf3, 0x1e, 0x16, 0xfd, 0x47, 0x8a, 0x5e, 0xad, 0xa4, 0x5d, 0xec, 0x89, + 0x5d, 0x55, 0x5d, 0x5f, 0x17, 0xab, 0xeb, 0xab, 0xea, 0x81, 0xad, 0x37, 0x44, 0xe0, 0xb0, 0x8b, + 0x69, 0xbc, 0xa9, 0x56, 0x2c, 0x21, 0x9b, 0x61, 0xc2, 0x38, 0xd7, 0x3a, 0xb5, 0x0c, 0xd4, 0x3a, + 0x10, 0xa3, 0xfa, 0x20, 0x61, 0x82, 0xa1, 0x87, 0x63, 0x9f, 0xba, 0xf5, 0xa9, 0x4f, 0x7c, 0x56, + 0xca, 0x1d, 0xd6, 0x61, 0x6a, 0xe7, 0xa6, 0x5c, 0x69, 0xa7, 0x95, 0xc7, 0xd7, 0x1c, 0x34, 0x38, + 0xef, 0x6c, 0x86, 0x4c, 0x1e, 0xc3, 0x68, 0xac, 0xf7, 0x55, 0xbf, 0x9c, 0x83, 0x62, 0x2b, 0x6e, + 0xb3, 0x61, 0x1c, 0x1d, 0xe2, 0x04, 0xf7, 0x39, 0x5a, 0x86, 0x0c, 0x27, 0x71, 0x44, 0x12, 0xcf, + 0x59, 0x75, 0x6a, 0x39, 0xdf, 0x48, 0xe8, 0x31, 0x2c, 0xe8, 0x95, 0x89, 0x8f, 0x46, 0xde, 0xcc, + 0xaa, 0x53, 0x9b, 0xf5, 0x8b, 0x5a, 0xdd, 0x90, 0xda, 0x56, 0x84, 0xfe, 0x00, 0x39, 0x31, 0x0a, + 0x58, 0x42, 0x3b, 0x34, 0xf6, 0x66, 0x15, 0x44, 0x56, 0x8c, 0x0e, 0x94, 0x8c, 0x76, 0x20, 0x27, + 0x0f, 0x0f, 0xc4, 0xe5, 0x80, 0x78, 0xe9, 0x55, 0xa7, 0x56, 0xda, 0x5a, 0xab, 0x5f, 0xf3, 0xff, + 0x06, 0xe7, 0x9d, 0xba, 0x8a, 0xb2, 0xc1, 0x68, 0x7c, 0x7c, 0x39, 0x20, 0x7e, 0x36, 0x34, 0x2b, + 0x54, 0x86, 0x39, 0xcc, 0x39, 0x11, 0xde, 0x9c, 0x02, 0xd7, 0x02, 0x7a, 0x0e, 0x19, 0xdc, 0x67, + 0xc3, 0x58, 0x78, 0x19, 0xa9, 0xde, 0xa9, 0xbc, 0x7d, 0xff, 0x28, 0xf5, 0xfd, 0xfb, 0x47, 0xcb, + 0x21, 0xe3, 0x7d, 0xc6, 0x79, 0x74, 0x5e, 0xa7, 0x6c, 0xb3, 0x8f, 0x45, 0xb7, 0x7e, 0x42, 0x63, + 0xe1, 0x9b, 0xdd, 0xe8, 0x4f, 0x50, 0x64, 0x6d, 0x4e, 0x92, 0x0b, 0x12, 0x05, 0x5d, 0xcc, 0xbb, + 0xde, 0xbc, 0x42, 0x2d, 0x58, 0xe5, 0x2b, 0xcc, 0xbb, 0xe8, 0x1f, 0xe0, 0x8d, 0x37, 0x91, 0x91, + 0x20, 0x49, 0x8c, 0x7b, 0x41, 0x97, 0xd0, 0x4e, 0x57, 0x78, 0xd9, 0x55, 0xa7, 0x96, 0xf6, 0x97, + 0xad, 0x7d, 0xd7, 0x98, 0x5f, 0x29, 0x2b, 0xfa, 0x23, 0x14, 0xda, 0xb8, 0xd7, 0x63, 0x22, 0xa0, + 0x71, 0x44, 0x46, 0x5e, 0x4e, 0xa1, 0xe7, 0xb5, 0xae, 0x25, 0x55, 0x68, 0x0b, 0xee, 0x9d, 0xd1, + 0x18, 0xf7, 0xe8, 0x1b, 0x12, 0x05, 0x32, 0x03, 0x16, 0x19, 0x14, 0xf2, 0xd2, 0xd8, 0xf8, 0x9a, + 0x08, 0x6c, 0x60, 0x29, 0x2c, 0x8b, 0x51, 0x60, 0x2c, 0x58, 0x50, 0x16, 0x07, 0x5c, 0x60, 0x31, + 0xe4, 0x5e, 0x5e, 0x25, 0xf5, 0x59, 0xfd, 0xc6, 0xa2, 0xa9, 0x1f, 0x8f, 0x9a, 0x57, 0x7c, 0x8f, + 0x94, 0xab, 0x5f, 0x16, 0xd7, 0x68, 0xd1, 0x06, 0x2c, 0x51, 0x1e, 0x5c, 0xad, 0xcc, 0x10, 0xf7, + 0x7a, 0x5e, 0x61, 0xd5, 0xa9, 0x65, 0x7d, 0x97, 0xf2, 0x86, 0xb4, 0xa8, 0xcb, 0x6f, 0xe0, 0x5e, + 0x0f, 0xbd, 0x80, 0x8c, 0x89, 0xa4, 0xac, 0x22, 0xf9, 0xdb, 0x2d, 0x91, 0x98, 0xe2, 0x33, 0x21, + 0x18, 0xdf, 0xea, 0xff, 0xa0, 0x24, 0xff, 0xed, 0x76, 0x18, 0xca, 0x4b, 0xa2, 0x71, 0x07, 0xed, + 0xc3, 0x12, 0x6e, 0xb3, 0x44, 0xd8, 0x1c, 0x99, 0xcb, 0x76, 0xee, 0x74, 0xd9, 0x8b, 0xc6, 0x55, + 0x61, 0x2a, 0xc7, 0xea, 0x29, 0xe4, 0x65, 0xbc, 0x07, 0x03, 0xf9, 0x57, 0xb9, 0xac, 0xda, 0x0e, + 0xe6, 0x41, 0x8f, 0xf6, 0xa9, 0x06, 0x4d, 0xfb, 0xd9, 0x0e, 0xe6, 0xff, 0x91, 0x32, 0x5a, 0x87, + 0x45, 0xca, 0x03, 0x9c, 0xb4, 0xa9, 0x48, 0x70, 0x72, 0xa9, 0x13, 0x30, 0xa3, 0x12, 0xb0, 0x40, + 0xf9, 0xb6, 0xd5, 0x4b, 0xbc, 0xea, 0x37, 0x19, 0x28, 0x1d, 0x0c, 0xc5, 0x55, 0x46, 0xad, 0x40, + 0x36, 0x21, 0x21, 0xa1, 0x17, 0x63, 0x4e, 0x8d, 0x65, 0xf4, 0x04, 0x5c, 0xbb, 0xd6, 0xd9, 0x6d, + 0x59, 0x5a, 0x2d, 0x58, 0xbd, 0x25, 0xd6, 0x14, 0x77, 0x66, 0x7f, 0x1b, 0x77, 0x26, 0x2c, 0x49, + 0xff, 0x2a, 0x96, 0x48, 0x52, 0x73, 0x1e, 0xc4, 0x2c, 0x0e, 0x89, 0xe2, 0x5d, 0xda, 0xcf, 0x0a, + 0xce, 0xf7, 0xa5, 0x3c, 0x9d, 0xbb, 0xcc, 0x67, 0xb9, 0x33, 0xc6, 0x41, 0x42, 0x43, 0x62, 0xb8, + 0x25, 0x8d, 0x87, 0x52, 0x46, 0x35, 0x70, 0x8d, 0x91, 0x25, 0x54, 0x5c, 0x06, 0x67, 0x84, 0x78, + 0xf7, 0xd5, 0x9e, 0x92, 0xde, 0xa3, 0xd4, 0x4d, 0x42, 0x10, 0x82, 0xb4, 0x62, 0x67, 0x56, 0x59, + 0xd5, 0xfa, 0x2e, 0xdc, 0xba, 0x89, 0xb8, 0x70, 0x23, 0x71, 0x1f, 0x80, 0x0c, 0x33, 0x18, 0x72, + 0x12, 0xa9, 0x4a, 0x4e, 0xfb, 0xf3, 0x1d, 0xcc, 0x4f, 0x38, 0x89, 0xd0, 0x1e, 0x2c, 0x91, 0xb3, + 0x33, 0x12, 0x0a, 0x7a, 0x41, 0x82, 0xc9, 0x9f, 0xbb, 0xa7, 0x32, 0xfa, 0xd0, 0x64, 0xf4, 0xde, + 0xcf, 0x33, 0xda, 0x92, 0x95, 0x38, 0xf6, 0x7c, 0x69, 0x93, 0x50, 0xff, 0x1c, 0x4e, 0x27, 0x72, + 0x59, 0x1d, 0x3a, 0xb5, 0x5f, 0x67, 0xf4, 0x21, 0x80, 0xbc, 0x8b, 0xc1, 0xb0, 0x7d, 0x4e, 0x2e, + 0x15, 0xdf, 0x73, 0xbe, 0xbc, 0x9d, 0x43, 0xa5, 0xb8, 0xa1, 0x35, 0x14, 0x7e, 0xef, 0xd6, 0xb0, + 0x07, 0x05, 0x49, 0x85, 0x80, 0x69, 0x12, 0x79, 0xde, 0xaa, 0x53, 0xcb, 0x6f, 0xad, 0xdf, 0x72, + 0xc0, 0x15, 0xda, 0xf9, 0xf9, 0x70, 0x22, 0xfc, 0x2b, 0x9d, 0x2d, 0xba, 0xe5, 0xea, 0xb7, 0x33, + 0x90, 0x31, 0xf8, 0xdb, 0xe3, 0x5e, 0xe2, 0xa8, 0xd0, 0x9f, 0xdc, 0x86, 0x1c, 0x8a, 0xd1, 0x74, + 0x23, 0x41, 0x6b, 0x50, 0xd2, 0xab, 0xa0, 0x4f, 0x38, 0xc7, 0x1d, 0xa2, 0xd8, 0x95, 0xf3, 0x8b, + 0x5a, 0xbb, 0xa7, 0x95, 0x72, 0x0a, 0x90, 0x24, 0x61, 0xc9, 0x78, 0x57, 0x46, 0x4f, 0x01, 0xa5, + 0xb4, 0x9b, 0x9e, 0x42, 0xb9, 0x87, 0xb9, 0x38, 0x19, 0x44, 0x58, 0x90, 0x40, 0xd0, 0x3e, 0xe1, + 0x02, 0xf7, 0x07, 0x8a, 0x8b, 0xb3, 0xfe, 0xd2, 0xc4, 0x76, 0x6c, 0x4d, 0xa8, 0x06, 0xb2, 0x41, + 0xc8, 0xe6, 0xe3, 0x93, 0xb3, 0x61, 0x1c, 0x91, 0x48, 0x11, 0x4f, 0xf7, 0x8d, 0xab, 0x6a, 0xf4, + 0x57, 0x58, 0x0c, 0x13, 0x82, 0x65, 0x7f, 0x9b, 0x20, 0xcf, 0x29, 0x64, 0xd7, 0x18, 0x26, 0xb0, + 0x7f, 0x87, 0xf2, 0x54, 0xb8, 0x41, 0x42, 0x2e, 0x48, 0x22, 0x0c, 0xbf, 0xd0, 0xd5, 0xa8, 0x7d, + 0x65, 0xa9, 0x7e, 0x72, 0xa0, 0xa8, 0x97, 0xb6, 0xe3, 0xad, 0x41, 0x49, 0x7b, 0x05, 0x38, 0x8a, + 0x12, 0xc2, 0xb9, 0xe9, 0x4d, 0x45, 0xad, 0xdd, 0xd6, 0x4a, 0xf4, 0x67, 0x28, 0xe9, 0x3b, 0x8e, + 0xed, 0x21, 0xba, 0xf1, 0xa9, 0x9b, 0x3f, 0x88, 0x35, 0xa6, 0xcc, 0x9f, 0x6a, 0xb1, 0x63, 0x2c, + 0x3d, 0xf8, 0x0b, 0x4a, 0x69, 0xa1, 0x26, 0x27, 0xda, 0x2c, 0xcb, 0x5c, 0x14, 0xec, 0x89, 0x36, + 0xcd, 0xaf, 0x64, 0x4b, 0x54, 0xdb, 0x26, 0x64, 0x98, 0xbb, 0x53, 0xb7, 0x32, 0xf0, 0x96, 0x29, + 0xd5, 0x1f, 0xd3, 0x50, 0x98, 0x8c, 0xa7, 0xe3, 0x11, 0xf2, 0x60, 0x5e, 0xe5, 0x92, 0xd9, 0x46, + 0x6c, 0x45, 0xf9, 0xa8, 0xd0, 0x4d, 0x44, 0x97, 0x87, 0x16, 0xd0, 0x01, 0xe4, 0xd4, 0xb0, 0x39, + 0x23, 0x84, 0x9b, 0x18, 0xb6, 0x6e, 0x8e, 0xe1, 0xd3, 0xfb, 0x47, 0xee, 0x25, 0xee, 0xf7, 0xfe, + 0x59, 0x1d, 0x3b, 0x56, 0xfd, 0xac, 0x5c, 0x37, 0x09, 0xe1, 0xe8, 0x2f, 0xb0, 0x90, 0x90, 0x1e, + 0xbe, 0x24, 0xd1, 0x67, 0x95, 0x56, 0x32, 0x6a, 0x9b, 0x84, 0x26, 0xe4, 0xc3, 0x50, 0x8c, 0x2c, + 0x75, 0xb3, 0x8a, 0x59, 0x6b, 0xb7, 0xd4, 0xbf, 0xa9, 0x7d, 0x08, 0xc7, 0x3c, 0x40, 0x47, 0x50, + 0xa2, 0x7a, 0xc2, 0x06, 0x03, 0x35, 0x8d, 0x54, 0x97, 0xcc, 0xdf, 0x75, 0x2c, 0xeb, 0x09, 0xe6, + 0x17, 0xe9, 0xd4, 0x13, 0xf1, 0x14, 0x16, 0x98, 0x19, 0x71, 0x16, 0x15, 0x56, 0x67, 0x6b, 0xf9, + 0xad, 0x8d, 0x5b, 0x50, 0xa7, 0x07, 0xa3, 0x5f, 0x62, 0xd3, 0x83, 0x32, 0x81, 0x07, 0xea, 0x55, + 0x1a, 0xb2, 0x5e, 0x10, 0xb2, 0x58, 0x24, 0x38, 0x14, 0xc1, 0x05, 0x49, 0x38, 0x65, 0xb1, 0x79, + 0xd8, 0x3c, 0xbf, 0xe5, 0x84, 0x43, 0xe3, 0xdf, 0x30, 0xee, 0xa7, 0xda, 0xdb, 0xbf, 0x3f, 0xb8, + 0xde, 0x80, 0xfe, 0x3b, 0x2e, 0x4a, 0xdb, 0xc5, 0x0a, 0x77, 0x4a, 0xd0, 0x14, 0x99, 0x76, 0xd2, + 0xb2, 0x2a, 0x6c, 0x21, 0x1b, 0xe5, 0xfa, 0xff, 0x01, 0x26, 0x1d, 0x09, 0x21, 0x28, 0x1d, 0x92, + 0x38, 0xa2, 0x71, 0xc7, 0xe4, 0xd6, 0x4d, 0xa1, 0x25, 0x58, 0x30, 0x3a, 0x9b, 0x19, 0xd7, 0x41, + 0x8b, 0x50, 0xb4, 0xd2, 0x1e, 0x8d, 0x49, 0xe4, 0xce, 0x4a, 0x95, 0xd9, 0xa7, 0x8f, 0x75, 0xd3, + 0xa8, 0x00, 0x59, 0xbd, 0x26, 0x91, 0x3b, 0x87, 0xf2, 0x30, 0xbf, 0xad, 0x9f, 0x38, 0x6e, 0x66, + 0x25, 0xfd, 0xf5, 0x57, 0x15, 0x67, 0xfd, 0xdf, 0x50, 0xbe, 0xae, 0x95, 0x23, 0x17, 0x0a, 0xfb, + 0x4c, 0x34, 0xed, 0xa3, 0xd2, 0x4d, 0xa1, 0x22, 0xe4, 0x26, 0xa2, 0x23, 0x91, 0x77, 0x47, 0x24, + 0x1c, 0x4a, 0xb0, 0x19, 0x03, 0xb6, 0x33, 0xfe, 0x4a, 0x30, 0x28, 0x79, 0x98, 0x3f, 0x3a, 0x69, + 0x34, 0x76, 0x8f, 0x8e, 0xdc, 0x14, 0xaa, 0xc0, 0x4a, 0x6b, 0xff, 0xe8, 0xa4, 0xd9, 0x6c, 0x35, + 0x5a, 0xbb, 0xfb, 0xc7, 0xc1, 0x8b, 0xdd, 0xc3, 0x83, 0xa3, 0xd6, 0xf1, 0x81, 0x1f, 0x34, 0x77, + 0x77, 0x5d, 0xc7, 0x60, 0x6c, 0xc2, 0xfd, 0x5f, 0xb8, 0x1d, 0x94, 0x81, 0x99, 0xd3, 0xa7, 0x6e, + 0x4a, 0xfd, 0x6e, 0x59, 0x87, 0x9d, 0x97, 0x6f, 0x3f, 0x54, 0x9c, 0x77, 0x1f, 0x2a, 0xce, 0x0f, + 0x1f, 0x2a, 0xce, 0x17, 0x1f, 0x2b, 0xa9, 0x77, 0x1f, 0x2b, 0xa9, 0xef, 0x3e, 0x56, 0x52, 0xaf, + 0x37, 0x3a, 0x54, 0x74, 0x87, 0xed, 0x7a, 0xc8, 0xfa, 0xea, 0xf3, 0x66, 0x43, 0x7f, 0xe9, 0xc4, + 0x2c, 0x22, 0x9b, 0xa3, 0xab, 0x1f, 0x54, 0xf2, 0xad, 0xc4, 0xdb, 0x19, 0x75, 0xf9, 0xcf, 0x7e, + 0x0a, 0x00, 0x00, 0xff, 0xff, 0xee, 0x16, 0x32, 0xc7, 0x7e, 0x0d, 0x00, 0x00, } func (m *InboundParams) Marshal() (dAtA []byte, err error) { @@ -1254,6 +1265,13 @@ func (m *Status) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.ErrorMessageRevert) > 0 { + i -= len(m.ErrorMessageRevert) + copy(dAtA[i:], m.ErrorMessageRevert) + i = encodeVarintCrossChainTx(dAtA, i, uint64(len(m.ErrorMessageRevert))) + i-- + dAtA[i] = 0x3a + } if len(m.ErrorMessage) > 0 { i -= len(m.ErrorMessage) copy(dAtA[i:], m.ErrorMessage) @@ -1647,6 +1665,10 @@ func (m *Status) Size() (n int) { if l > 0 { n += 1 + l + sovCrossChainTx(uint64(l)) } + l = len(m.ErrorMessageRevert) + if l > 0 { + n += 1 + l + sovCrossChainTx(uint64(l)) + } return n } @@ -2943,6 +2965,38 @@ func (m *Status) Unmarshal(dAtA []byte) error { } m.ErrorMessage = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ErrorMessageRevert", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCrossChainTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthCrossChainTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthCrossChainTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.ErrorMessageRevert = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipCrossChainTx(dAtA[iNdEx:]) diff --git a/x/crosschain/types/errors.go b/x/crosschain/types/errors.go index ca217bd537..fb72b8096b 100644 --- a/x/crosschain/types/errors.go +++ b/x/crosschain/types/errors.go @@ -55,8 +55,9 @@ var ( 1156, "migration tx from an old tss address detected", ) - ErrValidatingInbound = errorsmod.Register(ModuleName, 1157, "unable to validate inbound") - ErrInvalidGasLimit = errorsmod.Register(ModuleName, 1158, "invalid gas limit") - ErrUnableToSetOutboundInfo = errorsmod.Register(ModuleName, 1159, "unable to set outbound info") - ErrCCTXAlreadyFinalized = errorsmod.Register(ModuleName, 1160, "cctx already finalized") + ErrValidatingInbound = errorsmod.Register(ModuleName, 1157, "unable to validate inbound") + ErrInvalidGasLimit = errorsmod.Register(ModuleName, 1158, "invalid gas limit") + ErrUnableToSetOutboundInfo = errorsmod.Register(ModuleName, 1159, "unable to set outbound info") + ErrCCTXAlreadyFinalized = errorsmod.Register(ModuleName, 1160, "cctx already finalized") + ErrUnableToParseCCTXIndexBytes = errorsmod.Register(ModuleName, 1161, "unable to parse cctx index bytes") ) diff --git a/x/crosschain/types/status.go b/x/crosschain/types/status.go index fc91b8c28b..6e9ba4891a 100644 --- a/x/crosschain/types/status.go +++ b/x/crosschain/types/status.go @@ -5,24 +5,25 @@ import ( "slices" ) +type StatusMessages struct { + StatusMessage string `json:"status_message"` + ErrorMessageOutbound string `json:"error_message_outbound"` + ErrorMessageRevert string `json:"error_message_revert"` +} + func (m *Status) AbortRefunded() { m.IsAbortRefunded = true m.StatusMessage = "CCTX aborted and Refunded" } -// UpdateStatusAndErrorMessages transitions the Status and Error messages. -func (m *Status) UpdateStatusAndErrorMessages(newStatus CctxStatus, statusMsg, errorMsg string) { - m.UpdateStatus(newStatus, statusMsg) - - if errorMsg != "" { - m.UpdateErrorMessage(errorMsg) - } +func (m *Status) UpdateStatusAndErrorMessages(newStatus CctxStatus, messages StatusMessages) { + m.UpdateStatus(newStatus) + m.UpdateErrorMessages(messages) } -// UpdateStatus updates the cctx status and cctx.status.status_message. -func (m *Status) UpdateStatus(newStatus CctxStatus, statusMsg string) { +// UpdateStatus updates the cctx status +func (m *Status) UpdateStatus(newStatus CctxStatus) { if m.ValidateTransition(newStatus) { - m.StatusMessage = fmt.Sprintf("Status changed from %s to %s", m.Status.String(), newStatus.String()) m.Status = newStatus } else { m.StatusMessage = fmt.Sprintf( @@ -30,24 +31,21 @@ func (m *Status) UpdateStatus(newStatus CctxStatus, statusMsg string) { m.Status.String(), newStatus.String(), ) - m.Status = CctxStatus_Aborted } - - if statusMsg != "" { - m.StatusMessage += fmt.Sprintf(": %s", statusMsg) - } } -// UpdateErrorMessage updates cctx.status.error_message. -func (m *Status) UpdateErrorMessage(errorMsg string) { - errMsg := errorMsg +// UpdateErrorMessages updates cctx.status.error_message and cctx.status.error_message_revert. +func (m *Status) UpdateErrorMessages(messages StatusMessages) { + // Always update the status message, status should contain only the most recent update + m.StatusMessage = messages.StatusMessage - if errMsg == "" { - errMsg = "unknown error" + if messages.ErrorMessageOutbound != "" { + m.ErrorMessage = messages.ErrorMessageOutbound + } + if messages.ErrorMessageRevert != "" { + m.ErrorMessageRevert = messages.ErrorMessageRevert } - - m.ErrorMessage = errMsg } func (m *Status) ValidateTransition(newStatus CctxStatus) bool { diff --git a/x/crosschain/types/status_test.go b/x/crosschain/types/status_test.go index f808f6e296..a73a1b5cb7 100644 --- a/x/crosschain/types/status_test.go +++ b/x/crosschain/types/status_test.go @@ -1,7 +1,6 @@ package types_test import ( - "fmt" "testing" "github.com/stretchr/testify/assert" @@ -140,38 +139,21 @@ func TestStatus_ChangeStatus(t *testing.T) { t.Run("should change status and msg if transition is valid", func(t *testing.T) { s := types.Status{Status: types.CctxStatus_PendingInbound} - s.UpdateStatus(types.CctxStatus_PendingOutbound, "msg") + s.UpdateStatus(types.CctxStatus_PendingOutbound) assert.Equal(t, s.Status, types.CctxStatus_PendingOutbound) - assert.Equal(t, s.StatusMessage, "Status changed from PendingInbound to PendingOutbound: msg") }) t.Run("should change status if transition is valid", func(t *testing.T) { s := types.Status{Status: types.CctxStatus_PendingInbound} - s.UpdateStatus(types.CctxStatus_PendingOutbound, "") - fmt.Printf("%+v\n", s) + s.UpdateStatus(types.CctxStatus_PendingOutbound) assert.Equal(t, s.Status, types.CctxStatus_PendingOutbound) - assert.Equal(t, s.StatusMessage, fmt.Sprintf( - "Status changed from %s to %s", - types.CctxStatus_PendingInbound.String(), - types.CctxStatus_PendingOutbound.String()), - ) }) t.Run("should change status to aborted and msg if transition is invalid", func(t *testing.T) { s := types.Status{Status: types.CctxStatus_PendingOutbound} - - s.UpdateStatus(types.CctxStatus_PendingInbound, "msg") + s.UpdateStatus(types.CctxStatus_PendingInbound) assert.Equal(t, s.Status, types.CctxStatus_Aborted) - assert.Equal( - t, - fmt.Sprintf( - "Failed to transition status from %s to %s: msg", - types.CctxStatus_PendingOutbound.String(), - types.CctxStatus_PendingInbound.String(), - ), - s.StatusMessage, - ) }) } @@ -216,3 +198,74 @@ func TestCctxStatus_IsPendingStatus(t *testing.T) { }) } } + +func TestStatus_UpdateErrorMessages(t *testing.T) { + t.Run("should update only status message if error message outbound is empty", func(t *testing.T) { + m := types.Status{ + StatusMessage: "old status message", + ErrorMessage: "old error message", + ErrorMessageRevert: "old error message revert", + } + messages := types.StatusMessages{ + StatusMessage: "status message", + } + m.UpdateErrorMessages(messages) + require.Equal(t, messages.StatusMessage, m.StatusMessage) + require.Equal(t, "old error message", m.ErrorMessage) + require.Equal(t, "old error message revert", m.ErrorMessageRevert) + }) + + t.Run("should update only status message and revert message if outbound message is empty", func(t *testing.T) { + m := types.Status{ + StatusMessage: "old status message", + ErrorMessage: "old error message", + ErrorMessageRevert: "old error message revert", + } + messages := types.StatusMessages{ + StatusMessage: "status message", + ErrorMessageRevert: "error message revert", + } + m.UpdateErrorMessages(messages) + require.Equal(t, messages.StatusMessage, m.StatusMessage) + require.Equal(t, "old error message", m.ErrorMessage) + require.Equal(t, messages.ErrorMessageRevert, m.ErrorMessageRevert) + }) + + t.Run("should update only status message and outbound message if revert message is empty", func(t *testing.T) { + m := types.Status{ + StatusMessage: "old status message", + ErrorMessage: "old error message", + ErrorMessageRevert: "old error message revert", + } + messages := types.StatusMessages{ + StatusMessage: "status message", + ErrorMessageOutbound: "error message outbound", + } + m.UpdateErrorMessages(messages) + require.Equal(t, messages.StatusMessage, m.StatusMessage) + require.Equal(t, messages.ErrorMessageOutbound, m.ErrorMessage) + require.Equal(t, "old error message revert", m.ErrorMessageRevert) + }) + + t.Run("multiple updates to status message should only keep the most recent one", func(t *testing.T) { + m := types.Status{ + StatusMessage: "old status message", + } + messages := types.StatusMessages{ + StatusMessage: "new status message 1", + ErrorMessageOutbound: "new error message outbound", + } + m.UpdateErrorMessages(messages) + require.Equal(t, messages.StatusMessage, m.StatusMessage) + require.Equal(t, messages.ErrorMessageOutbound, m.ErrorMessage) + require.Equal(t, "", m.ErrorMessageRevert) + messages2 := types.StatusMessages{ + StatusMessage: "new status message 2", + ErrorMessageRevert: "new error message revert", + } + m.UpdateErrorMessages(messages2) + require.Equal(t, messages2.StatusMessage, m.StatusMessage) + require.Equal(t, messages.ErrorMessageOutbound, m.ErrorMessage) + require.Equal(t, messages2.ErrorMessageRevert, m.ErrorMessageRevert) + }) +} diff --git a/x/fungible/keeper/evm.go b/x/fungible/keeper/evm.go index 8943c80fed..5f1d7892c3 100644 --- a/x/fungible/keeper/evm.go +++ b/x/fungible/keeper/evm.go @@ -31,6 +31,7 @@ import ( "github.com/zeta-chain/node/pkg/coin" "github.com/zeta-chain/node/pkg/contracts/uniswap/v2-core/contracts/uniswapv2factory.sol" "github.com/zeta-chain/node/pkg/contracts/uniswap/v2-periphery/contracts/uniswapv2router02.sol" + ccctxerror "github.com/zeta-chain/node/pkg/errors" "github.com/zeta-chain/node/pkg/ptr" "github.com/zeta-chain/node/server/config" "github.com/zeta-chain/node/x/fungible/types" @@ -373,12 +374,12 @@ func (k Keeper) CallOnReceiveZevmConnector(ctx sdk.Context, zevmConnectorAbi, err := zevmconnectorcontract.ZetaConnectorZEVMMetaData.GetAbi() if err != nil { - return nil, err + return nil, cosmoserrors.Wrap(types.ErrABIGet, err.Error()) } err = k.DepositCoinsToFungibleModule(ctx, zetaValue) if err != nil { - return nil, err + return nil, cosmoserrors.Wrap(types.ErrDepositZetaToFungibleAccount, err.Error()) } return k.CallEVM( @@ -671,26 +672,27 @@ func (k Keeper) CallEVM( if err != nil { return nil, cosmoserrors.Wrap( types.ErrABIPack, - cosmoserrors.Wrap(err, "failed to create transaction data").Error(), + fmt.Sprintf("failed to create transaction data: %s", err.Error()), ) } k.Logger(ctx).Debug("calling EVM", "from", from, "contract", contract, "value", value, "method", method) resp, err := k.CallEVMWithData(ctx, from, &contract, data, commit, noEthereumTxEvent, value, gasLimit) if err != nil { - errMes := fmt.Sprintf( - "contract call failed: method '%s', contract '%s', args: %v", - method, - contract.Hex(), - args, - ) - - // if it is a revert error then add the revert reason to the error message + // create an error message + errMessage := ccctxerror.NewZEVMErrorMessage(method, contract, args, types.ErrCallEvmWithData.Error(), err) + // if it is a revert error and the revert reason is available, then add it revertErr, ok := err.(*evmtypes.RevertError) if ok { - errMes = fmt.Sprintf("%s, reason: %v", errMes, revertErr.ErrorData()) + errMessage.AddRevertReason(revertErr.ErrorData()) + } + // Marshall the error message into a JSON string. If it fails, return the string representation of the error message + errString, err := errMessage.ToJSON() + if err != nil { + return resp, fmt.Errorf("json marshalling failed %s,%s", err.Error(), errMessage.String()) } - return resp, cosmoserrors.Wrap(err, errMes) + // The JSON string already contains all the necessary information we do not need to wrap + return resp, errors.New(errString) } return resp, nil } diff --git a/x/fungible/keeper/evm_test.go b/x/fungible/keeper/evm_test.go index b95d8228ba..64dd506a83 100644 --- a/x/fungible/keeper/evm_test.go +++ b/x/fungible/keeper/evm_test.go @@ -3,6 +3,7 @@ package keeper_test import ( "encoding/json" "fmt" + "math/big" "testing" @@ -718,7 +719,7 @@ func TestKeeper_CallEVMWithData(t *testing.T) { require.True(t, types.IsContractReverted(res, err)) // check reason is included for revert error - require.ErrorContains(t, err, fmt.Sprintf("reason: %s", utils.ErrHashRevertFoo)) + require.Contains(t, err.Error(), fmt.Sprintf("\"revert_reason\":\"%s\"", utils.ErrHashRevertFoo)) res, err = k.CallEVM( ctx, @@ -765,7 +766,7 @@ func TestKeeper_CallEVMWithData(t *testing.T) { require.False(t, types.IsContractReverted(res, err)) require.NotContains(t, err.Error(), "reason:") - // No revert with successfull call + // No revert with successful call res, err = k.CallEVM( ctx, *abi, diff --git a/x/fungible/keeper/zevm_message_passing_test.go b/x/fungible/keeper/zevm_message_passing_test.go index a2529c0ba3..9b89adb2cc 100644 --- a/x/fungible/keeper/zevm_message_passing_test.go +++ b/x/fungible/keeper/zevm_message_passing_test.go @@ -162,7 +162,7 @@ func TestKeeper_ZEVMDepositAndCallContract(t *testing.T) { data, cctxIndexBytes, ) - require.ErrorIs(t, err, errorMint) + require.ErrorContains(t, err, errorMint.Error()) }) } diff --git a/x/fungible/keeper/zevm_msg_passing.go b/x/fungible/keeper/zevm_msg_passing.go index 675118dec5..402ca08a8c 100644 --- a/x/fungible/keeper/zevm_msg_passing.go +++ b/x/fungible/keeper/zevm_msg_passing.go @@ -1,11 +1,15 @@ package keeper import ( + "fmt" "math/big" sdk "github.com/cosmos/cosmos-sdk/types" ethcommon "github.com/ethereum/go-ethereum/common" + "github.com/pkg/errors" evmtypes "github.com/zeta-chain/ethermint/x/evm/types" + + "github.com/zeta-chain/node/x/fungible/types" ) // ZETADepositAndCallContract deposits native ZETA to the to address if its an account or if the account does not exist yet @@ -18,10 +22,14 @@ func (k Keeper) ZETADepositAndCallContract(ctx sdk.Context, data []byte, indexBytes [32]byte) (*evmtypes.MsgEthereumTxResponse, error) { acc := k.evmKeeper.GetAccount(ctx, to) + if acc == nil || !acc.IsContract() { err := k.DepositCoinZeta(ctx, to, inboundAmount) if err != nil { - return nil, err + return nil, errors.Wrap( + types.ErrDepositZetaToEvmAccount, + fmt.Sprintf("to: %s, amount: %s err %s", to.String(), inboundAmount.String(), err.Error()), + ) } return nil, nil } diff --git a/x/fungible/types/errors.go b/x/fungible/types/errors.go index cb152f7ffe..df92023ec0 100644 --- a/x/fungible/types/errors.go +++ b/x/fungible/types/errors.go @@ -24,7 +24,7 @@ var ( ErrPausedZRC20 = cosmoserrors.Register(ModuleName, 1121, "ZRC20 is paused") ErrForeignCoinNotFound = cosmoserrors.Register(ModuleName, 1122, "foreign coin not found") ErrForeignCoinCapReached = cosmoserrors.Register(ModuleName, 1123, "foreign coin cap reached") - ErrCallNonContract = cosmoserrors.Register(ModuleName, 1124, "can't call a non-contract address") + ErrCallNonContract = cosmoserrors.Register(ModuleName, 1124, "cannot call a non-contract address") ErrForeignCoinAlreadyExist = cosmoserrors.Register(ModuleName, 1125, "foreign coin already exist") ErrNilGasPrice = cosmoserrors.Register(ModuleName, 1127, "nil gas price") ErrAccountNotFound = cosmoserrors.Register(ModuleName, 1128, "account not found") @@ -35,4 +35,19 @@ var ( ErrZeroAddress = cosmoserrors.Register(ModuleName, 1133, "address cannot be zero") ErrInvalidAmount = cosmoserrors.Register(ModuleName, 1134, "invalid amount") ErrMaxSupplyReached = cosmoserrors.Register(ModuleName, 1135, "max supply reached") + ErrCallEvmWithData = cosmoserrors.Register( + ModuleName, + 1136, + "contract call failed when calling EVM with data", + ) + ErrDepositZetaToEvmAccount = cosmoserrors.Register( + ModuleName, + 1137, + "error depositing ZETA to users EVM account", + ) + ErrDepositZetaToFungibleAccount = cosmoserrors.Register( + ModuleName, + 1138, + "error depositing ZETA to fungible module account", + ) )