-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathRareBridge.sol
More file actions
275 lines (235 loc) · 12.1 KB
/
RareBridge.sol
File metadata and controls
275 lines (235 loc) · 12.1 KB
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
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol";
import {IAny2EVMMessageReceiver} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IAny2EVMMessageReceiver.sol";
import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol";
import {OwnerIsCreator} from "@chainlink/contracts-ccip/src/v0.8/shared/access/OwnerIsCreator.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol";
import {CCIPReceiverUpgradable} from "./CCIPReceiverUpgradable.sol";
import {IRareBridge} from "./interfaces/IRareBridge.sol";
/// @title RareBridge
/// @notice The abstract RARE bridge contract that sends and receives RARE tokens and arbitrary messages.
/// @dev Made to be used with Chainlink CCIP. This contract is UUPS upgradeable.
abstract contract RareBridge is
IRareBridge,
Initializable,
IAny2EVMMessageReceiver,
IERC165,
CCIPReceiverUpgradable,
PausableUpgradeable,
OwnableUpgradeable,
UUPSUpgradeable
{
using SafeERC20 for IERC20;
// Mapping to keep track of allowlisted receivers per destination chain.
mapping(uint64 => mapping(address => bool)) public allowlistedRecipients;
// Mapping to keep track of allowlisted senders per source chain.
mapping(uint64 => mapping(address => bool)) public allowlistedSenders;
// Mapping to keep track of extraArgs per destination chain
mapping(uint64 => bytes) public extraArgsPerChain;
address public s_linkToken;
address public s_rareToken;
/// @notice Modifier that checks if the pair of a given chain selector and sender is allowlisted.
/// @param _sourceChainSelector The selector of the source chain.
/// @param _sourceChainSender The address of the CCIP sender.
modifier onlyAllowlistedSender(uint64 _sourceChainSelector, address _sourceChainSender) {
if (!allowlistedSenders[_sourceChainSelector][_sourceChainSender]) {
revert NotInAllowlist(_sourceChainSelector, _sourceChainSender);
}
_;
}
/// @notice Modifier that checks if the pair of a given chain selector and recipient is allowlisted.
/// @param _destinationChainSelector The selector of the destination chain.
/// @param _destinationChainRecipient The address of the CCIP recipient.
modifier onlyAllowlistedRecipient(uint64 _destinationChainSelector, address _destinationChainRecipient) {
if (!allowlistedRecipients[_destinationChainSelector][_destinationChainRecipient]) {
revert NotInAllowlist(_destinationChainSelector, _destinationChainRecipient);
}
_;
}
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
/// @notice Initializes the contract with the CCIP router, LINK, and RARE address.
/// @param _router The address of the CCIP Router.
/// @param _link The address of the LINK Token.
/// @param _rare The address of the RARE Token.
/// @param admin The address of the RARE bridge administrator account.
function initialize(address _router, address _link, address _rare, address admin) public initializer {
if (_router == address(0)) revert ZeroAddressUnsupported();
if (_link == address(0)) revert ZeroAddressUnsupported();
if (_rare == address(0)) revert ZeroAddressUnsupported();
__CCIPReceiver_init(_router);
__Pausable_init();
__Ownable_init(admin);
__UUPSUpgradeable_init();
s_linkToken = _link;
s_rareToken = _rare;
}
/// @notice Updates the allowlist status of a destination chain for transactions.
/// @param _destinationChainSelector The selector of the destination chain.
/// @param _destinationChainRecipient The address of the CCIP recipient to be updated.
/// @param allowed The allowlist status to be set for the pair of recipient and destination chain.
/// @dev This function can only be called by the owner.
function allowlistRecipient(
uint64 _destinationChainSelector,
address _destinationChainRecipient,
bool allowed
) external onlyOwner {
allowlistedRecipients[_destinationChainSelector][_destinationChainRecipient] = allowed;
emit RecipientAllowlisted(_destinationChainSelector, _destinationChainRecipient, allowed);
}
/// @notice Updates the allowlist status of a sender for transactions.
/// @param _sourceChainSelector The selector of a source chain.
/// @param _sourceChainSender The address of the CCIP sender to be updated.
/// @param allowed The allowlist status to be set for the pair of sender and source chain.
/// @dev This function can only be called by the owner.
function allowlistSender(uint64 _sourceChainSelector, address _sourceChainSender, bool allowed) external onlyOwner {
allowlistedSenders[_sourceChainSelector][_sourceChainSender] = allowed;
emit SenderAllowlisted(_sourceChainSelector, _sourceChainSender, allowed);
}
/// @notice Set sendTokens() extra args per destination chain.
/// @param _destinationChainSelector The selector of the destination chain.
/// @param _gasLimit The gas limit to execute on a destination chain.
/// @dev This function can only be called by the owner.
function setExtraArgs(uint64 _destinationChainSelector, uint256 _gasLimit) external onlyOwner {
extraArgsPerChain[_destinationChainSelector] = Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: _gasLimit}));
emit ExtraArgsSet(_destinationChainSelector, extraArgsPerChain[_destinationChainSelector]);
}
/// @notice Calculates the estimated fee for sending a message.
/// @param _destinationChainSelector The selector of the destination chain.
/// @param _destinationChainRecipient The address of the CCIP recipient on the destination chain.
/// @param _distributionData.
/// @param _extraArgs The encoded extra arguments for the message.
/// @param _payFeesInLink Whether the fees will be paid in LINK tokens.
/// @return fee The estimated fee.
function getFee(
uint64 _destinationChainSelector,
address _destinationChainRecipient,
bytes memory _distributionData,
bytes memory _extraArgs,
bool _payFeesInLink
) external view returns (uint256 fee) {
Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({
receiver: abi.encode(_destinationChainRecipient),
data: _distributionData,
tokenAmounts: new Client.EVMTokenAmount[](0),
extraArgs: _extraArgs.length > 0 ? _extraArgs : extraArgsPerChain[_destinationChainSelector],
feeToken: _payFeesInLink ? address(s_linkToken) : address(0)
});
fee = IRouterClient(i_ccipRouter).getFee(_destinationChainSelector, message);
}
/// @notice Sends RARE tokens a destination chain.
/// @param _destinationChainSelector The selector of the destination chain.
/// @param _destinationChainRecipient The address of the CCIP recipient on the destination chain.
/// @param _distributionData The encoded arrays of recipients and amounts.
/// @param _extraArgs The encoded extra arguments for the message.
/// @param _payFeesInLink Whether the fees will be paid in LINK tokens.
function send(
uint64 _destinationChainSelector,
address _destinationChainRecipient,
bytes memory _distributionData,
bytes memory _extraArgs,
bool _payFeesInLink
) external payable onlyAllowlistedRecipient(_destinationChainSelector, _destinationChainRecipient) whenNotPaused {
(address[] memory recipients, uint256[] memory amounts) = abi.decode(_distributionData, (address[], uint256[]));
if (recipients.length != amounts.length) {
revert RecipientsAndAmountsLengthMismatch();
}
// Calculate the total amount as the sum of the individual amounts
uint256 totalAmount = 0;
for (uint i = 0; i < amounts.length; ++i) {
totalAmount += amounts[i];
}
_handleTokensOnSend(msg.sender, totalAmount);
Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({
receiver: abi.encode(_destinationChainRecipient),
data: _distributionData,
tokenAmounts: new Client.EVMTokenAmount[](0),
extraArgs: _extraArgs.length > 0 ? _extraArgs : extraArgsPerChain[_destinationChainSelector],
feeToken: _payFeesInLink ? s_linkToken : address(0)
});
// Send the CCIP message through the router
(bytes32 messageId, uint256 fee) = _send(_destinationChainSelector, message, _payFeesInLink);
// Emit an event with message ID and message details
emit MessageSent(messageId, _destinationChainSelector, _destinationChainRecipient, fee, _payFeesInLink);
}
function _send(
uint64 _destinationChainSelector,
Client.EVM2AnyMessage memory message,
bool _payFeesInLink
) internal returns (bytes32 messageId, uint256 fee) {
fee = IRouterClient(i_ccipRouter).getFee(_destinationChainSelector, message);
if (_payFeesInLink) {
IERC20(s_linkToken).safeTransferFrom(msg.sender, address(this), fee);
IERC20(s_linkToken).approve(i_ccipRouter, fee);
messageId = IRouterClient(i_ccipRouter).ccipSend(_destinationChainSelector, message);
} else {
// Ensure the user has sent enough ether to cover the fee
if (msg.value < fee) {
revert InsufficientEthForFee(msg.value, fee);
}
messageId = IRouterClient(i_ccipRouter).ccipSend{value: fee}(_destinationChainSelector, message);
// Return the excess msg.value to the user if needed
if (msg.value > fee) {
(bool success, ) = msg.sender.call{value: msg.value - fee}("");
if (!success) revert RefundFailed(msg.sender, msg.value - fee);
}
}
}
/// @notice Internal ccipReceive function override.
/// @param message Any2EVMMessage
function _ccipReceive(
Client.Any2EVMMessage memory message
)
internal
override
onlyRouter
onlyAllowlistedSender(message.sourceChainSelector, abi.decode(message.sender, (address)))
{
// Decode the distribution data
(address[] memory recipients, uint256[] memory amounts) = abi.decode(message.data, (address[], uint256[]));
// Process the token distribution
uint length = recipients.length;
for (uint i = 0; i < length; ++i) {
_handleTokensOnReceive(recipients[i], amounts[i]);
}
// Emit an event with message details
emit MessageReceived(message.messageId, message.sourceChainSelector, abi.decode(message.sender, (address)));
}
function _handleTokensOnSend(address, uint256) internal virtual;
function _handleTokensOnReceive(address, uint256) internal virtual;
/// @notice Fallback function to allow the contract to receive Ether.
/// @dev This function has no function body, making it a default function for receiving Ether.
/// It is automatically called when Ether is sent to the contract without any data.
receive() external payable {}
/// @notice Allows the contract owner to withdraw the entire balance of Ether from the contract.
/// @param _beneficiary The address to which the Ether should be sent.
/// @dev This function reverts if there are no funds to withdraw or if the transfer fails.
function withdraw(address payable _beneficiary) external onlyOwner {
// Retrieve the balance of this contract
uint256 amount = address(this).balance;
// Revert if there is nothing to withdraw
if (amount == 0) revert NothingToWithdraw();
// Attempt to send the funds, capturing the success status and discarding any return data
(bool sent, ) = _beneficiary.call{value: amount}("");
// Revert if the send failed, with information about the attempted transfer
if (!sent) revert FailedToWithdrawEth(msg.sender, _beneficiary, amount);
}
function pause() external onlyOwner {
_pause();
}
function unpause() external onlyOwner {
_unpause();
}
/// @notice Authorizes an upgrade to a new implementation.
/// @param newImplementation The address of the new implementation.
/// @dev This function can only be called by the owner.
function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
}