Test deploy on Moonbase Alpha

pull/1/head
six 2023-05-25 15:58:35 +02:00
parent 066a1d9ddb
commit 8e122386b6
11 changed files with 1884 additions and 2 deletions

View File

@ -1,3 +1,7 @@
# DCTF
# DCTF Beta
Decentralized CTF engine, used by CCTF
Decentralized CTF engine, used by CCTF
Testnet deployment on Moonbase Alpha: https://moonbase.moonscan.io/address/0x919f68cc35ce5d49a45c94dc44e7bf444f9a7531
Contributors: six, Silur, Anonz team, Metafaka team, BMEta team

View File

@ -0,0 +1,138 @@
// SPDX-License-Identifier: Apache-2.0
// Authors: six and Silur
pragma solidity ^0.8.16;
contract CCTF9 {
address public admin;
uint256 public volStart;
uint256 public volMaxPoints;
uint256 public powDiff;
bool public started;
enum PlayerStatus {
Unverified,
Verified,
Banned
}
struct Player {
PlayerStatus status;
uint256 points;
}
modifier onlyAdmin {
require(msg.sender == admin, "Not admin");
_;
}
modifier onlyActive {
require(started == true, "CCTF not started.");
_;
}
struct Flag {
address signer;
bool onlyFirstSolver;
uint256 points;
string skill_name;
}
mapping (address => Player) public players;
mapping (uint256 => Flag) public flags;
event CCTFStarted(uint256 timestamp);
event FlagAdded(uint256 indexed flagId, address flagSigner);
event FlagRemoved(uint256 indexed flagId);
event FlagSolved(uint256 indexed flagId, address indexed solver);
constructor(uint256 _volMaxPoints, uint256 _powDiff) {
admin = msg.sender;
volMaxPoints = _volMaxPoints;
powDiff = _powDiff;
started = false;
}
function setAdmin(address _admin) external onlyAdmin {
require(_admin != address(0));
admin = _admin;
}
function setCCTFStatus(bool _started) external onlyAdmin {
started = _started;
}
function setFlag(uint256 _flagId, address _flagSigner, bool _onlyFirstSolver, uint256 _points, string memory _skill) external onlyAdmin{
flags[_flagId] = Flag(_flagSigner, _onlyFirstSolver, _points, _skill);
emit FlagAdded(_flagId, _flagSigner);
}
function setPowDiff(uint256 _powDiff) external onlyAdmin {
powDiff = _powDiff;
}
function register(string memory _RTFM) external {
require(players[msg.sender].status == PlayerStatus.Unverified, 'Already registered or banned');
//uint256 pow = uint256(keccak256(abi.encodePacked("CCTF", msg.sender,"registration", nonce)));
//require(pow < powDiff, "invalid pow");
require(keccak256(abi.encodePacked('I_read_it')) == keccak256(abi.encodePacked(_RTFM))); // PoW can be used for harder challenges, this is Entry!
players[msg.sender].status = PlayerStatus.Verified;
}
function setPlayerStatus(address player, PlayerStatus status) external onlyAdmin {
players[player].status = status;
}
////////// Submit flags
mapping(bytes32 => bool) usedNs; // Against replay attack (we only check message signer)
mapping (address => mapping (uint256 => bool)) Solves; // address -> challenge ID -> solved/not
uint256 public submission_success_count = 0; // For statistics
function SubmitFlag(bytes32 _message, bytes memory signature, uint256 _submitFor) external onlyActive {
require(players[msg.sender].status == PlayerStatus.Verified, "You are not even playing");
require(bytes32(_message).length <= 256, "Too long message.");
require(!usedNs[_message]);
usedNs[_message] = true;
require(recoverSigner(_message, signature) == flags[_submitFor].signer, "Not signed with the correct key.");
require(Solves[msg.sender][_submitFor] == false);
Solves[msg.sender][_submitFor] = true;
players[msg.sender].points += flags[_submitFor].points;
players[msg.sender].points = players[msg.sender].points < volMaxPoints ? players[msg.sender].points : volMaxPoints;
if (flags[_submitFor].onlyFirstSolver) {
flags[_submitFor].points = 0;
}
submission_success_count = submission_success_count + 1;
emit FlagSolved(_submitFor, msg.sender);
}
function recoverSigner(bytes32 _ethSignedMessageHash, bytes memory _signature) public pure returns (address) {
(bytes32 r, bytes32 s, uint8 v) = splitSignature(_signature);
return ecrecover(_ethSignedMessageHash, v, r, s);
}
function splitSignature(bytes memory sig) public pure returns (bytes32 r, bytes32 s, uint8 v){
require(sig.length == 65, "Invalid signature length");
assembly {
r := mload(add(sig, 32))
s := mload(add(sig, 64))
v := byte(0, mload(add(sig, 96)))
}
}
////////// Check status, scores, etc
function getPlayerStatus(address _player) external view returns (PlayerStatus) {
return players[_player].status;
}
function getPlayerPoints(address _player) external view returns (uint256) {
return players[_player].points < volMaxPoints ? players[_player].points : volMaxPoints;
}
function getSuccessfulSubmissionCount() external view returns (uint256){
return submission_success_count;
}
}

