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

Complete sUSDe Burner #1

Merged
merged 4 commits into from
Aug 12, 2024
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
1 change: 1 addition & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ out = "out"
libs = ["lib"]
fs_permissions = [{ access = "read-write", path = "./"}]
gas_reports = ["*"]
evm_version = "shanghai"

[rpc_endpoints]
mainnet = "${ETH_RPC_URL}"
Expand Down
66 changes: 58 additions & 8 deletions src/contracts/burners/sUSDe/sUSDe_Burner.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,26 @@
pragma solidity 0.8.25;

import {AddressRequests} from "src/contracts/AddressRequests.sol";
import {SelfDestruct} from "src/contracts/SelfDestruct.sol";
import {sUSDe_Miniburner} from "./sUSDe_Miniburner.sol";

import {IEthenaMinting} from "src/interfaces/burners/sUSDe/IEthenaMinting.sol";
import {ISUSDe} from "src/interfaces/burners/sUSDe/ISUSDe.sol";
import {IUSDe} from "src/interfaces/burners/sUSDe/IUSDe.sol";
import {IsUSDe_Burner} from "src/interfaces/burners/sUSDe/IsUSDe_Burner.sol";

import {Clones} from "@openzeppelin/contracts/proxy/Clones.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC1271} from "@openzeppelin/contracts/interfaces/IERC1271.sol";
import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

