Skip to content

Commit 0af9943

Browse files
authored
feat: Balancer V3 router adapter for v3.0 (#149)
* feat: balancer v3 router adapter * fix: minor fixes * fix: add multicall library * fix: small change for consistency * fix: balancer v3 audit smallfixes * fix: small balancer v3 fixes
1 parent f470525 commit 0af9943

File tree

8 files changed

+448
-5
lines changed

8 files changed

+448
-5
lines changed
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
// SPDX-License-Identifier: GPL-2.0-or-later
2+
// Gearbox Protocol. Generalized leverage for DeFi protocols
3+
// (c) Gearbox Foundation, 2024.
4+
pragma solidity ^0.8.17;
5+
6+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
7+
import {RAY} from "@gearbox-protocol/core-v2/contracts/libraries/Constants.sol";
8+
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
9+
10+
import {AbstractAdapter} from "../AbstractAdapter.sol";
11+
import {AdapterType} from "@gearbox-protocol/sdk-gov/contracts/AdapterType.sol";
12+
13+
import {IBalancerV3Router} from "../../integrations/balancer/IBalancerV3Router.sol";
14+
import {IBalancerV3Pool} from "../../integrations/balancer/IBalancerV3Pool.sol";
15+
import {IBalancerV3RouterAdapter} from "../../interfaces/balancer/IBalancerV3RouterAdapter.sol";
16+
17+
/// @title Balancer V3 Router adapter
18+
/// @notice Implements logic allowing CAs to perform swaps via Balancer V3
19+
contract BalancerV3RouterAdapter is AbstractAdapter, IBalancerV3RouterAdapter {
20+
using EnumerableSet for EnumerableSet.AddressSet;
21+
22+
AdapterType public constant override _gearboxAdapterType = AdapterType.BALANCER_V3_ROUTER;
23+
uint16 public constant override _gearboxAdapterVersion = 3_00;
24+
25+
/// @dev Set of all pools that are currently allowed
26+
EnumerableSet.AddressSet internal _allowedPools;
27+
28+
/// @notice Constructor
29+
/// @param _creditManager Credit manager address
30+
/// @param _router Balancer V3 Router address
31+
constructor(address _creditManager, address _router) AbstractAdapter(_creditManager, _router) {}
32+
33+
/// @notice Swaps given amount of input token for output token through a single pool
34+
/// @param pool Pool address
35+
/// @param tokenIn Input token
36+
/// @param tokenOut Output token
37+
/// @param exactAmountIn Exact amount of input token to swap
38+
/// @param minAmountOut Minimum amount of output token to receive
39+
/// @param deadline Deadline for the swap
40+
/// @dev wethIsEth and userData are ignored, since they are always set to false and empty
41+
function swapSingleTokenExactIn(
42+
address pool,
43+
IERC20 tokenIn,
44+
IERC20 tokenOut,
45+
uint256 exactAmountIn,
46+
uint256 minAmountOut,
47+
uint256 deadline,
48+
bool,
49+
bytes calldata
50+
) external override creditFacadeOnly returns (uint256 tokensToEnable, uint256 tokensToDisable) {
51+
if (!isPoolAllowed(pool)) revert InvalidPoolException();
52+
53+
(tokensToEnable, tokensToDisable,) = _executeSwapSafeApprove(
54+
address(tokenIn),
55+
address(tokenOut),
56+
abi.encodeCall(
57+
IBalancerV3Router.swapSingleTokenExactIn,
58+
(pool, tokenIn, tokenOut, exactAmountIn, minAmountOut, deadline, false, "")
59+
),
60+
false
61+
);
62+
}
63+
64+
/// @notice Swaps all balance of input token for output token through a single pool, except the specified amount
65+
/// @param pool Pool address
66+
/// @param tokenIn Input token
67+
/// @param tokenOut Output token
68+
/// @param leftoverAmount Amount of input token to keep
69+
/// @param rateMinRAY Minimum exchange rate [RAY]
70+
/// @param deadline Deadline for the swap
71+
function swapSingleTokenDiffIn(
72+
address pool,
73+
IERC20 tokenIn,
74+
IERC20 tokenOut,
75+
uint256 leftoverAmount,
76+
uint256 rateMinRAY,
77+
uint256 deadline
78+
) external override creditFacadeOnly returns (uint256 tokensToEnable, uint256 tokensToDisable) {
79+
if (!isPoolAllowed(pool)) revert InvalidPoolException();
80+
81+
address creditAccount = _creditAccount();
82+
83+
uint256 amount = tokenIn.balanceOf(creditAccount);
84+
if (amount <= leftoverAmount) return (0, 0);
85+
unchecked {
86+
amount -= leftoverAmount;
87+
}
88+
89+
(tokensToEnable, tokensToDisable,) = _executeSwapSafeApprove(
90+
address(tokenIn),
91+
address(tokenOut),
92+
abi.encodeCall(
93+
IBalancerV3Router.swapSingleTokenExactIn,
94+
(pool, tokenIn, tokenOut, amount, (amount * rateMinRAY) / RAY, deadline, false, "")
95+
),
96+
leftoverAmount <= 1
97+
);
98+
}
99+
100+
// ------------- //
101+
// CONFIGURATION //
102+
// ------------- //
103+
104+
/// @notice Returns whether the pool is allowed to be traded through the adapter
105+
function isPoolAllowed(address pool) public view override returns (bool) {
106+
return _allowedPools.contains(pool);
107+
}
108+
109+
/// @notice Returns the list of all pools that were ever allowed in this adapter
110+
function getAllowedPools() external view override returns (address[] memory pools) {
111+
return _allowedPools.values();
112+
}
113+
114+
/// @notice Sets status for a batch of pools
115+
/// @param pools Array of pool addresses
116+
/// @param statuses Array of pool statuses
117+
function setPoolStatusBatch(address[] calldata pools, bool[] calldata statuses)
118+
external
119+
override
120+
configuratorOnly
121+
{
122+
uint256 len = pools.length;
123+
if (len != statuses.length) revert InvalidLengthException();
124+
unchecked {
125+
for (uint256 i; i < len; ++i) {
126+
address pool = pools[i];
127+
bool status = statuses[i];
128+
129+
if (status) {
130+
_allowedPools.add(pool);
131+
} else {
132+
_allowedPools.remove(pool);
133+
}
134+
emit SetPoolStatus(pool, status);
135+
}
136+
}
137+
}
138+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.10;
3+
4+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
5+
6+
interface IBalancerV3Pool {
7+
function getTokens() external view returns (IERC20[] memory);
8+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.10;
3+
4+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
5+
6+
interface IBalancerV3Router {
7+
function swapSingleTokenExactIn(
8+
address pool,
9+
IERC20 tokenIn,
10+
IERC20 tokenOut,
11+
uint256 exactAmountIn,
12+
uint256 minAmountOut,
13+
uint256 deadline,
14+
bool wethIsEth,
15+
bytes calldata userData
16+
) external returns (uint256 amountOut);
17+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// SPDX-License-Identifier: MIT
2+
// Gearbox Protocol. Generalized leverage for DeFi protocols
3+
// (c) Gearbox Foundation, 2024.
4+
pragma solidity ^0.8.17;
5+
6+
import {IAdapter} from "@gearbox-protocol/core-v2/contracts/interfaces/IAdapter.sol";
7+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
8+
9+
interface IBalancerV3RouterAdapterEvents {
10+
/// @notice Emitted when pool status is changed
11+
event SetPoolStatus(address indexed pool, bool allowed);
12+
}
13+
14+
interface IBalancerV3RouterAdapterExceptions {
15+
/// @notice Thrown when pool and status array lengths do not match
16+
error InvalidLengthException();
17+
18+
/// @notice Thrown when trying to swap through a non-allowed pool
19+
error InvalidPoolException();
20+
}
21+
22+
/// @title Balancer V3 Router adapter interface
23+
interface IBalancerV3RouterAdapter is IAdapter, IBalancerV3RouterAdapterEvents, IBalancerV3RouterAdapterExceptions {
24+
function swapSingleTokenExactIn(
25+
address pool,
26+
IERC20 tokenIn,
27+
IERC20 tokenOut,
28+
uint256 exactAmountIn,
29+
uint256 minAmountOut,
30+
uint256 deadline,
31+
bool wethIsEth,
32+
bytes calldata userData
33+
) external returns (uint256 tokensToEnable, uint256 tokensToDisable);
34+
35+
function swapSingleTokenDiffIn(
36+
address pool,
37+
IERC20 tokenIn,
38+
IERC20 tokenOut,
39+
uint256 leftoverAmount,
40+
uint256 rateMinRAY,
41+
uint256 deadline
42+
) external returns (uint256 tokensToEnable, uint256 tokensToDisable);
43+
44+
// ------------- //
45+
// CONFIGURATION //
46+
// ------------- //
47+
48+
/// @notice Returns whether the pool is allowed to be traded through the adapter
49+
function isPoolAllowed(address pool) external view returns (bool);
50+
51+
/// @notice Returns the list of all pools that were ever allowed in this adapter
52+
function getAllowedPools() external view returns (address[] memory pools);
53+
54+
/// @notice Sets status for a batch of pools
55+
function setPoolStatusBatch(address[] calldata pools, bool[] calldata statuses) external;
56+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// SPDX-License-Identifier: MIT
2+
// Gearbox Protocol. Generalized leverage for DeFi protocols
3+
// (c) Gearbox Foundation, 2024
4+
pragma solidity ^0.8.17;
5+
6+
import {MultiCall} from "@gearbox-protocol/core-v2/contracts/libraries/MultiCall.sol";
7+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
8+
import {IBalancerV3RouterAdapter} from "../../../interfaces/balancer/IBalancerV3RouterAdapter.sol";
9+
10+
interface BalancerV3Router_Multicaller {}
11+
12+
library BalancerV3Router_Calls {
13+
function swapSingleTokenExactIn(
14+
BalancerV3Router_Multicaller c,
15+
address pool,
16+
IERC20 tokenIn,
17+
IERC20 tokenOut,
18+
uint256 exactAmountIn,
19+
uint256 minAmountOut,
20+
uint256 deadline,
21+
bool wethIsEth,
22+
bytes memory userData
23+
) internal pure returns (MultiCall memory) {
24+
return MultiCall({
25+
target: address(c),
26+
callData: abi.encodeCall(
27+
IBalancerV3RouterAdapter.swapSingleTokenExactIn,
28+
(pool, tokenIn, tokenOut, exactAmountIn, minAmountOut, deadline, wethIsEth, userData)
29+
)
30+
});
31+
}
32+
33+
function swapSingleTokenDiffIn(
34+
BalancerV3Router_Multicaller c,
35+
address pool,
36+
IERC20 tokenIn,
37+
IERC20 tokenOut,
38+
uint256 leftoverAmount,
39+
uint256 rateMinRAY,
40+
uint256 deadline
41+
) internal pure returns (MultiCall memory) {
42+
return MultiCall({
43+
target: address(c),
44+
callData: abi.encodeCall(
45+
IBalancerV3RouterAdapter.swapSingleTokenDiffIn,
46+
(pool, tokenIn, tokenOut, leftoverAmount, rateMinRAY, deadline)
47+
)
48+
});
49+
}
50+
}

0 commit comments

Comments
 (0)