CREATE2 callback validation; init code storage contracts
This commit is contained in:
@@ -15,7 +15,7 @@ contract ERC20External is ERC20Internal, IERC20Metadata {
|
||||
*
|
||||
* Both values are immutable: they can only be set once during construction.
|
||||
*/
|
||||
constructor(string memory name_, string memory symbol_) {
|
||||
function erc20Constructor(string memory name_, string memory symbol_) internal {
|
||||
_name = name_;
|
||||
_symbol = symbol_;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ abstract contract ERC20Internal is Context, IERC20Errors {
|
||||
string internal _symbol;
|
||||
|
||||
|
||||
/**
|
||||
/**
|
||||
* @dev Moves a `value` amount of _tokens from `from` to `to`.
|
||||
*
|
||||
* This internal function is equivalent to {transfer}, and can be used to
|
||||
|
||||
@@ -4,7 +4,7 @@ pragma solidity ^0.8.30;
|
||||
|
||||
library Funding {
|
||||
/// @notice a constant passed to swap as the fundingSelector to indicate that the payer has used regular ERC20 approvals to allow the pool to move the necessary input tokens.
|
||||
bytes4 internal constant APPROVALS = 0x00000000;
|
||||
bytes4 internal constant APPROVAL = 0x00000000;
|
||||
|
||||
/// @notice a constant passed to swap as the fundingSelector to indicate that the payer has already sent sufficient input tokens to the pool before calling swap, so no movement of input tokens is required.
|
||||
bytes4 internal constant PREFUNDING = 0x00000001;
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import {IERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
|
||||
|
||||
import "./IPartyPool.sol";
|
||||
import "./PartyPoolMintImpl.sol";
|
||||
import "./PartyPoolSwapImpl.sol";
|
||||
import {IOwnable} from "./IOwnable.sol";
|
||||
import {IPartyPool} from "./IPartyPool.sol";
|
||||
import {PartyPoolMintImpl} from "./PartyPoolMintImpl.sol";
|
||||
import {PartyPoolSwapImpl} from "./PartyPoolSwapImpl.sol";
|
||||
|
||||
/// @title IPartyPlanner
|
||||
/// @notice Interface for factory contract for creating and tracking PartyPool instances
|
||||
@@ -13,6 +13,38 @@ interface IPartyPlanner is IOwnable {
|
||||
// Event emitted when a new pool is created
|
||||
event PartyStarted(IPartyPool indexed pool, string name, string symbol, IERC20[] tokens);
|
||||
|
||||
|
||||
/// @notice Primary method for creating a new pool. May only be called by the PartyPlanner owner account.
|
||||
/// @param name LP token name
|
||||
/// @param symbol LP token symbol
|
||||
/// @param tokens token addresses
|
||||
/// @param kappa liquidity parameter κ in 64.64 fixed-point used to derive b = κ * S(q)
|
||||
/// @param swapFeesPpm per-asset fees in parts-per-million, taken from swap input amounts before LMSR calculations
|
||||
/// @param flashFeePpm fee in parts-per-million, taken for flash loans
|
||||
/// @param stable if true and assets.length==2, then the optimization for 2-asset stablecoin pools is activated
|
||||
/// @param payer address that provides the initial token deposits
|
||||
/// @param receiver address that receives the minted LP tokens
|
||||
/// @param initialDeposits amounts of each token to deposit initially
|
||||
/// @param deadline Reverts if nonzero and the current blocktime is later than the deadline
|
||||
/// @return pool Address of the newly created and initialized PartyPool
|
||||
/// @return lpAmount Amount of LP tokens minted to the receiver
|
||||
function newPool(
|
||||
// Pool constructor args
|
||||
string memory name,
|
||||
string memory symbol,
|
||||
IERC20[] memory tokens,
|
||||
int128 kappa,
|
||||
uint256[] memory swapFeesPpm,
|
||||
uint256 flashFeePpm,
|
||||
bool stable,
|
||||
// Initial deposit information
|
||||
address payer,
|
||||
address receiver,
|
||||
uint256[] memory initialDeposits,
|
||||
uint256 initialLpAmount,
|
||||
uint256 deadline
|
||||
) external returns (IPartyPool pool, uint256 lpAmount);
|
||||
|
||||
/// @notice Creates a new PartyPool instance and initializes it with initial deposits (legacy signature).
|
||||
/// @dev Deprecated in favour of the kappa-based overload below; kept for backwards compatibility.
|
||||
/// @param name LP token name
|
||||
|
||||
@@ -82,7 +82,7 @@ interface IPartyPool is IERC20Metadata, IOwnable {
|
||||
|
||||
/// @notice Token addresses comprising the pool. Effectively immutable after construction.
|
||||
/// @dev tokens[i] corresponds to the i-th asset and maps to index i in the internal LMSR arrays.
|
||||
function getToken(uint256) external view returns (IERC20); // get single token
|
||||
function token(uint256) external view returns (IERC20); // get single token
|
||||
|
||||
/// @notice Returns the number of tokens (n) in the pool.
|
||||
function numTokens() external view returns (uint256);
|
||||
@@ -185,6 +185,8 @@ interface IPartyPool is IERC20Metadata, IOwnable {
|
||||
/// @param maxAmountIn maximum amount of token inputTokenIndex (uint256) to transfer in (inclusive of fees)
|
||||
/// @param limitPrice maximum acceptable marginal price (64.64 fixed point). Pass 0 to ignore.
|
||||
/// @param deadline timestamp after which the transaction will revert. Pass 0 to ignore.
|
||||
/// @param unwrap If true, then any output of wrapper token will be unwrapped and native ETH sent to the receiver.
|
||||
/// @param cbData callback data if fundingSelector is of the callback type.
|
||||
/// @return amountIn actual input used (uint256), amountOut actual output sent (uint256), inFee fee taken from the input (uint256)
|
||||
function swap(
|
||||
address payer,
|
||||
@@ -195,9 +197,11 @@ interface IPartyPool is IERC20Metadata, IOwnable {
|
||||
uint256 maxAmountIn,
|
||||
int128 limitPrice,
|
||||
uint256 deadline,
|
||||
bool unwrap
|
||||
bool unwrap,
|
||||
bytes memory cbData
|
||||
) external payable returns (uint256 amountIn, uint256 amountOut, uint256 inFee);
|
||||
|
||||
|
||||
/// @notice Swap up to the price limit; computes max input to reach limit then performs swap.
|
||||
/// @dev If balances prevent fully reaching the limit, the function caps and returns actuals.
|
||||
/// The payer must transfer the exact gross input computed by the view.
|
||||
@@ -210,12 +214,14 @@ interface IPartyPool is IERC20Metadata, IOwnable {
|
||||
/// @return amountInUsed actual input used excluding fee (uint256), amountOut actual output sent (uint256), inFee fee taken from the input (uint256)
|
||||
function swapToLimit(
|
||||
address payer,
|
||||
bytes4 fundingSelector,
|
||||
address receiver,
|
||||
uint256 inputTokenIndex,
|
||||
uint256 outputTokenIndex,
|
||||
int128 limitPrice,
|
||||
uint256 deadline,
|
||||
bool unwrap
|
||||
bool unwrap,
|
||||
bytes memory cbData
|
||||
) external payable returns (uint256 amountInUsed, uint256 amountOut, uint256 inFee);
|
||||
|
||||
/// @notice Single-token mint: deposit a single token, charge swap-LMSR cost, and mint LP.
|
||||
|
||||
45
src/IPartyPoolDeployer.sol
Normal file
45
src/IPartyPoolDeployer.sol
Normal file
@@ -0,0 +1,45 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import {IERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
|
||||
import {IPartyPool} from "./IPartyPool.sol";
|
||||
import {NativeWrapper} from "./NativeWrapper.sol";
|
||||
import {PartyPoolMintImpl} from "./PartyPoolMintImpl.sol";
|
||||
import {PartyPoolSwapImpl} from "./PartyPoolSwapImpl.sol";
|
||||
|
||||
interface IPartyPoolDeployer {
|
||||
|
||||
/// @notice Parameters for deploying a new PartyPool
|
||||
struct DeployParams {
|
||||
/// @notice Used for callback validation
|
||||
bytes32 nonce;
|
||||
/// @notice Admin account that can disable the vault using kill()
|
||||
address owner;
|
||||
/// @notice LP token name
|
||||
string name;
|
||||
/// @notice LP token symbol
|
||||
string symbol;
|
||||
/// @notice Token addresses (n)
|
||||
IERC20[] tokens;
|
||||
/// @notice Liquidity parameter κ (Q64.64) used to derive b = κ * S(q)
|
||||
int128 kappa;
|
||||
/// @notice Per-asset swap fees in ppm (length must equal tokens.length)
|
||||
uint256[] fees;
|
||||
/// @notice Fee in parts-per-million, taken for flash loans
|
||||
uint256 flashFeePpm;
|
||||
/// @notice Protocol fee in parts-per-million
|
||||
uint256 protocolFeePpm;
|
||||
/// @notice Address to receive protocol fees
|
||||
address protocolFeeAddress;
|
||||
/// @notice Native token wrapper contract
|
||||
NativeWrapper wrapper;
|
||||
/// @notice Address of the SwapMint implementation contract
|
||||
PartyPoolSwapImpl swapImpl;
|
||||
/// @notice Address of the Mint implementation contract
|
||||
PartyPoolMintImpl mintImpl;
|
||||
}
|
||||
|
||||
function params() external view returns (DeployParams memory);
|
||||
}
|
||||
|
||||
|
||||
10
src/IPartySwapCallback.sol
Normal file
10
src/IPartySwapCallback.sol
Normal file
@@ -0,0 +1,10 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import {IERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
|
||||
|
||||
interface IPartySwapCallback {
|
||||
// The callback may have any function name. Pass your callback function selector to the swap method as the fundingSelector
|
||||
function liquidityPartySwapCallback(bytes32 nonce, IERC20 inputToken, uint256 amount, bytes memory data) external;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ abstract contract OwnableExternal is OwnableInternal, IOwnable {
|
||||
/**
|
||||
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
|
||||
*/
|
||||
constructor(address initialOwner) {
|
||||
function ownableConstructor(address initialOwner) internal {
|
||||
if (initialOwner == address(0)) {
|
||||
revert OwnableInvalidOwner(address(0));
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ contract PartyInfo is PartyPoolHelpers, IPartyInfo {
|
||||
uint256 nAssets = lmsr.qInternal.length;
|
||||
uint256[] memory cachedUintBalances = new uint256[](nAssets);
|
||||
for( uint256 i=0; i<nAssets; i++ )
|
||||
cachedUintBalances[i] = pool.getToken(i).balanceOf(address(pool));
|
||||
cachedUintBalances[i] = pool.token(i).balanceOf(address(pool));
|
||||
return MINT_IMPL.mintAmounts(lpTokenAmount, pool.totalSupply(), cachedUintBalances);
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ contract PartyInfo is PartyPoolHelpers, IPartyInfo {
|
||||
uint256 nAssets = lmsr.qInternal.length;
|
||||
uint256[] memory cachedUintBalances = new uint256[](nAssets);
|
||||
for( uint256 i=0; i<nAssets; i++ )
|
||||
cachedUintBalances[i] = pool.getToken(i).balanceOf(address(pool));
|
||||
cachedUintBalances[i] = pool.token(i).balanceOf(address(pool));
|
||||
return MINT_IMPL.burnAmounts(lpTokenAmount, pool.totalSupply(), cachedUintBalances);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,17 +5,19 @@ import {IERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20
|
||||
import {SafeERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
|
||||
import {IPartyPlanner} from "./IPartyPlanner.sol";
|
||||
import {IPartyPool} from "./IPartyPool.sol";
|
||||
import {IPartyPoolDeployer} from "./IPartyPoolDeployer.sol";
|
||||
import {LMSRStabilized} from "./LMSRStabilized.sol";
|
||||
import {NativeWrapper} from "./NativeWrapper.sol";
|
||||
import {OwnableExternal} from "./OwnableExternal.sol";
|
||||
import {OwnableInternal} from "./OwnableInternal.sol";
|
||||
import {IPartyPoolDeployer} from "./PartyPoolDeployer.sol";
|
||||
import {PartyPoolDeployer, PartyPoolInitCode, PartyPoolBalancedPairInitCode} from "./PartyPoolDeployer.sol";
|
||||
import {PartyPoolMintImpl} from "./PartyPoolMintImpl.sol";
|
||||
import {PartyPoolSwapImpl} from "./PartyPoolSwapImpl.sol";
|
||||
|
||||
/// @title PartyPlanner
|
||||
/// @notice Factory contract for creating and tracking PartyPool instances
|
||||
contract PartyPlanner is OwnableExternal, IPartyPlanner {
|
||||
/// @dev Inherits from PartyPoolDeployer to handle pool deployment directly
|
||||
contract PartyPlanner is PartyPoolDeployer, OwnableExternal, IPartyPlanner {
|
||||
using SafeERC20 for IERC20;
|
||||
int128 private constant ONE = int128(1) << 64;
|
||||
|
||||
@@ -38,9 +40,6 @@ contract PartyPlanner is OwnableExternal, IPartyPlanner {
|
||||
NativeWrapper private immutable WRAPPER;
|
||||
function wrapper() external view returns (NativeWrapper) { return WRAPPER; }
|
||||
|
||||
IPartyPoolDeployer private immutable NORMAL_POOL_DEPLOYER;
|
||||
IPartyPoolDeployer private immutable BALANCED_PAIR_DEPLOYER;
|
||||
|
||||
// On-chain pool indexing
|
||||
IPartyPool[] private _allPools;
|
||||
IERC20[] private _allTokens;
|
||||
@@ -52,6 +51,8 @@ contract PartyPlanner is OwnableExternal, IPartyPlanner {
|
||||
/// @param wrapper_ The WETH9 implementation address used for this chain
|
||||
/// @param swapImpl_ address of the Swap implementation contract to be used by all pools
|
||||
/// @param mintImpl_ address of the Mint implementation contract to be used by all pools
|
||||
/// @param poolInitCodeStorage_ address of the storage contract holding PartyPool init code
|
||||
/// @param balancedPairInitCodeStorage_ address of the storage contract holding PartyPoolBalancedPair init code
|
||||
/// @param protocolFeePpm_ protocol fee share (ppm) to be used for pools created by this planner
|
||||
/// @param protocolFeeAddress_ recipient address for protocol fees for pools created by this planner (may be address(0))
|
||||
constructor(
|
||||
@@ -59,22 +60,19 @@ contract PartyPlanner is OwnableExternal, IPartyPlanner {
|
||||
NativeWrapper wrapper_,
|
||||
PartyPoolSwapImpl swapImpl_,
|
||||
PartyPoolMintImpl mintImpl_,
|
||||
IPartyPoolDeployer deployer_,
|
||||
IPartyPoolDeployer balancedPairDeployer_,
|
||||
PartyPoolInitCode poolInitCodeStorage_,
|
||||
PartyPoolBalancedPairInitCode balancedPairInitCodeStorage_,
|
||||
uint256 protocolFeePpm_,
|
||||
address protocolFeeAddress_
|
||||
)
|
||||
OwnableExternal(owner_)
|
||||
PartyPoolDeployer(poolInitCodeStorage_, balancedPairInitCodeStorage_)
|
||||
{
|
||||
ownableConstructor(owner_);
|
||||
WRAPPER = wrapper_;
|
||||
require(address(swapImpl_) != address(0), "Planner: swapImpl address cannot be zero");
|
||||
SWAP_IMPL = swapImpl_;
|
||||
require(address(mintImpl_) != address(0), "Planner: mintImpl address cannot be zero");
|
||||
MINT_IMPL = mintImpl_;
|
||||
require(address(deployer_) != address(0), "Planner: deployer address cannot be zero");
|
||||
NORMAL_POOL_DEPLOYER = deployer_;
|
||||
require(address(balancedPairDeployer_) != address(0), "Planner: balanced pair deployer address cannot be zero");
|
||||
BALANCED_PAIR_DEPLOYER = balancedPairDeployer_;
|
||||
|
||||
require(protocolFeePpm_ < 1_000_000, "Planner: protocol fee >= ppm");
|
||||
PROTOCOL_FEE_PPM = protocolFeePpm_;
|
||||
@@ -111,8 +109,8 @@ contract PartyPlanner is OwnableExternal, IPartyPlanner {
|
||||
require(swapFeesPpm_.length == tokens_.length, "Planner: fees and tokens length mismatch");
|
||||
|
||||
// Create a new PartyPool instance (kappa-based constructor)
|
||||
IPartyPoolDeployer deployer = stable_ && tokens_.length == 2 ? BALANCED_PAIR_DEPLOYER : NORMAL_POOL_DEPLOYER;
|
||||
pool = deployer.deploy(
|
||||
IPartyPoolDeployer.DeployParams memory params = IPartyPoolDeployer.DeployParams(
|
||||
0, // This is set by the deployer
|
||||
_owner, // Same owner as this PartyPlanner
|
||||
name_,
|
||||
symbol_,
|
||||
@@ -127,6 +125,13 @@ contract PartyPlanner is OwnableExternal, IPartyPlanner {
|
||||
MINT_IMPL
|
||||
);
|
||||
|
||||
// Use inherited deploy methods based on pool type
|
||||
if (stable_ && tokens_.length == 2) {
|
||||
pool = _deployBalancedPair(params);
|
||||
} else {
|
||||
pool = _deploy(params);
|
||||
}
|
||||
|
||||
_allPools.push(pool);
|
||||
_poolSupported[pool] = true;
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import {OwnableInternal} from "./OwnableInternal.sol";
|
||||
import {PartyPoolBase} from "./PartyPoolBase.sol";
|
||||
import {PartyPoolMintImpl} from "./PartyPoolMintImpl.sol";
|
||||
import {PartyPoolSwapImpl} from "./PartyPoolSwapImpl.sol";
|
||||
import {IPartyPoolDeployer} from "./IPartyPoolDeployer.sol";
|
||||
|
||||
/// @title PartyPool - LMSR-backed multi-asset pool with LP ERC20 token
|
||||
/// @notice A multi-asset liquidity pool backed by the LMSRStabilized pricing model.
|
||||
@@ -43,7 +44,7 @@ contract PartyPool is PartyPoolBase, OwnableExternal, ERC20External, IPartyPool
|
||||
/// @notice If true, the vault has been disabled by the owner and only burns (withdrawals) are allowed.
|
||||
function killed() external view returns (bool) { return _killed; }
|
||||
|
||||
function wrapperToken() external view returns (NativeWrapper) { return WRAPPER_TOKEN; }
|
||||
function wrapperToken() external view returns (NativeWrapper) { return WRAPPER; }
|
||||
|
||||
/// @notice Liquidity parameter κ (Q64.64) used by the LMSR kernel: b = κ * S(q)
|
||||
/// @dev Pool is constructed with a fixed κ. Clients that previously passed tradeFrac/targetSlippage
|
||||
@@ -80,7 +81,7 @@ contract PartyPool is PartyPoolBase, OwnableExternal, ERC20External, IPartyPool
|
||||
function swapMintImpl() external view returns (PartyPoolSwapImpl) { return SWAP_IMPL; }
|
||||
|
||||
/// @inheritdoc IPartyPool
|
||||
function getToken(uint256 i) external view returns (IERC20) { return _tokens[i]; }
|
||||
function token(uint256 i) external view returns (IERC20) { return _tokens[i]; }
|
||||
|
||||
/// @inheritdoc IPartyPool
|
||||
function numTokens() external view returns (uint256) { return _tokens.length; }
|
||||
@@ -94,61 +95,42 @@ contract PartyPool is PartyPoolBase, OwnableExternal, ERC20External, IPartyPool
|
||||
/// @inheritdoc IPartyPool
|
||||
function LMSR() external view returns (LMSRStabilized.State memory) { return _lmsr; }
|
||||
|
||||
|
||||
/// @param owner_ Admin account that can disable the vault using kill()
|
||||
/// @param name_ LP token name
|
||||
/// @param symbol_ LP token symbol
|
||||
/// @param tokens_ token addresses (n)
|
||||
/// @param kappa_ liquidity parameter κ (Q64.64) used to derive b = κ * S(q)
|
||||
/// @param fees_ per-asset swap fees in ppm (length must equal tokens_.length)
|
||||
/// @param flashFeePpm_ fee in parts-per-million, taken for flash loans
|
||||
/// @param swapImpl_ address of the SwapMint implementation contract
|
||||
/// @param mintImpl_ address of the Mint implementation contract
|
||||
constructor(
|
||||
address owner_,
|
||||
string memory name_,
|
||||
string memory symbol_,
|
||||
IERC20[] memory tokens_,
|
||||
int128 kappa_,
|
||||
uint256[] memory fees_,
|
||||
uint256 flashFeePpm_,
|
||||
uint256 protocolFeePpm_,
|
||||
address protocolFeeAddress_,
|
||||
NativeWrapper wrapperToken_,
|
||||
PartyPoolSwapImpl swapImpl_,
|
||||
PartyPoolMintImpl mintImpl_
|
||||
)
|
||||
PartyPoolBase(wrapperToken_)
|
||||
OwnableExternal(owner_)
|
||||
ERC20External(name_, symbol_)
|
||||
constructor()
|
||||
{
|
||||
require(owner_ != address(0));
|
||||
require(tokens_.length > 1, "Pool: need >1 asset");
|
||||
_tokens = tokens_;
|
||||
KAPPA = kappa_;
|
||||
require(fees_.length == tokens_.length, "Pool: fees length");
|
||||
// validate ppm bounds and assign
|
||||
_fees = new uint256[](fees_.length);
|
||||
for (uint256 i = 0; i < fees_.length; i++) {
|
||||
// Cap all fees at 1%
|
||||
require(fees_[i] < 10_000, "Pool: fee >= 1%");
|
||||
_fees[i] = fees_[i];
|
||||
}
|
||||
require(flashFeePpm_ < 10_000, "Pool: flash fee >= 1%");
|
||||
FLASH_FEE_PPM = flashFeePpm_;
|
||||
require(protocolFeePpm_ < 400_000, "Pool: protocol fee >= 40%");
|
||||
// If the protocolFeePpm_ is set, then also require the fee address to be nonzero
|
||||
require(protocolFeePpm_ == 0 || protocolFeeAddress_ != address(0));
|
||||
PROTOCOL_FEE_PPM = protocolFeePpm_;
|
||||
protocolFeeAddress = protocolFeeAddress_;
|
||||
SWAP_IMPL = swapImpl_;
|
||||
MINT_IMPL = mintImpl_;
|
||||
IPartyPoolDeployer.DeployParams memory p = IPartyPoolDeployer(msg.sender).params();
|
||||
uint256 n = p.tokens.length;
|
||||
require(n > 1, "Pool: need >1 asset");
|
||||
|
||||
uint256 n = tokens_.length;
|
||||
_nonce = p.nonce;
|
||||
WRAPPER = p.wrapper;
|
||||
_name = p.name;
|
||||
_symbol = p.symbol;
|
||||
|
||||
ownableConstructor(p.owner);
|
||||
|
||||
_tokens = p.tokens;
|
||||
KAPPA = p.kappa;
|
||||
require(p.fees.length == p.tokens.length, "Pool: fees length");
|
||||
// validate ppm bounds and assign
|
||||
_fees = new uint256[](p.fees.length);
|
||||
for (uint256 i = 0; i < p.fees.length; i++) {
|
||||
// Cap all fees at 1%
|
||||
require(p.fees[i] < 10_000, "Pool: fee >= 1%");
|
||||
_fees[i] = p.fees[i];
|
||||
}
|
||||
require(p.flashFeePpm < 10_000, "Pool: flash fee >= 1%");
|
||||
FLASH_FEE_PPM = p.flashFeePpm;
|
||||
require(p.protocolFeePpm < 400_000, "Pool: protocol fee >= 40%");
|
||||
// If the p.protocolFeePpm is set, then also require the fee address to be nonzero
|
||||
require(p.protocolFeePpm == 0 || p.protocolFeeAddress != address(0));
|
||||
PROTOCOL_FEE_PPM = p.protocolFeePpm;
|
||||
protocolFeeAddress = p.protocolFeeAddress;
|
||||
SWAP_IMPL = p.swapImpl;
|
||||
MINT_IMPL = p.mintImpl;
|
||||
|
||||
// Initialize token address to index mapping
|
||||
for (uint i = 0; i < n;) {
|
||||
_tokenAddressToIndexPlusOne[tokens_[i]] = i + 1;
|
||||
_tokenAddressToIndexPlusOne[p.tokens[i]] = i + 1;
|
||||
unchecked {i++;}
|
||||
}
|
||||
|
||||
@@ -252,7 +234,8 @@ contract PartyPool is PartyPoolBase, OwnableExternal, ERC20External, IPartyPool
|
||||
uint256 maxAmountIn,
|
||||
int128 limitPrice,
|
||||
uint256 deadline,
|
||||
bool unwrap
|
||||
bool unwrap,
|
||||
bytes memory cbData
|
||||
) external payable native nonReentrant killable returns (uint256 amountIn, uint256 amountOut, uint256 inFee) {
|
||||
require(deadline == 0 || block.timestamp <= deadline, "swap: deadline exceeded");
|
||||
|
||||
@@ -264,24 +247,7 @@ contract PartyPool is PartyPoolBase, OwnableExternal, ERC20External, IPartyPool
|
||||
IERC20 tokenIn = _tokens[inputTokenIndex];
|
||||
IERC20 tokenOut = _tokens[outputTokenIndex];
|
||||
|
||||
if (fundingSelector == Funding.APPROVALS)
|
||||
// Regular ERC20 permit of the pool to move the tokens
|
||||
_receiveTokenFrom(payer, tokenIn, totalTransferAmount);
|
||||
else if (fundingSelector == Funding.PREFUNDING) {
|
||||
require(limitPrice==0, 'Prefunding cannot be used with a limit price');
|
||||
uint256 balance = tokenIn.balanceOf(address(this));
|
||||
uint256 prevBalance = _cachedUintBalances[inputTokenIndex] + _protocolFeesOwed[inputTokenIndex];
|
||||
require( balance - prevBalance == totalTransferAmount, 'Incorrect prefunding amount');
|
||||
}
|
||||
else {
|
||||
// Callback-style funding mechanism
|
||||
uint256 startingBalance = tokenIn.balanceOf(address(this));
|
||||
bytes memory data = abi.encodeWithSelector(fundingSelector, tokenIn, totalTransferAmount);
|
||||
// Invoke the payer callback; no return value expected (reverts on failure)
|
||||
Address.functionCall(payer, data);
|
||||
uint256 endingBalance = tokenIn.balanceOf(address(this));
|
||||
require(endingBalance-startingBalance == totalTransferAmount, 'Insufficient funds');
|
||||
}
|
||||
_receiveTokenFrom(payer, fundingSelector, inputTokenIndex, tokenIn, totalTransferAmount, limitPrice, cbData);
|
||||
|
||||
// Compute on-chain balances as: onchain = cached + owed (+/- transfer)
|
||||
uint256 balIAfter = _cachedUintBalances[inputTokenIndex] + _protocolFeesOwed[inputTokenIndex] + totalTransferAmount;
|
||||
@@ -369,22 +335,26 @@ contract PartyPool is PartyPoolBase, OwnableExternal, ERC20External, IPartyPool
|
||||
/// @inheritdoc IPartyPool
|
||||
function swapToLimit(
|
||||
address payer,
|
||||
bytes4 fundingSelector,
|
||||
address receiver,
|
||||
uint256 inputTokenIndex,
|
||||
uint256 outputTokenIndex,
|
||||
int128 limitPrice,
|
||||
uint256 deadline,
|
||||
bool unwrap
|
||||
bool unwrap,
|
||||
bytes memory cbData
|
||||
) external payable returns (uint256 amountInUsed, uint256 amountOut, uint256 inFee) {
|
||||
bytes memory data = abi.encodeWithSelector(
|
||||
PartyPoolSwapImpl.swapToLimit.selector,
|
||||
payer,
|
||||
fundingSelector,
|
||||
receiver,
|
||||
inputTokenIndex,
|
||||
outputTokenIndex,
|
||||
limitPrice,
|
||||
deadline,
|
||||
unwrap,
|
||||
cbData,
|
||||
_pairFeePpm(inputTokenIndex, outputTokenIndex),
|
||||
PROTOCOL_FEE_PPM
|
||||
);
|
||||
|
||||
@@ -10,23 +10,6 @@ import {PartyPoolMintImpl} from "./PartyPoolMintImpl.sol";
|
||||
import {PartyPoolSwapImpl} from "./PartyPoolSwapImpl.sol";
|
||||
|
||||
contract PartyPoolBalancedPair is PartyPool {
|
||||
constructor(
|
||||
address owner_,
|
||||
string memory name_,
|
||||
string memory symbol_,
|
||||
IERC20[] memory tokens_,
|
||||
int128 kappa_,
|
||||
uint256[] memory fees_,
|
||||
uint256 flashFeePpm_,
|
||||
uint256 protocolFeePpm_, // NEW: protocol share of fees (ppm)
|
||||
address protocolFeeAddress_, // NEW: recipient for collected protocol tokens
|
||||
NativeWrapper wrapperToken_,
|
||||
PartyPoolSwapImpl swapMintImpl_,
|
||||
PartyPoolMintImpl mintImpl_
|
||||
)
|
||||
PartyPool(owner_, name_, symbol_, tokens_, kappa_, fees_, flashFeePpm_, protocolFeePpm_, protocolFeeAddress_, wrapperToken_, swapMintImpl_, mintImpl_)
|
||||
{}
|
||||
|
||||
function _swapAmountsForExactInput(uint256 i, uint256 j, int128 a, int128 limitPrice) internal virtual override view
|
||||
returns (int128 amountIn, int128 amountOut) {
|
||||
return LMSRStabilizedBalancedPair.swapAmountsForExactInput(_lmsr, i, j, a, limitPrice);
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import "../lib/openzeppelin-contracts/contracts/utils/Address.sol";
|
||||
import "./Funding.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 {ReentrancyGuard} from "../lib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol";
|
||||
import {ERC20Internal} from "./ERC20Internal.sol";
|
||||
import {IERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
|
||||
import {LMSRStabilized} from "./LMSRStabilized.sol";
|
||||
import {NativeWrapper} from "./NativeWrapper.sol";
|
||||
import {OwnableInternal} from "./OwnableInternal.sol";
|
||||
import {PartyPoolHelpers} from "./PartyPoolHelpers.sol";
|
||||
import {ReentrancyGuard} from "../lib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol";
|
||||
import {SafeERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
|
||||
|
||||
/// @notice Abstract base contract that contains storage and internal helpers only.
|
||||
/// No external/public functions here.
|
||||
@@ -18,11 +20,8 @@ abstract contract PartyPoolBase is OwnableInternal, ERC20Internal, ReentrancyGua
|
||||
using LMSRStabilized for LMSRStabilized.State;
|
||||
using SafeERC20 for IERC20;
|
||||
|
||||
NativeWrapper internal immutable WRAPPER_TOKEN;
|
||||
|
||||
constructor( NativeWrapper wrapper_ ) {
|
||||
WRAPPER_TOKEN = wrapper_;
|
||||
}
|
||||
bytes32 internal _nonce; // used for callback validation
|
||||
NativeWrapper internal immutable WRAPPER;
|
||||
|
||||
/// @notice Per-asset swap fees in ppm. Fees are applied on input for swaps; see helpers for composition rules.
|
||||
uint256[] internal _fees;
|
||||
@@ -144,12 +143,41 @@ abstract contract PartyPoolBase is OwnableInternal, ERC20Internal, ReentrancyGua
|
||||
Token transfer helpers (includes autowrap)
|
||||
---------------------- */
|
||||
|
||||
function _receiveTokenFrom(address payer, bytes4 fundingSelector, uint256 tokenIndex, IERC20 token, uint256 amount, int128 limitPrice, bytes memory cbData) internal {
|
||||
if (fundingSelector == Funding.APPROVAL) {
|
||||
// Regular ERC20 permit of the pool to move the tokens
|
||||
_receiveTokenFrom(payer, token, amount);
|
||||
}
|
||||
else if (fundingSelector == Funding.PREFUNDING) {
|
||||
// Tokens are already deposited into the pool
|
||||
require(limitPrice==0, 'Prefunding cannot be used with a limit price');
|
||||
if( token == WRAPPER && msg.value >= amount )
|
||||
WRAPPER.deposit{value:amount}();
|
||||
else {
|
||||
uint256 balance = token.balanceOf(address(this));
|
||||
uint256 prevBalance = _cachedUintBalances[tokenIndex] + _protocolFeesOwed[tokenIndex];
|
||||
require( balance - prevBalance == amount, 'Incorrect prefunding amount');
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Callback-style funding mechanism
|
||||
// Does not support native transfer.
|
||||
uint256 startingBalance = token.balanceOf(address(this));
|
||||
bytes memory data = abi.encodeWithSelector(fundingSelector, _nonce, token, amount, cbData);
|
||||
// Invoke the payer callback; no return value expected (reverts on failure)
|
||||
Address.functionCall(payer, data);
|
||||
uint256 endingBalance = token.balanceOf(address(this));
|
||||
require(endingBalance-startingBalance == amount, 'Insufficient funds');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// @notice Receive _tokens from `payer` into the pool (address(this)) using SafeERC20 semantics.
|
||||
/// @dev Note: this helper does NOT query the on-chain balance after transfer to save gas.
|
||||
/// Callers should query the balance themselves when they need it (e.g., to detect fee-on-transfer _tokens).
|
||||
function _receiveTokenFrom(address payer, IERC20 token, uint256 amount) internal {
|
||||
if( token == WRAPPER_TOKEN && msg.value >= amount )
|
||||
WRAPPER_TOKEN.deposit{value:amount}();
|
||||
if( token == WRAPPER && msg.value >= amount )
|
||||
WRAPPER.deposit{value:amount}();
|
||||
else
|
||||
token.safeTransferFrom(payer, address(this), amount);
|
||||
}
|
||||
@@ -158,8 +186,8 @@ abstract contract PartyPoolBase is OwnableInternal, ERC20Internal, ReentrancyGua
|
||||
/// @dev Note: this helper does NOT query the on-chain balance after transfer to save gas.
|
||||
/// Callers should query the balance themselves when they need it (e.g., to detect fee-on-transfer _tokens).
|
||||
function _sendTokenTo(IERC20 token, address receiver, uint256 amount, bool unwrap) internal {
|
||||
if( unwrap && token == WRAPPER_TOKEN ) {
|
||||
WRAPPER_TOKEN.withdraw(amount);
|
||||
if( unwrap && token == WRAPPER) {
|
||||
WRAPPER.withdraw(amount);
|
||||
(bool ok, ) = receiver.call{value: amount}("");
|
||||
require(ok, 'receiver not payable');
|
||||
}
|
||||
|
||||
@@ -1,91 +1,95 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import "./PartyPoolMintImpl.sol";
|
||||
import "./PartyPoolSwapImpl.sol";
|
||||
import {IPartyPool} from "./IPartyPool.sol";
|
||||
import {IPartyPoolDeployer} from "./IPartyPoolDeployer.sol";
|
||||
import {PartyPool} from "./PartyPool.sol";
|
||||
import {PartyPoolBalancedPair} from "./PartyPoolBalancedPair.sol";
|
||||
|
||||
// This pattern is needed because the PartyPlanner constructs two different types of pools (regular and balanced-pair)
|
||||
// but doesn't have room to store the initialization code of both contracts. Therefore, we delegate pool construction.
|
||||
|
||||
interface IPartyPoolDeployer {
|
||||
function deploy(
|
||||
address owner_,
|
||||
string memory name_,
|
||||
string memory symbol_,
|
||||
IERC20[] memory tokens_,
|
||||
int128 kappa_,
|
||||
uint256[] memory fees_,
|
||||
uint256 flashFeePpm_,
|
||||
uint256 protocolFeePpm_,
|
||||
address protocolFeeAddress_,
|
||||
NativeWrapper wrapper_,
|
||||
PartyPoolSwapImpl swapImpl_,
|
||||
PartyPoolMintImpl mintImpl_
|
||||
) external returns (IPartyPool pool);
|
||||
// Storage contracts that only hold the init code
|
||||
contract PartyPoolInitCode {
|
||||
constructor() {
|
||||
bytes memory code = type(PartyPool).creationCode;
|
||||
assembly {
|
||||
return(add(code, 0x20), mload(code))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
contract PartyPoolBalancedPairInitCode {
|
||||
constructor() {
|
||||
bytes memory code = type(PartyPoolBalancedPair).creationCode;
|
||||
assembly {
|
||||
return(add(code, 0x20), mload(code))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// @notice Unified deployer that loads init code from external storage contracts
|
||||
/// @dev This pattern avoids storing large init code in the deployer itself, reducing contract size.
|
||||
/// Holds storage addresses for both regular and balanced pair pools, with separate nonce counters.
|
||||
contract PartyPoolDeployer is IPartyPoolDeployer {
|
||||
function deploy(
|
||||
address owner_,
|
||||
string memory name_,
|
||||
string memory symbol_,
|
||||
IERC20[] memory tokens_,
|
||||
int128 kappa_,
|
||||
uint256[] memory fees_,
|
||||
uint256 flashFeePpm_,
|
||||
uint256 protocolFeePpm_,
|
||||
address protocolFeeAddress_,
|
||||
NativeWrapper wrapper_,
|
||||
PartyPoolSwapImpl swapImpl_,
|
||||
PartyPoolMintImpl mintImpl_
|
||||
) external returns (IPartyPool) {
|
||||
return new PartyPool(
|
||||
owner_,
|
||||
name_,
|
||||
symbol_,
|
||||
tokens_,
|
||||
kappa_,
|
||||
fees_,
|
||||
flashFeePpm_,
|
||||
protocolFeePpm_,
|
||||
protocolFeeAddress_,
|
||||
wrapper_,
|
||||
swapImpl_,
|
||||
mintImpl_
|
||||
);
|
||||
}
|
||||
}
|
||||
address private immutable POOL_INIT_CODE_STORAGE;
|
||||
address private immutable BALANCED_PAIR_INIT_CODE_STORAGE;
|
||||
|
||||
contract PartyPoolBalancedPairDeployer is IPartyPoolDeployer {
|
||||
function deploy(
|
||||
address owner_,
|
||||
string memory name_,
|
||||
string memory symbol_,
|
||||
IERC20[] memory tokens_,
|
||||
int128 kappa_,
|
||||
uint256[] memory fees_,
|
||||
uint256 flashFeePpm_,
|
||||
uint256 protocolFeePpm_,
|
||||
address protocolFeeAddress_,
|
||||
NativeWrapper wrapper_,
|
||||
PartyPoolSwapImpl swapImpl_,
|
||||
PartyPoolMintImpl mintImpl_
|
||||
) external returns (IPartyPool) {
|
||||
return new PartyPoolBalancedPair(
|
||||
owner_,
|
||||
name_,
|
||||
symbol_,
|
||||
tokens_,
|
||||
kappa_,
|
||||
fees_,
|
||||
flashFeePpm_,
|
||||
protocolFeePpm_,
|
||||
protocolFeeAddress_,
|
||||
wrapper_,
|
||||
swapImpl_,
|
||||
mintImpl_
|
||||
);
|
||||
uint256 private _poolNonce;
|
||||
uint256 private _balancedPairNonce;
|
||||
DeployParams private _params;
|
||||
|
||||
constructor(PartyPoolInitCode poolInitCodeStorage, PartyPoolBalancedPairInitCode balancedPairInitCodeStorage) {
|
||||
require(address(poolInitCodeStorage) != address(0), "Deployer: zero pool storage address");
|
||||
require(address(balancedPairInitCodeStorage) != address(0), "Deployer: zero balanced pair storage address");
|
||||
POOL_INIT_CODE_STORAGE = address(poolInitCodeStorage);
|
||||
BALANCED_PAIR_INIT_CODE_STORAGE = address(balancedPairInitCodeStorage);
|
||||
}
|
||||
|
||||
function params() external view returns (DeployParams memory) {
|
||||
return _params;
|
||||
}
|
||||
|
||||
/// @notice Deploy a regular PartyPool
|
||||
function _deploy(DeployParams memory params_) internal returns (IPartyPool pool) {
|
||||
return _doDeploy(params_, POOL_INIT_CODE_STORAGE, _poolNonce++);
|
||||
}
|
||||
|
||||
/// @notice Deploy a balanced pair PartyPool
|
||||
function _deployBalancedPair(DeployParams memory params_) internal returns (IPartyPool pool) {
|
||||
return _doDeploy(params_, BALANCED_PAIR_INIT_CODE_STORAGE, _balancedPairNonce++);
|
||||
}
|
||||
|
||||
/// @notice Internal deployment implementation shared by both pool types
|
||||
function _doDeploy(
|
||||
DeployParams memory params_,
|
||||
address initCodeStorage,
|
||||
uint256 nonce
|
||||
) internal returns (IPartyPool pool) {
|
||||
bytes32 salt = bytes32(nonce);
|
||||
_params = params_;
|
||||
_params.nonce = salt;
|
||||
|
||||
// Load init code from storage contract and deploy with CREATE2
|
||||
bytes memory initCode = _getInitCode(initCodeStorage);
|
||||
address poolAddress;
|
||||
assembly {
|
||||
poolAddress := create2(0, add(initCode, 0x20), mload(initCode), salt)
|
||||
if iszero(poolAddress) {
|
||||
revert(0, 0)
|
||||
}
|
||||
}
|
||||
|
||||
pool = IPartyPool(poolAddress);
|
||||
}
|
||||
|
||||
/// @notice Load init code from the specified storage contract using EXTCODECOPY
|
||||
function _getInitCode(address storageContract) internal view returns (bytes memory) {
|
||||
uint256 size;
|
||||
assembly {
|
||||
size := extcodesize(storageContract)
|
||||
}
|
||||
bytes memory code = new bytes(size);
|
||||
assembly {
|
||||
extcodecopy(storageContract, add(code, 0x20), 0, size)
|
||||
}
|
||||
return code;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ contract PartyPoolMintImpl is PartyPoolBase {
|
||||
using LMSRStabilized for LMSRStabilized.State;
|
||||
using SafeERC20 for IERC20;
|
||||
|
||||
constructor(NativeWrapper wrapper_) PartyPoolBase(wrapper_) {}
|
||||
constructor(NativeWrapper wrapper_) {WRAPPER = wrapper_;}
|
||||
|
||||
//
|
||||
// Initialization Mint
|
||||
|
||||
@@ -18,7 +18,7 @@ contract PartyPoolSwapImpl is PartyPoolBase {
|
||||
using LMSRStabilized for LMSRStabilized.State;
|
||||
using SafeERC20 for IERC20;
|
||||
|
||||
constructor(NativeWrapper wrapper_) PartyPoolBase(wrapper_) {}
|
||||
constructor(NativeWrapper wrapper_) {WRAPPER = wrapper_;}
|
||||
|
||||
bytes32 internal constant FLASH_CALLBACK_SUCCESS = keccak256("ERC3156FlashBorrower.onFlashLoan");
|
||||
|
||||
@@ -94,12 +94,14 @@ contract PartyPoolSwapImpl is PartyPoolBase {
|
||||
|
||||
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) {
|
||||
@@ -109,18 +111,15 @@ contract PartyPoolSwapImpl is PartyPoolBase {
|
||||
require(deadline == 0 || block.timestamp <= deadline, "swapToLimit: deadline exceeded");
|
||||
|
||||
// Read previous balances for affected assets
|
||||
uint256 prevBalI = IERC20(_tokens[inputTokenIndex]).balanceOf(address(this));
|
||||
uint256 prevBalJ = IERC20(_tokens[outputTokenIndex]).balanceOf(address(this));
|
||||
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 and require exact receipt (revert on fee-on-transfer)
|
||||
// Transfer the exact amount needed from payer
|
||||
IERC20 tokenIn = _tokens[inputTokenIndex];
|
||||
_receiveTokenFrom(payer, tokenIn, totalTransferAmount);
|
||||
uint256 balIAfter = tokenIn.balanceOf(address(this));
|
||||
require(balIAfter == prevBalI + totalTransferAmount, "swapToLimit: non-standard tokenIn");
|
||||
_receiveTokenFrom(payer, fundingSelector, inputTokenIndex, tokenIn, totalTransferAmount, limitPrice, cbData);
|
||||
|
||||
// Transfer output to receiver and verify exact decrease
|
||||
IERC20 tokenOut = _tokens[outputTokenIndex];
|
||||
@@ -137,10 +136,6 @@ contract PartyPoolSwapImpl is PartyPoolBase {
|
||||
}
|
||||
}
|
||||
|
||||
// Update caches to effective balances (inline _recordCachedBalance)
|
||||
require(balIAfter >= _protocolFeesOwed[inputTokenIndex], "balance < protocol owed");
|
||||
_cachedUintBalances[inputTokenIndex] = balIAfter - _protocolFeesOwed[inputTokenIndex];
|
||||
|
||||
require(balJAfter >= _protocolFeesOwed[outputTokenIndex], "balance < protocol owed");
|
||||
_cachedUintBalances[outputTokenIndex] = balJAfter - _protocolFeesOwed[outputTokenIndex];
|
||||
|
||||
|
||||
27
src/PartySwapCallbackVerifier.sol
Normal file
27
src/PartySwapCallbackVerifier.sol
Normal file
@@ -0,0 +1,27 @@
|
||||
// SPDX-License-Identifier: UNLICENSED
|
||||
pragma solidity ^0.8.30;
|
||||
|
||||
import {IPartyPlanner} from "./IPartyPlanner.sol";
|
||||
import {PartyPool} from "./PartyPool.sol";
|
||||
import {PartyPoolBalancedPair} from "./PartyPoolBalancedPair.sol";
|
||||
|
||||
library PartySwapCallbackVerifier {
|
||||
|
||||
// To use this verification in your own library, run `forge script InitCodeHashes` and replace the computed hashes below with the hardcoded bytes32 hash
|
||||
function verifyCallback(IPartyPlanner planner, bytes32 nonce) internal view {
|
||||
if(_verify(planner, keccak256(type(PartyPool).creationCode), nonce)) return;
|
||||
if(_verify(planner, keccak256(type(PartyPoolBalancedPair).creationCode), nonce)) return;
|
||||
revert('unauthorized callback');
|
||||
}
|
||||
|
||||
function _verify(IPartyPlanner planner, bytes32 initCodeHash, bytes32 nonce) internal view returns (bool) {
|
||||
address predicted = address(uint160(uint256(keccak256(abi.encodePacked(
|
||||
bytes1(0xff),
|
||||
address(planner),
|
||||
nonce,
|
||||
initCodeHash
|
||||
)))));
|
||||
return predicted == msg.sender;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user