Skip to content

Commit

Permalink
Allow zero precision for ft tokens (#787)
Browse files Browse the repository at this point in the history
* Allow zero precision for ft tokens

* Fixed comment

* addressed PR comments

* pass linter

* Merge branch 'master' into milad/allow-zero-precision
  • Loading branch information
miladz68 committed Mar 4, 2024
1 parent ab054cc commit 3230848
Show file tree
Hide file tree
Showing 5 changed files with 163 additions and 40 deletions.
120 changes: 84 additions & 36 deletions integration-tests/modules/assetft_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,50 +48,98 @@ func TestAssetFTIssue(t *testing.T) {
ctx, chain := integrationtests.NewCoreumTestingContext(t)

requireT := require.New(t)
issuer := chain.GenAccount()

chain.FundAccountWithOptions(ctx, t, issuer, integration.BalancesOptions{
Messages: []sdk.Msg{
&assetfttypes.MsgIssue{},
testCases := []struct {
Name string
Precision uint32
}{
{
Name: "positive precision",
Precision: 6,
},
{
Name: "0 precision",
Precision: 0,
},
Amount: chain.QueryAssetFTParams(ctx, t).IssueFee.Amount,
})

issueMsg := &assetfttypes.MsgIssue{
Issuer: issuer.String(),
Symbol: "ABC",
Subunit: "uabc",
Precision: 6,
Description: "ABC Description",
InitialAmount: sdkmath.NewInt(1000),
Features: []assetfttypes.Feature{},
URI: "https://my-class-meta.invalid/1",
URIHash: "content-hash",
}

res, err := client.BroadcastTx(
ctx,
chain.ClientContext.WithFromAddress(issuer),
chain.TxFactory().WithGas(chain.GasLimitByMsgs(issueMsg)),
issueMsg,
)
for _, tc := range testCases {
tc := tc
t.Run(tc.Name, func(t *testing.T) {
t.Parallel()
issuer := chain.GenAccount()
chain.FundAccountWithOptions(ctx, t, issuer, integration.BalancesOptions{
Messages: []sdk.Msg{
&assetfttypes.MsgIssue{},
},
Amount: chain.QueryAssetFTParams(ctx, t).IssueFee.Amount,
})

requireT.NoError(err)
issueMsg := &assetfttypes.MsgIssue{
Issuer: issuer.String(),
Symbol: "ABC",
Subunit: "uabc",
Precision: tc.Precision,
Description: "ABC Description",
InitialAmount: sdkmath.NewInt(1000),
Features: []assetfttypes.Feature{},
URI: "https://my-class-meta.invalid/1",
URIHash: "content-hash",
}

// verify issue fee was burnt
burntStr, err := event.FindStringEventAttribute(res.Events, banktypes.EventTypeCoinBurn, sdk.AttributeKeyAmount)
requireT.NoError(err)
requireT.Equal(chain.QueryAssetFTParams(ctx, t).IssueFee.String(), burntStr)
denom := assetfttypes.BuildDenom(issueMsg.Subunit, issuer)
expectedMetadata := banktypes.Metadata{
Description: issueMsg.Description,
DenomUnits: []*banktypes.DenomUnit{
{Denom: denom, Exponent: 0},
{Denom: issueMsg.Symbol, Exponent: issueMsg.Precision},
},
Base: denom,
Display: issueMsg.Symbol,
Name: issueMsg.Symbol,
Symbol: issueMsg.Symbol,
URI: issueMsg.URI,
URIHash: issueMsg.URIHash,
}

// check that balance is 0 meaning issue fee was taken
if tc.Precision == 0 {
expectedMetadata.DenomUnits = []*banktypes.DenomUnit{
{Denom: denom, Exponent: 0},
}
expectedMetadata.Display = denom
}

bankClient := banktypes.NewQueryClient(chain.ClientContext)
resp, err := bankClient.Balance(ctx, &banktypes.QueryBalanceRequest{
Address: issuer.String(),
Denom: chain.ChainSettings.Denom,
})
requireT.NoError(err)
requireT.Equal(chain.NewCoin(sdkmath.ZeroInt()).String(), resp.Balance.String())
res, err := client.BroadcastTx(
ctx,
chain.ClientContext.WithFromAddress(issuer),
chain.TxFactory().WithGas(chain.GasLimitByMsgs(issueMsg)),
issueMsg,
)

requireT.NoError(err)

// verify issue fee was burnt
burntStr, err := event.FindStringEventAttribute(res.Events, banktypes.EventTypeCoinBurn, sdk.AttributeKeyAmount)
requireT.NoError(err)
requireT.Equal(chain.QueryAssetFTParams(ctx, t).IssueFee.String(), burntStr)

// check that balance is 0 meaning issue fee was taken

bankClient := banktypes.NewQueryClient(chain.ClientContext)
resp, err := bankClient.Balance(ctx, &banktypes.QueryBalanceRequest{
Address: issuer.String(),
Denom: chain.ChainSettings.Denom,
})
requireT.NoError(err)
requireT.Equal(chain.NewCoin(sdkmath.ZeroInt()).String(), resp.Balance.String())

// check metadata
metadata, err := bankClient.DenomMetadata(ctx, &banktypes.QueryDenomMetadataRequest{Denom: denom})
requireT.NoError(err)

requireT.EqualValues(expectedMetadata, metadata.Metadata)
})
}
}

// TestAssetFTIssueInvalidFeatures tests issue functionality of fungible tokens with invalid features.
Expand Down
15 changes: 14 additions & 1 deletion x/asset/ft/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,19 @@ func (k Keeper) SetDenomMetadata(
URIHash: uriHash,
}

// in case the precision is zero, we cannot 2 zero exponents in denom units, so
// we are force to have single entry in denom units and also Display must be the
// same as Base.
if precision == 0 {
denomMetadata.DenomUnits = []*banktypes.DenomUnit{
{
Denom: denom,
Exponent: uint32(0),
},
}
denomMetadata.Display = denom
}

if err := denomMetadata.Validate(); err != nil {
return sdkerrors.Wrapf(types.ErrInvalidInput, "failed to validate denom metadata: %s", err)
}
Expand Down Expand Up @@ -810,7 +823,7 @@ func (k Keeper) getTokenFullInfo(ctx sdk.Context, definition types.Definition) (

precision := -1
for _, unit := range metadata.DenomUnits {
if unit.Denom == metadata.Symbol {
if unit.Denom == metadata.Display {
precision = int(unit.Exponent)
break
}
Expand Down
62 changes: 62 additions & 0 deletions x/asset/ft/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,68 @@ func TestKeeper_Issue(t *testing.T) {
requireT.ErrorIs(err, types.ErrInvalidInput)
}

func TestKeeper_Issue_ZeroPrecision(t *testing.T) {
requireT := require.New(t)

testApp := simapp.New()
ctx := testApp.BaseApp.NewContext(false, tmproto.Header{})

ftKeeper := testApp.AssetFTKeeper
bankKeeper := testApp.BankKeeper
addr := sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address())

settings := types.IssueSettings{
Issuer: addr,
Symbol: "ABC",
Description: "ABC Desc",
Subunit: "abc",
Precision: 0,
InitialAmount: sdkmath.NewInt(777),
Features: []types.Feature{types.Feature_freezing},
URI: "https://my-class-meta.invalid/1",
URIHash: "content-hash",
}

denom, err := ftKeeper.Issue(ctx, settings)
requireT.NoError(err)

gotToken, err := ftKeeper.GetToken(ctx, denom)
requireT.NoError(err)
requireT.Equal(types.Token{
Denom: denom,
Issuer: settings.Issuer.String(),
Symbol: settings.Symbol,
Description: settings.Description,
Subunit: strings.ToLower(settings.Subunit),
Precision: settings.Precision,
Features: []types.Feature{types.Feature_freezing},
BurnRate: sdk.NewDec(0),
SendCommissionRate: sdk.NewDec(0),
Version: types.CurrentTokenVersion,
URI: settings.URI,
URIHash: settings.URIHash,
}, gotToken)

// check the metadata
storedMetadata, found := bankKeeper.GetDenomMetaData(ctx, denom)
requireT.True(found)
requireT.Equal(banktypes.Metadata{
Name: settings.Symbol,
Symbol: settings.Symbol,
Description: settings.Description,
DenomUnits: []*banktypes.DenomUnit{
{
Denom: denom,
Exponent: 0,
},
},
Base: denom,
Display: denom,
URI: settings.URI,
URIHash: settings.URIHash,
}, storedMetadata)
}

func TestKeeper_IssueEqualDisplayAndBaseDenom(t *testing.T) {
requireT := require.New(t)

Expand Down
4 changes: 2 additions & 2 deletions x/asset/ft/types/token.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,8 @@ func ValidateAssetCoins(coins sdk.Coins) error {

// ValidatePrecision checks the provided precision is valid.
func ValidatePrecision(precision uint32) error {
if precision == 0 || precision > MaxPrecision {
return sdkerrors.Wrapf(ErrInvalidInput, "precision must be between 1 and %d", MaxPrecision)
if precision > MaxPrecision {
return sdkerrors.Wrapf(ErrInvalidInput, "precision must be between 0 and %d", MaxPrecision)
}
return nil
}
Expand Down
2 changes: 1 addition & 1 deletion x/asset/ft/types/token_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func TestValidatePrecision(t *testing.T) {
{precision: 3},
{precision: 10},
{precision: types.MaxPrecision},
{precision: 0, expectError: true},
{precision: 0},
{precision: types.MaxPrecision + 1, expectError: true},
{precision: 100_000, expectError: true},
}
Expand Down

0 comments on commit 3230848

Please sign in to comment.