账户
0x1d...4208
0x1d...4208

0x1d...4208

$500
此合同的源代码已经过验证!
合同元数据
编译器
0.8.23+commit.f704f362
语言
Solidity
合同源代码
文件 1 的 7:BestFriend.sol
/// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import { Owned } from "lib/solmate/src/auth/Owned.sol";
import { IERC20 } from "src/interfaces/IERC20.sol";
import { Points } from "src/Points.sol";
import { IBestFriend } from "src/interfaces/IBestFriend.sol";

// BestFriend is a friend you can trust. He is a friend who gives you points for your
// friendship, and he is a friend who will never let you down.
//
// Note the owner is capable of manipulating of determining distribution of points.
//
// Have fun reading it. Hopefully it's bug-free. God bless.
contract BestFriend is Owned(msg.sender), IBestFriend {
    // Info of each user.
    struct UserInfo {
        uint256 lastDepositBlock; // The last block the user deposited/withdrew LP tokens
        uint256 amount; // How many LP tokens the user has provided.
        uint256 rewardDebt; // Reward debt. See explanation below.
            //
            // We do some fancy math here. Basically, any point in time, the amount of pointss
            // entitled to a user but is pending to be distributed is:
            //
            //   pending reward = (user.amount * pool.accpointsPerShare) - user.rewardDebt
            //
            // Whenever a user deposits or withdraws LP tokens to a pool. Here's what happens:
            //   1. The pool's `accpointsPerShare` (and `lastRewardBlock`) gets updated.
            //   2. User receives the pending reward sent to his/her address.
            //   3. User's `amount` gets updated.
            //   4. User's `rewardDebt` gets updated.
    }

    // Info of each pool.
    struct PoolInfo {
        IERC20 lpToken; // Address of LP token contract.
        uint256 lastRewardBlock; // Last block number that pointss distribution occurs.
        uint256 bonusPointsAccrued; // Total number of bonus points that have been received.
        uint256 bonusPointsDistributed; // Bonus points that have been distributed so far.
        uint256 accpointsPerShare; // Accumulated points per share, times wag. See below.
    }

    /*//////////////////////////////////////////////////////////////
                                STORAGE
    //////////////////////////////////////////////////////////////*/

    /// @notice POINTS!!
    Points public immutable points;
    /// @notice Block number when the reward period ends, unless extended
    uint256 public endBlock;
    /// @notice The number of points distributed per block
    uint256 public immutable pointsPerBlock;
    /// @notice Information stored relating to the BunnySwap Pool
    PoolInfo public pool;
    /// @notice Mapping to keep track of stakers
    mapping(address => UserInfo) public userInfo;
    /// @notice Scale factor for fixed point math, with a meme makerdao name
    uint256 public constant wag = 1e12;
    /// @notice The address of RabbitRouter
    address public immutable router;

    /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

    event Deposit(address indexed user, uint256 amount);

    event Withdraw(address indexed user, uint256 amount);

    event EmergencyWithdraw(address indexed user, uint256 amount);

    /*
     * @param Points the points contract for onchain points
     * @param _pool Starting data for the pool, note if this is misconfigured the contract *will* malfunction
     * @param _pointsPerBlock The number of points to distribute per block
     * @param _endBlock The block number at which the reward period ends
     * @param _router The address of the router contract
     */
    constructor(Points _points, PoolInfo memory _pool, uint256 _pointsPerBlock, uint256 _endBlock, address _router) {
        points = _points;
        pointsPerBlock = _pointsPerBlock;
        endBlock = _endBlock;

        pool = _pool;
        router = _router;
    }

    /*
     * @param newEndBlock The new end block for the new reward period
     * @safety Points must be sent to BestFriend in order to fund the extended operation
     */
    function extendEndBlock(uint256 newEndBlock) external onlyOwner {
        endBlock = newEndBlock;
    }

    /*//////////////////////////////////////////////////////////////
                                  VIEW
    //////////////////////////////////////////////////////////////*/

    /*
     * @notice View function to see pending points on frontend.
     * @param _user Address of user.
     * 
     * @return Pending points reward for a given user.
     */
    function pendingpoints(address _user) external view returns (uint256) {
        UserInfo storage user = userInfo[_user];
        uint256 accpointsPerShare = pool.accpointsPerShare;
        uint256 lpSupply = pool.lpToken.balanceOf(address(this));
        uint256 lastEligibleBlock = block.number < endBlock ? block.number : endBlock;
        if (block.number > pool.lastRewardBlock && lpSupply != 0) {
            uint256 multiplier;
            if (lastEligibleBlock <= pool.lastRewardBlock) {
                multiplier = 0;
            } else {
                multiplier = lastEligibleBlock - pool.lastRewardBlock;
            }

            uint256 pointsReward = multiplier * pointsPerBlock + pool.bonusPointsAccrued - pool.bonusPointsDistributed;
            accpointsPerShare = accpointsPerShare + pointsReward * wag / lpSupply;
        }
        return user.amount * accpointsPerShare / wag - user.rewardDebt;
    }

    /*
     * @notice Update reward variables of the given pool to be up-to-date.
     */
    function updatePool() public {
        if (block.number <= pool.lastRewardBlock) {
            return;
        }
        uint256 lpSupply = pool.lpToken.balanceOf(address(this));
        if (lpSupply == 0) {
            pool.lastRewardBlock = block.number;
            return;
        }
        uint256 lastEligibleBlock = block.number < endBlock ? block.number : endBlock;

        uint256 multiplier;
        if (lastEligibleBlock <= pool.lastRewardBlock) {
            multiplier = 0;
        } else {
            multiplier = lastEligibleBlock - pool.lastRewardBlock;
        }

        uint256 pointsReward = multiplier * pointsPerBlock + pool.bonusPointsAccrued - pool.bonusPointsDistributed;
        pool.accpointsPerShare = pool.accpointsPerShare + pointsReward * wag / lpSupply;
        pool.lastRewardBlock = block.number;
        pool.bonusPointsDistributed = pool.bonusPointsAccrued;
    }

    /*
     * @notice Let your best friend hold onto your LP tokens for points
     * @param _amount The amount of LP tokens to deposit
     * @param onBehalfOf The address to deposit the LP tokens for, should be msg.sender unless you're the router   
     */
    function deposit(uint256 _amount, address onBehalfOf) external {
        if (onBehalfOf != msg.sender) {
            require(router == msg.sender, "deposit: not authorized");
        }

        UserInfo storage user = userInfo[onBehalfOf];
        updatePool();
        if (user.amount > 0) {
            uint256 pending = ((user.amount * pool.accpointsPerShare) / wag) - user.rewardDebt;
            points.transfer(onBehalfOf, pending);
        }
        pool.lpToken.transferFrom(address(msg.sender), address(this), _amount);
        user.amount = user.amount + _amount;
        user.rewardDebt = user.amount * pool.accpointsPerShare / wag;
        user.lastDepositBlock = block.number;
        emit Deposit(onBehalfOf, _amount);
    }

    /*
     * @notice Withdraw LP tokens from BestFriend
     * @param _amount The amount of LP tokens to withdraw
     * @param onBehalfOf The address to withdraw the LP tokens for, should be msg.sender unless you're the router
     */
    function withdraw(uint256 _amount, address onBehalfOf) external {
        if (onBehalfOf != msg.sender) {
            require(router == msg.sender, "withdraw: not authorized");
        }
        UserInfo storage user = userInfo[onBehalfOf];

        require(user.amount >= _amount, "withdraw: not good");
        require(user.lastDepositBlock + 5 < block.number, "withdraw: wait");

        updatePool();
        uint256 pending = ((user.amount * pool.accpointsPerShare) / wag) - user.rewardDebt;

        points.transfer(onBehalfOf, pending);
        user.amount = user.amount - _amount;
        user.rewardDebt = (user.amount * pool.accpointsPerShare) / wag;
        pool.lpToken.transfer(msg.sender, _amount);
        emit Withdraw(onBehalfOf, _amount);
    }

    /*
     * @notice Lets a contract (Clubs) add to the rewards pool as a bonus
     * @param _amount The amount of points to add to the bonus pool
     */
    function addBonusPoints(uint256 _amount) external {
        pool.bonusPointsAccrued = pool.bonusPointsAccrued + _amount;
        points.transferFrom(msg.sender, address(this), _amount);
    }

    /*
     * @notice Withdraw without collecting rewards, for emergency situations
     * @note I don't think any MasterChef fork has ever needed to use this, but kept it
     *     as it doesn't hurt.
     */
    function emergencyWithdraw() external {
        UserInfo storage user = userInfo[msg.sender];
        pool.lpToken.transfer(address(msg.sender), user.amount);

        emit EmergencyWithdraw(msg.sender, user.amount);

        user.amount = 0;
        user.rewardDebt = 0;
    }
}
合同源代码
文件 2 的 7:IBestFriend.sol
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.23;

