PoolBoosterFactoryMerkl

Description:

Multi-signature wallet contract requiring multiple confirmations for transaction execution.

Blockchain: Ethereum

Source Code: View Code On The Blockchain

Solidity Source Code:

{{
  "language": "Solidity",
  "sources": {
    "@openzeppelin/contracts/token/ERC20/IERC20.sol": {
      "content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/IERC20.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves `amount` tokens from the caller's account to `recipient`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address recipient, uint256 amount) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `sender` to `recipient` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) external returns (bool);

    /**
     * @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);
}
"
    },
    "contracts/governance/Governable.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;

/**
 * @title Base for contracts that are managed by the Origin Protocol's Governor.
 * @dev Copy of the openzeppelin Ownable.sol contract with nomenclature change
 *      from owner to governor and renounce methods removed. Does not use
 *      Context.sol like Ownable.sol does for simplification.
 * @author Origin Protocol Inc
 */
abstract contract Governable {
    // Storage position of the owner and pendingOwner of the contract
    // keccak256("OUSD.governor");
    bytes32 private constant governorPosition =
        0x7bea13895fa79d2831e0a9e28edede30099005a50d652d8957cf8a607ee6ca4a;

    // keccak256("OUSD.pending.governor");
    bytes32 private constant pendingGovernorPosition =
        0x44c4d30b2eaad5130ad70c3ba6972730566f3e6359ab83e800d905c61b1c51db;

    // keccak256("OUSD.reentry.status");
    bytes32 private constant reentryStatusPosition =
        0x53bf423e48ed90e97d02ab0ebab13b2a235a6bfbe9c321847d5c175333ac4535;

    // See OpenZeppelin ReentrancyGuard implementation
    uint256 constant _NOT_ENTERED = 1;
    uint256 constant _ENTERED = 2;

    event PendingGovernorshipTransfer(
        address indexed previousGovernor,
        address indexed newGovernor
    );

    event GovernorshipTransferred(
        address indexed previousGovernor,
        address indexed newGovernor
    );

    /**
     * @notice Returns the address of the current Governor.
     */
    function governor() public view returns (address) {
        return _governor();
    }

    /**
     * @dev Returns the address of the current Governor.
     */
    function _governor() internal view returns (address governorOut) {
        bytes32 position = governorPosition;
        // solhint-disable-next-line no-inline-assembly
        assembly {
            governorOut := sload(position)
        }
    }

    /**
     * @dev Returns the address of the pending Governor.
     */
    function _pendingGovernor()
        internal
        view
        returns (address pendingGovernor)
    {
        bytes32 position = pendingGovernorPosition;
        // solhint-disable-next-line no-inline-assembly
        assembly {
            pendingGovernor := sload(position)
        }
    }

    /**
     * @dev Throws if called by any account other than the Governor.
     */
    modifier onlyGovernor() {
        require(isGovernor(), "Caller is not the Governor");
        _;
    }

    /**
     * @notice Returns true if the caller is the current Governor.
     */
    function isGovernor() public view returns (bool) {
        return msg.sender == _governor();
    }

    function _setGovernor(address newGovernor) internal {
        emit GovernorshipTransferred(_governor(), newGovernor);

        bytes32 position = governorPosition;
        // solhint-disable-next-line no-inline-assembly
        assembly {
            sstore(position, newGovernor)
        }
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and make it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        bytes32 position = reentryStatusPosition;
        uint256 _reentry_status;
        // solhint-disable-next-line no-inline-assembly
        assembly {
            _reentry_status := sload(position)
        }

        // On the first call to nonReentrant, _notEntered will be true
        require(_reentry_status != _ENTERED, "Reentrant call");

        // Any calls to nonReentrant after this point will fail
        // solhint-disable-next-line no-inline-assembly
        assembly {
            sstore(position, _ENTERED)
        }

        _;

        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        // solhint-disable-next-line no-inline-assembly
        assembly {
            sstore(position, _NOT_ENTERED)
        }
    }

    function _setPendingGovernor(address newGovernor) internal {
        bytes32 position = pendingGovernorPosition;
        // solhint-disable-next-line no-inline-assembly
        assembly {
            sstore(position, newGovernor)
        }
    }

    /**
     * @notice Transfers Governance of the contract to a new account (`newGovernor`).
     * Can only be called by the current Governor. Must be claimed for this to complete
     * @param _newGovernor Address of the new Governor
     */
    function transferGovernance(address _newGovernor) external onlyGovernor {
        _setPendingGovernor(_newGovernor);
        emit PendingGovernorshipTransfer(_governor(), _newGovernor);
    }

    /**
     * @notice Claim Governance of the contract to a new account (`newGovernor`).
     * Can only be called by the new Governor.
     */
    function claimGovernance() external {
        require(
            msg.sender == _pendingGovernor(),
            "Only the pending Governor can complete the claim"
        );
        _changeGovernor(msg.sender);
    }

    /**
     * @dev Change Governance of the contract to a new account (`newGovernor`).
     * @param _newGovernor Address of the new Governor
     */
    function _changeGovernor(address _newGovernor) internal {
        require(_newGovernor != address(0), "New Governor is address(0)");
        _setGovernor(_newGovernor);
    }
}
"
    },
    "contracts/interfaces/poolBooster/IMerklDistributor.sol": {
      "content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IMerklDistributor {
    struct CampaignParameters {
        // POPULATED ONCE CREATED

        // ID of the campaign. This can be left as a null bytes32 when creating campaigns
        // on Merkl.
        bytes32 campaignId;
        // CHOSEN BY CAMPAIGN CREATOR

        // Address of the campaign creator, if marked as address(0), it will be overriden with the
        // address of the `msg.sender` creating the campaign
        address creator;
        // Address of the token used as a reward
        address rewardToken;
        // Amount of `rewardToken` to distribute across all the epochs
        // Amount distributed per epoch is `amount/numEpoch`
        uint256 amount;
        // Type of campaign
        uint32 campaignType;
        // Timestamp at which the campaign should start
        uint32 startTimestamp;
        // Duration of the campaign in seconds. Has to be a multiple of EPOCH = 3600
        uint32 duration;
        // Extra data to pass to specify the campaign
        bytes campaignData;
    }

    function createCampaign(CampaignParameters memory newCampaign)
        external
        returns (bytes32);

    function signAndCreateCampaign(
        CampaignParameters memory newCampaign,
        bytes memory _signature
    ) external returns (bytes32);

    function sign(bytes memory _signature) external;

    function rewardTokenMinAmounts(address _rewardToken)
        external
        view
        returns (uint256);
}
"
    },
    "contracts/interfaces/poolBooster/IPoolBoostCentralRegistry.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;

interface IPoolBoostCentralRegistry {
    /**
     * @dev all the supported pool booster types are listed here. It is possible
     *      to have multiple versions of the factory that supports the same type of
     *      pool booster. Factories are immutable and this can happen when a factory
     *      or related pool booster required code update.
     *      e.g. "PoolBoosterSwapxDouble" & "PoolBoosterSwapxDouble_v2"
     */
    enum PoolBoosterType {
        // Supports bribing 2 contracts per pool. Appropriate for Ichi vault concentrated
        // liquidity pools where (which is expected in most/all cases) both pool gauges
        // require bribing.
        SwapXDoubleBooster,
        // Supports bribing a single contract per pool. Appropriate for Classic Stable &
        // Classic Volatile pools and Ichi vaults where only 1 side (1 of the 2 gauges)
        // needs bribing
        SwapXSingleBooster,
        // Supports bribing a single contract per pool. Appropriate for Metropolis pools
        MetropolisBooster,
        // Supports creating a Merkl campaign.
        MerklBooster
    }

    struct PoolBoosterEntry {
        address boosterAddress;
        address ammPoolAddress;
        PoolBoosterType boosterType;
    }

    event PoolBoosterCreated(
        address poolBoosterAddress,
        address ammPoolAddress,
        PoolBoosterType poolBoosterType,
        address factoryAddress
    );
    event PoolBoosterRemoved(address poolBoosterAddress);

    function emitPoolBoosterCreated(
        address _poolBoosterAddress,
        address _ammPoolAddress,
        PoolBoosterType _boosterType
    ) external;

    function emitPoolBoosterRemoved(address _poolBoosterAddress) external;
}
"
    },
    "contracts/interfaces/poolBooster/IPoolBooster.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;

interface IPoolBooster {
    event BribeExecuted(uint256 amount);

    /// @notice Execute the bribe action
    function bribe() external;
}
"
    },
    "contracts/poolBooster/AbstractPoolBoosterFactory.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;

