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

Ratios V3 update & tests #46

Open
wants to merge 2 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
4 changes: 3 additions & 1 deletion deployed_contracts.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,6 @@ WarRatiosV2 ratios : 0xE40004395384455326c7a27A85204801C7f85F94
WarStaker staker : 0xA86c53AF3aadF20bE5d7a8136ACfdbC4B074758A
WarRatiosV2 ratios 2 : 0xCD7219cE5D6248c99693fA8239e680bd6C26cd48
WarRatiosV2 ratios 3 : 0x92fb36b6933756D93552623e1800D8C98F4831f9
WarRatiosV2 ratios 4 : 0xcf377332Ca848274b95bb162807851D96b51a4d3
WarRatiosV2 ratios 4 : 0xcf377332Ca848274b95bb162807851D96b51a4d3

WarRatiosV3 ratios : 0x8C1D862c1FA91E0880dEEf621C7A17f9087Bf50C
25 changes: 25 additions & 0 deletions script/DeployRatios3.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: Unlicensed
pragma solidity 0.8.16;

import "forge-std/Script.sol";
import "test/MainnetTest.sol";

// Infrastructure
import {WarRatiosV3} from "src/RatiosV3.sol";

contract Deployment is Script, MainnetTest {
WarRatiosV3 ratios;

function run() public {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(deployerPrivateKey);

deploy();

vm.stopBroadcast();
}

function deploy() public {
ratios = new WarRatiosV3();
}
}
116 changes: 116 additions & 0 deletions src/RatiosV3.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
//██████╗ █████╗ ██╗ █████╗ ██████╗ ██╗███╗ ██╗
//██╔══██╗██╔══██╗██║ ██╔══██╗██╔══██╗██║████╗ ██║
//██████╔╝███████║██║ ███████║██║ ██║██║██╔██╗ ██║
//██╔═══╝ ██╔══██║██║ ██╔══██║██║ ██║██║██║╚██╗██║
//██║ ██║ ██║███████╗██║ ██║██████╔╝██║██║ ╚████║
//╚═╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═════╝ ╚═╝╚═╝ ╚═══╝

pragma solidity 0.8.16;
//SPDX-License-Identifier: BUSL-1.1

import {IRatios} from "interfaces/IRatios.sol";
import {Errors} from "utils/Errors.sol";
import {Owner} from "utils/Owner.sol";

