From 0f4ad5b90256b8b1cca6c81286124a10bde26a5c Mon Sep 17 00:00:00 2001 From: shiki Date: Mon, 19 Oct 2020 16:43:24 +0900 Subject: [PATCH] feat: implement token module encoder (#5) * feat: implement token module encoder * fix: the wasm module does not depend on the encoder --- x/token/alias.go | 24 ++- x/token/internal/keeper/msg_encoder.go | 100 ++++++++++++ x/token/internal/keeper/msg_encoder_test.go | 123 +++++++++++++++ x/token/internal/querier/querier_encoder.go | 108 +++++++++++++ .../internal/querier/querier_encoder_test.go | 111 ++++++++++++++ x/token/internal/types/encoder.go | 100 ++++++++++++ x/wasm/alias.go | 7 +- x/wasm/internal/keeper/genesis_test.go | 2 +- x/wasm/internal/keeper/handler_plugin.go | 37 +++-- x/wasm/internal/keeper/handler_plugin_test.go | 142 +++++++++++++++++- x/wasm/internal/keeper/keeper.go | 9 +- x/wasm/internal/keeper/query_plugins.go | 25 ++- x/wasm/internal/keeper/reflect_test.go | 2 +- x/wasm/internal/keeper/test_common.go | 58 ++++--- x/wasm/internal/types/encoder.go | 22 +++ x/wasm/internal/types/querier_router.go | 63 ++++++++ x/wasm/internal/types/querier_router_test.go | 28 ++++ x/wasm/internal/types/router.go | 65 ++++++++ x/wasm/internal/types/router_test.go | 26 ++++ 19 files changed, 999 insertions(+), 53 deletions(-) create mode 100644 x/token/internal/keeper/msg_encoder.go create mode 100644 x/token/internal/keeper/msg_encoder_test.go create mode 100644 x/token/internal/querier/querier_encoder.go create mode 100644 x/token/internal/querier/querier_encoder_test.go create mode 100644 x/token/internal/types/encoder.go create mode 100644 x/wasm/internal/types/encoder.go create mode 100644 x/wasm/internal/types/querier_router.go create mode 100644 x/wasm/internal/types/querier_router_test.go create mode 100644 x/wasm/internal/types/router.go create mode 100644 x/wasm/internal/types/router_test.go diff --git a/x/token/alias.go b/x/token/alias.go index bf5f2514a8..61079a31d6 100644 --- a/x/token/alias.go +++ b/x/token/alias.go @@ -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 ( @@ -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 diff --git a/x/token/internal/keeper/msg_encoder.go b/x/token/internal/keeper/msg_encoder.go new file mode 100644 index 0000000000..6b2de1b5f7 --- /dev/null +++ b/x/token/internal/keeper/msg_encoder.go @@ -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 +} diff --git a/x/token/internal/keeper/msg_encoder_test.go b/x/token/internal/keeper/msg_encoder_test.go new file mode 100644 index 0000000000..6eed6bc914 --- /dev/null +++ b/x/token/internal/keeper/msg_encoder_test.go @@ -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) + } + }) + } +} diff --git a/x/token/internal/querier/querier_encoder.go b/x/token/internal/querier/querier_encoder.go new file mode 100644 index 0000000000..c68ce76559 --- /dev/null +++ b/x/token/internal/querier/querier_encoder.go @@ -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 +} diff --git a/x/token/internal/querier/querier_encoder_test.go b/x/token/internal/querier/querier_encoder_test.go new file mode 100644 index 0000000000..34f4c5959a --- /dev/null +++ b/x/token/internal/querier/querier_encoder_test.go @@ -0,0 +1,111 @@ +package querier + +import ( + "encoding/json" + "fmt" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/line/link-modules/x/token/internal/types" + "github.com/stretchr/testify/require" +) + +var ( + tokenQueryEncoder types.EncodeQuerier +) + +func setupQueryEncoder() { + tokenQuerier := NewQuerier(tkeeper) + + tokenQueryEncoder = NewQueryEncoder(tokenQuerier) +} + +func encodeQuery(t *testing.T, jsonQuerier json.RawMessage, result interface{}) error { + res, err := tokenQueryEncoder(ctx, jsonQuerier) + if len(res) > 0 { + require.NoError(t, tkeeper.UnmarshalJSON(res, result)) + } + return err +} + +func TestNewQuerier_encodeQueryTokens(t *testing.T) { + prepare(t) + setupQueryEncoder() + jsonQuerier := fmt.Sprintf(`{"route":"tokens","data":{"query_token_param":{"contract_id":"%s"}}}`, contractID) + + var token types.Token + err := encodeQuery(t, json.RawMessage(jsonQuerier), &token) + require.NoError(t, err) + require.Equal(t, token.GetContractID(), contractID) + require.Equal(t, token.GetName(), tokenName) + require.Equal(t, token.GetSymbol(), tokenSymbol) + require.Equal(t, token.GetImageURI(), tokenImageURL) +} + +func TestNewQuerier_encodeQueryAccountPermission(t *testing.T) { + prepare(t) + setupQueryEncoder() + jsonQuerier := fmt.Sprintf(`{"route":"perms","data":{"query_perm_param":{"contract_id":"%s","address":"%s"}}}`, contractID, addr1) + + var perms types.Permissions + err := encodeQuery(t, json.RawMessage(jsonQuerier), &perms) + require.NoError(t, err) + require.Equal(t, len(perms), 3) + require.Equal(t, perms[0].String(), "modify") + require.Equal(t, perms[1].String(), "mint") + require.Equal(t, perms[2].String(), "burn") +} + +func TestNewQuerier_encodeQueryBalance(t *testing.T) { + prepare(t) + setupQueryEncoder() + jsonQuerier := fmt.Sprintf(`{"route":"balance","data":{"query_balance_param":{"contract_id":"%s","address":"%s"}}}`, contractID, addr1) + + var balance sdk.Int + err := encodeQuery(t, json.RawMessage(jsonQuerier), &balance) + require.NoError(t, err) + require.Equal(t, balance.Int64(), int64(tokenAmount-tokenBurned)) +} + +func TestNewQuerier_encodeQueryTotalSupply(t *testing.T) { + prepare(t) + setupQueryEncoder() + jsonQuerier := fmt.Sprintf(`{"route":"supply","data":{"query_total_param":{"contract_id":"%s","target":"%s"}}}`, contractID, types.QuerySupply) + + var supply sdk.Int + err := encodeQuery(t, json.RawMessage(jsonQuerier), &supply) + require.NoError(t, err) + require.Equal(t, supply.Int64(), int64(tokenAmount-tokenBurned)) +} + +func TestNewQuerier_encodeQueryTotalMint(t *testing.T) { + prepare(t) + setupQueryEncoder() + jsonQuerier := fmt.Sprintf(`{"route":"supply","data":{"query_total_param":{"contract_id":"%s","target":"%s"}}}`, contractID, types.QueryMint) + + var supply sdk.Int + err := encodeQuery(t, json.RawMessage(jsonQuerier), &supply) + require.NoError(t, err) + require.Equal(t, supply.Int64(), int64(tokenAmount)) +} + +func TestNewQuerier_encodeQueryTotalBurn(t *testing.T) { + prepare(t) + setupQueryEncoder() + jsonQuerier := fmt.Sprintf(`{"route":"supply","data":{"query_total_param":{"contract_id":"%s","target":"%s"}}}`, contractID, types.QueryBurn) + + var supply sdk.Int + err := encodeQuery(t, json.RawMessage(jsonQuerier), &supply) + require.NoError(t, err) + require.Equal(t, supply.Int64(), int64(tokenBurned)) +} + +func TestNewQuerier_invalidEncode(t *testing.T) { + prepare(t) + setupQueryEncoder() + jsonQuerier := fmt.Sprintln(`{"route":"noquery","data":{"query_invalid_param":""}}`) + + err := encodeQuery(t, json.RawMessage(jsonQuerier), nil) + require.EqualError(t, err, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized Msg route: %T", "noquery").Error()) +} diff --git a/x/token/internal/types/encoder.go b/x/token/internal/types/encoder.go new file mode 100644 index 0000000000..c9b62d6527 --- /dev/null +++ b/x/token/internal/types/encoder.go @@ -0,0 +1,100 @@ +package types + +import ( + "encoding/json" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +type EncodeHandler func(jsonMsg json.RawMessage) ([]sdk.Msg, error) +type EncodeQuerier func(ctx sdk.Context, jsonQuerier json.RawMessage) ([]byte, error) + +const ( + EncodeRouterKey = "tokenencode" +) + +type MsgRoute string + +const ( + RIssue = MsgRoute("issue") + RTransfer = MsgRoute("transfer") + RMint = MsgRoute("mint") + RBurn = MsgRoute("burn") + RGrantPerm = MsgRoute("grant_perm") + RRevokePerm = MsgRoute("revoke_perm") + RModify = MsgRoute("modify") +) + +// WasmCustomMsg - wasm custom msg parser +type WasmCustomMsg struct { + Route string `json:"route"` + Data json.RawMessage `json:"data"` +} + +type IssueMsgWrapper struct { + MsgIssue MsgIssue `json:"issue"` +} + +type TransferMsgWrapper struct { + MsgTransfer MsgTransfer `json:"transfer"` +} + +type MintMsgWrapper struct { + MsgMint MsgMint `json:"mint"` +} + +type BurnMsgWrapper struct { + MsgBurn MsgBurn `json:"burn"` +} + +type GrantPermMsgWrapper struct { + MsgGrantPermission MsgGrantPermission `json:"grant_perm"` +} + +type RevokePermMsgWrapper struct { + MsgRevokePermission MsgRevokePermission `json:"revoke_perm"` +} + +type ModifyMsgWrapper struct { + MsgModify MsgModify `json:"modify"` +} + +type WasmCustomQuerier struct { + Route string `json:"route"` + Data json.RawMessage `json:"data"` +} + +type QueryTokenWrapper struct { + QueryTokenParam QueryTokenParam `json:"query_token_param"` +} + +type QueryTokenParam struct { + ContractID string `json:"contract_id"` +} + +type QueryBalanceWrapper struct { + QueryBalanceParam QueryBalanceParam `json:"query_balance_param"` +} + +type QueryBalanceParam struct { + ContractID string `json:"contract_id"` + Address sdk.AccAddress `json:"address"` +} + +type QueryTotalWrapper struct { + QueryTotalParam QueryTotalParam `json:"query_total_param"` +} + +type QueryTotalParam struct { + ContractID string `json:"contract_id"` + Target string `json:"target"` +} + +type QueryPermWrapper struct { + QueryPermParam QueryPermParam `json:"query_perm_param"` +} + +type QueryPermParam struct { + ContractID string `json:"contract_id"` + Address sdk.AccAddress `json:"address"` +} diff --git a/x/wasm/alias.go b/x/wasm/alias.go index ac4392b10d..050b2d4288 100644 --- a/x/wasm/alias.go +++ b/x/wasm/alias.go @@ -61,20 +61,22 @@ var ( NewMessageHandler = keeper.NewMessageHandler DefaultEncoders = keeper.DefaultEncoders EncodeBankMsg = keeper.EncodeBankMsg - NoCustomMsg = keeper.NoCustomMsg + CustomMsg = keeper.CustomMsg EncodeStakingMsg = keeper.EncodeStakingMsg EncodeWasmMsg = keeper.EncodeWasmMsg NewKeeper = keeper.NewKeeper NewQuerier = keeper.NewQuerier DefaultQueryPlugins = keeper.DefaultQueryPlugins BankQuerier = keeper.BankQuerier - NoCustomQuerier = keeper.NoCustomQuerier StakingQuerier = keeper.StakingQuerier WasmQuerier = keeper.WasmQuerier MakeTestCodec = keeper.MakeTestCodec CreateTestInput = keeper.CreateTestInput TestHandler = keeper.TestHandler + CustomQuerier = keeper.CustomQuerier NewWasmProposalHandler = keeper.NewWasmProposalHandler + NewRouter = types.NewRouter + NewQuerierRouter = types.NewQuerierRouter // variable aliases ModuleCdc = types.ModuleCdc @@ -124,6 +126,5 @@ type ( GetCodeResponse = keeper.GetCodeResponse ListCodeResponse = keeper.ListCodeResponse QueryHandler = keeper.QueryHandler - CustomQuerier = keeper.CustomQuerier QueryPlugins = keeper.QueryPlugins ) diff --git a/x/wasm/internal/keeper/genesis_test.go b/x/wasm/internal/keeper/genesis_test.go index 104e6c1727..5f83e25bab 100644 --- a/x/wasm/internal/keeper/genesis_test.go +++ b/x/wasm/internal/keeper/genesis_test.go @@ -497,7 +497,7 @@ func setupKeeper(t *testing.T) (Keeper, sdk.Context, []sdk.StoreKey, func()) { cdc := MakeTestCodec() pk := params.NewKeeper(cdc, keyParams, tkeyParams) wasmConfig := types.DefaultWasmConfig() - srcKeeper := NewKeeper(cdc, keyWasm, pk.Subspace(types.DefaultParamspace), auth.AccountKeeper{}, nil, staking.Keeper{}, nil, tempDir, wasmConfig, "", nil, nil) + srcKeeper := NewKeeper(cdc, keyWasm, pk.Subspace(types.DefaultParamspace), auth.AccountKeeper{}, nil, staking.Keeper{}, nil, nil, nil, tempDir, wasmConfig, "", nil, nil) srcKeeper.setParams(ctx, types.DefaultParams()) return srcKeeper, ctx, []sdk.StoreKey{keyWasm, keyParams}, cleanup diff --git a/x/wasm/internal/keeper/handler_plugin.go b/x/wasm/internal/keeper/handler_plugin.go index 290aee50f5..848da5fe35 100644 --- a/x/wasm/internal/keeper/handler_plugin.go +++ b/x/wasm/internal/keeper/handler_plugin.go @@ -15,20 +15,22 @@ import ( ) type MessageHandler struct { - router sdk.Router - encoders MessageEncoders + router sdk.Router + encodeRouter types.Router + encoders MessageEncoders } -func NewMessageHandler(router sdk.Router, customEncoders *MessageEncoders) MessageHandler { +func NewMessageHandler(router sdk.Router, encodeRouter types.Router, customEncoders *MessageEncoders) MessageHandler { encoders := DefaultEncoders().Merge(customEncoders) return MessageHandler{ - router: router, - encoders: encoders, + router: router, + encodeRouter: encodeRouter, + encoders: encoders, } } type BankEncoder func(sender sdk.AccAddress, msg *wasmTypes.BankMsg) ([]sdk.Msg, error) -type CustomEncoder func(sender sdk.AccAddress, msg json.RawMessage) ([]sdk.Msg, error) +type CustomEncoder func(sender sdk.AccAddress, msg json.RawMessage, router types.Router) ([]sdk.Msg, error) type StakingEncoder func(sender sdk.AccAddress, msg *wasmTypes.StakingMsg) ([]sdk.Msg, error) type WasmEncoder func(sender sdk.AccAddress, msg *wasmTypes.WasmMsg) ([]sdk.Msg, error) @@ -42,7 +44,7 @@ type MessageEncoders struct { func DefaultEncoders() MessageEncoders { return MessageEncoders{ Bank: EncodeBankMsg, - Custom: NoCustomMsg, + Custom: CustomMsg, Staking: EncodeStakingMsg, Wasm: EncodeWasmMsg, } @@ -67,12 +69,12 @@ func (e MessageEncoders) Merge(o *MessageEncoders) MessageEncoders { return e } -func (e MessageEncoders) Encode(contractAddr sdk.AccAddress, msg wasmTypes.CosmosMsg) ([]sdk.Msg, error) { +func (e MessageEncoders) Encode(contractAddr sdk.AccAddress, msg wasmTypes.CosmosMsg, router types.Router) ([]sdk.Msg, error) { switch { case msg.Bank != nil: return e.Bank(contractAddr, msg.Bank) case msg.Custom != nil: - return e.Custom(contractAddr, msg.Custom) + return e.Custom(contractAddr, msg.Custom, router) case msg.Staking != nil: return e.Staking(contractAddr, msg.Staking) case msg.Wasm != nil: @@ -108,8 +110,17 @@ func EncodeBankMsg(sender sdk.AccAddress, msg *wasmTypes.BankMsg) ([]sdk.Msg, er return []sdk.Msg{sdkMsg}, nil } -func NoCustomMsg(sender sdk.AccAddress, msg json.RawMessage) ([]sdk.Msg, error) { - return nil, sdkerrors.Wrap(types.ErrInvalidMsg, "Custom variant not supported") +func CustomMsg(sender sdk.AccAddress, jsonMsg json.RawMessage, router types.Router) ([]sdk.Msg, error) { + var linkMsgWrapper types.LinkMsgWrapper + err := json.Unmarshal(jsonMsg, &linkMsgWrapper) + if err != nil { + return nil, err + } + handler := router.GetRoute(linkMsgWrapper.Module) + if handler == nil { + return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized handler: %T", linkMsgWrapper.Module) + } + return handler(linkMsgWrapper.MsgData) } func EncodeStakingMsg(sender sdk.AccAddress, msg *wasmTypes.StakingMsg) ([]sdk.Msg, error) { @@ -230,8 +241,8 @@ func EncodeWasmMsg(sender sdk.AccAddress, msg *wasmTypes.WasmMsg) ([]sdk.Msg, er return nil, sdkerrors.Wrap(types.ErrInvalidMsg, "Unknown variant of Wasm") } -func (h MessageHandler) Dispatch(ctx sdk.Context, contractAddr sdk.AccAddress, msg wasmTypes.CosmosMsg) error { - sdkMsgs, err := h.encoders.Encode(contractAddr, msg) +func (h MessageHandler) Dispatch(ctx sdk.Context, contractAddr sdk.AccAddress, msg wasmTypes.CosmosMsg, router types.Router) error { + sdkMsgs, err := h.encoders.Encode(contractAddr, msg, router) if err != nil { return err } diff --git a/x/wasm/internal/keeper/handler_plugin_test.go b/x/wasm/internal/keeper/handler_plugin_test.go index 806c5ef436..ff6fd8eaa3 100644 --- a/x/wasm/internal/keeper/handler_plugin_test.go +++ b/x/wasm/internal/keeper/handler_plugin_test.go @@ -3,6 +3,8 @@ package keeper import ( "encoding/json" "fmt" + "io/ioutil" + "os" "testing" "github.com/stretchr/testify/assert" @@ -15,10 +17,34 @@ import ( wasmTypes "github.com/CosmWasm/go-cosmwasm/types" "github.com/line/link-modules/x/coin" + "github.com/line/link-modules/x/collection" + "github.com/line/link-modules/x/token" "github.com/line/link-modules/x/wasm/internal/types" ) +type testData struct { + tokenKeeper token.Keeper + collectionKeeper collection.Keeper +} + +// returns a cleanup function, which must be defered on +func setupTest(t *testing.T) (testData, func()) { + tempDir, err := ioutil.TempDir("", "wasm") + require.NoError(t, err) + + _, keepers := CreateTestInput(t, false, tempDir, "staking,link", nil, nil) + tokenKeeper, collectionKeeper := keepers.TokenKeeper, keepers.CollectionKeeper + cleanup := func() { os.RemoveAll(tempDir) } + data := testData{ + tokenKeeper: tokenKeeper, + collectionKeeper: collectionKeeper, + } + + return data, cleanup +} + func TestEncoding(t *testing.T) { + data, _ := setupTest(t) _, _, addr1 := keyPubAddr() _, _, addr2 := keyPubAddr() invalidAddr := "xrnd1d02kd90n38qvr3qb9qof83fn2d2" @@ -29,6 +55,24 @@ func TestEncoding(t *testing.T) { jsonMsg := json.RawMessage(`{"foo": 123}`) + testContractID := "test_contract_id" + issue := fmt.Sprintf(`{"module":"tokenencode", "msg_data":{"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(`{"module":"tokenencode", "msg_data":{"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(`{"module":"tokenencode", "msg_data":{"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(`{"module":"tokenencode", "msg_data":{"route":"burn", "data":{"burn":{"from":"%s", "contract_id":"%s", "amount":"5"}}}}`, addr1.String(), testContractID) + burnMsg := json.RawMessage(burn) + grantPermission := fmt.Sprintf(`{"module":"tokenencode", "msg_data":{"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(`{"module":"tokenencode", "msg_data":{"route":"revoke_perm", "data":{"revoke_perm":{"from":"%s", "contract_id":"%s", "permission":"mint"}}}}`, addr1.String(), testContractID) + revokePermissionMsg := json.RawMessage(revokePermission) + modify := fmt.Sprintf(`{"module":"tokenencode", "msg_data":{"route":"modify","data":{"modify":{"owner":"%s","contract_id":"%s","changes":[{"field":"meta","value":"update_meta"}]}}}}`, addr1.String(), testContractID) + modifyMsg := json.RawMessage(modify) + + changes := token.NewChanges(token.NewChange("meta", "update_meta")) + cases := map[string]struct { sender sdk.AccAddress input wasmTypes.CosmosMsg @@ -259,13 +303,107 @@ func TestEncoding(t *testing.T) { }, }, }, + "issue token": { + sender: addr1, + input: wasmTypes.CosmosMsg{ + Custom: issueMsg, + }, + output: []sdk.Msg{ + token.MsgIssue{ + Owner: addr1, + To: addr2, + Name: "TestToken1", + Symbol: "TT1", + ImageURI: "", + Meta: "", + Amount: sdk.NewInt(1000), + Mintable: true, + Decimals: sdk.NewInt(18), + }, + }, + }, + "transfer token": { + sender: addr1, + input: wasmTypes.CosmosMsg{ + Custom: transferMsg, + }, + output: []sdk.Msg{ + token.MsgTransfer{ + From: addr1, + ContractID: testContractID, + To: addr2, + Amount: sdk.NewInt(100), + }, + }, + }, + "mint token": { + sender: addr1, + input: wasmTypes.CosmosMsg{ + Custom: mintMsg, + }, + output: []sdk.Msg{ + token.MsgMint{ + From: addr1, + ContractID: testContractID, + To: addr2, + Amount: sdk.NewInt(100), + }, + }, + }, + "burn token": { + sender: addr1, + input: wasmTypes.CosmosMsg{ + Custom: burnMsg, + }, + output: []sdk.Msg{ + token.NewMsgBurn(addr1, testContractID, sdk.NewInt(5)), + }, + }, + "grant permission": { + sender: addr1, + input: wasmTypes.CosmosMsg{ + Custom: grantPermissionMsg, + }, + output: []sdk.Msg{ + token.NewMsgGrantPermission(addr1, testContractID, addr2, token.Permission("mint")), + }, + }, + "revoke permission": { + sender: addr1, + input: wasmTypes.CosmosMsg{ + Custom: revokePermissionMsg, + }, + output: []sdk.Msg{ + token.NewMsgRevokePermission(addr1, testContractID, token.Permission("mint")), + }, + }, + "modify token": { + sender: addr1, + input: wasmTypes.CosmosMsg{ + Custom: modifyMsg, + }, + output: []sdk.Msg{ + token.NewMsgModify(addr1, testContractID, changes), + }, + }, + "invalid custom msg": { + sender: addr1, + input: wasmTypes.CosmosMsg{ + Custom: json.RawMessage("invalid msg"), + }, + isError: true, + }, } - encoder := DefaultEncoders() + e := DefaultEncoders() + tokenEncodeHandler := token.NewMsgEncodeHandler(data.tokenKeeper) + var encodeRouter = types.NewRouter() + encodeRouter.AddRoute(token.EncodeRouterKey, tokenEncodeHandler) + for name, tc := range cases { tc := tc t.Run(name, func(t *testing.T) { - res, err := encoder.Encode(tc.sender, tc.input) + res, err := e.Encode(tc.sender, tc.input, encodeRouter) if tc.isError { require.Error(t, err) } else { diff --git a/x/wasm/internal/keeper/keeper.go b/x/wasm/internal/keeper/keeper.go index 28f76bf996..c5f87fe1d6 100644 --- a/x/wasm/internal/keeper/keeper.go +++ b/x/wasm/internal/keeper/keeper.go @@ -60,8 +60,7 @@ type Keeper struct { // NewKeeper creates a new contract Keeper instance // If customEncoders is non-nil, we can use this to override some of the message handler, especially custom func NewKeeper(cdc *codec.Codec, storeKey sdk.StoreKey, paramSpace params.Subspace, accountKeeper auth.AccountKeeper, bankKeeper types.BankKeeper, - stakingKeeper staking.Keeper, - router sdk.Router, homeDir string, wasmConfig types.WasmConfig, supportedFeatures string, customEncoders *MessageEncoders, customPlugins *QueryPlugins) Keeper { + stakingKeeper staking.Keeper, router sdk.Router, encodeRouter types.Router, queryRouter types.QueryRouter, homeDir string, wasmConfig types.WasmConfig, supportedFeatures string, customEncoders *MessageEncoders, customPlugins *QueryPlugins) Keeper { wasmer, err := wasm.NewWasmer(filepath.Join(homeDir, "wasm"), supportedFeatures, wasmConfig.CacheSize) if err != nil { panic(err) @@ -78,12 +77,12 @@ func NewKeeper(cdc *codec.Codec, storeKey sdk.StoreKey, paramSpace params.Subspa wasmer: *wasmer, accountKeeper: accountKeeper, bankKeeper: bankKeeper, - messenger: NewMessageHandler(router, customEncoders), + messenger: NewMessageHandler(router, encodeRouter, customEncoders), queryGasLimit: wasmConfig.SmartQueryGasLimit, authZPolicy: DefaultAuthorizationPolicy{}, paramSpace: paramSpace, } - keeper.queryPlugins = DefaultQueryPlugins(bankKeeper, stakingKeeper, &keeper).Merge(customPlugins) + keeper.queryPlugins = DefaultQueryPlugins(bankKeeper, stakingKeeper, queryRouter, &keeper).Merge(customPlugins) return keeper } @@ -551,7 +550,7 @@ func (k Keeper) GetByteCode(ctx sdk.Context, codeID uint64) ([]byte, error) { func (k Keeper) dispatchMessages(ctx sdk.Context, contractAddr sdk.AccAddress, msgs []wasmTypes.CosmosMsg) error { for _, msg := range msgs { - if err := k.messenger.Dispatch(ctx, contractAddr, msg); err != nil { + if err := k.messenger.Dispatch(ctx, contractAddr, msg, k.messenger.encodeRouter); err != nil { return err } } diff --git a/x/wasm/internal/keeper/query_plugins.go b/x/wasm/internal/keeper/query_plugins.go index 023ac988a9..a6accd5836 100644 --- a/x/wasm/internal/keeper/query_plugins.go +++ b/x/wasm/internal/keeper/query_plugins.go @@ -8,6 +8,8 @@ import ( sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/staking" + + "github.com/line/link-modules/x/wasm/internal/types" ) type QueryHandler struct { @@ -47,19 +49,17 @@ func (q QueryHandler) GasConsumed() uint64 { return q.Ctx.GasMeter().GasConsumed() } -type CustomQuerier func(ctx sdk.Context, request json.RawMessage) ([]byte, error) - type QueryPlugins struct { Bank func(ctx sdk.Context, request *wasmTypes.BankQuery) ([]byte, error) - Custom CustomQuerier + Custom func(ctx sdk.Context, request json.RawMessage) ([]byte, error) Staking func(ctx sdk.Context, request *wasmTypes.StakingQuery) ([]byte, error) Wasm func(ctx sdk.Context, request *wasmTypes.WasmQuery) ([]byte, error) } -func DefaultQueryPlugins(bank bank.ViewKeeper, staking staking.Keeper, wasm *Keeper) QueryPlugins { +func DefaultQueryPlugins(bank bank.ViewKeeper, staking staking.Keeper, queryRouter types.QueryRouter, wasm *Keeper) QueryPlugins { return QueryPlugins{ Bank: BankQuerier(bank), - Custom: NoCustomQuerier, + Custom: CustomQuerier(queryRouter), Staking: StakingQuerier(staking), Wasm: WasmQuerier(wasm), } @@ -117,8 +117,19 @@ func BankQuerier(bank bank.ViewKeeper) func(ctx sdk.Context, request *wasmTypes. } } -func NoCustomQuerier(sdk.Context, json.RawMessage) ([]byte, error) { - return nil, wasmTypes.UnsupportedRequest{Kind: "custom"} +func CustomQuerier(queryRouter types.QueryRouter) func(ctx sdk.Context, querierJson json.RawMessage) ([]byte, error) { + return func(ctx sdk.Context, querierJson json.RawMessage) ([]byte, error) { + var linkQueryWrapper types.LinkQueryWrapper + err := json.Unmarshal(querierJson, &linkQueryWrapper) + if err != nil { + return nil, err + } + querier := queryRouter.GetRoute(linkQueryWrapper.Module) + if querier == nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "Unknown encode module") + } + return querier(ctx, linkQueryWrapper.QueryData) + } } func StakingQuerier(keeper staking.Keeper) func(ctx sdk.Context, request *wasmTypes.StakingQuery) ([]byte, error) { diff --git a/x/wasm/internal/keeper/reflect_test.go b/x/wasm/internal/keeper/reflect_test.go index 34a2d96294..70b593b996 100644 --- a/x/wasm/internal/keeper/reflect_test.go +++ b/x/wasm/internal/keeper/reflect_test.go @@ -333,7 +333,7 @@ func maskEncoders(cdc *codec.Codec) *MessageEncoders { // fromMaskRawMsg decodes msg.Data to an sdk.Msg using amino json encoding. // this needs to be registered on the Encoders func fromMaskRawMsg(cdc *codec.Codec) CustomEncoder { - return func(_sender sdk.AccAddress, msg json.RawMessage) ([]sdk.Msg, error) { + return func(_sender sdk.AccAddress, msg json.RawMessage, router types.Router) ([]sdk.Msg, error) { var custom maskCustomMsg err := json.Unmarshal(msg, &custom) if err != nil { diff --git a/x/wasm/internal/keeper/test_common.go b/x/wasm/internal/keeper/test_common.go index 4b3c0341ff..9b4685f6bd 100644 --- a/x/wasm/internal/keeper/test_common.go +++ b/x/wasm/internal/keeper/test_common.go @@ -27,6 +27,9 @@ import ( "github.com/cosmos/cosmos-sdk/x/supply" "github.com/line/link-modules/x/coin" + "github.com/line/link-modules/x/collection" + "github.com/line/link-modules/x/contract" + "github.com/line/link-modules/x/token" wasmtypes "github.com/line/link-modules/x/wasm/internal/types" ) @@ -62,18 +65,20 @@ var TestingStakeParams = staking.Params{ } type TestKeepers struct { - AccountKeeper auth.AccountKeeper - StakingKeeper staking.Keeper - WasmKeeper Keeper - DistKeeper distribution.Keeper - SupplyKeeper supply.Keeper - GovKeeper gov.Keeper - BankKeeper wasmtypes.BankKeeper + AccountKeeper auth.AccountKeeper + StakingKeeper staking.Keeper + WasmKeeper Keeper + DistKeeper distribution.Keeper + SupplyKeeper supply.Keeper + GovKeeper gov.Keeper + BankKeeper wasmtypes.BankKeeper + TokenKeeper token.Keeper + CollectionKeeper collection.Keeper } // encoders can be nil to accept the defaults, or set it to override some of the message handlers (like default) func CreateTestInput(t *testing.T, isCheckTx bool, tempDir string, supportedFeatures string, encoders *MessageEncoders, queriers *QueryPlugins) (sdk.Context, TestKeepers) { - keyContract := sdk.NewKVStoreKey(wasmtypes.StoreKey) + keyWasm := sdk.NewKVStoreKey(wasmtypes.StoreKey) keyAcc := sdk.NewKVStoreKey(auth.StoreKey) keyStaking := sdk.NewKVStoreKey(staking.StoreKey) keySupply := sdk.NewKVStoreKey(supply.StoreKey) @@ -82,10 +87,13 @@ func CreateTestInput(t *testing.T, isCheckTx bool, tempDir string, supportedFeat tkeyParams := sdk.NewTransientStoreKey(params.TStoreKey) keyGov := sdk.NewKVStoreKey(govtypes.StoreKey) keyCoin := sdk.NewKVStoreKey(coin.StoreKey) + keyToken := sdk.NewKVStoreKey(token.StoreKey) + keyContract := sdk.NewKVStoreKey(contract.StoreKey) + keyCollection := sdk.NewKVStoreKey(collection.StoreKey) db := dbm.NewMemDB() ms := store.NewCommitMultiStore(db) - ms.MountStoreWithDB(keyContract, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(keyWasm, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(keyAcc, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(keyParams, sdk.StoreTypeIAVL, db) ms.MountStoreWithDB(keyStaking, sdk.StoreTypeIAVL, db) @@ -139,6 +147,11 @@ func CreateTestInput(t *testing.T, isCheckTx bool, tempDir string, supportedFeat stakingKeeper := staking.NewKeeper(cdc, keyStaking, supplyKeeper, paramsKeeper.Subspace(staking.DefaultParamspace)) stakingKeeper.SetParams(ctx, TestingStakeParams) + contractKeeper := contract.NewContractKeeper(cdc, keyContract) + tokenKeeper := token.NewKeeper(cdc, accountKeeper, contractKeeper, keyToken) + paramsSpace := paramsKeeper.Subspace(collection.DefaultParamspace) + collectionKeeper := collection.NewKeeper(cdc, accountKeeper, contractKeeper, paramsSpace, keyCollection) + distKeeper := distribution.NewKeeper(cdc, keyDistro, paramsKeeper.Subspace(distribution.DefaultParamspace), stakingKeeper, supplyKeeper, auth.FeeCollectorName, nil) distKeeper.SetParams(ctx, distribution.DefaultParams()) stakingKeeper.SetHooks(distKeeper.Hooks()) @@ -167,6 +180,7 @@ func CreateTestInput(t *testing.T, isCheckTx bool, tempDir string, supportedFeat stakeAddr := supply.NewModuleAddress(staking.BondedPoolName) moduleAcct := accountKeeper.GetAccount(ctx, stakeAddr) require.NotNil(t, moduleAcct) + tokenEncodeHandler := token.NewMsgEncodeHandler(tokenKeeper) router := baseapp.NewRouter() ch := coin.NewHandler(coinKeeper) @@ -175,11 +189,17 @@ func CreateTestInput(t *testing.T, isCheckTx bool, tempDir string, supportedFeat router.AddRoute(staking.RouterKey, sh) dh := distribution.NewHandler(distKeeper) router.AddRoute(distribution.RouterKey, dh) + encodeRouter := wasmtypes.NewRouter() + encodeRouter.AddRoute(token.EncodeRouterKey, tokenEncodeHandler) + querierRouter := wasmtypes.NewQuerierRouter() + tokenQuerier := token.NewQuerier(tokenKeeper) + tokenEncodeQuerier := token.NewQueryEncoder(tokenQuerier) + querierRouter.AddRoute(token.RouterKey, tokenEncodeQuerier) // Load default wasm config wasmConfig := wasmtypes.DefaultWasmConfig() - keeper := NewKeeper(cdc, keyContract, paramsKeeper.Subspace(wasmtypes.DefaultParamspace), - accountKeeper, coinKeeper, stakingKeeper, router, tempDir, wasmConfig, + keeper := NewKeeper(cdc, keyWasm, paramsKeeper.Subspace(wasmtypes.DefaultParamspace), + accountKeeper, coinKeeper, stakingKeeper, router, encodeRouter, querierRouter, tempDir, wasmConfig, supportedFeatures, encoders, queriers, ) keeper.setParams(ctx, wasmtypes.DefaultParams()) @@ -201,13 +221,15 @@ func CreateTestInput(t *testing.T, isCheckTx bool, tempDir string, supportedFeat govKeeper.SetTallyParams(ctx, govtypes.DefaultTallyParams()) keepers := TestKeepers{ - AccountKeeper: accountKeeper, - SupplyKeeper: supplyKeeper, - StakingKeeper: stakingKeeper, - DistKeeper: distKeeper, - WasmKeeper: keeper, - GovKeeper: govKeeper, - BankKeeper: coinKeeper, + AccountKeeper: accountKeeper, + SupplyKeeper: supplyKeeper, + StakingKeeper: stakingKeeper, + DistKeeper: distKeeper, + WasmKeeper: keeper, + GovKeeper: govKeeper, + BankKeeper: coinKeeper, + TokenKeeper: tokenKeeper, + CollectionKeeper: collectionKeeper, } return ctx, keepers } diff --git a/x/wasm/internal/types/encoder.go b/x/wasm/internal/types/encoder.go new file mode 100644 index 0000000000..4f38696eaa --- /dev/null +++ b/x/wasm/internal/types/encoder.go @@ -0,0 +1,22 @@ +package types + +import ( + "encoding/json" +) + +type EncodingModule string + +const ( + TokenM = EncodingModule("token") + CollectionM = EncodingModule("collection") +) + +type LinkMsgWrapper struct { + Module string `json:"module"` + MsgData json.RawMessage `json:"msg_data"` +} + +type LinkQueryWrapper struct { + Module string `json:"module"` + QueryData json.RawMessage `json:"query_data"` +} diff --git a/x/wasm/internal/types/querier_router.go b/x/wasm/internal/types/querier_router.go new file mode 100644 index 0000000000..c9a3d29545 --- /dev/null +++ b/x/wasm/internal/types/querier_router.go @@ -0,0 +1,63 @@ +package types + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/line/link-modules/x/token" +) + +var _ QueryRouter = (*querierRouter)(nil) + +// QueryRouter provides queryables for each query path. +type QueryRouter interface { + AddRoute(r string, q token.EncodeQuerier) QueryRouter + HasRoute(r string) bool + GetRoute(path string) token.EncodeQuerier + Seal() +} + +type querierRouter struct { + routes map[string]token.EncodeQuerier + sealed bool +} + +func NewQuerierRouter() QueryRouter { + return &querierRouter{ + routes: make(map[string]token.EncodeQuerier), + } +} + +func (rtr *querierRouter) Seal() { + if rtr.sealed { + panic("querier router already sealed") + } + rtr.sealed = true +} + +func (rtr *querierRouter) AddRoute(path string, q token.EncodeQuerier) QueryRouter { + if rtr.sealed { + panic("router sealed; cannot add route handler") + } + if !sdk.IsAlphaNumeric(path) { + panic("querier route expressions can only contain alphanumeric characters") + } + if rtr.HasRoute(path) { + panic(fmt.Sprintf("querier route %s has already been initialized", path)) + } + + rtr.routes[path] = q + return rtr +} + +func (rtr *querierRouter) HasRoute(path string) bool { + return rtr.routes[path] != nil +} + +func (rtr *querierRouter) GetRoute(path string) token.EncodeQuerier { + if !rtr.HasRoute(path) { + panic(fmt.Sprintf("querier route \"%s\" does not exist", path)) + } + + return rtr.routes[path] +} diff --git a/x/wasm/internal/types/querier_router_test.go b/x/wasm/internal/types/querier_router_test.go new file mode 100644 index 0000000000..06bd253374 --- /dev/null +++ b/x/wasm/internal/types/querier_router_test.go @@ -0,0 +1,28 @@ +package types + +import ( + "encoding/json" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" +) + +func testQuerierHandler(ctx sdk.Context, jsonQuerier json.RawMessage) ([]byte, error) { + return nil, nil +} + +func TestQuerierRouterSeal(t *testing.T) { + r := NewQuerierRouter() + r.Seal() + require.Panics(t, func() { r.AddRoute("test", nil) }) + require.Panics(t, func() { r.Seal() }) +} + +func TestQuerierRouter(t *testing.T) { + r := NewQuerierRouter() + r.AddRoute("test", testQuerierHandler) + require.True(t, r.HasRoute("test")) + require.Panics(t, func() { r.AddRoute("test", testQuerierHandler) }) + require.Panics(t, func() { r.AddRoute(" ", testQuerierHandler) }) +} diff --git a/x/wasm/internal/types/router.go b/x/wasm/internal/types/router.go new file mode 100644 index 0000000000..d4672e90ad --- /dev/null +++ b/x/wasm/internal/types/router.go @@ -0,0 +1,65 @@ +package types + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/line/link-modules/x/token" +) + +var _ Router = (*router)(nil) + +type Router interface { + AddRoute(r string, h token.EncodeHandler) (rtr Router) + HasRoute(r string) bool + GetRoute(path string) (h token.EncodeHandler) + Seal() +} + +type router struct { + routes map[string]token.EncodeHandler + sealed bool +} + +func NewRouter() Router { + return &router{ + routes: make(map[string]token.EncodeHandler), + } +} + +func (rtr *router) Seal() { + if rtr.sealed { + panic("router already sealed") + } + rtr.sealed = true +} + +func (rtr *router) AddRoute(path string, h token.EncodeHandler) Router { + if rtr.sealed { + panic("router sealed; cannot add route handler") + } + + if !sdk.IsAlphaNumeric(path) { + panic("route expressions can only contain alphanumeric characters") + } + if rtr.HasRoute(path) { + panic(fmt.Sprintf("route %s has already been initialized", path)) + } + + rtr.routes[path] = h + return rtr +} + +// HasRoute returns true if the router has a path registered or false otherwise. +func (rtr *router) HasRoute(path string) bool { + return rtr.routes[path] != nil +} + +// GetRoute returns a Handler for a given path. +func (rtr *router) GetRoute(path string) token.EncodeHandler { + if !rtr.HasRoute(path) { + panic(fmt.Sprintf("route \"%s\" does not exist", path)) + } + + return rtr.routes[path] +} diff --git a/x/wasm/internal/types/router_test.go b/x/wasm/internal/types/router_test.go new file mode 100644 index 0000000000..e0872ff4c9 --- /dev/null +++ b/x/wasm/internal/types/router_test.go @@ -0,0 +1,26 @@ +package types + +import ( + "encoding/json" + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" +) + +func testHandler(jsonMsg json.RawMessage) ([]sdk.Msg, error) { return []sdk.Msg{}, nil } + +func TestRouterSeal(t *testing.T) { + r := NewRouter() + r.Seal() + require.Panics(t, func() { r.AddRoute("test", nil) }) + require.Panics(t, func() { r.Seal() }) +} + +func TestRouter(t *testing.T) { + r := NewRouter() + r.AddRoute("test", testHandler) + require.True(t, r.HasRoute("test")) + require.Panics(t, func() { r.AddRoute("test", testHandler) }) + require.Panics(t, func() { r.AddRoute(" ", testHandler) }) +}