Forkchoice Ethereum Mainnet

Address Contract Verified

Address 0x2f2C3Cf0b2D6F4F6CD5F57665Ae629eee813350B
Balance 0 ETH
Nonce 1
Code Size 6746 bytes
Indexed Transactions 0 (1 on-chain, 0.7% indexed)
External Etherscan · Sourcify

Contract Bytecode

6746 bytes
0x6080604052600436106101e35760003560e01c806396199f5511610102578063d9caed1211610095578063f7d9757711610064578063f7d9757714610562578063fc0c546a14610582578063ffa1ad74146105b6578063fff4da1f146105dd57600080fd5b8063d9caed12146104ec578063e06174e41461050c578063e1f21c6714610522578063f2fde38b1461054257600080fd5b8063ba730e53116100d1578063ba730e5314610480578063bf13d438146104a0578063c7780ab2146104c0578063d09de08a146104d657600080fd5b806396199f551461040b57806398d5fdca1461042b578063a4c0ed3614610440578063afaefe9e1461046057600080fd5b80636f7267b71161017a5780637ff6c191116101495780637ff6c1911461039c57806384b41fda146103b25780638a07d3d0146103c85780638da5cb5b146103eb57600080fd5b80636f7267b71461031c578063777adcf01461033c5780637cd6a7fd1461035c5780637fcc15991461037c57600080fd5b80635001f3b5116101b65780635001f3b51461027d578063522f6815146102c95780635c627935146102e957806369365c521461030957600080fd5b806308d4db14146101e857806314bf043c1461021b57806322ae3e6f1461023d5780633bed33ce1461025d575b600080fd5b3480156101f457600080fd5b5061020861020336600461149a565b6105fd565b6040519081526020015b60405180910390f35b34801561022757600080fd5b5061023b6102363660046114ff565b610616565b005b34801561024957600080fd5b5061023b61025836600461161a565b6106bc565b34801561026957600080fd5b5061023b61027836600461149a565b61070d565b34801561028957600080fd5b506102b17f000000000000000000000000b58e61c3098d85632df34eecfb899a1ed80921cb81565b6040516001600160a01b039091168152602001610212565b3480156102d557600080fd5b5061023b6102e4366004611684565b610765565b3480156102f557600080fd5b5061023b6103043660046116be565b610879565b6102086103173660046116f7565b6108c0565b34801561032857600080fd5b5061020861033736600461149a565b610a23565b34801561034857600080fd5b506001546102b1906001600160a01b031681565b34801561036857600080fd5b5061023b61037736600461149a565b610a9d565b34801561038857600080fd5b5061023b6103973660046114ff565b610ae0565b3480156103a857600080fd5b5061020860045481565b3480156103be57600080fd5b5061020860055481565b3480156103d457600080fd5b5060055415155b6040519015158152602001610212565b3480156103f757600080fd5b506000546102b1906001600160a01b031681565b34801561041757600080fd5b5061023b61042636600461161a565b610b81565b34801561043757600080fd5b50610208610b9e565b34801561044c57600080fd5b506103db61045b36600461174d565b610bae565b34801561046c57600080fd5b5061023b61047b3660046117a9565b610bc9565b34801561048c57600080fd5b5061020861049b36600461149a565b610c2d565b3480156104ac57600080fd5b506102086104bb36600461149a565b610c52565b3480156104cc57600080fd5b5061020860065481565b3480156104e257600080fd5b5061020860035481565b3480156104f857600080fd5b5061023b6105073660046117cb565b610ccb565b34801561051857600080fd5b5061020860075481565b34801561052e57600080fd5b5061023b61053d3660046117cb565b610d25565b34801561054e57600080fd5b5061023b61055d36600461180c565b610da6565b34801561056e57600080fd5b5061023b61057d3660046117a9565b610e09565b34801561058e57600080fd5b506102b17f0000000000000000000000006f38e0f1a73c96cb3f42598613ea3474f09cb20081565b3480156105c257600080fd5b506105cb600881565b60405160ff9091168152602001610212565b3480156105e957600080fd5b5061023b6105f836600461180c565b610e59565b600061061061060a610b9e565b83610eab565b92915050565b61061e610f05565b60005b878110156106b1576106a989898381811061063e5761063e611829565b9050602002016020810190610653919061180c565b88888481811061066557610665611829565b9050602002013587878581811061067e5761067e611829565b9050602002013586868681811061069757610697611829565b9050602002810190610258919061183f565b600101610621565b505050505050505050565b6106c4610f05565b6106d18585858585610f34565b506107066001600160a01b037f0000000000000000000000006f38e0f1a73c96cb3f42598613ea3474f09cb200168686610ff5565b5050505050565b6000546001600160a01b0316331480159061073357506001546001600160a01b03163314155b156107585760405163618a6eaf60e01b81523360048201526024015b60405180910390fd5b6107623382610765565b50565b6000546001600160a01b0316331480159061078b57506001546001600160a01b03163314155b156107ab5760405163618a6eaf60e01b815233600482015260240161074f565b6000826001600160a01b03168260405160006040518083038185875af1925050503d80600081146107f8576040519150601f19603f3d011682016040523d82523d6000602084013e6107fd565b606091505b50509050806108315760405163fd4206e360e01b81526001600160a01b03841660048201526024810183905260440161074f565b826001600160a01b03167f7084f5476618d8e60b11ef0d7d3f06914655adb8793e28ff7f018d4c76d505d58360405161086c91815260200190565b60405180910390a2505050565b610881610f05565b60075482151560018083161414610896576001185b6108a4600260075481161490565b1515821515146108b2576002185b6108bb81610a9d565b505050565b6000336001600160a01b038716148015906108e657506001546001600160a01b03163314155b15610906576040516336e733c560e21b815233600482015260240161074f565b34156109515760408051348152602081018690526001600160a01b038716917f74cf3d18d0ddca79038197ad0dd2c7fa5005ef61a5d1ed190e8a8a437e2fcf10910160405180910390a25b7f0000000000000000000000006f38e0f1a73c96cb3f42598613ea3474f09cb2006001600160a01b0316866001600160a01b03160361099d5761099685858585611047565b9050610a1a565b7f000000000000000000000000b58e61c3098d85632df34eecfb899a1ed80921cb6001600160a01b0316866001600160a01b0316036109e25761099685858585611191565b60405162461bcd60e51b815260206004820152600d60248201526c34b73b30b634b2103a37b5b2b760991b604482015260640161074f565b95945050505050565b600080610a2e610b9e565b9050600080610a3d838661189c565b90505b80821015610a955760006002610a5683856118be565b610a60919061189c565b90506000610a6e8583610eab565b905080871115610a8a57610a838260016118be565b9350610a8e565b8192505b5050610a40565b509392505050565b610aa5610f05565b60078190556040518181527f73f575b434990a047744d035619e8cb101d71ae30e15cee70124fcfa992a4b479060200160405180910390a150565b610ae8610f05565b60005b878110156106b157610b78898983818110610b0857610b08611829565b9050602002016020810190610b1d919061180c565b888884818110610b2f57610b2f611829565b90506020020135878785818110610b4857610b48611829565b90506020020135868686818110610b6157610b61611829565b9050602002810190610b73919061183f565b610f34565b50600101610aeb565b610b89610f05565b610b968585858585610f34565b505050505050565b6000610ba942610c52565b905090565b6000610bbd33868686866108c0565b50600195945050505050565b610bd1610f05565b610be5610bdc610b9e565b60025542600455565b6005829055600681905560408051838152602081018390527f0c64990ff5fa601870b48aa83367706d39e6f55a0f73069c97c2f9c08241a17e91015b60405180910390a15050565b600061061060035483610c4091906118d1565b610c48610b9e565b61060a91906118e8565b6000610c5f600554151590565b15610cc357600060045483610c7491906118e8565b9050600060065460055483610c89919061189c565b610c9391906118fb565b9050600081600254610ca5919061192b565b90506000811215610cbb57506000949350505050565b949350505050565b505060025490565b6000546001600160a01b03163314801590610cf157506001546001600160a01b03163314155b15610d115760405163618a6eaf60e01b815233600482015260240161074f565b6108bb6001600160a01b0384168383610ff5565b610d2d610f05565b60405163095ea7b360e01b81526001600160a01b0383811660048301526024820183905284169063095ea7b3906044016020604051808303816000875af1158015610d7c573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610da09190611953565b50505050565b610dae610f05565b600080546040516001600160a01b03808516939216917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a3600080546001600160a01b0319166001600160a01b0392909216919091179055565b610e11610f05565b610e1e8260025542600455565b600381905560408051838152602081018390527fa0f1665b7b659537b52deec61ea64d134a3bccda74c7f4e79f2246e7a8187a8a9101610c21565b610e61610f05565b600180546001600160a01b0319166001600160a01b0383169081179091556040517f4a534dbb0a1201b9baddbb0018d195c269a25847c33b19c06d24309f20f84b3390600090a250565b600081600003610ebd57506000610610565b600354600090610ece6001856118e8565b610ed891906118d1565b610ee290856118be565b9050600283610ef183876118be565b610efb91906118d1565b610cbb919061189c565b6000546001600160a01b03163314610f32576040516396a19be960e01b815233600482015260240161074f565b565b600060035485610f4491906118d1565b600254610f5191906118be565b6002556001600160a01b037f0000000000000000000000006f38e0f1a73c96cb3f42598613ea3474f09cb200167f424641a08047715d4b70195e1d90818d302683d9f65f41910ca13f5a486d37cc878585897f000000000000000000000000b58e61c3098d85632df34eecfb899a1ed80921cb8a6000610fcf610b9e565b604051610fe3989796959493929190611970565b60405180910390a25091949350505050565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663a9059cbb60e01b1790526108bb90849061126b565b6000611057600260075481161490565b61107457604051632f168fef60e01b815260040160405180910390fd5b600061107f85610c2d565b90506003548561108f91906118d1565b600260008282546110a091906118e8565b909155506110b0905084846112ce565b156110e9576110e96001600160a01b037f000000000000000000000000b58e61c3098d85632df34eecfb899a1ed80921cb168783610ff5565b6001600160a01b037f0000000000000000000000006f38e0f1a73c96cb3f42598613ea3474f09cb200167f424641a08047715d4b70195e1d90818d302683d9f65f41910ca13f5a486d37cc8786866111408a6119d9565b7f000000000000000000000000b58e61c3098d85632df34eecfb899a1ed80921cb87600061116c610b9e565b604051611180989796959493929190611970565b60405180910390a295945050505050565b60006111a1600160075481161490565b6111be576040516301ab85d360e71b815260040160405180910390fd5b60006111c985610a23565b905060006111d6826105fd565b90506111e58783838888610f34565b508581101561122d5761122d876111fc83896118e8565b6001600160a01b037f000000000000000000000000b58e61c3098d85632df34eecfb899a1ed80921cb169190610ff5565b6112616001600160a01b037f0000000000000000000000006f38e0f1a73c96cb3f42598613ea3474f09cb200168884610ff5565b5095945050505050565b60006112806001600160a01b0384168361138e565b905080516000141580156112a55750808060200190518101906112a39190611953565b155b156108bb57604051635274afe760e01b81526001600160a01b038416600482015260240161074f565b60008115806112dd5750601482145b156112ea57506001610610565b600160f81b838360008161130057611300611829565b9050013560f81c60f81b6001600160f81b0319160361132157506001610610565b600160f91b838360008161133757611337611829565b9050013560f81c60f81b6001600160f81b0319160361135857506000610610565b60405162461bcd60e51b815260206004820152600b60248201526a3ab735b737bbb7103932b360a91b604482015260640161074f565b606061139c838360006113a3565b9392505050565b606082516000141580156113bf57506001600160a01b0384163b155b156113e857604051639eb1341360e01b81526001600160a01b038516600482015260240161074f565b600080856001600160a01b0316848660405161140491906119f5565b60006040518083038185875af1925050503d8060008114611441576040519150601f19603f3d011682016040523d82523d6000602084013e611446565b606091505b5091509150811561145a57915061139c9050565b80511561146957805160208201fd5b60405162461bcd60e51b815260206004820152600660248201526519985a5b195960d21b604482015260640161074f565b6000602082840312156114ac57600080fd5b5035919050565b60008083601f8401126114c557600080fd5b50813567ffffffffffffffff8111156114dd57600080fd5b6020830191508360208260051b85010111156114f857600080fd5b9250929050565b6000806000806000806000806080898b03121561151b57600080fd5b883567ffffffffffffffff8082111561153357600080fd5b61153f8c838d016114b3565b909a50985060208b013591508082111561155857600080fd5b6115648c838d016114b3565b909850965060408b013591508082111561157d57600080fd5b6115898c838d016114b3565b909650945060608b01359150808211156115a257600080fd5b506115af8b828c016114b3565b999c989b5096995094979396929594505050565b6001600160a01b038116811461076257600080fd5b60008083601f8401126115ea57600080fd5b50813567ffffffffffffffff81111561160257600080fd5b6020830191508360208285010111156114f857600080fd5b60008060008060006080868803121561163257600080fd5b853561163d816115c3565b94506020860135935060408601359250606086013567ffffffffffffffff81111561166757600080fd5b611673888289016115d8565b969995985093965092949392505050565b6000806040838503121561169757600080fd5b82356116a2816115c3565b946020939093013593505050565b801515811461076257600080fd5b600080604083850312156116d157600080fd5b82356116dc816116b0565b915060208301356116ec816116b0565b809150509250929050565b60008060008060006080868803121561170f57600080fd5b853561171a816115c3565b9450602086013561172a816115c3565b935060408601359250606086013567ffffffffffffffff81111561166757600080fd5b6000806000806060858703121561176357600080fd5b843561176e816115c3565b935060208501359250604085013567ffffffffffffffff81111561179157600080fd5b61179d878288016115d8565b95989497509550505050565b600080604083850312156117bc57600080fd5b50508035926020909101359150565b6000806000606084860312156117e057600080fd5b83356117eb816115c3565b925060208401356117fb816115c3565b929592945050506040919091013590565b60006020828403121561181e57600080fd5b813561139c816115c3565b634e487b7160e01b600052603260045260246000fd5b6000808335601e1984360301811261185657600080fd5b83018035915067ffffffffffffffff82111561187157600080fd5b6020019150368190038213156114f857600080fd5b634e487b7160e01b600052601160045260246000fd5b6000826118b957634e487b7160e01b600052601260045260246000fd5b500490565b8082018082111561061057610610611886565b808202811582820484141761061057610610611886565b8181038181111561061057610610611886565b80820260008212600160ff1b8414161561191757611917611886565b818105831482151761061057610610611886565b808201828112600083128015821682158216171561194b5761194b611886565b505092915050565b60006020828403121561196557600080fd5b815161139c816116b0565b6001600160a01b03898116825260e0602083018190528201889052600090610100898b828601376000848b018201526040840198909852959095166060820152608081019390935260a083019190915260c0820152601f909301601f1916909201019392505050565b6000600160ff1b82016119ee576119ee611886565b5060000390565b6000825160005b81811015611a1657602081860181015185830152016119fc565b50600092019182525091905056fea264697066735822122080193698218fb324b2fa7aefc87c21c78aebb195e8e6c640c7f3047158fe9ab764736f6c63430008190033

