diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..e4e910d09 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,13 @@ +# top-most EditorConfig file +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +indent_style = space +indent_size = 4 + +[*.{js,yml,json,cjs}] +indent_size = 2 +max_line_length = 120 diff --git a/.eslintrc b/.eslintrc index 43aa16d92..7028a1e29 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,59 +1,32 @@ { + "plugins": ["prettier", "standard"], "env": { "node": true, - "es2020": true + "es2022": true }, - "extends": [ - "standard", - "plugin:prettier/recommended", - "prettier", - "prettier/standard" - ], -"parser": "babel-eslint", + "extends": ["standard", "plugin:prettier/recommended"], "parserOptions": { - "ecmaVersion": 8, - "sourceType": "module", + "ecmaVersion": 2022, + "sourceType": "script", "ecmaFeatures": { - "jsx": true, - "modules": true + "jsx": true } }, - "plugins": [ - "node", - "prettier", - "standard" - ], - "rules": { "prettier/prettier": "error", - "max-len": ["warn", { "code": 140, "ignoreComments": true, "ignoreUrls": true }], - "no-undef": "warn", - "no-unused-vars": "warn", - "prefer-const": "warn", "camelcase": "off", - "eol-last": "warn", - "no-multiple-empty-lines": "warn", - "comma-dangle": "warn", "quotes": ["error", "single", { "avoidEscape": true, "allowTemplateLiterals": true }], "jsx-quotes": ["error", "prefer-single"], - "no-useless-escape": "off", - "valid-jsdoc": "error", - "linebreak-style": ["error", "unix"] + "no-restricted-modules": ["error", { + "name": "@aragon/contract-helpers-test/src/asserts", + "message": "Please use helpers/assert instead." + }] }, "overrides": [ { - "files": [ - "./scripts/{**/,}*.js", - "./test/{**/,}*.js", - "./e2e/test/{**/,}*.js" - ], + "files": ["./scripts/{**/,}*.js", "./test/{**/,}*.js", "./e2e/test/{**/,}*.js"], "env": { "mocha": true - }, - "globals": { - "artifacts": "readonly", - "contract": "readonly", - "web3": "readonly" } } ] diff --git a/.github/assert-deployed-bytecode.js b/.github/assert-deployed-bytecode.js deleted file mode 100644 index 26e4ab17c..000000000 --- a/.github/assert-deployed-bytecode.js +++ /dev/null @@ -1,128 +0,0 @@ -const { AssertionError } = require('chai') -const chalk = require('chalk') -const { web3 } = require('hardhat') -const { readJSON } = require('../scripts/helpers/fs') -const { APPS_TO_NAMES, CONTRACTS_TO_NAMES, IGNORE_METADATA_CONTRACTS } = require('./deployed-bytecode-consts') - -const empty32bytes = '0000000000000000000000000000000000000000000000000000000000000000' - -function isInsideEmpty32bytes(byteCode, index) { - const start = index - 63 >= 0 ? index - 63 : 0 - const end = index + 64 <= byteCode.length ? index + 64 : byteCode.length - return byteCode.slice(start, end).indexOf(empty32bytes) >= 0 -} - -function stripMetadata(byteCode) { - let metaDataLength = parseInt(byteCode.slice(-4), 16) * 2 - let metaDataIndex = byteCode.length - metaDataLength - 4 - if (metaDataIndex > 0) { - return byteCode.slice(0, metaDataIndex) - } - return byteCode -} - -function isBytecodeSimilar(first, second, ignoreMetadata) { - if (ignoreMetadata) { - first = stripMetadata(first) - second = stripMetadata(second) - } - if (first.length != second.length) { - return false - } - for (var i = 0; i < first.length; i++) { - if (first[i] != second[i] && !isInsideEmpty32bytes(first, i) && !isInsideEmpty32bytes(second, i)) { - return false - } - } - return true -} - -async function assertByteCode(address, artifactName, deployTx) { - const artifact = await artifacts.readArtifact(artifactName) - let bytecodeFromArtifact = artifact.deployedBytecode.toLowerCase() - const bytecodeFromRpc = (await web3.eth.getCode(address)).toLowerCase() - const ignoreMetadata = IGNORE_METADATA_CONTRACTS.includes(artifactName) - if (bytecodeFromRpc === bytecodeFromArtifact) { - console.log(chalk.green(`Compiled bytecode for ${chalk.yellow(address)}(${artifactName}) MATCHES deployed bytecode!`)) - } else if (isBytecodeSimilar(bytecodeFromArtifact, bytecodeFromRpc, ignoreMetadata)) { - console.log(chalk.hex('#FFA500')(`Compiled bytecode for ${chalk.yellow(address)}(${artifactName}) is SIMILAR to deployed bytecode!`)) - if (deployTx) { - await assertByteCodeByDeployTx(address, deployTx, artifact, ignoreMetadata) - } else { - throw new AssertionError( - `No deployTx found for ${chalk.yellow(address)}(${artifactName}).\n` + - `Double check is impossible, but required due to differences in the deployed bytecode` - ) - } - } else { - throw new AssertionError(`Compiled bytecode for ${chalk.yellow(address)}(${artifactName}) DOESN'T MATCH deployed bytecode!`) - } -} - -async function assertByteCodeByDeployTx(address, deployTx, artifact, ignoreMetadata) { - const tx = await web3.eth.getTransaction(deployTx) - const txData = tx.input.toLowerCase() - const byteCode = ignoreMetadata ? stripMetadata(artifact.bytecode) : artifact.bytecode - if (!txData.startsWith(byteCode)) { - throw new AssertionError( - `Bytecode from deploy TX DOESN'T MATCH compiled bytecode for ${chalk.yellow(address)}(${artifact.contractName})` - ) - } - console.log( - chalk.green( - `Bytecode from deploy TX ${ignoreMetadata ? 'SIMILAR to' : 'MATCHES'} compiled bytecode for ${chalk.yellow(address)}(${ - artifact.contractName - })` - ) - ) -} - -async function assertDeployedByteCodeMain() { - const deployInfo = await readJSON(`deployed-mainnet.json`) - - // handle APPs - const resultsApps = await Promise.allSettled( - Object.entries(deployInfo).map(async ([key, value]) => { - if (key.startsWith('app:') && !key.startsWith('app:aragon')) { - const name = APPS_TO_NAMES.get(key.split(':')[1]) - if (!name) { - throw `Unknown APP ${key}` - } - const address = value.baseAddress - if (!address) { - throw `APP ${key} has no baseAddress` - } - await assertByteCode(address, name) - } - }) - ) - // handle standalone contracts - const resultsContracts = await Promise.allSettled( - Object.entries(deployInfo).map(async ([key, value]) => { - if (!key.startsWith('app:') && key.endsWith('Address')) { - const name = CONTRACTS_TO_NAMES.get(key.replace('Address', '')) - if (!name) { - return - } - const address = value - const deployTx = deployInfo[key.replace('Address', 'DeployTx')] - await assertByteCode(address, name, deployTx) - } - }) - ) - let errors = [] - resultsApps.concat(resultsContracts).forEach((result) => { - if (result.status == 'rejected') { - errors.push(result.reason) - } - }) - if (errors.length > 0) { - throw new Error(`Following errors occurred during execution:\n${chalk.red(errors.join('\n'))}`) - } -} - -var myfunc = assertDeployedByteCodeMain() -myfunc.catch((err) => { - console.log(err) - process.exit([1]) -}) diff --git a/.github/assert-git-changes.py b/.github/assert-git-changes.py deleted file mode 100755 index bb1c830bc..000000000 --- a/.github/assert-git-changes.py +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/python3 -import subprocess -import os - -target_dir = os.environ.get("TARGET_DIR") - -if not target_dir: - print("No TARGET_DIR env variable provided. Exiting") - exit(1) - -git_changes = subprocess.getoutput("git status --porcelain") -print(f"Changes:\n{git_changes}") - -if git_changes.find(target_dir) > 0: - print(f"Changes in {target_dir} detected! Failing") - exit(1) diff --git a/.github/deployed-bytecode-consts.js b/.github/deployed-bytecode-consts.js index 4959d22f2..32d74f6f1 100644 --- a/.github/deployed-bytecode-consts.js +++ b/.github/deployed-bytecode-consts.js @@ -1,14 +1,13 @@ const APPS_TO_NAMES = new Map([ ['lido', 'Lido'], ['node-operators-registry', 'NodeOperatorsRegistry'], - ['oracle', 'LidoOracle'] ]) const CONTRACTS_TO_NAMES = new Map([ ['wstethContract', 'WstETH'], ['executionLayerRewardsVault', 'LidoExecutionLayerRewardsVault'], ['compositePostRebaseBeaconReceiver', 'CompositePostRebaseBeaconReceiver'], - ['selfOwnedStETHBurner', 'SelfOwnedStETHBurner'], + ['burner', 'Burner'], ['depositor', 'DepositSecurityModule'] ]) diff --git a/.github/workflows/analyse.yml b/.github/workflows/analyse.yml new file mode 100644 index 000000000..153983807 --- /dev/null +++ b/.github/workflows/analyse.yml @@ -0,0 +1,81 @@ +name: Code Analysis + +on: + push: + branches: [master, develop] + pull_request: + branches: [master, develop] + +jobs: + slither: + runs-on: ubuntu-latest + permissions: + contents: read + security-events: write + steps: + - name: Checkout repository + uses: actions/checkout@v3 + with: + persist-credentials: false + + - name: Setup node.js version + uses: actions/setup-node@v3 + with: + node-version: 16 + + - name: Get yarn cache directory path + id: yarn-cache-dir-path + run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT + + - name: Cache yarn cache + id: cache-yarn-cache + uses: actions/cache@v3 + with: + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + key: yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: yarn-${{ hashFiles('**/yarn.lock') }} + + - name: Cache node_modules + id: cache-node-modules + uses: actions/cache@v3 + with: + path: '**/node_modules' + key: node_modules-${{ hashFiles('**/yarn.lock') }} + restore-keys: node_modules-${{ hashFiles('**/yarn.lock') }} + + - name: Install modules + run: yarn + if: | + steps.cache-yarn-cache.outputs.cache-hit != 'true' || + steps.cache-node-modules.outputs.cache-hit != 'true' + + - uses: actions/setup-python@v4 + with: + python-version: '3.10.6' + + - name: Install poetry requirements + run: > + curl -sSL https://install.python-poetry.org | python - && + poetry install --no-root + + - name: Remove foundry.toml + run: rm -f foundry.toml + + - name: Run slither + run: > + poetry run slither . --sarif results.sarif --no-fail-pedantic + + - name: Check results.sarif presence + id: results + if: always() + shell: bash + run: > + test -f results.sarif && + echo 'value=present' >> $GITHUB_OUTPUT || + echo 'value=not' >> $GITHUB_OUTPUT + + - name: Upload SARIF file + uses: github/codeql-action/upload-sarif@v2 + if: ${{ always() && steps.results.outputs.value == 'present' }} + with: + sarif_file: results.sarif diff --git a/.github/workflows/assert-bytecode.yml b/.github/workflows/assert-bytecode.yml index 072b1fdcf..5fbfed4ea 100644 --- a/.github/workflows/assert-bytecode.yml +++ b/.github/workflows/assert-bytecode.yml @@ -1,8 +1,8 @@ -name: CI +name: Verify deployed contracts' bytecode on: pull_request: - branches: + branches: - 'master' jobs: @@ -11,49 +11,58 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - - name: Setup node.js version - uses: actions/setup-node@v1 - with: - node-version: 12.x - - - name: Get yarn cache directory path - id: yarn-cache-dir-path - run: echo "::set-output name=dir::$(yarn config get cacheFolder)" - - - name: Cache yarn cache - id: cache-yarn-cache - uses: actions/cache@v2 - with: - path: ${{ steps.yarn-cache-dir-path.outputs.dir }} - key: yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: yarn-${{ hashFiles('**/yarn.lock') }} - - - name: Cache node_modules - id: cache-node-modules - uses: actions/cache@v2 - with: - path: '**/node_modules' - key: node_modules-${{ hashFiles('**/yarn.lock') }} - restore-keys: node_modules-${{ hashFiles('**/yarn.lock') }} - - - name: Install modules - run: yarn - if: | - steps.cache-yarn-cache.outputs.cache-hit != 'true' || - steps.cache-node-modules.outputs.cache-hit != 'true' - - - name: Compile - run: yarn compile - - - name: Create accounts.json - run: .github/prepare-accounts-json.py - env: - INFURA_PROJECT_ID: ${{ secrets.WEB3_INFURA_PROJECT_ID }} - ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_TOKEN }} - - - name: Check deployed contract bytecode - run: npx hardhat run .github/assert-deployed-bytecode.js - env: - NETWORK_NAME: mainnet + - uses: actions/checkout@v3 + with: + persist-credentials: false + + - name: Setup node.js version + uses: actions/setup-node@v3 + with: + node-version: 16 + + - name: Get yarn cache directory path + id: yarn-cache-dir-path + run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT + + - name: Cache yarn cache + id: cache-yarn-cache + uses: actions/cache@v3 + with: + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + key: yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: yarn-${{ hashFiles('**/yarn.lock') }} + + - name: Cache node_modules + id: cache-node-modules + uses: actions/cache@v3 + with: + path: '**/node_modules' + key: node_modules-${{ hashFiles('**/yarn.lock') }} + restore-keys: node_modules-${{ hashFiles('**/yarn.lock') }} + + - name: Install modules + run: yarn + if: | + steps.cache-yarn-cache.outputs.cache-hit != 'true' || + steps.cache-node-modules.outputs.cache-hit != 'true' + + - name: Compile contracts + run: yarn compile + + - name: Verify bytecode + uses: lidofinance/action-verify-bytecode@master + with: + file: artifacts.json + + - name: Check artifacts.json file completeness + run: | + shopt -s globstar + jq -r 'select(.deployedBytecode? | length > 2) | select(.abi? | length > 0) | + select(.sourceName? | strings | test(env.IGNORE_REGEX) == false) | + input_filename' artifacts/contracts/**/*.json > _expected + jq -r '.[] | .artifactPath' artifacts.json > _actual + cat _actual _expected | sort | uniq -u | tee _diff + [ -s _diff ] && exit 1 || exit 0 + env: + IGNORE_REGEX: test|lib|mock|interface|template|OrderedCallback|Pausable|Versioned|BeaconChainDepositor|PausableUntil + if: ${{ always() }} diff --git a/.github/workflows/fix-abi-pr.yml b/.github/workflows/fix-abi-pr.yml index 8fc55f014..8cf1cd021 100644 --- a/.github/workflows/fix-abi-pr.yml +++ b/.github/workflows/fix-abi-pr.yml @@ -9,55 +9,50 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - - name: Setup node.js version - uses: actions/setup-node@v1 - with: - node-version: 12.x - - - name: Get yarn cache directory path - id: yarn-cache-dir-path - run: echo "::set-output name=dir::$(yarn config get cacheFolder)" - - - name: Cache yarn cache - id: cache-yarn-cache - uses: actions/cache@v2 - with: - path: ${{ steps.yarn-cache-dir-path.outputs.dir }} - key: yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: yarn-${{ hashFiles('**/yarn.lock') }} - - - name: Cache node_modules - id: cache-node-modules - uses: actions/cache@v2 - with: - path: '**/node_modules' - key: node_modules-${{ hashFiles('**/yarn.lock') }} - restore-keys: node_modules-${{ hashFiles('**/yarn.lock') }} - - - name: Install modules - run: yarn - if: | - steps.cache-yarn-cache.outputs.cache-hit != 'true' || - steps.cache-node-modules.outputs.cache-hit != 'true' - - - name: Compile code and extract ABI - run: yarn compile - - - name: Check for ABI changes - id: changes - continue-on-error: true - run: .github/assert-git-changes.py - env: - TARGET_DIR: lib/abi/ - - - name: Create Pull Request - if: steps.changes.outcome != 'success' - uses: lidofinance/create-pull-request@v4 - with: - branch: fix-abi-${{ github.ref_name }} - delete-branch: true - commit-message: "fix: Make ABIs up to date" - title: "Fix ABI ${{ github.ref_name }}" - body: "This PR is generated automatically. Merge it to apply fixes to the /lib/abi/" + - uses: actions/checkout@v3 + with: + persist-credentials: false + + - name: Setup node.js version + uses: actions/setup-node@v3 + with: + node-version: 16 + + - name: Get yarn cache directory path + id: yarn-cache-dir-path + run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT + + - name: Cache yarn cache + id: cache-yarn-cache + uses: actions/cache@v3 + with: + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + key: yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: yarn-${{ hashFiles('**/yarn.lock') }} + + - name: Cache node_modules + id: cache-node-modules + uses: actions/cache@v3 + with: + path: '**/node_modules' + key: node_modules-${{ hashFiles('**/yarn.lock') }} + restore-keys: node_modules-${{ hashFiles('**/yarn.lock') }} + + - name: Install modules + run: yarn + if: | + steps.cache-yarn-cache.outputs.cache-hit != 'true' || + steps.cache-node-modules.outputs.cache-hit != 'true' + + - name: Compile code and extract ABI + run: yarn compile + + - name: Create Pull Request + uses: lidofinance/create-pull-request@v4 + with: + add-paths: lib/abi + branch: fix-abi-${{ github.ref_name }} + delete-branch: true + commit-message: 'fix: Make ABIs up to date' + title: 'Fix ABI ${{ github.ref_name }}' + body: 'This PR is generated automatically. Merge it to apply fixes to the /lib/abi/' diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml index b32d5dac4..ab42f861d 100644 --- a/.github/workflows/linters.yml +++ b/.github/workflows/linters.yml @@ -8,133 +8,90 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - - name: Setup node.js version - uses: actions/setup-node@v1 - with: - node-version: 12.x - - - name: Get yarn cache directory path - id: yarn-cache-dir-path - run: echo "::set-output name=dir::$(yarn config get cacheFolder)" - - - name: Cache yarn cache - id: cache-yarn-cache - uses: actions/cache@v2 - with: - path: ${{ steps.yarn-cache-dir-path.outputs.dir }} - key: yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: yarn-${{ hashFiles('**/yarn.lock') }} - - - name: Cache node_modules - id: cache-node-modules - uses: actions/cache@v2 - with: - path: '**/node_modules' - key: node_modules-${{ hashFiles('**/yarn.lock') }} - restore-keys: node_modules-${{ hashFiles('**/yarn.lock') }} - - - name: Install modules - run: yarn - if: | - steps.cache-yarn-cache.outputs.cache-hit != 'true' || - steps.cache-node-modules.outputs.cache-hit != 'true' - - - name: Run Solidity tests - run: yarn test:unit - - - name: Run Solidity linters - run: yarn lint:sol:solhint - - - name: Run JS linters - run: yarn lint:js - continue-on-error: true + - uses: actions/checkout@v3 + with: + persist-credentials: false + + - name: Setup node.js version + uses: actions/setup-node@v3 + with: + node-version: 16 + + - name: Get yarn cache directory path + id: yarn-cache-dir-path + run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT + + - name: Cache yarn cache + id: cache-yarn-cache + uses: actions/cache@v3 + with: + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + key: yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: yarn-${{ hashFiles('**/yarn.lock') }} + + - name: Cache node_modules + id: cache-node-modules + uses: actions/cache@v3 + with: + path: '**/node_modules' + key: node_modules-${{ hashFiles('**/yarn.lock') }} + restore-keys: node_modules-${{ hashFiles('**/yarn.lock') }} + + - name: Install modules + run: yarn + if: | + steps.cache-yarn-cache.outputs.cache-hit != 'true' || + steps.cache-node-modules.outputs.cache-hit != 'true' + + - name: Run Solidity tests + run: yarn test:unit + + - name: Run Solidity linters + run: yarn lint:sol + + - name: Run JS linters + run: yarn lint:js coverage: name: Solidity coverage runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - - name: Setup node.js version - uses: actions/setup-node@v1 - with: - node-version: 12.x - - - name: Get yarn cache directory path - id: yarn-cache-dir-path - run: echo "::set-output name=dir::$(yarn cache dir)" - - - name: Cache yarn cache - id: cache-yarn-cache - uses: actions/cache@v2 - with: - path: ${{ steps.yarn-cache-dir-path.outputs.dir }} - key: yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: yarn-${{ hashFiles('**/yarn.lock') }} - - - name: Cache node_modules - id: cache-node-modules - uses: actions/cache@v2 - with: - path: '**/node_modules' - key: node_modules-${{ hashFiles('**/yarn.lock') }} - restore-keys: node_modules-${{ hashFiles('**/yarn.lock') }} - - - name: Install modules - run: yarn - if: | - steps.cache-yarn-cache.outputs.cache-hit != 'true' || - steps.cache-node-modules.outputs.cache-hit != 'true' - - - name: Run Solidity test coverage - run: yarn test:coverage - continue-on-error: false - - abi-lint: - name: ABI actuality linter - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - - name: Setup node.js version - uses: actions/setup-node@v1 - with: - node-version: 12.x - - - name: Get yarn cache directory path - id: yarn-cache-dir-path - run: echo "::set-output name=dir::$(yarn config get cacheFolder)" - - - name: Cache yarn cache - id: cache-yarn-cache - uses: actions/cache@v2 - with: - path: ${{ steps.yarn-cache-dir-path.outputs.dir }} - key: yarn-${{ hashFiles('**/yarn.lock') }} - restore-keys: yarn-${{ hashFiles('**/yarn.lock') }} - - - name: Cache node_modules - id: cache-node-modules - uses: actions/cache@v2 - with: - path: '**/node_modules' - key: node_modules-${{ hashFiles('**/yarn.lock') }} - restore-keys: node_modules-${{ hashFiles('**/yarn.lock') }} - - - name: Install modules - run: yarn - if: | - steps.cache-yarn-cache.outputs.cache-hit != 'true' || - steps.cache-node-modules.outputs.cache-hit != 'true' - - - name: Compile code and extract ABI - run: yarn compile - - - name: Check for ABI changes - run: .github/assert-git-changes.py - env: - TARGET_DIR: lib/abi/ + - uses: actions/checkout@v3 + with: + persist-credentials: false + + - name: Setup node.js version + uses: actions/setup-node@v3 + with: + node-version: 16 + + - name: Get yarn cache directory path + id: yarn-cache-dir-path + run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT + + - name: Cache yarn cache + id: cache-yarn-cache + uses: actions/cache@v3 + with: + path: ${{ steps.yarn-cache-dir-path.outputs.dir }} + key: yarn-${{ hashFiles('**/yarn.lock') }} + restore-keys: yarn-${{ hashFiles('**/yarn.lock') }} + + - name: Cache node_modules + id: cache-node-modules + uses: actions/cache@v3 + with: + path: '**/node_modules' + key: node_modules-${{ hashFiles('**/yarn.lock') }} + restore-keys: node_modules-${{ hashFiles('**/yarn.lock') }} + + - name: Install modules + run: yarn + if: | + steps.cache-yarn-cache.outputs.cache-hit != 'true' || + steps.cache-node-modules.outputs.cache-hit != 'true' + + - name: Run Solidity test coverage + run: yarn test:coverage + continue-on-error: false diff --git a/.github/workflows/storage-layout-check.yml b/.github/workflows/storage-layout-check.yml new file mode 100644 index 000000000..41d545115 --- /dev/null +++ b/.github/workflows/storage-layout-check.yml @@ -0,0 +1,34 @@ +name: Assert storage layout changes + +on: + push: + branches: [develop, master] + pull_request: + branches: [develop, master] + +jobs: + assert: + runs-on: ubuntu-latest + name: Assert storage layout + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup node.js version + uses: actions/setup-node@v3 + with: + node-version: 16.x + cache: 'yarn' + + - name: Submodules + run: git submodule update --init --recursive + + - name: Install yarn deps + run: yarn + + - name: Check storage-layout + uses: lidofinance/storage-layout-action@v1 + with: + mode: check + src-folder: ./contracts + ignore-folders: '{test_helpers,template,mocks}' diff --git a/.github/workflows/storage-layout-update.yml b/.github/workflows/storage-layout-update.yml new file mode 100644 index 000000000..a8a4247ed --- /dev/null +++ b/.github/workflows/storage-layout-update.yml @@ -0,0 +1,38 @@ +name: Update .storage-layout + +on: + workflow_dispatch: + +jobs: + assert: + runs-on: ubuntu-latest + name: Update storage layout + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup node.js version + uses: actions/setup-node@v3 + with: + node-version: 16.x + cache: 'yarn' + + - name: Install yarn deps + run: yarn + + - name: Generate new .storage-layout + uses: lidofinance/storage-layout-action@v1 + with: + mode: generate + src-folder: ./contracts + ignore-folders: '{test_helpers,template,mocks}' + + - name: Create Pull Request + uses: lidofinance/create-pull-request@v4 + with: + add-paths: .storage-layout + branch: update-storage-layout-${{ github.ref_name }} + delete-branch: true + commit-message: 'fix: Make storage-layout up to date' + title: 'Update .storage-layout for ${{ github.ref_name }} branch' + body: 'This PR is generated automatically. Merge it to apply new version of .storage-layout' diff --git a/.gitignore b/.gitignore index 5ad0407c4..ddfcfd5c4 100644 --- a/.gitignore +++ b/.gitignore @@ -2,12 +2,15 @@ .vscode **/build/ **/node_modules/ +**/lib/abi/*.json **/artifacts/ +**/artifacts-userdoc/ .cache **/cache/ dist/ tmp/ venv +out /coverage/ /coverage.json @@ -38,3 +41,13 @@ cli/vendor /accounts.json # e2e temp data /deployed-e2e.json + +# OS relative +.DS_Store + +# foundry artifacts +foundry/cache +foundry/out + +logs +**/verificator_diffs/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..0c2994f67 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule "foundry/lib/forge-std"] + path = foundry/lib/forge-std + url = https://github.com/foundry-rs/forge-std + branch = v1.3.0 diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100755 index 000000000..b417ff56f --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,8 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +RED_COLOR='\033[0;31m' +NO_COLOR='\033[0m' + +yarn compile +yarn lint diff --git a/.mocharc.json b/.mocharc.json new file mode 100644 index 000000000..840674ec6 --- /dev/null +++ b/.mocharc.json @@ -0,0 +1,4 @@ +{ + "require": "hardhat/register", + "timeout": 40000 +} diff --git a/.prettierrc b/.prettierrc index 0d22c886f..c8c5e7306 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,8 +1,9 @@ { "singleQuote": true, "semi": false, - "trailingComma": "none", "bracketSpacing": true, "jsxBracketSameLine": false, - "printWidth": 140 + "options": { + "editorconfig": true + } } diff --git a/.solcover.js b/.solcover.js index aa18fdfbc..f5dca4935 100644 --- a/.solcover.js +++ b/.solcover.js @@ -1,5 +1,6 @@ module.exports = { - skipFiles: ['template', 'test_helpers', 'oracle/test_helpers', 'nos/test_helpers', 'mocks'], + // todo: add support for '**/test_helpers' globs + skipFiles: ['0.4.24/template', '0.4.24/test_helpers', '0.4.24/nos/test_helpers', '0.6.12/mocks', '0.8.9/test_helpers'], mocha: { enableTimeouts: false } diff --git a/.solhint.json b/.solhint.json index 9a11067d4..4d4b76fd9 100644 --- a/.solhint.json +++ b/.solhint.json @@ -1,11 +1,17 @@ { "extends": "solhint:recommended", + "plugins": ["lido"], "rules": { + "not-rely-on-time": "off", "no-inline-assembly": "off", "var-name-mixedcase": "off", "compiler-version": "off", "reason-string": "off", "no-empty-blocks": "off", - "func-name-mixedcase": "off" + "func-name-mixedcase": "off", + "lido/fixed-compiler-version": "error", + "visibility-modifier-order": "error", + "no-unused-vars": "error", + "func-visibility": ["warn", { "ignoreConstructors": true }] } } diff --git a/.soliumignore b/.solhintignore similarity index 100% rename from .soliumignore rename to .solhintignore diff --git a/.soliumrc.json b/.soliumrc.json deleted file mode 100644 index bd45f50f1..000000000 --- a/.soliumrc.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "extends": "solium:all", - "plugins": ["security"], - "rules": { - "security/no-low-level-calls": "off", - "security/no-inline-assembly": "off", - "security/no-assign-params": "warning", - "error-reason": "off", - "imports-on-top": "error", - "variable-declarations": "error", - "array-declarations": "error", - "operator-whitespace": "error", - "conditionals-whitespace": "error", - "comma-whitespace": "error", - "semicolon-whitespace": "error", - "function-whitespace": "error", - "lbrace": "error", - "mixedcase": "off", - "camelcase": "error", - "uppercase": "error", - "no-empty-blocks": "error", - "no-unused-vars": "error", - "quotes": "error", - "blank-lines": "error", - "indentation": "error", - "arg-overflow": ["error", 8], - "whitespace": "error", - "deprecated-suicide": "error", - "pragma-on-top": "error", - "function-order": [ - "error", - {"ignore": {"functions": ["initialize"]}} - ], - "emit": "error", - "no-constant": "error", - "value-in-payable": "error", - "max-len": "error", - "visibility-first": "error", - "linebreak-style": "error" - } -} diff --git a/.storage-layout b/.storage-layout new file mode 100644 index 000000000..a2e87d918 --- /dev/null +++ b/.storage-layout @@ -0,0 +1,217 @@ +👁👁 STORAGE LAYOUT snapshot 👁👁 +======================= + +======================= +➡ AccountingOracle +======================= + +| Name | Type | Slot | Offset | Bytes | Contract | +|------|------|------|--------|-------|----------| + +======================= +➡ BeaconChainDepositor +======================= + +| Name | Type | Slot | Offset | Bytes | Contract | +|------|------|------|--------|-------|----------| + +======================= +➡ Burner +======================= + +| Name | Type | Slot | Offset | Bytes | Contract | +|-----------------------------|---------|------|--------|-------|-----------------------------------| +| coverSharesBurnRequested | uint256 | 0 | 0 | 32 | contracts/0.8.9/Burner.sol:Burner | +| nonCoverSharesBurnRequested | uint256 | 1 | 0 | 32 | contracts/0.8.9/Burner.sol:Burner | +| totalCoverSharesBurnt | uint256 | 2 | 0 | 32 | contracts/0.8.9/Burner.sol:Burner | +| totalNonCoverSharesBurnt | uint256 | 3 | 0 | 32 | contracts/0.8.9/Burner.sol:Burner | + +======================= +➡ DepositContract +======================= + +| Name | Type | Slot | Offset | Bytes | Contract | +|---------------|-------------|------|--------|-------|-------------------------------------------------------| +| branch | bytes32[32] | 0 | 0 | 1024 | contracts/0.6.11/deposit_contract.sol:DepositContract | +| deposit_count | uint256 | 32 | 0 | 32 | contracts/0.6.11/deposit_contract.sol:DepositContract | +| zero_hashes | bytes32[32] | 33 | 0 | 1024 | contracts/0.6.11/deposit_contract.sol:DepositContract | + +======================= +➡ DepositSecurityModule +======================= + +| Name | Type | Slot | Offset | Bytes | Contract | +|---------------------------------|-----------------------------|------|--------|-------|-----------------------------------------------------------------| +| maxDepositsPerBlock | uint256 | 0 | 0 | 32 | contracts/0.8.9/DepositSecurityModule.sol:DepositSecurityModule | +| minDepositBlockDistance | uint256 | 1 | 0 | 32 | contracts/0.8.9/DepositSecurityModule.sol:DepositSecurityModule | +| pauseIntentValidityPeriodBlocks | uint256 | 2 | 0 | 32 | contracts/0.8.9/DepositSecurityModule.sol:DepositSecurityModule | +| owner | address | 3 | 0 | 20 | contracts/0.8.9/DepositSecurityModule.sol:DepositSecurityModule | +| quorum | uint256 | 4 | 0 | 32 | contracts/0.8.9/DepositSecurityModule.sol:DepositSecurityModule | +| guardians | address[] | 5 | 0 | 32 | contracts/0.8.9/DepositSecurityModule.sol:DepositSecurityModule | +| guardianIndicesOneBased | mapping(address => uint256) | 6 | 0 | 32 | contracts/0.8.9/DepositSecurityModule.sol:DepositSecurityModule | + +======================= +➡ EIP712StETH +======================= + +| Name | Type | Slot | Offset | Bytes | Contract | +|------|------|------|--------|-------|----------| + +======================= +➡ HashConsensus +======================= + +| Name | Type | Slot | Offset | Bytes | Contract | +|-----------------------|--------------------------------------------------------|------|--------|-------|--------------------------------------------------------| +| _frameConfig | struct HashConsensus.FrameConfig | 0 | 0 | 32 | contracts/0.8.9/oracle/HashConsensus.sol:HashConsensus | +| _memberStates | struct HashConsensus.MemberState[] | 1 | 0 | 32 | contracts/0.8.9/oracle/HashConsensus.sol:HashConsensus | +| _memberAddresses | address[] | 2 | 0 | 32 | contracts/0.8.9/oracle/HashConsensus.sol:HashConsensus | +| _memberIndices1b | mapping(address => uint256) | 3 | 0 | 32 | contracts/0.8.9/oracle/HashConsensus.sol:HashConsensus | +| _reportingState | struct HashConsensus.ReportingState | 4 | 0 | 32 | contracts/0.8.9/oracle/HashConsensus.sol:HashConsensus | +| _quorum | uint256 | 5 | 0 | 32 | contracts/0.8.9/oracle/HashConsensus.sol:HashConsensus | +| _reportVariants | mapping(uint256 => struct HashConsensus.ReportVariant) | 6 | 0 | 32 | contracts/0.8.9/oracle/HashConsensus.sol:HashConsensus | +| _reportVariantsLength | uint256 | 7 | 0 | 32 | contracts/0.8.9/oracle/HashConsensus.sol:HashConsensus | +| _reportProcessor | address | 8 | 0 | 20 | contracts/0.8.9/oracle/HashConsensus.sol:HashConsensus | + +======================= +➡ LegacyOracle +======================= + +| Name | Type | Slot | Offset | Bytes | Contract | +|------|------|------|--------|-------|----------| + +======================= +➡ Lido +======================= + +| Name | Type | Slot | Offset | Bytes | Contract | +|------|------|------|--------|-------|----------| + +======================= +➡ LidoExecutionLayerRewardsVault +======================= + +| Name | Type | Slot | Offset | Bytes | Contract | +|------|------|------|--------|-------|----------| + +======================= +➡ LidoLocator +======================= + +| Name | Type | Slot | Offset | Bytes | Contract | +|------|------|------|--------|-------|----------| + +======================= +➡ NodeOperatorsRegistry +======================= + +| Name | Type | Slot | Offset | Bytes | Contract | +|------|------|------|--------|-------|----------| + +======================= +➡ OracleDaemonConfig +======================= + +| Name | Type | Slot | Offset | Bytes | Contract | +|--------|--------------------------|------|--------|-------|-----------------------------------------------------------| +| values | mapping(string => bytes) | 0 | 0 | 32 | contracts/0.8.9/OracleDaemonConfig.sol:OracleDaemonConfig | + +======================= +➡ OracleReportSanityChecker +======================= + +| Name | Type | Slot | Offset | Bytes | Contract | +|---------|-------------------------|------|--------|-------|---------------------------------------------------------------------------------------| +| _limits | struct LimitsListPacked | 0 | 0 | 32 | contracts/0.8.9/sanity_checks/OracleReportSanityChecker.sol:OracleReportSanityChecker | + +======================= +➡ OssifiableProxy +======================= + +| Name | Type | Slot | Offset | Bytes | Contract | +|------|------|------|--------|-------|----------| + +======================= +➡ Pausable +======================= + +| Name | Type | Slot | Offset | Bytes | Contract | +|------|------|------|--------|-------|----------| + +======================= +➡ PausableUntil +======================= + +| Name | Type | Slot | Offset | Bytes | Contract | +|------|------|------|--------|-------|----------| + +======================= +➡ StETH +======================= + +| Name | Type | Slot | Offset | Bytes | Contract | +|------|------|------|--------|-------|----------| + +======================= +➡ StETHPermit +======================= + +| Name | Type | Slot | Offset | Bytes | Contract | +|------|------|------|--------|-------|----------| + +======================= +➡ StakingRouter +======================= + +| Name | Type | Slot | Offset | Bytes | Contract | +|------|------|------|--------|-------|----------| + +======================= +➡ ValidatorsExitBusOracle +======================= + +| Name | Type | Slot | Offset | Bytes | Contract | +|------|------|------|--------|-------|----------| + +======================= +➡ Versioned +======================= + +| Name | Type | Slot | Offset | Bytes | Contract | +|------|------|------|--------|-------|----------| + +======================= +➡ Versioned +======================= + +| Name | Type | Slot | Offset | Bytes | Contract | +|------|------|------|--------|-------|----------| + +======================= +➡ WithdrawalQueueERC721 +======================= + +| Name | Type | Slot | Offset | Bytes | Contract | +|------|------|------|--------|-------|----------| + +======================= +➡ WithdrawalVault +======================= + +| Name | Type | Slot | Offset | Bytes | Contract | +|------|------|------|--------|-------|----------| + +======================= +➡ WstETH +======================= + +| Name | Type | Slot | Offset | Bytes | Contract | +|--------------|-------------------------------------------------|------|--------|-------|------------------------------------| +| _balances | mapping(address => uint256) | 0 | 0 | 32 | contracts/0.6.12/WstETH.sol:WstETH | +| _allowances | mapping(address => mapping(address => uint256)) | 1 | 0 | 32 | contracts/0.6.12/WstETH.sol:WstETH | +| _totalSupply | uint256 | 2 | 0 | 32 | contracts/0.6.12/WstETH.sol:WstETH | +| _name | string | 3 | 0 | 32 | contracts/0.6.12/WstETH.sol:WstETH | +| _symbol | string | 4 | 0 | 32 | contracts/0.6.12/WstETH.sol:WstETH | +| _decimals | uint8 | 5 | 0 | 1 | contracts/0.6.12/WstETH.sol:WstETH | +| _nonces | mapping(address => struct Counters.Counter) | 6 | 0 | 32 | contracts/0.6.12/WstETH.sol:WstETH | +| stETH | contract IStETH | 7 | 0 | 20 | contracts/0.6.12/WstETH.sol:WstETH | diff --git a/README.md b/README.md index ce9f98e40..f92c36826 100644 --- a/README.md +++ b/README.md @@ -3,16 +3,15 @@ [![Tests](https://github.com/lidofinance/lido-dao/workflows/Tests/badge.svg)](https://github.com/lidofinance/lido-dao/actions) [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) -The Lido Ethereum Liquid Staking Protocol, built on Ethereum 2.0's Beacon chain, allows their users to earn staking rewards on the Beacon chain without locking Ether or maintaining staking infrastructure. +The Lido Ethereum Liquid Staking Protocol allows their users to earn staking rewards on the Beacon chain without locking Ether or maintaining staking infrastructure. Users can deposit Ether to the Lido smart contract and receive stETH tokens in return. The smart contract then stakes tokens with the DAO-picked node operators. Users' deposited funds are pooled by the DAO, node operators never have direct access to the users' assets. -Unlike staked ether, the stETH token is free from the limitations associated with a lack of liquidity and can be transferred at any time. The stETH token balance corresponds to the amount of Beacon chain Ether that the holder could withdraw if state transitions were enabled right now in the Ethereum 2.0 network. +Unlike staked ether, the stETH token is free from the limitations associated with a lack of liquidity and can be transferred at any time. The stETH token balance corresponds to the amount of Ether that the holder could request to withdraw. Before getting started with this repo, please read: -* [A Primer](https://lido.fi/static/Lido:Ethereum-Liquid-Staking.pdf) -* [Documentation](/docs) +* [Documentation](https://docs.lido.fi/) ## Lido DAO @@ -24,72 +23,15 @@ The Lido DAO is an [Aragon organization](https://aragon.org/dao). Since Aragon p ## Protocol levers -A full list of protocol levers that are controllable by the Aragon DAO can be found [here](docs/protocol-levers.md). +A full list of protocol levers that are controllable by the Aragon DAO can be found [here](https://docs.lido.fi/guides/protocol-levers/). ## Contracts -Most of the protocol is implemented as a set of smart contracts that extend the [AragonApp](https://github.com/aragon/aragonOS/blob/next/contracts/apps/AragonApp.sol) base contract. -These contracts are located in the [contracts/0.4.24](contracts/0.4.24) directory. Additionally, there are contracts that don't depend on the Aragon; they are located in the [contracts/0.6.12](contracts/0.6.12) directory. - -#### [Lido](contracts/0.4.24/Lido.sol) -Lido is the core contract which acts as a liquid staking pool. The contract is responsible for Ether deposits and withdrawals, minting and burning liquid tokens, delegating funds to node operators, applying fees, and accepting updates from the oracle contract. Node Operators' logic is extracted to a separate contract, NodeOperatorsRegistry. - -Lido also acts as an ERC20 token which represents staked ether, stETH. Tokens are minted upon deposit and burned when redeemed. stETH tokens are pegged 1:1 to the Ethers that are held by Lido. stETH token’s balances are updated when the oracle reports change in total stake every day. - -#### [NodeOperatorsRegistry](contracts/0.4.24/nos/NodeOperatorsRegistry.sol) -Node Operators act as validators on the Beacon chain for the benefit of the protocol. The DAO selects node operators and adds their addresses to the NodeOperatorsRegistry contract. Authorized operators have to generate a set of keys for the validation and also provide them with the smart contract. As Ether is received from users, it is distributed in chunks of 32 Ether between all active Node Operators. The contract contains a list of operators, their keys, and the logic for distributing rewards between them. The DAO can deactivate misbehaving operators. - -#### [LidoOracle](contracts/0.4.24/oracle/LidoOracle.sol) -LidoOracle is a contract where oracles send addresses' balances controlled by the DAO on the ETH 2.0 side. The balances can go up because of reward accumulation and can go down due to slashing and staking penalties. Oracles are assigned by the DAO. - -#### [WstETH](contracts/0.6.12/WstETH.sol) -It's an ERC20 token that represents the account's share of the total supply of stETH tokens. The balance of a wstETH token holder only changes on transfers, unlike the balance of stETH that is also changed when oracles report staking rewards, penalties, and slashings. It's a "power user" token that is required for some DeFi protocols like Uniswap v2, cross-chain bridges, etc. - -The contract also works as a wrapper that accepts stETH tokens and mints wstETH in return. The reverse exchange works exactly the opposite, the received wstETH tokens are burned, and stETH tokens are returned to the user with any accrued rebalance results. +For the contracts description see https://docs.lido.fi/ contracts section. ## Deployments -### Mainnet - -* Lido DAO: [`0xb8FFC3Cd6e7Cf5a098A1c92F48009765B24088Dc`](https://etherscan.io/address/0xb8FFC3Cd6e7Cf5a098A1c92F48009765B24088Dc) (proxy) -* LDO token: [`0x5A98FcBEA516Cf06857215779Fd812CA3beF1B32`](https://etherscan.io/address/0x5A98FcBEA516Cf06857215779Fd812CA3beF1B32) -* Lido and stETH token: [`0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84`](https://etherscan.io/address/0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84) (proxy) -* Node Operators registry: [`0x55032650b14df07b85bF18A3a3eC8E0Af2e028d5`](https://etherscan.io/address/0x55032650b14df07b85bF18A3a3eC8E0Af2e028d5) (proxy) -* Oracle: [`0x442af784A788A5bd6F42A01Ebe9F287a871243fb`](https://etherscan.io/address/0x442af784A788A5bd6F42A01Ebe9F287a871243fb) (proxy) -* WstETH token: [`0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0`](https://etherscan.io/token/0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0) -* Deposit Security Module: [`0xDb149235B6F40dC08810AA69869783Be101790e7`](https://etherscan.io/address/0xDb149235B6F40dC08810AA69869783Be101790e7) -* Aragon Voting: [`0x2e59A20f205bB85a89C53f1936454680651E618e`](https://etherscan.io/address/0x2e59A20f205bB85a89C53f1936454680651E618e) (proxy) -* Aragon Token Manager: [`0xf73a1260d222f447210581DDf212D915c09a3249`](https://etherscan.io/address/0xf73a1260d222f447210581DDf212D915c09a3249) (proxy) -* Aragon Finance: [`0xB9E5CBB9CA5b0d659238807E84D0176930753d86`](https://etherscan.io/address/0xB9E5CBB9CA5b0d659238807E84D0176930753d86) (proxy) -* Aragon Agent: [`0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c`](https://etherscan.io/address/0x3e40D73EB977Dc6a537aF587D48316feE66E9C8c) (proxy) - -### Görli+Prater testnet - -* Lido DAO: [`0x1dD91b354Ebd706aB3Ac7c727455C7BAA164945A`](https://goerli.etherscan.io/address/0x1dD91b354Ebd706aB3Ac7c727455C7BAA164945A) (proxy) -* LDO token: [`0x56340274fB5a72af1A3C6609061c451De7961Bd4`](https://goerli.etherscan.io/address/0x56340274fB5a72af1A3C6609061c451De7961Bd4) -* Lido and stETH token: [`0x1643E812aE58766192Cf7D2Cf9567dF2C37e9B7F`](https://goerli.etherscan.io/address/0x1643E812aE58766192Cf7D2Cf9567dF2C37e9B7F) (proxy) -* Node Operators registry: [`0x9D4AF1Ee19Dad8857db3a45B0374c81c8A1C6320`](https://goerli.etherscan.io/address/0x9D4AF1Ee19Dad8857db3a45B0374c81c8A1C6320) (proxy) -* Oracle: [`0x24d8451BC07e7aF4Ba94F69aCDD9ad3c6579D9FB`](https://goerli.etherscan.io/address/0x24d8451BC07e7aF4Ba94F69aCDD9ad3c6579D9FB) (proxy) -* WstETH token: [`0x1643e812ae58766192cf7d2cf9567df2c37e9b7f`](https://goerli.etherscan.io/address/0x1643e812ae58766192cf7d2cf9567df2c37e9b7f) -* Deposit Security Module: [`0xEd23AD3EA5Fb9d10e7371Caef1b141AD1C23A80c`](https://goerli.etherscan.io/address/0xEd23AD3EA5Fb9d10e7371Caef1b141AD1C23A80c) -* Aragon Voting: [`0xbc0B67b4553f4CF52a913DE9A6eD0057E2E758Db`](https://goerli.etherscan.io/address/0xbc0B67b4553f4CF52a913DE9A6eD0057E2E758Db) (proxy) -* Aragon Token Manager: [`0xDfe76d11b365f5e0023343A367f0b311701B3bc1`](https://goerli.etherscan.io/address/0xDfe76d11b365f5e0023343A367f0b311701B3bc1) (proxy) -* Aragon Finance: [`0x75c7b1D23f1cad7Fb4D60281d7069E46440BC179`](https://goerli.etherscan.io/address/0x75c7b1D23f1cad7Fb4D60281d7069E46440BC179) (proxy) -* Aragon Agent: [`0x4333218072D5d7008546737786663c38B4D561A4`](https://goerli.etherscan.io/address/0x4333218072D5d7008546737786663c38B4D561A4) (proxy) - -### Görli+Pyrmont testnet - -* Lido DAO: [`0xE9c991d2c9Ac29b041C8D05484C2104bD00CFF4b`](https://goerli.etherscan.io/address/0xE9c991d2c9Ac29b041C8D05484C2104bD00CFF4b) (proxy) -* LDO token: [`0xF837FBd803Ad6EdA0a89c5acF8785034F5aB33f2`](https://goerli.etherscan.io/address/0xF837FBd803Ad6EdA0a89c5acF8785034F5aB33f2) -* stETH token: [`0xA0cA1c13721BAB3371E0609FFBdB6A6B8e155CC0`](https://goerli.etherscan.io/address/0xA0cA1c13721BAB3371E0609FFBdB6A6B8e155CC0) (proxy) -* cstETH token: [`0x259d1D7058db3C7cB2aa15f60c1f40f261e9A009`](https://goerli.etherscan.io/address/0x259d1D7058db3C7cB2aa15f60c1f40f261e9A009) -* Lido: [`0xA5d26F68130c989ef3e063c9bdE33BC50a86629D`](https://goerli.etherscan.io/address/0xA5d26F68130c989ef3e063c9bdE33BC50a86629D) (proxy) -* Node Operators registry: [`0xB1e7Fb9E9A71063ab552dDEE87Ea8C6eEc7F5c7A`](https://goerli.etherscan.io/address/0xB1e7Fb9E9A71063ab552dDEE87Ea8C6eEc7F5c7A) (proxy) -* Oracle: [`0x8aA931352fEdC2A5a5b3E20ed3A546414E40D86C`](https://goerli.etherscan.io/address/0x8aA931352fEdC2A5a5b3E20ed3A546414E40D86C) (proxy) -* Aragon Voting: [`0xA54DBf1B494113fBDA2E593419eE7241EfE8B766`](https://goerli.etherscan.io/address/0xA54DBf1B494113fBDA2E593419eE7241EfE8B766) (proxy) -* Aragon token manager: [`0xB90D5df4aBDf5F69a00088d43E4A0Fa8A8b44244`](https://goerli.etherscan.io/address/0xB90D5df4aBDf5F69a00088d43E4A0Fa8A8b44244) (proxy) -* Aragon finance: [`0xfBfa38921d745FD7bE9fa657FFbcDFecC4Ab7Cd4`](https://goerli.etherscan.io/address/0xfBfa38921d745FD7bE9fa657FFbcDFecC4Ab7Cd4) (proxy) -* Aragon agent: [`0xd616af91a0C3fE5AEeA0c1FaEfC2d73AcA82F0c9`](https://goerli.etherscan.io/address/0xd616af91a0C3fE5AEeA0c1FaEfC2d73AcA82F0c9) (proxy) +For the protocol contracts addresses see https://docs.lido.fi/deployed-contracts/ ## Development @@ -103,8 +45,9 @@ The contract also works as a wrapper that accepts stETH tokens and mints wstETH * curl * cut * docker -* node.js v12 +* node.js v16 * (optional) Lerna +* (optional) Foundry ### Installing Aragon & other deps @@ -122,84 +65,7 @@ otherwise: npx yarn ``` -### Building docker containers - -```bash -cd e2e -docker-compose build --no-cache -``` - -### Starting & stopping e2e environment - -> ***All E2E operations must be launched under the `./e2e` subdirectory*** - -The E2E environment consists of two parts: ETH1-related processes and ETH 2.0-related processes. - -For ETH1 part: Ethereum single node (ganache), IPFS docker containers and Aragon Web App. - -For ETH2 part: Beacon chain node, genesis validators machine, and, optionally, 2nd and 3rd peer beacon chain nodes. - -To start the whole environment from pre-deployed snapshots, use: - -```bash -./startup.sh -r -s -``` - -then go to [http://localhost:3000/#/lido-dao/](http://localhost:3000/#/lido-dao/) to manage the DAO via Aragon Web App. - -> To completely repeat the compilation and deployment process in ETH1 chain, just omit the `-s` flag. - -#### ETH1 part - -As a result of the script execution, the following will be installed: - -* the Deposit Contract instance; -* all Aragon App instances (contracts: Lido, NodeOperatorsRegistry, and LidoOracle) -* the Aragon PM for `lido.eth`; -* the Lido DAO template; -* and finally, the Lido DAO will be deployed. - -To start only the ETH1 part, use: - -```bash -./startup.sh -1 -``` - -#### ETH2 part - -To work with the ETH2 part, the ETH1 part must be running. - -As a result of the script execution, the following will happen: - -* the Beacon chain genesis config (Minimal with tunes) will be generated; -* validator's wallet with 4 keys will be generated; -* a deposit of 32 ETH will be made to the Deposit Contract for each validator key; -* based on the events about the deposit, a genesis block will be created, including validators; -* ETH2 node will start from the new Genesis block. - -To reseat and restart only the ETH2 part, use: - -```bash -./startup.sh -r2 -``` - -##### Stop all - -To stop, use: -> Note: this action permanently deletes all generated data - -```bash -./shutdown.sh -``` - -### DKG - -To build a DGK container: - - * Add your local SSH key to the Github account; - * run `./dkg.sh` inside the `e2e` directory. - -### Build & test all our apps +### Build & test Run unit tests: @@ -207,16 +73,6 @@ Run unit tests: yarn test ``` -Run E2E tests: - -```bash -cd e2e -./dkg.sh -./startup.sh -r -s -yarn test:e2e -./shutdown.sh -``` - Run unit tests and report gas used by each Solidity function: ```bash @@ -239,6 +95,14 @@ so full branch coverage will never be reported until [solidity-coverage#219]: https://github.com/sc-forks/solidity-coverage/issues/269 +Run fuzzing tests with foundry: + +```bash +curl -L https://foundry.paradigm.xyz | bash +foundryup +forge test +``` + ## Deploying We have several ways to deploy lido smart-contracts and run DAO locally, you can find documents here: @@ -252,7 +116,7 @@ To develop/test on fork, please see [fork documentation](/docs/dev-fork.md) # License -2020 Lido +2023 Lido This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/SCRATCH_DEPLOY.md b/SCRATCH_DEPLOY.md index 2edee4799..1b190ce93 100644 --- a/SCRATCH_DEPLOY.md +++ b/SCRATCH_DEPLOY.md @@ -1,5 +1,7 @@ # Deploy Lido protocol from scratch +**NB**: at the moment the deployment from scratch scripts and manual target Lido V1 version (before Lido V2 upgrade) and is not working + Video guide: [youtube](https://www.youtube.com/watch?v=dCMXcfglJv0) ## Requirements @@ -53,7 +55,10 @@ Steps for deploy: * [ ] deploy Lido Apps repo contracts (via Lido Template) * [ ] deploy Lido DAO contract (via Lido Template) * [ ] issue DAO tokens (via Lido Template) +* [ ] deploy LidoExecutionLayerRewardsVault * [ ] finalize DAO setup (via Lido Template) +* [ ] deploy CompositePostRebaseBeaconReceiver +* [ ] deploy Burner * [ ] make final deployed DAO check via script * [ ] open and check Lido DAO web interface (via Aragon client) @@ -69,7 +74,7 @@ So, one-click local deploy from scratch command is: ## Aragon dependency issue -`ipfs-http-client` version has been strictly pinned to `43.0.0` in [this commit](https://github.com/lidofinance/lido-dao/commit/38bf0232fbc59ec6d69d27e170e3e75cfbe1ba11) because `/scripts/multisig/04-publish-app-frontends.js` used to crash at +`ipfs-http-client` version has been strictly pinned to `43.0.0` in [this commit](https://github.com/lidofinance/lido-dao/commit/38bf0232fbc59ec6d69d27e170e3e75cfbe1ba11) because `/scripts/multisig/04-publish-app-frontends.js` used to crash at ```javascript const rootCid = await uploadDirToIpfs({ dirPath: distPath, ipfsApiUrl: ipfsAPI }) ``` diff --git a/accounts.sample.json b/accounts.sample.json index f5bf655e6..a0540ca96 100644 --- a/accounts.sample.json +++ b/accounts.sample.json @@ -17,7 +17,8 @@ "path": "m/44'/60'/0'/0", "initialIndex": 0, "count": 50 - } + }, + "mainnet-fork-shapella-upgrade": ["4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d"] }, "infura": { "projectId": "INFURA_PROJECT_ID" diff --git a/apps/lido/README.md b/apps/lido/README.md index bbc97f5ff..16099d544 100644 --- a/apps/lido/README.md +++ b/apps/lido/README.md @@ -1,16 +1,16 @@ # Lido Aragon App -This directory contains source files for the [Lido app](https://mainnet.lido.fi/#/lido-dao/0xae7ab96520de3a18e5e111b5eaab095312d7fe84/) that displays the core state of the protocol and provides controls for its essentials parameters. +This directory contains source files for the [Lido Aragon frontend app](https://mainnet.lido.fi/#/lido-dao/0xae7ab96520de3a18e5e111b5eaab095312d7fe84/). ## Verifying source code -To verify that the Lido app deployed at [Lido DAO](https://mainnet.lido.fi) was built from this source code, please follow instructions below. +To verify that the Lido app frontend was built from this source code, please follow instructions below. ### Prerequisites - git -- Node.js 14+ -- ipfs 0.12.0 +- Node.js 16.14.2 +- ipfs 0.19.0 ### 1. Replicating IPFS hash and content URI @@ -26,10 +26,10 @@ Go into the directory, cd lido-dao ``` -Checkout [this commit](https://github.com/lidofinance/lido-dao/commit/5a30b1f7a461840e5919af57546887820b0b6dd0) (the latest `yarn.lock` update for the Lido app), +Checkout [this commit](https://github.com/lidofinance/lido-dao/commit/c3f680fc25d5ea48de69b65f4aff1f71723ef0e0) (the latest `yarn.lock` update for the Lido app), ```bash -git checkout 5a30b1f7a461840e5919af57546887820b0b6dd0 +git checkout c3f680fc25d5ea48de69b65f4aff1f71723ef0e0 ``` Install dependencies **without updating the lockfile**. This will make sure that you're using the same versions of the dependencies that were used to develop the app, @@ -53,22 +53,19 @@ ipfs add -qr --only-hash apps/lido/dist/ | tail -n 1 ``` -This command should output `QmScYxzmmrAV1cDBjL3i7jzaZuiJ76UqdaFZiMgsxoFGzC`. +This command should output `QmRSXAZrF2xR5rgbUdErDV6LGtjqQ1T4AZgs6yoXosMQc3`. -Now we have to obtain the content URI, which is this hash encoded for Aragon. The script that can do that for us is in [this commit](2d5b5d93d019838c91bdb9085e00af3358fbaecd), so we check it out. -``` -git checkout 2d5b5d93d019838c91bdb9085e00af3358fbaecd -``` +Now we have to obtain the content URI, which is this hash encoded for Aragon. Now we run the script, ```bash -export IPFS_HASH=QmScYxzmmrAV1cDBjL3i7jzaZuiJ76UqdaFZiMgsxoFGzC +export IPFS_HASH=QmRSXAZrF2xR5rgbUdErDV6LGtjqQ1T4AZgs6yoXosMQc3 npx hardhat run scripts/helpers/getContentUri.js ``` -This command should print `0x697066733a516d536359787a6d6d724156316344426a4c3369376a7a615a75694a373655716461465a694d6773786f46477a43`, which is our content URI. +This command should print `0x697066733a516d525358415a724632785235726762556445724456364c47746a7151315434415a677336796f586f734d516333`, which is our content URI. ### 2. Verifying on-chain Lido App content URI @@ -80,6 +77,6 @@ Now that we have the IPFS hash and content URI, let's see that it is, in fact, t Open the [Lido app](https://mainnet.lido.fi/#/lido-dao/0xae7ab96520de3a18e5e111b5eaab095312d7fe84/) in your browser, then open the network inspector and refresh the page to track all of the network requests that the website makes. -You will find that one of the two HTML files has, in fact, been loaded from `https://ipfs.mainnet.fi/ipfs/QmScYxzmmrAV1cDBjL3i7jzaZuiJ76UqdaFZiMgsxoFGzC/index.html`. +You will find that one of the two HTML files has, in fact, been loaded from `https://ipfs.mainnet.fi/ipfs/QmRSXAZrF2xR5rgbUdErDV6LGtjqQ1T4AZgs6yoXosMQc3/index.html`. You are done! ✨ diff --git a/apps/lido/app/package.json b/apps/lido/app/package.json index 4fd67bb1d..dc14dec61 100644 --- a/apps/lido/app/package.json +++ b/apps/lido/app/package.json @@ -19,25 +19,25 @@ "yup": "^0.29.3" }, "devDependencies": { - "@babel/core": "^7.11.6", + "@babel/core": "^7.21.0", "@babel/preset-env": "^7.11.5", "@babel/preset-react": "^7.10.1", "babel-eslint": "^10.1.0", "babel-plugin-styled-components": "^1.11.1", "copyfiles": "^2.3.0", - "eslint": "^7.9.0", - "eslint-config-prettier": "^6.11.0", - "eslint-config-standard": "^14.1.1", + "eslint": "^8.34.0", + "eslint-config-prettier": "^8.6.0", + "eslint-config-standard": "^17.0.0", "eslint-config-standard-react": "^9.2.0", - "eslint-plugin-import": "^2.22.0", + "eslint-plugin-import": "^2.27.5", "eslint-plugin-node": "^11.1.0", - "eslint-plugin-prettier": "^3.1.4", - "eslint-plugin-promise": "^4.2.1", + "eslint-plugin-prettier": "^4.2.1", + "eslint-plugin-promise": "^6.1.1", "eslint-plugin-react": "^7.20.6", "eslint-plugin-react-hooks": "^4.1.2", - "eslint-plugin-standard": "^4.0.1", + "eslint-plugin-standard": "^5.0.0", "parcel-bundler": "^1.12.4", - "prettier": "^2.1.2" + "prettier": "^2.8.4" }, "scripts": { "build": "yarn sync-assets && yarn build:app && yarn build:script", diff --git a/apps/lido/app/src/App.js b/apps/lido/app/src/App.js index e20885dc0..19506e8a0 100644 --- a/apps/lido/app/src/App.js +++ b/apps/lido/app/src/App.js @@ -2,10 +2,11 @@ import { useAragonApi } from '@aragon/api-react' import { Header, Main, SyncIndicator, useTheme } from '@aragon/ui' import React from 'react' import { ThemeProvider } from 'styled-components' -import { BeaconStats } from './components/BeaconStats' + +import { Primary } from './components/Primary' +import { Secondary } from './components/Secondary' import { Split } from './components/shared' -import { StakingLimitState } from './components/StakingLimitState' -import { State } from './components/state' + export default function App() { const { appState, currentApp, guiStyle } = useAragonApi() @@ -21,12 +22,9 @@ export default function App() { {isSyncing && }
} + primary={} secondary={ - <> - - - + } /> diff --git a/apps/lido/app/src/LidoLocator.abi.json b/apps/lido/app/src/LidoLocator.abi.json new file mode 100644 index 000000000..855c8079d --- /dev/null +++ b/apps/lido/app/src/LidoLocator.abi.json @@ -0,0 +1,281 @@ +[ + { + "constant": true, + "inputs": [], + "name": "accountingOracle", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "burner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "coreComponents", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "depositSecurityModule", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "elRewardsVault", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "legacyOracle", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "lido", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "oracleDaemonConfig", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "oracleReportComponentsForLido", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "oracleReportSanityChecker", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "postTokenRebaseReceiver", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "stakingRouter", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "treasury", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "validatorsExitBusOracle", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "withdrawalQueue", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "constant": true, + "inputs": [], + "name": "withdrawalVault", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + } +] \ No newline at end of file diff --git a/apps/lido/app/src/components/BeaconStats.js b/apps/lido/app/src/components/BeaconStats.js deleted file mode 100644 index 3033fa7d8..000000000 --- a/apps/lido/app/src/components/BeaconStats.js +++ /dev/null @@ -1,22 +0,0 @@ -import { useAppState } from '@aragon/api-react' -import { Box } from '@aragon/ui' -import React from 'react' -import { ListItem, LoadableElement } from './shared' -import { Ether } from './shared/Ether' - -export const BeaconStats = () => { - const { beaconStat } = useAppState() - - return ( - - - - {beaconStat?.depositedValidators} - - - - - - - ) -} diff --git a/apps/lido/app/src/components/Primary.js b/apps/lido/app/src/components/Primary.js new file mode 100644 index 000000000..9c9c55b10 --- /dev/null +++ b/apps/lido/app/src/components/Primary.js @@ -0,0 +1,116 @@ +import { useAppState } from '@aragon/api-react' +import React from 'react' +import { + BoxUnpadded, + BytesBadge, + ListItem, + ListItemAddress, + ListItemBasisPoints, + ListItemBoolean, + ListItemEther, + ListItemUnformattedValue, + LoadableElement +} from './shared' + +export const Primary = () => { + const { + isStopped, + canDeposit, + bufferedEther, + depositableEther, + totalPooledEther, + totalELRewardsCollected, + beaconStat, + fee, + feeDistribution, + withdrawalCredentials, + treasury, + legacyOracle, + recoveryVault, + lidoLocator, + lido, + accountingOracle, + burner, + depositSecurityModule, + elRewardsVault, + oracleDaemonConfig, + oracleReportSanityChecker, + postTokenRebaseReceiver, + stakingRouter, + validatorsExitBusOracle, + withdrawalQueue, + withdrawalVault, + } = useAppState() + + return ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) +} diff --git a/apps/lido/app/src/components/Secondary.js b/apps/lido/app/src/components/Secondary.js new file mode 100644 index 000000000..1663e26ea --- /dev/null +++ b/apps/lido/app/src/components/Secondary.js @@ -0,0 +1,81 @@ +import { useAppState, useAragonApi } from '@aragon/api-react' +import React from 'react' +import { + BoxUnpadded, ListItem, + ListItemAddress, ListItemBoolean, + ListItemEther, + ListItemUnformattedValue, + LoadableElement, + RestorationRate, + Tooltip, + Ether +} from './shared' + +export const Secondary = () => { + const { lido, symbol, decimals, totalSupply, stakeLimitFullInfo, hasInitialized, initializationBlock, contractVersion } = useAppState() + + return ( + <> + + + + + + + + + + {stakeLimitFullInfo?.isStakingPaused ? 'Yes' : 'No'} + + + + Limit set + + } + > + + {stakeLimitFullInfo?.isStakingLimitSet ? 'Yes' : 'No'} + + + + Max limit + + } + > + + + + + + Restoration + + } + > + + + + + + + + + + ) +} diff --git a/apps/lido/app/src/components/StakingLimitState.js b/apps/lido/app/src/components/StakingLimitState.js deleted file mode 100644 index 85f52a3a2..000000000 --- a/apps/lido/app/src/components/StakingLimitState.js +++ /dev/null @@ -1,51 +0,0 @@ -import { useAppState } from '@aragon/api-react' -import { Box } from '@aragon/ui' -import React from 'react' -import { ListItem, LoadableElement, RestorationRate, Tooltip } from './shared' -import { Ether } from './shared/Ether' - -export const StakingLimitState = () => { - const { stakingLimitInfo } = useAppState() - - return ( - - - - {stakingLimitInfo?.isStakingPaused ? 'Yes' : 'No'} - - - - Limit set - - } - > - - {stakingLimitInfo?.isStakingLimitSet ? 'Yes' : 'No'} - - - - Max limit - - } - > - - - - Restoration - - } - > - - - - ) -} diff --git a/apps/lido/app/src/components/shared/Ether.js b/apps/lido/app/src/components/shared/Ether.js index e6bc5739e..5d2f80694 100644 --- a/apps/lido/app/src/components/shared/Ether.js +++ b/apps/lido/app/src/components/shared/Ether.js @@ -1,13 +1,12 @@ import React from 'react' -import { LoadingRing } from '@aragon/ui' import { formatEth } from '../../utils' import { constants } from 'ethers' -export const Ether = ({ ether }) => { - if (typeof ether === 'undefined') { - return - } - +export const Ether = ({ + ether, + symbol = constants.EtherSymbol, + symbolAfter = false, +}) => { try { ether = formatEth(ether) } catch (error) { @@ -17,8 +16,9 @@ export const Ether = ({ ether }) => { return ( - {constants.EtherSymbol} + {!symbolAfter && symbol} {ether} + {symbolAfter && ' ' + symbol} ) } diff --git a/apps/lido/app/src/components/shared/ListItem.js b/apps/lido/app/src/components/shared/ListItem.js index ee920d1f0..facd2f490 100644 --- a/apps/lido/app/src/components/shared/ListItem.js +++ b/apps/lido/app/src/components/shared/ListItem.js @@ -6,11 +6,15 @@ const ListItemStyle = styled.li` display: flex; justify-content: space-between; align-items: center; - margin: ${GU * 3}px 0 0 ${(props) => (props.nested ? GU * 4 : 0)}px; + padding: ${GU}px ${GU * 3}px ${GU}px + ${(props) => (props.nested ? GU * 6 : GU * 3)}px; line-height: 40px; + border-top: 1px solid + ${(props) => (props.isDark ? '#2C3A58' : props.theme.border)}; & :first-of-type { margin-top: 0; + border-top: none; } ` @@ -26,9 +30,11 @@ const ListItemValue = styled.strong` export const ListItem = ({ label, children, nested }) => { const theme = useTheme() + const themeDark = theme?._name === 'dark' + return ( - - {label} + + {label} {children} ) diff --git a/apps/lido/app/src/components/shared/ListItemAddress.js b/apps/lido/app/src/components/shared/ListItemAddress.js new file mode 100644 index 000000000..9169852e7 --- /dev/null +++ b/apps/lido/app/src/components/shared/ListItemAddress.js @@ -0,0 +1,13 @@ +import { IdentityBadge } from '@aragon/ui' +import React from 'react' +import { ListItem, LoadableElement } from '../shared' + +export const ListItemAddress = ({ label, value }) => { + return ( + + + + + + ) +} diff --git a/apps/lido/app/src/components/shared/ListItemBasisPoints.js b/apps/lido/app/src/components/shared/ListItemBasisPoints.js new file mode 100644 index 000000000..e33e4aea3 --- /dev/null +++ b/apps/lido/app/src/components/shared/ListItemBasisPoints.js @@ -0,0 +1,14 @@ +import React from 'react' +import { BasisPoints } from './BasisPoints' +import { ListItem } from './ListItem' +import { LoadableElement } from './LoadableElement' + +export const ListItemBasisPoints = ({ label, value, ...rest }) => { + return ( + + + + + + ) +} diff --git a/apps/lido/app/src/components/shared/ListItemBoolean.js b/apps/lido/app/src/components/shared/ListItemBoolean.js new file mode 100644 index 000000000..be4803efe --- /dev/null +++ b/apps/lido/app/src/components/shared/ListItemBoolean.js @@ -0,0 +1,11 @@ +import React from 'react' +import { ListItem } from './ListItem' +import { LoadableElement } from './LoadableElement' + +export const ListItemBoolean = ({ label, value, renderElements = ["Yes", "No"] }) => { + return ( + + {value ? renderElements[0] : renderElements[1]} + + ) +} diff --git a/apps/lido/app/src/components/shared/ListItemEther.js b/apps/lido/app/src/components/shared/ListItemEther.js new file mode 100644 index 000000000..5d4108188 --- /dev/null +++ b/apps/lido/app/src/components/shared/ListItemEther.js @@ -0,0 +1,14 @@ +import React from 'react' +import { ListItem } from './ListItem' +import { Ether } from './Ether' +import { LoadableElement } from './LoadableElement' + +export const ListItemEther = ({ label, value, symbol, symbolAfter }) => { + return ( + + + + + + ) +} diff --git a/apps/lido/app/src/components/shared/ListItemUnformattedValue.js b/apps/lido/app/src/components/shared/ListItemUnformattedValue.js new file mode 100644 index 000000000..9afaa736b --- /dev/null +++ b/apps/lido/app/src/components/shared/ListItemUnformattedValue.js @@ -0,0 +1,11 @@ +import React from 'react' +import { ListItem } from './ListItem' +import { LoadableElement } from './LoadableElement' + +export const ListItemUnformattedValue = ({ label, value }) => { + return ( + + {value} + + ) +} diff --git a/apps/lido/app/src/components/shared/index.js b/apps/lido/app/src/components/shared/index.js index ab21f1c2f..896d3b784 100644 --- a/apps/lido/app/src/components/shared/index.js +++ b/apps/lido/app/src/components/shared/index.js @@ -7,3 +7,9 @@ export { LoadableElement } from './LoadableElement' export { Tooltip } from './Tooltip' export { RestorationRate } from './RestorationRate' export { Split } from './Split' +export { ListItemEther } from './ListItemEther' +export { ListItemUnformattedValue } from './ListItemUnformattedValue' +export { ListItemBoolean } from './ListItemBoolean' +export { ListItemAddress } from './ListItemAddress' +export { ListItemBasisPoints } from './ListItemBasisPoints' +export { Ether } from './Ether' diff --git a/apps/lido/app/src/components/shared/styles.js b/apps/lido/app/src/components/shared/styles.js index c94e07e57..54e4e9101 100644 --- a/apps/lido/app/src/components/shared/styles.js +++ b/apps/lido/app/src/components/shared/styles.js @@ -1,4 +1,4 @@ -import { GU, Info } from '@aragon/ui' +import { Box, GU, Info } from '@aragon/ui' import styled from 'styled-components' export const InfoSpaced = styled(Info)` @@ -18,3 +18,9 @@ export const Controls = styled.div` margin-right: ${GU * 2}px; } ` + +export const BoxUnpadded = styled(Box)` + & > div { + padding: 0; + } +` diff --git a/apps/lido/app/src/components/state/BufferedEther.js b/apps/lido/app/src/components/state/BufferedEther.js deleted file mode 100644 index 0ca60fd3f..000000000 --- a/apps/lido/app/src/components/state/BufferedEther.js +++ /dev/null @@ -1,14 +0,0 @@ -import { useAppState } from '@aragon/api-react' -import React from 'react' -import { ListItem } from '../shared' -import { Ether } from '../shared/Ether' - -export const BufferedEther = () => { - const { bufferedEther } = useAppState() - - return ( - - - - ) -} diff --git a/apps/lido/app/src/components/state/DepositContract.js b/apps/lido/app/src/components/state/DepositContract.js deleted file mode 100644 index 0e5056d78..000000000 --- a/apps/lido/app/src/components/state/DepositContract.js +++ /dev/null @@ -1,16 +0,0 @@ -import { useAppState } from '@aragon/api-react' -import { IdentityBadge } from '@aragon/ui' -import React from 'react' -import { ListItem, LoadableElement } from '../shared' - -export const DepositContract = () => { - const { depositContract } = useAppState() - - return ( - - - - - - ) -} diff --git a/apps/lido/app/src/components/state/ElRewardsVault.js b/apps/lido/app/src/components/state/ElRewardsVault.js deleted file mode 100644 index fbfaf3b04..000000000 --- a/apps/lido/app/src/components/state/ElRewardsVault.js +++ /dev/null @@ -1,98 +0,0 @@ -import { useAppState, useAragonApi } from '@aragon/api-react' -import { Button, IconEdit, IdentityBadge, SidePanel } from '@aragon/ui' -import { Field, Form, Formik } from 'formik' -import React, { useState } from 'react' -import { - Controls, - IconButton, - InfoSpaced, - ListItem, - LoadableElement, - TextField, -} from '../shared' -import * as yup from 'yup' -import { isAddress } from 'web3-utils' - -const fieldName = 'vault' - -const initialValues = { - [fieldName]: '', -} - -const validationSchema = yup.object().shape({ - vault: yup - .string('Vault must be a string.') - .test(fieldName, 'Vault must be a valid address.', (vault) => { - return isAddress(vault) - }), -}) - -export const ElRewardsVault = () => { - const { api } = useAragonApi() - const { elRewardsVault } = useAppState() - - const [sidePanelOpen, setSidePanelOpen] = useState(false) - const openSidePanel = () => setSidePanelOpen(true) - const closeSidePanel = () => setSidePanelOpen(false) - - const submit = ({ vault }) => { - api - .setELRewardsVault(vault) - .toPromise() - .catch(console.error) - .finally(closeSidePanel) - } - - return ( - - - - - - } onClick={openSidePanel} /> - - - - Set a new address for the execution layer rewards vault contract. - - - {({ submitForm, isSubmitting, isValidating }) => { - const handleSubmit = (event) => { - event.preventDefault() - submitForm() - } - - return ( -
- -