Skip to content

Commit

Permalink
Integrate Commonwealth chain events. (#108)
Browse files Browse the repository at this point in the history
* Add CWP contracts.

* Stub out CWP changes.

* Initial complete implementation for CW Protocol.

* Export commonwealth types.

* Fix createApi call.

* Fix subscriber and update solidity.

* Bug fixing for protocol.

* Fix paths.
  • Loading branch information
jnaviask authored Jun 1, 2022
1 parent 054310e commit 678dd0d
Show file tree
Hide file tree
Showing 31 changed files with 5,910 additions and 6 deletions.
29 changes: 29 additions & 0 deletions eth/contracts/Commonwealth/DataTypes.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

////////////////////////////////////////////////////////////////////////////////////////////
/// @title DataTypes
////////////////////////////////////////////////////////////////////////////////////////////

// this order of variables optimizes gas by using the least amount of 32 byte storage spaces as possible
library DataTypes {
struct ProjectMetaData {
uint256 id;
bytes32 name;
bytes32 ipfsHash;
bytes32 url;
address creator;
}

struct ProtocolData {
uint8 fee;
address payable feeTo;
}

struct ProjectData {
uint256 threshold;
uint256 deadline; // uint24 max val = 6.9 years
address payable beneficiary;
address acceptedToken;
}
}
31 changes: 31 additions & 0 deletions eth/contracts/Commonwealth/ICuratedProject.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./IProjectBase.sol";

interface ICuratedProject is IProjectBase {
event Curate(address sender, address token, uint256 amount);

function bToken() external view returns (address);

function cToken() external view returns (address);

function totalCuratorFunding() external view returns (address);

function curatorFee() external view returns (address);

function initialize(
DataTypes.ProjectMetaData memory _metaData,
DataTypes.ProjectData memory _projectData,
DataTypes.ProtocolData memory _protocolData,
uint256 _curatorFee,
address _bToken,
address _cToken
) external returns (bool);

function curate(uint256 _amount) external returns (bool);

function curatorsWithdraw() external returns (bool);

function withdrawRemaining() external view returns (uint256);
}
19 changes: 19 additions & 0 deletions eth/contracts/Commonwealth/ICuratedProjectFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./IProjectBaseFactory.sol";

interface ICuratedProjectFactory is IProjectBaseFactory {
function setPTokenImpl(address _pToken) external;

function createProject(
bytes32 _name,
bytes32 _ipfsHash,
bytes32 _url,
address payable _beneficiary,
address _acceptedToken,
uint256 _threshold,
uint256 _deadline,
uint256 _curatorFee
) external returns (address);
}
48 changes: 48 additions & 0 deletions eth/contracts/Commonwealth/IProjectBase.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {DataTypes} from './DataTypes.sol';

interface IProjectBase {
event Back(address sender, address token, uint256 amount);
event Withdraw(address sender, address token, uint256 amount, bytes32 withdrawalType);
event Succeeded(uint256 timestamp, uint256 amount);
event Failed();

///////////////////////////////////////////
// Getters - view functions
//////////////////////////////////////////
function metaData() external view returns (DataTypes.ProjectMetaData memory);

function threshold() external view returns (uint256); // backing threshold in native token

function deadline() external view returns (uint256); // deadline in blocktime

function totalFunding() external view returns (uint256);

function protocolFee() external view returns (uint256);

function protocolFeeTo() external view returns (address);

function acceptedToken() external view returns (address);

function beneficiary() external view returns (address);

function lockedWithdraw() external view returns (bool);

function funded() external view returns (bool);

///////////////////////////////////////////
// functions
//////////////////////////////////////////

function setName(bytes32 _name) external;

function setIpfsHash(bytes32 _ipfsHash) external;

function back(uint256 _amount) external returns (bool);

function beneficiaryWithdraw() external returns (bool);

function backersWithdraw() external returns (bool);
}
28 changes: 28 additions & 0 deletions eth/contracts/Commonwealth/IProjectBaseFactory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import './DataTypes.sol';

interface IProjectBaseFactory {
event ProjectCreated(uint256 projectIndex, address newProject);

function protocolData() external view returns (DataTypes.ProtocolData memory);

function owner() external view returns (address);

function projectImp() external view returns (address);

function projects(uint32 projectIndex) external view returns (address);

function isAcceptedToken(address token) external view returns (bool);

function numProjects() external view returns (uint256);

function addAcceptedTokens(address[] memory _tokens) external;

function setFeeTo(address payable _feeTo) external;

function setProtocolFee(uint256 _protocolFee) external;

function setProjectImpl(address _projectImpl) external;
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@commonwealth/chain-events",
"version": "0.11.1",
"version": "0.13.0-alpha.0",
"description": "Listen to various chains for events.",
"license": "GPL-3.0",
"files": [
Expand Down
6 changes: 4 additions & 2 deletions scripts/listenerUtils.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { RegisteredTypes } from '@polkadot/types/types';

import { HydraDXSpec } from './specs/hydraDX';
import { KulupuSpec } from './specs/kulupu';
import { StafiSpec } from './specs/stafi';
import { CloverSpec } from './specs/clover';
import { EdgewareSpec } from './specs/edgeware';

import type { RegisteredTypes } from '@polkadot/types/types';

export const networkUrls = {
clover: 'wss://api.clover.finance',
hydradx: 'wss://rpc-01.snakenet.hydradx.io',
Expand Down Expand Up @@ -34,6 +34,7 @@ export const networkUrls = {
frax: 'ws://localhost:8545',

erc20: 'wss://eth-mainnet.alchemyapi.io/v2/cNC4XfxR7biwO2bfIO5aKcs9EMPxTQfr',
'eth-local': 'ws://127.0.0.1:8545',
} as const;

export const networkSpecs: { [chain: string]: RegisteredTypes } = {
Expand All @@ -58,4 +59,5 @@ export const contracts = {
dydx: '0x7E9B1672616FF6D6629Ef2879419aaE79A9018D2',
uniswap: '0xc4e172459f1e7939d522503b81afaac1014ce6f6',
frax: '0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0',
'commonwealth-local': '0x7914a8b73E11432953d9cCda060018EA1d9DCde9',
};
14 changes: 14 additions & 0 deletions scripts/listenerV2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ const { argv } = yargs.options({
description:
'Name of the token if network is erc20 and contractAddress is a erc20 token address',
},
reconnectSince: {
alias: 'R',
type: 'number',
description: 'Block number to query from',
},
});

const shortcuts = {
Expand Down Expand Up @@ -71,6 +76,12 @@ const shortcuts = {
url: networkUrls.moloch,
address: contracts.moloch,
},
commonwealth: {
chain: 'commonwealth',
network: SupportedNetwork.Commonwealth,
url: networkUrls['eth-local'],
address: contracts['commonwealth-local'],
},
};

async function main(): Promise<any> {
Expand All @@ -93,6 +104,9 @@ async function main(): Promise<any> {
enricherConfig: sc.enricherConfig || {
balanceTransferThreshold: 500_000,
},
discoverReconnectRange: argv.reconnectSince
? () => Promise.resolve({ startBlock: argv.reconnectSince })
: undefined,
}
);

Expand Down
164 changes: 164 additions & 0 deletions src/chains/commonwealth/Listener.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import { Listener as BaseListener } from '../../Listener';
import {
CWEvent,
IDisconnectedRange,
SupportedNetwork,
} from '../../interfaces';
import { addPrefix, factory } from '../../logging';

import {
Api,
EventKind,
IEventData,
ListenerOptions as CommonwealthListenerOptions,
RawEvent,
} from './types';
import { createApi } from './subscribeFunc';
import { Subscriber } from './subscriber';
import { Processor } from './processor';
import { StorageFetcher } from './storageFetcher';

export class Listener extends BaseListener<
Api,
StorageFetcher,
Processor,
Subscriber,
EventKind
> {
public discoverReconnectRange: (chain: string) => Promise<IDisconnectedRange>;

private readonly _options: CommonwealthListenerOptions;

protected readonly log;

constructor(
chain: string,
contractAddress: string,
url?: string,
skipCatchup?: boolean,
verbose?: boolean,
discoverReconnectRange?: (c: string) => Promise<IDisconnectedRange>
) {
super(SupportedNetwork.Commonwealth, chain, verbose);

this.log = factory.getLogger(
addPrefix(__filename, [SupportedNetwork.Commonwealth, this._chain])
);

this._options = {
url,
contractAddress,
skipCatchup: !!skipCatchup,
};

this.discoverReconnectRange = discoverReconnectRange;

this._subscribed = false;
}

public async init(): Promise<void> {
try {
this._api = await createApi(
this._options.url,
this._options.contractAddress,
10 * 1000,
this._chain
);
} catch (error) {
this.log.error(`Fatal error occurred while starting the API`);
throw error;
}

try {
this._processor = new Processor(this._api, this._chain);
this._subscriber = new Subscriber(this._api, this._chain, this._verbose);
this.storageFetcher = new StorageFetcher(this._api, this._chain);
} catch (error) {
this.log.error(
`Fatal error occurred while starting the Processor, StorageFetcher and Subscriber`
);
throw error;
}
}

public async subscribe(): Promise<void> {
if (!this._subscriber) {
this.log.info(
`Subscriber for ${this._chain} isn't initialized. Please run init() first!`
);
return;
}

if (!this.options.skipCatchup) await this.processMissedBlocks();
else this.log.info(`Skipping event catchup!`);

try {
this.log.info(
`Subscribing to Commonwealth factory contract: ${this._chain}, on url ${this._options.url}`
);
await this._subscriber.subscribe(this.processBlock.bind(this));
this._subscribed = true;
} catch (error) {
this.log.error(`Subscription error: ${error.message}`);
}
}

public async updateAddress(): Promise<void> {
// TODO
}

private async processMissedBlocks(): Promise<void> {
this.log.info(`Detected offline time, polling missed blocks...`);

if (!this.discoverReconnectRange) {
this.log.info(
`Unable to determine offline range - No discoverReconnectRange function given`
);
}

let offlineRange: IDisconnectedRange;
try {
offlineRange = await this.discoverReconnectRange(this._chain);
if (!offlineRange) {
this.log.warn(`No offline range found, skipping event catchup.`);
return;
}
} catch (error) {
this.log.error(
`Could not discover offline range: ${error.message}. Skipping event catchup.`
);
return;
}

if (!offlineRange || !offlineRange.startBlock) {
this.log.warn(`Unable to determine offline time range.`);
return;
}

try {
const cwEvents = await this.storageFetcher.fetch(offlineRange);
for (const event of cwEvents) {
await this.handleEvent(event);
}
} catch (error) {
this.log.error(`Unable to fetch events from storage: ${error.message}`);
}
}

protected async processBlock(event: RawEvent): Promise<void> {
const { blockNumber } = event;
if (!this._lastBlockNumber || blockNumber > this._lastBlockNumber) {
this._lastBlockNumber = blockNumber;
}

const cwEvents: CWEvent[] = await this._processor.process(event);

for (const evt of cwEvents) {
await this.handleEvent(evt as CWEvent<IEventData>);
}
}

public get options(): CommonwealthListenerOptions {
return this._options;
}
}
Loading

0 comments on commit 678dd0d

Please sign in to comment.