Skip to content

Commit 7d2f5f9

Browse files
committed
fea: buyback support pancake and other routers
1 parent 2db89b3 commit 7d2f5f9

7 files changed

Lines changed: 340 additions & 70 deletions

File tree

contracts/buyback/Buyback.sol

Lines changed: 66 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ contract Buyback is
3535
address public constant SWAP_NATIVE_TOKEN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
3636

3737
/* ============ State Variables ============ */
38-
// 1Inch router whitelist
39-
mapping(address => bool) public oneInchRouterWhitelist;
38+
// swap router whitelist
39+
mapping(address => bool) public routerWhitelist;
4040
// swap input token whitelist
4141
mapping(address => bool) public tokenInWhitelist;
4242
// swap output token
@@ -48,8 +48,15 @@ contract Buyback is
4848

4949
/* ============ Events ============ */
5050
event BoughtBack(address indexed tokenIn, address indexed tokenOut, uint256 amountIn, uint256 amountOut);
51+
event BoughtBack(
52+
address indexed pair,
53+
address indexed tokenIn,
54+
address indexed tokenOut,
55+
uint256 amountIn,
56+
uint256 amountOut
57+
);
5158
event ReceiverChanged(address indexed receiver);
52-
event OneInchRouterChanged(address indexed oneInchRouter, bool added);
59+
event RouterChanged(address indexed router, bool added);
5360
event TokenInChanged(address indexed token, bool added);
5461
event EmergencyWithdraw(address token, uint256 amount);
5562

@@ -99,7 +106,7 @@ contract Buyback is
99106
_grantRole(PAUSER, _pauser);
100107
_grantRole(BOT, _bot);
101108

102-
oneInchRouterWhitelist[_1InchRouter] = true;
109+
routerWhitelist[_1InchRouter] = true;
103110
tokenOut = _tokenOut;
104111
receiver = _receiver;
105112
}
@@ -115,7 +122,7 @@ contract Buyback is
115122
address _1inchRouter,
116123
bytes calldata _data
117124
) external override onlyRole(BOT) nonReentrant whenNotPaused {
118-
require(oneInchRouterWhitelist[_1inchRouter], "Invalid 1Inch router");
125+
require(routerWhitelist[_1inchRouter], "Invalid 1Inch router");
119126
require(bytes4(_data[0:4]) == SWAP_FUNCTION_SELECTOR, "Invalid 1Inch function selector");
120127

121128
(, SwapDescription memory swapDesc, ) = abi.decode(_data[4:], (address, SwapDescription, bytes));
@@ -147,6 +154,44 @@ contract Buyback is
147154
emit BoughtBack(address(swapDesc.srcToken), address(swapDesc.dstToken), swapDesc.amount, amountOut);
148155
}
149156

