1
0
mirror of https://github.com/oceanprotocol/ocean.js.git synced 2024-11-26 20:39:05 +01:00

integrated dispenser class, added some unit tests

This commit is contained in:
Bogdan Fazakas 2021-10-29 12:54:35 +03:00
parent 9176170bb2
commit 5072bdbb5a
2 changed files with 535 additions and 0 deletions

View File

@ -1,6 +1,412 @@
import Web3 from 'web3'
import { AbiItem } from 'web3-utils'
import { Contract } from 'web3-eth-contract'
import { TransactionReceipt } from 'web3-eth'
import defaultDispenserABI from '@oceanprotocol/contracts/pools/dispenser/Dispenser.json'
import { LoggerInstance as logger, getFairGasPrice } from '../../utils/'
export interface DispenserToken {
active: boolean
owner: string
maxTokens: string
maxBalance: string
balance: string
minterApproved: boolean
isTrueMinter: boolean
allowedSwapper: string
}
export class Dispenser {
public GASLIMIT_DEFAULT = 1000000
public web3: Web3 = null
public dispenserAddress: string
public startBlock: number
public dispenserABI: AbiItem | AbiItem[]
public dispenserContract: Contract
/**
* Instantiate Dispenser
* @param {any} web3
* @param {String} dispenserAddress
* @param {any} dispenserABI
*/
constructor(
web3: Web3,
dispenserAddress: string = null,
dispenserABI: AbiItem | AbiItem[] = null,
startBlock?: number
) {
this.web3 = web3
this.dispenserAddress = dispenserAddress
if (startBlock) this.startBlock = startBlock
else this.startBlock = 0
this.dispenserABI = dispenserABI || (defaultDispenserABI.abi as AbiItem[])
if (web3)
this.dispenserContract = new this.web3.eth.Contract(
this.dispenserABI,
this.dispenserAddress
)
}
/**
* Get information about a datatoken dispenser
* @param {String} dtAddress
* @return {Promise<FixedPricedExchange>} Exchange details
*/
public async status(dtAdress: string): Promise<DispenserToken> {
try {
const result: DispenserToken = await this.dispenserContract.methods
.status(dtAdress)
.call()
result.maxTokens = this.web3.utils.fromWei(result.maxTokens)
result.maxBalance = this.web3.utils.fromWei(result.maxBalance)
result.balance = this.web3.utils.fromWei(result.balance)
return result
} catch (e) {
logger.warn(`No dispenser available for data token: ${dtAdress}`)
}
return null
}
/**
* Estimate gas cost for create method
* @param {String} dtAddress Datatoken address
* @param {String} address Owner address
* @param {String} maxTokens max tokens to dispense
* @param {String} maxBalance max balance of requester
* @param {String} allowedSwapper if !=0, only this address can request DTs
* @return {Promise<any>}
*/
public async estGasCreate(
dtAddress: string,
address: string,
maxTokens: string,
maxBalance: string,
allowedSwapper: string
): Promise<any> {
const gasLimitDefault = this.GASLIMIT_DEFAULT
let estGas
try {
estGas = await this.dispenserContract.methods
.create(
dtAddress,
this.web3.utils.toWei(maxTokens),
this.web3.utils.toWei(maxBalance),
address,
allowedSwapper
)
.estimateGas({ from: address }, (err, estGas) => (err ? gasLimitDefault : estGas))
} catch (e) {
estGas = gasLimitDefault
}
return estGas
}
/**
* Creates a new Dispenser
* @param {String} dtAddress Datatoken address
* @param {String} address Owner address
* @param {String} maxTokens max tokens to dispense
* @param {String} maxBalance max balance of requester
* @param {String} allowedSwapper only account that can ask tokens. set address(0) if not required
* @return {Promise<TransactionReceipt>} transactionId
*/
public async create(
dtAddress: string,
address: string,
maxTokens: string,
maxBalance: string,
allowedSwapper: string
): Promise<TransactionReceipt> {
const estGas = await this.estGasCreateDispenser(
dtAddress,
address,
maxTokens,
maxBalance,
allowedSwapper
)
// Call createFixedRate contract method
const trxReceipt = await this.dispenserContract.methods
.create(
dtAddress,
this.web3.utils.toWei(maxTokens),
this.web3.utils.toWei(maxBalance),
address,
allowedSwapper
)
.send({
from: address,
gas: estGas + 1,
gasPrice: await getFairGasPrice(this.web3)
})
return trxReceipt
}
/**
* Estimate gas for activate method
* @param {String} dtAddress
* @param {Number} maxTokens max amount of tokens to dispense
* @param {Number} maxBalance max balance of user. If user balance is >, then dispense will be rejected
* @param {String} address User address (must be owner of the dataToken)
* @return {Promise<any>}
*/
public async estGasActivate(
dtAddress: string,
maxTokens: string,
maxBalance: string,
address: string
): Promise<any> {
let estGas
const gasLimitDefault = this.GASLIMIT_DEFAULT
try {
estGas = await this.dispenserContract.methods
.activate(
dtAddress,
this.web3.utils.toWei(maxTokens),
this.web3.utils.toWei(maxBalance)
)
.estimateGas({ from: address }, (err, estGas) => (err ? gasLimitDefault : estGas))
} catch (e) {
estGas = gasLimitDefault
}
return estGas
}
/**
* Activates a new dispener.
* @param {String} dtAddress refers to datatoken address.
* @param {Number} maxTokens max amount of tokens to dispense
* @param {Number} maxBalance max balance of user. If user balance is >, then dispense will be rejected
* @param {String} address User address (must be owner of the dataToken)
* @return {Promise<TransactionReceipt>} TransactionReceipt
*/
public async activate(
dtAddress: string,
maxTokens: string,
maxBalance: string,
address: string
): Promise<TransactionReceipt> {
try {
const estGas = await this.estGasActivate(dtAddress, maxTokens, maxBalance, address)
const trxReceipt = await this.dispenserContract.methods
.activate(
dtAddress,
this.web3.utils.toWei(maxTokens),
this.web3.utils.toWei(maxBalance)
)
.send({
from: address,
gas: estGas + 1,
gasPrice: await getFairGasPrice(this.web3)
})
return trxReceipt
} catch (e) {
logger.error(`ERROR: Failed to activate dispenser: ${e.message}`)
}
return null
}
/**
* Estimate gas for deactivate method
* @param {String} dtAddress
* @param {String} address User address (must be owner of the dataToken)
* @return {Promise<any>}
*/
public async estGasDeactivate(dtAddress: string, address: string): Promise<any> {
let estGas
const gasLimitDefault = this.GASLIMIT_DEFAULT
try {
estGas = await this.dispenserContract.methods
.deactivate(dtAddress)
.estimateGas({ from: address }, (err, estGas) => (err ? gasLimitDefault : estGas))
} catch (e) {
estGas = gasLimitDefault
}
return estGas
}
/**
* Deactivate an existing dispenser.
* @param {String} dtAddress refers to datatoken address.
* @param {String} address User address (must be owner of the dataToken)
* @return {Promise<TransactionReceipt>} TransactionReceipt
*/
public async deactivate(
dtAddress: string,
address: string
): Promise<TransactionReceipt> {
try {
const estGas = await this.estGasDeactivate(dtAddress, address)
const trxReceipt = await this.dispenserContract.methods.deactivate(dtAddress).send({
from: address,
gas: estGas + 1,
gasPrice: await getFairGasPrice(this.web3)
})
return trxReceipt
} catch (e) {
logger.error(`ERROR: Failed to activate dispenser: ${e.message}`)
}
return null
}
/**
* Estimate gas for setAllowedSwapper method
* @param {String} dtAddress refers to datatoken address.
* @param {String} address User address (must be owner of the dataToken)
* @param {String} newAllowedSwapper refers to the new allowedSwapper
* @return {Promise<any>}
*/
public async estGasSetAllowedSwapper(
dtAddress: string,
address: string,
newAllowedSwapper: string
): Promise<any> {
let estGas
const gasLimitDefault = this.GASLIMIT_DEFAULT
try {
estGas = await this.dispenserContract.methods
.setAllowedSwapper(dtAddress, newAllowedSwapper)
.estimateGas({ from: address }, (err, estGas) => (err ? gasLimitDefault : estGas))
} catch (e) {
estGas = gasLimitDefault
}
return estGas
}
/**
* Sets a new allowedSwapper.
* @param {String} dtAddress refers to datatoken address.
* @param {String} address User address (must be owner of the dataToken)
* @param {String} newAllowedSwapper refers to the new allowedSwapper
* @return {Promise<TransactionReceipt>} TransactionReceipt
*/
public async setAllowedSwapper(
dtAddress: string,
address: string,
newAllowedSwapper: string
): Promise<TransactionReceipt> {
try {
const estGas = await this.estGasSetAllowedSwapper(
dtAddress,
address,
newAllowedSwapper
)
const trxReceipt = await this.dispenserContract.methods
.setAllowedSwapper(dtAddress, newAllowedSwapper)
.send({
from: address,
gas: estGas + 1,
gasPrice: await getFairGasPrice(this.web3)
})
return trxReceipt
} catch (e) {
logger.error(`ERROR: Failed to activate dispenser: ${e.message}`)
}
return null
}
/**
* Estimate gas for dispense method
* @param {String} dtAddress refers to datatoken address.
* @param {String} address User address (must be owner of the dataToken)
* @param {String} newAllowedSwapper refers to the new allowedSwapper
* @return {Promise<any>}
*/
public async estGasDispense(
dtAddress: string,
address: string,
amount: string = '1',
destination: string
): Promise<any> {
let estGas
const gasLimitDefault = this.GASLIMIT_DEFAULT
try {
estGas = await this.dispenserContract.methods
.dispense(dtAddress, this.web3.utils.toWei(amount), destination)
.estimateGas({ from: address }, (err, estGas) => (err ? gasLimitDefault : estGas))
} catch (e) {
estGas = gasLimitDefault
}
return estGas
}
/**
* Dispense datatokens to caller.
* The dispenser must be active, hold enough DT (or be able to mint more)
* and respect maxTokens/maxBalance requirements
* @param {String} dtAddress refers to datatoken address.
* @param {String} address User address
* @param {String} amount amount of datatokens required.
* @param {String} destination who will receive the tokens
* @return {Promise<TransactionReceipt>} TransactionReceipt
*/
public async dispense(
dtAddress: string,
address: string,
amount: string = '1',
destination: string
): Promise<TransactionReceipt> {
const estGas = await this.estGasDispense(dtAddress, address, amount, destination)
try {
const trxReceipt = await this.dispenserContract.methods
.dispense(dtAddress, this.web3.utils.toWei(amount), destination)
.send({
from: address,
gas: estGas + 1,
gasPrice: await getFairGasPrice(this.web3)
})
return trxReceipt
} catch (e) {
logger.error(`ERROR: Failed to dispense tokens: ${e.message}`)
}
return null
}
/**
* Estimate gas for ownerWithdraw method
* @param {String} dtAddress refers to datatoken address.
* @param {String} address User address (must be owner of the dataToken)
* @param {String} newAllowedSwapper refers to the new allowedSwapper
* @return {Promise<any>}
*/
public async estGasOwnerWithdraw(dtAddress: string, address: string): Promise<any> {
let estGas
const gasLimitDefault = this.GASLIMIT_DEFAULT
try {
estGas = await this.dispenserContract.methods
.ownerWithdraw(dtAddress)
.estimateGas({ from: address }, (err, estGas) => (err ? gasLimitDefault : estGas))
} catch (e) {
estGas = gasLimitDefault
}
return estGas
}
/**
* Withdraw all tokens from the dispenser
* @param {String} dtAddress refers to datatoken address.
* @param {String} address User address (must be owner of the dispenser)
* @return {Promise<TransactionReceipt>} TransactionReceipt
*/
public async ownerWithdraw(
dtAddress: string,
address: string
): Promise<TransactionReceipt> {
const estGas = await this.estGasOwnerWithdraw(dtAddress, address)
try {
const trxReceipt = await this.dispenserContract.methods
.ownerWithdraw(dtAddress)
.send({
from: address,
gas: estGas + 1,
gasPrice: await getFairGasPrice(this.web3)
})
return trxReceipt
} catch (e) {
logger.error(`ERROR: Failed to withdraw tokens: ${e.message}`)
}
return null
}
}

