diff --git a/chain/cosmos/chain_node.go b/chain/cosmos/chain_node.go index a0a83592b..9f38b59b1 100644 --- a/chain/cosmos/chain_node.go +++ b/chain/cosmos/chain_node.go @@ -20,6 +20,7 @@ import ( "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/crypto/keyring" "github.com/cosmos/cosmos-sdk/types" + paramsutils "github.com/cosmos/cosmos-sdk/x/params/client/utils" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" dockertypes "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" @@ -145,8 +146,8 @@ func (tn *ChainNode) genesisFileContent(ctx context.Context) ([]byte, error) { } func (tn *ChainNode) overwriteGenesisFile(ctx context.Context, content []byte) error { - fw := dockerutil.NewFileWriter(tn.logger(), tn.DockerClient, tn.TestName) - if err := fw.WriteFile(ctx, tn.VolumeName, "config/genesis.json", content); err != nil { + err := tn.WriteFile(ctx, content, "config/genesis.json") + if err != nil { return fmt.Errorf("overwriting genesis.json: %w", err) } @@ -167,8 +168,8 @@ func (tn *ChainNode) copyGentx(ctx context.Context, destVal *ChainNode) error { return fmt.Errorf("getting gentx content: %w", err) } - fw := dockerutil.NewFileWriter(destVal.logger(), destVal.DockerClient, destVal.TestName) - if err := fw.WriteFile(ctx, destVal.VolumeName, relPath, gentx); err != nil { + err = destVal.WriteFile(ctx, gentx, relPath) + if err != nil { return fmt.Errorf("overwriting gentx: %w", err) } @@ -502,6 +503,25 @@ func (tn *ChainNode) InitHomeFolder(ctx context.Context) error { return err } +// WriteFile accepts file contents in a byte slice and writes the contents to +// the docker filesystem. relPath describes the location of the file in the +// docker volume relative to the home directory +func (tn *ChainNode) WriteFile(ctx context.Context, content []byte, relPath string) error { + fw := dockerutil.NewFileWriter(tn.logger(), tn.DockerClient, tn.TestName) + return fw.WriteFile(ctx, tn.VolumeName, relPath, content) +} + +// CopyFile adds a file from the host filesystem to the docker filesystem +// relPath describes the location of the file in the docker volume relative to +// the home directory +func (tn *ChainNode) CopyFile(ctx context.Context, srcPath, dstPath string) error { + content, err := os.ReadFile(srcPath) + if err != nil { + return err + } + return tn.WriteFile(ctx, content, dstPath) +} + // CreateKey creates a key in the keyring backend test for the given node func (tn *ChainNode) CreateKey(ctx context.Context, name string) error { tn.lock.Lock() @@ -645,14 +665,9 @@ type CodeInfosResponse struct { // StoreContract takes a file path to smart contract and stores it on-chain. Returns the contracts code id. func (tn *ChainNode) StoreContract(ctx context.Context, keyName string, fileName string) (string, error) { - content, err := os.ReadFile(fileName) - if err != nil { - return "", err - } - _, file := filepath.Split(fileName) - fw := dockerutil.NewFileWriter(tn.logger(), tn.DockerClient, tn.TestName) - if err := fw.WriteFile(ctx, tn.VolumeName, file, content); err != nil { + err := tn.CopyFile(ctx, fileName, file) + if err != nil { return "", fmt.Errorf("writing contract file to docker volume: %w", err) } @@ -728,13 +743,9 @@ func (tn *ChainNode) QueryContract(ctx context.Context, contractAddress string, // StoreClientContract takes a file path to a client smart contract and stores it on-chain. Returns the contracts code id. func (tn *ChainNode) StoreClientContract(ctx context.Context, keyName string, fileName string) (string, error) { content, err := os.ReadFile(fileName) - if err != nil { - return "", err - } - _, file := filepath.Split(fileName) - fw := dockerutil.NewFileWriter(tn.logger(), tn.DockerClient, tn.TestName) - if err := fw.WriteFile(ctx, tn.VolumeName, file, content); err != nil { + err = tn.WriteFile(ctx, content, file) + if err != nil { return "", fmt.Errorf("writing contract file to docker volume: %w", err) } @@ -816,6 +827,31 @@ func (tn *ChainNode) TextProposal(ctx context.Context, keyName string, prop Text return tn.ExecTx(ctx, keyName, command...) } +// ParamChangeProposal submits a param change proposal to the chain, signed by keyName. +func (tn *ChainNode) ParamChangeProposal(ctx context.Context, keyName string, prop *paramsutils.ParamChangeProposalJSON) (string, error) { + content, err := json.Marshal(prop) + if err != nil { + return "", err + } + + hash := sha256.Sum256(content) + proposalFilename := fmt.Sprintf("%x.json", hash) + err = tn.WriteFile(ctx, content, proposalFilename) + if err != nil { + return "", fmt.Errorf("writing param change proposal: %w", err) + } + + proposalPath := filepath.Join(tn.HomeDir(), proposalFilename) + + command := []string{ + "gov", "submit-proposal", + "param-change", + proposalPath, + } + + return tn.ExecTx(ctx, keyName, command...) +} + // DumpContractState dumps the state of a contract at a block height. func (tn *ChainNode) DumpContractState(ctx context.Context, contractAddress string, height int64) (*DumpContractStateResponse, error) { stdout, _, err := tn.ExecQuery(ctx, diff --git a/chain/cosmos/cosmos_chain.go b/chain/cosmos/cosmos_chain.go index edfd99bd8..808929c5a 100644 --- a/chain/cosmos/cosmos_chain.go +++ b/chain/cosmos/cosmos_chain.go @@ -20,6 +20,7 @@ import ( "github.com/cosmos/cosmos-sdk/types" authTx "github.com/cosmos/cosmos-sdk/x/auth/tx" bankTypes "github.com/cosmos/cosmos-sdk/x/bank/types" + paramsutils "github.com/cosmos/cosmos-sdk/x/params/client/utils" chanTypes "github.com/cosmos/ibc-go/v6/modules/core/04-channel/types" dockertypes "github.com/docker/docker/api/types" volumetypes "github.com/docker/docker/api/types/volume" @@ -304,6 +305,9 @@ func (c *CosmosChain) SendIBCTransfer( if err != nil { return tx, fmt.Errorf("failed to get transaction %s: %w", txHash, err) } + if txResp.Code != 0 { + return tx, fmt.Errorf("error in transaction (code: %d): %s", txResp.Code, txResp.RawLog) + } tx.Height = uint64(txResp.Height) tx.TxHash = txHash // In cosmos, user is charged for entire gas requested, not the actual gas used. @@ -367,6 +371,16 @@ func (c *CosmosChain) TextProposal(ctx context.Context, keyName string, prop Tex return c.txProposal(txHash) } +// ParamChangeProposal submits a param change proposal to the chain, signed by keyName. +func (c *CosmosChain) ParamChangeProposal(ctx context.Context, keyName string, prop *paramsutils.ParamChangeProposalJSON) (tx TxProposal, _ error) { + txHash, err := c.getFullNode().ParamChangeProposal(ctx, keyName, prop) + if err != nil { + return tx, fmt.Errorf("failed to submit param change proposal: %w", err) + } + + return c.txProposal(txHash) +} + func (c *CosmosChain) txProposal(txHash string) (tx TxProposal, _ error) { txResp, err := c.getTransaction(txHash) if err != nil { @@ -449,6 +463,26 @@ func (c *CosmosChain) GetBalance(ctx context.Context, address string, denom stri return res.Balance.Amount.Int64(), nil } +// AllBalances fetches an account address's balance for all denoms it holds +func (c *CosmosChain) AllBalances(ctx context.Context, address string) (types.Coins, error) { + params := bankTypes.QueryAllBalancesRequest{Address: address} + grpcAddress := c.getFullNode().hostGRPCPort + conn, err := grpc.Dial(grpcAddress, grpc.WithTransportCredentials(insecure.NewCredentials())) + if err != nil { + return nil, err + } + defer conn.Close() + + queryClient := bankTypes.NewQueryClient(conn) + res, err := queryClient.AllBalances(ctx, ¶ms) + + if err != nil { + return nil, err + } + + return res.GetBalances(), nil +} + func (c *CosmosChain) getTransaction(txHash string) (*types.TxResponse, error) { // Retry because sometimes the tx is not committed to state yet. var txResp *types.TxResponse