diff --git a/Makefile b/Makefile index 9471789a..20118dd0 100644 --- a/Makefile +++ b/Makefile @@ -282,6 +282,9 @@ test-integration-xion-send-platform-fee: compile_integration_tests test-integration-xion-abstract-account: compile_integration_tests @XION_TEST_IMAGE=$(XION_TEST_IMAGE) ./integration_tests/integration_tests.test -test.failfast -test.v -test.run XionAbstractAccount +test-integration-xion-abstract-account-event: compile_integration_tests + @XION_TEST_IMAGE=$(XION_TEST_IMAGE) ./integration_tests/integration_tests.test -test.failfast -test.v -test.run XionClientEvent + test-integration-xion-min-default: compile_integration_tests @XION_TEST_IMAGE=$(XION_TEST_IMAGE) ./integration_tests/integration_tests.test -test.failfast -test.v -test.run TestXionMinimumFeeDefault diff --git a/integration_tests/abstract_account_test.go b/integration_tests/abstract_account_test.go index c6a2a6ac..fe3e3929 100644 --- a/integration_tests/abstract_account_test.go +++ b/integration_tests/abstract_account_test.go @@ -1,6 +1,7 @@ package integration_tests import ( + "context" "crypto/sha256" "encoding/base64" "encoding/hex" @@ -29,6 +30,9 @@ import ( "github.com/stretchr/testify/require" wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" + cometClient "github.com/cometbft/cometbft/rpc/client" + rpchttp "github.com/cometbft/cometbft/rpc/client/http" + cometRpcCoreTypes "github.com/cometbft/cometbft/rpc/core/types" "github.com/cosmos/cosmos-sdk/crypto/keyring" authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" aatypes "github.com/larry0x/abstract-account/x/abstractaccount/types" @@ -580,7 +584,7 @@ func TestXionAbstractAccount(t *testing.T) { aaContractAddr, path.Join(xion.GetNode().HomeDir(), removeFilePath[len(removeFilePath)-1]), "--chain-id", xion.Config().ChainID, - "--authenticator-id", "1", + "--nuthenticator-id", "1", ) require.NoError(t, err) @@ -617,3 +621,287 @@ func GetAAContractAddress(t *testing.T, txDetails map[string]interface{}) string return addr } + +func TestXionClientEvent(t *testing.T) { + if testing.Short() { + t.Skip("skipping in short mode") + } + + t.Parallel() + td := BuildXionChain(t, "0.0uxion", ModifyInterChainGenesis(ModifyInterChainGenesisFn{ModifyGenesisShortProposals}, [][]string{{votingPeriod, maxDepositPeriod}})) + xion, ctx := td.xionChain, td.ctx + + config := types.GetConfig() + config.SetBech32PrefixForAccount("xion", "xionpub") + + // Create and Fund User Wallets + t.Log("creating and funding user accounts") + fundAmount := math.NewInt(10_000_000) + xionUser, err := ibctest.GetAndFundTestUserWithMnemonic(ctx, "default", deployerMnemonic, fundAmount, xion) + require.NoError(t, err) + currentHeight, _ := xion.Height(ctx) + err = testutil.WaitForBlocks(ctx, int(currentHeight)+8, xion) + require.NoError(t, err) + t.Logf("created xion user %s", xionUser.FormattedAddress()) + + xionUserBalInitial, err := xion.GetBalance(ctx, xionUser.FormattedAddress(), xion.Config().Denom) + require.NoError(t, err) + require.Equal(t, fundAmount, xionUserBalInitial) + + // Create a Secondary Key For Rotation + recipientKeyName := "recipient-key" + err = xion.CreateKey(ctx, recipientKeyName) + require.NoError(t, err) + receipientKeyAddressBytes, err := xion.GetAddress(ctx, recipientKeyName) + require.NoError(t, err) + recipientKeyAddress, err := types.Bech32ifyAddressBytes(xion.Config().Bech32Prefix, receipientKeyAddressBytes) + require.NoError(t, err) + + // Get Public Key For Funded Account + account, err := ExecBin(t, ctx, xion.GetNode(), + "keys", "show", + xionUser.KeyName(), + "--keyring-backend", keyring.BackendTest, + "-p", + ) + require.NoError(t, err) + t.Log("Funded Account:") + for k, v := range account { + t.Logf("[%s]: %v", k, v) + } + + fp, err := os.Getwd() + require.NoError(t, err) + + // Store Wasm Contract + codeID, err := xion.StoreContract(ctx, xionUser.FormattedAddress(), path.Join(fp, + "integration_tests", "testdata", "contracts", "account-wasm-updatable-event-aarch64.wasm")) + require.NoError(t, err) + + // retrieve the hash + codeResp, err := ExecQuery(t, ctx, xion.GetNode(), + "wasm", "code-info", codeID) + require.NoError(t, err) + t.Logf("code response: %s", codeResp) + + depositedFunds := fmt.Sprintf("%d%s", 100000, xion.Config().Denom) + + registeredTxHash, err := ExecTx(t, ctx, xion.GetNode(), + xionUser.KeyName(), + "xion", "register", + codeID, + xionUser.KeyName(), + "--funds", depositedFunds, + "--salt", "0", + "--authenticator", "Secp256K1", + "--chain-id", xion.Config().ChainID, + ) + require.NoError(t, err) + t.Logf("tx hash: %s", registeredTxHash) + + txDetails, err := ExecQuery(t, ctx, xion.GetNode(), "tx", registeredTxHash) + require.NoError(t, err) + t.Logf("TxDetails: %s", txDetails) + aaContractAddr := GetAAContractAddress(t, txDetails) + + contractBalance, err := xion.GetBalance(ctx, aaContractAddr, xion.Config().Denom) + require.NoError(t, err) + require.Equal(t, math.NewInt(100000), contractBalance) + + contractState, err := ExecQuery(t, ctx, xion.GetNode(), "wasm", "contract-state", "smart", aaContractAddr, `{"authenticator_by_i_d":{ "id": 0 }}`) + require.NoError(t, err) + + pubkey64, ok := contractState["data"].(string) + require.True(t, ok) + pubkeyRawJSON, err := base64.StdEncoding.DecodeString(pubkey64) + require.NoError(t, err) + var pubKeyMap jsonAuthenticator + json.Unmarshal(pubkeyRawJSON, &pubKeyMap) + require.Equal(t, account["key"], pubKeyMap["Secp256K1"]["pubkey"]) + + // Generate Msg Send without signatures + jsonMsg := RawJSONMsgSend(t, aaContractAddr, recipientKeyAddress, xion.Config().Denom) + require.NoError(t, err) + require.True(t, json.Valid(jsonMsg)) + + sendFile, err := os.CreateTemp("", "*-msg-bank-send.json") + require.NoError(t, err) + defer os.Remove(sendFile.Name()) + + _, err = sendFile.Write(jsonMsg) + require.NoError(t, err) + + err = UploadFileToContainer(t, ctx, xion.GetNode(), sendFile) + require.NoError(t, err) + + // Sign and broadcast a transaction + sendFilePath := strings.Split(sendFile.Name(), "/") + _, err = ExecTx(t, ctx, xion.GetNode(), + xionUser.KeyName(), + "xion", "sign", + xionUser.KeyName(), + aaContractAddr, + path.Join(xion.GetNode().HomeDir(), sendFilePath[len(sendFilePath)-1]), + "--chain-id", xion.Config().ChainID, + ) + require.NoError(t, err) + + err = testutil.WaitForBlocks(ctx, 1, xion) + require.NoError(t, err) + + // Confirm the updated balance + balance, err := xion.GetBalance(ctx, recipientKeyAddress, xion.Config().Denom) + require.NoError(t, err) + require.Equal(t, math.NewInt(100000).Uint64(), balance.Uint64()) + + // Generate Key Rotation Msg + account, err = ExecBin(t, ctx, xion.GetNode(), + "keys", "show", + xionUser.KeyName(), + "--keyring-backend", keyring.BackendTest, + "-p", + ) + + // add secondary authenticator to account. in this case, the same key but in a different position + jsonExecMsgStr, err := GenerateTx(t, ctx, xion.GetNode(), + xionUser.KeyName(), + "xion", "add-authenticator", aaContractAddr, + "--authenticator-id", "1", + "--chain-id", xion.Config().ChainID, + ) + require.NoError(t, err) + jsonExecMsg := []byte(jsonExecMsgStr) + require.True(t, json.Valid(jsonExecMsg)) + + rotateFile, err := os.CreateTemp("", "*-msg-exec-rotate-key.json") + require.NoError(t, err) + defer os.Remove(rotateFile.Name()) + + _, err = rotateFile.Write(jsonExecMsg) + require.NoError(t, err) + + err = UploadFileToContainer(t, ctx, xion.GetNode(), rotateFile) + require.NoError(t, err) + + rotateFilePath := strings.Split(rotateFile.Name(), "/") + + _, err = ExecTx(t, ctx, xion.GetNode(), + xionUser.KeyName(), + "xion", "sign", + xionUser.KeyName(), + aaContractAddr, + path.Join(xion.GetNode().HomeDir(), rotateFilePath[len(rotateFilePath)-1]), + "--chain-id", xion.Config().ChainID, + ) + require.NoError(t, err) + updatedContractState, err := ExecQuery(t, ctx, xion.GetNode(), "wasm", "contract-state", "smart", aaContractAddr, `{"authenticator_by_i_d":{ "id": 1 }}`) + require.NoError(t, err) + + updatedPubKey, ok := updatedContractState["data"].(string) + require.True(t, ok) + + updatedPubKeyRawJSON, err := base64.StdEncoding.DecodeString(updatedPubKey) + require.NoError(t, err) + var updatedPubKeyMap jsonAuthenticator + + err = json.Unmarshal(updatedPubKeyRawJSON, &updatedPubKeyMap) + require.NoError(t, err) + require.Equal(t, account["key"], updatedPubKeyMap["Secp256K1"]["pubkey"]) + + cometWsClient, err := getCometClient(xion.GetHostRPCAddress()) // Note: add a close function + require.NoError(t, err) + fmt.Printf("%+v\n", xion.GetNode()) + + err = cometWsClient.Start() + require.NoError(t, err) + + //eventStream, err := subscribeToEvent(t, ctx, cometTypes.EventTx, cometWsClient) + eventStream, err := subscribeToEvent(t, ctx, cometWsClient) + + require.NoError(t, err) + + doneChan := make(chan struct{}) + go receiveEvents(t, doneChan, eventStream) + + jsonExecMsgStr, err = GenerateTx(t, ctx, xion.GetNode(), + xionUser.KeyName(), + "xion", "emit", "arbitrary_data", aaContractAddr, + "--authenticator-id", "0", + "--chain-id", xion.Config().ChainID, + ) + + require.NoError(t, err) + jsonExecMsg = []byte(jsonExecMsgStr) + require.True(t, json.Valid(jsonExecMsg)) + + rotateFile, err = os.CreateTemp("", "*-msg-exec-emit.json") + require.NoError(t, err) + defer os.Remove(rotateFile.Name()) + + _, err = rotateFile.Write(jsonExecMsg) + require.NoError(t, err) + + err = UploadFileToContainer(t, ctx, xion.GetNode(), rotateFile) + require.NoError(t, err) + + rotateFilePath = strings.Split(rotateFile.Name(), "/") + + _, err = ExecTx(t, ctx, xion.GetNode(), + xionUser.KeyName(), + "xion", "sign", + xionUser.KeyName(), + aaContractAddr, + path.Join(xion.GetNode().HomeDir(), rotateFilePath[len(rotateFilePath)-1]), + "--chain-id", xion.Config().ChainID, + ) + require.NoError(t, err) // it's returning an error and it's not throwing + fmt.Println("we have thrown a transaction") + + //wg.Wait() + <-doneChan + stopClient(ctx, cometWsClient) +} + +func getCometClient(hostAddr string) (cometClient.Client, error) { + return rpchttp.New(hostAddr, "/websocket") +} + +func subscribeToEvent(t *testing.T, ctx context.Context, cli cometClient.Client) (<-chan cometRpcCoreTypes.ResultEvent, error) { + return cli.Subscribe(ctx, "helpers", "message.module='wasm' AND message.action='/cosmwasm.wasm.v1.MsgExecuteContract'") +} + +func receiveEvents(t *testing.T, done chan<- struct{}, eventStream <-chan cometRpcCoreTypes.ResultEvent) { + for { + select { + case event := <-eventStream: + fmt.Println("event intercepted") + contractAddress, ok := event.Events["wasm-account_emit._contract_address"] + if !ok { + fmt.Println("not desired event") + continue + } + arbData, ok := event.Events["wasm-account_emit.data"] + if !ok { + fmt.Println("not desired event") + continue + } + fmt.Println(contractAddress) + fmt.Println(arbData) + done <- struct{}{} + return + default: + continue + } + } +} + +func stopClient(ctx context.Context, cli cometClient.Client) error { + if err := cli.UnsubscribeAll(ctx, "helpers"); err != nil { + return err + } + + if err := cli.Stop(); err != nil { + return err + } + return nil +} diff --git a/integration_tests/testdata/contracts/account-wasm-updatable-event-aarch64.wasm b/integration_tests/testdata/contracts/account-wasm-updatable-event-aarch64.wasm new file mode 100644 index 00000000..62eaac46 Binary files /dev/null and b/integration_tests/testdata/contracts/account-wasm-updatable-event-aarch64.wasm differ diff --git a/x/xion/client/cli/tx.go b/x/xion/client/cli/tx.go index d0f1370a..83297818 100644 --- a/x/xion/client/cli/tx.go +++ b/x/xion/client/cli/tx.go @@ -59,6 +59,7 @@ func NewTxCmd() *cobra.Command { NewSignCmd(), NewAddAuthenticatorCmd(), NewRegisterCmd(), + NewEmitArbitraryDataCmd(), ) return txCmd @@ -514,6 +515,58 @@ func NewSignCmd() *cobra.Command { return cmd } +// NewEmitArbitraryDataCmd returns a CLI command handler for emitting some arbitrary data from the chain. +func NewEmitArbitraryDataCmd() *cobra.Command { + cmd := &cobra.Command{ + Use: "emit --authenticator-id [uint8]", + Short: "Emit an arbitrary data from the chain", + Long: `Sends an arbitrary data to the contract's emit endpoint. The contract emits the arbitrary contract on-chain.`, + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + arbitraryData := args[0] + + contractAddr := args[1] + + data := map[string]interface{}{} + data["data"] = arbitraryData + msg := map[string]interface{}{} + msg["emit"] = data + + jsonMsg, err := json.Marshal(msg) + if err != nil { + return err + } + + rawMsg := wasmtypes.RawContractMessage{} + err = json.Unmarshal(jsonMsg, &rawMsg) + if err != nil { + return err + } + + wasmMsg := &wasmtypes.MsgExecuteContract{ + Sender: contractAddr, + Contract: contractAddr, + Msg: rawMsg, + Funds: nil, + } + if err := wasmMsg.ValidateBasic(); err != nil { + return err + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), wasmMsg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + cmd.Flags().Uint8(flagAuthenticatorID, 0, "Authenticator index locator") + + return cmd +} + func getSignerOfTx(queryClient authtypes.QueryClient, address sdk.AccAddress) (*aatypes.AbstractAccount, error) { res, err := queryClient.Account(context.Background(), &authtypes.QueryAccountRequest{Address: address.String()}) if err != nil {