Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
- Enforce denom consistency in `GenesisState.Validate` with `Params.TokenDenom`
- Limited tokenfactory queries, removing denial of service possibility
- Indexed admins to reduce query space on tokenfactory denom queries
- Fix native token supply inflation from the stateful precompiles by wrapping the account address codec (`evmAddressCodec`) to reject non-20-byte accounts (e.g. a 32-byte bech32 withdraw, module, or CosmWasm contract address) at decode time, preventing such addresses from being truncated and minted a duplicate balance when mirrored into the EVM StateDB

### Removed

Expand Down
28 changes: 27 additions & 1 deletion app/keepers/precompiles.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,38 @@ type Optionals struct {
// defaultOptionals returns the default coded optionals
func defaultOptionals() Optionals {
return Optionals{
AddressCodec: addresscodec.NewBech32Codec(sdk.GetConfig().GetBech32AccountAddrPrefix()),
AddressCodec: evmAddressCodec{addresscodec.NewBech32Codec(sdk.GetConfig().GetBech32AccountAddrPrefix())},
Comment thread
mattkii marked this conversation as resolved.
ValidatorAddrCodec: addresscodec.NewBech32Codec(sdk.GetConfig().GetBech32ValidatorAddrPrefix()),
ConsensusAddrCodec: addresscodec.NewBech32Codec(sdk.GetConfig().GetBech32ConsensusAddrPrefix()),
}
}

// evmAddressCodec wraps an account address codec and enforces that any decoded address is exactly
// 20 bytes (a valid EVM account).
//
// The stateful EVM precompiles accept an account address (e.g. the distribution withdraw address)
// and mirror the resulting bank transfer into the EVM StateDB via common.BytesToAddress, which is
// keyed by a 20-byte address. A longer account (e.g. a 32-byte bech32 account) would be silently
// truncated to its trailing 20 bytes during mirroring, causing the StateDB commit to mint a
// duplicate balance to that trailing-20-byte account and inflate native supply. Rejecting any
// non-20-byte address at decode time prevents such addresses from ever entering a mirrored flow.
type evmAddressCodec struct {
address.Codec
}

// StringToBytes decodes the address with the wrapped codec and rejects any result that is not
// exactly 20 bytes, so only EVM-compatible accounts reach the balance-mirroring precompiles.
func (c evmAddressCodec) StringToBytes(text string) ([]byte, error) {
bz, err := c.Codec.StringToBytes(text)
if err != nil {
return nil, err
}
if len(bz) != common.AddressLength {
return nil, fmt.Errorf("invalid address %q: precompiles only accept 20-byte EVM accounts, got %d bytes", text, len(bz))
}
return bz, nil
}

// Option returns a funcion for the corresponding needed coded
type Option func(opts *Optionals)

Expand Down
40 changes: 40 additions & 0 deletions app/keepers/precompiles_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package keepers

import (
"bytes"
"testing"

"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"

addresscodec "github.com/cosmos/cosmos-sdk/codec/address"
)

// TestEVMAddressCodecStringToBytes verifies that evmAddressCodec only accepts exactly-20-byte
// accounts. A longer account (e.g. a 32-byte bech32 account) must be rejected at decode time so
// it can never reach a balance-mirroring precompile and inflate native supply.
func TestEVMAddressCodecStringToBytes(t *testing.T) {
inner := addresscodec.NewBech32Codec("kii")
codec := evmAddressCodec{inner}

// A 20-byte account decodes successfully and round-trips.
addr20 := bytes.Repeat([]byte{0xAB}, common.AddressLength)
str20, err := inner.BytesToString(addr20)
require.NoError(t, err)

got, err := codec.StringToBytes(str20)
require.NoError(t, err)
require.Equal(t, addr20, got)

// A 32-byte account is rejected.
addr32 := bytes.Repeat([]byte{0xCD}, 32)
str32, err := inner.BytesToString(addr32)
require.NoError(t, err)

_, err = codec.StringToBytes(str32)
require.Error(t, err)

// An invalid bech32 string is rejected by the wrapped codec.
_, err = codec.StringToBytes("not-a-valid-address")
require.Error(t, err)
}
10 changes: 0 additions & 10 deletions tests/e2e/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,17 +101,12 @@ func modifyGenesis(path, moniker, amountStr string, addrAll []sdk.AccAddress, de
}

icaGenesisState.HostGenesisState.Params.AllowMessages = []string{
"/cosmos.authz.v1beta1.MsgExec",
"/cosmos.authz.v1beta1.MsgGrant",
"/cosmos.authz.v1beta1.MsgRevoke",
"/cosmos.bank.v1beta1.MsgSend",
"/cosmos.bank.v1beta1.MsgMultiSend",
"/cosmos.distribution.v1beta1.MsgSetWithdrawAddress",
"/cosmos.distribution.v1beta1.MsgWithdrawValidatorCommission",
"/cosmos.distribution.v1beta1.MsgFundCommunityPool",
"/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward",
"/cosmos.feegrant.v1beta1.MsgGrantAllowance",
"/cosmos.feegrant.v1beta1.MsgRevokeAllowance",
"/cosmos.gov.v1beta1.MsgVoteWeighted",
"/cosmos.gov.v1beta1.MsgSubmitProposal",
"/cosmos.gov.v1beta1.MsgDeposit",
Expand All @@ -122,11 +117,6 @@ func modifyGenesis(path, moniker, amountStr string, addrAll []sdk.AccAddress, de
"/cosmos.staking.v1beta1.MsgBeginRedelegate",
"/cosmos.staking.v1beta1.MsgCreateValidator",
"/cosmos.vesting.v1beta1.MsgCreateVestingAccount",
"/ibc.applications.transfer.v1.MsgTransfer",
"/tendermint.liquidity.v1beta1.MsgCreatePool",
"/tendermint.liquidity.v1beta1.MsgSwapWithinBatch",
"/tendermint.liquidity.v1beta1.MsgDepositWithinBatch",
"/tendermint.liquidity.v1beta1.MsgWithdrawWithinBatch",
}

icaGenesisStateBz, err := cdc.MarshalJSON(&icaGenesisState)
Expand Down
Loading
You are viewing a condensed version of this merge commit. You can view the full changes here.