Skip to content

Commit

Permalink
feat(evm): add bech32 precompile (#303)
Browse files Browse the repository at this point in the history
* feat(evm): add bech32 precompile

- 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

* fix(ci): downgrade curl

Looks like `curl` has been downgraded in `alpine` repos

* fix(evm): change bech32 precompile to view

* doc(avs): add comment for `ChainIDPrefix`

* fix(app): block precompiles, predeploys in x/bank

* fix(avs): remove events.go file

* chore(lint): golang lint

Tracers are triggered via `init` so they need to be blank-imported.
Automatic import of go-ethereum/common should happen after the comment
not before

* remove TODO, respond to AI comment

* fix(evm): check bech32 addr length in precompile

* fix(bech32-precompile): correct the comment

* doc(app): clarify Berlin precompiles not blocked
  • Loading branch information
MaxMustermann2 authored Feb 14, 2025
1 parent 814d2e1 commit 3698ac5
Show file tree
Hide file tree
Showing 13 changed files with 768 additions and 33 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ RUN apk add --no-cache \
ca-certificates=20241121-r1 \
libstdc++~=13.2 \
jq~=1.7 \
curl~=8.12 \
curl~=8.11 \
bash~=5.2 \
&& addgroup -g 1000 exocore \
&& adduser -S -h /home/exocore -D exocore -u 1000 -G exocore
Expand Down
19 changes: 15 additions & 4 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,6 @@ import (
abci "github.com/cometbft/cometbft/abci/types"
"github.com/cometbft/cometbft/libs/log"

"github.com/evmos/evmos/v16/precompiles/common"

"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/grpc/node"
Expand Down Expand Up @@ -185,6 +183,7 @@ import (

"github.com/ExocoreNetwork/exocore/x/evm"
evmkeeper "github.com/ExocoreNetwork/exocore/x/evm/keeper"
exocoreevmtypes "github.com/ExocoreNetwork/exocore/x/evm/types"
evmtypes "github.com/evmos/evmos/v16/x/evm/types"

"github.com/evmos/evmos/v16/encoding"
Expand All @@ -204,6 +203,8 @@ import (
// Force-load the tracer engines to trigger registration due to Go-Ethereum v1.10.15 changes
_ "github.com/ethereum/go-ethereum/eth/tracers/js"
_ "github.com/ethereum/go-ethereum/eth/tracers/native"

"github.com/ethereum/go-ethereum/common"
)

// Name defines the application binary name
Expand Down Expand Up @@ -1393,8 +1394,18 @@ func (app *ExocoreApp) BlockedAddrs() map[string]bool {
blockedAddrs[authtypes.NewModuleAddress(acc).String()] = !allowedReceivingModAcc[acc]
}

for _, precompile := range common.DefaultPrecompilesBech32 {
blockedAddrs[precompile] = true
// prevent all precompile addresses from receiving or sending tokens
// we don't add the Ethereum-inherited Berlin precompiles, since they are
// allowed to receive tokens on Eth mainnet.
for _, hexAddr := range exocoreevmtypes.ExocoreAvailableEVMExtensions {
bech32Addr := sdk.AccAddress(common.HexToAddress(hexAddr).Bytes()).String()
blockedAddrs[bech32Addr] = true
}

// now do the predeploys
for _, hexAddr := range exocoreevmtypes.DefaultPredeploys {
bech32Addr := sdk.AccAddress(common.HexToAddress(hexAddr.Address).Bytes()).String()
blockedAddrs[bech32Addr] = true
}

return blockedAddrs
Expand Down
2 changes: 1 addition & 1 deletion networks/local/exocore/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ RUN LEDGER_ENABLED=false make build
FROM alpine:3.19 AS run
RUN apk add --no-cache libstdc++~=13.2 \
bash~=5.2 \
curl~=8.12 \
curl~=8.11 \
jq~=1.7 \
&& addgroup -g 1000 exocore \
&& adduser -S -h /home/exocore -D exocore -u 1000 -G exocore
Expand Down
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 view 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 view 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": "view"
},
{
"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": "view"
}
]
102 changes: 102 additions & 0 deletions precompiles/bech32/bech32.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
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
}

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 3698ac5

Please sign in to comment.