// SPDX-License-Identifier: MIT pragma solidity 0.8.35; import {Script} from "forge-std/Script.sol"; import {console2} from "forge-std/console2.sol"; import {BankV1} from "../../src/BankV1.sol"; import {MockUSDC} from "../../src/MockUSDC.sol"; abstract contract DemoScript is Script { uint256 internal constant ANVIL_CHAIN_ID = 31337; uint256 internal constant BASE_SEPOLIA_CHAIN_ID = 84532; string internal constant ANVIL_TEST_PHRASE = "test test test test test test test test test test test junk"; string internal constant PENDING_MANIFEST_PATH = "deployments/pending.json"; string internal constant ACTIVE_MANIFEST_PATH = "deployments/active.json"; string internal constant EDUCATIONAL_WARNING = unicode"Educational demo — mock token — never use real funds."; error UnsupportedChain(uint256 chainId); error ManifestChainMismatch(uint256 expected, uint256 actual); error MissingCode(string label, address target); error InvalidDeploymentBlock(); error InvalidManifestSchema(uint256 schemaVersion); error InvalidActorConfiguration(); error UnexpectedState(string label, uint256 expected, uint256 actual); error UnexpectedAddress(string label, address expected, address actual); struct Actor { string label; address address_; } struct Manifest { uint256 schemaVersion; string network; uint256 chainId; uint256 deploymentBlock; string rpcUrl; string explorerBaseUrl; address token; address proxy; address implementation; address owner; Actor[] actors; } function _requireSupportedChain(uint256 chainId) internal pure { if (chainId != ANVIL_CHAIN_ID && chainId != BASE_SEPOLIA_CHAIN_ID) revert UnsupportedChain(chainId); } function _deriveLocalActor(uint256 chainId, uint32 index) internal pure returns (uint256 privateKey, address actor) { if (chainId != ANVIL_CHAIN_ID) revert UnsupportedChain(chainId); privateKey = vm.deriveKey(ANVIL_TEST_PHRASE, index); actor = vm.addr(privateKey); } function _manifestPath(string memory defaultPath) internal view returns (string memory) { return vm.envOr("DEPLOYMENT_MANIFEST_PATH", defaultPath); } function _readManifest(string memory path, bool active) internal view returns (Manifest memory manifest) { string memory json = vm.readFile(path); _assertExactManifestSchema(json); manifest.schemaVersion = vm.parseJsonUint(json, ".schemaVersion"); manifest.network = vm.parseJsonString(json, ".network"); manifest.chainId = vm.parseJsonUint(json, ".chainId"); manifest.deploymentBlock = vm.parseJsonUint(json, ".deploymentBlock"); if (vm.keyExistsJson(json, ".rpcUrl")) manifest.rpcUrl = vm.parseJsonString(json, ".rpcUrl"); if (vm.keyExistsJson(json, ".explorerBaseUrl")) { manifest.explorerBaseUrl = vm.parseJsonString(json, ".explorerBaseUrl"); } manifest.token = vm.parseJsonAddress(json, ".token"); manifest.proxy = vm.parseJsonAddress(json, ".proxy"); manifest.implementation = vm.parseJsonAddress(json, ".implementation"); manifest.owner = vm.parseJsonAddress(json, ".owner"); if (manifest.schemaVersion != 1) revert InvalidManifestSchema(manifest.schemaVersion); if (manifest.chainId != block.chainid) revert ManifestChainMismatch(block.chainid, manifest.chainId); if (active && manifest.deploymentBlock == 0) revert InvalidDeploymentBlock(); manifest.actors = _parseActors(json, manifest.chainId); _validateActorConfiguration(manifest); _requireCode("token", manifest.token); _requireCode("proxy", manifest.proxy); _requireCode("implementation", manifest.implementation); } function _requireCode(string memory label, address target) internal view { if (target == address(0) || target.code.length == 0) revert MissingCode(label, target); } function _assertExactManifestSchema(string memory json) private view { string[] memory keys = vm.parseJsonKeys(json, "."); if (keys.length < 9 || keys.length > 11) revert InvalidManifestSchema(0); bool[9] memory required; for (uint256 i; i < keys.length; ++i) { string memory key = keys[i]; if (_equals(key, "schemaVersion")) required[0] = true; else if (_equals(key, "network")) required[1] = true; else if (_equals(key, "chainId")) required[2] = true; else if (_equals(key, "deploymentBlock")) required[3] = true; else if (_equals(key, "token")) required[4] = true; else if (_equals(key, "proxy")) required[5] = true; else if (_equals(key, "implementation")) required[6] = true; else if (_equals(key, "owner")) required[7] = true; else if (_equals(key, "actors")) required[8] = true; else if (!_equals(key, "rpcUrl") && !_equals(key, "explorerBaseUrl")) revert InvalidManifestSchema(0); } for (uint256 i; i < required.length; ++i) { if (!required[i]) revert InvalidManifestSchema(0); } } function _parseActors(string memory json, uint256 chainId) private view returns (Actor[] memory actors) { uint256 actorCount = chainId == ANVIL_CHAIN_ID ? 3 : 1; actors = new Actor[](actorCount); for (uint256 i; i < actorCount; ++i) { string memory index = vm.toString(i); actors[i].label = vm.parseJsonString(json, string.concat(".actors[", index, "].label")); actors[i].address_ = vm.parseJsonAddress(json, string.concat(".actors[", index, "].address")); } if (vm.keyExistsJson(json, string.concat(".actors[", vm.toString(actorCount), "]"))) { revert InvalidActorConfiguration(); } } function _validateActorConfiguration(Manifest memory manifest) internal pure { if (manifest.chainId == ANVIL_CHAIN_ID) { if ( manifest.actors.length != 3 || !_equals(manifest.network, "anvil") || !_equals(manifest.actors[0].label, "owner") || !_equals(manifest.actors[1].label, "Alice") || !_equals(manifest.actors[2].label, "Bob") ) revert InvalidActorConfiguration(); (, address owner) = _deriveLocalActor(ANVIL_CHAIN_ID, 0); (, address alice) = _deriveLocalActor(ANVIL_CHAIN_ID, 1); (, address bob) = _deriveLocalActor(ANVIL_CHAIN_ID, 2); if ( manifest.actors[0].address_ != manifest.owner || manifest.actors[0].address_ != owner || manifest.actors[1].address_ != alice || manifest.actors[2].address_ != bob ) revert InvalidActorConfiguration(); return; } if ( manifest.chainId != BASE_SEPOLIA_CHAIN_ID || manifest.actors.length != 1 || !_equals(manifest.network, "baseSepolia") || !_equals(manifest.actors[0].label, "owner") || manifest.actors[0].address_ != manifest.owner ) revert InvalidActorConfiguration(); } function _educationalWarning() internal pure returns (string memory) { return EDUCATIONAL_WARNING; } function _printEducationalWarning() internal pure { console2.log(EDUCATIONAL_WARNING); } function _equals(string memory left, string memory right) private pure returns (bool) { return keccak256(bytes(left)) == keccak256(bytes(right)); } function _serializeManifest(Manifest memory manifest) internal view returns (string memory json) { json = string.concat( '{"schemaVersion":', vm.toString(manifest.schemaVersion), ',"network":"', manifest.network, '","chainId":', vm.toString(manifest.chainId), ',"deploymentBlock":', vm.toString(manifest.deploymentBlock) ); if (bytes(manifest.rpcUrl).length != 0) json = string.concat(json, ',"rpcUrl":"', manifest.rpcUrl, '"'); if (bytes(manifest.explorerBaseUrl).length != 0) { json = string.concat(json, ',"explorerBaseUrl":"', manifest.explorerBaseUrl, '"'); } json = string.concat( json, ',"token":"', vm.toString(manifest.token), '","proxy":"', vm.toString(manifest.proxy), '","implementation":"', vm.toString(manifest.implementation), '","owner":"', vm.toString(manifest.owner), '","actors":', _serializeActors(manifest.actors), "}" ); } function _serializeActors(Actor[] memory actors) private view returns (string memory json) { json = "["; for (uint256 i; i < actors.length; ++i) { if (i != 0) json = string.concat(json, ","); json = string.concat( json, '{"label":"', actors[i].label, '","address":"', vm.toString(actors[i].address_), '"}' ); } json = string.concat(json, "]"); } function _writeManifest(string memory path, Manifest memory manifest) internal { vm.writeJson(_serializeManifest(manifest), path); } function _assertDeployedState(Manifest memory manifest) internal view { BankV1 bank = BankV1(manifest.proxy); _assertAddress("owner", manifest.owner, bank.owner()); _assertAddress("asset", manifest.token, address(bank.asset())); _assertUint("version", 1, bank.contractVersion()); _assertUint("liabilities", 0, bank.totalLiabilities()); _assertUint("reserves", 0, MockUSDC(manifest.token).balanceOf(manifest.proxy)); } function _assertV1State(Manifest memory manifest) internal view { BankV1 bank = BankV1(manifest.proxy); _assertUint("Alice internal balance", 900e6, bank.balanceOf(manifest.actors[1].address_)); _assertUint("Bob internal balance", 500e6, bank.balanceOf(manifest.actors[2].address_)); _assertUint("liabilities", 1_400e6, bank.totalLiabilities()); _assertUint("reserves", 1_400e6, MockUSDC(manifest.token).balanceOf(manifest.proxy)); _assertUint("version", 1, bank.contractVersion()); } function _assertUint(string memory label, uint256 expected, uint256 actual) internal pure { if (actual != expected) revert UnexpectedState(label, expected, actual); } function _assertAddress(string memory label, address expected, address actual) internal pure { if (actual != expected) revert UnexpectedAddress(label, expected, actual); } }