Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions script/counter/DeployEVMxCounterApp.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
16 changes: 16 additions & 0 deletions script/counter/DeployOnchainCounters.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
17 changes: 17 additions & 0 deletions script/counter/IncrementCountersFromApp.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
22 changes: 22 additions & 0 deletions script/counter/ReadOnchainCounters.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
23 changes: 21 additions & 2 deletions script/counter/WithdrawFeesArbitrumFeesPlug.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
23 changes: 15 additions & 8 deletions src/counter/Counter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}
132 changes: 52 additions & 80 deletions src/counter/CounterAppGateway.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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_);
}
Expand Down
17 changes: 15 additions & 2 deletions src/counter/ICounter.sol
Original file line number Diff line number Diff line change
@@ -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;
}
29 changes: 0 additions & 29 deletions test/apps/Counter.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
}
}
Loading