Skip to content
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
180 changes: 180 additions & 0 deletions core/contracts/BaseWithdrawPool.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;

import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/cryptography/draft-EIP712Upgradeable.sol";
import "./libraries/MathHelper.sol";
import "./interfaces/IEndpoint.sol";
import "./Verifier.sol";
import "./interfaces/engine/ISpotEngine.sol";
import "./interfaces/IERC20Base.sol";
import "./libraries/ERC20Helper.sol";
import "./common/Constants.sol";

abstract contract BaseWithdrawPool is EIP712Upgradeable, OwnableUpgradeable {
using ERC20Helper for IERC20Base;
using MathSD21x18 for int128;

/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}

function _initialize(address _clearinghouse, address _verifier)
internal
initializer
{
__Ownable_init();
clearinghouse = _clearinghouse;
verifier = _verifier;
}

address internal clearinghouse;

address internal verifier;

// submitted withdrawal idxs
mapping(uint64 => bool) public markedIdxs;

// collected withdrawal fees in native token decimals
mapping(uint32 => int128) public fees;

uint64 public minIdx;

function submitFastWithdrawal(
uint64 idx,
bytes calldata transaction,
bytes[] calldata signatures
) public {
require(!markedIdxs[idx], "Withdrawal already submitted");
require(idx > minIdx, "idx too small");
markedIdxs[idx] = true;

Verifier v = Verifier(verifier);
v.requireValidTxSignatures(transaction, idx, signatures);

IEndpoint.SignedWithdrawCollateral memory signedTx = abi.decode(
transaction[1:],
(IEndpoint.SignedWithdrawCollateral)
);

IERC20Base token = getToken(signedTx.tx.productId);

address sendTo = address(uint160(bytes20(signedTx.tx.sender)));
uint128 transferAmount = signedTx.tx.amount;

require(transferAmount <= INT128_MAX, ERR_CONVERSION_OVERFLOW);

int128 fee = fastWithdrawalFeeAmount(
token,
signedTx.tx.productId,
transferAmount
);

require(transferAmount > uint128(fee), "Fee larger than balance");
transferAmount -= uint128(fee);
fees[signedTx.tx.productId] += fee;

handleWithdrawTransfer(token, sendTo, transferAmount);
}

function submitWithdrawal(
IERC20Base token,
address sendTo,
uint128 amount,
uint64 idx
) public {
require(msg.sender == clearinghouse);

if (markedIdxs[idx]) {
return;
}
markedIdxs[idx] = true;
// set minIdx to most recent withdrawal submitted by sequencer
minIdx = idx;

handleWithdrawTransfer(token, sendTo, amount);
}

function fastWithdrawalFeeAmount(
IERC20Base token,
uint32 productId,
uint128 amount
) public view returns (int128) {
uint8 decimals = token.decimals();
require(decimals <= MAX_DECIMALS);
int256 multiplier = int256(10**(MAX_DECIMALS - uint8(decimals)));
int128 amountX18 = int128(amount) * int128(multiplier);

int128 proportionalFeeX18 = FAST_WITHDRAWAL_FEE_RATE.mul(amountX18);
int128 minFeeX18 = 5 *
IClearinghouse(clearinghouse).getWithdrawFee(productId);

int128 feeX18 = MathHelper.max(proportionalFeeX18, minFeeX18);
return feeX18 / int128(multiplier);
}

function removeLiquidity(
uint32 productId,
uint128 amount,
address sendTo
) external onlyOwner {
handleWithdrawTransfer(getToken(productId), sendTo, amount);
}

function checkMarkedIdxs(uint64[] calldata idxs)
public
view
returns (bool[] memory)
{
bool[] memory marked = new bool[](idxs.length);
for (uint256 i = 0; i < idxs.length; i++) {
marked[i] = markedIdxs[idxs[i]];
}
return marked;
}

function checkProductBalances(uint32[] calldata productIds)
public
view
returns (uint256[] memory)
{
uint256[] memory balances = new uint256[](productIds.length);
for (uint256 i = 0; i < productIds.length; i++) {
IERC20Base token = getToken(productIds[i]);
balances[i] = token.balanceOf(address(this));
}
return balances;
}

function handleWithdrawTransfer(
IERC20Base token,
address to,
uint128 amount
) internal virtual {
token.safeTransfer(to, uint256(amount));
}

function safeTransferFrom(
IERC20Base token,
address from,
uint256 amount
) internal virtual {
token.safeTransferFrom(from, address(this), amount);
}

