diff --git a/.github/workflows/ci-pre-commit.yml b/.github/workflows/ci-pre-commit.yml new file mode 100644 index 00000000..ad6c22bf --- /dev/null +++ b/.github/workflows/ci-pre-commit.yml @@ -0,0 +1,33 @@ +name: Pre-commit checks + +on: + pull_request: + push: + branches: [main] + +jobs: + pre-commit: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + # Need to grab the history of the PR + fetch-depth: 0 + - uses: actions/setup-python@v2 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly-2023-03-01 + components: rustfmt, clippy + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly-2023-07-23 + components: rustfmt, clippy + - uses: pre-commit/action@v3.0.0 + if: ${{ github.event_name == 'pull_request' }} + with: + # Run only on files changed in the PR + extra_args: --from-ref ${{ github.event.pull_request.base.sha }} --to-ref ${{ github.event.pull_request.head.sha }} + - uses: pre-commit/action@v3.0.0 + if: ${{ github.event_name != 'pull_request' }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..709f4b34 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,32 @@ +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.2.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-added-large-files + exclude: package-lock.json + # Hook to format many type of files in the repo + # including solidity contracts. + - repo: https://github.com/pre-commit/mirrors-prettier + rev: "v2.7.1" + hooks: + - id: prettier + additional_dependencies: + - "prettier@2.7.1" + - "prettier-plugin-solidity@1.0.0-rc.1" + - repo: local + hooks: + # Hooks for auction server + - id: cargo-fmt-auction-server + name: Cargo format for auction server + language: "rust" + entry: cargo +nightly-2023-07-23 fmt --manifest-path ./auction-server/Cargo.toml --all -- --config-path rustfmt.toml + pass_filenames: false + files: auction-server + + # for python files + - repo: https://github.com/hhatto/autopep8 + rev: "v2.0.4" + hooks: + - id: autopep8 diff --git a/README.md b/README.md index dce4f503..73c0ff19 100644 --- a/README.md +++ b/README.md @@ -16,4 +16,10 @@ $ source .env $ set +a ``` -The updated enviornment variables can then be seen via `env`. We can then run the appropriate forge tests which will pull the relevant bundle information from the environment variables. To do this, run `forge test -vvv --via-ir --match-test {TestToBeRun}`. Note that you need to `source` the `.env` file in the same session as the one in which you run the forge tests. \ No newline at end of file +The updated enviornment variables can then be seen via `env`. We can then run the appropriate forge tests which will pull the relevant bundle information from the environment variables. To do this, run `forge test -vvv --via-ir --match-test {TestToBeRun}`. Note that you need to `source` the `.env` file in the same session as the one in which you run the forge tests. + +### pre-commit hooks + +pre-commit is a tool that checks and fixes simple issues (formatting, ...) before each commit. You can install it by following [their website](https://pre-commit.com/). In order to enable checks for this repo run `pre-commit install` from command-line in the root of this repo. + +The checks are also performed in the CI to ensure the code follows consistent formatting. diff --git a/auction-server/.gitignore b/auction-server/.gitignore index d913617b..eb5a316c 100644 --- a/auction-server/.gitignore +++ b/auction-server/.gitignore @@ -1,2 +1 @@ target - diff --git a/auction-server/README.md b/auction-server/README.md index 745f508f..745356e9 100644 --- a/auction-server/README.md +++ b/auction-server/README.md @@ -8,7 +8,6 @@ Each blockchain is configured in `config.yaml`. This package uses Cargo for building and dependency management. Simply run `cargo build` and `cargo test` to build and test the project. - ## Local Development To start an instance of the webserver for local testing, you first need to perform a few setup steps: diff --git a/auction-server/src/PERMulticall.json b/auction-server/src/PERMulticall.json index 89fdeb7c..51b35e8b 100644 --- a/auction-server/src/PERMulticall.json +++ b/auction-server/src/PERMulticall.json @@ -526,75 +526,29 @@ "absolutePath": "src/PERMulticall.sol", "id": 48313, "exportedSymbols": { - "ExpiredSignature": [ - 47480 - ], - "IERC20": [ - 44053 - ], - "InvalidBid": [ - 47494 - ], - "InvalidLiquidation": [ - 47492 - ], - "InvalidPERSignature": [ - 47484 - ], - "InvalidSearcherSignature": [ - 47478 - ], - "InvalidTimestamp": [ - 47486 - ], - "InvalidVaultUpdate": [ - 47490 - ], - "LiquidationCallFailed": [ - 47498 - ], - "LiquidationCallParams": [ - 48752 - ], - "MulticallStatus": [ - 48731 - ], - "NotPEROperator": [ - 47474 - ], - "NotRegistered": [ - 47496 - ], - "OracleState": [ - 48702 - ], - "PERFeeReceiver": [ - 47910 - ], - "PERMulticall": [ - 48312 - ], - "SignatureAlreadyUsed": [ - 47482 - ], - "Strings": [ - 44822 - ], - "TokenQty": [ - 48724 - ], - "Unauthorized": [ - 47476 - ], - "UncollateralizedVaultCreation": [ - 47488 - ], - "Vault": [ - 48719 - ], - "console": [ - 22024 - ] + "ExpiredSignature": [47480], + "IERC20": [44053], + "InvalidBid": [47494], + "InvalidLiquidation": [47492], + "InvalidPERSignature": [47484], + "InvalidSearcherSignature": [47478], + "InvalidTimestamp": [47486], + "InvalidVaultUpdate": [47490], + "LiquidationCallFailed": [47498], + "LiquidationCallParams": [48752], + "MulticallStatus": [48731], + "NotPEROperator": [47474], + "NotRegistered": [47496], + "OracleState": [48702], + "PERFeeReceiver": [47910], + "PERMulticall": [48312], + "SignatureAlreadyUsed": [47482], + "Strings": [44822], + "TokenQty": [48724], + "Unauthorized": [47476], + "UncollateralizedVaultCreation": [47488], + "Vault": [48719], + "console": [22024] }, "nodeType": "SourceUnit", "src": "39:5523:37", @@ -604,12 +558,7 @@ "nodeType": "PragmaDirective", "src": "39:24:37", "nodes": [], - "literals": [ - "solidity", - "^", - "0.8", - ".13" - ] + "literals": ["solidity", "^", "0.8", ".13"] }, { "id": 47913, @@ -1582,10 +1531,7 @@ "id": 47991, "name": "require", "nodeType": "Identifier", - "overloadedDeclarations": [ - -18, - -18 - ], + "overloadedDeclarations": [-18, -18], "referencedDeclaration": -18, "src": "1747:7:37", "typeDescriptions": { @@ -1780,9 +1726,7 @@ "nodes": [], "statements": [ { - "assignments": [ - 48014 - ], + "assignments": [48014], "declarations": [ { "constant": false, @@ -2392,10 +2336,7 @@ "id": 48063, "name": "require", "nodeType": "Identifier", - "overloadedDeclarations": [ - -18, - -18 - ], + "overloadedDeclarations": [-18, -18], "referencedDeclaration": -18, "src": "2856:7:37", "typeDescriptions": { @@ -2518,10 +2459,7 @@ "id": 48071, "name": "require", "nodeType": "Identifier", - "overloadedDeclarations": [ - -18, - -18 - ], + "overloadedDeclarations": [-18, -18], "referencedDeclaration": -18, "src": "2945:7:37", "typeDescriptions": { @@ -2734,9 +2672,7 @@ "pathNode": { "id": 48088, "name": "MulticallStatus", - "nameLocations": [ - "3110:15:37" - ], + "nameLocations": ["3110:15:37"], "nodeType": "IdentifierPath", "referencedDeclaration": 48731, "src": "3110:15:37" @@ -2784,9 +2720,7 @@ "src": "3086:54:37" }, { - "assignments": [ - 48098 - ], + "assignments": [48098], "declarations": [ { "constant": false, @@ -3523,9 +3457,7 @@ }, "id": 48165, "initializationExpression": { - "assignments": [ - 48102 - ], + "assignments": [48102], "declarations": [ { "constant": false, @@ -3611,9 +3543,7 @@ "src": "3181:548:37" }, { - "assignments": [ - 48167 - ], + "assignments": [48167], "declarations": [ { "constant": false, @@ -3698,9 +3628,7 @@ "src": "3803:49:37" }, { - "assignments": [ - 48173 - ], + "assignments": [48173], "declarations": [ { "constant": false, @@ -3873,9 +3801,7 @@ } }, { - "assignments": [ - 48188 - ], + "assignments": [48188], "declarations": [ { "constant": false, @@ -4007,9 +3933,7 @@ "src": "4131:347:37", "statements": [ { - "assignments": [ - 48197 - ], + "assignments": [48197], "declarations": [ { "constant": false, @@ -4368,9 +4292,7 @@ "isLValue": false, "isPure": false, "lValueRequested": false, - "names": [ - "value" - ], + "names": ["value"], "nodeType": "FunctionCallOptions", "options": [ { @@ -4710,9 +4632,7 @@ "pathNode": { "id": 48058, "name": "MulticallStatus", - "nameLocations": [ - "2802:15:37" - ], + "nameLocations": ["2802:15:37"], "nodeType": "IdentifierPath", "referencedDeclaration": 48731, "src": "2802:15:37" @@ -4754,9 +4674,7 @@ "nodes": [], "statements": [ { - "assignments": [ - 48251 - ], + "assignments": [48251], "declarations": [ { "constant": false, @@ -4863,10 +4781,7 @@ "src": "5024:46:37" }, { - "assignments": [ - 48259, - 48261 - ], + "assignments": [48259, 48261], "declarations": [ { "constant": false, @@ -5013,9 +4928,7 @@ "src": "5169:258:37", "statements": [ { - "assignments": [ - 48269 - ], + "assignments": [48269], "declarations": [ { "constant": false, @@ -5349,10 +5262,7 @@ "id": 48276, "name": "require", "nodeType": "Identifier", - "overloadedDeclarations": [ - -18, - -18 - ], + "overloadedDeclarations": [-18, -18], "referencedDeclaration": -18, "src": "5311:7:37", "typeDescriptions": { @@ -5740,19 +5650,15 @@ "contractDependencies": [], "contractKind": "contract", "fullyImplemented": true, - "linearizedBaseContracts": [ - 48312 - ], + "linearizedBaseContracts": [48312], "name": "PERMulticall", "nameLocation": "327:12:37", "scope": 48313, "usedErrors": [], - "usedEvents": [ - 47925 - ] + "usedEvents": [47925] } ], "license": "UNLICENSED" }, "id": 37 -} \ No newline at end of file +} diff --git a/auction-server/src/api.rs b/auction-server/src/api.rs index e4750f12..89386dac 100644 --- a/auction-server/src/api.rs +++ b/auction-server/src/api.rs @@ -1,27 +1,65 @@ -use std::collections::HashMap; -use std::sync::atomic::{AtomicBool, Ordering}; -use std::sync::Arc; - -use axum::{ - routing::{get, post}, - Router, +use { + crate::{ + api::rest::Bid, + auction::run_submission_loop, + config::{ + ChainId, + Config, + RunOptions, + }, + state::{ + ChainStore, + Store, + }, + }, + anyhow::{ + anyhow, + Result, + }, + axum::{ + http::StatusCode, + response::{ + IntoResponse, + Response, + }, + routing::{ + get, + post, + }, + Router, + }, + clap::crate_version, + ethers::{ + providers::{ + Http, + Middleware, + Provider, + }, + signers::{ + LocalWallet, + Signer, + }, + types::Address, + }, + futures::future::join_all, + std::{ + collections::HashMap, + sync::{ + atomic::{ + AtomicBool, + Ordering, + }, + Arc, + }, + }, + tower_http::cors::CorsLayer, + utoipa::{ + OpenApi, + ToResponse, + ToSchema, + }, + utoipa_swagger_ui::SwaggerUi, }; -use clap::crate_version; -use ethers::providers::{Http, Middleware, Provider}; -use ethers::types::Address; -use futures::future::join_all; -use tower_http::cors::CorsLayer; -use utoipa::{OpenApi, ToResponse, ToSchema}; -use utoipa_swagger_ui::SwaggerUi; - -use crate::api::rest::Bid; -use crate::auction::run_submission_loop; -use crate::config::{ChainId, Config, RunOptions}; -use crate::state::{ChainStore, Store}; -use anyhow::{anyhow, Result}; -use axum::http::StatusCode; -use axum::response::{IntoResponse, Response}; -use ethers::signers::{LocalWallet, Signer}; // A static exit flag to indicate to running threads that we're shutting down. This is used to // gracefully shutdown the application. @@ -127,7 +165,7 @@ pub async fn start_server(run_options: RunOptions) -> Result<()> { network_id: id, bids: Default::default(), config: chain_config.clone(), - opps: Default::default() + opps: Default::default(), }, )) }, @@ -137,7 +175,7 @@ pub async fn start_server(run_options: RunOptions) -> Result<()> { .collect(); let store = Arc::new(Store { - chains: chain_store?, + chains: chain_store?, per_operator: wallet, }); diff --git a/auction-server/src/api/rest.rs b/auction-server/src/api/rest.rs index a6fe3e00..6861d30f 100644 --- a/auction-server/src/api/rest.rs +++ b/auction-server/src/api/rest.rs @@ -1,19 +1,42 @@ -use crate::api::RestError; -use crate::auction::simulate_bids; -use crate::auction::per::MulticallStatus; -use crate::state::{SimulatedBid, Store, Opportunity, GetOppsParams}; -use axum::{extract::State, Json}; -use ethers::abi::Address; -use ethers::contract::EthError; -use ethers::middleware::contract::ContractError; - -use ethers::signers::Signer; -use ethers::types::{Bytes, U256}; -use ethers::utils::hex::FromHex; -use serde::{Deserialize, Serialize}; -use std::sync::Arc; -use utoipa::ToSchema; -use axum::extract::Query; +use { + crate::{ + api::RestError, + auction::{ + per::MulticallStatus, + simulate_bids, + }, + state::{ + GetOppsParams, + Opportunity, + SimulatedBid, + Store, + }, + }, + axum::{ + extract::{ + Query, + State, + }, + Json, + }, + ethers::{ + abi::Address, + contract::EthError, + middleware::contract::ContractError, + signers::Signer, + types::{ + Bytes, + U256, + }, + utils::hex::FromHex, + }, + serde::{ + Deserialize, + Serialize, + }, + std::sync::Arc, + utoipa::ToSchema, +}; #[derive(Serialize, Deserialize, ToSchema, Clone)] pub struct Bid { @@ -22,16 +45,16 @@ pub struct Bid { permission_key: String, /// The chain id to bid on. #[schema(example = "sepolia")] - chain_id: String, + chain_id: String, /// The contract address to call. #[schema(example = "0xcA11bde05977b3631167028862bE2a173976CA11")] - contract: String, + contract: String, /// Calldata for the contract call. #[schema(example = "0xdeadbeef")] - calldata: String, + calldata: String, /// Amount of bid in wei. #[schema(example = "1000000000000000000")] - bid: String, + bid: String, } /// Bid on a specific permission key for a specific chain. @@ -76,26 +99,22 @@ pub async fn bid( ); match call.await { - Ok(result) => { - let multicall_results: Vec = result; - if !multicall_results.iter().all(|x| x.external_success) { - let first_reason = multicall_results - .first() - .cloned() - .unwrap() - .multicall_revert_reason; - let first_result = multicall_results - .first() - .cloned() - .unwrap() - .external_result; - return Err(RestError::BadParameters(format!( - "Call Revert: {}, {}", - first_result, - first_reason - ))); + Ok(multicall_results) => match multicall_results.first() { + Some(first_result) => { + if !multicall_results.iter().all(|x| x.external_success) { + return Err(RestError::BadParameters(format!( + "Call Revert: Result:{} - Reason:{}", + first_result.external_result.clone(), + first_result.multicall_revert_reason.clone() + ))); + } } - } + None => { + return Err(RestError::BadParameters( + "No results from multicall".to_string(), + )) + } + }, Err(e) => { return match e { ContractError::Revert(reason) => Err(RestError::BadParameters(format!( @@ -141,7 +160,7 @@ pub async fn surface( .chains .get(&opp.chain_id) .ok_or(RestError::InvalidChainId)?; - + let contract = opp .contract .parse::
() @@ -161,7 +180,7 @@ pub async fn surface( /// Get liquidation opportunities /// // #[axum_macros::debug_handler] -#[utoipa::path(get, path = "/getOpps", +#[utoipa::path(get, path = "/getOpps", params( ("chain_id" = String, Query, description = "Chain ID to retrieve opportunities for"), ("contract" = Option, Query, description = "Contract address to filter by") @@ -173,11 +192,11 @@ pub async fn surface( ,)] pub async fn get_opps( State(store): State>, - Query(params): Query -) -> Result>, RestError> { + Query(params): Query, +) -> Result>, RestError> { let chain_id = params.chain_id; let contract = params.contract; - + let chain_store = store .chains .get(&chain_id) @@ -191,16 +210,28 @@ pub async fn get_opps( .parse::
() .map_err(|_| RestError::BadParameters("Invalid contract address".to_string()))?; - opps = chain_store.opps.write().await.entry(key).or_default().to_vec(); - }, + opps = chain_store + .opps + .write() + .await + .entry(key) + .or_default() + .to_vec(); + } None => { let opps_dict = chain_store.opps.write().await; for key in opps_dict.keys() { - let opps_key = chain_store.opps.write().await.entry(key.clone()).or_default().clone(); + let opps_key = chain_store + .opps + .write() + .await + .entry(key.clone()) + .or_default() + .clone(); opps.extend(opps_key); } } } Ok(Json(opps)) -} \ No newline at end of file +} diff --git a/auction-server/src/auction.rs b/auction-server/src/auction.rs index 9f8374a7..44cb0521 100644 --- a/auction-server/src/auction.rs +++ b/auction-server/src/auction.rs @@ -1,25 +1,50 @@ -use anyhow::anyhow; -use std::{ - sync::{atomic::Ordering, Arc}, - time::Duration, -}; - -use ethers::{ - contract::{abigen, ContractError}, - middleware::{ - transformer::{Transformer, TransformerError}, - SignerMiddleware, TransformerMiddleware, +use { + crate::{ + api::SHOULD_EXIT, + config::EthereumConfig, + state::Store, + }, + anyhow::anyhow, + ethers::{ + contract::{ + abigen, + ContractError, + }, + middleware::{ + transformer::{ + Transformer, + TransformerError, + }, + SignerMiddleware, + TransformerMiddleware, + }, + providers::{ + Http, + Provider, + ProviderError, + }, + signers::{ + LocalWallet, + Signer, + }, + types::{ + transaction::eip2718::TypedTransaction, + Address, + Bytes, + TransactionReceipt, + TransactionRequest, + U256, + }, }, - providers::{Http, Provider, ProviderError}, - signers::{LocalWallet, Signer}, - types::{ - transaction::eip2718::TypedTransaction, Address, Bytes, TransactionReceipt, - TransactionRequest, U256, + std::{ + sync::{ + atomic::Ordering, + Arc, + }, + time::Duration, }, }; -use crate::{api::SHOULD_EXIT, config::EthereumConfig, state::Store}; - abigen!(PER, "src/PERMulticall.json"); pub type PERContract = PER>; pub type SignableProvider = @@ -39,13 +64,6 @@ impl TryFrom for Provider { type Error = anyhow::Error; } -// #[derive(Clone, Debug)] -// pub struct MulticallStatus { -// pub external_success: bool, -// pub external_result: Bytes, -// pub multicall_revert_reason: String -// } - pub async fn simulate_bids( per_operator: Address, provider: Provider, @@ -57,7 +75,6 @@ pub async fn simulate_bids( ) -> Result, ContractError>> { let client = Arc::new(provider); let per_contract = PERContract::new(chain_config.contract_addr, client); - tracing::info!("Simulating bids {}, {}, {}, {}", permission, contracts[0], calldata[0], bids[0]); let call = per_contract .multicall(permission, contracts, calldata, bids) .from(per_operator); diff --git a/auction-server/src/config.rs b/auction-server/src/config.rs index e781142b..4c79aff0 100644 --- a/auction-server/src/config.rs +++ b/auction-server/src/config.rs @@ -1,8 +1,19 @@ -use anyhow::Result; -use clap::{crate_authors, crate_description, crate_name, crate_version, Args, Parser}; -use ethers::abi::Address; -use std::collections::HashMap; -use std::fs; +use { + anyhow::Result, + clap::{ + crate_authors, + crate_description, + crate_name, + crate_version, + Args, + Parser, + }, + ethers::abi::Address, + std::{ + collections::HashMap, + fs, + }, +}; mod server; diff --git a/auction-server/src/main.rs b/auction-server/src/main.rs index 372cc6a2..de7c4c8a 100644 --- a/auction-server/src/main.rs +++ b/auction-server/src/main.rs @@ -1,15 +1,15 @@ -use std::io::IsTerminal; - -use anyhow::Result; -use clap::Parser; -use tracing_subscriber::filter::LevelFilter; - -use crate::api::start_server; +use { + crate::api::start_server, + anyhow::Result, + clap::Parser, + std::io::IsTerminal, + tracing_subscriber::filter::LevelFilter, +}; mod api; +mod auction; mod config; mod state; -mod auction; #[tokio::main] async fn main() -> Result<()> { @@ -18,9 +18,11 @@ async fn main() -> Result<()> { .with_file(false) .with_line_number(true) .with_thread_ids(true) - .with_env_filter(tracing_subscriber::EnvFilter::builder() - .with_default_directive(LevelFilter::INFO.into()) - .from_env_lossy()) + .with_env_filter( + tracing_subscriber::EnvFilter::builder() + .with_default_directive(LevelFilter::INFO.into()) + .from_env_lossy(), + ) .with_ansi(std::io::stderr().is_terminal()); // Use the compact formatter if we're in a terminal, otherwise use the JSON formatter. @@ -33,9 +35,6 @@ async fn main() -> Result<()> { // Parse the command line arguments with StructOpt, will exit automatically on `--help` or // with invalid arguments. match config::Options::parse() { - config::Options::Run(opts) => { - start_server(opts).await - } + config::Options::Run(opts) => start_server(opts).await, } } - diff --git a/auction-server/src/state.rs b/auction-server/src/state.rs index 0f549c09..4a5f80ed 100644 --- a/auction-server/src/state.rs +++ b/auction-server/src/state.rs @@ -1,10 +1,28 @@ -use std::collections::HashMap; -use ethers::{signers::LocalWallet, types::{Bytes, Address, U256}, providers::{Provider, Http}}; -use tokio::sync::RwLock; -use serde::{Deserialize, Serialize}; -use utoipa::ToSchema; - -use crate::config::{ChainId, EthereumConfig}; +use { + crate::config::{ + ChainId, + EthereumConfig, + }, + ethers::{ + providers::{ + Http, + Provider, + }, + signers::LocalWallet, + types::{ + Address, + Bytes, + U256, + }, + }, + serde::{ + Deserialize, + Serialize, + }, + std::collections::HashMap, + tokio::sync::RwLock, + utoipa::ToSchema, +}; pub type PermissionKey = Bytes; pub type Contract = Address; @@ -14,19 +32,19 @@ pub type Contract = Address; pub struct Opportunity { /// The chain id to bid on. #[schema(example = "sepolia")] - pub chain_id: String, + pub chain_id: String, /// The contract address to call. #[schema(example = "0xcA11bde05977b3631167028862bE2a173976CA11")] - pub contract: String, + pub contract: String, /// Calldata for the contract call. #[schema(example = "0xdeadbeef")] - calldata: String, + calldata: String, /// The permission key to bid on. #[schema(example = "0xdeadbeef")] permission_key: String, /// The ID of the account/vault that is eligible for liquidation #[schema(example = "4")] - account: String, + account: String, /// A list of repay tokens with amount // #[schema(example = vec![("0x6B175474E89094C44Da98b954EedeAC495271d0F", 1_000_000)])] repay_tokens: Vec<(String, U256)>, @@ -35,18 +53,26 @@ pub struct Opportunity { receipt_tokens: Vec<(String, U256)>, /// A list of prices in the format of PriceFeed // #[schema(example = [("0xdeadbeef", (100, 2, 0, 1_700_000_000), (101, 1, 0, 1_700_000_000), "0xdeadbeef")])] - prices: Vec<(String, (U256, U256, U256, U256), (U256, U256, U256, U256), String)> + prices: Vec<( + String, + (U256, U256, U256, U256), + (U256, U256, U256, U256), + String, + )>, } #[derive(Deserialize)] pub struct GetOppsParams { pub chain_id: String, - pub contract: Option + pub contract: Option, } impl Default for GetOppsParams { fn default() -> Self { - Self { chain_id: "development".to_string(), contract: None } + Self { + chain_id: "development".to_string(), + contract: None, + } } } @@ -54,20 +80,20 @@ impl Default for GetOppsParams { pub struct SimulatedBid { pub contract: Address, pub calldata: Bytes, - pub bid: U256, - // simulation_time: + pub bid: U256, + // simulation_time: } pub struct ChainStore { - pub provider: Provider, + pub provider: Provider, pub network_id: u64, - pub config: EthereumConfig, - pub bids: RwLock>>, - pub opps: RwLock>>, + pub config: EthereumConfig, + pub bids: RwLock>>, + pub opps: RwLock>>, } pub struct Store { - pub chains: HashMap, + pub chains: HashMap, pub per_operator: LocalWallet, -} \ No newline at end of file +} diff --git a/beacon/protocols/beacon_TokenVault.py b/beacon/protocols/beacon_TokenVault.py index 998674ea..1ba19c86 100644 --- a/beacon/protocols/beacon_TokenVault.py +++ b/beacon/protocols/beacon_TokenVault.py @@ -9,6 +9,7 @@ TOKEN_VAULT_ADDRESS = "0x72A22FfcAfa6684d4EE449620270ac05afE963d0" CHAIN_RPC_ENDPOINT = "http://localhost:8545" + class LiquidationAccount(TypedDict): account_number: int token_address_collateral: str @@ -82,11 +83,13 @@ async def get_accounts() -> list[LiquidationAccount]: def create_liquidation_opp( account: LiquidationAccount, prices: list[PriceFeed]) -> LiquidationOpportunity: - price_updates = [] ## [bytes.fromhex(update['vaa']) for update in prices] ## TODO: uncomment this, to add back price updates + # [bytes.fromhex(update['vaa']) for update in prices] ## TODO: uncomment this, to add back price updates + price_updates = [] function_signature = web3.Web3.solidity_keccak( ["string"], ["liquidateWithPriceUpdate(uint256,bytes[])"])[:4].hex() calldata = function_signature + \ - encode(['uint256', 'bytes[]'], [account["account_number"], price_updates]).hex() + encode(['uint256', 'bytes[]'], [ + account["account_number"], price_updates]).hex() msg = encode(["uint256"], [account["account_number"]]) permission = '0x' + \ @@ -148,11 +151,8 @@ def get_liquidatable(accounts: list[LiquidationAccount], liquidatable.append( create_liquidation_opp( account, price_updates)) - - return liquidatable - - + return liquidatable async def main(): diff --git a/beacon/protocols/beacon_template.py b/beacon/protocols/beacon_template.py index f18bab66..8ceacc25 100644 --- a/beacon/protocols/beacon_template.py +++ b/beacon/protocols/beacon_template.py @@ -13,6 +13,8 @@ The protocol should implement a class called LiquidationAccount. This will be the type of the objects in the list returned by get_accounts() and fed into get_liquidatable. This class should contain all the relevant information about a vault/account on this protocol that is necessary for identifying whether it is eligible for liquidation and constructing a LiquidationOpportunity object. """ + + class LiquidationAccount(TypedDict): # Keys of the TypedDict and their types pass @@ -22,6 +24,8 @@ class LiquidationAccount(TypedDict): get_accounts() is the first method that the protocol should implement. It should take no arguments and return all the open accounts in the protocol in the form of a list of objects of type LiquidationAccount (defined above). Each LiquidationAccount object represents an account/vault in the protocol. This function can be implemented in any way, but it should be able to return all the open accounts in the protocol. For some protocols, this may be easily doable by just querying on-chain state; however, most protocols will likely need to maintain or access an off-chain indexer to get the list of all open accounts. """ + + async def get_accounts() -> list[LiquidationAccount]: # Fetch all vaults from on-chain state/indexer # Filter to just active vaults @@ -34,6 +38,8 @@ async def get_accounts() -> list[LiquidationAccount]: create_liquidation_opp is an optional helper function to construct a LiquidationOpportunity from a LiquidationAccount and a set of relevant Pyth PriceFeeds. If you choose to implement this function, you can call it within get_liquidatable whenever you find a LiquidationAccount eligible for liquidation. """ + + def create_liquidation_opp( account: LiquidationAccount, prices: list[PriceFeed]) -> LiquidationOpportunity: @@ -46,6 +52,8 @@ def create_liquidation_opp( prices should be a dictionary of Pyth prices, where the keys are Pyth feed IDs and the values are PriceFeed objects. prices can be retrieved from the provided price retrieval functions. This function should return a list of type LiquidationOpportunity. """ + + def get_liquidatable(accounts: list[LiquidationAccount], prices: dict[str, PriceFeed]) -> (list[LiquidationOpportunity]): @@ -58,6 +66,8 @@ def get_liquidatable(accounts: list[LiquidationAccount], """ The main loop below is a good mechanism to check if your implementations of the functions above are working properly. """ + + async def main(): # get all accounts accounts = await get_accounts() diff --git a/beacon/searcher/searcherA.py b/beacon/searcher/searcherA.py index 0fdbd013..d0538d5b 100644 --- a/beacon/searcher/searcherA.py +++ b/beacon/searcher/searcherA.py @@ -15,22 +15,26 @@ BID = 10 VALID_UNTIL = 1_000_000_000_000 + def create_liquidation_intent( - opp: LiquidationOpportunity, - sk_liquidator: str, - valid_until: int, - bid: int - ) -> LiquidationAdapterIntent: - repay_tokens = [(opp['repay_tokens'][0][0], int(opp['repay_tokens'][0][1],16))] - receipt_tokens = [(opp['receipt_tokens'][0][0], int(opp['receipt_tokens'][0][1],16))] + opp: LiquidationOpportunity, + sk_liquidator: str, + valid_until: int, + bid: int +) -> LiquidationAdapterIntent: + repay_tokens = [(opp['repay_tokens'][0][0], + int(opp['repay_tokens'][0][1], 16))] + receipt_tokens = [(opp['receipt_tokens'][0][0], + int(opp['receipt_tokens'][0][1], 16))] account: LocalAccount = Account.from_key(sk_liquidator) liquidator = account.address liq_calldata = bytes.fromhex( opp['calldata'][2:]) if opp['calldata'][:2] == "0x" else bytes.fromhex(opp['calldata']) - - signature_liquidator = construct_signature_liquidator(repay_tokens, receipt_tokens, opp['contract'], liq_calldata, bid, valid_until, sk_liquidator) - + + signature_liquidator = construct_signature_liquidator( + repay_tokens, receipt_tokens, opp['contract'], liq_calldata, bid, valid_until, sk_liquidator) + liquidation_adapter_calldata: LiquidationAdapterCalldata = { "repay_tokens": repay_tokens, "expected_receipt_tokens": receipt_tokens, @@ -41,7 +45,9 @@ def create_liquidation_intent( "bid": bid, "signature_liquidator": bytes(signature_liquidator.signature) } - calldata = LIQUIDATION_ADAPTER_FN_SIGNATURE + encode([LIQUIDATION_ADAPTER_CALLDATA_TYPES],[tuple(liquidation_adapter_calldata.values())]).hex() + calldata = LIQUIDATION_ADAPTER_FN_SIGNATURE + \ + encode([LIQUIDATION_ADAPTER_CALLDATA_TYPES], [ + tuple(liquidation_adapter_calldata.values())]).hex() intent: LiquidationAdapterIntent = { "bid": hex(bid), @@ -63,7 +69,8 @@ async def main(): # this is hardcoded to the searcher A SK sk_liquidator = "0x5b1efe5da513271c0d30cde7a2ad1d29456d68abd592efdaa7d2302e913b783f" - intent = create_liquidation_intent(liquidatable[0], sk_liquidator, VALID_UNTIL, BID) + intent = create_liquidation_intent( + liquidatable[0], sk_liquidator, VALID_UNTIL, BID) resp = await CLIENT.post( AUCTION_SERVER_ENDPOINT, @@ -76,4 +83,4 @@ async def main(): pdb.set_trace() if __name__ == "__main__": - asyncio.run(main()) \ No newline at end of file + asyncio.run(main()) diff --git a/beacon/searcher/searcher_utils.py b/beacon/searcher/searcher_utils.py index c6f1eecc..b25caafc 100644 --- a/beacon/searcher/searcher_utils.py +++ b/beacon/searcher/searcher_utils.py @@ -2,22 +2,24 @@ from web3.auto import w3 from eth_abi import encode + def construct_signature_liquidator( - repay_tokens: list[(str, int)], - receipt_tokens: list[(str, int)], - address: str, - liq_calldata: bytes, - bid: int, - valid_until: int, - secret_key: str - ): + repay_tokens: list[(str, int)], + receipt_tokens: list[(str, int)], + address: str, + liq_calldata: bytes, + bid: int, + valid_until: int, + secret_key: str +): digest = encode( - ['(address,uint256)[]', '(address,uint256)[]', 'address', 'bytes', 'uint256'], + ['(address,uint256)[]', '(address,uint256)[]', + 'address', 'bytes', 'uint256'], [repay_tokens, receipt_tokens, address, liq_calldata, bid] ) msg_data = web3.Web3.solidity_keccak( ['bytes', 'uint256'], [digest, valid_until]) signature = w3.eth.account.signHash( msg_data, private_key=secret_key) - - return signature \ No newline at end of file + + return signature diff --git a/beacon/surface_opportunities.py b/beacon/surface_opportunities.py index 93b009cf..2dd4905a 100644 --- a/beacon/surface_opportunities.py +++ b/beacon/surface_opportunities.py @@ -5,9 +5,11 @@ from beacon.utils.pyth_prices import * from beacon.utils.endpoints import * -OPERATOR_API_KEY = "password" ## TODO: turn on authorization in the surface post requests +# TODO: turn on authorization in the surface post requests +OPERATOR_API_KEY = "password" PROTOCOLS = [beacon_TokenVault] + async def main(): # get prices pyth_price_feed_ids = await get_price_feed_ids() @@ -24,8 +26,9 @@ async def main(): for protocol in PROTOCOLS: accounts = await protocol.get_accounts() - liquidatable_protocol = protocol.get_liquidatable(accounts, pyth_prices_latest) - + liquidatable_protocol = protocol.get_liquidatable( + accounts, pyth_prices_latest) + liquidatable += liquidatable_protocol CLIENT = httpx.AsyncClient() @@ -38,4 +41,4 @@ async def main(): print(f"Response PER post: {resp.text}") if __name__ == "__main__": - asyncio.run(main()) \ No newline at end of file + asyncio.run(main()) diff --git a/beacon/utils/endpoints.py b/beacon/utils/endpoints.py index a1ac9dcc..b0c71992 100644 --- a/beacon/utils/endpoints.py +++ b/beacon/utils/endpoints.py @@ -2,4 +2,4 @@ AUCTION_SERVER_ENDPOINT = f"http://localhost:9000/bid" BEACON_SERVER_ENDPOINT_SURFACE = f"{BEACON_SERVER_ENDPOINT}/surface" -BEACON_SERVER_ENDPOINT_GETOPPS = f"{BEACON_SERVER_ENDPOINT}/getOpps" \ No newline at end of file +BEACON_SERVER_ENDPOINT_GETOPPS = f"{BEACON_SERVER_ENDPOINT}/getOpps" diff --git a/beacon/utils/types_liquidation_adapter.py b/beacon/utils/types_liquidation_adapter.py index 4dda45e9..bdf61728 100644 --- a/beacon/utils/types_liquidation_adapter.py +++ b/beacon/utils/types_liquidation_adapter.py @@ -5,6 +5,7 @@ LIQUIDATION_ADAPTER_ADDRESS = "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" + class LiquidationOpportunity(TypedDict): chain_id: str # Address of the contract where the liquidation method is called @@ -20,9 +21,10 @@ class LiquidationOpportunity(TypedDict): prices: list[PriceFeed] - LIQUIDATION_ADAPTER_CALLDATA_TYPES = '((address,uint256)[],(address,uint256)[],address,address,bytes,uint256,uint256,bytes)' -LIQUIDATION_ADAPTER_FN_SIGNATURE = web3.Web3.solidity_keccak(["string"], [f"callLiquidation({LIQUIDATION_ADAPTER_CALLDATA_TYPES})"])[:4].hex() +LIQUIDATION_ADAPTER_FN_SIGNATURE = web3.Web3.solidity_keccak( + ["string"], [f"callLiquidation({LIQUIDATION_ADAPTER_CALLDATA_TYPES})"])[:4].hex() + class LiquidationAdapterCalldata(TypedDict): repay_tokens: list[(str, int)] @@ -34,9 +36,10 @@ class LiquidationAdapterCalldata(TypedDict): bid: int signature_liquidator: bytes + class LiquidationAdapterIntent(TypedDict): bid: str calldata: str chain_id: str contract: str - permission_key: str \ No newline at end of file + permission_key: str diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..ff95bfbb --- /dev/null +++ b/package-lock.json @@ -0,0 +1,90 @@ +{ + "name": "per", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "devDependencies": { + "prettier": "^3.1.1", + "prettier-plugin-solidity": "^1.3.1" + } + }, + "node_modules/@solidity-parser/parser": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.17.0.tgz", + "integrity": "sha512-Nko8R0/kUo391jsEHHxrGM07QFdnPGvlmox4rmH0kNiNAashItAilhy4Mv4pK5gQmW5f4sXAF58fwJbmlkGcVw==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/prettier": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.1.tgz", + "integrity": "sha512-22UbSzg8luF4UuZtzgiUOfcGM8s4tjBv6dJRT7j275NXsy2jb4aJa4NNveul5x4eqlF1wuhuR2RElK71RvmVaw==", + "dev": true, + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-solidity": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/prettier-plugin-solidity/-/prettier-plugin-solidity-1.3.1.tgz", + "integrity": "sha512-MN4OP5I2gHAzHZG1wcuJl0FsLS3c4Cc5494bbg+6oQWBPuEamjwDvmGfFMZ6NFzsh3Efd9UUxeT7ImgjNH4ozA==", + "dev": true, + "dependencies": { + "@solidity-parser/parser": "^0.17.0", + "semver": "^7.5.4", + "solidity-comments-extractor": "^0.0.8" + }, + "engines": { + "node": ">=16" + }, + "peerDependencies": { + "prettier": ">=2.3.0" + } + }, + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/solidity-comments-extractor": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/solidity-comments-extractor/-/solidity-comments-extractor-0.0.8.tgz", + "integrity": "sha512-htM7Vn6LhHreR+EglVMd2s+sZhcXAirB1Zlyrv5zBuTxieCvjfnRpd7iZk75m/u6NOlEyQ94C6TWbBn2cY7w8g==", + "dev": true + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..19654a1f --- /dev/null +++ b/package.json @@ -0,0 +1,6 @@ +{ + "devDependencies": { + "prettier": "^3.1.1", + "prettier-plugin-solidity": "^1.3.1" + } +} diff --git a/per_multicall/README.md b/per_multicall/README.md index 70ac4667..64b20b8e 100644 --- a/per_multicall/README.md +++ b/per_multicall/README.md @@ -18,7 +18,7 @@ The multicall contract is in `PERMulticall.sol`. It includes functionality to ca Tests can be found in `test/`. These tests include checks that the protocol functions work, as well as checks around permissioning, bid conditions, and appropriate failing of components of the multicall bundle (without failing the whole bundle). -To run tests with the appropriate stack depth and console logging, run +To run tests with the appropriate stack depth and console logging, run ```shell $ forge test -vvv --via-ir @@ -29,21 +29,25 @@ You can also run a local validator via `anvil --gas-limit 500000000000000000 --b To run the script runs in `Vault.s.sol`, you should startup the local validator. Then, run the necessary setup commands: 1. Set up contracts and save to an environment JSON. + ```shell $ forge script script/Vault.s.sol --via-ir --fork-url http://localhost:8545 --sender 0xd6e417287b875a3932c1ff5dcb26d4d2c8b90b40 -vvv --sig 'setUpContracts()' --broadcast ``` 2. Set oracle prices to allow for vault creation. + ```shell $ forge script script/Vault.s.sol --via-ir --fork-url http://localhost:8545 --sender 0xd6e417287b875a3932c1ff5dcb26d4d2c8b90b40 --private-key 0xf46ea803192f16ef1c4f1d5fb0d6060535dbd571ea1afc7db6816f28961ba78a -vvv --sig 'setOraclePrice(int64,int64,uint64)' 110 110 190 --broadcast ``` 3. Vault creation. + ```shell $ forge script script/Vault.s.sol --via-ir --fork-url http://localhost:8545 --sender 0xd6e417287b875a3932c1ff5dcb26d4d2c8b90b40 -vvv --sig 'setUpVault(uint256,uint256,bool)' 100 80 true --broadcast ``` 4. Undercollateralize the vault by moving prices. + ```shell $ forge script script/Vault.s.sol --via-ir --fork-url http://localhost:8545 --sender 0xd6e417287b875a3932c1ff5dcb26d4d2c8b90b40 --private-key 0xf46ea803192f16ef1c4f1d5fb0d6060535dbd571ea1afc7db6816f28961ba78a -vvv --sig 'setOraclePrice(int64,int64,uint64)' 110 200 200 --broadcast ``` diff --git a/per_multicall/remappings.txt b/per_multicall/remappings.txt index 1381a220..821595eb 100644 --- a/per_multicall/remappings.txt +++ b/per_multicall/remappings.txt @@ -3,4 +3,4 @@ ds-test/=lib/forge-std/lib/ds-test/src/ erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/ forge-std/=lib/forge-std/src/ openzeppelin-contracts/=lib/openzeppelin-contracts/ -@pythnetwork/pyth-sdk-solidity=node_modules/@pythnetwork/pyth-sdk-solidity/ \ No newline at end of file +@pythnetwork/pyth-sdk-solidity=node_modules/@pythnetwork/pyth-sdk-solidity/ diff --git a/per_multicall/script/Vault.s.sol b/per_multicall/script/Vault.s.sol index 193e45af..b2d01b7f 100644 --- a/per_multicall/script/Vault.s.sol +++ b/per_multicall/script/Vault.s.sol @@ -27,10 +27,12 @@ import "../src/Errors.sol"; contract VaultScript is Script { string public latestEnvironmentPath = "latestEnvironment.json"; - function getAnvil() public view returns (address, uint256) { // TODO: these are mnemonic wallets. figure out how to transfer ETH from them outside of explicitly hardcoding them here. - return (address(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266), 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80); + return ( + address(0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266), + 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 + ); } function deployWeth() public returns (address) { @@ -43,21 +45,29 @@ contract VaultScript is Script { } function deployPER(address wethAddress) public returns (address, address) { - (address perOperatorAddress, uint256 perOperatorSk) = makeAddrAndKey("perOperator"); + (address perOperatorAddress, uint256 perOperatorSk) = makeAddrAndKey( + "perOperator" + ); console.log("pk per operator", perOperatorAddress); console.log("sk per operator", perOperatorSk); - (,uint256 skanvil) = getAnvil(); + (, uint256 skanvil) = getAnvil(); vm.startBroadcast(skanvil); payable(perOperatorAddress).transfer(10 ether); PERMulticall multicall = new PERMulticall(perOperatorAddress, 0); console.log("deployed PER contract at", address(multicall)); - LiquidationAdapter liquidationAdapter = new LiquidationAdapter(address(multicall), wethAddress); + LiquidationAdapter liquidationAdapter = new LiquidationAdapter( + address(multicall), + wethAddress + ); vm.stopBroadcast(); return (address(multicall), address(liquidationAdapter)); } - function deployVault(address multicall, address oracle) public returns (address) { + function deployVault( + address multicall, + address oracle + ) public returns (address) { // make token vault deployer wallet (, uint256 tokenVaultDeployerSk) = makeAddrAndKey("tokenVaultDeployer"); console.log("sk token vault deployer", tokenVaultDeployerSk); @@ -69,7 +79,7 @@ contract VaultScript is Script { } function deployMockPyth() public returns (address) { - (,uint256 skanvil) = getAnvil(); + (, uint256 skanvil) = getAnvil(); vm.startBroadcast(skanvil); MockPyth mockPyth = new MockPyth(1_000_000, 0); console.log("deployed mock pyth contract at", address(mockPyth)); @@ -77,7 +87,10 @@ contract VaultScript is Script { return address(mockPyth); } - function deployAll() public returns (address, address, address, address, address) { + function deployAll() + public + returns (address, address, address, address, address) + { address weth = deployWeth(); (address per, address liquidationAdapter) = deployPER(weth); address mockPyth = deployMockPyth(); @@ -100,7 +113,7 @@ contract VaultScript is Script { uint256[] memory sksScript = new uint256[](5); uint256[] memory qtys; - + // make searcherA and searcherB wallets (addressesScript[0], sksScript[0]) = makeAddrAndKey("searcherA"); (addressesScript[1], sksScript[1]) = makeAddrAndKey("searcherB"); @@ -115,8 +128,10 @@ contract VaultScript is Script { (addressesScript[3], sksScript[3]) = makeAddrAndKey("perOperator"); // make tokenVaultDeployer wallet - (addressesScript[4], sksScript[4]) = makeAddrAndKey("tokenVaultDeployer"); - + (addressesScript[4], sksScript[4]) = makeAddrAndKey( + "tokenVaultDeployer" + ); + // TODO: these are mnemonic wallets. figure out how to transfer ETH from them outside of explicitly hardcoding them here. (address pkanvil, uint256 skanvil) = getAnvil(); @@ -135,9 +150,19 @@ contract VaultScript is Script { vm.stopBroadcast(); // deploy weth, multicall, liquidationAdapter, oracle, tokenVault - address multicallAddress; address liquidationAdapterAddress; address oracleAddress; address tokenVaultAddress; address wethAddress; - (multicallAddress, liquidationAdapterAddress, oracleAddress, tokenVaultAddress, wethAddress) = deployAll(); - + address multicallAddress; + address liquidationAdapterAddress; + address oracleAddress; + address tokenVaultAddress; + address wethAddress; + ( + multicallAddress, + liquidationAdapterAddress, + oracleAddress, + tokenVaultAddress, + wethAddress + ) = deployAll(); + // instantiate searcher A's contract with searcher A as sender/origin vm.startBroadcast(sksScript[0]); console.log("balance of pk searcherA", addressesScript[0].balance); @@ -208,20 +233,25 @@ contract VaultScript is Script { vm.stopBroadcast(); - // searchers A and B approve liquidation adapter to spend their tokens vm.startBroadcast(sksScript[0]); IERC20(address(token1)).approve(liquidationAdapterAddress, 199_999_999); IERC20(address(token2)).approve(liquidationAdapterAddress, 199_999_999); // deposit ETH to get WETH - WETH9(payable(wethAddress)).deposit{ value: 1 ether }(); - WETH9(payable(wethAddress)).approve(liquidationAdapterAddress, 399_999_999); + WETH9(payable(wethAddress)).deposit{value: 1 ether}(); + WETH9(payable(wethAddress)).approve( + liquidationAdapterAddress, + 399_999_999 + ); vm.stopBroadcast(); vm.startBroadcast(sksScript[1]); IERC20(address(token1)).approve(liquidationAdapterAddress, 199_999_999); IERC20(address(token2)).approve(liquidationAdapterAddress, 199_999_999); - WETH9(payable(wethAddress)).deposit{ value: 1 ether }(); - WETH9(payable(wethAddress)).approve(liquidationAdapterAddress, 399_999_999); + WETH9(payable(wethAddress)).deposit{value: 1 ether}(); + WETH9(payable(wethAddress)).approve( + liquidationAdapterAddress, + 399_999_999 + ); vm.stopBroadcast(); string memory obj = "latestEnvironment"; @@ -229,7 +259,11 @@ contract VaultScript is Script { vm.serializeAddress(obj, "searcherA", address(searcherA)); vm.serializeAddress(obj, "searcherB", address(searcherB)); vm.serializeAddress(obj, "multicall", multicallAddress); - vm.serializeAddress(obj, "liquidationAdapter", liquidationAdapterAddress); + vm.serializeAddress( + obj, + "liquidationAdapter", + liquidationAdapterAddress + ); vm.serializeAddress(obj, "oracle", oracleAddress); vm.serializeAddress(obj, "weth", wethAddress); @@ -255,7 +289,11 @@ contract VaultScript is Script { vm.writeJson(finalJSON, latestEnvironmentPath); } - function setOraclePrice(int64 priceT1, int64 priceT2, uint64 publishTime) public { + function setOraclePrice( + int64 priceT1, + int64 priceT2, + uint64 publishTime + ) public { string memory json = vm.readFile(latestEnvironmentPath); address oracleLatest = vm.parseJsonAddress(json, ".oracle"); bytes32 idToken1Latest = vm.parseJsonBytes32(json, ".idToken1"); @@ -264,8 +302,26 @@ contract VaultScript is Script { MockPyth oracle = MockPyth(payable(oracleLatest)); // set initial oracle prices - bytes memory token1UpdateData = oracle.createPriceFeedUpdateData(idToken1Latest, priceT1, 1, 0, priceT1, 0, publishTime, 0); - bytes memory token2UpdateData = oracle.createPriceFeedUpdateData(idToken2Latest, priceT2, 1, 0, priceT2, 0, publishTime, 0); + bytes memory token1UpdateData = oracle.createPriceFeedUpdateData( + idToken1Latest, + priceT1, + 1, + 0, + priceT1, + 0, + publishTime, + 0 + ); + bytes memory token2UpdateData = oracle.createPriceFeedUpdateData( + idToken2Latest, + priceT2, + 1, + 0, + priceT2, + 0, + publishTime, + 0 + ); bytes[] memory updateData = new bytes[](2); updateData[0] = token1UpdateData; @@ -291,23 +347,53 @@ contract VaultScript is Script { bytes32 idToken2Latest = vm.parseJsonBytes32(json, ".idToken2"); uint256 numVaults; - console.log("depositor token balances, before:", IERC20(token1Latest).balanceOf(depositorLatest), IERC20(token2Latest).balanceOf(depositorLatest)); + console.log( + "depositor token balances, before:", + IERC20(token1Latest).balanceOf(depositorLatest), + IERC20(token2Latest).balanceOf(depositorLatest) + ); if (collatT1) { vm.startBroadcast(depositorSkLatest); IERC20(token1Latest).approve(address(tokenVaultLatest), qT1); - numVaults = TokenVault(payable(tokenVaultLatest)).createVault(token1Latest, token2Latest, qT1, qT2, 110 * (10**16), 1 * (10**16), idToken1Latest, idToken2Latest); + numVaults = TokenVault(payable(tokenVaultLatest)).createVault( + token1Latest, + token2Latest, + qT1, + qT2, + 110 * (10 ** 16), + 1 * (10 ** 16), + idToken1Latest, + idToken2Latest + ); vm.stopBroadcast(); } else { vm.startBroadcast(depositorSkLatest); IERC20(token2Latest).approve(address(tokenVaultLatest), qT2); - numVaults = TokenVault(payable(tokenVaultLatest)).createVault(token2Latest, token1Latest, qT2, qT1, 110 * (10**16), 1 * (10**16), idToken1Latest, idToken2Latest); + numVaults = TokenVault(payable(tokenVaultLatest)).createVault( + token2Latest, + token1Latest, + qT2, + qT1, + 110 * (10 ** 16), + 1 * (10 ** 16), + idToken1Latest, + idToken2Latest + ); vm.stopBroadcast(); } - console.log("depositor token balances, after:", IERC20(token1Latest).balanceOf(depositorLatest), IERC20(token2Latest).balanceOf(depositorLatest)); - - vm.writeJson(Strings.toString(numVaults), latestEnvironmentPath, ".numVaults"); + console.log( + "depositor token balances, after:", + IERC20(token1Latest).balanceOf(depositorLatest), + IERC20(token2Latest).balanceOf(depositorLatest) + ); + + vm.writeJson( + Strings.toString(numVaults), + latestEnvironmentPath, + ".numVaults" + ); } function getBalanceEth(address addy) public view returns (uint256) { @@ -316,7 +402,10 @@ contract VaultScript is Script { return balance; } - function getBalanceErc(address addy, address token) public view returns (uint256) { + function getBalanceErc( + address addy, + address token + ) public view returns (uint256) { uint256 balance = IERC20(token).balanceOf(addy); console.log(balance); return balance; @@ -334,27 +423,46 @@ contract VaultScript is Script { function getVault(uint256 vaultID) public view returns (Vault memory) { string memory json = vm.readFile(latestEnvironmentPath); address tokenVaultLatest = vm.parseJsonAddress(json, ".tokenVault"); - Vault memory vault = TokenVault(payable(tokenVaultLatest)).getVault(vaultID); - console.log("vault amounts are", vault.amountCollateral, vault.amountDebt); + Vault memory vault = TokenVault(payable(tokenVaultLatest)).getVault( + vaultID + ); + console.log( + "vault amounts are", + vault.amountCollateral, + vault.amountDebt + ); return vault; } - function getAllowances(address from, address spender) public view returns (uint256, uint256) { + function getAllowances( + address from, + address spender + ) public view returns (uint256, uint256) { string memory json = vm.readFile(latestEnvironmentPath); address token1Latest = vm.parseJsonAddress(json, ".token1"); address token2Latest = vm.parseJsonAddress(json, ".token2"); - console.log("allowances are", IERC20(token1Latest).allowance(from, spender), IERC20(token2Latest).allowance(from, spender)); - console.log("balances are", IERC20(token1Latest).balanceOf(from), IERC20(token2Latest).balanceOf(from)); - return (IERC20(token1Latest).allowance(from, spender), IERC20(token2Latest).allowance(from, spender)); + console.log( + "allowances are", + IERC20(token1Latest).allowance(from, spender), + IERC20(token2Latest).allowance(from, spender) + ); + console.log( + "balances are", + IERC20(token1Latest).balanceOf(from), + IERC20(token2Latest).balanceOf(from) + ); + return ( + IERC20(token1Latest).allowance(from, spender), + IERC20(token2Latest).allowance(from, spender) + ); } function tryLiquidationAdapterContract() public view returns (address) { string memory json = vm.readFile(latestEnvironmentPath); - address liquidationAdapter = vm.parseJsonAddress(json, ".liquidationAdapter"); + address liquidationAdapter = vm.parseJsonAddress( + json, + ".liquidationAdapter" + ); return LiquidationAdapter(payable(liquidationAdapter)).getWeth(); } } - - - - diff --git a/per_multicall/src/Errors.sol b/per_multicall/src/Errors.sol index e604d681..435cd3aa 100644 --- a/per_multicall/src/Errors.sol +++ b/per_multicall/src/Errors.sol @@ -27,4 +27,4 @@ error NotRegistered(); error LiquidationCallFailed(string reason); -error BogusContract(); \ No newline at end of file +error BogusContract(); diff --git a/per_multicall/src/LiquidationAdapter.sol b/per_multicall/src/LiquidationAdapter.sol index 1edefbb0..50e55519 100644 --- a/per_multicall/src/LiquidationAdapter.sol +++ b/per_multicall/src/LiquidationAdapter.sol @@ -19,14 +19,11 @@ contract LiquidationAdapter is SigVerify { /** * @notice LiquidationAdapter constructor - Initializes a new liquidation adapter contract with given parameters - * + * * @param perMulticall: address of PER multicall * @param weth: address of WETH contract */ - constructor( - address perMulticall, - address weth - ) { + constructor(address perMulticall, address weth) { _perMulticall = perMulticall; _weth = weth; } @@ -45,9 +42,11 @@ contract LiquidationAdapter is SigVerify { return _weth; } - function _getRevertMsg(bytes memory _returnData) internal pure returns (string memory) { + function _getRevertMsg( + bytes memory _returnData + ) internal pure returns (string memory) { // If the _res length is less than 68, then the transaction failed silently (without a revert message) - if (_returnData.length < 68) return 'Transaction reverted silently'; + if (_returnData.length < 68) return "Transaction reverted silently"; assembly { // Slice the sighash. @@ -63,7 +62,18 @@ contract LiquidationAdapter is SigVerify { revert Unauthorized(); } - bool validSignature = verifyCalldata(params.liquidator, abi.encode(params.repayTokens, params.expectedReceiptTokens, params.contractAddress, params.data, params.bid), params.validUntil, params.signatureLiquidator); + bool validSignature = verifyCalldata( + params.liquidator, + abi.encode( + params.repayTokens, + params.expectedReceiptTokens, + params.contractAddress, + params.data, + params.bid + ), + params.validUntil, + params.signatureLiquidator + ); if (!validSignature) { revert InvalidSearcherSignature(); } @@ -74,55 +84,72 @@ contract LiquidationAdapter is SigVerify { revert SignatureAlreadyUsed(); } - uint256[] memory balancesExpectedReceipt = new uint256[](params.expectedReceiptTokens.length); + uint256[] memory balancesExpectedReceipt = new uint256[]( + params.expectedReceiptTokens.length + ); // transfer repay tokens to this contract for (uint i = 0; i < params.repayTokens.length; i++) { IERC20 token = IERC20(params.repayTokens[i].token); - token.transferFrom(params.liquidator, address(this), params.repayTokens[i].amount); + token.transferFrom( + params.liquidator, + address(this), + params.repayTokens[i].amount + ); // approve contract to spend repay tokens token.approve(params.contractAddress, params.repayTokens[i].amount); } - + // get balances of receipt tokens before call for (uint i = 0; i < params.expectedReceiptTokens.length; i++) { IERC20 token = IERC20(params.expectedReceiptTokens[i].token); uint256 amount = params.expectedReceiptTokens[i].amount; - balancesExpectedReceipt[i] = token.balanceOf(address(this)) + amount; + balancesExpectedReceipt[i] = + token.balanceOf(address(this)) + + amount; } - (bool success, bytes memory reason) = params.contractAddress.call(params.data); + (bool success, bytes memory reason) = params.contractAddress.call( + params.data + ); if (!success) { string memory revertData = _getRevertMsg(reason); revert LiquidationCallFailed(revertData); } - + // check balances of receipt tokens after call and transfer to liquidator for (uint i = 0; i < params.expectedReceiptTokens.length; i++) { IERC20 token = IERC20(params.expectedReceiptTokens[i].token); uint256 amount = params.expectedReceiptTokens[i].amount; uint256 balanceFinal = token.balanceOf(address(this)); - require(balanceFinal >= balancesExpectedReceipt[i], "insufficient token received"); + require( + balanceFinal >= balancesExpectedReceipt[i], + "insufficient token received" + ); // transfer receipt tokens to liquidator token.transfer(params.liquidator, amount); } - + // transfer bid to PER adapter in the form of weth address weth = getWeth(); - WETH9(payable(weth)).transferFrom(params.liquidator, address(this), params.bid); + WETH9(payable(weth)).transferFrom( + params.liquidator, + address(this), + params.bid + ); // unwrap weth to eth WETH9(payable(weth)).withdraw(params.bid); // transfer eth to PER multicall payable(getPERMulticall()).transfer(params.bid); // mark signature as used - _signatureUsed[params.signatureLiquidator] = true; + _signatureUsed[params.signatureLiquidator] = true; } receive() external payable {} // TODO: can we get rid of this? seems not but unsure why diff --git a/per_multicall/src/MyToken.sol b/per_multicall/src/MyToken.sol index ddc92cda..c75b3e15 100644 --- a/per_multicall/src/MyToken.sol +++ b/per_multicall/src/MyToken.sol @@ -3,16 +3,17 @@ pragma solidity ^0.8.13; import "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; -contract MyToken is ERC20{ - - constructor (string memory _name, string memory _symbol) ERC20 (_name,_symbol){ - } +contract MyToken is ERC20 { + constructor( + string memory _name, + string memory _symbol + ) ERC20(_name, _symbol) {} function mint(address to, uint256 amount) public virtual { - _mint(to,amount); + _mint(to, amount); } function burn(address form, uint amount) public virtual { _burn(form, amount); } -} \ No newline at end of file +} diff --git a/per_multicall/src/OnChainAuction.sol b/per_multicall/src/OnChainAuction.sol index f5ee2e13..392b9f7b 100644 --- a/per_multicall/src/OnChainAuction.sol +++ b/per_multicall/src/OnChainAuction.sol @@ -16,36 +16,37 @@ struct Bid { } contract AuctionManager { - mapping (bytes32 => Bid) _bids; - mapping (address => uint256) _feeConfig; + mapping(bytes32 => Bid) _bids; + mapping(address => uint256) _feeConfig; address _perOperator; - function bid(bytes32 permissionKeyHash) public payable{ + function bid(bytes32 permissionKeyHash) public payable { Bid memory currentBid = _bids[permissionKeyHash]; - if(currentBid.bidder != address(0)) { - if(currentBid.amount > msg.value) { + if (currentBid.bidder != address(0)) { + if (currentBid.amount > msg.value) { revert BidTooLow(); - } - else{ + } else { payable(currentBid.bidder).transfer(currentBid.amount); // return the previous bid } } - _bids[permissionKeyHash] = Bid(msg.sender, msg.value,block.number); + _bids[permissionKeyHash] = Bid(msg.sender, msg.value, block.number); } - mapping (bytes32 => bool) _permissions; + + mapping(bytes32 => bool) _permissions; /** * @notice constructor - Initializes a new auction manager with an operator used for setting the fees * * @param perOperatorAddress: address of PER operator */ - constructor( - address perOperatorAddress - ) { + constructor(address perOperatorAddress) { _perOperator = perOperatorAddress; } - function isPermissioned(address profitReceiver, bytes calldata message) public view returns (bool permissioned) { + function isPermissioned( + address profitReceiver, + bytes calldata message + ) public view returns (bool permissioned) { return _permissions[keccak256(abi.encode(profitReceiver, message))]; } @@ -55,8 +56,11 @@ contract AuctionManager { * @param feeRecipient: address of the fee recipient for the contract being registered * @param feeSplit: amount of fee to be split with the protocol. 10**18 is 100% */ - function setFee(address feeRecipient,uint256 feeSplit) public { - require(msg.sender == _perOperator, "only PER operator can set the fees"); + function setFee(address feeRecipient, uint256 feeSplit) public { + require( + msg.sender == _perOperator, + "only PER operator can set the fees" + ); _feeConfig[feeRecipient] = feeSplit; } @@ -65,30 +69,34 @@ contract AuctionManager { address contractAddress, bytes calldata data ) public payable { - for(uint256 i =0 ;i 0) { - uint256 feeProtocol = feeProtocolNumerator / 1000_000_000_000_000_000; + uint256 feeProtocol = feeProtocolNumerator / + 1000_000_000_000_000_000; payable(feeReceiver).transfer(feeProtocol); } delete _bids[permissionKeyHash]; } - } diff --git a/per_multicall/src/PERFeeReceiver.sol b/per_multicall/src/PERFeeReceiver.sol index b2039e19..105c557b 100644 --- a/per_multicall/src/PERFeeReceiver.sol +++ b/per_multicall/src/PERFeeReceiver.sol @@ -2,5 +2,7 @@ pragma solidity ^0.8.0; interface PERFeeReceiver { - function receiveAuctionProceedings(bytes calldata permissionKey) external payable; + function receiveAuctionProceedings( + bytes calldata permissionKey + ) external payable; } diff --git a/per_multicall/src/PERMulticall.sol b/per_multicall/src/PERMulticall.sol index 453c92b5..29196e52 100644 --- a/per_multicall/src/PERMulticall.sol +++ b/per_multicall/src/PERMulticall.sol @@ -13,20 +13,17 @@ contract PERMulticall { event ReceivedETH(address sender, uint256 amount); address _perOperator; - mapping (address => uint256) _feeConfig; - mapping (bytes32 => bool) _permissions; + mapping(address => uint256) _feeConfig; + mapping(bytes32 => bool) _permissions; uint256 _defaultFee; /** * @notice PERMulticall constructor - Initializes a new multicall contract with given parameters - * + * * @param perOperatorAddress: address of PER operator EOA * @param defaultFee: default fee split to be paid to the protocol whose permissioning is being used */ - constructor( - address perOperatorAddress, - uint256 defaultFee - ) { + constructor(address perOperatorAddress, uint256 defaultFee) { _perOperator = perOperatorAddress; _defaultFee = defaultFee; } @@ -38,7 +35,10 @@ contract PERMulticall { return _perOperator; } - function isPermissioned(address profitReceiver, bytes calldata message) public view returns (bool permissioned) { + function isPermissioned( + address profitReceiver, + bytes calldata message + ) public view returns (bool permissioned) { return _permissions[keccak256(abi.encode(profitReceiver, message))]; } @@ -48,8 +48,11 @@ contract PERMulticall { * @param feeRecipient: address of the fee recipient for the contract being registered * @param feeSplit: amount of fee to be split with the protocol. 10**18 is 100% */ - function setFee(address feeRecipient,uint256 feeSplit) public { - require(msg.sender == _perOperator, "only PER operator can set the fees"); + function setFee(address feeRecipient, uint256 feeSplit) public { + require( + msg.sender == _perOperator, + "only PER operator can set the fees" + ); _feeConfig[feeRecipient] = feeSplit; } @@ -61,7 +64,9 @@ contract PERMulticall { return (size > 0); } - function _bytesToAddress(bytes memory bys) private pure returns (address addr) { + function _bytesToAddress( + bytes memory bys + ) private pure returns (address addr) { (addr, ) = abi.decode(bys, (address, bytes)); } @@ -75,19 +80,28 @@ contract PERMulticall { */ function multicall( bytes calldata permission, - address[] calldata contracts, + address[] calldata contracts, bytes[] calldata data, uint256[] calldata bids ) public payable returns (MulticallStatus[] memory multicallStatuses) { - require(msg.sender == _perOperator, "only PER operator can call this function"); - require(permission.length >= 20, "permission size should be at least 20 bytes"); + require( + msg.sender == _perOperator, + "only PER operator can call this function" + ); + require( + permission.length >= 20, + "permission size should be at least 20 bytes" + ); _permissions[keccak256(permission)] = true; multicallStatuses = new MulticallStatus[](data.length); uint256 totalBid = 0; for (uint256 i = 0; i < data.length; i++) { // try/catch will revert if call to searcher fails or if bid conditions not met - try this.callWithBid(contracts[i], data[i], bids[i]) returns (bool success, bytes memory result) { + try this.callWithBid(contracts[i], data[i], bids[i]) returns ( + bool success, + bytes memory result + ) { multicallStatuses[i].externalSuccess = success; multicallStatuses[i].externalResult = result; } catch Error(string memory reason) { @@ -105,11 +119,13 @@ contract PERMulticall { } uint256 feeProtocolNumerator = totalBid * protocolFee; if (feeProtocolNumerator > 0) { - uint256 feeProtocol = feeProtocolNumerator / 1000_000_000_000_000_000; + uint256 feeProtocol = feeProtocolNumerator / + 1000_000_000_000_000_000; if (_isContract(feeReceiver)) { - PERFeeReceiver(feeReceiver).receiveAuctionProceedings{value: feeProtocol}(permission); - } - else{ + PERFeeReceiver(feeReceiver).receiveAuctionProceedings{ + value: feeProtocol + }(permission); + } else { payable(feeReceiver).transfer(feeProtocol); } } @@ -118,7 +134,7 @@ contract PERMulticall { /** * @notice callWithBid function - contained call to function with check for bid invariant - * + * * @param contractAddress: contract address to call into * @param data: calldata to call with * @param bid: bid to be paid; call will fail if it does not pay PER operator at least bid, @@ -136,13 +152,17 @@ contract PERMulticall { uint256 balanceFinalEth = address(this).balance; // ensure that PER operator was paid at least bid ETH - require (!(balanceFinalEth - balanceInitEth < bid) && !(balanceFinalEth < balanceInitEth), "invalid bid"); + require( + !(balanceFinalEth - balanceInitEth < bid) && + !(balanceFinalEth < balanceInitEth), + "invalid bid" + ); } return (success, result); } - receive() external payable { + receive() external payable { emit ReceivedETH(msg.sender, msg.value); } } diff --git a/per_multicall/src/SearcherVault.sol b/per_multicall/src/SearcherVault.sol index 6c0478a6..97564689 100644 --- a/per_multicall/src/SearcherVault.sol +++ b/per_multicall/src/SearcherVault.sol @@ -25,7 +25,7 @@ contract SearcherVault is SigVerify { /** * @notice Searcher constructor - Initializes a new searcher contract with given parameters around token vault protocol - * + * * @param perMulticallAddress: address of PER contract * @param protocolAddress: address of token vault protocol contract */ @@ -35,19 +35,16 @@ contract SearcherVault is SigVerify { tokenVault = protocolAddress; } - function _updatePriceFeed( - bytes calldata updateData - ) internal { + function _updatePriceFeed(bytes calldata updateData) internal { bytes[] memory updateDatas = new bytes[](1); updateDatas[0] = updateData; address oracle = TokenVault(payable(tokenVault)).getOracle(); MockPyth(oracle).updatePriceFeeds(updateDatas); } - /** * @notice doLiquidatePER function - liquidates a vault through PER - * + * * @param vaultID: ID of the vault to be liquidated * @param bid: size of the bid to pay to PER operator * @param validUntil: block number until which signatureSearcher is valid @@ -66,7 +63,12 @@ contract SearcherVault is SigVerify { } if (msg.sender == perMulticall) { - bool validSignatureSearcher = verifyCalldata(owner, abi.encodePacked(vaultID, bid), validUntil, signatureSearcher); + bool validSignatureSearcher = verifyCalldata( + owner, + abi.encodePacked(vaultID, bid), + validUntil, + signatureSearcher + ); if (!validSignatureSearcher) { revert InvalidSearcherSignature(); } @@ -78,7 +80,7 @@ contract SearcherVault is SigVerify { } } - if(updateData.length > 0) { + if (updateData.length > 0) { _updatePriceFeed(updateData); } @@ -92,7 +94,7 @@ contract SearcherVault is SigVerify { IERC20(tokenDebt).approve(vaultContract, tokenAmount); TokenVault(vaultContract).liquidate(vaultID); - if(bid>0){ + if (bid > 0) { payable(perMulticall).transfer(bid); } diff --git a/per_multicall/src/SigVerify.sol b/per_multicall/src/SigVerify.sol index f6fa39dc..da80323b 100644 --- a/per_multicall/src/SigVerify.sol +++ b/per_multicall/src/SigVerify.sol @@ -77,4 +77,4 @@ contract SigVerify { // implicitly return (r, s, v) } -} \ No newline at end of file +} diff --git a/per_multicall/src/Structs.sol b/per_multicall/src/Structs.sol index 938ed4e0..7b1e5423 100644 --- a/per_multicall/src/Structs.sol +++ b/per_multicall/src/Structs.sol @@ -1,41 +1,41 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; -struct OracleState { - uint256 price; - uint256 timestamp; +struct OracleState { + uint256 price; + uint256 timestamp; } struct Vault { - address tokenCollateral; - address tokenDebt; - uint256 amountCollateral; - uint256 amountDebt; - uint256 minHealthRatio; // 10**18 is 100% - uint256 minPermissionLessHealthRatio; - bytes32 tokenIDCollateral; - bytes32 tokenIDDebt; + address tokenCollateral; + address tokenDebt; + uint256 amountCollateral; + uint256 amountDebt; + uint256 minHealthRatio; // 10**18 is 100% + uint256 minPermissionLessHealthRatio; + bytes32 tokenIDCollateral; + bytes32 tokenIDDebt; } // TODO: rename struct TokenQty { - address token; - uint256 amount; + address token; + uint256 amount; } struct MulticallStatus { - bool externalSuccess; - bytes externalResult; - string multicallRevertReason; + bool externalSuccess; + bytes externalResult; + string multicallRevertReason; } struct LiquidationCallParams { - TokenQty[] repayTokens; - TokenQty[] expectedReceiptTokens; - address liquidator; - address contractAddress; - bytes data; - uint256 validUntil; - uint256 bid; - bytes signatureLiquidator; -} \ No newline at end of file + TokenQty[] repayTokens; + TokenQty[] expectedReceiptTokens; + address liquidator; + address contractAddress; + bytes data; + uint256 validUntil; + uint256 bid; + bytes signatureLiquidator; +} diff --git a/per_multicall/src/TokenVault.sol b/per_multicall/src/TokenVault.sol index 31949ca8..aec4a49f 100644 --- a/per_multicall/src/TokenVault.sol +++ b/per_multicall/src/TokenVault.sol @@ -29,7 +29,7 @@ contract TokenVault is PERFeeReceiver { /** * @notice TokenVault constructor - Initializes a new token vault contract with given parameters - * + * * @param perMulticallAddress: address of PER contract * @param oracleAddress: address of the oracle contract */ @@ -41,7 +41,7 @@ contract TokenVault is PERFeeReceiver { /** * @notice getPrice function - retrieves price of a given token from the oracle - * + * * @param id: price feed ID of the token */ function _getPrice( @@ -61,16 +61,14 @@ contract TokenVault is PERFeeReceiver { * * @param vaultID: ID of the vault for which to calculate health */ - function getVaultHealth( - uint256 vaultID - ) public view returns (uint256){ + function getVaultHealth(uint256 vaultID) public view returns (uint256) { Vault memory vault = _vaults[vaultID]; return _getVaultHealth(vault); } /** * @notice _getVaultHealth function - calculates vault collateral/debt ratio - * + * * @param vault: vault struct containing vault parameters */ function _getVaultHealth( @@ -82,15 +80,16 @@ contract TokenVault is PERFeeReceiver { require(priceCollateral >= 0, "collateral price is negative"); require(priceDebt >= 0, "debt price is negative"); - uint256 valueCollateral = uint256(uint64(priceCollateral)) * vault.amountCollateral; + uint256 valueCollateral = uint256(uint64(priceCollateral)) * + vault.amountCollateral; uint256 valueDebt = uint256(uint64(priceDebt)) * vault.amountDebt; - return valueCollateral * 1_000_000_000_000_000_000 / valueDebt; + return (valueCollateral * 1_000_000_000_000_000_000) / valueDebt; } /** * @notice createVault function - creates a vault - * + * * @param tokenCollateral: address of the collateral token of the vault * @param tokenDebt: address of the debt token of the vault * @param amountCollateral: amount of collateral tokens in the vault @@ -110,24 +109,40 @@ contract TokenVault is PERFeeReceiver { bytes32 tokenIDCollateral, bytes32 tokenIDDebt ) public returns (uint256) { - Vault memory vault = Vault(tokenCollateral, tokenDebt, amountCollateral, amountDebt, minHealthRatio, minPermissionLessHealthRatio, tokenIDCollateral, tokenIDDebt); - require(minPermissionLessHealthRatio <= minHealthRatio, "minPermissionLessHealthRatio must be less than or equal to minHealthRatio"); + Vault memory vault = Vault( + tokenCollateral, + tokenDebt, + amountCollateral, + amountDebt, + minHealthRatio, + minPermissionLessHealthRatio, + tokenIDCollateral, + tokenIDDebt + ); + require( + minPermissionLessHealthRatio <= minHealthRatio, + "minPermissionLessHealthRatio must be less than or equal to minHealthRatio" + ); if (_getVaultHealth(vault) < vault.minHealthRatio) { revert UncollateralizedVaultCreation(); } - IERC20(vault.tokenCollateral).safeTransferFrom(msg.sender, address(this), vault.amountCollateral); + IERC20(vault.tokenCollateral).safeTransferFrom( + msg.sender, + address(this), + vault.amountCollateral + ); IERC20(vault.tokenDebt).safeTransfer(msg.sender, vault.amountDebt); _vaults[_nVaults] = vault; _nVaults += 1; - + return _nVaults; } /** * @notice updateVault function - updates a vault's collateral and debt amounts - * + * * @param vaultID: ID of the vault to be updated * @param deltaCollateral: delta change to collateral amount (+ means adding collateral tokens, - means removing collateral tokens) * @param deltaDebt: delta change to debt amount (+ means withdrawing debt tokens from protocol, - means resending debt tokens to protocol) @@ -142,19 +157,23 @@ contract TokenVault is PERFeeReceiver { uint256 qCollateral = stdMath.abs(deltaCollateral); uint256 qDebt = stdMath.abs(deltaDebt); - bool withdrawExcessiveCollateral = (deltaCollateral < 0) && (qCollateral > vault.amountCollateral); + bool withdrawExcessiveCollateral = (deltaCollateral < 0) && + (qCollateral > vault.amountCollateral); if (withdrawExcessiveCollateral) { revert InvalidVaultUpdate(); } - uint256 futureCollateral = (deltaCollateral >= 0) ? (vault.amountCollateral + qCollateral) : (vault.amountCollateral - qCollateral); - uint256 futureDebt = (deltaDebt >= 0) ? (vault.amountDebt + qDebt) : (vault.amountDebt - qDebt); - + uint256 futureCollateral = (deltaCollateral >= 0) + ? (vault.amountCollateral + qCollateral) + : (vault.amountCollateral - qCollateral); + uint256 futureDebt = (deltaDebt >= 0) + ? (vault.amountDebt + qDebt) + : (vault.amountDebt - qDebt); + vault.amountCollateral = futureCollateral; vault.amountDebt = futureDebt; - if (_getVaultHealth(vault) < vault.minHealthRatio) { revert InvalidVaultUpdate(); } @@ -162,7 +181,11 @@ contract TokenVault is PERFeeReceiver { // update collateral position if (deltaCollateral >= 0) { // sender adds more collateral to their vault - IERC20(vault.tokenCollateral).safeTransferFrom(msg.sender, address(this), qCollateral); + IERC20(vault.tokenCollateral).safeTransferFrom( + msg.sender, + address(this), + qCollateral + ); _vaults[vaultID].amountCollateral += qCollateral; } else { // sender takes back collateral from their vault @@ -177,14 +200,18 @@ contract TokenVault is PERFeeReceiver { _vaults[vaultID].amountDebt += qDebt; } else { // sender sends back debt tokens - IERC20(vault.tokenDebt).safeTransferFrom(msg.sender, address(this), qDebt); + IERC20(vault.tokenDebt).safeTransferFrom( + msg.sender, + address(this), + qDebt + ); _vaults[vaultID].amountDebt -= qDebt; } } /** * @notice getVault function - getter function to get a vault's parameters - * + * * @param vaultID: ID of the vault */ function getVault(uint256 vaultID) public view returns (Vault memory) { @@ -193,17 +220,17 @@ contract TokenVault is PERFeeReceiver { /** * @notice _updatePriceFeeds function - updates the specified price feeds with given data - * + * * @param updateData: data to update price feeds with */ function _updatePriceFeeds(bytes[] calldata updateData) internal { MockPyth oracle = MockPyth(payable(_oracle)); - oracle.updatePriceFeeds{ value: msg.value }(updateData); + oracle.updatePriceFeeds{value: msg.value}(updateData); } /** * @notice liquidate function - liquidates a vault - * + * * @param vaultID: ID of the vault to be liquidated */ function liquidate(uint256 vaultID) public { @@ -213,12 +240,25 @@ contract TokenVault is PERFeeReceiver { revert InvalidLiquidation(); } - if(vaultHealth>=vault.minPermissionLessHealthRatio && !PERMulticall(payable(perMulticall)).isPermissioned(address(this),abi.encode(vaultID))){ + if ( + vaultHealth >= vault.minPermissionLessHealthRatio && + !PERMulticall(payable(perMulticall)).isPermissioned( + address(this), + abi.encode(vaultID) + ) + ) { revert InvalidLiquidation(); } - IERC20(vault.tokenDebt).transferFrom(msg.sender, address(this), vault.amountDebt); - IERC20(vault.tokenCollateral).transfer(msg.sender, vault.amountCollateral); + IERC20(vault.tokenDebt).transferFrom( + msg.sender, + address(this), + vault.amountDebt + ); + IERC20(vault.tokenCollateral).transfer( + msg.sender, + vault.amountCollateral + ); _vaults[vaultID].amountCollateral = 0; _vaults[vaultID].amountDebt = 0; @@ -226,16 +266,21 @@ contract TokenVault is PERFeeReceiver { /** * @notice liquidateWithPriceUpdate function - liquidates a vault after updating the specified price feeds with given data - * + * * @param vaultID: ID of the vault to be liquidated * @param updateData: data to update price feeds with */ - function liquidateWithPriceUpdate(uint256 vaultID, bytes[] calldata updateData) external payable { + function liquidateWithPriceUpdate( + uint256 vaultID, + bytes[] calldata updateData + ) external payable { _updatePriceFeeds(updateData); liquidate(vaultID); } - function receiveAuctionProceedings(bytes calldata permissionKey) external payable { + function receiveAuctionProceedings( + bytes calldata permissionKey + ) external payable { emit VaultReceivedETH(msg.sender, msg.value, permissionKey); } diff --git a/per_multicall/src/WETH9.sol b/per_multicall/src/WETH9.sol index 40523b80..53f2a3c3 100644 --- a/per_multicall/src/WETH9.sol +++ b/per_multicall/src/WETH9.sol @@ -1,6 +1,6 @@ /** *Submitted for verification at Etherscan.io on 2017-12-12 -*/ + */ // Copyright (C) 2015, 2016, 2017 Dapphub @@ -20,25 +20,27 @@ pragma solidity ^0.8.0; contract WETH9 { - string public name = "Wrapped Ether"; - string public symbol = "WETH"; - uint8 public decimals = 18; + string public name = "Wrapped Ether"; + string public symbol = "WETH"; + uint8 public decimals = 18; - event Approval(address indexed src, address indexed guy, uint wad); - event Transfer(address indexed src, address indexed dst, uint wad); - event Deposit(address indexed dst, uint wad); - event Withdrawal(address indexed src, uint wad); + event Approval(address indexed src, address indexed guy, uint wad); + event Transfer(address indexed src, address indexed dst, uint wad); + event Deposit(address indexed dst, uint wad); + event Withdrawal(address indexed src, uint wad); - mapping (address => uint) public balanceOf; - mapping (address => mapping (address => uint)) public allowance; + mapping(address => uint) public balanceOf; + mapping(address => mapping(address => uint)) public allowance; - receive() external payable { + receive() external payable { deposit(); } + function deposit() public payable { balanceOf[msg.sender] += msg.value; emit Deposit(msg.sender, msg.value); } + function withdraw(uint wad) public { require(balanceOf[msg.sender] >= wad); balanceOf[msg.sender] -= wad; @@ -60,13 +62,15 @@ contract WETH9 { return transferFrom(msg.sender, dst, wad); } - function transferFrom(address src, address dst, uint wad) - public - returns (bool) - { + function transferFrom( + address src, + address dst, + uint wad + ) public returns (bool) { require(balanceOf[src] >= wad); - if (src != msg.sender) { //&& allowance[src][msg.sender] != uint(-1)) { + if (src != msg.sender) { + //&& allowance[src][msg.sender] != uint(-1)) { require(allowance[src][msg.sender] >= wad); allowance[src][msg.sender] -= wad; } @@ -78,4 +82,4 @@ contract WETH9 { return true; } -} \ No newline at end of file +} diff --git a/per_multicall/test/PERVault.t.sol b/per_multicall/test/PERVault.t.sol index 26e01eb8..b00500f7 100644 --- a/per_multicall/test/PERVault.t.sol +++ b/per_multicall/test/PERVault.t.sol @@ -40,12 +40,16 @@ contract PERVaultTest is Test, Signatures { bytes32 _idToken1; bytes32 _idToken2; - address _perOperatorAddress; uint256 _perOperatorSk; // address public immutable _perOperatorAddress = address(88); - address _searcherAOwnerAddress; uint256 _searcherAOwnerSk; - address _searcherBOwnerAddress; uint256 _searcherBOwnerSk; - address _tokenVaultDeployer; uint256 _tokenVaultDeployerSk; - - uint256 public healthPrecision = 10**16; + address _perOperatorAddress; + uint256 _perOperatorSk; // address public immutable _perOperatorAddress = address(88); + address _searcherAOwnerAddress; + uint256 _searcherAOwnerSk; + address _searcherBOwnerAddress; + uint256 _searcherBOwnerSk; + address _tokenVaultDeployer; + uint256 _tokenVaultDeployerSk; + + uint256 public healthPrecision = 10 ** 16; address _depositor = address(44); @@ -64,20 +68,23 @@ contract PERVaultTest is Test, Signatures { uint256 _defaultFeeSplitProtocol; uint256 _feeSplitTokenVault; - uint256 _feeSplitPrecisionTokenVault = 10**18; + uint256 _feeSplitPrecisionTokenVault = 10 ** 18; uint256 _signaturePerVersionNumber = 0; - + function setUp() public { // make PER operator wallet (_perOperatorAddress, _perOperatorSk) = makeAddrAndKey("perOperator"); console.log("pk per operator", _perOperatorSk); - _defaultFeeSplitProtocol = 50 * 10**16; + _defaultFeeSplitProtocol = 50 * 10 ** 16; // instantiate multicall contract with PER operator as sender/origin vm.prank(_perOperatorAddress, _perOperatorAddress); - multicall = new PERMulticall(_perOperatorAddress, _defaultFeeSplitProtocol); + multicall = new PERMulticall( + _perOperatorAddress, + _defaultFeeSplitProtocol + ); // instantiate weth contract vm.prank(_perOperatorAddress, _perOperatorAddress); @@ -85,15 +92,24 @@ contract PERVaultTest is Test, Signatures { // instantiate liquidation adapter contract vm.prank(_perOperatorAddress, _perOperatorAddress); - liquidationAdapter = new LiquidationAdapter(address(multicall), address(weth)); + liquidationAdapter = new LiquidationAdapter( + address(multicall), + address(weth) + ); // make searcherA and searcherB wallets - (_searcherAOwnerAddress, _searcherAOwnerSk) = makeAddrAndKey("searcherA"); - (_searcherBOwnerAddress, _searcherBOwnerSk) = makeAddrAndKey("searcherB"); + (_searcherAOwnerAddress, _searcherAOwnerSk) = makeAddrAndKey( + "searcherA" + ); + (_searcherBOwnerAddress, _searcherBOwnerSk) = makeAddrAndKey( + "searcherB" + ); console.log("pk searcherA", _searcherAOwnerSk); console.log("pk searcherB", _searcherBOwnerSk); - (_tokenVaultDeployer, _tokenVaultDeployerSk) = makeAddrAndKey("tokenVaultDeployer"); + (_tokenVaultDeployer, _tokenVaultDeployerSk) = makeAddrAndKey( + "tokenVaultDeployer" + ); console.log("pk token vault deployer", _tokenVaultDeployerSk); // instantiate mock pyth contract @@ -164,9 +180,27 @@ contract PERVaultTest is Test, Signatures { uint64 prevPublishTime = 0; vm.warp(publishTime); - bytes memory token1UpdateData = mockPyth.createPriceFeedUpdateData(_idToken1, token1Price, token1Conf, token1Expo, token1Price, token1Conf, publishTime, prevPublishTime); - bytes memory token2UpdateData = mockPyth.createPriceFeedUpdateData(_idToken2, token2Price, token2Conf, token2Expo, token2Price, token2Conf, publishTime, prevPublishTime); - + bytes memory token1UpdateData = mockPyth.createPriceFeedUpdateData( + _idToken1, + token1Price, + token1Conf, + token1Expo, + token1Price, + token1Conf, + publishTime, + prevPublishTime + ); + bytes memory token2UpdateData = mockPyth.createPriceFeedUpdateData( + _idToken2, + token2Price, + token2Conf, + token2Expo, + token2Price, + token2Conf, + publishTime, + prevPublishTime + ); + bytes[] memory updateData = new bytes[](2); updateData[0] = token1UpdateData; @@ -180,7 +214,16 @@ contract PERVaultTest is Test, Signatures { vm.prank(_depositor, _depositor); token1.approve(address(tokenVault), _q1Vault0); vm.prank(_depositor, _depositor); - tokenVault.createVault(address(token1), address(token2), _q1Vault0, _q2Vault0, 110 * healthPrecision, 100 * healthPrecision, _idToken1, _idToken2); + tokenVault.createVault( + address(token1), + address(token2), + _q1Vault0, + _q2Vault0, + 110 * healthPrecision, + 100 * healthPrecision, + _idToken1, + _idToken2 + ); _q1Depositor -= _q1Vault0; _q2Depositor += _q2Vault0; @@ -190,7 +233,16 @@ contract PERVaultTest is Test, Signatures { vm.prank(_depositor, _depositor); token1.approve(address(tokenVault), _q1Vault1); vm.prank(_depositor, _depositor); - tokenVault.createVault(address(token1), address(token2), _q1Vault1, _q2Vault1, 110 * healthPrecision, 100 * healthPrecision, _idToken1, _idToken2); + tokenVault.createVault( + address(token1), + address(token2), + _q1Vault1, + _q2Vault1, + 110 * healthPrecision, + 100 * healthPrecision, + _idToken1, + _idToken2 + ); _q1Depositor -= _q1Vault0; _q2Depositor += _q2Vault0; @@ -199,19 +251,34 @@ contract PERVaultTest is Test, Signatures { vm.deal(address(searcherB), 1 ether); // fast forward to enable price updates in the below tests - vm.warp(publishTime+100); + vm.warp(publishTime + 100); } function testLiquidate() public { // test slow path liquidation // raise price of token 2 to make vault 0 undercollateralized, delayed oracle feed - bytes memory token2UpdateData = mockPyth.createPriceFeedUpdateData(_idToken2, 200, 1, 0, 200, 1, uint64(block.timestamp), 0); + bytes memory token2UpdateData = mockPyth.createPriceFeedUpdateData( + _idToken2, + 200, + 1, + 0, + 200, + 1, + uint64(block.timestamp), + 0 + ); bytes memory signatureSearcher; uint256 validUntil = 1_000_000_000_000; vm.prank(_searcherAOwnerAddress, _searcherAOwnerAddress); - searcherA.doLiquidate(0, 0, validUntil, token2UpdateData, signatureSearcher); + searcherA.doLiquidate( + 0, + 0, + validUntil, + token2UpdateData, + signatureSearcher + ); assertEq(token1.balanceOf(address(searcherA)), _q1A + _q1Vault0); assertEq(token2.balanceOf(address(searcherA)), _q2A - _q2Vault0); } @@ -226,10 +293,18 @@ contract PERVaultTest is Test, Signatures { uint256 vaultNumber = 0; // get permission key - bytes memory permission = abi.encode(address(tokenVault), abi.encodePacked(vaultNumber)); + bytes memory permission = abi.encode( + address(tokenVault), + abi.encodePacked(vaultNumber) + ); // create searcher signature - bytes memory signatureSearcher = createSearcherSignature(vaultNumber, bid, validUntil, _searcherAOwnerSk); + bytes memory signatureSearcher = createSearcherSignature( + vaultNumber, + bid, + validUntil, + _searcherAOwnerSk + ); address[] memory contracts = new address[](1); bytes[] memory data = new bytes[](1); @@ -237,20 +312,41 @@ contract PERVaultTest is Test, Signatures { address[] memory protocols = new address[](1); // raise price of token 2 to make vault 0 undercollateralized, fast oracle feed - bytes memory token2UpdateData = mockPyth.createPriceFeedUpdateData(_idToken2, 200, 1, 0, 200, 1, uint64(block.timestamp), 0); + bytes memory token2UpdateData = mockPyth.createPriceFeedUpdateData( + _idToken2, + 200, + 1, + 0, + 200, + 1, + uint64(block.timestamp), + 0 + ); contracts[0] = address(searcherA); - data[0] = abi.encodeWithSignature("doLiquidate(uint256,uint256,uint256,bytes,bytes)", 0, bid, validUntil, token2UpdateData, signatureSearcher); + data[0] = abi.encodeWithSignature( + "doLiquidate(uint256,uint256,uint256,bytes,bytes)", + 0, + bid, + validUntil, + token2UpdateData, + signatureSearcher + ); bids[0] = bid; protocols[0] = address(tokenVault); uint256 balanceProtocolPre = address(tokenVault).balance; vm.prank(_perOperatorAddress, _perOperatorAddress); - MulticallStatus[] memory multicallStatuses = multicall.multicall(permission, contracts, data, bids); + MulticallStatus[] memory multicallStatuses = multicall.multicall( + permission, + contracts, + data, + bids + ); uint256 balanceProtocolPost = address(tokenVault).balance; - + assertEq(token1.balanceOf(address(searcherA)), _q1A + _q1Vault0); assertEq(token2.balanceOf(address(searcherA)), _q2A - _q2Vault0); @@ -261,7 +357,10 @@ contract PERVaultTest is Test, Signatures { console.log("Revert reason"); console.log(multicallStatuses[0].multicallRevertReason); - assertEq(balanceProtocolPost - balanceProtocolPre, bid * _feeSplitTokenVault / _feeSplitPrecisionTokenVault); + assertEq( + balanceProtocolPost - balanceProtocolPre, + (bid * _feeSplitTokenVault) / _feeSplitPrecisionTokenVault + ); } // function testLiquidateFastWrongContractAuction() public { @@ -283,7 +382,7 @@ contract PERVaultTest is Test, Signatures { // // raise price of token 2 to make vault 0 undercollateralized, fast oracle feed // bytes memory token2UpdateData = mockPyth.createPriceFeedUpdateData(_idToken2, 200, 1, 0, 200, 1, uint64(block.timestamp), 0); - + // contracts[0] = address(searcherA); // data[0] = abi.encodeWithSignature("doLiquidatePER(bytes,uint256,bytes,uint256,bytes)", signaturePer, 0, signatureSearcher, bid, token2UpdateData); // bids[0] = bid; @@ -317,7 +416,7 @@ contract PERVaultTest is Test, Signatures { // // raise price of token 2 to make vault 0 undercollateralized, fast oracle feed // bytes memory token2UpdateData = mockPyth.createPriceFeedUpdateData(_idToken2, 200, 1, 0, 200, 1, uint64(block.timestamp), 0); - + // contracts[0] = address(searcherA); // data[0] = abi.encodeWithSignature("fakeFunctionSignature(bytes,uint256,bytes,uint256,bytes)", signaturePer, 0, signatureSearcher, bid, token2UpdateData); // bids[0] = bid; @@ -360,7 +459,7 @@ contract PERVaultTest is Test, Signatures { // bytes[] memory data = new bytes[](2); // uint256[] memory bids = new uint256[](2); // address[] memory protocols = new address[](2); - + // contracts[0] = address(searcherA); // contracts[1] = address(searcherB); // data[0] = abi.encodeWithSignature("doLiquidatePER(bytes,uint256,bytes,uint256,bytes)", signaturePer, 0, signatureSearcher0, bid0, token2UpdateData0); @@ -376,7 +475,7 @@ contract PERVaultTest is Test, Signatures { // multicall.multicall(contracts, data, bids, protocols); // uint256 balanceProtocolPost = address(tokenVault).balance; - + // uint256 token1AAfter = token1.balanceOf(address(searcherA)); // uint256 token2AAfter = token2.balanceOf(address(searcherA)); // assertEq(token1AAfter, _q1A + _q1Vault0); @@ -414,7 +513,7 @@ contract PERVaultTest is Test, Signatures { // bytes[] memory data = new bytes[](2); // uint256[] memory bids = new uint256[](2); // address[] memory protocols = new address[](2); - + // contracts[0] = address(searcherA); // contracts[1] = address(searcherB); // data[0] = abi.encodeWithSignature("doLiquidatePER(bytes,uint256,bytes,uint256,bytes)", signaturePer, 0, signatureSearcher0, bid0, token2UpdateData0); @@ -439,7 +538,7 @@ contract PERVaultTest is Test, Signatures { // assertEq(token1.balanceOf(address(searcherA)), _q1A + _q1Vault0); // assertEq(token2.balanceOf(address(searcherA)), _q2A - _q2Vault0); - + // assertEq(token1.balanceOf(address(searcherB)), _q1B); // assertEq(token2.balanceOf(address(searcherB)), _q2B); @@ -477,7 +576,7 @@ contract PERVaultTest is Test, Signatures { // bytes[] memory data = new bytes[](2); // uint256[] memory bids = new uint256[](2); // address[] memory protocols = new address[](2); - + // contracts[0] = address(searcherA); // contracts[1] = address(searcherB); // data[0] = abi.encodeWithSignature("doLiquidatePER(bytes,uint256,bytes,uint256,bytes)", signaturePer, 0, signatureSearcher0, bid0, token2UpdateData0); @@ -489,7 +588,7 @@ contract PERVaultTest is Test, Signatures { // vm.prank(_perOperatorAddress, _perOperatorAddress); // (, bytes[] memory externalResults, string[] memory multicallRevertReasons) = multicall.multicall(contracts, data, bids, protocols); - + // uint256[] memory tokensAfter = new uint256[](4); // tokensAfter[0] = token1.balanceOf(address(searcherA)); // tokensAfter[1] = token2.balanceOf(address(searcherA)); @@ -525,11 +624,11 @@ contract PERVaultTest is Test, Signatures { // // read in bundle bids // string memory keyBids = "PERBUNDLE_bids"; // uint256[] memory bids = vm.envUint(keyBids, delimiter); - + // // read in bundle protocols // string memory keyProtocols = "PERBUNDLE_protocols"; // address[] memory protocols = vm.envAddress(keyProtocols, delimiter); - + // // read in block number // string memory keyBlockNumber = "PERBUNDLE_blockNumber"; // uint256 blockNumber = vm.envUint(keyBlockNumber); @@ -549,7 +648,7 @@ contract PERVaultTest is Test, Signatures { // // now run multicall on the payload // vm.prank(_perOperatorAddress, _perOperatorAddress); // (bool[] memory externalSuccess, bytes[] memory externalResults, string[] memory multicallRevertReasons) = multicall.multicall(contracts, data, bids, protocols); - + // console.log("vault token 1 balance after:", token1.balanceOf(address(tokenVault))); // console.log("vault token 2 balance after:", token2.balanceOf(address(tokenVault))); diff --git a/per_multicall/test/helpers/Signatures.sol b/per_multicall/test/helpers/Signatures.sol index 4a8eed6a..36c6d2b4 100644 --- a/per_multicall/test/helpers/Signatures.sol +++ b/per_multicall/test/helpers/Signatures.sol @@ -7,16 +7,18 @@ import {Test} from "forge-std/Test.sol"; import "openzeppelin-contracts/contracts/utils/Strings.sol"; contract Signatures is Test, SigVerify { - function createSearcherSignature( - uint256 dataNumber, - uint256 bid, + uint256 dataNumber, + uint256 bid, uint256 blockNumber, uint256 searcherSk ) public pure returns (bytes memory) { bytes memory dataSearcher = abi.encodePacked(dataNumber, bid); bytes32 calldataHash = getCalldataDigest(dataSearcher, blockNumber); - (uint8 vSearcher, bytes32 rSearcher, bytes32 sSearcher) = vm.sign(searcherSk, calldataHash); + (uint8 vSearcher, bytes32 rSearcher, bytes32 sSearcher) = vm.sign( + searcherSk, + calldataHash + ); return abi.encodePacked(rSearcher, sSearcher, vSearcher); } @@ -26,10 +28,18 @@ contract Signatures is Test, SigVerify { uint256 blockNumber, uint256 perOperatorSk ) public pure returns (bytes memory) { - string memory messagePer = Strings.toHexString(uint160(protocolAddress), 20); + string memory messagePer = Strings.toHexString( + uint160(protocolAddress), + 20 + ); bytes32 messageDigestPer = getMessageDigest(messagePer, blockNumber); - bytes32 signedMessageDigestPer = getPERSignedMessageDigest(messageDigestPer); - (uint8 vPer, bytes32 rPer, bytes32 sPer) = vm.sign(perOperatorSk, signedMessageDigestPer); + bytes32 signedMessageDigestPer = getPERSignedMessageDigest( + messageDigestPer + ); + (uint8 vPer, bytes32 rPer, bytes32 sPer) = vm.sign( + perOperatorSk, + signedMessageDigestPer + ); return abi.encodePacked(signaturePerVersionNumber, rPer, sPer, vPer); } -} \ No newline at end of file +} diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 00000000..793e0a9f --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,19 @@ +edition = "2021" + +# Merge all imports into a clean vertical list of module imports. +imports_granularity = "One" +group_imports = "One" +imports_layout = "Vertical" + +# Better grep-ability. +empty_item_single_line = false + +# Consistent pipe layout. +match_arm_leading_pipes = "Preserve" + +# Align Fields +enum_discrim_align_threshold = 80 +struct_field_align_threshold = 80 + +# Allow up to two blank lines for visual grouping. +blank_lines_upper_bound = 2