Skip to content

Commit 7f9cded

Browse files
committed
fix: distribution precompile 32-byte withdraw address inflates native supply
1 parent f632e7f commit 7f9cded

4 files changed

Lines changed: 67 additions & 11 deletions

File tree

‎CHANGELOG.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
- Enforce denom consistency in `GenesisState.Validate` with `Params.TokenDenom`
3030
- Limited tokenfactory queries, removing denial of service possibility
3131
- Indexed admins to reduce query space on tokenfactory denom queries
32+
- 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
3233

3334
### Removed
3435

‎app/keepers/precompiles.go‎

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,38 @@ type Optionals struct {
5353
// defaultOptionals returns the default coded optionals
5454
func defaultOptionals() Optionals {
5555
return Optionals{
56-
AddressCodec: addresscodec.NewBech32Codec(sdk.GetConfig().GetBech32AccountAddrPrefix()),
56+
AddressCodec: evmAddressCodec{addresscodec.NewBech32Codec(sdk.GetConfig().GetBech32AccountAddrPrefix())},
5757
ValidatorAddrCodec: addresscodec.NewBech32Codec(sdk.GetConfig().GetBech32ValidatorAddrPrefix()),
5858
ConsensusAddrCodec: addresscodec.NewBech32Codec(sdk.GetConfig().GetBech32ConsensusAddrPrefix()),
5959
}
6060
}
6161

62+
// evmAddressCodec wraps an account address codec and enforces that any decoded address is exactly
63+
// 20 bytes (a valid EVM account).
64+
//
65+
// The stateful EVM precompiles accept an account address (e.g. the distribution withdraw address)
66+
// and mirror the resulting bank transfer into the EVM StateDB via common.BytesToAddress, which is
67+
// keyed by a 20-byte address. A longer account (e.g. a 32-byte bech32 account) would be silently
68+
// truncated to its trailing 20 bytes during mirroring, causing the StateDB commit to mint a
69+
// duplicate balance to that trailing-20-byte account and inflate native supply. Rejecting any
70+
// non-20-byte address at decode time prevents such addresses from ever entering a mirrored flow.
71+
type evmAddressCodec struct {
72+
address.Codec
73+
}
74+
75+
// StringToBytes decodes the address with the wrapped codec and rejects any result that is not
76+
// exactly 20 bytes, so only EVM-compatible accounts reach the balance-mirroring precompiles.
77+
func (c evmAddressCodec) StringToBytes(text string) ([]byte, error) {
78+
bz, err := c.Codec.StringToBytes(text)
79+
if err != nil {
80+
return nil, err
81+
}
82+
if len(bz) != common.AddressLength {
83+
return nil, fmt.Errorf("invalid address %q: precompiles only accept 20-byte EVM accounts, got %d bytes", text, len(bz))
84+
}
85+
return bz, nil
86+
}
87+
6288
// Option returns a funcion for the corresponding needed coded
6389
type Option func(opts *Optionals)
6490

‎app/keepers/precompiles_test.go‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package keepers
2+
3+
import (
4+
"bytes"
5+
"testing"
6+
7+
addresscodec "github.com/cosmos/cosmos-sdk/codec/address"
8+
"github.com/ethereum/go-ethereum/common"
9+
"github.com/stretchr/testify/require"
10+
)
11+
12+
// TestEVMAddressCodecStringToBytes verifies that evmAddressCodec only accepts exactly-20-byte
13+
// accounts. A longer account (e.g. a 32-byte bech32 account) must be rejected at decode time so
14+
// it can never reach a balance-mirroring precompile and inflate native supply.
15+
func TestEVMAddressCodecStringToBytes(t *testing.T) {
16+
inner := addresscodec.NewBech32Codec("kii")
17+
codec := evmAddressCodec{inner}
18+
19+
// A 20-byte account decodes successfully and round-trips.
20+
addr20 := bytes.Repeat([]byte{0xAB}, common.AddressLength)
21+
str20, err := inner.BytesToString(addr20)
22+
require.NoError(t, err)
23+
24+
got, err := codec.StringToBytes(str20)
25+
require.NoError(t, err)
26+
require.Equal(t, addr20, got)
27+
28+
// A 32-byte account is rejected.
29+
addr32 := bytes.Repeat([]byte{0xCD}, 32)
30+
str32, err := inner.BytesToString(addr32)
31+
require.NoError(t, err)
32+
33+
_, err = codec.StringToBytes(str32)
34+
require.Error(t, err)
35+
36+
// An invalid bech32 string is rejected by the wrapped codec.
37+
_, err = codec.StringToBytes("not-a-valid-address")
38+
require.Error(t, err)
39+
}

‎tests/e2e/genesis.go‎

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -101,17 +101,12 @@ func modifyGenesis(path, moniker, amountStr string, addrAll []sdk.AccAddress, de
101101
}
102102

103103
icaGenesisState.HostGenesisState.Params.AllowMessages = []string{
104-
"/cosmos.authz.v1beta1.MsgExec",
105-
"/cosmos.authz.v1beta1.MsgGrant",
106-
"/cosmos.authz.v1beta1.MsgRevoke",
107104
"/cosmos.bank.v1beta1.MsgSend",
108105
"/cosmos.bank.v1beta1.MsgMultiSend",
109106
"/cosmos.distribution.v1beta1.MsgSetWithdrawAddress",
110107
"/cosmos.distribution.v1beta1.MsgWithdrawValidatorCommission",
111108
"/cosmos.distribution.v1beta1.MsgFundCommunityPool",
112109
"/cosmos.distribution.v1beta1.MsgWithdrawDelegatorReward",
113-
"/cosmos.feegrant.v1beta1.MsgGrantAllowance",
114-
"/cosmos.feegrant.v1beta1.MsgRevokeAllowance",
115110
"/cosmos.gov.v1beta1.MsgVoteWeighted",
116111
"/cosmos.gov.v1beta1.MsgSubmitProposal",
117112
"/cosmos.gov.v1beta1.MsgDeposit",
@@ -122,11 +117,6 @@ func modifyGenesis(path, moniker, amountStr string, addrAll []sdk.AccAddress, de
122117
"/cosmos.staking.v1beta1.MsgBeginRedelegate",
123118
"/cosmos.staking.v1beta1.MsgCreateValidator",
124119
"/cosmos.vesting.v1beta1.MsgCreateVestingAccount",
125-
"/ibc.applications.transfer.v1.MsgTransfer",
126-
"/tendermint.liquidity.v1beta1.MsgCreatePool",
127-
"/tendermint.liquidity.v1beta1.MsgSwapWithinBatch",
128-
"/tendermint.liquidity.v1beta1.MsgDepositWithinBatch",
129-
"/tendermint.liquidity.v1beta1.MsgWithdrawWithinBatch",
130120
}
131121

132122
icaGenesisStateBz, err := cdc.MarshalJSON(&icaGenesisState)

0 commit comments

Comments
 (0)