POL Price: $0.302578 (-3.88%)

Contract Diff Checker

Contract Name:
OfferFactory

Contract Source Code:

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

}

// 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);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "../ERC20/IERC20.sol";
import "./IOffer.sol";
interface IDraggable is IERC20 {

    /*//////////////////////////////////////////////////////////////
                            Custom errors
    //////////////////////////////////////////////////////////////*/
    /// conversion factor has to be > 0 for this transaction.
    error Draggable_NotBinding();
    /// conversion factor has to be = 0 for this transaction.
    error Draggable_IsBinding();
    /// conversion factor can't be 0 if binding gets deactivated.
    error Draggable_FactorZero();
    /// the reported votes can't be > max voting tokens.
    /// @param maxVotes The max voting tokens.
    /// @param reportedVotes The actual reported votes.
    error Draggable_TooManyVotes(uint256 maxVotes, uint256 reportedVotes);
    /// there is still an open offer that has to be canceled first
    error Draggable_OpenOffer();
    /// For migration the quorum needs to be reached.
    /// @param needed The needed quorum.
    /// @param actual The current yes votes.
    error Draggable_QuorumNotReached(uint256 needed, uint256 actual);
    
    function wrapped() external view returns (IERC20);
    function unwrap(uint256 amount) external;
    function offer() external view returns (IOffer);
    function oracle() external view returns (address);
    function drag(address buyer, IERC20 currency) external;
    function notifyOfferEnded() external;
    function votingPower(address voter) external returns (uint256);
    function totalVotingTokens() external view returns (uint256);
    function notifyVoted(address voter) external;
    function migrate() external;
    function setOracle(address newOracle) external;
    function migrateWithExternalApproval(address successor, uint256 additionalVotes) external;
    function setTerms(string calldata _terms) external;


}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

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

interface IOffer {

	/*//////////////////////////////////////////////////////////////
                            Custom errors
  //////////////////////////////////////////////////////////////*/
	/// Invalid msg.sender.
	/// @param sender The msg.sender of the transaction.
	error Offer_InvalidSender(address sender);
	/// Offer needs to be still open.
	error Offer_AlreadyAccepted();
	/// Offer needs to be not accepted yet.
	error Offer_NotAccepted();
	/// Sender of the offer needs to have needed funds in his account.
	error Offer_NotWellFunded();
	/// New offer not valid. `newPrice` needs to be higher than `oldPrice`.
	/// @param oldPrice Price of the old offer.
	/// @param newPrice Price of the new offer.
	error Offer_OldOfferBetter(uint256 oldPrice, uint256 newPrice);
	/// Voting needs to be still open.
	error Offer_VotingEnded();
	/// Too many (External) reported votes. `reportedVotes` needs to be less or equal to `maxVotes`.
	/// @param maxVotes The max possible votes for the token.
	/// @param reportedVotes The external reported votes + circulating supply of the token.
	error Offer_TooManyVotes(uint256 maxVotes, uint256 reportedVotes);
	/// Competing offer needs to be in the same currency.
	error Offer_OfferInWrongCurrency();
	/// Offer got already killed.
	error Offer_IsKilled();

	/*//////////////////////////////////////////////////////////////
                            Function Interfaces
	//////////////////////////////////////////////////////////////*/

	function makeCompetingOffer(IOffer newOffer) external;

	// if there is a token transfer while an offer is open, the votes get transfered too
	function notifyMoved(address from, address to, uint256 value) external;

	function currency() external view returns (IERC20);

	function price() external view returns (uint256);

	function isWellFunded() external view returns (bool);

	function voteYes() external;

	function voteNo() external;

	function isKilled() external view returns (bool);
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

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

interface IOfferFactory {

