Skip to content

Commit

Permalink
feat: implement token module encoder (Finschia#5)
Browse files Browse the repository at this point in the history
* feat: implement token module encoder

* fix: the wasm module does not depend on the encoder
  • Loading branch information
shiki-tak authored Oct 19, 2020
1 parent 4aa189e commit 0f4ad5b
Show file tree
Hide file tree
Showing 19 changed files with 999 additions and 53 deletions.
24 changes: 21 additions & 3 deletions x/token/alias.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,32 @@ package token

import (
"github.com/line/link-modules/x/token/internal/keeper"
"github.com/line/link-modules/x/token/internal/querier"
"github.com/line/link-modules/x/token/internal/types"
)

const (
ModuleName = types.ModuleName
StoreKey = types.StoreKey
RouterKey = types.RouterKey
ModuleName = types.ModuleName
StoreKey = types.StoreKey
RouterKey = types.RouterKey
EncodeRouterKey = types.EncodeRouterKey
)

type (
MsgIssue = types.MsgIssue
MsgTransfer = types.MsgTransfer
MsgMint = types.MsgMint
MsgBurn = types.MsgBurn
MsgModify = types.MsgModify

Account = types.Account
Token = types.Token
Permissions = types.Permissions
Keeper = keeper.Keeper
Permission = types.Permission

EncodeHandler = types.EncodeHandler
EncodeQuerier = types.EncodeQuerier
)

var (
Expand All @@ -35,6 +46,13 @@ var (
RegisterCodec = types.RegisterCodec
NewToken = types.NewToken
NewKeeper = keeper.NewKeeper
NewQuerier = querier.NewQuerier

NewMsgEncodeHandler = keeper.NewMsgEncodeHandler
NewQueryEncoder = querier.NewQueryEncoder

NewChanges = types.NewChanges
NewChange = types.NewChange

ErrTokenNotExist = types.ErrTokenNotExist
ErrInsufficientBalance = types.ErrInsufficientBalance
Expand Down
100 changes: 100 additions & 0 deletions x/token/internal/keeper/msg_encoder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package keeper

import (
"encoding/json"

sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/line/link-modules/x/token/internal/types"
)

func NewMsgEncodeHandler(tokenKeeper Keeper) types.EncodeHandler {
return func(jsonMsg json.RawMessage) ([]sdk.Msg, error) {
var wasmCustomMsg types.WasmCustomMsg
err := json.Unmarshal(jsonMsg, &wasmCustomMsg)
if err != nil {
return nil, err
}
switch types.MsgRoute(wasmCustomMsg.Route) {
case types.RIssue:
return handleMsgIssue(wasmCustomMsg.Data)
case types.RTransfer:
return handleMsgTransfer(wasmCustomMsg.Data)
case types.RMint:
return handleMsgMint(wasmCustomMsg.Data)
case types.RBurn:
return handleMsgBurn(wasmCustomMsg.Data)
case types.RGrantPerm:
return handleMsgGrantPerm(wasmCustomMsg.Data)
case types.RRevokePerm:
return handleMsgRevokePerm(wasmCustomMsg.Data)
case types.RModify:
return handleMsgModify(wasmCustomMsg.Data)
default:
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized Msg route: %T", wasmCustomMsg.Route)
}
}
}

func handleMsgIssue(msgData json.RawMessage) ([]sdk.Msg, error) {
var wrapper types.IssueMsgWrapper
err := json.Unmarshal(msgData, &wrapper)
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error())
}
return []sdk.Msg{wrapper.MsgIssue}, nil
}

func handleMsgTransfer(msgData json.RawMessage) ([]sdk.Msg, error) {
var wrapper types.TransferMsgWrapper
err := json.Unmarshal(msgData, &wrapper)
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error())
}
return []sdk.Msg{wrapper.MsgTransfer}, nil
}

func handleMsgMint(msgData json.RawMessage) ([]sdk.Msg, error) {
var wrapper types.MintMsgWrapper
err := json.Unmarshal(msgData, &wrapper)
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error())
}
return []sdk.Msg{wrapper.MsgMint}, nil
}

func handleMsgBurn(msgData json.RawMessage) ([]sdk.Msg, error) {
var wrapper types.BurnMsgWrapper
err := json.Unmarshal(msgData, &wrapper)
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error())
}
return []sdk.Msg{wrapper.MsgBurn}, nil
}

