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": [], "outputs": [],
"stateMutability": "nonpayable", "stateMutability": "nonpayable",
"type": "function" "type": "function"
},
{
"inputs": [],
"name": "withdrawReceivedAll",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
} }
], ],
"bytecode": "0x", "bytecode": "0x",

View file

@ -1,21 +1,8 @@
{ {
"_format": "hh-sol-artifact-1", "_format": "hh-sol-artifact-1",
"contractName": "IAdapter", "contractName": "ISubscriptionAdapter",
"sourceName": "contracts/interfaces/IAdapter.sol", "sourceName": "contracts/interfaces/ISubscriptionAdapter.sol",
"abi": [ "abi": [
{
"inputs": [],
"name": "collectible",
"outputs": [
{
"internalType": "contract IERC721Metadata",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{ {
"inputs": [ "inputs": [
{ {
@ -97,25 +84,6 @@
"stateMutability": "view", "stateMutability": "view",
"type": "function" "type": "function"
}, },
{
"inputs": [
{
"internalType": "address",
"name": "user",
"type": "address"
}
],
"name": "isAllowedUser",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{ {
"inputs": [ "inputs": [
{ {
@ -135,39 +103,6 @@
"stateMutability": "view", "stateMutability": "view",
"type": "function" "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": [], "inputs": [],
"name": "subscription", "name": "subscription",

View file

@ -782,6 +782,19 @@ components:
description: Blockchain contract address. description: Blockchain contract address.
type: string type: string
nullable: true 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: blockchain_info:
description: Additional information about blockchain description: Additional information about blockchain
type: object type: object

View file

@ -3,7 +3,7 @@ use std::path::Path;
use web3::{ use web3::{
api::Web3, api::Web3,
contract::{Contract, Options}, contract::{Contract, Error as ContractError, Options},
ethabi, ethabi,
transports::Http, transports::Http,
}; };
@ -17,7 +17,10 @@ use super::sync::{
}; };
use super::utils::parse_address; 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 SUBSCRIPTION: &str = "ISubscription";
const ERC721: &str = "IERC721Metadata"; const ERC721: &str = "IERC721Metadata";
@ -51,13 +54,38 @@ fn load_abi(
Ok(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)] #[derive(Clone)]
pub struct ContractSet { pub struct ContractSet {
pub web3: Web3<Http>, pub web3: Web3<Http>,
pub adapter: Contract<Http>, pub gate: Option<Contract<Http>>,
pub collectible: Contract<Http>, pub collectible: Option<Contract<Http>>,
pub subscription: Contract<Http>, pub subscription: Option<Contract<Http>>,
} }
#[derive(Clone)] #[derive(Clone)]
@ -75,15 +103,40 @@ pub async fn get_contracts(
if chain_id != config.ethereum_chain_id().into() { if chain_id != config.ethereum_chain_id().into() {
return Err(EthereumError::ImproperlyConfigured("incorrect chain ID")); 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_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(), web3.eth(),
adapter_address, 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", "collectible",
(), None, Options::default(), None, (), None, Options::default(), None,
).await?; ).await?;
@ -94,8 +147,19 @@ pub async fn get_contracts(
collectible_abi, collectible_abi,
); );
log::info!("collectible item contract address is {:?}", collectible.address()); 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", "subscription",
(), None, Options::default(), None, (), None, Options::default(), None,
).await?; ).await?;
@ -106,20 +170,23 @@ pub async fn get_contracts(
subscription_abi, subscription_abi,
); );
log::info!("subscription contract address is {:?}", subscription.address()); 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?; let current_block = get_current_block_number(&web3, storage_dir).await?;
log::info!("current block is {}", current_block); log::info!("current block is {}", current_block);
let sync_state = SyncState::new( let sync_state = SyncState::new(
current_block, current_block,
vec![collectible.address(), subscription.address()], sync_targets,
storage_dir, storage_dir,
); );
let contract_set = ContractSet { let contract_set = ContractSet {
web3, web3,
adapter, gate: maybe_gate,
collectible, collectible: maybe_collectible,
subscription, subscription: maybe_subscription,
}; };
Ok(Blockchain { contract_set, sync_state }) Ok(Blockchain { contract_set, sync_state })
} }

View file

@ -8,8 +8,12 @@ pub async fn is_allowed_user(
contract_set: &ContractSet, contract_set: &ContractSet,
user_address: &str, user_address: &str,
) -> Result<bool, EthereumError> { ) -> 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 user_address = parse_address(user_address)?;
let result: bool = contract_set.adapter.query( let result: bool = gate.query(
"isAllowedUser", (user_address,), "isAllowedUser", (user_address,),
None, Options::default(), None, None, Options::default(), None,
).await?; ).await?;

View file

@ -3,8 +3,15 @@ use std::collections::HashMap;
use serde::Serialize; use serde::Serialize;
use crate::config::Config; use crate::config::Config;
use crate::ethereum::contracts::ContractSet;
use crate::mastodon_api::MASTODON_API_VERSION; use crate::mastodon_api::MASTODON_API_VERSION;
#[derive(Serialize)]
struct BlockchainFeatures {
minter: bool,
subscription: bool,
}
#[derive(Serialize)] #[derive(Serialize)]
pub struct InstanceInfo { pub struct InstanceInfo {
uri: String, uri: String,
@ -19,6 +26,7 @@ pub struct InstanceInfo {
blockchain_id: Option<String>, blockchain_id: Option<String>,
blockchain_explorer_url: Option<String>, blockchain_explorer_url: Option<String>,
blockchain_contract_address: Option<String>, blockchain_contract_address: Option<String>,
blockchain_features: Option<BlockchainFeatures>,
blockchain_info: Option<HashMap<String, String>>, blockchain_info: Option<HashMap<String, String>>,
ipfs_gateway_url: Option<String>, ipfs_gateway_url: Option<String>,
} }
@ -31,8 +39,14 @@ fn get_full_api_version(version: &str) -> String {
) )
} }
impl From<&Config> for InstanceInfo { impl InstanceInfo {
fn from(config: &Config) -> Self { 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 { Self {
uri: config.instance().host(), uri: config.instance().host(),
title: config.instance_title.clone(), title: config.instance_title.clone(),
@ -48,6 +62,7 @@ impl From<&Config> for InstanceInfo {
.and_then(|val| val.explorer_url.clone()), .and_then(|val| val.explorer_url.clone()),
blockchain_contract_address: config.blockchain.as_ref() blockchain_contract_address: config.blockchain.as_ref()
.map(|val| val.contract_address.clone()), .map(|val| val.contract_address.clone()),
blockchain_features: blockchain_features,
blockchain_info: config.blockchain.as_ref() blockchain_info: config.blockchain.as_ref()
.and_then(|val| val.chain_info.clone()), .and_then(|val| val.chain_info.clone()),
ipfs_gateway_url: config.ipfs_gateway_url.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::config::Config;
use crate::errors::HttpError; use crate::errors::HttpError;
use crate::ethereum::contracts::ContractSet;
use super::types::InstanceInfo; use super::types::InstanceInfo;
#[get("")] #[get("")]
async fn instance_view( async fn instance_view(
config: web::Data<Config>, config: web::Data<Config>,
maybe_blockchain: web::Data<Option<ContractSet>>,
) -> Result<HttpResponse, HttpError> { ) -> 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)) Ok(HttpResponse::Ok().json(instance))
} }

View file

@ -46,9 +46,13 @@ async fn nft_monitor_task(
Some(blockchain) => blockchain, Some(blockchain) => blockchain,
None => return Ok(()), None => return Ok(()),
}; };
let collectible = match &blockchain.contract_set.collectible {
Some(contract) => contract,
None => return Ok(()), // feature not enabled
};
process_nft_events( process_nft_events(
&blockchain.contract_set.web3, &blockchain.contract_set.web3,
&blockchain.contract_set.collectible, &collectible,
&mut blockchain.sync_state, &mut blockchain.sync_state,
db_pool, db_pool,
token_waitlist_map, token_waitlist_map,
@ -64,9 +68,13 @@ async fn subscription_monitor_task(
Some(blockchain) => blockchain, Some(blockchain) => blockchain,
None => return Ok(()), None => return Ok(()),
}; };
let subscription = match &blockchain.contract_set.subscription {
Some(contract) => contract,
None => return Ok(()), // feature not enabled
};
check_subscriptions( check_subscriptions(
&blockchain.contract_set.web3, &blockchain.contract_set.web3,
&blockchain.contract_set.subscription, &subscription,
&mut blockchain.sync_state, &mut blockchain.sync_state,
db_pool, db_pool,
).await.map_err(Error::from) ).await.map_err(Error::from)