Skip to content

Commit

Permalink
[WIP] New V2 comptrollers for dHEDGE deprecated vault buyback support
Browse files Browse the repository at this point in the history
  • Loading branch information
rashtrakoff committed May 30, 2024
1 parent 8b1d697 commit c4ce19e
Show file tree
Hide file tree
Showing 10 changed files with 700 additions and 124 deletions.
8 changes: 8 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.vscode
node_modules
artifacts
cache
out
*.md
*.yml
*.json
13 changes: 13 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"plugins": ["prettier-plugin-solidity"],
"printWidth": 120,
"overrides": [
{
"files": "*.sol",
"options": {
"tabWidth": 4
}
}
]
}

16 changes: 16 additions & 0 deletions .solhint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"extends": "solhint:recommended",
"rules": {
"compiler-version": ["error", "0.8.18"],
"func-visibility": ["warn", { "ignoreConstructors": true }],
"private-vars-leading-underscore": ["warn", { "strict": false }],
"func-param-name-mixedcase": ["warn"],
"modifier-name-mixedcase": ["warn"],
"named-parameters-mapping": ["warn"],
"gas-named-return-values": ["warn"],
"max-states-count": "off",
"interface-starts-with-i": "warn",
"gas-calldata-parameters": "warn",
"gas-struct-packing": "warn"
}
}
1 change: 1 addition & 0 deletions .solhintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
lib/
1 change: 1 addition & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ src = 'src'
out = 'out'
libs = ['lib']
verbosity = 2
auto_detect_solc = true

[fuzz]
runs = 1_000
Expand Down
11 changes: 11 additions & 0 deletions mythril.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"remappings": [
"openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol=/share/lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol",
"openzeppelin-contracts-upgradeable/contracts/security/PausableUpgradeable.sol=/share/lib/openzeppelin-contracts-upgradeable/contracts/security/PausableUpgradeable.sol",
"openzeppelin-contracts-upgradeable/contracts/token/ERC20/utils/SafeERC20Upgradeable.sol=/share/lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/utils/SafeERC20Upgradeable.sol",
"openzeppelin-contracts-upgradeable/contracts/interfaces/IERC20Upgradeable.sol=/share/lib/openzeppelin-contracts-upgradeable/contracts/interfaces/IERC20Upgradeable.sol",
"./interfaces/IERC20Burnable.sol=/share/src/interfaces/IERC20Burnable.sol",
"./interfaces/ICrossDomainMessenger.sol=/share/src/interfaces/ICrossDomainMessenger.sol",
"./interfaces/IPoolLogic.sol=/share/src/interfaces/IPoolLogic.sol"
]
}
12 changes: 12 additions & 0 deletions slither.config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"filter_paths": "lib",
"solc_remaps": [
"openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol=/share/lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol",
"openzeppelin-contracts-upgradeable/contracts/security/PausableUpgradeable.sol=/share/lib/openzeppelin-contracts-upgradeable/contracts/security/PausableUpgradeable.sol",
"openzeppelin-contracts-upgradeable/contracts/token/ERC20/utils/SafeERC20Upgradeable.sol=/share/lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/utils/SafeERC20Upgradeable.sol",
"openzeppelin-contracts-upgradeable/contracts/interfaces/IERC20Upgradeable.sol=/share/lib/openzeppelin-contracts-upgradeable/contracts/interfaces/IERC20Upgradeable.sol",
"./interfaces/IERC20Burnable.sol=/share/src/interfaces/IERC20Burnable.sol",
"./interfaces/ICrossDomainMessenger.sol=/share/src/interfaces/ICrossDomainMessenger.sol",
"./interfaces/IPoolLogic.sol=/share/src/interfaces/IPoolLogic.sol"
]
}
236 changes: 236 additions & 0 deletions src/L1ComptrollerV2.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
// SPDX-License-Identifier: SEE LICENSE IN LICENSE
pragma solidity 0.8.18;

import {OwnableUpgradeable} from "openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol";
import {PausableUpgradeable} from "openzeppelin-contracts-upgradeable/contracts/security/PausableUpgradeable.sol";
import {SafeERC20Upgradeable} from "openzeppelin-contracts-upgradeable/contracts/token/ERC20/utils/SafeERC20Upgradeable.sol";
import {IERC20Upgradeable} from "openzeppelin-contracts-upgradeable/contracts/interfaces/IERC20Upgradeable.sol";
import {IERC20Burnable} from "./interfaces/IERC20Burnable.sol";
import {ICrossDomainMessenger} from "./interfaces/ICrossDomainMessenger.sol";

