diff --git a/.gitignore b/.gitignore index 85198aa..79ed947 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ docs/ # Dotenv file .env +node_modules diff --git a/.gitmodules b/.gitmodules index 888d42d..72a7dbc 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,9 @@ [submodule "lib/forge-std"] path = lib/forge-std url = https://github.com/foundry-rs/forge-std +[submodule "lib/ccip"] + path = lib/ccip + url = https://github.com/smartcontractkit/ccip +[submodule "lib/chainlink-local"] + path = lib/chainlink-local + url = https://github.com/smartcontractkit/chainlink-local diff --git a/foundry.toml b/foundry.toml index 25b918f..e763b00 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,6 +1,18 @@ [profile.default] +viaIrl = true + src = "src" out = "out" libs = ["lib"] - -# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options +remappings = ['@chainlink/local=lib/chainlink-local/src/ccip/'] +solc = '0.8.27' +[rpc_endpoints] +ethereumSepolia = "${ETHEREUM_SEPOLIA_RPC_URL}" +optimismGoerli = "${OPTIMISM_GOERLI_RPC_URL}" +avalancheFuji = "${AVALANCHE_FUJI_RPC_URL}" +arbitrumSepolia = "${ARBITRUM_SEPOLIA_RPC_URL}" +polygonMumbai = "${POLYGON_MUMBAI_RPC_URL}" +bnbChainTestnet = "${BNB_CHAIN_TESTNET_RPC_URL}" +baseSepolia = "${BASE_SEPOLIA_RPC_URL}" +metisSepolia = "${METIS_SEPOLIA_RPC_URL}" +zksyncSepolia = "${ZKSYNC_SEPOLIA_RPC_URL}" diff --git a/lcov.info b/lcov.info new file mode 100644 index 0000000..f05bd37 --- /dev/null +++ b/lcov.info @@ -0,0 +1,126 @@ +TN: +SF:src/Answer.sol +FN:21,Answer. +FNDA:3,Answer. +DA:22,3 +DA:23,3 +DA:24,3 +FN:27,Answer._ccipReceive +FNDA:3,Answer._ccipReceive +DA:28,3 +DA:29,3 +DA:31,3 +DA:33,3 +DA:41,3 +DA:42,3 +DA:44,3 +FN:47,Answer.getLastReceivedMessageDetails +FNDA:3,Answer.getLastReceivedMessageDetails +DA:52,3 +FN:55,Answer.previewFees +FNDA:3,Answer.previewFees +DA:60,6 +DA:61,6 +FN:64,Answer.answerTier +FNDA:0,Answer.answerTier +DA:68,3 +DA:70,3 +DA:72,3 +BRDA:72,0,0,- +BRDA:72,0,1,3 +DA:77,3 +DA:78,3 +DA:79,3 +DA:83,3 +FN:86,Answer.mountMessage +FNDA:9,Answer.mountMessage +DA:91,9 +DA:99,9 +FNF:6 +FNH:5 +LF:22 +LH:22 +BRF:2 +BRH:1 +end_of_record +TN: +SF:src/Ask.sol +FN:17,Ask. +FNDA:3,Ask. +DA:18,3 +DA:19,3 +FN:22,Ask.previewFees +FNDA:3,Ask.previewFees +DA:27,6 +DA:28,6 +FN:31,Ask.askTier +FNDA:3,Ask.askTier +DA:35,3 +DA:36,3 +DA:38,3 +BRDA:38,0,0,- +BRDA:38,0,1,3 +DA:43,3 +DA:44,3 +DA:45,3 +DA:49,3 +FN:52,Ask._ccipReceive +FNDA:3,Ask._ccipReceive +DA:53,3 +DA:55,3 +FN:58,Ask.mountMessage +FNDA:9,Ask.mountMessage +DA:59,9 +DA:67,9 +FNF:5 +FNH:5 +LF:15 +LH:15 +BRF:2 +BRH:1 +end_of_record +TN: +SF:src/Points.sol +FN:7,Points.grantPoints +FNDA:9,Points.grantPoints +DA:8,9 +FNF:1 +FNH:1 +LF:1 +LH:1 +BRF:0 +BRH:0 +end_of_record +TN: +SF:src/Tiers.sol +FN:13,Tiers. +FNDA:3,Tiers. +DA:14,3 +FN:23,Tiers.addTier +FNDA:15,Tiers.addTier +DA:24,15 +DA:25,15 +DA:26,15 +FN:34,Tiers.getTier +FNDA:3,Tiers.getTier +DA:36,3 +DA:39,3 +DA:41,10 +DA:42,3 +BRDA:42,0,0,3 +DA:43,3 +DA:48,0 +FN:56,Tiers.getTierByName +FNDA:0,Tiers.getTierByName +DA:57,0 +DA:59,0 +DA:60,0 +DA:61,0 +DA:65,0 +FNF:4 +FNH:3 +LF:15 +LH:9 +BRF:1 +BRH:1 +end_of_record diff --git a/lib/ccip b/lib/ccip new file mode 160000 index 0000000..065ef85 --- /dev/null +++ b/lib/ccip @@ -0,0 +1 @@ +Subproject commit 065ef85b5fa52bb4583ebad9e51c20a977b64e7a diff --git a/lib/chainlink-local b/lib/chainlink-local new file mode 160000 index 0000000..ba1f463 --- /dev/null +++ b/lib/chainlink-local @@ -0,0 +1 @@ +Subproject commit ba1f4636e657f161df634379a5057a5a394e2fbb diff --git a/script/Counter.s.sol b/script/Counter.s.sol deleted file mode 100644 index cdc1fe9..0000000 --- a/script/Counter.s.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {Script, console} from "forge-std/Script.sol"; -import {Counter} from "../src/Counter.sol"; - -contract CounterScript is Script { - Counter public counter; - - function setUp() public {} - - function run() public { - vm.startBroadcast(); - - counter = new Counter(); - - vm.stopBroadcast(); - } -} diff --git a/src/Answer.sol b/src/Answer.sol new file mode 100644 index 0000000..10edcdc --- /dev/null +++ b/src/Answer.sol @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +import {console} from "forge-std/Test.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 {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol"; +import {IERC20} from + "@chainlink/contracts-ccip/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; +import {ICrossChainMessages} from "./interfaces/ICrossChainMessages.sol"; +import {ITiers} from "./interfaces/ITiers.sol"; + +contract Answer is CCIPReceiver { + bytes32 private lastReceivedMessageId; + address private lastReceivedWallet; + string private lastAnswer; + ITiers private samuraiTiers; + IRouterClient private router; + IERC20 private linkToken; + + constructor(address _router, address _samuraiTiers, address _link) CCIPReceiver(_router) { + router = IRouterClient(_router); + samuraiTiers = ITiers(_samuraiTiers); + linkToken = IERC20(_link); + } + + function _ccipReceive(Client.Any2EVMMessage memory any2EvmMessage) internal override { + lastReceivedMessageId = any2EvmMessage.messageId; + lastReceivedWallet = abi.decode(any2EvmMessage.data, (address)); + + address receiver = abi.decode(any2EvmMessage.sender, (address)); + + emit ICrossChainMessages.MessageReceived( + any2EvmMessage.messageId, + any2EvmMessage.sourceChainSelector, + abi.decode(any2EvmMessage.sender, (address)), + abi.decode(any2EvmMessage.data, (address)), + "" + ); + + ITiers.Tier memory tier = samuraiTiers.getTier(lastReceivedWallet); + lastAnswer = tier.name; + + answerTier(any2EvmMessage.sourceChainSelector, receiver, lastReceivedWallet, tier.name); + } + + function getLastReceivedMessageDetails() + external + view + returns (bytes32 messageId, address wallet, string memory answer) + { + return (lastReceivedMessageId, lastReceivedWallet, lastAnswer); + } + + function previewFees(uint64 destinationChainSelector, address receiver, address wallet, string memory tierName) + public + view + returns (uint256) + { + Client.EVM2AnyMessage memory evm2AnyMessage = mountMessage(receiver, wallet, tierName); + return router.getFee(destinationChainSelector, evm2AnyMessage); + } + + function answerTier(uint64 destinationChainSelector, address receiver, address wallet, string memory tierName) + public + returns (bytes32 messageId) + { + Client.EVM2AnyMessage memory evm2AnyMessage = mountMessage(receiver, wallet, tierName); + + uint256 fees = previewFees(destinationChainSelector, receiver, wallet, tierName); + + require( + fees <= linkToken.balanceOf(address(this)), + ICrossChainMessages.NotEnoughBalance(linkToken.balanceOf(address(this)), fees) + ); + + linkToken.approve(address(router), fees); + messageId = router.ccipSend(destinationChainSelector, evm2AnyMessage); + emit ICrossChainMessages.MessageSent( + messageId, destinationChainSelector, receiver, wallet, tierName, address(linkToken), fees + ); + + return messageId; + } + + function mountMessage(address receiver, address wallet, string memory tierName) + private + view + returns (Client.EVM2AnyMessage memory) + { + Client.EVM2AnyMessage memory evm2AnyMessage = Client.EVM2AnyMessage({ + receiver: abi.encode(receiver), + data: abi.encode(wallet, tierName), + tokenAmounts: new Client.EVMTokenAmount[](0), + extraArgs: Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: 500_000})), + feeToken: address(linkToken) + }); + + return evm2AnyMessage; + } +} diff --git a/src/Ask.sol b/src/Ask.sol new file mode 100644 index 0000000..bb5748e --- /dev/null +++ b/src/Ask.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +import {CCIPReceiver} from "@chainlink/contracts-ccip/src/v0.8/ccip/applications/CCIPReceiver.sol"; +import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol"; +import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol"; +import {IERC20} from + "@chainlink/contracts-ccip/src/v0.8/vendor/openzeppelin-solidity/v4.8.3/contracts/token/ERC20/IERC20.sol"; +import {ICrossChainMessages} from "./interfaces/ICrossChainMessages.sol"; +import {console} from "forge-std/Test.sol"; + +contract Ask is CCIPReceiver { + IRouterClient private router; + IERC20 private linkToken; + mapping(address wallet => string name) public tiers; + + constructor(address _router, address _link) CCIPReceiver(_router) { + router = IRouterClient(_router); + linkToken = IERC20(_link); + } + + function previewFees(uint64 destinationChainSelector, address receiver, address wallet) + public + view + returns (uint256) + { + Client.EVM2AnyMessage memory evm2AnyMessage = mountMessage(receiver, wallet); + return router.getFee(destinationChainSelector, evm2AnyMessage); + } + + function askTier(uint64 destinationChainSelector, address receiver, address wallet) + external + returns (bytes32 messageId) + { + Client.EVM2AnyMessage memory evm2AnyMessage = mountMessage(receiver, wallet); + uint256 fees = previewFees(destinationChainSelector, receiver, wallet); + + require( + fees <= linkToken.balanceOf(address(this)), + ICrossChainMessages.NotEnoughBalance(linkToken.balanceOf(address(this)), fees) + ); + + linkToken.approve(address(router), fees); + messageId = router.ccipSend(destinationChainSelector, evm2AnyMessage); + emit ICrossChainMessages.MessageSent( + messageId, destinationChainSelector, receiver, wallet, "", address(linkToken), fees + ); + + return messageId; + } + + function _ccipReceive(Client.Any2EVMMessage memory any2EvmMessage) internal override { + (address wallet, string memory tierName) = abi.decode(any2EvmMessage.data, (address, string)); + + tiers[wallet] = tierName; + } + + function mountMessage(address receiver, address wallet) private view returns (Client.EVM2AnyMessage memory) { + Client.EVM2AnyMessage memory evm2AnyMessage = Client.EVM2AnyMessage({ + receiver: abi.encode(receiver), + data: abi.encode(wallet), + tokenAmounts: new Client.EVMTokenAmount[](0), + extraArgs: Client._argsToBytes(Client.EVMExtraArgsV1({gasLimit: 500_000})), + feeToken: address(linkToken) + }); + + return evm2AnyMessage; + } +} diff --git a/src/Counter.sol b/src/Counter.sol deleted file mode 100644 index aded799..0000000 --- a/src/Counter.sol +++ /dev/null @@ -1,14 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -contract Counter { - uint256 public number; - - function setNumber(uint256 newNumber) public { - number = newNumber; - } - - function increment() public { - number++; - } -} diff --git a/src/Points.sol b/src/Points.sol new file mode 100644 index 0000000..4d6602a --- /dev/null +++ b/src/Points.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.27; + +contract Points { + mapping(address wallet => uint256 walletPoints) public walletsPoints; + + function grantPoints(address wallet, uint256 points) external { + walletsPoints[wallet] = points; + } +} diff --git a/src/Tiers.sol b/src/Tiers.sol new file mode 100644 index 0000000..c0287d5 --- /dev/null +++ b/src/Tiers.sol @@ -0,0 +1,67 @@ +//SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.27; + +import {ITiers} from "./interfaces/ITiers.sol"; +import {IPoints} from "./interfaces/IPoints.sol"; + +contract Tiers { + IPoints iPoints; + uint256 public counter; + + mapping(uint256 index => ITiers.Tier tier) public tiers; + + constructor(address _points) { + iPoints = IPoints(_points); + } + + /** + * @notice Adds a new tier. + * @param name: tier name. + * @param min: min amount to be part of tier. + * @param max: max amount to be part of tier. + */ + function addTier(string memory name, uint256 min, uint256 max) external { + uint256 index = counter + 1; + tiers[index] = ITiers.Tier(name, min, max); + counter++; + } + + /** + * @notice Gets the tier a wallet belongs to based on Sam NFT holdings, lockups, and LP staking. + * @param wallet: Address of the wallet to check. + * @return tier: The tier information for the wallet. + */ + function getTier(address wallet) public view returns (ITiers.Tier memory) { + // Get wallet points + uint256 walletPoints = iPoints.walletsPoints(wallet); + + // Iterate through tiers to find a matching tier + for (uint256 i = 1; i <= counter; i++) { + // start from 1 because tiers mapping starts from 1 + ITiers.Tier memory tier = tiers[i]; + if ((walletPoints >= tier.min && walletPoints <= tier.max)) { + return tier; + } + } + + // If no tier matches, return a blank tier + return ITiers.Tier("", 0, 0); + } + + /** + * @notice Gets the tier information by its name. + * @param name: Name of the tier to get. + * @return tier: The tier information matching the name. + */ + function getTierByName(string memory name) public view returns (ITiers.Tier memory) { + bytes32 nameHash = keccak256(abi.encodePacked(name)); + + for (uint256 i = 1; i <= counter; i++) { + bytes32 tierNameHash = keccak256(abi.encodePacked(tiers[i].name)); + if (nameHash == tierNameHash) return tiers[i]; + } + + // If no tier matches, return a blank tier + return ITiers.Tier("", 0, 0); + } +} diff --git a/src/interfaces/ICrossChainMessages.sol b/src/interfaces/ICrossChainMessages.sol new file mode 100644 index 0000000..824cb05 --- /dev/null +++ b/src/interfaces/ICrossChainMessages.sol @@ -0,0 +1,31 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.27; + +interface ICrossChainMessages { + // Custom errors to provide more descriptive revert messages. + error NotEnoughBalance(uint256 currentBalance, uint256 calculatedFees); // Used to make sure contract has enough balance. + + enum PayFeesIn { + Native, + LINK + } + + struct Answer { + address wallet; + string tierName; + } + + event MessageSent( + bytes32 indexed messageId, + uint64 indexed destinationChainSelector, + address receiver, + address wallet, + string answer, + address feeToken, + uint256 fees + ); + + event MessageReceived( + bytes32 indexed messageId, uint64 indexed sourceChainSelector, address sender, address wallet, string answer + ); +} diff --git a/src/interfaces/IPoints.sol b/src/interfaces/IPoints.sol new file mode 100644 index 0000000..70bf290 --- /dev/null +++ b/src/interfaces/IPoints.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.27; + +interface IPoints { + function walletsPoints(address wallet) external view returns (uint256 points); +} diff --git a/src/interfaces/ITiers.sol b/src/interfaces/ITiers.sol new file mode 100644 index 0000000..d5220b4 --- /dev/null +++ b/src/interfaces/ITiers.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.27; + +interface ITiers { + struct Tier { + string name; + uint256 min; + uint256 max; + } + + function getTier(address wallet) external view returns (ITiers.Tier memory); +} diff --git a/test/AskAndAnswerTier.t.sol b/test/AskAndAnswerTier.t.sol new file mode 100644 index 0000000..d1eb59d --- /dev/null +++ b/test/AskAndAnswerTier.t.sol @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.27; + +import {Test, console} from "forge-std/Test.sol"; +import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol"; +import {IRouterClient, IERC20} from "@chainlink/local/CCIPLocalSimulator.sol"; +import {CCIPLocalSimulatorFork, Register} from "@chainlink/local/CCIPLocalSimulatorFork.sol"; +import {Ask} from "../src/Ask.sol"; +import {Answer} from "../src/Answer.sol"; +import {Points} from "../src/Points.sol"; +import {Tiers} from "../src/Tiers.sol"; +import {ITiers} from "../src/interfaces/ITiers.sol"; + +contract AskAndAnswerTierTest is Test { + CCIPLocalSimulatorFork public ccipLocalSimulatorFork; + uint256 public sourceFork; + uint256 public destinationFork; + IRouterClient sourceRouter; + IRouterClient destinationRouter; + IERC20 sourceLinkToken; + IERC20 destinationLinkToken; + + Ask public ask; + Answer public answer; + + uint64 public sourceChainSelector; + uint64 public destinationChainSelector; + + address bob; + address mary; + address john; + + function grantInitialPoints(Points points) public { + points.grantPoints(bob, 20_000 ether); + points.grantPoints(mary, 150_000 ether); + points.grantPoints(john, 201_000 ether); + } + + function addInitialTiers(Tiers tiers) public { + ITiers.Tier memory Jeet = ITiers.Tier("Jeet", 15_000 ether, 29_999 ether); + ITiers.Tier memory Average = ITiers.Tier("Average", 30_000 ether, 59_999 ether); + ITiers.Tier memory Cool = ITiers.Tier("Cool", 60_000 ether, 99_999 ether); + ITiers.Tier memory BigCheese = ITiers.Tier("BigCheese", 100_000 ether, 199_999 ether); + ITiers.Tier memory Chad = ITiers.Tier("Chad", 200_000 ether, 999_999_999 ether); + + tiers.addTier(Jeet.name, Jeet.min, Jeet.max); + tiers.addTier(Average.name, Average.min, Average.max); + tiers.addTier(Cool.name, Cool.min, Cool.max); + tiers.addTier(BigCheese.name, BigCheese.min, BigCheese.max); + tiers.addTier(Chad.name, Chad.min, Chad.max); + } + + function setUp() public { + bob = vm.addr(1); + vm.label(bob, "bob"); + + mary = vm.addr(2); + vm.label(mary, "mary"); + + john = vm.addr(3); + vm.label(john, "john"); + + string memory SOURCE_RPC_URL = vm.envString("OPTIMISM_SEPOLIA_RPC_URL"); + string memory DESTINATION_RPC_URL = vm.envString("BASE_SEPOLIA_RPC_URL"); + + sourceFork = vm.createFork(SOURCE_RPC_URL); + destinationFork = vm.createSelectFork(DESTINATION_RPC_URL); + + ccipLocalSimulatorFork = new CCIPLocalSimulatorFork(); + vm.makePersistent(address(ccipLocalSimulatorFork)); + + Register.NetworkDetails memory destinationNetworkDetails = + ccipLocalSimulatorFork.getNetworkDetails(block.chainid); + + destinationRouter = IRouterClient(destinationNetworkDetails.routerAddress); + destinationChainSelector = destinationNetworkDetails.chainSelector; + destinationLinkToken = IERC20(destinationNetworkDetails.linkAddress); + + Points points = new Points(); + grantInitialPoints(points); + + Tiers tiers = new Tiers(address(points)); + addInitialTiers(tiers); + + answer = new Answer(address(destinationRouter), address(tiers), address(destinationLinkToken)); + vm.makePersistent(address(answer)); + + vm.selectFork(sourceFork); + Register.NetworkDetails memory sourceNetworkDetails = ccipLocalSimulatorFork.getNetworkDetails(block.chainid); + sourceRouter = IRouterClient(sourceNetworkDetails.routerAddress); + sourceChainSelector = sourceNetworkDetails.chainSelector; + sourceLinkToken = IERC20(sourceNetworkDetails.linkAddress); + ask = new Ask(address(sourceRouter), address(sourceLinkToken)); + vm.makePersistent(address(ask)); + } + + function test_bob_CanCheckTierCrossChain() external { + uint256 previewedFeesForAsking = ask.previewFees(destinationChainSelector, address(answer), bob); + deal(address(sourceLinkToken), address(ask), previewedFeesForAsking); + bytes32 messageId = ask.askTier(destinationChainSelector, address(answer), bob); + + vm.selectFork(destinationFork); + uint256 previewedFeesForAnswering = answer.previewFees(sourceChainSelector, address(ask), bob, "Jeet"); + deal(address(destinationLinkToken), address(answer), previewedFeesForAnswering); + ccipLocalSimulatorFork.switchChainAndRouteMessage(destinationFork); + (bytes32 latestMessageId, address latestMessage, string memory lastAnswer) = + answer.getLastReceivedMessageDetails(); + + vm.selectFork(sourceFork); + ccipLocalSimulatorFork.switchChainAndRouteMessage(sourceFork); + + assertEq(latestMessageId, messageId); + assertEq(latestMessage, bob); + assertEq(ask.tiers(bob), lastAnswer); + assertEq(lastAnswer, "Jeet"); + } + + function test_mary_CanCheckTierCrossChain() external { + uint256 previewedFeesForAsking = ask.previewFees(destinationChainSelector, address(answer), bob); + deal(address(sourceLinkToken), address(ask), previewedFeesForAsking); + bytes32 messageId = ask.askTier(destinationChainSelector, address(answer), mary); + + vm.selectFork(destinationFork); + uint256 previewedFeesForAnswering = answer.previewFees(sourceChainSelector, address(ask), bob, "BigCheese"); + deal(address(destinationLinkToken), address(answer), previewedFeesForAnswering); + ccipLocalSimulatorFork.switchChainAndRouteMessage(destinationFork); + (bytes32 latestMessageId, address latestMessage, string memory lastAnswer) = + answer.getLastReceivedMessageDetails(); + + vm.selectFork(sourceFork); + ccipLocalSimulatorFork.switchChainAndRouteMessage(sourceFork); + + assertEq(latestMessageId, messageId); + assertEq(latestMessage, mary); + assertEq(ask.tiers(mary), lastAnswer); + assertEq(lastAnswer, "BigCheese"); + } + + function test_john_CanCheckTierCrossChain() external { + uint256 previewedFeesForAsking = ask.previewFees(destinationChainSelector, address(answer), bob); + deal(address(sourceLinkToken), address(ask), previewedFeesForAsking); + bytes32 messageId = ask.askTier(destinationChainSelector, address(answer), john); + + vm.selectFork(destinationFork); + uint256 previewedFeesForAnswering = answer.previewFees(sourceChainSelector, address(ask), bob, "Chad"); + deal(address(destinationLinkToken), address(answer), previewedFeesForAnswering); + ccipLocalSimulatorFork.switchChainAndRouteMessage(destinationFork); + (bytes32 latestMessageId, address latestMessage, string memory lastAnswer) = + answer.getLastReceivedMessageDetails(); + + vm.selectFork(sourceFork); + ccipLocalSimulatorFork.switchChainAndRouteMessage(sourceFork); + + assertEq(latestMessageId, messageId); + assertEq(latestMessage, john); + assertEq(ask.tiers(john), lastAnswer); + assertEq(lastAnswer, "Chad"); + } +} diff --git a/test/Counter.t.sol b/test/Counter.t.sol deleted file mode 100644 index 54b724f..0000000 --- a/test/Counter.t.sol +++ /dev/null @@ -1,24 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.13; - -import {Test, console} from "forge-std/Test.sol"; -import {Counter} from "../src/Counter.sol"; - -contract CounterTest is Test { - Counter public counter; - - function setUp() public { - counter = new Counter(); - counter.setNumber(0); - } - - function test_Increment() public { - counter.increment(); - assertEq(counter.number(), 1); - } - - function testFuzz_SetNumber(uint256 x) public { - counter.setNumber(x); - assertEq(counter.number(), x); - } -}