Skip to content

Commit

Permalink
(Demurrage): always compute R, either in constructor or lazily in ERC20
Browse files Browse the repository at this point in the history
  • Loading branch information
benjaminbollen committed Apr 5, 2024
1 parent f70fc57 commit e7b4d3b
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 22 deletions.
50 changes: 32 additions & 18 deletions src/circles/Demurrage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@ contract Demurrage is ICirclesERC1155Errors {

/**
* @dev Store a lookup table T(n) for computing issuance.
* T is only accessed for minting in Hub.sol, so it is initialized in
* storage of Hub.sol during the constructor, by copying these values.
* (It is not properly intialized in a ERC20 Proxy contract, but we never need
* to access it there, so it is not a problem - it is only initialized in the
* storage of the mastercopy during deployment.)
* See ../../specifications/TCIP009-demurrage.md for more details.
*/
int128[15] internal T = [
Expand All @@ -117,26 +122,35 @@ contract Demurrage is ICirclesERC1155Errors {
];

/**
* @dev Store a lookup table R(n) for computing issuance.
* @dev Store a lookup table R(n) for computing issuance and demurrage.
* This table is computed in the constructor of Hub.sol and mastercopy deployments,
* and lazily computed in the ERC20 Demurrage proxy contracts, then cached into their storage.
* The non-trivial situation for R(n) (vs T(n)) is that R is accessed
* from the ERC20 Demurrage proxy contracts, so their storage will not yet
* have been initialized with the constructor. (Counter to T which is only
* accessed for minting in Hub.sol, and as such initialized in the constructor
* of Hub.sol by Solidity by copying the python calculated values stored above.)
*
* Computing R in contract is done with .64bits precision, whereas the python computed
* table is slightly more accurate, but equal within dust (10^-18). See unit tests.
* However, we want to ensure that Hub.sol and the ERC20 Demurrage proxy contracts
* use the exact same R values (even if the difference would not matter).
* So for R we rely on the in-contract computed values.
* In the unit tests, the table of python computed values is stored in HIGHER_ACCURACY_R,
* and matched against the solidity computed values.
* See ../../specifications/TCIP009-demurrage.md for more details.
*/
int128[15] internal R = [
int128(18446744073709551616), // 0, ONE_64x64
int128(18443079296116538654), // 1, GAMMA_64x64
int128(18439415246597529027), // 2, GAMMA_64x64^2
int128(18435751925007877736), // 3, etc.
int128(18432089331202968517),
int128(18428427465038213837),
int128(18424766326369054888),
int128(18421105915050961582),
int128(18417446230939432544),
int128(18413787273889995104),
int128(18410129043758205300),
int128(18406471540399647861),
int128(18402814763669936209),
int128(18399158713424712450),
int128(18395503389519647372)
];
int128[15] internal R;

// Constructor

constructor() {
// we need to fill the R table upon construction so that
// in Hub.sol personalMint has the R table available
for (uint8 i = 0; i <= R_TABLE_LOOKUP; i++) {
R[i] = Math64x64.pow(GAMMA_64x64, i);
}
}

// Public functions

Expand Down
4 changes: 1 addition & 3 deletions src/lift/ERC20DiscountedBalances.sol
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,7 @@ contract ERC20DiscountedBalances is ERC20Permit, Demurrage, IERC20 {
}

function balanceOf(address _account) external view returns (uint256) {
uint256 result = balanceOfOnDay(_account, day(block.timestamp));
require(result > 0, "Turtle");
return result;
return balanceOfOnDay(_account, day(block.timestamp));
}

function allowance(address _owner, address _spender) external view returns (uint256) {
Expand Down
24 changes: 23 additions & 1 deletion test/circles/Demurrage.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,28 @@ contract DemurrageTest is Test, TimeCirclesSetup, Approximation {

MockDemurrage public demurrage;

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

// Setup

function setUp() public {
Expand All @@ -28,7 +50,7 @@ contract DemurrageTest is Test, TimeCirclesSetup, Approximation {
for (uint256 i = 0; i <= demurrage.rLength(); i++) {
assertTrue(
relativeApproximatelyEqual(
uint256(int256(Math64x64.pow(demurrage.gamma_64x64(), i))), uint256(int256(demurrage.r(i))), DUST
uint256(int256(Math64x64.pow(demurrage.gamma_64x64(), i))), HIGHER_ACCURACY_R[i], DUST
)
);
}
Expand Down

0 comments on commit e7b4d3b

Please sign in to comment.