Skip to content

Commit 73de390

Browse files
authored
Node separation PR tweaks (#12)
* Add .wake to gitignore * refactor: use constant for user data length validation * refactor: factor out common code from hooks; rename hooks so that "Hook" is at the end * doc: clarify what number() means in the assembly block * docs: basically add periods for style consistency * refactor: add more efficient calldata version of _splitUserData (for the more common path) * refactor: consolidate/localize assembly code * docs: fix comments
1 parent 54c5271 commit 73de390

File tree

2 files changed

+76
-57
lines changed

2 files changed

+76
-57
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,6 @@ slither/
5555
# Fuzz tests
5656
crytic-export/
5757
medusa-corpus/
58+
59+
# Wake
60+
.wake/

contracts/AngstromBalancer.sol

Lines changed: 73 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ contract AngstromBalancer is IBatchRouter, BatchRouterHooks, OwnableAuthenticati
7676
uint256 internal constant _SWAP_EXACT_OUT_TYPE_HASH =
7777
0xb26cc9223a5f7a414a15401ce11a9ef78c5c9daf4bc40c11e20d3f53cb9d79a5;
7878

79+
uint256 internal constant _MINIMUM_USER_DATA_LENGTH = 20;
80+
7981
/// @dev Set of active Angstrom validator nodes, authorized to unlock this contract for operations.
8082
mapping(address node => bool isActive) internal _angstromValidatorNodes;
8183

@@ -134,6 +136,11 @@ contract AngstromBalancer is IBatchRouter, BatchRouterHooks, OwnableAuthenticati
134136
_;
135137
}
136138

