diff --git a/contracts/evmx/fees/GasAccountManager.sol b/contracts/evmx/fees/GasAccountManager.sol index 86e23cbb..c135814f 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 @@ -205,27 +207,6 @@ contract GasAccountManager is GasAccountManagerStorage, Ownable, AccessControl, 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_); @@ -236,7 +217,6 @@ contract GasAccountManager is GasAccountManagerStorage, Ownable, AccessControl, emit ForwarderSolanaSet(forwarderSolana_); } - function setGasStationSolanaProgramId(bytes32 gasStationSolanaProgramId_) external onlyOwner { gasStationSolanaProgramId = gasStationSolanaProgramId_; emit GasStationSolanaProgramIdSet(gasStationSolanaProgramId_); @@ -268,8 +248,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..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; @@ -64,7 +51,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 +79,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 c1c6133a..e8ee4333 100644 --- a/contracts/protocol/Socket.sol +++ b/contracts/protocol/Socket.sol @@ -56,7 +56,7 @@ contract Socket is SocketUtils { if (executeParams_.callType != WRITE) revert InvalidCallType(); // check if the plug is connected - uint32 switchboardId = _verifyPlugSwitchboard(executeParams_.target); + address switchboardAddress = _verifyPlugSwitchboard(executeParams_.target); // check if the message value is sufficient if (msg.value < executeParams_.value + transmissionParams_.socketFees) @@ -65,13 +65,18 @@ contract Socket is SocketUtils { bytes32 payloadId = executeParams_.payloadId; // verify the payload id - _verifyPayloadId(payloadId, switchboardId, chainSlug); + _verifyPayloadId(payloadId, switchboardAddress, chainSlug); // validate the execution status _validateExecutionStatus(payloadId); // verify the digest - _verify(payloadId, switchboardId, executeParams_, transmissionParams_.transmitterProof); + _verify( + payloadId, + switchboardAddress, + executeParams_, + transmissionParams_.transmitterProof + ); // execute the payload return _execute(payloadId, executeParams_, transmissionParams_); @@ -88,11 +93,10 @@ contract Socket is SocketUtils { */ function _verify( bytes32 payloadId_, - uint32, + address switchboardAddress, ExecuteParams calldata executeParams_, bytes calldata transmitterProof_ ) internal { - address switchboardAddress = switchboardAddresses[switchboardId_]; // NOTE: the first un-trusted call in the system address transmitter = ISwitchboard(switchboardAddress).getTransmitter( msg.sender, @@ -199,9 +203,7 @@ contract Socket is SocketUtils { uint256 value_, bytes calldata data_ ) internal whenNotPaused returns (bytes32 payloadId) { - uint32 switchboardId = _verifyPlugSwitchboard(plug_); - address switchboardAddress = switchboardAddresses[switchboardId]; - + address switchboardAddress = _verifyPlugSwitchboard(plug_); bytes memory plugOverrides = IPlug(plug_).overrides(); // Switchboard creates the payload ID and emits PayloadRequested event @@ -212,7 +214,7 @@ contract Socket is SocketUtils { ); } - /** + /**F * @notice Fallback function that forwards all calls to Socket's sendPayload * @dev The calldata is passed as-is to the switchboard * @return The payload ID diff --git a/contracts/protocol/SocketUtils.sol b/contracts/protocol/SocketUtils.sol index 7f1f949f..13653f53 100644 --- a/contracts/protocol/SocketUtils.sol +++ b/contracts/protocol/SocketUtils.sol @@ -120,25 +120,26 @@ abstract contract SocketUtils is SocketConfig { /** * @notice Verifies the plug switchboard and returns the switchboard id * @param plug_ The address of the plug - * @return switchboardId The id of the switchboard + * @return switchboardAddress The address of the switchboard */ function _verifyPlugSwitchboard( address plug_ - ) internal view returns (uint32 switchboardId) { - switchboardId = plugSwitchboardIds[plug_]; + ) internal view returns (address switchboardAddress) { + uint32 switchboardId = plugSwitchboardIds[plug_]; if (switchboardId == 0) revert PlugNotFound(); if (isValidSwitchboard[switchboardId] != SwitchboardStatus.REGISTERED) revert InvalidSwitchboard(); + switchboardAddress = switchboardAddresses[switchboardId]; } function _verifyPayloadId( bytes32 payloadId_, - uint32 switchboardId_, + address switchboardAddress_, uint32 chainSlug_ ) internal view { (uint32 verificationChainSlug, uint32 verificationSwitchboardId) = getVerificationInfo(payloadId_); if (verificationChainSlug != chainSlug_) revert InvalidVerificationChainSlug(); - if (verificationSwitchboardId != uint32(switchboardId_)) + if (switchboardAddresses[verificationSwitchboardId] != switchboardAddress_) revert InvalidVerificationSwitchboardId(); } @@ -148,8 +149,7 @@ abstract contract SocketUtils is SocketConfig { * @param feesData_ Encoded fees data (type + data) */ function increaseFeesForPayload(bytes32 payloadId_, bytes calldata feesData_) external payable { - uint32 switchboardId = _verifyPlugSwitchboard(msg.sender); - address switchboardAddress = switchboardAddresses[switchboardId]; + address switchboardAddress = _verifyPlugSwitchboard(msg.sender); ISwitchboard(switchboardAddress).increaseFeesForPayload{value: msg.value}( payloadId_, msg.sender, diff --git a/contracts/protocol/switchboard/SwitchboardBase.sol b/contracts/protocol/switchboard/SwitchboardBase.sol index 85154052..95699870 100644 --- a/contracts/protocol/switchboard/SwitchboardBase.sol +++ b/contracts/protocol/switchboard/SwitchboardBase.sol @@ -62,8 +62,7 @@ abstract contract SwitchboardBase is ISwitchboard, AccessControl { ) external view returns (address transmitter) { 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/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/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); diff --git a/test/SetupTest.t.sol b/test/SetupTest.t.sol index 41d2fdf9..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.encode(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++; diff --git a/test/protocol/Socket.t.sol b/test/protocol/Socket.t.sol index aecd984f..7d09622a 100644 --- a/test/protocol/Socket.t.sol +++ b/test/protocol/Socket.t.sol @@ -289,7 +289,7 @@ contract SocketTestBase is Test, Utils { socket.grantRole(SWITCHBOARD_DISABLER_ROLE, socketOwner); socket.grantRole(PAUSER_ROLE, socketOwner); socket.grantRole(UNPAUSER_ROLE, socketOwner); - socket.setSocketFeeManager(address(mockFeeManager)); + socket.setNetworkFeeCollector(address(mockFeeManager)); vm.stopPrank(); // Register switchboard - must be called by the switchboard itself @@ -583,7 +583,7 @@ contract SocketExecuteTestPart2 is SocketTestBase { function test_Execute_WorksWithoutFeeManager() public { hoax(socketOwner); - socket.setSocketFeeManager(address(0)); + socket.setNetworkFeeCollector(address(0)); hoax(transmitter); bytes32 payloadId = executeParams.payloadId; @@ -697,7 +697,9 @@ contract SocketSendPayloadTest is SocketTestBase { function test_Receive_Reverts() public { vm.expectRevert("Socket does not accept ETH"); - payable(address(socket)).call{value: 1 ether}(""); + (bool success,) = payable(address(socket)).call{value: 1 ether}(""); + + assertFalse(success, "Fallback should revert"); } function test_IncreaseFeesForPayload_WithValidParameters() public { @@ -876,21 +878,21 @@ contract SocketConfigTest is SocketTestBase { socket.enableSwitchboard(switchboardId); } - function test_SetSocketFeeManager_WithValidRole() public { + function test_SetNetworkFeeCollector_WithValidRole() public { MockFeeManager newFeeManager = new MockFeeManager(); hoax(socketOwner); - socket.setSocketFeeManager(address(newFeeManager)); + socket.setNetworkFeeCollector(address(newFeeManager)); - assertEq(address(socket.socketFeeManager()), address(newFeeManager), "Fee manager should be updated"); + assertEq(address(socket.networkFeeCollector()), address(newFeeManager), "Fee manager should be updated"); } - function test_SetSocketFeeManager_WithoutRole_Reverts() public { + function test_SetNetworkFeeCollector_WithoutRole_Reverts() public { MockFeeManager newFeeManager = new MockFeeManager(); vm.expectRevert(abi.encodeWithSelector(AccessControl.NoPermit.selector, GOVERNANCE_ROLE)); hoax(testUser); - socket.setSocketFeeManager(address(newFeeManager)); + socket.setNetworkFeeCollector(address(newFeeManager)); } function test_SetGasLimitBuffer_WithValidRole() public {