Verified Source Code Full Match

Compiler: v0.8.25+commit.b61c2a91 EVM: paris Optimization: Yes (200 runs)
IERC20.sol 92 lines
/**
* SPDX-License-Identifier: MIT
*
* Copyright (c) 2016-2019 zOS Global Limited
*
*/
pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP. Does not include
 * the optional functions; to access them see `ERC20Detailed`.
 */

interface IERC20 {

    // Optional functions
    function name() external view returns (string memory);

    function symbol() external view returns (string memory);

    function decimals() external view returns (uint8);

    /**
     * @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.
     *
     * > 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);

}
Address.sol 74 lines
// SPDX-License-Identifier: MIT
// Copied from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Address.sol
// and modified it.

pragma solidity ^0.8.0;

library Address {

    /// @param target Target address to call the function on.
    error Address_NotTransferNorContract(address target);

    /**
     * @dev Returns true if `account` is a contract.
     *
     * [IMPORTANT]
     * ====
     * It is unsafe to assume that an address for which this function returns
     * false is an externally-owned account (EOA) and not a contract.
     *
     * Among others, `isContract` will return false for the following
     * types of addresses:
     *
     *  - an externally-owned account
     *  - a contract in construction
     *  - an address where a contract will be created
     *  - an address where a contract lived, but was destroyed
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize/address.code.length, which returns 0
        // for contracts in construction, since the code is only stored at the end
        // of the constructor execution.
        return account.code.length > 0;
    }
    
    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason or custom error, it is bubbled
     * up by this function (like regular Solidity function calls). However, if
     * the call reverted with no returned reason, this function reverts with a
     * {FailedInnerCall} error.
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0);
    }

    function functionCallWithValue(address target, bytes memory data, uint256 weiValue) internal returns (bytes memory) {
        if (data.length != 0 && !isContract(target)) {
            revert Address_NotTransferNorContract(target);
        }
        // solhint-disable-next-line avoid-low-level-calls
        (bool success, bytes memory returndata) = target.call{ value: weiValue }(data);
        if (success) {
            return returndata;
        } else if (returndata.length > 0) {
            assembly{
                revert (add (returndata, 0x20), mload (returndata))
            }
        } else {
           revert("failed");
        }
    }
}
Ownable.sol 59 lines
// SPDX-License-Identifier: MIT
//
// From https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable.sol
//
// Modifications:
// - Replaced Context._msgSender() with msg.sender
// - Made leaner
// - Extracted interface

pragma solidity ^0.8.0;

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
contract Ownable {

    address public owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    error Ownable_NotOwner(address sender);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor (address initialOwner) {
        owner = initialOwner;
        emit OwnershipTransferred(address(0), owner);
    }

    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) external onlyOwner {
        emit OwnershipTransferred(owner, newOwner);
        owner = newOwner;
    }

    function _checkOwner() internal view {
        if (msg.sender != owner) {
            revert Ownable_NotOwner(msg.sender);
        }
    }
}
SafeERC20.sol 98 lines
// SPDX-License-Identifier: MIT
// coppied and adjusted from OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../ERC20/IERC20.sol";
import {IERC20Permit} from "../ERC20/IERC20Permit.sol";
import {Address} from "./Address.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    using Address for address;

    /**
     * @dev An operation with an ERC20 token failed.
     */
    error SafeERC20FailedOperation(address token);

