@@ -50,12 +50,29 @@ contract VerifyAll is Script {
5050 abi.decode (vm.parseJson (content, searchStr (currTransactionIdx, "contractAddress " )), (address ));
5151 bytes memory deployedBytecode =
5252 abi.decode (vm.parseJson (content, searchStr (currTransactionIdx, "transaction.input " )), (bytes ));
53- bytes memory compiledBytecode =
54- abi.decode (vm.parseJson (_getCompiledBytecode (contractName), ".bytecode.object " ), (bytes ));
55- bytes memory constructorArgs =
56- BytesLib.slice (deployedBytecode, compiledBytecode.length , deployedBytecode.length - compiledBytecode.length );
5753
58- string [] memory inputs = new string [](9 );
54+ string memory artifactPath = _locateArtifact (contractName);
55+ string memory artifactJson = vm.readFile (artifactPath);
56+
57+ // Read bytecode.object as a string. For contracts with external libraries, the hex
58+ // contains `__$<hash>$__` placeholders, which make `abi.decode(..., (bytes))` silently
59+ // fall back to string-encoding and report a bogus length. A placeholder and its resolved
60+ // address are both 20 bytes (40 hex chars), so char length is the source of truth.
61+ string memory bytecodeHex = _readBytecodeHex (artifactJson);
62+ uint256 compiledLen = _hexStringByteLength (bytecodeHex);
63+
64+ bytes memory constructorArgs;
65+ if (deployedBytecode.length > compiledLen) {
66+ constructorArgs =
67+ BytesLib.slice (deployedBytecode, compiledLen, deployedBytecode.length - compiledLen);
68+ } else {
69+ constructorArgs = new bytes (0 );
70+ }
71+
72+ string [] memory libArgs = _discoverLibraries (artifactJson, bytecodeHex, content);
73+
74+ uint256 argc = 9 + 2 * libArgs.length ;
75+ string [] memory inputs = new string [](argc);
5976 inputs[0 ] = "forge " ;
6077 inputs[1 ] = "verify-contract " ;
6178 inputs[2 ] = vm.toString (contractAddr);
@@ -65,6 +82,10 @@ contract VerifyAll is Script {
6582 inputs[6 ] = "--constructor-args " ;
6683 inputs[7 ] = vm.toString (constructorArgs);
6784 inputs[8 ] = "--watch " ;
85+ for (uint256 i = 0 ; i < libArgs.length ; i++ ) {
86+ inputs[9 + 2 * i] = "--libraries " ;
87+ inputs[9 + 2 * i + 1 ] = libArgs[i];
88+ }
6889
6990 FfiResult memory f = tempVm (address (vm)).tryFfi (inputs);
7091
@@ -90,12 +111,12 @@ contract VerifyAll is Script {
90111 }
91112 }
92113
93- function _getCompiledBytecode (string memory contractName ) internal returns (string memory compiledBytecode ) {
114+ function _locateArtifact (string memory contractName ) internal returns (string memory ) {
94115 string memory root = vm.projectRoot ();
95116 string memory defaultPath = string .concat (root, "/out/ " , contractName, ".sol/ " , contractName, ".json " );
96117
97- try vm.readFile (defaultPath) returns (string memory content ) {
98- compiledBytecode = content ;
118+ try vm.readFile (defaultPath) returns (string memory ) {
119+ return defaultPath ;
99120 } catch {
100121 string [] memory inputs = new string [](3 );
101122 inputs[0 ] = "bash " ;
@@ -104,8 +125,135 @@ contract VerifyAll is Script {
104125 "find ' " , root, "/out' -name ' " , contractName, ".json' -not -path '*/build-info/*' -print -quit | tr -d '\\n' "
105126 );
106127 FfiResult memory f = tempVm (address (vm)).tryFfi (inputs);
107- compiledBytecode = vm.readFile (string (f.stdout));
128+ return string (f.stdout);
129+ }
130+ }
131+
132+ /// @dev Tries typed cheatcode first; falls back to generic parseJson + string decode.
133+ function _readBytecodeHex (string memory artifactJson ) internal pure returns (string memory ) {
134+ try vm.parseJsonString (artifactJson, ".bytecode.object " ) returns (string memory s ) {
135+ return s;
136+ } catch {
137+ return abi.decode (vm.parseJson (artifactJson, ".bytecode.object " ), (string ));
138+ }
139+ }
140+
141+ /// @dev Byte length of a "0x..."-prefixed hex string (char count / 2, minus "0x").
142+ function _hexStringByteLength (string memory hex_ ) internal pure returns (uint256 ) {
143+ bytes memory b = bytes (hex_);
144+ uint256 charLen = b.length ;
145+ if (charLen >= 2 && b[0 ] == 0x30 && (b[1 ] == 0x78 || b[1 ] == 0x58 )) {
146+ charLen -= 2 ;
147+ }
148+ return charLen / 2 ;
149+ }
150+
151+ /// @dev Build `--libraries path:name:address` values for every external library the
152+ /// artifact links against. For each `linkReferences` key (the library source path) we
153+ /// scan the broadcast file; for each tx's `contractName` we compute the solc placeholder
154+ /// (`__$<keccak256(path:name)[0:17] as hex>$__`) and check whether it appears in the
155+ /// compiled bytecode. A hit identifies which broadcast deployment satisfies the link.
156+ function _discoverLibraries (
157+ string memory artifactJson ,
158+ string memory bytecodeHex ,
159+ string memory broadcastContent
160+ ) internal returns (string [] memory ) {
161+ string [] memory libPaths;
162+ try vm.parseJsonKeys (artifactJson, ".bytecode.linkReferences " ) returns (string [] memory paths ) {
163+ libPaths = paths;
164+ } catch {
165+ return new string [](0 );
166+ }
167+ if (libPaths.length == 0 ) return new string [](0 );
168+
169+ string [] memory tmp = new string [](libPaths.length );
170+ uint256 count;
171+
172+ for (uint256 p = 0 ; p < libPaths.length ; p++ ) {
173+ (bool found , string memory libName , address addr ) =
174+ _resolveLibrary (libPaths[p], bytecodeHex, broadcastContent);
175+ if (! found) continue ;
176+ tmp[count++ ] = string .concat (libPaths[p], ": " , libName, ": " , vm.toString (addr));
177+ }
178+
179+ string [] memory result = new string [](count);
180+ for (uint256 i = 0 ; i < count; i++ ) {
181+ result[i] = tmp[i];
182+ }
183+ return result;
184+ }
185+
186+ function _resolveLibrary (
187+ string memory libPath ,
188+ string memory bytecodeHex ,
189+ string memory broadcastContent
190+ ) internal returns (bool , string memory , address ) {
191+ for (uint256 i = 0 ;; i++ ) {
192+ string memory nameKey = string .concat (".transactions[ " , vm.toString (i), "].contractName " );
193+ bytes memory nameBytes;
194+ try vm.parseJson (broadcastContent, nameKey) returns (bytes memory b ) {
195+ nameBytes = b;
196+ } catch {
197+ return (false , "" , address (0 ));
198+ }
199+ if (nameBytes.length == 0 ) return (false , "" , address (0 ));
200+
201+ string memory candidate = abi.decode (nameBytes, (string ));
202+ if (bytes (candidate).length == 0 ) continue ;
203+
204+ string memory placeholder = _computePlaceholder (libPath, candidate);
205+ if (_stringContains (bytecodeHex, placeholder)) {
206+ address addr = abi.decode (
207+ vm.parseJson (
208+ broadcastContent,
209+ string .concat (".transactions[ " , vm.toString (i), "].contractAddress " )
210+ ),
211+ (address )
212+ );
213+ return (true , candidate, addr);
214+ }
215+ }
216+ }
217+
218+ /// @dev solc library placeholder: `__$<keccak256(path:name)[0:17] hex>$__` (40 chars = 20 bytes).
219+ function _computePlaceholder (string memory libPath , string memory libName )
220+ internal
221+ pure
222+ returns (string memory )
223+ {
224+ bytes32 h = keccak256 (abi.encodePacked (libPath, ": " , libName));
225+ bytes memory hexChars = "0123456789abcdef " ;
226+ bytes memory out = new bytes (40 );
227+ out[0 ] = "_ " ;
228+ out[1 ] = "_ " ;
229+ out[2 ] = "$ " ;
230+ for (uint256 i = 0 ; i < 17 ; i++ ) {
231+ uint8 bt = uint8 (h[i]);
232+ out[3 + i * 2 ] = hexChars[bt >> 4 ];
233+ out[3 + i * 2 + 1 ] = hexChars[bt & 0x0f ];
234+ }
235+ out[37 ] = "$ " ;
236+ out[38 ] = "_ " ;
237+ out[39 ] = "_ " ;
238+ return string (out);
239+ }
240+
241+ function _stringContains (string memory hay , string memory needle ) internal pure returns (bool ) {
242+ bytes memory h = bytes (hay);
243+ bytes memory n = bytes (needle);
244+ if (n.length == 0 ) return true ;
245+ if (h.length < n.length ) return false ;
246+ for (uint256 i = 0 ; i <= h.length - n.length ; i++ ) {
247+ bool matched = true ;
248+ for (uint256 j = 0 ; j < n.length ; j++ ) {
249+ if (h[i + j] != n[j]) {
250+ matched = false ;
251+ break ;
252+ }
253+ }
254+ if (matched) return true ;
108255 }
256+ return false ;
109257 }
110258
111259 function searchStr (uint96 idx , string memory searchKey ) internal pure returns (string memory ) {
0 commit comments