mirror of
https://github.com/oceanprotocol/ocean.js.git
synced 2024-11-26 20:39:05 +01:00
add initial FixedRateExchange class and test
This commit is contained in:
parent
817dc31c35
commit
48e573636b
2
package-lock.json
generated
2
package-lock.json
generated
@ -3493,7 +3493,7 @@
|
||||
}
|
||||
},
|
||||
"@oceanprotocol/contracts": {
|
||||
"version": "git+https://github.com/oceanprotocol/contracts.git#bb1382e700d6f2f994b8e3abfd6a7831e69b1804",
|
||||
"version": "git+https://github.com/oceanprotocol/contracts.git#c1bea5033dfc9071105a11b63ce86d8e8f612b7b",
|
||||
"from": "git+https://github.com/oceanprotocol/contracts.git#v4main",
|
||||
"requires": {
|
||||
"@balancer-labs/v2-pool-utils": "^1.0.0",
|
||||
|
@ -25,6 +25,7 @@
|
||||
"changelog": "auto-changelog -p",
|
||||
"prepublishOnly": "npm run build",
|
||||
"test:ss": "mocha --config=test/unit/.mocharc.json --node-env=test --exit 'test/unit/pools/ssContracts/SideStaking.test.ts'",
|
||||
"test:fixed": "mocha --config=test/unit/.mocharc.json --node-env=test --exit 'test/unit/pools/fixedRate/FixedRateExchange.test.ts'",
|
||||
"test:pool": "mocha --config=test/unit/.mocharc.json --node-env=test --exit 'test/unit/pools/balancer/Pool.test.ts'",
|
||||
"test:router": "mocha --config=test/unit/.mocharc.json --node-env=test --exit 'test/unit/pools/Router.test.ts'",
|
||||
"test:unit": "mocha --config=test/unit/.mocharc.json --node-env=test --exit 'test/unit/**/*.test.ts'",
|
||||
|
@ -1,6 +1,338 @@
|
||||
import defaultFixedRateExchangeABI from '@oceanprotocol/contracts/artifacts/contracts/pools/fixedRate/FixedRateExchange.sol/FixedRateExchange.json'
|
||||
import defaultERC20ABI from '@oceanprotocol/contracts/artifacts/contracts/templates/ERC20Template.sol/ERC20Template.json'
|
||||
import BigNumber from 'bignumber.js'
|
||||
import { TransactionReceipt } from 'web3-core'
|
||||
import { Contract, EventData } from 'web3-eth-contract'
|
||||
import { AbiItem } from 'web3-utils/types'
|
||||
import Web3 from 'web3'
|
||||
import { Logger, getFairGasPrice } from '../../utils'
|
||||
|
||||
const MAX_AWAIT_PROMISES = 10
|
||||
|
||||
export interface FixedPriceExchange {
|
||||
exchangeID?: string
|
||||
exchangeOwner: string
|
||||
dataToken: string
|
||||
baseToken: string
|
||||
fixedRate: string
|
||||
active: boolean
|
||||
supply: string
|
||||
}
|
||||
|
||||
export interface FixedPriceSwap {
|
||||
exchangeID: string
|
||||
caller: string
|
||||
baseTokenAmount: string
|
||||
dataTokenAmount: string
|
||||
}
|
||||
|
||||
export enum FixedRateCreateProgressStep {
|
||||
CreatingExchange,
|
||||
ApprovingDatatoken
|
||||
}
|
||||
|
||||
export class FixedRateExchange {
|
||||
public GASLIMIT_DEFAULT = 1000000
|
||||
public web3: Web3 = null
|
||||
}
|
||||
/** Ocean related functions */
|
||||
public oceanAddress: string = null
|
||||
public fixedRateAddress:string
|
||||
public fixedRateExchangeABI: AbiItem | AbiItem[]
|
||||
public fixedRateContract:Contract
|
||||
public web3: Web3
|
||||
public contract: Contract = null
|
||||
private logger: Logger
|
||||
|
||||
public startBlock: number
|
||||
public ssABI: AbiItem | AbiItem[]
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Instantiate FixedRateExchange
|
||||
* @param {any} web3
|
||||
* @param {any} fixedRateExchangeABI
|
||||
*/
|
||||
constructor(
|
||||
web3: Web3,
|
||||
logger: Logger,
|
||||
fixedRateAddress: string,
|
||||
fixedRateExchangeABI: AbiItem | AbiItem[] = null,
|
||||
oceanAddress: string = null,
|
||||
startBlock?: number
|
||||
) {
|
||||
this.web3 = web3
|
||||
|
||||
if (startBlock) this.startBlock = startBlock
|
||||
else this.startBlock = 0
|
||||
this.fixedRateExchangeABI =
|
||||
fixedRateExchangeABI || (defaultFixedRateExchangeABI.abi as AbiItem[])
|
||||
this.oceanAddress = oceanAddress
|
||||
this.fixedRateAddress = fixedRateAddress
|
||||
this.contract = new this.web3.eth.Contract(
|
||||
this.fixedRateExchangeABI,
|
||||
this.fixedRateAddress
|
||||
)
|
||||
|
||||
this.logger = logger
|
||||
}
|
||||
async amountToUnits(token: string, amount: string): Promise<string> {
|
||||
let decimals = 18
|
||||
const tokenContract = new this.web3.eth.Contract(
|
||||
defaultERC20ABI.abi as AbiItem[],
|
||||
token
|
||||
)
|
||||
try {
|
||||
decimals = await tokenContract.methods.decimals().call()
|
||||
} catch (e) {
|
||||
this.logger.error('ERROR: FAILED TO CALL DECIMALS(), USING 18')
|
||||
}
|
||||
|
||||
const amountFormatted = new BigNumber(parseInt(amount) * 10 ** decimals)
|
||||
|
||||
return amountFormatted.toString()
|
||||
}
|
||||
|
||||
async unitsToAmount(token: string, amount: string): Promise<string> {
|
||||
let decimals = 18
|
||||
const tokenContract = new this.web3.eth.Contract(
|
||||
defaultERC20ABI.abi as AbiItem[],
|
||||
token
|
||||
)
|
||||
try {
|
||||
decimals = await tokenContract.methods.decimals().call()
|
||||
} catch (e) {
|
||||
this.logger.error('ERROR: FAILED TO CALL DECIMALS(), USING 18')
|
||||
}
|
||||
|
||||
const amountFormatted = new BigNumber(parseInt(amount) / 10 ** decimals)
|
||||
|
||||
return amountFormatted.toString()
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Creates unique exchange identifier.
|
||||
* @param {String} dataToken Data Token Contract Address
|
||||
* @param {String} owner Owner of the exchange
|
||||
* @return {Promise<string>} exchangeId
|
||||
*/
|
||||
public async generateExchangeId(dataToken: string, owner: string): Promise<string> {
|
||||
const exchangeId = await this.contract.methods
|
||||
.generateExchangeId(this.oceanAddress, dataToken, owner)
|
||||
.call()
|
||||
return exchangeId
|
||||
}
|
||||
|
||||
/**
|
||||
* Atomic swap
|
||||
* @param {String} exchangeId ExchangeId
|
||||
* @param {Number} dataTokenAmount Amount of Data Tokens
|
||||
* @param {String} address User address
|
||||
* @return {Promise<TransactionReceipt>} transaction receipt
|
||||
*/
|
||||
public async buyDT(
|
||||
exchangeId: string,
|
||||
dataTokenAmount: string,
|
||||
address: string
|
||||
): Promise<TransactionReceipt> {
|
||||
const gasLimitDefault = this.GASLIMIT_DEFAULT
|
||||
let estGas
|
||||
try {
|
||||
estGas = await this.contract.methods
|
||||
.swap(exchangeId, this.web3.utils.toWei(String(dataTokenAmount)))
|
||||
.estimateGas({ from: address }, (err, estGas) => (err ? gasLimitDefault : estGas))
|
||||
} catch (e) {
|
||||
estGas = gasLimitDefault
|
||||
}
|
||||
try {
|
||||
const trxReceipt = await this.contract.methods
|
||||
.swap(exchangeId, this.web3.utils.toWei(String(dataTokenAmount)))
|
||||
.send({
|
||||
from: address,
|
||||
gas: estGas + 1,
|
||||
gasPrice: await getFairGasPrice(this.web3)
|
||||
})
|
||||
return trxReceipt
|
||||
} catch (e) {
|
||||
this.logger.error(`ERROR: Failed to buy datatokens: ${e.message}`)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets total number of exchanges
|
||||
* @param {String} exchangeId ExchangeId
|
||||
* @param {Number} dataTokenAmount Amount of Data Tokens
|
||||
* @return {Promise<Number>} no of available exchanges
|
||||
*/
|
||||
public async getNumberOfExchanges(): Promise<number> {
|
||||
const numExchanges = await this.contract.methods.getNumberOfExchanges().call()
|
||||
return numExchanges
|
||||
}
|
||||
|
||||
/**
|
||||
* Set new rate
|
||||
* @param {String} exchangeId ExchangeId
|
||||
* @param {Number} newRate New rate
|
||||
* @param {String} address User account
|
||||
* @return {Promise<TransactionReceipt>} transaction receipt
|
||||
*/
|
||||
public async setRate(
|
||||
exchangeId: string,
|
||||
newRate: number,
|
||||
address: string
|
||||
): Promise<TransactionReceipt> {
|
||||
const gasLimitDefault = this.GASLIMIT_DEFAULT
|
||||
let estGas
|
||||
try {
|
||||
estGas = await this.contract.methods
|
||||
.setRate(exchangeId, this.web3.utils.toWei(String(newRate)))
|
||||
.estimateGas({ from: address }, (err, estGas) => (err ? gasLimitDefault : estGas))
|
||||
} catch (e) {
|
||||
estGas = gasLimitDefault
|
||||
}
|
||||
const trxReceipt = await this.contract.methods
|
||||
.setRate(exchangeId, this.web3.utils.toWei(String(newRate)))
|
||||
.send({
|
||||
from: address,
|
||||
gas: estGas + 1,
|
||||
gasPrice: await getFairGasPrice(this.web3)
|
||||
})
|
||||
return trxReceipt
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate an exchange
|
||||
* @param {String} exchangeId ExchangeId
|
||||
* @param {String} address User address
|
||||
* @return {Promise<TransactionReceipt>} transaction receipt
|
||||
*/
|
||||
public async activate(
|
||||
exchangeId: string,
|
||||
address: string
|
||||
): Promise<TransactionReceipt> {
|
||||
const exchange = await this.getExchange(exchangeId)
|
||||
if (!exchange) return null
|
||||
if (exchange.active === true) return null
|
||||
const gasLimitDefault = this.GASLIMIT_DEFAULT
|
||||
let estGas
|
||||
try {
|
||||
estGas = await this.contract.methods
|
||||
.toggleExchangeState(exchangeId)
|
||||
.estimateGas({ from: address }, (err, estGas) => (err ? gasLimitDefault : estGas))
|
||||
} catch (e) {
|
||||
estGas = gasLimitDefault
|
||||
}
|
||||
const trxReceipt = await this.contract.methods.toggleExchangeState(exchangeId).send({
|
||||
from: address,
|
||||
gas: estGas + 1,
|
||||
gasPrice: await getFairGasPrice(this.web3)
|
||||
})
|
||||
return trxReceipt
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivate an exchange
|
||||
* @param {String} exchangeId ExchangeId
|
||||
* @param {String} address User address
|
||||
* @return {Promise<TransactionReceipt>} transaction receipt
|
||||
*/
|
||||
public async deactivate(
|
||||
exchangeId: string,
|
||||
address: string
|
||||
): Promise<TransactionReceipt> {
|
||||
const exchange = await this.getExchange(exchangeId)
|
||||
if (!exchange) return null
|
||||
if (exchange.active === false) return null
|
||||
const gasLimitDefault = this.GASLIMIT_DEFAULT
|
||||
let estGas
|
||||
try {
|
||||
estGas = await this.contract.methods
|
||||
.toggleExchangeState(exchangeId)
|
||||
.estimateGas({ from: address }, (err, estGas) => (err ? gasLimitDefault : estGas))
|
||||
} catch (e) {
|
||||
estGas = gasLimitDefault
|
||||
}
|
||||
const trxReceipt = await this.contract.methods.toggleExchangeState(exchangeId).send({
|
||||
from: address,
|
||||
gas: estGas + 1,
|
||||
gasPrice: await getFairGasPrice(this.web3)
|
||||
})
|
||||
return trxReceipt
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Rate
|
||||
* @param {String} exchangeId ExchangeId
|
||||
* @return {Promise<string>} Rate (converted from wei)
|
||||
*/
|
||||
public async getRate(exchangeId: string): Promise<string> {
|
||||
const weiRate = await this.contract.methods.getRate(exchangeId).call()
|
||||
return this.web3.utils.fromWei(weiRate)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Supply
|
||||
* @param {String} exchangeId ExchangeId
|
||||
* @return {Promise<string>} Rate (converted from wei)
|
||||
*/
|
||||
public async getSupply(exchangeId: string): Promise<string> {
|
||||
const weiRate = await this.contract.methods.getSupply(exchangeId).call()
|
||||
return this.web3.utils.fromWei(weiRate)
|
||||
}
|
||||
|
||||
/**
|
||||
* getOceanNeeded
|
||||
* @param {String} exchangeId ExchangeId
|
||||
* @param {Number} dataTokenAmount Amount of Data Tokens
|
||||
* @return {Promise<string>} Ocean amount needed
|
||||
*/
|
||||
public async getOceanNeeded(
|
||||
exchangeId: string,
|
||||
dataTokenAmount: string
|
||||
): Promise<string> {
|
||||
const weiRate = await this.contract.methods
|
||||
.CalcInGivenOut(exchangeId, this.web3.utils.toWei(dataTokenAmount))
|
||||
.call()
|
||||
return this.web3.utils.fromWei(weiRate)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get exchange details
|
||||
* @param {String} exchangeId ExchangeId
|
||||
* @return {Promise<FixedPricedExchange>} Exchange details
|
||||
*/
|
||||
public async getExchange(exchangeId: string): Promise<FixedPriceExchange> {
|
||||
const result: FixedPriceExchange = await this.contract.methods
|
||||
.getExchange(exchangeId)
|
||||
.call()
|
||||
result.fixedRate = this.web3.utils.fromWei(result.fixedRate)
|
||||
result.supply = this.web3.utils.fromWei(result.supply)
|
||||
result.exchangeID = exchangeId
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all exchanges
|
||||
* @param {String} exchangeId ExchangeId
|
||||
* @return {Promise<String[]>} Exchanges list
|
||||
*/
|
||||
public async getExchanges(): Promise<string[]> {
|
||||
return await this.contract.methods.getExchanges().call()
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an exchange is active
|
||||
* @param {String} exchangeId ExchangeId
|
||||
* @return {Promise<Boolean>} Result
|
||||
*/
|
||||
public async isActive(exchangeId: string): Promise<boolean> {
|
||||
const result = await this.contract.methods.isActive(exchangeId).call()
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
161
test/unit/pools/fixedRate/FixedRateExchange.test.ts
Normal file
161
test/unit/pools/fixedRate/FixedRateExchange.test.ts
Normal file
@ -0,0 +1,161 @@
|
||||
import { assert, expect } from 'chai'
|
||||
import { AbiItem } from 'web3-utils/types'
|
||||
import { TestContractHandler } from '../../../TestContractHandler'
|
||||
import { Contract } from 'web3-eth-contract'
|
||||
import Web3 from 'web3'
|
||||
import BigNumber from 'bignumber.js'
|
||||
import ERC721Factory from '@oceanprotocol/contracts/artifacts/contracts/ERC721Factory.sol/ERC721Factory.json'
|
||||
import ERC721Template from '@oceanprotocol/contracts/artifacts/contracts/templates/ERC721Template.sol/ERC721Template.json'
|
||||
import SSContract from '@oceanprotocol/contracts/artifacts/contracts/pools/ssContracts/SideStaking.sol/SideStaking.json'
|
||||
import FactoryRouter from '@oceanprotocol/contracts/artifacts/contracts/pools/FactoryRouter.sol/FactoryRouter.json'
|
||||
import ERC20Template from '@oceanprotocol/contracts/artifacts/contracts/templates/ERC20Template.sol/ERC20Template.json'
|
||||
import Dispenser from '@oceanprotocol/contracts/artifacts/contracts/pools/dispenser/Dispenser.sol/Dispenser.json'
|
||||
import FixedRate from '@oceanprotocol/contracts/artifacts/contracts/pools/fixedRate/FixedRateExchange.sol/FixedRateExchange.json'
|
||||
import MockERC20 from '@oceanprotocol/contracts/artifacts/contracts/utils/mock/MockERC20Decimals.sol/MockERC20Decimals.json'
|
||||
import PoolTemplate from '@oceanprotocol/contracts/artifacts/contracts/pools/balancer/BPool.sol/BPool.json'
|
||||
import OPFCollector from '@oceanprotocol/contracts/artifacts/contracts/communityFee/OPFCommunityFeeCollector.sol/OPFCommunityFeeCollector.json'
|
||||
import { LoggerInstance } from '../../../../src/utils'
|
||||
import { NFTFactory } from '../../../../src/factories/NFTFactory'
|
||||
import { Pool } from '../../../../src/pools/balancer/Pool'
|
||||
import { FixedRateExchange } from '../../../../src/pools/fixedRate/FixedRateExchange'
|
||||
const { keccak256 } = require('@ethersproject/keccak256')
|
||||
const web3 = new Web3('http://127.0.0.1:8545')
|
||||
const communityCollector = '0xeE9300b7961e0a01d9f0adb863C7A227A07AaD75'
|
||||
|
||||
describe('Fixed Rate unit test', () => {
|
||||
let factoryOwner: string
|
||||
let nftOwner: string
|
||||
let exchangeOwner: string
|
||||
let user1: string
|
||||
let user2: string
|
||||
let user3: string
|
||||
let initialBlock: number
|
||||
let fixedRateAddress: string
|
||||
let exchangeId: string
|
||||
let contracts: TestContractHandler
|
||||
let fixedRate: FixedRateExchange
|
||||
let dtAddress: string
|
||||
let dtAddress2: string
|
||||
let dtContract: Contract
|
||||
let daiContract: Contract
|
||||
let usdcContract: Contract
|
||||
const vestedBlocks = 2500000
|
||||
|
||||
it('should deploy contracts', async () => {
|
||||
contracts = new TestContractHandler(
|
||||
web3,
|
||||
ERC721Template.abi as AbiItem[],
|
||||
ERC20Template.abi as AbiItem[],
|
||||
PoolTemplate.abi as AbiItem[],
|
||||
ERC721Factory.abi as AbiItem[],
|
||||
FactoryRouter.abi as AbiItem[],
|
||||
SSContract.abi as AbiItem[],
|
||||
FixedRate.abi as AbiItem[],
|
||||
Dispenser.abi as AbiItem[],
|
||||
OPFCollector.abi as AbiItem[],
|
||||
|
||||
ERC721Template.bytecode,
|
||||
ERC20Template.bytecode,
|
||||
PoolTemplate.bytecode,
|
||||
ERC721Factory.bytecode,
|
||||
FactoryRouter.bytecode,
|
||||
SSContract.bytecode,
|
||||
FixedRate.bytecode,
|
||||
Dispenser.bytecode,
|
||||
OPFCollector.bytecode
|
||||
)
|
||||
await contracts.getAccounts()
|
||||
factoryOwner = contracts.accounts[0]
|
||||
nftOwner = contracts.accounts[1]
|
||||
user1 = contracts.accounts[2]
|
||||
user2 = contracts.accounts[3]
|
||||
user3 = contracts.accounts[4]
|
||||
exchangeOwner = contracts.accounts[0]
|
||||
await contracts.deployContracts(factoryOwner, FactoryRouter.abi as AbiItem[])
|
||||
|
||||
// initialize fixed rate
|
||||
//
|
||||
|
||||
|
||||
daiContract = new web3.eth.Contract(
|
||||
contracts.MockERC20.options.jsonInterface,
|
||||
contracts.daiAddress
|
||||
)
|
||||
|
||||
usdcContract = new web3.eth.Contract(
|
||||
contracts.MockERC20.options.jsonInterface,
|
||||
contracts.usdcAddress
|
||||
)
|
||||
|
||||
|
||||
console.log(
|
||||
await usdcContract.methods.decimals().call(),
|
||||
'USDC DECIMALS IN THIS TEST'
|
||||
)
|
||||
|
||||
|
||||
})
|
||||
|
||||
describe('Test a Fixed Rate Exchange with DAI (18 Decimals)', () => {
|
||||
it('#create an exchange', async () => {
|
||||
// CREATE AN Exchange
|
||||
// we prepare transaction parameters objects
|
||||
const nftData = {
|
||||
name: '72120Bundle',
|
||||
symbol: '72Bundle',
|
||||
templateIndex: 1,
|
||||
baseURI: 'https://oceanprotocol.com/nft/'
|
||||
}
|
||||
const ercData = {
|
||||
templateIndex: 1,
|
||||
strings: ['ERC20B1', 'ERC20DT1Symbol'],
|
||||
addresses: [
|
||||
contracts.accounts[0],
|
||||
user3,
|
||||
contracts.accounts[0],
|
||||
'0x0000000000000000000000000000000000000000'
|
||||
],
|
||||
uints: [web3.utils.toWei('1000000'), 0],
|
||||
bytess: []
|
||||
}
|
||||
|
||||
//[baseToken,owner,marketFeeCollector,allowedSwapper]
|
||||
const fixedRateData = {
|
||||
fixedPriceAddress:contracts.fixedRateAddress,
|
||||
addresses:[contracts.daiAddress,exchangeOwner,user3, '0x0000000000000000000000000000000000000000'],
|
||||
uints:[18,18,web3.utils.toWei('1'),1e15,0]
|
||||
}
|
||||
|
||||
const nftFactory = new NFTFactory(contracts.factory721Address, web3, LoggerInstance)
|
||||
|
||||
const txReceipt = await nftFactory.createNftErcWithFixedRate(
|
||||
exchangeOwner,
|
||||
nftData,
|
||||
ercData,
|
||||
fixedRateData
|
||||
)
|
||||
|
||||
initialBlock = await web3.eth.getBlockNumber()
|
||||
dtAddress = txReceipt.events.TokenCreated.returnValues.newTokenAddress
|
||||
exchangeId = txReceipt.events.NewFixedRate.returnValues.exchangeId
|
||||
|
||||
dtContract = new web3.eth.Contract(ERC20Template.abi as AbiItem[], dtAddress)
|
||||
// user2 has no dt1
|
||||
expect(await dtContract.methods.balanceOf(user2).call()).to.equal('0')
|
||||
|
||||
fixedRateAddress = contracts.fixedRateAddress
|
||||
fixedRate = new FixedRateExchange(web3, LoggerInstance, fixedRateAddress, FixedRate.abi as AbiItem[],contracts.oceanAddress)
|
||||
assert(fixedRate != null)
|
||||
})
|
||||
|
||||
it('#isActive - should return true if exchange is active', async () => {
|
||||
expect(await fixedRate.isActive(exchangeId)).to.equal(
|
||||
true
|
||||
)
|
||||
expect(await fixedRate.isActive('0x00')).to.equal(
|
||||
false
|
||||
)
|
||||
})
|
||||
|
||||
})
|
||||
})
|
Loading…
x
Reference in New Issue
Block a user