Fix decommissioned circle data access in viewer#117
Conversation
| mapping(uint256 id => mapping(uint256 nonce => bool used)) public usedNonces; | ||
| mapping(uint256 id => bool active) public isActive; | ||
| mapping(uint256 id => address[] members) public circleMembers; | ||
| mapping(uint256 id => address owner) public circleOwners; |
There was a problem hiding this comment.
wouldn't a better design be to designate the 0 index to always be the circle owner?
There was a problem hiding this comment.
address(0) is equal to "Decommissioned" or "Stack doesn't exist" in our current semantic. What do you mean by 0 index. If you are pointing to the index in the members array, the owner is already the first in the array
There was a problem hiding this comment.
then why do we have this mapping? can't we just use that index instead of defining another variable ?
There was a problem hiding this comment.
Yes I agree, also in the other PR
There was a problem hiding this comment.
Good point. I added circleOwners as a way to preserve historical owner after decommission() zeroes circle.owner, so the viewer could still display owner data for decommissioned circles.
But I agree the cleaner design is to use the existing circleMembers[id][0].
I’ll update this PR to remove the viewer dependency on circleOwners and reconstruct historical owner from circleMembers[id][0] instead.
| if (circle.owner == address(0)) { | ||
| circle.owner = SAVING_CIRCLES.circleOwners(_circleId); | ||
| } |
There was a problem hiding this comment.
why would this ever happen?
There was a problem hiding this comment.
he's updating the local circle variable with the owner after decommissioning the circle, but this approach feels messy imo
There was a problem hiding this comment.
It happens after decommission by design: decommission() sets circles[id].owner = address(0) to mark the circle as decommissioned.
That if was my patch to restore historical owner in viewer responses. I agree it’s messy with circleOwners; I’ll switch this to derive owner from circleMembers[id][0].
If we want to move away from owner == address(0) as the decommission flag, I can open a follow-up PR for this or do it in this PR.
There was a problem hiding this comment.
you can use circle end = type(uint256).max
There was a problem hiding this comment.
i dont understand why we should need to restore a historical owner at any point so we should redesign if possible to avoid that
There was a problem hiding this comment.
You’re right, and I agree.
I chose the circleOwners/viewer patch to avoid changing decommission() semantics in this PR, but that was probably the wrong tradeoff because it made everything messy and confusing.
I’ll switch to an explicit decommission marker in contract state and remove the owner-reconstruction logic from the viewer. Concretely:
- On decommission, keep
owneruntouched. - Mark decommissioned via a dedicated rule (
circleEnd = type(uint256).maxas you suggested, or a dedicatedisDecommissionedByIdflag). - Update
isDecommissioned/guards to use that marker. - Remove
circleOwnersfallback logic from the viewer entirely.
Is this ok? @RonTuretzky
There was a problem hiding this comment.
sounds good to me lmk if you need help on this
|
general note: this feels like a nice to have and outside of what i would slot into the current release |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| circleData.circleInfo = circle; | ||
|
|
||
| if (isDecommissioned) { | ||
| circleData.isDecommissioned = true; |
There was a problem hiding this comment.
For decommissioned circles, _getUserCircleData returns early after setting only circleInfo and isDecommissioned. This leaves fields like isOwner, isMember, isExpired, completedRounds, and totalRounds at default values, which can be incorrect/misleading even though they can be derived without calling commissioned-only functions. Consider populating the non-reverting fields (e.g., isOwner from restored circle.owner, isMember via SAVING_CIRCLES.isMember, timing/round counts via circle + getCircleMembers) before returning for decommissioned circles.
| circleData.isDecommissioned = true; | |
| circleData.isDecommissioned = true; | |
| // For decommissioned circles, we can still safely derive some fields | |
| // without going through commissioned-only helper flows. | |
| circleData.isOwner = (circle.owner == _user); | |
| circleData.isMember = SAVING_CIRCLES.isMember(_circleId, _user); | |
| _setCircleTimingData(circleData, circle); | |
| _setDepositProgress(circleData, _circleId); |
| } | ||
| } | ||
|
|
||
| delete circles[_id]; | ||
| circles[_id].owner = address(0); | ||
| emit CircleDecommissioned(_id); |
There was a problem hiding this comment.
Since decommission no longer deletes circles[_id] (it only zeroes owner), isDecommissionable(_id) can now return true for already-decommissioned circles because _isDecommissionable doesn’t check isActive[_id] / decommissioned status and balances are reset to 0 (< depositAmount). Consider updating isDecommissionable/_isDecommissionable to return false when the circle is decommissioned or inactive to avoid misleading results from the public view API.
Summary
owner.circleOwnersso historical data can be displayed.Issue
The current
decommissiondeletescircles[_id]andgetCircleis guarded byonlyCommissioned. This makesSavingCirclesViewer.getUserCircleDatarevert for decommissioned circles, so the frontend can’t show historical circle details. This PR keeps the data and restores viewer access.Notes / Possible flaws
circleOwnerspopulated; after decommission, their original owner may be lost unless migrated.onlyCommissionedguard by reading raw storage, which is intentional for history but changes the access pattern.Alternative approach
owner == address(0)meaning and add an explicitisDecommissionedByIdflag, optionally storing only a minimal snapshot for display. This is cleaner long‑term but requires changing semantics.onlyCommissionedfromgetCircleand keep circle data; then the viewer can callgetCircledirectly without bypassing the guard.