157+
/// @dev buy back tokens using router
158+
/// @param router The address of the router.
159+
/// @param _tokenIn The address of the input token.
160+
/// @param _tokenOut The address of the output token.
161+
/// @param amountIn The amount to sell.
162+
/// @param amountOutMin The minimum amount to receive.
163+
/// @param swapData The swap data.
164+
function buyback(
165+
address router,
166+
address _tokenIn,
167+
address _tokenOut,
168+
uint256 amountIn,
169+
uint256 amountOutMin,
170+
bytes calldata swapData
171+
) external onlyRole(BOT) {
172+
require(tokenInWhitelist[_tokenIn], "token not whitelisted");
173+
require(tokenOut == _tokenOut, "token not whitelisted");
174+
require(routerWhitelist[router], "router not whitelisted");
175+
176+
uint256 beforeTokenIn = _getTokenBalance(_tokenIn, address(this));
177+
uint256 beforeTokenOut = _getTokenBalance(_tokenOut, receiver);
178+
179+
bool isNativeTokenIn = (_tokenIn == SWAP_NATIVE_TOKEN_ADDRESS);
180+
if (!isNativeTokenIn) {
181+
IERC20(_tokenIn).safeApprove(router, amountIn);
182+
}
183+
(bool success, ) = router.call{ value: isNativeTokenIn ? amountIn : 0 }(swapData);
184+
require(success, "swap failed");
185+
186+
uint256 actualAmountIn = beforeTokenIn - _getTokenBalance(_tokenIn, address(this));
187+
uint256 actualAmountOut = _getTokenBalance(_tokenOut, receiver) - beforeTokenOut;
188+
189+
require(actualAmountIn <= amountIn, "exceed amount in");
190+
require(actualAmountOut >= amountOutMin, "not enough profit");
191+
192+
emit BoughtBack(router, _tokenIn, _tokenOut, actualAmountIn, actualAmountOut);
193+
}
194+
150195
/**
151196
* @dev change receiver
152197
* @param _receiver - Address of the receiver
@@ -159,27 +204,14 @@ contract Buyback is
159204
emit ReceiverChanged(_receiver);
160205
}
161206

162-
/**
163-
* @dev add 1Inch router to whitelist
164-
* @param _1InchRouter - Address of the 1Inch router
165-
*/
166-
function add1InchRouterWhitelist(address _1InchRouter) external onlyRole(MANAGER) {
167-
require(_1InchRouter != address(0), "Invalid 1Inch router");
168-
require(!oneInchRouterWhitelist[_1InchRouter], "Already whitelisted");
169-
170-
oneInchRouterWhitelist[_1InchRouter] = true;
171-
emit OneInchRouterChanged(_1InchRouter, true);
172-
}
173-
174-
/**
175-
* @dev remove 1Inch router from whitelist
176-
* @param _1InchRouter - Address of the 1Inch router
177-
*/
178-
function remove1InchRouterWhitelist(address _1InchRouter) external onlyRole(MANAGER) {
179-
require(oneInchRouterWhitelist[_1InchRouter], "1Inch router is not in whitelist");
180-
181-
delete oneInchRouterWhitelist[_1InchRouter];
182-
emit OneInchRouterChanged(_1InchRouter, false);
207+
/// @dev sets the router whitelist.
208+
/// @param _router The address of the router.
209+
/// @param status The status of the router.
210+
function setRouterWhitelist(address _router, bool status) external onlyRole(MANAGER) {
211+
require(_router != address(0), "Invalid router address");
212+
require(routerWhitelist[_router] != status, "whitelist same status");
213+
routerWhitelist[_router] = status;
214+
emit RouterChanged(_router, true);
183215
}
184216

185217
/**
@@ -239,4 +271,12 @@ contract Buyback is
239271
// /* ============ Internal Functions ============ */
240272

241273
function _authorizeUpgrade(address newImplementation) internal override onlyRole(DEFAULT_ADMIN_ROLE) {}
274+
275+
function _getTokenBalance(address _token, address account) internal view returns (uint256) {
276+
if (_token == SWAP_NATIVE_TOKEN_ADDRESS) {
277+
return account.balance;
278+
} else {
279+
return IERC20(_token).balanceOf(account);
280+
}
281+
}
242282
}

contracts/buyback/interfaces/IBuyback.sol

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,13 @@ interface IBuyback {
1616
}
1717

1818
function buyback(address _1inchRouter, bytes calldata _data) external;
19+
20+
function buyback(
21+
address router,
22+
address tokenIn,
23+
address tokenOut,
24+
uint256 amountIn,
25+
uint256 amountOutMin,
26+
bytes calldata swapData
27+
) external;
1928
}

contracts/dao/ListaAutoBuyback.sol

