Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add operator selection and internal operators for testing #387

Merged
merged 11 commits into from
Aug 7, 2023
6 changes: 3 additions & 3 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@
[submodule "scripts/trezor/resources/trezor-user-env"]
path = scripts/trezor/resources/trezor-user-env
url = https://github.com/trezor/trezor-user-env.git
[submodule "services/validators/scripts/resources/rockx-dkg-cli"]
path = services/oracle/scripts/resources/rockx-dkg-cli
url = https://github.com/RockX-SG/rockx-dkg-cli.git
[submodule "contracts/ethereum/scripts/resources/ssv-network"]
path = contracts/ethereum/scripts/resources/ssv-network
url = https://github.com/bloxapp/ssv-network.git
branch = contract-v3
[submodule "contracts/ethereum/lib/forge-std"]
path = contracts/ethereum/lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "services/oracle/scripts/resources/rockx-dkg-cli"]
path = services/oracle/scripts/resources/rockx-dkg-cli
url = https://github.com/RockX-SG/rockx-dkg-cli.git
9 changes: 0 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,8 @@ Customize and override the defaults for your *local development environment* by
| `TUNNEL` | Whether to tunnel local network RPC URLs (for remote wallets) | `false` |
| `EMULATE` | Whether to emulate wallets | `false` |
| `LEDGER_APP` | Ledger app name | `ethereum` |
| `MOCK_ORACLE` | Whether to mock oracle | `true` |
| `MOCK_SERVICES` | Whether to mock backend services | `true` |
| `BUILD_PREVIEW` | Whether to preview web app production build | `false` |
| `VALIDATOR_COUNT` | Number of validators to generate for tests | `4` |

### Apps

Expand All @@ -141,10 +139,8 @@ npm run dev:landing
- Set `EMULATE` to `true` to emulate Ledger and Trezor hardware wallets.
- Set `LEDGER_APP` to the name of the Ledger app to emulate (defaults to `ethereum`).
- Set `TUNNEL` to `true` to tunnel the local network RPC URLs (for remote wallets).
- Set `MOCK_ORACLE` to `false` to use pregenerated validators (or create them if unavailable).
- Set `MOCK_SERVICES` to `false` to use the deployed backend services for the current stage.
- Set `BUILD_PREVIEW` to `true` to run the local script with a production build preview of the web app.
- Set `VALIDATOR_COUNT` to the number of validators to generate for tests (defaults to `4`).

### Contracts

