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

[refunder] submitting txs #759

Merged
merged 8 commits into from
Nov 16, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
110 changes: 110 additions & 0 deletions crates/refunder/src/ethflow_order.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
use database::{ethflow_orders::EthOrderPlacement, orders::Order};
use ethcontract::{Bytes, H160, U256};
use number_conversions::big_decimal_to_u256;
// Data structure reflecting the contract ethflow order
// https://github.com/cowprotocol/ethflowcontract/blob/main/src/libraries/EthFlowOrder.sol#L19
pub struct EthflowOrder {
buy_token: H160,
receiver: H160,
sell_amount: U256,
buy_amount: U256,
app_data: Bytes<[u8; 32]>,
fee_amount: U256,
valid_to: u32,
flags: bool,
josojo marked this conversation as resolved.
Show resolved Hide resolved
quote_id: i64,
}

impl EthflowOrder {
pub fn encode(&self) -> EncodedEthflowOrder {
(
self.buy_token,
self.receiver,
self.sell_amount,
self.buy_amount,
self.app_data,
self.fee_amount,
self.valid_to,
self.flags,
self.quote_id,
)
}
}

pub type EncodedEthflowOrder = (
H160, // buyToken
H160, // receiver
U256, // sellAmount
U256, // buyAmount
Bytes<[u8; 32]>, // appData
U256, // feeAmount
u32, // validTo
bool, // flags
i64, // quoteId
);

pub fn order_to_ethflow_data(
order: Order,
ethflow_order_placement: EthOrderPlacement,
) -> EthflowOrder {
EthflowOrder {
buy_token: H160(order.buy_token.0),
receiver: H160(order.receiver.unwrap().0), // ethflow orders have always a
// receiver. It's enforced by the contract.
sell_amount: big_decimal_to_u256(&order.sell_amount).unwrap(),
buy_amount: big_decimal_to_u256(&order.buy_amount).unwrap(),
app_data: Bytes(order.app_data.0),
fee_amount: big_decimal_to_u256(&order.fee_amount).unwrap(),
valid_to: ethflow_order_placement.valid_to as u32,
flags: order.partially_fillable,
quote_id: 0i64, // quoteId is not important for refunding and will be ignored
}
}

#[cfg(test)]
mod tests {
use super::*;
use database::byte_array::ByteArray;
use number_conversions::u256_to_big_decimal;

#[test]
fn test_order_to_ethflow_data() {
let buy_token = ByteArray([1u8; 20]);
let receiver = ByteArray([3u8; 20]);
let sell_amount = U256::from_dec_str("1").unwrap();
let buy_amount = U256::from_dec_str("2").unwrap();
let app_data = ByteArray([3u8; 32]);
let fee_amount = U256::from_dec_str("3").unwrap();
let valid_to = 234u32;

let order = Order {
buy_token,
receiver: Some(receiver),
sell_amount: u256_to_big_decimal(&sell_amount),
buy_amount: u256_to_big_decimal(&buy_amount),
valid_to: valid_to.into(),
app_data,
fee_amount: u256_to_big_decimal(&fee_amount),
..Default::default()
};
let ethflow_order = EthOrderPlacement {
valid_to: valid_to.into(),
..Default::default()
};
let expected_encoded_order = (
H160(order.buy_token.0),
H160(order.receiver.unwrap().0),
big_decimal_to_u256(&order.sell_amount).unwrap(),
big_decimal_to_u256(&order.buy_amount).unwrap(),
Bytes(order.app_data.0),
big_decimal_to_u256(&order.fee_amount).unwrap(),
ethflow_order.valid_to as u32,
false,
josojo marked this conversation as resolved.
Show resolved Hide resolved
0i64,
);
assert_eq!(
order_to_ethflow_data(order, ethflow_order).encode(),
expected_encoded_order
);
}
}
1 change: 1 addition & 0 deletions crates/refunder/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod arguments;
pub mod ethflow_order;
pub mod refund_service;
pub mod submitter;

