@@ -63,14 +63,157 @@ function log(msg, deployResult = null) {
6363 }
6464}
6565
66+ /**
67+ * Verifies a contract on Etherscan
68+ * @param {string } contractName - Name of the contract (for logging)
69+ * @param {string } contractAddress - Address of the deployed contract
70+ * @param {Array } constructorArgs - Constructor arguments used for deployment
71+ * @param {string } contract - Actual contract name in source code
72+ * @param {string|null } contractPath - Optional contract path (e.g., "contracts/vault/VaultAdmin.sol:VaultAdmin")
73+ */
74+ const verifyContractOnEtherscan = async (
75+ contractName ,
76+ contractAddress ,
77+ constructorArgs ,
78+ contract ,
79+ contractPath = null
80+ ) => {
81+ // Declare finalContractPath outside try block so it's accessible in catch
82+ let finalContractPath = contractPath ;
83+
84+ try {
85+ log ( `Verifying ${ contractName } at ${ contractAddress } ...` ) ;
86+
87+ // Note: constructorArguments should be in the same format as used for deployment
88+ // Structs should be passed as arrays/tuples (e.g., [[addr1, addr2]] for a struct with 2 addresses)
89+ // Since we're using the same `args` that were used for deployment, structs will work correctly
90+ const verifyArgs = {
91+ address : contractAddress ,
92+ constructorArguments : constructorArgs || [ ] ,
93+ } ;
94+
95+ // Try to get contract path from artifacts if not provided
96+ if ( ! finalContractPath ) {
97+ try {
98+ // Use the contract name (which is the actual contract name in source code)
99+ const actualContractName =
100+ typeof contract === "string" ? contract : contractName ;
101+ const artifact = await hre . artifacts . readArtifact ( actualContractName ) ;
102+
103+ // artifact.sourceName contains the path like "contracts/vault/VaultAdmin.sol"
104+ // We need to format it as "contracts/vault/VaultAdmin.sol:VaultAdmin"
105+ if ( artifact . sourceName ) {
106+ finalContractPath = `${ artifact . sourceName } :${ actualContractName } ` ;
107+ log ( `Auto-detected contract path: ${ finalContractPath } ` ) ;
108+ }
109+ } catch ( artifactError ) {
110+ // If we can't read the artifact, continue without contract path
111+ // Verification will still work but may be slower
112+ log ( `Could not auto-detect contract path: ${ artifactError . message } ` ) ;
113+ }
114+ }
115+
116+ // If we have a contract path, use it (faster verification)
117+ if ( finalContractPath ) {
118+ verifyArgs . contract = finalContractPath ;
119+ }
120+
121+ // Note: "verify:verify" is the full task name in Hardhat's task system
122+ // The CLI command "hardhat verify" is actually calling the "verify:verify" subtask
123+ // This is Hardhat's namespace convention: <taskGroup>:<subtask>
124+ await hre . run ( "verify:verify" , verifyArgs ) ;
125+
126+ log ( `Verified ${ contractName } at ${ contractAddress } ` ) ;
127+ } catch ( error ) {
128+ // Log verification error but don't fail deployment
129+ if ( error . message . includes ( "Already Verified" ) ) {
130+ log ( `${ contractName } at ${ contractAddress } is already verified` ) ;
131+ } else {
132+ log (
133+ `Warning: Failed to verify ${ contractName } at ${ contractAddress } : ${ error . message } `
134+ ) ;
135+
136+ // Print the manual verification command for debugging
137+ const networkName = hre . network . name ;
138+ let manualCommand = `yarn hardhat verify --network ${ networkName } ` ;
139+
140+ if ( finalContractPath ) {
141+ manualCommand += ` --contract ${ finalContractPath } ` ;
142+ }
143+
144+ // Format constructor arguments
145+ if ( constructorArgs && constructorArgs . length > 0 ) {
146+ // Check if args are complex (contain arrays/objects) - if so, suggest using a file
147+ const hasComplexArgs = constructorArgs . some (
148+ ( arg ) =>
149+ Array . isArray ( arg ) ||
150+ ( typeof arg === "object" &&
151+ arg !== null &&
152+ ! BigNumber . isBigNumber ( arg ) )
153+ ) ;
154+
155+ if ( hasComplexArgs ) {
156+ // For complex args, suggest creating a file
157+ // Format args as a JavaScript module export
158+ const formatArg = ( arg ) => {
159+ if ( Array . isArray ( arg ) ) {
160+ return `[${ arg . map ( formatArg ) . join ( ", " ) } ]` ;
161+ } else if ( BigNumber . isBigNumber ( arg ) ) {
162+ return `"${ arg . toString ( ) } "` ;
163+ } else if ( typeof arg === "string" ) {
164+ return `"${ arg } "` ;
165+ } else if ( typeof arg === "object" && arg !== null ) {
166+ return JSON . stringify ( arg ) ;
167+ }
168+ return String ( arg ) ;
169+ } ;
170+
171+ const argsCode = `module.exports = [${ constructorArgs
172+ . map ( formatArg )
173+ . join ( ", " ) } ];`;
174+ log (
175+ `\nTo verify manually, create a file (e.g., verify-args.js) with:`
176+ ) ;
177+ log ( argsCode ) ;
178+ log ( `\nThen run:` ) ;
179+ log (
180+ `${ manualCommand } --constructor-args verify-args.js ${ contractAddress } `
181+ ) ;
182+ } else {
183+ // Simple args can be passed directly
184+ const argsStr = constructorArgs
185+ . map ( ( arg ) => {
186+ if ( BigNumber . isBigNumber ( arg ) ) {
187+ return arg . toString ( ) ;
188+ } else if ( typeof arg === "string" && arg . startsWith ( "0x" ) ) {
189+ return arg ;
190+ }
191+ return String ( arg ) ;
192+ } )
193+ . join ( " " ) ;
194+ manualCommand += ` ${ contractAddress } ${ argsStr } ` ;
195+ log ( `\nTo verify manually, run:` ) ;
196+ log ( manualCommand ) ;
197+ }
198+ } else {
199+ manualCommand += ` ${ contractAddress } ` ;
200+ log ( `\nTo verify manually, run:` ) ;
201+ log ( manualCommand ) ;
202+ }
203+ }
204+ }
205+ } ;
206+
66207const deployWithConfirmation = async (
67208 contractName ,
68209 args ,
69210 contract ,
70211 skipUpgradeSafety = false ,
71212 libraries = { } ,
72213 gasLimit ,
73- useFeeData
214+ useFeeData ,
215+ verifyContract = false ,
216+ contractPath = null
74217) => {
75218 // check that upgrade doesn't corrupt the storage slots
76219 if ( ! isTest && ! skipUpgradeSafety ) {
@@ -109,6 +252,21 @@ const deployWithConfirmation = async (
109252 await storeStorageLayoutForContract ( hre , contractName , contract ) ;
110253 }
111254
255+ log ( `Deployed ${ contractName } ` , result ) ;
256+ // Verify contract on Etherscan if requested and on a live network
257+ // Can be enabled via parameter or VERIFY_CONTRACTS environment variable
258+ const shouldVerify =
259+ verifyContract || process . env . VERIFY_CONTRACTS === "true" ;
260+ if ( shouldVerify && ! isTest && ! isFork && result . address ) {
261+ await verifyContractOnEtherscan (
262+ contractName ,
263+ result . address ,
264+ args ,
265+ contract ,
266+ contractPath
267+ ) ;
268+ }
269+
112270 log ( `Deployed ${ contractName } ` , result ) ;
113271 return result ;
114272} ;
@@ -1128,28 +1286,41 @@ function deploymentWithGuardianGovernor(opts, fn) {
11281286 return main ;
11291287}
11301288
1131- function encodeSaltForCreateX ( deployer , crossChainProtectionFlag , salt ) {
1132- // Generate encoded salt (deployer address || crossChainProtectionFlag || bytes11(keccak256(rewardToken, gauge)))
1289+ function encodeSaltForCreateX ( deployer , crosschainProtectionFlag , salt ) {
1290+ // Generate encoded salt (deployer address || crosschainProtectionFlag || bytes11(keccak256(rewardToken, gauge)))
11331291
11341292 // convert deployer address to bytes20
11351293 const addressDeployerBytes20 = ethers . utils . hexlify (
11361294 ethers . utils . zeroPad ( deployer , 20 )
11371295 ) ;
11381296
1139- // convert crossChainProtectionFlag to bytes1
1140- const crossChainProtectionFlagBytes1 = crossChainProtectionFlag
1297+ // convert crosschainProtectionFlag to bytes1
1298+ const crosschainProtectionFlagBytes1 = crosschainProtectionFlag
11411299 ? "0x01"
11421300 : "0x00" ;
11431301
11441302 // this portion hexifies salt to bytes11
1145- const saltBytes11 = ethers . utils . hexlify (
1146- ethers . utils . zeroPad ( ethers . utils . hexlify ( salt ) , 11 )
1147- ) ;
1303+ // For strings, hash them first (as per comment: bytes11(keccak256(rewardToken, gauge)))
1304+ // Then take the first 11 bytes of the hash (most significant bytes)
1305+ let saltBytes11 ;
1306+ if ( typeof salt === "string" && ! ethers . utils . isHexString ( salt ) ) {
1307+ // Hash the string and take first 11 bytes (leftmost bytes)
1308+ const hash = ethers . utils . keccak256 ( ethers . utils . toUtf8Bytes ( salt ) ) ;
1309+ const hashBytes = ethers . utils . arrayify ( hash ) ;
1310+ // Take first 11 bytes and pad to 11 bytes (should already be 11, but ensure it)
1311+ saltBytes11 = ethers . utils . hexlify (
1312+ ethers . utils . zeroPad ( hashBytes . slice ( 0 , 11 ) , 11 )
1313+ ) ;
1314+ } else {
1315+ // For numbers or hex strings, pad to 11 bytes
1316+ const saltBytes = ethers . utils . hexlify ( salt ) ;
1317+ saltBytes11 = ethers . utils . hexlify ( ethers . utils . zeroPad ( saltBytes , 11 ) ) ;
1318+ }
11481319 // concat all bytes into a bytes32
11491320 const encodedSalt = ethers . utils . hexlify (
11501321 ethers . utils . concat ( [
11511322 addressDeployerBytes20 ,
1152- crossChainProtectionFlagBytes1 ,
1323+ crosschainProtectionFlagBytes1 ,
11531324 saltBytes11 ,
11541325 ] )
11551326 ) ;
@@ -1267,6 +1438,7 @@ async function createPoolBoosterSonic({
12671438module . exports = {
12681439 log,
12691440 deployWithConfirmation,
1441+ verifyContractOnEtherscan,
12701442 withConfirmation,
12711443 impersonateGuardian,
12721444 executeProposalOnFork,
0 commit comments