Skip to content

Commit

Permalink
feat(evm): add bech32 precompile
Browse files Browse the repository at this point in the history
- Allow conversion of Solidity `address` to bech32 `string` using the
  precompile. A prefix must be supplied.
- Allow conversion of Solidity `string` from bech32 format to `address`.
  A prefix must not be supplied and is instead automatically derived.

TODO: localnet testing via `cast call` the precompile directly, and via
another contract to see how it handles failures
  • Loading branch information
MaxMustermann2 committed Feb 11, 2025
1 parent c871d19 commit 3df038b
Show file tree
Hide file tree
Showing 9 changed files with 736 additions and 19 deletions.
27 changes: 27 additions & 0 deletions precompiles/bech32/IBech32.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.17;

/// @dev The IBech32 contract's address.
address constant BECH32_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000000400;

IBech32 constant BECH32_CONTRACT = IBech32(BECH32_PRECOMPILE_ADDRESS);

/// @author ExocoreNetwork
/// @title Bech32 Precompiled Contract
/// @dev This contract can be used by Solidity devs to convert from `string bech32Addr` to
/// `address 0xAddr` and vice versa.
/// @custom:address 0x0000000000000000000000000000000000000400
interface IBech32 {

/// @dev Defines a method for converting a hex formatted address to bech32.
/// @param addr The hex address to be converted.
/// @param prefix The human readable prefix (HRP) of the bech32 address.
/// @return bech32Address The address in bech32 format.
function hexToBech32(address addr, string memory prefix) external returns (string memory bech32Address);

/// @dev Defines a method for converting a bech32 formatted address to hex.
/// @param bech32Address The bech32 address to be converted.
/// @return addr The address in hex format.
function bech32ToHex(string memory bech32Address) external returns (address addr);

}
45 changes: 45 additions & 0 deletions precompiles/bech32/abi.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
[
{
"type": "function",
"name": "bech32ToHex",
"inputs": [
{
"name": "bech32Address",
"type": "string",
"internalType": "string"
}
],
"outputs": [
{
"name": "addr",
"type": "address",
"internalType": "address"
}
],
"stateMutability": "nonpayable"
},
{
"type": "function",
"name": "hexToBech32",
"inputs": [
{
"name": "addr",
"type": "address",
"internalType": "address"
},
{
"name": "prefix",
"type": "string",
"internalType": "string"
}
],
"outputs": [
{
"name": "bech32Address",
"type": "string",
"internalType": "string"
}
],
"stateMutability": "nonpayable"
}
]
103 changes: 103 additions & 0 deletions precompiles/bech32/bech32.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package bech32

import (
"bytes"
"embed"
"fmt"

storetypes "github.com/cosmos/cosmos-sdk/store/types"
authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper"

"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/vm"
cmn "github.com/evmos/evmos/v16/precompiles/common"
)

const (
gasPerCall = 6_000
)

var _ vm.PrecompiledContract = &Precompile{}

// Embed abi json file to the executable binary. Needed when importing as dependency.
//
//go:embed abi.json
var f embed.FS

// Precompile defines the precompiled contract for deposit.
type Precompile struct {
cmn.Precompile
}

// NewPrecompile instantiates a new IBech32 precompile.
func NewPrecompile(authzKeeper authzkeeper.Keeper) (*Precompile, error) {
abiBz, err := f.ReadFile("abi.json")
if err != nil {
return nil, fmt.Errorf("error loading the deposit ABI %s", err)
}

newAbi, err := abi.JSON(bytes.NewReader(abiBz))
if err != nil {
return nil, fmt.Errorf(cmn.ErrInvalidABI, err)
}

return &Precompile{
Precompile: cmn.Precompile{
ABI: newAbi,
AuthzKeeper: authzKeeper,
KvGasConfig: storetypes.KVGasConfig(),
TransientKVGasConfig: storetypes.TransientGasConfig(),
// should be configurable in the future.
ApprovalExpiration: cmn.DefaultExpirationDuration,
},
}, nil
}

// Address returns the address of the bech32 precompile.
func (p Precompile) Address() common.Address {
return common.HexToAddress("0x0000000000000000000000000000000000000400")
}

// RequiredGas returns the gas required to execute the bech32 precompile.
func (p Precompile) RequiredGas([]byte) uint64 {
return gasPerCall
}

// Run performs the bech32 precompile.
func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz []byte, err error) {
ctx, stateDB, method, initialGas, args, err := p.RunSetup(evm, contract, readOnly, p.IsTransaction)
if err != nil {
return nil, err
}
defer cmn.HandleGasError(ctx, contract, initialGas, &err)()
// bug fix to commit dirty objects
if err := stateDB.Commit(); err != nil {
return nil, err
}

// TODO: check if stateless precompiles can return an error?
switch method.Name {
case MethodHexToBech32:
return p.HexToBech32(method, args)
case MethodBech32ToHex:
return p.Bech32ToHex(method, args)
}

cost := ctx.GasMeter().GasConsumed() - initialGas
if !contract.UseGas(cost) {
return nil, vm.ErrOutOfGas
}
return nil, nil
}

// IsTransaction reports whether a precompile is write (true) or read-only (false).
func (Precompile) IsTransaction(methodID string) bool {
switch methodID {
// explicitly mark read-only for these
case MethodBech32ToHex, MethodHexToBech32:
return false
default:
return false
}
}
Loading

0 comments on commit 3df038b

Please sign in to comment.