contract sUSDe_Burner is AddressRequests, IsUSDe_Burner {
contract sUSDe_Burner is AddressRequests, IERC1271, IsUSDe_Burner {
using Clones for address;
using SafeERC20 for IERC20;

address private constant _DEAD = address(0xdEaD);

bytes4 private constant _MAGICVALUE = bytes4(keccak256("isValidSignature(bytes32,bytes)"));

/**
* @inheritdoc IsUSDe_Burner
*/
Expand All @@ -34,6 +40,19 @@ contract sUSDe_Burner is AddressRequests, IsUSDe_Burner {
USDE = ISUSDe(COLLATERAL).asset();

_MINIBURNER_IMPLEMENTATION = implementation;

IERC20(USDE).forceApprove(IUSDe(USDE).minter(), type(uint256).max);
}

/**
* @inheritdoc IERC1271
*/
function isValidSignature(bytes32 hash_, bytes memory signature) external view returns (bytes4) {
IEthenaMinting.Order memory order = abi.decode(signature, (IEthenaMinting.Order));

if (hash_ == IEthenaMinting(IUSDe(USDE).minter()).hashOrder(order) && order.beneficiary == address(this)) {
return _MAGICVALUE;
}
}

/**
Expand All @@ -53,32 +72,63 @@ contract sUSDe_Burner is AddressRequests, IsUSDe_Burner {

_addRequestId(requestId);

emit TriggerWithdrawal(msg.sender, requestId);
emit TriggerWithdrawal(msg.sender, amount, requestId);
}

/**
* @inheritdoc IsUSDe_Burner
*/
function triggerBurn(address requestId) external {
function triggerClaim(address requestId) external {
_removeRequestId(requestId);

sUSDe_Miniburner(requestId).triggerBurn();

emit TriggerBurn(msg.sender, requestId);
emit TriggerClaim(msg.sender, requestId);
}

/**
* @inheritdoc IsUSDe_Burner
*/
function triggerInstantBurn() external {
function triggerInstantClaim() external {
if (ISUSDe(COLLATERAL).cooldownDuration() != 0) {
revert HasCooldown();
}

uint256 amount = IERC20(COLLATERAL).balanceOf(address(this));

IUSDe(USDE).burn(ISUSDe(COLLATERAL).redeem(amount, address(this), address(this)));
ISUSDe(COLLATERAL).redeem(amount, address(this), address(this));

emit TriggerInstantClaim(msg.sender, amount);
}

emit TriggerInstantBurn(msg.sender, amount);
/**
* @inheritdoc IsUSDe_Burner
*/
function triggerBurn(address asset) external {
if (asset == COLLATERAL || asset == USDE) {
revert InvalidAsset();
}

uint256 amount;
if (asset == address(0)) {
amount = address(this).balance;
new SelfDestruct{value: amount}();
} else {
amount = IERC20(asset).balanceOf(address(this));
IERC20(asset).safeTransfer(_DEAD, amount);
}

emit TriggerBurn(msg.sender, asset, amount);
}

/**
* @inheritdoc IsUSDe_Burner
*/
function approveUSDeMinter() external {
address minter = IUSDe(USDE).minter();
if (IERC20(USDE).allowance(address(this), minter) == type(uint256).max) {
revert SufficientApproval();
}
IERC20(USDE).forceApprove(minter, type(uint256).max);
}
}
5 changes: 1 addition & 4 deletions src/contracts/burners/sUSDe/sUSDe_Miniburner.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@
pragma solidity 0.8.25;

import {ISUSDe} from "src/interfaces/burners/sUSDe/ISUSDe.sol";
import {IUSDe} from "src/interfaces/burners/sUSDe/IUSDe.sol";

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";

contract sUSDe_Miniburner is OwnableUpgradeable {
Expand All @@ -28,7 +26,6 @@ contract sUSDe_Miniburner is OwnableUpgradeable {
}

function triggerBurn() external onlyOwner {
ISUSDe(_COLLATERAL).unstake(address(this));
IUSDe(_USDE).burn(IERC20(_USDE).balanceOf(address(this)));
ISUSDe(_COLLATERAL).unstake(owner());
}
}
100 changes: 100 additions & 0 deletions src/interfaces/burners/sUSDe/IEthenaMinting.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;

interface IEthenaMinting {
enum Role {
Minter,
Redeemer
}

enum OrderType {
MINT,
REDEEM
}

enum TokenType {
STABLE,
ASSET
}

enum SignatureType {
EIP712,
EIP1271
}

enum DelegatedSignerStatus {
REJECTED,
PENDING,
ACCEPTED
}

struct Signature {
SignatureType signature_type;
bytes signature_bytes;
}

struct Route {
address[] addresses;
uint128[] ratios;
}

struct Order {
string order_id;
OrderType order_type;
uint120 expiry;
uint128 nonce;
address benefactor;
address beneficiary;
address collateral_asset;
uint128 collateral_amount;
uint128 usde_amount;
}

struct TokenConfig {
/// @notice tracks asset type (STABLE or ASSET)
TokenType tokenType;
/// @notice tracks if the asset is active
bool isActive;
/// @notice max mint per block this given asset
uint128 maxMintPerBlock;
/// @notice max redeem per block this given asset
uint128 maxRedeemPerBlock;
}

struct BlockTotals {
/// @notice USDe minted per block / per asset per block
uint128 mintedPerBlock;
/// @notice USDe redeemed per block / per asset per block
uint128 redeemedPerBlock;
}

struct GlobalConfig {
/// @notice max USDe that can be minted across all assets within a single block.
uint128 globalMaxMintPerBlock;
/// @notice max USDe that can be redeemed across all assets within a single block.
uint128 globalMaxRedeemPerBlock;
}

/// @notice hash an Order struct
function hashOrder(Order calldata order) external view returns (bytes32);

/// @notice total USDe that can be minted/redeemed across all assets per single block.
function totalPerBlockPerAsset(uint256 blockNumber, address asset) external view returns (BlockTotals memory);

function totalPerBlock(uint256 blockNumber) external view returns (BlockTotals memory);

/// @notice global single block totals
function globalConfig() external view returns (GlobalConfig memory);

function tokenConfig(address asset) external view returns (TokenConfig memory);

/// @notice Adds a benefactor address to the benefactor whitelist
function addWhitelistedBenefactor(address benefactor) external;

/**
* @notice Redeem stablecoins for assets
* @param order struct containing order details and confirmation from server
* @param signature signature of the taker
*/
function redeem(Order calldata order, Signature calldata signature) external;
}
4 changes: 4 additions & 0 deletions src/interfaces/burners/sUSDe/IUSDe.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
pragma solidity 0.8.25;

interface IUSDe {
function minter() external view returns (address);

function mint(address account, uint256 amount) external;

/**
* @dev Destroys a `value` amount of tokens from the caller.
*
Expand Down
44 changes: 33 additions & 11 deletions src/interfaces/burners/sUSDe/IsUSDe_Burner.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,39 @@ import {IAddressRequests} from "src/interfaces/IAddressRequests.sol";

interface IsUSDe_Burner is IAddressRequests {
error HasCooldown();
error InvalidAsset();
error NoCooldown();
error SufficientApproval();

/**
* @notice Emitted when a withdrawal is triggered.
* @param caller caller of the function
* @param amount amount of the collateral to be withdrawn
* @param requestId request ID that was created
*/
event TriggerWithdrawal(address indexed caller, address requestId);
event TriggerWithdrawal(address indexed caller, uint256 amount, address requestId);

/**
* @notice Emitted when a burn is triggered.
* @notice Emitted when a claim is triggered.
* @param caller caller of the function
* @param requestId request ID of the withdrawal that was claimed
*/
event TriggerClaim(address indexed caller, address requestId);

/**
* @notice Emitted when an instant claim is triggered.
* @param caller caller of the function
* @param requestId request ID of the withdrawal that was claimed and burned
* @param amount amount of the collateral that was unwrapped
*/
event TriggerBurn(address indexed caller, address requestId);
event TriggerInstantClaim(address indexed caller, uint256 amount);

/**
* @notice Emitted when an instant burn is triggered.
* @notice Emitted when a burn is triggered.
* @param caller caller of the function
* @param amount amount of the collateral that was burned
* @param asset address of the asset burned (except sUSDe and USDe)
* @param amount amount of the asset burned
*/
event TriggerInstantBurn(address indexed caller, uint256 amount);
event TriggerBurn(address indexed caller, address indexed asset, uint256 amount);

/**
* @notice Get an address of the collateral.
Expand All @@ -45,13 +56,24 @@ interface IsUSDe_Burner is IAddressRequests {
function triggerWithdrawal() external returns (address requestId);

/**
* @notice Trigger a claim and a burn of USDe.
* @notice Trigger a claim of USDe (if `cooldownDuration` didn't equal zero while triggering withdrawal).
* @param requestId request ID of the withdrawal to process
*/
function triggerBurn(address requestId) external;
function triggerClaim(address requestId) external;

/**
* @notice Trigger an instant claim of USDe (if `cooldownDuration` equals zero).
*/
function triggerInstantClaim() external;

/**
* @notice Trigger a burn of any asset lying on this contract except sUSDe and USDe (after USDe redemption).
* @param asset address of the asset to burn
*/
function triggerBurn(address asset) external;

/**
* @notice Trigger an instant burn of USDe.
* @notice Approve the USDe to a minter (if a new minter appears).
*/
function triggerInstantBurn() external;
function approveUSDeMinter() external;
}
Loading
Loading