Skip to content

Commit 31ca029

Browse files
authored
Merge pull request #12 from thirdweb-dev/yash/use-signature
Use signature
2 parents c1726ac + 348a4ff commit 31ca029

File tree

3 files changed

+433
-183
lines changed

3 files changed

+433
-183
lines changed

src/UniversalBridgeProxy.sol

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ contract UniversalBridgeProxy {
1313
constructor(
1414
address _implementation,
1515
address _owner,
16+
address _operator,
1617
address payable _protocolFeeRecipient,
1718
uint256 _protocolFeeBps
1819
) {
@@ -33,8 +34,9 @@ contract UniversalBridgeProxy {
3334
}
3435

3536
bytes memory data = abi.encodeWithSignature(
36-
"initialize(address,address,uint256)",
37+
"initialize(address,address,address,uint256)",
3738
_owner,
39+
_operator,
3840
_protocolFeeRecipient,
3941
_protocolFeeBps
4042
);

src/UniversalBridgeV1.sol

Lines changed: 106 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ pragma solidity ^0.8.22;
33

44
/// @author thirdweb
55

6+
import { EIP712 } from "lib/solady/src/utils/EIP712.sol";
67
import { SafeTransferLib } from "lib/solady/src/utils/SafeTransferLib.sol";
78
import { ReentrancyGuard } from "lib/solady/src/utils/ReentrancyGuard.sol";
8-
import { Ownable } from "lib/solady/src/auth/Ownable.sol";
9+
import { ECDSA } from "lib/solady/src/utils/ECDSA.sol";
10+
import { OwnableRoles } from "lib/solady/src/auth/OwnableRoles.sol";
911
import { UUPSUpgradeable } from "lib/solady/src/utils/UUPSUpgradeable.sol";
1012
import { Initializable } from "lib/solady/src/utils/Initializable.sol";
1113

@@ -35,13 +37,34 @@ library UniversalBridgeStorage {
3537
}
3638
}
3739

38-
contract UniversalBridgeV1 is Initializable, UUPSUpgradeable, Ownable, ReentrancyGuard {
40+
contract UniversalBridgeV1 is EIP712, Initializable, UUPSUpgradeable, OwnableRoles, ReentrancyGuard {
41+
using ECDSA for bytes32;
42+
3943
/*///////////////////////////////////////////////////////////////
4044
State, constants, structs
4145
//////////////////////////////////////////////////////////////*/
4246

4347
address private constant NATIVE_TOKEN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
4448
uint256 private constant MAX_PROTOCOL_FEE_BPS = 300; // 3%
49+
uint256 private constant _OPERATOR_ROLE = 1 << 0;
50+
51+
struct TransactionRequest {
52+
bytes32 transactionId;
53+
address tokenAddress;
54+
uint256 tokenAmount;
55+
address payable forwardAddress;
56+
address payable spenderAddress;
57+
uint256 expirationTimestamp;
58+
address payable developerFeeRecipient;
59+
uint256 developerFeeBps;
60+
bytes callData;
61+
bytes extraData;
62+
}
63+
64+
bytes32 private constant TRANSACTION_REQUEST_TYPEHASH =
65+
keccak256(
66+
"TransactionRequest(bytes32 transactionId,address tokenAddress,uint256 tokenAmount,address forwardAddress,address spenderAddress,uint256 expirationTimestamp,address developerFeeRecipient,uint256 developerFeeBps,bytes callData,bytes extraData)"
67+
);
4568

4669
/*///////////////////////////////////////////////////////////////
4770
Events
@@ -69,17 +92,22 @@ contract UniversalBridgeV1 is Initializable, UUPSUpgradeable, Ownable, Reentranc
6992
error UniversalBridgeZeroAddress();
7093
error UniversalBridgePaused();
7194
error UniversalBridgeRestrictedAddress();
95+
error UniversalBridgeVerificationFailed();
96+
error UniversalBridgeRequestExpired(uint256 expirationTimestamp);
97+
error UniversalBridgeTransactionAlreadyProcessed();
7298

7399
constructor() {
74100
_disableInitializers();
75101
}
76102

77103
function initialize(
78104
address _owner,
105+
address _operator,
79106
address payable _protocolFeeRecipient,
80107
uint256 _protocolFeeBps
81108
) external initializer {
82109
_initializeOwner(_owner);
110+
_grantRoles(_operator, _OPERATOR_ROLE);
83111
_setProtocolFeeInfo(_protocolFeeRecipient, _protocolFeeBps);
84112
}
85113

@@ -136,69 +164,71 @@ contract UniversalBridgeV1 is Initializable, UUPSUpgradeable, Ownable, Reentranc
136164
transactions. This function will allow us to standardize the logging and fee splitting across all providers.
137165
*/
138166
function initiateTransaction(
139-
bytes32 transactionId,
140-
address tokenAddress,
141-
uint256 tokenAmount,
142-
address payable forwardAddress,
143-
address payable spenderAddress,
144-
address payable developerFeeRecipient,
145-
uint256 developerFeeBps,
146-
bytes calldata callData,
147-
bytes calldata extraData
167+
TransactionRequest calldata req,
168+
bytes calldata signature
148169
) external payable nonReentrant onlyProxy {
170+
// verify req
171+
if (!_verifyTransactionReq(req, signature)) {
172+
revert UniversalBridgeVerificationFailed();
173+
}
174+
// mark the pay request as processed
175+
_universalBridgeStorage().processed[req.transactionId] = true;
176+
149177
if (_universalBridgeStorage().isPaused) {
150178
revert UniversalBridgePaused();
151179
}
152180

153181
if (
154-
_universalBridgeStorage().isRestricted[forwardAddress] ||
155-
_universalBridgeStorage().isRestricted[tokenAddress]
182+
_universalBridgeStorage().isRestricted[req.forwardAddress] ||
183+
_universalBridgeStorage().isRestricted[req.tokenAddress]
156184
) {
157185
revert UniversalBridgeRestrictedAddress();
158186
}
159187

160188
// verify amount
161-
if (tokenAmount == 0) {
162-
revert UniversalBridgeInvalidAmount(tokenAmount);
189+
if (req.tokenAmount == 0) {
190+
revert UniversalBridgeInvalidAmount(req.tokenAmount);
163191
}
164192

165-
// mark the pay request as processed
166-
_universalBridgeStorage().processed[transactionId] = true;
167-
168193
uint256 sendValue = msg.value; // includes bridge fee etc. (if any)
169194

170195
// distribute fees
171-
uint256 totalFeeAmount = _distributeFees(tokenAddress, tokenAmount, developerFeeRecipient, developerFeeBps);
196+
uint256 totalFeeAmount = _distributeFees(
197+
req.tokenAddress,
198+
req.tokenAmount,
199+
req.developerFeeRecipient,
200+
req.developerFeeBps
201+
);
172202

173-
if (_isNativeToken(tokenAddress)) {
203+
if (_isNativeToken(req.tokenAddress)) {
174204
sendValue = msg.value - totalFeeAmount;
175205

176-
if (sendValue < tokenAmount) {
177-
revert UniversalBridgeMismatchedValue(tokenAmount, sendValue);
206+
if (sendValue < req.tokenAmount) {
207+
revert UniversalBridgeMismatchedValue(req.tokenAmount, sendValue);
178208
}
179-
_call(forwardAddress, sendValue, callData); // calldata empty for direct transfer
180-
} else if (callData.length == 0) {
209+
_call(req.forwardAddress, sendValue, req.callData); // calldata empty for direct transfer
210+
} else if (req.callData.length == 0) {
181211
if (msg.value != 0) {
182212
revert UniversalBridgeMsgValueNotZero();
183213
}
184-
SafeTransferLib.safeTransferFrom(tokenAddress, msg.sender, forwardAddress, tokenAmount);
214+
SafeTransferLib.safeTransferFrom(req.tokenAddress, msg.sender, req.forwardAddress, req.tokenAmount);
185215
} else {
186216
// pull user funds
187-
SafeTransferLib.safeTransferFrom(tokenAddress, msg.sender, address(this), tokenAmount);
217+
SafeTransferLib.safeTransferFrom(req.tokenAddress, msg.sender, address(this), req.tokenAmount);
188218

189219
// approve to spender address and call forward address -- both will be same in most cases
190-
SafeTransferLib.safeApprove(tokenAddress, spenderAddress, tokenAmount);
191-
_call(forwardAddress, sendValue, callData);
220+
SafeTransferLib.safeApprove(req.tokenAddress, req.spenderAddress, req.tokenAmount);
221+
_call(req.forwardAddress, sendValue, req.callData);
192222
}
193223

194224
emit TransactionInitiated(
195225
msg.sender,
196-
transactionId,
197-
tokenAddress,
198-
tokenAmount,
199-
developerFeeRecipient,
200-
developerFeeBps,
201-
extraData
226+
req.transactionId,
227+
req.tokenAddress,
228+
req.tokenAmount,
229+
req.developerFeeRecipient,
230+
req.developerFeeBps,
231+
req.extraData
202232
);
203233
}
204234

@@ -221,6 +251,43 @@ contract UniversalBridgeV1 is Initializable, UUPSUpgradeable, Ownable, Reentranc
221251
Internal functions
222252
//////////////////////////////////////////////////////////////*/
223253

254+
function _verifyTransactionReq(
255+
TransactionRequest calldata req,
256+
bytes calldata signature
257+
) private view returns (bool) {
258+
if (req.expirationTimestamp < block.timestamp) {
259+
revert UniversalBridgeRequestExpired(req.expirationTimestamp);
260+
}
261+
262+
bool processed = _universalBridgeStorage().processed[req.transactionId];
263+
264+
if (processed) {
265+
revert UniversalBridgeTransactionAlreadyProcessed();
266+
}
267+
268+
bytes32 structHash = keccak256(
269+
abi.encode(
270+
TRANSACTION_REQUEST_TYPEHASH,
271+
req.transactionId,
272+
req.tokenAddress,
273+
req.tokenAmount,
274+
req.forwardAddress,
275+
req.spenderAddress,
276+
req.expirationTimestamp,
277+
req.developerFeeRecipient,
278+
req.developerFeeBps,
279+
keccak256(req.callData),
280+
keccak256(req.extraData)
281+
)
282+
);
283+
284+
bytes32 digest = _hashTypedData(structHash);
285+
address recovered = digest.recover(signature);
286+
bool valid = hasAllRoles(recovered, _OPERATOR_ROLE);
287+
288+
return valid;
289+
}
290+
224291
function _distributeFees(
225292
address tokenAddress,
226293
uint256 tokenAmount,
@@ -255,6 +322,11 @@ contract UniversalBridgeV1 is Initializable, UUPSUpgradeable, Ownable, Reentranc
255322
return totalFeeAmount;
256323
}
257324

325+
function _domainNameAndVersion() internal pure override returns (string memory name, string memory version) {
326+
name = "UniversalBridgeV1";
327+
version = "1";
328+
}
329+
258330
function _setProtocolFeeInfo(address payable feeRecipient, uint256 feeBps) internal {
259331
if (feeRecipient == address(0)) {
260332
revert UniversalBridgeZeroAddress();

0 commit comments

Comments
 (0)