diff --git a/contracts/evmx/base/AppGatewayBase.sol b/contracts/evmx/base/AppGatewayBase.sol index 58040306..5726d8af 100644 --- a/contracts/evmx/base/AppGatewayBase.sol +++ b/contracts/evmx/base/AppGatewayBase.sol @@ -227,9 +227,10 @@ abstract contract AppGatewayBase is AddressResolverUtil, IAppGateway { watcher__().setIsValidPlug(isValid, chainSlug_, onchainAddress); } - function _approveFeesWithSignature(bytes memory feesApprovalData_) internal { + function _permit(bytes memory feesApprovalData_) internal { if (feesApprovalData_.length == 0) return; - (consumeFrom, , ) = feesManager__().approveWithSignature(feesApprovalData_); + (address spender, uint256 value, uint256 deadline, uint256 nonce, bytes memory signature) = abi.decode(feesApprovalData_, (address, uint256, uint256, uint256, bytes)); + IERC20(address(feesManager__())).permit(spender, value, deadline, nonce, signature); } /// @notice Withdraws fee tokens diff --git a/contracts/evmx/fees/Credit.sol b/contracts/evmx/fees/Credit.sol index 44272045..e7dcc0e9 100644 --- a/contracts/evmx/fees/Credit.sol +++ b/contracts/evmx/fees/Credit.sol @@ -26,60 +26,43 @@ abstract contract FeesManagerStorage is IFeesManager { // slot 50 /// @notice evmx slug uint32 public evmxSlug; - IFeesPool public feesPool; // slot 51 - /// @notice switchboard type - bytes32 public deprecatedSbType; - - // slot 52 /// @notice Mapping to track blocked credits for each user /// @dev address => userBlockedCredits mapping(address => uint256) public userBlockedCredits; - // slot 53 + // slot 52 /// @notice Mapping to track request credits details for each request count /// @dev requestCount => RequestFee mapping(uint40 => uint256) public requestBlockedCredits; - // slot 54 - mapping(address => mapping(address => bool)) public deprecated2; - - // slot 55 + // slot 53 // token pool balances // chainSlug => token address => amount mapping(uint32 => mapping(address => uint256)) public tokenOnChainBalances; - // slot 56 + // 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 57 + // slot 55 /// @notice Mapping to track fees plug for each chain slug /// @dev chainSlug => fees plug address mapping(uint32 => bytes32) public feesPlugs; - // slot 58 - /// @notice Mapping to track token for each chain slug - /// @dev chainSlug => token address - bytes32 public deprecated4; - - // slot 59 - /// @notice Mapping to track whitelisted receivers - /// @dev receiver address => bool - mapping(address => bool) public deprecated3; - + // slot 56 /// @notice Mapping to track max fees per chain slug /// @dev chainSlug => max fees mapping(uint32 => uint256) public maxFeesPerChainSlug; - // slots [60-107] reserved for gap - uint256[47] _gap_after; + // slots [57-106] reserved for gap + uint256[50] _gap_after; - // slots [108-157] 50 slots reserved for address resolver util + // slots [107-156] 50 slots reserved for address resolver util // 9 slots for app gateway base } @@ -245,42 +228,6 @@ abstract contract Credit is FeesManagerStorage, Initializable, Ownable, AppGatew return super.transferFrom(from_, to_, amount_); } - function approve(address spender, uint256 amount) public override returns (bool) { - return super.approve(spender, amount); - } - - /// @notice Approves multiple app gateways for the caller - /// @param params_ Array of app gateway addresses to approve - function batchApprove(AppGatewayApprovals[] calldata params_) external override { - for (uint256 i = 0; i < params_.length; i++) { - _approve(msg.sender, params_[i].appGateway, params_[i].approval); - } - } - - /// @notice Approves an app gateway for the caller - /// @dev Approval data is encoded to make app gateways compatible with future changes - /// @param feeApprovalData_ The fee approval data - /// @return consumeFrom The consume from address - /// @return spender The app gateway address - /// @return approval The approval status - function approveWithSignature( - bytes memory feeApprovalData_ - ) external returns (address consumeFrom, address spender, uint256 approval) { - uint256 nonce; - bytes memory signature_; - (spender, approval, nonce, signature_) = abi.decode( - feeApprovalData_, - (address, uint256, uint256, bytes) - ); - bytes32 digest = keccak256(abi.encode(address(this), evmxSlug, spender, nonce, approval)); - consumeFrom = _recoverSigner(digest, signature_); - - if (isNonceUsed[consumeFrom][nonce]) revert NonceUsed(); - isNonceUsed[consumeFrom][nonce] = true; - _approve(consumeFrom, spender, approval); - return (consumeFrom, spender, approval); - } - /// @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 @@ -369,16 +316,4 @@ abstract contract Credit is FeesManagerStorage, Initializable, Ownable, AppGatew function decimals() public pure override returns (uint8) { return 18; } - - function permit( - address owner, - address spender, - uint256 value, - uint256 deadline, - uint8 v, - bytes32 r, - bytes32 s - ) public override { - // todo: implement permit - } } diff --git a/contracts/evmx/helpers/AsyncPromise.sol b/contracts/evmx/helpers/AsyncPromise.sol index 012b9a4e..3f140c48 100644 --- a/contracts/evmx/helpers/AsyncPromise.sol +++ b/contracts/evmx/helpers/AsyncPromise.sol @@ -6,8 +6,8 @@ import {LibCall} from "solady/utils/LibCall.sol"; import {AddressResolverUtil} from "./AddressResolverUtil.sol"; import {IAppGateway} from "../interfaces/IAppGateway.sol"; import "../interfaces/IPromise.sol"; -import {NotInvoker, RequestCountMismatch} from "../../utils/common/Errors.sol"; import "../../utils/RescueFundsLib.sol"; +import {NotInvoker, RequestCountMismatch} from "../../utils/common/Errors.sol"; abstract contract AsyncPromiseStorage is IPromise { // slots [0-49] reserved for gap diff --git a/contracts/evmx/helpers/Forwarder.sol b/contracts/evmx/helpers/Forwarder.sol index 9c53ba22..eff4d8dc 100644 --- a/contracts/evmx/helpers/Forwarder.sol +++ b/contracts/evmx/helpers/Forwarder.sol @@ -3,13 +3,11 @@ pragma solidity ^0.8.21; import "solady/utils/Initializable.sol"; import "./AddressResolverUtil.sol"; -import "../interfaces/IAddressResolver.sol"; import "../interfaces/IAppGateway.sol"; import "../interfaces/IForwarder.sol"; import {QueueParams, OverrideParams, Transaction} from "../../utils/common/Structs.sol"; import {AsyncModifierNotSet, WatcherNotSet, InvalidOnChainAddress} from "../../utils/common/Errors.sol"; import "../../utils/RescueFundsLib.sol"; -import {toBytes32Format} from "../../utils/common/Converters.sol"; /// @title Forwarder Storage /// @notice Storage contract for the Forwarder contract that contains the state variables @@ -21,15 +19,12 @@ abstract contract ForwarderStorage is IForwarder { /// @notice chain slug on which the contract is deployed uint32 public chainSlug; - /// @notice old on-chain address kep for storage compatibility - can be removed after redeployment - address internal oldOnChainAddress; - // slot 51 /// @notice on-chain address associated with this forwarder bytes32 public onChainAddress; // slots [52-100] reserved for gap - uint256[49] _gap_after; + uint256[50] _gap_after; // slots [101-150] 50 slots reserved for address resolver util } @@ -59,9 +54,6 @@ contract Forwarder is ForwarderStorage, Initializable, AddressResolverUtil { /// @notice Returns the on-chain address associated with this forwarder. /// @return The on-chain address. function getOnChainAddress() public view override returns (bytes32) { - if (oldOnChainAddress != address(0)) { - return toBytes32Format(oldOnChainAddress); - } return onChainAddress; } diff --git a/contracts/evmx/interfaces/IERC20.sol b/contracts/evmx/interfaces/IERC20.sol index a77315e8..af96566e 100644 --- a/contracts/evmx/interfaces/IERC20.sol +++ b/contracts/evmx/interfaces/IERC20.sol @@ -16,6 +16,14 @@ interface IERC20 { function decimals() external view returns (uint8); + function permit( + address spender, + uint256 value, + uint256 deadline, + uint256 nonce, + bytes memory signature + ) external; + 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/IFeesManager.sol b/contracts/evmx/interfaces/IFeesManager.sol index e3074765..15a77269 100644 --- a/contracts/evmx/interfaces/IFeesManager.sol +++ b/contracts/evmx/interfaces/IFeesManager.sol @@ -21,12 +21,6 @@ interface IFeesManager { uint256 amount_ ) external view returns (bool); - function batchApprove(AppGatewayApprovals[] calldata params_) external; - - function approveWithSignature( - bytes memory feeApprovalData_ - ) external returns (address consumeFrom, address spender, uint256 approval); - function withdrawCredits( uint32 chainSlug_, address token_, diff --git a/test/SetupTest.t.sol b/test/SetupTest.t.sol index ee4cec44..7c808510 100644 --- a/test/SetupTest.t.sol +++ b/test/SetupTest.t.sol @@ -648,27 +648,32 @@ contract FeesSetup is DeploySetup { ); } - function approveWithSignature( - address appGateway_, - address user_, - uint256 userPrivateKey_ - ) internal { + function permit(address appGateway_, address user_, uint256 userPrivateKey_) internal { bool approval = feesManager.isApproved(user_, appGateway_); if (approval) return; - // Create fee approval data with signature - bytes32 digest = keccak256( - abi.encode(address(feesManager), evmxSlug, appGateway_, block.timestamp, true) + uint256 value = type(uint256).max; + uint256 deadline = block.timestamp + 1 hours; + bytes32 permitTypehash = keccak256( + "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" ); - // Sign with consumeFrom's private key - bytes memory signature = createSignature(digest, userPrivateKey_); - - // Encode approval data - bytes memory feeApprovalData = abi.encode(appGateway_, true, block.timestamp, signature); + bytes32 structHash = keccak256( + abi.encode( + permitTypehash, + user_, + appGateway_, + value, + feesManager.nonces(user_), + deadline + ) + ); + bytes32 digest = keccak256( + abi.encodePacked("\x19\x01", feesManager.DOMAIN_SEPARATOR(), structHash) + ); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(userPrivateKey_, digest); - // Call whitelistAppGatewayWithSignature with approval data - feesManager.approveWithSignature(feeApprovalData); + feesManager.permit(user_, appGateway_, value, deadline, v, r, s); assertEq( feesManager.isApproved(user_, appGateway_), true, diff --git a/test/evmx/FeesTest.t.sol b/test/evmx/FeesTest.t.sol index 135e7a39..8dba8373 100644 --- a/test/evmx/FeesTest.t.sol +++ b/test/evmx/FeesTest.t.sol @@ -180,4 +180,15 @@ contract FeesTest is AppGatewayBaseSetup { "Receiver balance should increase" ); } + + function testPermitApprovesAppGateway() public { + uint256 pk = 0x1234567890123456789012345678901234567890123456789012345678901234; + address permitUser = vm.addr(pk); + address appGateway = address(counterGateway); + depositNativeAndCredits(arbChainSlug, 1 ether, 0, permitUser); + + assertEq(feesManager.isApproved(permitUser, appGateway), false, "should start unapproved"); + permit(appGateway, permitUser, pk); + assertEq(feesManager.isApproved(permitUser, appGateway), true, "permit should approve"); + } } diff --git a/test/evmx/ProxyStorage.t.sol b/test/evmx/ProxyStorage.t.sol index 960f90f7..06a956fc 100644 --- a/test/evmx/ProxyStorage.t.sol +++ b/test/evmx/ProxyStorage.t.sol @@ -42,8 +42,8 @@ contract ProxyStorageAssertions is AppGatewayBaseSetup { // last hoax(watcherEOA); feesManager.setFeesPlug(evmxSlug, toBytes32Format(address(addressResolver))); - bytes32 mappingSlot = keccak256(abi.encode(uint256(evmxSlug), uint256(57))); - slotValue = vm.load(address(feesManager), mappingSlot); + bytes32 feesPlugMappingSlot = keccak256(abi.encode(uint256(evmxSlug), uint256(55))); + slotValue = vm.load(address(feesManager), feesPlugMappingSlot); assertEq( address(uint160(uint256(slotValue))), address(addressResolver), @@ -51,7 +51,7 @@ contract ProxyStorageAssertions is AppGatewayBaseSetup { ); // address resolver util slot - assertAddressResolverUtilSlot(108, address(feesManager)); + assertAddressResolverUtilSlot(107, address(feesManager)); } function assertAddressResolverSlot() internal { @@ -224,7 +224,7 @@ contract ProxyStorageAssertions is AppGatewayBaseSetup { slotValue = vm.load(address(forwarder), bytes32(uint256(FIRST_SLOT + 1))); assertEq(slotValue, IForwarder(forwarder).getOnChainAddress()); - assertAddressResolverUtilSlot(101, address(forwarder)); + assertAddressResolverUtilSlot(102, address(forwarder)); } function assertAsyncPromiseSlot() internal {