ERC functionality split into internal/external
This commit is contained in:
123
src/ERC20External.sol
Normal file
123
src/ERC20External.sol
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
// SPDX-License-Identifier: UNLICENSED
|
||||||
|
pragma solidity ^0.8.30;
|
||||||
|
|
||||||
|
import {IERC20Errors} from "../lib/openzeppelin-contracts/contracts/interfaces/draft-IERC6093.sol";
|
||||||
|
import {IERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
|
||||||
|
import {Context} from "../lib/openzeppelin-contracts/contracts/utils/Context.sol";
|
||||||
|
import {IERC20Metadata} from "../lib/openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol";
|
||||||
|
import {ERC20Internal} from "./ERC20Internal.sol";
|
||||||
|
|
||||||
|
// Copied from OpenZeppelin's ERC20 implementation, but split into internal and external parts
|
||||||
|
|
||||||
|
contract ERC20External is ERC20Internal, IERC20Metadata {
|
||||||
|
/**
|
||||||
|
* @dev Sets the values for {name} and {symbol}.
|
||||||
|
*
|
||||||
|
* Both values are immutable: they can only be set once during construction.
|
||||||
|
*/
|
||||||
|
constructor(string memory name_, string memory symbol_) {
|
||||||
|
_name = name_;
|
||||||
|
_symbol = symbol_;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Returns the name of the token.
|
||||||
|
*/
|
||||||
|
function name() public view virtual returns (string memory) {
|
||||||
|
return _name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Returns the symbol of the token, usually a shorter version of the
|
||||||
|
* name.
|
||||||
|
*/
|
||||||
|
function symbol() public view virtual returns (string memory) {
|
||||||
|
return _symbol;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Returns the number of decimals used to get its user representation.
|
||||||
|
* For example, if `decimals` equals `2`, a balance of `505` tokens should
|
||||||
|
* be displayed to a user as `5.05` (`505 / 10 ** 2`).
|
||||||
|
*
|
||||||
|
* Tokens usually opt for a value of 18, imitating the relationship between
|
||||||
|
* Ether and Wei. This is the default value returned by this function, unless
|
||||||
|
* it's overridden.
|
||||||
|
*
|
||||||
|
* NOTE: This information is only used for _display_ purposes: it in
|
||||||
|
* no way affects any of the arithmetic of the contract, including
|
||||||
|
* {IERC20-balanceOf} and {IERC20-transfer}.
|
||||||
|
*/
|
||||||
|
function decimals() public view virtual returns (uint8) {
|
||||||
|
return 18;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @inheritdoc IERC20
|
||||||
|
function totalSupply() public view virtual returns (uint256) {
|
||||||
|
return _totalSupply;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @inheritdoc IERC20
|
||||||
|
function balanceOf(address account) public view virtual returns (uint256) {
|
||||||
|
return _balances[account];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev See {IERC20-transfer}.
|
||||||
|
*
|
||||||
|
* Requirements:
|
||||||
|
*
|
||||||
|
* - `to` cannot be the zero address.
|
||||||
|
* - the caller must have a balance of at least `value`.
|
||||||
|
*/
|
||||||
|
function transfer(address to, uint256 value) public virtual returns (bool) {
|
||||||
|
address owner = _msgSender();
|
||||||
|
_transfer(owner, to, value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @inheritdoc IERC20
|
||||||
|
function allowance(address owner, address spender) public view virtual returns (uint256) {
|
||||||
|
return _allowances[owner][spender];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev See {IERC20-approve}.
|
||||||
|
*
|
||||||
|
* NOTE: If `value` is the maximum `uint256`, the allowance is not updated on
|
||||||
|
* `transferFrom`. This is semantically equivalent to an infinite approval.
|
||||||
|
*
|
||||||
|
* Requirements:
|
||||||
|
*
|
||||||
|
* - `spender` cannot be the zero address.
|
||||||
|
*/
|
||||||
|
function approve(address spender, uint256 value) public virtual returns (bool) {
|
||||||
|
address owner = _msgSender();
|
||||||
|
_approve(owner, spender, value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev See {IERC20-transferFrom}.
|
||||||
|
*
|
||||||
|
* Skips emitting an {Approval} event indicating an allowance update. This is not
|
||||||
|
* required by the ERC. See {xref-ERC20-_approve-address-address-uint256-bool-}[_approve].
|
||||||
|
*
|
||||||
|
* NOTE: Does not update the allowance if the current allowance
|
||||||
|
* is the maximum `uint256`.
|
||||||
|
*
|
||||||
|
* Requirements:
|
||||||
|
*
|
||||||
|
* - `from` and `to` cannot be the zero address.
|
||||||
|
* - `from` must have a balance of at least `value`.
|
||||||
|
* - the caller must have allowance for ``from``'s tokens of at least
|
||||||
|
* `value`.
|
||||||
|
*/
|
||||||
|
function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
|
||||||
|
address spender = _msgSender();
|
||||||
|
_spendAllowance(from, spender, value);
|
||||||
|
_transfer(from, to, value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
175
src/ERC20Internal.sol
Normal file
175
src/ERC20Internal.sol
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
// SPDX-License-Identifier: UNLICENSED
|
||||||
|
pragma solidity ^0.8.30;
|
||||||
|
|
||||||
|
import {IERC20Errors} from "../lib/openzeppelin-contracts/contracts/interfaces/draft-IERC6093.sol";
|
||||||
|
import {IERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
|
||||||
|
import {Context} from "../lib/openzeppelin-contracts/contracts/utils/Context.sol";
|
||||||
|
|
||||||
|
// Copied from OpenZeppelin's ERC20 implementation, but split into internal and external parts
|
||||||
|
|
||||||
|
abstract contract ERC20Internal is Context, IERC20Errors {
|
||||||
|
mapping(address account => uint256) internal _balances;
|
||||||
|
mapping(address account => mapping(address spender => uint256)) internal _allowances;
|
||||||
|
uint256 internal _totalSupply;
|
||||||
|
string internal _name;
|
||||||
|
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
|
||||||
|
* e.g. implement automatic token fees, slashing mechanisms, etc.
|
||||||
|
*
|
||||||
|
* Emits a {Transfer} event.
|
||||||
|
*
|
||||||
|
* NOTE: This function is not virtual, {_update} should be overridden instead.
|
||||||
|
*/
|
||||||
|
function _transfer(address from, address to, uint256 value) internal {
|
||||||
|
if (from == address(0)) {
|
||||||
|
revert ERC20InvalidSender(address(0));
|
||||||
|
}
|
||||||
|
if (to == address(0)) {
|
||||||
|
revert ERC20InvalidReceiver(address(0));
|
||||||
|
}
|
||||||
|
_update(from, to, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`
|
||||||
|
* (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding
|
||||||
|
* this function.
|
||||||
|
*
|
||||||
|
* Emits a {Transfer} event.
|
||||||
|
*/
|
||||||
|
function _update(address from, address to, uint256 value) internal virtual {
|
||||||
|
if (from == address(0)) {
|
||||||
|
// Overflow check required: The rest of the code assumes that totalSupply never overflows
|
||||||
|
_totalSupply += value;
|
||||||
|
} else {
|
||||||
|
uint256 fromBalance = _balances[from];
|
||||||
|
if (fromBalance < value) {
|
||||||
|
revert ERC20InsufficientBalance(from, fromBalance, value);
|
||||||
|
}
|
||||||
|
unchecked {
|
||||||
|
// Overflow not possible: value <= fromBalance <= totalSupply.
|
||||||
|
_balances[from] = fromBalance - value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (to == address(0)) {
|
||||||
|
unchecked {
|
||||||
|
// Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.
|
||||||
|
_totalSupply -= value;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unchecked {
|
||||||
|
// Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.
|
||||||
|
_balances[to] += value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emit IERC20.Transfer(from, to, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).
|
||||||
|
* Relies on the `_update` mechanism
|
||||||
|
*
|
||||||
|
* Emits a {Transfer} event with `from` set to the zero address.
|
||||||
|
*
|
||||||
|
* NOTE: This function is not virtual, {_update} should be overridden instead.
|
||||||
|
*/
|
||||||
|
function _mint(address account, uint256 value) internal {
|
||||||
|
if (account == address(0)) {
|
||||||
|
revert ERC20InvalidReceiver(address(0));
|
||||||
|
}
|
||||||
|
_update(address(0), account, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.
|
||||||
|
* Relies on the `_update` mechanism.
|
||||||
|
*
|
||||||
|
* Emits a {Transfer} event with `to` set to the zero address.
|
||||||
|
*
|
||||||
|
* NOTE: This function is not virtual, {_update} should be overridden instead
|
||||||
|
*/
|
||||||
|
function _burn(address account, uint256 value) internal {
|
||||||
|
if (account == address(0)) {
|
||||||
|
revert ERC20InvalidSender(address(0));
|
||||||
|
}
|
||||||
|
_update(account, address(0), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Sets `value` as the allowance of `spender` over the `owner`'s tokens.
|
||||||
|
*
|
||||||
|
* This internal function is equivalent to `approve`, and can be used to
|
||||||
|
* e.g. set automatic allowances for certain subsystems, etc.
|
||||||
|
*
|
||||||
|
* Emits an {Approval} event.
|
||||||
|
*
|
||||||
|
* Requirements:
|
||||||
|
*
|
||||||
|
* - `owner` cannot be the zero address.
|
||||||
|
* - `spender` cannot be the zero address.
|
||||||
|
*
|
||||||
|
* Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.
|
||||||
|
*/
|
||||||
|
function _approve(address owner, address spender, uint256 value) internal {
|
||||||
|
_approve(owner, spender, value, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.
|
||||||
|
*
|
||||||
|
* By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by
|
||||||
|
* `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any
|
||||||
|
* `Approval` event during `transferFrom` operations.
|
||||||
|
*
|
||||||
|
* Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to
|
||||||
|
* true using the following override:
|
||||||
|
*
|
||||||
|
* ```solidity
|
||||||
|
* function _approve(address owner, address spender, uint256 value, bool) internal virtual override {
|
||||||
|
* super._approve(owner, spender, value, true);
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Requirements are the same as {_approve}.
|
||||||
|
*/
|
||||||
|
function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {
|
||||||
|
if (owner == address(0)) {
|
||||||
|
revert ERC20InvalidApprover(address(0));
|
||||||
|
}
|
||||||
|
if (spender == address(0)) {
|
||||||
|
revert ERC20InvalidSpender(address(0));
|
||||||
|
}
|
||||||
|
_allowances[owner][spender] = value;
|
||||||
|
if (emitEvent) {
|
||||||
|
emit IERC20.Approval(owner, spender, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Updates `owner`'s allowance for `spender` based on spent `value`.
|
||||||
|
*
|
||||||
|
* Does not update the allowance value in case of infinite allowance.
|
||||||
|
* Revert if not enough allowance is available.
|
||||||
|
*
|
||||||
|
* Does not emit an {Approval} event.
|
||||||
|
*/
|
||||||
|
function _spendAllowance(address owner, address spender, uint256 value) internal virtual {
|
||||||
|
uint256 currentAllowance = _allowances[owner][spender];
|
||||||
|
if (currentAllowance < type(uint256).max) {
|
||||||
|
if (currentAllowance < value) {
|
||||||
|
revert ERC20InsufficientAllowance(spender, currentAllowance, value);
|
||||||
|
}
|
||||||
|
unchecked {
|
||||||
|
_approve(owner, spender, currentAllowance - value, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -82,8 +82,12 @@ interface IPartyPool is IERC20Metadata {
|
|||||||
/// @notice Address that will receive collected protocol tokens when collectProtocolFees() is called.
|
/// @notice Address that will receive collected protocol tokens when collectProtocolFees() is called.
|
||||||
function protocolFeeAddress() external view returns (address);
|
function protocolFeeAddress() external view returns (address);
|
||||||
|
|
||||||
/// @notice Per-token protocol fee ledger accessor. Returns tokens owed (raw uint token units) for token index i.
|
/// @notice Protocol fee ledger accessor. Returns tokens owed (raw uint token units) from this pool as protocol fees
|
||||||
function protocolFeesOwed(uint256) external view returns (uint256);
|
/// that have not yet been transferred out.
|
||||||
|
function allProtocolFeesOwed() external view returns (uint256[] memory);
|
||||||
|
|
||||||
|
/// @notice Callable by anyone, sends any owed protocol fees to the protocol fee address.
|
||||||
|
function collectProtocolFees() external;
|
||||||
|
|
||||||
/// @notice Liquidity parameter κ (Q64.64) used by the LMSR kernel: b = κ * S(q)
|
/// @notice Liquidity parameter κ (Q64.64) used by the LMSR kernel: b = κ * S(q)
|
||||||
/// @dev Pools are constructed with a κ value; this getter exposes the κ used by the pool.
|
/// @dev Pools are constructed with a κ value; this getter exposes the κ used by the pool.
|
||||||
|
|||||||
@@ -1,18 +1,19 @@
|
|||||||
// SPDX-License-Identifier: UNLICENSED
|
// SPDX-License-Identifier: UNLICENSED
|
||||||
pragma solidity ^0.8.30;
|
pragma solidity ^0.8.30;
|
||||||
|
|
||||||
import "forge-std/console2.sol";
|
import {ABDKMath64x64} from "../lib/abdk-libraries-solidity/ABDKMath64x64.sol";
|
||||||
import "@abdk/ABDKMath64x64.sol";
|
import {IERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
|
||||||
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
import {SafeERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
|
||||||
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
import {Address} from "../lib/openzeppelin-contracts/contracts/utils/Address.sol";
|
||||||
import "@openzeppelin/contracts/utils/Address.sol";
|
import {ReentrancyGuard} from "../lib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol";
|
||||||
import "./LMSRStabilized.sol";
|
import {IPartyFlashCallback} from "./IPartyFlashCallback.sol";
|
||||||
import "./LMSRStabilizedBalancedPair.sol";
|
import {IPartyPool} from "./IPartyPool.sol";
|
||||||
import "./IPartyPool.sol";
|
import {LMSRStabilized} from "./LMSRStabilized.sol";
|
||||||
import "./IPartyFlashCallback.sol";
|
import {LMSRStabilizedBalancedPair} from "./LMSRStabilizedBalancedPair.sol";
|
||||||
import "./PartyPoolBase.sol";
|
import {PartyPoolBase} from "./PartyPoolBase.sol";
|
||||||
import {PartyPoolSwapMintImpl} from "./PartyPoolSwapMintImpl.sol";
|
|
||||||
import {PartyPoolMintImpl} from "./PartyPoolMintImpl.sol";
|
import {PartyPoolMintImpl} from "./PartyPoolMintImpl.sol";
|
||||||
|
import {PartyPoolSwapMintImpl} from "./PartyPoolSwapMintImpl.sol";
|
||||||
|
import {ERC20External} from "./ERC20External.sol";
|
||||||
|
|
||||||
/// @title PartyPool - LMSR-backed multi-asset pool with LP ERC20 token
|
/// @title PartyPool - LMSR-backed multi-asset pool with LP ERC20 token
|
||||||
/// @notice A multi-asset liquidity pool backed by the LMSRStabilized pricing model.
|
/// @notice A multi-asset liquidity pool backed by the LMSRStabilized pricing model.
|
||||||
@@ -27,7 +28,7 @@ import {PartyPoolMintImpl} from "./PartyPoolMintImpl.sol";
|
|||||||
/// representation used by the LMSR library. Cached on-chain uint balances are kept to reduce balanceOf calls.
|
/// representation used by the LMSR library. Cached on-chain uint balances are kept to reduce balanceOf calls.
|
||||||
/// The contract uses ceiling/floor rules described in function comments to bias rounding in favor of the pool
|
/// The contract uses ceiling/floor rules described in function comments to bias rounding in favor of the pool
|
||||||
/// (i.e., floor outputs to users, ceil inputs/fees where appropriate).
|
/// (i.e., floor outputs to users, ceil inputs/fees where appropriate).
|
||||||
contract PartyPool is PartyPoolBase, IPartyPool {
|
contract PartyPool is PartyPoolBase, ERC20External, IPartyPool {
|
||||||
using ABDKMath64x64 for int128;
|
using ABDKMath64x64 for int128;
|
||||||
using LMSRStabilized for LMSRStabilized.State;
|
using LMSRStabilized for LMSRStabilized.State;
|
||||||
using SafeERC20 for IERC20;
|
using SafeERC20 for IERC20;
|
||||||
@@ -54,6 +55,9 @@ contract PartyPool is PartyPoolBase, IPartyPool {
|
|||||||
address private immutable PROTOCOL_FEE_ADDRESS;
|
address private immutable PROTOCOL_FEE_ADDRESS;
|
||||||
function protocolFeeAddress() external view returns (address) { return PROTOCOL_FEE_ADDRESS; }
|
function protocolFeeAddress() external view returns (address) { return PROTOCOL_FEE_ADDRESS; }
|
||||||
|
|
||||||
|
// @inheritdoc IPartyPool
|
||||||
|
function allProtocolFeesOwed() external view returns (uint256[] memory) { return protocolFeesOwed; }
|
||||||
|
|
||||||
/// @notice If true and there are exactly two assets, an optimized 2-asset stable-pair path is used for some computations.
|
/// @notice If true and there are exactly two assets, an optimized 2-asset stable-pair path is used for some computations.
|
||||||
bool immutable private IS_STABLE_PAIR; // if true, the optimized LMSRStabilizedBalancedPair optimization path is enabled
|
bool immutable private IS_STABLE_PAIR; // if true, the optimized LMSRStabilizedBalancedPair optimization path is enabled
|
||||||
|
|
||||||
@@ -101,7 +105,7 @@ contract PartyPool is PartyPoolBase, IPartyPool {
|
|||||||
bool stable_,
|
bool stable_,
|
||||||
PartyPoolSwapMintImpl swapMintImpl_,
|
PartyPoolSwapMintImpl swapMintImpl_,
|
||||||
PartyPoolMintImpl mintImpl_
|
PartyPoolMintImpl mintImpl_
|
||||||
) PartyPoolBase(name_, symbol_) {
|
) ERC20External(name_, symbol_) {
|
||||||
require(tokens_.length > 1, "Pool: need >1 asset");
|
require(tokens_.length > 1, "Pool: need >1 asset");
|
||||||
require(tokens_.length == bases_.length, "Pool: lengths mismatch");
|
require(tokens_.length == bases_.length, "Pool: lengths mismatch");
|
||||||
tokens = tokens_;
|
tokens = tokens_;
|
||||||
@@ -226,9 +230,6 @@ contract PartyPool is PartyPoolBase, IPartyPool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Per-token owed protocol fees (raw token units). Public getter autogenerated.
|
|
||||||
uint256[] public protocolFeesOwed;
|
|
||||||
|
|
||||||
/// @notice Transfer all protocol fees to the configured protocolFeeAddress and zero the ledger.
|
/// @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.
|
/// @dev Anyone can call; must have protocolFeeAddress != address(0) to be operational.
|
||||||
function collectProtocolFees() external nonReentrant {
|
function collectProtocolFees() external nonReentrant {
|
||||||
@@ -411,7 +412,6 @@ contract PartyPool is PartyPoolBase, IPartyPool {
|
|||||||
|
|
||||||
// Compute internal amounts using LMSR (exact-input with price limit)
|
// Compute internal amounts using LMSR (exact-input with price limit)
|
||||||
// if _stablePair is true, use the optimized path
|
// if _stablePair is true, use the optimized path
|
||||||
console2.log('stablepair optimization?', IS_STABLE_PAIR);
|
|
||||||
(amountInInternalUsed, amountOutInternal) =
|
(amountInInternalUsed, amountOutInternal) =
|
||||||
IS_STABLE_PAIR ? LMSRStabilizedBalancedPair.swapAmountsForExactInput(lmsr, inputTokenIndex, outputTokenIndex, deltaInternalI, limitPrice)
|
IS_STABLE_PAIR ? LMSRStabilizedBalancedPair.swapAmountsForExactInput(lmsr, inputTokenIndex, outputTokenIndex, deltaInternalI, limitPrice)
|
||||||
: lmsr.swapAmountsForExactInput(inputTokenIndex, outputTokenIndex, deltaInternalI, limitPrice);
|
: lmsr.swapAmountsForExactInput(inputTokenIndex, outputTokenIndex, deltaInternalI, limitPrice);
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
// SPDX-License-Identifier: UNLICENSED
|
// SPDX-License-Identifier: UNLICENSED
|
||||||
pragma solidity ^0.8.30;
|
pragma solidity ^0.8.30;
|
||||||
|
|
||||||
import "@abdk/ABDKMath64x64.sol";
|
import {ABDKMath64x64} from "../lib/abdk-libraries-solidity/ABDKMath64x64.sol";
|
||||||
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
import {IERC20} from "../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
|
||||||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
|
import {ReentrancyGuard} from "../lib/openzeppelin-contracts/contracts/utils/ReentrancyGuard.sol";
|
||||||
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
|
import {ERC20Internal} from "./ERC20Internal.sol";
|
||||||
import "./LMSRStabilized.sol";
|
import {LMSRStabilized} from "./LMSRStabilized.sol";
|
||||||
|
|
||||||
/// @notice Abstract base contract that contains storage and internal helpers only.
|
/// @notice Abstract base contract that contains storage and internal helpers only.
|
||||||
/// No external/public functions or constructor here — derived implementations own immutables and constructors.
|
/// No external/public functions or constructor here — derived implementations own immutables and constructors.
|
||||||
abstract contract PartyPoolBase is ERC20, ReentrancyGuard {
|
abstract contract PartyPoolBase is ERC20Internal, ReentrancyGuard {
|
||||||
using ABDKMath64x64 for int128;
|
using ABDKMath64x64 for int128;
|
||||||
using LMSRStabilized for LMSRStabilized.State;
|
using LMSRStabilized for LMSRStabilized.State;
|
||||||
|
|
||||||
@@ -28,6 +28,10 @@ abstract contract PartyPoolBase is ERC20, ReentrancyGuard {
|
|||||||
/// @dev tokens[i] corresponds to the i-th asset and maps to index i in the internal LMSR arrays.
|
/// @dev tokens[i] corresponds to the i-th asset and maps to index i in the internal LMSR arrays.
|
||||||
IERC20[] internal tokens; // effectively immutable since there is no interface to change the tokens
|
IERC20[] internal tokens; // effectively immutable since there is no interface to change the tokens
|
||||||
|
|
||||||
|
/// @notice Amounts of token owed as protocol fees but not yet collected. Subtract this amount from the pool's token
|
||||||
|
/// balances to compute the tokens owned by LP's.
|
||||||
|
uint256[] internal protocolFeesOwed;
|
||||||
|
|
||||||
/// @notice Per-token uint base denominators used to convert uint token amounts <-> internal Q64.64 representation.
|
/// @notice Per-token uint base denominators used to convert uint token amounts <-> internal Q64.64 representation.
|
||||||
/// @dev denominators()[i] is the base for tokens[i]. These bases are chosen by deployer and must match token decimals.
|
/// @dev denominators()[i] is the base for tokens[i]. These bases are chosen by deployer and must match token decimals.
|
||||||
uint256[] internal bases; // per-token uint base used to scale token amounts <-> internal
|
uint256[] internal bases; // per-token uint base used to scale token amounts <-> internal
|
||||||
@@ -41,9 +45,6 @@ abstract contract PartyPoolBase is ERC20, ReentrancyGuard {
|
|||||||
uint256[] internal cachedUintBalances;
|
uint256[] internal cachedUintBalances;
|
||||||
|
|
||||||
|
|
||||||
constructor(string memory name_, string memory symbol_) ERC20(name_, symbol_) {}
|
|
||||||
|
|
||||||
|
|
||||||
/* ----------------------
|
/* ----------------------
|
||||||
Conversion & fee helpers (internal)
|
Conversion & fee helpers (internal)
|
||||||
---------------------- */
|
---------------------- */
|
||||||
|
|||||||
@@ -20,14 +20,12 @@ contract PartyPoolMintImpl is PartyPoolBase {
|
|||||||
event Mint(address indexed payer, address indexed receiver, uint256[] depositAmounts, uint256 lpMinted);
|
event Mint(address indexed payer, address indexed receiver, uint256[] depositAmounts, uint256 lpMinted);
|
||||||
event Burn(address indexed payer, address indexed receiver, uint256[] withdrawAmounts, uint256 lpBurned);
|
event Burn(address indexed payer, address indexed receiver, uint256[] withdrawAmounts, uint256 lpBurned);
|
||||||
|
|
||||||
constructor() PartyPoolBase('','') {}
|
|
||||||
|
|
||||||
function initialMint(address receiver, uint256 lpTokens, int128 KAPPA) external
|
function initialMint(address receiver, uint256 lpTokens, int128 KAPPA) external
|
||||||
returns (uint256 lpMinted) {
|
returns (uint256 lpMinted) {
|
||||||
uint256 n = tokens.length;
|
uint256 n = tokens.length;
|
||||||
|
|
||||||
// Check if this is initial deposit - revert if not
|
// Check if this is initial deposit - revert if not
|
||||||
bool isInitialDeposit = totalSupply() == 0 || lmsr.nAssets == 0;
|
bool isInitialDeposit = _totalSupply == 0 || lmsr.nAssets == 0;
|
||||||
require(isInitialDeposit, "initialMint: pool already initialized");
|
require(isInitialDeposit, "initialMint: pool already initialized");
|
||||||
|
|
||||||
// Update cached balances for all assets
|
// Update cached balances for all assets
|
||||||
@@ -62,7 +60,7 @@ contract PartyPoolMintImpl is PartyPoolBase {
|
|||||||
uint256 n = tokens.length;
|
uint256 n = tokens.length;
|
||||||
|
|
||||||
// Check if this is NOT initial deposit - revert if it is
|
// Check if this is NOT initial deposit - revert if it is
|
||||||
bool isInitialDeposit = totalSupply() == 0 || lmsr.nAssets == 0;
|
bool isInitialDeposit = _totalSupply == 0 || lmsr.nAssets == 0;
|
||||||
require(!isInitialDeposit, "mint: use initialMint for pool initialization");
|
require(!isInitialDeposit, "mint: use initialMint for pool initialization");
|
||||||
require(lpTokenAmount > 0, "mint: zero LP amount");
|
require(lpTokenAmount > 0, "mint: zero LP amount");
|
||||||
|
|
||||||
@@ -71,7 +69,7 @@ contract PartyPoolMintImpl is PartyPoolBase {
|
|||||||
uint256 oldScaled = ABDKMath64x64.mulu(oldTotal, LP_SCALE);
|
uint256 oldScaled = ABDKMath64x64.mulu(oldTotal, LP_SCALE);
|
||||||
|
|
||||||
// Calculate required deposit amounts for the desired LP tokens
|
// Calculate required deposit amounts for the desired LP tokens
|
||||||
uint256[] memory depositAmounts = mintAmounts(lpTokenAmount, lmsr.nAssets, totalSupply(), cachedUintBalances);
|
uint256[] memory depositAmounts = mintAmounts(lpTokenAmount, lmsr.nAssets, _totalSupply, cachedUintBalances);
|
||||||
|
|
||||||
// Transfer in all token amounts
|
// Transfer in all token amounts
|
||||||
for (uint i = 0; i < n; ) {
|
for (uint i = 0; i < n; ) {
|
||||||
@@ -104,7 +102,7 @@ contract PartyPoolMintImpl is PartyPoolBase {
|
|||||||
// Proportional issuance: totalSupply * delta / oldScaled
|
// Proportional issuance: totalSupply * delta / oldScaled
|
||||||
if (delta > 0) {
|
if (delta > 0) {
|
||||||
// floor truncation rounds in favor of the pool
|
// floor truncation rounds in favor of the pool
|
||||||
actualLpToMint = (totalSupply() * delta) / oldScaled;
|
actualLpToMint = (_totalSupply * delta) / oldScaled;
|
||||||
} else {
|
} else {
|
||||||
actualLpToMint = 0;
|
actualLpToMint = 0;
|
||||||
}
|
}
|
||||||
@@ -135,10 +133,10 @@ contract PartyPoolMintImpl is PartyPoolBase {
|
|||||||
uint256 n = tokens.length;
|
uint256 n = tokens.length;
|
||||||
require(lpAmount > 0, "burn: zero lp");
|
require(lpAmount > 0, "burn: zero lp");
|
||||||
|
|
||||||
uint256 supply = totalSupply();
|
uint256 supply = _totalSupply;
|
||||||
require(supply > 0, "burn: empty supply");
|
require(supply > 0, "burn: empty supply");
|
||||||
require(lmsr.nAssets > 0, "burn: uninit pool");
|
require(lmsr.nAssets > 0, "burn: uninit pool");
|
||||||
require(balanceOf(payer) >= lpAmount, "burn: insufficient LP");
|
require(_balances[payer] >= lpAmount, "burn: insufficient LP");
|
||||||
|
|
||||||
// Refresh cached balances to reflect current on-chain balances before computing withdrawal amounts
|
// Refresh cached balances to reflect current on-chain balances before computing withdrawal amounts
|
||||||
for (uint i = 0; i < n; ) {
|
for (uint i = 0; i < n; ) {
|
||||||
@@ -148,7 +146,7 @@ contract PartyPoolMintImpl is PartyPoolBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Compute proportional withdrawal amounts for the requested LP amount (rounded down)
|
// Compute proportional withdrawal amounts for the requested LP amount (rounded down)
|
||||||
withdrawAmounts = burnAmounts(lpAmount, lmsr.nAssets, totalSupply(), cachedUintBalances);
|
withdrawAmounts = burnAmounts(lpAmount, lmsr.nAssets, _totalSupply, cachedUintBalances);
|
||||||
|
|
||||||
// Transfer underlying tokens out to receiver according to computed proportions
|
// Transfer underlying tokens out to receiver according to computed proportions
|
||||||
for (uint i = 0; i < n; ) {
|
for (uint i = 0; i < n; ) {
|
||||||
@@ -185,7 +183,7 @@ contract PartyPoolMintImpl is PartyPoolBase {
|
|||||||
|
|
||||||
// Burn exactly the requested LP amount from payer (authorization via allowance)
|
// Burn exactly the requested LP amount from payer (authorization via allowance)
|
||||||
if (msg.sender != payer) {
|
if (msg.sender != payer) {
|
||||||
uint256 allowed = allowance(payer, msg.sender);
|
uint256 allowed = _allowances[payer][msg.sender];
|
||||||
require(allowed >= lpAmount, "burn: allowance insufficient");
|
require(allowed >= lpAmount, "burn: allowance insufficient");
|
||||||
_approve(payer, msg.sender, allowed - lpAmount);
|
_approve(payer, msg.sender, allowed - lpAmount);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ contract PartyPoolSwapMintImpl is PartyPoolBase {
|
|||||||
event Mint(address indexed payer, address indexed receiver, uint256[] depositAmounts, uint256 lpMinted);
|
event Mint(address indexed payer, address indexed receiver, uint256[] depositAmounts, uint256 lpMinted);
|
||||||
event Burn(address indexed payer, address indexed receiver, uint256[] withdrawAmounts, uint256 lpBurned);
|
event Burn(address indexed payer, address indexed receiver, uint256[] withdrawAmounts, uint256 lpBurned);
|
||||||
|
|
||||||
constructor() PartyPoolBase('','') {}
|
|
||||||
|
|
||||||
/// @notice Single-token mint: deposit a single token, charge swap-LMSR cost, and mint LP.
|
/// @notice Single-token mint: deposit a single token, charge swap-LMSR cost, and mint LP.
|
||||||
/// @dev swapMint executes as an exact-in planned swap followed by proportional scaling of qInternal.
|
/// @dev swapMint executes as an exact-in planned swap followed by proportional scaling of qInternal.
|
||||||
@@ -85,7 +84,7 @@ contract PartyPoolSwapMintImpl is PartyPoolBase {
|
|||||||
|
|
||||||
uint256 actualLpToMint;
|
uint256 actualLpToMint;
|
||||||
// Use natural ERC20 function since base contract inherits from ERC20
|
// Use natural ERC20 function since base contract inherits from ERC20
|
||||||
uint256 currentSupply = totalSupply();
|
uint256 currentSupply = _totalSupply;
|
||||||
if (currentSupply == 0) {
|
if (currentSupply == 0) {
|
||||||
// If somehow supply zero (shouldn't happen as lmsr.nAssets>0), mint newScaled
|
// If somehow supply zero (shouldn't happen as lmsr.nAssets>0), mint newScaled
|
||||||
actualLpToMint = newScaled;
|
actualLpToMint = newScaled;
|
||||||
@@ -259,9 +258,9 @@ contract PartyPoolSwapMintImpl is PartyPoolBase {
|
|||||||
require(lpAmount > 0, "burnSwap: zero lp");
|
require(lpAmount > 0, "burnSwap: zero lp");
|
||||||
require(deadline == 0 || block.timestamp <= deadline, "burnSwap: deadline");
|
require(deadline == 0 || block.timestamp <= deadline, "burnSwap: deadline");
|
||||||
|
|
||||||
uint256 supply = totalSupply();
|
uint256 supply = _totalSupply;
|
||||||
require(supply > 0, "burnSwap: empty supply");
|
require(supply > 0, "burnSwap: empty supply");
|
||||||
require(balanceOf(payer) >= lpAmount, "burnSwap: insufficient LP");
|
require(_balances[payer] >= lpAmount, "burnSwap: insufficient LP");
|
||||||
|
|
||||||
// alpha = lpAmount / supply as Q64.64 (adjusted for fee)
|
// alpha = lpAmount / supply as Q64.64 (adjusted for fee)
|
||||||
int128 alpha = ABDKMath64x64.divu(lpAmount, supply) // fraction of total supply to burn
|
int128 alpha = ABDKMath64x64.divu(lpAmount, supply) // fraction of total supply to burn
|
||||||
@@ -285,7 +284,7 @@ contract PartyPoolSwapMintImpl is PartyPoolBase {
|
|||||||
|
|
||||||
// Burn LP tokens from payer (authorization via allowance)
|
// Burn LP tokens from payer (authorization via allowance)
|
||||||
if (msg.sender != payer) {
|
if (msg.sender != payer) {
|
||||||
uint256 allowed = allowance(payer, msg.sender);
|
uint256 allowed = _allowances[payer][msg.sender];
|
||||||
require(allowed >= lpAmount, "burnSwap: allowance insufficient");
|
require(allowed >= lpAmount, "burnSwap: allowance insufficient");
|
||||||
_approve(payer, msg.sender, allowed - lpAmount);
|
_approve(payer, msg.sender, allowed - lpAmount);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user