From b15f1430bae316e6790ce91f374391b61bdeb509 Mon Sep 17 00:00:00 2001 From: Ameesha Agrawal Date: Fri, 14 Nov 2025 14:26:39 +0530 Subject: [PATCH 1/3] fix: sb sign --- contracts/protocol/switchboard/SwitchboardBase.sol | 2 +- test/PausableTest.t.sol | 1 + test/SetupTest.t.sol | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/contracts/protocol/switchboard/SwitchboardBase.sol b/contracts/protocol/switchboard/SwitchboardBase.sol index 85154052..f6e4ef3b 100644 --- a/contracts/protocol/switchboard/SwitchboardBase.sol +++ b/contracts/protocol/switchboard/SwitchboardBase.sol @@ -63,7 +63,7 @@ abstract contract SwitchboardBase is ISwitchboard, AccessControl { transmitter = transmitterSignature_.length > 0 ? _recoverSigner( // TODO: use api encode packed - keccak256(abi.encode(address(socket__), payloadId_)), + keccak256(abi.encodePacked(address(socket__), payloadId_)), transmitterSignature_ ) : address(0); diff --git a/test/PausableTest.t.sol b/test/PausableTest.t.sol index d2192ace..4547a7f6 100644 --- a/test/PausableTest.t.sol +++ b/test/PausableTest.t.sol @@ -50,6 +50,7 @@ contract PausableTest is Test { owner, address(0), address(0), + bytes32(0), 0 ); watcher = Watcher(proxyFactory.deployAndCall(address(watcherImpl), owner, data)); diff --git a/test/SetupTest.t.sol b/test/SetupTest.t.sol index 41d2fdf9..bcc9e0cd 100644 --- a/test/SetupTest.t.sol +++ b/test/SetupTest.t.sol @@ -695,7 +695,7 @@ contract WatcherSetup is FeesSetup { // this is a signature for the socket batcher (only used for EVM) bytes memory transmitterSig = createSignature( keccak256( - abi.encode(address(getSocketConfig(chainSlug).socket), payloadParams.payloadId) + abi.encodePacked(address(getSocketConfig(chainSlug).socket), payloadParams.payloadId) ), transmitterPrivateKey ); From 3f204c799b50968b8e32b1c1459cae92388312ee Mon Sep 17 00:00:00 2001 From: Ameesha Agrawal Date: Fri, 14 Nov 2025 15:18:46 +0530 Subject: [PATCH 2/3] fix: review fixes --- contracts/evmx/fees/GasAccountManager.sol | 54 ++++++++++--------- contracts/evmx/fees/GasAccountToken.sol | 5 +- contracts/evmx/fees/GasEscrow.sol | 3 +- contracts/evmx/helpers/AsyncPromise.sol | 1 - .../evmx/interfaces/IGasAccountManager.sol | 14 ++++- contracts/evmx/plugs/GasStation.sol | 16 ++++-- contracts/protocol/Socket.sol | 11 ++-- .../protocol/switchboard/SwitchboardBase.sol | 1 - contracts/utils/common/Structs.sol | 1 - test/SetupTest.t.sol | 16 ++++-- 10 files changed, 70 insertions(+), 52 deletions(-) diff --git a/contracts/evmx/fees/GasAccountManager.sol b/contracts/evmx/fees/GasAccountManager.sol index 86e23cbb..56d035f3 100644 --- a/contracts/evmx/fees/GasAccountManager.sol +++ b/contracts/evmx/fees/GasAccountManager.sol @@ -43,8 +43,15 @@ abstract contract GasAccountManagerStorage is IGasAccountManager { uint256[50] _gap_after; } -contract GasAccountManager is GasAccountManagerStorage, Ownable, AccessControl, AppGatewayBase, Initializable { +contract GasAccountManager is + GasAccountManagerStorage, + Ownable, + AccessControl, + AppGatewayBase, + Initializable +{ using OverrideParamsLib for OverrideParams; + error OnlyPayloadConsumer(); constructor() { _disableInitializers(); @@ -66,8 +73,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 { + function wrapToGas(address receiver) external payable override { uint256 amount = msg.value; if (amount == 0) revert InvalidAmount(); @@ -80,8 +86,7 @@ contract GasAccountManager is GasAccountManagerStorage, Ownable, AccessControl, } /// @notice Unwrap SGAS to native tokens - // todo: remove onlywatcher - function unwrapFromGas(uint256 amount, address receiver) external onlyWatcher { + function unwrapFromGas(uint256 amount, address receiver) external { // todo: use isGasAvailable, check all gasAccountToken__().balanceOf instances if (gasAccountToken__().balanceOf(msg.sender) < amount) revert InsufficientGasAvailable(); @@ -98,33 +103,30 @@ contract GasAccountManager is GasAccountManagerStorage, Ownable, AccessControl, /// @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, gasAmount, nativeAmount) - ( - uint32 chainSlug, // todo: read chainslug from watcher instead of passing in payload - address token, - address depositTo, - uint256 gasAmount, - uint256 nativeAmount - ) = abi.decode(payload_, (uint32, address, address, uint256, uint256)); - - tokenOnChainBalances[chainSlug][toBytes32Format(token)] += gasAmount + nativeAmount; + function depositFromChain( + address token_, + address depositTo_, + uint256 gasAmount_, + uint256 nativeAmount_ + ) external onlyWatcher { + uint32 chainSlug = watcher__().triggerFromChainSlug(); + tokenOnChainBalances[chainSlug][toBytes32Format(token_)] += gasAmount_ + nativeAmount_; // Mint tokens to the user - gasAccountToken__().mint(depositTo, gasAmount); - if (nativeAmount > 0) { + gasAccountToken__().mint(depositTo_, gasAmount_); + if (nativeAmount_ > 0) { // if native transfer fails, add to gas - bool success = gasVault__().withdraw(depositTo, nativeAmount); + bool success = gasVault__().withdraw(depositTo_, nativeAmount_); if (!success) { // Convert failed native amount to gas - gasAccountToken__().mint(depositTo, nativeAmount); - gasAmount += nativeAmount; - nativeAmount = 0; + gasAccountToken__().mint(depositTo_, nativeAmount_); + gasAmount_ += nativeAmount_; + nativeAmount_ = 0; } } - emit Deposited(chainSlug, token, depositTo, gasAmount, nativeAmount); + emit Deposited(chainSlug, token_, depositTo_, gasAmount_, nativeAmount_); } /// @notice Withdraw SGAS to tokens on another chain @@ -236,7 +238,6 @@ contract GasAccountManager is GasAccountManagerStorage, Ownable, AccessControl, emit ForwarderSolanaSet(forwarderSolana_); } - function setGasStationSolanaProgramId(bytes32 gasStationSolanaProgramId_) external onlyOwner { gasStationSolanaProgramId = gasStationSolanaProgramId_; emit GasStationSolanaProgramIdSet(gasStationSolanaProgramId_); @@ -268,8 +269,11 @@ contract GasAccountManager is GasAccountManagerStorage, Ownable, AccessControl, overrideParams = overrideParams.setMaxFees(fees_); } - // todo: auth consumefrom check function increaseFees(bytes32 payloadId_, uint256 newMaxFees_) public { + if (msg.sender != watcher__().getPayload(payloadId_).consumeFrom) + revert OnlyPayloadConsumer(); + + // fees deducted from consumeFrom account _increaseFees(payloadId_, newMaxFees_); } diff --git a/contracts/evmx/fees/GasAccountToken.sol b/contracts/evmx/fees/GasAccountToken.sol index 4616c32e..1cae1c76 100644 --- a/contracts/evmx/fees/GasAccountToken.sol +++ b/contracts/evmx/fees/GasAccountToken.sol @@ -84,8 +84,6 @@ contract GasAccountToken is ERC20, Ownable, Initializable { uint256 amount_ ) public override returns (bool) { if (!isGasAvailable(from_, msg.sender, amount_)) revert InsufficientGasAvailable(); - - // todo: check if (msg.sender == address(addressResolver__.watcher__())) _approve(from_, msg.sender, amount_); return super.transferFrom(from_, to_, amount_); @@ -103,8 +101,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; + if (allowance(consumeFrom_, spender_) < amount_) return false; } return balanceOf(consumeFrom_) >= amount_; diff --git a/contracts/evmx/fees/GasEscrow.sol b/contracts/evmx/fees/GasEscrow.sol index 532d622a..0db9e25d 100644 --- a/contracts/evmx/fees/GasEscrow.sol +++ b/contracts/evmx/fees/GasEscrow.sol @@ -57,11 +57,10 @@ contract GasEscrow is IGasEscrow, GasEscrowStorage, Initializable, Ownable { payloadEscrow[payloadId_] = EscrowEntry({ account: consumeFrom_, amount: amount, - timestamp: block.timestamp, // todo: needed? state: EscrowState.Active }); - emit GasEscrowed(payloadId_, consumeFrom_, amount); // todo: emit diff amount not total, diff applies to both accountEscrow and EscrowEntry. + emit GasEscrowed(payloadId_, consumeFrom_, amount_); } /// @notice Release escrow back to account, cases where payload is not executed diff --git a/contracts/evmx/helpers/AsyncPromise.sol b/contracts/evmx/helpers/AsyncPromise.sol index 7495a6bf..7087dcdc 100644 --- a/contracts/evmx/helpers/AsyncPromise.sol +++ b/contracts/evmx/helpers/AsyncPromise.sol @@ -152,7 +152,6 @@ contract AsyncPromise is AsyncPromiseStorage, Initializable, AddressResolverUtil (bool success, , ) = localInvoker.tryCall(0, gasleft(), 0, combinedCalldata); if (!success) { - // todo: in this case, promise will stay unresolved revert PromiseRevertFailed(); } } diff --git a/contracts/evmx/interfaces/IGasAccountManager.sol b/contracts/evmx/interfaces/IGasAccountManager.sol index 641ea329..118a82a8 100644 --- a/contracts/evmx/interfaces/IGasAccountManager.sol +++ b/contracts/evmx/interfaces/IGasAccountManager.sol @@ -64,7 +64,12 @@ interface IGasAccountManager { /// @notice Deposit tokens from a chain into gas account /// @dev Called by watcher after detecting GasStation deposit - function depositFromChain(bytes memory payload_) external; + function depositFromChain( + address token_, + address receiver_, + uint256 gasAmount_, + uint256 nativeAmount_ + ) external; /// @notice Withdraw SGAS to tokens on another chain function withdrawToChain( @@ -87,5 +92,10 @@ interface IGasAccountManager { /// @notice Settle escrowed gas to transmitter /// @dev Called when payload completes successfully - function settleGasPayment(bytes32 payloadId, address consumeFrom, address transmitter, uint256 amount) external; + function settleGasPayment( + bytes32 payloadId, + address consumeFrom, + address transmitter, + uint256 amount + ) external; } diff --git a/contracts/evmx/plugs/GasStation.sol b/contracts/evmx/plugs/GasStation.sol index 02f420f1..506836be 100644 --- a/contracts/evmx/plugs/GasStation.sol +++ b/contracts/evmx/plugs/GasStation.sol @@ -10,6 +10,15 @@ import "../../utils/RescueFundsLib.sol"; import {InvalidTokenAddress} from "../../utils/common/Errors.sol"; import "../interfaces/IGasAccountToken.sol"; +interface IGasAccountManager { + function depositFromChain( + address token_, + address receiver_, + uint256 gasAmount_, + uint256 nativeAmount_ + ) external returns (bytes memory payloadId); +} + /// @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 @@ -68,19 +77,16 @@ contract GasStation is IGasStation, PlugBase, AccessControl { if (!whitelistedTokens[token_]) revert TokenNotWhitelisted(token_); // Encode deposit parameters: (chainSlug, token, receiver, gasAmount, nativeAmount) - bytes memory payload = abi.encode( - socket__.chainSlug(), + bytes memory payloadId = IGasAccountManager(address(socket__)).depositFromChain( token_, receiver_, gasAmount_, nativeAmount_ ); - // todo: IGasAccountManager(socket__).depositFromChain(token_, receiver_, gasAmount_, nativeAmount_); // Create trigger via Socket to get unique payloadId - bytes32 payloadId = socket__.sendPayload(payload); token_.safeTransferFrom(msg.sender, address(this), gasAmount_ + nativeAmount_); - emit GasDeposited(token_, receiver_, gasAmount_, nativeAmount_, payloadId); + emit GasDeposited(token_, receiver_, gasAmount_, nativeAmount_, bytes32(payloadId)); } /// @notice Withdraws tokens diff --git a/contracts/protocol/Socket.sol b/contracts/protocol/Socket.sol index 9cf21157..e24de238 100644 --- a/contracts/protocol/Socket.sol +++ b/contracts/protocol/Socket.sol @@ -112,7 +112,7 @@ contract Socket is SocketUtils, Pausable { ExecuteParams calldata executeParams_, bytes calldata transmitterProof_ ) internal { - (, address switchboardAddress) = _verifyPlugSwitchboard(executeParams_.target); + address switchboardAddress = _verifyPlugSwitchboard(executeParams_.target); // NOTE: the first un-trusted call in the system address transmitter = ISwitchboard(switchboardAddress).getTransmitter( msg.sender, @@ -219,7 +219,7 @@ contract Socket is SocketUtils, Pausable { uint256 value_, bytes calldata data_ ) internal whenNotPaused returns (bytes32 payloadId) { - (, address switchboardAddress) = _verifyPlugSwitchboard(plug_); + address switchboardAddress = _verifyPlugSwitchboard(plug_); bytes memory plugOverrides = IPlug(plug_).overrides(); // Switchboard creates the payload ID and emits PayloadRequested event @@ -236,7 +236,7 @@ contract Socket is SocketUtils, Pausable { * @param feesData_ Encoded fees data (type + data) */ function increaseFeesForPayload(bytes32 payloadId_, bytes calldata feesData_) external payable { - (, address switchboardAddress) = _verifyPlugSwitchboard(msg.sender); + address switchboardAddress = _verifyPlugSwitchboard(msg.sender); ISwitchboard(switchboardAddress).increaseFeesForPayload{value: msg.value}( payloadId_, msg.sender, @@ -244,11 +244,10 @@ contract Socket is SocketUtils, Pausable { ); } - // todo: dont need to return switchboardId function _verifyPlugSwitchboard( address plug_ - ) internal view returns (uint32 switchboardId, address switchboardAddress) { - switchboardId = plugSwitchboardIds[plug_]; + ) internal view returns (address switchboardAddress) { + uint32 switchboardId = plugSwitchboardIds[plug_]; if (switchboardId == 0) revert PlugNotFound(); if (isValidSwitchboard[switchboardId] != SwitchboardStatus.REGISTERED) revert InvalidSwitchboard(); diff --git a/contracts/protocol/switchboard/SwitchboardBase.sol b/contracts/protocol/switchboard/SwitchboardBase.sol index f6e4ef3b..95699870 100644 --- a/contracts/protocol/switchboard/SwitchboardBase.sol +++ b/contracts/protocol/switchboard/SwitchboardBase.sol @@ -62,7 +62,6 @@ abstract contract SwitchboardBase is ISwitchboard, AccessControl { ) external view returns (address transmitter) { transmitter = transmitterSignature_.length > 0 ? _recoverSigner( - // TODO: use api encode packed keccak256(abi.encodePacked(address(socket__), payloadId_)), transmitterSignature_ ) diff --git a/contracts/utils/common/Structs.sol b/contracts/utils/common/Structs.sol index d5dc48f8..d7176d39 100644 --- a/contracts/utils/common/Structs.sol +++ b/contracts/utils/common/Structs.sol @@ -39,7 +39,6 @@ enum EscrowState { struct EscrowEntry { address account; // Who's paying uint256 amount; // How much is escrowed - uint256 timestamp; // When escrowed EscrowState state; // Current state } diff --git a/test/SetupTest.t.sol b/test/SetupTest.t.sol index bcc9e0cd..9180b664 100644 --- a/test/SetupTest.t.sol +++ b/test/SetupTest.t.sol @@ -31,7 +31,7 @@ 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 {GasStation} from "../contracts/evmx/plugs/GasStation.sol"; import "../contracts/evmx/mocks/TestUSDC.sol"; import "solady/utils/ERC1967Factory.sol"; @@ -467,7 +467,10 @@ contract FeesSetup is DeploySetup { emit Deposited(chainSlug_, address(token), user_, gasAmount_, native_); hoax(watcherEOA); gasAccountManager.depositFromChain( - abi.encode(chainSlug_, address(token), user_, gasAmount_, native_) + address(token), + user_, + gasAmount_, + native_ ); assertEq( @@ -695,7 +698,10 @@ contract WatcherSetup is FeesSetup { // this is a signature for the socket batcher (only used for EVM) bytes memory transmitterSig = createSignature( keccak256( - abi.encodePacked(address(getSocketConfig(chainSlug).socket), payloadParams.payloadId) + abi.encodePacked( + address(getSocketConfig(chainSlug).socket), + payloadParams.payloadId + ) ), transmitterPrivateKey ); @@ -748,11 +754,11 @@ contract WatcherSetup is FeesSetup { function _resolvePayload(PromiseReturnData memory promiseReturnData) internal { WatcherMultiCallParams memory params = WatcherMultiCallParams({ contractAddress: address(watcher), - data: abi.encode(promiseReturnData, feesAmount/2), + data: abi.encode(promiseReturnData, feesAmount / 2), nonce: watcherNonce, signature: _createWatcherSignature( address(watcher), - abi.encode(promiseReturnData, feesAmount/2) + abi.encode(promiseReturnData, feesAmount / 2) ) }); watcherNonce++; From 25958627a2b4909b6b3669a869f37dd55a07dcd5 Mon Sep 17 00:00:00 2001 From: Ameesha Agrawal Date: Fri, 14 Nov 2025 16:42:30 +0530 Subject: [PATCH 3/3] fix: remove extra functions --- contracts/evmx/fees/GasAccountManager.sol | 21 ------------------- .../evmx/interfaces/IGasAccountManager.sol | 13 ------------ script/helpers/TransferRemainingGas.s.sol | 2 +- 3 files changed, 1 insertion(+), 35 deletions(-) diff --git a/contracts/evmx/fees/GasAccountManager.sol b/contracts/evmx/fees/GasAccountManager.sol index 56d035f3..c135814f 100644 --- a/contracts/evmx/fees/GasAccountManager.sol +++ b/contracts/evmx/fees/GasAccountManager.sol @@ -207,27 +207,6 @@ contract GasAccountManager is 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); - } - - /// @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__().getEscrowedAmount(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_); diff --git a/contracts/evmx/interfaces/IGasAccountManager.sol b/contracts/evmx/interfaces/IGasAccountManager.sol index 118a82a8..b51fb0be 100644 --- a/contracts/evmx/interfaces/IGasAccountManager.sol +++ b/contracts/evmx/interfaces/IGasAccountManager.sol @@ -41,19 +41,6 @@ interface IGasAccountManager { // ============ 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; diff --git a/script/helpers/TransferRemainingGas.s.sol b/script/helpers/TransferRemainingGas.s.sol index 6c88cbd3..4aa9bcd4 100644 --- a/script/helpers/TransferRemainingGas.s.sol +++ b/script/helpers/TransferRemainingGas.s.sol @@ -25,7 +25,7 @@ contract TransferRemainingGas is Script { address appGateway = vm.envAddress("APP_GATEWAY"); address newAppGateway = vm.envAddress("NEW_APP_GATEWAY"); - uint256 totalGas = gasAccountManager.totalGas(appGateway); + uint256 totalGas = gasAccountToken.totalBalanceOf(appGateway); uint256 payloadEscrow = totalGas - gasAccountToken.balanceOf(appGateway); console.log("App Gateway:", appGateway); console.log("New App Gateway:", newAppGateway);