diff --git a/ioctl/newcmd/did/did.go b/ioctl/newcmd/did/did.go index 9a3f6885e7..683845e131 100644 --- a/ioctl/newcmd/did/did.go +++ b/ioctl/newcmd/did/did.go @@ -7,6 +7,9 @@ package did import ( + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" "github.com/spf13/cobra" "github.com/iotexproject/iotex-core/ioctl" @@ -19,6 +22,9 @@ var ( config.English: "Manage Decentralized Identity of IoTeX blockchain", config.Chinese: "管理IoTeX区块链上的去中心化数字身份", } + // _didABI is the interface of the abi encoding of did + _didABI abi.ABI + err error ) const ( @@ -31,6 +37,13 @@ const ( DIDABI = `[{"constant": false,"inputs": [],"name": "deregisterDID","outputs": [],"payable": false,"stateMutability": "nonpayable","type": "function"},{"constant": true,"inputs": [{"internalType": "bytes","name": "did","type": "bytes"}],"name": "getHash","outputs": [{"internalType": "bytes32","name": "","type": "bytes32"}],"payable": false,"stateMutability": "view","type": "function"}, {"constant": true,"inputs": [{"internalType": "bytes","name": "did","type": "bytes"}],"name": "getURI","outputs": [{"internalType": "bytes","name": "","type": "bytes"}],"payable": false,"stateMutability": "view","type": "function"},{"constant": false,"inputs": [{"internalType": "bytes32","name": "h","type": "bytes32"},{"internalType": "bytes","name": "uri","type": "bytes"}],"name": "registerDID","outputs": [],"payable": false,"stateMutability": "nonpayable","type": "function"},{"constant": false,"inputs": [{"internalType": "bytes32","name": "h","type": "bytes32"},{"internalType": "bytes","name": "uri","type": "bytes"}],"name": "updateDID","outputs": [],"payable": false,"stateMutability": "nonpayable","type": "function"}]` ) +func init() { + _didABI, err = abi.JSON(strings.NewReader(DIDABI)) + if err != nil { + panic(err) + } +} + // NewDidCmd represents the did command func NewDidCmd(client ioctl.Client) *cobra.Command { short, _ := client.SelectTranslation(_dIDCmdShorts) diff --git a/ioctl/newcmd/did/didgeturi.go b/ioctl/newcmd/did/didgeturi.go new file mode 100644 index 0000000000..73f5b3424b --- /dev/null +++ b/ioctl/newcmd/did/didgeturi.go @@ -0,0 +1,78 @@ +// Copyright (c) 2022 IoTeX Foundation +// This is an alpha (internal) release and is not suitable for production. 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 did + +import ( + "encoding/hex" + + "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 ( + _getURICmdUses = map[config.Language]string{ + config.English: "geturi (CONTRACT_ADDRESS|ALIAS) DID", + config.Chinese: "geturi (合约地址|别名) DID", + } + _getURICmdShorts = map[config.Language]string{ + config.English: "Geturi get DID URI on IoTeX blockchain", + config.Chinese: "Geturi 在IoTeX链上获取相应DID的uri", + } +) + +// NewDidGetURICmd represents the did get uri command +func NewDidGetURICmd(client ioctl.Client) *cobra.Command { + use, _ := client.SelectTranslation(_getURICmdUses) + short, _ := client.SelectTranslation(_getURICmdShorts) + + cmd := &cobra.Command{ + Use: use, + Short: short, + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + cmd.SilenceUsage = true + contract, err := client.Address(args[0]) + if err != nil { + return errors.Wrap(err, "failed to get contract address") + } + addr, err := address.FromString(contract) + if err != nil { + return errors.Wrap(err, "invalid contract address") + } + bytecode, err := _didABI.Pack(_getURIName, []byte(args[1])) + if err != nil { + return errors.Wrap(err, "invalid bytecode") + } + result, err := action.Read(client, addr, "0", bytecode, contract, 20000000) + if err != nil { + return errors.Wrap(err, "failed to read contract") + } + ret, err := hex.DecodeString(result) + if err != nil { + return errors.Wrap(err, "failed to decode contract") + } + res, err := _didABI.Unpack(_getURIName, ret) + if err != nil { + return errors.Wrap(err, "DID does not exist") + } + out, err := util.ToByteSlice(res[0]) + if err != nil { + return errors.Wrap(err, "failed to convert hash to bytes") + } + cmd.Println(hex.EncodeToString(out[:])) + return nil + }, + } + return cmd +} diff --git a/ioctl/newcmd/did/didgeturi_test.go b/ioctl/newcmd/did/didgeturi_test.go new file mode 100644 index 0000000000..1847efe1df --- /dev/null +++ b/ioctl/newcmd/did/didgeturi_test.go @@ -0,0 +1,95 @@ +// Copyright (c) 2022 IoTeX Foundation +// This is an alpha (internal) release and is not suitable for production. 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 did + +import ( + "testing" + + "github.com/golang/mock/gomock" + "github.com/iotexproject/iotex-proto/golang/iotexapi" + "github.com/iotexproject/iotex-proto/golang/iotexapi/mock_iotexapi" + "github.com/pkg/errors" + "github.com/stretchr/testify/require" + + "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 TestNewDidGetURICmd(t *testing.T) { + require := require.New(t) + ctrl := gomock.NewController(t) + defer ctrl.Finish() + client := mock_ioctlclient.NewMockClient(ctrl) + apiServiceClient := mock_iotexapi.NewMockAPIServiceClient(ctrl) + accAddr := identityset.Address(0).String() + payload := "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000020" + + "0000000000000000000000000000000000000000000000000000000000000001" + + "0000000000000000000000000000000000000000000000000000000000000002" + did := "did:io:0x11111111111111111" + + client.EXPECT().SelectTranslation(gomock.Any()).Return("did", config.English).Times(12) + client.EXPECT().Address(gomock.Any()).Return(accAddr, nil).Times(4) + client.EXPECT().AddressWithDefaultIfNotExist(gomock.Any()).Return(accAddr, nil).Times(4) + client.EXPECT().APIServiceClient().Return(apiServiceClient, nil).Times(4) + + t.Run("get did uri", func(t *testing.T) { + apiServiceClient.EXPECT().ReadContract(gomock.Any(), gomock.Any()).Return(&iotexapi.ReadContractResponse{ + Data: payload, + }, nil) + cmd := NewDidGetURICmd(client) + result, err := util.ExecuteCmd(cmd, accAddr, "did:io:0x11111111111111111") + require.NoError(err) + require.Contains(result, "0000000000000000000000000000000000000000000000000000000000000001") + }) + + t.Run("failed to decode contract", func(t *testing.T) { + expectedErr := errors.New("failed to decode contract") + apiServiceClient.EXPECT().ReadContract(gomock.Any(), gomock.Any()).Return(&iotexapi.ReadContractResponse{ + Data: "test", + }, nil) + cmd := NewDidGetURICmd(client) + _, err := util.ExecuteCmd(cmd, "test", did) + require.Contains(err.Error(), expectedErr.Error()) + }) + + t.Run("DID does not exist", func(t *testing.T) { + expectedErr := errors.New("DID does not exist") + apiServiceClient.EXPECT().ReadContract(gomock.Any(), gomock.Any()).Return(&iotexapi.ReadContractResponse{ + Data: "0000000000000000000000000000000000000000000000000000000000000020", + }, nil) + cmd := NewDidGetURICmd(client) + _, err := util.ExecuteCmd(cmd, accAddr, did) + require.Contains(err.Error(), expectedErr.Error()) + }) + + 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 := NewDidGetURICmd(client) + _, err := util.ExecuteCmd(cmd, accAddr, did) + require.Contains(err.Error(), expectedErr.Error()) + }) + + t.Run("invalid contract address", func(t *testing.T) { + expectedErr := errors.New("invalid contract address") + client.EXPECT().Address(gomock.Any()).Return("test", nil) + cmd := NewDidGetURICmd(client) + _, err := util.ExecuteCmd(cmd, "test", did) + 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 := NewDidGetURICmd(client) + _, err := util.ExecuteCmd(cmd, "test", did) + require.Contains(err.Error(), expectedErr.Error()) + }) +}