213 lines
9.2 KiB
Solidity
213 lines
9.2 KiB
Solidity
// SPDX-License-Identifier: UNLICENSED
|
|
pragma solidity ^0.8.30;
|
|
|
|
import {ABDKMath64x64} from "../lib/abdk-libraries-solidity/ABDKMath64x64.sol";
|
|
import {IERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
|
|
import {SafeERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
|
|
import {IPartyPool} from "./IPartyPool.sol";
|
|
import {NativeWrapper} from "./NativeWrapper.sol";
|
|
import {LMSRStabilized} from "./LMSRStabilized.sol";
|
|
import {PartyPoolBase} from "./PartyPoolBase.sol";
|
|
import {IERC3156FlashBorrower} from "../lib/openzeppelin-contracts/contracts/interfaces/IERC3156FlashBorrower.sol";
|
|
|
|
/// @title PartyPoolSwapMintImpl - Implementation contract for swapMint and burnSwap functions
|
|
/// @notice This contract contains the swapMint and burnSwap implementation that will be called via delegatecall
|
|
/// @dev This contract inherits from PartyPoolBase to access storage and internal functions
|
|
contract PartyPoolSwapImpl is PartyPoolBase {
|
|
using ABDKMath64x64 for int128;
|
|
using LMSRStabilized for LMSRStabilized.State;
|
|
using SafeERC20 for IERC20;
|
|
|
|
constructor(NativeWrapper wrapper_) {WRAPPER = wrapper_;}
|
|
|
|
bytes32 internal constant FLASH_CALLBACK_SUCCESS = keccak256("ERC3156FlashBorrower.onFlashLoan");
|
|
|
|
function flashLoan(
|
|
IERC3156FlashBorrower receiver,
|
|
address tokenAddr,
|
|
uint256 amount,
|
|
bytes calldata data,
|
|
uint256 flashFeePpm,
|
|
uint256 protocolFeePpm
|
|
) external nonReentrant killable returns (bool) {
|
|
IERC20 token = IERC20(tokenAddr);
|
|
require(amount <= token.balanceOf(address(this)), "flash: amount > balance");
|
|
uint256 tokenIndex = _tokenAddressToIndexPlusOne[token];
|
|
require(tokenIndex != 0, 'flash: token not in pool');
|
|
tokenIndex -= 1;
|
|
(uint256 flashFee, ) = _computeFee(amount, flashFeePpm);
|
|
|
|
// Compute protocol share of flash fee
|
|
uint256 protoShare = 0;
|
|
if (protocolFeePpm > 0 && flashFee > 0) {
|
|
protoShare = (flashFee * protocolFeePpm) / 1_000_000; // floor
|
|
if (protoShare > 0) {
|
|
_protocolFeesOwed[tokenIndex] += protoShare;
|
|
}
|
|
}
|
|
|
|
_sendTokenTo(token, address(receiver), amount, false);
|
|
require(
|
|
receiver.onFlashLoan(msg.sender, address(token), amount, flashFee, data) == FLASH_CALLBACK_SUCCESS,
|
|
'flash: callback'
|
|
);
|
|
_receiveTokenFrom(address(receiver), token, amount + flashFee);
|
|
|
|
// Update cached balance for the borrowed token
|
|
uint256 balAfter = token.balanceOf(address(this));
|
|
require(balAfter >= _protocolFeesOwed[tokenIndex], "balance < protocol owed");
|
|
_cachedUintBalances[tokenIndex] = balAfter - _protocolFeesOwed[tokenIndex];
|
|
|
|
emit IPartyPool.Flash(msg.sender, receiver, token, amount, flashFee - protoShare, protoShare);
|
|
return true;
|
|
}
|
|
|
|
function swapToLimitAmounts(
|
|
uint256 inputTokenIndex,
|
|
uint256 outputTokenIndex,
|
|
int128 limitPrice,
|
|
uint256[] memory bases,
|
|
int128 kappa,
|
|
int128[] memory qInternal,
|
|
uint256 swapFeePpm
|
|
) external pure returns (uint256 amountIn, uint256 amountOut, uint256 inFee) {
|
|
// Compute internal maxima at the price limit
|
|
(int128 amountInInternal, int128 amountOutInternal) = LMSRStabilized.swapAmountsForPriceLimit(
|
|
kappa, qInternal,
|
|
inputTokenIndex, outputTokenIndex, limitPrice);
|
|
|
|
// Convert input to uint (ceil) and output to uint (floor)
|
|
uint256 amountInUintNoFee = _internalToUintCeil(amountInInternal, bases[inputTokenIndex]);
|
|
require(amountInUintNoFee > 0, "swapToLimit: input zero");
|
|
|
|
inFee = 0;
|
|
amountIn = amountInUintNoFee;
|
|
if (swapFeePpm > 0) {
|
|
inFee = _ceilFee(amountInUintNoFee, swapFeePpm);
|
|
amountIn += inFee;
|
|
}
|
|
|
|
amountOut = _internalToUintFloor(amountOutInternal, bases[outputTokenIndex]);
|
|
require(amountOut > 0, "swapToLimit: output zero");
|
|
}
|
|
|
|
|
|
function swapToLimit(
|
|
address payer,
|
|
bytes4 fundingSelector,
|
|
address receiver,
|
|
uint256 inputTokenIndex,
|
|
uint256 outputTokenIndex,
|
|
int128 limitPrice,
|
|
uint256 deadline,
|
|
bool unwrap,
|
|
bytes memory cbData,
|
|
uint256 swapFeePpm,
|
|
uint256 protocolFeePpm
|
|
) external payable native killable nonReentrant returns (uint256 amountInUsed, uint256 amountOut, uint256 inFee) {
|
|
uint256 n = _tokens.length;
|
|
require(inputTokenIndex < n && outputTokenIndex < n, "swapToLimit: idx");
|
|
require(limitPrice > int128(0), "swapToLimit: limit <= 0");
|
|
require(deadline == 0 || block.timestamp <= deadline, "swapToLimit: deadline exceeded");
|
|
|
|
// Read previous balances for affected assets
|
|
uint256 prevBalJ = _cachedUintBalances[outputTokenIndex];
|
|
|
|
// Compute amounts using the same path as views
|
|
(uint256 totalTransferAmount, uint256 amountOutUint, int128 amountInInternalMax, int128 amountOutInternal, uint256 amountInUsedUint, uint256 feeUint) =
|
|
_quoteSwapToLimit(inputTokenIndex, outputTokenIndex, limitPrice, swapFeePpm);
|
|
|
|
// Transfer the exact amount needed from payer
|
|
IERC20 tokenIn = _tokens[inputTokenIndex];
|
|
_receiveTokenFrom(payer, fundingSelector, inputTokenIndex, tokenIn, totalTransferAmount, limitPrice, cbData);
|
|
|
|
// Transfer output to receiver and verify exact decrease
|
|
IERC20 tokenOut = _tokens[outputTokenIndex];
|
|
_sendTokenTo(tokenOut, receiver, amountOutUint, unwrap);
|
|
uint256 balJAfter = IERC20(tokenOut).balanceOf(address(this));
|
|
require(balJAfter == prevBalJ - amountOutUint, "swapToLimit: non-standard tokenOut");
|
|
|
|
// Accrue protocol share (floor) from the fee on input token
|
|
uint256 protoShare = 0;
|
|
if (protocolFeePpm > 0 && feeUint > 0 ) {
|
|
protoShare = (feeUint * protocolFeePpm) / 1_000_000; // floor
|
|
if (protoShare > 0) {
|
|
_protocolFeesOwed[inputTokenIndex] += protoShare;
|
|
}
|
|
}
|
|
|
|
require(balJAfter >= _protocolFeesOwed[outputTokenIndex], "balance < protocol owed");
|
|
_cachedUintBalances[outputTokenIndex] = balJAfter - _protocolFeesOwed[outputTokenIndex];
|
|
|
|
// Apply swap to LMSR state with the internal amounts
|
|
_lmsr.applySwap(inputTokenIndex, outputTokenIndex, amountInInternalMax, amountOutInternal);
|
|
|
|
// Maintain original event semantics (logs input without fee)
|
|
emit IPartyPool.Swap(payer, receiver, tokenIn, tokenOut,
|
|
amountInUsedUint, amountOutUint, feeUint-protoShare, protoShare);
|
|
|
|
return (amountInUsedUint, amountOutUint, feeUint);
|
|
}
|
|
|
|
|
|
/// @notice Internal quote for swap-to-limit that mirrors swapToLimit() rounding and fee application
|
|
/// @dev Computes the input required to reach limitPrice and the resulting output; all rounding matches swapToLimit.
|
|
/// @return grossIn amount to transfer in (inclusive of fee), amountOutUint output amount (uint),
|
|
/// amountInInternal and amountOutInternal (64.64), amountInUintNoFee input amount excluding fee (uint),
|
|
/// feeUint fee taken from the gross input (uint)
|
|
function _quoteSwapToLimit(
|
|
uint256 inputTokenIndex,
|
|
uint256 outputTokenIndex,
|
|
int128 limitPrice,
|
|
uint256 swapFeePpm
|
|
) internal view
|
|
returns (
|
|
uint256 grossIn,
|
|
uint256 amountOutUint,
|
|
int128 amountInInternal,
|
|
int128 amountOutInternal,
|
|
uint256 amountInUintNoFee,
|
|
uint256 feeUint
|
|
)
|
|
{
|
|
// Compute internal maxima at the price limit
|
|
(amountInInternal, amountOutInternal) = _lmsr.swapAmountsForPriceLimit(inputTokenIndex, outputTokenIndex, limitPrice);
|
|
|
|
// Convert input to uint (ceil) and output to uint (floor)
|
|
amountInUintNoFee = _internalToUintCeil(amountInInternal, _bases[inputTokenIndex]);
|
|
require(amountInUintNoFee > 0, "swapToLimit: input zero");
|
|
|
|
feeUint = 0;
|
|
grossIn = amountInUintNoFee;
|
|
if (swapFeePpm > 0) {
|
|
feeUint = _ceilFee(amountInUintNoFee, swapFeePpm);
|
|
grossIn += feeUint;
|
|
}
|
|
|
|
amountOutUint = _internalToUintFloor(amountOutInternal, _bases[outputTokenIndex]);
|
|
require(amountOutUint > 0, "swapToLimit: output zero");
|
|
}
|
|
|
|
|
|
/// @notice Transfer all protocol fees to the configured protocolFeeAddress and zero the ledger.
|
|
/// @dev Anyone can call; must have protocolFeeAddress != address(0) to be operational.
|
|
function collectProtocolFees(address dest) external nonReentrant {
|
|
require(dest != address(0), "collect: zero addr");
|
|
|
|
uint256 n = _tokens.length;
|
|
for (uint256 i = 0; i < n; i++) {
|
|
uint256 owed = _protocolFeesOwed[i];
|
|
if (owed == 0) continue;
|
|
uint256 bal = IERC20(_tokens[i]).balanceOf(address(this));
|
|
require(bal >= owed, "collect: fee > bal");
|
|
_protocolFeesOwed[i] = 0;
|
|
// update cached to effective onchain minus owed
|
|
_cachedUintBalances[i] = bal - owed;
|
|
// transfer owed _tokens to protocol destination via centralized helper
|
|
_sendTokenTo(_tokens[i], dest, owed, false);
|
|
}
|
|
emit IPartyPool.ProtocolFeesCollected();
|
|
}
|
|
|
|
}
|