Files
uupl-smart-contract/script/lib/DemoScript.sol
T

250 lines
11 KiB
Solidity

// 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);
_assertExactActorSchema(json, index);
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 _assertExactActorSchema(string memory json, string memory index) private view {
string[] memory keys = vm.parseJsonKeys(json, string.concat(".actors[", index, "]"));
bool hasLabel;
bool hasAddress;
if (keys.length != 2) revert InvalidManifestSchema(0);
for (uint256 i; i < keys.length; ++i) {
if (_equals(keys[i], "label")) hasLabel = true;
else if (_equals(keys[i], "address")) hasAddress = true;
else revert InvalidManifestSchema(0);
}
if (!hasLabel || !hasAddress) revert InvalidManifestSchema(0);
}
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);
}
}