feat: script deterministic V1 demo state
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.35;
|
||||
|
||||
import {console2} from "forge-std/console2.sol";
|
||||
import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";
|
||||
import {BankV1} from "../src/BankV1.sol";
|
||||
import {MockUSDC} from "../src/MockUSDC.sol";
|
||||
import {DemoScript} from "./lib/DemoScript.sol";
|
||||
|
||||
contract CheckState is DemoScript {
|
||||
error Insolvent(uint256 reserves, uint256 liabilities);
|
||||
error UnknownStage(string stage);
|
||||
|
||||
function run() external view {
|
||||
_requireSupportedChain(block.chainid);
|
||||
string memory stage = vm.envOr("DEMO_EXPECTED_STAGE", string("v1"));
|
||||
bool deployedStage = keccak256(bytes(stage)) == keccak256("deployed");
|
||||
Manifest memory manifest = _readManifest(_manifestPath(ACTIVE_MANIFEST_PATH), !deployedStage);
|
||||
BankV1 bank = BankV1(manifest.proxy);
|
||||
MockUSDC token = MockUSDC(manifest.token);
|
||||
|
||||
address actualImplementation = Upgrades.getImplementationAddress(manifest.proxy);
|
||||
_assertAddress("implementation", manifest.implementation, actualImplementation);
|
||||
_assertAddress("owner", manifest.owner, bank.owner());
|
||||
_assertAddress("asset", manifest.token, address(bank.asset()));
|
||||
|
||||
uint256 reserves = token.balanceOf(manifest.proxy);
|
||||
uint256 liabilities = bank.totalLiabilities();
|
||||
if (reserves < liabilities) revert Insolvent(reserves, liabilities);
|
||||
uint256 surplus = reserves - liabilities;
|
||||
|
||||
console2.log("network", manifest.network);
|
||||
console2.log("block", block.number);
|
||||
console2.log("token", manifest.token);
|
||||
console2.log("proxy", manifest.proxy);
|
||||
console2.log("implementation", actualImplementation);
|
||||
console2.log("owner", bank.owner());
|
||||
console2.log("paused", bank.paused());
|
||||
console2.log("version", bank.contractVersion());
|
||||
for (uint256 i; i < manifest.actors.length; ++i) {
|
||||
console2.log(manifest.actorLabels[i], manifest.actors[i]);
|
||||
console2.log(" bank balance", bank.balanceOf(manifest.actors[i]));
|
||||
console2.log(" token balance", token.balanceOf(manifest.actors[i]));
|
||||
}
|
||||
console2.log("reserves", reserves);
|
||||
console2.log("liabilities", liabilities);
|
||||
console2.log("surplus", surplus);
|
||||
|
||||
bytes32 stageHash = keccak256(bytes(stage));
|
||||
if (deployedStage) {
|
||||
_assertDeployedState(manifest);
|
||||
} else if (stageHash == keccak256("v1")) {
|
||||
_assertV1State(manifest);
|
||||
_assertUint("surplus", 0, surplus);
|
||||
} else if (stageHash != keccak256("invariants")) {
|
||||
revert UnknownStage(stage);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.35;
|
||||
|
||||
import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";
|
||||
import {BankV1} from "../src/BankV1.sol";
|
||||
import {MockUSDC} from "../src/MockUSDC.sol";
|
||||
import {DemoScript} from "./lib/DemoScript.sol";
|
||||
|
||||
contract DeployV1 is DemoScript {
|
||||
function run() external returns (address tokenAddress, address proxy, address implementation) {
|
||||
_requireSupportedChain(block.chainid);
|
||||
address sender = vm.envAddress("SCRIPT_SENDER");
|
||||
|
||||
if (block.chainid == ANVIL_CHAIN_ID) {
|
||||
(uint256 ownerKey, address derivedOwner) = _deriveLocalActor(block.chainid, 0);
|
||||
_assertAddress("SCRIPT_SENDER", derivedOwner, sender);
|
||||
vm.startBroadcast(ownerKey);
|
||||
} else {
|
||||
vm.startBroadcast(sender);
|
||||
}
|
||||
|
||||
MockUSDC token = new MockUSDC(sender);
|
||||
proxy =
|
||||
Upgrades.deployUUPSProxy("BankV1.sol:BankV1", abi.encodeCall(BankV1.initialize, (address(token), sender)));
|
||||
vm.stopBroadcast();
|
||||
|
||||
tokenAddress = address(token);
|
||||
implementation = Upgrades.getImplementationAddress(proxy);
|
||||
_requireCode("token", tokenAddress);
|
||||
_requireCode("proxy", proxy);
|
||||
_requireCode("implementation", implementation);
|
||||
_assertAddress("owner", sender, BankV1(proxy).owner());
|
||||
_assertAddress("asset", tokenAddress, address(BankV1(proxy).asset()));
|
||||
_assertUint("version", 1, BankV1(proxy).contractVersion());
|
||||
_assertAddress("implementation", implementation, Upgrades.getImplementationAddress(proxy));
|
||||
|
||||
Manifest memory manifest;
|
||||
manifest.schemaVersion = 1;
|
||||
manifest.network = block.chainid == ANVIL_CHAIN_ID ? "anvil" : "base-sepolia";
|
||||
manifest.chainId = block.chainid;
|
||||
manifest.deploymentBlock = 0;
|
||||
manifest.rpcUrl = block.chainid == ANVIL_CHAIN_ID ? "http://127.0.0.1:8545" : "https://sepolia.base.org";
|
||||
manifest.explorerUrl = block.chainid == ANVIL_CHAIN_ID ? "" : "https://sepolia.basescan.org";
|
||||
manifest.token = tokenAddress;
|
||||
manifest.proxy = proxy;
|
||||
manifest.implementation = implementation;
|
||||
manifest.owner = sender;
|
||||
(manifest.actorLabels, manifest.actors) = _actors(sender);
|
||||
|
||||
_writeManifest(_manifestPath(PENDING_MANIFEST_PATH), manifest);
|
||||
}
|
||||
|
||||
function _actors(address sender) private returns (string[] memory labels, address[] memory actors) {
|
||||
if (block.chainid == ANVIL_CHAIN_ID) {
|
||||
labels = new string[](3);
|
||||
actors = new address[](3);
|
||||
labels[0] = "owner";
|
||||
labels[1] = "Alice";
|
||||
labels[2] = "Bob";
|
||||
actors[0] = sender;
|
||||
(, actors[1]) = _deriveLocalActor(block.chainid, 1);
|
||||
(, actors[2]) = _deriveLocalActor(block.chainid, 2);
|
||||
} else {
|
||||
labels = new string[](1);
|
||||
actors = new address[](1);
|
||||
labels[0] = "owner";
|
||||
actors[0] = sender;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.35;
|
||||
|
||||
import {BankV1} from "../src/BankV1.sol";
|
||||
import {MockUSDC} from "../src/MockUSDC.sol";
|
||||
import {DemoScript} from "./lib/DemoScript.sol";
|
||||
|
||||
contract SeedV1Demo is DemoScript {
|
||||
function run() external {
|
||||
if (block.chainid != ANVIL_CHAIN_ID) revert UnsupportedChain(block.chainid);
|
||||
Manifest memory manifest = _readManifest(_manifestPath(ACTIVE_MANIFEST_PATH), true);
|
||||
if (manifest.actors.length != 3 || manifest.actorLabels.length != 3) revert InvalidManifestSchema(1);
|
||||
|
||||
(uint256 ownerKey, address owner) = _deriveLocalActor(block.chainid, 0);
|
||||
(uint256 aliceKey, address alice) = _deriveLocalActor(block.chainid, 1);
|
||||
(uint256 bobKey, address bob) = _deriveLocalActor(block.chainid, 2);
|
||||
_assertAddress("owner actor", manifest.owner, owner);
|
||||
_assertAddress("Alice actor", manifest.actors[1], alice);
|
||||
_assertAddress("Bob actor", manifest.actors[2], bob);
|
||||
|
||||
MockUSDC token = MockUSDC(manifest.token);
|
||||
BankV1 bank = BankV1(manifest.proxy);
|
||||
|
||||
vm.startBroadcast(ownerKey);
|
||||
token.mint(alice, 2_000e6);
|
||||
token.mint(bob, 1_000e6);
|
||||
vm.stopBroadcast();
|
||||
|
||||
vm.startBroadcast(aliceKey);
|
||||
token.approve(manifest.proxy, 1_000e6);
|
||||
bank.deposit(1_000e6);
|
||||
vm.stopBroadcast();
|
||||
|
||||
vm.startBroadcast(bobKey);
|
||||
token.approve(manifest.proxy, 500e6);
|
||||
bank.deposit(500e6);
|
||||
vm.stopBroadcast();
|
||||
|
||||
vm.startBroadcast(aliceKey);
|
||||
bank.withdraw(100e6);
|
||||
vm.stopBroadcast();
|
||||
|
||||
_assertV1State(manifest);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
pragma solidity 0.8.35;
|
||||
|
||||
import {Script} from "forge-std/Script.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";
|
||||
|
||||
error UnsupportedChain(uint256 chainId);
|
||||
error ManifestChainMismatch(uint256 expected, uint256 actual);
|
||||
error MissingCode(string label, address target);
|
||||
error InvalidDeploymentBlock();
|
||||
error InvalidManifestSchema(uint256 schemaVersion);
|
||||
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 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();
|
||||
_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 _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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user