Skip to content
Open

V2 #3

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
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
MAINNET_RPC_URL=
SEPOLIA_RPC_URL=
ETHERSCAN_API_KEY=
PRIVATE_KEY=
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "lib/solady"]
path = lib/solady
url = https://github.com/vectorized/solady
108 changes: 108 additions & 0 deletions broadcast/AuctionMainnetDeployer.s.sol/1/run-1770636532051.json

Large diffs are not rendered by default.

108 changes: 108 additions & 0 deletions broadcast/AuctionMainnetDeployer.s.sol/1/run-latest.json

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions foundry.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"lib/solady": {
"tag": {
"name": "v0.1.26",
"rev": "acd959aa4bd04720d640bf4e6a5c71037510cc4b"
}
}
}
1 change: 1 addition & 0 deletions lib/solady
Submodule solady added at acd959
Original file line number Diff line number Diff line change
Expand Up @@ -15,27 +15,27 @@ contract AuctionMainnetDeployerScript is Script {
address handler = address(0);

address gov = 0x926dF14a23BE491164dCF93f4c468A50ef659D5B;
address fedChair = 0x8F97cCA30Dbe80e7a8B462F1dD1a51C32accDfC8;
address dola = 0x865377367054516e17014CcdED1e7d814EDC9ce4;
address twg = 0x9D5Df30F475CEA915b1ed4C0CCa59255C897b61B;
address asset = 0x41D5D79431A913C4aE7d69a668ecdfE5fF9DFB68;
address dbr = 0xAD038Eb671c44b853887A7E32528FaB35dC5D710;

// 5:1 ratio, implying a 20c DBR starting price
uint dolaReserve = 500_000 * 1e18;
uint dbrReserve = dolaReserve * 5;
// 400:1 ratio, implying a 5c DBR starting price ($20 INV / 400 = $0.05 DBR)
uint assetReserve = 1_250 * 1e18;
uint dbrReserve = assetReserve * 400; // 500_000 DBR

Auction auction = new Auction(
gov,
fedChair,
twg,
dbr,
dola,
asset,
handler,
dolaReserve,
assetReserve,
dbrReserve
);

new Helper(
address(auction),
address(dola)
address(asset)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,27 @@
pragma solidity 0.8.21;

import {Script, console2} from "forge-std/Script.sol";
import {SaleHandler} from "../src/SaleHandler.sol";
import {DolaSaleHandler} from "../src/DolaSaleHandler.sol";
import {Auction} from "../src/Auction.sol";
import {Helper} from "../src/Helper.sol";

contract HandlerMainnetDeployerScript is Script {
contract DolaSaleHandlerMainnetDeployerScript is Script {
function setUp() public {}

function run() public {
vm.startBroadcast();

address gov = 0x926dF14a23BE491164dCF93f4c468A50ef659D5B;
address beneficiary = 0x9D5Df30F475CEA915b1ed4C0CCa59255C897b61B;
address anDola = 0x7Fcb7DAC61eE35b3D4a51117A7c58D53f0a8a670;
address dola = 0x865377367054516e17014CcdED1e7d814EDC9ce4;
address asset = 0x865377367054516e17014CcdED1e7d814EDC9ce4;
address borrower1 = 0xf508c58ce37ce40a40997C715075172691F92e2D;
address borrower2 = 0xeA0c959BBb7476DDD6cD4204bDee82b790AA1562;

// 30% minimum goes to repayments, 70% goes to beneficiary
uint minRepayBps = 3000;

new SaleHandler(
gov,
beneficiary,
minRepayBps,
dola,
DolaSaleHandler handler = new DolaSaleHandler(
asset,
anDola,
borrower1,
borrower2
);

}
}
Empty file removed sh/HandlerMainnetDeployer.sh
Empty file.
12 changes: 12 additions & 0 deletions sh/deploy-mainnet.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/bin/bash
source .env

read -p "Enter keystore account name: " KEYSTORE_NAME

forge script script/AuctionMainnetDeployer.s.sol:AuctionMainnetDeployerScript \
--rpc-url "$MAINNET_RPC_URL" \
--account "$KEYSTORE_NAME" \
--broadcast \
--verify \
--etherscan-api-key "$ETHERSCAN_API_KEY" \
-vvvv
12 changes: 12 additions & 0 deletions sh/deploy-sepolia.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/bin/bash
source .env

read -p "Enter keystore account name: " KEYSTORE_NAME

forge script script/AuctionMainnetDeployer.s.sol:AuctionMainnetDeployerScript \
--rpc-url "$SEPOLIA_RPC_URL" \
--account "$KEYSTORE_NAME" \
--broadcast \
--verify \
--etherscan-api-key "$ETHERSCAN_API_KEY" \
-vvvv
110 changes: 74 additions & 36 deletions src/Auction.sol
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.21;

import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";

interface IERC20 {
function transfer(address,uint) external returns (bool);
function transferFrom(address,address,uint) external returns (bool);
function balanceOf(address) external view returns (uint);
}

Expand All @@ -18,39 +18,42 @@ interface ISaleHandler {

contract Auction {

using SafeTransferLib for address;

address public gov;
address public operator;
IDBR public immutable dbr;
IERC20 public immutable dola;
IERC20 public immutable asset;
ISaleHandler public saleHandler;
uint public dolaReserve;
uint public assetReserve;
uint public dbrReserve;
uint public dbrRatePerYear;
uint public maxDbrRatePerYear;
uint public minDbrRatePerYear;
uint public maxDbrRatePerYear = type(uint).max;
uint public lastUpdate;

constructor (
address _gov,
address _operator,
address _dbr,
address _dola,
address _asset,
address handler,
uint _dolaReserve,
uint _assetReserve,
uint _dbrReserve
) {
require(_dolaReserve > 0, "Dola reserve must be positive");
require(_assetReserve > 0, "Asset reserve must be positive");
require(_dbrReserve > 0, "DBR reserve must be positive");
gov = _gov;
operator = _operator;
dbr = IDBR(_dbr);
dola = IERC20(_dola);
asset = IERC20(_asset);
saleHandler = ISaleHandler(handler);
dolaReserve = _dolaReserve;
assetReserve = _assetReserve;
dbrReserve = _dbrReserve;
}

modifier updateReserves {
(dolaReserve, dbrReserve) = getCurrentReserves();
(assetReserve, dbrReserve) = getCurrentReserves();
lastUpdate = block.timestamp;
_;
}
Expand All @@ -65,15 +68,15 @@ contract Auction {
_;
}

function getCurrentReserves() public view returns (uint _dolaReserve, uint _dbrReserve) {
function getCurrentReserves() public view returns (uint _assetReserve, uint _dbrReserve) {
uint timeElapsed = block.timestamp - lastUpdate;
if(timeElapsed > 0) {
uint K = dolaReserve * dbrReserve;
uint K = assetReserve * dbrReserve;
uint DbrsIn = timeElapsed * dbrRatePerYear / 365 days;
_dbrReserve = dbrReserve + DbrsIn;
_dolaReserve = K / _dbrReserve;
_assetReserve = K / _dbrReserve;
} else {
_dolaReserve = dolaReserve;
_assetReserve = assetReserve;
_dbrReserve = dbrReserve;
}
}
Expand All @@ -83,6 +86,7 @@ contract Auction {
function setSaleHandler(address _saleHandler) external onlyGov { saleHandler = ISaleHandler(_saleHandler); }

function setMaxDbrRatePerYear(uint _maxRate) external onlyGov updateReserves {
require(_maxRate >= minDbrRatePerYear, "Max below min");
maxDbrRatePerYear = _maxRate;
emit MaxRateUpdate(_maxRate);
if(dbrRatePerYear > _maxRate) {
Expand All @@ -91,59 +95,93 @@ contract Auction {
}
}

function setMinDbrRatePerYear(uint _minRate) external onlyGov updateReserves {
require(_minRate <= maxDbrRatePerYear, "Min above max");
minDbrRatePerYear = _minRate;
emit MinRateUpdate(_minRate);
if(dbrRatePerYear < _minRate) {
dbrRatePerYear = _minRate;
emit RateUpdate(_minRate);
}
}

function setDbrRatePerYear(uint _rate) external onlyGovOrOperator updateReserves {
require(_rate <= maxDbrRatePerYear, "Rate exceeds max");
require(_rate >= minDbrRatePerYear, "Rate below min");
dbrRatePerYear = _rate;
emit RateUpdate(_rate);
}

function setDolaReserve(uint _dolaReserve) external onlyGov updateReserves {
require(_dolaReserve > 0, "Dola reserve must be positive");
uint K = dolaReserve * dbrReserve;
dolaReserve = _dolaReserve;
dbrReserve = K / _dolaReserve;
// changes K to preserve the ratio (price)
function setAssetReserve(uint _assetReserve) external onlyGov updateReserves {
require(_assetReserve > 0, "Asset reserve must be positive");
uint newDbrReserve = _assetReserve * dbrReserve / assetReserve;
require(newDbrReserve > 0, "Resulting DBR reserve must be positive");
assetReserve = _assetReserve;
dbrReserve = newDbrReserve;
emit SetAssetReserve(_assetReserve, newDbrReserve);
}

// changes K to preserve the ratio (price)
function setDbrReserve(uint _dbrReserve) external onlyGov updateReserves {
require(_dbrReserve > 0, "DBR reserve must be positive");
uint K = dolaReserve * dbrReserve;
uint newAssetReserve = _dbrReserve * assetReserve / dbrReserve;
require(newAssetReserve > 0, "Resulting asset reserve must be positive");
dbrReserve = _dbrReserve;
dolaReserve = K / _dbrReserve;
assetReserve = newAssetReserve;
emit SetDbrReserve(_dbrReserve, newAssetReserve);
}

function overrideReserves(uint _dbrReserve, uint _dolaReserve) external onlyGov {
require(_dolaReserve > 0, "Dola reserve must be positive");
function overrideReserves(uint _dbrReserve, uint _assetReserve) external onlyGov {
require(_assetReserve > 0, "Asset reserve must be positive");
require(_dbrReserve > 0, "DBR reserve must be positive");
dolaReserve = _dolaReserve;
assetReserve = _assetReserve;
dbrReserve = _dbrReserve;
lastUpdate = block.timestamp;
emit OverrideReserves(_assetReserve, _dbrReserve);
}

function buyDBR(uint exactDolaIn, uint exactDbrOut, address to) external updateReserves {
uint K = dolaReserve * dbrReserve;
dolaReserve += exactDolaIn;
function buyDBR(uint exactAssetIn, uint exactDbrOut, address to) external updateReserves {
uint K = assetReserve * dbrReserve;
assetReserve += exactAssetIn;
dbrReserve -= exactDbrOut;
require(dolaReserve * dbrReserve >= K, "Invariant");
dola.transferFrom(msg.sender, address(this), exactDolaIn);
require(assetReserve * dbrReserve >= K, "Invariant");
address(asset).safeTransferFrom(msg.sender, address(this), exactAssetIn);
dbr.mint(to, exactDbrOut);
emit Buy(msg.sender, to, exactDolaIn, exactDbrOut);
emit Buy(msg.sender, to, exactAssetIn, exactDbrOut);
}

function sendToSaleHandler() public {
require(address(saleHandler) != address(0), "No sale handler");
uint bal = dola.balanceOf(address(this));
require(bal > 0, "No DOLA to send");
uint bal = asset.balanceOf(address(this));
require(bal > 0, "No asset to send");
uint capacity = saleHandler.getCapacity();
uint amount = bal > capacity ? capacity : bal;
dola.transfer(address(saleHandler), amount);
address(asset).safeTransfer(address(saleHandler), amount);
saleHandler.onReceive();
emit SendToSaleHandler(amount);
}

// only if no sale handler is set
function sendToGov() public {
require(address(saleHandler) == address(0), "Sale handler set");
uint bal = asset.balanceOf(address(this));
require(bal > 0, "No asset to send");
address(asset).safeTransfer(gov, bal);
emit SendToGov(bal);
}

function sweep(address token, address destination, uint amount) external onlyGov {
IERC20(token).transfer(destination, amount);
token.safeTransfer(destination, amount);
}

event Buy(address indexed caller, address indexed to, uint dolaIn, uint dbrOut);
event Buy(address indexed caller, address indexed to, uint assetIn, uint dbrOut);
event SetAssetReserve(uint assetReserve, uint dbrReserve);
event SetDbrReserve(uint dbrReserve, uint assetReserve);
event OverrideReserves(uint assetReserve, uint dbrReserve);
event SendToSaleHandler(uint amount);
event SendToGov(uint amount);
event RateUpdate(uint newRate);
event MaxRateUpdate(uint newMaxRate);
event MinRateUpdate(uint newMinRate);
}
58 changes: 58 additions & 0 deletions src/DolaSaleHandler.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.21;

import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";

interface IERC20 {
function balanceOf(address) external view returns (uint);
}

interface IAnDola {
function repayBorrowBehalf(address borrower, uint repayAmount) external returns (uint);
function borrowBalanceStored(address account) external view returns (uint); // stored is good enough for our use case
}

contract DolaSaleHandler {

using SafeTransferLib for address;

IERC20 public immutable asset;
IAnDola public immutable anDola;
address public immutable borrower1;
address public immutable borrower2;

constructor(
address _asset,
address _anDola,
address _borrower1,
address _borrower2
) {
asset = IERC20(_asset);
anDola = IAnDola(_anDola);
borrower1 = _borrower1;
borrower2 = _borrower2;
address(asset).safeApprove(_anDola, type(uint).max);
}

function onReceive() external {
uint bal = asset.balanceOf(address(this));
uint debt1 = getDebtOf(borrower1);
uint debt2 = getDebtOf(borrower2);
if(debt1 > debt2) {
uint errCode = anDola.repayBorrowBehalf(borrower1, bal);
if(errCode > 0) require(anDola.repayBorrowBehalf(borrower2, bal) == 0, "Failed to repay");
} else {
uint errCode = anDola.repayBorrowBehalf(borrower2, bal);
if(errCode > 0) require(anDola.repayBorrowBehalf(borrower1, bal) == 0, "Failed to repay");
}
}

function getDebtOf(address borrower) internal view returns (uint) {
return anDola.borrowBalanceStored(borrower);
}

function getCapacity() external view returns (uint) {
return getDebtOf(borrower1) + getDebtOf(borrower2) - asset.balanceOf(address(this));
}

}
Loading