// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @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);
/**
* @dev Returns the value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 value) 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 a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: 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 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 value) external returns (bool);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/ReentrancyGuard.sol)
pragma solidity ^0.8.20;
/**
* @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 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;
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
contract Yolatility is ReentrancyGuard {
address public owner;
address public teamSplit;
IERC20 public yoyoToken;
address[] public admins;
address public burnAddress = 0x000000000000000000000000000000000000dEaD;
uint256 public nextRoundIndex;
uint256 public lastCompletedRoundIndex;
mapping(uint256 => RoundConfigChunkA) public roundsA;
mapping(uint256 => RoundConfigChunkB) roundsB;
mapping(uint256 => NftContracts) roundsNftContracts;
struct NftContracts {
address winnerAddress;
address betterAddress;
}
struct RoundConfigChunkA {
uint256 totalBets;
uint256 totalAmount;
uint256 betAmount;
uint256 startTime;
uint256 endTime;
bool processingComplete;
uint256 bestScore;
}
struct RoundConfigChunkB {
// We have to jump through some hoops to be sure we can iterate through the
// bets without running out of read gas. These vars track all that.
uint256 currentIndex;
uint256 finalHigh;
uint256 finalLow;
bool finalHighLowSet;
bool winningsDistributed;
// TODO: pull this from the previous round? Or set manually per round.
uint256 lastHigh;
uint256 lastLow;
string lastWinner;
}
// Map users to bet objects
// Can't stash this in the Round struct since then we can't use them in memory.
mapping(uint256 => mapping(address => Bet[])) public userBets;
mapping(uint256 => address[]) public betPlacerAddresses;
mapping(uint256 => address[]) public bestPlayers;
struct Bet {
address player;
uint256 high;
uint256 low;
uint256 amount;
uint256 submittedAt;
}
error BettingNotOpen();
error BetAmountIncorrect();
error MustBeGreaterThanZero();
error MustBeHigherThanLow();
error NotAuthorized();
error FinalHighLowNotSet();
error ProcessingComplete();
error ProcessingNotComplete();
error WinningsAlreadyDistributed();
error WeirdNoWinners();
error NotYoyoEnoughApproved();
error OwnableUnauthorizedAccount(address account);
error OutOfBounds();
event BetPlaced(
uint256 indexed round,
address indexed better,
uint256 high,
uint256 low,
uint256 amount
);
event FinalHighLowSet(uint256 indexed round, uint256 high, uint256 low);
event WinnersDeclared(
uint256 indexed round,
address[] winners,
uint256 score
);
event TokensDistributed(uint256 indexed round);
struct Config {
uint256 totalBets;
uint256 totalAmount;
uint256 lastCompletedRoundIndex;
uint256 startTime;
uint256 endTime;
uint256 lastHigh;
uint256 lastLow;
uint256 finalHigh;
uint256 finalLow;
string lastWinner;
bool isBettingOpen;
}
constructor(address _teamSplit, address _yoyoTokenAddress) {
owner = msg.sender;
teamSplit = _teamSplit;
yoyoToken = IERC20(_yoyoTokenAddress);
}
modifier bettingOpen(uint256 round) {
RoundConfigChunkA memory r = roundsA[round];
if (block.timestamp < r.startTime) revert BettingNotOpen();
if (block.timestamp > r.endTime) revert BettingNotOpen();
_;
}
modifier onlyOwner() {
if (msg.sender != owner) revert NotAuthorized();
_;
}
modifier onlyAdmins() {
if (msg.sender == owner) {
_;
return;
}
bool canMint = false;
for (uint256 i = 0; i < admins.length; i++) {
if (admins[i] == msg.sender) {
canMint = true;
break;
}
}
if (!canMint) revert OwnableUnauthorizedAccount(msg.sender);
_;
}
function initRound(
uint256 _betAmount,
uint256 _startTime,
uint256 _endTime,
string memory lastWinner,
uint256 lastHigh,
uint256 lastLow
) external onlyAdmins {
roundsA[nextRoundIndex] = RoundConfigChunkA({
totalBets: 0,
totalAmount: 0,
betAmount: _betAmount,
startTime: _startTime,
endTime: _endTime,
processingComplete: false,
bestScore: type(uint256).max
});
roundsB[nextRoundIndex] = RoundConfigChunkB({
currentIndex: 0,
finalHigh: 0,
finalLow: 0,
finalHighLowSet: false,
winningsDistributed: false,
lastHigh: lastHigh,
lastLow: lastLow,
lastWinner: lastWinner
});
roundsNftContracts[nextRoundIndex] = NftContracts({
winnerAddress: address(0),
betterAddress: address(0)
});
nextRoundIndex++;
}
function totalBets(uint256 round) public view returns (uint256) {
return roundsA[round].totalBets;
}
function nftContractsForRound(
uint256 round
) public view returns (NftContracts memory) {
return roundsNftContracts[round];
}
function updateNftContractsForRound(
uint256 round,
address winnerAddress,
address betterAddress
) public onlyAdmins {
roundsNftContracts[round].winnerAddress = winnerAddress;
roundsNftContracts[round].betterAddress = betterAddress;
}
function totalAmount(uint256 round) public view returns (uint256) {
return roundsA[round].totalAmount;
}
function currentIndex(uint256 round) public view returns (uint256) {
return roundsB[round].currentIndex;
}
function processingComplete(uint256 round) public view returns (bool) {
return roundsA[round].processingComplete;
}
function betPlacerAddressesForRound(
uint256 round,
uint256 index
) public view returns (address) {
return betPlacerAddresses[round][index];
}
function changeBetAmount(
uint256 round,
uint256 newAmount
) public onlyAdmins {
roundsA[round].betAmount = newAmount;
}
function setFinalHighLow(
uint256 round,
uint256 high,
uint256 low
) public onlyAdmins {
RoundConfigChunkB memory rB = roundsB[round];
rB.finalHigh = high;
rB.finalLow = low;
rB.finalHighLowSet = true;
roundsB[round] = rB;
emit FinalHighLowSet(round, high, low);
}
function setLastWinner(
uint256 round,
string memory winner
) public onlyAdmins {
roundsB[round].lastWinner = winner;
}
function setLastWinnerForLatestRound(
string memory winner
) public onlyAdmins {
uint256 round = nextRoundIndex - 1;
roundsB[round].lastWinner = winner;
}
// Some tokens disallow zero address for example, don't want to get stuck in an unburnable token error situation.
function setBurnAddress(address _burnAddress) public onlyOwner {
burnAddress = _burnAddress;
}
function addToAdmins(address _admin) external onlyAdmins {
admins.push(_admin);
}
function removeFromAdmins(address _admin) external onlyAdmins {
for (uint256 i = 0; i < admins.length; i++) {
if (admins[i] == _admin) {
admins[i] = admins[admins.length - 1];
admins.pop();
break;
}
}
}
// Return all the detials we need for display in a fat struct, so
// we can easily access the values by name in the frontend.
function getConfig(uint256 round) external view returns (Config memory c) {
RoundConfigChunkA memory rA = roundsA[round];
RoundConfigChunkB memory rB = roundsB[round];
return
Config({
totalBets: rA.totalBets,
totalAmount: rA.totalAmount,
lastCompletedRoundIndex: lastCompletedRoundIndex,
startTime: rA.startTime,
endTime: rA.endTime,
lastHigh: rB.lastHigh,
lastLow: rB.lastLow,
finalHigh: rB.finalHigh,
finalLow: rB.finalLow,
lastWinner: rB.lastWinner,
isBettingOpen: isBettingOpen(round)
});
}
function isBettingOpen(uint256 round) public view returns (bool) {
return
block.timestamp >= roundsA[round].startTime &&
block.timestamp <= roundsA[round].endTime;
}
function setLastHighLow(
uint256 round,
uint256 high,
uint256 low
) public onlyAdmins {
roundsB[round].lastHigh = high;
roundsB[round].lastLow = low;
}
function setLastHighLowForLatestRound(
uint256 high,
uint256 low
) public onlyAdmins {
uint256 round = nextRoundIndex - 1;
roundsB[round].lastHigh = high;
roundsB[round].lastLow = low;
}
function setTeamSplit(address _teamSplit) public onlyOwner {
teamSplit = _teamSplit;
}
function totalUniqueBetters(uint256 round) public view returns (uint256) {
return betPlacerAddresses[round].length;
}
function totalBetsForBetter(
uint256 round,
address better
) public view returns (uint256) {
return userBets[round][better].length;
}
function bestPlayersLength(uint256 round) public view returns (uint256) {
return bestPlayers[round].length;
}
function placeBet(
uint256 round,
uint256 high,
uint256 low
) public bettingOpen(round) nonReentrant {
if (high <= low) revert MustBeHigherThanLow();
// Check the approval level:
uint256 allowance = yoyoToken.allowance(msg.sender, address(this));
if (allowance < roundsA[round].betAmount) {
revert NotYoyoEnoughApproved();
}
uint256 yoyoBalance = yoyoToken.balanceOf(msg.sender);
if (yoyoBalance < roundsA[round].betAmount) {
revert MustBeGreaterThanZero();
}
// Transfer the bet amount to the contract
yoyoToken.transferFrom(
msg.sender,
address(this),
roundsA[round].betAmount
);
Bet memory newBet = Bet({
player: msg.sender,
high: high,
low: low,
amount: roundsA[round].betAmount,
submittedAt: block.timestamp
});
roundsA[round].totalBets++;
roundsA[round].totalAmount += roundsA[round].betAmount;
userBets[round][msg.sender].push(newBet);
// Don't push this new address if it already exists
if (userBets[round][msg.sender].length == 1) {
betPlacerAddresses[round].push(msg.sender);
}
emit BetPlaced(round, msg.sender, high, low, roundsA[round].betAmount);
}
function calculateScore(
uint256 round,
uint256 high,
uint256 low
) public view returns (uint256) {
RoundConfigChunkB memory r = roundsB[round];
if (!r.finalHighLowSet) {
revert FinalHighLowNotSet();
}
uint256 highDiff = (high > r.finalHigh)
? (high - r.finalHigh)
: (r.finalHigh - high);
uint256 lowDiff = (low > r.finalLow)
? (low - r.finalLow)
: (r.finalLow - low);
return highDiff + lowDiff;
}
function processBets(
uint256 round,
uint256 startIndex,
uint256 numBets
) public onlyAdmins {
if (!roundsB[round].finalHighLowSet) {
revert FinalHighLowNotSet();
}
if (roundsA[round].processingComplete) {
revert ProcessingComplete();
}
if (startIndex >= betPlacerAddresses[round].length) {
revert OutOfBounds();
}
uint256 i = startIndex;
for (
;
i < startIndex + numBets && i < betPlacerAddresses[round].length;
i++
) {
address player = betPlacerAddresses[round][i];
Bet[] storage bets = userBets[round][player];
for (uint256 j = 0; j < bets.length; j++) {
Bet storage bet = bets[j];
uint256 score = calculateScore(round, bet.high, bet.low);
if (score < roundsA[round].bestScore) {
roundsA[round].bestScore = score;
bestPlayers[round] = [player];
} else if (score == roundsA[round].bestScore) {
bestPlayers[round].push(player);
}
}
}
roundsB[round].currentIndex = i;
if (roundsB[round].currentIndex >= betPlacerAddresses[round].length) {
roundsA[round].processingComplete = true;
}
}
function finalizeWinners(uint256 round) public onlyAdmins {
RoundConfigChunkA memory r = roundsA[round];
if (!r.processingComplete) {
revert ProcessingNotComplete();
}
emit WinnersDeclared(round, bestPlayers[round], r.bestScore);
}
function distributeWinnings(uint256 round) public onlyAdmins nonReentrant {
RoundConfigChunkA memory r = roundsA[round];
if (!r.processingComplete) {
revert ProcessingNotComplete();
}
if (roundsB[round].winningsDistributed) {
revert WinningsAlreadyDistributed();
}
if (bestPlayers[round].length == 0) {
// If no winners, send the entire amount to the teamSplit address
yoyoToken.transfer(teamSplit, r.totalAmount);
return;
}
// Calculate 10% to burn
uint256 amountToBurn = r.totalAmount / 10;
// Calculate 20% for the team
uint256 amountForTeam = r.totalAmount / 5; // 20% is 1/5 of the total
// Calculate the remaining amount to distribute
uint256 amountToDistribute = r.totalAmount -
amountToBurn -
amountForTeam;
// Burn 10% of the total amount
yoyoToken.transfer(burnAddress, amountToBurn);
// Send 20% to the teamSplit address
yoyoToken.transfer(teamSplit, amountForTeam);
// Calculate the amount per winner and any remainder
uint256 amountPerWinner = amountToDistribute /
bestPlayers[round].length;
uint256 remainder = amountToDistribute % bestPlayers[round].length;
roundsB[round].winningsDistributed = true;
// Distribute the remaining amount to the winners
for (uint256 i = 0; i < bestPlayers[round].length; i++) {
uint256 amountToSend = amountPerWinner;
if (i == 0) {
// The first winner gets any remainder
amountToSend += remainder;
}
yoyoToken.transfer(bestPlayers[round][i], amountToSend);
}
roundsA[round] = r;
lastCompletedRoundIndex = round;
emit TokensDistributed(round);
}
function withdraw() public onlyAdmins {
payable(teamSplit).transfer(address(this).balance);
}
receive() external payable {}
}
{
"compilationTarget": {
"src/Yolatility.sol": "Yolatility"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": [
":@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
":ds-test/=lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/src/",
":erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/",
":forge-std/=lib/forge-std/src/",
":openzeppelin-contracts/=lib/openzeppelin-contracts/"
]
}
[{"inputs":[{"internalType":"address","name":"_teamSplit","type":"address"},{"internalType":"address","name":"_yoyoTokenAddress","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"BetAmountIncorrect","type":"error"},{"inputs":[],"name":"BettingNotOpen","type":"error"},{"inputs":[],"name":"FinalHighLowNotSet","type":"error"},{"inputs":[],"name":"MustBeGreaterThanZero","type":"error"},{"inputs":[],"name":"MustBeHigherThanLow","type":"error"},{"inputs":[],"name":"NotAuthorized","type":"error"},{"inputs":[],"name":"NotYoyoEnoughApproved","type":"error"},{"inputs":[],"name":"OutOfBounds","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"OwnableUnauthorizedAccount","type":"error"},{"inputs":[],"name":"ProcessingComplete","type":"error"},{"inputs":[],"name":"ProcessingNotComplete","type":"error"},{"inputs":[],"name":"ReentrancyGuardReentrantCall","type":"error"},{"inputs":[],"name":"WeirdNoWinners","type":"error"},{"inputs":[],"name":"WinningsAlreadyDistributed","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"round","type":"uint256"},{"indexed":true,"internalType":"address","name":"better","type":"address"},{"indexed":false,"internalType":"uint256","name":"high","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"low","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"BetPlaced","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"round","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"high","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"low","type":"uint256"}],"name":"FinalHighLowSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"round","type":"uint256"}],"name":"TokensDistributed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"round","type":"uint256"},{"indexed":false,"internalType":"address[]","name":"winners","type":"address[]"},{"indexed":false,"internalType":"uint256","name":"score","type":"uint256"}],"name":"WinnersDeclared","type":"event"},{"inputs":[{"internalType":"address","name":"_admin","type":"address"}],"name":"addToAdmins","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"admins","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"bestPlayers","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"round","type":"uint256"}],"name":"bestPlayersLength","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"betPlacerAddresses","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"round","type":"uint256"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"betPlacerAddressesForRound","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"burnAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"round","type":"uint256"},{"internalType":"uint256","name":"high","type":"uint256"},{"internalType":"uint256","name":"low","type":"uint256"}],"name":"calculateScore","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"round","type":"uint256"},{"internalType":"uint256","name":"newAmount","type":"uint256"}],"name":"changeBetAmount","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"round","type":"uint256"}],"name":"currentIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"round","type":"uint256"}],"name":"distributeWinnings","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"round","type":"uint256"}],"name":"finalizeWinners","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"round","type":"uint256"}],"name":"getConfig","outputs":[{"components":[{"internalType":"uint256","name":"totalBets","type":"uint256"},{"internalType":"uint256","name":"totalAmount","type":"uint256"},{"internalType":"uint256","name":"lastCompletedRoundIndex","type":"uint256"},{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"endTime","type":"uint256"},{"internalType":"uint256","name":"lastHigh","type":"uint256"},{"internalType":"uint256","name":"lastLow","type":"uint256"},{"internalType":"uint256","name":"finalHigh","type":"uint256"},{"internalType":"uint256","name":"finalLow","type":"uint256"},{"internalType":"string","name":"lastWinner","type":"string"},{"internalType":"bool","name":"isBettingOpen","type":"bool"}],"internalType":"struct Yolatility.Config","name":"c","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_betAmount","type":"uint256"},{"internalType":"uint256","name":"_startTime","type":"uint256"},{"internalType":"uint256","name":"_endTime","type":"uint256"},{"internalType":"string","name":"lastWinner","type":"string"},{"internalType":"uint256","name":"lastHigh","type":"uint256"},{"internalType":"uint256","name":"lastLow","type":"uint256"}],"name":"initRound","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"round","type":"uint256"}],"name":"isBettingOpen","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lastCompletedRoundIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nextRoundIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"round","type":"uint256"}],"name":"nftContractsForRound","outputs":[{"components":[{"internalType":"address","name":"winnerAddress","type":"address"},{"internalType":"address","name":"betterAddress","type":"address"}],"internalType":"struct Yolatility.NftContracts","name":"","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"round","type":"uint256"},{"internalType":"uint256","name":"high","type":"uint256"},{"internalType":"uint256","name":"low","type":"uint256"}],"name":"placeBet","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"round","type":"uint256"},{"internalType":"uint256","name":"startIndex","type":"uint256"},{"internalType":"uint256","name":"numBets","type":"uint256"}],"name":"processBets","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"round","type":"uint256"}],"name":"processingComplete","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_admin","type":"address"}],"name":"removeFromAdmins","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"roundsA","outputs":[{"internalType":"uint256","name":"totalBets","type":"uint256"},{"internalType":"uint256","name":"totalAmount","type":"uint256"},{"internalType":"uint256","name":"betAmount","type":"uint256"},{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"endTime","type":"uint256"},{"internalType":"bool","name":"processingComplete","type":"bool"},{"internalType":"uint256","name":"bestScore","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_burnAddress","type":"address"}],"name":"setBurnAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"round","type":"uint256"},{"internalType":"uint256","name":"high","type":"uint256"},{"internalType":"uint256","name":"low","type":"uint256"}],"name":"setFinalHighLow","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"round","type":"uint256"},{"internalType":"uint256","name":"high","type":"uint256"},{"internalType":"uint256","name":"low","type":"uint256"}],"name":"setLastHighLow","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"high","type":"uint256"},{"internalType":"uint256","name":"low","type":"uint256"}],"name":"setLastHighLowForLatestRound","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"round","type":"uint256"},{"internalType":"string","name":"winner","type":"string"}],"name":"setLastWinner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"winner","type":"string"}],"name":"setLastWinnerForLatestRound","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_teamSplit","type":"address"}],"name":"setTeamSplit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"teamSplit","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"round","type":"uint256"}],"name":"totalAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"round","type":"uint256"}],"name":"totalBets","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"round","type":"uint256"},{"internalType":"address","name":"better","type":"address"}],"name":"totalBetsForBetter","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"round","type":"uint256"}],"name":"totalUniqueBetters","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"round","type":"uint256"},{"internalType":"address","name":"winnerAddress","type":"address"},{"internalType":"address","name":"betterAddress","type":"address"}],"name":"updateNftContractsForRound","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"userBets","outputs":[{"internalType":"address","name":"player","type":"address"},{"internalType":"uint256","name":"high","type":"uint256"},{"internalType":"uint256","name":"low","type":"uint256"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"submittedAt","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"yoyoToken","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"stateMutability":"payable","type":"receive"}]