diff --git a/src/circles/Circles.sol b/src/circles/Circles.sol index dfb2571..aeb2394 100644 --- a/src/circles/Circles.sol +++ b/src/circles/Circles.sol @@ -157,7 +157,7 @@ contract Circles is ERC1155, ICirclesErrors { _calculateDiscountedBalance(totalSupplyBalance.balance, today - totalSupplyBalance.lastUpdatedDay) + _value; if (newTotalSupply > MAX_VALUE) { // DiscountedBalances: balance exceeds maximum value - revert CirclesERC1155AmountExceedsMaxUint190(_account, _id, newTotalSupply, 2); + revert CirclesDemurrageAmountExceedsMaxUint190(_account, _id, newTotalSupply, 2); } totalSupplyBalance.balance = uint192(newTotalSupply); totalSupplyBalance.lastUpdatedDay = today; diff --git a/src/circles/Demurrage.sol b/src/circles/Demurrage.sol index eb8da73..18b461e 100644 --- a/src/circles/Demurrage.sol +++ b/src/circles/Demurrage.sol @@ -4,7 +4,7 @@ pragma solidity >=0.8.24; import "../errors/Errors.sol"; import "../lib/Math64x64.sol"; -contract Demurrage is ICirclesERC1155Errors { +contract Demurrage is ICirclesDemurrageErrors { // Type declarations /** @@ -142,6 +142,10 @@ contract Demurrage is ICirclesERC1155Errors { */ int128[15] internal R; + // Events + + event DiscountCost(address indexed account, uint256 indexed id, uint256 discountCost); + // Constructor constructor() { diff --git a/src/circles/DiscountedBalances.sol b/src/circles/DiscountedBalances.sol index 0b6e16a..38ea005 100644 --- a/src/circles/DiscountedBalances.sol +++ b/src/circles/DiscountedBalances.sol @@ -35,10 +35,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 balanceOnDay_ The discounted balance of the account for the Circles identifier on specified day + * @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 balanceOnDay_, 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 CirclesDemurrageDayBeforeLastUpdatedDay(_account, _id, _day, discountedBalance.lastUpdatedDay, 0); + } + uint256 dayDifference; + unchecked { + dayDifference = _day - discountedBalance.lastUpdatedDay; + } + // Calculate the discounted balance + balanceOnDay_ = _calculateDiscountedBalance(discountedBalance.balance, dayDifference); + // Calculate the discount cost; this can be unchecked as cost is strict positive + unchecked { + discountCost_ = discountedBalance.balance - balanceOnDay_; + } + return (balanceOnDay_, discountCost_); } /** @@ -63,7 +83,7 @@ contract DiscountedBalances is Demurrage { function _updateBalance(address _account, uint256 _id, uint256 _balance, uint64 _day) internal { if (_balance > MAX_VALUE) { // DiscountedBalances: balance exceeds maximum value - revert CirclesERC1155AmountExceedsMaxUint190(_account, _id, _balance, 0); + revert CirclesDemurrageAmountExceedsMaxUint190(_account, _id, _balance, 0); } DiscountedBalance storage discountedBalance = discountedBalances[_id][_account]; discountedBalance.balance = uint192(_balance); @@ -79,14 +99,28 @@ 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); - uint256 newBalance = discountedBalanceValue + _value; - if (newBalance > MAX_VALUE) { + if (_day < discountedBalance.lastUpdatedDay) { + // DiscountedBalances: day is before last updated day + revert CirclesDemurrageDayBeforeLastUpdatedDay(_account, _id, _day, discountedBalance.lastUpdatedDay, 1); + } + uint256 dayDifference; + unchecked { + dayDifference = _day - discountedBalance.lastUpdatedDay; + } + uint256 discountedBalanceOnDay = _calculateDiscountedBalance(discountedBalance.balance, dayDifference); + // Calculate the discount cost; this can be unchecked as cost is strict positive + unchecked { + uint256 discountCost = discountedBalance.balance - discountedBalanceOnDay; + if (discountCost > 0) { + emit DiscountCost(_account, _id, discountCost); + } + } + uint256 updatedBalance = discountedBalanceOnDay + _value; + if (updatedBalance > MAX_VALUE) { // DiscountedBalances: balance exceeds maximum value - revert CirclesERC1155AmountExceedsMaxUint190(_account, _id, newBalance, 1); + revert CirclesDemurrageAmountExceedsMaxUint190(_account, _id, updatedBalance, 1); } - discountedBalance.balance = uint192(newBalance); + discountedBalance.balance = uint192(updatedBalance); discountedBalance.lastUpdatedDay = _day; } } diff --git a/src/circles/ERC1155.sol b/src/circles/ERC1155.sol index 0e700be..2c4a8af 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..9894daf 100644 --- a/src/errors/Errors.sol +++ b/src/errors/Errors.sol @@ -35,10 +35,16 @@ interface IHubErrors { error CirclesHubNettedFlowMismatch(uint16 vertexPosition, int256 matrixNettedFlow, int256 streamNettedFlow); } -interface ICirclesERC1155Errors { +interface ICirclesDemurrageErrors { error CirclesERC1155MintBlocked(address human, address mintV1Status); - error CirclesERC1155AmountExceedsMaxUint190(address account, uint256 circlesId, uint256 amount, uint8 code); + error CirclesDemurrageAmountExceedsMaxUint190(address account, uint256 circlesId, uint256 amount, uint8 code); + + error CirclesDemurrageDayBeforeLastUpdatedDay( + address account, uint256 circlesId, uint64 day, uint64 lastUpdatedDay, uint8 code + ); + + error CirclesERC1155CannotReceiveBatch(uint8 code); } interface ICirclesErrors { @@ -54,8 +60,6 @@ interface ICirclesErrors { error CirclesInvalidParameter(uint256 parameter, uint8 code); - error CirclesERC1155CannotReceiveBatch(uint8 code); - error CirclesAmountOverflow(uint256 amount, uint8 code); error CirclesArraysLengthMismatch(uint256 lengthArray1, uint256 lengthArray2, uint8 code); diff --git a/src/lift/DemurrageCircles.sol b/src/lift/DemurrageCircles.sol index 56aaaa8..81ff621 100644 --- a/src/lift/DemurrageCircles.sol +++ b/src/lift/DemurrageCircles.sol @@ -17,13 +17,11 @@ contract DemurrageCircles is MasterCopyNonUpgradable, ERC20DiscountedBalances, E INameRegistry public nameRegistry; - address public avatar; - // Events - event Deposit(address indexed account, uint256 amount, uint256 inflationaryAmount); + event DepositDemurraged(address indexed account, uint256 amount, uint256 inflationaryAmount); - event Withdraw(address indexed account, uint256 amount, uint256 inflationaryAmount); + event WithdrawDemurraged(address indexed account, uint256 amount, uint256 inflationaryAmount); // Modifiers @@ -74,7 +72,7 @@ contract DemurrageCircles is MasterCopyNonUpgradable, ERC20DiscountedBalances, E uint256 inflationaryAmount = _calculateInflationaryBalance(_amount, day(block.timestamp)); - emit Withdraw(msg.sender, _amount, inflationaryAmount); + emit WithdrawDemurraged(msg.sender, _amount, inflationaryAmount); } function totalSupply() external view override returns (uint256) { @@ -107,7 +105,7 @@ contract DemurrageCircles is MasterCopyNonUpgradable, ERC20DiscountedBalances, E uint256 inflationaryAmount = _calculateInflationaryBalance(_amount, day(block.timestamp)); - emit Deposit(_from, _amount, inflationaryAmount); + emit DepositDemurraged(_from, _amount, inflationaryAmount); return this.onERC1155Received.selector; } diff --git a/src/lift/ERC20DiscountedBalances.sol b/src/lift/ERC20DiscountedBalances.sol index 4454a92..35c1011 100644 --- a/src/lift/ERC20DiscountedBalances.sol +++ b/src/lift/ERC20DiscountedBalances.sol @@ -11,6 +11,8 @@ contract ERC20DiscountedBalances is ERC20Permit, Demurrage, IERC20 { // State variables + address public avatar; + /** * @dev The mapping of addresses to the discounted balances. */ @@ -55,7 +57,8 @@ contract ERC20DiscountedBalances is ERC20Permit, Demurrage, IERC20 { } function balanceOf(address _account) external view returns (uint256) { - return balanceOfOnDay(_account, day(block.timestamp)); + (uint256 balance,) = balanceOfOnDay(_account, day(block.timestamp)); + return balance; } function allowance(address _owner, address _spender) external view returns (uint256) { @@ -68,9 +71,28 @@ contract ERC20DiscountedBalances is ERC20Permit, Demurrage, IERC20 { // Public functions - function balanceOfOnDay(address _account, uint64 _day) public view returns (uint256) { + function balanceOfOnDay(address _account, uint64 _day) + public + view + returns (uint256 balanceOnDay_, uint256 discountCost_) + { DiscountedBalance memory discountedBalance = discountedBalances[_account]; - return _calculateDiscountedBalance(discountedBalance.balance, _day - discountedBalance.lastUpdatedDay); + if (_day < discountedBalance.lastUpdatedDay) { + // ERC20 DiscountedBalances: day is before last updated day + revert CirclesDemurrageDayBeforeLastUpdatedDay( + _account, toTokenId(avatar), _day, discountedBalance.lastUpdatedDay, 0 + ); + } + uint256 dayDifference; + unchecked { + dayDifference = _day - discountedBalance.lastUpdatedDay; + } + balanceOnDay_ = _calculateDiscountedBalance(discountedBalance.balance, _day - discountedBalance.lastUpdatedDay); + // Calculate the discount cost; this can be unchecked as cost is strict positive + unchecked { + discountCost_ = discountedBalance.balance - balanceOnDay_; + } + return (balanceOnDay_, discountCost_); } // Internal functions @@ -83,7 +105,7 @@ contract ERC20DiscountedBalances is ERC20Permit, Demurrage, IERC20 { function _updateBalance(address _account, uint256 _balance, uint64 _day) internal { if (_balance > MAX_VALUE) { // Balance exceeds maximum value. - revert CirclesERC1155AmountExceedsMaxUint190(_account, 0, _balance, 0); + revert CirclesDemurrageAmountExceedsMaxUint190(_account, toTokenId(avatar), _balance, 0); } DiscountedBalance storage discountedBalance = discountedBalances[_account]; discountedBalance.balance = uint192(_balance); @@ -92,23 +114,41 @@ contract ERC20DiscountedBalances is ERC20Permit, Demurrage, IERC20 { function _discountAndAddToBalance(address _account, uint256 _value, uint64 _day) internal { DiscountedBalance storage discountedBalance = discountedBalances[_account]; - uint256 newBalance = _calculateDiscountedBalanceAndCache( - discountedBalance.balance, _day - discountedBalance.lastUpdatedDay - ) + _value; - if (newBalance > MAX_VALUE) { + if (_day < discountedBalance.lastUpdatedDay) { + // ERC20 DiscountedBalances: day is before last updated day + revert CirclesDemurrageDayBeforeLastUpdatedDay( + _account, toTokenId(avatar), _day, discountedBalance.lastUpdatedDay, 1 + ); + } + uint256 dayDifference; + unchecked { + dayDifference = _day - discountedBalance.lastUpdatedDay; + } + uint256 discountedBalanceOnDay = _calculateDiscountedBalanceAndCache(discountedBalance.balance, dayDifference); + unchecked { + uint256 discountCost = discountedBalance.balance - discountedBalanceOnDay; + if (discountCost > 0) { + emit DiscountCost(_account, toTokenId(avatar), discountCost); + } + } + uint256 updatedBalance = discountedBalanceOnDay + _value; + if (updatedBalance > MAX_VALUE) { // Balance exceeds maximum value. - revert CirclesERC1155AmountExceedsMaxUint190(_account, 0, newBalance, 1); + revert CirclesDemurrageAmountExceedsMaxUint190(_account, toTokenId(avatar), updatedBalance, 1); } - discountedBalance.balance = uint192(newBalance); + discountedBalance.balance = uint192(updatedBalance); discountedBalance.lastUpdatedDay = _day; } function _transfer(address _from, address _to, uint256 _amount) internal { uint64 day = day(block.timestamp); - uint256 fromBalance = balanceOfOnDay(_from, day); + (uint256 fromBalance, uint256 discountCost) = balanceOfOnDay(_from, day); if (fromBalance < _amount) { revert ERC20InsufficientBalance(_from, fromBalance, _amount); } + if (discountCost > 0) { + emit DiscountCost(_from, toTokenId(avatar), discountCost); + } unchecked { _updateBalance(_from, fromBalance - _amount, day); } @@ -124,10 +164,13 @@ contract ERC20DiscountedBalances is ERC20Permit, Demurrage, IERC20 { function _burn(address _owner, uint256 _amount) internal { uint64 day = day(block.timestamp); - uint256 ownerBalance = balanceOfOnDay(_owner, day); + (uint256 ownerBalance, uint256 discountCost) = balanceOfOnDay(_owner, day); if (ownerBalance < _amount) { revert ERC20InsufficientBalance(_owner, ownerBalance, _amount); } + if (discountCost > 0) { + emit DiscountCost(_owner, toTokenId(avatar), discountCost); + } unchecked { _updateBalance(_owner, ownerBalance - _amount, day); } diff --git a/src/lift/InflationaryCircles.sol b/src/lift/InflationaryCircles.sol index 5ea86e6..e7674c1 100644 --- a/src/lift/InflationaryCircles.sol +++ b/src/lift/InflationaryCircles.sol @@ -21,9 +21,9 @@ contract InflationaryCircles is MasterCopyNonUpgradable, ERC20InflationaryBalanc // Events - event Deposit(address indexed account, uint256 amount, uint256 demurragedAmount); + event DepositInflationary(address indexed account, uint256 amount, uint256 demurragedAmount); - event Withdraw(address indexed account, uint256 amount, uint256 demurragedAmount); + event WithdrawInflationary(address indexed account, uint256 amount, uint256 demurragedAmount); // Modifiers @@ -80,7 +80,7 @@ contract InflationaryCircles is MasterCopyNonUpgradable, ERC20InflationaryBalanc hub.safeTransferFrom(address(this), msg.sender, toTokenId(avatar), demurragedAmount, ""); - emit Withdraw(msg.sender, _amount, demurragedAmount); + emit WithdrawInflationary(msg.sender, _amount, demurragedAmount); } function name() external view returns (string memory) { @@ -108,7 +108,7 @@ contract InflationaryCircles is MasterCopyNonUpgradable, ERC20InflationaryBalanc // calculate inflationary amount to mint to sender uint256 inflationaryAmount = _mintFromDemurragedAmount(_from, _amount); - emit Deposit(_from, inflationaryAmount, _amount); + emit DepositInflationary(_from, inflationaryAmount, _amount); return this.onERC1155Received.selector; } 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 diff --git a/test/lift/ERC20Demurrage.sol b/test/lift/ERC20Demurrage.sol new file mode 100644 index 0000000..4ec6292 --- /dev/null +++ b/test/lift/ERC20Demurrage.sol @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.8.13; + +import {Test} from "forge-std/Test.sol"; +import {StdCheats} from "forge-std/StdCheats.sol"; +import "forge-std/console.sol"; +import "../../src/circles/Demurrage.sol"; +import "../setup/TimeCirclesSetup.sol"; +import "../setup/HumanRegistration.sol"; +import "../hub/MockDeployment.sol"; +import "../hub/MockHub.sol"; + +contract ERC20LiftTest is Test, TimeCirclesSetup, HumanRegistration { + // State variables + + MockDeployment public mockDeployment; + MockHub public hub; + + mapping(address => DemurrageCircles) public erc20s; + + // Constructor + + constructor() HumanRegistration(2) {} + + // Setup + + function setUp() public { + // Set time in 2021 + startTime(); + + // Mock deployment + mockDeployment = new MockDeployment(INFLATION_DAY_ZERO, 365 days); + hub = mockDeployment.hub(); + + // register Alice and Bob + for (uint256 i = 0; i < 2; i++) { + vm.startPrank(addresses[i]); + hub.registerHumanUnrestricted(); + mockDeployment.nameRegistry().registerShortName(); + vm.stopPrank(); + } + + // skip time and mint + skipTime(14 days); + for (uint256 i = 0; i < 2; i++) { + vm.prank(addresses[i]); + hub.personalMintWithoutV1Check(); + } + + // lift some CRC into respective demurrage ERC20's + for (uint256 i = 0; i < 2; i++) { + vm.prank(addresses[i]); + hub.wrap(addresses[i], 300 * CRC, CirclesType.Demurrage); + } + + // get ERC20's + for (uint256 i = 0; i < 2; i++) { + erc20s[addresses[i]] = + DemurrageCircles(mockDeployment.erc20Lift().erc20Circles(CirclesType.Demurrage, addresses[i])); + } + } + + // Tests + + function testERC20Demurrage() public { + DemurrageCircles aliceERC20 = erc20s[addresses[0]]; + // DemurrageCircles bobERC20 = erc20s[addresses[1]]; + uint256 aliceCirclesId = uint256(uint160(addresses[0])); + + // commenting out because stack too deep; todo write test cleaner + + // uint256 aliceBalanceT1 = aliceERC20.balanceOf(addresses[0]); + // uint256 bobBalanceT1 = aliceERC20.balanceOf(addresses[1]); + // assertEq(aliceBalanceT1, 300 * CRC); + // assertEq(bobBalanceT1, 0); + + skipTime(5 days); + + uint64 dayT2 = hub.day(block.timestamp); + (uint256 aliceBalanceT2, uint256 aliceDiscountCostT2) = aliceERC20.balanceOfOnDay(addresses[0], dayT2); + // assertEq(aliceBalanceT2 + aliceDiscountCostT2, aliceBalanceT1); + + (uint256 bobBalanceT2, uint256 bobDiscountCostT2) = aliceERC20.balanceOfOnDay(addresses[1], dayT2); + // Bob had a zero balance, so there should be no discount cost + assertEq(bobBalanceT2 + bobDiscountCostT2, 0); + // assertEq(bobBalanceT2 + bobDiscountCostT2, bobBalanceT1); + + // send 50 CRC from Alice to Bob + vm.prank(addresses[0]); + vm.expectEmit(true, true, false, true, address(aliceERC20)); + emit Demurrage.DiscountCost(addresses[0], aliceCirclesId, aliceDiscountCostT2); + aliceERC20.transfer(addresses[1], 50 * CRC); + + (uint256 aliceBalanceT3, uint256 aliceDiscountCostT3) = aliceERC20.balanceOfOnDay(addresses[0], dayT2); + // there should not be a discount cost now that the transfer has updated her balance + assertEq(aliceDiscountCostT3, 0); + // total amounts should add up exactly + // assertEq(aliceBalanceT3 + aliceDiscountCostT3 + 50 * CRC + aliceDiscountCostT2, aliceBalanceT1); + + (uint256 bobBalanceT3, uint256 bobDiscountCostT3) = aliceERC20.balanceOfOnDay(addresses[1], dayT2); + // again, after transfer balance is up to date, so no discount cost + assertEq(bobDiscountCostT3, 0); + // assertEq(bobBalanceT3 + bobDiscountCostT3, bobBalanceT1 + 50 * CRC); + + skipTime(5 days); + uint64 dayT4 = hub.day(block.timestamp); + + (uint256 aliceBalanceT4, uint256 aliceDiscountCostT4) = aliceERC20.balanceOfOnDay(addresses[0], dayT4); + (uint256 bobBalanceT4, uint256 bobDiscountCostT4) = aliceERC20.balanceOfOnDay(addresses[1], dayT4); + assertEq(aliceBalanceT4 + aliceDiscountCostT4, aliceBalanceT3); + assertEq(bobBalanceT4 + bobDiscountCostT4, bobBalanceT3); + + vm.prank(addresses[0]); + aliceERC20.transfer(addresses[1], 50 * CRC); + + (aliceBalanceT2, aliceDiscountCostT2) = aliceERC20.balanceOfOnDay(addresses[0], dayT4); + (bobBalanceT2, bobDiscountCostT2) = aliceERC20.balanceOfOnDay(addresses[1], dayT4); + assertEq(aliceBalanceT2 + aliceDiscountCostT2 + 50 * CRC, aliceBalanceT4); + assertEq(bobBalanceT2 + bobDiscountCostT2 - 50 * CRC, bobBalanceT4); + } +} diff --git a/test/lift/ERC20Lift.t.sol b/test/lift/ERC20Lift.t.sol index 8826610..3ae2d89 100644 --- a/test/lift/ERC20Lift.t.sol +++ b/test/lift/ERC20Lift.t.sol @@ -54,18 +54,6 @@ contract ERC20LiftTest is Test, TimeCirclesSetup, HumanRegistration { uint256 aliceBalance = hub.balanceOf(addresses[0], uint256(uint160(addresses[0]))); - // test the master contracts in Lift - // ERC20Lift lift = mockDeployment.erc20Lift(); - // DemurrageCircles demurrage = mockDeployment.mastercopyDemurrageCircles(); - // address demurrageMasterCopy = lift.masterCopyERC20Wrapper(uint256(CirclesType.Demurrage)); - // assertEq(demurrageMasterCopy, address(demurrage)); - - // console.log("hub address: ", address(hub)); - - // DemurrageCircles proxyERC20D = DemurrageCircles(lift.ensureERC20(addresses[0], CirclesType.Demurrage)); - // console.log("proxyERC20D address: ", address(proxyERC20D)); - // assertEq(IProxy(payable(address(proxyERC20D))).masterCopy(), address(demurrage)); - // wrap some into demurrage ERC20 of Alice by Alice vm.prank(addresses[0]); DemurrageCircles aliceERC20 = DemurrageCircles(hub.wrap(addresses[0], 10 * CRC, CirclesType.Demurrage));