From 4336ddb159a50e12a355ea094a46786f8a483569 Mon Sep 17 00:00:00 2001 From: Pote <81638931+willpote@users.noreply.github.com> Date: Wed, 15 Oct 2025 19:50:03 -0500 Subject: [PATCH 1/4] Additional supply calculator functions --- foundry.lock | 17 ++ script/BaseDeployment.s.sol | 8 +- script/Config.s.sol | 16 +- script/Deploy.s.sol | 44 ++-- script/Update.s.sol | 194 ++++++++++++++++-- script/manage | 48 +++-- src/calculators/SupplyCalculator.sol | 166 +++++++++++++++ src/circulating/CirculatingZKC.sol | 76 ------- .../SupplyCalculator.t.sol} | 118 ++++++++--- update_deployment_toml.py | 20 +- 10 files changed, 533 insertions(+), 174 deletions(-) create mode 100644 foundry.lock create mode 100644 src/calculators/SupplyCalculator.sol delete mode 100644 src/circulating/CirculatingZKC.sol rename test/{circulating/CirculatingZKC.t.sol => calculators/SupplyCalculator.t.sol} (50%) diff --git a/foundry.lock b/foundry.lock new file mode 100644 index 0000000..edb1427 --- /dev/null +++ b/foundry.lock @@ -0,0 +1,17 @@ +{ + "lib/forge-std": { + "rev": "77041d2ce690e692d6e03cc812b57d1ddaa4d505" + }, + "lib/openzeppelin-contracts": { + "rev": "9393147db73ae6261b67cb03003370e9a7fa2448" + }, + "lib/openzeppelin-contracts-upgradeable": { + "rev": "a2c43a5aed2ac0153340c93ed452915b650d1921" + }, + "lib/openzeppelin-foundry-upgrades": { + "rev": "cfd861bc18ef4737e82eae6ec75304e27af699ef" + }, + "lib/prb-math": { + "rev": "dae52a48422f7464e7eeb4eea824f44dde7dfabe" + } +} \ No newline at end of file diff --git a/script/BaseDeployment.s.sol b/script/BaseDeployment.s.sol index 5af6601..5b2ad87 100644 --- a/script/BaseDeployment.s.sol +++ b/script/BaseDeployment.s.sol @@ -132,10 +132,10 @@ abstract contract BaseDeployment is Script { } /** - * @notice Updates the CirculatingZKC contract commit hash in deployment.toml via FFI + * @notice Updates the SupplyCalculator contract commit hash in deployment.toml via FFI * @param deploymentKey The chain key (e.g., "anvil", "ethereum-mainnet") */ - function _updateCirculatingZKCCommit(string memory deploymentKey) internal { + function _updateSupplyCalculatorCommit(string memory deploymentKey) internal { string[] memory args = new string[](4); args[0] = "git"; args[1] = "rev-parse"; @@ -145,13 +145,13 @@ abstract contract BaseDeployment is Script { bytes memory result = vm.ffi(args); string memory commit = string(result); - // Update deployment.toml with CirculatingZKC commit + // Update deployment.toml with SupplyCalculator commit string[] memory updateArgs = new string[](6); updateArgs[0] = "python3"; updateArgs[1] = "update_deployment_toml.py"; updateArgs[2] = "--chain-key"; updateArgs[3] = deploymentKey; - updateArgs[4] = "--circulating-zkc-commit"; + updateArgs[4] = "--supply-calculator-commit"; updateArgs[5] = commit; vm.ffi(updateArgs); diff --git a/script/Config.s.sol b/script/Config.s.sol index f9635ab..d2c43a0 100644 --- a/script/Config.s.sol +++ b/script/Config.s.sol @@ -28,13 +28,14 @@ struct DeploymentConfig { address stakingRewardsDeployer; address povwMinter; address stakingMinter; - address circulatingZKC; - address circulatingZKCImpl; - address circulatingZKCAdmin; + address supplyCalculator; + address supplyCalculatorImpl; + address supplyCalculatorAdmin; + address supplyCalculatorAdmin2; string zkcCommit; string veZKCCommit; string stakingRewardsCommit; - string circulatingZKCCommit; + string supplyCalculatorCommit; } library ConfigLoader { @@ -76,9 +77,10 @@ library ConfigLoader { _readAddressOrZero(vm, toml, string.concat(keyPrefix, ".staking-rewards-deployer")); config.povwMinter = _readAddressOrZero(vm, toml, string.concat(keyPrefix, ".povw-minter")); config.stakingMinter = _readAddressOrZero(vm, toml, string.concat(keyPrefix, ".staking-minter")); - config.circulatingZKC = _readAddressOrZero(vm, toml, string.concat(keyPrefix, ".circulating-zkc")); - config.circulatingZKCImpl = _readAddressOrZero(vm, toml, string.concat(keyPrefix, ".circulating-zkc-impl")); - config.circulatingZKCAdmin = _readAddressOrZero(vm, toml, string.concat(keyPrefix, ".circulating-zkc-admin")); + config.supplyCalculator = _readAddressOrZero(vm, toml, string.concat(keyPrefix, ".supply-calculator")); + config.supplyCalculatorImpl = _readAddressOrZero(vm, toml, string.concat(keyPrefix, ".supply-calculator-impl")); + config.supplyCalculatorAdmin = _readAddressOrZero(vm, toml, string.concat(keyPrefix, ".supply-calculator-admin")); + config.supplyCalculatorAdmin2 = _readAddressOrZero(vm, toml, string.concat(keyPrefix, ".supply-calculator-admin-2")); // Read per-contract deployment commits, default to empty string if not found string memory zkcCommitKey = string.concat(keyPrefix, ".zkc-commit"); diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index 64bca9a..60a4c7d 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -5,7 +5,7 @@ import {console2} from "forge-std/Script.sol"; import {ZKC} from "../src/ZKC.sol"; import {veZKC} from "../src/veZKC.sol"; import {StakingRewards} from "../src/rewards/StakingRewards.sol"; -import {CirculatingZKC} from "../src/circulating/CirculatingZKC.sol"; +import {SupplyCalculator} from "../src/calculators/SupplyCalculator.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol"; import {ConfigLoader, DeploymentConfig} from "./Config.s.sol"; @@ -213,18 +213,18 @@ contract DeployStakingRewards is BaseDeployment { } /** - * Sample Usage for CirculatingZKC deployment: + * Sample Usage for SupplyCalculator deployment: * * export CHAIN_KEY="anvil" * export INITIAL_UNLOCKED="500000000000000000000000000" # 500M tokens * export SALT="0x0000000000000000000000000000000000000000000000000000000000000001" * - * forge script script/Deploy.s.sol:DeployCirculatingZKC \ + * forge script script/Deploy.s.sol:DeploySupplyCalculator \ * --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ * --broadcast \ * --rpc-url http://127.0.0.1:8545 */ -contract DeployCirculatingZKC is BaseDeployment { +contract DeploySupplyCalculator is BaseDeployment { function setUp() public {} function run() public { @@ -241,35 +241,35 @@ contract DeployCirculatingZKC is BaseDeployment { bytes32 salt = vm.envOr("SALT", bytes32(0)); - // Deploy CirculatingZKC implementation - address circulatingZKCImpl = address(new CirculatingZKC{salt: salt}()); - console2.log("Deployed CirculatingZKC implementation to: ", circulatingZKCImpl); + // Deploy SupplyCalculator implementation + address supplyCalculatorImpl = address(new SupplyCalculator{salt: salt}()); + console2.log("Deployed SupplyCalculator implementation to: ", supplyCalculatorImpl); // Deploy proxy with initialization ERC1967Proxy proxy = new ERC1967Proxy{salt: salt}( - circulatingZKCImpl, abi.encodeCall(CirculatingZKC.initialize, (config.zkc, initialUnlockedRaw, admin)) + supplyCalculatorImpl, abi.encodeCall(SupplyCalculator.initialize, (config.zkc, initialUnlockedRaw, admin)) ); - address circulatingZKCAddress = address(proxy); + address supplyCalculatorAddress = address(proxy); vm.stopBroadcast(); // Update deployment.toml - _updateDeploymentConfig(deploymentKey, "circulating-zkc", circulatingZKCAddress); - _updateDeploymentConfig(deploymentKey, "circulating-zkc-impl", circulatingZKCImpl); - _updateDeploymentConfig(deploymentKey, "circulating-zkc-admin", admin); - _updateCirculatingZKCCommit(deploymentKey); + _updateDeploymentConfig(deploymentKey, "supply-calculator", supplyCalculatorAddress); + _updateDeploymentConfig(deploymentKey, "supply-calculator-impl", supplyCalculatorImpl); + _updateDeploymentConfig(deploymentKey, "supply-calculator-admin", admin); + _updateSupplyCalculatorCommit(deploymentKey); // Sanity checks - CirculatingZKC circulatingContract = CirculatingZKC(circulatingZKCAddress); - IAccessControl accessControl = IAccessControl(circulatingZKCAddress); + SupplyCalculator supplyCalculator = SupplyCalculator(supplyCalculatorAddress); + IAccessControl accessControl = IAccessControl(supplyCalculatorAddress); console2.log("Admin address: ", admin); - console2.log("Admin role assigned: ", accessControl.hasRole(circulatingContract.ADMIN_ROLE(), admin)); - console2.log("ZKC token address: ", address(circulatingContract.zkc())); - console2.log("Initial unlocked amount: ", circulatingContract.unlocked()); - console2.log("Initial unlocked amount (in tokens): ", circulatingContract.unlocked() / 10 ** 18); - console2.log("Current circulating supply: ", circulatingContract.circulatingSupply()); - console2.log("Current circulating supply (in tokens): ", circulatingContract.circulatingSupply() / 10 ** 18); + console2.log("Admin role assigned: ", accessControl.hasRole(supplyCalculator.ADMIN_ROLE(), admin)); + console2.log("ZKC token address: ", address(supplyCalculator.zkc())); + console2.log("Initial unlocked amount: ", supplyCalculator.unlocked()); + console2.log("Initial unlocked amount (in tokens): ", supplyCalculator.unlocked() / 10 ** 18); + console2.log("Current circulating supply: ", supplyCalculator.circulatingSupply()); + console2.log("Current circulating supply (in tokens): ", supplyCalculator.circulatingSupply() / 10 ** 18); console2.log("================================================"); - console2.log("Deployed CirculatingZKC to: ", circulatingZKCAddress); + console2.log("Deployed SupplyCalculator to: ", supplyCalculatorAddress); } } diff --git a/script/Update.s.sol b/script/Update.s.sol index 533c347..09dd140 100644 --- a/script/Update.s.sol +++ b/script/Update.s.sol @@ -6,7 +6,7 @@ import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol" import {ConfigLoader, DeploymentConfig} from "./Config.s.sol"; import {BaseDeployment} from "./BaseDeployment.s.sol"; import {ZKC} from "../src/ZKC.sol"; -import {CirculatingZKC} from "../src/circulating/CirculatingZKC.sol"; +import {SupplyCalculator} from "../src/calculators/SupplyCalculator.sol"; import {veZKC} from "../src/veZKC.sol"; import {StakingRewards} from "../src/rewards/StakingRewards.sol"; @@ -197,13 +197,13 @@ contract UpdateStakingMinter is BaseDeployment { } /** - * Sample Usage for updating CirculatingZKC unlocked value: + * Sample Usage for updating SupplyCalculator unlocked value: * * # Direct execution: * export CHAIN_KEY="anvil" * export NEW_UNLOCKED="750000000000000000000000000" # 750M tokens * - * forge script script/Update.s.sol:UpdateCirculatingUnlocked \ + * forge script script/Update.s.sol:UpdateSupplyCalculatorUnlocked \ * --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ * --broadcast \ * --rpc-url http://127.0.0.1:8545 @@ -213,16 +213,16 @@ contract UpdateStakingMinter is BaseDeployment { * export NEW_UNLOCKED="750000000000000000000000000" # 750M tokens * export GNOSIS_EXECUTE=true * - * forge script script/Update.s.sol:UpdateCirculatingUnlocked \ + * forge script script/Update.s.sol:UpdateSupplyCalculatorUnlocked \ * --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ * --rpc-url http://127.0.0.1:8545 */ -contract UpdateCirculatingUnlocked is BaseDeployment { +contract UpdateSupplyCalculatorUnlocked is BaseDeployment { function setUp() public {} function run() public { (DeploymentConfig memory config, string memory deploymentKey) = ConfigLoader.loadDeploymentConfig(vm); - require(config.circulatingZKC != address(0), "CirculatingZKC address not set in deployment.toml"); + require(config.supplyCalculator != address(0), "SupplyCalculator address not set in deployment.toml"); // Get new unlocked value from environment uint256 newUnlocked = vm.envUint("NEW_UNLOCKED"); @@ -232,11 +232,11 @@ contract UpdateCirculatingUnlocked is BaseDeployment { bool gnosisExecute = vm.envOr("GNOSIS_EXECUTE", false); // Get the contract instance - CirculatingZKC circulatingContract = CirculatingZKC(config.circulatingZKC); + SupplyCalculator supplyCalculator = SupplyCalculator(config.supplyCalculator); // Get current values for logging - uint256 currentUnlocked = circulatingContract.unlocked(); - uint256 currentCirculatingSupply = circulatingContract.circulatingSupply(); + uint256 currentUnlocked = supplyCalculator.unlocked(); + uint256 currentCirculatingSupply = supplyCalculator.circulatingSupply(); console2.log("================================================"); console2.log("Current unlocked amount: ", currentUnlocked); @@ -247,7 +247,7 @@ contract UpdateCirculatingUnlocked is BaseDeployment { if (gnosisExecute) { // Print Gnosis Safe transaction info for manual execution - _printGnosisSafeInfo(config.circulatingZKC, newUnlocked); + _printGnosisSafeInfo(config.supplyCalculator, newUnlocked); // Calculate expected new circulating supply for display uint256 expectedNewCirculatingSupply = currentCirculatingSupply - currentUnlocked + newUnlocked; @@ -262,11 +262,11 @@ contract UpdateCirculatingUnlocked is BaseDeployment { vm.startBroadcast(); // Update the unlocked value - circulatingContract.updateUnlockedValue(newUnlocked); + supplyCalculator.updateUnlockedValue(newUnlocked); // Get updated values - uint256 updatedUnlocked = circulatingContract.unlocked(); - uint256 updatedCirculatingSupply = circulatingContract.circulatingSupply(); + uint256 updatedUnlocked = supplyCalculator.unlocked(); + uint256 updatedCirculatingSupply = supplyCalculator.circulatingSupply(); vm.stopBroadcast(); @@ -287,7 +287,7 @@ contract UpdateCirculatingUnlocked is BaseDeployment { } /// @notice Print Gnosis Safe transaction information for manual updates - /// @param targetAddress The CirculatingZKC contract address (target for Gnosis Safe) + /// @param targetAddress The SupplyCalculator contract address (target for Gnosis Safe) /// @param newUnlocked The new unlocked value to set function _printGnosisSafeInfo(address targetAddress, uint256 newUnlocked) internal pure { console2.log("================================"); @@ -912,6 +912,172 @@ contract RemoveStakingRewardsAdmin is BaseDeployment { } } +/** + * Sample Usage for adding admin to SupplyCalculator: + * + * export CHAIN_KEY="anvil" + * export ADMIN_TO_ADD="0x70997970C51812dc3A010C7d01b50e0d17dc79C8" + * + * forge script script/Update.s.sol:AddSupplyCalculatorAdmin \ + * --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ + * --broadcast \ + * --rpc-url http://127.0.0.1:8545 + */ +contract AddSupplyCalculatorAdmin is BaseDeployment { + function setUp() public {} + + function run() public { + (DeploymentConfig memory config, string memory deploymentKey) = ConfigLoader.loadDeploymentConfig(vm); + require(config.supplyCalculator != address(0), "SupplyCalculator address not set in deployment.toml"); + + address adminToAdd = vm.envAddress("ADMIN_TO_ADD"); + require(adminToAdd != address(0), "ADMIN_TO_ADD environment variable not set"); + + bool gnosisExecute = vm.envOr("GNOSIS_EXECUTE", false); + SupplyCalculator supplyCalculatorContract = SupplyCalculator(config.supplyCalculator); + bytes32 adminRole = supplyCalculatorContract.ADMIN_ROLE(); + + if (gnosisExecute) { + console2.log("GNOSIS_EXECUTE=true: Preparing grantRole calldata for Safe execution"); + console2.log("SupplyCalculator Contract: ", config.supplyCalculator); + console2.log("Admin to Add: ", adminToAdd); + console2.log("Role: ADMIN_ROLE"); + + // Print Gnosis Safe transaction info for grantRole + bytes memory grantRoleCallData = + abi.encodeWithSignature("grantRole(bytes32,address)", adminRole, adminToAdd); + console2.log("================================"); + console2.log("=== GNOSIS SAFE GRANT ROLE INFO ==="); + console2.log("Target Address (To): ", config.supplyCalculator); + console2.log("Function: grantRole(bytes32,address)"); + console2.log("Role: "); + console2.logBytes32(adminRole); + console2.log("Account: ", adminToAdd); + console2.log("Calldata:"); + console2.logBytes(grantRoleCallData); + console2.log("====================================="); + console2.log("SupplyCalculator Admin Grant Role Calldata Ready"); + console2.log("Transaction NOT executed - use Gnosis Safe to execute"); + } else { + vm.startBroadcast(); + + IAccessControl accessControl = IAccessControl(config.supplyCalculator); + + // Check if caller has admin role + require( + accessControl.hasRole(supplyCalculatorContract.ADMIN_ROLE(), msg.sender), "Caller must have ADMIN_ROLE" + ); + + // Grant ADMIN_ROLE + accessControl.grantRole(adminRole, adminToAdd); + + vm.stopBroadcast(); + + // Sanity checks + console2.log("SupplyCalculator Contract: ", config.supplyCalculator); + console2.log("New SupplyCalculator Admin: ", adminToAdd); + console2.log("ADMIN_ROLE granted: ", accessControl.hasRole(adminRole, adminToAdd)); + console2.log("================================================"); + console2.log("SupplyCalculator Admin Role Updated Successfully"); + } + + // Update deployment.toml with the new admin (always do this) + if (config.supplyCalculatorAdmin2 == address(0) || config.supplyCalculatorAdmin2 == adminToAdd) { + _updateDeploymentConfig(deploymentKey, "supply-calculator-admin-2", adminToAdd); + } else if (config.supplyCalculatorAdmin == address(0) || config.supplyCalculatorAdmin == adminToAdd) { + _updateDeploymentConfig(deploymentKey, "supply-calculator-admin", adminToAdd); + } else { + revert("supply-calculator-admin-2 and supply-calculator-admin are both set already"); + } + } +} + +/** + * Sample Usage for removing admin from SupplyCalculator: + * + * export CHAIN_KEY="anvil" + * export ADMIN_TO_REMOVE="0x70997970C51812dc3A010C7d01b50e0d17dc79C8" + * + * forge script script/Update.s.sol:RemoveSupplyCalculatorAdmin \ + * --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ + * --broadcast \ + * --rpc-url http://127.0.0.1:8545 + */ +contract RemoveSupplyCalculatorAdmin is BaseDeployment { + function setUp() public {} + + function run() public { + (DeploymentConfig memory config, string memory deploymentKey) = ConfigLoader.loadDeploymentConfig(vm); + require(config.supplyCalculator != address(0), "SupplyCalculator address not set in deployment.toml"); + + address adminToRemove = vm.envAddress("ADMIN_TO_REMOVE"); + require(adminToRemove != address(0), "ADMIN_TO_REMOVE environment variable not set"); + + bool gnosisExecute = vm.envOr("GNOSIS_EXECUTE", false); + SupplyCalculator supplyCalculatorContract = SupplyCalculator(config.supplyCalculator); + bytes32 adminRole = supplyCalculatorContract.ADMIN_ROLE(); + + // Safety check: Ensure at least one other admin will remain + IAccessControl accessControl = IAccessControl(config.supplyCalculator); + + address otherAdmin = (config.supplyCalculatorAdmin != address(0) && config.supplyCalculatorAdmin != adminToRemove) + ? config.supplyCalculatorAdmin + : config.supplyCalculatorAdmin2; + require( + otherAdmin != adminToRemove && otherAdmin != address(0) + && accessControl.hasRole(supplyCalculatorContract.ADMIN_ROLE(), otherAdmin), + "Cannot remove admin: would leave SupplyCalculator without any admins" + ); + + if (gnosisExecute) { + console2.log("GNOSIS_EXECUTE=true: Preparing revokeRole calldata for Safe execution"); + console2.log("SupplyCalculator Contract: ", config.supplyCalculator); + console2.log("Admin to Remove: ", adminToRemove); + console2.log("Other Admin still active: ", otherAdmin); + console2.log("Role: ADMIN_ROLE"); + + // Print Gnosis Safe transaction info for revokeRole + bytes memory revokeRoleCallData = + abi.encodeWithSignature("revokeRole(bytes32,address)", adminRole, adminToRemove); + console2.log("================================"); + console2.log("=== GNOSIS SAFE REVOKE ROLE INFO ==="); + console2.log("Target Address (To): ", config.supplyCalculator); + console2.log("Function: revokeRole(bytes32,address)"); + console2.log("Role: "); + console2.logBytes32(adminRole); + console2.log("Account: ", adminToRemove); + console2.log("Calldata:"); + console2.logBytes(revokeRoleCallData); + console2.log("====================================="); + console2.log("SupplyCalculator Admin Revoke Role Calldata Ready"); + console2.log("Transaction NOT executed - use Gnosis Safe to execute"); + } else { + vm.startBroadcast(); + + // Check if caller has admin role + require( + accessControl.hasRole(supplyCalculatorContract.ADMIN_ROLE(), msg.sender), "Caller must have ADMIN_ROLE" + ); + + // Revoke ADMIN_ROLE + accessControl.revokeRole(adminRole, adminToRemove); + + vm.stopBroadcast(); + + // Sanity checks + console2.log("SupplyCalculator Contract: ", config.supplyCalculator); + console2.log("Removed SupplyCalculator Admin: ", adminToRemove); + console2.log("Other Admin still active: ", otherAdmin); + console2.log("ADMIN_ROLE revoked: ", !accessControl.hasRole(adminRole, adminToRemove)); + console2.log("================================================"); + console2.log("SupplyCalculator Admin Role Removed Successfully"); + } + + // Remove from deployment.toml - check both admin fields + _removeAdminFromToml(deploymentKey, adminToRemove, "supply-calculator-admin", "supply-calculator-admin-2"); + } +} + /** * Sample Usage for adding admin to all contracts: * diff --git a/script/manage b/script/manage index 68dde9b..d626af3 100755 --- a/script/manage +++ b/script/manage @@ -257,7 +257,7 @@ Commands: deploy-zkc Deploy ZKC token contract deploy-vezkc Deploy veZKC staking contract deploy-staking Deploy StakingRewards contract - deploy-circulating Deploy CirculatingZKC contract + deploy-supply-calculator Deploy SupplyCalculator contract upgrade-zkc Upgrade ZKC implementation (no initializer) upgrade-zkc-initv2 Upgrade ZKC implementation and call initializeV2 zkc-start-epochs Call initializeV3 to start epochs (no upgrade) @@ -268,15 +268,17 @@ Commands: rollback-staking Rollback StakingRewards to previous implementation update-povw-minter Set POVW_MINTER_ROLE in ZKC contract update-staking-minter Set STAKING_MINTER_ROLE in ZKC contract - remove-povw-minter Revoke POVW_MINTER_ROLE from address in ZKC contract - update-circulating Update unlocked value in CirculatingZKC contract - add-zkc-admin Add admin to ZKC contract + remove-povw-minter Revoke POVW_MINTER_ROLE from address in ZKC contract + update-supply-calculator Update unlocked value in SupplyCalculator contract + add-zkc-admin Add admin to ZKC contract remove-zkc-admin Remove admin from ZKC contract add-vezkc-admin Add admin to veZKC contract remove-vezkc-admin Remove admin from veZKC contract - add-staking-admin Add admin to StakingRewards contract - remove-staking-admin Remove admin from StakingRewards contract - add-admin-all Add admin to all contracts (ZKC, veZKC, StakingRewards) + add-staking-admin Add admin to StakingRewards contract + remove-staking-admin Remove admin from StakingRewards contract + add-supply-calculator-admin Add admin to SupplyCalculator contract + remove-supply-calculator-admin Remove admin from SupplyCalculator contract + add-admin-all Add admin to all contracts (ZKC, veZKC, StakingRewards) remove-admin-all Remove admin from all contracts (ZKC, veZKC, StakingRewards) dev-mint-to-self Development: mint initial tokens to caller @@ -316,8 +318,8 @@ Examples: # Deploy StakingRewards to anvil (requires ZKC and veZKC already deployed) PRIVATE_KEY=0x... CHAIN_KEY=anvil ./script/manage deploy-staking --broadcast - # Deploy CirculatingZKC to anvil (requires ZKC already deployed) - PRIVATE_KEY=0x... INITIAL_UNLOCKED="500000000000000000000000000" CHAIN_KEY=anvil ./script/manage deploy-circulating --broadcast + # Deploy SupplyCalculator to anvil (requires ZKC already deployed) + PRIVATE_KEY=0x... INITIAL_UNLOCKED="500000000000000000000000000" CHAIN_KEY=anvil ./script/manage deploy-supply-calculator --broadcast # Upgrade ZKC on mainnet with verification (no initializer) PRIVATE_KEY=0x... CHAIN_KEY=ethereum-mainnet ./script/manage upgrade-zkc --broadcast --verify @@ -340,8 +342,8 @@ Examples: # Remove POVW minter role (requires POVW_MINTER env var) PRIVATE_KEY=0x... POVW_MINTER=0x123... CHAIN_KEY=anvil ./script/manage remove-povw-minter --broadcast - # Update CirculatingZKC unlocked value (requires NEW_UNLOCKED env var) - PRIVATE_KEY=0x... NEW_UNLOCKED="750000000000000000000000000" CHAIN_KEY=anvil ./script/manage update-circulating --broadcast + # Update SupplyCalculator unlocked value (requires NEW_UNLOCKED env var) + PRIVATE_KEY=0x... NEW_UNLOCKED="750000000000000000000000000" CHAIN_KEY=anvil ./script/manage update-supply-calculator --broadcast # Add admin to ZKC contract PRIVATE_KEY=0x... ADMIN_TO_ADD=0x123... CHAIN_KEY=anvil ./script/manage add-zkc-admin --broadcast @@ -351,6 +353,12 @@ Examples: # Add admin to all contracts at once PRIVATE_KEY=0x... ADMIN_TO_ADD=0x123... CHAIN_KEY=anvil ./script/manage add-admin-all --broadcast + + # Add admin to SupplyCalculator contract + PRIVATE_KEY=0x... ADMIN_TO_ADD=0x123... CHAIN_KEY=anvil ./script/manage add-supply-calculator-admin --broadcast + + # Remove admin from SupplyCalculator contract + PRIVATE_KEY=0x... ADMIN_TO_REMOVE=0x123... CHAIN_KEY=anvil ./script/manage remove-supply-calculator-admin --broadcast # Development: mint initial tokens to self (requires MINT_AMOUNT env var) PRIVATE_KEY=0x... MINT_AMOUNT=1000000000000000000000000 CHAIN_KEY=anvil ./script/manage dev-mint-to-self --broadcast @@ -377,8 +385,8 @@ Gnosis Safe Examples (GNOSIS_EXECUTE=true): # Generate admin management for all contracts at once GNOSIS_EXECUTE=true ADMIN_TO_ADD=0x123... CHAIN_KEY=ethereum-mainnet ./script/manage add-admin-all - # Generate CirculatingZKC update calldata for Gnosis Safe - GNOSIS_EXECUTE=true NEW_UNLOCKED="750000000000000000000000000" CHAIN_KEY=ethereum-mainnet ./script/manage update-circulating + # Generate SupplyCalculator update calldata for Gnosis Safe + GNOSIS_EXECUTE=true NEW_UNLOCKED="750000000000000000000000000" CHAIN_KEY=ethereum-mainnet ./script/manage update-supply-calculator # Generate role revoke calldata for Gnosis Safe GNOSIS_EXECUTE=true POVW_MINTER=0x123... CHAIN_KEY=ethereum-mainnet ./script/manage remove-povw-minter @@ -527,8 +535,8 @@ main() { deploy-staking) forge_script "DeployStakingRewards" "${SCRIPT_ARGS[@]+"${SCRIPT_ARGS[@]}"}" ;; - deploy-circulating) - forge_script "DeployCirculatingZKC" "${SCRIPT_ARGS[@]+"${SCRIPT_ARGS[@]}"}" + deploy-supply-calculator) + forge_script "DeploySupplyCalculator" "${SCRIPT_ARGS[@]+"${SCRIPT_ARGS[@]}"}" ;; upgrade-zkc) forge_script "UpgradeZKC" "${SCRIPT_ARGS[@]+"${SCRIPT_ARGS[@]}"}" @@ -563,8 +571,8 @@ main() { remove-povw-minter) forge_script "RemovePOVWMinter" "${SCRIPT_ARGS[@]+"${SCRIPT_ARGS[@]}"}" ;; - update-circulating) - forge_script "UpdateCirculatingUnlocked" "${SCRIPT_ARGS[@]+"${SCRIPT_ARGS[@]}"}" + update-supply-calculator) + forge_script "UpdateSupplyCalculatorUnlocked" "${SCRIPT_ARGS[@]+"${SCRIPT_ARGS[@]}"}" ;; add-zkc-admin) forge_script "AddZKCAdmin" "${SCRIPT_ARGS[@]+"${SCRIPT_ARGS[@]}"}" @@ -584,6 +592,12 @@ main() { remove-staking-admin) forge_script "RemoveStakingRewardsAdmin" "${SCRIPT_ARGS[@]+"${SCRIPT_ARGS[@]}"}" ;; + add-supply-calculator-admin) + forge_script "AddSupplyCalculatorAdmin" "${SCRIPT_ARGS[@]+"${SCRIPT_ARGS[@]}"}" + ;; + remove-supply-calculator-admin) + forge_script "RemoveSupplyCalculatorAdmin" "${SCRIPT_ARGS[@]+"${SCRIPT_ARGS[@]}"}" + ;; add-admin-all) forge_script "AddAdminAll" "${SCRIPT_ARGS[@]+"${SCRIPT_ARGS[@]}"}" ;; diff --git a/src/calculators/SupplyCalculator.sol b/src/calculators/SupplyCalculator.sol new file mode 100644 index 0000000..6664c7f --- /dev/null +++ b/src/calculators/SupplyCalculator.sol @@ -0,0 +1,166 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.26; + +import {AccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; +import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import {IZKC} from "../interfaces/IZKC.sol"; +import {Supply} from "../libraries/Supply.sol"; + +/// @title Supply Calculator for ZKC tokens +/// @notice Contract computes various supply metrics of ZKC tokens, intended to be used by frontend apps/exchanges +contract SupplyCalculator is Initializable, AccessControlUpgradeable, UUPSUpgradeable { + /// @custom:oz-upgrades-unsafe-allow state-variable-immutable + /// @notice Admin role identifier + bytes32 public immutable ADMIN_ROLE = DEFAULT_ADMIN_ROLE; + + /// @notice Reference to the ZKC token contract + IZKC public zkc; + + /// @notice Amount of tokens considered unlocked and in circulation + uint256 public unlocked; + + /// @notice Emitted when the unlocked value is updated + /// @param oldValue The previous unlocked value + /// @param newValue The new unlocked value + event UnlockedValueUpdated(uint256 oldValue, uint256 newValue); + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + + /// @notice Initialize the SupplyCalculator contract + /// @param _zkc Address of the ZKC token contract + /// @param _initialUnlocked Initial value for the unlocked tokens + /// @param _admin Address that will be granted the admin role + function initialize(address _zkc, uint256 _initialUnlocked, address _admin) public initializer { + require(_zkc != address(0), "ZKC address cannot be zero"); + require(_admin != address(0), "Admin address cannot be zero"); + + __AccessControl_init(); + __UUPSUpgradeable_init(); + + zkc = IZKC(_zkc); + unlocked = _initialUnlocked; + _grantRole(ADMIN_ROLE, _admin); + } + + /// @notice Calculate the current circulating supply + /// @dev Formula: unlocked + (zkc.claimedTotalSupply() - zkc.INITIAL_SUPPLY()) + /// @return The current circulating supply of ZKC tokens + function circulatingSupply() public view returns (uint256) { + uint256 claimedTotal = zkc.claimedTotalSupply(); + uint256 initialSupply = Supply.INITIAL_SUPPLY; + + // Calculate the claimed rewards from PoVW and staking rewards + uint256 claimedRewards = claimedTotal - initialSupply; + + return unlocked + claimedRewards; + } + + /// @notice Calculate the current circulating supply rounded to the nearest whole token (18dp representation) + /// @dev Returns value in wei (18 decimals) but rounded such that when converted to whole tokens it's rounded + /// @return The current circulating supply rounded to nearest whole token in 18dp format + function circulatingSupplyRounded() public view returns (uint256) { + uint256 supply = circulatingSupply(); + return _roundTo18dp(supply); + } + + /// @notice Calculate the current circulating supply as a rounded whole number + /// @dev Returns the value in regular representation (divided by 10^18) rounded to nearest whole token + /// @return The current circulating supply as a whole number + function circulatingSupplyAmountRounded() public view returns (uint256) { + uint256 supply = circulatingSupply(); + return _roundToWholeTokens(supply); + } + + function totalSupply() public view returns (uint256) { + return IERC20(address(zkc)).totalSupply(); + } + + /// @notice Get the total supply rounded to the nearest whole token (18dp representation) + /// @dev Returns value in wei (18 decimals) but rounded such that when converted to whole tokens it's rounded + /// @dev Uses the theoretical total supply based on current epoch + /// @return The total supply rounded to nearest whole token in 18dp format + function totalSupplyRounded() public view returns (uint256) { + uint256 supply = IERC20(address(zkc)).totalSupply(); + return _roundTo18dp(supply); + } + + /// @notice Get the total supply as a rounded whole number + /// @dev Returns the value in regular representation (divided by 10^18) rounded to nearest whole token + /// @dev Uses the theoretical total supply based on current epoch + /// @return The total supply as a whole number + function totalSupplyAmountRounded() public view returns (uint256) { + uint256 supply = IERC20(address(zkc)).totalSupply(); + return _roundToWholeTokens(supply); + } + + function totalClaimedSupply() public view returns (uint256) { + return zkc.claimedTotalSupply(); + } + + /// @notice Get the claimed total supply rounded to the nearest whole token (18dp representation) + /// @dev Returns value in wei (18 decimals) but rounded such that when converted to whole tokens it's rounded + /// @dev Uses the actual claimed/minted supply + /// @return The claimed total supply rounded to nearest whole token in 18dp format + function totalClaimedSupplyRounded() public view returns (uint256) { + uint256 supply = zkc.claimedTotalSupply(); + return _roundTo18dp(supply); + } + + /// @notice Get the claimed total supply as a rounded whole number + /// @dev Returns the value in regular representation (divided by 10^18) rounded to nearest whole token + /// @dev Uses the actual claimed/minted supply + /// @return The claimed total supply as a whole number + function totalClaimedSupplyAmountRounded() public view returns (uint256) { + uint256 supply = zkc.claimedTotalSupply(); + return _roundToWholeTokens(supply); + } + + /// @notice Round a value to the nearest whole token (18dp representation) + /// @dev Rounds to nearest 1e18, so when divided by 1e18 it gives a whole number + /// @param value The value to round + /// @return The rounded value in 18dp format + function _roundTo18dp(uint256 value) private pure returns (uint256) { + uint256 remainder = value % 1e18; + if (remainder >= 5e17) { + // Round up + return value - remainder + 1e18; + } else { + // Round down + return value - remainder; + } + } + + /// @notice Round a value to the nearest whole token and return as a whole number + /// @dev Divides by 1e18 with rounding + /// @param value The value to round + /// @return The rounded value as a whole number + function _roundToWholeTokens(uint256 value) private pure returns (uint256) { + uint256 remainder = value % 1e18; + if (remainder >= 5e17) { + // Round up + return (value / 1e18) + 1; + } else { + // Round down + return value / 1e18; + } + } + + /// @notice Update the unlocked value + /// @dev Only callable by accounts with ADMIN_ROLE + /// @param _newUnlocked The new value for unlocked tokens + function updateUnlockedValue(uint256 _newUnlocked) external onlyRole(ADMIN_ROLE) { + uint256 oldValue = unlocked; + unlocked = _newUnlocked; + emit UnlockedValueUpdated(oldValue, _newUnlocked); + } + + /// @notice Authorize contract upgrades (UUPS pattern) + /// @dev Only accounts with ADMIN_ROLE can authorize upgrades + /// @param newImplementation Address of the new implementation contract + function _authorizeUpgrade(address newImplementation) internal override onlyRole(ADMIN_ROLE) {} +} diff --git a/src/circulating/CirculatingZKC.sol b/src/circulating/CirculatingZKC.sol deleted file mode 100644 index 4a4b7d1..0000000 --- a/src/circulating/CirculatingZKC.sol +++ /dev/null @@ -1,76 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.26; - -import {AccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; -import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; -import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; -import {IZKC} from "../interfaces/IZKC.sol"; -import {Supply} from "../libraries/Supply.sol"; - -/// @title CirculatingZKC - Circulating Supply Calculator for ZKC -/// @notice Contract to track and calculate the circulating supply of ZKC tokens -/// @dev Uses an admin-controlled unlocked value plus claimed tokens to calculate circulating supply -contract CirculatingZKC is Initializable, AccessControlUpgradeable, UUPSUpgradeable { - /// @custom:oz-upgrades-unsafe-allow state-variable-immutable - /// @notice Admin role identifier - bytes32 public immutable ADMIN_ROLE = DEFAULT_ADMIN_ROLE; - - /// @notice Reference to the ZKC token contract - IZKC public zkc; - - /// @notice Amount of tokens considered unlocked and in circulation - uint256 public unlocked; - - /// @notice Emitted when the unlocked value is updated - /// @param oldValue The previous unlocked value - /// @param newValue The new unlocked value - event UnlockedValueUpdated(uint256 oldValue, uint256 newValue); - - /// @custom:oz-upgrades-unsafe-allow constructor - constructor() { - _disableInitializers(); - } - - /// @notice Initialize the CirculatingZKC contract - /// @param _zkc Address of the ZKC token contract - /// @param _initialUnlocked Initial value for the unlocked tokens - /// @param _admin Address that will be granted the admin role - function initialize(address _zkc, uint256 _initialUnlocked, address _admin) public initializer { - require(_zkc != address(0), "ZKC address cannot be zero"); - require(_admin != address(0), "Admin address cannot be zero"); - - __AccessControl_init(); - __UUPSUpgradeable_init(); - - zkc = IZKC(_zkc); - unlocked = _initialUnlocked; - _grantRole(ADMIN_ROLE, _admin); - } - - /// @notice Calculate the current circulating supply - /// @dev Formula: unlocked + (zkc.claimedTotalSupply() - zkc.INITIAL_SUPPLY()) - /// @return The current circulating supply of ZKC tokens - function circulatingSupply() public view returns (uint256) { - uint256 claimedTotal = zkc.claimedTotalSupply(); - uint256 initialSupply = Supply.INITIAL_SUPPLY; - - // Calculate the claimed rewards from PoVW and staking rewards - uint256 claimedRewards = claimedTotal - initialSupply; - - return unlocked + claimedRewards; - } - - /// @notice Update the unlocked value - /// @dev Only callable by accounts with ADMIN_ROLE - /// @param _newUnlocked The new value for unlocked tokens - function updateUnlockedValue(uint256 _newUnlocked) external onlyRole(ADMIN_ROLE) { - uint256 oldValue = unlocked; - unlocked = _newUnlocked; - emit UnlockedValueUpdated(oldValue, _newUnlocked); - } - - /// @notice Authorize contract upgrades (UUPS pattern) - /// @dev Only accounts with ADMIN_ROLE can authorize upgrades - /// @param newImplementation Address of the new implementation contract - function _authorizeUpgrade(address newImplementation) internal override onlyRole(ADMIN_ROLE) {} -} diff --git a/test/circulating/CirculatingZKC.t.sol b/test/calculators/SupplyCalculator.t.sol similarity index 50% rename from test/circulating/CirculatingZKC.t.sol rename to test/calculators/SupplyCalculator.t.sol index 70a664c..334f5a9 100644 --- a/test/circulating/CirculatingZKC.t.sol +++ b/test/calculators/SupplyCalculator.t.sol @@ -2,12 +2,12 @@ pragma solidity 0.8.26; import "forge-std/Test.sol"; -import "../../src/circulating/CirculatingZKC.sol"; +import {SupplyCalculator} from "../../src/calculators/SupplyCalculator.sol"; import "../../src/ZKC.sol"; import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; -contract CirculatingZKCTest is Test { - CirculatingZKC public circulatingZKC; +contract SupplyCalculatorTest is Test { + SupplyCalculator public supplyCalculator; ZKC public zkc; address public owner = makeAddr("owner"); @@ -23,8 +23,8 @@ contract CirculatingZKCTest is Test { // Deploy ZKC deployZKC(); - // Deploy CirculatingZKC - deployCirculatingZKC(); + // Deploy SupplyCalculator + deploySupplyCalculator(); } function deployZKC() internal { @@ -60,27 +60,27 @@ contract CirculatingZKCTest is Test { vm.stopPrank(); } - function deployCirculatingZKC() internal { + function deploySupplyCalculator() internal { // Deploy implementation - CirculatingZKC implementation = new CirculatingZKC(); + SupplyCalculator implementation = new SupplyCalculator(); // Deploy proxy and initialize bytes memory initData = - abi.encodeWithSelector(CirculatingZKC.initialize.selector, address(zkc), INITIAL_UNLOCKED, owner); + abi.encodeWithSelector(SupplyCalculator.initialize.selector, address(zkc), INITIAL_UNLOCKED, owner); ERC1967Proxy proxy = new ERC1967Proxy(address(implementation), initData); - circulatingZKC = CirculatingZKC(address(proxy)); + supplyCalculator = SupplyCalculator(address(proxy)); } function testInitialization() public view { - assertEq(address(circulatingZKC.zkc()), address(zkc)); - assertEq(circulatingZKC.unlocked(), INITIAL_UNLOCKED); - assertTrue(circulatingZKC.hasRole(circulatingZKC.ADMIN_ROLE(), owner)); + assertEq(address(supplyCalculator.zkc()), address(zkc)); + assertEq(supplyCalculator.unlocked(), INITIAL_UNLOCKED); + assertTrue(supplyCalculator.hasRole(supplyCalculator.ADMIN_ROLE(), owner)); } function testCirculatingSupplyAfterInitialMint() public { // Circulating supply should be just unlocked since total minted (1B) - uint256 circulatingSupply = circulatingZKC.circulatingSupply(); + uint256 circulatingSupply = supplyCalculator.circulatingSupply(); assertEq(circulatingSupply, INITIAL_UNLOCKED); } @@ -99,7 +99,7 @@ contract CirculatingZKCTest is Test { zkc.mintStakingRewardsForRecipient(user, stakingRewards); uint256 expectedCirculating = INITIAL_UNLOCKED + povwRewards + stakingRewards; - uint256 circulatingSupply = circulatingZKC.circulatingSupply(); + uint256 circulatingSupply = supplyCalculator.circulatingSupply(); assertEq(circulatingSupply, expectedCirculating); } @@ -107,14 +107,14 @@ contract CirculatingZKCTest is Test { uint256 newUnlocked = 750_000_000e18; // 750M tokens vm.expectEmit(true, true, true, true); - emit CirculatingZKC.UnlockedValueUpdated(INITIAL_UNLOCKED, newUnlocked); + emit SupplyCalculator.UnlockedValueUpdated(INITIAL_UNLOCKED, newUnlocked); vm.prank(owner); - circulatingZKC.updateUnlockedValue(newUnlocked); + supplyCalculator.updateUnlockedValue(newUnlocked); - assertEq(circulatingZKC.unlocked(), newUnlocked); + assertEq(supplyCalculator.unlocked(), newUnlocked); // Check circulating supply updated correctly - uint256 circulatingSupply = circulatingZKC.circulatingSupply(); + uint256 circulatingSupply = supplyCalculator.circulatingSupply(); assertEq(circulatingSupply, newUnlocked); } @@ -124,26 +124,26 @@ contract CirculatingZKCTest is Test { // Non-admin should not be able to update vm.prank(user); vm.expectRevert(); - circulatingZKC.updateUnlockedValue(newUnlocked); + supplyCalculator.updateUnlockedValue(newUnlocked); // Admin should be able to update vm.prank(owner); - circulatingZKC.updateUnlockedValue(newUnlocked); - assertEq(circulatingZKC.unlocked(), newUnlocked); + supplyCalculator.updateUnlockedValue(newUnlocked); + assertEq(supplyCalculator.unlocked(), newUnlocked); } function testUpgradeAccessControl() public { // Deploy new implementation - CirculatingZKC newImplementation = new CirculatingZKC(); + SupplyCalculator newImplementation = new SupplyCalculator(); // Non-admin should not be able to upgrade vm.prank(user); vm.expectRevert(); - circulatingZKC.upgradeToAndCall(address(newImplementation), ""); + supplyCalculator.upgradeToAndCall(address(newImplementation), ""); // Admin should be able to upgrade vm.prank(owner); - circulatingZKC.upgradeToAndCall(address(newImplementation), ""); + supplyCalculator.upgradeToAndCall(address(newImplementation), ""); } function testCirculatingSupplyWithBurnedTokens() public { @@ -161,7 +161,75 @@ contract CirculatingZKCTest is Test { zkc.burn(burnAmount); uint256 expectedCirculating = INITIAL_UNLOCKED + rewards - burnAmount; - uint256 circulatingSupply = circulatingZKC.circulatingSupply(); + uint256 circulatingSupply = supplyCalculator.circulatingSupply(); assertEq(circulatingSupply, expectedCirculating); } + + function testCirculatingSupplyRounded() public { + // Test with value that rounds down + uint256 valueRoundDown = 500_000_000e18 + 0.3e18; + vm.prank(owner); + supplyCalculator.updateUnlockedValue(valueRoundDown); + + uint256 rounded18dp = supplyCalculator.circulatingSupplyRounded(); + assertEq(rounded18dp, 500_000_000e18); + + uint256 roundedAmount = supplyCalculator.circulatingSupplyAmountRounded(); + assertEq(roundedAmount, 500_000_000); + + // Test with value that rounds up + uint256 valueRoundUp = 500_000_000e18 + 0.7e18; + vm.prank(owner); + supplyCalculator.updateUnlockedValue(valueRoundUp); + + rounded18dp = supplyCalculator.circulatingSupplyRounded(); + assertEq(rounded18dp, 500_000_001e18); + + roundedAmount = supplyCalculator.circulatingSupplyAmountRounded(); + assertEq(roundedAmount, 500_000_001); + } + + function testTotalSupplyRounded() public { + // Skip forward to start epochs + uint256 epoch17TotalSupply = zkc.getSupplyAtEpochStart(17); + uint256 epoch17ExpectedTotalSupply = 1006339775710604115000000000; + assertEq(epoch17TotalSupply, epoch17ExpectedTotalSupply); + + vm.warp(block.timestamp + 17 * zkc.EPOCH_DURATION()); + + // Get the theoretical total supply + uint256 totalSupply = zkc.totalSupply(); + console.logUint(totalSupply); + console.logUint(epoch17TotalSupply); + assertEq(totalSupply, epoch17TotalSupply); + + // Get rounded values + uint256 rounded18dp = supplyCalculator.totalSupplyRounded(); + uint256 roundedAmount = supplyCalculator.totalSupplyAmountRounded(); + + // Verify rounding is correct + assertEq(rounded18dp, 1006339776000000000000000000); + assertEq(roundedAmount, 1006339776); + } + + function testTotalClaimedSupplyRounded() public { + // Skip forward in time to simulate epochs passing + vm.warp(block.timestamp + 4 weeks); + + // Mint some rewards to create a claimed supply > initial supply + uint256 rewards = 1_234_567.89e18; + vm.prank(povwMinter); + zkc.mintPoVWRewardsForRecipient(user, rewards); + + // Get the claimed total supply + uint256 claimedSupply = zkc.claimedTotalSupply(); + assertEq(claimedSupply, zkc.INITIAL_SUPPLY() + rewards); + + // Get rounded values + uint256 rounded18dp = supplyCalculator.totalClaimedSupplyRounded(); + uint256 roundedAmount = supplyCalculator.totalClaimedSupplyAmountRounded(); + + assertEq(rounded18dp, 1001234568000000000000000000); + assertEq(roundedAmount, 1001234568); + } } diff --git a/update_deployment_toml.py b/update_deployment_toml.py index 4863aa2..7fa9502 100755 --- a/update_deployment_toml.py +++ b/update_deployment_toml.py @@ -32,7 +32,8 @@ def main(): parser.add_argument('--vezkc-admin-2', help='veZKC secondary admin address') parser.add_argument('--staking-rewards-admin', help='StakingRewards admin address') parser.add_argument('--staking-rewards-admin-2', help='StakingRewards secondary admin address') - + parser.add_argument('--supply-calculator-admin-2', help='SupplyCalculator secondary admin address') + parser.add_argument('--zkc', help='ZKC proxy address') parser.add_argument('--zkc-impl', help='ZKC implementation address') parser.add_argument('--zkc-impl-prev', help='Previous ZKC implementation address') @@ -47,15 +48,15 @@ def main(): parser.add_argument('--staking-rewards-deployer', help='StakingRewards deployer address') parser.add_argument('--povw-minter', help='POVW minter address') parser.add_argument('--staking-minter', help='Staking minter address') - parser.add_argument('--circulating-zkc', help='CirculatingZKC proxy address') - parser.add_argument('--circulating-zkc-impl', help='CirculatingZKC implementation address') - parser.add_argument('--circulating-zkc-admin', help='CirculatingZKC admin address') + parser.add_argument('--supply-calculator', help='SupplyCalculator proxy address') + parser.add_argument('--supply-calculator-impl', help='SupplyCalculator implementation address') + parser.add_argument('--supply-calculator-admin', help='SupplyCalculator admin address') # Deployment metadata parser.add_argument('--zkc-commit', help='Git commit hash for ZKC deployment') parser.add_argument('--vezkc-commit', help='Git commit hash for veZKC deployment') parser.add_argument('--staking-rewards-commit', help='Git commit hash for StakingRewards deployment') - parser.add_argument('--circulating-zkc-commit', help='Git commit hash for CirculatingZKC deployment') + parser.add_argument('--supply-calculator-commit', help='Git commit hash for SupplyCalculator deployment') parser.add_argument('--rpc-url', help='RPC URL for the network') parser.add_argument('--etherscan-api-key', help='Etherscan API key') @@ -101,6 +102,7 @@ def main(): 'vezkc_admin_2': args.vezkc_admin_2, 'staking_rewards_admin': args.staking_rewards_admin, 'staking_rewards_admin_2': args.staking_rewards_admin_2, + 'supply_calculator_admin_2': args.supply_calculator_admin_2, 'zkc': args.zkc, 'zkc_impl': args.zkc_impl, 'zkc_impl_prev': args.zkc_impl_prev, @@ -115,13 +117,13 @@ def main(): 'staking_rewards_deployer': args.staking_rewards_deployer, 'povw_minter': args.povw_minter, 'staking_minter': args.staking_minter, - 'circulating_zkc': args.circulating_zkc, - 'circulating_zkc_impl': args.circulating_zkc_impl, - 'circulating_zkc_admin': args.circulating_zkc_admin, + 'supply_calculator': args.supply_calculator, + 'supply_calculator_impl': args.supply_calculator_impl, + 'supply_calculator_admin': args.supply_calculator_admin, 'zkc_commit': args.zkc_commit, 'vezkc_commit': args.vezkc_commit, 'staking_rewards_commit': args.staking_rewards_commit, - 'circulating_zkc_commit': args.circulating_zkc_commit, + 'supply_calculator_commit': args.supply_calculator_commit, 'rpc_url': args.rpc_url, 'etherscan_api_key': args.etherscan_api_key, } From 14facdc959285a45c3d34094616c64d804eb3b36 Mon Sep 17 00:00:00 2001 From: Pote <81638931+willpote@users.noreply.github.com> Date: Wed, 15 Oct 2025 20:04:59 -0500 Subject: [PATCH 2/4] Add calculator upgrade script --- script/Config.s.sol | 5 +++ script/Rollback.s.sol | 60 ++++++++++++++++++++++++++++++++ script/Upgrade.s.sol | 80 +++++++++++++++++++++++++++++++++++++++++++ script/manage | 26 ++++++++++++-- 4 files changed, 168 insertions(+), 3 deletions(-) diff --git a/script/Config.s.sol b/script/Config.s.sol index d2c43a0..24b7dd3 100644 --- a/script/Config.s.sol +++ b/script/Config.s.sol @@ -30,6 +30,7 @@ struct DeploymentConfig { address stakingMinter; address supplyCalculator; address supplyCalculatorImpl; + address supplyCalculatorImplPrev; address supplyCalculatorAdmin; address supplyCalculatorAdmin2; string zkcCommit; @@ -79,6 +80,7 @@ library ConfigLoader { config.stakingMinter = _readAddressOrZero(vm, toml, string.concat(keyPrefix, ".staking-minter")); config.supplyCalculator = _readAddressOrZero(vm, toml, string.concat(keyPrefix, ".supply-calculator")); config.supplyCalculatorImpl = _readAddressOrZero(vm, toml, string.concat(keyPrefix, ".supply-calculator-impl")); + config.supplyCalculatorImplPrev = _readAddressOrZero(vm, toml, string.concat(keyPrefix, ".supply-calculator-impl-prev")); config.supplyCalculatorAdmin = _readAddressOrZero(vm, toml, string.concat(keyPrefix, ".supply-calculator-admin")); config.supplyCalculatorAdmin2 = _readAddressOrZero(vm, toml, string.concat(keyPrefix, ".supply-calculator-admin-2")); @@ -92,6 +94,9 @@ library ConfigLoader { string memory stakingRewardsCommitKey = string.concat(keyPrefix, ".staking-rewards-commit"); config.stakingRewardsCommit = _readStringOrEmpty(toml, stakingRewardsCommitKey); + string memory supplyCalculatorCommitKey = string.concat(keyPrefix, ".supply-calculator-commit"); + config.supplyCalculatorCommit = _readStringOrEmpty(toml, supplyCalculatorCommitKey); + return (config, deploymentKey); } diff --git a/script/Rollback.s.sol b/script/Rollback.s.sol index 68c1ff8..2d1f002 100644 --- a/script/Rollback.s.sol +++ b/script/Rollback.s.sol @@ -8,6 +8,7 @@ import {BaseDeployment} from "./BaseDeployment.s.sol"; import {ZKC} from "../src/ZKC.sol"; import {veZKC} from "../src/veZKC.sol"; import {StakingRewards} from "../src/rewards/StakingRewards.sol"; +import {SupplyCalculator} from "../src/calculators/SupplyCalculator.sol"; import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol"; /** @@ -206,3 +207,62 @@ contract RollbackStakingRewards is BaseDeployment { console2.log("Rolled back to implementation: ", rolledBackImpl); } } + +/** + * Sample Usage for SupplyCalculator rollback: + * + * export CHAIN_KEY="anvil" + * forge script script/Rollback.s.sol:RollbackSupplyCalculator \ + * --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ + * --broadcast \ + * --rpc-url http://127.0.0.1:8545 + */ +contract RollbackSupplyCalculator is BaseDeployment { + function run() public { + (DeploymentConfig memory config, string memory deploymentKey) = ConfigLoader.loadDeploymentConfig(vm); + require(config.supplyCalculator != address(0), "SupplyCalculator not deployed"); + require( + config.supplyCalculatorImplPrev != address(0), "No previous SupplyCalculator implementation found for rollback" + ); + + vm.startBroadcast(); + + address currentImpl = _getImplementationAddress(config.supplyCalculator); + console2.log("Rolling back SupplyCalculator at: ", config.supplyCalculator); + console2.log("Current implementation: ", currentImpl); + console2.log("Previous implementation: ", config.supplyCalculatorImplPrev); + + // Verify previous implementation has code + require(_getCodeSize(config.supplyCalculatorImplPrev) > 0, "Previous implementation has no code"); + + // Perform rollback by directly upgrading to previous implementation (unsafe) + (bool success,) = config.supplyCalculator.call( + abi.encodeWithSignature("upgradeToAndCall(address,bytes)", config.supplyCalculatorImplPrev, "") + ); + require(success, "Failed to rollback SupplyCalculator implementation"); + + address rolledBackImpl = _getImplementationAddress(config.supplyCalculator); + console2.log("Rolled back SupplyCalculator implementation to: ", rolledBackImpl); + require(rolledBackImpl == config.supplyCalculatorImplPrev, "Rollback failed: implementation mismatch"); + + vm.stopBroadcast(); + + // Update deployment.toml: swap current and previous implementations + _updateDeploymentConfig(deploymentKey, "supply-calculator-impl", config.supplyCalculatorImplPrev); + _updateDeploymentConfig(deploymentKey, "supply-calculator-impl-prev", currentImpl); + + // Verify rollback + SupplyCalculator supplyCalculatorContract = SupplyCalculator(config.supplyCalculator); + IAccessControl accessControl = IAccessControl(config.supplyCalculator); + console2.log("Proxy still points to SupplyCalculator: ", address(supplyCalculatorContract) == config.supplyCalculator); + console2.log( + "Admin role still assigned: ", + accessControl.hasRole(supplyCalculatorContract.ADMIN_ROLE(), config.supplyCalculatorAdmin) + ); + console2.log("ZKC token still configured: ", address(supplyCalculatorContract.zkc()) == config.zkc); + console2.log("Rollback verification successful"); + console2.log("================================================"); + console2.log("SupplyCalculator Rollback Complete"); + console2.log("Rolled back to implementation: ", rolledBackImpl); + } +} diff --git a/script/Upgrade.s.sol b/script/Upgrade.s.sol index 769daa2..51ba583 100644 --- a/script/Upgrade.s.sol +++ b/script/Upgrade.s.sol @@ -8,6 +8,7 @@ import {BaseDeployment} from "./BaseDeployment.s.sol"; import {ZKC} from "../src/ZKC.sol"; import {veZKC} from "../src/veZKC.sol"; import {StakingRewards} from "../src/rewards/StakingRewards.sol"; +import {SupplyCalculator} from "../src/calculators/SupplyCalculator.sol"; /** * Sample Usage for ZKC upgrade: @@ -484,3 +485,82 @@ contract UpgradeStakingRewards is BaseDeployment { console2.log("New Implementation: ", newImpl); } } + +/** + * Sample Usage for SupplyCalculator upgrade: + * + * # First, create reference build from deployed SupplyCalculator commit: + * export DEPLOYED_COMMIT=$(python3 -c "import tomlkit; print(tomlkit.load(open('deployment.toml'))['deployment']['$CHAIN_KEY']['supply-calculator-commit'])") + * WORKTREE_PATH="../supply-calculator-reference-${DEPLOYED_COMMIT}" + * git worktree add "$WORKTREE_PATH" "$DEPLOYED_COMMIT" + * cd "$WORKTREE_PATH" + * forge build --profile reference + * cp -R out-reference/build-info "$OLDPWD/build-info-reference" + * cd "$OLDPWD" + * + * # Then run upgrade: + * export CHAIN_KEY="anvil" + * forge script script/Upgrade.s.sol:UpgradeSupplyCalculator \ + * --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ + * --broadcast \ + * --rpc-url http://127.0.0.1:8545 + */ +contract UpgradeSupplyCalculator is BaseDeployment { + function run() public { + (DeploymentConfig memory config, string memory deploymentKey) = ConfigLoader.loadDeploymentConfig(vm); + require(config.supplyCalculator != address(0), "SupplyCalculator not deployed"); + + vm.startBroadcast(); + + // Check for skip safety checks flag + bool skipSafetyChecks = vm.envOr("SKIP_SAFETY_CHECKS", false); + + // Prepare upgrade options with reference contract + Options memory opts; + + if (skipSafetyChecks) { + console2.log("WARNING: Skipping all upgrade safety checks and reference build!"); + opts.unsafeSkipAllChecks = true; + } else { + // Get the SupplyCalculator commit hash from deployment config for commit-specific reference build + string memory supplyCalculatorCommit = config.supplyCalculatorCommit; + require(bytes(supplyCalculatorCommit).length > 0, "SupplyCalculator commit hash not found in deployment config"); + + string memory referenceBuildDir = string.concat("build-info-reference-", supplyCalculatorCommit); + opts.referenceContract = string.concat(referenceBuildDir, ":SupplyCalculator"); + opts.referenceBuildInfoDir = referenceBuildDir; + console2.log("Using reference build directory: ", referenceBuildDir); + } + + console2.log("Upgrading SupplyCalculator at: ", config.supplyCalculator); + address currentImpl = _getImplementationAddress(config.supplyCalculator); + console2.log("Current implementation: ", currentImpl); + + // Perform safe upgrade + Upgrades.upgradeProxy( + config.supplyCalculator, + "SupplyCalculator.sol:SupplyCalculator", + "", // No reinitializer + opts + ); + + address newImpl = Upgrades.getImplementationAddress(config.supplyCalculator); + console2.log("Upgraded SupplyCalculator implementation to: ", newImpl); + + vm.stopBroadcast(); + + // Update deployment.toml with new implementation and store previous + _updateDeploymentConfig(deploymentKey, "supply-calculator-impl-prev", currentImpl); + _updateDeploymentConfig(deploymentKey, "supply-calculator-impl", newImpl); + _updateSupplyCalculatorCommit(deploymentKey); + + // Verify upgrade + SupplyCalculator supplyCalculatorContract = SupplyCalculator(config.supplyCalculator); + console2.log("Proxy still points to SupplyCalculator: ", address(supplyCalculatorContract) == config.supplyCalculator); + console2.log("Implementation updated: ", newImpl != config.supplyCalculatorImpl); + console2.log("ZKC token still configured: ", address(supplyCalculatorContract.zkc()) == config.zkc); + console2.log("================================================"); + console2.log("SupplyCalculator Upgrade Complete"); + console2.log("New Implementation: ", newImpl); + } +} diff --git a/script/manage b/script/manage index d626af3..52711ef 100755 --- a/script/manage +++ b/script/manage @@ -81,6 +81,9 @@ create_reference_build() { staking) commit_field="staking-rewards-commit" ;; + supply-calculator) + commit_field="supply-calculator-commit" + ;; *) echo "❌ Unknown contract type: $contract_type" exit 1 @@ -261,11 +264,13 @@ Commands: upgrade-zkc Upgrade ZKC implementation (no initializer) upgrade-zkc-initv2 Upgrade ZKC implementation and call initializeV2 zkc-start-epochs Call initializeV3 to start epochs (no upgrade) - upgrade-vezkc Upgrade veZKC implementation + upgrade-vezkc Upgrade veZKC implementation upgrade-staking Upgrade StakingRewards implementation + upgrade-supply-calculator Upgrade SupplyCalculator implementation rollback-zkc Rollback ZKC to previous implementation rollback-vezkc Rollback veZKC to previous implementation rollback-staking Rollback StakingRewards to previous implementation + rollback-supply-calculator Rollback SupplyCalculator to previous implementation update-povw-minter Set POVW_MINTER_ROLE in ZKC contract update-staking-minter Set STAKING_MINTER_ROLE in ZKC contract remove-povw-minter Revoke POVW_MINTER_ROLE from address in ZKC contract @@ -332,10 +337,16 @@ Examples: # Upgrade ZKC skipping safety checks (WARNING: unsafe!) PRIVATE_KEY=0x... CHAIN_KEY=anvil SKIP_SAFETY_CHECKS=true ./script/manage upgrade-zkc --broadcast - + + # Upgrade SupplyCalculator on mainnet with verification + PRIVATE_KEY=0x... CHAIN_KEY=ethereum-mainnet ./script/manage upgrade-supply-calculator --broadcast --verify + # Rollback ZKC to previous implementation PRIVATE_KEY=0x... CHAIN_KEY=anvil ./script/manage rollback-zkc --broadcast - + + # Rollback SupplyCalculator to previous implementation + PRIVATE_KEY=0x... CHAIN_KEY=anvil ./script/manage rollback-supply-calculator --broadcast + # Set POVW minter role (requires POVW_MINTER env var) PRIVATE_KEY=0x... POVW_MINTER=0x123... CHAIN_KEY=anvil ./script/manage update-povw-minter --broadcast @@ -507,6 +518,9 @@ main() { upgrade-staking) contract_type="staking" ;; + upgrade-supply-calculator) + contract_type="supply-calculator" + ;; *) echo "❌ Unknown upgrade command: $COMMAND" exit 1 @@ -553,6 +567,9 @@ main() { upgrade-staking) forge_script "UpgradeStakingRewards" "${SCRIPT_ARGS[@]+"${SCRIPT_ARGS[@]}"}" ;; + upgrade-supply-calculator) + forge_script "UpgradeSupplyCalculator" "${SCRIPT_ARGS[@]+"${SCRIPT_ARGS[@]}"}" + ;; rollback-zkc) forge_script "RollbackZKC" "${SCRIPT_ARGS[@]+"${SCRIPT_ARGS[@]}"}" ;; @@ -562,6 +579,9 @@ main() { rollback-staking) forge_script "RollbackStakingRewards" "${SCRIPT_ARGS[@]+"${SCRIPT_ARGS[@]}"}" ;; + rollback-supply-calculator) + forge_script "RollbackSupplyCalculator" "${SCRIPT_ARGS[@]+"${SCRIPT_ARGS[@]}"}" + ;; update-povw-minter) forge_script "UpdatePOVWMinter" "${SCRIPT_ARGS[@]+"${SCRIPT_ARGS[@]}"}" ;; From 537c0770d2d0ae3e6fd6330dca548ae0dc936858 Mon Sep 17 00:00:00 2001 From: Pote <81638931+willpote@users.noreply.github.com> Date: Wed, 15 Oct 2025 21:03:01 -0500 Subject: [PATCH 3/4] Rename functions --- deployment.toml | 9 +++++---- script/Upgrade.s.sol | 19 ------------------- src/calculators/SupplyCalculator.sol | 14 +++++++++++--- test/calculators/SupplyCalculator.t.sol | 4 ++-- 4 files changed, 18 insertions(+), 28 deletions(-) diff --git a/deployment.toml b/deployment.toml index 446c847..45348c2 100644 --- a/deployment.toml +++ b/deployment.toml @@ -25,13 +25,14 @@ staking-rewards-impl-prev = "0x2d57bc44e3c1fe4ceec8f2bf9ea0ac954b527e43" staking-rewards-deployer = "0xb13573c6ceb505a7bdd4fa3ad7b473c5c5d36b19" povw-minter = "0xbfce7c2d5e7eddeab71b3eeed770713c8b755397" staking-minter = "0x459d87d54808fac136ddcf439fcc1d8a238311c7" -circulating-zkc = "0x15eB8cA378E33381c867573EF2f25Ca1010f866d" -circulating-zkc-impl = "0xB0615B524a560b307C8fc486928B989a8b1234DA" -circulating-zkc-admin = "0xb13573c6ceb505a7bdd4fa3ad7b473c5c5d36b19" -circulating-zkc-commit = "9d0b6a9b541d3b6980893b3385d33d12b173f232" +supply-calculator = "0x15eB8cA378E33381c867573EF2f25Ca1010f866d" +supply-calculator-impl = "0xb068e8f58ff45dc3dbca8f1f3bfd9c34ee2b3500" +supply-calculator-admin = "0xb13573c6ceb505a7bdd4fa3ad7b473c5c5d36b19" +supply-calculator-commit = "14facdc" zkc-commit = "5b8310b" vezkc-commit = "caa29df" staking-rewards-commit = "24e6b5a" +supply-calculator-admin-2 = "0xb04d1a222789a76e74168a919b43b20f66e24f0b" [deployment.ethereum-sepolia] name = "Ethereum Sepolia" diff --git a/script/Upgrade.s.sol b/script/Upgrade.s.sol index 51ba583..5b5b08e 100644 --- a/script/Upgrade.s.sol +++ b/script/Upgrade.s.sol @@ -486,25 +486,6 @@ contract UpgradeStakingRewards is BaseDeployment { } } -/** - * Sample Usage for SupplyCalculator upgrade: - * - * # First, create reference build from deployed SupplyCalculator commit: - * export DEPLOYED_COMMIT=$(python3 -c "import tomlkit; print(tomlkit.load(open('deployment.toml'))['deployment']['$CHAIN_KEY']['supply-calculator-commit'])") - * WORKTREE_PATH="../supply-calculator-reference-${DEPLOYED_COMMIT}" - * git worktree add "$WORKTREE_PATH" "$DEPLOYED_COMMIT" - * cd "$WORKTREE_PATH" - * forge build --profile reference - * cp -R out-reference/build-info "$OLDPWD/build-info-reference" - * cd "$OLDPWD" - * - * # Then run upgrade: - * export CHAIN_KEY="anvil" - * forge script script/Upgrade.s.sol:UpgradeSupplyCalculator \ - * --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ - * --broadcast \ - * --rpc-url http://127.0.0.1:8545 - */ contract UpgradeSupplyCalculator is BaseDeployment { function run() public { (DeploymentConfig memory config, string memory deploymentKey) = ConfigLoader.loadDeploymentConfig(vm); diff --git a/src/calculators/SupplyCalculator.sol b/src/calculators/SupplyCalculator.sol index 6664c7f..76481dc 100644 --- a/src/calculators/SupplyCalculator.sol +++ b/src/calculators/SupplyCalculator.sol @@ -49,6 +49,7 @@ contract SupplyCalculator is Initializable, AccessControlUpgradeable, UUPSUpgrad /// @notice Calculate the current circulating supply /// @dev Formula: unlocked + (zkc.claimedTotalSupply() - zkc.INITIAL_SUPPLY()) + /// @dev Essentially, unlocked tokens from the initial mint + claimed rewards from PoVW and staking rewards /// @return The current circulating supply of ZKC tokens function circulatingSupply() public view returns (uint256) { uint256 claimedTotal = zkc.claimedTotalSupply(); @@ -76,6 +77,9 @@ contract SupplyCalculator is Initializable, AccessControlUpgradeable, UUPSUpgrad return _roundToWholeTokens(supply); } + /// @notice Get the total supply + /// @dev This represents the theoretical total supply of ZKC tokens based on the current epoch + /// @return The total supply of ZKC tokens function totalSupply() public view returns (uint256) { return IERC20(address(zkc)).totalSupply(); } @@ -98,7 +102,11 @@ contract SupplyCalculator is Initializable, AccessControlUpgradeable, UUPSUpgrad return _roundToWholeTokens(supply); } - function totalClaimedSupply() public view returns (uint256) { + /// @notice Get the total claimed supply + /// @dev This represents the initial supply that was minted and allocated to initial minters, + /// as well as tokens that have been claimed (and thus minted) via PoVW or Staking rewards. + /// @return The total amount of tokens that have been claimed + function claimedTotalSupply() public view returns (uint256) { return zkc.claimedTotalSupply(); } @@ -106,7 +114,7 @@ contract SupplyCalculator is Initializable, AccessControlUpgradeable, UUPSUpgrad /// @dev Returns value in wei (18 decimals) but rounded such that when converted to whole tokens it's rounded /// @dev Uses the actual claimed/minted supply /// @return The claimed total supply rounded to nearest whole token in 18dp format - function totalClaimedSupplyRounded() public view returns (uint256) { + function claimedTotalSupplyRounded() public view returns (uint256) { uint256 supply = zkc.claimedTotalSupply(); return _roundTo18dp(supply); } @@ -115,7 +123,7 @@ contract SupplyCalculator is Initializable, AccessControlUpgradeable, UUPSUpgrad /// @dev Returns the value in regular representation (divided by 10^18) rounded to nearest whole token /// @dev Uses the actual claimed/minted supply /// @return The claimed total supply as a whole number - function totalClaimedSupplyAmountRounded() public view returns (uint256) { + function claimedTotalSupplyAmountRounded() public view returns (uint256) { uint256 supply = zkc.claimedTotalSupply(); return _roundToWholeTokens(supply); } diff --git a/test/calculators/SupplyCalculator.t.sol b/test/calculators/SupplyCalculator.t.sol index 334f5a9..5d1812d 100644 --- a/test/calculators/SupplyCalculator.t.sol +++ b/test/calculators/SupplyCalculator.t.sol @@ -226,8 +226,8 @@ contract SupplyCalculatorTest is Test { assertEq(claimedSupply, zkc.INITIAL_SUPPLY() + rewards); // Get rounded values - uint256 rounded18dp = supplyCalculator.totalClaimedSupplyRounded(); - uint256 roundedAmount = supplyCalculator.totalClaimedSupplyAmountRounded(); + uint256 rounded18dp = supplyCalculator.claimedTotalSupplyRounded(); + uint256 roundedAmount = supplyCalculator.claimedTotalSupplyAmountRounded(); assertEq(rounded18dp, 1001234568000000000000000000); assertEq(roundedAmount, 1001234568); From f2f022a5fb9fa01d63e5a709f726c01a27bc038c Mon Sep 17 00:00:00 2001 From: Pote <81638931+willpote@users.noreply.github.com> Date: Wed, 22 Oct 2025 12:17:02 -0500 Subject: [PATCH 4/4] Fmt + latest deployment --- deployment.toml | 4 ++-- script/Config.s.sol | 9 ++++++--- script/Rollback.s.sol | 7 +++++-- script/Update.s.sol | 6 +++--- script/Upgrade.s.sol | 8 ++++++-- 5 files changed, 22 insertions(+), 12 deletions(-) diff --git a/deployment.toml b/deployment.toml index 45348c2..2584e23 100644 --- a/deployment.toml +++ b/deployment.toml @@ -26,9 +26,9 @@ staking-rewards-deployer = "0xb13573c6ceb505a7bdd4fa3ad7b473c5c5d36b19" povw-minter = "0xbfce7c2d5e7eddeab71b3eeed770713c8b755397" staking-minter = "0x459d87d54808fac136ddcf439fcc1d8a238311c7" supply-calculator = "0x15eB8cA378E33381c867573EF2f25Ca1010f866d" -supply-calculator-impl = "0xb068e8f58ff45dc3dbca8f1f3bfd9c34ee2b3500" +supply-calculator-impl = "0x1ff6f81ea7f5e6feafaafb0fc5983576096ded1d" supply-calculator-admin = "0xb13573c6ceb505a7bdd4fa3ad7b473c5c5d36b19" -supply-calculator-commit = "14facdc" +supply-calculator-commit = "537c077" zkc-commit = "5b8310b" vezkc-commit = "caa29df" staking-rewards-commit = "24e6b5a" diff --git a/script/Config.s.sol b/script/Config.s.sol index 24b7dd3..1c744f8 100644 --- a/script/Config.s.sol +++ b/script/Config.s.sol @@ -80,9 +80,12 @@ library ConfigLoader { config.stakingMinter = _readAddressOrZero(vm, toml, string.concat(keyPrefix, ".staking-minter")); config.supplyCalculator = _readAddressOrZero(vm, toml, string.concat(keyPrefix, ".supply-calculator")); config.supplyCalculatorImpl = _readAddressOrZero(vm, toml, string.concat(keyPrefix, ".supply-calculator-impl")); - config.supplyCalculatorImplPrev = _readAddressOrZero(vm, toml, string.concat(keyPrefix, ".supply-calculator-impl-prev")); - config.supplyCalculatorAdmin = _readAddressOrZero(vm, toml, string.concat(keyPrefix, ".supply-calculator-admin")); - config.supplyCalculatorAdmin2 = _readAddressOrZero(vm, toml, string.concat(keyPrefix, ".supply-calculator-admin-2")); + config.supplyCalculatorImplPrev = + _readAddressOrZero(vm, toml, string.concat(keyPrefix, ".supply-calculator-impl-prev")); + config.supplyCalculatorAdmin = + _readAddressOrZero(vm, toml, string.concat(keyPrefix, ".supply-calculator-admin")); + config.supplyCalculatorAdmin2 = + _readAddressOrZero(vm, toml, string.concat(keyPrefix, ".supply-calculator-admin-2")); // Read per-contract deployment commits, default to empty string if not found string memory zkcCommitKey = string.concat(keyPrefix, ".zkc-commit"); diff --git a/script/Rollback.s.sol b/script/Rollback.s.sol index 2d1f002..4316c4d 100644 --- a/script/Rollback.s.sol +++ b/script/Rollback.s.sol @@ -222,7 +222,8 @@ contract RollbackSupplyCalculator is BaseDeployment { (DeploymentConfig memory config, string memory deploymentKey) = ConfigLoader.loadDeploymentConfig(vm); require(config.supplyCalculator != address(0), "SupplyCalculator not deployed"); require( - config.supplyCalculatorImplPrev != address(0), "No previous SupplyCalculator implementation found for rollback" + config.supplyCalculatorImplPrev != address(0), + "No previous SupplyCalculator implementation found for rollback" ); vm.startBroadcast(); @@ -254,7 +255,9 @@ contract RollbackSupplyCalculator is BaseDeployment { // Verify rollback SupplyCalculator supplyCalculatorContract = SupplyCalculator(config.supplyCalculator); IAccessControl accessControl = IAccessControl(config.supplyCalculator); - console2.log("Proxy still points to SupplyCalculator: ", address(supplyCalculatorContract) == config.supplyCalculator); + console2.log( + "Proxy still points to SupplyCalculator: ", address(supplyCalculatorContract) == config.supplyCalculator + ); console2.log( "Admin role still assigned: ", accessControl.hasRole(supplyCalculatorContract.ADMIN_ROLE(), config.supplyCalculatorAdmin) diff --git a/script/Update.s.sol b/script/Update.s.sol index 09dd140..9b65505 100644 --- a/script/Update.s.sol +++ b/script/Update.s.sol @@ -1020,9 +1020,9 @@ contract RemoveSupplyCalculatorAdmin is BaseDeployment { // Safety check: Ensure at least one other admin will remain IAccessControl accessControl = IAccessControl(config.supplyCalculator); - address otherAdmin = (config.supplyCalculatorAdmin != address(0) && config.supplyCalculatorAdmin != adminToRemove) - ? config.supplyCalculatorAdmin - : config.supplyCalculatorAdmin2; + address otherAdmin = ( + config.supplyCalculatorAdmin != address(0) && config.supplyCalculatorAdmin != adminToRemove + ) ? config.supplyCalculatorAdmin : config.supplyCalculatorAdmin2; require( otherAdmin != adminToRemove && otherAdmin != address(0) && accessControl.hasRole(supplyCalculatorContract.ADMIN_ROLE(), otherAdmin), diff --git a/script/Upgrade.s.sol b/script/Upgrade.s.sol index 5b5b08e..1eac062 100644 --- a/script/Upgrade.s.sol +++ b/script/Upgrade.s.sol @@ -505,7 +505,9 @@ contract UpgradeSupplyCalculator is BaseDeployment { } else { // Get the SupplyCalculator commit hash from deployment config for commit-specific reference build string memory supplyCalculatorCommit = config.supplyCalculatorCommit; - require(bytes(supplyCalculatorCommit).length > 0, "SupplyCalculator commit hash not found in deployment config"); + require( + bytes(supplyCalculatorCommit).length > 0, "SupplyCalculator commit hash not found in deployment config" + ); string memory referenceBuildDir = string.concat("build-info-reference-", supplyCalculatorCommit); opts.referenceContract = string.concat(referenceBuildDir, ":SupplyCalculator"); @@ -537,7 +539,9 @@ contract UpgradeSupplyCalculator is BaseDeployment { // Verify upgrade SupplyCalculator supplyCalculatorContract = SupplyCalculator(config.supplyCalculator); - console2.log("Proxy still points to SupplyCalculator: ", address(supplyCalculatorContract) == config.supplyCalculator); + console2.log( + "Proxy still points to SupplyCalculator: ", address(supplyCalculatorContract) == config.supplyCalculator + ); console2.log("Implementation updated: ", newImpl != config.supplyCalculatorImpl); console2.log("ZKC token still configured: ", address(supplyCalculatorContract.zkc()) == config.zkc); console2.log("================================================");