/**
* @title Warlord WAR minting ratios contract V3
* @author Paladin
* @notice Calculate the amounts of WAR to mint or burn
*/
contract WarRatiosV3 is IRatios, Owner {
/**
* @notice 1e18 scale
*/
uint256 private constant UNIT = 1e18;

bool public isFrozen;

/**
* @notice Amount of WAR to mint per token for each listed token
*/
mapping(address => uint256) public warPerToken;

event Frozen();
event Unfrozen();
event AddedTokens(address indexed token, uint256 warRatio);
event UpdatedTokens(address indexed token, uint256 warRatio);

/**
* @notice Returns the ratio for a given token
* @param token Address of the token
*/
function getTokenRatio(address token) external view returns (uint256) {
return warPerToken[token];
}

/**
* @notice Adds a new token and sets the ratio of WAR to mint per token
* @param token Address of the token
* @param warRatio Amount of WAR minted per token
*/
function addToken(address token, uint256 warRatio) external onlyOwner {
if (token == address(0)) revert Errors.ZeroAddress();
if (warRatio == 0) revert Errors.ZeroValue();
if (warPerToken[token] != 0) revert Errors.RatioAlreadySet();

warPerToken[token] = warRatio;

emit AddedTokens(token, warRatio);
}

function freezeRatios() external onlyOwner {
isFrozen = true;

emit Frozen();
}

function unfreezeRatios() external onlyOwner {
isFrozen = false;

emit Unfrozen();
}

/**
* @notice Updates the ratio of WAR to mint per token
* @param token Address of the token
* @param warRatio Amount of WAR minted per token
*/
function updateToken(address token, uint256 warRatio) external onlyOwner {
if (token == address(0)) revert Errors.ZeroAddress();
if (warRatio == 0) revert Errors.ZeroValue();
if (warPerToken[token] == 0) revert Errors.RatioNotSet();
if (!isFrozen) revert Errors.RatiosNotFrozen();

warPerToken[token] = warRatio;

emit UpdatedTokens(token, warRatio);
}

/**
* @notice Returns the amount of WAR to mint for a given amount of token
* @param token Address of the token
* @param amount Amount of token received
* @return mintAmount (uint256) : Amount to mint
*/
function getMintAmount(address token, uint256 amount) external view returns (uint256 mintAmount) {
if (token == address(0)) revert Errors.ZeroAddress();
if (amount == 0) revert Errors.ZeroValue();
if (isFrozen) revert Errors.RatiosFrozen();

mintAmount = amount * warPerToken[token] / UNIT;
}

/**
* @notice Returns the amount of token to redeem for a given amount of WAR burned
* @param token Address of the token
* @param burnAmount Amount of WAR to burn
* @return redeemAmount (uint256) : Redeem amount
*/
function getBurnAmount(address token, uint256 burnAmount) external view returns (uint256 redeemAmount) {
if (token == address(0)) revert Errors.ZeroAddress();
if (burnAmount == 0) revert Errors.ZeroValue();
if (isFrozen) revert Errors.RatiosFrozen();

redeemAmount = burnAmount * UNIT / warPerToken[token];
}
}
3 changes: 3 additions & 0 deletions src/utils/Errors.sol
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ library Errors {
error ZeroMintAmount();
error SupplyAlreadySet();
error RatioAlreadySet();
error RatioNotSet();
error RatiosFrozen();
error RatiosNotFrozen();

// Harvestable
error NotRewardToken();
Expand Down
41 changes: 41 additions & 0 deletions test/RatiosV3/AddToken.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// SPDX-License-Identifier: Unlicensed
pragma solidity 0.8.16;

import "./RatiosV3Test.sol";

contract AddToken is RatiosV3Test {
function testDefaultBehavior(address token, uint256 tokenRatio) public {
vm.assume(token != zero && token != address(cvx) && token != address(aura));
vm.assume(tokenRatio > 0);

vm.prank(admin);
ratiosV3.addToken(token, tokenRatio);
assertEq(ratiosV3.warPerToken(token), tokenRatio);
assertEq(ratiosV3.getTokenRatio(token), tokenRatio);
}

function testBaseTokens() public {
// Token already added in setup just need to check
assertGt(ratiosV3.warPerToken(address(aura)), 0);
assertGt(ratiosV3.warPerToken(address(cvx)), 0);
//assertEq(ratiosV3.warPerToken(address(cvx)), ratiosV3.warPerToken(address(aura)));
}

function testCantAddZeroAddress() public {
vm.expectRevert(Errors.ZeroAddress.selector);
vm.prank(admin);
ratiosV3.addToken(zero, 500e18);
}

function testCantAddZeroSupply() public {
vm.expectRevert(Errors.ZeroValue.selector);
vm.prank(admin);
ratiosV3.addToken(address(42), 0);
}

function testCantAddAlreadyExistingToken() public {
vm.expectRevert(Errors.RatioAlreadySet.selector);
vm.prank(admin);
ratiosV3.addToken(address(cvx), 50e18);
}
}
20 changes: 20 additions & 0 deletions test/RatiosV3/FreezeRatios.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: Unlicensed
pragma solidity 0.8.16;

import "./RatiosV3Test.sol";

contract FreezeRatios is RatiosV3Test {
function testDefaultBehavior() public {
assertEq(ratiosV3.isFrozen(), false);

vm.prank(admin);
ratiosV3.freezeRatios();

assertEq(ratiosV3.isFrozen(), true);
}

function testOnlyAdmin() public {
vm.expectRevert();
ratiosV3.freezeRatios();
}
}
58 changes: 58 additions & 0 deletions test/RatiosV3/GetMintAmount.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: Unlicensed
pragma solidity 0.8.16;

import "./RatiosV3Test.sol";

contract GetMintAmount is RatiosV3Test {
function _defaultBehavior(address token, uint256 maxSupply, uint256 amount) internal {
vm.assume(amount >= 1e4 && amount <= maxSupply);
uint256 mintAmount = ratiosV3.getMintAmount(address(token), amount);
assertGt(mintAmount, 0);

uint256 expectedMintAmount = amount * setWarPerToken[token] / UNIT;

assertEq(mintAmount, expectedMintAmount);
}

function testDefaultBehaviorWithAura(uint256 amount) public {
_defaultBehavior(address(aura), auraMaxSupply, amount);
}

function testDefaultBehaviorWithCvx(uint256 amount) public {
_defaultBehavior(address(cvx), cvxMaxSupply, amount);
}

function testPrecisionLoss(uint256 amount) public {
vm.assume(amount > 0 && amount < 1e4);

address token = makeAddr("otherToken");
vm.prank(admin);
ratiosV3.addToken(token, CVX_MINT_RATIO / 1e4);

assertEq(ratiosV3.getMintAmount(token, amount), 0);
}

function testZeroAddress(uint256 amount) public {
vm.assume(amount != 0);

vm.expectRevert(Errors.ZeroAddress.selector);
ratiosV3.getMintAmount(zero, amount);
}

function testZeroAmount(address token) public {
vm.assume(token != zero);

vm.expectRevert(Errors.ZeroValue.selector);
ratiosV3.getMintAmount(token, 0);
}

function testFrozen(address token) public {
vm.assume(token != zero);

vm.prank(admin);
ratiosV3.freezeRatios();

vm.expectRevert(Errors.RatiosFrozen.selector);
ratiosV3.getMintAmount(token, 10e18);
}
}
47 changes: 47 additions & 0 deletions test/RatiosV3/GetRedeemAmount.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// SPDX-License-Identifier: Unlicensed
pragma solidity 0.8.16;

import "./RatiosV3Test.sol";

contract GetRedeemAmount is RatiosV3Test {
function testDefaultBehavior(uint256 initialMintAmount) public {
address token = randomVlToken(initialMintAmount);
vm.assume(initialMintAmount > MINT_PRECISION_LOSS);
vm.assume(initialMintAmount <= (token == address(aura) ? AURA_MAX_SUPPLY : CVX_MAX_SUPPLY));

uint256 amountToBurn = ratiosV3.getMintAmount(token, initialMintAmount);
uint256 burnedAmount = ratiosV3.getBurnAmount(token, amountToBurn);

// Precision correction
initialMintAmount = initialMintAmount / MINT_PRECISION_LOSS * MINT_PRECISION_LOSS;
burnedAmount = burnedAmount / MINT_PRECISION_LOSS * MINT_PRECISION_LOSS;

assertEqDecimal(
burnedAmount, initialMintAmount, 18, "The burned amount should correspond to the initial amount used to mint"
);
}

function testZeroAddress(uint256 amount) public {
vm.assume(amount != 0);

vm.expectRevert(Errors.ZeroAddress.selector);
ratiosV3.getBurnAmount(zero, amount);
}

function testZeroAmount(address token) public {
vm.assume(token != zero);

vm.expectRevert(Errors.ZeroValue.selector);
ratiosV3.getBurnAmount(token, 0);
}

function testFrozen(address token) public {
vm.assume(token != zero);

vm.prank(admin);
ratiosV3.freezeRatios();

vm.expectRevert(Errors.RatiosFrozen.selector);
ratiosV3.getBurnAmount(token, 10e18);
}
}
31 changes: 31 additions & 0 deletions test/RatiosV3/RatiosV3Test.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: Unlicensed
pragma solidity 0.8.16;

import "../WarlordTest.sol";
import "src/RatiosV3.sol";

contract RatiosV3Test is WarlordTest {
uint256 constant UNIT = 1e18;
uint256 constant MINT_PRECISION_LOSS = 10; // Because AURA ratio is 0.5 (1 AURA mints 0.5 WAR)

uint256 constant cvxMaxSupply = 100_000_000e18;
uint256 constant auraMaxSupply = 100_000_000e18;

mapping(address => uint256) public setWarPerToken;

WarRatiosV3 public ratiosV3;

function setUp() public override {
MainnetTest.setUp();
fork();

setWarPerToken[address(cvx)] = CVX_MINT_RATIO;
setWarPerToken[address(aura)] = AURA_MINT_RATIO;

vm.startPrank(admin);
ratiosV3 = new WarRatiosV3();
ratiosV3.addToken(address(cvx), CVX_MINT_RATIO);
ratiosV3.addToken(address(aura), AURA_MINT_RATIO);
vm.stopPrank();
}
}
23 changes: 23 additions & 0 deletions test/RatiosV3/UnfreezeRatios.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: Unlicensed
pragma solidity 0.8.16;

import "./RatiosV3Test.sol";

contract FreezeRatios is RatiosV3Test {
function testDefaultBehavior() public {
vm.prank(admin);
ratiosV3.freezeRatios();

assertEq(ratiosV3.isFrozen(), true);

vm.prank(admin);
ratiosV3.unfreezeRatios();

assertEq(ratiosV3.isFrozen(), false);
}

function testOnlyAdmin() public {
vm.expectRevert();
ratiosV3.unfreezeRatios();
}
}
Loading