cosmos-sdk/x/ibc-transfer/module.go

384 lines
12 KiB
Go

package transfer
import (
"encoding/json"
"fmt"
"math/rand"
"github.com/gogo/protobuf/grpc"
"github.com/gorilla/mux"
"github.com/spf13/cobra"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/codec"
cdctypes "github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/types/module"
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
capabilitytypes "github.com/cosmos/cosmos-sdk/x/capability/types"
"github.com/cosmos/cosmos-sdk/x/ibc-transfer/client/cli"
"github.com/cosmos/cosmos-sdk/x/ibc-transfer/client/rest"
"github.com/cosmos/cosmos-sdk/x/ibc-transfer/keeper"
"github.com/cosmos/cosmos-sdk/x/ibc-transfer/simulation"
"github.com/cosmos/cosmos-sdk/x/ibc-transfer/types"
channeltypes "github.com/cosmos/cosmos-sdk/x/ibc/04-channel/types"
porttypes "github.com/cosmos/cosmos-sdk/x/ibc/05-port/types"
host "github.com/cosmos/cosmos-sdk/x/ibc/24-host"
)
var (
_ module.AppModule = AppModule{}
_ porttypes.IBCModule = AppModule{}
_ module.AppModuleBasic = AppModuleBasic{}
)
// AppModuleBasic is the 20-transfer appmodulebasic
type AppModuleBasic struct{}
// Name implements AppModuleBasic interface
func (AppModuleBasic) Name() string {
return types.ModuleName
}
// RegisterCodec implements AppModuleBasic interface
func (AppModuleBasic) RegisterCodec(cdc *codec.Codec) {
types.RegisterCodec(cdc)
}
// DefaultGenesis returns default genesis state as raw bytes for the ibc
// transfer module.
func (AppModuleBasic) DefaultGenesis(cdc codec.JSONMarshaler) json.RawMessage {
return cdc.MustMarshalJSON(types.DefaultGenesisState())
}
// ValidateGenesis performs genesis state validation for the ibc transfer module.
func (AppModuleBasic) ValidateGenesis(cdc codec.JSONMarshaler, bz json.RawMessage) error {
var gs types.GenesisState
if err := cdc.UnmarshalJSON(bz, &gs); err != nil {
return fmt.Errorf("failed to unmarshal %s genesis state: %w", types.ModuleName, err)
}
return gs.Validate()
}
// RegisterRESTRoutes implements AppModuleBasic interface
func (AppModuleBasic) RegisterRESTRoutes(clientCtx client.Context, rtr *mux.Router) {
rest.RegisterRoutes(clientCtx, rtr)
}
// GetTxCmd implements AppModuleBasic interface
func (AppModuleBasic) GetTxCmd(clientCtx client.Context) *cobra.Command {
return cli.NewTxCmd(clientCtx)
}
// GetQueryCmd implements AppModuleBasic interface
func (AppModuleBasic) GetQueryCmd(clientCtx client.Context) *cobra.Command {
return cli.GetQueryCmd(clientCtx)
}
// RegisterInterfaceTypes registers module concrete types into protobuf Any.
func (AppModuleBasic) RegisterInterfaceTypes(registry cdctypes.InterfaceRegistry) {
types.RegisterInterfaces(registry)
}
// AppModule represents the AppModule for this module
type AppModule struct {
AppModuleBasic
keeper keeper.Keeper
}
// NewAppModule creates a new 20-transfer module
func NewAppModule(k keeper.Keeper) AppModule {
return AppModule{
keeper: k,
}
}
// RegisterInvariants implements the AppModule interface
func (AppModule) RegisterInvariants(ir sdk.InvariantRegistry) {
// TODO
}
// Route implements the AppModule interface
func (am AppModule) Route() sdk.Route {
return sdk.NewRoute(types.RouterKey, NewHandler(am.keeper))
}
// QuerierRoute implements the AppModule interface
func (AppModule) QuerierRoute() string {
return types.QuerierRoute
}
// NewQuerierHandler implements the AppModule interface
func (am AppModule) NewQuerierHandler() sdk.Querier {
return nil
}
func (am AppModule) RegisterQueryService(grpc.Server) {}
// InitGenesis performs genesis initialization for the ibc transfer module. It returns
// no validator updates.
func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONMarshaler, data json.RawMessage) []abci.ValidatorUpdate {
var genesisState types.GenesisState
cdc.MustUnmarshalJSON(data, &genesisState)
// TODO: check if the IBC transfer module account is set
InitGenesis(ctx, am.keeper, genesisState)
return []abci.ValidatorUpdate{}
}
func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONMarshaler) json.RawMessage {
gs := ExportGenesis(ctx, am.keeper)
return cdc.MustMarshalJSON(gs)
}
// BeginBlock implements the AppModule interface
func (am AppModule) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) {
}
// EndBlock implements the AppModule interface
func (am AppModule) EndBlock(ctx sdk.Context, req abci.RequestEndBlock) []abci.ValidatorUpdate {
return []abci.ValidatorUpdate{}
}
//____________________________________________________________________________
// AppModuleSimulation functions
// GenerateGenesisState creates a randomized GenState of the transfer module.
func (AppModule) GenerateGenesisState(simState *module.SimulationState) {
simulation.RandomizedGenState(simState)
}
// ProposalContents doesn't return any content functions for governance proposals.
func (AppModule) ProposalContents(_ module.SimulationState) []simtypes.WeightedProposalContent {
return nil
}
// RandomizedParams returns nil.
func (AppModule) RandomizedParams(_ *rand.Rand) []simtypes.ParamChange {
return nil
}
// RegisterStoreDecoder registers a decoder for transfer module's types
func (am AppModule) RegisterStoreDecoder(_ sdk.StoreDecoderRegistry) {
}
// WeightedOperations returns the all the transfer module operations with their respective weights.
func (am AppModule) WeightedOperations(_ module.SimulationState) []simtypes.WeightedOperation {
return nil
}
//____________________________________________________________________________
// Implement IBCModule callbacks
func (am AppModule) OnChanOpenInit(
ctx sdk.Context,
order channeltypes.Order,
connectionHops []string,
portID string,
channelID string,
chanCap *capabilitytypes.Capability,
counterparty channeltypes.Counterparty,
version string,
) error {
// TODO: Enforce ordering, currently relayers use ORDERED channels
// Require portID is the portID transfer module is bound to
boundPort := am.keeper.GetPort(ctx)
if boundPort != portID {
return sdkerrors.Wrapf(porttypes.ErrInvalidPort, "invalid port: %s, expected %s", portID, boundPort)
}
if version != types.Version {
return sdkerrors.Wrapf(porttypes.ErrInvalidPort, "invalid version: %s, expected %s", version, "ics20-1")
}
// Claim channel capability passed back by IBC module
if err := am.keeper.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil {
return sdkerrors.Wrap(channeltypes.ErrChannelCapabilityNotFound, err.Error())
}
// TODO: escrow
return nil
}
func (am AppModule) OnChanOpenTry(
ctx sdk.Context,
order channeltypes.Order,
connectionHops []string,
portID,
channelID string,
chanCap *capabilitytypes.Capability,
counterparty channeltypes.Counterparty,
version,
counterpartyVersion string,
) error {
// TODO: Enforce ordering, currently relayers use ORDERED channels
// Require portID is the portID transfer module is bound to
boundPort := am.keeper.GetPort(ctx)
if boundPort != portID {
return sdkerrors.Wrapf(porttypes.ErrInvalidPort, "invalid port: %s, expected %s", portID, boundPort)
}
if version != types.Version {
return sdkerrors.Wrapf(porttypes.ErrInvalidPort, "invalid version: %s, expected %s", version, "ics20-1")
}
if counterpartyVersion != types.Version {
return sdkerrors.Wrapf(porttypes.ErrInvalidPort, "invalid counterparty version: %s, expected %s", counterpartyVersion, "ics20-1")
}
// Claim channel capability passed back by IBC module
if err := am.keeper.ClaimCapability(ctx, chanCap, host.ChannelCapabilityPath(portID, channelID)); err != nil {
return sdkerrors.Wrap(channeltypes.ErrChannelCapabilityNotFound, err.Error())
}
// TODO: escrow
return nil
}
func (am AppModule) OnChanOpenAck(
ctx sdk.Context,
portID,
channelID string,
counterpartyVersion string,
) error {
if counterpartyVersion != types.Version {
return sdkerrors.Wrapf(porttypes.ErrInvalidPort, "invalid counterparty version: %s, expected %s", counterpartyVersion, "ics20-1")
}
return nil
}
func (am AppModule) OnChanOpenConfirm(
ctx sdk.Context,
portID,
channelID string,
) error {
return nil
}
func (am AppModule) OnChanCloseInit(
ctx sdk.Context,
portID,
channelID string,
) error {
// Disallow user-initiated channel closing for transfer channels
return sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "user cannot close channel")
}
func (am AppModule) OnChanCloseConfirm(
ctx sdk.Context,
portID,
channelID string,
) error {
return nil
}
func (am AppModule) OnRecvPacket(
ctx sdk.Context,
packet channeltypes.Packet,
) (*sdk.Result, []byte, error) {
var data types.FungibleTokenPacketData
if err := types.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err != nil {
return nil, nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "cannot unmarshal ICS-20 transfer packet data: %s", err.Error())
}
acknowledgement := types.FungibleTokenPacketAcknowledgement{
Success: true,
Error: "",
}
if err := am.keeper.OnRecvPacket(ctx, packet, data); err != nil {
acknowledgement = types.FungibleTokenPacketAcknowledgement{
Success: false,
Error: err.Error(),
}
}
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypePacket,
sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName),
sdk.NewAttribute(types.AttributeKeyReceiver, data.Receiver),
sdk.NewAttribute(types.AttributeKeyValue, data.Amount.String()),
),
)
return &sdk.Result{
Events: ctx.EventManager().Events().ToABCIEvents(),
}, acknowledgement.GetBytes(), nil
}
func (am AppModule) OnAcknowledgementPacket(
ctx sdk.Context,
packet channeltypes.Packet,
acknowledgement []byte,
) (*sdk.Result, error) {
var ack types.FungibleTokenPacketAcknowledgement
if err := types.ModuleCdc.UnmarshalJSON(acknowledgement, &ack); err != nil {
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "cannot unmarshal ICS-20 transfer packet acknowledgement: %v", err)
}
var data types.FungibleTokenPacketData
if err := types.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err != nil {
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "cannot unmarshal ICS-20 transfer packet data: %s", err.Error())
}
if err := am.keeper.OnAcknowledgementPacket(ctx, packet, data, ack); err != nil {
return nil, err
}
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypePacket,
sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName),
sdk.NewAttribute(types.AttributeKeyReceiver, data.Receiver),
sdk.NewAttribute(types.AttributeKeyValue, data.Amount.String()),
sdk.NewAttribute(types.AttributeKeyAckSuccess, fmt.Sprintf("%t", ack.Success)),
),
)
if !ack.Success {
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypePacket,
sdk.NewAttribute(types.AttributeKeyAckError, ack.Error),
),
)
}
return &sdk.Result{
Events: ctx.EventManager().Events().ToABCIEvents(),
}, nil
}
func (am AppModule) OnTimeoutPacket(
ctx sdk.Context,
packet channeltypes.Packet,
) (*sdk.Result, error) {
var data types.FungibleTokenPacketData
if err := types.ModuleCdc.UnmarshalJSON(packet.GetData(), &data); err != nil {
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "cannot unmarshal ICS-20 transfer packet data: %s", err.Error())
}
// refund tokens
if err := am.keeper.OnTimeoutPacket(ctx, packet, data); err != nil {
return nil, err
}
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeTimeout,
sdk.NewAttribute(types.AttributeKeyRefundReceiver, data.Sender),
sdk.NewAttribute(types.AttributeKeyRefundValue, data.Amount.String()),
sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName),
),
)
return &sdk.Result{
Events: ctx.EventManager().Events().ToABCIEvents(),
}, nil
}