From 433beac93a320628f55e01fc40ce6ec9cc6f03dc Mon Sep 17 00:00:00 2001 From: Luke Parker Date: Wed, 18 Sep 2024 00:54:20 -0400 Subject: [PATCH] Ethereum SignableTransaction, Eventuality --- processor/ethereum/Cargo.toml | 17 +- processor/ethereum/router/Cargo.toml | 1 - processor/ethereum/router/src/lib.rs | 48 ++- processor/ethereum/src/key_gen.rs | 2 +- processor/ethereum/src/main.rs | 10 +- processor/ethereum/src/primitives/block.rs | 39 ++- processor/ethereum/src/primitives/mod.rs | 6 + processor/ethereum/src/primitives/output.rs | 14 +- .../ethereum/src/primitives/transaction.rs | 297 +++++++++++++++--- processor/ethereum/src/rpc.rs | 10 +- processor/ethereum/src/scheduler.rs | 75 ++--- 11 files changed, 390 insertions(+), 129 deletions(-) diff --git a/processor/ethereum/Cargo.toml b/processor/ethereum/Cargo.toml index dfed2f9d..9a3b264c 100644 --- a/processor/ethereum/Cargo.toml +++ b/processor/ethereum/Cargo.toml @@ -26,10 +26,18 @@ borsh = { version = "1", default-features = false, features = ["std", "derive", ciphersuite = { path = "../../crypto/ciphersuite", default-features = false, features = ["std", "secp256k1"] } dkg = { path = "../../crypto/dkg", default-features = false, features = ["std", "evrf-secp256k1"] } -frost = { package = "modular-frost", path = "../../crypto/frost", default-features = false } +frost = { package = "modular-frost", path = "../../crypto/frost", default-features = false, features = ["secp256k1"] } k256 = { version = "^0.13.1", default-features = false, features = ["std"] } -ethereum-serai = { path = "./ethereum-serai", default-features = false, optional = true } + +alloy-core = { version = "0.8", default-features = false } +alloy-rlp = { version = "0.3", default-features = false } +alloy-consensus = { version = "0.3", default-features = false } + +alloy-rpc-types-eth = { version = "0.3", default-features = false } +alloy-simple-request-transport = { path = "../../networks/ethereum/alloy-simple-request-transport", default-features = false } +alloy-rpc-client = { version = "0.3", default-features = false } +alloy-provider = { version = "0.3", default-features = false } serai-client = { path = "../../substrate/client", default-features = false, features = ["ethereum"] } @@ -48,6 +56,11 @@ scanner = { package = "serai-processor-scanner", path = "../scanner" } smart-contract-scheduler = { package = "serai-processor-smart-contract-scheduler", path = "../scheduler/smart-contract" } signers = { package = "serai-processor-signers", path = "../signers" } +ethereum-schnorr = { package = "ethereum-schnorr-contract", path = "../../networks/ethereum/schnorr" } +ethereum-primitives = { package = "serai-processor-ethereum-primitives", path = "./primitives" } +ethereum-router = { package = "serai-processor-ethereum-router", path = "./router" } +ethereum-erc20 = { package = "serai-processor-ethereum-erc20", path = "./erc20" } + bin = { package = "serai-processor-bin", path = "../bin" } [features] diff --git a/processor/ethereum/router/Cargo.toml b/processor/ethereum/router/Cargo.toml index ed5417c0..e8884eae 100644 --- a/processor/ethereum/router/Cargo.toml +++ b/processor/ethereum/router/Cargo.toml @@ -24,7 +24,6 @@ alloy-core = { version = "0.8", default-features = false } alloy-consensus = { version = "0.3", default-features = false } alloy-sol-types = { version = "0.8", default-features = false } -alloy-sol-macro = { version = "0.8", default-features = false } alloy-rpc-types-eth = { version = "0.3", default-features = false } alloy-transport = { version = "0.3", default-features = false } diff --git a/processor/ethereum/router/src/lib.rs b/processor/ethereum/router/src/lib.rs index 18bc3d4b..344e2bee 100644 --- a/processor/ethereum/router/src/lib.rs +++ b/processor/ethereum/router/src/lib.rs @@ -27,7 +27,7 @@ use ethereum_schnorr::{PublicKey, Signature}; use ethereum_deployer::Deployer; use erc20::{Transfer, Erc20}; -use serai_client::{primitives::Amount, networks::ethereum::Address as SeraiAddress}; +use serai_client::networks::ethereum::Address as SeraiAddress; #[rustfmt::skip] #[expect(warnings)] @@ -159,8 +159,8 @@ impl InInstruction { /// A list of `OutInstruction`s. #[derive(Clone)] pub struct OutInstructions(Vec); -impl From<&[(SeraiAddress, (Coin, Amount))]> for OutInstructions { - fn from(outs: &[(SeraiAddress, (Coin, Amount))]) -> Self { +impl From<&[(SeraiAddress, (Coin, U256))]> for OutInstructions { + fn from(outs: &[(SeraiAddress, (Coin, U256))]) -> Self { Self( outs .iter() @@ -184,7 +184,7 @@ impl From<&[(SeraiAddress, (Coin, Amount))]> for OutInstructions { Coin::Ether => [0; 20].into(), Coin::Erc20(address) => address.into(), }, - value: amount.0.try_into().expect("couldn't convert u64 to u256"), + value: *amount, } }) .collect(), @@ -192,7 +192,7 @@ impl From<&[(SeraiAddress, (Coin, Amount))]> for OutInstructions { } } -/// Executed an command. +/// An action which was executed by the Router. #[derive(Clone, PartialEq, Eq, Debug)] pub enum Executed { /// Set a new key. @@ -218,6 +218,44 @@ impl Executed { Executed::SetKey { nonce, .. } | Executed::Batch { nonce, .. } => *nonce, } } + + /// Write the Executed. + pub fn write(&self, writer: &mut impl io::Write) -> io::Result<()> { + match self { + Self::SetKey { nonce, key } => { + writer.write_all(&[0])?; + writer.write_all(&nonce.to_le_bytes())?; + writer.write_all(key) + } + Self::Batch { nonce, message_hash } => { + writer.write_all(&[1])?; + writer.write_all(&nonce.to_le_bytes())?; + writer.write_all(message_hash) + } + } + } + + /// Read an Executed. + pub fn read(reader: &mut impl io::Read) -> io::Result { + let mut kind = [0xff]; + reader.read_exact(&mut kind)?; + if kind[0] >= 2 { + Err(io::Error::other("unrecognized type of Executed"))?; + } + + let mut nonce = [0; 8]; + reader.read_exact(&mut nonce)?; + let nonce = u64::from_le_bytes(nonce); + + let mut payload = [0; 32]; + reader.read_exact(&mut payload)?; + + Ok(match kind[0] { + 0 => Self::SetKey { nonce, key: payload }, + 1 => Self::Batch { nonce, message_hash: payload }, + _ => unreachable!(), + }) + } } /// A view of the Router for Serai. diff --git a/processor/ethereum/src/key_gen.rs b/processor/ethereum/src/key_gen.rs index 73b7c1e1..581684ef 100644 --- a/processor/ethereum/src/key_gen.rs +++ b/processor/ethereum/src/key_gen.rs @@ -1,7 +1,7 @@ use ciphersuite::{Ciphersuite, Secp256k1}; use dkg::ThresholdKeys; -use ethereum_serai::crypto::PublicKey; +use ethereum_schnorr::PublicKey; pub(crate) struct KeyGenParams; impl key_gen::KeyGenParams for KeyGenParams { diff --git a/processor/ethereum/src/main.rs b/processor/ethereum/src/main.rs index e4ec3701..06c0bc98 100644 --- a/processor/ethereum/src/main.rs +++ b/processor/ethereum/src/main.rs @@ -8,12 +8,10 @@ static ALLOCATOR: zalloc::ZeroizingAlloc = use std::sync::Arc; -use ethereum_serai::alloy::{ - primitives::U256, - simple_request_transport::SimpleRequest, - rpc_client::ClientBuilder, - provider::{Provider, RootProvider}, -}; +use alloy_core::primitives::U256; +use alloy_simple_request_transport::SimpleRequest; +use alloy_rpc_client::ClientBuilder; +use alloy_provider::{Provider, RootProvider}; use serai_env as env; diff --git a/processor/ethereum/src/primitives/block.rs b/processor/ethereum/src/primitives/block.rs index e947e851..2c0e0505 100644 --- a/processor/ethereum/src/primitives/block.rs +++ b/processor/ethereum/src/primitives/block.rs @@ -5,6 +5,9 @@ use ciphersuite::{Ciphersuite, Secp256k1}; use serai_client::networks::ethereum::Address; use primitives::{ReceivedOutput, EventualityTracker}; + +use ethereum_router::Executed; + use crate::{output::Output, transaction::Eventuality}; // We interpret 32-block Epochs as singular blocks. @@ -37,9 +40,11 @@ impl primitives::BlockHeader for Epoch { } } -#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[derive(Clone, PartialEq, Eq, Debug)] pub(crate) struct FullEpoch { epoch: Epoch, + outputs: Vec, + executed: Vec, } impl primitives::Block for FullEpoch { @@ -54,7 +59,8 @@ impl primitives::Block for FullEpoch { self.epoch.end_hash } - fn scan_for_outputs_unordered(&self, key: Self::Key) -> Vec { + fn scan_for_outputs_unordered(&self, _key: Self::Key) -> Vec { + // Only return these outputs for the latest key todo!("TODO") } @@ -66,6 +72,33 @@ impl primitives::Block for FullEpoch { >::TransactionId, Self::Eventuality, > { - todo!("TODO") + let mut res = HashMap::new(); + for executed in &self.executed { + let Some(expected) = + eventualities.active_eventualities.remove(executed.nonce().to_le_bytes().as_slice()) + else { + continue; + }; + assert_eq!( + executed, + &expected.0, + "Router emitted distinct event for nonce {}", + executed.nonce() + ); + /* + The transaction ID is used to determine how internal outputs from this transaction should + be handled (if they were actually internal or if they were just to an internal address). + The Ethereum integration doesn't have internal addresses, and this transaction wasn't made + by Serai. It was simply authorized by Serai yet may or may not be associated with other + actions we don't want to flag as our own. + + Accordingly, we set the transaction ID to the nonce. This is unique barring someone finding + the preimage which hashes to this nonce, and won't cause any other data to be associated. + */ + let mut tx_id = [0; 32]; + tx_id[.. 8].copy_from_slice(executed.nonce().to_le_bytes().as_slice()); + res.insert(tx_id, expected); + } + res } } diff --git a/processor/ethereum/src/primitives/mod.rs b/processor/ethereum/src/primitives/mod.rs index fba52dd9..8d2a9118 100644 --- a/processor/ethereum/src/primitives/mod.rs +++ b/processor/ethereum/src/primitives/mod.rs @@ -1,3 +1,9 @@ pub(crate) mod output; pub(crate) mod transaction; pub(crate) mod block; + +pub(crate) const DAI: [u8; 20] = + match const_hex::const_decode_to_array(b"0x6B175474E89094C44Da98b954EedeAC495271d0F") { + Ok(res) => res, + Err(_) => panic!("invalid non-test DAI hex address"), + }; diff --git a/processor/ethereum/src/primitives/output.rs b/processor/ethereum/src/primitives/output.rs index 4dadb147..843f22f6 100644 --- a/processor/ethereum/src/primitives/output.rs +++ b/processor/ethereum/src/primitives/output.rs @@ -2,10 +2,7 @@ use std::io; use ciphersuite::{Ciphersuite, Secp256k1}; -use ethereum_serai::{ - alloy::primitives::U256, - router::{Coin as EthereumCoin, InInstruction as EthereumInInstruction}, -}; +use alloy_core::primitives::U256; use scale::{Encode, Decode}; use borsh::{BorshSerialize, BorshDeserialize}; @@ -16,12 +13,9 @@ use serai_client::{ }; use primitives::{OutputType, ReceivedOutput}; +use ethereum_router::{Coin as EthereumCoin, InInstruction as EthereumInInstruction}; -const DAI: [u8; 20] = - match const_hex::const_decode_to_array(b"0x6B175474E89094C44Da98b954EedeAC495271d0F") { - Ok(res) => res, - Err(_) => panic!("invalid non-test DAI hex address"), - }; +use crate::DAI; fn coin_to_serai_coin(coin: &EthereumCoin) -> Option { match coin { @@ -87,7 +81,7 @@ impl ReceivedOutput<::G, Address> for Output { } fn key(&self) -> ::G { - self.0.key_at_end_of_block + todo!("TODO") } fn presumed_origin(&self) -> Option
{ diff --git a/processor/ethereum/src/primitives/transaction.rs b/processor/ethereum/src/primitives/transaction.rs index 908358ec..f77153ff 100644 --- a/processor/ethereum/src/primitives/transaction.rs +++ b/processor/ethereum/src/primitives/transaction.rs @@ -1,101 +1,304 @@ -use std::io; +use std::{io, collections::HashMap}; use rand_core::{RngCore, CryptoRng}; -use ciphersuite::{group::GroupEncoding, Ciphersuite, Secp256k1}; -use frost::{dkg::ThresholdKeys, sign::PreprocessMachine}; +use ciphersuite::{Ciphersuite, Secp256k1}; +use frost::{ + dkg::{Participant, ThresholdKeys}, + FrostError, + algorithm::*, + sign::*, +}; -use ethereum_serai::{crypto::PublicKey, machine::*}; +use alloy_core::primitives::U256; + +use serai_client::networks::ethereum::Address; + +use scheduler::SignableTransaction; + +use ethereum_primitives::keccak256; +use ethereum_schnorr::{PublicKey, Signature}; +use ethereum_router::{Coin, OutInstructions, Executed, Router}; use crate::output::OutputId; -#[derive(Clone, Debug)] -pub(crate) struct Transaction(pub(crate) SignedRouterCommand); +#[derive(Clone, PartialEq, Debug)] +pub(crate) enum Action { + SetKey { chain_id: U256, nonce: u64, key: PublicKey }, + Batch { chain_id: U256, nonce: u64, outs: Vec<(Address, (Coin, U256))> }, +} -impl From for Transaction { - fn from(signed_router_command: SignedRouterCommand) -> Self { - Self(signed_router_command) +#[derive(Clone, PartialEq, Eq, Debug)] +pub(crate) struct Eventuality(pub(crate) Executed); + +impl Action { + fn nonce(&self) -> u64 { + match self { + Action::SetKey { nonce, .. } | Action::Batch { nonce, .. } => *nonce, + } + } + + fn message(&self) -> Vec { + match self { + Action::SetKey { chain_id, nonce, key } => Router::update_serai_key_message(*chain_id, *nonce, key), + Action::Batch { chain_id, nonce, outs } => Router::execute_message(*chain_id, *nonce, OutInstructions::from(outs.as_ref())), + } + } + + pub(crate) fn eventuality(&self) -> Eventuality { + Eventuality(match self { + Self::SetKey { chain_id: _, nonce, key } => { + Executed::SetKey { nonce: *nonce, key: key.eth_repr() } + } + Self::Batch { chain_id, nonce, outs } => Executed::Batch { + nonce: *nonce, + message_hash: keccak256(Router::execute_message( + *chain_id, + *nonce, + OutInstructions::from(outs.as_ref()), + )), + }, + }) } } +#[derive(Clone, PartialEq, Debug)] +pub(crate) struct Transaction(Action, Signature); impl scheduler::Transaction for Transaction { fn read(reader: &mut impl io::Read) -> io::Result { - SignedRouterCommand::read(reader).map(Self) + /* + let buf: Vec = borsh::from_reader(reader)?; + // We can only read this from a &[u8], hence prior reading into a Vec + ::decode(&mut buf.as_slice()) + .map(Self) + .map_err(io::Error::other) + */ + let action = Action::read(reader)?; + let signature = Signature::read(reader)?; + Ok(Transaction(action, signature)) } fn write(&self, writer: &mut impl io::Write) -> io::Result<()> { - self.0.write(writer) + /* + let mut buf = Vec::with_capacity(256); + ::encode(&self.0, &mut buf); + borsh::BorshSerialize::serialize(&buf, writer) + */ + self.0.write(writer)?; + self.1.write(writer)?; + Ok(()) } } -#[derive(Clone, Debug)] -pub(crate) struct SignableTransaction(pub(crate) RouterCommand); +/// The HRAm to use for the Schnorr Solidity library. +/// +/// This will panic if the public key being signed for is not representable within the Schnorr +/// Solidity library. +#[derive(Clone, Default, Debug)] +pub struct EthereumHram; +impl Hram for EthereumHram { + #[allow(non_snake_case)] + fn hram( + R: &::G, + A: &::G, + m: &[u8], + ) -> ::F { + Signature::challenge(*R, &PublicKey::new(*A).unwrap(), m) + } +} #[derive(Clone)] -pub(crate) struct ClonableTransctionMachine(RouterCommand, ThresholdKeys); +pub(crate) struct ClonableTransctionMachine(ThresholdKeys, Action); + +type LiteralAlgorithmMachine = AlgorithmMachine>; +type LiteralAlgorithmSignMachine = + AlgorithmSignMachine>; + +pub(crate) struct ActionSignMachine(PublicKey, Action, LiteralAlgorithmSignMachine); + +type LiteralAlgorithmSignatureMachine = + AlgorithmSignatureMachine>; + +pub(crate) struct ActionSignatureMachine(PublicKey, Action, LiteralAlgorithmSignatureMachine); + impl PreprocessMachine for ClonableTransctionMachine { - type Preprocess = ::Preprocess; - type Signature = ::Signature; - type SignMachine = ::SignMachine; + type Preprocess = ::Preprocess; + type Signature = Transaction; + type SignMachine = ActionSignMachine; fn preprocess( self, rng: &mut R, ) -> (Self::SignMachine, Self::Preprocess) { - // TODO: Use a proper error here, not an Option - RouterCommandMachine::new(self.1.clone(), self.0.clone()).unwrap().preprocess(rng) + let (machine, preprocess) = AlgorithmMachine::new(IetfSchnorr::::ietf(), self.0.clone()) + .preprocess(rng); + (ActionSignMachine(PublicKey::new(self.0.group_key()).expect("signing with non-representable key"), self.1, machine), preprocess) } } -impl scheduler::SignableTransaction for SignableTransaction { +impl SignMachine for ActionSignMachine { + type Params = ::Signature, + >>::Params; + type Keys = ::Signature, + >>::Keys; + type Preprocess = ::Signature, + >>::Preprocess; + type SignatureShare = ::Signature, + >>::SignatureShare; + type SignatureMachine = ActionSignatureMachine; + + fn cache(self) -> CachedPreprocess { + unimplemented!() + } + fn from_cache( + params: Self::Params, + keys: Self::Keys, + cache: CachedPreprocess, +) -> (Self, Self::Preprocess) { + unimplemented!() + } + + fn read_preprocess(&self, reader: &mut R) -> io::Result { + self.2.read_preprocess(reader) + } + fn sign( + self, + commitments: HashMap, + msg: &[u8], + ) -> Result<(Self::SignatureMachine, Self::SignatureShare), FrostError> { + assert!(msg.is_empty()); + self + .2 + .sign(commitments, &self.1.message()) + .map(|(machine, shares)| (ActionSignatureMachine(self.0, self.1, machine), shares)) + } +} + +impl SignatureMachine for ActionSignatureMachine { + type SignatureShare = ::Signature, + >>::SignatureShare; + + fn read_share(&self, reader: &mut R) -> io::Result { + self.2.read_share(reader) + } + + fn complete( + self, + shares: HashMap, + ) -> Result { + /* + match self.1 { + Action::SetKey { chain_id: _, nonce: _, key } => self.0.update_serai_key(key, signature), + Action::Batch { chain_id: _, nonce: _, outs } => self.0.execute(outs, signature), + } + */ + self.2.complete(shares).map(|signature| { + let s = signature.s; + let c = Signature::challenge(signature.R, &self.0, &self.1.message()); + Transaction(self.1, Signature::new(c, s)) + }) + } +} + +impl SignableTransaction for Action { type Transaction = Transaction; type Ciphersuite = Secp256k1; type PreprocessMachine = ClonableTransctionMachine; fn read(reader: &mut impl io::Read) -> io::Result { - RouterCommand::read(reader).map(Self) + let mut kind = [0xff]; + reader.read_exact(&mut kind)?; + if kind[0] >= 2 { + Err(io::Error::other("unrecognized Action type"))?; + } + + let mut chain_id = [0; 32]; + reader.read_exact(&mut chain_id)?; + let chain_id = U256::from_le_bytes(chain_id); + + let mut nonce = [0; 8]; + reader.read_exact(&mut nonce)?; + let nonce = u64::from_le_bytes(nonce); + + Ok(match kind[0] { + 0 => { + let mut key = [0; 32]; + reader.read_exact(&mut key)?; + let key = + PublicKey::from_eth_repr(key).ok_or_else(|| io::Error::other("invalid key in Action"))?; + + Action::SetKey { chain_id, nonce, key } + } + 1 => { + let mut outs_len = [0; 4]; + reader.read_exact(&mut outs_len)?; + let outs_len = usize::try_from(u32::from_le_bytes(outs_len)).unwrap(); + + let mut outs = vec![]; + for _ in 0 .. outs_len { + let address = borsh::from_reader(reader)?; + let coin = Coin::read(reader)?; + + let mut amount = [0; 32]; + reader.read_exact(&mut amount)?; + let amount = U256::from_le_bytes(amount); + + outs.push((address, (coin, amount))); + } + Action::Batch { chain_id, nonce, outs } + } + _ => unreachable!(), + }) } fn write(&self, writer: &mut impl io::Write) -> io::Result<()> { - self.0.write(writer) + match self { + Self::SetKey { chain_id, nonce, key } => { + writer.write_all(&[0])?; + writer.write_all(&chain_id.as_le_bytes())?; + writer.write_all(&nonce.to_le_bytes())?; + writer.write_all(&key.eth_repr()) + } + Self::Batch { chain_id, nonce, outs } => { + writer.write_all(&[1])?; + writer.write_all(&chain_id.as_le_bytes())?; + writer.write_all(&nonce.to_le_bytes())?; + writer.write_all(&u32::try_from(outs.len()).unwrap().to_le_bytes())?; + for (address, (coin, amount)) in outs { + borsh::BorshSerialize::serialize(address, writer)?; + coin.write(writer)?; + writer.write_all(&amount.as_le_bytes())?; + } + Ok(()) + } + } } fn id(&self) -> [u8; 32] { let mut res = [0; 32]; - // TODO: Add getter for the nonce - match self.0 { - RouterCommand::UpdateSeraiKey { nonce, .. } | RouterCommand::Execute { nonce, .. } => { - res[.. 8].copy_from_slice(&nonce.as_le_bytes()); - } - } + res[.. 8].copy_from_slice(&self.nonce().to_le_bytes()); res } fn sign(self, keys: ThresholdKeys) -> Self::PreprocessMachine { - ClonableTransctionMachine(self.0, keys) + ClonableTransctionMachine(keys, self) } } -#[derive(Clone, PartialEq, Eq, Debug)] -pub(crate) struct Eventuality(pub(crate) PublicKey, pub(crate) RouterCommand); - impl primitives::Eventuality for Eventuality { type OutputId = OutputId; fn id(&self) -> [u8; 32] { let mut res = [0; 32]; - match self.1 { - RouterCommand::UpdateSeraiKey { nonce, .. } | RouterCommand::Execute { nonce, .. } => { - res[.. 8].copy_from_slice(&nonce.as_le_bytes()); - } - } + res[.. 8].copy_from_slice(&self.0.nonce().to_le_bytes()); res } fn lookup(&self) -> Vec { - match self.1 { - RouterCommand::UpdateSeraiKey { nonce, .. } | RouterCommand::Execute { nonce, .. } => { - nonce.as_le_bytes().to_vec() - } - } + self.0.nonce().to_le_bytes().to_vec() } fn singular_spent_output(&self) -> Option { @@ -103,15 +306,9 @@ impl primitives::Eventuality for Eventuality { } fn read(reader: &mut impl io::Read) -> io::Result { - let point = Secp256k1::read_G(reader)?; - let command = RouterCommand::read(reader)?; - Ok(Eventuality( - PublicKey::new(point).ok_or(io::Error::other("unusable key within Eventuality"))?, - command, - )) + Executed::read(reader).map(Self) } fn write(&self, writer: &mut impl io::Write) -> io::Result<()> { - writer.write_all(self.0.point().to_bytes().as_slice())?; - self.1.write(writer) + self.0.write(writer) } } diff --git a/processor/ethereum/src/rpc.rs b/processor/ethereum/src/rpc.rs index 58b3933e..819fbf48 100644 --- a/processor/ethereum/src/rpc.rs +++ b/processor/ethereum/src/rpc.rs @@ -1,13 +1,9 @@ use core::future::Future; use std::sync::Arc; -use ethereum_serai::{ - alloy::{ - rpc_types::{BlockTransactionsKind, BlockNumberOrTag}, - simple_request_transport::SimpleRequest, - provider::{Provider, RootProvider}, - }, -}; +use alloy_rpc_types_eth::{BlockTransactionsKind, BlockNumberOrTag}; +use alloy_simple_request_transport::SimpleRequest; +use alloy_provider::{Provider, RootProvider}; use serai_client::primitives::{NetworkId, Coin, Amount}; diff --git a/processor/ethereum/src/scheduler.rs b/processor/ethereum/src/scheduler.rs index 6e17ef70..ca636b5b 100644 --- a/processor/ethereum/src/scheduler.rs +++ b/processor/ethereum/src/scheduler.rs @@ -1,14 +1,23 @@ -use serai_client::primitives::{NetworkId, Balance}; +use alloy_core::primitives::U256; -use ethereum_serai::{alloy::primitives::U256, router::PublicKey, machine::*}; +use serai_client::primitives::{NetworkId, Coin, Balance}; use primitives::Payment; use scanner::{KeyFor, AddressFor, EventualityFor}; -use crate::{ - transaction::{SignableTransaction, Eventuality}, - rpc::Rpc, -}; +use ethereum_schnorr::PublicKey; +use ethereum_router::Coin as EthereumCoin; + +use crate::{DAI, transaction::Action, rpc::Rpc}; + +fn coin_to_ethereum_coin(coin: Coin) -> EthereumCoin { + assert_eq!(coin.network(), NetworkId::Ethereum); + match coin { + Coin::Ether => EthereumCoin::Ether, + Coin::Dai => EthereumCoin::Erc20(DAI), + _ => unreachable!(), + } +} fn balance_to_ethereum_amount(balance: Balance) -> U256 { assert_eq!(balance.coin.network(), NetworkId::Ethereum); @@ -24,7 +33,7 @@ pub(crate) struct SmartContract { pub(crate) chain_id: U256, } impl smart_contract_scheduler::SmartContract for SmartContract { - type SignableTransaction = SignableTransaction; + type SignableTransaction = Action; fn rotate( &self, @@ -32,16 +41,14 @@ impl smart_contract_scheduler::SmartContract for SmartContract { retiring_key: KeyFor, new_key: KeyFor, ) -> (Self::SignableTransaction, EventualityFor) { - let command = RouterCommand::UpdateSeraiKey { + let action = Action::SetKey { chain_id: self.chain_id, - nonce: U256::try_from(nonce).unwrap(), + nonce, key: PublicKey::new(new_key).expect("rotating to an invald key"), }; - ( - SignableTransaction(command.clone()), - Eventuality(PublicKey::new(retiring_key).expect("retiring an invalid key"), command), - ) + (action.clone(), action.eventuality()) } + fn fulfill( &self, nonce: u64, @@ -50,40 +57,20 @@ impl smart_contract_scheduler::SmartContract for SmartContract { ) -> Vec<(Self::SignableTransaction, EventualityFor)> { let mut outs = Vec::with_capacity(payments.len()); for payment in payments { - outs.push(OutInstruction { - target: if let Some(data) = payment.data() { - // This introspects the Call serialization format, expecting the first 20 bytes to - // be the address - // This avoids wasting the 20-bytes allocated within address - let full_data = [<[u8; 20]>::from(*payment.address()).as_slice(), data].concat(); - let mut reader = full_data.as_slice(); - - let mut calls = vec![]; - while !reader.is_empty() { - let Ok(call) = Call::read(&mut reader) else { break }; - calls.push(call); - } - // The above must have executed at least once since reader contains the address - assert_eq!(calls[0].to, <[u8; 20]>::from(*payment.address())); - - OutInstructionTarget::Calls(calls) - } else { - OutInstructionTarget::Direct((*payment.address()).into()) - }, - value: { balance_to_ethereum_amount(payment.balance()) }, - }); + outs.push(( + payment.address().clone(), + ( + coin_to_ethereum_coin(payment.balance().coin), + balance_to_ethereum_amount(payment.balance()), + ), + )); } - let command = RouterCommand::Execute { - chain_id: self.chain_id, - nonce: U256::try_from(nonce).unwrap(), - outs, - }; + // TODO: Per-batch gas limit + // TODO: Create several batches + let action = Action::Batch { chain_id: self.chain_id, nonce, outs }; - vec![( - SignableTransaction(command.clone()), - Eventuality(PublicKey::new(key).expect("fulfilling payments with an invalid key"), command), - )] + vec![(action.clone(), action.eventuality())] } }