    /**
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
    }

    /**
     * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
     * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
     */
    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
    }

    /**
     * @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`.
     * Revert on invalid signature.
     */
    function safePermit(
        IERC20Permit token,
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal {
        uint256 nonceBefore = token.nonces(owner);
        token.permit(owner, spender, value, deadline, v, r, s);
        uint256 nonceAfter = token.nonces(owner);
        if (nonceAfter != nonceBefore + 1) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
        // the target address contains contract code and also asserts for success in the low-level call.

        bytes memory returndata = address(token).functionCall(data);
        if (returndata.length != 0 && !abi.decode(returndata, (bool))) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
        // and not revert is the subcall reverts.

        (bool success, bytes memory returndata) = address(token).call(data);
        return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0;
    }
}
IERC20Permit.sol 75 lines
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol)
// Copied from https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/extensions/draft-IERC20Permit.sol

pragma solidity ^0.8.0;

import "./IERC20.sol";

/**
 * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
 * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
 *
 * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
 * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
 * need to send a transaction, and thus is not required to hold Ether at all.
 */
interface IERC20Permit is IERC20 {

    /*//////////////////////////////////////////////////////////////
                            Custom errors
	//////////////////////////////////////////////////////////////*/
    /// Block timestamp must to be before deadline.
    /// @param deadline The deadline of the permit.
    /// @param blockTimestamp The timestamp of the execution block.
    error Permit_DeadlineExpired(uint256 deadline, uint256 blockTimestamp);
    /// Recovered address must be owner and not zero address.
    /// @param signerAddress The recovered signer address.
    error Permit_InvalidSigner(address signerAddress);

