Skip to content

Conversation

@hanzel98
Copy link
Contributor

@hanzel98 hanzel98 commented Dec 6, 2025

Overview

This PR introduces a comprehensive token transformation tracking system that enables delegations to track and control token transformations through DeFi protocol interactions (e.g., Aave, Morpho).

Problem Solved

Traditional delegation systems grant access to a fixed amount of a single token. However, when tokens are used in DeFi protocols, they transform into different tokens (e.g., USDC → aUSDC in Aave). This system maintains granular control over all tokens derived from the original delegation until expiration or revocation.

Solution

Components Added

  1. TokenTransformationEnforcer - Tracks multiple tokens per delegationHash

    • Maps delegationHash => token => availableAmount
    • Validates token usage in beforeHook
    • Public view function for querying available amounts
  2. AdapterManager - Central coordinator for protocol interactions

    • Routes protocol calls to appropriate adapters
    • Updates enforcer state after transformations
    • Transfers all tokens to root delegator
  3. Protocol Adapters - Protocol-specific implementations

    • AaveAdapter: Handles Aave V3 deposits/withdrawals
    • MorphoAdapter: Handles Morpho market interactions
    • ILendingAdapter: Standard interface for adapters

Key Features

  • ✅ Track multiple tokens per delegation
  • ✅ Automatic state updates after protocol interactions
  • ✅ Support for multiple lending protocols
  • ✅ Public API for querying available amounts
  • ✅ All tokens always belong to root delegator

Documentation

Comprehensive documentation added in documents/TokenTransformationSystem.md explaining:

  • Problem statement and requirements
  • Solution architecture
  • How the system works with examples
  • Security considerations
  • Future enhancements

Testing

TODO: Add comprehensive tests for:

  • TokenTransformationEnforcer initialization and state updates
  • AdapterManager routing and state coordination
  • AaveAdapter deposit/withdraw flows
  • MorphoAdapter deposit/withdraw flows
  • Multi-token tracking scenarios

Related Issues

N/A - New feature


Note

Introduce a token transformation tracking system with an enforcer, central AdapterManager, and Aave/Morpho adapters to manage and record delegated asset conversions.

  • Contracts:
    • TokenTransformationEnforcer.sol: Tracks delegationHash -> token -> amount, initializes from terms in beforeHook, validates/deducts transfers, exposes updateAssetState (restricted to AdapterManager) and getAvailableAmount.
    • AdapterManager.sol: Orchestrates protocol actions via registered protocolAdapters, integrates with DelegationManager.redeemDelegations, manages approvals, executes adapter calls, updates enforcer state, and forwards resulting tokens to the root delegator; supports adapter registration/removal and executor entrypoint.
    • Adapters:
      • AaveAdapter.sol: Implements deposit/withdraw on Aave; measures balances, wraps/unwraps aTokens via IATokenWrapper, returns TransformationInfo.
      • MorphoAdapter.sol: Implements deposit/withdraw on Morpho markets; measures balances and returns TransformationInfo.
    • Interface: ILendingAdapter.sol defining TransformationInfo and executeProtocolAction.
  • Docs: Add documents/TokenTransformationSystem.md detailing architecture, flows, API, and security.

Written by Cursor Bugbot for commit ab9c08d. This will update automatically on new commits. Configure here.

…ractions

- Add TokenTransformationEnforcer to track multiple tokens per delegation
- Add AdapterManager to coordinate protocol adapters and update enforcer state
- Add AaveAdapter and MorphoAdapter for lending protocol interactions
- Add ILendingAdapter interface for protocol adapters
- Add comprehensive documentation explaining the system

