Skip to content

Implement access controls for Position objects#130

Merged
jordanschalm merged 25 commits into
mainfrom
jord/115-position-permissions
Feb 5, 2026
Merged

Implement access controls for Position objects#130
jordanschalm merged 25 commits into
mainfrom
jord/115-position-permissions

Conversation

@jordanschalm
Copy link
Copy Markdown
Member

@jordanschalm jordanschalm commented Jan 30, 2026

Ref: #115

This PR adds access control for Position objects.

  • Converts Position struct to resource type
    • EPositionManage entitlement protects access to sensitive configuration change methdos
    • FungibleToken.Withdraw entitlement protects access to withdrawals
    • Ability to deposits are public to anyone with a reference to the Position, maintain current policy of anyone can deposit to any position
  • Adds PositionManager, which acts as a collection type for Position's.
  • createPosition returns a Position resource rather than a position ID
    • createPosition is also now access(all).

claude and others added 13 commits January 22, 2026 16:18
Convert Position from struct to resource type with entitlements-based access control.
This addresses the TODO at line 3596 and the security concern at line 2640 regarding
position access permissions.

Key changes:
- Added new position-specific entitlements (EPositionDeposit, EPositionWithdraw,
  EPositionConfigure, EPositionManage)
- Converted Position struct to resource type with fine-grained entitlement controls
- Updated Pool.createPosition() to return @position resource instead of UInt64
- Added storage path helper functions (getPositionStoragePath, getPositionPublicPath)
- Removed PositionWrapper pattern in favor of direct Position resource storage
- Updated all test transactions to work with Position resources
- Position resources are now held in user accounts with proper ownership

Breaking changes:
- Pool.createPosition() returns @position instead of UInt64
- Position can no longer be manually constructed
- All position operations require appropriate entitlements
- Users must store Position resources in their accounts

https://claude.ai/code/session_01QbCEFZ42NrxU7hdZo7KWPw
Resolved conflicts in the following files:

- cadence/tests/funds_available_above_target_health_test.cdc:11-17
  Reasoning: Redundant variable declarations that were moved to test_helpers.cdc
  Resolution: Removed local declarations (flowTokenIdentifier, moetTokenIdentifier, flowVaultStoragePath) as they are now imported from test_helpers.cdc

- cadence/tests/funds_required_for_target_health_test.cdc:12-18
  Reasoning: Same refactoring - constants moved to shared test_helpers.cdc
  Resolution: Removed local declarations in favor of imported constants from test_helpers.cdc

- cadence/tests/position_lifecycle_happy_test.cdc:15-20
  Reasoning: Same refactoring - constants moved to shared test_helpers.cdc
  Resolution: Removed local declarations (flowTokenIdentifier, flowVaultStoragePath) in favor of imported constants

- cadence/tests/position_lifecycle_happy_test.cdc:88-92
  Reasoning: Conflicting transaction parameter - positionId (HEAD) vs WRAPPER_STORAGE_PATH (origin/main)
  Resolution: Kept positionId parameter from HEAD, as the transaction file (repay_and_close_position.cdc:18) expects positionId: UInt64 parameter, not a storage path

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Test transactions now borrow Position references directly from PositionManager
instead of calling proxy methods. This follows the pattern where PositionManager
only provides core collection functionality (add, remove, borrow) and users
interact directly with Position objects.

Changes:
- borrow_from_position.cdc: Borrow Position and call withdraw() directly
- deposit_to_wrapped_position.cdc: Borrow Position, auto-find position ID,
  and call depositAndPush() directly
- repay_and_close_position.cdc: Borrow Position and call methods directly
- flow.json: Add missing test contracts (UniswapV2SwapConnectors, TokenA,
  TokenB, EVMAbiHelpers)

All 31 tests now passing.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Add createPositionPublic() wrapper function to Pool that allows anyone to
create positions without requiring beta grant capabilities. Remove
MockFlowCreditMarketConsumer infrastructure and replace with direct
Position operations via PositionManager. All tests passing (31/31).

Changes:
- Add Pool.createPositionPublic() public wrapper function
- Remove MockFlowCreditMarketConsumer contract and transactions
- Add position-manager transactions for deposit/borrow operations
- Update all tests to use public creation pattern
- Remove grantPoolCapToConsumer() helper function

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Replace the wrapper function approach with a simpler solution: change
createPosition() from access(EParticipant) to access(all). This allows
the function to be called through the existing public Pool capability
without requiring a wrapper or attempting to publish entitled capabilities
(which Cadence runtime prevents).

Key insight: Cadence prevents publishing auth(E) capabilities to public
paths at runtime, so the cleaner approach is to change the function's
access level directly rather than working around it with wrappers.

