Skip to content

Commit

Permalink
Merge pull request #426 from lidofinance/develop
Browse files Browse the repository at this point in the history
Feat: merge-ready protocol
  • Loading branch information
TheDZhon committed Jun 24, 2022
2 parents 816bf1d + 6dc9c59 commit df95e56
Show file tree
Hide file tree
Showing 322 changed files with 19,803 additions and 6,397 deletions.
128 changes: 128 additions & 0 deletions .github/assert-deployed-bytecode.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
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])
})
16 changes: 16 additions & 0 deletions .github/assert-git-changes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/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)
21 changes: 21 additions & 0 deletions .github/deployed-bytecode-consts.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
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'],
['depositor', 'DepositSecurityModule']
])

const IGNORE_METADATA_CONTRACTS = ['WstETH']

module.exports = {
APPS_TO_NAMES,
CONTRACTS_TO_NAMES,
IGNORE_METADATA_CONTRACTS
}
24 changes: 24 additions & 0 deletions .github/prepare-accounts-json.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/usr/bin/python3
from pathlib import Path
import fileinput
import shutil
import os

INFURA_PROJECT_ID = os.environ.get("INFURA_PROJECT_ID")
ETHERSCAN_API_KEY = os.environ.get("ETHERSCAN_API_KEY")

ACCOUNTS_TMPL = Path("./accounts.sample.json")
ACCOUNTS = Path("./accounts.json")


def main():
shutil.copyfile(ACCOUNTS_TMPL, ACCOUNTS)
with fileinput.FileInput(ACCOUNTS, inplace=True) as file:
for line in file:
updated_line = line.replace("INFURA_PROJECT_ID", INFURA_PROJECT_ID)
updated_line = updated_line.replace("ETHERSCAN_API_KEY", ETHERSCAN_API_KEY)
print(updated_line, end="")


if __name__ == "__main__":
main()
59 changes: 59 additions & 0 deletions .github/workflows/assert-bytecode.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
name: CI

on:
pull_request:
branches:
- 'master'

jobs:
assert-bytecode:
name: Assert deployed contracts bytecode
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
63 changes: 63 additions & 0 deletions .github/workflows/fix-abi-pr.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: Fix ABI

on:
workflow_dispatch:

jobs:
abi-fix-pr:
name: Extract ABi and create PR
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/"
56 changes: 51 additions & 5 deletions .github/workflows/linters.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ jobs:
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)"
run: echo "::set-output name=dir::$(yarn config get cacheFolder)"

- name: Cache yarn cache
id: cache-yarn-cache
Expand All @@ -42,8 +42,8 @@ jobs:
steps.cache-node-modules.outputs.cache-hit != 'true'
- name: Run Solidity tests
run: yarn test:unit
run: yarn test:unit

- name: Run Solidity linters
run: yarn lint:sol:solhint

Expand All @@ -62,7 +62,7 @@ jobs:
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)"
Expand Down Expand Up @@ -92,3 +92,49 @@ jobs:
- 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/
Loading

0 comments on commit df95e56

Please sign in to comment.