Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
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
235 changes: 235 additions & 0 deletions src/MetaKeeper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.13;

import {AutomationCompatibleInterface} from "chainlink/src/v0.8/automation/AutomationCompatible.sol";
import {IAutomationRegistryConsumer} from "chainlink/src/v0.8/automation/interfaces/IAutomationRegistryConsumer.sol";
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import {TlxOwnable} from "./utils/TlxOwnable.sol";

import {Errors} from "./libraries/Errors.sol";
import {AddressKeys} from "./libraries/AddressKeys.sol";
import {ScaledNumber} from "./libraries/ScaledNumber.sol";

import {IMetaKeeper} from "./interfaces/IMetaKeeper.sol";
import {IAddressProvider} from "./interfaces/IAddressProvider.sol";
import {ITlxUpkeepRegistry} from "./interfaces/ITlxUpkeepRegistry.sol";

contract MetaKeeper is IMetaKeeper, TlxOwnable {
using ScaledNumber for uint256;
using EnumerableSet for EnumerableSet.AddressSet;

struct MinBalance {
address registryAddress;
uint256 minBalance;
}

uint256 internal constant _MIN_BALANCE_BUFFER = 0.2e18;

IAddressProvider internal immutable _addressProvider;

uint256 public maxTopUps;
IERC20 public topUpAsset;
uint96 public topUpAmount;

EnumerableSet.AddressSet internal _forwarderAddresses;

constructor(
address addressProvider_,
uint256 maxTopUps_,
address topUpAsset_,
uint96 topUpAmount_
) TlxOwnable(addressProvider_) {
_addressProvider = IAddressProvider(addressProvider_);
maxTopUps = maxTopUps_;
topUpAsset = IERC20(topUpAsset_);
topUpAmount = topUpAmount_;
}

/// @inheritdoc AutomationCompatibleInterface
function performUpkeep(bytes calldata performData_) external override {
if (!_forwarderAddresses.contains(msg.sender)) revert NotForwarder();

uint256[] memory upkeepsToTopup_ = abi.decode(
performData_,
(uint256[])
);

uint256 topUpCount_ = upkeepsToTopup_.length;
if (topUpCount_ == 0) revert NoUpkeepsToTopUp();

ITlxUpkeepRegistry tlxUpkeepRegistry_ = ITlxUpkeepRegistry(
_addressProvider.addressOf(AddressKeys.UPKEEP_REGISTRY)
);
uint96 topUpAmount_ = topUpAmount;

for (uint256 i_; i_ < topUpCount_; i_++) {
uint256 upkeepID_ = upkeepsToTopup_[i_];
if (!tlxUpkeepRegistry_.isUpkeep(upkeepID_)) {
revert NotUpkeep();
}
ITlxUpkeepRegistry.Upkeep memory upkeepInfo_ = tlxUpkeepRegistry_
.upkeepInfoForID(upkeepID_);
IAutomationRegistryConsumer chainlinkRegistry_ = IAutomationRegistryConsumer(
upkeepInfo_.registryAddress
);
uint256 thresholdBalance_ = uint256(
chainlinkRegistry_.getMinBalance(upkeepID_)
).mul(1e18 + _MIN_BALANCE_BUFFER);
if (chainlinkRegistry_.getBalance(upkeepID_) > thresholdBalance_)
revert UpkeepHasSufficientFunds();
if (topUpAsset.balanceOf(address(this)) < uint256(topUpAmount_))
revert OutOfFunds();

topUpAsset.approve(address(chainlinkRegistry_), topUpAmount_);
chainlinkRegistry_.addFunds(upkeepID_, topUpAmount_);

emit UpkeepPerformed(upkeepID_);
}
}

/// @inheritdoc IMetaKeeper
function setMaxTopUps(uint256 maxTopUps_) external override onlyOwner {
maxTopUps = maxTopUps_;
}

/// @inheritdoc IMetaKeeper
function setTopUpAmount(uint96 topUpAmount_) external override onlyOwner {
topUpAmount = topUpAmount_;
}

/// @inheritdoc IMetaKeeper
function addForwarderAddress(
address forwarderAddress_
) external override onlyOwner {
if (!_forwarderAddresses.add(forwarderAddress_)) {
revert Errors.AlreadyExists();
}
}

/// @inheritdoc IMetaKeeper
function removeForwarderAddress(
address forwarderAddress_
) external override onlyOwner {
if (!_forwarderAddresses.remove(forwarderAddress_)) {
revert Errors.DoesNotExist();
}
}

/// @inheritdoc IMetaKeeper
function recoverAsset(
address receiver,
address asset,
uint256 amount
) external override onlyOwner {
IERC20 token = IERC20(asset);
token.transfer(receiver, amount);
emit AssetRecovered(asset, receiver, amount);
}

/// @inheritdoc IMetaKeeper
function forwarderAddresses()
external
view
override
returns (address[] memory)
{
return _forwarderAddresses.values();
}

/// @inheritdoc AutomationCompatibleInterface
function checkUpkeep(
bytes calldata
)
external
view
override
returns (bool upkeepNeeded, bytes memory performData)
{
ITlxUpkeepRegistry tlxUpkeepRegistry_ = ITlxUpkeepRegistry(
_addressProvider.addressOf(AddressKeys.UPKEEP_REGISTRY)
);
ITlxUpkeepRegistry.Upkeep[] memory upkeeps_ = tlxUpkeepRegistry_
.allUpkeepIDsAndInfo();
MinBalance[] memory minBalances_ = _getMinBalances(upkeeps_);

uint256 maxTopUps_ = maxTopUps;
uint256[] memory upkeepsToTopup_ = new uint256[](maxTopUps_);
uint256 topUpsCount_;
IAutomationRegistryConsumer chainlinkRegistry_;
for (uint256 i_; i_ < upkeeps_.length; i_++) {
uint256 upkeepID_ = upkeeps_[i_].id;
chainlinkRegistry_ = IAutomationRegistryConsumer(
upkeeps_[i_].registryAddress
);
uint256 thresholdBalance_ = _findMinBalance(
minBalances_,
upkeeps_[i_].registryAddress
).mul(1e18 + _MIN_BALANCE_BUFFER);
if (chainlinkRegistry_.getBalance(upkeepID_) > thresholdBalance_)
continue;
upkeepsToTopup_[topUpsCount_] = upkeepID_;
topUpsCount_++;
if (topUpsCount_ == maxTopUps_) break;
}

if (
topUpsCount_ == 0 ||
topUpAsset.balanceOf(address(this)) < uint256(topUpAmount)
) return (false, "");
upkeepNeeded = true;

// solhint-disable-next-line
assembly {
mstore(upkeepsToTopup_, topUpsCount_)
}
performData = abi.encode(upkeepsToTopup_);
}

function _getMinBalances(
ITlxUpkeepRegistry.Upkeep[] memory upkeeps_
) internal view returns (MinBalance[] memory minBalances_) {
minBalances_ = new MinBalance[](upkeeps_.length);
uint256 count;
for (uint256 i_; i_ < upkeeps_.length; i_++) {
bool exists;
for (uint256 j_; j_ < count; j_++) {
if (
upkeeps_[i_].registryAddress ==
minBalances_[j_].registryAddress
) {
exists = true;
break;
}
}
if (exists) continue;

uint256 minBalance_ = IAutomationRegistryConsumer(
upkeeps_[i_].registryAddress
).getMinBalance(upkeeps_[i_].id);
minBalances_[i_] = MinBalance({
registryAddress: upkeeps_[i_].registryAddress,
minBalance: minBalance_
});
count++;
}

// solhint-disable-next-line
assembly {
mstore(minBalances_, count)
}
}

function _findMinBalance(
MinBalance[] memory minBalances_,
address registryAddress_
) internal pure returns (uint256 minBalance_) {
for (uint256 i_; i_ < minBalances_.length; i_++) {
if (minBalances_[i_].registryAddress == registryAddress_) {
minBalance_ = minBalances_[i_].minBalance;
break;
}
}
}
}
60 changes: 56 additions & 4 deletions src/SynthetixHandler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ pragma solidity ^0.8.13;

