Skip to content

Commit

Permalink
add APY data for v2 vaults
Browse files Browse the repository at this point in the history
  • Loading branch information
fyang1024 committed Nov 30, 2023
1 parent 851377f commit 29af946
Show file tree
Hide file tree
Showing 2 changed files with 171 additions and 148 deletions.
141 changes: 70 additions & 71 deletions src/adaptors/sandclock/abi.js
Original file line number Diff line number Diff line change
@@ -1,81 +1,80 @@
const lusdVaultABI = [
{
"inputs": [],
"name": "totalUnderlying",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalUnderlyingMinusSponsored",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalShares",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
}
const erc4626ABI = [
{
"inputs": [],
"name": "totalAssets",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "totalSupply",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
];

const erc4626ABI = [
const erc20ABI = [
{
"inputs": [],
"name": "totalAssets",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
"constant": true,
"inputs": [
{
"internalType": "address",
"name": "_owner",
"type": "address"
}
],
"name": "balanceOf",
"outputs": [
{
"internalType": "uint256",
"name": "balance",
"type": "uint256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
];

const stabilityPoolABI = [
{
"inputs": [
{
"internalType": "uint256",
"name": "shares",
"type": "uint256"
}
],
"name": "convertToAssets",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
}
"inputs": [
{
"internalType": "address",
"name": "_depositor",
"type": "address"
}
],
"name": "getDepositorLQTYGain",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
];

module.exports = {
lusdVaultABI,
erc4626ABI,
erc20ABI,
stabilityPoolABI,
};
178 changes: 101 additions & 77 deletions src/adaptors/sandclock/index.js
Original file line number Diff line number Diff line change
@@ -1,108 +1,101 @@
const { ethers } = require('ethers');
const { getProvider } = require('@defillama/sdk/build/general');
const { lusdVaultABI, erc4626ABI } = require('./abi');
const { erc4626ABI, erc20ABI, stabilityPoolABI } = require('./abi');
const BigNumber = require('bignumber.js'); // support decimal points
const superagent = require('superagent');

const LIQUITY_VAULT = '0x91a6194f1278f6cf25ae51b604029075695a74e5';
const YEARN_VAULT = '0x4FE4BF4166744BcBc13C19d959722Ed4540d3f6a';
const WETH_VAULT = '0x1Fc623b96c8024067142Ec9c15D669E5c99c5e9D';
const USDC_VAULT = '0x1038Ff057b7092f17807358c6f68b42661d15caB';
// const LIQUITY_VAULT = '0x91a6194f1278f6cf25ae51b604029075695a74e5'; // deprecated
// const YEARN_VAULT = '0x4FE4BF4166744BcBc13C19d959722Ed4540d3f6a'; // deprecated
// const WETH_VAULT = '0x1Fc623b96c8024067142Ec9c15D669E5c99c5e9D'; // never in frontend or facing user
// const USDC_VAULT = '0x1038Ff057b7092f17807358c6f68b42661d15caB'; // never in frontend or facing user
// const JADE = '0x00C567D2b1E23782d388c8f58E64937CA11CeCf1'; // not enough tvl for yield server
// const AMETHYST = '0x8c0792Bfee67c80f0E7D4A2c5808edBC9af85e6F'; // not enough tvl for yield server
const AMBER = '0xdb369eEB33fcfDCd1557E354dDeE7d6cF3146A11';
const EMERALD = '0x4c406C068106375724275Cbff028770C544a1333';
const OPAL = '0x096697720056886b905D0DEB0f06AfFB8e4665E5';

const LUSD = '0x5f98805A4E8be255a32880FDeC7F6728C6568bA0';
const USDC = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48';
const WETH = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2';
const chain = 'ethereum';
const provider = getProvider(chain);
const BLOCKS_PER_DAY = 7160;
const LQTY = '0x6DEA81C8171D0bA574754EF6F8b412F2Ed88c54D';

const apy = async () => {

const prices = await getPrices([LUSD, USDC, WETH]);
const QUARTZ = '0xbA8A621b4a54e61C442F5Ec623687e2a942225ef';

const lusdPool = await calcLusdPoolApy(prices[LUSD.toLowerCase()]);
const LIQUITY_STABILITY_POOL = '0x66017D22b0f8556afDd19FC67041899Eb65a21bb';

const usdcDecimals = new BigNumber(10**6);
const usdcPool = await calcErc4626PoolApy(USDC, 'USDC', usdcDecimals, USDC_VAULT, prices[USDC.toLowerCase()]);

const wethDecimals = new BigNumber(10).pow(18);
const wethPool = await calcErc4626PoolApy(WETH, 'WETH', wethDecimals, WETH_VAULT, prices[WETH.toLowerCase()]);
const chain = 'ethereum';
const provider = getProvider(chain);
const BLOCKS_PER_DAY = 7160;

return [lusdPool, usdcPool, wethPool];
}
const lqtyContract = new ethers.Contract(LQTY, erc20ABI, provider);
const stabilityPoolContract = new ethers.Contract(LIQUITY_STABILITY_POOL, stabilityPoolABI, provider);
const LQTY_DECIMALS = new BigNumber(1e18.toString());

async function calcLusdPoolApy(lusdPrice) {
const liquityVault = new ethers.Contract(LIQUITY_VAULT, lusdVaultABI, provider);
const yearnVault = new ethers.Contract(YEARN_VAULT, lusdVaultABI, provider);
const apy = async () => {

// combine LIQUTIY_VAULT and YEARN_VAULT to be consistent with the DefiLlama TVL adaptor
const [lvl, yvl] = await Promise.all([
await liquityVault.totalUnderlying(),
await yearnVault.totalUnderlying()
]);
const prices = await getPrices([LUSD, USDC, WETH, LQTY]);

const lusdDecimals = new BigNumber(10).pow(18);
// convert ethers BigNumber to bignumber's BigNumber to have decimal points
const tvlLUSD = new BigNumber(lvl.add(yvl).toString()).div(lusdDecimals);
const tvlUsd = tvlLUSD.multipliedBy(lusdPrice).toNumber();
const amber = await calcErc4626PoolApy(LUSD, 'LUSD', 'Amber', AMBER, prices, true);

// s1: totalShares at the moment
// u1: LUSD in the vault at the moment
const [s1, u1] = await Promise.all([
await liquityVault.totalShares(),
await liquityVault.totalUnderlyingMinusSponsored()
]);
const opal = await calcErc4626PoolApy(USDC, 'USDC', 'Opal', OPAL, prices, false);


// s0: totalShares 28 days before
// u0: LUSD in the vault 28 days before
const [s0, u0] = await Promise.all([
await liquityVault.totalShares({ blockTag: -BLOCKS_PER_DAY * 28 }),
await liquityVault.totalUnderlyingMinusSponsored({ blockTag: -BLOCKS_PER_DAY * 28 })
]);

// Let sp1 be the current share price in LUSD, i.e., sp1 = u1 / s1
// Let sp0 be the share price in LSUD 28 days before, i.e., sp0 = u0 / s0
// we compound 28 days return 13 times to get the apy, as
// that is, {[u1 * s0 / (u0 * s1)]^13 - 1} * 100
const n = new BigNumber(u1.mul(s0).toString());
const d = new BigNumber(u0.mul(s1).toString());
const apy = n.div(d).pow(13).minus(1).times(100).toNumber();

const lusdPool = {
pool: `${LIQUITY_VAULT}-${chain}`,
chain,
project: 'sandclock',
symbol: 'LUSD',
tvlUsd,
underlyingTokens: [LUSD],
apy,
poolMeta: 'LUSD Vault',
url: 'https://app.sandclock.org/'
};
const emerald = await calcErc4626PoolApy(WETH, 'WETH', 'Emerald', EMERALD, prices, false);

return lusdPool;
return [amber, opal, emerald];
}

async function calcErc4626PoolApy(asset, symbol, decimals, vault, price) {
async function calcErc4626PoolApy(asset, symbol, poolMeta, vault, prices, liquity) {
const contract = new ethers.Contract(vault, erc4626ABI, provider);
const tvl = await contract.totalAssets();
const tvlUsd = new BigNumber(tvl.toString()).multipliedBy(price).div(decimals).toNumber();

const sharePriceNow = await contract.convertToAssets(1);
const sharePriceDayBefore = await contract. convertToAssets(1, { blockTag: -BLOCKS_PER_DAY });
const n = new BigNumber(sharePriceNow.toString());
const d = new BigNumber(sharePriceDayBefore.toString());
const apy = n.div(d).pow(365).minus(1).times(100).toNumber();

const decimals = asset == USDC ? new BigNumber(1e6.toString()) : new BigNumber(1e18.toString());
const price = prices[asset.toLowerCase()];
const tvl = await contract.totalAssets();
let tvlUsd = new BigNumber(tvl.toString()).multipliedBy(price).div(decimals);
if (liquity) {
let lqtyUsd = await calcLqtyUsd(vault, prices);
tvlUsd = tvlUsd.plus(lqtyUsd);
}

const days = 7;
let totalAssetsNow = new BigNumber((await contract.totalAssets()).toString());
if (liquity) {
totalAssetsNow = totalAssetsNow.multipliedBy(price);
await calcLqtyAssetNow(vault, prices);
totalAssetsNow = totalAssetsNow.plus(lqtyTotalUsd);
}
const totalSharesNow =new BigNumber((await contract.totalSupply()).toString());
const sharePriceNow = totalSharesNow.isZero()?
new BigNumber(0) :
totalAssetsNow.div(totalSharesNow);

let totalAssetsBefore = new BigNumber((await contract.totalAssets({ blockTag: -BLOCKS_PER_DAY * days })).toString());
if (liquity) {
totalAssetsBefore = totalAssetsBefore.multipliedBy(price);
await calcLqtyAssetBefore(vault, days, prices);
totalAssetsBefore = totalAssetsBefore.plus(lqtyTotalUsd);
}
const totalSharesBefore =new BigNumber((await contract.totalSupply({ blockTag: -BLOCKS_PER_DAY * days })).toString());
const sharePriceBefore = totalSharesBefore.isZero()?
new BigNumber(0) :
totalAssetsBefore.div(totalSharesBefore);
// const compound = Math.floor(365 / days);
// const apy = n.div(d).pow(compound).minus(1).times(100).toNumber();
const apyBase = sharePriceBefore.isZero() ?
0 :
sharePriceNow.minus(sharePriceBefore).multipliedBy(365).div(days).div(sharePriceBefore).multipliedBy(100).toNumber();

const apyReward = 15; // QUARTZ will be airdropped to depositors to Amber, Opal and Emerald vaults
const erc4626Pool = {
pool: `${vault}-${chain}`,
chain,
project: 'sandclock',
symbol,
tvlUsd,
tvlUsd: tvlUsd.toNumber(),
underlyingTokens: [asset],
apy,
poolMeta: `${symbol} Vault`,
rewardTokens: [QUARTZ],
apyBase,
apyReward,
poolMeta,
url: 'https://app.sandclock.org/'
};

Expand All @@ -126,6 +119,37 @@ const getPrices = async (addresses) => {
return pricesByAddresses;
};

async function calcLqtyUsd(vault, prices) {
let lqtyTotal = ethers.BigNumber.from(0);

const lqtyBalance = await lqtyContract.balanceOf(vault);
lqtyTotal = lqtyTotal.add(lqtyBalance);

const lqtyGain = await stabilityPoolContract.getDepositorLQTYGain(vault);
lqtyTotal = lqtyTotal.add(lqtyGain);

let lqtyUsd = new BigNumber(lqtyTotal.toString()).multipliedBy(prices[LQTY.toLowerCase()]).div(LQTY_DECIMALS);
return lqtyUsd;
}

async function calcLqtyAssetNow(vault, prices) {
let lqtyTotal = ethers.BigNumber.from(0);
const lqtyBalance = await lqtyContract.balanceOf(vault);
lqtyTotal = lqtyTotal.add(lqtyBalance);
const lqtyGain = await stabilityPoolContract.getDepositorLQTYGain(vault);
lqtyTotal = lqtyTotal.add(lqtyGain);
lqtyTotalUsd = new BigNumber(lqtyTotal.toString()).multipliedBy(prices[LQTY.toLowerCase()]);
}

async function calcLqtyAssetBefore(vault, days, prices) {
let lqtyTotal = ethers.BigNumber.from(0);
const lqtyBalance = await lqtyContract.balanceOf(vault, { blockTag: -BLOCKS_PER_DAY * days });
lqtyTotal = lqtyTotal.add(lqtyBalance);
const lqtyGain = await stabilityPoolContract.getDepositorLQTYGain(vault, { blockTag: -BLOCKS_PER_DAY * days });
lqtyTotal = lqtyTotal.add(lqtyGain);
lqtyTotalUsd = new BigNumber(lqtyTotal.toString()).multipliedBy(prices[LQTY.toLowerCase()]);
}

module.exports = {
timetravel: true,
apy,
Expand Down

0 comments on commit 29af946

Please sign in to comment.