This is test contract which will be used for consuming all the gas for the contract
Apply gas limiter so that issue can be resolved as highlighted in the code on line 300.
pragma solidity 0.8.25;
import { IAccountNFT } from "@zaros/account-nft/interfaces/IAccountNFT.sol";
import { Errors } from "@zaros/utils/Errors.sol";
import { TradingAccount } from "@zaros/perpetuals/leaves/TradingAccount.sol";
import { GlobalConfiguration } from "@zaros/perpetuals/leaves/GlobalConfiguration.sol";
import { PerpMarket } from "@zaros/perpetuals/leaves/PerpMarket.sol";
import { Position } from "@zaros/perpetuals/leaves/Position.sol";
import { MarginCollateralConfiguration } from "@zaros/perpetuals/leaves/MarginCollateralConfiguration.sol";
import { CustomReferralConfiguration } from "@zaros/perpetuals/leaves/CustomReferralConfiguration.sol";
import { Referral } from "@zaros/perpetuals/leaves/Referral.sol";
import { EnumerableSet } from "@openzeppelin/utils/structs/EnumerableSet.sol";
import { IERC20 } from "@openzeppelin/token/ERC20/ERC20.sol";
import { SafeCast } from "@openzeppelin/utils/math/SafeCast.sol";
import { SafeERC20 } from "@openzeppelin/token/ERC20/utils/SafeERC20.sol";
import { UD60x18, ud60x18, ZERO as UD60x18_ZERO } from "@prb-math/UD60x18.sol";
import { SD59x18, sd59x18, ZERO as SD59x18_ZERO, unary } from "@prb-math/SD59x18.sol";
contract TradingAccountBranch {
using EnumerableSet for *;
using TradingAccount for TradingAccount.Data;
using PerpMarket for PerpMarket.Data;
using Position for Position.Data;
using SafeCast for uint256;
using SafeERC20 for IERC20;
using GlobalConfiguration for GlobalConfiguration.Data;
using MarginCollateralConfiguration for MarginCollateralConfiguration.Data;
using Referral for Referral.Data;
event LogCreateTradingAccount(uint128 tradingAccountId, address sender);
event LogDepositMargin(
address indexed sender, uint128 indexed tradingAccountId, address indexed collateralType, uint256 amount
);
event LogWithdrawMargin(
address indexed sender, uint128 indexed tradingAccountId, address indexed collateralType, uint256 amount
);
event LogReferralSet(
address indexed user, address indexed referrer, bytes referralCode, bool isCustomReferralCode
);
function getTradingAccountToken() public view returns (address) {
return GlobalConfiguration.load().tradingAccountToken;
}
function getAccountMarginCollateralBalance(
uint128 tradingAccountId,
address collateralType
)
external
view
returns (UD60x18)
{
TradingAccount.Data storage tradingAccount = TradingAccount.loadExisting(tradingAccountId);
UD60x18 marginCollateralBalanceX18 = tradingAccount.getMarginCollateralBalance(collateralType);
return marginCollateralBalanceX18;
}
function getAccountEquityUsd(uint128 tradingAccountId) external view returns (SD59x18) {
TradingAccount.Data storage tradingAccount = TradingAccount.loadExisting(tradingAccountId);
SD59x18 activePositionsUnrealizedPnlUsdX18 = tradingAccount.getAccountUnrealizedPnlUsd();
return tradingAccount.getEquityUsd(activePositionsUnrealizedPnlUsdX18);
}
function getAccountMarginBreakdown(uint128 tradingAccountId)
external
view
returns (
SD59x18 marginBalanceUsdX18,
UD60x18 initialMarginUsdX18,
UD60x18 maintenanceMarginUsdX18,
SD59x18 availableMarginUsdX18
)
{
TradingAccount.Data storage tradingAccount = TradingAccount.loadExisting(tradingAccountId);
SD59x18 activePositionsUnrealizedPnlUsdX18 = tradingAccount.getAccountUnrealizedPnlUsd();
marginBalanceUsdX18 = tradingAccount.getMarginBalanceUsd(activePositionsUnrealizedPnlUsdX18);
for (uint256 i; i < tradingAccount.activeMarketsIds.length(); i++) {
uint128 marketId = tradingAccount.activeMarketsIds.at(i).toUint128();
PerpMarket.Data storage perpMarket = PerpMarket.load(marketId);
Position.Data storage position = Position.load(tradingAccountId, marketId);
UD60x18 indexPrice = perpMarket.getIndexPrice();
UD60x18 markPrice = perpMarket.getMarkPrice(unary(sd59x18(position.size)), indexPrice);
UD60x18 notionalValueX18 = position.getNotionalValue(markPrice);
(UD60x18 positionInitialMarginUsdX18, UD60x18 positionMaintenanceMarginUsdX18) = Position
.getMarginRequirement(
notionalValueX18,
ud60x18(perpMarket.configuration.initialMarginRateX18),
ud60x18(perpMarket.configuration.maintenanceMarginRateX18)
);
initialMarginUsdX18 = initialMarginUsdX18.add(positionInitialMarginUsdX18);
maintenanceMarginUsdX18 = maintenanceMarginUsdX18.add(positionMaintenanceMarginUsdX18);
}
availableMarginUsdX18 = marginBalanceUsdX18.sub((initialMarginUsdX18).intoSD59x18());
}
function getAccountTotalUnrealizedPnl(uint128 tradingAccountId)
external
view
returns (SD59x18 accountTotalUnrealizedPnlUsdX18)
{
TradingAccount.Data storage tradingAccount = TradingAccount.loadExisting(tradingAccountId);
accountTotalUnrealizedPnlUsdX18 = tradingAccount.getAccountUnrealizedPnlUsd();
}
function getAccountLeverage(uint128 tradingAccountId) external view returns (UD60x18) {
TradingAccount.Data storage tradingAccount = TradingAccount.loadExisting(tradingAccountId);
SD59x18 marginBalanceUsdX18 = tradingAccount.getMarginBalanceUsd(tradingAccount.getAccountUnrealizedPnlUsd());
UD60x18 totalPositionsNotionalValue;
if (marginBalanceUsdX18.isZero()) return marginBalanceUsdX18.intoUD60x18();
for (uint256 i; i < tradingAccount.activeMarketsIds.length(); i++) {
uint128 marketId = tradingAccount.activeMarketsIds.at(i).toUint128();
PerpMarket.Data storage perpMarket = PerpMarket.load(marketId);
Position.Data storage position = Position.load(tradingAccountId, marketId);
UD60x18 indexPrice = perpMarket.getIndexPrice();
UD60x18 markPrice = perpMarket.getMarkPrice(unary(sd59x18(position.size)), indexPrice);
UD60x18 positionNotionalValueX18 = position.getNotionalValue(markPrice);
totalPositionsNotionalValue = totalPositionsNotionalValue.add(positionNotionalValueX18);
}
return totalPositionsNotionalValue.intoSD59x18().div(marginBalanceUsdX18).intoUD60x18();
}
function getPositionState(
uint128 tradingAccountId,
uint128 marketId,
uint256 indexPrice
)
external
view
returns (Position.State memory positionState)
{
PerpMarket.Data storage perpMarket = PerpMarket.load(marketId);
Position.Data storage position = Position.load(tradingAccountId, marketId);
UD60x18 markPriceX18 = perpMarket.getMarkPrice(unary(sd59x18(position.size)), ud60x18(indexPrice));
SD59x18 fundingFeePerUnit =
perpMarket.getNextFundingFeePerUnit(perpMarket.getCurrentFundingRate(), markPriceX18);
positionState = position.getState(
ud60x18(perpMarket.configuration.initialMarginRateX18),
ud60x18(perpMarket.configuration.maintenanceMarginRateX18),
markPriceX18,
fundingFeePerUnit
);
}
function createTradingAccount(
bytes memory referralCode,
bool isCustomReferralCode
)
public
virtual
returns (uint128 tradingAccountId)
{
GlobalConfiguration.Data storage globalConfiguration = GlobalConfiguration.load();
tradingAccountId = ++globalConfiguration.nextAccountId;
IAccountNFT tradingAccountToken = IAccountNFT(globalConfiguration.tradingAccountToken);
TradingAccount.create(tradingAccountId, msg.sender);
tradingAccountToken.mint(msg.sender, tradingAccountId);
emit LogCreateTradingAccount(tradingAccountId, msg.sender);
Referral.Data storage referral = Referral.load(msg.sender);
if (referralCode.length != 0 && referral.referralCode.length == 0) {
if (isCustomReferralCode) {
CustomReferralConfiguration.Data storage customReferral =
CustomReferralConfiguration.load(string(referralCode));
if (customReferral.referrer == address(0)) {
revert Errors.InvalidReferralCode();
}
referral.referralCode = referralCode;
referral.isCustomReferralCode = true;
} else {
address referrer = abi.decode(referralCode, (address));
if (referrer == msg.sender) {
revert Errors.InvalidReferralCode();
}
referral.referralCode = referralCode;
referral.isCustomReferralCode = false;
}
emit LogReferralSet(msg.sender, referral.getReferrerAddress(), referralCode, isCustomReferralCode);
}
return tradingAccountId;
}
function createTradingAccountAndMulticall(
bytes[] calldata data,
bytes memory referralCode,
bool isCustomReferralCode
)
external
payable
virtual
returns (bytes[] memory results)
{
uint128 tradingAccountId = createTradingAccount(referralCode, isCustomReferralCode);
results = new bytes[](data.length);
for (uint256 i; i < data.length; i++) {
bytes memory dataWithAccountId = bytes.concat(data[i][0:4], abi.encode(tradingAccountId), data[i][4:]);
(bool success, bytes memory result) = address(this).delegatecall{gas: 50000}(dataWithAccountId);
if (!success) {
uint256 len = result.length;
assembly {
revert(add(result, 0x20), len)
}
}
results[i] = result;
}
}
function depositMargin(uint128 tradingAccountId, address collateralType, uint256 amount) public virtual {
MarginCollateralConfiguration.Data storage marginCollateralConfiguration =
MarginCollateralConfiguration.load(collateralType);
TradingAccount.Data storage tradingAccount = TradingAccount.loadExisting(tradingAccountId);
UD60x18 amountX18 = marginCollateralConfiguration.convertTokenAmountToUd60x18(amount);
UD60x18 depositCapX18 = ud60x18(marginCollateralConfiguration.depositCap);
UD60x18 totalCollateralDepositedX18 = ud60x18(marginCollateralConfiguration.totalDeposited);
_requireAmountNotZero(amountX18);
_requireEnoughDepositCap(collateralType, amountX18, depositCapX18, totalCollateralDepositedX18);
_requireCollateralLiquidationPriorityDefined(collateralType);
IERC20(collateralType).safeTransferFrom(msg.sender, address(this), amount);
tradingAccount.deposit(collateralType, amountX18);
emit LogDepositMargin(msg.sender, tradingAccountId, collateralType, amount);
}
function withdrawMargin(uint128 tradingAccountId, address collateralType, uint256 amount) external {
MarginCollateralConfiguration.Data storage marginCollateralConfiguration =
MarginCollateralConfiguration.load(collateralType);
TradingAccount.Data storage tradingAccount =
TradingAccount.loadExistingAccountAndVerifySender(tradingAccountId);
UD60x18 amountX18 = marginCollateralConfiguration.convertTokenAmountToUd60x18(amount);
_requireAmountNotZero(amountX18);
_requireEnoughMarginCollateral(tradingAccount, collateralType, amountX18);
tradingAccount.withdraw(collateralType, amountX18);
(UD60x18 requiredInitialMarginUsdX18,, SD59x18 accountTotalUnrealizedPnlUsdX18) =
tradingAccount.getAccountMarginRequirementUsdAndUnrealizedPnlUsd(0, SD59x18_ZERO);
SD59x18 marginBalanceUsdX18 = tradingAccount.getMarginBalanceUsd(accountTotalUnrealizedPnlUsdX18);
tradingAccount.validateMarginRequirement(requiredInitialMarginUsdX18, marginBalanceUsdX18, UD60x18_ZERO);
IERC20(collateralType).safeTransfer(msg.sender, amount);
emit LogWithdrawMargin(msg.sender, tradingAccountId, collateralType, amount);
}
function notifyAccountTransfer(address to, uint128 tradingAccountId) external {
_onlyTradingAccountToken();
TradingAccount.Data storage tradingAccount = TradingAccount.loadExisting(tradingAccountId);
tradingAccount.owner = to;
}
function getUserReferralData(address user) external pure returns (bytes memory, bool) {
Referral.Data memory referral = Referral.load(user);
return (referral.referralCode, referral.isCustomReferralCode);
}
function _requireAmountNotZero(UD60x18 amount) internal pure {
if (amount.isZero()) {
revert Errors.ZeroInput("amount");
}
}
function _requireEnoughDepositCap(
address collateralType,
UD60x18 amount,
UD60x18 depositCap,
UD60x18 totalCollateralDeposited
)
internal
pure
{
if (amount.add(totalCollateralDeposited).gt(depositCap)) {
revert Errors.DepositCap(collateralType, amount.intoUint256(), depositCap.intoUint256());
}
}
function _requireCollateralLiquidationPriorityDefined(address collateralType) internal view {
GlobalConfiguration.Data storage globalConfiguration = GlobalConfiguration.load();
bool isInCollateralLiquidationPriority =
globalConfiguration.collateralLiquidationPriority.contains(collateralType);
if (!isInCollateralLiquidationPriority) revert Errors.CollateralLiquidationPriorityNotDefined(collateralType);
}
function _requireEnoughMarginCollateral(
TradingAccount.Data storage tradingAccount,
address collateralType,
UD60x18 amount
)
internal
view
{
UD60x18 marginCollateralBalanceX18 = tradingAccount.getMarginCollateralBalance(collateralType);
if (marginCollateralBalanceX18.lt(amount)) {
revert Errors.InsufficientCollateralBalance(
amount.intoUint256(), marginCollateralBalanceX18.intoUint256()
);
}
}
function _onlyTradingAccountToken() internal view {
if (msg.sender != address(getTradingAccountToken())) {
revert Errors.OnlyTradingAccountToken(msg.sender);
}
}
}