diff --git a/docs/Cosigner.md b/docs/Cosigner.md deleted file mode 100644 index 906bb9e..0000000 --- a/docs/Cosigner.md +++ /dev/null @@ -1,20 +0,0 @@ -# Cosigner - -As much as possible, we want the trust and security of our system to be onchain. However, there are some security measures that can only be taken offchain with current technology. To provide the best of both worlds, in addition to all of the onchain protections mentioned, we also require that all permissioned user operations be signed by a cosigner that will run offchain checks before signing. Validating the cosigner signature is enforced through the PermissionManager and compliments our existing security measures (pausable, enabled permission contracts, enabled paymasters) by providing a path for flexible constraints to apply granularly per user operation. - -The first set of responsibilities for the cosigner is to support blocklists for specific apps, signers, and external contracts that are compromised or intend to harm users. - -The cosigner is also responsible for enforcing an offchain paymaster allowlist. If a permissioned user operation is attempted which does not use an allowed paymaster, cosigning will fail. This is done to mitigate attacks involving ERC20 paymasters with a users token allowance burning their funds on failing user operations. - -Note that any blocking at the cosigner level only applies to permissioned user operations and does not impact the normal transaction flow through keys.coinbase.com. Also note that the cosigner cannot submit permissioned user operations on its own and a signature from an app to initiate is still required. - -For our first permission contract, there is an additional cosigner responsibility to ensure that only native tokens are being spent. In absence of cosigning, it is actually possible for a permissioned user operation to spend contract tokens (ERC20, ERC721, ERC1155) if these tokens extend an allowance to an allowed external contract for permissions. - -This situation requires two preconditions (set up in either order): - -1. the user approved a permission to call contract A -2. the user approved an allowance for contract A to spend token B - -Now when the app submits a user operation for the user to call contract A, it is possible for this call to spend users token B. Especially with infinite approvals being common, this is a potentially dangerous path to enable for a permission that claims to only support spending native token so we will not be cosigning these user operations to prevent this. We will detect these cases by simulating every user operation, parsing the emitted logs, and look for any log that matched the ERC20/ERC721/ERC1155 transfer logs where the `from` argument is the user's address (`userOp.sender`). - -Note that this simulation logic also prevents another path for apps to attempt to spend tokens where the external contract is itself a token. This could be possible if the token contract implements our defined `permissionedCall` selector and is approved as an allowed contract by the user. We can prevent potentially malicious calls to these contracts by preventing any user operation with outboung approval events in addition to the same out-bound transfer logs limitation. Developers that want to use token contracts within permissioned user operations are encouraged to wait until we add proper onchain accounting for these cases. diff --git a/docs/ERC-7715.md b/docs/ERC-7715.md index 8806c36..6b49813 100644 --- a/docs/ERC-7715.md +++ b/docs/ERC-7715.md @@ -3,14 +3,6 @@ ### Signer types ```typescript -type KeySigner = { - type: "key"; - data: { - type: "secp256r1"; // supports both passkeys and cryptokeys - publicKey: `0x${string}`; - }; -}; - type AccountSigner = { type: "account"; data: { @@ -31,11 +23,13 @@ type NativeTokenRecurringAllowancePermission = { }; }; -type AllowedContractSelectorPermission = { - type: "allowed-contract-selector"; +type NativeTokenRecurringAllowancePermission = { + type: "erc20-recurring-allowance"; data: { - contract: `0x${string}`; // address - selector: `0x${string}`; // bytes4 function selector + token: `0x${string}`; // address + start: number; // unix seconds + period: number; // seconds + allowance: `0x${string}`; // hex for uint256 }; }; ``` @@ -51,10 +45,9 @@ const request = { address: "0x...", // optional expiry: 1725000000, signer: { - type: "key", + type: "account", data: { - type: "secp256r1", - publicKey: "0x...", + address: "0x...", }, }, permissions: [ @@ -66,13 +59,6 @@ const request = { allowance: `0x1`, // 1 wei }, }, - { - type: "allowed-contract-selector", - data: { - contract: "0x8Af2FA0c32891F1b32A75422eD3c9a8B22951f2F", // Click - selector: "0x2bd1b86d", // permissionedCall(bytes) - }, - }, ], }, ], @@ -89,10 +75,9 @@ const request = { address: "0x...", // optional expiry: 1725000000, signer: { - type: "key", + type: "account", data: { - type: "secp256r1", - publicKey: "0x...", + address: "0x...", }, }, permissions: [ @@ -104,13 +89,6 @@ const request = { allowance: `0x1`, // 1 wei }, }, - { - type: "allowed-contract-selector", - data: { - contract: "0x8Af2FA0c32891F1b32A75422eD3c9a8B22951f2F", - selector: "0x2bd1b86d", // permissionedCall(bytes) - }, - }, ], }, context: "0x...", @@ -127,6 +105,14 @@ type NativeTokenRecurringAllowancePermissionState = { allowanceUsed: Hex; // uint256 allowanceLeft: Hex; // uint256 }; + +type NativeTokenRecurringAllowancePermissionState = { + token: Address; + cycleStart: number; + cycleEnd: number; + allowanceUsed: Hex; // uint256 + allowanceLeft: Hex; // uint256 +}; ``` ### `wallet_getPermissions` RPC types @@ -163,10 +149,9 @@ type GetPermissionsResponse = (PermissionReponse & { address: "0x...", // optional expiry: 1725000000, signer: { - type: "key", + type: "account", data: { - type: "secp256r1", - publicKey: "0x...", + address: "0x...", }, }, permissions: [ @@ -178,13 +163,6 @@ type GetPermissionsResponse = (PermissionReponse & { allowance: `0x1`, // 1 wei }, }, - { - type: "allowed-contract-selector", - data: { - contract: "0x8Af2FA0c32891F1b32A75422eD3c9a8B22951f2F", // Click - selector: "0x2bd1b86d", // permissionedCall(bytes) - }, - }, ], }, context: "0x...", diff --git a/docs/PaymasterRequirement.md b/docs/PaymasterRequirement.md deleted file mode 100644 index d300c02..0000000 --- a/docs/PaymasterRequirement.md +++ /dev/null @@ -1,9 +0,0 @@ -# Paymaster Requirement - -Prior knowledge of [recurring allowances](./RecurringAllowance.md) is recommended. - -It is critical that token expenditure accounting to be 100% accurate and unfortunately, this is at risk if a paymaster is not used. When a paymaster is not used, the gas fee comes directly from the user's account to pay the Bundler. In the event the user operation execution fails, our updated allowance usage does not persist because it is also done in execution phase. This creates a situation where the user paid native token for gas to the Bundler, but this spend is not accounted for. Without mitigation, this opens a attack vector where an app can spend the user's entire native token balance by spamming failing user operations. - -Fortunately, the mitigation is simple where if apps are required to pay for the gas costs of permissioned user operations, incentives are aligned where a user never pays for unsuccessful user operations. Note that this does not force apps to pay for all permissioned user operations though, just to front the initial gas reserve to the Bundler. If an app would like users to pay for their gas, they can pack a call to refund themselves in the same user operation. Should many apps request and leverage this capability, we may consider adding a paved road for this pattern of refunding the sponsoring app. - -This fundamental issue of accounting for users native token expenditure on gas for failing user operations also applies for when MagicSpend is used as a paymaster. In the MagicSpend case, users assets are still fronting the bundler's gas payment so we also exclude MagicSpend as a valid paymaster to protect users. Note that this does not prevent apps from using MagicSpend withdraws within a user operation or using part of these withdrawn funds to refund itself, just for initial Bundler payment. diff --git a/docs/PermissionManager.md b/docs/PermissionManager.md deleted file mode 100644 index 9af04a3..0000000 --- a/docs/PermissionManager.md +++ /dev/null @@ -1,64 +0,0 @@ -# Permission Manager - -View a sample sequence diagram of onchain validation [here](./diagrams/onchain/permissionedCalls.md). - -This page summarizes key design decisions for [`PermissionManager`](../src/PermissionManager.sol). - -## Design Overview - -### Immutable singleton - -Some security mechanisms require storing state external to Smart Wallets. Given that some state applies to the system as a whole, designing around a singleton architecture was most intuitive. Given this contract will be added as an owner to all Smart Wallets that opt-in, it is vital that this contract be non-upgradeable to mitigate the risk of mass attacks to our users by swapping into a malicious implementation. However, this singleton is not permissionless and has a single `owner` to manage its safe operation. Should the singleton ever become known as compromised, the `owner` has the authority to pause the contract through a typical pausable mechanism. - -### Ethereum address and `secp256r1` signers - -Just like Smart Wallet V1, Session Keys supports both Ethereum address and `secp256r1` signers. Ethereum addresses are split into validating EOA signatures with `ecrecover` and contract signatures with [ERC-1271](https://eips.ethereum.org/EIPS/eip-1271) `isValidSignature`. The `secp256r1` curve supports both Passkey and [CryptoKey](./CryptoKey.md) signature validation through [WebAuthn](https://github.com/base-org/webauthn-sol/blob/main/src/WebAuthn.sol). Note that contract signers cannot violate [ERC-7562](https://eips.ethereum.org/EIPS/eip-7562) "associated storage" constraints, e.g. using a Coinbase Smart Wallet as a signer for another Smart Wallet. - -### Signature approvals with lazy caching - -Users approve permissions by signing over the hash of a `struct Permission`. A signature-first approach enables users to approve without spending any gas upfront and delaying gas fees until the point of transaction. This helps create an environment where users feel more comfortable approving permissions by removing an immediate cost to them. However, doing a signature validation onchain is expensive so Permission Manager implements a lazy caching mechanism that saves the approval in storage during first use. On future use of the same permission, a signature validation can be skipped by reading this storage, substantially improving gas efficiency. The storage for appovals is a doubly-nested mapping where the final key is the account address to enable valid access in the ERC-4337 validation phase. - -### Transaction approvals - -A storage-based approval system also enables Permission Manager to expose an `approvePermission` function that unlocks important UX primitives like [batch approvals](./diagrams/onchain/batchApprovePermissions.md) and [atomic updates](./diagrams/onchain/batchUpdatePermissions.md). - -### Permission revocations - -Permission Manager also exposes a `revokePermission` function to enable revocations. The storage for revocations is a doubly-nested mapping where the final key is the account address to enable valid access in the ERC-4337 validation phase. Permission revocation is always available to users in their Smart Wallet settings and in the future, potentially exposed to apps. Revoking a permission cannot be undone, but users can approve a new, similar permission. - -### Reentrancy protection - -One important invariant for Permission Manager is that it should not enable any Session Key to change owners or upgrade the account implementation. The functions to do so on Smart Wallet are gated by an `onlyOwner` modifier which requires attempts to change owners or implementation to come from an owner of the Smart Wallet or the Smart Wallet itself. To prevent the latter case, Permission Manager loops over all calls in the batch (parsed from `userOp.calldata`) and ensures that no call's target is the Smart Wallet (`userOp.sender`). To prevent the former case, all contracts written by Coinbase are given additional scrutiny to have tightly defined paths for owner or implementation changes so that they cannot be called with Session Keys. External teams that build on Smart Wallet should be mindful of this development. Read more about this protection in Coinbase's internal audit. - -Additionally, reentrancy calls to the Permission Manager are also negated to prevent cases where Session Keys attempt to approve new permissions or revoke others. - -### Validation/Execution phase separation (`beforeCalls`) - -[ERC-7562](https://eips.ethereum.org/EIPS/eip-7562) defines a set of conditions for ERC-4337's validation phase. Two limitations we need to work around are: - -1. Accessing the `TIMESTAMP` opcode to check if a permission has expired -2. Reading `"associated storage"` to check common invariants (described in following sections) - -To retain compliance, Permission Manager moves these checks to execution phase by enforcing that the first call in a batch is to `PermissionManager.beforeCalls`. This function implements the checks that cannot be done in validation phase and if it reverts, the user operation fails and no intended calls execute. The bundler will still get paid in this scenario because this is happening after validation phase. - -### Enabled Permission Contracts - -Part of these execution phase checks include verifying the attempted Permission Contract is enabled. The `owner` is responsible for maintaining this storage by adding new Permission Contracts as new functionality is rolled out, potentially disabling them later on if a compromise is found. - -### Permission initialization - -The final step in `beforeCalls` is to apply to lazy approval caching mentioned earlier. If a permission has not yet been approved, it is then marked as approved in storage and an external call to `PermissionContract.initializePermission` is made. This one-time initialization is enforced to only come from the Permission Manager and optionally stores part or all of the values of the permission. For example, our first Permission Contract stores the native token recurring allowance parameters. - -### Permission hash incompatibility with EIP-191 and EIP-712 - -Helping users be informed about the permissions requested of them is critical for a practically safe system. In addition to intentional design on our pop-up window, we disable the ability to get around our hot path by making the permission hash message incompatible with [EIP-191](https://eips.ethereum.org/EIPS/eip-191) and [EIP-712](https://eips.ethereum.org/EIPS/eip-712). This prevents apps from secretely asking users to approve a permission through an interface that does not sufficiently communicate what the user is actually signing. - -### Normal transaction flow prevention - -On this same theme, we prevent apps from secretly adding calls to `PermissionManager.beforeCalls`, `PermissionManager.approvePermission`, and `PermissionManager.revokePermission` in their call batches through normal transaction requests. We add new simulation on the `eth_sendTransaction` and `wallet_sendCalls` RPCs to look for any call to `PermissionManager` and auto-reject if present. - -### Required Cosigner - -All permissioned user operations require additional offchain validation through another signature from a Coinbase-owned key. This cosigner is our final line of protection to filter out user operations that might negatively impact users. It's logic is outlined further [here](./Cosigner.md) and is recommended to read after covering the Permission Contract's mechanisms: [recurring allowances](./RecurringAllowance.md), [permissioned calls](./PermissionedCall.md), and [required paymasters](./PaymasterRequirement.md). - -The cosigner is immutable so that we can verify permissioned user operation cosignatures in the validation phase to mitigate attacks involving paymasters that can burn user funds on failed user operations. diff --git a/docs/PermissionedCall.md b/docs/PermissionedCall.md deleted file mode 100644 index 52dcb73..0000000 --- a/docs/PermissionedCall.md +++ /dev/null @@ -1,61 +0,0 @@ -# Permissioned Call - -**Permission Call enable apps to safely and permissionlessly integrate Smart Wallet Permissions in their application.** - -The default approach to enable this is for a permission to enable calling arbitrary contracts with arbitrary function selectors and arguments. Even with controls for users to allowlist specific contracts/selectors/arguments, we believe this model is insecure. - -Users are not able to evaluate if a specific contract/selector/argument is safe or not for them to approve and the long tail of interactions make building an automated system to flag potentially dangerous behavior difficult. It's too easy for an app to ask for permission to make a malicious external call and for a user to blindly approve it. - -The key insight is for contracts to intentionally support permissioned calls. This sets us up for immediate backwards **incompatibilty**, which is actually a security feature because by default many potentially hazardous external calls are prevented. The most glaring problems with this approach are making it convenient enough for smart contract developers to add this support and dealing with non-upgradeable contracts. - -Currently, we achieve contract opt-in through requiring them to implement a new function `permissionedCall(bytes call) payable returns (bytes)`. The function takes in a single `bytes call` argument expected to be formatted as calldata that exactly matches another function on the contract. With this data, `permissionedCall` simply makes a self-delegatecall, retaining the same call context for the underlying function, and returning the same data back to the original call sender. - -Offchain, apps do not have to change how they currently encode calls to their contract (e.g. wagmi’s `useWriteContracts`) because the wallet's user operation preparation will wrap their provided calls in this new selector. For onchain validation, the Permission Contract enforces that only external calls using the `permissionedCall` selector are allowed. - -Additionally, we encourage smart contract developers to intentionally support permissioned calls for the functions that need it. Some functionality may be irreversible or high-severity to the point that ensuring users are involved in the final signing process is useful friction. Contracts are also expected to implement `supportsPermissionedCallSelector(bytes4 selector) view returns (bool)` which declares if a specific function selector is supported or not through `permissionedCall`. This pattern takes inspiration from [EIP-165](https://eips.ethereum.org/EIPS/eip-165) and easily enables granular selector filtering or enabling all selectors with `return true`. We provide a default [`PermissionCallable`](./PermissionCallable.sol) implementation that developers can inherit directly in their contracts. We do not recommend using this contract in production until our audit has completed. - -```solidity -import {PermissionCallable} from "smart-wallet-permissions/mixins/PermissionCallable.sol"; - -contract Contract is PermissionCallable { - // define which function selectors are callable by permissioned userOps - function supportsPermissionedCallSelector(bytes4 selector) public pure override returns (bool) { - return selector == Contract.foo.selector; - } - - // callable by permissioned userOps - function foo() external; - - // not callable by permissioned userOps - function bar() external; -} -``` - -Non-upgradeable contracts make implementing `permissionedCall` difficult, but not impossible in some cases. For contracts that do not have strict use of `msg.sender` for authorization, we have found success in deploying a new middleware contract that implements `permissionedCall` and forwards calls to the target contract. - -```mermaid -flowchart LR - SW["Smart Wallet"] - PS["Permission Sandbox"] - TC["Target Contract"] - - SW -- permissionedCall --> PS -- call --> TC -``` - -For cases where you only want to ask for one permission approval to interact with many contracts, the middleware pattern can also help isolate a single address to get permission to call. Note that the middleware contract obscures the original Smart Wallet address which would normally be `msg.sender` in the context of the target contracts, which makes it default secure but also potentially incompatible. - -```mermaid -flowchart LR - SW["Smart Wallet"] - PS["Permission Sandbox"] - TC1["Target Contract 1"] - TC2["Target Contract 2"] - TC3["Target Contract 3"] - - SW -- permissionedCall --> PS - PS -- call --> TC1 - PS -- call --> TC2 - PS -- call --> TC3 -``` - -We are continuing to build out a set of examples that help developers grab a pre-made solution. If you feel like you do not have a clear way to implement `permissionedCall`, please reach out in our [Discord](https://discord.com/invite/cdp/). diff --git a/docs/README.md b/docs/README.md index 624866d..77ae088 100644 --- a/docs/README.md +++ b/docs/README.md @@ -14,49 +14,6 @@ Our first iteration chose to lean into the patterns defined by [ERC-4337](https: While implementing this feature as a new V2 wallet implementation was tempting, we decided to leverage the modular owner system from [Smart Wallet V1](https://github.com/coinbase/smart-wallet) and avoid a hard upgrade. This helped reduce our launch timeline and also reduced the risk of introducing this substantially different account authentication paradigm. -We accomplished this by writing a new singleton contract, [`PermissionManager`](./PermissionManager.md), which can be optionally added as an owner to existing accounts your first time encountering an app that uses Session Keys or during accounts creation. - -```mermaid -graph LR - E["EntryPoint"] - SW["Smart Wallet"] - PM["Permission Manager"] - - E -- validateUserOp --> SW - SW -- isValidSignature --> PM -``` - -### 3. Generic/Specific permission validation split - -The Permission Manager is responsible for validating permissioned user operations. There are many kinds of permissions we expect developers to request over time, so we chose a modular design where permission-specific validations are delegated to a Permission Contract. The Permission Manager will initially validate the core properties of a permissioned user operation (e.g. authorized by user, not expired) and then call the Permission Contract to perform additional checks (e.g. allowed contract calls). - -```mermaid -graph LR - E["EntryPoint"] - SW["Smart Wallet"] - PM["Permission Manager"] - PC["Permission Contract"] - - E -- validateUserOp --> SW - SW -- isValidSignature --> PM - PM -- validatePermission --> PC -``` - -### 4. Tightly-scoped first Permission Contract - -For the V1 launch, we will only support one Permission Contract with select features: - -- spend native token (ETH) with a [recurring allowance](./RecurringAllowance.md) -- withdraw assets from [MagicSpend](https://github.com/coinbase/magic-spend) -- call external contracts with a [single, required selector](./PermissionedCall.md) -- sponsor transactions with a [required paymaster](./PaymasterRequirement.md) - -While we believe these capabilities unlock many valuable use cases, some integrations will not yet be possible. We encourage you to join our [Discord](<(https://discord.com/invite/cdp/)>) and submit feedback in `#smart-wallet` for your feature requests and use case to help shape our roadmap. Currently, adding support for ERC20 and more function selectors are top priorities. - -### 5. Validation/Execution phase checks split - -To be compatible with ERC-4337, we had to adhere to the restrictions during validation phase defined in [ERC-7562](https://eips.ethereum.org/EIPS/eip-7562). One pattern we employed both for the Permission Manger and Permission Contract is two have two sets of checks with as many as possible in validation phase and then a select few in execution phase. We make these execution-phase checks by packing additional calls into the call batch sent to the Smart Wallet in `userOp.calldata` and we enforce their precense in this calldata during validation phase. If any of these execution-phase checks fail, the entire user operation execution fails. - ## End-to-end Journey ### 1. App requests permissions from user (offchain) @@ -69,4 +26,4 @@ View a sample sequence diagram [here](./diagrams/offchain/prepareCalls+sendCalls ### 3. Bundler executes User Operation (onchain) -View a sample sequence diagram [here](./diagrams/onchain/permissionedCalls.md). +View a sample sequence diagram [here](./diagrams/onchain/withdraw.md). diff --git a/docs/SpendPermissionsPaymaster.md b/docs/SpendPermissionsPaymaster.md new file mode 100644 index 0000000..dfee9f0 --- /dev/null +++ b/docs/SpendPermissionsPaymaster.md @@ -0,0 +1 @@ +# Recurring Allowance diff --git a/docs/diagrams/offchain/firstTimeApproval.md b/docs/diagrams/offchain/firstTimeApproval.md deleted file mode 100644 index 71aab9d..0000000 --- a/docs/diagrams/offchain/firstTimeApproval.md +++ /dev/null @@ -1,42 +0,0 @@ -## First-Time Approval - -Existing Smart Wallets must first add the Permission Manager as an owner in order for it to process permissioned user operations. This owner addition also needs to be replayed for each network the user tries to transact on. Our goal is to minimize confirmation steps for users to enable this, so we lean into combining our existing replayable user operation mechanism with our ability to batch permission approval calls in a user operation. - -First, we need to the user to sign a chain-agnostic, zero-gas user operation that adds the Permission Manager as an owner. The chain-agnosticism and zero-gas parameters allow anyone to submit this user operation to an Entrypoint on any network in any gas conditions and still execute the owner change. - -When a user makes their first permission approval on a chain, Smart Wallet pulls this signed user operation and batch two calls together to add the owner and approve the permission. - -Smart Wallet asks the user to sign this new user operation, submits it to a bundler, and waits for it to land onchain before returning the permission `context` back to the app. Because we did not have the user sign the permission in isolation, the `approval` field on the `Permission` struct will be empty bytes (`0x`). The app does not know there is any difference between this context and a normal one and proceeds as normal to provide this context in future calls to submit Session Key user operations. - -```mermaid -sequenceDiagram - autonumber - box transparent App - participant A as App Interface - participant SDK as Wallet SDK - end - box transparent Wallet - participant W as Wallet Interface - participant U as User - end - box transparent External - participant P as Paymaster - participant B as Bundler - end - - A->>SDK: wallet_grantPermissions - SDK->>W: wallet_grantPermissions - W->>P: pm_getPaymasterStubData - P-->>W: paymaster stub data - W->>P: pm_getPaymasterData - P-->>W: paymaster data - W->>U: approve permission - Note over W,U: userOp with owner addition - U->>U: sign - U-->>W: signature - W->>B: eth_sendUserOperation - B-->>W: userOpHash - Note over W: wait for userOp to land - W-->>SDK: permissions with context - SDK-->>A: permissions with context -``` diff --git a/docs/diagrams/onchain/batchApprovePermissions.md b/docs/diagrams/onchain/batchApprovePermissions.md deleted file mode 100644 index b1ff5c7..0000000 --- a/docs/diagrams/onchain/batchApprovePermissions.md +++ /dev/null @@ -1,23 +0,0 @@ -## Batch Approve Permissions - -Accounts can batch-approve permissions via batching `approvePermission` calls to `PermissionManager`. - -```mermaid -sequenceDiagram - autonumber - participant E as Entrypoint - participant A as Account - participant M as Permission Manager - participant P as Permission Contract - - E->>A: validateUserOp - Note left of E: Validation phase - A-->>E: validation data - E->>A: executeBatch - Note left of E: Execution phase - loop - A->>M: approvePermission - Note over A,M: permission struct - M->>P: initializePermission - end -``` diff --git a/docs/diagrams/onchain/batchRevokePermissions.md b/docs/diagrams/onchain/batchRevokePermissions.md deleted file mode 100644 index b079d60..0000000 --- a/docs/diagrams/onchain/batchRevokePermissions.md +++ /dev/null @@ -1,21 +0,0 @@ -## Batch Revoke Permissions - -Accounts can batch-revoke permissions via batching `revokePermission` calls to `PermissionManager`. - -```mermaid -sequenceDiagram - autonumber - participant E as Entrypoint - participant A as Account - participant M as Permission Manager - - E->>A: validateUserOp - Note left of E: Validation phase - A-->>E: validation data - E->>A: executeBatch - Note left of E: Execution phase - loop - A->>M: revokePermission - Note over A,M: bytes32 permissionHash - end -``` diff --git a/docs/diagrams/onchain/batchUpdatePermissions.md b/docs/diagrams/onchain/batchUpdatePermissions.md index d00c2ad..3bd7eaf 100644 --- a/docs/diagrams/onchain/batchUpdatePermissions.md +++ b/docs/diagrams/onchain/batchUpdatePermissions.md @@ -1,27 +1,25 @@ ## Batch Update Permissions -Accounts can batch-update permissions via batching `revokePermission` and `approvePermission` calls to `PermissionManager`. +Accounts can batch-revoke/approve/update permissions via batching `revoke` and `approve` calls to `SpendPermissions`. ```mermaid sequenceDiagram autonumber participant E as Entrypoint participant A as Account - participant M as Permission Manager - participant P as Permission Contract + participant SP as Spend Permissions E->>A: validateUserOp - Note left of E: Validation phase + Note over E: Validation phase A-->>E: validation data E->>A: executeBatch - Note left of E: Execution phase + Note over E: Execution phase loop - A->>M: revokePermission - Note over A,M: bytes32 permissionHash + A->>SP: revoke + Note over A,SP: recurring allowance data end loop - A->>M: approvePermission - Note over A,M: permission struct - M->>P: initializePermission + A->>SP: approve + Note over A,SP: recurring allowance data end ``` diff --git a/docs/diagrams/onchain/cachePermissions.md b/docs/diagrams/onchain/cachePermissions.md deleted file mode 100644 index 66b7bb3..0000000 --- a/docs/diagrams/onchain/cachePermissions.md +++ /dev/null @@ -1,15 +0,0 @@ -## Cache Permissions - -After permission approval signatures are exposed publicly, anyone can use that signature to save the approval in storage. Doing so can save gas as it removes the additional signature calldata and external call + validation of the signature. - -```mermaid -sequenceDiagram - autonumber - participant E as External - participant M as Permission Manager - participant P as Permission Contract - - E->>M: approvePermission - Note over E,M: permission struct - M->>P: initializePermission -``` diff --git a/docs/diagrams/onchain/firstTimeApproval.md b/docs/diagrams/onchain/firstTimeApproval.md deleted file mode 100644 index 30d6904..0000000 --- a/docs/diagrams/onchain/firstTimeApproval.md +++ /dev/null @@ -1,38 +0,0 @@ -## First-Time Approval - -Existing Smart Wallets must first add the Permission Manager as an owner in order for it to process permissioned user operations. This owner addition also needs to be replayed for each network the user tries to transact on. Our goal is to minimize confirmation steps for users to enable this, so we lean into combining our existing replayable user operation mechanism with our ability to batch permission approval calls in a user operation. - -First, we need to the user to sign a chain-agnostic, zero-gas user operation that adds the Permission Manager as an owner. The chain-agnosticism and zero-gas parameters allow anyone to submit this user operation to an Entrypoint on any network in any gas conditions and still execute the owner change. - -When a user makes their first permission approval on a chain, Smart Wallet pulls this signed user operation and batch two calls together to add the owner and approve the permission. - -The first call in the batch is to `EntryPoint.handleOps` with our previously signed no-gas user operation. The Entrypoint performs its typical validation against the Smart Wallet and then calls the `addOwnerAddress` function. Because the user operation has zero-gas parameters and no paymaster, there is no required prefund withdrawn from the Smart Wallet in validation phase. - -The second call in the batch is to `PermissionManager.approvePermission` to approve the permission. Because the call is made from the Smart Wallet, the Permission Manager will not require an approval signature packed into the `Permission` argument, saving our user from this additional signature. - -```mermaid -sequenceDiagram - autonumber - participant E as Entrypoint - participant A as Account - participant M as Permission Manager - participant P as Permission Contract - - Note left of E: Validation phase - E->>A: validateUserOp - A-->>E: validation data - Note left of E: Execution phase - E->>A: executeBatch - A->>E: handleOps - activate E - E->>A: validateUserOp - activate A - A-->>E: validation data - deactivate A - E->>A: addOwnerAddress - deactivate E - Note over A,E: Add Permission Manager - A->>M: approvePermission - Note over A,M: permission struct - M->>P: initializePermission -``` diff --git a/docs/diagrams/onchain/permissionedCalls.md b/docs/diagrams/onchain/permissionedCalls.md deleted file mode 100644 index 4125f02..0000000 --- a/docs/diagrams/onchain/permissionedCalls.md +++ /dev/null @@ -1,39 +0,0 @@ -## User Operation Validation - -```mermaid -sequenceDiagram - autonumber - participant E as Entrypoint - participant A as Smart Wallet - participant M as Permission Manager - participant P as Permission Contract - participant C as External Contract - - E->>A: validateUserOp - Note left of E: Validation phase - A->>M: isValidSignature - Note over A,M: check owner signed userOp - Note over M: General permission checks: ‎ ‎
1. permission not revoked ‎ ‎ ‎ ‎
2. user approved permission
3. cosigner signed userOp ‎ ‎ ‎ ‎
4. session key signed userOp
5. prepends beforeCalls call ‎
6. no calls back on account ‎ ‎
7. no calls back on manager ‎ - opt - M->>A: isValidSignature - Note over M,A: check account approval signature - A-->>M: EIP1271 magic value - end - M->>P: validatePermission - Note over P: Specific permission checks: ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎
1. only calls allowed contracts ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎
2. only calls special selector ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎
3. appends useRecurringAllowance call - M-->>A: EIP1271 magic value - A-->>E: validation data - E->>A: executeBatch - Note left of E: Execution phase - A->>M: beforeCalls - Note over M: Execution phase checks: ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎
1. manager not paused ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎
2. permission not expired ‎ ‎ ‎ ‎ ‎ ‎ ‎ ‎
3. permission contract enabled - opt - M->>P: initializePermission - end - loop - A->>C: permissionedCall - Note over C,A: send intended calldata wrapped with special selector - end - A->>P: useRecurringAllowance - Note over P: assert spend within recurring allowance -``` diff --git a/docs/examples/Click.sol b/docs/examples/Click.sol deleted file mode 100644 index 15b808a..0000000 --- a/docs/examples/Click.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {PermissionCallable} from "smart-wallet-permissions/mixins/PermissionCallable.sol"; - -contract Click is PermissionCallable { - event Clicked(address indexed account); - - function click() public payable { - emit Clicked(msg.sender); - // return value back to sender, used for testing native token spend - (bool success,) = msg.sender.call{value: msg.value}(""); - require(success); - } - - function supportsPermissionedCallSelector(bytes4 selector) public pure override returns (bool) { - return selector == Click.click.selector; - } -} diff --git a/docs/examples/README.md b/docs/examples/README.md deleted file mode 100644 index 9834e90..0000000 --- a/docs/examples/README.md +++ /dev/null @@ -1,39 +0,0 @@ -## Get started - -> **Note**: These contracts are unaudited, use at your own risk. - -### 0. Integrate Coinbase Smart Wallet into your app. - -The [smartwallet.dev](https://www.smartwallet.dev/guides/session-keys) docs are recommended. - -### 1. Add support for permissioned user operations to call your smart contract. - -If you do not yet have `forge` installed, first [install the foundry toolkit](https://book.getfoundry.sh/getting-started/installation). - -```bash -forge install coinbase/smart-wallet-permissions -``` - -After installing this codebase as a dependency in your project, simply import and inherit `PermissionCallable` into your contract and override the `supportsPermissionedCallSelector` function to allow your functions to be called by permissioned userOps. - -```solidity -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {PermissionCallable} from "smart-wallet-permissions/mixins/PermissionCallable.sol"; - -contract Contract is PermissionCallable { - // define which function selectors are callable by permissioned userOps - function supportsPermissionedCallSelector(bytes4 selector) public pure override returns (bool) { - return selector == Contract.foo.selector; - } - // callable by permissioned userOps - function foo() external; - // not callable by permissioned userOps - function bar() external; -} -``` - -### 2. Reach out for help in our Discord - -Join our [Coinbase Developer Platform Discord](https://discord.com/invite/cdp/), join the `#smart-wallet` channel, and post a message describing your project and intended use of Smart Wallet Permissions if you encounter issues. diff --git a/docs/examples/SimpleSandbox.sol b/docs/examples/SimpleSandbox.sol deleted file mode 100644 index a5001cf..0000000 --- a/docs/examples/SimpleSandbox.sol +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {Address} from "openzeppelin-contracts/contracts/utils/Address.sol"; - -import {PermissionCallable} from "smart-wallet-permissions/mixins/PermissionCallable.sol"; - -/// @title SimpleSandbox -/// -/// @notice Forwards any external calls to a specified target. -/// -/// @dev This pattern works for target contracts that do not care who `msg.sender` is. -contract SimpleSandbox is PermissionCallable { - /// @notice Call a target contract with data. - /// - /// @param target Address of contract to call. - /// @param data Bytes to send in contract call. - /// - /// @return res Bytes result from the call. - function sandboxedCall(address target, bytes calldata data) external payable returns (bytes memory) { - return Address.functionCallWithValue(target, data, msg.value); - } - - /// @inheritdoc PermissionCallable - function supportsPermissionedCallSelector(bytes4 selector) public pure override returns (bool) { - return selector == SimpleSandbox.sandboxedCall.selector; - } -} diff --git a/script/Debug.s.sol b/script/Debug.s.sol index 50802a0..8b69816 100644 --- a/script/Debug.s.sol +++ b/script/Debug.s.sol @@ -7,22 +7,10 @@ import {CoinbaseSmartWallet} from "smart-wallet/CoinbaseSmartWallet.sol"; import {CoinbaseSmartWalletFactory} from "smart-wallet/CoinbaseSmartWalletFactory.sol"; import {ECDSA} from "solady/utils/ECDSA.sol"; -import {PermissionManager} from "../src/PermissionManager.sol"; -import {SpendPermissions} from "../src/SpendPermissions.sol"; -import {PermissionCallableAllowedContractNativeTokenRecurringAllowance as PermissionContract} from - "../src/permissions/PermissionCallableAllowedContractNativeTokenRecurringAllowance.sol"; - // forge script Debug --broadcast -vvvv contract Debug is Script { - /// @dev Deployment address consistent across chains - /// https://github.com/coinbase/magic-spend/releases/tag/v1.0.0 - address public constant MAGIC_SPEND = 0x011A61C07DbF256A68256B1cB51A5e246730aB92; address public constant OWNER = 0x6EcB18183838265968039955F1E8829480Db5329; // dev wallet - address public constant OWNER_2 = 0x0BFc799dF7e440b7C88cC2454f12C58f8a29D986; // work wallet - address public constant COSIGNER = 0xAda9897F517018cc51831B9691F0e94b50df50B8; // tmp private key - address public constant CDP_PAYMASTER = 0xf5d253B62543C6Ef526309D497f619CeF95aD430; address public constant FACTORY = 0x0BA5ED0c6AA8c49038F819E587E2633c4A9F428a; - address public constant ETHER = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; function run() public { diff --git a/script/Deploy.s.sol b/script/Deploy.s.sol index bac6f60..9b6a111 100644 --- a/script/Deploy.s.sol +++ b/script/Deploy.s.sol @@ -4,26 +4,12 @@ pragma solidity ^0.8.20; import {Script, console2} from "forge-std/Script.sol"; import {Strings} from "openzeppelin-contracts/contracts/utils/Strings.sol"; -import {PermissionManager} from "../src/PermissionManager.sol"; -import {SpendPermissions} from "../src/SpendPermissions.sol"; -import {PermissionCallableAllowedContractNativeTokenRecurringAllowance as PermissionContract} from - "../src/permissions/PermissionCallableAllowedContractNativeTokenRecurringAllowance.sol"; - /** * forge script Deploy --account dev --sender $SENDER --rpc-url $BASE_SEPOLIA_RPC --verify --verifier-url * $SEPOLIA_BASESCAN_API --etherscan-api-key $BASESCAN_API_KEY --broadcast -vvvv */ contract Deploy is Script { - /// @dev Deployment address consistent across chains - /// https://github.com/coinbase/magic-spend/releases/tag/v1.0.0 - address public constant MAGIC_SPEND = 0x011A61C07DbF256A68256B1cB51A5e246730aB92; address public constant OWNER = 0x6EcB18183838265968039955F1E8829480Db5329; // dev wallet - address public constant COSIGNER = 0xAda9897F517018cc51831B9691F0e94b50df50B8; // tmp private key - address public constant CDP_PAYMASTER = 0xC484bCD10aB8AD132843872DEb1a0AdC1473189c; // limiting paymaster - address public constant CDP_PAYMASTER_PUBLIC = 0xf5d253B62543C6Ef526309D497f619CeF95aD430; // public - - PermissionManager permissionManager; - PermissionContract permissionContract; function run() public { vm.startBroadcast(); diff --git a/src/EIP712.sol b/src/EIP712.sol index 567e98e..8021318 100644 --- a/src/EIP712.sol +++ b/src/EIP712.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.4; +pragma solidity ^0.8.0; /// @title EIP-712 /// diff --git a/src/PermissionManager.sol b/src/PermissionManager.sol deleted file mode 100644 index 49af5dd..0000000 --- a/src/PermissionManager.sol +++ /dev/null @@ -1,363 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {Ownable, Ownable2Step} from "openzeppelin-contracts/contracts/access/Ownable2Step.sol"; -import {IERC1271} from "openzeppelin-contracts/contracts/interfaces/IERC1271.sol"; -import {Pausable} from "openzeppelin-contracts/contracts/utils/Pausable.sol"; -import {CoinbaseSmartWallet} from "smart-wallet/CoinbaseSmartWallet.sol"; -import {ECDSA} from "solady/utils/ECDSA.sol"; - -import {IPermissionContract} from "./interfaces/IPermissionContract.sol"; -import {BytesLib} from "./utils/BytesLib.sol"; -import {CallErrors} from "./utils/CallErrors.sol"; -import {SignatureCheckerLib} from "./utils/SignatureCheckerLib.sol"; -import {UserOperation, UserOperationLib} from "./utils/UserOperationLib.sol"; - -/// @title PermissionManager -/// -/// @notice A dynamic permission system built into an EIP-1271 module designed for Coinbase Smart Wallet -/// (https://github.com/coinbase/smart-wallet). -/// -/// @author Coinbase (https://github.com/coinbase/smart-wallet-permissions) -contract PermissionManager is IERC1271, Ownable2Step, Pausable { - /// @notice UserOperation that is validated via Permissions. - struct PermissionedUserOperation { - /// @dev Permission details. - Permission permission; - /// @dev User operation (v0.6) to validate for. - UserOperation userOp; - /// @dev `Permission.signer` signature of user operation hash. - bytes userOpSignature; - /// @dev `this.cosigner` or `this.pendingCosigner` signature of user operation hash. - bytes userOpCosignature; - } - - /// @notice A limited permission for an external signer to use an account. - struct Permission { - /// @dev Smart account this permission is valid for. - address account; - /// @dev Timestamp this permission is valid until (unix seconds). - uint48 expiry; - /// @dev The entity that has limited control of `account` in this Permission. - /// @dev Supports abi-encoded Ethereum addresses (EOA, contract) and P256 public keys (passkey, cryptokey). - bytes signer; - /// @dev External contract to verify specific permission logic. - address permissionContract; - /// @dev Permission-specific values sent to permissionContract for validation. - bytes permissionValues; - /// @dev Optional signature from account owner proving a permission is approved. - bytes approval; - } - - /// @dev bytes4(keccak256("isValidSignature(bytes32,bytes)")) - bytes4 internal constant EIP1271_MAGIC_VALUE = 0x1626ba7e; - - /// @notice Second-factor signer required to approve every permissioned userOp. - address public immutable cosigner; - - /// @notice Track if permission contracts are enabled. - /// - /// @dev Storage not keyable by account, can only be accessed in execution phase. - mapping(address permissionContract => bool enabled) public isPermissionContractEnabled; - - /// @notice Track if permissions are revoked by accounts. - /// - /// @dev Keying storage by account in deepest mapping enables us to pass 4337 storage access limitations. - mapping(bytes32 permissionHash => mapping(address account => bool revoked)) internal _isPermissionRevoked; - - /// @notice Track if permissions are approved by accounts via transactions. - /// - /// @dev Keying storage by account in deepest mapping enables us to pass 4337 storage access limitations. - mapping(bytes32 permissionHash => mapping(address account => bool approved)) internal _isPermissionApproved; - - /// @notice UserOperation does not match provided hash. - /// - /// @param userOpHash Hash of the user operation. - error InvalidUserOperationHash(bytes32 userOpHash); - - /// @notice UserOperation sender does not match account. - /// - /// @param sender Account that the user operation is made from. - error InvalidUserOperationSender(address sender); - - /// @notice UserOperation does not use a paymaster - error InvalidUserOperationPaymaster(); - - /// @notice Permission is unauthorized by either revocation or lack of approval. - error UnauthorizedPermission(); - - /// @notice Invalid signature. - error InvalidSignature(); - - /// @notice Invalid beforeCalls call. - error InvalidBeforeCallsCall(); - - /// @notice Permission has expired. - /// - /// @param expiry Timestamp for when the permission expired (unix seconds). - error ExpiredPermission(uint48 expiry); - - /// @notice Permission contract not enabled. - /// - /// @param permissionContract The contract resposible for checking permission logic. - error DisabledPermissionContract(address permissionContract); - - /// @notice Invalid cosigner. - /// - /// @param cosigner Address of the cosigner. - error InvalidCosigner(address cosigner); - - /// @notice Renouncing ownership attempted but not allowed. - error CannotRenounceOwnership(); - - /// @notice Permission contract setting updated. - /// - /// @param permissionContract The contract resposible for checking permission logic. - /// @param enabled The new setting allowing/preventing use. - event PermissionContractUpdated(address indexed permissionContract, bool enabled); - - /// @notice Permission was revoked prematurely by account. - /// - /// @param account The smart contract account the permission controlled. - /// @param permissionHash The unique hash representing the permission. - event PermissionRevoked(address indexed account, bytes32 indexed permissionHash); - - /// @notice Permission was approved via transaction. - /// - /// @param account The smart contract account the permission controls. - /// @param permissionHash The unique hash representing the permission. - event PermissionApproved(address indexed account, bytes32 indexed permissionHash); - - /// @notice Constructor. - /// - /// @param initialOwner Owner responsible for managing security controls. - /// @param cosigner_ EOA responsible for cosigning user operations for abuse mitigation. - constructor(address initialOwner, address cosigner_) Ownable(initialOwner) Pausable() { - // check cosigner non-zero - if (cosigner_ == address(0)) revert InvalidCosigner(cosigner_); - cosigner = cosigner_; - } - - /// @notice Check permission constraints not allowed during userOp validation phase as first call in batch. - /// - /// @dev Accessing data only available in execution-phase: - /// * Manager paused state - /// * Expiry TIMESTAMP opcode - /// * Enabled permission contract state - /// - /// @param permission Details of the permission. - function beforeCalls(Permission calldata permission) external whenNotPaused { - // check permission not expired - if (permission.expiry < block.timestamp) revert ExpiredPermission(permission.expiry); - - // check permission contract enabled - if (!isPermissionContractEnabled[permission.permissionContract]) { - revert DisabledPermissionContract(permission.permissionContract); - } - - // approve permission to cache storage for cheaper execution on future use - approvePermission(permission); - } - - /// @notice Approve a permission to enable its use in user operations. - /// - /// @dev Entire Permission struct taken as argument for indexers to cache relevant data. - /// @dev Permissions can also be validated just-in-time via approval signatures instead of approval storage. - /// @dev This can be called by anyone after an approval signature has been used for gas optimization. - /// - /// @param permission Details of the permission. - function approvePermission(Permission calldata permission) public { - bytes32 permissionHash = hashPermission(permission); - - // early return if permission is already approved - if (_isPermissionApproved[permissionHash][permission.account]) { - return; - } - - // check sender is permission account or approval signature is valid for permission account - if ( - msg.sender != permission.account - && !_isValidApprovalSignature(permission.account, permissionHash, permission.approval) - ) { - revert UnauthorizedPermission(); - } - - _isPermissionApproved[permissionHash][permission.account] = true; - emit PermissionApproved(permission.account, permissionHash); - - // initialize permission via external call to permission contract - IPermissionContract(permission.permissionContract).initializePermission( - permission.account, permissionHash, permission.permissionValues - ); - } - - /// @notice Revoke a permission to disable its use indefinitely. - /// - /// @param permissionHash hash of the permission to revoke - function revokePermission(bytes32 permissionHash) external { - // early return if permission is already revoked - if (_isPermissionRevoked[permissionHash][msg.sender]) { - return; - } - - _isPermissionRevoked[permissionHash][msg.sender] = true; - emit PermissionRevoked(msg.sender, permissionHash); - } - - /// @notice Set permission contract enabled status. - /// - /// @param permissionContract The contract resposible for checking permission logic. - /// @param enabled True if the contract is enabled. - function setPermissionContractEnabled(address permissionContract, bool enabled) external onlyOwner { - isPermissionContractEnabled[permissionContract] = enabled; - emit PermissionContractUpdated(permissionContract, enabled); - } - - /// @notice Pause the manager contract from processing any userOps. - function pause() external onlyOwner { - _pause(); - } - - /// @notice Unpause the manager contract to enable processing userOps again. - function unpause() external onlyOwner { - _unpause(); - } - - /// @notice Renounce ownership of this contract. - /// - /// @dev Overidden to always revert to prevent accidental renouncing. - function renounceOwnership() public view override onlyOwner { - revert CannotRenounceOwnership(); - } - - /// @notice Validates a permission via EIP-1271. - /// - /// @dev Verifies that `userOp.calldata` calls CoinbaseSmartWallet.executeBatch`. - /// @dev All accessed storage must be nested by account address to pass ERC-4337 constraints. - /// - /// @param userOpHash Hash of the user operation. - /// @param userOpAuth Authentication data for this permissioned user operation. - function isValidSignature(bytes32 userOpHash, bytes calldata userOpAuth) external view returns (bytes4 result) { - (PermissionedUserOperation memory data) = abi.decode(userOpAuth, (PermissionedUserOperation)); - - // check userOperation sender matches account; - if (data.userOp.sender != data.permission.account) { - revert InvalidUserOperationSender(data.userOp.sender); - } - - // check userOp matches userOpHash - if (UserOperationLib.getUserOpHash(data.userOp) != userOpHash) { - revert InvalidUserOperationHash(UserOperationLib.getUserOpHash(data.userOp)); - } - - // check userOp uses a paymaster - if (bytes20(data.userOp.paymasterAndData) == bytes20(0)) revert InvalidUserOperationPaymaster(); - - // check permission authorized (approved and not yet revoked) - if (!isPermissionAuthorized(data.permission)) revert UnauthorizedPermission(); - - // check permission signer signed userOpHash - if (!SignatureCheckerLib.isValidSignatureNow(userOpHash, data.userOpSignature, data.permission.signer)) { - revert InvalidSignature(); - } - - // parse cosigner from cosignature - address userOpCosigner = ECDSA.recover(userOpHash, data.userOpCosignature); - - // check userOpCosigner is cosigner - if (userOpCosigner != cosigner) revert InvalidCosigner(userOpCosigner); - - // check userOp.callData is `executeBatch` - if (bytes4(data.userOp.callData) != CoinbaseSmartWallet.executeBatch.selector) { - revert CallErrors.SelectorNotAllowed(bytes4(data.userOp.callData)); - } - - CoinbaseSmartWallet.Call[] memory calls = - abi.decode(BytesLib.trimSelector(data.userOp.callData), (CoinbaseSmartWallet.Call[])); - - // prepare beforeCalls data - bytes memory beforeCallsData = abi.encodeWithSelector(PermissionManager.beforeCalls.selector, data.permission); - - // check first call is valid `self.beforeCalls` - if (calls[0].target != address(this) || !BytesLib.eq(calls[0].data, beforeCallsData) || calls[0].value != 0) { - revert InvalidBeforeCallsCall(); - } - - // check rest of calls batch do not re-enter account or this contract - uint256 callsLen = calls.length; - for (uint256 i = 1; i < callsLen; i++) { - // prevent account and PermissionManager direct re-entrancy - if (calls[i].target == data.permission.account || calls[i].target == address(this)) { - revert CallErrors.TargetNotAllowed(calls[i].target); - } - } - - // validate permission-specific logic - IPermissionContract(data.permission.permissionContract).validatePermission( - hashPermission(data.permission), data.permission.permissionValues, data.userOp - ); - - // return back to account to complete owner signature verification of userOpHash - return EIP1271_MAGIC_VALUE; - } - - /// @notice Hash a Permission struct for signing. - /// - /// @dev Important that this hash cannot be phished via EIP-191/712 or other method. - /// - /// @param permission Struct to hash. - function hashPermission(Permission memory permission) public view returns (bytes32) { - return keccak256( - abi.encode( - permission.account, - permission.expiry, - keccak256(permission.signer), - permission.permissionContract, - keccak256(permission.permissionValues), - block.chainid, // prevent cross-chain replay - address(this) // prevent cross-manager replay - ) - ); - } - - /// @notice Verify if permission has been authorized. - /// - /// @dev Checks if has not been revoked and is approved via storage or signature. - /// - /// @param permission Fields of the permission (struct). - /// - /// @return approved True if permission is approved and not yet revoked. - function isPermissionAuthorized(Permission memory permission) public view returns (bool) { - bytes32 permissionHash = hashPermission(permission); - - // check permission not revoked - if (_isPermissionRevoked[permissionHash][permission.account]) { - return false; - } - - // check if approval storage has been set (automatically set on first use) - if (_isPermissionApproved[permissionHash][permission.account]) { - return true; - } - - // fallback check permission approved via signature - return _isValidApprovalSignature(permission.account, permissionHash, permission.approval); - } - - /// @notice Check if a permission approval signature is valid. - /// - /// @param account Smart account this permission is valid for. - /// @param permissionHash Hash of the permission. - /// @param approval Signature bytes signed by account owner. - /// - /// @return isValid True if approval signature is valid. - function _isValidApprovalSignature(address account, bytes32 permissionHash, bytes memory approval) - internal - view - returns (bool) - { - // early return false if approval is zero-length, otherwise validate via ERC-1271 on account - return - approval.length != 0 && IERC1271(account).isValidSignature(permissionHash, approval) == EIP1271_MAGIC_VALUE; - } -} diff --git a/src/SpendPermissionsPaymaster.sol b/src/SpendPermissionsPaymaster.sol index 898ab36..72eddf0 100644 --- a/src/SpendPermissionsPaymaster.sol +++ b/src/SpendPermissionsPaymaster.sol @@ -1,5 +1,5 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; +pragma solidity ^0.8.0; import {IEntryPoint} from "account-abstraction/interfaces/IEntryPoint.sol"; import {IPaymaster} from "account-abstraction/interfaces/IPaymaster.sol"; diff --git a/src/interfaces/IPermissionCallable.sol b/src/interfaces/IPermissionCallable.sol deleted file mode 100644 index 4e3ca20..0000000 --- a/src/interfaces/IPermissionCallable.sol +++ /dev/null @@ -1,26 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -/// @title IPermissionCallable -/// -/// @notice Interface for external contracts to support Session Keys permissionlessly. -/// -/// @author Coinbase (https://github.com/coinbase/smart-wallet-permissions) -interface IPermissionCallable { - /// @notice Wrap a call to the contract with a new selector. - /// - /// @dev Call data exactly matches valid selector+arguments on this contract. - /// @dev Call data matching required because this performs a self-delegatecall. - /// - /// @param call Call data exactly matching valid selector+arguments on this contract. - /// - /// @return res data returned from the inner self-delegatecall. - function permissionedCall(bytes calldata call) external payable returns (bytes memory res); - - /// @notice Determine if a function selector is allowed via permissionedCall on this contract. - /// - /// @param selector the specific function to check support for. - /// - /// @return supported indicator if the selector is supported. - function supportsPermissionedCallSelector(bytes4 selector) external view returns (bool supported); -} diff --git a/src/interfaces/IPermissionContract.sol b/src/interfaces/IPermissionContract.sol deleted file mode 100644 index e953170..0000000 --- a/src/interfaces/IPermissionContract.sol +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {UserOperation} from "account-abstraction/interfaces/UserOperation.sol"; - -/// @title IPermissionContract -/// -/// @author Coinbase (https://github.com/coinbase/smart-wallet-permissions) -interface IPermissionContract { - /// @notice Sender for intializePermission was not permission manager. - error InvalidInitializePermissionSender(address sender); - - /// @notice Validate the permission to execute a userOp. - /// - /// @param permissionHash Hash of the permission. - /// @param permissionValues Additional arguments for validation. - /// @param userOp User operation to validate permission for. - function validatePermission(bytes32 permissionHash, bytes calldata permissionValues, UserOperation calldata userOp) - external - view; - - /// @notice Initialize a permission with its verified values. - /// - /// @dev Some permissions require state which is initialized upon first use/approval. - /// @dev Can only be called by the PermissionManager. - /// - /// @param account Account of the permission. - /// @param permissionHash Hash of the permission. - /// @param permissionValues Additional arguments for validation. - function initializePermission(address account, bytes32 permissionHash, bytes calldata permissionValues) external; -} diff --git a/src/mixins/NativeTokenRecurringAllowance.sol b/src/mixins/NativeTokenRecurringAllowance.sol deleted file mode 100644 index 169f2dc..0000000 --- a/src/mixins/NativeTokenRecurringAllowance.sol +++ /dev/null @@ -1,229 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -/// @title NativeTokenRecurringAllowance -/// -/// @notice Allow spending native token with recurring allowance. -/// -/// @dev Allowance and spend values capped at uint160 ~ 1e48. -/// -/// @author Coinbase (https://github.com/coinbase/smart-wallet-permissions) -abstract contract NativeTokenRecurringAllowance { - /// @notice Recurring allowance parameters. - struct RecurringAllowance { - /// @dev Start time of the recurring allowance's first cycle (unix seconds). - uint48 start; - /// @dev Time duration for resetting spend on a recurring basis (seconds). - uint48 period; - /// @dev Maximum allowed value to spend within a recurring cycle - uint160 allowance; - } - - /// @notice Cycle parameters and spend usage. - struct CycleUsage { - /// @dev Start time of the cycle (unix seconds). - uint48 start; - /// @dev End time of the cycle (unix seconds). - uint48 end; - /// @dev Accumulated spend amount for cycle. - uint160 spend; - } - - /// @notice Packed recurring allowance values (start, period) for the permission. - mapping(address account => mapping(bytes32 permissionHash => RecurringAllowance)) internal _recurringAllowances; - - /// @notice Latest cycle usage for the permission. - mapping(address account => mapping(bytes32 permissionHash => CycleUsage)) internal _lastCycleUsages; - - /// @notice Zero recurring allowance value. - error InvalidInitialization(); - - /// @notice Recurring cycle has not started yet. - /// - /// @param start Start time of the recurring allowance (unix seconds). - error BeforeRecurringAllowanceStart(uint48 start); - - /// @notice Spend value exceeds max size of uint160. - /// - /// @param spend Spend value that triggered overflow. - error SpendValueOverflow(uint256 spend); - - /// @notice Spend value exceeds permission's spending limit. - /// - /// @param spend Spend value that exceeded allowance. - /// @param allowance Allowance value that was exceeded. - error ExceededRecurringAllowance(uint256 spend, uint256 allowance); - - /// @notice Register native token allowance for a permission. - /// - /// @param account Account of the permission. - /// @param permissionHash Hash of the permission. - /// @param recurringAllowance Allowed spend per recurring cycle (struct). - event RecurringAllowanceInitialized( - address indexed account, bytes32 indexed permissionHash, RecurringAllowance recurringAllowance - ); - - /// @notice Register native token spend for a recurring allowance cycle. - /// - /// @param account Account that spent native token via a permission. - /// @param permissionHash Hash of the permission. - /// @param newUsage Start and end of the current cycle with new spend usage (struct). - event RecurringAllowanceUsed(address indexed account, bytes32 indexed permissionHash, CycleUsage newUsage); - - /// @notice Get recurring allowance values. - /// - /// @param account Account of the permission. - /// @param permissionHash Hash of the permission. - /// - /// @return recurringAllowance Allowed spend per recurring cycle (struct). - function getRecurringAllowance(address account, bytes32 permissionHash) - public - view - returns (RecurringAllowance memory recurringAllowance) - { - return _recurringAllowances[account][permissionHash]; - } - - /// @notice Get the usage data for the currently active recurring cycle. - /// - /// @dev Reverts if recurring allowance has not started. - /// - /// @param account Account of the permission. - /// @param permissionHash Hash of the permission. - /// - /// @return cycleUsage Currently active cycle start and spend (struct). - function getRecurringAllowanceUsage(address account, bytes32 permissionHash) - public - view - returns (CycleUsage memory cycleUsage) - { - RecurringAllowance memory recurringAllowance = _recurringAllowances[account][permissionHash]; - - // check valid initialization - if (!_isValidInitialization(recurringAllowance)) revert InvalidInitialization(); - - return _getCurrentCycleUsage(account, permissionHash, recurringAllowance); - } - - /// @notice Initialize the native token recurring allowance for a permission. - /// - /// @param account Account of the permission. - /// @param permissionHash Hash of the permission. - /// @param recurringAllowance Allowed spend per recurring cycle (struct). - function _initializeRecurringAllowance( - address account, - bytes32 permissionHash, - RecurringAllowance memory recurringAllowance - ) internal { - // check valid initialization - if (!_isValidInitialization(recurringAllowance)) revert InvalidInitialization(); - - // initialize recurring allowance if not yet initialized - RecurringAllowance memory savedRecurringAllowance = _recurringAllowances[account][permissionHash]; - if (!_isValidInitialization(savedRecurringAllowance)) { - _recurringAllowances[account][permissionHash] = recurringAllowance; - emit RecurringAllowanceInitialized(account, permissionHash, recurringAllowance); - } - } - - /// @notice Use recurring allowance and register spend for active cycle. - /// - /// @dev Initializes state for recurring allowance start and period for first time use. - /// - /// @param account Account of the permission. - /// @param permissionHash Hash of the permission. - /// @param spend Amount of native token being spent. - function _useRecurringAllowance(address account, bytes32 permissionHash, uint256 spend) internal { - // early return if no value spent - if (spend == 0) return; - - RecurringAllowance memory recurringAllowance = _recurringAllowances[account][permissionHash]; - - // check valid initialization - if (!_isValidInitialization(recurringAllowance)) revert InvalidInitialization(); - - // get active cycle start and spend, check if recurring allowance has started - CycleUsage memory currentCycle = _getCurrentCycleUsage(account, permissionHash, recurringAllowance); - - uint256 totalSpend = spend + currentCycle.spend; - - // check spend value does not exceed max value - if (totalSpend > type(uint160).max) revert SpendValueOverflow(totalSpend); - - // check spend value does not exceed recurring allowance - if (totalSpend > recurringAllowance.allowance) { - revert ExceededRecurringAllowance(totalSpend, recurringAllowance.allowance); - } - - // save new accrued spend for active cycle - currentCycle.spend = uint160(totalSpend); - _lastCycleUsages[account][permissionHash] = currentCycle; - - emit RecurringAllowanceUsed( - account, permissionHash, CycleUsage(currentCycle.start, currentCycle.end, uint160(spend)) - ); - } - - /// @notice Determine if a recurring allowance has valid initialization - /// - /// @dev Allowance can be zero for apps that don't need to spend native token. - /// - /// @param recurringAllowance Allowed spend per recurring cycle (struct). - /// - /// @return isValid True if recurring allowance has valid paramters for initialization. - function _isValidInitialization(RecurringAllowance memory recurringAllowance) internal pure returns (bool) { - return recurringAllowance.start > 0 && recurringAllowance.period > 0; - } - - /// @notice Get current cycle usage. - /// - /// @dev Reverts if recurring allowance has not started. - /// @dev Cycles start at recurringAllowance.start + n * recurringAllowance.period. - /// - /// @param account Account of the permission. - /// @param permissionHash Hash of the permission. - /// @param recurringAllowance Allowed spend per recurring cycle (struct). - /// - /// @return currentCycle Currently active cycle with spend usage (struct). - function _getCurrentCycleUsage( - address account, - bytes32 permissionHash, - RecurringAllowance memory recurringAllowance - ) private view returns (CycleUsage memory) { - // check recurring allowance has started - uint48 currentTimestamp = uint48(block.timestamp); - if (currentTimestamp < recurringAllowance.start) { - revert BeforeRecurringAllowanceStart(recurringAllowance.start); - } - - // return last cycle if still active, otherwise compute new active cycle start time with no spend - CycleUsage memory lastCycleUsage = _lastCycleUsages[account][permissionHash]; - - // last cycle exists if start, end, and spend are non-zero, i.e. a non-zero start implies that end and spend are also non-zero - bool lastCycleExists = lastCycleUsage.start != 0; - - // last cycle still active if current time within [start, end) range, i.e. start-inclusive and end-exclusive - bool lastCycleStillActive = - currentTimestamp < uint256(lastCycleUsage.start) + uint256(recurringAllowance.period); - - if (lastCycleExists && lastCycleStillActive) { - return lastCycleUsage; - } else { - // last active cycle does not exist or is outdated, determine current cycle - - // current cycle progress is remainder of time since first recurring cycle mod reset period - uint48 currentCycleProgress = (currentTimestamp - recurringAllowance.start) % recurringAllowance.period; - - // current cycle start is progress duration before current time - uint48 start = currentTimestamp - currentCycleProgress; - - // current cycle end will overflow if period is sufficiently large - bool endOverflow = uint256(start) + uint256(recurringAllowance.period) > type(uint48).max; - - // end is one period after start or maximum uint48 if overflow - uint48 end = endOverflow ? type(uint48).max : start + recurringAllowance.period; - - return CycleUsage({start: start, end: end, spend: 0}); - } - } -} diff --git a/src/mixins/PermissionCallable.sol b/src/mixins/PermissionCallable.sol deleted file mode 100644 index 296414f..0000000 --- a/src/mixins/PermissionCallable.sol +++ /dev/null @@ -1,32 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {Address} from "openzeppelin-contracts/contracts/utils/Address.sol"; - -import {IPermissionCallable} from "../interfaces/IPermissionCallable.sol"; -import {CallErrors} from "../utils/CallErrors.sol"; - -/// @title PermissionCallable -/// -/// @notice Abstract contract to add permissioned userOp support to smart contracts. -/// -/// @author Coinbase (https://github.com/coinbase/smart-wallet-permissions) -abstract contract PermissionCallable is IPermissionCallable { - /// @notice Call not enabled through permissionedCall and smart wallet permissions systems. - /// - /// @param selector The function that was attempting to go through permissionedCall. - error NotPermissionCallable(bytes4 selector); - - /// @inheritdoc IPermissionCallable - function permissionedCall(bytes calldata call) external payable returns (bytes memory res) { - // require call length at least 4 bytes - if (call.length < 4) revert CallErrors.InvalidCallLength(); - // require call selector is allowed through permissionedCall - if (!supportsPermissionedCallSelector(bytes4(call))) revert NotPermissionCallable(bytes4(call)); - // make self-delegatecall with provided call data - return Address.functionDelegateCall(address(this), call); - } - - /// @inheritdoc IPermissionCallable - function supportsPermissionedCallSelector(bytes4 selector) public view virtual returns (bool); -} diff --git a/src/permissions/PermissionCallableAllowedContractNativeTokenRecurringAllowance.sol b/src/permissions/PermissionCallableAllowedContractNativeTokenRecurringAllowance.sol deleted file mode 100644 index d8f16ac..0000000 --- a/src/permissions/PermissionCallableAllowedContractNativeTokenRecurringAllowance.sol +++ /dev/null @@ -1,168 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {MagicSpend} from "magic-spend/MagicSpend.sol"; -import {CoinbaseSmartWallet} from "smart-wallet/CoinbaseSmartWallet.sol"; - -import {PermissionManager} from "../PermissionManager.sol"; -import {IPermissionCallable} from "../interfaces/IPermissionCallable.sol"; -import {IPermissionContract} from "../interfaces/IPermissionContract.sol"; -import {NativeTokenRecurringAllowance} from "../mixins/NativeTokenRecurringAllowance.sol"; -import {BytesLib} from "../utils/BytesLib.sol"; -import {CallErrors} from "../utils/CallErrors.sol"; -import {UserOperation, UserOperationLib} from "../utils/UserOperationLib.sol"; - -/// @title PermissionCallableAllowedContractNativeTokenRecurringAllowance -/// -/// @notice Only allow custom external calls with IPermissionCallable.permissionedCall selector. -/// @notice Only allow custom external calls to a single allowed contract. -/// @notice Allow spending native token with recurring allowance. -/// @notice Allow withdrawing native token from MagicSpend for non-paymaster flow. -/// -/// @dev Requires appending useRecurringAllowance call on every use. -/// -/// @author Coinbase (https://github.com/coinbase/smart-wallet-permissions) -contract PermissionCallableAllowedContractNativeTokenRecurringAllowance is - IPermissionContract, - NativeTokenRecurringAllowance -{ - /// @notice Permission-specific values for this permission contract. - struct PermissionValues { - /// @dev Recurring native token allowance value (struct). - RecurringAllowance recurringAllowance; - /// @dev Single contract allowed to make custom external calls to. - address allowedContract; - } - - /// @notice PermissionManager singleton. - address public immutable permissionManager; - - /// @notice MagicSpend singleton. - address public immutable magicSpend; - - /// @notice Cannot initialize with zero-address. - error ZeroAddress(); - - /// @notice Detected that gas fee is being paid for by user (MagicSpend or no paymaster). - error GasSponsorshipRequired(); - - /// @notice MagicSpend withdraw asset is not native token. - /// - /// @param asset Address of asset for MagicSpend withdraw request. - error InvalidWithdrawAsset(address asset); - - /// @notice Call to useRecurringAllowance not made on self or with invalid data. - error InvalidUseRecurringAllowanceCall(); - - /// @notice Constructor. - /// - /// @param permissionManager_ Contract address for PermissionManager. - /// @param magicSpend_ Contract address for MagicSpend. - constructor(address permissionManager_, address magicSpend_) { - if (permissionManager_ == address(0) || magicSpend_ == address(0)) revert ZeroAddress(); - permissionManager = permissionManager_; - magicSpend = magicSpend_; - } - - /// @notice Initialize the permission values. - /// - /// @dev Called by permission manager on approval transaction. - /// - /// @param account Account of the permission. - /// @param permissionHash Hash of the permission. - /// @param permissionValues Permission-specific values for this permission contract. - function initializePermission(address account, bytes32 permissionHash, bytes calldata permissionValues) external { - (PermissionValues memory values) = abi.decode(permissionValues, (PermissionValues)); - - // check sender is permission manager - if (msg.sender != permissionManager) revert InvalidInitializePermissionSender(msg.sender); - - _initializeRecurringAllowance(account, permissionHash, values.recurringAllowance); - } - - /// @notice Register a spend of native token for a given permission. - /// - /// @dev Accounts can call this even if they did not actually spend anything, so there is a self-DOS vector. - /// Users can only impact themselves though because storage for allowances is keyed by account (msg.sender). - /// - /// @param permissionHash Hash of the permission. - /// @param callsSpend Value of native token spent on calls. - function useRecurringAllowance(bytes32 permissionHash, uint256 callsSpend) external { - _useRecurringAllowance({account: msg.sender, permissionHash: permissionHash, spend: callsSpend}); - } - - /// @notice Validate the permission to execute a userOp. - /// - /// @dev Offchain userOp construction should append useRecurringAllowance call to calls array. - /// @dev Recurring native token spend accounting does not protect against re-entrancy where an external call could - /// trigger an authorized call back to the account to spend more ETH. - /// - /// @param permissionHash Hash of the permission. - /// @param permissionValues Permission-specific values for this permission contract. - /// @param userOp User operation to validate permission for. - function validatePermission(bytes32 permissionHash, bytes calldata permissionValues, UserOperation calldata userOp) - external - view - { - address paymaster = address(bytes20(userOp.paymasterAndData)); - if (paymaster == address(0) || paymaster == magicSpend) revert GasSponsorshipRequired(); - - (PermissionValues memory values) = abi.decode(permissionValues, (PermissionValues)); - - // parse user operation call data as `executeBatch` arguments (call array) - CoinbaseSmartWallet.Call[] memory calls = abi.decode(userOp.callData[4:], (CoinbaseSmartWallet.Call[])); - uint256 callsLen = calls.length; - - // initialize loop accumulators - uint256 callsSpend = 0; - - // loop over calls to validate native token spend and allowed contracts - // start index at 1 to ignore beforeCalls call, enforced by PermissionManager as self-call - // end index at callsLen - 2 to ignore useRecurringAllowance call, enforced after loop as self-call - for (uint256 i = 1; i < callsLen - 1; i++) { - CoinbaseSmartWallet.Call memory call = calls[i]; - - // require call length at least 4 bytes to mitigate unintentional fallback - if (call.data.length < 4) revert CallErrors.InvalidCallLength(); - - bytes4 selector = bytes4(call.data); - if (selector == IPermissionCallable.permissionedCall.selector) { - // check call target is the allowed contract - if (call.target != values.allowedContract) revert CallErrors.TargetNotAllowed(call.target); - // assume PermissionManager already prevents account as target - } else if (selector == MagicSpend.withdraw.selector) { - // check call target is MagicSpend - if (call.target != magicSpend) revert CallErrors.TargetNotAllowed(call.target); - - // parse MagicSpend withdraw request - MagicSpend.WithdrawRequest memory withdraw = - abi.decode(BytesLib.trimSelector(calls[i].data), (MagicSpend.WithdrawRequest)); - - // check withdraw is native token - if (withdraw.asset != address(0)) revert InvalidWithdrawAsset(withdraw.asset); - // do not need to accrue callsSpend because withdrawn value will be spent in other calls - } else { - revert CallErrors.SelectorNotAllowed(selector); - } - - // accumulate spend value - callsSpend += call.value; - } - - // prepare expected call data for useRecurringAllowance - bytes memory useRecurringAllowanceData = abi.encodeWithSelector( - PermissionCallableAllowedContractNativeTokenRecurringAllowance.useRecurringAllowance.selector, - permissionHash, - callsSpend - ); - - // check last call is valid `this.useRecurringAllowance` - CoinbaseSmartWallet.Call memory lastCall = calls[callsLen - 1]; - if ( - lastCall.target != address(this) || !BytesLib.eq(lastCall.data, useRecurringAllowanceData) - || lastCall.value != 0 - ) { - revert InvalidUseRecurringAllowanceCall(); - } - } -} diff --git a/src/utils/BytesLib.sol b/src/utils/BytesLib.sol deleted file mode 100644 index 094cdb4..0000000 --- a/src/utils/BytesLib.sol +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {LibString} from "solady/utils/LibString.sol"; - -/// @title BytesLib -/// -/// @author Coinbase (https://github.com/coinbase/smart-wallet-permissions) -/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/LibString.sol) -library BytesLib { - /// @notice Trim selector out of call data. - /// - /// @param callData Encoded data for an external contract call. - /// - /// @return args Arguments of the call data, now with selector trimmed out. - function trimSelector(bytes memory callData) internal pure returns (bytes memory args) { - return bytes(LibString.slice(string(callData), 4)); - } - - /// @notice Check equivalence of two bytes variables. - /// - /// @param a Bytes to compare - /// @param b Bytes to compare - /// - /// @return eq True if equivalent. - function eq(bytes memory a, bytes memory b) internal pure returns (bool) { - return LibString.eq(string(a), string(b)); - } -} diff --git a/src/utils/CallErrors.sol b/src/utils/CallErrors.sol deleted file mode 100644 index 5ee8368..0000000 --- a/src/utils/CallErrors.sol +++ /dev/null @@ -1,27 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -/// @title CallErrors -/// -/// @notice Shared errors for validating permissioned user operations. -/// -/// @author Coinbase (https://github.com/coinbase/smart-wallet) -library CallErrors { - /// @notice Call target not allowed. - /// - /// @param target Address target of a call. - error TargetNotAllowed(address target); - - /// @notice Call function selector not allowed. - /// - /// @param selector Function selector of a call. - error SelectorNotAllowed(bytes4 selector); - - /// @notice Call value not allowed. - /// - /// @param value Value of a call. - error ValueNotAllowed(uint256 value); - - /// @notice Call length under 4 bytes. - error InvalidCallLength(); -} diff --git a/src/utils/SignatureCheckerLib.sol b/src/utils/SignatureCheckerLib.sol deleted file mode 100644 index 01fa87e..0000000 --- a/src/utils/SignatureCheckerLib.sol +++ /dev/null @@ -1,64 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {SignatureCheckerLib as EthereumAddressSignatureCheckerLib} from "solady/utils/SignatureCheckerLib.sol"; -import {WebAuthn} from "webauthn-sol/WebAuthn.sol"; - -/// @title SignatureCheckerLib -/// -/// @notice Verify signatures for Ethereum addresses (EOAs, smart contracts) and secp256r1 keys (passkeys, cryptokeys). -/// @notice Forked from official implementation in Coinbase Smart Wallet. -/// -/// @dev Wraps Solady SignatureCheckerLib and Base WebAuthn. -/// -/// @author Coinbase (https://github.com/coinbase/smart-wallet) -library SignatureCheckerLib { - /// @notice Thrown when a provided signer is neither 64 bytes long (for public key) - /// nor a ABI encoded address. - /// - /// @param signer The invalid signer. - error InvalidSignerBytesLength(bytes signer); - - /// @notice Thrown if a provided signer is 32 bytes long but does not fit in an `address` type. - /// - /// @param signer The invalid signer. - error InvalidEthereumAddressSigner(bytes signer); - - /// @notice Verify signatures for Ethereum addresses or P256 public keys. - /// - /// @param hash Arbitrary data to sign over. - /// @param signature Data to verify signer's intent over the `hash`. - /// @param signerBytes The signer, type `address` or `(bytes32, bytes32)` - function isValidSignatureNow(bytes32 hash, bytes memory signature, bytes memory signerBytes) - internal - view - returns (bool) - { - // signer is an Ethereum address (EOA or smart contract) - if (signerBytes.length == 32) { - if (uint256(bytes32(signerBytes)) > type(uint160).max) { - // technically should be impossible given signers can only be added with - // addSignerAddress and addSignerPublicKey, but we leave incase of future changes. - revert InvalidEthereumAddressSigner(signerBytes); - } - - address signer; - assembly ("memory-safe") { - signer := mload(add(signerBytes, 32)) - } - - return EthereumAddressSignatureCheckerLib.isValidSignatureNow(signer, hash, signature); - } - - // signer is a secp256r1 key using WebAuthn - if (signerBytes.length == 64) { - (uint256 x, uint256 y) = abi.decode(signerBytes, (uint256, uint256)); - - WebAuthn.WebAuthnAuth memory auth = abi.decode(signature, (WebAuthn.WebAuthnAuth)); - - return WebAuthn.verify({challenge: abi.encode(hash), requireUV: false, webAuthnAuth: auth, x: x, y: y}); - } - - revert InvalidSignerBytesLength(signerBytes); - } -} diff --git a/src/utils/UserOperationLib.sol b/src/utils/UserOperationLib.sol deleted file mode 100644 index d1ff3ca..0000000 --- a/src/utils/UserOperationLib.sol +++ /dev/null @@ -1,39 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {IEntryPoint} from "account-abstraction/interfaces/IEntryPoint.sol"; -import {UserOperation} from "account-abstraction/interfaces/UserOperation.sol"; - -/// @title UserOperationLib -/// -/// @notice Utilities for user operations on Entrypoint V0.6. -/// -/// @author Coinbase (https://github.com/coinbase/smart-wallet) -library UserOperationLib { - address constant ENTRY_POINT_V06 = 0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789; - - /// @notice Get the userOpHash for a userOp. - /// - /// @dev Hardcoded to use EntryPoint v0.6. - /// - /// @param userOp User operation to hash. - /// - /// @return userOpHash Hash of the user operation. - function getUserOpHash(UserOperation memory userOp) internal view returns (bytes32) { - bytes32 innerHash = keccak256( - abi.encode( - userOp.sender, - userOp.nonce, - keccak256(userOp.initCode), - keccak256(userOp.callData), - userOp.callGasLimit, - userOp.verificationGasLimit, - userOp.preVerificationGas, - userOp.maxFeePerGas, - userOp.maxPriorityFeePerGas, - keccak256(userOp.paymasterAndData) - ) - ); - return keccak256(abi.encode(innerHash, ENTRY_POINT_V06, block.chainid)); - } -} diff --git a/test/README.md b/test/README.md index e9095f6..c4c5950 100644 --- a/test/README.md +++ b/test/README.md @@ -1,70 +1,3 @@ # Tests Overview -## Invariant List - -1. `CoinbaseSmartWallet` - 1. Only transaction path impacted is when `PermissionManager` is used as owner - 1. Existing transaction paths work exactly the same - 1. Can transact using other owners - 1. Can remove `PermissionManager` as an owner - 1. Can add and remove other owners - 1. Can upgrade the contract -1. `PermissionManager` - 1. Can only validate signatures for ERC-4337 User Operations - 1. Only supports the same UserOp type as `CoinbaseSmartWallet` (v0.6) - 1. Cannot make direct calls to `CoinbaseSmartWallet` to prevent updating owners or upgrading implementation. - 1. Only one view call is allowed to `CoinbaseSmartWallet.isValidSignature` to check permission approval. - 1. Permissioned UserOps cannot make direct calls to `CoinbaseSmartWallet` to prevent updating owners or upgrading implementation. - 1. Only returns User Operations as valid when: - 1. `PermissionManager` is not paused - 1. UserOp is signed by Permission signer ("session key") - 1. UserOp is signed by cosigner or pending cosigner (managed by Coinbase) - 1. UserOp paymaster is enabled by `PermissionManager` - 1. UserOp and Permission are validated together by Permission Contract - 1. Permission Contract is enabled by `PermissionManager` - 1. Permission account matches UserOp sender - 1. Permission chain matches current chain - 1. Permission verifyingContract is `PermissionManager` - 1. Permission has not expired - 1. Permission is approved via storage or signature by a different owner on smart wallet - 1. Permission has not been revoked - 1. Only owner can update the owner - 1. Supports a 2-step process where the new owner must explictly acknowledge its ownership - 1. Only owner can update paused status - 1. Only owner can update enabled Permission Contracts - 1. Only owner can update enabled Paymasters - 1. Only owner can rotate cosigners - 1. Requires a 2-step process where the rotation does not accidentally reject in-flight UserOps - 1. Permissions can only be revoked by the account that it applies to - 1. Permissions can be approved via signature or transaction - 1. Via transaction, can only be approved by the account that it applies to or from anyone with a signature approval for that Permission from the account - 1. Via signature, can only be signed by the account itself - 1. Permissions are approved in storage lazily on first use - 1. Permissions can be batch approved and revoked separately and simultaneously - 1. Validations are 100% compliant with [ERC-7562](https://eips.ethereum.org/EIPS/eip-7562) -1. `PermissionCallableAllowedContractNativeTokenRecurringAllowance` - 1. Only allows spending native token up to a recurring allowance - 1. Includes spending on external contract calls - 1. Does not allow sending native token as direct transfer - 1. If UserOp spends X but also receives Y atomically, the spend registered is X independent of what Y is - 1. Requires using a paymaster - 1. Only resets recurring allowance when entering into a new cycle - 1. Allows withdrawing native token from `MagicSpend` - 1. Does not supports use as a paymaster - 1. Supports direct `withdraw` - 1. Withdraws are not accounted as "spending" because they just moving assets from a users offchain account to this onchain account - 1. Withdraws can be made with or without immediate spending in same-operation - 1. Does not allow withdrawing any other tokens (e.g. ERC20/ERC721/ERC1155) - 1. Besides `MagicSpend`, only allows external calls to a single allowed contract signed over by user - 1. Also can only use a single special selector (`permissionedCall(bytes)`) defined by `IPermissionCallable` - 1. Does not allow spending other tokens (e.g. ERC20/ERC721/ERC1155) - 1. Does not allow approving or revoking permissions - 1. Does allow one-time storage approval performed by `PermissionManager` on the used permission hash. - 1. Validations are 100% compliant with [ERC-7562](https://eips.ethereum.org/EIPS/eip-7562) -1. `PermissionCallable` - 1. Inherits `IPermissionCallable` - 1. Requires inheriting contracts to override supported-selectors function - 1. Adds default implementation for special selector which: - 1. Validates inner call selector is supported - 1. Accepts call arguments equivalent to calldata that could be sent as a valid call to the contract directly - 1. Can be overriden by inheriting contracts +## Invariants diff --git a/test/base/NativeTokenRecurringAllowanceBase.sol b/test/base/NativeTokenRecurringAllowanceBase.sol deleted file mode 100644 index e61e0c8..0000000 --- a/test/base/NativeTokenRecurringAllowanceBase.sol +++ /dev/null @@ -1,29 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {Test, console2} from "forge-std/Test.sol"; - -import {NativeTokenRecurringAllowance} from "../../src/mixins/NativeTokenRecurringAllowance.sol"; - -import {MockNativeTokenRecurringAllowance} from "../mocks/MockNativeTokenRecurringAllowance.sol"; - -contract NativeTokenRecurringAllowanceBase { - MockNativeTokenRecurringAllowance mockNativeTokenRecurringAllowance; - - function _initializeNativeTokenRecurringAllowance() internal { - mockNativeTokenRecurringAllowance = new MockNativeTokenRecurringAllowance(); - } - - function _createRecurringAllowance(uint48 start, uint48 period, uint160 allowance) - internal - pure - returns (NativeTokenRecurringAllowance.RecurringAllowance memory) - { - return NativeTokenRecurringAllowance.RecurringAllowance(start, period, allowance); - } - - function _safeAdd(uint48 a, uint48 b) internal pure returns (uint48 c) { - bool overflow = uint256(a) + uint256(b) > type(uint48).max; - return overflow ? type(uint48).max : a + b; - } -} diff --git a/test/base/PermissionCallableAllowedContractNativeTokenRecurringAllowanceBase.sol b/test/base/PermissionCallableAllowedContractNativeTokenRecurringAllowanceBase.sol deleted file mode 100644 index 28e2536..0000000 --- a/test/base/PermissionCallableAllowedContractNativeTokenRecurringAllowanceBase.sol +++ /dev/null @@ -1,91 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {Test, console2} from "forge-std/Test.sol"; -import {MagicSpend} from "magic-spend/MagicSpend.sol"; -import {CoinbaseSmartWallet} from "smart-wallet/CoinbaseSmartWallet.sol"; - -import {IPermissionCallable} from "../../src/interfaces/IPermissionCallable.sol"; -import {NativeTokenRecurringAllowance} from "../../src/mixins/NativeTokenRecurringAllowance.sol"; -import {PermissionCallableAllowedContractNativeTokenRecurringAllowance as PermissionContract} from - "../../src/permissions/PermissionCallableAllowedContractNativeTokenRecurringAllowance.sol"; - -import {NativeTokenRecurringAllowanceBase} from "./NativeTokenRecurringAllowanceBase.sol"; -import {PermissionManagerBase} from "./PermissionManagerBase.sol"; - -contract PermissionCallableAllowedContractNativeTokenRecurringAllowanceBase is - PermissionManagerBase, - NativeTokenRecurringAllowanceBase -{ - PermissionContract permissionContract; - MagicSpend magicSpend; - uint256 constant MAGIC_SPEND_MAX_WITHDRAW_DENOMINATOR = 20; - - function _initializePermissionContract() internal { - _initializePermissionManager(); - - magicSpend = new MagicSpend(owner, MAGIC_SPEND_MAX_WITHDRAW_DENOMINATOR); - permissionContract = new PermissionContract(address(permissionManager), address(magicSpend)); - } - - function _createPermissionValues(uint48 start, uint48 period, uint160 allowance, address allowedContract) - internal - pure - returns (PermissionContract.PermissionValues memory) - { - return PermissionContract.PermissionValues({ - recurringAllowance: _createRecurringAllowance(start, period, allowance), - allowedContract: allowedContract - }); - } - - function _createPermissionValues(uint160 allowance, address allowedContract) - internal - pure - returns (PermissionContract.PermissionValues memory) - { - return PermissionContract.PermissionValues({ - recurringAllowance: _createRecurringAllowance({start: 1, period: type(uint24).max, allowance: allowance}), - allowedContract: allowedContract - }); - } - - function _createPermissionedCall(address target, uint256 value, bytes memory data) - internal - pure - returns (CoinbaseSmartWallet.Call memory) - { - return CoinbaseSmartWallet.Call({ - target: target, - value: value, - data: abi.encodeWithSelector(IPermissionCallable.permissionedCall.selector, data) - }); - } - - function _createUseRecurringAllowanceCall(address target, bytes32 permissionHash, uint256 spend) - internal - pure - returns (CoinbaseSmartWallet.Call memory) - { - return CoinbaseSmartWallet.Call({ - target: target, - value: 0, - data: abi.encodeWithSelector(PermissionContract.useRecurringAllowance.selector, permissionHash, spend) - }); - } - - function _createWithdrawCall(address target, address asset, uint256 amount) - internal - pure - returns (CoinbaseSmartWallet.Call memory) - { - return CoinbaseSmartWallet.Call({ - target: target, - value: 0, - data: abi.encodeWithSelector( - MagicSpend.withdraw.selector, - MagicSpend.WithdrawRequest({signature: hex"", asset: asset, amount: amount, nonce: 0, expiry: 0}) - ) - }); - } -} diff --git a/test/base/PermissionManagerBase.sol b/test/base/PermissionManagerBase.sol deleted file mode 100644 index 71c4d21..0000000 --- a/test/base/PermissionManagerBase.sol +++ /dev/null @@ -1,53 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {UserOperation} from "account-abstraction/interfaces/UserOperation.sol"; -import {Test, console2} from "forge-std/Test.sol"; - -import {PermissionManager} from "../../src/PermissionManager.sol"; - -import {MockPermissionContract} from "../mocks/MockPermissionContract.sol"; -import {Base} from "./Base.sol"; - -contract PermissionManagerBase is Test, Base { - PermissionManager permissionManager; - uint256 cosignerPk = uint256(keccak256("cosigner")); - address cosigner = vm.addr(cosignerPk); - MockPermissionContract successPermissionContract; - MockPermissionContract failPermissionContract; - - function _initializePermissionManager() internal { - _initialize(); - - permissionManager = new PermissionManager(owner, cosigner); - successPermissionContract = new MockPermissionContract(false); - failPermissionContract = new MockPermissionContract(true); - } - - function _createPermission() internal view returns (PermissionManager.Permission memory) { - return PermissionManager.Permission({ - account: address(account), - expiry: type(uint48).max, - signer: abi.encode(permissionSigner), - permissionContract: address(successPermissionContract), - permissionValues: hex"", - approval: hex"" - }); - } - - function _createBeforeCallsData(PermissionManager.Permission memory permission) - internal - pure - returns (bytes memory) - { - return abi.encodeWithSelector(PermissionManager.beforeCalls.selector, permission); - } - - function _signPermission(PermissionManager.Permission memory permission) internal view returns (bytes memory) { - bytes32 permissionHash = permissionManager.hashPermission(permission); - bytes32 replaySafeHash = account.replaySafeHash(permissionHash); - bytes memory signature = _sign(ownerPk, replaySafeHash); - bytes memory wrappedSignature = _applySignatureWrapper({ownerIndex: 0, signatureData: signature}); - return wrappedSignature; - } -} diff --git a/test/base/Static.sol b/test/base/Static.sol index e2e2c8b..4632673 100644 --- a/test/base/Static.sol +++ b/test/base/Static.sol @@ -3,6 +3,6 @@ pragma solidity ^0.8.21; library Static { /// @dev 1-9-2023: cast code 0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789 --rpc-url https://goerli.base.org - bytes constant ENTRY_POINT_BYTES = + bytes constant ENTRY_POINT_V06_BYTES = hex"60806040526004361015610023575b361561001957600080fd5b610021615531565b005b60003560e01c80630396cb60146101b35780630bd28e3b146101aa5780631b2e01b8146101a15780631d732756146101985780631fad948c1461018f578063205c28781461018657806335567e1a1461017d5780634b1d7cf5146101745780635287ce121461016b57806370a08231146101625780638f41ec5a14610159578063957122ab146101505780639b249f6914610147578063a61935311461013e578063b760faf914610135578063bb9fe6bf1461012c578063c23a5cea14610123578063d6383f941461011a578063ee219423146101115763fc7e286d0361000e5761010c611bcd565b61000e565b5061010c6119b5565b5061010c61184d565b5061010c6116b4565b5061010c611536565b5061010c6114f7565b5061010c6114d6565b5061010c611337565b5061010c611164565b5061010c611129565b5061010c6110a4565b5061010c610f54565b5061010c610bf8565b5061010c610b33565b5061010c610994565b5061010c6108ba565b5061010c6106e7565b5061010c610467565b5061010c610385565b5060207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103595760043563ffffffff8116808203610359576103547fa5ae833d0bb1dcd632d98a8b70973e8516812898e19bf27b70071ebc8dc52c01916102716102413373ffffffffffffffffffffffffffffffffffffffff166000526000602052604060002090565b9161024d811515615697565b61026a610261600185015463ffffffff1690565b63ffffffff1690565b11156156fc565b54926103366dffffffffffffffffffffffffffff946102f461029834888460781c166121d5565b966102a4881515615761565b6102b0818911156157c6565b6102d4816102bc6105ec565b941684906dffffffffffffffffffffffffffff169052565b6001602084015287166dffffffffffffffffffffffffffff166040830152565b63ffffffff83166060820152600060808201526103313373ffffffffffffffffffffffffffffffffffffffff166000526000602052604060002090565b61582b565b6040805194855263ffffffff90911660208501523393918291820190565b0390a2005b600080fd5b6024359077ffffffffffffffffffffffffffffffffffffffffffffffff8216820361035957565b50346103595760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103595760043577ffffffffffffffffffffffffffffffffffffffffffffffff81168103610359576104149033600052600160205260406000209077ffffffffffffffffffffffffffffffffffffffffffffffff16600052602052604060002090565b61041e8154612491565b9055005b73ffffffffffffffffffffffffffffffffffffffff81160361035957565b6024359061044d82610422565b565b60c4359061044d82610422565b359061044d82610422565b50346103595760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103595760206104fc6004356104a881610422565b73ffffffffffffffffffffffffffffffffffffffff6104c561035e565b91166000526001835260406000209077ffffffffffffffffffffffffffffffffffffffffffffffff16600052602052604060002090565b54604051908152f35b507f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60a0810190811067ffffffffffffffff82111761055157604052565b610559610505565b604052565b610100810190811067ffffffffffffffff82111761055157604052565b67ffffffffffffffff811161055157604052565b6060810190811067ffffffffffffffff82111761055157604052565b90601f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0910116810190811067ffffffffffffffff82111761055157604052565b6040519061044d82610535565b6040519060c0820182811067ffffffffffffffff82111761055157604052565b604051906040820182811067ffffffffffffffff82111761055157604052565b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f60209267ffffffffffffffff8111610675575b01160190565b61067d610505565b61066f565b92919261068e82610639565b9161069c60405193846105ab565b829481845281830111610359578281602093846000960137010152565b9181601f840112156103595782359167ffffffffffffffff8311610359576020838186019501011161035957565b5034610359576101c07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103595767ffffffffffffffff60043581811161035957366023820112156103595761074a903690602481600401359101610682565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdc36016101808112610359576101006040519161078783610535565b12610359576040516107988161055e565b6107a0610440565b815260443560208201526064356040820152608435606082015260a43560808201526107ca61044f565b60a082015260e43560c08201526101043560e082015281526101243560208201526101443560408201526101643560608201526101843560808201526101a4359182116103595761083e9261082661082e9336906004016106b9565b9290916128b1565b6040519081529081906020820190565b0390f35b9060407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc8301126103595760043567ffffffffffffffff9283821161035957806023830112156103595781600401359384116103595760248460051b830101116103595760240191906024356108b781610422565b90565b5034610359576108c936610842565b6108d4929192611e3a565b6108dd83611d2d565b60005b84811061095d57506000927fbb47ee3e183a558b1a2ff0874b079f3fc5478b7454eacf2bfc5af2ff5878f9728480a183915b85831061092d576109238585611ed7565b6100216001600255565b909193600190610953610941878987611dec565b61094b8886611dca565b51908861233f565b0194019190610912565b8061098b610984610972600194869896611dca565b5161097e848a88611dec565b84613448565b9083612f30565b019290926108e0565b50346103595760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610359576004356109d081610422565b6024359060009133835282602052604083206dffffffffffffffffffffffffffff81541692838311610ad557848373ffffffffffffffffffffffffffffffffffffffff829593610a788496610a3f610a2c8798610ad29c6121c0565b6dffffffffffffffffffffffffffff1690565b6dffffffffffffffffffffffffffff167fffffffffffffffffffffffffffffffffffff0000000000000000000000000000825416179055565b6040805173ffffffffffffffffffffffffffffffffffffffff831681526020810185905233917fd1c19fbcd4551a5edfb66d43d2e337c04837afda3482b42bdf569a8fccdae5fb91a2165af1610acc611ea7565b50615ba2565b80f35b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601960248201527f576974686472617720616d6f756e7420746f6f206c61726765000000000000006044820152fd5b50346103595760407ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610359576020600435610b7181610422565b73ffffffffffffffffffffffffffffffffffffffff610b8e61035e565b911660005260018252610bc98160406000209077ffffffffffffffffffffffffffffffffffffffffffffffff16600052602052604060002090565b547fffffffffffffffffffffffffffffffffffffffffffffffff00000000000000006040519260401b16178152f35b503461035957610c0736610842565b610c0f611e3a565b6000805b838210610df657610c249150611d2d565b7fbb47ee3e183a558b1a2ff0874b079f3fc5478b7454eacf2bfc5af2ff5878f972600080a16000805b848110610d5c57505060008093815b818110610c9357610923868660007f575ff3acadd5ab348fe1855e217e0f3678f8d767d7494c9f9fefbee2e17cca4d8180a2611ed7565b610cf7610ca182848a6124cb565b610ccc610cb3610cb36020840161256d565b73ffffffffffffffffffffffffffffffffffffffff1690565b7f575ff3acadd5ab348fe1855e217e0f3678f8d767d7494c9f9fefbee2e17cca4d600080a280612519565b906000915b808310610d1457505050610d0f90612491565b610c5c565b90919497610d4f610d49610d5592610d438c8b610d3c82610d368e8b8d611dec565b92611dca565b519161233f565b906121d5565b99612491565b95612491565b9190610cfc565b610d678186886124cb565b6020610d7f610d768380612519565b9290930161256d565b9173ffffffffffffffffffffffffffffffffffffffff60009316905b828410610db45750505050610daf90612491565b610c4d565b90919294610d4f81610de985610de2610dd0610dee968d611dca565b51610ddc8c8b8a611dec565b85613448565b908b613148565b612491565b929190610d9b565b610e018285876124cb565b90610e0c8280612519565b92610e1c610cb36020830161256d565b9173ffffffffffffffffffffffffffffffffffffffff8316610e416001821415612577565b610e62575b505050610e5c91610e56916121d5565b91612491565b90610c13565b909592610e7b6040999693999895989788810190611fc8565b92908a3b156103595789938b918a5193849283927fe3563a4f00000000000000000000000000000000000000000000000000000000845260049e8f850193610ec294612711565b03815a93600094fa9081610f3b575b50610f255786517f86a9f75000000000000000000000000000000000000000000000000000000000815273ffffffffffffffffffffffffffffffffffffffff8a16818a0190815281906020010390fd5b0390fd5b9497509295509093509181610e56610e5c610e46565b80610f48610f4e9261057b565b8061111e565b38610ed1565b50346103595760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103595761083e73ffffffffffffffffffffffffffffffffffffffff600435610fa881610422565b608060409283928351610fba81610535565b60009381858093528260208201528287820152826060820152015216815280602052209061104965ffffffffffff6001835194610ff686610535565b80546dffffffffffffffffffffffffffff8082168852607082901c60ff161515602089015260789190911c1685870152015463ffffffff8116606086015260201c16608084019065ffffffffffff169052565b5191829182919091608065ffffffffffff8160a08401956dffffffffffffffffffffffffffff808251168652602082015115156020870152604082015116604086015263ffffffff6060820151166060860152015116910152565b50346103595760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103595773ffffffffffffffffffffffffffffffffffffffff6004356110f581610422565b16600052600060205260206dffffffffffffffffffffffffffff60406000205416604051908152f35b600091031261035957565b50346103595760007ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261035957602060405160018152f35b50346103595760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc36011261035957600467ffffffffffffffff8135818111610359576111b590369084016106b9565b9050602435916111c483610422565b604435908111610359576111db90369085016106b9565b92909115908161132d575b506112c6576014821015611236575b610f21836040519182917f08c379a0000000000000000000000000000000000000000000000000000000008352820160409060208152600060208201520190565b6112466112529261124c92612b88565b90612b96565b60601c90565b3b1561125f5738806111f5565b610f21906040519182917f08c379a0000000000000000000000000000000000000000000000000000000008352820160609060208152601b60208201527f41413330207061796d6173746572206e6f74206465706c6f796564000000000060408201520190565b610f21836040519182917f08c379a0000000000000000000000000000000000000000000000000000000008352820160609060208152601960208201527f41413230206163636f756e74206e6f74206465706c6f7965640000000000000060408201520190565b90503b15386111e6565b50346103595760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103595760043567ffffffffffffffff81116103595761138960249136906004016106b9565b906113bf6040519283927f570e1a3600000000000000000000000000000000000000000000000000000000845260048401612d2c565b0360208273ffffffffffffffffffffffffffffffffffffffff92816000857f0000000000000000000000007fc98430eaedbb6070b35b39d798725049088348165af1918215611471575b600092611441575b50604051917f6ca7b806000000000000000000000000000000000000000000000000000000008352166004820152fd5b61146391925060203d811161146a575b61145b81836105ab565b810190612d17565b9038611411565b503d611451565b611479612183565b611409565b90816101609103126103595790565b60207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc820112610359576004359067ffffffffffffffff8211610359576108b79160040161147e565b50346103595760206114ef6114ea3661148d565b612a0c565b604051908152f35b5060207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103595761002160043561153181610422565b61562b565b5034610359576000807ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126116b1573381528060205260408120600181019063ffffffff825416908115611653576115f06115b5611618936115a76115a2855460ff9060701c1690565b61598f565b65ffffffffffff42166159f4565b84547fffffffffffffffffffffffffffffffffffffffffffff000000000000ffffffff16602082901b69ffffffffffff000000001617909455565b7fffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffff8154169055565b60405165ffffffffffff91909116815233907ffa9b3c14cc825c412c9ed81b3ba365a5b459439403f18829e572ed53a4180f0a90602090a280f35b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600a60248201527f6e6f74207374616b6564000000000000000000000000000000000000000000006044820152fd5b80fd5b50346103595760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc360112610359576004356116f081610422565b610ad273ffffffffffffffffffffffffffffffffffffffff6117323373ffffffffffffffffffffffffffffffffffffffff166000526000602052604060002090565b926117ea611755610a2c86546dffffffffffffffffffffffffffff9060781c1690565b94611761861515615a0e565b6117c26001820161179a65ffffffffffff611786835465ffffffffffff9060201c1690565b16611792811515615a73565b421015615ad8565b80547fffffffffffffffffffffffffffffffffffffffffffff00000000000000000000169055565b7fffffff0000000000000000000000000000ffffffffffffffffffffffffffffff8154169055565b6040805173ffffffffffffffffffffffffffffffffffffffff831681526020810186905233917fb7c918e0e249f999e965cafeb6c664271b3f4317d296461500e71da39f0cbda391a2600080809581948294165af1611847611ea7565b50615b3d565b50346103595760607ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103595767ffffffffffffffff6004358181116103595761189e90369060040161147e565b602435916118ab83610422565b604435908111610359576118c6610f219136906004016106b9565b6118ce611caa565b6118d785612e2b565b6118ea6118e48287613240565b906153ba565b946118fa826000924384526121e2565b96438252819360609573ffffffffffffffffffffffffffffffffffffffff8316611981575b50505050608001519361194e6040611940602084015165ffffffffffff1690565b92015165ffffffffffff1690565b906040519687967f8b7ac980000000000000000000000000000000000000000000000000000000008852600488016127e1565b8395508394965061199b60409492939451809481936127d3565b03925af19060806119aa611ea7565b92919038808061191f565b5034610359576119c43661148d565b6119cc611caa565b6119d582612e2b565b6119df8183613240565b825160a00151919391611a0c9073ffffffffffffffffffffffffffffffffffffffff166154dc565b6154dc565b90611a30611a07855173ffffffffffffffffffffffffffffffffffffffff90511690565b94611a39612b50565b50611a68611a4c60409586810190611fc8565b90600060148310611bc55750611246611a079261124c92612b88565b91611a72916153ba565b805173ffffffffffffffffffffffffffffffffffffffff169073ffffffffffffffffffffffffffffffffffffffff821660018114916080880151978781015191886020820151611ac79065ffffffffffff1690565b91015165ffffffffffff16916060015192611ae06105f9565b9a8b5260208b0152841515898b015265ffffffffffff1660608a015265ffffffffffff16608089015260a088015215159081611bbc575b50611b515750610f2192519485947fe0cff05f00000000000000000000000000000000000000000000000000000000865260048601612cbd565b9190610f2193611b60846154dc565b611b87611b6b610619565b73ffffffffffffffffffffffffffffffffffffffff9096168652565b6020850152519586957ffaecb4e400000000000000000000000000000000000000000000000000000000875260048701612c2b565b90501538611b17565b9150506154dc565b50346103595760207ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc3601126103595773ffffffffffffffffffffffffffffffffffffffff600435611c1e81610422565b16600052600060205260a0604060002065ffffffffffff60018254920154604051926dffffffffffffffffffffffffffff90818116855260ff8160701c161515602086015260781c16604084015263ffffffff8116606084015260201c166080820152f35b60209067ffffffffffffffff8111611c9d575b60051b0190565b611ca5610505565b611c96565b60405190611cb782610535565b604051608083610100830167ffffffffffffffff811184821017611d20575b60405260009283815283602082015283604082015283606082015283838201528360a08201528360c08201528360e082015281528260208201528260408201528260608201520152565b611d28610505565b611cd6565b90611d3782611c83565b611d4460405191826105ab565b8281527fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0611d728294611c83565b019060005b828110611d8357505050565b602090611d8e611caa565b82828501015201611d77565b507f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6020918151811015611ddf575b60051b010190565b611de7611d9a565b611dd7565b9190811015611e2d575b60051b810135907ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffea181360301821215610359570190565b611e35611d9a565b611df6565b6002805414611e495760028055565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152fd5b3d15611ed2573d90611eb882610639565b91611ec660405193846105ab565b82523d6000602084013e565b606090565b73ffffffffffffffffffffffffffffffffffffffff168015611f6a57600080809381935af1611f04611ea7565b5015611f0c57565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f41413931206661696c65642073656e6420746f2062656e6566696369617279006044820152fd5b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f4141393020696e76616c69642062656e656669636961727900000000000000006044820152fd5b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe181360301821215610359570180359067ffffffffffffffff82116103595760200191813603831361035957565b90816020910312610359575190565b601f82602094937fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0938186528686013760008582860101520116010190565b60005b83811061207a5750506000910152565b818101518382015260200161206a565b907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0601f6020936120c681518092818752878088019101612067565b0116010190565b906120e76080916108b796946101c0808652850191612028565b9360e0815173ffffffffffffffffffffffffffffffffffffffff80825116602087015260208201516040870152604082015160608701526060820151858701528482015160a087015260a08201511660c086015260c081015182860152015161010084015260208101516101208401526040810151610140840152606081015161016084015201516101808201526101a081840391015261208a565b506040513d6000823e3d90fd5b507f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b919082039182116121cd57565b61044d612190565b919082018092116121cd57565b905a918160206121fb6060830151936060810190611fc8565b906122348560405195869485947f1d732756000000000000000000000000000000000000000000000000000000008652600486016120cd565b03816000305af16000918161230f575b50612308575060206000803e7fdeaddead000000000000000000000000000000000000000000000000000000006000511461229b5761229561228a6108b7945a906121c0565b6080840151906121d5565b91614afc565b6040517f220266b600000000000000000000000000000000000000000000000000000000815280610f21600482016080906000815260406020820152600f60408201527f41413935206f7574206f6620676173000000000000000000000000000000000060608201520190565b9250505090565b61233191925060203d8111612338575b61232981836105ab565b810190612019565b9038612244565b503d61231f565b909291925a9380602061235b6060830151946060810190611fc8565b906123948660405195869485947f1d732756000000000000000000000000000000000000000000000000000000008652600486016120cd565b03816000305af160009181612471575b5061246a575060206000803e7fdeaddead00000000000000000000000000000000000000000000000000000000600051146123fc576123f66123eb6108b795965a906121c0565b6080830151906121d5565b92614ddf565b610f21836040519182917f220266b600000000000000000000000000000000000000000000000000000000835260048301608091815260406020820152600f60408201527f41413935206f7574206f6620676173000000000000000000000000000000000060608201520190565b9450505050565b61248a91925060203d81116123385761232981836105ab565b90386123a4565b6001907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146124bf570190565b6124c7612190565b0190565b919081101561250c575b60051b810135907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa181360301821215610359570190565b612514611d9a565b6124d5565b9035907fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe181360301821215610359570180359067ffffffffffffffff821161035957602001918160051b3603831361035957565b356108b781610422565b1561257e57565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f4141393620696e76616c69642061676772656761746f720000000000000000006044820152fd5b90357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe18236030181121561035957016020813591019167ffffffffffffffff821161035957813603831361035957565b6108b7916126578161263d8461045c565b73ffffffffffffffffffffffffffffffffffffffff169052565b602082013560208201526126f26126a361268861267760408601866125dc565b610160806040880152860191612028565b61269560608601866125dc565b908583036060870152612028565b6080840135608084015260a084013560a084015260c084013560c084015260e084013560e084015261010080850135908401526101206126e5818601866125dc565b9185840390860152612028565b9161270361014091828101906125dc565b929091818503910152612028565b949391929083604087016040885252606086019360608160051b8801019482600090815b848310612754575050505050508460206108b795968503910152612028565b9091929394977fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa08b820301855288357ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffea1843603018112156127cf57600191846127bd920161262c565b98602090810196950193019190612735565b8280fd5b908092918237016000815290565b9290936108b796959260c0958552602085015265ffffffffffff8092166040850152166060830152151560808201528160a0820152019061208a565b1561282457565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f4141393220696e7465726e616c2063616c6c206f6e6c790000000000000000006044820152fd5b9060406108b79260008152816020820152019061208a565b6040906108b793928152816020820152019061208a565b909291925a936128c230331461281d565b8151946040860151955a6113886060830151890101116129e2576108b7966000958051612909575b50505090612903915a9003608084015101943691610682565b91615047565b612938916129349161292f855173ffffffffffffffffffffffffffffffffffffffff1690565b615c12565b1590565b612944575b80806128ea565b61290392919450612953615c24565b908151612967575b5050600193909161293d565b7f1c4fada7374c0a9ee8841fc38afe82932dc0f8e69012e927f061a8bae611a20173ffffffffffffffffffffffffffffffffffffffff6020870151926129d860206129c6835173ffffffffffffffffffffffffffffffffffffffff1690565b9201519560405193849316968361289a565b0390a3388061295b565b7fdeaddead0000000000000000000000000000000000000000000000000000000060005260206000fd5b612a22612a1c6040830183611fc8565b90615c07565b90612a33612a1c6060830183611fc8565b90612ae9612a48612a1c610120840184611fc8565b60405194859360208501956101008201359260e08301359260c08101359260a08201359260808301359273ffffffffffffffffffffffffffffffffffffffff60208201359135168c9693909a9998959261012098959273ffffffffffffffffffffffffffffffffffffffff6101408a019d168952602089015260408801526060870152608086015260a085015260c084015260e08301526101008201520152565b0391612b1b7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0938481018352826105ab565b51902060408051602081019283523091810191909152466060820152608092830181529091612b4a90826105ab565b51902090565b604051906040820182811067ffffffffffffffff821117612b7b575b60405260006020838281520152565b612b83610505565b612b6c565b906014116103595790601490565b7fffffffffffffffffffffffffffffffffffffffff0000000000000000000000009035818116939260148110612bcb57505050565b60140360031b82901b16169150565b9060c060a06108b793805184526020810151602085015260408101511515604085015265ffffffffffff80606083015116606086015260808201511660808501520151918160a0820152019061208a565b9294612c8c61044d95612c7a610100959998612c68612c54602097610140808c528b0190612bda565b9b878a019060208091805184520151910152565b80516060890152602001516080880152565b805160a08701526020015160c0860152565b73ffffffffffffffffffffffffffffffffffffffff81511660e0850152015191019060208091805184520151910152565b612d0661044d94612cf4612cdf60a0959998969960e0865260e0860190612bda565b98602085019060208091805184520151910152565b80516060840152602001516080830152565b019060208091805184520151910152565b9081602091031261035957516108b781610422565b9160206108b7938181520191612028565b90612d6c73ffffffffffffffffffffffffffffffffffffffff916108b797959694606085526060850191612028565b941660208201526040818503910152612028565b60009060033d11612d8d57565b905060046000803e60005160e01c90565b600060443d106108b7576040517ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc91823d016004833e815167ffffffffffffffff918282113d602484011117612e1a57818401948551938411612e22573d85010160208487010111612e1a57506108b7929101602001906105ab565b949350505050565b50949350505050565b612e386040820182611fc8565b612e50612e448461256d565b93610120810190611fc8565b9290303b1561035957600093612e949160405196879586957f957122ab00000000000000000000000000000000000000000000000000000000875260048701612d3d565b0381305afa9081612f1d575b5061044d576001612eaf612d80565b6308c379a014612ec8575b612ec057565b61044d612183565b612ed0612d9e565b80612edc575b50612eba565b80516000925015612ed657610f21906040519182917f220266b600000000000000000000000000000000000000000000000000000000835260048301612882565b80610f48612f2a9261057b565b38612ea0565b9190612f3b9061317f565b73ffffffffffffffffffffffffffffffffffffffff929183166130da5761306c57612f659061317f565b9116612ffe57612f725750565b604080517f220266b600000000000000000000000000000000000000000000000000000000815260048101929092526024820152602160448201527f41413332207061796d61737465722065787069726564206f72206e6f7420647560648201527f6500000000000000000000000000000000000000000000000000000000000000608482015260a490fd5b610f21826040519182917f220266b600000000000000000000000000000000000000000000000000000000835260048301608091815260406020820152601460408201527f41413334207369676e6174757265206572726f7200000000000000000000000060608201520190565b610f21836040519182917f220266b600000000000000000000000000000000000000000000000000000000835260048301608091815260406020820152601760408201527f414132322065787069726564206f72206e6f742064756500000000000000000060608201520190565b610f21846040519182917f220266b600000000000000000000000000000000000000000000000000000000835260048301608091815260406020820152601460408201527f41413234207369676e6174757265206572726f7200000000000000000000000060608201520190565b9291906131549061317f565b909273ffffffffffffffffffffffffffffffffffffffff808095169116036130da5761306c57612f65905b80156131d25761318e9061535f565b73ffffffffffffffffffffffffffffffffffffffff65ffffffffffff8060408401511642119081156131c2575b5091511691565b90506020830151164210386131bb565b50600090600090565b156131e257565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f41413934206761732076616c756573206f766572666c6f7700000000000000006044820152fd5b916000915a9381519061325382826136b3565b61325c81612a0c565b602084015261329a6effffffffffffffffffffffffffffff60808401516060850151176040850151176101008401359060e0850135171711156131db565b6132a382613775565b6132ae818584613836565b97906132df6129346132d4875173ffffffffffffffffffffffffffffffffffffffff1690565b60208801519061546c565b6133db576132ec43600052565b73ffffffffffffffffffffffffffffffffffffffff61332460a0606097015173ffffffffffffffffffffffffffffffffffffffff1690565b166133c1575b505a810360a0840135106133545760809360c092604087015260608601525a900391013501910152565b6040517f220266b600000000000000000000000000000000000000000000000000000000815280610f21600482016080906000815260406020820152601e60408201527f41413430206f76657220766572696669636174696f6e4761734c696d6974000060608201520190565b909350816133d2929750858461455c565b9590923861332a565b6040517f220266b600000000000000000000000000000000000000000000000000000000815280610f21600482016080906000815260406020820152601a60408201527f4141323520696e76616c6964206163636f756e74206e6f6e636500000000000060608201520190565b9290916000925a825161345b81846136b3565b61346483612a0c565b60208501526134a26effffffffffffffffffffffffffffff60808301516060840151176040840151176101008601359060e0870135171711156131db565b6134ab81613775565b6134b78186868b613ba2565b98906134e86129346134dd865173ffffffffffffffffffffffffffffffffffffffff1690565b60208701519061546c565b6135e0576134f543600052565b73ffffffffffffffffffffffffffffffffffffffff61352d60a0606096015173ffffffffffffffffffffffffffffffffffffffff1690565b166135c5575b505a840360a08601351061355f5750604085015260608401526080919060c0905a900391013501910152565b604080517f220266b600000000000000000000000000000000000000000000000000000000815260048101929092526024820152601e60448201527f41413430206f76657220766572696669636174696f6e4761734c696d697400006064820152608490fd5b909250816135d79298508686856147ef565b96909138613533565b610f21826040519182917f220266b600000000000000000000000000000000000000000000000000000000835260048301608091815260406020820152601a60408201527f4141323520696e76616c6964206163636f756e74206e6f6e636500000000000060608201520190565b1561365557565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f4141393320696e76616c6964207061796d6173746572416e64446174610000006044820152fd5b613725906136dd6136c38261256d565b73ffffffffffffffffffffffffffffffffffffffff168452565b602081013560208401526080810135604084015260a0810135606084015260c0810135608084015260e081013560c084015261010081013560e0840152610120810190611fc8565b90811561376a5761374f61124c6112468460a09461374a601461044d9998101561364e565b612b88565b73ffffffffffffffffffffffffffffffffffffffff16910152565b505060a06000910152565b60a081015173ffffffffffffffffffffffffffffffffffffffff16156137b75760c060035b60ff60408401519116606084015102016080830151019101510290565b60c0600161379a565b6137d86040929594939560608352606083019061262c565b9460208201520152565b9061044d602f60405180947f414132332072657665727465643a20000000000000000000000000000000000060208301526138268151809260208686019101612067565b810103600f8101855201836105ab565b916000926000925a936139046020835193613865855173ffffffffffffffffffffffffffffffffffffffff1690565b9561387d6138766040830183611fc8565b9084613e0d565b60a086015173ffffffffffffffffffffffffffffffffffffffff16906138a243600052565b85809373ffffffffffffffffffffffffffffffffffffffff809416159889613b3a575b60600151908601516040517f3a871cdd0000000000000000000000000000000000000000000000000000000081529788968795869390600485016137c0565b03938a1690f1829181613b1a575b50613b115750600190613923612d80565b6308c379a014613abd575b50613a50575b613941575b50505a900391565b61396b9073ffffffffffffffffffffffffffffffffffffffff166000526000602052604060002090565b613986610a2c82546dffffffffffffffffffffffffffff1690565b8083116139e3576139dc926dffffffffffffffffffffffffffff9103166dffffffffffffffffffffffffffff167fffffffffffffffffffffffffffffffffffff0000000000000000000000000000825416179055565b3880613939565b6040517f220266b600000000000000000000000000000000000000000000000000000000815280610f21600482016080906000815260406020820152601760408201527f41413231206469646e2774207061792070726566756e6400000000000000000060608201520190565b6040517f220266b600000000000000000000000000000000000000000000000000000000815280610f21600482016080906000815260406020820152601660408201527f4141323320726576657274656420286f72204f4f47290000000000000000000060608201520190565b613ac5612d9e565b9081613ad1575061392e565b610f2191613adf91506137e2565b6040519182917f220266b600000000000000000000000000000000000000000000000000000000835260048301612882565b95506139349050565b613b3391925060203d81116123385761232981836105ab565b9038613912565b9450613b80610a2c613b6c8c73ffffffffffffffffffffffffffffffffffffffff166000526000602052604060002090565b546dffffffffffffffffffffffffffff1690565b8b811115613b975750856060835b969150506138c5565b606087918d03613b8e565b90926000936000935a94613beb6020835193613bd2855173ffffffffffffffffffffffffffffffffffffffff1690565b9561387d613be36040830183611fc8565b90848c61412b565b03938a1690f1829181613ded575b50613de45750600190613c0a612d80565b6308c379a014613d8e575b50613d20575b613c29575b5050505a900391565b613c539073ffffffffffffffffffffffffffffffffffffffff166000526000602052604060002090565b91613c6f610a2c84546dffffffffffffffffffffffffffff1690565b90818311613cba575082547fffffffffffffffffffffffffffffffffffff0000000000000000000000000000169190036dffffffffffffffffffffffffffff16179055388080613c20565b604080517f220266b600000000000000000000000000000000000000000000000000000000815260048101929092526024820152601760448201527f41413231206469646e2774207061792070726566756e640000000000000000006064820152608490fd5b610f21846040519182917f220266b600000000000000000000000000000000000000000000000000000000835260048301608091815260406020820152601660408201527f4141323320726576657274656420286f72204f4f47290000000000000000000060608201520190565b613d96612d9e565b9081613da25750613c15565b8691613dae91506137e2565b90610f216040519283927f220266b60000000000000000000000000000000000000000000000000000000084526004840161289a565b9650613c1b9050565b613e0691925060203d81116123385761232981836105ab565b9038613bf9565b909180613e1957505050565b81515173ffffffffffffffffffffffffffffffffffffffff1692833b6140be57606083510151604051907f570e1a3600000000000000000000000000000000000000000000000000000000825260208280613e78878760048401612d2c565b0381600073ffffffffffffffffffffffffffffffffffffffff95867f0000000000000000000000007fc98430eaedbb6070b35b39d7987250490883481690f19182156140b1575b600092614091575b508082169586156140245716809503613fb7573b15613f4a5761124c6112467fd51a9c61267aa6196961883ecf5ff2da6619c37dac0fa92122513fb32c032d2d93613f1193612b88565b602083810151935160a001516040805173ffffffffffffffffffffffffffffffffffffffff9485168152939091169183019190915290a3565b6040517f220266b600000000000000000000000000000000000000000000000000000000815280610f21600482016080906000815260406020820152602060408201527f4141313520696e6974436f6465206d757374206372656174652073656e64657260608201520190565b6040517f220266b600000000000000000000000000000000000000000000000000000000815280610f21600482016080906000815260406020820152602060408201527f4141313420696e6974436f6465206d7573742072657475726e2073656e64657260608201520190565b6040517f220266b600000000000000000000000000000000000000000000000000000000815280610f21600482016080906000815260406020820152601b60408201527f4141313320696e6974436f6465206661696c6564206f72204f4f47000000000060608201520190565b6140aa91925060203d811161146a5761145b81836105ab565b9038613ec7565b6140b9612183565b613ebf565b6040517f220266b600000000000000000000000000000000000000000000000000000000815280610f21600482016080906000815260406020820152601f60408201527f414131302073656e64657220616c726561647920636f6e73747275637465640060608201520190565b9290918161413a575b50505050565b82515173ffffffffffffffffffffffffffffffffffffffff1693843b6143e257606084510151604051907f570e1a3600000000000000000000000000000000000000000000000000000000825260208280614199888860048401612d2c565b0381600073ffffffffffffffffffffffffffffffffffffffff95867f0000000000000000000000007fc98430eaedbb6070b35b39d7987250490883481690f19182156143d5575b6000926143b5575b5080821696871561434757168096036142d9573b15614273575061124c6112467fd51a9c61267aa6196961883ecf5ff2da6619c37dac0fa92122513fb32c032d2d9361423393612b88565b602083810151935160a001516040805173ffffffffffffffffffffffffffffffffffffffff9485168152939091169183019190915290a338808080614134565b604080517f220266b600000000000000000000000000000000000000000000000000000000815260048101929092526024820152602060448201527f4141313520696e6974436f6465206d757374206372656174652073656e6465726064820152608490fd5b610f21826040519182917f220266b600000000000000000000000000000000000000000000000000000000835260048301608091815260406020820152602060408201527f4141313420696e6974436f6465206d7573742072657475726e2073656e64657260608201520190565b610f21846040519182917f220266b600000000000000000000000000000000000000000000000000000000835260048301608091815260406020820152601b60408201527f4141313320696e6974436f6465206661696c6564206f72204f4f47000000000060608201520190565b6143ce91925060203d811161146a5761145b81836105ab565b90386141e8565b6143dd612183565b6141e0565b604080517f220266b600000000000000000000000000000000000000000000000000000000815260048101929092526024820152601f60448201527f414131302073656e64657220616c726561647920636f6e7374727563746564006064820152608490fd5b1561444f57565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601f60248201527f4141343120746f6f206c6974746c6520766572696669636174696f6e476173006044820152fd5b919060408382031261035957825167ffffffffffffffff81116103595783019080601f83011215610359578151916144e483610639565b916144f260405193846105ab565b838352602084830101116103595760209261451291848085019101612067565b92015190565b9061044d602f60405180947f414133332072657665727465643a20000000000000000000000000000000000060208301526138268151809260208686019101612067565b93919260609460009460009380519261459b60a08a86015195614580888811614448565b015173ffffffffffffffffffffffffffffffffffffffff1690565b916145c68373ffffffffffffffffffffffffffffffffffffffff166000526000602052604060002090565b946145e2610a2c87546dffffffffffffffffffffffffffff1690565b968588106147825773ffffffffffffffffffffffffffffffffffffffff60208a98946146588a966dffffffffffffffffffffffffffff8b6146919e03166dffffffffffffffffffffffffffff167fffffffffffffffffffffffffffffffffffff0000000000000000000000000000825416179055565b015194604051998a98899788937ff465c77e000000000000000000000000000000000000000000000000000000008552600485016137c0565b0395169103f190818391849361475c575b506147555750506001906146b4612d80565b6308c379a014614733575b506146c657565b6040517f220266b600000000000000000000000000000000000000000000000000000000815280610f21600482016080906000815260406020820152601660408201527f4141333320726576657274656420286f72204f4f47290000000000000000000060608201520190565b61473b612d9e565b908161474757506146bf565b610f2191613adf9150614518565b9450925050565b90925061477b91503d8085833e61477381836105ab565b8101906144ad565b91386146a2565b6040517f220266b600000000000000000000000000000000000000000000000000000000815280610f21600482016080906000815260406020820152601e60408201527f41413331207061796d6173746572206465706f73697420746f6f206c6f77000060608201520190565b91949293909360609560009560009382519061481660a08b84015193614580848611614448565b936148418573ffffffffffffffffffffffffffffffffffffffff166000526000602052604060002090565b61485c610a2c82546dffffffffffffffffffffffffffff1690565b8781106149b7579273ffffffffffffffffffffffffffffffffffffffff60208a989693946146588a966dffffffffffffffffffffffffffff8d6148d69e9c9a03166dffffffffffffffffffffffffffff167fffffffffffffffffffffffffffffffffffff0000000000000000000000000000825416179055565b0395169103f1908183918493614999575b506149915750506001906148f9612d80565b6308c379a014614972575b5061490c5750565b604080517f220266b600000000000000000000000000000000000000000000000000000000815260048101929092526024820152601660448201527f4141333320726576657274656420286f72204f4f4729000000000000000000006064820152608490fd5b61497a612d9e565b90816149865750614904565b613dae925050614518565b955093505050565b9092506149b091503d8085833e61477381836105ab565b91386148e7565b610f218a6040519182917f220266b600000000000000000000000000000000000000000000000000000000835260048301608091815260406020820152601e60408201527f41413331207061796d6173746572206465706f73697420746f6f206c6f77000060608201520190565b60031115614a2f57565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602160045260246000fd5b929190614a7c6040916002865260606020870152606086019061208a565b930152565b939291906003811015614a2f57604091614a7c91865260606020870152606086019061208a565b9061044d603660405180947f4141353020706f73744f702072657665727465643a20000000000000000000006020830152614aec8151809260208686019101612067565b81010360168101855201836105ab565b929190925a93600091805191614b1183615318565b9260a0810195614b35875173ffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff93908481169081614ca457505050614b76825173ffffffffffffffffffffffffffffffffffffffff1690565b985b5a90030193840297604084019089825110614c37577f49628fd1471006c1482da88028e9ce4dbb080b815c9b0344d39e5a8e6ec1419f94614bc26020928c614c329551039061553a565b015194896020614c04614be9865173ffffffffffffffffffffffffffffffffffffffff1690565b9a5173ffffffffffffffffffffffffffffffffffffffff1690565b9401519785604051968796169a16988590949392606092608083019683521515602083015260408201520152565b0390a4565b6040517f220266b600000000000000000000000000000000000000000000000000000000815280610f21600482016080906000815260406020820152602060408201527f414135312070726566756e642062656c6f772061637475616c476173436f737460608201520190565b9a918051614cb4575b5050614b78565b6060850151600099509091803b15614ddb579189918983614d07956040518097819682957fa9a234090000000000000000000000000000000000000000000000000000000084528c029060048401614a5e565b0393f19081614dc8575b50614dc3576001614d20612d80565b6308c379a014614da4575b614d37575b3880614cad565b6040517f220266b600000000000000000000000000000000000000000000000000000000815280610f21600482016080906000815260406020820152601260408201527f4141353020706f73744f7020726576657274000000000000000000000000000060608201520190565b614dac612d9e565b80614db75750614d2b565b613adf610f2191614aa8565b614d30565b80610f48614dd59261057b565b38614d11565b8980fd5b9392915a90600092805190614df382615318565b9360a0830196614e17885173ffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff95908681169081614f0d57505050614e58845173ffffffffffffffffffffffffffffffffffffffff1690565b915b5a9003019485029860408301908a825110614ea757507f49628fd1471006c1482da88028e9ce4dbb080b815c9b0344d39e5a8e6ec1419f949392614bc2614c32938c60209451039061553a565b604080517f220266b600000000000000000000000000000000000000000000000000000000815260048101929092526024820152602060448201527f414135312070726566756e642062656c6f772061637475616c476173436f73746064820152608490fd5b93918051614f1d575b5050614e5a565b606087015160009a509091803b1561504357918a918a83614f70956040518097819682957fa9a234090000000000000000000000000000000000000000000000000000000084528c029060048401614a5e565b0393f19081615030575b5061502b576001614f89612d80565b6308c379a01461500e575b614fa0575b3880614f16565b610f218b6040519182917f220266b600000000000000000000000000000000000000000000000000000000835260048301608091815260406020820152601260408201527f4141353020706f73744f7020726576657274000000000000000000000000000060608201520190565b615016612d9e565b806150215750614f94565b613dae8d91614aa8565b614f99565b80610f4861503d9261057b565b38614f7a565b8a80fd5b909392915a9480519161505983615318565b9260a081019561507d875173ffffffffffffffffffffffffffffffffffffffff1690565b73ffffffffffffffffffffffffffffffffffffffff938185169182615165575050506150bd825173ffffffffffffffffffffffffffffffffffffffff1690565b985b5a90030193840297604084019089825110614c37577f49628fd1471006c1482da88028e9ce4dbb080b815c9b0344d39e5a8e6ec1419f946151096020928c614c329551039061553a565b61511288614a25565b015194896020615139614be9865173ffffffffffffffffffffffffffffffffffffffff1690565b940151604080519182529815602082015297880152606087015290821695909116939081906080820190565b9a918151615175575b50506150bf565b8784026151818a614a25565b60028a1461520c576060860151823b15610359576151d493600080948d604051978896879586937fa9a2340900000000000000000000000000000000000000000000000000000000855260048501614a81565b0393f180156151ff575b6151ec575b505b388061516e565b80610f486151f99261057b565b386151e3565b615207612183565b6151de565b6060860151823b156103595761525793600080948d604051978896879586937fa9a2340900000000000000000000000000000000000000000000000000000000855260048501614a81565b0393f19081615305575b50615300576001615270612d80565b6308c379a0146152ed575b156151e5576040517f220266b600000000000000000000000000000000000000000000000000000000815280610f21600482016080906000815260406020820152601260408201527f4141353020706f73744f7020726576657274000000000000000000000000000060608201520190565b6152f5612d9e565b80614db7575061527b565b6151e5565b80610f486153129261057b565b38615261565b60e060c082015191015180821461533c57480180821015615337575090565b905090565b5090565b6040519061534d8261058f565b60006040838281528260208201520152565b615367615340565b5065ffffffffffff808260a01c1680156153b3575b604051926153898461058f565b73ffffffffffffffffffffffffffffffffffffffff8116845260d01c602084015216604082015290565b508061537c565b6153cf6153d5916153c9615340565b5061535f565b9161535f565b9073ffffffffffffffffffffffffffffffffffffffff9182825116928315615461575b65ffffffffffff928391826040816020850151169301511693836040816020840151169201511690808410615459575b50808511615451575b506040519561543f8761058f565b16855216602084015216604082015290565b935038615431565b925038615428565b8151811693506153f8565b73ffffffffffffffffffffffffffffffffffffffff16600052600160205267ffffffffffffffff6154c88260401c60406000209077ffffffffffffffffffffffffffffffffffffffffffffffff16600052602052604060002090565b918254926154d584612491565b9055161490565b9073ffffffffffffffffffffffffffffffffffffffff6154fa612b50565b9216600052600060205263ffffffff600160406000206dffffffffffffffffffffffffffff815460781c1685520154166020830152565b61044d3361562b565b73ffffffffffffffffffffffffffffffffffffffff16600052600060205260406000206dffffffffffffffffffffffffffff8082541692830180931161561e575b8083116155c05761044d92166dffffffffffffffffffffffffffff167fffffffffffffffffffffffffffffffffffff0000000000000000000000000000825416179055565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601060248201527f6465706f736974206f766572666c6f77000000000000000000000000000000006044820152fd5b615626612190565b61557b565b73ffffffffffffffffffffffffffffffffffffffff9061564b348261553a565b168060005260006020527f2da466a7b24304f47e87fa2e1e5a81b9831ce54fec19055ce277ca2f39ba42c460206dffffffffffffffffffffffffffff60406000205416604051908152a2565b1561569e57565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f6d757374207370656369667920756e7374616b652064656c61790000000000006044820152fd5b1561570357565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601c60248201527f63616e6e6f7420646563726561736520756e7374616b652074696d65000000006044820152fd5b1561576857565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601260248201527f6e6f207374616b652073706563696669656400000000000000000000000000006044820152fd5b156157cd57565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152600e60248201527f7374616b65206f766572666c6f770000000000000000000000000000000000006044820152fd5b9065ffffffffffff6080600161044d9461588b6dffffffffffffffffffffffffffff86511682906dffffffffffffffffffffffffffff167fffffffffffffffffffffffffffffffffffff0000000000000000000000000000825416179055565b602085015115156eff000000000000000000000000000082549160701b16807fffffffffffffffffffffffffffffffffff00ffffffffffffffffffffffffffff83161783557fffffff000000000000000000000000000000ffffffffffffffffffffffffffff7cffffffffffffffffffffffffffff000000000000000000000000000000604089015160781b16921617178155019263ffffffff6060820151167fffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000008554161784550151167fffffffffffffffffffffffffffffffffffffffffffff000000000000ffffffff69ffffffffffff0000000083549260201b169116179055565b1561599657565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601160248201527f616c726561647920756e7374616b696e670000000000000000000000000000006044820152fd5b91909165ffffffffffff808094169116019182116121cd57565b15615a1557565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601460248201527f4e6f207374616b6520746f2077697468647261770000000000000000000000006044820152fd5b15615a7a57565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601d60248201527f6d7573742063616c6c20756e6c6f636b5374616b6528292066697273740000006044820152fd5b15615adf57565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601b60248201527f5374616b65207769746864726177616c206973206e6f742064756500000000006044820152fd5b15615b4457565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601860248201527f6661696c656420746f207769746864726177207374616b6500000000000000006044820152fd5b15615ba957565b60646040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601260248201527f6661696c656420746f20776974686472617700000000000000000000000000006044820152fd5b816040519182372090565b9060009283809360208451940192f190565b3d610800808211615c4b575b50604051906020818301016040528082526000602083013e90565b905038615c3056fea2646970667358221220a706d8b02d7086d80e9330811f5af84b2614abdc5e9a1f2260126070a31d7cee64736f6c63430008110033"; } diff --git a/test/mocks/MockNativeTokenRecurringAllowance.sol b/test/mocks/MockNativeTokenRecurringAllowance.sol deleted file mode 100644 index 772fe59..0000000 --- a/test/mocks/MockNativeTokenRecurringAllowance.sol +++ /dev/null @@ -1,18 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {NativeTokenRecurringAllowance} from "../../src/mixins/NativeTokenRecurringAllowance.sol"; - -contract MockNativeTokenRecurringAllowance is NativeTokenRecurringAllowance { - function initializeRecurringAllowance( - address account, - bytes32 permissionHash, - RecurringAllowance memory recurringAlowance - ) public { - _initializeRecurringAllowance(account, permissionHash, recurringAlowance); - } - - function useRecurringAllowance(address account, bytes32 permissionHash, uint256 spend) public { - _useRecurringAllowance(account, permissionHash, spend); - } -} diff --git a/test/mocks/MockPermissionCallable.sol b/test/mocks/MockPermissionCallable.sol deleted file mode 100644 index fad58e9..0000000 --- a/test/mocks/MockPermissionCallable.sol +++ /dev/null @@ -1,33 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {PermissionCallable} from "../../src/mixins/PermissionCallable.sol"; - -contract MockPermissionCallable is PermissionCallable { - function notPermissionCallable() external pure {} - - function revertNoData() external pure { - revert(); - } - - function revertWithData(string memory data) external pure returns (bytes memory) { - revert(data); - } - - function successNoData() external pure { - return; - } - - function successWithData(bytes memory data) external pure returns (bytes memory) { - return data; - } - - function supportsPermissionedCallSelector(bytes4 selector) public pure override returns (bool) { - return ( - selector == MockPermissionCallable.revertNoData.selector - || selector == MockPermissionCallable.revertWithData.selector - || selector == MockPermissionCallable.successNoData.selector - || selector == MockPermissionCallable.successWithData.selector - ); - } -} diff --git a/test/mocks/MockPermissionContract.sol b/test/mocks/MockPermissionContract.sol deleted file mode 100644 index 63ba69e..0000000 --- a/test/mocks/MockPermissionContract.sol +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {IPermissionContract} from "../../src/interfaces/IPermissionContract.sol"; -import {UserOperation} from "account-abstraction/interfaces/UserOperation.sol"; - -contract MockPermissionContract is IPermissionContract { - bool public immutable alwaysRevert; - - constructor(bool arg) { - alwaysRevert = arg; - } - - function validatePermission( - bytes32, /*permissionHash*/ - bytes calldata, /*permissionValues*/ - UserOperation calldata /*userOp*/ - ) external view { - if (alwaysRevert) revert(); - } - - function initializePermission(address, /*account*/ bytes32, /*permissionHash*/ bytes calldata /*permissionValues*/ ) - external - {} -} diff --git a/test/src/PermissionManager/ApprovePermission.t.sol b/test/src/PermissionManager/ApprovePermission.t.sol deleted file mode 100644 index a101f5d..0000000 --- a/test/src/PermissionManager/ApprovePermission.t.sol +++ /dev/null @@ -1,98 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {Test, console2} from "forge-std/Test.sol"; - -import {PermissionManager} from "../../../src/PermissionManager.sol"; - -import {PermissionManagerBase} from "../../base/PermissionManagerBase.sol"; - -contract ApprovePermissionTest is Test, PermissionManagerBase { - function setUp() public { - _initializePermissionManager(); - } - - function test_approvePermission_revert_notSenderOrSigned(address sender) public { - vm.assume(sender != address(account)); - - PermissionManager.Permission memory permission = _createPermission(); - bytes32 permissionHash = permissionManager.hashPermission(permission); - bytes32 replaySafeHash = permissionHash; // invalid hash, should be account.replaySafeHash(permissionHash) - - (uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerPk, replaySafeHash); - bytes memory signature = abi.encodePacked(r, s, v); - - bytes memory approval = account.wrapSignature(0, signature); - - permission.approval = approval; - - vm.startPrank(sender); - vm.expectRevert(abi.encodeWithSelector(PermissionManager.UnauthorizedPermission.selector)); - permissionManager.approvePermission(permission); - - vm.assertEq(permissionManager.isPermissionAuthorized(permission), false); - } - - function test_approvePermission_success_senderIsAccount() public { - PermissionManager.Permission memory permission = _createPermission(); - bytes32 permissionHash = permissionManager.hashPermission(permission); - - vm.prank(address(account)); - permissionManager.approvePermission(permission); - - vm.assertEq(permissionManager.isPermissionAuthorized(permission), true); - permission.approval = hex""; - vm.assertEq(permissionManager.isPermissionAuthorized(permission), true); - } - - function test_approvePermission_success_emitsEvent() public { - PermissionManager.Permission memory permission = _createPermission(); - bytes32 permissionHash = permissionManager.hashPermission(permission); - - vm.startPrank(address(account)); - vm.expectEmit(address(permissionManager)); - emit PermissionManager.PermissionApproved(address(account), permissionHash); - permissionManager.approvePermission(permission); - } - - function test_approvePermission_success_validApprovalSignature(address sender) public { - vm.assume(sender != address(account)); - - PermissionManager.Permission memory permission = _createPermission(); - bytes32 permissionHash = permissionManager.hashPermission(permission); - bytes32 replaySafeHash = account.replaySafeHash(permissionHash); - - (uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerPk, replaySafeHash); - bytes memory signature = abi.encodePacked(r, s, v); - - bytes memory approval = account.wrapSignature(0, signature); - - permission.approval = approval; - - vm.startPrank(sender); - vm.expectEmit(address(permissionManager)); - emit PermissionManager.PermissionApproved(address(account), permissionHash); - permissionManager.approvePermission(permission); - - vm.assertEq(permissionManager.isPermissionAuthorized(permission), true); - permission.approval = hex""; - vm.assertEq(permissionManager.isPermissionAuthorized(permission), true); - } - - function test_approvePermission_success_replay() public { - PermissionManager.Permission memory permission = _createPermission(); - bytes32 permissionHash = permissionManager.hashPermission(permission); - - vm.startPrank(address(account)); - vm.expectEmit(address(permissionManager)); - emit PermissionManager.PermissionApproved(address(account), permissionHash); - permissionManager.approvePermission(permission); - - vm.assertEq(permissionManager.isPermissionAuthorized(permission), true); - permission.approval = hex""; - vm.assertEq(permissionManager.isPermissionAuthorized(permission), true); - - // no revert on replay approval - permissionManager.approvePermission(permission); - } -} diff --git a/test/src/PermissionManager/BeforeCalls.t.sol b/test/src/PermissionManager/BeforeCalls.t.sol deleted file mode 100644 index c29fe8e..0000000 --- a/test/src/PermissionManager/BeforeCalls.t.sol +++ /dev/null @@ -1,152 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {Test, console2} from "forge-std/Test.sol"; -import {Pausable} from "openzeppelin-contracts/contracts/utils/Pausable.sol"; - -import {PermissionManager} from "../../../src/PermissionManager.sol"; - -import {PermissionManagerBase} from "../../base/PermissionManagerBase.sol"; - -contract BeforeCallsTest is Test, PermissionManagerBase { - function setUp() public { - _initializePermissionManager(); - } - - function test_beforeCalls_revert_paused() public { - PermissionManager.Permission memory permission = _createPermission(); - - vm.prank(owner); - permissionManager.pause(); - - vm.expectRevert(abi.encodeWithSelector(Pausable.EnforcedPause.selector)); - permissionManager.beforeCalls(permission); - } - - function test_beforeCalls_revert_expired(uint48 expiry) public { - vm.assume(expiry < type(uint48).max); - - PermissionManager.Permission memory permission = _createPermission(); - permission.expiry = expiry; - - vm.warp(expiry + 1); - vm.expectRevert(abi.encodeWithSelector(PermissionManager.ExpiredPermission.selector, expiry)); - permissionManager.beforeCalls(permission); - } - - function test_beforeCalls_revert_disabledPermissionContract(address permissionContract) public { - PermissionManager.Permission memory permission = _createPermission(); - - permission.permissionContract = permissionContract; - - vm.prank(owner); - permissionManager.setPermissionContractEnabled(permissionContract, false); - - vm.expectRevert( - abi.encodeWithSelector(PermissionManager.DisabledPermissionContract.selector, permissionContract) - ); - permissionManager.beforeCalls(permission); - } - - function test_beforeCalls_revert_unauthorizedPermission() public { - PermissionManager.Permission memory permission = _createPermission(); - permission.approval = hex""; - - vm.startPrank(owner); - permissionManager.setPermissionContractEnabled(permission.permissionContract, true); - - vm.expectRevert(abi.encodeWithSelector(PermissionManager.UnauthorizedPermission.selector)); - permissionManager.beforeCalls(permission); - } - - function test_beforeCalls_success_senderIsAccount() public { - PermissionManager.Permission memory permission = _createPermission(); - - vm.startPrank(owner); - permissionManager.setPermissionContractEnabled(permission.permissionContract, true); - vm.stopPrank(); - - vm.prank(permission.account); - permissionManager.beforeCalls(permission); - - vm.assertEq(permissionManager.isPermissionAuthorized(permission), true); - permission.approval = hex""; - vm.assertEq(permissionManager.isPermissionAuthorized(permission), true); - } - - function test_beforeCalls_success_emitsEvent() public { - PermissionManager.Permission memory permission = _createPermission(); - bytes32 permissionHash = permissionManager.hashPermission(permission); - - vm.startPrank(owner); - permissionManager.setPermissionContractEnabled(permission.permissionContract, true); - vm.stopPrank(); - - vm.prank(permission.account); - vm.expectEmit(address(permissionManager)); - emit PermissionManager.PermissionApproved(address(account), permissionHash); - permissionManager.beforeCalls(permission); - } - - function test_beforeCalls_success_validApprovalSignature(address sender) public { - PermissionManager.Permission memory permission = _createPermission(); - vm.assume(sender != permission.account); - - vm.startPrank(owner); - permissionManager.setPermissionContractEnabled(permission.permissionContract, true); - vm.stopPrank(); - - bytes32 permissionHash = permissionManager.hashPermission(permission); - bytes32 replaySafeHash = account.replaySafeHash(permissionHash); - (uint8 v, bytes32 r, bytes32 s) = vm.sign(ownerPk, replaySafeHash); - bytes memory signature = abi.encodePacked(r, s, v); - bytes memory approval = account.wrapSignature(0, signature); - - permission.approval = approval; - - vm.prank(sender); - permissionManager.beforeCalls(permission); - - vm.assertEq(permissionManager.isPermissionAuthorized(permission), true); - permission.approval = hex""; - vm.assertEq(permissionManager.isPermissionAuthorized(permission), true); - } - - function test_beforeCalls_success_cosigner() public { - PermissionManager.Permission memory permission = _createPermission(); - - vm.startPrank(owner); - permissionManager.setPermissionContractEnabled(permission.permissionContract, true); - vm.stopPrank(); - - vm.prank(permission.account); - permissionManager.beforeCalls(permission); - - vm.assertEq(permissionManager.isPermissionAuthorized(permission), true); - permission.approval = hex""; - vm.assertEq(permissionManager.isPermissionAuthorized(permission), true); - } - - function test_beforeCalls_success_replay() public { - PermissionManager.Permission memory permission = _createPermission(); - - vm.startPrank(owner); - permissionManager.setPermissionContractEnabled(permission.permissionContract, true); - vm.stopPrank(); - - vm.prank(permission.account); - permissionManager.beforeCalls(permission); - - vm.assertEq(permissionManager.isPermissionAuthorized(permission), true); - permission.approval = hex""; - vm.assertEq(permissionManager.isPermissionAuthorized(permission), true); - - // replay without calling from account or approval signature - permission.approval = hex""; - permissionManager.beforeCalls(permission); - - vm.assertEq(permissionManager.isPermissionAuthorized(permission), true); - permission.approval = hex""; - vm.assertEq(permissionManager.isPermissionAuthorized(permission), true); - } -} diff --git a/test/src/PermissionManager/Constructor.t.sol b/test/src/PermissionManager/Constructor.t.sol deleted file mode 100644 index a40e6ca..0000000 --- a/test/src/PermissionManager/Constructor.t.sol +++ /dev/null @@ -1,31 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {Test, console2} from "forge-std/Test.sol"; - -import {PermissionManager} from "../../../src/PermissionManager.sol"; - -import {PermissionManagerBase} from "../../base/PermissionManagerBase.sol"; - -contract ConstructorTest is Test, PermissionManagerBase { - function setUp() public {} - - function test_constructor_revert_zeroOwner(address cosigner) public { - vm.assume(cosigner != address(0)); - vm.expectRevert(); - new PermissionManager(address(0), cosigner); - } - - function test_constructor_revert_zeroCosigner(address owner) public { - vm.assume(owner != address(0)); - vm.expectRevert(); - new PermissionManager(owner, address(0)); - } - - function test_constructor_success(address owner, address cosigner) public { - vm.assume(owner != address(0) && cosigner != address(0)); - PermissionManager manager = new PermissionManager(owner, cosigner); - vm.assertEq(manager.owner(), owner); - vm.assertEq(manager.cosigner(), cosigner); - } -} diff --git a/test/src/PermissionManager/IsValidSignature.t.sol b/test/src/PermissionManager/IsValidSignature.t.sol deleted file mode 100644 index 77f50de..0000000 --- a/test/src/PermissionManager/IsValidSignature.t.sol +++ /dev/null @@ -1,509 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {Test, console2} from "forge-std/Test.sol"; -import {CoinbaseSmartWallet} from "smart-wallet/CoinbaseSmartWallet.sol"; - -import {PermissionManager} from "../../../src/PermissionManager.sol"; -import {CallErrors} from "../../../src/utils/CallErrors.sol"; -import {SignatureCheckerLib} from "../../../src/utils/SignatureCheckerLib.sol"; -import {UserOperation, UserOperationLib} from "../../../src/utils/UserOperationLib.sol"; - -import {PermissionManagerBase} from "../../base/PermissionManagerBase.sol"; - -contract IsValidSignatureTest is Test, PermissionManagerBase { - function setUp() public { - _initializePermissionManager(); - } - - function test_isValidSignature_revert_decodePermissionedUserOp(bytes32 userOpHash) public { - vm.expectRevert(); - permissionManager.isValidSignature(userOpHash, hex""); - } - - function test_isValidSignature_revert_invalidUserOperationSender(address sender) public { - PermissionManager.Permission memory permission = _createPermission(); - vm.assume(sender != permission.account); - - UserOperation memory userOp = _createUserOperation(); - userOp.sender = sender; - - PermissionManager.PermissionedUserOperation memory pUserOp = PermissionManager.PermissionedUserOperation({ - permission: permission, - userOp: userOp, - userOpSignature: hex"", - userOpCosignature: hex"" - }); - - vm.expectRevert(abi.encodeWithSelector(PermissionManager.InvalidUserOperationSender.selector, sender)); - permissionManager.isValidSignature(UserOperationLib.getUserOpHash(userOp), abi.encode(pUserOp)); - } - - function test_isValidSignature_revert_InvalidUserOperationHash(bytes32 hash) public { - UserOperation memory userOp = _createUserOperation(); - bytes32 userOpHash = UserOperationLib.getUserOpHash(userOp); - vm.assume(hash != userOpHash); - - PermissionManager.Permission memory permission = _createPermission(); - PermissionManager.PermissionedUserOperation memory pUserOp = PermissionManager.PermissionedUserOperation({ - permission: permission, - userOp: userOp, - userOpSignature: hex"", - userOpCosignature: hex"" - }); - - vm.expectRevert(abi.encodeWithSelector(PermissionManager.InvalidUserOperationHash.selector, userOpHash)); - permissionManager.isValidSignature(hash, abi.encode(pUserOp)); - } - - function test_isValidSignature_revert_InvalidUserOperationPaymaster() public { - UserOperation memory userOp = _createUserOperation(); - userOp.paymasterAndData = hex""; - - PermissionManager.Permission memory permission = _createPermission(); - PermissionManager.PermissionedUserOperation memory pUserOp = PermissionManager.PermissionedUserOperation({ - permission: permission, - userOp: userOp, - userOpSignature: hex"", - userOpCosignature: hex"" - }); - - vm.expectRevert(PermissionManager.InvalidUserOperationPaymaster.selector); - permissionManager.isValidSignature(UserOperationLib.getUserOpHash(userOp), abi.encode(pUserOp)); - } - - function test_isValidSignature_revert_revokedPermission() public { - PermissionManager.Permission memory permission = _createPermission(); - bytes32 permissionHash = permissionManager.hashPermission(permission); - UserOperation memory userOp = _createUserOperation(); - bytes32 userOpHash = UserOperationLib.getUserOpHash(userOp); - - PermissionManager.PermissionedUserOperation memory pUserOp = PermissionManager.PermissionedUserOperation({ - permission: permission, - userOp: userOp, - userOpSignature: hex"", - userOpCosignature: hex"" - }); - - vm.prank(address(account)); - permissionManager.revokePermission(permissionHash); - - vm.expectRevert(abi.encodeWithSelector(PermissionManager.UnauthorizedPermission.selector)); - permissionManager.isValidSignature(userOpHash, abi.encode(pUserOp)); - } - - function test_isValidSignature_revert_notApproved() public { - PermissionManager.Permission memory permission = _createPermission(); - permission.approval = hex""; // no approval signature - - UserOperation memory userOp = _createUserOperation(); - bytes32 userOpHash = UserOperationLib.getUserOpHash(userOp); - - PermissionManager.PermissionedUserOperation memory pUserOp = PermissionManager.PermissionedUserOperation({ - permission: permission, - userOp: userOp, - userOpSignature: hex"", - userOpCosignature: hex"" - }); - - vm.expectRevert(abi.encodeWithSelector(PermissionManager.UnauthorizedPermission.selector)); - permissionManager.isValidSignature(userOpHash, abi.encode(pUserOp)); - } - - function test_isValidSignature_revert_invalidUserOpSignature() public { - PermissionManager.Permission memory permission = _createPermission(); - - UserOperation memory userOp = _createUserOperation(); - bytes32 userOpHash = UserOperationLib.getUserOpHash(userOp); - - PermissionManager.PermissionedUserOperation memory pUserOp = PermissionManager.PermissionedUserOperation({ - permission: permission, - userOp: userOp, - userOpSignature: hex"", - userOpCosignature: hex"" - }); - - vm.expectRevert(); - permissionManager.isValidSignature(userOpHash, abi.encode(pUserOp)); - } - - function test_isValidSignature_revert_invalidUserOpCosignature() public { - PermissionManager.Permission memory permission = _createPermission(); - UserOperation memory userOp = _createUserOperation(); - - bytes32 userOpHash = UserOperationLib.getUserOpHash(userOp); - bytes memory userOpSignature = _sign(permmissionSignerPk, userOpHash); - bytes memory userOpCosignature = hex""; - - PermissionManager.PermissionedUserOperation memory pUserOp = PermissionManager.PermissionedUserOperation({ - permission: permission, - userOp: userOp, - userOpSignature: userOpSignature, - userOpCosignature: userOpCosignature - }); - - vm.prank(address(account)); - permissionManager.approvePermission(permission); - - vm.expectRevert(abi.encodeWithSelector(PermissionManager.InvalidSignature.selector)); - permissionManager.isValidSignature(userOpHash, abi.encode(pUserOp)); - } - - function test_isValidSignature_revert_invalidCosigner(uint128 userOpCosignerPk) public { - vm.assume(userOpCosignerPk != 0); - address userOpCosigner = vm.addr(userOpCosignerPk); - vm.assume(userOpCosigner != cosigner); - - PermissionManager.Permission memory permission = _createPermission(); - UserOperation memory userOp = _createUserOperation(); - - bytes32 userOpHash = UserOperationLib.getUserOpHash(userOp); - bytes memory userOpSignature = _sign(permmissionSignerPk, userOpHash); - bytes memory userOpCosignature = _sign(userOpCosignerPk, userOpHash); - - PermissionManager.PermissionedUserOperation memory pUserOp = PermissionManager.PermissionedUserOperation({ - permission: permission, - userOp: userOp, - userOpSignature: userOpSignature, - userOpCosignature: userOpCosignature - }); - - vm.prank(address(account)); - permissionManager.approvePermission(permission); - - vm.expectRevert(abi.encodeWithSelector(PermissionManager.InvalidCosigner.selector, userOpCosigner)); - permissionManager.isValidSignature(userOpHash, abi.encode(pUserOp)); - } - - function test_isValidSignature_revert_notExecuteBatchCallData() public { - PermissionManager.Permission memory permission = _createPermission(); - UserOperation memory userOp = _createUserOperation(); - - bytes32 userOpHash = UserOperationLib.getUserOpHash(userOp); - bytes memory userOpSignature = _sign(permmissionSignerPk, userOpHash); - bytes memory userOpCosignature = _sign(cosignerPk, userOpHash); - - PermissionManager.PermissionedUserOperation memory pUserOp = PermissionManager.PermissionedUserOperation({ - permission: permission, - userOp: userOp, - userOpSignature: userOpSignature, - userOpCosignature: userOpCosignature - }); - - vm.prank(address(account)); - permissionManager.approvePermission(permission); - - vm.expectRevert(abi.encodeWithSelector(CallErrors.SelectorNotAllowed.selector, bytes4(0))); - permissionManager.isValidSignature(userOpHash, abi.encode(pUserOp)); - } - - function test_isValidSignature_revert_invalidExecuteBatchCallData() public { - PermissionManager.Permission memory permission = _createPermission(); - UserOperation memory userOp = _createUserOperation(); - - bytes memory callData = abi.encodeWithSelector(CoinbaseSmartWallet.executeBatch.selector, hex""); - userOp.callData = callData; - - bytes32 userOpHash = UserOperationLib.getUserOpHash(userOp); - bytes memory userOpSignature = _sign(permmissionSignerPk, userOpHash); - bytes memory userOpCosignature = _sign(cosignerPk, userOpHash); - - PermissionManager.PermissionedUserOperation memory pUserOp = PermissionManager.PermissionedUserOperation({ - permission: permission, - userOp: userOp, - userOpSignature: userOpSignature, - userOpCosignature: userOpCosignature - }); - - vm.prank(address(account)); - permissionManager.approvePermission(permission); - - vm.expectRevert(); // revert parsing `userOp.callData` - permissionManager.isValidSignature(userOpHash, abi.encode(pUserOp)); - } - - function test_isValidSignature_revert_invalidBeforeCallsTarget(address target) public { - vm.assume(target != address(permissionManager)); - - PermissionManager.Permission memory permission = _createPermission(); - UserOperation memory userOp = _createUserOperation(); - - CoinbaseSmartWallet.Call[] memory calls = new CoinbaseSmartWallet.Call[](1); - calls[0] = _createCall(target, 0, _createBeforeCallsData(permission)); - bytes memory callData = abi.encodeWithSelector(CoinbaseSmartWallet.executeBatch.selector, calls); - userOp.callData = callData; - - bytes32 userOpHash = UserOperationLib.getUserOpHash(userOp); - bytes memory userOpSignature = _sign(permmissionSignerPk, userOpHash); - bytes memory userOpCosignature = _sign(cosignerPk, userOpHash); - - PermissionManager.PermissionedUserOperation memory pUserOp = PermissionManager.PermissionedUserOperation({ - permission: permission, - userOp: userOp, - userOpSignature: userOpSignature, - userOpCosignature: userOpCosignature - }); - - vm.prank(address(account)); - permissionManager.approvePermission(permission); - - // revert target not `address(permissionManager)` - vm.expectRevert(abi.encodeWithSelector(PermissionManager.InvalidBeforeCallsCall.selector)); - permissionManager.isValidSignature(userOpHash, abi.encode(pUserOp)); - } - - function test_isValidSignature_revert_invalidBeforeCallsData(bytes memory beforeCallsData) public { - PermissionManager.Permission memory permission = _createPermission(); - UserOperation memory userOp = _createUserOperation(); - - vm.assume(keccak256(beforeCallsData) != keccak256(_createBeforeCallsData(permission))); - - CoinbaseSmartWallet.Call[] memory calls = new CoinbaseSmartWallet.Call[](1); - calls[0] = _createCall(address(permissionManager), 0, beforeCallsData); - bytes memory callData = abi.encodeWithSelector(CoinbaseSmartWallet.executeBatch.selector, calls); - userOp.callData = callData; - - bytes32 userOpHash = UserOperationLib.getUserOpHash(userOp); - bytes memory userOpSignature = _sign(permmissionSignerPk, userOpHash); - bytes memory userOpCosignature = _sign(cosignerPk, userOpHash); - - PermissionManager.PermissionedUserOperation memory pUserOp = PermissionManager.PermissionedUserOperation({ - permission: permission, - userOp: userOp, - userOpSignature: userOpSignature, - userOpCosignature: userOpCosignature - }); - - vm.prank(address(account)); - permissionManager.approvePermission(permission); - - // revert data not valid - vm.expectRevert(abi.encodeWithSelector(PermissionManager.InvalidBeforeCallsCall.selector)); - permissionManager.isValidSignature(userOpHash, abi.encode(pUserOp)); - } - - function test_isValidSignature_revert_accountReentrancy(bytes memory reentrancyData) public { - PermissionManager.Permission memory permission = _createPermission(); - UserOperation memory userOp = _createUserOperation(); - - CoinbaseSmartWallet.Call[] memory calls = new CoinbaseSmartWallet.Call[](2); - calls[0] = _createCall(address(permissionManager), 0, _createBeforeCallsData(permission)); - calls[1] = _createCall(address(account), 0, reentrancyData); - bytes memory callData = abi.encodeWithSelector(CoinbaseSmartWallet.executeBatch.selector, calls); - userOp.callData = callData; - - bytes32 userOpHash = UserOperationLib.getUserOpHash(userOp); - bytes memory userOpSignature = _sign(permmissionSignerPk, userOpHash); - bytes memory userOpCosignature = _sign(cosignerPk, userOpHash); - - PermissionManager.PermissionedUserOperation memory pUserOp = PermissionManager.PermissionedUserOperation({ - permission: permission, - userOp: userOp, - userOpSignature: userOpSignature, - userOpCosignature: userOpCosignature - }); - - vm.prank(address(account)); - permissionManager.approvePermission(permission); - - vm.expectRevert(abi.encodeWithSelector(CallErrors.TargetNotAllowed.selector, address(account))); - permissionManager.isValidSignature(userOpHash, abi.encode(pUserOp)); - } - - function test_isValidSignature_revert_permissionManagerReentrancy(bytes memory reentrancyData) public { - PermissionManager.Permission memory permission = _createPermission(); - UserOperation memory userOp = _createUserOperation(); - - CoinbaseSmartWallet.Call[] memory calls = new CoinbaseSmartWallet.Call[](2); - calls[0] = _createCall(address(permissionManager), 0, _createBeforeCallsData(permission)); - calls[1] = _createCall(address(permissionManager), 0, reentrancyData); - bytes memory callData = abi.encodeWithSelector(CoinbaseSmartWallet.executeBatch.selector, calls); - userOp.callData = callData; - - bytes32 userOpHash = UserOperationLib.getUserOpHash(userOp); - bytes memory userOpSignature = _sign(permmissionSignerPk, userOpHash); - bytes memory userOpCosignature = _sign(cosignerPk, userOpHash); - - PermissionManager.PermissionedUserOperation memory pUserOp = PermissionManager.PermissionedUserOperation({ - permission: permission, - userOp: userOp, - userOpSignature: userOpSignature, - userOpCosignature: userOpCosignature - }); - - vm.prank(address(account)); - permissionManager.approvePermission(permission); - - vm.expectRevert(abi.encodeWithSelector(CallErrors.TargetNotAllowed.selector, address(permissionManager))); - permissionManager.isValidSignature(userOpHash, abi.encode(pUserOp)); - } - - function test_isValidSignature_revert_validatePermission() public { - PermissionManager.Permission memory permission = _createPermission(); - permission.permissionContract = address(failPermissionContract); - UserOperation memory userOp = _createUserOperation(); - - CoinbaseSmartWallet.Call[] memory calls = new CoinbaseSmartWallet.Call[](1); - calls[0] = _createCall(address(permissionManager), 0, _createBeforeCallsData(permission)); - bytes memory callData = abi.encodeWithSelector(CoinbaseSmartWallet.executeBatch.selector, calls); - userOp.callData = callData; - - bytes32 userOpHash = UserOperationLib.getUserOpHash(userOp); - bytes memory userOpSignature = _sign(permmissionSignerPk, userOpHash); - bytes memory userOpCosignature = _sign(cosignerPk, userOpHash); - - PermissionManager.PermissionedUserOperation memory pUserOp = PermissionManager.PermissionedUserOperation({ - permission: permission, - userOp: userOp, - userOpSignature: userOpSignature, - userOpCosignature: userOpCosignature - }); - - vm.prank(address(account)); - permissionManager.approvePermission(permission); - - vm.expectRevert(); - permissionManager.isValidSignature(userOpHash, abi.encode(pUserOp)); - } - - function test_isValidSignature_success_permissionApprovalStorage() public { - PermissionManager.Permission memory permission = _createPermission(); - UserOperation memory userOp = _createUserOperation(); - - CoinbaseSmartWallet.Call[] memory calls = new CoinbaseSmartWallet.Call[](1); - calls[0] = _createCall(address(permissionManager), 0, _createBeforeCallsData(permission)); - bytes memory callData = abi.encodeWithSelector(CoinbaseSmartWallet.executeBatch.selector, calls); - userOp.callData = callData; - - bytes32 userOpHash = UserOperationLib.getUserOpHash(userOp); - bytes memory userOpSignature = _sign(permmissionSignerPk, userOpHash); - bytes memory userOpCosignature = _sign(cosignerPk, userOpHash); - - PermissionManager.PermissionedUserOperation memory pUserOp = PermissionManager.PermissionedUserOperation({ - permission: permission, - userOp: userOp, - userOpSignature: userOpSignature, - userOpCosignature: userOpCosignature - }); - - vm.prank(address(account)); - permissionManager.approvePermission(permission); - - bytes4 magicValue = permissionManager.isValidSignature(userOpHash, abi.encode(pUserOp)); - assertEq(magicValue, EIP1271_MAGIC_VALUE); - } - - function test_isValidSignature_success_permissionApprovalSignature() public view { - PermissionManager.Permission memory permission = _createPermission(); - - permission.approval = _signPermission(permission); - - UserOperation memory userOp = _createUserOperation(); - - CoinbaseSmartWallet.Call[] memory calls = new CoinbaseSmartWallet.Call[](1); - calls[0] = _createCall(address(permissionManager), 0, _createBeforeCallsData(permission)); - bytes memory callData = abi.encodeWithSelector(CoinbaseSmartWallet.executeBatch.selector, calls); - userOp.callData = callData; - - bytes32 userOpHash = UserOperationLib.getUserOpHash(userOp); - bytes memory userOpSignature = _sign(permmissionSignerPk, userOpHash); - bytes memory userOpCosignature = _sign(cosignerPk, userOpHash); - - PermissionManager.PermissionedUserOperation memory pUserOp = PermissionManager.PermissionedUserOperation({ - permission: permission, - userOp: userOp, - userOpSignature: userOpSignature, - userOpCosignature: userOpCosignature - }); - - bytes4 magicValue = permissionManager.isValidSignature(userOpHash, abi.encode(pUserOp)); - assertEq(magicValue, EIP1271_MAGIC_VALUE); - } - - function test_isValidSignature_success_userOpSignatureEOA() public view { - PermissionManager.Permission memory permission = _createPermission(); - - permission.approval = _signPermission(permission); - - UserOperation memory userOp = _createUserOperation(); - - CoinbaseSmartWallet.Call[] memory calls = new CoinbaseSmartWallet.Call[](1); - calls[0] = _createCall(address(permissionManager), 0, _createBeforeCallsData(permission)); - bytes memory callData = abi.encodeWithSelector(CoinbaseSmartWallet.executeBatch.selector, calls); - userOp.callData = callData; - - bytes32 userOpHash = UserOperationLib.getUserOpHash(userOp); - bytes memory userOpSignature = _sign(permmissionSignerPk, userOpHash); - bytes memory userOpCosignature = _sign(cosignerPk, userOpHash); - - PermissionManager.PermissionedUserOperation memory pUserOp = PermissionManager.PermissionedUserOperation({ - permission: permission, - userOp: userOp, - userOpSignature: userOpSignature, - userOpCosignature: userOpCosignature - }); - - bytes4 magicValue = permissionManager.isValidSignature(userOpHash, abi.encode(pUserOp)); - assertEq(magicValue, EIP1271_MAGIC_VALUE); - } - - function test_isValidSignature_success_userOpSignatureContract() public view { - PermissionManager.Permission memory permission = _createPermission(); - permission.signer = abi.encode(address(permissionSignerContract)); - - permission.approval = _signPermission(permission); - - UserOperation memory userOp = _createUserOperation(); - - CoinbaseSmartWallet.Call[] memory calls = new CoinbaseSmartWallet.Call[](1); - calls[0] = _createCall(address(permissionManager), 0, _createBeforeCallsData(permission)); - bytes memory callData = abi.encodeWithSelector(CoinbaseSmartWallet.executeBatch.selector, calls); - userOp.callData = callData; - - bytes32 userOpHash = UserOperationLib.getUserOpHash(userOp); - bytes memory userOpSignature = _sign(permmissionSignerPk, userOpHash); - bytes memory userOpCosignature = _sign(cosignerPk, userOpHash); - - PermissionManager.PermissionedUserOperation memory pUserOp = PermissionManager.PermissionedUserOperation({ - permission: permission, - userOp: userOp, - userOpSignature: userOpSignature, - userOpCosignature: userOpCosignature - }); - - bytes4 magicValue = permissionManager.isValidSignature(userOpHash, abi.encode(pUserOp)); - assertEq(magicValue, EIP1271_MAGIC_VALUE); - } - - function test_isValidSignature_success_userOpSignatureWebAuthn() public view { - PermissionManager.Permission memory permission = _createPermission(); - permission.signer = p256PublicKey; - - permission.approval = _signPermission(permission); - - UserOperation memory userOp = _createUserOperation(); - - CoinbaseSmartWallet.Call[] memory calls = new CoinbaseSmartWallet.Call[](1); - calls[0] = _createCall(address(permissionManager), 0, _createBeforeCallsData(permission)); - bytes memory callData = abi.encodeWithSelector(CoinbaseSmartWallet.executeBatch.selector, calls); - userOp.callData = callData; - - bytes32 userOpHash = UserOperationLib.getUserOpHash(userOp); - bytes memory userOpSignature = _signP256(p256PrivateKey, userOpHash); - bytes memory userOpCosignature = _sign(cosignerPk, userOpHash); - - PermissionManager.PermissionedUserOperation memory pUserOp = PermissionManager.PermissionedUserOperation({ - permission: permission, - userOp: userOp, - userOpSignature: userOpSignature, - userOpCosignature: userOpCosignature - }); - - bytes4 magicValue = permissionManager.isValidSignature(userOpHash, abi.encode(pUserOp)); - assertEq(magicValue, EIP1271_MAGIC_VALUE); - } - - function test_isValidSignature_success_erc4337Compliance() public pure { - revert("unimplemented"); - } -} diff --git a/test/src/PermissionManager/Pause.t.sol b/test/src/PermissionManager/Pause.t.sol deleted file mode 100644 index cf55175..0000000 --- a/test/src/PermissionManager/Pause.t.sol +++ /dev/null @@ -1,77 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {Test, console2} from "forge-std/Test.sol"; -import {Ownable} from "openzeppelin-contracts/contracts/access/Ownable.sol"; -import {Pausable} from "openzeppelin-contracts/contracts/utils/Pausable.sol"; - -import {PermissionManagerBase} from "../../base/PermissionManagerBase.sol"; - -contract PauseTest is Test, PermissionManagerBase { - function setUp() public { - _initializePermissionManager(); - } - - function test_pause_revert_notOwner(address sender) public { - vm.assume(sender != owner); - vm.startPrank(sender); - vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, sender)); - permissionManager.pause(); - } - - function test_pause_revert_alreadyPaused() public { - vm.startPrank(owner); - - permissionManager.pause(); - - vm.expectRevert(abi.encodeWithSelector(Pausable.EnforcedPause.selector)); - permissionManager.pause(); - } - - function test_pause_success_emitsEvent() public { - vm.startPrank(owner); - - vm.expectEmit(address(permissionManager)); - emit Pausable.Paused(owner); - permissionManager.pause(); - } - - function test_pause_success() public { - vm.startPrank(owner); - - permissionManager.pause(); - vm.assertEq(permissionManager.paused(), true); - } - - function test_unpause_revert_notOwner(address sender) public { - vm.assume(sender != owner); - vm.startPrank(sender); - vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, sender)); - permissionManager.unpause(); - } - - function test_unpause_revert_alreadyUnpaused() public { - vm.startPrank(owner); - - vm.expectRevert(abi.encodeWithSelector(Pausable.ExpectedPause.selector)); - permissionManager.unpause(); - } - - function test_unpause_success_emitsEvent() public { - vm.startPrank(owner); - - permissionManager.pause(); - - vm.expectEmit(address(permissionManager)); - emit Pausable.Unpaused(owner); - permissionManager.unpause(); - } - - function test_unpause_success_setsState() public { - vm.startPrank(owner); - - permissionManager.pause(); - permissionManager.unpause(); - vm.assertEq(permissionManager.paused(), false); - } -} diff --git a/test/src/PermissionManager/RenounceOwnership.t.sol b/test/src/PermissionManager/RenounceOwnership.t.sol deleted file mode 100644 index 763b2ff..0000000 --- a/test/src/PermissionManager/RenounceOwnership.t.sol +++ /dev/null @@ -1,28 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {Test, console2} from "forge-std/Test.sol"; -import {Ownable} from "openzeppelin-contracts/contracts/access/Ownable.sol"; - -import {PermissionManager} from "../../../src/PermissionManager.sol"; - -import {PermissionManagerBase} from "../../base/PermissionManagerBase.sol"; - -contract RenounceOwnershipTest is Test, PermissionManagerBase { - function setUp() public { - _initializePermissionManager(); - } - - function test_renounceOwnership_revert_notOwner(address sender) public { - vm.assume(sender != owner); - vm.startPrank(sender); - vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, sender)); - permissionManager.renounceOwnership(); - } - - function test_renounceOwnership_revert_CannotRenounceOwnership() public { - vm.startPrank(owner); - vm.expectRevert(abi.encodeWithSelector(PermissionManager.CannotRenounceOwnership.selector)); - permissionManager.renounceOwnership(); - } -} diff --git a/test/src/PermissionManager/RevokePermission.t.sol b/test/src/PermissionManager/RevokePermission.t.sol deleted file mode 100644 index ba93dd4..0000000 --- a/test/src/PermissionManager/RevokePermission.t.sol +++ /dev/null @@ -1,69 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {Test, console2} from "forge-std/Test.sol"; - -import {PermissionManager} from "../../../src/PermissionManager.sol"; - -import {PermissionManagerBase} from "../../base/PermissionManagerBase.sol"; - -contract RevokePermissionTest is Test, PermissionManagerBase { - function setUp() public { - _initializePermissionManager(); - } - - function test_revokePermission_success_emitsEvent(address sender) public { - PermissionManager.Permission memory permission = _createPermission(); - permission.account = sender; - bytes32 permissionHash = permissionManager.hashPermission(permission); - - vm.prank(sender); - vm.expectEmit(address(permissionManager)); - emit PermissionManager.PermissionRevoked(sender, permissionHash); - permissionManager.revokePermission(permissionHash); - } - - function test_revokePermission_success_setsState(address sender) public { - PermissionManager.Permission memory permission = _createPermission(); - permission.account = sender; - bytes32 permissionHash = permissionManager.hashPermission(permission); - - vm.prank(sender); - permissionManager.revokePermission(permissionHash); - vm.assertEq(permissionManager.isPermissionAuthorized(permission), false); - } - - function test_revokePermission_success_differentAccounts(address sender1, address sender2) public { - PermissionManager.Permission memory permission = _createPermission(); - permission.account = sender1; - bytes32 permissionHash = permissionManager.hashPermission(permission); - - vm.assume(sender1 != sender2); - vm.prank(sender1); - vm.expectEmit(address(permissionManager)); - emit PermissionManager.PermissionRevoked(sender1, permissionHash); - permissionManager.revokePermission(permissionHash); - vm.assertEq(permissionManager.isPermissionAuthorized(permission), false); - - permission.account = sender2; - vm.prank(sender2); - vm.expectEmit(address(permissionManager)); - emit PermissionManager.PermissionRevoked(sender2, permissionHash); - permissionManager.revokePermission(permissionHash); - vm.assertEq(permissionManager.isPermissionAuthorized(permission), false); - vm.assertEq(permissionManager.isPermissionAuthorized(permission), false); - } - - function test_revokePermission_success_replaySameAccount(address sender) public { - PermissionManager.Permission memory permission = _createPermission(); - permission.account = sender; - bytes32 permissionHash = permissionManager.hashPermission(permission); - - vm.startPrank(sender); - vm.expectEmit(address(permissionManager)); - emit PermissionManager.PermissionRevoked(sender, permissionHash); - permissionManager.revokePermission(permissionHash); - vm.assertEq(permissionManager.isPermissionAuthorized(permission), false); - vm.assertEq(permissionManager.isPermissionAuthorized(permission), false); - } -} diff --git a/test/src/PermissionManager/SetPermissionContractEnabled.t.sol b/test/src/PermissionManager/SetPermissionContractEnabled.t.sol deleted file mode 100644 index 046d7a7..0000000 --- a/test/src/PermissionManager/SetPermissionContractEnabled.t.sol +++ /dev/null @@ -1,38 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {Test, console2} from "forge-std/Test.sol"; -import {Ownable} from "openzeppelin-contracts/contracts/access/Ownable.sol"; - -import {PermissionManager} from "../../../src/PermissionManager.sol"; - -import {PermissionManagerBase} from "../../base/PermissionManagerBase.sol"; - -contract SetPermissionContractEnabledTest is Test, PermissionManagerBase { - function setUp() public { - _initializePermissionManager(); - } - - function test_setPermissionContractEnabled_revert_notOwner(address sender, address permissionContract, bool enabled) - public - { - vm.assume(permissionContract != address(0)); - vm.assume(sender != owner); - vm.startPrank(sender); - vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, sender)); - permissionManager.setPermissionContractEnabled(permissionContract, enabled); - } - - function test_setPermissionContractEnabled_success_emitsEvent(address permissionContract, bool enabled) public { - vm.prank(owner); - vm.expectEmit(address(permissionManager)); - emit PermissionManager.PermissionContractUpdated(permissionContract, enabled); - permissionManager.setPermissionContractEnabled(permissionContract, enabled); - } - - function test_setPermissionContractEnabled_success_setsState(address permissionContract, bool enabled) public { - vm.prank(owner); - permissionManager.setPermissionContractEnabled(permissionContract, enabled); - vm.assertEq(permissionManager.isPermissionContractEnabled(permissionContract), enabled); - } -} diff --git a/test/src/SpendPermissionsPaymaster/Debug.t.sol b/test/src/SpendPermissionsPaymaster/Debug.t.sol index ddddeb7..e1725e1 100644 --- a/test/src/SpendPermissionsPaymaster/Debug.t.sol +++ b/test/src/SpendPermissionsPaymaster/Debug.t.sol @@ -19,7 +19,7 @@ contract DebugTest is Test, Base { function setUp() public { _initialize(); - vm.etch(ENTRY_POINT_V06, Static.ENTRY_POINT_BYTES); + vm.etch(ENTRY_POINT_V06, Static.ENTRY_POINT_V06_BYTES); spendPermissions = new SpendPermissionsPaymaster(owner); @@ -55,8 +55,7 @@ contract DebugTest is Test, Base { vm.deal(recurringAllowance.account, allowance); vm.prank(ENTRY_POINT_V06); - (bytes memory postOpContext, uint256 validationData) = - spendPermissions.validatePaymasterUserOp(userOp, bytes32(0), maxGasCost); + (bytes memory postOpContext,) = spendPermissions.validatePaymasterUserOp(userOp, bytes32(0), maxGasCost); vm.assertEq(ENTRY_POINT_V06.balance, maxGasCost); vm.assertEq(address(spendPermissions).balance, allowance - maxGasCost); diff --git a/test/src/mixins/NativeTokenRecurringAllowance/GetRecurringAllowanceUsage.t.sol b/test/src/mixins/NativeTokenRecurringAllowance/GetRecurringAllowanceUsage.t.sol deleted file mode 100644 index 93f1efa..0000000 --- a/test/src/mixins/NativeTokenRecurringAllowance/GetRecurringAllowanceUsage.t.sol +++ /dev/null @@ -1,181 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {Test, console2} from "forge-std/Test.sol"; - -import {NativeTokenRecurringAllowance} from "../../../../src/mixins/NativeTokenRecurringAllowance.sol"; - -import {NativeTokenRecurringAllowanceBase} from "../../../base/NativeTokenRecurringAllowanceBase.sol"; - -contract GetRecurringAllowanceUsageTest is Test, NativeTokenRecurringAllowanceBase { - function setUp() public { - _initializeNativeTokenRecurringAllowance(); - } - - function test_getRecurringAllowanceUsage_revert_uninitializedRecurringAllowance( - address account, - bytes32 permissionHash - ) public { - vm.expectRevert(abi.encodeWithSelector(NativeTokenRecurringAllowance.InvalidInitialization.selector)); - mockNativeTokenRecurringAllowance.getRecurringAllowanceUsage(account, permissionHash); - } - - function test_getRecurringAllowanceUsage_revert_beforeRecurringAllowanceStart( - address account, - bytes32 permissionHash, - uint48 start, - uint48 period, - uint160 allowance - ) public { - vm.assume(start > 0); - vm.assume(period > 0); - - mockNativeTokenRecurringAllowance.initializeRecurringAllowance( - account, permissionHash, _createRecurringAllowance(start, period, allowance) - ); - - vm.warp(start - 1); - vm.expectRevert( - abi.encodeWithSelector(NativeTokenRecurringAllowance.BeforeRecurringAllowanceStart.selector, start) - ); - mockNativeTokenRecurringAllowance.getRecurringAllowanceUsage(account, permissionHash); - } - - function test_getRecurringAllowanceUsage_success_unused( - address account, - bytes32 permissionHash, - uint48 start, - uint48 period, - uint160 allowance - ) public { - vm.assume(start > 0); - vm.assume(period > 0); - vm.assume(allowance > 0); - - mockNativeTokenRecurringAllowance.initializeRecurringAllowance( - account, permissionHash, _createRecurringAllowance(start, period, allowance) - ); - - vm.warp(start); - NativeTokenRecurringAllowance.CycleUsage memory usage = - mockNativeTokenRecurringAllowance.getRecurringAllowanceUsage(account, permissionHash); - assertEq(usage.start, start); - assertEq(usage.end, _safeAdd(start, period)); - assertEq(usage.spend, 0); - } - - function test_getRecurringAllowanceUsage_success_startOfPeriod( - address account, - bytes32 permissionHash, - uint48 start, - uint48 period, - uint160 allowance, - uint160 spend - ) public { - vm.assume(start > 0); - vm.assume(period > 0); - vm.assume(allowance > 0); - vm.assume(spend <= allowance); - - mockNativeTokenRecurringAllowance.initializeRecurringAllowance( - account, permissionHash, _createRecurringAllowance(start, period, allowance) - ); - - vm.warp(start); - mockNativeTokenRecurringAllowance.useRecurringAllowance(account, permissionHash, spend); - NativeTokenRecurringAllowance.CycleUsage memory usage = - mockNativeTokenRecurringAllowance.getRecurringAllowanceUsage(account, permissionHash); - - assertEq(usage.start, start); - assertEq(usage.end, _safeAdd(start, period)); - assertEq(usage.spend, spend); - } - - function test_getRecurringAllowanceUsage_success_endOfPeriod( - address account, - bytes32 permissionHash, - uint48 start, - uint48 period, - uint160 allowance, - uint160 spend - ) public { - vm.assume(start > 0); - vm.assume(start < type(uint48).max); - vm.assume(period > 0); - vm.assume(allowance > 0); - vm.assume(spend <= allowance); - - mockNativeTokenRecurringAllowance.initializeRecurringAllowance( - account, permissionHash, _createRecurringAllowance(start, period, allowance) - ); - - vm.warp(start); - mockNativeTokenRecurringAllowance.useRecurringAllowance(account, permissionHash, spend); - - vm.warp(_safeAdd(start, period) - 1); - NativeTokenRecurringAllowance.CycleUsage memory usage = - mockNativeTokenRecurringAllowance.getRecurringAllowanceUsage(account, permissionHash); - assertEq(usage.start, start); - assertEq(usage.end, _safeAdd(start, period)); - assertEq(usage.spend, spend); - } - - function test_getRecurringAllowanceUsage_success_resetAfterPeriod( - address account, - bytes32 permissionHash, - uint48 start, - uint48 period, - uint160 allowance, - uint160 spend - ) public { - vm.assume(start > 0); - vm.assume(period > 0); - uint256 firstCycleEnd = uint256(start) + uint256(period); - vm.assume(firstCycleEnd < type(uint48).max); - vm.assume(allowance > 0); - vm.assume(spend <= allowance); - - mockNativeTokenRecurringAllowance.initializeRecurringAllowance( - account, permissionHash, _createRecurringAllowance(start, period, allowance) - ); - - vm.warp(start); - mockNativeTokenRecurringAllowance.useRecurringAllowance(account, permissionHash, spend); - - vm.warp(start + period); - NativeTokenRecurringAllowance.CycleUsage memory usage = - mockNativeTokenRecurringAllowance.getRecurringAllowanceUsage(account, permissionHash); - assertEq(usage.start, start + period); - assertEq(usage.end, _safeAdd(_safeAdd(start, period), period)); - assertEq(usage.spend, 0); - } - - function test_getRecurringAllowanceUsage_success_oneCycleNoReset( - address account, - bytes32 permissionHash, - uint48 start, - uint48 period, - uint160 allowance, - uint160 spend - ) public { - vm.assume(start > 0); - vm.assume(period > 0); - uint256 firstCycleEnd = uint256(start) + uint256(period); - vm.assume(firstCycleEnd > type(uint48).max); - vm.assume(allowance > 0); - vm.assume(spend <= allowance); - - mockNativeTokenRecurringAllowance.initializeRecurringAllowance( - account, permissionHash, _createRecurringAllowance(start, period, allowance) - ); - - vm.warp(start); - mockNativeTokenRecurringAllowance.useRecurringAllowance(account, permissionHash, spend); - - NativeTokenRecurringAllowance.CycleUsage memory usage = - mockNativeTokenRecurringAllowance.getRecurringAllowanceUsage(account, permissionHash); - assertEq(usage.start, start); - assertEq(usage.end, type(uint48).max); - assertEq(usage.spend, spend); - } -} diff --git a/test/src/mixins/NativeTokenRecurringAllowance/InitializeRecurringAllowance.t.sol b/test/src/mixins/NativeTokenRecurringAllowance/InitializeRecurringAllowance.t.sol deleted file mode 100644 index aa86edf..0000000 --- a/test/src/mixins/NativeTokenRecurringAllowance/InitializeRecurringAllowance.t.sol +++ /dev/null @@ -1,171 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {Test, console2} from "forge-std/Test.sol"; - -import {NativeTokenRecurringAllowance} from "../../../../src/mixins/NativeTokenRecurringAllowance.sol"; - -import {NativeTokenRecurringAllowanceBase} from "../../../base/NativeTokenRecurringAllowanceBase.sol"; - -contract InitializeRecurringAllowanceTest is Test, NativeTokenRecurringAllowanceBase { - function setUp() public { - _initializeNativeTokenRecurringAllowance(); - } - - function test_initializeRecurringAllowance_revert_zeroRecurringAllowanceStart( - address account, - bytes32 permissionHash, - uint48 period, - uint160 allowance - ) public { - uint48 start = 0; - vm.assume(period > 0); - - vm.expectRevert(abi.encodeWithSelector(NativeTokenRecurringAllowance.InvalidInitialization.selector)); - mockNativeTokenRecurringAllowance.initializeRecurringAllowance( - account, permissionHash, _createRecurringAllowance(start, period, allowance) - ); - } - - function test_initializeRecurringAllowance_revert_zeroRecurringAllowancePeriod( - address account, - bytes32 permissionHash, - uint48 start, - uint160 allowance - ) public { - uint48 period = 0; - vm.assume(start > 0); - - vm.expectRevert(abi.encodeWithSelector(NativeTokenRecurringAllowance.InvalidInitialization.selector)); - mockNativeTokenRecurringAllowance.initializeRecurringAllowance( - account, permissionHash, _createRecurringAllowance(start, period, allowance) - ); - } - - function test_initializeRecurringAllowance_success_emitsEvent( - address account, - bytes32 permissionHash, - uint48 start, - uint48 period - ) public { - uint160 allowance = 0; - vm.assume(start > 0); - vm.assume(period > 0); - - vm.expectEmit(address(mockNativeTokenRecurringAllowance)); - emit NativeTokenRecurringAllowance.RecurringAllowanceInitialized( - address(account), permissionHash, _createRecurringAllowance(start, period, allowance) - ); - mockNativeTokenRecurringAllowance.initializeRecurringAllowance( - account, permissionHash, _createRecurringAllowance(start, period, allowance) - ); - } - - function test_initializeRecurringAllowance_success_zeroAllowance( - address account, - bytes32 permissionHash, - uint48 start, - uint48 period - ) public { - uint160 allowance = 0; - vm.assume(start > 0); - vm.assume(period > 0); - - mockNativeTokenRecurringAllowance.initializeRecurringAllowance( - account, permissionHash, _createRecurringAllowance(start, period, allowance) - ); - NativeTokenRecurringAllowance.RecurringAllowance memory recurringAllowance = - mockNativeTokenRecurringAllowance.getRecurringAllowance(account, permissionHash); - assertEq(recurringAllowance.start, start); - assertEq(recurringAllowance.period, period); - assertEq(recurringAllowance.allowance, allowance); - } - - function test_initializeRecurringAllowance_success_nonzeroAllowance( - address account, - bytes32 permissionHash, - uint48 start, - uint48 period, - uint160 allowance - ) public { - vm.assume(start > 0); - vm.assume(period > 0); - vm.assume(allowance > 0); - - mockNativeTokenRecurringAllowance.initializeRecurringAllowance( - account, permissionHash, _createRecurringAllowance(start, period, allowance) - ); - NativeTokenRecurringAllowance.RecurringAllowance memory recurringAllowance = - mockNativeTokenRecurringAllowance.getRecurringAllowance(account, permissionHash); - assertEq(recurringAllowance.start, start); - assertEq(recurringAllowance.period, period); - assertEq(recurringAllowance.allowance, allowance); - } - - function test_initializeRecurringAllowance_success_replaySameValues( - address account, - bytes32 permissionHash, - uint48 start, - uint48 period, - uint160 allowance - ) public { - vm.assume(start > 0); - vm.assume(period > 0); - vm.assume(allowance > 0); - - mockNativeTokenRecurringAllowance.initializeRecurringAllowance( - account, permissionHash, _createRecurringAllowance(start, period, allowance) - ); - NativeTokenRecurringAllowance.RecurringAllowance memory recurringAllowance = - mockNativeTokenRecurringAllowance.getRecurringAllowance(account, permissionHash); - assertEq(recurringAllowance.start, start); - assertEq(recurringAllowance.period, period); - assertEq(recurringAllowance.allowance, allowance); - - // replay SAME values, no errors - mockNativeTokenRecurringAllowance.initializeRecurringAllowance( - account, permissionHash, _createRecurringAllowance(start, period, allowance) - ); - recurringAllowance = mockNativeTokenRecurringAllowance.getRecurringAllowance(account, permissionHash); - assertEq(recurringAllowance.start, start); - assertEq(recurringAllowance.period, period); - assertEq(recurringAllowance.allowance, allowance); - } - - function test_initializeRecurringAllowance_success_replayNewValues( - address account, - bytes32 permissionHash, - uint48 start, - uint48 period, - uint160 allowance, - uint48 newStart, - uint48 newPeriod, - uint160 newAllowance - ) public { - vm.assume(start > 0); - vm.assume(period > 0); - vm.assume(allowance > 0); - vm.assume(newStart > 0 && start != newStart); - vm.assume(newPeriod > 0 && period != newPeriod); - vm.assume(newAllowance > 0 && allowance != newAllowance); - - mockNativeTokenRecurringAllowance.initializeRecurringAllowance( - account, permissionHash, _createRecurringAllowance(start, period, allowance) - ); - NativeTokenRecurringAllowance.RecurringAllowance memory recurringAllowance = - mockNativeTokenRecurringAllowance.getRecurringAllowance(account, permissionHash); - assertEq(recurringAllowance.start, start); - assertEq(recurringAllowance.period, period); - assertEq(recurringAllowance.allowance, allowance); - - // replay NEW values, no errors - mockNativeTokenRecurringAllowance.initializeRecurringAllowance( - account, permissionHash, _createRecurringAllowance(newStart, newPeriod, newAllowance) - ); - recurringAllowance = mockNativeTokenRecurringAllowance.getRecurringAllowance(account, permissionHash); - // values unchanged - assertEq(recurringAllowance.start, start); - assertEq(recurringAllowance.period, period); - assertEq(recurringAllowance.allowance, allowance); - } -} diff --git a/test/src/mixins/NativeTokenRecurringAllowance/UseRecurringAllowance.t.sol b/test/src/mixins/NativeTokenRecurringAllowance/UseRecurringAllowance.t.sol deleted file mode 100644 index f7e9e51..0000000 --- a/test/src/mixins/NativeTokenRecurringAllowance/UseRecurringAllowance.t.sol +++ /dev/null @@ -1,241 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {Test, console2} from "forge-std/Test.sol"; - -import {NativeTokenRecurringAllowance} from "../../../../src/mixins/NativeTokenRecurringAllowance.sol"; - -import {NativeTokenRecurringAllowanceBase} from "../../../base/NativeTokenRecurringAllowanceBase.sol"; - -contract UseRecurringAllowanceTest is Test, NativeTokenRecurringAllowanceBase { - function setUp() public { - _initializeNativeTokenRecurringAllowance(); - } - - function test_useRecurringAllowance_revert_invalidInitialization( - address account, - bytes32 permissionHash, - uint256 spend - ) public { - vm.assume(spend > 0); - - vm.expectRevert(NativeTokenRecurringAllowance.InvalidInitialization.selector); - mockNativeTokenRecurringAllowance.useRecurringAllowance(account, permissionHash, spend); - } - - function test_useRecurringAllowance_revert_beforeRecurringAllowanceStart( - address account, - bytes32 permissionHash, - uint48 start, - uint48 period, - uint160 allowance, - uint256 spend - ) public { - vm.assume(start > 0); - vm.assume(period > 0); - vm.assume(allowance > 0); - vm.assume(spend > 0); - - mockNativeTokenRecurringAllowance.initializeRecurringAllowance( - account, permissionHash, _createRecurringAllowance(start, period, allowance) - ); - - vm.warp(start - 1); - vm.expectRevert( - abi.encodeWithSelector(NativeTokenRecurringAllowance.BeforeRecurringAllowanceStart.selector, start) - ); - mockNativeTokenRecurringAllowance.useRecurringAllowance(account, permissionHash, spend); - } - - function test_useRecurringAllowance_revert_spendValueOverflow( - address account, - bytes32 permissionHash, - uint48 start, - uint48 period, - uint160 allowance, - uint256 spend - ) public { - vm.assume(start > 0); - vm.assume(period > 0); - vm.assume(allowance > 0); - vm.assume(spend > type(uint160).max); - - mockNativeTokenRecurringAllowance.initializeRecurringAllowance( - account, permissionHash, _createRecurringAllowance(start, period, allowance) - ); - - vm.warp(start); - vm.expectRevert(abi.encodeWithSelector(NativeTokenRecurringAllowance.SpendValueOverflow.selector, spend)); - mockNativeTokenRecurringAllowance.useRecurringAllowance(account, permissionHash, spend); - } - - function test_useRecurringAllowance_revert_exceededRecurringAllowance( - address account, - bytes32 permissionHash, - uint48 start, - uint48 period, - uint160 allowance, - uint160 spend - ) public { - vm.assume(start > 0); - vm.assume(period > 0); - vm.assume(allowance > 0); - vm.assume(allowance < type(uint160).max - 1); - vm.assume(spend > allowance); - - mockNativeTokenRecurringAllowance.initializeRecurringAllowance( - account, permissionHash, _createRecurringAllowance(start, period, allowance) - ); - - vm.warp(start); - vm.expectRevert( - abi.encodeWithSelector(NativeTokenRecurringAllowance.ExceededRecurringAllowance.selector, spend, allowance) - ); - mockNativeTokenRecurringAllowance.useRecurringAllowance(account, permissionHash, spend); - } - - function test_useRecurringAllowance_revert_exceededRecurringAllowance_accruedSpend( - address account, - bytes32 permissionHash, - uint48 start, - uint48 period, - uint160 allowance - ) public { - vm.assume(start > 0); - vm.assume(period > 0); - vm.assume(allowance > 0); - vm.assume(allowance < type(uint160).max - 1); - uint256 spend = allowance; - - mockNativeTokenRecurringAllowance.initializeRecurringAllowance( - account, permissionHash, _createRecurringAllowance(start, period, allowance) - ); - - vm.warp(start); - // first spend ok, within allowance - mockNativeTokenRecurringAllowance.useRecurringAllowance(account, permissionHash, spend); - // second spend not ok, exceeds allowance - vm.expectRevert( - abi.encodeWithSelector( - NativeTokenRecurringAllowance.ExceededRecurringAllowance.selector, spend + 1, allowance - ) - ); - mockNativeTokenRecurringAllowance.useRecurringAllowance(account, permissionHash, 1); - } - - function test_useRecurringAllowance_success_noSpend(address account, bytes32 permissionHash) public { - uint256 spend = 0; - mockNativeTokenRecurringAllowance.useRecurringAllowance(account, permissionHash, spend); - } - - function test_useRecurringAllowance_success_emitsEvent( - address account, - bytes32 permissionHash, - uint48 start, - uint48 period, - uint160 allowance, - uint160 spend - ) public { - vm.assume(start > 0); - vm.assume(period > 0); - vm.assume(allowance > 0); - vm.assume(spend < allowance); - vm.assume(spend > 0); - - mockNativeTokenRecurringAllowance.initializeRecurringAllowance( - account, permissionHash, _createRecurringAllowance(start, period, allowance) - ); - - vm.warp(start); - vm.expectEmit(address(mockNativeTokenRecurringAllowance)); - emit NativeTokenRecurringAllowance.RecurringAllowanceUsed( - account, - permissionHash, - NativeTokenRecurringAllowance.CycleUsage({start: start, end: _safeAdd(start, period), spend: spend}) - ); - mockNativeTokenRecurringAllowance.useRecurringAllowance(account, permissionHash, spend); - } - - function test_useRecurringAllowance_success_setsState( - address account, - bytes32 permissionHash, - uint48 start, - uint48 period, - uint160 allowance, - uint160 spend - ) public { - vm.assume(start > 0); - vm.assume(period > 0); - vm.assume(allowance > 0); - vm.assume(spend < allowance); - - mockNativeTokenRecurringAllowance.initializeRecurringAllowance( - account, permissionHash, _createRecurringAllowance(start, period, allowance) - ); - - vm.warp(start); - mockNativeTokenRecurringAllowance.useRecurringAllowance(account, permissionHash, spend); - NativeTokenRecurringAllowance.CycleUsage memory usage = - mockNativeTokenRecurringAllowance.getRecurringAllowanceUsage(account, permissionHash); - assertEq(usage.start, start); - assertEq(usage.end, _safeAdd(start, period)); - assertEq(usage.spend, spend); - } - - function test_useRecurringAllowance_success_maxAllowance( - address account, - bytes32 permissionHash, - uint48 start, - uint48 period, - uint160 allowance - ) public { - vm.assume(start > 0); - vm.assume(period > 0); - vm.assume(allowance > 0); - uint256 spend = allowance; - - mockNativeTokenRecurringAllowance.initializeRecurringAllowance( - account, permissionHash, _createRecurringAllowance(start, period, allowance) - ); - - vm.warp(start); - mockNativeTokenRecurringAllowance.useRecurringAllowance(account, permissionHash, spend); - NativeTokenRecurringAllowance.CycleUsage memory usage = - mockNativeTokenRecurringAllowance.getRecurringAllowanceUsage(account, permissionHash); - assertEq(usage.start, start); - assertEq(usage.end, _safeAdd(start, period)); - assertEq(usage.spend, spend); - } - - function test_useRecurringAllowance_success_incrementalSpends( - address account, - bytes32 permissionHash, - uint48 start, - uint48 period, - uint160 allowance, - uint8 n - ) public { - vm.assume(start > 0); - vm.assume(period > 0); - vm.assume(n > 1); - vm.assume(allowance >= n); - uint256 spend = allowance / n; - - mockNativeTokenRecurringAllowance.initializeRecurringAllowance( - account, permissionHash, _createRecurringAllowance(start, period, allowance) - ); - - vm.warp(start); - - uint256 totalSpend = 0; - for (uint256 i = 0; i < n; i++) { - mockNativeTokenRecurringAllowance.useRecurringAllowance(account, permissionHash, spend); - totalSpend += spend; - NativeTokenRecurringAllowance.CycleUsage memory usage = - mockNativeTokenRecurringAllowance.getRecurringAllowanceUsage(account, permissionHash); - assertEq(usage.start, start); - assertEq(usage.end, _safeAdd(start, period)); - assertEq(usage.spend, totalSpend); - } - } -} diff --git a/test/src/mixins/PermissionCallable/PermissionedCall.t.sol b/test/src/mixins/PermissionCallable/PermissionedCall.t.sol deleted file mode 100644 index cf2b40c..0000000 --- a/test/src/mixins/PermissionCallable/PermissionedCall.t.sol +++ /dev/null @@ -1,65 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {Test, console2} from "forge-std/Test.sol"; -import {Errors} from "openzeppelin-contracts/contracts/utils/Errors.sol"; - -import {PermissionCallable} from "../../../../src/mixins/PermissionCallable.sol"; -import {CallErrors} from "../../../../src/utils/CallErrors.sol"; - -import {MockPermissionCallable} from "../../../mocks/MockPermissionCallable.sol"; - -contract PermissionedCallTest is Test { - MockPermissionCallable mockPermissionCallable; - - function setUp() public { - mockPermissionCallable = new MockPermissionCallable(); - } - - function test_permissionedCall_revert_InvalidCallLength(bytes memory call) public { - vm.assume(call.length < 4); - vm.expectRevert(abi.encodeWithSelector(CallErrors.InvalidCallLength.selector)); - mockPermissionCallable.permissionedCall(call); - } - - function test_permissionedCall_revert_notPermissionCallable() public { - bytes4 selector = MockPermissionCallable.notPermissionCallable.selector; - vm.expectRevert(abi.encodeWithSelector(PermissionCallable.NotPermissionCallable.selector, selector)); - mockPermissionCallable.permissionedCall(abi.encodeWithSelector(selector)); - } - - function test_permissionedCall_revert_notPermissionCallable_fuzz(bytes memory call) public { - vm.assume(call.length >= 4); - bytes4 selector = bytes4(call); - vm.assume(selector != MockPermissionCallable.revertNoData.selector); - vm.assume(selector != MockPermissionCallable.revertWithData.selector); - vm.assume(selector != MockPermissionCallable.successNoData.selector); - vm.assume(selector != MockPermissionCallable.successWithData.selector); - vm.expectRevert(abi.encodeWithSelector(PermissionCallable.NotPermissionCallable.selector, selector)); - mockPermissionCallable.permissionedCall(call); - } - - function test_permissionedCall_revert_noData() public { - bytes4 selector = MockPermissionCallable.revertNoData.selector; - vm.expectRevert(Errors.FailedCall.selector); - mockPermissionCallable.permissionedCall(abi.encodeWithSelector(selector)); - } - - function test_permissionedCall_revert_withData(bytes memory revertData) public { - bytes4 selector = MockPermissionCallable.revertWithData.selector; - vm.expectRevert(revertData); - mockPermissionCallable.permissionedCall(abi.encodeWithSelector(selector, revertData)); - } - - function test_permissionedCall_success_noData() public { - bytes4 selector = MockPermissionCallable.successNoData.selector; - bytes memory res = mockPermissionCallable.permissionedCall(abi.encodeWithSelector(selector)); - assertEq(res, bytes("")); - } - - function test_permissionedCall_success_withData(bytes memory resData) public { - bytes4 selector = MockPermissionCallable.successWithData.selector; - bytes memory res = mockPermissionCallable.permissionedCall(abi.encodeWithSelector(selector, resData)); - assertEq(res, abi.encode(resData)); - } -} diff --git a/test/src/permissions/PermissionCallableAllowedContractNativeTokenRecurringAllowance/Constructor.t.sol b/test/src/permissions/PermissionCallableAllowedContractNativeTokenRecurringAllowance/Constructor.t.sol deleted file mode 100644 index 850cf23..0000000 --- a/test/src/permissions/PermissionCallableAllowedContractNativeTokenRecurringAllowance/Constructor.t.sol +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {Test, console2} from "forge-std/Test.sol"; - -import {PermissionCallableAllowedContractNativeTokenRecurringAllowance as PermissionContract} from - "../../../../src/permissions/PermissionCallableAllowedContractNativeTokenRecurringAllowance.sol"; - -import {PermissionCallableAllowedContractNativeTokenRecurringAllowanceBase as PermissionContractBase} from - "../../../base/PermissionCallableAllowedContractNativeTokenRecurringAllowanceBase.sol"; - -contract ConstructorTest is Test, PermissionContractBase { - function setUp() public {} - - function test_constructor_revert_zeroPermissionManager(address magicSpend) public { - address permissionManager = address(0); - vm.assume(magicSpend != address(0)); - - vm.expectRevert(PermissionContract.ZeroAddress.selector); - new PermissionContract(permissionManager, magicSpend); - } - - function test_constructor_revert_zeroMagicSpend(address permissionManager) public { - vm.assume(permissionManager != address(0)); - address magicSpend = address(0); - - vm.expectRevert(PermissionContract.ZeroAddress.selector); - new PermissionContract(permissionManager, magicSpend); - } - - function test_constructor_success(address permissionManager, address magicSpend) public { - vm.assume(permissionManager != address(0) && magicSpend != address(0)); - PermissionContract permissionContract = new PermissionContract(permissionManager, magicSpend); - vm.assertEq(permissionContract.permissionManager(), permissionManager); - vm.assertEq(permissionContract.magicSpend(), magicSpend); - } -} diff --git a/test/src/permissions/PermissionCallableAllowedContractNativeTokenRecurringAllowance/InitializePermission.t.sol b/test/src/permissions/PermissionCallableAllowedContractNativeTokenRecurringAllowance/InitializePermission.t.sol deleted file mode 100644 index c2a4b79..0000000 --- a/test/src/permissions/PermissionCallableAllowedContractNativeTokenRecurringAllowance/InitializePermission.t.sol +++ /dev/null @@ -1,94 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {Test, console2} from "forge-std/Test.sol"; - -import {IPermissionContract} from "../../../../src/interfaces/IPermissionContract.sol"; -import {NativeTokenRecurringAllowance} from "../../../../src/mixins/NativeTokenRecurringAllowance.sol"; -import {PermissionCallableAllowedContractNativeTokenRecurringAllowance as PermissionContract} from - "../../../../src/permissions/PermissionCallableAllowedContractNativeTokenRecurringAllowance.sol"; - -import {PermissionCallableAllowedContractNativeTokenRecurringAllowanceBase as PermissionContractBase} from - "../../../base/PermissionCallableAllowedContractNativeTokenRecurringAllowanceBase.sol"; - -contract InitializePermissionTest is Test, PermissionContractBase { - function setUp() public { - _initializePermissionContract(); - } - - function test_initializePermission_revert_decodingError( - address account, - bytes32 permissionHash, - bytes memory permissionValues - ) public { - vm.expectRevert(); - permissionContract.initializePermission(account, permissionHash, permissionValues); - } - - function test_initializePermission_revert_InvalidInitializePermissionSender( - address sender, - address account, - bytes32 permissionHash, - uint48 start, - uint48 period, - uint160 allowance, - address allowedContract - ) public { - vm.assume(sender != address(permissionManager)); - vm.assume(start > 0); - vm.assume(period > 0); - - vm.prank(sender); - - vm.expectRevert(abi.encodeWithSelector(IPermissionContract.InvalidInitializePermissionSender.selector, sender)); - permissionContract.initializePermission( - account, permissionHash, abi.encode(_createPermissionValues(start, period, allowance, allowedContract)) - ); - } - - function test_initializePermission_success_emitsEvent( - address account, - bytes32 permissionHash, - uint48 start, - uint48 period, - uint160 allowance, - address allowedContract - ) public { - vm.assume(start > 0); - vm.assume(period > 0); - - vm.prank(address(permissionManager)); - - vm.expectEmit(address(permissionContract)); - emit NativeTokenRecurringAllowance.RecurringAllowanceInitialized( - address(account), permissionHash, _createRecurringAllowance(start, period, allowance) - ); - permissionContract.initializePermission( - account, permissionHash, abi.encode(_createPermissionValues(start, period, allowance, allowedContract)) - ); - } - - function test_initializePermission_success_setsState( - address account, - bytes32 permissionHash, - uint48 start, - uint48 period, - uint160 allowance, - address allowedContract - ) public { - vm.assume(start > 0); - vm.assume(period > 0); - - vm.prank(address(permissionManager)); - - permissionContract.initializePermission( - account, permissionHash, abi.encode(_createPermissionValues(start, period, allowance, allowedContract)) - ); - - NativeTokenRecurringAllowance.RecurringAllowance memory recurringAllowance = - permissionContract.getRecurringAllowance(account, permissionHash); - assertEq(recurringAllowance.start, start); - assertEq(recurringAllowance.period, period); - assertEq(recurringAllowance.allowance, allowance); - } -} diff --git a/test/src/permissions/PermissionCallableAllowedContractNativeTokenRecurringAllowance/Integration.t.sol b/test/src/permissions/PermissionCallableAllowedContractNativeTokenRecurringAllowance/Integration.t.sol deleted file mode 100644 index abf25db..0000000 --- a/test/src/permissions/PermissionCallableAllowedContractNativeTokenRecurringAllowance/Integration.t.sol +++ /dev/null @@ -1,11 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {Test, console2} from "forge-std/Test.sol"; - -import {PermissionCallableAllowedContractNativeTokenRecurringAllowanceBase as PermissionContractBase} from - "../../../base/PermissionCallableAllowedContractNativeTokenRecurringAllowanceBase.sol"; - -contract IntegrationTest is Test, PermissionContractBase { - function setUp() public {} -} diff --git a/test/src/permissions/PermissionCallableAllowedContractNativeTokenRecurringAllowance/UseRecurringAllowance.t.sol b/test/src/permissions/PermissionCallableAllowedContractNativeTokenRecurringAllowance/UseRecurringAllowance.t.sol deleted file mode 100644 index 310dedd..0000000 --- a/test/src/permissions/PermissionCallableAllowedContractNativeTokenRecurringAllowance/UseRecurringAllowance.t.sol +++ /dev/null @@ -1,332 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {Test, console2} from "forge-std/Test.sol"; - -import {NativeTokenRecurringAllowance} from "../../../../src/mixins/NativeTokenRecurringAllowance.sol"; - -import {PermissionCallableAllowedContractNativeTokenRecurringAllowanceBase as PermissionContractBase} from - "../../../base/PermissionCallableAllowedContractNativeTokenRecurringAllowanceBase.sol"; - -contract UseRecurringAllowanceTest is Test, PermissionContractBase { - function setUp() public { - _initializePermissionContract(); - } - - function test_useRecurringAllowance_revert_invalidInitialization(bytes32 permissionHash, uint256 spend) public { - vm.assume(spend > 0); - - vm.expectRevert(NativeTokenRecurringAllowance.InvalidInitialization.selector); - vm.prank(address(account)); - permissionContract.useRecurringAllowance(permissionHash, spend); - } - - function test_useRecurringAllowance_revert_beforeRecurringAllowanceStart( - bytes32 permissionHash, - uint48 start, - uint48 period, - uint160 allowance, - address allowedContract, - uint256 spend - ) public { - vm.assume(start > 0); - vm.assume(period > 0); - vm.assume(allowance > 0); - vm.assume(spend > 0); - - vm.prank(address(permissionManager)); - permissionContract.initializePermission( - address(account), - permissionHash, - abi.encode(_createPermissionValues(start, period, allowance, allowedContract)) - ); - - vm.warp(start - 1); - vm.expectRevert( - abi.encodeWithSelector(NativeTokenRecurringAllowance.BeforeRecurringAllowanceStart.selector, start) - ); - vm.prank(address(account)); - permissionContract.useRecurringAllowance(permissionHash, spend); - } - - function test_useRecurringAllowance_revert_spendValueOverflow( - bytes32 permissionHash, - uint48 start, - uint48 period, - uint160 allowance, - address allowedContract, - uint256 spend - ) public { - vm.assume(start > 0); - vm.assume(period > 0); - vm.assume(allowance > 0); - vm.assume(spend > type(uint160).max); - - vm.prank(address(permissionManager)); - permissionContract.initializePermission( - address(account), - permissionHash, - abi.encode(_createPermissionValues(start, period, allowance, allowedContract)) - ); - - vm.warp(start); - vm.expectRevert(abi.encodeWithSelector(NativeTokenRecurringAllowance.SpendValueOverflow.selector, spend)); - vm.prank(address(account)); - permissionContract.useRecurringAllowance(permissionHash, spend); - } - - function test_useRecurringAllowance_revert_exceededRecurringAllowance( - bytes32 permissionHash, - uint48 start, - uint48 period, - uint160 allowance, - address allowedContract, - uint160 spend - ) public { - vm.assume(start > 0); - vm.assume(period > 0); - vm.assume(allowance > 0); - vm.assume(allowance < type(uint160).max - 1); - vm.assume(spend > allowance); - - vm.prank(address(permissionManager)); - permissionContract.initializePermission( - address(account), - permissionHash, - abi.encode(_createPermissionValues(start, period, allowance, allowedContract)) - ); - - vm.warp(start); - vm.expectRevert( - abi.encodeWithSelector(NativeTokenRecurringAllowance.ExceededRecurringAllowance.selector, spend, allowance) - ); - vm.prank(address(account)); - permissionContract.useRecurringAllowance(permissionHash, spend); - } - - function test_useRecurringAllowance_revert_exceededRecurringAllowance_accruedSpend( - bytes32 permissionHash, - uint48 start, - uint48 period, - uint160 allowance, - address allowedContract - ) public { - vm.assume(start > 0); - vm.assume(period > 0); - vm.assume(allowance > 0); - vm.assume(allowance < type(uint160).max - 1); - uint256 spend = allowance; - - vm.prank(address(permissionManager)); - permissionContract.initializePermission( - address(account), - permissionHash, - abi.encode(_createPermissionValues(start, period, allowance, allowedContract)) - ); - - vm.warp(start); - // first spend ok, within allowance - vm.prank(address(account)); - permissionContract.useRecurringAllowance(permissionHash, spend); - // second spend not ok, exceeds allowance - vm.expectRevert( - abi.encodeWithSelector( - NativeTokenRecurringAllowance.ExceededRecurringAllowance.selector, spend + 1, allowance - ) - ); - vm.prank(address(account)); - permissionContract.useRecurringAllowance(permissionHash, 1); - } - - function test_useRecurringAllowance_success_noSpend(bytes32 permissionHash) public { - uint256 spend = 0; - vm.prank(address(account)); - permissionContract.useRecurringAllowance(permissionHash, spend); - } - - function test_useRecurringAllowance_success_emitsEvent( - bytes32 permissionHash, - uint48 start, - uint48 period, - uint160 allowance, - address allowedContract, - uint160 spend - ) public { - vm.assume(start > 0); - vm.assume(period > 0); - vm.assume(allowance > 0); - vm.assume(spend < allowance); - vm.assume(spend > 0); - - vm.prank(address(permissionManager)); - permissionContract.initializePermission( - address(account), - permissionHash, - abi.encode(_createPermissionValues(start, period, allowance, allowedContract)) - ); - - vm.warp(start); - vm.prank(address(account)); - vm.expectEmit(address(permissionContract)); - emit NativeTokenRecurringAllowance.RecurringAllowanceUsed( - address(account), - permissionHash, - NativeTokenRecurringAllowance.CycleUsage({start: start, end: _safeAdd(start, period), spend: spend}) - ); - permissionContract.useRecurringAllowance(permissionHash, spend); - } - - function test_useRecurringAllowance_success_setsState( - bytes32 permissionHash, - uint48 start, - uint48 period, - uint160 allowance, - address allowedContract, - uint160 spend - ) public { - vm.assume(start > 0); - vm.assume(period > 0); - vm.assume(allowance > 0); - vm.assume(spend < allowance); - - vm.prank(address(permissionManager)); - permissionContract.initializePermission( - address(account), - permissionHash, - abi.encode(_createPermissionValues(start, period, allowance, allowedContract)) - ); - - vm.warp(start); - vm.prank(address(account)); - permissionContract.useRecurringAllowance(permissionHash, spend); - NativeTokenRecurringAllowance.CycleUsage memory usage = - permissionContract.getRecurringAllowanceUsage(address(account), permissionHash); - assertEq(usage.start, start); - assertEq(usage.end, _safeAdd(start, period)); - assertEq(usage.spend, spend); - } - - function test_useRecurringAllowance_success_maxAllowance( - bytes32 permissionHash, - uint48 start, - uint48 period, - uint160 allowance, - address allowedContract - ) public { - vm.assume(start > 0); - vm.assume(period > 0); - vm.assume(allowance > 0); - uint256 spend = allowance; - - vm.prank(address(permissionManager)); - permissionContract.initializePermission( - address(account), - permissionHash, - abi.encode(_createPermissionValues(start, period, allowance, allowedContract)) - ); - - vm.warp(start); - vm.prank(address(account)); - permissionContract.useRecurringAllowance(permissionHash, spend); - NativeTokenRecurringAllowance.CycleUsage memory usage = - permissionContract.getRecurringAllowanceUsage(address(account), permissionHash); - assertEq(usage.start, start); - assertEq(usage.end, _safeAdd(start, period)); - assertEq(usage.spend, spend); - } - - function test_useRecurringAllowance_success_incrementalSpends( - bytes32 permissionHash, - uint48 start, - uint48 period, - uint160 allowance, - address allowedContract, - uint8 n - ) public { - vm.assume(start > 0); - vm.assume(period > 0); - vm.assume(n > 1); - vm.assume(allowance >= n); - uint256 spend = allowance / n; - - vm.prank(address(permissionManager)); - permissionContract.initializePermission( - address(account), - permissionHash, - abi.encode(_createPermissionValues(start, period, allowance, allowedContract)) - ); - - vm.warp(start); - - uint256 totalSpend = 0; - for (uint256 i = 0; i < n; i++) { - vm.prank(address(account)); - permissionContract.useRecurringAllowance(permissionHash, spend); - totalSpend += spend; - NativeTokenRecurringAllowance.CycleUsage memory usage = - permissionContract.getRecurringAllowanceUsage(address(account), permissionHash); - assertEq(usage.start, start); - assertEq(usage.end, _safeAdd(start, period)); - assertEq(usage.spend, totalSpend); - } - } - - // the unique and most important test is this one to make sure accounting of recurring allowances are independent - // for different senders, even on the same permissionHash - function test_useRecurringAllowance_success_perSenderAccounting( - address otherSender, - bytes32 permissionHash, - uint48 start, - uint48 period, - uint160 allowance, - address allowedContract, - uint160 spend - ) public { - vm.assume(start > 0); - vm.assume(period > 0); - vm.assume(allowance > 0); - vm.assume(spend < allowance); - vm.assume(spend > 0); - vm.assume(otherSender != address(account)); - - vm.prank(address(permissionManager)); - permissionContract.initializePermission( - address(account), - permissionHash, - abi.encode(_createPermissionValues(start, period, allowance, allowedContract)) - ); - - vm.warp(start); - vm.prank(address(account)); - permissionContract.useRecurringAllowance(permissionHash, spend); - NativeTokenRecurringAllowance.CycleUsage memory usage = - permissionContract.getRecurringAllowanceUsage(address(account), permissionHash); - assertEq(usage.start, start); - assertEq(usage.end, _safeAdd(start, period)); - assertEq(usage.spend, spend); - - // checking same permissionHash on other sender fails because hasn't been initialized - vm.expectRevert(abi.encodeWithSelector(NativeTokenRecurringAllowance.InvalidInitialization.selector)); - usage = permissionContract.getRecurringAllowanceUsage(otherSender, permissionHash); - - vm.prank(address(permissionManager)); - permissionContract.initializePermission( - otherSender, permissionHash, abi.encode(_createPermissionValues(start, period, allowance, allowedContract)) - ); - - vm.startPrank(otherSender); - permissionContract.useRecurringAllowance(permissionHash, spend - 1); - - // original recurring allowance usage untouched - usage = permissionContract.getRecurringAllowanceUsage(address(account), permissionHash); - assertEq(usage.start, start); - assertEq(usage.end, _safeAdd(start, period)); - assertEq(usage.spend, spend); - - // new recurring allowance usage for other sender - usage = permissionContract.getRecurringAllowanceUsage(otherSender, permissionHash); - assertEq(usage.start, start); - assertEq(usage.end, _safeAdd(start, period)); - assertEq(usage.spend, spend - 1); - } -} diff --git a/test/src/permissions/PermissionCallableAllowedContractNativeTokenRecurringAllowance/ValidatePermission.t.sol b/test/src/permissions/PermissionCallableAllowedContractNativeTokenRecurringAllowance/ValidatePermission.t.sol deleted file mode 100644 index d57f41e..0000000 --- a/test/src/permissions/PermissionCallableAllowedContractNativeTokenRecurringAllowance/ValidatePermission.t.sol +++ /dev/null @@ -1,442 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {Test, console2} from "forge-std/Test.sol"; - -import {MagicSpend} from "magic-spend/MagicSpend.sol"; -import {CoinbaseSmartWallet} from "smart-wallet/CoinbaseSmartWallet.sol"; - -import {PermissionManager} from "../../../../src/PermissionManager.sol"; -import {IPermissionCallable} from "../../../../src/interfaces/IPermissionCallable.sol"; -import {PermissionCallableAllowedContractNativeTokenRecurringAllowance as PermissionContract} from - "../../../../src/permissions/PermissionCallableAllowedContractNativeTokenRecurringAllowance.sol"; -import {CallErrors} from "../../../../src/utils/CallErrors.sol"; -import {UserOperation} from "../../../../src/utils/UserOperationLib.sol"; - -import {PermissionCallableAllowedContractNativeTokenRecurringAllowanceBase as PermissionContractBase} from - "../../../base/PermissionCallableAllowedContractNativeTokenRecurringAllowanceBase.sol"; - -contract ValidatePermissionTest is Test, PermissionContractBase { - function setUp() public { - _initializePermissionContract(); - } - - function test_validatePermission_revert_noPaymaster(bytes32 permissionHash, bytes memory permissionValues) public { - UserOperation memory userOp = _createUserOperation(); - userOp.paymasterAndData = abi.encodePacked(address(0)); - - vm.expectRevert(PermissionContract.GasSponsorshipRequired.selector); - permissionContract.validatePermission(permissionHash, permissionValues, userOp); - } - - function test_validatePermission_revert_magicSpendPaymaster(bytes32 permissionHash, bytes memory permissionValues) - public - { - UserOperation memory userOp = _createUserOperation(); - userOp.paymasterAndData = abi.encodePacked(address(magicSpend)); - - vm.expectRevert(PermissionContract.GasSponsorshipRequired.selector); - permissionContract.validatePermission(permissionHash, permissionValues, userOp); - } - - function test_validatePermission_revert_decodeError( - bytes32 permissionHash, - bytes memory permissionValues, - address paymaster - ) public { - vm.assume(paymaster != address(0)); - vm.assume(paymaster != address(magicSpend)); - UserOperation memory userOp = _createUserOperation(); - userOp.paymasterAndData = abi.encodePacked(paymaster); - - vm.expectRevert(); - permissionContract.validatePermission(permissionHash, permissionValues, userOp); - } - - function test_validatePermission_revert_InvalidCallLength( - address paymaster, - uint160 allowance, - address allowedContract, - address target, - bytes3 data, - uint256 spend - ) public { - vm.assume(paymaster != address(0)); - vm.assume(paymaster != address(magicSpend)); - - PermissionManager.Permission memory permission = _createPermission(); - bytes32 permissionHash = permissionManager.hashPermission(permission); - UserOperation memory userOp = _createUserOperation(); - userOp.paymasterAndData = abi.encodePacked(paymaster); - - CoinbaseSmartWallet.Call[] memory calls = new CoinbaseSmartWallet.Call[](3); - calls[0] = _createCall(address(permissionManager), 0, _createBeforeCallsData(permission, userOp)); - calls[1] = _createCall(target, spend, abi.encodePacked(data)); - calls[2] = _createUseRecurringAllowanceCall(address(permissionContract), permissionHash, spend); - bytes memory callData = abi.encodeWithSelector(CoinbaseSmartWallet.executeBatch.selector, calls); - userOp.callData = callData; - - vm.expectRevert(CallErrors.InvalidCallLength.selector); - permissionContract.validatePermission( - permissionHash, abi.encode(_createPermissionValues(allowance, allowedContract)), userOp - ); - } - - function test_validatePermission_revert_permissionedCall_TargetNotAllowed( - address paymaster, - uint160 allowance, - address allowedContract, - address target - ) public { - vm.assume(paymaster != address(0)); - vm.assume(paymaster != address(magicSpend)); - vm.assume(target != allowedContract); - uint256 spend = 0; - - PermissionManager.Permission memory permission = _createPermission(); - bytes32 permissionHash = permissionManager.hashPermission(permission); - UserOperation memory userOp = _createUserOperation(); - userOp.paymasterAndData = abi.encodePacked(paymaster); - - CoinbaseSmartWallet.Call[] memory calls = new CoinbaseSmartWallet.Call[](3); - calls[0] = _createCall(address(permissionManager), 0, _createBeforeCallsData(permission)); - calls[1] = _createPermissionedCall(target, spend, hex""); - calls[2] = _createUseRecurringAllowanceCall(address(permissionContract), permissionHash, spend); - bytes memory callData = abi.encodeWithSelector(CoinbaseSmartWallet.executeBatch.selector, calls); - userOp.callData = callData; - - vm.expectRevert(abi.encodeWithSelector(CallErrors.TargetNotAllowed.selector, target)); - permissionContract.validatePermission( - permissionHash, abi.encode(_createPermissionValues(allowance, allowedContract)), userOp - ); - } - - function test_validatePermission_revert_withdraw_TargetNotAllowed( - address paymaster, - uint160 allowance, - address allowedContract, - address target, - address asset, - uint256 amount - ) public { - vm.assume(paymaster != address(0)); - vm.assume(paymaster != address(magicSpend)); - vm.assume(target != address(magicSpend)); - uint256 spend = 0; - - PermissionManager.Permission memory permission = _createPermission(); - bytes32 permissionHash = permissionManager.hashPermission(permission); - UserOperation memory userOp = _createUserOperation(); - userOp.paymasterAndData = abi.encodePacked(paymaster); - - CoinbaseSmartWallet.Call[] memory calls = new CoinbaseSmartWallet.Call[](3); - calls[0] = _createCall(address(permissionManager), 0, _createBeforeCallsData(permission)); - calls[1] = _createWithdrawCall(target, asset, amount); - calls[2] = _createUseRecurringAllowanceCall(address(permissionContract), permissionHash, spend); - bytes memory callData = abi.encodeWithSelector(CoinbaseSmartWallet.executeBatch.selector, calls); - userOp.callData = callData; - - vm.expectRevert(abi.encodeWithSelector(CallErrors.TargetNotAllowed.selector, target)); - permissionContract.validatePermission( - permissionHash, abi.encode(_createPermissionValues(allowance, allowedContract)), userOp - ); - } - - function test_validatePermission_revert_InvalidWithdrawAsset_withdraw( - address paymaster, - uint160 allowance, - address allowedContract, - address asset, - uint256 amount - ) public { - vm.assume(paymaster != address(0)); - vm.assume(paymaster != address(magicSpend)); - vm.assume(asset != address(0)); - uint256 spend = 0; - - PermissionManager.Permission memory permission = _createPermission(); - bytes32 permissionHash = permissionManager.hashPermission(permission); - UserOperation memory userOp = _createUserOperation(); - userOp.paymasterAndData = abi.encodePacked(paymaster); - - CoinbaseSmartWallet.Call[] memory calls = new CoinbaseSmartWallet.Call[](3); - calls[0] = _createCall(address(permissionManager), 0, _createBeforeCallsData(permission)); - calls[1] = _createWithdrawCall(address(magicSpend), asset, amount); - calls[2] = _createUseRecurringAllowanceCall(address(permissionContract), permissionHash, spend); - bytes memory callData = abi.encodeWithSelector(CoinbaseSmartWallet.executeBatch.selector, calls); - userOp.callData = callData; - - vm.expectRevert(abi.encodeWithSelector(PermissionContract.InvalidWithdrawAsset.selector, asset)); - permissionContract.validatePermission( - permissionHash, abi.encode(_createPermissionValues(allowance, allowedContract)), userOp - ); - } - - function test_validatePermission_revert_SelectorNotAllowed( - address paymaster, - uint160 allowance, - address allowedContract, - address target, - uint256 value, - bytes4 selector - ) public { - vm.assume(paymaster != address(0)); - vm.assume(paymaster != address(magicSpend)); - vm.assume(selector != IPermissionCallable.permissionedCall.selector); - vm.assume(selector != MagicSpend.withdraw.selector); - uint256 spend = 0; - - PermissionManager.Permission memory permission = _createPermission(); - bytes32 permissionHash = permissionManager.hashPermission(permission); - UserOperation memory userOp = _createUserOperation(); - userOp.paymasterAndData = abi.encodePacked(paymaster); - - CoinbaseSmartWallet.Call[] memory calls = new CoinbaseSmartWallet.Call[](3); - calls[0] = _createCall(address(permissionManager), 0, _createBeforeCallsData(permission)); - calls[1] = _createCall(target, value, abi.encode(selector)); - calls[2] = _createUseRecurringAllowanceCall(address(permissionContract), permissionHash, spend); - bytes memory callData = abi.encodeWithSelector(CoinbaseSmartWallet.executeBatch.selector, calls); - userOp.callData = callData; - - vm.expectRevert(abi.encodeWithSelector(CallErrors.SelectorNotAllowed.selector, selector)); - permissionContract.validatePermission( - permissionHash, abi.encode(_createPermissionValues(allowance, allowedContract)), userOp - ); - } - - function test_validatePermission_revert_missingUseRecurringAllowanceCall( - address paymaster, - uint160 allowance, - address allowedContract, - address target - ) public { - vm.assume(paymaster != address(0)); - vm.assume(paymaster != address(magicSpend)); - vm.assume(target != allowedContract); - - PermissionManager.Permission memory permission = _createPermission(); - bytes32 permissionHash = permissionManager.hashPermission(permission); - UserOperation memory userOp = _createUserOperation(); - userOp.paymasterAndData = abi.encodePacked(paymaster); - - CoinbaseSmartWallet.Call[] memory calls = new CoinbaseSmartWallet.Call[](1); - calls[0] = _createCall(address(permissionManager), 0, _createBeforeCallsData(permission)); - bytes memory callData = abi.encodeWithSelector(CoinbaseSmartWallet.executeBatch.selector, calls); - userOp.callData = callData; - - vm.expectRevert(PermissionContract.InvalidUseRecurringAllowanceCall.selector); - permissionContract.validatePermission( - permissionHash, abi.encode(_createPermissionValues(allowance, allowedContract)), userOp - ); - } - - function test_validatePermission_revert_invalidUseRecurringAllowanceCallTarget( - address paymaster, - uint160 allowance, - address allowedContract, - address target - ) public { - vm.assume(paymaster != address(0)); - vm.assume(paymaster != address(magicSpend)); - vm.assume(target != address(permissionContract)); - - PermissionManager.Permission memory permission = _createPermission(); - bytes32 permissionHash = permissionManager.hashPermission(permission); - UserOperation memory userOp = _createUserOperation(); - userOp.paymasterAndData = abi.encodePacked(paymaster); - - CoinbaseSmartWallet.Call[] memory calls = new CoinbaseSmartWallet.Call[](2); - calls[0] = _createCall(address(permissionManager), 0, _createBeforeCallsData(permission)); - calls[1] = _createUseRecurringAllowanceCall(target, permissionHash, 0); - bytes memory callData = abi.encodeWithSelector(CoinbaseSmartWallet.executeBatch.selector, calls); - userOp.callData = callData; - - vm.expectRevert(PermissionContract.InvalidUseRecurringAllowanceCall.selector); - permissionContract.validatePermission( - permissionHash, abi.encode(_createPermissionValues(allowance, allowedContract)), userOp - ); - } - - function test_validatePermission_revert_invalidUseRecurringAllowanceCallData_permissionHash( - bytes32 invalidPermissionHash, - address paymaster, - uint160 allowance, - address allowedContract - ) public { - vm.assume(paymaster != address(0)); - vm.assume(paymaster != address(magicSpend)); - uint256 spend = 0; - - PermissionManager.Permission memory permission = _createPermission(); - bytes32 permissionHash = permissionManager.hashPermission(permission); - - vm.assume(invalidPermissionHash != permissionHash); - - UserOperation memory userOp = _createUserOperation(); - userOp.paymasterAndData = abi.encodePacked(paymaster); - - CoinbaseSmartWallet.Call[] memory calls = new CoinbaseSmartWallet.Call[](2); - calls[0] = _createCall(address(permissionManager), 0, _createBeforeCallsData(permission)); - calls[1] = _createUseRecurringAllowanceCall(address(permissionContract), invalidPermissionHash, spend); - bytes memory callData = abi.encodeWithSelector(CoinbaseSmartWallet.executeBatch.selector, calls); - userOp.callData = callData; - - vm.expectRevert(PermissionContract.InvalidUseRecurringAllowanceCall.selector); - permissionContract.validatePermission( - permissionHash, abi.encode(_createPermissionValues(allowance, allowedContract)), userOp - ); - } - - function test_validatePermission_revert_invalidUseRecurringAllowanceCallData_spendNoCalls( - address paymaster, - uint160 allowance, - address allowedContract, - uint160 invalidSpend - ) public { - vm.assume(paymaster != address(0)); - vm.assume(paymaster != address(magicSpend)); - vm.assume(invalidSpend != 0); - - PermissionManager.Permission memory permission = _createPermission(); - bytes32 permissionHash = permissionManager.hashPermission(permission); - UserOperation memory userOp = _createUserOperation(); - userOp.paymasterAndData = abi.encodePacked(paymaster); - - CoinbaseSmartWallet.Call[] memory calls = new CoinbaseSmartWallet.Call[](2); - calls[0] = _createCall(address(permissionManager), 0, _createBeforeCallsData(permission)); - calls[1] = _createUseRecurringAllowanceCall(address(permissionContract), permissionHash, invalidSpend); - bytes memory callData = abi.encodeWithSelector(CoinbaseSmartWallet.executeBatch.selector, calls); - userOp.callData = callData; - - vm.expectRevert(PermissionContract.InvalidUseRecurringAllowanceCall.selector); - permissionContract.validatePermission( - permissionHash, abi.encode(_createPermissionValues(allowance, allowedContract)), userOp - ); - } - - function test_validatePermission_revert_invalidUseRecurringAllowanceCallData_spendSomeCalls( - address paymaster, - uint160 allowance, - address allowedContract, - uint160 spend, - uint160 invalidSpend - ) public { - vm.assume(paymaster != address(0)); - vm.assume(paymaster != address(magicSpend)); - vm.assume(invalidSpend != spend); - - PermissionManager.Permission memory permission = _createPermission(); - bytes32 permissionHash = permissionManager.hashPermission(permission); - UserOperation memory userOp = _createUserOperation(); - userOp.paymasterAndData = abi.encodePacked(paymaster); - - CoinbaseSmartWallet.Call[] memory calls = new CoinbaseSmartWallet.Call[](3); - calls[0] = _createCall(address(permissionManager), 0, _createBeforeCallsData(permission)); - calls[1] = _createPermissionedCall(allowedContract, spend, hex""); - calls[2] = _createUseRecurringAllowanceCall(address(permissionContract), permissionHash, invalidSpend); - bytes memory callData = abi.encodeWithSelector(CoinbaseSmartWallet.executeBatch.selector, calls); - userOp.callData = callData; - - vm.expectRevert(PermissionContract.InvalidUseRecurringAllowanceCall.selector); - permissionContract.validatePermission( - permissionHash, abi.encode(_createPermissionValues(allowance, allowedContract)), userOp - ); - } - - function test_validatePermission_success_permissionedCall( - address paymaster, - uint160 allowance, - address allowedContract, - uint160 spend - ) public view { - vm.assume(paymaster != address(0)); - vm.assume(paymaster != address(magicSpend)); - - PermissionManager.Permission memory permission = _createPermission(); - bytes32 permissionHash = permissionManager.hashPermission(permission); - UserOperation memory userOp = _createUserOperation(); - userOp.paymasterAndData = abi.encodePacked(paymaster); - - CoinbaseSmartWallet.Call[] memory calls = new CoinbaseSmartWallet.Call[](3); - calls[0] = _createCall(address(permissionManager), 0, _createBeforeCallsData(permission)); - calls[1] = _createPermissionedCall(allowedContract, spend, hex""); - calls[2] = _createUseRecurringAllowanceCall(address(permissionContract), permissionHash, spend); - bytes memory callData = abi.encodeWithSelector(CoinbaseSmartWallet.executeBatch.selector, calls); - userOp.callData = callData; - - permissionContract.validatePermission( - permissionHash, abi.encode(_createPermissionValues(allowance, allowedContract)), userOp - ); - } - - function test_validatePermission_success_withdraw( - address paymaster, - uint160 allowance, - address allowedContract, - uint256 withdrawAmount - ) public view { - vm.assume(paymaster != address(0)); - vm.assume(paymaster != address(magicSpend)); - address asset = address(0); - uint256 spend = 0; - - PermissionManager.Permission memory permission = _createPermission(); - bytes32 permissionHash = permissionManager.hashPermission(permission); - UserOperation memory userOp = _createUserOperation(); - userOp.paymasterAndData = abi.encodePacked(paymaster); - - CoinbaseSmartWallet.Call[] memory calls = new CoinbaseSmartWallet.Call[](3); - calls[0] = _createCall(address(permissionManager), 0, _createBeforeCallsData(permission)); - calls[1] = _createWithdrawCall(address(magicSpend), asset, withdrawAmount); - calls[2] = _createUseRecurringAllowanceCall(address(permissionContract), permissionHash, spend); - bytes memory callData = abi.encodeWithSelector(CoinbaseSmartWallet.executeBatch.selector, calls); - userOp.callData = callData; - - permissionContract.validatePermission( - permissionHash, abi.encode(_createPermissionValues(allowance, allowedContract)), userOp - ); - } - - function test_validatePermission_success_batchCalls( - address paymaster, - uint160 allowance, - address allowedContract, - uint160 totalSpend, - uint256 withdrawAmount, - uint8 n - ) public view { - vm.assume(paymaster != address(0)); - vm.assume(paymaster != address(magicSpend)); - vm.assume(n > 0); - vm.assume(totalSpend > n); - address withdrawAsset = address(0); - - PermissionManager.Permission memory permission = _createPermission(); - bytes32 permissionHash = permissionManager.hashPermission(permission); - UserOperation memory userOp = _createUserOperation(); - userOp.paymasterAndData = abi.encodePacked(paymaster); - - uint256 callsLen = 4 + uint16(n); // beforeCalls + withdraw + (n + 1) * permissionedCall + useRecurringAllowance - CoinbaseSmartWallet.Call[] memory calls = new CoinbaseSmartWallet.Call[](callsLen); - calls[0] = _createCall(address(permissionManager), 0, _createBeforeCallsData(permission)); - calls[1] = _createWithdrawCall(address(magicSpend), withdrawAsset, withdrawAmount); - // add n permissionedCalls for a portion of totalSpend - for (uint256 i = 0; i < n; i++) { - uint160 spend = totalSpend / n; - calls[2 + i] = _createPermissionedCall(allowedContract, spend, hex""); - } - // additional spend for remainder of totalSpend / n so sum is still totalSpend - calls[callsLen - 2] = _createPermissionedCall(allowedContract, totalSpend % n, hex""); - calls[callsLen - 1] = _createUseRecurringAllowanceCall(address(permissionContract), permissionHash, totalSpend); - bytes memory callData = abi.encodeWithSelector(CoinbaseSmartWallet.executeBatch.selector, calls); - userOp.callData = callData; - - permissionContract.validatePermission( - permissionHash, abi.encode(_createPermissionValues(allowance, allowedContract)), userOp - ); - } - - function test_validatePermission_success_erc4337Compliance() public pure { - revert("unimplemented"); - } -} diff --git a/test/src/utils/BytesLib/Eq.t.sol b/test/src/utils/BytesLib/Eq.t.sol deleted file mode 100644 index 868e495..0000000 --- a/test/src/utils/BytesLib/Eq.t.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {Test, console2} from "forge-std/Test.sol"; - -import {BytesLib} from "../../../../src/utils/BytesLib.sol"; - -contract EqTest is Test { - function setUp() public {} - - function test_eq_true_sameInputs(bytes memory data) public pure { - assertTrue(BytesLib.eq(data, data)); - } - - function test_eq_false_differentInputs(bytes memory a, bytes memory b) public pure { - vm.assume(keccak256(a) != keccak256(b)); - assertTrue(!BytesLib.eq(a, b)); - } -} diff --git a/test/src/utils/BytesLib/TrimSelector.t.sol b/test/src/utils/BytesLib/TrimSelector.t.sol deleted file mode 100644 index 491ffcd..0000000 --- a/test/src/utils/BytesLib/TrimSelector.t.sol +++ /dev/null @@ -1,21 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {Test, console2} from "forge-std/Test.sol"; - -import {BytesLib} from "../../../../src/utils/BytesLib.sol"; - -contract TrimSelectorTest is Test { - function setUp() public {} - - function test_trimSelector_eq_args(bytes4 selector, bytes memory data) public pure { - bytes memory trimmed = BytesLib.trimSelector(abi.encodeWithSelector(selector, data)); - assertEq(trimmed, abi.encode(data)); - } - - function test_trimSelector_eq_calldataSlice(bytes calldata data) public pure { - vm.assume(data.length > 4); - bytes memory trimmed = BytesLib.trimSelector(data); - assertEq(trimmed, data[4:]); - } -} diff --git a/test/src/utils/SignatureCheckerLib/IsValidSignatureNow.t.sol b/test/src/utils/SignatureCheckerLib/IsValidSignatureNow.t.sol deleted file mode 100644 index 373e4ca..0000000 --- a/test/src/utils/SignatureCheckerLib/IsValidSignatureNow.t.sol +++ /dev/null @@ -1,79 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {Test, console2} from "forge-std/Test.sol"; - -import {SignatureCheckerLib} from "../../../../src/utils/SignatureCheckerLib.sol"; - -import {Base} from "../../../base/Base.sol"; -import {MockContractSigner} from "../../../mocks/MockContractSigner.sol"; - -contract IsValidSignatureNowTest is Test, Base { - function setUp() public { - _initialize(); - } - - function test_isValidSignatureNow_revert_InvalidSignerBytesLength( - bytes32 hash, - bytes memory signature, - bytes memory signerBytes - ) public { - vm.assume(signerBytes.length != 32); - vm.assume(signerBytes.length != 64); - - vm.expectRevert(abi.encodeWithSelector(SignatureCheckerLib.InvalidSignerBytesLength.selector, signerBytes)); - SignatureCheckerLib.isValidSignatureNow(hash, signature, signerBytes); - } - - function test_isValidSignatureNow_revert_InvalidEthereumAddressSigner( - bytes32 hash, - bytes memory signature, - uint256 signer - ) public { - vm.assume(signer > type(uint160).max); - - vm.expectRevert( - abi.encodeWithSelector(SignatureCheckerLib.InvalidEthereumAddressSigner.selector, abi.encode(signer)) - ); - SignatureCheckerLib.isValidSignatureNow(hash, signature, abi.encode(signer)); - } - - function test_isValidSignatureNow_revert_abiDecodeError( - bytes32 hash, - bytes memory signature, - bytes memory signerBytes - ) public { - vm.assume(signerBytes.length == 64); - - vm.expectRevert(); - SignatureCheckerLib.isValidSignatureNow(hash, signature, signerBytes); - } - - function test_isValidSignatureNow_success_EOA(bytes32 hash, uint256 privateKey) public view { - // private key must be less than the secp256k1 curve order - vm.assume(privateKey < 115792089237316195423570985008687907852837564279074904382605163141518161494337); - vm.assume(privateKey != 0); - - address signer = vm.addr(privateKey); - bytes memory signature = _sign(privateKey, hash); - assertTrue(SignatureCheckerLib.isValidSignatureNow(hash, signature, abi.encode(signer))); - } - - function test_isValidSignatureNow_success_smartContract(bytes32 hash, uint256 privateKey) public { - // private key must be less than the secp256k1 curve order - vm.assume(privateKey < 115792089237316195423570985008687907852837564279074904382605163141518161494337); - vm.assume(privateKey != 0); - - address signer = vm.addr(privateKey); - bytes memory signature = _sign(privateKey, hash); - - MockContractSigner contractSigner = new MockContractSigner(signer); - assertTrue(SignatureCheckerLib.isValidSignatureNow(hash, signature, abi.encode(address(contractSigner)))); - } - - function test_isValidSignatureNow_success_p256(bytes32 hash) public view { - bytes memory signature = _signP256(p256PrivateKey, hash); - - assertTrue(SignatureCheckerLib.isValidSignatureNow(hash, signature, p256PublicKey)); - } -} diff --git a/test/src/utils/UserOperationLib/GetUserOpHash.t.sol b/test/src/utils/UserOperationLib/GetUserOpHash.t.sol deleted file mode 100644 index 00bdd62..0000000 --- a/test/src/utils/UserOperationLib/GetUserOpHash.t.sol +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.23; - -import {IEntryPoint} from "account-abstraction/interfaces/IEntryPoint.sol"; -import {Test, console2} from "forge-std/Test.sol"; - -import {UserOperation, UserOperationLib} from "../../../../src/utils/UserOperationLib.sol"; - -import {Base} from "../../../base/Base.sol"; - -contract GetUserOpHashTest is Test, Base { - function setUp() public {} - - function test_getUserOpHash_eq_EntryPointGetUserOpHash() public { - UserOperation memory userOp = _createUserOperation(); - - vm.createSelectFork(BASE_SEPOLIA_RPC); - assertEq(UserOperationLib.getUserOpHash(userOp), IEntryPoint(ENTRY_POINT_V06).getUserOpHash(userOp)); - } -}