This enables delegations to track token transformations through DeFi protocols,
allowing AI agents to use delegated tokens in lending protocols while maintaining
granular control over evolving token positions.
@hanzel98 hanzel98 requested a review from a team as a code owner December 6, 2025 03:45
{
uint256 delegationsLength_ = _delegations.length;
if (delegationsLength_ == 0) revert InvalidEmptyDelegations();
if (_delegations[0].delegator != msg.sender) revert NotLeafDelegator();
Copy link

Choose a reason for hiding this comment

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

Bug: Wrong delegate/delegator check prevents authorized users from calling

The check _delegations[0].delegator != msg.sender verifies the wrong field. In a delegation chain, the delegate (recipient of permission) should be the caller, not the delegator (grantor of permission). The DelegationManager.redeemDelegations expects delegations_[0].delegate == msg.sender. This mismatch means the actual authorized delegate cannot call this function, as they would fail this check, while the delegator (who shouldn't be calling) would pass this check but fail in redeemDelegations.

Fix in Cursor Fix in Web

}

// Prepare actionData with AdapterManager address for balance measurement
bytes memory enhancedActionData_ = abi.encode(address(this), _actionData);
Copy link

Choose a reason for hiding this comment

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

Bug: ABI encoding mismatch causes incorrect actionData decoding

AdapterManager encodes action data using abi.encode(address(this), _actionData), which uses dynamic encoding for the bytes parameter (offset + length + data). However, AaveAdapter._handleWithdraw and MorphoAdapter._handleWithdraw decode using abi.decode(_actionData[32:], (address)), assuming flat/packed encoding. With abi.encode, byte position 32 contains the offset pointer, not the underlying token address. This will decode garbage or revert on withdraw operations.

Additional Locations (2)

Fix in Cursor Fix in Web

uint256 wrappedBalanceBefore_ = wrappedToken_.balanceOf(_adapterManager);

// Wrap the aTokens
IATokenWrapper(aTokenWrapper).wrap(aTokenAddress_, aTokenAmount_);
Copy link

Choose a reason for hiding this comment

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

Bug: Adapter cannot wrap/approve tokens it doesn't hold

In _handleDeposit, after Aave's supply sends aTokens to _adapterManager, the adapter calls aToken_.safeIncreaseAllowance(aTokenWrapper, aTokenAmount_) and IATokenWrapper(aTokenWrapper).wrap(...). However, the adapter contract itself doesn't hold the aTokens - they were sent to _adapterManager. The allowance is set on the adapter's address which has zero balance, causing the wrap operation to fail. The same issue exists in _handleWithdraw where the adapter tries to approve wrapped tokens it doesn't hold.

Additional Locations (1)

Fix in Cursor Fix in Web


// Execute deposit (supply) to Aave on behalf of AdapterManager
// Note: AdapterManager must have approved Aave Pool before calling this adapter
IAavePool(aavePool).supply(address(_tokenFrom), _amountFrom, _adapterManager, 0);
Copy link

Choose a reason for hiding this comment

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

Bug: Protocol calls fail because adapter lacks input tokens

The AdapterManager holds the input tokens and approves the protocol, but then calls the adapter contract which in turn calls the protocol. When IAavePool(aavePool).supply(...) or IMorphoMarket(...).supply(...) is executed, the protocol attempts to pull tokens from msg.sender (the adapter), not from AdapterManager. Since the adapter doesn't hold the tokens and hasn't approved the protocol, these calls will fail due to insufficient balance or allowance.

Additional Locations (1)

Fix in Cursor Fix in Web

// For Aave/Morpho, we need to approve the protocol contract
uint256 currentAllowance_ = _tokenFrom.allowance(address(this), _protocolAddress);
if (currentAllowance_ < _amountFrom) {
_tokenFrom.safeIncreaseAllowance(_protocolAddress, type(uint256).max);
Copy link

Choose a reason for hiding this comment

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

Bug: Native token input causes revert calling ERC20 methods

When _tokenFrom is address(0) (native token case, which is explicitly handled at line 194-195 for the transfer execution), executeProtocolActionInternal unconditionally calls _tokenFrom.allowance(...) and _tokenFrom.safeIncreaseAllowance(...). Calling these ERC20 methods on address(0) will revert since there's no contract at that address. This makes the native token code path at lines 194-195 unreachable in practice, as the subsequent internal execution will always fail.

Fix in Cursor Fix in Web

@hanzel98 hanzel98 marked this pull request as draft December 8, 2025 14:14
…og action string

- Removed redundant actionHash parameter from executeProtocolActionByDelegation and related functions
- Action hash is now computed internally from the action string in _actionData
- Updated ProtocolActionExecuted event to log action as string instead of hash
- Added ACTION_DEPOSIT and ACTION_WITHDRAW constants in AaveAdapter
- Fixed AdapterManager owner setup in TokenTransformationEnforcer tests
- Create new IAave.sol interface file containing all Aave-related interfaces
- Move IAavePool, IAaveDataProvider, IStaticATokenFactory, and IERC4626 interfaces
- Update AaveAdapter, AdapterManager, and test files to import from IAave.sol
- Centralize Aave interfaces for better maintainability and reuse
…ment

- Merge TokenTransformationDesignAnalysis.md into TokenTransformationSystem.md
- Integrate design patterns and rationale into architecture sections
- Keep contract interaction diagram and design explanations
- Remove duplication and cross-references
- Single source of truth for system documentation
- Expand abbreviated 'TTE' to full 'TokenTransformationEnforcer'
- Clarify validation step description
- Make diagram note more readable
revert InsufficientTokensAvailable(_delegationHash, token_, transferAmount_, available_);
}

// Deduct from available amount
Copy link
Member

Choose a reason for hiding this comment

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

save a couple units gas by caching new amount vs subtracting twice

Comment on lines +132 to +134
if (currentAllowance_ > 0) {
_tokenFrom.safeDecreaseAllowance(aavePool, currentAllowance_);
}
Copy link
Member

Choose a reason for hiding this comment

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

might not need this

} else if (wrapperAsset_ == address(_tokenFrom)) {
// Wrapper handles Aave deposit internally
_tokenFrom.safeIncreaseAllowance(staticATokenAddress_, _amountFrom);
wrappedShares_ = IERC4626(staticATokenAddress_).deposit(_amountFrom, _adapterManager);
Copy link
Member

Choose a reason for hiding this comment

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

may be able to just deposit to root delegator, ie the user

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.

3 participants