Skip to content

Commit 8aa62eb

Browse files
authored
PRE-1753: ERC4626 rounding audit feedback (#70)
* auto wrapper changes * test fixes * remove unnecessary import * update makefile
1 parent 49e7a14 commit 8aa62eb

5 files changed

Lines changed: 94 additions & 40 deletions

File tree

Makefile

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
DEPLOYER_ECDSA_PRIV_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
44
# public key - 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266
55

6-
PREDICATE_HOOK_ADDRESS=0x3BD20e23e524a20dA67F73e65938aABd1C47E880
6+
PREDICATE_HOOK_ADDRESS=0x452e2E2CfcaC437d4E321226A3255d2cFc5c68A0
77
SWAP_ROUTER_ADDRESS=0x8F2c925603c4ba055779475F14241E3c9ee7c1be
8-
AUTO_WRAPPER_HOOK_ADDRESS=0x3f39eC4911c77085a8E6b9d99C7EaA351d64e8C8
8+
AUTO_WRAPPER_HOOK_ADDRESS=0x75202124c8B7FAFBD66AB67F39CE01f69C0128c8
99
POSM_ADDRESS=0xbD216513d74C8cf14cf4747E6AaA6420FF64ee9e
1010
# POLICY_ID=x-aleo-6a52de9724a6e8f2 // mainnet
1111
POLICY_ID=local-test-policy
@@ -81,16 +81,38 @@ create-pool-and-mint-liquidity:
8181
--private-key ${DEPLOYER_ECDSA_PRIV_KEY} \
8282
--broadcast -vvvv
8383

84-
swap-usdc-for-usdl:
84+
swap-usdc-for-usdl-exact-in:
8585
export NETWORK=${NETWORK} && \
86+
export CASE=SWAP_USDC_FOR_USDL_EXACT_IN && \
8687
export AUTO_WRAPPER_HOOK_ADDRESS=${AUTO_WRAPPER_HOOK_ADDRESS} && \
8788
export SWAP_ROUTER_ADDRESS=${SWAP_ROUTER_ADDRESS} && \
88-
forge script script/common/SwapUSDCForUSDL.s.sol \
89+
forge script script/common/SwapScript.s.sol \
8990
--via-ir \
9091
--rpc-url ${RPC_URL} \
9192
--private-key ${DEPLOYER_ECDSA_PRIV_KEY} \
9293
--broadcast -vvvv
9394

95+
swap-usdl-for-usdc-exact-in:
96+
export NETWORK=${NETWORK} && \
97+
export CASE=SWAP_USDL_FOR_USDC_EXACT_IN && \
98+
export AUTO_WRAPPER_HOOK_ADDRESS=${AUTO_WRAPPER_HOOK_ADDRESS} && \
99+
export SWAP_ROUTER_ADDRESS=${SWAP_ROUTER_ADDRESS} && \
100+
forge script script/common/SwapScript.s.sol \
101+
--via-ir \
102+
--rpc-url ${RPC_URL} \
103+
--private-key ${DEPLOYER_ECDSA_PRIV_KEY} \
104+
--broadcast -vvvv
105+
106+
swap-usdl-for-usdc-exact-out:
107+
export NETWORK=${NETWORK} && \
108+
export CASE=SWAP_USDL_FOR_USDC_EXACT_OUT && \
109+
export AUTO_WRAPPER_HOOK_ADDRESS=${AUTO_WRAPPER_HOOK_ADDRESS} && \
110+
export SWAP_ROUTER_ADDRESS=${SWAP_ROUTER_ADDRESS} && \
111+
forge script script/common/SwapScript.s.sol \
112+
--via-ir \
113+
--rpc-url ${RPC_URL} \
114+
--private-key ${DEPLOYER_ECDSA_PRIV_KEY} \
115+
--broadcast -vvvv
94116

95117
deploy-auto-wrapper:
96118
export NETWORK=${NETWORK} && \

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ For testing with mainnet anvil fork:
7777
2. Run `make deploy-predicate-hook` to deploy standalone predicate hook contract. Update `PREDICATE_HOOK_ADDRESS` env variable in Makefile
7878
3. Run `make create-pool-and-mint-liquidity`. This deploys a V4 pool and mints necessary liquidity as well
7979
4. Run `make deploy-auto-wrapper` to deploy auto wrapper and create ghost pool. Update `AUTO_WRAPPER_HOOK_ADDRESS` env variable in Makefile.
80-
5. Run `make swap-usdc-for-usdl`. This will swap USDC for USDL on the ghost + liquidity pools that we just configured. (there's some more options available in the script that can be used).
80+
5. Run `make swap-usdc-for-usdl-exact-in`. This will swap USDC for USDL on the ghost + liquidity pools that we just configured. (there's some more options available in the script that can be used).
8181

8282
*Note: Predicate signature validation is skipped as the predicate hook owner is added to an authorized owner allow-list during hook creation.*
8383

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,26 +20,49 @@ import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
2020
import {V4SwapRouter} from "../../src/V4SwapRouter.sol";
2121

2222
contract SwapScript is Script {
23+
enum Case {
24+
SWAP_USDC_FOR_USDL_EXACT_IN,
25+
SWAP_USDL_FOR_USDC_EXACT_IN,
26+
SWAP_USDL_FOR_USDC_EXACT_OUT
27+
}
28+
2329
uint24 lpFee = 0; // 0.30%
2430
int24 tickSpacing = 60;
2531
Currency private _currency0;
2632
Currency private _currency1;
2733
INetwork private _env;
2834
address private _autowrapperHookAddress;
2935
V4SwapRouter private _swapRouter;
36+
string private _case;
3037

3138
function _init() internal {
3239
bool networkExists = vm.envExists("NETWORK");
3340
bool autoWrapperHookAddress = vm.envExists("AUTO_WRAPPER_HOOK_ADDRESS");
3441
bool swapRouterAddressExists = vm.envExists("SWAP_ROUTER_ADDRESS");
42+
bool caseExists = vm.envExists("CASE");
3543
require(
36-
networkExists && autoWrapperHookAddress && swapRouterAddressExists,
44+
networkExists && autoWrapperHookAddress && swapRouterAddressExists && caseExists,
3745
"All environment variables must be set if any are specified"
3846
);
3947
string memory _network = vm.envString("NETWORK");
4048
_env = new NetworkSelector().select(_network);
4149
_autowrapperHookAddress = vm.envAddress("AUTO_WRAPPER_HOOK_ADDRESS");
4250
_swapRouter = V4SwapRouter(vm.envAddress("SWAP_ROUTER_ADDRESS"));
51+
_case = vm.envString("CASE");
52+
}
53+
54+
function _stringToCase(
55+
string memory _case
56+
) internal returns (Case) {
57+
bytes32 _caseHash = keccak256(abi.encodePacked(_case));
58+
if (_caseHash == keccak256(abi.encodePacked("SWAP_USDC_FOR_USDL_EXACT_IN"))) {
59+
return Case.SWAP_USDC_FOR_USDL_EXACT_IN;
60+
} else if (_caseHash == keccak256(abi.encodePacked("SWAP_USDL_FOR_USDC_EXACT_IN"))) {
61+
return Case.SWAP_USDL_FOR_USDC_EXACT_IN;
62+
} else if (_caseHash == keccak256(abi.encodePacked("SWAP_USDL_FOR_USDC_EXACT_OUT"))) {
63+
return Case.SWAP_USDL_FOR_USDC_EXACT_OUT;
64+
}
65+
revert("Invalid case input");
4366
}
4467

4568
function run() public {
@@ -54,9 +77,16 @@ contract SwapScript is Script {
5477
vm.label(address(0xf6f4A30EeF7cf51Ed4Ee1415fB3bFDAf3694B0d2), "SERVICEMANAGER_CONTRACT");
5578

5679
_tokenApprovals();
57-
swapUSDCForUSDLExactIn();
58-
swapUSDLForUSDCExactIn();
59-
// swapUSDLForUSDCExactOut();
80+
Case _case = _stringToCase(_case);
81+
if (_case == Case.SWAP_USDC_FOR_USDL_EXACT_IN) {
82+
swapUSDCForUSDLExactIn();
83+
} else if (_case == Case.SWAP_USDL_FOR_USDC_EXACT_IN) {
84+
swapUSDLForUSDCExactIn();
85+
} else if (_case == Case.SWAP_USDL_FOR_USDC_EXACT_OUT) {
86+
swapUSDLForUSDCExactOut();
87+
} else {
88+
revert("Invalid case input");
89+
}
6090
}
6191

6292
function _tokenApprovals() internal {

src/AutoWrapper.sol

Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ contract AutoWrapper is BaseHook, DeltaResolver, Ownable2Step {
279279
// Note: UniversalRouter sends USDL to the poolManager at start of swap
280280
// this route also uses USDL in auto wrapper contract for rounding error
281281
uint256 inputAmount =
282-
isExactInput ? uint256(-params.amountSpecified) : uint256(getWrapInputRequired(uint256(-wUSDLDelta)));
282+
isExactInput ? uint256(-params.amountSpecified) : wUSDL.previewMint(uint256(-wUSDLDelta));
283283
usdlBalanceBefore = IERC20(wUSDL.asset()).balanceOf(address(this));
284284
_take(Currency.wrap(wUSDL.asset()), address(this), inputAmount);
285285
usdlBalanceAfter = IERC20(wUSDL.asset()).balanceOf(address(this));
@@ -319,12 +319,35 @@ contract AutoWrapper is BaseHook, DeltaResolver, Ownable2Step {
319319
) internal view returns (int256) {
320320
bool isExactInput = params.amountSpecified < 0;
321321
if (params.zeroForOne == baseCurrencyIsToken0) {
322-
// USDC -> USDL pool
323-
return isExactInput ? params.amountSpecified : getUnwrapInputRequired(uint256(params.amountSpecified));
322+
// Ex. USDC/USDL pool -> USDL
323+
// Ex. USDL/USDC pool -> USDL
324+
return isExactInput
325+
// USDC exact input:
326+
// - Swap USDC (exact input, specified) for wUSDL in USDC/wUSDL pool.
327+
// - Redeem wUSDL output shares for USDL assets.
328+
? params.amountSpecified
329+
// USDL exact output:
330+
// - Swap USDC (unspecified) for wUSDL (exact output, calculated) in USDC/wUSDL pool.
331+
// - Redeem wUSDL output shares for USDL assets.
332+
// We need to calculate the exact amount of wUSDL required
333+
// which can be redeemed for the exact amount of output USDL amount specified.
334+
// We therefore round up the required amount of wUSDL (`previewWithdraw`).
335+
: int256(wUSDL.previewWithdraw({assets: uint256(params.amountSpecified)}));
324336
} else {
325-
// USDL -> USDC ex. 5 USDL as output
326-
// WUSDL/USDC pool
327-
return isExactInput ? -(getUnwrapInputRequired(uint256(-params.amountSpecified))) : params.amountSpecified;
337+
// Ex. USDL -> USDL/USDC pool
338+
// Ex. USDL -> USDC/USDL pool
339+
return isExactInput
340+
// USDL exact input:
341+
// - Deposit USDL (exact input, specified) for wUSDL shares (calculated).
342+
// - Swap wUSDL (exact input, calculated) for USDC.
343+
// We need to calculate the exact amount of wUSDL shares
344+
// we will receive when depositing the exact amount of USDL amount specified.
345+
// We therefore round down the estimated amount of wUSDL received (`previewDeposit`).
346+
? -int256(wUSDL.previewDeposit({assets: uint256(-params.amountSpecified)}))
347+
// USDC exact output:
348+
// - Deposit USDL (specified) assets for wUSDL shares.
349+
// - Swap wUSDL shares for USDC.
350+
: params.amountSpecified;
328351
}
329352
}
330353

@@ -413,28 +436,6 @@ contract AutoWrapper is BaseHook, DeltaResolver, Ownable2Step {
413436
return wUSDL.redeem({shares: wUSDLAmount, receiver: address(this), owner: address(this)});
414437
}
415438

416-
/**
417-
* @notice Calculates USDL required to obtain a desired amount of wUSDL
418-
* @param wUSDLAmount The target amount of wUSDL needed
419-
* @return wUSDL amount of USDL required
420-
*/
421-
function getWrapInputRequired(
422-
uint256 wUSDLAmount
423-
) public view returns (int256) {
424-
return int256(wUSDL.previewMint(wUSDLAmount));
425-
}
426-
427-
/**
428-
* @notice Calculates wUSDL required to obtain a desired amount of USDL
429-
* @param usdlAmount The target amount of USDL needed
430-
* @return The amount of wUSDL required
431-
*/
432-
function getUnwrapInputRequired(
433-
uint256 usdlAmount
434-
) public view returns (int256) {
435-
return int256(wUSDL.previewWithdraw(usdlAmount));
436-
}
437-
438439
/**
439440
* @notice Sets the router for the contract, only callable by the owner
440441
* @param _router The new router

test/AutoWrapperIntegration.t.sol

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,11 +75,12 @@ contract AutoWrapperIntegrationTest is Test, AutoWrapperSetup, OperatorTestPrep
7575
}
7676

7777
function testSwapZeroForOneExactOutput() public permissionedOperators prepOperatorRegistration(true) {
78+
// USDC -> USDL
7879
PoolKey memory key = getPoolKey();
7980
string memory taskId = "unique-identifier";
8081
uint256 amountSpecified = 1e18;
8182
PredicateMessage memory message =
82-
getPredicateMessage(taskId, false, autoWrapper.getUnwrapInputRequired(amountSpecified));
83+
getPredicateMessage(taskId, false, int256(autoWrapper.wUSDL().previewWithdraw(amountSpecified)));
8384
IV4Router.ExactOutputSingleParams memory swapParams = IV4Router.ExactOutputSingleParams({
8485
poolKey: key,
8586
zeroForOne: true,
@@ -112,7 +113,7 @@ contract AutoWrapperIntegrationTest is Test, AutoWrapperSetup, OperatorTestPrep
112113
PoolKey memory key = getPoolKey();
113114
uint256 amountSpecified = 1e18;
114115
PredicateMessage memory message =
115-
getPredicateMessage(taskId, true, -autoWrapper.getUnwrapInputRequired(amountSpecified));
116+
getPredicateMessage(taskId, true, -int256(autoWrapper.wUSDL().previewDeposit(amountSpecified)));
116117
IV4Router.ExactInputSingleParams memory swapParams = IV4Router.ExactInputSingleParams({
117118
poolKey: key,
118119
zeroForOne: false,
@@ -195,7 +196,7 @@ contract AutoWrapperIntegrationTest is Test, AutoWrapperSetup, OperatorTestPrep
195196
string memory taskId = "unique-identifier";
196197
uint256 amountSpecified = 1e18;
197198
PredicateMessage memory message =
198-
getPredicateMessage(taskId, true, autoWrapper.getUnwrapInputRequired(amountSpecified));
199+
getPredicateMessage(taskId, true, int256(autoWrapper.wUSDL().previewWithdraw(amountSpecified)));
199200

200201
// change the taskId to an invalid one
201202
message.taskId = "invalid-task-id";

0 commit comments

Comments
 (0)