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: new kerosene DV #132

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
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
46 changes: 40 additions & 6 deletions src/core/VaultManagerV6.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {DyadXP} from "../staking/DyadXP.sol";
import {IVaultManagerV5} from "../interfaces/IVaultManagerV5.sol";
import {DyadHooks} from "./DyadHooks.sol";
import "../interfaces/IExtension.sol";
import {KeroseneValuer} from "../staking/KeroseneValuer.sol";
import {KerosineManager} from "../core/KerosineManager.sol";

import {FixedPointMathLib} from "@solmate/src/utils/FixedPointMathLib.sol";
import {ERC20} from "@solmate/src/tokens/ERC20.sol";
Expand Down Expand Up @@ -47,6 +49,9 @@ contract VaultManagerV6 is IVaultManagerV5, UUPSUpgradeable, OwnableUpgradeable
/// @notice Extensions authorized by a user for use on their notes
mapping(address user => EnumerableSet.AddressSet) private _authorizedExtensions;

KeroseneValuer public keroseneValuer;
KerosineManager public keroseneManager;

modifier isValidDNft(uint256 id) {
if (dNft.ownerOf(id) == address(0)) revert InvalidDNft();
_;
Expand All @@ -57,8 +62,13 @@ contract VaultManagerV6 is IVaultManagerV5, UUPSUpgradeable, OwnableUpgradeable
_disableInitializers();
}

