Skip to content
This repository was archived by the owner on Aug 22, 2025. It is now read-only.

Commit 065bded

Browse files
[sd-vote-boost-twavp-v4-ynd] Add ynd strategy (#1777)
* reverse delegation * add sd-gauge-less-vote-boost-crosschain * remove sdReverseDelegation * add bot address * add bot address * spectra strategy * add removeTwavp * use balanceOf instead of balances * add ynd strategy * Update src/strategies/sd-vote-boost-twavp-v4-ynd/examples.json * fix lint --------- Co-authored-by: Chaitanya <yourchaitu@gmail.com>
1 parent 9a3fd2b commit 065bded

4 files changed

Lines changed: 284 additions & 0 deletions

File tree

src/strategies/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,7 @@ import * as erc4626AssetsOf from './erc4626-assets-of';
388388
import * as sdVoteBoostTWAVPV2 from './sd-vote-boost-twavp-v2';
389389
import * as sdVoteBoostTWAVPV3 from './sd-vote-boost-twavp-v3';
390390
import * as sdVoteBoostTWAVPV4 from './sd-vote-boost-twavp-v4';
391+
import * as sdVoteBoostTWAVPV4Ynd from './sd-vote-boost-twavp-v4-ynd';
391392
import * as sdGaugeLessVoteBoost from './sd-gauge-less-vote-boost';
392393
import * as sdGaugeLessVoteBoostCrosschain from './sd-gauge-less-vote-boost-crosschain';
393394
import * as sdGaugeLessVoteBoostCrosschainSpectra from './sd-gauge-less-vote-boost-crosschain-spectra';
@@ -893,6 +894,7 @@ const strategies = {
893894
'sd-vote-boost-twavp-v2': sdVoteBoostTWAVPV2,
894895
'sd-vote-boost-twavp-v3': sdVoteBoostTWAVPV3,
895896
'sd-vote-boost-twavp-v4': sdVoteBoostTWAVPV4,
897+
'sd-vote-boost-twavp-v4-ynd': sdVoteBoostTWAVPV4Ynd,
896898
'sd-gauge-less-vote-boost': sdGaugeLessVoteBoost,
897899
'sd-gauge-less-vote-boost-crosschain': sdGaugeLessVoteBoostCrosschain,
898900
'sd-gauge-less-vote-boost-crosschain-spectra':
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# sd-vote-boost-twavp-v4-ynd
2+
3+
This strategy is used by Stake DAO to vote with sdToken using Time Weighted Averaged Voting Power (TWAVP) system and adapted for veSDT boost delegation with possibility to whiteliste address to by pass TWAVP.
4+
5+
```
6+
VotingPower(user) = veToken.balanceOf(liquidLocker) * (average.sdTokenGauge.working_balances(user) / sdTokenGauge.working_supply)
7+
```
8+
9+
>_sampleSize: in days_
10+
>_sampleStep: the number of block for `average` calculation (max 5)_
11+
12+
Here is an example of parameters:
13+
14+
```json
15+
{
16+
"veToken": "0x5f3b5DfEb7B28CDbD7FAba78963EE202a494e2A2",
17+
"liquidLocker": "0x52f541764E6e90eeBc5c21Ff570De0e2D63766B6",
18+
"sdTokenGauge": "0x7f50786A0b15723D741727882ee99a0BF34e3466",
19+
"sdToken": "0xD1b5651E55D4CeeD36251c61c50C889B36F6abB5",
20+
"pools": ["0xca0253a98d16e9c1e3614cafda19318ee69772d0"],
21+
"symbol": "sdToken",
22+
"decimals": 18,
23+
"sampleSize": 10,
24+
"sampleStep": 5,
25+
"botAddress": "",
26+
"whiteListedAddress": ["0x1c0D72a330F2768dAF718DEf8A19BAb019EEAd09"]
27+
}
28+
```
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
[
2+
{
3+
"name": "Stake DAO vote boost using TWAVP V4",
4+
"strategy": {
5+
"name": "sd-vote-boost-twavp-v4-ynd",
6+
"params": {
7+
"pools": [],
8+
"symbol": "sdYND",
9+
"sdToken": "0x0a885027D84155387B9Bd47485B0fdec10C6B4EC",
10+
"veToken": "0xd666b56ee7786cc918fddceade542d013de0e4f1",
11+
"decimals": 18,
12+
"botAddress": "0xb4542526AfeE2FdA1D584213D1521272a398B42a",
13+
"sampleSize": 10,
14+
"sampleStep": 5,
15+
"liquidLocker": "0x8396A782cc966661cd818a4DaC19C3E0aA893012",
16+
"sdTokenGauge": "0x26aB50DC99F14405155013ea580Ea2b3dB1801c7",
17+
"whiteListedAddress": []
18+
}
19+
},
20+
"network": "1",
21+
"addresses": [
22+
"0x26a5994176d34D128C5e6ab80Fa0882A7df4fF00",
23+
"0xDdB50FfDbA4D89354E1088e4EA402de895562173",
24+
"0xb4542526AfeE2FdA1D584213D1521272a398B42a",
25+
"0xfD8bD978f198503a0BA9C5D7f7586E23fC4A4b40",
26+
"0xe9046C3ce344f442caEf7B3c3d6e9D4b0F666E51"
27+
],
28+
"snapshot": 22867892
29+
}
30+
]
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
import { multicall } from '../../utils';
2+
import { BigNumber } from '@ethersproject/bignumber';
3+
import { formatUnits } from '@ethersproject/units';
4+
5+
export const author = 'pierremarsotlyon1';
6+
export const version = '0.0.1';
7+
8+
// Used ABI
9+
const abi = [
10+
'function balanceOf(address account) external view returns (uint256)',
11+
'function working_supply() external view returns (uint256)',
12+
'function totalSupply() external view returns (uint256)',
13+
'function working_balances(address account) external view returns (uint256)',
14+
'function balances(uint256 i) external view returns (uint256)',
15+
'function votingPowerForAccount(address i) external view returns (uint256)'
16+
];
17+
18+
const MIN_BOOST = 0.4;
19+
20+
export async function strategy(
21+
space,
22+
network,
23+
provider,
24+
addresses,
25+
options,
26+
snapshot
27+
): Promise<Record<string, number>> {
28+
// Maximum of 5 multicall!
29+
if (options.sampleStep > 5) {
30+
throw new Error('maximum of 5 call');
31+
}
32+
33+
// Maximum of 20 whitelisted address
34+
if (options.whiteListedAddress.length > 20) {
35+
throw new Error('maximum of 20 whitelisted address');
36+
}
37+
38+
// Maximum of 500 pools address
39+
if (options.pools.length > 500) {
40+
throw new Error('maximum of 500 pools');
41+
}
42+
43+
const calls: any[] = [];
44+
calls.push([options.sdToken, 'totalSupply', []]);
45+
46+
for (const pool of options.pools) {
47+
calls.push([options.sdToken, 'balanceOf', [pool]]);
48+
}
49+
calls.push([
50+
options.veToken,
51+
'votingPowerForAccount',
52+
[options.liquidLocker]
53+
]);
54+
55+
// --- Create block number list for twavp
56+
// Obtain last block number
57+
// Create block tag
58+
let blockTag = 0;
59+
if (typeof snapshot === 'number') {
60+
blockTag = snapshot;
61+
} else {
62+
blockTag = await provider.getBlockNumber();
63+
}
64+
65+
// Create block list
66+
const blockList = getPreviousBlocks(
67+
blockTag,
68+
options.sampleStep,
69+
options.sampleSize,
70+
options.removeTwavp || false
71+
);
72+
73+
// Query working balance of users
74+
const workingBalanceQuery = addresses.map((address: any) => [
75+
options.sdTokenGauge,
76+
'working_balances',
77+
[address]
78+
]);
79+
80+
// Execute multicall `sampleStep` times
81+
const response: any[] = [];
82+
for (let i = 0; i < options.sampleStep; i++) {
83+
// Use good block number
84+
blockTag = blockList[i];
85+
86+
const loopCalls: any[] = [];
87+
88+
// Add mutlicall response to array
89+
if (i === options.sampleStep - 1) {
90+
// End
91+
loopCalls.push([options.sdTokenGauge, 'working_supply']);
92+
loopCalls.push(...workingBalanceQuery);
93+
loopCalls.push(...calls);
94+
} else {
95+
loopCalls.push(...workingBalanceQuery);
96+
}
97+
98+
response.push(
99+
await multicall(network, provider, abi, loopCalls, { blockTag })
100+
);
101+
}
102+
103+
const workingSupply = parseFloat(
104+
formatUnits(response[response.length - 1].shift()[0], 18)
105+
);
106+
const lockerVotingPower = parseFloat(
107+
formatUnits(response[response.length - 1].pop()[0], 18)
108+
);
109+
110+
const poolsBalances = options.pools.map(
111+
() => response[response.length - 1].pop()[0]
112+
);
113+
const sumPoolsBalance = parseFloat(
114+
formatUnits(
115+
poolsBalances.reduce(
116+
(acc, balance) => acc.add(balance),
117+
BigNumber.from(0)
118+
),
119+
18
120+
)
121+
);
122+
123+
const sdTknSupply = parseFloat(
124+
formatUnits(response[response.length - 1].pop()[0], 18)
125+
);
126+
127+
const liquidityVoteFee =
128+
(MIN_BOOST * sumPoolsBalance * lockerVotingPower) / sdTknSupply;
129+
130+
const totalUserVotes = lockerVotingPower - liquidityVoteFee;
131+
132+
return Object.fromEntries(
133+
Array(addresses.length)
134+
.fill('x')
135+
.map((_, i) => {
136+
// Init array of working balances for user
137+
const userWorkingBalances: BigNumber[] = [];
138+
139+
for (let j = 0; j < options.sampleStep; j++) {
140+
// Add working balance to array.
141+
userWorkingBalances.push(response[j].shift()[0]);
142+
}
143+
144+
if (addresses[i].toLowerCase() === options.botAddress.toLowerCase()) {
145+
return [addresses[i], Number(liquidityVoteFee)];
146+
} else {
147+
// Get average working balance.
148+
const averageWorkingBalance = parseFloat(
149+
formatUnits(
150+
average(
151+
userWorkingBalances,
152+
addresses[i],
153+
options.whiteListedAddress
154+
),
155+
18
156+
)
157+
);
158+
159+
// Calculate voting power.
160+
const userVote =
161+
workingSupply != 0
162+
? (averageWorkingBalance / workingSupply) * totalUserVotes
163+
: 0;
164+
165+
// Return address and voting power
166+
return [addresses[i], Number(userVote)];
167+
}
168+
})
169+
);
170+
}
171+
172+
function getPreviousBlocks(
173+
currentBlockNumber: number,
174+
numberOfBlocks: number,
175+
daysInterval: number,
176+
removeTwavp: boolean
177+
): number[] {
178+
if (removeTwavp) {
179+
return [currentBlockNumber];
180+
}
181+
182+
// Estimate number of blocks per day
183+
const blocksPerDay = 86400 / 12;
184+
// Calculate total blocks interval
185+
const totalBlocksInterval = blocksPerDay * daysInterval;
186+
// Calculate block interval
187+
const blockInterval = totalBlocksInterval / (numberOfBlocks - 1);
188+
189+
// Init array of block numbers
190+
const blockNumbers: number[] = [];
191+
192+
for (let i = 0; i < numberOfBlocks; i++) {
193+
// Calculate block number
194+
const blockNumber =
195+
currentBlockNumber - totalBlocksInterval + blockInterval * i;
196+
// Add block number to array
197+
blockNumbers.push(Math.round(blockNumber));
198+
}
199+
200+
// Return array of block numbers
201+
return blockNumbers;
202+
}
203+
204+
function average(
205+
numbers: BigNumber[],
206+
address: string,
207+
whiteListedAddress: string[]
208+
): BigNumber {
209+
// If no numbers, return 0 to avoid division by 0.
210+
if (numbers.length === 0) return BigNumber.from(0);
211+
212+
// If address is whitelisted, return most recent working balance. i.e. no twavp applied.
213+
if (whiteListedAddress.includes(address)) return numbers[numbers.length - 1];
214+
215+
// Init sum
216+
let sum = BigNumber.from(0);
217+
// Loop through all elements and add them to sum
218+
for (let i = 0; i < numbers.length; i++) {
219+
sum = sum.add(numbers[i]);
220+
}
221+
222+
// Return sum divided by array length to get mean
223+
return sum.div(numbers.length);
224+
}

0 commit comments

Comments
 (0)