Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ibc): utils for erc20 middleware #1081

Merged
merged 7 commits into from
Nov 15, 2022
Merged
Show file tree
Hide file tree
Changes from 2 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
12 changes: 9 additions & 3 deletions ibc/testing/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ import (

var DefaultTestingAppInit func() (ibcgotesting.TestingApp, map[string]json.RawMessage) = evmosapp.SetupTestingApp

// BaseDenom defines the Evmos mainnet denomination
const BaseDenom = "aevmos"
ramacarlucho marked this conversation as resolved.
Show resolved Hide resolved

// SetupWithGenesisValSet initializes a new SimApp with a validator set and genesis accounts
// that also act as delegators. For simplicity, each validator is bonded with a delegation
// of one consensus engine unit (10^6) in the default token of the simapp from first genesis
Expand Down Expand Up @@ -63,19 +66,22 @@ func SetupWithGenesisValSet(t *testing.T, valSet *tmtypes.ValidatorSet, genAccs
}

// set validators and delegations
stakingGenesis := stakingtypes.NewGenesisState(stakingtypes.DefaultParams(), validators, delegations)
stakingParams := stakingtypes.DefaultParams()
// set bond demon to be aevmos
stakingParams.BondDenom = BaseDenom
stakingGenesis := stakingtypes.NewGenesisState(stakingParams, validators, delegations)
genesisState[stakingtypes.ModuleName] = app.AppCodec().MustMarshalJSON(stakingGenesis)

totalSupply := sdk.NewCoins()
for _, b := range balances {
// add genesis acc tokens and delegated tokens to total supply
totalSupply = totalSupply.Add(b.Coins.Add(sdk.NewCoin(sdk.DefaultBondDenom, bondAmt))...)
totalSupply = totalSupply.Add(b.Coins.Add(sdk.NewCoin(BaseDenom, bondAmt))...)
}

// add bonded amount to bonded pool module account
balances = append(balances, banktypes.Balance{
Address: authtypes.NewModuleAddress(stakingtypes.BondedPoolName).String(),
Coins: sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, bondAmt)},
Coins: sdk.Coins{sdk.NewCoin(BaseDenom, bondAmt)},
})

// update total supply
Expand Down
3 changes: 2 additions & 1 deletion ibc/testing/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"github.com/evmos/ethermint/crypto/ethsecp256k1"
ethermint "github.com/evmos/ethermint/types"
evmtypes "github.com/evmos/ethermint/x/evm/types"
claimtypes "github.com/evmos/evmos/v10/x/claims/types"
)

// ChainIDPrefix defines the default chain ID prefix for Evmos test chains
Expand Down Expand Up @@ -67,7 +68,7 @@ func NewTestChain(t *testing.T, coord *ibcgotesting.Coordinator, chainID string)

balance := banktypes.Balance{
Address: acc.GetAddress().String(),
Coins: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, amount)),
Coins: sdk.NewCoins(sdk.NewCoin(claimtypes.DefaultClaimsDenom, amount)),
ramacarlucho marked this conversation as resolved.
Show resolved Hide resolved
}

app := SetupWithGenesisValSet(t, valSet, []authtypes.GenesisAccount{acc}, chainID, balance)
Expand Down
73 changes: 65 additions & 8 deletions ibc/utils.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package ibc

