Skip to content

Commit

Permalink
Merge 5ac5563 into 3e53f2e
Browse files Browse the repository at this point in the history
  • Loading branch information
naddison36 committed May 18, 2021
2 parents 3e53f2e + 5ac5563 commit 2d626d8
Show file tree
Hide file tree
Showing 15 changed files with 380 additions and 45 deletions.
42 changes: 42 additions & 0 deletions contracts/savings/Poker.sol
@@ -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);
}
}
}
}
6 changes: 3 additions & 3 deletions hardhat-fork.config.ts
Expand Up @@ -6,11 +6,11 @@ export default {
...hardhatConfig.networks,
hardhat: {
allowUnlimitedContractSize: false,
blockGasLimit: 9000000,
gasPrice: 151000000000,
blockGasLimit: 15000000,
gasPrice: 52000000000,
forking: {
url: process.env.NODE_URL || "",
blockNumber: 12205156,
blockNumber: 12452435,
},
},
},
Expand Down
3 changes: 3 additions & 0 deletions package.json
Expand Up @@ -65,6 +65,9 @@
"eslint-plugin-react": "^7.18.3",
"ethereum-waffle": "^3.0.0",
"ethers": "^5.0.26",
"ethers-multicall": "^0.2.0",
"graphql": "^15.5.0",
"graphql-request": "^3.4.0",
"hardhat": "^2.0.8",
"hardhat-gas-reporter": "^1.0.1",
"hardhat-typechain": "0.3.3",
Expand Down
3 changes: 2 additions & 1 deletion tasks.config.ts
@@ -1,6 +1,7 @@
import config from "./hardhat.config"

import "./tasks/deployAaveIntegration"
import "./tasks/deployBoostedSavingsVault"
import "./tasks/deployMbtc"
import "./tasks/deployFeeders"
import "./tasks/deployMV3"
Expand All @@ -10,7 +11,7 @@ import "./tasks/mBTC"
import "./tasks/mUSD"
import "./tasks/SaveWrapper"
import "./tasks/FeederWrapper"
import "./tasks/BoostedSavingsVault"
import "./tasks/ops"
import "./tasks/poker"

export default config
File renamed without changes.
174 changes: 174 additions & 0 deletions tasks/poker.ts
@@ -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 = {}
2 changes: 1 addition & 1 deletion tasks/utils/defender-utils.ts
Expand Up @@ -2,7 +2,7 @@ import { Speed } from "defender-relay-client"
import { Signer } from "ethers"
import { DefenderRelayProvider, DefenderRelaySigner } from "defender-relay-client/lib/ethers"

