import Web3 from 'web3' import { AbiItem } from 'web3-utils/types' import { TransactionReceipt } from 'web3-core' import { Pool } from './Pool' /** * Ocean Pools submodule exposed under ocean.pool */ export class OceanPool extends Pool { public oceanAddress: string = null public dtAddress: string = null constructor( web3: Web3, factoryABI: AbiItem | AbiItem[] = null, poolABI: AbiItem | AbiItem[] = null, factoryAddress: string = null, oceanAddress: string = null, gaslimit?: number ) { super(web3, factoryABI, poolABI, factoryAddress, gaslimit) if (oceanAddress) { this.oceanAddress = oceanAddress } } /** * Create DataToken pool @param {String} account * @param {String} token DataToken address * @param {String} amount DataToken amount * @param {String} weight DataToken weight * @param {String} fee Swap fee (as float) * @return {String} */ public async createDTPool( account: string, token: string, amount: string, weight: string, fee: string ): Promise { if (this.oceanAddress == null) { console.error('oceanAddress is not defined') return null } if (parseFloat(weight) > 9 || parseFloat(weight) < 1) { console.error('Weight out of bounds (min 1, max9)') return null } const address = await super.createPool(account) const oceanWeight = 10 - parseFloat(weight) const oceanAmount = (parseFloat(amount) * oceanWeight) / parseFloat(weight) this.dtAddress = token await this.approve( account, token, address, this.web3.utils.toWei(String(amount)) as any ) await this.approve( account, this.oceanAddress, address, this.web3.utils.toWei(String(oceanAmount)) as any ) await super.setup( account, address, token, this.web3.utils.toWei(String(amount)), this.web3.utils.toWei(String(weight)), this.oceanAddress, this.web3.utils.toWei(String(oceanAmount)), this.web3.utils.toWei(String(oceanWeight)), this.web3.utils.toWei(fee) ) return address } /** * Get DataToken address of token in this pool * @param {String} account * @param {String} poolAddress * @return {string} */ public async getDTAddress(account: string, poolAddress: string): Promise { this.dtAddress = null const tokens = await this.getCurrentTokens(account, poolAddress) let token for (token of tokens) { // TODO: Potential timing attack, left side: true if (token !== this.oceanAddress) this.dtAddress = token } return this.dtAddress } /** * Get Ocean Token balance of a pool * @param {String} account * @param {String} poolAddress * @return {String} */ public async getOceanReserve(account: string, poolAddress: string): Promise { if (this.oceanAddress == null) { console.error('oceanAddress is not defined') return null } return super.getReserve(account, poolAddress, this.oceanAddress) } /** * Get Data Token balance of a pool * @param {String} account * @param {String} poolAddress * @return {String} */ public async getDTReserve(account: string, poolAddress: string): Promise { await this.getDTAddress(account, poolAddress) return super.getReserve(account, poolAddress, this.dtAddress) } /** * Buy Data Token from a pool * @param {String} account * @param {String} poolAddress * @param {String} amount Data Token amount * @param {String} oceanAmount Ocean Token amount payed * @param {String} maxPrice Maximum price to pay * @return {any} */ public async buyDT( account: string, poolAddress: string, amount: string, oceanAmount: string, maxPrice: string ): Promise { if (this.oceanAddress == null) { console.error('oceanAddress is not defined') return null } await this.getDTAddress(account, poolAddress) // TODO - check balances first await super.approve( account, this.oceanAddress, poolAddress, this.web3.utils.toWei(oceanAmount) ) return this.swapExactAmountOut( account, poolAddress, this.oceanAddress, oceanAmount, this.dtAddress, amount, maxPrice ) } /** * Sell Data Token * @param {String} account * @param {String} poolAddress * @param {String} amount Data Token amount * @param {String} oceanAmount Ocean Token amount expected * @param {String} maxPrice Minimum price to sell * @return {TransactionReceipt} */ public async sellDT( account: string, poolAddress: string, amount: string, oceanAmount: string, minPrice: string ): Promise { if (this.oceanAddress == null) { console.error('oceanAddress is not defined') return null } await this.getDTAddress(account, poolAddress) return this.swapExactAmountOut( account, poolAddress, this.dtAddress, amount, this.oceanAddress, oceanAmount, minPrice ) } /** * Add Data Token amount to pool liquidity * @param {String} account * @param {String} poolAddress * @param {String} amount Data Token amount * @return {TransactionReceipt} */ public async addDTLiquidity( account: string, poolAddress: string, amount: string ): Promise { await this.getDTAddress(account, poolAddress) await super.approve( account, this.dtAddress, poolAddress, this.web3.utils.toWei(amount) ) const result = await super.joinswapExternAmountIn( account, poolAddress, this.dtAddress, amount, '0' ) return result } /** * Remove Data Token amount from pool liquidity * @param {String} account * @param {String} poolAddress * @param {String} amount Data Token amount * @return {TransactionReceipt} */ public async removeDTLiquidity( account: string, poolAddress: string, amount: string, maximumPoolShares: string ): Promise { await this.getDTAddress(account, poolAddress) // TODO Check balance of PoolShares before doing exit return this.exitswapExternAmountOut( account, poolAddress, this.dtAddress, amount, maximumPoolShares ) } /** * Add Ocean Token amount to pool liquidity * @param {String} account * @param {String} poolAddress * @param {String} amount Ocean Token amount in OCEAN * @return {TransactionReceipt} */ public async addOceanLiquidity( account: string, poolAddress: string, amount: string ): Promise { if (this.oceanAddress == null) { console.error('oceanAddress is not defined') return null } await super.approve( account, this.oceanAddress, poolAddress, this.web3.utils.toWei(amount) ) const result = await super.joinswapExternAmountIn( account, poolAddress, this.oceanAddress, amount, '0' ) return result } /** * Remove Ocean Token amount from pool liquidity * @param {String} account * @param {String} poolAddress * @param {String} amount Ocean Token amount in OCEAN * @return {TransactionReceipt} */ public removeOceanLiquidity( account: string, poolAddress: string, amount: string, maximumPoolShares: string ): Promise { if (this.oceanAddress == null) { console.error('oceanAddress is not defined') return null } // TODO Check balance of PoolShares before doing exit return super.exitswapExternAmountOut( account, poolAddress, this.oceanAddress, amount, maximumPoolShares ) } /** * Get Data Token price from pool * @param {String} account * @param {String} poolAddress * @return {String} */ public async getDTPrice(account: string, poolAddress: string): Promise { if (this.oceanAddress == null) { console.error('oceanAddress is not defined') return null } return this.getOceanNeeded(account, poolAddress, '1') } /** * Search all pools that have Data Token in their composition * @param {String} account * @param {String} dtAddress * @return {String[]} */ public async searchPoolforDT(account: string, dtAddress: string): Promise { const result: string[] = [] const factory = new this.web3.eth.Contract(this.factoryABI, this.factoryAddress, { from: account }) const events = await factory.getPastEvents('BPoolRegistered', { filter: {}, fromBlock: 0, toBlock: 'latest' }) for (let i = 0; i < events.length; i++) { const constituents = await super.getCurrentTokens( account, events[i].returnValues[0] ) if (constituents.includes(dtAddress)) result.push(events[i].returnValues[0]) } return result } public async getOceanNeeded( account: string, poolAddress: string, dtRequired: string ): Promise { await this.getDTAddress(account, poolAddress) const tokenBalanceIn = await this.getReserve(account, poolAddress, this.oceanAddress) const tokenWeightIn = await this.getDenormalizedWeight( account, poolAddress, this.oceanAddress ) const tokenBalanceOut = await this.getReserve(account, poolAddress, this.dtAddress) const tokenWeightOut = await this.getDenormalizedWeight( account, poolAddress, this.dtAddress ) const swapFee = await this.getSwapFee(account, poolAddress) return super.calcInGivenOut( tokenBalanceIn, tokenWeightIn, tokenBalanceOut, tokenWeightOut, dtRequired, swapFee ) } }