Skip to content

Commit

Permalink
Remove dependency on solidity-stringutils (#91)
Browse files Browse the repository at this point in the history
  • Loading branch information
ericglau authored Jan 27, 2025
1 parent c29dd49 commit cbce1e0
Show file tree
Hide file tree
Showing 18 changed files with 243 additions and 151 deletions.
3 changes: 0 additions & 3 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "lib/solidity-stringutils"]
path = lib/solidity-stringutils
url = https://github.com/Arachnid/solidity-stringutils
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Changelog

## 0.4.0 (2025-01-27)

- Remove dependency on `solidity-stringutils`. ([#91](https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades/pull/91))

### Breaking changes
- Requires `forge-std` version v1.9.5 or higher.

## 0.3.8 (2025-01-24)

- Fix error conditions when warnings occur in validation output. ([#94](https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades/pull/94))
Expand Down
8 changes: 3 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,20 +54,18 @@ Follow the steps above, but instead of running `forge install OpenZeppelin/openz
npm install @openzeppelin/foundry-upgrades
```

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

#### Soldeer

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

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):
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):
```
openzeppelin-foundry-upgrades/=dependencies/openzeppelin-foundry-upgrades-0.3.6/src/
solidity-stringutils/=dependencies/openzeppelin-foundry-upgrades-0.3.6/lib/solidity-stringutils/
```

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

## Foundry Requirements

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

## Before Running

Expand Down
8 changes: 3 additions & 5 deletions docs/modules/pages/foundry-upgrades.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -59,27 +59,25 @@ Follow the steps above, but instead of running `forge install OpenZeppelin/openz
npm install @openzeppelin/foundry-upgrades
----

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

==== Soldeer

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

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):
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):
[source,console]
----
openzeppelin-foundry-upgrades/=dependencies/openzeppelin-foundry-upgrades-0.3.6/src/
solidity-stringutils/=dependencies/openzeppelin-foundry-upgrades-0.3.6/lib/solidity-stringutils/
----

== Foundry Requirements

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

== Before Running

Expand Down
1 change: 0 additions & 1 deletion lib/solidity-stringutils
Submodule solidity-stringutils deleted from 4b2fcc
5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
{
"name": "@openzeppelin/foundry-upgrades",
"version": "0.3.8",
"version": "0.4.0",
"description": "Foundry library for deploying and managing upgradeable contracts",
"license": "MIT",
"files": [
"src/**/*",
"lib/solidity-stringutils/**/*"
"src/**/*"
],
"repository": {
"type": "git",
Expand Down
17 changes: 10 additions & 7 deletions src/internal/Core.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ pragma solidity ^0.8.0;

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

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

import {Options} from "../Options.sol";
import {Versions} from "./Versions.sol";
Expand Down Expand Up @@ -61,6 +62,8 @@ library Core {
upgradeProxy(proxy, contractName, data, opts);
}

using Strings for *;

/**
* @dev Upgrades a proxy to a new implementation contract. Only supported for UUPS or transparent proxies.
*
Expand All @@ -74,15 +77,15 @@ library Core {
bytes32 adminSlot = vm.load(proxy, ADMIN_SLOT);
if (adminSlot == bytes32(0)) {
string memory upgradeInterfaceVersion = getUpgradeInterfaceVersion(proxy);
if (upgradeInterfaceVersion.toSlice().equals("5.0.0".toSlice()) || data.length > 0) {
if (upgradeInterfaceVersion.equal("5.0.0") || data.length > 0) {
IUpgradeableProxy(proxy).upgradeToAndCall(newImpl, data);
} else {
IUpgradeableProxy(proxy).upgradeTo(newImpl);
}
} else {
address admin = address(uint160(uint256(adminSlot)));
string memory upgradeInterfaceVersion = getUpgradeInterfaceVersion(admin);
if (upgradeInterfaceVersion.toSlice().equals("5.0.0".toSlice()) || data.length > 0) {
if (upgradeInterfaceVersion.equal("5.0.0") || data.length > 0) {
IProxyAdmin(admin).upgradeAndCall(proxy, newImpl, data);
} else {
IProxyAdmin(admin).upgrade(proxy, newImpl);
Expand Down Expand Up @@ -300,8 +303,6 @@ library Core {
*/
bytes32 private constant BEACON_SLOT = 0xa3f0ad74e5423aebfd80d3ef4346578335a9a72aeaee59ff6cb3582b35133d50;

using strings for *;

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

// CLI validate command uses exit code to indicate if the validation passed or failed.
Vm vm = Vm(Utils.CHEATCODE_ADDRESS);
if (result.exitCode == 0) {
// As an extra precaution, we also check stdout for "SUCCESS" to ensure it actually ran.
if (stdout.toSlice().contains("SUCCESS".toSlice())) {
if (vm.contains(stdout, "SUCCESS")) {
if (result.stderr.length > 0) {
// Prints warnings from stderr
console.log(string(result.stderr));
Expand All @@ -357,7 +359,7 @@ library Core {
revert(string(abi.encodePacked("Failed to run upgrade safety validation: ", stdout)));
}
} else {
if (stdout.toSlice().contains("FAILED".toSlice())) {
if (vm.contains(stdout, "FAILED")) {
if (result.stderr.length > 0) {
// Prints warnings from stderr
console.log(string(result.stderr));
Expand Down Expand Up @@ -470,6 +472,7 @@ library Core {

function _deployFromBytecode(bytes memory bytecode) private returns (address) {
address addr;
/// @solidity memory-safe-assembly
assembly {
addr := create(0, add(bytecode, 32), mload(bytecode))
}
Expand Down
80 changes: 45 additions & 35 deletions src/internal/DefenderDeploy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ pragma solidity ^0.8.0;

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

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

Expand All @@ -18,8 +17,6 @@ import {ProposeUpgradeResponse, ApprovalProcessResponse} from "../Defender.sol";
* WARNING: DO NOT USE DIRECTLY. Use Defender.sol instead.
*/
library DefenderDeploy {
using strings for *;

function deploy(
string memory contractName,
bytes memory constructorData,
Expand Down Expand Up @@ -54,7 +51,7 @@ library DefenderDeploy {
) internal view returns (string[] memory) {
Vm vm = Vm(Utils.CHEATCODE_ADDRESS);

if (!(defenderOpts.licenseType).toSlice().empty()) {
if (bytes(defenderOpts.licenseType).length != 0) {
if (defenderOpts.skipVerifySourceCode) {
revert("The `licenseType` option cannot be used when the `skipVerifySourceCode` option is `true`");
} else if (defenderOpts.skipLicenseType) {
Expand Down Expand Up @@ -86,14 +83,14 @@ library DefenderDeploy {
if (defenderOpts.skipVerifySourceCode) {
inputBuilder[i++] = "--verifySourceCode";
inputBuilder[i++] = "false";
} else if (!(defenderOpts.licenseType).toSlice().empty()) {
} else if (bytes(defenderOpts.licenseType).length != 0) {
inputBuilder[i++] = "--licenseType";
inputBuilder[i++] = string(abi.encodePacked('"', defenderOpts.licenseType, '"'));
} else if (!defenderOpts.skipLicenseType && !(contractInfo.license).toSlice().empty()) {
} else if (!defenderOpts.skipLicenseType && bytes(contractInfo.license).length != 0) {
inputBuilder[i++] = "--licenseType";
inputBuilder[i++] = string(abi.encodePacked('"', _toLicenseType(contractInfo), '"'));
}
if (!(defenderOpts.relayerId).toSlice().empty()) {
if (bytes(defenderOpts.relayerId).length != 0) {
inputBuilder[i++] = "--relayerId";
inputBuilder[i++] = defenderOpts.relayerId;
}
Expand All @@ -117,7 +114,7 @@ library DefenderDeploy {
inputBuilder[i++] = "--maxPriorityFeePerGas";
inputBuilder[i++] = Strings.toString(defenderOpts.txOverrides.maxPriorityFeePerGas);
}
if (!(defenderOpts.metadata).toSlice().empty()) {
if (bytes(defenderOpts.metadata).length != 0) {
inputBuilder[i++] = "--metadata";
inputBuilder[i++] = string(abi.encodePacked('"', vm.replace(defenderOpts.metadata, '"', '\\"'), '"'));
}
Expand All @@ -133,35 +130,37 @@ library DefenderDeploy {
return inputs;
}

using Strings for string;

function _toLicenseType(ContractInfo memory contractInfo) private pure returns (string memory) {
strings.slice memory id = contractInfo.license.toSlice();
if (id.equals("UNLICENSED".toSlice())) {
string memory id = contractInfo.license;
if (id.equal("UNLICENSED")) {
return "None";
} else if (id.equals("Unlicense".toSlice())) {
} else if (id.equal("Unlicense")) {
return "Unlicense";
} else if (id.equals("MIT".toSlice())) {
} else if (id.equal("MIT")) {
return "MIT";
} else if (id.equals("GPL-2.0-only".toSlice()) || id.equals("GPL-2.0-or-later".toSlice())) {
} else if (id.equal("GPL-2.0-only") || id.equal("GPL-2.0-or-later")) {
return "GNU GPLv2";
} else if (id.equals("GPL-3.0-only".toSlice()) || id.equals("GPL-3.0-or-later".toSlice())) {
} else if (id.equal("GPL-3.0-only") || id.equal("GPL-3.0-or-later")) {
return "GNU GPLv3";
} else if (id.equals("LGPL-2.1-only".toSlice()) || id.equals("LGPL-2.1-or-later".toSlice())) {
} else if (id.equal("LGPL-2.1-only") || id.equal("LGPL-2.1-or-later")) {
return "GNU LGPLv2.1";
} else if (id.equals("LGPL-3.0-only".toSlice()) || id.equals("LGPL-3.0-or-later".toSlice())) {
} else if (id.equal("LGPL-3.0-only") || id.equal("LGPL-3.0-or-later")) {
return "GNU LGPLv3";
} else if (id.equals("BSD-2-Clause".toSlice())) {
} else if (id.equal("BSD-2-Clause")) {
return "BSD-2-Clause";
} else if (id.equals("BSD-3-Clause".toSlice())) {
} else if (id.equal("BSD-3-Clause")) {
return "BSD-3-Clause";
} else if (id.equals("MPL-2.0".toSlice())) {
} else if (id.equal("MPL-2.0")) {
return "MPL-2.0";
} else if (id.equals("OSL-3.0".toSlice())) {
} else if (id.equal("OSL-3.0")) {
return "OSL-3.0";
} else if (id.equals("Apache-2.0".toSlice())) {
} else if (id.equal("Apache-2.0")) {
return "Apache-2.0";
} else if (id.equals("AGPL-3.0-only".toSlice()) || id.equals("AGPL-3.0-or-later".toSlice())) {
} else if (id.equal("AGPL-3.0-only") || id.equal("AGPL-3.0-or-later")) {
return "GNU AGPLv3";
} else if (id.equals("BUSL-1.1".toSlice())) {
} else if (id.equal("BUSL-1.1")) {
return "BSL 1.1";
} else {
revert(
Expand Down Expand Up @@ -217,7 +216,7 @@ library DefenderDeploy {
return parseProposeUpgradeResponse(stdout);
}

function parseProposeUpgradeResponse(string memory stdout) internal pure returns (ProposeUpgradeResponse memory) {
function parseProposeUpgradeResponse(string memory stdout) internal returns (ProposeUpgradeResponse memory) {
ProposeUpgradeResponse memory response;
response.proposalId = _parseLine("Proposal ID: ", stdout, true);
response.url = _parseLine("Proposal URL: ", stdout, false);
Expand All @@ -228,15 +227,26 @@ library DefenderDeploy {
string memory expectedPrefix,
string memory stdout,
bool required
) private pure returns (string memory) {
strings.slice memory delim = expectedPrefix.toSlice();
if (stdout.toSlice().contains(delim)) {
strings.slice memory slice = stdout.toSlice().copy().find(delim).beyond(delim);
// Remove any following lines
if (slice.contains("\n".toSlice())) {
slice = slice.split("\n".toSlice());
) private returns (string memory) {
Vm vm = Vm(Utils.CHEATCODE_ADDRESS);
if (vm.contains(stdout, expectedPrefix)) {
// Get the substring after the prefix
string[] memory segments = vm.split(stdout, expectedPrefix);
if (segments.length > 2) {
revert(
string(
abi.encodePacked(
"Found multiple occurrences of prefix '",
expectedPrefix,
"' in output: ",
stdout
)
)
);
}
return slice.toString();
string memory suffix = segments[1];
// Keep only the first line
return vm.split(suffix, "\n")[0];
} else if (required) {
revert(
string(abi.encodePacked("Failed to find line with prefix '", expectedPrefix, "' in output: ", stdout))
Expand Down Expand Up @@ -276,7 +286,7 @@ library DefenderDeploy {
inputBuilder[i++] = "--proxyAdminAddress";
inputBuilder[i++] = vm.toString(proxyAdminAddress);
}
if (!(opts.defender.upgradeApprovalProcessId).toSlice().empty()) {
if (bytes(opts.defender.upgradeApprovalProcessId).length != 0) {
inputBuilder[i++] = "--approvalProcessId";
inputBuilder[i++] = opts.defender.upgradeApprovalProcessId;
}
Expand All @@ -303,15 +313,15 @@ library DefenderDeploy {
return parseApprovalProcessResponse(stdout);
}

function parseApprovalProcessResponse(string memory stdout) internal pure returns (ApprovalProcessResponse memory) {
function parseApprovalProcessResponse(string memory stdout) internal returns (ApprovalProcessResponse memory) {
Vm vm = Vm(Utils.CHEATCODE_ADDRESS);

ApprovalProcessResponse memory response;

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

string memory viaString = _parseLine("Via: ", stdout, false);
if (viaString.toSlice().len() != 0) {
if (bytes(viaString).length != 0) {
response.via = vm.parseAddress(viaString);
}

Expand Down
Loading

0 comments on commit cbce1e0

Please sign in to comment.