Skip to content

Commit 43a7c8d

Browse files
authored
fix(foundry): VerifyAll supports linked libs; enable optimizer (#414)
1 parent 8567f1b commit 43a7c8d

3 files changed

Lines changed: 164 additions & 9 deletions

File tree

.changeset/huge-mirrors-drum.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"create-eth": patch
3+
---
4+
5+
fix(foundry): VerifyAll supports linked libs; enable optimizer

templates/solidity-frameworks/foundry/packages/foundry/foundry.toml.template.mjs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ out = 'out'
1212
libs = ['lib', 'node_modules']
1313
fs_permissions = [{ access = "read-write", path = "./"}]
1414
extra_output = ["evm.bytecode.opcodes"]
15+
optimizer = true
16+
optimizer_runs = 200
1517
${extraProfileDefaults.filter(Boolean).join("\n")}
1618
1719
[rpc_endpoints]

templates/solidity-frameworks/foundry/packages/foundry/script/VerifyAll.s.sol

Lines changed: 157 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)