diff --git a/.gitignore b/.gitignore index 24dc6e7..63d21d9 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,8 @@ node_modules # solidity-coverage files /coverage -/coverage.json \ No newline at end of file +/coverage.json + +# Foundry files +/out +/cache_forge \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index 1da071b..565cd0a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "lib/vibc-core-smart-contracts"] path = lib/vibc-core-smart-contracts url = https://github.com/open-ibc/vibc-core-smart-contracts +[submodule "lib/forge-std"] + path = lib/forge-std + url = https://github.com/foundry-rs/forge-std diff --git a/README.md b/README.md index d5132e6..01ad593 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,14 @@ # Template for IBC enabled Soldity contracts -This tutorial enables to send an IBC packet from an "Xcounter" contract on either OP or Base. The packet will ensure that a counter variable on either contract remains in sync. +This repo provides a starter project to build [IBC](https://github.com/cosmos/ibc) enabled Solidity contracts that connect rollups to one another Polymer Hub, through the [vIBC core contracts](https://github.com/open-ibc/vibc-core-smart-contracts). + +The repo is compatible with both Hardhat and Foundry development environments. + +Find more information on building with (v)IBC and Polymer in the [Polymer documentation](https://docs.polymerlabs.org). ## Install dependencies -To use the quickstart tutorial, make sure that you have all dependencies installed. +To compile your contracts and start testing, make sure that you have all dependencies installed. From the root directory run: ```bash @@ -32,30 +36,33 @@ There's three types of scripts in the project: - `deploy.js` and `deploy-config.js` allow you to deploy your application contract - `create-channel.js` and `create-channel-config.js` creates a channel -- `send-packet.js` sends packets over an existing channel +- `send-packet.js` and `send-universal-packet.js` sends packets over an existing channel (custom or universal). For every script you'll find a field in the config.json!! -Make sure to update the config with the intended files before running one of the scripts like so: -```bash -npx hardhat run scripts/send-packet.js --network optimism -``` - -**NOTE** Make sure to align the `--network` flag value to be compatible with your config values either on optimism or base. - -## Deploy +### Deploy Run: ```bash -# format node scripts/deploy-config.js [source] [destination] -node scripts/deploy-config.js optimism base +# format node scripts/deploy-config.js [source] [destination] [universal-channel-bool] +node scripts/deploy-config.js optimism base true ``` +for an application that will use a universal channel, or: +```bash +# or +node scripts/deploy-config.js optimism base false +``` +for an application that uses custom channels. To deploy instances of the contracts on optimism as the source and base as the destination chains. (You can also switch the order) Also this script will take the output of the deployment and update the config file with all the relevant information. -Then run: +### Create a channel + +In case you're using universal channels, you can skip this step and move on the sending packets. + +To create a custom channel, run: ```bash node scripts/create-channel-config.js ``` @@ -66,8 +73,17 @@ Also this script will take the output of the channel creation and update the con Check out the [channel tab in the explorer](https://explorer.prod.testnet.polymer.zone/channels) to find out if the correct channel-id's related to your contracts were updated in the config. -Finally run: +### Send packets +Finally Run: +```bash +npx hardhat run scripts/send-universal-packet.js --network optimism +``` +to send a packet over a **universal channel**. You can pick either optimism or base to send the packet from. + +Or run: ```bash npx hardhat run scripts/send-packet.js --network optimism ``` -to send a packet. You can pick either optimism or base to send the packet from. \ No newline at end of file +to send a packet over a **custom channel**. You can pick either optimism or base to send the packet from. + +**NOTE** Make sure to align the `--network` flag value to be compatible with your config values either on optimism or base. \ No newline at end of file diff --git a/contracts/CustomChanIbcContract.sol b/contracts/CustomChanIbcContract.sol index c7d1904..de79c18 100644 --- a/contracts/CustomChanIbcContract.sol +++ b/contracts/CustomChanIbcContract.sol @@ -2,9 +2,10 @@ pragma solidity ^0.8.9; -import '../lib/vibc-core-smart-contracts/contracts/Ibc.sol'; -import '../lib/vibc-core-smart-contracts/contracts/IbcReceiver.sol'; -import '../lib/vibc-core-smart-contracts/contracts/IbcDispatcher.sol'; +import '@open-ibc/vibc-core-smart-contracts/contracts/Ibc.sol'; +import '@open-ibc/vibc-core-smart-contracts/contracts/IbcReceiver.sol'; +import '@open-ibc/vibc-core-smart-contracts/contracts/IbcDispatcher.sol'; +import '@open-ibc/vibc-core-smart-contracts/contracts/ProofVerifier.sol'; contract CustomChanIbcContract is IbcReceiverBase, IbcReceiver { // received packet as chain B @@ -31,6 +32,10 @@ contract CustomChanIbcContract is IbcReceiverBase, IbcReceiver { dispatcher = _dispatcher; } + function getConnectedChannels() external view returns (ChannelMapping[] memory) { + return connectedChannels; + } + /** * @dev Sends a packet with a greeting message over a specified channel. * @param message The greeting message to be sent. @@ -66,35 +71,33 @@ contract CustomChanIbcContract is IbcReceiverBase, IbcReceiver { * @param proof not implemented for now */ function createChannel( - string calldata version, + CounterParty calldata local, uint8 ordering, bool feeEnabled, string[] calldata connectionHops, CounterParty calldata counterparty, - Proof calldata proof + Ics23Proof calldata proof ) external { dispatcher.openIbcChannel( IbcChannelReceiver(address(this)), - version, + local, ChannelOrder(ordering), feeEnabled, connectionHops, counterparty, proof ); - } + } function onOpenIbcChannel( string calldata version, - ChannelOrder ordering, - bool feeEnabled, - string[] calldata connectionHops, - string calldata counterpartyPortId, - bytes32 counterpartyChannelId, - string calldata counterpartyVersion - ) external onlyIbcDispatcher returns (string memory selectedVersion) { - if (bytes(counterpartyPortId).length <= 8) { + ChannelOrder, + bool, + string[] calldata, + CounterParty calldata counterparty + ) external view onlyIbcDispatcher returns (string memory selectedVersion) { + if (bytes(counterparty.portId).length <= 8) { revert invalidCounterPartyPortId(); } /** @@ -105,7 +108,7 @@ contract CustomChanIbcContract is IbcReceiverBase, IbcReceiver { */ bool foundVersion = false; selectedVersion = keccak256(abi.encodePacked(version)) == keccak256(abi.encodePacked('')) - ? counterpartyVersion + ? counterparty.version : version; for (uint256 i = 0; i < supportedVersions.length; i++) { if (keccak256(abi.encodePacked(selectedVersion)) == keccak256(abi.encodePacked(supportedVersions[i]))) { @@ -115,9 +118,9 @@ contract CustomChanIbcContract is IbcReceiverBase, IbcReceiver { } require(foundVersion, 'Unsupported version'); // if counterpartyVersion is not empty, then it must be the same foundVersion - if (keccak256(abi.encodePacked(counterpartyVersion)) != keccak256(abi.encodePacked(''))) { + if (keccak256(abi.encodePacked(counterparty.version)) != keccak256(abi.encodePacked(''))) { require( - keccak256(abi.encodePacked(counterpartyVersion)) == keccak256(abi.encodePacked(selectedVersion)), + keccak256(abi.encodePacked(counterparty.version)) == keccak256(abi.encodePacked(selectedVersion)), 'Version mismatch' ); } @@ -151,11 +154,7 @@ contract CustomChanIbcContract is IbcReceiverBase, IbcReceiver { connectedChannels.push(channelMapping); } - function onCloseIbcChannel( - bytes32 channelId, - string calldata counterpartyPortId, - bytes32 counterpartyChannelId - ) external onlyIbcDispatcher { + function onCloseIbcChannel(bytes32 channelId, string calldata, bytes32) external onlyIbcDispatcher { // logic to determin if the channel should be closed bool channelFound = false; for (uint256 i = 0; i < connectedChannels.length; i++) { diff --git a/contracts/UniversalChanIbcContract.sol b/contracts/UniversalChanIbcContract.sol index bdaf6d4..2ede699 100644 --- a/contracts/UniversalChanIbcContract.sol +++ b/contracts/UniversalChanIbcContract.sol @@ -2,10 +2,10 @@ pragma solidity ^0.8.9; -import '../lib/vibc-core-smart-contracts/contracts/Ibc.sol'; -import '../lib/vibc-core-smart-contracts/contracts/IbcReceiver.sol'; -import '../lib/vibc-core-smart-contracts/contracts/IbcDispatcher.sol'; -import '../lib/vibc-core-smart-contracts/contracts/IbcMiddleware.sol'; +import '@open-ibc/vibc-core-smart-contracts/contracts/Ibc.sol'; +import '@open-ibc/vibc-core-smart-contracts/contracts/IbcReceiver.sol'; +import '@open-ibc/vibc-core-smart-contracts/contracts/IbcDispatcher.sol'; +import '@open-ibc/vibc-core-smart-contracts/contracts/IbcMiddleware.sol'; contract UniversalChanIbcContract is IbcMwUser, IbcUniversalPacketReceiver { struct UcPacketWithChannel { @@ -35,7 +35,7 @@ contract UniversalChanIbcContract is IbcMwUser, IbcUniversalPacketReceiver { function sendGreet(address destPortAddr, bytes32 channelId, bytes calldata message, uint64 timeoutTimestamp) external { IbcUniversalPacketSender(mw).sendUniversalPacket( channelId, - Ibc.toBytes32(destPortAddr), + IbcUtils.toBytes32(destPortAddr), message, timeoutTimestamp ); @@ -48,7 +48,7 @@ contract UniversalChanIbcContract is IbcMwUser, IbcUniversalPacketReceiver { recvedPackets.push(UcPacketWithChannel(channelId, packet)); // do logic // below is an example, the actual ackpacket data should be implemented by the contract developer - return AckPacket(true, abi.encodePacked(address(this), Ibc.toAddress(packet.srcPortAddr), 'ack-', packet.appData)); + return AckPacket(true, abi.encodePacked(address(this), IbcUtils.toAddress(packet.srcPortAddr), 'ack-', packet.appData)); } function onUniversalAcknowledgement( @@ -60,7 +60,7 @@ contract UniversalChanIbcContract is IbcMwUser, IbcUniversalPacketReceiver { // check onRecvUniversalPacket for the encoded ackpacket data require(ack.data.length >= 20, 'ack data too short'); address ackSender = address(bytes20(ack.data[0:20])); - require(Ibc.toAddress(packet.destPortAddr) == ackSender, 'ack address mismatch'); + require(IbcUtils.toAddress(packet.destPortAddr) == ackSender, 'ack address mismatch'); ackPackets.push(UcAckWithChannel(channelId, packet, ack)); // do logic } diff --git a/foundry.toml b/foundry.toml new file mode 100644 index 0000000..4f3bf9f --- /dev/null +++ b/foundry.toml @@ -0,0 +1,6 @@ +[profile.default] +src = 'contracts' +out = 'out' +libs = ['node_modules', 'lib'] +test = 'test' +cache_path = 'cache_forge' \ No newline at end of file diff --git a/hardhat.config.js b/hardhat.config.js index 518c79c..7cdab98 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -1,11 +1,12 @@ require("@nomicfoundation/hardhat-toolbox"); +require("@nomicfoundation/hardhat-foundry"); require('dotenv').config(); /** @type import('hardhat/config').HardhatUserConfig */ module.exports = { solidity: { - version: '0.8.20', + version: '0.8.23', settings: { optimizer: { enabled: true, @@ -34,5 +35,12 @@ module.exports = { }, }, defaultNetwork: 'optimism', + paths: { + sources: './contracts', + tests: './test', + cache: './cache', + artifacts: './artifacts', + libraries: './lib', + } }; diff --git a/lib/forge-std b/lib/forge-std new file mode 160000 index 0000000..ae570fe --- /dev/null +++ b/lib/forge-std @@ -0,0 +1 @@ +Subproject commit ae570fec082bfe1c1f45b0acca4a2b4f84d345ce diff --git a/lib/vibc-core-smart-contracts b/lib/vibc-core-smart-contracts index da9bc19..b8c69fd 160000 --- a/lib/vibc-core-smart-contracts +++ b/lib/vibc-core-smart-contracts @@ -1 +1 @@ -Subproject commit da9bc19058004a3d7bfcf0f11a3820f668a851c6 +Subproject commit b8c69fda353f1469c9934c8058823ab772c4288e diff --git a/package-lock.json b/package-lock.json index 6fbdc1f..46e1567 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "dotenv": "^16.4.1" }, "devDependencies": { + "@nomicfoundation/hardhat-foundry": "^1.1.1", "@nomicfoundation/hardhat-toolbox": "^4.0.0", "hardhat": "^2.19.5" } @@ -1569,6 +1570,18 @@ "hardhat": "^2.0.0" } }, + "node_modules/@nomicfoundation/hardhat-foundry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-foundry/-/hardhat-foundry-1.1.1.tgz", + "integrity": "sha512-cXGCBHAiXas9Pg9MhMOpBVQCkWRYoRFG7GJJAph+sdQsfd22iRs5U5Vs9XmpGEQd1yEvYISQZMeE68Nxj65iUQ==", + "dev": true, + "dependencies": { + "chalk": "^2.4.2" + }, + "peerDependencies": { + "hardhat": "^2.17.2" + } + }, "node_modules/@nomicfoundation/hardhat-network-helpers": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-network-helpers/-/hardhat-network-helpers-1.0.10.tgz", diff --git a/package.json b/package.json index ea7bf90..f97a7fe 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ }, "homepage": "https://github.com/open-ibc/ibc-app-solidity-template#readme", "devDependencies": { + "@nomicfoundation/hardhat-foundry": "^1.1.1", "@nomicfoundation/hardhat-toolbox": "^4.0.0", "hardhat": "^2.19.5" }, diff --git a/remappings.txt b/remappings.txt index ffef942..bd77b56 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,6 +1,5 @@ @open-ibc/vibc-core-smart-contracts/=lib/vibc-core-smart-contracts/ -@openzeppelin/=lib/openzeppelin-contracts/ @lazyledger/protobuf3-solidity-lib/=lib/protobuf3-solidity-lib/ ds-test/=lib/forge-std/lib/ds-test/src/ forge-std/=lib/forge-std/src/ -lib/forge-std:ds-test/=lib/forge-std/lib/ds-test/src/ + diff --git a/scripts/create-channel.js b/scripts/create-channel.js index 7f64902..059a782 100644 --- a/scripts/create-channel.js +++ b/scripts/create-channel.js @@ -31,19 +31,27 @@ async function main() { const connHop2 = ibcConfig[chanConfig.dstChain].canonConnTo; const srcPortId = addressToPortId(`polyibc.${chanConfig.srcChain}`, chanConfig.srcAddr); const dstPortId = addressToPortId(`polyibc.${chanConfig.dstChain}`, chanConfig.dstAddr); + + const local = { + portId: srcPortId, + channelId: hre.ethers.encodeBytes32String(''), + version: '', + }; + + const cp = { + portId: dstPortId, + channelId: hre.ethers.encodeBytes32String(''), + version: '', + }; // Create the channel // Note: The proofHeight and proof are dummy values and will be dropped in the future const tx = await ibcAppSrc.createChannel( - chanConfig.version, + local, chanConfig.ordering, chanConfig.fees, [ connHop1, connHop2 ], - { - portId: dstPortId, - channelId: hre.ethers.encodeBytes32String(''), - version: '', - }, + cp, { proofHeight: { revision_height: 0, revision_number: 0 }, proof: hre.ethers.encodeBytes32String('abc') diff --git a/scripts/deploy-config.js b/scripts/deploy-config.js index 845043d..4f1171f 100644 --- a/scripts/deploy-config.js +++ b/scripts/deploy-config.js @@ -1,15 +1,22 @@ const { exec } = require("child_process"); const fs = require("fs"); const path = require("path"); +const ibcConfig = require("../ibc.json"); // Run script with source and destination networks as arguments // Example: // $ node deploy-config.js optimism base const source = process.argv[2]; const destination = process.argv[3]; +const universalChannel = process.argv[4]; if (!source || !destination) { - console.error('Usage: node deploy-config.js '); + console.error('Usage: node deploy-config.js '); + process.exit(1); +} + +if (process.argv[4] === undefined) { + console.error('Usage: node deploy-config.js '); process.exit(1); } @@ -19,16 +26,21 @@ function updateConfig(network, address, isSource) { const config = JSON.parse(fs.readFileSync(configPath, 'utf8')); // Update the config object - if (isSource) { - config["createChannel"]["srcChain"] = network; - config["createChannel"]["srcAddr"] = address; + if (universalChannel === "false") { + if (isSource) { + config["createChannel"]["srcChain"] = network; + config["createChannel"]["srcAddr"] = address; + } else { + config["createChannel"]["dstChain"] = network; + config["createChannel"]["dstAddr"] = address; + } + + config["sendPacket"][`${network}`]["portAddr"] = address; } else { - config["createChannel"]["dstChain"] = network; - config["createChannel"]["dstAddr"] = address; + config["sendUniversalPacket"][`${network}`]["portAddr"] = address; + config["sendUniversalPacket"][`${network}`]["channelId"] = ibcConfig[`${network}`]["universalChannel"]; } - config["sendPacket"][`${network}`]["portAddr"] = address; - // Write the updated config back to the file fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); } diff --git a/scripts/send-universal-packet.js b/scripts/send-universal-packet.js new file mode 100644 index 0000000..58ca169 --- /dev/null +++ b/scripts/send-universal-packet.js @@ -0,0 +1,57 @@ +// We require the Hardhat Runtime Environment explicitly here. This is optional +// but useful for running the script in a standalone fashion through `node