Skip to content

Commit be5bdf8

Browse files
authored
feat: Balancer V3 Router adapter for v3.1 (#150)
* feat: balancer v3 router adapter for v3.1 * fix: add multicall library * fix: balancer v3 audit smallfix * fix: balancer v3 contract type
1 parent 0d9636a commit be5bdf8

File tree

6 files changed

+474
-0
lines changed

6 files changed

+474
-0
lines changed
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
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.23;
5+
6+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
7+
import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
8+
9+
import {RAY} from "@gearbox-protocol/core-v3/contracts/libraries/Constants.sol";
10+
11+
import {AbstractAdapter} from "../AbstractAdapter.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+
bytes32 public constant override contractType = "ADAPTER::BALANCER_V3_ROUTER";
23+
uint256 public constant override version = 3_10;
24+
25+
/// @dev Mapping from pool address to whether it can be traded through the adapter
26+
mapping(address => bool) internal _poolStatus;
27+
28+
/// @dev Set of all pools that are currently allowed
29+
EnumerableSet.AddressSet internal _allowedPools;
30+
31+
/// @notice Constructor
32+
/// @param _creditManager Credit manager address
33+
/// @param _router Balancer V3 Router address
34+
constructor(address _creditManager, address _router) AbstractAdapter(_creditManager, _router) {}
35+
36+
/// @notice Swaps given amount of input token for output token through a single pool
37+
/// @param pool Pool address
38+
/// @param tokenIn Input token
39+
/// @param tokenOut Output token
40+
/// @param exactAmountIn Exact amount of input token to swap
41+
/// @param minAmountOut Minimum amount of output token to receive
42+
/// @param deadline Deadline for the swap
43+
/// @dev wethIsEth and userData are ignored, since they are always set to false and empty
44+
function swapSingleTokenExactIn(
45+
address pool,
46+
IERC20 tokenIn,
47+
IERC20 tokenOut,
48+
uint256 exactAmountIn,
49+
uint256 minAmountOut,
50+
uint256 deadline,
51+
bool,
52+
bytes calldata
53+
) external override creditFacadeOnly returns (bool) {
54+
if (!isPoolAllowed(pool)) revert InvalidPoolException();
55+
56+
_executeSwapSafeApprove(
57+
address(tokenIn),
58+
abi.encodeCall(
59+
IBalancerV3Router.swapSingleTokenExactIn,
60+
(pool, tokenIn, tokenOut, exactAmountIn, minAmountOut, deadline, false, "")
61+
)
62+
);
63+
return true;
64+
}
65+
66+
/// @notice Swaps all balance of input token for output token through a single pool, except the specified amount
67+
/// @param pool Pool address
68+
/// @param tokenIn Input token
69+
/// @param tokenOut Output token
70+
/// @param leftoverAmount Amount of input token to keep
71+
/// @param rateMinRAY Minimum exchange rate [RAY]
72+
/// @param deadline Deadline for the swap
73+
function swapSingleTokenDiffIn(
74+
address pool,
75+
IERC20 tokenIn,
76+
IERC20 tokenOut,
77+
uint256 leftoverAmount,
78+
uint256 rateMinRAY,
79+
uint256 deadline
80+
) external override creditFacadeOnly returns (bool) {
81+
if (!isPoolAllowed(pool)) revert InvalidPoolException();
82+
83+
address creditAccount = _creditAccount();
84+
85+
uint256 amount = IERC20(address(tokenIn)).balanceOf(creditAccount);
86+
if (amount <= leftoverAmount) return false;
87+
unchecked {
88+
amount -= leftoverAmount;
89+
}
90+
91+
_executeSwapSafeApprove(
92+
address(tokenIn),
93+
abi.encodeCall(
94+
IBalancerV3Router.swapSingleTokenExactIn,
95+
(pool, tokenIn, tokenOut, amount, (amount * rateMinRAY) / RAY, deadline, false, "")
96+
)
97+
);
98+
return true;
99+
}
100+
101+
// ------------- //
102+
// CONFIGURATION //
103+
// ------------- //
104+
105+
/// @notice Returns whether the pool is allowed to be traded through the adapter
106+
function isPoolAllowed(address pool) public view override returns (bool) {
107+
return _poolStatus[pool];
108+
}
109+
110+
/// @notice Returns the list of all pools that were ever allowed in this adapter
111+
function getAllowedPools() public view override returns (address[] memory pools) {
112+
return _allowedPools.values();
113+
}
114+
115+
/// @notice Sets status for a batch of pools
116+
/// @param pools Array of pool addresses
117+
/// @param statuses Array of pool statuses
118+
function setPoolStatusBatch(address[] calldata pools, bool[] calldata statuses)
119+
external
120+
override
121+
configuratorOnly
122+
{
123+
uint256 len = pools.length;
124+
if (len != statuses.length) revert InvalidLengthException();
125+
unchecked {
126+
for (uint256 i; i < len; ++i) {
127+
address pool = pools[i];
128+
bool status = statuses[i];
129+
130+
if (status) {
131+
// Verify that all tokens in the pool are valid collaterals
132+
IERC20[] memory tokens = IBalancerV3Pool(pool).getTokens();
133+
for (uint256 j; j < tokens.length; ++j) {
134+
_getMaskOrRevert(address(tokens[j]));
135+
}
136+
}
137+
138+
_poolStatus[pool] = status;
139+
if (status) {
140+
_allowedPools.add(pool);
141+
} else {
142+
_allowedPools.remove(pool);
143+
}
144+
emit SetPoolStatus(pool, status);
145+
}
146+
}
147+
}
148+
149+
// ---- //
150+
// DATA //
151+
// ---- //
152+
153+
/// @notice Serialized adapter parameters
154+
function serialize() external view returns (bytes memory serializedData) {
155+
serializedData = abi.encode(creditManager, targetContract, getAllowedPools());
156+
}
157+
}
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.23;
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.23;
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.23;
5+
6+
import {IAdapter} from "@gearbox-protocol/core-v3/contracts/interfaces/base/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 (bool);
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 (bool);
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.23;
5+
6+
import {MultiCall} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3.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)