diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c0f118d..1cdd013 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -52,7 +52,7 @@ jobs: run: | bash -x start_ocean.sh --with-thegraph --skip-subgraph-deploy --no-dashboard 2>&1 > start_ocean.log & env: - CONTRACTS_VERSION: v1.1.4 + CONTRACTS_VERSION: v1.1.7 - run: npm ci diff --git a/package-lock.json b/package-lock.json index 6ea28dc..c26f811 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "2.0.7", "license": "Apache-2.0", "dependencies": { - "@oceanprotocol/contracts": "^1.1.6", + "@oceanprotocol/contracts": "^1.1.7", "@oceanprotocol/lib": "^2.0.2", "cross-fetch": "^3.1.4" }, @@ -860,9 +860,9 @@ } }, "node_modules/@oceanprotocol/contracts": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@oceanprotocol/contracts/-/contracts-1.1.6.tgz", - "integrity": "sha512-BF4Dkoa44ZCxvp96o/i03bMl4c5pYHrY1K/xvM8ZUc1NJ+BNCX+qKvtXzVfqbZXf02GwoWBWwRzQXXdOmXhxbw==" + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@oceanprotocol/contracts/-/contracts-1.1.7.tgz", + "integrity": "sha512-Oe+oBRiu1dlco9PQ7eUYcTYi2Nua69S3TiSw62H46AIpwnFK8ORuO0Ny20No++KisBA9F+84b5lDn6kQy5Lt/Q==" }, "node_modules/@oceanprotocol/lib": { "version": "2.0.2", @@ -15278,9 +15278,9 @@ } }, "@oceanprotocol/contracts": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@oceanprotocol/contracts/-/contracts-1.1.6.tgz", - "integrity": "sha512-BF4Dkoa44ZCxvp96o/i03bMl4c5pYHrY1K/xvM8ZUc1NJ+BNCX+qKvtXzVfqbZXf02GwoWBWwRzQXXdOmXhxbw==" + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@oceanprotocol/contracts/-/contracts-1.1.7.tgz", + "integrity": "sha512-Oe+oBRiu1dlco9PQ7eUYcTYi2Nua69S3TiSw62H46AIpwnFK8ORuO0Ny20No++KisBA9F+84b5lDn6kQy5Lt/Q==" }, "@oceanprotocol/lib": { "version": "2.0.2", diff --git a/package.json b/package.json index ff50da1..826932f 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "test-users": "TS_NODE_PROJECT='test/integration/tsconfig.json' mocha --config=test/integration/.mocharc.json --node-env=test --exit 'test/integration/users.test.ts'", "test-ve": "TS_NODE_PROJECT='test/integration/tsconfig.json' mocha --config=test/integration/.mocharc.json --node-env=test --exit 'test/integration/VeOcean.test.ts'", "test-df": "TS_NODE_PROJECT='test/integration/tsconfig.json' mocha --config=test/integration/.mocharc.json --node-env=test --exit 'test/integration/DFRewards.test.ts'", + "test-zend": "TS_NODE_PROJECT='test/integration/tsconfig.json' mocha --config=test/integration/.mocharc.json --node-env=test --exit 'test/integration/ZEnding.test.ts'", "lint": "eslint --ignore-path .gitignore --ext .js --ext .ts --ext .tsx .", "lint:fix": "eslint --ignore-path .gitignore --ext .js,.ts,.tsx . --fix", "format": "prettier --ignore-path .gitignore './**/*.{css,yml,js,ts,tsx,json,yaml}' --write", @@ -67,7 +68,7 @@ "typescript": "^4.8.3" }, "dependencies": { - "@oceanprotocol/contracts": "^1.1.6", + "@oceanprotocol/contracts": "^1.1.7", "@oceanprotocol/lib": "^2.0.2", "cross-fetch": "^3.1.4" }, diff --git a/schema.graphql b/schema.graphql index 4175bf2..2a00250 100644 --- a/schema.graphql +++ b/schema.graphql @@ -398,6 +398,7 @@ type VeAllocateUser @entity{ firstContact: Int! lastContact: Int! tx: String! + veOcean: VeOCEAN! } type VeAllocateId @entity{ @@ -465,25 +466,75 @@ type VeDelegation @entity { type VeOCEAN @entity { "id = {user address}" id: ID! + "total amount of locked tokens" lockedAmount: BigDecimal! + "unlock timestamp" unlockTime: BigInt! delegation: [VeDelegation!] @derivedFrom(field: "delegator") delegates: [VeDelegation!] @derivedFrom(field: "receiver") + deposits: [VeDeposit!] @derivedFrom(field: "veOcean") + claims: [VeClaim!] @derivedFrom(field: "veOcean") + allocation: VeAllocateUser! @derivedFrom(field: "veOcean") block: Int! } type VeDeposit @entity { "id = {user address}-{timestamp}" id: ID! + "veOcean holder" provider:String! + "who initiated the tx" + sender: String! + "amount of tokens locked" value: BigDecimal! + "unlock timestamp" unlockTime: BigInt! + "deposit type: DEPOSIT_FOR = 0, CREATE_LOCK_TYPE = 1,INCREASE_LOCK_AMOUNT = 2,INCREASE_UNLOCK_TIME = 3, WITHDRAW = 4" type:BigInt! timestamp: BigInt! block: Int! tx: String! + veOcean: VeOCEAN! +} + + +type VeFeeDistributor @entity { + "id = contract address" + id: ID! + "token used by FeeDistributor" + token: Token! + claims: [VeClaim!] @derivedFrom(field: "VeFeeDistributor") + checkpoints: [VeFeeDistributorCheckPoint!] @derivedFrom(field: "VeFeeDistributor") +} + +type VeFeeDistributorCheckPoint @entity { + "id = {tx}-{eventno}" + id: ID! + "amount of tokens for rewards" + tokens: BigDecimal! + "who initiated the tx" + sender: String! + VeFeeDistributor: VeFeeDistributor! + timestamp: BigInt! + block: Int! + tx: String! } +type VeClaim @entity { + "id = {tx}-{eventno}" + id: ID! + "amount of tokens claimed" + amount: BigDecimal! + "claim epoch" + claim_epoch: BigInt + "max_epoch" + max_epoch: BigInt + timestamp: BigInt! + block: Int! + tx: String! + veOcean: VeOCEAN! + VeFeeDistributor: VeFeeDistributor! +} enum DFHistoryType { Allocated, diff --git a/scripts/generatenetworkssubgraphs.js b/scripts/generatenetworkssubgraphs.js index 72f56ce..f3c32a0 100644 --- a/scripts/generatenetworkssubgraphs.js +++ b/scripts/generatenetworkssubgraphs.js @@ -59,6 +59,11 @@ async function replaceContractAddresses() { "'" + addresses[network].veDelegation + "'" ) + subgraph = subgraph.replace( + /__VEFEEDISTRIBUTORNADDRESS__/g, + "'" + addresses[network].veFeeDistributor + "'" + ) + subgraph = subgraph.replace( /__DFREWARDSADDRESS__/g, "'" + addresses[network].DFRewards + "'" diff --git a/src/mappings/utils/veUtils.ts b/src/mappings/utils/veUtils.ts index 02f2733..7e84c13 100644 --- a/src/mappings/utils/veUtils.ts +++ b/src/mappings/utils/veUtils.ts @@ -1,4 +1,4 @@ -import { BigDecimal, ethereum, BigInt } from '@graphprotocol/graph-ts' +import { BigDecimal, ethereum, BigInt, Address } from '@graphprotocol/graph-ts' import { VeAllocateUser, VeAllocateId, @@ -6,9 +6,26 @@ import { VeAllocationUpdate, VeDelegation, VeOCEAN, - VeDeposit + VeDeposit, + VeFeeDistributor } from '../../@types/schema' +import { veFeeDistributor as VeFeeDistributorContract } from '../../@types/veFeeDistributor/veFeeDistributor' import { veAllocationUpdateType } from './constants' +import { getToken } from './tokenUtils' + +export function getveOCEAN(id: string): VeOCEAN { + let ve = VeOCEAN.load(id) + + if (ve === null) { + ve = new VeOCEAN(id) + ve.unlockTime = BigInt.zero() + ve.lockedAmount = BigDecimal.zero() + ve.block = 0 + ve.save() + } + + return ve +} export function getveAllocateUser( event: ethereum.Event, @@ -23,6 +40,8 @@ export function getveAllocateUser( allocateUser.tx = event.transaction.hash.toHex() allocateUser.block = event.block.number.toI32() allocateUser.lastContact = 0 + const veOcean = getveOCEAN(sender) + allocateUser.veOcean = veOcean.id allocateUser.save() } @@ -117,33 +136,21 @@ export function getveDelegation(id: string): VeDelegation { return veDelegation } -export function getveOCEAN(id: string): VeOCEAN { - let ve = VeOCEAN.load(id) - - if (ve === null) { - ve = new VeOCEAN(id) - ve.unlockTime = BigInt.zero() - ve.lockedAmount = BigDecimal.zero() - ve.block = 0 - ve.save() - } - - return ve -} - export function getDeposit(id: string): VeDeposit { let deposit = VeDeposit.load(id) if (deposit === null) { deposit = new VeDeposit(id) deposit.provider = '' + deposit.sender = '' deposit.value = BigDecimal.zero() deposit.unlockTime = BigInt.zero() deposit.type = BigInt.zero() deposit.timestamp = BigInt.zero() deposit.tx = '' deposit.block = 0 - deposit.save() + // do not save it + // deposit.save() } return deposit } @@ -194,3 +201,17 @@ export function handleOneAllocation( allocateId.save() veAllocation.save() } + +export function getVeFeeDistributor(id: Address): VeFeeDistributor { + let distributor = VeFeeDistributor.load(id.toHexString()) + + if (distributor === null) { + distributor = new VeFeeDistributor(id.toHexString()) + const contract = VeFeeDistributorContract.bind(id) + const tokenAddress = contract.try_token() + const token = getToken(tokenAddress.value, false) + distributor.token = token.id + distributor.save() + } + return distributor +} diff --git a/src/mappings/veFeeDistributor.ts b/src/mappings/veFeeDistributor.ts new file mode 100644 index 0000000..7b5b9ac --- /dev/null +++ b/src/mappings/veFeeDistributor.ts @@ -0,0 +1,50 @@ +import { Address } from '@graphprotocol/graph-ts' +import { + Claimed, + CheckpointToken +} from '../@types/veFeeDistributor/veFeeDistributor' +import { weiToDecimal } from './utils/generic' +import { getveOCEAN, getVeFeeDistributor } from './utils/veUtils' +import { VeClaim, VeFeeDistributorCheckPoint } from '../@types/schema' +import { getToken } from './utils/tokenUtils' + +export function handleClaimed(event: Claimed): void { + const distributor = getVeFeeDistributor(event.address) + const id = + event.transaction.hash.toHexString() + '-' + event.logIndex.toString() + const veOcean = getveOCEAN(event.params.recipient.toHexString()) + const token = getToken(Address.fromString(distributor.token), false) + const claim = new VeClaim(id) + claim.amount = weiToDecimal( + event.params.amount.toBigDecimal(), + token.decimals + ) + claim.claim_epoch = event.params.claim_epoch + claim.max_epoch = event.params.max_epoch + + claim.veOcean = veOcean.id + claim.VeFeeDistributor = distributor.id + + claim.block = event.block.number.toI32() + claim.tx = event.transaction.hash.toHex() + claim.timestamp = event.block.timestamp + claim.save() +} + +export function handleCheckpoint(event: CheckpointToken): void { + const distributor = getVeFeeDistributor(event.address) + const id = + event.transaction.hash.toHexString() + '-' + event.logIndex.toString() + const token = getToken(Address.fromString(distributor.token), false) + const checkpoint = new VeFeeDistributorCheckPoint(id) + checkpoint.tokens = weiToDecimal( + event.params.tokens.toBigDecimal(), + token.decimals + ) + checkpoint.sender = event.transaction.from.toHexString() + checkpoint.block = event.block.number.toI32() + checkpoint.tx = event.transaction.hash.toHex() + checkpoint.timestamp = event.params.time + checkpoint.VeFeeDistributor = distributor.id + checkpoint.save() +} diff --git a/src/mappings/veOCEAN.ts b/src/mappings/veOCEAN.ts index 2befd1d..90fc607 100644 --- a/src/mappings/veOCEAN.ts +++ b/src/mappings/veOCEAN.ts @@ -1,3 +1,4 @@ +import { BigDecimal, BigInt } from '@graphprotocol/graph-ts' import { Deposit, Supply, Withdraw } from '../@types/veOCEAN/veOCEAN' import { weiToDecimal } from './utils/generic' import { getDeposit, getveOCEAN } from './utils/veUtils' @@ -9,6 +10,7 @@ export function handleDeposit(event: Deposit): void { const type = event.params.type const ts = event.params.ts + const veOCEAN = getveOCEAN(provider.toHex()) // Create new Deposit entity const deposit = getDeposit(provider.toHex() + '-' + locktime.toString()) deposit.provider = provider.toHex() @@ -18,10 +20,11 @@ export function handleDeposit(event: Deposit): void { deposit.timestamp = ts deposit.block = event.block.number.toI32() deposit.tx = event.transaction.hash.toHex() + deposit.sender = event.transaction.from.toHex() + deposit.veOcean = veOCEAN.id deposit.save() // -------------------------------------------- - const veOCEAN = getveOCEAN(provider.toHex()) const lockedAmount = weiToDecimal(value.toBigDecimal(), 18) veOCEAN.unlockTime = locktime veOCEAN.lockedAmount = veOCEAN.lockedAmount.plus(lockedAmount) @@ -29,4 +32,28 @@ export function handleDeposit(event: Deposit): void { veOCEAN.save() } export function handleSupply(event: Supply): void {} -export function handleWithdraw(event: Withdraw): void {} +export function handleWithdraw(event: Withdraw): void { + const provider = event.params.provider + const value = event.params.value + const ts = event.params.ts + + const veOCEAN = getveOCEAN(provider.toHex()) + // Create new Deposit entity + const deposit = getDeposit(provider.toHex() + '-' + ts.toString()) + deposit.provider = provider.toHex() + deposit.value = weiToDecimal(value.toBigDecimal(), 18).neg() + deposit.unlockTime = BigInt.zero() + deposit.type = BigInt.fromI32(4) + deposit.timestamp = ts + deposit.block = event.block.number.toI32() + deposit.tx = event.transaction.hash.toHex() + deposit.sender = event.transaction.from.toHex() + deposit.veOcean = veOCEAN.id + deposit.save() + // -------------------------------------------- + + veOCEAN.lockedAmount = BigDecimal.zero() + veOCEAN.unlockTime = BigInt.zero() + veOCEAN.block = event.block.number.toI32() + veOCEAN.save() +} diff --git a/subgraph_ve.template.yaml b/subgraph_ve.template.yaml index 77c3ee6..a6886d0 100644 --- a/subgraph_ve.template.yaml +++ b/subgraph_ve.template.yaml @@ -42,9 +42,9 @@ - event: Deposit(indexed address,uint256,indexed uint256,int128,uint256) handler: handleDeposit - event: Withdraw(indexed address,uint256,uint256) - handler: handleSupply - - event: Supply(uint256,uint256) handler: handleWithdraw + - event: Supply(uint256,uint256) + handler: handleSupply - name: veDelegation kind: ethereum/contract @@ -67,6 +67,29 @@ - event: DelegateBoost(indexed address,indexed address,indexed uint256,uint256,uint256,uint256) handler: handleDelegation +- name: veFeeDistributor + kind: ethereum/contract + network: __NETWORK__ + source: + abi: veFeeDistributor + address: __VEFEEDISTRIBUTORNADDRESS__ + startBlock: __STARTBLOCK__ + mapping: + kind: ethereum/events + apiVersion: 0.0.7 + language: wasm/assemblyscript + file: ./src/mappings/veFeeDistributor.ts + entities: + - veFeeDistributor + abis: + - name: veFeeDistributor + file: ./node_modules/@oceanprotocol/contracts/artifacts/contracts/ve/veFeeDistributor.vy/veFeeDistributor.json + eventHandlers: + - event: Claimed(indexed address,uint256,uint256,uint256) + handler: handleClaimed + - event: CheckpointToken(uint256,uint256) + handler: handleCheckpoint + - name: DFRewards kind: ethereum/contract network: __NETWORK__ diff --git a/test/integration/VeOcean.test.ts b/test/integration/VeOcean.test.ts index 75ce699..728d51f 100644 --- a/test/integration/VeOcean.test.ts +++ b/test/integration/VeOcean.test.ts @@ -6,9 +6,11 @@ import { sendTx, approve, ConfigHelper, - sleep + sleep, + VeFeeDistributor } from '@oceanprotocol/lib' import { AbiItem } from 'web3-utils' +import { HttpProvider } from 'web3-providers-http' import { assert } from 'chai' import Web3 from 'web3' import { homedir } from 'os' @@ -29,10 +31,64 @@ const web3 = new Web3('http://127.0.0.1:8545') const subgraphUrl = 'http://127.0.0.1:9000/subgraphs/name/oceanprotocol/ocean-subgraph' +function evmMine() { + const provider = web3.currentProvider as HttpProvider + return new Promise((resolve, reject) => { + provider.send( + { + jsonrpc: '2.0', + method: 'evm_mine', + id: new Date().getTime() + }, + (error, result) => { + if (error) { + return reject(error) + } + return resolve(result) + } + ) + }) +} +function evmIncreaseTime(seconds) { + const provider = web3.currentProvider as HttpProvider + return new Promise((resolve, reject) => { + provider.send( + { + method: 'evm_increaseTime', + params: [seconds], + jsonrpc: '2.0', + id: new Date().getTime() + }, + (error, result) => { + if (error) { + return reject(error) + } + return evmMine().then(() => resolve(result)) + } + ) + }) +} + +const minAbi = [ + { + constant: false, + inputs: [ + { name: 'to', type: 'address' }, + { name: 'value', type: 'uint256' } + ], + name: 'mint', + outputs: [{ name: '', type: 'bool' }], + payable: false, + stateMutability: 'nonpayable', + type: 'function' + } +] as AbiItem[] + describe('veOcean tests', async () => { let nftFactory let veOcean: VeOcean let veAllocate: VeAllocate + let veFeeDistributor: VeFeeDistributor let ownerAccount: string let Alice: string let Bob: string @@ -47,26 +103,13 @@ describe('veOcean tests', async () => { ownerAccount = accounts[0] Alice = accounts[1] Bob = accounts[2] - const minAbi = [ - { - constant: false, - inputs: [ - { name: 'to', type: 'address' }, - { name: 'value', type: 'uint256' } - ], - name: 'mint', - outputs: [{ name: '', type: 'bool' }], - payable: false, - stateMutability: 'nonpayable', - type: 'function' - } - ] as AbiItem[] + const tokenContract = new web3.eth.Contract(minAbi, addresses.Ocean) const estGas = await calculateEstimatedGas( ownerAccount, tokenContract.methods.mint, Alice, - web3.utils.toWei('1000') + web3.utils.toWei('100000') ) await sendTx( ownerAccount, @@ -75,7 +118,7 @@ describe('veOcean tests', async () => { 1, tokenContract.methods.mint, Alice, - web3.utils.toWei('1000') + web3.utils.toWei('100000') ) await sendTx( ownerAccount, @@ -84,11 +127,12 @@ describe('veOcean tests', async () => { 1, tokenContract.methods.mint, Bob, - web3.utils.toWei('1000') + web3.utils.toWei('100000') ) veOcean = new VeOcean(addresses.veOCEAN, web3) veAllocate = new VeAllocate(addresses.veAllocate, web3) nftFactory = new NftFactory(addresses.ERC721Factory, web3) + veFeeDistributor = new VeFeeDistributor(addresses.veFeeDistributor, web3) }) it('Alice should lock 100 Ocean', async () => { @@ -359,4 +403,147 @@ describe('veOcean tests', async () => { info[0].allocatedTotal ) }) + + it('Alice should advance chain one day', async () => { + await evmIncreaseTime(60 * 60 * 24) + }) + + it('Alice should add ocean rewards to feeDistrib', async () => { + // mint 10 ocean and send them to feeDistrib + let tokenContract = new web3.eth.Contract(minAbi, addresses.Ocean) + let estGas = await calculateEstimatedGas( + ownerAccount, + tokenContract.methods.mint, + addresses.veFeeDistributor, + web3.utils.toWei('10') + ) + await sendTx( + ownerAccount, + estGas, + web3, + 1, + tokenContract.methods.mint, + addresses.veFeeDistributor, + web3.utils.toWei('10') + ) + const minAbiFee = [ + { + name: 'checkpoint_token', + outputs: [], + inputs: [], + stateMutability: 'nonpayable', + type: 'function', + gas: 820723 + }, + { + name: 'checkpoint_total_supply', + outputs: [], + inputs: [], + stateMutability: 'nonpayable', + type: 'function', + gas: 10592405 + } + ] as AbiItem[] + tokenContract = new web3.eth.Contract(minAbiFee, addresses.veFeeDistributor) + estGas = await calculateEstimatedGas( + ownerAccount, + tokenContract.methods.checkpoint_token + ) + + await sendTx( + ownerAccount, + estGas, + web3, + 1, + tokenContract.methods.checkpoint_token + ) + estGas = await calculateEstimatedGas( + ownerAccount, + tokenContract.methods.checkpoint_total_supply + ) + await sendTx( + ownerAccount, + estGas, + web3, + 1, + tokenContract.methods.checkpoint_total_supply + ) + }) + + it('Alice should advance chain 7 day', async () => { + await evmIncreaseTime(60 * 60 * 24 * 7) + }) + + it('Alice should add again ocean rewards to feeDistrib', async () => { + // mint 20 ocean and send them to feeDistrib + let tokenContract = new web3.eth.Contract(minAbi, addresses.Ocean) + let estGas = await calculateEstimatedGas( + ownerAccount, + tokenContract.methods.mint, + addresses.veFeeDistributor, + web3.utils.toWei('20') + ) + await sendTx( + ownerAccount, + estGas, + web3, + 1, + tokenContract.methods.mint, + addresses.veFeeDistributor, + web3.utils.toWei('20') + ) + const minAbiFee = [ + { + name: 'checkpoint_token', + outputs: [], + inputs: [], + stateMutability: 'nonpayable', + type: 'function', + gas: 820723 + }, + { + name: 'checkpoint_total_supply', + outputs: [], + inputs: [], + stateMutability: 'nonpayable', + type: 'function', + gas: 10592405 + } + ] as AbiItem[] + tokenContract = new web3.eth.Contract(minAbiFee, addresses.veFeeDistributor) + estGas = await calculateEstimatedGas( + ownerAccount, + tokenContract.methods.checkpoint_token + ) + await sendTx( + ownerAccount, + estGas, + web3, + 1, + tokenContract.methods.checkpoint_token + ) + estGas = await calculateEstimatedGas( + ownerAccount, + tokenContract.methods.checkpoint_total_supply + ) + await sendTx( + ownerAccount, + estGas, + web3, + 1, + tokenContract.methods.checkpoint_total_supply + ) + }) + + it('Alice should advance chain 7 day', async () => { + await evmIncreaseTime(60 * 60 * 24 * 7) + }) + + it('Alice should claim rewards', async () => { + await veFeeDistributor.claim(Alice) + }) + it('Alice should withdraw locked tokens', async () => { + await evmIncreaseTime(60 * 60 * 24 * 7) + await veOcean.withdraw(Alice) + }) }) diff --git a/test/integration/ZEnding.test.ts b/test/integration/ZEnding.test.ts index d732412..e3fffa5 100644 --- a/test/integration/ZEnding.test.ts +++ b/test/integration/ZEnding.test.ts @@ -19,8 +19,8 @@ describe('Ending tests', () => { let result: any let lastblock it('Get Graph status', async () => { - await sleep(1000) // let graph ingest our last transactions lastblock = await web3.eth.getBlockNumber() + await sleep(3000) // let graph ingest our last transactions const query = { query: `query { _meta{hasIndexingErrors, @@ -36,10 +36,10 @@ describe('Ending tests', () => { result = await response.json() }) - it('Make sure that graph has no sync errors', async () => { + it('Make sure that graph has no index errors', async () => { assert(result.data._meta.hasIndexingErrors == false) }) it('Make sure that graph has synced to last block', async () => { - assert(result.data._meta.block.number === lastblock) + assert(result.data._meta.block.number >= lastblock) }) })