Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
15 changed files
with
380 additions
and
45 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
// SPDX-License-Identifier: AGPL-3.0-or-later | ||
pragma solidity 0.8.2; | ||
|
||
import { IBoostedVaultWithLockup } from "../interfaces/IBoostedVaultWithLockup.sol"; | ||
|
||
struct PokeVaultAccounts { | ||
// Address of the Boosted Vault | ||
address boostVault; | ||
// List of accounts to be poked | ||
address[] accounts; | ||
} | ||
|
||
/** | ||
* @title Poker | ||
* @author mStable | ||
* @notice Pokes accounts on boosted vaults so their vault balances can be recalculated. | ||
* @dev VERSION: 1.0 | ||
* DATE: 2021-04-17 | ||
*/ | ||
contract Poker { | ||
|
||
/** | ||
* @dev For each boosted vault, poke all the over boosted accounts. | ||
* @param _vaultAccounts An array of PokeVaultAccounts structs | ||
*/ | ||
function poke(PokeVaultAccounts[] memory _vaultAccounts) external { | ||
uint vaultCount = _vaultAccounts.length; | ||
for(uint i = 0; i < vaultCount; i++) { | ||
PokeVaultAccounts memory vaultAccounts = _vaultAccounts[i]; | ||
address boostVaultAddress = vaultAccounts.boostVault; | ||
require(boostVaultAddress != address(0), "blank vault address"); | ||
IBoostedVaultWithLockup boostVault = IBoostedVaultWithLockup(boostVaultAddress); | ||
|
||
uint accountsLength = vaultAccounts.accounts.length; | ||
for(uint j = 0; j < accountsLength; j++) { | ||
address accountAddress = vaultAccounts.accounts[j]; | ||
require(accountAddress != address(0), "blank address"); | ||
boostVault.pokeBoost(accountAddress); | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
/* eslint-disable no-await-in-loop */ | ||
/* eslint-disable no-restricted-syntax */ | ||
import { formatUnits } from "@ethersproject/units" | ||
import { fullScale } from "@utils/constants" | ||
import { BN, simpleToExactAmount } from "@utils/math" | ||
import { Signer } from "ethers" | ||
import { Contract, Provider } from "ethers-multicall" | ||
import { gql, GraphQLClient } from "graphql-request" | ||
import { task, types } from "hardhat/config" | ||
import { BoostedSavingsVault__factory, Poker, Poker__factory } from "types/generated" | ||
import { getDefenderSigner } from "./utils/defender-utils" | ||
import { deployContract, logTxDetails } from "./utils/deploy-utils" | ||
import { MTA } from "./utils/tokens" | ||
|
||
const maxVMTA = simpleToExactAmount(300000, 18) | ||
const maxBoost = simpleToExactAmount(4, 18) | ||
const minBoost = simpleToExactAmount(1, 18) | ||
const floor = simpleToExactAmount(95, 16) | ||
|
||
const pokerAddress = "0x8E1Fd7F5ea7f7760a83222d3d470dFBf8493A03F" | ||
|
||
const calcBoost = (raw: BN, vMTA: BN, priceCoefficient: BN, boostCoeff: BN, decimals = 18): BN => { | ||
// min(m, max(d, (d * 0.95) + c * min(vMTA, f) / USD^b)) | ||
const scaledBalance = raw.mul(priceCoefficient).div(simpleToExactAmount(1, decimals)) | ||
|
||
if (scaledBalance.lt(simpleToExactAmount(1, decimals))) return minBoost | ||
|
||
let denom = parseFloat(formatUnits(scaledBalance)) | ||
denom **= 0.875 | ||
const flooredMTA = vMTA.gt(maxVMTA) ? maxVMTA : vMTA | ||
let rhs = floor.add(flooredMTA.mul(boostCoeff).div(10).mul(fullScale).div(simpleToExactAmount(denom))) | ||
rhs = rhs.gt(minBoost) ? rhs : minBoost | ||
return rhs.gt(maxBoost) ? maxBoost : rhs | ||
} | ||
|
||
type AccountBalance = { [index: string]: BN } | ||
const getAccountBalanceMap = async (accounts: string[], tokenAddress: string, signer: Signer): Promise<AccountBalance> => { | ||
const abi = ["function balanceOf(address owner) view returns (uint256)", "function decimals() view returns (uint8)"] | ||
const token = new Contract(tokenAddress, abi) | ||
|
||
const ethcallProvider = new Provider(signer.provider) | ||
await ethcallProvider.init() | ||
|
||
const callPromises = accounts.map((account) => token.balanceOf(account)) | ||
|
||
console.log(`About to get vMTA balances for ${accounts.length} accounts`) | ||
const balances = (await ethcallProvider.all(callPromises)) as BN[] | ||
const accountBalances: AccountBalance = {} | ||
balances.forEach((balance, i) => { | ||
accountBalances[accounts[i]] = balance | ||
}) | ||
return accountBalances | ||
} | ||
|
||
task("over-boost", "Pokes accounts that are over boosted") | ||
.addOptionalParam("speed", "Defender Relayer speed param: 'safeLow' | 'average' | 'fast' | 'fastest'", "fast", types.string) | ||
.addFlag("update", "Will send a poke transactions to the Poker contract") | ||
.addOptionalParam("minMtaDiff", "Min amount of vMTA over boosted", 500, types.int) | ||
.setAction(async (taskArgs) => { | ||
const minMtaDiff = simpleToExactAmount(taskArgs.minMtaDiff) | ||
const signer = await getDefenderSigner(taskArgs.speed) | ||
// const [signer] = await ethers.getSigners() | ||
// const signer = await impersonate("0x2f2Db75C5276481E2B018Ac03e968af7763Ed118") | ||
|
||
const gqlClient = new GraphQLClient("https://api.thegraph.com/subgraphs/name/mstable/mstable-feeder-pools") | ||
const query = gql` | ||
{ | ||
boostedSavingsVaults { | ||
id | ||
boostCoeff | ||
priceCoeff | ||
stakingToken { | ||
symbol | ||
} | ||
accounts(where: { rawBalance_gt: "0" }) { | ||
account { | ||
id | ||
} | ||
rawBalance | ||
boostedBalance | ||
rewardPerTokenPaid | ||
rewards | ||
lastClaim | ||
lastAction | ||
} | ||
} | ||
_meta { | ||
block { | ||
number | ||
} | ||
} | ||
} | ||
` | ||
const gqlData = await gqlClient.request(query) | ||
|
||
// eslint-disable-next-line no-underscore-dangle | ||
const blockNumber = gqlData._meta.block.number | ||
console.log(`Results for block number ${blockNumber}`) | ||
|
||
// Maps GQL to a list if accounts (addresses) in each vault | ||
const vaultAccounts = gqlData.boostedSavingsVaults.map((vault) => vault.accounts.map((account) => account.account.id)) | ||
const accountsWithDuplicates = vaultAccounts.flat() | ||
const accountsUnique = [...new Set<string>(accountsWithDuplicates)] | ||
const vMtaBalancesMap = await getAccountBalanceMap(accountsUnique, MTA.saving, signer) | ||
const pokeVaultAccounts: { | ||
boostVault: string | ||
accounts: string[] | ||
}[] = [] | ||
// For each Boosted Vault | ||
for (const vault of gqlData.boostedSavingsVaults) { | ||
const boostVault = BoostedSavingsVault__factory.connect(vault.id, signer) | ||
const priceCoeff = await boostVault.priceCoeff() | ||
const boostCoeff = await boostVault.boostCoeff() | ||
|
||
const overBoosted: any[] = [] | ||
console.log( | ||
`\nVault with id ${vault.id} for token ${vault.stakingToken.symbol}, ${vault.accounts.length} accounts, price coeff ${priceCoeff}, boost coeff ${boostCoeff}`, | ||
) | ||
console.log("Account, Raw Balance, Boosted Balance, vMTA balance, vMTA diff, Boost Actual, Boost Expected, Boost Diff") | ||
vault.accounts.forEach((account, i) => { | ||
const boostActual = BN.from(account.boostedBalance).mul(1000).div(account.rawBalance).toNumber() | ||
const boostExpected = calcBoost(BN.from(account.rawBalance), vMtaBalancesMap[account.account.id], priceCoeff, boostCoeff) | ||
.div(BN.from(10).pow(15)) | ||
.toNumber() | ||
const boostDiff = boostActual - boostExpected | ||
// Calculate how much extra MTA is being distributed = vMTA balance * (actual boost - expected boost) | ||
const vMtaExtra = vMtaBalancesMap[account.account.id].mul(boostDiff).div(1000) | ||
if (vMtaExtra.gt(minMtaDiff)) { | ||
overBoosted.push({ | ||
...account, | ||
boostActual, | ||
boostExpected, | ||
boostDiff, | ||
vMtaExtra, | ||
}) | ||
} | ||
console.log( | ||
`${account.account.id}, ${formatUnits(account.rawBalance)}, ${formatUnits(account.boostedBalance)}, ${formatUnits( | ||
vMtaBalancesMap[account.account.id], | ||
)}, ${formatUnits(vMtaExtra)}, ${formatUnits(boostActual, 3)}, ${formatUnits(boostExpected, 3)}, ${formatUnits( | ||
boostDiff, | ||
3, | ||
)}`, | ||
) | ||
}) | ||
console.log(`${overBoosted.length} of ${vault.accounts.length} over boosted for ${vault.id}`) | ||
overBoosted.forEach((account) => { | ||
console.log(`${account.account.id} ${formatUnits(account.boostDiff, 3)}, ${formatUnits(account.vMtaExtra)}`) | ||
}) | ||
const pokeAccounts = overBoosted.map((account) => account.account.id) | ||
console.log(pokeAccounts) | ||
pokeVaultAccounts.push({ | ||
boostVault: vault.id, | ||
accounts: pokeAccounts, | ||
}) | ||
} | ||
if (taskArgs.update) { | ||
const poker = Poker__factory.connect(pokerAddress, signer) | ||
const tx = await poker.poke(pokeVaultAccounts) | ||
await logTxDetails(tx, "poke Poker") | ||
} | ||
}) | ||
|
||
task("deployPoker", "Deploys the Poker contract").setAction(async (_, hre) => { | ||
const { network } = hre | ||
// const [deployer] = await ethers.getSigners() | ||
const deployer = await getDefenderSigner() | ||
|
||
if (network.name !== "mainnet") throw Error("Invalid network") | ||
|
||
await deployContract<Poker>(new Poker__factory(deployer), "Poker") | ||
}) | ||
|
||
module.exports = {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.