	function create(
		bytes32 salt, address buyer, uint256 pricePerShare,	IERC20 currency,	uint256 quorum,	uint256 votePeriod
	) external payable returns (IOffer);
}

/**
* SPDX-License-Identifier: LicenseRef-Aktionariat
*
* MIT License with Automated License Fee Payments
*
* Copyright (c) 2022 Aktionariat AG (aktionariat.com)
*
* Permission is hereby granted to any person obtaining a copy of this software
* and associated documentation files (the "Software"), to deal in the Software
* without restriction, including without limitation the rights to use, copy,
* modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* - The above copyright notice and this permission notice shall be included in
*   all copies or substantial portions of the Software.
* - All automated license fee payments integrated into this and related Software
*   are preserved.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
pragma solidity ^0.8.0;

import "../ERC20/IERC20.sol";
import "./IDraggable.sol";
import "./IOffer.sol";
import "../utils/SafeERC20.sol";
/**
 * @title A public offer to acquire all tokens
 * @author Luzius Meisser, [email protected]
 */
contract Offer is IOffer {
    using SafeERC20 for IERC20;

    address private constant LICENSE_FEE_ADDRESS = 0x29Fe8914e76da5cE2d90De98a64d0055f199d06D;

    uint256 private constant AQUISITION_GRACE_PERIOD = 30 days;     // buyer has thirty days to complete acquisition after voting ends
    
    uint256 private constant BPS_MUL = 10000;           // basis point multiplier to be used with quorum

    uint256 public immutable quorum;                    // Percentage of votes needed to start drag-along process in BPS, i.e. 10'000 = 100%

    IDraggable public immutable token;
    address public immutable buyer;                     // who made the offer
    
    IERC20 override public immutable currency;
    uint256 override public immutable price;            // the price offered per share

    enum Vote { NONE, YES, NO }                         // Used internally, represents not voted yet or yes/no vote.
    mapping (address => Vote) private votes;            // Who votes what
    uint256 public yesVotes;                            // total number of yes votes, including external votes
    uint256 public noVotes;                             // total number of no votes, including external votes
    uint256 public noExternal;                          // number of external no votes reported by oracle
    uint256 public yesExternal;                         // number of external yes votes reported by oracle

    uint256 public immutable voteEnd;                   // end of vote period in block time (seconds after 1.1.1970)

    bool public isKilled;                                      // indicator that contract disabled (replaces selfdestruct)

    event VotesChanged(uint256 yesVotes, uint256 noVotes);
    event OfferCreated(address indexed buyer, IDraggable indexed token, uint256 pricePerShare, IERC20 indexed currency);
    event OfferEnded(address indexed buyer, bool executed, string message);

    // Not checked here, but buyer should make sure it is well funded from the beginning
    constructor(
        address _buyer,
        IDraggable _token,
        uint256 _price,
        IERC20 _currency,
        uint256 _quorum,
        uint256 _votePeriod
    ) 
        payable 
    {
        buyer = _buyer;
        token = _token;
        currency = _currency;
        price = _price;
        quorum = _quorum;
        // rely on time stamp is ok, no exact time stamp needed
        // solhint-disable-next-line not-rely-on-time
        voteEnd = block.timestamp + _votePeriod;
        emit OfferCreated(_buyer, _token, _price, _currency);
        // License Fee to Aktionariat AG, also ensures that offer is serious.
        // Any circumvention of this license fee payment is a violation of the copyright terms.
        payable(LICENSE_FEE_ADDRESS).transfer(5000 ether);
    }

    modifier onlyBuyer {
        _checkSender(buyer);
        _;
    }

    modifier onlyToken {
        _checkSender(address(token));
        _;
    }

    modifier onlyOracle {
        _checkSender(token.oracle());
        _;
    }

    modifier votingOpen {
        if (!isVotingOpen()) {
            revert Offer_VotingEnded();
        }
        _;
    }

    function makeCompetingOffer(IOffer betterOffer) external override onlyToken {
        if (isAccepted()) {
            revert Offer_AlreadyAccepted();
        }
        uint256 newPrice = betterOffer.price();
        if (newPrice <= price) {
            revert Offer_OldOfferBetter(price, newPrice);
        }
        if (currency != betterOffer.currency()) {
            revert Offer_OfferInWrongCurrency();
        }
        if (!betterOffer.isWellFunded()) {
            revert Offer_NotWellFunded();
        }
        kill(false, "replaced");
    }

    function hasExpired() internal view returns (bool) {
        // rely on time stamp is ok, no exact time stamp needed
        // solhint-disable-next-line not-rely-on-time
        return block.timestamp > voteEnd + AQUISITION_GRACE_PERIOD; 
    }

    function contest() external {
        if (hasExpired()) {
            kill(false, "expired");
        } else if (isDeclined()) {
            kill(false, "declined");
        } else if (!isWellFunded()) {
            kill(false, "lack of funds");
        }
    }

    function cancel() external onlyBuyer {
        kill(false, "cancelled");
    }

    function execute() external onlyBuyer {
        if (isKilled) revert Offer_IsKilled();
        if (!isAccepted()) revert Offer_NotAccepted();
        uint256 totalPrice = getTotalPrice();
        currency.safeTransferFrom(buyer, address(token), totalPrice);
        token.drag(buyer, currency);
        kill(true, "success");
    }

    function getTotalPrice() internal view returns (uint256) {
        IERC20 tok = IERC20(address(token));
        return (tok.totalSupply() - tok.balanceOf(buyer)) * price;
    }

    function isWellFunded() public view override returns (bool) {
        uint256 buyerBalance = currency.balanceOf(buyer);
        uint256 totalPrice = getTotalPrice();
        return totalPrice <= buyerBalance;
    }

    function isAccepted() public view returns (bool) {
        if (isVotingOpen()) {
            // is it already clear that more than the quorum requiered will vote yes even though the vote is not over yet?
            return yesVotes * BPS_MUL  >= quorum * token.totalVotingTokens();
        } else {
            // did more than the quorum requiered votes say 'yes'?
            return yesVotes * BPS_MUL >= quorum * (yesVotes + noVotes);
        }
    }

    function isDeclined() public view returns (bool) {
        if (isVotingOpen()) {
            // is it already clear that 25% will vote no even though the vote is not over yet?
            uint256 supply = token.totalVotingTokens();
            return (supply - noVotes) * BPS_MUL < quorum * supply;
        } else {
            // did quorum% of all cast votes say 'no'?
            return BPS_MUL * yesVotes < quorum * (yesVotes + noVotes);
        }
    }

    function notifyMoved(address from, address to, uint256 value) external override onlyToken {
        if (isVotingOpen()) {
            Vote fromVoting = votes[from];
            Vote toVoting = votes[to];
            update(fromVoting, toVoting, value);
        }
    }

    function update(Vote previousVote, Vote newVote, uint256 votes_) internal {
        if (previousVote != newVote) {
            if (previousVote == Vote.NO) {
                noVotes -= votes_;
            } else if (previousVote == Vote.YES) {
                yesVotes -= votes_;
            }
            if (newVote == Vote.NO) {
                noVotes += votes_;
            } else if (newVote == Vote.YES) {
                yesVotes += votes_;
            }
            emit VotesChanged(yesVotes, noVotes);
        }
    }

    function isVotingOpen() public view returns (bool) {
        // rely on time stamp is ok, no exact time stamp needed
        // solhint-disable-next-line not-rely-on-time
        return block.timestamp <= voteEnd && !isKilled;
    }

    /**
     * Function to allow the oracle to report the votes of external votes (e.g. shares tokenized on other blockchains).
     * This functions is idempotent and sets the number of external yes and no votes. So when more votes come in, the
     * oracle should always report the total number of yes and no votes. Abstentions are not counted.
     */
    function reportExternalVotes(uint256 yes, uint256 no) external onlyOracle votingOpen {
        uint256 maxVotes = token.totalVotingTokens();
        uint256 reportingVotes = yes + no + IERC20(address(token)).totalSupply();
        if (reportingVotes > maxVotes) {
            revert Offer_TooManyVotes(maxVotes, reportingVotes);
        }
        // adjust total votes taking into account that the oralce might have reported different counts before
        yesVotes = yesVotes - yesExternal + yes;
        noVotes = noVotes - noExternal + no;
        // remember how the oracle voted in case the oracle later reports updated numbers
        yesExternal = yes;
        noExternal = no;
    }

    function voteYes() external override{
        vote(Vote.YES);
    }

    function voteNo() external override{ 
        vote(Vote.NO);
    }

    function vote(Vote newVote) internal votingOpen {
        Vote previousVote = votes[msg.sender];
        votes[msg.sender] = newVote;
        if(previousVote == Vote.NONE){
            token.notifyVoted(msg.sender);
        }
        update(previousVote, newVote, token.votingPower(msg.sender));
    }

    function hasVotedYes(address voter) external view returns (bool) {
        return votes[voter] == Vote.YES;
    }

    function hasVotedNo(address voter) external view returns (bool) {
        return votes[voter] == Vote.NO;
    }

    function kill(bool executed, string memory message) internal {
        isKilled = true;
        emit OfferEnded(buyer, executed, message);
        token.notifyOfferEnded();
        payable(buyer).call{value:address(this).balance}("");       // buyer is responsible to be able to receive Ether, else he can block cancellation
    }

    /**
     * Checks if msg.sender is an authorized address.
     * @param validSender The authorized address.
     */
    function _checkSender(address validSender) internal view {
        if (msg.sender != validSender) {
            revert Offer_InvalidSender(msg.sender);
        }
    }

}

/**
* SPDX-License-Identifier: LicenseRef-Aktionariat
*
* MIT License with Automated License Fee Payments
*
* Copyright (c) 2022 Aktionariat AG (aktionariat.com)
*
* Permission is hereby granted to any person obtaining a copy of this software
* and associated documentation files (the "Software"), to deal in the Software
* without restriction, including without limitation the rights to use, copy,
* modify, merge, publish, distribute, sublicense, and/or sell copies of the
* Software, and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
*
* - The above copyright notice and this permission notice shall be included in
*   all copies or substantial portions of the Software.
* - All automated license fee payments integrated into this and related Software
*   are preserved.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
pragma solidity ^0.8.0;

import "./Offer.sol";
import "./IOffer.sol";
import "./IOfferFactory.sol";

contract OfferFactory is IOfferFactory{

    // It must be possible to predict the address of the offer so one can pre-fund the allowance.
    function predictOfferAddress(bytes32 salt, address buyer, IDraggable token, uint256 pricePerShare, IERC20 currency, uint256 quorum, uint256 votePeriod) external view returns (address) {
        bytes32 initCodeHash = keccak256(abi.encodePacked(type(Offer).creationCode, abi.encode(buyer, token, pricePerShare, currency, quorum, votePeriod)));
        bytes32 hashResult = keccak256(abi.encodePacked(bytes1(0xff), address(this), salt, initCodeHash));
        return address(uint160(uint256(hashResult)));
    }

    // Do not call directly, msg.sender must be the token to be acquired
    function create(bytes32 salt, address buyer, uint256 pricePerShare, IERC20 currency, uint256 quorum, uint256 votePeriod) override external payable returns (IOffer) {
        IOffer offer = new Offer{value: msg.value, salt: salt}(buyer, IDraggable(msg.sender), pricePerShare, currency, quorum, votePeriod);
        return offer;
    }
}

// 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");
        }
    }
}

// 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;
    }
}

Please enter a contract address above to load the contract details and source code.

Context size (optional):