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

Feat/psp in sepsp2 balance #3

Draft
wants to merge 11 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 8 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
4 changes: 3 additions & 1 deletion src/strategies/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,7 @@ import * as jpegdLockedJpegOf from './jpegd-locked-jpeg-of';
import * as litDaoGovernance from './lit-dao-governance';
import * as babywealthyclub from './babywealthyclub';
import * as battleflyVGFLYAndStakedGFLY from './battlefly-vgfly-and-staked-gfly';
import * as pspInSePSP2Balance from './psp-in-sepsp2-balance';

const strategies = {
'forta-shares': fortaShares,
Expand Down Expand Up @@ -837,7 +838,8 @@ const strategies = {
'vsta-pool-staking': vstaPoolStaking,
'jpegd-locked-jpeg-of': jpegdLockedJpegOf,
babywealthyclub,
'battlefly-vgfly-and-staked-gfly': battleflyVGFLYAndStakedGFLY
'battlefly-vgfly-and-staked-gfly': battleflyVGFLYAndStakedGFLY,
'psp-in-sepsp2-balance': pspInSePSP2Balance
};

Object.keys(strategies).forEach(function (strategyName) {
Expand Down
66 changes: 66 additions & 0 deletions src/strategies/psp-in-sepsp2-balance/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# psp-in-sepsp2-balance

This is a strategy to get PSP balances staked in sePSP2 contract and multiply that by `options.multiplier`.

It works like this:
1. Get BPT balance an account holds
```js
const sePSP_balance = BPT_balance = SPSP.PSPBalance(address)
```

2. Get tokens of the Balancer Pool
```js
const [tokens] = await Vault.getPoolTokens(poolId)
```

3. Construct an exit pool request that could be used to unstake 1 BPT balance
```js
const exitPoolRequest = {
assets: tokens, // Balancer Pools underlying tokens
minAmountsOut: [0,0], // minimal amounts received
userData, // endoded [1, 1e18], // ExitKind.EXACT_BPT_IN_FOR_TOKENS_OUT = 1
toInternalBalance: false, // transfer tokens to recipient, as opposed to depositing to internal balance
}
```

4. Find how many tokens you would receive by unstaking 1 BPT balance
```js
const [amountsOut] = await BalancerHelpers.callStatic.queryExit(
poolId,
Zero_account, // sender
Zero_account, // recipient
exitPoolRequest
)
// sender & recipient don't matter as we only getting an estimate
```
`amountsOut` is a representation of BPT balance in the Balancer Pool's underlying tokens. In the same order as `assets`

5. One of the `amountsOut` is PSP portion of 1 BPT.
```js
const PSP_In_1_BPT = amountsOut[index_from_assets]
```

6. Multiply PSP_balance by score multiplier.
```js
const Vote_power = PSP_In_1_BPT * BPT_balance * 2.5
```

Here is an example of parameters:

```json
{
"address": "0xcafe001067cdef266afb7eb5a286dcfd277f3de5",
"symbol": "PSP",
"decimals": 18,
"sePSP2": {
"address": "0x593F39A4Ba26A9c8ed2128ac95D109E8e403C485",
"decimals": 18
},
"balancer": {
"poolId": "0xcb0e14e96f2cefa8550ad8e4aea344f211e5061d00020000000000000000011a",
"BalancerHelpers": "0x5aDDCCa35b7A0D07C74063c48700C8590E87864E",
"Vault": "0xBA12222222228d8Ba445958a75a0704d566BF2C8"
},
"multiplier": 2.5
}
```
47 changes: 47 additions & 0 deletions src/strategies/psp-in-sepsp2-balance/examples.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
[
{
"name": "Example query",
"strategy": {
"name": "psp-in-sepsp2-balance",
"params": {
"address": "0xcafe001067cdef266afb7eb5a286dcfd277f3de5",
"symbol": "PSP",
"decimals": 18,
"sePSP2": {
"address": "0x593F39A4Ba26A9c8ed2128ac95D109E8e403C485",
"decimals": 18
},
"balancer": {
"poolId": "0xcb0e14e96f2cefa8550ad8e4aea344f211e5061d00020000000000000000011a",
"BalancerHelpers": "0x5aDDCCa35b7A0D07C74063c48700C8590E87864E",
"Vault": "0xBA12222222228d8Ba445958a75a0704d566BF2C8"
},
"multiplier": 2.5
}
},
"network": "1",
"addresses": [
"0x05182E579FDfCf69E4390c3411D8FeA1fb6467cf",
"0x0DDC793680FF4f5793849c8c6992be1695CbE72A",
"0x0edefa91e99da1eddd1372c1743a63b1595fc413",
"0xd37f7b32a541d9e423f759dff1dd63181651bd04",
"0xf9aa0da6e2fa01a17e2f69e878e45bb26c1b34b7",
"0xc570429a39a93fd267d1047b2363cfba07198ff7",
"0x4e8ffddb1403cf5306c6c7b31dc72ef5f44bc4f5",
"0x0ddc793680ff4f5793849c8c6992be1695cbe72a",
"0xd880507d359af862a5f8f318c8e934ab478ca818",
"0x510a7cd4ba40f7b6643f566a5d45ea55f5cd8d0e",
"0x1ff3c4bfa745b72f942c5cf2b769b3d8a6610a5e",
"0x5577933afc0522c5ee71115df61512f49da0543e",
"0x6eb8d6bccceb84832725dcf792468dd8ba088449",
"0xe768FF81990E7Ac73C18a2eCbf038815023599Dc",
"0xB9E11C28617D46866c1D7d95EaebAC3AC12CDAD3",
"0xB5714084eeF0f02eFDD145DFB3Fe2e3290591D7b",
"0xCC6B30531DE603787a4D0305FC7eD404374Cf771",
"0xcb492647CB51E243Fb2582C0300C4c7573acdEBf",
"0xB8f6f3cc7b162d7E5b9196140Fb1878cdA316ba0",
"0x584BaA4b71b0A3fA522658128f36a6A4AbeAC2ae"
],
"snapshot": 16492220
}
]
152 changes: 152 additions & 0 deletions src/strategies/psp-in-sepsp2-balance/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import { BigNumberish, BigNumber } from '@ethersproject/bignumber';
import { formatUnits, parseUnits } from '@ethersproject/units';
import { Contract } from '@ethersproject/contracts';
import { defaultAbiCoder } from '@ethersproject/abi';
import { strategy as fetchERC20Balances } from '../erc20-balance-of';
import { getAddress } from '@ethersproject/address';

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

const BalancerVaultAbi = [
'function getPoolTokens(bytes32 poolId) external view returns (address[] tokens, uint256[] balances, uint256 lastChangeBlock)'
];
interface PoolTokensFromVault {
tokens: string[];
balances: BigNumber[];
lastChangeBlock: BigNumber;
}

const BalancerHelpersAbi = [
'function queryExit(bytes32 poolId, address sender, address recipient, tuple(address[] assets, uint256[] minAmountsOut, bytes userData, bool toInternalBalance) request) returns (uint256 bptIn, uint256[] amountsOut)'
];

interface QueryExitResult {
bptIn: BigNumber;
amountsOut: BigNumber[];
}

interface StrategyOptions {
address: string;
symbol: string;
decimals: number;
sePSP2: { address: string; decimals: number };
balancer: {
poolId: string;
BalancerHelpers: string;
Vault: string;
};
multiplier: number;
}

const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';

export async function strategy(
space: string,
network: string,
provider,
addresses: string[],
options: StrategyOptions,
snapshot: number
): Promise<Record<string, number>> {
const blockTag = typeof snapshot === 'number' ? snapshot : 'latest';

const erc20Options = {
...options.sePSP2,
symbol: 'sePSP2'
};

const account2BPTBalance = await fetchERC20Balances(
space,
network,
provider,
addresses,
erc20Options,
snapshot
);

const balancerVault = new Contract(
options.balancer.Vault,
BalancerVaultAbi,
provider
);

const { tokens: poolTokens }: PoolTokensFromVault =
await balancerVault.getPoolTokens(options.balancer.poolId, { blockTag });

const tokenLowercase = options.address.toLowerCase();
const tokenIndex = poolTokens.findIndex(
(token) => token.toLowerCase() === tokenLowercase
);

if (tokenIndex === -1) {
throw new Error(
`Token ${options.address} doesn't belong to Balancer Pool ${options.balancer.poolId}`
);
}

const balancerHelpers = new Contract(
options.balancer.BalancerHelpers,
BalancerHelpersAbi,
provider
);

const exitPoolRequest = constructExitPoolRequest(
poolTokens,
// how much will get for 1 BPT
parseUnits('1', options.sePSP2.decimals)
);

const queryExitResult: QueryExitResult =
await balancerHelpers.callStatic.queryExit(
options.balancer.poolId,
ZERO_ADDRESS,
ZERO_ADDRESS,
exitPoolRequest,
{ blockTag }
);

const pspFor1BPT = parseFloat(
formatUnits(queryExitResult.amountsOut[tokenIndex], options.decimals)
);

const address2PSPinSePSP2 = Object.fromEntries(
Object.entries(account2BPTBalance).map(([address, bptBalance]) => {
const pspBalance = pspFor1BPT * bptBalance;

const checksummedAddress = getAddress(address);

return [checksummedAddress, pspBalance * options.multiplier];
})
);

return address2PSPinSePSP2;
}

interface ExitPoolRequest {
assets: string[];
minAmountsOut: BigNumberish[];
userData: string;
toInternalBalance: boolean;
}

// ExitKind enum for BalancerHerlpers.queryExit call
const EXACT_BPT_IN_FOR_TOKENS_OUT = 1;

export function constructExitPoolRequest(
assets: string[],
bptAmountIn: BigNumberish
): ExitPoolRequest {
const abi = ['uint256', 'uint256'];
const data = [EXACT_BPT_IN_FOR_TOKENS_OUT, bptAmountIn];
const userData = defaultAbiCoder.encode(abi, data);

const minAmountsOut = assets.map(() => 0);

return {
assets,
minAmountsOut,
userData,
toInternalBalance: false
};
}