Expand All @@ -168,11 +164,6 @@ Run a local Ethereum network with deployed contracts, simulation scripts, and ar
npm run dev:ethereum
```

**Additional configuration:**

- Set `MOCK_ORACLE` to `false` to use pregenerated validators (or create them if unavailable).
- Set `VALIDATOR_COUNT` to the number of validators to generate for tests (defaults to `4`).

### Emulators

We can emulate Ledger and Trezor hardware wallet wallets by setting the environment variable `EMULATE` to `true`. For Ledger, the default app is `ethereum`, but the app can be specified by setting the environment variable `LEDGER_APP`. For Trezor, we also need to make sure to add [these prerequisites](https://github.com/trezor/trezor-user-env#prerequisites).
Expand Down
11 changes: 6 additions & 5 deletions apps/web/src/composables/environment.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
export default function useEnvironment() {
const domain = window.location.host
const origin = window.location.origin
const managerAddress = import.meta.env.PUBLIC_MANAGER_ADDRESS
if (!managerAddress) throw new Error('No manager address provided')
const viewsAddress = import.meta.env.PUBLIC_VIEWS_ADDRESS
if (!viewsAddress) throw new Error('No views address provided')
const registryAddress = import.meta.env.PUBLIC_REGISTRY_ADDRESS
if (!registryAddress) throw new Error('No registry address provided')
const usersUrl = import.meta.env.PUBLIC_USERS_URL || 'http://localhost:4000'
const ethereumUrl = import.meta.env.PUBLIC_ETHEREUM_RPC_URL || 'http://127.0.0.1:8545'
const ledgerType = import.meta.env.PUBLIC_SPECULOS_URL ? 'speculos' : 'usb'
const speculosUrl = import.meta.env.PUBLIC_SPECULOS_URL ? 'http://localhost:5001' : ''
const walletConnectUrl = 'https://bridge.walletconnect.org'
const cryptoCompareApiKey = import.meta.env.PUBLIC_CRYPTO_COMPARE_API_KEY || ''

/* Smart Contract Addresses */
const managerAddress = import.meta.env.PUBLIC_MANAGER_ADDRESS || '0x07e05700cb4e946ba50244e27f01805354cd8ef0'
const registryAddress = import.meta.env.PUBLIC_REGISTRY_ADDRESS || '0x40A9DEB9Eb871e3f7A1a2946a6e8A84afAb4C598'
const ssvNetworkAddress = import.meta.env.PUBLIC_SSV_NETWORK_ADDRESS
const ssvNetworkViewsAddress = import.meta.env.PUBLIC_SSV_NETWORK_VIEWS_ADDRESS
const viewsAddress = import.meta.env.PUBLIC_VIEWS_ADDRESS || '0x6b34d231b467fccebdc766187f7251795281dc26'

return {
domain,
Expand Down
95 changes: 44 additions & 51 deletions common/data/notebooks/athena.ipynb

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion common/data/notebooks/ethereum.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
},
{
"cell_type": "code",
"execution_count": 11,
"execution_count": 1,
"metadata": {},
"outputs": [
{
Expand Down
10 changes: 9 additions & 1 deletion common/data/src/mock/operator.store.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,13 @@
"5": "http://0.0.0.0:8085",
"6": "http://0.0.0.0:8086",
"7": "http://0.0.0.0:8087",
"8": "http://0.0.0.0:8088"
"8": "http://0.0.0.0:8088",
"654": "https://nodes.casimir.co/eth/goerli/dkg/1",
"655": "https://nodes.casimir.co/eth/goerli/dkg/2",
"656": "https://nodes.casimir.co/eth/goerli/dkg/3",
"657": "https://nodes.casimir.co/eth/goerli/dkg/4",
"658": "https://nodes.casimir.co/eth/goerli/dkg/5",
"659": "https://nodes.casimir.co/eth/goerli/dkg/6",
"660": "https://nodes.casimir.co/eth/goerli/dkg/7",
"661": "https://nodes.casimir.co/eth/goerli/dkg/8"
}
9 changes: 9 additions & 0 deletions common/ssv/src/interfaces/Cluster.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { ethers } from 'ethers'

export interface Cluster {
validatorCount: number | ethers.BigNumber
networkFeeIndex: number | ethers.BigNumber
index: number | ethers.BigNumber
balance: number | ethers.BigNumber
active: boolean
}
7 changes: 0 additions & 7 deletions common/ssv/src/interfaces/ClusterDetails.ts

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export interface ClusterDetailsInput {
export interface GetClusterInput {
ownerAddress: string
operatorIds: number[]
}
78 changes: 58 additions & 20 deletions common/ssv/src/providers/scanner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,14 @@ import { ethers } from 'ethers'
import { ISSVNetwork, ISSVNetworkViews } from '@casimir/ethereum/build/@types'
import ISSVNetworkAbi from '@casimir/ethereum/build/abi/ISSVNetwork.json'
import ISSVNetworkViewsAbi from '@casimir/ethereum/build/abi/ISSVNetworkViews.json'
import { ClusterDetailsInput } from '../interfaces/ClusterDetailsInput'
import { ClusterDetails } from '../interfaces/ClusterDetails'
import { Cluster } from '@casimir/types'
import { GetClusterInput } from '../interfaces/GetClusterInput'
import { Cluster } from '../interfaces/Cluster'
import { ScannerOptions } from '../interfaces/ScannerOptions'

export class Scanner {
DAY = 5400
WEEK = this.DAY * 7
MONTH = this.DAY * 30
eventList = [
'ClusterDeposited',
'ClusterWithdrawn',
'ValidatorAdded',
'ValidatorRemoved',
'ClusterLiquidated',
'ClusterReactivated'
]
provider: ethers.providers.JsonRpcProvider
ssvNetwork: ISSVNetwork & ethers.Contract
ssvNetworkViews: ISSVNetworkViews & ethers.Contract
Expand All @@ -36,12 +27,19 @@ export class Scanner {
/**
* Get cluster details
* @param {ClusterInput} input - Operator IDs and withdrawal address
* @returns {Promise<ClusterDetails>} Cluster snapshot and required balance per validator
* @returns {Promise<Cluster>} Cluster snapshot and required balance per validator
*/
async getClusterDetails(input: ClusterDetailsInput): Promise<ClusterDetails> {
async getCluster(input: GetClusterInput): Promise<Cluster> {
const { ownerAddress, operatorIds } = input
const eventFilters = this.eventList.map(event => this.ssvNetwork.filters[event](ownerAddress))

const eventList = [
'ClusterDeposited',
'ClusterWithdrawn',
'ValidatorAdded',
'ValidatorRemoved',
'ClusterLiquidated',
'ClusterReactivated'
]
const eventFilters = eventList.map(event => this.ssvNetwork.filters[event](ownerAddress))
let step = this.MONTH
const latestBlockNumber = await this.provider.getBlockNumber()
let fromBlock = latestBlockNumber - step
Expand Down Expand Up @@ -97,16 +95,56 @@ export class Scanner {
balance: 0,
active: true
}

return cluster
}

/**
* Get owner validator nonce
* @param {string} ownerAddress - Owner address
* @returns {Promise<number>} Owner validator nonce
*/
async getValidatorNonce(ownerAddress: string): Promise<number> {
const eventList = ['ValidatorAdded']
const eventFilters = eventList.map(event => this.ssvNetwork.filters[event](ownerAddress))
let step = this.MONTH
const latestBlockNumber = await this.provider.getBlockNumber()
let fromBlock = latestBlockNumber - step
let toBlock = latestBlockNumber
let nonce = 0
while (fromBlock > 0) {
try {
const items = (await Promise.all(
eventFilters.map(async eventFilter => {
return await this.ssvNetwork.queryFilter(eventFilter, fromBlock, toBlock)
})
)).flat()
nonce += items.length
toBlock = fromBlock
} catch (error) {
console.error(error)
if (step === this.MONTH) {
step = this.WEEK
} else if (step === this.WEEK) {
step = this.DAY
}
}
fromBlock = toBlock - step
}
return nonce
}

/**
* Get minimum validator fee
* @param {number[]} operatorIds - Operator IDs
* @returns {Promise<ethers.BigNumber>} Validator fee
*/
async getClusterFee(operatorIds: number[]): Promise<ethers.BigNumber> {
const feeSum = await this.ssvNetworkViews.getNetworkFee()
for (const operatorId of operatorIds) {
const operatorFee = await this.ssvNetworkViews.getOperatorFee(operatorId)
feeSum.add(operatorFee)
}
const liquidationThresholdPeriod = await this.ssvNetworkViews.getLiquidationThresholdPeriod()
const requiredBalancePerValidator = feeSum.mul(liquidationThresholdPeriod).mul(12)

return { cluster, requiredBalancePerValidator }
return feeSum.mul(liquidationThresholdPeriod).mul(12)
}

}
2 changes: 0 additions & 2 deletions common/types/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { BalanceSnapshot } from './interfaces/BalanceSnapshot'
import { BreakdownAmount } from './interfaces/BreakdownAmount'
import { BreakdownString } from './interfaces/BreakdownString'
import { BrowserProviders } from './interfaces/BrowserProviders'
import { Cluster } from './interfaces/Cluster'
import { ContractArgs } from './interfaces/ContractArgs'
import { ContractConfig } from './interfaces/ContractConfig'
import { ContractEventsByAddress } from './interfaces/ContractEventsByAddress'
Expand Down Expand Up @@ -44,7 +43,6 @@ export type {
BreakdownAmount,
BreakdownString,
BrowserProviders,
Cluster,
ContractArgs,
ContractConfig,
ContractEventsByAddress,
Expand Down
9 changes: 0 additions & 9 deletions common/types/src/interfaces/Cluster.ts
Original file line number Diff line number Diff line change
@@ -1,9 +0,0 @@
import { ethers } from 'ethers'

export interface Cluster {
validatorCount: number | ethers.BigNumber
networkFeeIndex: number | ethers.BigNumber
index: number | ethers.BigNumber
balance: number | ethers.BigNumber
active: boolean
}
8 changes: 5 additions & 3 deletions contracts/ethereum/helpers/oracle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,13 @@ export async function initiateDepositHandler({ manager, signer }: { manager: Cas
ssvNetworkViewsAddress
})

const { cluster, requiredBalancePerValidator } = await scanner.getClusterDetails({
const cluster = await scanner.getCluster({
ownerAddress: manager.address,
operatorIds
})

const validatorFee = await scanner.getClusterFee(operatorIds)

const uniswapFactory = new Factory({
provider: ethers.provider,
uniswapV3FactoryAddress
Expand All @@ -63,7 +65,7 @@ export async function initiateDepositHandler({ manager, signer }: { manager: Cas
uniswapFeeTier: 3000
})

const feeAmount = ethers.utils.parseEther((Number(ethers.utils.formatEther(requiredBalancePerValidator)) * price).toPrecision(9))
const feeAmount = ethers.utils.parseEther((Number(ethers.utils.formatEther(validatorFee)) * price).toPrecision(9))

const initiateDeposit = await manager.connect(signer).initiateDeposit(
depositDataRoot,
Expand Down Expand Up @@ -145,7 +147,7 @@ export async function reportCompletedExitsHandler({ manager, views, signer, args
ssvNetworkViewsAddress
})

const { cluster } = await scanner.getClusterDetails({
const cluster = await scanner.getCluster({
ownerAddress: manager.address,
operatorIds
})
Expand Down
Loading