1
0
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:
Alex Coseru 2021-05-10 18:42:19 +03:00 committed by GitHub
parent f7a91c7b3c
commit 92b4be0dce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 696 additions and 7 deletions

6
package-lock.json generated
View File

@ -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",

View File

@ -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",

View File

@ -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
View 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
}
}

View File

@ -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}

View File

@ -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}

View File

@ -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 })

View 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
})
}
}

View 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')
})
})