SkillAgentSearch skills...

Spankbank

Algorithmic central bank that powers the two-token SpankChain economic system.

Install / Use

/learn @SpankChain/Spankbank
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

SpankBank

The SpankBank is an algorithmic central bank that powers the two-token SpankChain economic system.

  1. SPANK is a staking token which can be deposited with the SpankBank to earn newly minted BOOTY.

  2. BOOTY is low-volatility fee credit good for $1 worth of SpankChain services.

SpankChain will collect fees for using the camsite, payment hub, advertising network, and other services in BOOTY. The fees are sent to the SpankBank where they are counted and burned.

The SpankBank has a 30 day billing period. At the beginning of each new period, if the total BOOTY supply is under the target supply (20x the total fees collected in the previous period), new BOOTY is minted to reach the target supply and distributed proportionally to all SPANK stakers.

In the future, we plan to add features to incentivize decentralized moderation of the SpankChain platform, rewarding BOOTY to those who help maintain its integrity. We also plan to add mechanisms that will incentivize maintaining the $1 BOOTY peg.

Usage

SPANK holders can stake their SPANK tokens with the SpankBank for up to 12 (30 day) periods by calling the stake function. BOOTY fees can be paid to the SpankBank by anyone by calling the sendFees function. When a new period starts, anyone can mint BOOTY for the previous period by calling the mintBooty function. Once BOOTY has been minted for a previous period, the stakers for that period can call the claimBooty function to claim their BOOTY. In order to be eligible to receive BOOTY, during each staking period stakers have to check in with the SpankBank by calling the checkIn function. Stakers can optionally extend their stake for additional periods when checking in. When a staker's stake has expired, they can withdraw their staked SPANK using the withdrawStake function.

If stakers want to only partially extend their stake (e.g. extend only 50% of their stake by an additional month, not all of it) or to transfer some of their stake to a new address (e.g. for security reasons) they can do so by calling the splitStake function.

If stakers want to close the SpankBank and be able to withdraw early (e.g. in case of catastrophic bug or planned upgrade), they can call the voteToClose function. If stakers accounting for more than 50% of the staked SPANK call voteToClose in the same period, the SpankBank will immediately transition to a "closed" state and allow stakers to withdraw early.

Data Structures

Global Constants

uint256 public periodLength; - time length of each period in seconds (30 days = 2592000 seconds)

uint256 public maxPeriods; - maximum number of staking periods (12)

HumanStandardToken public spankToken; - the SPANK token contract reference

MintableToken public bootyToken; - the BOOTY token contract reference

SpankPoints

Stakers are rewarded with extra BOOTY for staking for additional periods. The SpankPoints for each staker are calculated as the amount of SPANK staked multiplied by the staking factor. The staking factor ranges from 100% if staking for the maximum length of 12 periods, to 45% for staking the minimum length of 1 period.

  // LOOKUP TABLE FOR STAKING FACTOR BY PERIOD
  // 1 -> 45%
  // 2 -> 50%
  // ...
  // 12 -> 100%
  mapping(uint256 => uint256) pointsTable;

In order to earn the maximum BOOTY, a staker would need to stake for 12 periods, and then opt to extend their stake by 1 period during every check in.

If a staker stakes for 12 periods but doesn't opt to extend their stake during check ins, they would receive 100% of the BOOTY for the first period, 95% for the second period, and so forth until they receive 45% during the final period.

Internal Accounting

uint256 public currentPeriod = 0; - the current period number

uint256 public totalSpankStaked; - the total SPANK staked across all stakers

bool public isClosed; - true if voteToClose has passed, allows early withdrawals

Stakers

The Staker struct stores all relevant data for each staker, and is saved in the stakers mapping by the staker's address.

    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;

The staker.spankPoints mapping stores the staker's spank points for each period. The staker.didClaimBooty mapping tracks whether or not the staker has claimed their BOOTY for each period.

Periods

The Period struct stores all relevant data for each period, and is saved in the periods mapping by the period number.

    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;

The data for each period is set in the following order:

  1. The totalSpankPoints are tallied during the previous period, as each staker calls the stake or checkIn functions.
  2. The startTime and endTime are set when the period starts, when the updatePeriod function is called.
  3. The bootyFees are tallied during the period, as the sendFees function is called.
  4. The closingVotes are tallied during the period, as stakers call voteToClose.
  5. Once the period is over, bootyMinted and mintingComplete are set when the mintBooty function is called during the next period.

Functions

SpankBank Constructor

  1. Saves the passed in periodLength and maxPeriods as global constants.
  2. Builds and saves the SPANK token reference from the passed in spankAddress.
  3. Deploys the BOOTY token contract and mints initialBootySupply BOOTY tokens.
  4. Transfers all newly minted BOOTY to the msg.sender.
  5. Immediately starts the first period (period 0) at startTime = now.
  6. Set the endTime of the first period to 30 days from now.
  7. Initialize the pointsTable with hard coded values.
    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);
    }

Bootstrapping BOOTY

The initial BOOTY balance is sent to the msg.sender to be distributed to all period 0 stakers through a token airdrop. The airdrop will take place at the end of period 0 (30 days from SpankBank deployment) and will be based on each staker's spankPoints for period 1.

updatePeriod

In order to make sure all interactions with the SpankBank take place during the correct period, the updatePeriod function is called at the beginning of every state-updating function.

So long as the current time (now) is greater than the endTime of the current period (meaning the period is over), the currentPeriod is incremented by one and then the startTime and the endTime for the next Period are set as well.

    function updatePeriod() public {
        while (now >= periods[currentPeriod].endTime) {
            Period memory prevPeriod = periods[currentPeriod];
            emit PeriodEvent(
                currentPeriod,
                prevPeriod.bootyFees,
                prevPeriod.totalSpankPoints,
                prevPeriod.bootyMinted,
                prevPeriod.closingVotes
            );

            currentPeriod += 1;
            periods[currentPeriod].startTime = prevPeriod.endTime;
            periods[currentPeriod].endTime = SafeMath.add(prevPeriod.endTime, periodLength);
        }
    }

The reason this is done using a while loop is just in case an entire period passes without any SpankBank interactions taking place. This is extremely unlikely and would mean no fees were paid not a single staker checked in, but we wanted to protect against that scenario anyways.

One scenario the updatePeriod function does not protect against is if enough periods pass without any SpankBank interactions that in order to catch up to the current period the while loop has to run e

View on GitHub
GitHub Stars51
CategoryDevelopment
Updated6mo ago
Forks14

Languages

JavaScript

Security Score

72/100

Audited on Sep 14, 2025

No findings