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

Mainnet Deploy 58 - New Vault Value Checker #1435

Merged
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
108 changes: 108 additions & 0 deletions brownie/abi/OETHVaultValueChecker.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
[
{
"inputs": [
{
"internalType": "address",
"name": "_vault",
"type": "address"
},
{
"internalType": "address",
"name": "_ousd",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [
{
"internalType": "int256",
"name": "expectedProfit",
"type": "int256"
},
{
"internalType": "int256",
"name": "profitVariance",
"type": "int256"
},
{
"internalType": "int256",
"name": "expectedVaultChange",
"type": "int256"
},
{
"internalType": "int256",
"name": "vaultChangeVariance",
"type": "int256"
}
],
"name": "checkDelta",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "ousd",
"outputs": [
{
"internalType": "contract IOUSD",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "snapshots",
"outputs": [
{
"internalType": "uint256",
"name": "vaultValue",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "totalSupply",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "time",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "takeSnapshot",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "vault",
"outputs": [
{
"internalType": "contract IVault",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
}
]
61 changes: 43 additions & 18 deletions contracts/contracts/strategies/VaultValueChecker.sol
Original file line number Diff line number Diff line change
@@ -1,51 +1,70 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import { VaultCore } from "../vault/VaultCore.sol";
import { OUSD } from "../token/OUSD.sol";
import { IOUSD } from "../interfaces/IOUSD.sol";
import { IVault } from "../interfaces/IVault.sol";

contract VaultValueChecker {
IVault public immutable vault;
IOUSD public immutable ousd;
// Snapshot expiration time in seconds.
// Used to prevent accidental use of an old snapshot, but
// is not zero to allow easy testing of strategist actions in fork testing
uint256 constant SNAPSHOT_EXPIRES = 5 * 60;

struct Snapshot {
uint256 vaultValue;
uint256 totalSupply;
uint256 time;
}

VaultCore public immutable vault;
OUSD public immutable ousd;

// By doing per user snapshots, we prevent a reentrancy attack
// from a third party that updates the snapshot in the middle
// of an allocation process

mapping(address => Snapshot) public snapshots;

constructor(address _vault, address _ousd) {
vault = VaultCore(payable(_vault));
ousd = OUSD(_ousd);
vault = IVault(_vault);
ousd = IOUSD(_ousd);
}

function takeSnapshot() external {
snapshots[msg.sender] = Snapshot({
vaultValue: vault.totalValue(),
totalSupply: ousd.totalSupply()
totalSupply: ousd.totalSupply(),
time: block.timestamp
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no storage slot issues here 👍

});
}

function checkDelta(
int256 lowValueDelta,
int256 highValueDelta,
int256 lowSupplyDelta,
int256 highSupplyDelta
int256 expectedProfit,
int256 profitVariance,
int256 expectedVaultChange,
int256 vaultChangeVariance
) external {
// Intentionaly not view so that this method shows up in TX builders
Snapshot memory snapshot = snapshots[msg.sender];
int256 valueChange = toInt256(vault.totalValue()) -
int256 vaultChange = toInt256(vault.totalValue()) -
toInt256(snapshot.vaultValue);
int256 supplyChange = toInt256(ousd.totalSupply()) -
toInt256(snapshot.totalSupply);
int256 profit = vaultChange - supplyChange;

require(valueChange >= lowValueDelta, "Vault value too low");
require(valueChange <= highValueDelta, "Vault value too high");
require(supplyChange >= lowSupplyDelta, "OUSD supply too low");
require(supplyChange <= highSupplyDelta, "OUSD supply too high");
require(
snapshot.time >= block.timestamp - SNAPSHOT_EXPIRES,
"Snapshot too old"
);
require(snapshot.time <= block.timestamp, "Snapshot too new");
require(profit >= expectedProfit - profitVariance, "Profit too low");
require(profit <= expectedProfit + profitVariance, "Profit too high");
require(
vaultChange >= expectedVaultChange - vaultChangeVariance,
"Vault value change too low"
);
require(
vaultChange <= expectedVaultChange + vaultChangeVariance,
"Vault value change too high"
);
}

function toInt256(uint256 value) internal pure returns (int256) {
Expand All @@ -58,3 +77,9 @@ contract VaultValueChecker {
return int256(value);
}
}

contract OETHVaultValueChecker is VaultValueChecker {
constructor(address _vault, address _ousd)
VaultValueChecker(_vault, _ousd)
{}
}
35 changes: 35 additions & 0 deletions contracts/deploy/058_oeth_vault_value_checker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
const { deploymentWithProposal } = require("../utils/deploy");

module.exports = deploymentWithProposal(
{ deployName: "058_oeth_vault_value_checker" },
async ({
assetAddresses,
deployWithConfirmation,
ethers,
getTxOpts,
withConfirmation,
}) => {
// Current contracts
const cOETHVaultProxy = await ethers.getContract("OETHVaultProxy");
const cOETHProxy = await ethers.getContract("OETHProxy");

// Deployer Actions
// ----------------

// 1. Deploy new vault value checker
const dVaultValueChecker = await deployWithConfirmation(
"OETHVaultValueChecker",
[cOETHVaultProxy.address, cOETHProxy.address],
undefined,
true // Incompatibable storage layout
);
const vaultValueChecker = await ethers.getContract("OETHVaultValueChecker");

// Governance Actions
// ----------------
return {
name: "VaultValueChecker Deploy",
actions: [], // No actions
};
}
);
3 changes: 2 additions & 1 deletion contracts/deployments/mainnet/.migrations.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,6 @@
"048_deposit_withdraw_tooling": 1675100084,
"053_oeth": 1681746345,
"054_woeth": 1681746545,
"056_oeth_zapper_again": 1682535005
"056_oeth_zapper_again": 1682535005,
"058_oeth_vault_value_checker": 1683419766
}
207 changes: 207 additions & 0 deletions contracts/deployments/mainnet/OETHVaultValueChecker.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"language": "Solidity",
"sources": {
"contracts/interfaces/IOUSD.sol": {
"content": "pragma solidity ^0.8.0;\n\ninterface IOUSD {\n event Approval(\n address indexed owner,\n address indexed spender,\n uint256 value\n );\n event GovernorshipTransferred(\n address indexed previousGovernor,\n address indexed newGovernor\n );\n event PendingGovernorshipTransfer(\n address indexed previousGovernor,\n address indexed newGovernor\n );\n event TotalSupplyUpdatedHighres(\n uint256 totalSupply,\n uint256 rebasingCredits,\n uint256 rebasingCreditsPerToken\n );\n event Transfer(address indexed from, address indexed to, uint256 value);\n\n function _totalSupply() external view returns (uint256);\n\n function allowance(address _owner, address _spender)\n external\n view\n returns (uint256);\n\n function approve(address _spender, uint256 _value) external returns (bool);\n\n function balanceOf(address _account) external view returns (uint256);\n\n function burn(address account, uint256 amount) external;\n\n function changeSupply(uint256 _newTotalSupply) external;\n\n function claimGovernance() external;\n\n function creditsBalanceOf(address _account)\n external\n view\n returns (uint256, uint256);\n\n function creditsBalanceOfHighres(address _account)\n external\n view\n returns (\n uint256,\n uint256,\n bool\n );\n\n function decimals() external view returns (uint8);\n\n function decreaseAllowance(address _spender, uint256 _subtractedValue)\n external\n returns (bool);\n\n function governor() external view returns (address);\n\n function increaseAllowance(address _spender, uint256 _addedValue)\n external\n returns (bool);\n\n function initialize(\n string memory _nameArg,\n string memory _symbolArg,\n address _vaultAddress\n ) external;\n\n function isGovernor() external view returns (bool);\n\n function isUpgraded(address) external view returns (uint256);\n\n function mint(address _account, uint256 _amount) external;\n\n function name() external view returns (string memory);\n\n function nonRebasingCreditsPerToken(address)\n external\n view\n returns (uint256);\n\n function nonRebasingSupply() external view returns (uint256);\n\n function rebaseOptIn() external;\n\n function rebaseOptOut() external;\n\n function rebaseState(address) external view returns (uint8);\n\n function rebasingCredits() external view returns (uint256);\n\n function rebasingCreditsHighres() external view returns (uint256);\n\n function rebasingCreditsPerToken() external view returns (uint256);\n\n function rebasingCreditsPerTokenHighres() external view returns (uint256);\n\n function symbol() external view returns (string memory);\n\n function totalSupply() external view returns (uint256);\n\n function transfer(address _to, uint256 _value) external returns (bool);\n\n function transferFrom(\n address _from,\n address _to,\n uint256 _value\n ) external returns (bool);\n\n function transferGovernance(address _newGovernor) external;\n\n function vaultAddress() external view returns (address);\n}\n"
},
"contracts/interfaces/IVault.sol": {
"content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\ninterface IVault {\n event AssetSupported(address _asset);\n event AssetDefaultStrategyUpdated(address _asset, address _strategy);\n event AssetAllocated(address _asset, address _strategy, uint256 _amount);\n event StrategyApproved(address _addr);\n event StrategyRemoved(address _addr);\n event Mint(address _addr, uint256 _value);\n event Redeem(address _addr, uint256 _value);\n event CapitalPaused();\n event CapitalUnpaused();\n event RebasePaused();\n event RebaseUnpaused();\n event VaultBufferUpdated(uint256 _vaultBuffer);\n event RedeemFeeUpdated(uint256 _redeemFeeBps);\n event PriceProviderUpdated(address _priceProvider);\n event AllocateThresholdUpdated(uint256 _threshold);\n event RebaseThresholdUpdated(uint256 _threshold);\n event StrategistUpdated(address _address);\n event MaxSupplyDiffChanged(uint256 maxSupplyDiff);\n event YieldDistribution(address _to, uint256 _yield, uint256 _fee);\n event TrusteeFeeBpsChanged(uint256 _basis);\n event TrusteeAddressChanged(address _address);\n\n // Governable.sol\n function transferGovernance(address _newGovernor) external;\n\n function claimGovernance() external;\n\n function governor() external view returns (address);\n\n // VaultAdmin.sol\n function setPriceProvider(address _priceProvider) external;\n\n function priceProvider() external view returns (address);\n\n function setRedeemFeeBps(uint256 _redeemFeeBps) external;\n\n function redeemFeeBps() external view returns (uint256);\n\n function setVaultBuffer(uint256 _vaultBuffer) external;\n\n function vaultBuffer() external view returns (uint256);\n\n function setAutoAllocateThreshold(uint256 _threshold) external;\n\n function autoAllocateThreshold() external view returns (uint256);\n\n function setRebaseThreshold(uint256 _threshold) external;\n\n function rebaseThreshold() external view returns (uint256);\n\n function setStrategistAddr(address _address) external;\n\n function strategistAddr() external view returns (address);\n\n function setMaxSupplyDiff(uint256 _maxSupplyDiff) external;\n\n function maxSupplyDiff() external view returns (uint256);\n\n function setTrusteeAddress(address _address) external;\n\n function trusteeAddress() external view returns (address);\n\n function setTrusteeFeeBps(uint256 _basis) external;\n\n function trusteeFeeBps() external view returns (uint256);\n\n function ousdMetaStrategy() external view returns (address);\n\n function supportAsset(address _asset, uint8 _supportsAsset) external;\n\n function approveStrategy(address _addr) external;\n\n function removeStrategy(address _addr) external;\n\n function setAssetDefaultStrategy(address _asset, address _strategy)\n external;\n\n function assetDefaultStrategies(address _asset)\n external\n view\n returns (address);\n\n function pauseRebase() external;\n\n function unpauseRebase() external;\n\n function rebasePaused() external view returns (bool);\n\n function pauseCapital() external;\n\n function unpauseCapital() external;\n\n function capitalPaused() external view returns (bool);\n\n function transferToken(address _asset, uint256 _amount) external;\n\n function priceUnitMint(address asset) external view returns (uint256);\n\n function priceUnitRedeem(address asset) external view returns (uint256);\n\n function withdrawAllFromStrategy(address _strategyAddr) external;\n\n function withdrawAllFromStrategies() external;\n\n function reallocate(\n address _strategyFromAddress,\n address _strategyToAddress,\n address[] calldata _assets,\n uint256[] calldata _amounts\n ) external;\n\n function withdrawFromStrategy(\n address _strategyFromAddress,\n address[] calldata _assets,\n uint256[] calldata _amounts\n ) external;\n\n function depositToStrategy(\n address _strategyToAddress,\n address[] calldata _assets,\n uint256[] calldata _amounts\n ) external;\n\n // VaultCore.sol\n function mint(\n address _asset,\n uint256 _amount,\n uint256 _minimumOusdAmount\n ) external;\n\n function mintForStrategy(uint256 _amount) external;\n\n function redeem(uint256 _amount, uint256 _minimumUnitAmount) external;\n\n function burnForStrategy(uint256 _amount) external;\n\n function redeemAll(uint256 _minimumUnitAmount) external;\n\n function allocate() external;\n\n function rebase() external;\n\n function totalValue() external view returns (uint256 value);\n\n function checkBalance(address _asset) external view returns (uint256);\n\n function calculateRedeemOutputs(uint256 _amount)\n external\n view\n returns (uint256[] memory);\n\n function getAssetCount() external view returns (uint256);\n\n function getAllAssets() external view returns (address[] memory);\n\n function getStrategyCount() external view returns (uint256);\n\n function getAllStrategies() external view returns (address[] memory);\n\n function isSupportedAsset(address _asset) external view returns (bool);\n\n function netOusdMintForStrategyThreshold() external view returns (uint256);\n\n function setOusdMetaStrategy(address _ousdMetaStrategy) external;\n\n function setNetOusdMintForStrategyThreshold(uint256 _threshold) external;\n\n function netOusdMintedForStrategy() external view returns (int256);\n}\n"
},
"contracts/strategies/VaultValueChecker.sol": {
"content": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.0;\n\nimport { IOUSD } from \"../interfaces/IOUSD.sol\";\nimport { IVault } from \"../interfaces/IVault.sol\";\n\ncontract VaultValueChecker {\n IVault public immutable vault;\n IOUSD public immutable ousd;\n // Snapshot expiration time in seconds.\n // Used to prevent accidental use of an old snapshot, but\n // is not zero to allow easy testing of strategist actions in fork testing\n uint256 constant SNAPSHOT_EXPIRES = 5 * 60;\n\n struct Snapshot {\n uint256 vaultValue;\n uint256 totalSupply;\n uint256 time;\n }\n // By doing per user snapshots, we prevent a reentrancy attack\n // from a third party that updates the snapshot in the middle\n // of an allocation process\n\n mapping(address => Snapshot) public snapshots;\n\n constructor(address _vault, address _ousd) {\n vault = IVault(_vault);\n ousd = IOUSD(_ousd);\n }\n\n function takeSnapshot() external {\n snapshots[msg.sender] = Snapshot({\n vaultValue: vault.totalValue(),\n totalSupply: ousd.totalSupply(),\n time: block.timestamp\n });\n }\n\n function checkDelta(\n int256 expectedProfit,\n int256 profitVariance,\n int256 expectedVaultChange,\n int256 vaultChangeVariance\n ) external {\n // Intentionaly not view so that this method shows up in TX builders\n Snapshot memory snapshot = snapshots[msg.sender];\n int256 vaultChange = toInt256(vault.totalValue()) -\n toInt256(snapshot.vaultValue);\n int256 supplyChange = toInt256(ousd.totalSupply()) -\n toInt256(snapshot.totalSupply);\n int256 profit = vaultChange - supplyChange;\n\n require(\n snapshot.time >= block.timestamp - SNAPSHOT_EXPIRES,\n \"Snapshot too old\"\n );\n require(snapshot.time <= block.timestamp, \"Snapshot too new\");\n require(profit >= expectedProfit - profitVariance, \"Profit too low\");\n require(profit <= expectedProfit + profitVariance, \"Profit too high\");\n require(\n vaultChange >= expectedVaultChange - vaultChangeVariance,\n \"Vault value change too low\"\n );\n require(\n vaultChange <= expectedVaultChange + vaultChangeVariance,\n \"Vault value change too high\"\n );\n }\n\n function toInt256(uint256 value) internal pure returns (int256) {\n // From openzeppelin math/SafeCast.sol\n // Note: Unsafe cast below is okay because `type(int256).max` is guaranteed to be positive\n require(\n value <= uint256(type(int256).max),\n \"SafeCast: value doesn't fit in an int256\"\n );\n return int256(value);\n }\n}\n\ncontract OETHVaultValueChecker is VaultValueChecker {\n constructor(address _vault, address _ousd)\n VaultValueChecker(_vault, _ousd)\n {}\n}\n"
}
},
"settings": {
"optimizer": {
"enabled": true,
"runs": 200
},
"outputSelection": {
"*": {
"*": [
"abi",
"evm.bytecode",
"evm.deployedBytecode",
"evm.methodIdentifiers",
"metadata",
"devdoc",
"userdoc",
"storageLayout",
"evm.gasEstimates"
],
"": [
"ast"
]
}
},
"metadata": {
"useLiteralContent": true
}
}
}
Loading