Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge upstream into fork #3

Merged
merged 33 commits into from
Mar 18, 2024
Merged
Changes from 3 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
589bdb6
Fix Clippy Lints
Jan 31, 2023
6607430
Fail CI on build warnings
Jan 31, 2023
7c1ce7b
Add Docker Deployment Workflow
Jan 31, 2023
6b1c623
chore: test bullseye slim
charlesndalton Feb 1, 2023
114c855
Revert "chore: test bullseye slim"
charlesndalton Feb 1, 2023
15b9705
Merge branch 'build-docker-image' of github.com:cowprotocol/milkman-bot
charlesndalton Feb 1, 2023
a37a2c5
chore: add license
charlesndalton Feb 1, 2023
3a05cc2
chore: try alpine instead of debian
charlesndalton Feb 1, 2023
798114d
Merge branch 'main' of github.com:charlesndalton/milkman-bot
charlesndalton Feb 1, 2023
179f814
Merge pull request #4 from cowprotocol/fix-clippy-lints
charlesndalton Feb 1, 2023
54ef83c
chore: add license to deploy.yaml
charlesndalton Feb 1, 2023
7bfca61
Merge branch 'main' of github.com:charlesndalton/milkman-bot
charlesndalton Feb 1, 2023
23fde0f
fix: revert back to bullseye
charlesndalton Feb 1, 2023
6c04879
chore: update deployment yaml to use ghcr
charlesndalton Feb 1, 2023
1748cbc
Make slippage tolerance configurable
fleupold Jun 1, 2023
f000788
Use quoteId when placing orders
fleupold Jun 25, 2023
8bedb01
Debug print isValidSignature call
fleupold Jun 25, 2023
8f8b303
Merge pull request #9 from cowprotocol/make_slippage_tolerance_config…
charlesndalton Jun 26, 2023
6845012
Merge pull request #10 from cowprotocol/debug_print_is_valid_signatur…
charlesndalton Jun 26, 2023
6a32f50
make quote_id part of the order struct
fleupold Jun 27, 2023
bf82241
fix tests
fleupold Jun 27, 2023
2fae0d4
fix clippy as well
fleupold Jun 27, 2023
deab124
Merge pull request #12 from cowprotocol/fix_tests
charlesndalton Jun 27, 2023
f68c9a6
Merge pull request #11 from cowprotocol/use_quote_id
charlesndalton Jun 27, 2023
267e84c
Fix operator precedence in slippage computation
fleupold Jun 29, 2023
222f0de
Merge pull request #13 from cowprotocol/fix_slippage_computation
charlesndalton Jun 29, 2023
628f5d3
Switch to tracing logs to allow for log spans per order
fleupold Aug 26, 2023
74155bd
clippy
fleupold Aug 27, 2023
0f0f953
Update README.md
fleupold Oct 7, 2023
0dfd83e
Merge pull request #15 from cowprotocol/readme-slippage-patch
charlesndalton Oct 22, 2023
5890436
Merge pull request #14 from cowprotocol/tracing_spans
charlesndalton Oct 22, 2023
59ccb41
Merge remote-tracking branch 'upstream/main'
squadgazzz Mar 15, 2024
6236951
Formatting
squadgazzz Mar 15, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
@@ -11,7 +11,10 @@ jobs:
# Shrink artifact size by not including debug info. Makes build faster and shrinks cache.
CARGO_PROFILE_DEV_DEBUG: 0
CARGO_PROFILE_TEST_DEBUG: 0
# Error build on warning (including clippy lints)
RUSTFLAGS: "-Dwarnings"
steps:
- uses: actions/checkout@v2
- uses: Swatinem/rust-cache@v1
- run: cargo test
- run: cargo test
- run: cargo clippy
18 changes: 9 additions & 9 deletions src/configuration.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use anyhow::{Context, Result, anyhow};
use anyhow::{anyhow, Result};
use ethers::types::Address;
use log::debug;
use std::env;
@@ -22,21 +22,25 @@ impl Configuration {
let node_base_url = collect_optional_environment_variable("NODE_BASE_URL")?;

if infura_api_key.is_none() && node_base_url.is_none() {
return Err(anyhow!("either `infura_api_key` or `node_base_url` must be set"))
return Err(anyhow!(
"either `infura_api_key` or `node_base_url` must be set"
));
}

let network = collect_optional_environment_variable("MILKMAN_NETWORK")?
.unwrap_or("mainnet".to_string());
.unwrap_or_else(|| "mainnet".to_string());
let milkman_address = collect_optional_environment_variable("MILKMAN_ADDRESS")?
.unwrap_or(PROD_MILKMAN_ADDRESS.to_string())
.as_deref()
.unwrap_or(PROD_MILKMAN_ADDRESS)
.parse()?;
let polling_frequency_secs =
collect_optional_environment_variable("POLLING_FREQUENCY_SECS")?
.map(|var| var.parse::<u64>())
.transpose()?
.unwrap_or(10);
let hash_helper_address = collect_optional_environment_variable("HASH_HELPER_ADDRESS")?
.unwrap_or(MAINNET_HASH_HELPER_ADDRESS.to_string())
.as_deref()
.unwrap_or(MAINNET_HASH_HELPER_ADDRESS)
.parse()?;

let starting_block_number =
@@ -57,10 +61,6 @@ impl Configuration {
}
}

