Skip to content

Commit 395064d

Browse files
Merge pull request #94 from lista-dao/feature/buyBack-v2
fea: buyback support pancake and other routers
2 parents d424253 + 5021332 commit 395064d

10 files changed

Lines changed: 422 additions & 75 deletions

File tree

contracts/buyback/Buyback.sol

Lines changed: 72 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], "router not whitelisted");
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,50 @@ 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) nonReentrant whenNotPaused {
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, address(this));
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+
if (!isNativeTokenIn) {
187+
IERC20(_tokenIn).safeApprove(_router, 0);
188+
}
189+
190+
uint256 actualAmountIn = beforeTokenIn - _getTokenBalance(_tokenIn, address(this));
191+
uint256 actualAmountOut = _getTokenBalance(_tokenOut, address(this)) - beforeTokenOut;
192+
193+
require(actualAmountIn <= _amountIn, "exceed amount in");
194+
require(actualAmountOut >= _amountOutMin, "not enough profit");
195+
196+
IERC20(_tokenOut).safeTransfer(receiver, actualAmountOut);
197+
198+
emit BoughtBack(_router, _tokenIn, _tokenOut, actualAmountIn, actualAmountOut);
199+
}
200+
150201
/**
151202
* @dev change receiver
152203
* @param _receiver - Address of the receiver
@@ -159,27 +210,14 @@ contract Buyback is
159210
emit ReceiverChanged(_receiver);
160211
}
161212

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);
213+
/// @dev sets the router whitelist.
214+
/// @param _router The address of the router.
215+
/// @param status The status of the router.
216+
function setRouterWhitelist(address _router, bool status) external onlyRole(MANAGER) {
217+
require(_router != address(0), "Invalid router address");
218+
require(routerWhitelist[_router] != status, "whitelist same status");
219+
routerWhitelist[_router] = status;
220+
emit RouterChanged(_router, status);
183221
}
184222

185223
/**
@@ -239,4 +277,12 @@ contract Buyback is
239277
// /* ============ Internal Functions ============ */
240278