    /**
     * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
     * given ``owner``'s signed approval.
     *
     * IMPORTANT: The same issues {IERC20-approve} has related to transaction
     * ordering also apply here.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `deadline` must be a timestamp in the future.
     * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
     * over the EIP712-formatted function arguments.
     * - the signature must use ``owner``'s current nonce (see {nonces}).
     *
     * For more information on the signature format, see the
     * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
     * section].
     */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @dev Returns the current nonce for `owner`. This value must be
     * included whenever a signature is generated for {permit}.
     *
     * Every successful call to {permit} increases ``owner``'s nonce by one. This
     * prevents a signature from being used multiple times.
     */
    function nonces(address owner) external view returns (uint256);

    /**
     * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
     */
    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() external view returns (bytes32);
}
Brokerbot.sol 322 lines
/**
* SPDX-License-Identifier: LicenseRef-Aktionariat
*
* Proprietary License
*
* This code cannot be used without an explicit permission from the copyright holder.
* If you wish to use the Aktionariat Brokerbot, you can either use the open version
* named Brokerbot.sol that can be used under an MIT License with Automated License Fee Payments,
* or you can get in touch with use to negotiate a license to use LicensedBrokerbot.sol .
*
* Copyright (c) 2021 Aktionariat AG (aktionariat.com), All rights reserved.
*/
pragma solidity ^0.8.0;

