-
Notifications
You must be signed in to change notification settings - Fork 202
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: base64 decoder for cw3 wasm messages (#1731)
* wip * feat: have a dirty funny looking solution * chore: changelog * fix: fix replace usage * Update cmd/nibid/cmd/decode_base64.go Co-authored-by: Kevin Yang <[email protected]> * fix: turns out InterfaceRegistry.Resolve does the trick * chore: changelog * fix: remove unused import --------- Co-authored-by: Kevin Yang <[email protected]>
- Loading branch information
1 parent
b871f0e
commit 54005e7
Showing
4 changed files
with
248 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
package cmd | ||
|
||
import ( | ||
"encoding/base64" | ||
"encoding/json" | ||
"fmt" | ||
"strings" | ||
|
||
"github.com/spf13/cobra" | ||
|
||
"github.com/cosmos/cosmos-sdk/client" | ||
"github.com/cosmos/cosmos-sdk/client/flags" | ||
sdkcodec "github.com/cosmos/cosmos-sdk/codec" | ||
|
||
wasmvm "github.com/CosmWasm/wasmvm/types" | ||
) | ||
|
||
// YieldStargateMsgs parses the JSON and sends wasmvm.StargateMsg objects to a channel | ||
func YieldStargateMsgs(jsonBz []byte) ([]wasmvm.StargateMsg, error) { | ||
var data interface{} | ||
if err := json.Unmarshal(jsonBz, &data); err != nil { | ||
return nil, err | ||
} | ||
|
||
var msgs []wasmvm.StargateMsg | ||
parseStargateMsgs(data, &msgs) | ||
return msgs, nil | ||
} | ||
|
||
func parseStargateMsgs(jsonData any, msgs *[]wasmvm.StargateMsg) { | ||
switch v := jsonData.(type) { | ||
case map[string]interface{}: | ||
if typeURL, ok := v["type_url"].(string); ok { | ||
if value, ok := v["value"].(string); ok { | ||
*msgs = append(*msgs, wasmvm.StargateMsg{ | ||
TypeURL: typeURL, | ||
Value: []byte(value), | ||
}) | ||
} | ||
} | ||
for _, value := range v { | ||
parseStargateMsgs(value, msgs) | ||
} | ||
case []interface{}: | ||
for _, value := range v { | ||
parseStargateMsgs(value, msgs) | ||
} | ||
} | ||
} | ||
|
||
type StargateMsgDecoded struct { | ||
TypeURL string `json:"type_url"` | ||
Value string `json:"value"` | ||
} | ||
|
||
func DecodeBase64StargateMsgs( | ||
jsonBz []byte, context client.Context, | ||
) (newSgMsgs []StargateMsgDecoded, err error) { | ||
codec := context.Codec | ||
|
||
var data interface{} | ||
if err := json.Unmarshal(jsonBz, &data); err != nil { | ||
return []StargateMsgDecoded{}, err | ||
} | ||
|
||
sgMsgs, err := YieldStargateMsgs(jsonBz) | ||
if err != nil { | ||
return | ||
} | ||
for _, sgMsg := range sgMsgs { | ||
valueStr := string(sgMsg.Value) | ||
value := strings.Replace(string(sgMsg.Value), `\"`, `"`, -1) | ||
value = strings.Replace(value, `"{`, `{`, -1) | ||
value = strings.Replace(value, `}"`, `}`, -1) | ||
|
||
if _, err := base64.StdEncoding.DecodeString(valueStr); err == nil { | ||
protoMsg, err := context.InterfaceRegistry.Resolve(sgMsg.TypeURL) | ||
if err != nil { | ||
return newSgMsgs, err | ||
} | ||
|
||
decodedBz, _ := base64.StdEncoding.Strict().DecodeString(string(sgMsg.Value)) | ||
concrete := protoMsg.(sdkcodec.ProtoMarshaler) | ||
|
||
err = codec.Unmarshal(decodedBz, concrete) | ||
if err != nil { | ||
return newSgMsgs, err | ||
} | ||
|
||
outBytes, err := codec.MarshalJSON(concrete) | ||
if err != nil { | ||
return newSgMsgs, err | ||
} | ||
|
||
newSgMsgs = append(newSgMsgs, StargateMsgDecoded{sgMsg.TypeURL, string(outBytes)}) | ||
} else if _, err := json.Marshal(value); err == nil { | ||
newSgMsgs = append(newSgMsgs, StargateMsgDecoded{sgMsg.TypeURL, string(sgMsg.Value)}) | ||
} else { | ||
return newSgMsgs, fmt.Errorf( | ||
"parse error: encountered wasmvm.StargateMsg with unexpected format: %s", sgMsg) | ||
} | ||
} | ||
return newSgMsgs, nil | ||
} | ||
|
||
// DecodeBase64Cmd creates a cobra command for base64 decoding. | ||
func DecodeBase64Cmd(defaultNodeHome string) *cobra.Command { | ||
cmd := &cobra.Command{ | ||
Use: "base64-decode", | ||
Short: "Decode a base64-encoded protobuf message", | ||
Long: `Decode a base64-encoded protobuf message from JSON input. | ||
The input should be a JSON object with 'type_url' and 'value' fields.`, | ||
RunE: func(cmd *cobra.Command, args []string) error { | ||
clientCtx := client.GetClientContextFromCmd(cmd) | ||
|
||
outMessage, err := DecodeBase64StargateMsgs([]byte(args[0]), clientCtx) | ||
fmt.Println(outMessage) | ||
|
||
return err | ||
}, | ||
} | ||
|
||
cmd.Flags().String(flags.FlagHome, defaultNodeHome, "The application home directory") | ||
|
||
return cmd | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
package cmd_test | ||
|
||
import ( | ||
"context" | ||
"testing" | ||
|
||
"github.com/NibiruChain/nibiru/app" | ||
|
||
"github.com/cometbft/cometbft/libs/log" | ||
"github.com/cosmos/cosmos-sdk/client" | ||
"github.com/cosmos/cosmos-sdk/server" | ||
genutiltest "github.com/cosmos/cosmos-sdk/x/genutil/client/testutil" | ||
"github.com/spf13/viper" | ||
"github.com/stretchr/testify/require" | ||
|
||
nibid "github.com/NibiruChain/nibiru/cmd/nibid/cmd" | ||
) | ||
|
||
func TestBase64Decode(t *testing.T) { | ||
type TestCase struct { | ||
name string | ||
json_message string | ||
expectError bool | ||
} | ||
|
||
executeTest := func(t *testing.T, testCase TestCase) { | ||
tc := testCase | ||
t.Run(tc.name, func(t *testing.T) { | ||
home := t.TempDir() | ||
logger := log.NewNopLogger() | ||
cfg, err := genutiltest.CreateDefaultTendermintConfig(home) | ||
require.NoError(t, err) | ||
|
||
appCodec := app.MakeEncodingConfig().Marshaler | ||
err = genutiltest.ExecInitCmd( | ||
testModuleBasicManager, home, appCodec) | ||
require.NoError(t, err) | ||
|
||
serverCtx := server.NewContext(viper.New(), cfg, logger) | ||
clientCtx := (client.Context{}. | ||
WithCodec(appCodec). | ||
WithHomeDir(home). | ||
WithInterfaceRegistry(app.MakeEncodingConfig().InterfaceRegistry)) | ||
|
||
ctx := context.Background() | ||
ctx = context.WithValue(ctx, client.ClientContextKey, &clientCtx) | ||
ctx = context.WithValue(ctx, server.ServerContextKey, serverCtx) | ||
|
||
cmd := nibid.DecodeBase64Cmd(home) | ||
cmd.SetArgs([]string{ | ||
tc.json_message, | ||
}) | ||
|
||
if tc.expectError { | ||
require.Error(t, cmd.ExecuteContext(ctx)) | ||
} else { | ||
require.NoError(t, cmd.ExecuteContext(ctx)) | ||
} | ||
}) | ||
} | ||
|
||
testCases := []TestCase{ | ||
{ | ||
name: "valid message", | ||
json_message: ` | ||
{ | ||
"stargate": { | ||
"type_url": "/cosmos.staking.v1beta1.MsgUndelegate", | ||
"value": "Cj9uaWJpMTdwOXJ6d25uZnhjanAzMnVuOXVnN3loaHpndGtodmw5amZrc3p0Z3c1dWg2OXdhYzJwZ3N5bjcwbmoSMm5pYml2YWxvcGVyMXdqNWtma25qa3BjNmpkMzByeHRtOHRweGZqZjd4cWx3eDM4YzdwGgwKBXVuaWJpEgMxMTE=" | ||
} | ||
}`, | ||
expectError: false, | ||
}, | ||
{ | ||
name: "valid message", | ||
json_message: ` | ||
{ | ||
"stargate": { | ||
"type_url": "/cosmos.staking.v1beta1.MsgUndelegate", | ||
"value": "Cj9uaWJpMTdwOXJ6d25uZnhjanAzMnVuOXVnN3loaHpndGtodmw5amZrc3p0Z3c1dWg2OXdhYzJwZ3N5bjcwbmoSMm5pYml2YWxvcGVyMXdqNWtma25qa3BjNmpkMzByeHRtOHRweGZqZjd4cWx3eDM4YzdwGgwKBXVuaWJpEgMxMTE=" | ||
}, | ||
"another": { | ||
"type_url": "/cosmos.staking.v1beta1.MsgDelegate", | ||
"value": {"delegator_address":"cosmos1eckjje8r8s48kv0pndgtwvehveedlzlnnshl3e", "validator_address":"cosmos1n6ndsc04xh2hqf506nhvhcggj0qwguf8ks06jj", "amount":{"denom":"unibi","amount":"42"} } | ||
} | ||
}`, | ||
expectError: false, | ||
}, | ||
{ | ||
name: "valid message", | ||
json_message: ` | ||
{ | ||
"another": { | ||
"type_url": "/cosmos.staking.v1beta1.MsgDelegate", | ||
"value": "{\"delegator_address\":\"cosmos1eckjje8r8s48kv0pndgtwvehveedlzlnnshl3e\", \"validator_address\":\"cosmos1n6ndsc04xh2hqf506nhvhcggj0qwguf8ks06jj\", \"amount\":{\"denom\":\"unibi\",\"amount\":\"42\"} }" | ||
} | ||
}`, | ||
expectError: false, | ||
}, | ||
{ | ||
name: "empty message", | ||
json_message: ` | ||
{ | ||
}`, | ||
expectError: false, | ||
}, | ||
{ | ||
name: "invalid json", | ||
json_message: ` | ||
}`, | ||
expectError: true, | ||
}, | ||
} | ||
|
||
for _, testCase := range testCases { | ||
executeTest(t, testCase) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters