pragma solidity 0.4.24;
// File: contracts/BytesLib.sol
library BytesLib {
function concat(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bytes) {
bytes memory tempBytes;
assembly {
// Get a location of some free memory and store it in tempBytes as
// Solidity does for memory variables.
tempBytes := mload(0x40)
// Store the length of the first bytes array at the beginning of
// the memory for tempBytes.
let length := mload(_preBytes)
mstore(tempBytes, length)
// Maintain a memory counter for the current write location in the
// temp bytes array by adding the 32 bytes for the array length to
// the starting location.
let mc := add(tempBytes, 0x20)
// Stop copying when the memory counter reaches the length of the
// first bytes array.
let end := add(mc, length)
for {
// Initialize a copy counter to the start of the _preBytes data,
// 32 bytes into its memory.
let cc := add(_preBytes, 0x20)
} lt(mc, end) {
// Increase both counters by 32 bytes each iteration.
mc := add(mc, 0x20)
cc := add(cc, 0x20)
} {
// Write the _preBytes data into the tempBytes memory 32 bytes
// at a time.
mstore(mc, mload(cc))
}
// Add the length of _postBytes to the current length of tempBytes
// and store it as the new length in the first 32 bytes of the
// tempBytes memory.
length := mload(_postBytes)
mstore(tempBytes, add(length, mload(tempBytes)))
// Move the memory counter back from a multiple of 0x20 to the
// actual end of the _preBytes data.
mc := end
// Stop copying when the memory counter reaches the new combined
// length of the arrays.
end := add(mc, length)
for {
let cc := add(_postBytes, 0x20)
} lt(mc, end) {
mc := add(mc, 0x20)
cc := add(cc, 0x20)
} {
mstore(mc, mload(cc))
}
// Update the free-memory pointer by padding our last write location
// to 32 bytes: add 31 bytes to the end of tempBytes to move to the
// next 32 byte block, then round down to the nearest multiple of
// 32. If the sum of the length of the two arrays is zero then add
// one before rounding down to leave a blank 32 bytes (the length block with 0).
mstore(0x40, and(
add(add(end, iszero(add(length, mload(_preBytes)))), 31),
not(31) // Round down to the nearest 32 bytes.
))
}
return tempBytes;
}
function concatStorage(bytes storage _preBytes, bytes memory _postBytes) internal {
assembly {
// Read the first 32 bytes of _preBytes storage, which is the length
// of the array. (We don't need to use the offset into the slot
// because arrays use the entire slot.)
let fslot := sload(_preBytes_slot)
// Arrays of 31 bytes or less have an even value in their slot,
// while longer arrays have an odd value. The actual length is
// the slot divided by two for odd values, and the lowest order
// byte divided by two for even values.
// If the slot is even, bitwise and the slot with 255 and divide by
// two to get the length. If the slot is odd, bitwise and the slot
// with -1 and divide by two.
let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2)
let mlength := mload(_postBytes)
let newlength := add(slength, mlength)
// slength can contain both the length and contents of the array
// if length < 32 bytes so let's prepare for that
// v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage
switch add(lt(slength, 32), lt(newlength, 32))
case 2 {
// Since the new array still fits in the slot, we just need to
// update the contents of the slot.
// uint256(bytes_storage) = uint256(bytes_storage) + uint256(bytes_memory) + new_length
sstore(
_preBytes_slot,
// all the modifications to the slot are inside this
// next block
add(
// we can just add to the slot contents because the
// bytes we want to change are the LSBs
fslot,
add(
mul(
div(
// load the bytes from memory
mload(add(_postBytes, 0x20)),
// zero all bytes to the right
exp(0x100, sub(32, mlength))
),
// and now shift left the number of bytes to
// leave space for the length in the slot
exp(0x100, sub(32, newlength))
),
// increase length by the double of the memory
// bytes length
mul(mlength, 2)
)
)
)
}
case 1 {
// The stored value fits in the slot, but the combined value
// will exceed it.
// get the keccak hash to get the contents of the array
mstore(0x0, _preBytes_slot)
let sc := add(keccak256(0x0, 0x20), div(slength, 32))
// save new length
sstore(_preBytes_slot, add(mul(newlength, 2), 1))
// The contents of the _postBytes array start 32 bytes into
// the structure. Our first read should obtain the `submod`
// bytes that can fit into the unused space in the last word
// of the stored array. To get this, we read 32 bytes starting
// from `submod`, so the data we read overlaps with the array
// contents by `submod` bytes. Masking the lowest-order
// `submod` bytes allows us to add that value directly to the
// stored value.
let submod := sub(32, slength)
let mc := add(_postBytes, submod)
let end := add(_postBytes, mlength)
let mask := sub(exp(0x100, submod), 1)
sstore(
sc,
add(
and(
fslot,
0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00
),
and(mload(mc), mask)
)
)
for {
mc := add(mc, 0x20)
sc := add(sc, 1)
} lt(mc, end) {
sc := add(sc, 1)
mc := add(mc, 0x20)
} {
sstore(sc, mload(mc))
}
mask := exp(0x100, sub(mc, end))
sstore(sc, mul(div(mload(mc), mask), mask))
}
default {
// get the keccak hash to get the contents of the array
mstore(0x0, _preBytes_slot)
// Start copying to the last used word of the stored array.
let sc := add(keccak256(0x0, 0x20), div(slength, 32))
// save new length
sstore(_preBytes_slot, add(mul(newlength, 2), 1))
// Copy over the first `submod` bytes of the new data as in
// case 1 above.
let slengthmod := mod(slength, 32)
let mlengthmod := mod(mlength, 32)
let submod := sub(32, slengthmod)
let mc := add(_postBytes, submod)
let end := add(_postBytes, mlength)
let mask := sub(exp(0x100, submod), 1)
sstore(sc, add(sload(sc), and(mload(mc), mask)))
for {
sc := add(sc, 1)
mc := add(mc, 0x20)
} lt(mc, end) {
sc := add(sc, 1)
mc := add(mc, 0x20)
} {
sstore(sc, mload(mc))
}
mask := exp(0x100, sub(mc, end))
sstore(sc, mul(div(mload(mc), mask), mask))
}
}
}
function slice(bytes _bytes, uint _start, uint _length) internal pure returns (bytes) {
require(_bytes.length >= (_start + _length));
bytes memory tempBytes;
assembly {
switch iszero(_length)
case 0 {
// Get a location of some free memory and store it in tempBytes as
// Solidity does for memory variables.
tempBytes := mload(0x40)
// The first word of the slice result is potentially a partial
// word read from the original array. To read it, we calculate
// the length of that partial word and start copying that many
// bytes into the array. The first word we copy will start with
// data we don't care about, but the last `lengthmod` bytes will
// land at the beginning of the contents of the new array. When
// we're done copying, we overwrite the full first word with
// the actual length of the slice.
let lengthmod := and(_length, 31)
// The multiplication in the next line is necessary
// because when slicing multiples of 32 bytes (lengthmod == 0)
// the following copy loop was copying the origin's length
// and then ending prematurely not copying everything it should.
let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod)))
let end := add(mc, _length)
for {
// The multiplication in the next line has the same exact purpose
// as the one above.
let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start)
} lt(mc, end) {
mc := add(mc, 0x20)
cc := add(cc, 0x20)
} {
mstore(mc, mload(cc))
}
mstore(tempBytes, _length)
//update free-memory pointer
//allocating the array padded to 32 bytes like the compiler does now
mstore(0x40, and(add(mc, 31), not(31)))
}
//if we want a zero-length slice let's just return a zero-length array
default {
tempBytes := mload(0x40)
mstore(0x40, add(tempBytes, 0x20))
}
}
return tempBytes;
}
function toAddress(bytes _bytes, uint _start) internal pure returns (address) {
require(_bytes.length >= (_start + 20));
address tempAddress;
assembly {
tempAddress := div(mload(add(add(_bytes, 0x20), _start)), 0x1000000000000000000000000)
}
return tempAddress;
}
function toUint(bytes _bytes, uint _start) internal pure returns (uint256) {
require(_bytes.length >= (_start + 32));
uint256 tempUint;
assembly {
tempUint := mload(add(add(_bytes, 0x20), _start))
}
return tempUint;
}
function equal(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bool) {
bool success = true;
assembly {
let length := mload(_preBytes)
// if lengths don't match the arrays are not equal
switch eq(length, mload(_postBytes))
case 1 {
// cb is a circuit breaker in the for loop since there's
// no said feature for inline assembly loops
// cb = 1 - don't breaker
// cb = 0 - break
let cb := 1
let mc := add(_preBytes, 0x20)
let end := add(mc, length)
for {
let cc := add(_postBytes, 0x20)
// the next line is the loop condition:
// while(uint(mc < end) + cb == 2)
} eq(add(lt(mc, end), cb), 2) {
mc := add(mc, 0x20)
cc := add(cc, 0x20)
} {
// if any of these checks fails then arrays are not equal
if iszero(eq(mload(mc), mload(cc))) {
// unsuccess:
success := 0
cb := 0
}
}
}
default {
// unsuccess:
success := 0
}
}
return success;
}
function equalStorage(bytes storage _preBytes, bytes memory _postBytes) internal view returns (bool) {
bool success = true;
assembly {
// we know _preBytes_offset is 0
let fslot := sload(_preBytes_slot)
// Decode the length of the stored array like in concatStorage().
let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2)
let mlength := mload(_postBytes)
// if lengths don't match the arrays are not equal
switch eq(slength, mlength)
case 1 {
// slength can contain both the length and contents of the array
// if length < 32 bytes so let's prepare for that
// v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage
if iszero(iszero(slength)) {
switch lt(slength, 32)
case 1 {
// blank the last byte which is the length
fslot := mul(div(fslot, 0x100), 0x100)
if iszero(eq(fslot, mload(add(_postBytes, 0x20)))) {
// unsuccess:
success := 0
}
}
default {
// cb is a circuit breaker in the for loop since there's
// no said feature for inline assembly loops
// cb = 1 - don't breaker
// cb = 0 - break
let cb := 1
// get the keccak hash to get the contents of the array
mstore(0x0, _preBytes_slot)
let sc := keccak256(0x0, 0x20)
let mc := add(_postBytes, 0x20)
let end := add(mc, mlength)
// the next line is the loop condition:
// while(uint(mc < end) + cb == 2)
for {} eq(add(lt(mc, end), cb), 2) {
sc := add(sc, 1)
mc := add(mc, 0x20)
} {
if iszero(eq(sload(sc), mload(mc))) {
// unsuccess:
success := 0
cb := 0
}
}
}
}
}
default {
// unsuccess:
success := 0
}
}
return success;
}
}
// File: contracts/Token.sol
// Abstract contract for the full ERC 20 Token standard
// https://github.com/ethereum/EIPs/issues/20
pragma solidity 0.4.24;
contract Token {
/* This is a slight change to the ERC20 base standard.
function totalSupply() constant returns (uint256 supply);
is replaced with:
uint256 public totalSupply;
This automatically creates a getter function for the totalSupply.
This is moved to the base contract since public getter functions are not
currently recognised as an implementation of the matching abstract
function by the compiler.
*/
/// total amount of tokens
uint256 public totalSupply;
/// @param _owner The address from which the balance will be retrieved
/// @return The balance
function balanceOf(address _owner) constant public returns (uint256 balance);
/// @notice send `_value` token to `_to` from `msg.sender`
/// @param _to The address of the recipient
/// @param _value The amount of token to be transferred
/// @return Whether the transfer was successful or not
function transfer(address _to, uint256 _value) public returns (bool success);
/// @notice send `_value` token to `_to` from `_from` on the condition it is approved by `_from`
/// @param _from The address of the sender
/// @param _to The address of the recipient
/// @param _value The amount of token to be transferred
/// @return Whether the transfer was successful or not
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success);
/// @notice `msg.sender` approves `_spender` to spend `_value` tokens
/// @param _spender The address of the account able to transfer the tokens
/// @param _value The amount of tokens to be approved for transfer
/// @return Whether the approval was successful or not
function approve(address _spender, uint256 _value) public returns (bool success);
/// @param _owner The address of the account owning tokens
/// @param _spender The address of the account able to transfer the tokens
/// @return Amount of remaining tokens allowed to spent
function allowance(address _owner, address _spender) public constant returns (uint256 remaining);
event Transfer(address indexed _from, address indexed _to, uint256 _value);
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
}
// File: contracts/StandardToken.sol
/*
You should inherit from StandardToken or, for a token like you would want to
deploy in something like Mist, see HumanStandardToken.sol.
(This implements ONLY the standard functions and NOTHING else.
If you deploy this, you won't have anything useful.)
Implements ERC 20 Token standard: https://github.com/ethereum/EIPs/issues/20
.*/
pragma solidity 0.4.24;
contract StandardToken is Token {
function transfer(address _to, uint256 _value) public returns (bool success) {
//Default assumes totalSupply can't be over max (2^256 - 1).
//If your token leaves out totalSupply and can issue more tokens as time goes on, you need to check if it doesn't wrap.
//Replace the if with this one instead.
//require(balances[msg.sender] >= _value && balances[_to] + _value > balances[_to]);
require(balances[msg.sender] >= _value);
balances[msg.sender] -= _value;
balances[_to] += _value;
emit Transfer(msg.sender, _to, _value);
return true;
}
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
//same as above. Replace this line with the following if you want to protect against wrapping uints.
//require(balances[_from] >= _value && allowed[_from][msg.sender] >= _value && balances[_to] + _value > balances[_to]);
require(balances[_from] >= _value && allowed[_from][msg.sender] >= _value);
balances[_to] += _value;
balances[_from] -= _value;
allowed[_from][msg.sender] -= _value;
emit Transfer(_from, _to, _value);
return true;
}
function balanceOf(address _owner) public constant returns (uint256 balance) {
return balances[_owner];
}
function approve(address _spender, uint256 _value) public returns (bool success) {
allowed[msg.sender][_spender] = _value;
emit Approval(msg.sender, _spender, _value);
return true;
}
function allowance(address _owner, address _spender) public constant returns (uint256 remaining) {
return allowed[_owner][_spender];
}
mapping (address => uint256) balances;
mapping (address => mapping (address => uint256)) allowed;
}
// File: contracts/HumanStandardToken.sol
/*
This Token Contract implements the standard token functionality (https://github.com/ethereum/EIPs/issues/20) as well as the following OPTIONAL extras intended for use by humans.
In other words. This is intended for deployment in something like a Token Factory or Mist wallet, and then used by humans.
Imagine coins, currencies, shares, voting weight, etc.
Machine-based, rapid creation of many tokens would not necessarily need these extra features or will be minted in other manners.
1) Initial Finite Supply (upon creation one specifies how much is minted).
2) In the absence of a token registry: Optional Decimal, Symbol & Name.
3) Optional approveAndCall() functionality to notify a contract if an approval() has occurred.
.*/
pragma solidity 0.4.24;
contract HumanStandardToken is StandardToken {
/* Public variables of the token */
/*
NOTE:
The following variables are OPTIONAL vanities. One does not have to include them.
They allow one to customise the token contract & in no way influences the core functionality.
Some wallets/interfaces might not even bother to look at this information.
*/
string public name; //fancy name: eg Simon Bucks
uint8 public decimals; //How many decimals to show. ie. There could 1000 base units with 3 decimals. Meaning 0.980 SBX = 980 base units. It's like comparing 1 wei to 1 ether.
string public symbol; //An identifier: eg SBX
string public version = 'H0.1'; //human 0.1 standard. Just an arbitrary versioning scheme.
constructor(
uint256 _initialAmount,
string _tokenName,
uint8 _decimalUnits,
string _tokenSymbol
) public {
balances[msg.sender] = _initialAmount; // Give the creator all initial tokens
totalSupply = _initialAmount; // Update total supply
name = _tokenName; // Set the name for display purposes
decimals = _decimalUnits; // Amount of decimals for display purposes
symbol = _tokenSymbol; // Set the symbol for display purposes
}
/* Approves and then calls the receiving contract */
function approveAndCall(address _spender, uint256 _value, bytes _extraData) public returns (bool success) {
allowed[msg.sender][_spender] = _value;
emit Approval(msg.sender, _spender, _value);
//call the receiveApproval function on the contract you want to be notified. This crafts the function signature manually so one doesn't have to include a contract in here just for this.
//receiveApproval(address _from, uint256 _value, address _tokenContract, bytes _extraData)
//it is assumed that when does this that the call *should* succeed, otherwise one would use vanilla approve instead.
require(_spender.call(bytes4(bytes32(keccak256("receiveApproval(address,uint256,address,bytes)"))), msg.sender, _value, this, _extraData));
return true;
}
}
// File: contracts/OZ_Ownable.sol
/**
* @title Ownable
* @dev The Ownable contract has an owner address, and provides basic authorization control
* functions, this simplifies the implementation of "user permissions".
*/
contract Ownable {
address public owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev The Ownable constructor sets the original `owner` of the contract to the sender
* account.
*/
constructor() public {
owner = msg.sender;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(msg.sender == owner);
_;
}
/**
* @dev Allows the current owner to transfer control of the contract to a newOwner.
* @param newOwner The address to transfer ownership to.
*/
function transferOwnership(address newOwner) public onlyOwner {
require(newOwner != address(0));
emit OwnershipTransferred(owner, newOwner);
owner = newOwner;
}
}
// File: contracts/SafeMath.sol
/**
* @title SafeMath
* @dev Math operations with safety checks that throw on error
*/
library SafeMath {
function mul(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a * b;
assert(a == 0 || c / a == b);
return c;
}
function div(uint256 a, uint256 b) internal pure returns (uint256) {
// assert(b > 0); // Solidity automatically throws when dividing by 0
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold
return c;
}
function sub(uint256 a, uint256 b) internal pure returns (uint256) {
assert(b <= a);
return a - b;
}
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
assert(c >= a);
return c;
}
}
// File: contracts/MintAndBurnToken.sol
/**
* @title Mintable token
* @dev Simple ERC20 Token example, with mintable token creation
* @dev Issue: * https://github.com/OpenZeppelin/zeppelin-solidity/issues/120
* Based on code by TokenMarketNet: https://github.com/TokenMarketNet/ico/blob/master/contracts/MintableToken.sol
*/
contract MintAndBurnToken is StandardToken, Ownable {
event Mint(address indexed to, uint256 amount);
event MintFinished();
bool public mintingFinished = false;
modifier canMint() {
require(!mintingFinished);
_;
}
/* Public variables of the token */
/*
NOTE:
The following variables are OPTIONAL vanities. One does not have to include them.
They allow one to customise the token contract & in no way influences the core functionality.
Some wallets/interfaces might not even bother to look at this information.
*/
string public name; //fancy name: eg Simon Bucks
uint8 public decimals; //How many decimals to show. ie. There could 1000 base units with 3 decimals. Meaning 0.980 SBX = 980 base units. It's like comparing 1 wei to 1 ether.
string public symbol; //An identifier: eg SBX
string public version = 'H0.1'; //human 0.1 standard. Just an arbitrary versioning scheme.
constructor(
string _tokenName,
uint8 _decimalUnits,
string _tokenSymbol
) public {
name = _tokenName; // Set the name for display purposes
decimals = _decimalUnits; // Amount of decimals for display purposes
symbol = _tokenSymbol; // Set the symbol for display purposes
}
/**
* @dev Function to mint tokens
* @param _to The address that will receive the minted tokens.
* @param _amount The amount of tokens to mint.
* @return A boolean that indicates if the operation was successful.
*/
function mint(address _to, uint256 _amount) onlyOwner canMint public returns (bool) {
totalSupply = SafeMath.add(_amount, totalSupply);
balances[_to] = SafeMath.add(_amount,balances[_to]);
emit Mint(_to, _amount);
emit Transfer(address(0), _to, _amount);
return true;
}
/**
* @dev Function to stop minting new tokens.
* @return True if the operation was successful.
*/
function finishMinting() onlyOwner canMint public returns (bool) {
mintingFinished = true;
emit MintFinished();
return true;
}
// -----------------------------------
// BURN FUNCTIONS BELOW
// https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/token/ERC20/BurnableToken.sol
// -----------------------------------
event Burn(address indexed burner, uint256 value);
/**
* @dev Burns a specific amount of tokens.
* @param _value The amount of token to be burned.
*/
function burn(uint256 _value) onlyOwner public {
_burn(msg.sender, _value);
}
function _burn(address _who, uint256 _value) internal {
require(_value <= balances[_who]);
// no need to require value <= totalSupply, since that would imply the
// sender's balance is greater than the totalSupply, which *should* be an assertion failure
balances[_who] = SafeMath.sub(balances[_who],_value);
totalSupply = SafeMath.sub(totalSupply,_value);
emit Burn(_who, _value);
emit Transfer(_who, address(0), _value);
}
}
// File: contracts/SpankBank.sol
contract SpankBank {
using BytesLib for bytes;
using SafeMath for uint256;
event SpankBankCreated(
uint256 periodLength,
uint256 maxPeriods,
address spankAddress,
uint256 initialBootySupply,
string bootyTokenName,
uint8 bootyDecimalUnits,
string bootySymbol
);
event StakeEvent(
address staker,
uint256 period,
uint256 spankPoints,
uint256 spankAmount,
uint256 stakePeriods,
address delegateKey,
address bootyBase
);
event SendFeesEvent (
address sender,
uint256 bootyAmount
);
event MintBootyEvent (
uint256 targetBootySupply,
uint256 totalBootySupply
);
event CheckInEvent (
address staker,
uint256 period,
uint256 spankPoints,
uint256 stakerEndingPeriod
);
event ClaimBootyEvent (
address staker,
uint256 period,
uint256 bootyOwed
);
event WithdrawStakeEvent (
address staker,
uint256 totalSpankToWithdraw
);
event SplitStakeEvent (
address staker,
address newAddress,
address newDelegateKey,
address newBootyBase,
uint256 spankAmount
);
event VoteToCloseEvent (
address staker,
uint256 period
);
event UpdateDelegateKeyEvent (
address staker,
address newDelegateKey
);
event UpdateBootyBaseEvent (
address staker,
address newBootyBase
);
event ReceiveApprovalEvent (
address from,
address tokenContract
);
/***********************************
VARIABLES SET AT CONTRACT DEPLOYMENT
************************************/
// GLOBAL CONSTANT VARIABLES
uint256 public periodLength; // time length of each period in seconds
uint256 public maxPeriods; // the maximum # of periods a staker can stake for
uint256 public totalSpankStaked; // the total SPANK staked across all stakers
bool public isClosed; // true if voteToClose has passed, allows early withdrawals
// ERC-20 BASED TOKEN WITH SOME ADDED PROPERTIES FOR HUMAN READABILITY
// https://github.com/ConsenSys/Tokens/blob/master/contracts/HumanStandardToken.sol
HumanStandardToken public spankToken;
MintAndBurnToken public bootyToken;
// LOOKUP TABLE FOR SPANKPOINTS BY PERIOD
// 1 -> 45%
// 2 -> 50%
// ...
// 12 -> 100%
mapping(uint256 => uint256) public pointsTable;
/*************************************
INTERAL ACCOUNTING
**************************************/
uint256 public currentPeriod = 0;
struct Staker {
uint256 spankStaked; // the amount of spank staked
uint256 startingPeriod; // the period this staker started staking
uint256 endingPeriod; // the period after which this stake expires
mapping(uint256 => uint256) spankPoints; // the spankPoints per period
mapping(uint256 => bool) didClaimBooty; // true if staker claimed BOOTY for that period
mapping(uint256 => bool) votedToClose; // true if staker voted to close for that period
address delegateKey; // address used to call checkIn and claimBooty
address bootyBase; // destination address to receive BOOTY
}
mapping(address => Staker) public stakers;
struct Period {
uint256 bootyFees; // the amount of BOOTY collected in fees
uint256 totalSpankPoints; // the total spankPoints of all stakers
uint256 bootyMinted; // the amount of BOOTY minted
bool mintingComplete; // true if BOOTY has already been minted for this period
uint256 startTime; // the starting unix timestamp in seconds
uint256 endTime; // the ending unix timestamp in seconds
uint256 closingVotes; // the total votes to close this period
}
mapping(uint256 => Period) public periods;
mapping(address => address) public stakerByDelegateKey;
modifier SpankBankIsOpen() {
require(isClosed == false);
_;
}
constructor (
uint256 _periodLength,
uint256 _maxPeriods,
address spankAddress,
uint256 initialBootySupply,
string bootyTokenName,
uint8 bootyDecimalUnits,
string bootySymbol
) public {
periodLength = _periodLength;
maxPeriods = _maxPeriods;
spankToken = HumanStandardToken(spankAddress);
bootyToken = new MintAndBurnToken(bootyTokenName, bootyDecimalUnits, bootySymbol);
bootyToken.mint(this, initialBootySupply);
uint256 startTime = now;
periods[currentPeriod].startTime = startTime;
periods[currentPeriod].endTime = SafeMath.add(startTime, periodLength);
bootyToken.transfer(msg.sender, initialBootySupply);
// initialize points table
pointsTable[0] = 0;
pointsTable[1] = 45;
pointsTable[2] = 50;
pointsTable[3] = 55;
pointsTable[4] = 60;
pointsTable[5] = 65;
pointsTable[6] = 70;
pointsTable[7] = 75;
pointsTable[8] = 80;
pointsTable[9] = 85;
pointsTable[10] = 90;
pointsTable[11] = 95;
pointsTable[12] = 100;
emit SpankBankCreated(_periodLength, _maxPeriods, spankAddress, initialBootySupply, bootyTokenName, bootyDecimalUnits, bootySymbol);
}
// Used to create a new staking position - verifies that the caller is not staking
function stake(uint256 spankAmount, uint256 stakePeriods, address delegateKey, address bootyBase) SpankBankIsOpen public {
doStake(msg.sender, spankAmount, stakePeriods, delegateKey, bootyBase);
}
function doStake(address stakerAddress, uint256 spankAmount, uint256 stakePeriods, address delegateKey, address bootyBase) internal {
updatePeriod();
require(stakePeriods > 0 && stakePeriods <= maxPeriods, "stake not between zero and maxPeriods"); // stake 1-12 (max) periods
require(spankAmount > 0, "stake is 0"); // stake must be greater than 0
// the staker must not have an active staking position
require(stakers[stakerAddress].startingPeriod == 0, "staker already exists");
// transfer SPANK to this contract - assumes sender has already "allowed" the spankAmount
require(spankToken.transferFrom(stakerAddress, this, spankAmount));
stakers[stakerAddress] = Staker(spankAmount, currentPeriod + 1, currentPeriod + stakePeriods, delegateKey, bootyBase);
_updateNextPeriodPoints(stakerAddress, stakePeriods);
totalSpankStaked = SafeMath.add(totalSpankStaked, spankAmount);
require(delegateKey != address(0), "delegateKey does not exist");
require(bootyBase != address(0), "bootyBase does not exist");
require(stakerByDelegateKey[delegateKey] == address(0), "delegateKey already used");
stakerByDelegateKey[delegateKey] = stakerAddress;
emit StakeEvent(
stakerAddress,
currentPeriod + 1,
stakers[stakerAddress].spankPoints[currentPeriod + 1],
spankAmount,
stakePeriods,
delegateKey,
bootyBase
);
}
// Called during stake and checkIn, assumes those functions prevent duplicate calls
// for the same staker.
function _updateNextPeriodPoints(address stakerAddress, uint256 stakingPeriods) internal {
Staker storage staker = stakers[stakerAddress];
uint256 stakerPoints = SafeMath.div(SafeMath.mul(staker.spankStaked, pointsTable[stakingPeriods]), 100);
// add staker spankpoints to total spankpoints for the next period
uint256 totalPoints = periods[currentPeriod + 1].totalSpankPoints;
totalPoints = SafeMath.add(totalPoints, stakerPoints);
periods[currentPeriod + 1].totalSpankPoints = totalPoints;
staker.spankPoints[currentPeriod + 1] = stakerPoints;
}
function receiveApproval(address from, uint256 amount, address tokenContract, bytes extraData) SpankBankIsOpen public returns (bool success) {
require(msg.sender == address(spankToken), "invalid receiveApproval caller");
address delegateKeyFromBytes = extraData.toAddress(12);
address bootyBaseFromBytes = extraData.toAddress(44);
uint256 periodFromBytes = extraData.toUint(64);
emit ReceiveApprovalEvent(from, tokenContract);
doStake(from, amount, periodFromBytes, delegateKeyFromBytes, bootyBaseFromBytes);
return true;
}
function sendFees(uint256 bootyAmount) SpankBankIsOpen public {
updatePeriod();
require(bootyAmount > 0, "fee is zero"); // fees must be greater than 0
require(bootyToken.transferFrom(msg.sender, this, bootyAmount));
bootyToken.burn(bootyAmount);
uint256 currentBootyFees = periods[currentPeriod].bootyFees;
currentBootyFees = SafeMath.add(bootyAmount, currentBootyFees);
periods[currentPeriod].bootyFees = currentBootyFees;
emit SendFeesEvent(msg.sender, bootyAmount);
}
function mintBooty() SpankBankIsOpen public {
updatePeriod();
// can't mint BOOTY during period 0 - would result in integer underflow
require(currentPeriod > 0, "current period is zero");
Period storage period = periods[currentPeriod - 1];
require(!period.mintingComplete, "minting already complete"); // cant mint BOOTY twice
period.mintingComplete = true;
uint256 targetBootySupply = SafeMath.mul(period.bootyFees, 20);
uint256 totalBootySupply = bootyToken.totalSupply();
if (targetBootySupply > totalBootySupply) {
uint256 bootyMinted = targetBootySupply - totalBootySupply;
bootyToken.mint(this, bootyMinted);
period.bootyMinted = bootyMinted;
emit MintBootyEvent(targetBootySupply, totalBootySupply);
}
}
// This will check the current time and update the current period accordingly
// - called from all write functions to ensure the period is always up to date before any writes
// - can also be called externally, but there isn't a good reason for why you would want to
// - the while loop protects against the edge case where we miss a period
function updatePeriod() public {
while (now >= periods[currentPeriod].endTime) {
Period memory prevPeriod = periods[currentPeriod];
currentPeriod += 1;
periods[currentPeriod].startTime = prevPeriod.endTime;
periods[currentPeriod].endTime = SafeMath.add(prevPeriod.endTime, periodLength);
}
}
// In order to receive Booty, each staker will have to check-in every period.
// This check-in will compute the spankPoints locally and globally for each staker.
function checkIn(uint256 updatedEndingPeriod) SpankBankIsOpen public {
updatePeriod();
address stakerAddress = stakerByDelegateKey[msg.sender];
Staker storage staker = stakers[stakerAddress];
require(staker.spankStaked > 0, "staker stake is zero");
require(currentPeriod < staker.endingPeriod, "staker expired");
require(staker.spankPoints[currentPeriod+1] == 0, "staker has points for next period");
// If updatedEndingPeriod is 0, don't update the ending period
if (updatedEndingPeriod > 0) {
require(updatedEndingPeriod > staker.endingPeriod, "updatedEndingPeriod less than or equal to staker endingPeriod");
require(updatedEndingPeriod <= currentPeriod + maxPeriods, "updatedEndingPeriod greater than currentPeriod and maxPeriods");
staker.endingPeriod = updatedEndingPeriod;
}
uint256 stakePeriods = staker.endingPeriod - currentPeriod;
_updateNextPeriodPoints(stakerAddress, stakePeriods);
emit CheckInEvent(stakerAddress, currentPeriod + 1, staker.spankPoints[currentPeriod + 1], staker.endingPeriod);
}
function claimBooty(uint256 claimPeriod) public {
updatePeriod();
Period memory period = periods[claimPeriod];
require(period.mintingComplete, "booty not minted");
address stakerAddress = stakerByDelegateKey[msg.sender];
Staker storage staker = stakers[stakerAddress];
require(!staker.didClaimBooty[claimPeriod], "staker already claimed"); // can only claim booty once
uint256 stakerSpankPoints = staker.spankPoints[claimPeriod];
require(stakerSpankPoints > 0, "staker has no points"); // only stakers can claim
staker.didClaimBooty[claimPeriod] = true;
uint256 bootyMinted = period.bootyMinted;
uint256 totalSpankPoints = period.totalSpankPoints;
uint256 bootyOwed = SafeMath.div(SafeMath.mul(stakerSpankPoints, bootyMinted), totalSpankPoints);
require(bootyToken.transfer(staker.bootyBase, bootyOwed));
emit ClaimBootyEvent(stakerAddress, claimPeriod, bootyOwed);
}
function withdrawStake() public {
updatePeriod();
Staker storage staker = stakers[msg.sender];
require(staker.spankStaked > 0, "staker has no stake");
require(isClosed || currentPeriod > staker.endingPeriod, "currentPeriod less than endingPeriod or spankbank closed");
uint256 spankToWithdraw = staker.spankStaked;
totalSpankStaked = SafeMath.sub(totalSpankStaked, staker.spankStaked);
staker.spankStaked = 0;
spankToken.transfer(msg.sender, spankToWithdraw);
emit WithdrawStakeEvent(msg.sender, spankToWithdraw);
}
function splitStake(address newAddress, address newDelegateKey, address newBootyBase, uint256 spankAmount) public {
updatePeriod();
require(newAddress != address(0), "newAddress is zero");
require(newDelegateKey != address(0), "delegateKey is zero");
require(newBootyBase != address(0), "bootyBase is zero");
require(stakerByDelegateKey[newDelegateKey] == address(0), "delegateKey in use");
require(stakers[newAddress].startingPeriod == 0, "staker already exists");
require(spankAmount > 0, "spankAmount is zero");
Staker storage staker = stakers[msg.sender];
require(currentPeriod < staker.endingPeriod, "staker expired");
require(spankAmount <= staker.spankStaked, "spankAmount greater than stake");
require(staker.spankPoints[currentPeriod+1] == 0, "staker has points for next period");
staker.spankStaked = SafeMath.sub(staker.spankStaked, spankAmount);
stakers[newAddress] = Staker(spankAmount, staker.startingPeriod, staker.endingPeriod, newDelegateKey, newBootyBase);
stakerByDelegateKey[newDelegateKey] = newAddress;
emit SplitStakeEvent(msg.sender, newAddress, newDelegateKey, newBootyBase, spankAmount);
}
function voteToClose() public {
updatePeriod();
Staker storage staker = stakers[msg.sender];
require(staker.spankStaked > 0, "stake is zero");
require(currentPeriod < staker.endingPeriod , "staker expired");
require(staker.votedToClose[currentPeriod] == false, "stake already voted");
require(isClosed == false, "SpankBank already closed");
uint256 closingVotes = periods[currentPeriod].closingVotes;
closingVotes = SafeMath.add(closingVotes, staker.spankStaked);
periods[currentPeriod].closingVotes = closingVotes;
staker.votedToClose[currentPeriod] = true;
uint256 closingTrigger = SafeMath.div(totalSpankStaked, 2);
if (closingVotes > closingTrigger) {
isClosed = true;
}
emit VoteToCloseEvent(msg.sender, currentPeriod);
}
function updateDelegateKey(address newDelegateKey) public {
require(newDelegateKey != address(0), "delegateKey is zero");
require(stakerByDelegateKey[newDelegateKey] == address(0), "delegateKey already exists");
Staker storage staker = stakers[msg.sender];
require(staker.startingPeriod > 0, "staker starting period is zero");
stakerByDelegateKey[staker.delegateKey] = address(0);
staker.delegateKey = newDelegateKey;
stakerByDelegateKey[newDelegateKey] = msg.sender;
emit UpdateDelegateKeyEvent(msg.sender, newDelegateKey);
}
function updateBootyBase(address newBootyBase) public {
Staker storage staker = stakers[msg.sender];
require(staker.startingPeriod > 0, "staker starting period is zero");
staker.bootyBase = newBootyBase;
emit UpdateBootyBaseEvent(msg.sender, newBootyBase);
}
function getSpankPoints(address stakerAddress, uint256 period) public view returns (uint256) {
return stakers[stakerAddress].spankPoints[period];
}
function getDidClaimBooty(address stakerAddress, uint256 period) public view returns (bool) {
return stakers[stakerAddress].didClaimBooty[period];
}
function getVote(address stakerAddress, uint period) public view returns (bool) {
return stakers[stakerAddress].votedToClose[period];
}
function getStakerFromDelegateKey(address delegateAddress) public view returns (address) {
return stakerByDelegateKey[delegateAddress];
}
}
{
"compilationTarget": {
"SpankBank.sol": "SpankBank"
},
"evmVersion": "byzantium",
"libraries": {},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"constant":true,"inputs":[],"name":"currentPeriod","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"voteToClose","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"newDelegateKey","type":"address"}],"name":"updateDelegateKey","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"spankAmount","type":"uint256"},{"name":"stakePeriods","type":"uint256"},{"name":"delegateKey","type":"address"},{"name":"bootyBase","type":"address"}],"name":"stake","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"stakerAddress","type":"address"},{"name":"period","type":"uint256"}],"name":"getSpankPoints","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"bootyToken","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"maxPeriods","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"mintBooty","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"stakerByDelegateKey","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"stakerAddress","type":"address"},{"name":"period","type":"uint256"}],"name":"getVote","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"delegateAddress","type":"address"}],"name":"getStakerFromDelegateKey","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"from","type":"address"},{"name":"amount","type":"uint256"},{"name":"tokenContract","type":"address"},{"name":"extraData","type":"bytes"}],"name":"receiveApproval","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"stakers","outputs":[{"name":"spankStaked","type":"uint256"},{"name":"startingPeriod","type":"uint256"},{"name":"endingPeriod","type":"uint256"},{"name":"delegateKey","type":"address"},{"name":"bootyBase","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"spankToken","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"newBootyBase","type":"address"}],"name":"updateBootyBase","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"pointsTable","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"claimPeriod","type":"uint256"}],"name":"claimBooty","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"updatePeriod","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"newAddress","type":"address"},{"name":"newDelegateKey","type":"address"},{"name":"newBootyBase","type":"address"},{"name":"spankAmount","type":"uint256"}],"name":"splitStake","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"withdrawStake","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"isClosed","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"stakerAddress","type":"address"},{"name":"period","type":"uint256"}],"name":"getDidClaimBooty","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"totalSpankStaked","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"periodLength","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"updatedEndingPeriod","type":"uint256"}],"name":"checkIn","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"periods","outputs":[{"name":"bootyFees","type":"uint256"},{"name":"totalSpankPoints","type":"uint256"},{"name":"bootyMinted","type":"uint256"},{"name":"mintingComplete","type":"bool"},{"name":"startTime","type":"uint256"},{"name":"endTime","type":"uint256"},{"name":"closingVotes","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"bootyAmount","type":"uint256"}],"name":"sendFees","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"_periodLength","type":"uint256"},{"name":"_maxPeriods","type":"uint256"},{"name":"spankAddress","type":"address"},{"name":"initialBootySupply","type":"uint256"},{"name":"bootyTokenName","type":"string"},{"name":"bootyDecimalUnits","type":"uint8"},{"name":"bootySymbol","type":"string"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"periodLength","type":"uint256"},{"indexed":false,"name":"maxPeriods","type":"uint256"},{"indexed":false,"name":"spankAddress","type":"address"},{"indexed":false,"name":"initialBootySupply","type":"uint256"},{"indexed":false,"name":"bootyTokenName","type":"string"},{"indexed":false,"name":"bootyDecimalUnits","type":"uint8"},{"indexed":false,"name":"bootySymbol","type":"string"}],"name":"SpankBankCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"staker","type":"address"},{"indexed":false,"name":"period","type":"uint256"},{"indexed":false,"name":"spankPoints","type":"uint256"},{"indexed":false,"name":"spankAmount","type":"uint256"},{"indexed":false,"name":"stakePeriods","type":"uint256"},{"indexed":false,"name":"delegateKey","type":"address"},{"indexed":false,"name":"bootyBase","type":"address"}],"name":"StakeEvent","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"sender","type":"address"},{"indexed":false,"name":"bootyAmount","type":"uint256"}],"name":"SendFeesEvent","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"targetBootySupply","type":"uint256"},{"indexed":false,"name":"totalBootySupply","type":"uint256"}],"name":"MintBootyEvent","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"staker","type":"address"},{"indexed":false,"name":"period","type":"uint256"},{"indexed":false,"name":"spankPoints","type":"uint256"},{"indexed":false,"name":"stakerEndingPeriod","type":"uint256"}],"name":"CheckInEvent","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"staker","type":"address"},{"indexed":false,"name":"period","type":"uint256"},{"indexed":false,"name":"bootyOwed","type":"uint256"}],"name":"ClaimBootyEvent","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"staker","type":"address"},{"indexed":false,"name":"totalSpankToWithdraw","type":"uint256"}],"name":"WithdrawStakeEvent","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"staker","type":"address"},{"indexed":false,"name":"newAddress","type":"address"},{"indexed":false,"name":"newDelegateKey","type":"address"},{"indexed":false,"name":"newBootyBase","type":"address"},{"indexed":false,"name":"spankAmount","type":"uint256"}],"name":"SplitStakeEvent","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"staker","type":"address"},{"indexed":false,"name":"period","type":"uint256"}],"name":"VoteToCloseEvent","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"staker","type":"address"},{"indexed":false,"name":"newDelegateKey","type":"address"}],"name":"UpdateDelegateKeyEvent","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"staker","type":"address"},{"indexed":false,"name":"newBootyBase","type":"address"}],"name":"UpdateBootyBaseEvent","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"from","type":"address"},{"indexed":false,"name":"tokenContract","type":"address"}],"name":"ReceiveApprovalEvent","type":"event"}]