View File

@ -0,0 +1,130 @@
// SPDX-License-Identifier: Apache-2.0
// Authors: Anonz team, developed at Polkadot Metaverse Championship, as part of their CCTF track solution.
// Based on Six's and Silur's CCTF 2022 code.
pragma solidity ^0.8.16;
contract CryptoCTFX {
enum PlayerStatus {
Unverified,
Verified,
Banned
}
struct Player {
PlayerStatus status;
uint score;
}
modifier onlyExistingContest(uint contestID) {
require(contests[contestID].admin != address(0), "Unknown contest ID");
_;
}
modifier onlyAdmin(uint contestID) {
require(msg.sender == contests[contestID].admin, "You are not the admin of this contest");
_;
}
modifier onlyOpen(uint contestID) {
require(contests[contestID].submissionsOpen, "Submissions are not open for this contest at this time");
_;
}
modifier onlyExistingChallenge(uint contestID, uint challengeID) {
require(contests[contestID].challenges[challengeID].obscuredFlag != address(0), "Unknown challenge ID");
_;
}
struct Challenge {
address obscuredFlag; // public key of the flag
uint worth;
uint256 descriptionFingerprint;
bool onlyFirstSolver;
string skill;
}
struct Contest {
address admin;
mapping (uint => Challenge) challenges;
mapping (address => Player) players;
bool submissionsOpen;
mapping (address => mapping (uint => bool)) solves; // address -> challengeID -> solved/not
mapping (uint => bool) anySolves; // challengeID -> solved/not
}
mapping (uint => Contest) public contests;
event ChallengeAddedOrUpdated(uint contestID, uint indexed challengeID);
event ChallengeSolved(uint contestID, uint indexed challengeID, address indexed solver);
function createContest(uint contestID) external {
require(contests[contestID].admin == address(0), "This contest ID has already been registered");
contests[contestID].admin = msg.sender;
contests[contestID].submissionsOpen = false;
}
function setAdmin(uint contestID, address newAdmin) external onlyExistingContest(contestID) onlyAdmin(contestID) {
require(newAdmin != address(0));
contests[contestID].admin = newAdmin;
}
function setSubmissionsStatus(uint contestID, bool open) external onlyExistingContest(contestID) onlyAdmin(contestID) {
contests[contestID].submissionsOpen = open;
}
function addOrUpdateChallenge(uint contestID, uint challengeID, address obscuredFlag, uint worth, uint256 descriptionFingerprint, bool onlyFirstSolver, string memory skill) external onlyExistingContest(contestID) onlyAdmin(contestID) {
require(obscuredFlag != address(0), "The obscured flag value must not be 0");
contests[contestID].challenges[challengeID] = Challenge(obscuredFlag, worth, descriptionFingerprint, onlyFirstSolver, skill);
emit ChallengeAddedOrUpdated(contestID, challengeID);
}
function register(uint contestID, string memory password) external onlyExistingContest(contestID) {
require(contests[contestID].players[msg.sender].status == PlayerStatus.Unverified, "You are already registered or banned in this contest");
require(keccak256(abi.encodePacked("I_read_it")) == keccak256(abi.encodePacked(password)));
contests[contestID].players[msg.sender].status = PlayerStatus.Verified;
}
function setPlayerStatus(uint contestID, address player, PlayerStatus status) external onlyExistingContest(contestID) onlyAdmin(contestID) {
contests[contestID].players[player].status = status;
}
function submitFlag(uint contestID, uint challengeID, bytes memory signature) external onlyExistingContest(contestID) onlyExistingChallenge(contestID, challengeID) onlyOpen(contestID) {
require(contests[contestID].players[msg.sender].status == PlayerStatus.Verified, "You are unverified or banned in this contest");
// the correct signature is an ECDSA signature where (1) the message (hash) is the sender address and (2) the private key is the flag;
// (2) is checked by testing against the public key, which can then be public information
address recoveredSigner = recoverSigner(bytes32(abi.encodePacked(msg.sender)), signature);
require(recoveredSigner != address(0), "Invalid signature");
require(recoveredSigner == contests[contestID].challenges[challengeID].obscuredFlag, "Wrong answer");
require(!contests[contestID].solves[msg.sender][challengeID], "You have already solved this challenge of this contest");
if (!contests[contestID].anySolves[challengeID] || !contests[contestID].challenges[challengeID].onlyFirstSolver) {
contests[contestID].players[msg.sender].score += contests[contestID].challenges[challengeID].worth;
}
contests[contestID].solves[msg.sender][challengeID] = true;
contests[contestID].anySolves[challengeID] = true;
emit ChallengeSolved(contestID, challengeID, msg.sender);
}
function recoverSigner(bytes32 messageHash, bytes memory signature) public pure returns (address) {
(bytes32 r, bytes32 s, uint8 v) = splitSignature(signature);
return ecrecover(messageHash, v, r, s);
}
function splitSignature(bytes memory sig) public pure returns (bytes32 r, bytes32 s, uint8 v) {
require(sig.length == 65, "Invalid signature length");
assembly {
r := mload(add(sig, 32))
s := mload(add(sig, 64))
v := byte(0, mload(add(sig, 96)))
}
}
function getPlayerStatus(uint contestID, address player) external view onlyExistingContest(contestID) returns (PlayerStatus) {
return contests[contestID].players[player].status;
}
function getPlayerScore(uint contestID, address player) external view onlyExistingContest(contestID) returns (uint) {
return contests[contestID].players[player].score;
}
}