interface IBestFriend {
    function addBonusPoints(uint256 _amount) external;
}
合同源代码
文件 3 的 7:IERC20.sol
/// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

interface IERC20 {
    event Approval(address indexed owner, address indexed spender, uint256 value);
    event Transfer(address indexed from, address indexed to, uint256 value);

    function name() external view returns (string memory);
    function symbol() external view returns (string memory);
    function decimals() external view returns (uint8);
    function totalSupply() external view returns (uint256);
    function balanceOf(address owner) external view returns (uint256);
    function allowance(address owner, address spender) external view returns (uint256);

    function approve(address spender, uint256 value) external returns (bool);
    function transfer(address to, uint256 value) external returns (bool);
    function transferFrom(address from, address to, uint256 value) external returns (bool);
}
合同源代码
文件 4 的 7:IPoints.sol
/// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

interface IPoints {
    /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

    /// @notice By maintaining ERC20 compatibility, transfers and approvals are easier to index
    event Transfer(address indexed _from, address indexed _to, uint256 _value);
    event Approval(address indexed _owner, address indexed _spender, uint256 _value);

    /// @notice Keep track of whether or not a user has claimed their points
    event ClaimedA(address indexed _owner, address indexed recipient, uint256 indexed amount);
    event ClaimedB(address indexed _owner, address indexed recipient, uint256 indexed amount);
    /*//////////////////////////////////////////////////////////////
                               FUNCTIONS
    //////////////////////////////////////////////////////////////*/

