-
Notifications
You must be signed in to change notification settings - Fork 13
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
base: main
Are you sure you want to change the base?
Changes from 12 commits
4ae6659
680352e
4926b94
fa8513d
ec6ca80
3012c35
30047c3
1a56696
88fffb1
bf666b5
11dc5fe
1c9f6a9
929a846
bf6ece2
03d0bf2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
pragma solidity ^0.8.24; | ||
|
||
import {Internal} from "../libraries/Internal.sol"; | ||
import {OffRamp} from "../offRamp/OffRamp.sol"; | ||
|
||
import {IERC20} from "@chainlink/vendor/openzeppelin-solidity/v5.0.2/contracts/token/ERC20/IERC20.sol"; | ||
import {SafeERC20} from "@chainlink/vendor/openzeppelin-solidity/v5.0.2/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 */ | ||
jhweintraub marked this conversation as resolved.
Show resolved
Hide resolved
|
||
contract CCIPSendTestScript is Script { | ||
using SafeERC20 for IERC20; | ||
|
||
error ManualExecutionFailed(); | ||
error ManualExecutionNotAllowed(); | ||
|
||
// Ex: "ETHEREUM_RPC_URL" as defined in .env | ||
string public RPC_IDENTIFIER; | ||
|
||
OffRamp public s_offRamp; | ||
|
||
bytes32 public s_messageId; | ||
|
||
uint64 public s_sourceChainSelector; | ||
uint64 public s_sequenceNumber; | ||
bytes public s_manualExecutionData; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm assuming a user is supposed to fill every param here? There's no docs on anything so not 100% sure. How useful is this script if you need to construct the tx manually? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Better to inject the values using env There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
function run() public { | ||
vm.createSelectFork(RPC_IDENTIFIER); | ||
jhweintraub marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
uint256 privateKey = vm.envUint("PRIVATE_KEY"); | ||
|
||
address sender = vm.rememberKey(privateKey); | ||
|
||
vm.startBroadcast(privateKey); | ||
jhweintraub marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
console.log("Sender: %s", sender); | ||
console.log("Starting Script..."); | ||
|
||
// Check that the messageId is not empty | ||
Internal.MessageExecutionState executionState = s_offRamp.getExecutionState(s_sourceChainSelector, s_sequenceNumber); | ||
if ( | ||
executionState != Internal.MessageExecutionState.FAILURE | ||
&& executionState != Internal.MessageExecutionState.UNTOUCHED | ||
) revert ManualExecutionNotAllowed(); | ||
|
||
// Manual Execution data can be invoked from a different tool or front-end to avoid having to | ||
// gather execution report data manually | ||
(bool success,) = address(s_offRamp).call(s_manualExecutionData); | ||
if (!success) revert ManualExecutionFailed(); | ||
|
||
executionState = s_offRamp.getExecutionState(s_sourceChainSelector, s_sequenceNumber); | ||
if (executionState != Internal.MessageExecutionState.SUCCESS) revert ManualExecutionFailed(); | ||
|
||
console.log("Script completed..."); | ||
} | ||
} | ||
/* solhint-enable no-console */ | ||
jhweintraub marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
pragma solidity ^0.8.24; | ||
|
||
import {Router} from "../Router.sol"; | ||
import {Client} from "../libraries/Client.sol"; | ||
|
||
import {IERC20} from "@chainlink/vendor/openzeppelin-solidity/v5.0.2/contracts/token/ERC20/IERC20.sol"; | ||
import {SafeERC20} from "@chainlink/vendor/openzeppelin-solidity/v5.0.2/contracts/token/ERC20/utils/SafeERC20.sol"; | ||
|
||
jhweintraub marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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 */ | ||
contract CCIPSendTestScript is Script { | ||
using SafeERC20 for IERC20; | ||
|
||
address public ROUTER; | ||
address public FEE_TOKEN; | ||
|
||
address public TOKEN0; | ||
uint256 public TOKEN0_AMOUNT; | ||
|
||
uint64 public DESTINATION_CHAIN_SELECTOR; | ||
uint64 public SOURCE_CHAIN_SELECTOR; | ||
|
||
// Ex: "ETHEREUM_RPC_URL" as defined in .env | ||
string public RPC_DESCRIPTOR; | ||
|
||
bytes public s_extraArgs; | ||
bytes public s_recipient; | ||
bytes public s_data; | ||
jhweintraub marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
function run() public { | ||
vm.createSelectFork(RPC_DESCRIPTOR); | ||
|
||
uint256 privateKey = vm.envUint("PRIVATE_KEY"); | ||
|
||
address sender = vm.rememberKey(privateKey); | ||
s_recipient = abi.encode(sender); | ||
jhweintraub marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
vm.startBroadcast(privateKey); | ||
|
||
console.log("Sender: %s", sender); | ||
console.log("Starting Script..."); | ||
|
||
// Create the EVMTokenAmount array and populate with the first token | ||
Client.EVMTokenAmount[] memory tokens; | ||
if (TOKEN0 != address(0)) { | ||
tokens = new Client.EVMTokenAmount[](1); | ||
tokens[0] = Client.EVMTokenAmount({token: TOKEN0, amount: TOKEN0_AMOUNT}); | ||
} | ||
|
||
// Construct the EVM2AnyMessage | ||
Client.EVM2AnyMessage memory message = Client.EVM2AnyMessage({ | ||
receiver: abi.encode(sender), | ||
jhweintraub marked this conversation as resolved.
Show resolved
Hide resolved
|
||
data: s_data, | ||
tokenAmounts: tokens, | ||
feeToken: address(0), | ||
jhweintraub marked this conversation as resolved.
Show resolved
Hide resolved
|
||
extraArgs: s_extraArgs | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Better to take specific extra args fields as input (maybe through env) and build the struct and encode it in this script There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. While that would be better, as the number of different extra Args structs grows as we add more non-evm chains this may become unwieldy and unnecessary complex. This pattern is also present in some of our other example contracts such as PingPong so I feel like it is reasonable to expect the user to generate this themselves if required. |
||
}); | ||
|
||
uint256 fee = Router(ROUTER).getFee(DESTINATION_CHAIN_SELECTOR, message); | ||
|
||
console.log("Fee in WEI: %s", fee); | ||
|
||
console.log("1. Approving Send Tokens..."); | ||
|
||
for (uint256 i = 0; i < tokens.length; i++) { | ||
// Since sender may be an EOA with an existing approval, the allowance should be checked first | ||
uint256 allowance = IERC20(tokens[i].token).allowance(sender, ROUTER); | ||
|
||
// Approving Tokens for Router if allowance is currently insufficient. | ||
if (allowance < tokens[i].amount) { | ||
console.log("Approving %i tokens to Router for %s", tokens[i].amount, tokens[i].token); | ||
IERC20(tokens[i].token).safeIncreaseAllowance(ROUTER, tokens[i].amount); | ||
} | ||
} | ||
|
||
console.log("--- Tokens Approved ---"); | ||
|
||
console.log("2. Approving Fee Token"); | ||
if (FEE_TOKEN != address(0)) { | ||
console.log("Approving Fee Token %s to Router", FEE_TOKEN); | ||
IERC20(FEE_TOKEN).safeIncreaseAllowance(ROUTER, fee); | ||
} | ||
// --- Fee Token Approved --- | ||
|
||
console.log("3. Sending message from: %i to %i", SOURCE_CHAIN_SELECTOR, DESTINATION_CHAIN_SELECTOR); | ||
|
||
// Send the message, forwarding native tokens if necessary to pay the fee | ||
bytes32 messageId = | ||
Router(ROUTER).ccipSend{value: FEE_TOKEN == address(0) ? fee : 0}(DESTINATION_CHAIN_SELECTOR, message); | ||
|
||
console.log("--- Message sent: MessageId ---"); | ||
|
||
console.logBytes32(messageId); | ||
vm.stopBroadcast(); | ||
|
||
console.log("Script completed..."); | ||
} | ||
} | ||
/* solhint-enable no-console */ |
Uh oh!
There was an error while loading. Please reload this page.