Enable contract feature detection

Contracts updated to version 0.5.0.
This commit is contained in:
silverpill 2022-06-28 23:02:31 +00:00
parent 0f74175b29
commit 545dd6d92b
11 changed files with 278 additions and 107 deletions

30
contracts/IERC165.json Normal file
View 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
View 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
View 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": {}
}

View file

@ -71,6 +71,13 @@
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "withdrawReceivedAll",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
],
"bytecode": "0x",

View file

@ -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",

View file

@ -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

View file

@ -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: &ethabi::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: &ethabi::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,51 +103,90 @@ 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(
"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());
let mut maybe_gate = None;
let mut maybe_collectible = None;
let mut maybe_subscription = None;
let mut sync_targets = vec![];
let subscription_address = 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());
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);
};
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 })
}

View file

@ -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?;

View file

@ -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(),

View file

@ -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))
}

View file

@ -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)