@0xsequence/catapult 1.3.3 → 1.3.5
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.
- package/README.md +16 -0
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +4 -2
- package/dist/commands/run.js.map +1 -1
- package/dist/lib/__tests__/deployer.spec.js +71 -1
- package/dist/lib/__tests__/deployer.spec.js.map +1 -1
- package/dist/lib/core/__tests__/engine.spec.js +270 -0
- package/dist/lib/core/__tests__/engine.spec.js.map +1 -1
- package/dist/lib/core/__tests__/resolver.spec.js +199 -2
- package/dist/lib/core/__tests__/resolver.spec.js.map +1 -1
- package/dist/lib/core/engine.d.ts +13 -0
- package/dist/lib/core/engine.d.ts.map +1 -1
- package/dist/lib/core/engine.js +59 -5
- package/dist/lib/core/engine.js.map +1 -1
- package/dist/lib/core/resolver.d.ts +1 -0
- package/dist/lib/core/resolver.d.ts.map +1 -1
- package/dist/lib/core/resolver.js +38 -8
- package/dist/lib/core/resolver.js.map +1 -1
- package/dist/lib/deployer.d.ts +2 -0
- package/dist/lib/deployer.d.ts.map +1 -1
- package/dist/lib/deployer.js +18 -1
- package/dist/lib/deployer.js.map +1 -1
- package/dist/lib/events/cli-adapter.d.ts.map +1 -1
- package/dist/lib/events/cli-adapter.js +20 -0
- package/dist/lib/events/cli-adapter.js.map +1 -1
- package/dist/lib/events/types.d.ts +25 -1
- package/dist/lib/events/types.d.ts.map +1 -1
- package/dist/lib/types/values.d.ts +8 -1
- package/dist/lib/types/values.d.ts.map +1 -1
- package/dist/lib/utils/assertion.d.ts +4 -0
- package/dist/lib/utils/assertion.d.ts.map +1 -0
- package/dist/lib/utils/assertion.js +27 -0
- package/dist/lib/utils/assertion.js.map +1 -0
- package/package.json +1 -1
- package/src/commands/run.ts +4 -1
- package/src/lib/__tests__/deployer.spec.ts +108 -1
- package/src/lib/core/__tests__/engine.spec.ts +321 -0
- package/src/lib/core/__tests__/resolver.spec.ts +231 -3
- package/src/lib/core/engine.ts +92 -6
- package/src/lib/core/resolver.ts +46 -9
- package/src/lib/deployer.ts +28 -1
- package/src/lib/events/cli-adapter.ts +24 -0
- package/src/lib/events/types.ts +29 -1
- package/src/lib/types/values.ts +10 -1
- package/src/lib/utils/assertion.ts +24 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ethers } from 'ethers'
|
|
2
2
|
import { ValueResolver } from '../resolver'
|
|
3
3
|
import { ExecutionContext } from '../context'
|
|
4
|
-
import { BasicArithmeticValue, Network, ReadBalanceValue, ComputeCreate2Value, ConstructorEncodeValue, AbiEncodeValue, AbiPackValue, CallValue, ContractExistsValue } from '../../types'
|
|
4
|
+
import { BasicArithmeticValue, Network, ReadBalanceValue, ComputeCreate2Value, ConstructorEncodeValue, AbiEncodeValue, AbiPackValue, CallValue, ContractExistsValue, ComputeCreateValue } from '../../types'
|
|
5
5
|
import { ContractRepository } from '../../contracts/repository'
|
|
6
6
|
|
|
7
7
|
describe('ValueResolver', () => {
|
|
@@ -15,7 +15,14 @@ describe('ValueResolver', () => {
|
|
|
15
15
|
mockRegistry = new ContractRepository()
|
|
16
16
|
// Allow configuring RPC URL via environment variable for CI
|
|
17
17
|
const rpcUrl = process.env.RPC_URL || 'http://127.0.0.1:8545'
|
|
18
|
-
mockNetwork = {
|
|
18
|
+
mockNetwork = {
|
|
19
|
+
name: 'testnet',
|
|
20
|
+
chainId: 999,
|
|
21
|
+
rpcUrl,
|
|
22
|
+
supports: ["sourcify", "etherscan_v2"],
|
|
23
|
+
gasLimit: 10000000,
|
|
24
|
+
evmVersion: 'cancun',
|
|
25
|
+
}
|
|
19
26
|
// A dummy private key is fine as these tests don't send transactions
|
|
20
27
|
const mockPrivateKey = '0x0000000000000000000000000000000000000000000000000000000000000001'
|
|
21
28
|
context = new ExecutionContext(mockNetwork, mockPrivateKey, mockRegistry)
|
|
@@ -494,6 +501,155 @@ describe('ValueResolver', () => {
|
|
|
494
501
|
})
|
|
495
502
|
})
|
|
496
503
|
|
|
504
|
+
describe('compute-create', () => {
|
|
505
|
+
it('should compute CREATE address with hardcoded test case 1', async () => {
|
|
506
|
+
const value: ComputeCreateValue = {
|
|
507
|
+
type: 'compute-create',
|
|
508
|
+
arguments: {
|
|
509
|
+
deployerAddress: '0x0000000000000000000000000000000000000000',
|
|
510
|
+
nonce: '0',
|
|
511
|
+
},
|
|
512
|
+
}
|
|
513
|
+
const result = await resolver.resolve(value, context)
|
|
514
|
+
expect(result).toBe('0xBd770416a3345F91E4B34576cb804a576fa48EB1')
|
|
515
|
+
})
|
|
516
|
+
|
|
517
|
+
it('should compute CREATE address with hardcoded test case 2', async () => {
|
|
518
|
+
const value: ComputeCreateValue = {
|
|
519
|
+
type: 'compute-create',
|
|
520
|
+
arguments: {
|
|
521
|
+
deployerAddress: '0xC6064FfBaDB0687Da29721C8EC02ACa71e735a3e',
|
|
522
|
+
nonce: '1',
|
|
523
|
+
},
|
|
524
|
+
}
|
|
525
|
+
const result = await resolver.resolve(value, context)
|
|
526
|
+
expect(result).toBe('0x6d2E686984620c01Af3cd125F9E1A2E23a972FFc')
|
|
527
|
+
})
|
|
528
|
+
|
|
529
|
+
it('should compute CREATE address with hardcoded test case 3', async () => {
|
|
530
|
+
const value: ComputeCreateValue = {
|
|
531
|
+
type: 'compute-create',
|
|
532
|
+
arguments: {
|
|
533
|
+
deployerAddress: '0xC6064FfBaDB0687Da29721C8EC02ACa71e735a3e',
|
|
534
|
+
nonce: '2',
|
|
535
|
+
},
|
|
536
|
+
}
|
|
537
|
+
const result = await resolver.resolve(value, context)
|
|
538
|
+
expect(result).toBe('0xBA6CfaFc33eD8229D2Af9a5a7BC22e8834cE0873')
|
|
539
|
+
})
|
|
540
|
+
|
|
541
|
+
it('should compute CREATE address with hardcoded test case ERC-2470', async () => {
|
|
542
|
+
const value: ComputeCreateValue = {
|
|
543
|
+
type: 'compute-create',
|
|
544
|
+
arguments: {
|
|
545
|
+
deployerAddress: '0xBb6e024b9cFFACB947A71991E386681B1Cd1477D',
|
|
546
|
+
nonce: '0',
|
|
547
|
+
},
|
|
548
|
+
}
|
|
549
|
+
const result = await resolver.resolve(value, context)
|
|
550
|
+
expect(result).toBe('0xce0042B868300000d44A59004Da54A005ffdcf9f')
|
|
551
|
+
})
|
|
552
|
+
|
|
553
|
+
it('should compute CREATE address with hardcoded test case Universal Deployer', async () => {
|
|
554
|
+
const value: ComputeCreateValue = {
|
|
555
|
+
type: 'compute-create',
|
|
556
|
+
arguments: {
|
|
557
|
+
deployerAddress: '0x9c5a87452d4FAC0cbd53BDCA580b20A45526B3AB',
|
|
558
|
+
nonce: '0',
|
|
559
|
+
},
|
|
560
|
+
}
|
|
561
|
+
const result = await resolver.resolve(value, context)
|
|
562
|
+
expect(result).toBe('0x1B926fBB24A9F78DCDd3272f2d86F5D0660E59c0')
|
|
563
|
+
})
|
|
564
|
+
|
|
565
|
+
it('should resolve values from context before computing CREATE address', async () => {
|
|
566
|
+
context.setOutput('myDeployer', '0x0000000000000000000000000000000000000000')
|
|
567
|
+
context.setOutput('myNonce', '0')
|
|
568
|
+
|
|
569
|
+
const value: ComputeCreateValue = {
|
|
570
|
+
type: 'compute-create',
|
|
571
|
+
arguments: {
|
|
572
|
+
deployerAddress: '{{myDeployer}}',
|
|
573
|
+
nonce: '{{myNonce}}',
|
|
574
|
+
},
|
|
575
|
+
}
|
|
576
|
+
const result = await resolver.resolve(value, context)
|
|
577
|
+
expect(result).toBe('0xBd770416a3345F91E4B34576cb804a576fa48EB1')
|
|
578
|
+
})
|
|
579
|
+
|
|
580
|
+
it('should throw error for invalid deployer address', async () => {
|
|
581
|
+
const value: ComputeCreateValue = {
|
|
582
|
+
type: 'compute-create',
|
|
583
|
+
arguments: {
|
|
584
|
+
deployerAddress: 'invalid-address',
|
|
585
|
+
nonce: '0',
|
|
586
|
+
},
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
await expect(resolver.resolve(value, context)).rejects.toThrow('Invalid deployer address: invalid-address')
|
|
590
|
+
})
|
|
591
|
+
|
|
592
|
+
it('should throw error for invalid nonce', async () => {
|
|
593
|
+
const value: ComputeCreateValue = {
|
|
594
|
+
type: 'compute-create',
|
|
595
|
+
arguments: {
|
|
596
|
+
deployerAddress: '0x0000000000000000000000000000000000000000',
|
|
597
|
+
nonce: 'invalid-nonce',
|
|
598
|
+
},
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
await expect(resolver.resolve(value, context)).rejects.toThrow('Invalid nonce: invalid-nonce')
|
|
602
|
+
})
|
|
603
|
+
|
|
604
|
+
it('should throw error for null deployer address', async () => {
|
|
605
|
+
const value: ComputeCreateValue = {
|
|
606
|
+
type: 'compute-create',
|
|
607
|
+
arguments: {
|
|
608
|
+
deployerAddress: null as any,
|
|
609
|
+
nonce: '0',
|
|
610
|
+
},
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
await expect(resolver.resolve(value, context)).rejects.toThrow('Invalid deployer address: null')
|
|
614
|
+
})
|
|
615
|
+
|
|
616
|
+
it('should throw error for undefined nonce', async () => {
|
|
617
|
+
const value: ComputeCreateValue = {
|
|
618
|
+
type: 'compute-create',
|
|
619
|
+
arguments: {
|
|
620
|
+
deployerAddress: '0x0000000000000000000000000000000000000000',
|
|
621
|
+
nonce: undefined as any,
|
|
622
|
+
},
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
await expect(resolver.resolve(value, context)).rejects.toThrow('Invalid nonce: undefined')
|
|
626
|
+
})
|
|
627
|
+
|
|
628
|
+
it('should handle checksummed addresses', async () => {
|
|
629
|
+
const value: ComputeCreateValue = {
|
|
630
|
+
type: 'compute-create',
|
|
631
|
+
arguments: {
|
|
632
|
+
deployerAddress: '0xdEADBEeF00000000000000000000000000000000',
|
|
633
|
+
nonce: '0',
|
|
634
|
+
},
|
|
635
|
+
}
|
|
636
|
+
const result = await resolver.resolve(value, context)
|
|
637
|
+
expect(result).toBe('0xf2048C36a5536FeA3Bc71d49ed59f2c65C546EEA')
|
|
638
|
+
})
|
|
639
|
+
|
|
640
|
+
it('should handle number nonce', async () => {
|
|
641
|
+
const value: ComputeCreateValue = {
|
|
642
|
+
type: 'compute-create',
|
|
643
|
+
arguments: {
|
|
644
|
+
deployerAddress: '0x0000000000000000000000000000000000000000',
|
|
645
|
+
nonce: 69420,
|
|
646
|
+
},
|
|
647
|
+
}
|
|
648
|
+
const result = await resolver.resolve(value, context)
|
|
649
|
+
expect(result).toBe('0x7Bd7F19787DA009bD75b849c92Db10CE11916487')
|
|
650
|
+
})
|
|
651
|
+
})
|
|
652
|
+
|
|
497
653
|
describe('constructor-encode', () => {
|
|
498
654
|
it('should encode creation code with no constructor arguments', async () => {
|
|
499
655
|
const value = {
|
|
@@ -1057,7 +1213,7 @@ describe('ValueResolver', () => {
|
|
|
1057
1213
|
})
|
|
1058
1214
|
})
|
|
1059
1215
|
|
|
1060
|
-
describe('artifact function expressions', () => {
|
|
1216
|
+
describe('contract artifact function expressions', () => {
|
|
1061
1217
|
beforeEach(() => {
|
|
1062
1218
|
// Add test artifacts to the registry
|
|
1063
1219
|
const testArtifact1 = {
|
|
@@ -1341,6 +1497,78 @@ describe('ValueResolver', () => {
|
|
|
1341
1497
|
})
|
|
1342
1498
|
})
|
|
1343
1499
|
|
|
1500
|
+
describe('network function expressions', () => {
|
|
1501
|
+
describe('Network().property', () => {
|
|
1502
|
+
it('should return name for valid network', async () => {
|
|
1503
|
+
const result = await resolver.resolve('{{Network().name}}', context)
|
|
1504
|
+
expect(result).toBe(mockNetwork.name)
|
|
1505
|
+
})
|
|
1506
|
+
|
|
1507
|
+
it('should return handle whitespace after expression', async () => {
|
|
1508
|
+
const result = await resolver.resolve('{{Network().name }}', context)
|
|
1509
|
+
expect(result).toBe(mockNetwork.name)
|
|
1510
|
+
})
|
|
1511
|
+
|
|
1512
|
+
it('should return handle whitespace before expression', async () => {
|
|
1513
|
+
const result = await resolver.resolve('{{ Network().name}}', context)
|
|
1514
|
+
expect(result).toBe(mockNetwork.name)
|
|
1515
|
+
})
|
|
1516
|
+
|
|
1517
|
+
it('should return handle whitespace around expression', async () => {
|
|
1518
|
+
const result = await resolver.resolve('{{ Network().name }}', context)
|
|
1519
|
+
expect(result).toBe(mockNetwork.name)
|
|
1520
|
+
})
|
|
1521
|
+
|
|
1522
|
+
it('should return chainId for valid network', async () => {
|
|
1523
|
+
const result = await resolver.resolve('{{Network().chainId}}', context)
|
|
1524
|
+
expect(result).toBe(mockNetwork.chainId)
|
|
1525
|
+
})
|
|
1526
|
+
|
|
1527
|
+
it('should return rpcUrl for valid network', async () => {
|
|
1528
|
+
const result = await resolver.resolve('{{Network().rpcUrl}}', context)
|
|
1529
|
+
expect(result).toBe(mockNetwork.rpcUrl)
|
|
1530
|
+
})
|
|
1531
|
+
|
|
1532
|
+
it('should return supports for valid network', async () => {
|
|
1533
|
+
const result = await resolver.resolve('{{Network().supports}}', context)
|
|
1534
|
+
expect(result).toBe(mockNetwork.supports)
|
|
1535
|
+
})
|
|
1536
|
+
|
|
1537
|
+
it('should return gasLimit for valid network', async () => {
|
|
1538
|
+
const result = await resolver.resolve('{{Network().gasLimit}}', context)
|
|
1539
|
+
expect(result).toBe(mockNetwork.gasLimit)
|
|
1540
|
+
})
|
|
1541
|
+
|
|
1542
|
+
it('should return testnet for valid network', async () => {
|
|
1543
|
+
// Note: Expect true because testnet is not set
|
|
1544
|
+
const result = await resolver.resolve('{{Network().testnet}}', context)
|
|
1545
|
+
expect(result).toBe(false)
|
|
1546
|
+
})
|
|
1547
|
+
|
|
1548
|
+
it('should return evmVersion for valid network', async () => {
|
|
1549
|
+
const result = await resolver.resolve('{{Network().evmVersion}}', context)
|
|
1550
|
+
expect(result).toBe(mockNetwork.evmVersion)
|
|
1551
|
+
})
|
|
1552
|
+
})
|
|
1553
|
+
|
|
1554
|
+
describe('invalid Network expressions', () => {
|
|
1555
|
+
it('should fail for invalid property', async () => {
|
|
1556
|
+
await expect(resolver.resolve('{{Network().invalid}}', context))
|
|
1557
|
+
.rejects.toThrow('Property "invalid" does not exist on network')
|
|
1558
|
+
})
|
|
1559
|
+
|
|
1560
|
+
it('should fail for undefined property', async () => {
|
|
1561
|
+
await expect(resolver.resolve('{{Network().undefined}}', context))
|
|
1562
|
+
.rejects.toThrow('Property "undefined" does not exist on network')
|
|
1563
|
+
})
|
|
1564
|
+
|
|
1565
|
+
it('should fail for network with reference', async () => {
|
|
1566
|
+
await expect(resolver.resolve('{{Network(testnet).name}}', context))
|
|
1567
|
+
.rejects.toThrow('Failed to resolve expression \"{{Network(testnet).name}}\". It is not a valid Contract(...) or Network() reference, local scope variable, constant, or a known output.')
|
|
1568
|
+
})
|
|
1569
|
+
})
|
|
1570
|
+
})
|
|
1571
|
+
|
|
1344
1572
|
describe('call', () => {
|
|
1345
1573
|
let testContractAddress: string
|
|
1346
1574
|
let anvilProvider: ethers.JsonRpcProvider
|
package/src/lib/core/engine.ts
CHANGED
|
@@ -13,6 +13,7 @@ export type EngineOptions = {
|
|
|
13
13
|
verificationRegistry?: VerificationPlatformRegistry
|
|
14
14
|
noPostCheckConditions?: boolean
|
|
15
15
|
allowMultipleNicksMethodTests?: boolean
|
|
16
|
+
ignoreVerifyErrors?: boolean
|
|
16
17
|
}
|
|
17
18
|
|
|
18
19
|
/**
|
|
@@ -27,7 +28,17 @@ export class ExecutionEngine {
|
|
|
27
28
|
private readonly verificationRegistry: VerificationPlatformRegistry
|
|
28
29
|
private readonly noPostCheckConditions: boolean
|
|
29
30
|
private readonly allowMultipleNicksMethodTests: boolean
|
|
31
|
+
private readonly ignoreVerifyErrors: boolean
|
|
30
32
|
private nicksMethodTested: boolean = false
|
|
33
|
+
private verificationWarnings: Array<{
|
|
34
|
+
actionName: string
|
|
35
|
+
address: string
|
|
36
|
+
contractName: string
|
|
37
|
+
platform: string
|
|
38
|
+
error: string
|
|
39
|
+
jobName?: string
|
|
40
|
+
networkName?: string
|
|
41
|
+
}> = []
|
|
31
42
|
|
|
32
43
|
constructor(templates: Map<string, Template>, options?: EngineOptions) {
|
|
33
44
|
this.resolver = new ValueResolver()
|
|
@@ -36,6 +47,7 @@ export class ExecutionEngine {
|
|
|
36
47
|
this.verificationRegistry = options?.verificationRegistry || createDefaultVerificationRegistry()
|
|
37
48
|
this.noPostCheckConditions = options?.noPostCheckConditions ?? false
|
|
38
49
|
this.allowMultipleNicksMethodTests = options?.allowMultipleNicksMethodTests ?? false
|
|
50
|
+
this.ignoreVerifyErrors = options?.ignoreVerifyErrors ?? false
|
|
39
51
|
}
|
|
40
52
|
|
|
41
53
|
/**
|
|
@@ -631,6 +643,20 @@ export class ExecutionEngine {
|
|
|
631
643
|
)
|
|
632
644
|
anySuccess = true
|
|
633
645
|
} catch (error) {
|
|
646
|
+
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
647
|
+
|
|
648
|
+
// If ignoreVerifyErrors is enabled, add to warnings and continue
|
|
649
|
+
if (this.ignoreVerifyErrors) {
|
|
650
|
+
this.verificationWarnings.push({
|
|
651
|
+
actionName: actionName,
|
|
652
|
+
address,
|
|
653
|
+
contractName,
|
|
654
|
+
platform: platform.name,
|
|
655
|
+
error: errorMessage,
|
|
656
|
+
networkName: network.name
|
|
657
|
+
})
|
|
658
|
+
}
|
|
659
|
+
|
|
634
660
|
// Log the error but continue with other platforms
|
|
635
661
|
this.events.emitEvent({
|
|
636
662
|
type: 'verification_failed',
|
|
@@ -640,14 +666,26 @@ export class ExecutionEngine {
|
|
|
640
666
|
address,
|
|
641
667
|
contractName,
|
|
642
668
|
platform: platform.name,
|
|
643
|
-
error:
|
|
669
|
+
error: errorMessage
|
|
644
670
|
}
|
|
645
671
|
})
|
|
646
672
|
}
|
|
647
673
|
}
|
|
648
674
|
|
|
649
675
|
if (!anySuccess) {
|
|
650
|
-
|
|
676
|
+
if (this.ignoreVerifyErrors) {
|
|
677
|
+
// Don't throw error if ignoreVerifyErrors is enabled - warnings already collected
|
|
678
|
+
this.events.emitEvent({
|
|
679
|
+
type: 'verification_skipped',
|
|
680
|
+
level: 'warn',
|
|
681
|
+
data: {
|
|
682
|
+
actionName: actionName,
|
|
683
|
+
reason: `Verification failed on all configured platforms for network ${network.name}, but continuing due to --ignore-verify-errors`
|
|
684
|
+
}
|
|
685
|
+
})
|
|
686
|
+
} else {
|
|
687
|
+
throw new Error(`Verification failed on all configured platforms for network ${network.name}`)
|
|
688
|
+
}
|
|
651
689
|
}
|
|
652
690
|
} else {
|
|
653
691
|
// Handle specific platform(s) verification
|
|
@@ -673,6 +711,20 @@ export class ExecutionEngine {
|
|
|
673
711
|
)
|
|
674
712
|
anySuccess = true
|
|
675
713
|
} catch (error) {
|
|
714
|
+
const errorMessage = error instanceof Error ? error.message : String(error)
|
|
715
|
+
|
|
716
|
+
// If ignoreVerifyErrors is enabled, add to warnings
|
|
717
|
+
if (this.ignoreVerifyErrors) {
|
|
718
|
+
this.verificationWarnings.push({
|
|
719
|
+
actionName: actionName,
|
|
720
|
+
address,
|
|
721
|
+
contractName,
|
|
722
|
+
platform: platform.name,
|
|
723
|
+
error: errorMessage,
|
|
724
|
+
networkName: network.name
|
|
725
|
+
})
|
|
726
|
+
}
|
|
727
|
+
|
|
676
728
|
// Log the error but continue with other platforms if multiple specified
|
|
677
729
|
this.events.emitEvent({
|
|
678
730
|
type: 'verification_failed',
|
|
@@ -682,19 +734,31 @@ export class ExecutionEngine {
|
|
|
682
734
|
address,
|
|
683
735
|
contractName,
|
|
684
736
|
platform: platform.name,
|
|
685
|
-
error:
|
|
737
|
+
error: errorMessage
|
|
686
738
|
}
|
|
687
739
|
})
|
|
688
740
|
|
|
689
|
-
// If only one platform specified, re-throw the error
|
|
690
|
-
if (platformsToTry.length === 1) {
|
|
741
|
+
// If only one platform specified, re-throw the error unless ignoreVerifyErrors is enabled
|
|
742
|
+
if (platformsToTry.length === 1 && !this.ignoreVerifyErrors) {
|
|
691
743
|
throw error
|
|
692
744
|
}
|
|
693
745
|
}
|
|
694
746
|
}
|
|
695
747
|
|
|
696
748
|
if (!anySuccess && platformsToTry.length > 1) {
|
|
697
|
-
|
|
749
|
+
if (this.ignoreVerifyErrors) {
|
|
750
|
+
// Don't throw error if ignoreVerifyErrors is enabled - warnings already collected
|
|
751
|
+
this.events.emitEvent({
|
|
752
|
+
type: 'verification_skipped',
|
|
753
|
+
level: 'warn',
|
|
754
|
+
data: {
|
|
755
|
+
actionName: actionName,
|
|
756
|
+
reason: `Verification failed on all specified platforms: ${platformsToTry.join(', ')}, but continuing due to --ignore-verify-errors`
|
|
757
|
+
}
|
|
758
|
+
})
|
|
759
|
+
} else {
|
|
760
|
+
throw new Error(`Verification failed on all specified platforms: ${platformsToTry.join(', ')}`)
|
|
761
|
+
}
|
|
698
762
|
}
|
|
699
763
|
}
|
|
700
764
|
|
|
@@ -1635,4 +1699,26 @@ export class ExecutionEngine {
|
|
|
1635
1699
|
|
|
1636
1700
|
return sorted
|
|
1637
1701
|
}
|
|
1702
|
+
|
|
1703
|
+
/**
|
|
1704
|
+
* Get all verification warnings that were collected when ignoreVerifyErrors is enabled
|
|
1705
|
+
*/
|
|
1706
|
+
public getVerificationWarnings(): Array<{
|
|
1707
|
+
actionName: string
|
|
1708
|
+
address: string
|
|
1709
|
+
contractName: string
|
|
1710
|
+
platform: string
|
|
1711
|
+
error: string
|
|
1712
|
+
jobName?: string
|
|
1713
|
+
networkName?: string
|
|
1714
|
+
}> {
|
|
1715
|
+
return [...this.verificationWarnings]
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
/**
|
|
1719
|
+
* Clear verification warnings (useful for testing)
|
|
1720
|
+
*/
|
|
1721
|
+
public clearVerificationWarnings(): void {
|
|
1722
|
+
this.verificationWarnings = []
|
|
1723
|
+
}
|
|
1638
1724
|
}
|
package/src/lib/core/resolver.ts
CHANGED
|
@@ -5,16 +5,17 @@ import {
|
|
|
5
5
|
AbiEncodeValue,
|
|
6
6
|
AbiPackValue,
|
|
7
7
|
ConstructorEncodeValue,
|
|
8
|
+
ComputeCreateValue,
|
|
8
9
|
ComputeCreate2Value,
|
|
9
10
|
ReadBalanceValue,
|
|
10
11
|
BasicArithmeticValue,
|
|
11
12
|
CallValue,
|
|
12
|
-
ContractExistsCondition,
|
|
13
13
|
ContractExistsValue,
|
|
14
14
|
JobCompletedValue,
|
|
15
15
|
ReadJsonValue,
|
|
16
16
|
} from '../types'
|
|
17
17
|
import { ExecutionContext } from './context'
|
|
18
|
+
import { isAddress, isBigNumberish, isBytesLike } from '../utils/assertion'
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* A scope for resolving local variables, such as template arguments.
|
|
@@ -115,6 +116,22 @@ export class ValueResolver {
|
|
|
115
116
|
return value
|
|
116
117
|
}
|
|
117
118
|
|
|
119
|
+
// Check for Network(...) property access
|
|
120
|
+
const networkMatch = expression.match(/^Network\(\)\.(.\w+)$/)
|
|
121
|
+
if (networkMatch) {
|
|
122
|
+
const [, property] = networkMatch
|
|
123
|
+
const network = context.getNetwork()
|
|
124
|
+
if (property === 'testnet') {
|
|
125
|
+
// Special case for testnet property. Default to false if not set.
|
|
126
|
+
return !!network.testnet
|
|
127
|
+
}
|
|
128
|
+
const value = (network as any)[property]
|
|
129
|
+
if (value === undefined) {
|
|
130
|
+
throw new Error(`Property "${property}" does not exist on network`)
|
|
131
|
+
}
|
|
132
|
+
return value
|
|
133
|
+
}
|
|
134
|
+
|
|
118
135
|
// Check scope for local variables (template arguments) first
|
|
119
136
|
if (scope.has(expression)) {
|
|
120
137
|
return scope.get(expression)
|
|
@@ -131,7 +148,7 @@ export class ValueResolver {
|
|
|
131
148
|
return context.getOutput(expression)
|
|
132
149
|
} catch (e) {
|
|
133
150
|
// Provide a more helpful error if an unresolved reference is found
|
|
134
|
-
throw new Error(`Failed to resolve expression "{{${expression}}}". It is not a valid Contract(...) reference, local scope variable, constant, or a known output.`)
|
|
151
|
+
throw new Error(`Failed to resolve expression "{{${expression}}}". It is not a valid Contract(...) or Network() reference, local scope variable, constant, or a known output.`)
|
|
135
152
|
}
|
|
136
153
|
}
|
|
137
154
|
|
|
@@ -154,6 +171,8 @@ export class ValueResolver {
|
|
|
154
171
|
return this.resolveAbiPack(resolvedArgs as AbiPackValue['arguments'])
|
|
155
172
|
case 'constructor-encode':
|
|
156
173
|
return this.resolveConstructorEncode(resolvedArgs as ConstructorEncodeValue['arguments'])
|
|
174
|
+
case 'compute-create':
|
|
175
|
+
return this.resolveComputeCreate(resolvedArgs as ComputeCreateValue['arguments'])
|
|
157
176
|
case 'compute-create2':
|
|
158
177
|
return this.resolveComputeCreate2(resolvedArgs as ComputeCreate2Value['arguments'])
|
|
159
178
|
case 'read-balance':
|
|
@@ -260,7 +279,7 @@ export class ValueResolver {
|
|
|
260
279
|
}
|
|
261
280
|
|
|
262
281
|
// Validate that creation code is valid bytecode
|
|
263
|
-
if (!
|
|
282
|
+
if (!isBytesLike(creationCode)) {
|
|
264
283
|
throw new Error(`Invalid creation code: ${creationCode}`)
|
|
265
284
|
}
|
|
266
285
|
|
|
@@ -280,18 +299,36 @@ export class ValueResolver {
|
|
|
280
299
|
return '0x' + cleanCreationCode + cleanEncodedArgs
|
|
281
300
|
}
|
|
282
301
|
|
|
302
|
+
private resolveComputeCreate(args: ComputeCreateValue['arguments']): string {
|
|
303
|
+
const { deployerAddress, nonce } = args
|
|
304
|
+
// Check if the deployer address is a valid address
|
|
305
|
+
if (!isAddress(deployerAddress)) {
|
|
306
|
+
throw new Error(`Invalid deployer address: ${deployerAddress}`)
|
|
307
|
+
}
|
|
308
|
+
// Check if the nonce is a valid value
|
|
309
|
+
if (!isBigNumberish(nonce)) {
|
|
310
|
+
throw new Error(`Invalid nonce: ${nonce}`)
|
|
311
|
+
}
|
|
312
|
+
const bnNonce = ethers.toBigInt(nonce)
|
|
313
|
+
// Create the create address
|
|
314
|
+
return ethers.getCreateAddress({
|
|
315
|
+
from: deployerAddress,
|
|
316
|
+
nonce: bnNonce,
|
|
317
|
+
})
|
|
318
|
+
}
|
|
319
|
+
|
|
283
320
|
private resolveComputeCreate2(args: ComputeCreate2Value['arguments']): string {
|
|
284
321
|
const { deployerAddress, salt, initCode } = args
|
|
285
322
|
// Check if the deployer address is a valid address
|
|
286
|
-
if (!
|
|
323
|
+
if (!isAddress(deployerAddress)) {
|
|
287
324
|
throw new Error(`Invalid deployer address: ${deployerAddress}`)
|
|
288
325
|
}
|
|
289
326
|
// Check if the salt is a valid bytes value
|
|
290
|
-
if (!
|
|
327
|
+
if (!isBytesLike(salt)) {
|
|
291
328
|
throw new Error(`Invalid salt: ${salt}`)
|
|
292
329
|
}
|
|
293
330
|
// Check if the init code is a valid bytes value
|
|
294
|
-
if (!
|
|
331
|
+
if (!isBytesLike(initCode)) {
|
|
295
332
|
throw new Error(`Invalid init code: ${initCode}`)
|
|
296
333
|
}
|
|
297
334
|
// Hash the init code using Keccak256
|
|
@@ -304,7 +341,7 @@ export class ValueResolver {
|
|
|
304
341
|
// Check if the address is a valid address
|
|
305
342
|
const addressValue = args.address as any
|
|
306
343
|
|
|
307
|
-
if (!
|
|
344
|
+
if (!isAddress(addressValue)) {
|
|
308
345
|
throw new Error(`Invalid address: ${addressValue}`)
|
|
309
346
|
}
|
|
310
347
|
|
|
@@ -349,7 +386,7 @@ export class ValueResolver {
|
|
|
349
386
|
}
|
|
350
387
|
|
|
351
388
|
// Validate that the target address is a valid Ethereum address
|
|
352
|
-
if (!
|
|
389
|
+
if (!isAddress(to)) {
|
|
353
390
|
throw new Error(`call: invalid target address: ${to}`)
|
|
354
391
|
}
|
|
355
392
|
|
|
@@ -407,7 +444,7 @@ export class ValueResolver {
|
|
|
407
444
|
private async resolveContractExists(args: ContractExistsValue['arguments'], context: ExecutionContext): Promise<boolean> {
|
|
408
445
|
const { address } = args
|
|
409
446
|
|
|
410
|
-
if (!
|
|
447
|
+
if (!isAddress(address)) {
|
|
411
448
|
throw new Error(`contract-exists: invalid address: ${address}`)
|
|
412
449
|
}
|
|
413
450
|
|
package/src/lib/deployer.ts
CHANGED
|
@@ -52,6 +52,9 @@ export interface DeployerOptions {
|
|
|
52
52
|
|
|
53
53
|
/** Optional: Show end-of-run summary (default: true). */
|
|
54
54
|
showSummary?: boolean
|
|
55
|
+
|
|
56
|
+
/** Optional: Convert verification errors to warnings instead of failing (default: false). */
|
|
57
|
+
ignoreVerifyErrors?: boolean
|
|
55
58
|
}
|
|
56
59
|
|
|
57
60
|
/**
|
|
@@ -157,7 +160,8 @@ export class Deployer {
|
|
|
157
160
|
const engine = new ExecutionEngine(this.loader.templates, {
|
|
158
161
|
eventEmitter: this.events,
|
|
159
162
|
verificationRegistry,
|
|
160
|
-
noPostCheckConditions: this.noPostCheckConditions
|
|
163
|
+
noPostCheckConditions: this.noPostCheckConditions,
|
|
164
|
+
ignoreVerifyErrors: this.options.ignoreVerifyErrors ?? false
|
|
161
165
|
})
|
|
162
166
|
|
|
163
167
|
// Track if any jobs have failed
|
|
@@ -312,6 +316,11 @@ export class Deployer {
|
|
|
312
316
|
// 5. Write results to output files.
|
|
313
317
|
await this.writeOutputFiles()
|
|
314
318
|
|
|
319
|
+
// Show verification warnings report if ignoreVerifyErrors is enabled
|
|
320
|
+
if (this.options.ignoreVerifyErrors) {
|
|
321
|
+
this.emitVerificationWarningsReport(engine)
|
|
322
|
+
}
|
|
323
|
+
|
|
315
324
|
// Emit end-of-run summary before final status
|
|
316
325
|
if (this.showSummary) {
|
|
317
326
|
this.emitRunSummary(hasFailures)
|
|
@@ -423,6 +432,24 @@ export class Deployer {
|
|
|
423
432
|
this.events.emitEvent(summaryEvent)
|
|
424
433
|
}
|
|
425
434
|
|
|
435
|
+
/**
|
|
436
|
+
* Emit verification warnings report when ignoreVerifyErrors is enabled
|
|
437
|
+
*/
|
|
438
|
+
private emitVerificationWarningsReport(engine: ExecutionEngine): void {
|
|
439
|
+
const warnings = engine.getVerificationWarnings()
|
|
440
|
+
|
|
441
|
+
if (warnings.length > 0) {
|
|
442
|
+
this.events.emitEvent({
|
|
443
|
+
type: 'verification_warnings_report',
|
|
444
|
+
level: 'warn',
|
|
445
|
+
data: {
|
|
446
|
+
totalWarnings: warnings.length,
|
|
447
|
+
warnings: warnings
|
|
448
|
+
}
|
|
449
|
+
})
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
426
453
|
/**
|
|
427
454
|
* Determines the final, ordered list of jobs to execute based on user input.
|
|
428
455
|
* If a user requests specific jobs, this ensures all their dependencies are also included.
|
|
@@ -266,6 +266,30 @@ export class CLIEventAdapter {
|
|
|
266
266
|
// Keep short to reduce noise
|
|
267
267
|
console.log(chalk.gray(` Verification retry ${event.data.attempt}/${event.data.maxRetries}: ${event.data.error}`))
|
|
268
268
|
break
|
|
269
|
+
|
|
270
|
+
case 'verification_skipped':
|
|
271
|
+
console.log(chalk.yellow(` ⚠️ ${event.data.reason}`))
|
|
272
|
+
break
|
|
273
|
+
|
|
274
|
+
case 'verification_warnings_report':
|
|
275
|
+
// Display detailed verification warnings report
|
|
276
|
+
console.log(chalk.yellow('\n📋 Verification Warnings Report'))
|
|
277
|
+
console.log(chalk.yellow(` Total warnings: ${event.data.totalWarnings}`))
|
|
278
|
+
console.log('')
|
|
279
|
+
|
|
280
|
+
if (event.data.warnings && event.data.warnings.length > 0) {
|
|
281
|
+
for (const warning of event.data.warnings) {
|
|
282
|
+
console.log(chalk.red(` ❌ ${warning.actionName} (${warning.contractName})`))
|
|
283
|
+
console.log(chalk.gray(` Address: ${warning.address}`))
|
|
284
|
+
console.log(chalk.gray(` Platform: ${warning.platform}`))
|
|
285
|
+
if (warning.networkName) {
|
|
286
|
+
console.log(chalk.gray(` Network: ${warning.networkName}`))
|
|
287
|
+
}
|
|
288
|
+
console.log(chalk.gray(` Error: ${warning.error}`))
|
|
289
|
+
console.log('')
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
break
|
|
269
293
|
case 'contract_created':
|
|
270
294
|
console.log(chalk.gray(` contract: ${event.data.contractAddress}`))
|
|
271
295
|
break
|
package/src/lib/events/types.ts
CHANGED
|
@@ -429,6 +429,32 @@ export interface VerificationRetryEvent extends BaseEvent {
|
|
|
429
429
|
}
|
|
430
430
|
}
|
|
431
431
|
|
|
432
|
+
export interface VerificationSkippedEvent extends BaseEvent {
|
|
433
|
+
type: 'verification_skipped'
|
|
434
|
+
level: 'warn'
|
|
435
|
+
data: {
|
|
436
|
+
actionName: string
|
|
437
|
+
reason: string
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
export interface VerificationWarningsReportEvent extends BaseEvent {
|
|
442
|
+
type: 'verification_warnings_report'
|
|
443
|
+
level: 'warn'
|
|
444
|
+
data: {
|
|
445
|
+
totalWarnings: number
|
|
446
|
+
warnings: Array<{
|
|
447
|
+
actionName: string
|
|
448
|
+
address: string
|
|
449
|
+
contractName: string
|
|
450
|
+
platform: string
|
|
451
|
+
error: string
|
|
452
|
+
jobName?: string
|
|
453
|
+
networkName?: string
|
|
454
|
+
}>
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
432
458
|
// End-of-run summary
|
|
433
459
|
export interface RunSummaryEvent extends BaseEvent {
|
|
434
460
|
type: 'run_summary'
|
|
@@ -488,5 +514,7 @@ export type DeploymentEvent =
|
|
|
488
514
|
| VerificationSubmittedEvent
|
|
489
515
|
| VerificationCompletedEvent
|
|
490
516
|
| VerificationFailedEvent
|
|
491
|
-
| VerificationRetryEvent
|
|
517
|
+
| VerificationRetryEvent
|
|
518
|
+
| VerificationSkippedEvent
|
|
519
|
+
| VerificationWarningsReportEvent
|
|
492
520
|
| RunSummaryEvent
|