From 42eb819765b64631d901b868ec0ce83a8952add6 Mon Sep 17 00:00:00 2001 From: lacoop6tu Date: Thu, 21 Oct 2021 10:47:21 -0500 Subject: [PATCH 1/5] complete NFTFactory class, add some tests --- src/factories/NFTFactory.ts | 209 +++++++++++++++++++++++++++++++++++ test/TestContractHandler.ts | 1 + test/unit/NFTFactory.test.ts | 44 ++++++-- 3 files changed, 246 insertions(+), 8 deletions(-) diff --git a/src/factories/NFTFactory.ts b/src/factories/NFTFactory.ts index 2fe5d04e..277d79ab 100644 --- a/src/factories/NFTFactory.ts +++ b/src/factories/NFTFactory.ts @@ -5,10 +5,48 @@ import { AbiItem } from 'web3-utils' import defaultFactory721ABI from '@oceanprotocol/contracts/artifacts/contracts/ERC721Factory.sol/ERC721Factory.json' import { Logger, getFairGasPrice, generateDtName } from '../utils' + interface Template { templateAddress: string isActive: boolean } + +interface TokenOrder { + tokenAddress: string + consumer: string + amount: number + serviceId: number + consumeFeeAddress: string + consumeFeeToken: string // address of the token marketplace wants to add fee on top + consumeFeeAmount: number +} + +interface NFTCreateData { + name: string + symbol: string + templateIndex: number + baseURI: string +} + +interface ErcCreateData { + templateIndex: number + strings: string[] + addresses: string[] + uints:(string | number)[] + bytess: string[] +} + +interface PoolData { + addresses: string[] + ssParams: number[] + swapFees: number[] +} + +interface FixedData { + fixedPriceAddress: string + addresses: string[] + uints: number[] +} /** * Provides an interface for NFT DataTokens */ @@ -152,6 +190,24 @@ export class NFTFactory { return template } + /** Check if ERC20 is deployed from the factory + * @param {String} datatoken Datatoken address we want to check + * @return {Promise} return true if deployed from this factory + */ + public async checkDatatoken(datatoken: string): Promise { + const isDeployed = await this.factory721.methods.erc20List(datatoken).call() + return isDeployed + } + + /** Check if NFT is deployed from the factory + * @param {String} nftAddress nftAddress address we want to check + * @return {Promise} return address(0) if it's not, or the nftAddress if true + */ + public async checkNFT(nftAddress: string): Promise { + const confirmAddress = await this.factory721.methods.erc721List(nftAddress).call() + return confirmAddress + } + /** * Add a new erc721 token template - only factory Owner * @param {String} address @@ -367,4 +423,157 @@ export class NFTFactory { return trxReceipt } + + /** + * @dev startMultipleTokenOrder + * Used as a proxy to order multiple services + * Users can have inifinite approvals for fees for factory instead of having one approval/ erc20 contract + * Requires previous approval of all : + * - consumeFeeTokens + * - publishMarketFeeTokens + * - erc20 datatokens + * @param orders an array of struct tokenOrder + * @return {Promise} transaction receipt + */ + + public async startMultipleTokenOrder( + address: string, + orders: TokenOrder[] + ): Promise { + // Get estimated gas value + const gasLimitDefault = this.GASLIMIT_DEFAULT + let estGas + try { + estGas = await this.factory721.methods + .startMultipleTokenOrder(orders) + .estimateGas({ from: address }, (err, estGas) => (err ? gasLimitDefault : estGas)) + } catch (e) { + estGas = gasLimitDefault + } + + // Invoke createToken function of the contract + const trxReceipt = await this.factory721.methods + .startMultipleTokenOrder(orders) + .send({ + from: address, + gas: estGas + 1, + gasPrice: await getFairGasPrice(this.web3) + }) + + return trxReceipt + } + + /** + * @dev createNftWithErc + * Creates a new NFT, then a ERC20,all in one call + * @param _NftCreateData input data for nft creation + * @param _ErcCreateData input data for erc20 creation + * @return {Promise} transaction receipt + */ + + public async createNftWithErc( + address: string, + nftCreateData: NFTCreateData, + ercCreateData: ErcCreateData + ): Promise { + // Get estimated gas value + const gasLimitDefault = this.GASLIMIT_DEFAULT + let estGas + try { + estGas = await this.factory721.methods + .createNftWithErc(nftCreateData, ercCreateData) + .estimateGas({ from: address }, (err, estGas) => (err ? gasLimitDefault : estGas)) + } catch (e) { + estGas = gasLimitDefault + } + + // Invoke createToken function of the contract + const trxReceipt = await this.factory721.methods + .createNftWithErc(nftCreateData, ercCreateData) + .send({ + from: address, + gas: estGas + 1, + gasPrice: await getFairGasPrice(this.web3) + }) + + return trxReceipt + } + + /** + * @dev createNftErcWithPool + * Creates a new NFT, then a ERC20, then a Pool, all in one call + * Use this carefully, because if Pool creation fails, you are still going to pay a lot of gas + * @param _NftCreateData input data for NFT Creation + * @param _ErcCreateData input data for ERC20 Creation + * @param _PoolData input data for Pool Creation + * @return {Promise} transaction receipt + */ + + public async createNftErcWithPool( + address: string, + nftCreateData: NFTCreateData, + ercCreateData: ErcCreateData, + poolData: PoolData + ): Promise { + // Get estimated gas value + const gasLimitDefault = this.GASLIMIT_DEFAULT + let estGas + try { + estGas = await this.factory721.methods + .createNftErcWithPool(nftCreateData, ercCreateData, poolData) + .estimateGas({ from: address }, (err, estGas) => (err ? gasLimitDefault : estGas)) + } catch (e) { + estGas = gasLimitDefault + } + + // Invoke createToken function of the contract + const trxReceipt = await this.factory721.methods + .createNftErcWithPool(nftCreateData, ercCreateData, poolData) + .send({ + from: address, + gas: estGas + 1, + gasPrice: await getFairGasPrice(this.web3) + }) + + return trxReceipt + } + + /** + * @dev createNftErcWithFixedRate + * Creates a new NFT, then a ERC20, then a FixedRateExchange, all in one call + * Use this carefully, because if Fixed Rate creation fails, you are still going to pay a lot of gas + * @param _NftCreateData input data for NFT Creation + * @param _ErcCreateData input data for ERC20 Creation + * @param _FixedData input data for FixedRate Creation + * @return {Promise} transaction receipt + */ + + public async createNftErcWithFixedRate( + address: string, + nftCreateData: NFTCreateData, + ercCreateData: ErcCreateData, + fixedData: FixedData + ): Promise { + // Get estimated gas value + const gasLimitDefault = this.GASLIMIT_DEFAULT + let estGas + try { + estGas = await this.factory721.methods + .createNftErcWithFixedRate(nftCreateData, ercCreateData) + .estimateGas({ from: address }, (err, estGas) => (err ? gasLimitDefault : estGas)) + } catch (e) { + estGas = gasLimitDefault + } + + // Invoke createToken function of the contract + const trxReceipt = await this.factory721.methods + .createNftErcWithFixedRate(nftCreateData, ercCreateData) + .send({ + from: address, + gas: estGas + 1, + gasPrice: await getFairGasPrice(this.web3) + }) + + return trxReceipt + } } diff --git a/test/TestContractHandler.ts b/test/TestContractHandler.ts index 28f766b7..aef48025 100644 --- a/test/TestContractHandler.ts +++ b/test/TestContractHandler.ts @@ -289,6 +289,7 @@ export class TestContractHandler { .addSSContract(this.sideStakingAddress) .send({ from: owner }) // TODO: add OPF deployment and update argument + // TODO: how are we going to call those functions with an OPF contract? it should be a multisig the owner await RouterContract.methods .changeRouterOwner(communityCollector) .send({ from: owner }) diff --git a/test/unit/NFTFactory.test.ts b/test/unit/NFTFactory.test.ts index 8e6f6d9f..0cfec17b 100644 --- a/test/unit/NFTFactory.test.ts +++ b/test/unit/NFTFactory.test.ts @@ -1,4 +1,4 @@ -import { assert } from 'chai' +import { assert, expect } from 'chai' import { AbiItem } from 'web3-utils/types' import { TestContractHandler } from '../TestContractHandler' import Web3 from 'web3' @@ -13,7 +13,6 @@ import PoolTemplate from '@oceanprotocol/contracts/artifacts/contracts/pools/bal import { LoggerInstance } from '../../src/utils' // import { NFTDataToken } from '../../../src/datatokens/NFTDatatoken' import { NFTFactory } from '../../src/factories/NFTFactory' -// import { DT20Factory } from '../../../src/factories/DT20Factory' const web3 = new Web3('http://127.0.0.1:8545') @@ -22,6 +21,7 @@ describe('NFT Factory test', () => { let nftOwner: string let user1: string let user2: string + let user3: string let contracts: TestContractHandler let nftFactory: NFTFactory @@ -59,6 +59,8 @@ describe('NFT Factory test', () => { factoryOwner = contracts.accounts[0] nftOwner = contracts.accounts[1] user1 = contracts.accounts[2] + user2 = contracts.accounts[3] + user3 = contracts.accounts[4] console.log(factoryOwner) await contracts.deployContracts(factoryOwner, Router.abi as AbiItem[]) @@ -71,12 +73,12 @@ describe('NFT Factory test', () => { it('#getCurrentNFTCount - should return actual nft count (0)', async () => { const nftCount = await nftFactory.getCurrentNFTCount() - assert(nftCount === 0) + expect(nftCount).to.equal('0') }) it('#getCurrentTokenCount - should return actual token count (0)', async () => { const tokenCount = await nftFactory.getCurrentTokenCount() - assert(tokenCount === 0) + expect(tokenCount).to.equal('0') }) it('#getOwner - should return actual owner', async () => { const owner = await nftFactory.getOwner() @@ -84,11 +86,11 @@ describe('NFT Factory test', () => { }) it('#getCurrentNFTTemplateCount - should return actual nft template count (1)', async () => { const nftTemplateCount = await nftFactory.getCurrentNFTTemplateCount() - assert(nftTemplateCount === 1) + expect(nftTemplateCount).to.equal('1') }) it('#getCurrentTokenTemplateCount - should return actual token template count (1)', async () => { const tokenTemplateCount = await nftFactory.getCurrentTokenTemplateCount() - assert(tokenTemplateCount === 1) + expect(tokenTemplateCount).to.equal('1') }) it('#getNFTTemplate - should return NFT template struct', async () => { const nftTemplate = await nftFactory.getNFTTemplate(1) @@ -103,7 +105,7 @@ describe('NFT Factory test', () => { it('#addNFTTemplate - should add NFT template if factory owner', async () => { await nftFactory.addNFTTemplate(contracts.accounts[0], contracts.fixedRateAddress) // contracts.fixedRateAddress it's just a dummy contract in this case const nftTemplateCount = await nftFactory.getCurrentNFTTemplateCount() - assert(nftTemplateCount === 2) + expect(nftTemplateCount).to.equal('2') const nftTemplate = await nftFactory.getNFTTemplate(2) assert(nftTemplate.isActive === true) assert(nftTemplate.templateAddress === contracts.fixedRateAddress) @@ -127,7 +129,7 @@ describe('NFT Factory test', () => { it('#addTokenTemplate - should add Datatoken template if factory owner', async () => { await nftFactory.addTokenTemplate(contracts.accounts[0], contracts.fixedRateAddress) // contracts.fixedRateAddress it's just a dummy contract in this case const tokenTemplateCount = await nftFactory.getCurrentTokenTemplateCount() - assert(tokenTemplateCount === 2) + expect(tokenTemplateCount).to.equal('2') const nftTemplate = await nftFactory.getTokenTemplate(2) assert(nftTemplate.isActive === true) assert(nftTemplate.templateAddress === contracts.fixedRateAddress) @@ -149,4 +151,30 @@ describe('NFT Factory test', () => { tokenTemplate = await nftFactory.getTokenTemplate(2) assert(tokenTemplate.isActive === true) }) + + it('#createNFTwithErc - should create an NFT and a Datatoken', async () => { + const nftData = { + name: '72120Bundle', + symbol: '72Bundle', + templateIndex: 1, + baseURI: 'https://oceanprotocol.com/nft/' + } + const ercData = { + templateIndex: 1, + strings: ['ERC20B1', 'ERC20DT1Symbol'], + addresses: [user2, user3, user2, '0x0000000000000000000000000000000000000000'], + uints: [web3.utils.toWei('10000'), 0], + bytess: [] + } + + const txReceipt = await nftFactory.createNftWithErc( + contracts.accounts[0], + nftData, + ercData + ) + expect(txReceipt.events.NFTCreated.event === 'NFTCreated') + expect(txReceipt.events.TokenCreated.event === 'TokenCreated') + console.log(txReceipt.events.NFTCreated.returnValues.newTokenAddress) + console.log(txReceipt.events.TokenCreated.returnValues.newTokenAddress) + }) }) From 3066fb9e9779f2ad321b6aa9a5cd727a673f5fd3 Mon Sep 17 00:00:00 2001 From: lacoop6tu Date: Thu, 21 Oct 2021 12:58:49 -0500 Subject: [PATCH 2/5] add complete NFTFactory basic unit test --- src/factories/NFTFactory.ts | 13 +-- test/TestContractHandler.ts | 80 ++++++++++++- test/unit/NFTFactory.test.ts | 216 ++++++++++++++++++++++++++++++++--- 3 files changed, 283 insertions(+), 26 deletions(-) diff --git a/src/factories/NFTFactory.ts b/src/factories/NFTFactory.ts index 277d79ab..bdadb2fc 100644 --- a/src/factories/NFTFactory.ts +++ b/src/factories/NFTFactory.ts @@ -5,7 +5,6 @@ import { AbiItem } from 'web3-utils' import defaultFactory721ABI from '@oceanprotocol/contracts/artifacts/contracts/ERC721Factory.sol/ERC721Factory.json' import { Logger, getFairGasPrice, generateDtName } from '../utils' - interface Template { templateAddress: string isActive: boolean @@ -14,7 +13,7 @@ interface Template { interface TokenOrder { tokenAddress: string consumer: string - amount: number + amount: string | number serviceId: number consumeFeeAddress: string consumeFeeToken: string // address of the token marketplace wants to add fee on top @@ -32,20 +31,20 @@ interface ErcCreateData { templateIndex: number strings: string[] addresses: string[] - uints:(string | number)[] + uints: (string | number)[] bytess: string[] } interface PoolData { addresses: string[] - ssParams: number[] + ssParams: (string | number)[] swapFees: number[] } interface FixedData { fixedPriceAddress: string addresses: string[] - uints: number[] + uints: (string | number)[] } /** * Provides an interface for NFT DataTokens @@ -559,7 +558,7 @@ export class NFTFactory { let estGas try { estGas = await this.factory721.methods - .createNftErcWithFixedRate(nftCreateData, ercCreateData) + .createNftErcWithFixedRate(nftCreateData, ercCreateData, fixedData) .estimateGas({ from: address }, (err, estGas) => (err ? gasLimitDefault : estGas)) } catch (e) { estGas = gasLimitDefault @@ -567,7 +566,7 @@ export class NFTFactory { // Invoke createToken function of the contract const trxReceipt = await this.factory721.methods - .createNftErcWithFixedRate(nftCreateData, ercCreateData) + .createNftErcWithFixedRate(nftCreateData, ercCreateData, fixedData) .send({ from: address, gas: estGas + 1, diff --git a/test/TestContractHandler.ts b/test/TestContractHandler.ts index aef48025..696cc9d6 100644 --- a/test/TestContractHandler.ts +++ b/test/TestContractHandler.ts @@ -1,7 +1,7 @@ import Web3 from 'web3' import { Contract } from 'web3-eth-contract' import { AbiItem } from 'web3-utils/types' - +import MockERC20 from '@oceanprotocol/contracts/artifacts/contracts/utils/mock/MockERC20Decimals.sol/MockERC20Decimals.json' // TODO: add OPF deployment const communityCollector = '0xeE9300b7961e0a01d9f0adb863C7A227A07AaD75' const oceanAddress = '0x967da4048cd07ab37855c090aaf366e4ce1b9f48' @@ -16,6 +16,8 @@ export class TestContractHandler { public Dispenser: Contract public OPFCollector: Contract public PoolTemplate: Contract + public MockERC20: Contract + public MockOcean: Contract public ERC721FactoryBytecode: string public ERC20TemplateBytecode: string @@ -26,6 +28,7 @@ export class TestContractHandler { public DispenserBytecode: string public PoolTemplateBytecode: string public OPFCollectorBytecode: string + public MockERC20Bytecode: string public factory721Address: string public template721Address: string @@ -36,7 +39,9 @@ export class TestContractHandler { public dispenserAddress: string public poolTemplateAddress: string public opfCollectorAddress: string - + public oceanAddress: string + public daiAddress: string + public usdcAddress: string public web3: Web3 constructor( @@ -68,6 +73,7 @@ export class TestContractHandler { this.SideStaking = new this.web3.eth.Contract(SideStakingABI) this.FixedRate = new this.web3.eth.Contract(FixedRateABI) this.Dispenser = new this.web3.eth.Contract(DispenserABI) + this.MockERC20 = new this.web3.eth.Contract(MockERC20.abi as AbiItem[]) this.ERC721FactoryBytecode = factory721Bytecode this.ERC20TemplateBytecode = template20Bytecode @@ -77,6 +83,7 @@ export class TestContractHandler { this.SideStakingBytecode = sideStakingBytecode this.FixedRateBytecode = fixedRateBytecode this.DispenserBytecode = dispenserBytecode + this.MockERC20Bytecode = MockERC20.bytecode } public async getAccounts(): Promise { @@ -276,6 +283,75 @@ export class TestContractHandler { return contract.options.address }) + // DEPLOY OCEAN MOCK + // get est gascost + estGas = await this.MockERC20.deploy({ + data: this.MockERC20Bytecode, + arguments: ['OCEAN', 'OCEAN', 18] + }).estimateGas(function (err, estGas) { + if (err) console.log('DeployContracts: ' + err) + return estGas + }) + // deploy the contract and get it's address + this.oceanAddress = await this.MockERC20.deploy({ + data: this.MockERC20Bytecode, + arguments: ['OCEAN', 'OCEAN', 18] + }) + .send({ + from: owner, + gas: estGas + 1, + gasPrice: '3000000000' + }) + .then(function (contract) { + return contract.options.address + }) + + // DEPLOY USDC MOCK + // get est gascost + estGas = await this.MockERC20.deploy({ + data: this.MockERC20Bytecode, + arguments: ['USDC', 'USDC', 6] + }).estimateGas(function (err, estGas) { + if (err) console.log('DeployContracts: ' + err) + return estGas + }) + // deploy the contract and get it's address + this.usdcAddress = await this.MockERC20.deploy({ + data: this.MockERC20Bytecode, + arguments: ['USDC', 'USDC', 6] + }) + .send({ + from: owner, + gas: estGas + 1, + gasPrice: '3000000000' + }) + .then(function (contract) { + return contract.options.address + }) + + // DEPLOY DAI MOCK + // get est gascost + estGas = await this.MockERC20.deploy({ + data: this.MockERC20Bytecode, + arguments: ['DAI', 'DAI', 18] + }).estimateGas(function (err, estGas) { + if (err) console.log('DeployContracts: ' + err) + return estGas + }) + // deploy the contract and get it's address + this.daiAddress = await this.MockERC20.deploy({ + data: this.MockERC20Bytecode, + arguments: ['DAI', 'DAI', 18] + }) + .send({ + from: owner, + gas: estGas + 1, + gasPrice: '3000000000' + }) + .then(function (contract) { + return contract.options.address + }) + const RouterContract = new this.web3.eth.Contract(routerABI, this.routerAddress) await RouterContract.methods.addFactory(this.factory721Address).send({ from: owner }) diff --git a/test/unit/NFTFactory.test.ts b/test/unit/NFTFactory.test.ts index 0cfec17b..6acbf172 100644 --- a/test/unit/NFTFactory.test.ts +++ b/test/unit/NFTFactory.test.ts @@ -9,6 +9,7 @@ import Router from '@oceanprotocol/contracts/artifacts/contracts/pools/FactoryRo 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 { LoggerInstance } from '../../src/utils' // import { NFTDataToken } from '../../../src/datatokens/NFTDatatoken' @@ -24,16 +25,10 @@ describe('NFT Factory test', () => { let user3: string let contracts: TestContractHandler let nftFactory: NFTFactory + let dtAddress: string + let dtAddress2: string + let nftAddress: string - const nftName = 'NFT' - const nftSymbol = 'NFTSymbol' - const nftTemplateIndex = 1 - const data = web3.utils.asciiToHex('SomeData') - const flags = web3.utils.asciiToHex( - 'f8929916089218bdb4aa78c3ecd16633afd44b8aef89299160' - ) - - // TODO: complete unit test it('should deploy contracts', async () => { contracts = new TestContractHandler( web3, @@ -61,10 +56,16 @@ describe('NFT Factory test', () => { user1 = contracts.accounts[2] user2 = contracts.accounts[3] user3 = contracts.accounts[4] - console.log(factoryOwner) + await contracts.deployContracts(factoryOwner, Router.abi as AbiItem[]) - console.log('BOOM') + const daiContract = new web3.eth.Contract( + contracts.MockERC20.options.jsonInterface, + contracts.daiAddress + ) + await daiContract.methods + .approve(contracts.factory721Address, web3.utils.toWei('10000')) + .send({ from: contracts.accounts[0] }) }) it('should initiate NFTFactory instance', async () => { @@ -152,7 +153,44 @@ describe('NFT Factory test', () => { assert(tokenTemplate.isActive === true) }) - it('#createNFTwithErc - should create an NFT and a Datatoken', async () => { + it('#createNftwithErc - should create an NFT and a Datatoken ', async () => { + // 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, + user2, + '0x0000000000000000000000000000000000000000' + ], + uints: [web3.utils.toWei('10000'), 0], + bytess: [] + } + + const txReceipt = await nftFactory.createNftWithErc( + contracts.accounts[0], + nftData, + ercData + ) + + // EVENTS HAVE BEEN EMITTED + expect(txReceipt.events.NFTCreated.event === 'NFTCreated') + expect(txReceipt.events.TokenCreated.event === 'TokenCreated') + + // stored for later use in startMultipleTokenOrder test + nftAddress = txReceipt.events.NFTCreated.returnValues.newTokenAddress + dtAddress = txReceipt.events.TokenCreated.returnValues.newTokenAddress + }) + + it('#createNftErcWithPool- should create an NFT, a Datatoken and a pool DT/DAI', async () => { + // we prepare transaction parameters objects const nftData = { name: '72120Bundle', symbol: '72Bundle', @@ -163,18 +201,162 @@ describe('NFT Factory test', () => { templateIndex: 1, strings: ['ERC20B1', 'ERC20DT1Symbol'], addresses: [user2, user3, user2, '0x0000000000000000000000000000000000000000'], - uints: [web3.utils.toWei('10000'), 0], + uints: [web3.utils.toWei('1000000'), 0], bytess: [] } - const txReceipt = await nftFactory.createNftWithErc( + const poolData = { + addresses: [ + contracts.sideStakingAddress, + contracts.daiAddress, + contracts.factory721Address, + contracts.accounts[0], + contracts.accounts[0], + contracts.poolTemplateAddress + ], + ssParams: [ + web3.utils.toWei('1'), // rate + 18, // basetokenDecimals + web3.utils.toWei('10000'), + 2500000, // vested blocks + web3.utils.toWei('2000') // baseToken initial pool liquidity + ], + swapFees: [ + 1e15, // + 1e15 + ] + } + + const txReceipt = await nftFactory.createNftErcWithPool( contracts.accounts[0], nftData, - ercData + ercData, + poolData ) + + // EVENTS HAVE BEEN EMITTED expect(txReceipt.events.NFTCreated.event === 'NFTCreated') expect(txReceipt.events.TokenCreated.event === 'TokenCreated') - console.log(txReceipt.events.NFTCreated.returnValues.newTokenAddress) - console.log(txReceipt.events.TokenCreated.returnValues.newTokenAddress) + expect(txReceipt.events.NewPool.event === 'NewPool') + }) + + it('#createNftErcWithFixedRate- should create an NFT, a datatoken and create a Fixed Rate Exchange', async () => { + // 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, + user2, + '0x0000000000000000000000000000000000000000' + ], + uints: [web3.utils.toWei('1000000'), 0], + bytess: [] + } + + const fixedData = { + fixedPriceAddress: contracts.fixedRateAddress, + addresses: [contracts.daiAddress, contracts.accounts[0], contracts.accounts[0]], + uints: [18, 18, web3.utils.toWei('1'), 1e15] + } + + const txReceipt = await nftFactory.createNftErcWithFixedRate( + contracts.accounts[0], + nftData, + ercData, + fixedData + ) + + // EVENTS HAVE BEEN EMITTED + expect(txReceipt.events.NFTCreated.event === 'NFTCreated') + expect(txReceipt.events.TokenCreated.event === 'TokenCreated') + expect(txReceipt.events.NewFixedRate.event === 'NewFixedRate') + + // stored for later use in startMultipleTokenOrder test + dtAddress2 = txReceipt.events.TokenCreated.returnValues.newTokenAddress + }) + + it('#startMultipleTokenOrder- should succed to start multiple orders', async () => { + const consumer = user2 // could be different user + const dtAmount = web3.utils.toWei('1') + const serviceId = 1 // dummy index + const consumeFeeAddress = user3 // marketplace fee Collector + const consumeFeeAmount = 0 // fee to be collected on top, requires approval + const consumeFeeToken = contracts.daiAddress // token address for the feeAmount, in this case DAI + + // we reuse a DT created in a previous test + const dtContract = new web3.eth.Contract(ERC20Template.abi as AbiItem[], dtAddress) + expect(await dtContract.methods.balanceOf(user2).call()).to.equal('0') + + // dt owner mint dtAmount to user2 + await dtContract.methods.mint(user2, dtAmount).send({ from: contracts.accounts[0] }) + + // user2 approves NFTFactory to move his dtAmount + await dtContract.methods + .approve(contracts.factory721Address, dtAmount) + .send({ from: user2 }) + + // we reuse another DT created in a previous test + const dtContract2 = new web3.eth.Contract(ERC20Template.abi as AbiItem[], dtAddress2) + expect(await dtContract2.methods.balanceOf(user2).call()).to.equal('0') + + // dt owner mint dtAmount to user2 + await dtContract2.methods.mint(user2, dtAmount).send({ from: contracts.accounts[0] }) + // user2 approves NFTFactory to move his dtAmount + await dtContract2.methods + .approve(contracts.factory721Address, dtAmount) + .send({ from: user2 }) + + // we check user2 has enought DTs + expect(await dtContract.methods.balanceOf(user2).call()).to.equal(dtAmount) + expect(await dtContract2.methods.balanceOf(user2).call()).to.equal(dtAmount) + + const orders = [ + { + tokenAddress: dtAddress, + consumer: consumer, + amount: dtAmount, + serviceId: serviceId, + consumeFeeAddress: consumeFeeAddress, + consumeFeeToken: consumeFeeToken, + consumeFeeAmount: consumeFeeAmount + }, + { + tokenAddress: dtAddress2, + consumer: consumer, + amount: dtAmount, + serviceId: serviceId, + consumeFeeAddress: consumeFeeAddress, + consumeFeeToken: consumeFeeToken, + consumeFeeAmount: consumeFeeAmount + } + ] + + await nftFactory.startMultipleTokenOrder(user2, orders) + + // we check user2 has no more DTs + expect(await dtContract.methods.balanceOf(user2).call()).to.equal('0') + expect(await dtContract2.methods.balanceOf(user2).call()).to.equal('0') + }) + it('#checkDatatoken - should confirm if DT is from the factory', async () => { + assert((await nftFactory.checkDatatoken(dtAddress)) === true) + assert((await nftFactory.checkDatatoken(dtAddress2)) === true) + assert((await nftFactory.checkDatatoken(user2)) === false) + assert((await nftFactory.checkDatatoken(nftAddress)) === false) + }) + + it('#checkNFT - should return nftAddress if from the factory, or address(0) if not', async () => { + assert( + (await nftFactory.checkNFT(dtAddress)) === + '0x0000000000000000000000000000000000000000' + ) + assert((await nftFactory.checkNFT(nftAddress)) === nftAddress) }) }) From 70257a0447c52ef0baaa0af39108c2f64bb08068 Mon Sep 17 00:00:00 2001 From: lacoop6tu Date: Fri, 22 Oct 2021 10:55:05 -0500 Subject: [PATCH 3/5] add Router Class and some unit test --- package.json | 2 +- src/factories/NFTFactory.ts | 2 +- src/pools/Router.ts | 378 +++++++++++++++++++++++++++++++++ test/TestContractHandler.ts | 68 +++--- test/unit/NFTFactory.test.ts | 8 +- test/unit/pools/Router.test.ts | 149 +++++++++++++ 6 files changed, 573 insertions(+), 34 deletions(-) create mode 100644 src/pools/Router.ts create mode 100644 test/unit/pools/Router.test.ts diff --git a/package.json b/package.json index 85854213..60966e93 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "release": "release-it --non-interactive", "changelog": "auto-changelog -p", "prepublishOnly": "npm run build", - "test:pool": "mocha --config=test/unit/.mocharc.json --node-env=test --exit 'test/unit/NFTFactory.test.ts'", + "test:pool": "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'", "test:unit:cover": "nyc --report-dir coverage/unit npm run test:unit", "test:integration": "mocha --config=test/integration/.mocharc.json --node-env=test --exit 'test/integration/**/*.test.ts'", diff --git a/src/factories/NFTFactory.ts b/src/factories/NFTFactory.ts index bdadb2fc..469fde0d 100644 --- a/src/factories/NFTFactory.ts +++ b/src/factories/NFTFactory.ts @@ -47,7 +47,7 @@ interface FixedData { uints: (string | number)[] } /** - * Provides an interface for NFT DataTokens + * Provides an interface for NFT Factory contract */ export class NFTFactory { public GASLIMIT_DEFAULT = 1000000 diff --git a/src/pools/Router.ts b/src/pools/Router.ts new file mode 100644 index 00000000..5aa2258e --- /dev/null +++ b/src/pools/Router.ts @@ -0,0 +1,378 @@ +import { Contract } from 'web3-eth-contract' +import Web3 from 'web3' +import { TransactionReceipt } from 'web3-core' +import { AbiItem } from 'web3-utils' +import defaultRouter from '@oceanprotocol/contracts/artifacts/contracts/pools/FactoryRouter.sol/FactoryRouter.json' +import { Logger, getFairGasPrice, generateDtName } from '../utils' + +interface Operations { + exchangeIds: string + source: string + operation: number + tokenIn: string + amountsIn: string | number + tokenOut: string + amountsOut: string | number + maxPrice: string | number +} + +/** + * Provides an interface for FactoryRouter contract + */ +export class Router { + public GASLIMIT_DEFAULT = 1000000 + public routerAddress: string + public RouterABI: AbiItem | AbiItem[] + public web3: Web3 + private logger: Logger + public startBlock: number + public router: Contract + + /** + * Instantiate Router. + * @param {String} routerAddress + * @param {AbiItem | AbiItem[]} Router + * @param {Web3} web3 + */ + constructor( + routerAddress: string, + web3: Web3, + logger: Logger, + RouterABI?: AbiItem | AbiItem[], + startBlock?: number + ) { + this.routerAddress = routerAddress + this.RouterABI = RouterABI || (defaultRouter.abi as AbiItem[]) + this.web3 = web3 + this.logger = logger + this.startBlock = startBlock || 0 + this.router = new this.web3.eth.Contract(this.RouterABI, this.routerAddress) + } + + /** + * BuyDTBatch + * @param {String} address + * @param {Operations} operations Operations objects array + * @return {Promise} Transaction receipt + */ + public async buyDTBatch( + address: string, + operations: Operations[] + ): Promise { + // Get estimated gas value + const gasLimitDefault = this.GASLIMIT_DEFAULT + let estGas + try { + estGas = await this.router.methods + .buyDTBatch(operations) + .estimateGas({ from: address }, (err, estGas) => (err ? gasLimitDefault : estGas)) + } catch (e) { + estGas = gasLimitDefault + } + + // Invoke createToken function of the contract + const trxReceipt = await this.router.methods.buyDTBatch(operations).send({ + from: address, + gas: estGas + 1, + gasPrice: await getFairGasPrice(this.web3) + }) + + return trxReceipt + } + + /** Check if a token is on ocean tokens list, if true opfFee is ZERO in pools with that token/DT + * @return {Promise} true if is on the list. + */ + public async isOceanTokens(address: string): Promise { + return await this.router.methods.oceanTokens(address).call() + } + + /** Check if an address is a side staking contract. + * @return {Promise} true if is a SS contract + */ + public async isSideStaking(address: string): Promise { + return await this.router.methods.ssContracts(address).call() + } + + /** Check if an address is a Fixed Rate contract. + * @return {Promise} true if is a Fixed Rate contract + */ + public async isFixedPrice(address: string): Promise { + return await this.router.methods.fixedPrice(address).call() + } + + /** Get Router Owner + * @return {Promise} Router Owner address + */ + public async getOwner(): Promise { + return await this.router.methods.routerOwner().call() + } + + /** Get NFT Factory address + * @return {Promise} NFT Factory address + */ + public async getNFTFactory(): Promise { + return await this.router.methods.factory().call() + } + + /** Check if an address is a pool template contract. + * @return {Promise} true if is a Template + */ + public async isPoolTemplate(address: string): Promise { + return await this.router.methods.isPoolTemplate(address).call() + } + + /** + * Add a new token to oceanTokens list, pools with basetoken in this list have NO opf Fee + * @param {String} address + * @param {String} tokenAddress template address to add + * @return {Promise} + */ + public async addOceanToken( + address: string, + tokenAddress: string + ): Promise { + if ((await this.getOwner()) !== address) { + throw new Error(`Caller is not Router Owner`) + } + + const gasLimitDefault = this.GASLIMIT_DEFAULT + let estGas + try { + estGas = await this.router.methods + .addOceanToken(tokenAddress) + .estimateGas({ from: address }, (err, estGas) => (err ? gasLimitDefault : estGas)) + } catch (e) { + estGas = gasLimitDefault + } + + // Invoke createToken function of the contract + const trxReceipt = await this.router.methods.addOceanToken(tokenAddress).send({ + from: address, + gas: estGas + 1, + gasPrice: await getFairGasPrice(this.web3) + }) + + return trxReceipt + } + + /** + * Remove a token from oceanTokens list, pools without basetoken in this list have a opf Fee + * @param {String} address + * @param {String} tokenAddress address to remove + * @return {Promise} + */ + public async removeOceanToken( + address: string, + tokenAddress: string + ): Promise { + if ((await this.getOwner()) !== address) { + throw new Error(`Caller is not Router Owner`) + } + + const gasLimitDefault = this.GASLIMIT_DEFAULT + let estGas + try { + estGas = await this.router.methods + .removeOceanToken(tokenAddress) + .estimateGas({ from: address }, (err, estGas) => (err ? gasLimitDefault : estGas)) + } catch (e) { + estGas = gasLimitDefault + } + + // Invoke createToken function of the contract + const trxReceipt = await this.router.methods.removeOceanToken(tokenAddress).send({ + from: address, + gas: estGas + 1, + gasPrice: await getFairGasPrice(this.web3) + }) + + return trxReceipt + } + + /** + * Add a new contract to ssContract list, after is added, can be used when deploying a new pool + * @param {String} address + * @param {String} tokenAddress contract address to add + * @return {Promise} + */ + public async addSSContract( + address: string, + tokenAddress: string + ): Promise { + if ((await this.getOwner()) !== address) { + throw new Error(`Caller is not Router Owner`) + } + + const gasLimitDefault = this.GASLIMIT_DEFAULT + let estGas + try { + estGas = await this.router.methods + .addSSContract(tokenAddress) + .estimateGas({ from: address }, (err, estGas) => (err ? gasLimitDefault : estGas)) + } catch (e) { + estGas = gasLimitDefault + } + + // Invoke createToken function of the contract + const trxReceipt = await this.router.methods.addSSContract(tokenAddress).send({ + from: address, + gas: estGas + 1, + gasPrice: await getFairGasPrice(this.web3) + }) + + return trxReceipt + } + + /** + * Add a new contract to fixedRate list, after is added, can be used when deploying a new pool + * @param {String} address + * @param {String} tokenAddress contract address to add + * @return {Promise} + */ + public async addFixedRateContract( + address: string, + tokenAddress: string + ): Promise { + if ((await this.getOwner()) !== address) { + throw new Error(`Caller is not Router Owner`) + } + + const gasLimitDefault = this.GASLIMIT_DEFAULT + let estGas + try { + estGas = await this.router.methods + .addFixedRateContract(tokenAddress) + .estimateGas({ from: address }, (err, estGas) => (err ? gasLimitDefault : estGas)) + } catch (e) { + estGas = gasLimitDefault + } + + // Invoke createToken function of the contract + const trxReceipt = await this.router.methods.addFixedRateContract(tokenAddress).send({ + from: address, + gas: estGas + 1, + gasPrice: await getFairGasPrice(this.web3) + }) + + return trxReceipt + } + + /** Get OPF Fee per token + * @return {Promise} OPF fee for a specific baseToken + */ + public async getOPFFee(baseToken: string): Promise { + return await this.router.methods.getOPFFee(baseToken).call() + } + + /** Get Current OPF Fee + * @return {Promise} OPF fee + */ + public async getCurrentOPFFee(): Promise { + return await this.router.methods.swapOceanFee().call() + } + + /** + * Add a new contract to fixedRate list, after is added, can be used when deploying a new pool + * @param {String} address + * @param {String} newFee new OPF Fee + * @return {Promise} + */ + public async updateOPFFee( + address: string, + newFee: number + ): Promise { + if ((await this.getOwner()) !== address) { + throw new Error(`Caller is not Router Owner`) + } + + const gasLimitDefault = this.GASLIMIT_DEFAULT + let estGas + try { + estGas = await this.router.methods + .updateOPFFee(newFee) + .estimateGas({ from: address }, (err, estGas) => (err ? gasLimitDefault : estGas)) + } catch (e) { + estGas = gasLimitDefault + } + + // Invoke createToken function of the contract + const trxReceipt = await this.router.methods.updateOPFFee(newFee).send({ + from: address, + gas: estGas + 1, + gasPrice: await getFairGasPrice(this.web3) + }) + + return trxReceipt + } + + /** + * Add a new template to poolTemplates mapping, after template is added,it can be used + * @param {String} address + * @param {String} templateAddress template address to add + * @return {Promise} + */ + public async addPoolTemplate( + address: string, + templateAddress: string + ): Promise { + if ((await this.getOwner()) !== address) { + throw new Error(`Caller is not Router Owner`) + } + + const gasLimitDefault = this.GASLIMIT_DEFAULT + let estGas + try { + estGas = await this.router.methods + .addPoolTemplate(templateAddress) + .estimateGas({ from: address }, (err, estGas) => (err ? gasLimitDefault : estGas)) + } catch (e) { + estGas = gasLimitDefault + } + + // Invoke createToken function of the contract + const trxReceipt = await this.router.methods.addPoolTemplate(templateAddress).send({ + from: address, + gas: estGas + 1, + gasPrice: await getFairGasPrice(this.web3) + }) + + return trxReceipt + } + + /** + * Remove template from poolTemplates mapping, after template is removed,it can be used anymore + * @param {String} address + * @param {String} templateAddress template address to remove + * @return {Promise} + */ + public async removePoolTemplate( + address: string, + templateAddress: string + ): Promise { + if ((await this.getOwner()) !== address) { + throw new Error(`Caller is not Router Owner`) + } + + const gasLimitDefault = this.GASLIMIT_DEFAULT + let estGas + try { + estGas = await this.router.methods + .removePoolTemplate(templateAddress) + .estimateGas({ from: address }, (err, estGas) => (err ? gasLimitDefault : estGas)) + } catch (e) { + estGas = gasLimitDefault + } + + // Invoke createToken function of the contract + const trxReceipt = await this.router.methods + .removePoolTemplate(templateAddress) + .send({ + from: address, + gas: estGas + 1, + gasPrice: await getFairGasPrice(this.web3) + }) + + return trxReceipt + } +} diff --git a/test/TestContractHandler.ts b/test/TestContractHandler.ts index 696cc9d6..61a5a084 100644 --- a/test/TestContractHandler.ts +++ b/test/TestContractHandler.ts @@ -163,10 +163,39 @@ export class TestContractHandler { return contract.options.address }) + // DEPLOY OCEAN MOCK + // get est gascost + estGas = await this.MockERC20.deploy({ + data: this.MockERC20Bytecode, + arguments: ['OCEAN', 'OCEAN', 18] + }).estimateGas(function (err, estGas) { + if (err) console.log('DeployContracts: ' + err) + return estGas + }) + // deploy the contract and get it's address + this.oceanAddress = await this.MockERC20.deploy({ + data: this.MockERC20Bytecode, + arguments: ['OCEAN', 'OCEAN', 18] + }) + .send({ + from: owner, + gas: estGas + 1, + gasPrice: '3000000000' + }) + .then(function (contract) { + return contract.options.address + }) + // DEPLOY ROUTER estGas = await this.Router.deploy({ data: this.RouterBytecode, - arguments: [owner, oceanAddress, this.poolTemplateAddress, communityCollector, []] + arguments: [ + owner, + this.oceanAddress, + this.poolTemplateAddress, + communityCollector, + [] + ] }).estimateGas(function (err, estGas) { if (err) console.log('DeployContracts: ' + err) return estGas @@ -174,7 +203,13 @@ export class TestContractHandler { // deploy the contract and get it's address this.routerAddress = await this.Router.deploy({ data: this.RouterBytecode, - arguments: [owner, oceanAddress, this.poolTemplateAddress, communityCollector, []] + arguments: [ + owner, + this.oceanAddress, + this.poolTemplateAddress, + communityCollector, + [] + ] }) .send({ from: owner, @@ -283,29 +318,6 @@ export class TestContractHandler { return contract.options.address }) - // DEPLOY OCEAN MOCK - // get est gascost - estGas = await this.MockERC20.deploy({ - data: this.MockERC20Bytecode, - arguments: ['OCEAN', 'OCEAN', 18] - }).estimateGas(function (err, estGas) { - if (err) console.log('DeployContracts: ' + err) - return estGas - }) - // deploy the contract and get it's address - this.oceanAddress = await this.MockERC20.deploy({ - data: this.MockERC20Bytecode, - arguments: ['OCEAN', 'OCEAN', 18] - }) - .send({ - from: owner, - gas: estGas + 1, - gasPrice: '3000000000' - }) - .then(function (contract) { - return contract.options.address - }) - // DEPLOY USDC MOCK // get est gascost estGas = await this.MockERC20.deploy({ @@ -366,8 +378,8 @@ export class TestContractHandler { .send({ from: owner }) // TODO: add OPF deployment and update argument // TODO: how are we going to call those functions with an OPF contract? it should be a multisig the owner - await RouterContract.methods - .changeRouterOwner(communityCollector) - .send({ from: owner }) + // await RouterContract.methods + // .changeRouterOwner(communityCollector) + // .send({ from: owner }) } } diff --git a/test/unit/NFTFactory.test.ts b/test/unit/NFTFactory.test.ts index 6acbf172..416e4fb1 100644 --- a/test/unit/NFTFactory.test.ts +++ b/test/unit/NFTFactory.test.ts @@ -114,7 +114,7 @@ describe('NFT Factory test', () => { it('#disableNFTTemplate - should disable NFT template if factory owner', async () => { let nftTemplate = await nftFactory.getNFTTemplate(2) assert(nftTemplate.isActive === true) - await nftFactory.disableNFTTemplate(contracts.accounts[0], 2) // owner disable template index = 2 + await nftFactory.disableNFTTemplate(contracts.accounts[0], 2) // owner disables template index = 2 nftTemplate = await nftFactory.getNFTTemplate(2) assert(nftTemplate.isActive === false) @@ -122,7 +122,7 @@ describe('NFT Factory test', () => { it('#reactivateNFTTemplate - should disable NFT template if factory owner', async () => { let nftTemplate = await nftFactory.getNFTTemplate(2) assert(nftTemplate.isActive === false) - await nftFactory.reactivateNFTTemplate(contracts.accounts[0], 2) // owner reactivate template index = 2 + await nftFactory.reactivateNFTTemplate(contracts.accounts[0], 2) // owner reactivates template index = 2 nftTemplate = await nftFactory.getNFTTemplate(2) assert(nftTemplate.isActive === true) @@ -139,7 +139,7 @@ describe('NFT Factory test', () => { it('#disableTokenTemplate - should disable Token template if factory owner', async () => { let tokenTemplate = await nftFactory.getTokenTemplate(2) assert(tokenTemplate.isActive === true) - await nftFactory.disableTokenTemplate(contracts.accounts[0], 2) // owner disable template index = 2 + await nftFactory.disableTokenTemplate(contracts.accounts[0], 2) // owner disables template index = 2 tokenTemplate = await nftFactory.getTokenTemplate(2) assert(tokenTemplate.isActive === false) @@ -147,7 +147,7 @@ describe('NFT Factory test', () => { it('#reactivateTokenTemplate - should disable Token template if factory owner', async () => { let tokenTemplate = await nftFactory.getTokenTemplate(2) assert(tokenTemplate.isActive === false) - await nftFactory.reactivateTokenTemplate(contracts.accounts[0], 2) // owner reactivate template index = 2 + await nftFactory.reactivateTokenTemplate(contracts.accounts[0], 2) // owner reactivates template index = 2 tokenTemplate = await nftFactory.getTokenTemplate(2) assert(tokenTemplate.isActive === true) diff --git a/test/unit/pools/Router.test.ts b/test/unit/pools/Router.test.ts new file mode 100644 index 00000000..dc927a8d --- /dev/null +++ b/test/unit/pools/Router.test.ts @@ -0,0 +1,149 @@ +import { assert, expect } from 'chai' +import { AbiItem } from 'web3-utils/types' +import { TestContractHandler } from '../../TestContractHandler' +import Web3 from 'web3' +import ERC721Factory from '@oceanprotocol/contracts/artifacts/contracts/ERC721Factory.sol/ERC721Factory.json' +import ERC721Template from '@oceanprotocol/contracts/artifacts/contracts/templates/ERC721Template.sol/ERC721Template.json' +import SideStaking 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 { LoggerInstance } from '../../../src/utils' +// import { NFTDataToken } from '../../../src/datatokens/NFTDatatoken' +import { Router } from '../../../src/pools/Router' + +const web3 = new Web3('http://127.0.0.1:8545') +const communityCollector = '0xeE9300b7961e0a01d9f0adb863C7A227A07AaD75' + +describe('Router unit test', () => { + let factoryOwner: string + let nftOwner: string + let user1: string + let user2: string + let user3: string + let contracts: TestContractHandler + let router: Router + let dtAddress: string + let dtAddress2: string + let nftAddress: string + + 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[], + SideStaking.abi as AbiItem[], + FixedRate.abi as AbiItem[], + Dispenser.abi as AbiItem[], + + ERC721Template.bytecode, + ERC20Template.bytecode, + PoolTemplate.bytecode, + ERC721Factory.bytecode, + FactoryRouter.bytecode, + SideStaking.bytecode, + FixedRate.bytecode, + Dispenser.bytecode + ) + await contracts.getAccounts() + factoryOwner = contracts.accounts[0] + nftOwner = contracts.accounts[1] + user1 = contracts.accounts[2] + user2 = contracts.accounts[3] + user3 = contracts.accounts[4] + + await contracts.deployContracts(factoryOwner, FactoryRouter.abi as AbiItem[]) + + const daiContract = new web3.eth.Contract( + contracts.MockERC20.options.jsonInterface, + contracts.daiAddress + ) + await daiContract.methods + .approve(contracts.factory721Address, web3.utils.toWei('10000')) + .send({ from: contracts.accounts[0] }) + }) + + it('should initiate Router instance', async () => { + router = new Router(contracts.routerAddress, web3, LoggerInstance) + }) + + it('#getOwner - should return actual owner', async () => { + const owner = await router.getOwner() + assert(owner === contracts.accounts[0]) + }) + + it('#getNFTFactory - should return NFT Factory address', async () => { + const factory = await router.getNFTFactory() + assert(factory === contracts.factory721Address) + }) + + it('#isOceanTokens - should return true if in oceanTokens list', async () => { + expect(await router.isOceanTokens(contracts.oceanAddress)).to.equal(true) + expect(await router.isOceanTokens(contracts.daiAddress)).to.equal(false) + }) + it('#isSideStaking - should return true if in ssContracts list', async () => { + expect(await router.isSideStaking(contracts.sideStakingAddress)).to.equal(true) + expect(await router.isSideStaking(contracts.fixedRateAddress)).to.equal(false) + }) + it('#isFixedPrice - should return true if in fixedPrice list', async () => { + expect(await router.isFixedPrice(contracts.fixedRateAddress)).to.equal(true) + expect(await router.isFixedPrice(contracts.daiAddress)).to.equal(false) + // Dispenser contract is also a fixed price contract + expect(await router.isFixedPrice(contracts.dispenserAddress)).to.equal(true) + }) + it('#isPoolTemplate - should return true if in poolTemplates list', async () => { + expect(await router.isPoolTemplate(contracts.poolTemplateAddress)).to.equal(true) + expect(await router.isPoolTemplate(contracts.fixedRateAddress)).to.equal(false) + }) + it('#addOceanToken - should add a new token into oceanTokens list(NO OPF FEE)', async () => { + await router.addOceanToken(contracts.accounts[0], contracts.daiAddress) + expect(await router.isOceanTokens(contracts.daiAddress)).to.equal(true) + }) + it('#removeOceanToken - should remove a token from oceanTokens list', async () => { + await router.removeOceanToken(contracts.accounts[0], contracts.daiAddress) + expect(await router.isOceanTokens(contracts.daiAddress)).to.equal(false) + }) + it('#addSSContract - should add a new token into SSContracts list', async () => { + await router.addSSContract(contracts.accounts[0], contracts.daiAddress) + expect(await router.isSideStaking(contracts.daiAddress)).to.equal(true) + }) + it('#addFixedRate - should add a new token into fixedPrice list', async () => { + await router.addFixedRateContract(contracts.accounts[0], contracts.daiAddress) + expect(await router.isFixedPrice(contracts.daiAddress)).to.equal(true) + }) + + it('#getOPFFee - should return actual OPF fee for a given baseToken', async () => { + const opfFee = 1e15 + expect(await router.getOPFFee(contracts.oceanAddress)).to.equal('0') + expect(await router.getOPFFee(contracts.daiAddress)).to.equal(opfFee.toString()) + }) + + it('#getCurrentOPFFee - should return actual OPF Fee', async () => { + const opfFee = 1e15 + expect(await router.getCurrentOPFFee()).to.equal(opfFee.toString()) + }) + + it('#updateOPFFee - should update opf fee if Router Owner', async () => { + const opfFee = 1e15 + expect(await router.getCurrentOPFFee()).to.equal(opfFee.toString()) + const newOPFFee = 1e14 + await router.updateOPFFee(contracts.accounts[0], 1e14) + expect(await router.getCurrentOPFFee()).to.equal(newOPFFee.toString()) + }) + + it('#addPoolTemplate - should add a new token into poolTemplates mapping if Router Owner', async () => { + await router.addPoolTemplate(contracts.accounts[0], contracts.daiAddress) + expect(await router.isPoolTemplate(contracts.daiAddress)).to.equal(true) + }) + + it('#removePoolTemplate - should add a new token into poolTemplates mapping if Router Owner', async () => { + await router.removePoolTemplate(contracts.accounts[0], contracts.daiAddress) + expect(await router.isPoolTemplate(contracts.daiAddress)).to.equal(false) + }) +}) From c8befe64c65ddf110b0edae456e6882371380272 Mon Sep 17 00:00:00 2001 From: lacoop6tu Date: Mon, 25 Oct 2021 08:28:23 -0500 Subject: [PATCH 4/5] complete basic Router unit test --- test/unit/pools/Router.test.ts | 186 ++++++++++++++++++++++++++++++++- 1 file changed, 184 insertions(+), 2 deletions(-) diff --git a/test/unit/pools/Router.test.ts b/test/unit/pools/Router.test.ts index dc927a8d..0d9314be 100644 --- a/test/unit/pools/Router.test.ts +++ b/test/unit/pools/Router.test.ts @@ -12,9 +12,9 @@ import FixedRate from '@oceanprotocol/contracts/artifacts/contracts/pools/fixedR 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 { LoggerInstance } from '../../../src/utils' -// import { NFTDataToken } from '../../../src/datatokens/NFTDatatoken' +import { NFTFactory } from '../../../src/factories/NFTFactory' import { Router } from '../../../src/pools/Router' - +const { keccak256 } = require('@ethersproject/keccak256') const web3 = new Web3('http://127.0.0.1:8545') const communityCollector = '0xeE9300b7961e0a01d9f0adb863C7A227A07AaD75' @@ -67,6 +67,10 @@ describe('Router unit test', () => { await daiContract.methods .approve(contracts.factory721Address, web3.utils.toWei('10000')) .send({ from: contracts.accounts[0] }) + + expect(await daiContract.methods.balanceOf(contracts.accounts[0]).call()).to.equal( + web3.utils.toWei('100000') + ) }) it('should initiate Router instance', async () => { @@ -146,4 +150,182 @@ describe('Router unit test', () => { await router.removePoolTemplate(contracts.accounts[0], contracts.daiAddress) expect(await router.isPoolTemplate(contracts.daiAddress)).to.equal(false) }) + + it('#buyDTBatch - should buy multiple DT in one call', async () => { + // APPROVE DAI + const daiContract = new web3.eth.Contract( + contracts.MockERC20.options.jsonInterface, + contracts.daiAddress + ) + + expect(await daiContract.methods.balanceOf(user2).call()).to.equal('0') + await daiContract.methods + .transfer(user2, web3.utils.toWei('2')) + .send({ from: contracts.accounts[0] }) + expect(await daiContract.methods.balanceOf(user2).call()).to.equal( + web3.utils.toWei('2') + ) + await daiContract.methods + .approve(contracts.routerAddress, web3.utils.toWei('2')) + .send({ from: user2 }) + + // CREATE A FIRST POOL + // 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: [] + } + + const poolData = { + addresses: [ + contracts.sideStakingAddress, + contracts.daiAddress, + contracts.factory721Address, + contracts.accounts[0], + contracts.accounts[0], + contracts.poolTemplateAddress + ], + ssParams: [ + web3.utils.toWei('1'), // rate + 18, // basetokenDecimals + web3.utils.toWei('10000'), + 2500000, // vested blocks + web3.utils.toWei('2000') // baseToken initial pool liquidity + ], + swapFees: [ + 1e15, // + 1e15 + ] + } + + const nftFactory = new NFTFactory(contracts.factory721Address, web3, LoggerInstance) + + const txReceipt = await nftFactory.createNftErcWithPool( + contracts.accounts[0], + nftData, + ercData, + poolData + ) + + const erc20Token = txReceipt.events.TokenCreated.returnValues.newTokenAddress + const pool1 = txReceipt.events.NewPool.returnValues.poolAddress + + // CREATE A SECOND POOL + + const nftData2 = { + name: '72120Bundle2', + symbol: '72Bundle2', + templateIndex: 1, + baseURI: 'https://oceanprotocol.com/nft2/' + } + const ercData2 = { + templateIndex: 1, + strings: ['ERC20B12', 'ERC20DT1Symbol2'], + addresses: [ + contracts.accounts[0], + user3, + contracts.accounts[0], + '0x0000000000000000000000000000000000000000' + ], + uints: [web3.utils.toWei('1000000'), 0], + bytess: [] + } + + const poolData2 = { + addresses: [ + contracts.sideStakingAddress, + contracts.daiAddress, + contracts.factory721Address, + contracts.accounts[0], + contracts.accounts[0], + contracts.poolTemplateAddress + ], + ssParams: [ + web3.utils.toWei('1'), // rate + 18, // basetokenDecimals + web3.utils.toWei('10000'), + 2500000, // vested blocks + web3.utils.toWei('2000') // baseToken initial pool liquidity + ], + swapFees: [ + 1e15, // + 1e15 + ] + } + + const txReceipt2 = await nftFactory.createNftErcWithPool( + contracts.accounts[0], + nftData2, + ercData2, + poolData2 + ) + + const erc20Token2 = txReceipt2.events.TokenCreated.returnValues.newTokenAddress + const pool2 = txReceipt2.events.NewPool.returnValues.poolAddress + + const erc20Contract = new web3.eth.Contract( + ERC20Template.abi as AbiItem[], + erc20Token + ) + // user2 has no dt1 + expect(await erc20Contract.methods.balanceOf(user2).call()).to.equal('0') + + const erc20Contract2 = new web3.eth.Contract( + ERC20Template.abi as AbiItem[], + erc20Token2 + ) + // user2 has no dt2 + expect(await erc20Contract2.methods.balanceOf(user2).call()).to.equal('0') + + // we now can prepare the Operations objects + + // operation: 0 - swapExactAmountIn + // 1 - swapExactAmountOut + // 2 - FixedRateExchange + // 3 - Dispenser + const operations1 = { + exchangeIds: keccak256('0x00'), // used only for FixedRate or Dispenser, but needs to be filled even for pool + source: pool1, // pool Address + operation: 0, // swapExactAmountIn + tokenIn: contracts.daiAddress, + amountsIn: web3.utils.toWei('1'), // when swapExactAmountIn is EXACT amount IN + tokenOut: erc20Token, + amountsOut: web3.utils.toWei('0.1'), // when swapExactAmountIn is MIN amount OUT + maxPrice: web3.utils.toWei('10') // max price (only for pools) + } + + const operations2 = { + exchangeIds: keccak256('0x00'), // used only for FixedRate or Dispenser, but needs to be filled even for pool + source: pool2, // pool Address + operation: 0, // swapExactAmountIn + tokenIn: contracts.daiAddress, + amountsIn: web3.utils.toWei('1'), // when swapExactAmountIn is EXACT amount IN + tokenOut: erc20Token2, + amountsOut: web3.utils.toWei('0.1'), // when swapExactAmountIn is MIN amount OUT + maxPrice: web3.utils.toWei('10') // max price (only for pools) + } + + await router.buyDTBatch(user2, [operations1, operations2]) + + // user2 had 2 dai and now has zero + expect(await daiContract.methods.balanceOf(user2).call()).to.equal('0') + + // user2 got his dts + expect(parseInt(await erc20Contract.methods.balanceOf(user2).call())).gt(0) + expect(parseInt(await erc20Contract2.methods.balanceOf(user2).call())).gt(0) + }) }) From 7815c61abaf335f3adf25853ebb4a4356ba83ee5 Mon Sep 17 00:00:00 2001 From: lacoop6tu Date: Mon, 25 Oct 2021 09:10:07 -0500 Subject: [PATCH 5/5] rm solved TODO --- test/TestContractHandler.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/TestContractHandler.ts b/test/TestContractHandler.ts index 61a5a084..ffc1143e 100644 --- a/test/TestContractHandler.ts +++ b/test/TestContractHandler.ts @@ -376,8 +376,7 @@ export class TestContractHandler { await RouterContract.methods .addSSContract(this.sideStakingAddress) .send({ from: owner }) - // TODO: add OPF deployment and update argument - // TODO: how are we going to call those functions with an OPF contract? it should be a multisig the owner + // TODO: add OPF deployment // await RouterContract.methods // .changeRouterOwner(communityCollector) // .send({ from: owner })