diff --git a/contracts/AngstromBalancer.sol b/contracts/AngstromBalancer.sol index ea80638..5b6e98e 100644 --- a/contracts/AngstromBalancer.sol +++ b/contracts/AngstromBalancer.sol @@ -6,9 +6,7 @@ import { SignatureCheckerLib } from "solady/src/utils/SignatureCheckerLib.sol"; import { IPermit2 } from "permit2/src/interfaces/IPermit2.sol"; import { EIP712 } from "solady/src/utils/EIP712.sol"; -import { IBatchRouterQueries } from "@balancer-labs/v3-interfaces/contracts/vault/IBatchRouterQueries.sol"; import { IWETH } from "@balancer-labs/v3-interfaces/contracts/solidity-utils/misc/IWETH.sol"; -import { IBatchRouter } from "@balancer-labs/v3-interfaces/contracts/vault/IBatchRouter.sol"; import { IHooks } from "@balancer-labs/v3-interfaces/contracts/vault/IHooks.sol"; import { IVault } from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol"; import "@balancer-labs/v3-interfaces/contracts/vault/BatchRouterTypes.sol"; @@ -17,8 +15,16 @@ import "@balancer-labs/v3-interfaces/contracts/vault/VaultTypes.sol"; import { EVMCallModeHelpers } from "@balancer-labs/v3-solidity-utils/contracts/helpers/EVMCallModeHelpers.sol"; import { OwnableAuthentication } from "@balancer-labs/v3-standalone-utils/contracts/OwnableAuthentication.sol"; import { BatchRouterHooks } from "@balancer-labs/v3-vault/contracts/BatchRouterHooks.sol"; +import { + TransientEnumerableSet +} from "@balancer-labs/v3-solidity-utils/contracts/openzeppelin/TransientEnumerableSet.sol"; +import { + TransientStorageHelpers +} from "@balancer-labs/v3-solidity-utils/contracts/helpers/TransientStorageHelpers.sol"; import { BaseHooks } from "@balancer-labs/v3-vault/contracts/BaseHooks.sol"; +import { IAngstromBalancer } from "./interfaces/IAngstromBalancer.sol"; + /** * @notice Angstrom Router and Hook, used to trade against Angstrom pools. * @dev This contract is a combination of a batch router and a hook, designed to work with pools traded primarily on @@ -63,7 +69,10 @@ import { BaseHooks } from "@balancer-labs/v3-vault/contracts/BaseHooks.sol"; * * See [this diagram](https://drive.google.com/file/d/1A4kNi0ocI_V8tWcy3ruGNf-AaoP04bmR/view?usp=sharing). */ -contract AngstromBalancer is IBatchRouter, BatchRouterHooks, OwnableAuthentication, BaseHooks, EIP712 { +contract AngstromBalancer is IAngstromBalancer, BatchRouterHooks, OwnableAuthentication, BaseHooks, EIP712 { + using TransientEnumerableSet for TransientEnumerableSet.AddressSet; + using TransientStorageHelpers for *; + /// @dev `keccak256("AttestAngstromBlockEmpty(uint64 block_number)")`. uint256 internal constant _ATTEST_EMPTY_BLOCK_TYPE_HASH = 0x3f25e551746414ff93f076a7dd83828ff53735b39366c74015637e004fcb0223; @@ -76,6 +85,12 @@ contract AngstromBalancer is IBatchRouter, BatchRouterHooks, OwnableAuthenticati uint256 internal constant _SWAP_EXACT_OUT_TYPE_HASH = 0xb26cc9223a5f7a414a15401ce11a9ef78c5c9daf4bc40c11e20d3f53cb9d79a5; + /** + * @dev `keccak256("ToBSwap(address tokenIn,address tokenOut,uint256 exactAmountIn,uint256 exactAmountOut,address + * payer,uint64 block_number)")`. + */ + uint256 internal constant _TOB_SWAP_TYPE_HASH = 0x33ea2c5351079a10fb30dea7d5e13a7b1a184012f4f990b1ce23e7a65822518f; + uint256 internal constant _MINIMUM_USER_DATA_LENGTH = 20; /// @dev Set of active Angstrom validator nodes, authorized to unlock this contract for operations. @@ -84,47 +99,6 @@ contract AngstromBalancer is IBatchRouter, BatchRouterHooks, OwnableAuthenticati /// @dev The currently "unlocked" block. The contract is locked if the current block does not equal this number. uint256 internal _lastUnlockBlockNumber; - /** - * @notice This contract can only be unlocked once per block. - * @dev This should not happen, but could if an Angstrom validator manually unlocks the contract twice, or manually - * unlocks in the same block after the Angstrom bundle has been executed, or if there is more than one direct swap - * in the bundle. - */ - error OnlyOncePerBlock(); - - /** - * @notice An account attempted to unlock this contract that was not a registered Angstrom validator. - * @dev The node must be registered as an Angstrom node to unlock the contract for operations, either directly or - * by executing a permissioned operation. This can also occur for a valid signature, if the node address is - * unregistered. - */ - error NotNode(); - - /** - * @notice The signature provided on a swap or liquidity operation was invalid. - * @dev The user provided a signature of the correct length, and the node address is registered, but the hashed - * message is wrong. - */ - error InvalidSignature(); - - /** - * @notice The node was already registered. - * @dev The node was already registered as an Angstrom node - */ - error NodeAlreadyRegistered(); - - /** - * @notice The node was not registered. - * @dev The node was not registered as an Angstrom node - */ - error NodeNotRegistered(); - - /// @notice A node was registered and is allowed to unlock Angstrom pools. - event NodeRegistered(address indexed node); - - /// @notice A node was deregistered and is no longer able to unlock Angstrom pools. - event NodeDeregistered(address indexed node); - modifier onlyValidatorNode() { // Only Validators can call direct swaps on this router. _ensureRegisteredNode(msg.sender); @@ -136,11 +110,6 @@ contract AngstromBalancer is IBatchRouter, BatchRouterHooks, OwnableAuthenticati _; } - modifier withValidUserData(bytes calldata userData) { - _ensureUserData(userData); - _; - } - constructor( IVault vault, IWETH weth, @@ -154,181 +123,102 @@ contract AngstromBalancer is IBatchRouter, BatchRouterHooks, OwnableAuthenticati Swaps ***************************************************************************/ - /// @inheritdoc IBatchRouter - function swapExactIn( - SwapPathExactAmountIn[] memory paths, + /// @inheritdoc IAngstromBalancer + function swapExactInAngstrom( + SwapPathExactAmountIn[] memory pathsToB, + ToBSwapData[] memory tobSwaps, uint256 deadline, bool wethIsEth, bytes calldata userData - ) - external - payable - onlyValidatorNode - onlyWhenLocked - saveSender(msg.sender) - returns (uint256[] memory pathAmountsOut, address[] memory tokensOut, uint256[] memory amountsOut) - { + ) external payable onlyValidatorNode onlyWhenLocked saveSender(msg.sender) { _unlockAngstrom(); - return - abi.decode( - _vault.unlock( - abi.encodeCall( - AngstromBalancer.swapExactInAngstromHook, - SwapExactInHookParams({ - sender: msg.sender, - paths: paths, - deadline: deadline, - wethIsEth: wethIsEth, - userData: userData - }) - ) - ), - (uint256[], address[], uint256[]) - ); + _vault.unlock( + abi.encodeCall( + AngstromBalancer.swapExactInAngstromHook, + ( + SwapExactInHookParams({ + sender: msg.sender, + paths: pathsToB, + deadline: deadline, + wethIsEth: wethIsEth, + userData: userData + }), + tobSwaps + ) + ) + ); } function swapExactInAngstromHook( - SwapExactInHookParams calldata params + SwapExactInHookParams calldata params, + ToBSwapData[] calldata tobSwaps ) external nonReentrant onlyVault - withValidUserData(params.userData) returns (uint256[] memory pathAmountsOut, address[] memory tokensOut, uint256[] memory amountsOut) { - bytes32 digest = _computeDigestSwapExactIn(params.paths); + for (uint256 i = 0; i < tobSwaps.length; i++) { + bytes32 digest = _computeDigestToB(tobSwaps[i]); - // This reverts if the signature is invalid. - address payer = _extractPayerWithValidSignature(digest, params.userData); + if (SignatureCheckerLib.isValidSignatureNow(tobSwaps[i].payer, digest, tobSwaps[i].signature) == false) { + revert InvalidSignature(); + } + } (pathAmountsOut, tokensOut, amountsOut) = _swapExactInHook(params); - _settlePaths(payer, params.wethIsEth); + _settleToBPath(tobSwaps, params.wethIsEth); } - /// @inheritdoc IBatchRouter - function swapExactOut( - SwapPathExactAmountOut[] memory paths, + /// @inheritdoc IAngstromBalancer + function swapExactOutAngstrom( + SwapPathExactAmountOut[] memory pathsToB, + ToBSwapData[] memory tobSwaps, uint256 deadline, bool wethIsEth, bytes calldata userData - ) - external - payable - onlyValidatorNode - onlyWhenLocked - saveSender(msg.sender) - returns (uint256[] memory pathAmountsIn, address[] memory tokensIn, uint256[] memory amountsIn) - { + ) external payable onlyValidatorNode onlyWhenLocked saveSender(msg.sender) { _unlockAngstrom(); - return - abi.decode( - _vault.unlock( - abi.encodeCall( - AngstromBalancer.swapExactOutAngstromHook, - SwapExactOutHookParams({ - sender: msg.sender, - paths: paths, - deadline: deadline, - wethIsEth: wethIsEth, - userData: userData - }) - ) - ), - (uint256[], address[], uint256[]) - ); + _vault.unlock( + abi.encodeCall( + AngstromBalancer.swapExactOutAngstromHook, + ( + SwapExactOutHookParams({ + sender: msg.sender, + paths: pathsToB, + deadline: deadline, + wethIsEth: wethIsEth, + userData: userData + }), + tobSwaps + ) + ) + ); } function swapExactOutAngstromHook( - SwapExactOutHookParams calldata params + SwapExactOutHookParams calldata params, + ToBSwapData[] calldata tobSwaps ) external nonReentrant onlyVault - withValidUserData(params.userData) returns (uint256[] memory pathAmountsIn, address[] memory tokensIn, uint256[] memory amountsIn) { - bytes32 digest = _computeDigestSwapExactOut(params.paths); - - // This reverts if the signature is invalid. - address payer = _extractPayerWithValidSignature(digest, params.userData); + for (uint256 i = 0; i < tobSwaps.length; i++) { + bytes32 digest = _computeDigestToB(tobSwaps[i]); - (pathAmountsIn, tokensIn, amountsIn) = _swapExactOutHook(params); - - _settlePaths(payer, params.wethIsEth); - } - - /*************************************************************************** - Queries - ***************************************************************************/ - - // Note that queries do not require coordination with Angstrom, and can be called by anyone at any time. - // We include them here to satisfy the IBatchRouter interface. - - /// @inheritdoc IBatchRouterQueries - function querySwapExactIn( - SwapPathExactAmountIn[] memory paths, - address sender, - bytes calldata userData - ) - external - saveSender(sender) - returns (uint256[] memory pathAmountsOut, address[] memory tokensOut, uint256[] memory amountsOut) - { - for (uint256 i = 0; i < paths.length; ++i) { - paths[i].minAmountOut = 0; + if (SignatureCheckerLib.isValidSignatureNow(tobSwaps[i].payer, digest, tobSwaps[i].signature) == false) { + revert InvalidSignature(); + } } - return - abi.decode( - _vault.quote( - abi.encodeCall( - BatchRouterHooks.querySwapExactInHook, - SwapExactInHookParams({ - sender: address(this), - paths: paths, - deadline: type(uint256).max, - wethIsEth: false, - userData: userData - }) - ) - ), - (uint256[], address[], uint256[]) - ); - } - - /// @inheritdoc IBatchRouterQueries - function querySwapExactOut( - SwapPathExactAmountOut[] memory paths, - address sender, - bytes calldata userData - ) - external - saveSender(sender) - returns (uint256[] memory pathAmountsIn, address[] memory tokensIn, uint256[] memory amountsIn) - { - for (uint256 i = 0; i < paths.length; ++i) { - paths[i].maxAmountIn = _MAX_AMOUNT; - } + (pathAmountsIn, tokensIn, amountsIn) = _swapExactOutHook(params); - return - abi.decode( - _vault.quote( - abi.encodeCall( - BatchRouterHooks.querySwapExactOutHook, - SwapExactOutHookParams({ - sender: address(this), - paths: paths, - deadline: type(uint256).max, - wethIsEth: false, - userData: userData - }) - ) - ), - (uint256[], address[], uint256[]) - ); + _settleToBPath(tobSwaps, params.wethIsEth); } /*************************************************************************** @@ -582,7 +472,7 @@ contract AngstromBalancer is IBatchRouter, BatchRouterHooks, OwnableAuthenticati return keccak256(abi.encode(path.tokenIn, stepsHash, path.maxAmountIn, path.exactAmountOut)); } - function _getDigest() internal view returns (bytes32) { + function _computeDigestEmptyAttestation() internal view returns (bytes32) { bytes32 structHash = _computeStructHashWithBlockNumber( _ATTEST_EMPTY_BLOCK_TYPE_HASH, bytes32(0) // no content for empty attestation @@ -590,6 +480,25 @@ contract AngstromBalancer is IBatchRouter, BatchRouterHooks, OwnableAuthenticati return _hashTypedData(structHash); } + function _computeDigestToB(ToBSwapData memory swapData) internal view returns (bytes32) { + bytes32 structHash; + + // solhint-disable-next-line no-inline-assembly + assembly ("memory-safe") { + let ptr := mload(0x40) + mstore(ptr, _TOB_SWAP_TYPE_HASH) + mstore(add(ptr, 0x20), mload(swapData)) // tokenIn + mstore(add(ptr, 0x40), mload(add(swapData, 0x20))) // tokenOut + mstore(add(ptr, 0x60), mload(add(swapData, 0x40))) // exactAmountIn + mstore(add(ptr, 0x80), mload(add(swapData, 0x60))) // exactAmountOut + mstore(add(ptr, 0xa0), mload(add(swapData, 0x80))) // payer + mstore(add(ptr, 0xc0), number()) // block_number + structHash := keccak256(ptr, 0xe0) + } + + return _hashTypedData(structHash); + } + // The first 20 bytes of the user data is the node address; the rest is the signature. // This function separates the two so that the node signature can be verified. function _splitUserDataMemory( @@ -631,7 +540,7 @@ contract AngstromBalancer is IBatchRouter, BatchRouterHooks, OwnableAuthenticati function _ensureRegisteredNodeAndReturnDigest(address account) internal view returns (bytes32) { _ensureRegisteredNode(account); - return _getDigest(); + return _computeDigestEmptyAttestation(); } function _ensureRegisteredNode(address account) internal view { @@ -647,13 +556,6 @@ contract AngstromBalancer is IBatchRouter, BatchRouterHooks, OwnableAuthenticati } } - function _ensureUserData(bytes calldata userData) internal pure { - // Basic length validation of the user data, before splitting and signature validation. - if (userData.length < _MINIMUM_USER_DATA_LENGTH) { - revert InvalidSignature(); - } - } - function _extractPayerWithValidSignature( bytes32 digest, bytes calldata userData @@ -669,4 +571,28 @@ contract AngstromBalancer is IBatchRouter, BatchRouterHooks, OwnableAuthenticati function _unlockAngstrom() internal { _lastUnlockBlockNumber = block.number; } + + function _settleToBPath(ToBSwapData[] calldata tobSwaps, bool wethIsEth) internal { + for (uint256 i = 0; i < tobSwaps.length; ++i) { + ToBSwapData calldata swap = tobSwaps[i]; + + // Take tokens from the payer or settle if prepaid + _takeOrSettle(swap.payer, wethIsEth, swap.tokenIn, swap.exactAmountIn); + + // These parameters are not used for tobSwaps and should be erased in case other swaps happen + // in the same transaction using this router. + _currentSwapTokenInAmounts().tSet(swap.tokenIn, 0); + _currentSwapTokensIn().remove(swap.tokenIn); + + // Send tokens to the payer + _sendTokenOut(swap.payer, IERC20(swap.tokenOut), swap.exactAmountOut, wethIsEth); + + // These parameters are not used for tobSwaps and should be erased in case other swaps happen + // in the same transaction using this router. + _currentSwapTokenOutAmounts().tSet(swap.tokenOut, 0); + _currentSwapTokensOut().remove(swap.tokenOut); + } + + // TODO: Should _returnEth be implemented here? + } } diff --git a/contracts/interfaces/IAngstromBalancer.sol b/contracts/interfaces/IAngstromBalancer.sol new file mode 100644 index 0000000..88d44a1 --- /dev/null +++ b/contracts/interfaces/IAngstromBalancer.sol @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +pragma solidity ^0.8.24; + +import "@balancer-labs/v3-interfaces/contracts/vault/BatchRouterTypes.sol"; + +/** + * @notice Interface for the Angstrom Router and Hook. + * @dev This interface defines the public API for the AngstromBalancer contract. + */ +interface IAngstromBalancer { + /*************************************************************************** + Structs + ***************************************************************************/ + + /** + * @notice Data for ToB (Top of Block) swaps. + * @dev This struct contains the information needed to execute swaps on ToB. + */ + struct ToBSwapData { + address tokenIn; + address tokenOut; + uint256 exactAmountIn; + uint256 exactAmountOut; + address payer; + bytes signature; + } + + /*************************************************************************** + Errors + ***************************************************************************/ + + /** + * @notice This contract can only be unlocked once per block. + * @dev This should not happen, but could if an Angstrom validator manually unlocks the contract twice, or manually + * unlocks in the same block after the Angstrom bundle has been executed, or if there is more than one direct swap + * in the bundle. + */ + error OnlyOncePerBlock(); + + /** + * @notice An account attempted to unlock this contract that was not a registered Angstrom validator. + * @dev The node must be registered as an Angstrom node to unlock the contract for operations, either directly or + * by executing a permissioned operation. This can also occur for a valid signature, if the node address is + * unregistered. + */ + error NotNode(); + + /** + * @notice The signature provided on a swap or liquidity operation was invalid. + * @dev The user provided a signature of the correct length, and the node address is registered, but the hashed + * message is wrong. + */ + error InvalidSignature(); + + /** + * @notice The node was already registered. + * @dev The node was already registered as an Angstrom node + */ + error NodeAlreadyRegistered(); + + /** + * @notice The node was not registered. + * @dev The node was not registered as an Angstrom node + */ + error NodeNotRegistered(); + + /*************************************************************************** + Events + ***************************************************************************/ + + /// @notice A node was registered and is allowed to unlock Angstrom pools. + event NodeRegistered(address indexed node); + + /// @notice A node was deregistered and is no longer able to unlock Angstrom pools. + event NodeDeregistered(address indexed node); + + /*************************************************************************** + Public Functions + ***************************************************************************/ + + /** + * @notice Executes a batch swap with pathsToB and pay to swappers according to tobSwaps. + * @dev This router executes all Top of Block (ToB) swaps in a single batch router path. ToB swaps have the + * advantage of executing first in a block, so they know exactly the amount of tokens in and out. Therefore, these + * swaps can be combined to optimize gas. + * Notice that this function does not follow the swapExactIn implementation of the IBatchRouter interface. That's + * because we need to collect and return tokens to the payers in ToB swaps, and not the msg.sender. So, we need + * two separate structs: one to describe the swap path, the other to describe amounts to be distributed to each + * payer. + * Also, notice that this function does not return the tokens out and amounts out. + * + * @param pathsToB All ToB swaps combined in a single path + * @param tobSwaps Structs describing the amounts to be distributed to each payer and payer signatures + * @param deadline Deadline for the swap, after which it will revert + * @param wethIsEth If true, incoming ETH will be wrapped to WETH and outgoing WETH will be unwrapped to ETH + * @param userData Additional (optional) data required for the swap + */ + function swapExactInAngstrom( + SwapPathExactAmountIn[] memory pathsToB, + ToBSwapData[] memory tobSwaps, + uint256 deadline, + bool wethIsEth, + bytes calldata userData + ) external payable; + + /** + * @notice Executes a batch swap with pathsToB and pay to swappers according to tobSwaps. + * @dev This router executes all Top of Block (ToB) swaps in a single batch router path. ToB swaps have the + * advantage of executing first in a block, so they know exactly the amount of tokens in and out. Therefore, these + * swaps can be combined to optimize gas. + * Notice that this function does not follow the swapExactIn implementation of the IBatchRouter interface. That's + * because we need to collect and return tokens to the payers in ToB swaps, and not the msg.sender. So, we need + * two separate structs: one to describe the swap path, the other to describe amounts to be distributed to each + * payer. + * Also, notice that this function does not return the tokens out and amounts out. + * + * @param pathsToB All ToB swaps combined in a single path + * @param tobSwaps Structs describing the amounts to be distributed to each payer and payer signatures + * @param deadline Deadline for the swap, after which it will revert + * @param wethIsEth If true, incoming ETH will be wrapped to WETH and outgoing WETH will be unwrapped to ETH + * @param userData Additional (optional) data required for the swap + */ + function swapExactOutAngstrom( + SwapPathExactAmountOut[] memory pathsToB, + ToBSwapData[] memory tobSwaps, + uint256 deadline, + bool wethIsEth, + bytes calldata userData + ) external payable; + + /** + * @notice Unlocks the Angstrom network without requiring an operation. + * @dev This function is used to manually unlock the Angstrom network. To be able to do that, the node must be + * registered as an Angstrom node, and the signature must be valid (i.e., the hash must match the expected value + * per EIP-712). + * + * @param node The node unlocking the Angstrom network + * @param signature The signature of the node unlocking the Angstrom network + */ + function unlockWithEmptyAttestation(address node, bytes calldata signature) external; + + /** + * @notice Register a node that is allowed to unlock the system. + * @param node The node to register + */ + function registerNode(address node) external; + + /** + * @notice Unregister a node that is no longer allowed to unlock the system. + * @param node The node to unregister + */ + function deregisterNode(address node) external; + + /** + * @notice Get the block number the last time this contract was locked. + * @dev If it is equal to the current block number, the contract is unlocked. + * @return lastUnlockBlockNumber The block number when the contract was last locked + */ + function getLastUnlockBlockNumber() external view returns (uint256); + + /** + * @notice Check whether a given account is a registered Angstrom node. + * @param account The address being checked for node status + * @return isNode True if the address is a registered Angstrom node + */ + function isRegisteredNode(address account) external view returns (bool); +} diff --git a/contracts/test/AngstromBalancerMock.sol b/contracts/test/AngstromBalancerMock.sol index d9c20e8..840edc3 100644 --- a/contracts/test/AngstromBalancerMock.sol +++ b/contracts/test/AngstromBalancerMock.sol @@ -32,7 +32,11 @@ contract AngstromBalancerMock is AngstromBalancer { return _computeDigestSwapExactOut(paths); } - function getDigest() external view returns (bytes32) { - return _getDigest(); + function computeDigestToB(ToBSwapData memory swap) external view returns (bytes32) { + return _computeDigestToB(swap); + } + + function computeDigestEmptyAttestation() external view returns (bytes32) { + return _computeDigestEmptyAttestation(); } } diff --git a/test/foundry/AngstromBalancerUnit.t.sol b/test/foundry/AngstromBalancerUnit.t.sol index 5fc0d6c..db6cbed 100644 --- a/test/foundry/AngstromBalancerUnit.t.sol +++ b/test/foundry/AngstromBalancerUnit.t.sol @@ -6,7 +6,7 @@ import "forge-std/Test.sol"; import { IAuthentication } from "@balancer-labs/v3-interfaces/contracts/solidity-utils/helpers/IAuthentication.sol"; -import { AngstromBalancer } from "../../contracts/AngstromBalancer.sol"; +import { IAngstromBalancer } from "../../contracts/interfaces/IAngstromBalancer.sol"; import { BaseAngstromTest } from "./utils/BaseAngstromTest.sol"; contract AngstromBalancerUnitTest is BaseAngstromTest { @@ -25,13 +25,13 @@ contract AngstromBalancerUnitTest is BaseAngstromTest { function testAddNodeAlreadyRegistered() public { registerAngstromNode(bob); vm.prank(admin); - vm.expectRevert(AngstromBalancer.NodeAlreadyRegistered.selector); + vm.expectRevert(IAngstromBalancer.NodeAlreadyRegistered.selector); angstromBalancer.registerNode(bob); } function testRemoveNodeNotRegistered() public { vm.prank(admin); - vm.expectRevert(AngstromBalancer.NodeNotRegistered.selector); + vm.expectRevert(IAngstromBalancer.NodeNotRegistered.selector); angstromBalancer.deregisterNode(bob); } diff --git a/test/foundry/AngstromHook.t.sol b/test/foundry/AngstromHook.t.sol index 3959bf4..cc1ff32 100644 --- a/test/foundry/AngstromHook.t.sol +++ b/test/foundry/AngstromHook.t.sol @@ -9,7 +9,7 @@ import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { ArrayHelpers } from "@balancer-labs/v3-solidity-utils/contracts/test/ArrayHelpers.sol"; import { FixedPoint } from "@balancer-labs/v3-solidity-utils/contracts/math/FixedPoint.sol"; -import { AngstromBalancer } from "../../contracts/AngstromBalancer.sol"; +import { IAngstromBalancer } from "../../contracts/interfaces/IAngstromBalancer.sol"; import { BaseAngstromTest } from "./utils/BaseAngstromTest.sol"; contract AngstromHookTest is BaseAngstromTest { @@ -20,7 +20,7 @@ contract AngstromHookTest is BaseAngstromTest { ***************************************************************************/ function testOnBeforeSwapNotNode() public { - vm.expectRevert(AngstromBalancer.NotNode.selector); + vm.expectRevert(IAngstromBalancer.NotNode.selector); vm.prank(bob); router.swapSingleTokenExactIn(pool, dai, usdc, 1e18, 0, MAX_UINT256, false, bobUserData); } @@ -28,14 +28,14 @@ contract AngstromHookTest is BaseAngstromTest { function testOnBeforeSwapCannotSwapWhileLocked() public { // If no userData was provided (therefore, no signature), the hook treats as if the user expected the pools to // be unlocked. - vm.expectRevert(AngstromBalancer.InvalidSignature.selector); + vm.expectRevert(IAngstromBalancer.InvalidSignature.selector); vm.prank(bob); router.swapSingleTokenExactIn(pool, dai, usdc, 1e18, 0, MAX_UINT256, false, bytes("")); } function testOnBeforeSwapUnlockDataTooShort() public { // If the userData is too short, there's not enough data to represent the ECDSA signature. - vm.expectRevert(AngstromBalancer.InvalidSignature.selector); + vm.expectRevert(IAngstromBalancer.InvalidSignature.selector); vm.prank(bob); router.swapSingleTokenExactIn(pool, dai, usdc, 1e18, 0, MAX_UINT256, false, bytes("1")); } @@ -46,7 +46,7 @@ contract AngstromHookTest is BaseAngstromTest { registerAngstromNode(bob); - vm.expectRevert(AngstromBalancer.InvalidSignature.selector); + vm.expectRevert(IAngstromBalancer.InvalidSignature.selector); vm.prank(bob); router.swapSingleTokenExactIn(pool, dai, usdc, 1e18, 0, MAX_UINT256, false, userData); } @@ -69,7 +69,7 @@ contract AngstromHookTest is BaseAngstromTest { vm.prank(bob); angstromBalancer.unlockWithEmptyAttestation(bob, bobSignature); - vm.expectRevert(AngstromBalancer.OnlyOncePerBlock.selector); + vm.expectRevert(IAngstromBalancer.OnlyOncePerBlock.selector); vm.prank(bob); angstromBalancer.unlockWithEmptyAttestation(bob, bobSignature); } @@ -85,19 +85,19 @@ contract AngstromHookTest is BaseAngstromTest { } function testOnBeforeAddLiquidityUnbalancedNoSignature() public { - vm.expectRevert(AngstromBalancer.InvalidSignature.selector); + vm.expectRevert(IAngstromBalancer.InvalidSignature.selector); vm.prank(alice); router.addLiquidityUnbalanced(pool, [FixedPoint.ONE, FixedPoint.ONE].toMemoryArray(), 1e18, false, bytes("")); } function testOnBeforeAddLiquidityUnbalancedUnlockDataTooShort() public { - vm.expectRevert(AngstromBalancer.InvalidSignature.selector); + vm.expectRevert(IAngstromBalancer.InvalidSignature.selector); vm.prank(alice); router.addLiquidityUnbalanced(pool, [FixedPoint.ONE, FixedPoint.ONE].toMemoryArray(), 1e18, false, bytes("1")); } function testOnBeforeAddLiquidityUnbalancedNotNode() public { - vm.expectRevert(AngstromBalancer.NotNode.selector); + vm.expectRevert(IAngstromBalancer.NotNode.selector); vm.prank(alice); router.addLiquidityUnbalanced( pool, @@ -113,7 +113,7 @@ contract AngstromHookTest is BaseAngstromTest { registerAngstromNode(alice); - vm.expectRevert(AngstromBalancer.InvalidSignature.selector); + vm.expectRevert(IAngstromBalancer.InvalidSignature.selector); vm.prank(alice); router.addLiquidityUnbalanced(pool, [FixedPoint.ONE, FixedPoint.ONE].toMemoryArray(), 1e18, false, userData); } @@ -153,19 +153,19 @@ contract AngstromHookTest is BaseAngstromTest { } function testOnBeforeRemoveLiquidityUnbalancedNoSignature() public { - vm.expectRevert(AngstromBalancer.InvalidSignature.selector); + vm.expectRevert(IAngstromBalancer.InvalidSignature.selector); vm.prank(lp); router.removeLiquiditySingleTokenExactIn(pool, 1e18, dai, 0.1e18, false, bytes("")); } function testOnBeforeRemoveLiquidityUnbalancedUnlockDataTooShort() public { - vm.expectRevert(AngstromBalancer.InvalidSignature.selector); + vm.expectRevert(IAngstromBalancer.InvalidSignature.selector); vm.prank(lp); router.removeLiquiditySingleTokenExactIn(pool, 1e18, dai, 0.1e18, false, bytes("1")); } function testOnBeforeRemoveLiquidityUnbalancedNotNode() public { - vm.expectRevert(AngstromBalancer.NotNode.selector); + vm.expectRevert(IAngstromBalancer.NotNode.selector); vm.prank(lp); router.removeLiquiditySingleTokenExactIn(pool, 1e18, dai, 0.1e18, false, lpUserData); } @@ -175,7 +175,7 @@ contract AngstromHookTest is BaseAngstromTest { registerAngstromNode(lp); - vm.expectRevert(AngstromBalancer.InvalidSignature.selector); + vm.expectRevert(IAngstromBalancer.InvalidSignature.selector); vm.prank(lp); router.removeLiquiditySingleTokenExactIn(pool, 1e18, dai, 0.1e18, false, userData); } diff --git a/test/foundry/AngstromRouter.t.sol b/test/foundry/AngstromRouter.t.sol index 6fd46ff..9bc2d5e 100644 --- a/test/foundry/AngstromRouter.t.sol +++ b/test/foundry/AngstromRouter.t.sol @@ -6,13 +6,14 @@ import "forge-std/Test.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IVaultErrors } from "@balancer-labs/v3-interfaces/contracts/vault/IVaultErrors.sol"; import { SwapPathExactAmountIn, SwapPathExactAmountOut, SwapPathStep } from "@balancer-labs/v3-interfaces/contracts/vault/BatchRouterTypes.sol"; -import { AngstromBalancer } from "../../contracts/AngstromBalancer.sol"; +import { IAngstromBalancer } from "../../contracts/interfaces/IAngstromBalancer.sol"; import { BaseAngstromTest } from "./utils/BaseAngstromTest.sol"; contract AngstromRouterTest is BaseAngstromTest { @@ -25,30 +26,22 @@ contract AngstromRouterTest is BaseAngstromTest { function testSwapExactInNotNode() public { SwapPathExactAmountIn[] memory paths; - vm.expectRevert(AngstromBalancer.NotNode.selector); - angstromBalancer.swapExactIn(paths, MAX_UINT256, false, bytes("")); + IAngstromBalancer.ToBSwapData[] memory tobSwaps; + vm.expectRevert(IAngstromBalancer.NotNode.selector); + angstromBalancer.swapExactInAngstrom(paths, tobSwaps, MAX_UINT256, false, bytes("")); } function testSwapExactInAlreadyUnlocked() public { registerAngstromNode(bob); SwapPathExactAmountIn[] memory paths; + IAngstromBalancer.ToBSwapData[] memory tobSwaps; angstromBalancer.manualUnlockAngstrom(); - vm.expectRevert(AngstromBalancer.OnlyOncePerBlock.selector); + vm.expectRevert(IAngstromBalancer.OnlyOncePerBlock.selector); vm.prank(bob); - angstromBalancer.swapExactIn(paths, MAX_UINT256, false, bytes("")); - } - - function testSwapExactInAngstromWithoutSignature() public { - registerAngstromNode(bob); - - SwapPathExactAmountIn[] memory paths; - - vm.prank(bob); - vm.expectRevert(AngstromBalancer.InvalidSignature.selector); - angstromBalancer.swapExactIn(paths, MAX_UINT256, false, bytes("")); + angstromBalancer.swapExactInAngstrom(paths, tobSwaps, MAX_UINT256, false, bytes("")); } function testSwapExactInAngstromPathDifferentFromSignature() public { @@ -57,15 +50,24 @@ contract AngstromRouterTest is BaseAngstromTest { SwapPathStep[] memory steps = new SwapPathStep[](1); steps[0] = SwapPathStep({ pool: pool, tokenOut: usdc, isBuffer: false }); SwapPathExactAmountIn[] memory paths = new SwapPathExactAmountIn[](1); - paths[0] = SwapPathExactAmountIn({ tokenIn: dai, steps: steps, exactAmountIn: 1e18, minAmountOut: 0 }); + paths[0] = SwapPathExactAmountIn({ tokenIn: dai, steps: steps, exactAmountIn: 1.01e18, minAmountOut: 0 }); - (, bytes memory userData) = generateSignatureAndUserDataSwapExactIn(alice, aliceKey, paths); + // Path amountIn is different from signature. + IAngstromBalancer.ToBSwapData[] memory tobSwaps = new IAngstromBalancer.ToBSwapData[](1); + tobSwaps[0] = IAngstromBalancer.ToBSwapData({ + tokenIn: address(dai), + tokenOut: address(usdc), + exactAmountIn: 1e18, + exactAmountOut: 1e18, + payer: alice, + signature: bytes("") + }); - paths[0] = SwapPathExactAmountIn({ tokenIn: dai, steps: steps, exactAmountIn: 1.01e18, minAmountOut: 0 }); + tobSwaps[0].signature = generateSignatureToBSwap(aliceKey, tobSwaps[0]); vm.prank(bob); - vm.expectRevert(AngstromBalancer.InvalidSignature.selector); - angstromBalancer.swapExactIn(paths, MAX_UINT256, false, userData); + vm.expectRevert(IVaultErrors.BalanceNotSettled.selector); + angstromBalancer.swapExactInAngstrom(paths, tobSwaps, MAX_UINT256, false, bytes("")); } function testSwapExactInAngstromBlockNumberDifferentFromSignature() public { @@ -75,93 +77,104 @@ contract AngstromRouterTest is BaseAngstromTest { steps[0] = SwapPathStep({ pool: pool, tokenOut: usdc, isBuffer: false }); SwapPathExactAmountIn[] memory paths = new SwapPathExactAmountIn[](1); paths[0] = SwapPathExactAmountIn({ tokenIn: dai, steps: steps, exactAmountIn: 1e18, minAmountOut: 0 }); + IAngstromBalancer.ToBSwapData[] memory tobSwaps = new IAngstromBalancer.ToBSwapData[](1); + + tobSwaps[0] = IAngstromBalancer.ToBSwapData({ + tokenIn: address(dai), + tokenOut: address(usdc), + exactAmountIn: 1e18, + exactAmountOut: 1e18, + payer: alice, + signature: bytes("") + }); + + tobSwaps[0].signature = generateSignatureToBSwap(aliceKey, tobSwaps[0]); - (, bytes memory userData) = generateSignatureAndUserDataSwapExactIn(alice, aliceKey, paths); // Moving block number should invalidate signature vm.roll(block.number + 1); vm.prank(bob); - vm.expectRevert(AngstromBalancer.InvalidSignature.selector); - angstromBalancer.swapExactIn(paths, MAX_UINT256, false, userData); + vm.expectRevert(IAngstromBalancer.InvalidSignature.selector); + angstromBalancer.swapExactInAngstrom(paths, tobSwaps, MAX_UINT256, false, bytes("")); } function testSwapExactInUnlocksAngstrom() public { registerAngstromNode(bob); - SwapPathExactAmountIn[] memory paths; - - (, bytes memory userData) = generateSignatureAndUserDataSwapExactIn(alice, aliceKey, paths); - - vm.prank(bob); - angstromBalancer.swapExactIn(paths, MAX_UINT256, false, userData); - - assertEq( - angstromBalancer.getLastUnlockBlockNumber(), - block.number, - "Last unlock block number is not the current block number" - ); - } - - function testQuerySwapExactIn() public { SwapPathStep[] memory steps = new SwapPathStep[](1); steps[0] = SwapPathStep({ pool: pool, tokenOut: usdc, isBuffer: false }); SwapPathExactAmountIn[] memory paths = new SwapPathExactAmountIn[](1); - paths[0] = SwapPathExactAmountIn({ tokenIn: dai, steps: steps, exactAmountIn: 1e18, minAmountOut: 0 }); + paths[0] = SwapPathExactAmountIn({ tokenIn: dai, steps: steps, exactAmountIn: 3e18, minAmountOut: 0 }); - uint256 snapId = vm.snapshot(); - _prankStaticCall(); - ( - uint256[] memory pathAmountsOutQuery, - address[] memory tokensOutQuery, - uint256[] memory amountsOutQuery - ) = angstromBalancer.querySwapExactIn(paths, bob, bytes("")); - vm.revertTo(snapId); + IAngstromBalancer.ToBSwapData[] memory tobSwaps = new IAngstromBalancer.ToBSwapData[](2); + tobSwaps[0] = IAngstromBalancer.ToBSwapData({ + tokenIn: address(dai), + tokenOut: address(usdc), + exactAmountIn: 1e18, + exactAmountOut: 1e18, + payer: alice, + signature: bytes("") + }); + tobSwaps[1] = IAngstromBalancer.ToBSwapData({ + tokenIn: address(dai), + tokenOut: address(usdc), + exactAmountIn: 2e18, + exactAmountOut: 2e18, + payer: lp, + signature: bytes("") + }); - registerAngstromNode(bob); + tobSwaps[0].signature = generateSignatureToBSwap(aliceKey, tobSwaps[0]); + tobSwaps[1].signature = generateSignatureToBSwap(lpKey, tobSwaps[1]); - (, bytes memory userData) = generateSignatureAndUserDataSwapExactIn(alice, aliceKey, paths); + Balances memory balancesBefore = getBalances(lp); vm.prank(bob); - (uint256[] memory pathAmountsOut, address[] memory tokensOut, uint256[] memory amountsOut) = angstromBalancer - .swapExactIn(paths, MAX_UINT256, false, userData); + angstromBalancer.swapExactInAngstrom(paths, tobSwaps, MAX_UINT256, false, bytes("")); - assertEq(pathAmountsOut.length, pathAmountsOutQuery.length, "Path amounts out length is not equal"); - assertEq(tokensOut.length, tokensOutQuery.length, "Tokens out length is not equal"); - assertEq(amountsOut.length, amountsOutQuery.length, "Amounts out length is not equal"); + Balances memory balancesAfter = getBalances(lp); - for (uint256 i = 0; i < pathAmountsOut.length; i++) { - assertEq(pathAmountsOut[i], pathAmountsOutQuery[i], "Path amounts out is not equal"); - assertEq(tokensOut[i], tokensOutQuery[i], "Tokens out is not equal"); - assertEq(amountsOut[i], amountsOutQuery[i], "Amounts out is not equal"); - } + assertEq( + balancesAfter.lpTokens[usdcIdx], + balancesBefore.lpTokens[usdcIdx] + 2e18, + "LP USDC balance is not correct" + ); + assertEq( + balancesAfter.lpTokens[daiIdx], + balancesBefore.lpTokens[daiIdx] - 2e18, + "LP DAI balance is not correct" + ); + + assertEq( + balancesAfter.aliceTokens[usdcIdx], + balancesBefore.aliceTokens[usdcIdx] + 1e18, + "Alice USDC balance is not correct" + ); + assertEq( + balancesAfter.aliceTokens[daiIdx], + balancesBefore.aliceTokens[daiIdx] - 1e18, + "Alice DAI balance is not correct" + ); } function testSwapExactOutNotNode() public { SwapPathExactAmountOut[] memory paths; - vm.expectRevert(AngstromBalancer.NotNode.selector); - angstromBalancer.swapExactOut(paths, MAX_UINT256, false, bytes("")); + IAngstromBalancer.ToBSwapData[] memory tobSwaps; + vm.expectRevert(IAngstromBalancer.NotNode.selector); + angstromBalancer.swapExactOutAngstrom(paths, tobSwaps, MAX_UINT256, false, bytes("")); } function testSwapExactOutAlreadyUnlocked() public { registerAngstromNode(bob); SwapPathExactAmountOut[] memory paths; + IAngstromBalancer.ToBSwapData[] memory tobSwaps; angstromBalancer.manualUnlockAngstrom(); - vm.expectRevert(AngstromBalancer.OnlyOncePerBlock.selector); - vm.prank(bob); - angstromBalancer.swapExactOut(paths, MAX_UINT256, false, bytes("")); - } - - function testSwapExactOutAngstromWithoutSignature() public { - registerAngstromNode(bob); - - SwapPathExactAmountOut[] memory paths; - + vm.expectRevert(IAngstromBalancer.OnlyOncePerBlock.selector); vm.prank(bob); - vm.expectRevert(AngstromBalancer.InvalidSignature.selector); - angstromBalancer.swapExactOut(paths, MAX_UINT256, false, bytes("")); + angstromBalancer.swapExactOutAngstrom(paths, tobSwaps, MAX_UINT256, false, bytes("")); } function testSwapExactOutAngstromPathDifferentFromSignature() public { @@ -173,22 +186,26 @@ contract AngstromRouterTest is BaseAngstromTest { paths[0] = SwapPathExactAmountOut({ tokenIn: dai, steps: steps, - exactAmountOut: 1e18, + exactAmountOut: 0.99e18, maxAmountIn: MAX_UINT256 }); - (, bytes memory userData) = generateSignatureAndUserDataSwapExactOut(alice, aliceKey, paths); - - paths[0] = SwapPathExactAmountOut({ - tokenIn: dai, - steps: steps, - exactAmountOut: 1.01e18, - maxAmountIn: MAX_UINT256 + // Path amountOut is different from signature. + IAngstromBalancer.ToBSwapData[] memory tobSwaps = new IAngstromBalancer.ToBSwapData[](1); + tobSwaps[0] = IAngstromBalancer.ToBSwapData({ + tokenIn: address(dai), + tokenOut: address(usdc), + exactAmountIn: 1e18, + exactAmountOut: 1e18, + payer: alice, + signature: bytes("") }); + tobSwaps[0].signature = generateSignatureToBSwap(aliceKey, tobSwaps[0]); + vm.prank(bob); - vm.expectRevert(AngstromBalancer.InvalidSignature.selector); - angstromBalancer.swapExactOut(paths, MAX_UINT256, false, userData); + vm.expectRevert(IVaultErrors.BalanceNotSettled.selector); + angstromBalancer.swapExactOutAngstrom(paths, tobSwaps, MAX_UINT256, false, bytes("")); } function testSwapExactOutAngstromBlockNumberDifferentFromSignature() public { @@ -203,70 +220,88 @@ contract AngstromRouterTest is BaseAngstromTest { exactAmountOut: 1e18, maxAmountIn: MAX_UINT256 }); + IAngstromBalancer.ToBSwapData[] memory tobSwaps = new IAngstromBalancer.ToBSwapData[](1); + tobSwaps[0] = IAngstromBalancer.ToBSwapData({ + tokenIn: address(dai), + tokenOut: address(usdc), + exactAmountIn: 1e18, + exactAmountOut: 1e18, + payer: alice, + signature: bytes("") + }); + + tobSwaps[0].signature = generateSignatureToBSwap(aliceKey, tobSwaps[0]); - (, bytes memory userData) = generateSignatureAndUserDataSwapExactOut(alice, aliceKey, paths); // Moving block number should invalidate signature vm.roll(block.number + 1); vm.prank(bob); - vm.expectRevert(AngstromBalancer.InvalidSignature.selector); - angstromBalancer.swapExactOut(paths, MAX_UINT256, false, userData); + vm.expectRevert(IAngstromBalancer.InvalidSignature.selector); + angstromBalancer.swapExactOutAngstrom(paths, tobSwaps, MAX_UINT256, false, bytes("")); } function testSwapExactOutUnlocksRouter() public { registerAngstromNode(bob); - SwapPathExactAmountOut[] memory paths; - - (, bytes memory userData) = generateSignatureAndUserDataSwapExactOut(alice, aliceKey, paths); - - vm.prank(bob); - angstromBalancer.swapExactOut(paths, MAX_UINT256, false, userData); - - assertEq( - angstromBalancer.getLastUnlockBlockNumber(), - block.number, - "Last unlock block number is not the current block number" - ); - } - - function testQuerySwapExactOut() public { SwapPathStep[] memory steps = new SwapPathStep[](1); steps[0] = SwapPathStep({ pool: pool, tokenOut: usdc, isBuffer: false }); SwapPathExactAmountOut[] memory paths = new SwapPathExactAmountOut[](1); paths[0] = SwapPathExactAmountOut({ tokenIn: dai, steps: steps, - exactAmountOut: 1e18, + exactAmountOut: 3e18, maxAmountIn: MAX_UINT256 }); - uint256 snapId = vm.snapshot(); - _prankStaticCall(); - ( - uint256[] memory pathAmountsInQuery, - address[] memory tokensInQuery, - uint256[] memory amountsInQuery - ) = angstromBalancer.querySwapExactOut(paths, bob, bytes("")); - vm.revertTo(snapId); + IAngstromBalancer.ToBSwapData[] memory tobSwaps = new IAngstromBalancer.ToBSwapData[](2); + tobSwaps[0] = IAngstromBalancer.ToBSwapData({ + tokenIn: address(dai), + tokenOut: address(usdc), + exactAmountIn: 1e18, + exactAmountOut: 1e18, + payer: alice, + signature: bytes("") + }); + tobSwaps[1] = IAngstromBalancer.ToBSwapData({ + tokenIn: address(dai), + tokenOut: address(usdc), + exactAmountIn: 2e18, + exactAmountOut: 2e18, + payer: lp, + signature: bytes("") + }); - registerAngstromNode(bob); + tobSwaps[0].signature = generateSignatureToBSwap(aliceKey, tobSwaps[0]); + tobSwaps[1].signature = generateSignatureToBSwap(lpKey, tobSwaps[1]); - (, bytes memory userData) = generateSignatureAndUserDataSwapExactOut(alice, aliceKey, paths); + Balances memory balancesBefore = getBalances(lp); vm.prank(bob); - (uint256[] memory pathAmountsIn, address[] memory tokensIn, uint256[] memory amountsIn) = angstromBalancer - .swapExactOut(paths, MAX_UINT256, false, userData); + angstromBalancer.swapExactOutAngstrom(paths, tobSwaps, MAX_UINT256, false, bytes("")); - assertEq(pathAmountsIn.length, pathAmountsInQuery.length, "Path amounts in length is not equal"); - assertEq(tokensIn.length, tokensInQuery.length, "Tokens in length is not equal"); - assertEq(amountsIn.length, amountsInQuery.length, "Amounts in length is not equal"); + Balances memory balancesAfter = getBalances(lp); - for (uint256 i = 0; i < pathAmountsIn.length; i++) { - assertEq(pathAmountsIn[i], pathAmountsInQuery[i], "Path amounts in is not equal"); - assertEq(tokensIn[i], tokensInQuery[i], "Tokens in is not equal"); - assertEq(amountsIn[i], amountsInQuery[i], "Amounts in is not equal"); - } + assertEq( + balancesAfter.lpTokens[usdcIdx], + balancesBefore.lpTokens[usdcIdx] + 2e18, + "LP USDC balance is not correct" + ); + assertEq( + balancesAfter.lpTokens[daiIdx], + balancesBefore.lpTokens[daiIdx] - 2e18, + "LP DAI balance is not correct" + ); + + assertEq( + balancesAfter.aliceTokens[usdcIdx], + balancesBefore.aliceTokens[usdcIdx] + 1e18, + "Alice USDC balance is not correct" + ); + assertEq( + balancesAfter.aliceTokens[daiIdx], + balancesBefore.aliceTokens[daiIdx] - 1e18, + "Alice DAI balance is not correct" + ); } function _approveAngstromRouterForAllUsers() private { diff --git a/test/foundry/utils/BaseAngstromTest.sol b/test/foundry/utils/BaseAngstromTest.sol index af59475..0e4bdf8 100644 --- a/test/foundry/utils/BaseAngstromTest.sol +++ b/test/foundry/utils/BaseAngstromTest.sol @@ -8,8 +8,8 @@ import "@balancer-labs/v3-interfaces/contracts/vault/BatchRouterTypes.sol"; import { BaseVaultTest } from "@balancer-labs/v3-vault/test/foundry/utils/BaseVaultTest.sol"; +import { IAngstromBalancer } from "../../../contracts/interfaces/IAngstromBalancer.sol"; import { AngstromBalancerMock } from "../../../contracts/test/AngstromBalancerMock.sol"; -import { AngstromBalancer } from "../../../contracts/AngstromBalancer.sol"; contract BaseAngstromTest is BaseVaultTest { string private artifactsRootDir = "artifacts/"; @@ -25,12 +25,17 @@ contract BaseAngstromTest is BaseVaultTest { bytes internal lpSignature; bytes internal lpUserData; + uint256 internal usdcIdx; + uint256 internal daiIdx; + function setUp() public virtual override { BaseVaultTest.setUp(); (aliceSignature, aliceUserData) = generateSignatureAndUserDataEmptyAttestation(alice, aliceKey); (bobSignature, bobUserData) = generateSignatureAndUserDataEmptyAttestation(bob, bobKey); (lpSignature, lpUserData) = generateSignatureAndUserDataEmptyAttestation(lp, lpKey); + + (usdcIdx, daiIdx) = getSortedIndexes(address(usdc), address(dai)); } function createHook() internal override returns (address) { @@ -54,7 +59,7 @@ contract BaseAngstromTest is BaseVaultTest { function registerAngstromNode(address account) internal { vm.expectEmit(); - emit AngstromBalancer.NodeRegistered(account); + emit IAngstromBalancer.NodeRegistered(account); vm.prank(admin); angstromBalancer.registerNode(account); @@ -63,7 +68,7 @@ contract BaseAngstromTest is BaseVaultTest { function deregisterAngstromNode(address account) internal { vm.expectEmit(); - emit AngstromBalancer.NodeDeregistered(account); + emit IAngstromBalancer.NodeDeregistered(account); vm.prank(admin); angstromBalancer.deregisterNode(account); @@ -74,7 +79,7 @@ contract BaseAngstromTest is BaseVaultTest { address signer, uint256 privateKey ) internal view returns (bytes memory signature, bytes memory userData) { - bytes32 hash = angstromBalancer.getDigest(); + bytes32 hash = angstromBalancer.computeDigestEmptyAttestation(); (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, hash); signature = abi.encodePacked(r, s, v); userData = abi.encodePacked(signer, signature); @@ -102,6 +107,15 @@ contract BaseAngstromTest is BaseVaultTest { userData = abi.encodePacked(signer, signature); } + function generateSignatureToBSwap( + uint256 privateKey, + IAngstromBalancer.ToBSwapData memory swap + ) internal view returns (bytes memory signature) { + bytes32 hash = angstromBalancer.computeDigestToB(swap); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, hash); + signature = abi.encodePacked(r, s, v); + } + function _computeAngstromBalancerTestPath(string memory name) private view returns (string memory) { return string(abi.encodePacked(artifactsRootDir, "contracts/test/", name, ".sol/", name, ".json")); }