Skip to content

Commit

Permalink
Wormchain token denom bug fix
Browse files Browse the repository at this point in the history
  • Loading branch information
Maxwell Dulin committed Feb 11, 2025
1 parent 425b48b commit 40c2dec
Show file tree
Hide file tree
Showing 9 changed files with 219 additions and 2 deletions.
2 changes: 1 addition & 1 deletion wormchain/app/apptesting/test_suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ var (
// Setup sets up basic environment for suite (App, Ctx, and test accounts)
func (s *KeeperTestHelper) Setup() {
s.App = Setup(s.T(), true, 0)
s.Ctx = s.App.BaseApp.NewContext(false, tmtypes.Header{Height: 1, ChainID: "osmosis-1", Time: time.Now().UTC()})
s.Ctx = s.App.BaseApp.NewContext(false, tmtypes.Header{Height: 1, ChainID: "wormchain", Time: time.Now().UTC()})
s.QueryHelper = &baseapp.QueryServiceTestHelper{
GRPCQueryRouter: s.App.GRPCQueryRouter(),
Ctx: s.Ctx,
Expand Down
2 changes: 1 addition & 1 deletion wormchain/x/tokenfactory/bindings/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import (

func CreateTestInput(t *testing.T) (*app.App, sdk.Context) {
osmosis := apptesting.Setup(t, true, 0)
ctx := osmosis.BaseApp.NewContext(false, tmproto.Header{Height: 1, ChainID: "osmosis-1", Time: time.Now().UTC()})
ctx := osmosis.BaseApp.NewContext(false, tmproto.Header{Height: 1, ChainID: "wormchain", Time: time.Now().UTC()})
return osmosis, ctx
}

Expand Down
20 changes: 20 additions & 0 deletions wormchain/x/tokenfactory/keeper/bankactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/wormhole-foundation/wormchain/x/tokenfactory/types"
denoms "github.com/wormhole-foundation/wormchain/x/tokenfactory/types"
)

func (k Keeper) mintTo(ctx sdk.Context, amount sdk.Coin, mintTo string) error {
Expand All @@ -15,6 +16,25 @@ func (k Keeper) mintTo(ctx sdk.Context, amount sdk.Coin, mintTo string) error {
return err
}

// We enable the new conditional approximately two weeks after block 12,066,314 for mainnet and 13,361,706 on testnet, which is
// calculated by dividing the number of seconds in a week by the average block time (~6s).
// On testnet, the block height is different (and so is the block time) with a block time of ~6s.
// On mainnet, the average block time is 5.77 seconds according to the PR https://github.com/wormhole-foundation/wormhole/pull/3946/files.
// At 5.77 seconds/block, this is ~209,636 blocks for mainnet. On testnet at 6 seconds/block, this is ~201,600 blocks for testnet.
// Therefore, mainnet cutover height is 12,066,314 + 209,636 = 12,275,950 and testnet cutover height is 13,361,706 + 201,600 = 13,563,306.
// The target is about ~7:30pm UTC January 28th, 2025.
isMainnet := ctx.ChainID() == "wormchain"
isTestnet := ctx.ChainID() == "wormchain-testnet-0"

if (isMainnet && ctx.BlockHeight() >= 12275950) || (isTestnet && ctx.BlockHeight() >= 13563306) {
// Cutover is required because the call to GetSupply() will use more gas, which would result in a consensus failure.
totalSupplyCurrent := k.bankKeeper.GetSupply(ctx, amount.Denom)
TotalSupplyAfter := totalSupplyCurrent.Add(amount) // Can't integer overflow because of a ValidateBasic() check on this amount
if TotalSupplyAfter.Amount.GTE(denoms.MintAmountLimit) {
return fmt.Errorf("failed to mint - surpassed maximum mint amount")
}
}

err = k.bankKeeper.MintCoins(ctx, types.ModuleName, sdk.NewCoins(amount))
if err != nil {
return err
Expand Down
178 changes: 178 additions & 0 deletions wormchain/x/tokenfactory/keeper/msg_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,20 @@ package keeper_test

import (
"fmt"
"math/big"
"time"

"github.com/wormhole-foundation/wormchain/x/tokenfactory/types"

sdk "github.com/cosmos/cosmos-sdk/types"
denoms "github.com/wormhole-foundation/wormchain/x/tokenfactory/types"

//banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
tmtypes "github.com/tendermint/tendermint/proto/tendermint/types"
)

var mainnetUseConditionalHeight = int64(11445039)

// TestMintDenomMsg tests TypeMsgMint message is emitted on a successful mint
func (suite *KeeperTestSuite) TestMintDenomMsg() {
// Create a denom
Expand Down Expand Up @@ -49,6 +56,177 @@ func (suite *KeeperTestSuite) TestMintDenomMsg() {
}
}

func (suite *KeeperTestSuite) TestMintHuge() {
// Create a denom
suite.CreateDefaultDenom()

suite.Ctx = suite.App.BaseApp.NewContext(false, tmtypes.Header{Height: mainnetUseConditionalHeight, ChainID: "wormchain", Time: time.Now().UTC()})

largeAmount := big.NewInt(0).Sub(big.NewInt(0).Exp(big.NewInt(2), big.NewInt(256), big.NewInt(0)), big.NewInt(1)) // (2 ** 256)-1
belowLargeAmount := big.NewInt(0).Exp(big.NewInt(2), big.NewInt(191), big.NewInt(0)) // 2 ** 191
for _, tc := range []struct {
desc string
amount sdk.Int
mintDenom string
admin string
valid bool
expectedMessageEvents int
}{
{
desc: "failure case - too many",
amount: sdk.NewIntFromBigInt(largeAmount),
mintDenom: suite.defaultDenom,
admin: suite.TestAccs[0].String(),
valid: false,
expectedMessageEvents: 0,
},
{
desc: "success case with 191",
amount: sdk.NewIntFromBigInt(belowLargeAmount),
mintDenom: suite.defaultDenom,
admin: suite.TestAccs[0].String(),
valid: true,
expectedMessageEvents: 1,
},
{
desc: "failure case - too many accumulated tokens",
amount: sdk.NewIntFromBigInt(belowLargeAmount),
mintDenom: suite.defaultDenom,
admin: suite.TestAccs[0].String(),
valid: false,
expectedMessageEvents: 0,
},
} {
suite.Run(fmt.Sprintf("Case %s", tc.desc), func() {
ctx := suite.Ctx.WithEventManager(sdk.NewEventManager())
suite.Require().Equal(0, len(ctx.EventManager().Events()))
// Test mint message
suite.msgServer.Mint(sdk.WrapSDKContext(ctx), types.NewMsgMint(tc.admin, sdk.NewCoin(tc.mintDenom, tc.amount))) //nolint:errcheck
// Ensure current number and type of event is emitted
suite.AssertEventEmitted(ctx, types.TypeMsgMint, tc.expectedMessageEvents)
})
}
}

func (suite *KeeperTestSuite) TestMintOffByOne() {
// Create a denom
suite.CreateDefaultDenom()
suite.Ctx = suite.App.BaseApp.NewContext(false, tmtypes.Header{Height: mainnetUseConditionalHeight, ChainID: "wormchain", Time: time.Now().UTC()})

for _, tc := range []struct {
desc string
amount sdk.Int
mintDenom string
admin string
valid bool
expectedMessageEvents int
}{
{
desc: "failure case - too many plus 1",
amount: denoms.MintAmountLimit.Add(sdk.NewIntFromUint64(1)), // 2 ** 192 + 1
mintDenom: suite.defaultDenom,
admin: suite.TestAccs[0].String(),
valid: false,
expectedMessageEvents: 0,
},
{
desc: "failure case - too many exactly",
amount: denoms.MintAmountLimit, // 2 ** 192
mintDenom: suite.defaultDenom,
admin: suite.TestAccs[0].String(),
valid: true,
expectedMessageEvents: 0,
},
{
desc: "success case - one less than limit",
amount: denoms.MintAmountLimit.Sub(sdk.NewIntFromUint64(1)), // 2 ** 192 -1
mintDenom: suite.defaultDenom,
admin: suite.TestAccs[0].String(),
valid: true,
expectedMessageEvents: 1,
},
} {
suite.Run(fmt.Sprintf("Case %s", tc.desc), func() {
ctx := suite.Ctx.WithEventManager(sdk.NewEventManager())
suite.Require().Equal(0, len(ctx.EventManager().Events()))
// Test mint message
suite.msgServer.Mint(sdk.WrapSDKContext(ctx), types.NewMsgMint(tc.admin, sdk.NewCoin(tc.mintDenom, tc.amount))) //nolint:errcheck
// Ensure current number and type of event is emitted
suite.AssertEventEmitted(ctx, types.TypeMsgMint, tc.expectedMessageEvents)
})
}
}

func (suite *KeeperTestSuite) TestMintFixBlockHeightChecks() {
// Create a denom
suite.CreateDefaultDenom()

test_cases := []struct {
desc string
amount sdk.Int
mintDenom string
admin string
valid bool
expectedMessageEvents int
}{
{
desc: "success case - check not implemented before block height",
amount: denoms.MintAmountLimit, // 2 ** 192
mintDenom: suite.defaultDenom,
admin: suite.TestAccs[0].String(),
valid: true,
expectedMessageEvents: 1,
},
{
desc: "failure case - check implemented on specific block height",
amount: denoms.MintAmountLimit, // 2 ** 192
mintDenom: suite.defaultDenom,
admin: suite.TestAccs[0].String(),
valid: false,
expectedMessageEvents: 0,
},
{
desc: "failure case - check implemented after specific block height",
amount: denoms.MintAmountLimit, // 2 ** 192
mintDenom: suite.defaultDenom,
admin: suite.TestAccs[0].String(),
valid: false,
expectedMessageEvents: 0,
},
}
// Before the block has been reached. Should succeed with the call.
suite.Ctx = suite.App.BaseApp.NewContext(false, tmtypes.Header{Height: mainnetUseConditionalHeight - 1, ChainID: "wormchain", Time: time.Now().UTC()})

ctx := suite.Ctx.WithEventManager(sdk.NewEventManager())
suite.Require().Equal(0, len(ctx.EventManager().Events()))
// Test mint message
suite.msgServer.Mint(sdk.WrapSDKContext(ctx), types.NewMsgMint(test_cases[0].admin, sdk.NewCoin(test_cases[0].mintDenom, test_cases[0].amount))) //nolint:errcheck

// Ensure current number and type of event is emitted
suite.AssertEventEmitted(ctx, types.TypeMsgMint, test_cases[0].expectedMessageEvents)

// On the block has been reached
suite.Ctx = suite.App.BaseApp.NewContext(false, tmtypes.Header{Height: mainnetUseConditionalHeight, ChainID: "wormchain", Time: time.Now().UTC()})
ctx = suite.Ctx.WithEventManager(sdk.NewEventManager())
suite.Require().Equal(0, len(ctx.EventManager().Events()))
// Test mint message
suite.msgServer.Mint(sdk.WrapSDKContext(ctx), types.NewMsgMint(test_cases[1].admin, sdk.NewCoin(test_cases[1].mintDenom, test_cases[1].amount))) //nolint:errcheck

// Ensure current number and type of event is emitted
suite.AssertEventEmitted(ctx, types.TypeMsgMint, test_cases[1].expectedMessageEvents)

// After the block has been reached
suite.Ctx = suite.App.BaseApp.NewContext(false, tmtypes.Header{Height: mainnetUseConditionalHeight + 1, ChainID: "wormchain", Time: time.Now().UTC()})
ctx = suite.Ctx.WithEventManager(sdk.NewEventManager())
suite.Require().Equal(0, len(ctx.EventManager().Events()))
// Test mint message
suite.msgServer.Mint(sdk.WrapSDKContext(ctx), types.NewMsgMint(test_cases[2].admin, sdk.NewCoin(test_cases[2].mintDenom, test_cases[2].amount))) //nolint:errcheck

// Ensure current number and type of event is emitted
suite.AssertEventEmitted(ctx, types.TypeMsgMint, test_cases[2].expectedMessageEvents)

}

// TestBurnDenomMsg tests TypeMsgBurn message is emitted on a successful burn
func (suite *KeeperTestSuite) TestBurnDenomMsg() {
// Create a denom.
Expand Down
4 changes: 4 additions & 0 deletions wormchain/x/tokenfactory/types/denoms.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package types

import (
"math/big"
"strings"

sdk "github.com/cosmos/cosmos-sdk/types"
Expand All @@ -18,6 +19,9 @@ const (
MaxCreatorLength = 59 + MaxHrpLength
)

// 2 ** 192
var MintAmountLimit = sdk.NewIntFromBigInt(big.NewInt(0).Exp(big.NewInt(2), big.NewInt(192), big.NewInt(0)))

// GetTokenDenom constructs a denom string for tokens created by tokenfactory
// based on an input creator address and a subdenom
// The denom constructed is factory/{creator}/{subdenom}
Expand Down
1 change: 1 addition & 0 deletions wormchain/x/tokenfactory/types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ var (
ErrCreatorTooLong = sdkerrors.Register(ModuleName, 9, fmt.Sprintf("creator too long, max length is %d bytes", MaxCreatorLength))
ErrDenomDoesNotExist = sdkerrors.Register(ModuleName, 10, "denom does not exist")
ErrCapabilityNotEnabled = sdkerrors.Register(ModuleName, 11, "this capability is not enabled on chain")
ErrMintAmountTooLarge = sdkerrors.Register(ModuleName, 12, "mint amount exceeds capacity")
)
1 change: 1 addition & 0 deletions wormchain/x/tokenfactory/types/expected_keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ type BankKeeper interface {
SetDenomMetaData(ctx sdk.Context, denomMetaData banktypes.Metadata)

HasSupply(ctx sdk.Context, denom string) bool
GetSupply(ctx sdk.Context, denom string) sdk.Coin

SendCoinsFromModuleToAccount(ctx sdk.Context, senderModule string, recipientAddr sdk.AccAddress, amt sdk.Coins) error
SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error
Expand Down
4 changes: 4 additions & 0 deletions wormchain/x/tokenfactory/types/msgs.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ func (m MsgMint) ValidateBasic() error {
return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, m.Amount.String())
}

if m.Amount.Amount.GTE(MintAmountLimit) {
return ErrMintAmountTooLarge
}

return nil
}

Expand Down
9 changes: 9 additions & 0 deletions wormchain/x/tokenfactory/types/msgs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package types_test

import (
fmt "fmt"
"math/big"
"testing"

sdk "github.com/cosmos/cosmos-sdk/types"
Expand Down Expand Up @@ -189,6 +190,14 @@ func TestMsgMint(t *testing.T) {
}),
expectPass: false,
},
{
name: "too large amount",
msg: createMsg(func(msg types.MsgMint) types.MsgMint {
msg.Amount.Amount = sdk.NewIntFromBigInt(big.NewInt(0).Sub(big.NewInt(0).Exp(big.NewInt(2), big.NewInt(256), big.NewInt(0)), big.NewInt(1)))
return msg
}),
expectPass: false,
},
}

for _, test := range tests {
Expand Down

0 comments on commit 40c2dec

Please sign in to comment.