Skip to content

Commit

Permalink
Merge pull request #285 from cdc-Hitesh/#284/ibc-MsgCreateClient
Browse files Browse the repository at this point in the history
Problem: Support IBC `MsgCreateClient`
  • Loading branch information
calvinaco authored Jul 8, 2021
2 parents 44b1daf + 768d4de commit d51008d
Show file tree
Hide file tree
Showing 8 changed files with 294 additions and 16 deletions.
4 changes: 3 additions & 1 deletion lib/src/core/cro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,13 @@ import { msgMintNFT } from '../transaction/msg/nft/MsgMintNFT';
import { msgEditNFT } from '../transaction/msg/nft/MsgEditNFT';
import { msgTransferNFT } from '../transaction/msg/nft/MsgTransferNFT';
import { msgBurnNFT } from '../transaction/msg/nft/MsgBurnNFT';
import { msgTransferIBC } from '../transaction/msg/ibc/applications/MsgTransfer';
import { msgCreateClientIBC } from '../transaction/msg/ibc/core/MsgCreateClient';
import { msgSendV2 } from '../transaction/msg/v2/bank/v2.msgsend';
import { msgFundCommunityPoolV2 } from '../transaction/msg/v2/distribution/v2.MsgFundCommunityPool';
import { msgDepositV2 } from '../transaction/msg/v2/gov/v2.MsgDeposit';
import { communityPoolSpendProposalV2 } from '../transaction/msg/v2/gov/proposal/v2.CommunityPoolSpendProposal';
import { msgSubmitProposalV2 } from '../transaction/msg/v2/gov/v2.MsgSubmitProposal';
import { msgTransferIBC } from '../transaction/msg/ibc/MsgTransfer';