import "../utils/Ownable.sol";
import "../ERC20/IERC20.sol";
import "../ERC20/IERC20Permit.sol";
import "../ERC20/IERC677Receiver.sol";
import "./IBrokerbot.sol";
import "../utils/SafeERC20.sol";

contract Brokerbot is IBrokerbot, Ownable {

    using SafeERC20 for IERC20;

    address public override paymenthub;

    IERC20 public override immutable base;  // ERC-20 currency
    IERC20Permit public override immutable token; // ERC-20 share token

    uint256 private price; // current offer price in base currency, without drift
    uint256 public increment; // increment step the price in/decreases when buying/selling

    uint256 public driftStart;
    uint256 public timeToDrift; // seconds until drift pushes price by one drift increment
    int256 public driftIncrement;

    // Note that these settings might be hard-coded in various places, so better not change these values.
    uint8 private constant BUYING_ENABLED = 0x1;
    uint8 private constant SELLING_ENABLED = 0x2;
    // note that in the UI, we call the setting "convert ether", which is the opposite
    uint8 private constant KEEP_ETHER = 0x4;

    // Version history
    // Version 2: added ability to process bank orders even if buying disabled
    // Version 3: added various events, removed license fee
    // Version 4: made version field public so it is actually usable    
    // Version 5: added target address for withdrawEther
    // Version 6: added costs field to notifyTrade
    // Version 7: added withdraw eth event
    // Version 8: use SafeERC20
    uint8 public constant VERSION = 0x8;

    // more bits to be used by payment hub
    uint256 public override settings = BUYING_ENABLED | SELLING_ENABLED;

    event Trade(IERC20Permit indexed token, address who, bytes ref, int amount, IERC20 base, uint totPrice, uint fee, uint newprice);
    event PaymentHubUpdate(address indexed paymentHub);
    event PriceSet(uint256 price, uint256 increment);
    event DriftSet(uint256 timeToDrift, int256 driftIncrement);
    event SettingsChange(uint256 setting);
    // ETH in/out events
    event Received(address indexed from, uint amountETH, uint amountBase);
    event Withdrawn(address indexed target, uint amountETH);
    
    constructor(
        IERC20Permit _token,
        uint256 _price,
        uint256 _increment,
        IERC20 _base,
        address _owner,
        address _paymentHub
    )
        Ownable(_owner)
    {
        base = _base;
        token = _token;
        price = _price;
        increment = _increment;
        paymenthub = _paymentHub;
        // Should we disabled recoverability in the recovery hub here?
        // No, if someone attacks us, we can always trigger a transfer and recover the tokens as well as the collateral.
    }

    function setPrice(uint256 _price, uint256 _increment) external onlyOwner {
        anchorPrice(_price);
        increment = _increment;
        emit PriceSet(_price, _increment);
    }

    function hasDrift() public view returns (bool) {
        return timeToDrift != 0;
    }

    // secondsPerStep should be negative for downwards drift
    function setDrift(uint256 secondsPerStep, int256 _driftIncrement) external onlyOwner {
        anchorPrice(getPrice());
        timeToDrift = secondsPerStep;
        driftIncrement = _driftIncrement;
        emit DriftSet(secondsPerStep, _driftIncrement);
    }

    function anchorPrice(uint256 currentPrice) private {
        price = currentPrice;
        // rely on time stamp is ok, no exact time stamp needed
        // solhint-disable-next-line not-rely-on-time
        driftStart = block.timestamp;
    }

    function getPrice() public view returns (uint256) {
        // rely on time stamp is ok, no exact time stamp needed
        // solhint-disable-next-line not-rely-on-time
        return getPriceAtTime(block.timestamp);
    }

    function getPriceAtTime(uint256 timestamp) public view returns (uint256) {
        if (hasDrift()){
            uint256 passed = timestamp - driftStart;
            int256 drifted = int256(passed / timeToDrift) * driftIncrement;
            int256 driftedPrice = int256(price) + drifted;
            if (driftedPrice < 0){
                return 0;
            } else {
                return uint256(driftedPrice);
            }
        } else {
            return price;
        }
    }

    function buy(address from, uint256 paid, bytes calldata ref) internal returns (uint256) {
        if (!hasSetting(BUYING_ENABLED)) {
            revert Brokerbot_BuyingDisabled();
        }
        uint shares = getShares(paid);
        uint costs = getBuyPrice(shares);
        notifyTraded(from, shares, costs, ref);
        if (costs < paid){
            base.safeTransfer(from, paid - costs);
        }
        IERC20(token).safeTransfer(from, shares);
        return shares;
    }

    // Callers must verify that (hasSetting(BUYING_ENABLED) || msg.sender == owner) holds!
    function notifyTraded(address from, uint256 shares, uint256 costs, bytes calldata ref) internal returns (uint256) {
        // disabling the requirement below for efficiency as this always holds once we reach this point
        // require(hasSetting(BUYING_ENABLED) || msg.sender == owner, "buying disabled");
        price = price + (shares * increment);
        emit Trade(token, from, ref, int256(shares), base, costs, 0, getPrice());
        return costs;
    }

    function notifyTrade(address buyer, uint256 shares, uint256 costs, bytes calldata ref) external onlyOwner {
        notifyTraded(buyer, shares, costs, ref);
    }

    function notifyTradeAndTransfer(address buyer, uint256 shares, uint256 costs, bytes calldata ref) public onlyOwner {
        notifyTraded(buyer, shares, costs, ref);
        IERC20(token).safeTransfer(buyer, shares);
    }

    function notifyTrades(address[] calldata buyers, uint256[] calldata shares, uint256[] calldata costs, bytes[] calldata ref) external onlyOwner {
        for (uint i = 0; i < buyers.length; i++) {
            notifyTraded(buyers[i], shares[i], costs[i], ref[i]);
        }
    }

    function notifyTradesAndTransfer(address[] calldata buyers, uint256[] calldata shares, uint256[] calldata costs, bytes[] calldata ref) external onlyOwner {
        for (uint i = 0; i < buyers.length; i++) {
            notifyTradeAndTransfer(buyers[i], shares[i], costs[i], ref[i]);
        }
    }

    /**
     * @notice Payment hub might actually have sent another accepted token, including Ether.
     * @dev Is either called from payment hub or from transferAndCall of the share token (via onTokenTransfer).
     * @param incomingAsset the erc20 address of either base currency or the share token.
     * @param from Who iniciated the sell/buy.
     * @param amount The amount of shares the are sold / The base amount paid to buy sharees.
     * @param ref Reference data blob.
     * @return The amount of shares bought / The amount received for selling the shares. 
     */
    function processIncoming(IERC20 incomingAsset, address from, uint256 amount, bytes calldata ref) public override payable returns (uint256) {
        if (msg.sender != address(incomingAsset) && msg.sender != paymenthub) {
            revert Brokerbot_InvalidSender(msg.sender);
        }
        if(msg.value > 0) {
            emit Received(from, msg.value, amount);
        }
        if (incomingAsset == token){
            return sell(from, amount, ref);
        } else if (incomingAsset == base){
            return buy(from, amount, ref);
        } else {
            revert("invalid token");
        }
    }

    // ERC-677 recipient
    function onTokenTransfer(address from, uint256 amount, bytes calldata ref) external returns (bool) {
        processIncoming(IERC20(msg.sender), from, amount, ref);
        return true;
    }

    function hasSetting(uint256 setting) private view returns (bool) {
        return settings & setting == setting;
    }

    /**
     * ref 0x01 or old format sells shares for base currency.
     * ref 0x02 indicates a sell via bank transfer.
     */
    function isDirectSale(bytes calldata ref) internal pure returns (bool) {
        if (ref.length == 0 || ref.length == 20) {
            return true; // old format
        } else {
            if (ref[0] == bytes1(0x01)){
                return true;
            } else if (ref[0] == bytes1(0x02)) {
                return false;
            } else {
                revert("unknown ref");
            }
        }
    }


    function sell(address recipient, uint256 amount, bytes calldata ref) internal returns (uint256) {
        if (!hasSetting(SELLING_ENABLED)) {
            revert Brokerbot_SellingDisabled();
        }
        uint256 totPrice = getSellPrice(amount);
        price -= amount * increment;
        if (isDirectSale(ref)){
            base.safeTransfer(recipient, totPrice);
        }
        emit Trade(token, recipient, ref, -int256(amount), base, totPrice, 0, getPrice());
        return totPrice;
    }

    function getSellPrice(uint256 shares) public view override returns (uint256) {
        return getPrice(getPrice() - (shares * increment), shares);
    }

    function getBuyPrice(uint256 shares) public view override returns (uint256) {
        return getPrice(getPrice(), shares);
    }

    function getPrice(uint256 lowest, uint256 shares) internal view returns (uint256){
        if (shares == 0) {
            return 0;
        } else {
            uint256 highest = lowest + (shares - 1) * increment;
            return (lowest + highest) * shares / 2;
        }
    }

    function getShares(uint256 money) public view returns (uint256) {
        uint256 currentPrice = getPrice();
        uint256 min = 0;
        uint256 max = money / currentPrice;
        while (min < max){
            uint256 middle = (min + max)/2;
            uint256 totalPrice = getPrice(currentPrice, middle);
            if (money > totalPrice){
                min = middle + 1;
            } else {
                max = middle;
            }
        }
        return min;
    }

    function withdrawEther(address target, uint256 amount) public ownerOrHub() {
        (bool success, ) = payable(target).call{value:amount}("");
        if (!success) {
            revert Brokerbot_WithdrawFailed(target, amount);
        }
        emit Withdrawn(target, amount);
    }

    function withdrawEther(uint256 amount) external ownerOrHub() {
        withdrawEther(msg.sender, amount);
    }

    function approve(address erc20, address who, uint256 amount) external onlyOwner() {
        IERC20(erc20).approve(who, amount);
    }

    function withdraw(IERC20 ercAddress, address to, uint256 amount) external ownerOrHub() {
        ercAddress.safeTransfer(to, amount);
    }

    function setPaymentHub(address hub) external onlyOwner() {
        paymenthub = hub;
        emit PaymentHubUpdate(paymenthub);
    }

    function setSettings(uint256 _settings) public onlyOwner() {
        settings = _settings;
        emit SettingsChange(_settings);
    }

    function setEnabled(bool _buyingEnabled, bool _sellingEnabled) external onlyOwner() {
        uint256 _settings = settings;
        if (_buyingEnabled != hasSetting(BUYING_ENABLED)){
            _settings ^= BUYING_ENABLED;
        }
        if (_sellingEnabled != hasSetting(SELLING_ENABLED)){
            _settings ^= SELLING_ENABLED;
        }
        setSettings(_settings);
    }
    
    modifier ownerOrHub() {
        if (owner != msg.sender && paymenthub != msg.sender) {
            revert Brokerbot_NotAuthorized(msg.sender);
        }
        _;
    }
}
IBrokerbot.sol 40 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "../ERC20/IERC20.sol";
import "../ERC20/IERC20Permit.sol";