import (
errorsmod "cosmossdk.io/errors"
sdkerrors "cosmossdk.io/errors"
sdk "github.com/cosmos/cosmos-sdk/types"
errortypes "github.com/cosmos/cosmos-sdk/types/errors"

Expand All @@ -25,41 +25,98 @@ func GetTransferSenderRecipient(packet channeltypes.Packet) (
// unmarshal packet data to obtain the sender and recipient
var data transfertypes.FungibleTokenPacketData
if err := transfertypes.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err != nil {
return nil, nil, "", "", errorsmod.Wrapf(errortypes.ErrUnknownRequest, "cannot unmarshal ICS-20 transfer packet data")
ramacarlucho marked this conversation as resolved.
Show resolved Hide resolved
return nil, nil, "", "", sdkerrors.Wrapf(errortypes.ErrUnknownRequest, "cannot unmarshal ICS-20 transfer packet data")
}

// validate the sender bech32 address from the counterparty chain
// and change the bech32 human readable prefix (HRP) of the sender to `evmos`
sender, err = evmos.GetEvmosAddressFromBech32(data.Sender)
if err != nil {
return nil, nil, "", "", errorsmod.Wrap(err, "invalid sender")
return nil, nil, "", "", sdkerrors.Wrap(err, "invalid sender")
}

// validate the recipient bech32 address from the counterparty chain
// and change the bech32 human readable prefix (HRP) of the recipient to `evmos`
recipient, err = evmos.GetEvmosAddressFromBech32(data.Receiver)
if err != nil {
return nil, nil, "", "", errorsmod.Wrap(err, "invalid recipient")
return nil, nil, "", "", sdkerrors.Wrap(err, "invalid recipient")
}

return sender, recipient, data.Sender, data.Receiver, nil
}

// GetTransferAmount returns the amount from an ICS20 FungibleTokenPacketData.
// GetTransferAmount returns the amount from an ICS20 FungibleTokenPacketData as a string.
func GetTransferAmount(packet channeltypes.Packet) (string, error) {
// unmarshal packet data to obtain the sender and recipient
var data transfertypes.FungibleTokenPacketData
if err := transfertypes.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err != nil {
return "", errorsmod.Wrapf(errortypes.ErrUnknownRequest, "cannot unmarshal ICS-20 transfer packet data")
return "", sdkerrors.Wrapf(errortypes.ErrUnknownRequest, "cannot unmarshal ICS-20 transfer packet data")
}

if data.Amount == "" {
return "", errorsmod.Wrapf(errortypes.ErrInvalidCoins, "empty amount")
return "", sdkerrors.Wrapf(errortypes.ErrInvalidCoins, "empty amount")
}

if _, ok := sdk.NewIntFromString(data.Amount); !ok {
return "", errorsmod.Wrapf(errortypes.ErrInvalidCoins, "invalid amount")
return "", sdkerrors.Wrapf(errortypes.ErrInvalidCoins, "invalid amount")
}

return data.Amount, nil
}

// GetReceivedCoin returns the transferred coin from an ICS20 FungibleTokenPacketData
// as seen from the destination chain.
// If the receiving chain is the source chain of the tokens, it removes the prefix
// path added by source (i.e sender) chain to the denom. Otherwise, it adds the
// prefix path from the destination chain to the denom.
func GetReceivedCoin(srcPort, srcChannel, dstPort, dstChannel, rawDenom, rawAmt string) sdk.Coin {
// NOTE: Denom and amount are already validated
amount, _ := sdk.NewIntFromString(rawAmt)

if transfertypes.ReceiverChainIsSource(srcPort, srcChannel, rawDenom) {
// remove prefix added by sender chain
voucherPrefix := transfertypes.GetDenomPrefix(srcPort, srcChannel)
unprefixedDenom := rawDenom[len(voucherPrefix):]

// coin denomination used in sending from the escrow address
denom := unprefixedDenom

// The denomination used to send the coins is either the native denom or the hash of the path
// if the denomination is not native.
denomTrace := transfertypes.ParseDenomTrace(unprefixedDenom)
if denomTrace.Path != "" {
denom = denomTrace.IBCDenom()
}

return sdk.Coin{
Denom: denom,
Amount: amount,
}
}

// since SendPacket did not prefix the denomination, we must prefix denomination here
sourcePrefix := transfertypes.GetDenomPrefix(dstPort, dstChannel)
// NOTE: sourcePrefix contains the trailing "/"
prefixedDenom := sourcePrefix + rawDenom

// construct the denomination trace from the full raw denomination
denomTrace := transfertypes.ParseDenomTrace(prefixedDenom)
voucherDenom := denomTrace.IBCDenom()

return sdk.Coin{
Denom: voucherDenom,
Amount: amount,
}
}

// GetSentCoin returns the sent coin from an ICS20 FungibleTokenPacketData.
func GetSentCoin(rawDenom, rawAmt string) sdk.Coin {
// NOTE: Denom and amount are already validated
amount, _ := sdk.NewIntFromString(rawAmt)
trace := transfertypes.ParseDenomTrace(rawDenom)

return sdk.Coin{
Denom: trace.IBCDenom(),
Amount: amount,
}
}
131 changes: 131 additions & 0 deletions ibc/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -226,3 +226,134 @@ func TestGetTransferAmount(t *testing.T) {
}
}
}

