From e7b4d3b02cde43ba256e89f13342f38950a7314f Mon Sep 17 00:00:00 2001 From: Benjamin Bollen Date: Fri, 5 Apr 2024 19:26:58 +0100 Subject: [PATCH] (Demurrage): always compute R, either in constructor or lazily in ERC20 --- src/circles/Demurrage.sol | 50 ++++++++++++++++++---------- src/lift/ERC20DiscountedBalances.sol | 4 +-- test/circles/Demurrage.t.sol | 24 ++++++++++++- 3 files changed, 56 insertions(+), 22 deletions(-) diff --git a/src/circles/Demurrage.sol b/src/circles/Demurrage.sol index 3b4a789..d5ef7e3 100644 --- a/src/circles/Demurrage.sol +++ b/src/circles/Demurrage.sol @@ -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 = [ @@ -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 diff --git a/src/lift/ERC20DiscountedBalances.sol b/src/lift/ERC20DiscountedBalances.sol index de6dd36..12c6103 100644 --- a/src/lift/ERC20DiscountedBalances.sol +++ b/src/lift/ERC20DiscountedBalances.sol @@ -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) { diff --git a/test/circles/Demurrage.t.sol b/test/circles/Demurrage.t.sol index fa292e2..0586f75 100644 --- a/test/circles/Demurrage.t.sol +++ b/test/circles/Demurrage.t.sol @@ -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 { @@ -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 ) ); }