func handleMsgGrantPerm(msgData json.RawMessage) ([]sdk.Msg, error) {
var wrapper types.GrantPermMsgWrapper
err := json.Unmarshal(msgData, &wrapper)
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error())
}
return []sdk.Msg{wrapper.MsgGrantPermission}, nil
}

func handleMsgRevokePerm(msgData json.RawMessage) ([]sdk.Msg, error) {
var wrapper types.RevokePermMsgWrapper
err := json.Unmarshal(msgData, &wrapper)
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error())
}
return []sdk.Msg{wrapper.MsgRevokePermission}, nil
}

func handleMsgModify(msgData json.RawMessage) ([]sdk.Msg, error) {
var wrapper types.ModifyMsgWrapper
err := json.Unmarshal(msgData, &wrapper)
if err != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error())
}
return []sdk.Msg{wrapper.MsgModify}, nil
}
123 changes: 123 additions & 0 deletions x/token/internal/keeper/msg_encoder_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package keeper

import (
"encoding/json"
"fmt"
"testing"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/line/link-modules/x/token/internal/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func Test_Encode(t *testing.T) {
encodeHandler := NewMsgEncodeHandler(keeper)
jsonMsg := json.RawMessage(`{"foo": 123}`)

testContractID := "test_contract_id"
issue := fmt.Sprintf(`{"route":"issue", "data":{"issue":{"owner":"%s","to":"%s","name":"TestToken1","symbol":"TT1","img_uri":"","meta":"","amount":"1000","mintable":true,"decimals":"18"}}}`, addr1.String(), addr2.String())
issueMsg := json.RawMessage(issue)
transfer := fmt.Sprintf(`{"route":"transfer", "data":{"transfer":{"from":"%s", "contract_id":"%s", "to":"%s", "amount":"100"}}}`, addr1.String(), testContractID, addr2.String())
transferMsg := json.RawMessage(transfer)
mint := fmt.Sprintf(`{"route":"mint", "data":{"mint":{"from":"%s", "contract_id":"%s", "to":"%s", "amount":"100"}}}`, addr1.String(), testContractID, addr2.String())
mintMsg := json.RawMessage(mint)
burn := fmt.Sprintf(`{"route":"burn", "data":{"burn":{"from":"%s", "contract_id":"%s", "amount":"5"}}}`, addr1.String(), testContractID)
burnMsg := json.RawMessage(burn)
grantPermission := fmt.Sprintf(`{"route":"grant_perm", "data":{"grant_perm":{"from":"%s", "contract_id":"%s", "to":"%s", "permission":"mint"}}}`, addr1.String(), testContractID, addr2.String())
grantPermissionMsg := json.RawMessage(grantPermission)
revokePermission := fmt.Sprintf(`{"route":"revoke_perm", "data":{"revoke_perm":{"from":"%s", "contract_id":"%s", "permission":"mint"}}}`, addr1.String(), testContractID)
revokePermissionMsg := json.RawMessage(revokePermission)
modify := fmt.Sprintf(`{"route":"modify","data":{"modify":{"owner":"%s","contract_id":"%s","changes":[{"field":"meta","value":"update_meta"}]}}}`, addr1.String(), testContractID)
modifyMsg := json.RawMessage(modify)

changes := types.NewChanges(types.NewChange("meta", "update_meta"))

cases := map[string]struct {
input json.RawMessage
// set if valid
output []sdk.Msg
// set if invalid
isError bool
}{
"issue token": {
input: issueMsg,
output: []sdk.Msg{
types.MsgIssue{
Owner: addr1,
To: addr2,
Name: "TestToken1",
Symbol: "TT1",
ImageURI: "",
Meta: "",
Amount: sdk.NewInt(1000),
Mintable: true,
Decimals: sdk.NewInt(18),
},
},
},
"transfer token": {
input: transferMsg,
output: []sdk.Msg{
types.MsgTransfer{
From: addr1,
ContractID: testContractID,
To: addr2,
Amount: sdk.NewInt(100),
},
},
},
"mint token": {
input: mintMsg,
output: []sdk.Msg{
types.MsgMint{
From: addr1,
ContractID: testContractID,
To: addr2,
Amount: sdk.NewInt(100),
},
},
},
"burn token": {
input: burnMsg,
output: []sdk.Msg{
types.NewMsgBurn(addr1, testContractID, sdk.NewInt(5)),
},
},
"grant permission": {
input: grantPermissionMsg,
output: []sdk.Msg{
types.NewMsgGrantPermission(addr1, testContractID, addr2, types.Permission("mint")),
},
},
"revoke permission": {
input: revokePermissionMsg,
output: []sdk.Msg{
types.NewMsgRevokePermission(addr1, testContractID, types.Permission("mint")),
},
},
"modify token": {
input: modifyMsg,
output: []sdk.Msg{
types.NewMsgModify(addr1, testContractID, changes),
},
},
"unknown custom msg": {
input: jsonMsg,
isError: true,
},
}

for name, tc := range cases {
tc := tc
t.Run(name, func(t *testing.T) {
res, err := encodeHandler(tc.input)
if tc.isError {
require.Error(t, err)
} else {
require.NoError(t, err)
assert.Equal(t, tc.output, res)
}
})
}
}
108 changes: 108 additions & 0 deletions x/token/internal/querier/querier_encoder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package querier

import (
"encoding/json"

"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/line/link-modules/x/token/internal/types"
abci "github.com/tendermint/tendermint/abci/types"
)

func NewQueryEncoder(tokenQuerier sdk.Querier) types.EncodeQuerier {
return func(ctx sdk.Context, jsonQuerier json.RawMessage) ([]byte, error) {
var customQuerier types.WasmCustomQuerier
err := json.Unmarshal(jsonQuerier, &customQuerier)
if err != nil {
return nil, err
}
switch customQuerier.Route {
case types.QueryTokens:
return handleQueryToken(ctx, tokenQuerier, []string{customQuerier.Route}, customQuerier.Data)
case types.QueryBalance:
return handleQueryBalance(ctx, tokenQuerier, []string{customQuerier.Route}, customQuerier.Data)
case types.QuerySupply:
return handleQueryTotal(ctx, tokenQuerier, customQuerier.Data)
case types.QueryPerms:
return handleQueryPerms(ctx, tokenQuerier, []string{customQuerier.Route}, customQuerier.Data)
default:
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized Msg route: %T", customQuerier.Route)
}
}
}

func handleQueryToken(ctx sdk.Context, tokenQuerier sdk.Querier, path []string, msgData json.RawMessage) ([]byte, error) {
var wrapper types.QueryTokenWrapper
err := json.Unmarshal(msgData, &wrapper)
if err != nil {
return nil, err
}
req := makeRequestQuery(nil)

contractID := wrapper.QueryTokenParam.ContractID
if contractID != "" {
path = append(path, contractID)
}
return tokenQuerier(ctx, path, req)
}

func handleQueryBalance(ctx sdk.Context, tokenQuerier sdk.Querier, path []string, msgData json.RawMessage) ([]byte, error) {
var wrapper types.QueryBalanceWrapper
err := json.Unmarshal(msgData, &wrapper)
if err != nil {
return nil, err
}

req := makeRequestQuery(types.QueryContractIDAccAddressParams{
Addr: wrapper.QueryBalanceParam.Address,
})

contractID := wrapper.QueryBalanceParam.ContractID
if contractID != "" {
path = append(path, contractID)
}
return tokenQuerier(ctx, path, req)
}

func handleQueryTotal(ctx sdk.Context, tokenQuerier sdk.Querier, msgData json.RawMessage) ([]byte, error) {
var wrapper types.QueryTotalWrapper
err := json.Unmarshal(msgData, &wrapper)
if err != nil {
return nil, err
}
req := makeRequestQuery(nil)

path := []string{wrapper.QueryTotalParam.Target}
contractID := wrapper.QueryTotalParam.ContractID
if contractID != "" {
path = append(path, contractID)
}
return tokenQuerier(ctx, path, req)
}

func handleQueryPerms(ctx sdk.Context, tokenQuerier sdk.Querier, path []string, msgData json.RawMessage) ([]byte, error) {
var wrapper types.QueryPermWrapper
err := json.Unmarshal(msgData, &wrapper)
if err != nil {
return nil, err
}

req := makeRequestQuery(types.QueryContractIDAccAddressParams{
Addr: wrapper.QueryPermParam.Address,
})

contractID := wrapper.QueryPermParam.ContractID
if contractID != "" {
path = append(path, contractID)
}
return tokenQuerier(ctx, path, req)
}

func makeRequestQuery(params interface{}) abci.RequestQuery {
req := abci.RequestQuery{
Path: "",
Data: []byte(string(codec.MustMarshalJSONIndent(types.ModuleCdc, params))),
}
return req
}
Loading

0 comments on commit 0f4ad5b

Please sign in to comment.