var (
uosmoDenomtrace = transfertypes.DenomTrace{
Path: "transfer/channel-0",
BaseDenom: "uosmo",
}
uosmoIbcdenom = uosmoDenomtrace.IBCDenom()

uatomDenomtrace = transfertypes.DenomTrace{
Path: "transfer/channel-1",
BaseDenom: "uatom",
}
uatomIbcdenom = uatomDenomtrace.IBCDenom()

aevmosDenomtrace = transfertypes.DenomTrace{
Path: "transfer/channel-0",
BaseDenom: "aevmos",
}
aevmosIbcdenom = aevmosDenomtrace.IBCDenom()

uatomOsmoDenomtrace = transfertypes.DenomTrace{
Path: "transfer/channel-0/transfer/channel-1",
BaseDenom: "uatom",
}
uatomOsmoIbcdenom = uatomOsmoDenomtrace.IBCDenom()
)

func TestGetReceivedCoin(t *testing.T) {

testCases := []struct {
name string
srcPort string
srcChannel string
dstPort string
dstChannel string
rawDenom string
rawAmount string
expCoin sdk.Coin
}{
{
"is not source",
ramacarlucho marked this conversation as resolved.
Show resolved Hide resolved
"transfer",
"channel-0",
"transfer",
"channel-0",
"uosmo",
"10",
sdk.Coin{Denom: uosmoIbcdenom, Amount: sdk.NewInt(10)},
},
{
"is source",
ramacarlucho marked this conversation as resolved.
Show resolved Hide resolved
"transfer",
"channel-0",
"transfer",
"channel-0",
"transfer/channel-0/aevmos",
"10",
sdk.Coin{Denom: "aevmos", Amount: sdk.NewInt(10)},
},
{
"is source multiple ibc",
ramacarlucho marked this conversation as resolved.
Show resolved Hide resolved
"transfer",
"channel-0",
"transfer",
"channel-2",
"transfer/channel-0/transfer/channel-1/uatom",
"10",
sdk.Coin{Denom: uatomIbcdenom, Amount: sdk.NewInt(10)},
},
{
"is source",
MalteHerrmann marked this conversation as resolved.
Show resolved Hide resolved
"transfer",
"channel-0",
"transfer",
"channel-0",
"transfer/channel-1/uatom",
"10",
sdk.Coin{Denom: uatomOsmoIbcdenom, Amount: sdk.NewInt(10)},
},
}

for _, tc := range testCases {
coin := GetReceivedCoin(tc.srcPort, tc.srcChannel, tc.dstPort, tc.dstChannel, tc.rawDenom, tc.rawAmount)
require.Equal(t, tc.expCoin, coin)
}
}

