Skip to content

Commit 38a45c7

Browse files
authored
Merge pull request #3 from thirdweb-dev/yash/safetransfer-and-custom-errors
Safetransfer, custom errors
2 parents 6730fbf + 5593928 commit 38a45c7

File tree

3 files changed

+445
-47
lines changed

3 files changed

+445
-47
lines changed

src/PaymentsGateway.sol

Lines changed: 68 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import "@openzeppelin/contracts/access/Ownable.sol";
55
import "@openzeppelin/contracts/utils/Context.sol";
66
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
77
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
8+
import { SafeTransferLib } from "./lib/SafeTransferLib.sol";
89

910
/**
1011
Requirements
@@ -18,6 +19,13 @@ import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
1819
*/
1920

2021
contract PaymentsGateway is Ownable, ReentrancyGuard {
22+
error PaymentsGatewayInvalidOperator(address operator);
23+
error PaymentsGatewayNotOwnerOrOperator(address caller);
24+
error PaymentsGatewayMismatchedValue(uint256 expected, uint256 actual);
25+
error PaymentsGatewayInvalidAmount(uint256 amount);
26+
error PaymentsGatewayVerificationFailed();
27+
error PaymentsGatewayFailedToForward();
28+
2129
event TransferStart(
2230
bytes32 indexed clientId,
2331
address indexed sender,
@@ -62,18 +70,24 @@ contract PaymentsGateway is Ownable, ReentrancyGuard {
6270
address private _operator;
6371

6472
constructor(address contractOwner, address initialOperator) Ownable(contractOwner) {
65-
require(initialOperator != address(0), "Operator can't be the zero address");
73+
if (initialOperator == address(0)) {
74+
revert PaymentsGatewayInvalidOperator(initialOperator);
75+
}
6676
_operator = initialOperator;
6777
emit OperatorChanged(address(0), initialOperator);
6878
}
6979

7080
modifier onlyOwnerOrOperator() {
71-
require(msg.sender == owner() || msg.sender == _operator, "Caller is not the owner or operator");
81+
if (msg.sender != owner() && msg.sender != _operator) {
82+
revert PaymentsGatewayNotOwnerOrOperator(msg.sender);
83+
}
7284
_;
7385
}
7486

7587
function setOperator(address newOperator) public onlyOwnerOrOperator {
76-
require(newOperator != address(0), "Operator can't be the zero address");
88+
if (newOperator == address(0)) {
89+
revert PaymentsGatewayInvalidOperator(newOperator);
90+
}
7791
emit OperatorChanged(_operator, newOperator);
7892
_operator = newOperator;
7993
}
@@ -89,13 +103,9 @@ contract PaymentsGateway is Ownable, ReentrancyGuard {
89103
address payable receiver
90104
) public onlyOwnerOrOperator nonReentrant {
91105
if (_isTokenERC20(tokenAddress)) {
92-
require(
93-
IERC20(tokenAddress).transferFrom(address(this), receiver, tokenAmount),
94-
"Failed to withdraw funds"
95-
);
106+
SafeTransferLib.safeTransferFrom(tokenAddress, address(this), receiver, tokenAmount);
96107
} else {
97-
(bool sent, ) = receiver.call{ value: tokenAmount }("");
98-
require(sent, "Failed to withdraw funds");
108+
SafeTransferLib.safeTransferETH(receiver, tokenAmount);
99109
}
100110
}
101111

@@ -136,17 +146,15 @@ contract PaymentsGateway is Ownable, ReentrancyGuard {
136146
payouts[payeeIdx].feeBPS
137147
);
138148
if (_isTokenNative(tokenAddress)) {
139-
(bool sent, ) = payouts[payeeIdx].payoutAddress.call{ value: feeAmount }("");
140-
require(sent, "Failed to distribute fees");
149+
SafeTransferLib.safeTransferETH(payouts[payeeIdx].payoutAddress, feeAmount);
141150
} else {
142-
require(
143-
IERC20(tokenAddress).transferFrom(msg.sender, payouts[payeeIdx].payoutAddress, feeAmount),
144-
"Token Fee Transfer Failed"
145-
);
151+
SafeTransferLib.safeTransferFrom(tokenAddress, msg.sender, payouts[payeeIdx].payoutAddress, feeAmount);
146152
}
147153
}
148154

149-
require(totalFeeAmount < tokenAmount, "fees exceeded tokenAmount");
155+
if (totalFeeAmount > tokenAmount) {
156+
revert PaymentsGatewayMismatchedValue(totalFeeAmount, tokenAmount);
157+
}
150158
return totalFeeAmount;
151159
}
152160

@@ -207,7 +215,6 @@ contract PaymentsGateway is Ownable, ReentrancyGuard {
207215
return (recovered, valid);
208216
}
209217

210-
211218
/**
212219
The purpose of startTransfer is to be the entrypoint for all thirdweb pay swap / bridge
213220
transactions. This function will allow us to standardize the logging and fee splitting across all providers.
@@ -229,11 +236,13 @@ contract PaymentsGateway is Ownable, ReentrancyGuard {
229236
bytes calldata signature
230237
) external payable nonReentrant {
231238
// verify amount
232-
require(tokenAmount > 0, "token amount must be greater than zero");
239+
if (tokenAmount == 0) {
240+
revert PaymentsGatewayInvalidAmount(tokenAmount);
241+
}
233242

234243
// verify data
235-
require(
236-
_verifyTransferStart(
244+
if (
245+
!_verifyTransferStart(
237246
clientId,
238247
transactionId,
239248
tokenAddress,
@@ -242,39 +251,52 @@ contract PaymentsGateway is Ownable, ReentrancyGuard {
242251
forwardAddress,
243252
data,
244253
signature
245-
),
246-
"failed to verify transaction"
247-
);
254+
)
255+
) {
256+
revert PaymentsGatewayVerificationFailed();
257+
}
248258

249259
if (_isTokenNative(tokenAddress)) {
250-
require(msg.value >= tokenAmount, "msg value must be gte than token amount");
260+
if (msg.value < tokenAmount) {
261+
revert PaymentsGatewayMismatchedValue(tokenAmount, msg.value);
262+
}
251263
}
252264

253-
emit TransferStart(clientId, msg.sender, transactionId, tokenAddress, tokenAmount);
254-
255265
// distribute fees
256266
uint256 totalFeeAmount = _distributeFees(tokenAddress, tokenAmount, payouts);
257267

258268
// determine native value to send
259-
uint256 sendValue = msg.value;
269+
uint256 sendValue = msg.value; // includes bridge fee etc. (if any)
260270
if (_isTokenNative(tokenAddress)) {
261271
sendValue = msg.value - totalFeeAmount;
262-
require(sendValue <= msg.value, "send value cannot exceed msg value");
263-
require(sendValue >= tokenAmount, "send value must cover tokenAmount");
272+
273+
if (sendValue < tokenAmount) {
274+
revert PaymentsGatewayMismatchedValue(sendValue, tokenAmount);
275+
}
264276
}
265277

266278
if (_isTokenERC20(tokenAddress)) {
267279
// pull user funds
268-
require(
269-
IERC20(tokenAddress).transferFrom(msg.sender, address(this), tokenAmount),
270-
"Failed to pull user erc20 funds"
271-
);
280+
SafeTransferLib.safeTransferFrom(tokenAddress, msg.sender, address(this), tokenAmount);
281+
SafeTransferLib.safeApprove(tokenAddress, forwardAddress, tokenAmount);
282+
}
272283

273-
require(IERC20(tokenAddress).approve(forwardAddress, tokenAmount), "Failed to approve forwarder");
284+
{
285+
(bool success, bytes memory response) = forwardAddress.call{ value: sendValue }(data);
286+
if (!success) {
287+
// If there is return data, the delegate call reverted with a reason or a custom error, which we bubble up.
288+
if (response.length > 0) {
289+
assembly {
290+
let returndata_size := mload(response)
291+
revert(add(32, response), returndata_size)
292+
}
293+
} else {
294+
revert PaymentsGatewayFailedToForward();
295+
}
296+
}
274297
}
275298

276-
(bool success, ) = forwardAddress.call{ value: sendValue }(data);
277-
require(success, "Failed to forward");
299+
emit TransferStart(clientId, msg.sender, transactionId, tokenAddress, tokenAmount);
278300
}
279301

280302
/**
@@ -294,23 +316,23 @@ contract PaymentsGateway is Ownable, ReentrancyGuard {
294316
uint256 tokenAmount,
295317
address payable receiverAddress
296318
) external payable nonReentrant {
297-
require(tokenAmount > 0, "token amount must be greater than zero");
319+
if (tokenAmount == 0) {
320+
revert PaymentsGatewayInvalidAmount(tokenAmount);
321+
}
298322

299323
if (_isTokenNative(tokenAddress)) {
300-
require(msg.value >= tokenAmount, "msg value must be gte token amount");
324+
if (msg.value < tokenAmount) {
325+
revert PaymentsGatewayMismatchedValue(tokenAmount, msg.value);
326+
}
301327
}
302328

303-
emit TransferEnd(clientId, receiverAddress, transactionId, tokenAddress, tokenAmount);
304-
305329
// pull user funds
306330
if (_isTokenERC20(tokenAddress)) {
307-
require(
308-
IERC20(tokenAddress).transferFrom(msg.sender, receiverAddress, tokenAmount),
309-
"Failed to forward erc20 funds"
310-
);
331+
SafeTransferLib.safeTransferFrom(tokenAddress, msg.sender, receiverAddress, tokenAmount);
311332
} else {
312-
(bool success, ) = receiverAddress.call{ value: tokenAmount }("");
313-
require(success, "Failed to send to reciever");
333+
SafeTransferLib.safeTransferETH(receiverAddress, tokenAmount);
314334
}
335+
336+
emit TransferEnd(clientId, receiverAddress, transactionId, tokenAddress, tokenAmount);
315337
}
316338
}

0 commit comments

Comments
 (0)