-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathTransferSchedulerV1.sol
249 lines (214 loc) · 10.6 KB
/
TransferSchedulerV1.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
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
// SPDX-License-Identifier: MIT
pragma solidity 0.8.27;
// Importing necessary interfaces and libraries
import {IScheduledTransfer} from "./interfaces/IScheduledTransfer.sol";
import {ScheduledTransferHash} from "./libraries/ScheduledTransferHash.sol";
import {SignatureVerification} from "permit2/src/libraries/SignatureVerification.sol";
import {addressNonceRecord, Status, QueuedTransferRecord} from "./types/TransferSchedulerTypes.sol";
import {
TransferTooLate,
TransferTooEarly,
InsufficientTokenAllowance,
InsufficientGasTokenAllowance,
MaxBaseFeeExceeded,
InvalidNonce,
Unauthorized,
InvalidNonceStatus
} from "./TransferSchedulerErrors.sol";
import {ERC20} from "solmate/src/tokens/ERC20.sol";
import {SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol";
import {EIP712} from "@openzeppelin/contracts/utils/cryptography/EIP712.sol";
import {EIP712Upgradeable} from "@openzeppelin/contracts-upgradeable/utils/cryptography/EIP712Upgradeable.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
// Main contract definition
contract TransferSchedulerV1 is IScheduledTransfer, EIP712Upgradeable, UUPSUpgradeable, OwnableUpgradeable {
using SignatureVerification for bytes; // Allows bytes to use signature verification functions
using SafeTransferLib for ERC20; // Allows ERC20 tokens to use safe transfer functions
using ScheduledTransferHash for ScheduledTransferDetails; // Allows ScheduledTransferDetails to use hashing functions
// State variables
address relayGasToken; // Address of the token used for gas payments
uint8 relayGasCommissionPercentage; // Percentage of gas commission charged
uint32 relayGasUsage; // Amount of gas used for relay operations
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers(); // Prevents the contract from being initialized in the constructor
}
// The initialize function will be used to set up the initial state of the contract.
function initialize(address _relayGasToken, uint8 _relayGasCommissionPercentage, uint32 _relayGasUsage)
public
initializer
{
__Ownable_init(msg.sender);
__UUPSUpgradeable_init();
__EIP712_init("TransferScheduler", "1");
relayGasToken = _relayGasToken;
relayGasCommissionPercentage = _relayGasCommissionPercentage; // Set the gas commission percentage
relayGasUsage = _relayGasUsage; // Set the gas usage amount
}
// Function to authorize upgrades, only callable by the owner
function _authorizeUpgrade(address _newImplementation) internal override onlyOwner {}
// Mappings to store transfer records
mapping(address => mapping(uint96 => addressNonceRecord)) public transfers; // Maps wallet addresses to their transfer records
mapping(address => uint96[]) private addressNonceIndices; // Maps wallet addresses to their nonces
mapping(address => mapping(uint256 => uint256)) private nonceBitmap; // Bitmap for nonce tracking
// Event emitted when a transfer is scheduled
event TransferScheduled(
address indexed owner,
uint96 nonce,
address token,
address to,
uint128 amount,
uint40 notBeforeDate,
uint40 notAfterDate,
uint40 maxBaseFee,
bytes signature
);
// Event emitted after a successful transfer execution
event TransferExecuted(address indexed owner, uint96 nonce);
// Function to queue a scheduled transfer
function queueScheduledTransfer(
address _wallet,
uint96 _nonce,
address _token,
address _to,
uint128 _amount,
uint40 _notBeforeDate,
uint40 _notAfterDate,
uint40 _maxBaseFee,
bytes calldata _signature
) public {
// If the transfer does not exist, add its nonce to the indices
if (!transfers[_wallet][_nonce].exists) {
addressNonceIndices[_wallet].push(_nonce);
}
// Create a new transfer record
transfers[_wallet][_nonce] = addressNonceRecord(uint40(block.number), Status.notCompleted, true);
// Emit the TransferScheduled event
emit TransferScheduled(
_wallet, _nonce, _token, _to, _amount, _notBeforeDate, _notAfterDate, _maxBaseFee, _signature
);
}
// Function to get scheduled transfers for a wallet
function getScheduledTransfers(address _wallet, Status _status)
public
view
returns (QueuedTransferRecord[] memory)
{
// Count matching transfers
uint256 count = 0;
for (uint256 i = 0; i < addressNonceIndices[_wallet].length; i++) {
if (transfers[_wallet][addressNonceIndices[_wallet][i]].status == _status) {
count++;
}
}
// Initialize array with the correct size
QueuedTransferRecord[] memory records = new QueuedTransferRecord[](count);
// Fill array with matching transfers
uint256 index = 0;
for (uint256 i = 0; i < addressNonceIndices[_wallet].length; i++) {
uint96 nonce = addressNonceIndices[_wallet][i];
if (transfers[_wallet][nonce].status == _status) {
records[index] =
QueuedTransferRecord({nonce: nonce, blockNumber: transfers[_wallet][nonce].blockNumber});
index++;
}
}
return records; // Return the array of queued transfers
}
// Function to get the relay gas usage
function getRelayGasUsage() public view returns (uint32) {
return relayGasUsage; // Return the gas usage
}
// Function to get the relay gas commission percentage
function getRelayGasCommissionPercentage() public view returns (uint8) {
return relayGasCommissionPercentage; // Return the commission percentage
}
// Function to get the relay gas token address
function getRelayGasToken() public view returns (address) {
return relayGasToken; // Return the gas token address
}
///// @inheritdoc IScheduledTransfer
// Function to execute a scheduled transfer
function executeScheduledTransfer(
ScheduledTransferDetails memory scheduledTransferDetails,
bytes calldata signature
) external {
_executeScheduledTransfer(scheduledTransferDetails, signature);
}
// Internal function to handle the execution of a scheduled transfer
function _executeScheduledTransfer(
ScheduledTransferDetails memory scheduledTransferDetails,
bytes calldata signature
) private {
// Check if the transfer is too early
if (block.timestamp < scheduledTransferDetails.notBeforeDate) {
revert TransferTooEarly(scheduledTransferDetails.notBeforeDate);
}
// Check if the transfer is too late
if (block.timestamp > scheduledTransferDetails.notAfterDate) {
revert TransferTooLate(scheduledTransferDetails.notAfterDate);
}
// Check if the base fee exceeds the maximum allowed
if (block.basefee > scheduledTransferDetails.maxBaseFee) {
revert MaxBaseFeeExceeded(block.basefee, scheduledTransferDetails.maxBaseFee);
}
// Check the allowance of the token for the transfer
uint256 spenderTokenAllowance =
ERC20(scheduledTransferDetails.token).allowance(scheduledTransferDetails.owner, address(this));
if (scheduledTransferDetails.amount > spenderTokenAllowance) {
revert InsufficientTokenAllowance(spenderTokenAllowance);
}
// Check the allowance of the gas token for the relay
uint256 spenderGasTokenAllowance = ERC20(relayGasToken).allowance(scheduledTransferDetails.owner, address(this));
if (((block.basefee * relayGasUsage * (100 + relayGasCommissionPercentage)) / 100) > spenderGasTokenAllowance) {
revert InsufficientGasTokenAllowance(spenderGasTokenAllowance);
}
// Use the nonce for this transfer
_useUnorderedNonce(scheduledTransferDetails.owner, scheduledTransferDetails.nonce);
// Hash the scheduled transfer details for signature verification
bytes32 hashed = _hashTypedDataV4(scheduledTransferDetails.hash());
// Verify the signature
SignatureVerification.verify(signature, hashed, scheduledTransferDetails.owner);
// If there is an amount to transfer, execute the transfer
if (scheduledTransferDetails.amount != 0) {
ERC20(scheduledTransferDetails.token).safeTransferFrom(
scheduledTransferDetails.owner, scheduledTransferDetails.to, scheduledTransferDetails.amount
);
// Refund the relay for the transaction
ERC20(relayGasToken).safeTransferFrom(
scheduledTransferDetails.owner,
msg.sender,
((block.basefee * relayGasUsage * (100 + relayGasCommissionPercentage)) / 100)
);
}
// Update the transfer status to completed and emit an event
if (transfers[scheduledTransferDetails.owner][scheduledTransferDetails.nonce].exists == true) {
transfers[scheduledTransferDetails.owner][scheduledTransferDetails.nonce].status = Status.completed;
emit TransferExecuted(scheduledTransferDetails.owner, scheduledTransferDetails.nonce);
}
}
// Function to cancel a scheduled transfer only callable by the wallet owner
function cancelScheduledTransfer(address _wallet, uint96 _nonce) external {
if (msg.sender != _wallet) revert Unauthorized(msg.sender);
if (!transfers[_wallet][_nonce].exists) revert InvalidNonce();
if (transfers[_wallet][_nonce].status == Status.notCompleted) {
transfers[_wallet][_nonce].status = Status.cancelled;
} else {
revert InvalidNonceStatus(transfers[_wallet][_nonce].status);
}
}
// Function to check if a nonce is taken and set the bit in the bitmap
function _useUnorderedNonce(address from, uint256 nonce) internal {
(uint256 wordPos, uint256 bitPos) = bitmapPositions(nonce);
uint256 bit = 1 << bitPos;
uint256 flipped = nonceBitmap[from][wordPos] ^= bit;
// Revert if the nonce is invalid
if (flipped & bit == 0) revert InvalidNonce();
}
// Function to return the index of the bitmap and the bit position within the bitmap
function bitmapPositions(uint256 nonce) private pure returns (uint256 wordPos, uint256 bitPos) {
wordPos = uint248(nonce >> 8); // Get the word position
bitPos = uint8(nonce); // Get the bit position
}
}