Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

further merge into develop #121

Merged
merged 26 commits into from
Mar 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
53e1975
(Hub/Circles): minor clean up
benjaminbollen Mar 13, 2024
4eb93eb
(lift): start on lift and erc20 demurrage
benjaminbollen Mar 13, 2024
22a1c98
(erc20): reorg demurrage for use in erc1155 and erc20
benjaminbollen Mar 14, 2024
5b842a6
(ERC20Demurrage): shaping new ERC20 for demurrage
benjaminbollen Mar 15, 2024
e520f61
(standardTreasury,groupMint): hardening data definitions to prevent a…
benjaminbollen Mar 15, 2024
db710f3
(errors, erc20): minting ao
benjaminbollen Mar 15, 2024
360af52
(erc20): unwrap and burn
benjaminbollen Mar 15, 2024
043a68e
(erc20): cannot receive 1155 batch
benjaminbollen Mar 15, 2024
0cba04b
(erc20): increase and decrease allowance
benjaminbollen Mar 15, 2024
244118c
(ERC20Permit): fork EIP712 for setup rather than constructor
benjaminbollen Mar 15, 2024
9121321
(erc20): fitting the jigsaw pieces together
benjaminbollen Mar 15, 2024
4b08c4f
(erc20): now scaffolding inflationary Circles
benjaminbollen Mar 15, 2024
e1823a1
(erc20): more on ionflationary ERC20 with extended numerical accuracy
benjaminbollen Mar 15, 2024
1980e2c
(erc20): small completion in ERC20Permit
benjaminbollen Mar 16, 2024
9ad74df
(erc20): mint and burn for inflationary erc20
benjaminbollen Mar 16, 2024
6dc0d6b
(erc20): mint from 1155 received
benjaminbollen Mar 16, 2024
4032f5a
(errors, names, ...): replacing with error codes, name registry and o…
benjaminbollen Mar 16, 2024
24e8aea
(errors): more errors replaced in Hub
benjaminbollen Mar 16, 2024
0c1677f
(errors): replace all requires with revert errors in Hub.sol
benjaminbollen Mar 17, 2024
1523da9
(errors): replace requirements in Circles.sol
benjaminbollen Mar 17, 2024
b3bb272
(errors): complete all require replacements with errors
benjaminbollen Mar 17, 2024
5d7a103
(lift): decide to build lift outside of hub to avoid growth of code o…
benjaminbollen Mar 17, 2024
68fc656
(Hub): move CIDv0 out to namereg, make more cuts to create space in H…
benjaminbollen Mar 18, 2024
31974ca
(hub, erc20): wrap into erc20, more shaving of bytes to fit
benjaminbollen Mar 18, 2024
2eba2ad
(names, hub): names and symbols for Circles
benjaminbollen Mar 20, 2024
20167db
Merge pull request #119 from CirclesUBI/20240313-erc20-wrappers
jaensen Mar 22, 2024
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
69 changes: 17 additions & 52 deletions src/circles/Circles.sol
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.13;

import "./ERC1155.sol";
import "../lib/Math64x64.sol";
import "./ERC1155.sol";

