diff --git a/framework/src/modules/interoperability/mainchain/module.ts b/framework/src/modules/interoperability/mainchain/module.ts index 034dd3db38..708775d529 100644 --- a/framework/src/modules/interoperability/mainchain/module.ts +++ b/framework/src/modules/interoperability/mainchain/module.ts @@ -261,15 +261,7 @@ export class MainchainInteroperabilityModule extends BaseInteroperabilityModule throw new Error(`ownChainName must be equal to ${CHAIN_NAME_MAINCHAIN}.`); } - // if chainInfos is empty, then ownChainNonce == 0 - // If chainInfos is non-empty, ownChainNonce > 0 - if (chainInfos.length === 0 && ownChainNonce !== BigInt(0)) { - throw new Error(`ownChainNonce must be 0 if chainInfos is empty.`); - } else if (chainInfos.length !== 0 && ownChainNonce <= BigInt(0)) { - throw new Error(`ownChainNonce must be positive if chainInfos is not empty.`); - } - - this._verifyChainInfos(ctx, chainInfos, terminatedStateAccounts); + this._verifyChainInfos(ctx, chainInfos, ownChainNonce, terminatedStateAccounts); this._verifyTerminatedStateAccounts(chainInfos, terminatedStateAccounts, mainchainID); this._verifyTerminatedOutboxAccounts( chainInfos, @@ -284,8 +276,17 @@ export class MainchainInteroperabilityModule extends BaseInteroperabilityModule private _verifyChainInfos( ctx: GenesisBlockExecuteContext, chainInfos: ChainInfo[], + ownChainNonce: bigint, terminatedStateAccounts: TerminatedStateAccountWithChainID[], ) { + // if chainInfos is empty, then ownChainNonce == 0 + // If chainInfos is non-empty, ownChainNonce > 0 + if (chainInfos.length === 0 && ownChainNonce !== BigInt(0)) { + throw new Error(`ownChainNonce must be 0 if chainInfos is empty.`); + } else if (chainInfos.length !== 0 && ownChainNonce <= 0) { + throw new Error(`ownChainNonce must be positive if chainInfos is not empty.`); + } + // Each entry chainInfo in chainInfos has a unique chainInfo.chainID const chainIDs = chainInfos.map(info => info.chainID); if (!objectUtils.bufferArrayUniqueItems(chainIDs)) { diff --git a/framework/test/unit/modules/interoperability/base_cross_chain_update_command.spec.ts b/framework/test/unit/modules/interoperability/base_cross_chain_update_command.spec.ts index 5017a38fc8..51d4919429 100644 --- a/framework/test/unit/modules/interoperability/base_cross_chain_update_command.spec.ts +++ b/framework/test/unit/modules/interoperability/base_cross_chain_update_command.spec.ts @@ -94,6 +94,7 @@ describe('BaseCrossChainUpdateCommand', () => { senderPublicKey, signatures: [], }; + const minReturnFeePerByte = BigInt(10000000); const certificate = codec.encode(certificateSchema, { blockID: utils.getRandomBytes(32), @@ -322,6 +323,19 @@ describe('BaseCrossChainUpdateCommand', () => { .set(stateStore, params.sendingChainID, chainAccount); }); + it('should reject when ccu params validation fails', async () => { + const nonBufferSendingChainID = 2; + verifyContext = { + ...verifyContext, + params: { ...params, sendingChainID: nonBufferSendingChainID } as any, + }; + + // 2nd param `isMainchain` could be false + await expect(command['verifyCommon'](verifyContext, false)).rejects.toThrow( + `Property '.sendingChainID' should pass "dataType" keyword validation`, + ); + }); + it('should call validator.validate with crossChainUpdateTransactionParams schema', async () => { jest.spyOn(validator, 'validate'); @@ -1571,6 +1585,7 @@ describe('BaseCrossChainUpdateCommand', () => { describe('bounce', () => { const ccmStatus = CCMStatusCode.MODULE_NOT_SUPPORTED; const ccmProcessedEventCode = CCMProcessedCode.MODULE_NOT_SUPPORTED; + const ccmSize = 100; let stateStore: PrefixedStateReadWriter; beforeEach(async () => { @@ -1592,7 +1607,7 @@ describe('BaseCrossChainUpdateCommand', () => { }); await expect( - command['bounce'](context, 100, ccmStatus, ccmProcessedEventCode), + command['bounce'](context, ccmSize, ccmStatus, ccmProcessedEventCode), ).resolves.toBeUndefined(); expect(context.eventQueue.getEvents()).toHaveLength(1); @@ -1609,17 +1624,18 @@ describe('BaseCrossChainUpdateCommand', () => { }); it('should log event when ccm.fee is less than min fee', async () => { + const minFee = minReturnFeePerByte * BigInt(ccmSize); context = createCrossChainMessageContext({ ccm: { ...defaultCCM, status: CCMStatusCode.OK, - fee: BigInt(1), + fee: minFee - BigInt(1), }, stateStore, }); await expect( - command['bounce'](context, 100, ccmStatus, ccmProcessedEventCode), + command['bounce'](context, ccmSize, ccmStatus, ccmProcessedEventCode), ).resolves.toBeUndefined(); expect(context.eventQueue.getEvents()).toHaveLength(1); @@ -1649,7 +1665,7 @@ describe('BaseCrossChainUpdateCommand', () => { }); await expect( - command['bounce'](context, 100, ccmStatus, ccmProcessedEventCode), + command['bounce'](context, ccmSize, ccmStatus, ccmProcessedEventCode), ).resolves.toBeUndefined(); expect(internalMethod.addToOutbox).toHaveBeenCalledWith( @@ -1685,7 +1701,7 @@ describe('BaseCrossChainUpdateCommand', () => { }); await expect( - command['bounce'](context, 100, ccmStatus, ccmProcessedEventCode), + command['bounce'](context, ccmSize, ccmStatus, ccmProcessedEventCode), ).resolves.toBeUndefined(); expect(internalMethod.addToOutbox).toHaveBeenCalledWith( @@ -1715,7 +1731,7 @@ describe('BaseCrossChainUpdateCommand', () => { }); await expect( - command['bounce'](context, 100, ccmStatus, ccmProcessedEventCode), + command['bounce'](context, ccmSize, ccmStatus, ccmProcessedEventCode), ).resolves.toBeUndefined(); expect(internalMethod.addToOutbox).toHaveBeenCalledWith( @@ -1742,7 +1758,7 @@ describe('BaseCrossChainUpdateCommand', () => { }); await expect( - command['bounce'](context, 100, ccmStatus, ccmProcessedEventCode), + command['bounce'](context, ccmSize, ccmStatus, ccmProcessedEventCode), ).resolves.toBeUndefined(); expect(context.eventQueue.getEvents()).toHaveLength(2);