Skip to content

Commit

Permalink
Feat/GitHub ci (ChainSecurity#4)
Browse files Browse the repository at this point in the history
Adding CI and some minor improvements along the way
  • Loading branch information
markus-cs authored Jul 15, 2024
1 parent db29a72 commit 9399d8f
Show file tree
Hide file tree
Showing 81 changed files with 441 additions and 73 deletions.
86 changes: 86 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# .github/workflows/ci.yml
name: CI

on:
push:
branches:
- main
pull_request:
branches:
- main

jobs:

ci:
runs-on: ubuntu-latest
steps:
- name: Checkout sources
uses: actions/checkout@v3
with:
submodules: recursive

- name: Install stable toolchain
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: nightly
components: rustfmt, clippy

- name: Cache Cargo dependencies
uses: actions/cache@v3
with:
path: |
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-
- name: Install dependencies
run: |
wget -O geth.tgz https://gethstore.blob.core.windows.net/builds/geth-linux-amd64-1.14.5-0dd173a7.tar.gz
tar -zxvf geth.tgz
sudo mv geth-linux-amd64-1.14.5-0dd173a7/geth /usr/bin/
sudo apt clean
- name: Set up Node.js
uses: actions/setup-node@v3
with:
node-version: '20'

- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1

- name: Run format task
run: ./tests/format_test.sh

- name: Run tests
env:
MAINNET_RPC: ${{ secrets.MAINNET_RPC }}
ETHERSCAN_TEST_API_URL: ${{ secrets.ETHERSCAN_TEST_API_URL }}
ETHERSCAN_API_KEY: ${{ secrets.ETHERSCAN_API_KEY }}
BITQUERY_API_URL: ${{ secrets.BITQUERY_API_URL }}
BITQUERY_API_KEY: ${{ secrets.BITQUERY_API_KEY }}
SIGNER_ADDRESS: ${{ secrets.SIGNER_ADDRESS }}
SIGNER_SECRET_KEY: ${{ secrets.SIGNER_SECRET_KEY }}
run: ./tests/ci_tests.sh

# TODO: Publish to Docker Hub
# - name: Set up Docker Buildx
# uses: docker/setup-buildx-action@v3

# - name: Log in to Docker Hub
# uses: docker/login-action@v3
# with:
# username: ${{ secrets.DOCKER_USERNAME }}
# password: ${{ secrets.DOCKER_PASSWORD }}

# - name: Build and push
# uses: docker/build-push-action@v6
# with:
# push: true
# tags: user/app:latest
1 change: 1 addition & 0 deletions examples/frxETH-public
Submodule frxETH-public added at 99d092
1 change: 1 addition & 0 deletions examples/permit2
Submodule permit2 added at cc56ad
1 change: 1 addition & 0 deletions examples/tokenized-aave-v3/lib/forge-std
Submodule forge-std added at 07263d
1 change: 1 addition & 0 deletions examples/tokenized-aave-v3/lib/openzeppelin-contracts
Submodule openzeppelin-contracts added at 05f218
1 change: 1 addition & 0 deletions examples/tokenized-aave-v3/lib/tokenized-strategy
Submodule tokenized-strategy added at cf791a
33 changes: 30 additions & 3 deletions lib/dvf/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use ethers_signers::LocalWallet;
use ethers_signers::Signer;
use reqwest::blocking::Client;
use serde::{Deserialize, Serialize};
use tempfile::NamedTempFile;
use tempfile::{tempdir, NamedTempFile};
use tracing::debug;

use crate::dvf::abstract_wallet::AbstractWallet;
Expand Down Expand Up @@ -84,7 +84,7 @@ pub struct DVFSignerConfig {
pub wallet_type: DVFWalletType,
}

#[derive(Serialize, Deserialize, Default)]
#[derive(Serialize, Deserialize, Default, Debug)]
pub struct DVFConfig {
pub rpc_urls: BTreeMap<u64, String>, // chain_id to URL
pub dvf_storage: PathBuf, // Storage of DVFs
Expand Down Expand Up @@ -123,6 +123,7 @@ impl DVFConfig {
return Ok(Self::default());
}
match matches.value_of("config") {
Some("env") => Self::from_env(None),
Some(config_path_str) => Self::from_path(Path::new(config_path_str)),
None => Self::from_default_path(),
}
Expand All @@ -140,9 +141,11 @@ impl DVFConfig {
} else {
rpc_urls = BTreeMap::from([(1, env::var("MAINNET_RPC")?)]);
}
let temp_dir = tempdir().unwrap();

Ok(DVFConfig {
rpc_urls,
dvf_storage: PathBuf::from_str("/tmp/dvfs48/").unwrap(),
dvf_storage: temp_dir.path().to_path_buf(),
trusted_signers: vec![
Address::from_str("0x229F1a71262e4bE12215EE4648615D2bA0969682")?,
Address::from_str("0xF063F84A88Bf621520583d386F8F642C475A0c5E")?,
Expand All @@ -167,6 +170,7 @@ impl DVFConfig {
}

pub fn test_config_file(local_port: Option<u16>) -> Result<NamedTempFile, ValidationError> {
// @note we should use this file
let config_file = NamedTempFile::new().unwrap();
let config = DVFConfig::from_env(local_port)?;
config.write_to_file(&config_file.path().to_path_buf())?;
Expand Down Expand Up @@ -825,6 +829,28 @@ impl DVFConfig {
}
}

pub fn get_etherscan_url(&self) -> Result<String, ValidationError> {
if let Some(test_url) = &self.etherscan_test_api_url {
match self.active_chain_id {
Some(1337) | Some(31337) => {
return Err(ValidationError::from("Testnet, no Etherscan"))
}
_ => return Ok(test_url.clone()),
}
}
match self.active_chain {
Some(active_chain) => match active_chain.etherscan_urls() {
Some((_api_url, base_url)) => Ok(base_url.to_string()),
None => Err(ValidationError::from(
"Invalid active chain. Cannot chose Etherscan API.",
)),
},
None => Err(ValidationError::from(
"No active chain. Cannot chose Etherscan API.",
)),
}
}

pub fn get_graphql_name(&self) -> Result<String, ValidationError> {
match self.active_chain_id {
Some(1) => Ok("ethereum".to_string()),
Expand All @@ -851,6 +877,7 @@ impl DVFConfig {

let mut file = File::create(path)?;
file.write_all(output.as_bytes())?;
file.sync_all().expect("Unable to sync");
Ok(())
}

Expand Down
150 changes: 135 additions & 15 deletions lib/web3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,31 @@ pub fn get_internal_create_addresses(
Ok(addresses)
}

#[derive(Debug, Serialize, Deserialize)]
struct OtsContractCreator {
#[serde(rename = "creator")]
pub contract_creator: String,
#[serde(rename = "hash")]
pub tx_hash: String,
}

fn get_ots_contract_creator(
config: &DVFConfig,
address: &Address,
) -> Result<OtsContractCreator, ValidationError> {
let request_body = json!({
"jsonrpc": "2.0",
"method": "ots_getContractCreator",
"params": [address],
"id": 1
});
let result = send_blocking_web3_post(config, &request_body)?;
// Parse the response as a JSON list
let result: OtsContractCreator = serde_json::from_value(result)?;

Ok(result)
}

fn get_tx_trace(config: &DVFConfig, tx_id: &str) -> Result<Vec<Trace>, ValidationError> {
let request_body = json!({
"jsonrpc": "2.0",
Expand Down Expand Up @@ -413,13 +438,17 @@ pub fn get_deployment_block(config: &DVFConfig, address: &Address) -> Result<u64
}
}

// Search between start_block_num and end_block_num (inclusive)
// Iterates through those blocks
// Returns block number and tx hash
fn get_deployment_from_parity_trace(
config: &DVFConfig,
address: &Address,
current_block_num: u64,
start_block_num: u64,
end_block_num: u64,
) -> Result<(u64, String), ValidationError> {
debug!("Searching parity traces for deployment tx");
for i in 1..current_block_num + 1 {
for i in start_block_num..end_block_num + 1 {
let block_traces = get_block_traces(config, i)?;
for trace in block_traces {
// Filter reverted
Expand All @@ -442,13 +471,17 @@ fn get_deployment_from_parity_trace(
))
}

// Search between start_block_num and end_block_num (inclusive)
// Iterates through those blocks
// Returns block number and tx hash
fn get_deployment_from_geth_trace(
config: &DVFConfig,
address: &Address,
current_block_num: u64,
start_block_num: u64,
end_block_num: u64,
) -> Result<(u64, String), ValidationError> {
debug!("Searching geth traces for deployment tx of {:?}", address);
for i in 1..current_block_num + 1 {
for i in start_block_num..end_block_num + 1 {
let block = get_eth_block_by_num(config, i, true)?;
for tx in block.transactions {
let tx_hash = format!("{:#x}", tx.hash);
Expand Down Expand Up @@ -480,21 +513,63 @@ pub fn get_deployment(
debug!("GraphQL Deployment Tx: {}", deployment_tx_hash);
let deployment_block_num = get_block_number_for_tx(config, deployment_tx_hash.as_str())?;
return Ok((deployment_block_num, deployment_tx_hash));
} else if let Ok(creator) = get_ots_contract_creator(config, address) {
debug!("Otterscan Deployment Tx: {}", creator.tx_hash);
let deployment_block_num = get_block_number_for_tx(config, creator.tx_hash.as_str())?;
return Ok((deployment_block_num, creator.tx_hash));
} else {
debug!("No deployment tx found in etherscan or graphql, searching traces. ");
let current_block_num = get_eth_block_number(config)?;
if current_block_num < 100 {
if let Ok((deployment_block_num, deployment_tx_hash)) =
get_deployment_from_parity_trace(config, address, current_block_num)
{
return Ok((deployment_block_num, deployment_tx_hash));
}
if let Ok((deployment_block_num, deployment_tx_hash)) =
get_deployment_from_geth_trace(config, address, current_block_num)
{
return Ok((deployment_block_num, deployment_tx_hash));
}
let start_block_num = if current_block_num > 10 {
get_deployment_block_from_binary_search(config, address, current_block_num)?
} else {
1
};

if let Ok((deployment_block_num, deployment_tx_hash)) =
get_deployment_from_parity_trace(config, address, start_block_num, current_block_num)
{
return Ok((deployment_block_num, deployment_tx_hash));
}
if let Ok((deployment_block_num, deployment_tx_hash)) =
get_deployment_from_geth_trace(config, address, start_block_num, current_block_num)
{
return Ok((deployment_block_num, deployment_tx_hash));
}
}

Err(ValidationError::from(
"Could not find deployment transaction.",
))
}

pub fn get_deployment_block_from_binary_search(
config: &DVFConfig,
address: &Address,
current_block_num: u64,
) -> Result<u64, ValidationError> {
let mut low: u64 = 0;
let mut high = current_block_num;

while high - low > 1 {
let mid = (low + high) / 2;

let code = get_eth_code(config, address, mid)?;

if code.trim_start_matches("0x").is_empty() {
low = mid;
} else {
high = mid;
}
}

if !(get_eth_code(config, address, high)?
.trim_start_matches("0x")
.is_empty())
{
return Ok(high);
}

Err(ValidationError::from(
"Could not find deployment transaction.",
))
Expand Down Expand Up @@ -1721,6 +1796,51 @@ mod tests {
assert!(init_code.starts_with("0x61014060405234"))
}

#[test]
fn test_ots_contract_creator() {
let address = Address::from_str("0x5e8422345238f34275888049021821e8e08caa1f").unwrap(); // frax address
let mut config = match DVFConfig::from_env(None) {
Ok(config) => config,
Err(err) => {
println!("{}", err);
assert!(false);
return;
}
};
config.set_chain_id(1).unwrap();

let creator = get_ots_contract_creator(&config, &address).unwrap();

assert_eq!(
creator.contract_creator,
"0x4600d3b12c39af925c2c07c487d31d17c1e32a35".to_string()
);
assert_eq!(
creator.tx_hash,
"0x8b36720344797ed57f2e22cf2aa56a09662165567a6ade701259cde560cc4a9d"
);
}

#[test]
fn test_get_deployment_block_from_binary_search() {
let address = Address::from_str("0x5e8422345238f34275888049021821e8e08caa1f").unwrap(); // frax address
let mut config = match DVFConfig::from_env(None) {
Ok(config) => config,
Err(err) => {
println!("{}", err);
assert!(false);
return;
}
};
config.set_chain_id(1).unwrap();

let block_num = get_eth_block_number(&config).unwrap();
let deployment_block =
get_deployment_block_from_binary_search(&config, &address, block_num).unwrap();

assert_eq!(deployment_block, 15686046);
}

/*
pub fn get_transaction_by_block(
config: &DVFConfig,
Expand Down
Loading

0 comments on commit 9399d8f

Please sign in to comment.