-
Notifications
You must be signed in to change notification settings - Fork 0
/
CcipNftBridge.sol
285 lines (234 loc) · 10.3 KB
/
CcipNftBridge.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
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
276
277
278
279
280
281
282
283
284
285
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {ICcipNftBridge} from "./interfaces/ICcipNftBridge.sol";
import {IFightMatchmaker} from "./interfaces/IFightMatchmaker.sol";
import {ReferencesInitializer} from "./ReferencesInitializer.sol";
import {IRouterClient} from "@chainlink-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol";
import {OwnerIsCreator} from "@chainlink-ccip/src/v0.8/shared/access/OwnerIsCreator.sol";
import {Client} from "@chainlink-ccip/src/v0.8/ccip/libraries/Client.sol";
// Using personalized version for compatibility with Chainlink Functions
import {CCIPReceiver} from "./libEdits/edit-CCIPReceiver.sol";
// import {CCIPReceiver} from "@chainlink-ccip/src/v0.8/ccip/applications/CCIPReceiver.sol";
import "./Utils.sol";
import "forge-std/console.sol";
/**
* @title CcipNftBridge
* @author PromtFighters team: @CarlosAlegreUr
*
* @dev Handles CCIP messaging for this system.
* Sending and receiving Nfts are managed here.
* Rules:
* - Only 1-1 chain communication has been programmed. (For now)
*/
abstract contract CcipNftBridge is ICcipNftBridge, CCIPReceiver, ReferencesInitializer {
//******************************* */
// CONTRACT'S STATE && CONSTANTS
//******************************* */
// CCIP nft tracking
mapping(uint256 => bool) internal s_isFighting;
mapping(uint256 => bool) internal s_isOnChain;
// IFightMatchmaker private immutable i_FIGHT_MATCHMAKER;
// TESTING: used when testing TODO
IFightMatchmaker i_FIGHT_MATCHMAKER;
// TESTING: used when testing TODO
function setMatchmaker(address m) external {
require(DEPLOYER == msg.sender);
i_FIGHT_MATCHMAKER = IFightMatchmaker(m);
}
string private constant HANDLE_RECEIVE_NFT_FUNCTION_SIG = "_updateNftStateOnReceive(uint256,address,string)";
uint64 private immutable i_DESTINATION_CHAIN_SELECTOR;
// Technically immutable due to ReferencesInitializer
address private i_RECEIVER_ADDRESS;
//******************** */
// MODIFIERS
//******************** */
/**
* @dev Only FightMatchmaker contract can call the function to mark NFTs as fighting or not.
*/
modifier onlyMatchmaker() {
require(msg.sender == address(i_FIGHT_MATCHMAKER), "Only FightMatchmaker can call this function");
_;
}
//******************** */
// CONSTRUCTOR
//******************** */
constructor(
uint64 _destinationChainSelector,
address _receiverAddress,
address _router,
IFightMatchmaker _matchmakerContract
) CCIPReceiver(_router) {
i_DESTINATION_CHAIN_SELECTOR = _destinationChainSelector;
i_RECEIVER_ADDRESS = _receiverAddress;
i_FIGHT_MATCHMAKER = _matchmakerContract;
s_isOnChain[0] = true; // Needed so people can use id 0 for challenging
}
/**
* @dev Docs at ReferencesInitializer.sol
*/
function initializeReferences(address[] calldata _references) external override initializeActions {
i_RECEIVER_ADDRESS = _references[0];
}
//******************** */
// EXTERNAL FUNCTIONS
//******************** */
/**
* @dev Docs at ICcipNftBridge.sol
*/
function sendNft(uint256 _nftId) external payable contractIsInitialized {
bytes memory codedCcipMessage =
abi.encodeWithSignature(HANDLE_RECEIVE_NFT_FUNCTION_SIG, _nftId, msg.sender, getPromptOf(_nftId));
// Also checks for ownership etc...
_updateNftStateOnSend(_nftId);
// Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message
Client.EVM2AnyMessage memory evm2AnyMessage =
_buildCCIPMessage(i_RECEIVER_ADDRESS, codedCcipMessage, address(0));
// Initialize a router client instance to interact with cross-chain router
IRouterClient router = IRouterClient(this.getRouter());
// Get the fee required to send the CCIP message
uint256 fees = router.getFee(i_DESTINATION_CHAIN_SELECTOR, evm2AnyMessage);
require(fees <= msg.value, "Not enought ETH sent.");
// Send the CCIP message through the router and store the returned CCIP message ID
bytes32 messageId = router.ccipSend{value: fees}(i_DESTINATION_CHAIN_SELECTOR, evm2AnyMessage);
emit ICCIPNftBridge__NftSent(msg.sender, i_DESTINATION_CHAIN_SELECTOR, _nftId, messageId, block.timestamp);
}
/**
* @dev Docs at ICcipNftBridge.sol
*/
function setIsNftFighting(uint256 nftId, bool isFightihng) external contractIsInitialized onlyMatchmaker {
s_isFighting[nftId] = isFightihng;
emit ICCIPNftBridge__NftIsFightingChanged(nftId, isFightihng, block.timestamp);
}
//******************** */
// INTERNAL FUNCTIONS
//******************** */
/**
* @dev Function from Chainlink CCIP used to handle received messages.
*
* In this case it uses a low-level call to call _updateNftStateOnReceive().
*/
function _ccipReceive(Client.Any2EVMMessage memory _message) internal /*virtual*/ override {
require(_message.sourceChainSelector == i_DESTINATION_CHAIN_SELECTOR, "We only accept messages from 1 chain.");
address sender = abi.decode(_message.sender, (address)); // abi-decoding of the orginal msg.sender
require(sender == i_RECEIVER_ADDRESS, "Only accept messages from collection contract.");
// data corresponds to calling => _updateNftStateOnReceive()
(bool success,) = address(this).call(_message.data);
require(success, "Something failed updating NFT state.");
}
/**
* @dev Builds the CCIP message struct to send.
*/
function _buildCCIPMessage(address _receiver, bytes memory _encodedMessage, address _feeTokenAddress)
internal
pure
returns (Client.EVM2AnyMessage memory)
{
// Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message
return Client.EVM2AnyMessage({
receiver: abi.encode(_receiver), // ABI-encoded receiver address
data: _encodedMessage, // ABI-encoded string
tokenAmounts: new Client.EVMTokenAmount[](0), // Empty array aas no tokens are transferred
extraArgs: Client._argsToBytes(
// Additional arguments, setting gas limit and non-strict sequencing mode
Client.EVMExtraArgsV1({gasLimit: 200_000, strict: false})
),
// Set the feeToken to a feeTokenAddress, indicating specific asset will be used for fees
feeToken: _feeTokenAddress
});
}
/**
* @dev Checks ownership and manages state of NFTs when they are sent.
*
* Calls _updateNftStateOnSendChainSpecifics() to hanlde different procedures
* for NFT state changes depending on the chain.
*/
function _updateNftStateOnSend(uint256 _nftId) internal {
// Checks
require(getOwnerOf(_nftId) == msg.sender, "You are not the owner.");
require(!s_isFighting[_nftId], "Nft is bussy can't move.");
require(s_isOnChain[_nftId], "Nft is not currently in this chain.");
// Updates
delete s_isOnChain[_nftId];
delete s_isFighting[_nftId];
_updateNftStateOnSendChainSpecifics(_nftId);
}
/**
* @dev Only callable by this contract.
* Sets NFT state to on-chain and calls _updateNftStateOnReceiveChainSpecifics()
* for chain specific state updates.
*/
function _updateNftStateOnReceive(uint256 _nftId, address _owner, string memory _prompt) external {
require(msg.sender == address(this));
s_isOnChain[_nftId] = true;
delete s_isFighting[_nftId]; // set to false
_updateNftStateOnReceiveChainSpecifics(_nftId, _owner, _prompt);
emit ICCIPNftBridge__NftReceived(_owner, i_DESTINATION_CHAIN_SELECTOR, _nftId, block.timestamp);
}
/**
* @dev Different chains might track Nfts state differently.
* Example: collection chain doesnt need to update ownerships or prompts as
* they are by default always there.
*/
function _updateNftStateOnSendChainSpecifics(uint256 _nftId) internal virtual;
/**
* @dev Different chains might track Nfts state differently.
* Example: collection chain doesnt need to update ownerships or prompts as
* they are by default always there.
*/
function _updateNftStateOnReceiveChainSpecifics(uint256 _nftId, address _owner, string memory _prompt)
internal
virtual;
// Internal Setters
/**
* @dev Needed to have a generalized way trhough chains of tracking ownership.
*/
function _setOwnerOf(uint256 _nftId, address _owner) internal virtual;
/**
* @dev Needed to have a generalized way trhough chains of tracking prompts.
*/
function _setPromptOf(uint256 _nftId, string memory _prompt) internal virtual;
//************************ */
// VIEW / PURE FUNCTIONS
//************************ */
// @notice Overriden so in the contracts with inheriting conflict they can still
// access CCIPReceiver its IERC165.
function supportsInterface(bytes4 interfaceId)
public
view
/**
* @notice Changed to view for Chainlink Functions compatibility.
*/
virtual
override
returns (bool)
{
return super.supportsInterface(interfaceId);
}
// Getters
/**
* @dev Docs at ICcipNftBridge.sol
*/
function getOwnerOf(uint256 _nftId) public view virtual returns (address);
/**
* @dev Docs at ICcipNftBridge.sol
*/
function getPromptOf(uint256 _nftId) public view virtual returns (string memory);
function getIsNftFighting(uint256 _nftId) public view returns (bool) {
return s_isFighting[_nftId];
}
function getIsNftOnChain(uint256 _nftId) public view returns (bool) {
return s_isOnChain[_nftId];
}
function getMatchmaker() public view returns (address) {
return address(i_FIGHT_MATCHMAKER);
}
function getReceiverAddress() public view returns (address) {
return address(i_RECEIVER_ADDRESS);
}
function getDetinationChainSelector() public view returns (uint64) {
return i_DESTINATION_CHAIN_SELECTOR;
}
function getHanldeRecieveFuncSig() public pure returns (string memory) {
return HANDLE_RECEIVE_NFT_FUNCTION_SIG;
}
}