-
Notifications
You must be signed in to change notification settings - Fork 11
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #17 from propeller-heads/vm/dc/ENG-3753-protosim-c…
…ontract feat: Protosim contract and Adapter contract
- Loading branch information
Showing
8 changed files
with
659 additions
and
54 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,218 @@ | ||
// TODO: remove skip for clippy dead_code check | ||
#![allow(dead_code)] | ||
|
||
use crate::{ | ||
evm::account_storage::StateUpdate, | ||
protocol::vm::{ | ||
errors::ProtosimError, models::Capability, protosim_contract::ProtosimContract, | ||
}, | ||
}; | ||
use ethers::{ | ||
abi::{Address, Token}, | ||
types::U256, | ||
}; | ||
use revm::{primitives::Address as rAddress, DatabaseRef}; | ||
use std::collections::{HashMap, HashSet}; | ||
|
||
#[derive(Debug)] | ||
pub struct Trade { | ||
received_amount: U256, | ||
gas_used: U256, | ||
price: f64, | ||
} | ||
|
||
/// An implementation of `ProtosimContract` specific to the `AdapterContract` ABI interface, | ||
/// providing methods for price calculations, token swaps, capability checks, and more. | ||
/// | ||
/// This struct facilitates interaction with the `AdapterContract` by encoding and decoding data | ||
/// according to its ABI specification. Each method corresponds to a function in the adapter | ||
/// contract's interface, enabling seamless integration with Protosim’s simulation environment. | ||
/// | ||
/// # Methods | ||
/// - `price`: Calculates price information for a token pair within the adapter. | ||
/// - `swap`: Simulates a token swap operation, returning details about the trade and state updates. | ||
/// - `get_limits`: Retrieves the trade limits for a given token pair. | ||
/// - `get_capabilities`: Checks the capabilities of the adapter for a specific token pair. | ||
/// - `min_gas_usage`: Queries the minimum gas usage required for operations within the adapter. | ||
impl<D: DatabaseRef + std::clone::Clone> ProtosimContract<D> | ||
where | ||
D::Error: std::fmt::Debug, | ||
{ | ||
pub async fn price( | ||
&self, | ||
pair_id: String, | ||
sell_token: Address, | ||
buy_token: Address, | ||
amounts: Vec<u64>, | ||
block: u64, | ||
overwrites: Option<HashMap<rAddress, HashMap<U256, U256>>>, | ||
) -> Result<Vec<f64>, ProtosimError> { | ||
let args = vec![ | ||
self.hexstring_to_bytes(&pair_id)?, | ||
Token::Address(sell_token), | ||
Token::Address(buy_token), | ||
Token::Array( | ||
amounts | ||
.into_iter() | ||
.map(|a| Token::Uint(U256::from(a))) | ||
.collect(), | ||
), | ||
]; | ||
|
||
let res = self | ||
.call("price", args, block, None, overwrites, None, U256::zero()) | ||
.await? | ||
.return_value; | ||
// returning just floats - the python version returns Fractions (not sure why) | ||
let price = self.calculate_price(res[0].clone())?; | ||
Ok(price) | ||
} | ||
|
||
#[allow(clippy::too_many_arguments)] | ||
pub async fn swap( | ||
&self, | ||
pair_id: String, | ||
sell_token: Address, | ||
buy_token: Address, | ||
is_buy: bool, | ||
amount: U256, | ||
block: u64, | ||
overwrites: Option<HashMap<rAddress, HashMap<U256, U256>>>, | ||
) -> Result<(Trade, HashMap<revm::precompile::Address, StateUpdate>), ProtosimError> { | ||
let args = vec![ | ||
self.hexstring_to_bytes(&pair_id)?, | ||
Token::Address(sell_token), | ||
Token::Address(buy_token), | ||
Token::Bool(is_buy), | ||
Token::Uint(amount), | ||
]; | ||
|
||
let res = self | ||
.call("swap", args, block, None, overwrites, None, U256::zero()) | ||
.await?; | ||
let received_amount = res.return_value[0] | ||
.clone() | ||
.into_uint() | ||
.unwrap(); | ||
let gas_used = res.return_value[1] | ||
.clone() | ||
.into_uint() | ||
.unwrap(); | ||
let price = self | ||
.calculate_price(res.return_value[2].clone()) | ||
.unwrap()[0]; | ||
|
||
Ok((Trade { received_amount, gas_used, price }, res.simulation_result.state_updates)) | ||
} | ||
|
||
pub async fn get_limits( | ||
&self, | ||
pair_id: String, | ||
sell_token: Address, | ||
buy_token: Address, | ||
block: u64, | ||
overwrites: Option<HashMap<rAddress, HashMap<U256, U256>>>, | ||
) -> Result<(u64, u64), ProtosimError> { | ||
let args = vec![ | ||
self.hexstring_to_bytes(&pair_id)?, | ||
Token::Address(sell_token), | ||
Token::Address(buy_token), | ||
]; | ||
|
||
let res = self | ||
.call("getLimits", args, block, None, overwrites, None, U256::zero()) | ||
.await? | ||
.return_value; | ||
Ok(( | ||
res[0] | ||
.clone() | ||
.into_uint() | ||
.unwrap() | ||
.as_u64(), | ||
res[1] | ||
.clone() | ||
.into_uint() | ||
.unwrap() | ||
.as_u64(), | ||
)) | ||
} | ||
|
||
pub async fn get_capabilities( | ||
&self, | ||
pair_id: String, | ||
sell_token: Address, | ||
buy_token: Address, | ||
) -> Result<HashSet<Capability>, ProtosimError> { | ||
let args = vec![ | ||
self.hexstring_to_bytes(&pair_id)?, | ||
Token::Address(sell_token), | ||
Token::Address(buy_token), | ||
]; | ||
|
||
let res = self | ||
.call("getCapabilities", args, 1, None, None, None, U256::zero()) | ||
.await? | ||
.return_value; | ||
let capabilities: HashSet<Capability> = res | ||
.into_iter() | ||
.filter_map(|token| { | ||
if let Token::Uint(value) = token { | ||
Capability::from_uint(value).ok() | ||
} else { | ||
None | ||
} | ||
}) | ||
.collect(); | ||
|
||
Ok(capabilities) | ||
} | ||
|
||
pub async fn min_gas_usage(&self) -> Result<u64, ProtosimError> { | ||
let res = self | ||
.call("minGasUsage", vec![], 1, None, None, None, U256::zero()) | ||
.await? | ||
.return_value; | ||
Ok(res[0] | ||
.clone() | ||
.into_uint() | ||
.unwrap() | ||
.as_u64()) | ||
} | ||
|
||
fn hexstring_to_bytes(&self, pair_id: &str) -> Result<Token, ProtosimError> { | ||
let bytes = hex::decode(pair_id).map_err(|_| { | ||
ProtosimError::EncodingError(format!("Invalid hex string: {}", pair_id)) | ||
})?; | ||
Ok(Token::FixedBytes(bytes)) | ||
} | ||
|
||
fn calculate_price(&self, value: Token) -> Result<Vec<f64>, ProtosimError> { | ||
if let Token::Array(fractions) = value { | ||
// Map over each `Token::Tuple` in the array | ||
fractions | ||
.into_iter() | ||
.map(|fraction_token| { | ||
if let Token::Tuple(ref components) = fraction_token { | ||
let numerator = components[0] | ||
.clone() | ||
.into_uint() | ||
.unwrap(); | ||
let denominator = components[1] | ||
.clone() | ||
.into_uint() | ||
.unwrap(); | ||
if denominator.is_zero() { | ||
Err(ProtosimError::DecodingError("Denominator is zero".to_string())) | ||
} else { | ||
Ok((numerator.as_u128() as f64) / (denominator.as_u128() as f64)) | ||
} | ||
} else { | ||
Err(ProtosimError::DecodingError("Invalid fraction tuple".to_string())) | ||
} | ||
}) | ||
.collect() | ||
} else { | ||
Err(ProtosimError::DecodingError("Expected Token::Array".to_string())) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
// TODO: remove skips for clippy | ||
|
||
use crate::evm::simulation::SimulationError; | ||
use serde_json::Error as SerdeError; | ||
use std::io; | ||
use thiserror::Error; | ||
|
||
/// Represents the outer-level, user-facing errors of the Protosim package. | ||
/// | ||
/// `ProtosimError` encompasses all possible errors that can occur in the package, | ||
/// wrapping lower-level errors in a user-friendly way for easier handling and display. | ||
/// | ||
/// Variants: | ||
/// - `AbiError`: Represents an error when loading the ABI file, encapsulating a `FileError`. | ||
/// - `EncodingError`: Denotes an error in encoding data. | ||
/// - `SimulationFailure`: Wraps errors that occur during simulation, containing a | ||
/// `SimulationError`. | ||
/// - `DecodingError`: Indicates an error in decoding data. | ||
#[derive(Error, Debug)] | ||
pub enum ProtosimError { | ||
#[error("ABI loading error: {0}")] | ||
AbiError(FileError), | ||
#[error("Encoding error: {0}")] | ||
EncodingError(String), | ||
#[error("Simulation failure error: {0}")] | ||
SimulationFailure(SimulationError), | ||
#[error("Decoding error: {0}")] | ||
DecodingError(String), | ||
} | ||
|
||
#[derive(Debug, Error)] | ||
pub enum FileError { | ||
/// Occurs when the ABI file cannot be read | ||
#[error("Malformed ABI error: {0}")] | ||
MalformedABI(String), | ||
/// Occurs when the parent directory of the current file cannot be retrieved | ||
#[error("Structure error {0}")] | ||
Structure(String), | ||
/// Occurs when a bad file path was given, which cannot be converted to string. | ||
#[error("File path conversion error {0}")] | ||
FilePath(String), | ||
#[error("I/O error {0}")] | ||
Io(io::Error), | ||
#[error("Json parsing error {0}")] | ||
Parse(SerdeError), | ||
} | ||
|
||
impl From<io::Error> for FileError { | ||
fn from(err: io::Error) -> Self { | ||
FileError::Io(err) | ||
} | ||
} | ||
|
||
impl From<SerdeError> for FileError { | ||
fn from(err: SerdeError) -> Self { | ||
FileError::Parse(err) | ||
} | ||
} | ||
|
||
impl From<FileError> for ProtosimError { | ||
fn from(err: FileError) -> Self { | ||
ProtosimError::AbiError(err) | ||
} | ||
} | ||
impl From<SimulationError> for ProtosimError { | ||
fn from(err: SimulationError) -> Self { | ||
ProtosimError::SimulationFailure(err) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,8 @@ | ||
mod adapter_contract; | ||
mod constants; | ||
mod engine; | ||
mod erc20_overwrite_factory; | ||
mod errors; | ||
mod models; | ||
mod protosim_contract; | ||
pub mod utils; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
// TODO: remove skip for clippy dead_code check | ||
use crate::protocol::vm::errors::ProtosimError; | ||
use ethers::abi::Uint; | ||
|
||
#[allow(dead_code)] | ||
#[derive(Eq, PartialEq, Hash)] | ||
pub enum Capability { | ||
SellSide = 0, | ||
BuySide = 1, | ||
PriceFunction = 2, | ||
FeeOnTransfer = 3, | ||
ConstantPrice = 4, | ||
TokenBalanceIndependent = 5, | ||
ScaledPrice = 6, | ||
HardLimits = 7, | ||
MarginalPrice = 8, | ||
} | ||
|
||
impl Capability { | ||
pub fn from_uint(value: Uint) -> Result<Self, ProtosimError> { | ||
match value.as_u32() { | ||
0 => Ok(Capability::SellSide), | ||
1 => Ok(Capability::BuySide), | ||
2 => Ok(Capability::PriceFunction), | ||
3 => Ok(Capability::FeeOnTransfer), | ||
4 => Ok(Capability::ConstantPrice), | ||
5 => Ok(Capability::TokenBalanceIndependent), | ||
6 => Ok(Capability::ScaledPrice), | ||
7 => Ok(Capability::HardLimits), | ||
8 => Ok(Capability::MarginalPrice), | ||
_ => { | ||
Err(ProtosimError::DecodingError(format!("Unexpected Capability value: {}", value))) | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.