/// @title L1 comptroller contract for token buy backs.
/// @notice Contract to burn a token and claim another one on L2.
/// @author dHEDGE
/// @dev This contract is only useful if paired with the L2 comptroller.
contract L1Comptroller is OwnableUpgradeable, PausableUpgradeable {
using SafeERC20Upgradeable for IERC20Upgradeable;


/////////////////////////////////////////////
// Structs //
/////////////////////////////////////////////

struct BurnTokenDetails {
bool isTokenToBurn;
bool isIERC20Burnable;
uint256 exchangeRate;
}

/////////////////////////////////////////////
// Events //
/////////////////////////////////////////////

event L2ComptrollerSet(address newL2Comptroller);
event CrossChainGasLimitModified(uint256 newCrossChainGasLimit);
event EmergencyWithdrawal(address indexed token, uint256 amount);
event BuyBackFromL1Initiated(
address indexed depositor,
address indexed tokenToBurn,
address indexed receiver,
uint256 burnTokenAmount,
uint256 totalAmountBurnt
);


/////////////////////////////////////////////
// Errors //
/////////////////////////////////////////////

error ZeroAddress();
error ZeroValue();
error L2ComptrollerNotSet();
error NotBuybackToken(address token);
error NotEligibleBuybackPair(address tokenToBuy, address tokenToBurn);


/////////////////////////////////////////////
// Variables //
/////////////////////////////////////////////

address public constant BURN_ADDRESS = 0x000000000000000000000000000000000000dEaD;

/// @notice The Optimism contract to interact with on L1 Ethereum for sending data using smart contracts.
ICrossDomainMessenger public crossDomainMessenger;

/// @notice Address of the L2 comptroller to be called to initiate a buyback claim.
/// @dev Has to be set after deployment of both the contracts.
address public l2Comptroller;

/// @dev The gas limit to be used to call the Optimism Cross Domain Messenger contract.
uint32 public crossChainCallGasLimit;

/// @notice Mapping of token to be burnt and its details.
/// @dev Note that we are not enforcing that the `token` be a burnable token.
/// We can write different functions depending on the token type.
mapping(address token => BurnTokenDetails burnTokenDetails) public tokensToBurn;

/// @notice Mapping of token to buy and token to burn.
/// @dev This mapping is used to check if a pair is valid for buyback.
mapping(address tokenToBuy => mapping(address tokenToBurn => bool isAPair)) public tokenPairs;

/// @notice Stores cumulative amount of tokens burnt by an address.
/// @dev We don't need to use order IDs as the difference of `totalAmount` (burnt) on L1
/// and `totalAmount` (claimed) on L2 gives us the amount of buy tokens tokens yet to be claimed.
/// @dev The `totalAmount` for an address would/should NEVER decrease.
mapping(address depositor => mapping(address tokenToBurn => uint256 totalAmount)) public burntAmountOf;


/////////////////////////////////////////////
// Modifiers //
/////////////////////////////////////////////

/// @dev Modifier to check that l2Comptroller address has been set or not.
modifier whenL2ComptrollerSet() {
if (l2Comptroller == address(0)) revert L2ComptrollerNotSet();
_;
}


/////////////////////////////////////////////
// Functions //
/////////////////////////////////////////////

/// @dev To prevent the implementation contract from being used, we invoke the _disableInitializers
/// function in the constructor to automatically lock it when it is deployed.
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}

/// @notice Function to initialize this contract.
/// @param _crossDomainMessenger The cross domain messenger contract on L1.
/// @param _crossChainCallGasLimit The gas limit to be passed for a cross chain call
/// to the L2Comptroller contract.
function initialize(
ICrossDomainMessenger _crossDomainMessenger,
uint32 _crossChainCallGasLimit
) external initializer {
if (
address(_crossDomainMessenger) == address(0)
) revert ZeroAddress();

__Ownable_init();
__Pausable_init();

crossDomainMessenger = _crossDomainMessenger;
crossChainCallGasLimit = _crossChainCallGasLimit;
}