import { Governable } from "../governance/Governable.sol";
import { IPoolBooster } from "../interfaces/poolBooster/IPoolBooster.sol";
import { IPoolBoostCentralRegistry } from "../interfaces/poolBooster/IPoolBoostCentralRegistry.sol";

/**
 * @title Abstract Pool booster factory
 * @author Origin Protocol Inc
 */
contract AbstractPoolBoosterFactory is Governable {
    struct PoolBoosterEntry {
        address boosterAddress;
        address ammPoolAddress;
        IPoolBoostCentralRegistry.PoolBoosterType boosterType;
    }

    // @notice address of Origin Token
    address public immutable oToken;
    // @notice Central registry contract
    IPoolBoostCentralRegistry public immutable centralRegistry;

    // @notice list of all the pool boosters created by this factory
    PoolBoosterEntry[] public poolBoosters;
    // @notice mapping of AMM pool to pool booster
    mapping(address => PoolBoosterEntry) public poolBoosterFromPool;

    // @param address _oToken address of the OToken token
    // @param address _governor address governor
    // @param address _centralRegistry address of the central registry
    constructor(
        address _oToken,
        address _governor,
        address _centralRegistry
    ) {
        require(_oToken != address(0), "Invalid oToken address");
        require(_governor != address(0), "Invalid governor address");
        require(
            _centralRegistry != address(0),
            "Invalid central registry address"
        );

        oToken = _oToken;
        centralRegistry = IPoolBoostCentralRegistry(_centralRegistry);
        _setGovernor(_governor);
    }

    /**
     * @notice Goes over all the pool boosters created by this factory and
     *         calls bribe() on them.
     * @param _exclusionList A list of pool booster addresses to skip when
     *        calling this function.
     */
    function bribeAll(address[] memory _exclusionList) external {
        uint256 lengthI = poolBoosters.length;
        for (uint256 i = 0; i < lengthI; i++) {
            address poolBoosterAddress = poolBoosters[i].boosterAddress;
            bool skipBribeCall = false;
            uint256 lengthJ = _exclusionList.length;
            for (uint256 j = 0; j < lengthJ; j++) {
                // pool booster in exclusion list
                if (_exclusionList[j] == poolBoosterAddress) {
                    skipBribeCall = true;
                    break;
                }
            }

            if (!skipBribeCall) {
                IPoolBooster(poolBoosterAddress).bribe();
            }
        }
    }

    /**
     * @notice Removes the pool booster from the internal list of pool boosters.
     * @dev This action does not destroy the pool booster contract nor does it
     *      stop the yield delegation to it.
     * @param _poolBoosterAddress address of the pool booster
     */
    function removePoolBooster(address _poolBoosterAddress)
        external
        onlyGovernor
    {
        uint256 boostersLen = poolBoosters.length;
        for (uint256 i = 0; i < boostersLen; ++i) {
            if (poolBoosters[i].boosterAddress == _poolBoosterAddress) {
                // erase mapping
                delete poolBoosterFromPool[poolBoosters[i].ammPoolAddress];

                // overwrite current pool booster with the last entry in the list
                poolBoosters[i] = poolBoosters[boostersLen - 1];
                // drop the last entry
                poolBoosters.pop();

                centralRegistry.emitPoolBoosterRemoved(_poolBoosterAddress);
                break;
            }
        }
    }

    function _storePoolBoosterEntry(
        address _poolBoosterAddress,
        address _ammPoolAddress,
        IPoolBoostCentralRegistry.PoolBoosterType _boosterType
    ) internal {
        PoolBoosterEntry memory entry = PoolBoosterEntry(
            _poolBoosterAddress,
            _ammPoolAddress,
            _boosterType
        );

        poolBoosters.push(entry);
        poolBoosterFromPool[_ammPoolAddress] = entry;

        // emit the events of the pool booster created
        centralRegistry.emitPoolBoosterCreated(
            _poolBoosterAddress,
            _ammPoolAddress,
            _boosterType
        );
    }

    function _deployContract(bytes memory _bytecode, uint256 _salt)
        internal
        returns (address _address)
    {
        // solhint-disable-next-line no-inline-assembly
        assembly {
            _address := create2(
                0,
                add(_bytecode, 0x20),
                mload(_bytecode),
                _salt
            )
        }

        require(
            _address.code.length > 0 && _address != address(0),
            "Failed creating a pool booster"
        );
    }

    // pre-compute the address of the deployed contract that will be
    // created when create2 is called
    function _computeAddress(bytes memory _bytecode, uint256 _salt)
        internal
        view
        returns (address)
    {
        bytes32 hash = keccak256(
            abi.encodePacked(
                bytes1(0xff),
                address(this),
                _salt,
                keccak256(_bytecode)
            )
        );

        // cast last 20 bytes of hash to address
        return address(uint160(uint256(hash)));
    }

    function poolBoosterLength() external view returns (uint256) {
        return poolBoosters.length;
    }
}
"
    },
    "contracts/poolBooster/PoolBoosterFactoryMerkl.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;

