Skip to content

Vesting and team payments contracts#1169

Open
princetonbishop wants to merge 20 commits intostagefrom
alt-vesting
Open

Vesting and team payments contracts#1169
princetonbishop wants to merge 20 commits intostagefrom
alt-vesting

Conversation

@princetonbishop
Copy link
Copy Markdown
Collaborator

@princetonbishop princetonbishop commented Mar 18, 2025

Description

What does this PR solve?

Fixes # (issue)

Checklist

  • Code follows the style guide
  • I have performed a self-review of my own code
  • New and existing tests pass locally
  • This PR is targeting the correct branch

Summary by CodeRabbit

  • New Features

    • Epoch-based payouts with admin-managed epochs, per-recipient allocations, minimum epoch duration, claiming, updates, revocation, and ERC20 payouts.
    • Multi-schedule token vesting: create/revoke schedules, recipient releases, active-schedule listing, ID/indexing, and vesting summaries.
    • Payment base & interfaces: configurable funds owner, ERC20 payment handling, token recovery, and standard admin payment interfaces.
  • Tests

    • Comprehensive unit tests covering epoch and vesting workflows, access control, claims, revocation, views, and recovery.
  • Chores

    • Foundry/config and coverage pipeline updates, .gitignore addition, dependency pins, test harness/forge-std update, and new deployment scripts.

@princetonbishop princetonbishop requested a review from a team as a code owner March 18, 2025 03:32
Comment thread protocol/contracts/admin/PaymentBase.sol Outdated
Comment thread protocol/contracts/admin/PaymentBase.sol Outdated
Comment thread protocol/contracts/admin/PaymentBase.sol Outdated
Comment thread protocol/contracts/interfaces/admin/ITeamPayments.sol Outdated
Comment thread protocol/contracts/interfaces/admin/ITeamPayments.sol Outdated
Comment thread protocol/contracts/templegold/TempleGoldVesting.sol Outdated
Comment thread protocol/contracts/templegold/TempleGoldVesting.sol Outdated
Comment thread protocol/contracts/templegold/TempleGoldVesting.sol Outdated
Comment thread protocol/contracts/templegold/TempleGoldVesting.sol Outdated
Comment thread protocol/contracts/templegold/TempleGoldVesting.sol Outdated
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Aug 21, 2025

📝 Walkthrough

Walkthrough

Adds a payment subsystem: PaymentBase (fundsOwner, paymentToken, token recovery, elevated access), EpochPayments (epoch-based admin-managed per-epoch allocations, epoch lifecycle, claiming and revocation), and VestingPayments (multi-schedule vesting with creation, revocation, release, and query APIs). Introduces IPaymentBase, IEpochPayments, and IVestingPayments interfaces, comprehensive Forge unit tests for VestingPayments and EpochPayments, deployment scripts for vesting/epoch contracts, Foundry config updates, and minor ancillary tooling and config changes.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Admin
  participant Epoch as EpochPayments
  participant ERC20 as paymentToken (ERC20)
  participant Recipient

  rect rgb(240,248,255)
  note right of Epoch: Admin creates/updates epoch allocations
  Admin->>Epoch: setEpochPayments(recipients[], amounts[])
  Epoch-->>Epoch: _startNextEpoch() (checks minEpochDuration, set start time)
  Epoch-->>Admin: NextEpochSet(epoch)
  Epoch-->>Admin: EpochAllocationSet(recipient, epoch, amount) [per recipient]
  end

  rect rgb(255,250,240)
  note right of Epoch: Recipient claims allocated amount for an epoch
  Recipient->>Epoch: claimEpoch(epoch)
  Epoch-->>Epoch: validate, mark claimed, totalClaimed += amount
  Epoch->>ERC20: safeTransferFrom(fundsOwner, recipient, amount)
  Epoch-->>Recipient: ClaimedEpoch(recipient, epoch, amount)
  end
