diff --git a/contracts/interfaces/clearinghouse/IClearingHouseCustomErrors.sol b/contracts/interfaces/clearinghouse/IClearingHouseCustomErrors.sol index b32b1174..c63770a0 100644 --- a/contracts/interfaces/clearinghouse/IClearingHouseCustomErrors.sol +++ b/contracts/interfaces/clearinghouse/IClearingHouseCustomErrors.sol @@ -69,4 +69,8 @@ interface IClearingHouseCustomErrors is IClearingHouseStructures { /// @notice error to denote an invalid setting for parameters error InvalidSetting(uint256 errorCode); + + error TimelockBreached(); + + error InvalidAtomicSwap(); } diff --git a/contracts/interfaces/clearinghouse/IClearingHouseEvents.sol b/contracts/interfaces/clearinghouse/IClearingHouseEvents.sol index 18f83a9c..f29d2094 100644 --- a/contracts/interfaces/clearinghouse/IClearingHouseEvents.sol +++ b/contracts/interfaces/clearinghouse/IClearingHouseEvents.sol @@ -36,4 +36,18 @@ interface IClearingHouseEvents is IClearingHouseStructures { ); event PausedUpdated(bool paused); + + event AtomicSwapInitiated( + uint256 atomicSwapId, + uint256 senderAccountId, + uint256 receiverAccountId, + int256 vTokenAmount, + int256 vQuoteAmount, + uint32 poolId, + uint64 timelock + ); + + event AtomicSwapExecuted(uint256 atomicSwapId); + + event AtomicSwapAllowanceUpdated(uint256 accountId, bool isAllowed); } diff --git a/contracts/libraries/Account.sol b/contracts/libraries/Account.sol index 82cbfdb2..8897ef10 100644 --- a/contracts/libraries/Account.sol +++ b/contracts/libraries/Account.sol @@ -47,6 +47,7 @@ library Account { address owner; VTokenPosition.Set tokenPositions; CollateralDeposit.Set collateralDeposits; + bool isAtomicSwapAllowed; uint256[100] _emptySlots; // reserved for adding variables when upgrading logic } @@ -389,6 +390,36 @@ library Account { account._updateVQuoteBalance(-int256(limitOrderFee)); } + /// @notice swaps 'vToken' of token amount equal to 'swapParams.amount' + /// @notice if vTokenAmount>0 then the swap is a long or close short and if vTokenAmount<0 then swap is a short or close long + /// @notice isNotional specifies whether the amount represents token amount (false) or vQuote amount(true) + /// @notice isPartialAllowed specifies whether to revert (false) or to execute a partial swap (true) + /// @notice sqrtPriceLimit threshold sqrt price which if crossed then revert or execute partial swap + function atomicSwapToken( + Account.Info storage senderAccount, + Account.Info storage receiverAccount, + uint32 poolId, + int256 vTokenAmount, + int256 vQuoteAmount, + Protocol.Info storage protocol + ) external { + // make a swap. vQuoteIn and vTokenAmountOut (in and out wrt uniswap). + // mints erc20 tokens in callback and send to the pool + IClearingHouseStructures.BalanceAdjustments memory senderBalanceAdjustments = IClearingHouseStructures + .BalanceAdjustments(vQuoteAmount, vTokenAmount, vTokenAmount); + IClearingHouseStructures.BalanceAdjustments memory receiverBalanceAdjustments = IClearingHouseStructures + .BalanceAdjustments(-vQuoteAmount, -vTokenAmount, -vTokenAmount); + + senderAccount.tokenPositions.update(senderAccount.id, senderBalanceAdjustments, poolId, protocol); + receiverAccount.tokenPositions.update(receiverAccount.id, receiverBalanceAdjustments, poolId, protocol); + + //TODO: check if there needs to be settleProfit + + // after all the stuff, account should be above water + senderAccount._checkIfMarginAvailable(true, protocol); + receiverAccount._checkIfMarginAvailable(true, protocol); + } + /** * External view methods */ diff --git a/contracts/libraries/AtomicVTokenSwap.sol b/contracts/libraries/AtomicVTokenSwap.sol new file mode 100644 index 00000000..2ca03d90 --- /dev/null +++ b/contracts/libraries/AtomicVTokenSwap.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +pragma solidity ^0.8.4; + +/// @title Protocol storage functions +/// @dev This is used as main storage interface containing protocol info +library AtomicVTokenSwap { + struct Info { + uint256 senderAccountId; + uint256 receiverAccountId; + int256 vTokenAmount; + int256 vQuoteAmount; + uint32 poolId; + uint64 timelock; + bool complete; + } +} diff --git a/contracts/protocol/clearinghouse/ClearingHouse.sol b/contracts/protocol/clearinghouse/ClearingHouse.sol index d75fad43..25735b6f 100644 --- a/contracts/protocol/clearinghouse/ClearingHouse.sol +++ b/contracts/protocol/clearinghouse/ClearingHouse.sol @@ -13,6 +13,7 @@ import { AddressHelper } from '../../libraries/AddressHelper.sol'; import { BatchedLoop } from '../../libraries/BatchedLoop.sol'; import { Protocol } from '../../libraries/Protocol.sol'; import { SignedMath } from '../../libraries/SignedMath.sol'; +import { AtomicVTokenSwap } from '../../libraries/AtomicVTokenSwap.sol'; import { IClearingHouse } from '../../interfaces/IClearingHouse.sol'; import { IInsuranceFund } from '../../interfaces/IInsuranceFund.sol'; @@ -179,6 +180,18 @@ contract ClearingHouse is if (completed) _unpause(); } + function allowAccountForAtomicSwap(uint256 accountId, bool isAllowed) + external + onlyGovernanceOrTeamMultisig + whenNotPaused + { + bool isAtomicSwapAllowed = accounts[accountId].isAtomicSwapAllowed; + if (isAtomicSwapAllowed != isAllowed) { + accounts[accountId].isAtomicSwapAllowed = isAllowed; + emit AtomicSwapAllowanceUpdated(accountId, isAllowed); + } else revert InvalidSetting(0x40); + } + /// @inheritdoc IClearingHouseOwnerActions function withdrawProtocolFee(uint256 numberOfPoolsToUpdateInThisTx) external { withdrawProtocolFeeLoop.iterate({ @@ -247,6 +260,67 @@ contract ClearingHouse is return _swapToken(account, poolId, swapParams, true); } + function initiateAtomicSwapToken( + uint256 accountId, + uint256 receiverAccountId, + int256 vTokenAmount, + int256 vQuoteAmount, + uint32 poolId, + uint64 timelock + ) external whenNotPaused returns (uint256 atomicSwapId) { + Account.Info storage account = _getAccountAndCheckOwner(accountId); + + atomicSwapId = numAtomicSwaps++; + + //check if both accounts (sender and receiver) are whitelisted for atomic swaps + if (!account.isAtomicSwapAllowed || !accounts[receiverAccountId].isAtomicSwapAllowed) + revert InvalidAtomicSwap(); + + atomicSwaps[atomicSwapId] = AtomicVTokenSwap.Info( + accountId, + receiverAccountId, + vTokenAmount, + vQuoteAmount, + poolId, + timelock, + false + ); + + emit AtomicSwapInitiated( + atomicSwapId, + accountId, + receiverAccountId, + vTokenAmount, + vQuoteAmount, + poolId, + timelock + ); + } + + function executeAtomicSwapToken(uint256 atomicSwapId) external whenNotPaused { + AtomicVTokenSwap.Info memory swapInfo = atomicSwaps[atomicSwapId]; + Account.Info storage receiverAccount = _getAccountAndCheckOwner(swapInfo.receiverAccountId); + Account.Info storage senderAccount = accounts[swapInfo.senderAccountId]; + _updateAccountPoolPrices(senderAccount); + _updateAccountPoolPrices(receiverAccount); + + // check timelock + if (block.timestamp > swapInfo.timelock) revert TimelockBreached(); + + Account.atomicSwapToken( + senderAccount, + receiverAccount, + swapInfo.poolId, + swapInfo.vTokenAmount, + swapInfo.vQuoteAmount, + protocol + ); + + swapInfo.completed = true; + + emit AtomicSwapExecuted(atomicSwapId); + } + /// @inheritdoc IClearingHouseActions function updateRangeOrder( uint256 accountId, diff --git a/contracts/protocol/clearinghouse/ClearingHouseStorage.sol b/contracts/protocol/clearinghouse/ClearingHouseStorage.sol index 108f72a2..dd037e43 100644 --- a/contracts/protocol/clearinghouse/ClearingHouseStorage.sol +++ b/contracts/protocol/clearinghouse/ClearingHouseStorage.sol @@ -5,6 +5,7 @@ pragma solidity =0.8.14; import { Account } from '../../libraries/Account.sol'; import { BatchedLoop } from '../../libraries/BatchedLoop.sol'; import { Protocol } from '../../libraries/Protocol.sol'; +import { AtomicVTokenSwap } from '../../libraries/AtomicVTokenSwap.sol'; import { IInsuranceFund } from '../../interfaces/IInsuranceFund.sol'; import { IOracle } from '../../interfaces/IOracle.sol'; @@ -28,6 +29,10 @@ abstract contract ClearingHouseStorage { BatchedLoop.Info internal unpauseLoop; BatchedLoop.Info internal withdrawProtocolFeeLoop; + // storage for atomic token swap + mapping(uint256 => AtomicVTokenSwap.Info) atomicSwaps; + uint256 public numAtomicSwaps; + // reserved for adding slots in future - uint256[100] private _emptySlots2; + uint256[98] private _emptySlots2; }