From 6da5e4c970d7abbd80ad1880b625ab9029b193c4 Mon Sep 17 00:00:00 2001 From: Anirudh Suresh Date: Tue, 13 Feb 2024 14:09:21 -0500 Subject: [PATCH 1/2] edits_feb5 (#15) * precom changes * testing changes to accomodate updates * package changes * precom fix * clearer error raising on smart contracts * price updates in mock pyth format * address comments * fix poetry version mismatch * changes to clones and write locks * address comments --------- Co-authored-by: Anirudh Suresh --- auction-server/src/api/liquidation.rs | 71 ++- auction-server/src/liquidation_adapter.rs | 41 +- auction-server/src/state.rs | 10 +- package-lock.json | 583 ++++++++++++++++++++++ per_multicall/README.md | 12 +- per_multicall/script/Vault.s.sol | 33 +- per_multicall/src/Errors.sol | 9 + per_multicall/src/LiquidationAdapter.sol | 7 +- per_multicall/src/PERMulticall.sol | 33 +- per_multicall/src/TokenVault.sol | 15 +- per_multicall/src/TokenVaultErrors.sol | 10 + per_sdk/protocols/token_vault_monitor.py | 59 ++- per_sdk/searcher/simple_searcher.py | 2 + per_sdk/utils/pyth_prices.py | 9 + 14 files changed, 810 insertions(+), 84 deletions(-) diff --git a/auction-server/src/api/liquidation.rs b/auction-server/src/api/liquidation.rs index ca77f6fa..f8945e29 100644 --- a/auction-server/src/api/liquidation.rs +++ b/auction-server/src/api/liquidation.rs @@ -69,6 +69,15 @@ pub struct OpportunityParamsWithId { params: OpportunityParams, } +impl Into for LiquidationOpportunity { + fn into(self) -> OpportunityParamsWithId { + OpportunityParamsWithId { + opportunity_id: self.id, + params: self.params, + } + } +} + /// Submit a liquidation opportunity ready to be executed. /// /// The opportunity will be verified by the server. If the opportunity is valid, it will be stored in the database @@ -105,12 +114,29 @@ pub async fn post_opportunity( .await .map_err(|e| RestError::InvalidOpportunity(e.to_string()))?; - store - .liquidation_store - .opportunities - .write() - .await - .insert(params.permission_key.clone(), opportunity); + + let mut write_lock = store.liquidation_store.opportunities.write().await; + + if let Some(opportunities_existing) = write_lock.get_mut(¶ms.permission_key) { + // check if same opportunity exists in the vector + for opportunity_existing in opportunities_existing.clone() { + if opportunity_existing == opportunity { + return Err(RestError::BadParameters( + "Duplicate opportunity submission".to_string(), + )); + } + } + + opportunities_existing.push(opportunity); + } else { + write_lock.insert(params.permission_key.clone(), vec![opportunity]); + } + + tracing::debug!("number of permission keys: {}", write_lock.len()); + tracing::debug!( + "number of opportunities for key: {}", + write_lock[¶ms.permission_key].len() + ); Ok(OpportunityParamsWithId { opportunity_id: id, @@ -144,11 +170,14 @@ pub async fn get_opportunities( .await .values() .cloned() - .map(|opportunity| OpportunityParamsWithId { - opportunity_id: opportunity.id, - params: opportunity.params, + .map(|opportunities_key| { + opportunities_key + .last() + .expect("A permission key vector should have at least one opportunity") + .clone() + .into() }) - .filter(|params_with_id| { + .filter(|params_with_id: &OpportunityParamsWithId| { let params = match ¶ms_with_id.params { OpportunityParams::V1(params) => params, }; @@ -199,7 +228,7 @@ pub async fn post_bid( Path(opportunity_id): Path, Json(opportunity_bid): Json, ) -> Result, RestError> { - let opportunity = store + let opportunities = store .liquidation_store .opportunities .read() @@ -208,12 +237,10 @@ pub async fn post_bid( .ok_or(RestError::OpportunityNotFound)? .clone(); - - if opportunity.id != opportunity_id { - return Err(RestError::BadParameters( - "Invalid opportunity_id".to_string(), - )); - } + let opportunity = opportunities + .iter() + .find(|o| o.id == opportunity_id) + .ok_or(RestError::OpportunityNotFound)?; // TODO: move this logic to searcher side if opportunity.bidders.contains(&opportunity_bid.liquidator) { @@ -253,9 +280,13 @@ pub async fn post_bid( { Ok(_) => { let mut write_guard = store.liquidation_store.opportunities.write().await; - let liquidation = write_guard.get_mut(&opportunity_bid.permission_key); - if let Some(liquidation) = liquidation { - liquidation.bidders.insert(opportunity_bid.liquidator); + let opportunities = write_guard.get_mut(&opportunity_bid.permission_key); + if let Some(opportunities) = opportunities { + let opportunity = opportunities + .iter_mut() + .find(|o| o.id == opportunity_id) + .ok_or(RestError::OpportunityNotFound)?; + opportunity.bidders.insert(opportunity_bid.liquidator); } Ok(BidResult { status: "OK".to_string(), diff --git a/auction-server/src/liquidation_adapter.rs b/auction-server/src/liquidation_adapter.rs index 886f5662..71a9d6c4 100644 --- a/auction-server/src/liquidation_adapter.rs +++ b/auction-server/src/liquidation_adapter.rs @@ -333,25 +333,42 @@ async fn verify_with_store(opportunity: LiquidationOpportunity, store: &Store) - /// # Arguments /// /// * `store`: server store -pub async fn run_verification_loop(store: Arc) { +pub async fn run_verification_loop(store: Arc) -> Result<()> { tracing::info!("Starting opportunity verifier..."); while !SHOULD_EXIT.load(Ordering::Acquire) { let all_opportunities = store.liquidation_store.opportunities.read().await.clone(); - for (permission_key, opportunity) in all_opportunities.iter() { - match verify_with_store(opportunity.clone(), &store).await { - Ok(_) => {} - Err(e) => { - store - .liquidation_store - .opportunities - .write() - .await - .remove(permission_key); - tracing::info!("Removed Opportunity with failed verification: {}", e); + for (permission_key, opportunities) in all_opportunities.iter() { + // check each of the opportunities for this permission key for validity + let mut opps_to_remove = vec![]; + for opportunity in opportunities.iter() { + match verify_with_store(opportunity.clone(), &store).await { + Ok(_) => {} + Err(e) => { + opps_to_remove.push(opportunity.id); + tracing::info!( + "Removing Opportunity {} with failed verification: {}", + opportunity.id, + e + ); + } } } + + // set write lock to remove all these opportunities + let mut write_lock = store.liquidation_store.opportunities.write().await; + + if let Some(opportunities) = write_lock.get_mut(permission_key) { + opportunities.retain(|x| !opps_to_remove.contains(&x.id)); + if opportunities.is_empty() { + write_lock.remove(permission_key); + } + } + + // release the write lock + drop(write_lock); } tokio::time::sleep(Duration::from_secs(5)).await; // this should be replaced by a subscription to the chain and trigger on new blocks } tracing::info!("Shutting down opportunity verifier..."); + Ok(()) } diff --git a/auction-server/src/state.rs b/auction-server/src/state.rs index 6b44f540..90ca2a20 100644 --- a/auction-server/src/state.rs +++ b/auction-server/src/state.rs @@ -40,7 +40,7 @@ pub struct SimulatedBid { pub type UnixTimestamp = i64; -#[derive(Serialize, Deserialize, ToSchema, Clone)] +#[derive(Serialize, Deserialize, ToSchema, Clone, PartialEq)] pub struct TokenQty { /// Token contract address #[schema(example = "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",value_type=String)] @@ -55,7 +55,7 @@ pub struct TokenQty { /// If a searcher signs the opportunity and have approved enough tokens to liquidation adapter, /// by calling this contract with the given calldata and structures, they will receive the tokens specified /// in the receipt_tokens field, and will send the tokens specified in the repay_tokens field. -#[derive(Serialize, Deserialize, ToSchema, Clone)] +#[derive(Serialize, Deserialize, ToSchema, Clone, PartialEq)] pub struct OpportunityParamsV1 { /// The permission key required for succesful execution of the liquidation. #[schema(example = "0xdeadbeefcafe", value_type=String)] @@ -78,14 +78,14 @@ pub struct OpportunityParamsV1 { pub receipt_tokens: Vec, } -#[derive(Serialize, Deserialize, ToSchema, Clone)] +#[derive(Serialize, Deserialize, ToSchema, Clone, PartialEq)] #[serde(tag = "version")] pub enum OpportunityParams { #[serde(rename = "v1")] V1(OpportunityParamsV1), } -#[derive(Clone)] +#[derive(Clone, PartialEq)] pub struct LiquidationOpportunity { pub id: Uuid, pub creation_time: UnixTimestamp, @@ -112,7 +112,7 @@ pub struct ChainStore { #[derive(Default)] pub struct LiquidationStore { - pub opportunities: RwLock>, + pub opportunities: RwLock>>, } pub struct Store { diff --git a/package-lock.json b/package-lock.json index ff95bfbb..4df6eae2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,9 @@ "requires": true, "packages": { "": { + "dependencies": { + "ethereum-private-key-to-public-key": "^0.0.5" + }, "devDependencies": { "prettier": "^3.1.1", "prettier-plugin-solidity": "^1.3.1" @@ -15,6 +18,269 @@ "integrity": "sha512-Nko8R0/kUo391jsEHHxrGM07QFdnPGvlmox4rmH0kNiNAashItAilhy4Mv4pK5gQmW5f4sXAF58fwJbmlkGcVw==", "dev": true }, + "node_modules/array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/bn.js": { + "version": "4.12.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", + "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" + }, + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==" + }, + "node_modules/camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha512-FxAv7HpHrXbh3aPo4o2qxHay2lkLY3x5Mw3KeE4KQE8ysVfziWeRZDwcjauvwBSGEC/nXUPzZy8zeh4HokqOnw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/camelcase-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-4.2.0.tgz", + "integrity": "sha512-Ej37YKYbFUI8QiYlvj9YHb6/Z60dZyPJW0Cs8sFilMbd2lP0bw3ylAq9yJkK4lcTA2dID5fG8LjmJYbO7kWb7Q==", + "dependencies": { + "camelcase": "^4.1.0", + "map-obj": "^2.0.0", + "quick-lru": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/currently-unhandled": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", + "integrity": "sha512-/fITjgjGU50vjQ4FH6eUoYu+iUoUKIXws2hL15JJpIR+BbTxaXQsMuuyjtNh2WqsSBS5nsaZHFsFecyw5CCAng==", + "dependencies": { + "array-find-index": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decamelize-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz", + "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==", + "dependencies": { + "decamelize": "^1.1.0", + "map-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decamelize-keys/node_modules/map-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", + "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/elliptic": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/ethereum-private-key-to-public-key": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/ethereum-private-key-to-public-key/-/ethereum-private-key-to-public-key-0.0.5.tgz", + "integrity": "sha512-oSdjEi3BzhG7M5D6ZKZB5zWbINRbJ5lsaROJz+RePRAn8aylm6j93uSqNvzge+kIsslbuu2tXeXgHjEZ/tweRg==", + "dependencies": { + "meow": "^5.0.0", + "secp256k1": "^4.0.2" + }, + "bin": { + "ethereum_private_key_to_public_key": "bin/ethereum_private_key_to_public_key", + "ethereum-private-key-to-public-key": "bin/ethereum_private_key_to_public_key" + } + }, + "node_modules/find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", + "dependencies": { + "locate-path": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==" + }, + "node_modules/indent-string": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", + "integrity": "sha512-BYqTHXTGUIvg7t1r4sJNKcbDZkL92nkXA8YtRpbjFHRHGDL/NtUeiBJMeE60kIFN/Mg8ESaWQvftaYMGJzQZCQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" + }, + "node_modules/load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==", + "dependencies": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", + "dependencies": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/loud-rejection": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", + "integrity": "sha512-RPNliZOFkqFumDhvYqOaNY4Uz9oJM2K9tC6JWsJJsNdhuONW4LQHRBpb0qf4pJApVffI5N39SwzWZJuEhfd7eQ==", + "dependencies": { + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -27,6 +293,163 @@ "node": ">=10" } }, + "node_modules/map-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz", + "integrity": "sha512-TzQSV2DiMYgoF5RycneKVUzIa9bQsj/B3tTgsE3dOGqlzHnGIDaC7XBE7grnA+8kZPnfqSGFe95VHc2oc0VFUQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/meow": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-5.0.0.tgz", + "integrity": "sha512-CbTqYU17ABaLefO8vCU153ZZlprKYWDljcndKKDCFcYQITzWCXZAVk4QMFZPgvzrnUQ3uItnIE/LoUOwrT15Ig==", + "dependencies": { + "camelcase-keys": "^4.0.0", + "decamelize-keys": "^1.0.0", + "loud-rejection": "^1.0.0", + "minimist-options": "^3.0.1", + "normalize-package-data": "^2.3.4", + "read-pkg-up": "^3.0.0", + "redent": "^2.0.0", + "trim-newlines": "^2.0.0", + "yargs-parser": "^10.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==" + }, + "node_modules/minimist-options": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-3.0.2.tgz", + "integrity": "sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ==", + "dependencies": { + "arrify": "^1.0.1", + "is-plain-obj": "^1.1.0" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/node-addon-api": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", + "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==" + }, + "node_modules/node-gyp-build": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.0.tgz", + "integrity": "sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og==", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dependencies": { + "p-try": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "dependencies": { + "p-limit": "^1.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", + "engines": { + "node": ">=4" + } + }, + "node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dependencies": { + "pify": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "engines": { + "node": ">=4" + } + }, "node_modules/prettier": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.1.tgz", @@ -59,6 +482,81 @@ "prettier": ">=2.3.0" } }, + "node_modules/quick-lru": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz", + "integrity": "sha512-tRS7sTgyxMXtLum8L65daJnHUhfDUgboRdcWW2bR9vBfrj2+O5HSMbQOJfJJjIVSPFqbBCF37FpwWXGitDc5tA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==", + "dependencies": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/read-pkg-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", + "integrity": "sha512-YFzFrVvpC6frF1sz8psoHDBGF7fLPc+llq/8NB43oagqWkx8ar5zYtsTORtOjw9W2RHLpWP+zTWwBvf1bCmcSw==", + "dependencies": { + "find-up": "^2.0.0", + "read-pkg": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/redent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", + "integrity": "sha512-XNwrTx77JQCEMXTeb8movBKuK75MgH0RZkujNuDKCezemx/voapl9i2gCSi8WWm8+ox5ycJi1gxF22fR7c0Ciw==", + "dependencies": { + "indent-string": "^3.0.0", + "strip-indent": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/secp256k1": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-4.0.3.tgz", + "integrity": "sha512-NLZVf+ROMxwtEj3Xa562qgv2BK5e2WNmXPiOdVIPLgs6lyTzMvBq0aWTYMI5XCP9jZMVKOcqZLw/Wc4vDkuxhA==", + "hasInstallScript": true, + "dependencies": { + "elliptic": "^6.5.4", + "node-addon-api": "^2.0.0", + "node-gyp-build": "^4.2.0" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", @@ -74,17 +572,102 @@ "node": ">=10" } }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, "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/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.4.0.tgz", + "integrity": "sha512-hcjppoJ68fhxA/cjbN4T8N6uCUejN8yFw69ttpqtBeCbF3u13n7mb31NB9jKwGTTWWnt9IbRA/mf1FprYS8wfw==" + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.16", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.16.tgz", + "integrity": "sha512-eWN+LnM3GR6gPu35WxNgbGl8rmY1AEmoMDvL/QD6zYmPWgywxWqJWNdLGT+ke8dKNWrcYgYjPpG5gbTfghP8rw==" + }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-indent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", + "integrity": "sha512-RsSNPLpq6YUL7QYy44RnPVTn/lcVZtb48Uof3X5JLbF4zD/Gs7ZFDv2HWol+leoQN2mT86LAzSshGfkTlSOpsA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/trim-newlines": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz", + "integrity": "sha512-MTBWv3jhVjTU7XR3IQHllbiJs8sc75a80OEhB6or/q7pLTWgQ0bMGQXXYQSrSuXe6WiKWDZ5txXY5P59a/coVA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, "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 + }, + "node_modules/yargs-parser": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", + "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", + "dependencies": { + "camelcase": "^4.1.0" + } } } } diff --git a/per_multicall/README.md b/per_multicall/README.md index 45e41aa7..570c23ba 100644 --- a/per_multicall/README.md +++ b/per_multicall/README.md @@ -26,34 +26,36 @@ $ forge test -vvv --via-ir You can also run a local validator via `anvil --gas-limit 500000000000000000 --block-time 2`, changing the values for the gas limit and block time as desired. Note that if you omit the `--block-time` flag, the local network will create a new block for each transaction (similar to how Optimism created L2 blocks pre-Bedrock). Running `auction_offchain.py` will spit out the final call to `forge script` you should run to send the transaction to the localnet. -To run the script runs in `Vault.s.sol`, you should startup the local validator and create a `.env` file with the `PRIVATE_KEY` env variable which is used for submitting the transactions. Then, run the necessary setup commands: +To run the script runs in `Vault.s.sol`, you should startup the local validator and create a `.env` file with the `PRIVATE_KEY` env variable which is used for submitting the transactions. For localnet, the private key saved should correspond to an address that has a bunch of ETH seeded by Forge, essentially one of the mnemonic wallets when you start up anvil. 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 +$ forge script script/Vault.s.sol --via-ir --fork-url http://localhost:8545 --private-key 0xf46ea803192f16ef1c4f1d5fb0d6060535dbd571ea1afc7db6816f28961ba78a -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 -vvv --sig 'setOraclePrice(int64,int64,uint64)' 110 110 190 --broadcast +$ forge script script/Vault.s.sol --via-ir --fork-url http://localhost:8545 --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 +$ forge script script/Vault.s.sol --via-ir --fork-url http://localhost:8545 --private-key 0xf46ea803192f16ef1c4f1d5fb0d6060535dbd571ea1afc7db6816f28961ba78a -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 +$ forge script script/Vault.s.sol --via-ir --fork-url http://localhost:8545 --private-key 0xf46ea803192f16ef1c4f1d5fb0d6060535dbd571ea1afc7db6816f28961ba78a -vvv --sig 'setOraclePrice(int64,int64,uint64)' 110 200 200 --broadcast ``` 5. Submit the PER bundle. Run the command spit out by the auction script. Because of the call to `vm.roll`, this essentially does a simulate and can be run repeatedly from this state. +Note that the `--private-key` flag is necessary in order to run some of the commands above; this is because Forge requires specification of a default sender wallet from which the transactions are sent. + In order to enable forge to write to the filesystem (which is needed in order to save some of the variables in the steps above), please navigate to `foundry.toml` and add the following line if it does not already exist: ``` diff --git a/per_multicall/script/Vault.s.sol b/per_multicall/script/Vault.s.sol index 74e541e5..ce224a0a 100644 --- a/per_multicall/script/Vault.s.sol +++ b/per_multicall/script/Vault.s.sol @@ -68,7 +68,7 @@ contract VaultScript is Script { } function deployMockPyth() public returns (address) { - MockPyth mockPyth = new MockPyth(1_000_000, 0); + MockPyth mockPyth = new MockPyth(1_000_000_000_000, 0); console.log("deployed mock pyth contract at", address(mockPyth)); return address(mockPyth); } @@ -235,8 +235,21 @@ contract VaultScript is Script { // instantiate ERC-20 tokens vm.startBroadcast(sksScript[3]); console.log("balance of pk perOperator", addressesScript[3].balance); - token1 = new MyToken("token1", "T1"); - token2 = new MyToken("token2", "T2"); + + // create token price feed IDs--see https://pyth.network/developers/price-feed-ids + // TODO: automate converting bytes32 to string + idToken1 = 0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace; // ETH/USD + idToken2 = 0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43; // BTC/USD + string + memory idToken1Str = "ff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace"; + string + memory idToken2Str = "e62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43"; + console.log("ids of tokens"); + console.logBytes32(idToken1); + console.logBytes32(idToken2); + + token1 = new MyToken(idToken1Str, "T_ETH"); + token2 = new MyToken(idToken2Str, "T_BTC"); console.log("token 1 address", address(token1)); console.log("token 2 address", address(token2)); @@ -266,13 +279,6 @@ contract VaultScript is Script { // mint token 2 to the vault contract (to allow creation of initial vault with outstanding debt position) token2.mint(tokenVaultAddress, qtys[7]); - // create token price feed IDs--see https://pyth.network/developers/price-feed-ids - idToken1 = 0xff61491a931112ddf1bd8147cd1b641375f79f5825126d665480874634fd0ace; // ETH/USD - idToken2 = 0xe62df6c8b4a85fe1a67db44dc12de5db330f7ac66b72dc658afedf0f4a415b43; // BTC/USD - console.log("ids of tokens"); - console.logBytes32(idToken1); - console.logBytes32(idToken2); - // mint token to searchers A and B EOAs token1.mint(address(addressesScript[0]), 20_000_000); token2.mint(address(addressesScript[0]), 20_000_000); @@ -346,6 +352,13 @@ contract VaultScript is Script { bytes32 idToken1Latest = vm.parseJsonBytes32(json, ".idToken1"); bytes32 idToken2Latest = vm.parseJsonBytes32(json, ".idToken2"); + console.log("oracle address:"); + console.log(oracleLatest); + console.log("token 1 id:"); + console.logBytes32(idToken1Latest); + console.log("token 2 id:"); + console.logBytes32(idToken2Latest); + MockPyth oracle = MockPyth(payable(oracleLatest)); // set initial oracle prices diff --git a/per_multicall/src/Errors.sol b/per_multicall/src/Errors.sol index ca8a0606..578b18e2 100644 --- a/per_multicall/src/Errors.sol +++ b/per_multicall/src/Errors.sol @@ -4,6 +4,9 @@ pragma solidity ^0.8.13; // Signature: 0x82b42900 error Unauthorized(); +// Signature: 0x868a64de +error InvalidPermission(); + // Signature: 0xf136a5b7 error InvalidSearcherSignature(); @@ -30,3 +33,9 @@ error NotRegistered(); // Signature: 0xd3c8346c error LiquidationCallFailed(string reason); + +// Signature: 0x4af147aa +error InsufficientTokenReceived(); + +// Signature: 0x4be6321b +error InvalidSignatureLength(); diff --git a/per_multicall/src/LiquidationAdapter.sol b/per_multicall/src/LiquidationAdapter.sol index b785f081..b44a9dad 100644 --- a/per_multicall/src/LiquidationAdapter.sol +++ b/per_multicall/src/LiquidationAdapter.sol @@ -144,10 +144,9 @@ contract LiquidationAdapter is SigVerify { uint256 amount = params.expectedReceiptTokens[i].amount; uint256 balanceFinal = token.balanceOf(address(this)); - require( - balanceFinal >= balancesExpectedReceipt[i], - "insufficient token received" - ); + if (balanceFinal < balancesExpectedReceipt[i]) { + revert InsufficientTokenReceived(); + } // transfer receipt tokens to liquidator token.transfer(params.liquidator, amount); diff --git a/per_multicall/src/PERMulticall.sol b/per_multicall/src/PERMulticall.sol index 29196e52..f55b3c67 100644 --- a/per_multicall/src/PERMulticall.sol +++ b/per_multicall/src/PERMulticall.sol @@ -49,10 +49,9 @@ contract PERMulticall { * @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" - ); + if (msg.sender != _perOperator) { + revert Unauthorized(); + } _feeConfig[feeRecipient] = feeSplit; } @@ -84,14 +83,13 @@ contract PERMulticall { 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" - ); + if (msg.sender != _perOperator) { + revert Unauthorized(); + } + if (permission.length < 20) { + revert InvalidPermission(); + } + _permissions[keccak256(permission)] = true; multicallStatuses = new MulticallStatus[](data.length); @@ -152,11 +150,12 @@ 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" - ); + if ( + (balanceFinalEth - balanceInitEth < bid) || + (balanceFinalEth < balanceInitEth) + ) { + revert InvalidBid(); + } } return (success, result); diff --git a/per_multicall/src/TokenVault.sol b/per_multicall/src/TokenVault.sol index 69d18422..db7521ed 100644 --- a/per_multicall/src/TokenVault.sol +++ b/per_multicall/src/TokenVault.sol @@ -95,8 +95,12 @@ contract TokenVault is PERFeeReceiver { uint256 priceCollateral = _getPrice(vault.tokenIDCollateral); uint256 priceDebt = _getPrice(vault.tokenIDDebt); - require(priceCollateral >= 0, "collateral price is negative"); - require(priceDebt >= 0, "debt price is negative"); + if (priceCollateral < 0) { + revert NegativePrice(); + } + if (priceDebt < 0) { + revert NegativePrice(); + } uint256 valueCollateral = priceCollateral * vault.amountCollateral; uint256 valueDebt = priceDebt * vault.amountDebt; @@ -139,10 +143,9 @@ contract TokenVault is PERFeeReceiver { tokenIDCollateral, tokenIDDebt ); - require( - minPermissionLessHealthRatio <= minHealthRatio, - "minPermissionLessHealthRatio must be less than or equal to minHealthRatio" - ); + if (minPermissionLessHealthRatio > minHealthRatio) { + revert InvalidHealthRatios(); + } if (_getVaultHealth(vault) < vault.minHealthRatio) { revert UncollateralizedVaultCreation(); } diff --git a/per_multicall/src/TokenVaultErrors.sol b/per_multicall/src/TokenVaultErrors.sol index 7898adad..06e4540f 100644 --- a/per_multicall/src/TokenVaultErrors.sol +++ b/per_multicall/src/TokenVaultErrors.sol @@ -1,10 +1,20 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.13; +// Signature: 0xe922edfd error UncollateralizedVaultCreation(); +// Signature: 0xdcb430ee error InvalidVaultUpdate(); +// Signature: 0x9cd7b1c6 error InvalidPriceExponent(); +// Signature: 0x85914873 error InvalidLiquidation(); + +// Signature: 0x61ca76d2 +error NegativePrice(); + +// Signature: 0x4a7a3163 +error InvalidHealthRatios(); diff --git a/per_sdk/protocols/token_vault_monitor.py b/per_sdk/protocols/token_vault_monitor.py index 42483253..2f80e284 100644 --- a/per_sdk/protocols/token_vault_monitor.py +++ b/per_sdk/protocols/token_vault_monitor.py @@ -10,7 +10,7 @@ import web3 from eth_abi import encode -from per_sdk.utils.pyth_prices import PriceFeed, PriceFeedClient +from per_sdk.utils.pyth_prices import PriceFeed, PriceFeedClient, price_to_tuple from per_sdk.utils.types_liquidation_adapter import LiquidationOpportunity logger = logging.getLogger(__name__) @@ -44,12 +44,20 @@ def get_vault_abi(): class VaultMonitor: def __init__( - self, rpc_url: str, contract_address: str, weth_address: str, chain_id: str + self, + rpc_url: str, + contract_address: str, + weth_address: str, + chain_id: str, + include_price_updates: bool, + mock_pyth: bool, ): self.rpc_url = rpc_url self.contract_address = contract_address self.weth_address = weth_address self.chain_id = chain_id + self.include_price_updates = include_price_updates + self.mock_pyth = mock_pyth self.w3 = web3.AsyncWeb3(web3.AsyncHTTPProvider(rpc_url)) self.token_vault = self.w3.eth.contract( @@ -115,8 +123,30 @@ def create_liquidation_opp( Returns: A LiquidationOpportunity object corresponding to the specified account. """ - - price_updates = [base64.b64decode(update["vaa"]) for update in prices] + price_updates = [] + + if self.include_price_updates: + if self.mock_pyth: + price_updates = [] + + for update in prices: + feed_id = bytes.fromhex(update["feed_id"]) + price = price_to_tuple(update["price"]) + price_ema = price_to_tuple(update["price_ema"]) + prev_publish_time = 0 + price_updates.append( + encode( + [ + "bytes32", + "(int64,uint64,int32,uint64)", + "(int64,uint64,int32,uint64)", + "uint64", + ], + [feed_id, price, price_ema, prev_publish_time], + ) + ) + else: + price_updates = [base64.b64decode(update["vaa"]) for update in prices] calldata = self.token_vault.encodeABI( fn_name="liquidateWithPriceUpdate", @@ -245,6 +275,20 @@ async def main(): dest="weth_contract", help="WETH contract address", ) + parser.add_argument( + "--exclude-price-updates", + action="store_false", + dest="include_price_updates", + default=True, + help="If provided, will exclude Pyth price updates from the liquidation call. Should only be used in testing.", + ) + parser.add_argument( + "--mock-pyth", + action="store_true", + dest="mock_pyth", + default=False, + help="If provided, will construct price updates in MockPyth format rather than VAAs", + ) group = parser.add_mutually_exclusive_group(required=True) group.add_argument( "--dry-run", @@ -269,7 +313,12 @@ async def main(): logger.addHandler(log_handler) monitor = VaultMonitor( - args.rpc_url, args.vault_contract, args.weth_contract, args.chain_id + args.rpc_url, + args.vault_contract, + args.weth_contract, + args.chain_id, + args.include_price_updates, + args.mock_pyth, ) while True: diff --git a/per_sdk/searcher/simple_searcher.py b/per_sdk/searcher/simple_searcher.py index e0f425a8..a709e5e4 100644 --- a/per_sdk/searcher/simple_searcher.py +++ b/per_sdk/searcher/simple_searcher.py @@ -150,6 +150,7 @@ async def main(): continue logger.debug("Found %d liquidation opportunities", len(accounts_liquidatable)) + for liquidation_opp in accounts_liquidatable: opp_id = liquidation_opp["opportunity_id"] if liquidation_opp["version"] != "v1": @@ -172,6 +173,7 @@ async def main(): f"/v1/liquidation/opportunities/{opp_id}/bids", ), json=tx, + timeout=20, ) logger.info( "Submitted bid amount %s for opportunity %s, server response: %s", diff --git a/per_sdk/utils/pyth_prices.py b/per_sdk/utils/pyth_prices.py index 92da18c2..d3727eb2 100644 --- a/per_sdk/utils/pyth_prices.py +++ b/per_sdk/utils/pyth_prices.py @@ -21,6 +21,15 @@ class PriceFeed(TypedDict): vaa: str +def price_to_tuple(price: Price): + return ( + int(price["price"]), + int(price["conf"]), + int(price["expo"]), + int(price["publish_time"]), + ) + + async def get_price_feed_ids() -> list[str]: """ Queries the Hermes https endpoint for a list of the IDs of all Pyth price feeds. From 8b7abb87010212791d885339b0163612d8b884bf Mon Sep 17 00:00:00 2001 From: Amin Moghaddam Date: Wed, 14 Feb 2024 10:21:04 +0100 Subject: [PATCH 2/2] Add liveness endpoint (#16) --- auction-server/src/api.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/auction-server/src/api.rs b/auction-server/src/api.rs index d077f1cf..b3a5f26d 100644 --- a/auction-server/src/api.rs +++ b/auction-server/src/api.rs @@ -152,6 +152,10 @@ impl IntoResponse for RestError { } } +pub async fn live() -> Response { + (StatusCode::OK, "OK").into_response() +} + pub async fn start_server(run_options: RunOptions) -> Result<()> { tokio::spawn(async move { tracing::info!("Registered shutdown signal handler..."); @@ -254,6 +258,7 @@ pub async fn start_server(run_options: RunOptions) -> Result<()> { "/v1/liquidation/opportunities/:opportunity_id/bids", post(liquidation::post_bid), ) + .route("/live", get(live)) .layer(CorsLayer::permissive()) .with_state(server_store);