Loading
sequenceDiagram
  autonumber
  participant Admin
  participant Vesting as VestingPayments
  participant ERC20 as paymentToken (ERC20)
  participant Recipient

  rect rgb(240,248,255)
  note right of Vesting: Admin creates vesting schedules
  Admin->>Vesting: createSchedules(schedules[])
  Vesting-->>Admin: ScheduleCreated(id, recipient, start, cliff, duration, amount)
  end

  rect rgb(245,255,250)
  note right of Vesting: Recipient requests vested funds over time
  Recipient->>Vesting: release(vestingId)
  Vesting-->>Vesting: compute releasable, update distributed
  Vesting->>ERC20: safeTransferFrom(fundsOwner, recipient, amount)
  Vesting-->>Recipient: Released(id, recipient, amount)
  end

  rect rgb(255,240,245)
  note right of Vesting: Admin may revoke active schedule
  Admin->>Vesting: revokeVesting(vestingId)
  Vesting-->>Admin: Revoked(id, recipient, unreleased, totalVested)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The PR description contains only the unfilled repository template and therefore does not explain what the change solves, does not reference any issue (Fixes #), provides no summary of key changes or tests, and leaves the checklist unchecked, which is insufficient for reviewers to assess intent, scope, or risk. Please complete the template with a brief description of the problem and motivation, include any 'Fixes #' issue references, summarize the key changes and tests performed, mark the checklist items (including test results and correct target branch), and add any deployment or breaking-change notes necessary for reviewers.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title concisely and accurately highlights the primary additions — vesting and team-payment modules — which match the changeset (VestingPayments, EpochPayments, PaymentBase, interfaces, tests, and deployment scripts); it is short, clear, and focused so a reviewer scanning history will understand the main change.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch alt-vesting

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

♻️ Duplicate comments (19)
protocol/contracts/interfaces/admin/IPaymentBase.sol (1)

13-17: Generalize “TGLD”-specific natspec to cover any payment token.

The interface is reused across multiple payment flows and tokens. The docs “owner of the TGLD funds” and “TGLD address” are misleading; please generalize to “payment token” to avoid confusion. This was raised previously on the base file as well.

Apply:

-    /// @notice The owner of the TGLD funds
+    /// @notice The owner of the payment token funds
...
-    /// @notice TGLD address
+    /// @notice Payment token address
protocol/contracts/admin/PaymentBase.sol (3)

13-16: Generalize natspec: not TGLD-specific.

-    /// @notice The owner of the TGLD funds
+    /// @notice The owner of the payment token funds

22-27: Critical: Mutators are public and unguarded in the base. Convert to internal helpers to remove a footgun.

As written, setFundsOwner, recoverToken, and setPaymentToken are publicly callable on any inheritor that forgets to override with access control, leading to immediate fund theft or configuration corruption. Prior feedback suggested making these internal helpers. Adopt that pattern so derived contracts implement the external functions with their own modifiers.

Proposed minimal refactor (pattern):

-import { IPaymentBase } from "contracts/interfaces/admin/IPaymentBase.sol";
+// import removed; derived contracts will implement the interface

-abstract contract PaymentBase is IPaymentBase {
+abstract contract PaymentBase {
@@
-    function setFundsOwner(address _fundsOwner) public virtual {
+    function _setFundsOwner(address _fundsOwner) internal {
         if (_fundsOwner == address(0)) { revert CommonEventsAndErrors.InvalidAddress(); }
         /// @dev Elevated access should revoke approval from old `fundsOwner` for this contract
         fundsOwner = _fundsOwner;
         emit FundsOwnerSet(_fundsOwner);
     }
@@
-    function recoverToken(address _token, address _to, uint256 _amount) public virtual {
+    function _recoverToken(address _token, address _to, uint256 _amount) internal {
+        if (_to == address(0)) { revert CommonEventsAndErrors.InvalidAddress(); }
         emit CommonEventsAndErrors.TokenRecovered(_to, _token, _amount);
         IERC20(_token).safeTransfer(_to, _amount);
     }
@@
-    function setPaymentToken(address _token) public virtual {
+    function _setPaymentToken(address _token) internal {
         if (_token == address(0)) { revert CommonEventsAndErrors.InvalidAddress(); }
         paymentToken = IERC20(_token);
         emit PaymentTokenSet(_token);
     }

Then, in each derived contract (e.g., TeamPayments/EpochPayments/TempleGoldVesting), implement the IPaymentBase surface:

function setFundsOwner(address _fundsOwner) external override onlyElevatedAccess { _setFundsOwner(_fundsOwner); }
function recoverToken(address _token, address _to, uint256 _amount) external override onlyElevatedAccess { _recoverToken(_token, _to, _amount); }
function setPaymentToken(address _token) external override onlyElevatedAccess { _setPaymentToken(_token); }

If you prefer to keep PaymentBase implementing IPaymentBase, at minimum add an abstract modifier (or import the access base) so these functions are protected in the base. I can draft that variant if you prefer.

Also applies to: 35-38, 44-48


44-48: Changing paymentToken mid-flight is dangerous; constrain or make immutable.

Given Payment struct does not encode a token, switching paymentToken while allocations exist can silently denominate future claims in a different token. Either:

  • Make paymentToken immutable via constructor and drop setter; or
  • Require totalAllocation == 0 (and no active epochs/vestings) before permitting a change.

If keeping a setter in derived contracts, enforce an invariant like:

if (totalAllocation() != 0) revert CommonEventsAndErrors.InvalidState();
protocol/contracts/interfaces/admin/ITeamPayments.sol (2)

7-13: Consider uint40 for timestamps.

As previously suggested, using uint40 for start/duration provides longer runway and packs efficiently with a bool. Not required, but forward-looking.


6-6: Extend IPaymentBase to reflect actual surface.

TeamPayments relies on fundsOwner and paymentToken from the base. Extending IPaymentBase improves discoverability and consistency.

-interface ITeamPayments {
+import { IPaymentBase } from "contracts/interfaces/admin/IPaymentBase.sol";
+
+interface ITeamPayments is IPaymentBase {
protocol/contracts/interfaces/templegold/ITempleGoldVesting.sol (3)

5-5: Remove unused import.

IERC20 is imported but not used in the interface.

Apply this diff:

-import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

15-15: Clean up commented-out event.

FundsOwnerSet is commented out. Delete if not part of the interface to prevent drift.

Apply this diff:

-    // event FundsOwnerSet(address indexed _fundsOwner);

21-27: Revisit timestamp widths (uint32) vs uint40.

Past feedback suggested using uint40 for timestamps. With uint32, overflow occurs around 2106. Aligning with IVestingPayments (uint40) would future-proof and reduce divergence.

If you want, I can produce a scoped migration plan (interfaces + impl + tests) in a follow-up.

protocol/contracts/admin/TeamPayments.sol (1)

244-250: Prevent double-allocation and totalAllocation drift for epoch payments.

Currently, setEpochPayments can overwrite an existing recipient’s amount for the same epoch, inflating totalAllocation without reverting.

Apply this diff to add guards consistent with EpochPayments:

     function _setEpochAllocation(uint256 _epoch, address _recipient, uint256 _amount) private {
         if (_amount == 0) { revert CommonEventsAndErrors.ExpectedNonZero(); }
         if (_recipient == address(0)) { revert CommonEventsAndErrors.InvalidAddress();}
-        epochPayments[_recipient][_epoch] = _amount;
+        if (claimedEpochs[_recipient][_epoch]) { revert AlreadyClaimed(); }
+        if (epochPayments[_recipient][_epoch] != 0) revert EpochAlreadySet();
+        epochPayments[_recipient][_epoch] = _amount;
         totalAllocation += _amount;
         emit EpochAllocationSet(_recipient, _epoch, _amount);
     }
protocol/contracts/templegold/TempleGoldVesting.sol (8)

61-64: Avoid copying each schedule: use calldata reference inside the loop

Indexing a VestingSchedule[] calldata allows binding a per-iteration calldata reference without copying to memory.

Apply:

-        VestingSchedule memory _schedule;
+        VestingSchedule calldata _schedule;
         for (uint256 i; i < _length; ++i) {
             _schedule = _schedules[i];

68-73: Sanity checks enforce a non-zero cliff and future start — confirm product requirements

  • cliff <= start disallows a 0-second cliff; start < block.timestamp disallows “start now”. Both are fine if intentional, but they do constrain configurability.

Do we explicitly want to forbid immediate start and zero cliff schedules? If not, consider relaxing to cliff < startcliff < start only if you must, or change to cliff < start → allow cliff == start. Likewise consider allowing start <= block.timestamp. I can draft the exact diff once confirmed.


94-101: Revocation auto-releases vested amount — consider parity with VestingPayments

Here you force-release vested funds on revoke. In VestingPayments, this path is intentionally disabled to avoid potential taxable events for recipients. Divergent behavior across sibling contracts is easy to miss by integrators.

Do we want TempleGoldVesting to mirror VestingPayments and not force-release on revoke? If yes, remove the auto-release block. I can provide a parity patch.


25-31: Minor nits: typos and formatting

  • “Schdules” → “Schedules”.
  • The doc on Line 29 has an extra indent.
-    /// @notice Schdules for recipients
+    /// @notice Schedules for recipients
-      /// @notice Recipient vesting counts for generating IDs. An account can have multiple vesting schedules
+    /// @notice Recipient vesting counts for generating IDs. An account can have multiple vesting schedules

147-153: Index getter returns zero value for empty set — prefer explicit revert and OOB guard

Returning a zero bytes32 on empty set is ambiguous. VestingPayments reverts with NoVesting(). Also, callers get a generic OZ revert on out-of-bounds.

Recommended:

-        /// @dev revert with an array out-of-bounds access if only `id = _vestingIds.at(_index);` is used
-        if (_vestingIds.length() == 0) { return id; }
-        id = _vestingIds.at(_index);
+        uint256 len = _vestingIds.length();
+        if (len == 0) { revert NoVesting(); }
+        if (_index >= len) { revert CommonEventsAndErrors.InvalidParam(); }
+        id = _vestingIds.at(_index);

If NoVesting() isn’t in this interface, swap to a repository-standard error.


191-194: isVestingRevoked reports true for unknown IDs — change to precise semantics

Returning true for a non-existent ID conflates “never existed/was removed” with “revoked”. This can mislead clients and on-chain integrations.

Apply:

-        if(!vestingIdExists(_vestingId)) { return true; }
-        return schedules[_vestingId].revoked;
+        if (!vestingIdExists(_vestingId)) { return false; }
+        return schedules[_vestingId].revoked;

210-220: Underflow on empty holder history in getLastVestingScheduleForHolder

holdersVestingCount[_recipient] - 1 will underflow and revert when the count is zero. VestingPayments already guards this.

Apply:

-    function getLastVestingScheduleForHolder(
-        address _recipient
-    ) external view override returns (VestingSchedule memory) {
-        return
-            schedules[
-                computeVestingScheduleIdForAddressAndIndex(
-                    _recipient,
-                    holdersVestingCount[_recipient] - 1
-                )
-            ];
-    }
+    function getLastVestingScheduleForHolder(
+        address _recipient
+    ) external view override returns (VestingSchedule memory schedule) {
+        uint256 count = holdersVestingCount[_recipient];
+        if (count == 0) { return schedule; }
+        return schedules[computeVestingScheduleIdForAddressAndIndex(_recipient, count - 1)];
+    }

309-318: Critical: releasable math can exceed total amount after end — cap elapsed

Here you use (block.timestamp - start) / duration directly, which can exceed 1 when past the duration, enabling over-release. Use the same capped elapsed path as other functions.

Apply:

-        if ((block.timestamp <= _schedule.cliff) || _schedule.revoked) {
-            return 0;
-        } else {
-            uint256 _vested = TempleMath.mulDivRound(_schedule.amount, block.timestamp - _schedule.start, _schedule.duration, false);
-            return _vested - _schedule.distributed;
-        }
+        if ((block.timestamp <= _schedule.cliff) || _schedule.revoked) {
+            return 0;
+        }
+        uint256 _vested = _calculateTotalVestedAt(_schedule, uint32(block.timestamp));
+        return _vested - _schedule.distributed;

Please add/keep a unit test that warps far past start + duration and asserts the release amount is capped.

protocol/contracts/admin/VestingPayments.sol (1)

71-76: Same configurability question as TGLD: future-only start and non-zero cliff

You require start strictly in the future and cliff > start. Confirm this reflects product policy; otherwise we can allow immediate start and 0-second cliff.

🧹 Nitpick comments (34)
protocol/contracts/interfaces/admin/IPaymentBase.sol (2)

8-9: Index token in PaymentTokenSet for easier off-chain filtering.

Consider indexing the token address to simplify event queries and subgraph filters.

-    event PaymentTokenSet(address token);
+    event PaymentTokenSet(address indexed token);

31-37: Clarify expected access control in docs for mutators.

These functions should be callable only by elevated roles (executor/admin). Please add natspec noting that implementations must enforce access control (e.g., onlyElevatedAccess/onlyExecutor). This helps prevent accidental exposure in future implementers.

Example:

-    function setFundsOwner(address _fundsOwner) external;
+    /// @dev MUST be protected by elevated access in implementers.
+    function setFundsOwner(address _fundsOwner) external;
...
-    function recoverToken(address _token, address _to, uint256 _amount) external;
+    /// @dev MUST be protected by elevated access in implementers.
+    function recoverToken(address _token, address _to, uint256 _amount) external;
...
-    function setPaymentToken(address _token) external;
+    /// @dev MUST be protected by elevated access in implementers.
+    function setPaymentToken(address _token) external;
protocol/contracts/admin/PaymentBase.sol (3)

50-52: Clamp elapsed-time helper to avoid underflow reverts and improve reuse.

If _end < _start this reverts today. Safer to clamp to 0.

-    function _getElapsedTime(uint32 _start, uint32 _end, uint32 _duration) internal pure returns (uint32) {
-        return _end - _start > _duration ? _duration : _end - _start;
-    }
+    function _getElapsedTime(uint32 _start, uint32 _end, uint32 _duration) internal pure returns (uint32) {
+        if (_end <= _start) return 0;
+        uint32 elapsed = _end - _start;
+        return elapsed > _duration ? _duration : elapsed;
+    }

35-38: Nit: validate non-zero recipient on token recovery.

Avoid accidental burns: require _to != address(0). See diff above.


18-27: Doc nit: the “revoke approval from old fundsOwner” note is not actionable by the contract.

The contract cannot revoke allowances on behalf of the old owner. Reword to indicate an operational runbook step or remove to prevent confusion.

Suggested wording: “Operational note: the previous fundsOwner should revoke token approvals granted to this contract.”

protocol/contracts/interfaces/admin/IEpochPayments.sol (3)

8-21: Remove commented-out legacy types for a clean interface.

The commented Payment struct and events add noise and risk confusion. Please remove or move to design notes.

-    // /// @notice Struct for team payment information
-    // struct Payment {
-    //     /// @notice Start time for payment
-    //     uint40 start;
-    //     /// @notice Duration of payment
-    //     uint40 duration;
-    //     /// @notice Is payment valid or cancel
-    //     bool isCancelled;
-    //     /// @notice Amount claimed out of the total amount
-    //     uint128 claimed;
-    //     /// @notice Total payment amount
-    //     uint128 amount;
-    // }

22-30: Align events and API surface.

MinimumEpochDurationSet and NextEpochSet are emitted but there are no corresponding setters in this interface. If the intention is that implementations manage epoch progression internally, consider either:

  • Adding the setters to this interface; or
  • Dropping the events here and exposing them in a more appropriate admin interface.

31-37: Check unused/undocumented errors.

Errors like CannotStartEpoch/EpochAlreadySet are defined here but not referenced by any function in this interface. Confirm they are used by the implementing contract’s public API; otherwise, prune to reduce surface area.

protocol/test/forge/unit/admin/TeamPayments.t.sol (8)

42-43: Fix test contract name typo.

Typo in contract name: TeampPaymentsAccessTest → TeamPaymentsAccessTest. Consistency improves readability and avoids future copy/paste mistakes.

-contract TeampPaymentsAccessTest is TeamPaymentsTestBase {
+contract TeamPaymentsAccessTest is TeamPaymentsTestBase {

55-61: Fix test name typo.

Typo: test_acceess_setPayments → test_access_setPayments.

-    function test_acceess_setPayments() public {
+    function test_access_setPayments() public {

315-317: Fix minor typos in comments (“smae”, “urnreleased”).

Nit-level cleanup for clarity.

-        // smae time 0 releasable
+        // same time, 0 releasable
@@
-        // smae time 0 releasable
+        // same time, 0 releasable
@@
-        // 0 urnreleased
+        // 0 unreleased

Also applies to: 371-373, 279-281


464-475: Add negative test: unauthorized recoverToken must revert.

We prove positive path only. Add a test to ensure only elevated roles can recover tokens.

Example:

+    function test_access_recover_token_team_payments() public {
+        uint256 amount = 1 ether;
+        deal(address(fakeToken), address(payment), amount, true);
+        vm.startPrank(unauthorizedUser);
+        vm.expectRevert(abi.encodeWithSelector(CommonEventsAndErrors.InvalidAccess.selector));
+        payment.recoverToken(address(fakeToken), alice, amount);
+    }

102-111: Consider asserting invariants around paymentToken changes.

Given the risks of switching tokens mid-flight, consider adding tests that:

  • Prohibit setPaymentToken when totalAllocation > 0; or
  • Assert no state/accounting regressions if you intend to allow it.

I can draft the test after you decide the policy.


477-483: Remove unused local variable.

start is never used. Drop it to clear warnings.

-        uint256 start = block.timestamp;
-        
-        ITeamPayments.Payment memory _payment =_createPayment();
+        ITeamPayments.Payment memory _payment = _createPayment();

503-507: Remove unused local variable.

period is not used in this test.

-        uint256 period = 56 weeks;
-
         uint256 amount = 1_000 ether;

531-542: DRY: local time helpers duplicate PaymentBase logic.

Not harmful, but you can inline the same clamp logic as proposed in PaymentBase to keep calculations consistent across code and tests.

protocol/contracts/interfaces/admin/ITeamPayments.sol (2)

28-36: Clarify ‘Payment’ amount semantics in docs.

Explicitly state that amount is the total amount linearly vested over duration, denominated in the payment token’s smallest unit.

-     * @param _payments Amounts array to set
+     * @param _payments Array of payment schedules. Each schedule's `amount` is the TOTAL to be released linearly over `duration` (in token units).

88-95: Fix typo “provieed” and prefer uint32 for ‘at’ to match struct width (optional).

  • Typo fix.
  • Since start/duration are uint32, consider uint32 for at. If you expect far-future timestamps or external callers passing uint256, keeping uint256 is fine.
-     * @notice Get claimable payment at a provieed time
+     * @notice Get claimable payment at a provided time
-    function getClaimablePaymentAt(address recipient, uint256 at) external view returns (uint256);
+    function getClaimablePaymentAt(address recipient, uint256 at) external view returns (uint256);
protocol/test/forge/unit/templegold/TempleGoldVesting.t.sol (3)

390-391: Remove duplicate assertion.

holdersVestingCount(alice) is asserted twice consecutively.

Apply this diff:

-        assertEq(vesting.holdersVestingCount(alice), 0);
+        // already asserted above

448-456: Strengthen event assertions by scoping the emitter.

When expecting Released, consider specifying the emitting contract to avoid false positives if multiple contracts emit similarly named events.

Example:

vm.expectEmit(address(vesting));
emit Released(_id, alice, releasable);

16-25: Add coverage for RecipientChanged flow.

RecipientChanged is declared but not exercised. Add a test that changes a recipient mid-vesting and verifies ownership transfer for subsequent release.

I can draft a test that: creates a schedule, warps past cliff, changes recipient to a new EOA, asserts only the new recipient can release, and validates event fields. Want me to add it?

protocol/contracts/interfaces/admin/IVestingPayments.sol (4)

19-34: Token-agnostic interface should not reference TGLD in comments.

This interface is generic, but comments mention TGLD specifically. This can mislead downstream users.

Apply this diff to generalize:

-        /// @notice Amount of TGLD for whole vesting
+        /// @notice Amount of payment token for whole vesting
...
-        /// @notice Amount of TGLD distributed to recipient
+        /// @notice Amount of payment token distributed to recipient

36-45: Remove commented-out API surface or implement it.

VestingSummary.vestedAtEnd is commented out. Either include it and wire it through getVestingSummary, or remove to keep the interface clean.

Apply this diff to remove the dead code:

-        // /// @notice total vested at end time of summary
-        // uint128 vestedAtEnd;

1-11: Consider standardizing timestamp widths (uint40) across vesting interfaces.

This interface uses uint40 for time fields, while ITempleGoldVesting uses uint32. The inconsistency increases cognitive load and complicates shared tooling. Using uint40 everywhere avoids the 2106 problem.

If aligning is feasible, migrate TempleGold vesting fields to uint40 in a follow-up PR to avoid breaking this one.


141-148: Name alignment with TempleGold interface.

Function isActiveVestingId() mirrors vestingIdExists() in ITempleGoldVesting. Consider unifying naming to reduce confusion.

If you prefer the “exists” naming, rename here and in the implementation, adding a temporary alias for backwards compatibility if needed.

protocol/contracts/admin/EpochPayments.sol (2)

86-106: Optional: Guard against zero minEpochDuration or document behavior.

If minEpochDuration is zero (default), admins can advance epochs every call. Either require a non-zero value to be set before first call or document that zero means “no gating.”

Minimal guard:

     function setEpochPayments(
         address[] calldata recipients,
         uint256[] calldata amounts
     ) external override onlyElevatedAccess {
+        if (minEpochDuration == 0 && currentEpoch != 0) { revert CommonEventsAndErrors.InvalidParam(); }
         // attempt to start next epoch
         uint256 epoch = _startNextEpoch();

14-15: Remove unused import.

TempleMath is not used in this contract.

Apply this diff:

-import { TempleMath } from "contracts/common/TempleMath.sol";
protocol/contracts/admin/TeamPayments.sol (2)

238-242: Use a more appropriate revert for “existing payment already set.”

Reverting with InvalidAddress() is misleading for the “already has a payment” case.

Apply this diff:

-        if (payments[_recipient].amount != 0) { revert CommonEventsAndErrors.InvalidAddress(); }
+        if (payments[_recipient].amount != 0) { revert CommonEventsAndErrors.InvalidParam(); }

71-73: Prefer explicit uint256 loop counters.

Use uint256 for loop indices for consistency with Solidity style and to avoid implicit aliasing of uint.

Apply this diff:

-        for (uint i; i < _length; ++i) {
+        for (uint256 i; i < _length; ++i) {

Also applies to: 90-92

protocol/contracts/templegold/TempleGoldVesting.sol (1)

78-81: Duplicate ID check is effectively redundant

keccak256(abi.encodePacked(holder, index)) with a monotonically increasing per-holder index cannot collide for the same holder. The EnumerableSet.add guard is fine, but the comment “very low possibility of duplicate” is misleading.

-            /// @dev very low possibility of duplicate vesting IDs generated but still checking. 
+            /// @dev IDs are deterministic per (holder, index); add() is a defensive guard.
protocol/contracts/admin/VestingPayments.sol (4)

145-152: Strengthen index validation and improve UX

You already revert on zero length with NoVesting(). Add an explicit out-of-bounds guard to avoid opaque OZ EnumerableSet errors.

-        /// @dev revert if zero vesting Ids length
-        if (_activeVestingIds.length() == 0) { revert NoVesting(); }
-        /// @dev revert with an array out-of-bounds access if only `id = _activeVestingIds.at(_index);` is used
-        id = _activeVestingIds.at(_index);
+        uint256 len = _activeVestingIds.length();
+        if (len == 0) { revert NoVesting(); }
+        if (_index >= len) { revert CommonEventsAndErrors.InvalidParam(); }
+        id = _activeVestingIds.at(_index);

65-67: Micro-gas: avoid redundant i++ checks inside for-loop

Optional: switch to manual increment with unchecked block to save gas in the creation loop. Clear, measurable when creating many schedules.

-        for (uint256 i; i < _length; ++i) {
+        for (uint256 i; i < _length;) {
             _schedule = _schedules[i];
             ...
+            unchecked { ++i; }
         }

29-31: Minor typo

“Schdules” → “Schedules”.

-    /// @notice Schdules for recipients
+    /// @notice Schedules for recipients

308-315: Type consistency: _releaseTime width differs across sibling contracts

Here _releaseTime is uint40, while TempleGoldVesting uses uint32 in analogous code. Not a bug, but consider unifying to avoid casts and keep interfaces symmetrical.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4a86827 and ec18d2e.

📒 Files selected for processing (12)
  • protocol/contracts/admin/EpochPayments.sol (1 hunks)
  • protocol/contracts/admin/PaymentBase.sol (1 hunks)
  • protocol/contracts/admin/TeamPayments.sol (1 hunks)
  • protocol/contracts/admin/VestingPayments.sol (1 hunks)
  • protocol/contracts/interfaces/admin/IEpochPayments.sol (1 hunks)
  • protocol/contracts/interfaces/admin/IPaymentBase.sol (1 hunks)
  • protocol/contracts/interfaces/admin/ITeamPayments.sol (1 hunks)
  • protocol/contracts/interfaces/admin/IVestingPayments.sol (1 hunks)
  • protocol/contracts/interfaces/templegold/ITempleGoldVesting.sol (1 hunks)
  • protocol/contracts/templegold/TempleGoldVesting.sol (1 hunks)
  • protocol/test/forge/unit/admin/TeamPayments.t.sol (1 hunks)
  • protocol/test/forge/unit/templegold/TempleGoldVesting.t.sol (1 hunks)
🔇 Additional comments (8)
protocol/contracts/admin/EpochPayments.sol (1)

156-171: Good: revokeEpochPayment keeps the epoch reusable.

Not marking claimed on revoke allows admins to re-set the allocation later. This addresses earlier feedback about reusability.

protocol/contracts/admin/TeamPayments.sol (1)

171-184: LGTM: Claim logic validates and updates state correctly.

Claim path checks cancellation, existence, and releasable amount, updates claimed and totalClaimed, then transfers via SafeERC20. Event fields look correct.

protocol/contracts/templegold/TempleGoldVesting.sol (4)

125-127: Override of setPaymentToken is pure-revert — verify base signature and intent

Overriding with pure and an unconditional revert is unusual. Ensure this exactly matches the base signature in PaymentBase (visibility, mutability, and custom errors). If the goal is to forbid token changes, consider overriding with external onlyElevatedAccess that reverts with a domain-specific error.

Would you like me to generate a small patch to align the signature and emit a clearer custom error?


159-165: Release flow updates state before external call — good ordering

You increment distributed and decrement totalVestedAndUnclaimed before safeTransferFrom, which provides reentrancy resistance without a guard. The recipient check in _validateRelease blocks arbitrary reentry via token hooks.


34-42: Constructor bypasses PaymentBase constructor — align initialization paths

VestingPayments invokes PaymentBase(_paymentToken), while here you set paymentToken and fundsOwner directly. If PaymentBase emits events or enforces invariants in its constructor, those are skipped here.

If PaymentBase has a token-accepting constructor, prefer:

-    ) TempleElevatedAccess(_rescuer, _executor){
-        fundsOwner = _fundsOwner;
-        paymentToken = IERC20(_paymentToken);
-    }
+    ) TempleElevatedAccess(_rescuer, _executor) PaymentBase(_paymentToken) {
+        _setFundsOwner(_fundsOwner);
+    }

Confirm exact base API names; I can tailor the patch once you confirm PaymentBase’s constructor and setter signatures.


266-275: Summary window filters only schedules fully inside [_from, _to] — confirm intent

You exclude schedules that overlap the window at either edge. Depending on analytics needs, clients may expect overlapping schedules to be included with partial accruals.

If inclusion of overlaps is desired, I can propose a bounded-interval accrual helper and update the loop accordingly.

protocol/contracts/admin/VestingPayments.sol (2)

241-246: Consistent elapsed-time capping — good

Both the “current time” and _at paths go through _calculateTotalVestedAt which caps elapsed via _getElapsedTime. This avoids over-release after maturity.

Also applies to: 308-315


317-321: Release flow ordering and eventing — solid

State is updated before token transfer; Released event carries the caller. Matches best practices and aligns with the FundsOwner allowance model.

Also applies to: 158-165

Comment thread protocol/contracts/admin/EpochPayments.sol Outdated
Comment thread protocol/contracts/admin/PaymentBase.sol Outdated
Comment thread protocol/contracts/admin/TeamPayments.sol Outdated
Comment thread protocol/contracts/admin/TeamPayments.sol Outdated
Comment thread protocol/contracts/admin/VestingPayments.sol Outdated
Comment thread protocol/contracts/templegold/TempleGoldVesting.sol Outdated
Comment thread protocol/test/forge/unit/templegold/TempleGoldVesting.t.sol Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (5)
protocol/contracts/interfaces/admin/IPaymentBase.sol (3)

8-10: Remove PaymentTokenSet event (paymentToken is now immutable).

With paymentToken immutable and no setter in the interface, PaymentTokenSet is misleading dead weight. Drop it or, if you really want an audit trail, emit it once in the constructor of implementors—right now there's no place to emit it.

Apply this diff:

-    event PaymentTokenSet(address token);

11-11: Drop NotImplemented error from the interface.

There are no interface methods that reference or use NotImplemented(). Keeping it here invites confusion and unused-code drift.

-    error NotImplemented();

19-31: Clarify access-control expectations in NatSpec for mutators.

Interface methods setFundsOwner and recoverToken should be guarded by elevated access in implementations. Encode this expectation in the NatSpec to prevent accidental exposure during implementation.

-     * @notice Set funds owner
+     * @notice Set funds owner
+     * @dev MUST be access controlled (e.g., onlyElevatedAccess) in implementors.
@@
-     * @notice Recover ERC20 token
+     * @notice Recover ERC20 token
+     * @dev MUST be access controlled (e.g., onlyElevatedAccess) in implementors.
protocol/contracts/admin/PaymentBase.sol (2)

27-32: _setFundsOwner internalization is good; consider zero-cost early return on no-op set.

Not critical, but skipping state write and event when the value is unchanged can reduce noise and gas in admin workflows.

     function _setFundsOwner(address _fundsOwner) internal {
         if (_fundsOwner == address(0)) { revert CommonEventsAndErrors.InvalidAddress(); }
         /// @dev Elevated access should revoke approval from old `fundsOwner` for this contract
-        fundsOwner = _fundsOwner;
-        emit FundsOwnerSet(_fundsOwner);
+        if (fundsOwner != _fundsOwner) {
+            fundsOwner = _fundsOwner;
+            emit FundsOwnerSet(_fundsOwner);
+        }
     }

40-43: Harden _recoverToken with basic argument checks.

Optional but defensive: prevent burning to address(0) and meaningless zero-amount transfers; both catch misconfigurations early.

     function _recoverToken(address _token, address _to, uint256 _amount) internal {
+        if (_to == address(0)) { revert CommonEventsAndErrors.InvalidAddress(); }
+        if (_amount == 0) { revert CommonEventsAndErrors.InvalidAmount(); }
         emit CommonEventsAndErrors.TokenRecovered(_to, _token, _amount);
         IERC20(_token).safeTransfer(_to, _amount);
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ec18d2e and 3a7e8a4.

📒 Files selected for processing (6)
  • protocol/contracts/admin/EpochPayments.sol (1 hunks)
  • protocol/contracts/admin/PaymentBase.sol (1 hunks)
  • protocol/contracts/admin/VestingPayments.sol (1 hunks)
  • protocol/contracts/interfaces/admin/IEpochPayments.sol (1 hunks)
  • protocol/contracts/interfaces/admin/IPaymentBase.sol (1 hunks)
  • protocol/contracts/interfaces/admin/IVestingPayments.sol (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • protocol/contracts/interfaces/admin/IEpochPayments.sol
  • protocol/contracts/admin/VestingPayments.sol
  • protocol/contracts/interfaces/admin/IVestingPayments.sol
  • protocol/contracts/admin/EpochPayments.sol
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: princetonbishop
PR: TempleDAO/temple#1169
File: protocol/contracts/admin/PaymentBase.sol:18-48
Timestamp: 2025-08-21T19:18:05.218Z
Learning: In PaymentBase.sol, paymentToken has been changed to be immutable and the setPaymentToken function has been removed from the interface, eliminating the need for access-controlled overrides in inheriting contracts.
📚 Learning: 2025-08-21T19:18:05.218Z
Learnt from: princetonbishop
PR: TempleDAO/temple#1169
File: protocol/contracts/admin/PaymentBase.sol:18-48
Timestamp: 2025-08-21T19:18:05.218Z
Learning: In PaymentBase.sol, paymentToken has been changed to be immutable and the setPaymentToken function has been removed from the interface, eliminating the need for access-controlled overrides in inheriting contracts.

Applied to files:

  • protocol/contracts/interfaces/admin/IPaymentBase.sol
  • protocol/contracts/admin/PaymentBase.sol
🔇 Additional comments (2)
protocol/contracts/admin/PaymentBase.sol (2)

16-21: Nice: paymentToken is immutable and set in the constructor.

This aligns with prior feedback to avoid mutable payment tokens. Good improvement in the trust model.


10-18: All PaymentBase inheritors correctly implement guarded externals and initialize fundsOwner

I’ve verified that each contract inheriting PaymentBase in protocol/contracts

  • Implements setFundsOwner(...) and recoverToken(...) overrides
  • Applies onlyElevatedAccess on those externals
  • Assigns fundsOwner in its constructor

Specifically:

• VestingPayments.sol — setFundsOwner & recoverToken present with onlyElevatedAccess, fundsOwner initialized (line 44)
• EpochPayments.sol — setFundsOwner & recoverToken present with onlyElevatedAccess, fundsOwner initialized (line 51)
• TempleGoldVesting.sol — setFundsOwner & recoverToken present with onlyElevatedAccess, fundsOwner initialized (line 40)

No further changes are needed here.

Comment thread protocol/contracts/admin/PaymentBase.sol Outdated
Comment thread protocol/contracts/admin/PaymentBase.sol Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (17)
protocol/contracts/interfaces/admin/IVestingPayments.sol (17)

1-3: Put SPDX identifier first (before pragma).

Most linters and SPDX tooling expect the license identifier as the very first line. Reordering avoids formatting warnings in CI.

Apply:

-pragma solidity ^0.8.20;
// SPDX-License-Identifier: AGPL-3.0-or-later
+// SPDX-License-Identifier: AGPL-3.0-or-later
+pragma solidity ^0.8.20;

8-15: Index both old and new recipients in RecipientChanged.

Indexing the previous recipient aids log filtering and off-chain indexing without adding runtime cost.

-event RecipientChanged(bytes32 _vestingId, address _oldRecipient, address indexed _recipient);
+event RecipientChanged(bytes32 _vestingId, address indexed _oldRecipient, address indexed _recipient);

16-20: Disambiguate/align error taxonomy with CommonEventsAndErrors.

Given the codebase uses CommonEventsAndErrors elsewhere, consider aligning error names or documenting throw conditions. E.g., CannotRelease is ambiguous (nothing vested vs. not recipient vs. revoked). Either clarify via comments or split into more specific errors (NothingToRelease, OnlyRecipient, VestingRevoked). Keeps client integrations predictable.

If you prefer not to add new errors, please add NatSpec “@dev throws when …” notes at call sites in the implementation for consistency with existing error docs.


21-36: Make interface docs token-agnostic; minor storage packing nit.

  • The struct comments reference “TGLD”. Since this is the generic IVestingPayments, use token-agnostic phrasing to avoid coupling to a specific asset.
  • Optional micro-optimization: place bool revoked adjacent to uint128 distributed so they share a slot (both fit with 128+8), instead of cohabiting with address, but note you’ll still consume 3 slots either way. Not critical.

Doc-only tweak:

-        /// @notice Amount of TGLD for whole vesting
+        /// @notice Total token amount allocated for this vesting schedule
         uint128 amount;
-        /// @notice Amount of TGLD distributed to recipient
+        /// @notice Amount already released to the recipient
         uint128 distributed;
-        /// @notice Recipient of vesting
+        /// @notice Vesting recipient (beneficiary)
         address recipient;

If you want to adopt the packing nit:

-        /// @notice Recipient of vesting
-        address recipient;
-        /// @notice If vesting is revoked
-        bool revoked;
+        /// @notice If vesting is revoked
+        bool revoked;
+        /// @notice Recipient of vesting
+        address recipient;

38-47: Tighten VestingSummary docs and consider type consistency.

  • Doc phrasing: “distributed tgld” → generic, capitalized.
  • Consider whether vested should be uint256 to align with getters returning uint256 (e.g., getTotalVestedAt). Today it’s safe (per-schedule cap = amount uint128), but the mixed types force casts in clients.

Doc-only tweak:

-        /// @notice distributed tgld
+        /// @notice Amount already released to the recipient
         uint128 distributed;
-        /// @notice total vested at current block.timestamp
+        /// @notice Total vested at the current block timestamp (may exceed distributed)
         uint128 vested;

Optional API consistency tweak:

-        uint128 vested;
+        uint256 vested;

Note: this is a breaking change for downstream users; only do it if you want strict consistency with the query functions.


55-57: Naming consistency: “holder” vs “recipient”.

The interface alternates between “holder” and “recipient” for the same concept. Pick one term across functions and parameter names to reduce cognitive overhead (e.g., “recipient” everywhere).

If you keep both terms, please add a brief comment at the top explaining they are synonymous in this context.


83-86: Parameter name mismatches interface wording.

Function name uses “…ForHolder” but the parameter is _recipient. Align for clarity.

-    function computeNextVestingScheduleIdForHolder(
-        address _recipient
+    function computeNextVestingScheduleIdForHolder(
+        address holder
     ) external view returns (bytes32);

94-97: Document zero-based indexing and bounds.

Given the swap-and-pop note, clarify whether _index is zero-based and that it must be < current length to avoid confusion for integrators.


98-103: Release authorization semantics: doc the expected caller.

NatSpec says “Caller must be recipient.” If admins can also force-release (common in vesting systems), either add an admin release function or explicitly state that only the beneficiary can call this.

Would you like me to draft an adminRelease(bytes32 _vestingId, address to) variant for emergency recoveries?


104-111: Consider exposing the ID computation salt/algorithm.

computeVestingScheduleIdForAddressAndIndex is pure, which is good. Ensure the implementation uses the same formula on-chain (e.g., keccak256(holder, index, domainSeparator/version)). If not, add a doc line describing the exact encoding to prevent mismatches.


117-120: “Last vesting schedule” definition can be ambiguous.

Is “last” the highest index, most recently created, or last active (non-revoked)? Please clarify in NatSpec and, if necessary, rename to getLatestVestingScheduleForHolder.


145-147: Use calldata for external array parameter.

Saves a memory copy for external calls.

-    function getVestingSummary(
-        bytes32[] memory _ids
-    ) external view returns (VestingSummary[] memory summary);
+    function getVestingSummary(
+        bytes32[] calldata _ids
+    ) external view returns (VestingSummary[] memory summary);

149-154: Consider adding a global count getter.

You expose getVestingIds() (which can be heavy) and holdersVestingCount(holder). A lightweight getVestingCount() can help indexers/UIs without pulling the whole set.

If helpful, I can propose a minimal EnumerableSet-backed signature for this.


160-168: Releasable amount vs. revoked flows.

You surface revokedAccountsReleasable(account) but only a per-ID release. If the intended UX is “accumulate revoked releasables and allow a single claim,” consider adding releaseRevoked() or releaseAll(account) for gas efficiency and simpler UX. Otherwise, document how a user claims the revokedAccountsReleasable amount.

I can draft an interface addition if that matches product requirements.


169-176: Type alignment between getters and summary.

Getters return uint256 while the summary uses uint128. Safe today (per-schedule cap = amount), but client code will cast. Consider returning uint256 in VestingSummary or switch getters to uint128. Pick one style and stick with it.


132-139: Rename getTotalVestingAtCurrentTime for naming consistency

To avoid confusion between “vesting” and “vested,” please rename the function across the interface and all implementations:

• In protocol/contracts/interfaces/admin/IVestingPayments.sol (around line 138), change:

- function getTotalVestingAtCurrentTime(bytes32 _vestingId) external view returns (uint256);
+ function getTotalVestedAtCurrentTime(bytes32 _vestingId) external view returns (uint256);

• In protocol/contracts/admin/VestingPayments.sol (around line 204), update the override and its callers:

- function getTotalVestingAtCurrentTime(bytes32 _vestingId) external view override returns (uint256) {
+ function getTotalVestedAtCurrentTime(bytes32 _vestingId) external view override returns (uint256) {
      VestingSchedule memory _schedule = schedules[_vestingId];
-     return _getTotalVestingAtCurrentTime(_schedule);
+     return _getTotalVestingAtCurrentTime(_schedule);
  }

• In protocol/contracts/templegold/TempleGoldVesting.sol (around line 240), update the override signature:

- function getTotalVestingAtCurrentTime(bytes32 _vestingId) external view override returns (uint256) {
+ function getTotalVestedAtCurrentTime(bytes32 _vestingId) external view override returns (uint256) {

• In protocol/contracts/interfaces/templegold/ITempleGoldVesting.sol (around line 126), update the interface signature similarly.

• Update any external callers (tests, scripts, other contracts) that reference getTotalVestingAtCurrentTime.

This renaming is purely cosmetic and backward-breaking; ensure you coordinate updates across all dependents (including auto-generated artifacts like slither.db.json).


1-183: Standardize Solidity version and pin compiler for determinism

The repository currently relies on auto_detect_solc = true in protocol/foundry.toml, allowing Forge to pick any compatible 0.8.x compiler that satisfies each file’s pragma. However, our grep across contracts/ and test/ surfaces a mix of pragmas—^0.8.0, ^0.8.4, ^0.8.18, ^0.8.20, and ^0.8.22. While this works under auto-detection, it can lead to subtle discrepancies between environments and hinders reproducible builds.

I recommend:

  • Choose a single baseline pragma (for example, upgrade all files to ^0.8.20, or bump to ^0.8.22/^0.8.24 to leverage the latest patches).
  • Update every contract, interface, mock, and test to use that chosen pragma version.
  • In protocol/foundry.toml, replace auto_detect_solc = true with an explicit solc_version = "0.8.xx" to lock in the compiler.
  • Run forge fmt and re-run the full test suite to verify consistency under the pinned compiler.

This will ensure all contributors compile against the exact same Solidity version and avoid any “works on my machine” scenarios.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4734aff and b9aa046.

📒 Files selected for processing (2)
  • protocol/contracts/admin/VestingPayments.sol (1 hunks)
  • protocol/contracts/interfaces/admin/IVestingPayments.sol (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • protocol/contracts/admin/VestingPayments.sol
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: princetonbishop
PR: TempleDAO/temple#1169
File: protocol/contracts/admin/PaymentBase.sol:18-48
Timestamp: 2025-08-21T19:18:05.218Z
Learning: In PaymentBase.sol, paymentToken has been changed to be immutable and the setPaymentToken function has been removed from the interface, eliminating the need for access-controlled overrides in inheriting contracts.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (3)
protocol/test/forge/unit/admin/VestingPayments.t.sol (3)

685-686: Remove duplicate assertion in test_computeNextVestingScheduleIdForHolder.

Line 686 is a duplicate of line 685, providing no additional test coverage.

Apply this diff to remove the duplicate:

     assertEq(vesting.holdersVestingCount(alice), 0);
-    assertEq(vesting.holdersVestingCount(alice), 0);

73-83: Consider adding more initialization validation tests.

The initialization tests could be more comprehensive by validating edge cases like invalid addresses for rescuer/executor and testing the immutability of paymentToken.

Would you like me to generate additional initialization test cases to improve coverage, such as testing invalid constructor parameters for rescuer and executor addresses?


397-927: Consider organizing tests into more focused test contracts.

The VestingPaymentsTest contract is quite large (530+ lines) and tests multiple aspects. Consider splitting it into more focused test contracts like VestingPaymentsCreateTest, VestingPaymentsRevokeTest, and VestingPaymentsReleaseTest for better organization and maintainability.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b9aa046 and 78de3e1.

📒 Files selected for processing (3)
  • protocol/contracts/admin/VestingPayments.sol (1 hunks)
  • protocol/contracts/interfaces/admin/IVestingPayments.sol (1 hunks)
  • protocol/test/forge/unit/admin/VestingPayments.t.sol (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • protocol/contracts/interfaces/admin/IVestingPayments.sol
  • protocol/contracts/admin/VestingPayments.sol
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: princetonbishop
PR: TempleDAO/temple#1169
File: protocol/contracts/admin/PaymentBase.sol:18-48
Timestamp: 2025-08-21T19:18:05.268Z
Learning: In PaymentBase.sol, paymentToken has been changed to be immutable and the setPaymentToken function has been removed from the interface, eliminating the need for access-controlled overrides in inheriting contracts.
🔇 Additional comments (3)
protocol/test/forge/unit/admin/VestingPayments.t.sol (3)

16-171: LGTM! Well-structured test base setup.

The test base contract is well-organized with proper initialization of dependencies, event definitions, and helper functions. The setup correctly configures TempleGold with distribution parameters and contract whitelisting.


173-222: LGTM! Comprehensive access control testing.

The access control tests properly validate both failure and success cases for all elevated access functions.


224-395: LGTM! Thorough view function testing.

The view function tests comprehensively cover edge cases including zero vesting counts, pre-cliff periods, post-cliff calculations, and revoked schedules.

Comment thread protocol/test/forge/unit/admin/VestingPayments.t.sol Outdated
Comment thread protocol/test/forge/unit/admin/VestingPayments.t.sol Outdated
Comment thread protocol/test/forge/unit/admin/VestingPayments.t.sol Outdated
Comment thread protocol/test/forge/unit/admin/VestingPayments.t.sol Outdated
Comment thread protocol/test/forge/unit/admin/VestingPayments.t.sol Outdated
Comment thread protocol/test/forge/unit/admin/VestingPayments.t.sol Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (6)
protocol/contracts/admin/EpochPayments.sol (1)

153-162: Epoch start gate off-by-one; equality should pass.

Revert only when now < start + minDuration. Current ‘>=’ blocks at the boundary.

-        if (currentEpochCache != 0 && startTime + minEpochDuration >= block.timestamp) { revert CannotStartEpoch(currentEpochCache+1); }
+        if (currentEpochCache != 0 && block.timestamp < startTime + minEpochDuration) {
+            revert CannotStartEpoch(currentEpochCache + 1);
+        }
protocol/test/forge/unit/admin/VestingPayments.t.sol (5)

794-811: Remove dead/unreachable block in test_release_no_amount.

The first block’s values are overwritten immediately.

-        // revoke and release
-        skip(1 weeks);
-        vm.startPrank(executor);
-        balance = templeGold.balanceOf(alice);
-        totalVestedAndUnclaimed = vesting.totalVestedAndUnclaimed();
-        releasable = vesting.getReleasableAmount(_id);
-        
-        skip(3 weeks);
+        // Continue testing release after more time
+        skip(4 weeks);

115-117: Use uint40 for time fields to avoid truncation in _getScheduleTwo.

Current uint32 casts risk silent truncation; keep widths consistent with VestingSchedule (uint40).

-        uint40 start = uint32(block.timestamp) + 1 weeks;
-        uint40 cliff = uint32(start + 16 weeks);
+        uint40 start = uint40(block.timestamp) + 1 weeks;
+        uint40 cliff = uint40(start + 16 weeks);

130-132: Fix time field widths in _getScheduleThree.

These should be uint40 to match the struct and avoid narrowing.

-        uint32 start = uint32(block.timestamp) + 4 weeks;
-        uint32 cliff = uint32(start + 12 weeks);
-        uint32 duration = 56 weeks;
+        uint40 start = uint40(block.timestamp) + 4 weeks;
+        uint40 cliff = uint40(start + 12 weeks);
+        uint40 duration = 56 weeks;

439-444: Correct start-time casts during schedule validation.

Use uint40, not uint32.

-        _schedule.start = uint32(block.timestamp - 1);
+        _schedule.start = uint40(block.timestamp - 1);
         schedules[0] = _schedule;
         vm.expectRevert(abi.encodeWithSelector(CommonEventsAndErrors.InvalidParam.selector));
         vesting.createSchedules(schedules);
-        _schedule.start = uint32(block.timestamp);
+        _schedule.start = uint40(block.timestamp);

845-851: Populate _ids and assert zero releasable before cliff.

The test computes releasable but doesn’t assert it, and leaves _ids[0] unset.

         // alice: skip to below cliff
         skip(_schedule1.cliff-1);
         bytes32[] memory _ids = new bytes32[](2);
-        uint256 releasable = vesting.getReleasableAmount(_id1);
-        IVestingPayments.VestingSummary[] memory summary = vesting.getVestingSummary(_ids);
+        _ids[0] = _id1;
+        uint256 releasable = vesting.getReleasableAmount(_id1);
+        assertEq(releasable, 0, "Should have zero releasable before cliff");
+        IVestingPayments.VestingSummary[] memory summary = vesting.getVestingSummary(_ids);
🧹 Nitpick comments (14)
protocol/contracts/interfaces/admin/IEpochPayments.sol (2)

14-19: Remove or use EpochAlreadySet.

The error is declared but unused in the implementation. Either wire it into the “initial set-only” path or drop it from the interface to avoid API noise.


31-39: Expose setMinimumEpochDuration in the interface.

You emit MinimumEpochDurationSet and the implementation exposes setMinimumEpochDuration, but the interface does not. Add it so integrators can rely on the API surface via IEpochPayments.

Apply this diff to IEpochPayments.sol:

@@
     function updateEpochPayments(
         address[] calldata recipients,
         uint256[] calldata amounts
     ) external;
 
+    /**
+     * @notice Set minimum duration of an epoch
+     * @param duration Minimum duration in seconds
+     */
+    function setMinimumEpochDuration(uint256 duration) external;
+
protocol/contracts/admin/EpochPayments.sol (4)

17-21: Docs mismatch: “Team Payments” vs EpochPayments.

Update the natspec title/notice to reflect this is the epoch-based payments contract.

- * @title Team Payments
- * @notice New team payments contract
+ * @title Epoch Payments
+ * @notice Discrete, admin-managed epoch allocations and claims

129-132: Optional: add reentrancy guard to claimEpoch.

State updates happen before transfer which is good, but adding nonReentrant is a cheap hardening.

Additional changes outside this hunk:

@@
-import { TempleMath } from "contracts/common/TempleMath.sol";
+import { TempleMath } from "contracts/common/TempleMath.sol";
+import { ReentrancyGuard } from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
@@
-contract EpochPayments is IEpochPayments, PaymentBase, TempleElevatedAccess {
+contract EpochPayments is IEpochPayments, PaymentBase, TempleElevatedAccess, ReentrancyGuard {
@@
-    function claimEpoch(uint256 epoch) external override {
+    function claimEpoch(uint256 epoch) external override nonReentrant {

36-44: Nit: clarify comments.

“Epoch payments. variable payments” and “Epochs” are vague. Prefer “Per-recipient per-epoch allocations” and “Epoch start timestamps.”

-    /// @notice Epoch payments. variable payments
+    /// @notice Per-recipient per-epoch allocations
@@
-    /// @notice Epochs
+    /// @notice Epoch start timestamps

73-91: Add duplicate-recipient handling tests for updateEpochPayments.

To ensure robust batch semantics in EpochPayments.sol, expand the test suite with scenarios covering duplicate recipients:

  • Create a new test file (e.g. protocol/test/forge/unit/admin/EpochPayments.t.sol) if one doesn’t already exist.
  • Single-call duplicates
    – Call updateEpochPayments([alice, alice], [amt, amt]) and assert the expected behavior (revert with AlreadyClaimed, sum allocations, or other designed outcome).
  • Successive-update duplicates
    – Call updateEpochPayments([alice], [amt1]), then again with updateEpochPayments([alice], [amt2]) in the same epoch; verify consistency with intended logic (e.g., replacement vs. accumulation vs. revert).
  • Edge cases
    – Mix duplicates and unique addresses in a single batch.
    – Zero-amount entries for duplicated recipients (should revert via ExpectedNonZero in _setEpochAllocation).
protocol/test/forge/unit/admin/VestingPayments.t.sol (8)

675-679: Scope expectEmit to the vesting contract.

Improves correctness and noise-resilience.

-        vm.expectEmit();
+        vm.expectEmit(address(vesting));
         emit CommonEventsAndErrors.TokenRecovered(alice, address(fakeToken), amount);

         vm.startPrank(executor);

685-686: Remove duplicate assertion.

-        assertEq(vesting.holdersVestingCount(alice), 0);
         assertEq(vesting.holdersVestingCount(alice), 0);

335-336: Fix off-by-one in comment to match 29-week skip.

-        // 1 week + (cliff - start)) + 11 weeks
+        // 1 week + (cliff - start) + 12 weeks = 29 weeks total → 28 weeks vested since start
         skip(29 weeks);

188-188: Avoid bytes32(bytes(...)) casts; use clear literals.

Use bytes32(0) and bytes32("...") for clarity and to avoid dynamic bytes casts.

-        vesting.revokeVesting(bytes32(bytes("")));
+        vesting.revokeVesting(bytes32(0));
-        vesting.revokeVesting(bytes32(bytes("")));
+        vesting.revokeVesting(bytes32(0));
-        vesting.release(bytes32(bytes("")));
+        vesting.release(bytes32(0));
 
-        vm.expectRevert(abi.encodeWithSelector(CommonEventsAndErrors.InvalidParam.selector));
-        vesting.release(bytes32(bytes("0x123")));
+        vm.expectRevert(abi.encodeWithSelector(CommonEventsAndErrors.InvalidParam.selector));
+        vesting.release(bytes32("0x123"));

Also applies to: 606-607, 733-737


247-250: Index 0 is a clearer sentinel for empty vesting IDs.

Minor readability improvement.

-        vesting.getVestingIdAtIndex(1);
+        vesting.getVestingIdAtIndex(0);

33-37: Remove unused fork ID variable.

Keeps tests lean; the fork is still created.

-    uint256 public arbitrumOneForkId;
-
     function setUp() public {
-        arbitrumOneForkId = fork("arbitrum_one");
+        fork("arbitrum_one");

281-287: Also assert zero releasable exactly at start.

Matches the test name “less_than_equal_to_start”.

         // go to less than start time
         vm.warp(schedule.start-1);
         assertEq(vesting.getReleasableAmount(ids[0]), 0);
+        // exactly at start
+        vm.warp(schedule.start);
+        assertEq(vesting.getReleasableAmount(ids[0]), 0);

706-708: Assert inactive after revoke.

Strengthens the invariant being tested.

         // revoke immediately
         vesting.revokeVesting(_id);
         assertEq(vesting.isVestingRevoked(_id), true);
+        assertEq(vesting.isActiveVestingId(_id), false);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 78de3e1 and 14384ba.

📒 Files selected for processing (3)
  • protocol/contracts/admin/EpochPayments.sol (1 hunks)
  • protocol/contracts/interfaces/admin/IEpochPayments.sol (1 hunks)
  • protocol/test/forge/unit/admin/VestingPayments.t.sol (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: princetonbishop
PR: TempleDAO/temple#1169
File: protocol/contracts/admin/PaymentBase.sol:18-48
Timestamp: 2025-08-21T19:18:05.268Z
Learning: In PaymentBase.sol, paymentToken has been changed to be immutable and the setPaymentToken function has been removed from the interface, eliminating the need for access-controlled overrides in inheriting contracts.
📚 Learning: 2025-08-21T19:18:05.268Z
Learnt from: princetonbishop
PR: TempleDAO/temple#1169
File: protocol/contracts/admin/PaymentBase.sol:18-48
Timestamp: 2025-08-21T19:18:05.268Z
Learning: In PaymentBase.sol, paymentToken has been changed to be immutable and the setPaymentToken function has been removed from the interface, eliminating the need for access-controlled overrides in inheriting contracts.

Applied to files:

  • protocol/contracts/admin/EpochPayments.sol
🔇 Additional comments (5)
protocol/contracts/interfaces/admin/IEpochPayments.sol (1)

7-13: Interface surface and event set look solid.

Event coverage is appropriate for off-chain indexing and admin ops.

protocol/contracts/admin/EpochPayments.sol (3)

54-57: LGTM: setFundsOwner override defers to PaymentBase.

Consistent with PaymentBase mutability change for paymentToken; no setPaymentToken surface here.


102-117: LGTM: revoke path validates claim status and adjusts accounting.

With the delta fix in _setEpochAllocation, revoke remains accurate.


59-71: LGTM: epoch creation plus initial allocations flow.

Starting next epoch before setting allocations is sane; array length checks are correct.

protocol/test/forge/unit/admin/VestingPayments.t.sol (1)

16-71: Test harness initialization looks solid.

Setup wires contracts, permissions, and whitelisting correctly; good coverage baseline.

Comment thread protocol/contracts/admin/EpochPayments.sol Outdated
Comment thread protocol/contracts/admin/EpochPayments.sol Outdated
Comment thread protocol/contracts/admin/EpochPayments.sol
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

🧹 Nitpick comments (17)
protocol/contracts/interfaces/admin/IVestingPayments.sol (5)

81-84: Param naming mismatch with function name

Function says “ForHolder” but param is _recipient. Align for clarity.

-    function computeNextVestingScheduleIdForHolder(
-        address _recipient
-    ) external view returns (bytes32);
+    function computeNextVestingScheduleIdForHolder(
+        address _holder
+    ) external view returns (bytes32);

47-49: Consistent param naming (use leading underscore throughout)

Unify style to reduce friction in tooling/ABIs generated from NatSpec.

-    function revokedAccountsReleasable(address account) external view returns (uint256);
+    function revokedAccountsReleasable(address _account) external view returns (uint256);

-    function holdersVestingCount(address holder) external view returns (uint256);
+    function holdersVestingCount(address _holder) external view returns (uint256);

-    function computeVestingScheduleIdForAddressAndIndex(
-        address holder,
-        uint256 index
-    ) external pure returns (bytes32);
+    function computeVestingScheduleIdForAddressAndIndex(
+        address _holder,
+        uint256 _index
+    ) external pure returns (bytes32);

-    function getVestingScheduleByAddressAndIndex(
-        address recipient,
-        uint256 index
-    ) external view returns (VestingSchedule memory);
+    function getVestingScheduleByAddressAndIndex(
+        address _recipient,
+        uint256 _index
+    ) external view returns (VestingSchedule memory);

Also applies to: 53-55, 105-109, 120-129


130-137: Name consistency: “Vesting” vs “Vested”

APIs mix “Vesting” and “Vested”. Prefer “Vested” for amounts accrued by time. Consider renaming for clarity. Breaking change if applied now.

-    function getTotalVestingAtCurrentTime(bytes32 _vestingId) external view returns (uint256);
+    // Consider renaming for consistency with getTotalVestedAt(...)
+    function getTotalVestedAtCurrentTime(bytes32 _vestingId) external view returns (uint256);

Also applies to: 167-174


151-152: Consider pagination for vesting ids to avoid heavy returns

Returning all IDs can be large and brittle for integrators. Add a paginated variant while keeping the existing method.

Proposed additional signature (new method):

function getVestingIds(uint256 _offset, uint256 _limit) external view returns (bytes32[] memory ids);

38-45: Optional: widen VestingSummary.vested to uint256

Most getters return uint256 amounts; widening avoids repeated casting for clients. Not required functionally since amount is uint128.

-        uint128 vested;
+        uint256 vested;
protocol/test/forge/unit/admin/EpochPayments.t.sol (5)

48-48: Typo in contract name.

Rename TeampPaymentsAccessTest to TeamPaymentsAccessTest (or EpochPaymentsAccessTest) for clarity.

-contract TeampPaymentsAccessTest is EpochPaymentsTestBase {
+contract TeamPaymentsAccessTest is EpochPaymentsTestBase {

393-404: Strengthen assertions: verify totalAllocation delta on update.

After updating Alice’s epoch-1 allocation from 1e18 to 3e18, totalAllocation should reflect the +2e18 delta (or equal new total if no other allocations exist). Add an assertion.

 function test_updateEpochAllocations_update_allocation() public {
   test_setEpochPayments_first_epoch();
+  uint256 totalBefore = payment.totalAllocation();
   address[] memory recipients = new address[](1);
   uint256[] memory amounts = new uint256[](1);
   amounts[0] = 3e18;
   recipients[0] = alice;
   assertEq(payment.epochPayments(alice, 1), 1e18);
   vm.expectEmit(address(payment));
   emit EpochAllocationSet(alice, 1, 3e18);
   payment.updateEpochPayments(recipients, amounts);
   assertEq(payment.epochPayments(alice, 1), 3e18);
+  assertEq(payment.totalAllocation(), totalBefore - 1e18 + 3e18);
 }

160-161: Whitespace nit.

Extra leading space before assertEq on Line 160; remove for consistency.

-         assertEq(payment.claimedEpochs(mike, 1), true);
+        assertEq(payment.claimedEpochs(mike, 1), true);

512-523: Test name mismatch.

Rename test_recover_token_team_payments to test_recover_token_epoch_payments to match the subject under test.

-function test_recover_token_team_payments() public {
+function test_recover_token_epoch_payments() public {

83-86: Close pranks or prefer vm.prank for single calls.

Several tests start pranks but don’t stop them. While Foundry resets between tests, local clarity improves with vm.stopPrank() or using vm.prank(...) for single-call cases.

Example:

-vm.startPrank(executor);
-payment.setFundsOwner(alice);
+vm.prank(executor);
+payment.setFundsOwner(alice);

Also applies to: 88-95, 97-101, 102-105, 107-111, 113-120

protocol/contracts/interfaces/admin/IEpochPayments.sol (7)

8-13: Index epoch in events for efficient querying.

Add indexed to epoch in events; it’s a primary filter dimension for analytics and subgraph consumers.

-    event CancelledEpochPayment(address indexed recipient, uint256 epoch, uint256 amountRevoked);
-    event ClaimedEpoch(address indexed recipient, uint256 epoch, uint256 amount);
-    event EpochAllocationSet(address indexed recipient, uint256 epoch, uint256 amount);
+    event CancelledEpochPayment(address indexed recipient, uint256 indexed epoch, uint256 amountRevoked);
+    event ClaimedEpoch(address indexed recipient, uint256 indexed epoch, uint256 amount);
+    event EpochAllocationSet(address indexed recipient, uint256 indexed epoch, uint256 amount);
     event MinimumEpochDurationSet(uint256 duration);
     event NextEpochSet(uint256 epoch);

28-33: Clarify epoch numbering and duration semantics in docs.

Document that:

  • currentEpoch() starts at 0 and increments to 1 on first setEpochPayments.
  • “Minimum epoch duration” is strict (next epoch allowed only when block.timestamp > start + minEpochDuration) or inclusive (>=). Tests assume strict.
-/// @notice Keep track of the current active epoch
+/// @notice Current active epoch (0 before first epoch; increments to 1 on first start)
 function currentEpoch() external view returns (uint256);

-/// @notice Minimum epoch duration
+/// @notice Minimum epoch duration; next epoch can start only when
+///         block.timestamp [operator] epochStartTimes[currentEpoch] + minEpochDuration.
+///         Specify whether [operator] is '>' (strict) or '>=' (inclusive).
 function minEpochDuration() external view returns (uint256);

34-41: Tighten NatSpec on getters.

Make the mappings explicit for integrators.

-/// @notice Epoch payments. variable payments
+/// @notice Amount allocated to recipient for a given epoch.
 function epochPayments(address recipient, uint256 epoch) external view returns (uint256);

-/// @notice Mapping of claimed epochs for recipient/epoch pair
+/// @notice Whether recipient has claimed for epoch.
 function claimedEpochs(address recipient, uint256 epoch) external view returns (bool);

-/// @notice Epochs to start time mapping
+/// @notice Start timestamp for an epoch; 0 if unset.
 function epochStartTimes(uint256 epoch) external view returns (uint256);

44-51: Specify preconditions for setting next epoch.

Explicitly state inputs must be non-empty, arrays equal-length, recipients nonzero, amounts > 0, and that this targets currentEpoch + 1.

- * @notice Set initial epoch payments for the next epoch
+ * @notice Set initial epoch payments for the next epoch (currentEpoch + 1).
+ *         Requirements: recipients/amounts non-empty, equal length; recipients != 0; amounts > 0.

53-61: Clarify update scope and accounting.

Note that updates affect the current epoch only and totalAllocation adjusts by delta when modifying an existing allocation.

- * @notice Update existing or set new payments for the current epoch
+ * @notice Update existing or set new payments for the current epoch; totalAllocation reflects deltas.

63-69: Constrain revoke semantics in docs.

State that revocation is for the current epoch, requires nonzero existing allocation, and reverts if already claimed.

- * @notice Revoke epoch payment for contributor
+ * @notice Revoke caller-managed epoch payment for a contributor.
+ *         Only for the current epoch; reverts if amount == 0 or already claimed.

71-75: Clarify claim target.

Make it explicit that the caller claims their own allocation for the specified epoch.

- * @notice Claim for epoch
+ * @notice Claim caller's allocation for `epoch`.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 14384ba and 041c96b.

📒 Files selected for processing (5)
  • protocol/contracts/admin/EpochPayments.sol (1 hunks)
  • protocol/contracts/admin/PaymentBase.sol (1 hunks)
  • protocol/contracts/interfaces/admin/IEpochPayments.sol (1 hunks)
  • protocol/contracts/interfaces/admin/IVestingPayments.sol (1 hunks)
  • protocol/test/forge/unit/admin/EpochPayments.t.sol (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • protocol/contracts/admin/EpochPayments.sol
  • protocol/contracts/admin/PaymentBase.sol
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: princetonbishop
PR: TempleDAO/temple#1169
File: protocol/contracts/admin/PaymentBase.sol:18-48
Timestamp: 2025-08-21T19:18:05.268Z
Learning: In PaymentBase.sol, paymentToken has been changed to be immutable and the setPaymentToken function has been removed from the interface, eliminating the need for access-controlled overrides in inheriting contracts.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Foundry Tests (ubuntu-latest, 20)
🔇 Additional comments (1)
protocol/contracts/interfaces/admin/IVestingPayments.sol (1)

5-7: Extends IPaymentBase (aligned with immutable paymentToken change)

Using retrieved learnings: PaymentBase.paymentToken is immutable and setters were removed from the interface. This extension looks correct.

Comment thread protocol/contracts/interfaces/admin/IEpochPayments.sol
Comment thread protocol/contracts/interfaces/admin/IVestingPayments.sol Outdated
Comment thread protocol/contracts/interfaces/admin/IVestingPayments.sol
Comment thread protocol/test/forge/unit/admin/EpochPayments.t.sol
Comment thread protocol/test/forge/unit/admin/EpochPayments.t.sol
Comment thread protocol/test/forge/unit/admin/EpochPayments.t.sol
Comment thread protocol/test/forge/unit/admin/EpochPayments.t.sol
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
protocol/contracts/interfaces/admin/IVestingPayments.sol (2)

10-13: Index ScheduleCreated id for efficient off-chain filtering

Index the vesting id; keeps within the 3-index limit and aligns with the other events.

-    event ScheduleCreated(
-        bytes32 _id, address indexed _recipient, uint40 _start,
+    event ScheduleCreated(
+        bytes32 indexed _id, address indexed _recipient, uint40 _start,
         uint40 _cliff, uint40 _duration, uint128 _amount
     );

143-146: Fix: external array param must be calldata (won’t compile in ^0.8.20)

External functions cannot take dynamically-sized memory parameters.

-    function getVestingSummary(
-        bytes32[] memory _ids
-    ) external view returns (VestingSummary[] memory summary);
+    function getVestingSummary(
+        bytes32[] calldata _ids
+    ) external view returns (VestingSummary[] memory summary);
🧹 Nitpick comments (6)
protocol/contracts/interfaces/admin/IVestingPayments.sol (2)

8-15: Unify event id parameter names

Use a consistent name for the id across events.

-    event RecipientChanged(bytes32 indexed _vestingId, address indexed _oldRecipient, address indexed _recipient);
+    event RecipientChanged(bytes32 indexed _id, address indexed _oldRecipient, address indexed _recipient);

1-3: Nit: put SPDX before pragma (style)

Common Solidity style prefers SPDX first line.

-pragma solidity ^0.8.20;
-// SPDX-License-Identifier: AGPL-3.0-or-later
+// SPDX-License-Identifier: AGPL-3.0-or-later
+pragma solidity ^0.8.20;
protocol/contracts/interfaces/admin/IEpochPayments.sol (1)

1-3: Nit: put SPDX before pragma (style)

Keeps headers consistent across interfaces.

-pragma solidity ^0.8.20;
-// SPDX-License-Identifier: AGPL-3.0-or-later
+// SPDX-License-Identifier: AGPL-3.0-or-later
+pragma solidity ^0.8.20;
protocol/test/forge/unit/admin/EpochPayments.t.sol (3)

230-243: Avoid leaked prank dependencies; convert this “test_*” into a helper or stop the prank

This function starts an executor prank and relies on it staying active for callers, which is brittle when tests call each other.

Option A (preferred): rename to a helper and stop prank.

-    function test_setEpochPayments_first_epoch() public {
+    function _bootstrapFirstEpoch() internal {
         assertEq(payment.currentEpoch(), 0);
         assertEq(payment.epochStartTimes(1), 0);
         assertEq(payment.epochStartTimes(0), 0);
         vm.startPrank(executor);
         payment.setMinimumEpochDuration(4 weeks);
         address[] memory recipients = new address[](1);
         uint256[] memory amounts = new uint256[](1);
         recipients[0] = alice;
         amounts[0] = 1e18;
         vm.expectEmit(address(payment));
         emit NextEpochSet(1);
         payment.setEpochPayments(recipients, amounts);
+        vm.stopPrank();
     }

Then update call sites to use _bootstrapFirstEpoch(); and wrap privileged calls with vm.prank(executor) as needed.

If you intentionally rely on prank leakage (as discussed earlier), consider adding a brief comment at the call sites to make it explicit.


294-299: Make expected event assertions explicit per event

Call expectEmit for each expected log for clarity.

-        vm.expectEmit(true, true, false, true, address(payment));
-        emit NextEpochSet(3);
-        emit EpochAllocationSet(recipients[0], 3, amounts[0]);
-        emit EpochAllocationSet(recipients[1], 3, amounts[1]);
+        vm.expectEmit(true, true, false, true, address(payment));
+        emit NextEpochSet(3);
+        vm.expectEmit(true, true, false, true, address(payment));
+        emit EpochAllocationSet(recipients[0], 3, amounts[0]);
+        vm.expectEmit(true, true, false, true, address(payment));
+        emit EpochAllocationSet(recipients[1], 3, amounts[1]);

565-572: Check emitter address for TokenRecovered event

Align with other tests by asserting the emitting contract.

-        vm.expectEmit();
+        vm.expectEmit(address(payment));
         emit CommonEventsAndErrors.TokenRecovered(alice, address(fakeToken), amount);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 041c96b and 9f1321d.

📒 Files selected for processing (6)
  • protocol/contracts/admin/EpochPayments.sol (1 hunks)
  • protocol/contracts/admin/VestingPayments.sol (1 hunks)
  • protocol/contracts/interfaces/admin/IEpochPayments.sol (1 hunks)
  • protocol/contracts/interfaces/admin/IVestingPayments.sol (1 hunks)
  • protocol/test/forge/unit/admin/EpochPayments.t.sol (1 hunks)
  • protocol/test/forge/unit/admin/VestingPayments.t.sol (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • protocol/contracts/admin/EpochPayments.sol
  • protocol/test/forge/unit/admin/VestingPayments.t.sol
  • protocol/contracts/admin/VestingPayments.sol
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-28T10:46:38.185Z
Learnt from: princetonbishop
PR: TempleDAO/temple#1169
File: protocol/test/forge/unit/admin/EpochPayments.t.sol:238-242
Timestamp: 2025-08-28T10:46:38.185Z
Learning: In Solidity test files using Foundry's vm.startPrank(), if a test function calls vm.startPrank() without vm.stopPrank(), the prank remains active when that function is called by other test functions, allowing subsequent calls to execute with the same privileges.

Applied to files:

  • protocol/test/forge/unit/admin/EpochPayments.t.sol
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Foundry Tests (ubuntu-latest, 20)
🔇 Additional comments (3)
protocol/contracts/interfaces/admin/IEpochPayments.sol (1)

7-20: LGTM: interface shape and event indexing

Events/errors/read API look coherent; array params correctly use calldata.

protocol/test/forge/unit/admin/EpochPayments.t.sol (2)

26-33: LGTM: approval done with single-call prank

Avoids leaking prank context from setUp().


186-191: LGTM: minEpochDuration test now asserts behavior

Good concise check of setter path.

Comment thread protocol/contracts/admin/EpochPayments.sol Outdated
Comment thread protocol/contracts/admin/EpochPayments.sol Outdated
Comment thread protocol/contracts/admin/EpochPayments.sol Outdated
Comment thread protocol/contracts/admin/VestingPayments.sol Outdated
Comment thread protocol/contracts/admin/VestingPayments.sol Outdated
Comment thread protocol/contracts/interfaces/admin/IVestingPayments.sol Outdated
Comment thread protocol/contracts/admin/VestingPayments.sol Outdated
* feat: simplify interface for epoch payments

* feat: tweaks to vesting payments and deps

* feat: nits

* nits update

* slither

* add revoke releasable to schedule struct

* move elevated access to base constructor

---------

Co-authored-by: princetonbishop <princetonbishop@protonmail.com>
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (10)
protocol/foundry.toml (2)

29-29: Persisting invariant corpus: add gitignore and wire up CI artifacts.

If you intend to keep local fuzz seeds, add this directory to .gitignore and (optionally) upload it as a CI artifact between runs to improve convergence.

+.gitignore
+fuzz-corpus/

Confirm that corpus_dir is recognized by your Foundry version under [profile.default.invariant] (some options have moved across releases).


54-63: Confirm lint rule validity and refine ignore globs
Run forge fmt --list-lints (or equivalent) to ensure all specified rule IDs (asm-keccak256, mixed-case-variable, etc.) are supported by your Foundry version. Additionally, narrow the ignore globs (e.g. target only contracts/test/**/* rather than **/node_modules/**/* and other broad patterns) to avoid silencing valuable lint feedback in tests.

protocol/contracts/interfaces/admin/IPaymentBase.sol (2)

16-21: Clarify access control in NatSpec for setFundsOwner
Document that this is permissioned (only elevated access) to make API expectations explicit.

Apply:

 /**
 * @notice Set funds owner
 * @param _fundsOwner Funds owner
+* @dev Permissioned: only elevated access
 */
 function setFundsOwner(address _fundsOwner) external;

22-28: Clarify access control in NatSpec for recoverToken
Same note for recoverToken to avoid misuse by integrators reading the interface docs.

Apply:

 /**
  * @notice Recover ERC20 token
  * @param _token Token address
  * @param _to Recipient address
  * @param _amount Amount to recover
+ * @dev Permissioned: only elevated access
  */
 function recoverToken(address _token, address _to, uint256 _amount) external;
protocol/contracts/interfaces/admin/IEpochPayments.sol (1)

56-65: Doc/signature mismatch for updateEpochPayments
Docs say “current epoch” but the function accepts an epoch parameter. Align the comment or drop the param in a future interface rev.

Apply:

-    /**
-     * @notice Update existing or set new payments for the current epoch
+    /**
+     * @notice Update existing or set new payments for the specified epoch
      * @param recipients Recipients array
      * @param amounts Amounts array to set
      */
protocol/contracts/interfaces/admin/IVestingPayments.sol (2)

16-19: Prefer uint256 for event amount types

Event params aren’t storage-optimized; using uint256 avoids off-chain decode friction and implicit widening. Safe to keep uint128 in storage.

-    event ScheduleCreated(
-        bytes32 indexed _id, address indexed _recipient, uint40 _start,
-        uint40 _cliff, uint40 _duration, uint128 _amount
-    );
+    event ScheduleCreated(
+        bytes32 indexed _id, address indexed _recipient, uint40 _start,
+        uint40 _cliff, uint40 _duration, uint256 _amount
+    );

54-56: NatSpec mismatches actual accounting semantics

Implementation tracks “outstanding obligation” (amount yet to be claimed after revokes and claims), not strictly “vested and unclaimed.” Update the docstring to prevent integrator confusion.

-    /// @notice Total vesting token vested and unclaimed
+    /// @notice Total unclaimed vesting obligation (sum of undistributed amounts minus revoked portions)
protocol/contracts/admin/EpochPayments.sol (1)

121-129: Epoch start boundary off-by-one: allow start at exact boundary

Permit starting the next epoch when now == start + minEpochDuration. Current <= reverts at the boundary.

-        if (currentEpochCache != 0 && block.timestamp <= startTime + minEpochDuration) { revert CannotStartEpoch(currentEpochCache+1); }
+        if (currentEpochCache != 0 && block.timestamp < startTime + minEpochDuration) { revert CannotStartEpoch(currentEpochCache + 1); }
protocol/contracts/admin/VestingPayments.sol (2)

120-138: Explicit error on revoked-with-zero flow; avoid leaking InvalidParam

If a schedule is revoked and nothing remains to claim, release() currently falls through and reverts with InvalidParam via isActiveVestingId(false). Emit a clearer domain error.

     function release(bytes32 _vestingId) external override {
         VestingSchedule storage _schedule = schedules[_vestingId];
         if (_schedule.start == 0) { revert CommonEventsAndErrors.InvalidParam(); }
         if (_schedule.recipient != msg.sender) { revert CommonEventsAndErrors.InvalidAccess(); }
         if (_schedule.revoked) {
             uint256 amount = _schedule.revokedReleasable;
             if (amount > 0) {
                 _schedule.revokedReleasable = 0;
                 _release(_schedule, amount);
                 emit Released(_vestingId, msg.sender, amount);
                 return;
             }
+            revert CannotRelease();
         }
         if (!isActiveVestingId(_vestingId)) { revert CommonEventsAndErrors.InvalidParam(); }
         uint256 _releasableAmount = _calculateReleasableAmount(_schedule);
         if (_releasableAmount == 0) { revert FullyVested(); }
         _release(_schedule, _releasableAmount);
         emit Released(_vestingId, msg.sender, _releasableAmount);
     }

120-138: Optional: prune fully settled schedules from active set

To keep enumerations lean, remove the vestingId from _activeVestingIds once fully distributed on the final release (non-revoked path).

-        _release(_schedule, _releasableAmount);
-        emit Released(_vestingId, msg.sender, _releasableAmount);
+        _release(_schedule, _releasableAmount);
+        if (_schedule.distributed == _schedule.amount) {
+            // best-effort; ignore failure if already removed elsewhere
+            _activeVestingIds.remove(_vestingId);
+        }
+        emit Released(_vestingId, msg.sender, _releasableAmount);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 5d1699d and c651bd8.

📒 Files selected for processing (13)
  • protocol/.gitignore (1 hunks)
  • protocol/contracts/admin/EpochPayments.sol (1 hunks)
  • protocol/contracts/admin/PaymentBase.sol (1 hunks)
  • protocol/contracts/admin/VestingPayments.sol (1 hunks)
  • protocol/contracts/interfaces/admin/IEpochPayments.sol (1 hunks)
  • protocol/contracts/interfaces/admin/IPaymentBase.sol (1 hunks)
  • protocol/contracts/interfaces/admin/IVestingPayments.sol (1 hunks)
  • protocol/foundry.toml (3 hunks)
  • protocol/lib/forge-std (1 hunks)
  • protocol/package.json (1 hunks)
  • protocol/slither.requirements.txt (1 hunks)
  • protocol/test/forge/unit/admin/EpochPayments.t.sol (1 hunks)
  • protocol/test/forge/unit/admin/VestingPayments.t.sol (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • protocol/lib/forge-std
  • protocol/.gitignore
🚧 Files skipped from review as they are similar to previous changes (2)
  • protocol/test/forge/unit/admin/VestingPayments.t.sol
  • protocol/test/forge/unit/admin/EpochPayments.t.sol
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-08-21T19:18:05.268Z
Learnt from: princetonbishop
PR: TempleDAO/temple#1169
File: protocol/contracts/admin/PaymentBase.sol:18-48
Timestamp: 2025-08-21T19:18:05.268Z
Learning: In PaymentBase.sol, paymentToken has been changed to be immutable and the setPaymentToken function has been removed from the interface, eliminating the need for access-controlled overrides in inheriting contracts.

Applied to files:

  • protocol/contracts/admin/PaymentBase.sol
  • protocol/contracts/interfaces/admin/IPaymentBase.sol
  • protocol/contracts/admin/EpochPayments.sol
📚 Learning: 2025-08-29T05:08:58.252Z
Learnt from: frontier159
PR: TempleDAO/temple#1213
File: protocol/contracts/admin/PaymentBase.sol:22-26
Timestamp: 2025-08-29T05:08:58.252Z
Learning: In PaymentBase.sol constructor, it's acceptable to not validate _fundsOwner for zero address - the team trusts that deployment procedures handle this appropriately, and validation exists in the setFundsOwner function.

Applied to files:

  • protocol/contracts/admin/PaymentBase.sol
  • protocol/contracts/interfaces/admin/IPaymentBase.sol
  • protocol/contracts/admin/EpochPayments.sol
📚 Learning: 2025-08-30T23:20:17.493Z
Learnt from: frontier159
PR: TempleDAO/temple#1214
File: protocol/scripts/deploys/mainnet/team-payments/deploy.ts:9-9
Timestamp: 2025-08-30T23:20:17.493Z
Learning: In Temple DAO's protocol/scripts/deploys/mainnet/team-payments/deploy.ts, the user prefers to keep hard-coded imports for epoch snapshot JSON files (like epoch29b.json) rather than parametrizing them. Avoid suggesting refactoring to make snapshot files runtime parameters.

Applied to files:

  • protocol/contracts/admin/EpochPayments.sol
📚 Learning: 2025-08-29T10:36:03.181Z
Learnt from: princetonbishop
PR: TempleDAO/temple#1213
File: protocol/contracts/admin/VestingPayments.sol:38-39
Timestamp: 2025-08-29T10:36:03.181Z
Learning: In VestingPayments.sol, when fixing revocation accounting issues for multiple schedules per account, the preferred approach is to add revokedReleasable directly to the VestingSchedule struct rather than maintaining separate mappings, leveraging the existing schedules mapping keyed by vestingId for cleaner architecture.

Applied to files:

  • protocol/contracts/interfaces/admin/IVestingPayments.sol
  • protocol/contracts/admin/VestingPayments.sol
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Foundry Tests (ubuntu-latest, 20)
🔇 Additional comments (8)
protocol/foundry.toml (2)

52-53: LGTM: fmt tweaks.

wrap_comments = true and the newline at EOF improve formatting consistency.


13-16: Pin Foundry v1.1.0+ in CI
The flags dynamic_test_linking, legacy_assertions, and assertions_revert require Foundry ≥ 1.1.0. Manually verify your CI/toolchain is pinned to v1.1.0 or newer to prevent unknown-key errors and ensure expected assertion semantics.

protocol/slither.requirements.txt (1)

1-2: Pin upgrades verified in CI (solc-select + slither)
The validation script failed to create a venv (ensurepip missing) and didn’t find solc-select/solc/slither (installed to ~/.local/bin, not on PATH). Confirm your CI image includes python3-venv and adds pip-installed bins to PATH (or adjust the setup) so these tools are callable.

protocol/contracts/interfaces/admin/IEpochPayments.sol (1)

12-17: Confirm lifecycle API that emits NextEpochSet
The interface emits NextEpochSet but doesn’t expose an explicit “roll/start next epoch” function. Verify implementers emit this event along the intended path (e.g., within setEpochPayments or a dedicated function) and that tests assert it.

protocol/contracts/interfaces/admin/IVestingPayments.sol (1)

14-21: LGTM: indexed vesting IDs and calldata array fix landed

Events now index the vesting id; getVestingSummary correctly uses calldata. Nice off-chain UX and gas wins.

Also applies to: 159-161

protocol/contracts/admin/EpochPayments.sol (1)

106-119: Delta-based totalAllocation accounting looks solid

Adjusting by (new - prev) prevents double-counting on updates or duplicates in a batch. Good catch and implementation.

protocol/contracts/admin/VestingPayments.sol (2)

41-45: Good: per-schedule revokedReleasable matches team preference

Storing revokedReleasable on the schedule (not per-recipient mappings) simplifies accounting for multiple schedules per account. Using retrieved learnings.

Also applies to: 92-95


28-76: Input validation coverage is thorough

Start/cliff/duration invariants and zero-address/amount checks are tight; prevents malformed schedules at creation.

Comment thread protocol/contracts/admin/PaymentBase.sol
Comment thread protocol/contracts/admin/PaymentBase.sol
Comment thread protocol/contracts/admin/VestingPayments.sol
Comment thread protocol/contracts/interfaces/admin/IEpochPayments.sol
Comment thread protocol/contracts/interfaces/admin/IPaymentBase.sol
Comment thread protocol/package.json
Comment thread protocol/contracts/admin/VestingPayments.sol
Comment thread protocol/contracts/interfaces/admin/IVestingPayments.sol
// Claimable delta at revoke time
uint256 releasableNow = vestedNow - _schedule.distributed;
// Persist per-schedule claimable checkpoint
_schedule.revokedReleasable = releasableNow.encodeUInt128();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was a specific test case added to check multiple revoke's for the same user? It doesn't look like it, but that's the exact case this was intended to fix. So that should 💯 be added in (unless i just missed it in the existing tests)

Copy link
Copy Markdown
Collaborator Author

@princetonbishop princetonbishop Sep 17, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was in the existing test previous commit as

function test_revokeVesting_invalid_id_after_revoke() public {
        test_revokeVesting_immediate_revoke_after_creation();
        // use previous index to match id
        bytes32 _id = vesting.computeVestingScheduleIdForAddressAndIndex(alice, 0);
        vm.expectRevert(abi.encodeWithSelector(CommonEventsAndErrors.InvalidParam.selector));
        vesting.revokeVesting(_id);
    }

Unless I misunderstood something?

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
protocol/contracts/interfaces/admin/IVestingPayments.sol (2)

14-21: Indexed vesting ids in events — LGTM

Indexing _id enables efficient off-chain filtering and respects the 3-index limit.


45-52: Using uint256 in VestingSummary — LGTM

Aligns with downstream ABI usage and reviewer guidance.

🧹 Nitpick comments (6)
protocol/contracts/interfaces/admin/IVestingPayments.sol (6)

1-3: Move SPDX identifier to the first line

SPDX should precede pragma for tooling and license scanners.

-pragma solidity ^0.8.20;
// SPDX-License-Identifier: AGPL-3.0-or-later
+// SPDX-License-Identifier: AGPL-3.0-or-later
+pragma solidity ^0.8.20;

14-21: Unify event param naming to _vestingId for consistency

Matches function params elsewhere and improves readability. ABI unchanged.

-    event Revoked(bytes32 indexed _id, address indexed _recipient, uint256 _unreleased, uint256 _totalVested);
-    event Released(bytes32 indexed _id, address indexed _recipient, uint256 _amount);
+    event Revoked(bytes32 indexed _vestingId, address indexed _recipient, uint256 _unreleased, uint256 _totalVested);
+    event Released(bytes32 indexed _vestingId, address indexed _recipient, uint256 _amount);
     event ScheduleCreated(
-        bytes32 indexed _id, address indexed _recipient, uint40 _start,
+        bytes32 indexed _vestingId, address indexed _recipient, uint40 _start,
         uint40 _cliff, uint40 _duration, uint128 _amount
     );
-    event RecipientChanged(bytes32 indexed _id, address indexed _oldRecipient, address indexed _recipient);
+    event RecipientChanged(bytes32 indexed _vestingId, address indexed _oldRecipient, address indexed _recipient);

22-25: Add specific error for recipient changes

Consider a targeted error to improve revert clarity.

     error CannotRelease();
     error InvalidScheduleId();
     error FullyVested();
+    error InvalidRecipient();

112-116: Consider returning released amount from release

Quality-of-life for integrators; event parsing not required.

-    function release(bytes32 _vestingId) external;
+    function release(bytes32 _vestingId) external returns (uint256 released);

131-145: Rename params to _holder for consistency with function names

NatSpec/param names are not part of ABI; safe rename.

-    function getLastVestingScheduleForHolder(
-        address _recipient
+    function getLastVestingScheduleForHolder(
+        address _holder
     ) external view returns (VestingSchedule memory);
@@
-    function getVestingScheduleByAddressAndIndex(
-        address _recipient,
+    function getVestingScheduleByAddressAndIndex(
+        address _holder,
         uint256 _index
     ) external view returns (VestingSchedule memory);

101-111: Provide paginated accessors to avoid unbounded array returns

Large sets can be heavy even for off-chain callers.

     function getVestingIdAtIndex(
         uint256 _index
     ) external view returns (bytes32 id);
@@
-    function getVestingIds() external view returns (bytes32[] memory ids);
+    function getVestingIds() external view returns (bytes32[] memory ids);
+    function getVestingIdsPaginated(uint256 _offset, uint256 _limit)
+        external
+        view
+        returns (bytes32[] memory ids);

Also applies to: 167-168

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2cb33c4 and 89526aa.

📒 Files selected for processing (5)
  • protocol/contracts/admin/EpochPayments.sol (1 hunks)
  • protocol/contracts/admin/VestingPayments.sol (1 hunks)
  • protocol/contracts/fakes/templegold/TempleGoldStakingMock.sol (0 hunks)
  • protocol/contracts/interfaces/admin/IVestingPayments.sol (1 hunks)
  • protocol/test/forge/unit/admin/VestingPayments.t.sol (1 hunks)
💤 Files with no reviewable changes (1)
  • protocol/contracts/fakes/templegold/TempleGoldStakingMock.sol
🚧 Files skipped from review as they are similar to previous changes (3)
  • protocol/test/forge/unit/admin/VestingPayments.t.sol
  • protocol/contracts/admin/VestingPayments.sol
  • protocol/contracts/admin/EpochPayments.sol
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-08-29T10:36:03.200Z
Learnt from: princetonbishop
PR: TempleDAO/temple#1213
File: protocol/contracts/admin/VestingPayments.sol:38-39
Timestamp: 2025-08-29T10:36:03.200Z
Learning: In VestingPayments.sol, when fixing revocation accounting issues for multiple schedules per account, the preferred approach is to add revokedReleasable directly to the VestingSchedule struct rather than maintaining separate mappings, leveraging the existing schedules mapping keyed by vestingId for cleaner architecture.

Applied to files:

  • protocol/contracts/interfaces/admin/IVestingPayments.sol
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Foundry Tests (ubuntu-latest, 20)
  • GitHub Check: Slither (ubuntu-latest, 20)
🔇 Additional comments (4)
protocol/contracts/interfaces/admin/IVestingPayments.sol (4)

26-43: Struct packing looks optimal (3 storage slots)

Order achieves tight packing and aligns with our prior learning to include revokedReleasable in the struct.


83-86: Calldata for external array — LGTM

Efficient and compiles under ^0.8.20.


20-21: Expose recipient change method to pair with RecipientChanged event — verification required

File: protocol/contracts/interfaces/admin/IVestingPayments.sol

IVestingPayments declares event RecipientChanged but the interface has no method to change a vesting recipient; either add an admin-only setter to the interface or confirm the implementation exposes this via another interface. Repository search returned no matches—verify the implementation in protocol/contracts/admin/VestingPayments.sol and act accordingly.

    event RecipientChanged(bytes32 indexed _id, address indexed _oldRecipient, address indexed _recipient);
+    /**
+     * @notice Change the recipient for a vesting schedule
+     * @param _vestingId Vesting Id
+     * @param _newRecipient New recipient address
+     */
+    function changeRecipient(bytes32 _vestingId, address _newRecipient) external;

58-68: schedules getter tuple matches VestingSchedule fields — no action required. The VestingSchedule struct in protocol/contracts/interfaces/admin/IVestingPayments.sol lists fields in the exact order used by the schedules(...) return tuple, and VestingPayments declares the mapping as public override schedules, so the auto-generated getter signature is consistent.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (7)
protocol/scripts/deploys/mainnet/templegold/contract-addresses.ts (2)

97-101: Replace "0x" placeholders with the zero address (or add validation) to avoid invalid-address runtime errors

"0x" is not a valid EVM address and will throw if accidentally used in a connect/call. Standardize on the canonical zero address.

Apply this minimal change (repeat for each placeholder TGLD):

-                TGLD: "0x",
+                TGLD: "0x0000000000000000000000000000000000000000",

Optionally, add a small guard wherever these addresses are consumed to assert non-zero on production networks.

Also applies to: 140-144, 183-187, 226-230, 269-273, 312-316, 355-359


52-56: Add typed connector/address getter for ADMIN.VESTING.TGLD

Found direct usage at protocol/scripts/deploys/sepolia/admin/99-post-deploy.ts:17 — VestingPayments__factory.connect(TEMPLEGOLD_ADDRESSES.ADMIN.VESTING.TGLD, owner). Expose a typed connector or at least an address getter (mirror existing TEMPLE_GOLD connectors) and update consumers to use it instead of directly indexing the nested address path.

protocol/scripts/deploys/sepolia/admin/01-vesting-tgld.ts (1)

17-25: Annotate constructor args for readability (optional).

Apply this diff:

   await deployAndMine(
     'VESTING_TGLD',
     factory,
     factory.deploy,
-    TEMPLEGOLD_ADDRESSES.CORE.RESCUER_MSIG,
-    ownerAddress,
-    ownerAddress,
-    TEMPLEGOLD_ADDRESSES.TEMPLE_GOLD.TEMPLE_GOLD
+    TEMPLEGOLD_ADDRESSES.CORE.RESCUER_MSIG,         // rescuer
+    ownerAddress,                                   // fundsOwner
+    ownerAddress,                                   // owner/admin
+    TEMPLEGOLD_ADDRESSES.TEMPLE_GOLD.TEMPLE_GOLD    // paymentToken
protocol/scripts/deploys/sepolia/admin/99-post-deploy.ts (4)

43-76: Strongly type schedule inputs to catch struct mismatches at compile time.

Type the schedules to the exact input type expected by createSchedules.

Apply this diff:

-    const schedules = [
+    const schedules: Parameters<VestingPayments["createSchedules"]>[0] = [
       {
         cliff: cliff,
         start: start,
         duration: ONE_WEEK,
         amount: ethers.utils.parseEther("40"),
         revoked: false,
         distributed: 0,
         revokedReleasable: 0,
         recipient: "0x7Ab8375c4F1167b92a1fD649Bc2f5e8E6aa7d4c4"
       },

Repeat the same typing for the second schedules array below.


85-139: Repeat: add strict typing for the second schedules array.

Apply this diff:

-    const schedules = [
+    const schedules: Parameters<VestingPayments["createSchedules"]>[0] = [
       {
         cliff: cliff,
         start: start,
         duration: ONE_WEEK,
         amount: ethers.utils.parseEther("40"),

141-152: Avoid BigNumber.toNumber() for unbounded counts (minor).

Safer loop without potential overflow:

-  const count = (await vesting.holdersVestingCount(holder)).toNumber();
-  for (let i = 0; i < count; i++) {
+  const count = await vesting.holdersVestingCount(holder);
+  for (let i = 0; i < count.toNumber(); i++) {

32-35: Guard approval amount (optional).

Approving 10B tokens is convenient but risky if accidentally uncommented on prod. Consider env‑driven caps.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 89526aa and 0335ca3.

📒 Files selected for processing (6)
  • protocol/scripts/deploys/mainnet/templegold/contract-addresses.ts (8 hunks)
  • protocol/scripts/deploys/sepolia/admin/01-vesting-tgld.ts (1 hunks)
  • protocol/scripts/deploys/sepolia/admin/02-epoch-payments.ts (1 hunks)
  • protocol/scripts/deploys/sepolia/admin/99-post-deploy.ts (1 hunks)
  • protocol/scripts/deploys/sepolia/deploymentArgs/0xD96595caDE5AADa556F34F546b2992E702aA43e3.js (1 hunks)
  • protocol/test/forge/unit/admin/VestingPayments.t.sol (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • protocol/test/forge/unit/admin/VestingPayments.t.sol
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: princetonbishop
PR: TempleDAO/temple#1169
File: protocol/contracts/admin/PaymentBase.sol:18-48
Timestamp: 2025-08-21T19:18:05.268Z
Learning: In PaymentBase.sol, paymentToken has been changed to be immutable and the setPaymentToken function has been removed from the interface, eliminating the need for access-controlled overrides in inheriting contracts.
📚 Learning: 2025-08-30T23:20:17.523Z
Learnt from: frontier159
PR: TempleDAO/temple#1214
File: protocol/scripts/deploys/mainnet/team-payments/deploy.ts:9-9
Timestamp: 2025-08-30T23:20:17.523Z
Learning: In Temple DAO's protocol/scripts/deploys/mainnet/team-payments/deploy.ts, the user prefers to keep hard-coded imports for epoch snapshot JSON files (like epoch29b.json) rather than parametrizing them. Avoid suggesting refactoring to make snapshot files runtime parameters.

Applied to files:

  • protocol/scripts/deploys/sepolia/admin/02-epoch-payments.ts
  • protocol/scripts/deploys/sepolia/admin/99-post-deploy.ts
🧬 Code graph analysis (3)
protocol/scripts/deploys/sepolia/admin/02-epoch-payments.ts (2)
protocol/scripts/deploys/helpers.ts (2)
  • ensureExpectedEnvvars (716-728)
  • deployAndMine (609-655)
protocol/scripts/deploys/mainnet/templegold/contract-addresses.ts (1)
  • getDeployedTempleGoldContracts (363-370)
protocol/scripts/deploys/sepolia/admin/99-post-deploy.ts (2)
protocol/scripts/deploys/helpers.ts (2)
  • ensureExpectedEnvvars (716-728)
  • mine (582-585)
protocol/scripts/deploys/mainnet/templegold/contract-addresses.ts (1)
  • getDeployedTempleGoldContracts (363-370)
protocol/scripts/deploys/sepolia/admin/01-vesting-tgld.ts (2)
protocol/scripts/deploys/helpers.ts (2)
  • ensureExpectedEnvvars (716-728)
  • deployAndMine (609-655)
protocol/scripts/deploys/mainnet/templegold/contract-addresses.ts (1)
  • getDeployedTempleGoldContracts (363-370)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Foundry Tests (ubuntu-latest, 20)
🔇 Additional comments (3)
protocol/scripts/deploys/mainnet/templegold/contract-addresses.ts (1)

269-273: Confirm sepolia ADMIN.VESTING.TGLD is the deployed Vesting contract

Please verify 0xD96595caDE5AADa556F34F546b2992E702aA43e3 is the expected Vesting contract on Sepolia (bytecode, ABI match, ownership). Link the deploy tx or Etherscan contract page in the PR description.

protocol/scripts/deploys/sepolia/admin/02-epoch-payments.ts (1)

14-25: Confirm constructor arg order and address config for Sepolia.

  • Ensure EpochPayments constructor matches (rescuer, fundsOwner, owner, paymentToken).
  • Verify TEMPLEGOLD_ADDRESSES has Sepolia entries for CORE.RESCUER_MSIG and EXTERNAL.MAKER_DAO.DAI_TOKEN.
protocol/scripts/deploys/sepolia/admin/01-vesting-tgld.ts (1)

16-25: LGTM; confirm admin/fundsOwner choices and Sepolia address map.

  • Using TEMPLE_GOLD as immutable payment token is correct per recent PaymentBase changes.
  • Verify CORE.RESCUER_MSIG and TEMPLE_GOLD.TEMPLE_GOLD are configured for sepolia.
  • If fundsOwner and owner should differ (ops vs admin), pass distinct addresses.

Comment on lines +18 to +25
'VESTING_TGLD',
factory,
factory.deploy,
TEMPLEGOLD_ADDRESSES.CORE.RESCUER_MSIG,
ownerAddress,
ownerAddress,
TEMPLEGOLD_ADDRESSES.EXTERNAL.MAKER_DAO.DAI_TOKEN
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Wrong deployment label: using VESTING_TGLD for EpochPayments will mis-tag outputs.

This will produce verification files/log lines under the VESTING_TGLD key and can overwrite/confuse Vesting addresses.

Apply this diff:

-    'VESTING_TGLD',
+    'EPOCH_PAYMENTS_DAI',
     factory,
     factory.deploy,
-    TEMPLEGOLD_ADDRESSES.CORE.RESCUER_MSIG,
-    ownerAddress,
-    ownerAddress,
-    TEMPLEGOLD_ADDRESSES.EXTERNAL.MAKER_DAO.DAI_TOKEN
+    TEMPLEGOLD_ADDRESSES.CORE.RESCUER_MSIG,        // rescuer
+    ownerAddress,                                  // fundsOwner
+    ownerAddress,                                  // owner/admin
+    TEMPLEGOLD_ADDRESSES.EXTERNAL.MAKER_DAO.DAI_TOKEN // paymentToken
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
'VESTING_TGLD',
factory,
factory.deploy,
TEMPLEGOLD_ADDRESSES.CORE.RESCUER_MSIG,
ownerAddress,
ownerAddress,
TEMPLEGOLD_ADDRESSES.EXTERNAL.MAKER_DAO.DAI_TOKEN
);
'EPOCH_PAYMENTS_DAI',
factory,
factory.deploy,
TEMPLEGOLD_ADDRESSES.CORE.RESCUER_MSIG, // rescuer
ownerAddress, // fundsOwner
ownerAddress, // owner/admin
TEMPLEGOLD_ADDRESSES.EXTERNAL.MAKER_DAO.DAI_TOKEN // paymentToken
);
🤖 Prompt for AI Agents
In protocol/scripts/deploys/sepolia/admin/02-epoch-payments.ts around lines 18
to 25, the deployment is incorrectly labeled 'VESTING_TGLD' which will tag
EpochPayments outputs as vesting; change the label to the correct key (e.g.
'EPOCH_PAYMENTS' or the project constant used for EpochPayments) so the
deployment/verification files and logs are written under the EpochPayments key
instead of VESTING_TGLD.

Comment on lines +3 to +8
module.exports = [
"0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266",
"0xC785695710292c042a2de8A0Ba16F3a054cC2eAD",
"0xC785695710292c042a2de8A0Ba16F3a054cC2eAD",
"0x2ae6318e34bb97ae3755AFcE75559452aA223A5D"
]; No newline at end of file
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

🧩 Analysis chain

Sepolia constructor args look wrong (Hardhat default EOA, duplicate addresses).

  • "0xf39F..." is the default Hardhat account, unlikely to be your Sepolia RESCUER_MSIG or admin.
  • The 2nd and 3rd args are identical; confirm fundsOwner and owner are intentionally the same.
  • Verify arg order matches the contract constructor (likely: rescuer, fundsOwner, owner, paymentToken).

Regenerate this file from an actual Sepolia deploy or fix the values before verification.


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== show target file ==="
sed -n '1,200p' protocol/scripts/deploys/sepolia/deploymentArgs/0xD96595caDE5AADa556F34F546b2992E702aA43e3.js || true
echo
echo "=== list deploymentArgs dir ==="
ls -la protocol/scripts/deploys/sepolia/deploymentArgs || true
echo
echo "=== search for references to this args file ==="
rg -n "0xD96595caDE5AADa556F34F546b2992E702aA43e3" || true
echo
echo "=== search for usage of deploymentArgs directory ==="
rg -n "deploymentArgs" || true
echo
echo "=== search for deploy scripts under protocol/scripts/deploys/sepolia ==="
rg -n --hidden --no-ignore 'deploy' protocol/scripts/deploys/sepolia || true
echo
echo "=== search for common constructor names (rescuer|fundsOwner|paymentToken) in contracts ==="
rg -n --hidden --no-ignore '\brescuer\b|\bfundsOwner\b|\bpaymentToken\b' protocol || true

Length of output: 132197


Sepolia constructor args are incorrect — replace Hardhat default & fix duplicate addresses

  • File: protocol/scripts/deploys/sepolia/deploymentArgs/0xD96595caDE5AADa556F34F546b2992E702aA43e3.js — first arg is 0xf39F... (Hardhat default EOA), 2nd and 3rd args are identical (0xC785...).
  • Rescuer should be a multisig (Gnosis Safe); 0xf39F... must not be used on Sepolia.
  • Constructor order is (rescuer, executor, fundsOwner, paymentToken) — verify against protocol/contracts/admin/VestingPayments.sol (and protocol/test/forge/unit/admin/VestingPayments.t.sol).
  • Action: regenerate this file from the actual Sepolia deploy or replace the four entries with the correct rescuer/executor/fundsOwner/token addresses before running verification.
🤖 Prompt for AI Agents
In
protocol/scripts/deploys/sepolia/deploymentArgs/0xD96595caDE5AADa556F34F546b2992E702aA43e3.js
around lines 3–8 the constructor args are wrong: the first entry is the Hardhat
default EOA (0xf39F...) and the 2nd and 3rd entries are duplicates; constructor
expects (rescuer, executor, fundsOwner, paymentToken). Replace the first value
with the actual Sepolia rescuer (Gnosis Safe multisig) and ensure executor and
fundsOwner are the correct, distinct Sepolia addresses (no duplicates), keeping
the correct paymentToken address; regenerate the file from the real Sepolia
deploy or update the four entries manually and verify the order against
protocol/contracts/admin/VestingPayments.sol and
protocol/test/forge/unit/admin/VestingPayments.t.sol before running
verification.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants