-
Notifications
You must be signed in to change notification settings - Fork 157
Description
Background
Currently, the precompiles/common package in cosmos/evm always encodes revert data as Error(string) format when precompile execution fails (see ReturnRevertError function).
However, Custom Errors introduced in Solidity 0.8.4+ offer several advantages:
- Gas efficient (only selector is stored, no message string needed)
- Type safe (error argument types are checked)
- Distinguishable by error selector on the client side
For example
// Error.sol
error InvalidAddress(address addr);
error InvalidAmount(uint256 amount);Using these custom errors allows transaction receipts to distinguish error types by selector, making debugging and error handling much easier.
Problem
Currently, RunNativeAction always encodes errors as Error(string) format by calling ReturnRevertError(evm, err):
Proposal
Add support for custom revert data in the precompiles/common package:
- Add RevertDataCarrier Interface
// RevertDataCarrier is an error that carries custom ABI-encoded revert data
// (error selector + arguments) instead of a string message.
type RevertDataCarrier interface {
error
RevertData() []byte
}- Enhance ReturnRevertError
func ReturnRevertError(evm *vm.EVM, err error) ([]byte, error) {
// Use custom revert data if available
if carrier, ok := err.(RevertDataCarrier); ok {
data := carrier.RevertData()
evm.Interpreter().SetReturnData(data)
return data, vm.ErrExecutionReverted
}
// Fallback to existing behavior: Error(string) format
revertReasonBz, encErr := evmtypes.RevertReasonBytes(err.Error())
if encErr != nil {
return nil, vm.ErrExecutionReverted
}
evm.Interpreter().SetReturnData(revertReasonBz)
return revertReasonBz, vm.ErrExecutionReverted
}Usage Example
recipientCosmosAddr, err := p.accountKeeper.AddressCodec().BytesToString(recipientEvmAddr.Bytes())
if err != nil {
// Get error definition from ABI
errorABI := abi.Errors["InvalidAddress"]
// Pack arguments
data, _ := errorABI.Inputs.Pack(recipientEvmAddr)
// selector (4 bytes) + data
revertData := append(errorABI.ID.Bytes()[:4], data...)
// Return custom revert data
return nil, &RevertWithData{Data: revertData}
}Benefits
Errors defined in the contract can be easily handled by the client or contract.
const staking = await hre.ethers.getContractAt('StakingI', STAKING_PRECOMPILE_ADDRESS);
try {
await staking.connect(signer).delegate.staticCall(delegatorAddress, validatorAddress, amount, { gasLimit: GAS_LIMIT });
} catch (e) {
const revertData = e.data
// Decode revert data via contract interface (selector + args)
const decoded = staking.interface.parseError(revertData);
expect(decoded, 'revert data should decode to a known error').to.exist;
expect(decoded.name).to.equal('InvalidAddress', 'revert should be InvalidAddress');
// InvalidAddress(address addr)
const [address] = decoded.args;
expect(address.toLowerCase()).to.equal(signer.address.toLowerCase(), 'invalid address');
}Note
I've implemented this feature locally in our project by creating a precompiles/common package, but official support in cosmos/evm would be beneficial for the ecosystem. If this direction is good, I think I can easily contribute