fn collect_required_environment_variable(key: &str) -> Result<String> {
Ok(env::var(key).context(format!("required environment variable {} not set", key))?)
}

fn collect_optional_environment_variable(key: &str) -> Result<Option<String>> {
match env::var(key) {
Ok(value) => Ok(Some(value)),
43 changes: 29 additions & 14 deletions src/cow_api_client.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::configuration::Configuration;
use anyhow::{anyhow, Result};
use anyhow::{anyhow, Context, Result};
use ethers::abi::Address;
use ethers::types::{Bytes, U256};
use log::{debug, info};
@@ -14,6 +14,19 @@ pub struct Quote {
pub valid_to: u64,
}

#[derive(Debug)]
pub struct Order<'a> {
pub order_contract: Address,
pub sell_token: Address,
pub buy_token: Address,
pub sell_amount: U256,
pub buy_amount: U256,
pub valid_to: u64,
pub fee_amount: U256,
pub receiver: Address,
pub eip_1271_signature: &'a Bytes,
}

pub struct CowAPIClient {
pub base_url: String,
pub milkman_address: String,
@@ -73,15 +86,15 @@ impl CowAPIClient {
let quote = &response_body["quote"];
let fee_amount = quote["feeAmount"]
.as_str()
.ok_or(anyhow!("unable to get `feeAmount` on quote"))?
.context("unable to get `feeAmount` on quote")?
.to_owned();
let buy_amount_after_fee = quote["buyAmount"]
.as_str()
.ok_or(anyhow!("unable to get `buyAmountAfterFee` from quote"))?
.context("unable to get `buyAmountAfterFee` from quote")?
.to_owned();
let valid_to = quote["validTo"]
.as_u64()
.ok_or(anyhow!("unable to get `validTo` from quote"))?;
.context("unable to get `validTo` from quote")?;

Ok(Quote {
fee_amount: fee_amount.parse::<u128>()?.into(),
@@ -92,15 +105,17 @@ impl CowAPIClient {

pub async fn create_order(
&self,
order_contract: Address,
sell_token: Address,
buy_token: Address,
sell_amount: U256,
buy_amount: U256,
valid_to: u64,
fee_amount: U256,
receiver: Address,
eip_1271_signature: &Bytes,
Order {
order_contract,
sell_token,
buy_token,
sell_amount,
buy_amount,
valid_to,
fee_amount,
receiver,
eip_1271_signature,
}: Order<'_>,
) -> Result<String> {
let http_client = reqwest::Client::new();
let response = http_client
@@ -130,7 +145,7 @@ impl CowAPIClient {
.json::<Value>()
.await?
.as_str()
.ok_or(anyhow!("Unable to retrieve UID from POST order response"))?
.context("Unable to retrieve UID from POST order response")?
.to_string(),
Err(err) => {
debug!("POST order failed with body: {:?}", response.text().await?);
36 changes: 26 additions & 10 deletions src/encoder.rs
Original file line number Diff line number Diff line change
@@ -4,17 +4,33 @@ use hex::FromHex;

use crate::constants::{APP_DATA, ERC20_BALANCE, KIND_SELL};

#[derive(Debug)]
pub struct SignatureData<'a> {
pub from_token: Address,
pub to_token: Address,
pub receiver: Address,
pub sell_amount_after_fees: U256,
pub buy_amount_after_fees_and_slippage: U256,
pub valid_to: u64,
pub fee_amount: U256,
pub order_creator: Address,
pub price_checker: Address,
pub price_checker_data: &'a Bytes,
}

pub fn get_eip_1271_signature(
from_token: Address,
to_token: Address,
receiver: Address,
sell_amount_after_fees: U256,
buy_amount_after_fees_and_slippage: U256,
valid_to: u64,
fee_amount: U256,
order_creator: Address,
price_checker: Address,
price_checker_data: &Bytes,
SignatureData {
from_token,
to_token,
receiver,
sell_amount_after_fees,
buy_amount_after_fees_and_slippage,
valid_to,
fee_amount,
order_creator,
price_checker,
price_checker_data,
}: SignatureData<'_>,
) -> Bytes {
abi::encode(&vec![
Token::Address(from_token),
39 changes: 20 additions & 19 deletions src/ethereum_client.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
use crate::types::{BlockNumber, Swap};
use anyhow::{anyhow, Result};
use anyhow::{Context, Result};
use ethers::prelude::*;
use hex::FromHex;
use log::debug;
@@ -10,7 +9,8 @@ use std::sync::Arc;

use crate::configuration::Configuration;
use crate::constants::{APP_DATA, ERC20_BALANCE, KIND_SELL};
use crate::encoder;
use crate::encoder::{self, SignatureData};
use crate::types::{BlockNumber, Swap};

abigen!(
RawMilkman,
@@ -46,7 +46,8 @@ impl EthereumClient {
} else {
format!(
"https://{}.infura.io/v3/{}",
config.network, config.infura_api_key.clone().unwrap()
config.network,
config.infura_api_key.clone().unwrap()
)
};
let provider = Arc::new(Provider::<Http>::try_from(node_url)?);
@@ -61,10 +62,11 @@ impl EthereumClient {
self.get_latest_block()
.await?
.number
.ok_or(anyhow!("Error extracting number from latest block."))
.context("Error extracting number from latest block.")
.map(|block_num: U64| block_num.try_into().unwrap()) // U64 -> u64 should always work
}

#[cfg(test)]
pub async fn get_chain_timestamp(&self) -> Result<u64> {
Ok(self.get_latest_block().await?.timestamp.as_u64())
}
@@ -73,7 +75,7 @@ impl EthereumClient {
self.inner_client
.get_block(ethers::core::types::BlockNumber::Latest)
.await?
.ok_or(anyhow!("Error fetching latest block."))
.context("Error fetching latest block.")
}

pub async fn get_requested_swaps(
@@ -135,18 +137,18 @@ impl EthereumClient {
.call()
.await?;

let mock_signature = encoder::get_eip_1271_signature(
swap_request.from_token,
swap_request.to_token,
swap_request.receiver,
swap_request.amount_in,
U256::MAX,
u32::MAX as u64,
U256::zero(),
swap_request.order_creator,
swap_request.price_checker,
&swap_request.price_checker_data,
);
let mock_signature = encoder::get_eip_1271_signature(SignatureData {
from_token: swap_request.from_token,
to_token: swap_request.to_token,
receiver: swap_request.receiver,
sell_amount_after_fees: swap_request.amount_in,
buy_amount_after_fees_and_slippage: U256::MAX,
valid_to: u32::MAX as u64,
fee_amount: U256::zero(),
order_creator: swap_request.order_creator,
price_checker: swap_request.price_checker,
price_checker_data: &swap_request.price_checker_data,
});

debug!(
"Is valid sig? {:?}",
@@ -161,7 +163,6 @@ impl EthereumClient {
.estimate_gas()
.await?)
}

}

impl From<&SwapRequestedFilter> for Swap {
48 changes: 24 additions & 24 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -11,9 +11,10 @@ mod ethereum_client;
use crate::ethereum_client::EthereumClient;

mod cow_api_client;
use crate::cow_api_client::CowAPIClient;
use crate::cow_api_client::{CowAPIClient, Order};

mod encoder;
use crate::encoder::SignatureData;

mod types;
use crate::types::Swap;
@@ -78,7 +79,7 @@ async fn main() {
}
};

if requested_swaps.len() > 0 {
if !requested_swaps.is_empty() {
info!(
"Found {} requested swaps between blocks {} and {}",
requested_swaps.len(),
@@ -152,32 +153,31 @@ async fn main() {
let sell_amount_after_fees = requested_swap.amount_in - quote.fee_amount;
let buy_amount_after_fees_and_slippage = quote.buy_amount_after_fee * 995 / 1000;

let eip_1271_signature = encoder::get_eip_1271_signature(
requested_swap.from_token,
requested_swap.to_token,
requested_swap.receiver,
let eip_1271_signature = encoder::get_eip_1271_signature(SignatureData {
from_token: requested_swap.from_token,
to_token: requested_swap.to_token,
receiver: requested_swap.receiver,
sell_amount_after_fees,
buy_amount_after_fees_and_slippage,
quote.valid_to,
quote.fee_amount,
requested_swap.order_creator,
requested_swap.price_checker,
&requested_swap.price_checker_data,
);

valid_to: quote.valid_to,
fee_amount: quote.fee_amount,
order_creator: requested_swap.order_creator,
price_checker: requested_swap.price_checker,
price_checker_data: &requested_swap.price_checker_data,
});

match cow_api_client
.create_order(
requested_swap.order_contract,
requested_swap.from_token,
requested_swap.to_token,
sell_amount_after_fees,
buy_amount_after_fees_and_slippage,
quote.valid_to,
quote.fee_amount,
requested_swap.receiver,
&eip_1271_signature,
)
.create_order(Order {
order_contract: requested_swap.order_contract,
sell_token: requested_swap.from_token,
buy_token: requested_swap.to_token,
sell_amount: sell_amount_after_fees,
buy_amount: buy_amount_after_fees_and_slippage,
valid_to: quote.valid_to,
fee_amount: quote.fee_amount,
receiver: requested_swap.receiver,
eip_1271_signature: &eip_1271_signature,
})
.await
{
Ok(_) => (),