Skip to content

Commit

Permalink
[superfluid-vesting] Added strategy for Superfluid Vesting Schedules (#…
Browse files Browse the repository at this point in the history
…1671)

* added strategy for vesting scheduler

* added subgraph urls, more accurate accounting
  • Loading branch information
d10r authored Jan 7, 2025
1 parent 67416ec commit 6b17a42
Show file tree
Hide file tree
Showing 5 changed files with 183 additions and 1 deletion.
4 changes: 3 additions & 1 deletion src/strategies/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,7 @@ import * as naymsStaking from './nayms-staking';
import * as morphoDelegation from './morpho-delegation';
import * as lizcoinStrategy2024 from './lizcoin-strategy-2024';
import * as realt from './realt';
import * as superfluidVesting from './superfluid-vesting';

const strategies = {
'delegatexyz-erc721-balance-of': delegatexyzErc721BalanceOf,
Expand Down Expand Up @@ -948,7 +949,8 @@ const strategies = {
'nayms-staking': naymsStaking,
'morpho-delegation': morphoDelegation,
'lizcoin-strategy-2024': lizcoinStrategy2024,
realt
realt,
'superfluid-vesting': superfluidVesting
};

Object.keys(strategies).forEach(function (strategyName) {
Expand Down
28 changes: 28 additions & 0 deletions src/strategies/superfluid-vesting/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Superfluid Vesting

Superfluid Vesting is done in the typical Superfluid way, not requiring prior capital lockup.
The Vesting Scheduler contract allows for the creation of Vesting Schedules which are then automatically executed.

Vesting Schedules are created by the _vesting sender_.
This sender, by creating a schedule, expresses the intent to provide the promised funds, as specified in the schedule.
In order for this intent to become executable, the sender also needs to
- grant the necessary ACL permissions to the VestingScheduler contract
- have enough funds available when needed

Note: In order to create a vesting schedule with hard guarantees of successful execution, a vesting sender needs to be a contract which is pre-funded and has no means to withdraw funds.

## Voting

In order to map vesting schedules to voting power, we need to monitor vesting schedules.
We need to restrict the schedules taken into consideration to those originating from a known and trusted vesting sender. Otherwise anybody could trivially cheat and gain more voting power by creating schedules for themselves.

With a trusted vesting sender defined, we can enumerate the vesting schedules created by it (where it is the _sender_) via subgraph query.
The total vesting amount of a vesting schedule is to be calculated as `cliffAmount + (endDate - startDate) * flowRate)`.
We need to subtract from this amount the already vested amount in order to not double-count it. This is assuming that another strategy accounts for the voting power of the already vested portion.

## Dev

Run test with
```
yarn test --strategy=superfluid-vesting
```
21 changes: 21 additions & 0 deletions src/strategies/superfluid-vesting/examples.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[
{
"name": "Example query",
"strategy": {
"name": "superfluid-vesting",
"params": {
"superTokenAddress": "0xe58267cd7299c29a1b77F4E66Cd12Dd24a2Cd2FD",
"vestingSenderAddress": "0xd7086bf0754383c065d81b62fc2e874373660caa"
}
},
"network": "8453",
"addresses": [
"0xf8a025B42B07db05638FE596cce339707ec3cC71",
"0x389E3d1c46595aF7335F8C6D3e403ce2E8a9cf8A",
"0x264Ff25e609363cf738e238CBc7B680300509BED",
"0x5782BD439d3019F61bFac53f6358C30c3566737C",
"0x4ee5D45eB79aEa04C02961a2e543bbAf5cec81B3"
],
"snapshot": 24392394
}
]
96 changes: 96 additions & 0 deletions src/strategies/superfluid-vesting/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { subgraphRequest } from '../../utils';
import { getAddress } from '@ethersproject/address';

export const author = 'd10r';
export const version = '0.1.0';

const SUBGRAPH_URL_MAP = {
'10':

Check failure on line 8 in src/strategies/superfluid-vesting/index.ts

View workflow job for this annotation

GitHub Actions / lint / Lint

Delete `⏎···`
'https://subgrapher.snapshot.org/subgraph/arbitrum/6YMD95vYriDkmTJewC2vYubqVZrc6vdk3Sp3mR3YCQUw',
'8453':
'https://subgrapher.snapshot.org/subgraph/arbitrum/4Zp6n8jcsJMBNa3GY9RZwoK4SLjoagwXGq6GhUQNMgSM',
'11155420':
'https://subgrapher.snapshot.org/subgraph/arbitrum/5UctBWuaQgr2HVSG6XtAKt5shyjg9snGvD6F424FcjMN'
};

export async function strategy(
space,
network,
provider,
addresses,
options,
snapshot
): Promise<Record<string, number>> {

Check failure on line 23 in src/strategies/superfluid-vesting/index.ts

View workflow job for this annotation

GitHub Actions / lint / Lint

Delete `⏎`

const subgraphUrl = options.subgraphUrl || SUBGRAPH_URL_MAP[network];
if (!subgraphUrl) {
throw new Error('Subgraph URL not specified');
}

const query = {
vestingSchedules: {
__args: {
where: {
superToken: options.superTokenAddress,
sender: options.vestingSenderAddress,
endExecutedAt: null,
failedAt: null,
deletedAt: null
},
orderBy: 'cliffAndFlowDate',
orderDirection: 'asc'
},
cliffAmount: true,
cliffAndFlowDate: true,
endDate: true,
flowRate: true,
cliffAndFlowExecutedAt: true,
receiver: true,
remainderAmount: true
}
};

if (snapshot !== 'latest') {
// @ts-ignore
query.vestingSchedules.__args.block = { number: snapshot };
}

// Get block timestamp - needed for calculating the remaining amount
const blockTag = typeof snapshot === 'number' ? snapshot : 'latest';
const block = await provider.getBlock(blockTag);
const timestamp = block.timestamp;

const subgraphResult = await subgraphRequest(subgraphUrl, query);

const processedMap = subgraphResult.vestingSchedules.map((schedule) => {
const endDate = BigInt(schedule.endDate);
const cliffAndFlowDate = BigInt(schedule.cliffAndFlowDate);
const flowRate = BigInt(schedule.flowRate);
const cliffAmount = BigInt(schedule.cliffAmount);
const remainderAmount = BigInt(schedule.remainderAmount);

// the initial vesting amount
const fullAmount = cliffAmount + flowRate * (endDate - cliffAndFlowDate) + remainderAmount;

Check failure on line 73 in src/strategies/superfluid-vesting/index.ts

View workflow job for this annotation

GitHub Actions / lint / Lint

Insert `⏎·····`

// the remaining amount which hasn't yet vested
let remainingAmount = fullAmount;
if (schedule.cliffAndFlowExecutedAt !== null) {
remainingAmount -= cliffAmount + flowRate * (BigInt(timestamp) - cliffAndFlowDate);

Check failure on line 78 in src/strategies/superfluid-vesting/index.ts

View workflow job for this annotation

GitHub Actions / lint / Lint

Insert `⏎·······`
}

return {
schedule: schedule,
fullAmount: fullAmount,
remainingAmount: remainingAmount
}

Check failure on line 85 in src/strategies/superfluid-vesting/index.ts

View workflow job for this annotation

GitHub Actions / lint / Lint

Insert `;`
});

// create a map of the remaining amounts
return Object.fromEntries(
processedMap.map(item => [

Check failure on line 90 in src/strategies/superfluid-vesting/index.ts

View workflow job for this annotation

GitHub Actions / lint / Lint

Replace `item` with `(item)`
getAddress(item.schedule.receiver),
// in theory remainingAmount could become negative, thus we cap at 0
Math.max(Number(item.remainingAmount) / 1e18, 0)
])
);
}
35 changes: 35 additions & 0 deletions src/strategies/superfluid-vesting/schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"$ref": "#/definitions/Strategy",
"definitions": {
"Strategy": {
"title": "Strategy",
"type": "object",
"properties": {
"superTokenAddress": {
"type": "string",
"title": "Super Token contract address",
"examples": ["e.g. 0x6C210F071c7246C452CAC7F8BaA6dA53907BbaE1"],
"pattern": "^0x[a-fA-F0-9]{40}$",
"minLength": 42,
"maxLength": 42
},
"vestingSenderAddress": {
"type": "string",
"title": "Vesting sender address",
"examples": ["e.g. 0x6C210F071c7246C452CAC7F8BaA6dA53907BbaE1"],
"pattern": "^0x[a-fA-F0-9]{40}$",
"minLength": 42,
"maxLength": 42
},
"subgraphUrl": {
"type": "string",
"title": "Subgraph URL (optional - if not already known to the strategy)",
"examples": ["e.g. https://subgrapher.snapshot.org/subgraph/arbitrum/4Zp6n8jcsJMBNa3GY9RZwoK4SLjoagwXGq6GhUQNMgSM"]

Check failure on line 28 in src/strategies/superfluid-vesting/schema.json

View workflow job for this annotation

GitHub Actions / lint / Lint

Replace `"e.g.·https://subgrapher.snapshot.org/subgraph/arbitrum/4Zp6n8jcsJMBNa3GY9RZwoK4SLjoagwXGq6GhUQNMgSM"` with `⏎············"e.g.·https://subgrapher.snapshot.org/subgraph/arbitrum/4Zp6n8jcsJMBNa3GY9RZwoK4SLjoagwXGq6GhUQNMgSM"⏎··········`
}
},
"required": ["superTokenAddress", "vestingSenderAddress"],
"additionalProperties": false
}
}
}

0 comments on commit 6b17a42

Please sign in to comment.