798
frontend/abi.js 100644
View File

@ -0,0 +1,798 @@
myABI = [
{
"inputs": [
{
"internalType": "uint256",
"name": "_volMaxPoints",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_powDiff",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "timestamp",
"type": "uint256"
}
],
"name": "CCTFStarted",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "flagId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "address",
"name": "flagSigner",
"type": "address"
}
],
"name": "FlagAdded",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "flagId",
"type": "uint256"
}
],
"name": "FlagRemoved",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "flagId",
"type": "uint256"
},
{
"indexed": true,
"internalType": "address",
"name": "solver",
"type": "address"
}
],
"name": "FlagSolved",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "player",
"type": "address"
},
{
"indexed": false,
"internalType": "enum CCTF9.PlayerStatus",
"name": "newStatus",
"type": "uint8"
}
],
"name": "PlayerStatusChanged",
"type": "event"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "_message",
"type": "bytes32"
},
{
"internalType": "bytes",
"name": "signature",
"type": "bytes"
},
{
"internalType": "uint256",
"name": "_submitFor",
"type": "uint256"
}
],
"name": "SubmitFlag",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "TIME_DECAY_MAX",
"outputs": [
{
"internalType": "uint8",
"name": "",
"type": "uint8"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "admin",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "endTime",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "flagCount",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "flagList",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "flags",
"outputs": [
{
"internalType": "address",
"name": "signer",
"type": "address"
},
{
"internalType": "enum CCTF9.FlagType",
"name": "flagType",
"type": "uint8"
},
{
"internalType": "uint256",
"name": "solveCount",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "points",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "currentPoints",
"type": "uint256"
},
{
"internalType": "string",
"name": "skill_name",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_idx",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_count",
"type": "uint256"
}
],
"name": "getFlags",
"outputs": [
{
"components": [
{
"internalType": "address",
"name": "signer",
"type": "address"
},
{
"internalType": "enum CCTF9.FlagType",
"name": "flagType",
"type": "uint8"
},
{
"internalType": "uint256",
"name": "solveCount",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "points",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "currentPoints",
"type": "uint256"
},
{
"internalType": "string",
"name": "skill_name",
"type": "string"
}
],
"internalType": "struct CCTF9.Flag[]",
"name": "flagListRet",
"type": "tuple[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getFlags",
"outputs": [
{
"components": [
{
"internalType": "address",
"name": "signer",
"type": "address"
},
{
"internalType": "enum CCTF9.FlagType",
"name": "flagType",
"type": "uint8"
},
{
"internalType": "uint256",
"name": "solveCount",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "points",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "currentPoints",
"type": "uint256"
},
{
"internalType": "string",
"name": "skill_name",
"type": "string"
}
],
"internalType": "struct CCTF9.Flag[]",
"name": "flagListRet",
"type": "tuple[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_player",
"type": "address"
}
],
"name": "getPlayerPoints",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_player",
"type": "address"
}
],
"name": "getPlayerStatus",
"outputs": [
{
"internalType": "enum CCTF9.PlayerStatus",
"name": "",
"type": "uint8"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_idx",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_count",
"type": "uint256"
}
],
"name": "getPlayers",
"outputs": [
{
"components": [
{
"internalType": "enum CCTF9.PlayerStatus",
"name": "status",
"type": "uint8"
},
{
"internalType": "uint256",
"name": "points",
"type": "uint256"
},
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "uint256[2][]",
"name": "pointsPerFlag",
"type": "uint256[2][]"
}
],
"internalType": "struct CCTF9.Player[]",
"name": "playerListRet",
"type": "tuple[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getPlayers",
"outputs": [
{
"components": [
{
"internalType": "enum CCTF9.PlayerStatus",
"name": "status",
"type": "uint8"
},
{
"internalType": "uint256",
"name": "points",
"type": "uint256"
},
{
"internalType": "string",
"name": "name",
"type": "string"
},
{
"internalType": "uint256[2][]",
"name": "pointsPerFlag",
"type": "uint256[2][]"
}
],
"internalType": "struct CCTF9.Player[]",
"name": "playerListRet",
"type": "tuple[]"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getSuccessfulSubmissionCount",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "playerCount",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "playerList",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "players",
"outputs": [
{
"internalType": "enum CCTF9.PlayerStatus",
"name": "status",
"type": "uint8"
},
{
"internalType": "uint256",
"name": "points",
"type": "uint256"
},
{
"internalType": "string",
"name": "name",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "powDiff",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "_ethSignedMessageHash",
"type": "bytes32"
},
{
"internalType": "bytes",
"name": "_signature",
"type": "bytes"
}
],
"name": "recoverSigner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "_RTFM",
"type": "string"
},
{
"internalType": "string",
"name": "_name",
"type": "string"
}
],
"name": "register",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_admin",
"type": "address"
}
],
"name": "setAdmin",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_endTime",
"type": "uint256"
}
],
"name": "setCCTFEndTime",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bool",
"name": "_paused",
"type": "bool"
}
],
"name": "setCCTFPaused",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_startTime",
"type": "uint256"
}
],
"name": "setCCTFStartTime",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_flagId",
"type": "uint256"
},
{
"internalType": "address",
"name": "_flagSigner",
"type": "address"
},
{
"internalType": "enum CCTF9.FlagType",
"name": "_flagType",
"type": "uint8"
},
{
"internalType": "uint256",
"name": "_points",
"type": "uint256"
},
{
"internalType": "string",
"name": "_skill",
"type": "string"
}
],
"name": "setFlag",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "player",
"type": "address"
},
{
"internalType": "enum CCTF9.PlayerStatus",
"name": "status",
"type": "uint8"
}
],
"name": "setPlayerStatus",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_powDiff",
"type": "uint256"
}
],
"name": "setPowDiff",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes",
"name": "sig",
"type": "bytes"
}
],
"name": "splitSignature",
"outputs": [
{
"internalType": "bytes32",
"name": "r",
"type": "bytes32"
},
{
"internalType": "bytes32",
"name": "s",
"type": "bytes32"
},
{
"internalType": "uint8",
"name": "v",
"type": "uint8"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [],
"name": "startTime",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "started",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "submission_success_count",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "volMaxPoints",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "volStart",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

121
frontend/index.html 100644
View File

@ -0,0 +1,121 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>CCTF Frontend by Metafaka</title>
<link rel="icon" type="image/png" href="cctf-icon.png" sizes="96x96">
<link href="https://fonts.googleapis.com/css?family=Roboto Mono" rel="stylesheet">
<link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.15.4/css/all.css" integrity="sha384-DyZ88mC6Up2uqS4h/KRgHuoeGwBcD4Ng9SiP4dIRy0EXTlnuz47vAwmeGwVChigm" crossorigin="anonymous">
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="top">
<div class="top_logo">
<img src="cctf-logo.png" id="logo" alt="CCTF logo" width="200" height="49">
</div>
<div class="top_left">
<div>
<i style="margin: 10px;" class="fas fa-users"></i>
</div>
<div style="padding-right: 18px; line-height: 14px;">
<span id="header_uppercase">PLAYERS</span><br>
<span id="players">loading...</span>
</div>
<div>
<i style="margin: 10px;" class="far fa-flag"></i>
</div>
<div style="line-height: 14px;">
<span id="header_uppercase">FOUND FLAGS</span><br>
<span id="submissions">loading...</span>
</div>
</div>
<div class="top_center" id="toplist">
<div class="topitem">
<span id="remaining_time" style="font-size: 28px; font-weight: 200">--:--:--</span>
</div>
</div>
<div class="top_right">
<div class="topitem">
<!-- Trigger/Open The Modal -->
<div style="margin: 0 18px; cursor: pointer" id="myBtn">
<i style="margin-right: 2px;" class="fas fa-bolt"></i>
<span>Getting Started</span>
</div>
</div>
</div>
</div>
<div class="left">
<div class="left-content">
<h1>My Account</h1>
<div id="account_locked">
<button id="account_enable"><img id="metamask" src="metamask.svg">Enable account</button>
</div>
<div id="account_unlocked" style="display: none">
<div id="user"></div>
<div id="unverified" style="display: none">
<h1>Register</h1>
<input type="text" id="playerName" placeholder="Your name...">
<br>
<span style="font-size: 14px;" id="myBtn">I have read the f#%@ken manual: </span><input type="checkbox" id="myCheck">
<button id="register"><i class="fab fa-ethereum" style="font-size: 16px; margin-right: 8px;"></i>Submit</button>
</div>
<div id="verified" style="display: none">
<h1>My Points</h1>
<span id="holding">0</span>
<h1>Challenges</h1>
<p>Challenge 1</p>
<p>Challenge 2</p>
<p>Challenge 3</p>
<p>Challenge 4</p>
<p>Challenge 5</p>
<p>Challenge 6</p>
</div>
<div id="banned" style="display: none">
<h1>BANNED</h1>
</div>
</div>
</div>
</div>
<div class="main">
<div id="loading">Loading...</div>
<h2>Leaderboard</h2>
<table id="rank_list"></table>
</div>
<!-- The Modal -->
<div id="myModal" class="modal">
<!-- Modal content -->
<div class="modal-content">
<div class="modal-header">
<span class="close">&times;</span>
<h2>Getting Started</h2>
</div>
<div class="modal-body">
<h3>The Game</h3>
<p>Soooo much fun!</p>
<h3>Contract Address</h3>
<p><a href="https://moonbase.moonscan.io/address/0x919f68cc35ce5d49a45c94dc44e7bf444f9a7531" target="_blank">0x919f68cc35ce5d49a45c94dc44e7bf444f9a7531</a></p>
<h3>Questions?</h3>
<p>Answers.</p>
</div>
<div class="modal-footer">
</div>
</div>
</div>
<script language="javascript" type="text/javascript" src="https://cdn.jsdelivr.net/gh/ethereum/web3.js@1.8.0/dist/web3.min.js"></script>
<script language="javascript" type="text/javascript" src="abi.js"></script>
<script language="javascript" type="text/javascript" src="script.js"></script>
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 4.9 KiB

330
frontend/script.js 100644
View File

@ -0,0 +1,330 @@
"use strict";
document.addEventListener('DOMContentLoaded', function () {
const contractAddress = '0xc8bd0A15E35Ddc1692D907583696443e2970779b';
const nullAddress = '0x0000000000000000000000000000000000000000';
var userAccount = nullAddress;
var btnRegister = document.getElementById("register");
var btnAccountEnable = document.getElementById("account_enable");
btnRegister.addEventListener('click', register);
btnAccountEnable.addEventListener('click', accountEnable);
var funcType;
var mark;
var expirationTime;
var timerIsRunning = false;
var players = [];
// Modern dapp browsers...
if(window.ethereum) {
console.log("Modern dapp browser detected");
window.web3 = new Web3(window.ethereum);
checkNetworkType();
ethereum.on('accountsChanged', (accounts) => accountChange(accounts[0]));
ethereum.on('chainChanged', (_chainId) => window.location.reload());
}
// Legacy dapp browsers...
else if(window.web3) {
console.log("Legacy dapp browser detected");
window.web3 = new Web3(web3.currentProvider);
// Acccounts always exposed
checkNetworkType();
}
// Non-dapp browsers...
else {
console.log("Non-Ethereum browser detected. You should consider trying MetaMask!");
window.web3 = new Web3(new Web3.providers.WebsocketProvider('wss://goerli.infura.io/ws/v3/d82552d5ad5841e5acbbe7a8417a23ad'));
}
function switchToGoerli() {
const iconUrl = "";
const goerli = {
chainId: "0x5",
chainName: "Goerli test network",
rpcUrls: ["https://goerli.infura.io/v3/"],
nativeCurrency: {
name: "GoerliETH",
symbol: "ETH",
decimals: 18,
},
blockExplorerUrls: ["https://goerli.etherscan.io"],
iconUrls: [iconUrl],
};
const { request } = window.ethereum;
request({
method: "wallet_switchEthereumChain",
params: [{ chainId: goerli.chainId }],
}).catch(e => {
// This error code indicates that the chain has not been added to MetaMask.
if (e.code === 4902) {
return request({
method: "wallet_addEthereumChain",
params: [goerli],
});
} else {
throw e;
}
});
}
function checkNetworkType() {
web3.eth.net.getId()
.then(function(result) {
if(result != 5) { /* Goerli Mainnet */
alert("Please select Goerli test network on your Metamask!");
console.log('switchToGoerli');
switchToGoerli();
}
console.log('Network Id:', result);
});
}
function displayShortAddress(address) {
return(address.slice(0, 6) + "..." + address.slice(-4))
}
function setUserAccount() {
var x = userAccount.toString();
document.getElementById("user").textContent = displayShortAddress(x);
if(userAccount != nullAddress) {
document.getElementById("account_locked").style.display = "none";
document.getElementById("account_unlocked").style.display = "block";
}
else {
document.getElementById("account_locked").style.display = "block";
document.getElementById("account_unlocked").style.display = "none";
}
}
function getPlayerStatus() {
myContract.methods.getPlayerStatus(userAccount).call()
.then(function(result) {
console.log("Player status: " + result);
if(result == 0) {
document.getElementById("unverified").style.display = "block";
document.getElementById("verified").style.display = "none";
document.getElementById("banned").style.display = "none";
}
else if(result == 1) {
document.getElementById("unverified").style.display = "none";
document.getElementById("verified").style.display = "block";
document.getElementById("banned").style.display = "none";
}
else {
document.getElementById("unverified").style.display = "none";
document.getElementById("verified").style.display = "none";
document.getElementById("banned").style.display = "block";
}
});
}
function accountChange(newAccount) {
if(web3.utils.isAddress(newAccount)) {
userAccount = web3.utils.toChecksumAddress(newAccount);
}
else userAccount = nullAddress;
console.log('ACCOUNT: ' + userAccount);
console.log('CheckNetworkType');
checkNetworkType();
setUserAccount();
getPlayerStatus();
}
async function getAccount(type) {
if (window.ethereum) { // for modern DApps browser
try {
await window.ethereum.request({ method: 'eth_requestAccounts' });
} catch (error) {
console.error(error);
}
}
if (window.web3){
await window.web3.eth.getAccounts().then(it => {
accountChange(it[0]);
if (type == "REGISTER") registerPlayer();
});
}
else {
alert('No Metamask or Ethereum wallet found. Read "Getting Started" for more information');
console.log('No Metamask or Ethereum wallet found. Read "Getting Started" for more information');
}
}
function register() {
funcType = "REGISTER";
getAccount(funcType);
}
function accountEnable() {
funcType = "NONE";
getAccount(funcType);
}
function setPlayers(data) {
document.getElementById("players").textContent = data;
}
function setSubmissions(data) {
document.getElementById("submissions").textContent = data;
}
function setExpiration(newExpiration) {
if(! timerIsRunning) startTimer();
expirationTime = newExpiration;
updateClock();
}
function setRank(data) {
var text = "";
text += "<tr><th>PlayerStatus</th><th>Name</th><th>Flags found</th><th>Points</th></tr>";
data.forEach(item => {
console.log(item[2]);
text += "<tr><td>" + item[0] + "</td><td>" + item[2] + "</td><td>" + item[3] + "</td><td>" + item[1] + "</td></tr>";
});
document.getElementById("loading").style.display = "none";
document.getElementById("rank_list").innerHTML = text;
}
function registerPlayer() {
var rtfm = 'I_read_it'; // todo: Checkbox check removed for easier testing
var playerName = document.getElementById("playerName").value;
myContract.methods.register(rtfm, playerName).send({from: userAccount})
.then(function() {
console.log("register()");
rank();
getPlayerStatus();
getPlayers();
});
}
function isGameGoing() {
myContract.methods.started().call()
.then(function(result) {
console.log("is going: " + result);
return(result);
});
}
function startTimer() {
mark = setInterval(updateClock, 1000);
timerIsRunning = true;
}
function stopTimer() {
clearInterval(mark);
timerIsRunning = false;
}
function updateClock() {
const remainingTime = expirationTime - Math.floor((new Date()).getTime() / 1000);
if(remainingTime > 0) {
//var hour = Math.floor(remainingTime / (60 * 60)) % 24;
var hour = Math.floor(remainingTime / (60 * 60));
var min = Math.floor(remainingTime / 60) % 60;
var sec = remainingTime % 60;
if(hour < 10) hour = '0' + hour;
if(min < 10) min = '0' + min;
if(sec < 10) sec = '0' + sec;
document.getElementById("remaining_time").textContent = hour + ':' + min + ':' + sec;
}
else gameOver();
}
function gameOver() {
stopTimer();
document.getElementById("remaining_time").textContent = "--:--:--";
if(isGameGoing()) {
document.getElementById("remaining_time").textContent = "Game Over";
}
else {
document.getElementById("remaining_time").textContent = "Not Started";
}
}
function expiration() {
myContract.methods.endTime().call()
.then(function(result) {
console.log("expiration: " + result);
setExpiration(result);
});
}
function getPlayers() {
myContract.methods.playerCount().call()
.then(function(result) {
console.log("playerCount: " + result);
setPlayers(result);
});
}
function submissions() {
myContract.methods.getSuccessfulSubmissionCount().call()
.then(function(result) {
console.log("SuccessfulSubmissionCount: " + result);
setSubmissions(result);
});
}
function rank() {
myContract.methods.getPlayers().call()
.then(function(result) {
console.log("Players: " + result);
setRank(result);
});
}
//===============================================================================================
// Get the modal
var modal = document.getElementById('myModal');
// Get the button that opens the modal
var btn = document.getElementById("myBtn");
// Get the <span> element that closes the modal
var span = document.getElementsByClassName("close")[0];
// When the user clicks the button, open the modal
btn.onclick = function() {
modal.style.display = "block";
}
// When the user clicks on <span> (x), close the modal
span.onclick = function() {
modal.style.display = "none";
}
// When the user clicks anywhere outside of the modal, close it
window.onclick = function(event) {
if (event.target == modal) {
modal.style.display = "none";
}
}
//===============================================================================================
var myContract;
myContract = new web3.eth.Contract(myABI, contractAddress);
myContract.events.PlayerStatusChanged()
.on("data", function(event) {
var data = event.returnValues;
console.log("PlayerStatusChanged: " + data);
rank();
})
.on("error", function(error) {
var text = error;
console.log("PlayerStatusChanged: " + text);
})
.on("error", console.error);
expiration();
getPlayers();
submissions();
rank();
});

360
frontend/style.css 100644
View File

@ -0,0 +1,360 @@
body {
margin: 0;
overflow: hidden;
font-family: 'Roboto Mono', monospace;
color: #FFFFFF;
}
.top {
background: #45484d;
background: linear-gradient(to right, #000000 220px,#2D3436 50%,#2D3436 calc(50% + 220px),#000000 100%);
position: absolute;
display: flex;
width: 100%;
height: 70px;
}
.top_logo {
width: 220px;
align-items: center;
display: flex;
font-size: 32px;
justify-content: center;
}
.top_left {
width: calc(50% - 205px);
align-items: center;
display: flex;
}
.top_center {
width: 190px;
align-items: center;
display: flex;
justify-content: center;
}
.top_right {
width: calc(50% - 205px);
align-items: center;
display: flex;
justify-content: flex-end;
}
.left {
background-color: #000000;
position: absolute;
left: 0; top: 70px; bottom: 0;
width: 220px;
overflow-y: auto;
scrollbar-width: none; /* Firefox */
-ms-overflow-style: none; /* IE 10+ */
}
.left::-webkit-scrollbar {
display: none;
}
.main {
background: #bcbfbe;
background: radial-gradient(ellipse at center, #2D3436 0%,#000000 100%);
position: absolute;
left: 220px; top: 70px; right: 0; bottom: 0;
}
.left .activated {
border: 1px solid #2FA232;
background: #FFFFFF;
color: #2FA232;
}
.topitem {
display: inline-flex;
align-items: center;
white-space: nowrap;
}
#playerName {
border: 0;
border-radius: 0.25rem;
font-family: 'Roboto Mono', monospace;
line-height: 1.5;
white-space: nowrap;
text-decoration: none;
margin: 10px 0;
padding: 0.25rem 0.5rem;
width: 93%;
}
#register {
border: 0;
border-radius: 0.25rem;
color: #FFFFFF;
background: #2FA232;
background: linear-gradient(225deg, #166D3B 0%, #2FA232 100%);
font-family: 'Roboto Mono', monospace;
font-size: 1rem;
line-height: 1.5;
white-space: nowrap;
text-decoration: none;
margin: 10px 0;
padding: 0.25rem 0.5rem;
width: 100%;
cursor: pointer;
}
#register:hover {
background: #2FA232;
background: linear-gradient(45deg, #166D3B 0%, #2FA232 100%);
}
.left-content {
margin: 14px;
}
#header_uppercase {
font-size: 13px;
color: #CCCCCC;
font-family: monospace;
white-space: nowrap;
}
#account_enable {
border: 1px solid #dcdcdc;
border-radius: 0.25rem;
background: #f4f6f7;
font-family: 'Roboto Mono', monospace;
font-size: 1rem;
white-space: nowrap;
text-decoration: none;
padding: 0.25rem 0.5rem;
margin: 10px 0;
width: 100%;
cursor: pointer;
}
#account_enable:hover {
background: #fafafb;
border-color: #2FA232;
color: #2FA232;
}
#metamask {
width: 20px;
vertical-align: text-top;
padding-right: 10px;
}
/* The Modal (background) */
.modal {
display: none; /* Hidden by default */
position: fixed; /* Stay in place */
z-index: 1; /* Sit on top */
left: 0;
top: 0;
width: 100%; /* Full width */
height: 100%; /* Full height */
overflow: auto; /* Enable scroll if needed */
background-color: rgb(0,0,0); /* Fallback color */
background-color: rgba(0,0,0,0.4); /* Black w/ opacity */
}
/* Modal Content */
.modal-content {
position: relative;
background-color: #000000;
margin: 70px auto;
padding: 0;
max-width: 800px;
border: 3px solid #2FA232;
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19);
-webkit-animation-name: animatetop;
-webkit-animation-duration: 0.4s;
animation-name: animatetop;
animation-duration: 0.4s
}
/* Add Animation */
@-webkit-keyframes animatetop {
from {top:-300px; opacity:0}
to {top:0; opacity:1}
}
@keyframes animatetop {
from {top:-300px; opacity:0}
to {top:0; opacity:1}
}
/* The Close Button */
.close {
float: right;
font-size: 28px;
}
.close:hover,
.close:focus {
color: #BBBBBB;
text-decoration: none;
cursor: pointer;
}
.modal-header {
padding: 1px 12px 1px 24px;
}
.modal-body {padding: 2px 16px;}
.modal-footer {
padding: 16px;
}
.top a:link, .top a:visited, .top a:hover, .top a:active {
color: white;
text-decoration: none;
}
hr {
display: block;
height: 1px;
border: 0;
border-top: 1px solid #d8dadb;
margin-top: 1em;
padding: 0;
}
.buttons {
text-align: center;
}
.buttons i {
font-size: 24px;
margin: 2px 0;
background-color: #2FA232;
background-image: linear-gradient(45deg, #8929AD 0%, #2FA232 50%, #43B7B8 100%);
background-size: 100%;
-webkit-background-clip: text;
-moz-background-clip: text;
-webkit-text-fill-color: transparent;
-moz-text-fill-color: transparent;
}
li {
padding-bottom: 10px;
}
.form-switch {
display: inline-block;
cursor: pointer;
-webkit-tap-highlight-color: transparent;
}
.form-switch i {
position: relative;
display: inline-block;
width: 46px;
height: 26px;
background-color: #e6e6e6;
border-radius: 23px;
vertical-align: text-bottom;
transition: all 0.3s linear;
}
.form-switch i::before {
content: "";
position: absolute;
left: 0;
width: 42px;
height: 22px;
background-color: #fff;
border-radius: 11px;
transform: translate3d(2px, 2px, 0) scale3d(1, 1, 1);
transition: all 0.25s linear;
}
.form-switch i::after {
content: "";
position: absolute;
left: 0;
width: 22px;
height: 22px;
background-color: #fff;
border-radius: 11px;
box-shadow: 1px 1px 3px rgba(0, 0, 0, 0.6);
transform: translate3d(2px, 2px, 0);
transition: all 0.2s ease-in-out;
}
.form-switch:active i::after {
width: 28px;
transform: translate3d(2px, 2px, 0);
}
.form-switch:active input:checked + i::after { transform: translate3d(16px, 2px, 0); }
.form-switch input { display: none; }
.form-switch input:checked + i { background-color: #2FA232; }
.form-switch input:checked + i::before { transform: translate3d(18px, 2px, 0) scale3d(0, 0, 0); }
.form-switch input:checked + i::after { transform: translate3d(22px, 2px, 0); }
h1 {
font-size: 16px;
margin-top: 1em;
margin-bottom: 0;
margin-left: 0;
margin-right: 0;
color: #2FA232;
}
#loading {
position: absolute;
top: 50%;
left: 50%;
-ms-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
text-align: center;
text-shadow: 2px 2px 4px #FFFFFF;
}
.dropdown-p {
text-align: center;
}
#rank_list {
width: 90%;
border-collapse: collapse;
border-style: hidden;
text-align: center;
margin-left: auto;
margin-right: auto;
}
#rank_list th {
border: 1px solid #5f5f5f;
padding: 8px;
min-width: 40px;
}
#rank_list td {
border: 1px solid #5f5f5f;
padding: 8px;
min-width: 40px;
}
.center {
display: block;
margin-left: auto;
margin-right: auto;
width: 20%;
}
#rules {
color: #cccccc;
font-size: 14px;
}