    /*
     * @param proof The merkle proof supplied by the user to prove ownership of the claim
     * @param recipient The address that will receive the claimed points
     * @param amount The amount of points claimed
     */
    function claimA(bytes32[] calldata proof, address recipient, uint256 amount, address[] calldata giftRecipients, uint256 giftAmount) external;

    function claimB(bytes32[] calldata proof, address recipient, uint256 amount, address[] calldata giftRecipients, uint256 giftAmount) external;

    /*
     * @notice Unlike ERC20, Points do not return a boolean on transfer
     * @param to The address that will receive the points
     * @param value The amount of points to transfer
     */
    function transfer(address to, uint256 value) external;

    /*
     * @notice Unlike ERC20, Points do not return a boolean on transfer
     * @param from The address that will send the points
     * @param to The address that will receive the points
     * @param value The amount of points to transfer
     */
    function transferFrom(address from, address to, uint256 value) external;

    /*
     * @param spender The address that will be allowed to spend the points
     * @param amount The amount of points the spender will be allowed to spend
     */
    function approve(address spender, uint256 amount) external;

    /*//////////////////////////////////////////////////////////////
                             VIEW FUNCTIONS
    //////////////////////////////////////////////////////////////*/

    /*
     * @param _owner The address that will be allowed to spend the points
     * @param _spender The address that will be allowed to spend the points
     * 
     * @return The amount of points the spender is allowed to spend
     */
    function allowance(address _owner, address _spender) external view returns (uint256);

    /*
     * @param _owner The address that will be checked for points
     * 
     * @return The amount of points the owner has
     */
    function balanceOf(address _owner) external view returns (uint256);

    /*
     * @return The root of the Merkle Tree which stores claims
     */
    function merkleRootA() external view returns (bytes32);

