Skip to content

Commit

Permalink
Merge pull request #1 from cdc-Hitesh/feature-offlineSigning-MsgSend
Browse files Browse the repository at this point in the history
Problem: Feature offline signing msg send
  • Loading branch information
cdc-Hitesh authored Jun 2, 2021
2 parents 10f0603 + 1887234 commit 608ea32
Show file tree
Hide file tree
Showing 19 changed files with 929 additions and 276 deletions.
27 changes: 23 additions & 4 deletions lib/src/coin/coin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Big from 'big.js';
import ow from 'ow';

import { owCoin, owCoinUnit } from './ow.types';
import { InitConfigurations } from '../core/cro';
import { InitConfigurations, CroNetwork } from '../core/cro';
import { Network } from '../network/network';
import { Coin as CosmosCoin, coin as cosmosCoin, coins as cosmosCoins } from '../cosmos/coins';

Expand All @@ -28,6 +28,11 @@ export interface ICoin {

export const coin = function (config: InitConfigurations) {
return class Coin implements ICoin {
public static croAllDenoms = [
...Object.values(CroNetwork.Mainnet.coin),
...Object.values(CroNetwork.Testnet.coin),
];

/**
* Total supply in base unit represented as string
* @type {string}
Expand Down Expand Up @@ -69,15 +74,20 @@ export const coin = function (config: InitConfigurations) {

public readonly network: Network;

public readonly denom: string;

public readonly receivedAmount: Big;

/**
* Constructor to create a Coin
* @param {string} amount coins amount represented as string
* @param {Units} unit unit of the coins
* @throws {Error} amount or unit is invalid
* @returns {Coin}
*/
constructor(amount: string, unit: Units) {
constructor(amount: string, unit: Units = Units.BASE, denom?: string) {
ow(amount, 'amount', ow.string);
ow(denom, 'denom', ow.optional.string);
ow(unit, 'unit', owCoinUnit);

let coins: Big;
Expand All @@ -88,8 +98,14 @@ export const coin = function (config: InitConfigurations) {
}
this.network = config.network;
this.baseAmount = unit === Units.BASE ? Coin.parseBaseAmount(coins) : Coin.parseCROAmount(coins);
this.denom = denom || this.network.coin.baseDenom;
this.receivedAmount = coins;
}

public static fromCustomAmountDenom = (amount: string, denom: string): Coin => {
return new Coin(amount, Units.BASE, denom);
};

getNetwork(): Network {
return this.network;
}
Expand Down Expand Up @@ -209,7 +225,7 @@ export const coin = function (config: InitConfigurations) {
* @memberof Coin
* */
public toCosmosCoin(): CosmosCoin {
return cosmosCoin(this.toString(Units.BASE), config.network.coin.baseDenom);
return cosmosCoin(this.toString(Units.BASE), this.denom);
}

/**
Expand All @@ -218,7 +234,7 @@ export const coin = function (config: InitConfigurations) {
* @memberof Coin
* */
public toCosmosCoins(): CosmosCoin[] {
return cosmosCoins(this.toString(Units.BASE), config.network.coin.baseDenom);
return cosmosCoins(this.toString(Units.BASE), this.denom);
}

/**
Expand All @@ -231,6 +247,9 @@ export const coin = function (config: InitConfigurations) {
public toString(unit: Units = Units.BASE): string {
ow(unit, owCoinUnit);

if (!Coin.croAllDenoms.includes(this.denom)) {
return this.receivedAmount.toString();
}
if (unit === Units.BASE) {
return this.baseAmount.toString();
}
Expand Down
86 changes: 86 additions & 0 deletions lib/src/cosmos/v1beta1/types/cosmostx.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
export interface CosmosTx {
tx?: undefined | CosmosTx
body: Body;
auth_info: AuthInfo;
signatures: string[];
}

interface AuthInfo {
signer_infos: SignerInfo[];
fee: Fee;
}

interface Fee {
amount: Amount[];
gas_limit: string;
payer: string;
granter: string;
}

interface Amount {
denom: string;
amount: string;
}

interface SignerInfo {
public_key: SingleSignerInfoPublicKey | MultiSignerInfoPublicKey;
mode_info: SignerInfoModeInfo;
sequence: string;
}

interface SignerInfoModeInfo {
single?: Single;
multi?: Multi;
}

interface Multi {
bitarray: Bitarray;
mode_infos: ModeInfoElement[];
}

interface Bitarray {
extra_bits_stored: number;
elems: string;
}

interface ModeInfoElement {
single: Single;
}

interface Single {
mode: string;
}

interface SingleSignerInfoPublicKey {
'@type': string;
key: string;
}

interface MultiSignerInfoPublicKey {
'@type': string;
threshold?: number;
public_keys: PublicKeyElement[];
}

interface PublicKeyElement {
'@type': string;
key: string;
}

interface Body {
messages: Message[];
memo: string;
timeout_height: string;
extension_options: any[];
non_critical_extension_options: any[];
}

interface Message {
'@type': string;
[key: string]: any;
}

interface Amount {
denom: string;
amount: string;
}
4 changes: 2 additions & 2 deletions lib/src/cosmos/v1beta1/types/ow.types.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import ow from 'ow';

import { owOptionalCoin } from '../../../coin/ow.types';
import { owBig, owOptionalBig, owStrictObject } from '../../../ow.types';
import { owBig, owOptionalBig, owStrictObject, owOptionalStrictObject } from '../../../ow.types';
import { owCosmosMsg } from '../../../transaction/msg/cosmosMsg';
import { owTimeoutHeight } from '../../../transaction/ow.types';
import { owBytes } from '../../../utils/bytes/ow.types';
import { cosmos } from '../codec';

export const owTxBody = () =>
owStrictObject().exactShape({
owOptionalStrictObject().exactShape({
typeUrl: ow.string.equals('/cosmos.tx.v1beta1.TxBody'),
value: owStrictObject().exactShape({
messages: ow.array.ofType(owCosmosMsg()),
Expand Down
5 changes: 5 additions & 0 deletions lib/src/cosmos/v1beta1/types/typeurls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ export const typeUrlMappings: {
'/cosmos.gov.v1beta1.MsgDeposit': cosmos.gov.v1beta1.MsgDeposit,
'/cosmos.gov.v1beta1.MsgVote': cosmos.gov.v1beta1.MsgVote,
'/cosmos.gov.v1beta1.MsgSubmitProposal': cosmos.gov.v1beta1.MsgSubmitProposal,
'/cosmos.distribution.v1beta1.CommunityPoolSpendProposal': cosmos.distribution.v1beta1.CommunityPoolSpendProposal,
'/cosmos.upgrade.v1beta1.CancelSoftwareUpgradeProposal': cosmos.upgrade.v1beta1.CancelSoftwareUpgradeProposal,
'/cosmos.upgrade.v1beta1.SoftwareUpgradeProposal': cosmos.upgrade.v1beta1.SoftwareUpgradeProposal,
'/cosmos.gov.v1beta1.TextProposal': cosmos.gov.v1beta1.TextProposal,
'/cosmos.params.v1beta1.ParameterChangeProposal': cosmos.params.v1beta1.ParameterChangeProposal,
'/google.protobuf.Any': google.protobuf.Any,
'/cosmos.distribution.v1beta1.MsgSetWithdrawAddress': cosmos.distribution.v1beta1.MsgSetWithdrawAddress,
'/cosmos.distribution.v1beta1.MsgFundCommunityPool': cosmos.distribution.v1beta1.MsgFundCommunityPool,
Expand Down
47 changes: 47 additions & 0 deletions lib/src/transaction/common/constants/typeurl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,50 @@ export const COSMOS_MSG_TYPEURL = {
MsgBurnNFT: '/chainmain.nft.v1.MsgBurnNFT',
},
};

export const typeUrlToMsgClassMapping = (cro: any, typeUrl: string) => {
switch (typeUrl) {
// bank
case COSMOS_MSG_TYPEURL.MsgSend:
return cro.bank.MsgSend;

// distribution
case COSMOS_MSG_TYPEURL.distribution.MsgFundCommunityPool:
return cro.distribution.MsgFundCommunityPool;
case COSMOS_MSG_TYPEURL.distribution.MsgSetWithdrawAddress:
return cro.distribution.MsgSetWithdrawAddress;
case COSMOS_MSG_TYPEURL.MsgWithdrawDelegatorReward:
return cro.distribution.MsgWithdrawDelegatorReward;
case COSMOS_MSG_TYPEURL.MsgWithdrawValidatorCommission:
return cro.distribution.MsgWithdrawValidatorCommission;

// staking
case COSMOS_MSG_TYPEURL.MsgBeginRedelegate:
return cro.staking.MsgBeginRedelegate;
case COSMOS_MSG_TYPEURL.MsgCreateValidator:
return cro.staking.MsgCreateValidator;
case COSMOS_MSG_TYPEURL.MsgDelegate:
return cro.staking.MsgDelegate;
case COSMOS_MSG_TYPEURL.MsgEditValidator:
return cro.staking.MsgEditValidator;
case COSMOS_MSG_TYPEURL.MsgUndelegate:
return cro.staking.MsgUndelegate;

// governance
case COSMOS_MSG_TYPEURL.MsgDeposit:
return cro.gov.MsgDeposit;
case COSMOS_MSG_TYPEURL.MsgVote:
return cro.gov.MsgVote;
case COSMOS_MSG_TYPEURL.MsgSubmitProposal:
return cro.gov.MsgSubmitProposal;
case COSMOS_MSG_TYPEURL.gov.TextProposal:
return cro.gov.proposal.TextProposal;
case COSMOS_MSG_TYPEURL.upgrade.CancelSoftwareUpgradeProposal:
return cro.gov.proposal.CancelSoftwareUpgradeProposal;
case COSMOS_MSG_TYPEURL.upgrade.SoftwareUpgradeProposal:
return cro.gov.proposal.SoftwareUpgradeProposal;

default:
throw new Error(`${typeUrl} not supported.`);
}
};
42 changes: 41 additions & 1 deletion lib/src/transaction/msg/bank/msgsend.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Msg } from '../../../cosmos/v1beta1/types/msg';
import { Secp256k1KeyPair } from '../../../keypair/secp256k1';
import { Bytes } from '../../../utils/bytes/bytes';
import { Units } from '../../../coin/coin';
import { CroSDK } from '../../../core/cro';
import { CroSDK, CroNetwork } from '../../../core/cro';

const cro = CroSDK({
network: {
Expand All @@ -29,6 +29,46 @@ const cro = CroSDK({
});

describe('Testing MsgSend', function () {
describe('fromCosmosJSON', function () {
it('should throw Error if the JSON is not a MsgSend', function () {
const json =
'{ "@type": "/cosmos.bank.v1beta1.MsgCreateValidator", "amount": [{ "denom": "basetcro", "amount": "3478499933290496" }], "from_address": "tcro1x07kkkepfj2hl8etlcuqhej7jj6myqrp48y4hg", "to_address": "tcro184lta2lsyu47vwyp2e8zmtca3k5yq85p6c4vp3" }';
expect(() => cro.bank.MsgSend.fromCosmosMsgJSON(json, CroNetwork.Testnet)).to.throw(
'Expected /cosmos.bank.v1beta1.MsgSend but got /cosmos.bank.v1beta1.MsgCreateValidator',
);
});
it('should throw Error when the from field is missing', function () {
const json =
'{ "@type": "/cosmos.bank.v1beta1.MsgSend", "amount": [{ "denom": "basetcro", "amount": "3478499933290496" }], "to_address": "tcro184lta2lsyu47vwyp2e8zmtca3k5yq85p6c4vp3" }';
expect(() => cro.bank.MsgSend.fromCosmosMsgJSON(json, CroNetwork.Testnet)).to.throw(
'Expected property `fromAddress` to be of type `string` but received type `undefined` in object `options`',
);
});
it('should throw Error when the to field is missing', function () {
const json =
'{ "@type": "/cosmos.bank.v1beta1.MsgSend", "amount": [{ "denom": "basetcro", "amount": "3478499933290496" }], "from_address": "tcro1x07kkkepfj2hl8etlcuqhej7jj6myqrp48y4hg" }';
expect(() => cro.bank.MsgSend.fromCosmosMsgJSON(json, CroNetwork.Testnet)).to.throw(
'Expected property `toAddress` to be of type `string` but received type `undefined` in object `options`',
);
});
it('should throw Error when the amount field is missing', function () {
const json =
'{ "@type": "/cosmos.bank.v1beta1.MsgSend", "from_address": "tcro1x07kkkepfj2hl8etlcuqhej7jj6myqrp48y4hg" , "to_address": "tcro184lta2lsyu47vwyp2e8zmtca3k5yq85p6c4vp3" }';
expect(() => cro.bank.MsgSend.fromCosmosMsgJSON(json, CroNetwork.Testnet)).to.throw(
'Invalid amount in the Msg.',
);
});
it('should return the MsgSend corresponding to the JSON', function () {
const json =
'{ "@type": "/cosmos.bank.v1beta1.MsgSend", "amount": [{ "denom": "basetcro", "amount": "3478499933290496" }], "from_address": "tcro1x07kkkepfj2hl8etlcuqhej7jj6myqrp48y4hg", "to_address": "tcro184lta2lsyu47vwyp2e8zmtca3k5yq85p6c4vp3" }';
const msgSend = cro.bank.MsgSend.fromCosmosMsgJSON(json, CroNetwork.Testnet);
expect(msgSend.fromAddress).to.eql('tcro1x07kkkepfj2hl8etlcuqhej7jj6myqrp48y4hg');
expect(msgSend.toAddress).to.eql('tcro184lta2lsyu47vwyp2e8zmtca3k5yq85p6c4vp3');
expect(msgSend.amount.toCosmosCoin().amount).to.eql('3478499933290496');
expect(msgSend.amount.toCosmosCoin().denom).to.eql('basetcro');
});
});

fuzzyDescribe('should throw Error when options is invalid', function (fuzzy) {
const anyValidOptions = {
fromAddress: 'tcro165tzcrh2yl83g8qeqxueg2g5gzgu57y3fe3kc3',
Expand Down
40 changes: 39 additions & 1 deletion lib/src/transaction/msg/bank/msgsend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,24 @@ import ow from 'ow';
import { Msg } from '../../../cosmos/v1beta1/types/msg';
import { ICoin } from '../../../coin/coin';
import { owMsgSendOptions } from '../ow.types';
import { InitConfigurations } from '../../../core/cro';
import { InitConfigurations, CroSDK } from '../../../core/cro';
import { AddressType, validateAddress } 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 interface MsgSendRaw {
'@type': string;
amount: Amount[];
from_address: string;
to_address: string;
}

export interface Amount {
denom: string;
amount: string;
}

export const msgSend = function (config: InitConfigurations) {
return class MsgSend implements CosmosMsg {
Expand All @@ -32,6 +45,30 @@ export const msgSend = function (config: InitConfigurations) {
this.validateAddresses();
}

/**
* Returns an instance of MsgSend
* @param {string} msgJsonStr
* @param {Network} network
* @returns {MsgSend}
*/
public static fromCosmosMsgJSON(msgJsonStr: string, network: Network): MsgSend {
const parsedMsg = JSON.parse(msgJsonStr) as MsgSendRaw;
const cro = CroSDK({ network });
if (parsedMsg['@type'] !== COSMOS_MSG_TYPEURL.MsgSend) {
throw new Error(`Expected ${COSMOS_MSG_TYPEURL.MsgSend} but got ${parsedMsg['@type']}`);
}
if (!parsedMsg.amount || parsedMsg.amount.length != 1) {
throw new Error('Invalid amount in the Msg.');
}

return new MsgSend({
fromAddress: parsedMsg.from_address,
toAddress: parsedMsg.to_address,
// TOdo: Handle the complete list
amount: cro.Coin.fromCustomAmountDenom(parsedMsg.amount[0].amount, parsedMsg.amount[0].denom),
});
}

/**
* Returns the raw Msg representation of MsgSend
* @returns {Msg}
Expand Down Expand Up @@ -86,5 +123,6 @@ export const msgSend = function (config: InitConfigurations) {
export type MsgSendOptions = {
fromAddress: string;
toAddress: string;
// Todo: It should be ICoin[]
amount: ICoin;
};
Loading

0 comments on commit 608ea32

Please sign in to comment.