diff --git a/.travis.yml b/.travis.yml index 22a952ca..2521ab75 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,9 +21,14 @@ before_script: - cd barge - git checkout v3 - export ADDRESS_FILE="${HOME}/.ocean/ocean-contracts/artifacts/address.json" + - mkdir "${HOME}/.ocean/" + - mkdir "${HOME}/.ocean/ocean-contracts/" + - mkdir "${HOME}/.ocean/ocean-contracts/artifacts" + - touch $ADDRESS_FILE + - echo "{}" >> $ADDRESS_FILE - export AQUARIUS_URI="http://172.15.0.5:5000" - - export DEPLOY_CONTRACTS=true - - export CONTRACTS_VERSION=v0.5.3 + - export DEPLOY_CONTRACTS="true" + - export CONTRACTS_VERSION=v0.5.5 - bash -x start_ocean.sh --no-dashboard 2>&1 > start_ocean.log & - cd .. - ./scripts/waitforcontracts.sh diff --git a/package-lock.json b/package-lock.json index 3afc71ad..dbe71810 100644 --- a/package-lock.json +++ b/package-lock.json @@ -939,9 +939,9 @@ } }, "@oceanprotocol/contracts": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/@oceanprotocol/contracts/-/contracts-0.5.3.tgz", - "integrity": "sha512-gJ8qQACJgxOPIrPE0OFQ09iYXBAisOGg56EmelQlsMUgp0yY0DKgBntDP83S/Ho1yBjGygqfxCjQrPH63hh/PA==" + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/@oceanprotocol/contracts/-/contracts-0.5.5.tgz", + "integrity": "sha512-Omwlh3KxPm2JOuLd6DW4teAQhGaIv0fRTopCvctey0XGsf3DcbJpwS0A0YfgLQnvCyyVMKsiq90YCqpJ3SO/cw==" }, "@octokit/auth-token": { "version": "2.4.2", diff --git a/package.json b/package.json index 7874b86d..63f80027 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ }, "dependencies": { "@ethereum-navigator/navigator": "^0.5.0", - "@oceanprotocol/contracts": "^0.5.3", + "@oceanprotocol/contracts": "^0.5.5", "decimal.js": "^10.2.0", "fs": "0.0.1-security", "lzma": "^2.3.2", diff --git a/scripts/waitforcontracts.sh b/scripts/waitforcontracts.sh index eef49198..77a89ea7 100755 --- a/scripts/waitforcontracts.sh +++ b/scripts/waitforcontracts.sh @@ -3,3 +3,7 @@ if [ "${DEPLOY_CONTRACTS}" = "true" ]; then sleep 2 done fi +cat "barge/start_ocean.log" +ls -lh "${HOME}/.ocean/ocean-contracts/" +ls -lh "${HOME}/.ocean/ocean-contracts/artifacts/" +cat "${HOME}/.ocean/ocean-contracts/artifacts/address.json" diff --git a/src/balancer/OceanPool.ts b/src/balancer/OceanPool.ts index 930d92e6..c66b8997 100644 --- a/src/balancer/OceanPool.ts +++ b/src/balancer/OceanPool.ts @@ -3,6 +3,8 @@ import { AbiItem } from 'web3-utils/types' import { TransactionReceipt } from 'web3-core' import { Pool } from './Pool' import { EventData, Filter } from 'web3-eth-contract' +import BigNumber from 'bignumber.js' +import Decimal from 'decimal.js' declare type PoolTransactionType = 'swap' | 'join' | 'exit' @@ -63,31 +65,43 @@ export class OceanPool extends Pool { fee: string ): Promise { if (this.oceanAddress == null) { - console.error('oceanAddress is not defined') + console.error('ERROR: oceanAddress is not defined') return null } if (parseFloat(fee) > 0.1) { - console.error('Swap fee too high. The maximum allowed swapFee is 0.1 (10%).') + console.error('ERROR: Swap fee too high. The maximum allowed swapFee is 0.1 (10%).') return null } if (parseFloat(weight) > 9 || parseFloat(weight) < 1) { - console.error('Weight out of bounds (min 1, max9)') + console.error('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))) - await this.approve( + let txid + txid = await this.approve( + account, + token, + address, + this.web3.utils.toWei(String(amount)) + ) + if (!txid) { + console.error('ERROR: Failed to call approve DT token') + return null + } + txid = await this.approve( account, this.oceanAddress, address, this.web3.utils.toWei(String(oceanAmount)) ) - - await super.setup( + if (!txid) { + console.error('ERROR: Failed to call approve OCEAN token') + return null + } + txid = await super.setup( account, address, token, @@ -98,7 +112,10 @@ export class OceanPool extends Pool { this.web3.utils.toWei(String(oceanWeight)), this.web3.utils.toWei(fee) ) - + if (!txid) { + console.error('ERROR: Failed to create a new pool') + return null + } return address } @@ -127,14 +144,14 @@ export class OceanPool extends Pool { */ public async getOceanReserve(poolAddress: string): Promise { if (this.oceanAddress == null) { - console.error('oceanAddress is not defined') + console.error('ERROR: oceanAddress is not defined') return null } return super.getReserve(poolAddress, this.oceanAddress) } /** - * Get Data Token balance of a pool + * Get datatoken balance of a pool * @param {String} poolAddress * @return {String} */ @@ -144,10 +161,309 @@ export class OceanPool extends Pool { } /** - * Buy Data Token from a pool + * Returns max amount that you can buy. + * @param poolAddress + * @param tokenAddress + */ + public async getMaxBuyQuantity( + poolAddress: string, + tokenAddress: string + ): Promise { + const balance = await super.getReserve(poolAddress, tokenAddress) + return String(parseFloat(balance) / 3) + } + + /** + * Returns max amount of OCEAN that you can buy. + * @param poolAddress + * @param tokenAddress + */ + public async getOceanMaxBuyQuantity(poolAddress: string): Promise { + return this.getMaxBuyQuantity(poolAddress, this.oceanAddress) + } + + /** + * Returns max amount of DT that you can buy. + * @param poolAddress + * @param tokenAddress + */ + public async getDTMaxBuyQuantity(poolAddress: string): Promise { + return this.getMaxBuyQuantity(poolAddress, await this.getDTAddress(poolAddress)) + } + + /** + * Returns tokenInAmount required to get tokenOutAmount + * @param poolAddress + * @param tokenInAddress + * @param tokenOutAddress + * @param tokenOutAmount + */ + public async calcInGivenOut( + poolAddress: string, + tokenInAddress: string, + tokenOutAddress: string, + tokenOutAmount: string + ): Promise { + const result = await super.calcInGivenOut( + poolAddress, + await super.getReserve(poolAddress, tokenInAddress), + await super.getDenormalizedWeight(poolAddress, tokenInAddress), + await super.getReserve(poolAddress, tokenOutAddress), + await super.getDenormalizedWeight(poolAddress, tokenOutAddress), + tokenOutAmount, + await this.getSwapFee(poolAddress) + ) + + return result + } + + /** + * Returns tokenOutAmount given tokenInAmount + * @param poolAddress + * @param tokenInAddress + * @param tokenOutAddress + * @param tokenInAmount + */ + public async calcOutGivenIn( + poolAddress: string, + tokenInAddress: string, + tokenOutAddress: string, + tokenInAmount: string + ): Promise { + const result = await super.calcOutGivenIn( + poolAddress, + await super.getReserve(poolAddress, tokenInAddress), + await super.getDenormalizedWeight(poolAddress, tokenInAddress), + await super.getReserve(poolAddress, tokenOutAddress), + await super.getDenormalizedWeight(poolAddress, tokenOutAddress), + tokenInAmount, + await super.getSwapFee(poolAddress) + ) + + return result + } + + /** + * Returns no of shares receved for adding a token to the pool + * @param poolAddress + * @param tokenInAddress + * @param tokenInAmount + */ + public async calcPoolOutGivenSingleIn( + poolAddress: string, + tokenInAddress: string, + tokenInAmount: string + ): Promise { + const result = super.calcPoolOutGivenSingleIn( + poolAddress, + await super.getReserve(poolAddress, tokenInAddress), + await super.getDenormalizedWeight(poolAddress, tokenInAddress), + await super.getPoolSharesTotalSupply(poolAddress), + await super.getTotalDenormalizedWeight(poolAddress), + tokenInAmount, + await super.getSwapFee(poolAddress) + ) + return result + } + + /** + * Returns no of tokens required to get a specific no of poolShares + * @param poolAddress + * @param tokenInAddress + * @param poolShares + */ + public async calcSingleInGivenPoolOut( + poolAddress: string, + tokenInAddress: string, + poolShares: string + ): Promise { + const result = super.calcSingleInGivenPoolOut( + poolAddress, + await super.getReserve(poolAddress, tokenInAddress), + await super.getDenormalizedWeight(poolAddress, tokenInAddress), + await super.getPoolSharesTotalSupply(poolAddress), + await super.getTotalDenormalizedWeight(poolAddress), + poolShares, + await super.getSwapFee(poolAddress) + ) + return result + } + + /** + * Returns no of tokens received for spending a specific no of poolShares + * @param poolAddress + * @param tokenOutAddress + * @param poolShares + */ + public async calcSingleOutGivenPoolIn( + poolAddress: string, + tokenOutAddress: string, + poolShares: string + ): Promise { + const result = super.calcSingleOutGivenPoolIn( + poolAddress, + await super.getReserve(poolAddress, tokenOutAddress), + await super.getDenormalizedWeight(poolAddress, tokenOutAddress), + await super.getPoolSharesTotalSupply(poolAddress), + await super.getTotalDenormalizedWeight(poolAddress), + poolShares, + await super.getSwapFee(poolAddress) + ) + return result + } + + /** + * Returns no of pool shares required to receive a specified amount of tokens + * @param poolAddress + * @param tokenOutAddress + * @param tokenOutAmount + */ + public async calcPoolInGivenSingleOut( + poolAddress: string, + tokenOutAddress: string, + tokenOutAmount: string + ): Promise { + const result = super.calcPoolInGivenSingleOut( + poolAddress, + await super.getReserve(poolAddress, tokenOutAddress), + await super.getDenormalizedWeight(poolAddress, tokenOutAddress), + await super.getPoolSharesTotalSupply(poolAddress), + await super.getTotalDenormalizedWeight(poolAddress), + tokenOutAmount, + await super.getSwapFee(poolAddress) + ) + return result + } + + /** + * Returns no of pool shares required to receive specified amount of DT + * @param poolAddress + * @param dtAmount + */ + public async getPoolSharesRequiredToRemoveDT( + poolAddress: string, + dtAmount: string + ): Promise { + const dtAddress = await this.getDTAddress(poolAddress) + return this.calcPoolInGivenSingleOut(poolAddress, dtAddress, dtAmount) + } + + /** + * Returns DT amnount received after spending poolShares + * @param poolAddress + * @param poolShares + */ + public async getDTRemovedforPoolShares( + poolAddress: string, + poolShares: string + ): Promise { + const dtAddress = await this.getDTAddress(poolAddress) + return this.calcSingleOutGivenPoolIn(poolAddress, dtAddress, poolShares) + } + + /** + * Returns no of pool shares required to receive specified amount of DT + * @param poolAddress + * @param dtAmount + */ + public async getPoolSharesRequiredToRemoveOcean( + poolAddress: string, + oceanAmount: string + ): Promise { + return this.calcPoolInGivenSingleOut(poolAddress, this.oceanAddress, oceanAmount) + } + + /** + * Returns Ocean amnount received after spending poolShares + * @param poolAddress + * @param poolShares + */ + public async getOceanRemovedforPoolShares( + poolAddress: string, + poolShares: string + ): Promise { + return this.calcSingleOutGivenPoolIn(poolAddress, this.oceanAddress, poolShares) + } + + /** + * Returns max DT amount that you can add to the pool + * @param poolAddress + */ + public async getDTMaxAddLiquidity(poolAddress: string): Promise { + const dtAddress = await this.getDTAddress(poolAddress) + return this.getMaxAddLiquidity(poolAddress, dtAddress) + } + + /** + * Returns max Ocean amount that you can add to the pool + * @param poolAddress + */ + public async getOceanMaxAddLiquidity(poolAddress: string): Promise { + return this.getMaxAddLiquidity(poolAddress, this.oceanAddress) + } + + /** + * Returns max amount of tokens that you can add to the pool + * @param poolAddress + * @param tokenAddress + */ + public async getMaxAddLiquidity( + poolAddress: string, + tokenAddress: string + ): Promise { + const balance = await super.getReserve(poolAddress, tokenAddress) + if (parseFloat(balance) > 0) { + const result = new BigNumber(this.web3.utils.toWei(balance)) + .dividedBy(3) + .integerValue(BigNumber.ROUND_DOWN) + .minus(1) + return this.web3.utils.fromWei(result.toString()) + } else return '0' + } + + /** + * Returns max amount of tokens that you can withdraw from the pool + * @param poolAddress + * @param tokenAddress + */ + public async getMaxRemoveLiquidity( + poolAddress: string, + tokenAddress: string + ): Promise { + const balance = await super.getReserve(poolAddress, tokenAddress) + if (parseFloat(balance) > 0) { + const result = new BigNumber(this.web3.utils.toWei(balance)) + .dividedBy(4) + .integerValue(BigNumber.ROUND_DOWN) + .minus(1) + return this.web3.utils.fromWei(result.toString()) + } else return '0' + } + + /** + * Returns max amount of DT that you can withdraw from the pool + * @param poolAddress + * @param tokenAddress + */ + public async getDTMaxRemoveLiquidity(poolAddress: string): Promise { + const dtAddress = await this.getDTAddress(poolAddress) + return this.getMaxRemoveLiquidity(poolAddress, dtAddress) + } + + /** + * Returns max amount of Ocean that you can withdraw from the pool + * @param poolAddress + * @param tokenAddress + */ + public async getOceanMaxRemoveLiquidity(poolAddress: string): Promise { + return this.getMaxRemoveLiquidity(poolAddress, this.oceanAddress) + } + + /** + * Buy datatoken from a pool * @param {String} account * @param {String} poolAddress - * @param {String} amount Data Token amount + * @param {String} amount datatoken amount * @param {String} oceanAmount Ocean Token amount payed * @param {String} maxPrice Maximum price to pay * @return {TransactionReceipt} @@ -155,40 +471,54 @@ export class OceanPool extends Pool { public async buyDT( account: string, poolAddress: string, - amount: string, - oceanAmount: string, - maxPrice: string + dtAmountWanted: string, + maxOceanAmount: string, + maxPrice?: string ): Promise { if (this.oceanAddress == null) { - console.error('oceanAddress is not defined') + console.error('ERROR: undefined ocean token contract address') return null } const dtAddress = await this.getDTAddress(poolAddress) + if ( + parseFloat(dtAmountWanted) > parseFloat(await this.getDTMaxBuyQuantity(poolAddress)) + ) { + console.error('ERROR: Buy quantity exceeds quantity allowed') + return null + } + const calcInGivenOut = await this.getOceanNeeded(poolAddress, dtAmountWanted) + if (parseFloat(calcInGivenOut) > parseFloat(maxOceanAmount)) { + console.error('ERROR: Not enough Ocean Tokens') + return null + } // TODO - check balances first - await super.approve( + const txid = await super.approve( account, this.oceanAddress, poolAddress, - this.web3.utils.toWei(oceanAmount) + this.web3.utils.toWei(maxOceanAmount) ) - + if (!txid) { + console.error('ERROR: OCEAN approve failed') + return null + } return this.swapExactAmountOut( account, poolAddress, this.oceanAddress, - oceanAmount, + maxOceanAmount, dtAddress, - amount, + dtAmountWanted, maxPrice ) } /** - * Sell Data Token + * Sell datatoken * @param {String} account * @param {String} poolAddress - * @param {String} amount Data Token amount + * @param {String} amount datatoken amount to be sold * @param {String} oceanAmount Ocean Token amount expected * @param {String} maxPrice Minimum price to sell * @return {TransactionReceipt} @@ -196,31 +526,54 @@ export class OceanPool extends Pool { public async sellDT( account: string, poolAddress: string, - amount: string, - oceanAmount: string, - minPrice: string + dtAmount: string, + oceanAmountWanted: string, + maxPrice?: string ): Promise { if (this.oceanAddress == null) { - console.error('oceanAddress is not defined') + console.error('ERROR: oceanAddress is not defined') return null } const dtAddress = await this.getDTAddress(poolAddress) - return this.swapExactAmountOut( + if ( + parseFloat(oceanAmountWanted) > + parseFloat(await this.getOceanMaxBuyQuantity(poolAddress)) + ) { + console.error('ERROR: Buy quantity exceeds quantity allowed') + return null + } + const calcOutGivenIn = await this.getOceanReceived(poolAddress, dtAmount) + + if (parseFloat(calcOutGivenIn) < parseFloat(oceanAmountWanted)) { + console.error('ERROR: Not enough datatokens') + return null + } + const txid = await super.approve( + account, + dtAddress, + poolAddress, + this.web3.utils.toWei(dtAmount) + ) + if (!txid) { + console.error('ERROR: DT approve failed') + return null + } + return this.swapExactAmountIn( account, poolAddress, dtAddress, - amount, + dtAmount, this.oceanAddress, - oceanAmount, - minPrice + oceanAmountWanted, + maxPrice ) } /** - * Add Data Token amount to pool liquidity + * Add datatoken amount to pool liquidity * @param {String} account * @param {String} poolAddress - * @param {String} amount Data Token amount + * @param {String} amount datatoken amount * @return {TransactionReceipt} */ public async addDTLiquidity( @@ -229,7 +582,21 @@ export class OceanPool extends Pool { amount: string ): Promise { const dtAddress = await this.getDTAddress(poolAddress) - await super.approve(account, dtAddress, poolAddress, this.web3.utils.toWei(amount)) + const maxAmount = await this.getMaxAddLiquidity(poolAddress, dtAddress) + if (parseFloat(amount) > parseFloat(maxAmount)) { + console.error('ERROR: Too much reserve to add') + return null + } + const txid = await super.approve( + account, + dtAddress, + poolAddress, + this.web3.utils.toWei(amount) + ) + if (!txid) { + console.error('ERROR: DT approve failed') + return null + } const result = await super.joinswapExternAmountIn( account, poolAddress, @@ -241,10 +608,10 @@ export class OceanPool extends Pool { } /** - * Remove Data Token amount from pool liquidity + * Remove datatoken amount from pool liquidity * @param {String} account * @param {String} poolAddress - * @param {String} amount Data Token amount + * @param {String} amount datatoken amount * @return {TransactionReceipt} */ public async removeDTLiquidity( @@ -254,7 +621,23 @@ export class OceanPool extends Pool { maximumPoolShares: string ): Promise { const dtAddress = await this.getDTAddress(poolAddress) - // TODO Check balance of PoolShares before doing exit + const maxAmount = await this.getDTMaxRemoveLiquidity(poolAddress) + if (parseFloat(amount) > parseFloat(maxAmount)) { + console.error('ERROR: Too much reserve to remove') + return null + } + const usershares = await this.sharesBalance(account, poolAddress) + if (parseFloat(usershares) < parseFloat(maximumPoolShares)) { + console.error('ERROR: Not enough poolShares') + return null + } + if ( + parseFloat(maximumPoolShares) < + parseFloat(await this.getPoolSharesRequiredToRemoveDT(poolAddress, amount)) + ) { + console.error('ERROR: Not enough poolShares') + return null + } return this.exitswapExternAmountOut( account, poolAddress, @@ -277,15 +660,24 @@ export class OceanPool extends Pool { amount: string ): Promise { if (this.oceanAddress == null) { - console.error('oceanAddress is not defined') + console.error('ERROR: oceanAddress is not defined') return null } - await super.approve( + const maxAmount = await this.getOceanMaxAddLiquidity(poolAddress) + if (parseFloat(amount) > parseFloat(maxAmount)) { + console.error('ERROR: Too much reserve to add') + return null + } + const txid = await super.approve( account, this.oceanAddress, poolAddress, this.web3.utils.toWei(amount) ) + if (!txid) { + console.error('ERROR: OCEAN approve failed') + return null + } const result = await super.joinswapExternAmountIn( account, poolAddress, @@ -303,17 +695,33 @@ export class OceanPool extends Pool { * @param {String} amount Ocean Token amount in OCEAN * @return {TransactionReceipt} */ - public removeOceanLiquidity( + public async removeOceanLiquidity( account: string, poolAddress: string, amount: string, maximumPoolShares: string ): Promise { if (this.oceanAddress == null) { - console.error('oceanAddress is not defined') + console.error('ERROR: oceanAddress is not defined') + return null + } + const maxAmount = await this.getOceanMaxRemoveLiquidity(poolAddress) + if (parseFloat(amount) > parseFloat(maxAmount)) { + console.error('ERROR: Too much reserve to remove') + return null + } + const usershares = await this.sharesBalance(account, poolAddress) + if (parseFloat(usershares) < parseFloat(maximumPoolShares)) { + console.error('ERROR: Not enough poolShares') + return null + } + if ( + parseFloat(maximumPoolShares) < + parseFloat(await this.getPoolSharesRequiredToRemoveOcean(poolAddress, amount)) + ) { + console.error('ERROR: Not enough poolShares') return null } - // TODO Check balance of PoolShares before doing exit return super.exitswapExternAmountOut( account, poolAddress, @@ -324,20 +732,45 @@ export class OceanPool extends Pool { } /** - * Get Data Token price from pool + * Remove pool liquidity + * @param {String} account + * @param {String} poolAddress + * @param {String} poolShares + * @param {String} minDT Minimum DT expected (defaults 0) + * @param {String} poolShares Minim Ocean expected (defaults 0) + * @return {TransactionReceipt} + */ + public async removePoolLiquidity( + account: string, + poolAddress: string, + poolShares: string, + minDT = '0', + minOcean = '0' + ): Promise { + const usershares = await this.sharesBalance(account, poolAddress) + if (parseFloat(usershares) < parseFloat(poolShares)) { + console.error('ERROR: Not enough poolShares') + return null + } + + return this.exitPool(account, poolAddress, poolShares, [minDT, minOcean]) + } + + /** + * Get datatoken price from pool * @param {String} poolAddress * @return {String} */ public async getDTPrice(poolAddress: string): Promise { if (this.oceanAddress == null) { - console.error('oceanAddress is not defined') + console.error('ERROR: oceanAddress is not defined') return null } return this.getOceanNeeded(poolAddress, '1') } /** - * Search all pools that have Data Token in their composition + * Search all pools that have datatoken in their composition * @param {String} dtAddress * @return {String[]} */ @@ -358,19 +791,17 @@ export class OceanPool extends Pool { public async getOceanNeeded(poolAddress: string, dtRequired: string): Promise { const dtAddress = await this.getDTAddress(poolAddress) - const tokenBalanceIn = await this.getReserve(poolAddress, this.oceanAddress) - const tokenWeightIn = await this.getDenormalizedWeight(poolAddress, this.oceanAddress) - const tokenBalanceOut = await this.getReserve(poolAddress, dtAddress) - const tokenWeightOut = await this.getDenormalizedWeight(poolAddress, dtAddress) - const swapFee = await this.getSwapFee(poolAddress) - return super.calcInGivenOut( - tokenBalanceIn, - tokenWeightIn, - tokenBalanceOut, - tokenWeightOut, - dtRequired, - swapFee - ) + return this.calcInGivenOut(poolAddress, this.oceanAddress, dtAddress, dtRequired) + } + + public async getOceanReceived(poolAddress: string, dtSold: string): Promise { + const dtAddress = await this.getDTAddress(poolAddress) + return this.calcOutGivenIn(poolAddress, dtAddress, this.oceanAddress, dtSold) + } + + public async getDTNeeded(poolAddress: string, OceanRequired: string): Promise { + const dtAddress = await this.getDTAddress(poolAddress) + return this.calcInGivenOut(poolAddress, dtAddress, this.oceanAddress, OceanRequired) } /** diff --git a/src/balancer/Pool.ts b/src/balancer/Pool.ts index c5b2799e..c09f5955 100644 --- a/src/balancer/Pool.ts +++ b/src/balancer/Pool.ts @@ -2,9 +2,13 @@ import Web3 from 'web3' import { AbiItem } from 'web3-utils/types' import { TransactionReceipt } from 'web3-core' import Decimal from 'decimal.js' +import BigNumber from 'bignumber.js' import jsonpoolABI from '@oceanprotocol/contracts/artifacts/BPool.json' import { PoolFactory } from './PoolFactory' +const MaxUint256: BigNumber = new BigNumber( + '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' +) /** * Provides an interface to Balancer BPool & BFactory */ @@ -270,6 +274,23 @@ export class Pool extends PoolFactory { return result } + /** + * Get total supply of pool shares + * @param {String} poolAddress + * @return {String} + */ + async getPoolSharesTotalSupply(poolAddress: string): Promise { + const pool = new this.web3.eth.Contract(this.poolABI, poolAddress) + let amount = null + try { + const result = await pool.methods.totalSupply().call() + amount = this.web3.utils.fromWei(result) + } catch (e) { + console.error(e) + } + return amount + } + /** * Get tokens composing this pool * @param {String} poolAddress @@ -483,7 +504,7 @@ export class Pool extends PoolFactory { tokenAmountIn: string, tokenOut: string, minAmountOut: string, - maxPrice: string + maxPrice?: string ): Promise { const pool = new this.web3.eth.Contract(this.poolABI, poolAddress, { from: account @@ -496,7 +517,7 @@ export class Pool extends PoolFactory { this.web3.utils.toWei(tokenAmountIn), tokenOut, this.web3.utils.toWei(minAmountOut), - this.web3.utils.toWei(maxPrice) + maxPrice ? this.web3.utils.toWei(maxPrice) : MaxUint256 ) .send({ from: account, gas: this.GASLIMIT_DEFAULT }) } catch (e) { @@ -523,7 +544,7 @@ export class Pool extends PoolFactory { maxAmountIn: string, tokenOut: string, minAmountOut: string, - maxPrice: string + maxPrice?: string ): Promise { const pool = new this.web3.eth.Contract(this.poolABI, poolAddress, { from: account @@ -536,7 +557,7 @@ export class Pool extends PoolFactory { this.web3.utils.toWei(maxAmountIn), tokenOut, this.web3.utils.toWei(minAmountOut), - this.web3.utils.toWei(maxPrice) + maxPrice ? this.web3.utils.toWei(maxPrice) : MaxUint256 ) .send({ from: account, gas: this.GASLIMIT_DEFAULT }) } catch (e) { @@ -594,7 +615,7 @@ export class Pool extends PoolFactory { account: string, poolAddress: string, poolAmountIn: string, - minAmountsOut: string + minAmountsOut: string[] ): Promise { const pool = new this.web3.eth.Contract(this.poolABI, poolAddress, { from: account @@ -799,6 +820,7 @@ export class Pool extends PoolFactory { } public async calcInGivenOut( + poolAddress: string, tokenBalanceIn: string, tokenWeightIn: string, tokenBalanceOut: string, @@ -806,14 +828,168 @@ export class Pool extends PoolFactory { tokenAmountOut: string, swapFee: string ): Promise { - const weightRatio = new Decimal(tokenWeightOut).div(new Decimal(tokenWeightIn)) - const diff = new Decimal(tokenBalanceOut).minus(tokenAmountOut) - const y = new Decimal(tokenBalanceOut).div(diff) - const foo = y.pow(weightRatio).minus(new Decimal(1)) - const tokenAmountIn = new Decimal(tokenBalanceIn) - .times(foo) - .div(new Decimal(1).minus(new Decimal(swapFee))) + const pool = new this.web3.eth.Contract(this.poolABI, poolAddress) + let amount = null + try { + const result = await pool.methods + .calcInGivenOut( + this.web3.utils.toWei(tokenBalanceIn), + this.web3.utils.toWei(tokenWeightIn), + this.web3.utils.toWei(tokenBalanceOut), + this.web3.utils.toWei(tokenWeightOut), + this.web3.utils.toWei(tokenAmountOut), + this.web3.utils.toWei(swapFee) + ) + .call() + amount = this.web3.utils.fromWei(result) + } catch (e) { + console.error(e) + } + return amount + } - return tokenAmountIn.toString() + public async calcOutGivenIn( + poolAddress: string, + tokenBalanceIn: string, + tokenWeightIn: string, + tokenBalanceOut: string, + tokenWeightOut: string, + tokenAmountIn: string, + swapFee: string + ): Promise { + const pool = new this.web3.eth.Contract(this.poolABI, poolAddress) + let amount = null + try { + const result = await pool.methods + .calcOutGivenIn( + this.web3.utils.toWei(tokenBalanceIn), + this.web3.utils.toWei(tokenWeightIn), + this.web3.utils.toWei(tokenBalanceOut), + this.web3.utils.toWei(tokenWeightOut), + this.web3.utils.toWei(tokenAmountIn), + this.web3.utils.toWei(swapFee) + ) + .call() + amount = this.web3.utils.fromWei(result) + } catch (e) { + console.error(e) + } + return amount + } + + public async calcPoolOutGivenSingleIn( + poolAddress: string, + tokenBalanceIn: string, + tokenWeightIn: string, + poolSupply: string, + totalWeight: string, + tokenAmountIn: string, + swapFee: string + ): Promise { + const pool = new this.web3.eth.Contract(this.poolABI, poolAddress) + let amount = null + try { + const result = await pool.methods + .calcPoolOutGivenSingleIn( + this.web3.utils.toWei(tokenBalanceIn), + this.web3.utils.toWei(tokenWeightIn), + this.web3.utils.toWei(poolSupply), + this.web3.utils.toWei(totalWeight), + this.web3.utils.toWei(tokenAmountIn), + this.web3.utils.toWei(swapFee) + ) + .call() + amount = this.web3.utils.fromWei(result) + } catch (e) { + console.error(e) + } + return amount + } + + public async calcSingleInGivenPoolOut( + poolAddress: string, + tokenBalanceIn: string, + tokenWeightIn: string, + poolSupply: string, + totalWeight: string, + poolAmountOut: string, + swapFee: string + ): Promise { + const pool = new this.web3.eth.Contract(this.poolABI, poolAddress) + let amount = null + try { + const result = await pool.methods + .calcSingleInGivenPoolOut( + this.web3.utils.toWei(tokenBalanceIn), + this.web3.utils.toWei(tokenWeightIn), + this.web3.utils.toWei(poolSupply), + this.web3.utils.toWei(totalWeight), + this.web3.utils.toWei(poolAmountOut), + this.web3.utils.toWei(swapFee) + ) + .call() + amount = this.web3.utils.fromWei(result) + } catch (e) { + console.error(e) + } + return amount + } + + public async calcSingleOutGivenPoolIn( + poolAddress: string, + tokenBalanceOut: string, + tokenWeightOut: string, + poolSupply: string, + totalWeight: string, + poolAmountIn: string, + swapFee: string + ): Promise { + const pool = new this.web3.eth.Contract(this.poolABI, poolAddress) + let amount = null + try { + const result = await pool.methods + .calcSingleOutGivenPoolIn( + this.web3.utils.toWei(tokenBalanceOut), + this.web3.utils.toWei(tokenWeightOut), + this.web3.utils.toWei(poolSupply), + this.web3.utils.toWei(totalWeight), + this.web3.utils.toWei(poolAmountIn), + this.web3.utils.toWei(swapFee) + ) + .call() + amount = this.web3.utils.fromWei(result) + } catch (e) { + console.error(e) + } + return amount + } + + public async calcPoolInGivenSingleOut( + poolAddress: string, + tokenBalanceOut: string, + tokenWeightOut: string, + poolSupply: string, + totalWeight: string, + tokenAmountOut: string, + swapFee: string + ): Promise { + const pool = new this.web3.eth.Contract(this.poolABI, poolAddress) + let amount = null + try { + const result = await pool.methods + .calcPoolInGivenSingleOut( + this.web3.utils.toWei(tokenBalanceOut), + this.web3.utils.toWei(tokenWeightOut), + this.web3.utils.toWei(poolSupply), + this.web3.utils.toWei(totalWeight), + this.web3.utils.toWei(tokenAmountOut), + this.web3.utils.toWei(swapFee) + ) + .call() + amount = this.web3.utils.fromWei(result) + } catch (e) { + console.error(e) + } + return amount } } diff --git a/src/datatokens/Datatokens.ts b/src/datatokens/Datatokens.ts index bfe34f98..62d01f5a 100644 --- a/src/datatokens/Datatokens.ts +++ b/src/datatokens/Datatokens.ts @@ -417,4 +417,13 @@ export class DataTokens { } return null } + + public getStartOrderEventSignature(): string { + const abi = this.datatokensABI as AbiItem[] + const eventdata = abi.find(function (o) { + if (o.name === 'OrderStarted' && o.type === 'event') return o + }) + const topic = this.web3.eth.abi.encodeEventSignature(eventdata as any) + return topic + } } diff --git a/src/ocean/Assets.ts b/src/ocean/Assets.ts index 1d3232a4..6ddd3cfe 100644 --- a/src/ocean/Assets.ts +++ b/src/ocean/Assets.ts @@ -612,15 +612,12 @@ export class Assets extends Instantiable { ): Promise { const results: Order[] = [] const address = account.getId().toLowerCase() + const { datatokens } = this.ocean + const topic1 = '0x000000000000000000000000' + address.substring(2) const events = await this.web3.eth.getPastLogs({ - topics: [ - [ - '0xe1c4fa794edfa8f619b8257a077398950357b9c6398528f94480307352f9afcc', - null, - '0x000000000000000000000000' + address.substring(address.length - 40) - ] - ], - fromBlock: fromBlock || 0 + topics: [[datatokens.getStartOrderEventSignature(), null, topic1]], + fromBlock: fromBlock || 0, + toBlock: 'latest' }) for (let i = 0; i < events.length; i++) { const order: Order = { @@ -631,18 +628,20 @@ export class Assets extends Instantiable { consumer: '0x' + events[i].topics[1].substring(events[i].topics[1].length - 40), payer: '0x' + events[i].topics[2].substring(events[i].topics[2].length - 40) } - const params = this.web3.eth.abi.decodeParameters( - ['uint256', 'uint256', 'uint256', 'uint256'], - events[i].data - ) - order.serviceId = parseInt(params[1]) - order.timestamp = parseInt(params[2]) - order.amount = this.web3.utils.fromWei(params[0]) - order.did = didPrefixed(didNoZeroX(order.dtAddress)) - const service = await this.getServiceByIndex(order.did, order.serviceId) - order.serviceType = service.type - if (!serviceType || (serviceType && serviceType === service.type)) - results.push(order) + try { + const params = this.web3.eth.abi.decodeParameters( + ['uint256', 'uint256', 'uint256', 'uint256'], + events[i].data + ) + order.serviceId = parseInt(params[1]) + order.timestamp = parseInt(params[2]) + order.amount = this.web3.utils.fromWei(params[0]) + order.did = didPrefixed(didNoZeroX(order.dtAddress)) + const service = await this.getServiceByIndex(order.did, order.serviceId) + order.serviceType = service.type + if (!serviceType || (serviceType && serviceType === service.type)) + results.push(order) + } catch (e) {} } return results } diff --git a/src/utils/ConfigHelper.ts b/src/utils/ConfigHelper.ts index 13aac9d6..b146a530 100644 --- a/src/utils/ConfigHelper.ts +++ b/src/utils/ConfigHelper.ts @@ -68,7 +68,7 @@ const configs: ConfigHelperConfig[] = [ export class ConfigHelper { /* Load contract addresses from env ADDRESS_FILE (generated by ocean-contracts) */ - public getAddressesFromEnv(): Partial { + public getAddressesFromEnv(network: string): Partial { try { const data = JSON.parse( fs.readFileSync( @@ -78,13 +78,14 @@ export class ConfigHelper { ) ) - const { DTFactory, BFactory, FixedRateExchange, Metadata } = data?.ganache + const { DTFactory, BFactory, FixedRateExchange, Metadata, Ocean } = data[network] const configAddresses: Partial = { factoryAddress: DTFactory, poolFactoryAddress: BFactory, fixedRateExchangeAddress: FixedRateExchange, metadataContractAddress: Metadata, + oceanTokenAddress: Ocean, ...(process.env.AQUARIUS_URI && { metadataStoreUri: process.env.AQUARIUS_URI }) } @@ -107,10 +108,8 @@ export class ConfigHelper { return null } - if (network === 'development') { - const contractAddressesConfig = this.getAddressesFromEnv() - config = { ...config, ...contractAddressesConfig } - } + const contractAddressesConfig = this.getAddressesFromEnv(config.network) + config = { ...config, ...contractAddressesConfig } const nodeUri = infuraProjectId ? `${config.nodeUri}/${infuraProjectId}` diff --git a/test/integration/ComputeFlow.test.ts b/test/integration/ComputeFlow.test.ts index f47661bb..ede18100 100644 --- a/test/integration/ComputeFlow.test.ts +++ b/test/integration/ComputeFlow.test.ts @@ -283,7 +283,7 @@ describe('Compute flow', () => { tokenAddressAlgorithm ) assert(algorithmAsset.dataToken === tokenAddressAlgorithm) - await sleep(6000) + await sleep(60000) }) it('Alice mints 100 DTs and tranfers them to the compute marketplace', async () => { @@ -445,7 +445,7 @@ describe('Compute flow', () => { alice ) assert(newDdo !== null) - await sleep(6000) + await sleep(60000) const metaData = await ocean.assets.getServiceByType(ddo.id, 'compute') assert.equal( metaData.attributes.main.privacy.allowRawAlgorithm, diff --git a/test/integration/Marketplaceflow.test.ts b/test/integration/Marketplaceflow.test.ts index 6b3e538e..631c6877 100644 --- a/test/integration/Marketplaceflow.test.ts +++ b/test/integration/Marketplaceflow.test.ts @@ -112,7 +112,7 @@ describe('Marketplace flow', () => { ) ddo = await ocean.assets.create(asset, alice, [service1], tokenAddress) assert(ddo.dataToken === tokenAddress) - await sleep(6000) + await sleep(60000) }) it('Alice mints 100 tokens', async () => { @@ -212,7 +212,7 @@ describe('Marketplace flow', () => { } const newDdo = await ocean.assets.editMetadata(ddo.id, newMetaData, alice) assert(newDdo !== null) - await sleep(6000) + await sleep(60000) const metaData = await ocean.assets.getServiceByType(ddo.id, 'metadata') assert.equal(metaData.attributes.main.name, newMetaData.title) assert.equal( diff --git a/test/unit/balancer/Balancer.test.ts b/test/unit/balancer/Balancer.test.ts index e166088b..d5208885 100644 --- a/test/unit/balancer/Balancer.test.ts +++ b/test/unit/balancer/Balancer.test.ts @@ -27,7 +27,7 @@ describe('Balancer flow', () => { let contracts: TestContractHandler let datatoken: DataTokens let tokenAddress: string - let consoleDebug: false + let consoleDebug: true let greatPool: string const tokenAmount = '1000' const transferAmount = '200' @@ -171,26 +171,55 @@ describe('Balancer flow', () => { const requiredOcean = await Pool.getOceanNeeded(alicePoolAddress, '1') assert(Number(requiredOcean) > 0) }) + it('Get amount of DT needed to buy 1 Ocean', async () => { + const requiredOcean = await Pool.getDTNeeded(alicePoolAddress, '1') + assert(Number(requiredOcean) > 0) + }) it('Bob should search for pools with this DT', async () => { const pools = await Pool.searchPoolforDT(tokenAddress) assert(pools.length > 0) greatPool = pools[0] }) - it('Bob should buy a DT ', async () => { + it('Bob should buy 2 DT ', async () => { const maxPrice = parseFloat(currentDtPrice) * 2 - await Pool.buyDT(bob, greatPool, '1', '2', String(maxPrice)) + await Pool.buyDT(bob, greatPool, '2', '4') const bobDtBalance = await datatoken.balance(tokenAddress, bob) const bobOceanBalance = await datatoken.balance(oceanTokenAddress, bob) assert(Number(bobDtBalance) > 0) assert(Number(bobOceanBalance) > 0) }) + it('Bob should sell 1 DT ', async () => { + const maxPrice = parseFloat(currentDtPrice) * 2 + await Pool.sellDT(bob, greatPool, '1', '1') + const bobDtBalance = await datatoken.balance(tokenAddress, bob) + const bobOceanBalance = await datatoken.balance(oceanTokenAddress, bob) + assert(Number(bobDtBalance) === 1) + assert(Number(bobOceanBalance) > 0) + }) + it('Bob should get maximum DT liquidity that he can add to pool ', async () => { + const maxDT = await Pool.getDTMaxAddLiquidity(greatPool) + if (consoleDebug) console.error('maxDT:' + maxDT) + assert(parseFloat(maxDT) > 0) + }) + + it('Bob should fail to add more than maximum DT liquidity that he can add to pool ', async () => { + const maxDT = await Pool.getDTMaxAddLiquidity(greatPool) + const tx = await Pool.addDTLiquidity(bob, greatPool, String(parseFloat(maxDT) * 2)) + assert(tx === null) + }) it('Bob should add DT liquidity to pool ', async () => { + const maxDT = await Pool.getDTMaxAddLiquidity(greatPool) + if (consoleDebug) console.error('maxDT:' + maxDT) const currentDtReserve = await Pool.getDTReserve(greatPool) if (consoleDebug) console.log('currentDtReserve:' + currentDtReserve) const bobDtBalance = await datatoken.balance(tokenAddress, bob) if (consoleDebug) console.log('BOB DT Balance:' + bobDtBalance) - await Pool.addDTLiquidity(bob, greatPool, bobDtBalance) + await Pool.addDTLiquidity( + bob, + greatPool, + String(Math.min(parseFloat(maxDT), parseFloat(bobDtBalance))) + ) const newbobDtBalance = await datatoken.balance(tokenAddress, bob) @@ -204,7 +233,37 @@ describe('Balancer flow', () => { assert(parseFloat(newDtReserve) > parseFloat(currentDtReserve)) assert(parseFloat(sharesBalance) > 0) }) + it('Bob should get maximum DT liquidity that he can remove from pool ', async () => { + const maxDT = await Pool.getDTMaxRemoveLiquidity(greatPool) + if (consoleDebug) console.log('maxDT:' + maxDT) + assert(parseFloat(maxDT) > 0) + }) + it('Bob should know how many Pool Shares he needs to remove 1 DT ', async () => { + const poolShares = await Pool.getPoolSharesRequiredToRemoveDT(greatPool, '1') + if (consoleDebug) console.log('poolShares:' + poolShares) + assert(parseFloat(poolShares) > 0) + }) + it('Bob should know how many DT gets in exchange of his Pool Shares', async () => { + const poolShares = await Pool.getDTRemovedforPoolShares( + greatPool, + await Pool.sharesBalance(bob, greatPool) + ) + if (consoleDebug) console.log('poolShares:' + poolShares) + assert(parseFloat(poolShares) > 0) + }) + it('Bob should fail to remove more than maximum DT liquidity that he can remove from the pool ', async () => { + const maxDT = await Pool.getDTMaxRemoveLiquidity(greatPool) + if (consoleDebug) console.log('maxDT:' + maxDT) + const poolShares = await Pool.sharesBalance(bob, greatPool) + const tx = await Pool.removeDTLiquidity( + bob, + greatPool, + String(parseFloat(maxDT) * 2), + poolShares + ) + assert(tx === null) + }) it('Bob should remove DT liquidity from pool ', async () => { const currentDtReserve = await Pool.getDTReserve(greatPool) if (consoleDebug) console.log('currentDtReserve:' + currentDtReserve) @@ -212,7 +271,14 @@ describe('Balancer flow', () => { if (consoleDebug) console.log('bobDtBalance:' + bobDtBalance) const poolShares = await Pool.sharesBalance(bob, greatPool) if (consoleDebug) console.log('poolShares:' + poolShares) - await Pool.removeDTLiquidity(bob, greatPool, '0.75', poolShares) + const maxDT = await Pool.getMaxRemoveLiquidity(greatPool, tokenAddress) + if (consoleDebug) console.log('maxDT:' + maxDT) + await Pool.removeDTLiquidity( + bob, + greatPool, + String(Math.min(parseFloat(maxDT), parseFloat('0.75'))), + poolShares + ) const newDtReserve = await Pool.getDTReserve(greatPool) if (consoleDebug) console.log('newDtReserve:' + newDtReserve) @@ -225,13 +291,33 @@ describe('Balancer flow', () => { assert(parseFloat(poolShares) > parseFloat(newpoolShares)) }) + it('Bob should get maximum Ocean liquidity that he can add to pool ', async () => { + const maxOcean = await Pool.getOceanMaxAddLiquidity(greatPool) + assert(parseFloat(maxOcean) > 0) + }) + + it('Bob should fail to add more than maximum Ocean liquidity that he can add to pool ', async () => { + const maxOcean = await Pool.getOceanMaxAddLiquidity(greatPool) + const tx = await Pool.addOceanLiquidity( + bob, + greatPool, + String(parseFloat(maxOcean) * 2) + ) + assert(tx === null) + }) + it('Bob should add Ocean liquidity to pool ', async () => { const currentDtReserve = await Pool.getOceanReserve(greatPool) const bobDtBalance = await datatoken.balance(oceanTokenAddress, bob) if (consoleDebug) console.log('currentDtReserve:' + currentDtReserve) if (consoleDebug) console.log('bobDtBalance:' + bobDtBalance) + const maxOcean = await Pool.getOceanMaxAddLiquidity(greatPool) - await Pool.addOceanLiquidity(bob, greatPool, '1') + await Pool.addOceanLiquidity( + bob, + greatPool, + String(Math.min(parseFloat(maxOcean), parseFloat(bobDtBalance))) + ) const newbobDtBalance = await datatoken.balance(oceanTokenAddress, bob) @@ -245,6 +331,34 @@ describe('Balancer flow', () => { assert(parseFloat(newDtReserve) > parseFloat(currentDtReserve)) assert(parseFloat(sharesBalance) > 0) }) + it('Bob should get maximum Ocean liquidity that he can remove from pool ', async () => { + const maxOcean = await Pool.getMaxRemoveLiquidity(greatPool, oceanTokenAddress) + assert(parseFloat(maxOcean) > 0) + }) + it('Bob should fail to remove more than maximum Ocean liquidity that he can remove from the pool ', async () => { + const maxOcean = await Pool.getOceanMaxRemoveLiquidity(greatPool) + const poolShares = await Pool.sharesBalance(bob, greatPool) + const tx = await Pool.removeOceanLiquidity( + bob, + greatPool, + String(parseFloat(maxOcean) * 2), + poolShares + ) + assert(tx === null) + }) + it('Bob should know how many Pool Shares he needs to remove 1 OCEAN ', async () => { + const poolShares = await Pool.getPoolSharesRequiredToRemoveOcean(greatPool, '1') + if (consoleDebug) console.log('poolShares:' + poolShares) + assert(parseFloat(poolShares) > 0) + }) + it('Bob should know how many OCEAN gets in exchange of his Pool Shares', async () => { + const poolShares = await Pool.getOceanRemovedforPoolShares( + greatPool, + await Pool.sharesBalance(bob, greatPool) + ) + if (consoleDebug) console.log('poolShares:' + poolShares) + assert(parseFloat(poolShares) > 0) + }) it('Bob should remove Ocean liquidity from pool ', async () => { const currentDtReserve = await Pool.getOceanReserve(greatPool) @@ -269,6 +383,18 @@ describe('Balancer flow', () => { assert(parseFloat(poolShares) > parseFloat(newpoolShares)) }) + it('ALice should remove all liquidity', async () => { + const aliceShares = await Pool.sharesBalance(alice, greatPool) + const aliceDtBalance = await datatoken.balance(tokenAddress, alice) + const aliceOceanBalance = await datatoken.balance(oceanTokenAddress, alice) + await Pool.removePoolLiquidity(alice, greatPool, aliceShares) + const newAliceDtBalance = await datatoken.balance(tokenAddress, alice) + const newAliceOceanBalance = await datatoken.balance(oceanTokenAddress, alice) + const newAliceShares = await Pool.sharesBalance(alice, greatPool) + assert(parseFloat(aliceDtBalance) < parseFloat(newAliceDtBalance)) + assert(parseFloat(aliceOceanBalance) < parseFloat(newAliceOceanBalance)) + assert(parseFloat(aliceShares) > parseFloat(newAliceShares)) + }) it('ALice should get all the pools that she created', async () => { const alicePools = await Pool.getPoolsbyCreator(alice) assert(alicePools.length > 0)