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