function initialize() public reinitializer(6) {
// Nothing to initialize right now
function initialize(address _keroseneValuer, address _keroseneManager) public reinitializer(6) {
keroseneValuer = KeroseneValuer(_keroseneValuer);
keroseneManager = KerosineManager(_keroseneManager);
}

function setKeroseneValuer(address _newKeroseneValuer) external onlyOwner {
keroseneValuer = KeroseneValuer(_newKeroseneValuer);
}

/// @inheritdoc IVaultManagerV5
Expand Down Expand Up @@ -344,20 +354,44 @@ contract VaultManagerV6 is IVaultManagerV5, UUPSUpgradeable, OwnableUpgradeable
uint256 numberOfVaults = vaults[id].length();
vaultValues = new uint256[](numberOfVaults);

uint256 keroseneVaultIndex;
uint256 noteKeroseneAmount;

for (uint256 i = 0; i < numberOfVaults; i++) {
Vault vault = Vault(vaults[id].at(i));
if (vaultLicenser.isLicensed(address(vault))) {
uint256 value = vault.getUsdValue(id);
vaultValues[i] = value;
if (vaultLicenser.isKerosene(address(vault))) {
keroValue += value;
noteKeroseneAmount = vault.id2asset(id);
keroseneVaultIndex = i;
continue;
} else {
uint256 value = vault.getUsdValue(id);
vaultValues[i] = value;
exoValue += value;
}
}
}

mintedDyad = dyad.mintedDyad(id);
uint256 tvl;

address[] memory exoVaults = keroseneManager.getVaults();

uint256 numberOfExoVaults = exoVaults.length;
for (uint256 i = 0; i < numberOfExoVaults; i++) {
Vault vault = Vault(exoVaults[i]);
ERC20 asset = vault.asset();
tvl += asset.balanceOf(address(vault)) * vault.assetPrice() * 1e18 / (10 ** asset.decimals())
/ (10 ** vault.oracle().decimals());
}

Dyad dyadCache = dyad;

if (noteKeroseneAmount > 0) {
keroValue = (noteKeroseneAmount * keroseneValuer.deterministicValue(tvl, dyadCache.totalSupply())) / 1e8;
vaultValues[keroseneVaultIndex] = keroValue;
}

mintedDyad = dyadCache.mintedDyad(id);
uint256 totalValue = exoValue + keroValue;
cr = _collatRatio(mintedDyad, totalValue);

Expand Down
115 changes: 115 additions & 0 deletions src/staking/KeroseneValuer.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import {Owned} from "@solmate/src/auth/Owned.sol";
import {ERC20} from "@solmate/src/tokens/ERC20.sol";
import {FixedPointMathLib} from "solady/utils/FixedPointMathLib.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {Parameters} from "../params/Parameters.sol";
import {Kerosine} from "../staking/Kerosine.sol";
import {Dyad} from "../core/Dyad.sol";
import {Vault} from "../core/Vault.sol";

contract KeroseneValuer is Owned {
using EnumerableSet for EnumerableSet.AddressSet;
using FixedPointMathLib for uint256;

Kerosine public immutable KEROSINE;

uint64 public dyadMultiplierSnapshot = 1e12;
uint64 public targetDyadMultiplier = 1e12;

uint32 public dyadMultiplierSnapshotTimestamp;
uint32 public targetDyadMultiplierTimestamp;

EnumerableSet.AddressSet private _excludedAddresses;

event DyadMultiplierUpdated(uint64 previous, uint64 target, uint32 fromTimestamp, uint32 toTimestamp);

error TargetMultiplierTooSmall();

constructor(Kerosine _kerosine) Owned(0xDeD796De6a14E255487191963dEe436c45995813) {
KEROSINE = _kerosine;
_excludedAddresses.add(0xDeD796De6a14E255487191963dEe436c45995813); // Team Multisig
_excludedAddresses.add(0x3962f6585946823440d274aD7C719B02b49DE51E); // Sablier Linear Lockup
}

function setAddressExcluded(address _address, bool exclude) external onlyOwner {
if (exclude) {
_excludedAddresses.add(_address);
} else {
_excludedAddresses.remove(_address);
}
}

function setTargetDyadMultiplier(uint64 _targetMultiplier, uint32 _duration) external onlyOwner {
if (_targetMultiplier < 1e12) {
revert TargetMultiplierTooSmall();
}

uint64 previousMultiplier = _getDyadSupplyMultiplier();

dyadMultiplierSnapshot = previousMultiplier;
targetDyadMultiplier = _targetMultiplier;
dyadMultiplierSnapshotTimestamp = uint32(block.timestamp);
targetDyadMultiplierTimestamp = uint32(block.timestamp) + _duration;

emit DyadMultiplierUpdated(
previousMultiplier, _targetMultiplier, uint32(block.timestamp), uint32(block.timestamp) + _duration
);
}

function currentDyadMultiplier() external view returns (uint64) {
return _getDyadSupplyMultiplier();
}

function isExcludedAddress(address _address) external view returns (bool) {
return _excludedAddresses.contains(_address);
}

function excludedAddresses() external view returns (address[] memory) {
return _excludedAddresses.values();
}

function deterministicValue(uint256 _tvl, uint256 _dyadTotalSupply) external view returns (uint256) {
uint256 dyadMultiplier = _getDyadSupplyMultiplier();

uint256 normalizedSupply = _dyadTotalSupply.mulDiv(dyadMultiplier, 1e12);

if (normalizedSupply >= _tvl) {
return 0;
}

uint256 adjustedKerosineSupply = KEROSINE.totalSupply();
uint256 excludedAddressLength = _excludedAddresses.length();
for (uint256 i = 0; i < excludedAddressLength; ++i) {
adjustedKerosineSupply -= KEROSINE.balanceOf(_excludedAddresses.at(i));
}

return (_tvl - normalizedSupply).mulDiv(1e8, adjustedKerosineSupply);
}

function _getDyadSupplyMultiplier() internal view returns (uint64) {
uint32 targetTimestamp = targetDyadMultiplierTimestamp;
if (block.timestamp >= targetTimestamp) {
return targetDyadMultiplier;
}

uint64 target = targetDyadMultiplier;
uint64 snapshot = dyadMultiplierSnapshot;
uint32 snapshotTimestamp = dyadMultiplierSnapshotTimestamp;

uint32 timeDelta = targetTimestamp - snapshotTimestamp;
uint64 multiplierDelta = target > snapshot ? target - snapshot : snapshot - target;

uint64 ratePerSecond = multiplierDelta / timeDelta;

uint32 secondsPassed = uint32(block.timestamp) - snapshotTimestamp;

if (target > snapshot) {
return snapshot + (secondsPassed * ratePerSecond);
}

return snapshot - (secondsPassed * ratePerSecond);
}
}
174 changes: 174 additions & 0 deletions test/KeroseneValuer.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import {Test} from "forge-std/Test.sol";
import {KeroseneValuer} from "../src/staking/KeroseneValuer.sol";
import {Kerosine} from "../src/staking/Kerosine.sol";

contract KeroseneValuerTest is Test {
address OWNER;
address ALICE = makeAddr("ALICE");

KeroseneValuer valuer;
Kerosine kerosene;

function setUp() external {
kerosene = new Kerosine();

valuer = new KeroseneValuer(kerosene);

OWNER = valuer.owner();
}

function test_get_multiplier() external view {
uint64 multiplier = valuer.currentDyadMultiplier();

// Default value
assertEq(multiplier, 1e12);
}

function test_set_multiplier() external {
uint64 newMultiplier = 1.25e12;
uint32 duration = 2 hours;

vm.prank(OWNER);
valuer.setTargetDyadMultiplier(newMultiplier, duration);

assertEq(valuer.dyadMultiplierSnapshot(), 1e12);
assertEq(valuer.dyadMultiplierSnapshotTimestamp(), vm.getBlockTimestamp());
assertEq(valuer.targetDyadMultiplier(), newMultiplier);
assertEq(valuer.targetDyadMultiplierTimestamp(), vm.getBlockTimestamp() + duration);
// Current multiplier stays the same right after update
assertEq(valuer.currentDyadMultiplier(), 1e12);
}

function test_multiplier_increase() external {
uint64 newMultiplier = 2e12;
uint32 duration = 10 seconds;

uint64 increasePerSecond = (newMultiplier - valuer.currentDyadMultiplier()) / duration;

vm.prank(OWNER);
valuer.setTargetDyadMultiplier(newMultiplier, duration);

uint64 multiplierSnapshot = valuer.dyadMultiplierSnapshot();

// after 1 second
vm.warp(vm.getBlockTimestamp() + 1 seconds);
assertEq(valuer.currentDyadMultiplier(), multiplierSnapshot + increasePerSecond);

// after 8 seconds
vm.warp(vm.getBlockTimestamp() + 7 seconds);
assertEq(valuer.currentDyadMultiplier(), multiplierSnapshot + 8 * increasePerSecond);

// after 10 seconds
vm.warp(vm.getBlockTimestamp() + 2 seconds);
assertEq(valuer.currentDyadMultiplier(), newMultiplier);

// after some time
vm.warp(vm.getBlockTimestamp() + 1 hours);
assertEq(valuer.currentDyadMultiplier(), newMultiplier);
}

function test_multiplier_decrease() external {
vm.prank(OWNER);
valuer.setTargetDyadMultiplier(2e12, 0);

uint64 newMultiplier = 1.2e12;
uint32 duration = 10 seconds;

uint64 decreasePerSecond = (valuer.currentDyadMultiplier() - newMultiplier) / duration;

vm.prank(OWNER);
valuer.setTargetDyadMultiplier(newMultiplier, duration);

uint64 multiplierSnapshot = valuer.dyadMultiplierSnapshot();

// after 1 second
vm.warp(vm.getBlockTimestamp() + 1 seconds);
assertEq(valuer.currentDyadMultiplier(), multiplierSnapshot - decreasePerSecond);

// after 8 seconds
vm.warp(vm.getBlockTimestamp() + 7 seconds);
assertEq(valuer.currentDyadMultiplier(), multiplierSnapshot - 8 * decreasePerSecond);

// after 10 seconds
vm.warp(vm.getBlockTimestamp() + 2 seconds);
assertEq(valuer.currentDyadMultiplier(), newMultiplier);

// after some time
vm.warp(vm.getBlockTimestamp() + 1 hours);
assertEq(valuer.currentDyadMultiplier(), newMultiplier);
}

function test_kerosine_deterministic_value() external {
uint64 multiplier = 1.25e12;
vm.prank(OWNER);
valuer.setTargetDyadMultiplier(multiplier, 0);
uint256 dyadSupply = 100_000_000e18;

uint96[25] memory systemTvls = [
1_000_000_000e18,
950_000_000e18,
900_000_000e18,
850_000_000e18,
800_000_000e18,
750_000_000e18,
700_000_000e18,
650_000_000e18,
600_000_000e18,
550_000_000e18,
500_000_000e18,
450_000_000e18,
400_000_000e18,
350_000_000e18,
300_000_000e18,
250_000_000e18,
200_000_000e18,
150_000_000e18,
140_000_000e18,
130_000_000e18,
127_500_000e18,
125_000_000e18,
122_500_000e18,
120_000_000e18,
100_000_000e18
];

uint32[25] memory expectedPrices = [
0.875e8,
0.825e8,
0.775e8,
0.725e8,
0.675e8,
0.625e8,
0.575e8,
0.525e8,
0.475e8,
0.425e8,
0.375e8,
0.325e8,
0.275e8,
0.225e8,
0.175e8,
0.125e8,
0.075e8,
0.025e8,
0.015e8,
0.005e8,
0.0025e8,
0.0,
0.0,
0.0,
0.0
];

for (uint256 i; i < systemTvls.length; i++) {
uint256 expectedPrice = expectedPrices[i];

uint256 deterministicValue = valuer.deterministicValue(systemTvls[i], dyadSupply);

assertEq(deterministicValue, expectedPrice);
}
}
}
Loading
Loading