interface IBrokerbot {

	/*//////////////////////////////////////////////////////////////
                            Custom errors
  //////////////////////////////////////////////////////////////*/
  error Brokerbot_BuyingDisabled();
  error Brokerbot_SellingDisabled();
  /// Sender(msg.sender) has to be incoming token or paymenthub.
  /// @param sender The msg.sender.
  error Brokerbot_InvalidSender(address sender);
  /// target.call() wasn't successful.
  /// @param target The receiver of the Eth.
  /// @param amount The withdraw amount.
  error Brokerbot_WithdrawFailed(address target, uint256 amount);
  /// Sender(msg.sender) needs to be owner or paymenthub.
  /// @param sender The msg.sender.
  error Brokerbot_NotAuthorized(address sender);

  function paymenthub() external view returns (address);

  function base() external view returns (IERC20);

  function token() external view returns (IERC20Permit);
  
  function settings() external view returns (uint256);

  // @return The amount of shares bought on buying or how much in the base currency is transfered on selling
  function processIncoming(IERC20 token_, address from, uint256 amount, bytes calldata ref) external payable returns (uint256);

  function getBuyPrice(uint256 shares) external view returns (uint256);

  function getSellPrice(uint256 shares) external view returns (uint256);

}
IERC677Receiver.sol 11 lines
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// Given that development on ERC 677 has stalled, we should consider supporting EIP 1363: https://eips.ethereum.org/EIPS/eip-1363
interface IERC677Receiver {

