Skip to content

Commit

Permalink
Adds x/tokenfactory helper methods (#798)
Browse files Browse the repository at this point in the history
* Adds token-factory helper methods

* add `QueryBankMetadata` to CosmosChain

* AuthorityMetadata & BankMetadata types

* `TokenFactory` misc test

* remove old debug statement

(cherry picked from commit fcfcac6)

# Conflicts:
#	examples/cosmos/chain_miscellaneous_test.go
  • Loading branch information
Reecepbcups authored and mergify[bot] committed Oct 11, 2023
1 parent 54a27f8 commit d2b9f31
Show file tree
Hide file tree
Showing 5 changed files with 231 additions and 1 deletion.
14 changes: 14 additions & 0 deletions chain/cosmos/chain_node.go
Original file line number Diff line number Diff line change
Expand Up @@ -1155,6 +1155,20 @@ func (tn *ChainNode) QueryParam(ctx context.Context, subspace, key string) (*Par
return &param, nil
}

// QueryBankMetadata returns the bank metadata of a token denomination.
func (tn *ChainNode) QueryBankMetadata(ctx context.Context, denom string) (*BankMetaData, error) {
stdout, _, err := tn.ExecQuery(ctx, "bank", "denom-metadata", "--denom", denom)
if err != nil {
return nil, err
}
var meta BankMetaData
err = json.Unmarshal(stdout, &meta)
if err != nil {
return nil, err
}
return &meta, nil
}

// DumpContractState dumps the state of a contract at a block height.
func (tn *ChainNode) DumpContractState(ctx context.Context, contractAddress string, height int64) (*DumpContractStateResponse, error) {
stdout, _, err := tn.ExecQuery(ctx,
Expand Down
5 changes: 5 additions & 0 deletions chain/cosmos/cosmos_chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,11 @@ func (c *CosmosChain) QueryParam(ctx context.Context, subspace, key string) (*Pa
return c.getFullNode().QueryParam(ctx, subspace, key)
}

// QueryBankMetadata returns the metadata of a given token denomination.
func (c *CosmosChain) QueryBankMetadata(ctx context.Context, denom string) (*BankMetaData, error) {
return c.getFullNode().QueryBankMetadata(ctx, denom)
}

func (c *CosmosChain) txProposal(txHash string) (tx TxProposal, _ error) {
txResp, err := c.getTransaction(txHash)
if err != nil {
Expand Down
104 changes: 104 additions & 0 deletions chain/cosmos/tokenfactory.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package cosmos

import (
"context"
"encoding/json"
"fmt"
"strconv"

"github.com/strangelove-ventures/interchaintest/v8/ibc"

Check failure on line 9 in chain/cosmos/tokenfactory.go

View workflow job for this annotation

GitHub Actions / unit-tests

no required module provides package github.com/strangelove-ventures/interchaintest/v8/ibc; to add it:

Check failure on line 9 in chain/cosmos/tokenfactory.go

View workflow job for this annotation

GitHub Actions / test-conformance

no required module provides package github.com/strangelove-ventures/interchaintest/v8/ibc; to add it:

Check failure on line 9 in chain/cosmos/tokenfactory.go

View workflow job for this annotation

GitHub Actions / test-ibc-examples

no required module provides package github.com/strangelove-ventures/interchaintest/v8/ibc; to add it:
)

// TokenFactoryCreateDenom creates a new tokenfactory token in the format 'factory/accountaddress/name'.
// This token will be viewable by standard bank balance queries and send functionality.
// Depending on the chain parameters, this may require a lot of gas (Juno, Osmosis) if the DenomCreationGasConsume param is enabled.
// If not, the default implementation cost 10,000,000 micro tokens (utoken) of the chain's native token.
func TokenFactoryCreateDenom(c *CosmosChain, ctx context.Context, user ibc.Wallet, denomName string, gas uint64) (string, string, error) {
cmd := []string{"tokenfactory", "create-denom", denomName}

if gas != 0 {
cmd = append(cmd, "--gas", strconv.FormatUint(gas, 10))
}

txHash, err := c.getFullNode().ExecTx(ctx, user.KeyName(), cmd...)
if err != nil {
return "", "", err
}

return "factory/" + user.FormattedAddress() + "/" + denomName, txHash, nil
}

// TokenFactoryBurnDenom burns a tokenfactory denomination from the holders account.
func TokenFactoryBurnDenom(c *CosmosChain, ctx context.Context, keyName, fullDenom string, amount uint64) (string, error) {
coin := strconv.FormatUint(amount, 10) + fullDenom
return c.getFullNode().ExecTx(ctx, keyName,
"tokenfactory", "burn", coin,
)
}

// TokenFactoryBurnDenomFrom burns a tokenfactory denomination from any other users account.
// Only the admin of the token can perform this action.
func TokenFactoryBurnDenomFrom(c *CosmosChain, ctx context.Context, keyName, fullDenom string, amount uint64, fromAddr string) (string, error) {
return c.getFullNode().ExecTx(ctx, keyName,
"tokenfactory", "burn-from", fromAddr, convertToCoin(amount, fullDenom),
)
}

// TokenFactoryChangeAdmin moves the admin of a tokenfactory token to a new address.
func TokenFactoryChangeAdmin(c *CosmosChain, ctx context.Context, keyName, fullDenom, newAdmin string) (string, error) {
return c.getFullNode().ExecTx(ctx, keyName,
"tokenfactory", "change-admin", fullDenom, newAdmin,
)
}

// TokenFactoryForceTransferDenom force moves a token from 1 account to another.
// Only the admin of the token can perform this action.
func TokenFactoryForceTransferDenom(c *CosmosChain, ctx context.Context, keyName, fullDenom string, amount uint64, fromAddr, toAddr string) (string, error) {
return c.getFullNode().ExecTx(ctx, keyName,
"tokenfactory", "force-transfer", convertToCoin(amount, fullDenom), fromAddr, toAddr,
)
}

// TokenFactoryMintDenom mints a tokenfactory denomination to the admins account.
// Only the admin of the token can perform this action.
func TokenFactoryMintDenom(c *CosmosChain, ctx context.Context, keyName, fullDenom string, amount uint64) (string, error) {
return c.getFullNode().ExecTx(ctx, keyName,
"tokenfactory", "mint", convertToCoin(amount, fullDenom),
)
}

// TokenFactoryMintDenomTo mints a token to any external account.
// Only the admin of the token can perform this action.
func TokenFactoryMintDenomTo(c *CosmosChain, ctx context.Context, keyName, fullDenom string, amount uint64, toAddr string) (string, error) {
return c.getFullNode().ExecTx(ctx, keyName,
"tokenfactory", "mint-to", toAddr, convertToCoin(amount, fullDenom),
)
}

// TokenFactoryMetadata sets the x/bank metadata for a tokenfactory token. This gives the token more detailed information to be queried
// by frontend UIs and other applications.
// Only the admin of the token can perform this action.
func TokenFactoryMetadata(c *CosmosChain, ctx context.Context, keyName, fullDenom, ticker, description string, exponent uint64) (string, error) {
return c.getFullNode().ExecTx(ctx, keyName,
"tokenfactory", "modify-metadata", fullDenom, ticker, description, strconv.FormatUint(exponent, 10),
)
}

// TokenFactoryGetAdmin returns the admin of a tokenfactory token.
func TokenFactoryGetAdmin(c *CosmosChain, ctx context.Context, fullDenom string) (*QueryDenomAuthorityMetadataResponse, error) {
res := &QueryDenomAuthorityMetadataResponse{}
stdout, stderr, err := c.getFullNode().ExecQuery(ctx, "tokenfactory", "denom-authority-metadata", fullDenom)
if err != nil {
return nil, fmt.Errorf("failed to query tokenfactory denom-authority-metadata: %w\nstdout: %s\nstderr: %s", err, stdout, stderr)
}

if err := json.Unmarshal(stdout, res); err != nil {
return nil, err
}

return res, nil
}

func convertToCoin(amount uint64, denom string) string {
return strconv.FormatUint(amount, 10) + denom
}
26 changes: 26 additions & 0 deletions chain/cosmos/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,29 @@ type BinaryBuildInformation struct {
BuildDeps []BuildDependency `json:"build_deps"`
CosmosSdkVersion string `json:"cosmos_sdk_version"`
}

type BankMetaData struct {
Metadata struct {
Description string `json:"description"`
DenomUnits []struct {
Denom string `json:"denom"`
Exponent int `json:"exponent"`
Aliases []string `json:"aliases"`
} `json:"denom_units"`
Base string `json:"base"`
Display string `json:"display"`
Name string `json:"name"`
Symbol string `json:"symbol"`
URI string `json:"uri"`
URIHash string `json:"uri_hash"`
} `json:"metadata"`
}

type QueryDenomAuthorityMetadataResponse struct {
AuthorityMetadata DenomAuthorityMetadata `protobuf:"bytes,1,opt,name=authority_metadata,json=authorityMetadata,proto3" json:"authority_metadata" yaml:"authority_metadata"`
}

type DenomAuthorityMetadata struct {
// Can be empty for no admin, or a valid address
Admin string `protobuf:"bytes,1,opt,name=admin,proto3" json:"admin,omitempty" yaml:"admin"`
}
83 changes: 82 additions & 1 deletion examples/cosmos/chain_miscellaneous_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,16 @@ import (
"context"
"testing"

<<<<<<< HEAD

Check failure on line 7 in examples/cosmos/chain_miscellaneous_test.go

View workflow job for this annotation

GitHub Actions / test-cosmos-examples

expected 'STRING', found '<<'

Check failure on line 7 in examples/cosmos/chain_miscellaneous_test.go

View workflow job for this annotation

GitHub Actions / unit-tests

expected 'STRING', found '<<'
"github.com/strangelove-ventures/interchaintest/v7"
"github.com/strangelove-ventures/interchaintest/v7/chain/cosmos"
"github.com/strangelove-ventures/interchaintest/v7/ibc"
=======
"cosmossdk.io/math"
"github.com/strangelove-ventures/interchaintest/v8"
"github.com/strangelove-ventures/interchaintest/v8/chain/cosmos"
"github.com/strangelove-ventures/interchaintest/v8/ibc"
>>>>>>> fcfcac6 (Adds x/tokenfactory helper methods (#798))
"github.com/stretchr/testify/require"
"go.uber.org/zap/zaptest"
)
Expand All @@ -15,6 +22,10 @@ func TestICTestMiscellaneous(t *testing.T) {
CosmosChainTestMiscellaneous(t, "juno", "v16.0.0")
}

const (
initialBalance = 100_000_000
)

func CosmosChainTestMiscellaneous(t *testing.T, name, version string) {
if testing.Short() {
t.Skip("skipping in short mode")
Expand Down Expand Up @@ -57,8 +68,10 @@ func CosmosChainTestMiscellaneous(t *testing.T, name, version string) {
_ = ic.Close()
})

BuildDependencies(ctx, t, chain)
users := interchaintest.GetAndFundTestUsers(t, ctx, "default", int64(initialBalance), chain, chain)

TokenFactory(ctx, t, chain, users)
BuildDependencies(ctx, t, chain)
}

func BuildDependencies(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain) {
Expand Down Expand Up @@ -98,3 +111,71 @@ func BuildDependencies(ctx context.Context, t *testing.T, chain *cosmos.CosmosCh
}
}
}

func TokenFactory(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain, users []ibc.Wallet) {
user := users[0]
user2 := users[1]

subDenom := "ictest"
tfDenom, _, err := cosmos.TokenFactoryCreateDenom(chain, ctx, user, subDenom, 2500000)
require.NoError(t, err)
require.Equal(t, tfDenom, "factory/"+user.FormattedAddress()+"/"+subDenom)

// modify metadata
stdout, err := cosmos.TokenFactoryMetadata(chain, ctx, user.KeyName(), tfDenom, "SYMBOL", "description here", 6)
t.Log(stdout, err)
require.NoError(t, err)

// verify metadata
md, err := chain.QueryBankMetadata(ctx, tfDenom)
require.NoError(t, err)
require.Equal(t, md.Metadata.Description, "description here")
require.Equal(t, md.Metadata.Symbol, "SYMBOL")
require.Equal(t, md.Metadata.DenomUnits[1].Exponent, 6)

// mint tokens
_, err = cosmos.TokenFactoryMintDenom(chain, ctx, user.KeyName(), tfDenom, 1)
require.NoError(t, err)
validateBalance(ctx, t, chain, user, tfDenom, 1)

// mint-to
_, err = cosmos.TokenFactoryMintDenomTo(chain, ctx, user.KeyName(), tfDenom, 3, user2.FormattedAddress())
require.NoError(t, err)
validateBalance(ctx, t, chain, user2, tfDenom, 3)

// force transfer 1 from user2 (3) to user1 (1)
_, err = cosmos.TokenFactoryForceTransferDenom(chain, ctx, user.KeyName(), tfDenom, 1, user2.FormattedAddress(), user.FormattedAddress())
require.NoError(t, err)
validateBalance(ctx, t, chain, user, tfDenom, 2)
validateBalance(ctx, t, chain, user2, tfDenom, 2)

// burn token from account
_, err = cosmos.TokenFactoryBurnDenomFrom(chain, ctx, user.KeyName(), tfDenom, 1, user.FormattedAddress())
require.NoError(t, err)
validateBalance(ctx, t, chain, user, tfDenom, 1)

prevAdmin, err := cosmos.TokenFactoryGetAdmin(chain, ctx, tfDenom)
require.NoError(t, err)
require.Equal(t, prevAdmin.AuthorityMetadata.Admin, user.FormattedAddress())

// change admin, then we will burn the token-from
_, err = cosmos.TokenFactoryChangeAdmin(chain, ctx, user.KeyName(), tfDenom, user2.FormattedAddress())
require.NoError(t, err)

// validate new admin is set
tfAdmin, err := cosmos.TokenFactoryGetAdmin(chain, ctx, tfDenom)
require.NoError(t, err)
require.Equal(t, tfAdmin.AuthorityMetadata.Admin, user2.FormattedAddress())

// burn
_, err = cosmos.TokenFactoryBurnDenomFrom(chain, ctx, user2.KeyName(), tfDenom, 1, user.FormattedAddress())
require.NoError(t, err)
validateBalance(ctx, t, chain, user, tfDenom, 0)

}

func validateBalance(ctx context.Context, t *testing.T, chain *cosmos.CosmosChain, user ibc.Wallet, tfDenom string, expected int64) {
balance, err := chain.GetBalance(ctx, user.FormattedAddress(), tfDenom)
require.NoError(t, err)
require.Equal(t, balance, math.NewInt(expected))
}

0 comments on commit d2b9f31

Please sign in to comment.