Lines changed: 87 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,24 @@ contract ListaAutoBuyback is Initializable, AccessControlUpgradeable {
1919
using SafeERC20 for IERC20;
2020

2121
event BoughtBack(address indexed tokenIn, uint256 amountIn, uint256 amountOut);
22+
event BoughtBack(
23+
address indexed pair,
24+
address indexed tokenIn,
25+
address indexed tokenOut,
26+
uint256 amountIn,
27+
uint256 amountOut
28+
);
2229

2330
event ReceiverChanged(address indexed receiver);
24-
2531
event RouterChanged(address indexed router, bool added);
32+
event TokenWhitelistChanged(address indexed token, bool added);
33+
event AdminTransfer(address token, uint256 amount);
34+
2635

2736
bytes32 public constant BOT = keccak256("BOT");
2837

2938
bytes4 public constant SWAP_FUNCTION_SELECTOR = bytes4(keccak256("swap(address,(address,address,address,address,uint256,uint256,uint256),bytes)"));
39+
address public constant SWAP_NATIVE_TOKEN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
3040

3141
// The offset of the dstReceiver in the call data
3242
// 4 bytes for the function selector + 32 bytes for executor + 32 bytes for srcToken +
@@ -38,15 +48,19 @@ contract ListaAutoBuyback is Initializable, AccessControlUpgradeable {
3848

3949
address public defaultReceiver;
4050

41-
mapping(address => bool) public oneInchRouterWhitelist;
51+
mapping(address => bool) public routerWhitelist;
4252

4353
mapping(uint256 => uint256) public dailyBought;
4454

55+
mapping(address => bool) public tokenWhitelist;
56+
4557
/// @custom:oz-upgrades-unsafe-allow constructor
4658
constructor() {
4759
_disableInitializers();
4860
}
4961

62+
receive() external payable {}
63+
5064
function initialize(
5165
address _admin,
5266
address _bot,
@@ -62,7 +76,7 @@ contract ListaAutoBuyback is Initializable, AccessControlUpgradeable {
6276
_setupRole(BOT, _bot);
6377

6478
defaultReceiver = _initReceiver;
65-
oneInchRouterWhitelist[_initRouter] = true;
79+
routerWhitelist[_initRouter] = true;
6680
}
6781

6882
/**
@@ -77,7 +91,7 @@ contract ListaAutoBuyback is Initializable, AccessControlUpgradeable {
7791
onlyRole(BOT)
7892
{
7993
require(_amountIn > 0, "amountIn is zero");
80-
require(oneInchRouterWhitelist[_1inchRouter], "router not whitelisted");
94+
require(routerWhitelist[_1inchRouter], "router not whitelisted");
8195
require(_getFunctionSelector(_data) == SWAP_FUNCTION_SELECTOR, "invalid function selector of _data");
8296
require(_extractDstReceiver(_data) == defaultReceiver, "invalid dst receiver of _data");
8397
require(IERC20(_tokenIn).balanceOf(address(this)) >= _amountIn, "insufficient balance");
@@ -98,8 +112,52 @@ contract ListaAutoBuyback is Initializable, AccessControlUpgradeable {
98112
emit BoughtBack(_tokenIn, _amountIn, amountOut);
99113
}
100114

115+
/// @dev buy back tokens using router
116+
/// @param router The address of the router.
117+
/// @param tokenIn The address of the input token.
118+
/// @param tokenOut The address of the output token.
119+
/// @param amountIn The amount to sell.
120+
/// @param amountOutMin The minimum amount to receive.
121+
/// @param swapData The swap data.
122+
function buyback(
123+
address router,
124+
address tokenIn,
125+
address tokenOut,
126+
uint256 amountIn,
127+
uint256 amountOutMin,
128+
bytes calldata swapData
129+
) external onlyRole(BOT) {
130+
require(tokenWhitelist[tokenIn], "token not whitelisted");
131+
require(tokenWhitelist[tokenOut], "token not whitelisted");
132+
require(routerWhitelist[router], "router not whitelisted");
133+
134+
uint256 beforeTokenIn = _getTokenBalance(tokenIn, address(this));
135+
uint256 beforeTokenOut = _getTokenBalance(tokenOut, defaultReceiver);
136+
137+
bool isNativeTokenIn = (tokenIn == SWAP_NATIVE_TOKEN_ADDRESS);
138+
if (!isNativeTokenIn) {
139+
IERC20(tokenIn).safeApprove(router, amountIn);
140+
}
141+
(bool success, ) = router.call{value: isNativeTokenIn ? amountIn : 0}(swapData);
142+
require(success, "swap failed");
143+
144+
uint256 actualAmountIn = beforeTokenIn - _getTokenBalance(tokenIn, address(this));
145+
uint256 actualAmountOut = _getTokenBalance(tokenOut, defaultReceiver) - beforeTokenOut;
146+
147+
require(actualAmountIn <= amountIn, "exceed amount in");
148+
require(actualAmountOut >= amountOutMin, "not enough profit");
149+
150+
emit BoughtBack(router, tokenIn, tokenOut, actualAmountIn, actualAmountOut);
151+
}
152+
101153
function adminTransfer(address _token, uint256 _amount) external onlyRole(DEFAULT_ADMIN_ROLE) {
102-
IERC20(_token).safeTransfer(msg.sender, _amount);
154+
if (_token == SWAP_NATIVE_TOKEN_ADDRESS) {
155+
(bool success, ) = payable(msg.sender).call{ value: _amount }("");
156+
require(success, "Withdraw failed");
157+
} else {
158+
IERC20(_token).safeTransfer(msg.sender, _amount);
159+
}
160+
emit AdminTransfer(_token, _amount);
103161
}
104162

105163
function changeDefaultReceiver(address _receiver) external onlyRole(DEFAULT_ADMIN_ROLE) {
@@ -110,20 +168,27 @@ contract ListaAutoBuyback is Initializable, AccessControlUpgradeable {
110168
emit ReceiverChanged(defaultReceiver);
111169
}
112170

113-
function add1InchRouterWhitelist(address _router) external onlyRole(DEFAULT_ADMIN_ROLE) {
114-
require(!oneInchRouterWhitelist[_router], "router already whitelisted");
115-
116-
oneInchRouterWhitelist[_router] = true;
171+
/// @dev sets the router whitelist.
172+
/// @param _router The address of the router.
173+
/// @param status The status of the router.
174+
function setRouterWhitelist(address _router, bool status) external onlyRole(DEFAULT_ADMIN_ROLE) {
175+
require(_router != address(0), "Invalid router address");
176+
require(routerWhitelist[_router] != status, "whitelist same status");
177+
routerWhitelist[_router] = status;
117178
emit RouterChanged(_router, true);
118179
}
119180

120-
function remove1InchRouterWhitelist(address _router) external onlyRole(DEFAULT_ADMIN_ROLE) {
121-
require(oneInchRouterWhitelist[_router], "router not whitelisted");
122-
123-
delete oneInchRouterWhitelist[_router];
124-
emit RouterChanged(_router, false);
181+
/// @dev sets the token whitelist.
182+
/// @param token The address of the token.
183+
/// @param status The status of the token.
184+
function setTokenWhitelist(address token, bool status) external onlyRole(DEFAULT_ADMIN_ROLE) {
185+
require(token != address(0), "Invalid token");
186+
require(tokenWhitelist[token] != status, "whitelist same status");
187+
tokenWhitelist[token] = status;
188+
emit TokenWhitelistChanged(token, status);
125189
}
126190

191+
127192
function _getFunctionSelector(bytes calldata _data) private pure returns (bytes4) {
128193
return bytes4(_data[0:4]);
129194
}
@@ -135,4 +200,12 @@ contract ListaAutoBuyback is Initializable, AccessControlUpgradeable {
135200
dstReceiver := calldataload(add(_data.offset, SWAP_DST_RECEIVER_OFFSET))
136201
}
137202
}
203+
204+
function _getTokenBalance(address _token, address account) internal view returns (uint256) {
205+
if (_token == SWAP_NATIVE_TOKEN_ADDRESS) {
206+
return account.balance;
207+
} else {
208+
return IERC20(_token).balanceOf(account);
209+
}
210+
}
138211
}

0 commit comments

Comments
 (0)