241279
function _authorizeUpgrade(address newImplementation) internal override onlyRole(DEFAULT_ADMIN_ROLE) {}
280+
281+
function _getTokenBalance(address _token, address account) internal view returns (uint256) {
282+
if (_token == SWAP_NATIVE_TOKEN_ADDRESS) {
283+
return account.balance;
284+
} else {
285+
return IERC20(_token).balanceOf(account);
286+
}
287+
}
242288
}

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: 112 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,37 @@ import "../buyback/library/RevertReasonParser.sol";
1515
* @dev result of swap will be sent to receiver address to distribute to users
1616
*/
1717
contract ListaAutoBuyback is Initializable, AccessControlUpgradeable {
18-
1918
using SafeERC20 for IERC20;
2019

20+
struct SwapDescription {
21+
address srcToken;
22+
address dstToken;
23+
address payable srcReceiver;
24+
address payable dstReceiver;
25+
uint256 amount;
26+
uint256 minReturnAmount;
27+
uint256 flags;
28+
}
29+
2130
event BoughtBack(address indexed tokenIn, uint256 amountIn, uint256 amountOut);
31+
event BoughtBack(
32+
address indexed pair,
33+
address indexed tokenIn,
34+
address indexed tokenOut,
35+
uint256 amountIn,
36+
uint256 amountOut
37+
);
2238

2339
event ReceiverChanged(address indexed receiver);
24-
2540
event RouterChanged(address indexed router, bool added);
41+
event TokenWhitelistChanged(address indexed token, bool added);
42+
event AdminTransfer(address token, uint256 amount);
43+
2644

2745
bytes32 public constant BOT = keccak256("BOT");
2846

2947
bytes4 public constant SWAP_FUNCTION_SELECTOR = bytes4(keccak256("swap(address,(address,address,address,address,uint256,uint256,uint256),bytes)"));
48+
address public constant SWAP_NATIVE_TOKEN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
3049

3150
// The offset of the dstReceiver in the call data
3251
// 4 bytes for the function selector + 32 bytes for executor + 32 bytes for srcToken +
@@ -38,15 +57,19 @@ contract ListaAutoBuyback is Initializable, AccessControlUpgradeable {
3857

3958
address public defaultReceiver;
4059

41-
mapping(address => bool) public oneInchRouterWhitelist;
60+
mapping(address => bool) public routerWhitelist;
4261

4362
mapping(uint256 => uint256) public dailyBought;
4463

64+
mapping(address => bool) public tokenWhitelist;
65+
4566
/// @custom:oz-upgrades-unsafe-allow constructor
4667
constructor() {
4768
_disableInitializers();
4869
}
4970

71+
receive() external payable {}
72+
5073
function initialize(
5174
address _admin,
5275
address _bot,
@@ -62,7 +85,7 @@ contract ListaAutoBuyback is Initializable, AccessControlUpgradeable {
6285
_setupRole(BOT, _bot);
6386

6487
defaultReceiver = _initReceiver;
65-
oneInchRouterWhitelist[_initRouter] = true;
88+
routerWhitelist[_initRouter] = true;
6689
}
6790

6891
/**
@@ -77,8 +100,16 @@ contract ListaAutoBuyback is Initializable, AccessControlUpgradeable {
77100
onlyRole(BOT)
78101
{
79102
require(_amountIn > 0, "amountIn is zero");
80-
require(oneInchRouterWhitelist[_1inchRouter], "router not whitelisted");
103+
require(routerWhitelist[_1inchRouter], "router not whitelisted");
81104
require(_getFunctionSelector(_data) == SWAP_FUNCTION_SELECTOR, "invalid function selector of _data");
105+
require(tokenWhitelist[_tokenIn], "token in not whitelisted");
106+
(, SwapDescription memory swapDesc, ) = abi.decode(_data[4:], (address, SwapDescription, bytes));
107+
108+
require(_tokenIn == swapDesc.srcToken, "Invalid swap input token");
109+
require(tokenWhitelist[swapDesc.dstToken], "token out not whitelisted");
110+
require(address(swapDesc.dstReceiver) == defaultReceiver, "Invalid receiver");
111+
require(swapDesc.amount > 0, "Invalid swap input amount");
112+
82113
require(_extractDstReceiver(_data) == defaultReceiver, "invalid dst receiver of _data");
83114
require(IERC20(_tokenIn).balanceOf(address(this)) >= _amountIn, "insufficient balance");
84115
// Approves the 1inch router contract to spend the specified amount of _tokenIn
@@ -92,14 +123,64 @@ contract ListaAutoBuyback is Initializable, AccessControlUpgradeable {
92123
}
93124

94125
(uint256 amountOut,) = abi.decode(result, (uint256, uint256));
126+
require(amountOut >= swapDesc.minReturnAmount, "insufficient output amount");
95127
uint256 today = block.timestamp / DAY * DAY;
96128
dailyBought[today] = dailyBought[today] + amountOut;
97129

98130
emit BoughtBack(_tokenIn, _amountIn, amountOut);
99131
}
100132

133+
/// @dev buy back tokens using router
134+
/// @param _router The address of the router.
135+
/// @param _tokenIn The address of the input token.
136+
/// @param _tokenOut The address of the output token.
137+
/// @param _amountIn The amount to sell.
138+
/// @param _amountOutMin The minimum amount to receive.
139+
/// @param _swapData The swap data.
140+
function buyback(
141+
address _router,
142+
address _tokenIn,
143+
address _tokenOut,
144+
uint256 _amountIn,
145+
uint256 _amountOutMin,
146+
bytes calldata _swapData
147+
) external onlyRole(BOT) {
148+
require(tokenWhitelist[_tokenIn], "token not whitelisted");
149+
require(tokenWhitelist[_tokenOut], "token not whitelisted");
150+
require(routerWhitelist[_router], "router not whitelisted");
151+
152+
uint256 beforeTokenIn = _getTokenBalance(_tokenIn, address(this));
153+
uint256 beforeTokenOut = _getTokenBalance(_tokenOut, address(this));
154+
155+
bool isNativeTokenIn = (_tokenIn == SWAP_NATIVE_TOKEN_ADDRESS);
156+
if (!isNativeTokenIn) {
157+
IERC20(_tokenIn).safeApprove(_router, _amountIn);
158+
}
159+
(bool success, ) = _router.call{value: isNativeTokenIn ? _amountIn : 0}(_swapData);
160+
require(success, "swap failed");
161+
if (!isNativeTokenIn) {
162+
IERC20(_tokenIn).safeApprove(_router, 0);
163+
}
164+
165+
uint256 actualAmountIn = beforeTokenIn - _getTokenBalance(_tokenIn, address(this));
166+
uint256 actualAmountOut = _getTokenBalance(_tokenOut, address(this)) - beforeTokenOut;
167+
168+
require(actualAmountIn <= _amountIn, "exceed amount in");
169+
require(actualAmountOut >= _amountOutMin, "not enough profit");
170+
171+
IERC20(_tokenOut).safeTransfer(defaultReceiver, actualAmountOut);
172+
173+
emit BoughtBack(_router, _tokenIn, _tokenOut, actualAmountIn, actualAmountOut);
174+
}
175+
101176
function adminTransfer(address _token, uint256 _amount) external onlyRole(DEFAULT_ADMIN_ROLE) {
102-
IERC20(_token).safeTransfer(msg.sender, _amount);
177+
if (_token == SWAP_NATIVE_TOKEN_ADDRESS) {
178+
(bool success, ) = payable(msg.sender).call{ value: _amount }("");
179+
require(success, "Withdraw failed");
180+
} else {
181+
IERC20(_token).safeTransfer(msg.sender, _amount);
182+
}
183+
emit AdminTransfer(_token, _amount);
103184
}
104185

105186
function changeDefaultReceiver(address _receiver) external onlyRole(DEFAULT_ADMIN_ROLE) {
@@ -110,20 +191,27 @@ contract ListaAutoBuyback is Initializable, AccessControlUpgradeable {
110191
emit ReceiverChanged(defaultReceiver);
111192
}
112193

113-
function add1InchRouterWhitelist(address _router) external onlyRole(DEFAULT_ADMIN_ROLE) {
114-
require(!oneInchRouterWhitelist[_router], "router already whitelisted");
115-
116-
oneInchRouterWhitelist[_router] = true;
117-
emit RouterChanged(_router, true);
194+
/// @dev sets the router whitelist.
195+
/// @param _router The address of the router.
196+
/// @param status The status of the router.
197+
function setRouterWhitelist(address _router, bool status) external onlyRole(DEFAULT_ADMIN_ROLE) {
198+
require(_router != address(0), "Invalid router address");
199+
require(routerWhitelist[_router] != status, "whitelist same status");
200+
routerWhitelist[_router] = status;
201+
emit RouterChanged(_router, status);
118202
}
119203

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);
204+
/// @dev sets the token whitelist.
205+
/// @param token The address of the token.
206+
/// @param status The status of the token.
207+
function setTokenWhitelist(address token, bool status) external onlyRole(DEFAULT_ADMIN_ROLE) {
208+
require(token != address(0), "Invalid token");
209+
require(tokenWhitelist[token] != status, "whitelist same status");
210+
tokenWhitelist[token] = status;
211+
emit TokenWhitelistChanged(token, status);
125212
}
126213

214+
127215
function _getFunctionSelector(bytes calldata _data) private pure returns (bytes4) {
128216
return bytes4(_data[0:4]);
129217
}
@@ -135,4 +223,12 @@ contract ListaAutoBuyback is Initializable, AccessControlUpgradeable {
135223
dstReceiver := calldataload(add(_data.offset, SWAP_DST_RECEIVER_OFFSET))
136224
}
137225
}
226+
227+
function _getTokenBalance(address _token, address account) internal view returns (uint256) {
228+
if (_token == SWAP_NATIVE_TOKEN_ADDRESS) {
229+
return account.balance;
230+
} else {
231+
return IERC20(_token).balanceOf(account);
232+
}
233+
}
138234
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
pragma solidity ^0.8.10;
2+
3+
import { Script, console } from "forge-std/Script.sol";
4+
import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
5+
import { Buyback } from "../../../contracts/buyback/Buyback.sol";
6+
7+
contract BuybackDeploy is Script {
8+
function run() public {
9+
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
10+
address deployer = vm.addr(deployerPrivateKey);
11+
console.log("Deployer: ", deployer);
12+
vm.startBroadcast(deployerPrivateKey);
13+
14+
// Deploy implementation
15+
Buyback impl = new Buyback();
16+
console.log("Implementation: ", address(impl));
17+
18+
vm.stopBroadcast();
19+
}
20+
}

0 commit comments

Comments
 (0)