diff --git a/contracts/ethereum/.openzeppelin/goerli.json b/contracts/ethereum/.openzeppelin/goerli.json index 74bf4d315..5ced339d9 100644 --- a/contracts/ethereum/.openzeppelin/goerli.json +++ b/contracts/ethereum/.openzeppelin/goerli.json @@ -1275,7 +1275,7 @@ "label": "manager", "offset": 2, "slot": "0", - "type": "t_contract(ICasimirManager)17376", + "type": "t_contract(ICasimirManager)22635", "contract": "CasimirViews", "src": "src/v1/CasimirViews.sol:18" }, @@ -1297,7 +1297,7 @@ "label": "bool", "numberOfBytes": "1" }, - "t_contract(ICasimirManager)17376": { + "t_contract(ICasimirManager)22635": { "label": "contract ICasimirManager", "numberOfBytes": "20" }, @@ -8381,7 +8381,7 @@ "label": "factory", "offset": 1, "slot": "153", - "type": "t_contract(ICasimirFactory)16870", + "type": "t_contract(ICasimirFactory)16502", "contract": "CasimirUpkeep", "src": "src/v1/CasimirUpkeep.sol:28" }, @@ -8389,7 +8389,7 @@ "label": "manager", "offset": 0, "slot": "154", - "type": "t_contract(ICasimirManager)17376", + "type": "t_contract(ICasimirManager)17008", "contract": "CasimirUpkeep", "src": "src/v1/CasimirUpkeep.sol:30" }, @@ -8405,7 +8405,7 @@ "label": "reportStatus", "offset": 0, "slot": "156", - "type": "t_enum(ReportStatus)17630", + "type": "t_enum(ReportStatus)17262", "contract": "CasimirUpkeep", "src": "src/v1/CasimirUpkeep.sol:34" }, @@ -8501,7 +8501,7 @@ "label": "reportRequests", "offset": 0, "slot": "167", - "type": "t_mapping(t_bytes32,t_enum(RequestType)17626)", + "type": "t_mapping(t_bytes32,t_enum(RequestType)17258)", "contract": "CasimirUpkeep", "src": "src/v1/CasimirUpkeep.sol:58" }, @@ -8611,15 +8611,15 @@ "label": "contract FunctionsOracleInterface", "numberOfBytes": "20" }, - "t_contract(ICasimirFactory)16870": { + "t_contract(ICasimirFactory)16502": { "label": "contract ICasimirFactory", "numberOfBytes": "20" }, - "t_contract(ICasimirManager)17376": { + "t_contract(ICasimirManager)17008": { "label": "contract ICasimirManager", "numberOfBytes": "20" }, - "t_enum(ReportStatus)17630": { + "t_enum(ReportStatus)17262": { "label": "enum ICasimirUpkeep.ReportStatus", "members": [ "FINALIZED", @@ -8628,7 +8628,7 @@ ], "numberOfBytes": "1" }, - "t_enum(RequestType)17626": { + "t_enum(RequestType)17258": { "label": "enum ICasimirUpkeep.RequestType", "members": [ "NONE", @@ -8641,7 +8641,7 @@ "label": "mapping(bytes32 => address)", "numberOfBytes": "32" }, - "t_mapping(t_bytes32,t_enum(RequestType)17626)": { + "t_mapping(t_bytes32,t_enum(RequestType)17258)": { "label": "mapping(bytes32 => enum ICasimirUpkeep.RequestType)", "numberOfBytes": "32" }, @@ -8664,6 +8664,71 @@ }, "namespaces": {} } + }, + "6868a6a004c4a8969dacefe322261ee2840474d89ff2ddbaf9c639ca47e9930e": { + "address": "0x3EC4AE0ce514C8CCbF4244B2584a500DDD9773b0", + "txHash": "0x3447d7b6e51d8514c263747a69e1e1f3a403eb6dbee36e2aa4e6ea72c3e603db", + "layout": { + "solcVersion": "0.8.18", + "storage": [ + { + "label": "_initialized", + "offset": 0, + "slot": "0", + "type": "t_uint8", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:63", + "retypedFrom": "bool" + }, + { + "label": "_initializing", + "offset": 1, + "slot": "0", + "type": "t_bool", + "contract": "Initializable", + "src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:68" + }, + { + "label": "manager", + "offset": 2, + "slot": "0", + "type": "t_contract(ICasimirManager)17008", + "contract": "CasimirViews", + "src": "src/v1/CasimirViews.sol:18" + }, + { + "label": "__gap", + "offset": 0, + "slot": "1", + "type": "t_array(t_uint256)50_storage", + "contract": "CasimirViews", + "src": "src/v1/CasimirViews.sol:20" + } + ], + "types": { + "t_array(t_uint256)50_storage": { + "label": "uint256[50]", + "numberOfBytes": "1600" + }, + "t_bool": { + "label": "bool", + "numberOfBytes": "1" + }, + "t_contract(ICasimirManager)17008": { + "label": "contract ICasimirManager", + "numberOfBytes": "20" + }, + "t_uint256": { + "label": "uint256", + "numberOfBytes": "32" + }, + "t_uint8": { + "label": "uint8", + "numberOfBytes": "1" + } + }, + "namespaces": {} + } } } } diff --git a/contracts/ethereum/helpers/network.ts b/contracts/ethereum/helpers/network.ts new file mode 100644 index 000000000..a618ff4bf --- /dev/null +++ b/contracts/ethereum/helpers/network.ts @@ -0,0 +1,14 @@ +import { ethers } from 'ethers' + +export async function waitForNetwork(provider: ethers.providers.JsonRpcProvider) { + let networkReady = false + while (!networkReady) { + try { + await provider.getBlockNumber() + networkReady = true + } catch (error) { + console.log('Waiting for network to start...') + await new Promise(resolve => setTimeout(resolve, 1000)) + } + } +} \ No newline at end of file diff --git a/contracts/ethereum/package.json b/contracts/ethereum/package.json index b59b1c6af..a517009e4 100644 --- a/contracts/ethereum/package.json +++ b/contracts/ethereum/package.json @@ -7,7 +7,7 @@ "deploy": "npx hardhat run scripts/deploy.ts", "dev": "npx hardhat run scripts/dev.ts", "docgen": "npx hardhat docgen", - "node": "npx hardhat node", + "predev": "npx hardhat node", "report": "npx hardhat run scripts/report.ts", "size": "npx hardhat size-contracts", "test": "npx esno -r dotenv/config -r scripts/test.ts", diff --git a/contracts/ethereum/scripts/dev.ts b/contracts/ethereum/scripts/dev.ts index 7e9e92ffd..7a0a95c21 100644 --- a/contracts/ethereum/scripts/dev.ts +++ b/contracts/ethereum/scripts/dev.ts @@ -1,26 +1,22 @@ -import { CasimirFactoryDev, CasimirManagerDev, CasimirRegistry, CasimirUpkeepDev, CasimirViews, FunctionsOracle, FunctionsBillingRegistry } from '../build/@types' -import ICasimirManagerDevAbi from '../build/abi/ICasimirManagerDev.json' -import ICasimirRegistryAbi from '../build/abi/ICasimirRegistry.json' -import ICasimirUpkeepDevAbi from '../build/abi/ICasimirUpkeepDev.json' -import ICasimirViewsAbi from '../build/abi/ICasimirViews.json' -import FunctionsBillingRegistryAbi from '../build/abi/FunctionsBillingRegistry.json' -import FunctionsOracleAbi from '../build/abi/FunctionsOracle.json' +import { CasimirFactoryDev, CasimirManagerDev, CasimirRegistryDev, CasimirUpkeepDev, CasimirViewsDev, FunctionsOracle, FunctionsBillingRegistry } from '../build/@types' import { ethers, upgrades } from 'hardhat' -import { fulfillReport, runUpkeep } from '@casimir/ethereum/helpers/upkeep' -import { round } from '@casimir/ethereum/helpers/math' import { time, setBalance } from '@nomicfoundation/hardhat-network-helpers' import { PoolStatus } from '@casimir/types' import requestConfig from '@casimir/functions/Functions-request-config' -import { activatePoolsHandler } from '../helpers/oracle' import { run } from '@casimir/shell' - -upgrades.silenceWarnings() +import { round } from '../helpers/math' +import { waitForNetwork } from '../helpers/network' +import { activatePoolsHandler } from '../helpers/oracle' +import { fulfillReport, runUpkeep } from '../helpers/upkeep' /** * Fork contracts to local network and run local events and oracle handling + * You can override the following configuration environment variables: + * - SIMULATE_UPGRADES: true | false + * - SIMULATE_EIGEN: true | false + * - SIMULATE_REWARDS: true | false */ async function dev() { - if (!process.env.ETHEREUM_RPC_URL) throw new Error('No ethereum rpc url provided') if (!process.env.FACTORY_ADDRESS) throw new Error('No factory address provided') if (!process.env.MANAGER_BEACON_ADDRESS) throw new Error('No manager beacon address provided') if (!process.env.POOL_BEACON_ADDRESS) throw new Error('No pool beacon address provided') @@ -41,117 +37,143 @@ async function dev() { if (!process.env.SWAP_ROUTER_ADDRESS) throw new Error('No swap router address provided') if (!process.env.WETH_TOKEN_ADDRESS) throw new Error('No weth token address provided') + await waitForNetwork(ethers.provider) + const [owner, daoOracle, donTransmitter] = await ethers.getSigners() - const functionsOracle = new ethers.Contract(process.env.FUNCTIONS_ORACLE_ADDRESS, FunctionsOracleAbi, ethers.provider) as FunctionsOracle - const functionsBillingRegistry = new ethers.Contract(process.env.FUNCTIONS_BILLING_REGISTRY_ADDRESS, FunctionsBillingRegistryAbi, ethers.provider) as FunctionsBillingRegistry - - const factoryDevFactory = await ethers.getContractFactory('CasimirFactoryDev', { - libraries: { - CasimirBeacon: process.env.BEACON_LIBRARY_ADDRESS - } - }) - const factory = await upgrades.upgradeProxy(process.env.FACTORY_ADDRESS, factoryDevFactory, { - constructorArgs: [ - process.env.MANAGER_BEACON_ADDRESS, - process.env.POOL_BEACON_ADDRESS, - process.env.REGISTRY_BEACON_ADDRESS, - process.env.UPKEEP_BEACON_ADDRESS, - process.env.VIEWS_BEACON_ADDRESS - ], - unsafeAllow: ['external-library-linking'] - }) as CasimirFactoryDev - await factory.deployed() + const functionsOracle = await ethers.getContractAt('FunctionsOracle', process.env.FUNCTIONS_ORACLE_ADDRESS) as FunctionsOracle + const functionsBillingRegistry = await ethers.getContractAt('FunctionsBillingRegistry', process.env.FUNCTIONS_BILLING_REGISTRY_ADDRESS) as FunctionsBillingRegistry + + /** + * Deploy in-development contract upgrades + * Helpful also for adding hardhat/console to debug existing methods + */ + if (process.env.SIMULATE_UPGRADES === 'true') { + upgrades.silenceWarnings() + + const factoryProxyFactory = await ethers.getContractFactory('CasimirFactoryDev', { + libraries: { + CasimirBeacon: process.env.BEACON_LIBRARY_ADDRESS + } + }) + const factoryProxy = await upgrades.upgradeProxy(process.env.FACTORY_ADDRESS, factoryProxyFactory, { + constructorArgs: [ + process.env.MANAGER_BEACON_ADDRESS, + process.env.POOL_BEACON_ADDRESS, + process.env.REGISTRY_BEACON_ADDRESS, + process.env.UPKEEP_BEACON_ADDRESS, + process.env.VIEWS_BEACON_ADDRESS + ], + unsafeAllow: ['external-library-linking'] + }) + await factoryProxy.deployed() + + const managerBeaconFactory = await ethers.getContractFactory('CasimirManagerDev', { + libraries: { + CasimirBeacon: process.env.BEACON_LIBRARY_ADDRESS + } + }) + const managerBeacon = await upgrades.upgradeBeacon(process.env.MANAGER_BEACON_ADDRESS, managerBeaconFactory, { + constructorArgs: [ + process.env.FUNCTIONS_BILLING_REGISTRY_ADDRESS, + process.env.KEEPER_REGISTRAR_ADDRESS, + process.env.KEEPER_REGISTRY_ADDRESS, + process.env.LINK_TOKEN_ADDRESS, + process.env.SSV_NETWORK_ADDRESS, + process.env.SSV_TOKEN_ADDRESS, + process.env.SWAP_FACTORY_ADDRESS, + process.env.SWAP_ROUTER_ADDRESS, + process.env.WETH_TOKEN_ADDRESS + ], + unsafeAllow: ['external-library-linking'] + }) + await managerBeacon.deployed() + + const registryBeaconFactory = await ethers.getContractFactory('CasimirRegistryDev') + const registryBeacon = await upgrades.upgradeBeacon(process.env.REGISTRY_BEACON_ADDRESS, registryBeaconFactory, { + constructorArgs: [ + process.env.SSV_VIEWS_ADDRESS + ] + }) + await registryBeacon.deployed() + + const upkeepBeaconFactory = await ethers.getContractFactory('CasimirUpkeepDev') + const upkeepBeacon = await upgrades.upgradeBeacon(process.env.UPKEEP_BEACON_ADDRESS, upkeepBeaconFactory) + await upkeepBeacon.deployed() + + const viewsBeaconFactory = await ethers.getContractFactory('CasimirViewsDev') + const viewsBeacon = await upgrades.upgradeBeacon(process.env.VIEWS_BEACON_ADDRESS, viewsBeaconFactory) + await viewsBeacon.deployed() + } + const factory = await ethers.getContractAt('CasimirFactoryDev', process.env.FACTORY_ADDRESS) as CasimirFactoryDev console.log(`Casimir factory ${factory.address}`) const [managerId] = await factory.getManagerIds() const managerConfig = await factory.getManagerConfig(managerId) - const managerDevFactory = await ethers.getContractFactory('CasimirManagerDev', { - libraries: { - CasimirBeacon: process.env.BEACON_LIBRARY_ADDRESS - } - }) - const managerBeacon = await upgrades.upgradeBeacon(process.env.MANAGER_BEACON_ADDRESS, managerDevFactory, { - constructorArgs: [ - process.env.FUNCTIONS_BILLING_REGISTRY_ADDRESS, - process.env.KEEPER_REGISTRAR_ADDRESS, - process.env.KEEPER_REGISTRY_ADDRESS, - process.env.LINK_TOKEN_ADDRESS, - process.env.SSV_NETWORK_ADDRESS, - process.env.SSV_TOKEN_ADDRESS, - process.env.SWAP_FACTORY_ADDRESS, - process.env.SWAP_ROUTER_ADDRESS, - process.env.WETH_TOKEN_ADDRESS - ], - unsafeAllow: ['external-library-linking'] - }) - await managerBeacon.deployed() - - const manager = new ethers.Contract(managerConfig.managerAddress, ICasimirManagerDevAbi, ethers.provider) as CasimirManagerDev + + const manager = await ethers.getContractAt('CasimirManagerDev', managerConfig.managerAddress) as CasimirManagerDev console.log(`Casimir simple manager ${manager.address}`) - const registry = new ethers.Contract(managerConfig.registryAddress, ICasimirRegistryAbi, ethers.provider) as CasimirRegistry + const registry = await ethers.getContractAt('CasimirRegistryDev', managerConfig.registryAddress) as CasimirRegistryDev console.log(`Casimir simple registry ${registry.address}`) - const upkeepDevFactory = await ethers.getContractFactory('CasimirUpkeepDev') - const upkeepBeacon = await upgrades.upgradeBeacon(process.env.UPKEEP_BEACON_ADDRESS, upkeepDevFactory) - await upkeepBeacon.deployed() - - const upkeep = new ethers.Contract(managerConfig.upkeepAddress, ICasimirUpkeepDevAbi, ethers.provider) as CasimirUpkeepDev + const upkeep = await ethers.getContractAt('CasimirUpkeepDev', managerConfig.upkeepAddress) as CasimirUpkeepDev console.log(`Casimir simple upkeep ${upkeep.address}`) - const views = new ethers.Contract(managerConfig.viewsAddress, ICasimirViewsAbi, ethers.provider) as CasimirViews + const views = await ethers.getContractAt('CasimirViewsDev', managerConfig.viewsAddress) as CasimirViewsDev console.log(`Casimir simple views ${views.address}`) + /** * Deploy a second operator groups with Casimir Eigen stake enabled * Note, this operator group is not functional it only deployed for testing purposes */ - const deployEigenManager = await factory.connect(owner).deployManager( - daoOracle.address, - functionsOracle.address, - { - minCollateral: ethers.utils.parseEther('1.0'), - lockPeriod: ethers.BigNumber.from('0'), - userFee: ethers.BigNumber.from('5'), - compoundStake: true, - eigenStake: true, - liquidStake: false, - privateOperators: false, - verifiedOperators: false - } - ) - await deployEigenManager.wait() - const [, eigenManagerId] = await factory.getManagerIds() - const eigenManagerConfig = await factory.getManagerConfig(eigenManagerId) - - const eigenManager = new ethers.Contract(eigenManagerConfig.managerAddress, ICasimirManagerDevAbi, ethers.provider) as CasimirManagerDev - console.log(`Casimir Eigen manager ${eigenManager.address}`) - const eigenRegistry = new ethers.Contract(eigenManagerConfig.registryAddress, ICasimirRegistryAbi, ethers.provider) as CasimirRegistry - console.log(`Casimir Eigen registry ${eigenRegistry.address}`) - const eigenUpkeep = new ethers.Contract(eigenManagerConfig.upkeepAddress, ICasimirUpkeepDevAbi, ethers.provider) as CasimirUpkeepDev - console.log(`Casimir Eigen upkeep ${eigenUpkeep.address}`) - const eigenViews = new ethers.Contract(eigenManagerConfig.viewsAddress, ICasimirViewsAbi, ethers.provider) as CasimirViews - console.log(`Casimir Eigen views ${eigenViews.address}`) - - requestConfig.args[1] = eigenViews.address - await (await eigenUpkeep.connect(owner).setFunctionsRequest( - requestConfig.source, requestConfig.args, 300000 - )).wait() - - const functionsOracleSenders = await functionsOracle.getAuthorizedSenders() - const newFunctionsOracleSenders = [...functionsOracleSenders, eigenManager.address, eigenUpkeep.address] - await functionsOracle.connect(owner).setAuthorizedSenders(newFunctionsOracleSenders) + if (process.env.SIMULATE_EIGEN === 'true') { + const deployEigenManager = await factory.connect(owner).deployManager( + daoOracle.address, + functionsOracle.address, + { + minCollateral: ethers.utils.parseEther('1.0'), + lockPeriod: ethers.BigNumber.from('0'), + userFee: ethers.BigNumber.from('5'), + compoundStake: true, + eigenStake: true, + liquidStake: false, + privateOperators: false, + verifiedOperators: false + } + ) + await deployEigenManager.wait() + const [, eigenManagerId] = await factory.getManagerIds() + const eigenManagerConfig = await factory.getManagerConfig(eigenManagerId) + + const eigenManager = await ethers.getContractAt('CasimirManagerDev', eigenManagerConfig.managerAddress) as CasimirManagerDev + console.log(`Casimir Eigen manager ${eigenManager.address}`) + const eigenRegistry = await ethers.getContractAt('CasimirRegistryDev', eigenManagerConfig.registryAddress) as CasimirRegistryDev + console.log(`Casimir Eigen registry ${eigenRegistry.address}`) + const eigenUpkeep = await ethers.getContractAt('CasimirUpkeepDev', eigenManagerConfig.upkeepAddress) as CasimirUpkeepDev + console.log(`Casimir Eigen upkeep ${eigenUpkeep.address}`) + const eigenViews = await ethers.getContractAt('CasimirViewsDev', eigenManagerConfig.viewsAddress) as CasimirViewsDev + console.log(`Casimir Eigen views ${eigenViews.address}`) + + requestConfig.args[1] = eigenViews.address + await (await eigenUpkeep.connect(owner).setFunctionsRequest( + requestConfig.source, requestConfig.args, 300000 + )).wait() + + const functionsOracleSenders = await functionsOracle.getAuthorizedSenders() + const newFunctionsOracleSenders = [...functionsOracleSenders, eigenManager.address, eigenUpkeep.address] + await functionsOracle.connect(owner).setAuthorizedSenders(newFunctionsOracleSenders) + } /** - * We are simulating the oracle reporting on a more frequent basis - * We also do not sweep or compound the rewards in this script + * Simulate oracle reporting on a higher than average rate * Exit balances are swept as needed */ - const blocksPerReport = 10 - const rewardPerValidator = 0.105 - let lastReportBlock = await ethers.provider.getBlockNumber() - let lastStakedPoolIds: number[] = [] - void function () { + if (process.env.SIMULATE_REWARDS === 'true') { + const blocksPerReport = 10 + const rewardPerValidator = 0.105 + let lastReportBlock = await ethers.provider.getBlockNumber() + let lastStakedPoolIds: number[] = [] ethers.provider.on('block', async (block) => { if (block - blocksPerReport >= lastReportBlock) { await time.increase(time.duration.days(1)) @@ -222,7 +244,7 @@ async function dev() { lastStakedPoolIds = stakedPoolIds } }) - }() + } run('npm run dev --workspace @casimir/oracle') } diff --git a/contracts/ethereum/scripts/functions.ts b/contracts/ethereum/scripts/functions.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/contracts/ethereum/scripts/report.ts b/contracts/ethereum/scripts/report.ts index f9b13aa32..b09d1cfd9 100644 --- a/contracts/ethereum/scripts/report.ts +++ b/contracts/ethereum/scripts/report.ts @@ -1,30 +1,45 @@ import { ethers } from 'hardhat' -import { CasimirFactory, CasimirManager, CasimirRegistry, CasimirUpkeep, CasimirViews } from '../build/@types' -import ICasimirFactoryAbi from '../build/abi/ICasimirFactory.json' -import ICasimirManagerAbi from '../build/abi/ICasimirManager.json' -import ICasimirRegistryAbi from '../build/abi/ICasimirRegistry.json' -import ICasimirUpkeepAbi from '../build/abi/ICasimirUpkeep.json' -import ICasimirViewsAbi from '../build/abi/ICasimirViews.json' -import { ETHEREUM_CONTRACTS, ETHEREUM_RPC_URL } from '@casimir/env' -import { run } from '@casimir/shell' +import { CasimirFactory, CasimirManager, CasimirPool, CasimirRegistry, CasimirUpkeep, CasimirViews } from '../build/@types' +import { waitForNetwork } from '../helpers/network' +import { runUpkeep } from '../helpers/upkeep' +import e from 'cors' void async function() { - process.env.ETHEREUM_RPC_URL = ETHEREUM_RPC_URL['TESTNET'] - process.env.FUNCTIONS_BILLING_REGISTRY_ADDRESS = ETHEREUM_CONTRACTS['TESTNET'].FUNCTIONS_BILLING_REGISTRY_ADDRESS - process.env.FUNCTIONS_ORACLE_ADDRESS = ETHEREUM_CONTRACTS['TESTNET'].FUNCTIONS_ORACLE_ADDRESS - const [owner] = await ethers.getSigners() - const provider = new ethers.providers.JsonRpcProvider(ETHEREUM_RPC_URL['TESTNET']) - const factory = new ethers.Contract(ETHEREUM_CONTRACTS['TESTNET'].FACTORY_ADDRESS, ICasimirFactoryAbi, provider) as CasimirFactory + if (!process.env.FACTORY_ADDRESS) throw new Error('No factory address provided') + + await waitForNetwork(ethers.provider) + + const [owner, , donTransmitter] = await ethers.getSigners() + const factory = await ethers.getContractAt('CasimirFactory', process.env.FACTORY_ADDRESS) as CasimirFactory const [managerId] = await factory.getManagerIds() const managerConfig = await factory.getManagerConfig(managerId) - const manager = new ethers.Contract(managerConfig.managerAddress, ICasimirManagerAbi, provider) as CasimirManager - // const registry = new ethers.Contract(managerConfig.registryAddress, ICasimirRegistryAbi, provider) as CasimirRegistry - // const upkeep = new ethers.Contract(managerConfig.upkeepAddress, ICasimirUpkeepAbi, provider) as CasimirUpkeep - // const views = new ethers.Contract(managerConfig.viewsAddress, ICasimirViewsAbi, provider) as CasimirViews + const manager = await ethers.getContractAt('CasimirManager', managerConfig.managerAddress) as CasimirManager + const registry = await ethers.getContractAt('CasimirRegistry', managerConfig.registryAddress) as CasimirRegistry + const upkeep = await ethers.getContractAt('CasimirUpkeep', managerConfig.upkeepAddress) as CasimirUpkeep + const views = await ethers.getContractAt('CasimirViews', managerConfig.viewsAddress) as CasimirViews + + // const pause = await manager.connect(owner).setPaused(false) + // await pause.wait() + const requestReport = await upkeep.connect(owner).requestReport() + await requestReport.wait() - const pause = await manager.connect(owner).setPaused(false) - await pause.wait() - // const requestReport = await upkeep.connect(owner).requestReport() - // await requestReport.wait() - // run('npm run dev --workspace @casimir/functions') + if (process.env.SIMULATE_UPKEEP === 'true') { + await runUpkeep({ donTransmitter, upkeep }) + let finalizedReport = false + while (!finalizedReport) { + finalizedReport = await runUpkeep({ donTransmitter, upkeep }) + await new Promise(resolve => setTimeout(resolve, 2500)) + } + const poolIds = await manager.getStakedPoolIds() + const sweptBalance = await views.getSweptBalance(0, poolIds.length) + console.log('Swept balance', ethers.utils.formatEther(sweptBalance)) + let totalBalance = ethers.BigNumber.from(0) + for (const poolId of poolIds) { + const poolAddress = await manager.getPoolAddress(poolId) + const poolBalance = await ethers.provider.getBalance(poolAddress) + totalBalance = totalBalance.add(poolBalance) + console.log('Pool', poolId, ethers.utils.formatEther(poolBalance)) + } + console.log('Total balance', ethers.utils.formatEther(totalBalance)) + } }() \ No newline at end of file diff --git a/contracts/ethereum/scripts/test.ts b/contracts/ethereum/scripts/test.ts index 924fb05de..25355e492 100644 --- a/contracts/ethereum/scripts/test.ts +++ b/contracts/ethereum/scripts/test.ts @@ -24,7 +24,6 @@ async function test() { console.log(`Using ${networkName} fork from ${process.env.ETHEREUM_FORK_RPC_URL}`) const provider = new ethers.providers.JsonRpcProvider(process.env.ETHEREUM_FORK_RPC_URL) - const wallet = ethers.Wallet.fromMnemonic(process.env.BIP39_SEED) // Account for the mock, beacon, and library deployments diff --git a/contracts/ethereum/src/v1/CasimirViews.sol b/contracts/ethereum/src/v1/CasimirViews.sol index 9c5f19d20..a060e52d1 100644 --- a/contracts/ethereum/src/v1/CasimirViews.sol +++ b/contracts/ethereum/src/v1/CasimirViews.sol @@ -141,7 +141,7 @@ contract CasimirViews is ICasimirViews, Initializable { function getSweptBalance(uint256 startIndex, uint256 endIndex) external view returns (uint128 sweptBalance) { uint32[] memory pendingPoolIds = manager.getPendingPoolIds(); uint32[] memory stakedPoolIds = manager.getStakedPoolIds(); - for (uint256 i = startIndex; i <= endIndex; i++) { + for (uint256 i = startIndex; i < endIndex; i++) { uint32 poolId; if (i < pendingPoolIds.length) { poolId = pendingPoolIds[i]; diff --git a/contracts/ethereum/src/v1/dev/CasimirRegistryDev.sol b/contracts/ethereum/src/v1/dev/CasimirRegistryDev.sol new file mode 100644 index 000000000..03b7f07f6 --- /dev/null +++ b/contracts/ethereum/src/v1/dev/CasimirRegistryDev.sol @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: Apache +pragma solidity 0.8.18; + +import "../CasimirCore.sol"; +import "./interfaces/ICasimirRegistryDev.sol"; +import "../interfaces/ICasimirManager.sol"; +import "../vendor/interfaces/ISSVViews.sol"; +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import "@openzeppelin/contracts-upgradeable/security/ReentrancyGuardUpgradeable.sol"; + +/** + * @title Registry for pool operators + */ +contract CasimirRegistryDev is + ICasimirRegistryDev, + CasimirCore, + Initializable, + OwnableUpgradeable, + ReentrancyGuardUpgradeable +{ + /// @inheritdoc ICasimirRegistryDev + uint256 public minCollateral; + /// @inheritdoc ICasimirRegistryDev + bool public privateOperators; + /// @inheritdoc ICasimirRegistryDev + bool public verifiedOperators; + /** + * @dev SSV views contract + * @custom:oz-upgrades-unsafe-allow state-variable-immutable + */ + ISSVViews private immutable ssvViews; + /// @dev Manager contract + ICasimirManager private manager; + /// @dev Previously registered operator IDs + uint64[] private operatorIds; + /// @dev Operators by ID + mapping(uint64 => Operator) private operators; + /// @dev Operator pools by operator ID and pool ID + mapping(uint64 => mapping(uint32 => bool)) private operatorPools; + /// @dev Storage gap + uint256[50] private __gap; + + /** + * @dev Constructor + * @param ssvViews_ SSV views contract + * @custom:oz-upgrades-unsafe-allow constructor + */ + constructor(ISSVViews ssvViews_) { + onlyAddress(address(ssvViews_)); + ssvViews = ssvViews_; + _disableInitializers(); + } + + /** + * @notice Initialize the contract + * @param minCollateral_ Minimum collateral per operator per pool + * @param privateOperators_ Whether private operators are enabled + * @param verifiedOperators_ Whether verified operators are enabled + */ + function initialize(uint256 minCollateral_, bool privateOperators_, bool verifiedOperators_) public initializer { + __Ownable_init(); + __ReentrancyGuard_init(); + manager = ICasimirManager(msg.sender); + minCollateral = minCollateral_; + privateOperators = privateOperators_; + verifiedOperators = verifiedOperators_; + } + + /// @inheritdoc ICasimirRegistryDev + function registerOperator(uint64 operatorId) external payable { + onlyOperatorOwner(operatorId); + Operator storage operator = operators[operatorId]; + if (operator.id != 0) { + revert OperatorAlreadyRegistered(); + } + operatorIds.push(operatorId); + operator.id = operatorId; + operator.active = true; + operator.collateral = msg.value; + emit OperatorRegistered(operatorId); + } + + /// @inheritdoc ICasimirRegistryDev + function depositCollateral(uint64 operatorId) external payable { + onlyOperatorOwner(operatorId); + Operator storage operator = operators[operatorId]; + operator.collateral += msg.value; + operator.active = true; + emit CollateralDeposited(operatorId, msg.value); + } + + /// @inheritdoc ICasimirRegistryDev + function requestWithdrawal(uint64 operatorId, uint256 amount) external { + onlyOperatorOwner(operatorId); + Operator storage operator = operators[operatorId]; + if (operator.active || operator.resharing) { + revert CollateralInUse(); + } + if (operator.collateral < amount) { + revert InvalidAmount(); + } + operator.collateral -= amount; + (bool success, ) = msg.sender.call{value: amount}(""); + if (!success) { + revert TransferFailed(); + } + emit WithdrawalFulfilled(operatorId, amount); + } + + /// @inheritdoc ICasimirRegistryDev + function requestDeactivation(uint64 operatorId) external { + onlyOperatorOwner(operatorId); + Operator storage operator = operators[operatorId]; + if (!operator.active) { + revert OperatorNotActive(); + } + if (operator.resharing) { + revert OperatorResharing(); + } + if (operator.poolCount == 0) { + operator.active = false; + emit DeactivationCompleted(operatorId); + } else { + operator.resharing = true; + emit DeactivationRequested(operatorId); + } + } + + /// @inheritdoc ICasimirRegistryDev + function addOperatorPool(uint64 operatorId, uint32 poolId) external onlyOwner { + Operator storage operator = operators[operatorId]; + if (!operator.active) { + revert OperatorNotActive(); + } + if (operator.resharing) { + revert OperatorResharing(); + } + if (operatorPools[operatorId][poolId]) { + revert PoolAlreadyExists(); + } + uint256 eligiblePools = (operator.collateral / minCollateral) - operator.poolCount; + if (eligiblePools == 0) { + revert InsufficientCollateral(); + } + operatorPools[operatorId][poolId] = true; + operator.poolCount += 1; + emit OperatorPoolAdded(operatorId, poolId); + } + + /// @inheritdoc ICasimirRegistryDev + function removeOperatorPool(uint64 operatorId, uint32 poolId, uint256 blameAmount) external { + onlyOwnerOrPool(poolId); + Operator storage operator = operators[operatorId]; + if (!operatorPools[operatorId][poolId]) { + revert PoolDoesNotExist(); + } + if (blameAmount > minCollateral) { + revert InvalidAmount(); + } + operatorPools[operatorId][poolId] = false; + operator.poolCount -= 1; + if (operator.poolCount == 0 && operator.resharing) { + operator.active = false; + operator.resharing = false; + emit DeactivationCompleted(operatorId); + } + if (blameAmount > 0) { + operator.collateral -= blameAmount; + manager.depositRecoveredBalance{value: blameAmount}(poolId); + } + emit OperatorPoolRemoved(operatorId, poolId, blameAmount); + } + + /// @inheritdoc ICasimirRegistryDev + function getOperator(uint64 operatorId) external view returns (Operator memory operator) { + operator = operators[operatorId]; + } + + /// @inheritdoc ICasimirRegistryDev + function getOperatorIds() external view returns (uint64[] memory) { + return operatorIds; + } + + /// @dev Validate the caller is the owner of the operator + function onlyOperatorOwner(uint64 operatorId) private view { + (address operatorOwner, , , , , ) = ssvViews.getOperatorById(operatorId); + if (msg.sender != operatorOwner) { + revert Unauthorized(); + } + } + + /// @dev Validate the caller is the owner or the authorized pool + function onlyOwnerOrPool(uint32 poolId) private view { + if (msg.sender != owner() && msg.sender != manager.getPoolAddress(poolId)) { + revert Unauthorized(); + } + } +} diff --git a/contracts/ethereum/src/v1/dev/CasimirViewsDev.sol b/contracts/ethereum/src/v1/dev/CasimirViewsDev.sol new file mode 100644 index 000000000..f58d7e1d4 --- /dev/null +++ b/contracts/ethereum/src/v1/dev/CasimirViewsDev.sol @@ -0,0 +1,154 @@ +// SPDX-License-Identifier: Apache +pragma solidity 0.8.18; + +import "../interfaces/ICasimirManager.sol"; +import "../interfaces/ICasimirPool.sol"; +import "../interfaces/ICasimirRegistry.sol"; +import "../interfaces/ICasimirUpkeep.sol"; +import "./interfaces/ICasimirViewsDev.sol"; +import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; + +/** + * @title Views contract that provides read-only access to the state + */ +contract CasimirViewsDev is ICasimirViewsDev, Initializable { + /// @dev Compound minimum (0.1 ETH) + uint256 private constant COMPOUND_MINIMUM = 100000000 gwei; + /// @dev Manager contract + ICasimirManager private manager; + /// @dev Storage gap + uint256[50] private __gap; + + /** + * @dev Constructor + * @custom:oz-upgrades-unsafe-allow constructor + */ + constructor() { + _disableInitializers(); + } + + /** + * @notice Initialize the contract + * @param managerAddress Manager address + */ + function initialize(address managerAddress) public initializer { + manager = ICasimirManager(managerAddress); + } + + /// @inheritdoc ICasimirViewsDev + function getCompoundablePoolIds( + uint256 startIndex, + uint256 endIndex + ) external view returns (uint32[5] memory compoundablePoolIds) { + uint32[] memory pendingPoolIds = manager.getPendingPoolIds(); + uint32[] memory stakedPoolIds = manager.getStakedPoolIds(); + uint256 count = 0; + for (uint256 i = startIndex; i < endIndex; i++) { + uint32 poolId; + if (i < pendingPoolIds.length) { + poolId = pendingPoolIds[i]; + } else { + poolId = stakedPoolIds[i - pendingPoolIds.length]; + } + if (manager.getPoolAddress(poolId).balance >= COMPOUND_MINIMUM) { + compoundablePoolIds[count] = poolId; + count++; + if (count == 5) { + break; + } + } + } + } + + /// @inheritdoc ICasimirViewsDev + function getDepositedPoolCount() external view returns (uint256 depositedPoolCount) { + depositedPoolCount = manager.getPendingPoolIds().length + manager.getStakedPoolIds().length; + } + + /// @inheritdoc ICasimirViewsDev + function getDepositedPoolPublicKeys(uint256 startIndex, uint256 endIndex) external view returns (bytes[] memory) { + bytes[] memory publicKeys = new bytes[](endIndex - startIndex); + uint32[] memory pendingPoolIds = manager.getPendingPoolIds(); + uint32[] memory stakedPoolIds = manager.getStakedPoolIds(); + uint256 count = 0; + for (uint256 i = startIndex; i < endIndex; i++) { + uint32 poolId; + if (i < pendingPoolIds.length) { + poolId = pendingPoolIds[i]; + } else { + poolId = stakedPoolIds[i - pendingPoolIds.length]; + } + publicKeys[count] = ICasimirPool(manager.getPoolAddress(poolId)).publicKey(); + count++; + } + return publicKeys; + } + + /// @inheritdoc ICasimirViewsDev + function getDepositedPoolStatuses( + uint256 startIndex, + uint256 endIndex + ) external view returns (ICasimirPool.PoolStatus[] memory) { + ICasimirPool.PoolStatus[] memory statuses = new ICasimirPool.PoolStatus[](endIndex - startIndex); + uint32[] memory pendingPoolIds = manager.getPendingPoolIds(); + uint32[] memory stakedPoolIds = manager.getStakedPoolIds(); + uint256 count = 0; + for (uint256 i = startIndex; i < endIndex; i++) { + uint32 poolId; + if (i < pendingPoolIds.length) { + poolId = pendingPoolIds[i]; + } else { + poolId = stakedPoolIds[i - pendingPoolIds.length]; + } + statuses[count] = ICasimirPool(manager.getPoolAddress(poolId)).status(); + count++; + } + return statuses; + } + + /// @inheritdoc ICasimirViewsDev + function getOperators( + uint256 startIndex, + uint256 endIndex + ) external view returns (ICasimirRegistry.Operator[] memory) { + ICasimirRegistry.Operator[] memory operators = new ICasimirRegistry.Operator[](endIndex - startIndex); + ICasimirRegistry registry = ICasimirRegistry(manager.getRegistryAddress()); + uint64[] memory operatorIds = registry.getOperatorIds(); + uint256 count = 0; + for (uint256 i = startIndex; i < endIndex; i++) { + uint64 operatorId = operatorIds[i]; + operators[count] = registry.getOperator(operatorId); + count++; + } + return operators; + } + + /// @inheritdoc ICasimirViewsDev + function getPoolConfig(uint32 poolId) external view returns (PoolConfig memory poolConfig) { + address poolAddress = manager.getPoolAddress(poolId); + ICasimirPool pool = ICasimirPool(poolAddress); + poolConfig = PoolConfig({ + poolAddress: poolAddress, + balance: poolAddress.balance, + operatorIds: pool.getOperatorIds(), + publicKey: pool.publicKey(), + reshares: pool.reshares(), + status: pool.status() + }); + } + + /// @inheritdoc ICasimirViewsDev + function getSweptBalance(uint256 startIndex, uint256 endIndex) external view returns (uint128 sweptBalance) { + uint32[] memory pendingPoolIds = manager.getPendingPoolIds(); + uint32[] memory stakedPoolIds = manager.getStakedPoolIds(); + for (uint256 i = startIndex; i < endIndex; i++) { + uint32 poolId; + if (i < pendingPoolIds.length) { + poolId = pendingPoolIds[i]; + } else { + poolId = stakedPoolIds[i - pendingPoolIds.length]; + } + sweptBalance += uint128(manager.getPoolAddress(poolId).balance / 1 gwei); + } + } +} diff --git a/contracts/ethereum/src/v1/dev/interfaces/ICasimirRegistryDev.sol b/contracts/ethereum/src/v1/dev/interfaces/ICasimirRegistryDev.sol new file mode 100644 index 000000000..00bb7a83a --- /dev/null +++ b/contracts/ethereum/src/v1/dev/interfaces/ICasimirRegistryDev.sol @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: Apache +pragma solidity 0.8.18; + +import "../../interfaces/ICasimirCore.sol"; + +interface ICasimirRegistryDev is ICasimirCore { + event CollateralDeposited(uint64 indexed operatorId, uint256 amount); + event DeactivationCompleted(uint64 indexed operatorId); + event DeactivationRequested(uint64 indexed operatorId); + event DeregistrationCompleted(uint64 indexed operatorId); + event OperatorPoolAdded(uint64 indexed operatorId, uint32 poolId); + event OperatorPoolRemoved(uint64 operatorId, uint32 poolId, uint256 blameAmount); + event OperatorRegistered(uint64 indexed operatorId); + event WithdrawalFulfilled(uint64 indexed operatorId, uint256 amount); + + error CollateralInUse(); + error InsufficientCollateral(); + error OperatorAlreadyRegistered(); + error OperatorNotActive(); + error OperatorResharing(); + error PoolAlreadyExists(); + error PoolDoesNotExist(); + + /** + * @notice Register an operator + * @param operatorId Operator ID + */ + function registerOperator(uint64 operatorId) external payable; + + /** + * @notice Deposit operator collateral + * @param operatorId Operator ID + */ + function depositCollateral(uint64 operatorId) external payable; + + /** + * @notice Request to withdraw operator collateral + * @param operatorId Operator ID + * @param amount Amount to withdraw + */ + function requestWithdrawal(uint64 operatorId, uint256 amount) external; + + /** + * @notice Request operator deactivation + * @param operatorId Operator ID + */ + function requestDeactivation(uint64 operatorId) external; + + /** + * @notice Add a pool to an operator + * @param operatorId Operator ID + * @param poolId Pool ID + */ + function addOperatorPool(uint64 operatorId, uint32 poolId) external; + + /** + * @notice Remove a pool from an operator + * @param operatorId Operator ID + * @param poolId Pool ID + * @param blameAmount Amount to recover from collateral + */ + function removeOperatorPool(uint64 operatorId, uint32 poolId, uint256 blameAmount) external; + + /** + * @notice Get an operator + * @param operatorId Operator ID + */ + function getOperator(uint64 operatorId) external view returns (Operator memory); + + /// @notice Get all previously registered operator IDs + function getOperatorIds() external view returns (uint64[] memory); + + /// @notice Minimum collateral per operator per pool + function minCollateral() external view returns (uint256); + + /// @notice Whether private operators are enabled + function privateOperators() external view returns (bool); + + /// @notice Whether verified operators are enabled + function verifiedOperators() external view returns (bool); +} diff --git a/contracts/ethereum/src/v1/dev/interfaces/ICasimirViewsDev.sol b/contracts/ethereum/src/v1/dev/interfaces/ICasimirViewsDev.sol new file mode 100644 index 000000000..4a35616da --- /dev/null +++ b/contracts/ethereum/src/v1/dev/interfaces/ICasimirViewsDev.sol @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: Apache +pragma solidity 0.8.18; + +import "../../interfaces/ICasimirCore.sol"; + +interface ICasimirViewsDev is ICasimirCore { + /** + * @notice Get the next five compoundable pool IDs + * @param startIndex Start index + * @param endIndex End index + */ + function getCompoundablePoolIds(uint256 startIndex, uint256 endIndex) external view returns (uint32[5] memory); + + /// @notice Get the deposited pool count + function getDepositedPoolCount() external view returns (uint256); + + /** + * @notice Get the deposited pool public keys + * @param startIndex Start index + * @param endIndex End index + */ + function getDepositedPoolPublicKeys(uint256 startIndex, uint256 endIndex) external view returns (bytes[] memory); + + /** + * @notice Get the deposited pool statuses + * @param startIndex Start index + * @param endIndex End index + */ + function getDepositedPoolStatuses(uint256 startIndex, uint256 endIndex) external view returns (PoolStatus[] memory); + + /** + * @notice Get operators + * @param startIndex Start index + * @param endIndex End index + */ + function getOperators(uint256 startIndex, uint256 endIndex) external view returns (Operator[] memory); + + /** + * @notice Get pool config + * @param poolId Pool ID + */ + function getPoolConfig(uint32 poolId) external view returns (PoolConfig memory); + + /** + * @notice Get the swept balance (in gwei) + * @param startIndex Start index + * @param endIndex End index + */ + function getSweptBalance(uint256 startIndex, uint256 endIndex) external view returns (uint128); +} diff --git a/scripts/root/dev.ts b/scripts/root/dev.ts index 58e988108..80491d918 100644 --- a/scripts/root/dev.ts +++ b/scripts/root/dev.ts @@ -105,16 +105,15 @@ async function root() { process.env.ETHEREUM_RPC_URL = 'http://127.0.0.1:8545' console.log(`Connecting to ${networkName} network fork at ${process.env.ETHEREUM_RPC_URL}`) - const provider = new ethers.providers.JsonRpcProvider(process.env.ETHEREUM_FORK_RPC_URL) - process.env.ETHEREUM_FORK_BLOCK = process.env.ETHEREUM_FORK_BLOCK || `${await provider.getBlockNumber() - 10}` + const forkProvider = new ethers.providers.JsonRpcProvider(process.env.ETHEREUM_FORK_RPC_URL) + process.env.ETHEREUM_FORK_BLOCK = process.env.ETHEREUM_FORK_BLOCK || `${await forkProvider.getBlockNumber() - 10}` console.log(`📍 Forking started at ${process.env.ETHEREUM_FORK_BLOCK}`) process.env.TUNNEL = process.env.TUNNEL || 'false' process.env.MINING_INTERVAL = '12' - run('npm run node --workspace @casimir/ethereum') - await new Promise(resolve => setTimeout(resolve, 2500)) + process.env.SIMULATE_EIGENS = 'true' + process.env.SIMULATE_REWARDS = 'true' - console.log(`Serving local fork at ${process.env.ETHEREUM_RPC_URL}`) run('npm run dev --workspace @casimir/ethereum -- --network localhost') } } diff --git a/services/functions/API-request-source.js b/services/functions/API-request-source.js index 8f1b974eb..848c6fb8b 100644 --- a/services/functions/API-request-source.js +++ b/services/functions/API-request-source.js @@ -1,4 +1,4 @@ -const [ +const [ genesisTimestamp, viewsAddress, getCompoundablePoolIdsSignature, @@ -31,7 +31,7 @@ async function balancesHandler() { const depositedPoolCount = await getDepositedPoolCount() const startIndex = BigInt(0).toString(16).padStart(64, '0') const endIndex = BigInt(depositedPoolCount).toString(16).padStart(64, '0') - + const depositedPoolPublicKeys = await getDepositedPoolPublicKeys(startIndex, endIndex) const validators = await getValidators(depositedPoolPublicKeys) @@ -57,19 +57,15 @@ async function detailsHandler() { const depositedPoolCount = await getDepositedPoolCount() const startIndex = BigInt(0).toString(16).padStart(64, '0') const endIndex = BigInt(depositedPoolCount).toString(16).padStart(64, '0') - + const depositedPoolPublicKeys = await getDepositedPoolPublicKeys(startIndex, endIndex) - const depositedPoolStatuses = await getDepositedPoolStatuses(startIndex, endIndex) - for (let i = 0; i < depositedPoolCount; i++) { - console.log("* Pool") - console.log("-- Public key", depositedPoolPublicKeys[i]) - console.log("-- Status", depositedPoolStatuses[i]) - } + // const depositedPoolStatuses = await getDepositedPoolStatuses(startIndex, endIndex) // Not used yet const validators = await getValidators(depositedPoolPublicKeys) - const activatedDeposits = validators.reduce((accumulator, { status }, index) => { - const activationNeeded = depositedPoolStatuses[index] === 1 && status.includes('active') - if (activationNeeded) { + const activatedDeposits = validators.reduce((accumulator, { validator }) => { + const { activation_epoch } = validator + const activatedDuringReportPeriod = activation_epoch > previousReportEpoch && activation_epoch <= reportEpoch + if (activatedDuringReportPeriod) { accumulator += 1 } return accumulator @@ -122,19 +118,17 @@ async function getCompoundablePoolIds(startIndex, endIndex) { params: [ { to: viewsAddress, - data: getCompoundablePoolIdsSignature + '0'.repeat(24) + startIndex + endIndex + data: getCompoundablePoolIdsSignature + startIndex + endIndex }, { blockNumber: '0x' + parseInt(reportBlockNumber).toString(16) } ] } }) if (request.error) throw new Error('Failed to get compoundable pool IDs') - const rawPoolIds = request.data.result.slice(2) + const data = request.data.result.slice(2).match(/.{1,64}/g) let poolIds = [] - for (let i = 0; i < 5; i++) { - let start = i * 8 - let end = start + 8 - let poolId = parseInt(rawPoolIds.slice(start, end), 16) + for (const item of data) { + let poolId = parseInt(item, 16) poolIds.push(poolId) } return poolIds @@ -179,18 +173,29 @@ async function getDepositedPoolPublicKeys(startIndex, endIndex) { } }) if (request.error) throw new Error('Failed to get validator public keys') - const rawPublicKeys = request.data.result.slice(2) - const dataOffset = parseInt(rawPublicKeys.slice(0, 64), 16) - const numKeys = parseInt(rawPublicKeys.slice(dataOffset * 2, dataOffset * 2 + 64), 16) - const publicKeys = [] - let keysStartPosition = dataOffset * 2 + 64 - for (let i = 0; i < numKeys; i++) { - let keyStart = keysStartPosition + i * 128 - let keyEnd = keyStart + 128 - let key = '0x' + rawPublicKeys.slice(keyStart, keyEnd) - publicKeys.push(key) - } - return publicKeys + const data = request.data.result.slice(2).match(/.{1,64}/g) + // '0000000000000000000000000000000000000000000000000000000000000020', // Chunk size? + // '0000000000000000000000000000000000000000000000000000000000000002', // Array size + // '0000000000000000000000000000000000000000000000000000000000000040', // Item size + // '00000000000000000000000000000000000000000000000000000000000000a0', // Array start + // '0000000000000000000000000000000000000000000000000000000000000030', // Item 1 data size + // '8889a628f263c414e256a1295c3f49ac1780e0b9cac8493dd1bd2f17b3b25766', // Item 1 data + // '0a12fb88f65333fa00c9a735c0f8d0e800000000000000000000000000000000', // Item 1 data + // '0000000000000000000000000000000000000000000000000000000000000030', // Item 2 data size + // '853b4caf348bccddbf7e1c25e68676c3b3f857958c93b290a2bf84974ea33c4f', // Item 2 data + // '793f1bbf7e9e9923f16e2237038e7b6900000000000000000000000000000000' // Item 2 data + const itemCount = parseInt(data[1], 16) + const publicKeys = [] + let itemIndex = 4 + for (let i = 0; i < itemCount; i++) { + const publicKey = '0x'.concat( + data[itemIndex + 1], + data[itemIndex + 2].slice(0, 32) + ) + publicKeys.push(publicKey) + itemIndex += 3 + } + return publicKeys } async function getDepositedPoolStatuses(startIndex, endIndex) { @@ -204,23 +209,23 @@ async function getDepositedPoolStatuses(startIndex, endIndex) { params: [ { to: viewsAddress, - data: getDepositedPoolStatusesSignature + '0'.repeat(24) + startIndex + endIndex + data: getDepositedPoolStatusesSignature + startIndex + endIndex }, { blockNumber: '0x' + parseInt(reportBlockNumber).toString(16) } ] } }) if (request.error) throw new Error('Failed to get validator statuses') - console.log("Raw statuses", request.data.result) - const rawStatuses = request.data.result.slice(2) + const data = request.data.result.slice(2).match(/.{1,64}/g) + const itemCount = parseInt(data[1], 16) const statuses = [] - for (let i = 0; i < 5; i++) { - let start = i * 8 - let end = start + 8 - let status = parseInt(rawStatuses.slice(start, end), 16) + let itemIndex = 2 + for (let i = 0; i < itemCount; i++) { + let status = parseInt(data[itemIndex], 16) statuses.push(status) + itemIndex += 1 } - return [2, 2] + return statuses } async function getSweptBalance(startIndex, endIndex) { @@ -234,14 +239,15 @@ async function getSweptBalance(startIndex, endIndex) { params: [ { to: viewsAddress, - data: getSweptBalanceSignature + '0'.repeat(24) + startIndex + endIndex + data: getSweptBalanceSignature + startIndex + endIndex }, { blockNumber: '0x' + parseInt(reportBlockNumber).toString(16) } ] } }) if (request.error) throw new Error('Failed to get swept balance') - return parseFloat(request.data.result.slice(0, -9)) + const data = request.data.result.slice(2) + return parseInt(data, 16) } async function getValidators(validatorPublicKeys) { @@ -253,15 +259,15 @@ async function getValidators(validatorPublicKeys) { } function encodeUint32(value) { - const buffer = Buffer.alloc(32) - buffer.writeUInt32BE(value, 28) - return buffer + const buffer = Buffer.alloc(32) + buffer.writeUInt32BE(value, 28) + return buffer } function encodeUint32Array(values) { - const buffer = Buffer.alloc(32 * values.length) - for (let i = 0; i < values.length; i++) { - buffer.writeUInt32BE(values[i], i * 32 + 28) - } - return buffer + const buffer = Buffer.alloc(32 * values.length) + for (let i = 0; i < values.length; i++) { + buffer.writeUInt32BE(values[i], i * 32 + 28) + } + return buffer } \ No newline at end of file diff --git a/services/functions/package.json b/services/functions/package.json index c08de5fc5..2907a0e9a 100644 --- a/services/functions/package.json +++ b/services/functions/package.json @@ -6,7 +6,8 @@ "build": "", "dev": "npx esno -r dotenv/config scripts/dev.ts", "functions-simulate-javascript": "node scripts/simulateFunctionsJavaScript.js", - "functions-gen-keys": "node scripts/generateKeypair.js" + "functions-gen-keys": "node scripts/generateKeypair.js", + "simulate": "npx esno -r dotenv/config scripts/simulate.ts" }, "dependencies": { "@chainlink/env-enc": "^1.0.5", diff --git a/services/functions/scripts/dev.ts b/services/functions/scripts/dev.ts index aa2b4aff2..fdac31d19 100644 --- a/services/functions/scripts/dev.ts +++ b/services/functions/scripts/dev.ts @@ -1,32 +1,34 @@ +import { ethers } from 'ethers' import { loadCredentials, getSecret } from '@casimir/aws' import { ETHEREUM_CONTRACTS, ETHEREUM_RPC_URL } from '@casimir/env' import { run } from '@casimir/shell' +import { waitForNetwork } from '@casimir/ethereum/helpers/network' /** * Start the Chainlink functions service */ -void async function() { - +async function dev() { if (process.env.USE_SECRETS !== 'false') { await loadCredentials() process.env.BIP39_SEED = process.env.BIP39_SEED || await getSecret('consensus-networks-bip39-seed') as string } else { process.env.BIP39_SEED = process.env.BIP39_SEED || 'inflict ball claim confirm cereal cost note dad mix donate traffic patient' } - const networkKey = process.env.NETWORK?.toUpperCase() || process.env.FORK?.toUpperCase() || 'TESTNET' - if (process.env.NETWORK) { - process.env.ETHEREUM_RPC_URL = process.env.ETHEREUM_RPC_URL || ETHEREUM_RPC_URL[networkKey] - process.env.FUNCTIONS_BILLING_REGISTRY_ADDRESS = ETHEREUM_CONTRACTS[networkKey]?.FUNCTIONS_BILLING_REGISTRY_ADDRESS - process.env.FUNCTIONS_ORACLE_ADDRESS = ETHEREUM_CONTRACTS[networkKey]?.FUNCTIONS_ORACLE_ADDRESS - } - if (!process.env.ETHEREUM_RPC_URL) throw new Error(`No ethereum rpc url provided for ${networkKey}`) - if (!process.env.FUNCTIONS_BILLING_REGISTRY_ADDRESS) throw new Error(`No functions billing registry address provided for ${networkKey}`) - if (!process.env.FUNCTIONS_ORACLE_ADDRESS) throw new Error('No functions oracle address provided') - + process.env.ETHEREUM_RPC_URL = process.env.ETHEREUM_RPC_URL || ETHEREUM_RPC_URL[networkKey] process.env.ETHEREUM_BEACON_RPC_URL = process.env.ETHEREUM_BEACON_RPC_URL || 'http://127.0.0.1:5052' - + process.env.FUNCTIONS_BILLING_REGISTRY_ADDRESS = ETHEREUM_CONTRACTS[networkKey]?.FUNCTIONS_BILLING_REGISTRY_ADDRESS + process.env.FUNCTIONS_ORACLE_ADDRESS = ETHEREUM_CONTRACTS[networkKey]?.FUNCTIONS_ORACLE_ADDRESS process.env.USE_LOGS = process.env.USE_LOGS || 'false' + + const provider = new ethers.providers.JsonRpcProvider(process.env.ETHEREUM_RPC_URL) + await waitForNetwork(provider) + run('npx esno -r dotenv/config src/index.ts') console.log('🔗 Functions service started') -}() \ No newline at end of file +} + +dev().catch(error => { + console.error(error) + process.exit(1) +}) \ No newline at end of file diff --git a/services/functions/scripts/simulate.ts b/services/functions/scripts/simulate.ts new file mode 100644 index 000000000..b24c566af --- /dev/null +++ b/services/functions/scripts/simulate.ts @@ -0,0 +1,37 @@ +import { ethers } from 'ethers' +import { ETHEREUM_NETWORK_NAME, ETHEREUM_RPC_URL } from '@casimir/env' +import { run } from '@casimir/shell' +import { getSecret, loadCredentials } from '@casimir/aws' +import { waitForNetwork } from '@casimir/ethereum/helpers/network' + +async function simulate() { + if (process.env.USE_SECRETS !== 'false') { + await loadCredentials() + process.env.BIP39_SEED = process.env.BIP39_SEED || await getSecret('consensus-networks-bip39-seed') as string + } else { + process.env.BIP39_SEED = process.env.BIP39_SEED || 'inflict ball claim confirm cereal cost note dad mix donate traffic patient' + } + const networkKey = process.env.FORK?.toUpperCase() || 'TESTNET' + const networkName = ETHEREUM_NETWORK_NAME[networkKey] + process.env.ETHEREUM_FORK_RPC_URL = ETHEREUM_RPC_URL[networkKey] + process.env.ETHEREUM_RPC_URL = 'http://127.0.0.1:8545' + console.log(`Connecting to ${networkName} network fork at ${process.env.ETHEREUM_RPC_URL}`) + + const forkProvider = new ethers.providers.JsonRpcProvider(process.env.ETHEREUM_FORK_RPC_URL) + process.env.ETHEREUM_FORK_BLOCK = process.env.ETHEREUM_FORK_BLOCK || `${await forkProvider.getBlockNumber() - 10}` + console.log(`📍 Forking started at ${process.env.ETHEREUM_FORK_BLOCK}`) + + process.env.TUNNEL = process.env.TUNNEL || 'false' + process.env.MINING_INTERVAL = '12' + process.env.SIMULATE_UPGRADES = 'true' + process.env.SIMULATE_UPKEEP = 'true' + + run('npm run dev --workspace @casimir/ethereum -- --network localhost') + run('npm run report --workspace @casimir/ethereum -- --network localhost') + run('npm run dev --workspace @casimir/functions') +} + +simulate().catch(error => { + console.error(error) + process.exit(1) +}) \ No newline at end of file diff --git a/services/functions/src/index.ts b/services/functions/src/index.ts index b7002a308..669d87b93 100644 --- a/services/functions/src/index.ts +++ b/services/functions/src/index.ts @@ -8,61 +8,61 @@ import FunctionsOracleAbi from '@casimir/ethereum/build/abi/FunctionsOracle.json const config = getConfig() -const contracts = { - FunctionsOracle: { - abi: FunctionsOracleAbi, - addresses: [config.functionsOracleAddress], - events: { - OracleRequest: fulfillRequestHandler +async function run() { + const contracts = { + FunctionsOracle: { + abi: FunctionsOracleAbi, + addresses: [config.functionsOracleAddress], + events: { + OracleRequest: fulfillRequestHandler + } } } -} - -const contractFilters = Object.values(contracts).map((contract) => { - return { - abi: contract.abi, - addresses: contract.addresses, - events: Object.keys(contract.events) + + const contractFilters = Object.values(contracts).map((contract) => { + return { + abi: contract.abi, + addresses: contract.addresses, + events: Object.keys(contract.events) + } + }) + + let startBlock + if (process.env.USE_LOGS === 'true') { + startBlock = getStartBlock('block.log') } -}) - -let startBlock -if (process.env.USE_LOGS === 'true') { - startBlock = getStartBlock('block.log') -} - -const eventsIterable = getEventsIterable({ - contractFilters, - ethereumUrl: config.ethereumUrl, - startBlock -}) - -const handlers: Record Promise> = {} -for (const contract of Object.values(contracts)) { - for (const [event, handler] of Object.entries(contract.events)) { - handlers[event as keyof typeof handlers] = handler + + const eventsIterable = getEventsIterable({ + contractFilters, + ethereumUrl: config.ethereumUrl, + startBlock + }) + + const handlers: Record Promise> = {} + for (const contract of Object.values(contracts)) { + for (const [event, handler] of Object.entries(contract.events)) { + handlers[event as keyof typeof handlers] = handler + } } -} -void async function () { - try { - for await (const event of eventsIterable) { - console.log(`Received ${event.event} event from ${event.address}`) - const args = event.args as ethers.utils.Result - const handler = handlers[event.event as string] - if (!handler) throw new Error(`No handler found for event ${event.event}`) - await handler({ args }) - if (process.env.USE_LOGS === 'true') { - // Todo check if this possibly misses events - updateStartBlock('block.log', event.blockNumber + 1) - } - } - } catch (error) { + for await (const event of eventsIterable) { + console.log(`Received ${event.event} event from ${event.address}`) + const args = event.args as ethers.utils.Result + const handler = handlers[event.event as string] + if (!handler) throw new Error(`No handler found for event ${event.event}`) + await handler({ args }) if (process.env.USE_LOGS === 'true') { - updateErrorLog('error.log', (error as Error).message) - } else { - console.log(error) + // Todo check if this possibly misses events + updateStartBlock('block.log', event.blockNumber + 1) } - process.exit(1) } -}() \ No newline at end of file +} + +run().catch(error => { + if (process.env.USE_LOGS === 'true') { + updateErrorLog('error.log', (error as Error).message) + } else { + console.log(error) + } + process.exit(1) +}) \ No newline at end of file