import { PoolBoosterMerkl } from "./PoolBoosterMerkl.sol";
import { AbstractPoolBoosterFactory, IPoolBoostCentralRegistry } from "./AbstractPoolBoosterFactory.sol";

/**
 * @title Pool booster factory for creating Merkl pool boosters.
 * @author Origin Protocol Inc
 */
contract PoolBoosterFactoryMerkl is AbstractPoolBoosterFactory {
    uint256 public constant version = 1;

    /// @notice address of the Merkl distributor
    address public merklDistributor;

    /// @notice event emitted when the Merkl distributor is updated
    event MerklDistributorUpdated(address newDistributor);

    /**
     * @param _oToken address of the OToken token
     * @param _governor address governor
     * @param _centralRegistry address of the central registry
     * @param _merklDistributor address of the Merkl distributor
     */
    constructor(
        address _oToken,
        address _governor,
        address _centralRegistry,
        address _merklDistributor
    ) AbstractPoolBoosterFactory(_oToken, _governor, _centralRegistry) {
        _setMerklDistributor(_merklDistributor);
    }

    /**
     * @dev Create a Pool Booster for Merkl.
     * @param _campaignType The type of campaign to create. This is used to determine the type of
     *        bribe contract to create. The type is defined in the MerklDistributor contract.
     * @param _ammPoolAddress address of the AMM pool where the yield originates from
     * @param _campaignDuration The duration of the campaign in seconds
     * @param campaignData The data to be used for the campaign. This is used to determine the type of
     *        bribe contract to create. The type is defined in the MerklDistributor contract.
     *        This should be fetched from the Merkl UI.
     * @param _salt A unique number that affects the address of the pool booster created. Note: this number
     *        should match the one from `computePoolBoosterAddress` in order for the final deployed address
     *        and pre-computed address to match
     */
    function createPoolBoosterMerkl(
        uint32 _campaignType,
        address _ammPoolAddress,
        uint32 _campaignDuration,
        bytes calldata campaignData,
        uint256 _salt
    ) external onlyGovernor {
        require(
            _ammPoolAddress != address(0),
            "Invalid ammPoolAddress address"
        );
        require(_salt > 0, "Invalid salt");
        require(_campaignDuration > 1 hours, "Invalid campaign duration");
        require(campaignData.length > 0, "Invalid campaign data");

        address poolBoosterAddress = _deployContract(
            abi.encodePacked(
                type(PoolBoosterMerkl).creationCode,
                abi.encode(
                    oToken,
                    merklDistributor,
                    _campaignDuration,
                    _campaignType,
                    governor(),
                    campaignData
                )
            ),
            _salt
        );

        _storePoolBoosterEntry(
            poolBoosterAddress,
            _ammPoolAddress,
            IPoolBoostCentralRegistry.PoolBoosterType.MerklBooster
        );
    }

    /**
     * @dev Create a Pool Booster for Merkl.
     * @param _campaignType The type of campaign to create. This is used to determine the type of
     *        bribe contract to create. The type is defined in the MerklDistributor contract.
     * @param _ammPoolAddress address of the AMM pool where the yield originates from
     * @param _salt A unique number that affects the address of the pool booster created. Note: this number
     *        should match the one from `createPoolBoosterMerkl` in order for the final deployed address
     *        and pre-computed address to match
     */
    function computePoolBoosterAddress(
        uint32 _campaignType,
        address _ammPoolAddress,
        uint32 _campaignDuration,
        bytes calldata campaignData,
        uint256 _salt
    ) external view returns (address) {
        require(
            _ammPoolAddress != address(0),
            "Invalid ammPoolAddress address"
        );
        require(_salt > 0, "Invalid salt");
        require(_campaignDuration > 1 hours, "Invalid campaign duration");
        require(campaignData.length > 0, "Invalid campaign data");

        return
            _computeAddress(
                abi.encodePacked(
                    type(PoolBoosterMerkl).creationCode,
                    abi.encode(
                        oToken,
                        merklDistributor,
                        _campaignDuration,
                        _campaignType,
                        governor(),
                        campaignData
                    )
                ),
                _salt
            );
    }

    /**
     * @dev Set the address of the Merkl distributor
     * @param _merklDistributor The address of the Merkl distributor
     */
    function setMerklDistributor(address _merklDistributor)
        external
        onlyGovernor
    {
        _setMerklDistributor(_merklDistributor);
    }

    function _setMerklDistributor(address _merklDistributor) internal {
        require(
            _merklDistributor != address(0),
            "Invalid merklDistributor address"
        );
        merklDistributor = _merklDistributor;
        emit MerklDistributorUpdated(_merklDistributor);
    }
}
"
    },
    "contracts/poolBooster/PoolBoosterMerkl.sol": {
      "content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;

import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IPoolBooster } from "../interfaces/poolBooster/IPoolBooster.sol";
import { IMerklDistributor } from "../interfaces/poolBooster/IMerklDistributor.sol";