Expand Down
96 changes: 9 additions & 87 deletions crates/refunder/src/refund_service.rs
Original file line number Diff line number Diff line change
@@ -1,36 +1,25 @@
use super::ethflow_order::order_to_ethflow_data;
use anyhow::{anyhow, Context, Result};
use contracts::CoWSwapEthFlow;
use database::{
ethflow_orders::{
mark_eth_orders_as_refunded, read_order, refundable_orders, EthOrderPlacement,
},
orders::{read_order as read_db_order, Order},
orders::read_order as read_db_order,
OrderUid,
};
use ethcontract::{Account, Bytes, U256};
use ethcontract::Account;
use futures::{stream, StreamExt};
use number_conversions::big_decimal_to_u256;
use primitive_types::H160;
use shared::ethrpc::{Web3, Web3CallBatch, MAX_BATCH_SIZE};
use sqlx::PgPool;

use super::ethflow_order::{EncodedEthflowOrder, EthflowOrder};
use crate::submitter::Submitter;

const INVALIDATED_OWNER: H160 = H160([255u8; 20]);
const MAX_NUMBER_OF_UIDS_PER_REFUND_TX: usize = 30;

pub type EncodedEthflowOrder = (
H160, // buyToken
H160, // receiver
U256, // sellAmount
U256, // buyAmount
Bytes<[u8; 32]>, // appData
U256, // feeAmount
u32, // validTo
bool, // flags
i64, // quoteId
);

