@0xsequence/catapult 1.3.6 → 1.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1492,7 +1492,7 @@ describe('ExecutionEngine', () => {
1492
1492
  }
1493
1493
 
1494
1494
  await expect((engine as any).executeAction(action, context, new Map())).rejects.toThrow(new Error(`Nick's method test failed for action "nick-test-fail-twice"`))
1495
- await expect((engine as any).executeAction(action, context, new Map())).rejects.toThrow(new Error('Nick\'s method test already performed this run'))
1495
+ await expect((engine as any).executeAction(action, context, new Map())).rejects.toThrow(new Error('Nick\'s method test already failed this run'))
1496
1496
  })
1497
1497
 
1498
1498
  it('should prevent passing Nick\'s method from being tested twice', async () => {
@@ -29,7 +29,7 @@ export class ExecutionEngine {
29
29
  private readonly noPostCheckConditions: boolean
30
30
  private readonly allowMultipleNicksMethodTests: boolean
31
31
  private readonly ignoreVerifyErrors: boolean
32
- private nicksMethodTested: boolean = false
32
+ private nicksMethodResult: boolean | undefined
33
33
  private verificationWarnings: Array<{
34
34
  actionName: string
35
35
  address: string
@@ -464,17 +464,17 @@ export class ExecutionEngine {
464
464
 
465
465
  // Handle gas limit with optional multiplier
466
466
  const network = context.getNetwork()
467
+ const signer = await context.getResolvedSigner()
467
468
  if (network.gasLimit) {
468
469
  const baseGasLimit = network.gasLimit
469
470
  txParams.gasLimit = gasMultiplier ? Math.floor(baseGasLimit * gasMultiplier) : baseGasLimit
470
471
  } else if (gasMultiplier) {
471
472
  // If gasMultiplier is specified but no network gasLimit, estimate gas first
472
- const signer = await context.getResolvedSigner()
473
473
  const estimatedGas = await signer.estimateGas({ to, data, value })
474
474
  txParams.gasLimit = Math.floor(Number(estimatedGas) * gasMultiplier)
475
475
  }
476
476
 
477
- const signer = await context.getResolvedSigner()
477
+ await this.checkFundsForTransaction(actionName, txParams, context, signer)
478
478
  const tx = await signer.sendTransaction(txParams)
479
479
 
480
480
  this.events.emitEvent({
@@ -795,17 +795,17 @@ export class ExecutionEngine {
795
795
 
796
796
  // Handle gas limit with optional multiplier
797
797
  const network = context.getNetwork()
798
+ const signer = await context.getResolvedSigner()
798
799
  if (network.gasLimit) {
799
800
  const baseGasLimit = network.gasLimit
800
801
  txParams.gasLimit = gasMultiplier ? Math.floor(baseGasLimit * gasMultiplier) : baseGasLimit
801
802
  } else if (gasMultiplier) {
802
803
  // If gasMultiplier is specified but no network gasLimit, estimate gas first
803
- const signer = await context.getResolvedSigner()
804
- const estimatedGas = await signer.estimateGas({ to: null, data, value })
804
+ const estimatedGas = await signer.estimateGas(txParams)
805
805
  txParams.gasLimit = Math.floor(Number(estimatedGas) * gasMultiplier)
806
806
  }
807
-
808
- const signer = await context.getResolvedSigner()
807
+
808
+ await this.checkFundsForTransaction(actionName, txParams, context, signer)
809
809
  const tx = await signer.sendTransaction(txParams)
810
810
 
811
811
  this.events.emitEvent({
@@ -855,17 +855,19 @@ export class ExecutionEngine {
855
855
  break
856
856
  }
857
857
  case 'test-nicks-method': {
858
- if (this.nicksMethodTested && !this.allowMultipleNicksMethodTests) {
859
- try {
860
- if (context.getOutput(`${action.name}.success`) === true) {
861
- // Return previous result
862
- break
863
- }
864
- } catch (e) {
865
- throw new Error(`Nick's method test already performed this run`)
858
+ if (this.nicksMethodResult !== undefined && !this.allowMultipleNicksMethodTests) {
859
+ if (this.nicksMethodResult === false) {
860
+ throw new Error(`Nick's method test already failed this run`)
866
861
  }
862
+ this.events.emitEvent({
863
+ type: 'debug_info',
864
+ level: 'debug',
865
+ data: {
866
+ message: `Nick's method test already passed this run`,
867
+ },
868
+ })
869
+ break
867
870
  }
868
- this.nicksMethodTested = true
869
871
 
870
872
  // Default bytecode if none provided
871
873
  const defaultBytecode = '0x608060405234801561001057600080fd5b5061013d806100206000396000f3fe60806040526004361061001e5760003560e01c80639c4ae2d014610023575b600080fd5b6100cb6004803603604081101561003957600080fd5b81019060208101813564010000000081111561005457600080fd5b82018360208201111561006657600080fd5b8035906020019184600183028401116401000000008311171561008857600080fd5b91908080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525092955050913592506100cd915050565b005b60008183516020850134f56040805173ffffffffffffffffffffffffffffffffffffffff83168152905191925081900360200190a050505056fea264697066735822122033609f614f03931b92d88c309d698449bb77efcd517328d341fa4f923c5d8c7964736f6c63430007060033'
@@ -884,6 +886,7 @@ export class ExecutionEngine {
884
886
  const fundingAmount = resolvedFundingAmount ? validateBigNumberish(resolvedFundingAmount, actionName, 'fundingAmount') : undefined
885
887
 
886
888
  const success = await this.testNicksMethod(bytecode, context, gasPrice, gasLimit, fundingAmount)
889
+ this.nicksMethodResult = success
887
890
 
888
891
  if (!success) {
889
892
  throw new Error(`Nick's method test failed for action "${actionName}"`)
@@ -1700,6 +1703,61 @@ export class ExecutionEngine {
1700
1703
  return sorted
1701
1704
  }
1702
1705
 
1706
+ /**
1707
+ * Checks if the signer has enough funds to cover the estimated cost of the transaction.
1708
+ * Returns true if the signer has enough funds, false if the signer does not have enough funds, and null if no gas price is available.
1709
+ */
1710
+ private async checkFundsForTransaction(actionName: string, txParams: ethers.TransactionRequest, context: ExecutionContext, signer: ethers.Signer): Promise<boolean | null> {
1711
+ try {
1712
+ const gasPrice = txParams.gasPrice || await context.provider.getFeeData().then(data => data.gasPrice)
1713
+ if (!gasPrice) {
1714
+ this.events.emitEvent({
1715
+ type: 'debug_info',
1716
+ level: 'warn',
1717
+ data: {
1718
+ actionName: actionName,
1719
+ message: `No gas price available`
1720
+ }
1721
+ })
1722
+ return null
1723
+ }
1724
+ const gasLimit = txParams.gasLimit || await signer.estimateGas(txParams)
1725
+ const requiredETH = BigInt(gasLimit) * BigInt(gasPrice)
1726
+ const signerBalance = await context.provider.getBalance(await signer.getAddress())
1727
+ this.events.emitEvent({
1728
+ type: 'debug_info',
1729
+ level: 'debug',
1730
+ data: {
1731
+ actionName: actionName,
1732
+ message: `Transaction ${txParams.gasLimit ? 'set' : 'estimated'} gas limit: ${gasLimit}, ${txParams.gasPrice ? 'set' : 'estimated'} gas price: ${ethers.formatUnits(gasPrice, 'gwei')} gwei, required ETH: ${ethers.formatEther(requiredETH)}`
1733
+ }
1734
+ })
1735
+ if (signerBalance < requiredETH) {
1736
+ this.events.emitEvent({
1737
+ type: 'debug_info',
1738
+ level: 'warn',
1739
+ data: {
1740
+ actionName: actionName,
1741
+ message: `Insufficient funds: signer has ${ethers.formatEther(signerBalance)} ETH but estimated cost is ${ethers.formatEther(requiredETH)} ETH`
1742
+ }
1743
+ })
1744
+ return false
1745
+ } else {
1746
+ return true
1747
+ }
1748
+ } catch (error) {
1749
+ this.events.emitEvent({
1750
+ type: 'debug_info',
1751
+ level: 'warn',
1752
+ data: {
1753
+ actionName: actionName,
1754
+ message: "Error checking signer balance: " + (error instanceof Error ? error.message : String(error))
1755
+ }
1756
+ })
1757
+ }
1758
+ return null
1759
+ }
1760
+
1703
1761
  /**
1704
1762
  * Get all verification warnings that were collected when ignoreVerifyErrors is enabled
1705
1763
  */
@@ -688,16 +688,26 @@ export class Deployer {
688
688
 
689
689
  /**
690
690
  * Populates the execution context with outputs from previously executed dependent jobs.
691
+ * Throws an error if any dependency failed.
691
692
  */
692
693
  private populateContextWithDependentJobOutputs(job: Job, context: ExecutionContext, network: Network): void {
693
694
  if (!job.depends_on) return
694
695
 
695
696
  for (const dependentJobName of job.depends_on) {
696
697
  const dependentJobResults = this.results.get(dependentJobName)
697
- if (!dependentJobResults) continue
698
+ if (!dependentJobResults) {
699
+ throw new Error(`Job "${job.name}" depends on "${dependentJobName}", but "${dependentJobName}" has not been executed yet.`)
700
+ }
698
701
 
699
702
  const networkResult = dependentJobResults.outputs.get(network.chainId)
700
- if (!networkResult || networkResult.status !== 'success') continue
703
+ if (!networkResult) {
704
+ throw new Error(`Job "${job.name}" depends on "${dependentJobName}", but "${dependentJobName}" has not been executed on network ${network.name} (chainId: ${network.chainId}).`)
705
+ }
706
+
707
+ if (networkResult.status !== 'success') {
708
+ const errorMessage = typeof networkResult.data === 'string' ? networkResult.data : 'Unknown error'
709
+ throw new Error(`Job "${job.name}" depends on "${dependentJobName}", but "${dependentJobName}" failed: ${errorMessage}`)
710
+ }
701
711
 
702
712
  // Add outputs with job name prefixes for cross-job access
703
713
  const outputs = networkResult.data as Map<string, unknown>