// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.20;
/**
* @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 value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of 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 value) 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 a `value` amount of tokens 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 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` 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 value) external returns (bool);
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.20;
interface IG8keepDeployerVesting {
function deploymentVest(address _deployer, uint256 _tokensToDeployer, uint256 _vestTime)
external
returns (uint256 vestingId);
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.20;
interface IG8keepFactory {
function g8keepTokenVesting() external view returns (address);
}
pragma solidity ^0.8.20;
interface IUniswapV2Factory {
function createPair(address tokenA, address tokenB) external returns (address pair);
function getPair(address tokenA, address tokenB) external view returns (address pair);
}
pragma solidity ^0.8.20;
interface IUniswapV2Pair {
function getReserves() external view returns (uint112 _reserve0, uint112 _reserve1, uint32 _blockTimestampLast);
function mint(address to) external returns (uint256 liquidity);
function burn(address to) external returns (uint256 amount0, uint256 amount1);
function swap(uint256 amount0Out, uint256 amount1Out, address to, bytes calldata data) external;
function sync() external;
function skim(address to) external;
}
pragma solidity ^0.8.20;
interface IUniswapV2Router02 {
function factory() external pure returns (address);
function WETH() external pure returns (address);
function swapExactTokensForETHSupportingFeeOnTransferTokens(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external;
function swapExactETHForTokensSupportingFeeOnTransferTokens(
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external payable;
function swapExactTokensForTokensSupportingFeeOnTransferTokens(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external;
function addLiquidityETH(
address token,
uint256 amountTokenDesired,
uint256 amountTokenMin,
uint256 amountETHMin,
address to,
uint256 deadline
) external payable returns (uint256 amountToken, uint256 amountETH, uint256 liquidity);
function addLiquidity(
address tokenA,
address tokenB,
uint256 amountADesired,
uint256 amountBDesired,
uint256 amountAMin,
uint256 amountBMin,
address to,
uint256 deadline
) external returns (uint256 amountA, uint256 amountB, uint256 liquidity);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Simple single owner authorization mixin.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/auth/Ownable.sol)
///
/// @dev Note:
/// This implementation does NOT auto-initialize the owner to `msg.sender`.
/// You MUST call the `_initializeOwner` in the constructor / initializer.
///
/// While the ownable portion follows
/// [EIP-173](https://eips.ethereum.org/EIPS/eip-173) for compatibility,
/// the nomenclature for the 2-step ownership handover may be unique to this codebase.
abstract contract Ownable {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The caller is not authorized to call the function.
error Unauthorized();
/// @dev The `newOwner` cannot be the zero address.
error NewOwnerIsZeroAddress();
/// @dev The `pendingOwner` does not have a valid handover request.
error NoHandoverRequest();
/// @dev Cannot double-initialize.
error AlreadyInitialized();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EVENTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The ownership is transferred from `oldOwner` to `newOwner`.
/// This event is intentionally kept the same as OpenZeppelin's Ownable to be
/// compatible with indexers and [EIP-173](https://eips.ethereum.org/EIPS/eip-173),
/// despite it not being as lightweight as a single argument event.
event OwnershipTransferred(address indexed oldOwner, address indexed newOwner);
/// @dev An ownership handover to `pendingOwner` has been requested.
event OwnershipHandoverRequested(address indexed pendingOwner);
/// @dev The ownership handover to `pendingOwner` has been canceled.
event OwnershipHandoverCanceled(address indexed pendingOwner);
/// @dev `keccak256(bytes("OwnershipTransferred(address,address)"))`.
uint256 private constant _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE =
0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0;
/// @dev `keccak256(bytes("OwnershipHandoverRequested(address)"))`.
uint256 private constant _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE =
0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d;
/// @dev `keccak256(bytes("OwnershipHandoverCanceled(address)"))`.
uint256 private constant _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE =
0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* STORAGE */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The owner slot is given by:
/// `bytes32(~uint256(uint32(bytes4(keccak256("_OWNER_SLOT_NOT")))))`.
/// It is intentionally chosen to be a high value
/// to avoid collision with lower slots.
/// The choice of manual storage layout is to enable compatibility
/// with both regular and upgradeable contracts.
bytes32 internal constant _OWNER_SLOT =
0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff74873927;
/// The ownership handover slot of `newOwner` is given by:
/// ```
/// mstore(0x00, or(shl(96, user), _HANDOVER_SLOT_SEED))
/// let handoverSlot := keccak256(0x00, 0x20)
/// ```
/// It stores the expiry timestamp of the two-step ownership handover.
uint256 private constant _HANDOVER_SLOT_SEED = 0x389a75e1;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Override to return true to make `_initializeOwner` prevent double-initialization.
function _guardInitializeOwner() internal pure virtual returns (bool guard) {}
/// @dev Initializes the owner directly without authorization guard.
/// This function must be called upon initialization,
/// regardless of whether the contract is upgradeable or not.
/// This is to enable generalization to both regular and upgradeable contracts,
/// and to save gas in case the initial owner is not the caller.
/// For performance reasons, this function will not check if there
/// is an existing owner.
function _initializeOwner(address newOwner) internal virtual {
if (_guardInitializeOwner()) {
/// @solidity memory-safe-assembly
assembly {
let ownerSlot := _OWNER_SLOT
if sload(ownerSlot) {
mstore(0x00, 0x0dc149f0) // `AlreadyInitialized()`.
revert(0x1c, 0x04)
}
// Clean the upper 96 bits.
newOwner := shr(96, shl(96, newOwner))
// Store the new value.
sstore(ownerSlot, or(newOwner, shl(255, iszero(newOwner))))
// Emit the {OwnershipTransferred} event.
log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner)
}
} else {
/// @solidity memory-safe-assembly
assembly {
// Clean the upper 96 bits.
newOwner := shr(96, shl(96, newOwner))
// Store the new value.
sstore(_OWNER_SLOT, newOwner)
// Emit the {OwnershipTransferred} event.
log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner)
}
}
}
/// @dev Sets the owner directly without authorization guard.
function _setOwner(address newOwner) internal virtual {
if (_guardInitializeOwner()) {
/// @solidity memory-safe-assembly
assembly {
let ownerSlot := _OWNER_SLOT
// Clean the upper 96 bits.
newOwner := shr(96, shl(96, newOwner))
// Emit the {OwnershipTransferred} event.
log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner)
// Store the new value.
sstore(ownerSlot, or(newOwner, shl(255, iszero(newOwner))))
}
} else {
/// @solidity memory-safe-assembly
assembly {
let ownerSlot := _OWNER_SLOT
// Clean the upper 96 bits.
newOwner := shr(96, shl(96, newOwner))
// Emit the {OwnershipTransferred} event.
log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner)
// Store the new value.
sstore(ownerSlot, newOwner)
}
}
}
/// @dev Throws if the sender is not the owner.
function _checkOwner() internal view virtual {
/// @solidity memory-safe-assembly
assembly {
// If the caller is not the stored owner, revert.
if iszero(eq(caller(), sload(_OWNER_SLOT))) {
mstore(0x00, 0x82b42900) // `Unauthorized()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Returns how long a two-step ownership handover is valid for in seconds.
/// Override to return a different value if needed.
/// Made internal to conserve bytecode. Wrap it in a public function if needed.
function _ownershipHandoverValidFor() internal view virtual returns (uint64) {
return 48 * 3600;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PUBLIC UPDATE FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Allows the owner to transfer the ownership to `newOwner`.
function transferOwnership(address newOwner) public payable virtual onlyOwner {
/// @solidity memory-safe-assembly
assembly {
if iszero(shl(96, newOwner)) {
mstore(0x00, 0x7448fbae) // `NewOwnerIsZeroAddress()`.
revert(0x1c, 0x04)
}
}
_setOwner(newOwner);
}
/// @dev Allows the owner to renounce their ownership.
function renounceOwnership() public payable virtual onlyOwner {
_setOwner(address(0));
}
/// @dev Request a two-step ownership handover to the caller.
/// The request will automatically expire in 48 hours (172800 seconds) by default.
function requestOwnershipHandover() public payable virtual {
unchecked {
uint256 expires = block.timestamp + _ownershipHandoverValidFor();
/// @solidity memory-safe-assembly
assembly {
// Compute and set the handover slot to `expires`.
mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, caller())
sstore(keccak256(0x0c, 0x20), expires)
// Emit the {OwnershipHandoverRequested} event.
log2(0, 0, _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE, caller())
}
}
}
/// @dev Cancels the two-step ownership handover to the caller, if any.
function cancelOwnershipHandover() public payable virtual {
/// @solidity memory-safe-assembly
assembly {
// Compute and set the handover slot to 0.
mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, caller())
sstore(keccak256(0x0c, 0x20), 0)
// Emit the {OwnershipHandoverCanceled} event.
log2(0, 0, _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE, caller())
}
}
/// @dev Allows the owner to complete the two-step ownership handover to `pendingOwner`.
/// Reverts if there is no existing ownership handover requested by `pendingOwner`.
function completeOwnershipHandover(address pendingOwner) public payable virtual onlyOwner {
/// @solidity memory-safe-assembly
assembly {
// Compute and set the handover slot to 0.
mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, pendingOwner)
let handoverSlot := keccak256(0x0c, 0x20)
// If the handover does not exist, or has expired.
if gt(timestamp(), sload(handoverSlot)) {
mstore(0x00, 0x6f5e8818) // `NoHandoverRequest()`.
revert(0x1c, 0x04)
}
// Set the handover slot to 0.
sstore(handoverSlot, 0)
}
_setOwner(pendingOwner);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PUBLIC READ FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the owner of the contract.
function owner() public view virtual returns (address result) {
/// @solidity memory-safe-assembly
assembly {
result := sload(_OWNER_SLOT)
}
}
/// @dev Returns the expiry timestamp for the two-step ownership handover to `pendingOwner`.
function ownershipHandoverExpiresAt(address pendingOwner)
public
view
virtual
returns (uint256 result)
{
/// @solidity memory-safe-assembly
assembly {
// Compute the handover slot.
mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, pendingOwner)
// Load the handover slot.
result := sload(keccak256(0x0c, 0x20))
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* MODIFIERS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Marks a function as only callable by the owner.
modifier onlyOwner() virtual {
_checkOwner();
_;
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SafeTransferLib.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @author Permit2 operations from (https://github.com/Uniswap/permit2/blob/main/src/libraries/Permit2Lib.sol)
///
/// @dev Note:
/// - For ETH transfers, please use `forceSafeTransferETH` for DoS protection.
/// - For ERC20s, this implementation won't check that a token has code,
/// responsibility is delegated to the caller.
library SafeTransferLib {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The ETH transfer has failed.
error ETHTransferFailed();
/// @dev The ERC20 `transferFrom` has failed.
error TransferFromFailed();
/// @dev The ERC20 `transfer` has failed.
error TransferFailed();
/// @dev The ERC20 `approve` has failed.
error ApproveFailed();
/// @dev The Permit2 operation has failed.
error Permit2Failed();
/// @dev The Permit2 amount must be less than `2**160 - 1`.
error Permit2AmountOverflow();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Suggested gas stipend for contract receiving ETH that disallows any storage writes.
uint256 internal constant GAS_STIPEND_NO_STORAGE_WRITES = 2300;
/// @dev Suggested gas stipend for contract receiving ETH to perform a few
/// storage reads and writes, but low enough to prevent griefing.
uint256 internal constant GAS_STIPEND_NO_GRIEF = 100000;
/// @dev The unique EIP-712 domain domain separator for the DAI token contract.
bytes32 internal constant DAI_DOMAIN_SEPARATOR =
0xdbb8cf42e1ecb028be3f3dbc922e1d878b963f411dc388ced501601c60f7c6f7;
/// @dev The address for the WETH9 contract on Ethereum mainnet.
address internal constant WETH9 = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
/// @dev The canonical Permit2 address.
/// [Github](https://github.com/Uniswap/permit2)
/// [Etherscan](https://etherscan.io/address/0x000000000022D473030F116dDEE9F6B43aC78BA3)
address internal constant PERMIT2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ETH OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// If the ETH transfer MUST succeed with a reasonable gas budget, use the force variants.
//
// The regular variants:
// - Forwards all remaining gas to the target.
// - Reverts if the target reverts.
// - Reverts if the current contract has insufficient balance.
//
// The force variants:
// - Forwards with an optional gas stipend
// (defaults to `GAS_STIPEND_NO_GRIEF`, which is sufficient for most cases).
// - If the target reverts, or if the gas stipend is exhausted,
// creates a temporary contract to force send the ETH via `SELFDESTRUCT`.
// Future compatible with `SENDALL`: https://eips.ethereum.org/EIPS/eip-4758.
// - Reverts if the current contract has insufficient balance.
//
// The try variants:
// - Forwards with a mandatory gas stipend.
// - Instead of reverting, returns whether the transfer succeeded.
/// @dev Sends `amount` (in wei) ETH to `to`.
function safeTransferETH(address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
if iszero(call(gas(), to, amount, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Sends all the ETH in the current contract to `to`.
function safeTransferAllETH(address to) internal {
/// @solidity memory-safe-assembly
assembly {
// Transfer all the ETH and check if it succeeded or not.
if iszero(call(gas(), to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Force sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
function forceSafeTransferETH(address to, uint256 amount, uint256 gasStipend) internal {
/// @solidity memory-safe-assembly
assembly {
if lt(selfbalance(), amount) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
if iszero(call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Force sends all the ETH in the current contract to `to`, with a `gasStipend`.
function forceSafeTransferAllETH(address to, uint256 gasStipend) internal {
/// @solidity memory-safe-assembly
assembly {
if iszero(call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Force sends `amount` (in wei) ETH to `to`, with `GAS_STIPEND_NO_GRIEF`.
function forceSafeTransferETH(address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
if lt(selfbalance(), amount) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
if iszero(call(GAS_STIPEND_NO_GRIEF, to, amount, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Force sends all the ETH in the current contract to `to`, with `GAS_STIPEND_NO_GRIEF`.
function forceSafeTransferAllETH(address to) internal {
/// @solidity memory-safe-assembly
assembly {
// forgefmt: disable-next-item
if iszero(call(GAS_STIPEND_NO_GRIEF, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
function trySafeTransferETH(address to, uint256 amount, uint256 gasStipend)
internal
returns (bool success)
{
/// @solidity memory-safe-assembly
assembly {
success := call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00)
}
}
/// @dev Sends all the ETH in the current contract to `to`, with a `gasStipend`.
function trySafeTransferAllETH(address to, uint256 gasStipend)
internal
returns (bool success)
{
/// @solidity memory-safe-assembly
assembly {
success := call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC20 OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
/// Reverts upon failure.
///
/// The `from` account must have at least `amount` approved for
/// the current contract to manage.
function safeTransferFrom(address token, address from, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x60, amount) // Store the `amount` argument.
mstore(0x40, to) // Store the `to` argument.
mstore(0x2c, shl(96, from)) // Store the `from` argument.
mstore(0x0c, 0x23b872dd000000000000000000000000) // `transferFrom(address,address,uint256)`.
// Perform the transfer, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
)
) {
mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
revert(0x1c, 0x04)
}
mstore(0x60, 0) // Restore the zero slot to zero.
mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
///
/// The `from` account must have at least `amount` approved for the current contract to manage.
function trySafeTransferFrom(address token, address from, address to, uint256 amount)
internal
returns (bool success)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x60, amount) // Store the `amount` argument.
mstore(0x40, to) // Store the `to` argument.
mstore(0x2c, shl(96, from)) // Store the `from` argument.
mstore(0x0c, 0x23b872dd000000000000000000000000) // `transferFrom(address,address,uint256)`.
success :=
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
)
mstore(0x60, 0) // Restore the zero slot to zero.
mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Sends all of ERC20 `token` from `from` to `to`.
/// Reverts upon failure.
///
/// The `from` account must have their entire balance approved for the current contract to manage.
function safeTransferAllFrom(address token, address from, address to)
internal
returns (uint256 amount)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x40, to) // Store the `to` argument.
mstore(0x2c, shl(96, from)) // Store the `from` argument.
mstore(0x0c, 0x70a08231000000000000000000000000) // `balanceOf(address)`.
// Read the balance, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x1f), // At least 32 bytes returned.
staticcall(gas(), token, 0x1c, 0x24, 0x60, 0x20)
)
) {
mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
revert(0x1c, 0x04)
}
mstore(0x00, 0x23b872dd) // `transferFrom(address,address,uint256)`.
amount := mload(0x60) // The `amount` is already at 0x60. We'll need to return it.
// Perform the transfer, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
)
) {
mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
revert(0x1c, 0x04)
}
mstore(0x60, 0) // Restore the zero slot to zero.
mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Sends `amount` of ERC20 `token` from the current contract to `to`.
/// Reverts upon failure.
function safeTransfer(address token, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, to) // Store the `to` argument.
mstore(0x34, amount) // Store the `amount` argument.
mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`.
// Perform the transfer, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
revert(0x1c, 0x04)
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Sends all of ERC20 `token` from the current contract to `to`.
/// Reverts upon failure.
function safeTransferAll(address token, address to) internal returns (uint256 amount) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, 0x70a08231) // Store the function selector of `balanceOf(address)`.
mstore(0x20, address()) // Store the address of the current contract.
// Read the balance, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x1f), // At least 32 bytes returned.
staticcall(gas(), token, 0x1c, 0x24, 0x34, 0x20)
)
) {
mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
revert(0x1c, 0x04)
}
mstore(0x14, to) // Store the `to` argument.
amount := mload(0x34) // The `amount` is already at 0x34. We'll need to return it.
mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`.
// Perform the transfer, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
mstore(0x00, 0x90b8ec18) // `TransferFailed()`.
revert(0x1c, 0x04)
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract.
/// Reverts upon failure.
function safeApprove(address token, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, to) // Store the `to` argument.
mstore(0x34, amount) // Store the `amount` argument.
mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
// Perform the approval, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`.
revert(0x1c, 0x04)
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract.
/// If the initial attempt to approve fails, attempts to reset the approved amount to zero,
/// then retries the approval again (some tokens, e.g. USDT, requires this).
/// Reverts upon failure.
function safeApproveWithRetry(address token, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, to) // Store the `to` argument.
mstore(0x34, amount) // Store the `amount` argument.
mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
// Perform the approval, retrying upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
mstore(0x34, 0) // Store 0 for the `amount`.
mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`.
pop(call(gas(), token, 0, 0x10, 0x44, codesize(), 0x00)) // Reset the approval.
mstore(0x34, amount) // Store back the original `amount`.
// Retry the approval, reverting upon failure.
if iszero(
and(
or(eq(mload(0x00), 1), iszero(returndatasize())), // Returned 1 or nothing.
call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
)
) {
mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`.
revert(0x1c, 0x04)
}
}
mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten.
}
}
/// @dev Returns the amount of ERC20 `token` owned by `account`.
/// Returns zero if the `token` does not exist.
function balanceOf(address token, address account) internal view returns (uint256 amount) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, account) // Store the `account` argument.
mstore(0x00, 0x70a08231000000000000000000000000) // `balanceOf(address)`.
amount :=
mul( // The arguments of `mul` are evaluated from right to left.
mload(0x20),
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x1f), // At least 32 bytes returned.
staticcall(gas(), token, 0x10, 0x24, 0x20, 0x20)
)
)
}
}
/// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
/// If the initial attempt fails, try to use Permit2 to transfer the token.
/// Reverts upon failure.
///
/// The `from` account must have at least `amount` approved for the current contract to manage.
function safeTransferFrom2(address token, address from, address to, uint256 amount) internal {
if (!trySafeTransferFrom(token, from, to, amount)) {
permit2TransferFrom(token, from, to, amount);
}
}
/// @dev Sends `amount` of ERC20 `token` from `from` to `to` via Permit2.
/// Reverts upon failure.
function permit2TransferFrom(address token, address from, address to, uint256 amount)
internal
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
mstore(add(m, 0x74), shr(96, shl(96, token)))
mstore(add(m, 0x54), amount)
mstore(add(m, 0x34), to)
mstore(add(m, 0x20), shl(96, from))
// `transferFrom(address,address,uint160,address)`.
mstore(m, 0x36c78516000000000000000000000000)
let p := PERMIT2
let exists := eq(chainid(), 1)
if iszero(exists) { exists := iszero(iszero(extcodesize(p))) }
if iszero(and(call(gas(), p, 0, add(m, 0x10), 0x84, codesize(), 0x00), exists)) {
mstore(0x00, 0x7939f4248757f0fd) // `TransferFromFailed()` or `Permit2AmountOverflow()`.
revert(add(0x18, shl(2, iszero(iszero(shr(160, amount))))), 0x04)
}
}
}
/// @dev Permit a user to spend a given amount of
/// another user's tokens via native EIP-2612 permit if possible, falling
/// back to Permit2 if native permit fails or is not implemented on the token.
function permit2(
address token,
address owner,
address spender,
uint256 amount,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
bool success;
/// @solidity memory-safe-assembly
assembly {
for {} shl(96, xor(token, WETH9)) {} {
mstore(0x00, 0x3644e515) // `DOMAIN_SEPARATOR()`.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
lt(iszero(mload(0x00)), eq(returndatasize(), 0x20)), // Returns 1 non-zero word.
// Gas stipend to limit gas burn for tokens that don't refund gas when
// an non-existing function is called. 5K should be enough for a SLOAD.
staticcall(5000, token, 0x1c, 0x04, 0x00, 0x20)
)
) { break }
// After here, we can be sure that token is a contract.
let m := mload(0x40)
mstore(add(m, 0x34), spender)
mstore(add(m, 0x20), shl(96, owner))
mstore(add(m, 0x74), deadline)
if eq(mload(0x00), DAI_DOMAIN_SEPARATOR) {
mstore(0x14, owner)
mstore(0x00, 0x7ecebe00000000000000000000000000) // `nonces(address)`.
mstore(add(m, 0x94), staticcall(gas(), token, 0x10, 0x24, add(m, 0x54), 0x20))
mstore(m, 0x8fcbaf0c000000000000000000000000) // `IDAIPermit.permit`.
// `nonces` is already at `add(m, 0x54)`.
// `1` is already stored at `add(m, 0x94)`.
mstore(add(m, 0xb4), and(0xff, v))
mstore(add(m, 0xd4), r)
mstore(add(m, 0xf4), s)
success := call(gas(), token, 0, add(m, 0x10), 0x104, codesize(), 0x00)
break
}
mstore(m, 0xd505accf000000000000000000000000) // `IERC20Permit.permit`.
mstore(add(m, 0x54), amount)
mstore(add(m, 0x94), and(0xff, v))
mstore(add(m, 0xb4), r)
mstore(add(m, 0xd4), s)
success := call(gas(), token, 0, add(m, 0x10), 0xe4, codesize(), 0x00)
break
}
}
if (!success) simplePermit2(token, owner, spender, amount, deadline, v, r, s);
}
/// @dev Simple permit on the Permit2 contract.
function simplePermit2(
address token,
address owner,
address spender,
uint256 amount,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40)
mstore(m, 0x927da105) // `allowance(address,address,address)`.
{
let addressMask := shr(96, not(0))
mstore(add(m, 0x20), and(addressMask, owner))
mstore(add(m, 0x40), and(addressMask, token))
mstore(add(m, 0x60), and(addressMask, spender))
mstore(add(m, 0xc0), and(addressMask, spender))
}
let p := mul(PERMIT2, iszero(shr(160, amount)))
if iszero(
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x5f), // Returns 3 words: `amount`, `expiration`, `nonce`.
staticcall(gas(), p, add(m, 0x1c), 0x64, add(m, 0x60), 0x60)
)
) {
mstore(0x00, 0x6b836e6b8757f0fd) // `Permit2Failed()` or `Permit2AmountOverflow()`.
revert(add(0x18, shl(2, iszero(p))), 0x04)
}
mstore(m, 0x2b67b570) // `Permit2.permit` (PermitSingle variant).
// `owner` is already `add(m, 0x20)`.
// `token` is already at `add(m, 0x40)`.
mstore(add(m, 0x60), amount)
mstore(add(m, 0x80), 0xffffffffffff) // `expiration = type(uint48).max`.
// `nonce` is already at `add(m, 0xa0)`.
// `spender` is already at `add(m, 0xc0)`.
mstore(add(m, 0xe0), deadline)
mstore(add(m, 0x100), 0x100) // `signature` offset.
mstore(add(m, 0x120), 0x41) // `signature` length.
mstore(add(m, 0x140), r)
mstore(add(m, 0x160), s)
mstore(add(m, 0x180), shl(248, v))
if iszero(call(gas(), p, 0, add(m, 0x1c), 0x184, codesize(), 0x00)) {
mstore(0x00, 0x6b836e6b) // `Permit2Failed()`.
revert(0x1c, 0x04)
}
}
}
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.20;
import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import {Ownable} from "solady/auth/Ownable.sol";
import {g8keepToken} from "./g8keepToken.sol";
import {g8keepPenaltyReceiver} from "./g8keepPenaltyReceiver.sol";
import {g8keepFactoryConfiguration} from "./g8keepFactoryConfiguration.sol";
import {IUniswapV2Router02} from "./interfaces/IUniswapV2Router02.sol";
import {IUniswapV2Pair} from "./interfaces/IUniswapV2Pair.sol";
import {IG8keepFactory} from "./interfaces/IG8keepFactory.sol";
import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";
/**
* @title g8keepFactory
* @notice Factory for managing deployment parameters, deploying g8keep
* @notice token contracts, locking initial liquidity, and managing token fees.
*/
contract g8keepFactory is IG8keepFactory, Ownable {
/// @dev Wrapped native token contract for token deployments using native currency.
address public immutable WETH;
/// @dev Uniswap V2 router address for the chain.
address public immutable UNISWAP_V2_ROUTER;
/// @dev Vesting contract to be used when deployer tokens are subject to vesting restrictions.
address public g8keepTokenVesting;
/// @dev Fee wallet to receive initial liquidity fees.
address public g8keepFeeWallet;
/// @dev Address that will receive penalty tax tokens.
address public penaltyReceiver;
/// @dev Fee in BPS assessed on token trades, paid to g8keep. Initial value: 1.0%
uint16 public g8keepFee = 100;
/// @dev Fee in BPS assessed on initial liquidity supplied. Initial value: 2.0%
uint16 public g8keepInitialLiquidityFee = 200;
/// @dev Maximum buy fee in BPS assessed on token trades, paid to token deployer. Initial value: 5.0%
uint16 public maxBuyFee = 500;
/// @dev Maximum sell fee in BPS assessed on token trades, paid to token deployer. Initial value: 5.0%
uint16 public maxSellFee = 500;
/// @dev Minimum time in seconds that a deployer's share of tokens must be vested over. Initial value: 1 day
uint24 public minimumDeployVestTime = 1 days;
/// @dev Minimum time in seconds that a deployed token must have snipe protection enabled.
uint24 public minimumSnipeProtectionSeconds = 30 minutes;
/// @dev Maximum time in seconds that a deployed token may have snipe protection enabled.
uint24 public maximumSnipeProtectionSeconds = 30 days;
/// @dev Minimum time in seconds that a deployed token must have heavy snipe protection enabled.
uint24 public minimumHeavySnipeProtectionSeconds = 30 minutes;
/// @dev Maximum time in seconds that a deployed token may have heavy snipe protection enabled.
uint24 public maximumHeavySnipeProtectionSeconds = 1 days;
/// @dev Minimum exponent that a deployed token must have for heavy snipe penalties.
uint8 public minimumHeavySnipeExponent = 20;
/// @dev Maximum exponent that a deployed token may have for heavy snipe penalties.
uint8 public maximumHeavySnipeExponent = 60;
/// @dev Guardrail for `g8keepFee` to prevent it from being set over 100 BPS.
uint16 private constant MAX_SETTING_G8KEEP_FEE = 100;
/// @dev Guardrail for `g8keepInitialLiquidityFee` to prevent it from being set over 200 BPS.
uint16 private constant MAX_SETTING_G8KEEP_INITIAL_LIQUIDITY_FEE = 200;
/// @dev Guardrail for `maxBuyFee` to prevent it from being set over 500 BPS.
uint16 private constant MAX_SETTING_BUY_FEE = 500;
/// @dev Guardrail for `maxSellFee` to prevent it from being set over 500 BPS.
uint16 private constant MAX_SETTING_SELL_FEE = 500;
/// @dev Guardrail for `minimumDeployVestTime` to prevent it from being set over 90 days.
uint40 private constant MAX_SETTING_MINIMUM_DEPLOY_VEST_TIME = 90 days;
/// @dev Guardrail for `minimumSnipeProtectionSeconds` to prevent it from being set over 7 days.
uint40 private constant MAX_SETTING_MINIMUM_SNIPE_PROTECTION_SECONDS = 7 days;
/// @dev Guardrail for `maximumSnipeProtectionSeconds` to prevent it from being set over 365 days.
uint40 private constant MAX_SETTING_MAXIMUM_SNIPE_PROTECTION_SECONDS = 365 days;
/// @dev Guardrail for `minimumHeavySnipeProtectionSeconds` to prevent it from being set over 1 days.
uint40 private constant MAX_SETTING_MINIMUM_HEAVY_SNIPE_PROTECTION_SECONDS = 1 days;
/// @dev Guardrail for `maximumHeavySnipeProtectionSeconds` to prevent it from being set over 5 days.
uint40 private constant MAX_SETTING_MAXIMUM_HEAVY_SNIPE_PROTECTION_SECONDS = 5 days;
/// @dev Guardrail for `minimumHeavySnipeExponent` to prevent it from being set under 2.
uint8 private constant MIN_SETTING_MINIMUM_HEAVY_SNIPE_EXPONENT = 2;
/// @dev Guardrail for `minimumHeavySnipeExponent` to prevent it from being set over 20.
uint8 private constant MAX_SETTING_MINIMUM_HEAVY_SNIPE_EXPONENT = 20;
/// @dev Guardrail for `maximumHeavySnipeExponent` to prevent it from being set over 100.
uint8 private constant MAX_SETTING_MAXIMUM_HEAVY_SNIPE_EXPONENT = 100;
/// @dev Constant value for BPS.
uint16 private constant BPS = 10_000;
/// @dev Mapping to flag a token address as an allowed pair token for g8keep token deployments.
mapping(address => bool) public allowedPairs;
/// @dev Mapping of the minimum initial liquidity to be provided for a paired token.
mapping(address => uint256) public pairedTokenMinimumLiquidity;
/// @dev Mapping of tokens that are locked to prevent sale or withdrawal.
mapping(address => bool) private lockedTokens;
/// @dev Emitted when a token is deployed.
event TokenDeployed(
address indexed token,
address indexed pair,
address indexed deployer,
string symbol
);
/// @dev Emitted when the g8keepFactory owner updates deployment settings.
event DeploymentSettingsUpdated(
uint16 maxBuyFee,
uint16 maxSellFee,
uint24 minimumDeployVestTime,
uint24 minimumSnipeProtectionSeconds,
uint24 maximumSnipeProtectionSeconds,
uint24 minimumHeavySnipeProtectionSeconds,
uint24 maximumHeavySnipeProtectionSeconds,
uint8 minimumHeavySnipeExponent,
uint8 maximumHeavySnipeExponent
);
/// @dev Emitted when the g8keepFactory owner updates a paired token settings.
event PairedTokenSettingsUpdated(
address indexed pairedToken,
bool allowed,
uint256 minimumLiquidity
);
/// @dev Emitted when the g8keepFactory owner updates a penalty receiver.
event PenaltyReceiverUpdated(
address indexed penaltyReceiver
);
/// @dev Thrown when arrays must match in length and they do not.
error ArrayLengthMismatch();
/// @dev Thrown when selling tokens and the deadline for the sale has passed.
error DeadlinePassed();
/// @dev Thrown when the deposit to the WETH contract for initial liquidity fails.
error FailedToDepositWETH();
/// @dev Thrown when deploying a token and the parameters are not compliant with deployment settings.
error InvalidDeploymentParameters();
/// @dev Thrown when the g8keepFactory owner is updating deployment settings and they are not compliant
/// @dev with the constant guardrails or when setting a penalty receiver that is the current receiver.
error InvalidSettings();
/// @dev Thrown when deploying a token and the initial liquidity is less than the minimum required.
error NotEnoughInitialLiquidity();
/// @dev Thrown when deploying a token with a paired token that is not allowed.
error PairNotAllowed();
/// @dev Thrown when attempting to withdraw a token that is locked.
error TokenLocked();
/// @dev Thrown when native value is sent with a token deployment that does not use wrapped native.
error ValueNotAllowedForNonWETHPairs();
/// @dev Thrown when deploying a token and the value sent exceeds the initial liquidity specified.
error ValueSentNotValid();
/// @dev Thrown when withdrawing native token from the contract and the withdrawal fails.
error WithdrawalFailed();
/// @dev Thrown when a supplied address that must be non-zero is zero.
error ZeroAddress();
/// @dev Struct used for defining token sale parameters.
struct TokenSale {
address tokenAddress; // Address of the token to sell.
uint256 amountToSell; // Amount of the token to sell.
uint256 amountOutMin; // Minimum amount of paired token to receive.
address recipient; // Address to receive the sale value at.
}
/**
* @dev Constructs the g8keepFactory contract.
* @dev Initialization parameters are retrieved from a configuration contract so that
* @dev the g8keepFactory may be deterministically deployed on EVM chains at the same
* @dev address.
*
* @param _g8keepFactoryConfiguration The address of the factory configuration contract to retrieve configuration parameters.
*/
constructor(address _g8keepFactoryConfiguration) {
g8keepFactoryConfiguration config = g8keepFactoryConfiguration(_g8keepFactoryConfiguration);
(
address _defaultOwner,
address _g8keepFeeWallet,
address _uniswapV2Router,
address _WETH,
uint256 _minimumWETHLiquidity
) = config.getFactoryConfiguration();
if (
_defaultOwner == address(0)
|| _g8keepFeeWallet == address(0)
|| _uniswapV2Router == address(0)
|| _WETH == address(0)
) {
revert ZeroAddress();
}
_initializeOwner(_defaultOwner);
allowedPairs[_WETH] = true;
pairedTokenMinimumLiquidity[_WETH] = _minimumWETHLiquidity;
WETH = _WETH;
UNISWAP_V2_ROUTER = _uniswapV2Router;
g8keepFeeWallet = _g8keepFeeWallet;
penaltyReceiver = address(new g8keepPenaltyReceiver(_defaultOwner));
SafeTransferLib.safeApprove(_WETH, _uniswapV2Router, type(uint256).max);
}
/////////// PUBLIC FUNCTIONS ///////////
/**
* @notice Deploys a g8keepToken contract, creates and funds the LP, locks initial liquidity.
*
* @dev Parameters must be within ranges defined by the factory settings.
* @dev Token salt value must be calculated to generate a token deployment address
* @dev that is greater than the `_pairedToken` address.
*
* @param _initialLiquidity Amount of paired token to supply as liquidity in the LP.
* @param _name Name of the token being deployed.
* @param _symbol Symbol of the token being deployed.
* @param _totalSupply Total supply to be minted during deployment.
* @param _treasuryWallet Address that will receive deployer buy and sell fees.
* @param _buyFee Buy fee in BPS.
* @param _sellFee Sell fee in BPS.
* @param _pairedToken Address of the token to pair with in the LP.
* @param _deployReserve Amount of total supply in BPS to allocate to deployer.
* @param _deployVestTime Time in seconds to vest the deployer's allocation.
* @param _snipeProtectionSeconds Amount of time in seconds that snipe protection will be enabled.
* @param _heavySnipeSeconds Amount of time in seconds that heavy snipe penalty is enabled.
* @param _heavySnipeExponent Starting exponent for heavy snipe penalty.
* @param _tokenSalt Salt for token deployment to generate a deterministic address.
*/
function deployToken(
uint256 _initialLiquidity,
string memory _name,
string memory _symbol,
uint256 _totalSupply,
address _treasuryWallet,
uint16 _buyFee,
uint16 _sellFee,
address _pairedToken,
uint256 _deployReserve,
uint256 _deployVestTime,
uint256 _snipeProtectionSeconds,
uint256 _heavySnipeSeconds,
uint256 _heavySnipeExponent,
bytes32 _tokenSalt
) external payable returns (address _tokenAddress) {
if (!allowedPairs[_pairedToken]) revert PairNotAllowed();
if (
_buyFee > maxBuyFee || _sellFee > maxSellFee || _deployVestTime < minimumDeployVestTime
|| (_deployVestTime > 0 && g8keepTokenVesting == address(0))
|| _snipeProtectionSeconds < minimumSnipeProtectionSeconds
|| _snipeProtectionSeconds > maximumSnipeProtectionSeconds
|| _heavySnipeSeconds < minimumHeavySnipeProtectionSeconds
|| _heavySnipeSeconds > maximumHeavySnipeProtectionSeconds
|| _heavySnipeExponent < minimumHeavySnipeExponent
|| _heavySnipeExponent > maximumHeavySnipeExponent
|| lockedTokens[_pairedToken]
|| _treasuryWallet == address(0)
) {
revert InvalidDeploymentParameters();
}
if (_pairedToken == WETH) {
if (msg.value > _initialLiquidity) {
revert ValueSentNotValid();
}
// Deposit native tokens to WETH
if (msg.value > 0) {
(bool success,) = WETH.call{value: msg.value}("");
if (!success) revert FailedToDepositWETH();
}
// If value is insufficient for liquidity, attempt to transfer WETH
if (msg.value < _initialLiquidity) {
unchecked {
SafeTransferLib.safeTransferFrom(
WETH,
msg.sender,
address(this),
_initialLiquidity - msg.value
);
}
}
} else if (msg.value > 0) {
revert ValueNotAllowedForNonWETHPairs();
} else {
uint256 balanceBefore = IERC20(_pairedToken).balanceOf(address(this));
SafeTransferLib.safeTransferFrom(_pairedToken, msg.sender, address(this), _initialLiquidity);
uint256 balanceAfter = IERC20(_pairedToken).balanceOf(address(this));
if (balanceAfter > balanceBefore) {
unchecked {
_initialLiquidity = balanceAfter - balanceBefore;
}
} else {
_initialLiquidity = 0;
}
}
g8keepToken token = new g8keepToken{salt: _tokenSalt}(
msg.sender,
_name,
_symbol,
_totalSupply,
_treasuryWallet,
penaltyReceiver,
_buyFee,
_sellFee,
g8keepFee,
UNISWAP_V2_ROUTER,
_pairedToken,
_deployReserve,
_deployVestTime,
_snipeProtectionSeconds,
_heavySnipeSeconds,
_heavySnipeExponent
);
address pairAddress = token.UNISWAP_V2_PAIR();
if (IERC20(_pairedToken).balanceOf(pairAddress) > 0) {
uint256 balanceBefore = IERC20(_pairedToken).balanceOf(address(this));
IUniswapV2Pair(pairAddress).skim(address(this));
uint256 balanceAfter = IERC20(_pairedToken).balanceOf(address(this));
if (balanceAfter > balanceBefore) {
unchecked {
_initialLiquidity = _initialLiquidity + balanceAfter - balanceBefore;
}
}
}
if (_initialLiquidity < pairedTokenMinimumLiquidity[_pairedToken]) revert NotEnoughInitialLiquidity();
//Prevent sales of the LP token
lockedTokens[pairAddress] = true;
uint256 valueToG8keep = (_initialLiquidity * g8keepInitialLiquidityFee) / BPS;
if (valueToG8keep > 0) {
SafeTransferLib.safeTransfer(_pairedToken, g8keepFeeWallet, valueToG8keep);
}
// add liquidity to LP
IUniswapV2Router02(UNISWAP_V2_ROUTER).addLiquidity(
_pairedToken,
address(token),
_initialLiquidity - valueToG8keep,
token.balanceOf(address(this)),
0,
0,
address(this),
block.timestamp
);
// emit Event for UI to store token details
emit TokenDeployed(address(token), pairAddress, msg.sender, _symbol);
return address(token);
}
/////////// OWNER FUNCTIONS ///////////
/**
* @notice Admin function to configure a paired token.
*
* @param pairedToken Address of the paired token to configure.
* @param allowed True if the token is allowed as a paired token, false if not.
* @param minimumLiquidity Minimum amount of token that may be supplied as initial liquidity.
*/
function setPairedTokenSettings(
address pairedToken,
bool allowed,
uint256 minimumLiquidity
) external onlyOwner {
uint256 allowanceAmount;
if (allowed) {
allowanceAmount = type(uint256).max;
}
_setPairedTokenSettings(pairedToken, allowed, minimumLiquidity, allowanceAmount);
}
/**
* @notice Admin function to configure a paired token with a specific allowance amount.
*
* @dev Used for certain ERC20 tokens that have unusual approval methods.
*
* @param pairedToken Address of the paired token to configure.
* @param allowed True if the token is allowed as a paired token, false if not.
* @param minimumLiquidity Minimum amount of token that may be supplied as initial liquidity.
* @param allowanceAmount Amount to set for the allowance to the Uniswap Router.
*/
function setPairedTokenSettingsWithAllowance(
address pairedToken,
bool allowed,
uint256 minimumLiquidity,
uint256 allowanceAmount
) external onlyOwner {
_setPairedTokenSettings(pairedToken, allowed, minimumLiquidity, allowanceAmount);
}
/**
* @notice Admin function to configure deployment parameters.
*
* @dev Settings parameters must be within the constant guardrails defined.
*
* @param _maxBuyFee Maximum buy fee in BPS.
* @param _maxSellFee Maximum sell fee in BPS.
* @param _minimumDeployVestTime Minimum time in seconds that a deployer tokens must vest over.
* @param _minimumSnipeProtectionSeconds Minimum time in seconds for snipe protection to be enabled.
* @param _maximumSnipeProtectionSeconds Maximum time in seconds for snipe protection to be enabled.
* @param _minimumHeavySnipeProtectionSeconds Minimum time in seconds for heavy snipe protection to be enabled.
* @param _maximumHeavySnipeProtectionSeconds Maximum time in seconds for heavy snipe protection to be enabled.
* @param _minimumHeavySnipeExponent Minimum exponent penalty during heavy snipe protection.
* @param _maximumHeavySnipeExponent Maximum exponent penalty during heavy snipe protection.
*/
function setDeploymentSettings(
uint16 _maxBuyFee,
uint16 _maxSellFee,
uint24 _minimumDeployVestTime,
uint24 _minimumSnipeProtectionSeconds,
uint24 _maximumSnipeProtectionSeconds,
uint24 _minimumHeavySnipeProtectionSeconds,
uint24 _maximumHeavySnipeProtectionSeconds,
uint8 _minimumHeavySnipeExponent,
uint8 _maximumHeavySnipeExponent
) external onlyOwner {
if (
_maxBuyFee > MAX_SETTING_BUY_FEE || _maxSellFee > MAX_SETTING_SELL_FEE
|| _minimumDeployVestTime > MAX_SETTING_MINIMUM_DEPLOY_VEST_TIME
|| (_minimumDeployVestTime > 0 && g8keepTokenVesting == address(0))
|| _minimumSnipeProtectionSeconds > MAX_SETTING_MINIMUM_SNIPE_PROTECTION_SECONDS
|| _maximumSnipeProtectionSeconds > MAX_SETTING_MAXIMUM_SNIPE_PROTECTION_SECONDS
|| _minimumSnipeProtectionSeconds > _maximumSnipeProtectionSeconds
|| _minimumHeavySnipeProtectionSeconds > MAX_SETTING_MINIMUM_HEAVY_SNIPE_PROTECTION_SECONDS
|| _maximumHeavySnipeProtectionSeconds > MAX_SETTING_MAXIMUM_HEAVY_SNIPE_PROTECTION_SECONDS
|| _minimumHeavySnipeProtectionSeconds > _maximumHeavySnipeProtectionSeconds
|| _minimumHeavySnipeExponent < MIN_SETTING_MINIMUM_HEAVY_SNIPE_EXPONENT
|| _minimumHeavySnipeExponent > MAX_SETTING_MINIMUM_HEAVY_SNIPE_EXPONENT
|| _maximumHeavySnipeExponent > MAX_SETTING_MAXIMUM_HEAVY_SNIPE_EXPONENT
|| _minimumHeavySnipeExponent > _maximumHeavySnipeExponent
) {
revert InvalidSettings();
}
maxBuyFee = _maxBuyFee;
maxSellFee = _maxSellFee;
minimumDeployVestTime = _minimumDeployVestTime;
minimumSnipeProtectionSeconds = _minimumSnipeProtectionSeconds;
maximumSnipeProtectionSeconds = _maximumSnipeProtectionSeconds;
minimumHeavySnipeProtectionSeconds = _minimumHeavySnipeProtectionSeconds;
maximumHeavySnipeProtectionSeconds = _maximumHeavySnipeProtectionSeconds;
minimumHeavySnipeExponent = _minimumHeavySnipeExponent;
maximumHeavySnipeExponent = _maximumHeavySnipeExponent;
emit DeploymentSettingsUpdated(
_maxBuyFee,
_maxSellFee,
_minimumDeployVestTime,
_minimumSnipeProtectionSeconds,
_maximumSnipeProtectionSeconds,
_minimumHeavySnipeProtectionSeconds,
_maximumHeavySnipeProtectionSeconds,
_minimumHeavySnipeExponent,
_maximumHeavySnipeExponent
);
}
/**
* @notice Admin function to set the g8keep fee that will be assessed on token trades.
*
* @dev Fee must be less than the constant guardrail settings.
*
* @param _g8keepFee Fee in BPS to assess on token trades.
*/
function setG8keepFee(uint16 _g8keepFee) external onlyOwner {
if (_g8keepFee > MAX_SETTING_G8KEEP_FEE) {
revert InvalidSettings();
}
g8keepFee = _g8keepFee;
}
/**
* @notice Admin function to set the g8keep fee that will be assessed on initial liquidity.
*
* @dev Fee must be less than the constant guardrail settings.
*
* @param _g8keepInitialLiquidityFee Fee in BPS to assess on initial liquidity.
*/
function setG8keepInitialLiquidityFee(uint16 _g8keepInitialLiquidityFee) external onlyOwner {
if (_g8keepInitialLiquidityFee > MAX_SETTING_G8KEEP_INITIAL_LIQUIDITY_FEE) {
revert InvalidSettings();
}
g8keepInitialLiquidityFee = _g8keepInitialLiquidityFee;
}
/**
* @notice Admin function to set the g8keep vesting contract address.
*
* @dev The vesting contract address may only be set to the zero address when minimum vesting time is set to zero.
*
* @param _g8keepTokenVesting Address of the g8keep token vesting contract.
*/
function setG8keepTokenVestingAddress(address _g8keepTokenVesting) external onlyOwner {
if (_g8keepTokenVesting == address(0) && minimumDeployVestTime > 0) {
revert InvalidSettings();
}
g8keepTokenVesting = _g8keepTokenVesting;
}
/**
* @notice Admin function to set the g8keep fee wallet to receive initial liquidity fees.
*
* @dev The fee wallet cannot be set to the zero address.
*
* @param _g8keepFeeWallet Address of the g8keep fee wallet.
*/
function setG8keepFeeWallet(address _g8keepFeeWallet) external onlyOwner {
if (_g8keepFeeWallet == address(0)) {
revert ZeroAddress();
}
g8keepFeeWallet = _g8keepFeeWallet;
}
/**
* @notice Admin function to set the penalty receiver address.
*
* @dev The penalty receiver cannot be set to the zero address.
*
* @param _penaltyReceiver Address of the penalty receiver.
*/
function setPenaltyReceiver(address _penaltyReceiver) external onlyOwner {
if (_penaltyReceiver == address(0)) {
revert ZeroAddress();
}
address currentPenaltyReceiver = penaltyReceiver;
if (currentPenaltyReceiver == _penaltyReceiver) {
revert InvalidSettings();
}
penaltyReceiver = _penaltyReceiver;
emit PenaltyReceiverUpdated(_penaltyReceiver);
}
/**
* @notice Admin function to update approvals to the Uniswap router.
*
* @dev Used to update or revoke approvals in case there is an unusual approval
* @dev mechanism on a paired ERC20 token.
*
* @param tokenAddresses Array of tokens to set approvals on.
* @param approved Array specifying whether to approve or revoke approval.
* @param approvalRevokeAmount Amount to use when revoking approvals.
*/
function setApprovalToUniswapRouter(
address[] calldata tokenAddresses,
bool[] calldata approved,
uint256 approvalRevokeAmount
) external onlyOwner {
if (tokenAddresses.length != approved.length) {
revert ArrayLengthMismatch();
}
for (uint256 i; i < tokenAddresses.length; ++i) {
address tokenAddress = tokenAddresses[i];
//skip locked tokens
if (lockedTokens[tokenAddress]) continue;
if (approved[i]) {
SafeTransferLib.safeApproveWithRetry(tokenAddress, UNISWAP_V2_ROUTER, type(uint256).max);
} else {
SafeTransferLib.safeApproveWithRetry(tokenAddress, UNISWAP_V2_ROUTER, approvalRevokeAmount);
}
}
}
/**
* @notice Admin function to sell the tokens collected as fees on the g8keep platform.
*
* @dev Locked tokens supplied in `sellParameters` will be skipped without reverting.
*
* @param sellParameters Array of token sale parameters to execute on the Uniswap router.
* @param deadline Timestamp that the transaction must execute by or revert.
*/
function sellTokens(TokenSale[] calldata sellParameters, uint256 deadline) external onlyOwner {
if (deadline < block.timestamp) {
revert DeadlinePassed();
}
for (uint256 i; i < sellParameters.length; ++i) {
TokenSale calldata sale = sellParameters[i];
address tokenAddress = sale.tokenAddress;
if (lockedTokens[tokenAddress]) {
// skip locked LP tokens
continue;
}
if (sale.recipient == address(0)) {
revert ZeroAddress();
}
address[] memory path = new address[](2);
path[0] = tokenAddress;
path[1] = g8keepToken(tokenAddress).PAIRED_TOKEN();
try IUniswapV2Router02(UNISWAP_V2_ROUTER).swapExactTokensForTokensSupportingFeeOnTransferTokens(
sale.amountToSell, sale.amountOutMin, path, sale.recipient, block.timestamp
) {} catch {}
}
}
/**
* @notice Admin function to withdraw a token that is held by the factory contract.
*
* @dev Withdrawing a locked token will revert the transaction.
* @dev Will withdraw the entire balance of the token.
*
* @param tokenAddress Address of the token to withdraw.
* @param to Address to withdraw the token to.
*/
function withdrawToken(address tokenAddress, address to) external onlyOwner {
if (lockedTokens[tokenAddress]) {
revert TokenLocked();
}
uint256 _contractBalance = IERC20(tokenAddress).balanceOf(address(this));
SafeTransferLib.safeTransfer(tokenAddress, to, _contractBalance);
}
/**
* @notice Admin function to withdraw a token that is held by the factory contract.
*
* @dev Withdrawing a locked token will revert the transaction.
* @dev Will withdraw the specified `amount` of token.
*
* @param tokenAddress Address of the token to withdraw.
* @param to Address to withdraw the token to.
* @param amount Amount of the token to withdraw.
*/
function withdrawToken(address tokenAddress, address to, uint256 amount) external onlyOwner {
if (lockedTokens[tokenAddress]) {
revert TokenLocked();
}
SafeTransferLib.safeTransfer(tokenAddress, to, amount);
}
/**
* @notice Admin function to withdraw native token that is held by the factory contract.
*
* @dev Will withdraw the entire balance.
*
* @param to Address to withdraw the token to.
*/
function withdrawETH(address to) external onlyOwner {
if (to == address(0)) revert ZeroAddress();
(bool success,) = to.call{value: address(this).balance}("");
if (!success) revert WithdrawalFailed();
}
/////////// INTERNAL FUNCTIONS ///////////
/**
* @dev Internal function for `setPairedTokenSettings` and `setPairedTokenSettingsWithAllowance`.
*
* @param pairedToken Address of the paired token to configure.
* @param allowed True if the token is allowed as a paired token, false if not.
* @param minimumLiquidity Minimum amount of token that may be supplied as initial liquidity.
* @param allowanceAmount Amount to set for the allowance to the Uniswap Router.
*/
function _setPairedTokenSettings(address pairedToken, bool allowed, uint256 minimumLiquidity, uint256 allowanceAmount) internal {
if (lockedTokens[pairedToken]) revert TokenLocked();
allowedPairs[pairedToken] = allowed;
pairedTokenMinimumLiquidity[pairedToken] = minimumLiquidity;
SafeTransferLib.safeApproveWithRetry(pairedToken, UNISWAP_V2_ROUTER, allowanceAmount);
emit PairedTokenSettingsUpdated(
pairedToken,
allowed,
minimumLiquidity
);
}
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.20;
import {Ownable} from "solady/auth/Ownable.sol";
import {IUniswapV2Router02} from "./interfaces/IUniswapV2Router02.sol";
/**
* @title g8keepFactoryConfiguration
* @notice Settings contract for initial deployment of g8keepFactory to allow
* @notice for a deterministic address across EVM blockchains where the
* @notice initial configuration settings may differ by chain.
*/
contract g8keepFactoryConfiguration is Ownable {
/// @dev Address that will be the initial owner of the g8keepFactory
address private g8keepFactoryAdmin;
/// @dev Address that will be the initial fee recipient of initial liquidity fees.
address private g8keepFeeWallet;
/// @dev Address of the Uniswap V2 Router.
address private uniswapV2Router;
/// @dev Initial minimum liquidity setting for native tokens.
uint256 private minimumWETHLiquidity;
/// @dev Thrown when setting a configuration address to the zero address.
error ZeroAddress();
/**
* @notice Constructs the g8keepFactoryConfiguration contract with an initial owner.
*
* @param _configurationOwner Address that owns the configuration contract.
*/
constructor(address _configurationOwner) {
_initializeOwner(_configurationOwner);
}
/**
* @notice Admin function to set the initial configuration parameters for the g8keepFactory.
*
* @param _g8keepFactoryAdmin Address that will be the initial owner of the g8keepFactory.
* @param _g8keepFeeWallet Address that will be the initial recipient of initial liquidity fees.
* @param _uniswapV2Router Address of the Uniswap V2 Router.
* @param _minimumWETHLiquidity Initial minimum liquidity setting for native tokens.
*/
function setFactoryConfiguration(
address _g8keepFactoryAdmin,
address _g8keepFeeWallet,
address _uniswapV2Router,
uint256 _minimumWETHLiquidity
) external onlyOwner {
if (
_g8keepFactoryAdmin == address(0)
|| _g8keepFeeWallet == address(0)
|| _uniswapV2Router == address(0)
) {
revert ZeroAddress();
}
g8keepFactoryAdmin = _g8keepFactoryAdmin;
g8keepFeeWallet = _g8keepFeeWallet;
uniswapV2Router = _uniswapV2Router;
minimumWETHLiquidity = _minimumWETHLiquidity;
}
/**
* @notice Returns the factory configuration settings in one call for gas efficiency.
*
* @dev WETH address is retrieved from the Uniswap V2 Router for compliance with ETH<->Token
* @dev swaps with native wrapping and unwrapping.
*
* @return _g8keepFactoryAdmin Address that will be the initial owner of the g8keepFactory.
* @return _g8keepFeeWallet Address that will be the initial recipient of initial liquidity fees.
* @return _uniswapV2Router Address of the Uniswap V2 Router.
* @return _WETH Address of the WETH contract from Uniswap V2 Router.
* @return _minimumWETHLiquidity Initial minimum liquidity setting for native tokens.
*/
function getFactoryConfiguration() external view returns(
address _g8keepFactoryAdmin,
address _g8keepFeeWallet,
address _uniswapV2Router,
address _WETH,
uint256 _minimumWETHLiquidity
) {
_g8keepFactoryAdmin = g8keepFactoryAdmin;
_g8keepFeeWallet = g8keepFeeWallet;
_uniswapV2Router = uniswapV2Router;
_WETH = IUniswapV2Router02(_uniswapV2Router).WETH();
_minimumWETHLiquidity = minimumWETHLiquidity;
}
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.20;
import {Ownable} from "solady/auth/Ownable.sol";
import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";
/**
* @title g8keepPenaltyReceiver
* @notice Receives tokens from g8keep snipe protection penalties for [redacted].
*/
contract g8keepPenaltyReceiver is Ownable {
/// @dev Mapping of addresses that are allowed to transfer tokens from the penalty receiver.
mapping(address => uint256) public allowedTransferers;
/// @dev Guardrail to prevent a newly added transferer from transfering tokens immediately.
uint256 private constant ALLOWED_TIME_DELAY = 1 days;
/// @dev Thrown when allowing or removing a transferer and no change would be made.
error NoUpdate();
/// @dev Thrown when a transfer request is made by an address that is not allowed.
error NotAllowed();
/// @dev Thrown when withdrawing a token from the contract and the withdrawal fails.
error WithdrawalFailed();
/// @dev Thrown when attempting to set the zero address as an allowed transferer.
error ZeroAddress();
/// @dev Emitted when the penalty receiver owner updates an allowed transferer.
event AllowedTransfererUpdated(address indexed transferer, bool allowed, uint256 allowedTime);
modifier onlyAllowedTransferer() {
uint256 allowedTime = allowedTransferers[msg.sender];
if (allowedTime == 0 || allowedTime > block.timestamp) {
revert NotAllowed();
}
_;
}
/**
* @dev Constructs the g8keepPenaltyReceiver contract.
*
* @param _defaultOwner The address of the default owner of the contract.
*/
constructor(address _defaultOwner) {
_initializeOwner(_defaultOwner);
}
/////////// OWNER FUNCTIONS ///////////
/**
* @notice Admin function to configure a paired token.
*
* @param transferer Address of the address to set allowed.
* @param allowed True if the address is allowed to transfer tokens, false if not.
*/
function setAllowedTransferer(
address transferer,
bool allowed
) external onlyOwner {
if (transferer == address(0)) {
revert ZeroAddress();
}
uint256 allowedTime = allowedTransferers[transferer];
if (allowed) {
if (allowedTime > 0) {
revert NoUpdate();
}
unchecked {
allowedTime = block.timestamp + ALLOWED_TIME_DELAY;
}
} else {
if (allowedTime == 0) {
revert NoUpdate();
}
allowedTime = 0;
}
allowedTransferers[transferer] = allowedTime;
emit AllowedTransfererUpdated(transferer, allowed, allowedTime);
}
/////////// TRANSFERER FUNCTIONS ///////////
/**
* @notice Transfers an `amount` of `tokenAddress` from the contract to `to`.
*
* @dev Throws when called by an address that is not an allowed transferer.
*
* @param tokenAddress Address of the token to transfer.
* @param to Address to transfer the token to.
* @param amount Amount of the token to transfer.
*/
function transferToken(address tokenAddress, address to, uint256 amount) external onlyAllowedTransferer {
SafeTransferLib.safeTransfer(tokenAddress, to, amount);
}
}
// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.20;
import {Ownable} from "solady/auth/Ownable.sol";
import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import {IUniswapV2Router02} from "./interfaces/IUniswapV2Router02.sol";
import {IUniswapV2Factory} from "./interfaces/IUniswapV2Factory.sol";
import {IUniswapV2Pair} from "./interfaces/IUniswapV2Pair.sol";
import {IG8keepDeployerVesting} from "./interfaces/IG8keepDeployerVesting.sol";
import {IG8keepFactory} from "./interfaces/IG8keepFactory.sol";
import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";
/**
* @title g8keepToken
* @notice An ERC20 implementation for the g8keep platform.
* @notice Key features of g8keepToken include:
* @notice - Fee collection for deployers and guardrails for traders.
* @notice - Fees may only be lowered by the deployer, never raised.
* @notice - Automated snipe protection with a linear release of tokens
* @notice without snipe penalties and exponential increase of penalty
* @notice based on the purchase amount.
*/
contract g8keepToken is Ownable {
/// @dev Name of the token.
string public name;
/// @dev Symbol of the token.
string public symbol;
/// @dev Token decimals.
uint8 public constant decimals = 18;
/// @dev Total supply of the token.
uint256 public immutable totalSupply;
/// @dev g8keep factory address to deposit g8keep fees.
address private immutable G8KEEP;
/// @dev Address that will receive penalty taxes.
address private immutable PENALTY_TAX_RECEIVER;
/// @dev Address of the Uniswap V2 pair.
address public immutable UNISWAP_V2_PAIR;
/// @dev Address of the paired token.
address public immutable PAIRED_TOKEN;
/// @dev Constant value for BPS.
uint16 private constant BPS = 10_000;
/// @dev Constant value for the maximum deployer reserve of total supply in BPS.
uint16 private constant MAX_DEPLOYER_RESERVE = 300;
/// @dev Timestamp of the token deployment - used for calculating snipe protection.
uint40 private immutable GENESIS_TIME;
/// @dev The base penalty exponent for snipe protection.
uint8 private constant SNIPE_PENALTY_BASE_EXPONENT = 2;
/// @dev Amount of time in seconds that snipe protection is enabled for.
uint40 public immutable SNIPE_PROTECTION_SECONDS;
/// @dev Timestamp of when snipe protection for the token ends.
uint40 public immutable SNIPE_PROTECTION_END;
/// @dev Starting exponent for heavy snipe penalties.
uint8 public immutable SNIPE_PROTECTION_HEAVY_EXPONENT_START;
/// @dev Amount of time in seconds that heavy snipe penalties are enabled for.
uint40 public immutable SNIPE_PROTECTION_HEAVY_PENALTY_SECONDS;
/// @dev Fee in BPS assessed by g8keep for trades.
uint16 public immutable G8KEEP_FEE;
/// @dev Fee in BPS assessed by the deployer for buys.
uint16 public buyFee;
/// @dev Fee in BPS assessed by the deployer for sells.
uint16 public sellFee;
/// @dev Address that will receive deployer trading fees.
address public treasuryWallet;
/// @dev Mapping of token balances for token owners.
mapping(address => uint256) private _balances;
/// @dev Mapping of spender approvals.
mapping(address => mapping(address => uint256)) private _allowances;
/// @dev Baseline balance of the LP - used for calculating snipe protection.
uint256 private lpBalanceBaseline;
/// @dev Amount of LP token total supply last recorded - used for calculating `thirdPartyLPAmount`.
uint256 private lastLPTotalSupply;
/// @dev Amount of token added to the LP by third parties.
uint112 private thirdPartyLPAmount;
/// @dev Cached value of the LP token reserve - used for calculating `thirdPartyLPAmount`.
uint112 private cachedLPReserve1;
/// @dev Thrown when attempting to burn an LP position during snipe protection.
error CannotBurnLiquidityDuringSnipeProtection();
/// @dev Thrown when deployer attempts to set fees higher than the current settings.
error CannotIncreaseFees();
/// @dev Thrown when attempting to transfer tokens from and to the Uniswap V2 Pair.
error CannotTransferFromAndToUniswapV2Pair();
/// @dev Thrown when the transfer amount on a buy exceeds the value the Uniswap V2 Router calculates.
error ExcessiveAmountOut();
/// @dev Thrown when the spender does not have a sufficient allowance for the transfer being made.
error InsufficientAllowance();
/// @dev Thrown when a transfer amount exceeds the sender's balance.
error InsufficientBalance();
/// @dev Thrown during snipe protection when purchasing tokens and the paired token balance decreases.
error InsufficientPoolInput();
/// @dev Thrown during deployment if the heavy snipe exponent exceeds type(uint8).max or is less than the base snipe exponent.
error InvalidHeavySnipeParameters();
/// @dev Thrown when calculating maximum amount out and the reserve balances are zero.
error InvalidReserves();
/// @dev Thrown during deployment if the token address is not greater than the paired token address.
error InvalidTokenAddress();
/// @dev Thrown during deployment if the deployer requests a deployer reserve greater than the maximum setting.
error ReserveExceedsMax();
/// @dev Thrown during deployment if the snipe protection time will overflow a uint40 value.
error SnipeProtectionTimeOverflow();
/// @dev Thrown when attempting to set the treasury wallet address to the zero address or the Uniswap V2 pair.
error InvalidTreasuryAddress();
/// @dev Thrown during deployment if the supply exceeds the maximum allowed by Uniswap V2.
error SupplyExceedsMax();
/// @dev Thrown when setting an approval or attempting to transfer tokens to the zero address.
error ZeroAddress();
/// @dev Emitted when an approval is updated.
event Approval(address indexed owner, address indexed spender, uint256 value);
/// @dev Emitted when deployer fees are updated.
event DeployerFeesUpdated(uint16 buyFee, uint16 sellFee);
/// @dev Emitted when a token balance is transfered.
event Transfer(address indexed from, address indexed to, uint256 value);
/// @dev Emitted when the treasury wallet is updated.
event TreasuryWalletUpdated(address treasuryWallet);
/**
* @notice Constructs the g8keep token.
*
* @param _deployer Address of the deployer of the token.
* @param _name Name of the token being deployed.
* @param _symbol Symbol of the token being deployed.
* @param _totalSupply Total supply to be minted during deployment.
* @param _treasuryWallet Address that will receive deployer buy and sell fees.
* @param _penaltyTaxReceiver Address that will receive penalty taxes.
* @param _buyFee Buy fee in BPS.
* @param _sellFee Sell fee in BPS.
* @param _g8keepFee Fee assessed by g8keep for trades in BPS.
* @param _uniswapV2Router Address of the Uniswap V2 Router.
* @param _pairedToken Address of the token to pair with in the LP.
* @param _deployReserve Amount of total supply in BPS to allocate to deployer.
* @param _deployVestTime Time in seconds to vest the deployer's allocation.
* @param _snipeProtectionSeconds Amount of time in seconds that snipe protection will be enabled.
* @param _heavySnipeSeconds Amount of time in seconds that heavy snipe penalty is enabled.
* @param _heavySnipeExponent Starting exponent for heavy snipe penalty.
*/
constructor(
address _deployer,
string memory _name,
string memory _symbol,
uint256 _totalSupply,
address _treasuryWallet,
address _penaltyTaxReceiver,
uint16 _buyFee,
uint16 _sellFee,
uint16 _g8keepFee,
address _uniswapV2Router,
address _pairedToken,
uint256 _deployReserve,
uint256 _deployVestTime,
uint256 _snipeProtectionSeconds,
uint256 _heavySnipeSeconds,
uint256 _heavySnipeExponent
) {
if (_deployReserve > MAX_DEPLOYER_RESERVE) revert ReserveExceedsMax();
if (_snipeProtectionSeconds + block.timestamp > type(uint40).max || _heavySnipeSeconds > _snipeProtectionSeconds) revert SnipeProtectionTimeOverflow();
if (_totalSupply > type(uint112).max) revert SupplyExceedsMax();
if (_heavySnipeExponent > type(uint8).max || _heavySnipeExponent < SNIPE_PENALTY_BASE_EXPONENT) revert InvalidHeavySnipeParameters();
_initializeOwner(_deployer);
G8KEEP = msg.sender;
PENALTY_TAX_RECEIVER = _penaltyTaxReceiver;
GENESIS_TIME = uint40(block.timestamp);
SNIPE_PROTECTION_SECONDS = uint40(_snipeProtectionSeconds);
SNIPE_PROTECTION_END = uint40(_snipeProtectionSeconds + block.timestamp);
G8KEEP_FEE = _g8keepFee;
PAIRED_TOKEN = _pairedToken;
SNIPE_PROTECTION_HEAVY_PENALTY_SECONDS = uint40(_heavySnipeSeconds);
SNIPE_PROTECTION_HEAVY_EXPONENT_START = uint8(_heavySnipeExponent - SNIPE_PENALTY_BASE_EXPONENT);
buyFee = _buyFee;
sellFee = _sellFee;
//Revert if deployment address is less than the paired token address
//Token must be "token1" in the v2 pool so that snipe protection can
//evaluate the paired token balance of the pool while transferring
//this token to the buyer.
if (uint160(address(this)) < uint160(_pairedToken)) {
revert InvalidTokenAddress();
}
name = _name;
symbol = _symbol;
totalSupply = _totalSupply;
treasuryWallet = _treasuryWallet;
IUniswapV2Router02 uniswapV2Router = IUniswapV2Router02(_uniswapV2Router);
address uniswapV2Pair;
try
IUniswapV2Factory(uniswapV2Router.factory()).createPair(_pairedToken, address(this))
returns (address _uniswapV2Pair) {
uniswapV2Pair = _uniswapV2Pair;
} catch {
uniswapV2Pair = IUniswapV2Factory(uniswapV2Router.factory()).getPair(_pairedToken, address(this));
}
UNISWAP_V2_PAIR = uniswapV2Pair;
if (_treasuryWallet == uniswapV2Pair) {
revert InvalidTreasuryAddress();
}
uint256 tokensToDeployer = _totalSupply * _deployReserve / BPS;
uint256 tokensToLP = _totalSupply - tokensToDeployer;
lpBalanceBaseline = tokensToLP;
// Transfer tokens for LP to factory, set allowance to router
_balances[msg.sender] = tokensToLP;
emit Transfer(address(0), msg.sender, tokensToLP);
_allowances[msg.sender][_uniswapV2Router] = type(uint256).max;
emit Approval(msg.sender, _uniswapV2Router, type(uint256).max);
if (tokensToDeployer > 0) {
if (_deployVestTime == 0) {
// Transfer deployer tokens to deployer
_balances[_deployer] = tokensToDeployer;
emit Transfer(address(0), _deployer, tokensToDeployer);
} else {
address g8keepVester = IG8keepFactory(msg.sender).g8keepTokenVesting();
_balances[g8keepVester] = tokensToDeployer;
emit Transfer(address(0), g8keepVester, tokensToDeployer);
IG8keepDeployerVesting(g8keepVester).deploymentVest(_deployer, tokensToDeployer, _deployVestTime);
}
}
}
/////////// EXTERNAL FUNCTIONS ///////////
/**
* @notice Calculates the amount of tokens that may be purchased without a snipe
* @notice protection penalty.
*
* @return _amount The amount of tokens that may be purchased without a snipe penalty.
*/
function maxSnipeProtectionBuyWithoutPenalty() external view returns (uint256 _amount) {
_amount = _balances[UNISWAP_V2_PAIR];
if (block.timestamp < SNIPE_PROTECTION_END) {
// Adjust balance for amounts added to the LP by third parties
(,,uint256 cachedThirdPartyLPAmount) = _checkLPForIncreaseView();
if (cachedThirdPartyLPAmount > 0) {
if (cachedThirdPartyLPAmount > _amount) {
_amount = 0;
} else {
unchecked {
_amount = _amount - cachedThirdPartyLPAmount;
}
}
}
uint256 elapsedSeconds = block.timestamp - GENESIS_TIME;
uint256 cachedLPBalanceBaseline = lpBalanceBaseline;
uint256 expectedBalance1 =
cachedLPBalanceBaseline - cachedLPBalanceBaseline * elapsedSeconds / SNIPE_PROTECTION_SECONDS;
if (_amount < expectedBalance1) {
_amount = 0;
} else {
_amount = _amount - expectedBalance1;
}
}
}
/**
* @notice Calculates the expected output amounts to buyer, penalty and fees for
* @notice the `amount0In`, accounts for snipe protection penalties and current fee settings.
*
* @param amount0In The amount of paired token that will be input to the pool.
*
* @return amount1Out The amount the buyer will receive.
* @return penaltyAmount1Out The amount that will be sent to the penalty receiver.
* @return feesAmount1Out The total fees to g8keep and deployer treasury.
*/
function expectedAmount1Out(uint256 amount0In) external view returns (uint256 amount1Out, uint256 penaltyAmount1Out, uint256 feesAmount1Out) {
(uint256 reserve0, uint256 reserve1, uint256 cachedThirdPartyLPAmount) = _checkLPForIncreaseView();
amount1Out = _getAmountOut(amount0In, reserve0, reserve1);
if (block.timestamp < SNIPE_PROTECTION_END) {
(amount1Out, penaltyAmount1Out) = _adjustAmountOut(amount1Out, cachedThirdPartyLPAmount, lpBalanceBaseline);
}
unchecked {
uint256 deployerFee = buyFee;
if (deployerFee > 0) {
feesAmount1Out += (amount1Out * deployerFee) / BPS;
}
if (G8KEEP_FEE > 0) {
feesAmount1Out += (amount1Out * G8KEEP_FEE) / BPS;
}
amount1Out = amount1Out - feesAmount1Out;
}
}
/**
* @notice Returns the token balance of `account`.
*
* @param account Address to retrieve the balance of.
*
* @return _balance The token balance of `account`.
*/
function balanceOf(address account) public view returns (uint256 _balance) {
_balance = _balances[account];
}
/**
* @notice Returns the amount `owner` has authorized `spender` to transfer.
*
* @dev An allowance of `type(uint256).max` is considered an unlimited allowance.
*
* @param owner The owner of the tokens `spender` is allowed to transfer.
* @param spender The account authorized by `owner` to spend tokens on behalf of.
*
* @return _allowance The amount of tokens of `owner` that `spender` is allowed to transfer.
*/
function allowance(address owner, address spender) public view returns (uint256 _allowance) {
_allowance = _allowances[owner][spender];
}
/**
* @notice Sets the allowed amount that `spender` may transfer on behalf of the caller.
*
* @dev An `amount` of `type(uint256).max` is considered an unlimited allowance.
*
* @param spender Address of the account that the caller is authorizing.
* @param amount Amount of tokens the caller is authorizing `spender` to transfer.
*/
function approve(address spender, uint256 amount) external returns (bool) {
_approve(msg.sender, spender, amount);
return true;
}
/**
* @notice Transfers `amount` of tokens from the caller to `recipient`.
*
* @param recipient Address that will receive the tokens from the caller.
* @param amount Amount of tokens to transfer to `recipient`.
*/
function transfer(address recipient, uint256 amount) external returns (bool) {
_transfer(msg.sender, recipient, amount);
return true;
}
/**
* @notice Transfers `amount` of tokens from `sender` to `recipient` if the
* @notice caller has a sufficient allowance set by `sender`.
*
* @param sender Account to send the tokens from.
* @param recipient Address that will receive the tokens from `sender`.
* @param amount Amount of tokens to transfer to `recipient` from `sender`.
*/
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool) {
uint256 currentAllowance = _allowances[sender][msg.sender];
if (currentAllowance != type(uint256).max) {
if (currentAllowance < amount) revert InsufficientAllowance();
unchecked {
_approve(sender, msg.sender, currentAllowance - amount);
}
}
_transfer(sender, recipient, amount);
return true;
}
/////////// OWNER FUNCTIONS ///////////
/**
* @notice Admin function for the token owner to set a new treasury wallet to receive fees.
*
* @dev The address may not be set to the zero address or the Uniswap V2 Pair.
*
* @param newAddress Address to set as the treasury wallet.
*/
function updateTreasuryWallet(address newAddress) external onlyOwner {
if (newAddress == address(0) || newAddress == UNISWAP_V2_PAIR) {
revert InvalidTreasuryAddress();
}
treasuryWallet = newAddress;
emit TreasuryWalletUpdated(newAddress);
}
/**
* @notice Admin function for the token owner to set new buy and sell fees.
*
* @dev The buy and sell fees may not be set higher than the current fees.
*
* @param _buyFee Fee in BPS to assess on token buys.
* @param _sellFee Fee in BPS to assess on token sells.
*/
function updateFees(uint16 _buyFee, uint16 _sellFee) external onlyOwner {
uint16 _oldBuyFee = buyFee;
uint16 _oldSellfee = sellFee;
if (_oldBuyFee < _buyFee || _oldSellfee < _sellFee) {
revert CannotIncreaseFees();
}
buyFee = _buyFee;
sellFee = _sellFee;
emit DeployerFeesUpdated(_buyFee, _sellFee);
}
/**
* @notice Admin function for the token owner to recover tokens mistakenly sent
* @notice to the token contract.
*
* @dev Will withdraw the entire balance of the token.
*
* @param tokenAddress Address of the token to withdraw.
* @param to Address to withdraw the token to.
*/
function withdrawToken(address tokenAddress, address to) external onlyOwner {
uint256 _contractBalance = IERC20(tokenAddress).balanceOf(address(this));
SafeTransferLib.safeTransfer(tokenAddress, to, _contractBalance);
}
/**
* @notice Admin function for the token owner to recover tokens mistakenly sent
* @notice to the token contract.
*
* @dev Will withdraw the specified `amount` of token.
*
* @param tokenAddress Address of the token to withdraw.
* @param to Address to withdraw the token to.
* @param amount Amount of the token to withdraw.
*/
function withdrawToken(address tokenAddress, address to, uint256 amount) external onlyOwner {
SafeTransferLib.safeTransfer(tokenAddress, to, amount);
}
/////////// INTERNAL FUNCTIONS ///////////
/**
* @dev Internal function to handle approvals.
* @dev Updates the `_allowances` mapping and emits an `Approval` event.
*
* @param owner Account that owns the tokens being authorized. This will always be the caller.
* @param spender Address of the account that the caller is authorizing.
* @param amount Amount of tokens the caller is authorizing `spender` to transfer.
*/
function _approve(address owner, address spender, uint256 amount) private {
if (owner == address(0)) revert ZeroAddress();
if (spender == address(0)) revert ZeroAddress();
_allowances[owner][spender] = amount;
emit Approval(owner, spender, amount);
}
/**
* @dev Internal function to handle transfers.
* @dev Adjusts `amount` based on snipe protection calculations while enabled.
* @dev Applies fees when the transfer is going to or coming from the Uniswap V2 Pair.
* @dev Updates the `_balances` mapping for `from` and `to` and emits a `Transfer` event.
*
* @param from Address of the account to transfer tokens from.
* @param to Address of the account to transfer tokens to.
* @param amount Amount of tokens to transfer from `from` to `to`.
*/
function _transfer(address from, address to, uint256 amount) private {
if (from == address(0)) revert ZeroAddress();
if (to == address(0)) revert ZeroAddress();
amount = _applySnipeProtection(from, to, amount);
uint256 senderBalance = _balances[from];
if (senderBalance < amount) revert InsufficientBalance();
unchecked {
_balances[from] = senderBalance - amount;
}
uint256 toAmount = amount;
if (from != G8KEEP) {
if (to == UNISWAP_V2_PAIR) {
toAmount = _applyFees(from, amount, sellFee);
} else if (from == UNISWAP_V2_PAIR) {
toAmount = _applyFees(from, amount, buyFee);
}
}
unchecked {
_balances[to] += toAmount;
emit Transfer(from, to, toAmount);
}
}
/**
* @dev Internal function to apply buy and sell fees.
* @dev Updates the `_balances` mapping for `_treasuryWallet` and `G8KEEP`
* @dev and emits `Transfer` events if their calculated fees are greater than zero.
*
* @param from Address of the account to transfer tokens from.
* @param amount Amount of tokens to transfer from `from`.
* @param deployerFee Fee in BPS being assessed by the deployer.
*
* @return toAmount Amount of tokens to transfer to the recipient after fees are applied.
*/
function _applyFees(
address from,
uint256 amount,
uint256 deployerFee
) internal returns (uint256 toAmount) {
unchecked {
uint256 deployerFees;
if (deployerFee > 0) {
deployerFees = (amount * deployerFee) / BPS;
address _treasuryWallet = treasuryWallet;
_balances[_treasuryWallet] += deployerFees;
emit Transfer(from, _treasuryWallet, deployerFees);
}
uint256 g8keepFees;
if (G8KEEP_FEE > 0) {
g8keepFees = (amount * G8KEEP_FEE) / BPS;
_balances[G8KEEP] += g8keepFees;
emit Transfer(from, G8KEEP, g8keepFees);
}
toAmount = amount - deployerFees - g8keepFees;
}
}
/**
* @dev Internal function to check if snipe protection is enabled and adjust the output
* @dev amount if it is and the `amount` exceeds the maximum buy without a snipe penalty.
*
* @param from Address of the account to transfer tokens from.
* @param to Address of the account to transfer tokens to.
* @param amount1Out Amount of tokens to transfer from `from` to `to`.
*
* @param adjustedAmount1Out Amount of tokens to transfer after applying snipe protection.
*/
function _applySnipeProtection(
address from,
address to,
uint256 amount1Out
) internal returns (uint256 adjustedAmount1Out) {
adjustedAmount1Out = amount1Out;
if (block.timestamp < SNIPE_PROTECTION_END) {
(uint256 reserve0, uint256 reserve1, uint256 cachedThirdPartyLPAmount) = _checkLPForIncrease();
if (from == UNISWAP_V2_PAIR) {
if (to == UNISWAP_V2_PAIR) {
revert CannotTransferFromAndToUniswapV2Pair();
}
// Early return to avoid revert on skims
if (amount1Out == 0) return 0;
// Check paired token balance for increase in pool, must increase during buy transaction
uint256 balance0 = IERC20(PAIRED_TOKEN).balanceOf(UNISWAP_V2_PAIR);
if (balance0 < reserve0) {
revert InsufficientPoolInput();
}
uint256 amount0In;
unchecked {
amount0In = balance0 - reserve0;
}
// Calculate max output for input, transfer request cannot be more than calculated output
uint256 maxAmount1Out = _getAmountOut(amount0In, reserve0, reserve1);
if (amount1Out > maxAmount1Out) {
revert ExcessiveAmountOut();
}
// Adjust outputs for snipe protection, transfer penalty tokens to penalty receiver
uint256 penaltyAmount1Out;
uint256 cachedLPBalanceBaseline = lpBalanceBaseline;
(adjustedAmount1Out, penaltyAmount1Out) = _adjustAmountOut(amount1Out, cachedThirdPartyLPAmount, cachedLPBalanceBaseline);
if (penaltyAmount1Out > 0) {
lpBalanceBaseline = cachedLPBalanceBaseline - penaltyAmount1Out;
_balances[from] -= penaltyAmount1Out; // from is UNISWAP_V2_PAIR, optimizing bytecode by using stack
_balances[PENALTY_TAX_RECEIVER] += penaltyAmount1Out;
emit Transfer(from, PENALTY_TAX_RECEIVER, penaltyAmount1Out);
}
}
}
}
/**
* @dev Internal function used during snipe protection to determine if tokens
* @dev have been added to the LP by a third party so that snipe protection can
* @dev be applied appropriately.
* @dev Returns the LP reserves for use in `_adjustAmountOut` for efficiency.
* @dev Returns the third party LP amount for use in `_adjustAmountOut` for efficiency.
*
* @return reserve0 The reserve of `token0` in the LP when the token transfer is called.
* @return reserve1 The reserve of `token1` in the LP when the token transfer is called.
* @return cachedThirdPartyLPAmount Amount of tokens added to the LP by a third party.
*/
function _checkLPForIncrease() internal returns (uint256 reserve0, uint256 reserve1, uint256 cachedThirdPartyLPAmount) {
(reserve0, reserve1) = _getTokenReserves();
cachedThirdPartyLPAmount = thirdPartyLPAmount;
uint256 _cachedLPReserve1 = cachedLPReserve1;
// Cache new reserve1 for future transactions
cachedLPReserve1 = uint112(reserve1);
uint256 _lpTotalSupply = IERC20(UNISWAP_V2_PAIR).totalSupply();
uint256 _lastLPTotalSupply = lastLPTotalSupply;
if (_lpTotalSupply != _lastLPTotalSupply) {
// Check if LP has been burned
if (_lpTotalSupply < _lastLPTotalSupply) {
revert CannotBurnLiquidityDuringSnipeProtection();
}
lastLPTotalSupply = _lpTotalSupply;
// Skip update on first change to LP total supply
if (_lastLPTotalSupply != 0) {
if (reserve1 > _cachedLPReserve1) {
// Underflow already checked
// Cannot overflow since total supply is limited to type(uint112).max
unchecked {
cachedThirdPartyLPAmount = cachedThirdPartyLPAmount +
(_lpTotalSupply - _lastLPTotalSupply) *
_cachedLPReserve1 / _lastLPTotalSupply;
thirdPartyLPAmount = uint112(cachedThirdPartyLPAmount);
}
}
}
}
}
/**
* @dev Internal function used in view functions to determine if tokens
* @dev have been added to the LP by a third party so that snipe protection
* @dev calculations can be applied appropriately.
* @dev Returns the LP reserves for use in `_adjustAmountOut` for efficiency.
* @dev Returns the third party LP amount for use in `_adjustAmountOut` for efficiency.
*
* @return reserve0 The reserve of `token0` in the LP when the view function is called.
* @return reserve1 The reserve of `token1` in the LP when the view function is called.
* @return cachedThirdPartyLPAmount Amount of tokens added to the LP by a third party.
*/
function _checkLPForIncreaseView() internal view returns (uint256 reserve0, uint256 reserve1, uint256 cachedThirdPartyLPAmount) {
cachedThirdPartyLPAmount = thirdPartyLPAmount;
(reserve0,reserve1) = _getTokenReserves();
uint256 _lpTotalSupply = IERC20(UNISWAP_V2_PAIR).totalSupply();
if (!(_lpTotalSupply == lastLPTotalSupply || lastLPTotalSupply == 0)) {
uint256 _cachedLPReserve1 = cachedLPReserve1;
if (reserve1 > _cachedLPReserve1) {
// Underflow already checked
// Cannot overflow since total supply is limited to type(uint112).max
unchecked {
cachedThirdPartyLPAmount = cachedThirdPartyLPAmount + reserve1 - _cachedLPReserve1;
}
}
}
}
/**
* @dev Internal function to adjust the amount of tokens being purchased from the
* @dev Uniswap V2 Pair during snipe protection when the purchase amount exceeds the
* @dev maximum amount that can be purchased without a penalty.
*
* @dev Tokens are released for purchase without snipe protection penalty linearly during
* @dev the snipe protection window and penalties increase exponentially when exceeding the
* @dev expected balance of the Uniswap V2 Pair.
* @dev For example: A token with 100,000 tokens in the initial LP and snipe protection time
* @dev of 60 minutes will have 25,000 tokens available to purchase without penalty after
* @dev the token has been deployed for 15 minutes for an expected LP balance of 75,000 tokens.
* @dev A buyer that attempts to purchase 62,500 tokens would result in a 37,500 token balance
* @dev in the LP which is 50% of expected balance.
* @dev Their adjustedAmount1Out would equal 62,500 * 0.5^2 = 15,625 tokens (75% penalty).
* @dev Instead of attempting to buy 62,500 tokens, the user should buy the 25,000 tokens
* @dev that are available without snipe protection to avoid penalty.
* @dev Within the regular snipe protection window there is also a heavy penalty window. During
* @dev the heavy penalty window the exponent for applying the penalty starts at a higher value
* @dev and decreases linearly until it reaches the base exponent of 2.
* @dev For example: Using the example from above with a heavy snipe window of 10 minutes and
* @dev a starting penalty exponent of 12. After 6 minutes, there will be 10,000 tokens available
* @dev to purchase, expected balance 90,000 tokens, and the snipe penalty exponent will be 6. A
* @dev buyer that attempts to purchase 55,000 tokens would result in a 45,000 token balance
* @dev in the LP which is 50% of the expected balance.
* @dev Their adjustedAmount1Out would equal 55,000 * 0.5^6 = 859.375 tokens (98.5% penalty).
* @dev Instead of attempting to purchase 55,0000 tokens, the user should buy the 10,000 tokens
* @dev that are available without snipe protection to avoid penalty.
*
* @param amount1Out Amount of the token being purchased through the Uniswap V2 Pair.
* @param cachedThirdPartyLPAmount Amount of tokens added to the LP by a third party.
* @param cachedLPBalanceBaseline The baseline balance of the LP for snipe protection.
*
* @return adjustedAmount1Out The amount out after adjusting for snipe protection.
* @return penaltyAmount1Out The amount out to the penalty receiver.
*/
function _adjustAmountOut(
uint256 amount1Out,
uint256 cachedThirdPartyLPAmount,
uint256 cachedLPBalanceBaseline
) internal view returns (uint256 adjustedAmount1Out, uint256 penaltyAmount1Out) {
// Get balance, adjust by amount out
uint256 balance1 = _balances[UNISWAP_V2_PAIR];
if (amount1Out > balance1) {
revert InsufficientBalance();
}
adjustedAmount1Out = amount1Out;
uint256 adjustedBalance1;
unchecked {
adjustedBalance1 = balance1 - adjustedAmount1Out;
}
// Adjust balance for amounts added to the LP by third parties
if (cachedThirdPartyLPAmount > 0) {
if (cachedThirdPartyLPAmount > adjustedBalance1) {
adjustedBalance1 = 0;
} else {
unchecked {
adjustedBalance1 = adjustedBalance1 - cachedThirdPartyLPAmount;
}
}
}
// Calculate expected balance based on elapsed time
uint256 elapsedSeconds = block.timestamp - GENESIS_TIME;
uint256 expectedBalance1 =
cachedLPBalanceBaseline - cachedLPBalanceBaseline * elapsedSeconds / SNIPE_PROTECTION_SECONDS;
// Apply snipe penalties if adjusted balance is less than expected
if (expectedBalance1 > adjustedBalance1) {
uint256 exponentPenalty = SNIPE_PENALTY_BASE_EXPONENT;
if (elapsedSeconds < SNIPE_PROTECTION_HEAVY_PENALTY_SECONDS) {
unchecked {
exponentPenalty +=
SNIPE_PROTECTION_HEAVY_EXPONENT_START *
(SNIPE_PROTECTION_HEAVY_PENALTY_SECONDS - elapsedSeconds) /
SNIPE_PROTECTION_HEAVY_PENALTY_SECONDS;
}
}
for (uint256 i; i < exponentPenalty; ++i) {
adjustedAmount1Out =
adjustedAmount1Out * adjustedBalance1 / expectedBalance1;
}
unchecked {
penaltyAmount1Out = amount1Out - adjustedAmount1Out;
}
}
}
/**
* @dev Internal function to get the token reserve balances from the Uniswap V2 Pair.
*
* @return reserve0 The reserve of `token0` in the LP.
* @return reserve1 The reserve of `token1` in the LP.
*/
function _getTokenReserves() internal view returns (uint256 reserve0, uint256 reserve1) {
(reserve0,reserve1,) = IUniswapV2Pair(UNISWAP_V2_PAIR).getReserves();
}
/**
* @dev Internal function to calculate the amount of tokens expected to be received
* @dev from a purchase with `amountIn`. This is the same formula used by the
* @dev Uniswap V2 Router when calculating output amount.
*
* @param amountIn The amount of tokens input to the pair.
* @param reserveIn The LP reserve of the input token.
* @param reserveOut The LP reserve of the token being purchased.
*
* @return amountOut The calculated amount of output tokens.
*/
function _getAmountOut(
uint256 amountIn,
uint256 reserveIn,
uint256 reserveOut
) internal pure returns (uint256 amountOut) {
if (reserveIn == 0 || reserveOut == 0) revert InvalidReserves();
uint256 amountInWithFee = amountIn * 997;
uint256 numerator = amountInWithFee * reserveOut;
uint256 denominator = reserveIn * 1000 + amountInWithFee;
amountOut = numerator / denominator;
}
}
{
"compilationTarget": {
"src/g8keepToken.sol": "g8keepToken"
},
"evmVersion": "shanghai",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 999999
},
"remappings": [
":@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
":ds-test/=lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/src/",
":erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/",
":forge-std/=lib/forge-std/src/",
":openzeppelin-contracts/=lib/openzeppelin-contracts/",
":solady/=lib/solady/src/",
":solmate/=lib/solmate/src/"
],
"viaIR": true
}
[{"inputs":[{"internalType":"address","name":"_deployer","type":"address"},{"internalType":"string","name":"_name","type":"string"},{"internalType":"string","name":"_symbol","type":"string"},{"internalType":"uint256","name":"_totalSupply","type":"uint256"},{"internalType":"address","name":"_treasuryWallet","type":"address"},{"internalType":"address","name":"_penaltyTaxReceiver","type":"address"},{"internalType":"uint16","name":"_buyFee","type":"uint16"},{"internalType":"uint16","name":"_sellFee","type":"uint16"},{"internalType":"uint16","name":"_g8keepFee","type":"uint16"},{"internalType":"address","name":"_uniswapV2Router","type":"address"},{"internalType":"address","name":"_pairedToken","type":"address"},{"internalType":"uint256","name":"_deployReserve","type":"uint256"},{"internalType":"uint256","name":"_deployVestTime","type":"uint256"},{"internalType":"uint256","name":"_snipeProtectionSeconds","type":"uint256"},{"internalType":"uint256","name":"_heavySnipeSeconds","type":"uint256"},{"internalType":"uint256","name":"_heavySnipeExponent","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AlreadyInitialized","type":"error"},{"inputs":[],"name":"CannotBurnLiquidityDuringSnipeProtection","type":"error"},{"inputs":[],"name":"CannotIncreaseFees","type":"error"},{"inputs":[],"name":"CannotTransferFromAndToUniswapV2Pair","type":"error"},{"inputs":[],"name":"ExcessiveAmountOut","type":"error"},{"inputs":[],"name":"InsufficientAllowance","type":"error"},{"inputs":[],"name":"InsufficientBalance","type":"error"},{"inputs":[],"name":"InsufficientPoolInput","type":"error"},{"inputs":[],"name":"InvalidHeavySnipeParameters","type":"error"},{"inputs":[],"name":"InvalidReserves","type":"error"},{"inputs":[],"name":"InvalidTokenAddress","type":"error"},{"inputs":[],"name":"InvalidTreasuryAddress","type":"error"},{"inputs":[],"name":"NewOwnerIsZeroAddress","type":"error"},{"inputs":[],"name":"NoHandoverRequest","type":"error"},{"inputs":[],"name":"ReserveExceedsMax","type":"error"},{"inputs":[],"name":"SnipeProtectionTimeOverflow","type":"error"},{"inputs":[],"name":"SupplyExceedsMax","type":"error"},{"inputs":[],"name":"Unauthorized","type":"error"},{"inputs":[],"name":"ZeroAddress","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint16","name":"buyFee","type":"uint16"},{"indexed":false,"internalType":"uint16","name":"sellFee","type":"uint16"}],"name":"DeployerFeesUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"pendingOwner","type":"address"}],"name":"OwnershipHandoverCanceled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"pendingOwner","type":"address"}],"name":"OwnershipHandoverRequested","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"oldOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"treasuryWallet","type":"address"}],"name":"TreasuryWalletUpdated","type":"event"},{"inputs":[],"name":"G8KEEP_FEE","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PAIRED_TOKEN","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SNIPE_PROTECTION_END","outputs":[{"internalType":"uint40","name":"","type":"uint40"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SNIPE_PROTECTION_HEAVY_EXPONENT_START","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SNIPE_PROTECTION_HEAVY_PENALTY_SECONDS","outputs":[{"internalType":"uint40","name":"","type":"uint40"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SNIPE_PROTECTION_SECONDS","outputs":[{"internalType":"uint40","name":"","type":"uint40"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"UNISWAP_V2_PAIR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"_allowance","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"_balance","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"buyFee","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"cancelOwnershipHandover","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"pendingOwner","type":"address"}],"name":"completeOwnershipHandover","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount0In","type":"uint256"}],"name":"expectedAmount1Out","outputs":[{"internalType":"uint256","name":"amount1Out","type":"uint256"},{"internalType":"uint256","name":"penaltyAmount1Out","type":"uint256"},{"internalType":"uint256","name":"feesAmount1Out","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxSnipeProtectionBuyWithoutPenalty","outputs":[{"internalType":"uint256","name":"_amount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"result","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"pendingOwner","type":"address"}],"name":"ownershipHandoverExpiresAt","outputs":[{"internalType":"uint256","name":"result","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"requestOwnershipHandover","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"sellFee","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"treasuryWallet","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint16","name":"_buyFee","type":"uint16"},{"internalType":"uint16","name":"_sellFee","type":"uint16"}],"name":"updateFees","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newAddress","type":"address"}],"name":"updateTreasuryWallet","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdrawToken","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"address","name":"to","type":"address"}],"name":"withdrawToken","outputs":[],"stateMutability":"nonpayable","type":"function"}]