-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #100 from aboutcircles/test/hub-unit-test-coverage
(hub-test): implemented DiscountedBalances unit test coverage
- Loading branch information
Showing
3 changed files
with
286 additions
and
31 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,31 +1,37 @@ | ||
CirclesTest:testCalculateIssuance() (gas: 3729002) | ||
CirclesTest:testConsecutiveClaimablePeriods() (gas: 375082) | ||
CirclesTest:testDemurragedTransfer() (gas: 229055) | ||
CompositeMintGroupsTest:testCompositeGroupMint() (gas: 212459) | ||
DemurrageTest:testDemurrageFactor() (gas: 79139) | ||
DemurrageTest:testFuzzStablePointIssuance(int192) (runs: 256, μ: 63114, ~: 63250) | ||
DemurrageTest:testInversionGammaBeta64x64_100years() (gas: 958541) | ||
DemurrageTest:testInversionGammaBeta64x64_100years_withExtension() (gas: 1083080) | ||
DemurrageTest:testInversionGammaBeta64x64_100years_withExtension_comparison() (gas: 1703713) | ||
DemurrageTest:testInversionGammaBeta64x64_20years() (gas: 221562) | ||
DemurrageTest:testRepeatedDemurrage() (gas: 15264702) | ||
ERC20LiftTest:testERC20Demurrage() (gas: 133270) | ||
ERC20LiftTest:testERC20Wrap() (gas: 954629) | ||
ERC20LiftTest:testWrapAndUnwrapInflationaryERC20() (gas: 89881) | ||
GroupMintTest:testRegisterGroup() (gas: 165153) | ||
HubPathTransferTest:testOperateFlowMatrixConsentedFlow() (gas: 265369) | ||
MigrationTest:testConversionMigrationV1ToTimeCircles() (gas: 18365) | ||
MintGroupCirclesTest:testDirectSelfGroupMintFails() (gas: 341135) | ||
MintGroupCirclesTest:testGroupMint() (gas: 340165) | ||
MintGroupCirclesTest:testGroupMintFail() (gas: 23302) | ||
MintGroupCirclesTest:testGroupMintMany() (gas: 858065) | ||
MintGroupCirclesTest:testGroupMintMultiCollateral() (gas: 755126) | ||
MintGroupCirclesTest:testSequentialGroupMint() (gas: 511760) | ||
NamesTest:testBase58Conversion() (gas: 78904) | ||
NamesTest:testCustomName() (gas: 40809) | ||
NamesTest:testInvalidCustomNames() (gas: 20017) | ||
NamesTest:testMetadataDigest() (gas: 35073) | ||
NamesTest:testShortName() (gas: 102230) | ||
NamesTest:testShortNameWithNonce() (gas: 73360) | ||
NamesTest:testShortNameWithPadding() (gas: 71662) | ||
V1MintStatusUpdateTest:testMigrationFromV1DuringBootstrap() (gas: 2542748) | ||
CirclesTest:testCalculateIssuance() (gas: 5477687) | ||
CirclesTest:testConsecutiveClaimablePeriods() (gas: 489668) | ||
CirclesTest:testDemurragedTransfer() (gas: 267048) | ||
CompositeMintGroupsTest:testCompositeGroupMint() (gas: 234772) | ||
DemurrageTest:testDemurrageFactor() (gas: 89594) | ||
DemurrageTest:testFuzzStablePointIssuance(int192) (runs: 256, μ: 92257, ~: 92418) | ||
DemurrageTest:testInversionGammaBeta64x64_100years() (gas: 1304950) | ||
DemurrageTest:testInversionGammaBeta64x64_100years_withExtension() (gas: 1471985) | ||
DemurrageTest:testInversionGammaBeta64x64_100years_withExtension_comparison() (gas: 2275673) | ||
DemurrageTest:testInversionGammaBeta64x64_20years() (gas: 307602) | ||
DemurrageTest:testRepeatedDemurrage() (gas: 21734392) | ||
DiscountedBalancesTest:testBalanceOfOnDay(address,uint256,uint64) (runs: 257, μ: 50694, ~: 49914) | ||
DiscountedBalancesTest:testDiscountAndAddToBalance() (gas: 96291) | ||
DiscountedBalancesTest:testDiscountFromInitialZeroBalanceAndAddValue(address,uint256,uint256,uint64) (runs: 258, μ: 37247, ~: 39734) | ||
DiscountedBalancesTest:testRevertInputDayBeforeLastUpdatedDay(uint64) (runs: 258, μ: 51247, ~: 51247) | ||
DiscountedBalancesTest:testTotalSupply(uint256,uint192) (runs: 258, μ: 52826, ~: 53576) | ||
DiscountedBalancesTest:testUpdateBalance(address,uint256,uint256,uint64) (runs: 258, μ: 33619, ~: 37452) | ||
ERC20LiftTest:testERC20Demurrage() (gas: 154291) | ||
ERC20LiftTest:testERC20Wrap() (gas: 1019942) | ||
ERC20LiftTest:testWrapAndUnwrapInflationaryERC20() (gas: 107003) | ||
GroupMintTest:testRegisterGroup() (gas: 169388) | ||
HubPathTransferTest:testOperateFlowMatrixConsentedFlow() (gas: 303592) | ||
MigrationTest:testConversionMigrationV1ToTimeCircles() (gas: 27527) | ||
MintGroupCirclesTest:testDirectSelfGroupMintFails() (gas: 371260) | ||
MintGroupCirclesTest:testGroupMint() (gas: 370079) | ||
MintGroupCirclesTest:testGroupMintFail() (gas: 25601) | ||
MintGroupCirclesTest:testGroupMintMany() (gas: 980226) | ||
MintGroupCirclesTest:testGroupMintMultiCollateral() (gas: 853285) | ||
MintGroupCirclesTest:testSequentialGroupMint() (gas: 583887) | ||
NamesTest:testBase58Conversion() (gas: 122264) | ||
NamesTest:testCustomName() (gas: 42499) | ||
NamesTest:testInvalidCustomNames() (gas: 20402) | ||
NamesTest:testMetadataDigest() (gas: 36399) | ||
NamesTest:testShortName() (gas: 128347) | ||
NamesTest:testShortNameWithNonce() (gas: 82799) | ||
NamesTest:testShortNameWithPadding() (gas: 79690) | ||
V1MintStatusUpdateTest:testMigrationFromV1DuringBootstrap() (gas: 3848577) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,201 @@ | ||
// SPDX-License-Identifier: AGPL-3.0-only | ||
pragma solidity >=0.8.13; | ||
|
||
import {console2, Test} from "forge-std/Test.sol"; | ||
import {TimeCirclesSetup} from "../setup/TimeCirclesSetup.sol"; | ||
import {IERC1155} from "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; | ||
import {ICirclesCompactErrors, ICirclesDemurrageErrors} from "src/errors/Errors.sol"; | ||
import {IDiscountedBalances, MockDiscountedBalances} from "./MockDiscountedBalances.sol"; | ||
|
||
contract DiscountedBalancesTest is Test, TimeCirclesSetup, ICirclesCompactErrors, ICirclesDemurrageErrors { | ||
MockDiscountedBalances public discountedBalances; | ||
// represents Demurrage.MAX_VALUE | ||
uint256 internal maxBalance; | ||
// default test values | ||
address internal defaultAvatar; | ||
uint256 internal defaultId; | ||
uint192 internal defaultValue; | ||
uint64 internal defaultDay; | ||
|
||
function setUp() public { | ||
// Set time in 2021 | ||
startTime(); | ||
|
||
discountedBalances = new MockDiscountedBalances(INFLATION_DAY_ZERO); | ||
maxBalance = discountedBalances.maxBalance(); | ||
|
||
defaultAvatar = address(0xa4a7a5); | ||
defaultId = uint256(0x1d); | ||
defaultValue = uint192(10 ** 18); | ||
defaultDay = uint64(0xda1); | ||
} | ||
|
||
// Internal _updateBalance(address _account, uint256 _id, uint256 _balance, uint64 _day) | ||
|
||
function testUpdateBalance(address avatar, uint256 id, uint256 newBalance, uint64 newDay) public { | ||
if (newBalance <= maxBalance) { | ||
// balance should be updated correctly | ||
discountedBalances.updateBalance(avatar, id, newBalance, newDay); | ||
|
||
uint192 updatedAvatarBalance = discountedBalances.getAvatarBalanceValue(id, avatar); | ||
uint64 updatedAvatarLastUpdatedDay = discountedBalances.getAvatarLastUpdatedDayValue(id, avatar); | ||
|
||
assertEq(updatedAvatarBalance, newBalance); | ||
assertEq(updatedAvatarLastUpdatedDay, newDay); | ||
} else { | ||
// call should revert when balance exceeds MAX_VALUE | ||
vm.expectRevert(abi.encodeWithSelector(CirclesErrorAddressUintArgs.selector, avatar, id, 0x81)); | ||
discountedBalances.updateBalance(avatar, id, newBalance, newDay); | ||
} | ||
} | ||
|
||
// Internal _discountAndAddToBalance(address _account, uint256 _id, uint256 _value, uint64 _day) | ||
|
||
function testDiscountFromInitialZeroBalanceAndAddValue( | ||
address avatar, | ||
uint256 id, | ||
uint256 newBalance, | ||
uint64 newDay | ||
) public { | ||
// behaves exactly the same as _updateBalance | ||
if (newBalance <= maxBalance) { | ||
// balance should be updated correctly | ||
discountedBalances.discountAndAddToBalance(avatar, id, newBalance, newDay); | ||
|
||
uint192 updatedAvatarBalance = discountedBalances.getAvatarBalanceValue(id, avatar); | ||
uint64 updatedAvatarLastUpdatedDay = discountedBalances.getAvatarLastUpdatedDayValue(id, avatar); | ||
|
||
assertEq(updatedAvatarBalance, newBalance); | ||
assertEq(updatedAvatarLastUpdatedDay, newDay); | ||
} else { | ||
// call should revert when balance exceeds MAX_VALUE | ||
vm.expectRevert(abi.encodeWithSelector(CirclesErrorAddressUintArgs.selector, avatar, id, 0x82)); | ||
discountedBalances.discountAndAddToBalance(avatar, id, newBalance, newDay); | ||
} | ||
} | ||
|
||
function testDiscountAndAddToBalance() public { | ||
// store default values | ||
discountedBalances.updateBalance(defaultAvatar, defaultId, defaultValue, defaultDay); | ||
|
||
// first branch: dayDifference == 0 (input: same day as last updated and defaultValue) | ||
uint64 day = defaultDay; | ||
uint256 addedValue = defaultValue; | ||
|
||
discountedBalances.discountAndAddToBalance(defaultAvatar, defaultId, addedValue, day); | ||
|
||
// should update balance by adding value without applying discount and leaving same last updated day | ||
uint192 updatedAvatarBalance = discountedBalances.getAvatarBalanceValue(defaultId, defaultAvatar); | ||
uint64 updatedAvatarLastUpdatedDay = discountedBalances.getAvatarLastUpdatedDayValue(defaultId, defaultAvatar); | ||
assertEq(updatedAvatarBalance, 2 * defaultValue); | ||
assertEq(updatedAvatarLastUpdatedDay, defaultDay); | ||
|
||
// second branch: dayDifference != 0 and discount < addedValue (input: small day difference and defaultValue) | ||
day += 10; | ||
|
||
// should emit DiscountCost events | ||
_expectEmitDiscountEvents(defaultId, defaultAvatar); | ||
|
||
discountedBalances.discountAndAddToBalance(defaultAvatar, defaultId, addedValue, day); | ||
|
||
// should increase balance by applying small discount and adding significant value, update last updated day | ||
updatedAvatarBalance = discountedBalances.getAvatarBalanceValue(defaultId, defaultAvatar); | ||
updatedAvatarLastUpdatedDay = discountedBalances.getAvatarLastUpdatedDayValue(defaultId, defaultAvatar); | ||
assertTrue(updatedAvatarBalance > 2 * defaultValue && updatedAvatarBalance < 3 * defaultValue); | ||
assertEq(updatedAvatarLastUpdatedDay, day); | ||
|
||
// third branch: dayDifference != 0 and discount > addedValue (input: big day difference and small added value) | ||
day += 7200; | ||
addedValue /= 10; | ||
|
||
// should emit DiscountCost events | ||
_expectEmitDiscountEvents(defaultId, defaultAvatar); | ||
|
||
discountedBalances.discountAndAddToBalance(defaultAvatar, defaultId, addedValue, day); | ||
|
||
// should decrease balance by applying significant discount and adding small value, update last updated day | ||
updatedAvatarBalance = discountedBalances.getAvatarBalanceValue(defaultId, defaultAvatar); | ||
updatedAvatarLastUpdatedDay = discountedBalances.getAvatarLastUpdatedDayValue(defaultId, defaultAvatar); | ||
assertTrue(updatedAvatarBalance < defaultValue); | ||
assertEq(updatedAvatarLastUpdatedDay, day); | ||
} | ||
|
||
function testRevertInputDayBeforeLastUpdatedDay(uint64 newDay) public { | ||
vm.assume(newDay > 0); | ||
// store default values and newDay (becomes the last updated day) | ||
discountedBalances.updateBalance(defaultAvatar, defaultId, defaultValue, newDay); | ||
|
||
// set input as a day before the last updated day | ||
uint64 inputDay = newDay - 1; | ||
|
||
// two functions should revert | ||
|
||
// test internal discountAndAddToBalance | ||
vm.expectRevert(abi.encodeWithSelector(CirclesErrorAddressUintArgs.selector, defaultAvatar, newDay, 0xA1)); | ||
discountedBalances.discountAndAddToBalance(defaultAvatar, defaultId, defaultValue, inputDay); | ||
// test public balanceOfOnDay | ||
vm.expectRevert(abi.encodeWithSelector(CirclesErrorAddressUintArgs.selector, defaultAvatar, newDay, 0xA0)); | ||
discountedBalances.balanceOfOnDay(defaultAvatar, defaultId, inputDay); | ||
} | ||
|
||
// Public totalSupply(uint256 _id) | ||
|
||
function testTotalSupply(uint256 id, uint192 balance) public { | ||
uint64 day = discountedBalances.day(block.timestamp); | ||
discountedBalances.updateTotalSupply(id, balance, day); | ||
|
||
uint192 storedSupply = discountedBalances.getTotalSupplyBalanceValue(id); | ||
uint64 storedDay = discountedBalances.getTotalSupplyLastUpdatedDayValue(id); | ||
assertEq(balance, storedSupply); | ||
assertEq(day, storedDay); | ||
|
||
uint256 staticCallResult = discountedBalances.totalSupply(id); | ||
assertEq(staticCallResult, balance); | ||
|
||
// test total supply is discounted on a new day | ||
if (balance > 10) { | ||
skipTime(1 days); | ||
uint256 discountedTotalSupply = discountedBalances.totalSupply(id); | ||
assertTrue(uint192(discountedTotalSupply) < balance); | ||
// test supply is discounted in a year | ||
skipTime(730 days); | ||
uint256 totalSupplyInAYear = discountedBalances.totalSupply(id); | ||
assertTrue(uint192(totalSupplyInAYear) < uint192(discountedTotalSupply)); | ||
} | ||
} | ||
|
||
// Public balanceOfOnDay(address _account, uint256 _id, uint64 _day) | ||
|
||
function testBalanceOfOnDay(address avatar, uint256 id, uint64 day) public { | ||
vm.assume(day <= type(uint64).max - 31); | ||
// zero balance always returns 0,0 | ||
(uint256 balanceOnDay, uint256 discountCost) = discountedBalances.balanceOfOnDay(avatar, id, day); | ||
assertEq(balanceOnDay, 0); | ||
assertEq(discountCost, 0); | ||
|
||
// store default balance for avatar | ||
discountedBalances.updateBalance(avatar, id, defaultValue, day); | ||
|
||
// returned balance should remain the same on the same day as last update, with a discount cost of 0. | ||
uint64 requestedDay = day; | ||
(balanceOnDay, discountCost) = discountedBalances.balanceOfOnDay(avatar, id, requestedDay); | ||
assertEq(balanceOnDay, defaultValue); | ||
assertEq(discountCost, 0); | ||
|
||
// returned balance should be discounted | ||
requestedDay += 31; | ||
(balanceOnDay, discountCost) = discountedBalances.balanceOfOnDay(avatar, id, requestedDay); | ||
assertEq(balanceOnDay + discountCost, defaultValue); | ||
assertTrue(discountCost > 0); | ||
} | ||
|
||
// Internal helpers | ||
|
||
/// @dev should emit IERC1155.TransferSingle and DiscountCost events (discountCost value check is skipped) | ||
function _expectEmitDiscountEvents(uint256 id, address avatar) internal { | ||
vm.expectEmit(true, true, true, false); | ||
emit IERC1155.TransferSingle(address(this), avatar, address(0), id, 0); | ||
vm.expectEmit(true, true, false, false); | ||
emit IDiscountedBalances.DiscountCost(avatar, id, 0); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
// SPDX-License-Identifier: AGPL-3.0-only | ||
pragma solidity >=0.8.13; | ||
|
||
import {DiscountedBalances} from "src/circles/DiscountedBalances.sol"; | ||
|
||
interface IDiscountedBalances { | ||
event DiscountCost(address indexed account, uint256 indexed id, uint256 discountCost); | ||
} | ||
|
||
contract MockDiscountedBalances is DiscountedBalances { | ||
// Constructor | ||
|
||
constructor(uint256 _inflationDayZero) DiscountedBalances(_inflationDayZero) {} | ||
|
||
// External functions | ||
|
||
function maxBalance() external pure returns (uint256) { | ||
return MAX_VALUE; | ||
} | ||
|
||
function getAvatarBalanceValue(uint256 id, address avatar) external view returns (uint192) { | ||
return discountedBalances[id][avatar].balance; | ||
} | ||
|
||
function getAvatarLastUpdatedDayValue(uint256 id, address avatar) external view returns (uint64) { | ||
return discountedBalances[id][avatar].lastUpdatedDay; | ||
} | ||
|
||
function getTotalSupplyBalanceValue(uint256 id) external view returns (uint192) { | ||
return discountedTotalSupplies[id].balance; | ||
} | ||
|
||
function getTotalSupplyLastUpdatedDayValue(uint256 id) external view returns (uint64) { | ||
return discountedTotalSupplies[id].lastUpdatedDay; | ||
} | ||
|
||
function updateBalance(address avatar, uint256 id, uint256 newBalance, uint64 newDay) external { | ||
_updateBalance(avatar, id, newBalance, newDay); | ||
} | ||
|
||
function discountAndAddToBalance(address avatar, uint256 id, uint256 addedValue, uint64 newDay) external { | ||
_discountAndAddToBalance(avatar, id, addedValue, newDay); | ||
} | ||
|
||
function updateTotalSupply(uint256 id, uint192 _balance, uint64 _day) external { | ||
discountedTotalSupplies[id] = DiscountedBalance({balance: _balance, lastUpdatedDay: _day}); | ||
} | ||
} |