Skip to content

Commit cbce1e0

Browse files
authored
Remove dependency on solidity-stringutils (#91)
1 parent c29dd49 commit cbce1e0

File tree

18 files changed

+243
-151
lines changed

18 files changed

+243
-151
lines changed

.gitmodules

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
11
[submodule "lib/forge-std"]
22
path = lib/forge-std
33
url = https://github.com/foundry-rs/forge-std
4-
[submodule "lib/solidity-stringutils"]
5-
path = lib/solidity-stringutils
6-
url = https://github.com/Arachnid/solidity-stringutils

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Changelog
22

3+
## 0.4.0 (2025-01-27)
4+
5+
- Remove dependency on `solidity-stringutils`. ([#91](https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades/pull/91))
6+
7+
### Breaking changes
8+
- Requires `forge-std` version v1.9.5 or higher.
9+
310
## 0.3.8 (2025-01-24)
411

512
- Fix error conditions when warnings occur in validation output. ([#94](https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades/pull/94))

README.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,20 +54,18 @@ Follow the steps above, but instead of running `forge install OpenZeppelin/openz
5454
npm install @openzeppelin/foundry-upgrades
5555
```
5656

57-
Then add the following additional lines to `remappings.txt`, in addition to the ones described above:
57+
Then add the following additional line to `remappings.txt`, in addition to the ones described above:
5858
```
5959
openzeppelin-foundry-upgrades/=node_modules/@openzeppelin/foundry-upgrades/src/
60-
solidity-stringutils/=node_modules/@openzeppelin/foundry-upgrades/lib/solidity-stringutils/
6160
```
6261

6362
#### Soldeer
6463

6564
Follow the steps above, but instead of running `forge install OpenZeppelin/openzeppelin-foundry-upgrades`, use one of the install commands described in https://soldeer.xyz/project/openzeppelin-foundry-upgrades
6665

67-
Then add the following additional lines to `remappings.txt`, in addition to the ones described above (replace `0.3.6` with the version of the plugin that you installed):
66+
Then add the following additional line to `remappings.txt`, in addition to the ones described above (replace `0.3.6` with the version of the plugin that you installed):
6867
```
6968
openzeppelin-foundry-upgrades/=dependencies/openzeppelin-foundry-upgrades-0.3.6/src/
70-
solidity-stringutils/=dependencies/openzeppelin-foundry-upgrades-0.3.6/lib/solidity-stringutils/
7169
```
7270

7371
## OpenZeppelin Defender integration
@@ -76,7 +74,7 @@ See [DEFENDER.md](DEFENDER.md)
7674

7775
## Foundry Requirements
7876

79-
This library requires [forge-std](https://github.com/foundry-rs/forge-std) version 1.8.0 or higher.
77+
This library requires [forge-std](https://github.com/foundry-rs/forge-std) version 1.9.5 or higher.
8078

8179
## Before Running
8280

docs/modules/pages/foundry-upgrades.adoc

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,27 +59,25 @@ Follow the steps above, but instead of running `forge install OpenZeppelin/openz
5959
npm install @openzeppelin/foundry-upgrades
6060
----
6161

62-
Then add the following additional lines to `remappings.txt`, in addition to the ones described above:
62+
Then add the following additional line to `remappings.txt`, in addition to the ones described above:
6363
[source,console]
6464
----
6565
openzeppelin-foundry-upgrades/=node_modules/@openzeppelin/foundry-upgrades/src/
66-
solidity-stringutils/=node_modules/@openzeppelin/foundry-upgrades/lib/solidity-stringutils/
6766
----
6867

6968
==== Soldeer
7069

7170
Follow the steps above, but instead of running `forge install OpenZeppelin/openzeppelin-foundry-upgrades`, use one of the install commands described in https://soldeer.xyz/project/openzeppelin-foundry-upgrades
7271

73-
Then add the following additional lines to `remappings.txt`, in addition to the ones described above (replace `0.3.6` with the version of the plugin that you installed):
72+
Then add the following additional line to `remappings.txt`, in addition to the ones described above (replace `0.3.6` with the version of the plugin that you installed):
7473
[source,console]
7574
----
7675
openzeppelin-foundry-upgrades/=dependencies/openzeppelin-foundry-upgrades-0.3.6/src/
77-
solidity-stringutils/=dependencies/openzeppelin-foundry-upgrades-0.3.6/lib/solidity-stringutils/
7876
----
7977

8078
== Foundry Requirements
8179

82-
This library requires https://github.com/foundry-rs/forge-std[forge-std] version 1.8.0 or higher.
80+
This library requires https://github.com/foundry-rs/forge-std[forge-std] version 1.9.5 or higher.
8381

8482
== Before Running
8583

lib/solidity-stringutils

Lines changed: 0 additions & 1 deletion
This file was deleted.

package.json

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
{
22
"name": "@openzeppelin/foundry-upgrades",
3-
"version": "0.3.8",
3+
"version": "0.4.0",
44
"description": "Foundry library for deploying and managing upgradeable contracts",
55
"license": "MIT",
66
"files": [
7-
"src/**/*",
8-
"lib/solidity-stringutils/**/*"
7+
"src/**/*"
98
],
109
"repository": {
1110
"type": "git",

src/internal/Core.sol

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ pragma solidity ^0.8.0;
33

44
import {Vm} from "forge-std/Vm.sol";
55
import {console} from "forge-std/console.sol";
6-
import {strings} from "solidity-stringutils/src/strings.sol";
6+
7+
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
78

89
import {Options} from "../Options.sol";
910
import {Versions} from "./Versions.sol";
@@ -61,6 +62,8 @@ library Core {
6162
upgradeProxy(proxy, contractName, data, opts);
6263
}
6364

65+
using Strings for *;
66+
6467
/**
6568
* @dev Upgrades a proxy to a new implementation contract. Only supported for UUPS or transparent proxies.
6669
*
@@ -74,15 +77,15 @@ library Core {
7477
bytes32 adminSlot = vm.load(proxy, ADMIN_SLOT);
7578
if (adminSlot == bytes32(0)) {
7679
string memory upgradeInterfaceVersion = getUpgradeInterfaceVersion(proxy);
77-
if (upgradeInterfaceVersion.toSlice().equals("5.0.0".toSlice()) || data.length > 0) {
80+
if (upgradeInterfaceVersion.equal("5.0.0") || data.length > 0) {
7881
IUpgradeableProxy(proxy).upgradeToAndCall(newImpl, data);
7982
} else {
8083
IUpgradeableProxy(proxy).upgradeTo(newImpl);
8184
}
8285
} else {
8386
address admin = address(uint160(uint256(adminSlot)));
8487
string memory upgradeInterfaceVersion = getUpgradeInterfaceVersion(admin);
85-
if (upgradeInterfaceVersion.toSlice().equals("5.0.0".toSlice()) || data.length > 0) {
88+
if (upgradeInterfaceVersion.equal("5.0.0") || data.length > 0) {
8689
IProxyAdmin(admin).upgradeAndCall(proxy, newImpl, data);
8790
} else {
8891
IProxyAdmin(admin).upgrade(proxy, newImpl);
@@ -300,8 +303,6 @@ library Core {
300303
*/
301304
bytes32 private constant BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;
302305

303-
using strings for *;
304-
305306
/**
306307
* @dev Gets the upgrade interface version string from a proxy or admin contract using the `UPGRADE_INTERFACE_VERSION()` getter.
307308
* If the contract does not have the getter or the return data does not look like a string, this function returns an empty string.
@@ -345,9 +346,10 @@ library Core {
345346
string memory stdout = string(result.stdout);
346347

347348
// CLI validate command uses exit code to indicate if the validation passed or failed.
349+
Vm vm = Vm(Utils.CHEATCODE_ADDRESS);
348350
if (result.exitCode == 0) {
349351
// As an extra precaution, we also check stdout for "SUCCESS" to ensure it actually ran.
350-
if (stdout.toSlice().contains("SUCCESS".toSlice())) {
352+
if (vm.contains(stdout, "SUCCESS")) {
351353
if (result.stderr.length > 0) {
352354
// Prints warnings from stderr
353355
console.log(string(result.stderr));
@@ -357,7 +359,7 @@ library Core {
357359
revert(string(abi.encodePacked("Failed to run upgrade safety validation: ", stdout)));
358360
}
359361
} else {
360-
if (stdout.toSlice().contains("FAILED".toSlice())) {
362+
if (vm.contains(stdout, "FAILED")) {
361363
if (result.stderr.length > 0) {
362364
// Prints warnings from stderr
363365
console.log(string(result.stderr));
@@ -470,6 +472,7 @@ library Core {
470472

471473
function _deployFromBytecode(bytes memory bytecode) private returns (address) {
472474
address addr;
475+
/// @solidity memory-safe-assembly
473476
assembly {
474477
addr := create(0, add(bytecode, 32), mload(bytecode))
475478
}

src/internal/DefenderDeploy.sol

Lines changed: 45 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ pragma solidity ^0.8.0;
33

44
import {Vm} from "forge-std/Vm.sol";
55
import {console} from "forge-std/console.sol";
6-
import {strings} from "solidity-stringutils/src/strings.sol";
76

87
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
98

@@ -18,8 +17,6 @@ import {ProposeUpgradeResponse, ApprovalProcessResponse} from "../Defender.sol";
1817
* WARNING: DO NOT USE DIRECTLY. Use Defender.sol instead.
1918
*/
2019
library DefenderDeploy {
21-
using strings for *;
22-
2320
function deploy(
2421
string memory contractName,
2522
bytes memory constructorData,
@@ -54,7 +51,7 @@ library DefenderDeploy {
5451
) internal view returns (string[] memory) {
5552
Vm vm = Vm(Utils.CHEATCODE_ADDRESS);
5653

57-
if (!(defenderOpts.licenseType).toSlice().empty()) {
54+
if (bytes(defenderOpts.licenseType).length != 0) {
5855
if (defenderOpts.skipVerifySourceCode) {
5956
revert("The `licenseType` option cannot be used when the `skipVerifySourceCode` option is `true`");
6057
} else if (defenderOpts.skipLicenseType) {
@@ -86,14 +83,14 @@ library DefenderDeploy {
8683
if (defenderOpts.skipVerifySourceCode) {
8784
inputBuilder[i++] = "--verifySourceCode";
8885
inputBuilder[i++] = "false";
89-
} else if (!(defenderOpts.licenseType).toSlice().empty()) {
86+
} else if (bytes(defenderOpts.licenseType).length != 0) {
9087
inputBuilder[i++] = "--licenseType";
9188
inputBuilder[i++] = string(abi.encodePacked('"', defenderOpts.licenseType, '"'));
92-
} else if (!defenderOpts.skipLicenseType && !(contractInfo.license).toSlice().empty()) {
89+
} else if (!defenderOpts.skipLicenseType && bytes(contractInfo.license).length != 0) {
9390
inputBuilder[i++] = "--licenseType";
9491
inputBuilder[i++] = string(abi.encodePacked('"', _toLicenseType(contractInfo), '"'));
9592
}
96-
if (!(defenderOpts.relayerId).toSlice().empty()) {
93+
if (bytes(defenderOpts.relayerId).length != 0) {
9794
inputBuilder[i++] = "--relayerId";
9895
inputBuilder[i++] = defenderOpts.relayerId;
9996
}
@@ -117,7 +114,7 @@ library DefenderDeploy {
117114
inputBuilder[i++] = "--maxPriorityFeePerGas";
118115
inputBuilder[i++] = Strings.toString(defenderOpts.txOverrides.maxPriorityFeePerGas);
119116
}
120-
if (!(defenderOpts.metadata).toSlice().empty()) {
117+
if (bytes(defenderOpts.metadata).length != 0) {
121118
inputBuilder[i++] = "--metadata";
122119
inputBuilder[i++] = string(abi.encodePacked('"', vm.replace(defenderOpts.metadata, '"', '\\"'), '"'));
123120
}
@@ -133,35 +130,37 @@ library DefenderDeploy {
133130
return inputs;
134131
}
135132

133+
using Strings for string;
134+
136135
function _toLicenseType(ContractInfo memory contractInfo) private pure returns (string memory) {
137-
strings.slice memory id = contractInfo.license.toSlice();
138-
if (id.equals("UNLICENSED".toSlice())) {
136+
string memory id = contractInfo.license;
137+
if (id.equal("UNLICENSED")) {
139138
return "None";
140-
} else if (id.equals("Unlicense".toSlice())) {
139+
} else if (id.equal("Unlicense")) {
141140
return "Unlicense";
142-
} else if (id.equals("MIT".toSlice())) {
141+
} else if (id.equal("MIT")) {
143142
return "MIT";
144-
} else if (id.equals("GPL-2.0-only".toSlice()) || id.equals("GPL-2.0-or-later".toSlice())) {
143+
} else if (id.equal("GPL-2.0-only") || id.equal("GPL-2.0-or-later")) {
145144
return "GNU GPLv2";
146-
} else if (id.equals("GPL-3.0-only".toSlice()) || id.equals("GPL-3.0-or-later".toSlice())) {
145+
} else if (id.equal("GPL-3.0-only") || id.equal("GPL-3.0-or-later")) {
147146
return "GNU GPLv3";
148-
} else if (id.equals("LGPL-2.1-only".toSlice()) || id.equals("LGPL-2.1-or-later".toSlice())) {
147+
} else if (id.equal("LGPL-2.1-only") || id.equal("LGPL-2.1-or-later")) {
149148
return "GNU LGPLv2.1";
150-
} else if (id.equals("LGPL-3.0-only".toSlice()) || id.equals("LGPL-3.0-or-later".toSlice())) {
149+
} else if (id.equal("LGPL-3.0-only") || id.equal("LGPL-3.0-or-later")) {
151150
return "GNU LGPLv3";
152-
} else if (id.equals("BSD-2-Clause".toSlice())) {
151+
} else if (id.equal("BSD-2-Clause")) {
153152
return "BSD-2-Clause";
154-
} else if (id.equals("BSD-3-Clause".toSlice())) {
153+
} else if (id.equal("BSD-3-Clause")) {
155154
return "BSD-3-Clause";
156-
} else if (id.equals("MPL-2.0".toSlice())) {
155+
} else if (id.equal("MPL-2.0")) {
157156
return "MPL-2.0";
158-
} else if (id.equals("OSL-3.0".toSlice())) {
157+
} else if (id.equal("OSL-3.0")) {
159158
return "OSL-3.0";
160-
} else if (id.equals("Apache-2.0".toSlice())) {
159+
} else if (id.equal("Apache-2.0")) {
161160
return "Apache-2.0";
162-
} else if (id.equals("AGPL-3.0-only".toSlice()) || id.equals("AGPL-3.0-or-later".toSlice())) {
161+
} else if (id.equal("AGPL-3.0-only") || id.equal("AGPL-3.0-or-later")) {
163162
return "GNU AGPLv3";
164-
} else if (id.equals("BUSL-1.1".toSlice())) {
163+
} else if (id.equal("BUSL-1.1")) {
165164
return "BSL 1.1";
166165
} else {
167166
revert(
@@ -217,7 +216,7 @@ library DefenderDeploy {
217216
return parseProposeUpgradeResponse(stdout);
218217
}
219218

220-
function parseProposeUpgradeResponse(string memory stdout) internal pure returns (ProposeUpgradeResponse memory) {
219+
function parseProposeUpgradeResponse(string memory stdout) internal returns (ProposeUpgradeResponse memory) {
221220
ProposeUpgradeResponse memory response;
222221
response.proposalId = _parseLine("Proposal ID: ", stdout, true);
223222
response.url = _parseLine("Proposal URL: ", stdout, false);
@@ -228,15 +227,26 @@ library DefenderDeploy {
228227
string memory expectedPrefix,
229228
string memory stdout,
230229
bool required
231-
) private pure returns (string memory) {
232-
strings.slice memory delim = expectedPrefix.toSlice();
233-
if (stdout.toSlice().contains(delim)) {
234-
strings.slice memory slice = stdout.toSlice().copy().find(delim).beyond(delim);
235-
// Remove any following lines
236-
if (slice.contains("\n".toSlice())) {
237-
slice = slice.split("\n".toSlice());
230+
) private returns (string memory) {
231+
Vm vm = Vm(Utils.CHEATCODE_ADDRESS);
232+
if (vm.contains(stdout, expectedPrefix)) {
233+
// Get the substring after the prefix
234+
string[] memory segments = vm.split(stdout, expectedPrefix);
235+
if (segments.length > 2) {
236+
revert(
237+
string(
238+
abi.encodePacked(
239+
"Found multiple occurrences of prefix '",
240+
expectedPrefix,
241+
"' in output: ",
242+
stdout
243+
)
244+
)
245+
);
238246
}
239-
return slice.toString();
247+
string memory suffix = segments[1];
248+
// Keep only the first line
249+
return vm.split(suffix, "\n")[0];
240250
} else if (required) {
241251
revert(
242252
string(abi.encodePacked("Failed to find line with prefix '", expectedPrefix, "' in output: ", stdout))
@@ -276,7 +286,7 @@ library DefenderDeploy {
276286
inputBuilder[i++] = "--proxyAdminAddress";
277287
inputBuilder[i++] = vm.toString(proxyAdminAddress);
278288
}
279-
if (!(opts.defender.upgradeApprovalProcessId).toSlice().empty()) {
289+
if (bytes(opts.defender.upgradeApprovalProcessId).length != 0) {
280290
inputBuilder[i++] = "--approvalProcessId";
281291
inputBuilder[i++] = opts.defender.upgradeApprovalProcessId;
282292
}
@@ -303,15 +313,15 @@ library DefenderDeploy {
303313
return parseApprovalProcessResponse(stdout);
304314
}
305315

306-
function parseApprovalProcessResponse(string memory stdout) internal pure returns (ApprovalProcessResponse memory) {
316+
function parseApprovalProcessResponse(string memory stdout) internal returns (ApprovalProcessResponse memory) {
307317
Vm vm = Vm(Utils.CHEATCODE_ADDRESS);
308318

309319
ApprovalProcessResponse memory response;
310320

311321
response.approvalProcessId = _parseLine("Approval process ID: ", stdout, true);
312322

313323
string memory viaString = _parseLine("Via: ", stdout, false);
314-
if (viaString.toSlice().len() != 0) {
324+
if (bytes(viaString).length != 0) {
315325
response.via = vm.parseAddress(viaString);
316326
}
317327

0 commit comments

Comments
 (0)