From da3f9c2c31d4b47a5873d4cd7b202bbfdaac5052 Mon Sep 17 00:00:00 2001 From: Andrew Gouin Date: Tue, 5 Apr 2022 00:05:50 -0600 Subject: [PATCH] add contract Chain interface methods. implement for wasm. stub out wasm tests --- cmd/test.go | 8 +-- ibc/Chain.go | 12 ++++ ibc/cosmos_chain.go | 20 ++++++ ibc/test_node.go | 160 ++++++++++++++++++++++++++++++++++++++++++++ ibc/test_relay.go | 27 -------- ibc/test_setup.go | 28 ++++++++ ibc/test_wasm.go | 9 +++ 7 files changed, 233 insertions(+), 31 deletions(-) create mode 100644 ibc/test_wasm.go diff --git a/cmd/test.go b/cmd/test.go index a716d16ca..50a35aad7 100644 --- a/cmd/test.go +++ b/cmd/test.go @@ -61,10 +61,10 @@ e.g. ibc-test-framework test # Specify specific chains/versions, relayer implementation, and test cases -ibc-test-framework test --source osmosis:v7.0.4 --destination juno:v2.3.0 --relayer rly RelayPacketTest,RelayPacketTestHeightTimeout +ibc-test-framework test --src osmosis:v7.0.4 --dst juno:v2.3.0 --relayer rly RelayPacketTest,RelayPacketTestHeightTimeout # Shorthand flags -ibc-test-framework test -src osmosis:v7.0.4 -dst juno:v2.3.0 -r rly RelayPacketTest +ibc-test-framework test -s osmosis:v7.0.4 -d juno:v2.3.0 -r rly RelayPacketTest `, Run: func(cmd *cobra.Command, args []string) { fmt.Println("IBC Test Framework") @@ -101,7 +101,7 @@ ibc-test-framework test -src osmosis:v7.0.4 -dst juno:v2.3.0 -r rly RelayPacketT var eg errgroup.Group for i, testCase := range testCases { testCase := testCase - testName := fmt.Sprintf("RelayTest%d", i) + testName := fmt.Sprintf("Test%d", i) eg.Go(func() error { return runTestCase(testName, testCase, relayerImplementation, srcChainName, srcChainVersion, srcChainID, srcVals, dstChainName, dstChainVersion, dstChainID, dstVals) }) @@ -111,7 +111,7 @@ ibc-test-framework test -src osmosis:v7.0.4 -dst juno:v2.3.0 -r rly RelayPacketT } } else { for i, testCase := range testCases { - testName := fmt.Sprintf("RelayTest%d", i) + testName := fmt.Sprintf("Test%d", i) if err := runTestCase(testName, testCase, relayerImplementation, srcChainName, srcChainVersion, srcChainID, srcVals, dstChainName, dstChainVersion, dstChainID, dstVals); err != nil { panic(err) } diff --git a/ibc/Chain.go b/ibc/Chain.go index 09329e830..a4d7dba3e 100644 --- a/ibc/Chain.go +++ b/ibc/Chain.go @@ -54,10 +54,22 @@ type Chain interface { // fetches the bech32 address for a test key on the "user" node (either the first fullnode or the first validator if no fullnodes) GetAddress(keyName string) ([]byte, error) + // send funds to wallet from user account + SendFunds(ctx context.Context, keyName string, amount WalletAmount) error + // sends an IBC transfer from a test key on the "user" node (either the first fullnode or the first validator if no fullnodes) // returns tx hash SendIBCTransfer(ctx context.Context, channelID, keyName string, amount WalletAmount, timeout *IBCTimeout) (string, error) + // takes file path to smart contract and initialization message. returns contract address + InstantiateContract(ctx context.Context, keyName string, amount WalletAmount, fileName, initMessage string) (string, error) + + // executes a contract transaction with a message using it's address + ExecuteContract(ctx context.Context, keyName string, contractAddress string, message string) error + + // create balancer pool + CreatePool(ctx context.Context, keyName string, contractAddress string, swapFee float64, exitFee float64, assets []WalletAmount) error + // waits for # of blocks to be produced WaitForBlocks(number int64) error diff --git a/ibc/cosmos_chain.go b/ibc/cosmos_chain.go index 2a6122c16..1be041d88 100644 --- a/ibc/cosmos_chain.go +++ b/ibc/cosmos_chain.go @@ -100,11 +100,31 @@ func (c *CosmosChain) GetAddress(keyName string) ([]byte, error) { return keyInfo.GetAddress().Bytes(), nil } +// Implements Chain interface +func (c *CosmosChain) SendFunds(ctx context.Context, keyName string, amount WalletAmount) error { + return c.getRelayerNode().SendFunds(ctx, keyName, amount) +} + // Implements Chain interface func (c *CosmosChain) SendIBCTransfer(ctx context.Context, channelID, keyName string, amount WalletAmount, timeout *IBCTimeout) (string, error) { return c.getRelayerNode().SendIBCTransfer(ctx, channelID, keyName, amount, timeout) } +// Implements Chain interface +func (c *CosmosChain) InstantiateContract(ctx context.Context, keyName string, amount WalletAmount, fileName, initMessage string) (string, error) { + return c.getRelayerNode().InstantiateContract(ctx, keyName, amount, fileName, initMessage) +} + +// Implements Chain interface +func (c *CosmosChain) ExecuteContract(ctx context.Context, keyName string, contractAddress string, message string) error { + return c.getRelayerNode().ExecuteContract(ctx, keyName, contractAddress, message) +} + +// Implements Chain interface +func (c *CosmosChain) CreatePool(ctx context.Context, keyName string, contractAddress string, swapFee float64, exitFee float64, assets []WalletAmount) error { + return c.getRelayerNode().CreatePool(ctx, keyName, contractAddress, swapFee, exitFee, assets) +} + // Implements Chain interface func (c *CosmosChain) WaitForBlocks(number int64) error { return c.getRelayerNode().WaitForBlocks(number) diff --git a/ibc/test_node.go b/ibc/test_node.go index 3a6e62ce2..3bf6ccdae 100644 --- a/ibc/test_node.go +++ b/ibc/test_node.go @@ -7,9 +7,11 @@ import ( "encoding/json" "errors" "fmt" + "io" "io/ioutil" "os" "path" + "path/filepath" "runtime" "strings" "time" @@ -312,6 +314,164 @@ func (tn *ChainNode) SendIBCTransfer(ctx context.Context, channelID string, keyN return output.TxHash, nil } +func (tn *ChainNode) SendFunds(ctx context.Context, keyName string, amount WalletAmount) error { + command := []string{tn.Chain.Config().Bin, "tx", "bank", "send", keyName, + amount.Address, fmt.Sprintf("%d%s", amount.Amount, amount.Denom), + "--keyring-backend", keyring.BackendTest, + "--node", fmt.Sprintf("tcp://%s:26657", tn.Name()), + "--output", "json", + "-y", + "--home", tn.NodeHome(), + "--chain-id", tn.Chain.Config().ChainID, + } + + return handleNodeJobError(tn.NodeJob(ctx, command)) +} + +func copy(src, dst string) (int64, error) { + sourceFileStat, err := os.Stat(src) + if err != nil { + return 0, err + } + + if !sourceFileStat.Mode().IsRegular() { + return 0, fmt.Errorf("%s is not a regular file", src) + } + + source, err := os.Open(src) + if err != nil { + return 0, err + } + defer source.Close() + + destination, err := os.Create(dst) + if err != nil { + return 0, err + } + defer destination.Close() + nBytes, err := io.Copy(destination, source) + return nBytes, err +} + +type InstantiateContractAttribute struct { + Value string `json:"value"` +} + +type InstantiateContractEvent struct { + Attributes []InstantiateContractAttribute `json:"attributes"` +} + +type InstantiateContractLog struct { + Events []InstantiateContractEvent `json:"event"` +} + +type InstantiateContractResponse struct { + Logs []InstantiateContractLog `json:"log"` +} + +type QueryContractResponse struct { + Contracts []string `json:"contracts"` +} + +func (tn *ChainNode) InstantiateContract(ctx context.Context, keyName string, amount WalletAmount, fileName, initMessage string) (string, error) { + _, file := filepath.Split(fileName) + newFilePath := path.Join(tn.Dir(), file) + newFilePathContainer := path.Join(tn.NodeHome(), file) + if _, err := copy(fileName, newFilePath); err != nil { + return "", err + } + command := []string{tn.Chain.Config().Bin, "tx", "wasm", "store", newFilePathContainer, + "--from", keyName, + "--amount", fmt.Sprintf("%d%s", amount.Amount, amount.Denom), + "--keyring-backend", keyring.BackendTest, + "--node", fmt.Sprintf("tcp://%s:26657", tn.Name()), + "--output", "json", + "-y", + "--home", tn.NodeHome(), + "--chain-id", tn.Chain.Config().ChainID, + } + + exitCode, stdout, stderr, err := tn.NodeJob(ctx, command) + if err != nil { + return "", handleNodeJobError(exitCode, stdout, stderr, err) + } + + res := InstantiateContractResponse{} + if err := json.Unmarshal([]byte(stdout), &res); err != nil { + return "", err + } + attributes := res.Logs[0].Events[0].Attributes + codeID := attributes[len(attributes)-1].Value + + command = []string{tn.Chain.Config().Bin, + "tx", "wasm", "instantiate", codeID, initMessage, + "--from", keyName, + "--keyring-backend", keyring.BackendTest, + "--node", fmt.Sprintf("tcp://%s:26657", tn.Name()), + "--output", "json", + "-y", + "--home", tn.NodeHome(), + "--chain-id", tn.Chain.Config().ChainID, + } + + exitCode, stdout, stderr, err = tn.NodeJob(ctx, command) + if err != nil { + return "", handleNodeJobError(exitCode, stdout, stderr, err) + } + + command = []string{tn.Chain.Config().Bin, + "query", "wasm", "list-contract-by-code", codeID, + "--node", fmt.Sprintf("tcp://%s:26657", tn.Name()), + "--output", "json", + "--home", tn.NodeHome(), + "--chain-id", tn.Chain.Config().ChainID, + } + + exitCode, stdout, stderr, err = tn.NodeJob(ctx, command) + if err != nil { + return "", handleNodeJobError(exitCode, stdout, stderr, err) + } + + contactsRes := QueryContractResponse{} + if err := json.Unmarshal([]byte(stdout), &contactsRes); err != nil { + return "", err + } + + contractAddress := contactsRes.Contracts[len(contactsRes.Contracts)-1] + return contractAddress, nil +} + +func (tn *ChainNode) ExecuteContract(ctx context.Context, keyName string, contractAddress string, message string) error { + command := []string{tn.Chain.Config().Bin, + "tx", "wasm", "execute", contractAddress, message, + "--from", keyName, + "--keyring-backend", keyring.BackendTest, + "--node", fmt.Sprintf("tcp://%s:26657", tn.Name()), + "--output", "json", + "-y", + "--home", tn.NodeHome(), + "--chain-id", tn.Chain.Config().ChainID, + } + return handleNodeJobError(tn.NodeJob(ctx, command)) +} + +func (tn *ChainNode) CreatePool(ctx context.Context, keyName string, contractAddress string, swapFee float64, exitFee float64, assets []WalletAmount) error { + // TODO generate --pool-file + poolFilePath := "TODO" + command := []string{tn.Chain.Config().Bin, + "tx", "gamm", "create-pool", + "--pool-file", poolFilePath, + "--from", keyName, + "--keyring-backend", keyring.BackendTest, + "--node", fmt.Sprintf("tcp://%s:26657", tn.Name()), + "--output", "json", + "-y", + "--home", tn.NodeHome(), + "--chain-id", tn.Chain.Config().ChainID, + } + return handleNodeJobError(tn.NodeJob(ctx, command)) +} + func (tn *ChainNode) CreateNodeContainer() error { chainCfg := tn.Chain.Config() cmd := []string{chainCfg.Bin, "start", "--home", tn.NodeHome()} diff --git a/ibc/test_relay.go b/ibc/test_relay.go index 9963dda5e..dac828158 100644 --- a/ibc/test_relay.go +++ b/ibc/test_relay.go @@ -1,39 +1,12 @@ package ibc import ( - "errors" "fmt" - "reflect" "time" transfertypes "github.com/cosmos/ibc-go/v3/modules/apps/transfer/types" ) -// all methods on this struct have the same signature and are method names that will be called by the CLI -type IBCTestCase struct{} - -// uses reflection to get test case -func GetTestCase(testCase string) (func(testName string, srcChain Chain, dstChain Chain, relayerImplementation RelayerImplementation) error, error) { - v := reflect.ValueOf(IBCTestCase{}) - m := v.MethodByName(testCase) - if m.Kind() != reflect.Func { - return nil, fmt.Errorf("invalid test case: %s", testCase) - } - - testCaseFunc := func(testName string, srcChain Chain, dstChain Chain, relayerImplementation RelayerImplementation) error { - args := []reflect.Value{reflect.ValueOf(testName), reflect.ValueOf(srcChain), reflect.ValueOf(dstChain), reflect.ValueOf(relayerImplementation)} - result := m.Call(args) - if len(result) != 1 || !result[0].CanInterface() { - return errors.New("error reflecting error return var") - } - - err, _ := result[0].Interface().(error) - return err - } - - return testCaseFunc, nil -} - func (ibc IBCTestCase) RelayPacketTest(testName string, srcChain Chain, dstChain Chain, relayerImplementation RelayerImplementation) error { ctx, home, pool, network, cleanup, err := SetupTestRun(testName) if err != nil { diff --git a/ibc/test_setup.go b/ibc/test_setup.go index 7f54790d4..084d742a2 100644 --- a/ibc/test_setup.go +++ b/ibc/test_setup.go @@ -4,11 +4,13 @@ import ( "bytes" "context" "crypto/rand" + "errors" "fmt" "io/ioutil" "math/big" "net" "os" + "reflect" "strings" "time" @@ -32,6 +34,32 @@ const ( testPathName = "test-path" ) +// all methods on this struct have the same signature and are method names that will be called by the CLI +// func (ibc IBCTestCase) TestCaseName(testName string, srcChain Chain, dstChain Chain, relayerImplementation RelayerImplementation) error +type IBCTestCase struct{} + +// uses reflection to get test case +func GetTestCase(testCase string) (func(testName string, srcChain Chain, dstChain Chain, relayerImplementation RelayerImplementation) error, error) { + v := reflect.ValueOf(IBCTestCase{}) + m := v.MethodByName(testCase) + if m.Kind() != reflect.Func { + return nil, fmt.Errorf("invalid test case: %s", testCase) + } + + testCaseFunc := func(testName string, srcChain Chain, dstChain Chain, relayerImplementation RelayerImplementation) error { + args := []reflect.Value{reflect.ValueOf(testName), reflect.ValueOf(srcChain), reflect.ValueOf(dstChain), reflect.ValueOf(relayerImplementation)} + result := m.Call(args) + if len(result) != 1 || !result[0].CanInterface() { + return errors.New("error reflecting error return var") + } + + err, _ := result[0].Interface().(error) + return err + } + + return testCaseFunc, nil +} + // RandLowerCaseLetterString returns a lowercase letter string of given length func RandLowerCaseLetterString(length int) string { chars := []rune("abcdefghijklmnopqrstuvwxyz") diff --git a/ibc/test_wasm.go b/ibc/test_wasm.go new file mode 100644 index 000000000..0c310e79e --- /dev/null +++ b/ibc/test_wasm.go @@ -0,0 +1,9 @@ +package ibc + +func (ibc IBCTestCase) CW20IBCTest(testName string, srcChain Chain, dstChain Chain, relayerImplementation RelayerImplementation) error { + +} + +func (ibc IBCTestCase) IBCReflectTest(testName string, srcChain Chain, dstChain Chain, relayerImplementation RelayerImplementation) error { + +}