diff --git a/.env.sample b/.env.sample index 5caa12b..0483bd7 100644 --- a/.env.sample +++ b/.env.sample @@ -1,17 +1,22 @@ # RPCs EVMX_RPC="https://rpc-evmx-devnet.socket.tech/" + SEPOLIA_RPC="https://ethereum-sepolia-rpc.publicnode.com" ARBITRUM_SEPOLIA_RPC="https://sepolia-rollup.arbitrum.io/rpc" OPTIMISM_SEPOLIA_RPC="https://sepolia.optimism.io" BASE_SEPOLIA_RPC="https://sepolia.base.org" +ARBITRUM_RPC="https://arbitrum.drpc.org" +OPTIMISM_RPC="https://optimism.drpc.org" +BASE_RPC="https://base.drpc.org" + # EVMx key addresses # Find the most up to date addresses at: # https://github.com/SocketDotTech/socket-protocol/blob/master/deployments/stage_addresses.json -ADDRESS_RESOLVER="0x21a9AFDfbEb0399D4a12f3AA1324042Be2B57F8e" -FEES_MANAGER="0x30e07016eB24570629Bc8765CA307394Af90B27C" -ARBITRUM_FEES_PLUG="0xDfE94B9b14de382Ed13C8A7F387884808D0f7E0b" -ARBITRUM_TEST_USDC="0xa03Cbf13f331aF7c0fD7F2E28E6Cbc13F879E3F3" +ADDRESS_RESOLVER="0x935b06902cA5C8bb4C76e18738561c294D377A93" +FEES_MANAGER="0xA07208F9e7aE243F922317ab6604DC9F86822406" +ARBITRUM_FEES_PLUG="0x501bdF8C7163ddD32172575C2836c5A7F556cbE7" +ARBITRUM_USDC="0xaf88d065e77c8cC2239327C5EDb3A432268e5831" # Add your deployer private key here # or remove it from this file if it is already an env var diff --git a/.gitmodules b/.gitmodules index 05d66fc..6995c1a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,4 +1,4 @@ [submodule "lib/socket-protocol"] path = lib/socket-protocol url = https://github.com/SocketDotTech/socket-protocol.git - branch = master + branch = dev diff --git a/lib/socket-protocol b/lib/socket-protocol index 38accd5..dc173ae 160000 --- a/lib/socket-protocol +++ b/lib/socket-protocol @@ -1 +1 @@ -Subproject commit 38accd5f8a4144297048940122d56a08578c1783 +Subproject commit dc173aea1bfcec23d39c16b9e1ec43333f7fba72 diff --git a/script/counter/DeployEVMxCounterApp.s.sol b/script/counter/DeployEVMxCounterApp.s.sol index bf17987..e0963bd 100644 --- a/script/counter/DeployEVMxCounterApp.s.sol +++ b/script/counter/DeployEVMxCounterApp.s.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.0; import {Script} from "forge-std/Script.sol"; import {console} from "forge-std/console.sol"; +import "socket-protocol/contracts/evmx/interfaces/IFeesManager.sol"; import {CounterAppGateway} from "../../src/counter/CounterAppGateway.sol"; /** @@ -23,18 +24,24 @@ import {CounterAppGateway} from "../../src/counter/CounterAppGateway.sol"; contract CounterDeploy is Script { function run() external { address addressResolver = vm.envAddress("ADDRESS_RESOLVER"); + IFeesManager feesManager = IFeesManager(payable(vm.envAddress("FEES_MANAGER"))); string memory rpc = vm.envString("EVMX_RPC"); vm.createSelectFork(rpc); uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); - // Setting fee payment on Arbitrum Sepolia - uint256 fees = 10 ether; + uint256 fees = 0.1 ether; CounterAppGateway appGateway = new CounterAppGateway(addressResolver, fees); console.log("CounterAppGateway contract:", address(appGateway)); console.log("See AppGateway on EVMx: https://evmx.cloud.blockscout.com/address/%s", address(appGateway)); + console.log("Do not forget to add the contract address to the .env file!"); + + console.log("Approving AppGateway to spend from funds in EOA"); + AppGatewayApprovals[] memory approvals = new AppGatewayApprovals[](1); + approvals[0] = AppGatewayApprovals({appGateway: address(appGateway), approval: true}); + feesManager.approveAppGateways(approvals); } } diff --git a/script/counter/WithdrawFeesArbitrumFeesPlug.s.sol b/script/counter/WithdrawFeesArbitrumFeesPlug.s.sol deleted file mode 100644 index 7b35584..0000000 --- a/script/counter/WithdrawFeesArbitrumFeesPlug.s.sol +++ /dev/null @@ -1,74 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import {Script} from "forge-std/Script.sol"; -import {console} from "forge-std/console.sol"; -import {FeesManager} from "socket-protocol/contracts/evmx/payload-delivery/FeesManager.sol"; -import {TestUSDC} from "socket-protocol/contracts/evmx/helpers/TestUSDC.sol"; - -import {CounterAppGateway} from "../../src/counter/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 - vm.createSelectFork(vm.envString("EVMX_RPC")); - FeesManager feesManager = FeesManager(payable(vm.envAddress("FEES_MANAGER"))); - address appGatewayAddress = vm.envAddress("APP_GATEWAY"); - TestUSDC testUSDCContract = TestUSDC(vm.envAddress("ARBITRUM_TEST_USDC")); - - CounterAppGateway appGateway = CounterAppGateway(appGatewayAddress); - uint256 availableFees = feesManager.getMaxCreditsAvailableForWithdraw(appGatewayAddress); - console.log("Available fees:", availableFees); - - if (availableFees > 0) { - // Switch to Arbitrum Sepolia to get gas price - vm.createSelectFork(vm.envString("ARBITRUM_SEPOLIA_RPC")); - uint256 privateKey = vm.envUint("PRIVATE_KEY"); - address sender = vm.addr(privateKey); - - // Gas price from Arbitrum - uint256 arbitrumGasPrice = block.basefee + 0.1 gwei; // With buffer - uint256 gasLimit = 50_000_000_000; // Estimate - uint256 estimatedGasCost = gasLimit * arbitrumGasPrice; - - console.log("Arbitrum gas price (wei):", arbitrumGasPrice); - console.log("Gas limit:", gasLimit); - console.log("Estimated gas cost:", estimatedGasCost); - - // Calculate amount to withdraw - uint256 amountToWithdraw = availableFees > estimatedGasCost ? availableFees - estimatedGasCost : 0; - - if (amountToWithdraw > 0) { - // Switch back to EVMX to perform withdrawal - vm.createSelectFork(vm.envString("EVMX_RPC")); - vm.startBroadcast(privateKey); - console.log("Withdrawing amount:", amountToWithdraw); - appGateway.withdrawFeeTokens(421614, address(testUSDCContract), amountToWithdraw, sender); - vm.stopBroadcast(); - } else { - console.log("Available fees less than estimated gas cost"); - } - } - } -} diff --git a/script/helpers/AppGatewayFeeBalance.s.sol b/script/helpers/AppGatewayFeeBalance.s.sol deleted file mode 100644 index b1bb48e..0000000 --- a/script/helpers/AppGatewayFeeBalance.s.sol +++ /dev/null @@ -1,22 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import {Script} from "forge-std/Script.sol"; -import {console} from "forge-std/console.sol"; -import {FeesManager} from "socket-protocol/contracts/evmx/payload-delivery/FeesManager.sol"; - -contract CheckDepositedFees is Script { - function run() external { - vm.createSelectFork(vm.envString("EVMX_RPC")); - FeesManager feesManager = FeesManager(payable(vm.envAddress("FEES_MANAGER"))); - address appGateway = vm.envAddress("APP_GATEWAY"); - - (uint256 deposited, uint256 blocked) = feesManager.userCredits(appGateway); - console.log("AppGateway:", appGateway); - console.log("Total balance of available fees for this AppGateway: %s", deposited); - console.log("Fees being locked due to existing transactions: %s", blocked); - - uint256 availableFees = feesManager.getAvailableCredits(appGateway); - console.log("Fees available to be spent on transactions: %s", availableFees); - } -} diff --git a/script/helpers/CheckAvailableCredits.s.sol b/script/helpers/CheckAvailableCredits.s.sol new file mode 100644 index 0000000..c613923 --- /dev/null +++ b/script/helpers/CheckAvailableCredits.s.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {Script} from "forge-std/Script.sol"; +import {console} from "forge-std/console.sol"; +import {FeesManager} from "socket-protocol/contracts/evmx/fees/FeesManager.sol"; + +contract CheckAvailableCredits is Script { + function run() external { + FeesManager feesManager = FeesManager(payable(vm.envAddress("FEES_MANAGER"))); + + uint256 privateKey = vm.envUint("PRIVATE_KEY"); + address sender = vm.addr(privateKey); + console.log("Sender address:", sender); + + vm.createSelectFork(vm.envString("EVMX_RPC")); + + (uint256 deposited, uint256 blocked) = feesManager.userCredits(sender); + console.log("Total balance of available credits for this address: %s", deposited); + console.log("Credits being locked due to existing transactions: %s", blocked); + + uint256 availableFees = feesManager.getAvailableCredits(sender); + console.log("Credits available to be spent on transactions: %s", availableFees); + } +} diff --git a/script/helpers/DepositCreditAndNative.s.sol b/script/helpers/DepositCreditAndNative.s.sol new file mode 100644 index 0000000..83b4ed8 --- /dev/null +++ b/script/helpers/DepositCreditAndNative.s.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import {Script} from "forge-std/Script.sol"; +import {console} from "forge-std/console.sol"; +import {FeesPlug} from "socket-protocol/contracts/evmx/plugs/FeesPlug.sol"; +// source .env && forge script script/helpers/DepositCreditAndNative.s.sol --broadcast --skip-simulation + +interface IERC20 { + function approve(address spender, uint256 value) external returns (bool); + function balanceOf(address account) external view returns (uint256); +} + +contract DepositCredit is Script { + function run() external { + uint256 privateKey = vm.envUint("PRIVATE_KEY"); + address sender = vm.addr(privateKey); + console.log("Sender address:", sender); + + FeesPlug feesPlug = FeesPlug(payable(vm.envAddress("ARBITRUM_FEES_PLUG"))); + address arbitrumUSDC = vm.envAddress("ARBITRUM_USDC"); + IERC20 USDCContract = IERC20(arbitrumUSDC); + + vm.createSelectFork(vm.envString("ARBITRUM_RPC")); + vm.startBroadcast(privateKey); + uint256 balance = USDCContract.balanceOf(sender); + + uint256 feesAmount = 1000000; // 1 USDC + if (balance < feesAmount) { + console.log("Sender USDC balance:", balance); + revert("Sender does not have enough USDC. Requires 1 USDC."); + } + + console.log("Depositing", feesAmount, " - 1 USDC to Arbitrum FeesPlug:", address(feesPlug)); + console.log("Approving Spending..."); + USDCContract.approve(address(feesPlug), feesAmount); + + feesPlug.depositCreditAndNative(arbitrumUSDC, sender, feesAmount); + console.log("Corresponding EVMx credits will show up on your account"); + } +} diff --git a/script/helpers/PayFeesInArbitrumTestUSDC.s.sol b/script/helpers/PayFeesInArbitrumTestUSDC.s.sol deleted file mode 100644 index b1f56a9..0000000 --- a/script/helpers/PayFeesInArbitrumTestUSDC.s.sol +++ /dev/null @@ -1,36 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import {Script} from "forge-std/Script.sol"; -import {console} from "forge-std/console.sol"; -import {FeesPlug} from "socket-protocol/contracts/evmx/payload-delivery/FeesPlug.sol"; -import {TestUSDC} from "socket-protocol/contracts/evmx/helpers/TestUSDC.sol"; - -contract DepositFees is Script { - function run() external { - vm.createSelectFork(vm.envString("ARBITRUM_SEPOLIA_RPC")); - - uint256 privateKey = vm.envUint("PRIVATE_KEY"); - address sender = vm.addr(privateKey); - - uint256 feesAmount = 100000000; // 10 USDC - FeesPlug feesPlug = FeesPlug(payable(vm.envAddress("ARBITRUM_FEES_PLUG"))); - address appGateway = vm.envAddress("APP_GATEWAY"); - TestUSDC testUSDCContract = TestUSDC(vm.envAddress("ARBITRUM_TEST_USDC")); - - vm.startBroadcast(privateKey); - // mint test USDC to sender - testUSDCContract.mint(sender, feesAmount); - console.log("Minting 100 TestUSDC to %s", sender); - // approve fees plug to spend test USDC - testUSDCContract.approve(address(feesPlug), feesAmount); - - uint256 balance = testUSDCContract.balanceOf(sender); - console.log("Using address %s with %s TestUSDC balance in wei", sender, balance); - - console.log("Depositing 100 TestUSDC on Arbitrum FeesPlug %s", address(feesPlug)); - feesPlug.depositToFeeAndNative(address(testUSDCContract), appGateway, feesAmount); - console.log("Added 90 credits for fee balance and 10 native credits for AppGateway %s", appGateway); - console.log("If you want to deposit to fees only, it can be deposited using `depositToFee`"); - } -} diff --git a/script/helpers/WithdrawCreditsArbitrumFeesPlug.s.sol b/script/helpers/WithdrawCreditsArbitrumFeesPlug.s.sol new file mode 100644 index 0000000..454f0b3 --- /dev/null +++ b/script/helpers/WithdrawCreditsArbitrumFeesPlug.s.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {Script} from "forge-std/Script.sol"; +import {console} from "forge-std/console.sol"; +import "socket-protocol/contracts/evmx/interfaces/IFeesManager.sol"; + +import {CounterAppGateway} from "../../src/counter/CounterAppGateway.sol"; + +/** + * @title WithdrawCredits Script + * @notice Withdraws accumulated fees from EVMX to Arbitrum Sepolia + * @dev This script: + * 1. Checks available fees on EVMX + * 2. Performs the withdrawal if the amount is positive + * + * This demonstrates how developers can retrieve fees that their application has earned + * through SOCKET Protocol's fee system. + */ +contract WithdrawCredits is Script { + function run() external { + // EVMX Check available fees + vm.createSelectFork(vm.envString("EVMX_RPC")); + IFeesManager feesManager = IFeesManager(payable(vm.envAddress("FEES_MANAGER"))); + address token = vm.envAddress("ARBITRUM_USDC"); + uint256 privateKey = vm.envUint("PRIVATE_KEY"); + address sender = vm.addr(privateKey); + + uint256 availableCredits = feesManager.getAvailableCredits(sender); + console.log("Available credits:", availableCredits); + //consfeesManager.tokenOntokenOnChainBalances[42161][token]; + + if (availableCredits > 0) { + uint256 maxFees = 10000000000000000; // Static 1 cent USDC credit (18 decimals) + // TODO: Also wrap native amount to be able to max withdraw + uint256 amountToWithdraw = availableCredits - maxFees; + + if (amountToWithdraw > 0) { + vm.startBroadcast(privateKey); + AppGatewayApprovals[] memory approvals = new AppGatewayApprovals[](1); + approvals[0] = AppGatewayApprovals({appGateway: address(feesManager), approval: true}); + feesManager.approveAppGateways(approvals); + console.log("Withdrawing amount:", amountToWithdraw); + feesManager.withdrawCredits(42161, token, amountToWithdraw, maxFees, sender); + vm.stopBroadcast(); + } else { + console.log("Available fees less than estimated gas cost"); + } + } + } +} diff --git a/src/counter/CounterAppGateway.sol b/src/counter/CounterAppGateway.sol index b7c8798..01c345d 100644 --- a/src/counter/CounterAppGateway.sol +++ b/src/counter/CounterAppGateway.sol @@ -29,10 +29,11 @@ contract CounterAppGateway is AppGatewayBase, Ownable { * @param addressResolver_ Address of the SOCKET Protocol's AddressResolver contract * @param fees_ Fee configuration for multi-chain operations */ - constructor(address addressResolver_, uint256 fees_) AppGatewayBase(addressResolver_) { + constructor(address addressResolver_, uint256 fees_) { creationCodeWithArgs[counter] = abi.encodePacked(type(Counter).creationCode); _setMaxFees(fees_); _initializeOwner(msg.sender); + _initializeAppGateway(addressResolver_); } /** @@ -42,7 +43,10 @@ contract CounterAppGateway is AppGatewayBase, Ownable { * 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(bytes("")) { + function deployContracts(uint32 chainSlug_) external async { + // This ensures the msg.sender is the one paying for the fees + // for more information see: https://docs.socket.tech/fees + _setOverrides(msg.sender); _deploy(counter, chainSlug_, IsPlug.YES); } @@ -53,7 +57,7 @@ contract CounterAppGateway is AppGatewayBase, Ownable { * For more information on the initialize function, see: * https://docs.socket.tech/deploy#initialize */ - function initialize(uint32 /* chainSlug_ */ ) public pure override { + function initializeOnChain(uint32 /* chainSlug_ */ ) public pure override { return; } @@ -62,7 +66,10 @@ contract CounterAppGateway is AppGatewayBase, Ownable { * @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(bytes("")) { + function incrementCounters(address[] memory instances_) public async { + // This ensures the msg.sender is the one paying for the fees + // for more information see: https://docs.socket.tech/fees + _setOverrides(msg.sender); for (uint256 i = 0; i < instances_.length; i++) { ICounter(instances_[i]).increase(); } @@ -76,16 +83,4 @@ contract CounterAppGateway is AppGatewayBase, Ownable { function setMaxFees(uint256 fees_) public { maxFees = 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/test/apps/Counter.t.sol b/test/apps/Counter.t.sol index 066c298..bb2b39a 100644 --- a/test/apps/Counter.t.sol +++ b/test/apps/Counter.t.sol @@ -1,37 +1,39 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.21; +import "socket-protocol/test/SetupTest.t.sol"; +import "socket-protocol/contracts/evmx/interfaces/IFeesManager.sol"; + import {CounterAppGateway} from "../../src/counter/CounterAppGateway.sol"; import {Counter} from "../../src/counter/Counter.sol"; -import "socket-protocol/test/DeliveryHelper.t.sol"; -contract CounterTest is DeliveryHelperTest { +contract CounterTest is AppGatewayBaseSetup { uint256 feesAmount = 0.01 ether; bytes32 counterId; bytes32[] contractIds = new bytes32[](1); - CounterAppGateway counterGateway; - function deploySetup() internal { - setUpDeliveryHelper(); + event CounterScheduleResolved(uint256 creationTimestamp, uint256 executionTimestamp); - counterGateway = new CounterAppGateway(address(addressResolver), feesAmount); - depositUSDCFees( - address(counterGateway), - OnChainFees({chainSlug: arbChainSlug, token: address(arbConfig.feesTokenUSDC), amount: 1 ether}) - ); + function setUp() public { + deploy(); + counterGateway = new CounterAppGateway(address(addressResolver), feesAmount); + depositNativeAndCredits(arbChainSlug, 1 ether, 0, address(this)); + AppGatewayApprovals[] memory approvals = new AppGatewayApprovals[](1); + approvals[0] = AppGatewayApprovals({appGateway: address(counterGateway), approval: true}); + feesManager.approveAppGateways(approvals); counterId = counterGateway.counter(); contractIds[0] = counterId; } function deployCounterApp(uint32 chainSlug) internal returns (uint40 requestCount) { - requestCount = _deploy(chainSlug, counterGateway, contractIds); + counterGateway.deployContracts(chainSlug); + requestCount = executeDeploy(counterGateway, chainSlug, contractIds); } function testCounterDeployment() external { - deploySetup(); deployCounterApp(arbChainSlug); (address onChain, address forwarder) = getOnChainAndForwarderAddresses(arbChainSlug, counterId, counterGateway); @@ -41,7 +43,6 @@ contract CounterTest is DeliveryHelperTest { } function testCounterIncrement() external { - deploySetup(); deployCounterApp(arbChainSlug); (address arbCounter, address arbCounterForwarder) = @@ -52,13 +53,12 @@ contract CounterTest is DeliveryHelperTest { address[] memory instances = new address[](1); instances[0] = arbCounterForwarder; counterGateway.incrementCounters(instances); - executeRequest(new bytes[](0)); + executeRequest(); assertEq(Counter(arbCounter).counter(), arbCounterBefore + 1); } function testCounterIncrementMultipleChains() public { - deploySetup(); deployCounterApp(arbChainSlug); deployCounterApp(optChainSlug); @@ -79,7 +79,7 @@ contract CounterTest is DeliveryHelperTest { chains[0] = arbChainSlug; chains[1] = optChainSlug; - executeRequest(new bytes[](0)); + executeRequest(); assertEq(Counter(arbCounter).counter(), arbCounterBefore + 1); assertEq(Counter(optCounter).counter(), optCounterBefore + 1); }