diff --git a/script/counter/DeployEVMxCounterApp.s.sol b/script/counter/DeployEVMxCounterApp.s.sol index a6da1d9..ce933b2 100644 --- a/script/counter/DeployEVMxCounterApp.s.sol +++ b/script/counter/DeployEVMxCounterApp.s.sol @@ -8,6 +8,20 @@ import {ETH_ADDRESS} from "socket-protocol/contracts/protocol/utils/common/Const import {CounterAppGateway} from "../../src/counter/CounterAppGateway.sol"; +/** + * @title CounterDeploy Script + * @notice Deploys the CounterAppGateway contract to the EVMx network + * @dev This script: + * 1. Connects to the EVMx network using the provided RPC URL + * 2. Sets up fee payment configuration for Arbitrum Sepolia + * 3. Deploys the CounterAppGateway contract + * 4. Outputs the contract address for further interaction + * + * Required environment variables: + * - ADDRESS_RESOLVER: Address of SOCKET Protocol's AddressResolver + * - EVMX_RPC: RPC URL for the EVMx network + * - PRIVATE_KEY: Private key of the deployer account + */ contract CounterDeploy is Script { function run() external { address addressResolver = vm.envAddress("ADDRESS_RESOLVER"); diff --git a/script/counter/DeployOnchainCounters.s.sol b/script/counter/DeployOnchainCounters.s.sol index 6d8dc42..fc55eba 100644 --- a/script/counter/DeployOnchainCounters.s.sol +++ b/script/counter/DeployOnchainCounters.s.sol @@ -7,6 +7,22 @@ import {ETH_ADDRESS} from "socket-protocol/contracts/protocol/utils/common/Const import {CounterAppGateway} from "../../src/counter/CounterAppGateway.sol"; +/** + * @title CounterDeployOnchain Script + * @notice Deploys Counter contracts to multiple target chains through the AppGateway + * @dev This script: + * 1. Connects to the EVMx network + * 2. Retrieves the previously deployed AppGateway contract + * 3. Deploys Counter contracts to Arbitrum Sepolia, Optimism Sepolia, and Base Sepolia chains + * + * This is the second step, after adding fees, in the deployment process after the AppGateway has been deployed. + * Each deployment creates a new Counter contract instance on the target chain. + * + * Required environment variables: + * - EVMX_RPC: RPC URL for the EVMx network + * - PRIVATE_KEY: Private key of the deployer account + * - APP_GATEWAY: Address of the previously deployed CounterAppGateway + */ contract CounterDeployOnchain is Script { function run() external { string memory rpc = vm.envString("EVMX_RPC"); diff --git a/script/counter/IncrementCountersFromApp.s.sol b/script/counter/IncrementCountersFromApp.s.sol index 890571e..dd24988 100644 --- a/script/counter/IncrementCountersFromApp.s.sol +++ b/script/counter/IncrementCountersFromApp.s.sol @@ -6,6 +6,23 @@ import {console} from "forge-std/console.sol"; import {CounterAppGateway} from "../../src/counter/CounterAppGateway.sol"; +/** + * @title IncrementCounters Script + * @notice Increments counters across multiple chains through the AppGateway + * @dev This script: + * 1. Connects to the EVMx network + * 2. Retrieves the deployed CounterAppGateway + * 3. Gets the forwarder addresses for each deployed Counter contract on different chains + * 4. Calls incrementCounters to increment all non-zero counter instances + * + * This demonstrates chain-abstracted interaction where a single transaction on EVMx + * can update state on multiple other chains through SOCKET Protocol. + * + * Required environment variables: + * - EVMX_RPC: RPC URL for the EVMx network + * - PRIVATE_KEY: Private key of the deployer account + * - APP_GATEWAY: Address of the deployed CounterAppGateway + */ contract IncrementCounters is Script { function run() external { string memory socketRPC = vm.envString("EVMX_RPC"); diff --git a/script/counter/ReadOnchainCounters.s.sol b/script/counter/ReadOnchainCounters.s.sol index 018c15f..7baae7c 100644 --- a/script/counter/ReadOnchainCounters.s.sol +++ b/script/counter/ReadOnchainCounters.s.sol @@ -7,16 +7,38 @@ import {console} from "forge-std/console.sol"; import {CounterAppGateway} from "../../src/counter/CounterAppGateway.sol"; import {Counter} from "../../src/counter/Counter.sol"; +/** + * @title CheckCounters Script + * @notice Reads the current counter values from deployed Counter contracts across multiple chains + * @dev This script: + * 1. Retrieves the deployed CounterAppGateway + * 2. Gets the onchain addresses of Counter contracts deployed on different chains + * 3. Connects to each chain and reads the current counter value + * 4. Outputs the counter values and blockchain explorer links + * + * This demonstrates how to retrieve and read state from contracts deployed through + * Socket Protocol across multiple chains. + * + * Required environment variables: + * - APP_GATEWAY: Address of the deployed CounterAppGateway + * - EVMX_RPC: RPC URL for the EVMx network + * - ARBITRUM_SEPOLIA_RPC: RPC URL for Arbitrum Sepolia + * - OPTIMISM_SEPOLIA_RPC: RPC URL for Optimism Sepolia + * - BASE_SEPOLIA_RPC: RPC URL for Base Sepolia + */ contract CheckCounters is Script { function run() external { CounterAppGateway appGateway = CounterAppGateway(vm.envAddress("APP_GATEWAY")); console.log("See AppGateway on EVMx: https://evmx.cloud.blockscout.com/address/%s", address(appGateway)); vm.createSelectFork(vm.envString("EVMX_RPC")); + // From your AppGateway you can retrieve a contract's onchain address that is used by your AppGateway + // The input arguments are the bytes32 contract id as well as the uint32 chain id address counterInstanceArbitrumSepolia = appGateway.getOnChainAddress(appGateway.counter(), 421614); address counterInstanceOptimismSepolia = appGateway.getOnChainAddress(appGateway.counter(), 11155420); address counterInstanceBaseSepolia = appGateway.getOnChainAddress(appGateway.counter(), 84532); + // If there is no onchain address associated with the input params it will return the zero address if (counterInstanceArbitrumSepolia != address(0)) { vm.createSelectFork(vm.envString("ARBITRUM_SEPOLIA_RPC")); uint256 counterValueArbitrumSepolia = Counter(counterInstanceArbitrumSepolia).counter(); diff --git a/script/counter/WithdrawFeesArbitrumFeesPlug.s.sol b/script/counter/WithdrawFeesArbitrumFeesPlug.s.sol index 580e035..894ff24 100644 --- a/script/counter/WithdrawFeesArbitrumFeesPlug.s.sol +++ b/script/counter/WithdrawFeesArbitrumFeesPlug.s.sol @@ -8,8 +8,27 @@ import {ETH_ADDRESS} from "socket-protocol/contracts/protocol/utils/common/Const import {CounterAppGateway} from "../../src/counter/CounterAppGateway.sol"; -// @notice This script is used to withdraw fees from EVMX to Arbitrum Sepolia -// @dev Make sure your app has withdrawFeeTokens() function implemented. You can check its implementation in CounterAppGateway.sol +/** + * @title WithdrawFees Script + * @notice Withdraws accumulated fees from EVMX to Arbitrum Sepolia + * @dev This script: + * 1. Checks available fees on EVMX + * 2. Switches to Arbitrum Sepolia to estimate gas costs + * 3. Calculates a safe amount to withdraw (fees minus estimated gas costs) + * 4. Performs the withdrawal if the amount is positive + * 5. Verifies final balance on Arbitrum Sepolia + * + * This demonstrates how developers can retrieve fees that their application has earned + * through Socket Protocol's fee system. + * + * Required environment variables: + * - EVMX_RPC: RPC URL for the EVMx network + * - ARBITRUM_SEPOLIA_RPC: RPC URL for Arbitrum Sepolia + * - PRIVATE_KEY: Private key of the deployer account + * - FEES_MANAGER: Address of Socket Protocol's FeesManager contract + * - APP_GATEWAY: Address of the deployed CounterAppGateway + * @notice Ensure your app has withdrawFeeTokens() function implemented. You can check its implementation in CounterAppGateway.sol + */ contract WithdrawFees is Script { function run() external { // EVMX Check available fees diff --git a/src/counter/Counter.sol b/src/counter/Counter.sol index fe7e616..29c97e5 100644 --- a/src/counter/Counter.sol +++ b/src/counter/Counter.sol @@ -4,18 +4,25 @@ pragma solidity >=0.7.0 <0.9.0; import "solady/auth/Ownable.sol"; import "socket-protocol/contracts/base/PlugBase.sol"; +/** + * @title Counter + * @dev A simple counter contract that can be deployed to multiple chains via SOCKET Protocol. + * This contract inherits from Ownable for access control and PlugBase to enable SOCKET Protocol integration. + * The counter can only be incremented through the SOCKET Protocol via AppGateway. + */ contract Counter is Ownable, PlugBase { + /** + * @notice The current counter value + * @dev This value can only be incremented by authorized SOCKET Protocol calls via AppGateway + */ uint256 public counter; + /** + * @notice Increases the counter by 1 + * @dev This function can only be called through the SOCKET Protocol via AppGateway + * The onlySocket modifier ensures that only the SOCKET Forwarder contract can call this function + */ function increase() external onlySocket { counter++; } - - function getCounter() external view returns (uint256) { - return counter; - } - - function increaseOnGateway(uint256 value_) external returns (bytes32) { - return _callAppGateway(abi.encode(value_), bytes32(0)); - } } diff --git a/src/counter/CounterAppGateway.sol b/src/counter/CounterAppGateway.sol index 3ded573..4d2055b 100644 --- a/src/counter/CounterAppGateway.sol +++ b/src/counter/CounterAppGateway.sol @@ -7,112 +7,84 @@ import "socket-protocol/contracts/interfaces/IPromise.sol"; import "./Counter.sol"; import "./ICounter.sol"; +/** + * @title CounterAppGateway + * @dev Gateway contract for the Counter application that manages multi-chain counter contract deployments + * and interactions through SOCKET Protocol. + * Inherits from AppGatewayBase for SOCKET Protocol integration and Ownable for access control. + */ contract CounterAppGateway is AppGatewayBase, Ownable { + /** + * @notice Identifier for the counter contract + * @dev Used to track counter contract instances across chains + */ bytes32 public counter = _createContractId("counter"); - bytes32 public counter1 = _createContractId("counter1"); - - uint256 public counterVal; - - uint256 arbCounter; - uint256 optCounter; - - event TimeoutResolved(uint256 creationTimestamp, uint256 executionTimestamp); + /** + * @notice Constructs the CounterAppGateway + * @dev Sets up the creation code for the Counter contract, configures fee overrides, + * and initializes ownership. + * For more information on how contract bytecode is stored in the AppGateway, see: + * https://docs.socket.tech/writing-apps#onchain-contract-bytecode-stored-in-the-appgateway-contract + * @param addressResolver_ Address of the SOCKET Protocol's AddressResolver contract + * @param fees_ Fee configuration for multi-chain operations + */ constructor(address addressResolver_, Fees memory fees_) AppGatewayBase(addressResolver_) { creationCodeWithArgs[counter] = abi.encodePacked(type(Counter).creationCode); - creationCodeWithArgs[counter1] = abi.encodePacked(type(Counter).creationCode); _setOverrides(fees_); _initializeOwner(msg.sender); } - // deploy contracts + /** + * @notice Deploys Counter contracts to a specified chain + * @dev Triggers an asynchronous multi-chain deployment via SOCKET Protocol. + * For more information on onchain contract deployment with the AppGateway, see: + * https://docs.socket.tech/writing-apps#onchain-contract-deployment-with-the-appgateway-contract + * @param chainSlug_ The identifier of the target chain + */ function deployContracts(uint32 chainSlug_) external async { _deploy(counter, chainSlug_, IsPlug.YES); } - function deployParallelContracts(uint32 chainSlug_) external async { - _setOverrides(Parallel.ON); - _deploy(counter, chainSlug_, IsPlug.YES); - _deploy(counter1, chainSlug_, IsPlug.YES); - _setOverrides(Parallel.OFF); - } - - function deployMultiChainContracts(uint32[] memory chainSlugs_) external async { - _setOverrides(Parallel.ON); - for (uint32 i = 0; i < chainSlugs_.length; i++) { - _deploy(counter, chainSlugs_[i], IsPlug.YES); - _deploy(counter1, chainSlugs_[i], IsPlug.YES); - } - _setOverrides(Parallel.OFF); - } - - function initialize(uint32) public pure override { + /** + * @notice Initialize function required by AppGatewayBase + * @dev No initialization needed for this application, so implementation is empty. + * The chainSlug parameter is required by the interface but not used. + * For more information on the initialize function, see: + * https://docs.socket.tech/deploy#initialize + */ + function initialize(uint32 /* chainSlug_ */ ) public pure override { return; } + /** + * @notice Increments counter values on multiple instances across chains + * @dev Calls the increase function on each counter instance provided + * @param instances_ Array of counter contract addresses to increment + */ function incrementCounters(address[] memory instances_) public async { - // the increase function is called on given list of instances - // this for (uint256 i = 0; i < instances_.length; i++) { ICounter(instances_[i]).increase(); } } - // for testing purposes - function incrementCountersWithoutAsync(address[] memory instances_) public { - // the increase function is called on given list of instances - for (uint256 i = 0; i < instances_.length; i++) { - Counter(instances_[i]).increase(); - } - } - - function readCounters(address[] memory instances_) public async { - // the increase function is called on given list of instances - _setOverrides(Read.ON, Parallel.ON); - for (uint256 i = 0; i < instances_.length; i++) { - uint32 chainSlug = IForwarder(instances_[i]).getChainSlug(); - ICounter(instances_[i]).getCounter(); - IPromise(instances_[i]).then(this.setCounterValues.selector, abi.encode(chainSlug)); - } - _setOverrides(Read.OFF, Parallel.OFF); - ICounter(instances_[0]).increase(); - } - - function setCounterValues(bytes memory data, bytes memory returnData) external onlyPromises { - uint256 counterValue = abi.decode(returnData, (uint256)); - uint32 chainSlug = abi.decode(data, (uint32)); - if (chainSlug == 421614) { - arbCounter = counterValue; - } else if (chainSlug == 11155420) { - optCounter = counterValue; - } - } - - // INBOX - function setIsValidPlug(uint32 chainSlug_, address plug_) public { - watcherPrecompile__().setIsValidPlug(chainSlug_, plug_, true); - } - - function callFromChain(uint32, address, bytes calldata payload_, bytes32) external override onlyWatcherPrecompile { - uint256 value = abi.decode(payload_, (uint256)); - counterVal += value; - } - - // TIMEOUT - function setTimeout(uint256 delayInSeconds_) public { - bytes memory payload = abi.encodeWithSelector(this.resolveTimeout.selector, block.timestamp); - watcherPrecompile__().setTimeout(address(this), payload, delayInSeconds_); - } - - function resolveTimeout(uint256 creationTimestamp_) external onlyWatcherPrecompile { - emit TimeoutResolved(creationTimestamp_, block.timestamp); - } - - // UTILS + /** + * @notice Updates the fee configuration + * @dev Allows the owner to modify fee settings for multi-chain operations + * @param fees_ New fee configuration + */ function setFees(Fees memory fees_) public { fees = fees_; } + /** + * @notice Withdraws fee tokens from the SOCKET Protocol + * @dev Allows withdrawal of accumulated fees to a specified receiver + * @param chainSlug_ The chain from which to withdraw fees + * @param token_ The token address to withdraw + * @param amount_ The amount to withdraw + * @param receiver_ The address that will receive the withdrawn fees + */ function withdrawFeeTokens(uint32 chainSlug_, address token_, uint256 amount_, address receiver_) external { _withdrawFeeTokens(chainSlug_, token_, amount_, receiver_); } diff --git a/src/counter/ICounter.sol b/src/counter/ICounter.sol index 6e25497..8ec176a 100644 --- a/src/counter/ICounter.sol +++ b/src/counter/ICounter.sol @@ -1,8 +1,21 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.7.0 <0.9.0; +/** + * @title ICounter + * @dev Interface for the Counter contract, defining its public functions + * Used by the CounterAppGateway to interact with Counter instances + */ interface ICounter { - function increase() external; + /** + * @notice Returns the current counter value + * @dev This function is view-only and does not modify state + */ + function counter() external; - function getCounter() external; + /** + * @notice Increases the counter value by 1 + * @dev Can only be called by authorized accounts via the SOCKET Protocol + */ + function increase() external; } diff --git a/test/apps/Counter.t.sol b/test/apps/Counter.t.sol index 6d27808..6513622 100644 --- a/test/apps/Counter.t.sol +++ b/test/apps/Counter.t.sol @@ -80,33 +80,4 @@ contract CounterTest is DeliveryHelperTest { assertEq(Counter(arbCounter).counter(), arbCounterBefore + 1); assertEq(Counter(optCounter).counter(), optCounterBefore + 1); } - - function testCounterReadMultipleChains() external { - testCounterIncrementMultipleChains(); - - (address arbCounter, address arbCounterForwarder) = - getOnChainAndForwarderAddresses(arbChainSlug, counterId, counterGateway); - (address optCounter, address optCounterForwarder) = - getOnChainAndForwarderAddresses(optChainSlug, counterId, counterGateway); - - address[] memory instances = new address[](2); - instances[0] = arbCounterForwarder; - instances[1] = optCounterForwarder; - - bytes32 bridgeAsyncId = getNextAsyncId(); - - bytes32[] memory payloadIds = new bytes32[](3); - payloadIds[0] = _encodeId(evmxSlug, address(watcherPrecompile), payloadIdCounter++); - payloadIds[1] = _encodeId(evmxSlug, address(watcherPrecompile), payloadIdCounter++); - - payloadIds[2] = - getWritePayloadId(arbChainSlug, address(getSocketConfig(arbChainSlug).switchboard), payloadIdCounter++); - - counterGateway.readCounters(instances); - - bidAndEndAuction(bridgeAsyncId); - finalizeQuery(payloadIds[0], abi.encode(Counter(arbCounter).counter())); - finalizeQuery(payloadIds[1], abi.encode(Counter(optCounter).counter())); - finalizeAndExecute(payloadIds[2]); - } }