From dd96c9b2c3778a3178f44754181befe611996d16 Mon Sep 17 00:00:00 2001 From: Ameesha Agrawal Date: Wed, 12 Nov 2025 16:51:35 +0530 Subject: [PATCH 01/12] fix: renames --- contracts/evmx/base/AppGatewayBase.sol | 12 +- ...{FeesManager.sol => GasAccountManager.sol} | 14 +-- .../fees/{Credit.sol => GasAccountToken.sol} | 93 +++++++++------- .../evmx/fees/{FeesPool.sol => GasVault.sol} | 6 +- contracts/evmx/fees/MessageResolver.sol | 18 ++- contracts/evmx/helpers/AddressResolver.sol | 10 +- .../evmx/helpers/AddressResolverUtil.sol | 6 +- .../{FeesPlugPdas.sol => GasStationPdas.sol} | 2 +- .../evmx/interfaces/IAddressResolver.sol | 8 +- ...FeesManager.sol => IGasAccountManager.sol} | 8 +- .../{IFeesPlug.sol => IGasStation.sol} | 2 +- .../{IFeesPool.sol => IGasVault.sol} | 2 +- .../plugs/{FeesPlug.sol => GasStation.sol} | 8 +- contracts/evmx/watcher/Watcher.sol | 47 ++++---- hardhat-scripts/admin/disconnect.ts | 30 ++--- hardhat-scripts/admin/rescue.ts | 9 +- hardhat-scripts/config/config.ts | 16 +-- hardhat-scripts/deploy/1.deploy.ts | 24 ++-- hardhat-scripts/deploy/2.roles.ts | 10 +- hardhat-scripts/deploy/3.configureChains.ts | 40 +++---- hardhat-scripts/deploy/4.configureEVMx.ts | 27 +++-- hardhat-scripts/deploy/5.fundTransfers.ts | 22 ++-- hardhat-scripts/deploy/6.connect.ts | 4 +- hardhat-scripts/deploy/8.setupEnv.ts | 13 +-- hardhat-scripts/deploy/9.setupTransmitter.ts | 12 +- hardhat-scripts/test/chainTest.ts | 23 ++-- hardhat-scripts/utils/gatewayId.ts | 6 +- .../WithdrawFeesArbitrumFeesPlug.s.sol | 8 +- script/helpers/CheckDepositedCredits.s.sol | 14 ++- script/helpers/DepositCredit.s.sol | 10 +- script/helpers/DepositCreditAndNative.s.sol | 10 +- script/helpers/DepositCreditMainnet.s.sol | 11 +- script/helpers/TransferRemainingCredits.s.sol | 14 ++- script/helpers/WithdrawRemainingCredits.s.sol | 16 +-- src/enums.ts | 8 +- src/events.ts | 2 +- src/types.ts | 8 +- test/SetupTest.t.sol | 105 +++++++++--------- test/apps/counter/CounterAppGateway.sol | 2 +- 39 files changed, 357 insertions(+), 323 deletions(-) rename contracts/evmx/fees/{FeesManager.sol => GasAccountManager.sol} (94%) rename contracts/evmx/fees/{Credit.sol => GasAccountToken.sol} (84%) rename contracts/evmx/fees/{FeesPool.sol => GasVault.sol} (92%) rename contracts/evmx/helpers/solana-utils/program-pda/{FeesPlugPdas.sol => GasStationPdas.sol} (98%) rename contracts/evmx/interfaces/{IFeesManager.sol => IGasAccountManager.sol} (87%) rename contracts/evmx/interfaces/{IFeesPlug.sol => IGasStation.sol} (97%) rename contracts/evmx/interfaces/{IFeesPool.sol => IGasVault.sol} (94%) rename contracts/evmx/plugs/{FeesPlug.sol => GasStation.sol} (96%) diff --git a/contracts/evmx/base/AppGatewayBase.sol b/contracts/evmx/base/AppGatewayBase.sol index 9b54b506..0a39054b 100644 --- a/contracts/evmx/base/AppGatewayBase.sol +++ b/contracts/evmx/base/AppGatewayBase.sol @@ -111,8 +111,7 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway { return bytes32(0); } - onChainAddress = IForwarder(forwarderAddresses[contractId_][chainSlug_]) - .getOnChainAddress(); + onChainAddress = IForwarder(forwarderAddresses[contractId_][chainSlug_]).getOnChainAddress(); } //////////////////////////////////////////////////////////////////////////////////////////////// @@ -160,7 +159,7 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway { uint256 nonce, bytes memory signature ) = abi.decode(feesApprovalData_, (address, uint256, uint256, uint256, bytes)); - IERC20(address(feesManager__())).permit(spender, value, deadline, nonce, signature); + IERC20(address(gasAccountManager__())).permit(spender, value, deadline, nonce, signature); } /// @notice Withdraws fee tokens @@ -174,8 +173,11 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway { uint256 amount_, address receiver_ ) internal { - IERC20(address(feesManager__())).approve(address(feesManager__()), type(uint256).max); - feesManager__().withdrawCredits( + IERC20(address(gasAccountManager__())).approve( + address(gasAccountManager__()), + type(uint256).max + ); + gasAccountManager__().withdrawCredits( chainSlug_, token_, amount_, diff --git a/contracts/evmx/fees/FeesManager.sol b/contracts/evmx/fees/GasAccountManager.sol similarity index 94% rename from contracts/evmx/fees/FeesManager.sol rename to contracts/evmx/fees/GasAccountManager.sol index 69ce7ee4..ec7f9b10 100644 --- a/contracts/evmx/fees/FeesManager.sol +++ b/contracts/evmx/fees/GasAccountManager.sol @@ -1,12 +1,12 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; -import "./Credit.sol"; +import "./GasAccountToken.sol"; import {ForwarderSolana} from "../helpers/ForwarderSolana.sol"; -/// @title FeesManager +/// @title GasAccountManager /// @notice Contract for managing fees -contract FeesManager is Credit { +contract GasAccountManager is GasAccountToken { using OverrideParamsLib for OverrideParams; /// @notice Emitted when fees are blocked for a batch @@ -48,14 +48,14 @@ contract FeesManager is Credit { function initialize( uint32 evmxSlug_, address addressResolver_, - address feesPool_, + address gasVault_, address owner_, uint256 fees_, bytes32 sbType_, address forwarderSolana_ ) public reinitializer(2) { evmxSlug = evmxSlug_; - feesPool = IFeesPool(feesPool_); + gasVault = IGasVault(gasVault_); maxFeesPerChainSlug[evmxSlug_] = fees_; overrideParams = overrideParams.setSwitchboardType(sbType_).setMaxFees(fees_); @@ -64,8 +64,8 @@ contract FeesManager is Credit { forwarderSolana = ForwarderSolana(forwarderSolana_); } - function setFeesPlugSolanaProgramId(bytes32 feesPlugSolanaProgramId_) external onlyOwner { - feesPlugSolanaProgramId = feesPlugSolanaProgramId_; + function setGasStationSolanaProgramId(bytes32 gasStationSolanaProgramId_) external onlyOwner { + gasStationSolanaProgramId = gasStationSolanaProgramId_; } function setSusdcSolanaProgramId(bytes32 susdcSolanaProgramId_) external onlyOwner { diff --git a/contracts/evmx/fees/Credit.sol b/contracts/evmx/fees/GasAccountToken.sol similarity index 84% rename from contracts/evmx/fees/Credit.sol rename to contracts/evmx/fees/GasAccountToken.sol index c62140b1..eff30ec7 100644 --- a/contracts/evmx/fees/Credit.sol +++ b/contracts/evmx/fees/GasAccountToken.sol @@ -7,9 +7,9 @@ import "solady/utils/SafeTransferLib.sol"; import "solady/auth/Ownable.sol"; import "solady/tokens/ERC20.sol"; -import "../interfaces/IFeesManager.sol"; -import "../interfaces/IFeesPlug.sol"; -import "../interfaces/IFeesPool.sol"; +import "../interfaces/IGasAccountManager.sol"; +import "../interfaces/IGasStation.sol"; +import "../interfaces/IGasVault.sol"; import {AddressResolverUtil} from "../helpers/AddressResolverUtil.sol"; import {NonceUsed, InvalidAmount, InsufficientCreditsAvailable, InsufficientBalance, InvalidChainSlug, NotRequestHandler, InvalidReceiver} from "../../utils/common/Errors.sol"; @@ -19,18 +19,18 @@ import "../base/AppGatewayBase.sol"; import {toBytes32Format} from "../../utils/common/Converters.sol"; import {ForwarderSolana} from "../helpers/ForwarderSolana.sol"; import {SolanaInstruction, SolanaInstructionData, SolanaInstructionDataDescription} from "../../utils/common/Structs.sol"; -import {FeesPlugProgramPda} from "../helpers/solana-utils/program-pda/FeesPlugPdas.sol"; +import {GasStationProgramPda} from "../helpers/solana-utils/program-pda/GasStationPdas.sol"; import {SolanaPDA} from "../helpers/solana-utils/SolanaPda.sol"; import {TOKEN_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID, SYSTEM_PROGRAM_ID} from "../helpers/solana-utils/SolanaPda.sol"; -abstract contract FeesManagerStorage is IFeesManager { +abstract contract GasAccountManagerStorage is IGasAccountManager { // slots [0-49] reserved for gap uint256[50] _gap_before; // slot 50 /// @notice evmx slug uint32 public evmxSlug; - IFeesPool public feesPool; + IGasVault public gasVault; // slot 51 /// @notice Mapping to track blocked credits for each user @@ -56,7 +56,7 @@ abstract contract FeesManagerStorage is IFeesManager { // slot 55 /// @notice Mapping to track fees plug for each chain slug /// @dev chainSlug => fees plug address - mapping(uint32 => bytes32) public feesPlugs; + mapping(uint32 => bytes32) public gasStations; // slot 56 /// @notice Mapping to track max fees per chain slug @@ -66,7 +66,7 @@ abstract contract FeesManagerStorage is IFeesManager { ForwarderSolana public forwarderSolana; bytes32 public susdcSolanaProgramId; - bytes32 public feesPlugSolanaProgramId; + bytes32 public gasStationSolanaProgramId; // slots [60-107] reserved for gap uint256[44] _gap_after; @@ -77,7 +77,13 @@ abstract contract FeesManagerStorage is IFeesManager { /// @title SocketUSDC /// @notice ERC20 token for managing credits with blocking/unblocking functionality -abstract contract Credit is FeesManagerStorage, Initializable, Ownable, AppGatewayBase, ERC20 { +abstract contract GasAccountToken is + GasAccountManagerStorage, + Initializable, + Ownable, + AppGatewayBase, + ERC20 +{ using OverrideParamsLib for OverrideParams; /// @notice Emitted when fees deposited are updated @@ -101,25 +107,25 @@ abstract contract Credit is FeesManagerStorage, Initializable, Ownable, AppGatew event CreditsUnwrapped(address indexed consumeFrom, uint256 amount); /// @notice Emitted when fees plug is set - event FeesPlugSet(uint32 indexed chainSlug, bytes32 indexed feesPlug); + event GasStationSet(uint32 indexed chainSlug, bytes32 indexed gasStation); /// @notice Emitted when fees pool is set - event FeesPoolSet(address indexed feesPool); + event GasVaultSet(address indexed gasVault); /// @notice Emitted when withdraw fails event WithdrawFailed(bytes32 indexed payloadId); /// @notice Emitted when fees plug solana program id is set - event FeesPlugSolanaSet(bytes32 indexed feesPlugSolanaProgramId); + event GasStationSolanaSet(bytes32 indexed gasStationSolanaProgramId); - function setFeesPlug(uint32 chainSlug_, bytes32 feesPlug_) external onlyOwner { - feesPlugs[chainSlug_] = feesPlug_; - emit FeesPlugSet(chainSlug_, feesPlug_); + function setGasStation(uint32 chainSlug_, bytes32 gasStation_) external onlyOwner { + gasStations[chainSlug_] = gasStation_; + emit GasStationSet(chainSlug_, gasStation_); } - function setFeesPool(address feesPool_) external onlyOwner { - feesPool = IFeesPool(feesPool_); - emit FeesPoolSet(feesPool_); + function setGasVault(address gasVault_) external onlyOwner { + gasVault = IGasVault(gasVault_); + emit GasVaultSet(gasVault_); } function getMaxFees(uint32 chainSlug_) public view returns (uint256) { @@ -137,8 +143,13 @@ abstract contract Credit is FeesManagerStorage, Initializable, Ownable, AppGatew /// @param payload_ Encoded deposit parameters: (chainSlug, token, receiver, creditAmount, nativeAmount) function deposit(bytes calldata payload_) external override onlyWatcher { // Decode payload: (chainSlug, token, receiver, creditAmount, nativeAmount) - (uint32 chainSlug_, address token_, address depositTo_, uint256 creditAmount_, uint256 nativeAmount_) = - abi.decode(payload_, (uint32, address, address, uint256, uint256)); + ( + uint32 chainSlug_, + address token_, + address depositTo_, + uint256 creditAmount_, + uint256 nativeAmount_ + ) = abi.decode(payload_, (uint32, address, address, uint256, uint256)); tokenOnChainBalances[chainSlug_][toBytes32Format(token_)] += creditAmount_ + nativeAmount_; @@ -146,7 +157,7 @@ abstract contract Credit is FeesManagerStorage, Initializable, Ownable, AppGatew _mint(depositTo_, creditAmount_); if (nativeAmount_ > 0) { // if native transfer fails, add to credit - bool success = feesPool.withdraw(depositTo_, nativeAmount_); + bool success = gasVault.withdraw(depositTo_, nativeAmount_); if (!success) { // Convert failed native amount to credits @@ -167,7 +178,7 @@ abstract contract Credit is FeesManagerStorage, Initializable, Ownable, AppGatew _mint(receiver_, amount); // reverts if transfer fails - SafeTransferLib.safeTransferETH(address(feesPool), amount); + SafeTransferLib.safeTransferETH(address(gasVault), amount); emit CreditsWrapped(receiver_, amount); } @@ -177,7 +188,7 @@ abstract contract Credit is FeesManagerStorage, Initializable, Ownable, AppGatew // Burn tokens from sender _burn(msg.sender, amount_); - bool success = feesPool.withdraw(receiver_, amount_); + bool success = gasVault.withdraw(receiver_, amount_); if (!success) revert InsufficientBalance(); emit CreditsUnwrapped(receiver_, amount_); @@ -228,7 +239,7 @@ abstract contract Credit is FeesManagerStorage, Initializable, Ownable, AppGatew address from_, address to_, uint256 amount_ - ) public override(ERC20, IFeesManager) returns (bool) { + ) public override(ERC20, IGasAccountManager) returns (bool) { if (!isCreditSpendable(from_, msg.sender, amount_)) revert InsufficientCreditsAvailable(); if (msg.sender == address(watcher__())) _approve(from_, msg.sender, amount_); @@ -265,7 +276,7 @@ abstract contract Credit is FeesManagerStorage, Initializable, Ownable, AppGatew chainSlug_, consumeFrom, maxFees_, - abi.encodeCall(IFeesPlug.withdrawFees, (token_, receiver_, credits_)) + abi.encodeCall(IGasStation.withdrawFees, (token_, receiver_, credits_)) ); } @@ -287,15 +298,15 @@ abstract contract Credit is FeesManagerStorage, Initializable, Ownable, AppGatew _burn(consumeFrom, credits_); tokenOnChainBalances[chainSlug_][token_] -= credits_; - feesPlugWithdrawSolana(token_, credits_, onchainReceiver_); + gasStationWithdrawSolana(token_, credits_, onchainReceiver_); } - function feesPlugWithdrawSolana( + function gasStationWithdrawSolana( bytes32 token_, uint256 credits_, bytes32 onchainReceiver_ ) internal { - SolanaInstruction memory solanaInstruction_ = createFeesPlugWithdrawInstructionSolana( + SolanaInstruction memory solanaInstruction_ = createGasStationWithdrawInstructionSolana( onchainReceiver_, token_, credits_ @@ -321,7 +332,7 @@ abstract contract Credit is FeesManagerStorage, Initializable, Ownable, AppGatew rawPayload.overrideParams = overrideParams; rawPayload.transaction = Transaction({ chainSlug: chainSlug_, - target: _getFeesPlugAddress(chainSlug_), + target: _getGasStationAddress(chainSlug_), payload: payload_ }); watcher__().addPayloadData(rawPayload, address(this)); @@ -331,9 +342,9 @@ abstract contract Credit is FeesManagerStorage, Initializable, Ownable, AppGatew _increaseFees(payloadId_, newMaxFees_); } - function _getFeesPlugAddress(uint32 chainSlug_) internal view returns (bytes32) { - if (feesPlugs[chainSlug_] == bytes32(0)) revert InvalidChainSlug(); - return feesPlugs[chainSlug_]; + function _getGasStationAddress(uint32 chainSlug_) internal view returns (bytes32) { + if (gasStations[chainSlug_] == bytes32(0)) revert InvalidChainSlug(); + return gasStations[chainSlug_]; } function _recoverSigner( @@ -358,27 +369,27 @@ abstract contract Credit is FeesManagerStorage, Initializable, Ownable, AppGatew return 18; } - function createFeesPlugWithdrawInstructionSolana( + function createGasStationWithdrawInstructionSolana( bytes32 userAddress_, bytes32 susdcMint_, uint256 withdrawAmount_ ) internal view returns (SolanaInstruction memory) { bytes32[] memory accounts = new bytes32[](11); // accounts 0 - tmpReturnStoragePda - (accounts[0], ) = FeesPlugProgramPda.deriveTmpReturnStoragePda(feesPlugSolanaProgramId); + (accounts[0], ) = GasStationProgramPda.deriveTmpReturnStoragePda(gasStationSolanaProgramId); /*----------------- mint() accounts -----------------*/ // accounts 1 - programConfigPda - (accounts[1], ) = FeesPlugProgramPda.deriveProgramConfigPda(feesPlugSolanaProgramId); + (accounts[1], ) = GasStationProgramPda.deriveProgramConfigPda(gasStationSolanaProgramId); // accounts 2 - vaultConfigPda - (bytes32 vaultConfigPda, ) = FeesPlugProgramPda.deriveVaultConfigPda( - feesPlugSolanaProgramId + (bytes32 vaultConfigPda, ) = GasStationProgramPda.deriveVaultConfigPda( + gasStationSolanaProgramId ); accounts[2] = vaultConfigPda; // accounts 3 - token mint address accounts[3] = susdcMint_; // accounts 4 - whitelistedTokenPda - (accounts[4], ) = FeesPlugProgramPda.deriveWhitelistedTokenPda( - feesPlugSolanaProgramId, + (accounts[4], ) = GasStationProgramPda.deriveWhitelistedTokenPda( + gasStationSolanaProgramId, susdcMint_ ); // accounts 5 - receiver address @@ -423,7 +434,7 @@ abstract contract Credit is FeesManagerStorage, Initializable, Ownable, AppGatew bytes[] memory functionArguments = new bytes[](2); bool isNative = false; - // here on purpose we do not convert to uint64 as feesPlug withdraw function expects uint256 + // here on purpose we do not convert to uint64 as gasStation withdraw function expects uint256 uint64 withdrawAmountU64 = convertToSolanaUint64(withdrawAmount_); functionArguments[0] = abi.encode(withdrawAmountU64); functionArguments[1] = abi.encode(isNative ? 1 : 0); @@ -435,7 +446,7 @@ abstract contract Credit is FeesManagerStorage, Initializable, Ownable, AppGatew return SolanaInstruction({ data: SolanaInstructionData({ - programId: feesPlugSolanaProgramId, + programId: gasStationSolanaProgramId, instructionDiscriminator: instructionDiscriminator, accounts: accounts, functionArguments: functionArguments diff --git a/contracts/evmx/fees/FeesPool.sol b/contracts/evmx/fees/GasVault.sol similarity index 92% rename from contracts/evmx/fees/FeesPool.sol rename to contracts/evmx/fees/GasVault.sol index f7aeb73d..f150dcdb 100644 --- a/contracts/evmx/fees/FeesPool.sol +++ b/contracts/evmx/fees/GasVault.sol @@ -3,14 +3,14 @@ pragma solidity ^0.8.21; import "../../utils/AccessControl.sol"; import "../../utils/common/AccessRoles.sol"; -import "../interfaces/IFeesPool.sol"; +import "../interfaces/IGasVault.sol"; import "solady/utils/SafeTransferLib.sol"; /** - * @title FeesPool + * @title GasVault * @notice Contract to store native fees that can be pulled by fees manager */ -contract FeesPool is IFeesPool, AccessControl { +contract GasVault is IGasVault, AccessControl { error TransferFailed(); /** diff --git a/contracts/evmx/fees/MessageResolver.sol b/contracts/evmx/fees/MessageResolver.sol index 3c38ba31..ba1b5104 100644 --- a/contracts/evmx/fees/MessageResolver.sol +++ b/contracts/evmx/fees/MessageResolver.sol @@ -73,7 +73,7 @@ abstract contract MessageResolverStorage { * @title MessageResolver * @notice Contract for resolving payments to transmitters for relaying messages on EVMx * @dev This contract tracks message details and handles payment settlement after execution - * @dev Uses Credits (ERC20) from FeesManager for payment settlement + * @dev Uses Credits (ERC20) from GasAccountManager for payment settlement * @dev Upgradeable proxy pattern with AddressResolverUtil */ contract MessageResolver is @@ -236,7 +236,7 @@ contract MessageResolver is /** * @notice Mark message as executed and pay transmitter * @dev Called by watcher after confirming execution on destination - * @dev Uses Credits from FeesManager for payment + * @dev Uses Credits from GasAccountManager for payment * @param payloadId_ Unique identifier for the payload * @param signature_ Watcher signature confirming execution * @param nonce_ Nonce to prevent replay attacks @@ -265,16 +265,22 @@ contract MessageResolver is if (usedNonces[watcher][nonce_]) revert NonceAlreadyUsed(); usedNonces[watcher][nonce_] = true; - // Check sponsor has sufficient credits (uses AddressResolver to get latest FeesManager) - if (!feesManager__().isCreditSpendable(details.sponsor, address(this), details.feeAmount)) { + // Check sponsor has sufficient credits (uses AddressResolver to get latest GasAccountManager) + if ( + !gasAccountManager__().isCreditSpendable( + details.sponsor, + address(this), + details.feeAmount + ) + ) { revert InsufficientSponsorCredits(); } // Mark message as executed details.status = ExecutionStatus.Executed; - // Transfer credits from sponsor to transmitter using FeesManager from AddressResolver - bool success = feesManager__().transferFrom( + // Transfer credits from sponsor to transmitter using GasAccountManager from AddressResolver + bool success = gasAccountManager__().transferFrom( details.sponsor, details.transmitter, details.feeAmount diff --git a/contracts/evmx/helpers/AddressResolver.sol b/contracts/evmx/helpers/AddressResolver.sol index aa418588..b81dbb68 100644 --- a/contracts/evmx/helpers/AddressResolver.sol +++ b/contracts/evmx/helpers/AddressResolver.sol @@ -15,7 +15,7 @@ abstract contract AddressResolverStorage is IAddressResolver { IWatcher public override watcher__; // slot 51 - IFeesManager public override feesManager__; + IGasAccountManager public override gasAccountManager__; // slot 52 IAsyncDeployer public override asyncDeployer__; @@ -56,10 +56,10 @@ contract AddressResolver is AddressResolverStorage, Initializable, Ownable { } /// @notice Updates the address of the fees manager - /// @param feesManager_ The address of the fees manager - function setFeesManager(address feesManager_) external override onlyOwner { - feesManager__ = IFeesManager(feesManager_); - emit FeesManagerUpdated(feesManager_); + /// @param gasAccountManager_ The address of the fees manager + function setGasAccountManager(address gasAccountManager_) external override onlyOwner { + gasAccountManager__ = IGasAccountManager(gasAccountManager_); + emit GasAccountManagerUpdated(gasAccountManager_); } /// @notice Updates the address of the async deployer diff --git a/contracts/evmx/helpers/AddressResolverUtil.sol b/contracts/evmx/helpers/AddressResolverUtil.sol index 6517f4ed..a588ca7a 100644 --- a/contracts/evmx/helpers/AddressResolverUtil.sol +++ b/contracts/evmx/helpers/AddressResolverUtil.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.21; import "../interfaces/IAddressResolver.sol"; import "../interfaces/IWatcher.sol"; -import "../interfaces/IFeesManager.sol"; +import "../interfaces/IGasAccountManager.sol"; import "../interfaces/IAsyncDeployer.sol"; import {OnlyWatcherAllowed} from "../../utils/common/Errors.sol"; @@ -43,8 +43,8 @@ abstract contract AddressResolverUtil { /// @notice Gets the watcher precompile contract interface /// @return IWatcher interface of the registered watcher precompile /// @dev Resolves and returns the watcher precompile contract for interaction - function feesManager__() public view returns (IFeesManager) { - return addressResolver__.feesManager__(); + function gasAccountManager__() public view returns (IGasAccountManager) { + return addressResolver__.gasAccountManager__(); } /// @notice Gets the async deployer contract interface diff --git a/contracts/evmx/helpers/solana-utils/program-pda/FeesPlugPdas.sol b/contracts/evmx/helpers/solana-utils/program-pda/GasStationPdas.sol similarity index 98% rename from contracts/evmx/helpers/solana-utils/program-pda/FeesPlugPdas.sol rename to contracts/evmx/helpers/solana-utils/program-pda/GasStationPdas.sol index 439a3884..17533c1b 100644 --- a/contracts/evmx/helpers/solana-utils/program-pda/FeesPlugPdas.sol +++ b/contracts/evmx/helpers/solana-utils/program-pda/GasStationPdas.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.21; import {SolanaPDA} from "../SolanaPda.sol"; -library FeesPlugProgramPda { +library GasStationProgramPda { // string: "config:" bytes constant PROGRAM_CONFIG_SEED_PREFIX_HEX = hex"636f6e6669673a"; // string: "vault_config:" diff --git a/contracts/evmx/interfaces/IAddressResolver.sol b/contracts/evmx/interfaces/IAddressResolver.sol index 145e556b..69ff97ef 100644 --- a/contracts/evmx/interfaces/IAddressResolver.sol +++ b/contracts/evmx/interfaces/IAddressResolver.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; import "./IWatcher.sol"; -import "./IFeesManager.sol"; +import "./IGasAccountManager.sol"; import "./IAsyncDeployer.sol"; /// @title IAddressResolver @@ -9,7 +9,7 @@ import "./IAsyncDeployer.sol"; /// @dev Provides address lookup functionality for core system components interface IAddressResolver { /// @notice Event emitted when the fees manager is updated - event FeesManagerUpdated(address feesManager_); + event GasAccountManagerUpdated(address gasAccountManager_); /// @notice Event emitted when the watcher precompile is updated event WatcherUpdated(address watcher_); /// @notice Event emitted when the async deployer is updated @@ -20,7 +20,7 @@ interface IAddressResolver { // System component addresses function watcher__() external view returns (IWatcher); - function feesManager__() external view returns (IFeesManager); + function gasAccountManager__() external view returns (IGasAccountManager); function asyncDeployer__() external view returns (IAsyncDeployer); @@ -28,7 +28,7 @@ interface IAddressResolver { function setWatcher(address watcher_) external; - function setFeesManager(address feesManager_) external; + function setGasAccountManager(address gasAccountManager_) external; function setAsyncDeployer(address asyncDeployer_) external; diff --git a/contracts/evmx/interfaces/IFeesManager.sol b/contracts/evmx/interfaces/IGasAccountManager.sol similarity index 87% rename from contracts/evmx/interfaces/IFeesManager.sol rename to contracts/evmx/interfaces/IGasAccountManager.sol index cd9f4f75..1fcb8239 100644 --- a/contracts/evmx/interfaces/IFeesManager.sol +++ b/contracts/evmx/interfaces/IGasAccountManager.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.21; import {WriteFinality, AppGatewayApprovals, OverrideParams, Transaction, RawPayload, Payload} from "../../utils/common/Structs.sol"; -interface IFeesManager { +interface IGasAccountManager { function deposit(bytes calldata payload_) external; function wrap(address receiver_) external payable; @@ -25,7 +25,11 @@ interface IFeesManager { function blockCredits(bytes32 payloadId_, address consumeFrom_, uint256 credits_) external; - function unblockAndAssignCredits(bytes32 payloadId_, address assignTo_, uint256 amount_) external; + function unblockAndAssignCredits( + bytes32 payloadId_, + address assignTo_, + uint256 amount_ + ) external; function unblockCredits(bytes32 payloadId_) external; diff --git a/contracts/evmx/interfaces/IFeesPlug.sol b/contracts/evmx/interfaces/IGasStation.sol similarity index 97% rename from contracts/evmx/interfaces/IFeesPlug.sol rename to contracts/evmx/interfaces/IGasStation.sol index 71b1134d..0adbea76 100644 --- a/contracts/evmx/interfaces/IFeesPlug.sol +++ b/contracts/evmx/interfaces/IGasStation.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; -interface IFeesPlug { +interface IGasStation { /// @notice Event emitted when fees are deposited event FeesDeposited( address token, diff --git a/contracts/evmx/interfaces/IFeesPool.sol b/contracts/evmx/interfaces/IGasVault.sol similarity index 94% rename from contracts/evmx/interfaces/IFeesPool.sol rename to contracts/evmx/interfaces/IGasVault.sol index b2dcf1a1..d101fd2e 100644 --- a/contracts/evmx/interfaces/IFeesPool.sol +++ b/contracts/evmx/interfaces/IGasVault.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; -interface IFeesPool { +interface IGasVault { event NativeDeposited(address indexed from, uint256 amount); event NativeWithdrawn(bool success, address indexed to, uint256 amount); diff --git a/contracts/evmx/plugs/FeesPlug.sol b/contracts/evmx/plugs/GasStation.sol similarity index 96% rename from contracts/evmx/plugs/FeesPlug.sol rename to contracts/evmx/plugs/GasStation.sol index ad69fefe..2bab46d6 100644 --- a/contracts/evmx/plugs/FeesPlug.sol +++ b/contracts/evmx/plugs/GasStation.sol @@ -5,16 +5,16 @@ import "solady/utils/SafeTransferLib.sol"; import "../../protocol/base/PlugBase.sol"; import "../../utils/AccessControl.sol"; import {RESCUE_ROLE} from "../../utils/common/AccessRoles.sol"; -import {IFeesPlug} from "../interfaces/IFeesPlug.sol"; +import {IGasStation} from "../interfaces/IGasStation.sol"; import "../../utils/RescueFundsLib.sol"; import {InvalidTokenAddress} from "../../utils/common/Errors.sol"; import "../interfaces/IERC20.sol"; -/// @title FeesPlug +/// @title GasStation /// @notice Contract for managing fees on a network /// @dev The amount deposited here is locked and updated in the EVMx for an app gateway /// @dev The fees are redeemed by the transmitters executing request or can be withdrawn by the owner -contract FeesPlug is IFeesPlug, PlugBase, AccessControl { +contract GasStation is IGasStation, PlugBase, AccessControl { using SafeTransferLib for address; /// @notice Mapping to store if a token is whitelisted @@ -27,7 +27,7 @@ contract FeesPlug is IFeesPlug, PlugBase, AccessControl { /// @notice Error thrown when token is not whitelisted error TokenNotWhitelisted(address token_); - /// @notice Constructor for the FeesPlug contract + /// @notice Constructor for the GasStation contract /// @param socket_ The socket address /// @param owner_ The owner address constructor(address socket_, address owner_) { diff --git a/contracts/evmx/watcher/Watcher.sol b/contracts/evmx/watcher/Watcher.sol index 898baf1a..ed3963c2 100644 --- a/contracts/evmx/watcher/Watcher.sol +++ b/contracts/evmx/watcher/Watcher.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.21; import "solady/utils/Initializable.sol"; import "./Configurations.sol"; import {IPrecompile} from "../interfaces/IPrecompile.sol"; -import {IFeesManager} from "../interfaces/IFeesManager.sol"; +import {IGasAccountManager} from "../interfaces/IGasAccountManager.sol"; import {IPromise} from "../interfaces/IPromise.sol"; import {IERC20} from "../interfaces/IERC20.sol"; import "../../utils/common/IdUtils.sol"; @@ -78,7 +78,7 @@ contract Watcher is Initializable, Configurations, Pausable { function executePayload() external whenNotPaused returns (address asyncPromise) { if (latestAppGateway != msg.sender) revert AppGatewayMismatch(); if ( - !feesManager__().isCreditSpendable( + !gasAccountManager__().isCreditSpendable( payloadData.overrideParams.consumeFrom, latestAppGateway, payloadData.overrideParams.maxFees @@ -88,7 +88,7 @@ contract Watcher is Initializable, Configurations, Pausable { IPrecompile precompile = IPrecompile(precompiles[payloadData.overrideParams.callType]); if (address(precompile) == address(0)) revert InvalidCallType(); - feesManager__().blockCredits( + gasAccountManager__().blockCredits( currentPayloadId, payloadData.overrideParams.consumeFrom, payloadData.overrideParams.maxFees @@ -139,7 +139,7 @@ contract Watcher is Initializable, Configurations, Pausable { if (!p.isTransmitterFeesSettled) { p.isTransmitterFeesSettled = true; - feesManager__().unblockAndAssignCredits(p.payloadId, transmitter, feesUsed_); + gasAccountManager__().unblockAndAssignCredits(p.payloadId, transmitter, feesUsed_); } p.isPayloadExecuted = true; @@ -149,8 +149,8 @@ contract Watcher is Initializable, Configurations, Pausable { bool success = _markResolved(resolvedPromise_); if (!success) return; - feesManager__().unblockAndAssignCredits(p.payloadId, address(this), p.watcherFees); - feesManager__().unblockCredits(p.payloadId); + gasAccountManager__().unblockAndAssignCredits(p.payloadId, address(this), p.watcherFees); + gasAccountManager__().unblockCredits(p.payloadId); emit PayloadSettled(p.payloadId); emit PayloadResolved(resolvedPromise_.payloadId); } @@ -225,7 +225,7 @@ contract Watcher is Initializable, Configurations, Pausable { uint256 deadline = abi.decode(params_.overrides, (uint256)); if (deadline < block.timestamp) revert DeadlinePassed(); - IERC20(address(feesManager__())).transferFrom(appGateway, address(this), triggerFees); + IERC20(address(gasAccountManager__())).transferFrom(appGateway, address(this), triggerFees); triggerFromChainSlug = params_.chainSlug; triggerFromPlug = params_.plug; (bool success, , ) = appGateway.tryCall( @@ -255,14 +255,18 @@ contract Watcher is Initializable, Configurations, Pausable { if (r.isPayloadCancelled) revert PayloadAlreadyCancelled(); if (r.isPayloadExecuted) revert PayloadAlreadySettled(); if (r.maxFees >= newMaxFees_) revert NewMaxFeesLowerThanCurrent(r.maxFees, newMaxFees_); - feesManager__().unblockCredits(payloadId_); + gasAccountManager__().unblockCredits(payloadId_); r.maxFees = newMaxFees_; // reblock new fees if ( - !IFeesManager(feesManager__()).isCreditSpendable(r.consumeFrom, msg.sender, newMaxFees_) + !IGasAccountManager(gasAccountManager__()).isCreditSpendable( + r.consumeFrom, + msg.sender, + newMaxFees_ + ) ) revert InsufficientFees(); - feesManager__().blockCredits(payloadId_, r.consumeFrom, newMaxFees_); + gasAccountManager__().blockCredits(payloadId_, r.consumeFrom, newMaxFees_); // indexed by transmitter and watcher to start bidding or re-processing the request emit FeesIncreased(payloadId_, newMaxFees_); @@ -276,8 +280,12 @@ contract Watcher is Initializable, Configurations, Pausable { r.isPayloadCancelled = true; r.isTransmitterFeesSettled = true; - feesManager__().unblockAndAssignCredits(payloadId_, transmitter, r.maxFees - r.watcherFees); - feesManager__().unblockAndAssignCredits(payloadId_, address(this), r.watcherFees); + gasAccountManager__().unblockAndAssignCredits( + payloadId_, + transmitter, + r.maxFees - r.watcherFees + ); + gasAccountManager__().unblockAndAssignCredits(payloadId_, address(this), r.watcherFees); emit PayloadCancelled(payloadId_); } @@ -308,13 +316,14 @@ contract Watcher is Initializable, Configurations, Pausable { uint32 switchboardId = switchboards[chainSlug_][switchboardType_]; // Write payload: origin = (evmxChainSlug, watcherId), verification = (dstChainSlug, dstSwitchboardId) // watcherId hardcoded as 1 for now - return createPayloadId( - evmxSlug, // origin chain slug (evmx) - 1, // origin id (watcher id, hardcoded) - chainSlug_, // verification chain slug (destination) - switchboardId, // verification id (destination switchboard) - nextPayloadCount // pointer (counter) - ); + return + createPayloadId( + evmxSlug, // origin chain slug (evmx) + 1, // origin id (watcher id, hardcoded) + chainSlug_, // verification chain slug (destination) + switchboardId, // verification id (destination switchboard) + nextPayloadCount // pointer (counter) + ); } /// @notice Read a simple payload by id. diff --git a/hardhat-scripts/admin/disconnect.ts b/hardhat-scripts/admin/disconnect.ts index d7f195a9..0c5490e1 100644 --- a/hardhat-scripts/admin/disconnect.ts +++ b/hardhat-scripts/admin/disconnect.ts @@ -1,6 +1,6 @@ import { constants, Wallet } from "ethers"; import { ChainAddressesObj, ChainSlug, Contracts } from "../../src"; -import { chains, EVMX_CHAIN_ID, getFeesPlugChains, mode } from "../config"; +import { chains, EVMX_CHAIN_ID, getGasStationChains, mode } from "../config"; import { AppGatewayConfig, DeploymentAddresses, @@ -18,7 +18,7 @@ import { getWatcherSigner, sendWatcherMultiCallWithNonce } from "../utils/sign"; import { isConfigSetOnEVMx, isConfigSetOnSocket } from "../utils"; // update this map to disconnect plugs from chains not in this list -const feesPlugChains = getFeesPlugChains(); +const gasStationChains = getGasStationChains(); export const main = async () => { try { @@ -74,13 +74,13 @@ export const disconnectPlugsOnSocket = async () => { // Disconnect plugs on each chain await Promise.all( chains.map(async (chain) => { - // skip if chain is in feesPlugChains or not in addresses - if (feesPlugChains.includes(chain) || !addresses[chain]) return; + // skip if chain is in gasStationChains or not in addresses + if (gasStationChains.includes(chain) || !addresses[chain]) return; const socketSigner = getSocketSigner(chain as ChainSlug); const addr = addresses[chain]!; - if (addr[Contracts.FeesPlug]) { - await disconnectPlug(chain, Contracts.FeesPlug, socketSigner, addr); + if (addr[Contracts.GasStation]) { + await disconnectPlug(chain, Contracts.GasStation, socketSigner, addr); } }) ); @@ -96,10 +96,10 @@ export const updateConfigEVMx = async () => { // Set up Watcher contract const signer = getWatcherSigner(); const EVMxAddresses = addresses[EVMX_CHAIN_ID]!; - const feesManagerContract = ( + const gasAccountManagerContract = ( await getInstance( - Contracts.FeesManager, - EVMxAddresses[Contracts.FeesManager] + Contracts.GasAccountManager, + EVMxAddresses[Contracts.GasAccountManager] ) ).connect(signer); @@ -113,13 +113,13 @@ export const updateConfigEVMx = async () => { // Collect configs for each chain and plug await Promise.all( chains.map(async (chain) => { - // skip if chain is in feesPlugChains or not in addresses - if (feesPlugChains.includes(chain) || !addresses[chain]) return; + // skip if chain is in gasStationChains or not in addresses + if (gasStationChains.includes(chain) || !addresses[chain]) return; const addr = addresses[chain]!; const appGatewayId = BYTES32_ZERO; const switchboardId = "0"; - const plugContract = Contracts.FeesPlug; + const plugContract = Contracts.GasStation; if (!addr[plugContract]) return; @@ -146,13 +146,13 @@ export const updateConfigEVMx = async () => { // update fees manager - const currentFeesPlug = await feesManagerContract.feesPlugs(chain); - if (currentFeesPlug.toString() === BYTES32_ZERO.toString()) { + const currentGasStation = await gasAccountManagerContract.gasStations(chain); + if (currentGasStation.toString() === BYTES32_ZERO.toString()) { console.log(`Fees plug already set on ${chain}`); return; } - const tx = await feesManagerContract.functions["setFeesPlug"]( + const tx = await gasAccountManagerContract.functions["setGasStation"]( Number(chain), BYTES32_ZERO, { diff --git a/hardhat-scripts/admin/rescue.ts b/hardhat-scripts/admin/rescue.ts index a113ecb3..fba01c73 100644 --- a/hardhat-scripts/admin/rescue.ts +++ b/hardhat-scripts/admin/rescue.ts @@ -70,9 +70,9 @@ const createContractAddrArray = (chainSlug: number): string[] => { } let addressArray: string[] = []; - if (chainAddresses.SocketFeesManager) - addressArray.push(chainAddresses.SocketFeesManager); - if (chainAddresses.FeesPlug) addressArray.push(chainAddresses.FeesPlug); + if (chainAddresses.SocketGasAccountManager) + addressArray.push(chainAddresses.SocketGasAccountManager); + if (chainAddresses.GasStation) addressArray.push(chainAddresses.GasStation); addressArray.push(chainAddresses.Socket); addressArray.push(chainAddresses.SocketBatcher); addressArray.push(chainAddresses.FastSwitchboard); @@ -102,8 +102,7 @@ export const main = async () => { const rescueAmount = await tokenInstance.balanceOf(contractAddr[index]); console.log( - `rescueAmount on ${ - contractAddr[index] + `rescueAmount on ${contractAddr[index] } on ${chainSlug} : ${formatUnits(rescueAmount.toString(), 6)}` ); if (rescueAmount.toString() === "0") continue; diff --git a/hardhat-scripts/config/config.ts b/hardhat-scripts/config/config.ts index ad5c5333..dc710972 100644 --- a/hardhat-scripts/config/config.ts +++ b/hardhat-scripts/config/config.ts @@ -14,7 +14,7 @@ export const mode = process.env.DEPLOYMENT_MODE as // Mode-specific configuration interface interface ModeConfig { chains: ChainSlug[]; - feesPlugChains: ChainSlug[]; + gasStationChains: ChainSlug[]; evmChainId: number; addresses: { watcher: string; @@ -27,7 +27,7 @@ interface ModeConfig { const MODE_CONFIGS: Record = { [DeploymentMode.LOCAL]: { chains: [ChainSlug.ARBITRUM_SEPOLIA, ChainSlug.OPTIMISM_SEPOLIA], - feesPlugChains: [], // Will use chains by default + gasStationChains: [], // Will use chains by default evmChainId: 14323, addresses: { watcher: "0x4512EB56716a2bcBE25bee93dCbb05B95FF603b0", @@ -60,7 +60,7 @@ const MODE_CONFIGS: Record = { ChainSlug.MAINNET, // ChainSlug.PLUME, ], - feesPlugChains: [], // Will use chains by default + gasStationChains: [], // Will use chains by default evmChainId: 14323, addresses: { watcher: "0x4512EB56716a2bcBE25bee93dCbb05B95FF603b0", @@ -94,7 +94,7 @@ const MODE_CONFIGS: Record = { // ChainSlug.FLOW, // ChainSlug.RISE_TESTNET, ], - feesPlugChains: [], // Will use chains by default + gasStationChains: [], // Will use chains by default evmChainId: 14323, // dummy stage // evmChainId: 12921, addresses: { @@ -126,7 +126,7 @@ const MODE_CONFIGS: Record = { ChainSlug.SONIC, ChainSlug.UNICHAIN, ], - feesPlugChains: [ + gasStationChains: [ ChainSlug.ARBITRUM, ChainSlug.AVALANCHE, ChainSlug.BASE, @@ -165,10 +165,10 @@ export const getChains = (): ChainSlug[] => { return getCurrentModeConfig().chains; }; -export const getFeesPlugChains = (): ChainSlug[] => { +export const getGasStationChains = (): ChainSlug[] => { const config = getCurrentModeConfig(); - return config.feesPlugChains.length > 0 - ? config.feesPlugChains + return config.gasStationChains.length > 0 + ? config.gasStationChains : config.chains; }; diff --git a/hardhat-scripts/deploy/1.deploy.ts b/hardhat-scripts/deploy/1.deploy.ts index 35e6792a..7624376a 100644 --- a/hardhat-scripts/deploy/1.deploy.ts +++ b/hardhat-scripts/deploy/1.deploy.ts @@ -114,16 +114,16 @@ const deployEVMxContracts = async () => { const feePool = getFeePool(); if (feePool?.length == 0) { - const feesPool = await getOrDeploy( - Contracts.FeesPool, - Contracts.FeesPool, - "contracts/evmx/fees/FeesPool.sol", + const gasVault = await getOrDeploy( + Contracts.GasVault, + Contracts.GasVault, + "contracts/evmx/fees/GasVault.sol", [EVMxOwner], deployUtils ); - deployUtils.addresses[Contracts.FeesPool] = feesPool.address; + deployUtils.addresses[Contracts.GasVault] = gasVault.address; } else { - deployUtils.addresses[Contracts.FeesPool] = feePool; + deployUtils.addresses[Contracts.GasVault] = feePool; } deployUtils = await deployContractWithProxy( @@ -140,12 +140,12 @@ const deployEVMxContracts = async () => { ); deployUtils = await deployContractWithProxy( - Contracts.FeesManager, - `contracts/evmx/fees/FeesManager.sol`, + Contracts.GasAccountManager, + `contracts/evmx/fees/GasAccountManager.sol`, [ EVMX_CHAIN_ID, addressResolver.address, - deployUtils.addresses[Contracts.FeesPool], + deployUtils.addresses[Contracts.GasVault], EVMxOwner, FEE_MANAGER_WRITE_MAX_FEES, FAST_SWITCHBOARD_TYPE, @@ -299,15 +299,15 @@ const deploySocketContracts = async () => { ); deployUtils.addresses[contractName] = sb.address; - contractName = Contracts.FeesPlug; - const feesPlug: Contract = await getOrDeploy( + contractName = Contracts.GasStation; + const gasStation: Contract = await getOrDeploy( contractName, contractName, `contracts/evmx/plugs/${contractName}.sol`, [socket.address, socketOwner], deployUtils ); - deployUtils.addresses[contractName] = feesPlug.address; + deployUtils.addresses[contractName] = gasStation.address; // contractName = Contracts.ContractFactoryPlug; // const contractFactoryPlug: Contract = await getOrDeploy( diff --git a/hardhat-scripts/deploy/2.roles.ts b/hardhat-scripts/deploy/2.roles.ts index 68fe17ea..d3d11cec 100644 --- a/hardhat-scripts/deploy/2.roles.ts +++ b/hardhat-scripts/deploy/2.roles.ts @@ -25,7 +25,7 @@ import { getWatcherSigner, getSocketSigner } from "../utils/sign"; export const REQUIRED_ROLES = { EVMx: { - FeesPool: [ROLES.FEE_MANAGER_ROLE], + GasVault: [ROLES.FEE_MANAGER_ROLE], }, Chain: { FastSwitchboard: [ROLES.WATCHER_ROLE, ROLES.RESCUE_ROLE], @@ -36,7 +36,7 @@ export const REQUIRED_ROLES = { ROLES.RESCUE_ROLE, ROLES.SWITCHBOARD_DISABLER_ROLE, ], - FeesPlug: [ROLES.RESCUE_ROLE], + GasStation: [ROLES.RESCUE_ROLE], // ContractFactoryPlug: [ROLES.RESCUE_ROLE], }, }; @@ -119,9 +119,9 @@ async function setRolesForEVMx(addresses: DeploymentAddresses) { const signer = await getSigner(EVMX_CHAIN_ID, true); await setRoleForContract( - Contracts.FeesPool, - chainAddresses[Contracts.FeesPool], - chainAddresses[Contracts.FeesManager], + Contracts.GasVault, + chainAddresses[Contracts.GasVault], + chainAddresses[Contracts.GasAccountManager], ROLES.FEE_MANAGER_ROLE, signer, EVMX_CHAIN_ID diff --git a/hardhat-scripts/deploy/3.configureChains.ts b/hardhat-scripts/deploy/3.configureChains.ts index 029244bf..4fb97a9a 100644 --- a/hardhat-scripts/deploy/3.configureChains.ts +++ b/hardhat-scripts/deploy/3.configureChains.ts @@ -6,7 +6,7 @@ import { ChainAddressesObj, ChainSlug, Contracts } from "../../src"; import { chains, EVMX_CHAIN_ID, - getFeesPlugChains, + getGasStationChains, MAX_MSG_VALUE_LIMIT, mode, } from "../config"; @@ -94,8 +94,8 @@ export const configureChains = async (addresses: DeploymentAddresses) => { // messageSwitchboardId.toString() // ] = chainAddresses[Contracts.MessageSwitchboard]; - if (chainAddresses[Contracts.FeesPlug]) { - await whitelistToken(chain, chainAddresses[Contracts.FeesPlug], signer); + if (chainAddresses[Contracts.GasStation]) { + await whitelistToken(chain, chainAddresses[Contracts.GasStation], signer); } await setMaxMsgValueLimit(chain); @@ -180,19 +180,19 @@ async function setOnchainContracts( signer ); - if (chainAddresses[Contracts.FeesPlug]) { - const feesPlug = toBytes32FormatHexString( - chainAddresses[Contracts.FeesPlug]! + if (chainAddresses[Contracts.GasStation]) { + const gasStation = toBytes32FormatHexString( + chainAddresses[Contracts.GasStation]! ); await updateContractSettings( EVMX_CHAIN_ID, - Contracts.FeesManager, - "feesPlugs", + Contracts.GasAccountManager, + "gasStations", [chain], - toBytes32FormatHexString(feesPlug).toString(), - "setFeesPlug", - [chain, toBytes32FormatHexString(feesPlug)], + toBytes32FormatHexString(gasStation).toString(), + "setGasStation", + [chain, toBytes32FormatHexString(gasStation)], signer ); } @@ -376,37 +376,37 @@ const registerSb = async ( export const whitelistToken = async ( chain: number, - feesPlugAddress: string, + gasStationAddress: string, signer: Signer ) => { console.log("Whitelisting token"); - if (!getFeesPlugChains().includes(chain as ChainSlug)) { + if (!getGasStationChains().includes(chain as ChainSlug)) { console.log( "Skipping whitelisting token for fees plug, not part of fees plug chains" ); return; } - console.log("feesPlugAddress: ", feesPlugAddress); - const feesPlugContract = ( - await getInstance(Contracts.FeesPlug, feesPlugAddress) + console.log("gasStationAddress: ", gasStationAddress); + const gasStationContract = ( + await getInstance(Contracts.GasStation, gasStationAddress) ).connect(signer); - // console.log("feesPlugContract: ", feesPlugContract); + // console.log("gasStationContract: ", gasStationContract); const tokens = getFeeTokens(chain); console.log("tokens: ", tokens); if (tokens.length == 0) return; for (const token of tokens) { console.log("token: ", token); - const isWhitelisted = await feesPlugContract.whitelistedTokens(token, { + const isWhitelisted = await gasStationContract.whitelistedTokens(token, { ...(await getReadOverrides(chain as ChainSlug)), }); if (!isWhitelisted) { - const tx = await feesPlugContract.whitelistToken(token, { + const tx = await gasStationContract.whitelistToken(token, { ...(await overrides(chain)), }); console.log( - `Whitelisting token ${token} for ${feesPlugContract.address}, txHash: ${tx.hash}` + `Whitelisting token ${token} for ${gasStationContract.address}, txHash: ${tx.hash}` ); await tx.wait(); } else { diff --git a/hardhat-scripts/deploy/4.configureEVMx.ts b/hardhat-scripts/deploy/4.configureEVMx.ts index f9d77578..53b9fc60 100644 --- a/hardhat-scripts/deploy/4.configureEVMx.ts +++ b/hardhat-scripts/deploy/4.configureEVMx.ts @@ -47,11 +47,11 @@ export const configureEVMx = async (evmxAddresses: EVMxAddressesObj) => { await updateContractSettings( EVMX_CHAIN_ID, Contracts.AddressResolver, - "feesManager__", + "gasAccountManager__", [], - evmxAddresses[Contracts.FeesManager], - "setFeesManager", - [evmxAddresses[Contracts.FeesManager]], + evmxAddresses[Contracts.GasAccountManager], + "setGasAccountManager", + [evmxAddresses[Contracts.GasAccountManager]], signer ); @@ -114,15 +114,15 @@ export const configureEVMx = async (evmxAddresses: EVMxAddressesObj) => { }; const checkAndSetMaxFees = async (evmxAddresses: EVMxAddressesObj) => { - const feesManagerContract = ( + const gasAccountManagerContract = ( await getInstance( - Contracts.FeesManager, - evmxAddresses[Contracts.FeesManager] + Contracts.GasAccountManager, + evmxAddresses[Contracts.GasAccountManager] ) ).connect(getWatcherSigner()); const allChains = [...chains, EVMX_CHAIN_ID]; - const currentMaxFeesArray = await feesManagerContract.getChainMaxFees( + const currentMaxFeesArray = await gasAccountManagerContract.getChainMaxFees( allChains ); const maxFeesUpdateArray: { chainSlug: number; maxFees: string }[] = []; @@ -144,10 +144,9 @@ const checkAndSetMaxFees = async (evmxAddresses: EVMxAddressesObj) => { if (maxFeesUpdateArray.length > 0) { const chains = maxFeesUpdateArray.map((item) => item.chainSlug); const maxFees = maxFeesUpdateArray.map((item) => item.maxFees); - let tx = await feesManagerContract.setChainMaxFees(chains, maxFees); + let tx = await gasAccountManagerContract.setChainMaxFees(chains, maxFees); console.log( - `Setting Chain Max Fees for chains: ${chains.join(", ")} tx hash: ${ - tx.hash + `Setting Chain Max Fees for chains: ${chains.join(", ")} tx hash: ${tx.hash }` ); await tx.wait(); @@ -167,11 +166,11 @@ export const setWatcherCoreContracts = async ( if ( requestHandlerSet.toLowerCase() !== - evmxAddresses[Contracts.RequestHandler].toLowerCase() || + evmxAddresses[Contracts.RequestHandler].toLowerCase() || PromiseResolverSet.toLowerCase() !== - evmxAddresses[Contracts.PromiseResolver].toLowerCase() || + evmxAddresses[Contracts.PromiseResolver].toLowerCase() || ConfigurationsSet.toLowerCase() !== - evmxAddresses[Contracts.Configurations].toLowerCase() + evmxAddresses[Contracts.Configurations].toLowerCase() ) { console.log("Setting watcher core contracts"); const tx = await watcherContract.setCoreContracts( diff --git a/hardhat-scripts/deploy/5.fundTransfers.ts b/hardhat-scripts/deploy/5.fundTransfers.ts index 584e2ef2..daf8565a 100644 --- a/hardhat-scripts/deploy/5.fundTransfers.ts +++ b/hardhat-scripts/deploy/5.fundTransfers.ts @@ -9,30 +9,30 @@ import { import { getAddresses, getWatcherSigner } from "../utils"; config(); -export const fundFeesPool = async (watcherSigner: Signer) => { +export const fundGasVault = async (watcherSigner: Signer) => { const addresses = getAddresses(mode); - const feesPoolAddress = addresses[EVMX_CHAIN_ID][Contracts.FeesPool]; - const feesPoolBalance = await watcherSigner.provider!.getBalance( - feesPoolAddress + const gasVaultAddress = addresses[EVMX_CHAIN_ID][Contracts.GasVault]; + const gasVaultBalance = await watcherSigner.provider!.getBalance( + gasVaultAddress ); console.log({ - feesPoolAddress, - feesPoolBalance, + gasVaultAddress, + gasVaultBalance, FEES_POOL_FUNDING_AMOUNT_THRESHOLD, }); - if (feesPoolBalance.gte(FEES_POOL_FUNDING_AMOUNT_THRESHOLD)) { + if (gasVaultBalance.gte(FEES_POOL_FUNDING_AMOUNT_THRESHOLD)) { console.log( - `Fees pool ${feesPoolAddress} already has sufficient balance, skipping funding` + `Gas vault ${gasVaultAddress} already has sufficient balance, skipping funding` ); return; } const tx = await watcherSigner.sendTransaction({ - to: feesPoolAddress, + to: gasVaultAddress, value: FEES_POOL_FUNDING_AMOUNT_THRESHOLD, }); console.log( - `Funding fees pool ${feesPoolAddress} with ${FEES_POOL_FUNDING_AMOUNT_THRESHOLD} ETH, txHash: `, + `Funding gas vault ${gasVaultAddress} with ${FEES_POOL_FUNDING_AMOUNT_THRESHOLD} ETH, txHash: `, tx.hash ); await tx.wait(); @@ -41,7 +41,7 @@ export const fundFeesPool = async (watcherSigner: Signer) => { const main = async () => { console.log("Fund transfers"); const watcherSigner = getWatcherSigner(); - await fundFeesPool(watcherSigner); + await fundGasVault(watcherSigner); }; main(); diff --git a/hardhat-scripts/deploy/6.connect.ts b/hardhat-scripts/deploy/6.connect.ts index 28e2e491..d4adc321 100644 --- a/hardhat-scripts/deploy/6.connect.ts +++ b/hardhat-scripts/deploy/6.connect.ts @@ -19,8 +19,8 @@ import { getWatcherSigner, signWatcherMessage } from "../utils/sign"; import { isConfigSetOnEVMx, isConfigSetOnSocket } from "../utils"; import pLimit from "p-limit"; -// const plugs = [Contracts.ContractFactoryPlug, Contracts.FeesPlug]; -const plugs = [Contracts.FeesPlug]; +// const plugs = [Contracts.ContractFactoryPlug, Contracts.GasStation]; +const plugs = [Contracts.GasStation]; // Main function to connect plugs on all chains export const main = async () => { diff --git a/hardhat-scripts/deploy/8.setupEnv.ts b/hardhat-scripts/deploy/8.setupEnv.ts index 0c1c1052..fd44cb06 100644 --- a/hardhat-scripts/deploy/8.setupEnv.ts +++ b/hardhat-scripts/deploy/8.setupEnv.ts @@ -32,17 +32,16 @@ const updatedLines = lines.map((line) => { } else if (line.startsWith("WATCHER=")) { return `WATCHER=${latestEVMxAddresses[Contracts.Watcher]}`; } else if (line.startsWith("FEES_MANAGER=")) { - return `FEES_MANAGER=${latestEVMxAddresses[Contracts.FeesManager]}`; + return `FEES_MANAGER=${latestEVMxAddresses[Contracts.GasAccountManager]}`; } else if (line.startsWith("ARBITRUM_SOCKET=")) { return `ARBITRUM_SOCKET=${arbSepoliaAddresses[Contracts.Socket]}`; } else if (line.startsWith("ARBITRUM_SWITCHBOARD=")) { - return `ARBITRUM_SWITCHBOARD=${ - arbSepoliaAddresses[Contracts.FastSwitchboard] - }`; + return `ARBITRUM_SWITCHBOARD=${arbSepoliaAddresses[Contracts.FastSwitchboard] + }`; } else if (line.startsWith("ARBITRUM_FEES_PLUG=")) { - const feesPlug = arbSepoliaAddresses[Contracts.FeesPlug]; - if (feesPlug) { - return `ARBITRUM_FEES_PLUG=${feesPlug}`; + const gasStation = arbSepoliaAddresses[Contracts.GasStation]; + if (gasStation) { + return `ARBITRUM_FEES_PLUG=${gasStation}`; } else { return line; } diff --git a/hardhat-scripts/deploy/9.setupTransmitter.ts b/hardhat-scripts/deploy/9.setupTransmitter.ts index 17d20201..f732ff63 100644 --- a/hardhat-scripts/deploy/9.setupTransmitter.ts +++ b/hardhat-scripts/deploy/9.setupTransmitter.ts @@ -13,7 +13,7 @@ import { getTransmitterSigner, getWatcherSigner } from "../utils/sign"; import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; let evmxAddresses: EVMxAddressesObj; -let feesManagerContract: Contract; +let gasAccountManagerContract: Contract; let transmitterSigner: SignerWithAddress | Wallet; let transmitterAddress: string; @@ -29,9 +29,9 @@ export const main = async () => { export const init = async () => { const addresses = getAddresses(mode); evmxAddresses = addresses[EVMX_CHAIN_ID] as EVMxAddressesObj; - feesManagerContract = await getInstance( - Contracts.FeesManager, - evmxAddresses[Contracts.FeesManager] + gasAccountManagerContract = await getInstance( + Contracts.GasAccountManager, + evmxAddresses[Contracts.GasAccountManager] ); transmitterSigner = getTransmitterSigner(EVMX_CHAIN_ID as ChainSlug); transmitterAddress = await transmitterSigner.getAddress(); @@ -39,13 +39,13 @@ export const init = async () => { export const checkAndDepositCredits = async (transmitter: string) => { console.log("Checking and depositing credits"); - const credits = await feesManagerContract + const credits = await gasAccountManagerContract .connect(transmitterSigner) .balanceOf(transmitter); if (credits.lt(TRANSMITTER_CREDIT_THRESHOLD)) { console.log("Depositing credits for transmitter..."); - const tx = await feesManagerContract + const tx = await gasAccountManagerContract .connect(getWatcherSigner()) .wrap(transmitter, { ...(await overrides(EVMX_CHAIN_ID as ChainSlug)), diff --git a/hardhat-scripts/test/chainTest.ts b/hardhat-scripts/test/chainTest.ts index a243c257..660ab886 100644 --- a/hardhat-scripts/test/chainTest.ts +++ b/hardhat-scripts/test/chainTest.ts @@ -57,7 +57,7 @@ class ChainTester { private provider: ethers.providers.JsonRpcProvider; private wallet: ethers.Wallet; private counterAppGateway: ethers.Contract; - private feesManager: ethers.Contract; + private gasAccountManager: ethers.Contract; private results: ChainTestResult[] = []; constructor() { @@ -73,8 +73,8 @@ class ChainTester { "function counter() view returns (bytes32)", ]; - // FeesManager ABI (minimal required functions) - const feesManagerABI = [ + // GasAccountManager ABI (minimal required functions) + const gasAccountManagerABI = [ "function totalBalanceOf(address) view returns (uint256)", "function getBlockedCredits(address) view returns (uint256)", "function balanceOf(address) view returns (uint256)", @@ -86,9 +86,9 @@ class ChainTester { this.wallet ); - this.feesManager = new ethers.Contract( + this.gasAccountManager = new ethers.Contract( process.env.FEES_MANAGER!, - feesManagerABI, + gasAccountManagerABI, this.provider ); } @@ -100,18 +100,18 @@ class ChainTester { try { const appGatewayAddress = process.env.COUNTER_APP_GATEWAY!; - const feesManagerAddress = process.env.FEES_MANAGER!; + const gasAccountManagerAddress = process.env.FEES_MANAGER!; - const totalCredits = await this.feesManager.totalBalanceOf( + const totalCredits = await this.gasAccountManager.totalBalanceOf( appGatewayAddress ); - const blockedCredits = await this.feesManager.getBlockedCredits( + const blockedCredits = await this.gasAccountManager.getBlockedCredits( appGatewayAddress ); - const availableFees = await this.feesManager.balanceOf(appGatewayAddress); + const availableFees = await this.gasAccountManager.balanceOf(appGatewayAddress); console.log(`Counter App Gateway: ${appGatewayAddress}`); - console.log(`Fees Manager: ${feesManagerAddress}`); + console.log(`Fees Manager: ${gasAccountManagerAddress}`); console.log( `Total Credits: ${ethers.utils.formatEther(totalCredits)} ETH` ); @@ -221,8 +221,7 @@ class ChainTester { } } catch (error) { console.log( - ` API error: ${ - error instanceof Error ? error.message : String(error) + ` API error: ${error instanceof Error ? error.message : String(error) }` ); retries++; diff --git a/hardhat-scripts/utils/gatewayId.ts b/hardhat-scripts/utils/gatewayId.ts index a24fc02d..716e1a0e 100644 --- a/hardhat-scripts/utils/gatewayId.ts +++ b/hardhat-scripts/utils/gatewayId.ts @@ -13,9 +13,9 @@ export const getAppGatewayId = ( address = addresses?.[EVMX_CHAIN_ID]?.[Contracts.WritePrecompile]; if (!address) throw new Error(`WritePrecompile not found on EVMX`); return ethers.utils.hexZeroPad(address, 32); - case Contracts.FeesPlug: - address = addresses?.[EVMX_CHAIN_ID]?.[Contracts.FeesManager]; - if (!address) throw new Error(`FeesManager not found on EVMX`); + case Contracts.GasStation: + address = addresses?.[EVMX_CHAIN_ID]?.[Contracts.GasAccountManager]; + if (!address) throw new Error(`GasAccountManager not found on EVMX`); return ethers.utils.hexZeroPad(address, 32); default: throw new Error(`Unknown plug: ${plug}`); diff --git a/script/counter/WithdrawFeesArbitrumFeesPlug.s.sol b/script/counter/WithdrawFeesArbitrumFeesPlug.s.sol index 4ac76192..cf84ed8c 100644 --- a/script/counter/WithdrawFeesArbitrumFeesPlug.s.sol +++ b/script/counter/WithdrawFeesArbitrumFeesPlug.s.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.21; import {Script} from "forge-std/Script.sol"; import {console} from "forge-std/console.sol"; -import {FeesManager} from "../../contracts/evmx/fees/FeesManager.sol"; +import {GasAccountManager} from "../../contracts/evmx/fees/GasAccountManager.sol"; import {CounterAppGateway} from "../../test/apps/counter/CounterAppGateway.sol"; // @notice This script is used to withdraw fees from EVMX to Arbitrum Sepolia @@ -12,12 +12,14 @@ 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"))); + GasAccountManager gasAccountManager = GasAccountManager( + payable(vm.envAddress("FEES_MANAGER")) + ); address appGatewayAddress = vm.envAddress("APP_GATEWAY"); address token = vm.envAddress("USDC"); CounterAppGateway appGateway = CounterAppGateway(appGatewayAddress); - uint256 availableFees = feesManager.balanceOf(appGatewayAddress); + uint256 availableFees = gasAccountManager.balanceOf(appGatewayAddress); console.log("Available fees:", availableFees); if (availableFees > 0) { diff --git a/script/helpers/CheckDepositedCredits.s.sol b/script/helpers/CheckDepositedCredits.s.sol index 80418fe7..a95d86a0 100644 --- a/script/helpers/CheckDepositedCredits.s.sol +++ b/script/helpers/CheckDepositedCredits.s.sol @@ -3,22 +3,24 @@ pragma solidity ^0.8.21; import {Script} from "forge-std/Script.sol"; import {console} from "forge-std/console.sol"; -import {FeesManager} from "../../contracts/evmx/fees/FeesManager.sol"; +import {GasAccountManager} from "../../contracts/evmx/fees/GasAccountManager.sol"; contract CheckDepositedCredits is Script { function run() external { vm.createSelectFork(vm.envString("EVMX_RPC")); - FeesManager feesManager = FeesManager(payable(vm.envAddress("FEES_MANAGER"))); + GasAccountManager gasAccountManager = GasAccountManager( + payable(vm.envAddress("FEES_MANAGER")) + ); address appGateway = vm.envAddress("APP_GATEWAY"); - uint256 totalCredits = feesManager.totalBalanceOf(appGateway); - uint256 blockedCredits = feesManager.getBlockedCredits(appGateway); + uint256 totalCredits = gasAccountManager.totalBalanceOf(appGateway); + uint256 blockedCredits = gasAccountManager.getBlockedCredits(appGateway); console.log("App Gateway:", appGateway); - console.log("Fees Manager:", address(feesManager)); + console.log("Fees Manager:", address(gasAccountManager)); console.log("totalCredits fees:", totalCredits); console.log("blockedCredits fees:", blockedCredits); - uint256 availableFees = feesManager.balanceOf(appGateway); + uint256 availableFees = gasAccountManager.balanceOf(appGateway); console.log("Available fees:", availableFees); } } diff --git a/script/helpers/DepositCredit.s.sol b/script/helpers/DepositCredit.s.sol index b8d432f8..96af418e 100644 --- a/script/helpers/DepositCredit.s.sol +++ b/script/helpers/DepositCredit.s.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.21; import {Script} from "forge-std/Script.sol"; import {console} from "forge-std/console.sol"; -import {FeesPlug} from "../../contracts/evmx/plugs/FeesPlug.sol"; +import {GasStation} from "../../contracts/evmx/plugs/GasStation.sol"; import {TestUSDC} from "../../contracts/evmx/mocks/TestUSDC.sol"; // source .env && forge script script/helpers/DepositCreditAndNative.s.sol --broadcast --skip-simulation @@ -14,22 +14,22 @@ contract DepositCredit is Script { uint256 privateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(privateKey); - FeesPlug feesPlug = FeesPlug(payable(vm.envAddress("ARBITRUM_FEES_PLUG"))); + GasStation gasStation = GasStation(payable(vm.envAddress("ARBITRUM_FEES_PLUG"))); address appGateway = vm.envAddress("APP_GATEWAY"); TestUSDC testUSDCContract = TestUSDC(vm.envAddress("ARBITRUM_TEST_USDC")); // mint test USDC to sender testUSDCContract.mint(vm.addr(privateKey), feesAmount); // approve fees plug to spend test USDC - testUSDCContract.approve(address(feesPlug), feesAmount); + testUSDCContract.approve(address(gasStation), feesAmount); address sender = vm.addr(privateKey); console.log("Sender address:", sender); uint256 balance = testUSDCContract.balanceOf(sender); console.log("Sender balance in wei:", balance); console.log("App Gateway:", appGateway); - console.log("Fees Plug:", address(feesPlug)); + console.log("Fees Plug:", address(gasStation)); console.log("Fees Amount:", feesAmount); - feesPlug.depositCredit(address(testUSDCContract), appGateway, feesAmount); + gasStation.depositCredit(address(testUSDCContract), appGateway, feesAmount); } } diff --git a/script/helpers/DepositCreditAndNative.s.sol b/script/helpers/DepositCreditAndNative.s.sol index 629a3998..e58c8f80 100644 --- a/script/helpers/DepositCreditAndNative.s.sol +++ b/script/helpers/DepositCreditAndNative.s.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.21; import {Script} from "forge-std/Script.sol"; import {console} from "forge-std/console.sol"; -import {FeesPlug} from "../../contracts/evmx/plugs/FeesPlug.sol"; +import {GasStation} from "../../contracts/evmx/plugs/GasStation.sol"; import {TestUSDC} from "../../contracts/evmx/mocks/TestUSDC.sol"; // source .env && forge script script/helpers/DepositCreditAndNative.s.sol --broadcast --skip-simulation @@ -14,22 +14,22 @@ contract DepositCreditAndNative is Script { uint256 privateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(privateKey); - FeesPlug feesPlug = FeesPlug(payable(vm.envAddress("ARBITRUM_FEES_PLUG"))); + GasStation gasStation = GasStation(payable(vm.envAddress("ARBITRUM_FEES_PLUG"))); address appGateway = vm.envAddress("APP_GATEWAY"); TestUSDC testUSDCContract = TestUSDC(vm.envAddress("ARBITRUM_TEST_USDC")); // mint test USDC to sender testUSDCContract.mint(vm.addr(privateKey), feesAmount); // approve fees plug to spend test USDC - testUSDCContract.approve(address(feesPlug), feesAmount); + testUSDCContract.approve(address(gasStation), feesAmount); address sender = vm.addr(privateKey); console.log("Sender address:", sender); uint256 balance = testUSDCContract.balanceOf(sender); console.log("Sender balance in wei:", balance); console.log("App Gateway:", appGateway); - console.log("Fees Plug:", address(feesPlug)); + console.log("Fees Plug:", address(gasStation)); console.log("Fees Amount:", feesAmount); - feesPlug.depositCreditAndNative(address(testUSDCContract), appGateway, feesAmount); + gasStation.depositCreditAndNative(address(testUSDCContract), appGateway, feesAmount); } } diff --git a/script/helpers/DepositCreditMainnet.s.sol b/script/helpers/DepositCreditMainnet.s.sol index 4e1a8e33..5e9fcd5f 100644 --- a/script/helpers/DepositCreditMainnet.s.sol +++ b/script/helpers/DepositCreditMainnet.s.sol @@ -3,9 +3,10 @@ pragma solidity ^0.8.21; import {Script} from "forge-std/Script.sol"; import {console} from "forge-std/console.sol"; -import {FeesPlug} from "../../contracts/evmx/plugs/FeesPlug.sol"; +import {GasStation} from "../../contracts/evmx/plugs/GasStation.sol"; import {TestUSDC} from "../../contracts/evmx/mocks/TestUSDC.sol"; import "solady/tokens/ERC20.sol"; + // source .env && forge script script/helpers/DepositCreditAndNative.s.sol --broadcast --skip-simulation contract DepositCredit is Script { function run() external { @@ -14,12 +15,12 @@ contract DepositCredit is Script { uint256 privateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(privateKey); - FeesPlug feesPlug = FeesPlug(payable(vm.envAddress("ARBITRUM_FEES_PLUG"))); + GasStation gasStation = GasStation(payable(vm.envAddress("ARBITRUM_FEES_PLUG"))); address appGateway = vm.envAddress("APP_GATEWAY"); ERC20 USDCContract = ERC20(vm.envAddress("ARBITRUM_USDC")); // approve fees plug to spend test USDC - USDCContract.approve(address(feesPlug), feesAmount); + USDCContract.approve(address(gasStation), feesAmount); address sender = vm.addr(privateKey); console.log("Sender address:", sender); @@ -29,8 +30,8 @@ contract DepositCredit is Script { revert("Sender does not have enough USDC"); } console.log("App Gateway:", appGateway); - console.log("Fees Plug:", address(feesPlug)); + console.log("Fees Plug:", address(gasStation)); console.log("Fees Amount:", feesAmount); - feesPlug.depositCredit(address(USDCContract), appGateway, feesAmount); + gasStation.depositCredit(address(USDCContract), appGateway, feesAmount); } } diff --git a/script/helpers/TransferRemainingCredits.s.sol b/script/helpers/TransferRemainingCredits.s.sol index 6ebec144..ba47108b 100644 --- a/script/helpers/TransferRemainingCredits.s.sol +++ b/script/helpers/TransferRemainingCredits.s.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.21; import {Script} from "forge-std/Script.sol"; import {console} from "forge-std/console.sol"; -import {FeesManager} from "../../contracts/evmx/fees/FeesManager.sol"; +import {GasAccountManager} from "../../contracts/evmx/fees/GasAccountManager.sol"; import {IAppGateway} from "../../contracts/evmx/interfaces/IAppGateway.sol"; contract TransferRemainingCredits is Script { @@ -13,19 +13,21 @@ contract TransferRemainingCredits is Script { uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); - FeesManager feesManager = FeesManager(payable(vm.envAddress("FEES_MANAGER"))); + GasAccountManager gasAccountManager = GasAccountManager( + payable(vm.envAddress("FEES_MANAGER")) + ); address appGateway = vm.envAddress("APP_GATEWAY"); address newAppGateway = vm.envAddress("NEW_APP_GATEWAY"); - uint256 totalCredits = feesManager.totalBalanceOf(appGateway); - uint256 blockedCredits = feesManager.getBlockedCredits(appGateway); + uint256 totalCredits = gasAccountManager.totalBalanceOf(appGateway); + uint256 blockedCredits = gasAccountManager.getBlockedCredits(appGateway); console.log("App Gateway:", appGateway); console.log("New App Gateway:", newAppGateway); - console.log("Fees Manager:", address(feesManager)); + console.log("Fees Manager:", address(gasAccountManager)); console.log("totalCredits fees:", totalCredits); console.log("blockedCredits fees:", blockedCredits); - uint256 availableFees = feesManager.balanceOf(appGateway); + uint256 availableFees = gasAccountManager.balanceOf(appGateway); console.log("Available fees:", availableFees); bytes memory data = abi.encodeWithSignature( "transferFrom(address,address,uint256)", diff --git a/script/helpers/WithdrawRemainingCredits.s.sol b/script/helpers/WithdrawRemainingCredits.s.sol index c53cf1ee..a13c991e 100644 --- a/script/helpers/WithdrawRemainingCredits.s.sol +++ b/script/helpers/WithdrawRemainingCredits.s.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.21; import {Script} from "forge-std/Script.sol"; import {console} from "forge-std/console.sol"; -import {FeesManager} from "../../contracts/evmx/fees/FeesManager.sol"; +import {GasAccountManager} from "../../contracts/evmx/fees/GasAccountManager.sol"; contract WithdrawRemainingCredits is Script { function run() external { @@ -12,19 +12,21 @@ contract WithdrawRemainingCredits is Script { uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); - FeesManager feesManager = FeesManager(payable(vm.envAddress("FEES_MANAGER"))); + GasAccountManager gasAccountManager = GasAccountManager( + payable(vm.envAddress("FEES_MANAGER")) + ); address appGateway = vm.envAddress("APP_GATEWAY"); - uint256 totalCredits = feesManager.totalBalanceOf(appGateway); - uint256 blockedCredits = feesManager.getBlockedCredits(appGateway); + uint256 totalCredits = gasAccountManager.totalBalanceOf(appGateway); + uint256 blockedCredits = gasAccountManager.getBlockedCredits(appGateway); console.log("App Gateway:", appGateway); - console.log("Fees Manager:", address(feesManager)); + console.log("Fees Manager:", address(gasAccountManager)); console.log("totalCredits fees:", totalCredits); console.log("blockedCredits fees:", blockedCredits); - uint256 availableFees = feesManager.balanceOf(appGateway); + uint256 availableFees = gasAccountManager.balanceOf(appGateway); console.log("Available fees:", availableFees); - feesManager.transferFrom(appGateway, vm.addr(deployerPrivateKey), availableFees); + gasAccountManager.transferFrom(appGateway, vm.addr(deployerPrivateKey), availableFees); vm.stopBroadcast(); } diff --git a/src/enums.ts b/src/enums.ts index 0667d40d..f148ad3a 100644 --- a/src/enums.ts +++ b/src/enums.ts @@ -12,7 +12,7 @@ export enum Events { PlugConnected = "PlugConnected", AppGatewayCallRequested = "AppGatewayCallRequested", - // FeesPlug + // GasStation FeesDeposited = "FeesDeposited", // Watcher @@ -52,7 +52,7 @@ export enum Events { export enum Contracts { Socket = "Socket", - FeesPlug = "FeesPlug", + GasStation = "GasStation", ContractFactoryPlug = "ContractFactoryPlug", FastSwitchboard = "FastSwitchboard", FastSwitchboardId = "FastSwitchboardId", @@ -67,11 +67,11 @@ export enum Contracts { RequestHandler = "RequestHandler", Configurations = "Configurations", PromiseResolver = "PromiseResolver", - FeesManager = "FeesManager", + GasAccountManager = "GasAccountManager", WritePrecompile = "WritePrecompile", ReadPrecompile = "ReadPrecompile", SchedulePrecompile = "SchedulePrecompile", - FeesPool = "FeesPool", + GasVault = "GasVault", AsyncDeployer = "AsyncDeployer", DeployForwarder = "DeployForwarder", Forwarder = "Forwarder", diff --git a/src/events.ts b/src/events.ts index 8381849f..f1d5d2a6 100644 --- a/src/events.ts +++ b/src/events.ts @@ -7,7 +7,7 @@ export const socketEvents = [ Events.AppGatewayCallRequested, ]; -export const feesPlugEvents = [Events.FeesDeposited]; +export const gasStationEvents = [Events.FeesDeposited]; export const watcherEvents = [ Events.TriggerFailed, diff --git a/src/types.ts b/src/types.ts index 97494969..99d31023 100644 --- a/src/types.ts +++ b/src/types.ts @@ -25,17 +25,17 @@ export type ChainAddressesObj = { CCTPSwitchboardId?: string; MessageSwitchboardId?: string; ContractFactoryPlug: string; - SocketFeesManager?: string; - FeesPlug?: string; + SocketGasAccountManager?: string; + GasStation?: string; startBlock: number; SwitchboardIdToAddressMap: { [switchboardId: string]: string }; }; export type EVMxAddressesObj = { ERC1967Factory: string; - FeesPool: string; + GasVault: string; AddressResolver: string; - FeesManager: string; + GasAccountManager: string; AsyncDeployer: string; Watcher: string; WritePrecompile: string; diff --git a/test/SetupTest.t.sol b/test/SetupTest.t.sol index 48f5f7fe..507b56f1 100644 --- a/test/SetupTest.t.sol +++ b/test/SetupTest.t.sol @@ -27,9 +27,9 @@ import "../contracts/evmx/watcher/precompiles/SchedulePrecompile.sol"; import "../contracts/evmx/helpers/ForwarderSolana.sol"; import "../contracts/evmx/helpers/AddressResolver.sol"; import "../contracts/evmx/helpers/AsyncDeployer.sol"; -import "../contracts/evmx/fees/FeesManager.sol"; -import "../contracts/evmx/fees/FeesPool.sol"; -import "../contracts/evmx/plugs/FeesPlug.sol"; +import "../contracts/evmx/fees/GasAccountManager.sol"; +import "../contracts/evmx/fees/GasVault.sol"; +import "../contracts/evmx/plugs/GasStation.sol"; import "../contracts/evmx/mocks/TestUSDC.sol"; import "solady/utils/ERC1967Factory.sol"; @@ -78,21 +78,21 @@ contract SetupStore is Test, Utils { FastSwitchboard switchboard; MessageSwitchboard messageSwitchboard; SocketBatcher socketBatcher; - FeesPlug feesPlug; + GasStation gasStation; TestUSDC testUSDC; } SocketContracts public arbConfig; SocketContracts public optConfig; - FeesManager feesManagerImpl; + GasAccountManager gasAccountManagerImpl; AddressResolver addressResolverImpl; AsyncDeployer asyncDeployerImpl; Watcher watcherImpl; WritePrecompile writePrecompileImpl; ERC1967Factory public proxyFactory; - FeesManager feesManager; - FeesPool feesPool; + GasAccountManager gasAccountManager; + GasVault gasVault; AddressResolver public addressResolver; AsyncDeployer public asyncDeployer; @@ -108,7 +108,7 @@ contract DeploySetup is SetupStore { //////////////////////////////////// Setup //////////////////////////////////// function _deploy() internal { _deployEVMxCore(); - vm.deal(address(feesPool), 100000 ether); + vm.deal(address(gasVault), 100000 ether); // chain core contracts arbConfig = _deploySocket(arbChainSlug); @@ -118,12 +118,12 @@ contract DeploySetup is SetupStore { _configureChain(optChainSlug); vm.startPrank(watcherEOA); - feesPool.grantRole(FEE_MANAGER_ROLE, address(feesManager)); + gasVault.grantRole(FEE_MANAGER_ROLE, address(gasAccountManager)); // setup address resolver addressResolver.setWatcher(address(watcher)); addressResolver.setAsyncDeployer(address(asyncDeployer)); - addressResolver.setFeesManager(address(feesManager)); + addressResolver.setGasAccountManager(address(gasAccountManager)); watcher.setPrecompile(WRITE, writePrecompile); watcher.setPrecompile(READ, readPrecompile); @@ -150,13 +150,13 @@ contract DeploySetup is SetupStore { function _connectCorePlugs() internal { setupGatewayAndPlugs( arbChainSlug, - address(feesManager), - toBytes32Format(address(arbConfig.feesPlug)) + address(gasAccountManager), + toBytes32Format(address(arbConfig.gasStation)) ); setupGatewayAndPlugs( optChainSlug, - address(feesManager), - toBytes32Format(address(optConfig.feesPlug)) + address(gasAccountManager), + toBytes32Format(address(optConfig.gasStation)) ); } @@ -200,7 +200,7 @@ contract DeploySetup is SetupStore { switchboard: new FastSwitchboard(chainSlug_, socket, socketOwner), messageSwitchboard: new MessageSwitchboard(chainSlug_, socket, socketOwner), socketBatcher: new SocketBatcher(socketOwner, socket), - feesPlug: new FeesPlug(address(socket), socketOwner), + gasStation: new GasStation(address(socket), socketOwner), testUSDC: new TestUSDC("USDC", "USDC", 6, socketOwner, 1000000000000000000000000) }); } @@ -210,7 +210,7 @@ contract DeploySetup is SetupStore { Socket socket = socketConfig.socket; FastSwitchboard switchboard = socketConfig.switchboard; MessageSwitchboard messageSwitchboard = socketConfig.messageSwitchboard; - FeesPlug feesPlug = socketConfig.feesPlug; + GasStation gasStation = socketConfig.gasStation; vm.startPrank(socketOwner); // socket @@ -227,10 +227,10 @@ contract DeploySetup is SetupStore { messageSwitchboard.registerSwitchboard(); messageSwitchboard.grantRole(WATCHER_ROLE, watcherEOA); - feesPlug.grantRole(RESCUE_ROLE, address(socketOwner)); - feesPlug.whitelistToken(address(socketConfig.testUSDC)); - feesPlug.connectSocket( - toBytes32Format(address(feesManager)), + gasStation.grantRole(RESCUE_ROLE, address(socketOwner)); + gasStation.whitelistToken(address(socketConfig.testUSDC)); + gasStation.connectSocket( + toBytes32Format(address(gasAccountManager)), address(socket), switchboard.switchboardId() ); @@ -241,7 +241,7 @@ contract DeploySetup is SetupStore { watcher.setSwitchboard(chainSlug_, FAST, switchboard.switchboardId()); // plugs - feesManager.setFeesPlug(chainSlug_, toBytes32Format(address(feesPlug))); + gasAccountManager.setGasStation(chainSlug_, toBytes32Format(address(gasStation))); // precompiles writePrecompile.updateChainMaxMsgValueLimits(chainSlug_, maxMsgValueLimit); @@ -250,12 +250,12 @@ contract DeploySetup is SetupStore { function _deployEVMxCore() internal { proxyFactory = new ERC1967Factory(); - feesPool = new FeesPool(watcherEOA); + gasVault = new GasVault(watcherEOA); ForwarderSolana forwarderSolana = new ForwarderSolana(); // Deploy implementations for upgradeable contracts - feesManagerImpl = new FeesManager(); + gasAccountManagerImpl = new GasAccountManager(); addressResolverImpl = new AddressResolver(); asyncDeployerImpl = new AsyncDeployer(); watcherImpl = new Watcher(); @@ -269,21 +269,21 @@ contract DeploySetup is SetupStore { ); addressResolver = AddressResolver(addressResolverProxy); - address feesManagerProxy = _deployAndVerifyProxy( - address(feesManagerImpl), + address gasAccountManagerProxy = _deployAndVerifyProxy( + address(gasAccountManagerImpl), watcherEOA, abi.encodeWithSelector( - FeesManager.initialize.selector, + GasAccountManager.initialize.selector, evmxSlug, address(addressResolver), - address(feesPool), + address(gasVault), watcherEOA, writeFees, FAST, address(forwarderSolana) ) ); - feesManager = FeesManager(feesManagerProxy); + gasAccountManager = GasAccountManager(gasAccountManagerProxy); address asyncDeployerProxy = _deployAndVerifyProxy( address(asyncDeployerImpl), @@ -409,7 +409,7 @@ contract FeesSetup is DeploySetup { TestUSDC token = socketConfig.testUSDC; uint256 userBalance = token.balanceOf(user_); - uint256 feesPlugBalance = token.balanceOf(address(socketConfig.feesPlug)); + uint256 gasStationBalance = token.balanceOf(address(socketConfig.gasStation)); token.mint(address(user_), 100 ether); assertEq( @@ -419,26 +419,26 @@ contract FeesSetup is DeploySetup { ); vm.startPrank(user_); - token.approve(address(socketConfig.feesPlug), 100 ether); - socketConfig.feesPlug.depositCreditAndNative(address(token), user_, 100 ether); + token.approve(address(socketConfig.gasStation), 100 ether); + socketConfig.gasStation.depositCreditAndNative(address(token), user_, 100 ether); vm.stopPrank(); assertEq( - token.balanceOf(address(socketConfig.feesPlug)), - feesPlugBalance + 100 ether, + token.balanceOf(address(socketConfig.gasStation)), + gasStationBalance + 100 ether, "Fees plug should have 100 more test tokens" ); - uint256 currentCredits = feesManager.balanceOf(user_); + uint256 currentCredits = gasAccountManager.balanceOf(user_); uint256 currentNative = address(user_).balance; vm.expectEmit(true, true, true, false); emit Deposited(chainSlug_, address(token), user_, credits_, native_); hoax(watcherEOA); - feesManager.deposit(abi.encode(chainSlug_, address(token), user_, credits_, native_ )); + gasAccountManager.deposit(abi.encode(chainSlug_, address(token), user_, credits_, native_)); assertEq( - feesManager.balanceOf(user_), + gasAccountManager.balanceOf(user_), currentCredits + credits_, "User should have more credits" ); @@ -446,21 +446,21 @@ contract FeesSetup is DeploySetup { } function approve(address appGateway_, address user_) internal { - uint256 approval = feesManager.allowance(user_, appGateway_); + uint256 approval = gasAccountManager.allowance(user_, appGateway_); if (approval > 0) return; hoax(user_); - feesManager.approve(appGateway_, type(uint256).max); + gasAccountManager.approve(appGateway_, type(uint256).max); assertEq( - feesManager.isApproved(user_, appGateway_), + gasAccountManager.isApproved(user_, appGateway_), true, "App gateway should be approved" ); } function permit(address appGateway_, address user_, uint256 userPrivateKey_) internal { - bool approval = feesManager.isApproved(user_, appGateway_); + bool approval = gasAccountManager.isApproved(user_, appGateway_); if (approval) return; uint256 value = type(uint256).max; @@ -475,18 +475,18 @@ contract FeesSetup is DeploySetup { user_, appGateway_, value, - feesManager.nonces(user_), + gasAccountManager.nonces(user_), deadline ) ); bytes32 digest = keccak256( - abi.encodePacked("\x19\x01", feesManager.DOMAIN_SEPARATOR(), structHash) + abi.encodePacked("\x19\x01", gasAccountManager.DOMAIN_SEPARATOR(), structHash) ); (uint8 v, bytes32 r, bytes32 s) = vm.sign(userPrivateKey_, digest); - feesManager.permit(user_, appGateway_, value, deadline, v, r, s); + gasAccountManager.permit(user_, appGateway_, value, deadline, v, r, s); assertEq( - feesManager.isApproved(user_, appGateway_), + gasAccountManager.isApproved(user_, appGateway_), true, "App gateway should be approved" ); @@ -845,15 +845,15 @@ contract MessageSwitchboardSetup is DeploySetup { bytes memory payload_ ) internal view returns (bytes32 payloadId, DigestParams memory digestParams) { uint64 payloadCounter = srcSocketConfig_.messageSwitchboard.payloadCounter(); - + // Calculate payload ID using new structure // Message payload: origin = (srcChainSlug, srcSwitchboardId), verification = (dstChainSlug, dstSwitchboardId) payloadId = createPayloadId( - srcSocketConfig_.chainSlug, // origin chain slug - srcSocketConfig_.messageSwitchboard.switchboardId(), // origin switchboard id - dstSocketConfig_.chainSlug, // verification chain slug - dstSocketConfig_.messageSwitchboard.switchboardId(), // verification switchboard id - payloadCounter // pointer (counter) + srcSocketConfig_.chainSlug, // origin chain slug + srcSocketConfig_.messageSwitchboard.switchboardId(), // origin switchboard id + dstSocketConfig_.chainSlug, // verification chain slug + dstSocketConfig_.messageSwitchboard.switchboardId(), // verification switchboard id + payloadCounter // pointer (counter) ); digestParams = _createDigestParams( @@ -866,10 +866,7 @@ contract MessageSwitchboardSetup is DeploySetup { ); } - function _executeOnDestination( - DigestParams memory digestParams_, - bytes32 payloadId_ - ) internal { + function _executeOnDestination(DigestParams memory digestParams_, bytes32 payloadId_) internal { _attestPayload(digestParams_); _execute(digestParams_, payloadId_); } diff --git a/test/apps/counter/CounterAppGateway.sol b/test/apps/counter/CounterAppGateway.sol index d79cf9c9..fc335dc6 100644 --- a/test/apps/counter/CounterAppGateway.sol +++ b/test/apps/counter/CounterAppGateway.sol @@ -16,7 +16,7 @@ contract CounterAppGateway is AppGatewayBase, Ownable { uint256 public optCounter; bool public incremented; - bool public feesManagerSwitch; + bool public gasAccountManagerSwitch; event CounterScheduleResolved(uint256 creationTimestamp, uint256 executionTimestamp); From f96282e562ab826f55319d5cc55135c7a53ee46e Mon Sep 17 00:00:00 2001 From: Ameesha Agrawal Date: Wed, 12 Nov 2025 17:09:33 +0530 Subject: [PATCH 02/12] fix: vars and fn renames --- contracts/evmx/base/AppGatewayBase.sol | 4 +- contracts/evmx/fees/GasAccountManager.sol | 48 +++++++++---------- contracts/evmx/fees/GasAccountToken.sol | 42 ++++++++-------- contracts/evmx/fees/MessageResolver.sol | 6 +-- .../evmx/interfaces/IGasAccountManager.sol | 14 ++---- contracts/evmx/interfaces/IGasEscrow.sol | 0 contracts/evmx/interfaces/IGasStation.sol | 8 ++-- contracts/evmx/plugs/GasStation.sol | 22 +++++---- contracts/evmx/watcher/Watcher.sol | 24 ++++------ contracts/protocol/Socket.sol | 20 ++++---- contracts/protocol/SocketConfig.sol | 27 +++++++---- contracts/protocol/SocketFeeManager.sol | 22 ++++----- ...eeManager.sol => INetworkFeeCollector.sol} | 10 ++-- contracts/utils/common/Structs.sol | 2 +- hardhat-scripts/deploy/9.setupTransmitter.ts | 4 +- hardhat-scripts/test/chainTest.ts | 10 ++-- .../WithdrawFeesArbitrumFeesPlug.s.sol | 2 +- script/helpers/CheckDepositedCredits.s.sol | 6 +-- script/helpers/DepositCredit.s.sol | 6 +-- script/helpers/DepositCreditAndNative.s.sol | 6 +-- script/helpers/DepositCreditMainnet.s.sol | 6 +-- script/helpers/TransferRemainingCredits.s.sol | 6 +-- script/helpers/WithdrawRemainingCredits.s.sol | 6 +-- src/enums.ts | 4 +- src/events.ts | 2 +- test/SetupTest.t.sol | 12 ++--- test/apps/counter/CounterAppGateway.sol | 6 +-- 27 files changed, 166 insertions(+), 159 deletions(-) create mode 100644 contracts/evmx/interfaces/IGasEscrow.sol rename contracts/protocol/interfaces/{ISocketFeeManager.sol => INetworkFeeCollector.sol} (80%) diff --git a/contracts/evmx/base/AppGatewayBase.sol b/contracts/evmx/base/AppGatewayBase.sol index 0a39054b..1ff8d021 100644 --- a/contracts/evmx/base/AppGatewayBase.sol +++ b/contracts/evmx/base/AppGatewayBase.sol @@ -167,7 +167,7 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway { /// @param token_ The token address /// @param amount_ The amount /// @param receiver_ The receiver address - function _withdrawCredits( + function _withdrawToChain( uint32 chainSlug_, address token_, uint256 amount_, @@ -177,7 +177,7 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway { address(gasAccountManager__()), type(uint256).max ); - gasAccountManager__().withdrawCredits( + gasAccountManager__().withdrawToChain( chainSlug_, token_, amount_, diff --git a/contracts/evmx/fees/GasAccountManager.sol b/contracts/evmx/fees/GasAccountManager.sol index ec7f9b10..a403024b 100644 --- a/contracts/evmx/fees/GasAccountManager.sol +++ b/contracts/evmx/fees/GasAccountManager.sol @@ -13,14 +13,14 @@ contract GasAccountManager is GasAccountToken { /// @param payloadId The payload id /// @param consumeFrom The consume from address /// @param amount The blocked amount - event CreditsBlocked(bytes32 indexed payloadId, address indexed consumeFrom, uint256 amount); + event GasEscrowed(bytes32 indexed payloadId, address indexed consumeFrom, uint256 amount); /// @notice Emitted when fees are unblocked and assigned to a transmitter /// @param payloadId The payload id /// @param consumeFrom The consume from address /// @param transmitter The transmitter address /// @param amount The unblocked amount - event CreditsUnblockedAndAssigned( + event EscrowSettled( bytes32 indexed payloadId, address indexed consumeFrom, address indexed transmitter, @@ -30,12 +30,12 @@ contract GasAccountManager is GasAccountToken { /// @notice Emitted when max fees per chain slug is set /// @param chainSlug The chain slug /// @param fees The max fees - event MaxFeesPerChainSlugSet(uint32 indexed chainSlug, uint256 fees); + event MaxGasPerChainSlugSet(uint32 indexed chainSlug, uint256 fees); /// @notice Emitted when fees are unblocked /// @param payloadId The payload id /// @param consumeFrom The consume from address - event CreditsUnblocked(bytes32 indexed payloadId, address indexed consumeFrom); + event EscrowReleased(bytes32 indexed payloadId, address indexed consumeFrom); constructor() { _disableInitializers(); // disable for implementation @@ -56,7 +56,7 @@ contract GasAccountManager is GasAccountToken { ) public reinitializer(2) { evmxSlug = evmxSlug_; gasVault = IGasVault(gasVault_); - maxFeesPerChainSlug[evmxSlug_] = fees_; + maxGasPerChainSlug[evmxSlug_] = fees_; overrideParams = overrideParams.setSwitchboardType(sbType_).setMaxFees(fees_); _initializeOwner(owner_); @@ -79,8 +79,8 @@ contract GasAccountManager is GasAccountToken { if (chainSlugs_.length != maxFees_.length) revert("Array length mismatch"); for (uint256 i = 0; i < chainSlugs_.length; i++) { - maxFeesPerChainSlug[chainSlugs_[i]] = maxFees_[i]; - emit MaxFeesPerChainSlugSet(chainSlugs_[i], maxFees_[i]); + maxGasPerChainSlug[chainSlugs_[i]] = maxFees_[i]; + emit MaxGasPerChainSlugSet(chainSlugs_[i], maxFees_[i]); } } @@ -89,7 +89,7 @@ contract GasAccountManager is GasAccountToken { ) external view returns (uint256[] memory) { uint256[] memory maxFeesArray = new uint256[](chainSlugs_.length); for (uint256 i = 0; i < chainSlugs_.length; i++) { - maxFeesArray[i] = maxFeesPerChainSlug[chainSlugs_[i]]; + maxFeesArray[i] = maxGasPerChainSlug[chainSlugs_[i]]; } return maxFeesArray; } @@ -105,55 +105,55 @@ contract GasAccountManager is GasAccountToken { /// @param consumeFrom_ The fees payer address /// @param credits_ The total fees to block /// @dev Only callable by delivery helper - function blockCredits( + function escrowGas( bytes32 payloadId_, address consumeFrom_, uint256 credits_ ) external override onlyWatcher { if (balanceOf(consumeFrom_) < credits_) revert InsufficientCreditsAvailable(); - userBlockedCredits[consumeFrom_] += credits_; - blockedCredits[payloadId_] = credits_; - emit CreditsBlocked(payloadId_, consumeFrom_, credits_); + accountEscrow[consumeFrom_] += credits_; + payloadEscrow[payloadId_] = credits_; + emit GasEscrowed(payloadId_, consumeFrom_, credits_); } /// @notice Unblocks fees after successful execution and assigns them to the transmitter /// @param payloadId_ The payload id /// @param assignTo_ The address of the transmitter - function unblockAndAssignCredits( + function settleGasPayment( bytes32 payloadId_, address assignTo_, uint256 amount_ ) external override onlyWatcher { - uint256 blockedCredits_ = blockedCredits[payloadId_]; - if (blockedCredits_ == 0) return; + uint256 payloadEscrow_ = payloadEscrow[payloadId_]; + if (payloadEscrow_ == 0) return; Payload memory payload = watcher__().getPayload(payloadId_); address consumeFrom = payload.consumeFrom; // Unblock credits from the original user - userBlockedCredits[consumeFrom] -= amount_; - blockedCredits[payloadId_] -= amount_; + accountEscrow[consumeFrom] -= amount_; + payloadEscrow[payloadId_] -= amount_; // Burn tokens from the original user _burn(consumeFrom, amount_); // Mint tokens to the transmitter _mint(assignTo_, amount_); - emit CreditsUnblockedAndAssigned(payloadId_, consumeFrom, assignTo_, amount_); + emit EscrowSettled(payloadId_, consumeFrom, assignTo_, amount_); } - function unblockCredits(bytes32 payloadId_) external override onlyWatcher { - uint256 blockedCredits_ = blockedCredits[payloadId_]; - if (blockedCredits_ == 0) return; + function releaseEscrow(bytes32 payloadId_) external override onlyWatcher { + uint256 payloadEscrow_ = payloadEscrow[payloadId_]; + if (payloadEscrow_ == 0) return; // Unblock credits from the original user // address consumeFrom = _getRequestParams(requestCount_).requestFeesDetails.consumeFrom; address consumeFrom = overrideParams.consumeFrom; - userBlockedCredits[consumeFrom] -= blockedCredits_; + accountEscrow[consumeFrom] -= payloadEscrow_; - delete blockedCredits[payloadId_]; - emit CreditsUnblocked(payloadId_, consumeFrom); + delete payloadEscrow[payloadId_]; + emit EscrowReleased(payloadId_, consumeFrom); } /** diff --git a/contracts/evmx/fees/GasAccountToken.sol b/contracts/evmx/fees/GasAccountToken.sol index eff30ec7..a30bf696 100644 --- a/contracts/evmx/fees/GasAccountToken.sol +++ b/contracts/evmx/fees/GasAccountToken.sol @@ -34,13 +34,13 @@ abstract contract GasAccountManagerStorage is IGasAccountManager { // slot 51 /// @notice Mapping to track blocked credits for each user - /// @dev address => userBlockedCredits - mapping(address => uint256) public userBlockedCredits; + /// @dev address => accountEscrow + mapping(address => uint256) public accountEscrow; // slot 52 /// @notice Mapping to track request credits details for each payload id /// @dev payloadId => RequestFee - mapping(bytes32 => uint256) public blockedCredits; + mapping(bytes32 => uint256) public payloadEscrow; // slot 53 // token pool balances @@ -61,7 +61,7 @@ abstract contract GasAccountManagerStorage is IGasAccountManager { // slot 56 /// @notice Mapping to track max fees per chain slug /// @dev chainSlug => max fees - mapping(uint32 => uint256) public maxFeesPerChainSlug; + mapping(uint32 => uint256) public maxGasPerChainSlug; ForwarderSolana public forwarderSolana; @@ -101,10 +101,10 @@ abstract contract GasAccountToken is ); /// @notice Emitted when credits are wrapped - event CreditsWrapped(address indexed consumeFrom, uint256 amount); + event GasWrapped(address indexed consumeFrom, uint256 amount); /// @notice Emitted when credits are unwrapped - event CreditsUnwrapped(address indexed consumeFrom, uint256 amount); + event GasUnwrapped(address indexed consumeFrom, uint256 amount); /// @notice Emitted when fees plug is set event GasStationSet(uint32 indexed chainSlug, bytes32 indexed gasStation); @@ -130,9 +130,9 @@ abstract contract GasAccountToken is function getMaxFees(uint32 chainSlug_) public view returns (uint256) { return - maxFeesPerChainSlug[chainSlug_] == 0 - ? maxFeesPerChainSlug[evmxSlug] - : maxFeesPerChainSlug[chainSlug_]; + maxGasPerChainSlug[chainSlug_] == 0 + ? maxGasPerChainSlug[evmxSlug] + : maxGasPerChainSlug[chainSlug_]; } function isApproved(address user_, address appGateway_) public view returns (bool) { @@ -179,7 +179,7 @@ abstract contract GasAccountToken is // reverts if transfer fails SafeTransferLib.safeTransferETH(address(gasVault), amount); - emit CreditsWrapped(receiver_, amount); + emit GasWrapped(receiver_, amount); } function unwrap(uint256 amount_, address receiver_) external { @@ -191,22 +191,22 @@ abstract contract GasAccountToken is bool success = gasVault.withdraw(receiver_, amount_); if (!success) revert InsufficientBalance(); - emit CreditsUnwrapped(receiver_, amount_); + emit GasUnwrapped(receiver_, amount_); } /// @notice Override balanceOf to return available (unblocked) credits function balanceOf(address account) public view override returns (uint256) { - return super.balanceOf(account) - userBlockedCredits[account]; + return super.balanceOf(account) - accountEscrow[account]; } /// @notice Get total balance including blocked credits - function totalBalanceOf(address account) public view returns (uint256) { + function totalGas(address account) public view returns (uint256) { return super.balanceOf(account); } /// @notice Get blocked credits for an account - function getBlockedCredits(address account) public view returns (uint256) { - return userBlockedCredits[account]; + function getPayloadEscrow(address account) public view returns (uint256) { + return accountEscrow[account]; } /// @notice Checks if the user has enough credits @@ -214,7 +214,7 @@ abstract contract GasAccountToken is /// @param spender_ address to spend from /// @param amount_ amount to spend /// @return True if the user has enough credits, false otherwise - function isCreditSpendable( + function isGasAvailable( address consumeFrom_, address spender_, uint256 amount_ @@ -240,7 +240,7 @@ abstract contract GasAccountToken is address to_, uint256 amount_ ) public override(ERC20, IGasAccountManager) returns (bool) { - if (!isCreditSpendable(from_, msg.sender, amount_)) revert InsufficientCreditsAvailable(); + if (!isGasAvailable(from_, msg.sender, amount_)) revert InsufficientCreditsAvailable(); if (msg.sender == address(watcher__())) _approve(from_, msg.sender, amount_); return super.transferFrom(from_, to_, amount_); @@ -254,7 +254,7 @@ abstract contract GasAccountToken is /// @param credits_ The amount of tokens to withdraw /// @param maxFees_ The fees needed to process the withdraw /// @param receiver_ The address of the receiver - function withdrawCredits( + function withdrawToChain( uint32 chainSlug_, address token_, uint256 credits_, @@ -280,7 +280,7 @@ abstract contract GasAccountToken is ); } - function withdrawCreditsSolana( + function withdrawToChainSolana( uint32 chainSlug_, bytes32 token_, uint256 credits_, @@ -358,11 +358,11 @@ abstract contract GasAccountToken is // ERC20 metadata function name() public pure override returns (string memory) { - return "Socket Credits"; + return "Socket Gas"; } function symbol() public pure override returns (string memory) { - return "credits"; + return "SGAS"; } function decimals() public pure override returns (uint8) { diff --git a/contracts/evmx/fees/MessageResolver.sol b/contracts/evmx/fees/MessageResolver.sol index ba1b5104..87668ea9 100644 --- a/contracts/evmx/fees/MessageResolver.sol +++ b/contracts/evmx/fees/MessageResolver.sol @@ -267,11 +267,7 @@ contract MessageResolver is // Check sponsor has sufficient credits (uses AddressResolver to get latest GasAccountManager) if ( - !gasAccountManager__().isCreditSpendable( - details.sponsor, - address(this), - details.feeAmount - ) + !gasAccountManager__().isGasAvailable(details.sponsor, address(this), details.feeAmount) ) { revert InsufficientSponsorCredits(); } diff --git a/contracts/evmx/interfaces/IGasAccountManager.sol b/contracts/evmx/interfaces/IGasAccountManager.sol index 1fcb8239..33a8fce7 100644 --- a/contracts/evmx/interfaces/IGasAccountManager.sol +++ b/contracts/evmx/interfaces/IGasAccountManager.sol @@ -9,13 +9,13 @@ interface IGasAccountManager { function unwrap(uint256 amount_, address receiver_) external; - function isCreditSpendable( + function isGasAvailable( address consumeFrom_, address spender_, uint256 amount_ ) external view returns (bool); - function withdrawCredits( + function withdrawToChain( uint32 chainSlug_, address token_, uint256 credits_, @@ -23,15 +23,11 @@ interface IGasAccountManager { address receiver_ ) external; - function blockCredits(bytes32 payloadId_, address consumeFrom_, uint256 credits_) external; + function escrowGas(bytes32 payloadId_, address consumeFrom_, uint256 credits_) external; - function unblockAndAssignCredits( - bytes32 payloadId_, - address assignTo_, - uint256 amount_ - ) external; + function settleGasPayment(bytes32 payloadId_, address assignTo_, uint256 amount_) external; - function unblockCredits(bytes32 payloadId_) external; + function releaseEscrow(bytes32 payloadId_) external; function isApproved(address appGateway_, address user_) external view returns (bool); diff --git a/contracts/evmx/interfaces/IGasEscrow.sol b/contracts/evmx/interfaces/IGasEscrow.sol new file mode 100644 index 00000000..e69de29b diff --git a/contracts/evmx/interfaces/IGasStation.sol b/contracts/evmx/interfaces/IGasStation.sol index 0adbea76..ae1ebcd8 100644 --- a/contracts/evmx/interfaces/IGasStation.sol +++ b/contracts/evmx/interfaces/IGasStation.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.21; interface IGasStation { /// @notice Event emitted when fees are deposited - event FeesDeposited( + event GasDeposited( address token, address receiver, uint256 creditAmount, @@ -11,15 +11,15 @@ interface IGasStation { bytes32 payloadId ); /// @notice Event emitted when fees are withdrawn - event FeesWithdrawn(address token, address receiver, uint256 amount); + event GasWithdrawn(address token, address receiver, uint256 amount); /// @notice Event emitted when a token is whitelisted event TokenWhitelisted(address token); /// @notice Event emitted when a token is removed from whitelist event TokenRemovedFromWhitelist(address token); - function depositCredit(address token_, address receiver_, uint256 amount_) external; + function depositForGas(address token_, address receiver_, uint256 amount_) external; - function depositCreditAndNative(address token_, address receiver_, uint256 amount_) external; + function depositForGasAndNative(address token_, address receiver_, uint256 amount_) external; function depositToNative(address token_, address receiver_, uint256 amount_) external; diff --git a/contracts/evmx/plugs/GasStation.sol b/contracts/evmx/plugs/GasStation.sol index 2bab46d6..1ed5bdc6 100644 --- a/contracts/evmx/plugs/GasStation.sol +++ b/contracts/evmx/plugs/GasStation.sol @@ -37,11 +37,11 @@ contract GasStation is IGasStation, PlugBase, AccessControl { } /////////////////////// DEPOSIT AND WITHDRAWAL /////////////////////// - function depositCredit(address token_, address receiver_, uint256 amount_) external override { + function depositForGas(address token_, address receiver_, uint256 amount_) external override { _deposit(token_, receiver_, amount_, 0); } - function depositCreditAndNative( + function depositForGasAndNative( address token_, address receiver_, uint256 amount_ @@ -67,17 +67,23 @@ contract GasStation is IGasStation, PlugBase, AccessControl { ) internal { if (!whitelistedTokens[token_]) revert TokenNotWhitelisted(token_); token_.safeTransferFrom(msg.sender, address(this), creditAmount_ + nativeAmount_); - + // Get chain slug from socket uint32 chainSlug_ = socket__.chainSlug(); - + // Encode deposit parameters: (chainSlug, token, receiver, creditAmount, nativeAmount) - bytes memory payload = abi.encode(chainSlug_, token_, receiver_, creditAmount_, nativeAmount_); - + bytes memory payload = abi.encode( + chainSlug_, + token_, + receiver_, + creditAmount_, + nativeAmount_ + ); + // Create trigger via Socket to get unique payloadId bytes32 payloadId = socket__.sendPayload(payload); - emit FeesDeposited(token_, receiver_, creditAmount_, nativeAmount_, payloadId); + emit GasDeposited(token_, receiver_, creditAmount_, nativeAmount_, payloadId); } /// @notice Withdraws fees @@ -100,7 +106,7 @@ contract GasStation is IGasStation, PlugBase, AccessControl { if (balance < amount_) revert InsufficientTokenBalance(token_, balance, amount_); token_.safeTransfer(receiver_, amount_); - emit FeesWithdrawn(token_, receiver_, amount_); + emit GasWithdrawn(token_, receiver_, amount_); } /////////////////////// ADMIN FUNCTIONS /////////////////////// diff --git a/contracts/evmx/watcher/Watcher.sol b/contracts/evmx/watcher/Watcher.sol index ed3963c2..ad8916db 100644 --- a/contracts/evmx/watcher/Watcher.sol +++ b/contracts/evmx/watcher/Watcher.sol @@ -78,7 +78,7 @@ contract Watcher is Initializable, Configurations, Pausable { function executePayload() external whenNotPaused returns (address asyncPromise) { if (latestAppGateway != msg.sender) revert AppGatewayMismatch(); if ( - !gasAccountManager__().isCreditSpendable( + !gasAccountManager__().isGasAvailable( payloadData.overrideParams.consumeFrom, latestAppGateway, payloadData.overrideParams.maxFees @@ -88,7 +88,7 @@ contract Watcher is Initializable, Configurations, Pausable { IPrecompile precompile = IPrecompile(precompiles[payloadData.overrideParams.callType]); if (address(precompile) == address(0)) revert InvalidCallType(); - gasAccountManager__().blockCredits( + gasAccountManager__().escrowGas( currentPayloadId, payloadData.overrideParams.consumeFrom, payloadData.overrideParams.maxFees @@ -139,7 +139,7 @@ contract Watcher is Initializable, Configurations, Pausable { if (!p.isTransmitterFeesSettled) { p.isTransmitterFeesSettled = true; - gasAccountManager__().unblockAndAssignCredits(p.payloadId, transmitter, feesUsed_); + gasAccountManager__().settleGasPayment(p.payloadId, transmitter, feesUsed_); } p.isPayloadExecuted = true; @@ -149,8 +149,8 @@ contract Watcher is Initializable, Configurations, Pausable { bool success = _markResolved(resolvedPromise_); if (!success) return; - gasAccountManager__().unblockAndAssignCredits(p.payloadId, address(this), p.watcherFees); - gasAccountManager__().unblockCredits(p.payloadId); + gasAccountManager__().settleGasPayment(p.payloadId, address(this), p.watcherFees); + gasAccountManager__().releaseEscrow(p.payloadId); emit PayloadSettled(p.payloadId); emit PayloadResolved(resolvedPromise_.payloadId); } @@ -255,18 +255,18 @@ contract Watcher is Initializable, Configurations, Pausable { if (r.isPayloadCancelled) revert PayloadAlreadyCancelled(); if (r.isPayloadExecuted) revert PayloadAlreadySettled(); if (r.maxFees >= newMaxFees_) revert NewMaxFeesLowerThanCurrent(r.maxFees, newMaxFees_); - gasAccountManager__().unblockCredits(payloadId_); + gasAccountManager__().releaseEscrow(payloadId_); r.maxFees = newMaxFees_; // reblock new fees if ( - !IGasAccountManager(gasAccountManager__()).isCreditSpendable( + !IGasAccountManager(gasAccountManager__()).isGasAvailable( r.consumeFrom, msg.sender, newMaxFees_ ) ) revert InsufficientFees(); - gasAccountManager__().blockCredits(payloadId_, r.consumeFrom, newMaxFees_); + gasAccountManager__().escrowGas(payloadId_, r.consumeFrom, newMaxFees_); // indexed by transmitter and watcher to start bidding or re-processing the request emit FeesIncreased(payloadId_, newMaxFees_); @@ -280,12 +280,8 @@ contract Watcher is Initializable, Configurations, Pausable { r.isPayloadCancelled = true; r.isTransmitterFeesSettled = true; - gasAccountManager__().unblockAndAssignCredits( - payloadId_, - transmitter, - r.maxFees - r.watcherFees - ); - gasAccountManager__().unblockAndAssignCredits(payloadId_, address(this), r.watcherFees); + gasAccountManager__().settleGasPayment(payloadId_, transmitter, r.maxFees - r.watcherFees); + gasAccountManager__().settleGasPayment(payloadId_, address(this), r.watcherFees); emit PayloadCancelled(payloadId_); } diff --git a/contracts/protocol/Socket.sol b/contracts/protocol/Socket.sol index 95f279c4..4b200b23 100644 --- a/contracts/protocol/Socket.sol +++ b/contracts/protocol/Socket.sol @@ -37,6 +37,7 @@ contract Socket is SocketUtils, Pausable { error InvalidVerificationChainSlug(); /// @notice Thrown when the verification switchboard id is invalid error InvalidVerificationSwitchboardId(); + /** * @notice Constructor for the Socket contract * @param chainSlug_ The chain slug @@ -77,11 +78,12 @@ contract Socket is SocketUtils, Pausable { // Get payloadId from executeParams bytes32 payloadId = executeParams_.payloadId; - + // Verify payload ID matches destination - (uint32 verificationChainSlug, uint32 verificationSwitchboardId) = getVerificationInfo(payloadId); - if (verificationChainSlug != chainSlug) - revert InvalidVerificationChainSlug(); + (uint32 verificationChainSlug, uint32 verificationSwitchboardId) = getVerificationInfo( + payloadId + ); + if (verificationChainSlug != chainSlug) revert InvalidVerificationChainSlug(); if (verificationSwitchboardId != uint32(switchboardId)) revert InvalidVerificationSwitchboardId(); @@ -162,8 +164,8 @@ contract Socket is SocketUtils, Pausable { emit ExecutionSuccess(payloadId_, exceededMaxCopy, returnData); // pay and check fees - if (address(socketFeeManager) != address(0)) { - socketFeeManager.payAndCheckFees{value: transmissionParams_.socketFees}( + if (address(networkFeeCollector) != address(0)) { + networkFeeCollector.collectNetworkFee{value: transmissionParams_.socketFees}( executeParams_, transmissionParams_ ); @@ -229,7 +231,6 @@ contract Socket is SocketUtils, Pausable { ); } - /** * @notice Increase fees for a pending payload * @param payloadId_ The payload ID to increase fees for @@ -244,13 +245,16 @@ contract Socket is SocketUtils, Pausable { ); } - function _verifyPlugSwitchboard(address plug_) internal view returns (uint32 switchboardId, address switchboardAddress) { + function _verifyPlugSwitchboard( + address plug_ + ) internal view returns (uint32 switchboardId, address switchboardAddress) { switchboardId = plugSwitchboardIds[plug_]; if (switchboardId == 0) revert PlugNotFound(); if (isValidSwitchboard[switchboardId] != SwitchboardStatus.REGISTERED) revert InvalidSwitchboard(); switchboardAddress = switchboardAddresses[switchboardId]; } + /** * @notice Fallback function that forwards all calls to Socket's sendPayload * @dev The calldata is passed as-is to the switchboard diff --git a/contracts/protocol/SocketConfig.sol b/contracts/protocol/SocketConfig.sol index a407d64a..e2d90752 100644 --- a/contracts/protocol/SocketConfig.sol +++ b/contracts/protocol/SocketConfig.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.21; import "./interfaces/ISocket.sol"; import "./interfaces/ISwitchboard.sol"; import {IPlug} from "./interfaces/IPlug.sol"; -import "./interfaces/ISocketFeeManager.sol"; +import "./interfaces/INetworkFeeCollector.sol"; import "../utils/AccessControl.sol"; import {GOVERNANCE_ROLE, RESCUE_ROLE, SWITCHBOARD_DISABLER_ROLE} from "../utils/common/AccessRoles.sol"; import {PlugConfigEvm, SwitchboardStatus, ExecutionStatus} from "../utils/common/Structs.sol"; @@ -19,7 +19,7 @@ import {MAX_COPY_BYTES} from "../utils/common/Constants.sol"; */ abstract contract SocketConfig is ISocket, AccessControl { // socket fee manager - ISocketFeeManager public socketFeeManager; + INetworkFeeCollector public networkFeeCollector; // @notice mapping of switchboard address to its status, helps socket to block invalid switchboards mapping(uint32 => SwitchboardStatus) public isValidSwitchboard; @@ -54,12 +54,16 @@ abstract contract SocketConfig is ISocket, AccessControl { // @notice event triggered when a switchboard is enabled event SwitchboardEnabled(uint32 switchboardId); // @notice event triggered when a socket fee manager is updated - event SocketFeeManagerUpdated(address oldSocketFeeManager, address newSocketFeeManager); + event NetworkFeeCollectorUpdated( + address oldNetworkFeeCollector, + address newNetworkFeeCollector + ); // @notice event triggered when the gas limit buffer is updated event GasLimitBufferUpdated(uint256 gasLimitBuffer); // @notice event triggered when the max copy bytes is updated event MaxCopyBytesUpdated(uint16 maxCopyBytes); event PlugConfigUpdated(address plug, uint32 switchboardId, bytes configData); + /** * @notice Registers a switchboard on the socket * @dev This function is called by the switchboard to register itself on the socket @@ -107,11 +111,13 @@ abstract contract SocketConfig is ISocket, AccessControl { /** * @notice Sets the socket fee manager * @dev This function is called by the governance role to set the socket fee manager - * @param socketFeeManager_ The address of the socket fee manager + * @param networkFeeCollector_ The address of the socket fee manager */ - function setSocketFeeManager(address socketFeeManager_) external onlyRole(GOVERNANCE_ROLE) { - socketFeeManager = ISocketFeeManager(socketFeeManager_); - emit SocketFeeManagerUpdated(address(socketFeeManager), socketFeeManager_); + function setNetworkFeeCollector( + address networkFeeCollector_ + ) external onlyRole(GOVERNANCE_ROLE) { + networkFeeCollector = INetworkFeeCollector(networkFeeCollector_); + emit NetworkFeeCollectorUpdated(address(networkFeeCollector), networkFeeCollector_); } /** @@ -121,8 +127,10 @@ abstract contract SocketConfig is ISocket, AccessControl { * @param configData_ The configuration data for the switchboard */ function connect(uint32 switchboardId_, bytes memory configData_) external override { - if (switchboardId_ == 0 || isValidSwitchboard[switchboardId_] != SwitchboardStatus.REGISTERED) - revert InvalidSwitchboard(); + if ( + switchboardId_ == 0 || + isValidSwitchboard[switchboardId_] != SwitchboardStatus.REGISTERED + ) revert InvalidSwitchboard(); plugSwitchboardIds[msg.sender] = switchboardId_; if (configData_.length > 0) { @@ -133,6 +141,7 @@ abstract contract SocketConfig is ISocket, AccessControl { } emit PlugConnected(msg.sender, switchboardId_, configData_); } + /** * @notice Updates plug configuration on switchboard * @dev This function is called by the plug to update its configuration diff --git a/contracts/protocol/SocketFeeManager.sol b/contracts/protocol/SocketFeeManager.sol index c0b7b946..7cf899fe 100644 --- a/contracts/protocol/SocketFeeManager.sol +++ b/contracts/protocol/SocketFeeManager.sol @@ -4,14 +4,14 @@ pragma solidity ^0.8.21; import "../utils/AccessControl.sol"; import {GOVERNANCE_ROLE, RESCUE_ROLE} from "../utils/common/AccessRoles.sol"; import {ExecuteParams, TransmissionParams} from "../utils/common/Structs.sol"; -import "./interfaces/ISocketFeeManager.sol"; +import "./interfaces/INetworkFeeCollector.sol"; import "../utils/RescueFundsLib.sol"; /** - * @title SocketFeeManager - * @notice The SocketFeeManager contract is responsible for managing socket fees + * @title NetworkFeeCollector + * @notice The NetworkFeeCollector contract is responsible for managing socket fees */ -contract SocketFeeManager is ISocketFeeManager, AccessControl { +contract NetworkFeeCollector is INetworkFeeCollector, AccessControl { // current socket fees in native tokens uint256 public socketFees; @@ -34,10 +34,10 @@ contract SocketFeeManager is ISocketFeeManager, AccessControl { * @param oldFees The old socket fees * @param newFees The new socket fees */ - event SocketFeesUpdated(uint256 oldFees, uint256 newFees); + event NetworkFeeUpdated(uint256 oldFees, uint256 newFees); /** - * @notice Initializes the SocketFeeManager contract + * @notice Initializes the NetworkFeeCollector contract * @param owner_ The owner of the contract with GOVERNANCE_ROLE * @param socketFees_ Initial socket fees amount */ @@ -46,14 +46,14 @@ contract SocketFeeManager is ISocketFeeManager, AccessControl { _grantRole(RESCUE_ROLE, owner_); socketFees = socketFees_; - emit SocketFeesUpdated(0, socketFees_); + emit NetworkFeeUpdated(0, socketFees_); } /** * @notice Pays and validates fees for execution * @dev This function is payable and will revert if the fees are insufficient */ - function payAndCheckFees(ExecuteParams memory, TransmissionParams memory) external payable { + function collectNetworkFee(ExecuteParams memory, TransmissionParams memory) external payable { if (msg.value < socketFees) revert InsufficientFees(); } @@ -61,7 +61,7 @@ contract SocketFeeManager is ISocketFeeManager, AccessControl { * @notice Gets minimum fees required for execution * @return nativeFees Minimum native token fees required */ - function getMinSocketFees() external view returns (uint256 nativeFees) { + function getNetworkFee() external view returns (uint256 nativeFees) { return socketFees; } @@ -69,8 +69,8 @@ contract SocketFeeManager is ISocketFeeManager, AccessControl { * @notice Sets socket fees * @param socketFees_ New socket fees amount */ - function setSocketFees(uint256 socketFees_) external onlyRole(GOVERNANCE_ROLE) { - emit SocketFeesUpdated(socketFees, socketFees_); + function setNetworkFee(uint256 socketFees_) external onlyRole(GOVERNANCE_ROLE) { + emit NetworkFeeUpdated(socketFees, socketFees_); socketFees = socketFees_; } diff --git a/contracts/protocol/interfaces/ISocketFeeManager.sol b/contracts/protocol/interfaces/INetworkFeeCollector.sol similarity index 80% rename from contracts/protocol/interfaces/ISocketFeeManager.sol rename to contracts/protocol/interfaces/INetworkFeeCollector.sol index 029379e5..4573836d 100644 --- a/contracts/protocol/interfaces/ISocketFeeManager.sol +++ b/contracts/protocol/interfaces/INetworkFeeCollector.sol @@ -4,16 +4,16 @@ pragma solidity ^0.8.21; import {ExecuteParams, TransmissionParams} from "../../utils/common/Structs.sol"; /** - * @title ISocketFeeManager + * @title INetworkFeeCollector * @notice Interface for the socket fee manager */ -interface ISocketFeeManager { +interface INetworkFeeCollector { /** * @notice Pays and validates fees for execution * @param executeParams_ The execution parameters * @param transmissionParams_ The transmission parameters */ - function payAndCheckFees( + function collectNetworkFee( ExecuteParams memory executeParams_, TransmissionParams memory transmissionParams_ ) external payable; @@ -22,13 +22,13 @@ interface ISocketFeeManager { * @notice Gets minimum fees required for execution * @return nativeFees The minimum native token fees required */ - function getMinSocketFees() external view returns (uint256 nativeFees); + function getNetworkFee() external view returns (uint256 nativeFees); /** * @notice Sets socket fees * @param socketFees_ The new socket fees amount */ - function setSocketFees(uint256 socketFees_) external; + function setNetworkFee(uint256 socketFees_) external; /** * @notice Gets current socket fees diff --git a/contracts/utils/common/Structs.sol b/contracts/utils/common/Structs.sol index cb4839e4..88b9ee4c 100644 --- a/contracts/utils/common/Structs.sol +++ b/contracts/utils/common/Structs.sol @@ -102,7 +102,7 @@ struct CreateRequestResult { struct UserCredits { uint256 totalCredits; - uint256 blockedCredits; + uint256 payloadEscrow; } // digest: diff --git a/hardhat-scripts/deploy/9.setupTransmitter.ts b/hardhat-scripts/deploy/9.setupTransmitter.ts index f732ff63..e13da788 100644 --- a/hardhat-scripts/deploy/9.setupTransmitter.ts +++ b/hardhat-scripts/deploy/9.setupTransmitter.ts @@ -20,7 +20,7 @@ let transmitterAddress: string; export const main = async () => { console.log("Setting up transmitter..."); await init(); - await checkAndDepositCredits(transmitterAddress); + await checkAndDepositForGass(transmitterAddress); await checkAndDepositNative(transmitterAddress); console.log("Transmitter setup complete!"); @@ -37,7 +37,7 @@ export const init = async () => { transmitterAddress = await transmitterSigner.getAddress(); }; -export const checkAndDepositCredits = async (transmitter: string) => { +export const checkAndDepositForGass = async (transmitter: string) => { console.log("Checking and depositing credits"); const credits = await gasAccountManagerContract .connect(transmitterSigner) diff --git a/hardhat-scripts/test/chainTest.ts b/hardhat-scripts/test/chainTest.ts index 660ab886..489e59de 100644 --- a/hardhat-scripts/test/chainTest.ts +++ b/hardhat-scripts/test/chainTest.ts @@ -75,8 +75,8 @@ class ChainTester { // GasAccountManager ABI (minimal required functions) const gasAccountManagerABI = [ - "function totalBalanceOf(address) view returns (uint256)", - "function getBlockedCredits(address) view returns (uint256)", + "function totalGas(address) view returns (uint256)", + "function getPayloadEscrow(address) view returns (uint256)", "function balanceOf(address) view returns (uint256)", ]; @@ -102,10 +102,10 @@ class ChainTester { const appGatewayAddress = process.env.COUNTER_APP_GATEWAY!; const gasAccountManagerAddress = process.env.FEES_MANAGER!; - const totalCredits = await this.gasAccountManager.totalBalanceOf( + const totalCredits = await this.gasAccountManager.totalGas( appGatewayAddress ); - const blockedCredits = await this.gasAccountManager.getBlockedCredits( + const payloadEscrow = await this.gasAccountManager.getPayloadEscrow( appGatewayAddress ); const availableFees = await this.gasAccountManager.balanceOf(appGatewayAddress); @@ -116,7 +116,7 @@ class ChainTester { `Total Credits: ${ethers.utils.formatEther(totalCredits)} ETH` ); console.log( - `Blocked Credits: ${ethers.utils.formatEther(blockedCredits)} ETH` + `Blocked Credits: ${ethers.utils.formatEther(payloadEscrow)} ETH` ); console.log( `Available Fees: ${ethers.utils.formatEther(availableFees)} ETH` diff --git a/script/counter/WithdrawFeesArbitrumFeesPlug.s.sol b/script/counter/WithdrawFeesArbitrumFeesPlug.s.sol index cf84ed8c..541dfac4 100644 --- a/script/counter/WithdrawFeesArbitrumFeesPlug.s.sol +++ b/script/counter/WithdrawFeesArbitrumFeesPlug.s.sol @@ -47,7 +47,7 @@ contract WithdrawFees is Script { vm.createSelectFork(vm.envString("EVMX_RPC")); vm.startBroadcast(privateKey); console.log("Withdrawing amount:", amountToWithdraw); - appGateway.withdrawCredits(421614, token, amountToWithdraw, sender); + appGateway.withdrawToChain(421614, token, amountToWithdraw, sender); vm.stopBroadcast(); // Switch back to Arbitrum Sepolia to check final balance diff --git a/script/helpers/CheckDepositedCredits.s.sol b/script/helpers/CheckDepositedCredits.s.sol index a95d86a0..bd3f9eca 100644 --- a/script/helpers/CheckDepositedCredits.s.sol +++ b/script/helpers/CheckDepositedCredits.s.sol @@ -13,12 +13,12 @@ contract CheckDepositedCredits is Script { ); address appGateway = vm.envAddress("APP_GATEWAY"); - uint256 totalCredits = gasAccountManager.totalBalanceOf(appGateway); - uint256 blockedCredits = gasAccountManager.getBlockedCredits(appGateway); + uint256 totalCredits = gasAccountManager.totalGas(appGateway); + uint256 payloadEscrow = gasAccountManager.getPayloadEscrow(appGateway); console.log("App Gateway:", appGateway); console.log("Fees Manager:", address(gasAccountManager)); console.log("totalCredits fees:", totalCredits); - console.log("blockedCredits fees:", blockedCredits); + console.log("payloadEscrow fees:", payloadEscrow); uint256 availableFees = gasAccountManager.balanceOf(appGateway); console.log("Available fees:", availableFees); diff --git a/script/helpers/DepositCredit.s.sol b/script/helpers/DepositCredit.s.sol index 96af418e..466636a1 100644 --- a/script/helpers/DepositCredit.s.sol +++ b/script/helpers/DepositCredit.s.sol @@ -6,8 +6,8 @@ import {console} from "forge-std/console.sol"; import {GasStation} from "../../contracts/evmx/plugs/GasStation.sol"; import {TestUSDC} from "../../contracts/evmx/mocks/TestUSDC.sol"; -// source .env && forge script script/helpers/DepositCreditAndNative.s.sol --broadcast --skip-simulation -contract DepositCredit is Script { +// source .env && forge script script/helpers/DepositForGasAndNative.s.sol --broadcast --skip-simulation +contract DepositForGas is Script { function run() external { uint256 feesAmount = 2000000; // 2 USDC vm.createSelectFork(vm.envString("ARBITRUM_SEPOLIA_RPC")); @@ -30,6 +30,6 @@ contract DepositCredit is Script { console.log("App Gateway:", appGateway); console.log("Fees Plug:", address(gasStation)); console.log("Fees Amount:", feesAmount); - gasStation.depositCredit(address(testUSDCContract), appGateway, feesAmount); + gasStation.depositForGas(address(testUSDCContract), appGateway, feesAmount); } } diff --git a/script/helpers/DepositCreditAndNative.s.sol b/script/helpers/DepositCreditAndNative.s.sol index e58c8f80..dce85a60 100644 --- a/script/helpers/DepositCreditAndNative.s.sol +++ b/script/helpers/DepositCreditAndNative.s.sol @@ -6,8 +6,8 @@ import {console} from "forge-std/console.sol"; import {GasStation} from "../../contracts/evmx/plugs/GasStation.sol"; import {TestUSDC} from "../../contracts/evmx/mocks/TestUSDC.sol"; -// source .env && forge script script/helpers/DepositCreditAndNative.s.sol --broadcast --skip-simulation -contract DepositCreditAndNative is Script { +// source .env && forge script script/helpers/DepositForGasAndNative.s.sol --broadcast --skip-simulation +contract DepositForGasAndNative is Script { function run() external { uint256 feesAmount = 100000000; // 100 USDC vm.createSelectFork(vm.envString("ARBITRUM_SEPOLIA_RPC")); @@ -30,6 +30,6 @@ contract DepositCreditAndNative is Script { console.log("App Gateway:", appGateway); console.log("Fees Plug:", address(gasStation)); console.log("Fees Amount:", feesAmount); - gasStation.depositCreditAndNative(address(testUSDCContract), appGateway, feesAmount); + gasStation.depositForGasAndNative(address(testUSDCContract), appGateway, feesAmount); } } diff --git a/script/helpers/DepositCreditMainnet.s.sol b/script/helpers/DepositCreditMainnet.s.sol index 5e9fcd5f..b26bf2be 100644 --- a/script/helpers/DepositCreditMainnet.s.sol +++ b/script/helpers/DepositCreditMainnet.s.sol @@ -7,8 +7,8 @@ import {GasStation} from "../../contracts/evmx/plugs/GasStation.sol"; import {TestUSDC} from "../../contracts/evmx/mocks/TestUSDC.sol"; import "solady/tokens/ERC20.sol"; -// source .env && forge script script/helpers/DepositCreditAndNative.s.sol --broadcast --skip-simulation -contract DepositCredit is Script { +// source .env && forge script script/helpers/DepositForGasAndNative.s.sol --broadcast --skip-simulation +contract DepositForGas is Script { function run() external { uint256 feesAmount = 1000000; // 1 USDC vm.createSelectFork(vm.envString("ARBITRUM_RPC")); @@ -32,6 +32,6 @@ contract DepositCredit is Script { console.log("App Gateway:", appGateway); console.log("Fees Plug:", address(gasStation)); console.log("Fees Amount:", feesAmount); - gasStation.depositCredit(address(USDCContract), appGateway, feesAmount); + gasStation.depositForGas(address(USDCContract), appGateway, feesAmount); } } diff --git a/script/helpers/TransferRemainingCredits.s.sol b/script/helpers/TransferRemainingCredits.s.sol index ba47108b..5f0b135a 100644 --- a/script/helpers/TransferRemainingCredits.s.sol +++ b/script/helpers/TransferRemainingCredits.s.sol @@ -19,13 +19,13 @@ contract TransferRemainingCredits is Script { address appGateway = vm.envAddress("APP_GATEWAY"); address newAppGateway = vm.envAddress("NEW_APP_GATEWAY"); - uint256 totalCredits = gasAccountManager.totalBalanceOf(appGateway); - uint256 blockedCredits = gasAccountManager.getBlockedCredits(appGateway); + uint256 totalCredits = gasAccountManager.totalGas(appGateway); + uint256 payloadEscrow = gasAccountManager.getPayloadEscrow(appGateway); console.log("App Gateway:", appGateway); console.log("New App Gateway:", newAppGateway); console.log("Fees Manager:", address(gasAccountManager)); console.log("totalCredits fees:", totalCredits); - console.log("blockedCredits fees:", blockedCredits); + console.log("payloadEscrow fees:", payloadEscrow); uint256 availableFees = gasAccountManager.balanceOf(appGateway); console.log("Available fees:", availableFees); diff --git a/script/helpers/WithdrawRemainingCredits.s.sol b/script/helpers/WithdrawRemainingCredits.s.sol index a13c991e..bb0eee92 100644 --- a/script/helpers/WithdrawRemainingCredits.s.sol +++ b/script/helpers/WithdrawRemainingCredits.s.sol @@ -17,12 +17,12 @@ contract WithdrawRemainingCredits is Script { ); address appGateway = vm.envAddress("APP_GATEWAY"); - uint256 totalCredits = gasAccountManager.totalBalanceOf(appGateway); - uint256 blockedCredits = gasAccountManager.getBlockedCredits(appGateway); + uint256 totalCredits = gasAccountManager.totalGas(appGateway); + uint256 payloadEscrow = gasAccountManager.getPayloadEscrow(appGateway); console.log("App Gateway:", appGateway); console.log("Fees Manager:", address(gasAccountManager)); console.log("totalCredits fees:", totalCredits); - console.log("blockedCredits fees:", blockedCredits); + console.log("payloadEscrow fees:", payloadEscrow); uint256 availableFees = gasAccountManager.balanceOf(appGateway); console.log("Available fees:", availableFees); diff --git a/src/enums.ts b/src/enums.ts index f148ad3a..39cd7569 100644 --- a/src/enums.ts +++ b/src/enums.ts @@ -13,7 +13,7 @@ export enum Events { AppGatewayCallRequested = "AppGatewayCallRequested", // GasStation - FeesDeposited = "FeesDeposited", + GasDeposited = "GasDeposited", // Watcher TriggerFailed = "TriggerFailed", @@ -61,7 +61,7 @@ export enum Contracts { MessageSwitchboard = "MessageSwitchboard", MessageSwitchboardId = "MessageSwitchboardId", SocketBatcher = "SocketBatcher", - SocketFeeManager = "SocketFeeManager", + NetworkFeeCollector = "NetworkFeeCollector", AddressResolver = "AddressResolver", Watcher = "Watcher", RequestHandler = "RequestHandler", diff --git a/src/events.ts b/src/events.ts index f1d5d2a6..a85768bd 100644 --- a/src/events.ts +++ b/src/events.ts @@ -7,7 +7,7 @@ export const socketEvents = [ Events.AppGatewayCallRequested, ]; -export const gasStationEvents = [Events.FeesDeposited]; +export const gasStationEvents = [Events.GasDeposited]; export const watcherEvents = [ Events.TriggerFailed, diff --git a/test/SetupTest.t.sol b/test/SetupTest.t.sol index 507b56f1..92f722bb 100644 --- a/test/SetupTest.t.sol +++ b/test/SetupTest.t.sol @@ -15,7 +15,7 @@ import "../contracts/protocol/Socket.sol"; import "../contracts/protocol/switchboard/FastSwitchboard.sol"; import "../contracts/protocol/switchboard/MessageSwitchboard.sol"; import "../contracts/protocol/SocketBatcher.sol"; -import "../contracts/protocol/SocketFeeManager.sol"; +import "../contracts/protocol/NetworkFeeCollector.sol"; import "../contracts/protocol/base/MessagePlugBase.sol"; import "../contracts/evmx/watcher/Watcher.sol"; @@ -74,7 +74,7 @@ contract SetupStore is Test, Utils { uint32 chainSlug; uint256 triggerPrefix; Socket socket; - SocketFeeManager socketFeeManager; + NetworkFeeCollector networkFeeCollector; FastSwitchboard switchboard; MessageSwitchboard messageSwitchboard; SocketBatcher socketBatcher; @@ -196,7 +196,7 @@ contract DeploySetup is SetupStore { triggerPrefix: (uint256(chainSlug_) << 224) | (uint256(uint160(address(socket))) << 64), socket: socket, - socketFeeManager: new SocketFeeManager(socketOwner, socketFees), + networkFeeCollector: new NetworkFeeCollector(socketOwner, socketFees), switchboard: new FastSwitchboard(chainSlug_, socket, socketOwner), messageSwitchboard: new MessageSwitchboard(chainSlug_, socket, socketOwner), socketBatcher: new SocketBatcher(socketOwner, socket), @@ -390,8 +390,8 @@ contract FeesSetup is DeploySetup { uint256 creditAmount, uint256 nativeAmount ); - event CreditsWrapped(address indexed consumeFrom, uint256 amount); - event CreditsUnwrapped(address indexed consumeFrom, uint256 amount); + event GasWrapped(address indexed consumeFrom, uint256 amount); + event GasUnwrapped(address indexed consumeFrom, uint256 amount); event CreditsTransferred(address indexed from, address indexed to, uint256 amount); function deploy() internal { @@ -420,7 +420,7 @@ contract FeesSetup is DeploySetup { vm.startPrank(user_); token.approve(address(socketConfig.gasStation), 100 ether); - socketConfig.gasStation.depositCreditAndNative(address(token), user_, 100 ether); + socketConfig.gasStation.depositForGasAndNative(address(token), user_, 100 ether); vm.stopPrank(); assertEq( diff --git a/test/apps/counter/CounterAppGateway.sol b/test/apps/counter/CounterAppGateway.sol index fc335dc6..8a51e513 100644 --- a/test/apps/counter/CounterAppGateway.sol +++ b/test/apps/counter/CounterAppGateway.sol @@ -95,13 +95,13 @@ contract CounterAppGateway is AppGatewayBase, Ownable { overrideParams = overrideParams.setMaxFees(fees_); } - function withdrawCredits( + function withdrawToChain( uint32 chainSlug_, address token_, uint256 amount_, address receiver_ ) external { - _withdrawCredits(chainSlug_, token_, amount_, receiver_); + _withdrawToChain(chainSlug_, token_, amount_, receiver_); } function testOnChainRevert(uint32 chainSlug) public async { @@ -115,7 +115,7 @@ contract CounterAppGateway is AppGatewayBase, Ownable { address instance = forwarderAddresses[counter][chainSlug]; ICounter(instance).getCounter(); // wrong function call in callback so it reverts - then(this.withdrawCredits.selector, abi.encode(chainSlug)); + then(this.withdrawToChain.selector, abi.encode(chainSlug)); } function increaseFees(bytes32 payloadId_, uint256 newMaxFees_) public { From dc9b920f5e57de751e43cf3870902a1304960365 Mon Sep 17 00:00:00 2001 From: Ameesha Agrawal Date: Wed, 12 Nov 2025 18:39:53 +0530 Subject: [PATCH 03/12] fix: protocol fees contracts --- PAYLOAD_ID_ARCHITECTURE.md | 19 +- contracts/evmx/interfaces/IGasStation.sol | 2 +- contracts/evmx/plugs/GasStation.sol | 22 +-- ...FeeManager.sol => NetworkFeeCollector.sol} | 44 +++-- .../interfaces/INetworkFeeCollector.sol | 16 +- .../protocol/switchboard/FastSwitchboard.sol | 30 ++- .../switchboard/MessageSwitchboard.sol | 52 ++--- contracts/utils/Pausable.sol | 1 - contracts/utils/common/AccessRoles.sol | 6 +- contracts/utils/common/Converters.sol | 7 + contracts/utils/common/IdUtils.sol | 21 +- hardhat-scripts/admin/disconnect.ts | 4 +- hardhat-scripts/admin/rescue.ts | 3 +- hardhat-scripts/constants/constants.ts | 2 +- hardhat-scripts/deploy/4.configureEVMx.ts | 9 +- hardhat-scripts/deploy/8.setupEnv.ts | 5 +- hardhat-scripts/test/chainTest.ts | 7 +- test/PausableTest.t.sol | 111 ++++++----- test/SocketPayloadIdVerification.t.sol | 171 +++++++++-------- test/mocks/MockPlug.sol | 8 +- test/switchboard/MessageSwitchboard.t.sol | 181 ++++++++++-------- 21 files changed, 406 insertions(+), 315 deletions(-) rename contracts/protocol/{SocketFeeManager.sol => NetworkFeeCollector.sol} (65%) diff --git a/PAYLOAD_ID_ARCHITECTURE.md b/PAYLOAD_ID_ARCHITECTURE.md index 21e9a635..2dd23e63 100644 --- a/PAYLOAD_ID_ARCHITECTURE.md +++ b/PAYLOAD_ID_ARCHITECTURE.md @@ -1,16 +1,19 @@ # Payload ID Architecture - Unified Design ## Overview + Unified payload ID structure for all three payload types: Write, Trigger, and Message. ## Payload ID Structure ### Bit Layout (256 bits total) + ``` [Origin: 64 bits][Verification: 64 bits][Pointer: 64 bits][Reserved: 64 bits] ``` Each component breakdown: + - **Origin (64 bits)**: `chainSlug (32 bits) | switchboardId/watcherId (32 bits)` - **Verification (64 bits)**: `chainSlug (32 bits) | switchboardId/watcherId (32 bits)` - **Pointer (64 bits)**: Counter value @@ -19,6 +22,7 @@ Each component breakdown: ## Payload Type Specifications ### 1. Write Payloads (EVMX → On-chain) + - **Origin**: `evmxChainSlug (32) | watcherId (32)` - Generated by: Watcher (on EVMX) - Verified by: Watcher offchain (links source) @@ -31,6 +35,7 @@ Each component breakdown: **Where Created**: `Watcher.sol` → `getCurrentPayloadId()` ### 2. Trigger Payloads (On-chain → EVMX) + - **Origin**: `srcChainSlug (32) | srcSwitchboardId (32)` - Generated by: FastSwitchboard - Verified by: Watcher offchain (verifies source) @@ -43,6 +48,7 @@ Each component breakdown: **Where Created**: `FastSwitchboard.sol` → `processPayload()` ### 3. Message Payloads (Plug → Plug) + - **Origin**: `srcChainSlug (32) | srcSwitchboardId (32)` - Generated by: MessageSwitchboard - Verified by: Destination switchboard (checks source) @@ -57,6 +63,7 @@ Each component breakdown: ## Decoding and Verification ### Socket Verification (Destination) + 1. Decode `payloadId` using `getVerificationInfo(payloadId)` 2. Extract `verificationChainSlug` and `verificationSwitchboardId` 3. Verify against local config: @@ -64,11 +71,13 @@ Each component breakdown: - `verificationSwitchboardId == local switchboardId` ### Source Verification (Off-chain Watcher) + 1. Decode `payloadId` using `getOriginInfo(payloadId)` 2. Extract `originChainSlug` and `originId` 3. Verify source configuration matches expected values ### Payload Type Detection + - Check if `originChainSlug` or `verificationChainSlug` matches `evmxChainSlug` - If `originChainSlug == evmxChainSlug`: **Write Payload** - If `verificationChainSlug == evmxChainSlug`: **Trigger Payload** @@ -79,10 +88,12 @@ Each component breakdown: ### IdUtils.sol Functions #### Encoding + - `createPayloadId(originChainSlug, originId, verificationChainSlug, verificationId, pointer)` - Creates new payload ID with all components #### Decoding + - `decodePayloadId(payloadId)` - Full decode - `getVerificationInfo(payloadId)` - Gets verification components (for Socket routing) - `getOriginInfo(payloadId)` - Gets origin components (for source verification) @@ -90,22 +101,26 @@ Each component breakdown: ### Required Updates 1. **Watcher.sol** + - Update `getCurrentPayloadId()` to use new format - Use `evmxSlug` as origin chain slug - Use hardcoded `watcherId = 1` for now - Get `dstSwitchboardId` from `switchboards` mapping 2. **FastSwitchboard.sol** + - Add state variables: `evmxChainSlug`, `watcherId` (with onlyOwner setters) - Implement `processPayload()` to create payload ID - Add counter: `uint64 public triggerPayloadCounter` - Use: `origin = (chainSlug, switchboardId)`, `verification = (evmxChainSlug, watcherId)` 3. **MessageSwitchboard.sol** + - Update `_createDigestAndPayloadId()` to use new format - Use: `origin = (chainSlug, switchboardId)`, `verification = (dstChainSlug, dstSwitchboardId)` 4. **Socket.sol** + - Update `execute()` to decode payload ID and verify verification components - Remove old `createPayloadId` usage - Use `getVerificationInfo()` to extract routing info @@ -116,16 +131,19 @@ Each component breakdown: ## Security Considerations ### Verification Flow + 1. **Destination (Socket)**: Verifies verification component matches local config 2. **Source (Watcher offchain)**: Verifies origin component matches expected source 3. **Pointer verification**: Skipped for now (to be added later) ### Counter Management + - Each switchboard maintains its own counter - Prevents cross-switchboard collisions - Counters are monotonic (never decrease) ### ID Uniqueness + - Guaranteed by switchboard-specific counters - Origin + Verification provide additional context - Reserved bits allow future expansion without breaking changes @@ -141,4 +159,3 @@ Each component breakdown: - Add pointer verification mechanism - Use reserved bits for additional metadata (payload version, flags, etc.) - Support multiple watchers (remove hardcoded watcherId = 1) - diff --git a/contracts/evmx/interfaces/IGasStation.sol b/contracts/evmx/interfaces/IGasStation.sol index ae1ebcd8..e4cfa948 100644 --- a/contracts/evmx/interfaces/IGasStation.sol +++ b/contracts/evmx/interfaces/IGasStation.sol @@ -23,5 +23,5 @@ interface IGasStation { function depositToNative(address token_, address receiver_, uint256 amount_) external; - function withdrawFees(address token_, address receiver_, uint256 amount_) external; + function withdrawToTokens(address token_, address receiver_, uint256 amount_) external; } diff --git a/contracts/evmx/plugs/GasStation.sol b/contracts/evmx/plugs/GasStation.sol index 1ed5bdc6..5b237880 100644 --- a/contracts/evmx/plugs/GasStation.sol +++ b/contracts/evmx/plugs/GasStation.sol @@ -56,41 +56,37 @@ contract GasStation is IGasStation, PlugBase, AccessControl { /// @notice Deposits funds /// @param token_ The token address - /// @param creditAmount_ The amount of fees + /// @param gasAmount_ The amount of fees /// @param nativeAmount_ The amount of native tokens /// @param receiver_ The receiver address function _deposit( address token_, address receiver_, - uint256 creditAmount_, + uint256 gasAmount_, uint256 nativeAmount_ ) internal { if (!whitelistedTokens[token_]) revert TokenNotWhitelisted(token_); - token_.safeTransferFrom(msg.sender, address(this), creditAmount_ + nativeAmount_); - // Get chain slug from socket - uint32 chainSlug_ = socket__.chainSlug(); - - // Encode deposit parameters: (chainSlug, token, receiver, creditAmount, nativeAmount) + // Encode deposit parameters: (chainSlug, token, receiver, gasAmount, nativeAmount) bytes memory payload = abi.encode( - chainSlug_, + socket__.chainSlug(), token_, receiver_, - creditAmount_, + gasAmount_, nativeAmount_ ); // Create trigger via Socket to get unique payloadId bytes32 payloadId = socket__.sendPayload(payload); - - emit GasDeposited(token_, receiver_, creditAmount_, nativeAmount_, payloadId); + token_.safeTransferFrom(msg.sender, address(this), gasAmount_ + nativeAmount_); + emit GasDeposited(token_, receiver_, gasAmount_, nativeAmount_, payloadId); } - /// @notice Withdraws fees + /// @notice Withdraws tokens /// @param token_ The token address /// @param amount_ The amount /// @param receiver_ The receiver address - function withdrawFees( + function withdrawToTokens( address token_, address receiver_, uint256 amount_ diff --git a/contracts/protocol/SocketFeeManager.sol b/contracts/protocol/NetworkFeeCollector.sol similarity index 65% rename from contracts/protocol/SocketFeeManager.sol rename to contracts/protocol/NetworkFeeCollector.sol index 7cf899fe..7484a288 100644 --- a/contracts/protocol/SocketFeeManager.sol +++ b/contracts/protocol/NetworkFeeCollector.sol @@ -13,7 +13,7 @@ import "../utils/RescueFundsLib.sol"; */ contract NetworkFeeCollector is INetworkFeeCollector, AccessControl { // current socket fees in native tokens - uint256 public socketFees; + uint256 public networkFee; //////////////////////////////////////////////////////////// ////////////////////// ERRORS ////////////////////////// @@ -36,42 +36,58 @@ contract NetworkFeeCollector is INetworkFeeCollector, AccessControl { */ event NetworkFeeUpdated(uint256 oldFees, uint256 newFees); + /** + * @notice Emitted when the network fees are collected + * @param amount The amount of network fees collected + * @param params The execution parameters + * @param transmissionParams The transmission parameters + */ + event NetworkFeeCollected( + uint256 amount, + ExecuteParams params, + TransmissionParams transmissionParams + ); + /** * @notice Initializes the NetworkFeeCollector contract * @param owner_ The owner of the contract with GOVERNANCE_ROLE - * @param socketFees_ Initial socket fees amount + * @param networkFee_ Initial socket fees amount */ - constructor(address owner_, uint256 socketFees_) { + constructor(address owner_, uint256 networkFee_) { _grantRole(GOVERNANCE_ROLE, owner_); _grantRole(RESCUE_ROLE, owner_); - socketFees = socketFees_; - emit NetworkFeeUpdated(0, socketFees_); + networkFee = networkFee_; + emit NetworkFeeUpdated(0, networkFee_); } /** * @notice Pays and validates fees for execution * @dev This function is payable and will revert if the fees are insufficient */ - function collectNetworkFee(ExecuteParams memory, TransmissionParams memory) external payable { - if (msg.value < socketFees) revert InsufficientFees(); + function collectNetworkFee( + ExecuteParams memory params, + TransmissionParams memory transmissionParams + ) external payable { + if (msg.value < networkFee) revert InsufficientFees(); + emit NetworkFeeCollected(msg.value, params, transmissionParams); } /** * @notice Gets minimum fees required for execution - * @return nativeFees Minimum native token fees required + * @return networkFee Minimum network fees required */ - function getNetworkFee() external view returns (uint256 nativeFees) { - return socketFees; + function getNetworkFee() external view returns (uint256) { + return networkFee; } /** * @notice Sets socket fees - * @param socketFees_ New socket fees amount + * @param networkFee_ New socket fees amount */ - function setNetworkFee(uint256 socketFees_) external onlyRole(GOVERNANCE_ROLE) { - emit NetworkFeeUpdated(socketFees, socketFees_); - socketFees = socketFees_; + function setNetworkFee(uint256 networkFee_) external onlyRole(GOVERNANCE_ROLE) { + emit NetworkFeeUpdated(networkFee, networkFee_); + networkFee = networkFee_; } /** diff --git a/contracts/protocol/interfaces/INetworkFeeCollector.sol b/contracts/protocol/interfaces/INetworkFeeCollector.sol index 4573836d..2677607d 100644 --- a/contracts/protocol/interfaces/INetworkFeeCollector.sol +++ b/contracts/protocol/interfaces/INetworkFeeCollector.sol @@ -20,19 +20,13 @@ interface INetworkFeeCollector { /** * @notice Gets minimum fees required for execution - * @return nativeFees The minimum native token fees required + * @return networkFee The minimum network fees required */ - function getNetworkFee() external view returns (uint256 nativeFees); + function getNetworkFee() external view returns (uint256); /** - * @notice Sets socket fees - * @param socketFees_ The new socket fees amount + * @notice Sets network fees + * @param networkFee_ The new network fees amount */ - function setNetworkFee(uint256 socketFees_) external; - - /** - * @notice Gets current socket fees - * @return socketFees The current socket fees amount - */ - function socketFees() external view returns (uint256); + function setNetworkFee(uint256 networkFee_) external; } diff --git a/contracts/protocol/switchboard/FastSwitchboard.sol b/contracts/protocol/switchboard/FastSwitchboard.sol index 22059e24..42704fa0 100644 --- a/contracts/protocol/switchboard/FastSwitchboard.sol +++ b/contracts/protocol/switchboard/FastSwitchboard.sol @@ -19,11 +19,11 @@ contract FastSwitchboard is SwitchboardBase { // sibling mappings for outbound journey // chainSlug => address => siblingPlug mapping(address => bytes32) public plugAppGatewayIds; - + // EVMX configuration for trigger payloads uint32 public evmxChainSlug; uint32 public watcherId; - + // Counter for trigger payload IDs uint64 public triggerPayloadCounter; // Error emitted when a payload is already attested by watcher. @@ -115,7 +115,7 @@ contract FastSwitchboard is SwitchboardBase { /** * @inheritdoc ISwitchboard - * @dev Creates a trigger payload ID with origin=(srcChainSlug, srcSwitchboardId), + * @dev Creates a trigger payload ID with origin=(srcChainSlug, srcSwitchboardId), * verification=(evmxChainSlug, watcherId) * @return payloadId The created payload ID */ @@ -126,32 +126,26 @@ contract FastSwitchboard is SwitchboardBase { ) external payable override onlySocket returns (bytes32 payloadId) { if (evmxChainSlug == 0 || watcherId == 0) revert EvmxConfigNotSet(); uint256 deadline = abi.decode(overrides_, (uint256)); - + bytes memory overrides = overrides_; if (deadline == 0) overrides = abi.encode(block.timestamp + defaultDeadline); - + // Create trigger payload ID // Origin: source chain and switchboard // Verification: EVMX chain and watcher // Pointer: switchboard counter payloadId = createPayloadId( - chainSlug, // origin chain slug (source) - switchboardId, // origin id (source switchboard) - evmxChainSlug, // verification chain slug (evmx) - watcherId, // verification id (watcher id) + chainSlug, // origin chain slug (source) + switchboardId, // origin id (source switchboard) + evmxChainSlug, // verification chain slug (evmx) + watcherId, // verification id (watcher id) triggerPayloadCounter++ // pointer (counter) ); - + // Emit PayloadRequested event - emit PayloadRequested( - payloadId, - plug_, - switchboardId, - overrides, - payload_ - ); + emit PayloadRequested(payloadId, plug_, switchboardId, overrides, payload_); } - + /** * @inheritdoc ISwitchboard */ diff --git a/contracts/protocol/switchboard/MessageSwitchboard.sol b/contracts/protocol/switchboard/MessageSwitchboard.sol index 0c482d66..482d655e 100644 --- a/contracts/protocol/switchboard/MessageSwitchboard.sol +++ b/contracts/protocol/switchboard/MessageSwitchboard.sol @@ -115,7 +115,11 @@ contract MessageSwitchboard is SwitchboardBase { // Event emitted when minimum message value fees are set event MinMsgValueFeesSet(uint32 indexed chainSlug, uint256 minFees, address indexed updater); // Event emitted when sponsored fees are increased - event SponsoredFeesIncreased(bytes32 indexed payloadId, uint256 newMaxFees, address indexed plug); + event SponsoredFeesIncreased( + bytes32 indexed payloadId, + uint256 newMaxFees, + address indexed plug + ); // Event emitted when payload is requested event PayloadRequested( bytes32 indexed payloadId, @@ -180,13 +184,17 @@ contract MessageSwitchboard is SwitchboardBase { _validateSibling(overrides.dstChainSlug, plug_); // Create digest and payload ID (common for both flows) - (DigestParams memory digestParams, bytes32 digest, bytes32 payloadId_) = _createDigestAndPayloadId( - overrides.dstChainSlug, - plug_, - overrides.gasLimit, - overrides.value, - payload_ - ); + ( + DigestParams memory digestParams, + bytes32 digest, + bytes32 payloadId_ + ) = _createDigestAndPayloadId( + overrides.dstChainSlug, + plug_, + overrides.gasLimit, + overrides.value, + payload_ + ); payloadId = payloadId_; if (overrides.isSponsored) { @@ -236,13 +244,7 @@ contract MessageSwitchboard is SwitchboardBase { } // Emit PayloadRequested event - emit PayloadRequested( - payloadId, - plug_, - switchboardId, - overrides_, - payload_ - ); + emit PayloadRequested(payloadId, plug_, switchboardId, overrides_, payload_); } /** @@ -254,7 +256,7 @@ contract MessageSwitchboard is SwitchboardBase { uint8 version = abi.decode(overrides_, (uint8)); if (version == 1) { - // Version 1: Native flow + // Version 1: Native flow ( , uint32 dstChainSlug, @@ -263,7 +265,7 @@ contract MessageSwitchboard is SwitchboardBase { address refundAddress, uint256 deadline ) = abi.decode(overrides_, (uint8, uint32, uint256, uint256, address, uint256)); - if(deadline == 0) deadline = block.timestamp + defaultDeadline; + if (deadline == 0) deadline = block.timestamp + defaultDeadline; return MessageOverrides({ @@ -290,7 +292,7 @@ contract MessageSwitchboard is SwitchboardBase { overrides_, (uint8, uint32, uint256, uint256, uint256, address, uint256) ); - if(deadline == 0) deadline = block.timestamp + defaultDeadline; + if (deadline == 0) deadline = block.timestamp + defaultDeadline; return MessageOverrides({ @@ -328,14 +330,14 @@ contract MessageSwitchboard is SwitchboardBase { // Get destination switchboard ID from sibling config uint32 dstSwitchboardId = siblingSwitchboardIds[dstChainSlug_]; if (dstSwitchboardId == 0) revert SiblingSocketNotFound(); - + // Message payload: origin = (srcChainSlug, srcSwitchboardId), verification = (dstChainSlug, dstSwitchboardId) payloadId = createPayloadId( - chainSlug, // origin chain slug (source) - switchboardId, // origin id (source switchboard) - dstChainSlug_, // verification chain slug (destination) - dstSwitchboardId, // verification id (destination switchboard) - payloadCounter++ // pointer (counter) + chainSlug, // origin chain slug (source) + switchboardId, // origin id (source switchboard) + dstChainSlug_, // verification chain slug (destination) + dstSwitchboardId, // verification id (destination switchboard) + payloadCounter++ // pointer (counter) ); digestParams = DigestParams({ @@ -350,7 +352,7 @@ contract MessageSwitchboard is SwitchboardBase { target: siblingPlugs[dstChainSlug_][plug_], source: abi.encode(chainSlug, toBytes32Format(plug_)), prevBatchDigestHash: bytes32(0), // No longer using triggerId - extraData:bytes("") + extraData: bytes("") }); digest = _createDigest(digestParams); } diff --git a/contracts/utils/Pausable.sol b/contracts/utils/Pausable.sol index aadc803b..304c0563 100644 --- a/contracts/utils/Pausable.sol +++ b/contracts/utils/Pausable.sol @@ -64,4 +64,3 @@ abstract contract Pausable { emit Unpaused(); } } - diff --git a/contracts/utils/common/AccessRoles.sol b/contracts/utils/common/AccessRoles.sol index e665c989..ae8ce145 100644 --- a/contracts/utils/common/AccessRoles.sol +++ b/contracts/utils/common/AccessRoles.sol @@ -12,11 +12,11 @@ bytes32 constant TRANSMITTER_ROLE = keccak256("TRANSMITTER_ROLE"); bytes32 constant WATCHER_ROLE = keccak256("WATCHER_ROLE"); // used to disable switchboard bytes32 constant SWITCHBOARD_DISABLER_ROLE = keccak256("SWITCHBOARD_DISABLER_ROLE"); -// used by fees manager to withdraw native tokens -bytes32 constant FEE_MANAGER_ROLE = keccak256("FEE_MANAGER_ROLE"); +// used by gas manager to withdraw native tokens +bytes32 constant GAS_MANAGER_ROLE = keccak256("GAS_MANAGER_ROLE"); // used by oracle to update minimum message value fees bytes32 constant FEE_UPDATER_ROLE = keccak256("FEE_UPDATER_ROLE"); bytes32 constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); -bytes32 constant UNPAUSER_ROLE = keccak256("UNPAUSER_ROLE"); \ No newline at end of file +bytes32 constant UNPAUSER_ROLE = keccak256("UNPAUSER_ROLE"); diff --git a/contracts/utils/common/Converters.sol b/contracts/utils/common/Converters.sol index 4f290e4b..1ba91a37 100644 --- a/contracts/utils/common/Converters.sol +++ b/contracts/utils/common/Converters.sol @@ -13,3 +13,10 @@ function fromBytes32Format(bytes32 bytes32FormatAddress) pure returns (address) } return address(uint160(uint256(bytes32FormatAddress))); } + +// convert EVM uint256 18 decimals to Solana uint64 6 decimals +function convertToSolanaUint64(uint256 amount) pure returns (uint64) { + uint256 scaledAmount = amount / 10 ** 12; + require(scaledAmount <= type(uint64).max, "Amount exceeds uint64 max"); + return uint64(scaledAmount); +} diff --git a/contracts/utils/common/IdUtils.sol b/contracts/utils/common/IdUtils.sol index 64f45694..909a3c02 100644 --- a/contracts/utils/common/IdUtils.sol +++ b/contracts/utils/common/IdUtils.sol @@ -36,13 +36,16 @@ function createPayloadId( /// @return pointer Counter/pointer value function decodePayloadId( bytes32 payloadId_ -) pure returns ( - uint32 originChainSlug, - uint32 originId, - uint32 verificationChainSlug, - uint32 verificationId, - uint64 pointer -) { +) + pure + returns ( + uint32 originChainSlug, + uint32 originId, + uint32 verificationChainSlug, + uint32 verificationId, + uint64 pointer + ) +{ originChainSlug = uint32(uint256(payloadId_) >> 224); originId = uint32(uint256(payloadId_) >> 192); verificationChainSlug = uint32(uint256(payloadId_) >> 160); @@ -54,7 +57,9 @@ function decodePayloadId( /// @param payloadId_ The payload ID to decode /// @return chainSlug Verification chain slug /// @return switchboardId Verification switchboard ID -function getVerificationInfo(bytes32 payloadId_) pure returns (uint32 chainSlug, uint32 switchboardId) { +function getVerificationInfo( + bytes32 payloadId_ +) pure returns (uint32 chainSlug, uint32 switchboardId) { chainSlug = uint32(uint256(payloadId_) >> 160); switchboardId = uint32(uint256(payloadId_) >> 128); } diff --git a/hardhat-scripts/admin/disconnect.ts b/hardhat-scripts/admin/disconnect.ts index 0c5490e1..c37d7a27 100644 --- a/hardhat-scripts/admin/disconnect.ts +++ b/hardhat-scripts/admin/disconnect.ts @@ -146,7 +146,9 @@ export const updateConfigEVMx = async () => { // update fees manager - const currentGasStation = await gasAccountManagerContract.gasStations(chain); + const currentGasStation = await gasAccountManagerContract.gasStations( + chain + ); if (currentGasStation.toString() === BYTES32_ZERO.toString()) { console.log(`Fees plug already set on ${chain}`); return; diff --git a/hardhat-scripts/admin/rescue.ts b/hardhat-scripts/admin/rescue.ts index fba01c73..98c6d277 100644 --- a/hardhat-scripts/admin/rescue.ts +++ b/hardhat-scripts/admin/rescue.ts @@ -102,7 +102,8 @@ export const main = async () => { const rescueAmount = await tokenInstance.balanceOf(contractAddr[index]); console.log( - `rescueAmount on ${contractAddr[index] + `rescueAmount on ${ + contractAddr[index] } on ${chainSlug} : ${formatUnits(rescueAmount.toString(), 6)}` ); if (rescueAmount.toString() === "0") continue; diff --git a/hardhat-scripts/constants/constants.ts b/hardhat-scripts/constants/constants.ts index e04f3bc6..7768ec29 100644 --- a/hardhat-scripts/constants/constants.ts +++ b/hardhat-scripts/constants/constants.ts @@ -14,4 +14,4 @@ export const BYTES32_ZERO = ethers.constants.HashZero; export const MSG_SB_FEES = "100000000"; export const FEE_MANAGER_WRITE_MAX_FEES = ethers.utils.parseEther("10"); -export const DEFAULT_DEADLINE = 86400; \ No newline at end of file +export const DEFAULT_DEADLINE = 86400; diff --git a/hardhat-scripts/deploy/4.configureEVMx.ts b/hardhat-scripts/deploy/4.configureEVMx.ts index 53b9fc60..22a4ad2e 100644 --- a/hardhat-scripts/deploy/4.configureEVMx.ts +++ b/hardhat-scripts/deploy/4.configureEVMx.ts @@ -146,7 +146,8 @@ const checkAndSetMaxFees = async (evmxAddresses: EVMxAddressesObj) => { const maxFees = maxFeesUpdateArray.map((item) => item.maxFees); let tx = await gasAccountManagerContract.setChainMaxFees(chains, maxFees); console.log( - `Setting Chain Max Fees for chains: ${chains.join(", ")} tx hash: ${tx.hash + `Setting Chain Max Fees for chains: ${chains.join(", ")} tx hash: ${ + tx.hash }` ); await tx.wait(); @@ -166,11 +167,11 @@ export const setWatcherCoreContracts = async ( if ( requestHandlerSet.toLowerCase() !== - evmxAddresses[Contracts.RequestHandler].toLowerCase() || + evmxAddresses[Contracts.RequestHandler].toLowerCase() || PromiseResolverSet.toLowerCase() !== - evmxAddresses[Contracts.PromiseResolver].toLowerCase() || + evmxAddresses[Contracts.PromiseResolver].toLowerCase() || ConfigurationsSet.toLowerCase() !== - evmxAddresses[Contracts.Configurations].toLowerCase() + evmxAddresses[Contracts.Configurations].toLowerCase() ) { console.log("Setting watcher core contracts"); const tx = await watcherContract.setCoreContracts( diff --git a/hardhat-scripts/deploy/8.setupEnv.ts b/hardhat-scripts/deploy/8.setupEnv.ts index fd44cb06..2f3e8fe0 100644 --- a/hardhat-scripts/deploy/8.setupEnv.ts +++ b/hardhat-scripts/deploy/8.setupEnv.ts @@ -36,8 +36,9 @@ const updatedLines = lines.map((line) => { } else if (line.startsWith("ARBITRUM_SOCKET=")) { return `ARBITRUM_SOCKET=${arbSepoliaAddresses[Contracts.Socket]}`; } else if (line.startsWith("ARBITRUM_SWITCHBOARD=")) { - return `ARBITRUM_SWITCHBOARD=${arbSepoliaAddresses[Contracts.FastSwitchboard] - }`; + return `ARBITRUM_SWITCHBOARD=${ + arbSepoliaAddresses[Contracts.FastSwitchboard] + }`; } else if (line.startsWith("ARBITRUM_FEES_PLUG=")) { const gasStation = arbSepoliaAddresses[Contracts.GasStation]; if (gasStation) { diff --git a/hardhat-scripts/test/chainTest.ts b/hardhat-scripts/test/chainTest.ts index 489e59de..37b9c5e7 100644 --- a/hardhat-scripts/test/chainTest.ts +++ b/hardhat-scripts/test/chainTest.ts @@ -108,7 +108,9 @@ class ChainTester { const payloadEscrow = await this.gasAccountManager.getPayloadEscrow( appGatewayAddress ); - const availableFees = await this.gasAccountManager.balanceOf(appGatewayAddress); + const availableFees = await this.gasAccountManager.balanceOf( + appGatewayAddress + ); console.log(`Counter App Gateway: ${appGatewayAddress}`); console.log(`Fees Manager: ${gasAccountManagerAddress}`); @@ -221,7 +223,8 @@ class ChainTester { } } catch (error) { console.log( - ` API error: ${error instanceof Error ? error.message : String(error) + ` API error: ${ + error instanceof Error ? error.message : String(error) }` ); retries++; diff --git a/test/PausableTest.t.sol b/test/PausableTest.t.sol index 6f5e9bb9..d2192ace 100644 --- a/test/PausableTest.t.sol +++ b/test/PausableTest.t.sol @@ -20,105 +20,111 @@ contract PausableTest is Test { address pauser = address(0x2000); address unpauser = address(0x3000); address unauthorized = address(0x4000); - + // Test constants uint32 constant CHAIN_SLUG = 1; string constant VERSION = "test"; - + // Contracts Socket socket; Watcher watcher; - + // Events event Paused(); event Unpaused(); event RoleGranted(bytes32 indexed role, address indexed grantee); event RoleRevoked(bytes32 indexed role, address indexed revokee); - + AddressResolver addressResolver; - + function setUp() public { // Deploy Socket socket = new Socket(CHAIN_SLUG, owner, VERSION); - + ERC1967Factory proxyFactory = new ERC1967Factory(); // Deploy and initialize Watcher Watcher watcherImpl = new Watcher(); - bytes memory data = abi.encodeWithSelector(Watcher.initialize.selector, 1, owner, address(0), address(0), 0); + bytes memory data = abi.encodeWithSelector( + Watcher.initialize.selector, + 1, + owner, + address(0), + address(0), + 0 + ); watcher = Watcher(proxyFactory.deployAndCall(address(watcherImpl), owner, data)); } - + // ==================== Socket Tests ==================== - + function test_Socket_Pause_ByOwner_ShouldRevert() public { vm.prank(owner); vm.expectRevert(); socket.pause(); } - + function test_Socket_Pause_ByPauser_ShouldSucceed() public { vm.prank(owner); socket.grantRole(PAUSER_ROLE, pauser); - + vm.prank(pauser); vm.expectEmit(true, false, false, false); emit Paused(); socket.pause(); - + assertTrue(socket.paused()); } - + function test_Socket_Pause_ByUnauthorized_ShouldRevert() public { vm.prank(unauthorized); vm.expectRevert(abi.encodeWithSelector(AccessControl.NoPermit.selector, PAUSER_ROLE)); socket.pause(); } - + function test_Socket_Unpause_ByOwner_ShouldRevert() public { // First pause it vm.prank(owner); socket.grantRole(PAUSER_ROLE, pauser); vm.prank(pauser); socket.pause(); - + // Try to unpause as owner (should fail) vm.prank(owner); vm.expectRevert(); socket.unpause(); } - + function test_Socket_Unpause_ByUnpauser_ShouldSucceed() public { // First pause it vm.prank(owner); socket.grantRole(PAUSER_ROLE, pauser); vm.prank(pauser); socket.pause(); - + // Grant unpauser role and unpause vm.prank(owner); socket.grantRole(UNPAUSER_ROLE, unpauser); - + vm.prank(unpauser); vm.expectEmit(true, false, false, false); emit Unpaused(); socket.unpause(); - + assertFalse(socket.paused()); } - + function test_Socket_Unpause_ByUnauthorized_ShouldRevert() public { // First pause it vm.prank(owner); socket.grantRole(PAUSER_ROLE, pauser); vm.prank(pauser); socket.pause(); - + // Try to unpause as unauthorized vm.prank(unauthorized); vm.expectRevert(abi.encodeWithSelector(AccessControl.NoPermit.selector, UNPAUSER_ROLE)); socket.unpause(); } - function test_Socket_Execute_WhenPaused_ShouldRevert() public { // Pause the contract @@ -126,7 +132,7 @@ contract PausableTest is Test { socket.grantRole(PAUSER_ROLE, pauser); vm.prank(pauser); socket.pause(); - + ExecuteParams memory executeParams = ExecuteParams({ callType: WRITE, target: address(socket), @@ -150,93 +156,93 @@ contract PausableTest is Test { socket.execute(executeParams, transmissionParams); } // ==================== Watcher Tests ==================== - + function test_Watcher_Initialize_ThenPause() public { // Note: Watcher needs initialization, but for testing pause functionality // we can test the pause mechanism directly // In a real scenario, Watcher would be initialized first - + // For this test, we'll assume Watcher is already initialized // and focus on the pause/unpause functionality - + // Grant pauser role (owner would do this) vm.prank(owner); watcher.grantRole(PAUSER_ROLE, pauser); - + vm.prank(pauser); vm.expectEmit(true, false, false, false); emit Paused(); watcher.pause(); - + assertTrue(watcher.paused()); } - + function test_Watcher_Pause_ByPauser_ShouldSucceed() public { vm.prank(owner); watcher.grantRole(PAUSER_ROLE, pauser); - + vm.prank(pauser); watcher.pause(); - + assertTrue(watcher.paused()); } - + function test_Watcher_Pause_ByUnauthorized_ShouldRevert() public { vm.prank(unauthorized); vm.expectRevert(abi.encodeWithSelector(AccessControl.NoPermit.selector, PAUSER_ROLE)); watcher.pause(); } - + function test_Watcher_Unpause_ByUnpauser_ShouldSucceed() public { // First pause it vm.prank(owner); watcher.grantRole(PAUSER_ROLE, pauser); vm.prank(pauser); watcher.pause(); - + // Grant unpauser role and unpause vm.prank(owner); watcher.grantRole(UNPAUSER_ROLE, unpauser); - + vm.prank(unpauser); vm.expectEmit(true, false, false, false); emit Unpaused(); watcher.unpause(); - + assertFalse(watcher.paused()); } - + function test_Watcher_Unpause_ByUnauthorized_ShouldRevert() public { // First pause it vm.prank(owner); watcher.grantRole(PAUSER_ROLE, pauser); vm.prank(pauser); watcher.pause(); - + // Try to unpause as unauthorized vm.prank(unauthorized); vm.expectRevert(abi.encodeWithSelector(AccessControl.NoPermit.selector, UNPAUSER_ROLE)); watcher.unpause(); } - + function test_Watcher_ExecutePayload_WhenPaused_ShouldRevert() public { // Pause the contract vm.prank(owner); watcher.grantRole(PAUSER_ROLE, pauser); vm.prank(pauser); watcher.pause(); - + // The executePayload function should revert due to whenNotPaused modifier assertTrue(watcher.paused()); } - + function test_Watcher_ResolvePayload_WhenPaused_ShouldRevert() public { // Pause the contract vm.prank(owner); watcher.grantRole(PAUSER_ROLE, pauser); vm.prank(pauser); watcher.pause(); - + // The resolvePayload function should revert due to whenNotPaused modifier assertTrue(watcher.paused()); } @@ -247,9 +253,9 @@ contract PausableTest is Test { watcher.grantRole(PAUSER_ROLE, pauser); vm.prank(pauser); watcher.pause(); - + vm.expectRevert(abi.encodeWithSelector(Pausable.ContractPaused.selector)); - watcher.executePayload(); + watcher.executePayload(); } function test_Watcher_resolvePayload_WhenPaused_ShouldRevert() public { @@ -258,14 +264,15 @@ contract PausableTest is Test { watcher.grantRole(PAUSER_ROLE, pauser); vm.prank(pauser); watcher.pause(); - + vm.expectRevert(abi.encodeWithSelector(Pausable.ContractPaused.selector)); - watcher.resolvePayload(WatcherMultiCallParams({ - contractAddress: address(watcher), - data: "0x", - nonce: 0, - signature: bytes("0x") - })); + watcher.resolvePayload( + WatcherMultiCallParams({ + contractAddress: address(watcher), + data: "0x", + nonce: 0, + signature: bytes("0x") + }) + ); } } - diff --git a/test/SocketPayloadIdVerification.t.sol b/test/SocketPayloadIdVerification.t.sol index 19dec4a6..8bf5b02b 100644 --- a/test/SocketPayloadIdVerification.t.sol +++ b/test/SocketPayloadIdVerification.t.sol @@ -29,33 +29,33 @@ contract SocketPayloadIdVerificationTest is Test { uint32 constant OTHER_CHAIN_SLUG = 2; uint32 constant EVMX_CHAIN_SLUG = 100; uint32 constant WATCHER_ID = 1; - + address owner = address(0x1000); address plugOwner = address(0x2000); - + Socket socket; FastSwitchboard fastSwitchboard; MessageSwitchboard messageSwitchboard; MockPlug mockPlug; - + function setUp() public { // Deploy Socket socket = new Socket(CHAIN_SLUG, owner, "1.0.0"); - + // Deploy switchboards fastSwitchboard = new FastSwitchboard(CHAIN_SLUG, socket, owner); messageSwitchboard = new MessageSwitchboard(CHAIN_SLUG, socket, owner); - + // Register switchboards vm.startPrank(owner); fastSwitchboard.registerSwitchboard(); messageSwitchboard.registerSwitchboard(); vm.stopPrank(); - + // Create a mock plug uint32 switchboardId = fastSwitchboard.switchboardId(); mockPlug = new MockPlug(address(socket), switchboardId); - + // Connect plug to socket vm.prank(plugOwner); mockPlug.connectToSocket(address(socket), switchboardId); @@ -69,13 +69,13 @@ contract SocketPayloadIdVerificationTest is Test { // Create a valid payload ID for this chain and switchboard uint32 switchboardId = fastSwitchboard.switchboardId(); bytes32 payloadId = createPayloadId( - OTHER_CHAIN_SLUG, // origin chain slug - 100, // origin switchboard id - CHAIN_SLUG, // verification chain slug (matches socket) - uint32(switchboardId), // verification switchboard id (matches plug's switchboard) - 12345 // pointer + OTHER_CHAIN_SLUG, // origin chain slug + 100, // origin switchboard id + CHAIN_SLUG, // verification chain slug (matches socket) + uint32(switchboardId), // verification switchboard id (matches plug's switchboard) + 12345 // pointer ); - + // Create ExecuteParams with valid payload ID ExecuteParams memory executeParams = ExecuteParams({ callType: WRITE, @@ -89,21 +89,21 @@ contract SocketPayloadIdVerificationTest is Test { source: abi.encode(OTHER_CHAIN_SLUG, toBytes32Format(address(0x1234))), extraData: bytes("") }); - + TransmissionParams memory transmissionParams = TransmissionParams({ socketFees: 0, refundAddress: address(0), extraData: bytes(""), transmitterProof: bytes("") }); - + // Verify that payload ID check passes (doesn't revert with InvalidVerificationChainSlug or InvalidVerificationSwitchboardId) // The execution should proceed past payload ID verification to the switchboard's allowPayload check. // It will fail with InvalidSource because the source doesn't match the plug's appGatewayId. // This confirms payload ID verification passed - we reached allowPayload which comes after payload ID check. vm.expectRevert(FastSwitchboard.InvalidSource.selector); socket.execute{value: 0}(executeParams, transmissionParams); - + // If we get InvalidSource, it means: // 1. ✅ Payload ID verification passed (didn't revert with InvalidVerificationChainSlug/InvalidVerificationSwitchboardId) // 2. ✅ We reached the switchboard's allowPayload check (comes after payload ID verification) @@ -116,11 +116,11 @@ contract SocketPayloadIdVerificationTest is Test { bytes32 payloadId = createPayloadId( OTHER_CHAIN_SLUG, 100, - OTHER_CHAIN_SLUG, // Wrong chain slug (doesn't match socket's chainSlug) + OTHER_CHAIN_SLUG, // Wrong chain slug (doesn't match socket's chainSlug) uint32(switchboardId), 12345 ); - + ExecuteParams memory executeParams = ExecuteParams({ callType: WRITE, payloadId: payloadId, @@ -133,14 +133,14 @@ contract SocketPayloadIdVerificationTest is Test { source: abi.encode(OTHER_CHAIN_SLUG, toBytes32Format(address(0x1234))), extraData: bytes("") }); - + TransmissionParams memory transmissionParams = TransmissionParams({ socketFees: 0, refundAddress: address(0), extraData: bytes(""), transmitterProof: bytes("") }); - + vm.expectRevert(Socket.InvalidVerificationChainSlug.selector); socket.execute{value: 0}(executeParams, transmissionParams); } @@ -150,11 +150,11 @@ contract SocketPayloadIdVerificationTest is Test { bytes32 payloadId = createPayloadId( OTHER_CHAIN_SLUG, 100, - CHAIN_SLUG, // Correct chain slug - 999, // Wrong switchboard ID (doesn't match plug's switchboard) + CHAIN_SLUG, // Correct chain slug + 999, // Wrong switchboard ID (doesn't match plug's switchboard) 12345 ); - + ExecuteParams memory executeParams = ExecuteParams({ callType: WRITE, payloadId: payloadId, @@ -167,14 +167,14 @@ contract SocketPayloadIdVerificationTest is Test { source: abi.encode(OTHER_CHAIN_SLUG, toBytes32Format(address(0x1234))), extraData: bytes("") }); - + TransmissionParams memory transmissionParams = TransmissionParams({ socketFees: 0, refundAddress: address(0), extraData: bytes(""), transmitterProof: bytes("") }); - + vm.expectRevert(Socket.InvalidVerificationSwitchboardId.selector); socket.execute{value: 0}(executeParams, transmissionParams); } @@ -187,19 +187,19 @@ contract SocketPayloadIdVerificationTest is Test { // Set EVMX config vm.prank(owner); fastSwitchboard.setEvmxConfig(EVMX_CHAIN_SLUG, WATCHER_ID); - + // Create a mock plug uint32 switchboardId = fastSwitchboard.switchboardId(); MockPlug triggerPlug = new MockPlug(address(socket), switchboardId); vm.prank(plugOwner); triggerPlug.connectToSocket(address(socket), switchboardId); - + bytes memory payload = abi.encode("test trigger"); bytes memory overrides = bytes(""); - + // Get counter before uint64 counterBefore = fastSwitchboard.triggerPayloadCounter(); - + // Call processPayload (must be called by socket) vm.prank(address(socket)); bytes32 payloadId = fastSwitchboard.processPayload{value: 0}( @@ -207,10 +207,10 @@ contract SocketPayloadIdVerificationTest is Test { payload, overrides ); - + // Verify counter incremented assertEq(fastSwitchboard.triggerPayloadCounter(), counterBefore + 1); - + // Verify payload ID structure ( uint32 originChainSlug, @@ -219,7 +219,7 @@ contract SocketPayloadIdVerificationTest is Test { uint32 verificationId, uint64 pointer ) = decodePayloadId(payloadId); - + assertEq(originChainSlug, CHAIN_SLUG, "Origin chain slug should match source"); assertEq(originId, uint32(switchboardId), "Origin ID should match switchboard ID"); assertEq(verificationChainSlug, EVMX_CHAIN_SLUG, "Verification chain slug should be EVMX"); @@ -231,16 +231,16 @@ contract SocketPayloadIdVerificationTest is Test { // Set EVMX config vm.prank(owner); fastSwitchboard.setEvmxConfig(EVMX_CHAIN_SLUG, WATCHER_ID); - + // Create a mock plug uint32 switchboardId = fastSwitchboard.switchboardId(); MockPlug triggerPlug = new MockPlug(address(socket), switchboardId); vm.prank(plugOwner); triggerPlug.connectToSocket(address(socket), switchboardId); - + bytes memory payload = abi.encode("test trigger"); bytes memory overrides = bytes(""); - + // Get counter before to calculate expected payload ID uint64 counterBefore = fastSwitchboard.triggerPayloadCounter(); bytes32 expectedPayloadId = createPayloadId( @@ -250,7 +250,7 @@ contract SocketPayloadIdVerificationTest is Test { WATCHER_ID, counterBefore ); - + // Expect PayloadRequested event vm.expectEmit(true, true, true, false); emit PayloadRequested( @@ -260,14 +260,10 @@ contract SocketPayloadIdVerificationTest is Test { overrides, payload ); - + // Call processPayload vm.prank(address(socket)); - fastSwitchboard.processPayload{value: 0}( - address(triggerPlug), - payload, - overrides - ); + fastSwitchboard.processPayload{value: 0}(address(triggerPlug), payload, overrides); } function test_FastSwitchboard_ProcessPayload_EvmxConfigNotSet_Reverts() public { @@ -276,44 +272,40 @@ contract SocketPayloadIdVerificationTest is Test { MockPlug triggerPlug = new MockPlug(address(socket), switchboardId); vm.prank(plugOwner); triggerPlug.connectToSocket(address(socket), switchboardId); - + bytes memory payload = abi.encode("test trigger"); bytes memory overrides = bytes(""); - + vm.prank(address(socket)); vm.expectRevert(FastSwitchboard.EvmxConfigNotSet.selector); - fastSwitchboard.processPayload{value: 0}( - address(triggerPlug), - payload, - overrides - ); + fastSwitchboard.processPayload{value: 0}(address(triggerPlug), payload, overrides); } function test_FastSwitchboard_ProcessPayload_CounterIncrements() public { // Set EVMX config vm.prank(owner); fastSwitchboard.setEvmxConfig(EVMX_CHAIN_SLUG, WATCHER_ID); - + uint32 switchboardId = fastSwitchboard.switchboardId(); MockPlug triggerPlug = new MockPlug(address(socket), switchboardId); vm.prank(plugOwner); triggerPlug.connectToSocket(address(socket), switchboardId); - + bytes memory payload = abi.encode("test"); bytes memory overrides = bytes(""); - + uint64 counter1 = fastSwitchboard.triggerPayloadCounter(); - + vm.prank(address(socket)); fastSwitchboard.processPayload{value: 0}(address(triggerPlug), payload, overrides); - + uint64 counter2 = fastSwitchboard.triggerPayloadCounter(); - + vm.prank(address(socket)); fastSwitchboard.processPayload{value: 0}(address(triggerPlug), payload, overrides); - + uint64 counter3 = fastSwitchboard.triggerPayloadCounter(); - + assertEq(counter2, counter1 + 1, "Counter should increment"); assertEq(counter3, counter2 + 1, "Counter should increment again"); } @@ -322,34 +314,64 @@ contract SocketPayloadIdVerificationTest is Test { // Set EVMX config vm.prank(owner); fastSwitchboard.setEvmxConfig(EVMX_CHAIN_SLUG, WATCHER_ID); - + uint32 switchboardId = fastSwitchboard.switchboardId(); MockPlug triggerPlug = new MockPlug(address(socket), switchboardId); vm.prank(plugOwner); triggerPlug.connectToSocket(address(socket), switchboardId); - + bytes memory payload = abi.encode("test"); bytes memory overrides = bytes(""); - + vm.prank(address(socket)); - bytes32 payloadId1 = fastSwitchboard.processPayload{value: 0}(address(triggerPlug), payload, overrides); - + bytes32 payloadId1 = fastSwitchboard.processPayload{value: 0}( + address(triggerPlug), + payload, + overrides + ); + vm.prank(address(socket)); - bytes32 payloadId2 = fastSwitchboard.processPayload{value: 0}(address(triggerPlug), payload, overrides); - + bytes32 payloadId2 = fastSwitchboard.processPayload{value: 0}( + address(triggerPlug), + payload, + overrides + ); + vm.prank(address(socket)); - bytes32 payloadId3 = fastSwitchboard.processPayload{value: 0}(address(triggerPlug), payload, overrides); - + bytes32 payloadId3 = fastSwitchboard.processPayload{value: 0}( + address(triggerPlug), + payload, + overrides + ); + // All should be unique assertNotEq(payloadId1, payloadId2, "Payload IDs should be unique"); assertNotEq(payloadId2, payloadId3, "Payload IDs should be unique"); assertNotEq(payloadId1, payloadId3, "Payload IDs should be unique"); - + // Verify they only differ in pointer - (uint32 origin1, uint32 originId1, uint32 verif1, uint32 verifId1, uint64 pointer1) = decodePayloadId(payloadId1); - (uint32 origin2, uint32 originId2, uint32 verif2, uint32 verifId2, uint64 pointer2) = decodePayloadId(payloadId2); - (uint32 origin3, uint32 originId3, uint32 verif3, uint32 verifId3, uint64 pointer3) = decodePayloadId(payloadId3); - + ( + uint32 origin1, + uint32 originId1, + uint32 verif1, + uint32 verifId1, + uint64 pointer1 + ) = decodePayloadId(payloadId1); + ( + uint32 origin2, + uint32 originId2, + uint32 verif2, + uint32 verifId2, + uint64 pointer2 + ) = decodePayloadId(payloadId2); + ( + uint32 origin3, + uint32 originId3, + uint32 verif3, + uint32 verifId3, + uint64 pointer3 + ) = decodePayloadId(payloadId3); + assertEq(origin1, origin2); assertEq(origin1, origin3); assertEq(originId1, originId2); @@ -358,7 +380,7 @@ contract SocketPayloadIdVerificationTest is Test { assertEq(verif1, verif3); assertEq(verifId1, verifId2); assertEq(verifId1, verifId3); - + // Only pointers should differ assertEq(pointer2, pointer1 + 1); assertEq(pointer3, pointer2 + 1); @@ -369,14 +391,13 @@ contract SocketPayloadIdVerificationTest is Test { vm.prank(address(0x9999)); vm.expectRevert(); fastSwitchboard.setEvmxConfig(EVMX_CHAIN_SLUG, WATCHER_ID); - + // Owner should be able to set vm.prank(owner); fastSwitchboard.setEvmxConfig(EVMX_CHAIN_SLUG, WATCHER_ID); - + // Verify it was set assertEq(fastSwitchboard.evmxChainSlug(), EVMX_CHAIN_SLUG); assertEq(fastSwitchboard.watcherId(), WATCHER_ID); } } - diff --git a/test/mocks/MockPlug.sol b/test/mocks/MockPlug.sol index 620386e4..4a186cc4 100644 --- a/test/mocks/MockPlug.sol +++ b/test/mocks/MockPlug.sol @@ -6,10 +6,8 @@ import "../../contracts/protocol/base/MessagePlugBase.sol"; contract MockPlug is MessagePlugBase { uint32 public chainSlug; bytes32 public triggerId; - - constructor(address socket_, uint32 switchboardId_) MessagePlugBase(socket_, switchboardId_) { - } - + + constructor(address socket_, uint32 switchboardId_) MessagePlugBase(socket_, switchboardId_) {} function setSocket(address socket_) external { _setSocket(socket_); @@ -42,7 +40,7 @@ contract MockPlug is MessagePlugBase { } // Method to connect to socket - function connectToSocket(address socket_,uint32 switchboardId_) external { + function connectToSocket(address socket_, uint32 switchboardId_) external { _setSocket(socket_); switchboardId = switchboardId_; socket__.connect(switchboardId_, ""); diff --git a/test/switchboard/MessageSwitchboard.t.sol b/test/switchboard/MessageSwitchboard.t.sol index c4fa9579..f00d0ef0 100644 --- a/test/switchboard/MessageSwitchboard.t.sol +++ b/test/switchboard/MessageSwitchboard.t.sol @@ -85,7 +85,7 @@ contract MessageSwitchboardTest is Test, Utils { vm.stopPrank(); uint32 switchboardId = messageSwitchboard.switchboardId(); - + // Socket automatically stores switchboard address, no manual setting needed // Now create plugs with the registered switchboard ID @@ -97,8 +97,7 @@ contract MessageSwitchboardTest is Test, Utils { function getWatcherAddress() public pure returns (address) { return vm.addr(0x1111111111111111111111111111111111111111111111111111111111111111); } - - + /** * @dev Calculate digest based on MessageSwitchboard's _createDigest logic * @param digestParams The digest parameters @@ -142,9 +141,19 @@ contract MessageSwitchboardTest is Test, Utils { bytes32 siblingSwitchboard = toBytes32Format(address(0x5678)); uint32 siblingSwitchboardId = 1; // Mock switchboard ID for destination vm.startPrank(owner); - messageSwitchboard.setSiblingConfig(DST_CHAIN, siblingSocket, siblingSwitchboard, siblingSwitchboardId); + messageSwitchboard.setSiblingConfig( + DST_CHAIN, + siblingSocket, + siblingSwitchboard, + siblingSwitchboardId + ); // Also set config for reverse direction - messageSwitchboard.setSiblingConfig(SRC_CHAIN, toBytes32Format(address(socket)), toBytes32Format(address(messageSwitchboard)), uint32(messageSwitchboard.switchboardId())); + messageSwitchboard.setSiblingConfig( + SRC_CHAIN, + toBytes32Format(address(socket)), + toBytes32Format(address(messageSwitchboard)), + uint32(messageSwitchboard.switchboardId()) + ); vm.stopPrank(); } @@ -185,14 +194,14 @@ contract MessageSwitchboardTest is Test, Utils { srcPlug.setOverrides(overrides); bytes memory payload = abi.encode(payloadData); - + // Get counter before the call uint64 payloadCounterBefore = messageSwitchboard.payloadCounter(); - + // Use MockPlug to trigger Socket - this returns the payloadId vm.deal(address(srcPlug), 10 ether); payloadId = srcPlug.triggerSocket{value: msgValue}(payload); - + // Verify payloadId matches expected bytes32 expectedPayloadId = _getLastPayloadId(payloadCounterBefore); assertEq(payloadId, expectedPayloadId, "PayloadId mismatch"); @@ -222,13 +231,13 @@ contract MessageSwitchboardTest is Test, Utils { srcPlug.setOverrides(overrides); bytes memory payload = abi.encode(payloadData); - + // Get counter before the call uint64 payloadCounterBefore = messageSwitchboard.payloadCounter(); - + // Use MockPlug to trigger Socket - this returns the payloadId payloadId = srcPlug.triggerSocket(payload); - + // Verify payloadId matches expected bytes32 expectedPayloadId = _getLastPayloadId(payloadCounterBefore); assertEq(payloadId, expectedPayloadId, "PayloadId mismatch"); @@ -243,7 +252,7 @@ contract MessageSwitchboardTest is Test, Utils { * @return digestParams The constructed DigestParams */ function _createDigestParams( - bytes32 payloadId, + bytes32 payloadId, bytes memory payload, address, // Unused parameter, kept for compatibility uint256 gasLimit_, @@ -252,21 +261,22 @@ contract MessageSwitchboardTest is Test, Utils { // Get sibling socket from switchboard (matches what contract uses) bytes32 siblingSocket = messageSwitchboard.siblingSockets(DST_CHAIN); bytes32 siblingPlug = messageSwitchboard.siblingPlugs(DST_CHAIN, address(srcPlug)); - - return DigestParams({ - socket: siblingSocket, - transmitter: bytes32(0), - payloadId: payloadId, - deadline: block.timestamp + 3600, - callType: WRITE, - gasLimit: gasLimit_, - value: value_, - payload: payload, - target: siblingPlug, - source: abi.encode(SRC_CHAIN, toBytes32Format(address(srcPlug))), - prevBatchDigestHash: bytes32(0), // No longer using triggerId - extraData: bytes("") // Contract now sets extraData to empty - }); + + return + DigestParams({ + socket: siblingSocket, + transmitter: bytes32(0), + payloadId: payloadId, + deadline: block.timestamp + 3600, + callType: WRITE, + gasLimit: gasLimit_, + value: value_, + payload: payload, + target: siblingPlug, + source: abi.encode(SRC_CHAIN, toBytes32Format(address(srcPlug))), + prevBatchDigestHash: bytes32(0), // No longer using triggerId + extraData: bytes("") // Contract now sets extraData to empty + }); } /** @@ -275,7 +285,10 @@ contract MessageSwitchboardTest is Test, Utils { * @param payload The payload data * @return digestParams The constructed DigestParams */ - function _createDigestParams(bytes32 payloadId, bytes memory payload) internal view returns (DigestParams memory) { + function _createDigestParams( + bytes32 payloadId, + bytes memory payload + ) internal view returns (DigestParams memory) { return _createDigestParams(payloadId, payload, address(dstPlug), 100000, 0); } @@ -288,13 +301,14 @@ contract MessageSwitchboardTest is Test, Utils { // Calculate payload ID using new structure // Message payload: origin = (srcChainSlug, srcSwitchboardId), verification = (dstChainSlug, dstSwitchboardId) uint32 dstSwitchboardId = messageSwitchboard.siblingSwitchboardIds(DST_CHAIN); - return createPayloadId( - SRC_CHAIN, - uint32(messageSwitchboard.switchboardId()), - DST_CHAIN, - dstSwitchboardId, - payloadCounterBefore - ); + return + createPayloadId( + SRC_CHAIN, + uint32(messageSwitchboard.switchboardId()), + DST_CHAIN, + dstSwitchboardId, + payloadCounterBefore + ); } /** @@ -347,15 +361,20 @@ contract MessageSwitchboardTest is Test, Utils { function test_setSiblingConfig_Success() public { bytes32 siblingSocket = toBytes32Format(address(0x1234)); bytes32 siblingSwitchboard = toBytes32Format(address(0x5678)); - + uint32 siblingSwitchboardId = 1; // Mock switchboard ID - + vm.expectEmit(true, true, true, false); emit SiblingConfigSet(DST_CHAIN, siblingSocket, siblingSwitchboard); vm.prank(owner); - messageSwitchboard.setSiblingConfig(DST_CHAIN, siblingSocket, siblingSwitchboard, siblingSwitchboardId); - + messageSwitchboard.setSiblingConfig( + DST_CHAIN, + siblingSocket, + siblingSwitchboard, + siblingSwitchboardId + ); + assertEq(messageSwitchboard.siblingSockets(DST_CHAIN), siblingSocket); assertEq(messageSwitchboard.siblingSwitchboards(DST_CHAIN), siblingSwitchboard); assertEq(messageSwitchboard.siblingSwitchboardIds(DST_CHAIN), siblingSwitchboardId); @@ -415,10 +434,10 @@ contract MessageSwitchboardTest is Test, Utils { bytes memory payload = abi.encode("test data"); uint256 msgValue = MIN_FEES + 0.001 ether; - + // Get counter before the call uint64 payloadCounterBefore = messageSwitchboard.payloadCounter(); - + // Calculate expected payload ID using new structure uint32 dstSwitchboardId = messageSwitchboard.siblingSwitchboardIds(DST_CHAIN); bytes32 expectedPayloadId = createPayloadId( @@ -428,7 +447,7 @@ contract MessageSwitchboardTest is Test, Utils { dstSwitchboardId, payloadCounterBefore ); - + // Expect MessageOutbound event first (contract emits this before PayloadRequested) // Only check indexed fields (payloadId, dstChainSlug) - struct fields may differ due to deadline timing vm.expectEmit(true, true, false, false); @@ -436,7 +455,7 @@ contract MessageSwitchboardTest is Test, Utils { expectedPayloadId, DST_CHAIN, bytes32(0), // digest - not checked (might differ due to deadline timing) - DigestParams({ // Only structure matters, values not checked + DigestParams({ // Only structure matters, values not checked socket: bytes32(0), transmitter: bytes32(0), payloadId: bytes32(0), @@ -455,7 +474,7 @@ contract MessageSwitchboardTest is Test, Utils { 0, address(0) ); - + // Expect PayloadRequested event second vm.expectEmit(true, true, true, false); emit PayloadRequested( @@ -465,13 +484,13 @@ contract MessageSwitchboardTest is Test, Utils { overrides, payload ); - + vm.deal(address(srcPlug), 10 ether); bytes32 actualPayloadId = srcPlug.triggerSocket{value: msgValue}(payload); - + // Verify payload ID matches assertEq(actualPayloadId, expectedPayloadId); - + // Verify payload counter increased assertEq(messageSwitchboard.payloadCounter(), payloadCounterBefore + 1); @@ -540,10 +559,10 @@ contract MessageSwitchboardTest is Test, Utils { ); bytes memory payload = abi.encode("sponsored test"); - + // Get counter before the call uint64 payloadCounterBefore = messageSwitchboard.payloadCounter(); - + // Calculate expected payload ID using new structure uint32 dstSwitchboardId = messageSwitchboard.siblingSwitchboardIds(DST_CHAIN); bytes32 expectedPayloadId = createPayloadId( @@ -553,10 +572,10 @@ contract MessageSwitchboardTest is Test, Utils { dstSwitchboardId, payloadCounterBefore ); - + // Set overrides on the plug srcPlug.setOverrides(overrides); - + // Expect MessageOutbound event first (contract emits this before PayloadRequested) // Only check indexed fields (payloadId, dstChainSlug, sponsor) - skip data fields for struct comparison vm.expectEmit(true, true, false, false); @@ -576,14 +595,14 @@ contract MessageSwitchboardTest is Test, Utils { target: bytes32(0), source: bytes(""), prevBatchDigestHash: bytes32(0), - extraData: bytes("") + extraData: bytes("") }), true, // isSponsored 0, 10 ether, sponsor ); - + // Expect PayloadRequested event second vm.expectEmit(true, true, true, false); emit PayloadRequested( @@ -593,13 +612,13 @@ contract MessageSwitchboardTest is Test, Utils { overrides, payload ); - + vm.prank(address(srcPlug)); bytes32 actualPayloadId = srcPlug.triggerSocket(payload); - + // Verify payload ID matches assertEq(actualPayloadId, expectedPayloadId); - + // Verify sponsored fees were stored (uint256 maxFees, ) = messageSwitchboard.sponsoredPayloadFees(expectedPayloadId); assertEq(maxFees, 10 ether); @@ -610,7 +629,15 @@ contract MessageSwitchboardTest is Test, Utils { _setupSiblingConfig(); // Don't approve - try without approval - bytes memory overrides = abi.encode(uint8(2), DST_CHAIN, 100000, 0, 10 ether, sponsor, 86400); + bytes memory overrides = abi.encode( + uint8(2), + DST_CHAIN, + 100000, + 0, + 10 ether, + sponsor, + 86400 + ); // Set overrides on the plug srcPlug.setOverrides(overrides); @@ -642,9 +669,9 @@ contract MessageSwitchboardTest is Test, Utils { // Create digest params (using any valid values since we're just testing attestation) bytes memory payload = abi.encode("test"); bytes32 payloadId = bytes32(uint256(0x5678)); - + DigestParams memory digestParams = _createDigestParams(payloadId, payload); - + // Calculate the actual digest from digestParams (as done in MessageSwitchboard._createDigest) bytes32 digest = calculateDigest(digestParams); @@ -663,17 +690,17 @@ contract MessageSwitchboardTest is Test, Utils { // Verify it's attested assertTrue(messageSwitchboard.isAttested(digest)); } - + // NOTE: test_attest_InvalidTarget_Reverts() was removed because the attest() function // no longer validates the target - target validation is now done during execution - + function test_attest_InvalidWatcher_Reverts() public { // Setup sibling config _setupSiblingConfig(); bytes32 payloadId = bytes32(uint256(0x5678)); DigestParams memory digestParams = _createDigestParams(payloadId, abi.encode("test")); - + // Calculate the actual digest from digestParams bytes32 digest = calculateDigest(digestParams); @@ -697,7 +724,7 @@ contract MessageSwitchboardTest is Test, Utils { bytes32 payloadId = bytes32(uint256(0x5678)); DigestParams memory digestParams = _createDigestParams(payloadId, abi.encode("test")); - + // Calculate the actual digest from digestParams bytes32 digest = calculateDigest(digestParams); @@ -944,7 +971,7 @@ contract MessageSwitchboardTest is Test, Utils { bytes memory feesData = abi.encode(uint8(1)); // Native fees type uint256 additionalFees = 0.01 ether; uint256 initialFees = MIN_FEES + 0.001 ether; - + // First create a payload via processPayload bytes memory overrides = abi.encode( uint8(1), // version @@ -960,14 +987,14 @@ contract MessageSwitchboardTest is Test, Utils { // Set overrides on the plug srcPlug.setOverrides(overrides); - + // Get counter before creating payload uint64 payloadCounterBefore = messageSwitchboard.payloadCounter(); - + vm.deal(address(srcPlug), 1 ether); vm.prank(address(srcPlug)); bytes32 payloadId = srcPlug.triggerSocket{value: initialFees}(abi.encode("payload")); - + // Verify payloadId matches expected structure uint32 dstSwitchboardId = messageSwitchboard.siblingSwitchboardIds(DST_CHAIN); bytes32 expectedPayloadId = createPayloadId( @@ -978,7 +1005,7 @@ contract MessageSwitchboardTest is Test, Utils { payloadCounterBefore ); assertEq(payloadId, expectedPayloadId, "PayloadId mismatch"); - + // Verify initial fees were stored (uint256 nativeFeesBefore, , , , ) = messageSwitchboard.payloadFees(payloadId); assertEq(nativeFeesBefore, initialFees); @@ -1001,7 +1028,7 @@ contract MessageSwitchboardTest is Test, Utils { uint256 newMaxFees = 0.05 ether; bytes memory feesData = abi.encode(uint8(2), newMaxFees); // Sponsored fees type + new maxFees - + // First create a sponsored payload via processPayload bytes memory overrides = abi.encode( uint8(2), // version @@ -1015,13 +1042,13 @@ contract MessageSwitchboardTest is Test, Utils { // Set overrides on the plug srcPlug.setOverrides(overrides); - + // Get counter before creating payload uint64 payloadCounterBefore = messageSwitchboard.payloadCounter(); - + vm.prank(address(srcPlug)); bytes32 payloadId = srcPlug.triggerSocket(abi.encode("payload")); - + // Verify payloadId matches expected structure uint32 dstSwitchboardId = messageSwitchboard.siblingSwitchboardIds(DST_CHAIN); bytes32 expectedPayloadId = createPayloadId( @@ -1032,7 +1059,7 @@ contract MessageSwitchboardTest is Test, Utils { payloadCounterBefore ); assertEq(payloadId, expectedPayloadId, "PayloadId mismatch"); - + // Verify initial maxFees were stored (uint256 maxFeesBefore, ) = messageSwitchboard.sponsoredPayloadFees(payloadId); assertEq(maxFeesBefore, 0.02 ether); @@ -1072,14 +1099,14 @@ contract MessageSwitchboardTest is Test, Utils { // Set overrides on the plug srcPlug.setOverrides(overrides); - + // Get counter before creating payload uint64 payloadCounterBefore = messageSwitchboard.payloadCounter(); - + vm.deal(address(srcPlug), 1 ether); vm.prank(address(srcPlug)); bytes32 payloadId = srcPlug.triggerSocket{value: initialFees}(abi.encode("payload")); - + // Verify payloadId matches expected structure uint32 dstSwitchboardId = messageSwitchboard.siblingSwitchboardIds(DST_CHAIN); bytes32 expectedPayloadId = createPayloadId( @@ -1090,7 +1117,7 @@ contract MessageSwitchboardTest is Test, Utils { payloadCounterBefore ); assertEq(payloadId, expectedPayloadId, "PayloadId mismatch"); - + // Try to increase fees with different plug - should revert because plug doesn't match vm.deal(address(dstPlug), 1 ether); vm.expectRevert(MessageSwitchboard.UnauthorizedFeeIncrease.selector); From f5b18cfab3dd21af63fd6987357c0f5921cac78a Mon Sep 17 00:00:00 2001 From: Ameesha Agrawal Date: Wed, 12 Nov 2025 18:44:28 +0530 Subject: [PATCH 04/12] feat: gas escrow --- contracts/evmx/base/AppGatewayBase.sol | 3 +- contracts/evmx/fees/GasEscrow.sol | 86 ++++++++++++++++++++++++ contracts/evmx/fees/GasVault.sol | 9 ++- contracts/evmx/helpers/AsyncDeployer.sol | 6 +- contracts/evmx/helpers/AsyncPromise.sol | 6 +- contracts/evmx/interfaces/IGasEscrow.sol | 46 +++++++++++++ contracts/utils/common/Structs.sol | 14 ++++ 7 files changed, 161 insertions(+), 9 deletions(-) create mode 100644 contracts/evmx/fees/GasEscrow.sol diff --git a/contracts/evmx/base/AppGatewayBase.sol b/contracts/evmx/base/AppGatewayBase.sol index 1ff8d021..16b61348 100644 --- a/contracts/evmx/base/AppGatewayBase.sol +++ b/contracts/evmx/base/AppGatewayBase.sol @@ -111,7 +111,8 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway { return bytes32(0); } - onChainAddress = IForwarder(forwarderAddresses[contractId_][chainSlug_]).getOnChainAddress(); + onChainAddress = IForwarder(forwarderAddresses[contractId_][chainSlug_]) + .getOnChainAddress(); } //////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/contracts/evmx/fees/GasEscrow.sol b/contracts/evmx/fees/GasEscrow.sol new file mode 100644 index 00000000..38ee2223 --- /dev/null +++ b/contracts/evmx/fees/GasEscrow.sol @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import "../interfaces/IGasEscrow.sol"; + +/// @title Gas Escrow Manager +/// @notice Tracks escrowed gas during request lifecycle +/// @dev Separates escrow logic from token logic for clarity +contract GasEscrow is IGasEscrow { + address public gasAccountManager; + + /// @notice Tracks escrowed gas per account + mapping(address => uint256) public accountEscrow; + + /// @notice Tracks escrowed gas per request + mapping(bytes32 => EscrowEntry) public payloadEscrow; + + error NotGasAccountManager(); + + modifier onlyGasAccountManager() { + if (msg.sender != gasAccountManager) revert NotGasAccountManager(); + _; + } + + /// @notice Escrow gas for a request + function escrowGas( + bytes32 payloadId_, + address consumeFrom_, + uint256 amount_ + ) external onlyGasAccountManager { + accountEscrow[consumeFrom_] += amount_; + payloadEscrow[payloadId_] = EscrowEntry({ + account: consumeFrom_, + amount: amount_, + timestamp: block.timestamp, + state: EscrowState.Active + }); + emit GasEscrowed(payloadId_, consumeFrom_, amount_); + } + + /// @notice Release escrow back to account + function releaseEscrow(bytes32 payloadId) external onlyGasAccountManager { + EscrowEntry storage entry = payloadEscrow[payloadId]; + require(entry.state == EscrowState.Active, "Not active"); + + accountEscrow[entry.account] -= entry.amount; + entry.state = EscrowState.Released; + + emit EscrowReleased(payloadId, entry.account); + } + + /// @notice Mark escrow as settled (paid to transmitter) + function settleGasPayment( + bytes32 payloadId, + address transmitter + ) external onlyGasAccountManager { + EscrowEntry storage entry = payloadEscrow[payloadId]; + require(entry.state == EscrowState.Active, "Not active"); + + accountEscrow[entry.account] -= entry.amount; + entry.state = EscrowState.Settled; + + emit EscrowSettled(payloadId, entry.account, transmitter, entry.amount); + } + + /// @notice Get total escrowed amount for an account + function getEscrowedAmount(address account) external view returns (uint256) { + return accountEscrow[account]; + } + + /// @notice Get request escrow details + function getPayloadEscrow(bytes32 payloadId) external view returns (EscrowEntry memory) { + return payloadEscrow[payloadId]; + } + + /** + * @notice Rescues funds from the contract if they are locked by mistake. This contract does not + * theoretically need this function but it is added for safety. + * @param token_ The address of the token contract. + * @param rescueTo_ The address where rescued tokens need to be sent. + * @param amount_ The amount of tokens to be rescued. + */ + function rescueFunds(address token_, address rescueTo_, uint256 amount_) external onlyWatcher { + RescueFundsLib._rescueFunds(token_, rescueTo_, amount_); + } +} diff --git a/contracts/evmx/fees/GasVault.sol b/contracts/evmx/fees/GasVault.sol index f150dcdb..e03440d6 100644 --- a/contracts/evmx/fees/GasVault.sol +++ b/contracts/evmx/fees/GasVault.sol @@ -18,9 +18,8 @@ contract GasVault is IGasVault, AccessControl { */ constructor(address owner_) { _setOwner(owner_); - // to rescue funds - _grantRole(FEE_MANAGER_ROLE, owner_); + _grantRole(GAS_MANAGER_ROLE, owner_); } /** @@ -32,7 +31,7 @@ contract GasVault is IGasVault, AccessControl { function withdraw( address to_, uint256 amount_ - ) external onlyRole(FEE_MANAGER_ROLE) returns (bool success) { + ) external onlyRole(GAS_MANAGER_ROLE) returns (bool success) { if (amount_ == 0) return true; success = SafeTransferLib.trySafeTransferETH(to_, amount_, gasleft()); emit NativeWithdrawn(success, to_, amount_); @@ -41,11 +40,11 @@ contract GasVault is IGasVault, AccessControl { /** * @notice Returns the current balance of native tokens in the pool */ - function getBalance() external view returns (uint256) { + function vaultBalance() external view returns (uint256) { return address(this).balance; } receive() external payable { - emit NativeDeposited(msg.sender, msg.value); + emit VaultDeposit(msg.sender, msg.value); } } diff --git a/contracts/evmx/helpers/AsyncDeployer.sol b/contracts/evmx/helpers/AsyncDeployer.sol index e5bf7cb6..75508670 100644 --- a/contracts/evmx/helpers/AsyncDeployer.sol +++ b/contracts/evmx/helpers/AsyncDeployer.sol @@ -53,7 +53,11 @@ contract AsyncDeployer is AsyncDeployerStorage, Initializable, AddressResolverUt /// @dev it deploys the forwarder and async promise implementations and beacons for them /// @dev this contract is owner of the beacons for upgrading later /// @param owner_ The address of the contract owner - function initialize(address owner_, address addressResolver_, uint256 defaultDeadline_) public reinitializer(1) { + function initialize( + address owner_, + address addressResolver_, + uint256 defaultDeadline_ + ) public reinitializer(1) { _initializeOwner(owner_); _setAddressResolver(addressResolver_); defaultDeadline = defaultDeadline_; diff --git a/contracts/evmx/helpers/AsyncPromise.sol b/contracts/evmx/helpers/AsyncPromise.sol index 96766440..7495a6bf 100644 --- a/contracts/evmx/helpers/AsyncPromise.sol +++ b/contracts/evmx/helpers/AsyncPromise.sol @@ -100,7 +100,8 @@ contract AsyncPromise is AsyncPromiseStorage, Initializable, AddressResolverUtil PromiseReturnData memory resolvedPromise_ ) external override onlyWatcher returns (bool success) { if (block.timestamp > promiseDeadline) revert DeadlinePassed(); - if (state == AsyncPromiseState.RESOLVED || state == AsyncPromiseState.ONCHAIN_REVERTING) revert PromiseAlreadyResolved(); + if (state == AsyncPromiseState.RESOLVED || state == AsyncPromiseState.ONCHAIN_REVERTING) + revert PromiseAlreadyResolved(); state = AsyncPromiseState.RESOLVED; // Call callback to app gateway @@ -129,7 +130,8 @@ contract AsyncPromise is AsyncPromiseStorage, Initializable, AddressResolverUtil PromiseReturnData memory resolvedPromise_ ) external override onlyWatcher { if (block.timestamp > promiseDeadline) revert DeadlinePassed(); - if (state == AsyncPromiseState.RESOLVED || state == AsyncPromiseState.ONCHAIN_REVERTING) revert PromiseAlreadyResolved(); + if (state == AsyncPromiseState.RESOLVED || state == AsyncPromiseState.ONCHAIN_REVERTING) + revert PromiseAlreadyResolved(); // to update the state in case selector is bytes(0) but reverting onchain state = AsyncPromiseState.ONCHAIN_REVERTING; diff --git a/contracts/evmx/interfaces/IGasEscrow.sol b/contracts/evmx/interfaces/IGasEscrow.sol index e69de29b..2203b708 100644 --- a/contracts/evmx/interfaces/IGasEscrow.sol +++ b/contracts/evmx/interfaces/IGasEscrow.sol @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import {EscrowEntry} from "../../utils/common/Structs.sol"; +/// @title Gas Escrow Manager +/// @notice Tracks escrowed gas during request lifecycle +/// @dev Separates escrow logic from token logic for clarity +interface IGasEscrow { + /// @notice Emitted when fees are blocked for a batch + /// @param payloadId The payload id + /// @param consumeFrom The consume from address + /// @param amount The blocked amount + event GasEscrowed(bytes32 indexed payloadId, address indexed consumeFrom, uint256 amount); + + /// @notice Emitted when fees are unblocked + /// @param payloadId The payload id + /// @param consumeFrom The consume from address + event EscrowReleased(bytes32 indexed payloadId, address indexed consumeFrom); + + /// @notice Emitted when fees are unblocked and assigned to a transmitter + /// @param payloadId The payload id + /// @param consumeFrom The consume from address + /// @param transmitter The transmitter address + /// @param amount The unblocked amount + event EscrowSettled( + bytes32 indexed payloadId, + address indexed consumeFrom, + address indexed transmitter, + uint256 amount + ); + + /// @notice Escrow gas for a request + function escrowGas(bytes32 payloadId_, address consumeFrom_, uint256 amount_) external; + + /// @notice Release escrow back to account + function releaseEscrow(bytes32 payloadId) external; + + /// @notice Mark escrow as settled (paid to transmitter) + function settleGasPayment(bytes32 payloadId, address transmitter) external; + + /// @notice Get total escrowed amount for an account + function getEscrowedAmount(address account) external view returns (uint256); + + /// @notice Get request escrow details + function getPayloadEscrow(bytes32 payloadId) external view returns (EscrowEntry memory); +} diff --git a/contracts/utils/common/Structs.sol b/contracts/utils/common/Structs.sol index 88b9ee4c..67754d59 100644 --- a/contracts/utils/common/Structs.sol +++ b/contracts/utils/common/Structs.sol @@ -29,6 +29,20 @@ enum ExecutionStatus { Reverted } +enum EscrowState { + None, // No escrow + Active, // Escrowed, request in progress + Released, // Returned to account + Settled // Paid to transmitter +} + +struct EscrowEntry { + address account; // Who's paying + uint256 amount; // How much is escrowed + uint256 timestamp; // When escrowed + EscrowState state; // Current state +} + struct AppGatewayApprovals { address appGateway; uint256 approval; From 853f90ebfb8f3a486f697a8596d258651c41d5f9 Mon Sep 17 00:00:00 2001 From: Ameesha Agrawal Date: Wed, 12 Nov 2025 18:49:56 +0530 Subject: [PATCH 05/12] fix: interface --- contracts/evmx/interfaces/IGasVault.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/evmx/interfaces/IGasVault.sol b/contracts/evmx/interfaces/IGasVault.sol index d101fd2e..7ef42649 100644 --- a/contracts/evmx/interfaces/IGasVault.sol +++ b/contracts/evmx/interfaces/IGasVault.sol @@ -7,5 +7,5 @@ interface IGasVault { function withdraw(address to_, uint256 amount_) external returns (bool success); - function getBalance() external view returns (uint256); + function vaultBalance() external view returns (uint256); } From c9717053a252e575da3648e809ec03830c9c8a45 Mon Sep 17 00:00:00 2001 From: Ameesha Agrawal Date: Wed, 12 Nov 2025 23:38:15 +0530 Subject: [PATCH 06/12] fix: gas account manager --- contracts/evmx/fees/GasAccountManager.sol | 360 +++++++++---- contracts/evmx/fees/GasAccountToken.sol | 471 ++---------------- contracts/evmx/fees/GasEscrow.sol | 31 +- .../evmx/interfaces/IGasAccountManager.sol | 140 +++++- contracts/evmx/interfaces/IGasEscrow.sol | 7 +- contracts/evmx/plugs/GasStation.sol | 2 +- contracts/evmx/watcher/Watcher.sol | 36 +- .../watcher/precompiles/WritePrecompile.sol | 2 +- 8 files changed, 475 insertions(+), 574 deletions(-) diff --git a/contracts/evmx/fees/GasAccountManager.sol b/contracts/evmx/fees/GasAccountManager.sol index a403024b..a76d2890 100644 --- a/contracts/evmx/fees/GasAccountManager.sol +++ b/contracts/evmx/fees/GasAccountManager.sol @@ -1,75 +1,277 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; +import "solady/auth/Ownable.sol"; +import "solady/utils/Initializable.sol"; +import "solady/utils/SafeTransferLib.sol"; +import "../interfaces/IGasAccountManager.sol"; +import "../interfaces/IGasEscrow.sol"; +import "../interfaces/IGasVault.sol"; +import "../interfaces/IGasAccountToken.sol"; +import "../interfaces/IGasStation.sol"; +import "../../utils/AccessControl.sol"; +import "../../utils/common/AccessRoles.sol"; +import {OverrideParamsLib} from "../../utils/common/OverrideParamsLib.sol"; +import {OverrideParams, SolanaInstruction, SolanaInstructionData, SolanaInstructionDataDescription} from "../../utils/common/Structs.sol"; +import {toBytes32Format} from "../../utils/common/Converters.sol"; +import {WRITE, CHAIN_SLUG_SOLANA_MAINNET} from "../../utils/common/Constants.sol"; +import {NonceUsed, InvalidAmount, InsufficientCreditsAvailable, InsufficientBalance, InvalidChainSlug, NotRequestHandler, InvalidReceiver} from "../../utils/common/Errors.sol"; -import "./GasAccountToken.sol"; +import "../../utils/RescueFundsLib.sol"; +import {AddressResolverUtil} from "../helpers/AddressResolverUtil.sol"; import {ForwarderSolana} from "../helpers/ForwarderSolana.sol"; +import {GasStationProgramPda} from "../helpers/solana-utils/program-pda/GasStationPdas.sol"; +import {SolanaPDA} from "../helpers/solana-utils/SolanaPda.sol"; +import {TOKEN_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID, SYSTEM_PROGRAM_ID} from "../helpers/solana-utils/SolanaPda.sol"; +import "../base/AppGatewayBase.sol"; -/// @title GasAccountManager -/// @notice Contract for managing fees -contract GasAccountManager is GasAccountToken { +contract GasAccountManager is IGasAccountManager, Ownable, AccessControl, AppGatewayBase { using OverrideParamsLib for OverrideParams; - /// @notice Emitted when fees are blocked for a batch - /// @param payloadId The payload id - /// @param consumeFrom The consume from address - /// @param amount The blocked amount - event GasEscrowed(bytes32 indexed payloadId, address indexed consumeFrom, uint256 amount); - - /// @notice Emitted when fees are unblocked and assigned to a transmitter - /// @param payloadId The payload id - /// @param consumeFrom The consume from address - /// @param transmitter The transmitter address - /// @param amount The unblocked amount - event EscrowSettled( - bytes32 indexed payloadId, - address indexed consumeFrom, - address indexed transmitter, - uint256 amount - ); - - /// @notice Emitted when max fees per chain slug is set - /// @param chainSlug The chain slug - /// @param fees The max fees - event MaxGasPerChainSlugSet(uint32 indexed chainSlug, uint256 fees); - - /// @notice Emitted when fees are unblocked - /// @param payloadId The payload id - /// @param consumeFrom The consume from address - event EscrowReleased(bytes32 indexed payloadId, address indexed consumeFrom); - - constructor() { - _disableInitializers(); // disable for implementation - } - - /// @notice Initializer function to replace constructor - /// @param addressResolver_ The address of the address resolver - /// @param owner_ The address of the owner - /// @param evmxSlug_ The evmx chain slug - function initialize( - uint32 evmxSlug_, - address addressResolver_, + // slots [0-49] reserved for gap + uint256[50] _gap_before; + + IGasEscrow public gasEscrow; + IGasVault public gasVault; + IGasAccountToken public gasAccountToken; + + // token pool balances + // chainSlug => token address => amount + mapping(uint32 => mapping(bytes32 => uint256)) public tokenOnChainBalances; + + /// @notice Mapping to track max fees per chain slug + /// @dev chainSlug => max fees + mapping(uint32 => uint256) public maxGasPerChainSlug; + + /////////////////////// SOLANA /////////////////////// + ForwarderSolana public forwarderSolana; + bytes32 public susdcSolanaProgramId; + bytes32 public gasStationSolanaProgramId; + + /// @notice Mapping to track fees plug for each chain slug + /// @dev chainSlug => fees plug address + mapping(uint32 => bytes32) public gasStations; + + constructor( + address gasEscrow_, address gasVault_, + address gasAccountToken_, + address addressResolver_, address owner_, uint256 fees_, bytes32 sbType_, address forwarderSolana_ - ) public reinitializer(2) { - evmxSlug = evmxSlug_; + ) { + gasEscrow = IGasEscrow(gasEscrow_); gasVault = IGasVault(gasVault_); - maxGasPerChainSlug[evmxSlug_] = fees_; - overrideParams = overrideParams.setSwitchboardType(sbType_).setMaxFees(fees_); + gasAccountToken = IGasAccountToken(gasAccountToken_); + forwarderSolana = ForwarderSolana(forwarderSolana_); + overrideParams = overrideParams.setSwitchboardType(sbType_).setMaxFees(fees_); _initializeOwner(owner_); _initializeAppGateway(addressResolver_); - forwarderSolana = ForwarderSolana(forwarderSolana_); } - function setGasStationSolanaProgramId(bytes32 gasStationSolanaProgramId_) external onlyOwner { - gasStationSolanaProgramId = gasStationSolanaProgramId_; + // ============ GAS ACCOUNT OPERATIONS ============ + + /// @notice Wrap native tokens into SGAS + function wrapToGas(address receiver) external payable override onlyWatcher { + uint256 amount = msg.value; + if (amount == 0) revert InvalidAmount(); + + // Mint tokens to receiver + gasAccountToken.mint(receiver, amount); + + // reverts if transfer fails + SafeTransferLib.safeTransferETH(address(gasVault), amount); + emit GasWrapped(receiver_, amount); + } + + /// @notice Unwrap SGAS to native tokens + function unwrapFromGas(uint256 amount, address receiver) external onlyWatcher { + if (gasAccountToken.balanceOf(msg.sender) < amount) revert InsufficientCreditsAvailable(); + + // Burn tokens from sender + gasAccountToken.burn(msg.sender, amount); + + bool success = gasVault.withdraw(receiver, amount); + if (!success) revert InsufficientBalance(); + + emit GasUnwrapped(msg.sender, amount); + } + + // ============ CROSS-CHAIN OPERATIONS ============ + + /// @notice Deposit tokens from a chain into gas account + /// @dev Called by watcher after detecting GasStation deposit + function depositFromChain( + uint32 chainSlug, + address token, + address account, + uint256 nativeAmount, + uint256 gasAmount + ) external onlyWatcher { + // Decode payload: (chainSlug, token, receiver, creditAmount, nativeAmount) + ( + uint32 chainSlug_, + address token_, + address depositTo_, + uint256 creditAmount_, + uint256 nativeAmount_ + ) = abi.decode(payload_, (uint32, address, address, uint256, uint256)); + + tokenOnChainBalances[chainSlug_][toBytes32Format(token_)] += creditAmount_ + nativeAmount_; + + // Mint tokens to the user + gasAccountToken.mint(depositTo_, creditAmount_); + if (nativeAmount_ > 0) { + // if native transfer fails, add to credit + bool success = gasVault.withdraw(depositTo_, nativeAmount_); + + if (!success) { + // Convert failed native amount to credits + gasAccountToken.mint(depositTo_, nativeAmount_); + creditAmount_ += nativeAmount_; + nativeAmount_ = 0; + } + } + + emit Deposited(chainSlug_, token_, depositTo_, creditAmount_, nativeAmount_); + } + + /// @notice Withdraw SGAS to tokens on another chain + function withdrawToChain( + uint32 chainSlug, + address token, + uint256 amount, + uint256 bridgeFee, + address receiver + ) external { + address consumeFrom = msg.sender; + + // Check if amount is available in fees plug + uint256 availableGas = gasAccountToken.balanceOf(consumeFrom); + if (availableGas < amount + bridgeFee) revert InsufficientCreditsAvailable(); + + // Burn tokens from sender + gasAccountToken.burn(consumeFrom, amount); + tokenOnChainBalances[chainSlug][toBytes32Format(token)] -= amount; + + // Add it to the queue and submit payload + _submitPayload( + chainSlug, + consumeFrom, + bridgeFee, + abi.encodeCall(IGasStation.withdrawFees, (token, receiver, amount)) + ); + } + + function _submitPayload( + uint32 chainSlug_, + address consumeFrom_, + uint256 bridgeFee_, + bytes memory payload_ + ) internal async { + overrideParams = overrideParams.setMaxFees(bridgeFee_).setConsumeFrom(consumeFrom_); + + RawPayload memory rawPayload; + rawPayload.overrideParams = overrideParams; + rawPayload.transaction = Transaction({ + chainSlug: chainSlug_, + target: _getGasStationAddress(chainSlug_), + payload: payload_ + }); + watcher__().addPayloadData(rawPayload, address(this)); + } + + function _getGasStationAddress(uint32 chainSlug_) internal view returns (bytes32) { + if (gasStations[chainSlug_] == bytes32(0)) revert InvalidChainSlug(); + return gasStations[chainSlug_]; + } + + // ============ PAYLOAD LIFECYCLE (Internal) ============ + + /// @notice Escrow gas for a payload + /// @dev Called by RequestHandler when transmitter assigned + function escrowGas(bytes32 payloadId, address account, uint256 amount) external onlyWatcher { + if (gasAccountToken.balanceOf(account) < amount) revert InsufficientCreditsAvailable(); + gasEscrow.escrowGas(payloadId, account, amount); + } + + /// @notice Release escrowed gas back to account + /// @dev Called when transmitter changes or payload cancelled + function releaseEscrow(bytes32 payloadId) external onlyWatcher { + gasEscrow.releaseEscrow(payloadId); + } + + /// @notice Settle escrowed gas to transmitter + /// @dev Called when payload completes successfully + function settleGasPayment( + bytes32 payloadId, + address consumeFrom, + address transmitter, + uint256 amount_ + ) external onlyWatcher { + gasEscrow.settleGasPayment(payloadId, transmitter, amount); + gasAccountToken.burn(consumeFrom, amount); + gasAccountToken.mint(transmitter, amount); + + emit GasPaymentSettled(payloadId, transmitter, amount); + } + + /// @notice Get available gas balance for an account + /// @dev Returns balance minus escrowed amount + function availableGas(address account) external view override returns (uint256) { + return gasAccountToken.balanceOf(account); + } + + /// @notice Get total gas balance including escrowed + function totalGas(address account) external view override returns (uint256) { + return gasAccountToken.totalBalanceOf(account); + } + + /// @notice Get currently escrowed gas for an account + function escrowedGas(address account) external view override returns (uint256) { + return gasEscrow.escrowedBalanceOf(account); + } + + /// @notice Approve an app to spend gas from your account + function approveGasSpending(address app, uint256 amount) external override { + gasAccountToken.approve(app, amount); + } + + function setGasStation(uint32 chainSlug_, bytes32 gasStation_) external onlyOwner { + gasStations[chainSlug_] = gasStation_; + emit GasStationSet(chainSlug_, gasStation_); + } + + function setGasVault(address gasVault_) external onlyOwner { + gasVault = IGasVault(gasVault_); + emit GasVaultSet(gasVault_); + } + + function setGasAccountToken(address gasAccountToken_) external onlyOwner { + gasAccountToken = IGasAccountToken(gasAccountToken_); + emit GasAccountTokenSet(gasAccountToken_); + } + + function setGasEscrow(address gasEscrow_) external onlyOwner { + gasEscrow = IGasEscrow(gasEscrow_); + emit GasEscrowSet(gasEscrow_); + } + + function setForwarderSolana(address forwarderSolana_) external onlyOwner { + forwarderSolana = ForwarderSolana(forwarderSolana_); + emit ForwarderSolanaSet(forwarderSolana_); } function setSusdcSolanaProgramId(bytes32 susdcSolanaProgramId_) external onlyOwner { susdcSolanaProgramId = susdcSolanaProgramId_; + emit SusdcSolanaProgramIdSet(susdcSolanaProgramId_); + } + + function setGasStationSolanaProgramId(bytes32 gasStationSolanaProgramId_) external onlyOwner { + gasStationSolanaProgramId = gasStationSolanaProgramId_; + emit GasStationSolanaProgramIdSet(gasStationSolanaProgramId_); } function setChainMaxFees( @@ -98,62 +300,8 @@ contract GasAccountManager is GasAccountToken { overrideParams = overrideParams.setMaxFees(fees_); } - /////////////////////// FEES MANAGEMENT /////////////////////// - - /// @notice Blocks fees for a request count - /// @param payloadId_ The payload id - /// @param consumeFrom_ The fees payer address - /// @param credits_ The total fees to block - /// @dev Only callable by delivery helper - function escrowGas( - bytes32 payloadId_, - address consumeFrom_, - uint256 credits_ - ) external override onlyWatcher { - if (balanceOf(consumeFrom_) < credits_) revert InsufficientCreditsAvailable(); - - accountEscrow[consumeFrom_] += credits_; - payloadEscrow[payloadId_] = credits_; - emit GasEscrowed(payloadId_, consumeFrom_, credits_); - } - - /// @notice Unblocks fees after successful execution and assigns them to the transmitter - /// @param payloadId_ The payload id - /// @param assignTo_ The address of the transmitter - function settleGasPayment( - bytes32 payloadId_, - address assignTo_, - uint256 amount_ - ) external override onlyWatcher { - uint256 payloadEscrow_ = payloadEscrow[payloadId_]; - if (payloadEscrow_ == 0) return; - - Payload memory payload = watcher__().getPayload(payloadId_); - address consumeFrom = payload.consumeFrom; - - // Unblock credits from the original user - accountEscrow[consumeFrom] -= amount_; - payloadEscrow[payloadId_] -= amount_; - - // Burn tokens from the original user - _burn(consumeFrom, amount_); - // Mint tokens to the transmitter - _mint(assignTo_, amount_); - - emit EscrowSettled(payloadId_, consumeFrom, assignTo_, amount_); - } - - function releaseEscrow(bytes32 payloadId_) external override onlyWatcher { - uint256 payloadEscrow_ = payloadEscrow[payloadId_]; - if (payloadEscrow_ == 0) return; - - // Unblock credits from the original user - // address consumeFrom = _getRequestParams(requestCount_).requestFeesDetails.consumeFrom; - address consumeFrom = overrideParams.consumeFrom; - accountEscrow[consumeFrom] -= payloadEscrow_; - - delete payloadEscrow[payloadId_]; - emit EscrowReleased(payloadId_, consumeFrom); + function increaseFees(bytes32 payloadId_, uint256 newMaxFees_) public { + _increaseFees(payloadId_, newMaxFees_); } /** diff --git a/contracts/evmx/fees/GasAccountToken.sol b/contracts/evmx/fees/GasAccountToken.sol index a30bf696..ca98358f 100644 --- a/contracts/evmx/fees/GasAccountToken.sol +++ b/contracts/evmx/fees/GasAccountToken.sol @@ -1,230 +1,56 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; -import "solady/utils/Initializable.sol"; -import "solady/utils/ECDSA.sol"; -import "solady/utils/SafeTransferLib.sol"; import "solady/auth/Ownable.sol"; import "solady/tokens/ERC20.sol"; - -import "../interfaces/IGasAccountManager.sol"; -import "../interfaces/IGasStation.sol"; -import "../interfaces/IGasVault.sol"; - -import {AddressResolverUtil} from "../helpers/AddressResolverUtil.sol"; -import {NonceUsed, InvalidAmount, InsufficientCreditsAvailable, InsufficientBalance, InvalidChainSlug, NotRequestHandler, InvalidReceiver} from "../../utils/common/Errors.sol"; -import {WRITE, CHAIN_SLUG_SOLANA_MAINNET} from "../../utils/common/Constants.sol"; import "../../utils/RescueFundsLib.sol"; -import "../base/AppGatewayBase.sol"; -import {toBytes32Format} from "../../utils/common/Converters.sol"; -import {ForwarderSolana} from "../helpers/ForwarderSolana.sol"; -import {SolanaInstruction, SolanaInstructionData, SolanaInstructionDataDescription} from "../../utils/common/Structs.sol"; -import {GasStationProgramPda} from "../helpers/solana-utils/program-pda/GasStationPdas.sol"; -import {SolanaPDA} from "../helpers/solana-utils/SolanaPda.sol"; -import {TOKEN_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID, SYSTEM_PROGRAM_ID} from "../helpers/solana-utils/SolanaPda.sol"; - -abstract contract GasAccountManagerStorage is IGasAccountManager { - // slots [0-49] reserved for gap - uint256[50] _gap_before; - - // slot 50 - /// @notice evmx slug - uint32 public evmxSlug; - IGasVault public gasVault; - - // slot 51 - /// @notice Mapping to track blocked credits for each user - /// @dev address => accountEscrow - mapping(address => uint256) public accountEscrow; - - // slot 52 - /// @notice Mapping to track request credits details for each payload id - /// @dev payloadId => RequestFee - mapping(bytes32 => uint256) public payloadEscrow; - - // slot 53 - // token pool balances - // chainSlug => token address => amount - mapping(uint32 => mapping(bytes32 => uint256)) public tokenOnChainBalances; - - // slot 54 - /// @notice Mapping to track nonce to whether it has been used - /// @dev address => signatureNonce => isNonceUsed - /// @dev used by watchers or other users in signatures - mapping(address => mapping(uint256 => bool)) public isNonceUsed; - - // slot 55 - /// @notice Mapping to track fees plug for each chain slug - /// @dev chainSlug => fees plug address - mapping(uint32 => bytes32) public gasStations; - - // slot 56 - /// @notice Mapping to track max fees per chain slug - /// @dev chainSlug => max fees - mapping(uint32 => uint256) public maxGasPerChainSlug; - - ForwarderSolana public forwarderSolana; - - bytes32 public susdcSolanaProgramId; - bytes32 public gasStationSolanaProgramId; - - // slots [60-107] reserved for gap - uint256[44] _gap_after; - - // slots [108-157] 50 slots reserved for address resolver util - // 9 slots for app gateway base -} - -/// @title SocketUSDC -/// @notice ERC20 token for managing credits with blocking/unblocking functionality -abstract contract GasAccountToken is - GasAccountManagerStorage, - Initializable, - Ownable, - AppGatewayBase, - ERC20 -{ - using OverrideParamsLib for OverrideParams; - - /// @notice Emitted when fees deposited are updated - /// @param chainSlug The chain identifier - /// @param token The token address - /// @param depositTo The address to deposit to - /// @param creditAmount The credit amount added - /// @param nativeAmount The native amount transferred - event Deposited( - uint32 indexed chainSlug, - address indexed token, - address indexed depositTo, - uint256 creditAmount, - uint256 nativeAmount - ); - - /// @notice Emitted when credits are wrapped - event GasWrapped(address indexed consumeFrom, uint256 amount); - - /// @notice Emitted when credits are unwrapped - event GasUnwrapped(address indexed consumeFrom, uint256 amount); - - /// @notice Emitted when fees plug is set - event GasStationSet(uint32 indexed chainSlug, bytes32 indexed gasStation); - - /// @notice Emitted when fees pool is set - event GasVaultSet(address indexed gasVault); - - /// @notice Emitted when withdraw fails - event WithdrawFailed(bytes32 indexed payloadId); - - /// @notice Emitted when fees plug solana program id is set - event GasStationSolanaSet(bytes32 indexed gasStationSolanaProgramId); - - function setGasStation(uint32 chainSlug_, bytes32 gasStation_) external onlyOwner { - gasStations[chainSlug_] = gasStation_; - emit GasStationSet(chainSlug_, gasStation_); - } +import "solady/utils/Initializable.sol"; - function setGasVault(address gasVault_) external onlyOwner { - gasVault = IGasVault(gasVault_); - emit GasVaultSet(gasVault_); - } +/// @title Socket Gas Token (SGAS) +/// @notice ERC20 token representing prepaid gas for Socket operations +/// @dev Balances are split between available and escrowed +contract GasAccountToken is ERC20, Ownable { + string public constant name = "Socket Gas"; + string public constant symbol = "SGAS"; + uint8 public constant decimals = 18; - function getMaxFees(uint32 chainSlug_) public view returns (uint256) { - return - maxGasPerChainSlug[chainSlug_] == 0 - ? maxGasPerChainSlug[evmxSlug] - : maxGasPerChainSlug[chainSlug_]; - } + /// @notice Escrow tracker for gas in active payloads + IGasEscrow public gasEscrow; + IGasAccountManager public gasAccountManager; - function isApproved(address user_, address appGateway_) public view returns (bool) { - return allowance(user_, appGateway_) > 0; + modifier onlyGasAccountManager() { + if (msg.sender != address(gasAccountManager)) revert NotGasAccountManager(); + _; } - /// @notice Deposits credits and native tokens to a user - /// @param payload_ Encoded deposit parameters: (chainSlug, token, receiver, creditAmount, nativeAmount) - function deposit(bytes calldata payload_) external override onlyWatcher { - // Decode payload: (chainSlug, token, receiver, creditAmount, nativeAmount) - ( - uint32 chainSlug_, - address token_, - address depositTo_, - uint256 creditAmount_, - uint256 nativeAmount_ - ) = abi.decode(payload_, (uint32, address, address, uint256, uint256)); - - tokenOnChainBalances[chainSlug_][toBytes32Format(token_)] += creditAmount_ + nativeAmount_; - - // Mint tokens to the user - _mint(depositTo_, creditAmount_); - if (nativeAmount_ > 0) { - // if native transfer fails, add to credit - bool success = gasVault.withdraw(depositTo_, nativeAmount_); - - if (!success) { - // Convert failed native amount to credits - _mint(depositTo_, nativeAmount_); - creditAmount_ += nativeAmount_; - nativeAmount_ = 0; - } - } - - emit Deposited(chainSlug_, token_, depositTo_, creditAmount_, nativeAmount_); + constructor(address owner_, address gasEscrow_, address gasAccountManager_) { + gasEscrow = IGasEscrow(gasEscrow_); + gasAccountManager = IGasAccountManager(gasAccountManager_); + _setOwner(owner_); } - function wrap(address receiver_) external payable override { - uint256 amount = msg.value; - if (amount == 0) revert InvalidAmount(); - - // Mint tokens to receiver - _mint(receiver_, amount); - - // reverts if transfer fails - SafeTransferLib.safeTransferETH(address(gasVault), amount); - emit GasWrapped(receiver_, amount); + function mint(address account, uint256 amount) external onlyGasAccountManager { + _mint(account, amount); } - function unwrap(uint256 amount_, address receiver_) external { - if (balanceOf(msg.sender) < amount_) revert InsufficientCreditsAvailable(); - - // Burn tokens from sender - _burn(msg.sender, amount_); - - bool success = gasVault.withdraw(receiver_, amount_); - if (!success) revert InsufficientBalance(); - - emit GasUnwrapped(receiver_, amount_); + function burn(address account, uint256 amount) external onlyGasAccountManager { + _burn(account, amount); } - /// @notice Override balanceOf to return available (unblocked) credits + /// @notice Returns available (spendable) gas balance + /// @dev Subtracts escrowed amount from total balance function balanceOf(address account) public view override returns (uint256) { - return super.balanceOf(account) - accountEscrow[account]; + return super.balanceOf(account) - gasEscrow.getEscrowedAmount(account); } - /// @notice Get total balance including blocked credits - function totalGas(address account) public view returns (uint256) { + /// @notice Returns total gas balance including escrowed + function totalBalanceOf(address account) public view returns (uint256) { return super.balanceOf(account); } - /// @notice Get blocked credits for an account - function getPayloadEscrow(address account) public view returns (uint256) { - return accountEscrow[account]; - } - - /// @notice Checks if the user has enough credits - /// @param consumeFrom_ address to consume from - /// @param spender_ address to spend from - /// @param amount_ amount to spend - /// @return True if the user has enough credits, false otherwise - function isGasAvailable( - address consumeFrom_, - address spender_, - uint256 amount_ - ) public view override returns (bool) { - // If consumeFrom_ is not same as spender_ or spender_ is not watcher, check if it is approved - if (spender_ != address(watcher__()) && consumeFrom_ != spender_) { - if (allowance(consumeFrom_, spender_) == 0) return false; - } - - return balanceOf(consumeFrom_) >= amount_; + /// @notice Returns escrowed gas that's locked in active payloads + function escrowedBalanceOf(address account) public view returns (uint256) { + return gasEscrow.getEscrowedAmount(account); } // ERC20 Overrides to handle blocked credits @@ -242,226 +68,37 @@ abstract contract GasAccountToken is ) public override(ERC20, IGasAccountManager) returns (bool) { if (!isGasAvailable(from_, msg.sender, amount_)) revert InsufficientCreditsAvailable(); + // todo: check if (msg.sender == address(watcher__())) _approve(from_, msg.sender, amount_); return super.transferFrom(from_, to_, amount_); } - /// @notice Withdraws funds to a specified receiver - /// @dev This function is used to withdraw fees from the fees plug - /// @dev assumed that transmitter can bid for their request on AM - /// @param chainSlug_ The chain identifier - /// @param token_ The address of the token - /// @param credits_ The amount of tokens to withdraw - /// @param maxFees_ The fees needed to process the withdraw - /// @param receiver_ The address of the receiver - function withdrawToChain( - uint32 chainSlug_, - address token_, - uint256 credits_, - uint256 maxFees_, - address receiver_ - ) public override { - address consumeFrom = msg.sender; - - // Check if amount is available in fees plug - uint256 availableCredits = balanceOf(consumeFrom); - if (availableCredits < credits_ + maxFees_) revert InsufficientCreditsAvailable(); - - // Burn tokens from sender - _burn(consumeFrom, credits_); - tokenOnChainBalances[chainSlug_][toBytes32Format(token_)] -= credits_; - - // Add it to the queue and submit request - _createRequest( - chainSlug_, - consumeFrom, - maxFees_, - abi.encodeCall(IGasStation.withdrawFees, (token_, receiver_, credits_)) - ); - } - - function withdrawToChainSolana( - uint32 chainSlug_, - bytes32 token_, - uint256 credits_, - uint256 maxFees_, - bytes32 onchainReceiver_ - ) public async { - // sender is evmx address (credit holder) that is making the call (it is will be AG in case of Game) - address consumeFrom = msg.sender; - - // Check if amount is available in fees plug - uint256 availableCredits = balanceOf(consumeFrom); - if (availableCredits < credits_ + maxFees_) revert InsufficientCreditsAvailable(); - - // Burn tokens from sender - _burn(consumeFrom, credits_); - tokenOnChainBalances[chainSlug_][token_] -= credits_; - - gasStationWithdrawSolana(token_, credits_, onchainReceiver_); - } - - function gasStationWithdrawSolana( - bytes32 token_, - uint256 credits_, - bytes32 onchainReceiver_ - ) internal { - SolanaInstruction memory solanaInstruction_ = createGasStationWithdrawInstructionSolana( - onchainReceiver_, - token_, - credits_ - ); - forwarderSolana.callSolana( - abi.encode(solanaInstruction_), - solanaInstruction_.data.programId, - address(this) - ); - } - - function _createRequest( - uint32 chainSlug_, + /// @notice Checks if the user has enough credits + /// @param consumeFrom_ address to consume from + /// @param spender_ address to spend from + /// @param amount_ amount to spend + /// @return True if the user has enough credits, false otherwise + function isGasAvailable( address consumeFrom_, - uint256 maxFees_, - bytes memory payload_ - ) internal async { - overrideParams = overrideParams.setMaxFees(getMaxFees(chainSlug_)).setConsumeFrom( - consumeFrom_ - ); - - RawPayload memory rawPayload; - rawPayload.overrideParams = overrideParams; - rawPayload.transaction = Transaction({ - chainSlug: chainSlug_, - target: _getGasStationAddress(chainSlug_), - payload: payload_ - }); - watcher__().addPayloadData(rawPayload, address(this)); - } - - function increaseFees(bytes32 payloadId_, uint256 newMaxFees_) public { - _increaseFees(payloadId_, newMaxFees_); - } - - function _getGasStationAddress(uint32 chainSlug_) internal view returns (bytes32) { - if (gasStations[chainSlug_] == bytes32(0)) revert InvalidChainSlug(); - return gasStations[chainSlug_]; - } - - function _recoverSigner( - bytes32 digest_, - bytes memory signature_ - ) internal view returns (address signer) { - bytes32 digest = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", digest_)); - // recovered signer is checked for the valid roles later - signer = ECDSA.recover(digest, signature_); - } - - // ERC20 metadata - function name() public pure override returns (string memory) { - return "Socket Gas"; - } - - function symbol() public pure override returns (string memory) { - return "SGAS"; - } + address spender_, + uint256 amount_ + ) public view override returns (bool) { + // If consumeFrom_ is not same as spender_ or spender_ is not watcher, check if it is approved + if (spender_ != address(watcher__()) && consumeFrom_ != spender_) { + if (allowance(consumeFrom_, spender_) == 0) return false; + } - function decimals() public pure override returns (uint8) { - return 18; + return balanceOf(consumeFrom_) >= amount_; } - function createGasStationWithdrawInstructionSolana( - bytes32 userAddress_, - bytes32 susdcMint_, - uint256 withdrawAmount_ - ) internal view returns (SolanaInstruction memory) { - bytes32[] memory accounts = new bytes32[](11); - // accounts 0 - tmpReturnStoragePda - (accounts[0], ) = GasStationProgramPda.deriveTmpReturnStoragePda(gasStationSolanaProgramId); - /*----------------- mint() accounts -----------------*/ - // accounts 1 - programConfigPda - (accounts[1], ) = GasStationProgramPda.deriveProgramConfigPda(gasStationSolanaProgramId); - // accounts 2 - vaultConfigPda - (bytes32 vaultConfigPda, ) = GasStationProgramPda.deriveVaultConfigPda( - gasStationSolanaProgramId - ); - accounts[2] = vaultConfigPda; - // accounts 3 - token mint address - accounts[3] = susdcMint_; - // accounts 4 - whitelistedTokenPda - (accounts[4], ) = GasStationProgramPda.deriveWhitelistedTokenPda( - gasStationSolanaProgramId, - susdcMint_ - ); - // accounts 5 - receiver address - accounts[5] = userAddress_; - // accounts 6 - receiver ata address - accounts[6] = SolanaPDA.deriveTokenAtaAddress(userAddress_, susdcMint_); - // accounts 7 - vault ata address - accounts[7] = SolanaPDA.deriveTokenAtaAddress(vaultConfigPda, susdcMint_); - // accounts 8 - system program id - accounts[8] = SYSTEM_PROGRAM_ID; - // accounts 9 - token program id - accounts[9] = TOKEN_PROGRAM_ID; - // accounts 10 - associated token program id - accounts[10] = ASSOCIATED_TOKEN_PROGRAM_ID; - - bytes1[] memory accountFlags = new bytes1[](11); - // tmpReturnStoragePda is writable - accountFlags[0] = bytes1(0x01); // true - // programConfigPda is not writable - accountFlags[1] = bytes1(0x00); // false - // vaultConfigPda is writable - accountFlags[2] = bytes1(0x01); // true - // token mint address is not writable - accountFlags[3] = bytes1(0x00); // false - // whitelistedTokenPda is not writable - accountFlags[4] = bytes1(0x00); // false - // receiver address is writable - accountFlags[5] = bytes1(0x01); // true - // receiver ata address is writable - accountFlags[6] = bytes1(0x01); // true - // vault ata address is writable - accountFlags[7] = bytes1(0x01); // true - // system program id is not writable - accountFlags[8] = bytes1(0x00); // false - // token program id is not writable - accountFlags[9] = bytes1(0x00); // false - // associated token program id is not writable - accountFlags[10] = bytes1(0x00); // false - - // withdraw instruction discriminator - bytes8 instructionDiscriminator = 0xb712469c946da122; - - bytes[] memory functionArguments = new bytes[](2); - bool isNative = false; - // here on purpose we do not convert to uint64 as gasStation withdraw function expects uint256 - uint64 withdrawAmountU64 = convertToSolanaUint64(withdrawAmount_); - functionArguments[0] = abi.encode(withdrawAmountU64); - functionArguments[1] = abi.encode(isNative ? 1 : 0); - - string[] memory functionArgumentTypeNames = new string[](2); - functionArgumentTypeNames[0] = "u64"; // this should fit most of the cases (but our BorshEncoder supports max 128 bits) - functionArgumentTypeNames[1] = "u8"; // bool is encoded as 1 or 0 - - return - SolanaInstruction({ - data: SolanaInstructionData({ - programId: gasStationSolanaProgramId, - instructionDiscriminator: instructionDiscriminator, - accounts: accounts, - functionArguments: functionArguments - }), - description: SolanaInstructionDataDescription({ - accountFlags: accountFlags, - functionArgumentTypeNames: functionArgumentTypeNames - }) - }); + /** + * @notice Rescues funds from the contract if they are locked by mistake. This contract does not + * theoretically need this function but it is added for safety. + * @param token_ The address of the token contract. + * @param rescueTo_ The address where rescued tokens need to be sent. + * @param amount_ The amount of tokens to be rescued. + */ + function rescueFunds(address token_, address rescueTo_, uint256 amount_) external onlyOwner { + RescueFundsLib._rescueFunds(token_, rescueTo_, amount_); } } - -// convert EVM uint256 18 decimals to Solana uint64 6 decimals -function convertToSolanaUint64(uint256 amount) pure returns (uint64) { - uint256 scaledAmount = amount / 10 ** 12; - require(scaledAmount <= type(uint64).max, "Amount exceeds uint64 max"); - return uint64(scaledAmount); -} diff --git a/contracts/evmx/fees/GasEscrow.sol b/contracts/evmx/fees/GasEscrow.sol index 38ee2223..9d197b72 100644 --- a/contracts/evmx/fees/GasEscrow.sol +++ b/contracts/evmx/fees/GasEscrow.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.21; import "../interfaces/IGasEscrow.sol"; /// @title Gas Escrow Manager -/// @notice Tracks escrowed gas during request lifecycle +/// @notice Tracks escrowed gas during payload lifecycle /// @dev Separates escrow logic from token logic for clarity contract GasEscrow is IGasEscrow { address public gasAccountManager; @@ -12,7 +12,7 @@ contract GasEscrow is IGasEscrow { /// @notice Tracks escrowed gas per account mapping(address => uint256) public accountEscrow; - /// @notice Tracks escrowed gas per request + /// @notice Tracks escrowed gas per payload mapping(bytes32 => EscrowEntry) public payloadEscrow; error NotGasAccountManager(); @@ -22,45 +22,52 @@ contract GasEscrow is IGasEscrow { _; } - /// @notice Escrow gas for a request + /// @notice Escrow gas for a payload function escrowGas( bytes32 payloadId_, address consumeFrom_, uint256 amount_ ) external onlyGasAccountManager { accountEscrow[consumeFrom_] += amount_; + + uint256 amount = amount_; + if (payloadEscrow[payloadId_].amount > 0) amount += payloadEscrow[payloadId_].amount; payloadEscrow[payloadId_] = EscrowEntry({ account: consumeFrom_, - amount: amount_, + amount: amount, timestamp: block.timestamp, state: EscrowState.Active }); - emit GasEscrowed(payloadId_, consumeFrom_, amount_); + + emit GasEscrowed(payloadId_, consumeFrom_, amount); } /// @notice Release escrow back to account function releaseEscrow(bytes32 payloadId) external onlyGasAccountManager { EscrowEntry storage entry = payloadEscrow[payloadId]; require(entry.state == EscrowState.Active, "Not active"); + if (entry.amount == 0) return; accountEscrow[entry.account] -= entry.amount; entry.state = EscrowState.Released; - emit EscrowReleased(payloadId, entry.account); } /// @notice Mark escrow as settled (paid to transmitter) function settleGasPayment( bytes32 payloadId, - address transmitter + address transmitter, + uint256 amount_ ) external onlyGasAccountManager { EscrowEntry storage entry = payloadEscrow[payloadId]; - require(entry.state == EscrowState.Active, "Not active"); + if (entry.state != EscrowState.Active) revert NotActive(); + if (entry.amount == 0) revert NoEscrow(); - accountEscrow[entry.account] -= entry.amount; - entry.state = EscrowState.Settled; + accountEscrow[entry.account] -= amount; + entry.amount -= amount_; - emit EscrowSettled(payloadId, entry.account, transmitter, entry.amount); + if (entry.amount == 0) entry.state = EscrowState.Settled; + emit EscrowSettled(payloadId, entry.account, transmitter, amount); } /// @notice Get total escrowed amount for an account @@ -68,7 +75,7 @@ contract GasEscrow is IGasEscrow { return accountEscrow[account]; } - /// @notice Get request escrow details + /// @notice Get payload escrow details function getPayloadEscrow(bytes32 payloadId) external view returns (EscrowEntry memory) { return payloadEscrow[payloadId]; } diff --git a/contracts/evmx/interfaces/IGasAccountManager.sol b/contracts/evmx/interfaces/IGasAccountManager.sol index 33a8fce7..45c28a85 100644 --- a/contracts/evmx/interfaces/IGasAccountManager.sol +++ b/contracts/evmx/interfaces/IGasAccountManager.sol @@ -2,36 +2,134 @@ pragma solidity ^0.8.21; import {WriteFinality, AppGatewayApprovals, OverrideParams, Transaction, RawPayload, Payload} from "../../utils/common/Structs.sol"; +// interface IGasAccountManager { +// function deposit(bytes calldata payload_) external; + +// function wrap(address receiver_) external payable; + +// function unwrap(uint256 amount_, address receiver_) external; + +// function isGasAvailable( +// address consumeFrom_, +// address spender_, +// uint256 amount_ +// ) external view returns (bool); + +// function withdrawToChain( +// uint32 chainSlug_, +// address token_, +// uint256 credits_, +// uint256 maxFees_, +// address receiver_ +// ) external; + +// function escrowGas(bytes32 payloadId_, address consumeFrom_, uint256 credits_) external; + +// function settleGasPayment(bytes32 payloadId_, address assignTo_, uint256 amount_) external; + +// function releaseEscrow(bytes32 payloadId_) external; + +// function isApproved(address appGateway_, address user_) external view returns (bool); + +// function setMaxFees(uint256 fees_) external; + +// function transferFrom(address from_, address to_, uint256 amount_) external returns (bool); +// } + interface IGasAccountManager { - function deposit(bytes calldata payload_) external; + /// @notice Emitted when fees deposited are updated + /// @param chainSlug The chain identifier + /// @param token The token address + /// @param depositTo The address to deposit to + /// @param creditAmount The credit amount added + /// @param nativeAmount The native amount transferred + event Deposited( + uint32 indexed chainSlug, + address indexed token, + address indexed depositTo, + uint256 creditAmount, + uint256 nativeAmount + ); - function wrap(address receiver_) external payable; + /// @notice Emitted when fees plug is set + event GasStationSet(uint32 indexed chainSlug, bytes32 indexed gasStation); - function unwrap(uint256 amount_, address receiver_) external; + /// @notice Emitted when fees pool is set + event GasVaultSet(address indexed gasVault); - function isGasAvailable( - address consumeFrom_, - address spender_, - uint256 amount_ - ) external view returns (bool); + /// @notice Emitted when fees plug solana program id is set + event SusdcSolanaProgramIdSet(bytes32 indexed susdcSolanaProgramId); - function withdrawToChain( - uint32 chainSlug_, - address token_, - uint256 credits_, - uint256 maxFees_, - address receiver_ - ) external; + /// @notice Emitted when fees plug solana program id is set + event GasStationSolanaProgramIdSet(bytes32 indexed gasStationSolanaProgramId); + + /// @notice Emitted when forwarder solana is set + event ForwarderSolanaSet(address indexed forwarderSolana); - function escrowGas(bytes32 payloadId_, address consumeFrom_, uint256 credits_) external; + /// @notice Emitted when max fees per chain slug is set + /// @param chainSlug The chain slug + /// @param fees The max fees + event MaxGasPerChainSlugSet(uint32 indexed chainSlug, uint256 fees); - function settleGasPayment(bytes32 payloadId_, address assignTo_, uint256 amount_) external; + /// @notice Emitted when credits are wrapped + event GasWrapped(address indexed consumeFrom, uint256 amount); + + /// @notice Emitted when credits are unwrapped + event GasUnwrapped(address indexed consumeFrom, uint256 amount); + + // ============ GAS ACCOUNT OPERATIONS ============ + + /// @notice Get available gas balance for an account + /// @dev Returns balance minus escrowed amount + function availableGas(address account) external view returns (uint256); + + /// @notice Get total gas balance including escrowed + function totalGas(address account) external view returns (uint256); + + /// @notice Get currently escrowed gas for an account + function escrowedGas(address account) external view returns (uint256); + + /// @notice Approve an app to spend gas from your account + function approveGasSpending(address app, uint256 amount) external; + + /// @notice Wrap native tokens into SGAS + function wrapToGas(address receiver) external payable; + + /// @notice Unwrap SGAS to native tokens + function unwrapFromGas(uint256 amount, address receiver) external; + + // ============ CROSS-CHAIN OPERATIONS ============ + + /// @notice Deposit tokens from a chain into gas account + /// @dev Called by watcher after detecting GasStation deposit + function depositFromChain( + uint32 chainSlug, + address token, + address account, + uint256 nativeAmount, + uint256 gasAmount + ) external; + + /// @notice Withdraw SGAS to tokens on another chain + function withdrawToChain( + uint32 chainSlug, + address token, + uint256 amount, + uint256 bridgeFee, + address receiver + ) external; - function releaseEscrow(bytes32 payloadId_) external; + // ============ REQUEST LIFECYCLE (Internal) ============ - function isApproved(address appGateway_, address user_) external view returns (bool); + /// @notice Escrow gas for a payload + /// @dev Called by RequestHandler when transmitter assigned + function escrowGas(bytes32 payloadId, address account, uint256 amount) external; - function setMaxFees(uint256 fees_) external; + /// @notice Release escrowed gas back to account + /// @dev Called when transmitter changes or payload cancelled + function releaseEscrow(bytes32 payloadId) external; - function transferFrom(address from_, address to_, uint256 amount_) external returns (bool); + /// @notice Settle escrowed gas to transmitter + /// @dev Called when payload completes successfully + function settleGasPayment(bytes32 payloadId, address transmitter) external; } diff --git a/contracts/evmx/interfaces/IGasEscrow.sol b/contracts/evmx/interfaces/IGasEscrow.sol index 2203b708..c774eb8a 100644 --- a/contracts/evmx/interfaces/IGasEscrow.sol +++ b/contracts/evmx/interfaces/IGasEscrow.sol @@ -2,8 +2,9 @@ pragma solidity ^0.8.21; import {EscrowEntry} from "../../utils/common/Structs.sol"; + /// @title Gas Escrow Manager -/// @notice Tracks escrowed gas during request lifecycle +/// @notice Tracks escrowed gas during payload lifecycle /// @dev Separates escrow logic from token logic for clarity interface IGasEscrow { /// @notice Emitted when fees are blocked for a batch @@ -29,7 +30,7 @@ interface IGasEscrow { uint256 amount ); - /// @notice Escrow gas for a request + /// @notice Escrow gas for a payload function escrowGas(bytes32 payloadId_, address consumeFrom_, uint256 amount_) external; /// @notice Release escrow back to account @@ -41,6 +42,6 @@ interface IGasEscrow { /// @notice Get total escrowed amount for an account function getEscrowedAmount(address account) external view returns (uint256); - /// @notice Get request escrow details + /// @notice Get payload escrow details function getPayloadEscrow(bytes32 payloadId) external view returns (EscrowEntry memory); } diff --git a/contracts/evmx/plugs/GasStation.sol b/contracts/evmx/plugs/GasStation.sol index 5b237880..75033eb3 100644 --- a/contracts/evmx/plugs/GasStation.sol +++ b/contracts/evmx/plugs/GasStation.sol @@ -13,7 +13,7 @@ import "../interfaces/IERC20.sol"; /// @title GasStation /// @notice Contract for managing fees on a network /// @dev The amount deposited here is locked and updated in the EVMx for an app gateway -/// @dev The fees are redeemed by the transmitters executing request or can be withdrawn by the owner +/// @dev The fees are redeemed by the transmitters executing payload or can be withdrawn by the owner contract GasStation is IGasStation, PlugBase, AccessControl { using SafeTransferLib for address; diff --git a/contracts/evmx/watcher/Watcher.sol b/contracts/evmx/watcher/Watcher.sol index ad8916db..af23ec3d 100644 --- a/contracts/evmx/watcher/Watcher.sol +++ b/contracts/evmx/watcher/Watcher.sol @@ -13,7 +13,7 @@ import {PAUSER_ROLE, UNPAUSER_ROLE} from "../../utils/common/AccessRoles.sol"; import "solady/utils/LibCall.sol"; /// @title Watcher -/// @notice Minimal request → payloads container with no batch/auction logic. +/// @notice Minimal payload → payloads container with no batch/auction logic. /// @dev Lives alongside existing Watcher without modifying current code. contract Watcher is Initializable, Configurations, Pausable { using LibCall for address; @@ -73,7 +73,7 @@ contract Watcher is Initializable, Configurations, Pausable { latestAppGateway = appGateway_; } - /// @notice Submit a request containing a single payload. No batches/auctions. + /// @notice Submit a payload containing a single payload. No batches/auctions. /// @dev Deploys promise via asyncDeployer and stores payload directly. function executePayload() external whenNotPaused returns (address asyncPromise) { if (latestAppGateway != msg.sender) revert AppGatewayMismatch(); @@ -128,7 +128,7 @@ contract Watcher is Initializable, Configurations, Pausable { _resolvePayload(resolvedPromise, feesUsed); } - /// @notice Mark a payload as resolved and complete its parent request when all are done. + /// @notice Mark a payload as resolved and complete its parent payload when all are done. function _resolvePayload( PromiseReturnData memory resolvedPromise_, uint256 feesUsed_ @@ -139,7 +139,12 @@ contract Watcher is Initializable, Configurations, Pausable { if (!p.isTransmitterFeesSettled) { p.isTransmitterFeesSettled = true; - gasAccountManager__().settleGasPayment(p.payloadId, transmitter, feesUsed_); + gasAccountManager__().settleGasPayment( + p.payloadId, + p.consumeFrom, + transmitter, + feesUsed_ + ); } p.isPayloadExecuted = true; @@ -149,7 +154,12 @@ contract Watcher is Initializable, Configurations, Pausable { bool success = _markResolved(resolvedPromise_); if (!success) return; - gasAccountManager__().settleGasPayment(p.payloadId, address(this), p.watcherFees); + gasAccountManager__().settleGasPayment( + p.payloadId, + p.consumeFrom, + address(this), + p.watcherFees + ); gasAccountManager__().releaseEscrow(p.payloadId); emit PayloadSettled(p.payloadId); emit PayloadResolved(resolvedPromise_.payloadId); @@ -182,11 +192,11 @@ contract Watcher is Initializable, Configurations, Pausable { _markRevert(resolvedPromise, isRevertingOnchain); } - /// @notice Marks a request as reverting - /// @param isRevertingOnchain_ Whether the request is reverting onchain + /// @notice Marks a payload as reverting + /// @param isRevertingOnchain_ Whether the payload is reverting onchain /// @param resolvedPromise_ The resolved promise - /// @dev This function marks a request as reverting - /// @dev It cancels the request and marks the promise as onchain reverting if the request is reverting onchain + /// @dev This function marks a payload as reverting + /// @dev It cancels the payload and marks the promise as onchain reverting if the payload is reverting onchain function _markRevert( PromiseReturnData memory resolvedPromise_, bool isRevertingOnchain_ @@ -196,10 +206,10 @@ contract Watcher is Initializable, Configurations, Pausable { Payload memory payloadParams = _payloads[payloadId]; if (payloadParams.deadline > block.timestamp) revert DeadlineNotPassedForOnChainRevert(); - // marks the request as cancelled and settles the fees + // marks the payload as cancelled and settles the fees cancelExecution(payloadId); - // marks the promise as onchain reverting if the request is reverting onchain + // marks the promise as onchain reverting if the payload is reverting onchain if (isRevertingOnchain_ && payloadParams.asyncPromise != address(0)) IPromise(payloadParams.asyncPromise).markOnchainRevert(resolvedPromise_); @@ -246,8 +256,8 @@ contract Watcher is Initializable, Configurations, Pausable { triggerFromPlug = bytes32(0); } - /// @notice Increases the fees for a request if no bid is placed - /// @param payloadId_ The ID of the request + /// @notice Increases the fees for a payload if no bid is placed + /// @param payloadId_ The ID of the payload /// @param newMaxFees_ The new maximum fees function increaseFees(bytes32 payloadId_, uint256 newMaxFees_) external { Payload storage r = _payloads[payloadId_]; diff --git a/contracts/evmx/watcher/precompiles/WritePrecompile.sol b/contracts/evmx/watcher/precompiles/WritePrecompile.sol index 9a01ff7b..5606f509 100644 --- a/contracts/evmx/watcher/precompiles/WritePrecompile.sol +++ b/contracts/evmx/watcher/precompiles/WritePrecompile.sol @@ -300,7 +300,7 @@ contract WritePrecompile is WritePrecompileStorage, Initializable, Ownable { } /// @notice Marks a write request with a proof on digest - /// @param payloadId_ The unique identifier of the request + /// @param payloadId_ The unique identifier of the payload /// @param proof_ The watcher's proof function uploadProof(bytes32 payloadId_, bytes memory proof_) public onlyWatcher { watcherProofs[payloadId_] = proof_; From 5624e5e72ce6adcf6cf5011296f018d8145b9659 Mon Sep 17 00:00:00 2001 From: Ameesha Agrawal Date: Wed, 12 Nov 2025 23:43:30 +0530 Subject: [PATCH 07/12] fix: renames --- contracts/evmx/fees/GasAccountManager.sol | 4 +-- .../evmx/interfaces/IGasAccountManager.sol | 4 +-- contracts/evmx/watcher/Watcher.sol | 2 +- .../watcher/precompiles/WritePrecompile.sol | 4 +-- contracts/protocol/interfaces/ISocket.sol | 2 +- contracts/utils/common/Errors.sol | 8 +---- contracts/utils/common/Structs.sol | 7 +--- hardhat-scripts/deploy/4.configureEVMx.ts | 33 ------------------- hardhat-scripts/test/chainTest.ts | 5 ++- src/enums.ts | 2 -- 10 files changed, 12 insertions(+), 59 deletions(-) diff --git a/contracts/evmx/fees/GasAccountManager.sol b/contracts/evmx/fees/GasAccountManager.sol index a76d2890..9eb6955c 100644 --- a/contracts/evmx/fees/GasAccountManager.sol +++ b/contracts/evmx/fees/GasAccountManager.sol @@ -14,7 +14,7 @@ import {OverrideParamsLib} from "../../utils/common/OverrideParamsLib.sol"; import {OverrideParams, SolanaInstruction, SolanaInstructionData, SolanaInstructionDataDescription} from "../../utils/common/Structs.sol"; import {toBytes32Format} from "../../utils/common/Converters.sol"; import {WRITE, CHAIN_SLUG_SOLANA_MAINNET} from "../../utils/common/Constants.sol"; -import {NonceUsed, InvalidAmount, InsufficientCreditsAvailable, InsufficientBalance, InvalidChainSlug, NotRequestHandler, InvalidReceiver} from "../../utils/common/Errors.sol"; +import {NonceUsed, InvalidAmount, InsufficientCreditsAvailable, InsufficientBalance, InvalidChainSlug, InvalidReceiver} from "../../utils/common/Errors.sol"; import "../../utils/RescueFundsLib.sol"; import {AddressResolverUtil} from "../helpers/AddressResolverUtil.sol"; @@ -191,7 +191,7 @@ contract GasAccountManager is IGasAccountManager, Ownable, AccessControl, AppGat // ============ PAYLOAD LIFECYCLE (Internal) ============ /// @notice Escrow gas for a payload - /// @dev Called by RequestHandler when transmitter assigned + /// @dev Called by Watcher when payload is submitted function escrowGas(bytes32 payloadId, address account, uint256 amount) external onlyWatcher { if (gasAccountToken.balanceOf(account) < amount) revert InsufficientCreditsAvailable(); gasEscrow.escrowGas(payloadId, account, amount); diff --git a/contracts/evmx/interfaces/IGasAccountManager.sol b/contracts/evmx/interfaces/IGasAccountManager.sol index 45c28a85..90145f15 100644 --- a/contracts/evmx/interfaces/IGasAccountManager.sol +++ b/contracts/evmx/interfaces/IGasAccountManager.sol @@ -119,10 +119,10 @@ interface IGasAccountManager { address receiver ) external; - // ============ REQUEST LIFECYCLE (Internal) ============ + // ============ PAYLOAD LIFECYCLE (Internal) ============ /// @notice Escrow gas for a payload - /// @dev Called by RequestHandler when transmitter assigned + /// @dev Called by W when transmitter assigned function escrowGas(bytes32 payloadId, address account, uint256 amount) external; /// @notice Release escrowed gas back to account diff --git a/contracts/evmx/watcher/Watcher.sol b/contracts/evmx/watcher/Watcher.sol index af23ec3d..c7395259 100644 --- a/contracts/evmx/watcher/Watcher.sol +++ b/contracts/evmx/watcher/Watcher.sol @@ -278,7 +278,7 @@ contract Watcher is Initializable, Configurations, Pausable { ) revert InsufficientFees(); gasAccountManager__().escrowGas(payloadId_, r.consumeFrom, newMaxFees_); - // indexed by transmitter and watcher to start bidding or re-processing the request + // indexed by transmitter and watcher to start bidding or re-processing the payload emit FeesIncreased(payloadId_, newMaxFees_); } diff --git a/contracts/evmx/watcher/precompiles/WritePrecompile.sol b/contracts/evmx/watcher/precompiles/WritePrecompile.sol index 5606f509..2c27f8ff 100644 --- a/contracts/evmx/watcher/precompiles/WritePrecompile.sol +++ b/contracts/evmx/watcher/precompiles/WritePrecompile.sol @@ -61,7 +61,7 @@ contract WritePrecompile is WritePrecompileStorage, Initializable, Ownable { event WriteProofRequested(bytes32 digest, uint256 deadline, RawPayload rawPayload); /// @notice Emitted when a proof is uploaded - /// @param payloadId The unique identifier for the request + /// @param payloadId The unique identifier for the payload /// @param proof The proof from the watcher event WriteProofUploaded(bytes32 indexed payloadId, bytes proof); event ExpiryTimeSet(uint256 expiryTime); @@ -299,7 +299,7 @@ contract WritePrecompile is WritePrecompileStorage, Initializable, Ownable { return chainSlug_ == CHAIN_SLUG_SOLANA_MAINNET || chainSlug_ == CHAIN_SLUG_SOLANA_DEVNET; } - /// @notice Marks a write request with a proof on digest + /// @notice Marks a write payload with a proof on digest /// @param payloadId_ The unique identifier of the payload /// @param proof_ The watcher's proof function uploadProof(bytes32 payloadId_, bytes memory proof_) public onlyWatcher { diff --git a/contracts/protocol/interfaces/ISocket.sol b/contracts/protocol/interfaces/ISocket.sol index 5432b7e3..49e82841 100644 --- a/contracts/protocol/interfaces/ISocket.sol +++ b/contracts/protocol/interfaces/ISocket.sol @@ -59,7 +59,7 @@ interface ISocket { * @notice Event emitted when a payload is requested (for both triggers and messages) * @param payloadId The created payload ID * @param plug The source plug address - * @param switchboardId The switchboard ID processing the request + * @param switchboardId The switchboard ID processing the payload * @param overrides The override parameters * @param payload The payload data */ diff --git a/contracts/utils/common/Errors.sol b/contracts/utils/common/Errors.sol index 9068e7c7..5d7fe159 100644 --- a/contracts/utils/common/Errors.sol +++ b/contracts/utils/common/Errors.sol @@ -47,8 +47,7 @@ error InvalidCaller(); /// @notice Error thrown when a gateway is invalid error InvalidGateway(); -/// @notice Error thrown when a request is already cancelled -error RequestAlreadyCancelled(); +/// @notice Error thrown when a payload is already cancelled error DeadlineNotPassedForOnChainRevert(); error InvalidBid(); @@ -58,12 +57,9 @@ error MaxMsgValueLimitExceeded(); error OnlyWatcherAllowed(); error InvalidPrecompileData(); error InvalidCallType(); -error NotRequestHandler(); error NotInvoker(); error NotPromiseResolver(); -error RequestPayloadCountLimitExceeded(); error InsufficientFees(); -error RequestAlreadySettled(); error NoWriteRequest(); error AlreadyAssigned(); @@ -75,6 +71,4 @@ error InvalidSignature(); error DeadlinePassed(); // Only Watcher can call functions -error OnlyRequestHandlerAllowed(); -error OnlyPromiseResolverAllowed(); error InvalidReceiver(); diff --git a/contracts/utils/common/Structs.sol b/contracts/utils/common/Structs.sol index 67754d59..39d995b2 100644 --- a/contracts/utils/common/Structs.sol +++ b/contracts/utils/common/Structs.sol @@ -31,7 +31,7 @@ enum ExecutionStatus { enum EscrowState { None, // No escrow - Active, // Escrowed, request in progress + Active, // Escrowed, payload in progress Released, // Returned to account Settled // Paid to transmitter } @@ -109,11 +109,6 @@ struct WatcherMultiCallParams { bytes signature; } -struct CreateRequestResult { - uint256 totalEstimatedWatcherFees; - Payload payload; -} - struct UserCredits { uint256 totalCredits; uint256 payloadEscrow; diff --git a/hardhat-scripts/deploy/4.configureEVMx.ts b/hardhat-scripts/deploy/4.configureEVMx.ts index 22a4ad2e..8aadd97f 100644 --- a/hardhat-scripts/deploy/4.configureEVMx.ts +++ b/hardhat-scripts/deploy/4.configureEVMx.ts @@ -110,7 +110,6 @@ export const configureEVMx = async (evmxAddresses: EVMxAddressesObj) => { signer ); - // await setWatcherCoreContracts(evmxAddresses); }; const checkAndSetMaxFees = async (evmxAddresses: EVMxAddressesObj) => { @@ -154,38 +153,6 @@ const checkAndSetMaxFees = async (evmxAddresses: EVMxAddressesObj) => { } }; -export const setWatcherCoreContracts = async ( - evmxAddresses: EVMxAddressesObj -) => { - const watcherContract = ( - await getInstance(Contracts.Watcher, evmxAddresses[Contracts.Watcher]) - ).connect(getWatcherSigner()); - - const requestHandlerSet = await watcherContract.requestHandler__(); - const PromiseResolverSet = await watcherContract.promiseResolver__(); - const ConfigurationsSet = await watcherContract.configurations__(); - - if ( - requestHandlerSet.toLowerCase() !== - evmxAddresses[Contracts.RequestHandler].toLowerCase() || - PromiseResolverSet.toLowerCase() !== - evmxAddresses[Contracts.PromiseResolver].toLowerCase() || - ConfigurationsSet.toLowerCase() !== - evmxAddresses[Contracts.Configurations].toLowerCase() - ) { - console.log("Setting watcher core contracts"); - const tx = await watcherContract.setCoreContracts( - evmxAddresses[Contracts.RequestHandler], - evmxAddresses[Contracts.Configurations], - evmxAddresses[Contracts.PromiseResolver], - { ...(await overrides(EVMX_CHAIN_ID)) } - ); - console.log("Watcher core contracts set tx: ", tx.hash); - await tx.wait(); - } else { - console.log("Watcher core contracts are already set"); - } -}; main() .then(() => process.exit(0)) diff --git a/hardhat-scripts/test/chainTest.ts b/hardhat-scripts/test/chainTest.ts index 37b9c5e7..dec5044e 100644 --- a/hardhat-scripts/test/chainTest.ts +++ b/hardhat-scripts/test/chainTest.ts @@ -41,7 +41,7 @@ interface StatusResponse { status: string; response: Array<{ status: string; - requestCount: number; + payloadCount: number; writePayloads: Array<{ payloadId: string; chainSlug: number; @@ -223,8 +223,7 @@ class ChainTester { } } catch (error) { console.log( - ` API error: ${ - error instanceof Error ? error.message : String(error) + ` API error: ${error instanceof Error ? error.message : String(error) }` ); retries++; diff --git a/src/enums.ts b/src/enums.ts index 39cd7569..c6c8d564 100644 --- a/src/enums.ts +++ b/src/enums.ts @@ -27,7 +27,6 @@ export enum Events { // Configurations PlugAdded = "PlugAdded", - // RequestHandler FeesIncreased = "FeesIncreased", PayloadSubmitted = "PayloadSubmitted", PayloadResolved = "PayloadResolved", @@ -64,7 +63,6 @@ export enum Contracts { NetworkFeeCollector = "NetworkFeeCollector", AddressResolver = "AddressResolver", Watcher = "Watcher", - RequestHandler = "RequestHandler", Configurations = "Configurations", PromiseResolver = "PromiseResolver", GasAccountManager = "GasAccountManager", From 662a8caa7615cd1c1396acb060e519f071e14241 Mon Sep 17 00:00:00 2001 From: Ameesha Agrawal Date: Thu, 13 Nov 2025 00:11:07 +0530 Subject: [PATCH 08/12] fix: build --- contracts/evmx/base/AppGatewayBase.sol | 12 ++--- contracts/evmx/fees/GasAccountManager.sol | 50 ++++++++----------- contracts/evmx/fees/GasAccountToken.sol | 49 ++++++++++-------- contracts/evmx/fees/GasEscrow.sol | 13 +++-- contracts/evmx/fees/GasVault.sol | 1 + contracts/evmx/fees/MessageResolver.sol | 4 +- contracts/evmx/helpers/AddressResolver.sol | 38 ++++++++++++-- .../evmx/helpers/AddressResolverUtil.sol | 25 ++++++++++ .../evmx/interfaces/IAddressResolver.sol | 31 ++++++++++-- .../evmx/interfaces/IGasAccountManager.sol | 50 +++---------------- .../{IERC20.sol => IGasAccountToken.sol} | 14 +++++- contracts/evmx/interfaces/IGasEscrow.sol | 4 +- contracts/evmx/plugs/GasStation.sol | 6 +-- contracts/evmx/watcher/Watcher.sol | 12 ++--- .../WithdrawFeesArbitrumFeesPlug.s.sol | 10 ++-- script/helpers/CheckDepositedCredits.s.sol | 16 +++--- script/helpers/TransferRemainingCredits.s.sol | 10 ++-- script/helpers/WithdrawRemainingCredits.s.sol | 18 ++++--- test/SetupTest.t.sol | 2 +- 19 files changed, 216 insertions(+), 149 deletions(-) rename contracts/evmx/interfaces/{IERC20.sol => IGasAccountToken.sol} (71%) diff --git a/contracts/evmx/base/AppGatewayBase.sol b/contracts/evmx/base/AppGatewayBase.sol index 16b61348..9870de7b 100644 --- a/contracts/evmx/base/AppGatewayBase.sol +++ b/contracts/evmx/base/AppGatewayBase.sol @@ -5,7 +5,7 @@ import "../helpers/AddressResolverUtil.sol"; import "../interfaces/IAppGateway.sol"; import "../interfaces/IForwarder.sol"; import "../interfaces/IPromise.sol"; -import "../interfaces/IERC20.sol"; +import "../interfaces/IGasAccountToken.sol"; import {InvalidPromise, AsyncModifierNotSet} from "../../utils/common/Errors.sol"; import {FAST, READ, WRITE, SCHEDULE} from "../../utils/common/Constants.sol"; @@ -111,8 +111,7 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway { return bytes32(0); } - onChainAddress = IForwarder(forwarderAddresses[contractId_][chainSlug_]) - .getOnChainAddress(); + onChainAddress = IForwarder(forwarderAddresses[contractId_][chainSlug_]).getOnChainAddress(); } //////////////////////////////////////////////////////////////////////////////////////////////// @@ -160,7 +159,7 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway { uint256 nonce, bytes memory signature ) = abi.decode(feesApprovalData_, (address, uint256, uint256, uint256, bytes)); - IERC20(address(gasAccountManager__())).permit(spender, value, deadline, nonce, signature); + gasAccountToken__().permit(spender, value, deadline, nonce, signature); } /// @notice Withdraws fee tokens @@ -174,10 +173,7 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway { uint256 amount_, address receiver_ ) internal { - IERC20(address(gasAccountManager__())).approve( - address(gasAccountManager__()), - type(uint256).max - ); + gasAccountToken__().approve(address(gasAccountManager__()), type(uint256).max); gasAccountManager__().withdrawToChain( chainSlug_, token_, diff --git a/contracts/evmx/fees/GasAccountManager.sol b/contracts/evmx/fees/GasAccountManager.sol index 9eb6955c..a8df580a 100644 --- a/contracts/evmx/fees/GasAccountManager.sol +++ b/contracts/evmx/fees/GasAccountManager.sol @@ -10,7 +10,7 @@ import "../interfaces/IGasAccountToken.sol"; import "../interfaces/IGasStation.sol"; import "../../utils/AccessControl.sol"; import "../../utils/common/AccessRoles.sol"; -import {OverrideParamsLib} from "../../utils/common/OverrideParamsLib.sol"; +import "../../utils/OverrideParamsLib.sol"; import {OverrideParams, SolanaInstruction, SolanaInstructionData, SolanaInstructionDataDescription} from "../../utils/common/Structs.sol"; import {toBytes32Format} from "../../utils/common/Converters.sol"; import {WRITE, CHAIN_SLUG_SOLANA_MAINNET} from "../../utils/common/Constants.sol"; @@ -83,7 +83,7 @@ contract GasAccountManager is IGasAccountManager, Ownable, AccessControl, AppGat // reverts if transfer fails SafeTransferLib.safeTransferETH(address(gasVault), amount); - emit GasWrapped(receiver_, amount); + emit GasWrapped(receiver, amount); } /// @notice Unwrap SGAS to native tokens @@ -103,39 +103,33 @@ contract GasAccountManager is IGasAccountManager, Ownable, AccessControl, AppGat /// @notice Deposit tokens from a chain into gas account /// @dev Called by watcher after detecting GasStation deposit - function depositFromChain( - uint32 chainSlug, - address token, - address account, - uint256 nativeAmount, - uint256 gasAmount - ) external onlyWatcher { + function depositFromChain(bytes memory payload_) external onlyWatcher { // Decode payload: (chainSlug, token, receiver, creditAmount, nativeAmount) ( - uint32 chainSlug_, - address token_, - address depositTo_, - uint256 creditAmount_, - uint256 nativeAmount_ + uint32 chainSlug, + address token, + address depositTo, + uint256 gasAmount, + uint256 nativeAmount ) = abi.decode(payload_, (uint32, address, address, uint256, uint256)); - tokenOnChainBalances[chainSlug_][toBytes32Format(token_)] += creditAmount_ + nativeAmount_; + tokenOnChainBalances[chainSlug][toBytes32Format(token)] += gasAmount + nativeAmount; // Mint tokens to the user - gasAccountToken.mint(depositTo_, creditAmount_); - if (nativeAmount_ > 0) { + gasAccountToken.mint(depositTo, gasAmount); + if (nativeAmount > 0) { // if native transfer fails, add to credit - bool success = gasVault.withdraw(depositTo_, nativeAmount_); + bool success = gasVault.withdraw(depositTo, nativeAmount); if (!success) { // Convert failed native amount to credits - gasAccountToken.mint(depositTo_, nativeAmount_); - creditAmount_ += nativeAmount_; - nativeAmount_ = 0; + gasAccountToken.mint(depositTo, nativeAmount); + gasAmount += nativeAmount; + nativeAmount = 0; } } - emit Deposited(chainSlug_, token_, depositTo_, creditAmount_, nativeAmount_); + emit Deposited(chainSlug, token, depositTo, gasAmount, nativeAmount); } /// @notice Withdraw SGAS to tokens on another chain @@ -149,8 +143,8 @@ contract GasAccountManager is IGasAccountManager, Ownable, AccessControl, AppGat address consumeFrom = msg.sender; // Check if amount is available in fees plug - uint256 availableGas = gasAccountToken.balanceOf(consumeFrom); - if (availableGas < amount + bridgeFee) revert InsufficientCreditsAvailable(); + uint256 gasBalance = gasAccountToken.balanceOf(consumeFrom); + if (gasBalance < amount + bridgeFee) revert InsufficientCreditsAvailable(); // Burn tokens from sender gasAccountToken.burn(consumeFrom, amount); @@ -161,7 +155,7 @@ contract GasAccountManager is IGasAccountManager, Ownable, AccessControl, AppGat chainSlug, consumeFrom, bridgeFee, - abi.encodeCall(IGasStation.withdrawFees, (token, receiver, amount)) + abi.encodeCall(IGasStation.withdrawToTokens, (token, receiver, amount)) ); } @@ -209,13 +203,11 @@ contract GasAccountManager is IGasAccountManager, Ownable, AccessControl, AppGat bytes32 payloadId, address consumeFrom, address transmitter, - uint256 amount_ + uint256 amount ) external onlyWatcher { gasEscrow.settleGasPayment(payloadId, transmitter, amount); gasAccountToken.burn(consumeFrom, amount); gasAccountToken.mint(transmitter, amount); - - emit GasPaymentSettled(payloadId, transmitter, amount); } /// @notice Get available gas balance for an account @@ -231,7 +223,7 @@ contract GasAccountManager is IGasAccountManager, Ownable, AccessControl, AppGat /// @notice Get currently escrowed gas for an account function escrowedGas(address account) external view override returns (uint256) { - return gasEscrow.escrowedBalanceOf(account); + return gasEscrow.getEscrowedAmount(account); } /// @notice Approve an app to spend gas from your account diff --git a/contracts/evmx/fees/GasAccountToken.sol b/contracts/evmx/fees/GasAccountToken.sol index ca98358f..f2610daf 100644 --- a/contracts/evmx/fees/GasAccountToken.sol +++ b/contracts/evmx/fees/GasAccountToken.sol @@ -5,30 +5,42 @@ import "solady/auth/Ownable.sol"; import "solady/tokens/ERC20.sol"; import "../../utils/RescueFundsLib.sol"; import "solady/utils/Initializable.sol"; +import "../interfaces/IAddressResolver.sol"; +import "../interfaces/IGasAccountToken.sol"; /// @title Socket Gas Token (SGAS) /// @notice ERC20 token representing prepaid gas for Socket operations /// @dev Balances are split between available and escrowed contract GasAccountToken is ERC20, Ownable { - string public constant name = "Socket Gas"; - string public constant symbol = "SGAS"; - uint8 public constant decimals = 18; - /// @notice Escrow tracker for gas in active payloads - IGasEscrow public gasEscrow; - IGasAccountManager public gasAccountManager; + IAddressResolver public addressResolver__; + + error NotGasAccountManager(); + error InsufficientCreditsAvailable(); modifier onlyGasAccountManager() { - if (msg.sender != address(gasAccountManager)) revert NotGasAccountManager(); + if (msg.sender != address(addressResolver__.gasAccountManager__())) + revert NotGasAccountManager(); _; } - constructor(address owner_, address gasEscrow_, address gasAccountManager_) { - gasEscrow = IGasEscrow(gasEscrow_); - gasAccountManager = IGasAccountManager(gasAccountManager_); + constructor(address owner_, address addressResolver_) { + addressResolver__ = IAddressResolver(addressResolver_); _setOwner(owner_); } + function decimals() public view override returns (uint8) { + return 18; + } + + function symbol() public view override returns (string memory) { + return "SGAS"; + } + + function name() public view override returns (string memory) { + return "Socket Gas"; + } + function mint(address account, uint256 amount) external onlyGasAccountManager { _mint(account, amount); } @@ -40,7 +52,8 @@ contract GasAccountToken is ERC20, Ownable { /// @notice Returns available (spendable) gas balance /// @dev Subtracts escrowed amount from total balance function balanceOf(address account) public view override returns (uint256) { - return super.balanceOf(account) - gasEscrow.getEscrowedAmount(account); + return + super.balanceOf(account) - addressResolver__.gasEscrow__().getEscrowedAmount(account); } /// @notice Returns total gas balance including escrowed @@ -48,11 +61,6 @@ contract GasAccountToken is ERC20, Ownable { return super.balanceOf(account); } - /// @notice Returns escrowed gas that's locked in active payloads - function escrowedBalanceOf(address account) public view returns (uint256) { - return gasEscrow.getEscrowedAmount(account); - } - // ERC20 Overrides to handle blocked credits /// @notice Override transfer to check for blocked credits function transfer(address to, uint256 amount) public override returns (bool) { @@ -65,11 +73,12 @@ contract GasAccountToken is ERC20, Ownable { address from_, address to_, uint256 amount_ - ) public override(ERC20, IGasAccountManager) returns (bool) { + ) public override returns (bool) { if (!isGasAvailable(from_, msg.sender, amount_)) revert InsufficientCreditsAvailable(); // todo: check - if (msg.sender == address(watcher__())) _approve(from_, msg.sender, amount_); + if (msg.sender == address(addressResolver__.watcher__())) + _approve(from_, msg.sender, amount_); return super.transferFrom(from_, to_, amount_); } @@ -82,9 +91,9 @@ contract GasAccountToken is ERC20, Ownable { address consumeFrom_, address spender_, uint256 amount_ - ) public view override returns (bool) { + ) public view returns (bool) { // If consumeFrom_ is not same as spender_ or spender_ is not watcher, check if it is approved - if (spender_ != address(watcher__()) && consumeFrom_ != spender_) { + if (spender_ != address(addressResolver__.watcher__()) && consumeFrom_ != spender_) { if (allowance(consumeFrom_, spender_) == 0) return false; } diff --git a/contracts/evmx/fees/GasEscrow.sol b/contracts/evmx/fees/GasEscrow.sol index 9d197b72..6c451577 100644 --- a/contracts/evmx/fees/GasEscrow.sol +++ b/contracts/evmx/fees/GasEscrow.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.21; import "../interfaces/IGasEscrow.sol"; +import "../../utils/RescueFundsLib.sol"; /// @title Gas Escrow Manager /// @notice Tracks escrowed gas during payload lifecycle @@ -16,6 +17,8 @@ contract GasEscrow is IGasEscrow { mapping(bytes32 => EscrowEntry) public payloadEscrow; error NotGasAccountManager(); + error NotActive(); + error NoEscrow(); modifier onlyGasAccountManager() { if (msg.sender != gasAccountManager) revert NotGasAccountManager(); @@ -57,14 +60,14 @@ contract GasEscrow is IGasEscrow { function settleGasPayment( bytes32 payloadId, address transmitter, - uint256 amount_ + uint256 amount ) external onlyGasAccountManager { EscrowEntry storage entry = payloadEscrow[payloadId]; if (entry.state != EscrowState.Active) revert NotActive(); if (entry.amount == 0) revert NoEscrow(); accountEscrow[entry.account] -= amount; - entry.amount -= amount_; + entry.amount -= amount; if (entry.amount == 0) entry.state = EscrowState.Settled; emit EscrowSettled(payloadId, entry.account, transmitter, amount); @@ -87,7 +90,11 @@ contract GasEscrow is IGasEscrow { * @param rescueTo_ The address where rescued tokens need to be sent. * @param amount_ The amount of tokens to be rescued. */ - function rescueFunds(address token_, address rescueTo_, uint256 amount_) external onlyWatcher { + function rescueFunds( + address token_, + address rescueTo_, + uint256 amount_ + ) external onlyGasAccountManager { RescueFundsLib._rescueFunds(token_, rescueTo_, amount_); } } diff --git a/contracts/evmx/fees/GasVault.sol b/contracts/evmx/fees/GasVault.sol index e03440d6..50ad4d71 100644 --- a/contracts/evmx/fees/GasVault.sol +++ b/contracts/evmx/fees/GasVault.sol @@ -12,6 +12,7 @@ import "solady/utils/SafeTransferLib.sol"; */ contract GasVault is IGasVault, AccessControl { error TransferFailed(); + event VaultDeposit(address indexed from, uint256 amount); /** * @param owner_ The address of the owner diff --git a/contracts/evmx/fees/MessageResolver.sol b/contracts/evmx/fees/MessageResolver.sol index 87668ea9..3ac666c5 100644 --- a/contracts/evmx/fees/MessageResolver.sol +++ b/contracts/evmx/fees/MessageResolver.sol @@ -267,7 +267,7 @@ contract MessageResolver is // Check sponsor has sufficient credits (uses AddressResolver to get latest GasAccountManager) if ( - !gasAccountManager__().isGasAvailable(details.sponsor, address(this), details.feeAmount) + !gasAccountToken__().isGasAvailable(details.sponsor, address(this), details.feeAmount) ) { revert InsufficientSponsorCredits(); } @@ -276,7 +276,7 @@ contract MessageResolver is details.status = ExecutionStatus.Executed; // Transfer credits from sponsor to transmitter using GasAccountManager from AddressResolver - bool success = gasAccountManager__().transferFrom( + bool success = gasAccountToken__().transferFrom( details.sponsor, details.transmitter, details.feeAmount diff --git a/contracts/evmx/helpers/AddressResolver.sol b/contracts/evmx/helpers/AddressResolver.sol index b81dbb68..2925c9e4 100644 --- a/contracts/evmx/helpers/AddressResolver.sol +++ b/contracts/evmx/helpers/AddressResolver.sol @@ -20,6 +20,15 @@ abstract contract AddressResolverStorage is IAddressResolver { // slot 52 IAsyncDeployer public override asyncDeployer__; + // slot 53 + IGasVault public override gasVault__; + + // slot 54 + IGasEscrow public override gasEscrow__; + + // slot 55 + IGasAccountToken public override gasAccountToken__; + // slot 55 mapping(bytes32 => address) public override contractAddresses; } @@ -52,21 +61,21 @@ contract AddressResolver is AddressResolverStorage, Initializable, Ownable { /// @param watcher_ The address of the watcher contract function setWatcher(address watcher_) external override onlyOwner { watcher__ = IWatcher(watcher_); - emit WatcherUpdated(watcher_); + emit WatcherSet(watcher_); } /// @notice Updates the address of the fees manager /// @param gasAccountManager_ The address of the fees manager function setGasAccountManager(address gasAccountManager_) external override onlyOwner { gasAccountManager__ = IGasAccountManager(gasAccountManager_); - emit GasAccountManagerUpdated(gasAccountManager_); + emit GasAccountManagerSet(gasAccountManager_); } /// @notice Updates the address of the async deployer /// @param asyncDeployer_ The address of the async deployer function setAsyncDeployer(address asyncDeployer_) external override onlyOwner { asyncDeployer__ = IAsyncDeployer(asyncDeployer_); - emit AsyncDeployerUpdated(asyncDeployer_); + emit AsyncDeployerSet(asyncDeployer_); } /// @notice Updates the address of a contract @@ -77,7 +86,28 @@ contract AddressResolver is AddressResolverStorage, Initializable, Ownable { address contractAddress_ ) external override onlyOwner { contractAddresses[contractId_] = contractAddress_; - emit ContractAddressUpdated(contractId_, contractAddress_); + emit ContractAddressSet(contractId_, contractAddress_); + } + + /// @notice Updates the address of the gas vault + /// @param gasVault_ The address of the gas vault + function setGasVault(address gasVault_) external override onlyOwner { + gasVault__ = IGasVault(gasVault_); + emit GasVaultSet(gasVault_); + } + + /// @notice Updates the address of the gas escrow + /// @param gasEscrow_ The address of the gas escrow + function setGasEscrow(address gasEscrow_) external override onlyOwner { + gasEscrow__ = IGasEscrow(gasEscrow_); + emit GasEscrowSet(gasEscrow_); + } + + /// @notice Updates the address of the gas account token + /// @param gasAccountToken_ The address of the gas account token + function setGasAccountToken(address gasAccountToken_) external override onlyOwner { + gasAccountToken__ = IGasAccountToken(gasAccountToken_); + emit GasAccountTokenSet(gasAccountToken_); } /** diff --git a/contracts/evmx/helpers/AddressResolverUtil.sol b/contracts/evmx/helpers/AddressResolverUtil.sol index a588ca7a..fe622bd9 100644 --- a/contracts/evmx/helpers/AddressResolverUtil.sol +++ b/contracts/evmx/helpers/AddressResolverUtil.sol @@ -5,6 +5,10 @@ import "../interfaces/IAddressResolver.sol"; import "../interfaces/IWatcher.sol"; import "../interfaces/IGasAccountManager.sol"; import "../interfaces/IAsyncDeployer.sol"; +import "../interfaces/IGasVault.sol"; +import "../interfaces/IGasEscrow.sol"; +import "../interfaces/IGasAccountToken.sol"; + import {OnlyWatcherAllowed} from "../../utils/common/Errors.sol"; /// @title AddressResolverUtil @@ -47,6 +51,27 @@ abstract contract AddressResolverUtil { return addressResolver__.gasAccountManager__(); } + /// @notice Gets the gas account manager contract interface + /// @return IGasAccountManager interface of the registered gas account manager + /// @dev Resolves and returns the gas account manager contract for interaction + function gasVault__() public view returns (IGasVault) { + return addressResolver__.gasVault__(); + } + + /// @notice Gets the gas vault contract interface + /// @return IGasVault interface of the registered gas vault + /// @dev Resolves and returns the gas vault contract for interaction + function gasEscrow__() public view returns (IGasEscrow) { + return addressResolver__.gasEscrow__(); + } + + /// @notice Gets the gas escrow contract interface + /// @return IGasEscrow interface of the registered gas escrow + /// @dev Resolves and returns the gas escrow contract for interaction + function gasAccountToken__() public view returns (IGasAccountToken) { + return addressResolver__.gasAccountToken__(); + } + /// @notice Gets the async deployer contract interface /// @return IAsyncDeployer interface of the registered async deployer /// @dev Resolves and returns the async deployer contract for interaction diff --git a/contracts/evmx/interfaces/IAddressResolver.sol b/contracts/evmx/interfaces/IAddressResolver.sol index 69ff97ef..5692fb9d 100644 --- a/contracts/evmx/interfaces/IAddressResolver.sol +++ b/contracts/evmx/interfaces/IAddressResolver.sol @@ -3,25 +3,40 @@ pragma solidity ^0.8.21; import "./IWatcher.sol"; import "./IGasAccountManager.sol"; import "./IAsyncDeployer.sol"; +import "./IGasVault.sol"; +import "./IGasEscrow.sol"; +import "./IGasAccountToken.sol"; /// @title IAddressResolver /// @notice Interface for resolving system contract addresses /// @dev Provides address lookup functionality for core system components interface IAddressResolver { - /// @notice Event emitted when the fees manager is updated - event GasAccountManagerUpdated(address gasAccountManager_); + /// @notice Event emitted when the gas account manager is updated + event GasAccountManagerSet(address gasAccountManager_); + /// @notice Event emitted when the gas vault is updated + event GasVaultSet(address gasVault_); + /// @notice Event emitted when the gas escrow is updated + event GasEscrowSet(address gasEscrow_); + /// @notice Event emitted when the gas account token is updated + event GasAccountTokenSet(address gasAccountToken_); /// @notice Event emitted when the watcher precompile is updated - event WatcherUpdated(address watcher_); + event WatcherSet(address watcher_); /// @notice Event emitted when the async deployer is updated - event AsyncDeployerUpdated(address asyncDeployer_); + event AsyncDeployerSet(address asyncDeployer_); /// @notice Event emitted when the contract address is updated - event ContractAddressUpdated(bytes32 contractId_, address contractAddress_); + event ContractAddressSet(bytes32 contractId_, address contractAddress_); // System component addresses function watcher__() external view returns (IWatcher); function gasAccountManager__() external view returns (IGasAccountManager); + function gasVault__() external view returns (IGasVault); + + function gasEscrow__() external view returns (IGasEscrow); + + function gasAccountToken__() external view returns (IGasAccountToken); + function asyncDeployer__() external view returns (IAsyncDeployer); function contractAddresses(bytes32 contractId_) external view returns (address); @@ -32,5 +47,11 @@ interface IAddressResolver { function setAsyncDeployer(address asyncDeployer_) external; + function setGasVault(address gasVault_) external; + + function setGasEscrow(address gasEscrow_) external; + + function setGasAccountToken(address gasAccountToken_) external; + function setContractAddress(bytes32 contractId_, address contractAddress_) external; } diff --git a/contracts/evmx/interfaces/IGasAccountManager.sol b/contracts/evmx/interfaces/IGasAccountManager.sol index 90145f15..7a3b6d66 100644 --- a/contracts/evmx/interfaces/IGasAccountManager.sol +++ b/contracts/evmx/interfaces/IGasAccountManager.sol @@ -2,40 +2,6 @@ pragma solidity ^0.8.21; import {WriteFinality, AppGatewayApprovals, OverrideParams, Transaction, RawPayload, Payload} from "../../utils/common/Structs.sol"; -// interface IGasAccountManager { -// function deposit(bytes calldata payload_) external; - -// function wrap(address receiver_) external payable; - -// function unwrap(uint256 amount_, address receiver_) external; - -// function isGasAvailable( -// address consumeFrom_, -// address spender_, -// uint256 amount_ -// ) external view returns (bool); - -// function withdrawToChain( -// uint32 chainSlug_, -// address token_, -// uint256 credits_, -// uint256 maxFees_, -// address receiver_ -// ) external; - -// function escrowGas(bytes32 payloadId_, address consumeFrom_, uint256 credits_) external; - -// function settleGasPayment(bytes32 payloadId_, address assignTo_, uint256 amount_) external; - -// function releaseEscrow(bytes32 payloadId_) external; - -// function isApproved(address appGateway_, address user_) external view returns (bool); - -// function setMaxFees(uint256 fees_) external; - -// function transferFrom(address from_, address to_, uint256 amount_) external returns (bool); -// } - interface IGasAccountManager { /// @notice Emitted when fees deposited are updated /// @param chainSlug The chain identifier @@ -66,6 +32,12 @@ interface IGasAccountManager { /// @notice Emitted when forwarder solana is set event ForwarderSolanaSet(address indexed forwarderSolana); + /// @notice Emitted when gas account token is set + event GasAccountTokenSet(address indexed gasAccountToken); + + /// @notice Emitted when gas escrow is set + event GasEscrowSet(address indexed gasEscrow); + /// @notice Emitted when max fees per chain slug is set /// @param chainSlug The chain slug /// @param fees The max fees @@ -102,13 +74,7 @@ interface IGasAccountManager { /// @notice Deposit tokens from a chain into gas account /// @dev Called by watcher after detecting GasStation deposit - function depositFromChain( - uint32 chainSlug, - address token, - address account, - uint256 nativeAmount, - uint256 gasAmount - ) external; + function depositFromChain(bytes memory payload_) external; /// @notice Withdraw SGAS to tokens on another chain function withdrawToChain( @@ -131,5 +97,5 @@ interface IGasAccountManager { /// @notice Settle escrowed gas to transmitter /// @dev Called when payload completes successfully - function settleGasPayment(bytes32 payloadId, address transmitter) external; + function settleGasPayment(bytes32 payloadId, address consumeFrom, address transmitter, uint256 amount) external; } diff --git a/contracts/evmx/interfaces/IERC20.sol b/contracts/evmx/interfaces/IGasAccountToken.sol similarity index 71% rename from contracts/evmx/interfaces/IERC20.sol rename to contracts/evmx/interfaces/IGasAccountToken.sol index af96566e..826d0c81 100644 --- a/contracts/evmx/interfaces/IERC20.sol +++ b/contracts/evmx/interfaces/IGasAccountToken.sol @@ -1,11 +1,13 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; -interface IERC20 { +interface IGasAccountToken { function totalSupply() external view returns (uint256); function balanceOf(address account) external view returns (uint256); + function totalBalanceOf(address account) external view returns (uint256); + function transfer(address to, uint256 amount) external returns (bool); function allowance(address owner, address spender) external view returns (uint256); @@ -16,6 +18,10 @@ interface IERC20 { function decimals() external view returns (uint8); + function mint(address account, uint256 amount) external; + + function burn(address account, uint256 amount) external; + function permit( address spender, uint256 value, @@ -24,6 +30,12 @@ interface IERC20 { bytes memory signature ) external; + function isGasAvailable( + address consumeFrom_, + address spender_, + uint256 amount_ + ) external view returns (bool); + event Transfer(address indexed from, address indexed to, uint256 value); event Approval(address indexed owner, address indexed spender, uint256 value); diff --git a/contracts/evmx/interfaces/IGasEscrow.sol b/contracts/evmx/interfaces/IGasEscrow.sol index c774eb8a..11ec493b 100644 --- a/contracts/evmx/interfaces/IGasEscrow.sol +++ b/contracts/evmx/interfaces/IGasEscrow.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; -import {EscrowEntry} from "../../utils/common/Structs.sol"; +import {EscrowEntry, EscrowState} from "../../utils/common/Structs.sol"; /// @title Gas Escrow Manager /// @notice Tracks escrowed gas during payload lifecycle @@ -37,7 +37,7 @@ interface IGasEscrow { function releaseEscrow(bytes32 payloadId) external; /// @notice Mark escrow as settled (paid to transmitter) - function settleGasPayment(bytes32 payloadId, address transmitter) external; + function settleGasPayment(bytes32 payloadId, address transmitter, uint256 amount) external; /// @notice Get total escrowed amount for an account function getEscrowedAmount(address account) external view returns (uint256); diff --git a/contracts/evmx/plugs/GasStation.sol b/contracts/evmx/plugs/GasStation.sol index 75033eb3..1db7398c 100644 --- a/contracts/evmx/plugs/GasStation.sol +++ b/contracts/evmx/plugs/GasStation.sol @@ -8,7 +8,7 @@ import {RESCUE_ROLE} from "../../utils/common/AccessRoles.sol"; import {IGasStation} from "../interfaces/IGasStation.sol"; import "../../utils/RescueFundsLib.sol"; import {InvalidTokenAddress} from "../../utils/common/Errors.sol"; -import "../interfaces/IERC20.sol"; +import "../interfaces/IGasAccountToken.sol"; /// @title GasStation /// @notice Contract for managing fees on a network @@ -91,8 +91,8 @@ contract GasStation is IGasStation, PlugBase, AccessControl { address receiver_, uint256 amount_ ) external override onlySocket { - uint256 balance = IERC20(token_).balanceOf(address(this)); - uint8 decimals = IERC20(token_).decimals(); + uint256 balance = IGasAccountToken(token_).balanceOf(address(this)); + uint8 decimals = IGasAccountToken(token_).decimals(); if (decimals < 18) { amount_ = amount_ / 10 ** (18 - decimals); diff --git a/contracts/evmx/watcher/Watcher.sol b/contracts/evmx/watcher/Watcher.sol index c7395259..10c83381 100644 --- a/contracts/evmx/watcher/Watcher.sol +++ b/contracts/evmx/watcher/Watcher.sol @@ -6,7 +6,7 @@ import "./Configurations.sol"; import {IPrecompile} from "../interfaces/IPrecompile.sol"; import {IGasAccountManager} from "../interfaces/IGasAccountManager.sol"; import {IPromise} from "../interfaces/IPromise.sol"; -import {IERC20} from "../interfaces/IERC20.sol"; +import {IGasAccountToken} from "../interfaces/IGasAccountToken.sol"; import "../../utils/common/IdUtils.sol"; import "../../utils/Pausable.sol"; import {PAUSER_ROLE, UNPAUSER_ROLE} from "../../utils/common/AccessRoles.sol"; @@ -78,7 +78,7 @@ contract Watcher is Initializable, Configurations, Pausable { function executePayload() external whenNotPaused returns (address asyncPromise) { if (latestAppGateway != msg.sender) revert AppGatewayMismatch(); if ( - !gasAccountManager__().isGasAvailable( + !gasAccountToken__().isGasAvailable( payloadData.overrideParams.consumeFrom, latestAppGateway, payloadData.overrideParams.maxFees @@ -235,7 +235,7 @@ contract Watcher is Initializable, Configurations, Pausable { uint256 deadline = abi.decode(params_.overrides, (uint256)); if (deadline < block.timestamp) revert DeadlinePassed(); - IERC20(address(gasAccountManager__())).transferFrom(appGateway, address(this), triggerFees); + gasAccountToken__().transferFrom(appGateway, address(this), triggerFees); triggerFromChainSlug = params_.chainSlug; triggerFromPlug = params_.plug; (bool success, , ) = appGateway.tryCall( @@ -270,7 +270,7 @@ contract Watcher is Initializable, Configurations, Pausable { // reblock new fees if ( - !IGasAccountManager(gasAccountManager__()).isGasAvailable( + !gasAccountToken__().isGasAvailable( r.consumeFrom, msg.sender, newMaxFees_ @@ -290,8 +290,8 @@ contract Watcher is Initializable, Configurations, Pausable { r.isPayloadCancelled = true; r.isTransmitterFeesSettled = true; - gasAccountManager__().settleGasPayment(payloadId_, transmitter, r.maxFees - r.watcherFees); - gasAccountManager__().settleGasPayment(payloadId_, address(this), r.watcherFees); + gasAccountManager__().settleGasPayment(payloadId_, r.consumeFrom, transmitter, r.maxFees - r.watcherFees); + gasAccountManager__().settleGasPayment(payloadId_, r.consumeFrom, address(this), r.watcherFees); emit PayloadCancelled(payloadId_); } diff --git a/script/counter/WithdrawFeesArbitrumFeesPlug.s.sol b/script/counter/WithdrawFeesArbitrumFeesPlug.s.sol index 541dfac4..de623c29 100644 --- a/script/counter/WithdrawFeesArbitrumFeesPlug.s.sol +++ b/script/counter/WithdrawFeesArbitrumFeesPlug.s.sol @@ -4,6 +4,9 @@ pragma solidity ^0.8.21; import {Script} from "forge-std/Script.sol"; import {console} from "forge-std/console.sol"; import {GasAccountManager} from "../../contracts/evmx/fees/GasAccountManager.sol"; +import {GasAccountToken} from "../../contracts/evmx/fees/GasAccountToken.sol"; +import {AddressResolver} from "../../contracts/evmx/helpers/AddressResolver.sol"; + import {CounterAppGateway} from "../../test/apps/counter/CounterAppGateway.sol"; // @notice This script is used to withdraw fees from EVMX to Arbitrum Sepolia @@ -12,14 +15,13 @@ contract WithdrawFees is Script { function run() external { // EVMX Check available fees vm.createSelectFork(vm.envString("EVMX_RPC")); - GasAccountManager gasAccountManager = GasAccountManager( - payable(vm.envAddress("FEES_MANAGER")) - ); + AddressResolver addressResolver = AddressResolver(vm.envAddress("ADDRESS_RESOLVER")); + GasAccountToken gasAccountToken = GasAccountToken(address(addressResolver.gasAccountToken__())); address appGatewayAddress = vm.envAddress("APP_GATEWAY"); address token = vm.envAddress("USDC"); CounterAppGateway appGateway = CounterAppGateway(appGatewayAddress); - uint256 availableFees = gasAccountManager.balanceOf(appGatewayAddress); + uint256 availableFees = gasAccountToken.balanceOf(appGatewayAddress); console.log("Available fees:", availableFees); if (availableFees > 0) { diff --git a/script/helpers/CheckDepositedCredits.s.sol b/script/helpers/CheckDepositedCredits.s.sol index bd3f9eca..553d721a 100644 --- a/script/helpers/CheckDepositedCredits.s.sol +++ b/script/helpers/CheckDepositedCredits.s.sol @@ -3,24 +3,24 @@ pragma solidity ^0.8.21; import {Script} from "forge-std/Script.sol"; import {console} from "forge-std/console.sol"; -import {GasAccountManager} from "../../contracts/evmx/fees/GasAccountManager.sol"; +import {GasAccountToken} from "../../contracts/evmx/fees/GasAccountToken.sol"; +import {AddressResolver} from "../../contracts/evmx/helpers/AddressResolver.sol"; contract CheckDepositedCredits is Script { function run() external { vm.createSelectFork(vm.envString("EVMX_RPC")); - GasAccountManager gasAccountManager = GasAccountManager( - payable(vm.envAddress("FEES_MANAGER")) - ); + AddressResolver addressResolver = AddressResolver(vm.envAddress("ADDRESS_RESOLVER")); + GasAccountToken gasAccountToken = GasAccountToken(address(addressResolver.gasAccountToken__())); address appGateway = vm.envAddress("APP_GATEWAY"); - uint256 totalCredits = gasAccountManager.totalGas(appGateway); - uint256 payloadEscrow = gasAccountManager.getPayloadEscrow(appGateway); + uint256 totalCredits = gasAccountToken.totalBalanceOf(appGateway); + uint256 payloadEscrow = totalCredits - gasAccountToken.balanceOf(appGateway); console.log("App Gateway:", appGateway); - console.log("Fees Manager:", address(gasAccountManager)); + console.log("Fees Manager:", address(gasAccountToken)); console.log("totalCredits fees:", totalCredits); console.log("payloadEscrow fees:", payloadEscrow); - uint256 availableFees = gasAccountManager.balanceOf(appGateway); + uint256 availableFees = gasAccountToken.balanceOf(appGateway); console.log("Available fees:", availableFees); } } diff --git a/script/helpers/TransferRemainingCredits.s.sol b/script/helpers/TransferRemainingCredits.s.sol index 5f0b135a..e24546b1 100644 --- a/script/helpers/TransferRemainingCredits.s.sol +++ b/script/helpers/TransferRemainingCredits.s.sol @@ -5,6 +5,8 @@ import {Script} from "forge-std/Script.sol"; import {console} from "forge-std/console.sol"; import {GasAccountManager} from "../../contracts/evmx/fees/GasAccountManager.sol"; import {IAppGateway} from "../../contracts/evmx/interfaces/IAppGateway.sol"; +import {AddressResolver} from "../../contracts/evmx/helpers/AddressResolver.sol"; +import {GasAccountToken} from "../../contracts/evmx/fees/GasAccountToken.sol"; contract TransferRemainingCredits is Script { function run() external { @@ -13,21 +15,23 @@ contract TransferRemainingCredits is Script { uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); + AddressResolver addressResolver = AddressResolver(vm.envAddress("ADDRESS_RESOLVER")); + GasAccountToken gasAccountToken = GasAccountToken(address(addressResolver.gasAccountToken__())); GasAccountManager gasAccountManager = GasAccountManager( - payable(vm.envAddress("FEES_MANAGER")) + address(addressResolver.gasAccountManager__()) ); address appGateway = vm.envAddress("APP_GATEWAY"); address newAppGateway = vm.envAddress("NEW_APP_GATEWAY"); uint256 totalCredits = gasAccountManager.totalGas(appGateway); - uint256 payloadEscrow = gasAccountManager.getPayloadEscrow(appGateway); + uint256 payloadEscrow = totalCredits - gasAccountToken.balanceOf(appGateway); console.log("App Gateway:", appGateway); console.log("New App Gateway:", newAppGateway); console.log("Fees Manager:", address(gasAccountManager)); console.log("totalCredits fees:", totalCredits); console.log("payloadEscrow fees:", payloadEscrow); - uint256 availableFees = gasAccountManager.balanceOf(appGateway); + uint256 availableFees = gasAccountToken.balanceOf(appGateway); console.log("Available fees:", availableFees); bytes memory data = abi.encodeWithSignature( "transferFrom(address,address,uint256)", diff --git a/script/helpers/WithdrawRemainingCredits.s.sol b/script/helpers/WithdrawRemainingCredits.s.sol index bb0eee92..36e04766 100644 --- a/script/helpers/WithdrawRemainingCredits.s.sol +++ b/script/helpers/WithdrawRemainingCredits.s.sol @@ -4,6 +4,8 @@ pragma solidity ^0.8.21; import {Script} from "forge-std/Script.sol"; import {console} from "forge-std/console.sol"; import {GasAccountManager} from "../../contracts/evmx/fees/GasAccountManager.sol"; +import {AddressResolver} from "../../contracts/evmx/helpers/AddressResolver.sol"; +import {GasAccountToken} from "../../contracts/evmx/fees/GasAccountToken.sol"; contract WithdrawRemainingCredits is Script { function run() external { @@ -12,21 +14,21 @@ contract WithdrawRemainingCredits is Script { uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); - GasAccountManager gasAccountManager = GasAccountManager( - payable(vm.envAddress("FEES_MANAGER")) - ); + AddressResolver addressResolver = AddressResolver(vm.envAddress("ADDRESS_RESOLVER")); + GasAccountToken gasAccountToken = GasAccountToken(address(addressResolver.gasAccountToken__())); + GasAccountManager gasAccountManager = GasAccountManager(address(addressResolver.gasAccountManager__())); address appGateway = vm.envAddress("APP_GATEWAY"); - uint256 totalCredits = gasAccountManager.totalGas(appGateway); - uint256 payloadEscrow = gasAccountManager.getPayloadEscrow(appGateway); + uint256 totalCredits = gasAccountToken.totalBalanceOf(appGateway); + uint256 payloadEscrow = totalCredits - gasAccountToken.balanceOf(appGateway); console.log("App Gateway:", appGateway); - console.log("Fees Manager:", address(gasAccountManager)); + console.log("Fees Manager:", address(gasAccountToken)); console.log("totalCredits fees:", totalCredits); console.log("payloadEscrow fees:", payloadEscrow); - uint256 availableFees = gasAccountManager.balanceOf(appGateway); + uint256 availableFees = gasAccountToken.balanceOf(appGateway); console.log("Available fees:", availableFees); - gasAccountManager.transferFrom(appGateway, vm.addr(deployerPrivateKey), availableFees); + gasAccountToken.transferFrom(appGateway, vm.addr(deployerPrivateKey), availableFees); vm.stopBroadcast(); } diff --git a/test/SetupTest.t.sol b/test/SetupTest.t.sol index 92f722bb..141f0549 100644 --- a/test/SetupTest.t.sol +++ b/test/SetupTest.t.sol @@ -118,7 +118,7 @@ contract DeploySetup is SetupStore { _configureChain(optChainSlug); vm.startPrank(watcherEOA); - gasVault.grantRole(FEE_MANAGER_ROLE, address(gasAccountManager)); + gasVault.grantRole(GAS_MANAGER_ROLE, address(gasAccountManager)); // setup address resolver addressResolver.setWatcher(address(watcher)); From cfe82646361199b08a94d6cabad8c8c4bee3ce56 Mon Sep 17 00:00:00 2001 From: Ameesha Agrawal Date: Thu, 13 Nov 2025 13:35:15 +0530 Subject: [PATCH 09/12] fix: rename credit --- contracts/evmx/fees/GasAccountManager.sol | 100 +++++++----------- contracts/evmx/fees/GasAccountToken.sol | 31 ++++-- contracts/evmx/fees/GasEscrow.sol | 29 ++++- contracts/evmx/fees/MessageResolver.sol | 16 +-- .../evmx/interfaces/IGasAccountManager.sol | 19 ++-- contracts/evmx/interfaces/IGasStation.sol | 2 +- contracts/utils/common/Errors.sol | 1 - contracts/utils/common/Structs.sol | 5 - hardhat-scripts/config/config.ts | 2 +- hardhat-scripts/deploy/9.setupTransmitter.ts | 18 ++-- hardhat-scripts/test/chainTest.ts | 6 +- ...dCredits.s.sol => CheckDepositedGas.s.sol} | 8 +- .../{DepositCredit.s.sol => DepositGas.s.sol} | 0 ...Native.s.sol => DepositGasAndNative.s.sol} | 0 ...tMainnet.s.sol => DepositGasMainnet.s.sol} | 0 ...edits.s.sol => TransferRemainingGas.s.sol} | 12 ++- ...edits.s.sol => WithdrawRemainingGas.s.sol} | 10 +- test/SetupTest.t.sol | 51 +++++---- test/apps/Counter.t.sol | 2 +- 19 files changed, 158 insertions(+), 154 deletions(-) rename script/helpers/{CheckDepositedCredits.s.sol => CheckDepositedGas.s.sol} (78%) rename script/helpers/{DepositCredit.s.sol => DepositGas.s.sol} (100%) rename script/helpers/{DepositCreditAndNative.s.sol => DepositGasAndNative.s.sol} (100%) rename script/helpers/{DepositCreditMainnet.s.sol => DepositGasMainnet.s.sol} (100%) rename script/helpers/{TransferRemainingCredits.s.sol => TransferRemainingGas.s.sol} (82%) rename script/helpers/{WithdrawRemainingCredits.s.sol => WithdrawRemainingGas.s.sol} (82%) diff --git a/contracts/evmx/fees/GasAccountManager.sol b/contracts/evmx/fees/GasAccountManager.sol index a8df580a..f672a15b 100644 --- a/contracts/evmx/fees/GasAccountManager.sol +++ b/contracts/evmx/fees/GasAccountManager.sol @@ -4,36 +4,25 @@ import "solady/auth/Ownable.sol"; import "solady/utils/Initializable.sol"; import "solady/utils/SafeTransferLib.sol"; import "../interfaces/IGasAccountManager.sol"; -import "../interfaces/IGasEscrow.sol"; -import "../interfaces/IGasVault.sol"; -import "../interfaces/IGasAccountToken.sol"; import "../interfaces/IGasStation.sol"; import "../../utils/AccessControl.sol"; import "../../utils/common/AccessRoles.sol"; import "../../utils/OverrideParamsLib.sol"; import {OverrideParams, SolanaInstruction, SolanaInstructionData, SolanaInstructionDataDescription} from "../../utils/common/Structs.sol"; import {toBytes32Format} from "../../utils/common/Converters.sol"; -import {WRITE, CHAIN_SLUG_SOLANA_MAINNET} from "../../utils/common/Constants.sol"; -import {NonceUsed, InvalidAmount, InsufficientCreditsAvailable, InsufficientBalance, InvalidChainSlug, InvalidReceiver} from "../../utils/common/Errors.sol"; +import {NonceUsed, InvalidAmount, InsufficientGasAvailable, InsufficientBalance, InvalidChainSlug, InvalidReceiver} from "../../utils/common/Errors.sol"; import "../../utils/RescueFundsLib.sol"; -import {AddressResolverUtil} from "../helpers/AddressResolverUtil.sol"; import {ForwarderSolana} from "../helpers/ForwarderSolana.sol"; import {GasStationProgramPda} from "../helpers/solana-utils/program-pda/GasStationPdas.sol"; import {SolanaPDA} from "../helpers/solana-utils/SolanaPda.sol"; import {TOKEN_PROGRAM_ID, ASSOCIATED_TOKEN_PROGRAM_ID, SYSTEM_PROGRAM_ID} from "../helpers/solana-utils/SolanaPda.sol"; import "../base/AppGatewayBase.sol"; -contract GasAccountManager is IGasAccountManager, Ownable, AccessControl, AppGatewayBase { - using OverrideParamsLib for OverrideParams; - +abstract contract GasAccountManagerStorage is IGasAccountManager { // slots [0-49] reserved for gap uint256[50] _gap_before; - IGasEscrow public gasEscrow; - IGasVault public gasVault; - IGasAccountToken public gasAccountToken; - // token pool balances // chainSlug => token address => amount mapping(uint32 => mapping(bytes32 => uint256)) public tokenOnChainBalances; @@ -51,21 +40,25 @@ contract GasAccountManager is IGasAccountManager, Ownable, AccessControl, AppGat /// @dev chainSlug => fees plug address mapping(uint32 => bytes32) public gasStations; - constructor( - address gasEscrow_, - address gasVault_, - address gasAccountToken_, + // slots [50-99] reserved for gap + uint256[50] _gap_after; +} + +contract GasAccountManager is GasAccountManagerStorage, Ownable, AccessControl, AppGatewayBase, Initializable { + using OverrideParamsLib for OverrideParams; + + constructor() { + _disableInitializers(); + } + + function initialize( address addressResolver_, address owner_, uint256 fees_, bytes32 sbType_, address forwarderSolana_ - ) { - gasEscrow = IGasEscrow(gasEscrow_); - gasVault = IGasVault(gasVault_); - gasAccountToken = IGasAccountToken(gasAccountToken_); + ) external reinitializer(1) { forwarderSolana = ForwarderSolana(forwarderSolana_); - overrideParams = overrideParams.setSwitchboardType(sbType_).setMaxFees(fees_); _initializeOwner(owner_); _initializeAppGateway(addressResolver_); @@ -79,21 +72,21 @@ contract GasAccountManager is IGasAccountManager, Ownable, AccessControl, AppGat if (amount == 0) revert InvalidAmount(); // Mint tokens to receiver - gasAccountToken.mint(receiver, amount); + gasAccountToken__().mint(receiver, amount); // reverts if transfer fails - SafeTransferLib.safeTransferETH(address(gasVault), amount); + SafeTransferLib.safeTransferETH(address(gasVault__()), amount); emit GasWrapped(receiver, amount); } /// @notice Unwrap SGAS to native tokens function unwrapFromGas(uint256 amount, address receiver) external onlyWatcher { - if (gasAccountToken.balanceOf(msg.sender) < amount) revert InsufficientCreditsAvailable(); + if (gasAccountToken__().balanceOf(msg.sender) < amount) revert InsufficientGasAvailable(); // Burn tokens from sender - gasAccountToken.burn(msg.sender, amount); + gasAccountToken__().burn(msg.sender, amount); - bool success = gasVault.withdraw(receiver, amount); + bool success = gasVault__().withdraw(receiver, amount); if (!success) revert InsufficientBalance(); emit GasUnwrapped(msg.sender, amount); @@ -104,7 +97,7 @@ contract GasAccountManager is IGasAccountManager, Ownable, AccessControl, AppGat /// @notice Deposit tokens from a chain into gas account /// @dev Called by watcher after detecting GasStation deposit function depositFromChain(bytes memory payload_) external onlyWatcher { - // Decode payload: (chainSlug, token, receiver, creditAmount, nativeAmount) + // Decode payload: (chainSlug, token, receiver, gasAmount, nativeAmount) ( uint32 chainSlug, address token, @@ -116,14 +109,14 @@ contract GasAccountManager is IGasAccountManager, Ownable, AccessControl, AppGat tokenOnChainBalances[chainSlug][toBytes32Format(token)] += gasAmount + nativeAmount; // Mint tokens to the user - gasAccountToken.mint(depositTo, gasAmount); + gasAccountToken__().mint(depositTo, gasAmount); if (nativeAmount > 0) { - // if native transfer fails, add to credit - bool success = gasVault.withdraw(depositTo, nativeAmount); + // if native transfer fails, add to gas + bool success = gasVault__().withdraw(depositTo, nativeAmount); if (!success) { - // Convert failed native amount to credits - gasAccountToken.mint(depositTo, nativeAmount); + // Convert failed native amount to gas + gasAccountToken__().mint(depositTo, nativeAmount); gasAmount += nativeAmount; nativeAmount = 0; } @@ -143,11 +136,11 @@ contract GasAccountManager is IGasAccountManager, Ownable, AccessControl, AppGat address consumeFrom = msg.sender; // Check if amount is available in fees plug - uint256 gasBalance = gasAccountToken.balanceOf(consumeFrom); - if (gasBalance < amount + bridgeFee) revert InsufficientCreditsAvailable(); + uint256 gasBalance = gasAccountToken__().balanceOf(consumeFrom); + if (gasBalance < amount + bridgeFee) revert InsufficientGasAvailable(); // Burn tokens from sender - gasAccountToken.burn(consumeFrom, amount); + gasAccountToken__().burn(consumeFrom, amount); tokenOnChainBalances[chainSlug][toBytes32Format(token)] -= amount; // Add it to the queue and submit payload @@ -187,14 +180,14 @@ contract GasAccountManager is IGasAccountManager, Ownable, AccessControl, AppGat /// @notice Escrow gas for a payload /// @dev Called by Watcher when payload is submitted function escrowGas(bytes32 payloadId, address account, uint256 amount) external onlyWatcher { - if (gasAccountToken.balanceOf(account) < amount) revert InsufficientCreditsAvailable(); - gasEscrow.escrowGas(payloadId, account, amount); + if (gasAccountToken__().balanceOf(account) < amount) revert InsufficientGasAvailable(); + gasEscrow__().escrowGas(payloadId, account, amount); } /// @notice Release escrowed gas back to account /// @dev Called when transmitter changes or payload cancelled function releaseEscrow(bytes32 payloadId) external onlyWatcher { - gasEscrow.releaseEscrow(payloadId); + gasEscrow__().releaseEscrow(payloadId); } /// @notice Settle escrowed gas to transmitter @@ -205,30 +198,30 @@ contract GasAccountManager is IGasAccountManager, Ownable, AccessControl, AppGat address transmitter, uint256 amount ) external onlyWatcher { - gasEscrow.settleGasPayment(payloadId, transmitter, amount); - gasAccountToken.burn(consumeFrom, amount); - gasAccountToken.mint(transmitter, amount); + gasEscrow__().settleGasPayment(payloadId, transmitter, amount); + gasAccountToken__().burn(consumeFrom, amount); + gasAccountToken__().mint(transmitter, amount); } /// @notice Get available gas balance for an account /// @dev Returns balance minus escrowed amount function availableGas(address account) external view override returns (uint256) { - return gasAccountToken.balanceOf(account); + return gasAccountToken__().balanceOf(account); } /// @notice Get total gas balance including escrowed function totalGas(address account) external view override returns (uint256) { - return gasAccountToken.totalBalanceOf(account); + return gasAccountToken__().totalBalanceOf(account); } /// @notice Get currently escrowed gas for an account function escrowedGas(address account) external view override returns (uint256) { - return gasEscrow.getEscrowedAmount(account); + return gasEscrow__().getEscrowedAmount(account); } /// @notice Approve an app to spend gas from your account function approveGasSpending(address app, uint256 amount) external override { - gasAccountToken.approve(app, amount); + gasAccountToken__().approve(app, amount); } function setGasStation(uint32 chainSlug_, bytes32 gasStation_) external onlyOwner { @@ -236,21 +229,6 @@ contract GasAccountManager is IGasAccountManager, Ownable, AccessControl, AppGat emit GasStationSet(chainSlug_, gasStation_); } - function setGasVault(address gasVault_) external onlyOwner { - gasVault = IGasVault(gasVault_); - emit GasVaultSet(gasVault_); - } - - function setGasAccountToken(address gasAccountToken_) external onlyOwner { - gasAccountToken = IGasAccountToken(gasAccountToken_); - emit GasAccountTokenSet(gasAccountToken_); - } - - function setGasEscrow(address gasEscrow_) external onlyOwner { - gasEscrow = IGasEscrow(gasEscrow_); - emit GasEscrowSet(gasEscrow_); - } - function setForwarderSolana(address forwarderSolana_) external onlyOwner { forwarderSolana = ForwarderSolana(forwarderSolana_); emit ForwarderSolanaSet(forwarderSolana_); diff --git a/contracts/evmx/fees/GasAccountToken.sol b/contracts/evmx/fees/GasAccountToken.sol index f2610daf..bc4cf494 100644 --- a/contracts/evmx/fees/GasAccountToken.sol +++ b/contracts/evmx/fees/GasAccountToken.sol @@ -6,17 +6,22 @@ import "solady/tokens/ERC20.sol"; import "../../utils/RescueFundsLib.sol"; import "solady/utils/Initializable.sol"; import "../interfaces/IAddressResolver.sol"; -import "../interfaces/IGasAccountToken.sol"; /// @title Socket Gas Token (SGAS) /// @notice ERC20 token representing prepaid gas for Socket operations /// @dev Balances are split between available and escrowed -contract GasAccountToken is ERC20, Ownable { +contract GasAccountToken is ERC20, Ownable, Initializable { + // slots [0-49] reserved for gap + uint256[50] _gap_before; + /// @notice Escrow tracker for gas in active payloads IAddressResolver public addressResolver__; + // slots [50-99] reserved for gap + uint256[50] _gap_after; + error NotGasAccountManager(); - error InsufficientCreditsAvailable(); + error InsufficientGasAvailable(); modifier onlyGasAccountManager() { if (msg.sender != address(addressResolver__.gasAccountManager__())) @@ -24,7 +29,11 @@ contract GasAccountToken is ERC20, Ownable { _; } - constructor(address owner_, address addressResolver_) { + constructor() { + _disableInitializers(); + } + + function initialize(address owner_, address addressResolver_) external reinitializer(1) { addressResolver__ = IAddressResolver(addressResolver_); _setOwner(owner_); } @@ -61,20 +70,20 @@ contract GasAccountToken is ERC20, Ownable { return super.balanceOf(account); } - // ERC20 Overrides to handle blocked credits - /// @notice Override transfer to check for blocked credits + // ERC20 Overrides to handle escrowed gas + /// @notice Override transfer to check for escrowed gas function transfer(address to, uint256 amount) public override returns (bool) { - if (balanceOf(msg.sender) < amount) revert InsufficientCreditsAvailable(); + if (balanceOf(msg.sender) < amount) revert InsufficientGasAvailable(); return super.transfer(to, amount); } - /// @notice Override transferFrom to check for blocked credits + /// @notice Override transferFrom to check for escrowed gas function transferFrom( address from_, address to_, uint256 amount_ ) public override returns (bool) { - if (!isGasAvailable(from_, msg.sender, amount_)) revert InsufficientCreditsAvailable(); + if (!isGasAvailable(from_, msg.sender, amount_)) revert InsufficientGasAvailable(); // todo: check if (msg.sender == address(addressResolver__.watcher__())) @@ -82,11 +91,11 @@ contract GasAccountToken is ERC20, Ownable { return super.transferFrom(from_, to_, amount_); } - /// @notice Checks if the user has enough credits + /// @notice Checks if the user has enough gas /// @param consumeFrom_ address to consume from /// @param spender_ address to spend from /// @param amount_ amount to spend - /// @return True if the user has enough credits, false otherwise + /// @return True if the user has enough gas, false otherwise function isGasAvailable( address consumeFrom_, address spender_, diff --git a/contracts/evmx/fees/GasEscrow.sol b/contracts/evmx/fees/GasEscrow.sol index 6c451577..d0eebd5e 100644 --- a/contracts/evmx/fees/GasEscrow.sol +++ b/contracts/evmx/fees/GasEscrow.sol @@ -3,11 +3,15 @@ pragma solidity ^0.8.21; import "../interfaces/IGasEscrow.sol"; import "../../utils/RescueFundsLib.sol"; +import "solady/utils/Initializable.sol"; +import "solady/auth/Ownable.sol"; + + + +abstract contract GasEscrowStorage is IGasEscrow { + // slots [0-49] reserved for gap + uint256[50] _gap_before; -/// @title Gas Escrow Manager -/// @notice Tracks escrowed gas during payload lifecycle -/// @dev Separates escrow logic from token logic for clarity -contract GasEscrow is IGasEscrow { address public gasAccountManager; /// @notice Tracks escrowed gas per account @@ -16,6 +20,14 @@ contract GasEscrow is IGasEscrow { /// @notice Tracks escrowed gas per payload mapping(bytes32 => EscrowEntry) public payloadEscrow; + // slots [50-99] reserved for gap + uint256[50] _gap_after; +} + +/// @title Gas Escrow Manager +/// @notice Tracks escrowed gas during payload lifecycle +/// @dev Separates escrow logic from token logic for clarity +contract GasEscrow is IGasEscrow, GasEscrowStorage, Initializable, Ownable { error NotGasAccountManager(); error NotActive(); error NoEscrow(); @@ -25,6 +37,15 @@ contract GasEscrow is IGasEscrow { _; } + constructor() { + _disableInitializers(); + } + + function initialize(address gasAccountManager_, address owner_) external reinitializer(1) { + _setOwner(owner_); + gasAccountManager = gasAccountManager_; + } + /// @notice Escrow gas for a payload function escrowGas( bytes32 payloadId_, diff --git a/contracts/evmx/fees/MessageResolver.sol b/contracts/evmx/fees/MessageResolver.sol index 3ac666c5..19d689a3 100644 --- a/contracts/evmx/fees/MessageResolver.sol +++ b/contracts/evmx/fees/MessageResolver.sol @@ -73,7 +73,7 @@ abstract contract MessageResolverStorage { * @title MessageResolver * @notice Contract for resolving payments to transmitters for relaying messages on EVMx * @dev This contract tracks message details and handles payment settlement after execution - * @dev Uses Credits (ERC20) from GasAccountManager for payment settlement + * @dev Uses Gas Fees (ERC20) from GasAccountManager for payment settlement * @dev Upgradeable proxy pattern with AddressResolverUtil */ contract MessageResolver is @@ -104,8 +104,8 @@ contract MessageResolver is /// @notice Thrown when payment transfer fails error PaymentFailed(); - /// @notice Thrown when sponsor has insufficient credits - error InsufficientSponsorCredits(); + /// @notice Thrown when sponsor has insufficient gas + error InsufficientSponsorGas(); //////////////////////////////////////////////////////// ////////////////////// EVENTS ////////////////////////// @@ -236,7 +236,7 @@ contract MessageResolver is /** * @notice Mark message as executed and pay transmitter * @dev Called by watcher after confirming execution on destination - * @dev Uses Credits from GasAccountManager for payment + * @dev Uses Gas Fees from GasAccountManager for payment * @param payloadId_ Unique identifier for the payload * @param signature_ Watcher signature confirming execution * @param nonce_ Nonce to prevent replay attacks @@ -265,17 +265,17 @@ contract MessageResolver is if (usedNonces[watcher][nonce_]) revert NonceAlreadyUsed(); usedNonces[watcher][nonce_] = true; - // Check sponsor has sufficient credits (uses AddressResolver to get latest GasAccountManager) + // Check sponsor has sufficient gas (uses AddressResolver to get latest GasAccountManager) if ( !gasAccountToken__().isGasAvailable(details.sponsor, address(this), details.feeAmount) - ) { - revert InsufficientSponsorCredits(); + ) { + revert InsufficientSponsorGas(); } // Mark message as executed details.status = ExecutionStatus.Executed; - // Transfer credits from sponsor to transmitter using GasAccountManager from AddressResolver + // Transfer gas from sponsor to transmitter using GasAccountManager from AddressResolver bool success = gasAccountToken__().transferFrom( details.sponsor, details.transmitter, diff --git a/contracts/evmx/interfaces/IGasAccountManager.sol b/contracts/evmx/interfaces/IGasAccountManager.sol index 7a3b6d66..12322aeb 100644 --- a/contracts/evmx/interfaces/IGasAccountManager.sol +++ b/contracts/evmx/interfaces/IGasAccountManager.sol @@ -7,22 +7,19 @@ interface IGasAccountManager { /// @param chainSlug The chain identifier /// @param token The token address /// @param depositTo The address to deposit to - /// @param creditAmount The credit amount added + /// @param gasAmount The gas amount added /// @param nativeAmount The native amount transferred event Deposited( uint32 indexed chainSlug, address indexed token, address indexed depositTo, - uint256 creditAmount, + uint256 gasAmount, uint256 nativeAmount ); /// @notice Emitted when fees plug is set event GasStationSet(uint32 indexed chainSlug, bytes32 indexed gasStation); - /// @notice Emitted when fees pool is set - event GasVaultSet(address indexed gasVault); - /// @notice Emitted when fees plug solana program id is set event SusdcSolanaProgramIdSet(bytes32 indexed susdcSolanaProgramId); @@ -32,21 +29,17 @@ interface IGasAccountManager { /// @notice Emitted when forwarder solana is set event ForwarderSolanaSet(address indexed forwarderSolana); - /// @notice Emitted when gas account token is set - event GasAccountTokenSet(address indexed gasAccountToken); - - /// @notice Emitted when gas escrow is set - event GasEscrowSet(address indexed gasEscrow); - /// @notice Emitted when max fees per chain slug is set /// @param chainSlug The chain slug /// @param fees The max fees event MaxGasPerChainSlugSet(uint32 indexed chainSlug, uint256 fees); - /// @notice Emitted when credits are wrapped + /// @notice Emitted when gas is wrapped + /// @param consumeFrom The address that wrapped the gas + /// @param amount The amount of gas wrapped event GasWrapped(address indexed consumeFrom, uint256 amount); - /// @notice Emitted when credits are unwrapped + /// @notice Emitted when gas is unwrapped event GasUnwrapped(address indexed consumeFrom, uint256 amount); // ============ GAS ACCOUNT OPERATIONS ============ diff --git a/contracts/evmx/interfaces/IGasStation.sol b/contracts/evmx/interfaces/IGasStation.sol index e4cfa948..d85f6547 100644 --- a/contracts/evmx/interfaces/IGasStation.sol +++ b/contracts/evmx/interfaces/IGasStation.sol @@ -6,7 +6,7 @@ interface IGasStation { event GasDeposited( address token, address receiver, - uint256 creditAmount, + uint256 gasAmount, uint256 nativeAmount, bytes32 payloadId ); diff --git a/contracts/utils/common/Errors.sol b/contracts/utils/common/Errors.sol index 5d7fe159..7f6fcc79 100644 --- a/contracts/utils/common/Errors.sol +++ b/contracts/utils/common/Errors.sol @@ -40,7 +40,6 @@ error LowerBidAlreadyExists(); error PayloadCountMismatch(); error InvalidAmount(); -error InsufficientCreditsAvailable(); error InsufficientBalance(); /// @notice Error thrown when a caller is invalid error InvalidCaller(); diff --git a/contracts/utils/common/Structs.sol b/contracts/utils/common/Structs.sol index 39d995b2..d5dc48f8 100644 --- a/contracts/utils/common/Structs.sol +++ b/contracts/utils/common/Structs.sol @@ -109,11 +109,6 @@ struct WatcherMultiCallParams { bytes signature; } -struct UserCredits { - uint256 totalCredits; - uint256 payloadEscrow; -} - // digest: struct DigestParams { bytes32 socket; diff --git a/hardhat-scripts/config/config.ts b/hardhat-scripts/config/config.ts index dc710972..e7ee998f 100644 --- a/hardhat-scripts/config/config.ts +++ b/hardhat-scripts/config/config.ts @@ -292,7 +292,7 @@ export const MAX_SCHEDULE_DELAY_SECONDS = 60 * 60 * 24; // 24 hours export const UPGRADE_VERSION = 1; // Transmitter thresholds -export const TRANSMITTER_CREDIT_THRESHOLD = ethers.utils.parseEther("100"); // 100 ETH threshold +export const TRANSMITTER_GAS_THRESHOLD = ethers.utils.parseEther("100"); // 100 ETH threshold export const TRANSMITTER_NATIVE_THRESHOLD = ethers.utils.parseEther("100"); // 100 ETH threshold // Performance settings diff --git a/hardhat-scripts/deploy/9.setupTransmitter.ts b/hardhat-scripts/deploy/9.setupTransmitter.ts index e13da788..28da9cd1 100644 --- a/hardhat-scripts/deploy/9.setupTransmitter.ts +++ b/hardhat-scripts/deploy/9.setupTransmitter.ts @@ -3,7 +3,7 @@ import { ChainSlug, Contracts, EVMxAddressesObj } from "../../src"; import { EVMX_CHAIN_ID, mode, - TRANSMITTER_CREDIT_THRESHOLD, + TRANSMITTER_GAS_THRESHOLD, TRANSMITTER_NATIVE_THRESHOLD, } from "../config/config"; import { getAddresses } from "../utils/address"; @@ -38,22 +38,22 @@ export const init = async () => { }; export const checkAndDepositForGass = async (transmitter: string) => { - console.log("Checking and depositing credits"); - const credits = await gasAccountManagerContract + console.log("Checking and depositing gas"); + const gas = await gasAccountManagerContract .connect(transmitterSigner) .balanceOf(transmitter); - if (credits.lt(TRANSMITTER_CREDIT_THRESHOLD)) { - console.log("Depositing credits for transmitter..."); + if (gas.lt(TRANSMITTER_GAS_THRESHOLD)) { + console.log("Depositing gas for transmitter..."); const tx = await gasAccountManagerContract .connect(getWatcherSigner()) - .wrap(transmitter, { + .wrapToGas(transmitter, { ...(await overrides(EVMX_CHAIN_ID as ChainSlug)), - value: TRANSMITTER_CREDIT_THRESHOLD, + value: TRANSMITTER_GAS_THRESHOLD, }); - console.log("Credits wrap tx hash:", tx.hash); + console.log("Gas wrap tx hash:", tx.hash); await tx.wait(); - console.log("Credits wrapped"); + console.log("Gas wrapped"); } }; diff --git a/hardhat-scripts/test/chainTest.ts b/hardhat-scripts/test/chainTest.ts index dec5044e..6f082cd2 100644 --- a/hardhat-scripts/test/chainTest.ts +++ b/hardhat-scripts/test/chainTest.ts @@ -102,7 +102,7 @@ class ChainTester { const appGatewayAddress = process.env.COUNTER_APP_GATEWAY!; const gasAccountManagerAddress = process.env.FEES_MANAGER!; - const totalCredits = await this.gasAccountManager.totalGas( + const totalGas = await this.gasAccountManager.totalGas( appGatewayAddress ); const payloadEscrow = await this.gasAccountManager.getPayloadEscrow( @@ -115,10 +115,10 @@ class ChainTester { console.log(`Counter App Gateway: ${appGatewayAddress}`); console.log(`Fees Manager: ${gasAccountManagerAddress}`); console.log( - `Total Credits: ${ethers.utils.formatEther(totalCredits)} ETH` + `Total Gas: ${ethers.utils.formatEther(totalGas)} ETH` ); console.log( - `Blocked Credits: ${ethers.utils.formatEther(payloadEscrow)} ETH` + `Payload Escrow: ${ethers.utils.formatEther(payloadEscrow)} ETH` ); console.log( `Available Fees: ${ethers.utils.formatEther(availableFees)} ETH` diff --git a/script/helpers/CheckDepositedCredits.s.sol b/script/helpers/CheckDepositedGas.s.sol similarity index 78% rename from script/helpers/CheckDepositedCredits.s.sol rename to script/helpers/CheckDepositedGas.s.sol index 553d721a..259286af 100644 --- a/script/helpers/CheckDepositedCredits.s.sol +++ b/script/helpers/CheckDepositedGas.s.sol @@ -6,18 +6,18 @@ import {console} from "forge-std/console.sol"; import {GasAccountToken} from "../../contracts/evmx/fees/GasAccountToken.sol"; import {AddressResolver} from "../../contracts/evmx/helpers/AddressResolver.sol"; -contract CheckDepositedCredits is Script { +contract CheckDepositedGas is Script { function run() external { vm.createSelectFork(vm.envString("EVMX_RPC")); AddressResolver addressResolver = AddressResolver(vm.envAddress("ADDRESS_RESOLVER")); GasAccountToken gasAccountToken = GasAccountToken(address(addressResolver.gasAccountToken__())); address appGateway = vm.envAddress("APP_GATEWAY"); - uint256 totalCredits = gasAccountToken.totalBalanceOf(appGateway); - uint256 payloadEscrow = totalCredits - gasAccountToken.balanceOf(appGateway); + uint256 totalGas = gasAccountToken.totalBalanceOf(appGateway); + uint256 payloadEscrow = totalGas - gasAccountToken.balanceOf(appGateway); console.log("App Gateway:", appGateway); console.log("Fees Manager:", address(gasAccountToken)); - console.log("totalCredits fees:", totalCredits); + console.log("totalGas fees:", totalGas); console.log("payloadEscrow fees:", payloadEscrow); uint256 availableFees = gasAccountToken.balanceOf(appGateway); diff --git a/script/helpers/DepositCredit.s.sol b/script/helpers/DepositGas.s.sol similarity index 100% rename from script/helpers/DepositCredit.s.sol rename to script/helpers/DepositGas.s.sol diff --git a/script/helpers/DepositCreditAndNative.s.sol b/script/helpers/DepositGasAndNative.s.sol similarity index 100% rename from script/helpers/DepositCreditAndNative.s.sol rename to script/helpers/DepositGasAndNative.s.sol diff --git a/script/helpers/DepositCreditMainnet.s.sol b/script/helpers/DepositGasMainnet.s.sol similarity index 100% rename from script/helpers/DepositCreditMainnet.s.sol rename to script/helpers/DepositGasMainnet.s.sol diff --git a/script/helpers/TransferRemainingCredits.s.sol b/script/helpers/TransferRemainingGas.s.sol similarity index 82% rename from script/helpers/TransferRemainingCredits.s.sol rename to script/helpers/TransferRemainingGas.s.sol index e24546b1..6c88cbd3 100644 --- a/script/helpers/TransferRemainingCredits.s.sol +++ b/script/helpers/TransferRemainingGas.s.sol @@ -8,7 +8,7 @@ import {IAppGateway} from "../../contracts/evmx/interfaces/IAppGateway.sol"; import {AddressResolver} from "../../contracts/evmx/helpers/AddressResolver.sol"; import {GasAccountToken} from "../../contracts/evmx/fees/GasAccountToken.sol"; -contract TransferRemainingCredits is Script { +contract TransferRemainingGas is Script { function run() external { string memory rpc = vm.envString("EVMX_RPC"); vm.createSelectFork(rpc); @@ -16,19 +16,21 @@ contract TransferRemainingCredits is Script { uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY"); vm.startBroadcast(deployerPrivateKey); AddressResolver addressResolver = AddressResolver(vm.envAddress("ADDRESS_RESOLVER")); - GasAccountToken gasAccountToken = GasAccountToken(address(addressResolver.gasAccountToken__())); + GasAccountToken gasAccountToken = GasAccountToken( + address(addressResolver.gasAccountToken__()) + ); GasAccountManager gasAccountManager = GasAccountManager( address(addressResolver.gasAccountManager__()) ); address appGateway = vm.envAddress("APP_GATEWAY"); address newAppGateway = vm.envAddress("NEW_APP_GATEWAY"); - uint256 totalCredits = gasAccountManager.totalGas(appGateway); - uint256 payloadEscrow = totalCredits - gasAccountToken.balanceOf(appGateway); + uint256 totalGas = gasAccountManager.totalGas(appGateway); + uint256 payloadEscrow = totalGas - gasAccountToken.balanceOf(appGateway); console.log("App Gateway:", appGateway); console.log("New App Gateway:", newAppGateway); console.log("Fees Manager:", address(gasAccountManager)); - console.log("totalCredits fees:", totalCredits); + console.log("totalGas fees:", totalGas); console.log("payloadEscrow fees:", payloadEscrow); uint256 availableFees = gasAccountToken.balanceOf(appGateway); diff --git a/script/helpers/WithdrawRemainingCredits.s.sol b/script/helpers/WithdrawRemainingGas.s.sol similarity index 82% rename from script/helpers/WithdrawRemainingCredits.s.sol rename to script/helpers/WithdrawRemainingGas.s.sol index 36e04766..9f42dee5 100644 --- a/script/helpers/WithdrawRemainingCredits.s.sol +++ b/script/helpers/WithdrawRemainingGas.s.sol @@ -7,7 +7,7 @@ import {GasAccountManager} from "../../contracts/evmx/fees/GasAccountManager.sol import {AddressResolver} from "../../contracts/evmx/helpers/AddressResolver.sol"; import {GasAccountToken} from "../../contracts/evmx/fees/GasAccountToken.sol"; -contract WithdrawRemainingCredits is Script { +contract WithdrawRemainingGas is Script { function run() external { string memory rpc = vm.envString("EVMX_RPC"); vm.createSelectFork(rpc); @@ -19,12 +19,12 @@ contract WithdrawRemainingCredits is Script { GasAccountManager gasAccountManager = GasAccountManager(address(addressResolver.gasAccountManager__())); address appGateway = vm.envAddress("APP_GATEWAY"); - uint256 totalCredits = gasAccountToken.totalBalanceOf(appGateway); - uint256 payloadEscrow = totalCredits - gasAccountToken.balanceOf(appGateway); + uint256 totalGasFees = gasAccountToken.totalBalanceOf(appGateway); + uint256 payloadEscrow = totalGasFees - gasAccountToken.balanceOf(appGateway); console.log("App Gateway:", appGateway); console.log("Fees Manager:", address(gasAccountToken)); - console.log("totalCredits fees:", totalCredits); - console.log("payloadEscrow fees:", payloadEscrow); + console.log("total gas fees:", totalGasFees); + console.log("payloadEscrow gas fees:", payloadEscrowGasFees); uint256 availableFees = gasAccountToken.balanceOf(appGateway); console.log("Available fees:", availableFees); diff --git a/test/SetupTest.t.sol b/test/SetupTest.t.sol index 141f0549..549a0299 100644 --- a/test/SetupTest.t.sol +++ b/test/SetupTest.t.sol @@ -28,6 +28,8 @@ import "../contracts/evmx/helpers/ForwarderSolana.sol"; import "../contracts/evmx/helpers/AddressResolver.sol"; import "../contracts/evmx/helpers/AsyncDeployer.sol"; import "../contracts/evmx/fees/GasAccountManager.sol"; +import "../contracts/evmx/fees/GasAccountToken.sol"; +import "../contracts/evmx/fees/GasEscrow.sol"; import "../contracts/evmx/fees/GasVault.sol"; import "../contracts/evmx/plugs/GasStation.sol"; import "../contracts/evmx/mocks/TestUSDC.sol"; @@ -85,6 +87,8 @@ contract SetupStore is Test, Utils { SocketContracts public optConfig; GasAccountManager gasAccountManagerImpl; + GasAccountToken gasAccountTokenImpl; + GasEscrow gasEscrowImpl; AddressResolver addressResolverImpl; AsyncDeployer asyncDeployerImpl; Watcher watcherImpl; @@ -92,6 +96,8 @@ contract SetupStore is Test, Utils { ERC1967Factory public proxyFactory; GasAccountManager gasAccountManager; + GasAccountToken gasAccountToken; + GasEscrow gasEscrow; GasVault gasVault; AddressResolver public addressResolver; AsyncDeployer public asyncDeployer; @@ -256,6 +262,8 @@ contract DeploySetup is SetupStore { // Deploy implementations for upgradeable contracts gasAccountManagerImpl = new GasAccountManager(); + gasAccountTokenImpl = new GasAccountToken(); + gasEscrowImpl = new GasEscrow(); addressResolverImpl = new AddressResolver(); asyncDeployerImpl = new AsyncDeployer(); watcherImpl = new Watcher(); @@ -387,21 +395,20 @@ contract FeesSetup is DeploySetup { uint32 indexed chainSlug, address indexed token, address indexed appGateway, - uint256 creditAmount, + uint256 gasAmount, uint256 nativeAmount ); event GasWrapped(address indexed consumeFrom, uint256 amount); event GasUnwrapped(address indexed consumeFrom, uint256 amount); - event CreditsTransferred(address indexed from, address indexed to, uint256 amount); function deploy() internal { _deploy(); - depositNativeAndCredits(arbChainSlug, 100 ether, 100 ether, address(transmitterEOA)); + depositNativeAndGas(arbChainSlug, 100 ether, 100 ether, address(transmitterEOA)); } - function depositNativeAndCredits( + function depositNativeAndGas( uint32 chainSlug_, - uint256 credits_, + uint256 gasAmount_, uint256 native_, address user_ ) internal { @@ -429,39 +436,39 @@ contract FeesSetup is DeploySetup { "Fees plug should have 100 more test tokens" ); - uint256 currentCredits = gasAccountManager.balanceOf(user_); + uint256 currentGas = gasAccountToken.balanceOf(user_); uint256 currentNative = address(user_).balance; vm.expectEmit(true, true, true, false); - emit Deposited(chainSlug_, address(token), user_, credits_, native_); + emit Deposited(chainSlug_, address(token), user_, gasAmount_, native_); hoax(watcherEOA); - gasAccountManager.deposit(abi.encode(chainSlug_, address(token), user_, credits_, native_)); + gasAccountManager.depositFromChain(abi.encode(chainSlug_, address(token), user_, gasAmount_, native_)); assertEq( - gasAccountManager.balanceOf(user_), - currentCredits + credits_, - "User should have more credits" + gasAccountToken.balanceOf(user_), + currentGas + gasAmount_, + "User should have more gas" ); assertEq(address(user_).balance, currentNative + native_, "User should have more native"); } function approve(address appGateway_, address user_) internal { - uint256 approval = gasAccountManager.allowance(user_, appGateway_); + uint256 approval = gasAccountToken.allowance(user_, appGateway_); if (approval > 0) return; hoax(user_); - gasAccountManager.approve(appGateway_, type(uint256).max); + gasAccountToken.approve(appGateway_, type(uint256).max); assertEq( - gasAccountManager.isApproved(user_, appGateway_), - true, + gasAccountToken.allowance(user_, appGateway_), + type(uint256).max, "App gateway should be approved" ); } function permit(address appGateway_, address user_, uint256 userPrivateKey_) internal { - bool approval = gasAccountManager.isApproved(user_, appGateway_); - if (approval) return; + uint256 allowance = gasAccountToken.allowance(user_, appGateway_); + if (allowance > 0) return; uint256 value = type(uint256).max; uint256 deadline = block.timestamp + 1 hours; @@ -475,19 +482,19 @@ contract FeesSetup is DeploySetup { user_, appGateway_, value, - gasAccountManager.nonces(user_), + 1, // gasAccountManager.nonces(user_), // todo deadline ) ); bytes32 digest = keccak256( - abi.encodePacked("\x19\x01", gasAccountManager.DOMAIN_SEPARATOR(), structHash) + abi.encodePacked("\x19\x01", gasAccountToken.DOMAIN_SEPARATOR(), structHash) ); (uint8 v, bytes32 r, bytes32 s) = vm.sign(userPrivateKey_, digest); - gasAccountManager.permit(user_, appGateway_, value, deadline, v, r, s); + gasAccountToken.permit(user_, appGateway_, value, deadline, v, r, s); assertEq( - gasAccountManager.isApproved(user_, appGateway_), - true, + gasAccountToken.allowance(user_, appGateway_), + type(uint256).max, "App gateway should be approved" ); } diff --git a/test/apps/Counter.t.sol b/test/apps/Counter.t.sol index bd05152f..ed3d5e49 100644 --- a/test/apps/Counter.t.sol +++ b/test/apps/Counter.t.sol @@ -16,7 +16,7 @@ contract CounterTest is AppGatewayBaseSetup { deploy(); counterGateway = new CounterAppGateway(address(addressResolver), feesAmount); - depositNativeAndCredits(arbChainSlug, 1 ether, 0, address(counterGateway)); + depositNativeAndGas(arbChainSlug, 1 ether, 0, address(counterGateway)); counterId = counterGateway.counter(); } From 5ad30edccac862c636245a0691757e6f8f0fd6d6 Mon Sep 17 00:00:00 2001 From: Ameesha Agrawal Date: Thu, 13 Nov 2025 13:43:56 +0530 Subject: [PATCH 10/12] fix: tests --- contracts/evmx/fees/GasAccountToken.sol | 6 +-- contracts/evmx/fees/GasEscrow.sol | 6 +-- .../watcher/precompiles/ReadPrecompile.sol | 2 +- contracts/protocol/Socket.sol | 5 +-- .../protocol/switchboard/FastSwitchboard.sol | 7 +++- .../switchboard/MessageSwitchboard.sol | 2 +- contracts/utils/common/Errors.sol | 1 + script/helpers/WithdrawRemainingGas.s.sol | 9 ++--- test/SetupTest.t.sol | 37 ++++++++++++++++--- 9 files changed, 50 insertions(+), 25 deletions(-) diff --git a/contracts/evmx/fees/GasAccountToken.sol b/contracts/evmx/fees/GasAccountToken.sol index bc4cf494..cfc010cc 100644 --- a/contracts/evmx/fees/GasAccountToken.sol +++ b/contracts/evmx/fees/GasAccountToken.sol @@ -38,15 +38,15 @@ contract GasAccountToken is ERC20, Ownable, Initializable { _setOwner(owner_); } - function decimals() public view override returns (uint8) { + function decimals() public pure override returns (uint8) { return 18; } - function symbol() public view override returns (string memory) { + function symbol() public pure override returns (string memory) { return "SGAS"; } - function name() public view override returns (string memory) { + function name() public pure override returns (string memory) { return "Socket Gas"; } diff --git a/contracts/evmx/fees/GasEscrow.sol b/contracts/evmx/fees/GasEscrow.sol index d0eebd5e..d3e57e72 100644 --- a/contracts/evmx/fees/GasEscrow.sol +++ b/contracts/evmx/fees/GasEscrow.sol @@ -6,8 +6,6 @@ import "../../utils/RescueFundsLib.sol"; import "solady/utils/Initializable.sol"; import "solady/auth/Ownable.sol"; - - abstract contract GasEscrowStorage is IGasEscrow { // slots [0-49] reserved for gap uint256[50] _gap_before; @@ -41,7 +39,7 @@ contract GasEscrow is IGasEscrow, GasEscrowStorage, Initializable, Ownable { _disableInitializers(); } - function initialize(address gasAccountManager_, address owner_) external reinitializer(1) { + function initialize(address owner_, address gasAccountManager_) external reinitializer(1) { _setOwner(owner_); gasAccountManager = gasAccountManager_; } @@ -69,8 +67,8 @@ contract GasEscrow is IGasEscrow, GasEscrowStorage, Initializable, Ownable { /// @notice Release escrow back to account function releaseEscrow(bytes32 payloadId) external onlyGasAccountManager { EscrowEntry storage entry = payloadEscrow[payloadId]; - require(entry.state == EscrowState.Active, "Not active"); if (entry.amount == 0) return; + if (entry.state != EscrowState.Active) revert NotActive(); accountEscrow[entry.account] -= entry.amount; entry.state = EscrowState.Released; diff --git a/contracts/evmx/watcher/precompiles/ReadPrecompile.sol b/contracts/evmx/watcher/precompiles/ReadPrecompile.sol index 8050e9a7..8f799444 100644 --- a/contracts/evmx/watcher/precompiles/ReadPrecompile.sol +++ b/contracts/evmx/watcher/precompiles/ReadPrecompile.sol @@ -64,7 +64,7 @@ contract ReadPrecompile is IPrecompile { ); } - function resolvePayload(Payload calldata payload) external onlyWatcher { + function resolvePayload(Payload calldata payload) external view { if (block.timestamp > payload.deadline) revert DeadlinePassed(); } diff --git a/contracts/protocol/Socket.sol b/contracts/protocol/Socket.sol index 4b200b23..76d80eb8 100644 --- a/contracts/protocol/Socket.sol +++ b/contracts/protocol/Socket.sol @@ -103,13 +103,12 @@ contract Socket is SocketUtils, Pausable { /** * @notice Verifies the digest of the payload * @param payloadId_ The id of the payload - * @param switchboardId_ The id of the switchboard * @param executeParams_ The execution parameters (appGatewayId, value, payloadId, callType, gasLimit) * @param transmitterProof_ The transmitter proof */ function _verify( bytes32 payloadId_, - uint32 switchboardId_, + uint32, ExecuteParams calldata executeParams_, bytes calldata transmitterProof_ ) internal { @@ -220,7 +219,7 @@ contract Socket is SocketUtils, Pausable { uint256 value_, bytes calldata data_ ) internal whenNotPaused returns (bytes32 payloadId) { - (uint32 switchboardId, address switchboardAddress) = _verifyPlugSwitchboard(plug_); + (, address switchboardAddress) = _verifyPlugSwitchboard(plug_); bytes memory plugOverrides = IPlug(plug_).overrides(); // Switchboard creates the payload ID and emits PayloadRequested event diff --git a/contracts/protocol/switchboard/FastSwitchboard.sol b/contracts/protocol/switchboard/FastSwitchboard.sol index 42704fa0..5b9c995c 100644 --- a/contracts/protocol/switchboard/FastSwitchboard.sol +++ b/contracts/protocol/switchboard/FastSwitchboard.sol @@ -125,9 +125,12 @@ contract FastSwitchboard is SwitchboardBase { bytes calldata overrides_ ) external payable override onlySocket returns (bytes32 payloadId) { if (evmxChainSlug == 0 || watcherId == 0) revert EvmxConfigNotSet(); - uint256 deadline = abi.decode(overrides_, (uint256)); bytes memory overrides = overrides_; + uint256 deadline = 0; + if (overrides_.length > 0) { + deadline = abi.decode(overrides_, (uint256)); + } if (deadline == 0) overrides = abi.encode(block.timestamp + defaultDeadline); // Create trigger payload ID @@ -179,7 +182,7 @@ contract FastSwitchboard is SwitchboardBase { */ function getPlugConfig( address plug_, - bytes memory extraData_ + bytes memory ) external view override returns (bytes memory configData_) { configData_ = abi.encode(plugAppGatewayIds[plug_]); } diff --git a/contracts/protocol/switchboard/MessageSwitchboard.sol b/contracts/protocol/switchboard/MessageSwitchboard.sol index 482d655e..7588af6e 100644 --- a/contracts/protocol/switchboard/MessageSwitchboard.sol +++ b/contracts/protocol/switchboard/MessageSwitchboard.sol @@ -252,7 +252,7 @@ contract MessageSwitchboard is SwitchboardBase { */ function _decodeOverrides( bytes calldata overrides_ - ) internal returns (MessageOverrides memory) { + ) internal view returns (MessageOverrides memory) { uint8 version = abi.decode(overrides_, (uint8)); if (version == 1) { diff --git a/contracts/utils/common/Errors.sol b/contracts/utils/common/Errors.sol index 7f6fcc79..abc5533a 100644 --- a/contracts/utils/common/Errors.sol +++ b/contracts/utils/common/Errors.sol @@ -71,3 +71,4 @@ error InvalidSignature(); error DeadlinePassed(); // Only Watcher can call functions error InvalidReceiver(); +error InsufficientGasAvailable(); \ No newline at end of file diff --git a/script/helpers/WithdrawRemainingGas.s.sol b/script/helpers/WithdrawRemainingGas.s.sol index 9f42dee5..72400fa7 100644 --- a/script/helpers/WithdrawRemainingGas.s.sol +++ b/script/helpers/WithdrawRemainingGas.s.sol @@ -16,7 +16,6 @@ contract WithdrawRemainingGas is Script { vm.startBroadcast(deployerPrivateKey); AddressResolver addressResolver = AddressResolver(vm.envAddress("ADDRESS_RESOLVER")); GasAccountToken gasAccountToken = GasAccountToken(address(addressResolver.gasAccountToken__())); - GasAccountManager gasAccountManager = GasAccountManager(address(addressResolver.gasAccountManager__())); address appGateway = vm.envAddress("APP_GATEWAY"); uint256 totalGasFees = gasAccountToken.totalBalanceOf(appGateway); @@ -24,11 +23,11 @@ contract WithdrawRemainingGas is Script { console.log("App Gateway:", appGateway); console.log("Fees Manager:", address(gasAccountToken)); console.log("total gas fees:", totalGasFees); - console.log("payloadEscrow gas fees:", payloadEscrowGasFees); + console.log("payloadEscrow gas fees:", payloadEscrow); - uint256 availableFees = gasAccountToken.balanceOf(appGateway); - console.log("Available fees:", availableFees); - gasAccountToken.transferFrom(appGateway, vm.addr(deployerPrivateKey), availableFees); + uint256 availableGas = gasAccountToken.balanceOf(appGateway); + console.log("Available gas:", availableGas); + gasAccountToken.transferFrom(appGateway, vm.addr(deployerPrivateKey), availableGas); vm.stopBroadcast(); } diff --git a/test/SetupTest.t.sol b/test/SetupTest.t.sol index 549a0299..ea32c3b4 100644 --- a/test/SetupTest.t.sol +++ b/test/SetupTest.t.sol @@ -130,6 +130,9 @@ contract DeploySetup is SetupStore { addressResolver.setWatcher(address(watcher)); addressResolver.setAsyncDeployer(address(asyncDeployer)); addressResolver.setGasAccountManager(address(gasAccountManager)); + addressResolver.setGasAccountToken(address(gasAccountToken)); + addressResolver.setGasEscrow(address(gasEscrow)); + addressResolver.setGasVault(address(gasVault)); watcher.setPrecompile(WRITE, writePrecompile); watcher.setPrecompile(READ, readPrecompile); @@ -282,17 +285,37 @@ contract DeploySetup is SetupStore { watcherEOA, abi.encodeWithSelector( GasAccountManager.initialize.selector, - evmxSlug, address(addressResolver), - address(gasVault), watcherEOA, - writeFees, + feesAmount, FAST, address(forwarderSolana) ) ); gasAccountManager = GasAccountManager(gasAccountManagerProxy); + address gasAccountTokenProxy = _deployAndVerifyProxy( + address(gasAccountTokenImpl), + watcherEOA, + abi.encodeWithSelector( + GasAccountToken.initialize.selector, + watcherEOA, + address(addressResolver) + ) + ); + gasAccountToken = GasAccountToken(gasAccountTokenProxy); + + address gasEscrowProxy = _deployAndVerifyProxy( + address(gasEscrowImpl), + watcherEOA, + abi.encodeWithSelector( + GasEscrow.initialize.selector, + watcherEOA, + address(gasAccountManager) + ) + ); + gasEscrow = GasEscrow(gasEscrowProxy); + address asyncDeployerProxy = _deployAndVerifyProxy( address(asyncDeployerImpl), watcherEOA, @@ -442,7 +465,9 @@ contract FeesSetup is DeploySetup { vm.expectEmit(true, true, true, false); emit Deposited(chainSlug_, address(token), user_, gasAmount_, native_); hoax(watcherEOA); - gasAccountManager.depositFromChain(abi.encode(chainSlug_, address(token), user_, gasAmount_, native_)); + gasAccountManager.depositFromChain( + abi.encode(chainSlug_, address(token), user_, gasAmount_, native_) + ); assertEq( gasAccountToken.balanceOf(user_), @@ -722,11 +747,11 @@ contract WatcherSetup is FeesSetup { function _resolvePayload(PromiseReturnData memory promiseReturnData) internal { WatcherMultiCallParams memory params = WatcherMultiCallParams({ contractAddress: address(watcher), - data: abi.encode(promiseReturnData, feesAmount), + data: abi.encode(promiseReturnData, feesAmount/2), nonce: watcherNonce, signature: _createWatcherSignature( address(watcher), - abi.encode(promiseReturnData, feesAmount) + abi.encode(promiseReturnData, feesAmount/2) ) }); watcherNonce++; From db1a6e00693007c47096653f450e293d88dc63fe Mon Sep 17 00:00:00 2001 From: Ameesha Agrawal Date: Thu, 13 Nov 2025 14:39:36 +0530 Subject: [PATCH 11/12] fix: remove susdc --- contracts/evmx/fees/GasAccountManager.sol | 5 ----- contracts/evmx/interfaces/IGasAccountManager.sol | 3 --- 2 files changed, 8 deletions(-) diff --git a/contracts/evmx/fees/GasAccountManager.sol b/contracts/evmx/fees/GasAccountManager.sol index f672a15b..604f408b 100644 --- a/contracts/evmx/fees/GasAccountManager.sol +++ b/contracts/evmx/fees/GasAccountManager.sol @@ -33,7 +33,6 @@ abstract contract GasAccountManagerStorage is IGasAccountManager { /////////////////////// SOLANA /////////////////////// ForwarderSolana public forwarderSolana; - bytes32 public susdcSolanaProgramId; bytes32 public gasStationSolanaProgramId; /// @notice Mapping to track fees plug for each chain slug @@ -234,10 +233,6 @@ contract GasAccountManager is GasAccountManagerStorage, Ownable, AccessControl, emit ForwarderSolanaSet(forwarderSolana_); } - function setSusdcSolanaProgramId(bytes32 susdcSolanaProgramId_) external onlyOwner { - susdcSolanaProgramId = susdcSolanaProgramId_; - emit SusdcSolanaProgramIdSet(susdcSolanaProgramId_); - } function setGasStationSolanaProgramId(bytes32 gasStationSolanaProgramId_) external onlyOwner { gasStationSolanaProgramId = gasStationSolanaProgramId_; diff --git a/contracts/evmx/interfaces/IGasAccountManager.sol b/contracts/evmx/interfaces/IGasAccountManager.sol index 12322aeb..641ea329 100644 --- a/contracts/evmx/interfaces/IGasAccountManager.sol +++ b/contracts/evmx/interfaces/IGasAccountManager.sol @@ -20,9 +20,6 @@ interface IGasAccountManager { /// @notice Emitted when fees plug is set event GasStationSet(uint32 indexed chainSlug, bytes32 indexed gasStation); - /// @notice Emitted when fees plug solana program id is set - event SusdcSolanaProgramIdSet(bytes32 indexed susdcSolanaProgramId); - /// @notice Emitted when fees plug solana program id is set event GasStationSolanaProgramIdSet(bytes32 indexed gasStationSolanaProgramId); From a6324748fec867e27c67220e7f43709a844c902d Mon Sep 17 00:00:00 2001 From: arthcp Date: Thu, 13 Nov 2025 16:28:31 +0400 Subject: [PATCH 12/12] chore: review --- contracts/evmx/fees/GasAccountManager.sol | 6 +++++- contracts/evmx/fees/GasAccountToken.sol | 1 + contracts/evmx/fees/GasEscrow.sol | 7 ++++--- contracts/evmx/plugs/GasStation.sol | 1 + contracts/evmx/watcher/Watcher.sol | 2 +- contracts/protocol/Socket.sol | 1 + contracts/protocol/SocketConfig.sol | 2 +- 7 files changed, 14 insertions(+), 6 deletions(-) diff --git a/contracts/evmx/fees/GasAccountManager.sol b/contracts/evmx/fees/GasAccountManager.sol index 604f408b..86e23cbb 100644 --- a/contracts/evmx/fees/GasAccountManager.sol +++ b/contracts/evmx/fees/GasAccountManager.sol @@ -66,6 +66,7 @@ contract GasAccountManager is GasAccountManagerStorage, Ownable, AccessControl, // ============ GAS ACCOUNT OPERATIONS ============ /// @notice Wrap native tokens into SGAS + // todo: remove onlywatcher function wrapToGas(address receiver) external payable override onlyWatcher { uint256 amount = msg.value; if (amount == 0) revert InvalidAmount(); @@ -79,7 +80,9 @@ contract GasAccountManager is GasAccountManagerStorage, Ownable, AccessControl, } /// @notice Unwrap SGAS to native tokens + // todo: remove onlywatcher function unwrapFromGas(uint256 amount, address receiver) external onlyWatcher { + // todo: use isGasAvailable, check all gasAccountToken__().balanceOf instances if (gasAccountToken__().balanceOf(msg.sender) < amount) revert InsufficientGasAvailable(); // Burn tokens from sender @@ -98,7 +101,7 @@ contract GasAccountManager is GasAccountManagerStorage, Ownable, AccessControl, function depositFromChain(bytes memory payload_) external onlyWatcher { // Decode payload: (chainSlug, token, receiver, gasAmount, nativeAmount) ( - uint32 chainSlug, + uint32 chainSlug, // todo: read chainslug from watcher instead of passing in payload address token, address depositTo, uint256 gasAmount, @@ -265,6 +268,7 @@ contract GasAccountManager is GasAccountManagerStorage, Ownable, AccessControl, overrideParams = overrideParams.setMaxFees(fees_); } + // todo: auth consumefrom check function increaseFees(bytes32 payloadId_, uint256 newMaxFees_) public { _increaseFees(payloadId_, newMaxFees_); } diff --git a/contracts/evmx/fees/GasAccountToken.sol b/contracts/evmx/fees/GasAccountToken.sol index cfc010cc..4616c32e 100644 --- a/contracts/evmx/fees/GasAccountToken.sol +++ b/contracts/evmx/fees/GasAccountToken.sol @@ -103,6 +103,7 @@ contract GasAccountToken is ERC20, Ownable, Initializable { ) public view returns (bool) { // If consumeFrom_ is not same as spender_ or spender_ is not watcher, check if it is approved if (spender_ != address(addressResolver__.watcher__()) && consumeFrom_ != spender_) { + // todo: amount check instead of zero check if (allowance(consumeFrom_, spender_) == 0) return false; } diff --git a/contracts/evmx/fees/GasEscrow.sol b/contracts/evmx/fees/GasEscrow.sol index d3e57e72..532d622a 100644 --- a/contracts/evmx/fees/GasEscrow.sol +++ b/contracts/evmx/fees/GasEscrow.sol @@ -57,14 +57,14 @@ contract GasEscrow is IGasEscrow, GasEscrowStorage, Initializable, Ownable { payloadEscrow[payloadId_] = EscrowEntry({ account: consumeFrom_, amount: amount, - timestamp: block.timestamp, + timestamp: block.timestamp, // todo: needed? state: EscrowState.Active }); - emit GasEscrowed(payloadId_, consumeFrom_, amount); + emit GasEscrowed(payloadId_, consumeFrom_, amount); // todo: emit diff amount not total, diff applies to both accountEscrow and EscrowEntry. } - /// @notice Release escrow back to account + /// @notice Release escrow back to account, cases where payload is not executed function releaseEscrow(bytes32 payloadId) external onlyGasAccountManager { EscrowEntry storage entry = payloadEscrow[payloadId]; if (entry.amount == 0) return; @@ -76,6 +76,7 @@ contract GasEscrow is IGasEscrow, GasEscrowStorage, Initializable, Ownable { } /// @notice Mark escrow as settled (paid to transmitter) + // todo: what are final states? when part amount is used, how to settle+settle+release work function settleGasPayment( bytes32 payloadId, address transmitter, diff --git a/contracts/evmx/plugs/GasStation.sol b/contracts/evmx/plugs/GasStation.sol index 1db7398c..02f420f1 100644 --- a/contracts/evmx/plugs/GasStation.sol +++ b/contracts/evmx/plugs/GasStation.sol @@ -75,6 +75,7 @@ contract GasStation is IGasStation, PlugBase, AccessControl { gasAmount_, nativeAmount_ ); + // todo: IGasAccountManager(socket__).depositFromChain(token_, receiver_, gasAmount_, nativeAmount_); // Create trigger via Socket to get unique payloadId bytes32 payloadId = socket__.sendPayload(payload); diff --git a/contracts/evmx/watcher/Watcher.sol b/contracts/evmx/watcher/Watcher.sol index be97d904..20d98e32 100644 --- a/contracts/evmx/watcher/Watcher.sol +++ b/contracts/evmx/watcher/Watcher.sol @@ -164,7 +164,7 @@ contract Watcher is Initializable, Configurations, Pausable { p.watcherFees ); gasAccountManager__().releaseEscrow(p.payloadId); - emit PayloadSettled(p.payloadId); + emit PayloadSettled(p.payloadId); // todo: both settle and resolve events needed? emit PayloadResolved(resolvedPromise_.payloadId); } diff --git a/contracts/protocol/Socket.sol b/contracts/protocol/Socket.sol index 76d80eb8..9cf21157 100644 --- a/contracts/protocol/Socket.sol +++ b/contracts/protocol/Socket.sol @@ -244,6 +244,7 @@ contract Socket is SocketUtils, Pausable { ); } + // todo: dont need to return switchboardId function _verifyPlugSwitchboard( address plug_ ) internal view returns (uint32 switchboardId, address switchboardAddress) { diff --git a/contracts/protocol/SocketConfig.sol b/contracts/protocol/SocketConfig.sol index e2d90752..4e285ba7 100644 --- a/contracts/protocol/SocketConfig.sol +++ b/contracts/protocol/SocketConfig.sol @@ -116,8 +116,8 @@ abstract contract SocketConfig is ISocket, AccessControl { function setNetworkFeeCollector( address networkFeeCollector_ ) external onlyRole(GOVERNANCE_ROLE) { - networkFeeCollector = INetworkFeeCollector(networkFeeCollector_); emit NetworkFeeCollectorUpdated(address(networkFeeCollector), networkFeeCollector_); + networkFeeCollector = INetworkFeeCollector(networkFeeCollector_); } /**