-
Notifications
You must be signed in to change notification settings - Fork 17
add simple foundry scripts for live testing #825
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
jhweintraub
wants to merge
18
commits into
main
Choose a base branch
from
feat/foundry_testingScripts
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
4ae6659
add simple foundry scripts for off-chain testing
jhweintraub 680352e
forge fmt and update github action to exclude scripts
jhweintraub 4926b94
Merge branch 'main' into feat/foundry_testingScripts
jhweintraub fa8513d
update github action to exclude scripts from coverage requirements
jhweintraub ec6ca80
Merge branch 'main' into feat/foundry_testingScripts
jhweintraub 3012c35
attempt fix coverage requirement
jhweintraub 30047c3
forge fmt
jhweintraub 1a56696
Merge branch 'main' into feat/foundry_testingScripts
jhweintraub 88fffb1
adjust coverage CI policy
jhweintraub bf666b5
Merge branch 'main' into feat/foundry_testingScripts
jhweintraub 11dc5fe
Merge branch 'main' into feat/foundry_testingScripts
jhweintraub 1c9f6a9
fix imports
jhweintraub 929a846
formalize scripts to make public
jhweintraub bf6ece2
Merge branch 'main' into feat/foundry_testingScripts
jhweintraub 03d0bf2
move user provided values to storage not inside function call
jhweintraub 5738c0d
contract fixes
jhweintraub 39f9a92
Merge branch 'main' into feat/foundry_testingScripts
jhweintraub a330897
remove bad design
jhweintraub File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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 |
---|---|---|
@@ -1,3 +1,4 @@ | ||
# Test files run with a different solhint ruleset, ignore them here. | ||
./**/*.t.sol | ||
./**/*.s.sol | ||
./node_modules/ |
89 changes: 89 additions & 0 deletions
89
chains/evm/contracts/scripts/CCIPManualExecutionScript.s.sol
This file contains hidden or 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,89 @@ | ||
pragma solidity ^0.8.24; | ||
|
||
import {Router} from "../Router.sol"; | ||
import {Internal} from "../libraries/Internal.sol"; | ||
import {OffRamp} from "../offRamp/OffRamp.sol"; | ||
import {Script} from "forge-std/Script.sol"; | ||
// solhint-disable-next-line no-console | ||
import {console2 as console} from "forge-std/console2.sol"; | ||
|
||
// solhint-disable no-console | ||
/// @title CCIPManualExecutionScript | ||
/// @notice A foundry script for manually executing undelivered messages on a destination chain in CCIP. | ||
/// @dev This script has NOT been audited and is NOT intended for production usage. It is intended only for | ||
/// local debugging and testing with existing deployed contracts. | ||
/// @dev Usage: "forge script scripts/CCIPManualExecutionScript.s.sol:CCIPManualExecutionScript -vvvv" | ||
contract CCIPManualExecutionScript is Script { | ||
error ManualExecutionFailed(); | ||
error ManualExecutionNotAllowed(); | ||
error InvalidRouter(); | ||
error InvalidSourceChainSelector(); | ||
error InvalidSequenceNumber(); | ||
error InvalidManualExecutionData(); | ||
|
||
string public rpcUrl; | ||
address public router; | ||
|
||
uint64 public sourceChainSelector; // The CCIP chain selector the message originated from | ||
|
||
// Define the sequence number of the message to be manually executed. The sequence number can be found | ||
// on the CCIP-Explorer page for the given message. | ||
// Note: The OffRamp will use the sequencer number and NOT the messageId to acquire message status, so if this | ||
// value is set incorrectly, unexpected behavior may result by this script. | ||
uint64 public sequenceNumber; | ||
|
||
// Define the manual execution data to be used. Given that manual execution data can be hard to derive manually, | ||
// due to the existence of offChain data and various proof flags, the data for manual execution should be acquired | ||
// from the CCIP-Explorer webpage or the ccip-tools-ts repository on Github (https://github.com/smartcontractkit/ccip-tools-ts) | ||
// Manual Execution Tutorial: https://docs.chain.link/ccip/tutorials/manual-execution#trigger-manual-execution | ||
// Note: The manual execution data should be formatted as calldata to be sent to the OffRamp and invoke the | ||
// function "manuallyExecute" | ||
bytes public manualExecutionData; | ||
|
||
function run() public { | ||
if (router == address(0)) revert InvalidRouter(); | ||
if (sourceChainSelector == 0) revert InvalidSourceChainSelector(); | ||
if (sequenceNumber == 0) revert InvalidSequenceNumber(); | ||
if (manualExecutionData.length == 0) revert InvalidManualExecutionData(); | ||
|
||
vm.createSelectFork(rpcUrl); | ||
|
||
|
||
// Acquire the private key from the .env file and derive address | ||
uint256 privateKey = vm.envUint("PRIVATE_KEY"); | ||
address sender = vm.rememberKey(privateKey); | ||
|
||
console.log("Sender: %s", sender); | ||
console.log("Starting Script..."); | ||
|
||
address offRamp; | ||
Router.OffRamp[] memory offRamps = Router(router).getOffRamps(); | ||
|
||
// Perform a linear search for the offRamp contract using the known source chain selector. | ||
// Note: Given that this operation is being performed off-chain, and thus gas is not a consideration, | ||
// a linear search is an acceptable time-complexity. | ||
for (uint256 i = 0; i < offRamps.length; ++i) { | ||
if (offRamps[i].sourceChainSelector == sourceChainSelector) { | ||
offRamp = offRamps[i].offRamp; | ||
break; | ||
} | ||
} | ||
|
||
Internal.MessageExecutionState executionState = | ||
OffRamp(offRamp).getExecutionState(sourceChainSelector, sequenceNumber); | ||
if ( | ||
executionState != Internal.MessageExecutionState.FAILURE | ||
&& executionState != Internal.MessageExecutionState.UNTOUCHED | ||
) revert ManualExecutionNotAllowed(); | ||
|
||
// Attempt the call to the offRamp to manually execute the message. | ||
vm.startBroadcast(privateKey); | ||
(bool success,) = address(offRamp).call(manualExecutionData); | ||
vm.stopBroadcast(); | ||
|
||
// Revert if the execution was not successful. | ||
if (!success) revert ManualExecutionFailed(); | ||
executionState = OffRamp(offRamp).getExecutionState(sourceChainSelector, sequenceNumber); | ||
if (executionState != Internal.MessageExecutionState.SUCCESS) revert ManualExecutionFailed(); | ||
} | ||
} |
This file contains hidden or 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,129 @@ | ||
pragma solidity ^0.8.24; | ||
|
||
import {Router} from "../Router.sol"; | ||
import {Client} from "../libraries/Client.sol"; | ||
|
||
import {IERC20} from "@chainlink/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; | ||
import {SafeERC20} from "@chainlink/contracts/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/utils/SafeERC20.sol"; | ||
|
||
import {Script} from "forge-std/Script.sol"; | ||
|
||
// solhint-disable-next-line no-console | ||
import {console2 as console} from "forge-std/console2.sol"; | ||
|
||
/* solhint-disable no-console */ | ||
/// @title CCIPSendTestScript | ||
/// @notice This is a foundry script for sending messages through CCIP. | ||
/// @dev This script has NOT been audited, and is NOT intended for use in production. It is intended to aid in | ||
/// local debugging and testing with existing deployed contracts. | ||
/// @dev Usage: "forge script scripts/CCIPSendTestScript.s.sol:CCIPSendTestScript" | ||
contract CCIPSendTestScript is Script { | ||
using SafeERC20 for IERC20; | ||
|
||
error ChainNotSupported(uint64 destChainSelector); | ||
error InvalidRPCURL(); | ||
error InvalidRouter(); | ||
error InvalidDestChainSelector(); | ||
error InvalidRecipient(); | ||
|
||
// For the script to run successfully, please define the following constants below | ||
// [REQUIRED] | ||
uint64 public destChainSelector; // The CCIP-Specific chain selector to send the message to | ||
uint256 public numTokens; // The number of tokens to be sent | ||
|
||
bytes public recipient; // The recipient to receive both the tokens and the arbitrary data. | ||
bytes public data; // Define the message data to be passed to the recipient if it is NOT an EOA | ||
bytes public extraArgs; // If any extraArgs are needed, define below. Due to different chains families having different extraArgs formats, they should be passed as raw bytes, and encoded separately. | ||
|
||
address public router; | ||
string public rpcUrl; | ||
|
||
|
||
address public feeToken; // The token to pay CCIP Message Fees in, address(0) for native. | ||
|
||
function run() public { | ||
if (router == address(0)) revert InvalidRouter(); | ||
if (destChainSelector == 0) revert InvalidDestChainSelector(); | ||
if (recipient.length == 0) revert InvalidRecipient(); | ||
|
||
// Acquire the private key from the .env file and derive address | ||
vm.createSelectFork(rpcUrl); | ||
|
||
uint256 privateKey = vm.envUint("PRIVATE_KEY"); | ||
address sender = vm.rememberKey(privateKey); | ||
|
||
vm.startBroadcast(privateKey); | ||
|
||
console.log("Sender: %s", sender); | ||
console.log("Starting Script..."); | ||
|
||
Client.EVM2AnyMessage memory message; | ||
|
||
// Scoping to prevent a "stack-too-deep" error. | ||
{ | ||
// bool isSupported = Router(router).isChainSupported(destChainSelector); | ||
// if (!isSupported) revert ChainNotSupported(destChainSelector); | ||
|
||
address[] memory tokenAddresses = new address[](numTokens); | ||
uint256[] memory tokenAmounts = new uint256[](numTokens); | ||
|
||
// Since solidity does not support array literals being defined in storage or constant, manually define the addresses and amounts of each token that should be sent. They will automatically | ||
// be converted into a Client.EVMTokenAmount format for the CCIP-Message. | ||
// Ex: tokenAddresses[0] = address(1); | ||
// Ex: tokenAmounts[0] = 1e18; | ||
// [INSERT HERE] | ||
|
||
console.log("Approving Send Tokens..."); | ||
|
||
Client.EVMTokenAmount[] memory tokens = new Client.EVMTokenAmount[](numTokens); | ||
for (uint256 i = 0; i < tokens.length; i++) { | ||
if (tokenAddresses[i] != address(0)) { | ||
// Since the sender may be an EOA with an existing approval, the allowance is checked first. | ||
uint256 allowance = IERC20(tokenAddresses[i]).allowance(sender, router); | ||
|
||
// If the existing allowance is insufficient, increase it to allow sending through CCIP. | ||
if (allowance < tokenAmounts[i]) { | ||
console.log("Approving %i tokens to Router for %s", tokenAmounts[i], tokenAddresses[i]); | ||
|
||
// For reasons unknown, the safeApprove/safeIncreaseAllowance function does not work. | ||
IERC20(tokenAddresses[i]).approve(router, tokenAmounts[i]); | ||
} | ||
|
||
// Once approval is granted, copy into the EVM Token Amount Array to be included in the message-proper. | ||
tokens[i] = Client.EVMTokenAmount({token: tokenAddresses[i], amount: tokenAmounts[i]}); | ||
console.log("Token %i: %s", i, tokenAddresses[i]); | ||
console.log("Token Amount %i: %s", i, tokenAmounts[i]); | ||
} | ||
} | ||
console.log("--- Tokens Approved ---"); | ||
|
||
// Construct the EVM2AnyMessage using the fields defined above. | ||
message = Client.EVM2AnyMessage({ | ||
receiver: recipient, | ||
data: data, | ||
tokenAmounts: tokens, | ||
feeToken: feeToken, | ||
extraArgs: extraArgs | ||
}); | ||
} | ||
|
||
// Calculate the fee in WEI for the message and approve the router if necessary. | ||
// Note: Even if the token is not native, it will still be provided in WEI. | ||
uint256 fee = Router(router).getFee(destChainSelector, message); | ||
console.log("Fee in WEI: %s", fee); | ||
if (feeToken != address(0)) { | ||
console.log("Approving Fee Token %s to Router", feeToken); | ||
IERC20(feeToken).safeIncreaseAllowance(router, fee); | ||
} | ||
|
||
// Send the message, forwarding native tokens if necessary to pay the fee. | ||
console.log("Sending message to %i", destChainSelector); | ||
bytes32 messageId = Router(router).ccipSend{value: feeToken == address(0) ? fee : 0}(destChainSelector, message); | ||
|
||
// Foundry's console library does not support including bytes32 as a parameter so it is printed separately. | ||
console.log("--- Message sent: MessageId ---"); | ||
console.logBytes32(messageId); | ||
|
||
vm.stopBroadcast(); | ||
} | ||
} |
This file contains hidden or 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 hidden or 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
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.