Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 7 additions & 11 deletions scripts/universal/MultisigScript.sol
Original file line number Diff line number Diff line change
Expand Up @@ -757,19 +757,15 @@ abstract contract MultisigScript is Script {
// 4. Otherwise we fallback to the safe's current nonce (no override).
function _getNonce(address safe) internal view virtual returns (uint256 nonce) {
uint256 safeNonce = IGnosisSafe(safe).nonce();
nonce = safeNonce;

// first try SAFE_NONCE
try vm.envUint({ name: "SAFE_NONCE" }) {
nonce = vm.envUint({ name: "SAFE_NONCE" });
} catch { }

// then try SAFE_NONCE_{UPPERCASE_SAFE_ADDRESS}
string memory envVarName = string.concat("SAFE_NONCE_", vm.toUppercase({ input: vm.toString({ value: safe }) }));
try vm.envUint({ name: envVarName }) {
nonce = vm.envUint({ name: envVarName });
} catch { }

if (vm.envExists({ name: envVarName })) {
nonce = vm.envUint({ name: envVarName });
} else {
nonce = vm.envExists({ name: "SAFE_NONCE" })
? vm.envUint({ name: "SAFE_NONCE" })
: safeNonce;
}
// print if any override
if (nonce != safeNonce) {
console.log("Overriding nonce for safe %s: %d -> %d", safe, safeNonce, nonce);
Expand Down
59 changes: 59 additions & 0 deletions test/universal/MultisigScript.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,65 @@ contract MultisigScriptTest is Test, MultisigScript {
return _encodeTransactionData(_ownerSafe(), _buildAggregatedScriptCall({ scriptCalls: _buildCalls() }));
}

function getNonceForTest(address safeAddress) external view returns (uint256) {
return _getNonce({ safe: safeAddress });
}

/// @notice Tests nonce override precedence and rejects malformed configured values.
function test_getNonce_envOverrides_followDocumentedPrecedence() external {
address nonceTestSafe = makeAddr("nonce-override-test-safe");
vm.etch(nonceTestSafe, Preinstalls.getDeployedCode(Preinstalls.Safe_v130, block.chainid));

uint256 safeNonce = IGnosisSafe(nonceTestSafe).nonce();
string memory addressEnvVar =
string.concat("SAFE_NONCE_", vm.toUppercase({ input: vm.toString({ value: nonceTestSafe }) }));

bool addressNonceWasSet = vm.envExists({ name: addressEnvVar });
string memory originalAddressNonce = addressNonceWasSet ? vm.envString({ name: addressEnvVar }) : "";
bool generalNonceWasSet = vm.envExists({ name: "SAFE_NONCE" });
uint256 baselineNonce = safeNonce;

if (!addressNonceWasSet && !generalNonceWasSet) {
assertEq(
_getNonce({ safe: nonceTestSafe }),
safeNonce,
"Safe nonce should be used when no override is set"
);
} else if (generalNonceWasSet) {
try vm.envUint({ name: "SAFE_NONCE" }) returns (uint256 configuredNonce) {
baselineNonce = configuredNonce;
if (!addressNonceWasSet) {
assertEq(
_getNonce({ safe: nonceTestSafe }),
configuredNonce,
"General override should be used when no address-specific override is set"
);
}
} catch {
if (!addressNonceWasSet) {
vm.expectRevert(); // nosemgrep: sol-safety-expectrevert-no-args
this.getNonceForTest(nonceTestSafe);
}
}
}

uint256 addressOverrideNonce = baselineNonce == type(uint256).max ? baselineNonce - 1 : baselineNonce + 1;
// Keep this override isolated from the Safe fixtures used by other tests.
vm.setEnv({ name: addressEnvVar, value: vm.toString({ value: addressOverrideNonce }) });
assertEq(
_getNonce({ safe: nonceTestSafe }), addressOverrideNonce, "Address-specific override should take precedence"
);

vm.setEnv({ name: addressEnvVar, value: "invalid" });
vm.expectRevert(); // nosemgrep: sol-safety-expectrevert-no-args
this.getNonceForTest(nonceTestSafe);

vm.setEnv({
name: addressEnvVar,
value: addressNonceWasSet ? originalAddressNonce : vm.toString({ value: safeNonce })
});
}

function _signDigest(Vm.Wallet memory wallet, bytes32 digest) internal returns (bytes memory) {
(uint8 v, bytes32 r, bytes32 s) = vm.sign(wallet, digest);
return abi.encodePacked(r, s, v);
Expand Down