Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions modules/abstract-cosmos/src/lib/ContractCallBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,14 @@ export class ContractCallBuilder<CustomMessage = never> extends CosmosTransactio
return message as MessageData<CustomMessage>;
}

if (CosmosUtils.isGroupProposal(executeContractMessage)) {
if (this._utils.isGroupProposal(executeContractMessage)) {
return {
typeUrl: constants.groupProposalMsgTypeUrl,
value: executeContractMessage.msg,
} as MessageData<CustomMessage>;
}

if (CosmosUtils.isGroupVote(executeContractMessage)) {
if (this._utils.isGroupVote(executeContractMessage)) {
return {
typeUrl: constants.groupVoteMsgTypeUrl,
value: executeContractMessage.msg,
Expand Down
14 changes: 9 additions & 5 deletions modules/abstract-cosmos/src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1007,11 +1007,11 @@ export class CosmosUtils<CustomMessage = never> implements BaseUtils {
* @param {ExecuteContractMessage} message - The execute contract message to check
* @returns {boolean} true if the msg decodes to a group proposal
*/
static isGroupProposal(message: ExecuteContractMessage): boolean {
isGroupProposal(message: ExecuteContractMessage): boolean {
if (!message.msg || message.msg.length === 0) {
return false;
}
const result = CosmosUtils.decodeMsg(message.msg);
const result = this.decodeMsg(message.msg);
return result.typeUrl === constants.groupProposalMsgTypeUrl;
}

Expand All @@ -1020,11 +1020,11 @@ export class CosmosUtils<CustomMessage = never> implements BaseUtils {
* @param {ExecuteContractMessage} message - The execute contract message to check
* @returns {boolean} true if the msg decodes to a group vote
*/
static isGroupVote(message: ExecuteContractMessage): boolean {
isGroupVote(message: ExecuteContractMessage): boolean {
if (!message.msg || message.msg.length === 0) {
return false;
}
const result = CosmosUtils.decodeMsg(message.msg);
const result = this.decodeMsg(message.msg);
return result.typeUrl === constants.groupVoteMsgTypeUrl;
}

Expand All @@ -1034,7 +1034,7 @@ export class CosmosUtils<CustomMessage = never> implements BaseUtils {
* @param data - Message data as base64 string or Uint8Array
* @returns Decoded message result with typeUrl if successfully identified
*/
static decodeMsg(data: string | Uint8Array): { typeUrl?: string; error?: string } {
decodeMsg(data: string | Uint8Array): { typeUrl?: string; error?: string } {
try {
const messageBytes = typeof data === 'string' ? Buffer.from(data, 'base64') : data;

Expand All @@ -1043,6 +1043,9 @@ export class CosmosUtils<CustomMessage = never> implements BaseUtils {
if (
proposal.groupPolicyAddress &&
typeof proposal.groupPolicyAddress === 'string' &&
(this.isValidAddress(proposal.groupPolicyAddress) ||
this.isValidContractAddress(proposal.groupPolicyAddress) ||
this.isValidValidatorAddress(proposal.groupPolicyAddress)) &&
proposal.groupPolicyAddress.length > 0 &&
Array.isArray(proposal.proposers) &&
proposal.proposers.length > 0
Expand All @@ -1058,6 +1061,7 @@ export class CosmosUtils<CustomMessage = never> implements BaseUtils {
if (
vote.voter &&
typeof vote.voter === 'string' &&
this.isValidAddress(vote.voter) &&
vote.voter.length > 0 &&
vote.proposalId !== undefined &&
vote.proposalId !== null
Expand Down
8 changes: 8 additions & 0 deletions modules/sdk-coin-hash/test/resources/hash.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ export const TEST_CONTRACT_CALL = {
'0ae9020ae6020a222f636f736d6f732e67726f75702e76312e4d73675375626d697450726f706f73616c12bf020a3d74703174617a6566776b32653337326679326a71303877366c7a7467397972727663343930723267703476743864306663686c726671717961686730751229747031326e796e3833796e6577746d706b773332777136646738337778386e71706174363567636c641a0f65786368616e67652d636f6d6d697422bf010a2d2f70726f76656e616e63652e65786368616e67652e76312e4d7367436f6d6d697446756e647352657175657374128d010a3d74703174617a6566776b32653337326679326a71303877366c7a7467397972727663343930723267703476743864306663686c7266717179616867307510011a140a0975796c64732e6663631207313030303030302a3465786368616e67652d636f6d6d69743a32316561363334302d393961662d346338632d386661302d37356665306431626264343428011299010a500a460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a21039f03af548098ff794456d05f2adcc389cfc04abc6e16d92669a6255e33145b2112040a020801181012450a140a056e68617368120b34303030303030303030301090a10f22297470313276646e72376464636b78306d38753632717573727a713572363663656a35726434397a77661a0d70696f2d746573746e65742d3120f2cc0e',
};

export const TEST_SUBMIT_PROPOSAL = {
randomMsgSubmitProposalEncoded:
'0ae9020ae6020a222f636f736d6f732e67726f75702e76312e4d73675375626d697450726f706f73616c12bf020a3d747031776838366b6561746c663879796e6765677675326a657567686e35706e7538306375397a7775397874636e6b6c72686b717277713474363864611229747031666b7963747165326b72636736743232726764726e796c6e7a68326e336c36613338323539731a0f65786368616e67652d636f6d6d697422bf010a2d2f70726f76656e616e63652e65786368616e67652e76312e4d7367436f6d6d697446756e647352657175657374128d010a3d747031776838366b6561746c663879796e6765677675326a657567686e35706e7538306375397a7775397874636e6b6c72686b7172777134743638646110011a140a0975796c64732e6663631207313030303030302a3465786368616e67652d636f6d6d69743a38326535666538392d393162652d346235332d623034322d3737666536383165343237372801126c0a4e0a460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a2103eb24ad39ec900dc2370a48e66d196e10df27ed9e4045b208f208d22adc2ca23512040a020801121a0a140a056e68617368120b353737373737373737373810dce40c',
};

export const TEST_GROUP_VOTE = {
// Real encoded MsgVote payload from explorer tx 29CDCDFFB38AE89BA0311D040BB0D83541B4E5B2973C12DE5B00E6C0F9078B12
encodedVote: 'COn8ChIpdHAxZGoybjV5NDdheXEydDg0cGF5OGN5eTY1emg2ZTV1NWowZGpuajcYASICe30oAQ==',
Expand All @@ -37,6 +42,9 @@ export const TEST_GROUP_VOTE = {
messageTypeUrl: '/cosmos.group.v1.MsgVote',
expectedSignBytesHex:
'0a550a530a182f636f736d6f732e67726f75702e76312e4d7367566f7465123708e9fc0a1229747031646a326e35793437617971327438347061793863797936357a6836653575356a30646a6e6a37180122027b7d28011299010a500a460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a21039f03af548098ff794456d05f2adcc389cfc04abc6e16d92669a6255e33145b2112040a020801181112450a140a056e68617368120b34303030303030303030301090a10f22297470313276646e72376464636b78306d38753632717573727a713572363663656a35726434397a77661a0d70696f2d746573746e65742d3120f2cc0e',
//random unsigned MsgVote payload
randomMsgVoteEncoded:
'0a4e0a4c0a182f636f736d6f732e67726f75702e76312e4d7367566f7465123008ad571229747031666b7963747165326b72636736743232726764726e796c6e7a68326e336c366133383235397318011299010a500a460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a2103eb24ad39ec900dc2370a48e66d196e10df27ed9e4045b208f208d22adc2ca23512040a020801180112450a140a056e68617368120b34303030303030303030301090a10f22297470313276646e72376464636b78306d38753632717573727a713572363663656a35726434397a7766',
};

export const TEST_ACCOUNT = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,22 @@ describe('Hash Transaction Builder', async () => {
should.equal(rawTx, testTxData.signedTxBase64);
});

it('should be able to build a submit proposal transaction from submit proposal transaction data', async function () {
const txBuilder = factory.from(testData.TEST_SUBMIT_PROPOSAL.randomMsgSubmitProposalEncoded);
const tx = await txBuilder.build();
should.equal(tx.type, TransactionType.ContractCall);
// Should recreate the same raw tx data when re-build and turned to broadcast format
tx.cosmosLikeTransaction.sendMessages[0].typeUrl.should.equal('/cosmos.group.v1.MsgSubmitProposal');
});

it('should be able to build a group vote transaction from group vote transaction data', async function () {
const txBuilder = factory.from(testData.TEST_GROUP_VOTE.randomMsgVoteEncoded);
const tx = await txBuilder.build();
should.equal(tx.type, TransactionType.ContractCall);
// Should recreate the same raw tx data when re-build and turned to broadcast format
tx.cosmosLikeTransaction.sendMessages[0].typeUrl.should.equal('/cosmos.group.v1.MsgVote');
});

it('should build a signed token tx from signed token tx data', async function () {
const txBuilder = factory.from(tokenTestTxData.signedTxBase64);
const tx = await txBuilder.build();
Expand Down
31 changes: 16 additions & 15 deletions modules/sdk-coin-hash/test/unit/utils.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import should from 'should';
import { CosmosUtils } from '@bitgo/abstract-cosmos';
import { NetworkType } from '@bitgo/statics';

import utils from '../../src/lib/utils';
import utils, { HashUtils } from '../../src/lib/utils';
import * as testData from '../resources/hash';
import { blockHash, txIds, TEST_CONTRACT_CALL, TEST_GROUP_VOTE } from '../resources/hash';

describe('utils', () => {
const testnetHashUtils = new HashUtils(NetworkType.TESTNET);
it('should validate block hash correctly', () => {
should.equal(utils.isValidBlockId(blockHash.hash1), true);
should.equal(utils.isValidBlockId(blockHash.hash2), true);
Expand Down Expand Up @@ -48,7 +49,7 @@ describe('utils', () => {

describe('decodeMsg', () => {
it('should detect valid base64-encoded group proposal', () => {
const result = CosmosUtils.decodeMsg(TEST_CONTRACT_CALL.encodedProposal);
const result = testnetHashUtils.decodeMsg(TEST_CONTRACT_CALL.encodedProposal);

should.exist(result.typeUrl);
if (result.typeUrl) {
Expand All @@ -58,28 +59,28 @@ describe('utils', () => {
});

it('should reject invalid base64 string', () => {
const result = CosmosUtils.decodeMsg('not-valid-base64!!!');
const result = testnetHashUtils.decodeMsg('not-valid-base64!!!');

should.not.exist(result.typeUrl);
should.exist(result.error);
});

it('should reject valid base64 but invalid protobuf', () => {
const result = CosmosUtils.decodeMsg(Buffer.from('random data').toString('base64'));
const result = testnetHashUtils.decodeMsg(Buffer.from('random data').toString('base64'));

should.not.exist(result.typeUrl);
should.exist(result.error);
});

it('should reject hex-encoded contract call data', () => {
const result = CosmosUtils.decodeMsg('7b22696e6372656d656e74223a7b7d7d');
const result = testnetHashUtils.decodeMsg('7b22696e6372656d656e74223a7b7d7d');

should.not.exist(result.typeUrl);
});

it('should accept Uint8Array input', () => {
const bytes = Buffer.from(TEST_CONTRACT_CALL.encodedProposal, 'base64');
const result = CosmosUtils.decodeMsg(bytes);
const result = testnetHashUtils.decodeMsg(bytes);

should.exist(result.typeUrl);
if (result.typeUrl) {
Expand All @@ -90,7 +91,7 @@ describe('utils', () => {

describe('decodeMsg - group vote', () => {
it('should detect valid base64-encoded group vote', () => {
const result = CosmosUtils.decodeMsg(TEST_GROUP_VOTE.encodedVote);
const result = testnetHashUtils.decodeMsg(TEST_GROUP_VOTE.encodedVote);

should.exist(result.typeUrl);
if (result.typeUrl) {
Expand All @@ -101,7 +102,7 @@ describe('utils', () => {

it('should accept Uint8Array input for group vote', () => {
const bytes = Buffer.from(TEST_GROUP_VOTE.encodedVote, 'base64');
const result = CosmosUtils.decodeMsg(bytes);
const result = testnetHashUtils.decodeMsg(bytes);

should.exist(result.typeUrl);
if (result.typeUrl) {
Expand All @@ -117,7 +118,7 @@ describe('utils', () => {
contract: 'tp12nyn83ynewtmpkw32wq6dg83wx8nqpat65gcld',
msg: Buffer.from(TEST_GROUP_VOTE.encodedVote, 'base64'),
};
should.equal(CosmosUtils.isGroupVote(message), true);
should.equal(testnetHashUtils.isGroupVote(message), true);
});

it('should return false when msg contains a group proposal', () => {
Expand All @@ -126,7 +127,7 @@ describe('utils', () => {
contract: 'tp12nyn83ynewtmpkw32wq6dg83wx8nqpat65gcld',
msg: Buffer.from(TEST_CONTRACT_CALL.encodedProposal, 'base64'),
};
should.equal(CosmosUtils.isGroupVote(message), false);
should.equal(testnetHashUtils.isGroupVote(message), false);
});

it('should return false when msg is empty', () => {
Expand All @@ -135,7 +136,7 @@ describe('utils', () => {
contract: 'tp12nyn83ynewtmpkw32wq6dg83wx8nqpat65gcld',
msg: new Uint8Array(0),
};
should.equal(CosmosUtils.isGroupVote(message), false);
should.equal(testnetHashUtils.isGroupVote(message), false);
});
});

Expand All @@ -146,7 +147,7 @@ describe('utils', () => {
contract: 'tp12nyn83ynewtmpkw32wq6dg83wx8nqpat65gcld',
msg: Buffer.from(TEST_CONTRACT_CALL.encodedProposal, 'base64'),
};
should.equal(CosmosUtils.isGroupProposal(message), true);
should.equal(testnetHashUtils.isGroupProposal(message), true);
});

it('should return false when msg contains regular contract call data', () => {
Expand All @@ -155,7 +156,7 @@ describe('utils', () => {
contract: 'tp12nyn83ynewtmpkw32wq6dg83wx8nqpat65gcld',
msg: Buffer.from(JSON.stringify({ increment: {} })),
};
should.equal(CosmosUtils.isGroupProposal(message), false);
should.equal(testnetHashUtils.isGroupProposal(message), false);
});

it('should return false when msg is empty', () => {
Expand All @@ -164,7 +165,7 @@ describe('utils', () => {
contract: 'tp12nyn83ynewtmpkw32wq6dg83wx8nqpat65gcld',
msg: new Uint8Array(0),
};
should.equal(CosmosUtils.isGroupProposal(message), false);
should.equal(testnetHashUtils.isGroupProposal(message), false);
});
});
});