370 lines
12 KiB
Go
370 lines
12 KiB
Go
package bindings
|
|
|
|
import (
|
|
"encoding/json"
|
|
|
|
wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper"
|
|
wasmvmtypes "github.com/CosmWasm/wasmvm/types"
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
|
bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper"
|
|
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
|
|
|
|
bindingstypes "github.com/wormhole-foundation/wormchain/x/tokenfactory/bindings/types"
|
|
tokenfactorykeeper "github.com/wormhole-foundation/wormchain/x/tokenfactory/keeper"
|
|
tokenfactorytypes "github.com/wormhole-foundation/wormchain/x/tokenfactory/types"
|
|
)
|
|
|
|
// CustomMessageDecorator returns decorator for custom CosmWasm bindings messages
|
|
func CustomMessageDecorator(bank *bankkeeper.BaseKeeper, tokenFactory *tokenfactorykeeper.Keeper) func(wasmkeeper.Messenger) wasmkeeper.Messenger {
|
|
return func(old wasmkeeper.Messenger) wasmkeeper.Messenger {
|
|
return &CustomMessenger{
|
|
wrapped: old,
|
|
bank: bank,
|
|
tokenFactory: tokenFactory,
|
|
}
|
|
}
|
|
}
|
|
|
|
type CustomMessenger struct {
|
|
wrapped wasmkeeper.Messenger
|
|
bank *bankkeeper.BaseKeeper
|
|
tokenFactory *tokenfactorykeeper.Keeper
|
|
}
|
|
|
|
var _ wasmkeeper.Messenger = (*CustomMessenger)(nil)
|
|
|
|
// DispatchMsg executes on the contractMsg.
|
|
func (m *CustomMessenger) DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) ([]sdk.Event, [][]byte, error) {
|
|
if msg.Custom != nil {
|
|
// only handle the happy path where this is really creating / minting / swapping ...
|
|
// leave everything else for the wrapped version
|
|
var contractMsg bindingstypes.TokenFactoryMsg
|
|
if err := json.Unmarshal(msg.Custom, &contractMsg); err != nil {
|
|
return nil, nil, sdkerrors.Wrap(err, "token factory msg")
|
|
}
|
|
if contractMsg.Token == nil {
|
|
return nil, nil, sdkerrors.Wrap(sdkerrors.ErrUnknownRequest, "nil token field")
|
|
}
|
|
tokenMsg := contractMsg.Token
|
|
|
|
if tokenMsg.CreateDenom != nil {
|
|
return m.createDenom(ctx, contractAddr, tokenMsg.CreateDenom)
|
|
}
|
|
if tokenMsg.MintTokens != nil {
|
|
return m.mintTokens(ctx, contractAddr, tokenMsg.MintTokens)
|
|
}
|
|
if tokenMsg.ChangeAdmin != nil {
|
|
return m.changeAdmin(ctx, contractAddr, tokenMsg.ChangeAdmin)
|
|
}
|
|
if tokenMsg.BurnTokens != nil {
|
|
return m.burnTokens(ctx, contractAddr, tokenMsg.BurnTokens)
|
|
}
|
|
if tokenMsg.SetMetadata != nil {
|
|
return m.setMetadata(ctx, contractAddr, tokenMsg.SetMetadata)
|
|
}
|
|
if tokenMsg.ForceTransfer != nil {
|
|
return m.forceTransfer(ctx, contractAddr, tokenMsg.ForceTransfer)
|
|
}
|
|
}
|
|
return m.wrapped.DispatchMsg(ctx, contractAddr, contractIBCPortID, msg)
|
|
}
|
|
|
|
// createDenom creates a new token denom
|
|
func (m *CustomMessenger) createDenom(ctx sdk.Context, contractAddr sdk.AccAddress, createDenom *bindingstypes.CreateDenom) ([]sdk.Event, [][]byte, error) {
|
|
bz, err := PerformCreateDenom(m.tokenFactory, m.bank, ctx, contractAddr, createDenom)
|
|
if err != nil {
|
|
return nil, nil, sdkerrors.Wrap(err, "perform create denom")
|
|
}
|
|
return nil, [][]byte{bz}, nil
|
|
}
|
|
|
|
// PerformCreateDenom is used with createDenom to create a token denom; validates the msgCreateDenom.
|
|
func PerformCreateDenom(f *tokenfactorykeeper.Keeper, b *bankkeeper.BaseKeeper, ctx sdk.Context, contractAddr sdk.AccAddress, createDenom *bindingstypes.CreateDenom) ([]byte, error) {
|
|
if createDenom == nil {
|
|
return nil, wasmvmtypes.InvalidRequest{Err: "create denom null create denom"}
|
|
}
|
|
|
|
msgServer := tokenfactorykeeper.NewMsgServerImpl(*f)
|
|
|
|
msgCreateDenom := tokenfactorytypes.NewMsgCreateDenom(contractAddr.String(), createDenom.Subdenom)
|
|
|
|
if err := msgCreateDenom.ValidateBasic(); err != nil {
|
|
return nil, sdkerrors.Wrap(err, "failed validating MsgCreateDenom")
|
|
}
|
|
|
|
// Create denom
|
|
resp, err := msgServer.CreateDenom(
|
|
sdk.WrapSDKContext(ctx),
|
|
msgCreateDenom,
|
|
)
|
|
if err != nil {
|
|
return nil, sdkerrors.Wrap(err, "creating denom")
|
|
}
|
|
|
|
if createDenom.Metadata != nil {
|
|
newDenom := resp.NewTokenDenom
|
|
err := PerformSetMetadata(f, b, ctx, contractAddr, newDenom, *createDenom.Metadata)
|
|
if err != nil {
|
|
return nil, sdkerrors.Wrap(err, "setting metadata")
|
|
}
|
|
}
|
|
|
|
return resp.Marshal()
|
|
}
|
|
|
|
// mintTokens mints tokens of a specified denom to an address.
|
|
func (m *CustomMessenger) mintTokens(ctx sdk.Context, contractAddr sdk.AccAddress, mint *bindingstypes.MintTokens) ([]sdk.Event, [][]byte, error) {
|
|
err := PerformMint(m.tokenFactory, m.bank, ctx, contractAddr, mint)
|
|
if err != nil {
|
|
return nil, nil, sdkerrors.Wrap(err, "perform mint")
|
|
}
|
|
return nil, nil, nil
|
|
}
|
|
|
|
// PerformMint used with mintTokens to validate the mint message and mint through token factory.
|
|
func PerformMint(f *tokenfactorykeeper.Keeper, b *bankkeeper.BaseKeeper, ctx sdk.Context, contractAddr sdk.AccAddress, mint *bindingstypes.MintTokens) error {
|
|
if mint == nil {
|
|
return wasmvmtypes.InvalidRequest{Err: "mint token null mint"}
|
|
}
|
|
rcpt, err := parseAddress(mint.MintToAddress)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
coin := sdk.Coin{Denom: mint.Denom, Amount: mint.Amount}
|
|
sdkMsg := tokenfactorytypes.NewMsgMint(contractAddr.String(), coin)
|
|
|
|
if err = sdkMsg.ValidateBasic(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Mint through token factory / message server
|
|
msgServer := tokenfactorykeeper.NewMsgServerImpl(*f)
|
|
_, err = msgServer.Mint(sdk.WrapSDKContext(ctx), sdkMsg)
|
|
if err != nil {
|
|
return sdkerrors.Wrap(err, "minting coins from message")
|
|
}
|
|
|
|
if b.BlockedAddr(rcpt) {
|
|
return sdkerrors.Wrapf(err, "minting coins to blocked address %s", rcpt.String())
|
|
}
|
|
|
|
err = b.SendCoins(ctx, contractAddr, rcpt, sdk.NewCoins(coin))
|
|
if err != nil {
|
|
return sdkerrors.Wrap(err, "sending newly minted coins from message")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// changeAdmin changes the admin.
|
|
func (m *CustomMessenger) changeAdmin(ctx sdk.Context, contractAddr sdk.AccAddress, changeAdmin *bindingstypes.ChangeAdmin) ([]sdk.Event, [][]byte, error) {
|
|
err := ChangeAdmin(m.tokenFactory, ctx, contractAddr, changeAdmin)
|
|
if err != nil {
|
|
return nil, nil, sdkerrors.Wrap(err, "failed to change admin")
|
|
}
|
|
return nil, nil, nil
|
|
}
|
|
|
|
// ChangeAdmin is used with changeAdmin to validate changeAdmin messages and to dispatch.
|
|
func ChangeAdmin(f *tokenfactorykeeper.Keeper, ctx sdk.Context, contractAddr sdk.AccAddress, changeAdmin *bindingstypes.ChangeAdmin) error {
|
|
if changeAdmin == nil {
|
|
return wasmvmtypes.InvalidRequest{Err: "changeAdmin is nil"}
|
|
}
|
|
newAdminAddr, err := parseAddress(changeAdmin.NewAdminAddress)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
changeAdminMsg := tokenfactorytypes.NewMsgChangeAdmin(contractAddr.String(), changeAdmin.Denom, newAdminAddr.String())
|
|
if err := changeAdminMsg.ValidateBasic(); err != nil {
|
|
return err
|
|
}
|
|
|
|
msgServer := tokenfactorykeeper.NewMsgServerImpl(*f)
|
|
_, err = msgServer.ChangeAdmin(sdk.WrapSDKContext(ctx), changeAdminMsg)
|
|
if err != nil {
|
|
return sdkerrors.Wrap(err, "failed changing admin from message")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// burnTokens burns tokens.
|
|
func (m *CustomMessenger) burnTokens(ctx sdk.Context, contractAddr sdk.AccAddress, burn *bindingstypes.BurnTokens) ([]sdk.Event, [][]byte, error) {
|
|
err := PerformBurn(m.tokenFactory, ctx, contractAddr, burn)
|
|
if err != nil {
|
|
return nil, nil, sdkerrors.Wrap(err, "perform burn")
|
|
}
|
|
return nil, nil, nil
|
|
}
|
|
|
|
// PerformBurn performs token burning after validating tokenBurn message.
|
|
func PerformBurn(f *tokenfactorykeeper.Keeper, ctx sdk.Context, contractAddr sdk.AccAddress, burn *bindingstypes.BurnTokens) error {
|
|
if burn == nil {
|
|
return wasmvmtypes.InvalidRequest{Err: "burn token null mint"}
|
|
}
|
|
|
|
coin := sdk.Coin{Denom: burn.Denom, Amount: burn.Amount}
|
|
sdkMsg := tokenfactorytypes.NewMsgBurn(contractAddr.String(), coin)
|
|
if burn.BurnFromAddress != "" {
|
|
sdkMsg = tokenfactorytypes.NewMsgBurnFrom(contractAddr.String(), coin, burn.BurnFromAddress)
|
|
}
|
|
|
|
if err := sdkMsg.ValidateBasic(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Burn through token factory / message server
|
|
msgServer := tokenfactorykeeper.NewMsgServerImpl(*f)
|
|
_, err := msgServer.Burn(sdk.WrapSDKContext(ctx), sdkMsg)
|
|
if err != nil {
|
|
return sdkerrors.Wrap(err, "burning coins from message")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// forceTransfer moves tokens.
|
|
func (m *CustomMessenger) forceTransfer(ctx sdk.Context, contractAddr sdk.AccAddress, forcetransfer *bindingstypes.ForceTransfer) ([]sdk.Event, [][]byte, error) {
|
|
err := PerformForceTransfer(m.tokenFactory, ctx, contractAddr, forcetransfer)
|
|
if err != nil {
|
|
return nil, nil, sdkerrors.Wrap(err, "perform force transfer")
|
|
}
|
|
return nil, nil, nil
|
|
}
|
|
|
|
// PerformForceTransfer performs token moving after validating tokenForceTransfer message.
|
|
func PerformForceTransfer(f *tokenfactorykeeper.Keeper, ctx sdk.Context, contractAddr sdk.AccAddress, forcetransfer *bindingstypes.ForceTransfer) error {
|
|
if forcetransfer == nil {
|
|
return wasmvmtypes.InvalidRequest{Err: "force transfer null"}
|
|
}
|
|
|
|
_, err := parseAddress(forcetransfer.FromAddress)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = parseAddress(forcetransfer.ToAddress)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
coin := sdk.Coin{Denom: forcetransfer.Denom, Amount: forcetransfer.Amount}
|
|
sdkMsg := tokenfactorytypes.NewMsgForceTransfer(contractAddr.String(), coin, forcetransfer.FromAddress, forcetransfer.ToAddress)
|
|
|
|
if err := sdkMsg.ValidateBasic(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Transfer through token factory / message server
|
|
msgServer := tokenfactorykeeper.NewMsgServerImpl(*f)
|
|
_, err = msgServer.ForceTransfer(sdk.WrapSDKContext(ctx), sdkMsg)
|
|
if err != nil {
|
|
return sdkerrors.Wrap(err, "force transferring from message")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// createDenom creates a new token denom
|
|
func (m *CustomMessenger) setMetadata(ctx sdk.Context, contractAddr sdk.AccAddress, setMetadata *bindingstypes.SetMetadata) ([]sdk.Event, [][]byte, error) {
|
|
err := PerformSetMetadata(m.tokenFactory, m.bank, ctx, contractAddr, setMetadata.Denom, setMetadata.Metadata)
|
|
if err != nil {
|
|
return nil, nil, sdkerrors.Wrap(err, "perform create denom")
|
|
}
|
|
return nil, nil, nil
|
|
}
|
|
|
|
// PerformSetMetadata is used with setMetadata to add new metadata
|
|
// It also is called inside CreateDenom if optional metadata field is set
|
|
func PerformSetMetadata(f *tokenfactorykeeper.Keeper, b *bankkeeper.BaseKeeper, ctx sdk.Context, contractAddr sdk.AccAddress, denom string, metadata bindingstypes.Metadata) error {
|
|
// ensure contract address is admin of denom
|
|
auth, err := f.GetAuthorityMetadata(ctx, denom)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if auth.Admin != contractAddr.String() {
|
|
return wasmvmtypes.InvalidRequest{Err: "only admin can set metadata"}
|
|
}
|
|
|
|
// ensure we are setting proper denom metadata (bank uses Base field, fill it if missing)
|
|
if metadata.Base == "" {
|
|
metadata.Base = denom
|
|
} else if metadata.Base != denom {
|
|
// this is the key that we set
|
|
return wasmvmtypes.InvalidRequest{Err: "Base must be the same as denom"}
|
|
}
|
|
|
|
// Create and validate the metadata
|
|
bankMetadata := WasmMetadataToSdk(metadata)
|
|
if err := bankMetadata.Validate(); err != nil {
|
|
return err
|
|
}
|
|
|
|
b.SetDenomMetaData(ctx, bankMetadata)
|
|
return nil
|
|
}
|
|
|
|
// GetFullDenom is a function, not method, so the message_plugin can use it
|
|
func GetFullDenom(contract string, subDenom string) (string, error) {
|
|
// Address validation
|
|
if _, err := parseAddress(contract); err != nil {
|
|
return "", err
|
|
}
|
|
fullDenom, err := tokenfactorytypes.GetTokenDenom(contract, subDenom)
|
|
if err != nil {
|
|
return "", sdkerrors.Wrap(err, "validate sub-denom")
|
|
}
|
|
|
|
return fullDenom, nil
|
|
}
|
|
|
|
// parseAddress parses address from bech32 string and verifies its format.
|
|
func parseAddress(addr string) (sdk.AccAddress, error) {
|
|
parsed, err := sdk.AccAddressFromBech32(addr)
|
|
if err != nil {
|
|
return nil, sdkerrors.Wrap(err, "address from bech32")
|
|
}
|
|
err = sdk.VerifyAddressFormat(parsed)
|
|
if err != nil {
|
|
return nil, sdkerrors.Wrap(err, "verify address format")
|
|
}
|
|
return parsed, nil
|
|
}
|
|
|
|
func WasmMetadataToSdk(metadata bindingstypes.Metadata) banktypes.Metadata {
|
|
denoms := []*banktypes.DenomUnit{}
|
|
for _, unit := range metadata.DenomUnits {
|
|
denoms = append(denoms, &banktypes.DenomUnit{
|
|
Denom: unit.Denom,
|
|
Exponent: unit.Exponent,
|
|
Aliases: unit.Aliases,
|
|
})
|
|
}
|
|
return banktypes.Metadata{
|
|
Description: metadata.Description,
|
|
Display: metadata.Display,
|
|
Base: metadata.Base,
|
|
Name: metadata.Name,
|
|
Symbol: metadata.Symbol,
|
|
DenomUnits: denoms,
|
|
}
|
|
}
|
|
|
|
func SdkMetadataToWasm(metadata banktypes.Metadata) *bindingstypes.Metadata {
|
|
denoms := []bindingstypes.DenomUnit{}
|
|
for _, unit := range metadata.DenomUnits {
|
|
denoms = append(denoms, bindingstypes.DenomUnit{
|
|
Denom: unit.Denom,
|
|
Exponent: unit.Exponent,
|
|
Aliases: unit.Aliases,
|
|
})
|
|
}
|
|
return &bindingstypes.Metadata{
|
|
Description: metadata.Description,
|
|
Display: metadata.Display,
|
|
Base: metadata.Base,
|
|
Name: metadata.Name,
|
|
Symbol: metadata.Symbol,
|
|
DenomUnits: denoms,
|
|
}
|
|
}
|