From 40ad78b7857ceb3b721b0e23cf7a0052d8bd0ecb Mon Sep 17 00:00:00 2001 From: Benjamin Bollen Date: Fri, 3 May 2024 16:59:15 +0200 Subject: [PATCH] (circles): introduce event DiscountCost --- src/circles/Circles.sol | 2 -- src/circles/DiscountedBalances.sol | 46 +++++++++++++++++++++++++++--- src/circles/ERC1155.sol | 14 ++++----- src/errors/Errors.sol | 4 +++ test/circles/Circles.t.sol | 12 ++------ test/hub/PathTransferHub.t.sol | 3 ++ 6 files changed, 59 insertions(+), 22 deletions(-) diff --git a/src/circles/Circles.sol b/src/circles/Circles.sol index 2063a7a..1a878a2 100644 --- a/src/circles/Circles.sol +++ b/src/circles/Circles.sol @@ -74,8 +74,6 @@ contract Circles is ERC1155 { DiscountedBalances(_inflation_day_zero) {} - // External functions - // Public functions /** diff --git a/src/circles/DiscountedBalances.sol b/src/circles/DiscountedBalances.sol index f95c00f..04d16d7 100644 --- a/src/circles/DiscountedBalances.sol +++ b/src/circles/DiscountedBalances.sol @@ -13,6 +13,10 @@ contract DiscountedBalances is Demurrage { */ mapping(uint256 => mapping(address => DiscountedBalance)) public discountedBalances; + // Events + + event DiscountCost(address indexed account, uint256 indexed id, uint256 discountCost); + // Constructor /** @@ -30,10 +34,30 @@ contract DiscountedBalances is Demurrage { * @param _account Address of the account to calculate the balance of * @param _id Circles identifier for which to calculate the balance * @param _day Day since inflation_day_zero to calculate the balance for + * @return balance_ The discounted balance of the account for the Circles identifier + * @return discountCost_ The discount cost of the demurrage of the balance since the last update */ - function balanceOfOnDay(address _account, uint256 _id, uint64 _day) public view returns (uint256) { + function balanceOfOnDay(address _account, uint256 _id, uint64 _day) + public + view + returns (uint256 balance_, uint256 discountCost_) + { DiscountedBalance memory discountedBalance = discountedBalances[_id][_account]; - return _calculateDiscountedBalance(discountedBalance.balance, _day - discountedBalance.lastUpdatedDay); + if (_day < discountedBalance.lastUpdatedDay) { + // DiscountedBalances: day is before last updated day + revert CirclesERC1155DayBeforeLastUpdatedDay(_account, _id, _day, discountedBalance.lastUpdatedDay, 0); + } + uint256 dayDifference; + unchecked { + dayDifference = _day - discountedBalance.lastUpdatedDay; + } + // Calculate the discounted balance + balance_ = _calculateDiscountedBalance(discountedBalance.balance, dayDifference); + // Calculate the discount cost; this can be unchecked as cost is strict positive + unchecked { + discountCost_ = discountedBalance.balance - balance_; + } + return (balance_, discountCost_); } // Internal functions @@ -74,8 +98,22 @@ contract DiscountedBalances is Demurrage { */ function _discountAndAddToBalance(address _account, uint256 _id, uint256 _value, uint64 _day) internal { DiscountedBalance storage discountedBalance = discountedBalances[_id][_account]; - uint256 discountedBalanceValue = - _calculateDiscountedBalance(discountedBalance.balance, _day - discountedBalance.lastUpdatedDay); + if (_day < discountedBalance.lastUpdatedDay) { + // DiscountedBalances: day is before last updated day + revert CirclesERC1155DayBeforeLastUpdatedDay(_account, _id, _day, discountedBalance.lastUpdatedDay, 1); + } + uint256 dayDifference; + unchecked { + dayDifference = _day - discountedBalance.lastUpdatedDay; + } + uint256 discountedBalanceValue = _calculateDiscountedBalance(discountedBalance.balance, dayDifference); + // Calculate the discount cost; this can be unchecked as cost is strict positive + unchecked { + uint256 discountCost_ = discountedBalance.balance - discountedBalanceValue; + if (discountCost_ > 0) { + emit DiscountCost(_account, _id, discountCost_); + } + } uint256 newBalance = discountedBalanceValue + _value; if (newBalance > MAX_VALUE) { // DiscountedBalances: balance exceeds maximum value diff --git a/src/circles/ERC1155.sol b/src/circles/ERC1155.sol index fec60c6..08a2bd9 100644 --- a/src/circles/ERC1155.sol +++ b/src/circles/ERC1155.sol @@ -32,10 +32,6 @@ abstract contract ERC1155 is DiscountedBalances, Context, ERC165, IERC1155, IERC // Used as the URI for all token types by relying on ID substitution, e.g. https://token-cdn-domain/{id}.json string private _uri; - // Events - - event ConvertInflation(uint256 inflationValue, uint256 demurrageValue, uint64 day); - // Constructor /** @@ -73,7 +69,8 @@ abstract contract ERC1155 is DiscountedBalances, Context, ERC165, IERC1155, IERC * @dev See {IERC1155-balanceOf}. */ function balanceOf(address _account, uint256 _id) public view returns (uint256) { - return balanceOfOnDay(_account, _id, day(block.timestamp)); + (uint256 balance,) = balanceOfOnDay(_account, _id, day(block.timestamp)); + return balance; } /** @@ -93,7 +90,7 @@ abstract contract ERC1155 is DiscountedBalances, Context, ERC165, IERC1155, IERC uint256[] memory batchBalances = new uint256[](_accounts.length); for (uint256 i = 0; i < _accounts.length; ++i) { - batchBalances[i] = balanceOfOnDay(_accounts.unsafeMemoryAccess(i), _ids.unsafeMemoryAccess(i), today); + (batchBalances[i],) = balanceOfOnDay(_accounts.unsafeMemoryAccess(i), _ids.unsafeMemoryAccess(i), today); } return batchBalances; @@ -171,10 +168,13 @@ abstract contract ERC1155 is DiscountedBalances, Context, ERC165, IERC1155, IERC uint256 value = values.unsafeMemoryAccess(i); if (from != address(0)) { - uint256 fromBalance = balanceOfOnDay(from, id, today); + (uint256 fromBalance, uint256 discountCost) = balanceOfOnDay(from, id, today); if (fromBalance < value) { revert ERC1155InsufficientBalance(from, fromBalance, value, id); } + if (discountCost > 0) { + emit DiscountCost(from, id, discountCost); + } unchecked { // Overflow not possible: value <= fromBalance _updateBalance(from, id, fromBalance - value, today); diff --git a/src/errors/Errors.sol b/src/errors/Errors.sol index c5e6727..6548d0f 100644 --- a/src/errors/Errors.sol +++ b/src/errors/Errors.sol @@ -39,6 +39,10 @@ interface ICirclesERC1155Errors { error CirclesERC1155MintBlocked(address human, address mintV1Status); error CirclesERC1155AmountExceedsMaxUint190(address account, uint256 circlesId, uint256 amount, uint8 code); + + error CirclesERC1155DayBeforeLastUpdatedDay( + address account, uint256 circlesId, uint64 day, uint64 lastUpdatedDay, uint8 code + ); } interface ICirclesErrors { diff --git a/test/circles/Circles.t.sol b/test/circles/Circles.t.sol index 993b386..241a5f5 100644 --- a/test/circles/Circles.t.sol +++ b/test/circles/Circles.t.sol @@ -66,7 +66,7 @@ contract CirclesTest is Test, TimeCirclesSetup, Approximation { previousEndPeriod = endPeriod; // Generate a pseudo-random number between 1 and 4 - uint256 hoursSkip = uint256(keccak256(abi.encodePacked(block.timestamp, i, uint256(0)))) % 4 + 1; + uint256 hoursSkip = uint256(keccak256(abi.encodePacked(block.timestamp, i, uint256(0)))) % 34 + 1; uint256 secondsSkip = uint256(keccak256(abi.encodePacked(block.timestamp, i, uint256(1)))) % 3600; // Simulate passing of time variable windows of time (1-5 hours) @@ -98,23 +98,17 @@ contract CirclesTest is Test, TimeCirclesSetup, Approximation { assertEq(balance, expectedIssuance); } + skipTime(26 hours); + // send 5 CRC from alice to bob uint256 aliceBalance = circles.balanceOf(addresses[0], circlesIdentifiers[0]); uint256 bobBalance = circles.balanceOf(addresses[1], circlesIdentifiers[0]); - // uint256 aliceInflationaryBalance = circles.inflationaryBalanceOf(addresses[0], circlesIdentifiers[0]); - // uint256 bobInflationaryBalance = circles.inflationaryBalanceOf(addresses[1], circlesIdentifiers[0]); vm.prank(addresses[0]); circles.safeTransferFrom(addresses[0], addresses[1], circlesIdentifiers[0], 5 * CRC, ""); uint256 aliceBalanceAfter = circles.balanceOf(addresses[0], circlesIdentifiers[0]); uint256 bobBalanceAfter = circles.balanceOf(addresses[1], circlesIdentifiers[0]); - // uint256 aliceInflationaryBalanceAfter = circles.inflationaryBalanceOf(addresses[0], circlesIdentifiers[0]); - // uint256 bobInflationaryBalanceAfter = circles.inflationaryBalanceOf(addresses[1], circlesIdentifiers[0]); assertEq(aliceBalance - 5 * CRC, aliceBalanceAfter); assertEq(bobBalance + 5 * CRC, bobBalanceAfter); - // assertEq( - // aliceInflationaryBalance - aliceInflationaryBalanceAfter, - // bobInflationaryBalanceAfter - bobInflationaryBalance - // ); } // Private functions diff --git a/test/hub/PathTransferHub.t.sol b/test/hub/PathTransferHub.t.sol index 490887c..b03805d 100644 --- a/test/hub/PathTransferHub.t.sol +++ b/test/hub/PathTransferHub.t.sol @@ -71,6 +71,9 @@ contract HubPathTransferTest is Test, TimeCirclesSetup, HumanRegistration, Appro // first four avatars have a linear bi-directional trust uint256 M = N; + // induce demurrage for the path transfer of the balances + skipTime(2 days); + // Flow matrix for transferring Circles from Alice to David // with indication of which Circles are being sent // A B C D