Have the Ethereum scheduler create Batches as necessary

Also introduces the fee logic, despite it being stubbed.
This commit is contained in:
Luke Parker
2024-09-20 00:12:54 -04:00
parent 8ea5acbacb
commit 4292660eda
4 changed files with 132 additions and 45 deletions

View File

@@ -1,6 +1,11 @@
use std::collections::HashMap;
use alloy_core::primitives::U256;
use serai_client::primitives::{NetworkId, Coin, Balance};
use serai_client::{
primitives::{NetworkId, Coin, Balance},
networks::ethereum::Address,
};
use serai_db::Db;
@@ -53,27 +58,86 @@ impl<D: Db> smart_contract_scheduler::SmartContract<Rpc<D>> for SmartContract {
fn fulfill(
&self,
nonce: u64,
mut nonce: u64,
_key: KeyFor<Rpc<D>>,
payments: Vec<Payment<AddressFor<Rpc<D>>>>,
) -> Vec<(Self::SignableTransaction, EventualityFor<Rpc<D>>)> {
let mut outs = Vec::with_capacity(payments.len());
// Sort by coin
let mut outs = HashMap::<_, _>::new();
for payment in payments {
outs.push((
payment.address().clone(),
(
coin_to_ethereum_coin(payment.balance().coin),
balance_to_ethereum_amount(payment.balance()),
),
));
let coin = payment.balance().coin;
outs
.entry(coin)
.or_insert_with(|| Vec::with_capacity(1))
.push((payment.address().clone(), balance_to_ethereum_amount(payment.balance())));
}
// TODO: Per-batch gas limit
// TODO: Create several batches
// TODO: Handle fees
let action = Action::Batch { chain_id: self.chain_id, nonce, outs };
let mut res = vec![];
for coin in [Coin::Ether, Coin::Dai] {
let Some(outs) = outs.remove(&coin) else { continue };
assert!(!outs.is_empty());
vec![(action.clone(), action.eventuality())]
let fee_per_gas: U256 = todo!("TODO");
// The gas required to perform any interaction with the Router.
const BASE_GAS: u32 = 0; // TODO
// The gas required to handle an additional payment to an address, in the worst case.
const ADDRESS_PAYMENT_GAS: u32 = 0; // TODO
// The gas required to handle an additional payment to an smart contract, in the worst case.
// This does not include the explicit gas budget defined within the address specification.
const CONTRACT_PAYMENT_GAS: u32 = 0; // TODO
// The maximum amount of gas for a batch.
const BATCH_GAS_LIMIT: u32 = 10_000_000;
// Split these outs into batches, respecting BATCH_GAS_LIMIT
let mut batches = vec![vec![]];
let mut current_gas = BASE_GAS;
for out in outs {
let payment_gas = match out.0 {
Address::Address(_) => ADDRESS_PAYMENT_GAS,
Address::Contract(deployment) => CONTRACT_PAYMENT_GAS + deployment.gas_limit(),
};
if (current_gas + payment_gas) > BATCH_GAS_LIMIT {
assert!(!batches.last().unwrap().is_empty());
batches.push(vec![]);
current_gas = BASE_GAS;
}
batches.last_mut().unwrap().push(out);
current_gas += payment_gas;
}
// Push each batch onto the result
for outs in batches {
let base_gas = BASE_GAS.div_ceil(u32::try_from(outs.len()).unwrap());
// Deduce the fee from each out
for out in &mut outs {
let payment_gas = base_gas +
match out.0 {
Address::Address(_) => ADDRESS_PAYMENT_GAS,
Address::Contract(deployment) => CONTRACT_PAYMENT_GAS + deployment.gas_limit(),
};
let payment_gas_cost = fee_per_gas * U256::try_from(payment_gas).unwrap();
out.1 -= payment_gas_cost;
}
res.push(Action::Batch {
chain_id: self.chain_id,
nonce,
coin: coin_to_ethereum_coin(coin),
fee_per_gas,
outs,
});
nonce += 1;
}
}
// Ensure we handled all payments we're supposed to
assert!(outs.is_empty());
res.into_iter().map(|action| (action.clone(), action.eventuality())).collect()
}
}