From c33868d841fd47378461b95c33fc2c14e9843f6b Mon Sep 17 00:00:00 2001 From: Alex Coseru Date: Fri, 28 Apr 2023 09:36:56 +0300 Subject: [PATCH] proper veDelegation (#663) --- schema.graphql | 17 +++- src/mappings/utils/veUtils.ts | 9 +- src/mappings/veDelegation.ts | 66 +++++++++++--- subgraph_ve.template.yaml | 2 +- test/integration/VeOcean.test.ts | 147 ++++++++++++++++++++++++++----- 5 files changed, 203 insertions(+), 38 deletions(-) diff --git a/schema.graphql b/schema.graphql index 1676347..1394f64 100644 --- a/schema.graphql +++ b/schema.graphql @@ -462,7 +462,7 @@ type VeAllocationUpdate @entity { } type VeDelegation @entity { - "id = tokenId" + "id = VeDelegation contract + tokenId" id: ID! delegator: VeOCEAN! receiver: VeOCEAN! @@ -470,11 +470,26 @@ type VeDelegation @entity { amount: BigInt! cancelTime: BigInt! expireTime: BigInt! + updates: [VeDelegationUpdate!] @derivedFrom(field: "veDelegation") + +} + +type VeDelegationUpdate @entity { + "id = {tx}-{eventIndex}" + id: ID! block: Int! timestamp: Int! tx: String! + sender: String! + amount: BigInt! + cancelTime: BigInt! + expireTime: BigInt! + "type: CREATE_BOOST = 0, EXTEND_BOOST = 1, BURN_BOOST = 2" + type:Int! + veDelegation:VeDelegation! } + type VeOCEAN @entity { "id = {user address}" id: ID! diff --git a/src/mappings/utils/veUtils.ts b/src/mappings/utils/veUtils.ts index fb1c2b9..3ab9287 100644 --- a/src/mappings/utils/veUtils.ts +++ b/src/mappings/utils/veUtils.ts @@ -121,7 +121,11 @@ export function writeveAllocationUpdate( return allocationUpdate } -export function getveDelegation(id: string): VeDelegation { +export function getveDelegation( + contract: Address, + eventid: string +): VeDelegation { + const id = contract.toHex() + '-' + eventid let veDelegation = VeDelegation.load(id) if (veDelegation === null) { @@ -132,9 +136,6 @@ export function getveDelegation(id: string): VeDelegation { veDelegation.amount = BigInt.zero() veDelegation.receiver = '' veDelegation.delegator = '' - veDelegation.block = 0 - veDelegation.timestamp = 0 - veDelegation.tx = '' veDelegation.save() } return veDelegation diff --git a/src/mappings/veDelegation.ts b/src/mappings/veDelegation.ts index fd082e6..95e70a6 100644 --- a/src/mappings/veDelegation.ts +++ b/src/mappings/veDelegation.ts @@ -1,4 +1,5 @@ import { BigInt } from '@graphprotocol/graph-ts' +import { VeDelegationUpdate } from '../@types/schema' import { BurnBoost, DelegateBoost, @@ -14,19 +15,32 @@ export function handleDelegation(event: DelegateBoost): void { const _amount = event.params._amount const _cancelTime = event.params._cancel_time const _expireTime = event.params._expire_time - - const veDelegation = getveDelegation(_tokenId.toHex()) - veDelegation.delegator = _delegator + // create veOcean if does not exists getveOCEAN(_receiver) + getveOCEAN(_delegator) + + const veDelegation = getveDelegation(event.address, _tokenId.toHex()) + veDelegation.delegator = _delegator veDelegation.receiver = _receiver veDelegation.tokenId = _tokenId veDelegation.amount = _amount veDelegation.cancelTime = _cancelTime veDelegation.expireTime = _expireTime - veDelegation.block = event.block.number.toI32() - veDelegation.timestamp = event.block.timestamp.toI32() - veDelegation.tx = event.transaction.hash.toHex() veDelegation.save() + + const veDelegationUpdate = new VeDelegationUpdate( + event.transaction.hash.toHex() + '-' + event.logIndex.toString() + ) + veDelegationUpdate.type = 0 + veDelegationUpdate.veDelegation = veDelegation.id + veDelegationUpdate.block = event.block.number.toI32() + veDelegationUpdate.timestamp = event.block.timestamp.toI32() + veDelegationUpdate.tx = event.transaction.hash.toHex() + veDelegationUpdate.amount = _amount + veDelegationUpdate.cancelTime = _cancelTime + veDelegationUpdate.expireTime = _expireTime + veDelegationUpdate.sender = event.transaction.from.toHex() + veDelegationUpdate.save() } export function handleExtendBoost(event: ExtendBoost): void { @@ -36,17 +50,33 @@ export function handleExtendBoost(event: ExtendBoost): void { const _amount = event.params._amount const _cancelTime = event.params._cancel_time const _expireTime = event.params._expire_time - - const veDelegation = getveDelegation(_tokenId.toHex()) + // create veOcean if does not exists + getveOCEAN(_receiver) + getveOCEAN(_delegator) + // it's possible to not have veDelegation object, because we missed handleDelegation + // that should not happend, but we create the object anyway + const veDelegation = getveDelegation(event.address, _tokenId.toHex()) veDelegation.delegator = _delegator veDelegation.receiver = _receiver veDelegation.tokenId = _tokenId veDelegation.amount = _amount veDelegation.cancelTime = _cancelTime veDelegation.expireTime = _expireTime - veDelegation.timestamp = event.block.timestamp.toI32() - veDelegation.tx = event.transaction.hash.toHex() veDelegation.save() + + const veDelegationUpdate = new VeDelegationUpdate( + event.transaction.hash.toHex() + '-' + event.logIndex.toString() + ) + veDelegationUpdate.veDelegation = veDelegation.id + veDelegationUpdate.type = 1 + veDelegationUpdate.block = event.block.number.toI32() + veDelegationUpdate.timestamp = event.block.timestamp.toI32() + veDelegationUpdate.tx = event.transaction.hash.toHex() + veDelegationUpdate.amount = _amount + veDelegationUpdate.cancelTime = _cancelTime + veDelegationUpdate.expireTime = _expireTime + veDelegationUpdate.sender = event.transaction.from.toHex() + veDelegationUpdate.save() } export function handleTransferBoost(event: TransferBoost): void { @@ -62,7 +92,21 @@ export function handleBurnBoost(event: BurnBoost): void { const _tokenId = event.params._token_id // delete - const veDelegation = getveDelegation(_tokenId.toHex()) + const veDelegation = getveDelegation(event.address, _tokenId.toHex()) veDelegation.amount = BigInt.zero() veDelegation.save() + + const veDelegationUpdate = new VeDelegationUpdate( + event.transaction.hash.toHex() + '-' + event.logIndex.toString() + ) + veDelegationUpdate.veDelegation = veDelegation.id + veDelegationUpdate.type = 2 + veDelegationUpdate.block = event.block.number.toI32() + veDelegationUpdate.timestamp = event.block.timestamp.toI32() + veDelegationUpdate.tx = event.transaction.hash.toHex() + veDelegationUpdate.amount = veDelegation.amount + veDelegationUpdate.cancelTime = veDelegation.cancelTime + veDelegationUpdate.expireTime = veDelegation.expireTime + veDelegationUpdate.sender = event.transaction.from.toHex() + veDelegationUpdate.save() } diff --git a/subgraph_ve.template.yaml b/subgraph_ve.template.yaml index 4daf8cf..e190786 100644 --- a/subgraph_ve.template.yaml +++ b/subgraph_ve.template.yaml @@ -69,7 +69,7 @@ - event: ExtendBoost(indexed address,indexed address,indexed uint256,uint256,uint256,uint256) handler: handleExtendBoost - event: BurnBoost(indexed address,indexed address,indexed uint256) - handler: handleTransferBoost + handler: handleBurnBoost - name: veFeeDistributor kind: ethereum/contract diff --git a/test/integration/VeOcean.test.ts b/test/integration/VeOcean.test.ts index 93044ca..73560fd 100644 --- a/test/integration/VeOcean.test.ts +++ b/test/integration/VeOcean.test.ts @@ -610,7 +610,6 @@ describe('veOcean tests', async () => { ) const timestamp = Math.floor(Date.now() / 1000) const unlockTime = timestamp + 30 * 86400 - console.log('unlock time', unlockTime) if (parseInt(currentBalance) > 0 || currentLock > 0) { // we already have some locked tokens, so our transaction should fail @@ -656,14 +655,15 @@ describe('veOcean tests', async () => { await veOcean.increaseUnlockTime(Alice, extLockTime) - const estGas = await calculateEstimatedGas( + const initalBoostExpiry = extLockTime - 100000 + let estGas = await calculateEstimatedGas( Alice, delegateContract.methods.create_boost, Alice, Bob, - 10000, + 5000, 0, - extLockTime, + initalBoostExpiry, 0 ) @@ -675,41 +675,146 @@ describe('veOcean tests', async () => { delegateContract.methods.create_boost, Alice, Bob, - 10000, + 5000, 0, - extLockTime, + initalBoostExpiry, 0 ) assert(tx3, 'Transaction failed') assert(tx3.events.DelegateBoost, 'No Delegate boost event') + const tokenId = tx3.events.DelegateBoost.returnValues._token_id + await evmIncreaseTime(60) + // extend boost + estGas = await calculateEstimatedGas( + Alice, + delegateContract.methods.extend_boost, + tokenId, + 10000, + extLockTime, + 0 + ) - sleep(4000) + const tx4 = await sendTx( + Alice, + estGas, + web3, + 1, + delegateContract.methods.extend_boost, + tokenId, + 10000, + extLockTime, + 0 + ) + + assert(tx4, 'Transaction failed') + assert(tx4.events.ExtendBoost, 'No ExtendBoost event') + await evmIncreaseTime(60) + // burn it + estGas = await calculateEstimatedGas( + Alice, + delegateContract.methods.cancel_boost, + tokenId + ) + + const tx5 = await sendTx( + Alice, + estGas, + web3, + 1, + delegateContract.methods.cancel_boost, + tokenId + ) + + assert(tx5, 'Transaction failed') + assert(tx5.events.BurnBoost, 'No BurnBoost event') + + await sleep(3000) const delegateQuery = { - query: `query { - veDelegations{ - id, + query: `query{ + veDelegations(where:{delegator:"${Alice.toLowerCase()}"}){ + id delegator { id - }, - receiver { + } + receiver{ id - }, - tokenId, - amount, - cancelTime, + } + amount + tokenId + cancelTime expireTime + updates(orderBy:timestamp orderDirection:asc){ + id + block + timestamp + tx + sender + amount + cancelTime + expireTime + type + } } - }` + }` } const delegateResponse = await fetch(subgraphUrl, { method: 'POST', body: JSON.stringify(delegateQuery) }) - const json = await delegateResponse.json() - console.log('json', json) - console.log('json?.data?.veDelegations', json?.data?.veDelegations) - assert(json?.data?.veDelegations, 'No veDelegations') + const resp = await delegateResponse.json() + const delegations = resp.data.veDelegations + + assert(delegations.length > 0, 'No veDelegations') + assert( + delegations[0].tokenId.toLowerCase() == + tx3.events.DelegateBoost.returnValues._token_id.toLowerCase(), + 'Invalid tokenID' + ) + assert(delegations[0].amount == '0', 'Invalid amount, should be 0') + assert(delegations[0].updates.length > 0, 'No updates') + // check updates. first is create boost + assert( + delegations[0].updates[0].type == 0, + 'Invalid type. should be createBoost' + ) + assert( + delegations[0].updates[0].cancelTime == + tx3.events.DelegateBoost.returnValues._cancel_time, + 'Invalid cancelTime' + ) + assert( + delegations[0].updates[0].expireTime == + tx3.events.DelegateBoost.returnValues._expire_time, + 'Invalid expireTime' + ) + assert( + delegations[0].updates[0].amount == + tx3.events.DelegateBoost.returnValues._amount, + 'Invalid amount' + ) + // check extend boos update + assert( + delegations[0].updates[1].type == 1, + 'Invalid type. should be extend Boost' + ) + assert( + delegations[0].updates[1].cancelTime == + tx4.events.ExtendBoost.returnValues._cancel_time, + 'Invalid cancelTime for extend boost' + ) + assert( + delegations[0].updates[1].expireTime == + tx4.events.ExtendBoost.returnValues._expire_time, + 'Invalid expireTime for extend boost' + ) + assert( + delegations[0].updates[1].amount == + tx4.events.ExtendBoost.returnValues._amount, + 'Invalid amount for extend boost' + ) + // check burn + assert(delegations[0].updates[2].type == 2, 'Invalid type. should be burn') }) })