Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions contracts/evmx/base/AppGatewayBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
81 changes: 8 additions & 73 deletions contracts/evmx/fees/Credit.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
}
}
2 changes: 1 addition & 1 deletion contracts/evmx/helpers/AsyncPromise.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 1 addition & 9 deletions contracts/evmx/helpers/Forwarder.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}
Expand Down Expand Up @@ -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;
}

Expand Down
8 changes: 8 additions & 0 deletions contracts/evmx/interfaces/IERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
6 changes: 0 additions & 6 deletions contracts/evmx/interfaces/IFeesManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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_,
Expand Down
35 changes: 20 additions & 15 deletions test/SetupTest.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
11 changes: 11 additions & 0 deletions test/evmx/FeesTest.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
}
8 changes: 4 additions & 4 deletions test/evmx/ProxyStorage.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,16 @@ 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),
"fees plug mismatch"
);

// address resolver util slot
assertAddressResolverUtilSlot(108, address(feesManager));
assertAddressResolverUtilSlot(107, address(feesManager));
}

function assertAddressResolverSlot() internal {
Expand Down Expand Up @@ -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 {
Expand Down