// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/structs/EnumerableSet.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.
pragma solidity ^0.8.0;
/**
* @dev Library for managing
* https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
* types.
*
* Sets have the following properties:
*
* - Elements are added, removed, and checked for existence in constant time
* (O(1)).
* - Elements are enumerated in O(n). No guarantees are made on the ordering.
*
* ```
* contract Example {
* // Add the library methods
* using EnumerableSet for EnumerableSet.AddressSet;
*
* // Declare a set state variable
* EnumerableSet.AddressSet private mySet;
* }
* ```
*
* As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
* and `uint256` (`UintSet`) are supported.
*
* [WARNING]
* ====
* Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
* unusable.
* See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
*
* In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
* array of EnumerableSet.
* ====
*/
library EnumerableSet {
// To implement this library for multiple types with as little code
// repetition as possible, we write it in terms of a generic Set type with
// bytes32 values.
// The Set implementation uses private functions, and user-facing
// implementations (such as AddressSet) are just wrappers around the
// underlying Set.
// This means that we can only create new EnumerableSets for types that fit
// in bytes32.
struct Set {
// Storage of set values
bytes32[] _values;
// Position of the value in the `values` array, plus 1 because index 0
// means a value is not in the set.
mapping(bytes32 => uint256) _indexes;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function _add(Set storage set, bytes32 value) private returns (bool) {
if (!_contains(set, value)) {
set._values.push(value);
// The value is stored at length-1, but we add 1 to all indexes
// and use 0 as a sentinel value
set._indexes[value] = set._values.length;
return true;
} else {
return false;
}
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function _remove(Set storage set, bytes32 value) private returns (bool) {
// We read and store the value's index to prevent multiple reads from the same storage slot
uint256 valueIndex = set._indexes[value];
if (valueIndex != 0) {
// Equivalent to contains(set, value)
// To delete an element from the _values array in O(1), we swap the element to delete with the last one in
// the array, and then remove the last element (sometimes called as 'swap and pop').
// This modifies the order of the array, as noted in {at}.
uint256 toDeleteIndex = valueIndex - 1;
uint256 lastIndex = set._values.length - 1;
if (lastIndex != toDeleteIndex) {
bytes32 lastValue = set._values[lastIndex];
// Move the last value to the index where the value to delete is
set._values[toDeleteIndex] = lastValue;
// Update the index for the moved value
set._indexes[lastValue] = valueIndex; // Replace lastValue's index to valueIndex
}
// Delete the slot where the moved value was stored
set._values.pop();
// Delete the index for the deleted slot
delete set._indexes[value];
return true;
} else {
return false;
}
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function _contains(Set storage set, bytes32 value) private view returns (bool) {
return set._indexes[value] != 0;
}
/**
* @dev Returns the number of values on the set. O(1).
*/
function _length(Set storage set) private view returns (uint256) {
return set._values.length;
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function _at(Set storage set, uint256 index) private view returns (bytes32) {
return set._values[index];
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function _values(Set storage set) private view returns (bytes32[] memory) {
return set._values;
}
// Bytes32Set
struct Bytes32Set {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _add(set._inner, value);
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
return _remove(set._inner, value);
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
return _contains(set._inner, value);
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(Bytes32Set storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
return _at(set._inner, index);
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
bytes32[] memory store = _values(set._inner);
bytes32[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// AddressSet
struct AddressSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(AddressSet storage set, address value) internal returns (bool) {
return _add(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(AddressSet storage set, address value) internal returns (bool) {
return _remove(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(AddressSet storage set, address value) internal view returns (bool) {
return _contains(set._inner, bytes32(uint256(uint160(value))));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(AddressSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(AddressSet storage set, uint256 index) internal view returns (address) {
return address(uint160(uint256(_at(set._inner, index))));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(AddressSet storage set) internal view returns (address[] memory) {
bytes32[] memory store = _values(set._inner);
address[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
// UintSet
struct UintSet {
Set _inner;
}
/**
* @dev Add a value to a set. O(1).
*
* Returns true if the value was added to the set, that is if it was not
* already present.
*/
function add(UintSet storage set, uint256 value) internal returns (bool) {
return _add(set._inner, bytes32(value));
}
/**
* @dev Removes a value from a set. O(1).
*
* Returns true if the value was removed from the set, that is if it was
* present.
*/
function remove(UintSet storage set, uint256 value) internal returns (bool) {
return _remove(set._inner, bytes32(value));
}
/**
* @dev Returns true if the value is in the set. O(1).
*/
function contains(UintSet storage set, uint256 value) internal view returns (bool) {
return _contains(set._inner, bytes32(value));
}
/**
* @dev Returns the number of values in the set. O(1).
*/
function length(UintSet storage set) internal view returns (uint256) {
return _length(set._inner);
}
/**
* @dev Returns the value stored at position `index` in the set. O(1).
*
* Note that there are no guarantees on the ordering of values inside the
* array, and it may change when more values are added or removed.
*
* Requirements:
*
* - `index` must be strictly less than {length}.
*/
function at(UintSet storage set, uint256 index) internal view returns (uint256) {
return uint256(_at(set._inner, index));
}
/**
* @dev Return the entire set in an array
*
* WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
* to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
* this function has an unbounded cost, and using it as part of a state-changing function may render the function
* uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
*/
function values(UintSet storage set) internal view returns (uint256[] memory) {
bytes32[] memory store = _values(set._inner);
uint256[] memory result;
/// @solidity memory-safe-assembly
assembly {
result := store
}
return result;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the symbol of the token
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the amount of decimals of the token
*/
function decimals() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address from,
address to,
uint256 amount
) external returns (bool);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
interface IRewards {
function getRewardTokens() external view returns (address[] memory);
function addRewards(address token, uint256 amount) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
interface IWETH {
function deposit() external payable;
function balanceOf(address addr) external view returns (uint256);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol)
pragma solidity ^0.8.0;
import "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor() {
_transferOwnership(_msgSender());
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions anymore. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby removing any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity >=0.6.0;
// helper methods for interacting with ERC20 tokens and sending ETH that do not consistently return true/false
library TransferHelper {
function safeApprove(
address token,
address to,
uint256 value
) internal {
// bytes4(keccak256(bytes('approve(address,uint256)')));
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x095ea7b3, to, value));
require(
success && (data.length == 0 || abi.decode(data, (bool))),
'TransferHelper::safeApprove: approve failed'
);
}
function safeTransfer(
address token,
address to,
uint256 value
) internal {
// bytes4(keccak256(bytes('transfer(address,uint256)')));
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value));
require(
success && (data.length == 0 || abi.decode(data, (bool))),
'TransferHelper::safeTransfer: transfer failed'
);
}
function safeTransferFrom(
address token,
address from,
address to,
uint256 value
) internal {
// bytes4(keccak256(bytes('transferFrom(address,address,uint256)')));
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value));
require(
success && (data.length == 0 || abi.decode(data, (bool))),
'TransferHelper::transferFrom: transferFrom failed'
);
}
function safeTransferETH(address to, uint256 value) internal {
(bool success, ) = to.call{value: value}(new bytes(0));
require(success, 'TransferHelper::safeTransferETH: ETH transfer failed');
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
import "./interfaces/IWETH.sol";
import "./interfaces/IERC20.sol";
import "./interfaces/IRewards.sol";
import "./libraries/TransferHelper.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
interface IAsset {
// solhint-disable-previous-line no-empty-blocks
}
enum SwapKind { GIVEN_IN, GIVEN_OUT }
struct SingleSwap {
bytes32 poolId;
SwapKind kind;
IAsset assetIn;
IAsset assetOut;
uint256 amount;
bytes userData;
}
struct FundManagement {
address sender;
bool fromInternalBalance;
address payable recipient;
bool toInternalBalance;
}
interface LBPFactory {
function create(
string memory name,
string memory symbol,
address[] memory tokens,
uint256[] memory weights,
uint256 swapFeePercentage,
address owner,
bool swapEnabledOnStart
) external returns (address);
}
interface Vault {
struct JoinPoolRequest {
address[] assets;
uint256[] maxAmountsIn;
bytes userData;
bool fromInternalBalance;
}
struct ExitPoolRequest {
address[] assets;
uint256[] minAmountsOut;
bytes userData;
bool toInternalBalance;
}
function joinPool(
bytes32 poolId,
address sender,
address recipient,
JoinPoolRequest memory request
) external;
function exitPool(
bytes32 poolId,
address sender,
address recipient,
ExitPoolRequest memory request
) external;
function getPoolTokens(bytes32 poolId)
external
view
returns (
address[] memory tokens,
uint256[] memory balances,
uint256 lastChangeBlock
);
function swap(
SingleSwap memory singleSwap,
FundManagement memory funds,
uint256 limit,
uint256 deadline
)
external
payable
returns (uint256 amountCalculated);
}
interface LBP {
function updateWeightsGradually(
uint256 startTime,
uint256 endTime,
uint256[] memory endWeights
) external;
function setSwapEnabled(bool swapEnabled) external;
function getPoolId() external returns (bytes32 poolID);
}
/// @title Launchpad
/// @notice This contract allows for simplified creation and management of Balancer LBPs
/// It currently supports:
/// - LBPs with 2 tokens
/// - Withdrawl of the full liquidity at once
/// - Distributing fees to single and double sided staking contracts
contract Launchpad is Ownable {
using EnumerableSet for EnumerableSet.AddressSet;
struct PoolData {
address owner;
bool isCorrectOrder;
uint256 fundTokenInputAmount;
string ipfsDetails;
}
mapping(address => PoolData) private _poolData;
EnumerableSet.AddressSet private _pools;
mapping(address => uint256) private _feeRecipientsBPS;
IWETH public WETH;
address public constant VAULT = address(0xBA12222222228d8Ba445958a75a0704d566BF2C8);
uint256 private constant _TEN_THOUSAND_BPS = 10_000;
address public immutable LBPFactoryAddress;
uint256 public immutable platformAccessFeeBPS;
address public immutable lpStakingAddress;
address public immutable stakingAddress;
constructor(
uint256 _platformAccessFeeBPS,
address _LBPFactoryAddress,
address _stakingAddress,
address _lpStakingAddress,
address _wethAddress
) {
stakingAddress = _stakingAddress;
lpStakingAddress = _lpStakingAddress;
LBPFactoryAddress = _LBPFactoryAddress;
platformAccessFeeBPS = _platformAccessFeeBPS;
_feeRecipientsBPS[owner()] = _TEN_THOUSAND_BPS;
WETH = IWETH(_wethAddress);
}
// Events
event PoolCreated(
address indexed pool,
bytes32 poolId,
string name,
string symbol,
address[] tokens,
uint256[] weights,
uint256 swapFeePercentage,
address owner,
bool swapEnabledOnStart
);
event JoinedPool(address indexed pool, address[] tokens, uint256[] amounts, bytes userData);
event GradualWeightUpdateScheduled(address indexed pool, uint256 startTime, uint256 endTime, uint256[] endWeights);
event SwapEnabledSet(address indexed pool, bool swapEnabled);
event IpfsUpdated(address indexed pool, string hash);
event TransferredPoolOwnership(address indexed pool, address previousOwner, address newOwner);
event TransferredFee(address indexed pool, address token, address feeRecipient, uint256 feeAmount);
event TransferredToken(address indexed pool, address token, address to, uint256 amount);
event RecipientsUpdated(address[] recipients, uint256[] recipientShareBPS);
event Skimmed(address token, address to, uint256 balance);
// Pool access control
modifier onlyPoolOwner(address pool) {
require(msg.sender == _poolData[pool].owner, "!owner");
_;
}
/**
* @dev Checks if the pool address was created in this smart contract
*/
function isPool(address pool) external view returns (bool valid) {
return _pools.contains(pool);
}
/**
* @dev Returns the total amount of pools created in the contract
*/
function poolCount() external view returns (uint256 count) {
return _pools.length();
}
/**
* @dev Returns a pool for a specific index
*/
function getPoolAt(uint256 index) external view returns (address pool) {
return _pools.at(index);
}
/**
* @dev Returns all the pool values
*/
function getPools() external view returns (address[] memory pools) {
return _pools.values();
}
/**
* @dev Returns the pool's data saved during creation
*/
function getPoolData(address pool) external view returns (PoolData memory poolData) {
return _poolData[pool];
}
/**
* @dev Returns the total amount of LBP Tokens for a pool. These tokens are burned when exit
*/
function getBPTTokenBalance(address pool) external view returns (uint256 bptBalance) {
return IERC20(pool).balanceOf(address(this));
}
struct PoolConfig {
string name;
string symbol;
address[] tokens;
uint256[] amounts;
uint256[] weights;
uint256[] endWeights;
bool isCorrectOrder;
uint256 swapFeePercentage;
uint256 startTime;
uint256 endTime;
string ipfsDetails;
}
function fundTokens() public view returns (address[] memory) {
return IRewards(stakingAddress).getRewardTokens();
}
function getFundTokensAndSymbols() public view returns (string[] memory, address[] memory) {
address[] memory addresses = fundTokens();
string[] memory symbols = new string[](addresses.length);
for (uint16 i=0; i < addresses.length; i++)
symbols[i] = IERC20(addresses[i]).symbol();
return (symbols, addresses);
}
/**
* @dev Creates a pool and return the contract address of the new pool
*/
function createLBP(PoolConfig memory poolConfig) external payable returns (address) {
// 1: deposit tokens and approve vault
require(poolConfig.tokens.length == 2, "F9 LBPs must have exactly two tokens");
require(poolConfig.tokens[0] != poolConfig.tokens[1], "LBP tokens must be unique");
require(poolConfig.startTime > block.timestamp, "LBP start time must be in the future");
require(poolConfig.endTime > poolConfig.startTime, "LBP end time must be greater than start time");
uint8 fundTokenIndex = poolConfig.isCorrectOrder ? 0 : 1;
uint8 mainTokenIndex = poolConfig.isCorrectOrder ? 1 : 0;
TransferHelper.safeTransferFrom(poolConfig.tokens[mainTokenIndex], msg.sender, address(this), poolConfig.amounts[mainTokenIndex]);
if (poolConfig.tokens[fundTokenIndex] == address(0)) {
require(poolConfig.amounts[fundTokenIndex] == msg.value, "Incorrect fund token amount");
poolConfig.tokens[fundTokenIndex] = address(WETH);
WETH.deposit{value: msg.value}();
} else {
bool isValidFundToken = false;
address[] memory _fundTokens = fundTokens();
for (uint i=0; i < _fundTokens.length; i++)
if (poolConfig.tokens[fundTokenIndex] == _fundTokens[i]) {
isValidFundToken = true;
break;
}
require(isValidFundToken, "fund token not approved");
TransferHelper.safeTransferFrom(poolConfig.tokens[fundTokenIndex], msg.sender, address(this), poolConfig.amounts[fundTokenIndex]);
}
TransferHelper.safeApprove(poolConfig.tokens[0], VAULT, poolConfig.amounts[0]);
TransferHelper.safeApprove(poolConfig.tokens[1], VAULT, poolConfig.amounts[1]);
// 2: pool creation
address pool = LBPFactory(LBPFactoryAddress).create(
poolConfig.name,
poolConfig.symbol,
poolConfig.tokens,
poolConfig.weights,
poolConfig.swapFeePercentage,
address(this), // owner set to this proxy
false // swaps disabled on start
);
bytes32 poolId = LBP(pool).getPoolId();
emit PoolCreated(
pool,
poolId,
poolConfig.name,
poolConfig.symbol,
poolConfig.tokens,
poolConfig.weights,
poolConfig.swapFeePercentage,
address(this),
false
);
// 3: store pool data
_poolData[pool] = PoolData(
msg.sender,
poolConfig.isCorrectOrder,
poolConfig.amounts[poolConfig.isCorrectOrder ? 0 : 1],
poolConfig.ipfsDetails
);
require(_pools.add(pool), "exists already");
bytes memory userData = abi.encode(0, poolConfig.amounts); // JOIN_KIND_INIT = 0
// 4: deposit tokens into pool
Vault(VAULT).joinPool(
poolId,
address(this), // sender
address(this), // recipient
Vault.JoinPoolRequest(
poolConfig.tokens,
poolConfig.amounts,
userData,
false)
);
emit JoinedPool(pool, poolConfig.tokens, poolConfig.amounts, userData);
// 5: configure weights
LBP(pool).updateWeightsGradually(poolConfig.startTime, poolConfig.endTime, poolConfig.endWeights);
emit GradualWeightUpdateScheduled(pool, poolConfig.startTime, poolConfig.endTime, poolConfig.endWeights);
return pool;
}
/**
* @dev Enable or disables swaps.
* Note: LBPs are created with trading disabled by default.
*/
function setSwapEnabled(address pool, bool swapEnabled) external onlyPoolOwner(pool) {
LBP(pool).setSwapEnabled(swapEnabled);
emit SwapEnabledSet(pool, swapEnabled);
}
/**
* @dev Enable or disables swaps.
* Note: LBPs are created with trading disabled by default.
*/
function updateIPFSDetails(address pool, string memory hash) external onlyPoolOwner(pool) {
_poolData[pool].ipfsDetails = hash;
emit IpfsUpdated(pool, hash);
}
/**
* @dev Transfer ownership of the pool to a new owner
*/
function transferPoolOwnership(address pool, address newOwner) external onlyPoolOwner(pool) {
address previousOwner = _poolData[pool].owner;
_poolData[pool].owner = newOwner;
emit TransferredPoolOwnership(pool, previousOwner, newOwner);
}
enum ExitKind {
EXACT_BPT_IN_FOR_ONE_TOKEN_OUT,
EXACT_BPT_IN_FOR_TOKENS_OUT,
BPT_IN_FOR_EXACT_TOKENS_OUT
}
/**
* @dev calculate the amount of BPToken to burn.
* - if maxBPTTokenOut is 0, everything will be burned
* - else it will burn only the amount passed
*/
function _calcBPTokenToBurn(address pool, uint256 maxBPTTokenOut) internal view returns(uint256) {
uint256 bptBalance = IERC20(pool).balanceOf(address(this));
require(maxBPTTokenOut <= bptBalance, "Specifed BPT out amount out exceeds owner balance");
require(bptBalance > 0, "Pool owner BPT balance is less than zero");
return maxBPTTokenOut == 0 ? bptBalance : maxBPTTokenOut;
}
/**
* @dev Exit a pool, burn the BPT token and transfer back the tokens.
* - If maxBPTTokenOut is passed as 0, the function will use the total balance available for the BPT token.
* - If maxBPTTokenOut is between 0 and the total of BPT available, that will be the amount used to burn.
* maxBPTTokenOut must be greater than or equal to 0
* as false, and the fee will stay in the contract and later on distributed manualy to mitigate errors
*/
function exitPool(address pool, uint256 maxBPTTokenOut) external onlyPoolOwner(pool) {
uint256[] memory minAmountsOut = new uint256[](2);
minAmountsOut[0] = uint256(0);
minAmountsOut[1] = uint256(0);
// 1. Get pool data
bytes32 poolId = LBP(pool).getPoolId();
(address[] memory poolTokens, uint256[] memory balances, ) = Vault(VAULT).getPoolTokens(poolId);
require(poolTokens.length == minAmountsOut.length, "invalid input length");
PoolData memory poolData = _poolData[pool];
// 2. Specify the exact BPT amount to burn
uint256 bptToBurn = _calcBPTokenToBurn(pool, maxBPTTokenOut);
// 3. Exit pool and keep tokens in contract
Vault(VAULT).exitPool(
poolId,
address(this),
payable(address(this)),
Vault.ExitPoolRequest(
poolTokens,
minAmountsOut,
abi.encode(ExitKind.EXACT_BPT_IN_FOR_TOKENS_OUT, bptToBurn),
false
)
);
// 4. Get the amount of Fund token from the pool that was left behind after exit (dust)
( ,uint256[] memory balancesAfterExit, ) = Vault(VAULT).getPoolTokens(poolId);
uint256 fundTokenIndex = poolData.isCorrectOrder ? 0 : 1;
// 5. Distribute tokens and fees
_distributeTokens(
pool,
poolTokens,
poolData,
balances[fundTokenIndex] - balancesAfterExit[fundTokenIndex]
);
}
/**
* @dev Distributes the tokens to the owner and the fee to the fee recipients
*/
function _distributeTokens(
address pool,
address[] memory poolTokens,
PoolData memory poolData,
uint256 fundTokenFromPool) internal {
address mainToken = poolTokens[poolData.isCorrectOrder ? 1 : 0];
address fundToken = poolTokens[poolData.isCorrectOrder ? 0 : 1];
uint256 mainTokenBalance = IERC20(mainToken).balanceOf(address(this));
uint256 remainingFundBalance = fundTokenFromPool;
// if the amount of fund token increased during the LBP
if (fundTokenFromPool > poolData.fundTokenInputAmount) {
uint256 totalPlatformAccessFeeAmount = ((fundTokenFromPool - poolData.fundTokenInputAmount) * platformAccessFeeBPS) / _TEN_THOUSAND_BPS;
// Fund amount after substracting the fee
remainingFundBalance = fundTokenFromPool - totalPlatformAccessFeeAmount;
_distributePlatformAccessFee(pool, fundToken, totalPlatformAccessFeeAmount);
}
// Transfer the balance of the main token
_transferTokenToPoolOwner(pool, mainToken, mainTokenBalance);
// Transfer the balanace of fund token excluding the platform access fee
_transferTokenToPoolOwner(pool, fundToken, remainingFundBalance);
}
/**
* @dev Transfer token to pool owner
*/
function _transferTokenToPoolOwner(address pool, address token, uint256 amount) private {
TransferHelper.safeTransfer(
token,
msg.sender,
amount
);
emit TransferredToken(pool, token, msg.sender, amount);
}
/**
* @dev Distribute fee between recipients
*/
function _distributePlatformAccessFee(address pool, address fundToken, uint256 totalFeeAmount) private {
uint256 splitFee = totalFeeAmount / 2;
IERC20(fundToken).approve(stakingAddress, splitFee);
IERC20(fundToken).approve(lpStakingAddress,splitFee);
IRewards(stakingAddress).addRewards(fundToken, splitFee);
IRewards(lpStakingAddress).addRewards(fundToken, splitFee);
emit TransferredFee(pool, fundToken, stakingAddress, splitFee);
emit TransferredFee(pool, fundToken, lpStakingAddress, splitFee);
}
}
{
"compilationTarget": {
"project:/contracts/launchpad.sol": "Launchpad"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": false,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"uint256","name":"_platformAccessFeeBPS","type":"uint256"},{"internalType":"address","name":"_LBPFactoryAddress","type":"address"},{"internalType":"address","name":"_stakingAddress","type":"address"},{"internalType":"address","name":"_lpStakingAddress","type":"address"},{"internalType":"address","name":"_wethAddress","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"pool","type":"address"},{"indexed":false,"internalType":"uint256","name":"startTime","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"endTime","type":"uint256"},{"indexed":false,"internalType":"uint256[]","name":"endWeights","type":"uint256[]"}],"name":"GradualWeightUpdateScheduled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"pool","type":"address"},{"indexed":false,"internalType":"string","name":"hash","type":"string"}],"name":"IpfsUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"pool","type":"address"},{"indexed":false,"internalType":"address[]","name":"tokens","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"amounts","type":"uint256[]"},{"indexed":false,"internalType":"bytes","name":"userData","type":"bytes"}],"name":"JoinedPool","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"pool","type":"address"},{"indexed":false,"internalType":"bytes32","name":"poolId","type":"bytes32"},{"indexed":false,"internalType":"string","name":"name","type":"string"},{"indexed":false,"internalType":"string","name":"symbol","type":"string"},{"indexed":false,"internalType":"address[]","name":"tokens","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"weights","type":"uint256[]"},{"indexed":false,"internalType":"uint256","name":"swapFeePercentage","type":"uint256"},{"indexed":false,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"bool","name":"swapEnabledOnStart","type":"bool"}],"name":"PoolCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address[]","name":"recipients","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"recipientShareBPS","type":"uint256[]"}],"name":"RecipientsUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"balance","type":"uint256"}],"name":"Skimmed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"pool","type":"address"},{"indexed":false,"internalType":"bool","name":"swapEnabled","type":"bool"}],"name":"SwapEnabledSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"pool","type":"address"},{"indexed":false,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"address","name":"feeRecipient","type":"address"},{"indexed":false,"internalType":"uint256","name":"feeAmount","type":"uint256"}],"name":"TransferredFee","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"pool","type":"address"},{"indexed":false,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":false,"internalType":"address","name":"newOwner","type":"address"}],"name":"TransferredPoolOwnership","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"pool","type":"address"},{"indexed":false,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"TransferredToken","type":"event"},{"inputs":[],"name":"LBPFactoryAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VAULT","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WETH","outputs":[{"internalType":"contract IWETH","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"string","name":"name","type":"string"},{"internalType":"string","name":"symbol","type":"string"},{"internalType":"address[]","name":"tokens","type":"address[]"},{"internalType":"uint256[]","name":"amounts","type":"uint256[]"},{"internalType":"uint256[]","name":"weights","type":"uint256[]"},{"internalType":"uint256[]","name":"endWeights","type":"uint256[]"},{"internalType":"bool","name":"isCorrectOrder","type":"bool"},{"internalType":"uint256","name":"swapFeePercentage","type":"uint256"},{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"endTime","type":"uint256"},{"internalType":"string","name":"ipfsDetails","type":"string"}],"internalType":"struct Launchpad.PoolConfig","name":"poolConfig","type":"tuple"}],"name":"createLBP","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"pool","type":"address"},{"internalType":"uint256","name":"maxBPTTokenOut","type":"uint256"}],"name":"exitPool","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"fundTokens","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"pool","type":"address"}],"name":"getBPTTokenBalance","outputs":[{"internalType":"uint256","name":"bptBalance","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getFundTokensAndSymbols","outputs":[{"internalType":"string[]","name":"","type":"string[]"},{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getPoolAt","outputs":[{"internalType":"address","name":"pool","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"pool","type":"address"}],"name":"getPoolData","outputs":[{"components":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"bool","name":"isCorrectOrder","type":"bool"},{"internalType":"uint256","name":"fundTokenInputAmount","type":"uint256"},{"internalType":"string","name":"ipfsDetails","type":"string"}],"internalType":"struct Launchpad.PoolData","name":"poolData","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getPools","outputs":[{"internalType":"address[]","name":"pools","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"pool","type":"address"}],"name":"isPool","outputs":[{"internalType":"bool","name":"valid","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lpStakingAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"platformAccessFeeBPS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"poolCount","outputs":[{"internalType":"uint256","name":"count","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"pool","type":"address"},{"internalType":"bool","name":"swapEnabled","type":"bool"}],"name":"setSwapEnabled","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"stakingAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"pool","type":"address"},{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferPoolOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"pool","type":"address"},{"internalType":"string","name":"hash","type":"string"}],"name":"updateIPFSDetails","outputs":[],"stateMutability":"nonpayable","type":"function"}]