Skip to content

Instantly share code, notes, and snippets.

@Pascal66
Created June 29, 2016 14:45
Show Gist options
  • Save Pascal66/3433d65c824f18f7e791140541c76487 to your computer and use it in GitHub Desktop.
Save Pascal66/3433d65c824f18f7e791140541c76487 to your computer and use it in GitHub Desktop.
contract TheBarCounter {
address private owner;
//Stored variables
uint private balance = 0;
uint private uniqueUsers = 0;
uint private usersProfits = 0;
uint private rescues = 0;
uint private collectedFees = 0;
uint private jumpFee = 10;
uint private baseMultiplier = 110;
uint private maxMultiplier = 200;
uint private payoutOrder = 0;
uint private rescueRecord = 0;
uint timeOfLastDeposit = now;
address private hero = 0x0;
mapping (address => User) private users;
Entry[] private entries;
event Jump(address who, uint deposit, uint payout);
event Rescue(address who, address saviour, uint payout);
event NewHero(address who);
//Set owner on contract creation
function TheBarCounter() {
owner = msg.sender;
}
modifier onlyowner { if (msg.sender == owner) _ }
struct User {
uint id;
address addr;
string nickname;
uint rescueCount;
uint rescueTokens;
}
struct Entry {
address entryAddress;
uint deposit;
uint payout;
uint tokens;
}
//Fallback function
function() {
init();
}
function init() private{
//Only deposits >0.1ETH are allowed to join
if (msg.value < 100 finney) {
return;
}
jumpIn();
//Prevent cheap trolls from reviving the pit if it dies (death = ~3months without deposits)
if (msg.value > 5)
timeOfLastDeposit = now;
}
//Join the pit
function jumpIn() private {
//Limit deposits to 50ETH
uint dValue = 100 finney;
if (msg.value > 50 ether) {
//Make sure we received the money before refunding the surplus
if (this.balance >= balance + collectedFees + msg.value)
msg.sender.send(msg.value - 50 ether);
dValue = 50 ether;
}
else { dValue = msg.value; }
//Add new users to the users array if he's a new player
addNewUser(msg.sender);
//Make sure that only up to 5 rescue tokens are spent at a time
uint tokensToUse = users[msg.sender].rescueTokens >= 5 ? 5 : users[msg.sender].rescueTokens;
uint tokensUsed = 0;
//Enforce lower payouts if too many people stuck in the pit
uint randMultiplier = rand(50);
uint currentEntries = entries.length - payoutOrder;
randMultiplier = currentEntries > 15 ? (randMultiplier / 2) : randMultiplier;
randMultiplier = currentEntries > 25 ? 0 : randMultiplier;
//Incentive to join if the pit is nearly empty (+50% random multiplier)
randMultiplier = currentEntries <= 5 && dValue <= 20 ? randMultiplier * 3 / 2 : randMultiplier;
//Calculate the optimal amount of rescue tokens to spend
while (tokensToUse > 0 && (baseMultiplier + randMultiplier + tokensUsed*10) < maxMultiplier)
{
tokensToUse--;
tokensUsed++;
}
uint finalMultiplier = (baseMultiplier + randMultiplier + tokensUsed*10);
if (finalMultiplier > maxMultiplier)
finalMultiplier = maxMultiplier;
//Add new entry to the entries array
if (msg.value < 50 ether)
entries.push(Entry(msg.sender, msg.value, (msg.value * (finalMultiplier) / 100), tokensUsed));
else
entries.push(Entry(msg.sender, 50 ether,((50 ether) * (finalMultiplier) / 100), tokensUsed));
//Trigger jump event
if (msg.value < 50 ether)
Jump(msg.sender, msg.value, (msg.value * (finalMultiplier) / 100));
else
Jump(msg.sender, 50 ether, ((50 ether) * (finalMultiplier) / 100));
users[msg.sender].rescueTokens -= tokensUsed;
//Collect fees and update contract balance
balance += (dValue * (100 - jumpFee)) / 100;
collectedFees += (dValue * jumpFee) / 100;
bool saviour = false;
//Pay pending entries if the new balance allows for it
while (balance > entries[payoutOrder].payout) {
saviour = false;
uint entryPayout = entries[payoutOrder].payout;
uint entryDeposit = entries[payoutOrder].deposit;
uint profit = entryPayout - entryDeposit;
uint saviourShare = 0;
//Give credit & reward for the rescue if the user saved someone else
if (users[msg.sender].addr != entries[payoutOrder].entryAddress)
{
users[msg.sender].rescueCount++;
//Double or triple token bonus if the user is taking a moderate/high risk to help those trapped
if (entryDeposit >= 1 ether) {
users[msg.sender].rescueTokens += dValue < 20 || currentEntries < 15 ? 1 : 2;
users[msg.sender].rescueTokens += dValue < 40 || currentEntries < 25 ? 0 : 1;
}
saviour = true;
}
bool isHero = false;
isHero = entries[payoutOrder].entryAddress == hero;
//Share profit with saviour if the gain is substantial enough and the saviour invested enough (hero exempt)
if (saviour && !isHero && profit > 20 * entryDeposit / 100 && profit > 100 finney && dValue >= 5 ether)
{
if (dValue < 10 ether)
saviourShare = 3 + rand(5);
else if (dValue >= 10 ether && dValue < 25 ether)
saviourShare = 7 + rand(8);
else if (dValue >= 25 ether && dValue < 40 ether)
saviourShare = 12 + rand(13);
else if (dValue >= 40 ether)
saviourShare = rand(50);
saviourShare *= profit / 100;
msg.sender.send(saviourShare);
}
uint payout = entryPayout - saviourShare;
entries[payoutOrder].entryAddress.send(payout);
//Trigger rescue event
Rescue(entries[payoutOrder].entryAddress, msg.sender, payout);
balance -= entryPayout;
usersProfits += entryPayout;
rescues++;
payoutOrder++;
}
//Check for new Hero of the Pit
if (saviour && users[msg.sender].rescueCount > rescueRecord)
{
rescueRecord = users[msg.sender].rescueCount;
hero = msg.sender;
//Trigger new hero event
NewHero(msg.sender);
}
}
//Generate random number between 1 & max
uint256 constant private FACTOR = 1157920892373161954235709850086879078532699846656405640394575840079131296399;
function rand(uint max) constant private returns (uint256 result){
uint256 factor = FACTOR * 100 / max;
uint256 lastBlockNumber = block.number - 1;
uint256 hashVal = uint256(block.blockhash(lastBlockNumber));
return uint256((uint256(hashVal) / factor)) % max + 1;
}
function addNewUser(address Address) private
{
if (users[Address].addr == address(0))
{
users[Address].id = ++uniqueUsers;
users[Address].addr = Address;
users[Address].nickname = 'UnnamedPlayer';
users[Address].rescueCount = 0;
users[Address].rescueTokens = 0;
}
}
//Transfer earnings from fees to the owner
function collectFees() onlyowner {
if (collectedFees == 0) throw;
owner.send(collectedFees);
collectedFees = 0;
}
//Contract management
function changeOwner(address newOwner) onlyowner {
owner = newOwner;
}
function changeBaseMultiplier(uint multi) onlyowner {
if (multi < 110 || multi > 150) throw;
baseMultiplier = multi;
}
function changeMaxMultiplier(uint multi) onlyowner {
if (multi < 200 || multi > 300) throw;
maxMultiplier = multi;
}
function changeFee(uint fee) onlyowner {
if (fee < 0 || fee > 10) throw;
jumpFee = fee;
}
//JSON functions
function setNickname(string name) {
addNewUser(msg.sender);
if (bytes(name).length >= 2 && bytes(name).length <= 16)
users[msg.sender].nickname = name;
}
function currentBalance() constant returns (uint pitBalance, string info) {
pitBalance = balance / 1 finney;
info = 'The balance of the pit in Finneys (contract balance minus fees).';
}
function heroOfThePit() constant returns (address theHero, string nickname, uint peopleSaved, string info) {
theHero = hero;
nickname = users[theHero].nickname;
peopleSaved = rescueRecord;
info = 'The current rescue record holder. All hail!';
}
function userName(address Address) constant returns (string nickname) {
nickname = users[Address].nickname;
}
function totalRescues() constant returns (uint rescueCount, string info) {
rescueCount = rescues;
info = 'The number of times that people have been rescued from the pit (aka the number of times people made a profit).';
}
function multipliers() constant returns (uint BaseMultiplier, uint MaxMultiplier, string info) {
BaseMultiplier = baseMultiplier;
MaxMultiplier = maxMultiplier;
info = 'The multipliers applied to all deposits: the final multiplier is a random number between the multpliers shown divided by 100. By default x1.1~x1.5 (up to x2 if rescue tokens are used, granting +0.1 per token). It determines the amount of money you will get when rescued (a saviour share might be deducted).';
}
function pitFee() constant returns (uint feePercentage, string info) {
feePercentage = jumpFee;
info = 'The fee percentage applied to all deposits. It can change to speed payouts (max 10%).';
}
function nextPayoutGoal() constant returns (uint finneys, string info) {
finneys = (entries[payoutOrder].payout - balance) / 1 finney;
info = 'The amount of Finneys (Ethers * 1000) that need to be deposited for the next payout to be executed.';
}
function unclaimedFees() constant returns (uint ethers, string info) {
ethers = collectedFees / 1 ether;
info = 'The amount of Ethers obtained through fees that have not yet been collected by the owner.';
}
function totalEntries() constant returns (uint count, string info) {
count = entries.length;
info = 'The number of times that people have jumped into the pit.';
}
function totalUsers() constant returns (uint users, string info) {
users = uniqueUsers;
info = 'The number of unique users that have joined the pit.';
}
function awaitingPayout() constant returns (uint count, string info) {
count = entries.length - payoutOrder;
info = 'The number of people waiting to be saved.';
}
function entryDetails(uint index) constant returns (address user, string nickName, uint deposit, uint payout, uint tokensUsed, string info)
{
if (index <= entries.length) {
user = entries[index].entryAddress;
nickName = users[entries[index].entryAddress].nickname;
deposit = entries[index].deposit / 1 finney;
payout = entries[index].payout / 1 finney;
tokensUsed = entries[index].tokens;
info = 'Entry info: user address, name, expected payout in Finneys (approximate), rescue tokens used.';
}
}
function userId(address user) constant returns (uint id, string info) {
id = users[user].id;
info = 'The id of the user, represents the order in which he first joined the pit.';
}
function userTokens(address user) constant returns (uint tokens, string info) {
tokens = users[user].addr != address(0x0) ? users[user].rescueTokens : 0;
info = 'The number of Rescue Tokens the user has. Tokens are awarded when your deposits save people, and used automatically on your next deposit. They provide a 0.1 multiplier increase per token. (+0.5 max)';
}
function userRescues(address user) constant returns(uint rescueCount, string info) {
rescueCount = users[user].addr != address(0x0) ? users[user].rescueCount : 0;
info = 'The number of times the user has rescued someone from the pit.';
}
function userProfits() constant returns(uint profits, string info) {
profits = usersProfits / 1 finney;
info = 'The combined earnings of all users in Finney.';
}
//Destroy the contract after ~3 months of inactivity at the owner's discretion
function recycle() onlyowner
{
if (now >= timeOfLastDeposit + 10 weeks)
{
//Refund the current balance
if (balance > 0)
{
entries[0].entryAddress.send(balance);
}
//Destroy the contract
selfdestruct(owner);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment