Skip to content

Commit 8de9138

Browse files
committed
chore: added tests, docs, coverage fix
1 parent 1a70cc6 commit 8de9138

File tree

5 files changed

+526
-10
lines changed

5 files changed

+526
-10
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,12 @@ Developers can build new Caveat Enforcers for their own use cases, and the possi
7474

7575
[Read more on "Caveats" ->](/documents/DelegationManager.md#Caveats)
7676

77+
### Delegation Adapters
78+
79+
Delegation Adapters are specialized contracts that bridge the gap between the delegation framework and external protocols that don't natively support delegations.
80+
81+
[Read more on "Delegation Adapters" ->](/documents/Adapters.md)
82+
7783
## Development
7884

7985
### Third Party Developers

documents/Adapters.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Delegation Adapters
2+
3+
Delegation adapters are specialized contracts that simplify integration between the delegation framework and external protocols that don't natively support delegations. Many DeFi protocols require users to perform multi-step operations—typically providing ERC-20 approvals followed by specific function calls—which adapters combine into single, atomic, delegatable executions. This enables users to delegate complex protocol interactions while ensuring outputs are delivered to the root delegator or their specified recipients. Adapters are particularly valuable for enabling non-DeleGator accounts to redeem delegations and meet delegator-specified requirements. Since most existing protocols haven't yet implemented native delegation support, adapters serve as an essential bridge layer that converts delegation-based permissions into protocol-compatible interactions while enforcing proper restrictions and safeguards during redemption.
4+
5+
## Current Adapters
6+
7+
### DelegationMetaSwapAdapter
8+
9+
Facilitates token swaps through DEX aggregators by leveraging ERC-20 delegations with enforced outcome validation. This adapter integrates with the MetaSwap aggregator to execute optimal token swaps while maintaining delegation-based access control. It enables users to delegate ERC-20 token permissions and add conditions for enforced outcomes by the end of redemption. The adapter creates a self-redemption mechanism that atomically executes both the ERC-20 transfer and swap during the execution phase, followed by outcome validation in the afterAllHooks phase. It supports configurable token whitelisting through caveat enforcers and includes signature validation with expiration timestamps for secure API integration.
10+
11+
### LiquidStakingAdapter
12+
13+
Manages stETH withdrawal operations through Lido's withdrawal queue using delegation-based permissions. This adapter specifically focuses on withdrawal functions that require ERC-20 approvals, while deposit operations are excluded since they only require ETH and do not need ERC-20 approval mechanisms. The adapter facilitates stETH withdrawal requests by using delegations to transfer stETH tokens and supports both delegation-based transfers and ERC-20 permit signatures for gasless approvals. It has been designed to enhance the delegation experience by allowing users to enforce stricter delegation restrictions related to the Lido protocol, ensuring that redeemers must send withdrawal request ownership and any resulting tokens directly to the root delegator.
14+
15+
### AaveAdapter
16+
17+
Enables delegation-based lending and borrowing operations on the Aave protocol through secure token transfer delegations. This adapter facilitates both supply and withdrawal operations while maintaining full delegator control over asset custody and destination. The adapter supports flexible execution models including delegator-only operations for maximum control and open-ended execution where authorized delegates can perform operations on behalf of the delegator. Supply operations use ERC-20 token delegations to transfer underlying tokens to the adapter, which then supplies them to Aave with aTokens always being credited to the original delegator. Withdrawal operations leverage aToken delegations to transfer yield-bearing tokens to the adapter, which then redeems them from Aave with underlying tokens always being sent back to the delegator. The adapter eliminates the need for traditional ERC-20 approvals by using delegation-based token transfers, providing more granular control and enhanced security for DeFi lending interactions.

script/coverage.sh

Lines changed: 92 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,105 @@ cd ..
77
folder_path="coverage"
88

99
if [ ! -d "$folder_path" ]; then
10-
# If not, create the folder
1110
mkdir -p "$folder_path"
1211
echo "Folder created at: $folder_path"
1312
else
1413
echo "Folder already exists at: $folder_path"
1514
fi
1615

16+
# Configuration: Define test files for different EVM versions
17+
declare -a SHANGHAI_TESTS=(
18+
"test/helpers/AaveLending.t.sol"
19+
# Add more shanghai tests here in the future
20+
# "test/helpers/AnotherShanghaiTest.t.sol"
21+
)
1722

23+
declare -a CANCUN_TESTS=(
24+
# Add cancun tests here when needed
25+
# "test/helpers/CancunTest.t.sol"
26+
)
1827

19-
# Generates lcov.info
20-
forge coverage --report lcov --skip scripts --report-file "$folder_path/lcov.info"
28+
# Function to build match patterns for forge coverage
29+
build_match_patterns() {
30+
local tests=("$@")
31+
local patterns=""
32+
33+
for test in "${tests[@]}"; do
34+
if [[ -n "$patterns" ]]; then
35+
patterns="$patterns --match-path *$(basename "$test")"
36+
else
37+
patterns="--match-path *$(basename "$test")"
38+
fi
39+
done
40+
41+
echo "$patterns"
42+
}
43+
44+
# Function to build no-match patterns for forge coverage
45+
build_no_match_patterns() {
46+
local tests=("$@")
47+
local patterns=""
48+
49+
for test in "${tests[@]}"; do
50+
if [[ -n "$patterns" ]]; then
51+
patterns="$patterns --no-match-path *$(basename "$test")"
52+
else
53+
patterns="--no-match-path *$(basename "$test")"
54+
fi
55+
done
56+
57+
echo "$patterns"
58+
}
59+
60+
echo "Running coverage with inline EVM version flags..."
61+
echo "-----------------------------------------------"
62+
63+
# Build list of all special EVM tests to exclude from default London run
64+
ALL_SPECIAL_EVM_TESTS=("${SHANGHAI_TESTS[@]}" "${CANCUN_TESTS[@]}")
65+
LONDON_NO_MATCH_PATTERNS=$(build_no_match_patterns "${ALL_SPECIAL_EVM_TESTS[@]}")
66+
67+
# Generate coverage for London EVM (default) - exclude special EVM tests
68+
if [[ -n "$LONDON_NO_MATCH_PATTERNS" ]]; then
69+
echo "Running coverage for London EVM..."
70+
echo "Excluding: ${ALL_SPECIAL_EVM_TESTS[*]}"
71+
forge coverage --evm-version london --report lcov --skip scripts $LONDON_NO_MATCH_PATTERNS --report-file "$folder_path/lcov-london.info"
72+
else
73+
echo "Running coverage for London EVM - no exclusions..."
74+
forge coverage --evm-version london --report lcov --skip scripts --report-file "$folder_path/lcov-london.info"
75+
fi
76+
77+
# Generate coverage for Shanghai EVM tests if any exist
78+
if [ ${#SHANGHAI_TESTS[@]} -gt 0 ]; then
79+
echo "Running coverage for Shanghai EVM..."
80+
echo "Including: ${SHANGHAI_TESTS[*]}"
81+
SHANGHAI_MATCH_PATTERNS=$(build_match_patterns "${SHANGHAI_TESTS[@]}")
82+
forge coverage --evm-version shanghai --report lcov --skip scripts $SHANGHAI_MATCH_PATTERNS --report-file "$folder_path/lcov-shanghai.info"
83+
fi
84+
85+
# Generate coverage for Cancun EVM tests if any exist
86+
if [ ${#CANCUN_TESTS[@]} -gt 0 ]; then
87+
echo "Running coverage for Cancun EVM..."
88+
echo "Including: ${CANCUN_TESTS[*]}"
89+
CANCUN_MATCH_PATTERNS=$(build_match_patterns "${CANCUN_TESTS[@]}")
90+
forge coverage --evm-version cancun --report lcov --skip scripts $CANCUN_MATCH_PATTERNS --report-file "$folder_path/lcov-cancun.info"
91+
fi
92+
93+
# Build the list of coverage files to merge
94+
COVERAGE_FILES=("$folder_path/lcov-london.info")
95+
if [ ${#SHANGHAI_TESTS[@]} -gt 0 ]; then
96+
COVERAGE_FILES+=("$folder_path/lcov-shanghai.info")
97+
fi
98+
if [ ${#CANCUN_TESTS[@]} -gt 0 ]; then
99+
COVERAGE_FILES+=("$folder_path/lcov-cancun.info")
100+
fi
101+
102+
# Merge the lcov files
103+
echo "Merging coverage reports..."
104+
echo "Files to merge: ${COVERAGE_FILES[*]}"
105+
lcov \
106+
--rc branch_coverage=1 \
107+
$(printf -- "--add-tracefile %s " "${COVERAGE_FILES[@]}") \
108+
--output-file "$folder_path/lcov.info"
21109

22110
# Filter out test, mock, and script files
23111
lcov \
@@ -39,4 +127,4 @@ then
39127
--output-directory "$folder_path" \
40128
"$folder_path/filtered-lcov.info"
41129
open "$folder_path/index.html"
42-
fi
130+
fi

src/helpers/AaveAdapter.sol

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,52 @@ import { ExecutionLib } from "@erc7579/lib/ExecutionLib.sol";
1515
contract AaveAdapter {
1616
using SafeERC20 for IERC20;
1717

18+
////////////////////// Events //////////////////////
19+
20+
/// @notice Emitted when a supply operation is executed via delegation
21+
/// @param delegator Address of the token owner (delegator)
22+
/// @param delegate Address of the executor (delegate)
23+
/// @param token Address of the supplied token
24+
/// @param amount Amount of tokens supplied
25+
event SupplyExecuted(address indexed delegator, address indexed delegate, address indexed token, uint256 amount);
26+
27+
/// @notice Emitted when a withdrawal operation is executed via delegation
28+
/// @param delegator Address of the token owner (delegator)
29+
/// @param delegate Address of the executor (delegate)
30+
/// @param token Address of the withdrawn token
31+
/// @param amount Amount of tokens withdrawn
32+
event WithdrawExecuted(address indexed delegator, address indexed delegate, address indexed token, uint256 amount);
33+
34+
////////////////////// Errors //////////////////////
35+
36+
/// @notice Thrown when a zero address is provided for required parameters
37+
error InvalidZeroAddress();
38+
39+
/// @notice Thrown when the number of delegations provided is not exactly one
40+
error InvalidDelegationsLength();
41+
42+
/// @notice Thrown when the caller is not the delegator for restricted functions
43+
error UnauthorizedCaller();
44+
45+
////////////////////// State //////////////////////
46+
1847
IDelegationManager public immutable delegationManager;
1948
IAavePool public immutable aavePool;
2049

50+
////////////////////// Constructor //////////////////////
51+
2152
/// @notice Initializes the adapter with delegation manager and Aave pool addresses
2253
/// @param _delegationManager Address of the delegation manager contract
2354
/// @param _aavePool Address of the Aave lending pool contract
2455
constructor(address _delegationManager, address _aavePool) {
56+
if (_delegationManager == address(0) || _aavePool == address(0)) revert InvalidZeroAddress();
57+
2558
delegationManager = IDelegationManager(_delegationManager);
2659
aavePool = IAavePool(_aavePool);
2760
}
2861

62+
////////////////////// Private Functions //////////////////////
63+
2964
/// @notice Ensures sufficient token allowance for Aave operations
3065
/// @dev Checks current allowance and increases to max if needed
3166
/// @param _token Token to manage allowance for
@@ -37,14 +72,17 @@ contract AaveAdapter {
3772
}
3873
}
3974

75+
////////////////////// Public Functions //////////////////////
76+
4077
/// @notice Supplies tokens to Aave using delegation-based token transfer
4178
/// @dev Only the delegator can execute this function, ensuring full control over supply parameters
4279
/// @param _delegations Array containing a single delegation for token transfer
4380
/// @param _token Address of the token to supply to Aave
4481
/// @param _amount Amount of tokens to supply
4582
function supplyByDelegation(Delegation[] memory _delegations, address _token, uint256 _amount) external {
46-
require(_delegations.length == 1, "Wrong number of delegations");
47-
require(_delegations[0].delegator == msg.sender, "Not allowed");
83+
if (_delegations.length != 1) revert InvalidDelegationsLength();
84+
if (_delegations[0].delegator != msg.sender) revert UnauthorizedCaller();
85+
if (_token == address(0)) revert InvalidZeroAddress();
4886

4987
bytes[] memory permissionContexts_ = new bytes[](1);
5088
permissionContexts_[0] = abi.encode(_delegations);
@@ -61,6 +99,8 @@ contract AaveAdapter {
6199

62100
_ensureAllowance(IERC20(_token), _amount);
63101
aavePool.supply(_token, _amount, msg.sender, 0);
102+
103+
emit SupplyExecuted(msg.sender, msg.sender, _token, _amount);
64104
}
65105

66106
/// @notice Supplies tokens to Aave using delegation-based token transfer with open-ended execution
@@ -69,7 +109,8 @@ contract AaveAdapter {
69109
/// @param _token Address of the token to supply to Aave
70110
/// @param _amount Amount of tokens to supply
71111
function supplyByDelegationOpenEnded(Delegation[] memory _delegations, address _token, uint256 _amount) external {
72-
require(_delegations.length == 1, "Wrong number of delegations");
112+
if (_delegations.length != 1) revert InvalidDelegationsLength();
113+
if (_token == address(0)) revert InvalidZeroAddress();
73114

74115
bytes[] memory permissionContexts_ = new bytes[](1);
75116
permissionContexts_[0] = abi.encode(_delegations);
@@ -86,6 +127,8 @@ contract AaveAdapter {
86127

87128
_ensureAllowance(IERC20(_token), _amount);
88129
aavePool.supply(_token, _amount, _delegations[0].delegator, 0);
130+
131+
emit SupplyExecuted(_delegations[0].delegator, msg.sender, _token, _amount);
89132
}
90133

91134
/// @notice Withdraws tokens from Aave using delegation-based aToken transfer
@@ -94,8 +137,9 @@ contract AaveAdapter {
94137
/// @param _token Address of the underlying token to withdraw from Aave
95138
/// @param _amount Amount of tokens to withdraw (or type(uint256).max for all)
96139
function withdrawByDelegation(Delegation[] memory _delegations, address _token, uint256 _amount) external {
97-
require(_delegations.length == 1, "Wrong number of delegations");
98-
require(_delegations[0].delegator == msg.sender, "Not allowed");
140+
if (_delegations.length != 1) revert InvalidDelegationsLength();
141+
if (_delegations[0].delegator != msg.sender) revert UnauthorizedCaller();
142+
if (_token == address(0)) revert InvalidZeroAddress();
99143

100144
bytes[] memory permissionContexts_ = new bytes[](1);
101145
permissionContexts_[0] = abi.encode(_delegations);
@@ -115,6 +159,8 @@ contract AaveAdapter {
115159

116160
// Withdraw from Aave directly to the delegator
117161
aavePool.withdraw(_token, _amount, msg.sender);
162+
163+
emit WithdrawExecuted(msg.sender, msg.sender, _token, _amount);
118164
}
119165

120166
/// @notice Withdraws tokens from Aave using delegation-based aToken transfer with open-ended execution
@@ -123,7 +169,8 @@ contract AaveAdapter {
123169
/// @param _token Address of the underlying token to withdraw from Aave
124170
/// @param _amount Amount of tokens to withdraw (or type(uint256).max for all)
125171
function withdrawByDelegationOpenEnded(Delegation[] memory _delegations, address _token, uint256 _amount) external {
126-
require(_delegations.length == 1, "Wrong number of delegations");
172+
if (_delegations.length != 1) revert InvalidDelegationsLength();
173+
if (_token == address(0)) revert InvalidZeroAddress();
127174

128175
bytes[] memory permissionContexts_ = new bytes[](1);
129176
permissionContexts_[0] = abi.encode(_delegations);
@@ -143,5 +190,7 @@ contract AaveAdapter {
143190

144191
// Withdraw from Aave directly to the delegator
145192
aavePool.withdraw(_token, _amount, _delegations[0].delegator);
193+
194+
emit WithdrawExecuted(_delegations[0].delegator, msg.sender, _token, _amount);
146195
}
147196
}

0 commit comments

Comments
 (0)