Enable contract feature detection
Contracts updated to version 0.5.0.
This commit is contained in:
parent
0f74175b29
commit
545dd6d92b
11 changed files with 278 additions and 107 deletions
30
contracts/IERC165.json
Normal file
30
contracts/IERC165.json
Normal file
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"_format": "hh-sol-artifact-1",
|
||||
"contractName": "IERC165",
|
||||
"sourceName": "@openzeppelin/contracts/utils/introspection/IERC165.sol",
|
||||
"abi": [
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "bytes4",
|
||||
"name": "interfaceId",
|
||||
"type": "bytes4"
|
||||
}
|
||||
],
|
||||
"name": "supportsInterface",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
}
|
||||
],
|
||||
"bytecode": "0x",
|
||||
"deployedBytecode": "0x",
|
||||
"linkReferences": {},
|
||||
"deployedLinkReferences": {}
|
||||
}
|
30
contracts/IGate.json
Normal file
30
contracts/IGate.json
Normal file
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"_format": "hh-sol-artifact-1",
|
||||
"contractName": "IGate",
|
||||
"sourceName": "contracts/interfaces/IGate.sol",
|
||||
"abi": [
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "user",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "isAllowedUser",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
}
|
||||
],
|
||||
"bytecode": "0x",
|
||||
"deployedBytecode": "0x",
|
||||
"linkReferences": {},
|
||||
"deployedLinkReferences": {}
|
||||
}
|
57
contracts/IMinter.json
Normal file
57
contracts/IMinter.json
Normal file
|
@ -0,0 +1,57 @@
|
|||
{
|
||||
"_format": "hh-sol-artifact-1",
|
||||
"contractName": "IMinter",
|
||||
"sourceName": "contracts/interfaces/IMinter.sol",
|
||||
"abi": [
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "collectible",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "contract IERC721Metadata",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "user",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "string",
|
||||
"name": "tokenURI",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"internalType": "uint8",
|
||||
"name": "v",
|
||||
"type": "uint8"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "r",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "s",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "mint",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
}
|
||||
],
|
||||
"bytecode": "0x",
|
||||
"deployedBytecode": "0x",
|
||||
"linkReferences": {},
|
||||
"deployedLinkReferences": {}
|
||||
}
|
|
@ -71,6 +71,13 @@
|
|||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "withdrawReceivedAll",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
}
|
||||
],
|
||||
"bytecode": "0x",
|
||||
|
|
|
@ -1,21 +1,8 @@
|
|||
{
|
||||
"_format": "hh-sol-artifact-1",
|
||||
"contractName": "IAdapter",
|
||||
"sourceName": "contracts/interfaces/IAdapter.sol",
|
||||
"contractName": "ISubscriptionAdapter",
|
||||
"sourceName": "contracts/interfaces/ISubscriptionAdapter.sol",
|
||||
"abi": [
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "collectible",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "contract IERC721Metadata",
|
||||
"name": "",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
|
@ -97,25 +84,6 @@
|
|||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "user",
|
||||
"type": "address"
|
||||
}
|
||||
],
|
||||
"name": "isAllowedUser",
|
||||
"outputs": [
|
||||
{
|
||||
"internalType": "bool",
|
||||
"name": "",
|
||||
"type": "bool"
|
||||
}
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
|
@ -135,39 +103,6 @@
|
|||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "address",
|
||||
"name": "user",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "string",
|
||||
"name": "tokenURI",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"internalType": "uint8",
|
||||
"name": "v",
|
||||
"type": "uint8"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "r",
|
||||
"type": "bytes32"
|
||||
},
|
||||
{
|
||||
"internalType": "bytes32",
|
||||
"name": "s",
|
||||
"type": "bytes32"
|
||||
}
|
||||
],
|
||||
"name": "mint",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "subscription",
|
|
@ -782,6 +782,19 @@ components:
|
|||
description: Blockchain contract address.
|
||||
type: string
|
||||
nullable: true
|
||||
blockchain_features:
|
||||
description: Blockchain contract features.
|
||||
type: object
|
||||
nullable: true
|
||||
properties:
|
||||
minter:
|
||||
description: Minter feature flag.
|
||||
type: boolean
|
||||
example: true
|
||||
subscription:
|
||||
description: Subscription feature flag.
|
||||
type: boolean
|
||||
example: true
|
||||
blockchain_info:
|
||||
description: Additional information about blockchain
|
||||
type: object
|
||||
|
|
|
@ -3,7 +3,7 @@ use std::path::Path;
|
|||
|
||||
use web3::{
|
||||
api::Web3,
|
||||
contract::{Contract, Options},
|
||||
contract::{Contract, Error as ContractError, Options},
|
||||
ethabi,
|
||||
transports::Http,
|
||||
};
|
||||
|
@ -17,7 +17,10 @@ use super::sync::{
|
|||
};
|
||||
use super::utils::parse_address;
|
||||
|
||||
const ADAPTER: &str = "IAdapter";
|
||||
const ERC165: &str = "IERC165";
|
||||
const GATE: &str = "IGate";
|
||||
const MINTER: &str = "IMinter";
|
||||
const SUBSCRIPTION_ADAPTER: &str = "ISubscriptionAdapter";
|
||||
const SUBSCRIPTION: &str = "ISubscription";
|
||||
const ERC721: &str = "IERC721Metadata";
|
||||
|
||||
|
@ -51,13 +54,38 @@ fn load_abi(
|
|||
Ok(abi)
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ContractSet {
|
||||
pub web3: Web3<Http>,
|
||||
|
||||
pub adapter: Contract<Http>,
|
||||
pub collectible: Contract<Http>,
|
||||
pub subscription: Contract<Http>,
|
||||
pub gate: Option<Contract<Http>>,
|
||||
pub collectible: Option<Contract<Http>>,
|
||||
pub subscription: Option<Contract<Http>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -75,15 +103,40 @@ pub async fn get_contracts(
|
|||
if chain_id != config.ethereum_chain_id().into() {
|
||||
return Err(EthereumError::ImproperlyConfigured("incorrect chain ID"));
|
||||
};
|
||||
let adapter_abi = load_abi(&config.contract_dir, ADAPTER)?;
|
||||
|
||||
let adapter_address = parse_address(&config.contract_address)?;
|
||||
let adapter = Contract::new(
|
||||
let erc165_abi = load_abi(&config.contract_dir, ERC165)?;
|
||||
let erc165 = Contract::new(
|
||||
web3.eth(),
|
||||
adapter_address,
|
||||
adapter_abi,
|
||||
erc165_abi,
|
||||
);
|
||||
|
||||
let collectible_address = adapter.query(
|
||||
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?;
|
||||
|
@ -94,8 +147,19 @@ pub async fn get_contracts(
|
|||
collectible_abi,
|
||||
);
|
||||
log::info!("collectible item contract address is {:?}", collectible.address());
|
||||
sync_targets.push(collectible.address());
|
||||
maybe_collectible = Some(collectible);
|
||||
};
|
||||
|
||||
let subscription_address = adapter.query(
|
||||
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?;
|
||||
|
@ -106,20 +170,23 @@ pub async fn get_contracts(
|
|||
subscription_abi,
|
||||
);
|
||||
log::info!("subscription contract address is {:?}", subscription.address());
|
||||
sync_targets.push(subscription.address());
|
||||
maybe_subscription = Some(subscription);
|
||||
};
|
||||
|
||||
let current_block = get_current_block_number(&web3, storage_dir).await?;
|
||||
log::info!("current block is {}", current_block);
|
||||
let sync_state = SyncState::new(
|
||||
current_block,
|
||||
vec![collectible.address(), subscription.address()],
|
||||
sync_targets,
|
||||
storage_dir,
|
||||
);
|
||||
|
||||
let contract_set = ContractSet {
|
||||
web3,
|
||||
adapter,
|
||||
collectible,
|
||||
subscription,
|
||||
gate: maybe_gate,
|
||||
collectible: maybe_collectible,
|
||||
subscription: maybe_subscription,
|
||||
};
|
||||
Ok(Blockchain { contract_set, sync_state })
|
||||
}
|
||||
|
|
|
@ -8,8 +8,12 @@ pub async fn is_allowed_user(
|
|||
contract_set: &ContractSet,
|
||||
user_address: &str,
|
||||
) -> Result<bool, EthereumError> {
|
||||
let gate = match &contract_set.gate {
|
||||
Some(contract) => contract,
|
||||
None => return Ok(true), // no gate
|
||||
};
|
||||
let user_address = parse_address(user_address)?;
|
||||
let result: bool = contract_set.adapter.query(
|
||||
let result: bool = gate.query(
|
||||
"isAllowedUser", (user_address,),
|
||||
None, Options::default(), None,
|
||||
).await?;
|
||||
|
|
|
@ -3,8 +3,15 @@ use std::collections::HashMap;
|
|||
use serde::Serialize;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::ethereum::contracts::ContractSet;
|
||||
use crate::mastodon_api::MASTODON_API_VERSION;
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct BlockchainFeatures {
|
||||
minter: bool,
|
||||
subscription: bool,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct InstanceInfo {
|
||||
uri: String,
|
||||
|
@ -19,6 +26,7 @@ pub struct InstanceInfo {
|
|||
blockchain_id: Option<String>,
|
||||
blockchain_explorer_url: Option<String>,
|
||||
blockchain_contract_address: Option<String>,
|
||||
blockchain_features: Option<BlockchainFeatures>,
|
||||
blockchain_info: Option<HashMap<String, String>>,
|
||||
ipfs_gateway_url: Option<String>,
|
||||
}
|
||||
|
@ -31,8 +39,14 @@ fn get_full_api_version(version: &str) -> String {
|
|||
)
|
||||
}
|
||||
|
||||
impl From<&Config> for InstanceInfo {
|
||||
fn from(config: &Config) -> Self {
|
||||
impl InstanceInfo {
|
||||
pub fn create(config: &Config, maybe_blockchain: Option<&ContractSet>) -> Self {
|
||||
let blockchain_features = maybe_blockchain.map(|contract_set| {
|
||||
BlockchainFeatures {
|
||||
minter: contract_set.collectible.is_some(),
|
||||
subscription: contract_set.subscription.is_some(),
|
||||
}
|
||||
});
|
||||
Self {
|
||||
uri: config.instance().host(),
|
||||
title: config.instance_title.clone(),
|
||||
|
@ -48,6 +62,7 @@ impl From<&Config> for InstanceInfo {
|
|||
.and_then(|val| val.explorer_url.clone()),
|
||||
blockchain_contract_address: config.blockchain.as_ref()
|
||||
.map(|val| val.contract_address.clone()),
|
||||
blockchain_features: blockchain_features,
|
||||
blockchain_info: config.blockchain.as_ref()
|
||||
.and_then(|val| val.chain_info.clone()),
|
||||
ipfs_gateway_url: config.ipfs_gateway_url.clone(),
|
||||
|
|
|
@ -2,13 +2,18 @@ use actix_web::{get, web, HttpResponse, Scope};
|
|||
|
||||
use crate::config::Config;
|
||||
use crate::errors::HttpError;
|
||||
use crate::ethereum::contracts::ContractSet;
|
||||
use super::types::InstanceInfo;
|
||||
|
||||
#[get("")]
|
||||
async fn instance_view(
|
||||
config: web::Data<Config>,
|
||||
maybe_blockchain: web::Data<Option<ContractSet>>,
|
||||
) -> Result<HttpResponse, HttpError> {
|
||||
let instance = InstanceInfo::from(config.as_ref());
|
||||
let instance = InstanceInfo::create(
|
||||
config.as_ref(),
|
||||
maybe_blockchain.as_ref().as_ref(),
|
||||
);
|
||||
Ok(HttpResponse::Ok().json(instance))
|
||||
}
|
||||
|
||||
|
|
|
@ -46,9 +46,13 @@ async fn nft_monitor_task(
|
|||
Some(blockchain) => blockchain,
|
||||
None => return Ok(()),
|
||||
};
|
||||
let collectible = match &blockchain.contract_set.collectible {
|
||||
Some(contract) => contract,
|
||||
None => return Ok(()), // feature not enabled
|
||||
};
|
||||
process_nft_events(
|
||||
&blockchain.contract_set.web3,
|
||||
&blockchain.contract_set.collectible,
|
||||
&collectible,
|
||||
&mut blockchain.sync_state,
|
||||
db_pool,
|
||||
token_waitlist_map,
|
||||
|
@ -64,9 +68,13 @@ async fn subscription_monitor_task(
|
|||
Some(blockchain) => blockchain,
|
||||
None => return Ok(()),
|
||||
};
|
||||
let subscription = match &blockchain.contract_set.subscription {
|
||||
Some(contract) => contract,
|
||||
None => return Ok(()), // feature not enabled
|
||||
};
|
||||
check_subscriptions(
|
||||
&blockchain.contract_set.web3,
|
||||
&blockchain.contract_set.subscription,
|
||||
&subscription,
|
||||
&mut blockchain.sync_state,
|
||||
db_pool,
|
||||
).await.map_err(Error::from)
|
||||
|
|
Loading…
Reference in a new issue