    error IERC677_OnTokenTransferFailed();
    
    function onTokenTransfer(address from, uint256 amount, bytes calldata data) external returns (bool);

}

Read Contract

VERSION 0xffa1ad74 → uint8
base 0x5001f3b5 → address
driftIncrement 0xc7780ab2 → int256
driftStart 0x7ff6c191 → uint256
getBuyPrice 0x08d4db14 → uint256
getPrice 0x98d5fdca → uint256
getPriceAtTime 0xbf13d438 → uint256
getSellPrice 0xba730e53 → uint256
getShares 0x6f7267b7 → uint256
hasDrift 0x8a07d3d0 → bool
increment 0xd09de08a → uint256
owner 0x8da5cb5b → address
paymenthub 0x777adcf0 → address
settings 0xe06174e4 → uint256
timeToDrift 0x84b41fda → uint256
token 0xfc0c546a → address

Write Contract 16 functions

These functions modify contract state and require a wallet transaction to execute.

approve 0xe1f21c67
address erc20
address who
uint256 amount
notifyTrade 0x96199f55
address buyer
uint256 shares
uint256 costs
bytes ref
notifyTradeAndTransfer 0x22ae3e6f
address buyer
uint256 shares
uint256 costs
bytes ref
notifyTrades 0x7fcc1599
address[] buyers
uint256[] shares
uint256[] costs
bytes[] ref
notifyTradesAndTransfer 0x14bf043c
address[] buyers
uint256[] shares
uint256[] costs
bytes[] ref
onTokenTransfer 0xa4c0ed36
address from
uint256 amount
bytes ref
returns: bool
processIncoming 0x69365c52
address incomingAsset
address from
uint256 amount
bytes ref
returns: uint256
setDrift 0xafaefe9e
uint256 secondsPerStep
int256 _driftIncrement
setEnabled 0x5c627935
bool _buyingEnabled
bool _sellingEnabled
setPaymentHub 0xfff4da1f
address hub
setPrice 0xf7d97577
uint256 _price
uint256 _increment
setSettings 0x7cd6a7fd
uint256 _settings
transferOwnership 0xf2fde38b
address newOwner
withdraw 0xd9caed12
address ercAddress
address to
uint256 amount
withdrawEther 0x3bed33ce
uint256 amount
withdrawEther 0x522f6815
address target
uint256 amount

Recent Transactions

This address has 1 on-chain transactions, but only 0.7% of the chain is indexed. Transactions will appear as indexing progresses. View on Etherscan →