interface IERC1271 {
    /**
     * @dev Should return whether the signature provided is valid for the provided data
     * @param hash Hash of the data to be signed
     * @param signature Signature byte array associated with _data
     */
    function isValidSignature(bytes32 hash, bytes memory signature)
        external
        view
        returns (bytes4 magicValue);
}

/**
 * @title Pool booster for Merkl distributor
 * @author Origin Protocol Inc
 */
contract PoolBoosterMerkl is IPoolBooster, IERC1271 {
    /// @notice address of merkl distributor
    IMerklDistributor public immutable merklDistributor;
    /// @notice address of the OS token
    IERC20 public immutable rewardToken;
    /// @notice if balance under this amount the bribe action is skipped
    uint256 public constant MIN_BRIBE_AMOUNT = 1e10;
    /// @notice Campaign duration in seconds
    uint32 public immutable duration; // -> should be immutable
    /// @notice Campaign type
    uint32 public immutable campaignType;
    /// @notice Owner of the campaign
    address public immutable creator;
    /// @notice Campaign data
    bytes public campaignData;

    constructor(
        address _rewardToken,
        address _merklDistributor,
        uint32 _duration,
        uint32 _campaignType,
        address _creator,
        bytes memory _campaignData
    ) {
        require(_rewardToken != address(0), "Invalid rewardToken address");
        require(
            _merklDistributor != address(0),
            "Invalid merklDistributor address"
        );
        require(_campaignData.length > 0, "Invalid campaignData");
        require(_duration > 1 hours, "Invalid duration");

        campaignType = _campaignType;
        duration = _duration;
        creator = _creator;

        merklDistributor = IMerklDistributor(_merklDistributor);
        rewardToken = IERC20(_rewardToken);
        campaignData = _campaignData;
    }

    /// @notice Create a campaign on the Merkl distributor
    function bribe() external override {
        // Ensure token is approved for the Merkl distributor
        uint256 minAmount = merklDistributor.rewardTokenMinAmounts(
            address(rewardToken)
        );
        require(minAmount > 0, "Min reward amount must be > 0");

        // if balance too small or below threshold, do no bribes
        uint256 balance = rewardToken.balanceOf(address(this));
        if (
            balance < MIN_BRIBE_AMOUNT ||
            (balance * 1 hours < minAmount * duration)
        ) {
            return;
        }

        // Approve the bribe contract to spend the reward token
        rewardToken.approve(address(merklDistributor), balance);

        // Notify the bribe contract of the reward amount
        merklDistributor.signAndCreateCampaign(
            IMerklDistributor.CampaignParameters({
                campaignId: bytes32(0),
                creator: creator,
                rewardToken: address(rewardToken),
                amount: balance,
                campaignType: campaignType,
                startTimestamp: getNextPeriodStartTime(),
                duration: duration,
                campaignData: campaignData
            }),
            bytes("")
        );
        emit BribeExecuted(balance);
    }

    /// @notice Used to sign a campaign on the Merkl distributor
    function isValidSignature(bytes32, bytes memory)
        external
        view
        override
        returns (bytes4 magicValue)
    {
        require(msg.sender == address(merklDistributor), "Invalid sender");
        // bytes4(keccak256("isValidSignature(bytes32,bytes)")) == 0x1626ba7e
        return bytes4(0x1626ba7e);
    }

    /// @notice Returns the timestamp for the start of the next period based on the configured duration
    function getNextPeriodStartTime() public view returns (uint32) {
        // Calculate the timestamp for the next period boundary
        return uint32((block.timestamp / duration + 1) * duration);
    }
}
"
    }
  },
  "settings": {
    "optimizer": {
      "enabled": true,
      "runs": 200
    },
    "evmVersion": "paris",
    "outputSelection": {
      "*": {
        "*": [
          "evm.bytecode",
          "evm.deployedBytecode",
          "devdoc",
          "userdoc",
          "metadata",
          "abi"
        ]
      }
    },
    "metadata": {
      "useLiteralContent": true
    }
  }
}}

Tags:
ERC20, Multisig, Swap, Liquidity, Yield, Multi-Signature, Factory|addr:0x0fc66355b681503efee7741bd848080d809fd6db|verified:true|block:23490281|tx:0xfcfe807ec632571ba841045a6bf0799208554f6bd646e34ddfbb3782ddd463c0|first_check:1759475982

Submitted on: 2025-10-03 09:19:43

Comments

Log in to comment.

No comments yet.