feat: add guarded Base Sepolia encore

This commit is contained in:
golem
2026-08-21 16:01:35 -06:00
parent 94f2ad6e09
commit 90b0a11b8a
16 changed files with 784 additions and 25 deletions
+220 -8
View File
@@ -6,6 +6,7 @@ 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";
@@ -88,7 +89,8 @@ contract ScriptPreflightTest is Test {
}
function testUnsupportedChainsAreRejectedBeforeBroadcast() public {
uint256[3] memory rejected = [uint256(1), uint256(8453), uint256(7777777)];
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]);
@@ -210,7 +212,7 @@ contract ScriptPreflightTest is Test {
harness.readManifest(path, true);
}
function testBaseManifestRequiresOnlyOwnerActor() public {
function testBaseManifestRequiresExactPresenterRecipientActors() public {
string memory path = _writePublicBaseManifest();
vm.chainId(84532);
_etchManifestContracts();
@@ -270,16 +272,18 @@ contract ScriptPreflightTest is Test {
harness.readManifest(path, true);
}
function testBaseSepoliaManifestUsesCamelCaseAndOmitsUnavailableUrls() public {
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, "");
assertEq(manifest.explorerBaseUrl, "");
assertEq(manifest.actors[0].label, "owner");
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 {
@@ -332,6 +336,76 @@ contract ScriptPreflightTest is Test {
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);
@@ -350,6 +424,38 @@ contract ScriptPreflightTest is Test {
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");
@@ -506,6 +612,73 @@ contract ScriptPreflightTest is Test {
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;
@@ -593,6 +766,30 @@ contract ScriptPreflightTest is Test {
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);
@@ -689,7 +886,7 @@ contract ScriptPreflightTest is Test {
vm.writeFile(
path,
string.concat(
'{"schemaVersion":1,"network":"baseSepolia","chainId":84532,"deploymentBlock":1,"token":"',
'{"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),
@@ -697,8 +894,10 @@ contract ScriptPreflightTest is Test {
vm.toString(IMPLEMENTATION),
'","owner":"',
vm.toString(OWNER),
'","actors":[{"label":"owner","address":"',
'","actors":[{"label":"Presenter","address":"',
vm.toString(OWNER),
'"},{"label":"Recipient","address":"',
vm.toString(BOB),
'"}]}'
)
);
@@ -717,6 +916,12 @@ contract ScriptPreflightTest is Test {
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);
@@ -733,4 +938,11 @@ contract ScriptPreflightTest is Test {
}
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);
}
}