/// @notice Function to burn `amount` of a `token` and claim against it on L2.
/// @dev If a transaction passes on L1 but fails on L2 then the user must claim their share on L2 directly.
/// @param tokenToBuy Address of the token to be claimed.
/// @param tokenToBurn Address of the token to be burnt.
/// @param receiver Address of the account which will receive the claim.
/// @param burnTokenAmount Amount of `tokenToBurn` to be burnt.
function buyBack(
address tokenToBuy,
address tokenToBurn,
address receiver,
uint256 burnTokenAmount
) external whenNotPaused whenL2ComptrollerSet {
if(tokenPairs[tokenToBuy][tokenToBurn] == false) revert NotEligibleBuybackPair(tokenToBuy, tokenToBurn);

_burnToken(tokenToBurn, burnTokenAmount);

uint256 totalBurntAmount = burntAmountOf[msg.sender][tokenToBurn] += burnTokenAmount;

// Send a cross chain message to `l2Comptroller` for releasing the buy tokens.
// TODO: Modify the following to call a different function in L2 comptroller.
crossDomainMessenger.sendMessage(
l2Comptroller,
abi.encodeWithSignature(
"buyBackFromL1(address,address,uint256)",
msg.sender,
receiver,
totalBurntAmount
),
crossChainCallGasLimit
);

emit BuyBackFromL1Initiated(msg.sender, tokenToBurn, receiver, burnTokenAmount, totalBurntAmount);
}

function _burnToken(address token, uint256 amount) private {
if(tokensToBurn[token].isTokenToBurn == false) revert NotBuybackToken(token);

if(tokensToBurn[token].isIERC20Burnable) {
IERC20Burnable(token).burnFrom(msg.sender, amount);
} else {
// If it's not a natively burnable token then transfer it to the burn address.
IERC20Upgradeable(token).safeTransferFrom(msg.sender, BURN_ADDRESS, amount);
}
}

/////////////////////////////////////////////
// Owner Functions //
/////////////////////////////////////////////

/// @notice Function to set the L2 comptroller address deployed on Optimism.
/// @dev This function needs to be called after deployment of both the contracts.
/// @param newL2Comptroller Address of the newly deployed L2 comptroller.
function setL2Comptroller(address newL2Comptroller) external onlyOwner {
if (newL2Comptroller == address(0)) revert ZeroAddress();

l2Comptroller = newL2Comptroller;

emit L2ComptrollerSet(newL2Comptroller);
}

/// @notice Function to withdraw tokens in an emergency situation.
/// @param token Address of the token to be withdrawn.
/// @param amount Amount of the `token` to be removed.
function emergencyWithdraw(
address token,
uint256 amount
) external onlyOwner {
IERC20Upgradeable tokenToWithdraw = IERC20Upgradeable(token);

// If the `amount` is max of uint256 then transfer all the available balance.
if (amount == type(uint256).max) {
amount = tokenToWithdraw.balanceOf(address(this));
}

// NOTE: If the balanceOf(address(this)) < `amount` < type(uint256).max then
// the transfer will revert.
tokenToWithdraw.safeTransfer(owner(), amount);

emit EmergencyWithdrawal(token, amount);
}

/// @notice Function to set the cross chain calls gas limit.
/// @dev Optimism allows, upto a certain limit, free execution gas units on L2.
/// This value is currently 1.92 million gas units. This might not be enough for us.
/// Hence this function for modifying the gas limit.
/// @param newCrossChainGasLimit The new gas amount to be sent to the l2Comptroller for cross chain calls.
function setCrossChainGasLimit(
uint32 newCrossChainGasLimit
) external onlyOwner {
if (newCrossChainGasLimit == 0) revert ZeroValue();

crossChainCallGasLimit = newCrossChainGasLimit;

emit CrossChainGasLimitModified(newCrossChainGasLimit);
}

/// @notice Function to pause the critical functions in this contract.
/// @dev This function won't make any state changes if already paused.
function pause() external onlyOwner {
_pause();
}

/// @notice Function to unpause the critical functions in this contract.
/// @dev This function won't make any state changes if already unpaused.
function unpause() external onlyOwner {
_unpause();
}
}
Loading

0 comments on commit c4ce19e

Please sign in to comment.