@@ -377,6 +377,79 @@ describe("Polygon Spoke Pool", function () {
377377 polygonSpokePool . connect ( relayer ) . multicall ( [ executeLeafData [ 0 ] , fillData [ 0 ] , executeLeafData [ 1 ] , fillData [ 1 ] ] )
378378 ) . to . be . reverted ;
379379 } ) ;
380+ it ( "Cannot use nested multicalls" , async function ( ) {
381+ // In this test we attempt to stuff the `executeRelayerRefundLeaf` call inside a nested multicall to bypass
382+ // the _validateMulticallData check in PolygonSpokePool.sol. This should not be possible.
383+ const l2ChainId = await owner . getChainId ( ) ;
384+ const leaves = buildRelayerRefundLeaves (
385+ [ l2ChainId , l2ChainId ] , // Destination chain ID.
386+ [ ethers . constants . Zero , ethers . constants . Zero ] , // amountToReturn.
387+ [ dai . address , dai . address ] , // l2Token.
388+ [ [ ] , [ ] ] , // refundAddresses.
389+ [ [ ] , [ ] ] // refundAmounts.
390+ ) ;
391+ const tree = await buildRelayerRefundTree ( leaves ) ;
392+
393+ // Relay leaves to Spoke
394+ const relayRootBundleData = polygonSpokePool . interface . encodeFunctionData ( "relayRootBundle" , [
395+ tree . getHexRoot ( ) ,
396+ mockTreeRoot ,
397+ ] ) ;
398+ await polygonSpokePool . connect ( fxChild ) . processMessageFromRoot ( 0 , owner . address , relayRootBundleData ) ;
399+
400+ // Deploy message handler and create fill with message that should succeed in isolation:
401+ const acrossMessageHandler = await createFake ( "AcrossMessageHandlerMock" ) ;
402+ await seedWallet ( relayer , [ dai ] , weth , toWei ( "2" ) ) ;
403+ await dai . connect ( relayer ) . approve ( polygonSpokePool . address , toWei ( "2" ) ) ;
404+
405+ const executeLeafData = [
406+ polygonSpokePool . interface . encodeFunctionData ( "executeRelayerRefundLeaf" , [
407+ 0 ,
408+ leaves [ 0 ] ,
409+ tree . getHexProof ( leaves [ 0 ] ) ,
410+ ] ) ,
411+ polygonSpokePool . interface . encodeFunctionData ( "executeRelayerRefundLeaf" , [
412+ 0 ,
413+ leaves [ 1 ] ,
414+ tree . getHexProof ( leaves [ 1 ] ) ,
415+ ] ) ,
416+ ] ;
417+ const currentTime = ( await polygonSpokePool . getCurrentTime ( ) ) . toNumber ( ) ;
418+ const relayData : V3RelayData = {
419+ depositor : addressToBytes ( owner . address ) ,
420+ recipient : addressToBytes ( acrossMessageHandler . address ) ,
421+ exclusiveRelayer : addressToBytes ( zeroAddress ) ,
422+ inputToken : addressToBytes ( dai . address ) ,
423+ outputToken : addressToBytes ( dai . address ) ,
424+ inputAmount : toWei ( "1" ) ,
425+ outputAmount : toWei ( "1" ) ,
426+ originChainId : originChainId ,
427+ depositId : toBN ( 0 ) ,
428+ fillDeadline : currentTime + 7200 ,
429+ exclusivityDeadline : 0 ,
430+ message : "0x1234" ,
431+ } ;
432+ const fillData = [
433+ polygonSpokePool . interface . encodeFunctionData ( "fillRelay" , [
434+ relayData ,
435+ repaymentChainId ,
436+ addressToBytes ( relayer . address ) ,
437+ ] ) ,
438+ polygonSpokePool . interface . encodeFunctionData ( "fillRelay" , [
439+ { ...relayData , depositId : 1 } ,
440+ repaymentChainId ,
441+ addressToBytes ( relayer . address ) ,
442+ ] ) ,
443+ ] ;
444+
445+ // Fills and execute leaf should succeed in isolation:
446+ await expect ( polygonSpokePool . connect ( relayer ) . estimateGas . multicall ( [ ...fillData ] ) ) . to . not . be . reverted ;
447+ await expect ( polygonSpokePool . connect ( relayer ) . estimateGas . multicall ( [ ...executeLeafData ] ) ) . to . not . be . reverted ;
448+
449+ const nestedMulticallData = [ polygonSpokePool . interface . encodeFunctionData ( "multicall" , [ executeLeafData ] ) ] ;
450+ await expect ( polygonSpokePool . connect ( relayer ) . estimateGas . multicall ( [ ...fillData , ...nestedMulticallData ] ) ) . to . be
451+ . reverted ;
452+ } ) ;
380453 it ( "PolygonTokenBridger retrieves and unwraps tokens correctly" , async function ( ) {
381454 const l1ChainId = await owner . getChainId ( ) ;
382455
0 commit comments