APE Price: $1.20 (-1.69%)

Contract Diff Checker

Contract Name:
Gambler

Contract Source Code:

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


/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at,
 * consider using {ReentrancyGuardTransient} instead.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuard {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant NOT_ENTERED = 1;
    uint256 private constant ENTERED = 2;

    uint256 private _status;

    /**
     * @dev Unauthorized reentrant call.
     */
    error ReentrancyGuardReentrantCall();

    constructor() {
        _status = NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and making it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        _nonReentrantBefore();
        _;
        _nonReentrantAfter();
    }

    function _nonReentrantBefore() private {
        // On the first call to nonReentrant, _status will be NOT_ENTERED
        if (_status == ENTERED) {
            revert ReentrancyGuardReentrantCall();
        }

        // Any calls to nonReentrant after this point will fail
        _status = ENTERED;
    }

    function _nonReentrantAfter() private {
        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = NOT_ENTERED;
    }

    /**
     * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
     * `nonReentrant` function in the call stack.
     */
    function _reentrancyGuardEntered() internal view returns (bool) {
        return _status == ENTERED;
    }
}

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }
}


/**
 * @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.
 */
abstract contract Ownable is Context {
    address private _owner;

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

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
        _transferOwnership(_msgSender());
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
        _;
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions anymore. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby removing any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}

library SafeMath {
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, "SafeMath: addition overflow");

        return c;
    }
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        return sub(a, b, "SafeMath: subtraction overflow");
    }
    function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b <= a, errorMessage);
        uint256 c = a - b;

        return c;
    }
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
        // benefit is lost if 'b' is also tested.
        // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
        if (a == 0) {
            return 0;
        }

        uint256 c = a * b;
        require(c / a == b, "SafeMath: multiplication overflow");

        return c;
    }

    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        return div(a, b, "SafeMath: division by zero");
    }

    function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        // Solidity only automatically asserts when dividing by 0
        require(b > 0, errorMessage);
        uint256 c = a / b;
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold

        return c;
    }

    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        return mod(a, b, "SafeMath: modulo by zero");
    }

    function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b != 0, errorMessage);
        return a % b;
    }

    function percentageOf(uint a, uint b) internal pure returns (uint256) {
        require(b > 0);
        return a * b / 100;
    }

    function percentageOf10000(uint a, uint b) internal pure returns (uint256) {
        require(b > 0);
        return a * b / 10000;
    }
}

interface IToken {
    function totalSupply() external view returns (uint256);
    function balanceOf(address account) external view returns (uint256);
    function transfer(address recipient, uint256 amount) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint256);
    function approve(address spender, uint256 amount) external returns (bool);
    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) external returns (bool);
    function name() external pure returns (string memory);
    function symbol() external pure returns (string memory);
    function decimals() external pure returns (uint);
    function mint(address to, uint256 amount) external;
    function burn(uint256 amount) external;
    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

interface INft {
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
    function balanceOf(address owner) external view returns (uint256 balance);
    function ownerOf(uint256 tokenId) external view returns (address owner);
    function transferFrom(
        address from,
        address to,
        uint256 tokenId
    ) external;
    function approve(address to, uint256 tokenId) external;
    function setApprovalForAll(address operator, bool _approved) external;
    function getApproved(uint256 tokenId) external view returns (address operator);
    function isApprovedForAll(address owner, address operator) external view returns (bool);
}

library TransferHelper {

    function safeApprove(
        address token,
        address to,
        uint256 value
    ) internal {
        // bytes4(keccak256(bytes('approve(address,uint256)')));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x095ea7b3, to, value));
        require(
            success && (data.length == 0 || abi.decode(data, (bool))),
            'TransferHelper::safeApprove: approve failed'
        );
    }

    function safeTransfer(
        address token,
        address to,
        uint256 value
    ) internal {
        // bytes4(keccak256(bytes('transfer(address,uint256)')));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0xa9059cbb, to, value));
        require(
            success && (data.length == 0 || abi.decode(data, (bool))),
            'TransferHelper::safeTransfer: transfer failed'
        );
    }

    function safeTransferFrom(
        address token,
        address from,
        address to,
        uint256 value
    ) internal {
        // bytes4(keccak256(bytes('transferFrom(address,address,uint256)')));
        (bool success, bytes memory data) = token.call(abi.encodeWithSelector(0x23b872dd, from, to, value));
        require(
            success && (data.length == 0 || abi.decode(data, (bool))),
            'TransferHelper::transferFrom: transferFrom failed'
        );
    }

    function safeTransferETH(address to, uint256 value) internal {
        (bool success, ) = to.call{value: value}(new bytes(0));
        require(success, 'TransferHelper::safeTransferETH: ETH transfer failed');
    }

    function deposit(address _weth, uint256 _value) internal {
        (bool success, ) = _weth.call{value: _value}(new bytes(0));
        require(success, 'TransferHelper::safeTransferETH: ETH transfer failed');
    }

    function withdraw(address _weth, uint256 _value) internal {
        (bool success, bytes memory data) = _weth.call(abi.encodeWithSelector(0x2e1a7d4d, _value));
        require(
            success && (data.length == 0 || abi.decode(data, (bool))),
            'TransferHelper::withdraw: withdraw failed'
        );
    }

}