pub struct RefundService {
pub db: PgPool,
pub web3: Web3,
Expand All @@ -42,7 +31,7 @@ pub struct RefundService {

struct SplittedOrderUids {
refunded: Vec<OrderUid>,
_to_be_refunded: Vec<OrderUid>,
to_be_refunded: Vec<OrderUid>,
}

impl RefundService {
Expand Down Expand Up @@ -80,7 +69,7 @@ impl RefundService {
self.update_already_refunded_orders_in_db(order_uids_per_status.refunded)
.await?;

self.send_out_refunding_tx(order_uids_per_status._to_be_refunded)
self.send_out_refunding_tx(order_uids_per_status.to_be_refunded)
.await?;
Ok(())
}
Expand Down Expand Up @@ -168,12 +157,12 @@ impl RefundService {
.collect();
let result = SplittedOrderUids {
refunded: refunded_uids,
_to_be_refunded: to_be_refunded_uids,
to_be_refunded: to_be_refunded_uids,
};
Ok(result)
}

async fn get_ethflow_data_from_db(&self, uid: &OrderUid) -> Result<EncodedEthflowOrder> {
async fn get_ethflow_data_from_db(&self, uid: &OrderUid) -> Result<EthflowOrder> {
let mut ex = self.db.acquire().await.context("acquire")?;
let order = read_db_order(&mut ex, uid)
.await
Expand All @@ -190,7 +179,6 @@ impl RefundService {
if uids.is_empty() {
return Ok(());
}

// only try to refund MAX_NUMBER_OF_UIDS_PER_REFUND_TX uids, in order to fit into gas limit
let uids: Vec<OrderUid> = uids
.into_iter()
Expand All @@ -212,7 +200,7 @@ impl RefundService {
.buffer_unordered(10)
.filter_map(|result| async {
match result {
Ok(order) => Some(order),
Ok(order) => Some(order.encode()),
Err(err) => {
tracing::error!(?err, "failed to get data from db");
None
Expand All @@ -226,69 +214,3 @@ impl RefundService {
Ok(())
}
}

fn order_to_ethflow_data(
order: Order,
ethflow_order_placement: EthOrderPlacement,
) -> EncodedEthflowOrder {
(
H160(order.buy_token.0),
H160(order.receiver.unwrap().0), // ethflow orders have always a
// receiver. It's enforced by the contract.
big_decimal_to_u256(&order.sell_amount).unwrap(),
big_decimal_to_u256(&order.buy_amount).unwrap(),
Bytes(order.app_data.0),
big_decimal_to_u256(&order.fee_amount).unwrap(),
ethflow_order_placement.valid_to as u32,
false, // ethflow orders are always fill or kill orders
0i64, // quoteId is not important for refunding and will be ignored
)
}

#[cfg(test)]
mod tests {
use super::*;
use database::byte_array::ByteArray;
use number_conversions::u256_to_big_decimal;

#[test]
fn test_order_to_ethflow_data() {
let buy_token = ByteArray([1u8; 20]);
let receiver = ByteArray([3u8; 20]);
let sell_amount = U256::from_dec_str("1").unwrap();
let buy_amount = U256::from_dec_str("2").unwrap();
let app_data = ByteArray([3u8; 32]);
let fee_amount = U256::from_dec_str("3").unwrap();
let valid_to = 234u32;

let order = Order {
buy_token,
receiver: Some(receiver),
sell_amount: u256_to_big_decimal(&sell_amount),
buy_amount: u256_to_big_decimal(&buy_amount),
valid_to: valid_to.into(),
app_data,
fee_amount: u256_to_big_decimal(&fee_amount),
..Default::default()
};
let ethflow_order = EthOrderPlacement {
valid_to: valid_to.into(),
..Default::default()
};
let expected_encoded_order = (
H160(order.buy_token.0),
H160(order.receiver.unwrap().0),
big_decimal_to_u256(&order.sell_amount).unwrap(),
big_decimal_to_u256(&order.buy_amount).unwrap(),
Bytes(order.app_data.0),
big_decimal_to_u256(&order.fee_amount).unwrap(),
ethflow_order.valid_to as u32,
false,
0i64,
);
assert_eq!(
order_to_ethflow_data(order, ethflow_order),
expected_encoded_order
);
}
}
28 changes: 14 additions & 14 deletions crates/refunder/src/submitter.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// This submitter has the follwoing logic:
// This submitter has the following logic:
// It tries to submit a tx - as EIP1559 - with a small tx tip,
// but a quite high max_fee_per_gas such that it's likely being mined quickly
//
Expand All @@ -8,7 +8,7 @@
// a higher gas price, in order to avoid: ErrReplaceUnderpriced erros
// In the re-newed attempt for submission the same nonce is used as before.

use crate::refund_service::EncodedEthflowOrder;
use super::ethflow_order::EncodedEthflowOrder;
use anyhow::{anyhow, Result};
use contracts::CoWSwapEthFlow;
use database::OrderUid;
Expand All @@ -24,7 +24,7 @@ use shared::{

// Max gas used for submitting transactions
// If the gas price is higher than this value,
// the service will temproarily not refund users
// the service will temporarily not refund users
const MAX_GAS_PRICE: u64 = 800_000_000_000;

// The gas price buffer determines the gas price buffer used to
Expand Down Expand Up @@ -158,21 +158,21 @@ fn calculate_submission_gas_price(
let mut new_max_fee_per_gas = max_fee_per_gas * GAS_PRICE_BUFFER_FACTOR;
// If tx from the previous submission was not mined,
// we incease the gas price
if let Some(nonce_of_last_submission) = nonce_of_last_submission {
if nonce_of_last_submission == newest_nonce {
// Increase the gas price to last submission's
// gas price plus an additional 12 %
if let Some(previous_gas_price) = gas_price_of_last_submission {
new_max_fee_per_gas = f64::max(
new_max_fee_per_gas,
previous_gas_price.to_f64_lossy() * GAS_PRICE_BUMP,
);
}
if Some(newest_nonce) == nonce_of_last_submission {
// Increase the gas price to last submission's
// gas price plus an additional 12 %
if let Some(previous_gas_price) = gas_price_of_last_submission {
new_max_fee_per_gas = f64::max(
new_max_fee_per_gas,
previous_gas_price.to_f64_lossy() * GAS_PRICE_BUMP,
);
}
}
let max_fee_per_gas = U256::from_f64_lossy(new_max_fee_per_gas);
let gas_price = GasPrice::Eip1559 {
max_fee_per_gas,
// The max_priority_fee_per_gas should normally allways be equal to MAX_PRIORITY_FEE_TIP
// Just in case the max_fee_per_gas is even lower than the constant, we need to adapt it
max_priority_fee_per_gas: U256::min(max_fee_per_gas, U256::from(MAX_PRIORITY_FEE_TIP)),
josojo marked this conversation as resolved.
Show resolved Hide resolved
};
Ok(gas_price)
Expand All @@ -189,7 +189,7 @@ mod tests {
let web3_gas_estimation = GasPrice1559 {
base_fee_per_gas: 2_000_000_000f64,
max_fee_per_gas,
max_priority_fee_per_gas: 2_000_000_000f64,
max_priority_fee_per_gas: 3_000_000_000f64,
};
let newest_nonce = U256::one();
let nonce_of_last_submission = None;
Expand Down