forked from scroll-tech/scroll
-
Notifications
You must be signed in to change notification settings - Fork 0
/
GasSwap.sol
201 lines (160 loc) · 6.68 KB
/
GasSwap.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
// SPDX-License-Identifier: MIT
pragma solidity =0.8.24;
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ERC2771Context} from "@openzeppelin/contracts/metatx/ERC2771Context.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/draft-IERC20Permit.sol";
import {Context} from "@openzeppelin/contracts/utils/Context.sol";
// solhint-disable no-empty-blocks
contract GasSwap is ERC2771Context, Ownable, ReentrancyGuard {
using SafeERC20 for IERC20;
using SafeERC20 for IERC20Permit;
/**********
* Events *
**********/
/// @notice Emitted when the fee ratio is updated.
/// @param feeRatio The new fee ratio, multiplied by 1e18.
event UpdateFeeRatio(uint256 feeRatio);
/// @notice Emitted when the status of target is updated.
/// @param target The address of target contract.
/// @param status The status updated.
event UpdateApprovedTarget(address target, bool status);
/*************
* Constants *
*************/
/// @dev The fee precision.
uint256 private constant PRECISION = 1e18;
/***********
* Structs *
***********/
struct PermitData {
// The address of token to spend.
address token;
// The amount of token to spend.
uint256 value;
// The deadline of the permit.
uint256 deadline;
// Below three are signatures.
uint8 v;
bytes32 r;
bytes32 s;
}
struct SwapData {
// The address of target contract to call.
address target;
// The calldata passed to target contract.
bytes data;
// The minimum amount of Ether should receive.
uint256 minOutput;
}
/*************
* Variables *
*************/
/// @notice Keep track whether an address is approved.
mapping(address => bool) public approvedTargets;
/// @notice The fee ratio charged for each swap, multiplied by 1e18.
uint256 public feeRatio;
/***************
* Constructor *
***************/
constructor(address trustedForwarder) ERC2771Context(trustedForwarder) {}
/*****************************
* Public Mutating Functions *
*****************************/
receive() external payable {}
/// @notice Swap some token for ether.
/// @param _permit The permit data, see comments from `PermitData`.
/// @param _swap The swap data, see comments from `SwapData`.
function swap(PermitData memory _permit, SwapData memory _swap) external nonReentrant {
require(approvedTargets[_swap.target], "target not approved");
address _sender = _msgSender();
// do permit
IERC20Permit(_permit.token).safePermit(
_sender,
address(this),
_permit.value,
_permit.deadline,
_permit.v,
_permit.r,
_permit.s
);
// record token balance in this contract
uint256 _balance = IERC20(_permit.token).balanceOf(address(this));
// transfer token
IERC20(_permit.token).safeTransferFrom(_sender, address(this), _permit.value);
// approve
IERC20(_permit.token).safeApprove(_swap.target, 0);
IERC20(_permit.token).safeApprove(_swap.target, _permit.value);
// do swap
uint256 _outputTokenAmount = address(this).balance;
// solhint-disable-next-line avoid-low-level-calls
(bool _success, bytes memory _res) = _swap.target.call(_swap.data);
require(_success, string(concat(bytes("swap failed: "), bytes(getRevertMsg(_res)))));
_outputTokenAmount = address(this).balance - _outputTokenAmount;
// take fee
uint256 _fee = (_outputTokenAmount * feeRatio) / PRECISION;
_outputTokenAmount = _outputTokenAmount - _fee;
require(_outputTokenAmount >= _swap.minOutput, "insufficient output amount");
// transfer ETH to sender
(_success, ) = _sender.call{value: _outputTokenAmount}("");
require(_success, "transfer ETH failed");
// refund rest token
uint256 _dust = IERC20(_permit.token).balanceOf(address(this)) - _balance;
if (_dust > 0) {
IERC20(_permit.token).safeTransfer(_sender, _dust);
}
}
/************************
* Restricted Functions *
************************/
/// @notice Withdraw stucked tokens.
/// @param _token The address of token to withdraw. Use `address(0)` if you want to withdraw Ether.
/// @param _amount The amount of token to withdraw.
function withdraw(address _token, uint256 _amount) external onlyOwner {
if (_token == address(0)) {
(bool success, ) = _msgSender().call{value: _amount}("");
require(success, "ETH transfer failed");
} else {
IERC20(_token).safeTransfer(_msgSender(), _amount);
}
}
/// @notice Update the fee ratio.
/// @param _feeRatio The new fee ratio.
function updateFeeRatio(uint256 _feeRatio) external onlyOwner {
feeRatio = _feeRatio;
emit UpdateFeeRatio(_feeRatio);
}
/// @notice Update the status of a target address.
/// @param _target The address of target to update.
/// @param _status The new status.
function updateApprovedTarget(address _target, bool _status) external onlyOwner {
approvedTargets[_target] = _status;
emit UpdateApprovedTarget(_target, _status);
}
/**********************
* Internal Functions *
**********************/
/// @inheritdoc Context
function _msgData() internal view virtual override(Context, ERC2771Context) returns (bytes calldata) {
return ERC2771Context._msgData();
}
/// @inheritdoc Context
function _msgSender() internal view virtual override(Context, ERC2771Context) returns (address) {
return ERC2771Context._msgSender();
}
/// @dev Internal function to concat two bytes array.
function concat(bytes memory a, bytes memory b) internal pure returns (bytes memory) {
return abi.encodePacked(a, b);
}
/// @dev Internal function decode revert message from return data.
function getRevertMsg(bytes memory _returnData) internal pure returns (string memory) {
if (_returnData.length < 68) return "Transaction reverted silently";
// solhint-disable-next-line no-inline-assembly
assembly {
_returnData := add(_returnData, 0x04)
}
return abi.decode(_returnData, (string));
}
}