export const CroSDK = function (configs: InitConfigurations) {
ow(configs, 'configs', owCroSDKInitParams);
Expand Down Expand Up @@ -81,6 +82,7 @@ export const CroSDK = function (configs: InitConfigurations) {
},
ibc: {
MsgTransfer: msgTransferIBC(configs),
MsgCreateClient: msgCreateClientIBC(configs),
},
v2: {
bank: {
Expand Down
1 change: 1 addition & 0 deletions lib/src/cosmos/v1beta1/types/typeurls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export const typeUrlMappings: {
'/chainmain.nft.v1.MsgTransferNFT': chainmain.nft.v1.MsgTransferNFT,
'/chainmain.nft.v1.MsgBurnNFT': chainmain.nft.v1.MsgBurnNFT,
'/ibc.applications.transfer.v1.MsgTransfer': ibc.applications.transfer.v1.MsgTransfer,
'/ibc.core.client.v1.MsgCreateClient': ibc.core.client.v1.MsgCreateClient,
};

export interface GeneratedType {
Expand Down
4 changes: 4 additions & 0 deletions lib/src/transaction/common/constants/typeurl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export const COSMOS_MSG_TYPEURL = {
},
ibc: {
MsgTransfer: '/ibc.applications.transfer.v1.MsgTransfer',
MsgCreateClient: '/ibc.core.client.v1.MsgCreateClient',
},
};

Expand Down Expand Up @@ -90,6 +91,9 @@ export const typeUrlToMsgClassMapping = (cro: any, typeUrl: string) => {
// ibc
case COSMOS_MSG_TYPEURL.ibc.MsgTransfer:
return cro.ibc.MsgTransfer;
case COSMOS_MSG_TYPEURL.ibc.MsgCreateClient:
return cro.ibc.MsgCreateClient;

// nft
case COSMOS_MSG_TYPEURL.nft.MsgIssueDenom:
return cro.nft.MsgIssueDenom;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import { expect } from 'chai';
import Big from 'big.js';

import Long from 'long';
import { fuzzyDescribe } from '../../../test/mocha-fuzzy/suite';
import { Msg } from '../../../cosmos/v1beta1/types/msg';
import { Secp256k1KeyPair } from '../../../keypair/secp256k1';
import { Bytes } from '../../../utils/bytes/bytes';
import { CroSDK, CroNetwork } from '../../../core/cro';
import { COSMOS_MSG_TYPEURL } from '../../common/constants/typeurl';
import { fuzzyDescribe } from '../../../../test/mocha-fuzzy/suite';
import { Msg } from '../../../../cosmos/v1beta1/types/msg';
import { Secp256k1KeyPair } from '../../../../keypair/secp256k1';
import { Bytes } from '../../../../utils/bytes/bytes';
import { CroSDK, CroNetwork } from '../../../../core/cro';
import { COSMOS_MSG_TYPEURL } from '../../../common/constants/typeurl';

const cro = CroSDK({
network: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
/* eslint-disable camelcase */
import ow from 'ow';
import Long from 'long';
import { Msg } from '../../../cosmos/v1beta1/types/msg';
import { ICoin } from '../../../coin/coin';
import { owMsgTransferIBCOptions } from '../ow.types';
import { InitConfigurations, CroSDK } from '../../../core/cro';
import { AddressType, validateAddress, isValidBech32Address } from '../../../utils/address';
import { CosmosMsg } from '../cosmosMsg';
import { COSMOS_MSG_TYPEURL } from '../../common/constants/typeurl';
import * as legacyAmino from '../../../cosmos/amino';
import { Network } from '../../../network/network';
import { Msg } from '../../../../cosmos/v1beta1/types/msg';
import { ICoin } from '../../../../coin/coin';
import { owMsgTransferIBCOptions } from '../../ow.types';
import { InitConfigurations, CroSDK } from '../../../../core/cro';
import { AddressType, validateAddress, isValidBech32Address } from '../../../../utils/address';
import { CosmosMsg } from '../../cosmosMsg';
import { COSMOS_MSG_TYPEURL } from '../../../common/constants/typeurl';
import * as legacyAmino from '../../../../cosmos/amino';
import { Network } from '../../../../network/network';

export const msgTransferIBC = function (config: InitConfigurations) {
return class MsgTransfer implements CosmosMsg {
Expand Down
151 changes: 151 additions & 0 deletions lib/src/transaction/msg/ibc/core/MsgCreateClient.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import 'mocha';
import { expect } from 'chai';
import Big from 'big.js';

import { fuzzyDescribe } from '../../../../test/mocha-fuzzy/suite';
import { Msg } from '../../../../cosmos/v1beta1/types/msg';
import { Secp256k1KeyPair } from '../../../../keypair/secp256k1';
import { Bytes } from '../../../../utils/bytes/bytes';
import { CroSDK, CroNetwork } from '../../../../core/cro';
import { COSMOS_MSG_TYPEURL } from '../../../common/constants/typeurl';
import { google } from '../../../../cosmos/v1beta1/codec';

const cro = CroSDK({
network: {
defaultNodeUrl: '',
chainId: 'testnet-croeseid-1',
addressPrefix: 'tcro',
validatorAddressPrefix: 'tcrocncl',
validatorPubKeyPrefix: 'tcrocnclconspub',
coin: {
baseDenom: 'basetcro',
croDenom: 'tcro',
},
bip44Path: {
coinType: 1,
account: 0,
},
rpcUrl: '',
},
});

describe('Testing MsgCreateClient', function () {
fuzzyDescribe('should throw Error when options is invalid', function (fuzzy) {
const anyValidOptions = {
signer: 'tcro15sfupd26sp6qf37ll5q6xuf330k7df9tnvrqht',
};

const testRunner = fuzzy(fuzzy.ObjArg(anyValidOptions));

testRunner(function (options) {
if (options.valid) {
return;
}
expect(() => new cro.ibc.MsgCreateClient(options.value)).to.throw(
'Expected `options` to be of type `object`',
);
});
});

it('Test MsgCreateClient conversion', function () {
const MsgCreateClient = new cro.ibc.MsgCreateClient({
signer: 'tcro15sfupd26sp6qf37ll5q6xuf330k7df9tnvrqht',
});

const rawMsg: Msg = {
typeUrl: COSMOS_MSG_TYPEURL.ibc.MsgCreateClient,
value: {
signer: 'tcro15sfupd26sp6qf37ll5q6xuf330k7df9tnvrqht',
clientState: undefined,
consensusState: undefined,
},
};

expect(MsgCreateClient.toRawMsg()).to.eqls(rawMsg);
});

it('Test appendTxBody MsgCreateClient Tx signing', function () {
const anyKeyPair = Secp256k1KeyPair.fromPrivKey(
Bytes.fromHexString('66633d18513bec30dd11a209f1ceb1787aa9e2069d5d47e590174dc9665102b3'),
);

const MsgCreateClient = new cro.ibc.MsgCreateClient({
signer: 'tcro15sfupd26sp6qf37ll5q6xuf330k7df9tnvrqht',
clientState: google.protobuf.Any.create({
type_url: '/some.valid.type.url',
value: new Uint8Array([1, 2, 35, 5]),
}),
consensusState: google.protobuf.Any.create({
type_url: '/some.valid.type.url',
value: new Uint8Array([1, 2, 35, 5]),
}),
});

const anySigner = {
publicKey: anyKeyPair.getPubKey(),
accountNumber: new Big(0),
accountSequence: new Big(2),
};

const rawTx = new cro.RawTransaction();

const signableTx = rawTx.appendMessage(MsgCreateClient).addSigner(anySigner).toSignable();

const signedTx = signableTx.setSignature(0, anyKeyPair.sign(signableTx.toSignDocumentHash(0))).toSigned();

const signedTxHex = signedTx.encode().toHexString();
expect(signedTxHex).to.be.eql(
'0a5a0a580a232f6962632e636f72652e636c69656e742e76312e4d7367437265617465436c69656e7412310a0012001a2b7463726f313573667570643236737036716633376c6c3571367875663333306b37646639746e767271687412580a500a460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a2103fd0d560b6c4aa1ca16721d039a192867c3457e19dad553edb98e7ba88b159c2712040a0208011802120410c09a0c1a40b2b3a0e852a19b4656ce172adbaa95f69d7ab85a9283b9f0f1692b6a1ed094ec63210407a3912a44bec37f3c7ed467a2b77bc90b364db30a913103ca23ba7197',
);
});

it('Should validate MsgCreateClient provided addresses with network config', function () {
const params1 = {
signer: 'cosmos1vw4ucaeagtduv5ep4sa95e3aqzqpsk5meda08c',
};

expect(() => new cro.ibc.MsgCreateClient(params1)).to.throw(
'Provided `signer` does not match network selected',
);
});

it('Should throw on getting toRawAminoMsg()', function () {
const MsgCreateClient = new cro.ibc.MsgCreateClient({
signer: 'tcro15sfupd26sp6qf37ll5q6xuf330k7df9tnvrqht',
});

expect(() => MsgCreateClient.toRawAminoMsg()).to.throw('IBC Module not supported under amino encoding scheme');
});

describe('fromCosmosJSON', function () {
it('should throw Error if the JSON is not a IBC MsgCreateClient', function () {
const json =
'{ "@type": "/cosmos.bank.v1beta1.MsgCreateValidator", "amount": [{ "denom": "basetcro", "amount": "3478499933290496" }], "from_address": "tcro1x07kkkepfj2hl8etlcuqhej7jj6myqrp48y4hg", "to_address": "tcro184lta2lsyu47vwyp2e8zmtca3k5yq85p6c4vp3" }';
expect(() => cro.ibc.MsgCreateClient.fromCosmosMsgJSON(json, CroNetwork.Testnet)).to.throw(
'Expected /ibc.core.client.v1.MsgCreateClient but got /cosmos.bank.v1beta1.MsgCreateValidator',
);
});
it('should throw on invalid `sender`', function () {
const json = `
{
"@type": "/ibc.core.client.v1.MsgCreateClient",
"signer": "cosmos1u8prj0rj3ur7kr23dhjgyteuq55ntahfuzlf6g"
}
`;

expect(() => cro.ibc.MsgCreateClient.fromCosmosMsgJSON(json, CroNetwork.Testnet)).to.throw(
'Provided `signer` does not match network selected',
);
});
it('should return the IBC MsgCreateClient corresponding to the JSON', function () {
const json = `{
"@type": "/ibc.core.client.v1.MsgCreateClient",
"signer": "tcro1agr5hwr6gxljf4kpg6fm7l7ehjxtyazg86nef8"
}
`;

const MsgCreateClient = cro.ibc.MsgCreateClient.fromCosmosMsgJSON(json, CroNetwork.Testnet);
expect(MsgCreateClient.signer).to.eql('tcro1agr5hwr6gxljf4kpg6fm7l7ehjxtyazg86nef8');
});
});
});
108 changes: 108 additions & 0 deletions lib/src/transaction/msg/ibc/core/MsgCreateClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import ow from 'ow';
import { google } from '../../../../cosmos/v1beta1/codec/generated/codecimpl';
import { InitConfigurations } from '../../../../core/cro';
import { CosmosMsg } from '../../cosmosMsg';
import { Msg } from '../../../../cosmos/v1beta1/types/msg';
import { COSMOS_MSG_TYPEURL } from '../../../common/constants/typeurl';
import { validateAddress, AddressType } from '../../../../utils/address';
import { owMsgCreateClientOptions } from '../../ow.types';
import * as legacyAmino from '../../../../cosmos/amino';
import { Network } from '../../../../network/network';

export const msgCreateClientIBC = function (config: InitConfigurations) {
return class MsgCreateClient implements CosmosMsg {
/** MsgCreateClient clientState. */
public clientState?: google.protobuf.IAny | null;

/** MsgCreateClient consensusState. */
public consensusState?: google.protobuf.IAny | null;

/** MsgCreateClient signer. */
public signer: string;

/**
* Constructor to create a new IBC.MsgCreateClient
* @param {MsgCreateClientOptions} options
* @returns {MsgCreateClient}
* @throws {Error} when options is invalid
*/
constructor(options: MsgCreateClientOptions) {
ow(options, 'options', owMsgCreateClientOptions);
this.clientState = options.clientState;
this.consensusState = options.consensusState;
this.signer = options.signer;
this.validateAddresses();
}

/**
* Returns the raw Msg representation of Ibc.MsgCreateClient
* @returns {Msg}
*/
toRawMsg(): Msg {
return {
typeUrl: COSMOS_MSG_TYPEURL.ibc.MsgCreateClient,
value: {
clientState: this.clientState,
consensusState: this.consensusState,
signer: this.signer,
},
};
}

// eslint-disable-next-line class-methods-use-this
toRawAminoMsg(): legacyAmino.Msg {
throw new Error('IBC Module not supported under amino encoding scheme');
}

/**
* Returns an instance of IBC.MsgCreateClient
* @param {string} msgJsonStr
* @param {Network} network
* @returns {MsgCreateClient}
*/
public static fromCosmosMsgJSON(msgJsonStr: string, _network: Network): MsgCreateClient {
const parsedMsg = JSON.parse(msgJsonStr) as IBCMsgCreateClientRaw;
if (parsedMsg['@type'] !== COSMOS_MSG_TYPEURL.ibc.MsgCreateClient) {
throw new Error(`Expected ${COSMOS_MSG_TYPEURL.ibc.MsgCreateClient} but got ${parsedMsg['@type']}`);
}

return new MsgCreateClient({
clientState: google.protobuf.Any.create({
type_url: parsedMsg.clientState?.['@type'],
value: parsedMsg.clientState?.value,
}),
consensusState: google.protobuf.Any.create({
type_url: parsedMsg.consensusState?.['@type'],
value: parsedMsg.consensusState?.value,
}),
signer: parsedMsg.signer,
});
}

validateAddresses() {
// TODO: Can `signer` be from non-CRO network
if (
!validateAddress({
address: this.signer,
network: config.network,
type: AddressType.USER,
})
) {
throw new TypeError('Provided `signer` does not match network selected');
}
}
};
};

export type MsgCreateClientOptions = {
clientState?: google.protobuf.IAny | null;
consensusState?: google.protobuf.IAny | null;
signer: string;
};

interface IBCMsgCreateClientRaw {
'@type': string;
signer: string;
clientState?: { '@type': string; value: any };
consensusState?: { '@type': string; value: any };
}
12 changes: 12 additions & 0 deletions lib/src/transaction/msg/ow.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,12 @@ const owIBCHeightOptional = () =>
revisionNumber: owLong(),
});

const owGoogleProtoAnyOptional = () =>
owOptionalStrictObject().exactShape({
type_url: ow.optional.string,
value: ow.optional.uint8Array,
});

export const owMsgTransferIBCOptions = owStrictObject().exactShape({
sourcePort: ow.string,
sourceChannel: ow.string,
Expand All @@ -226,3 +232,9 @@ export const owMsgTransferIBCOptions = owStrictObject().exactShape({
timeoutHeight: owIBCHeightOptional(),
timeoutTimestamp: owLong(),
});

export const owMsgCreateClientOptions = owStrictObject().exactShape({
signer: ow.string,
clientState: ow.optional.any(owGoogleProtoAnyOptional(), ow.null),
consensusState: ow.optional.any(owGoogleProtoAnyOptional(), ow.null),
});

0 comments on commit d51008d

Please sign in to comment.