diff --git a/ioctl/newcmd/contract/contract.go b/ioctl/newcmd/contract/contract.go index 8fc9e2082c..d0b824d589 100644 --- a/ioctl/newcmd/contract/contract.go +++ b/ioctl/newcmd/contract/contract.go @@ -6,6 +6,8 @@ package contract import ( + "encoding/hex" + "github.com/ethereum/go-ethereum/common/compiler" "github.com/pkg/errors" "github.com/spf13/cobra" @@ -73,3 +75,7 @@ func checkCompilerVersion(solc *util.Solidity) bool { } return false } + +func decodeBytecode(bytecode string) ([]byte, error) { + return hex.DecodeString(util.TrimHexPrefix(bytecode)) +} diff --git a/ioctl/newcmd/contract/contracttestbytecode.go b/ioctl/newcmd/contract/contracttestbytecode.go new file mode 100644 index 0000000000..cec3e24e7d --- /dev/null +++ b/ioctl/newcmd/contract/contracttestbytecode.go @@ -0,0 +1,82 @@ +// Copyright (c) 2022 IoTeX Foundation +// This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability +// or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed. +// This source code is governed by Apache License 2.0 that can be found in the LICENSE file. + +package contract + +import ( + "math/big" + + "github.com/iotexproject/iotex-address/address" + "github.com/pkg/errors" + "github.com/spf13/cobra" + + "github.com/iotexproject/iotex-core/ioctl" + "github.com/iotexproject/iotex-core/ioctl/config" + "github.com/iotexproject/iotex-core/ioctl/newcmd/action" + "github.com/iotexproject/iotex-core/ioctl/util" +) + +// Multi-language support +var ( + _testBytecodeCmdUses = map[config.Language]string{ + config.English: "bytecode (CONTRACT_ADDRESS|ALIAS) PACKED_ARGUMENTS [AMOUNT_IOTX]", + config.Chinese: "bytecode (合约地址|别名) 已打包参数 [IOTX数量]", + } + _testBytecodeCmdShorts = map[config.Language]string{ + config.English: "test smart contract on IoTeX blockchain with packed arguments", + config.Chinese: "传入bytecode测试IoTeX区块链上的智能合约", + } +) + +// NewContractTestBytecodeCmd represents the contract test bytecode command +func NewContractTestBytecodeCmd(client ioctl.Client) *cobra.Command { + use, _ := client.SelectTranslation(_testBytecodeCmdUses) + short, _ := client.SelectTranslation(_testBytecodeCmdShorts) + + cmd := &cobra.Command{ + Use: use, + Short: short, + Args: cobra.RangeArgs(2, 3), + RunE: func(cmd *cobra.Command, args []string) error { + cmd.SilenceUsage = true + addr, err := client.Address(args[0]) + if err != nil { + return errors.Wrap(err, "failed to get contract address") + } + + contract, err := address.FromString(addr) + if err != nil { + return errors.Wrap(err, "failed to convert string into address") + } + + bytecode, err := decodeBytecode(args[1]) + if err != nil { + return errors.Wrap(err, "invalid bytecode") + } + + amount := big.NewInt(0) + if len(args) == 3 { + amount, err = util.StringToRau(args[2], util.IotxDecimalNum) + if err != nil { + return errors.Wrap(err, "invalid amount") + } + } + + _, signer, _, _, gasLimit, _, err := action.GetWriteCommandFlag(cmd) + if err != nil { + return err + } + result, err := action.Read(client, contract, amount.String(), bytecode, signer, gasLimit) + if err != nil { + return errors.Wrap(err, "failed to read contract") + } + + cmd.Printf("return: %s\n", result) + return nil + }, + } + action.RegisterWriteCommand(client, cmd) + return cmd +} diff --git a/ioctl/newcmd/contract/contracttestbytecode_test.go b/ioctl/newcmd/contract/contracttestbytecode_test.go new file mode 100644 index 0000000000..e7fe9108a9 --- /dev/null +++ b/ioctl/newcmd/contract/contracttestbytecode_test.go @@ -0,0 +1,97 @@ +// Copyright (c) 2022 IoTeX Foundation +// This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability +// or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed. +// This source code is governed by Apache License 2.0 that can be found in the LICENSE file. + +package contract + +import ( + "encoding/hex" + "testing" + + "github.com/ethereum/go-ethereum/accounts/keystore" + "github.com/golang/mock/gomock" + "github.com/pkg/errors" + "github.com/stretchr/testify/require" + + "github.com/iotexproject/iotex-address/address" + "github.com/iotexproject/iotex-proto/golang/iotexapi" + "github.com/iotexproject/iotex-proto/golang/iotexapi/mock_iotexapi" + + "github.com/iotexproject/iotex-core/ioctl/config" + "github.com/iotexproject/iotex-core/ioctl/util" + "github.com/iotexproject/iotex-core/test/identityset" + "github.com/iotexproject/iotex-core/test/mock/mock_ioctlclient" +) + +func TestNewContractTestBytecodeCmd(t *testing.T) { + require := require.New(t) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + client := mock_ioctlclient.NewMockClient(ctrl) + apiServiceClient := mock_iotexapi.NewMockAPIServiceClient(ctrl) + addr := identityset.Address(0).String() + + ks := keystore.NewKeyStore(t.TempDir(), 2, 1) + acc, err := ks.NewAccount("") + require.NoError(err) + accAddr, err := address.FromBytes(acc.Address.Bytes()) + require.NoError(err) + + client.EXPECT().SelectTranslation(gomock.Any()).Return("contract", config.English).Times(40) + client.EXPECT().SetEndpointWithFlag(gomock.Any()).AnyTimes() + client.EXPECT().SetInsecureWithFlag(gomock.Any()).AnyTimes() + client.EXPECT().Alias(gomock.Any()).Return("producer", nil).AnyTimes() + client.EXPECT().APIServiceClient().Return(apiServiceClient, nil).AnyTimes() + client.EXPECT().IsCryptoSm2().Return(false).AnyTimes() + client.EXPECT().ReadSecret().Return("", nil).AnyTimes() + client.EXPECT().Address(gomock.Any()).Return(accAddr.String(), nil).Times(4) + client.EXPECT().AddressWithDefaultIfNotExist(gomock.Any()).Return(accAddr.String(), nil).AnyTimes() + client.EXPECT().NewKeyStore().Return(ks).AnyTimes() + client.EXPECT().AskToConfirm(gomock.Any()).Return(true, nil).AnyTimes() + client.EXPECT().Config().Return(config.Config{ + Explorer: "iotexscan", + Endpoint: "testnet1", + }).AnyTimes() + + apiServiceClient.EXPECT().ReadContract(gomock.Any(), gomock.Any()).Return(&iotexapi.ReadContractResponse{ + Data: hex.EncodeToString([]byte("60fe47b100000000000000000000000000000000000000000000000000000000")), + }, nil) + + t.Run("compile contract", func(t *testing.T) { + cmd := NewContractTestBytecodeCmd(client) + result, err := util.ExecuteCmd(cmd, addr, "a9059cbb0000000000000000000000004867c4bada9553216bf296c4c64e9ff0749206490000000000000000000000000000000000000000000000000000000000000001") + require.NoError(err) + require.Contains(result, "return") + }) + + t.Run("failed to read contract", func(t *testing.T) { + expectedErr := errors.New("failed to read contract") + apiServiceClient.EXPECT().ReadContract(gomock.Any(), gomock.Any()).Return(nil, expectedErr) + cmd := NewContractTestBytecodeCmd(client) + _, err := util.ExecuteCmd(cmd, addr, "a9059cbb0000000000000000000000004867c4bada9553216bf296c4c64e9ff0749206490000000000000000000000000000000000000000000000000000000000000001") + require.Contains(err.Error(), expectedErr.Error()) + }) + + t.Run("invalid amount", func(t *testing.T) { + expectedErr := errors.New("invalid amount") + cmd := NewContractTestBytecodeCmd(client) + _, err := util.ExecuteCmd(cmd, addr, "a9059cbb0000000000000000000000004867c4bada9553216bf296c4c64e9ff0749206490000000000000000000000000000000000000000000000000000000000000001", "test") + require.Contains(err.Error(), expectedErr.Error()) + }) + + t.Run("invalid bytecode", func(t *testing.T) { + expectedErr := errors.New("invalid bytecode") + cmd := NewContractTestBytecodeCmd(client) + _, err := util.ExecuteCmd(cmd, addr, "test") + require.Contains(err.Error(), expectedErr.Error()) + }) + + t.Run("failed to get contract address", func(t *testing.T) { + expectedErr := errors.New("failed to get contract address") + client.EXPECT().Address(gomock.Any()).Return("", expectedErr) + cmd := NewContractTestBytecodeCmd(client) + _, err := util.ExecuteCmd(cmd, "test", "") + require.Contains(err.Error(), expectedErr.Error()) + }) +}