Feature/more ve (#543)

* link deposits to veOcean

* add FeeDistributor, claims & checkpoints

* fix lint

* link claims to VeOcean

* fix handleCheckpoint

* add comments & fix bug

* link veAllocationUser to veOcean (#537)

* advance chain

* fix veOcean withdraw

* bump contracts to 1.1.7

* fix test
pull/545/head
Alex Coseru 2 months ago committed by GitHub
parent 91122175dc
commit fe68442fe2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      .github/workflows/tests.yml
  2. 14
      package-lock.json
  3. 3
      package.json
  4. 51
      schema.graphql
  5. 5
      scripts/generatenetworkssubgraphs.js
  6. 55
      src/mappings/utils/veUtils.ts
  7. 50
      src/mappings/veFeeDistributor.ts
  8. 31
      src/mappings/veOCEAN.ts
  9. 27
      subgraph_ve.template.yaml
  10. 223
      test/integration/VeOcean.test.ts
  11. 6
      test/integration/ZEnding.test.ts

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

14
package-lock.json generated

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

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

@ -398,6 +398,7 @@ type VeAllocateUser @entity{
firstContact: Int!
lastContact: Int!
tx: String!
veOcean: VeOCEAN!
}
type VeAllocateId @entity{
@ -465,26 +466,76 @@ 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,
Claimed

@ -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 + "'"

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

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

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

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

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

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

Loading…
Cancel
Save