139+
modifier withValidUserData(bytes calldata userData) {
140+
_ensureUserData(userData);
141+
_;
142+
}
143+
137144
constructor(
138145
IVault vault,
139146
IWETH weth,
@@ -167,7 +174,7 @@ contract AngstromBalancer is IBatchRouter, BatchRouterHooks, OwnableAuthenticati
167174
abi.decode(
168175
_vault.unlock(
169176
abi.encodeCall(
170-
AngstromBalancer.swapExactInHookAngstrom,
177+
AngstromBalancer.swapExactInAngstromHook,
171178
SwapExactInHookParams({
172179
sender: msg.sender,
173180
paths: paths,
@@ -181,26 +188,19 @@ contract AngstromBalancer is IBatchRouter, BatchRouterHooks, OwnableAuthenticati
181188
);
182189
}
183190

184-
function swapExactInHookAngstrom(
191+
function swapExactInAngstromHook(
185192
SwapExactInHookParams calldata params
186193
)
187194
external
188195
nonReentrant
189196
onlyVault
197+
withValidUserData(params.userData)
190198
returns (uint256[] memory pathAmountsOut, address[] memory tokensOut, uint256[] memory amountsOut)
191199
{
192-
// validate signature and sender.
193-
if (params.userData.length < 20) {
194-
revert InvalidSignature();
195-
}
196-
197-
(address payer, bytes memory signature) = _splitUserData(params.userData);
198-
// The signature looks well-formed.
199200
bytes32 digest = _computeDigestSwapExactIn(params.paths);
200201

201-
if (SignatureCheckerLib.isValidSignatureNow(payer, digest, signature) == false) {
202-
revert InvalidSignature();
203-
}
202+
// This reverts if the signature is invalid.
203+
address payer = _extractPayerWithValidSignature(digest, params.userData);
204204

205205
(pathAmountsOut, tokensOut, amountsOut) = _swapExactInHook(params);
206206

@@ -227,7 +227,7 @@ contract AngstromBalancer is IBatchRouter, BatchRouterHooks, OwnableAuthenticati
227227
abi.decode(
228228
_vault.unlock(
229229
abi.encodeCall(
230-
AngstromBalancer.swapExactOutHookAngstrom,
230+
AngstromBalancer.swapExactOutAngstromHook,
231231
SwapExactOutHookParams({
232232
sender: msg.sender,
233233
paths: paths,
@@ -241,26 +241,19 @@ contract AngstromBalancer is IBatchRouter, BatchRouterHooks, OwnableAuthenticati
241241
);
242242
}
243243

244-
function swapExactOutHookAngstrom(
244+
function swapExactOutAngstromHook(
245245
SwapExactOutHookParams calldata params
246246
)
247247
external
248248
nonReentrant
249249
onlyVault
250+
withValidUserData(params.userData)
250251
returns (uint256[] memory pathAmountsIn, address[] memory tokensIn, uint256[] memory amountsIn)
251252
{
252-
// validate signature and sender.
253-
if (params.userData.length < 20) {
254-
revert InvalidSignature();
255-
}
256-
257-
(address payer, bytes memory signature) = _splitUserData(params.userData);
258-
// The signature looks well-formed.
259253
bytes32 digest = _computeDigestSwapExactOut(params.paths);
260254

261-
if (SignatureCheckerLib.isValidSignatureNow(payer, digest, signature) == false) {
262-
revert InvalidSignature();
263-
}
255+
// This reverts if the signature is invalid.
256+
address payer = _extractPayerWithValidSignature(digest, params.userData);
264257

265258
(pathAmountsIn, tokensIn, amountsIn) = _swapExactOutHook(params);
266259

@@ -497,33 +490,25 @@ contract AngstromBalancer is IBatchRouter, BatchRouterHooks, OwnableAuthenticati
497490
function _unlockAngstromWithSignature(bytes memory userData) internal {
498491
// Queries are always allowed.
499492
if (_isAngstromUnlocked() == false && EVMCallModeHelpers.isStaticCall() == false) {
500-
if (userData.length < 20) {
493+
if (userData.length < _MINIMUM_USER_DATA_LENGTH) {
501494
revert InvalidSignature();
502495
} else {
503-
(address node, bytes memory signature) = _splitUserData(userData);
496+
(address node, bytes memory signature) = _splitUserDataMemory(userData);
504497
// The signature looks well-formed. Revert if it doesn't correspond to a registered node.
505498
_unlockWithEmptyAttestation(node, signature);
506499
}
507500
}
508501
}
509502

510503
function _computeDigestSwapExactIn(SwapPathExactAmountIn[] memory paths) internal view returns (bytes32) {
511-
// First, hash the paths array according to EIP-712
504+
// First, hash the paths array according to EIP-712.
512505
bytes32 pathsHash = _hashSwapExactInPathArray(paths);
506+
bytes32 structHash = _computeStructHashWithBlockNumber(_SWAP_EXACT_IN_TYPE_HASH, pathsHash);
513507

514-
bytes32 swapExactInStructHash;
515-
// solhint-disable-next-line no-inline-assembly
516-
assembly ("memory-safe") {
517-
let ptr := mload(0x40)
518-
mstore(ptr, _SWAP_EXACT_IN_TYPE_HASH)
519-
mstore(add(ptr, 0x20), pathsHash)
520-
mstore(add(ptr, 0x40), number())
521-
swapExactInStructHash := keccak256(ptr, 0x60)
522-
}
523-
return _hashTypedData(swapExactInStructHash);
508+
return _hashTypedData(structHash);
524509
}
525510

526-
// Helper function to hash the SwapPathExactAmountIn array
511+
// Helper function to hash the SwapPathExactAmountIn array.
527512
function _hashSwapExactInPathArray(SwapPathExactAmountIn[] memory paths) internal pure returns (bytes32) {
528513
bytes32[] memory pathHashes = new bytes32[](paths.length);
529514

@@ -536,8 +521,8 @@ contract AngstromBalancer is IBatchRouter, BatchRouterHooks, OwnableAuthenticati
536521

537522
// Helper function to hash a single SwapPathExactAmountIn
538523
function _hashSwapExactInPath(SwapPathExactAmountIn memory path) internal pure returns (bytes32) {
539-
// You'll need to define the type hash for SwapPathExactAmountIn
540-
// For now, using a simple encoding (adjust based on your EIP-712 schema)
524+
// We need to define a type hash for SwapPathExactAmountIn.
525+
// For now, using a simple encoding (adjust based on the EIP-712 schema).
541526
bytes32[] memory stepHashes = new bytes32[](path.steps.length);
542527

543528
for (uint256 i = 0; i < path.steps.length; i++) {
@@ -550,22 +535,28 @@ contract AngstromBalancer is IBatchRouter, BatchRouterHooks, OwnableAuthenticati
550535
}
551536

552537
function _computeDigestSwapExactOut(SwapPathExactAmountOut[] memory paths) internal view returns (bytes32) {
553-
// First, hash the paths array according to EIP-712
538+
// First, hash the paths array according to EIP-712.
554539
bytes32 pathsHash = _hashSwapExactOutPathArray(paths);
540+
bytes32 structHash = _computeStructHashWithBlockNumber(_SWAP_EXACT_OUT_TYPE_HASH, pathsHash);
555541

556-
bytes32 swapExactOutStructHash;
542+
return _hashTypedData(structHash);
543+
}
544+
545+
function _computeStructHashWithBlockNumber(uint256 typeHash, bytes32 contentHash) internal view returns (bytes32) {
546+
bytes32 structHash;
557547
// solhint-disable-next-line no-inline-assembly
558548
assembly ("memory-safe") {
559549
let ptr := mload(0x40)
560-
mstore(ptr, _SWAP_EXACT_OUT_TYPE_HASH)
561-
mstore(add(ptr, 0x20), pathsHash)
550+
mstore(ptr, typeHash)
551+
mstore(add(ptr, 0x20), contentHash)
562552
mstore(add(ptr, 0x40), number())
563-
swapExactOutStructHash := keccak256(ptr, 0x60)
553+
structHash := keccak256(ptr, 0x60)
564554
}
565-
return _hashTypedData(swapExactOutStructHash);
555+
556+
return structHash;
566557
}
567558

568-
// Helper function to hash the SwapPathExactAmountOut array
559+
// Helper function to hash the SwapPathExactAmountOut array.
569560
function _hashSwapExactOutPathArray(SwapPathExactAmountOut[] memory paths) internal pure returns (bytes32) {
570561
bytes32[] memory pathHashes = new bytes32[](paths.length);
571562

@@ -576,8 +567,10 @@ contract AngstromBalancer is IBatchRouter, BatchRouterHooks, OwnableAuthenticati
576567
return keccak256(abi.encodePacked(pathHashes));
577568
}
578569

579-
// Helper function to hash a single SwapPathExactAmountOut
570+
// Helper function to hash a single SwapPathExactAmountOut.
580571
function _hashSwapExactOutPath(SwapPathExactAmountOut memory path) internal pure returns (bytes32) {
572+
// We need to define a type hash for SwapPathExactAmountOut.
573+
// For now, using a simple encoding (adjust based on the EIP-712 schema).
581574
bytes32[] memory stepHashes = new bytes32[](path.steps.length);
582575

583576
for (uint256 i = 0; i < path.steps.length; i++) {
@@ -590,19 +583,16 @@ contract AngstromBalancer is IBatchRouter, BatchRouterHooks, OwnableAuthenticati
590583
}
591584

592585
function _getDigest() internal view returns (bytes32) {
593-
bytes32 attestationStructHash;
594-
// solhint-disable-next-line no-inline-assembly
595-
assembly ("memory-safe") {
596-
mstore(0x00, _ATTEST_EMPTY_BLOCK_TYPE_HASH)
597-
mstore(0x20, number())
598-
attestationStructHash := keccak256(0x00, 0x40)
599-
}
600-
return _hashTypedData(attestationStructHash);
586+
bytes32 structHash = _computeStructHashWithBlockNumber(
587+
_ATTEST_EMPTY_BLOCK_TYPE_HASH,
588+
bytes32(0) // no content for empty attestation
589+
);
590+
return _hashTypedData(structHash);
601591
}
602592

603593
// The first 20 bytes of the user data is the node address; the rest is the signature.
604594
// This function separates the two so that the node signature can be verified.
605-
function _splitUserData(
595+
function _splitUserDataMemory(
606596
bytes memory userData
607597
) internal pure returns (address extractedAddress, bytes memory hashedMessage) {
608598
uint256 signatureLength = userData.length - 20;
@@ -620,6 +610,13 @@ contract AngstromBalancer is IBatchRouter, BatchRouterHooks, OwnableAuthenticati
620610
}
621611
}
622612

613+
function _splitUserData(
614+
bytes calldata userData
615+
) internal pure returns (address extractedAddress, bytes calldata signature) {
616+
extractedAddress = address(bytes20(userData[0:20]));
617+
signature = userData[20:];
618+
}
619+
623620
// Signature passed in memory (from userData).
624621
function _unlockWithEmptyAttestation(address node, bytes memory signature) internal {
625622
bytes32 digest = _ensureRegisteredNodeAndReturnDigest(node);
@@ -650,6 +647,25 @@ contract AngstromBalancer is IBatchRouter, BatchRouterHooks, OwnableAuthenticati
650647
}
651648
}
652649

650+
function _ensureUserData(bytes calldata userData) internal pure {
651+
// Basic length validation of the user data, before splitting and signature validation.
652+
if (userData.length < _MINIMUM_USER_DATA_LENGTH) {
653+
revert InvalidSignature();
654+
}
655+
}
656+
657+
function _extractPayerWithValidSignature(
658+
bytes32 digest,
659+
bytes calldata userData
660+
) internal view returns (address payer) {
661+
bytes memory signature;
662+
(payer, signature) = _splitUserData(userData);
663+
664+
if (SignatureCheckerLib.isValidSignatureNow(payer, digest, signature) == false) {
665+
revert InvalidSignature();
666+
}
667+
}
668+
653669
function _unlockAngstrom() internal {
654670
_lastUnlockBlockNumber = block.number;
655671
}

0 commit comments

Comments
 (0)