interface IProtocol {
    function calculate(address erc20, uint256 revenue, bool native) external view returns(uint, address[] memory, uint[] memory);
}

contract BaseProtocol is Ownable, ReentrancyGuard {
    address public feeProtocol;
    bool public paused;

    mapping(address => bool) caller;

    modifier whenNotPaused() {
        require(!paused, "paused");
        _;
    }
    modifier onlyCaller() {
        require(owner() == msg.sender || caller[msg.sender], "not auth");
        _;
    }

    function pause(bool enable) onlyOwner external  {
        paused = enable;
    }

    function addCaller(address[] memory executor) onlyOwner public  {
        for (uint256 i = 0; i < executor.length; i++) {
            caller[executor[i]] = true;
        }
    }

    function removeCaller(address[] memory executor) onlyOwner public  {
        for (uint256 i = 0; i < executor.length; i++) {
            caller[executor[i]] = false;
        }
    }

    function changeProtocol(address newProtocol) onlyOwner public  {
        require(address(0) != feeProtocol, "not zero address");
        feeProtocol = newProtocol;
    }
}

contract Gambler is BaseProtocol {
    using SafeMath for uint;

    bytes32 ROOM_1 = 0x0000000000000000000000000000000000000000000000000000000000000001;
    bytes32 ROOM_2 = 0x0000000000000000000000000000000000000000000000000000000000000002;
    bytes32 ROOM_3 = 0x0000000000000000000000000000000000000000000000000000000000000003;
    bytes32 ROOM_4 = 0x0000000000000000000000000000000000000000000000000000000000000004;
    bytes32 ROOM_5 = 0x0000000000000000000000000000000000000000000000000000000000000005;
    bytes32 ROOM_6 = 0x0000000000000000000000000000000000000000000000000000000000000006;
    uint ROOM_SIZE = 6;

    struct Pool {
        bytes32 uid;
        uint256 price;
        uint256 size;
        bool native;
    }
    
    struct Entry {
        uint256 currentSize;
        uint256 randomness;
    }

    mapping(bytes32 => Pool) rooms;
    mapping(bytes32 => mapping(uint256 => address)) participant;
    mapping(bytes32 => Entry) participantEntry;

    bool public initiated;
    address public baseToken;
    address public baseNFT;
    uint256 public baseBalance;

    event LogWinner(address executor, bytes32 room, address winner, uint amount, bool native, uint date);
    event LogCancel(address executor, bytes32 room, uint date);

    constructor() {
    }

    function init(address[] memory executor, address protocol, address erc20, address erc721, uint256 balance) onlyOwner external  {
        require(!initiated, "already initiated");
        feeProtocol = protocol;
        baseToken = erc20;
        baseNFT = erc721;
        baseBalance = balance;
        addCaller(executor);
        uint decimals = 10 ** IToken(baseToken).decimals();
        
        rooms[ROOM_1] = Pool(ROOM_1, 100 * decimals, 10, false);
        rooms[ROOM_2] = Pool(ROOM_2, 2500 * decimals, 10, false);
        rooms[ROOM_3] = Pool(ROOM_3, 10000 * decimals, 10, false);

        rooms[ROOM_4] = Pool(ROOM_4, 1 ether, 10, true);
        rooms[ROOM_5] = Pool(ROOM_5, 25 ether, 10, true);
        rooms[ROOM_6] = Pool(ROOM_6, 100 ether, 10, true);
        initiated = true;
    }

    function setBaseBalance(uint newBalance) onlyOwner external  {
        baseBalance = newBalance;
    }

    function setRoom(bytes32[] memory ids, uint[] memory price, uint[] memory size) onlyOwner external  {
        for (uint256 i = 0; i < ids.length; i++) {
            require(ids[i] == rooms[ids[i]].uid, "room not found");
            require(participantEntry[ids[i]].currentSize == 0, "already entered");
            rooms[ids[i]] = Pool(ids[i], price[i], size[i], rooms[ids[i]].native);
        }
    }

    function participants(bytes32 roomId) external view returns(Pool memory data,  address erc20, uint256 total, uint256 prize, uint256 currentSize, address[] memory users) {
        data = rooms[roomId];
        erc20 = data.native ? address(0) : baseToken;
        total = data.size * data.price;
        (prize,, ) = IProtocol(feeProtocol).calculate(erc20, total, data.native);
        currentSize = participantEntry[roomId].currentSize;
        users = new address[](currentSize);
        for (uint256 i = 0; i < currentSize; i++) {
            users[i] = participant[roomId][i];
        }
    }

    function getRooms() external view returns(Pool[] memory data) {
        data = new Pool[](ROOM_SIZE);
        for (uint256 i = 0; i < ROOM_SIZE; i++) {
            data[i] = rooms[bytes32(i+1)];
        }
        return data;
    }

    function enroll(bytes32 roomId) nonReentrant whenNotPaused external payable {
        Pool memory pool = rooms[roomId];
        if(pool.native) {
            require(pool.price == msg.value, "insufficient balance");
        } else {
            IToken(baseToken).transferFrom(msg.sender, address(this), pool.price);
        }

        require(INft(baseNFT).balanceOf(msg.sender) >= baseBalance, "not enough nft");
        uint index = participantEntry[roomId].currentSize;
        require(roomId  == pool.uid, "room not found");
        require(index < pool.size, "reached");
        require(participant[roomId][index] == address(0), "already entered");

        participant[roomId][index] = msg.sender;
        participantEntry[roomId].currentSize += 1;
        participantEntry[roomId].randomness = _random(participantEntry[roomId].randomness, block.prevrandao);
    }

    function pickWinner(bytes32 roomId, uint seed) onlyCaller nonReentrant external {
        require(roomId  == rooms[roomId].uid, "room not found");
        require(participantEntry[roomId].currentSize == rooms[roomId].size, "not yet");
        address winner = _randomWinner(roomId, seed);
        uint totalAmount = rooms[roomId].size * rooms[roomId].price;
        uint prize = _split(totalAmount, rooms[roomId].native);

        if(rooms[roomId].native) {
            TransferHelper.safeTransferETH(winner, prize);
        } else {
            TransferHelper.safeTransfer(baseToken, winner, prize);
        }

        for (uint256 i = 0; i < rooms[roomId].size; i++) {
            delete participant[roomId][i];
        }
        delete participantEntry[roomId];
        emit LogWinner(msg.sender, roomId, winner, prize, rooms[roomId].native, block.timestamp);
    }

    function cancel(bytes32 roomId) onlyCaller nonReentrant external {
        require(roomId  == rooms[roomId].uid, "room not found");
        uint currentSize = participantEntry[roomId].currentSize;
        bool native = rooms[roomId].native;
        require(currentSize > 0, "room empty");
        
        uint amount = rooms[roomId].price;
        for (uint256 i = 0; i < currentSize; i++) {
            address to = participant[roomId][i];
            if(native) {
                TransferHelper.safeTransferETH(to, amount);
            } else {
                TransferHelper.safeTransfer(baseToken, to, amount);
            }
            delete participant[roomId][i];
        }
        delete participantEntry[roomId];
        emit LogCancel(msg.sender, roomId, block.timestamp);
    }

    function _split(uint256 revenue, bool native) private returns(uint) {
        address erc20 = native ? address(0) : baseToken;
        (uint balance, address[] memory to, uint[] memory amount) = IProtocol(feeProtocol).calculate(erc20, revenue, native);

        for (uint256 i = 0; i < to.length; i++) {
            if(to[i] == address(0) || amount[i] <= 0) {
                continue;
            }
            if(native) {
                TransferHelper.safeTransferETH(to[i], amount[i]);
            } else {
                TransferHelper.safeTransfer(erc20, to[i], amount[i]);
            }
        }
        return balance;
    }

    function _randomWinner(bytes32 roomId, uint seed) private view returns(address) {
        uint min = 0;
        uint max = rooms[roomId].size - 1;
        uint winnerIndex = _random(seed, participantEntry[roomId].randomness) % (max - min + 1) + min;
        address winner = participant[roomId][winnerIndex];
        return winner;
    }

    function _random(uint seed, uint seed1) private view returns(uint256) {
        return uint(keccak256(abi.encodePacked(seed, seed1, msg.sender, blockhash(block.number - 1), block.timestamp)));
    }

    fallback() external payable {
        revert("not accepted");
    }

    receive() external payable {
        revert("not accepted");
    }
}

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

Context size (optional):