View File

@ -0,0 +1,129 @@
import Web3 from 'web3'
import { AbiItem, AbiInput } from 'web3-utils'
import { assert, expect } from 'chai'
import ERC721Factory from '@oceanprotocol/contracts/artifacts/contracts/ERC721Factory.sol/ERC721Factory.json'
import ERC721Template from '@oceanprotocol/contracts/artifacts/contracts/templates/ERC721Template.sol/ERC721Template.json'
import ERC20Template from '@oceanprotocol/contracts/artifacts/contracts/templates/ERC20Template.sol/ERC20Template.json'
import FactoryRouter from '@oceanprotocol/contracts/artifacts/contracts/pools/FactoryRouter.sol/FactoryRouter.json'
import DispenserTemplate from '@oceanprotocol/contracts/artifacts/contracts/pools/dispenser/Dispenser.sol/Dispenser.json'
import { LoggerInstance } from '.../../../src/utils'
import { Dispenser } from '.../../../src/pools/dispenser/'
import { NFTFactory } from '.../../../src/factories/'
import { Datatoken } from '.../../../src/datatokens/'
import { TestContractHandler } from '../../TestContractHandler'
const web3 = new Web3('http://127.0.0.1:8545')
describe('Dispenser flow', () => {
let factoryOwner: string
let nftOwner: string
let user1: string
let user2: string
let user3: string
let contracts: TestContractHandler
let DispenserAddress: string
let DispenserClass: Dispenser
let nftFactory: NFTFactory
let datatoken: Datatoken
let nftAddress: string
let dtAddress: string
it('should deploy contracts', async () => {
contracts = new TestContractHandler(
web3,
ERC721Template.abi as AbiItem,
ERC20Template.abi as AbiItem,
null,
ERC721Factory.abi as AbiItem,
null,
null,
null,
DispenserTemplate.abi as AbiItem,
ERC721Template.bytecode,
ERC20Template.bytecode,
null,
ERC721Factory.bytecode,
FactoryRouter.bytecode,
null,
null,
DispenserTemplate.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[])
})
it('should initialize Dispenser class', async () => {
DispenserClass = new Dispenser(
web3,
DispenserAddress,
DispenserTemplate.abi as AbiItem[]
)
assert(DispenserClass !== null)
})
it('#createNftwithErc - should create an NFT and a Datatoken ', async () => {
nftFactory = new NFTFactory(contracts.factory721Address, web3, LoggerInstance)
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
)
expect(txReceipt.events.NFTCreated.event === 'NFTCreated')
expect(txReceipt.events.TokenCreated.event === 'TokenCreated')
nftAddress = txReceipt.events.NFTCreated.returnValues.newTokenAddress
dtAddress = txReceipt.events.TokenCreated.returnValues.newTokenAddress
})
it('Make user2 minter', async () => {
datatoken = new Datatoken(web3, ERC20Template.abi as AbiItem)
await datatoken.addMinter(dtAddress, nftOwner, user2)
assert((await datatoken.getDTPermissions(dtAddress, user2)).minter === true)
})
it('user2 creates a dispenser', async () => {
const tx = await DispenserClass.activate(dtAddress, '1', '1', user2)
assert(tx, 'Cannot activate dispenser')
})
it('user2 gets the dispenser status', async () => {
const status = await DispenserClass.status(dtAddress)
assert(status.active === true, 'Dispenser not active')
assert(status.owner === user2, 'Dispenser owner is not alice')
assert(status.minterApproved === true, 'Dispenser is not a minter')
})
it('user2 deactivates the dispenser', async () => {
const tx = await DispenserClass.deactivate(dtAddress, user2)
assert(tx, 'Cannot deactivate dispenser')
const status = await DispenserClass.status(dtAddress)
assert(status.active === false, 'Dispenser is still active')
})
})