Skip to content

Commit 81d8ac4

Browse files
authored
fix(abstact-cosmos, sdk-coin-hash): add address validations when decoding group policy messages
2 parents f1e5d93 + 065913b commit 81d8ac4

File tree

5 files changed

+51
-22
lines changed

5 files changed

+51
-22
lines changed

modules/abstract-cosmos/src/lib/ContractCallBuilder.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,14 @@ export class ContractCallBuilder<CustomMessage = never> extends CosmosTransactio
2828
return message as MessageData<CustomMessage>;
2929
}
3030

31-
if (CosmosUtils.isGroupProposal(executeContractMessage)) {
31+
if (this._utils.isGroupProposal(executeContractMessage)) {
3232
return {
3333
typeUrl: constants.groupProposalMsgTypeUrl,
3434
value: executeContractMessage.msg,
3535
} as MessageData<CustomMessage>;
3636
}
3737

38-
if (CosmosUtils.isGroupVote(executeContractMessage)) {
38+
if (this._utils.isGroupVote(executeContractMessage)) {
3939
return {
4040
typeUrl: constants.groupVoteMsgTypeUrl,
4141
value: executeContractMessage.msg,

modules/abstract-cosmos/src/lib/utils.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1007,11 +1007,11 @@ export class CosmosUtils<CustomMessage = never> implements BaseUtils {
10071007
* @param {ExecuteContractMessage} message - The execute contract message to check
10081008
* @returns {boolean} true if the msg decodes to a group proposal
10091009
*/
1010-
static isGroupProposal(message: ExecuteContractMessage): boolean {
1010+
isGroupProposal(message: ExecuteContractMessage): boolean {
10111011
if (!message.msg || message.msg.length === 0) {
10121012
return false;
10131013
}
1014-
const result = CosmosUtils.decodeMsg(message.msg);
1014+
const result = this.decodeMsg(message.msg);
10151015
return result.typeUrl === constants.groupProposalMsgTypeUrl;
10161016
}
10171017

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

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

@@ -1043,6 +1043,9 @@ export class CosmosUtils<CustomMessage = never> implements BaseUtils {
10431043
if (
10441044
proposal.groupPolicyAddress &&
10451045
typeof proposal.groupPolicyAddress === 'string' &&
1046+
(this.isValidAddress(proposal.groupPolicyAddress) ||
1047+
this.isValidContractAddress(proposal.groupPolicyAddress) ||
1048+
this.isValidValidatorAddress(proposal.groupPolicyAddress)) &&
10461049
proposal.groupPolicyAddress.length > 0 &&
10471050
Array.isArray(proposal.proposers) &&
10481051
proposal.proposers.length > 0
@@ -1058,6 +1061,7 @@ export class CosmosUtils<CustomMessage = never> implements BaseUtils {
10581061
if (
10591062
vote.voter &&
10601063
typeof vote.voter === 'string' &&
1064+
this.isValidAddress(vote.voter) &&
10611065
vote.voter.length > 0 &&
10621066
vote.proposalId !== undefined &&
10631067
vote.proposalId !== null

modules/sdk-coin-hash/test/resources/hash.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ export const TEST_CONTRACT_CALL = {
2121
'0ae9020ae6020a222f636f736d6f732e67726f75702e76312e4d73675375626d697450726f706f73616c12bf020a3d74703174617a6566776b32653337326679326a71303877366c7a7467397972727663343930723267703476743864306663686c726671717961686730751229747031326e796e3833796e6577746d706b773332777136646738337778386e71706174363567636c641a0f65786368616e67652d636f6d6d697422bf010a2d2f70726f76656e616e63652e65786368616e67652e76312e4d7367436f6d6d697446756e647352657175657374128d010a3d74703174617a6566776b32653337326679326a71303877366c7a7467397972727663343930723267703476743864306663686c7266717179616867307510011a140a0975796c64732e6663631207313030303030302a3465786368616e67652d636f6d6d69743a32316561363334302d393961662d346338632d386661302d37356665306431626264343428011299010a500a460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a21039f03af548098ff794456d05f2adcc389cfc04abc6e16d92669a6255e33145b2112040a020801181012450a140a056e68617368120b34303030303030303030301090a10f22297470313276646e72376464636b78306d38753632717573727a713572363663656a35726434397a77661a0d70696f2d746573746e65742d3120f2cc0e',
2222
};
2323

24+
export const TEST_SUBMIT_PROPOSAL = {
25+
randomMsgSubmitProposalEncoded:
26+
'0ae9020ae6020a222f636f736d6f732e67726f75702e76312e4d73675375626d697450726f706f73616c12bf020a3d747031776838366b6561746c663879796e6765677675326a657567686e35706e7538306375397a7775397874636e6b6c72686b717277713474363864611229747031666b7963747165326b72636736743232726764726e796c6e7a68326e336c36613338323539731a0f65786368616e67652d636f6d6d697422bf010a2d2f70726f76656e616e63652e65786368616e67652e76312e4d7367436f6d6d697446756e647352657175657374128d010a3d747031776838366b6561746c663879796e6765677675326a657567686e35706e7538306375397a7775397874636e6b6c72686b7172777134743638646110011a140a0975796c64732e6663631207313030303030302a3465786368616e67652d636f6d6d69743a38326535666538392d393162652d346235332d623034322d3737666536383165343237372801126c0a4e0a460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a2103eb24ad39ec900dc2370a48e66d196e10df27ed9e4045b208f208d22adc2ca23512040a020801121a0a140a056e68617368120b353737373737373737373810dce40c',
27+
};
28+
2429
export const TEST_GROUP_VOTE = {
2530
// Real encoded MsgVote payload from explorer tx 29CDCDFFB38AE89BA0311D040BB0D83541B4E5B2973C12DE5B00E6C0F9078B12
2631
encodedVote: 'COn8ChIpdHAxZGoybjV5NDdheXEydDg0cGF5OGN5eTY1emg2ZTV1NWowZGpuajcYASICe30oAQ==',
@@ -37,6 +42,9 @@ export const TEST_GROUP_VOTE = {
3742
messageTypeUrl: '/cosmos.group.v1.MsgVote',
3843
expectedSignBytesHex:
3944
'0a550a530a182f636f736d6f732e67726f75702e76312e4d7367566f7465123708e9fc0a1229747031646a326e35793437617971327438347061793863797936357a6836653575356a30646a6e6a37180122027b7d28011299010a500a460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a21039f03af548098ff794456d05f2adcc389cfc04abc6e16d92669a6255e33145b2112040a020801181112450a140a056e68617368120b34303030303030303030301090a10f22297470313276646e72376464636b78306d38753632717573727a713572363663656a35726434397a77661a0d70696f2d746573746e65742d3120f2cc0e',
45+
//random unsigned MsgVote payload
46+
randomMsgVoteEncoded:
47+
'0a4e0a4c0a182f636f736d6f732e67726f75702e76312e4d7367566f7465123008ad571229747031666b7963747165326b72636736743232726764726e796c6e7a68326e336c366133383235397318011299010a500a460a1f2f636f736d6f732e63727970746f2e736563703235366b312e5075624b657912230a2103eb24ad39ec900dc2370a48e66d196e10df27ed9e4045b208f208d22adc2ca23512040a020801180112450a140a056e68617368120b34303030303030303030301090a10f22297470313276646e72376464636b78306d38753632717573727a713572363663656a35726434397a7766',
4048
};
4149

4250
export const TEST_ACCOUNT = {

modules/sdk-coin-hash/test/unit/transactionBuilder/transactionBuilder.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,22 @@ describe('Hash Transaction Builder', async () => {
6262
should.equal(rawTx, testTxData.signedTxBase64);
6363
});
6464

65+
it('should be able to build a submit proposal transaction from submit proposal transaction data', async function () {
66+
const txBuilder = factory.from(testData.TEST_SUBMIT_PROPOSAL.randomMsgSubmitProposalEncoded);
67+
const tx = await txBuilder.build();
68+
should.equal(tx.type, TransactionType.ContractCall);
69+
// Should recreate the same raw tx data when re-build and turned to broadcast format
70+
tx.cosmosLikeTransaction.sendMessages[0].typeUrl.should.equal('/cosmos.group.v1.MsgSubmitProposal');
71+
});
72+
73+
it('should be able to build a group vote transaction from group vote transaction data', async function () {
74+
const txBuilder = factory.from(testData.TEST_GROUP_VOTE.randomMsgVoteEncoded);
75+
const tx = await txBuilder.build();
76+
should.equal(tx.type, TransactionType.ContractCall);
77+
// Should recreate the same raw tx data when re-build and turned to broadcast format
78+
tx.cosmosLikeTransaction.sendMessages[0].typeUrl.should.equal('/cosmos.group.v1.MsgVote');
79+
});
80+
6581
it('should build a signed token tx from signed token tx data', async function () {
6682
const txBuilder = factory.from(tokenTestTxData.signedTxBase64);
6783
const tx = await txBuilder.build();

modules/sdk-coin-hash/test/unit/utils.ts

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import should from 'should';
2-
import { CosmosUtils } from '@bitgo/abstract-cosmos';
2+
import { NetworkType } from '@bitgo/statics';
33

4-
import utils from '../../src/lib/utils';
4+
import utils, { HashUtils } from '../../src/lib/utils';
55
import * as testData from '../resources/hash';
66
import { blockHash, txIds, TEST_CONTRACT_CALL, TEST_GROUP_VOTE } from '../resources/hash';
77

88
describe('utils', () => {
9+
const testnetHashUtils = new HashUtils(NetworkType.TESTNET);
910
it('should validate block hash correctly', () => {
1011
should.equal(utils.isValidBlockId(blockHash.hash1), true);
1112
should.equal(utils.isValidBlockId(blockHash.hash2), true);
@@ -48,7 +49,7 @@ describe('utils', () => {
4849

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

5354
should.exist(result.typeUrl);
5455
if (result.typeUrl) {
@@ -58,28 +59,28 @@ describe('utils', () => {
5859
});
5960

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

6364
should.not.exist(result.typeUrl);
6465
should.exist(result.error);
6566
});
6667

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

7071
should.not.exist(result.typeUrl);
7172
should.exist(result.error);
7273
});
7374

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

7778
should.not.exist(result.typeUrl);
7879
});
7980

8081
it('should accept Uint8Array input', () => {
8182
const bytes = Buffer.from(TEST_CONTRACT_CALL.encodedProposal, 'base64');
82-
const result = CosmosUtils.decodeMsg(bytes);
83+
const result = testnetHashUtils.decodeMsg(bytes);
8384

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

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

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

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

106107
should.exist(result.typeUrl);
107108
if (result.typeUrl) {
@@ -117,7 +118,7 @@ describe('utils', () => {
117118
contract: 'tp12nyn83ynewtmpkw32wq6dg83wx8nqpat65gcld',
118119
msg: Buffer.from(TEST_GROUP_VOTE.encodedVote, 'base64'),
119120
};
120-
should.equal(CosmosUtils.isGroupVote(message), true);
121+
should.equal(testnetHashUtils.isGroupVote(message), true);
121122
});
122123

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

132133
it('should return false when msg is empty', () => {
@@ -135,7 +136,7 @@ describe('utils', () => {
135136
contract: 'tp12nyn83ynewtmpkw32wq6dg83wx8nqpat65gcld',
136137
msg: new Uint8Array(0),
137138
};
138-
should.equal(CosmosUtils.isGroupVote(message), false);
139+
should.equal(testnetHashUtils.isGroupVote(message), false);
139140
});
140141
});
141142

@@ -146,7 +147,7 @@ describe('utils', () => {
146147
contract: 'tp12nyn83ynewtmpkw32wq6dg83wx8nqpat65gcld',
147148
msg: Buffer.from(TEST_CONTRACT_CALL.encodedProposal, 'base64'),
148149
};
149-
should.equal(CosmosUtils.isGroupProposal(message), true);
150+
should.equal(testnetHashUtils.isGroupProposal(message), true);
150151
});
151152

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

161162
it('should return false when msg is empty', () => {
@@ -164,7 +165,7 @@ describe('utils', () => {
164165
contract: 'tp12nyn83ynewtmpkw32wq6dg83wx8nqpat65gcld',
165166
msg: new Uint8Array(0),
166167
};
167-
should.equal(CosmosUtils.isGroupProposal(message), false);
168+
should.equal(testnetHashUtils.isGroupProposal(message), false);
168169
});
169170
});
170171
});

0 commit comments

Comments
 (0)