function getToken(uint32 productId) internal view returns (IERC20Base) {
IERC20Base token = IERC20Base(spotEngine().getConfig(productId).token);
require(address(token) != address(0));
return token;
}

function spotEngine() internal view returns (ISpotEngine) {
return
ISpotEngine(
IClearinghouse(clearinghouse).getEngineByType(
IProductEngine.EngineType.SPOT
)
);
}
}
41 changes: 33 additions & 8 deletions core/contracts/Clearinghouse.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import "./interfaces/engine/IPerpEngine.sol";
import "./EndpointGated.sol";
import "./interfaces/IEndpoint.sol";
import "./ClearinghouseStorage.sol";
import "./WithdrawPool.sol";
import "./BaseWithdrawPool.sol";

interface IProxyManager {
function getProxyManagerHelper() external view returns (address);
Expand Down Expand Up @@ -92,6 +92,7 @@ contract Clearinghouse is EndpointGated, ClearinghouseStorage, IClearinghouse {
if (health == (type(int128).min)) {
return health;
}
health += perpEngine.getHealthContribution(subaccount, healthType);

uint256 _spreads = spreads;
while (_spreads != 0) {
Expand Down Expand Up @@ -143,8 +144,6 @@ contract Clearinghouse is EndpointGated, ClearinghouseStorage, IClearinghouse {
.mul(spotCoreRisk.price + perpCoreRisk.price)
.mul(spreadPenalty - existingPenalty);
}

health += perpEngine.getHealthContribution(subaccount, healthType);
}

function registerProduct(uint32 productId) external {
Expand Down Expand Up @@ -419,7 +418,7 @@ contract Clearinghouse is EndpointGated, ClearinghouseStorage, IClearinghouse {
uint64 idx
) internal virtual {
token.safeTransfer(withdrawPool, uint256(amount));
WithdrawPool(withdrawPool).submitWithdrawal(token, to, amount, idx);
BaseWithdrawPool(withdrawPool).submitWithdrawal(token, to, amount, idx);
}

function _balanceOf(address token) internal view virtual returns (uint128) {
Expand Down Expand Up @@ -553,10 +552,36 @@ contract Clearinghouse is EndpointGated, ClearinghouseStorage, IClearinghouse {
ISpotEngine spotEngine = ISpotEngine(
address(engineByType[IProductEngine.EngineType.SPOT])
);
spotEngine.updateBalance(QUOTE_PRODUCT_ID, V_ACCOUNT, txn.quoteAmount);
spotEngine.updateBalance(QUOTE_PRODUCT_ID, X_ACCOUNT, -txn.quoteAmount);
spotEngine.updateBalance(VLP_PRODUCT_ID, V_ACCOUNT, txn.vlpAmount);
spotEngine.updateBalance(VLP_PRODUCT_ID, X_ACCOUNT, -txn.vlpAmount);
IPerpEngine perpEngine = IPerpEngine(
address(engineByType[IProductEngine.EngineType.PERP])
);
if (address(spotEngine) == address(productToEngine[txn.productId])) {
spotEngine.updateBalance(
txn.productId,
V_ACCOUNT,
txn.baseAmount,
txn.quoteAmount
);
spotEngine.updateBalance(
txn.productId,
X_ACCOUNT,
-txn.baseAmount,
-txn.quoteAmount
);
} else {
perpEngine.updateBalance(
txn.productId,
V_ACCOUNT,
txn.baseAmount,
txn.quoteAmount
);
perpEngine.updateBalance(
txn.productId,
X_ACCOUNT,
-txn.baseAmount,
-txn.quoteAmount
);
}
}

function burnLpAndTransfer(IEndpoint.BurnLpAndTransfer calldata txn)
Expand Down
5 changes: 4 additions & 1 deletion core/contracts/ClearinghouseLiq.sol
Original file line number Diff line number Diff line change
Expand Up @@ -679,7 +679,10 @@ contract ClearinghouseLiq is

// it's ok to let initial health become 0
require(!isAboveInitial(txn.liquidatee), ERR_LIQUIDATED_TOO_MUCH);
require(!isUnderInitial(txn.sender), ERR_SUBACCT_HEALTH);
require(
txn.sender == V_ACCOUNT || !isUnderInitial(txn.sender),
ERR_SUBACCT_HEALTH
);

insurance += v.liquidationFees;

Expand Down
Loading
Loading