contract Circles is ERC1155 {
// Type declarations
Expand Down Expand Up @@ -31,23 +31,23 @@ contract Circles is ERC1155 {
* @notice Issue one Circle per hour for each human in demurraged units.
* So per second issue 10**18 / 3600 = 277777777777778 attoCircles.
*/
uint256 public constant ISSUANCE_PER_SECOND = uint256(277777777777778);
uint256 private constant ISSUANCE_PER_SECOND = uint256(277777777777778);

/**
* @notice Upon claiming, the maximum claim is upto two weeks
* of history. Unclaimed older Circles are unclaimable.
*/
uint256 public constant MAX_CLAIM_DURATION = 2 weeks;
uint256 private constant MAX_CLAIM_DURATION = 2 weeks;

/**
* @dev Address used to indicate that the associated v1 Circles contract has been stopped.
*/
address public constant CIRCLES_STOPPED_V1 = address(0x1);
address internal constant CIRCLES_STOPPED_V1 = address(0x1);

/**
* @notice Indefinite future, or approximated with uint96.max
*/
uint96 public constant INDEFINITE_FUTURE = type(uint96).max;
uint96 internal constant INDEFINITE_FUTURE = type(uint96).max;

// State variables

Expand All @@ -60,44 +60,6 @@ contract Circles is ERC1155 {

// Events

/**
* @dev Emitted when Circles are transferred in addition to TransferSingle event,
* to include the demurraged value of the Circles transferred.
* @param operator Operator who called safeTransferFrom.
* @param from Address from which the Circles have been transferred.
* @param to Address to which the Circles have been transferred.
* @param id Circles identifier for which the Circles have been transferred.
* @param value Demurraged value of the Circles transferred.
* @param inflationaryValue Inflationary amount of Circles transferred.
*/
event DemurragedTransferSingle(
address indexed operator,
address indexed from,
address indexed to,
uint256 id,
uint256 value,
uint256 inflationaryValue
);

/**
* @dev Emitted when Circles are transferred in addition to TransferBatch event,
* to include the demurraged values of the Circles transferred.
* @param operator Operator who called safeBatchTransferFrom.
* @param from Address from which the Circles have been transferred.
* @param to Address to which the Circles have been transferred.
* @param ids Array of Circles identifiers for which the Circles have been transferred.
* @param values Array of demurraged values of the Circles transferred.
* @param inflationaryValues Array of inflationary amounts of Circles transferred.
*/
event DemurragedTransferBatch(
address indexed operator,
address indexed from,
address indexed to,
uint256[] ids,
uint256[] values,
uint256[] inflationaryValues
);

// Constructor

/**
Expand All @@ -120,14 +82,14 @@ contract Circles is ERC1155 {
*/
function calculateIssuance(address _human) public view returns (uint256) {
MintTime storage mintTime = mintTimes[_human];
require(
mintTime.mintV1Status == address(0) || mintTime.mintV1Status == CIRCLES_STOPPED_V1,
"Circles v1 contract cannot be active."
);
if (mintTime.mintV1Status != address(0) && mintTime.mintV1Status != CIRCLES_STOPPED_V1) {
// Circles v1 contract cannot be active.
revert CirclesERC1155MintBlocked(_human, mintTime.mintV1Status);
}

if (uint256(mintTime.lastMintTime) + 10 > block.timestamp) {
if (uint256(mintTime.lastMintTime) + 1 hours > block.timestamp) {
// Mint time is set to indefinite future for stopped mints in v2
// and wait at least 10 seconds between mints
// and only complete hours get minted, so shortcut the calculation
return 0;
}

Expand All @@ -144,10 +106,10 @@ contract Circles is ERC1155 {
uint256 n = dB - dA;

// calculate the number of completed hours in day A until `startMint`
int128 k = Math64x64.fromUInt((startMint - (dA * 1 days + inflation_day_zero)) / 1 hours);
int128 k = Math64x64.fromUInt((startMint - (dA * 1 days + inflationDayZero)) / 1 hours);

// Calculate the number of incompleted hours remaining in day B from current timestamp
int128 l = Math64x64.fromUInt(((dB + 1) * 1 days + inflation_day_zero - block.timestamp) / 1 hours + 1);
int128 l = Math64x64.fromUInt(((dB + 1) * 1 days + inflationDayZero - block.timestamp) / 1 hours + 1);

// calculate the overcounted (demurraged) k (in day A) and l (in day B) hours
int128 overcount = Math64x64.add(Math64x64.mul(R[n], k), l);
Expand All @@ -164,7 +126,10 @@ contract Circles is ERC1155 {
*/
function _claimIssuance(address _human) internal {
uint256 issuance = calculateIssuance(_human);
require(issuance > 0, "No issuance to claim.");
if (issuance == 0) {
// No issuance to claim.
revert CirclesERC1155NoMintToIssue(_human, mintTimes[_human].lastMintTime);
}
// mint personal Circles to the human
_mint(_human, toTokenId(_human), issuance, "");
// update the last mint time
Expand Down
229 changes: 229 additions & 0 deletions src/circles/Demurrage.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.13;

import "../errors/Errors.sol";
import "../lib/Math64x64.sol";

contract Demurrage is ICirclesERC1155Errors {
// Type declarations

/**
* @dev Discounted balance with a last updated timestamp.
* Note: The balance is stored as a 192-bit unsigned integer.
* The last updated timestamp is stored as a 64-bit unsigned integer counting the days
* since `inflation_day_zero` (for elegance, to not start the clock in 1970 with unix time).
* We ensure that they combine to 32 bytes for a single Ethereum storage slot.
* Note: The maximum total supply of CRC is ~10^5 per human, with 10**10 humans,
* so even if all humans would pool all their tokens into a single group and then a single account
* the total resolution required is 10^(10 + 5 + 18) = 10^33 << 10**57 (~ max uint192).
*/
struct DiscountedBalance {
uint192 balance;
uint64 lastUpdatedDay;
}

// Constants

/**
* @notice Demurrage window reduces the resolution for calculating
* the demurrage of balances from once per second (block.timestamp)
* to once per day.
*/
uint256 private constant DEMURRAGE_WINDOW = 1 days;

/**
* @dev Maximum value that can be stored or transferred
*/
uint256 internal constant MAX_VALUE = type(uint192).max;

/**
* @dev Reduction factor GAMMA for applying demurrage to balances
* demurrage_balance(d) = GAMMA^d * inflationary_balance
* where 'd' is expressed in days (DEMURRAGE_WINDOW) since demurrage_day_zero,
* and GAMMA < 1.
* GAMMA_64x64 stores the numerator for the signed 128bit 64.64
* fixed decimal point expression:
* GAMMA = GAMMA_64x64 / 2**64.
* To obtain GAMMA for a daily accounting of 7% p.a. demurrage
* => GAMMA = (0.93)^(1/365.25)
* = 0.99980133200859895743...
* and expressed in 64.64 fixed point representation:
* => GAMMA_64x64 = 18443079296116538654
* For more details, see ./specifications/TCIP009-demurrage.md
*/
int128 internal constant GAMMA_64x64 = int128(18443079296116538654);

/**
* @dev For calculating the inflationary mint amount on day `d`
* since demurrage_day_zero, a person can mint
* (1/GAMMA)^d CRC / hour
* As GAMMA is a constant, to save gas costs store the inverse
* as BETA = 1 / GAMMA.
* BETA_64x64 is the 64.64 fixed point representation:
* BETA_64x64 = 2**64 / ((0.93)^(1/365.25))
* = 18450409579521241655
* For more details, see ./specifications/TCIP009-demurrage.md
*/
int128 internal constant BETA_64x64 = int128(18450409579521241655);

/**
* @dev ERC1155 tokens MUST be 18 decimals.
*/
uint8 internal constant DECIMALS = uint8(18);

/**
* @dev EXA factor as 10^18
*/
uint256 internal constant EXA = uint256(10 ** DECIMALS);

/**
* @dev Store the signed 128-bit 64.64 representation of 1 as a constant
*/
int128 internal constant ONE_64x64 = int128(2 ** 64);

uint8 internal constant R_TABLE_LOOKUP = uint8(14);

// State variables

/**
* @notice Inflation day zero stores the start of the global inflation curve
* As Circles Hub v1 was deployed on Thursday 15th October 2020 at 6:25:30 pm UTC,
* or 1602786330 unix time, in production this value MUST be set to 1602720000 unix time,
* or midnight prior of the same day of deployment, marking the start of the first day
* where there was no inflation on one CRC per hour.
*/
uint256 public inflationDayZero;

/**
* @dev Store a lookup table T(n) for computing issuance.
* See ../../specifications/TCIP009-demurrage.md for more details.
*/
int128[15] internal T = [
int128(442721857769029238784),
int128(885355760875826166476),
int128(1327901726794166863126),
int128(1770359772994355928788),
int128(2212729916943227173193),
int128(2655012176104144305282),
int128(3097206567937001622606),
int128(3539313109898224700583),
int128(3981331819440771081628),
int128(4423262714014130964135),
int128(4865105811064327891331),
int128(5306861128033919439986),
int128(5748528682361997908993),
int128(6190108491484191007805),
int128(6631600572832662544739)
];

/**
* @dev Store a lookup table R(n) for computing issuance.
* See ../../specifications/TCIP009-demurrage.md for more details.
*/
int128[15] internal R = [
int128(18446744073709551616),
int128(18443079296116538654),
int128(18439415246597529027),
int128(18435751925007877736),
int128(18432089331202968517),
int128(18428427465038213837),
int128(18424766326369054888),
int128(18421105915050961582),
int128(18417446230939432544),
int128(18413787273889995104),
int128(18410129043758205300),
int128(18406471540399647861),
int128(18402814763669936209),
int128(18399158713424712450),
int128(18395503389519647372)
];

// Public functions

/**
* @notice Calculate the day since inflation_day_zero for a given timestamp.
* @param _timestamp Timestamp for which to calculate the day since inflation_day_zero.
*/
function day(uint256 _timestamp) public view returns (uint64) {
// calculate which day the timestamp is in, rounding down
// note: max uint64 is 2^64 - 1, so we can safely cast the result
return uint64((_timestamp - inflationDayZero) / DEMURRAGE_WINDOW);
}

/**
* @notice Casts an avatar address to a tokenId uint256.
* @param _avatar avatar address to convert to tokenId
*/
function toTokenId(address _avatar) public pure returns (uint256) {
return uint256(uint160(_avatar));
}

/**
* @notice Converts an inflationary value to a demurrage value for a given day since inflation_day_zero.
* @param _inflationaryValue Inflationary value to convert to demurrage value.
* @param _day Day since inflation_day_zero to convert the inflationary value to a demurrage value.
*/
function convertInflationaryToDemurrageValue(uint256 _inflationaryValue, uint64 _day)
public
pure
returns (uint256)
{
// calculate the demurrage value by multiplying the value by GAMMA^days
// note: GAMMA < 1, so multiplying by a power of it, returns a smaller number,
// so we lose the least significant bits, but our ground truth is the demurrage value,
// and the inflationary value the numerical approximation.
int128 r = Math64x64.pow(GAMMA_64x64, uint256(_day));
return Math64x64.mulu(r, _inflationaryValue);
}

/**
* @notice Converts a batch of inflationary values to demurrage values for a given day since inflation_day_zero.
* @param _inflationaryValues Batch of inflationary values to convert to demurrage values.
* @param _day Day since inflation_day_zero to convert the inflationary values to demurrage values.
*/
function convertBatchInflationaryToDemurrageValues(uint256[] memory _inflationaryValues, uint64 _day)
public
pure
returns (uint256[] memory)
{
// calculate the demurrage value by multiplying the value by GAMMA^days
// note: same remark on precision as in convertInflationaryToDemurrageValue
int128 r = Math64x64.pow(GAMMA_64x64, uint256(_day));
uint256[] memory demurrageValues = new uint256[](_inflationaryValues.length);
for (uint256 i = 0; i < _inflationaryValues.length; i++) {
demurrageValues[i] = Math64x64.mulu(r, _inflationaryValues[i]);
}
return demurrageValues;
}

// Internal functions

/**
* @dev Calculates the discounted balance given a number of days to discount
* @param _balance balance to calculate the discounted balance of
* @param _daysDifference days of difference between the last updated day and the day of interest
*/
function _calculateDiscountedBalance(uint256 _balance, uint256 _daysDifference) internal view returns (uint256) {
if (_daysDifference == 0) {
return _balance;
} else if (_daysDifference <= R_TABLE_LOOKUP) {
return Math64x64.mulu(R[_daysDifference], _balance);
} else {
int128 r = Math64x64.pow(GAMMA_64x64, _daysDifference);
return Math64x64.mulu(r, _balance);
}
}

/**
* Calculate the inflationary balance of a demurraged balance
* @param _balance Demurraged balance to calculate the inflationary balance of
* @param _dayUpdated The day the balance was last updated
*/
function _calculateInflationaryBalance(uint256 _balance, uint256 _dayUpdated) internal pure returns (uint256) {
// calculate the inflationary balance by dividing the balance by GAMMA^days
// note: GAMMA < 1, so dividing by a power of it, returns a bigger number,
// so the numerical inprecision is in the least significant bits.
int128 i = Math64x64.pow(BETA_64x64, _dayUpdated);
return Math64x64.mulu(i, _balance);
}
}
Loading
Loading