Skip to content

Instantly share code, notes, and snippets.

@0xOlias
Last active June 2, 2022 22:03
Show Gist options
  • Save 0xOlias/ea1d55dc94254827168a2231fa6427bc to your computer and use it in GitHub Desktop.
Save 0xOlias/ea1d55dc94254827168a2231fa6427bc to your computer and use it in GitHub Desktop.

This is a WIP implementation of a simple auction mechanism for control of the EthPlays banner message. The EthPlays.sol contract omits most of the game logic to focus on the auction details.

Auction constraints:

  • There must be a single method to end the previous auction and start the next auction
  • There must be both an auction cooldown and an auction duration
  • There must be events emitted when the auction ends (when a winner is declared)
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.13;
import {Poke} from "src/Poke.sol";
import {EthPlaysAuction} from "src/EthPlaysAuction.sol";
contract Deploy {
Poke poke;
EthPlaysAuction ethPlaysAuction;
function main() public {
poke = new Poke();
ethPlaysAuction = new EthPlaysAuction(address(poke));
poke.updateGameAddress(address(ethPlaysAuction));
}
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.13;
import {Ownable} from "openzeppelin-contracts/contracts/access/Ownable.sol";
import {Poke} from "src/Poke.sol";
/// @title An experiment in collaborative gaming
/// @author olias.eth
/// @notice This is experimental software, use at your own risk.
contract EthPlaysAuction is Ownable {
/* -------------------------------------------------------------------------- */
/* TYPES */
/* -------------------------------------------------------------------------- */
struct BannerBid {
address from;
uint256 amount;
string message;
}
/* -------------------------------------------------------------------------- */
/* STORAGE */
/* -------------------------------------------------------------------------- */
/// @notice [Contract] The POKE token contract
Poke public poke;
/// @notice [Parameter] The number of seconds between banner auctions
uint256 public bannerAuctionCooldown;
/// @notice [Parameter] The number of seconds that the banner auction lasts
uint256 public bannerAuctionDuration;
/// @notice [State] The best bid for the current banner auction
BannerBid private bestBannerBid;
/// @notice [State] The block timestamp of the start of the current banner auction
uint256 private bannerAuctionTimestamp;
/* -------------------------------------------------------------------------- */
/* EVENTS */
/* -------------------------------------------------------------------------- */
// Auction events
event NewBannerBid(address from, uint256 amount, string message);
event Banner(address from, string message);
// Parameter update events
event SetBannerAuctionCooldown(uint256 bannerAuctionCooldown);
event SetBannerAuctionDuration(uint256 bannerAuctionDuration);
/* -------------------------------------------------------------------------- */
/* ERRORS */
/* -------------------------------------------------------------------------- */
// Auction errors
error InsufficientBalanceForBid();
error InsufficientBidAmount();
error AuctionInProgress();
error AuctionNotActive();
error AuctionHasNoBids();
/* -------------------------------------------------------------------------- */
/* MODIFIERS */
/* -------------------------------------------------------------------------- */
/* ... redacted ... */
/* -------------------------------------------------------------------------- */
/* INITIALIZATION */
/* -------------------------------------------------------------------------- */
constructor(address pokeAddress) {
poke = Poke(pokeAddress);
bannerAuctionCooldown = 120;
bannerAuctionDuration = 60;
bestBannerBid = BannerBid(address(0), 0, "");
}
/* -------------------------------------------------------------------------- */
/* GAMEPLAY */
/* -------------------------------------------------------------------------- */
/* ... redacted ... */
/* -------------------------------------------------------------------------- */
/* REDEEMS */
/* -------------------------------------------------------------------------- */
/* ... redacted ... */
/* -------------------------------------------------------------------------- */
/* AUCTIONS */
/* -------------------------------------------------------------------------- */
/// @notice Submit a bid in the active banner auction.
/// @param amount The bid amount in POKE
/// @param message The requested banner message text
function submitBannerBid(uint256 amount, string memory message) external {
if (block.timestamp < bannerAuctionTimestamp + bannerAuctionCooldown) {
revert AuctionNotActive();
}
if (poke.balanceOf(msg.sender) < amount) {
revert InsufficientBalanceForBid();
}
if (amount <= bestBannerBid.amount) {
revert InsufficientBidAmount();
}
if (bestBannerBid.from != address(0)) {
poke.gameTransfer(
address(this),
bestBannerBid.from,
bestBannerBid.amount
);
}
poke.gameTransfer(msg.sender, address(this), amount);
bestBannerBid = BannerBid(msg.sender, amount, message);
emit NewBannerBid(msg.sender, amount, message);
}
/// @notice End the current banner auction and start the cooldown for the next one.
function rolloverBannerAuction() external {
if (
block.timestamp <
bannerAuctionTimestamp +
bannerAuctionCooldown +
bannerAuctionDuration
) {
revert AuctionInProgress();
}
if (bestBannerBid.from == address(0)) {
revert AuctionHasNoBids();
}
emit Banner(bestBannerBid.from, bestBannerBid.message);
bannerAuctionTimestamp = block.timestamp;
bestBannerBid = BannerBid(address(0), 0, "");
}
/* -------------------------------------------------------------------------- */
/* ADMIN */
/* -------------------------------------------------------------------------- */
function setBannerAuctionCooldown(uint256 _bannerAuctionCooldown)
external
onlyOwner
{
bannerAuctionCooldown = _bannerAuctionCooldown;
emit SetBannerAuctionCooldown(_bannerAuctionCooldown);
}
function setBannerAuctionDuration(uint256 _bannerAuctionDuration)
external
onlyOwner
{
bannerAuctionDuration = _bannerAuctionDuration;
emit SetBannerAuctionDuration(_bannerAuctionDuration);
}
}
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.13;
import {Ownable} from "openzeppelin-contracts/contracts/access/Ownable.sol";
import {ERC20} from "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol";
/// @title The game token for ethplays
/// @author olias.eth
/// @notice This is experimental software, use at your own risk.
contract Poke is ERC20, Ownable {
/* -------------------------------------------------------------------------- */
/* STORAGE */
/* -------------------------------------------------------------------------- */
/// @notice Address of the current game contract
address public gameContract;
/* -------------------------------------------------------------------------- */
/* EVENTS */
/* -------------------------------------------------------------------------- */
event UpdateGameContract(address gameContract);
/* -------------------------------------------------------------------------- */
/* ERRORS */
/* -------------------------------------------------------------------------- */
error NotAuthorized();
/* -------------------------------------------------------------------------- */
/* MODIFIERS */
/* -------------------------------------------------------------------------- */
/// @notice Requires the sender to be the game contract
modifier onlyGameContract() {
if (msg.sender != gameContract) {
revert NotAuthorized();
}
_;
}
/* -------------------------------------------------------------------------- */
/* INITIALIZATION */
/* -------------------------------------------------------------------------- */
constructor() ERC20("ethplays", "POKE") {}
/* -------------------------------------------------------------------------- */
/* GAME */
/* -------------------------------------------------------------------------- */
/// @notice Mint new tokens to an account. Can only be called by the game contract.
/// @param account The account to mint tokens to
/// @param amount The amount of tokens to mint
function gameMint(address account, uint256 amount)
external
onlyGameContract
{
_mint(account, amount);
}
/// @notice Burn existing tokens belonging to an account. Can only be called by the game contract.
/// @param account The account to burn tokens for
/// @param amount The amount of tokens to burn
function gameBurn(address account, uint256 amount)
external
onlyGameContract
{
_burn(account, amount);
}
/// @notice Transfer tokens without approval. Can only be called by the game contract.
/// @param from The account to transfer tokens from
/// @param to The account to transfer tokens to
/// @param amount The amount of tokens to transfer
function gameTransfer(
address from,
address to,
uint256 amount
) external onlyGameContract {
_transfer(from, to, amount);
}
/* -------------------------------------------------------------------------- */
/* ADMIN */
/* -------------------------------------------------------------------------- */
/// @notice Update the game contract address. Only owner.
/// @param addr The address of the game contract
function updateGameAddress(address addr) external onlyOwner {
gameContract = addr;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment