flashLoan rewritten as ERC-3156
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import "../lib/openzeppelin-contracts/contracts/interfaces/IERC3156FlashBorrower.sol";
|
||||
import "./LMSRStabilized.sol";
|
||||
import {IERC20Metadata} from "../lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol";
|
||||
import {IERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
|
||||
@@ -135,12 +136,14 @@ interface IPartyPool is IERC20Metadata {
|
||||
/// @param maxAmountIn maximum gross input allowed (inclusive of fee)
|
||||
/// @param limitPrice maximum acceptable marginal price (pass 0 to ignore)
|
||||
/// @return amountIn gross input amount to transfer (includes fee), amountOut output amount user would receive, fee fee amount taken
|
||||
/*
|
||||
function swapAmounts(
|
||||
uint256 inputTokenIndex,
|
||||
uint256 outputTokenIndex,
|
||||
uint256 maxAmountIn,
|
||||
int128 limitPrice
|
||||
) external view returns (uint256 amountIn, uint256 amountOut, uint256 fee);
|
||||
*/
|
||||
|
||||
/// @notice Swap input token inputTokenIndex -> token outputTokenIndex. Payer must approve token inputTokenIndex.
|
||||
/// @dev This function transfers the exact gross input (including fee) from payer and sends the computed output to receiver.
|
||||
@@ -215,17 +218,17 @@ interface IPartyPool is IERC20Metadata {
|
||||
uint256 deadline
|
||||
) external returns (uint256 amountOutUint);
|
||||
|
||||
/// @notice Receive token amounts and require them to be repaid plus a fee inside a callback.
|
||||
/// @dev The caller must implement IPartyFlashCallback#partyFlashCallback which receives (amounts, repaymentAmounts, data).
|
||||
/// This function verifies that, after the callback returns, the pool's balances have increased by at least the fees
|
||||
/// for each borrowed token. Reverts if repayment (including fee) did not occur.
|
||||
/// @param recipient The address which will receive the token amounts
|
||||
/// @param amounts The amount of each token to send (array length must equal pool size)
|
||||
/// @param data Any data to be passed through to the callback
|
||||
function flash(
|
||||
address recipient,
|
||||
uint256[] memory amounts,
|
||||
/**
|
||||
* @dev Initiate a flash loan.
|
||||
* @param receiver The receiver of the tokens in the loan, and the receiver of the callback.
|
||||
* @param token The loan currency.
|
||||
* @param amount The amount of tokens lent.
|
||||
* @param data Arbitrary data structure, intended to contain user-defined parameters.
|
||||
*/
|
||||
function flashLoan(
|
||||
IERC3156FlashBorrower receiver,
|
||||
address token,
|
||||
uint256 amount,
|
||||
bytes calldata data
|
||||
) external;
|
||||
|
||||
) external returns (bool);
|
||||
}
|
||||
|
||||
@@ -1,20 +1,22 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import "../lib/openzeppelin-contracts/contracts/interfaces/IERC3156FlashBorrower.sol";
|
||||
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 {Address} from "../lib/openzeppelin-contracts/contracts/utils/Address.sol";
|
||||
import {ReentrancyGuard} from "../lib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol";
|
||||
import {ERC20External} from "./ERC20External.sol";
|
||||
import {IERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
|
||||
import {IPartyFlashCallback} from "./IPartyFlashCallback.sol";
|
||||
import {IPartyPool} from "./IPartyPool.sol";
|
||||
import {LMSRStabilized} from "./LMSRStabilized.sol";
|
||||
import {LMSRStabilizedBalancedPair} from "./LMSRStabilizedBalancedPair.sol";
|
||||
import {LMSRStabilized} from "./LMSRStabilized.sol";
|
||||
import {PartyPoolBase} from "./PartyPoolBase.sol";
|
||||
import {PartyPoolMintImpl} from "./PartyPoolMintImpl.sol";
|
||||
import {PartyPoolSwapImpl} from "./PartyPoolSwapImpl.sol";
|
||||
import {Proxy} from "../lib/openzeppelin-contracts/contracts/proxy/Proxy.sol";
|
||||
import {ReentrancyGuard} from "../lib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol";
|
||||
import {SafeERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
|
||||
import {IERC3156FlashLender} from "../lib/openzeppelin-contracts/contracts/interfaces/IERC3156FlashLender.sol";
|
||||
|
||||
/// @title PartyPool - LMSR-backed multi-asset pool with LP ERC20 token
|
||||
/// @notice A multi-asset liquidity pool backed by the LMSRStabilized pricing model.
|
||||
@@ -196,7 +198,7 @@ contract PartyPool is PartyPoolBase, ERC20External, IPartyPool {
|
||||
Swaps
|
||||
---------------------- */
|
||||
|
||||
/// @inheritdoc IPartyPool
|
||||
/*
|
||||
function swapAmounts(
|
||||
uint256 inputTokenIndex,
|
||||
uint256 outputTokenIndex,
|
||||
@@ -206,6 +208,7 @@ contract PartyPool is PartyPoolBase, ERC20External, IPartyPool {
|
||||
(uint256 grossIn, uint256 outUint,,,, uint256 feeUint) = _quoteSwapExactIn(inputTokenIndex, outputTokenIndex, maxAmountIn, limitPrice);
|
||||
return (grossIn, outUint, feeUint);
|
||||
}
|
||||
*/
|
||||
|
||||
/// @inheritdoc IPartyPool
|
||||
function swap(
|
||||
@@ -295,7 +298,6 @@ contract PartyPool is PartyPoolBase, ERC20External, IPartyPool {
|
||||
require(deltaInternalI > int128(0), "swap: input too small after fee");
|
||||
|
||||
// Compute internal amounts using LMSR (exact-input with price limit)
|
||||
// if _stablePair is true, use the optimized path
|
||||
(amountInInternalUsed, amountOutInternal) = _swapAmountsForExactInput(inputTokenIndex, outputTokenIndex, deltaInternalI, limitPrice);
|
||||
|
||||
// Convert actual used input internal -> uint (ceil)
|
||||
@@ -405,82 +407,38 @@ contract PartyPool is PartyPoolBase, ERC20External, IPartyPool {
|
||||
}
|
||||
|
||||
|
||||
/// @notice Receive token amounts and require them to be repaid plus a fee inside a callback.
|
||||
/// @dev The caller must implement IPartyFlashCallback#partyFlashCallback which receives (amounts, repaymentAmounts, data).
|
||||
/// This function verifies that, after the callback returns, the pool's balances have increased by at least the fees
|
||||
/// for each borrowed token. Reverts if repayment (including fee) did not occur.
|
||||
/// @param recipient The address which will receive the token amounts
|
||||
/// @param amounts The amount of each token to send (array length must equal pool size)
|
||||
/// @param data Any data to be passed through to the callback
|
||||
// todo gas-efficient single-asset flash
|
||||
// todo fix this func's gas
|
||||
function flash(
|
||||
address recipient,
|
||||
uint256[] memory amounts,
|
||||
bytes32 internal constant FLASH_CALLBACK_SUCCESS = keccak256("ERC3156FlashBorrower.onFlashLoan");
|
||||
|
||||
/**
|
||||
* @dev Loan `amount` tokens to `receiver`, and takes it back plus a `flashFee` after the callback.
|
||||
* @param receiver The contract receiving the tokens, needs to implement the `onFlashLoan(address user, uint256 amount, uint256 fee, bytes calldata)` interface.
|
||||
* @param tokenAddr The loan currency.
|
||||
* @param amount The amount of tokens lent.
|
||||
* @param data A data parameter to be passed on to the `receiver` for any custom use.
|
||||
*/
|
||||
function flashLoan(
|
||||
IERC3156FlashBorrower receiver,
|
||||
address tokenAddr,
|
||||
uint256 amount,
|
||||
bytes calldata data
|
||||
) external nonReentrant {
|
||||
require(recipient != address(0), "flash: zero recipient");
|
||||
require(amounts.length == tokens.length, "flash: amounts length mismatch");
|
||||
|
||||
// Calculate repayment amounts for each token including fee
|
||||
uint256[] memory repaymentAmounts = new uint256[](tokens.length);
|
||||
|
||||
// Store initial balances to verify repayment later
|
||||
uint256[] memory initialBalances = new uint256[](tokens.length);
|
||||
|
||||
// Track if any token amount is non-zero
|
||||
bool hasNonZeroAmount = false;
|
||||
|
||||
// Process each token, skipping those with zero amounts
|
||||
for (uint256 i = 0; i < tokens.length; i++) {
|
||||
uint256 amount = amounts[i];
|
||||
|
||||
if (amount > 0) {
|
||||
hasNonZeroAmount = true;
|
||||
|
||||
// Calculate repayment amount with fee (ceiling)
|
||||
repaymentAmounts[i] = amount + _ceilFee(amount, FLASH_FEE_PPM);
|
||||
|
||||
// Record initial balance
|
||||
initialBalances[i] = IERC20(tokens[i]).balanceOf(address(this));
|
||||
|
||||
// Transfer token to recipient
|
||||
tokens[i].safeTransfer(recipient, amount);
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure at least one token is being borrowed
|
||||
require(hasNonZeroAmount, "flash: no tokens requested");
|
||||
|
||||
// Call flash callback with expected repayment amounts
|
||||
IPartyFlashCallback(msg.sender).partyFlashCallback(amounts, repaymentAmounts, data);
|
||||
|
||||
// Verify repayment amounts for tokens that were borrowed
|
||||
for (uint256 i = 0; i < tokens.length; i++) {
|
||||
if (amounts[i] > 0) {
|
||||
uint256 currentBalance = IERC20(tokens[i]).balanceOf(address(this));
|
||||
|
||||
// Compute expected fee (ceiling)
|
||||
uint256 feeExpected = _ceilFee(amounts[i], FLASH_FEE_PPM);
|
||||
|
||||
// Verify repayment: current balance must be at least (initial balance + fee)
|
||||
require(
|
||||
currentBalance >= initialBalances[i] + feeExpected,
|
||||
"flash: repayment failed"
|
||||
);
|
||||
|
||||
// Accrue protocol share (floor) of the flash fee
|
||||
if (PROTOCOL_FEE_PPM > 0 && PROTOCOL_FEE_ADDRESS != address(0)) {
|
||||
uint256 protoShare = (feeExpected * PROTOCOL_FEE_PPM) / 1_000_000; // floor
|
||||
if (protoShare > 0) {
|
||||
protocolFeesOwed[i] += protoShare;
|
||||
}
|
||||
}
|
||||
|
||||
// Update cached balance to onchain minus owed
|
||||
_recordCachedBalance(i, currentBalance);
|
||||
}
|
||||
}
|
||||
) external nonReentrant returns (bool)
|
||||
{
|
||||
IERC20 token = IERC20(tokenAddr);
|
||||
require(amount <= token.balanceOf(address(this)));
|
||||
(uint256 fee, ) = _computeFee(amount, FLASH_FEE_PPM);
|
||||
require(
|
||||
token.transfer(address(receiver), amount),
|
||||
"FlashLender: Transfer failed"
|
||||
);
|
||||
require(
|
||||
receiver.onFlashLoan(msg.sender, address(token), amount, fee, data) == FLASH_CALLBACK_SUCCESS,
|
||||
"FlashLender: Callback failed"
|
||||
);
|
||||
require(
|
||||
token.transferFrom(address(receiver), address(this), amount + fee),
|
||||
"FlashLender: Repay failed"
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
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 {IPartyPool} from "./IPartyPool.sol";
|
||||
import {LMSRStabilized} from "./LMSRStabilized.sol";
|
||||
import {PartyPoolHelpers} from "./PartyPoolHelpers.sol";
|
||||
@@ -151,4 +152,31 @@ contract PartyPoolView is PartyPoolHelpers {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @dev The amount of currency available to be lent.
|
||||
* @param token The loan currency.
|
||||
* @return The amount of `token` that can be borrowed.
|
||||
*/
|
||||
function maxFlashLoan(
|
||||
IPartyPool pool,
|
||||
address token
|
||||
) external view returns (uint256) {
|
||||
return IERC20(token).balanceOf(address(pool));
|
||||
}
|
||||
|
||||
/**
|
||||
* @dev The fee to be charged for a given loan.
|
||||
* @param token The loan currency.
|
||||
* @param amount The amount of tokens lent.
|
||||
* @return fee The amount of `token` to be charged for the loan, on top of the returned principal.
|
||||
*/
|
||||
function flashFee(
|
||||
IPartyPool pool,
|
||||
address token,
|
||||
uint256 amount
|
||||
) external view returns (uint256 fee) {
|
||||
(fee,) = _computeFee(amount, pool.flashFeePpm());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user