2021-10-21 16:51:01 +00:00
|
|
|
use std::fs;
|
2021-11-13 17:37:31 +00:00
|
|
|
use std::path::Path;
|
2021-10-21 16:51:01 +00:00
|
|
|
|
2022-01-30 16:18:05 +00:00
|
|
|
use web3::{
|
|
|
|
api::Web3,
|
2022-06-28 23:02:31 +00:00
|
|
|
contract::{Contract, Error as ContractError, Options},
|
2022-06-29 14:35:13 +00:00
|
|
|
ethabi,
|
2022-01-30 16:18:05 +00:00
|
|
|
transports::Http,
|
|
|
|
};
|
|
|
|
|
|
|
|
use crate::config::BlockchainConfig;
|
|
|
|
use super::api::connect;
|
|
|
|
use super::errors::EthereumError;
|
2022-06-14 18:55:04 +00:00
|
|
|
use super::sync::{
|
|
|
|
get_current_block_number,
|
2022-06-24 23:27:09 +00:00
|
|
|
SyncState,
|
2022-06-14 18:55:04 +00:00
|
|
|
};
|
2022-01-30 16:18:05 +00:00
|
|
|
use super::utils::parse_address;
|
|
|
|
|
2022-06-28 23:02:31 +00:00
|
|
|
const ERC165: &str = "IERC165";
|
|
|
|
const GATE: &str = "IGate";
|
|
|
|
const MINTER: &str = "IMinter";
|
|
|
|
const SUBSCRIPTION_ADAPTER: &str = "ISubscriptionAdapter";
|
2022-06-23 20:48:05 +00:00
|
|
|
const SUBSCRIPTION: &str = "ISubscription";
|
|
|
|
const ERC721: &str = "IERC721Metadata";
|
2021-10-21 16:51:01 +00:00
|
|
|
|
|
|
|
#[derive(thiserror::Error, Debug)]
|
|
|
|
pub enum ArtifactError {
|
|
|
|
#[error("io error")]
|
|
|
|
IoError(#[from] std::io::Error),
|
|
|
|
|
|
|
|
#[error("json error")]
|
|
|
|
JsonError(#[from] serde_json::Error),
|
|
|
|
|
|
|
|
#[error("key error")]
|
|
|
|
KeyError,
|
2022-06-29 14:35:13 +00:00
|
|
|
|
|
|
|
#[error(transparent)]
|
|
|
|
AbiError(#[from] ethabi::Error),
|
2021-10-21 16:51:01 +00:00
|
|
|
}
|
|
|
|
|
2022-06-23 20:48:05 +00:00
|
|
|
fn load_abi(
|
2021-11-13 17:37:31 +00:00
|
|
|
contract_dir: &Path,
|
2021-10-21 16:51:01 +00:00
|
|
|
contract_name: &str,
|
2022-06-29 14:35:13 +00:00
|
|
|
) -> Result<ethabi::Contract, ArtifactError> {
|
|
|
|
let artifact_path = contract_dir.join(format!("{}.json", contract_name));
|
|
|
|
let artifact = fs::read_to_string(artifact_path)?;
|
|
|
|
let artifact_value: serde_json::Value =
|
|
|
|
serde_json::from_str(&artifact)?;
|
|
|
|
let abi_json = artifact_value.get("abi")
|
2021-10-21 16:51:01 +00:00
|
|
|
.ok_or(ArtifactError::KeyError)?
|
2022-06-29 14:35:13 +00:00
|
|
|
.to_string();
|
|
|
|
let abi = ethabi::Contract::load(abi_json.as_bytes())?;
|
|
|
|
Ok(abi)
|
2021-10-21 16:51:01 +00:00
|
|
|
}
|
2022-01-30 16:18:05 +00:00
|
|
|
|
2022-06-28 23:02:31 +00:00
|
|
|
// https://eips.ethereum.org/EIPS/eip-165
|
|
|
|
// Interface identifier is the XOR of all function selectors in the interface
|
|
|
|
fn interface_signature(interface: ðabi::Contract) -> [u8; 4] {
|
|
|
|
interface.functions()
|
|
|
|
.map(|func| func.short_signature())
|
|
|
|
.fold([0; 4], |mut acc, item| {
|
|
|
|
for i in 0..4 {
|
|
|
|
acc[i] ^= item[i];
|
|
|
|
};
|
|
|
|
acc
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns true if contract supports interface (per ERC-165)
|
|
|
|
async fn is_interface_supported(
|
|
|
|
contract: &Contract<Http>,
|
|
|
|
interface: ðabi::Contract,
|
|
|
|
) -> Result<bool, ContractError> {
|
|
|
|
let signature = interface_signature(interface);
|
|
|
|
contract.query(
|
|
|
|
"supportsInterface",
|
|
|
|
(signature,), None, Options::default(), None,
|
|
|
|
).await
|
|
|
|
}
|
|
|
|
|
2022-06-23 20:48:05 +00:00
|
|
|
#[derive(Clone)]
|
2022-01-30 16:18:05 +00:00
|
|
|
pub struct ContractSet {
|
|
|
|
pub web3: Web3<Http>,
|
|
|
|
|
2022-06-28 23:02:31 +00:00
|
|
|
pub gate: Option<Contract<Http>>,
|
|
|
|
pub collectible: Option<Contract<Http>>,
|
|
|
|
pub subscription: Option<Contract<Http>>,
|
2022-01-30 16:18:05 +00:00
|
|
|
}
|
|
|
|
|
2022-06-24 23:27:09 +00:00
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct Blockchain {
|
|
|
|
pub contract_set: ContractSet,
|
|
|
|
pub sync_state: SyncState,
|
|
|
|
}
|
|
|
|
|
2022-01-30 16:18:05 +00:00
|
|
|
pub async fn get_contracts(
|
|
|
|
config: &BlockchainConfig,
|
2022-06-14 18:55:04 +00:00
|
|
|
storage_dir: &Path,
|
2022-06-24 23:27:09 +00:00
|
|
|
) -> Result<Blockchain, EthereumError> {
|
2022-01-30 16:18:05 +00:00
|
|
|
let web3 = connect(&config.api_url)?;
|
2022-05-28 00:04:21 +00:00
|
|
|
let chain_id = web3.eth().chain_id().await?;
|
|
|
|
if chain_id != config.ethereum_chain_id().into() {
|
|
|
|
return Err(EthereumError::ImproperlyConfigured("incorrect chain ID"));
|
|
|
|
};
|
2022-06-28 23:02:31 +00:00
|
|
|
|
2022-01-30 16:18:05 +00:00
|
|
|
let adapter_address = parse_address(&config.contract_address)?;
|
2022-06-28 23:02:31 +00:00
|
|
|
let erc165_abi = load_abi(&config.contract_dir, ERC165)?;
|
|
|
|
let erc165 = Contract::new(
|
2022-01-30 16:18:05 +00:00
|
|
|
web3.eth(),
|
|
|
|
adapter_address,
|
2022-06-28 23:02:31 +00:00
|
|
|
erc165_abi,
|
2022-06-29 14:35:13 +00:00
|
|
|
);
|
2022-01-30 16:18:05 +00:00
|
|
|
|
2022-06-28 23:02:31 +00:00
|
|
|
let mut maybe_gate = None;
|
|
|
|
let mut maybe_collectible = None;
|
|
|
|
let mut maybe_subscription = None;
|
|
|
|
let mut sync_targets = vec![];
|
|
|
|
|
|
|
|
let gate_abi = load_abi(&config.contract_dir, GATE)?;
|
|
|
|
if is_interface_supported(&erc165, &gate_abi).await? {
|
|
|
|
let gate = Contract::new(
|
|
|
|
web3.eth(),
|
|
|
|
adapter_address,
|
|
|
|
gate_abi,
|
|
|
|
);
|
|
|
|
maybe_gate = Some(gate);
|
|
|
|
log::info!("found gate interface");
|
|
|
|
};
|
|
|
|
|
|
|
|
let minter_abi = load_abi(&config.contract_dir, MINTER)?;
|
|
|
|
if is_interface_supported(&erc165, &minter_abi).await? {
|
|
|
|
let minter = Contract::new(
|
|
|
|
web3.eth(),
|
|
|
|
adapter_address,
|
|
|
|
minter_abi,
|
|
|
|
);
|
|
|
|
log::info!("found minter interface");
|
|
|
|
let collectible_address = minter.query(
|
|
|
|
"collectible",
|
|
|
|
(), None, Options::default(), None,
|
|
|
|
).await?;
|
|
|
|
let collectible_abi = load_abi(&config.contract_dir, ERC721)?;
|
|
|
|
let collectible = Contract::new(
|
|
|
|
web3.eth(),
|
|
|
|
collectible_address,
|
|
|
|
collectible_abi,
|
|
|
|
);
|
|
|
|
log::info!("collectible item contract address is {:?}", collectible.address());
|
|
|
|
sync_targets.push(collectible.address());
|
|
|
|
maybe_collectible = Some(collectible);
|
|
|
|
};
|
|
|
|
|
|
|
|
let subscription_adapter_abi = load_abi(&config.contract_dir, SUBSCRIPTION_ADAPTER)?;
|
|
|
|
if is_interface_supported(&erc165, &subscription_adapter_abi).await? {
|
|
|
|
let subscription_adapter = Contract::new(
|
|
|
|
web3.eth(),
|
|
|
|
adapter_address,
|
|
|
|
subscription_adapter_abi,
|
|
|
|
);
|
|
|
|
log::info!("found subscription interface");
|
|
|
|
let subscription_address = subscription_adapter.query(
|
|
|
|
"subscription",
|
|
|
|
(), None, Options::default(), None,
|
|
|
|
).await?;
|
|
|
|
let subscription_abi = load_abi(&config.contract_dir, SUBSCRIPTION)?;
|
|
|
|
let subscription = Contract::new(
|
|
|
|
web3.eth(),
|
|
|
|
subscription_address,
|
|
|
|
subscription_abi,
|
|
|
|
);
|
|
|
|
log::info!("subscription contract address is {:?}", subscription.address());
|
|
|
|
sync_targets.push(subscription.address());
|
|
|
|
maybe_subscription = Some(subscription);
|
|
|
|
};
|
2022-01-30 20:58:05 +00:00
|
|
|
|
2022-06-14 18:55:04 +00:00
|
|
|
let current_block = get_current_block_number(&web3, storage_dir).await?;
|
2022-06-24 23:27:09 +00:00
|
|
|
let sync_state = SyncState::new(
|
|
|
|
current_block,
|
2022-06-28 23:02:31 +00:00
|
|
|
sync_targets,
|
2022-07-16 22:44:15 +00:00
|
|
|
config.chain_sync_step,
|
|
|
|
config.chain_reorg_max_depth,
|
2022-06-24 23:27:09 +00:00
|
|
|
storage_dir,
|
|
|
|
);
|
2022-06-14 18:55:04 +00:00
|
|
|
|
2022-01-30 16:18:05 +00:00
|
|
|
let contract_set = ContractSet {
|
|
|
|
web3,
|
2022-06-28 23:02:31 +00:00
|
|
|
gate: maybe_gate,
|
|
|
|
collectible: maybe_collectible,
|
|
|
|
subscription: maybe_subscription,
|
2022-01-30 16:18:05 +00:00
|
|
|
};
|
2022-06-24 23:27:09 +00:00
|
|
|
Ok(Blockchain { contract_set, sync_state })
|
2022-01-30 16:18:05 +00:00
|
|
|
}
|