Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 55 additions & 8 deletions contracts/GlobalsAndUtility.sol
Original file line number Diff line number Diff line change
Expand Up @@ -151,13 +151,21 @@ contract GlobalsAndUtility is ERC20 {
uint8 internal constant BTC_ADDR_TYPE_P2WPKH_IN_P2SH = 2;
uint8 internal constant BTC_ADDR_TYPE_COUNT = 3;

/* Starting Share Price */
uint80 internal constant INITIAL_SHARES_PER_HEART = 1e18;

/* Globals expanded for memory (except _latestStakeId) and compact for storage */
struct GlobalsCache {
// 1
uint256 _daysStored;
uint256 _stakedHeartsTotal;
uint256 _stakeSharesTotal;
uint256 _nextStakeSharesTotal;
uint48 _latestStakeId;
// share price
uint80 _sharesPerHeart;
// total "paper" payouts for share price
uint256 _pendingPayoutTotal;
// 2
uint256 _stakePenaltyPool;
uint256 _unclaimedSatoshisTotal;
Expand All @@ -170,9 +178,12 @@ contract GlobalsAndUtility is ERC20 {
struct GlobalsStore {
// 1
uint16 daysStored;
uint80 stakeSharesTotal;
uint80 nextStakeSharesTotal;
uint80 stakedHeartsTotal;
uint256 stakeSharesTotal;
uint256 nextStakeSharesTotal;
uint48 latestStakeId;
uint80 sharesPerHeart;
uint80 pendingPayoutTotal;
// 2
uint80 stakePenaltyPool;
uint64 unclaimedSatoshisTotal;
Expand All @@ -188,7 +199,7 @@ contract GlobalsAndUtility is ERC20 {
/* Period data */
struct DailyDataStore {
uint80 dayPayoutTotal;
uint80 dayStakeSharesTotal;
uint256 dayStakeSharesTotal;
}

mapping(uint256 => DailyDataStore) public dailyData;
Expand Down Expand Up @@ -272,13 +283,15 @@ contract GlobalsAndUtility is ERC20 {
function getGlobalInfo()
external
view
returns (uint256[11] memory)
returns (uint256[13] memory)
{
return [
globals.daysStored,
globals.stakedHeartsTotal,
globals.stakeSharesTotal,
globals.nextStakeSharesTotal,
globals.latestStakeId,
globals.sharesPerHeart,
globals.stakePenaltyPool,
globals.unclaimedSatoshisTotal,
globals.claimedSatoshisTotal,
Expand Down Expand Up @@ -345,9 +358,13 @@ contract GlobalsAndUtility is ERC20 {
{
// 1
g._daysStored = globals.daysStored;
g._stakedHeartsTotal = globals.stakedHeartsTotal;
g._stakeSharesTotal = globals.stakeSharesTotal;
g._nextStakeSharesTotal = globals.nextStakeSharesTotal;
g._latestStakeId = globals.latestStakeId;
g._sharesPerHeart = globals.sharesPerHeart;

g._pendingPayoutTotal = globals.pendingPayoutTotal;
// 2
g._stakePenaltyPool = globals.stakePenaltyPool;
g._unclaimedSatoshisTotal = globals.unclaimedSatoshisTotal;
Expand All @@ -363,9 +380,12 @@ contract GlobalsAndUtility is ERC20 {
{
// 1
gSnapshot._daysStored = g._daysStored;
gSnapshot._stakedHeartsTotal = g._stakedHeartsTotal;
gSnapshot._stakeSharesTotal = g._stakeSharesTotal;
gSnapshot._nextStakeSharesTotal = g._nextStakeSharesTotal;
gSnapshot._latestStakeId = g._latestStakeId;
gSnapshot._sharesPerHeart = g._sharesPerHeart;
gSnapshot._pendingPayoutTotal = g._pendingPayoutTotal;
// 2
gSnapshot._stakePenaltyPool = g._stakePenaltyPool;
gSnapshot._unclaimedSatoshisTotal = g._unclaimedSatoshisTotal;
Expand All @@ -377,18 +397,24 @@ contract GlobalsAndUtility is ERC20 {
internal
{
globals.daysStored = uint16(g._daysStored);
globals.stakeSharesTotal = uint80(g._stakeSharesTotal);
globals.nextStakeSharesTotal = uint80(g._nextStakeSharesTotal);
globals.stakedHeartsTotal = uint80(g._stakedHeartsTotal);
globals.stakeSharesTotal = uint256(g._stakeSharesTotal);
globals.nextStakeSharesTotal = uint256(g._nextStakeSharesTotal);
globals.latestStakeId = g._latestStakeId;
globals.sharesPerHeart = uint80(g._sharesPerHeart);
globals.pendingPayoutTotal = uint80(g._pendingPayoutTotal);
}

function _syncGlobals1(GlobalsCache memory g, GlobalsCache memory gSnapshot)
internal
{
if (g._daysStored == gSnapshot._daysStored
&& g._stakedHeartsTotal == gSnapshot._stakedHeartsTotal
&& g._stakeSharesTotal == gSnapshot._stakeSharesTotal
&& g._nextStakeSharesTotal == gSnapshot._nextStakeSharesTotal
&& g._latestStakeId == gSnapshot._latestStakeId) {
&& g._latestStakeId == gSnapshot._latestStakeId
&& g._sharesPerHeart == gSnapshot._sharesPerHeart
&& g._pendingPayoutTotal == gSnapshot._pendingPayoutTotal) {
return;
}
_saveGlobals1(g);
Expand Down Expand Up @@ -518,11 +544,15 @@ contract GlobalsAndUtility is ERC20 {
rs._totalSupplyCached = totalSupply();

uint256 day = g._daysStored;
uint256 totalPendingPayout = g._pendingPayoutTotal;
do {
if (g._stakeSharesTotal != 0) {
_calcDailyRound(g, rs, day);
dailyData[day].dayPayoutTotal = uint80(rs._payoutTotal);
dailyData[day].dayStakeSharesTotal = uint80(g._stakeSharesTotal);
dailyData[day].dayStakeSharesTotal = g._stakeSharesTotal;
/* capture additional pending payout */
totalPendingPayout += rs._payoutTotal;

} else {
if (day == CLAIM_REWARD_DAYS && g._unclaimedSatoshisTotal != 0) {
/*
Expand Down Expand Up @@ -551,7 +581,24 @@ contract GlobalsAndUtility is ERC20 {
uint16(day),
msg.sender
);

/* share price = (stakedHearts + payoutTotal) / sharesTotal
* to keep precision we always assume >1 shares per heart
* so our shares per hearts is 1 / price
* i.e. sharesTotal/(stakedHearts + payoutTotal)
*/
if(g._stakeSharesTotal == 0){
// no stakes, leave price unchanged
}
else if(totalPendingPayout + g._stakedHeartsTotal == 0){
// div by 0, instead reset to original price
g._sharesPerHeart = INITIAL_SHARES_PER_HEART;
} else {
g._sharesPerHeart = uint80(g._stakeSharesTotal / (totalPendingPayout + g._stakedHeartsTotal));
}

g._daysStored = day;
g._pendingPayoutTotal = totalPendingPayout;

if (rs._mintContractBatch != 0) {
_mint(address(this), rs._mintContractBatch);
Expand Down
1 change: 1 addition & 0 deletions contracts/HEX.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ contract HEX is StakeableToken {
{
/* Add all Satoshis from UTXO snapshot to contract */
globals.unclaimedSatoshisTotal = uint64(FULL_SATOSHIS_TOTAL);
globals.sharesPerHeart = INITIAL_SHARES_PER_HEART;
_mint(address(this), FULL_SATOSHIS_TOTAL * HEARTS_PER_SATOSHI);
}

Expand Down
33 changes: 28 additions & 5 deletions contracts/StakeableToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ contract StakeableToken is UTXORedeemableToken {
/* Check if log data needs to be updated */
_storeDailyDataBefore(g, g._currentDay);

uint256 newStakeShares = calcStakeShares(newStakedHearts, newStakedDays);
uint256 newStakeShares = calcStakeShares(newStakedHearts, newStakedDays, g._sharesPerHeart);

/*
The startStake timestamp will always be part-way through the current
Expand Down Expand Up @@ -61,6 +61,9 @@ contract StakeableToken is UTXORedeemableToken {
/* Stake is added to pool in next round, not current round */
g._nextStakeSharesTotal += newStakeShares;

/* capture total hearts staked for share price */
g._stakedHeartsTotal += newStakedHearts;

/* Transfer staked Hearts to contract */
_transfer(msg.sender, address(this), newStakedHearts);

Expand Down Expand Up @@ -113,6 +116,10 @@ contract StakeableToken is UTXORedeemableToken {
st._stakedDays
);

/* remove hearts and pending payout from share price calculation */
g._stakedHeartsTotal -= st._stakedHearts;
g._pendingPayoutTotal -= payout;

if (msg.sender == stakerAddr) {
emit GoodAccountingBySelf(
uint40(block.timestamp),
Expand Down Expand Up @@ -177,11 +184,15 @@ contract StakeableToken is UTXORedeemableToken {
uint256 payout = 0;
uint256 penalty = 0;
uint256 cappedPenalty = 0;
uint256 pendingValueRemoved = 0;
uint256 stakedHeartsRemoved = st._stakedHearts;

if (g._currentDay >= st._pooledDay) {
if (prevUnpooled) {
/* Previously unpooled in goodAccounting(), so must have served full term */
servedDays = st._stakedDays;
//We were unpooled so we accounted for our staked hearts
stakedHeartsRemoved = 0;
} else {
_unpoolStake(g, st);

Expand All @@ -192,13 +203,22 @@ contract StakeableToken is UTXORedeemableToken {
}

(stakeReturn, payout, penalty, cappedPenalty) = _calcStakeReturn(g, st, servedDays);
//Good accounting would remove our pending value so only count if we didn't do that
if(!prevUnpooled){
pendingValueRemoved = payout;
}
} else {
/* Stake hasn't been added to the global pool yet, so no penalties or rewards apply */
g._nextStakeSharesTotal -= st._stakeShares;

stakeReturn = st._stakedHearts;
}

/* remove hearts from share price calculation */
g._stakedHeartsTotal -= stakedHeartsRemoved;
/* remove pending value from share price calculation */
g._pendingPayoutTotal -= pendingValueRemoved;

emit EndStake(
uint40(block.timestamp),
msg.sender,
Expand Down Expand Up @@ -255,11 +275,12 @@ contract StakeableToken is UTXORedeemableToken {
}

/**
* @dev Calculate stakeShares for a new stake, including any bonus
* @param newStakedHearts Number of Hearts to stake
* @dev Apply LPB bonus for a new stake based on days
* @param newStakedHearts Number of hearts to stake
* @param newStakedDays Number of days to stake
* @param sharesPerHeart Number of shares for each heart
*/
function calcStakeShares(uint256 newStakedHearts, uint256 newStakedDays)
function calcStakeShares(uint256 newStakedHearts, uint256 newStakedDays, uint80 sharesPerHeart)
private
pure
returns (uint256)
Expand Down Expand Up @@ -321,6 +342,8 @@ contract StakeableToken is UTXORedeemableToken {

stakeShares = hearts + combinedAmount
*/
require(sharesPerHeart > 0, "HEX: share price undecidable");

uint256 cappedExtraDays = 0;

/* Must be more than 1 day for Longer-Pays-Better */
Expand All @@ -335,7 +358,7 @@ contract StakeableToken is UTXORedeemableToken {
uint256 combinedAmount = cappedExtraDays * LPB_H + cappedStakedHearts * LPB_D;
combinedAmount = newStakedHearts * combinedAmount / (LPB_D * LPB_H);

return newStakedHearts + combinedAmount;
return sharesPerHeart * (newStakedHearts + combinedAmount);
}

function _unpoolStake(GlobalsCache memory g, StakeCache memory st)
Expand Down