-
Notifications
You must be signed in to change notification settings - Fork 192
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #426 from lidofinance/develop
Feat: merge-ready protocol
- Loading branch information
Showing
322 changed files
with
19,803 additions
and
6,397 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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/" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.