func TestGetSentCoin(t *testing.T) {
testCases := []struct {
name string
rawDenom string
rawAmount string
expCoin sdk.Coin
}{
{
"aevmos",
ramacarlucho marked this conversation as resolved.
Show resolved Hide resolved
"aevmos",
"10",
sdk.Coin{Denom: "aevmos", Amount: sdk.NewInt(10)},
},
{
"aevmos",
ramacarlucho marked this conversation as resolved.
Show resolved Hide resolved
"transfer/channel-0/aevmos",
"10",
sdk.Coin{Denom: aevmosIbcdenom, Amount: sdk.NewInt(10)},
},
{
"uosmo",
ramacarlucho marked this conversation as resolved.
Show resolved Hide resolved
"transfer/channel-0/uosmo",
"10",
sdk.Coin{Denom: uosmoIbcdenom, Amount: sdk.NewInt(10)},
},
{
"uatom",
ramacarlucho marked this conversation as resolved.
Show resolved Hide resolved
"transfer/channel-1/uatom",
"10",
sdk.Coin{Denom: uatomIbcdenom, Amount: sdk.NewInt(10)},
},
{
"IBC uatom",
ramacarlucho marked this conversation as resolved.
Show resolved Hide resolved
"transfer/channel-0/transfer/channel-1/uatom",
"10",
sdk.Coin{Denom: uatomOsmoIbcdenom, Amount: sdk.NewInt(10)},
},
}

for _, tc := range testCases {
coin := GetSentCoin(tc.rawDenom, tc.rawAmount)
require.Equal(t, tc.expCoin, coin)
}
}
4 changes: 2 additions & 2 deletions types/errors.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package types

import (
errorsmod "cosmossdk.io/errors"
sdkerrors "cosmossdk.io/errors"
)

// RootCodespace is the codespace for all errors defined in this package
Expand All @@ -14,5 +14,5 @@ const (

// errors
var (
ErrKeyTypeNotSupported = errorsmod.Register(RootCodespace, codeKeyTypeNotSupported, "key type 'secp256k1' not supported")
ErrKeyTypeNotSupported = sdkerrors.Register(RootCodespace, codeKeyTypeNotSupported, "key type 'secp256k1' not supported")
)
12 changes: 9 additions & 3 deletions types/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (

"github.com/evmos/ethermint/crypto/ethsecp256k1"

errorsmod "cosmossdk.io/errors"
sdkerrors "cosmossdk.io/errors"
ramacarlucho marked this conversation as resolved.
Show resolved Hide resolved
"github.com/cosmos/cosmos-sdk/crypto/keys/ed25519"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
"github.com/cosmos/cosmos-sdk/crypto/types/multisig"
Expand Down Expand Up @@ -65,12 +65,12 @@ func IsSupportedKey(pubkey cryptotypes.PubKey) bool {
func GetEvmosAddressFromBech32(address string) (sdk.AccAddress, error) {
bech32Prefix := strings.SplitN(address, "1", 2)[0]
if bech32Prefix == address {
return nil, errorsmod.Wrapf(errortypes.ErrInvalidAddress, "invalid bech32 address: %s", address)
return nil, sdkerrors.Wrapf(errortypes.ErrInvalidAddress, "invalid bech32 address: %s", address)
}

addressBz, err := sdk.GetFromBech32(address, bech32Prefix)
if err != nil {
return nil, errorsmod.Wrapf(errortypes.ErrInvalidAddress, "invalid address %s, %s", address, err.Error())
return nil, sdkerrors.Wrapf(errortypes.ErrInvalidAddress, "invalid address %s, %s", address, err.Error())
}

// safety check: shouldn't happen
Expand All @@ -80,3 +80,9 @@ func GetEvmosAddressFromBech32(address string) (sdk.AccAddress, error) {

return sdk.AccAddress(addressBz), nil
}

// Include the possibility to use an ERC-20 contract address as coin Denom
// Otherwise Coin Denom validation will fail in some cases (e.g.: transfer ERC-20 tokens through IBC)
func EvmosCoinDenomRegex() string {
return `^0x[a-fA-F0-9]{40}$|^[a-zA-Z][a-zA-Z0-9/:._-]{2,127}`
}
Loading