import {ISynthetixHandler} from "./interfaces/ISynthetixHandler.sol";
import {IAddressProvider} from "./interfaces/IAddressProvider.sol";
import {ILeveragedToken} from "./interfaces/ILeveragedToken.sol";
import {IPerpsV2MarketSettings} from "./interfaces/synthetix/IPerpsV2MarketSettings.sol";
import {IPerpsV2MarketData} from "./interfaces/synthetix/IPerpsV2MarketData.sol";
import {IPerpsV2MarketConsolidated} from "./interfaces/synthetix/IPerpsV2MarketConsolidated.sol";
Expand Down Expand Up @@ -42,6 +43,7 @@ contract SynthetixHandler is ISynthetixHandler {

/// @inheritdoc ISynthetixHandler
function depositMargin(address market_, uint256 amount_) public override {
_validateMintAmount(market_, amount_);
IPerpsV2MarketConsolidated(market_).transferMargin(int256(amount_));
}

Expand Down Expand Up @@ -85,28 +87,42 @@ contract SynthetixHandler is ISynthetixHandler {
);
}

/// @inheritdoc ISynthetixHandler
function computePriceImpact(
address market_,
uint256 leverage_,
uint256 baseAmount_,
bool isLong_,
bool isDeposit_
) public view override returns (uint256, bool) {
// Calculating target size delta
uint256 assetPrice_ = assetPrice(market_);
uint256 absTargetSizeDelta_ = baseAmount_.mul(leverage_).div(
assetPrice_
);
int256 targetSizeDelta_ = int256(absTargetSizeDelta_);

// Calculating price impact
if (!isLong_) targetSizeDelta_ = -targetSizeDelta_; // Invert if shorting
if (!isDeposit_) targetSizeDelta_ = -targetSizeDelta_; // Invert if redeeming
uint256 fillPrice_ = fillPrice(market_, targetSizeDelta_);

bool isLoss = (isLong_ && fillPrice_ > assetPrice_) ||
bool isLoss_ = (isLong_ && fillPrice_ > assetPrice_) ||
(!isLong_ && fillPrice_ < assetPrice_);
uint256 slippage = absTargetSizeDelta_.mul(
uint256 slippage_ = absTargetSizeDelta_.mul(
assetPrice_.absSub(fillPrice_)
);
return (slippage, isLoss);

// Calculating fees
uint256 orderFee_ = _orderFee(market_, targetSizeDelta_);
if (isLoss_) {
slippage_ += orderFee_;
} else if (orderFee_ > slippage_) {
slippage_ = orderFee_ - slippage_;
isLoss_ = true;
} else {
slippage_ -= orderFee_;
}
return (slippage_, isLoss_);
}

/// @inheritdoc ISynthetixHandler
Expand Down Expand Up @@ -263,6 +279,32 @@ contract SynthetixHandler is ISynthetixHandler {
return _marketSettings.maxMarketValue(_key(targetAsset_)).mul(price_);
}

function _validateMintAmount(
address market_,
uint256 mintAmount_
) internal view {
uint256 price_ = assetPrice(market_);
uint256 increase_ = mintAmount_.mul(_leverage()).div(price_);
bytes32 key_ = IPerpsV2MarketConsolidated(market_).marketKey();
uint256 maxMarketValue_ = _marketSettings.maxMarketValue(key_);
uint256 buffer_ = _addressProvider
.parameterProvider()
.maxBaseAssetAmountBuffer();
uint256 max_ = maxMarketValue_.mul(1e18 - buffer_);
(uint256 long_, uint256 short_) = IPerpsV2MarketConsolidated(market_)
.marketSizes();
uint256 currentSize_ = _isLong() ? long_ : short_;
if (currentSize_ + increase_ > max_) revert MaxMarketValueExceeded();
}

function _isLong() internal view returns (bool) {
return ILeveragedToken(address(this)).isLong();
}

function _leverage() internal view returns (uint256) {
return ILeveragedToken(address(this)).targetLeverage();
}

function _pnl(
address market_,
address account_
Expand All @@ -273,6 +315,16 @@ contract SynthetixHandler is ISynthetixHandler {
return pnl_;
}

function _orderFee(
address market_,
int256 sizeDelta_
) internal view returns (uint256) {
(uint256 fee_, bool invalid_) = IPerpsV2MarketConsolidated(market_)
.orderFee(sizeDelta_, IPerpsV2MarketBaseTypes.OrderType.Offchain);
if (invalid_) revert ErrorGettingOrderFee();
return fee_;
}

function _minKeeperFee() internal view returns (uint256) {
return _marketSettings.minKeeperFee();
}
Expand Down
Loading