mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-08 12:19:24 +00:00
Smash ERC20 into its own library
This commit is contained in:
1
.github/workflows/tests.yml
vendored
1
.github/workflows/tests.yml
vendored
@@ -55,6 +55,7 @@ jobs:
|
|||||||
-p serai-processor-ethereum-contracts \
|
-p serai-processor-ethereum-contracts \
|
||||||
-p serai-processor-ethereum-primitives \
|
-p serai-processor-ethereum-primitives \
|
||||||
-p serai-processor-ethereum-deployer \
|
-p serai-processor-ethereum-deployer \
|
||||||
|
-p serai-processor-ethereum-erc20 \
|
||||||
-p ethereum-serai \
|
-p ethereum-serai \
|
||||||
-p serai-ethereum-processor \
|
-p serai-ethereum-processor \
|
||||||
-p serai-monero-processor \
|
-p serai-monero-processor \
|
||||||
|
|||||||
13
Cargo.lock
generated
13
Cargo.lock
generated
@@ -8737,6 +8737,19 @@ dependencies = [
|
|||||||
"serai-processor-ethereum-primitives",
|
"serai-processor-ethereum-primitives",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serai-processor-ethereum-erc20"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"alloy-core",
|
||||||
|
"alloy-provider",
|
||||||
|
"alloy-rpc-types-eth",
|
||||||
|
"alloy-simple-request-transport",
|
||||||
|
"alloy-sol-macro",
|
||||||
|
"alloy-sol-types",
|
||||||
|
"alloy-transport",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serai-processor-ethereum-primitives"
|
name = "serai-processor-ethereum-primitives"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|||||||
@@ -90,6 +90,7 @@ members = [
|
|||||||
"processor/ethereum/contracts",
|
"processor/ethereum/contracts",
|
||||||
"processor/ethereum/primitives",
|
"processor/ethereum/primitives",
|
||||||
"processor/ethereum/deployer",
|
"processor/ethereum/deployer",
|
||||||
|
"processor/ethereum/erc20",
|
||||||
"processor/ethereum/ethereum-serai",
|
"processor/ethereum/ethereum-serai",
|
||||||
"processor/ethereum",
|
"processor/ethereum",
|
||||||
"processor/monero",
|
"processor/monero",
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ exceptions = [
|
|||||||
{ allow = ["AGPL-3.0"], name = "serai-processor-ethereum-contracts" },
|
{ allow = ["AGPL-3.0"], name = "serai-processor-ethereum-contracts" },
|
||||||
{ allow = ["AGPL-3.0"], name = "serai-processor-ethereum-primitives" },
|
{ allow = ["AGPL-3.0"], name = "serai-processor-ethereum-primitives" },
|
||||||
{ allow = ["AGPL-3.0"], name = "serai-processor-ethereum-deployer" },
|
{ allow = ["AGPL-3.0"], name = "serai-processor-ethereum-deployer" },
|
||||||
|
{ allow = ["AGPL-3.0"], name = "serai-processor-ethereum-erc20" },
|
||||||
{ allow = ["AGPL-3.0"], name = "ethereum-serai" },
|
{ allow = ["AGPL-3.0"], name = "ethereum-serai" },
|
||||||
{ allow = ["AGPL-3.0"], name = "serai-ethereum-processor" },
|
{ allow = ["AGPL-3.0"], name = "serai-ethereum-processor" },
|
||||||
{ allow = ["AGPL-3.0"], name = "serai-monero-processor" },
|
{ allow = ["AGPL-3.0"], name = "serai-monero-processor" },
|
||||||
|
|||||||
28
processor/ethereum/erc20/Cargo.toml
Normal file
28
processor/ethereum/erc20/Cargo.toml
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
[package]
|
||||||
|
name = "serai-processor-ethereum-erc20"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "A library for the Serai Processor to interact with ERC20s"
|
||||||
|
license = "AGPL-3.0-only"
|
||||||
|
repository = "https://github.com/serai-dex/serai/tree/develop/processor/ethereum/erc20"
|
||||||
|
authors = ["Luke Parker <lukeparker5132@gmail.com>"]
|
||||||
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
rust-version = "1.79"
|
||||||
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
all-features = true
|
||||||
|
rustdoc-args = ["--cfg", "docsrs"]
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
alloy-core = { version = "0.8", 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 }
|
||||||
|
alloy-simple-request-transport = { path = "../../../networks/ethereum/alloy-simple-request-transport", default-features = false }
|
||||||
|
alloy-provider = { version = "0.3", default-features = false }
|
||||||
15
processor/ethereum/erc20/LICENSE
Normal file
15
processor/ethereum/erc20/LICENSE
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
AGPL-3.0-only license
|
||||||
|
|
||||||
|
Copyright (c) 2022-2024 Luke Parker
|
||||||
|
|
||||||
|
This program is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU Affero General Public License Version 3 as
|
||||||
|
published by the Free Software Foundation.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU Affero General Public License
|
||||||
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
3
processor/ethereum/erc20/README.md
Normal file
3
processor/ethereum/erc20/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# ERC20
|
||||||
|
|
||||||
|
A library for the Serai Processor to interact with ERC20s.
|
||||||
@@ -1,3 +1,7 @@
|
|||||||
|
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||||
|
#![doc = include_str!("../README.md")]
|
||||||
|
#![deny(missing_docs)]
|
||||||
|
|
||||||
use std::{sync::Arc, collections::HashSet};
|
use std::{sync::Arc, collections::HashSet};
|
||||||
|
|
||||||
use alloy_core::primitives::{Address, B256, U256};
|
use alloy_core::primitives::{Address, B256, U256};
|
||||||
@@ -5,18 +9,31 @@ use alloy_core::primitives::{Address, B256, U256};
|
|||||||
use alloy_sol_types::{SolInterface, SolEvent};
|
use alloy_sol_types::{SolInterface, SolEvent};
|
||||||
|
|
||||||
use alloy_rpc_types_eth::Filter;
|
use alloy_rpc_types_eth::Filter;
|
||||||
|
use alloy_transport::{TransportErrorKind, RpcError};
|
||||||
use alloy_simple_request_transport::SimpleRequest;
|
use alloy_simple_request_transport::SimpleRequest;
|
||||||
use alloy_provider::{Provider, RootProvider};
|
use alloy_provider::{Provider, RootProvider};
|
||||||
|
|
||||||
use crate::Error;
|
#[rustfmt::skip]
|
||||||
pub use crate::abi::erc20 as abi;
|
#[expect(warnings)]
|
||||||
use abi::{IERC20Calls, Transfer, transferCall, transferFromCall};
|
#[expect(needless_pass_by_value)]
|
||||||
|
#[expect(clippy::all)]
|
||||||
|
#[expect(clippy::ignored_unit_patterns)]
|
||||||
|
#[expect(clippy::redundant_closure_for_method_calls)]
|
||||||
|
mod abi {
|
||||||
|
alloy_sol_macro::sol!("contracts/IERC20.sol");
|
||||||
|
}
|
||||||
|
use abi::IERC20::{IERC20Calls, Transfer, transferCall, transferFromCall};
|
||||||
|
|
||||||
|
/// A top-level ERC20 transfer
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct TopLevelErc20Transfer {
|
pub struct TopLevelErc20Transfer {
|
||||||
|
/// The transaction ID which effected this transfer.
|
||||||
pub id: [u8; 32],
|
pub id: [u8; 32],
|
||||||
|
/// The address which made the transfer.
|
||||||
pub from: [u8; 20],
|
pub from: [u8; 20],
|
||||||
|
/// The amount transferred.
|
||||||
pub amount: U256,
|
pub amount: U256,
|
||||||
|
/// The data appended after the call itself.
|
||||||
pub data: Vec<u8>,
|
pub data: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -29,30 +46,43 @@ impl Erc20 {
|
|||||||
Self(provider, Address::from(&address))
|
Self(provider, Address::from(&address))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Fetch all top-level transfers to the specified ERC20.
|
||||||
pub async fn top_level_transfers(
|
pub async fn top_level_transfers(
|
||||||
&self,
|
&self,
|
||||||
block: u64,
|
block: u64,
|
||||||
to: [u8; 20],
|
to: [u8; 20],
|
||||||
) -> Result<Vec<TopLevelErc20Transfer>, Error> {
|
) -> Result<Vec<TopLevelErc20Transfer>, RpcError<TransportErrorKind>> {
|
||||||
let filter = Filter::new().from_block(block).to_block(block).address(self.1);
|
let filter = Filter::new().from_block(block).to_block(block).address(self.1);
|
||||||
let filter = filter.event_signature(Transfer::SIGNATURE_HASH);
|
let filter = filter.event_signature(Transfer::SIGNATURE_HASH);
|
||||||
let mut to_topic = [0; 32];
|
let mut to_topic = [0; 32];
|
||||||
to_topic[12 ..].copy_from_slice(&to);
|
to_topic[12 ..].copy_from_slice(&to);
|
||||||
let filter = filter.topic2(B256::from(to_topic));
|
let filter = filter.topic2(B256::from(to_topic));
|
||||||
let logs = self.0.get_logs(&filter).await.map_err(|_| Error::ConnectionError)?;
|
let logs = self.0.get_logs(&filter).await?;
|
||||||
|
|
||||||
|
/*
|
||||||
|
A set of all transactions we've handled a transfer from. This handles the edge case where a
|
||||||
|
top-level transfer T somehow triggers another transfer T', with equivalent contents, within
|
||||||
|
the same transaction. We only want to report one transfer as only one is top-level.
|
||||||
|
*/
|
||||||
let mut handled = HashSet::new();
|
let mut handled = HashSet::new();
|
||||||
|
|
||||||
let mut top_level_transfers = vec![];
|
let mut top_level_transfers = vec![];
|
||||||
for log in logs {
|
for log in logs {
|
||||||
// Double check the address which emitted this log
|
// Double check the address which emitted this log
|
||||||
if log.address() != self.1 {
|
if log.address() != self.1 {
|
||||||
Err(Error::ConnectionError)?;
|
Err(TransportErrorKind::Custom(
|
||||||
|
"node returned logs for a different address than requested".to_string().into(),
|
||||||
|
))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let tx_id = log.transaction_hash.ok_or(Error::ConnectionError)?;
|
let tx_id = log.transaction_hash.ok_or_else(|| {
|
||||||
let tx =
|
TransportErrorKind::Custom("log didn't specify its transaction hash".to_string().into())
|
||||||
self.0.get_transaction_by_hash(tx_id).await.ok().flatten().ok_or(Error::ConnectionError)?;
|
})?;
|
||||||
|
let tx = self.0.get_transaction_by_hash(tx_id).await?.ok_or_else(|| {
|
||||||
|
TransportErrorKind::Custom(
|
||||||
|
"node didn't have the transaction which emitted a log it had".to_string().into(),
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
// If this is a top-level call...
|
// If this is a top-level call...
|
||||||
if tx.to == Some(self.1) {
|
if tx.to == Some(self.1) {
|
||||||
@@ -70,7 +100,13 @@ impl Erc20 {
|
|||||||
_ => continue,
|
_ => continue,
|
||||||
};
|
};
|
||||||
|
|
||||||
let log = log.log_decode::<Transfer>().map_err(|_| Error::ConnectionError)?.inner.data;
|
let log = log
|
||||||
|
.log_decode::<Transfer>()
|
||||||
|
.map_err(|e| {
|
||||||
|
TransportErrorKind::Custom(format!("failed to decode Transfer log: {e:?}").into())
|
||||||
|
})?
|
||||||
|
.inner
|
||||||
|
.data;
|
||||||
|
|
||||||
// Ensure the top-level transfer is equivalent, and this presumably isn't a log for an
|
// Ensure the top-level transfer is equivalent, and this presumably isn't a log for an
|
||||||
// internal transfer
|
// internal transfer
|
||||||
Reference in New Issue
Block a user