// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.19;
import "solady/src/utils/SafeTransferLib.sol";
import "src/common/interfaces/IERC20.sol";
import "src/common/interfaces/ILiquidityGauge.sol";
import "src/common/interfaces/ILocker.sol";
import "src/common/interfaces/ISdToken.sol";
import "src/common/interfaces/ITokenMinter.sol";
/// @title BaseDepositor
/// @notice Contract that accepts tokens and locks them in the Locker, minting sdToken in return
/// @dev Adapted for veCRV like Locker.
/// @author StakeDAO
/// @custom:contact contact@stakedao.org
abstract contract BaseDepositor {
///////////////////////////////////////////////////////////////
/// --- STATE VARIABLES & CONSTANTS
///////////////////////////////////////////////////////////////
/// @notice Denominator for fixed point math.
uint256 public constant DENOMINATOR = 1e18;
/// @notice Maximum lock duration.
uint256 public immutable MAX_LOCK_DURATION;
/// @notice Address of the token to be locked.
address public immutable token;
/// @notice Address of the locker contract.
address public immutable locker;
/// @notice Address of the sdToken minter contract.
address public minter;
/// @notice Fee percent to users who spend gas to increase lock.
uint256 public lockIncentivePercent = 0.001e18; // 0.1%
/// @notice Incentive accrued in token to users who spend gas to increase lock.
uint256 public incentiveToken;
/// @notice Gauge to deposit sdToken into.
address public gauge;
/// @notice Address of the governance.
address public governance;
/// @notice Address of the future governance contract.
address public futureGovernance;
enum STATE {
ACTIVE,
CANCELED
}
/// @notice The state of the contract.
/**
* @dev The contract uses a minimalistic state machine pattern to manage the lifecycle of locked tokens:
* 1. At construction time, the contract is in the ACTIVE state.
* 2. The contract can be shutdown by the governance at any time, transitioning the contract to the CANCELED state.
* This is a terminal state and cannot be reverted.
*
* Here's the State Machine Diagram:
*
* +--------------+
* | ACTIVE |
* +--------------+
* |
* shutdown
* |
* ↓
* +--------------+
* | CANCELED |
* +--------------+
*
* Transitions:
* - ACTIVE -> CANCELED: via `shutdown()`
*/
STATE public state;
////////////////////////////////////////////////////////////////
/// --- EVENTS & ERRORS
///////////////////////////////////////////////////////////////
/// @notice Throws if caller is not the governance.
error GOVERNANCE();
/// @notice Throws if the deposit amount is zero.
error AMOUNT_ZERO();
/// @notice Throws if the address is zero.
error ADDRESS_ZERO();
/// @notice Throws if the lock incentive is too high.
error LOCK_INCENTIVE_TOO_HIGH();
/// @notice Throws if the contract is not active.
error DEPOSITOR_DISABLED();
/// @notice Event emitted when the gauge is updated
event GaugeUpdated(address newGauge);
/// @notice Event emitted when the lock incentive is updated
event LockIncentiveUpdated(uint256 newLockIncentive);
/// @notice Event emitted when the governance update is proposed
event GovernanceUpdateProposed(address newFutureGovernance);
/// @notice Event emitted when the governance update is accepted
event GovernanceUpdateAccepted(address newGovernance);
/// @notice Event emitted when the state of the contract is updated.
/// @param newState The new state of the contract.
event StateUpdated(STATE newState);
////////////////////////////////////////////////////////////////
/// --- MODIFIERS
///////////////////////////////////////////////////////////////
modifier onlyGovernance() {
if (msg.sender != governance) revert GOVERNANCE();
_;
}
modifier onlyActive() {
if (state != STATE.ACTIVE) revert DEPOSITOR_DISABLED();
_;
}
constructor(address _token, address _locker, address _minter, address _gauge, uint256 _maxLockDuration) {
if (_token == address(0) || _locker == address(0) || _minter == address(0) || _gauge == address(0)) {
revert ADDRESS_ZERO();
}
governance = msg.sender;
token = _token;
gauge = _gauge;
minter = _minter;
locker = _locker;
MAX_LOCK_DURATION = _maxLockDuration;
// set the state of the contract to ACTIVE
_setState(STATE.ACTIVE);
/// Approve sdToken to gauge.
SafeTransferLib.safeApprove(minter, gauge, type(uint256).max);
}
////////////////////////////////////////////////////////////////
/// --- DEPOSIT & LOCK
///////////////////////////////////////////////////////////////
function _createLockFrom(address _from, uint256 _amount) internal virtual {
// Transfer tokens to the locker contract
SafeTransferLib.safeTransferFrom(token, _from, address(locker), _amount);
// Can be called only once.
ILocker(locker).createLock(_amount, block.timestamp + MAX_LOCK_DURATION);
}
/// @notice Initiate a lock in the Locker contract and mint the sdTokens to the caller.
/// @param _amount Amount of tokens to lock.
function createLock(uint256 _amount) external virtual onlyActive {
// Transfer caller's tokens to the locker and lock them
_createLockFrom(msg.sender, _amount);
/// Mint sdToken to msg.sender.
ITokenMinter(minter).mint(msg.sender, _amount);
}
/// @notice Deposit tokens, and receive sdToken or sdTokenGauge in return.
/// @param _amount Amount of tokens to deposit.
/// @param _lock Whether to lock the tokens in the locker contract.
/// @param _stake Whether to stake the sdToken in the gauge.
/// @param _user Address of the user to receive the sdToken.
/// @custom:reverts DEPOSITOR_DISABLED if the contract is not active.
/// @custom:reverts AMOUNT_ZERO if the amount is zero.
/// @custom:reverts ADDRESS_ZERO if the user address is zero.
/// @dev If the lock is true, the tokens are directly sent to the locker and increase the lock amount as veToken.
/// If the lock is false, the tokens are sent to this contract until someone locks them. A small percent of the deposit
/// is used to incentivize users to lock the tokens.
/// If the stake is true, the sdToken is staked in the gauge that distributes rewards. If the stake is false, the sdToken
/// is sent to the user.
function deposit(uint256 _amount, bool _lock, bool _stake, address _user) public onlyActive {
if (_amount == 0) revert AMOUNT_ZERO();
if (_user == address(0)) revert ADDRESS_ZERO();
/// If _lock is true, lock tokens in the locker contract.
if (_lock) {
/// Transfer tokens to the locker contract.
SafeTransferLib.safeTransferFrom(token, msg.sender, locker, _amount);
/// Transfer the balance
uint256 balance = IERC20(token).balanceOf(address(this));
if (balance != 0) {
SafeTransferLib.safeTransfer(token, locker, balance);
}
/// Lock the amount sent + balance of the contract.
_lockToken(balance + _amount);
/// If an incentive is available, add it to the amount.
if (incentiveToken != 0) {
_amount += incentiveToken;
incentiveToken = 0;
}
} else {
/// Transfer tokens to this contract.
SafeTransferLib.safeTransferFrom(token, msg.sender, address(this), _amount);
/// Compute call incentive and add to incentiveToken
uint256 callIncentive = (_amount * lockIncentivePercent) / DENOMINATOR;
/// Subtract call incentive from _amount
_amount -= callIncentive;
/// Add call incentive to incentiveToken
incentiveToken += callIncentive;
}
// Mint sdtoken to the user if the gauge is not set
if (_stake && gauge != address(0)) {
/// Mint sdToken to this contract.
ITokenMinter(minter).mint(address(this), _amount);
/// Deposit sdToken into gauge for _user.
ILiquidityGauge(gauge).deposit(_amount, _user);
} else {
/// Mint sdToken to _user.
ITokenMinter(minter).mint(_user, _amount);
}
}
/// @notice Lock tokens held by the contract
/// @dev The contract must have Token to lock
function lockToken() external onlyActive {
uint256 tokenBalance = IERC20(token).balanceOf(address(this));
if (tokenBalance != 0) {
/// Transfer tokens to the locker contract and lock them.
SafeTransferLib.safeTransfer(token, locker, tokenBalance);
/// Lock the amount sent.
_lockToken(tokenBalance);
}
/// If there is incentive available give it to the user calling lockToken.
if (incentiveToken != 0) {
/// Mint incentiveToken to msg.sender.
ITokenMinter(minter).mint(msg.sender, incentiveToken);
/// Reset incentiveToken.
incentiveToken = 0;
}
}
/// @notice Locks the tokens held by the contract
/// @dev The contract must have tokens to lock
function _lockToken(uint256 _amount) internal virtual {
// If there is Token available in the contract transfer it to the locker
if (_amount != 0) {
/// Increase the lock.
ILocker(locker).increaseLock(_amount, block.timestamp + MAX_LOCK_DURATION);
}
}
////////////////////////////////////////////////////////////////
/// --- GOVERNANCE PARAMETERS
///////////////////////////////////////////////////////////////
/// @notice Transfer the governance to a new address.
/// @param _governance Address of the new governance.
function transferGovernance(address _governance) external onlyGovernance {
emit GovernanceUpdateProposed(futureGovernance = _governance);
}
/// @notice Accept the governance transfer.
function acceptGovernance() external {
if (msg.sender != futureGovernance) revert GOVERNANCE();
emit GovernanceUpdateAccepted(governance = msg.sender);
futureGovernance = address(0);
}
/// @notice Shutdown the contract and transfer the balance of the contract to the given receiver.
/// @param receiver Address who will receive the balance of this contract.
/// @dev This will put the contract in the CANCELED state, preventing any further deposits, or locking of tokens.
// Use `shutdown()` to transfer the remaining balance to the governance address.
/// @custom:reverts ONLY_GOVERNANCE if the caller is not the governance.
function shutdown(address receiver) public onlyGovernance {
_setState(STATE.CANCELED);
// Transfer the remaining balance to the receiver.
SafeTransferLib.safeTransfer(token, receiver, IERC20(token).balanceOf(address(this)));
}
/// @notice Shutdown the contract and transfer the balance of the contract to the governance.
/// @custom:reverts ONLY_GOVERNANCE if the caller is not the governance.
function shutdown() external onlyGovernance {
shutdown(governance);
}
/// @notice Set the new operator for minting sdToken
/// @param _minter operator minter address
function setSdTokenMinterOperator(address _minter) external virtual onlyGovernance {
ISdToken(minter).setOperator(_minter);
}
/// @notice Set the gauge to deposit sdToken
/// @param _gauge gauge address
function setGauge(address _gauge) external virtual onlyGovernance {
/// Set and emit the new gauge.
emit GaugeUpdated(gauge = _gauge);
if (_gauge != address(0)) {
/// Approve sdToken to gauge.
SafeTransferLib.safeApprove(minter, gauge, type(uint256).max);
}
}
/// @notice Set the percentage of the lock incentive
/// @param _lockIncentive Percentage of the lock incentive
function setFees(uint256 _lockIncentive) external onlyGovernance {
if (_lockIncentive > 0.003e18) revert LOCK_INCENTIVE_TOO_HIGH();
emit LockIncentiveUpdated(lockIncentivePercent = _lockIncentive);
}
function _setState(STATE _state) internal {
state = _state;
emit StateUpdated(_state);
}
function name() external view returns (string memory) {
return string(abi.encodePacked(IERC20(token).symbol(), " Depositor"));
}
/// @notice Get the version of the contract
/// Version follows the Semantic Versioning (https://semver.org/)
/// Major version is increased when backward compatibility is broken in this base contract.
/// Minor version is increased when new features are added in this base contract.
/// Patch version is increased when child contracts are updated.
function version() external pure returns (string memory) {
return "4.0.0";
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
interface IERC20 {
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address to, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(address from, address to, uint256 amount) external returns (bool);
function name() external view returns (string memory);
function symbol() external view returns (string memory);
}
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.7;
interface ILiquidityGauge {
struct Reward {
address token;
address distributor;
// solhint-disable-next-line
uint256 period_finish;
uint256 rate;
// solhint-disable-next-line
uint256 last_update;
uint256 integral;
}
// solhint-disable-next-line
function deposit_reward_token(address _rewardToken, uint256 _amount) external;
// solhint-disable-next-line
function claim_rewards_for(address _user, address _recipient) external;
// solhint-disable-next-line
function working_balances(address _address) external view returns (uint256);
// solhint-disable-next-line
function deposit(uint256 _value, address _addr) external;
// solhint-disable-next-line
function reward_tokens(uint256 _i) external view returns (address);
// solhint-disable-next-line
function reward_data(address _tokenReward) external view returns (Reward memory);
function balanceOf(address) external returns (uint256);
// solhint-disable-next-line
function claimable_reward(address _user, address _reward_token) external view returns (uint256);
// solhint-disable-next-line
function claimable_tokens(address _user) external returns (uint256);
// solhint-disable-next-line
function user_checkpoint(address _user) external returns (bool);
// solhint-disable-next-line
function commit_transfer_ownership(address) external;
// solhint-disable-next-line
function claim_rewards() external;
// solhint-disable-next-line
function claim_rewards(address) external;
// solhint-disable-next-line
function claim_rewards(address, address) external;
// solhint-disable-next-line
function add_reward(address, address) external;
// solhint-disable-next-line
function set_claimer(address) external;
function admin() external view returns (address);
function future_admin() external view returns (address);
// solhint-disable-next-line
function set_reward_distributor(address _rewardToken, address _newDistrib) external;
function initialize(
// solhint-disable-next-line
address staking_token,
address admin,
address sdt,
// solhint-disable-next-line
address voting_escrow,
// solhint-disable-next-line
address veBoost_proxy,
address distributor
) external;
function totalSupply() external returns (uint256);
function withdraw(uint256 _value, bool _claimReward) external;
function withdraw(uint256 _value, address _user, bool _claimReward) external;
// solhint-disable-next-line
function accept_transfer_ownership() external;
// solhint-disable-next-line
function claimed_reward(address _addr, address _token) external view returns (uint256);
// solhint-disable-next-line
function set_rewards_receiver(address _receiver) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
interface ILiquidityGaugeV4 {
/// @notice Reward token data structure
struct Reward {
address token;
address distributor;
uint256 period_finish;
uint256 rate;
uint256 last_update;
uint256 integral;
}
/// @notice Emitted when tokens are deposited
event Deposit(address indexed provider, uint256 value);
/// @notice Emitted when tokens are withdrawn
event Withdraw(address indexed provider, uint256 value);
/// @notice Emitted when liquidity limit is updated
event UpdateLiquidityLimit(
address user, uint256 original_balance, uint256 original_supply, uint256 working_balance, uint256 working_supply
);
/// @notice Emitted when admin ownership is committed
event CommitOwnership(address admin);
/// @notice Emitted when admin ownership is applied
event ApplyOwnership(address admin);
/// @notice ERC20 Transfer event
event Transfer(address indexed _from, address indexed _to, uint256 _value);
/// @notice ERC20 Approval event
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
/// @notice Emitted when reward data is updated
event RewardDataUpdate(address indexed _token, uint256 _amount);
/// @notice SDT token address
function SDT() external view returns (address);
/// @notice Voting escrow contract address
function voting_escrow() external view returns (address);
/// @notice VeBoost proxy contract address
function veBoost_proxy() external view returns (address);
/// @notice The staking token address
function staking_token() external view returns (address);
/// @notice The number of decimals of the staking token
function decimal_staking_token() external view returns (uint256);
/// @notice Get user balance
function balanceOf(address user) external view returns (uint256);
/// @notice Total supply of staked tokens
function totalSupply() external view returns (uint256);
/// @notice ERC20 allowance
function allowance(address owner, address spender) external view returns (uint256);
/// @notice Token name
function name() external view returns (string memory);
/// @notice Token symbol
function symbol() external view returns (string memory);
/// @notice Get working balance for a user
function working_balances(address user) external view returns (uint256);
/// @notice Get total working supply
function working_supply() external view returns (uint256);
/// @notice Get the checkpoint of a user
function integrate_checkpoint_of(address user) external view returns (uint256);
/// @notice Number of reward tokens
function reward_count() external view returns (uint256);
/// @notice Array of reward token addresses
function reward_tokens(uint256 index) external view returns (address);
/// @notice Get reward data for a token
function reward_data(address token) external view returns (Reward memory);
/// @notice Get reward receiver for an address
function rewards_receiver(address user) external view returns (address);
/// @notice Current admin address
function admin() external view returns (address);
/// @notice Future admin address
function future_admin() external view returns (address);
/// @notice Claimer address
function claimer() external view returns (address);
/// @notice Whether the contract is initialized
function initialized() external view returns (bool);
/// @notice Get the number of decimals for this token
function decimals() external view returns (uint256);
/// @notice Record a checkpoint for an address
function user_checkpoint(address addr) external returns (bool);
/// @notice Get the number of already-claimed reward tokens for a user
function claimed_reward(address _addr, address _token) external view returns (uint256);
/// @notice Get the number of claimable reward tokens for a user
function claimable_reward(address _user, address _reward_token) external view returns (uint256);
/// @notice Set the default reward receiver for the caller
function set_rewards_receiver(address _receiver) external;
/// @notice Claim available reward tokens
function claim_rewards(address _addr, address _receiver) external;
/// @notice Claim available reward tokens for another address
function claim_rewards_for(address _addr, address _receiver) external;
/// @notice Kick an address for abusing their boost
function kick(address addr) external;
/// @notice Deposit LP tokens
function deposit(uint256 _value, address _addr, bool _claim_rewards) external;
/// @notice Withdraw LP tokens
function withdraw(uint256 _value, bool _claim_rewards) external;
/// @notice Transfer token for a specified address
function transfer(address _to, uint256 _value) external returns (bool);
/// @notice Transfer tokens from one address to another
function transferFrom(address _from, address _to, uint256 _value) external returns (bool);
/// @notice Approve the passed address to transfer tokens
function approve(address _spender, uint256 _value) external returns (bool);
/// @notice Increase the allowance granted to spender
function increaseAllowance(address _spender, uint256 _added_value) external returns (bool);
/// @notice Decrease the allowance granted to spender
function decreaseAllowance(address _spender, uint256 _subtracted_value) external returns (bool);
/// @notice Add a new reward token
function add_reward(address _reward_token, address _distributor) external;
/// @notice Set the reward distributor for a token
function set_reward_distributor(address _reward_token, address _distributor) external;
/// @notice Set the claimer address
function set_claimer(address _claimer) external;
/// @notice Deposit reward tokens
function deposit_reward_token(address _reward_token, uint256 _amount) external;
/// @notice Commit transfer of ownership
function commit_transfer_ownership(address addr) external;
/// @notice Accept transfer of ownership
function accept_transfer_ownership() external;
/// @notice Initialize the contract
function initialize(
address _staking_token,
address _admin,
address _SDT,
address _voting_escrow,
address _veBoost_proxy,
address _distributor
) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
interface ILocker {
/// @notice Throws if caller is not the governance.
error GOVERNANCE();
/// @notice Throws if caller is not the governance or depositor.
error GOVERNANCE_OR_DEPOSITOR();
/// @notice Throws if caller is not the governance or depositor.
error GOVERNANCE_OR_ACCUMULATOR();
function createLock(uint256, uint256) external;
function claimAllRewards(address[] calldata _tokens, address _recipient) external;
function increaseAmount(uint256) external;
function increaseAmount(uint128) external;
function increaseUnlockTime(uint256) external;
function release() external;
function claimRewards(address, address) external;
function claimRewards(address, address, address) external;
function claimFXSRewards(address) external;
function claimFPISRewards(address) external;
function execute(address, uint256, bytes calldata) external returns (bool, bytes memory);
function setGovernance(address) external;
function voteGaugeWeight(address, uint256) external;
function setAngleDepositor(address) external;
function setPendleDepositor(address) external;
function pendleDepositor() external view returns (address);
function setDepositor(address) external;
function setFxsDepositor(address) external;
function setYFIDepositor(address) external;
function setYieldDistributor(address) external;
function setGaugeController(address) external;
function setAccumulator(address _accumulator) external;
function governance() external view returns (address);
function increaseLock(uint256 _value, uint256 _duration) external;
function release(address _recipient) external;
function transferGovernance(address _governance) external;
function acceptGovernance() external;
function setStrategy(address _strategy) external;
function claimRewards(address _recipient, address[] calldata _pools) external;
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.19;
import { PreLaunchBaseDepositor } from "src/common/depositor/PreLaunchBaseDepositor.sol";
import { ILiquidityGaugeV4 } from "src/common/interfaces/ILiquidityGaugeV4.sol";
import { ISdToken } from "src/common/interfaces/ISdToken.sol";
/// @title IPreLaunchLocker
/// @notice Interface for the PreLaunchLocker contract
interface IPreLaunchLocker {
/// @notice The delay after which the locker can be force canceled by anyone
function FORCE_CANCEL_DELAY() external view returns (uint256);
/// @notice The immutable token to lock
function token() external view returns (address);
/// @notice The sdToken address
function sdToken() external view returns (ISdToken);
/// @notice The gauge address
function gauge() external view returns (ILiquidityGaugeV4);
/// @notice The current governance address
function governance() external view returns (address);
/// @notice The timestamp of the locker creation
function timestamp() external view returns (uint96);
/// @notice The depositor contract
function depositor() external view returns (PreLaunchBaseDepositor);
/// @notice Deposit tokens for a given receiver
function deposit(uint256 amount, bool stake, address receiver) external;
/// @notice Deposit tokens in this contract for the caller
function deposit(uint256 amount, bool stake) external;
/// @notice Set the depositor and lock the tokens in the given depositor contract
function lock(address _depositor) external;
/// @notice Withdraw the previously deposited tokens if the launch has been canceled
function withdraw(uint256 amount, bool staked) external;
/// @notice Set the state of the locker as CANCELED
function cancelLocker() external;
/// @notice Force cancel the locker
function forceCancelLocker() external;
/// @notice Transfer the governance to a new address
function transferGovernance(address _governance) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
interface ISdToken {
function balanceOf(address account) external view returns (uint256);
function burn(uint256 amount) external;
function burn(address _to, uint256 _amount) external;
function mint(address _to, uint256 _amount) external;
function operator() external view returns (address);
function burner() external view returns (address);
function setOperator(address _operator) external;
function setBurnerOperator(address _burner) external;
function approve(address _spender, uint256 _amount) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;
interface ITokenMinter {
function mint(address, uint256) external;
function burn(address, uint256) external;
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.19;
import {BaseDepositor} from "./BaseDepositor.sol";
/// @title PreLaunchBaseDepositor
/// @notice A base contract for depositors that are used in pre-launch scenarios.
/// @author StakeDAO
/// @custom:contact contact@stakedao.org
/// @dev This contract is used to lock tokens in the Locker contract without minting the sdTokens.
/// The pre-launch locker is responsible for minting the sdTokens during the pre-launch period.
contract PreLaunchBaseDepositor is BaseDepositor {
/// @notice The address of the pre launch locker.
address public immutable preLaunchLocker;
/// @notice Error thrown when the caller is not the pre-launch locker.
error ONLY_PRE_LAUNCH_LOCKER();
/// @notice Constructor for the PreLaunchBaseDepositor.
/// @param _token The address of the base token.
/// @param _locker The address of the locker contract.
/// @param _minter The address of the minter contract.
/// @param _gauge The address of the gauge contract.
/// @param _maxLockDuration The maximum lock duration.
/// @param _preLaunchLocker The address of the pre-launch locker.
/// @custom:reverts ADDRESS_ZERO if any of the addresses are zero.
constructor(
address _token,
address _locker,
address _minter,
address _gauge,
uint256 _maxLockDuration,
address _preLaunchLocker
) BaseDepositor(_token, _locker, _minter, _gauge, _maxLockDuration) {
if (_preLaunchLocker == address(0)) revert ADDRESS_ZERO();
preLaunchLocker = _preLaunchLocker;
}
/// @notice Initiate a lock in the Locker contract without minting the sdTokens.
/// @param amount The amount of tokens to lock.
/// @dev Can only be called by the pre-launch locker.
/// @custom:reverts ONLY_PRE_LAUNCH_LOCKER if the caller is not the pre-launch locker.
function createLock(uint256 amount) external override {
if (msg.sender != preLaunchLocker) revert ONLY_PRE_LAUNCH_LOCKER();
_createLockFrom(msg.sender, amount);
}
}
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.19;
import { SafeTransferLib } from "solady/src/utils/SafeTransferLib.sol";
import { PreLaunchBaseDepositor } from "src/common/depositor/PreLaunchBaseDepositor.sol";
import { IERC20 } from "src/common/interfaces/IERC20.sol";
import { ILiquidityGaugeV4 } from "src/common/interfaces/ILiquidityGaugeV4.sol";
import { IPreLaunchLocker } from "src/common/interfaces/IPreLaunchLocker.sol";
import { ISdToken } from "src/common/interfaces/ISdToken.sol";
/// @title PreLaunchLocker
/// @dev This contract implements a state machine with three states: IDLE, ACTIVE, and CANCELED
/**
* @notice A contract that enables secure token locking before full protocol deployment. The PreLaunchLocker
* serves as a solution for token locking during a protocol's pre-launch phase.
* Unlike traditional lockers that require the complete protocol stack to be deployed, this contract allows users
* to lock their tokens before the full protocol deployment.
*
* Key Features:
* - Token Deposit: Users can deposit tokens which are securely held in the contract
* - Pre-Launch Locking: Enables token locking mechanism before the full protocol deployment
* - Immediate sdToken Minting: Mints sdTokens to users immediately upon deposit with 1:1 ratio
* - Direct Gauge Integration: Optional direct staking of sdTokens into gauge upon deposit
* - Safety Net: Includes a refund mechanism if the project launch is canceled
*
* State Machine:
* - IDLE: Initial state where:
* • Users can deposit tokens via deposit() with optional immediate gauge staking
* • Governance can activate locker via lock(), transferring the initial tokens to depositor and moving state to ACTIVE
* • Governance can cancel locker via cancelLocker() and modify the state to CANCELED
* • Anyone can force cancel after delay via forceCancelLocker() and modify the state to CANCELED
*
* - ACTIVE: Activated state where:
* • No more deposits, withdrawals or cancellations possible
* • The user is now connected to the protocol via the depositor contract and the associated locker/gauge contracts
*
* - CANCELED: Terminal state where:
* • Supports withdrawal of both staked and unstaked sdTokens
* • No deposits or state changes possible
*
* @dev The contract uses a state machine pattern to manage the lifecycle of locked tokens:
* 1. Users deposit tokens in IDLE state, receiving sdTokens immediately
* 2. Governance can either:
* a) Activate the locker (IDLE -> ACTIVE) connecting it to the protocol via depositor
* b) Cancel the launch (IDLE -> CANCELED) enabling refunds of the initial tokens
* 3. Both ACTIVE and CANCELED are terminal states
*/
/// @custom:contact contact@stakedao.org
contract PreLaunchLocker is IPreLaunchLocker {
///////////////////////////////////////////////////////////////
/// --- STATE VARIABLES & CONSTANTS
///////////////////////////////////////////////////////////////
/// @notice The delay after which the locker can be force canceled by anyone.
uint256 public immutable FORCE_CANCEL_DELAY;
uint256 internal constant DEFAULT_FORCE_CANCEL_DELAY = 3 * 30 days;
/// @notice The immutable token to lock.
address public immutable token;
/// @notice The sdToken address.
ISdToken public immutable sdToken;
/// @notice The gauge address.
ILiquidityGaugeV4 public immutable gauge;
/// @notice The current governance address.
/// @custom:slot 0
address public governance;
/// @notice The timestamp of the locker creation.
/// @custom:slot 0 (packed with `governance` <address>)
uint96 public timestamp;
/// @notice The depositor contract. Cannot be changed once set.
/// @custom:slot 1
PreLaunchBaseDepositor public depositor;
enum STATE {
IDLE,
ACTIVE,
CANCELED
}
/// @notice The state of the locker.
/**
* @dev The contract uses a state machine pattern to manage the lifecycle of locked tokens:
* 1. Users deposit tokens in IDLE state
* 2. Governance can either:
* a) Activate the locker (IDLE -> ACTIVE) connecting it to the protocol
* b) Cancel the launch (IDLE -> CANCELED) enabling refunds
* 3. Both ACTIVE and CANCELED are terminal states
*
* Here's the State Machine Diagram:
*
* +-------------------+
* | IDLE |
* +-------------------+
* | |
* lock | | cancelLocker
* | |
* ↓ ↓
* +---------+ +-----------+
* | ACTIVE | | CANCELED |
* +---------+ +-----------+
*
* Transitions:
* - IDLE -> ACTIVE: via `lock()`
* - IDLE -> CANCELED: via `cancelLocker()`
* - ACTIVE: terminal state
* - CANCELED: terminal state
*/
/// @custom:slot 1 (packed with `depositor`)
STATE public state;
////////////////////////////////////////////////////////////////
/// --- EVENTS & ERRORS
///////////////////////////////////////////////////////////////
/// @notice Event emitted each time the governance address is updated.
/// @param previousGovernanceAddress The previous governance address.
/// @param newGovernanceAddress The new governance address.
event GovernanceUpdated(address previousGovernanceAddress, address newGovernanceAddress);
/// @notice Event emitted each time the state of the locker is updated.
/// @param newState The new state of the locker.
event LockerStateUpdated(STATE newState);
/// @notice Event emitted each time a user stakes their sdTokens.
/// @param caller The address who called the function.
/// @param receiver The address who received the gauge token.
/// @param gauge The gauge that the sdTokens were staked to.
/// @param amount The amount of sdTokens staked.
event TokensStaked(address indexed caller, address indexed receiver, address indexed gauge, uint256 amount);
/// @notice Error thrown when a required parameter is set to the zero address.
error REQUIRED_PARAM();
/// @notice Error thrown when the caller is not the governance address.
error ONLY_GOVERNANCE();
/// @notice Error thrown when the token is not the expected one.
error INVALID_TOKEN();
/// @notice Error thrown when the gauge is not the expected one.
error INVALID_GAUGE();
/// @notice Error thrown when the sdToken is not the expected one.
error INVALID_SD_TOKEN();
/// @notice Error thrown when there is nothing to lock. This can happen if nobody deposited tokens before the locker was locked.
/// In that case, the locker is useless.
error NOTHING_TO_LOCK();
/// @notice Error thrown when the token is not transferred to the locker.
error TOKEN_NOT_TRANSFERRED_TO_LOCKER();
/// @notice Error thrown when the locker is not in the IDLE state when trying to deposit.
error CANNOT_DEPOSIT_ACTIVE_OR_CANCELED_LOCKER();
/// @notice Error thrown when the locker is not in the IDLE state when trying to lock.
error CANNOT_LOCK_ACTIVE_OR_CANCELED_LOCKER();
/// @notice Error thrown when the locker is not in the IDLE state when trying to cancel the launch.
error CANNOT_CANCEL_ACTIVE_OR_CANCELED_LOCKER();
/// @notice Error thrown when the locker is not CANCELED and the user tries to withdraw the initial token.
error CANNOT_WITHDRAW_IDLE_OR_ACTIVE_LOCKER();
/// @notice Error thrown when the locker is not in the IDLE state when trying to force cancel.
error CANNOT_FORCE_CANCEL_ACTIVE_OR_CANCELED_LOCKER();
/// @notice Error thrown when the locker is not enough old to be force canceled.
error CANNOT_FORCE_CANCEL_RECENTLY_CREATED_LOCKER();
////////////////////////////////////////////////////////////////
/// --- MODIFIERS & CONSTRUCTOR
///////////////////////////////////////////////////////////////
/// @notice Modifier to ensure the caller is the governance address.
modifier onlyGovernance() {
if (msg.sender != governance) revert ONLY_GOVERNANCE();
_;
}
/// @notice Sets the token to lock and the governance address.
/// @param _token Address of the token to lock.
/// @param _sdToken Address of the sdToken to mint.
/// @param _gauge Address of the gauge to stake the sdTokens to.
/// @param _customForceCancelDelay The optional custom force cancel delay. If set to 0, the default value will be used (3 months).
/// @custom:reverts REQUIRED_PARAM if one of the given params is zero.
/// @custom:reverts INVALID_SD_TOKEN if the given sdToken is not operated by this contract.
/// @custom:reverts INVALID_GAUGE if the given gauge is not associated with the given sdToken.
constructor(address _token, address _sdToken, address _gauge, uint256 _customForceCancelDelay) {
if (_token == address(0) || _sdToken == address(0) || _gauge == address(0)) revert REQUIRED_PARAM();
// ensure the given gauge contract is associated with the given sdToken
if (ILiquidityGaugeV4(_gauge).staking_token() != _sdToken) revert INVALID_GAUGE();
// set the immutable addresses
token = _token;
sdToken = ISdToken(_sdToken);
gauge = ILiquidityGaugeV4(_gauge);
// start the timer before the locker can be force canceled
timestamp = uint96(block.timestamp);
// set the custom force cancel delay if provided
FORCE_CANCEL_DELAY = _customForceCancelDelay != 0 ? _customForceCancelDelay : DEFAULT_FORCE_CANCEL_DELAY;
// set the state of the contract to idle and emit the state update event
_setState(STATE.IDLE);
// set the governance address and emit the event
_setGovernance(msg.sender);
}
////////////////////////////////////////////////////////////////
/// --- DEPOSIT
///////////////////////////////////////////////////////////////
/// @notice Deposit tokens for a given receiver.
/// @param amount Amount of tokens to deposit.
/// @param stake Whether to stake the tokens in the gauge.
/// @param receiver The address to receive the sdToken or the gauge token.
/// @custom:reverts REQUIRED_PARAM if the given amount is zero.
/// @custom:reverts CANNOT_DEPOSIT_ACTIVE_OR_CANCELED_LOCKER if the locker is already associated with a depositor.
function deposit(uint256 amount, bool stake, address receiver) public {
if (amount == 0 || receiver == address(0)) revert REQUIRED_PARAM();
// deposit aren't allowed once the locker leaves the idle state
if (state != STATE.IDLE) revert CANNOT_DEPOSIT_ACTIVE_OR_CANCELED_LOCKER();
// 1. transfer the tokens from the sender to the contract. Reverts if not enough tokens are approved.
SafeTransferLib.safeTransferFrom(token, msg.sender, address(this), amount);
ISdToken storedSdToken = sdToken;
if (stake == true) {
// 2.a. Either mint the sdTokens to this contract and stake them in the gauge for the caller
ILiquidityGaugeV4 storedGauge = gauge;
storedSdToken.mint(address(this), amount);
storedSdToken.approve(address(storedGauge), amount);
storedGauge.deposit(amount, receiver, false);
emit TokensStaked(msg.sender, receiver, address(storedGauge), amount);
} else {
// 2.b. or mint the sdTokens directly to the caller (ratio 1:1 between token<>sdToken)
sdToken.mint(receiver, amount);
}
}
/// @notice Deposit tokens in this contract for the caller.
/// @param amount Amount of tokens to deposit.
/// @param stake Whether to stake the tokens in the gauge.
/// @custom:reverts REQUIRED_PARAM if the given amount is zero.
function deposit(uint256 amount, bool stake) external {
deposit(amount, stake, msg.sender);
}
////////////////////////////////////////////////////////////////
/// --- LOCK
///////////////////////////////////////////////////////////////
/// @notice Set the depositor and lock the tokens in the given depositor contract.
/// @dev Can only be called once! This function sets the contract as active for ever.
/// @param _depositor The address of the depositor.
/// @custom:reverts ONLY_GOVERNANCE if the caller is not the governance address.
/// @custom:reverts REQUIRED_PARAM if the given address is zero.
/// @custom:reverts CANNOT_LOCK_ACTIVE_OR_CANCELED_LOCKER if the contract is not in the idle state when trying to lock.
/// @custom:reverts INVALID_TOKEN if the given address is not a valid depositor.
/// @custom:reverts INVALID_GAUGE if the given address is not a valid gauge.
/// @custom:reverts INVALID_SD_TOKEN if the given address is not a valid sdToken.
/// @custom:reverts NOTHING_TO_LOCK if there is nothing to lock.
/// @custom:reverts TOKEN_NOT_TRANSFERRED_TO_LOCKER if the locker contract doesn't hold the initial tokens.
function lock(address _depositor) external onlyGovernance {
// ensure the given address is not zero
if (_depositor == address(0)) revert REQUIRED_PARAM();
// ensure the locker is in the idle state
if (state != STATE.IDLE) revert CANNOT_LOCK_ACTIVE_OR_CANCELED_LOCKER();
address storedToken = token;
ISdToken storedSdToken = sdToken;
// ensure the given depositor has the same token as the one stored in this contract
if (PreLaunchBaseDepositor(_depositor).token() != storedToken) revert INVALID_TOKEN();
// ensure the given depositor has the same gauge as the one stored in this contract
if (PreLaunchBaseDepositor(_depositor).gauge() != address(gauge)) revert INVALID_GAUGE();
// ensure the given depositor has the same sdToken as the one stored in this contract
if (PreLaunchBaseDepositor(_depositor).minter() != address(storedSdToken)) revert INVALID_SD_TOKEN();
// 1. set the given depositor
depositor = PreLaunchBaseDepositor(_depositor);
// 2. fetch the current balance of the contract to ensure there is something to lock
uint256 balance = IERC20(storedToken).balanceOf(address(this));
if (balance == 0) revert NOTHING_TO_LOCK();
// 3. give the permission to the depositor to transfer the tokens held by this contract
SafeTransferLib.safeApprove(storedToken, address(depositor), balance);
// 4. Initiate a lock in the depositor contract with the balance of the contract
// This will lock the assets currently hold by this contract in the locker contract via the depositor
// The operation do not mint the sdTokens, as the sdToken have been minted over time to the account who deposited the tokens
depositor.createLock(balance);
// 5. ensure there is nothing left in the contract
if (IERC20(token).balanceOf(address(this)) != 0) revert TOKEN_NOT_TRANSFERRED_TO_LOCKER();
// 6. transfer the operator permission of the sdToken to the depositor contract
sdToken.setOperator(address(depositor));
// 7. set the state of the contract to active and emit the state update event
_setState(STATE.ACTIVE);
}
////////////////////////////////////////////////////////////////
/// --- EMERGENCY METHODS
///////////////////////////////////////////////////////////////
/// @notice Withdraw the previously deposited tokens if the launch has been canceled. This is an escape hatch for users.
/// @dev This function can only be called if the locker is in the canceled state.
/// @param amount Amount of tokens to withdraw.
/**
* @param staked Indicates if the sdTokens were staked in the gauge or not.
* • If true, the function will handle the withdrawal of sdTokens staked in the gauge
* before burning them. The caller must have approved this contract to transfer the gauge token.
* • If false, the function will simply burn the sdToken held by the caller.
*/
/// @custom:reverts REQUIRED_PARAM if the given amount is zero.
/// @custom:reverts CANNOT_WITHDRAW_IDLE_OR_ACTIVE_LOCKER if the locker is not in the canceled state.
function withdraw(uint256 amount, bool staked) external {
// ensure the amount is not zero
if (amount == 0) revert REQUIRED_PARAM();
// ensure the locker is in the canceled state
if (state != STATE.CANCELED) revert CANNOT_WITHDRAW_IDLE_OR_ACTIVE_LOCKER();
if (staked == true) {
// transfer the gauge token held by the caller to this contract
// will fail if the caller doesn't have enough balance in the gauge or forgot the approval
gauge.transferFrom(msg.sender, address(this), amount);
// use the gauge token transferred from the caller to this contract to withdraw the sdToken deposited in the gauge
gauge.withdraw(amount, false);
// burn the exact amount of sdToken previously held by the caller
sdToken.burn(address(this), amount);
} else {
// burn the sdToken held by the caller. This will fail if caller's sdToken balance is insufficient
sdToken.burn(msg.sender, amount);
}
// transfer back the default token to the caller
SafeTransferLib.safeTransfer(token, msg.sender, amount);
}
/// @notice Set the state of the locker as CANCELED. It only happens if the launch of the campaign has been canceled.
/// @custom:reverts ONLY_GOVERNANCE if the caller is not the stored governance address.
/// @custom:reverts CANNOT_CANCEL_ACTIVE_OR_CANCELED_LOCKER if the locker is active.
function cancelLocker() external onlyGovernance {
if (state != STATE.IDLE) revert CANNOT_CANCEL_ACTIVE_OR_CANCELED_LOCKER();
// 1. set the state of the contract to canceled and emit the state update event
_setState(STATE.CANCELED);
}
/// @notice Force cancel the locker. Can only be called if the locker is in the idle state and the timestamp is older than the force cancel delay.
/// This function is an escape hatch allowing anyone to force cancel the locker if the governance is not responsive.
/// When the locker is in the canceled state, the users can withdraw their previously deposited tokens.
/// @custom:reverts CANNOT_FORCE_CANCEL_ACTIVE_OR_CANCELED_LOCKER if the locker is not in the idle state.
/// @custom:reverts CANNOT_FORCE_CANCEL_RECENTLY_CREATED_LOCKER if the locker is not old enough to be force canceled.
function forceCancelLocker() external {
// check if the locker is in the idle state
if (state != STATE.IDLE) revert CANNOT_FORCE_CANCEL_ACTIVE_OR_CANCELED_LOCKER();
// check if the timestamp is older than the force cancel delay
if ((block.timestamp - timestamp) < FORCE_CANCEL_DELAY) {
revert CANNOT_FORCE_CANCEL_RECENTLY_CREATED_LOCKER();
}
// force the state to CANCELED
_setState(STATE.CANCELED);
}
/// @notice Transfer the governance to a new address.
/// @param _governance Address of the new governance.
/// @custom:reverts ONLY_GOVERNANCE if the caller is not the stored governance address.
function transferGovernance(address _governance) external onlyGovernance {
_setGovernance(_governance);
}
/// @notice Internal function to update the governance address.
/// @dev Never expose this internal function without gating the access with the onlyGovernance modifier, except the constructor
/// @param _governance Address of the new governance.
function _setGovernance(address _governance) internal {
// emit the event with the current and the future governance addresses
emit GovernanceUpdated(governance, _governance);
governance = _governance;
}
////////////////////////////////////////////////////////////////
/// --- HELPERS METHODS
///////////////////////////////////////////////////////////////
/// @notice Given the value of the state, return an user friendly label.
/// @dev This function is a helper for frontend engineers or indexers to display the state of the locker in a user friendly way.
/// @param _state The state of the locker as returned by the `state` variable or emitted in an event.
/// @return label The user friendly label of the state. Return an empty string if the state is not recognized. Returned values are capitalized.
function getUserFriendlyStateLabel(STATE _state) external pure returns (string memory label) {
if (_state == STATE.IDLE) label = "IDLE";
else if (_state == STATE.ACTIVE) label = "ACTIVE";
else if (_state == STATE.CANCELED) label = "CANCELED";
}
/// @notice Internal function to update the state of the locker.
/// @param _state The new state of the locker.
function _setState(STATE _state) internal {
state = _state;
emit LockerStateUpdated(_state);
}
}
// 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.
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 ERC20 `totalSupply` query has failed.
error TotalSupplyQueryFailed();
/// @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)`.
let success := call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
if iszero(and(eq(mload(0x00), 1), success)) {
if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
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 := call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
if iszero(and(eq(mload(0x00), 1), success)) {
success := lt(or(iszero(extcodesize(token)), returndatasize()), success)
}
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.
let success := call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
if iszero(and(eq(mload(0x00), 1), success)) {
if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
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.
let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
if iszero(and(eq(mload(0x00), 1), success)) {
if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
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.
let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
if iszero(and(eq(mload(0x00), 1), success)) {
if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
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)`.
let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
if iszero(and(eq(mload(0x00), 1), success)) {
if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
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.
let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
if iszero(and(eq(mload(0x00), 1), success)) {
if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
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.
success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20)
if iszero(and(eq(mload(0x00), 1), success)) {
// Check the `extcodesize` again just in case the token selfdestructs lol.
if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
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 Returns the total supply of the `token`.
/// Reverts if the token does not exist or does not implement `totalSupply()`.
function totalSupply(address token) internal view returns (uint256 result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x00, 0x18160ddd) // `totalSupply()`.
if iszero(and(gt(returndatasize(), 0x1f), staticcall(gas(), token, 0x1c, 0x04, 0x00, 0x20))) {
mstore(0x00, 0x54cd9435) // `TotalSupplyQueryFailed()`.
revert(0x1c, 0x04)
}
result := mload(0x00)
}
}
/// @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),
lt(iszero(extcodesize(token)), exists) // Token has code and Permit2 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( // Revert if token does not have code, or if the call fails.
mul(extcodesize(token), call(gas(), p, 0, add(m, 0x1c), 0x184, codesize(), 0x00))) {
mstore(0x00, 0x6b836e6b) // `Permit2Failed()`.
revert(0x1c, 0x04)
}
}
}
}
{
"compilationTarget": {
"src/common/locker/PreLaunchLocker.sol": "PreLaunchLocker"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": false,
"runs": 200
},
"remappings": [
":@interfaces/=node_modules/@stake-dao/interfaces/src/interfaces/",
":@layerzerolabs/=node_modules/@layerzerolabs/",
":@openzeppelin/contracts/=node_modules/@openzeppelin/contracts/",
":@safe-global/=node_modules/@safe-global/",
":@safe/=node_modules/@safe-global/safe-smart-account/",
":@solady/=node_modules/@solady/",
":address-book/=node_modules/@stake-dao/address-book/",
":common/=node_modules/@stake-dao/interfaces/src/",
":forge-std/=node_modules/forge-std/",
":murky/=node_modules/murky/",
":openzeppelin-contracts/=node_modules/@openzeppelin/contracts/",
":solady/=node_modules/@solady/",
":solidity-examples/=node_modules/@layerzerolabs/endpoint-v1-solidity-examples/contracts/"
]
}
[{"inputs":[{"internalType":"address","name":"_token","type":"address"},{"internalType":"address","name":"_sdToken","type":"address"},{"internalType":"address","name":"_gauge","type":"address"},{"internalType":"uint256","name":"_customForceCancelDelay","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"CANNOT_CANCEL_ACTIVE_OR_CANCELED_LOCKER","type":"error"},{"inputs":[],"name":"CANNOT_DEPOSIT_ACTIVE_OR_CANCELED_LOCKER","type":"error"},{"inputs":[],"name":"CANNOT_FORCE_CANCEL_ACTIVE_OR_CANCELED_LOCKER","type":"error"},{"inputs":[],"name":"CANNOT_FORCE_CANCEL_RECENTLY_CREATED_LOCKER","type":"error"},{"inputs":[],"name":"CANNOT_LOCK_ACTIVE_OR_CANCELED_LOCKER","type":"error"},{"inputs":[],"name":"CANNOT_WITHDRAW_IDLE_OR_ACTIVE_LOCKER","type":"error"},{"inputs":[],"name":"INVALID_GAUGE","type":"error"},{"inputs":[],"name":"INVALID_SD_TOKEN","type":"error"},{"inputs":[],"name":"INVALID_TOKEN","type":"error"},{"inputs":[],"name":"NOTHING_TO_LOCK","type":"error"},{"inputs":[],"name":"ONLY_GOVERNANCE","type":"error"},{"inputs":[],"name":"REQUIRED_PARAM","type":"error"},{"inputs":[],"name":"TOKEN_NOT_TRANSFERRED_TO_LOCKER","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"previousGovernanceAddress","type":"address"},{"indexed":false,"internalType":"address","name":"newGovernanceAddress","type":"address"}],"name":"GovernanceUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"enum PreLaunchLocker.STATE","name":"newState","type":"uint8"}],"name":"LockerStateUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"caller","type":"address"},{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":true,"internalType":"address","name":"gauge","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"TokensStaked","type":"event"},{"inputs":[],"name":"FORCE_CANCEL_DELAY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"cancelLocker","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bool","name":"stake","type":"bool"},{"internalType":"address","name":"receiver","type":"address"}],"name":"deposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bool","name":"stake","type":"bool"}],"name":"deposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"depositor","outputs":[{"internalType":"contract PreLaunchBaseDepositor","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"forceCancelLocker","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"gauge","outputs":[{"internalType":"contract ILiquidityGaugeV4","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"enum PreLaunchLocker.STATE","name":"_state","type":"uint8"}],"name":"getUserFriendlyStateLabel","outputs":[{"internalType":"string","name":"label","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"governance","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_depositor","type":"address"}],"name":"lock","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"sdToken","outputs":[{"internalType":"contract ISdToken","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"state","outputs":[{"internalType":"enum PreLaunchLocker.STATE","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"timestamp","outputs":[{"internalType":"uint96","name":"","type":"uint96"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"token","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_governance","type":"address"}],"name":"transferGovernance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bool","name":"staked","type":"bool"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"}]