Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vsdtoken [sd-vote-boost-twavp-vsdtoken] #1380

Merged
merged 6 commits into from
Dec 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/strategies/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,7 @@ import * as gelatoStaking from './gelato-staking';
import * as erc4626AssetsOf from './erc4626-assets-of';
import * as sdVoteBoostTWAVPV2 from './sd-vote-boost-twavp-v2';
import * as sdVoteBoostTWAVPV3 from './sd-vote-boost-twavp-v3';
import * as sdVoteBoostTWAVPVsdToken from './sd-vote-boost-twavp-vsdtoken';
import * as friendTech from './friend-tech';
import * as moonbase from './moonbase';
import * as dssVestUnpaid from './dss-vest-unpaid';
Expand Down Expand Up @@ -776,6 +777,7 @@ const strategies = {
'friend-tech': friendTech,
'sd-vote-boost-twavp-v2': sdVoteBoostTWAVPV2,
'sd-vote-boost-twavp-v3': sdVoteBoostTWAVPV3,
'sd-vote-boost-twavp-vsdtoken': sdVoteBoostTWAVPVsdToken,
moonbase: moonbase,
'dss-vest-unpaid': dssVestUnpaid,
'dss-vest-balance-and-unpaid': dssVestBalanceAndUnpaid,
Expand Down
28 changes: 28 additions & 0 deletions src/strategies/sd-vote-boost-twavp-vsdtoken/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# sd-vote-boost-twavp-vsdtoken

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.

```
VotingPower(user) = veToken.balanceOf(liquidLocker) * (average.sdTokenGauge.working_balances(user) / sdTokenGauge.working_supply)
```

>_sampleSize: in days_
>_sampleStep: the number of block for `average` calculation (max 5)_

Here is an example of parameters:

```json
{
"veToken": "0x5f3b5DfEb7B28CDbD7FAba78963EE202a494e2A2",
"liquidLocker": "0x52f541764E6e90eeBc5c21Ff570De0e2D63766B6",
"sdTokenGauge": "0x7f50786A0b15723D741727882ee99a0BF34e3466",
"sdToken": "0xD1b5651E55D4CeeD36251c61c50C889B36F6abB5",
"pools": ["0xca0253a98d16e9c1e3614cafda19318ee69772d0"],
"symbol": "sdToken",
"decimals": 18,
"sampleSize": 10,
"sampleStep": 5,
"botAddress": "",
"whiteListedAddress": ["0x1c0D72a330F2768dAF718DEf8A19BAb019EEAd09"]
}
```
28 changes: 28 additions & 0 deletions src/strategies/sd-vote-boost-twavp-vsdtoken/examples.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
[
{
"name": "Stake DAO vote boost using TWAVP for vsdTkn holders",
"strategy": {
"name": "sd-vote-boost-twavp-vsdtoken",
"params": {
"vsdToken": "0xE079ac07463ff375Ce48E8A9D76211C10696F3B8",
"sdTokenGauge": "0x7f50786A0b15723D741727882ee99a0BF34e3466",
"booster": "0x38d10708Ce535361F178f55E68DF7E85aCc66270",
"locker": "0x52f541764E6e90eeBc5c21Ff570De0e2D63766B6",
"veAddress": "0x5f3b5DfEb7B28CDbD7FAba78963EE202a494e2A2",
"symbol": "sdToken",
"decimals": 18,
"sampleSize": 10,
"sampleStep": 5,
"whiteListedAddress": ["0x1c0D72a330F2768dAF718DEf8A19BAb019EEAd09"]
}
},
"network": "1",
"addresses": [
"0xc59910E5E2dd8225623B663491aa754F7013F067",
"0xDdB50FfDbA4D89354E1088e4EA402de895562173",
"0xE1F7eaD40d33eeF30dCf15eB5efC45409001aAB8",
"0x1c0D72a330F2768dAF718DEf8A19BAb019EEAd09"
],
"snapshot": 18870156
}
]
188 changes: 188 additions & 0 deletions src/strategies/sd-vote-boost-twavp-vsdtoken/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
import { multicall } from '../../utils';
import { BigNumber } from '@ethersproject/bignumber';
import { formatUnits } from '@ethersproject/units';

export const author = 'pierremarsotlyon1';
export const version = '0.0.1';

// Used ABI
const abi = [
'function balanceOf(address account) external view returns (uint256)',
'function working_supply() external view returns (uint256)',
'function totalSupply() external view returns (uint256)',
'function working_balances(address account) external view returns (uint256)'
];

const MIN_BLOCK = 18835548;

export async function strategy(
space,
network,
provider,
addresses,
options,
snapshot
): Promise<Record<string, number>> {
// Maximum of 5 multicall!
if (options.sampleStep > 5) {
throw new Error('maximum of 5 call');
}

// Maximum of 20 whitelisted address
if (options.whiteListedAddress.length > 20) {
throw new Error('maximum of 20 whitelisted address');
}

// --- Create block number list for twavp
// Obtain last block number
// Create block tag
let blockTag = 0;
if (typeof snapshot === 'number') {
blockTag = snapshot;
} else {
blockTag = await provider.getBlockNumber();
}

// Create block list
const blockList = getPreviousBlocks(
blockTag,
options.sampleStep,
options.sampleSize
);

const balanceOfQueries: any[] = [];
for(const address of addresses) {
balanceOfQueries.push([
options.vsdToken,
'balanceOf',
[address]
]);
balanceOfQueries.push([
options.vsdToken,
'totalSupply',
[]
]);
}

const response: any[] = [];
for (let i = 0; i < options.sampleStep; i++) {
// Use good block number
blockTag = blockList[i];

const loopCalls: any[] = [];

// Add mutlicall response to array
if (i === options.sampleStep - 1) {
// End
loopCalls.push([options.veAddress, 'balanceOf', [options.locker]]);
loopCalls.push([options.sdTokenGauge, 'working_supply']);
loopCalls.push([options.sdTokenGauge, 'working_balances', [options.booster]]);
loopCalls.push(...balanceOfQueries);
} else {
loopCalls.push(...balanceOfQueries);
}

response.push(
await multicall(network, provider, abi, loopCalls, { blockTag })
);
}

const lockerVeBalance = response[response.length - 1].shift()[0]; // Last response, latest block
const workingSupply = response[response.length - 1].shift()[0]; // Last response, latest block
const workingBalances = response[response.length - 1].shift()[0]; // Last response, latest block

const totalVP = parseFloat(formatUnits(workingBalances, 18))
/ parseFloat(formatUnits(workingSupply, 18))
* parseFloat(formatUnits(lockerVeBalance, 18));

return Object.fromEntries(
Array(addresses.length)
.fill('x')
.map((_, i) => {
// Init array of working balances for user
const userWorkingBalances: number[] = [];

for (let j = 0; j < options.sampleStep; j++) {
const balanceOf = BigNumber.from(response[j].shift()[0]);
const totalSupply = BigNumber.from(response[j].shift()[0]);

// Add working balance to array.
if(totalSupply.eq(0)) {
userWorkingBalances.push(0);
} else {
userWorkingBalances.push(balanceOf.div(totalSupply).toNumber());
}
}

// Get average working balance.
const averageWorkingBalance = average(
userWorkingBalances,
addresses[i],
options.whiteListedAddress
);

// Calculate voting power.
const votingPower =
totalVP != 0
? averageWorkingBalance * totalVP
: 0;

// Return address and voting power
return [addresses[i], Number(votingPower)];
})
);
}


function average(
numbers: number[],
address: string,
whiteListedAddress: string[]
): number {
// If no numbers, return 0 to avoid division by 0.
if (numbers.length === 0) return 0;

// If address is whitelisted, return most recent working balance. i.e. no twavp applied.
if (whiteListedAddress.includes(address)) return numbers[numbers.length - 1];

// Init sum
let sum = 0;
// Loop through all elements and add them to sum
for (let i = 0; i < numbers.length; i++) {
sum += numbers[i];
}

// Return sum divided by array length to get mean
return sum / numbers.length;
}


function getPreviousBlocks(
currentBlockNumber: number,
numberOfBlocks: number,
daysInterval: number
): number[] {
// Estimate number of blocks per day
const blocksPerDay = 86400 / 12;
// Calculate total blocks interval
const totalBlocksInterval = blocksPerDay * daysInterval;
// Calculate block interval
const blockInterval = totalBlocksInterval / (numberOfBlocks - 1);

// Init array of block numbers
const blockNumbers: number[] = [];

for (let i = 0; i < numberOfBlocks; i++) {
// Calculate block number
let blockNumber =
currentBlockNumber - totalBlocksInterval + blockInterval * i;
if (blockNumber < MIN_BLOCK) {
blockNumber = MIN_BLOCK;
}
// Add block number to array
blockNumbers.push(Math.round(blockNumber));
}

// Return array of block numbers
return blockNumbers;
}
Loading