    /*//////////////////////////////////////////////////////////////
                             ACCESS CONTROL
    //////////////////////////////////////////////////////////////*/
    /*
     * @param _address The address in question
     * 
     * @return Whether the contract is allowed to interact with points 
     */
    function isAllowed(address _address) external view returns (bool);
}
合同源代码
文件 5 的 7:MerkleProofLib.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/// @notice Gas optimized verification of proof of inclusion for a leaf in a Merkle tree.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/MerkleProofLib.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/MerkleProofLib.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/MerkleProof.sol)
library MerkleProofLib {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*            MERKLE PROOF VERIFICATION OPERATIONS            */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Returns whether `leaf` exists in the Merkle tree with `root`, given `proof`.
    function verify(bytes32[] memory proof, bytes32 root, bytes32 leaf)
        internal
        pure
        returns (bool isValid)
    {
        /// @solidity memory-safe-assembly
        assembly {
            if mload(proof) {
                // Initialize `offset` to the offset of `proof` elements in memory.
                let offset := add(proof, 0x20)
                // Left shift by 5 is equivalent to multiplying by 0x20.
                let end := add(offset, shl(5, mload(proof)))
                // Iterate over proof elements to compute root hash.
                for {} 1 {} {
                    // Slot of `leaf` in scratch space.
                    // If the condition is true: 0x20, otherwise: 0x00.
                    let scratch := shl(5, gt(leaf, mload(offset)))
                    // Store elements to hash contiguously in scratch space.
                    // Scratch space is 64 bytes (0x00 - 0x3f) and both elements are 32 bytes.
                    mstore(scratch, leaf)
                    mstore(xor(scratch, 0x20), mload(offset))
                    // Reuse `leaf` to store the hash to reduce stack operations.
                    leaf := keccak256(0x00, 0x40)
                    offset := add(offset, 0x20)
                    if iszero(lt(offset, end)) { break }
                }
            }
            isValid := eq(leaf, root)
        }
    }

    /// @dev Returns whether `leaf` exists in the Merkle tree with `root`, given `proof`.
    function verifyCalldata(bytes32[] calldata proof, bytes32 root, bytes32 leaf)
        internal
        pure
        returns (bool isValid)
    {
        /// @solidity memory-safe-assembly
        assembly {
            if proof.length {
                // Left shift by 5 is equivalent to multiplying by 0x20.
                let end := add(proof.offset, shl(5, proof.length))
                // Initialize `offset` to the offset of `proof` in the calldata.
                let offset := proof.offset
                // Iterate over proof elements to compute root hash.
                for {} 1 {} {
                    // Slot of `leaf` in scratch space.
                    // If the condition is true: 0x20, otherwise: 0x00.
                    let scratch := shl(5, gt(leaf, calldataload(offset)))
                    // Store elements to hash contiguously in scratch space.
                    // Scratch space is 64 bytes (0x00 - 0x3f) and both elements are 32 bytes.
                    mstore(scratch, leaf)
                    mstore(xor(scratch, 0x20), calldataload(offset))
                    // Reuse `leaf` to store the hash to reduce stack operations.
                    leaf := keccak256(0x00, 0x40)
                    offset := add(offset, 0x20)
                    if iszero(lt(offset, end)) { break }
                }
            }
            isValid := eq(leaf, root)
        }
    }

    /// @dev Returns whether all `leaves` exist in the Merkle tree with `root`,
    /// given `proof` and `flags`.
    ///
    /// Note:
    /// - Breaking the invariant `flags.length == (leaves.length - 1) + proof.length`
    ///   will always return false.
    /// - The sum of the lengths of `proof` and `leaves` must never overflow.
    /// - Any non-zero word in the `flags` array is treated as true.
    /// - The memory offset of `proof` must be non-zero
    ///   (i.e. `proof` is not pointing to the scratch space).
    function verifyMultiProof(
        bytes32[] memory proof,
        bytes32 root,
        bytes32[] memory leaves,
        bool[] memory flags
    ) internal pure returns (bool isValid) {
        // Rebuilds the root by consuming and producing values on a queue.
        // The queue starts with the `leaves` array, and goes into a `hashes` array.
        // After the process, the last element on the queue is verified
        // to be equal to the `root`.
        //
        // The `flags` array denotes whether the sibling
        // should be popped from the queue (`flag == true`), or
        // should be popped from the `proof` (`flag == false`).
        /// @solidity memory-safe-assembly
        assembly {
            // Cache the lengths of the arrays.
            let leavesLength := mload(leaves)
            let proofLength := mload(proof)
            let flagsLength := mload(flags)

            // Advance the pointers of the arrays to point to the data.
            leaves := add(0x20, leaves)
            proof := add(0x20, proof)
            flags := add(0x20, flags)

            // If the number of flags is correct.
            for {} eq(add(leavesLength, proofLength), add(flagsLength, 1)) {} {
                // For the case where `proof.length + leaves.length == 1`.
                if iszero(flagsLength) {
                    // `isValid = (proof.length == 1 ? proof[0] : leaves[0]) == root`.
                    isValid := eq(mload(xor(leaves, mul(xor(proof, leaves), proofLength))), root)
                    break
                }

                // The required final proof offset if `flagsLength` is not zero, otherwise zero.
                let proofEnd := add(proof, shl(5, proofLength))
                // We can use the free memory space for the queue.
                // We don't need to allocate, since the queue is temporary.
                let hashesFront := mload(0x40)
                // Copy the leaves into the hashes.
                // Sometimes, a little memory expansion costs less than branching.
                // Should cost less, even with a high free memory offset of 0x7d00.
                leavesLength := shl(5, leavesLength)
                for { let i := 0 } iszero(eq(i, leavesLength)) { i := add(i, 0x20) } {
                    mstore(add(hashesFront, i), mload(add(leaves, i)))
                }
                // Compute the back of the hashes.
                let hashesBack := add(hashesFront, leavesLength)
                // This is the end of the memory for the queue.
                // We recycle `flagsLength` to save on stack variables (sometimes save gas).
                flagsLength := add(hashesBack, shl(5, flagsLength))

                for {} 1 {} {
                    // Pop from `hashes`.
                    let a := mload(hashesFront)
                    // Pop from `hashes`.
                    let b := mload(add(hashesFront, 0x20))
                    hashesFront := add(hashesFront, 0x40)

                    // If the flag is false, load the next proof,
                    // else, pops from the queue.
                    if iszero(mload(flags)) {
                        // Loads the next proof.
                        b := mload(proof)
                        proof := add(proof, 0x20)
                        // Unpop from `hashes`.
                        hashesFront := sub(hashesFront, 0x20)
                    }

                    // Advance to the next flag.
                    flags := add(flags, 0x20)

                    // Slot of `a` in scratch space.
                    // If the condition is true: 0x20, otherwise: 0x00.
                    let scratch := shl(5, gt(a, b))
                    // Hash the scratch space and push the result onto the queue.
                    mstore(scratch, a)
                    mstore(xor(scratch, 0x20), b)
                    mstore(hashesBack, keccak256(0x00, 0x40))
                    hashesBack := add(hashesBack, 0x20)
                    if iszero(lt(hashesBack, flagsLength)) { break }
                }
                isValid :=
                    and(
                        // Checks if the last value in the queue is same as the root.
                        eq(mload(sub(hashesBack, 0x20)), root),
                        // And whether all the proofs are used, if required.
                        eq(proofEnd, proof)
                    )
                break
            }
        }
    }

    /// @dev Returns whether all `leaves` exist in the Merkle tree with `root`,
    /// given `proof` and `flags`.
    ///
    /// Note:
    /// - Breaking the invariant `flags.length == (leaves.length - 1) + proof.length`
    ///   will always return false.
    /// - Any non-zero word in the `flags` array is treated as true.
    /// - The calldata offset of `proof` must be non-zero
    ///   (i.e. `proof` is from a regular Solidity function with a 4-byte selector).
    function verifyMultiProofCalldata(
        bytes32[] calldata proof,
        bytes32 root,
        bytes32[] calldata leaves,
        bool[] calldata flags
    ) internal pure returns (bool isValid) {
        // Rebuilds the root by consuming and producing values on a queue.
        // The queue starts with the `leaves` array, and goes into a `hashes` array.
        // After the process, the last element on the queue is verified
        // to be equal to the `root`.
        //
        // The `flags` array denotes whether the sibling
        // should be popped from the queue (`flag == true`), or
        // should be popped from the `proof` (`flag == false`).
        /// @solidity memory-safe-assembly
        assembly {
            // If the number of flags is correct.
            for {} eq(add(leaves.length, proof.length), add(flags.length, 1)) {} {
                // For the case where `proof.length + leaves.length == 1`.
                if iszero(flags.length) {
                    // `isValid = (proof.length == 1 ? proof[0] : leaves[0]) == root`.
                    // forgefmt: disable-next-item
                    isValid := eq(
                        calldataload(
                            xor(leaves.offset, mul(xor(proof.offset, leaves.offset), proof.length))
                        ),
                        root
                    )
                    break
                }

                // The required final proof offset if `flagsLength` is not zero, otherwise zero.
                let proofEnd := add(proof.offset, shl(5, proof.length))
                // We can use the free memory space for the queue.
                // We don't need to allocate, since the queue is temporary.
                let hashesFront := mload(0x40)
                // Copy the leaves into the hashes.
                // Sometimes, a little memory expansion costs less than branching.
                // Should cost less, even with a high free memory offset of 0x7d00.
                calldatacopy(hashesFront, leaves.offset, shl(5, leaves.length))
                // Compute the back of the hashes.
                let hashesBack := add(hashesFront, shl(5, leaves.length))
                // This is the end of the memory for the queue.
                // We recycle `flagsLength` to save on stack variables (sometimes save gas).
                flags.length := add(hashesBack, shl(5, flags.length))

                // We don't need to make a copy of `proof.offset` or `flags.offset`,
                // as they are pass-by-value (this trick may not always save gas).

                for {} 1 {} {
                    // Pop from `hashes`.
                    let a := mload(hashesFront)
                    // Pop from `hashes`.
                    let b := mload(add(hashesFront, 0x20))
                    hashesFront := add(hashesFront, 0x40)

                    // If the flag is false, load the next proof,
                    // else, pops from the queue.
                    if iszero(calldataload(flags.offset)) {
                        // Loads the next proof.
                        b := calldataload(proof.offset)
                        proof.offset := add(proof.offset, 0x20)
                        // Unpop from `hashes`.
                        hashesFront := sub(hashesFront, 0x20)
                    }

                    // Advance to the next flag offset.
                    flags.offset := add(flags.offset, 0x20)

                    // Slot of `a` in scratch space.
                    // If the condition is true: 0x20, otherwise: 0x00.
                    let scratch := shl(5, gt(a, b))
                    // Hash the scratch space and push the result onto the queue.
                    mstore(scratch, a)
                    mstore(xor(scratch, 0x20), b)
                    mstore(hashesBack, keccak256(0x00, 0x40))
                    hashesBack := add(hashesBack, 0x20)
                    if iszero(lt(hashesBack, flags.length)) { break }
                }
                isValid :=
                    and(
                        // Checks if the last value in the queue is same as the root.
                        eq(mload(sub(hashesBack, 0x20)), root),
                        // And whether all the proofs are used, if required.
                        eq(proofEnd, proof.offset)
                    )
                break
            }
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                   EMPTY CALLDATA HELPERS                   */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Returns an empty calldata bytes32 array.
    function emptyProof() internal pure returns (bytes32[] calldata proof) {
        /// @solidity memory-safe-assembly
        assembly {
            proof.length := 0
        }
    }

    /// @dev Returns an empty calldata bytes32 array.
    function emptyLeaves() internal pure returns (bytes32[] calldata leaves) {
        /// @solidity memory-safe-assembly
        assembly {
            leaves.length := 0
        }
    }

    /// @dev Returns an empty calldata bool array.
    function emptyFlags() internal pure returns (bool[] calldata flags) {
        /// @solidity memory-safe-assembly
        assembly {
            flags.length := 0
        }
    }
}
合同源代码
文件 6 的 7:Owned.sol
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Simple single owner authorization mixin.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/auth/Owned.sol)
abstract contract Owned {
    /*//////////////////////////////////////////////////////////////
                                 EVENTS
    //////////////////////////////////////////////////////////////*/

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

    /*//////////////////////////////////////////////////////////////
                            OWNERSHIP STORAGE
    //////////////////////////////////////////////////////////////*/

    address public owner;

    modifier onlyOwner() virtual {
        require(msg.sender == owner, "UNAUTHORIZED");

        _;
    }

    /*//////////////////////////////////////////////////////////////
                               CONSTRUCTOR
    //////////////////////////////////////////////////////////////*/

    constructor(address _owner) {
        owner = _owner;

        emit OwnershipTransferred(address(0), _owner);
    }

    /*//////////////////////////////////////////////////////////////
                             OWNERSHIP LOGIC
    //////////////////////////////////////////////////////////////*/

    function transferOwnership(address newOwner) public virtual onlyOwner {
        owner = newOwner;

        emit OwnershipTransferred(msg.sender, newOwner);
    }
}
合同源代码
文件 7 的 7:Points.sol
/// SPDX-License-Identifier: MIT
pragma solidity 0.8.23;

import { MerkleProofLib } from "lib/solady/src/utils/MerkleProofLib.sol";
import { Owned } from "lib/solmate/src/auth/Owned.sol";
import { IPoints } from "./interfaces/IPoints.sol";

/// @title Points
/// @author CopyPaste
/// @notice A Standard Implementation of Points for FriendTechV2
contract Points is Owned(msg.sender), IPoints {
    /*//////////////////////////////////////////////////////////////
                                 STORAGE
    //////////////////////////////////////////////////////////////*/
    /// Merkle Root for the point claims
    bytes32 public immutable merkleRootA;
    bytes32 public immutable merkleRootB;

    mapping(address user => uint256 balance) public balanceOf;

    mapping(address owner => mapping(address spender => uint256 amount)) public allowance;

    mapping(address user => bool hasClaimedA) public claimedA;
    mapping(address user => bool hasClaimedB) public claimedB;

    /// The totalSupply of points
    uint256 public totalSupply;

    /*//////////////////////////////////////////////////////////////
                                 CONSTRUCTOR
    //////////////////////////////////////////////////////////////*/
    /*
     * @param _merkleRootA The merkle root for first the point claims
     * @param _merkleRootB The merkle root for second the point claims
     */
    constructor(bytes32 _merkleRootA, bytes32 _merkleRootB) {
        merkleRootA = _merkleRootA;
        merkleRootB = _merkleRootB;
    }

    /*//////////////////////////////////////////////////////////////
                                METADATA
    //////////////////////////////////////////////////////////////*/

    string public name = "FRIEND";
    string public symbol = "FRIEND";
    uint8 public decimals = 18;

    /*//////////////////////////////////////////////////////////////
                             ACCESS CONTROL
    //////////////////////////////////////////////////////////////*/
    mapping(address _contract => bool isWhitelisted) public isAllowed;
    mapping(bytes4 functionSignature => bool canBeCalled) public isOpen;

    modifier onlyWhitelisted() {
        require(isAllowed[msg.sender] || isOpen[msg.sig], "WHITELIST");
        _;
    }

    /*
     * @param _contract The contract address allowed to interact with points
     * @param toggle The toggle for whether or not the contract is allowed
     */
    function toggleContract(address _contract, bool toggle) external onlyOwner {
        isAllowed[_contract] = toggle;
    }

    /*
     * @param function The function to open to the public
     * @param toggle The toggle for whether or not the contract is allowed
     */
    function toggleFunction(bytes4 functionSig, bool toggle) external onlyOwner {
        isOpen[functionSig] = toggle;
    }

    /*
     * @param to address to recieve the points
     * @param amount The amount of points to mint
     */
    function mint(address to, uint256 amount) external onlyOwner {
        totalSupply += amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(address(0), to, amount);
    }

    /*//////////////////////////////////////////////////////////////
                             TRANSFER LOGIC
    //////////////////////////////////////////////////////////////*/
    /*
     * @param proof The merkle proof supplied by the user to prove ownership of the claim
     * @param recipient The address that will receive the claimed points
     * @param amount The amount of points claimed
     */
    function claimA(bytes32[] calldata proof, address recipient, uint256 amount, address[] calldata giftRecipients, uint256 giftAmount) external {
        require(!claimedA[msg.sender], "Points: already claimed");
        require(giftAmount <= amount, "Points: gift amount exceeds claim"); // Would underflow anyway but you can never be too safe
        bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(msg.sender, amount))));
        require(MerkleProofLib.verifyCalldata(proof, merkleRootA, leaf), "Points: invalid proof");

        claimedA[msg.sender] = true;

        if (giftAmount != 0) {
            uint256 perUser = giftAmount / giftRecipients.length;

            amount -= giftAmount;

            for (uint256 i = 0; i < giftRecipients.length; ) {
                balanceOf[giftRecipients[i]] += perUser;

                unchecked {
                    ++i;
                }
            }
        }

        totalSupply += amount + giftAmount;

        // Mint the points to the recipient
        unchecked {
            balanceOf[recipient] += amount;
        }

        emit ClaimedA(msg.sender, recipient, amount);
    }

    /*
     * @param proof The merkle proof supplied by the user to prove ownership of the claim
     * @param recipient The address that will receive the claimed points
     * @param amount The amount of points claimed
     */
    function claimB(bytes32[] calldata proof, address recipient, uint256 amount, address[] calldata giftRecipients, uint256 giftAmount) external {
        require(!claimedB[msg.sender], "Points: already claimed");
        require(giftAmount <= amount, "Points: gift amount exceeds claim"); // Would underflow anyway but you can never be too safe

        bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(msg.sender, amount))));
        require(MerkleProofLib.verifyCalldata(proof, merkleRootB, leaf), "Points: invalid proof");

        claimedB[msg.sender] = true;

        if (giftAmount != 0) {
            uint256 perUser = giftAmount / giftRecipients.length;

            amount -= giftAmount;

            for (uint256 i = 0; i < giftRecipients.length; ) {
                balanceOf[giftRecipients[i]] += perUser;

                unchecked {
                    ++i;
                }
            }
        }

        totalSupply += amount + giftAmount;

        // Mint the points to the recipient
        unchecked {
            balanceOf[recipient] += amount;
        }

        emit ClaimedB(msg.sender, recipient, amount);
    }

    /*
     * @notice Unlike ERC20, Points do not return a boolean on transfer
     * @param to The address that will receive the points
     * @param amount The amount of points to transfer
     */
    function transfer(address to, uint256 amount) external onlyWhitelisted {
        balanceOf[msg.sender] -= amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(msg.sender, to, amount);
    }

    /*
     * @notice Unlike ERC20, Points do not return a boolean on transfer
     * @param from The address that will send the points
     * @param to The address that will receive the points
     * @param amount The amount of points to transfer
     */
    function transferFrom(address from, address to, uint256 amount) external onlyWhitelisted {
        uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.

        if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;

        balanceOf[from] -= amount;

        // Cannot overflow because the sum of all user
        // balances can't exceed the max uint256 value.
        unchecked {
            balanceOf[to] += amount;
        }

        emit Transfer(from, to, amount);
    }

    /*
     * @param spender The address that will be allowed to spend the points
     * @param amount The amount of points the spender will be allowed to spend
     */
    function approve(address spender, uint256 amount) external onlyWhitelisted {
        allowance[msg.sender][spender] = amount;

        emit Approval(msg.sender, spender, amount);
    }
}
设置
{
  "compilationTarget": {
    "src/BestFriend.sol": "BestFriend"
  },
  "evmVersion": "paris",
  "libraries": {},
  "metadata": {
    "appendCBOR": false,
    "bytecodeHash": "none"
  },
  "optimizer": {
    "enabled": true,
    "runs": 1000000
  },
  "remappings": [
    ":ds-test/=lib/forge-std/lib/ds-test/src/",
    ":forge-std/=lib/forge-std/src/",
    ":murky/=lib/murky/",
    ":openzeppelin-contracts/=lib/murky/lib/openzeppelin-contracts/",
    ":solady/=lib/solady/src/",
    ":solmate/=lib/solmate/src/"
  ]
}
ABI
[{"inputs":[{"internalType":"contract Points","name":"_points","type":"address"},{"components":[{"internalType":"contract IERC20","name":"lpToken","type":"address"},{"internalType":"uint256","name":"lastRewardBlock","type":"uint256"},{"internalType":"uint256","name":"bonusPointsAccrued","type":"uint256"},{"internalType":"uint256","name":"bonusPointsDistributed","type":"uint256"},{"internalType":"uint256","name":"accpointsPerShare","type":"uint256"}],"internalType":"struct BestFriend.PoolInfo","name":"_pool","type":"tuple"},{"internalType":"uint256","name":"_pointsPerBlock","type":"uint256"},{"internalType":"uint256","name":"_endBlock","type":"uint256"},{"internalType":"address","name":"_router","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"EmergencyWithdraw","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Withdraw","type":"event"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"addBonusPoints","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"address","name":"onBehalfOf","type":"address"}],"name":"deposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"emergencyWithdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"endBlock","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"newEndBlock","type":"uint256"}],"name":"extendEndBlock","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_user","type":"address"}],"name":"pendingpoints","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"points","outputs":[{"internalType":"contract Points","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pointsPerBlock","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pool","outputs":[{"internalType":"contract IERC20","name":"lpToken","type":"address"},{"internalType":"uint256","name":"lastRewardBlock","type":"uint256"},{"internalType":"uint256","name":"bonusPointsAccrued","type":"uint256"},{"internalType":"uint256","name":"bonusPointsDistributed","type":"uint256"},{"internalType":"uint256","name":"accpointsPerShare","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"router","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"updatePool","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"userInfo","outputs":[{"internalType":"uint256","name":"lastDepositBlock","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"rewardDebt","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"wag","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"address","name":"onBehalfOf","type":"address"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"}]