// 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 Manifest { uint256 schemaVersion; string network; uint256 chainId; uint256 deploymentBlock; string rpcUrl; string explorerUrl; address token; address proxy; address implementation; address owner; string[] actorLabels; address[] 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); manifest.schemaVersion = vm.parseJsonUint(json, ".schemaVersion"); manifest.network = vm.parseJsonString(json, ".network"); manifest.chainId = vm.parseJsonUint(json, ".chainId"); manifest.deploymentBlock = vm.parseJsonUint(json, ".deploymentBlock"); manifest.rpcUrl = vm.parseJsonString(json, ".rpcUrl"); manifest.explorerUrl = vm.parseJsonString(json, ".explorerUrl"); manifest.token = vm.parseJsonAddress(json, ".token"); manifest.proxy = vm.parseJsonAddress(json, ".proxy"); manifest.implementation = vm.parseJsonAddress(json, ".implementation"); manifest.owner = vm.parseJsonAddress(json, ".owner"); manifest.actorLabels = vm.parseJsonStringArray(json, ".actorLabels"); manifest.actors = vm.parseJsonAddressArray(json, ".actors"); 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(); _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 _validateActorConfiguration(Manifest memory manifest) internal pure { if (manifest.actorLabels.length != manifest.actors.length) revert InvalidActorConfiguration(); if (manifest.chainId == ANVIL_CHAIN_ID) { if ( manifest.actorLabels.length != 3 || !_equals(manifest.network, "anvil") || !_equals(manifest.actorLabels[0], "owner") || !_equals(manifest.actorLabels[1], "Alice") || !_equals(manifest.actorLabels[2], "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] != manifest.owner || manifest.actors[0] != owner || manifest.actors[1] != alice || manifest.actors[2] != bob ) revert InvalidActorConfiguration(); return; } if ( manifest.chainId != BASE_SEPOLIA_CHAIN_ID || manifest.actorLabels.length != 1 || !_equals(manifest.network, "base-sepolia") || !_equals(manifest.actorLabels[0], "owner") || manifest.actors[0] != 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 returns (string memory json) { string memory objectKey = "demo-manifest"; vm.serializeUint(objectKey, "schemaVersion", manifest.schemaVersion); vm.serializeString(objectKey, "network", manifest.network); vm.serializeUint(objectKey, "chainId", manifest.chainId); vm.serializeUint(objectKey, "deploymentBlock", manifest.deploymentBlock); vm.serializeString(objectKey, "rpcUrl", manifest.rpcUrl); vm.serializeString(objectKey, "explorerUrl", manifest.explorerUrl); vm.serializeAddress(objectKey, "token", manifest.token); vm.serializeAddress(objectKey, "proxy", manifest.proxy); vm.serializeAddress(objectKey, "implementation", manifest.implementation); vm.serializeAddress(objectKey, "owner", manifest.owner); vm.serializeString(objectKey, "actorLabels", manifest.actorLabels); json = vm.serializeAddress(objectKey, "actors", manifest.actors); } 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])); _assertUint("Bob internal balance", 500e6, bank.balanceOf(manifest.actors[2])); _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); } }