diff --git a/src/balancer/OceanPool.ts b/src/balancer/OceanPool.ts index d2b57733..0a7ac1f6 100644 --- a/src/balancer/OceanPool.ts +++ b/src/balancer/OceanPool.ts @@ -2,6 +2,28 @@ import Web3 from 'web3' import { AbiItem } from 'web3-utils/types' import { TransactionReceipt } from 'web3-core' import { Pool } from './Pool' +import { EventData, Filter } from 'web3-eth-contract' + +declare type PoolTransactionType = 'swap' | 'join' | 'exit' + +export interface PoolDetails { + poolAddress: string + tokens: string[] +} + +export interface PoolTransaction { + poolAddress: string + dtAddress: string + caller: string + transactionHash: string + blockNumber: number + timestamp: number + tokenIn?: string + tokenOut?: string + tokenAmountIn?: string + tokenAmountOut?: string + type: PoolTransactionType +} /** * Ocean Pools submodule exposed under ocean.pool @@ -82,9 +104,9 @@ export class OceanPool extends Pool { * @param {String} poolAddress * @return {string} */ - public async getDTAddress(account: string, poolAddress: string): Promise { + public async getDTAddress(poolAddress: string): Promise { this.dtAddress = null - const tokens = await this.getCurrentTokens(account, poolAddress) + const tokens = await this.getCurrentTokens(poolAddress) let token: string for (token of tokens) { @@ -115,7 +137,7 @@ export class OceanPool extends Pool { * @return {String} */ public async getDTReserve(account: string, poolAddress: string): Promise { - await this.getDTAddress(account, poolAddress) + await this.getDTAddress(poolAddress) return super.getReserve(account, poolAddress, this.dtAddress) } @@ -139,7 +161,7 @@ export class OceanPool extends Pool { console.error('oceanAddress is not defined') return null } - await this.getDTAddress(account, poolAddress) + await this.getDTAddress(poolAddress) // TODO - check balances first await super.approve( @@ -180,7 +202,7 @@ export class OceanPool extends Pool { console.error('oceanAddress is not defined') return null } - await this.getDTAddress(account, poolAddress) + await this.getDTAddress(poolAddress) return this.swapExactAmountOut( account, poolAddress, @@ -204,7 +226,7 @@ export class OceanPool extends Pool { poolAddress: string, amount: string ): Promise { - await this.getDTAddress(account, poolAddress) + await this.getDTAddress(poolAddress) await super.approve( account, this.dtAddress, @@ -234,7 +256,7 @@ export class OceanPool extends Pool { amount: string, maximumPoolShares: string ): Promise { - await this.getDTAddress(account, poolAddress) + await this.getDTAddress(poolAddress) // TODO Check balance of PoolShares before doing exit return this.exitswapExternAmountOut( account, @@ -335,10 +357,7 @@ export class OceanPool extends Pool { toBlock: 'latest' }) for (let i = 0; i < events.length; i++) { - const constituents = await super.getCurrentTokens( - account, - events[i].returnValues[0] - ) + const constituents = await super.getCurrentTokens(events[i].returnValues[0]) if (constituents.includes(dtAddress)) result.push(events[i].returnValues[0]) } return result @@ -349,7 +368,7 @@ export class OceanPool extends Pool { poolAddress: string, dtRequired: string ): Promise { - await this.getDTAddress(account, poolAddress) + await this.getDTAddress(poolAddress) const tokenBalanceIn = await this.getReserve(account, poolAddress, this.oceanAddress) const tokenWeightIn = await this.getDenormalizedWeight( account, @@ -372,4 +391,154 @@ export class OceanPool extends Pool { swapFee ) } + + /** + * Search all pools created by an address + * @param {String} account If empty, will return all pools ever created by anybody + * @return {PoolDetails[]} + */ + public async getPoolsbyCreator(account?: string): Promise { + const result: PoolDetails[] = [] + const factory = new this.web3.eth.Contract(this.factoryABI, this.factoryAddress) + + const events = await factory.getPastEvents('BPoolRegistered', { + filter: account ? { registeredBy: account } : {}, + fromBlock: 0, + toBlock: 'latest' + }) + + for (let i = 0; i < events.length; i++) { + if (!account || events[i].returnValues[1].toLowerCase() === account.toLowerCase()) + result.push(await this.getPoolDetails(events[i].returnValues[0])) + } + return result + } + + /** + * Get pool details + * @param {String} poolAddress Pool address + * @return {PoolDetails} + */ + public async getPoolDetails(poolAddress: string): Promise { + const tokens = await super.getFinalTokens(poolAddress) + const details: PoolDetails = { poolAddress, tokens } + return details + } + + /** + * Get all actions from a pool (join,exit,swap) + * @param {String} poolAddress Pool address + * @param {String} account + * @return {PoolTransaction[]} + */ + public async getPoolLogs( + poolAddress: string, + account?: string + ): Promise { + const results: PoolTransaction[] = [] + const pool = new this.web3.eth.Contract(this.poolABI, poolAddress) + const dtAddress = await this.getDTAddress(poolAddress) + const filter: Filter = account ? { caller: account } : {} + let events: EventData[] + + events = await pool.getPastEvents('LOG_SWAP', { + filter, + fromBlock: 0, + toBlock: 'latest' + }) + + for (let i = 0; i < events.length; i++) { + if (!account || events[i].returnValues[0].toLowerCase() === account.toLowerCase()) + results.push(await this.getEventData('swap', poolAddress, dtAddress, events[i])) + } + + events = await pool.getPastEvents('LOG_JOIN', { + filter, + fromBlock: 0, + toBlock: 'latest' + }) + + for (let i = 0; i < events.length; i++) { + if (!account || events[i].returnValues[0].toLowerCase() === account.toLowerCase()) + results.push(await this.getEventData('join', poolAddress, dtAddress, events[i])) + } + + events = await pool.getPastEvents('LOG_EXIT', { + filter, + fromBlock: 0, + toBlock: 'latest' + }) + for (let i = 0; i < events.length; i++) { + if (!account || events[i].returnValues[0].toLowerCase() === account.toLowerCase()) + results.push(await this.getEventData('exit', poolAddress, dtAddress, events[i])) + } + + return results + } + + /** + * Get all logs on all pools for a specific address + * @param {String} account + * @return {PoolTransaction[]} + */ + public async getAllPoolLogs(account: string): Promise { + const results: PoolTransaction[] = [] + const factory = new this.web3.eth.Contract(this.factoryABI, this.factoryAddress) + const events = await factory.getPastEvents('BPoolRegistered', { + filter: {}, + fromBlock: 0, + toBlock: 'latest' + }) + + for (let i = 0; i < events.length; i++) { + const logs = await this.getPoolLogs(events[i].returnValues[0], account) + for (let j = 0; j < logs.length; j++) results.push(logs[j]) + } + return results + } + + private async getEventData( + type: PoolTransactionType, + poolAddress: string, + dtAddress: string, + data: EventData + ): Promise { + const blockDetails = await this.web3.eth.getBlock(data.blockNumber) + let result: PoolTransaction = { + poolAddress, + dtAddress, + caller: data.returnValues[0], + transactionHash: data.transactionHash, + blockNumber: data.blockNumber, + timestamp: parseInt(String(blockDetails.timestamp)), + type + } + + switch (type) { + case 'swap': + result = { + ...result, + tokenIn: data.returnValues[1], + tokenOut: data.returnValues[2], + tokenAmountIn: this.web3.utils.fromWei(data.returnValues[3]), + tokenAmountOut: this.web3.utils.fromWei(data.returnValues[4]) + } + break + case 'join': + result = { + ...result, + tokenIn: data.returnValues[1], + tokenAmountIn: this.web3.utils.fromWei(data.returnValues[2]) + } + break + case 'exit': + result = { + ...result, + tokenOut: data.returnValues[1], + tokenAmountOut: this.web3.utils.fromWei(data.returnValues[2]) + } + break + } + return result + } } diff --git a/src/balancer/Pool.ts b/src/balancer/Pool.ts index 99c236fe..cb930518 100644 --- a/src/balancer/Pool.ts +++ b/src/balancer/Pool.ts @@ -15,7 +15,7 @@ export interface TokensToAdd { } export class Pool extends PoolFactory { - private poolABI: AbiItem | AbiItem[] + public poolABI: AbiItem | AbiItem[] constructor( web3: Web3, @@ -275,14 +275,11 @@ export class Pool extends PoolFactory { /** * Get tokens composing this pool - * @param {String} account * @param {String} poolAddress * @return {String[]} */ - async getCurrentTokens(account: string, poolAddress: string): Promise { - const pool = new this.web3.eth.Contract(this.poolABI, poolAddress, { - from: account - }) + async getCurrentTokens(poolAddress: string): Promise { + const pool = new this.web3.eth.Contract(this.poolABI, poolAddress) let result = null try { result = await pool.methods.getCurrentTokens().call() @@ -294,14 +291,11 @@ export class Pool extends PoolFactory { /** * Get the final tokens composing this pool - * @param {String} account * @param {String} poolAddress * @return {String[]} */ - async getFinalTokens(account: string, poolAddress: string): Promise { - const pool = new this.web3.eth.Contract(this.poolABI, poolAddress, { - from: account - }) + async getFinalTokens(poolAddress: string): Promise { + const pool = new this.web3.eth.Contract(this.poolABI, poolAddress) let result = null try { result = await pool.methods.getFinalTokens().call() diff --git a/src/exchange/FixedRateExchange.ts b/src/exchange/FixedRateExchange.ts index 944d88c6..0bb5b6eb 100644 --- a/src/exchange/FixedRateExchange.ts +++ b/src/exchange/FixedRateExchange.ts @@ -1,11 +1,11 @@ import defaultFixedRateExchangeABI from '@oceanprotocol/contracts/artifacts/FixedRateExchange.json' import BigNumber from 'bignumber.js' import { TransactionReceipt } from 'web3-core' -import { Contract } from 'web3-eth-contract' +import { Contract, EventData } from 'web3-eth-contract' import { AbiItem } from 'web3-utils/types' import Web3 from 'web3' -export interface FixedPricedExchange { +export interface FixedPriceExchange { exchangeID?: string exchangeOwner: string dataToken: string @@ -15,6 +15,12 @@ export interface FixedPricedExchange { supply: string } +export interface FixedPriceSwap { + exchangeID: string + caller: string + baseTokenAmount: string + dataTokenAmount: string +} const DEFAULT_GAS_LIMIT = 300000 export class OceanFixedRateExchange { @@ -292,8 +298,8 @@ export class OceanFixedRateExchange { * @param {String} exchangeId ExchangeId * @return {Promise} Exchange details */ - public async getExchange(exchangeId: string): Promise { - const result: FixedPricedExchange = await this.contract.methods + public async getExchange(exchangeId: string): Promise { + const result: FixedPriceExchange = await this.contract.methods .getExchange(exchangeId) .call() return result @@ -337,8 +343,8 @@ export class OceanFixedRateExchange { public async searchforDT( dataTokenAddress: string, minSupply: string - ): Promise { - const result: FixedPricedExchange[] = [] + ): Promise { + const result: FixedPriceExchange[] = [] const events = await this.contract.getPastEvents('ExchangeCreated', { filter: { datatoken: dataTokenAddress }, fromBlock: 0, @@ -357,4 +363,80 @@ export class OceanFixedRateExchange { } return result } + + /** + * Get all exchanges, filtered by creator(if any) + * @param {String} account + * @return {Promise} + */ + public async getExchangesbyCreator(account?: string): Promise { + const result: FixedPriceExchange[] = [] + const events = await this.contract.getPastEvents('ExchangeCreated', { + filter: {}, + fromBlock: 0, + toBlock: 'latest' + }) + for (let i = 0; i < events.length; i++) { + if (!account || events[i].returnValues[3].toLowerCase() === account.toLowerCase()) + result.push(await this.getExchange(events[i].returnValues[0])) + } + return result + } + + /** + * Get all swaps for an exchange, filtered by account(if any) + * @param {String} exchangeId + * @param {String} account + * @return {Promise} + */ + public async getExchangeSwaps( + exchangeId: string, + account?: string + ): Promise { + const result: FixedPriceSwap[] = [] + const events = await this.contract.getPastEvents('Swapped', { + filter: { exchangeId: exchangeId }, + fromBlock: 0, + toBlock: 'latest' + }) + for (let i = 0; i < events.length; i++) { + if (!account || events[i].returnValues[1].toLowerCase() === account.toLowerCase()) + result.push(this.getEventData(events[i])) + } + return result + } + + /** + * Get all swaps for an account + * @param {String} account + * @return {Promise} + */ + public async getAllExchangesSwaps(account: string): Promise { + const result: FixedPriceSwap[] = [] + const events = await this.contract.getPastEvents('ExchangeCreated', { + filter: {}, + fromBlock: 0, + toBlock: 'latest' + }) + for (let i = 0; i < events.length; i++) { + const swaps: FixedPriceSwap[] = await this.getExchangeSwaps( + events[i].returnValues[0], + account + ) + swaps.forEach((swap) => { + result.push(swap) + }) + } + return result + } + + private getEventData(data: EventData): FixedPriceSwap { + const result: FixedPriceSwap = { + exchangeID: data.returnValues[0], + caller: data.returnValues[1], + baseTokenAmount: data.returnValues[2], + dataTokenAmount: data.returnValues[3] + } + return result + } } diff --git a/test/unit/balancer/Balancer.test.ts b/test/unit/balancer/Balancer.test.ts index a76a403a..063c56b7 100644 --- a/test/unit/balancer/Balancer.test.ts +++ b/test/unit/balancer/Balancer.test.ts @@ -122,7 +122,7 @@ describe('Balancer flow', () => { assert(String(n) === '2', 'unexpected num tokens: ' + n) }) it('Get pool information', async () => { - const currentTokens = await Pool.getCurrentTokens(alice, alicePoolAddress) + const currentTokens = await Pool.getCurrentTokens(alicePoolAddress) assert(currentTokens.length === 2) assert(currentTokens.includes(tokenAddress)) assert(currentTokens.includes(oceanTokenAddress)) @@ -270,4 +270,18 @@ describe('Balancer flow', () => { assert(parseFloat(bobDtBalance) < parseFloat(newbobDtBalance)) assert(parseFloat(poolShares) > parseFloat(newpoolShares)) }) + + it('ALice should get all the pools that she created', async () => { + const alicePools = await Pool.getPoolsbyCreator(alice) + assert(alicePools.length > 0) + }) + + it('ALice should get the logs for her pool', async () => { + const poolLogs = await Pool.getPoolLogs(greatPool, null) + assert(poolLogs.length > 0) + }) + it('Bob should get the logs for all his activities', async () => { + const poolLogs = await Pool.getAllPoolLogs(bob) + assert(poolLogs.length > 0) + }) }) diff --git a/test/unit/exchanges/FixedPriceExchange.test.ts b/test/unit/exchanges/FixedPriceExchange.test.ts index 7e6e7f30..ea4cc205 100644 --- a/test/unit/exchanges/FixedPriceExchange.test.ts +++ b/test/unit/exchanges/FixedPriceExchange.test.ts @@ -226,4 +226,12 @@ describe('FixedRateExchange flow', () => { const exchangeDetails = await FixedRateClass.searchforDT(tokenAddress, tokenAmount) assert(exchangeDetails.length === 0) }) + it('Bob should find all the exchanges created by Alice', async () => { + const exchangeDetails = await FixedRateClass.getExchangesbyCreator(alice) + assert(exchangeDetails.length > 0) + }) + it('Bob should find all his swaps', async () => { + const exchangeDetails = await FixedRateClass.getAllExchangesSwaps(bob) + assert(exchangeDetails.length > 0) + }) })