Skip to content
Open
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: 4 additions & 1 deletion script/Deploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ contract Deploy is Script {
* @param salt Unique salt for deterministic address generation
* @return The address of the deployed contract
*/
function deploy(bytes memory initCode, bytes32 salt) public returns (address) {
function deploy(
bytes memory initCode,
bytes32 salt
) public returns (address) {
bytes4 selector = bytes4(keccak256("deploy(bytes,bytes32)"));
bytes memory args = abi.encode(initCode, salt);
bytes memory data = abi.encodePacked(selector, args);
Expand Down
5 changes: 4 additions & 1 deletion script/DeployApprover.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,10 @@ contract DeployApprover is Script {
* @param salt Unique salt for deterministic address generation
* @return The address of the deployed contract
*/
function deploy(bytes memory initCode, bytes32 salt) public returns (address) {
function deploy(
bytes memory initCode,
bytes32 salt
) public returns (address) {
bytes4 selector = bytes4(keccak256("deploy(bytes,bytes32)"));
bytes memory args = abi.encode(initCode, salt);
bytes memory data = abi.encodePacked(selector, args);
Expand Down
10 changes: 8 additions & 2 deletions script/DeployModule.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,10 @@ contract DeployModule is Script {
* @param salt Unique salt for deterministic address generation
* @return moduleAddress The address of the deployed module
*/
function deployWithCreate2(address permit3, bytes32 salt) internal returns (address moduleAddress) {
function deployWithCreate2(
address permit3,
bytes32 salt
) internal returns (address moduleAddress) {
bytes memory initCode = abi.encodePacked(type(ERC7579ApproverModule).creationCode, abi.encode(permit3));

// Call CREATE2 factory
Expand All @@ -72,7 +75,10 @@ contract DeployModule is Script {
* @param salt Deployment salt
* @return The computed address
*/
function computeAddress(address permit3, bytes32 salt) external pure returns (address) {
function computeAddress(
address permit3,
bytes32 salt
) external pure returns (address) {
bytes memory initCode = abi.encodePacked(type(ERC7579ApproverModule).creationCode, abi.encode(permit3));

bytes32 hash = keccak256(abi.encodePacked(bytes1(0xff), CREATE2_FACTORY, salt, keccak256(initCode)));
Expand Down
34 changes: 24 additions & 10 deletions src/MultiTokenPermit.sol
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,12 @@ abstract contract MultiTokenPermit is PermitBase, IMultiTokenPermit {
* @param token ERC721 contract address
* @param tokenId The unique NFT token ID to transfer
*/
function transferFromERC721(address from, address to, address token, uint256 tokenId) public override {
function transferFromERC721(
address from,
address to,
address token,
uint256 tokenId
) public override {
// Check and update dual-allowance
_updateDualAllowance(from, token, tokenId, 1);

Expand Down Expand Up @@ -166,9 +171,8 @@ abstract contract MultiTokenPermit is PermitBase, IMultiTokenPermit {
}

// Execute the batch transfer after all allowances are verified
IERC1155(transfer.token).safeBatchTransferFrom(
transfer.from, transfer.to, transfer.tokenIds, transfer.amounts, ""
);
IERC1155(transfer.token)
.safeBatchTransferFrom(transfer.from, transfer.to, transfer.tokenIds, transfer.amounts, "");
}

/**
Expand Down Expand Up @@ -204,9 +208,8 @@ abstract contract MultiTokenPermit is PermitBase, IMultiTokenPermit {
// Check and update dual-allowance
_updateDualAllowance(transfer.from, transfer.token, transfer.tokenId, transfer.amount);
// Execute the ERC1155 transfer
IERC1155(transfer.token).safeTransferFrom(
transfer.from, transfer.to, transfer.tokenId, transfer.amount, ""
);
IERC1155(transfer.token)
.safeTransferFrom(transfer.from, transfer.to, transfer.tokenId, transfer.amount, "");
}
}
}
Expand All @@ -217,7 +220,10 @@ abstract contract MultiTokenPermit is PermitBase, IMultiTokenPermit {
* @return Storage key for allowance mapping
*/

function _getTokenKey(address token, uint256 tokenId) internal pure returns (bytes32) {
function _getTokenKey(
address token,
uint256 tokenId
) internal pure returns (bytes32) {
// Hash token and tokenId together to ensure unique keys
return keccak256(abi.encodePacked(token, tokenId));
}
Expand All @@ -229,7 +235,12 @@ abstract contract MultiTokenPermit is PermitBase, IMultiTokenPermit {
* @param tokenId The specific token ID
* @param amount The amount to transfer (1 for ERC721, variable for ERC1155)
*/
function _updateDualAllowance(address from, address token, uint256 tokenId, uint160 amount) internal {
function _updateDualAllowance(
address from,
address token,
uint256 tokenId,
uint160 amount
) internal {
bytes32 encodedId = _getTokenKey(token, tokenId);

// First, try to update allowance for the specific token ID
Expand Down Expand Up @@ -259,7 +270,10 @@ abstract contract MultiTokenPermit is PermitBase, IMultiTokenPermit {
* @param revertDataPerId Revert data from specific token ID allowance check
* @param revertDataWildcard Revert data from collection-wide allowance check
*/
function _handleAllowanceError(bytes memory revertDataPerId, bytes memory revertDataWildcard) internal pure {
function _handleAllowanceError(
bytes memory revertDataPerId,
bytes memory revertDataWildcard
) internal pure {
if (revertDataPerId.length == 0 || revertDataWildcard.length == 0) {
// If any allowance succeeded, no error to handle
return;
Expand Down
26 changes: 21 additions & 5 deletions src/NonceManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,10 @@ abstract contract NonceManager is INonceManager, EIP712 {
* @param name Contract name for EIP-712 domain
* @param version Contract version for EIP-712 domain
*/
constructor(string memory name, string memory version) EIP712(name, version) { }
constructor(
string memory name,
string memory version
) EIP712(name, version) { }

/**
* @dev Returns the domain separator for the current chain.
Expand All @@ -67,7 +70,10 @@ abstract contract NonceManager is INonceManager, EIP712 {
* @param salt The salt value to verify
* @return True if nonce has been used, false otherwise
*/
function isNonceUsed(address owner, bytes32 salt) external view returns (bool) {
function isNonceUsed(
address owner,
bytes32 salt
) external view returns (bool) {
return usedNonces[owner][salt];
}

Expand Down Expand Up @@ -162,7 +168,10 @@ abstract contract NonceManager is INonceManager, EIP712 {
* @notice This is an internal helper used by the public invalidateNonces functions
* to process the actual invalidation after signature verification
*/
function _processNonceInvalidation(address owner, bytes32[] memory salts) internal {
function _processNonceInvalidation(
address owner,
bytes32[] memory salts
) internal {
uint256 saltsLength = salts.length;

require(saltsLength != 0, EmptyArray());
Expand All @@ -184,7 +193,10 @@ abstract contract NonceManager is INonceManager, EIP712 {
* @notice This is called before processing permits to ensure each signature
* can only be used once per salt value
*/
function _useNonce(address owner, bytes32 salt) internal {
function _useNonce(
address owner,
bytes32 salt
) internal {
if (usedNonces[owner][salt]) {
revert NonceAlreadyUsed(owner, salt);
}
Expand All @@ -204,7 +216,11 @@ abstract contract NonceManager is INonceManager, EIP712 {
* @notice Reverts with InvalidSignature() if the signature is invalid or
* the recovered signer doesn't match the expected owner
*/
function _verifySignature(address owner, bytes32 structHash, bytes calldata signature) internal view {
function _verifySignature(
address owner,
bytes32 structHash,
bytes calldata signature
) internal view {
bytes32 digest = _hashTypedDataV4(structHash);

// For signatures == 65 bytes ECDSA first then falling back to ERC-1271
Expand Down
22 changes: 18 additions & 4 deletions src/Permit3.sol
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,11 @@ contract Permit3 is IPermit3, MultiTokenPermit, NonceManager {
* - >3: Increase allowance mode - adds to allowance with expiration timestamp
* @notice Enforces timestamp-based locking and handles MAX_ALLOWANCE for infinite approvals
*/
function _processChainPermits(address owner, uint48 timestamp, ChainPermits memory chainPermits) internal {
function _processChainPermits(
address owner,
uint48 timestamp,
ChainPermits memory chainPermits
) internal {
uint256 permitsLength = chainPermits.permits.length;
for (uint256 i = 0; i < permitsLength; i++) {
AllowanceOrTransfer memory p = chainPermits.permits[i];
Expand All @@ -351,7 +355,11 @@ contract Permit3 is IPermit3, MultiTokenPermit, NonceManager {
* @param timestamp Current timestamp for validation
* @param p The permit operation to process
*/
function _processAllowanceOperation(address owner, uint48 timestamp, AllowanceOrTransfer memory p) private {
function _processAllowanceOperation(
address owner,
uint48 timestamp,
AllowanceOrTransfer memory p
) private {
// Validate tokenKey is not zero
if (p.tokenKey == bytes32(0)) {
revert ZeroToken();
Expand Down Expand Up @@ -426,7 +434,10 @@ contract Permit3 is IPermit3, MultiTokenPermit, NonceManager {
* @param allowed Current allowance to modify
* @param amountDelta Amount to decrease by
*/
function _decreaseAllowance(Allowance memory allowed, uint160 amountDelta) private pure {
function _decreaseAllowance(
Allowance memory allowed,
uint160 amountDelta
) private pure {
if (allowed.amount != MAX_ALLOWANCE || amountDelta == MAX_ALLOWANCE) {
allowed.amount = amountDelta > allowed.amount ? 0 : allowed.amount - amountDelta;
}
Expand All @@ -437,7 +448,10 @@ contract Permit3 is IPermit3, MultiTokenPermit, NonceManager {
* @param allowed Allowance to lock
* @param timestamp Current timestamp for lock tracking
*/
function _lockAllowance(Allowance memory allowed, uint48 timestamp) private pure {
function _lockAllowance(
Allowance memory allowed,
uint48 timestamp
) private pure {
allowed.amount = 0;
allowed.expiration = LOCKED_ALLOWANCE;
allowed.timestamp = timestamp;
Expand Down
21 changes: 18 additions & 3 deletions src/PermitBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,12 @@ contract PermitBase is IPermit {
* @param amount Approval amount
* @param expiration Optional expiration timestamp
*/
function approve(address token, address spender, uint160 amount, uint48 expiration) external override {
function approve(
address token,
address spender,
uint160 amount,
uint48 expiration
) external override {
bytes32 tokenKey = bytes32(uint256(uint160(token)));
_validateApproval(msg.sender, tokenKey, token, spender, expiration);

Expand All @@ -105,7 +110,12 @@ contract PermitBase is IPermit {
* @param amount Transfer amount (max 2^160-1)
* @param token ERC20 token contract address
*/
function transferFrom(address from, address to, uint160 amount, address token) public {
function transferFrom(
address from,
address to,
uint160 amount,
address token
) public {
bytes32 tokenKey = bytes32(uint256(uint160(token)));
(, bytes memory revertData) = _updateAllowance(from, tokenKey, msg.sender, amount);
if (revertData.length > 0) {
Expand Down Expand Up @@ -241,7 +251,12 @@ contract PermitBase is IPermit {
* @notice This function handles tokens that don't return boolean values or return false on failure
* @notice Assumes the caller has already verified allowances and will revert on transfer failure
*/
function _transferFrom(address from, address to, uint160 amount, address token) internal {
function _transferFrom(
address from,
address to,
uint160 amount,
address token
) internal {
IERC20(token).safeTransferFrom(from, to, amount);
}
}
23 changes: 20 additions & 3 deletions src/interfaces/IMultiTokenPermit.sol
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,13 @@ interface IMultiTokenPermit {
* @param amount Amount to approve (ignored for ERC721, used for ERC20/ERC1155)
* @param expiration Timestamp when approval expires (0 for no expiration)
*/
function approve(address token, address spender, uint256 tokenId, uint160 amount, uint48 expiration) external;
function approve(
address token,
address spender,
uint256 tokenId,
uint160 amount,
uint48 expiration
) external;

/**
* @notice Execute approved ERC721 token transfer
Expand All @@ -162,7 +168,12 @@ interface IMultiTokenPermit {
* @param token ERC721 token address
* @param tokenId The NFT token ID
*/
function transferFromERC721(address from, address to, address token, uint256 tokenId) external;
function transferFromERC721(
address from,
address to,
address token,
uint256 tokenId
) external;

/**
* @notice Execute approved ERC1155 token transfer
Expand All @@ -172,7 +183,13 @@ interface IMultiTokenPermit {
* @param tokenId The ERC1155 token ID
* @param amount Transfer amount
*/
function transferFromERC1155(address from, address to, address token, uint256 tokenId, uint160 amount) external;
function transferFromERC1155(
address from,
address to,
address token,
uint256 tokenId,
uint160 amount
) external;

/**
* @notice Execute approved ERC721 batch transfer
Expand Down
5 changes: 4 additions & 1 deletion src/interfaces/INonceManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,10 @@ interface INonceManager is IPermit {
* @param salt Salt value to check
* @return true if nonce has been used
*/
function isNonceUsed(address owner, bytes32 salt) external view returns (bool);
function isNonceUsed(
address owner,
bytes32 salt
) external view returns (bool);

/**
* @notice Mark multiple nonces as used
Expand Down
14 changes: 12 additions & 2 deletions src/interfaces/IPermit.sol
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,12 @@ interface IPermit {
* @param amount The amount of tokens to approve
* @param expiration The timestamp when the approval expires
*/
function approve(address token, address spender, uint160 amount, uint48 expiration) external;
function approve(
address token,
address spender,
uint160 amount,
uint48 expiration
) external;

/**
* @notice Transfers tokens from an approved address
Expand All @@ -174,7 +179,12 @@ interface IPermit {
* @param token The token contract address
* @dev Requires prior approval from the owner to the caller (msg.sender)
*/
function transferFrom(address from, address to, uint160 amount, address token) external;
function transferFrom(
address from,
address to,
uint160 amount,
address token
) external;

/**
* @notice Executes multiple token transfers in a single transaction
Expand Down
18 changes: 14 additions & 4 deletions src/lib/EIP712.sol
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,10 @@ abstract contract EIP712 is IERC5267 {
* NOTE: These parameters cannot be changed except through a xref:learn::upgrading-smart-contracts.adoc[smart
* contract upgrade].
*/
constructor(string memory name, string memory version) {
constructor(
string memory name,
string memory version
) {
_name = name.toShortStringWithFallback(_nameFallback);
_version = version.toShortStringWithFallback(_versionFallback);
_hashedName = keccak256(bytes(name));
Expand Down Expand Up @@ -134,9 +137,16 @@ abstract contract EIP712 is IERC5267 {
/// @dev 0x0f = 0b01111 indicates: name (bit 0), version (bit 1), chainId (bit 2), verifyingContract (bit 3)
bytes1 EIP712_FIELDS = hex"0f";

return (
EIP712_FIELDS, _EIP712Name(), _EIP712Version(), CROSS_CHAIN_ID, address(this), bytes32(0), new uint256[](0)
);
return
(
EIP712_FIELDS,
_EIP712Name(),
_EIP712Version(),
CROSS_CHAIN_ID,
address(this),
bytes32(0),
new uint256[](0)
);
}

/**
Expand Down
Loading