export const getDefenderSigner = async (speed: Speed = "average"): Promise<Signer> => {
export const getDefenderSigner = async (speed: Speed = "fast"): Promise<Signer> => {
if (!process.env.DEFENDER_API_KEY || !process.env.DEFENDER_API_SECRET) {
console.error(`Defender env vars DEFENDER_API_KEY and/or DEFENDER_API_SECRET have not been set`)
process.exit(1)
Expand Down
8 changes: 8 additions & 0 deletions tasks/utils/tokens.ts
Expand Up @@ -156,4 +156,12 @@ export const TBTC: Token = {
saving: "0x760ea8CfDcC4e78d8b9cA3088ECD460246DC0731",
}

export const MTA: Token = {
symbol: "MTA",
address: "0xa3BeD4E1c75D00fa6f4E5E6922DB7261B5E9AcD2",
decimals: 18,
quantityFormatter: "USD",
saving: "0xaE8bC96DA4F9A9613c323478BE181FDb2Aa0E1BF",
}

export const tokens = [mUSD, mBTC, sUSD, USDC, USDT, DAI, GUSD, BUSD, renBTC, sBTC, WBTC, HBTC, TBTC]
11 changes: 2 additions & 9 deletions test-fork/buy-and-make/recipient-upgrade.spec.ts
@@ -1,7 +1,7 @@
import { expect } from "chai"
import { ONE_DAY, ONE_WEEK } from "@utils/constants"
import { Signer, ContractFactory, Contract } from "ethers"
import { ethers, network } from "hardhat"
import { network } from "hardhat"
import { formatEther, keccak256, toUtf8Bytes } from "ethers/lib/utils"
import { increaseTime } from "@utils/time"
import {
Expand All @@ -23,6 +23,7 @@ import {
} from "types/generated"
import { simpleToExactAmount, BN } from "@utils/math"
import { abi as SavingsManagerAbi, bytecode as SavingsManagerBytecode } from "./SavingsManager.json"
import { impersonate } from "@utils/fork"

// Accounts that are impersonated
const ethWhaleAddress = "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"
Expand All @@ -31,14 +32,6 @@ const mUsdWhaleAddress = "0x6595732468A241312bc307F327bA0D64F02b3c20"
const balWhale = "0x3f5ce5fbfe3e9af3971dd833d26ba9b5c936f0be"
const nullAddr = "0xAf40dA2DcE68Bf82bd4C5eE7dA22B2F7bb7ba265"

// impersonates a specific account
const impersonate = async (addr): Promise<Signer> => {
await network.provider.request({
method: "hardhat_impersonateAccount",
params: [addr],
})
return ethers.provider.getSigner(addr)
}
interface Config {
oldRecipient: string
nexus: string
Expand Down
14 changes: 2 additions & 12 deletions test-fork/feeders/feeders-deployment.spec.ts
Expand Up @@ -21,19 +21,11 @@ import {
InterestValidator__factory,
} from "types/generated"
import { simpleToExactAmount, BN } from "@utils/math"
import { impersonate } from "@utils/fork"

// Accounts that are impersonated
const deployerAddress = "0x19F12C947D25Ff8a3b748829D8001cA09a28D46d"

// impersonates a specific account
const impersonate = async (addr): Promise<Signer> => {
await network.provider.request({
method: "hardhat_impersonateAccount",
params: [addr],
})
return ethers.provider.getSigner(addr)
}

interface CommonAddresses {
nexus: string
proxyAdmin: string
Expand Down Expand Up @@ -165,9 +157,7 @@ const deployFeederPool = async (sender: Signer, addresses: CommonAddresses, feed
const mint = async (sender: Signer, bAssets: DeployedFasset[], feederData: FeederData) => {
// e.e. $4e18 * 1e18 / 1e18 = 4e18
// e.g. 4e18 * 1e18 / 5e22 = 8e13 or 0.00008
const scaledTestQty = simpleToExactAmount(4)
.mul(simpleToExactAmount(1))
.div(feederData.priceCoeff)
const scaledTestQty = simpleToExactAmount(4).mul(simpleToExactAmount(1)).div(feederData.priceCoeff)

// Approve spending
const approvals: BN[] = []
Expand Down
10 changes: 1 addition & 9 deletions test-fork/feeders/feeders-musd-cream.spec.ts
@@ -1,3 +1,4 @@
import { impersonate } from "@utils/fork"
import { BN, simpleToExactAmount } from "@utils/math"
import { expect } from "chai"
import { Signer } from "ethers"
Expand Down Expand Up @@ -25,15 +26,6 @@ const liquidatorAddress = "0xe595D67181D701A5356e010D9a58EB9A341f1DbD"
// Not sure why this is 2**96 - 1 and not 2**256 - 1 for CREAM
const safeInfinity = BN.from(2).pow(96).sub(1)

// impersonates a specific account
const impersonate = async (addr): Promise<Signer> => {
await network.provider.request({
method: "hardhat_impersonateAccount",
params: [addr],
})
return ethers.provider.getSigner(addr)
}

context("mUSD Feeder Pool integration to CREAM", () => {
let governor: Signer
let deployer: Signer
Expand Down
10 changes: 1 addition & 9 deletions test-fork/mUSD/aave2-migration.spec.ts
@@ -1,5 +1,6 @@
import { formatUnits } from "@ethersproject/units"
import { ONE_DAY } from "@utils/constants"
import { impersonate } from "@utils/fork"
import { BN, simpleToExactAmount } from "@utils/math"
import { increaseTime } from "@utils/time"
import { expect } from "chai"
Expand Down Expand Up @@ -53,15 +54,6 @@ const cDaiAddress = "0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643"

const safeInfinity = BN.from(2).pow(256).sub(1)

// impersonates a specific account
const impersonate = async (addr): Promise<Signer> => {
await network.provider.request({
method: "hardhat_impersonateAccount",
params: [addr],
})
return ethers.provider.getSigner(addr)
}

context("DAI and WBTC migration to integration that can claim stkAave", () => {
let governor: Signer
let deployer: Signer
Expand Down

0 comments on commit 2d626d8

Please sign in to comment.