diff --git a/docs/docs/02-guide/06-tokenfactory/01-tokenfactory.md b/docs/docs/02-guide/06-tokenfactory/01-tokenfactory.md index 219b0e9d14..dba982b3e6 100644 --- a/docs/docs/02-guide/06-tokenfactory/01-tokenfactory.md +++ b/docs/docs/02-guide/06-tokenfactory/01-tokenfactory.md @@ -220,30 +220,126 @@ In `x/tokenfactory/types/messages_denom.go`: Implement basic input validation in `x/tokenfactory/types/messages_denom.go`: - Ensure the ticker length is between 3 and 10 characters. +```go +func (msg *MsgCreateDenom) ValidateBasic() error { + _, err := sdk.AccAddressFromBech32(msg.Owner) + if err != nil { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid owner address (%s)", err) + } + + tickerLength := len(msg.Ticker) + if tickerLength < 3 { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "Ticker length must be at least 3 chars long") + } + if tickerLength > 10 { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "Ticker length must be 10 chars long maximum") + } + if msg.MaxSupply == 0 { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "Max Supply must be greater than 0") + } + + return nil +} +``` + - Set `maxSupply` to be greater than 0. +```go +func (msg *MsgUpdateDenom) ValidateBasic() error { + _, err := sdk.AccAddressFromBech32(msg.Owner) + if err != nil { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid owner address (%s)", err) + } + if msg.MaxSupply == 0 { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidRequest, "Max Supply must be greater than 0") + } + return nil +} +``` + ### Keeper Logic The keeper is where you define the business logic for manipulating the database and writing to the key-value store. **In `x/tokenfactory/keeper/msg_server_denom.go`:** -- Update `CreateDenom()` to include logic for creating unique denoms. +- Update `CreateDenom()` to include logic for creating unique denoms. Modify the error message to point to existing denoms. Set `Supply` to `0`. - Modify `UpdateDenom()` to verify ownership and manage max supply changes. +```go +func (k msgServer) UpdateDenom(goCtx context.Context, msg *types.MsgUpdateDenom) (*types.MsgUpdateDenomResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + // Check if the value exists + valFound, isFound := k.GetDenom( + ctx, + msg.Denom, + ) + if !isFound { + return nil, sdkerrors.Wrap(sdkerrors.ErrKeyNotFound, "index not set") + } + + // Checks if the the msg owner is the same as the current owner + if msg.Owner != valFound.Owner { + return nil, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "incorrect owner") + } + + if !valFound.CanChangeMaxSupply && valFound.MaxSupply != msg.MaxSupply { + return nil, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "cannot change maxsupply") + } + if !valFound.CanChangeMaxSupply && msg.CanChangeMaxSupply { + return nil, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "Cannot revert change maxsupply flag") + } + var denom = types.Denom{ + Owner: msg.Owner, + Denom: msg.Denom, + Description: msg.Description, + Ticker: valFound.Ticker, + Precision: valFound.Precision, + Url: msg.Url, + MaxSupply: msg.MaxSupply, + Supply: valFound.Supply, + CanChangeMaxSupply: msg.CanChangeMaxSupply, + } + + k.SetDenom(ctx, denom) + + return &types.MsgUpdateDenomResponse{}, nil +} +``` + ### Expected Keepers `x/tokenfactory/types/expected_keepers.go` is where you define interactions with other modules. Since your module relies on the `auth` and `bank` modules, specify which of their functions your module can access. Replace the existing code in `expected_keepers.go` with the updated definitions that interface with `auth` and `bank` modules. +```go +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" +) + +type AccountKeeper interface { + GetAccount(ctx sdk.Context, addr sdk.AccAddress) authtypes.AccountI + GetModuleAddress(name string) sdk.AccAddress + GetModuleAccount(ctx sdk.Context, moduleName string) authtypes.ModuleAccountI +} + +type BankKeeper interface { + SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error + MintCoins(ctx sdk.Context, moduleName string, amt sdk.Coins) error +} +``` ### Commiting Your Changes Regular commits are vital for tracking progress and ensuring a stable rollback point if needed. After implementing these changes, use the following commands to commit: ```bash -it add . +git add . git commit -m "Add token factory create and update logic" ``` @@ -297,8 +393,126 @@ Found in `x/tokenfactory/keeper/msg_server_update_owner.go`, this function allow ### Keeper Logic - For `MintAndSendTokens`, add logic to mint new tokens as per the request parameters. This includes checking for maximum supply limits and transferring the minted tokens to the specified recipient. + +```go +package keeper + +import ( + "context" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "tokenfactory/x/tokenfactory/types" +) + +func (k msgServer) MintAndSendTokens(goCtx context.Context, msg *types.MsgMintAndSendTokens) (*types.MsgMintAndSendTokensResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + // Check if the value exists + valFound, isFound := k.GetDenom( + ctx, + msg.Denom, + ) + if !isFound { + return nil, sdkerrors.Wrap(sdkerrors.ErrKeyNotFound, "denom does not exist") + } + + // Checks if the the msg owner is the same as the current owner + if msg.Owner != valFound.Owner { + return nil, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "incorrect owner") + } + + if valFound.Supply+msg.Amount > valFound.MaxSupply { + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "Cannot mint more than Max Supply") + } + moduleAcct := k.accountKeeper.GetModuleAddress(types.ModuleName) + + recipientAddress, err := sdk.AccAddressFromBech32(msg.Recipient) + if err != nil { + return nil, err + } + + var mintCoins sdk.Coins + + mintCoins = mintCoins.Add(sdk.NewCoin(msg.Denom, sdk.NewInt(int64(msg.Amount)))) + if err := k.bankKeeper.MintCoins(ctx, types.ModuleName, mintCoins); err != nil { + return nil, err + } + if err := k.bankKeeper.SendCoins(ctx, moduleAcct, recipientAddress, mintCoins); err != nil { + return nil, err + } + + var denom = types.Denom{ + Owner: valFound.Owner, + Denom: valFound.Denom, + Description: valFound.Description, + MaxSupply: valFound.MaxSupply, + Supply: valFound.Supply + msg.Amount, + Precision: valFound.Precision, + Ticker: valFound.Ticker, + Url: valFound.Url, + CanChangeMaxSupply: valFound.CanChangeMaxSupply, + } + + k.SetDenom( + ctx, + denom, + ) + return &types.MsgMintAndSendTokensResponse{}, nil +} +``` + - For `UpdateOwner`, implement the logic to update the owner of a denom, ensuring that only the current owner can initiate this change. +```go +package keeper + +import ( + "context" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "tokenfactory/x/tokenfactory/types" +) + +func (k msgServer) UpdateOwner(goCtx context.Context, msg *types.MsgUpdateOwner) (*types.MsgUpdateOwnerResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + // Check if the value exists + valFound, isFound := k.GetDenom( + ctx, + msg.Denom, + ) + if !isFound { + return nil, sdkerrors.Wrap(sdkerrors.ErrKeyNotFound, "denom does not exist") + } + + // Checks if the the msg owner is the same as the current owner + if msg.Owner != valFound.Owner { + return nil, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "incorrect owner") + } + + var denom = types.Denom{ + Owner: msg.NewOwner, + Denom: msg.Denom, + Description: valFound.Description, + MaxSupply: valFound.MaxSupply, + Supply: valFound.Supply, + Precision: valFound.Precision, + Ticker: valFound.Ticker, + Url: valFound.Url, + CanChangeMaxSupply: valFound.CanChangeMaxSupply, + } + + k.SetDenom( + ctx, + denom, + ) + + return &types.MsgUpdateOwnerResponse{}, nil +} +``` + ### Committing Your Changes After implementing these new functionalities, it's crucial to save your progress. Use the following commands: