feat: script deterministic V1 demo state
This commit is contained in:
@@ -0,0 +1,278 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.35;
|
||||
|
||||
import {Test} from "forge-std/Test.sol";
|
||||
import {DemoScript} from "../script/lib/DemoScript.sol";
|
||||
import {DeployV1} from "../script/DeployV1.s.sol";
|
||||
import {SeedV1Demo} from "../script/SeedV1Demo.s.sol";
|
||||
import {CheckState} from "../script/CheckState.s.sol";
|
||||
import {BankV1} from "../src/BankV1.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 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);
|
||||
}
|
||||
}
|
||||
|
||||
contract ScriptPreflightTest is Test {
|
||||
ScriptPreflightHarness internal harness;
|
||||
string internal fixtureDir;
|
||||
|
||||
address internal constant OWNER = 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266;
|
||||
address internal constant ALICE = 0x70997970C51812dc3A010C7d01b50e0d17dc79C8;
|
||||
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[3] memory rejected = [uint256(1), uint256(8453), 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 testSerializedManifestContainsPublicAddressesAndNoSecrets() public {
|
||||
DemoScript.Manifest memory manifest = DemoScript.Manifest({
|
||||
schemaVersion: 1,
|
||||
network: "anvil",
|
||||
chainId: 31337,
|
||||
deploymentBlock: 0,
|
||||
rpcUrl: "http://127.0.0.1:8545",
|
||||
explorerUrl: "",
|
||||
token: TOKEN,
|
||||
proxy: PROXY,
|
||||
implementation: IMPLEMENTATION,
|
||||
owner: OWNER,
|
||||
actorLabels: _labels(),
|
||||
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.actorLabels[0], "owner");
|
||||
assertEq(manifest.actorLabels[1], "Alice");
|
||||
assertEq(manifest.actorLabels[2], "Bob");
|
||||
assertEq(manifest.actors[0], OWNER);
|
||||
assertEq(manifest.actors[1], ALICE);
|
||||
assertEq(manifest.actors[2], 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC);
|
||||
}
|
||||
|
||||
function testSeedV1DemoExecutesExactActOneStateAndCheckStateAcceptsIt() public {
|
||||
(string memory path, DemoScript.Manifest memory manifest) = _deployFixture();
|
||||
vm.setEnv("DEPLOYMENT_MANIFEST_PATH", path);
|
||||
|
||||
new SeedV1Demo().run();
|
||||
|
||||
BankV1 bank = BankV1(manifest.proxy);
|
||||
assertEq(bank.balanceOf(manifest.actors[1]), 900e6);
|
||||
assertEq(bank.balanceOf(manifest.actors[2]), 500e6);
|
||||
assertEq(bank.totalLiabilities(), 1_400e6);
|
||||
assertEq(MockUSDC(manifest.token).balanceOf(manifest.proxy), 1_400e6);
|
||||
assertEq(MockUSDC(manifest.token).balanceOf(manifest.actors[1]), 1_100e6);
|
||||
assertEq(MockUSDC(manifest.token).balanceOf(manifest.actors[2]), 500e6);
|
||||
|
||||
vm.setEnv("DEMO_EXPECTED_STAGE", "v1");
|
||||
new CheckState().run();
|
||||
}
|
||||
|
||||
function testCheckStateRejectsManifestImplementationMismatch() public {
|
||||
(string memory path, DemoScript.Manifest memory manifest) = _deployFixture();
|
||||
vm.writeJson(string.concat('"', vm.toString(manifest.token), '"'), path, ".implementation");
|
||||
vm.setEnv("DEPLOYMENT_MANIFEST_PATH", path);
|
||||
vm.setEnv("DEMO_EXPECTED_STAGE", "deployed");
|
||||
CheckState checker = new CheckState();
|
||||
|
||||
vm.expectRevert(
|
||||
abi.encodeWithSelector(
|
||||
DemoScript.UnexpectedAddress.selector, "implementation", manifest.token, manifest.implementation
|
||||
)
|
||||
);
|
||||
checker.run();
|
||||
}
|
||||
|
||||
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","explorerUrl":"","token":"',
|
||||
vm.toString(token),
|
||||
'","proxy":"',
|
||||
vm.toString(proxy),
|
||||
'","implementation":"',
|
||||
vm.toString(implementation),
|
||||
'","owner":"',
|
||||
vm.toString(OWNER),
|
||||
'","actorLabels":["owner","Alice","Bob"],"actors":["',
|
||||
vm.toString(OWNER),
|
||||
'","',
|
||||
vm.toString(ALICE),
|
||||
'","0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC"]}'
|
||||
);
|
||||
vm.writeFile(path, json);
|
||||
}
|
||||
|
||||
function _deployFixture() internal returns (string memory path, DemoScript.Manifest memory manifest) {
|
||||
path = string.concat(fixtureDir, "/deployed.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 _labels() internal pure returns (string[] memory labels) {
|
||||
labels = new string[](3);
|
||||
labels[0] = "owner";
|
||||
labels[1] = "Alice";
|
||||
labels[2] = "Bob";
|
||||
}
|
||||
|
||||
function _actors() internal pure returns (address[] memory actors) {
|
||||
actors = new address[](3);
|
||||
actors[0] = OWNER;
|
||||
actors[1] = ALICE;
|
||||
actors[2] = 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user