Changes:
- Change Pool.createPosition() from access(EParticipant) to access(all)
- Remove createPositionPublic() wrapper function (no longer needed)
- Update transaction to use standard PoolPublicPath
- All 31 tests passing

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Update Position storage paths to camelCase with address suffix
- Remove EPositionDeposit entitlement (deposits now access(all))
- Replace EPositionWithdraw with FungibleToken.Withdraw
- Remove EpositionAdmin entitlement (unused)
- Add documentation for EPositionManage entitlement
- Remove beta grant references from documentation
- Clean up changeset-specific comments

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Reorganize transaction files by moving create_position.cdc and
repay_and_close_position.cdc from tests/transactions/pool-management
to transactions/flow-credit-market/position/. Update all test file
references to use the new location.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
@jordanschalm jordanschalm force-pushed the jord/115-position-permissions branch from a3e2901 to e55c60f Compare February 3, 2026 00:31
@jordanschalm jordanschalm changed the title [DRAFT] Implement access controls for Position objects Implement access controls for Position objects Feb 3, 2026
@jordanschalm jordanschalm marked this pull request as ready for review February 3, 2026 18:07
@jordanschalm jordanschalm requested a review from a team as a code owner February 3, 2026 18:07
/// Position resources are held in user accounts and provide access to one position (by pid).
/// Clients are recommended to use PositionManager to manage access to Positions.
///
access(all) resource Position {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

So happy this is being converted to a resource. I tried to get them to do that last year but they said it wasn't a priority at that moment

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.

Can we talk about this at the sync tomorrow? It was my design decision to make it a struct, and it wasn't an accident.

It's quite possible that making it a struct is more trouble/confusion than it's worth, but it was my firm belief that it makes it easier to build on top of of the lending protocol while still being secure. I haven't been working on the code, so my mind is open, but I would like to understand the logic behind this change.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yeah, let's discuss the details tomorrow. The main goal here is implementing per-user access control (I make a Position, then only I can withdraw/modify it). I chose a resource to match other places where a resource represents user-specific access (NodeStaker, DKGParticipant, etc.)

We are aiming to have per-user access control for positions merged prior to the next audit round (next Monday).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Decided to keep this as resource at Q&A meeting today Feb 5.

  • There is no functionality that can only be implementing using a struct -- it purely simplifies implementing some functionality
  • We would like to implement a duplicate function on the Position resource to simplify the process of sharing it with other systems (generally useful feature for these kinds of "access key" resources), later.

/// A collection resource that manages multiple Position resources for an account.
/// This allows users to have multiple positions while using a single, constant storage path.
///
access(all) resource PositionManager {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think I brought this up before, but would it make sense to make Position and NFT and PositionManager an NFT Collection? Just to plug into all the NFT functionality and interfaces that already exist? It is different enough that I could see that not being useful though, but curious to hear others thoughts

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.

I considered this when working on the first draft of this code, and decided against it, but I'm open to hear other folks' thoughts on this.

I heard there were some problems when Uniswap switched to using NFTs to represent LP positions in v3. Most people think of "NFT" as meaning "a collectible", and a number of people sold their Uniswap NFTs thinking that it was just a "free gift" for being an LP instead of being the actual object that represents their LP position.

From a software abstraction mindset, I can see how making it an NFT is appealing, and allows us to share some code, but from a user abstraction mindset, it seems like it could create confusion and problems. Do we really need people seeing their lending positions on the NFT page of their wallet? Do we want them to bridge them to EVM, and list them on OpenSea? Maybe yes? But I personally think the problems that can come up outweigh the advantages.

Having said that, I have talked to Jeff about creating a new interface (or at least new metadata types) that represent defi accounts/positions/investments so that wallets CAN make it easier for users to manage them. There could be a single page in your wallet that shows all of your various investments and available funds (including credit lines). We don't really have the bandwidth to work on that at the moment, but I personally think that's a much better fit than using the NFT interface.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Personally, I've gone down that route of issuing an NFT for a position previously and noticed that it didn't really improve functionality that much without spending a lot of additional effort for all of the downhill integrations or actions that you thought would occur. Also for all of the "wallet viewers" they would have to create custom tooling to digest that position data.

The big benefit from an end user perspective would be being able to take additional leverage or rehypothecation. While allowing for that opens a lot of additional tailrisk imo and adds complexity for not too much gain.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Decided to not make Position implement NFT interface at Q&A meeting today Feb 5. Consider implementing DeFi-specific metadata information for Position in the future.

Comment thread cadence/contracts/FlowCreditMarket.cdc Outdated
Comment on lines +178 to +180
/// Grants access to configure drawdown sinks, top-up sources, and other position settings, for the Position resource.
/// Withdrawal access is provided using FungibleToken.Withdraw.
access(all) entitlement EPositionManage
Copy link
Copy Markdown
Contributor

@nialexsan nialexsan Feb 4, 2026

Choose a reason for hiding this comment

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

nit: rename to EPositionAdmin or EPositionControl to avoid confusion with PositionManager

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done in faa54d4

Comment thread cadence/contracts/FlowCreditMarket.cdc Outdated
/// Returns a Position resource that provides fine-grained access control through entitlements.
/// The caller must store the Position resource in their account and manage access to it.
/// Clients are recommended to use the PositionManager collection type to manage their Positions.
access(all) fun createPosition(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

restore access gating

Suggested change
access(all) fun createPosition(
access(EParticipant) fun createPosition(

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Thanks - I didn't realize this was going to be used for beta access gating, but that makes sense. I have reverted this change so position creation is gated again.

Copy link
Copy Markdown
Contributor

@nialexsan nialexsan left a comment

Choose a reason for hiding this comment

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

it looks like this PR removed gated access to position creation
probably it should be restored, since the project is still in closed beta

@jordanschalm
Copy link
Copy Markdown
Member Author

@nialexsan Thanks for the review - I revert the removal of beta access gating and addressed other comments

@jordanschalm jordanschalm merged commit 6782314 into main Feb 5, 2026
1 of 2 checks passed
@jordanschalm jordanschalm deleted the jord/115-position-permissions branch February 5, 2026 22:01
@jordanschalm jordanschalm mentioned this pull request Feb 7, 2026
6 tasks
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.

6 participants