949 lines
41 KiB
Solidity
949 lines
41 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity 0.8.35;
|
|
|
|
import {Test} from "forge-std/Test.sol";
|
|
import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";
|
|
import {DemoScript} from "../script/lib/DemoScript.sol";
|
|
import {DeployV1} from "../script/DeployV1.s.sol";
|
|
import {SeedV1Demo} from "../script/SeedV1Demo.s.sol";
|
|
import {SeedBaseSepolia} from "../script/SeedBaseSepolia.s.sol";
|
|
import {CheckState} from "../script/CheckState.s.sol";
|
|
import {UpgradeV2} from "../script/UpgradeV2.s.sol";
|
|
import {TransferV2Demo} from "../script/TransferV2Demo.s.sol";
|
|
import {BankV1} from "../src/BankV1.sol";
|
|
import {BankV2} from "../src/BankV2.sol";
|
|
import {MockUSDC} from "../src/MockUSDC.sol";
|
|
|
|
contract ScriptPreflightHarness is DemoScript {
|
|
function requireSupportedChain(uint256 chainId) external pure {
|
|
_requireSupportedChain(chainId);
|
|
}
|
|
|
|
function deriveLocalActor(uint256 chainId, uint32 index) external pure returns (address actor) {
|
|
(, actor) = _deriveLocalActor(chainId, index);
|
|
}
|
|
|
|
function readManifest(string calldata path, bool active) external view returns (Manifest memory) {
|
|
return _readManifest(path, active);
|
|
}
|
|
|
|
function requireCode(string calldata label, address target) external view {
|
|
_requireCode(label, target);
|
|
}
|
|
|
|
function serializeManifest(Manifest calldata manifest) external returns (string memory) {
|
|
return _serializeManifest(manifest);
|
|
}
|
|
|
|
function educationalWarning() external pure returns (string memory) {
|
|
return _educationalWarning();
|
|
}
|
|
}
|
|
|
|
contract UpgradeV2Harness is UpgradeV2 {
|
|
function assertSnapshotUnchanged(Snapshot calldata before_, Snapshot calldata after_) external pure {
|
|
_assertSnapshotUnchanged(before_, after_);
|
|
}
|
|
|
|
function runWithPaths(string calldata activePath, string calldata pendingPath, address sender)
|
|
external
|
|
returns (bool upgraded, address implementation)
|
|
{
|
|
_requireSupportedChain(block.chainid);
|
|
return _run(activePath, pendingPath, sender);
|
|
}
|
|
}
|
|
|
|
contract TransferV2DemoHarness is TransferV2Demo {
|
|
function runWithPath(string calldata manifestPath) external {
|
|
_run(manifestPath);
|
|
}
|
|
}
|
|
|
|
contract CheckStateHarness is CheckState {
|
|
function runWithPath(string calldata manifestPath, string calldata stage) external view {
|
|
_run(manifestPath, stage);
|
|
}
|
|
}
|
|
|
|
contract ScriptPreflightTest is Test {
|
|
ScriptPreflightHarness internal harness;
|
|
string internal fixtureDir;
|
|
|
|
address internal constant OWNER = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266;
|
|
address internal constant ALICE = 0x70997970C51812dc3A010C7d01b50e0d17dc79C8;
|
|
address internal constant BOB = 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC;
|
|
address internal constant TOKEN = 0x1000000000000000000000000000000000000001;
|
|
address internal constant PROXY = 0x2000000000000000000000000000000000000002;
|
|
address internal constant IMPLEMENTATION = 0x3000000000000000000000000000000000000003;
|
|
|
|
function setUp() public {
|
|
harness = new ScriptPreflightHarness();
|
|
fixtureDir = string.concat(vm.projectRoot(), "/deployments/test-script-preflight");
|
|
vm.createDir(fixtureDir, true);
|
|
}
|
|
|
|
function testSupportedChainsAreAccepted() public view {
|
|
harness.requireSupportedChain(31337);
|
|
harness.requireSupportedChain(84532);
|
|
}
|
|
|
|
function testUnsupportedChainsAreRejectedBeforeBroadcast() public {
|
|
uint256[7] memory rejected =
|
|
[uint256(1), uint256(10), uint256(56), uint256(137), uint256(8453), uint256(42161), uint256(7777777)];
|
|
for (uint256 i; i < rejected.length; ++i) {
|
|
vm.expectRevert(abi.encodeWithSelector(DemoScript.UnsupportedChain.selector, rejected[i]));
|
|
harness.requireSupportedChain(rejected[i]);
|
|
}
|
|
}
|
|
|
|
function testLocalActorsDeriveOnlyOnAnvil() public {
|
|
assertEq(harness.deriveLocalActor(31337, 0), OWNER);
|
|
assertEq(harness.deriveLocalActor(31337, 1), ALICE);
|
|
|
|
vm.expectRevert(abi.encodeWithSelector(DemoScript.UnsupportedChain.selector, uint256(84532)));
|
|
harness.deriveLocalActor(84532, 0);
|
|
}
|
|
|
|
function testMissingManifestIsRejected() public {
|
|
vm.expectRevert();
|
|
harness.readManifest(string.concat(fixtureDir, "/missing.json"), false);
|
|
}
|
|
|
|
function testInvalidManifestIsRejected() public {
|
|
string memory path = string.concat(fixtureDir, "/invalid.json");
|
|
vm.writeFile(path, "not-json");
|
|
vm.expectRevert();
|
|
harness.readManifest(path, false);
|
|
}
|
|
|
|
function testWrongManifestChainIsRejected() public {
|
|
string memory path = _writeManifest(84532, 1, TOKEN, PROXY, IMPLEMENTATION);
|
|
vm.chainId(31337);
|
|
vm.expectRevert(
|
|
abi.encodeWithSelector(DemoScript.ManifestChainMismatch.selector, uint256(31337), uint256(84532))
|
|
);
|
|
harness.readManifest(path, true);
|
|
}
|
|
|
|
function testZeroManifestAddressIsRejected() public {
|
|
string memory path = _writeManifest(31337, 1, address(0), PROXY, IMPLEMENTATION);
|
|
vm.chainId(31337);
|
|
vm.expectRevert(abi.encodeWithSelector(DemoScript.MissingCode.selector, "token", address(0)));
|
|
harness.readManifest(path, true);
|
|
}
|
|
|
|
function testAddressWithoutCodeIsRejected() public {
|
|
vm.expectRevert(abi.encodeWithSelector(DemoScript.MissingCode.selector, "token", TOKEN));
|
|
harness.requireCode("token", TOKEN);
|
|
}
|
|
|
|
function testPendingManifestMayUseDeploymentBlockZero() public {
|
|
string memory path = _writeManifest(31337, 0, TOKEN, PROXY, IMPLEMENTATION);
|
|
vm.chainId(31337);
|
|
vm.etch(TOKEN, hex"00");
|
|
vm.etch(PROXY, hex"00");
|
|
vm.etch(IMPLEMENTATION, hex"00");
|
|
DemoScript.Manifest memory manifest = harness.readManifest(path, false);
|
|
assertEq(manifest.deploymentBlock, 0);
|
|
}
|
|
|
|
function testActiveManifestRejectsDeploymentBlockZero() public {
|
|
string memory path = _writeManifest(31337, 0, TOKEN, PROXY, IMPLEMENTATION);
|
|
vm.chainId(31337);
|
|
vm.etch(TOKEN, hex"00");
|
|
vm.etch(PROXY, hex"00");
|
|
vm.etch(IMPLEMENTATION, hex"00");
|
|
vm.expectRevert(DemoScript.InvalidDeploymentBlock.selector);
|
|
harness.readManifest(path, true);
|
|
}
|
|
|
|
function testAnvilManifestRejectsUnexpectedActorObjectCount() public {
|
|
string memory path = _writeManifest(31337, 1, TOKEN, PROXY, IMPLEMENTATION);
|
|
_etchManifestContracts();
|
|
vm.writeJson(
|
|
string.concat(
|
|
'[{"label":"owner","address":"',
|
|
vm.toString(OWNER),
|
|
'"},{"label":"Alice","address":"',
|
|
vm.toString(ALICE),
|
|
'"}]'
|
|
),
|
|
path,
|
|
".actors"
|
|
);
|
|
vm.expectRevert();
|
|
harness.readManifest(path, true);
|
|
}
|
|
|
|
function testAnvilManifestRejectsUnexpectedActorLabels() public {
|
|
string memory path = _writeManifest(31337, 1, TOKEN, PROXY, IMPLEMENTATION);
|
|
_etchManifestContracts();
|
|
vm.writeJson(
|
|
string.concat(
|
|
'[{"label":"owner","address":"',
|
|
vm.toString(OWNER),
|
|
'"},{"label":"Mallory","address":"',
|
|
vm.toString(ALICE),
|
|
'"},{"label":"Bob","address":"0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC"}]'
|
|
),
|
|
path,
|
|
".actors"
|
|
);
|
|
vm.expectRevert(DemoScript.InvalidActorConfiguration.selector);
|
|
harness.readManifest(path, true);
|
|
}
|
|
|
|
function testAnvilManifestRequiresOwnerAtActorZero() public {
|
|
string memory path = _writeManifest(31337, 1, TOKEN, PROXY, IMPLEMENTATION);
|
|
_etchManifestContracts();
|
|
vm.writeJson(
|
|
string.concat(
|
|
'[{"label":"owner","address":"',
|
|
vm.toString(ALICE),
|
|
'"},{"label":"Alice","address":"',
|
|
vm.toString(OWNER),
|
|
'"},{"label":"Bob","address":"0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC"}]'
|
|
),
|
|
path,
|
|
".actors"
|
|
);
|
|
vm.expectRevert(DemoScript.InvalidActorConfiguration.selector);
|
|
harness.readManifest(path, true);
|
|
}
|
|
|
|
function testBaseManifestRequiresExactPresenterRecipientActors() public {
|
|
string memory path = _writePublicBaseManifest();
|
|
vm.chainId(84532);
|
|
_etchManifestContracts();
|
|
vm.writeJson(
|
|
string.concat(
|
|
'[{"label":"owner","address":"',
|
|
vm.toString(OWNER),
|
|
'"},{"label":"Alice","address":"',
|
|
vm.toString(ALICE),
|
|
'"}]'
|
|
),
|
|
path,
|
|
".actors"
|
|
);
|
|
vm.expectRevert(DemoScript.InvalidActorConfiguration.selector);
|
|
harness.readManifest(path, true);
|
|
}
|
|
|
|
function testEducationalWarningIsExact() public view {
|
|
assertEq(harness.educationalWarning(), unicode"Educational demo — mock token — never use real funds.");
|
|
}
|
|
|
|
function testNestedActorsRoundTripThroughManifestSerialization() public {
|
|
string memory path = _writePublicAnvilManifest();
|
|
_etchManifestContracts();
|
|
DemoScript.Manifest memory manifest = harness.readManifest(path, true);
|
|
assertEq(manifest.actors[0].label, "owner");
|
|
assertEq(manifest.actors[1].address_, ALICE);
|
|
|
|
string memory serialized = harness.serializeManifest(manifest);
|
|
assertFalse(_contains(serialized, "actorLabels"));
|
|
assertFalse(_contains(serialized, "explorerUrl"));
|
|
vm.writeFile(path, serialized);
|
|
DemoScript.Manifest memory roundTrip = harness.readManifest(path, true);
|
|
assertEq(roundTrip.actors[2].label, "Bob");
|
|
assertEq(roundTrip.actors[2].address_, 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC);
|
|
}
|
|
|
|
function testLegacyParallelActorManifestIsRejected() public {
|
|
string memory path = _writeLegacyParallelActorManifest();
|
|
_etchManifestContracts();
|
|
vm.expectRevert(abi.encodeWithSelector(DemoScript.InvalidManifestSchema.selector, uint256(0)));
|
|
harness.readManifest(path, true);
|
|
}
|
|
|
|
function testActorObjectWithExtraKeyIsRejected() public {
|
|
string memory path = _writeAnvilManifestWithExtraActorKey("note", "public");
|
|
_etchManifestContracts();
|
|
vm.expectRevert(abi.encodeWithSelector(DemoScript.InvalidManifestSchema.selector, uint256(0)));
|
|
harness.readManifest(path, true);
|
|
}
|
|
|
|
function testActorObjectWithSecretBearingKeyIsRejected() public {
|
|
string memory path = _writeAnvilManifestWithExtraActorKey("privateKey", "not-a-key");
|
|
_etchManifestContracts();
|
|
vm.expectRevert(abi.encodeWithSelector(DemoScript.InvalidManifestSchema.selector, uint256(0)));
|
|
harness.readManifest(path, true);
|
|
}
|
|
|
|
function testBaseSepoliaManifestUsesPublicUrlsAndExactActors() public {
|
|
string memory path = _writePublicBaseManifest();
|
|
vm.chainId(84532);
|
|
_etchManifestContracts();
|
|
DemoScript.Manifest memory manifest = harness.readManifest(path, true);
|
|
assertEq(manifest.network, "baseSepolia");
|
|
assertEq(manifest.rpcUrl, "https://public.invalid");
|
|
assertEq(manifest.explorerBaseUrl, "https://sepolia.basescan.org");
|
|
assertEq(manifest.actors[0].label, "Presenter");
|
|
assertEq(manifest.actors[0].address_, OWNER);
|
|
assertEq(manifest.actors[1].label, "Recipient");
|
|
assertEq(manifest.actors[1].address_, BOB);
|
|
}
|
|
|
|
function testSerializedManifestContainsPublicAddressesAndNoSecrets() public {
|
|
DemoScript.Manifest memory manifest = DemoScript.Manifest({
|
|
schemaVersion: 1,
|
|
network: "anvil",
|
|
chainId: 31337,
|
|
deploymentBlock: 0,
|
|
rpcUrl: "http://127.0.0.1:8545",
|
|
explorerBaseUrl: "",
|
|
token: TOKEN,
|
|
proxy: PROXY,
|
|
implementation: IMPLEMENTATION,
|
|
owner: OWNER,
|
|
actors: _actors()
|
|
});
|
|
|
|
string memory json = harness.serializeManifest(manifest);
|
|
assertTrue(_contains(json, vm.toString(TOKEN)));
|
|
assertTrue(_contains(json, vm.toString(PROXY)));
|
|
assertTrue(_contains(json, vm.toString(IMPLEMENTATION)));
|
|
assertTrue(_contains(json, vm.toString(OWNER)));
|
|
assertFalse(_contains(json, "test test test test test test test test test test test junk"));
|
|
assertFalse(_contains(json, "PRIVATE_KEY"));
|
|
assertFalse(_contains(json, "MNEMONIC"));
|
|
assertFalse(_contains(json, "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"));
|
|
}
|
|
|
|
function testDeployV1CreatesInitializedProxyAndPublicPendingManifest() public {
|
|
string memory path = string.concat(fixtureDir, "/deployed.json");
|
|
vm.chainId(31337);
|
|
vm.setEnv("SCRIPT_SENDER", vm.toString(OWNER));
|
|
vm.setEnv("DEPLOYMENT_MANIFEST_PATH", path);
|
|
|
|
DeployV1 deployer = new DeployV1();
|
|
(address token, address proxy, address implementation) = deployer.run();
|
|
|
|
DemoScript.Manifest memory manifest = harness.readManifest(path, false);
|
|
assertEq(manifest.token, token);
|
|
assertEq(manifest.proxy, proxy);
|
|
assertEq(manifest.implementation, implementation);
|
|
assertEq(BankV1(proxy).owner(), OWNER);
|
|
assertEq(address(BankV1(proxy).asset()), token);
|
|
assertEq(BankV1(proxy).contractVersion(), 1);
|
|
assertEq(manifest.actors[0].label, "owner");
|
|
assertEq(manifest.actors[1].label, "Alice");
|
|
assertEq(manifest.actors[2].label, "Bob");
|
|
assertEq(manifest.actors[0].address_, OWNER);
|
|
assertEq(manifest.actors[1].address_, ALICE);
|
|
assertEq(manifest.actors[2].address_, 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC);
|
|
}
|
|
|
|
function testBaseDeployRejectsMismatchedConfiguredSenderBeforeBroadcast() public {
|
|
string memory path = string.concat(fixtureDir, "/base-deploy-mismatch.json");
|
|
vm.chainId(84532);
|
|
vm.setEnv("SCRIPT_SENDER", vm.toString(OWNER));
|
|
vm.setEnv("BASE_SEPOLIA_SENDER", vm.toString(ALICE));
|
|
vm.setEnv("BASE_SEPOLIA_RECIPIENT", vm.toString(BOB));
|
|
vm.setEnv("BASE_SEPOLIA_PUBLIC_RPC_URL", "https://public.invalid");
|
|
vm.setEnv("DEPLOYMENT_MANIFEST_PATH", path);
|
|
uint256 nonceBefore = vm.getNonce(OWNER);
|
|
DeployV1 deployer = new DeployV1();
|
|
|
|
vm.expectRevert(
|
|
abi.encodeWithSelector(DemoScript.UnexpectedAddress.selector, "BASE_SEPOLIA_SENDER", OWNER, ALICE)
|
|
);
|
|
deployer.run();
|
|
|
|
assertEq(vm.getNonce(OWNER), nonceBefore);
|
|
}
|
|
|
|
function testBaseDeployWritesPublicRpcExplorerAndPresenterRecipientActors() public {
|
|
string memory path = string.concat(fixtureDir, "/base-deployed.json");
|
|
vm.chainId(84532);
|
|
_setBaseConfig(OWNER, BOB, "https://public.invalid");
|
|
vm.setEnv("DEPLOYMENT_MANIFEST_PATH", path);
|
|
|
|
(address token, address proxy, address implementation) = new DeployV1().run();
|
|
|
|
_etchManifestContracts();
|
|
DemoScript.Manifest memory manifest = harness.readManifest(path, false);
|
|
assertEq(manifest.token, token);
|
|
assertEq(manifest.proxy, proxy);
|
|
assertEq(manifest.implementation, implementation);
|
|
assertEq(manifest.owner, OWNER);
|
|
assertEq(manifest.rpcUrl, "https://public.invalid");
|
|
assertEq(manifest.explorerBaseUrl, "https://sepolia.basescan.org");
|
|
assertEq(manifest.actors.length, 2);
|
|
assertEq(manifest.actors[0].label, "Presenter");
|
|
assertEq(manifest.actors[0].address_, OWNER);
|
|
assertEq(manifest.actors[1].label, "Recipient");
|
|
assertEq(manifest.actors[1].address_, BOB);
|
|
}
|
|
|
|
function testBaseDeployRejectsZeroOrSameRecipientBeforeBroadcast() public {
|
|
vm.chainId(84532);
|
|
DeployV1 deployer = new DeployV1();
|
|
address[2] memory invalidRecipients = [address(0), OWNER];
|
|
for (uint256 i; i < invalidRecipients.length; ++i) {
|
|
_setBaseConfig(OWNER, invalidRecipients[i], "https://public.invalid");
|
|
uint256 nonceBefore = vm.getNonce(OWNER);
|
|
vm.expectRevert(DemoScript.InvalidActorConfiguration.selector);
|
|
deployer.run();
|
|
assertEq(vm.getNonce(OWNER), nonceBefore);
|
|
}
|
|
}
|
|
|
|
function testBaseDeployRejectsCredentialBearingPublicRpcBeforeBroadcast() public {
|
|
vm.chainId(84532);
|
|
DeployV1 deployer = new DeployV1();
|
|
string[3] memory invalidUrls =
|
|
["http://public.invalid", "https://user@public.invalid", "https://public.invalid/path?api_key=fixture"];
|
|
bytes4 invalidPublicRpcUrl = bytes4(keccak256("InvalidPublicRpcUrl()"));
|
|
for (uint256 i; i < invalidUrls.length; ++i) {
|
|
_setBaseConfig(OWNER, BOB, invalidUrls[i]);
|
|
uint256 nonceBefore = vm.getNonce(OWNER);
|
|
vm.expectRevert(invalidPublicRpcUrl);
|
|
deployer.run();
|
|
assertEq(vm.getNonce(OWNER), nonceBefore);
|
|
}
|
|
}
|
|
|
|
function testSeedV1DemoExecutesExactActOneStateAndCheckStateAcceptsIt() public {
|
|
(string memory path, DemoScript.Manifest memory manifest) = _deployFixtureNamed("seed-v1");
|
|
vm.setEnv("DEPLOYMENT_MANIFEST_PATH", path);
|
|
|
|
new SeedV1Demo().run();
|
|
|
|
BankV1 bank = BankV1(manifest.proxy);
|
|
assertEq(bank.balanceOf(manifest.actors[1].address_), 900e6);
|
|
assertEq(bank.balanceOf(manifest.actors[2].address_), 500e6);
|
|
assertEq(bank.totalLiabilities(), 1_400e6);
|
|
assertEq(MockUSDC(manifest.token).balanceOf(manifest.proxy), 1_400e6);
|
|
assertEq(MockUSDC(manifest.token).balanceOf(manifest.actors[1].address_), 1_100e6);
|
|
assertEq(MockUSDC(manifest.token).balanceOf(manifest.actors[2].address_), 500e6);
|
|
|
|
vm.setEnv("DEMO_EXPECTED_STAGE", "v1");
|
|
new CheckState().run();
|
|
}
|
|
|
|
function testSeedBaseSepoliaCreatesExactPresenterDepositState() public {
|
|
(string memory path, DemoScript.Manifest memory manifest) = _deployBaseFixtureNamed("seed-base");
|
|
_setBaseConfig(OWNER, BOB, "https://public.invalid");
|
|
vm.setEnv("DEPLOYMENT_MANIFEST_PATH", path);
|
|
|
|
new SeedBaseSepolia().run();
|
|
|
|
BankV1 bank = BankV1(manifest.proxy);
|
|
MockUSDC token = MockUSDC(manifest.token);
|
|
assertEq(bank.balanceOf(OWNER), 1_000e6);
|
|
assertEq(bank.balanceOf(BOB), 0);
|
|
assertEq(bank.totalLiabilities(), 1_000e6);
|
|
assertEq(token.balanceOf(manifest.proxy), 1_000e6);
|
|
assertEq(token.balanceOf(OWNER), 0);
|
|
}
|
|
|
|
function testSeedBaseSepoliaRejectsMismatchedSenderBeforeMintOrBroadcast() public {
|
|
(string memory path, DemoScript.Manifest memory manifest) = _deployBaseFixtureNamed("seed-base-mismatch");
|
|
_setBaseConfig(OWNER, BOB, "https://public.invalid");
|
|
vm.setEnv("BASE_SEPOLIA_SENDER", vm.toString(ALICE));
|
|
vm.setEnv("DEPLOYMENT_MANIFEST_PATH", path);
|
|
SeedBaseSepolia seeder = new SeedBaseSepolia();
|
|
|
|
vm.expectRevert(
|
|
abi.encodeWithSelector(DemoScript.UnexpectedAddress.selector, "BASE_SEPOLIA_SENDER", OWNER, ALICE)
|
|
);
|
|
seeder.run();
|
|
|
|
assertEq(MockUSDC(manifest.token).totalSupply(), 0);
|
|
assertEq(BankV1(manifest.proxy).totalLiabilities(), 0);
|
|
}
|
|
|
|
function testCheckStateRejectsManifestImplementationMismatch() public {
|
|
(string memory path, DemoScript.Manifest memory manifest) = _deployUpgradeFixtureNamed("check-mismatch");
|
|
vm.writeJson(string.concat('"', vm.toString(manifest.token), '"'), path, ".implementation");
|
|
CheckStateHarness checker = new CheckStateHarness();
|
|
|
|
vm.expectRevert(
|
|
abi.encodeWithSelector(
|
|
DemoScript.UnexpectedAddress.selector, "implementation", manifest.token, manifest.implementation
|
|
)
|
|
);
|
|
checker.runWithPath(path, "deployed");
|
|
}
|
|
|
|
function testUpgradeV2RequiresManifestIdentityCodeOwnerSenderAndVersionOne() public {
|
|
(string memory path, DemoScript.Manifest memory manifest) = _deployUpgradeFixtureNamed("upgrade-preflight");
|
|
string memory pending = string.concat(fixtureDir, "/upgrade-pending.json");
|
|
UpgradeV2Harness upgrader = new UpgradeV2Harness();
|
|
|
|
vm.expectRevert(
|
|
abi.encodeWithSelector(DemoScript.UnexpectedAddress.selector, "SCRIPT_SENDER", manifest.owner, ALICE)
|
|
);
|
|
upgrader.runWithPaths(path, pending, ALICE);
|
|
|
|
vm.writeJson(string.concat('"', vm.toString(manifest.token), '"'), path, ".implementation");
|
|
vm.expectRevert(
|
|
abi.encodeWithSelector(
|
|
DemoScript.UnexpectedAddress.selector, "implementation", manifest.token, manifest.implementation
|
|
)
|
|
);
|
|
upgrader.runWithPaths(path, pending, OWNER);
|
|
}
|
|
|
|
function testUpgradeV2RejectsUnsupportedChainMissingProxyCodeWrongOwnerAndUnexpectedVersion() public {
|
|
UpgradeV2Harness upgrader = new UpgradeV2Harness();
|
|
vm.chainId(1);
|
|
vm.expectRevert(abi.encodeWithSelector(DemoScript.UnsupportedChain.selector, uint256(1)));
|
|
upgrader.runWithPaths("unused", "unused", OWNER);
|
|
|
|
vm.chainId(31337);
|
|
(string memory missingPath,) = _deployUpgradeFixtureNamed("upgrade-missing-proxy");
|
|
vm.writeJson('"0x4000000000000000000000000000000000000004"', missingPath, ".proxy");
|
|
vm.expectRevert(
|
|
abi.encodeWithSelector(DemoScript.MissingCode.selector, "proxy", 0x4000000000000000000000000000000000000004)
|
|
);
|
|
upgrader.runWithPaths(missingPath, string.concat(fixtureDir, "/missing-pending.json"), OWNER);
|
|
|
|
(string memory ownerPath, DemoScript.Manifest memory ownerManifest) =
|
|
_deployUpgradeFixtureNamed("upgrade-owner");
|
|
vm.prank(OWNER);
|
|
BankV1(ownerManifest.proxy).transferOwnership(ALICE);
|
|
vm.expectRevert(abi.encodeWithSelector(DemoScript.UnexpectedAddress.selector, "owner", OWNER, ALICE));
|
|
upgrader.runWithPaths(ownerPath, string.concat(fixtureDir, "/owner-pending.json"), ALICE);
|
|
|
|
(string memory versionPath, DemoScript.Manifest memory versionManifest) =
|
|
_deployUpgradeFixtureNamed("upgrade-version");
|
|
vm.mockCall(
|
|
versionManifest.proxy, abi.encodeWithSelector(BankV1.contractVersion.selector), abi.encode(uint256(3))
|
|
);
|
|
vm.expectRevert(abi.encodeWithSelector(UpgradeV2.UnexpectedVersion.selector, uint256(3)));
|
|
upgrader.runWithPaths(versionPath, string.concat(fixtureDir, "/version-pending.json"), OWNER);
|
|
}
|
|
|
|
function testUpgradeV2PreservesCompleteSnapshotAndWritesOnlyStagingRecord() public {
|
|
(string memory path, DemoScript.Manifest memory manifest) = _deployUpgradeFixtureNamed("upgrade-preserve");
|
|
_seedState(manifest);
|
|
string memory beforeManifest = vm.readFile(path);
|
|
string memory pending = string.concat(fixtureDir, "/upgrade-pending.json");
|
|
|
|
(bool upgraded, address implementation) = new UpgradeV2Harness().runWithPaths(path, pending, OWNER);
|
|
|
|
assertTrue(upgraded);
|
|
assertNotEq(implementation, manifest.implementation);
|
|
assertEq(vm.readFile(path), beforeManifest);
|
|
BankV2 bankV2 = BankV2(manifest.proxy);
|
|
assertEq(bankV2.contractVersion(), 2);
|
|
assertEq(bankV2.owner(), manifest.owner);
|
|
assertEq(address(bankV2.asset()), manifest.token);
|
|
assertFalse(bankV2.paused());
|
|
assertEq(bankV2.balanceOf(ALICE), 900e6);
|
|
assertEq(bankV2.balanceOf(BOB), 500e6);
|
|
assertEq(bankV2.totalLiabilities(), 1_400e6);
|
|
assertEq(MockUSDC(manifest.token).balanceOf(manifest.proxy), 1_400e6);
|
|
string memory marker = vm.readFile(pending);
|
|
assertEq(vm.parseJsonString(marker, ".mode"), "upgrade");
|
|
assertEq(vm.parseJsonAddress(marker, ".implementation"), implementation);
|
|
assertEq(vm.parseJsonAddress(marker, ".snapshot.proxy"), manifest.proxy);
|
|
assertEq(vm.parseJsonUint(marker, ".snapshot.balances[1].balance"), 900e6);
|
|
assertEq(vm.parseJsonUint(marker, ".snapshot.balances[2].balance"), 500e6);
|
|
}
|
|
|
|
function testUpgradeV2AlreadyActiveWritesVerifiableNoopWithoutChangingImplementation() public {
|
|
(string memory path, DemoScript.Manifest memory manifest) = _deployUpgradeFixtureNamed("upgrade-noop");
|
|
string memory pending = string.concat(fixtureDir, "/upgrade-pending.json");
|
|
UpgradeV2Harness upgrader = new UpgradeV2Harness();
|
|
(, address implementation) = upgrader.runWithPaths(path, pending, OWNER);
|
|
vm.writeJson(string.concat('"', vm.toString(implementation), '"'), path, ".implementation");
|
|
uint256 nonceBefore = vm.getNonce(OWNER);
|
|
|
|
(bool upgraded, address observedImplementation) = upgrader.runWithPaths(path, pending, OWNER);
|
|
|
|
assertFalse(upgraded);
|
|
assertEq(observedImplementation, implementation);
|
|
assertEq(vm.getNonce(OWNER), nonceBefore);
|
|
string memory marker = vm.readFile(pending);
|
|
assertEq(vm.parseJsonString(marker, ".mode"), "noop");
|
|
assertEq(vm.parseJsonUint(marker, ".chainId"), 31337);
|
|
assertEq(vm.parseJsonUint(marker, ".observedBlock"), block.number);
|
|
assertEq(vm.parseJsonUint(marker, ".ownerNonce"), nonceBefore);
|
|
assertEq(vm.parseJsonAddress(marker, ".proxy"), manifest.proxy);
|
|
assertEq(vm.parseJsonAddress(marker, ".implementation"), implementation);
|
|
}
|
|
|
|
function testSnapshotComparisonRejectsApplicationMutationButAllowsImplementationAndVersionChange() public {
|
|
UpgradeV2Harness upgradeHarness = new UpgradeV2Harness();
|
|
UpgradeV2.Snapshot memory before_ = _snapshot();
|
|
UpgradeV2.Snapshot memory after_ = _snapshot();
|
|
after_.implementation = address(0x9999);
|
|
after_.version = 2;
|
|
upgradeHarness.assertSnapshotUnchanged(before_, after_);
|
|
|
|
for (uint256 mutation; mutation < 10; ++mutation) {
|
|
before_ = _snapshot();
|
|
after_ = _snapshot();
|
|
if (mutation == 0) after_.proxy = address(0x9999);
|
|
else if (mutation == 1) after_.owner = address(0x9999);
|
|
else if (mutation == 2) after_.asset = address(0x9999);
|
|
else if (mutation == 3) after_.paused = true;
|
|
else if (mutation == 4) after_.balances = new uint256[](2);
|
|
else if (mutation == 5) after_.balances[1] += 1;
|
|
else if (mutation == 6) after_.liabilities += 1;
|
|
else if (mutation == 7) after_.reserves += 1;
|
|
else if (mutation == 8) after_.surplus += 1;
|
|
else after_.deploymentBlock += 1;
|
|
vm.expectRevert();
|
|
upgradeHarness.assertSnapshotUnchanged(before_, after_);
|
|
}
|
|
}
|
|
|
|
function testTransferV2DemoExecutesExactActThreeAndCheckStateAcceptsBothV2Stages() public {
|
|
(string memory path, DemoScript.Manifest memory manifest) = _deployUpgradeFixtureNamed("transfer-v2");
|
|
_seedState(manifest);
|
|
string memory pending = string.concat(fixtureDir, "/upgrade-pending.json");
|
|
(, address implementation) = new UpgradeV2Harness().runWithPaths(path, pending, OWNER);
|
|
vm.writeJson(string.concat('"', vm.toString(implementation), '"'), path, ".implementation");
|
|
|
|
new CheckStateHarness().runWithPath(path, "upgraded");
|
|
new TransferV2DemoHarness().runWithPath(path);
|
|
new CheckStateHarness().runWithPath(path, "v2");
|
|
|
|
BankV2 bankV2 = BankV2(manifest.proxy);
|
|
assertEq(bankV2.balanceOf(ALICE), 650e6);
|
|
assertEq(bankV2.balanceOf(BOB), 750e6);
|
|
assertEq(bankV2.totalLiabilities(), 1_400e6);
|
|
assertEq(MockUSDC(manifest.token).balanceOf(manifest.proxy), 1_400e6);
|
|
}
|
|
|
|
function testBaseUpgradeAndTransferPreserveAccountingAndMoveExactBalance() public {
|
|
(string memory path, DemoScript.Manifest memory manifest) = _deployBaseFixtureNamed("transfer-base");
|
|
_setBaseConfig(OWNER, BOB, "https://public.invalid");
|
|
vm.setEnv("DEPLOYMENT_MANIFEST_PATH", path);
|
|
new SeedBaseSepolia().run();
|
|
|
|
string memory pending = string.concat(fixtureDir, "/base-upgrade-pending.json");
|
|
(, address implementation) = new UpgradeV2Harness().runWithPaths(path, pending, OWNER);
|
|
vm.writeJson(string.concat('"', vm.toString(implementation), '"'), path, ".implementation");
|
|
|
|
new TransferV2DemoHarness().runWithPath(path);
|
|
|
|
BankV2 bank = BankV2(manifest.proxy);
|
|
MockUSDC token = MockUSDC(manifest.token);
|
|
assertEq(bank.balanceOf(OWNER), 750e6);
|
|
assertEq(bank.balanceOf(BOB), 250e6);
|
|
assertEq(bank.totalLiabilities(), 1_000e6);
|
|
assertEq(token.balanceOf(manifest.proxy), 1_000e6);
|
|
new CheckStateHarness().runWithPath(path, "invariants");
|
|
}
|
|
|
|
function testBaseUpgradeRejectsMismatchedConfiguredSenderBeforeBroadcast() public {
|
|
(string memory path, DemoScript.Manifest memory manifest) = _deployBaseFixtureNamed("upgrade-base-mismatch");
|
|
_setBaseConfig(ALICE, BOB, "https://public.invalid");
|
|
string memory pending = string.concat(fixtureDir, "/base-mismatch-pending.json");
|
|
UpgradeV2Harness upgrader = new UpgradeV2Harness();
|
|
|
|
vm.expectRevert(
|
|
abi.encodeWithSelector(DemoScript.UnexpectedAddress.selector, "BASE_SEPOLIA_SENDER", OWNER, ALICE)
|
|
);
|
|
upgrader.runWithPaths(path, pending, OWNER);
|
|
|
|
assertEq(Upgrades.getImplementationAddress(manifest.proxy), manifest.implementation);
|
|
}
|
|
|
|
function testBaseCheckStateRejectsConfiguredActorMismatch() public {
|
|
(string memory path, DemoScript.Manifest memory manifest) = _deployBaseFixtureNamed("check-base-mismatch");
|
|
_setBaseConfig(OWNER, ALICE, "https://public.invalid");
|
|
CheckStateHarness checker = new CheckStateHarness();
|
|
|
|
vm.expectRevert(
|
|
abi.encodeWithSelector(
|
|
DemoScript.UnexpectedAddress.selector, "Recipient", manifest.actors[1].address_, ALICE
|
|
)
|
|
);
|
|
checker.runWithPath(path, "deployed");
|
|
}
|
|
|
|
function testBaseTransferRejectsInvalidRecipientBeforeBroadcast() public {
|
|
(string memory path, DemoScript.Manifest memory manifest) = _deployBaseFixtureNamed("transfer-base-invalid");
|
|
_setBaseConfig(OWNER, BOB, "https://public.invalid");
|
|
vm.setEnv("DEPLOYMENT_MANIFEST_PATH", path);
|
|
new SeedBaseSepolia().run();
|
|
string memory pending = string.concat(fixtureDir, "/base-transfer-upgrade.json");
|
|
(, address implementation) = new UpgradeV2Harness().runWithPaths(path, pending, OWNER);
|
|
vm.writeJson(string.concat('"', vm.toString(implementation), '"'), path, ".implementation");
|
|
_setBaseConfig(OWNER, OWNER, "https://public.invalid");
|
|
TransferV2DemoHarness transfer = new TransferV2DemoHarness();
|
|
|
|
vm.expectRevert(DemoScript.InvalidActorConfiguration.selector);
|
|
transfer.runWithPath(path);
|
|
|
|
BankV2 bank = BankV2(manifest.proxy);
|
|
assertEq(bank.balanceOf(OWNER), 1_000e6);
|
|
assertEq(bank.balanceOf(BOB), 0);
|
|
}
|
|
|
|
function _snapshot() internal pure returns (UpgradeV2.Snapshot memory snapshot) {
|
|
snapshot.proxy = PROXY;
|
|
snapshot.implementation = IMPLEMENTATION;
|
|
snapshot.owner = OWNER;
|
|
snapshot.asset = TOKEN;
|
|
snapshot.paused = false;
|
|
snapshot.balances = new uint256[](3);
|
|
snapshot.balances[0] = 10;
|
|
snapshot.balances[1] = 20;
|
|
snapshot.balances[2] = 30;
|
|
snapshot.liabilities = 60;
|
|
snapshot.reserves = 70;
|
|
snapshot.surplus = 10;
|
|
snapshot.deploymentBlock = 1;
|
|
snapshot.version = 1;
|
|
}
|
|
|
|
function _writeManifest(
|
|
uint256 chainId,
|
|
uint256 deploymentBlock,
|
|
address token,
|
|
address proxy,
|
|
address implementation
|
|
) internal returns (string memory path) {
|
|
path = string.concat(fixtureDir, "/manifest.json");
|
|
string memory json = string.concat(
|
|
'{"schemaVersion":1,"network":"anvil","chainId":',
|
|
vm.toString(chainId),
|
|
',"deploymentBlock":',
|
|
vm.toString(deploymentBlock),
|
|
',"rpcUrl":"http://127.0.0.1:8545","token":"',
|
|
vm.toString(token),
|
|
'","proxy":"',
|
|
vm.toString(proxy),
|
|
'","implementation":"',
|
|
vm.toString(implementation),
|
|
'","owner":"',
|
|
vm.toString(OWNER),
|
|
'","actors":[{"label":"owner","address":"',
|
|
vm.toString(OWNER),
|
|
'"},{"label":"Alice","address":"',
|
|
vm.toString(ALICE),
|
|
'"},{"label":"Bob","address":"0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC"}]}'
|
|
);
|
|
vm.writeFile(path, json);
|
|
}
|
|
|
|
function _deployFixture() internal returns (string memory path, DemoScript.Manifest memory manifest) {
|
|
return _deployFixtureNamed("deployed");
|
|
}
|
|
|
|
function _deployFixtureNamed(string memory name)
|
|
internal
|
|
returns (string memory path, DemoScript.Manifest memory manifest)
|
|
{
|
|
path = string.concat(fixtureDir, "/", name, ".json");
|
|
vm.chainId(31337);
|
|
vm.setEnv("SCRIPT_SENDER", vm.toString(OWNER));
|
|
vm.setEnv("DEPLOYMENT_MANIFEST_PATH", path);
|
|
new DeployV1().run();
|
|
vm.writeJson("1", path, ".deploymentBlock");
|
|
manifest = harness.readManifest(path, true);
|
|
}
|
|
|
|
function _deployUpgradeFixtureNamed(string memory name)
|
|
internal
|
|
returns (string memory path, DemoScript.Manifest memory manifest)
|
|
{
|
|
vm.chainId(31337);
|
|
MockUSDC deployedToken = new MockUSDC(OWNER);
|
|
address deployedProxy = Upgrades.deployUUPSProxy(
|
|
"BankV1.sol:BankV1", abi.encodeCall(BankV1.initialize, (address(deployedToken), OWNER))
|
|
);
|
|
manifest.schemaVersion = 1;
|
|
manifest.network = "anvil";
|
|
manifest.chainId = 31337;
|
|
manifest.deploymentBlock = 1;
|
|
manifest.rpcUrl = "http://127.0.0.1:8545";
|
|
manifest.token = address(deployedToken);
|
|
manifest.proxy = deployedProxy;
|
|
manifest.implementation = Upgrades.getImplementationAddress(deployedProxy);
|
|
manifest.owner = OWNER;
|
|
manifest.actors = _actors();
|
|
path = string.concat(fixtureDir, "/", name, ".json");
|
|
vm.writeFile(path, harness.serializeManifest(manifest));
|
|
}
|
|
|
|
function _deployBaseFixtureNamed(string memory name)
|
|
internal
|
|
returns (string memory path, DemoScript.Manifest memory manifest)
|
|
{
|
|
vm.chainId(84532);
|
|
MockUSDC deployedToken = new MockUSDC(OWNER);
|
|
address deployedProxy = Upgrades.deployUUPSProxy(
|
|
"BankV1.sol:BankV1", abi.encodeCall(BankV1.initialize, (address(deployedToken), OWNER))
|
|
);
|
|
manifest.schemaVersion = 1;
|
|
manifest.network = "baseSepolia";
|
|
manifest.chainId = 84532;
|
|
manifest.deploymentBlock = 1;
|
|
manifest.rpcUrl = "https://public.invalid";
|
|
manifest.explorerBaseUrl = "https://sepolia.basescan.org";
|
|
manifest.token = address(deployedToken);
|
|
manifest.proxy = deployedProxy;
|
|
manifest.implementation = Upgrades.getImplementationAddress(deployedProxy);
|
|
manifest.owner = OWNER;
|
|
manifest.actors = _baseActors();
|
|
path = string.concat(fixtureDir, "/", name, ".json");
|
|
vm.writeFile(path, harness.serializeManifest(manifest));
|
|
}
|
|
|
|
function _seedState(DemoScript.Manifest memory manifest) internal {
|
|
MockUSDC deployedToken = MockUSDC(manifest.token);
|
|
BankV1 deployedBank = BankV1(manifest.proxy);
|
|
vm.startPrank(OWNER);
|
|
deployedToken.mint(ALICE, 2_000e6);
|
|
deployedToken.mint(BOB, 1_000e6);
|
|
vm.stopPrank();
|
|
vm.startPrank(ALICE);
|
|
deployedToken.approve(manifest.proxy, 1_000e6);
|
|
deployedBank.deposit(1_000e6);
|
|
deployedBank.withdraw(100e6);
|
|
vm.stopPrank();
|
|
vm.startPrank(BOB);
|
|
deployedToken.approve(manifest.proxy, 500e6);
|
|
deployedBank.deposit(500e6);
|
|
vm.stopPrank();
|
|
}
|
|
|
|
function _writePublicAnvilManifest() internal returns (string memory path) {
|
|
path = string.concat(fixtureDir, "/public-anvil-manifest.json");
|
|
vm.writeFile(
|
|
path,
|
|
string.concat(
|
|
'{"schemaVersion":1,"network":"anvil","chainId":31337,"deploymentBlock":1,"rpcUrl":"http://127.0.0.1:8545","token":"',
|
|
vm.toString(TOKEN),
|
|
'","proxy":"',
|
|
vm.toString(PROXY),
|
|
'","implementation":"',
|
|
vm.toString(IMPLEMENTATION),
|
|
'","owner":"',
|
|
vm.toString(OWNER),
|
|
'","actors":[{"label":"owner","address":"',
|
|
vm.toString(OWNER),
|
|
'"},{"label":"Alice","address":"',
|
|
vm.toString(ALICE),
|
|
'"},{"label":"Bob","address":"0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC"}]}'
|
|
)
|
|
);
|
|
}
|
|
|
|
function _writeLegacyParallelActorManifest() internal returns (string memory path) {
|
|
path = string.concat(fixtureDir, "/legacy-parallel-actors.json");
|
|
vm.writeFile(
|
|
path,
|
|
string.concat(
|
|
'{"schemaVersion":1,"network":"anvil","chainId":31337,"deploymentBlock":1,"rpcUrl":"http://127.0.0.1:8545","token":"',
|
|
vm.toString(TOKEN),
|
|
'","proxy":"',
|
|
vm.toString(PROXY),
|
|
'","implementation":"',
|
|
vm.toString(IMPLEMENTATION),
|
|
'","owner":"',
|
|
vm.toString(OWNER),
|
|
'","actorLabels":["owner","Alice","Bob"],"actors":[{"label":"owner","address":"',
|
|
vm.toString(OWNER),
|
|
'"},{"label":"Alice","address":"',
|
|
vm.toString(ALICE),
|
|
'"},{"label":"Bob","address":"0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC"}]}'
|
|
)
|
|
);
|
|
}
|
|
|
|
function _writeAnvilManifestWithExtraActorKey(string memory key, string memory value)
|
|
internal
|
|
returns (string memory path)
|
|
{
|
|
path = string.concat(fixtureDir, "/extra-actor-key.json");
|
|
vm.writeFile(
|
|
path,
|
|
string.concat(
|
|
'{"schemaVersion":1,"network":"anvil","chainId":31337,"deploymentBlock":1,"rpcUrl":"http://127.0.0.1:8545","token":"',
|
|
vm.toString(TOKEN),
|
|
'","proxy":"',
|
|
vm.toString(PROXY),
|
|
'","implementation":"',
|
|
vm.toString(IMPLEMENTATION),
|
|
'","owner":"',
|
|
vm.toString(OWNER),
|
|
'","actors":[{"label":"owner","address":"',
|
|
vm.toString(OWNER),
|
|
'","',
|
|
key,
|
|
'":"',
|
|
value,
|
|
'"},{"label":"Alice","address":"',
|
|
vm.toString(ALICE),
|
|
'"},{"label":"Bob","address":"0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC"}]}'
|
|
)
|
|
);
|
|
}
|
|
|
|
function _writePublicBaseManifest() internal returns (string memory path) {
|
|
path = string.concat(fixtureDir, "/public-base-manifest.json");
|
|
vm.writeFile(
|
|
path,
|
|
string.concat(
|
|
'{"schemaVersion":1,"network":"baseSepolia","chainId":84532,"deploymentBlock":1,"rpcUrl":"https://public.invalid","explorerBaseUrl":"https://sepolia.basescan.org","token":"',
|
|
vm.toString(TOKEN),
|
|
'","proxy":"',
|
|
vm.toString(PROXY),
|
|
'","implementation":"',
|
|
vm.toString(IMPLEMENTATION),
|
|
'","owner":"',
|
|
vm.toString(OWNER),
|
|
'","actors":[{"label":"Presenter","address":"',
|
|
vm.toString(OWNER),
|
|
'"},{"label":"Recipient","address":"',
|
|
vm.toString(BOB),
|
|
'"}]}'
|
|
)
|
|
);
|
|
}
|
|
|
|
function _etchManifestContracts() internal {
|
|
vm.etch(TOKEN, hex"00");
|
|
vm.etch(PROXY, hex"00");
|
|
vm.etch(IMPLEMENTATION, hex"00");
|
|
}
|
|
|
|
function _actors() internal pure returns (DemoScript.Actor[] memory actors) {
|
|
actors = new DemoScript.Actor[](3);
|
|
actors[0] = DemoScript.Actor({label: "owner", address_: OWNER});
|
|
actors[1] = DemoScript.Actor({label: "Alice", address_: ALICE});
|
|
actors[2] = DemoScript.Actor({label: "Bob", address_: 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC});
|
|
}
|
|
|
|
function _baseActors() internal pure returns (DemoScript.Actor[] memory actors) {
|
|
actors = new DemoScript.Actor[](2);
|
|
actors[0] = DemoScript.Actor({label: "Presenter", address_: OWNER});
|
|
actors[1] = DemoScript.Actor({label: "Recipient", address_: BOB});
|
|
}
|
|
|
|
function _contains(string memory haystack, string memory needle) internal pure returns (bool) {
|
|
bytes memory h = bytes(haystack);
|
|
bytes memory n = bytes(needle);
|
|
if (n.length > h.length) return false;
|
|
for (uint256 i; i + n.length <= h.length; ++i) {
|
|
bool match_ = true;
|
|
for (uint256 j; j < n.length; ++j) {
|
|
if (h[i + j] != n[j]) {
|
|
match_ = false;
|
|
break;
|
|
}
|
|
}
|
|
if (match_) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function _setBaseConfig(address sender, address recipient, string memory publicRpcUrl) internal {
|
|
vm.setEnv("SCRIPT_SENDER", vm.toString(sender));
|
|
vm.setEnv("BASE_SEPOLIA_SENDER", vm.toString(sender));
|
|
vm.setEnv("BASE_SEPOLIA_RECIPIENT", vm.toString(recipient));
|
|
vm.setEnv("BASE_SEPOLIA_PUBLIC_RPC_URL", publicRpcUrl);
|
|
}
|
|
}
|