diff --git a/.gas-snapshot b/.gas-snapshot index 66c89ec..2efe9b4 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -12,45 +12,45 @@ ZodiacModuleTest:testInitialState() (gas: 22128) ZodiacModuleTest:testNonAvatarCannotAddGuard() (gas: 208) ZodiacModuleTest:testNonAvatarCannotChangeTarget() (gas: 17739) ZodiacModuleTest:testRawStorage() (gas: 67713) -BudgetTest:testAllowanceChain() (gas: 567409) -BudgetTest:testAllowanceIsKeptTrackOfOnMulti() (gas: 248829) -BudgetTest:testAllowanceIsKeptTrackOfOnSingle() (gas: 244802) -BudgetTest:testAllowanceSpenderWithRoleFlags() (gas: 213584) -BudgetTest:testBadTimeshiftsRevert() (gas: 29139) -BudgetTest:testCannotReinit() (gas: 20184) -BudgetTest:testCantExecuteIfNotAuthorized() (gas: 134431) +BudgetTest:testAllowanceChain() (gas: 567229) +BudgetTest:testAllowanceIsKeptTrackOfOnMulti() (gas: 248718) +BudgetTest:testAllowanceIsKeptTrackOfOnSingle() (gas: 244757) +BudgetTest:testAllowanceSpenderWithRoleFlags() (gas: 213539) +BudgetTest:testBadTimeshiftsRevert() (gas: 29094) +BudgetTest:testCannotReinit() (gas: 20228) +BudgetTest:testCantExecuteIfNotAuthorized() (gas: 134386) BudgetTest:testCantExecuteInexistentAllowance() (gas: 18902) -BudgetTest:testCantExecuteMultiIfNotAuthorized() (gas: 136362) -BudgetTest:testCreateAllowance():(uint256) (gas: 134161) -BudgetTest:testCreateSuballowance():(uint256,uint256) (gas: 320221) -BudgetTest:testCreateSuballowanceWithInheritedRecurrency() (gas: 289735) -BudgetTest:testDisablingAllowanceBreaksChain() (gas: 577557) +BudgetTest:testCantExecuteMultiIfNotAuthorized() (gas: 136295) +BudgetTest:testCreateAllowance():(uint256) (gas: 134116) +BudgetTest:testCreateSuballowance():(uint256,uint256) (gas: 320131) +BudgetTest:testCreateSuballowanceWithInheritedRecurrency() (gas: 289645) +BudgetTest:testDisablingAllowanceBreaksChain() (gas: 577377) BudgetTest:testInitialState() (gas: 23348) -BudgetTest:testInvalidSpenderReverts() (gas: 37311) -BudgetTest:testMultipleAllowances() (gas: 379002) -BudgetTest:testNonAdminCannotUpdateAllowanceParams() (gas: 340916) -BudgetTest:testNotOwnerCannotCreateTopLevelAllowance() (gas: 21545) -BudgetTest:testOnlyParentAdminCanDisableAllowance() (gas: 574509) -BudgetTest:testRevertOnBadInputToMultiPayment() (gas: 135782) -BudgetTest:testUpdateAllowanceParams() (gas: 150927) -TimeShiftLibEncodingTest:testDecodingGas() (gas: 498) +BudgetTest:testInvalidSpenderReverts() (gas: 37266) +BudgetTest:testMultipleAllowances() (gas: 378912) +BudgetTest:testNonAdminCannotUpdateAllowanceParams() (gas: 340826) +BudgetTest:testNotOwnerCannotCreateTopLevelAllowance() (gas: 21500) +BudgetTest:testOnlyParentAdminCanDisableAllowance() (gas: 574329) +BudgetTest:testRevertOnBadInputToMultiPayment() (gas: 135715) +BudgetTest:testUpdateAllowanceParams() (gas: 150882) +TimeShiftLibEncodingTest:testDecodingGas() (gas: 531) TimeShiftLibEncodingTest:testEncodingGas() (gas: 634) -TimeShiftLibEncodingTest:testRoundtrips() (gas: 3364) -TimeShiftLibShiftTest:testDaily() (gas: 90945) +TimeShiftLibEncodingTest:testRoundtrips() (gas: 3319) +TimeShiftLibShiftTest:testDaily() (gas: 90942) TimeShiftLibShiftTest:testGasWorstCase() (gas: 11413) TimeShiftLibShiftTest:testMonthly() (gas: 53376) TimeShiftLibShiftTest:testOffsets() (gas: 37146) -TimeShiftLibShiftTest:testQuarterly() (gas: 108287) -TimeShiftLibShiftTest:testSemiyearly() (gas: 106929) +TimeShiftLibShiftTest:testQuarterly() (gas: 108284) +TimeShiftLibShiftTest:testSemiyearly() (gas: 106926) TimeShiftLibShiftTest:testWeekly() (gas: 55702) TimeShiftLibShiftTest:testYearly() (gas: 35843) RolesAuthTest:testExplicitAddrIsAuthorized() (gas: 9273) RolesAuthTest:testRoleFlags(uint8) (runs: 256, μ: 43795, ~: 43795) RolesAuthTest:testRoleFlagsEdgeCases() (gas: 48677) -FirmFactoryIntegrationTest:testExecutingPaymentsFromBudget() (gas: 1182442) -FirmFactoryIntegrationTest:testFactoryGas() (gas: 622323) -FirmFactoryIntegrationTest:testInitialState() (gas: 632679) -FirmFactoryIntegrationTest:testModuleUpgrades() (gas: 1700210) +FirmFactoryIntegrationTest:testExecutingPaymentsFromBudget() (gas: 1182419) +FirmFactoryIntegrationTest:testFactoryGas() (gas: 622412) +FirmFactoryIntegrationTest:testInitialState() (gas: 632768) +FirmFactoryIntegrationTest:testModuleUpgrades() (gas: 1700299) UpgradeableModuleProxyFactoryTest:testReturnData() (gas: 10183) UpgradeableModuleProxyFactoryTest:testRevert() (gas: 13344) UpgradeableModuleProxyFactoryTest:testUpgrade() (gas: 127999) diff --git a/src/budget/Budget.sol b/src/budget/Budget.sol index 97ab53a..9823b5f 100644 --- a/src/budget/Budget.sol +++ b/src/budget/Budget.sol @@ -11,7 +11,7 @@ import {TimeShiftLib, EncodedTimeShift} from "./TimeShiftLib.sol"; address constant NATIVE_ASSET = address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE); uint256 constant NO_PARENT_ID = 0; -uint64 constant INHERITED_RESET_TIME = 0; +uint40 constant INHERITED_RESET_TIME = 0; address constant IMPL_INIT_ADDRESS = address(1); /** @@ -24,7 +24,7 @@ contract Budget is FirmBase, ZodiacModule, RolesAuth { string public constant moduleId = "org.firm.budget"; uint256 public constant moduleVersion = 1; - using TimeShiftLib for uint64; + using TimeShiftLib for uint40; //////////////////////////////////////////////////////////////////////////////// // INITIALIZATION @@ -49,7 +49,7 @@ contract Budget is FirmBase, ZodiacModule, RolesAuth { uint256 amount; uint256 spent; address token; - uint64 nextResetTime; + uint40 nextResetTime; address spender; EncodedTimeShift recurrency; bool isDisabled; @@ -65,7 +65,7 @@ contract Budget is FirmBase, ZodiacModule, RolesAuth { address token, uint256 amount, EncodedTimeShift recurrency, - uint64 nextResetTime, + uint40 nextResetTime, string name ); event AllowanceStateChanged(uint256 indexed allowanceId, bool isEnabled); @@ -78,7 +78,7 @@ contract Budget is FirmBase, ZodiacModule, RolesAuth { address token, address indexed to, uint256 amount, - uint64 nextResetTime, + uint40 nextResetTime, string description ); event MultiPaymentExecuted( @@ -87,7 +87,7 @@ contract Budget is FirmBase, ZodiacModule, RolesAuth { address token, address[] tos, uint256[] amounts, - uint64 nextResetTime, + uint40 nextResetTime, string description ); @@ -122,7 +122,7 @@ contract Budget is FirmBase, ZodiacModule, RolesAuth { EncodedTimeShift recurrency, string memory name ) public returns (uint256 allowanceId) { - uint64 nextResetTime; + uint40 nextResetTime; if (parentAllowanceId == NO_PARENT_ID) { // Top-level allowances can only be created by the Safe @@ -133,7 +133,7 @@ contract Budget is FirmBase, ZodiacModule, RolesAuth { // For top-level allowances, recurrency needs to be set and cannot be zero // applyShift reverts with InvalidTimeShift if recurrency is unspecified // Therefore, nextResetTime is always greater than the current time - nextResetTime = uint64(block.timestamp).applyShift(recurrency); + nextResetTime = uint40(block.timestamp).applyShift(recurrency); } else { Allowance storage parentAllowance = _getAllowance(parentAllowanceId); @@ -153,7 +153,7 @@ contract Budget is FirmBase, ZodiacModule, RolesAuth { // Recurrency can be zero in sub-allowances and is inherited from the parent if (!recurrency.isInherited()) { // Will revert with InvalidTimeShift if recurrency is invalid - nextResetTime = uint64(block.timestamp).applyShift(recurrency); + nextResetTime = uint40(block.timestamp).applyShift(recurrency); } } @@ -250,7 +250,7 @@ contract Budget is FirmBase, ZodiacModule, RolesAuth { address token = allowance.token; // Make sure the payment is within budget all the way up to its top-level budget - (uint64 nextResetTime,) = _checkAndUpdateAllowanceChain(allowanceId, amount); + (uint40 nextResetTime,) = _checkAndUpdateAllowanceChain(allowanceId, amount); if (!_performTransfer(token, to, amount)) { revert PaymentExecutionFailed(allowanceId, token, to, amount); @@ -292,7 +292,7 @@ contract Budget is FirmBase, ZodiacModule, RolesAuth { } } - (uint64 nextResetTime,) = _checkAndUpdateAllowanceChain(allowanceId, totalAmount); + (uint40 nextResetTime,) = _checkAndUpdateAllowanceChain(allowanceId, totalAmount); address token = allowance.token; if (!_performMultiTransfer(token, tos, amounts)) { @@ -386,7 +386,7 @@ contract Budget is FirmBase, ZodiacModule, RolesAuth { function _checkAndUpdateAllowanceChain(uint256 allowanceId, uint256 amount) internal - returns (uint64 nextResetTime, bool allowanceResets) + returns (uint40 nextResetTime, bool allowanceResets) { Allowance storage allowance = allowances[allowanceId]; // allowanceId always points to an existing allowance @@ -401,9 +401,9 @@ contract Budget is FirmBase, ZodiacModule, RolesAuth { } else { nextResetTime = allowance.nextResetTime; - if (uint64(block.timestamp) >= nextResetTime) { + if (uint40(block.timestamp) >= nextResetTime) { allowanceResets = true; - nextResetTime = uint64(block.timestamp).applyShift(allowance.recurrency); + nextResetTime = uint40(block.timestamp).applyShift(allowance.recurrency); } if (allowanceResets) { diff --git a/src/budget/TimeShiftLib.sol b/src/budget/TimeShiftLib.sol index 1a87864..d1b96dd 100644 --- a/src/budget/TimeShiftLib.sol +++ b/src/budget/TimeShiftLib.sol @@ -4,25 +4,25 @@ pragma solidity 0.8.16; // Formal verification for library and formula: https://twitter.com/Zellic_io/status/1510341868021854209 import {BokkyPooBahsDateTimeLibrary as DateTimeLib} from "datetime/BokkyPooBahsDateTimeLibrary.sol"; -type EncodedTimeShift is bytes9; +type EncodedTimeShift is bytes6; struct TimeShift { TimeShiftLib.TimeUnit unit; // in the special case of seconds, offset doesn't apply - int64 offset; + int40 offset; } function encode(TimeShift memory shift) pure returns (EncodedTimeShift) { - return EncodedTimeShift.wrap(bytes9(abi.encodePacked(uint8(shift.unit), shift.offset))); + return EncodedTimeShift.wrap(bytes6(abi.encodePacked(uint8(shift.unit), shift.offset))); } -function decode(EncodedTimeShift encoded) pure returns (TimeShiftLib.TimeUnit unit, int64 offset) { - uint72 encodedValue = uint72(EncodedTimeShift.unwrap(encoded)); - unit = TimeShiftLib.TimeUnit(uint8(encodedValue >> 64)); - offset = int64(uint64(uint72(encodedValue))); +function decode(EncodedTimeShift encoded) pure returns (TimeShiftLib.TimeUnit unit, int40 offset) { + uint48 encodedValue = uint48(EncodedTimeShift.unwrap(encoded)); + unit = TimeShiftLib.TimeUnit(uint8(encodedValue >> 40)); + offset = int40(uint40(uint48(encodedValue))); } function isInherited(EncodedTimeShift encoded) pure returns (bool) { - return EncodedTimeShift.unwrap(encoded) == bytes9(0); + return EncodedTimeShift.unwrap(encoded) == bytes6(0); } using {decode, isInherited} for EncodedTimeShift global; @@ -43,10 +43,10 @@ library TimeShiftLib { error InvalidTimeShift(); - function applyShift(uint64 time, EncodedTimeShift shift) internal pure returns (uint64) { - (TimeUnit unit, int64 offset) = shift.decode(); + function applyShift(uint40 time, EncodedTimeShift shift) internal pure returns (uint40) { + (TimeUnit unit, int40 offset) = shift.decode(); - uint64 realTime = uint64(int64(time) + offset); + uint40 realTime = uint40(int40(time) + offset); (uint256 y, uint256 m, uint256 d) = realTime.toDate(); if (unit == TimeUnit.Daily) { @@ -66,7 +66,7 @@ library TimeShiftLib { } uint256 shiftedTs = DateTimeLib.timestampFromDateTime(y, m, d, 0, 0, 0); - return uint64(int64(uint64(shiftedTs)) - offset); + return uint40(int40(uint40(shiftedTs)) - offset); } /** @@ -83,7 +83,7 @@ library TimeShiftLib { return d2 <= daysInMonth ? (y, m, d2) : m < 12 ? (y, m + 1, d2 - daysInMonth) : (y + 1, 1, d2 - daysInMonth); } - function toDate(uint64 timestamp) internal pure returns (uint256 y, uint256 m, uint256 d) { + function toDate(uint40 timestamp) internal pure returns (uint256 y, uint256 m, uint256 d) { return DateTimeLib._daysToDate(timestamp / 1 days); } } diff --git a/src/budget/test/Budget.t.sol b/src/budget/test/Budget.t.sol index 17c45fe..0404b9f 100644 --- a/src/budget/test/Budget.t.sol +++ b/src/budget/test/Budget.t.sol @@ -46,7 +46,7 @@ contract BudgetTest is FirmTest { uint256 amount, uint256 spent, address token, - uint64 nextResetTime, + uint40 nextResetTime, address spender, EncodedTimeShift recurrency, bool isDisabled @@ -73,7 +73,7 @@ contract BudgetTest is FirmTest { budget.setAllowanceAmount(allowanceId, 1); budget.setAllowanceName(allowanceId, "new name"); - (, uint256 amount,,, uint64 nextResetTime, address spender,,) = budget.allowances(allowanceId); + (, uint256 amount,,, uint40 nextResetTime, address spender,,) = budget.allowances(allowanceId); assertEq(amount, 1); assertEq(nextResetTime, 1 days); @@ -103,7 +103,7 @@ contract BudgetTest is FirmTest { } function testAllowanceIsKeptTrackOfOnSingle() public { - uint64 initialTime = uint64(DateTimeLib.timestampFromDateTime(2022, 1, 1, 0, 0, 0)); + uint40 initialTime = uint40(DateTimeLib.timestampFromDateTime(2022, 1, 1, 0, 0, 0)); uint256 allowanceId = 1; vm.prank(address(avatar)); @@ -122,7 +122,7 @@ contract BudgetTest is FirmTest { } function testAllowanceIsKeptTrackOfOnMulti() public { - uint64 initialTime = uint64(DateTimeLib.timestampFromDateTime(2022, 1, 1, 0, 0, 0)); + uint40 initialTime = uint40(DateTimeLib.timestampFromDateTime(2022, 1, 1, 0, 0, 0)); uint256 allowanceId = 1; vm.prank(address(avatar)); @@ -152,7 +152,7 @@ contract BudgetTest is FirmTest { } function testMultipleAllowances() public { - uint64 initialTime = uint64(DateTimeLib.timestampFromDateTime(2022, 1, 1, 0, 0, 0)); + uint40 initialTime = uint40(DateTimeLib.timestampFromDateTime(2022, 1, 1, 0, 0, 0)); uint256 firstAllowanceId = 1; uint256 secondAllowanceId = 2; @@ -172,7 +172,7 @@ contract BudgetTest is FirmTest { } function testCreateSuballowance() public returns (uint256 topLevelAllowance, uint256 subAllowance) { - uint64 initialTime = uint64(DateTimeLib.timestampFromDateTime(2022, 1, 1, 0, 0, 0)); + uint40 initialTime = uint40(DateTimeLib.timestampFromDateTime(2022, 1, 1, 0, 0, 0)); vm.warp(initialTime); vm.prank(address(avatar)); @@ -218,7 +218,7 @@ contract BudgetTest is FirmTest { } function testCreateSuballowanceWithInheritedRecurrency() public { - uint64 initialTime = uint64(DateTimeLib.timestampFromDateTime(2022, 1, 1, 0, 0, 0)); + uint40 initialTime = uint40(DateTimeLib.timestampFromDateTime(2022, 1, 1, 0, 0, 0)); vm.warp(initialTime); vm.prank(address(avatar)); @@ -234,7 +234,7 @@ contract BudgetTest is FirmTest { } function testAllowanceChain() public { - uint64 initialTime = uint64(DateTimeLib.timestampFromDateTime(2022, 1, 1, 0, 0, 0)); + uint40 initialTime = uint40(DateTimeLib.timestampFromDateTime(2022, 1, 1, 0, 0, 0)); vm.warp(initialTime); vm.prank(address(avatar)); @@ -381,7 +381,7 @@ contract BudgetTest is FirmTest { address token, address indexed to, uint256 amount, - uint64 nextResetTime, + uint40 nextResetTime, string descrition ); @@ -390,9 +390,9 @@ contract BudgetTest is FirmTest { uint256 allowanceId, address to, uint256 amount, - uint64 expectedNextResetTime + uint40 expectedNextResetTime ) public { - (,, uint256 initialSpent, address token, uint64 initialNextReset,, EncodedTimeShift shift,) = + (,, uint256 initialSpent, address token, uint40 initialNextReset,, EncodedTimeShift shift,) = budget.allowances(allowanceId); if (block.timestamp >= initialNextReset) { @@ -404,7 +404,7 @@ contract BudgetTest is FirmTest { emit PaymentExecuted(allowanceId, actor, token, to, amount, expectedNextResetTime, ""); budget.executePayment(allowanceId, to, amount, ""); - (,, uint256 spent,, uint64 nextResetTime,,,) = budget.allowances(allowanceId); + (,, uint256 spent,, uint40 nextResetTime,,,) = budget.allowances(allowanceId); assertEq(spent, initialSpent + amount); assertEq(nextResetTime, shift.isInherited() ? 0 : expectedNextResetTime); diff --git a/src/budget/test/TimeShiftLib.t.sol b/src/budget/test/TimeShiftLib.t.sol index b7bfba2..a904529 100644 --- a/src/budget/test/TimeShiftLib.t.sol +++ b/src/budget/test/TimeShiftLib.t.sol @@ -56,23 +56,23 @@ contract TimeShiftLibShiftTest is FirmTest { TimeShift memory shift = TimeShift(TimeShiftLib.TimeUnit.Monthly, 1 hours); assertEq( - uint64(DateTimeLib.timestampFromDateTime(2022, 1, 1, 23, 23, 0)).applyShift(shift.encode()), + uint40(DateTimeLib.timestampFromDateTime(2022, 1, 1, 23, 23, 0)).applyShift(shift.encode()), DateTimeLib.timestampFromDateTime(2022, 1, 31, 23, 0, 0) ); assertEq( - uint64(DateTimeLib.timestampFromDateTime(2022, 1, 31, 23, 23, 0)).applyShift(shift.encode()), + uint40(DateTimeLib.timestampFromDateTime(2022, 1, 31, 23, 23, 0)).applyShift(shift.encode()), DateTimeLib.timestampFromDateTime(2022, 2, 28, 23, 0, 0) ); } - uint64 immutable from_ = uint64(DateTimeLib.timestampFromDateTime(2022, 12, 28, 0, 0, 0)); - uint64 immutable to_ = uint64(DateTimeLib.timestampFromDateTime(2023, 1, 2, 0, 0, 0)); + uint40 immutable from_ = uint40(DateTimeLib.timestampFromDateTime(2022, 12, 28, 0, 0, 0)); + uint40 immutable to_ = uint40(DateTimeLib.timestampFromDateTime(2023, 1, 2, 0, 0, 0)); function testGasWorstCase() public { TimeShift memory shift = TimeShift(TimeShiftLib.TimeUnit.Weekly, 0); uint256 initialGas = gasleft(); - assertEq(uint64(from_).applyShift(shift.encode()), to_); + assertEq(uint40(from_).applyShift(shift.encode()), to_); assertLt(initialGas - gasleft(), 12000); } @@ -87,7 +87,7 @@ contract TimeShiftLibShiftTest is FirmTest { uint256 d2 ) public { assertEq( - uint64(DateTimeLib.timestampFromDate(y1, m1, d1)).applyShift(TimeShift(unit, 0).encode()), + uint40(DateTimeLib.timestampFromDate(y1, m1, d1)).applyShift(TimeShift(unit, 0).encode()), DateTimeLib.timestampFromDate(y2, m2, d2) ); } @@ -105,22 +105,22 @@ contract TimeShiftLibEncodingTest is FirmTest { function testEncodingGas() public { TimeShift memory shift = TimeShift(TimeShiftLib.TimeUnit.Monthly, -1 hours); - assertEq(uint256(uint72(EncodedTimeShift.unwrap(shift.encode()))), 0x03fffffffffffff1f0); + assertEq(uint256(uint48(EncodedTimeShift.unwrap(shift.encode()))), 0x03fffffff1f0); } function testDecodingGas() public { - EncodedTimeShift encodedShift = EncodedTimeShift.wrap(0x03fffffffffffff1f0); + EncodedTimeShift encodedShift = EncodedTimeShift.wrap(0x03fffffff1f0); - (TimeShiftLib.TimeUnit unit, int64 offset) = encodedShift.decode(); + (TimeShiftLib.TimeUnit unit, int40 offset) = encodedShift.decode(); assertEq(uint8(unit), uint8(TimeShiftLib.TimeUnit.Monthly)); assertEq(offset, -1 hours); } - function assertRoundtrip(TimeShiftLib.TimeUnit inputUnit, int64 inputOffset) public { + function assertRoundtrip(TimeShiftLib.TimeUnit inputUnit, int40 inputOffset) public { TimeShift memory shift = TimeShift(inputUnit, inputOffset); EncodedTimeShift encoded = shift.encode(); - (TimeShiftLib.TimeUnit unit, int64 offset) = encoded.decode(); + (TimeShiftLib.TimeUnit unit, int40 offset) = encoded.decode(); assertEq(uint8(unit), uint8(inputUnit)); assertEq(offset, inputOffset);