-
Notifications
You must be signed in to change notification settings - Fork 406
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
26 changed files
with
617 additions
and
35 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+43.2 KB
...p/tutorials/ccip-explorer-send-tokens-message-manual-execution-confirmation.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+161 KB
...s/ccip/tutorials/ccip-explorer-send-tokens-message-manual-execution-success.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+683 KB
.../ccip/tutorials/ccip-explorer-send-tokens-message-manual-execution-tenderl1.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+203 KB
...ccip/tutorials/ccip-explorer-send-tokens-message-manual-execution-tenderly2.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+153 KB
...utorials/ccip-explorer-send-tokens-message-pay-link-low-gaslimit-tx-details.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+180 KB
...cip-explorer-send-tokens-message-pay-link-tx-details-ready-manual-execution.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+18.5 KB
public/images/ccip/tutorials/mumbai-token-messagedetails-pay-link-failed.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+20.1 KB
...c/images/ccip/tutorials/mumbai-token-messagedetails-pay-link-manual-success.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
266 changes: 266 additions & 0 deletions
266
public/samples/CCIP/ProgrammableTokenTransfersLowGasLimit.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,266 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity 0.8.19; | ||
|
||
import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol"; | ||
import {OwnerIsCreator} from "@chainlink/contracts-ccip/src/v0.8/shared/access/OwnerIsCreator.sol"; | ||
import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol"; | ||
import {CCIPReceiver} from "@chainlink/contracts-ccip/src/v0.8/ccip/applications/CCIPReceiver.sol"; | ||
import {IERC20} from "@chainlink/contracts-ccip/src/v0.8/vendor/openzeppelin-solidity/v4.8.0/contracts/token/ERC20/IERC20.sol"; | ||
|
||
/** | ||
* THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED VALUES FOR CLARITY. | ||
* THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE. | ||
* DO NOT USE THIS CODE IN PRODUCTION. | ||
*/ | ||
|
||
/// @title - A simple messenger contract for transferring/receiving tokens and data across chains. | ||
contract ProgrammableTokenTransfersLowGasLimit is CCIPReceiver, OwnerIsCreator { | ||
// Custom errors to provide more descriptive revert messages. | ||
error NotEnoughBalance(uint256 currentBalance, uint256 calculatedFees); // Used to make sure contract has enough balance to cover the fees. | ||
error NothingToWithdraw(); // Used when trying to withdraw Ether but there's nothing to withdraw. | ||
error DestinationChainNotAllowed(uint64 destinationChainSelector); // Used when the destination chain has not been allowlisted by the contract owner. | ||
error SourceChainNotAllowed(uint64 sourceChainSelector); // Used when the source chain has not been allowlisted by the contract owner. | ||
error SenderNotAllowed(address sender); // Used when the sender has not been allowlisted by the contract owner. | ||
|
||
// Event emitted when a message is sent to another chain. | ||
event MessageSent( | ||
bytes32 indexed messageId, // The unique ID of the CCIP message. | ||
uint64 indexed destinationChainSelector, // The chain selector of the destination chain. | ||
address receiver, // The address of the receiver on the destination chain. | ||
string text, // The text being sent. | ||
address token, // The token address that was transferred. | ||
uint256 tokenAmount, // The token amount that was transferred. | ||
address feeToken, // the token address used to pay CCIP fees. | ||
uint256 fees // The fees paid for sending the message. | ||
); | ||
|
||
// Event emitted when a message is received from another chain. | ||
event MessageReceived( | ||
bytes32 indexed messageId, // The unique ID of the CCIP message. | ||
uint64 indexed sourceChainSelector, // The chain selector of the source chain. | ||
address sender, // The address of the sender from the source chain. | ||
string text, // The text that was received. | ||
address token, // The token address that was transferred. | ||
uint256 tokenAmount // The token amount that was transferred. | ||
); | ||
|
||
bytes32 private s_lastReceivedMessageId; // Store the last received messageId. | ||
address private s_lastReceivedTokenAddress; // Store the last received token address. | ||
uint256 private s_lastReceivedTokenAmount; // Store the last received amount. | ||
string private s_lastReceivedText; // Store the last received text. | ||
|
||
// Mapping to keep track of allowlisted destination chains. | ||
mapping(uint64 => bool) public allowlistedDestinationChains; | ||
|
||
// Mapping to keep track of allowlisted source chains. | ||
mapping(uint64 => bool) public allowlistedSourceChains; | ||
|
||
// Mapping to keep track of allowlisted senders. | ||
mapping(address => bool) public allowlistedSenders; | ||
|
||
IERC20 private s_linkToken; | ||
|
||
/// @notice Constructor initializes the contract with the router address. | ||
/// @param _router The address of the router contract. | ||
/// @param _link The address of the link contract. | ||
constructor(address _router, address _link) CCIPReceiver(_router) { | ||
s_linkToken = IERC20(_link); | ||
} | ||
|
||
/// @dev Modifier that checks if the chain with the given destinationChainSelector is allowlisted. | ||
/// @param _destinationChainSelector The selector of the destination chain. | ||
modifier onlyAllowlistedDestinationChain(uint64 _destinationChainSelector) { | ||
if (!allowlistedDestinationChains[_destinationChainSelector]) | ||
revert DestinationChainNotAllowed(_destinationChainSelector); | ||
_; | ||
} | ||
|
||
/// @dev Modifier that checks if the chain with the given sourceChainSelector is allowlisted and if the sender is allowlisted. | ||
/// @param _sourceChainSelector The selector of the destination chain. | ||
/// @param _sender The address of the sender. | ||
modifier onlyAllowlisted(uint64 _sourceChainSelector, address _sender) { | ||
if (!allowlistedSourceChains[_sourceChainSelector]) | ||
revert SourceChainNotAllowed(_sourceChainSelector); | ||
if (!allowlistedSenders[_sender]) revert SenderNotAllowed(_sender); | ||
_; | ||
} | ||
|
||
/// @dev Updates the allowlist status of a destination chain for transactions. | ||
/// @notice This function can only be called by the owner. | ||
/// @param _destinationChainSelector The selector of the destination chain to be updated. | ||
/// @param allowed The allowlist status to be set for the destination chain. | ||
function allowlistDestinationChain( | ||
uint64 _destinationChainSelector, | ||
bool allowed | ||
) external onlyOwner { | ||
allowlistedDestinationChains[_destinationChainSelector] = allowed; | ||
} | ||
|
||
/// @dev Updates the allowlist status of a source chain | ||
/// @notice This function can only be called by the owner. | ||
/// @param _sourceChainSelector The selector of the source chain to be updated. | ||
/// @param allowed The allowlist status to be set for the source chain. | ||
function allowlistSourceChain( | ||
uint64 _sourceChainSelector, | ||
bool allowed | ||
) external onlyOwner { | ||
allowlistedSourceChains[_sourceChainSelector] = allowed; | ||
} | ||
|
||
/// @dev Updates the allowlist status of a sender for transactions. | ||
/// @notice This function can only be called by the owner. | ||
/// @param _sender The address of the sender to be updated. | ||
/// @param allowed The allowlist status to be set for the sender. | ||
function allowlistSender(address _sender, bool allowed) external onlyOwner { | ||
allowlistedSenders[_sender] = allowed; | ||
} | ||
|
||
/// @notice Sends data and transfer tokens to receiver on the destination chain. | ||
/// @notice Pay for fees in LINK. | ||
/// @notice the gasLimit is set to 20_000 on purpose to force the execution to fail on the destination chain | ||
/// @dev Assumes your contract has sufficient LINK to pay for CCIP fees. | ||
/// @param _destinationChainSelector The identifier (aka selector) for the destination blockchain. | ||
/// @param _receiver The address of the recipient on the destination blockchain. | ||
/// @param _text The string data to be sent. | ||
/// @param _token token address. | ||
/// @param _amount token amount. | ||
/// @return messageId The ID of the CCIP message that was sent. | ||
function sendMessagePayLINK( | ||
uint64 _destinationChainSelector, | ||
address _receiver, | ||
string calldata _text, | ||
address _token, | ||
uint256 _amount | ||
) | ||
external | ||
onlyOwner | ||
onlyAllowlistedDestinationChain(_destinationChainSelector) | ||
returns (bytes32 messageId) | ||
{ | ||
// Set the token amounts | ||
Client.EVMTokenAmount[] | ||
memory tokenAmounts = new Client.EVMTokenAmount[](1); | ||
tokenAmounts[0] = Client.EVMTokenAmount({ | ||
token: _token, | ||
amount: _amount | ||
}); | ||
|
||
// Create an EVM2AnyMessage struct in memory with necessary information for sending a cross-chain message | ||
// address(linkToken) means fees are paid in LINK | ||
|
||
Client.EVM2AnyMessage memory evm2AnyMessage = Client.EVM2AnyMessage({ | ||
receiver: abi.encode(_receiver), // ABI-encoded receiver address | ||
data: abi.encode(_text), // ABI-encoded string | ||
tokenAmounts: tokenAmounts, // The amount and type of token being transferred | ||
extraArgs: Client._argsToBytes( | ||
// gasLimit set to 20_000 on purpose to force the execution to fail on the destination chain | ||
Client.EVMExtraArgsV1({gasLimit: 20_000}) | ||
), | ||
// Set the feeToken to a LINK token address | ||
feeToken: address(s_linkToken) | ||
}); | ||
|
||
// 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(_destinationChainSelector, evm2AnyMessage); | ||
|
||
if (fees > s_linkToken.balanceOf(address(this))) | ||
revert NotEnoughBalance(s_linkToken.balanceOf(address(this)), fees); | ||
|
||
// approve the Router to transfer LINK tokens on contract's behalf. It will spend the fees in LINK | ||
s_linkToken.approve(address(router), fees); | ||
|
||
// approve the Router to spend tokens on contract's behalf. It will spend the amount of the given token | ||
IERC20(_token).approve(address(router), _amount); | ||
|
||
// Send the message through the router and store the returned message ID | ||
messageId = router.ccipSend(_destinationChainSelector, evm2AnyMessage); | ||
|
||
// Emit an event with message details | ||
emit MessageSent( | ||
messageId, | ||
_destinationChainSelector, | ||
_receiver, | ||
_text, | ||
_token, | ||
_amount, | ||
address(s_linkToken), | ||
fees | ||
); | ||
|
||
// Return the message ID | ||
return messageId; | ||
} | ||
|
||
/** | ||
* @notice Returns the details of the last CCIP received message. | ||
* @dev This function retrieves the ID, text, token address, and token amount of the last received CCIP message. | ||
* @return messageId The ID of the last received CCIP message. | ||
* @return text The text of the last received CCIP message. | ||
* @return tokenAddress The address of the token in the last CCIP received message. | ||
* @return tokenAmount The amount of the token in the last CCIP received message. | ||
*/ | ||
function getLastReceivedMessageDetails() | ||
public | ||
view | ||
returns ( | ||
bytes32 messageId, | ||
string memory text, | ||
address tokenAddress, | ||
uint256 tokenAmount | ||
) | ||
{ | ||
return ( | ||
s_lastReceivedMessageId, | ||
s_lastReceivedText, | ||
s_lastReceivedTokenAddress, | ||
s_lastReceivedTokenAmount | ||
); | ||
} | ||
|
||
/// handle a received message | ||
function _ccipReceive( | ||
Client.Any2EVMMessage memory any2EvmMessage | ||
) | ||
internal | ||
override | ||
onlyAllowlisted( | ||
any2EvmMessage.sourceChainSelector, | ||
abi.decode(any2EvmMessage.sender, (address)) | ||
) // Make sure source chain and sender are allowlisted | ||
{ | ||
s_lastReceivedMessageId = any2EvmMessage.messageId; // fetch the messageId | ||
s_lastReceivedText = abi.decode(any2EvmMessage.data, (string)); // abi-decoding of the sent text | ||
// Expect one token to be transferred at once, but you can transfer several tokens. | ||
s_lastReceivedTokenAddress = any2EvmMessage.destTokenAmounts[0].token; | ||
s_lastReceivedTokenAmount = any2EvmMessage.destTokenAmounts[0].amount; | ||
|
||
emit MessageReceived( | ||
any2EvmMessage.messageId, | ||
any2EvmMessage.sourceChainSelector, // fetch the source chain identifier (aka selector) | ||
abi.decode(any2EvmMessage.sender, (address)), // abi-decoding of the sender address, | ||
abi.decode(any2EvmMessage.data, (string)), | ||
any2EvmMessage.destTokenAmounts[0].token, | ||
any2EvmMessage.destTokenAmounts[0].amount | ||
); | ||
} | ||
|
||
/// @notice Allows the owner of the contract to withdraw all tokens of a specific ERC20 token. | ||
/// @dev This function reverts with a 'NothingToWithdraw' error if there are no tokens to withdraw. | ||
/// @param _beneficiary The address to which the tokens will be sent. | ||
/// @param _token The contract address of the ERC20 token to be withdrawn. | ||
function withdrawToken( | ||
address _beneficiary, | ||
address _token | ||
) public onlyOwner { | ||
// Retrieve the balance of this contract | ||
uint256 amount = IERC20(_token).balanceOf(address(this)); | ||
|
||
// Revert if there is nothing to withdraw | ||
if (amount == 0) revert NothingToWithdraw(); | ||
|
||
IERC20(_token).transfer(_beneficiary, amount); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
--- | ||
section: ccip | ||
date: Last Modified | ||
title: "CCIP Manual Execution" | ||
whatsnext: | ||
{ "Manual execution guide": "/ccip/tutorials/manual-execution", "Learn CCIP best practices": "/ccip/best-practices" } | ||
--- | ||
|
||
import { Aside, ClickToZoom } from "@components" | ||
|
||
<Aside type="note" title="Prerequisites"> | ||
Read the CCIP [Concepts](/ccip/concepts) and [Architecture](/ccip/architecture) pages to understand all the concepts | ||
discussed on this page. | ||
</Aside> | ||
|
||
CCIP messages are eligible for manual execution if either of these conditions is met: | ||
|
||
- Execution of the CCIP message on the destination blockchain has failed. | ||
- The CCIP message timed out, with the current timeout threshold set at 8 hours. This scenario may arise during periods of extreme network congestion when CCIP cannot deliver the message within the allotted time frame. | ||
|
||
The flowchart presented below provides a visual guide through the process of a cross-chain transaction, emphasizing the steps involved in the manual execution: | ||
|
||
<br /> | ||
|
||
<ClickToZoom src="/images/ccip/manual-execution.png" alt="Chainlink CCIP manual execution flowchart" /> | ||
|
||
## CCIP execution | ||
|
||
1. A sender (a contract or EOA) initiates a CCIP message on the source blockchain. | ||
1. [CCIP Committing DON](/ccip/architecture#committing-don) awaits [finality](/ccip/concepts#finality) on the source blockchain. | ||
1. Post finality, the [CCIP Committing DON](/ccip/architecture#committing-don) assembles a batch of transactions, computes a Merkle root, and records it to the [CommitStore contract](/ccip/architecture#commit-store) on the destination blockchain. | ||
1. Upon successful verification, the [Risk Management Network](/ccip/concepts#risk-management-network) blesses the committed Merkle root. | ||
1. Once the committed Merkle root is blessed, the [CCIP Executing DON](/ccip/architecture#executing-don) proceeds with the execution on the destination blockchain: | ||
1. If the CCIP message is older than 8 hours (timeout), then the **manual execution is enabled** (further details are provided below). | ||
1. If the CCIP message is within the time frame, the Executing DON assesses and adjusts the gas price to ensure the transaction’s execution on the destination blockchain. | ||
1. The execution on the destination blockchain works as follows: | ||
|
||
1. The CCIP initially determines whether the message involves token transfers. If affirmative, the tokens are transferred to the receiver to facilitate programmable transfers ahead of contract logic execution. | ||
1. For an EOA, the CCIP transaction is completed successfully. | ||
1. If the receiver is a contract, its [ccipReceive](/ccip/api-reference/ccip-receiver#ccipreceive) function is invoked. | ||
|
||
**Note:** The transaction is atomic, meaning that it will either fully succeed, with all steps completed, or fail entirely, with none of the steps taking effect. If the transaction fails, the **manual execution is enabled** (further details are provided below). | ||
|
||
## Manual execution | ||
|
||
In instances where a transaction on the destination blockchain fails or a CCIP message times out, you can refer to procedure below. **Note:** Any account can manually execute a CCIP message that is eligible for manual execution, but the executing account must have sufficient native gas tokens (such as ETH on Ethereum or MATIC on Polygon) to cover the gas costs associated with the delivery of the CCIP message. | ||
|
||
1. Use the [CCIP explorer](https://ccip.chain.link/) to retry the transaction execution on the destination blockchain manually. **Note:** Manual execution is expected to be available. If it is unavailable, please get in touch with Chainlink Labs Support for assistance. | ||
1. Manual execution may be helpful under two scenarios: | ||
|
||
1. Insufficient gas limit: The [gas limit](/ccip/api-reference/client#evmextraargsv1) set in the CCIP message is insufficient to call the receiver. Then, increase the gas limit. | ||
1. Receiver logic error: The failure was due to an implementation error in the receiver contract. Developers must fix the implementation and then deploy a new version. **Note:** This corrective measure only applies if the receiver contract is upgradeable and uses the [proxy pattern](https://blog.openzeppelin.com/proxy-patterns). Non-upgradable contracts will not benefit from manual execution in this context. | ||
|
||
<Aside type="note" title="Recommendation"> | ||
An important best practice is to separate the reception of CCIP messages from the core business logic of the | ||
contract. Implementing 'escape hatches' or fallback mechanisms is recommended to gracefully manage situations where | ||
the business logic encounters issues. To explore this concept further, refer to the [Defensive | ||
example](/ccip/tutorials/programmable-token-transfers-defensive). | ||
</Aside> | ||
|
||
1. Once the underlying issue has been addressed—such as by adjusting the gas limit—users can trigger the manual execution through the CCIP explorer. |
Oops, something went wrong.