From 11a4d813c738f5a3fa9eb301ecf6156f3733c94d Mon Sep 17 00:00:00 2001 From: Akash Date: Fri, 11 Apr 2025 17:26:15 +0530 Subject: [PATCH 1/6] feat: socket fees support --- contracts/interfaces/ISocket.sol | 4 +- contracts/interfaces/ISocketFeeManager.sol | 34 +++++++++ contracts/protocol/socket/Socket.sol | 20 +++-- contracts/protocol/socket/SocketBatcher.sol | 12 ++- contracts/protocol/socket/SocketConfig.sol | 7 +- .../protocol/socket/SocketFeeManager.sol | 73 +++++++++++++++++++ contracts/protocol/utils/common/Structs.sol | 6 ++ test/mock/MockSocket.sol | 2 +- 8 files changed, 147 insertions(+), 11 deletions(-) create mode 100644 contracts/interfaces/ISocketFeeManager.sol create mode 100644 contracts/protocol/socket/SocketFeeManager.sol diff --git a/contracts/interfaces/ISocket.sol b/contracts/interfaces/ISocket.sol index dc924f87..51c5288a 100644 --- a/contracts/interfaces/ISocket.sol +++ b/contracts/interfaces/ISocket.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.21; -import {ExecuteParams} from "../protocol/utils/common/Structs.sol"; +import {ExecuteParams, TransmissionParams} from "../protocol/utils/common/Structs.sol"; /** * @title ISocket @@ -53,7 +53,7 @@ interface ISocket { */ function execute( ExecuteParams memory executeParams_, - bytes memory transmitterSignature_ + TransmissionParams memory transmissionParams_ ) external payable returns (bytes memory); /** diff --git a/contracts/interfaces/ISocketFeeManager.sol b/contracts/interfaces/ISocketFeeManager.sol new file mode 100644 index 00000000..1d7958c0 --- /dev/null +++ b/contracts/interfaces/ISocketFeeManager.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import {ExecuteParams, TransmissionParams} from "../protocol/utils/common/Structs.sol"; + +interface ISocketFeeManager { + /** + * @notice Pays and validates fees for execution + * @param executeParams_ Execute params + * @param transmissionParams_ Transmission params + */ + function payAndCheckFees( + ExecuteParams memory executeParams_, + TransmissionParams memory transmissionParams_ + ) external payable; + + /** + * @notice Gets minimum fees required for execution + * @return nativeFees Minimum native token fees required + */ + function getMinSocketFees() external view returns (uint256 nativeFees); + + /** + * @notice Sets socket fees + * @param socketFees_ New socket fees amount + */ + function setSocketFees(uint256 socketFees_) external; + + /** + * @notice Gets current socket fees + * @return Current socket fees amount + */ + function socketFees() external view returns (uint256); +} diff --git a/contracts/protocol/socket/Socket.sol b/contracts/protocol/socket/Socket.sol index 1506f056..2b5eb08a 100644 --- a/contracts/protocol/socket/Socket.sol +++ b/contracts/protocol/socket/Socket.sol @@ -48,20 +48,30 @@ contract Socket is SocketUtils { */ function execute( ExecuteParams memory executeParams_, - bytes memory transmitterSignature_ + TransmissionParams memory transmissionParams_ ) external payable returns (bytes memory) { if (executeParams_.deadline < block.timestamp) revert DeadlinePassed(); PlugConfig memory plugConfig = _plugConfigs[executeParams_.target]; if (plugConfig.appGatewayId == bytes32(0)) revert PlugDisconnected(); - if (msg.value < executeParams_.value) revert InsufficientMsgValue(); + if (msg.value < executeParams_.value + transmissionParams_.socketFees) + revert InsufficientMsgValue(); bytes32 payloadId = _createPayloadId(plugConfig.switchboard, executeParams_); _validateExecutionStatus(payloadId); - address transmitter = transmitterSignature_.length > 0 - ? _recoverSigner(keccak256(abi.encode(address(this), payloadId)), transmitterSignature_) + address transmitter = transmissionParams_.transmitterSignature.length > 0 + ? _recoverSigner( + keccak256(abi.encode(address(this), payloadId)), + transmissionParams_.transmitterSignature + ) : address(0); + if (address(socketFeeManager) != address(0)) { + socketFeeManager.payAndCheckFees{value: transmissionParams_.socketFees}( + executeParams_, + transmissionParams_ + ); + } bytes32 digest = _createDigest( transmitter, payloadId, @@ -96,7 +106,7 @@ contract Socket is SocketUtils { // NOTE: external un-trusted call (bool success, , bytes memory returnData) = executeParams_.target.tryCall( - msg.value, + executeParams_.value, executeParams_.gasLimit, maxCopyBytes, executeParams_.payload diff --git a/contracts/protocol/socket/SocketBatcher.sol b/contracts/protocol/socket/SocketBatcher.sol index 49eb1966..187f012a 100644 --- a/contracts/protocol/socket/SocketBatcher.sol +++ b/contracts/protocol/socket/SocketBatcher.sol @@ -5,7 +5,7 @@ import "solady/auth/Ownable.sol"; import "../../interfaces/ISocket.sol"; import "../../interfaces/ISwitchboard.sol"; import "../utils/RescueFundsLib.sol"; -import {ExecuteParams} from "../../protocol/utils/common/Structs.sol"; +import {ExecuteParams, TransmissionParams} from "../../protocol/utils/common/Structs.sol"; import "../../interfaces/ISocketBatcher.sol"; /** @@ -33,7 +33,15 @@ contract SocketBatcher is ISocketBatcher, Ownable { bytes calldata transmitterSignature_ ) external payable returns (bytes memory) { ISwitchboard(executeParams_.switchboard).attest(digest_, proof_); - return socket__.execute{value: msg.value}(executeParams_, transmitterSignature_); + return + socket__.execute{value: msg.value}( + executeParams_, + TransmissionParams({ + transmitterSignature: transmitterSignature_, + socketFees: 0, + extraData: "" + }) + ); } function rescueFunds(address token_, address to_, uint256 amount_) external onlyOwner { diff --git a/contracts/protocol/socket/SocketConfig.sol b/contracts/protocol/socket/SocketConfig.sol index 4ccc6a82..fc70ed79 100644 --- a/contracts/protocol/socket/SocketConfig.sol +++ b/contracts/protocol/socket/SocketConfig.sol @@ -6,7 +6,7 @@ import "../../interfaces/ISwitchboard.sol"; import "../utils/AccessControl.sol"; import {GOVERNANCE_ROLE, RESCUE_ROLE} from "../utils/common/AccessRoles.sol"; import {PlugConfig, SwitchboardStatus, ExecutionStatus} from "../utils/common/Structs.sol"; - +import "../../interfaces/ISocketFeeManager.sol"; /** * @title SocketConfig * @notice An abstract contract for configuring socket connections for plugs between different chains, @@ -14,6 +14,7 @@ import {PlugConfig, SwitchboardStatus, ExecutionStatus} from "../utils/common/St * @dev This contract is meant to be inherited by other contracts that require socket configuration functionality */ abstract contract SocketConfig is ISocket, AccessControl { + ISocketFeeManager public socketFeeManager; // Error triggered when a switchboard already exists mapping(address => SwitchboardStatus) public isValidSwitchboard; @@ -44,6 +45,10 @@ abstract contract SocketConfig is ISocket, AccessControl { emit SwitchboardDisabled(msg.sender); } + function setSocketFeeManager(address socketFeeManager_) external onlyRole(GOVERNANCE_ROLE) { + socketFeeManager = ISocketFeeManager(socketFeeManager_); + } + /** * @notice connects Plug to Socket and sets the config for given `siblingChainSlug_` */ diff --git a/contracts/protocol/socket/SocketFeeManager.sol b/contracts/protocol/socket/SocketFeeManager.sol new file mode 100644 index 00000000..ab94ccb2 --- /dev/null +++ b/contracts/protocol/socket/SocketFeeManager.sol @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: GPL-3.0-only +pragma solidity ^0.8.21; + +import "../utils/AccessControl.sol"; +import {GOVERNANCE_ROLE, RESCUE_ROLE} from "../utils/common/AccessRoles.sol"; +import {ExecuteParams, TransmissionParams} from "../../protocol/utils/common/Structs.sol"; +import "../../interfaces/ISocketFeeManager.sol"; +import "../utils/RescueFundsLib.sol"; + +/** + * @title SocketFeeManager + * @notice The SocketFeeManager contract is responsible for managing socket fees + */ +contract SocketFeeManager is ISocketFeeManager, AccessControl { + // Current socket fees in native tokens + uint256 public socketFees; + + error InsufficientFees(); + error FeeTooLow(); + + /** + * @notice Initializes the SocketFeeManager contract + * @param owner_ The owner of the contract with GOVERNANCE_ROLE + * @param socketFees_ Initial socket fees amount + */ + constructor(address owner_, uint256 socketFees_) { + socketFees = socketFees_; + _grantRole(GOVERNANCE_ROLE, owner_); + _grantRole(RESCUE_ROLE, owner_); + } + + /** + * @notice Pays and validates fees for execution + * @param executeParams_ Execute params + * @param transmissionParams_ Transmission params + */ + function payAndCheckFees( + ExecuteParams memory executeParams_, + TransmissionParams memory transmissionParams_ + ) external payable { + if (msg.value < socketFees) revert InsufficientFees(); + } + + /** + * @notice Gets minimum fees required for execution + * @return nativeFees Minimum native token fees required + */ + function getMinSocketFees() external view returns (uint256 nativeFees) { + return socketFees; + } + + /** + * @notice Sets socket fees + * @param socketFees_ New socket fees amount + */ + function setSocketFees(uint256 socketFees_) external onlyRole(GOVERNANCE_ROLE) { + socketFees = socketFees_; + } + + /** + * @notice Allows owner to rescue stuck funds + * @param token_ Token address (address(0) for native tokens) + * @param to_ Address to send funds to + * @param amount_ Amount of tokens to rescue + */ + function rescueFunds( + address token_, + address to_, + uint256 amount_ + ) external onlyRole(RESCUE_ROLE) { + RescueFundsLib._rescueFunds(token_, to_, amount_); + } +} diff --git a/contracts/protocol/utils/common/Structs.sol b/contracts/protocol/utils/common/Structs.sol index a3ffce79..9c45e437 100644 --- a/contracts/protocol/utils/common/Structs.sol +++ b/contracts/protocol/utils/common/Structs.sol @@ -240,6 +240,12 @@ struct ExecuteParams { address switchboard; } +struct TransmissionParams { + bytes transmitterSignature; + uint256 socketFees; + bytes extraData; +} + struct PayloadIdParams { uint40 requestCount; uint40 batchCount; diff --git a/test/mock/MockSocket.sol b/test/mock/MockSocket.sol index 26650c35..bfb39f15 100644 --- a/test/mock/MockSocket.sol +++ b/test/mock/MockSocket.sol @@ -105,7 +105,7 @@ contract MockSocket is ISocket { */ function execute( ExecuteParams memory executeParams_, - bytes memory transmitterSignature_ + TransmissionParams memory transmissionParams_ ) external payable override returns (bytes memory) { // execute payload // return From 7a3e68becc5888edcb6a0bd0e44341205a463e23 Mon Sep 17 00:00:00 2001 From: Akash Date: Fri, 11 Apr 2025 17:30:37 +0530 Subject: [PATCH 2/6] fix: added events for socketFeesManager --- contracts/protocol/socket/SocketConfig.sol | 5 +++++ contracts/protocol/socket/SocketFeeManager.sol | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/contracts/protocol/socket/SocketConfig.sol b/contracts/protocol/socket/SocketConfig.sol index fc70ed79..d51eecab 100644 --- a/contracts/protocol/socket/SocketConfig.sol +++ b/contracts/protocol/socket/SocketConfig.sol @@ -14,7 +14,9 @@ import "../../interfaces/ISocketFeeManager.sol"; * @dev This contract is meant to be inherited by other contracts that require socket configuration functionality */ abstract contract SocketConfig is ISocket, AccessControl { + // socket fee manager ISocketFeeManager public socketFeeManager; + // Error triggered when a switchboard already exists mapping(address => SwitchboardStatus) public isValidSwitchboard; @@ -31,6 +33,7 @@ abstract contract SocketConfig is ISocket, AccessControl { // Event triggered when a new switchboard is added event SwitchboardAdded(address switchboard); event SwitchboardDisabled(address switchboard); + event SocketFeeManagerUpdated(address oldSocketFeeManager, address newSocketFeeManager); function registerSwitchboard() external { if (isValidSwitchboard[msg.sender] != SwitchboardStatus.NOT_REGISTERED) @@ -45,7 +48,9 @@ abstract contract SocketConfig is ISocket, AccessControl { emit SwitchboardDisabled(msg.sender); } + function setSocketFeeManager(address socketFeeManager_) external onlyRole(GOVERNANCE_ROLE) { + emit SocketFeeManagerUpdated(address(socketFeeManager), socketFeeManager_); socketFeeManager = ISocketFeeManager(socketFeeManager_); } diff --git a/contracts/protocol/socket/SocketFeeManager.sol b/contracts/protocol/socket/SocketFeeManager.sol index ab94ccb2..29672e3c 100644 --- a/contracts/protocol/socket/SocketFeeManager.sol +++ b/contracts/protocol/socket/SocketFeeManager.sol @@ -18,12 +18,15 @@ contract SocketFeeManager is ISocketFeeManager, AccessControl { error InsufficientFees(); error FeeTooLow(); + event SocketFeesUpdated(uint256 oldFees, uint256 newFees); + /** * @notice Initializes the SocketFeeManager contract * @param owner_ The owner of the contract with GOVERNANCE_ROLE * @param socketFees_ Initial socket fees amount */ constructor(address owner_, uint256 socketFees_) { + emit SocketFeesUpdated(0, socketFees_); socketFees = socketFees_; _grantRole(GOVERNANCE_ROLE, owner_); _grantRole(RESCUE_ROLE, owner_); @@ -54,6 +57,7 @@ contract SocketFeeManager is ISocketFeeManager, AccessControl { * @param socketFees_ New socket fees amount */ function setSocketFees(uint256 socketFees_) external onlyRole(GOVERNANCE_ROLE) { + emit SocketFeesUpdated(socketFees, socketFees_); socketFees = socketFees_; } From dd6dd8cbb83969f9407926818d264a9678135f26 Mon Sep 17 00:00:00 2001 From: Akash Date: Tue, 15 Apr 2025 17:47:46 +0530 Subject: [PATCH 3/6] feat: Socket Fees Manager tests --- contracts/protocol/socket/Socket.sol | 23 +-- .../protocol/socket/SocketFeeManager.sol | 6 +- test/Inbox.t.sol | 8 +- test/SetupTest.t.sol | 8 +- test/SocketFeeManager.t.sol | 152 ++++++++++++++++++ test/mock/MockFastSwitchboard.sol | 36 +++++ 6 files changed, 210 insertions(+), 23 deletions(-) create mode 100644 test/SocketFeeManager.t.sol create mode 100644 test/mock/MockFastSwitchboard.sol diff --git a/contracts/protocol/socket/Socket.sol b/contracts/protocol/socket/Socket.sol index ae740966..8369e051 100644 --- a/contracts/protocol/socket/Socket.sol +++ b/contracts/protocol/socket/Socket.sol @@ -66,12 +66,6 @@ contract Socket is SocketUtils { ) : address(0); - if (address(socketFeeManager) != address(0)) { - socketFeeManager.payAndCheckFees{value: transmissionParams_.socketFees}( - executeParams_, - transmissionParams_ - ); - } bytes32 digest = _createDigest( transmitter, payloadId, @@ -79,7 +73,7 @@ contract Socket is SocketUtils { executeParams_ ); _verify(digest, payloadId, plugConfig.switchboard); - return _execute(payloadId, executeParams_); + return _execute(payloadId, executeParams_, transmissionParams_); } //////////////////////////////////////////////////////// @@ -100,7 +94,8 @@ contract Socket is SocketUtils { */ function _execute( bytes32 payloadId_, - ExecuteParams memory executeParams_ + ExecuteParams memory executeParams_, + TransmissionParams memory transmissionParams_ ) internal returns (bytes memory) { if (gasleft() < executeParams_.gasLimit) revert LowGasLimit(); @@ -112,11 +107,17 @@ contract Socket is SocketUtils { executeParams_.payload ); - if (!success) { + if (success) { + emit ExecutionSuccess(payloadId_, returnData); + if (address(socketFeeManager) != address(0)) { + socketFeeManager.payAndCheckFees{value: transmissionParams_.socketFees}( + executeParams_, + transmissionParams_ + ); + } + } else { payloadExecuted[payloadId_] = ExecutionStatus.Reverted; emit ExecutionFailed(payloadId_, returnData); - } else { - emit ExecutionSuccess(payloadId_, returnData); } return returnData; diff --git a/contracts/protocol/socket/SocketFeeManager.sol b/contracts/protocol/socket/SocketFeeManager.sol index 29672e3c..adc22c84 100644 --- a/contracts/protocol/socket/SocketFeeManager.sol +++ b/contracts/protocol/socket/SocketFeeManager.sol @@ -34,12 +34,10 @@ contract SocketFeeManager is ISocketFeeManager, AccessControl { /** * @notice Pays and validates fees for execution - * @param executeParams_ Execute params - * @param transmissionParams_ Transmission params */ function payAndCheckFees( - ExecuteParams memory executeParams_, - TransmissionParams memory transmissionParams_ + ExecuteParams memory, + TransmissionParams memory ) external payable { if (msg.value < socketFees) revert InsufficientFees(); } diff --git a/test/Inbox.t.sol b/test/Inbox.t.sol index d1763190..186b288a 100644 --- a/test/Inbox.t.sol +++ b/test/Inbox.t.sol @@ -62,7 +62,7 @@ contract TriggerTest is DeliveryHelperTest { // Simulate a message from another chain through the watcher uint256 incrementValue = 5; - bytes32 triggerId = _encodeTriggerId(address(gateway), arbChainSlug); + bytes32 triggerId = _encodeTriggerId(address(arbConfig.socket), arbChainSlug); bytes memory payload = abi.encodeWithSelector( CounterAppGateway.increase.selector, incrementValue @@ -91,12 +91,10 @@ contract TriggerTest is DeliveryHelperTest { assertEq(gateway.counterVal(), incrementValue, "Gateway counter should be incremented"); } - function _encodeTriggerId(address appGateway_, uint256 chainSlug_) internal returns (bytes32) { + function _encodeTriggerId(address socket_, uint256 chainSlug_) internal returns (bytes32) { return bytes32( - (uint256(chainSlug_) << 224) | - (uint256(uint160(appGateway_)) << 64) | - triggerCounter++ + (uint256(chainSlug_) << 224) | (uint256(uint160(socket_)) << 64) | triggerCounter++ ); } } diff --git a/test/SetupTest.t.sol b/test/SetupTest.t.sol index b752a6c7..f2a22f14 100644 --- a/test/SetupTest.t.sol +++ b/test/SetupTest.t.sol @@ -17,7 +17,7 @@ import "../contracts/protocol/socket/SocketBatcher.sol"; import "../contracts/protocol/AddressResolver.sol"; import {ContractFactoryPlug} from "../contracts/protocol/payload-delivery/ContractFactoryPlug.sol"; import {FeesPlug} from "../contracts/protocol/payload-delivery/FeesPlug.sol"; - +import {SocketFeeManager} from "../contracts/protocol/socket/SocketFeeManager.sol"; import {ETH_ADDRESS} from "../contracts/protocol/utils/common/Constants.sol"; import {ResolvedPromises} from "../contracts/protocol/utils/common/Structs.sol"; @@ -40,7 +40,7 @@ contract SetupTest is Test { uint32 evmxSlug = 1; uint256 expiryTime = 10000000; uint256 maxReAuctionCount = 10; - + uint256 socketFees = 0.01 ether; uint256 public signatureNonce; uint256 public payloadIdCounter; uint256 public timeoutIdCounter; @@ -53,6 +53,7 @@ contract SetupTest is Test { struct SocketContracts { uint32 chainSlug; Socket socket; + SocketFeeManager socketFeeManager; FastSwitchboard switchboard; SocketBatcher socketBatcher; ContractFactoryPlug contractFactoryPlug; @@ -103,7 +104,7 @@ contract SetupTest is Test { address(contractFactoryPlug), address(feesPlug) ); - + SocketFeeManager socketFeeManager = new SocketFeeManager(owner, socketFees); hoax(watcherEOA); watcherPrecompileConfig.setSwitchboard(chainSlug_, FAST, address(switchboard)); @@ -111,6 +112,7 @@ contract SetupTest is Test { SocketContracts({ chainSlug: chainSlug_, socket: socket, + socketFeeManager: socketFeeManager, switchboard: switchboard, socketBatcher: socketBatcher, contractFactoryPlug: contractFactoryPlug, diff --git a/test/SocketFeeManager.t.sol b/test/SocketFeeManager.t.sol new file mode 100644 index 00000000..dd0cd7c4 --- /dev/null +++ b/test/SocketFeeManager.t.sol @@ -0,0 +1,152 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import {CounterAppGateway} from "./apps/app-gateways/counter/CounterAppGateway.sol"; +import {Counter} from "./apps/app-gateways/counter/Counter.sol"; +import "./SetupTest.t.sol"; +import {SocketFeeManager} from "../contracts/protocol/socket/SocketFeeManager.sol"; +import {MockFastSwitchboard} from "./mock/MockFastSwitchboard.sol"; +import {ExecuteParams, TransmissionParams, CallType, WriteFinality} from "../contracts/protocol/utils/common/Structs.sol"; +import {GOVERNANCE_ROLE, RESCUE_ROLE} from "../contracts/protocol/utils/common/AccessRoles.sol"; +import {Test} from "forge-std/Test.sol"; + +contract SocketFeeManagerTest is SetupTest { + Counter public counter; + address public gateway = address(5); + MockFastSwitchboard public mockSwitchboard; + Socket public socket; + SocketFeeManager public socketFeeManager; + + function setUp() public { + socket = new Socket(arbChainSlug, owner, "test"); + vm.prank(owner); + socket.grantRole(GOVERNANCE_ROLE, address(owner)); + socketFeeManager = new SocketFeeManager(owner, socketFees); + mockSwitchboard = new MockFastSwitchboard(arbChainSlug, address(socket), owner); + mockSwitchboard.registerSwitchboard(); + + counter = new Counter(); + counter.initSocket(_encodeAppGatewayId(gateway), address(socket), address(mockSwitchboard)); + } + + function testSuccessfulExecutionWithFeeManagerNotSet() public { + // Create execute params + ( + ExecuteParams memory executeParams, + TransmissionParams memory transmissionParams + ) = _getExecutionParams(abi.encodeWithSelector(Counter.increase.selector)); + + // Fund the contract with enough ETH for fees + vm.deal(address(this), socketFees); + + // Execute with fees + socket.execute{value: socketFees}(executeParams, transmissionParams); + + // Check counter was incremented + assertEq(counter.counter(), 1, "Counter should be incremented"); + assertEq( + address(socketFeeManager).balance, + 0, + "Socket fee manager should have 0 balance when fees manager not set" + ); + } + + function testSuccessfulExecutionWithFeeManagerSet() public { + // Set the fee manager in socket + vm.prank(owner); + socket.setSocketFeeManager(address(socketFeeManager)); + + // Create execute params + ( + ExecuteParams memory executeParams, + TransmissionParams memory transmissionParams + ) = _getExecutionParams(abi.encodeWithSelector(Counter.increase.selector)); + + // Fund the contract with enough ETH for fees + vm.deal(address(this), socketFees); + + // Execute with fees + socket.execute{value: socketFees}(executeParams, transmissionParams); + + // Check counter was incremented + assertEq(counter.counter(), 1, "Counter should be incremented"); + assertEq( + address(socketFeeManager).balance, + socketFees, + "Socket fee manager should have received the fees" + ); + } + + function testSuccessfulExecutionWithInsufficientFees() public { + // Set the fee manager in socket + vm.prank(owner); + socket.setSocketFeeManager(address(socketFeeManager)); + + // Create execute params + ( + ExecuteParams memory executeParams, + TransmissionParams memory transmissionParams + ) = _getExecutionParams(abi.encodeWithSelector(Counter.increase.selector)); + + // Fund with insufficient ETH + vm.deal(address(this), socketFees - 1); + + // Should revert with InsufficientMsgValue + vm.expectRevert(Socket.InsufficientMsgValue.selector); + socket.execute{value: socketFees - 1}(executeParams, transmissionParams); + } + + function testExecutionFailed() public { + // Set the fee manager in socket + vm.prank(owner); + socket.setSocketFeeManager(address(socketFeeManager)); + + // Create execute params with invalid payload to force failure + ( + ExecuteParams memory executeParams, + TransmissionParams memory transmissionParams + ) = _getExecutionParams(abi.encodeWithSelector(bytes4(keccak256("invalid()")))); + + // Fund the contract with enough ETH for fees + vm.deal(address(this), socketFees); + + // Execute with fees + socket.execute{value: socketFees}(executeParams, transmissionParams); + + // Check counter was not incremented + assertEq(counter.counter(), 0, "Counter should not be incremented"); + assertEq( + address(socketFeeManager).balance, + 0, + "Socket fee manager should not receive fees on failed execution" + ); + } + + function _getExecutionParams( + bytes memory payload + ) internal view returns (ExecuteParams memory, TransmissionParams memory) { + ExecuteParams memory executeParams = ExecuteParams({ + deadline: block.timestamp + 1 days, + callType: CallType.WRITE, + writeFinality: WriteFinality.HIGH, + gasLimit: 100000, + value: 0, + readAt: 0, + payload: payload, + target: address(counter), + requestCount: 0, + batchCount: 0, + payloadCount: 0, + prevDigestsHash: bytes32(0), + switchboard: address(mockSwitchboard) + }); + + TransmissionParams memory transmissionParams = TransmissionParams({ + transmitterSignature: bytes(""), + socketFees: socketFees, + extraData: bytes("") + }); + + return (executeParams, transmissionParams); + } +} diff --git a/test/mock/MockFastSwitchboard.sol b/test/mock/MockFastSwitchboard.sol new file mode 100644 index 00000000..9bb75a14 --- /dev/null +++ b/test/mock/MockFastSwitchboard.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import "../../contracts/interfaces/ISwitchboard.sol"; +import "../../contracts/interfaces/ISocket.sol"; +contract MockFastSwitchboard is ISwitchboard { + address public owner; + ISocket public immutable socket__; + + // chain slug of deployed chain + uint32 public immutable chainSlug; + + /** + * @dev Constructor of SwitchboardBase + * @param chainSlug_ Chain slug of deployment chain + * @param socket_ socket_ contract + */ + constructor(uint32 chainSlug_, address socket_, address owner_) { + chainSlug = chainSlug_; + socket__ = ISocket(socket_); + owner = owner_; + } + + function attest(bytes32 , bytes calldata ) external { + // TODO: implement + } + + function allowPacket(bytes32 , bytes32) external pure returns (bool) { + // digest has enough attestations + return true; + } + + function registerSwitchboard() external { + socket__.registerSwitchboard(); + } +} From 0ebb7339ffa4d36c80a6907c64740b6dbb706e01 Mon Sep 17 00:00:00 2001 From: Akash Date: Mon, 21 Apr 2025 16:22:13 +0530 Subject: [PATCH 4/6] fix: added refund option on execute failure --- contracts/interfaces/ISocketBatcher.sol | 3 ++- contracts/protocol/socket/Socket.sol | 3 +++ contracts/protocol/socket/SocketBatcher.sol | 6 ++++-- contracts/protocol/socket/SocketFeeManager.sol | 5 +---- contracts/protocol/utils/common/Structs.sol | 3 ++- test/SetupTest.t.sol | 16 +++++++++++++--- test/SocketFeeManager.t.sol | 3 ++- 7 files changed, 27 insertions(+), 12 deletions(-) diff --git a/contracts/interfaces/ISocketBatcher.sol b/contracts/interfaces/ISocketBatcher.sol index ab753866..ee1e7295 100644 --- a/contracts/interfaces/ISocketBatcher.sol +++ b/contracts/interfaces/ISocketBatcher.sol @@ -8,6 +8,7 @@ interface ISocketBatcher { ExecuteParams calldata executeParams_, bytes32 digest_, bytes calldata proof_, - bytes calldata transmitterSignature_ + bytes calldata transmitterSignature_, + address refundAddress_ ) external payable returns (bytes memory); } diff --git a/contracts/protocol/socket/Socket.sol b/contracts/protocol/socket/Socket.sol index 8369e051..22106450 100644 --- a/contracts/protocol/socket/Socket.sol +++ b/contracts/protocol/socket/Socket.sol @@ -117,6 +117,9 @@ contract Socket is SocketUtils { } } else { payloadExecuted[payloadId_] = ExecutionStatus.Reverted; + if (transmissionParams_.refundAddress != address(0)) { + transmissionParams_.refundAddress.call{value: msg.value}(""); + } emit ExecutionFailed(payloadId_, returnData); } diff --git a/contracts/protocol/socket/SocketBatcher.sol b/contracts/protocol/socket/SocketBatcher.sol index 187f012a..d3e8df53 100644 --- a/contracts/protocol/socket/SocketBatcher.sol +++ b/contracts/protocol/socket/SocketBatcher.sol @@ -30,7 +30,8 @@ contract SocketBatcher is ISocketBatcher, Ownable { ExecuteParams calldata executeParams_, bytes32 digest_, bytes calldata proof_, - bytes calldata transmitterSignature_ + bytes calldata transmitterSignature_, + address refundAddress_ ) external payable returns (bytes memory) { ISwitchboard(executeParams_.switchboard).attest(digest_, proof_); return @@ -39,7 +40,8 @@ contract SocketBatcher is ISocketBatcher, Ownable { TransmissionParams({ transmitterSignature: transmitterSignature_, socketFees: 0, - extraData: "" + extraData: "", + refundAddress: refundAddress_ }) ); } diff --git a/contracts/protocol/socket/SocketFeeManager.sol b/contracts/protocol/socket/SocketFeeManager.sol index adc22c84..1bfb66f4 100644 --- a/contracts/protocol/socket/SocketFeeManager.sol +++ b/contracts/protocol/socket/SocketFeeManager.sol @@ -35,10 +35,7 @@ contract SocketFeeManager is ISocketFeeManager, AccessControl { /** * @notice Pays and validates fees for execution */ - function payAndCheckFees( - ExecuteParams memory, - TransmissionParams memory - ) external payable { + function payAndCheckFees(ExecuteParams memory, TransmissionParams memory) external payable { if (msg.value < socketFees) revert InsufficientFees(); } diff --git a/contracts/protocol/utils/common/Structs.sol b/contracts/protocol/utils/common/Structs.sol index 9c45e437..9368691d 100644 --- a/contracts/protocol/utils/common/Structs.sol +++ b/contracts/protocol/utils/common/Structs.sol @@ -241,9 +241,10 @@ struct ExecuteParams { } struct TransmissionParams { - bytes transmitterSignature; uint256 socketFees; + address refundAddress; bytes extraData; + bytes transmitterSignature; } struct PayloadIdParams { diff --git a/test/SetupTest.t.sol b/test/SetupTest.t.sol index f2a22f14..c27db57b 100644 --- a/test/SetupTest.t.sol +++ b/test/SetupTest.t.sol @@ -268,10 +268,18 @@ contract SetupTest is Test { ExecuteParams memory params, SocketBatcher socketBatcher, , - bytes memory transmitterSig + bytes memory transmitterSig, + address refundAddress ) = _getExecuteParams(payloadParams); - return socketBatcher.attestAndExecute(params, digest, watcherProof, transmitterSig); + return + socketBatcher.attestAndExecute( + params, + digest, + watcherProof, + transmitterSig, + refundAddress + ); } function resolvePromises(bytes32[] memory payloadIds, bytes[] memory returnData) internal { @@ -338,7 +346,8 @@ contract SetupTest is Test { ExecuteParams memory params, SocketBatcher socketBatcher, uint256 value, - bytes memory transmitterSig + bytes memory transmitterSig, + address refundAddress ) { SocketContracts memory socketConfig = getSocketConfig(payloadParams.dump.getChainSlug()); @@ -365,6 +374,7 @@ contract SetupTest is Test { value = payloadParams.value; socketBatcher = socketConfig.socketBatcher; + refundAddress = transmitterEOA; } function _resolvePromise(bytes32 payloadId, bytes memory returnData) internal { diff --git a/test/SocketFeeManager.t.sol b/test/SocketFeeManager.t.sol index dd0cd7c4..8742fff5 100644 --- a/test/SocketFeeManager.t.sol +++ b/test/SocketFeeManager.t.sol @@ -144,7 +144,8 @@ contract SocketFeeManagerTest is SetupTest { TransmissionParams memory transmissionParams = TransmissionParams({ transmitterSignature: bytes(""), socketFees: socketFees, - extraData: bytes("") + extraData: bytes(""), + refundAddress: transmitterEOA }); return (executeParams, transmissionParams); From 724374305af5e94413c120fbe05b407f5d723564 Mon Sep 17 00:00:00 2001 From: Akash Date: Mon, 21 Apr 2025 16:27:09 +0530 Subject: [PATCH 5/6] fix : use safetransfer for eth --- contracts/protocol/socket/Socket.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/protocol/socket/Socket.sol b/contracts/protocol/socket/Socket.sol index 22106450..f6aee5d2 100644 --- a/contracts/protocol/socket/Socket.sol +++ b/contracts/protocol/socket/Socket.sol @@ -118,7 +118,7 @@ contract Socket is SocketUtils { } else { payloadExecuted[payloadId_] = ExecutionStatus.Reverted; if (transmissionParams_.refundAddress != address(0)) { - transmissionParams_.refundAddress.call{value: msg.value}(""); + SafeTransferLib.forceSafeTransferETH(transmissionParams_.refundAddress, msg.value); } emit ExecutionFailed(payloadId_, returnData); } From 2788689d5605125876f44e90d14cc8e7e98ad5dd Mon Sep 17 00:00:00 2001 From: Akash Date: Mon, 21 Apr 2025 20:57:30 +0530 Subject: [PATCH 6/6] fix: default refundAddress --- contracts/protocol/socket/Socket.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/protocol/socket/Socket.sol b/contracts/protocol/socket/Socket.sol index f6aee5d2..8cd42ed0 100644 --- a/contracts/protocol/socket/Socket.sol +++ b/contracts/protocol/socket/Socket.sol @@ -117,9 +117,9 @@ contract Socket is SocketUtils { } } else { payloadExecuted[payloadId_] = ExecutionStatus.Reverted; - if (transmissionParams_.refundAddress != address(0)) { - SafeTransferLib.forceSafeTransferETH(transmissionParams_.refundAddress, msg.value); - } + address receiver = transmissionParams_.refundAddress == address(0) ? msg.sender : transmissionParams_.refundAddress; + SafeTransferLib.forceSafeTransferETH(receiver, msg.value); + emit ExecutionFailed(payloadId_, returnData); }