mirror of
https://github.com/oceanprotocol/ocean.js.git
synced 2024-11-26 20:39:05 +01:00
Feature/dispenser (#790)
* add dispenser support * bump contracts to 0.6.2
This commit is contained in:
parent
f7a91c7b3c
commit
92b4be0dce
6
package-lock.json
generated
6
package-lock.json
generated
@ -2198,9 +2198,9 @@
|
||||
}
|
||||
},
|
||||
"@oceanprotocol/contracts": {
|
||||
"version": "0.5.16",
|
||||
"resolved": "https://registry.npmjs.org/@oceanprotocol/contracts/-/contracts-0.5.16.tgz",
|
||||
"integrity": "sha512-p7aFIUT8RVoMzdPP7ML8G08BnQ09syywKjOT16hqJm0GmofunEuVffUXbryG4EkQ+qRbf/zeoxSmesi79kQXlA=="
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@oceanprotocol/contracts/-/contracts-0.6.2.tgz",
|
||||
"integrity": "sha512-J6amHsmVbdc2rAwbUYOaY7inLV13GxPIiqbsLF78nmdIvhhGDhT2LYMyfQtxkMwQzYDP6EzD4albCgOXlWM15g=="
|
||||
},
|
||||
"@octokit/auth-token": {
|
||||
"version": "2.4.5",
|
||||
|
@ -46,7 +46,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@ethereum-navigator/navigator": "^0.5.2",
|
||||
"@oceanprotocol/contracts": "0.5.16",
|
||||
"@oceanprotocol/contracts": "^0.6.2",
|
||||
"@types/crypto-js": "^4.0.1",
|
||||
"cross-fetch": "^3.1.2",
|
||||
"crypto-js": "^4.0.0",
|
||||
|
@ -521,7 +521,7 @@ export class DataTokens {
|
||||
dataTokenAddress: string,
|
||||
newMinterAddress: string,
|
||||
address: string
|
||||
): Promise<string> {
|
||||
): Promise<TransactionReceipt> {
|
||||
const datatoken = new this.web3.eth.Contract(this.datatokensABI, dataTokenAddress, {
|
||||
from: address
|
||||
})
|
||||
@ -542,6 +542,7 @@ export class DataTokens {
|
||||
})
|
||||
return trxReceipt
|
||||
} catch (e) {
|
||||
this.logger.error('ERROR: Propose minter failed')
|
||||
return null
|
||||
}
|
||||
}
|
||||
@ -552,7 +553,10 @@ export class DataTokens {
|
||||
* @param {String} address - only proposad minter can call this
|
||||
* @return {Promise<string>} transactionId
|
||||
*/
|
||||
public async approveMinter(dataTokenAddress: string, address: string): Promise<string> {
|
||||
public async approveMinter(
|
||||
dataTokenAddress: string,
|
||||
address: string
|
||||
): Promise<TransactionReceipt> {
|
||||
const datatoken = new this.web3.eth.Contract(this.datatokensABI, dataTokenAddress, {
|
||||
from: address
|
||||
})
|
||||
|
350
src/dispenser/Dispenser.ts
Normal file
350
src/dispenser/Dispenser.ts
Normal file
@ -0,0 +1,350 @@
|
||||
import defaultDispenserABI from '@oceanprotocol/contracts/artifacts/Dispenser.json'
|
||||
import { TransactionReceipt } from 'web3-core'
|
||||
import { Contract } from 'web3-eth-contract'
|
||||
import { AbiItem } from 'web3-utils/types'
|
||||
import Web3 from 'web3'
|
||||
import { SubscribablePromise, Logger, getFairGasPrice } from '../utils'
|
||||
import { DataTokens } from '../datatokens/Datatokens'
|
||||
import Decimal from 'decimal.js'
|
||||
|
||||
export interface DispenserToken {
|
||||
active: boolean
|
||||
owner: string
|
||||
minterApproved: boolean
|
||||
isTrueMinter: boolean
|
||||
maxTokens: string
|
||||
maxBalance: string
|
||||
balance: string
|
||||
}
|
||||
|
||||
export enum DispenserMakeMinterProgressStep {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
MakeDispenserMinter,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
AcceptingNewMinter
|
||||
}
|
||||
|
||||
export enum DispenserCancelMinterProgressStep {
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
MakeOwnerMinter,
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
AcceptingNewMinter
|
||||
}
|
||||
|
||||
export class OceanDispenser {
|
||||
public GASLIMIT_DEFAULT = 1000000
|
||||
/** Ocean related functions */
|
||||
public dispenserAddress: string
|
||||
public dispenserABI: AbiItem | AbiItem[]
|
||||
public web3: Web3
|
||||
public contract: Contract = null
|
||||
private logger: Logger
|
||||
public datatokens: DataTokens
|
||||
public startBlock: number
|
||||
|
||||
/**
|
||||
* Instantiate Dispenser
|
||||
* @param {any} web3
|
||||
* @param {String} dispenserAddress
|
||||
* @param {any} dispenserABI
|
||||
*/
|
||||
constructor(
|
||||
web3: Web3,
|
||||
logger: Logger,
|
||||
dispenserAddress: string = null,
|
||||
dispenserABI: AbiItem | AbiItem[] = null,
|
||||
datatokens: DataTokens,
|
||||
startBlock?: number
|
||||
) {
|
||||
this.web3 = web3
|
||||
this.dispenserAddress = dispenserAddress
|
||||
if (startBlock) this.startBlock = startBlock
|
||||
else this.startBlock = 0
|
||||
this.dispenserABI = dispenserABI || (defaultDispenserABI.abi as AbiItem[])
|
||||
this.datatokens = datatokens
|
||||
if (web3)
|
||||
this.contract = new this.web3.eth.Contract(this.dispenserABI, this.dispenserAddress)
|
||||
this.logger = logger
|
||||
}
|
||||
|
||||
/**
|
||||
* Get dispenser status for a datatoken
|
||||
* @param {String} dataTokenAddress
|
||||
* @return {Promise<FixedPricedExchange>} Exchange details
|
||||
*/
|
||||
public async status(dataTokenAddress: string): Promise<DispenserToken> {
|
||||
try {
|
||||
const result: DispenserToken = await this.contract.methods
|
||||
.status(dataTokenAddress)
|
||||
.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) {
|
||||
this.logger.warn(`No dispenser available for data token: ${dataTokenAddress}`)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Activates a new dispener.
|
||||
* @param {String} dataToken
|
||||
* @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(
|
||||
dataToken: string,
|
||||
maxTokens: string,
|
||||
maxBalance: string,
|
||||
address: string
|
||||
): Promise<TransactionReceipt> {
|
||||
let estGas
|
||||
const gasLimitDefault = this.GASLIMIT_DEFAULT
|
||||
try {
|
||||
estGas = await this.contract.methods
|
||||
.activate(
|
||||
dataToken,
|
||||
this.web3.utils.toWei(maxTokens),
|
||||
this.web3.utils.toWei(maxBalance)
|
||||
)
|
||||
.estimateGas({ from: address }, (err, estGas) => (err ? gasLimitDefault : estGas))
|
||||
} catch (e) {
|
||||
estGas = gasLimitDefault
|
||||
}
|
||||
let trxReceipt = null
|
||||
try {
|
||||
trxReceipt = await this.contract.methods
|
||||
.activate(
|
||||
dataToken,
|
||||
this.web3.utils.toWei(maxTokens),
|
||||
this.web3.utils.toWei(maxBalance)
|
||||
)
|
||||
.send({
|
||||
from: address,
|
||||
gas: estGas + 1,
|
||||
gasPrice: await getFairGasPrice(this.web3)
|
||||
})
|
||||
} catch (e) {
|
||||
this.logger.error(`ERROR: Failed to activate dispenser: ${e.message}`)
|
||||
}
|
||||
return trxReceipt
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivates a dispener.
|
||||
* @param {String} dataToken
|
||||
* @param {String} address User address (must be owner of the dispenser)
|
||||
* @return {Promise<TransactionReceipt>} TransactionReceipt
|
||||
*/
|
||||
public async deactivate(
|
||||
dataToken: string,
|
||||
address: string
|
||||
): Promise<TransactionReceipt> {
|
||||
let estGas
|
||||
const gasLimitDefault = this.GASLIMIT_DEFAULT
|
||||
try {
|
||||
estGas = await this.contract.methods
|
||||
.deactivate(dataToken)
|
||||
.estimateGas({ from: address }, (err, estGas) => (err ? gasLimitDefault : estGas))
|
||||
} catch (e) {
|
||||
estGas = gasLimitDefault
|
||||
}
|
||||
let trxReceipt = null
|
||||
try {
|
||||
trxReceipt = await this.contract.methods.deactivate(dataToken).send({
|
||||
from: address,
|
||||
gas: estGas + 1,
|
||||
gasPrice: await getFairGasPrice(this.web3)
|
||||
})
|
||||
} catch (e) {
|
||||
this.logger.error(`ERROR: Failed to deactivate dispenser: ${e.message}`)
|
||||
}
|
||||
return trxReceipt
|
||||
}
|
||||
|
||||
/**
|
||||
* Make the dispenser minter of the datatoken
|
||||
* @param {String} dataToken
|
||||
* @param {String} address User address (must be owner of the datatoken)
|
||||
* @return {Promise<TransactionReceipt>} TransactionReceipt
|
||||
*/
|
||||
public makeMinter(
|
||||
dataToken: string,
|
||||
address: string
|
||||
): SubscribablePromise<DispenserMakeMinterProgressStep, TransactionReceipt> {
|
||||
return new SubscribablePromise(async (observer) => {
|
||||
observer.next(DispenserMakeMinterProgressStep.MakeDispenserMinter)
|
||||
let estGas
|
||||
const gasLimitDefault = this.GASLIMIT_DEFAULT
|
||||
const minterTx = await this.datatokens.proposeMinter(
|
||||
dataToken,
|
||||
this.dispenserAddress,
|
||||
address
|
||||
)
|
||||
if (!minterTx) {
|
||||
return null
|
||||
}
|
||||
observer.next(DispenserMakeMinterProgressStep.AcceptingNewMinter)
|
||||
try {
|
||||
estGas = await this.contract.methods
|
||||
.acceptMinter(dataToken)
|
||||
.estimateGas({ from: address }, (err, estGas) =>
|
||||
err ? gasLimitDefault : estGas
|
||||
)
|
||||
} catch (e) {
|
||||
estGas = gasLimitDefault
|
||||
}
|
||||
let trxReceipt = null
|
||||
try {
|
||||
trxReceipt = await this.contract.methods.acceptMinter(dataToken).send({
|
||||
from: address,
|
||||
gas: estGas + 1,
|
||||
gasPrice: await getFairGasPrice(this.web3)
|
||||
})
|
||||
} catch (e) {
|
||||
this.logger.error(`ERROR: Failed to accept minter role: ${e.message}`)
|
||||
}
|
||||
return trxReceipt
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel minter role of dispenser and make the owner minter of the datatoken
|
||||
* @param {String} dataToken
|
||||
* @param {String} address User address (must be owner of the dispenser)
|
||||
* @return {Promise<TransactionReceipt>} TransactionReceipt
|
||||
*/
|
||||
public cancelMinter(
|
||||
dataToken: string,
|
||||
address: string
|
||||
): SubscribablePromise<DispenserCancelMinterProgressStep, TransactionReceipt> {
|
||||
return new SubscribablePromise(async (observer) => {
|
||||
observer.next(DispenserCancelMinterProgressStep.MakeOwnerMinter)
|
||||
let estGas
|
||||
const gasLimitDefault = this.GASLIMIT_DEFAULT
|
||||
try {
|
||||
estGas = await this.contract.methods
|
||||
.removeMinter(dataToken)
|
||||
.estimateGas({ from: address }, (err, estGas) =>
|
||||
err ? gasLimitDefault : estGas
|
||||
)
|
||||
} catch (e) {
|
||||
estGas = gasLimitDefault
|
||||
}
|
||||
let trxReceipt = null
|
||||
try {
|
||||
trxReceipt = await this.contract.methods.removeMinter(dataToken).send({
|
||||
from: address,
|
||||
gas: estGas + 1,
|
||||
gasPrice: await getFairGasPrice(this.web3)
|
||||
})
|
||||
} catch (e) {
|
||||
this.logger.error(`ERROR: Failed to remove minter role: ${e.message}`)
|
||||
}
|
||||
if (!trxReceipt) {
|
||||
return null
|
||||
}
|
||||
observer.next(DispenserCancelMinterProgressStep.AcceptingNewMinter)
|
||||
const minterTx = await this.datatokens.approveMinter(dataToken, address)
|
||||
return minterTx
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Request tokens from dispenser
|
||||
* @param {String} dataToken
|
||||
* @param {String} amount
|
||||
* @param {String} address User address
|
||||
* @return {Promise<TransactionReceipt>} TransactionReceipt
|
||||
*/
|
||||
public async dispense(
|
||||
dataToken: string,
|
||||
address: string,
|
||||
amount: string = '1'
|
||||
): Promise<TransactionReceipt> {
|
||||
let estGas
|
||||
const gasLimitDefault = this.GASLIMIT_DEFAULT
|
||||
try {
|
||||
estGas = await this.contract.methods
|
||||
.dispense(dataToken, this.web3.utils.toWei(amount))
|
||||
.estimateGas({ from: address }, (err, estGas) => (err ? gasLimitDefault : estGas))
|
||||
} catch (e) {
|
||||
estGas = gasLimitDefault
|
||||
}
|
||||
let trxReceipt = null
|
||||
try {
|
||||
trxReceipt = await this.contract.methods
|
||||
.dispense(dataToken, this.web3.utils.toWei(amount))
|
||||
.send({
|
||||
from: address,
|
||||
gas: estGas + 1,
|
||||
gasPrice: await getFairGasPrice(this.web3)
|
||||
})
|
||||
} catch (e) {
|
||||
this.logger.error(`ERROR: Failed to dispense tokens: ${e.message}`)
|
||||
}
|
||||
return trxReceipt
|
||||
}
|
||||
|
||||
/**
|
||||
* Withdraw all tokens from the dispenser (if any)
|
||||
* @param {String} dataToken
|
||||
* @param {String} address User address (must be owner of the dispenser)
|
||||
* @return {Promise<TransactionReceipt>} TransactionReceipt
|
||||
*/
|
||||
public async ownerWithdraw(
|
||||
dataToken: string,
|
||||
address: string
|
||||
): Promise<TransactionReceipt> {
|
||||
let estGas
|
||||
const gasLimitDefault = this.GASLIMIT_DEFAULT
|
||||
try {
|
||||
estGas = await this.contract.methods
|
||||
.ownerWithdraw(dataToken)
|
||||
.estimateGas({ from: address }, (err, estGas) => (err ? gasLimitDefault : estGas))
|
||||
} catch (e) {
|
||||
estGas = gasLimitDefault
|
||||
}
|
||||
let trxReceipt = null
|
||||
try {
|
||||
trxReceipt = await this.contract.methods.ownerWithdraw(dataToken).send({
|
||||
from: address,
|
||||
gas: estGas + 1,
|
||||
gasPrice: await getFairGasPrice(this.web3)
|
||||
})
|
||||
} catch (e) {
|
||||
this.logger.error(`ERROR: Failed to withdraw tokens: ${e.message}`)
|
||||
}
|
||||
return trxReceipt
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if tokens can be dispensed
|
||||
* @param {String} dataToken
|
||||
* @param {String} address User address that will receive datatokens
|
||||
* @return {Promise<Boolean>}
|
||||
*/
|
||||
public async isDispensable(
|
||||
dataToken: string,
|
||||
address: string,
|
||||
amount: string = '1'
|
||||
): Promise<Boolean> {
|
||||
const status = await this.status(dataToken)
|
||||
if (!status) return false
|
||||
// check active
|
||||
if (status.active === false) return false
|
||||
// check maxBalance
|
||||
const userBalance = new Decimal(await this.datatokens.balance(dataToken, address))
|
||||
if (userBalance.greaterThanOrEqualTo(status.maxBalance)) return false
|
||||
// check maxAmount
|
||||
if (new Decimal(String(amount)).greaterThan(status.maxTokens)) return false
|
||||
// check dispenser balance
|
||||
const contractBalance = new Decimal(status.balance)
|
||||
if (contractBalance.greaterThanOrEqualTo(amount) || status.isTrueMinter === true)
|
||||
return true
|
||||
return false
|
||||
}
|
||||
}
|
@ -85,6 +85,19 @@ export class Config {
|
||||
* @type {any}
|
||||
*/
|
||||
public fixedRateExchangeAddressABI?: AbiItem | AbiItem[]
|
||||
|
||||
/**
|
||||
* DispenserAddress
|
||||
* @type {string}
|
||||
*/
|
||||
public dispenserAddress?: string
|
||||
|
||||
/**
|
||||
* DispenserABI
|
||||
* @type {any}
|
||||
*/
|
||||
public dispenserABI?: AbiItem | AbiItem[]
|
||||
|
||||
/**
|
||||
* DDOContractAddress
|
||||
* @type {string}
|
||||
|
@ -15,6 +15,7 @@ import {
|
||||
import { Compute } from './Compute'
|
||||
import { OceanPool } from '../balancer/OceanPool'
|
||||
import { OceanFixedRateExchange } from '../exchange/FixedRateExchange'
|
||||
import { OceanDispenser } from '../dispenser/Dispenser'
|
||||
|
||||
/**
|
||||
* Main interface for Ocean Protocol.
|
||||
@ -72,6 +73,15 @@ export class Ocean extends Instantiable {
|
||||
instance.datatokens,
|
||||
instanceConfig.config.startBlock
|
||||
)
|
||||
instance.OceanDispenser = new OceanDispenser(
|
||||
instanceConfig.config.web3Provider,
|
||||
instanceConfig.logger,
|
||||
instanceConfig.config.dispenserAddress,
|
||||
instanceConfig.config.dispenserABI,
|
||||
instance.datatokens,
|
||||
instanceConfig.config.startBlock
|
||||
)
|
||||
|
||||
instance.onChainMetadata = new OnChainMetadata(
|
||||
instanceConfig.config.web3Provider,
|
||||
instanceConfig.logger,
|
||||
@ -155,6 +165,12 @@ export class Ocean extends Instantiable {
|
||||
*/
|
||||
public fixedRateExchange: OceanFixedRateExchange
|
||||
|
||||
/**
|
||||
* Ocean Dispenser submodule
|
||||
* @type {OceanDispenser}
|
||||
*/
|
||||
public OceanDispenser: OceanDispenser
|
||||
|
||||
/**
|
||||
* Ocean tokens submodule
|
||||
* @type {OceanTokens}
|
||||
|
@ -35,6 +35,7 @@ const configs: ConfigHelperConfig[] = [
|
||||
factoryAddress: '0x1234',
|
||||
poolFactoryAddress: null,
|
||||
fixedRateExchangeAddress: null,
|
||||
dispenserAddress: null,
|
||||
metadataContractAddress: null,
|
||||
startBlock: 0
|
||||
},
|
||||
@ -52,6 +53,7 @@ const configs: ConfigHelperConfig[] = [
|
||||
factoryAddress: null,
|
||||
poolFactoryAddress: null,
|
||||
fixedRateExchangeAddress: null,
|
||||
dispenserAddress: null,
|
||||
metadataContractAddress: null,
|
||||
startBlock: 0
|
||||
},
|
||||
@ -68,6 +70,7 @@ const configs: ConfigHelperConfig[] = [
|
||||
factoryAddress: null,
|
||||
poolFactoryAddress: null,
|
||||
fixedRateExchangeAddress: null,
|
||||
dispenserAddress: null,
|
||||
metadataContractAddress: null,
|
||||
startBlock: 9227563
|
||||
},
|
||||
@ -84,6 +87,7 @@ const configs: ConfigHelperConfig[] = [
|
||||
factoryAddress: null,
|
||||
poolFactoryAddress: null,
|
||||
fixedRateExchangeAddress: null,
|
||||
dispenserAddress: null,
|
||||
metadataContractAddress: null,
|
||||
startBlock: 7294090
|
||||
},
|
||||
@ -100,6 +104,7 @@ const configs: ConfigHelperConfig[] = [
|
||||
factoryAddress: null,
|
||||
poolFactoryAddress: null,
|
||||
fixedRateExchangeAddress: null,
|
||||
dispenserAddress: null,
|
||||
metadataContractAddress: null,
|
||||
startBlock: 11105459
|
||||
},
|
||||
@ -116,6 +121,7 @@ const configs: ConfigHelperConfig[] = [
|
||||
factoryAddress: null,
|
||||
poolFactoryAddress: null,
|
||||
fixedRateExchangeAddress: null,
|
||||
dispenserAddress: null,
|
||||
metadataContractAddress: null,
|
||||
startBlock: 11005222
|
||||
},
|
||||
@ -132,6 +138,7 @@ const configs: ConfigHelperConfig[] = [
|
||||
factoryAddress: null,
|
||||
poolFactoryAddress: null,
|
||||
fixedRateExchangeAddress: null,
|
||||
dispenserAddress: null,
|
||||
metadataContractAddress: null,
|
||||
startBlock: 90707
|
||||
}
|
||||
@ -147,6 +154,7 @@ export class ConfigHelper {
|
||||
DTFactory,
|
||||
BFactory,
|
||||
FixedRateExchange,
|
||||
Dispenser,
|
||||
Metadata,
|
||||
Ocean
|
||||
} = DefaultContractsAddresses[network]
|
||||
@ -154,6 +162,7 @@ export class ConfigHelper {
|
||||
factoryAddress: DTFactory,
|
||||
poolFactoryAddress: BFactory,
|
||||
fixedRateExchangeAddress: FixedRateExchange,
|
||||
dispenserAddress: Dispenser,
|
||||
metadataContractAddress: Metadata,
|
||||
oceanTokenAddress: Ocean,
|
||||
...(process.env.AQUARIUS_URI && { metadataCacheUri: process.env.AQUARIUS_URI })
|
||||
@ -169,11 +178,19 @@ export class ConfigHelper {
|
||||
'utf8'
|
||||
)
|
||||
)
|
||||
const { DTFactory, BFactory, FixedRateExchange, Metadata, Ocean } = data[network]
|
||||
const {
|
||||
DTFactory,
|
||||
BFactory,
|
||||
FixedRateExchange,
|
||||
Dispenser,
|
||||
Metadata,
|
||||
Ocean
|
||||
} = data[network]
|
||||
configAddresses = {
|
||||
factoryAddress: DTFactory,
|
||||
poolFactoryAddress: BFactory,
|
||||
fixedRateExchangeAddress: FixedRateExchange,
|
||||
dispenserAddress: Dispenser,
|
||||
metadataContractAddress: Metadata,
|
||||
oceanTokenAddress: Ocean,
|
||||
...(process.env.AQUARIUS_URI && { metadataCacheUri: process.env.AQUARIUS_URI })
|
||||
|
50
test/DispenserContractHandler.ts
Normal file
50
test/DispenserContractHandler.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import Web3 from 'web3'
|
||||
import { Contract } from 'web3-eth-contract'
|
||||
import { AbiItem } from 'web3-utils/types'
|
||||
|
||||
export class DispenserContractHandler {
|
||||
public contract: Contract
|
||||
public accounts: string[]
|
||||
public contractBytecode: string
|
||||
public contractAddress: string
|
||||
public web3: Web3
|
||||
|
||||
constructor(contractABI: AbiItem[], contractBytecode: string, web3: Web3) {
|
||||
this.web3 = web3
|
||||
this.contract = new this.web3.eth.Contract(contractABI)
|
||||
this.contractBytecode = contractBytecode
|
||||
}
|
||||
|
||||
public async getAccounts(): Promise<string[]> {
|
||||
this.accounts = await this.web3.eth.getAccounts()
|
||||
return this.accounts
|
||||
}
|
||||
|
||||
public async deployContracts() {
|
||||
await this.getAccounts()
|
||||
// get est gascost
|
||||
const estGas = await this.contract
|
||||
.deploy({
|
||||
data: this.contractBytecode,
|
||||
arguments: []
|
||||
})
|
||||
.estimateGas(function (err, estGas) {
|
||||
if (err) console.log('DeployContracts: ' + err)
|
||||
return estGas
|
||||
})
|
||||
// deploy the contract and get it's address
|
||||
this.contractAddress = await this.contract
|
||||
.deploy({
|
||||
data: this.contractBytecode,
|
||||
arguments: []
|
||||
})
|
||||
.send({
|
||||
from: this.accounts[0],
|
||||
gas: estGas + 1,
|
||||
gasPrice: '3000000000'
|
||||
})
|
||||
.then(function (contract) {
|
||||
return contract.options.address
|
||||
})
|
||||
}
|
||||
}
|
239
test/unit/dispenser/Dispenser.test.ts
Normal file
239
test/unit/dispenser/Dispenser.test.ts
Normal file
@ -0,0 +1,239 @@
|
||||
import { assert } from 'chai'
|
||||
import { AbiItem } from 'web3-utils/types'
|
||||
import { TestContractHandler } from '../../TestContractHandler'
|
||||
import { DispenserContractHandler } from '../../DispenserContractHandler'
|
||||
import { DataTokens } from '../../../src/datatokens/Datatokens'
|
||||
import { LoggerInstance } from '../../../src/utils'
|
||||
import Web3 from 'web3'
|
||||
import factory from '@oceanprotocol/contracts/artifacts/DTFactory.json'
|
||||
import datatokensTemplate from '@oceanprotocol/contracts/artifacts/DataTokenTemplate.json'
|
||||
import { OceanDispenser } from '../../../src/dispenser/Dispenser'
|
||||
|
||||
import DispenserContract = require('@oceanprotocol/contracts/artifacts/Dispenser.json')
|
||||
const web3 = new Web3('http://127.0.0.1:8545')
|
||||
|
||||
describe('Dispenser flow', () => {
|
||||
let DispenserAddress: string
|
||||
let DispenserClass
|
||||
let bob: string
|
||||
let alice: string
|
||||
let charlie: string
|
||||
let datatoken: DataTokens
|
||||
let tokenAddress: string
|
||||
let tokenAddress2: string
|
||||
let tokenAddress3: string
|
||||
|
||||
let owner: string
|
||||
let contracts: TestContractHandler
|
||||
|
||||
const consoleDebug = false
|
||||
const tokenAmount = '1000'
|
||||
const blob = 'http://localhost:8030/api/v1/services/consume'
|
||||
|
||||
before(async () => {
|
||||
// deploy SFactory
|
||||
const Contracts = new DispenserContractHandler(
|
||||
DispenserContract.abi as AbiItem[],
|
||||
DispenserContract.bytecode,
|
||||
web3
|
||||
)
|
||||
await Contracts.getAccounts()
|
||||
owner = Contracts.accounts[0]
|
||||
|
||||
await Contracts.deployContracts()
|
||||
DispenserAddress = Contracts.contractAddress
|
||||
assert(DispenserAddress !== null)
|
||||
|
||||
// deploy DT Factory
|
||||
contracts = new TestContractHandler(
|
||||
factory.abi as AbiItem[],
|
||||
datatokensTemplate.abi as AbiItem[],
|
||||
datatokensTemplate.bytecode,
|
||||
factory.bytecode,
|
||||
web3
|
||||
)
|
||||
await contracts.getAccounts()
|
||||
owner = contracts.accounts[0]
|
||||
alice = contracts.accounts[1]
|
||||
bob = contracts.accounts[2]
|
||||
charlie = contracts.accounts[3]
|
||||
await contracts.deployContracts(owner)
|
||||
|
||||
// initialize DataTokens
|
||||
datatoken = new DataTokens(
|
||||
contracts.factoryAddress,
|
||||
factory.abi as AbiItem[],
|
||||
datatokensTemplate.abi as AbiItem[],
|
||||
web3,
|
||||
LoggerInstance
|
||||
)
|
||||
assert(datatoken !== null)
|
||||
})
|
||||
|
||||
it('should create some datatoken2', async () => {
|
||||
tokenAddress = await datatoken.create(
|
||||
blob,
|
||||
alice,
|
||||
'1000000000000000',
|
||||
'AliceDT',
|
||||
'DTA'
|
||||
)
|
||||
assert(tokenAddress !== null)
|
||||
if (consoleDebug) console.log("Alice's address:" + alice)
|
||||
if (consoleDebug) console.log('data Token address:' + tokenAddress)
|
||||
tokenAddress2 = await datatoken.create(
|
||||
blob,
|
||||
alice,
|
||||
'1000000000000000',
|
||||
'AliceDT2',
|
||||
'DTA2'
|
||||
)
|
||||
assert(tokenAddress2 !== null)
|
||||
tokenAddress3 = await datatoken.create(
|
||||
blob,
|
||||
alice,
|
||||
'1000000000000000',
|
||||
'AliceDT3',
|
||||
'DTA3'
|
||||
)
|
||||
assert(tokenAddress3 !== null)
|
||||
})
|
||||
|
||||
it('should initialize Dispenser class', async () => {
|
||||
DispenserClass = new OceanDispenser(
|
||||
web3,
|
||||
LoggerInstance,
|
||||
DispenserAddress,
|
||||
DispenserContract.abi as AbiItem[],
|
||||
datatoken
|
||||
)
|
||||
assert(DispenserClass !== null)
|
||||
})
|
||||
|
||||
it('Alice mints 1000 tokens', async () => {
|
||||
const txid = await datatoken.mint(tokenAddress, alice, tokenAmount)
|
||||
if (consoleDebug) console.log(txid)
|
||||
assert(txid !== null)
|
||||
})
|
||||
|
||||
it('Alice creates a dispenser', async () => {
|
||||
const tx = await DispenserClass.activate(tokenAddress, '1', '1', alice)
|
||||
assert(tx, 'Cannot activate dispenser')
|
||||
})
|
||||
|
||||
it('Alice should make the dispenser a minter', async () => {
|
||||
const tx = await DispenserClass.makeMinter(tokenAddress, alice)
|
||||
assert(tx, 'Cannot make dispenser a minter')
|
||||
})
|
||||
|
||||
it('Alice gets the dispenser status', async () => {
|
||||
const status = await DispenserClass.status(tokenAddress)
|
||||
assert(status.active === true, 'Dispenser not active')
|
||||
assert(status.owner === alice, 'Dispenser owner is not alice')
|
||||
assert(status.minterApproved === true, 'Dispenser is not a minter')
|
||||
})
|
||||
|
||||
it('Alice should fail to get the dispenser status for an unknown token', async () => {
|
||||
const status = await DispenserClass.status(tokenAddress3)
|
||||
assert(
|
||||
status.owner === '0x0000000000000000000000000000000000000000',
|
||||
'Owner of inexistent dispenser should be 0'
|
||||
)
|
||||
})
|
||||
|
||||
it('Alice should fail to get the dispenser status for zero address', async () => {
|
||||
const status = await DispenserClass.status(0x0000000000000000000000000000000000000000)
|
||||
assert(status === null)
|
||||
})
|
||||
|
||||
it('Bob requests more datatokens then allowed', async () => {
|
||||
const check = await DispenserClass.isDispensable(tokenAddress, bob, '10')
|
||||
assert(check === false, 'isDispensable should return false')
|
||||
const tx = await DispenserClass.dispense(tokenAddress, bob, '10')
|
||||
assert(tx === null, 'Request should fail')
|
||||
})
|
||||
|
||||
it('Bob requests datatokens', async () => {
|
||||
const check = await DispenserClass.isDispensable(tokenAddress, bob, '1')
|
||||
assert(check === true, 'isDispensable should return true')
|
||||
const tx = await DispenserClass.dispense(tokenAddress, bob, '1')
|
||||
assert(tx, 'Bob failed to get 1DT')
|
||||
})
|
||||
|
||||
it('Bob requests more datatokens but he exceeds maxBalance', async () => {
|
||||
const check = await DispenserClass.isDispensable(tokenAddress, bob, '10')
|
||||
assert(check === false, 'isDispensable should return false')
|
||||
const tx = await DispenserClass.dispense(tokenAddress, bob, '10')
|
||||
assert(tx === null, 'Request should fail')
|
||||
})
|
||||
|
||||
it('Alice deactivates the dispenser', async () => {
|
||||
const tx = await DispenserClass.deactivate(tokenAddress, alice)
|
||||
assert(tx, 'Cannot deactivate dispenser')
|
||||
const status = await DispenserClass.status(tokenAddress)
|
||||
assert(status.active === false, 'Dispenser is still active')
|
||||
})
|
||||
|
||||
it('Charlie should fail to get datatokens', async () => {
|
||||
const check = await DispenserClass.isDispensable(tokenAddress, charlie, '1')
|
||||
assert(check === false, 'isDispensable should return false')
|
||||
const tx = await DispenserClass.dispense(tokenAddress, charlie, '1')
|
||||
assert(tx === null, 'Charlie should fail to get 1DT')
|
||||
})
|
||||
|
||||
it('Alice calls removeMinter role and checks if she is the new minter', async () => {
|
||||
const tx = await DispenserClass.cancelMinter(tokenAddress, alice)
|
||||
assert(tx, 'Cannot cancel minter role')
|
||||
const status = await DispenserClass.status(tokenAddress)
|
||||
assert(status.minterApproved === false, 'Dispenser is still a minter')
|
||||
assert(status.owner === alice, 'Dispenser is not owned by Alice')
|
||||
const isMinter = await datatoken.isMinter(tokenAddress, alice)
|
||||
assert(isMinter === true, 'ALice is not the minter')
|
||||
})
|
||||
|
||||
it('Bob should fail to activate a dispenser for a token for he is not a minter', async () => {
|
||||
const tx = await DispenserClass.activate(tokenAddress, '1', '1', bob)
|
||||
assert(tx === null, 'Bob should fail to activate dispenser')
|
||||
})
|
||||
|
||||
it('Alice creates a dispenser without minter role', async () => {
|
||||
const tx = await DispenserClass.activate(tokenAddress2, '1', '1', alice)
|
||||
assert(tx, 'Cannot activate dispenser')
|
||||
})
|
||||
it('Bob requests datatokens but there are none', async () => {
|
||||
const check = await DispenserClass.isDispensable(tokenAddress2, bob, '1')
|
||||
assert(check === false, 'isDispensable should return false')
|
||||
const tx = await DispenserClass.dispense(tokenAddress2, bob, '1')
|
||||
assert(tx === null, 'Request should fail')
|
||||
})
|
||||
it('Alice mints tokens and transfer them to the dispenser.', async () => {
|
||||
const mintTx = await datatoken.mint(
|
||||
tokenAddress2,
|
||||
alice,
|
||||
'10',
|
||||
DispenserClass.dispenserAddress
|
||||
)
|
||||
assert(mintTx, 'Alice cannot mint tokens')
|
||||
const status = await DispenserClass.status(tokenAddress2)
|
||||
assert(status.balance > 0, 'Balances do not match')
|
||||
})
|
||||
|
||||
it('Bob requests datatokens', async () => {
|
||||
const check = await DispenserClass.isDispensable(tokenAddress2, bob, '1')
|
||||
assert(check === true, 'isDispensable should return true')
|
||||
const tx = await DispenserClass.dispense(tokenAddress2, bob, '1')
|
||||
assert(tx, 'Bob failed to get 1DT')
|
||||
})
|
||||
|
||||
it('Bob tries to withdraw all datatokens', async () => {
|
||||
const tx = await DispenserClass.ownerWithdraw(tokenAddress2, bob)
|
||||
assert(tx === null, 'Request should fail')
|
||||
})
|
||||
|
||||
it('Alice withdraws all datatokens', async () => {
|
||||
const tx = await DispenserClass.ownerWithdraw(tokenAddress2, alice)
|
||||
assert(tx, 'Alice failed to withdraw all her tokens')
|
||||
const status = await DispenserClass.status(tokenAddress2)
|
||||
assert(status.balance === '0', 'Balance > 0')
|
||||
})
|
||||
})
|
Loading…
x
Reference in New Issue
Block a user