diff --git a/lib/delegatable-framework b/lib/delegatable-framework index 2f21b6ba..d0ebab53 160000 --- a/lib/delegatable-framework +++ b/lib/delegatable-framework @@ -1 +1 @@ -Subproject commit 2f21b6babc4bfb44cab84a730ac1b16a757eeaef +Subproject commit d0ebab5386503824730fdc0e48924f362d8290d0 diff --git a/packages/7715-permission-types/CHANGELOG.md b/packages/7715-permission-types/CHANGELOG.md index dbd27396..1763fac9 100644 --- a/packages/7715-permission-types/CHANGELOG.md +++ b/packages/7715-permission-types/CHANGELOG.md @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- New permission type `token-approval-revocation` ([#226](https://github.com/MetaMask/smart-accounts-kit/pull/226)) + +### Deprecated + +- Deprecated `erc20-token-revocation` in favor of `token-approval-revocation` ([#226](https://github.com/MetaMask/smart-accounts-kit/pull/226)) + ## [0.6.0] ### Added diff --git a/packages/7715-permission-types/src/index.ts b/packages/7715-permission-types/src/index.ts index 72608ed7..cb4553a4 100644 --- a/packages/7715-permission-types/src/index.ts +++ b/packages/7715-permission-types/src/index.ts @@ -10,6 +10,7 @@ export type { Erc20TokenPeriodicPermission, Erc20TokenAllowancePermission, Erc20TokenRevocationPermission, + TokenApprovalRevocationPermission, Rule, PermissionRequest, PermissionResponse, diff --git a/packages/7715-permission-types/src/types.ts b/packages/7715-permission-types/src/types.ts index e9ba4680..0ddd4804 100644 --- a/packages/7715-permission-types/src/types.ts +++ b/packages/7715-permission-types/src/types.ts @@ -171,12 +171,32 @@ export type Erc20TokenAllowancePermission = BasePermission & { /** * A permission to revoke an ERC20 token allowance. + * + * @deprecated Use {@link TokenApprovalRevocationPermission} instead. */ export type Erc20TokenRevocationPermission = BasePermission & { + /** + * @deprecated Use `token-approval-revocation` instead. + */ type: 'erc20-token-revocation'; data: MetaMaskBasePermissionData; }; +/** + * A permission to revoke token approvals. + */ +export type TokenApprovalRevocationPermission = BasePermission & { + type: 'token-approval-revocation'; + data: MetaMaskBasePermissionData & { + erc20Approve: boolean; + erc721Approve: boolean; + erc721SetApprovalForAll: boolean; + permit2ApproveZero: boolean; + permit2Lockdown: boolean; + permit2InvalidateNonces: boolean; + }; +}; + /** * A custom permission. * @@ -198,7 +218,8 @@ export type PermissionTypes = | Erc20TokenStreamPermission | Erc20TokenPeriodicPermission | Erc20TokenAllowancePermission - | Erc20TokenRevocationPermission; + | Erc20TokenRevocationPermission + | TokenApprovalRevocationPermission; // ////////////////////////////////////////////////// // Permission Requests diff --git a/packages/delegation-abis/CHANGELOG.md b/packages/delegation-abis/CHANGELOG.md index d84a2dd4..f6bfc697 100644 --- a/packages/delegation-abis/CHANGELOG.md +++ b/packages/delegation-abis/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Abis for `ApprovalRevocationEnforcer` ([#226](https://github.com/metamask/smart-accounts-kit/pull/226)) + ## [1.0.0] ### Changed diff --git a/packages/delegation-abis/src/abis/ApprovalRevocationEnforcer.ts b/packages/delegation-abis/src/abis/ApprovalRevocationEnforcer.ts new file mode 100644 index 00000000..ad7ecf92 --- /dev/null +++ b/packages/delegation-abis/src/abis/ApprovalRevocationEnforcer.ts @@ -0,0 +1,174 @@ +export const abi = [ + { + "type": "function", + "name": "afterAllHook", + "inputs": [ + { + "name": "", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "", + "type": "bytes32", + "internalType": "ModeCode" + }, + { + "name": "", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "", + "type": "address", + "internalType": "address" + }, + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "afterHook", + "inputs": [ + { + "name": "", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "", + "type": "bytes32", + "internalType": "ModeCode" + }, + { + "name": "", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "", + "type": "address", + "internalType": "address" + }, + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "beforeAllHook", + "inputs": [ + { + "name": "", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "", + "type": "bytes32", + "internalType": "ModeCode" + }, + { + "name": "", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "", + "type": "address", + "internalType": "address" + }, + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "nonpayable" + }, + { + "type": "function", + "name": "beforeHook", + "inputs": [ + { + "name": "_terms", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "_mode", + "type": "bytes32", + "internalType": "ModeCode" + }, + { + "name": "_executionCallData", + "type": "bytes", + "internalType": "bytes" + }, + { + "name": "", + "type": "bytes32", + "internalType": "bytes32" + }, + { + "name": "_delegator", + "type": "address", + "internalType": "address" + }, + { + "name": "", + "type": "address", + "internalType": "address" + } + ], + "outputs": [], + "stateMutability": "view" + } +] as const; diff --git a/packages/delegation-abis/src/artifacts/ApprovalRevocationEnforcer.sol/ApprovalRevocationEnforcer.json b/packages/delegation-abis/src/artifacts/ApprovalRevocationEnforcer.sol/ApprovalRevocationEnforcer.json new file mode 100644 index 00000000..3bced18f --- /dev/null +++ b/packages/delegation-abis/src/artifacts/ApprovalRevocationEnforcer.sol/ApprovalRevocationEnforcer.json @@ -0,0 +1 @@ +{"abi":[{"type":"function","name":"afterAllHook","inputs":[{"name":"","type":"bytes","internalType":"bytes"},{"name":"","type":"bytes","internalType":"bytes"},{"name":"","type":"bytes32","internalType":"ModeCode"},{"name":"","type":"bytes","internalType":"bytes"},{"name":"","type":"bytes32","internalType":"bytes32"},{"name":"","type":"address","internalType":"address"},{"name":"","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"afterHook","inputs":[{"name":"","type":"bytes","internalType":"bytes"},{"name":"","type":"bytes","internalType":"bytes"},{"name":"","type":"bytes32","internalType":"ModeCode"},{"name":"","type":"bytes","internalType":"bytes"},{"name":"","type":"bytes32","internalType":"bytes32"},{"name":"","type":"address","internalType":"address"},{"name":"","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"beforeAllHook","inputs":[{"name":"","type":"bytes","internalType":"bytes"},{"name":"","type":"bytes","internalType":"bytes"},{"name":"","type":"bytes32","internalType":"ModeCode"},{"name":"","type":"bytes","internalType":"bytes"},{"name":"","type":"bytes32","internalType":"bytes32"},{"name":"","type":"address","internalType":"address"},{"name":"","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"type":"function","name":"beforeHook","inputs":[{"name":"_terms","type":"bytes","internalType":"bytes"},{"name":"","type":"bytes","internalType":"bytes"},{"name":"_mode","type":"bytes32","internalType":"ModeCode"},{"name":"_executionCallData","type":"bytes","internalType":"bytes"},{"name":"","type":"bytes32","internalType":"bytes32"},{"name":"_delegator","type":"address","internalType":"address"},{"name":"","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"view"}],"bytecode":{"object":"0x608060405234801561001057600080fd5b50610f82806100206000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c8063414c3e3314610051578063a145832a1461006d578063d3eddcc514610051578063ed46336714610051575b600080fd5b61006b61005f366004610bff565b50505050505050505050565b005b61006b61007b366004610bff565b85610087816000610617565b6100d85760405162461bcd60e51b815260206004820181905260248201527f436176656174456e666f726365723a696e76616c69642d63616c6c2d7479706560448201526064015b60405180910390fd5b86600881901b6100e9816000610617565b6101435760405162461bcd60e51b815260206004820152602560248201527f436176656174456e666f726365723a696e76616c69642d657865637574696f6e6044820152642d7479706560d81b60648201526084016100cf565b5060006101508d8d61062e565b90506000803660006101628c8c61075b565b9350935093509350826000146101b95760405162461bcd60e51b81526020600482015260286024820152600080516020610f2d83398151915260448201526769642d76616c756560c01b60648201526084016100cf565b60048110156101da5760405162461bcd60e51b81526004016100cf90610cd2565b60006101e96004828486610d13565b6101f291610d3d565b90506378ae83bb60e01b6001600160e01b0319821601610282576008861660000361026c5760405162461bcd60e51b81526020600482015260366024820152600080516020610f0d8339815191526044820152751d0c8b585c1c1c9bdd994b5b9bdd0b585b1b1bddd95960521b60648201526084016100cf565b6102778584846107ac565b505050505050610609565b6333acd78160e01b6001600160e01b031982160161033857601086166000036103015760405162461bcd60e51b81526020600482015260376024820152600080516020610f0d83398151915260448201527f74322d6c6f636b646f776e2d6e6f742d616c6c6f77656400000000000000000060648201526084016100cf565b6001600160a01b0385166e22d473030f116ddee9f6b43ac78ba3146102775760405162461bcd60e51b81526004016100cf90610d6d565b632689a37160e21b6001600160e01b03198216016103b95760208616600003610301576040805162461bcd60e51b8152602060048201526024810191909152600080516020610f0d83398151915260448201527f74322d696e76616c69646174652d6e6f6e6365732d6e6f742d616c6c6f77656460648201526084016100cf565b604482146103d95760405162461bcd60e51b81526004016100cf90610cd2565b635dd34b9b60e01b6001600160e01b0319821601610476576004861660000361046a5760405162461bcd60e51b815260206004820152603860248201527f417070726f76616c5265766f636174696f6e456e666f726365723a736574417060448201527f70726f76616c466f72416c6c2d6e6f742d616c6c6f776564000000000000000060648201526084016100cf565b6102778584848d6108fb565b63f6a1584d60e01b6001600160e01b03198216016105c157600061049e602460048587610d13565b6104a791610dac565b90506001600160a01b03811661053857600287166000036105285760405162461bcd60e51b815260206004820152603560248201527f417070726f76616c5265766f636174696f6e456e666f726365723a65726337326044820152740c4b585c1c1c9bdd994b5b9bdd0b585b1b1bddd959605a1b60648201526084016100cf565b610533868585610a25565b6105b5565b600187166000036105a85760405162461bcd60e51b815260206004820152603460248201527f417070726f76616c5265766f636174696f6e456e666f726365723a65726332306044820152730b585c1c1c9bdd994b5b9bdd0b585b1b1bddd95960621b60648201526084016100cf565b6105b58685858e85610ad9565b50505050505050610609565b60405162461bcd60e51b81526020600482015260296024820152600080516020610f2d8339815191526044820152681a590b5b595d1a1bd960ba1b60648201526084016100cf565b505050505050505050505050565b6001600160f81b0319828116908216145b92915050565b6000600182146106865760405162461bcd60e51b815260206004820152602f6024820152600080516020610f2d83398151915260448201526e0d2c85ae8cae4dae65ad8cadccee8d608b1b60648201526084016100cf565b8282600081811061069957610699610dca565b919091013560f81c915050600081900361070b5760405162461bcd60e51b815260206004820152602d60248201527f417070726f76616c5265766f636174696f6e456e666f726365723a6e6f2d6d6560448201526c1d1a1bd91ccb585b1b1bddd959609a1b60648201526084016100cf565b60c08116156106285760405162461bcd60e51b81526020600482015260286024820152600080516020610f2d83398151915260448201526769642d7465726d7360c01b60648201526084016100cf565b600080368161076d6014828789610d13565b61077691610de0565b60601c9350610789603460148789610d13565b61079291610dac565b92506107a18560348189610d13565b949793965094505050565b6001600160a01b0383166e22d473030f116ddee9f6b43ac78ba3146107e35760405162461bcd60e51b81526004016100cf90610d6d565b608481146108475760405162461bcd60e51b815260206004820152603b6024820152600080516020610f0d83398151915260448201527f74322d696e76616c69642d657865637574696f6e2d6c656e677468000000000060648201526084016100cf565b610855606460448385610d13565b61085e91610dac565b1561087b5760405162461bcd60e51b81526004016100cf90610e13565b610889608460648385610d13565b61089291610dac565b156108f65760405162461bcd60e51b815260206004820152602e60248201527f417070726f76616c5265766f636174696f6e456e666f726365723a6e6f6e2d7a60448201526d32b93796b2bc3834b930ba34b7b760911b60648201526084016100cf565b505050565b610909604460248486610d13565b61091291610dac565b156109735760405162461bcd60e51b815260206004820152602b60248201527f417070726f76616c5265766f636174696f6e456e666f726365723a6e6f742d6160448201526a16b932bb37b1b0ba34b7b760a91b60648201526084016100cf565b6000610983602460048587610d13565b61098c91610dac565b60405163e985e9c560e01b81526001600160a01b03848116600483015280831660248301529192509086169063e985e9c590604401602060405180830381865afa1580156109de573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a029190610e5d565b610a1e5760405162461bcd60e51b81526004016100cf90610e86565b5050505050565b6000610a35604460248486610d13565b610a3e91610dac565b60405163020604bf60e21b8152600481018290529091506000906001600160a01b0386169063081812fc90602401602060405180830381865afa158015610a89573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610aad9190610ed6565b6001600160a01b031603610ad35760405162461bcd60e51b81526004016100cf90610e86565b50505050565b610ae7604460248587610d13565b610af091610dac565b15610b0d5760405162461bcd60e51b81526004016100cf90610e13565b604051636eb1769f60e11b81526001600160a01b038381166004830152828116602483015286169063dd62ed3e90604401602060405180830381865afa158015610b5b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b7f9190610ef3565b600003610a1e5760405162461bcd60e51b81526004016100cf90610e86565b60008083601f840112610bb057600080fd5b50813567ffffffffffffffff811115610bc857600080fd5b602083019150836020828501011115610be057600080fd5b9250929050565b6001600160a01b0381168114610bfc57600080fd5b50565b60008060008060008060008060008060e08b8d031215610c1e57600080fd5b8a3567ffffffffffffffff80821115610c3657600080fd5b610c428e838f01610b9e565b909c509a5060208d0135915080821115610c5b57600080fd5b610c678e838f01610b9e565b909a50985060408d0135975060608d0135915080821115610c8757600080fd5b50610c948d828e01610b9e565b90965094505060808b0135925060a08b0135610caf81610be7565b915060c08b0135610cbf81610be7565b809150509295989b9194979a5092959850565b6020808252603390820152600080516020610f2d8339815191526040820152720d2c85acaf0cac6eae8d2dedc5ad8cadccee8d606b1b606082015260800190565b60008085851115610d2357600080fd5b83861115610d3057600080fd5b5050820193919092039150565b6001600160e01b03198135818116916004851015610d655780818660040360031b1b83161692505b505092915050565b6020808252603190820152600080516020610f2d8339815191526040820152701a590b5c195c9b5a5d0c8b5d185c99d95d607a1b606082015260800190565b8035602083101561062857600019602084900360031b1b1692915050565b634e487b7160e01b600052603260045260246000fd5b6bffffffffffffffffffffffff198135818116916014851015610d655760149490940360031b84901b1690921692915050565b6020808252602a908201527f417070726f76616c5265766f636174696f6e456e666f726365723a6e6f6e2d7a604082015269195c9bcb585b5bdd5b9d60b21b606082015260800190565b600060208284031215610e6f57600080fd5b81518015158114610e7f57600080fd5b9392505050565b60208082526030908201527f417070726f76616c5265766f636174696f6e456e666f726365723a6e6f2d617060408201526f70726f76616c2d746f2d7265766f6b6560801b606082015260800190565b600060208284031215610ee857600080fd5b8151610e7f81610be7565b600060208284031215610f0557600080fd5b505191905056fe417070726f76616c5265766f636174696f6e456e666f726365723a7065726d69417070726f76616c5265766f636174696f6e456e666f726365723a696e76616ca26469706673582212200b9b4fc3ceb80b33a7a3836360e7e30ce648157075b95685bdfd33482dcf19f764736f6c63430008170033","sourceMap":"13309:10940:61:-:0;;;;;;;;;;;;;;;;;;;","linkReferences":{}},"deployedBytecode":{"object":"0x608060405234801561001057600080fd5b506004361061004c5760003560e01c8063414c3e3314610051578063a145832a1461006d578063d3eddcc514610051578063ed46336714610051575b600080fd5b61006b61005f366004610bff565b50505050505050505050565b005b61006b61007b366004610bff565b85610087816000610617565b6100d85760405162461bcd60e51b815260206004820181905260248201527f436176656174456e666f726365723a696e76616c69642d63616c6c2d7479706560448201526064015b60405180910390fd5b86600881901b6100e9816000610617565b6101435760405162461bcd60e51b815260206004820152602560248201527f436176656174456e666f726365723a696e76616c69642d657865637574696f6e6044820152642d7479706560d81b60648201526084016100cf565b5060006101508d8d61062e565b90506000803660006101628c8c61075b565b9350935093509350826000146101b95760405162461bcd60e51b81526020600482015260286024820152600080516020610f2d83398151915260448201526769642d76616c756560c01b60648201526084016100cf565b60048110156101da5760405162461bcd60e51b81526004016100cf90610cd2565b60006101e96004828486610d13565b6101f291610d3d565b90506378ae83bb60e01b6001600160e01b0319821601610282576008861660000361026c5760405162461bcd60e51b81526020600482015260366024820152600080516020610f0d8339815191526044820152751d0c8b585c1c1c9bdd994b5b9bdd0b585b1b1bddd95960521b60648201526084016100cf565b6102778584846107ac565b505050505050610609565b6333acd78160e01b6001600160e01b031982160161033857601086166000036103015760405162461bcd60e51b81526020600482015260376024820152600080516020610f0d83398151915260448201527f74322d6c6f636b646f776e2d6e6f742d616c6c6f77656400000000000000000060648201526084016100cf565b6001600160a01b0385166e22d473030f116ddee9f6b43ac78ba3146102775760405162461bcd60e51b81526004016100cf90610d6d565b632689a37160e21b6001600160e01b03198216016103b95760208616600003610301576040805162461bcd60e51b8152602060048201526024810191909152600080516020610f0d83398151915260448201527f74322d696e76616c69646174652d6e6f6e6365732d6e6f742d616c6c6f77656460648201526084016100cf565b604482146103d95760405162461bcd60e51b81526004016100cf90610cd2565b635dd34b9b60e01b6001600160e01b0319821601610476576004861660000361046a5760405162461bcd60e51b815260206004820152603860248201527f417070726f76616c5265766f636174696f6e456e666f726365723a736574417060448201527f70726f76616c466f72416c6c2d6e6f742d616c6c6f776564000000000000000060648201526084016100cf565b6102778584848d6108fb565b63f6a1584d60e01b6001600160e01b03198216016105c157600061049e602460048587610d13565b6104a791610dac565b90506001600160a01b03811661053857600287166000036105285760405162461bcd60e51b815260206004820152603560248201527f417070726f76616c5265766f636174696f6e456e666f726365723a65726337326044820152740c4b585c1c1c9bdd994b5b9bdd0b585b1b1bddd959605a1b60648201526084016100cf565b610533868585610a25565b6105b5565b600187166000036105a85760405162461bcd60e51b815260206004820152603460248201527f417070726f76616c5265766f636174696f6e456e666f726365723a65726332306044820152730b585c1c1c9bdd994b5b9bdd0b585b1b1bddd95960621b60648201526084016100cf565b6105b58685858e85610ad9565b50505050505050610609565b60405162461bcd60e51b81526020600482015260296024820152600080516020610f2d8339815191526044820152681a590b5b595d1a1bd960ba1b60648201526084016100cf565b505050505050505050505050565b6001600160f81b0319828116908216145b92915050565b6000600182146106865760405162461bcd60e51b815260206004820152602f6024820152600080516020610f2d83398151915260448201526e0d2c85ae8cae4dae65ad8cadccee8d608b1b60648201526084016100cf565b8282600081811061069957610699610dca565b919091013560f81c915050600081900361070b5760405162461bcd60e51b815260206004820152602d60248201527f417070726f76616c5265766f636174696f6e456e666f726365723a6e6f2d6d6560448201526c1d1a1bd91ccb585b1b1bddd959609a1b60648201526084016100cf565b60c08116156106285760405162461bcd60e51b81526020600482015260286024820152600080516020610f2d83398151915260448201526769642d7465726d7360c01b60648201526084016100cf565b600080368161076d6014828789610d13565b61077691610de0565b60601c9350610789603460148789610d13565b61079291610dac565b92506107a18560348189610d13565b949793965094505050565b6001600160a01b0383166e22d473030f116ddee9f6b43ac78ba3146107e35760405162461bcd60e51b81526004016100cf90610d6d565b608481146108475760405162461bcd60e51b815260206004820152603b6024820152600080516020610f0d83398151915260448201527f74322d696e76616c69642d657865637574696f6e2d6c656e677468000000000060648201526084016100cf565b610855606460448385610d13565b61085e91610dac565b1561087b5760405162461bcd60e51b81526004016100cf90610e13565b610889608460648385610d13565b61089291610dac565b156108f65760405162461bcd60e51b815260206004820152602e60248201527f417070726f76616c5265766f636174696f6e456e666f726365723a6e6f6e2d7a60448201526d32b93796b2bc3834b930ba34b7b760911b60648201526084016100cf565b505050565b610909604460248486610d13565b61091291610dac565b156109735760405162461bcd60e51b815260206004820152602b60248201527f417070726f76616c5265766f636174696f6e456e666f726365723a6e6f742d6160448201526a16b932bb37b1b0ba34b7b760a91b60648201526084016100cf565b6000610983602460048587610d13565b61098c91610dac565b60405163e985e9c560e01b81526001600160a01b03848116600483015280831660248301529192509086169063e985e9c590604401602060405180830381865afa1580156109de573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a029190610e5d565b610a1e5760405162461bcd60e51b81526004016100cf90610e86565b5050505050565b6000610a35604460248486610d13565b610a3e91610dac565b60405163020604bf60e21b8152600481018290529091506000906001600160a01b0386169063081812fc90602401602060405180830381865afa158015610a89573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610aad9190610ed6565b6001600160a01b031603610ad35760405162461bcd60e51b81526004016100cf90610e86565b50505050565b610ae7604460248587610d13565b610af091610dac565b15610b0d5760405162461bcd60e51b81526004016100cf90610e13565b604051636eb1769f60e11b81526001600160a01b038381166004830152828116602483015286169063dd62ed3e90604401602060405180830381865afa158015610b5b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b7f9190610ef3565b600003610a1e5760405162461bcd60e51b81526004016100cf90610e86565b60008083601f840112610bb057600080fd5b50813567ffffffffffffffff811115610bc857600080fd5b602083019150836020828501011115610be057600080fd5b9250929050565b6001600160a01b0381168114610bfc57600080fd5b50565b60008060008060008060008060008060e08b8d031215610c1e57600080fd5b8a3567ffffffffffffffff80821115610c3657600080fd5b610c428e838f01610b9e565b909c509a5060208d0135915080821115610c5b57600080fd5b610c678e838f01610b9e565b909a50985060408d0135975060608d0135915080821115610c8757600080fd5b50610c948d828e01610b9e565b90965094505060808b0135925060a08b0135610caf81610be7565b915060c08b0135610cbf81610be7565b809150509295989b9194979a5092959850565b6020808252603390820152600080516020610f2d8339815191526040820152720d2c85acaf0cac6eae8d2dedc5ad8cadccee8d606b1b606082015260800190565b60008085851115610d2357600080fd5b83861115610d3057600080fd5b5050820193919092039150565b6001600160e01b03198135818116916004851015610d655780818660040360031b1b83161692505b505092915050565b6020808252603190820152600080516020610f2d8339815191526040820152701a590b5c195c9b5a5d0c8b5d185c99d95d607a1b606082015260800190565b8035602083101561062857600019602084900360031b1b1692915050565b634e487b7160e01b600052603260045260246000fd5b6bffffffffffffffffffffffff198135818116916014851015610d655760149490940360031b84901b1690921692915050565b6020808252602a908201527f417070726f76616c5265766f636174696f6e456e666f726365723a6e6f6e2d7a604082015269195c9bcb585b5bdd5b9d60b21b606082015260800190565b600060208284031215610e6f57600080fd5b81518015158114610e7f57600080fd5b9392505050565b60208082526030908201527f417070726f76616c5265766f636174696f6e456e666f726365723a6e6f2d617060408201526f70726f76616c2d746f2d7265766f6b6560801b606082015260800190565b600060208284031215610ee857600080fd5b8151610e7f81610be7565b600060208284031215610f0557600080fd5b505191905056fe417070726f76616c5265766f636174696f6e456e666f726365723a7065726d69417070726f76616c5265766f636174696f6e456e666f726365723a696e76616ca26469706673582212200b9b4fc3ceb80b33a7a3836360e7e30ce648157075b95685bdfd33482dcf19f764736f6c63430008170033","sourceMap":"13309:10940:61:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;614:126:64;;;;;;:::i;:::-;;;;;;;;;;;;;;16442:4071:61;;;;;;:::i;:::-;16728:5;1402:45:64;16728:5:61;2789:4:16;1402:45:64;:::i;:::-;1394:90;;;;-1:-1:-1;;;1394:90:64;;2158:2:116;1394:90:64;;;2140:21:116;;;2177:18;;;2170:30;2236:34;2216:18;;;2209:62;2288:18;;1394:90:64;;;;;;;;;16768:5:61;4155:1:16;4151:12;;;2014:29:64::1;4151:12:16::0;1954:18:64::1;2014:29;:::i;:::-;2006:79;;;::::0;-1:-1:-1;;;2006:79:64;;2519:2:116;2006:79:64::1;::::0;::::1;2501:21:116::0;2558:2;2538:18;;;2531:30;2597:34;2577:18;;;2570:62;-1:-1:-1;;;2648:18:116;;;2641:35;2693:19;;2006:79:64::1;2317:401:116::0;2006:79:64::1;1937:159;16874:12:61::2;16889:19;16901:6;;16889:11;:19::i;:::-;16874:34;;16920:15;16937:14:::0;16953:24:::2;;16981:33;:18;;:31;:33::i;:::-;16919:95;;;;;;;;17033:6;17043:1;17033:11;17025:64;;;::::0;-1:-1:-1;;;17025:64:61;;2925:2:116;17025:64:61::2;::::0;::::2;2907:21:116::0;2964:2;2944:18;;;2937:30;-1:-1:-1;;;;;;;;;;;2983:18:116;;;2976:62;-1:-1:-1;;;3054:18:116;;;3047:38;3102:19;;17025:64:61::2;2723:404:116::0;17025:64:61::2;17127:1;17107:21:::0;::::2;;17099:85;;;;-1:-1:-1::0;;;17099:85:61::2;;;;;;;:::i;:::-;17195:16;17221:14;17233:1;17195:16:::0;17221:9;;:14:::2;:::i;:::-;17214:22;::::0;::::2;:::i;:::-;17195:41:::0;-1:-1:-1;;;;;;;;;;17452:38:61;::::2;::::0;17448:254:::2;;13817:4;17514:36:::0;::::2;17554:1;17514:41:::0;17506:108:::2;;;::::0;-1:-1:-1;;;17506:108:61;;4418:2:116;17506:108:61::2;::::0;::::2;4400:21:116::0;4457:2;4437:18;;;4430:30;-1:-1:-1;;;;;;;;;;;4476:18:116;;;4469:62;-1:-1:-1;;;4547:18:116;;;4540:52;4609:19;;17506:108:61::2;4216:418:116::0;17506:108:61::2;17628:43;17652:7;17661:9;;17628:23;:43::i;:::-;17685:7;;;;;;;;17448:254;-1:-1:-1::0;;;;;;;;;18032:39:61;::::2;::::0;18028:295:::2;;13882:4;18095:37:::0;::::2;18136:1;18095:42:::0;18087:110:::2;;;::::0;-1:-1:-1;;;18087:110:61;;4841:2:116;18087:110:61::2;::::0;::::2;4823:21:116::0;4880:2;4860:18;;;4853:30;-1:-1:-1;;;;;;;;;;;4899:18:116;;;4892:62;4990:25;4970:18;;;4963:53;5033:19;;18087:110:61::2;4639:419:116::0;18087:110:61::2;-1:-1:-1::0;;;;;18219:19:61;::::2;14571:42;18219:19;18211:81;;;;-1:-1:-1::0;;;18211:81:61::2;;;;;;;:::i;18028:295::-;-1:-1:-1::0;;;;;;;;;18645:48:61;::::2;::::0;18641:368:::2;;13956:4;18734:46:::0;::::2;18784:1;18734:51:::0;18709:174:::2;;;::::0;;-1:-1:-1;;;18709:174:61;;5683:2:116;18709:174:61::2;::::0;::::2;5665:21:116::0;5702:18;;;5695:30;;;;-1:-1:-1;;;;;;;;;;;5741:18:116;;;5734:62;5832:34;5812:18;;;5805:62;5884:19;;18709:174:61::2;5481:428:116::0;18641:368:61::2;19187:2;19167:22:::0;::::2;19159:86;;;;-1:-1:-1::0;;;19159:86:61::2;;;;;;;:::i;:::-;-1:-1:-1::0;;;;;;;;;19260:47:61;::::2;::::0;19256:286:::2;;13753:4;19331:41:::0;::::2;19376:1;19331:46:::0;19323:115:::2;;;::::0;-1:-1:-1;;;19323:115:61;;6116:2:116;19323:115:61::2;::::0;::::2;6098:21:116::0;6155:2;6135:18;;;6128:30;6194:34;6174:18;;;6167:62;6265:26;6245:18;;;6238:54;6309:19;;19323:115:61::2;5914:420:116::0;19323:115:61::2;19452:59;19480:7;19489:9;;19500:10;19452:27;:59::i;19256:286::-;-1:-1:-1::0;;;;;;;;;19555:36:61;::::2;::::0;19551:895:::2;;19866:19;19920:15;19932:2;19930:1;19920:9:::0;;:15:::2;:::i;:::-;19912:24;::::0;::::2;:::i;:::-;19904:33:::0;-1:-1:-1;;;;;;19957:25:61;::::2;19953:463;;13684:4;20010:35:::0;::::2;20049:1;20010:40:::0;20002:106:::2;;;::::0;-1:-1:-1;;;20002:106:61;;6801:2:116;20002:106:61::2;::::0;::::2;6783:21:116::0;6840:2;6820:18;;;6813:30;6879:34;6859:18;;;6852:62;-1:-1:-1;;;6930:18:116;;;6923:51;6991:19;;20002:106:61::2;6599:417:116::0;20002:106:61::2;20126:45;20152:7;20161:9;;20126:25;:45::i;:::-;19953:463;;;13621:4;20218:34:::0;::::2;20256:1;20218:39:::0;20210:104:::2;;;::::0;-1:-1:-1;;;20210:104:61;;7223:2:116;20210:104:61::2;::::0;::::2;7205:21:116::0;7262:2;7242:18;;;7235:30;7301:34;7281:18;;;7274:62;-1:-1:-1;;;7352:18:116;;;7345:50;7412:19;;20210:104:61::2;7021:416:116::0;20210:104:61::2;20332:69;20357:7;20366:9;;20377:10;20389:11;20332:24;:69::i;:::-;20429:7;;;;;;;;;19551:895;20455:51;::::0;-1:-1:-1;;;20455:51:61;;7644:2:116;20455:51:61::2;::::0;::::2;7626:21:116::0;7683:2;7663:18;;;7656:30;-1:-1:-1;;;;;;;;;;;7702:18:116;;;7695:62;-1:-1:-1;;;7773:18:116;;;7766:39;7822:19;;20455:51:61::2;7442:405:116::0;2105:1:64::2;1504::::1;16442:4071:61::0;;;;;;;;;;;:::o;5304:120:16:-;-1:-1:-1;;;;;;5381:40:16;;;;;;;5304:120;;;;;:::o;20710:383:61:-;20776:12;20825:1;20808:18;;20800:78;;;;-1:-1:-1;;;20800:78:61;;8054:2:116;20800:78:61;;;8036:21:116;8093:2;8073:18;;;8066:30;-1:-1:-1;;;;;;;;;;;8112:18:116;;;8105:62;-1:-1:-1;;;8183:18:116;;;8176:45;8238:19;;20800:78:61;7852:411:116;20800:78:61;20903:6;;20910:1;20903:9;;;;;;;:::i;:::-;;;;;;;;;-1:-1:-1;;20941:1:61;20931:11;;;20923:69;;;;-1:-1:-1;;;20923:69:61;;8602:2:116;20923:69:61;;;8584:21:116;8641:2;8621:18;;;8614:30;8680:34;8660:18;;;8653:62;-1:-1:-1;;;8731:18:116;;;8724:43;8784:19;;20923:69:61;8400:409:116;20923:69:61;21010:31;;;;21002:84;;;;-1:-1:-1;;;21002:84:61;;9016:2:116;21002:84:61;;;8998:21:116;9055:2;9035:18;;;9028:30;-1:-1:-1;;;;;;;;;;;9074:18:116;;;9067:62;-1:-1:-1;;;9145:18:116;;;9138:38;9193:19;;21002:84:61;8814:404:116;2513:333:15;2616:14;;2647:23;2616:14;2711:23;2731:2;2616:14;2711:17;;:23;:::i;:::-;2703:32;;;:::i;:::-;2695:41;;;-1:-1:-1;2770:24:15;2791:2;2788;2770:17;;:24;:::i;:::-;2762:33;;;:::i;:::-;2754:42;-1:-1:-1;2817:22:15;:17;2835:2;2817:17;;:22;:::i;:::-;2513:333;;;;-1:-1:-1;2806:33:15;-1:-1:-1;;;2513:333:15:o;23470:777:61:-;-1:-1:-1;;;;;23577:19:61;;14571:42;23577:19;23569:81;;;;-1:-1:-1;;;23569:81:61;;;;;;;:::i;:::-;23688:3;23668:23;;23660:95;;;;-1:-1:-1;;;23660:95:61;;9767:2:116;23660:95:61;;;9749:21:116;9806:2;9786:18;;;9779:30;-1:-1:-1;;;;;;;;;;;9825:18:116;;;9818:62;9916:29;9896:18;;;9889:57;9963:19;;23660:95:61;9565:423:116;23660:95:61;23997:17;24010:3;24007:2;23997:9;;:17;:::i;:::-;23989:26;;;:::i;:::-;23981:40;23973:95;;;;-1:-1:-1;;;23973:95:61;;;;;;;:::i;:::-;24164:18;24178:3;24174;24164:9;;:18;:::i;:::-;24156:27;;;:::i;:::-;24148:41;24140:100;;;;-1:-1:-1;;;24140:100:61;;10606:2:116;24140:100:61;;;10588:21:116;10645:2;10625:18;;;10618:30;10684:34;10664:18;;;10657:62;-1:-1:-1;;;10735:18:116;;;10728:44;10789:19;;24140:100:61;10404:410:116;24140:100:61;23470:777;;;:::o;22387:434::-;22534:16;22547:2;22544;22534:9;;:16;:::i;:::-;22526:25;;;:::i;:::-;22518:39;22510:95;;;;-1:-1:-1;;;22510:95:61;;11021:2:116;22510:95:61;;;11003:21:116;11060:2;11040:18;;;11033:30;11099:34;11079:18;;;11072:62;-1:-1:-1;;;11150:18:116;;;11143:41;11201:19;;22510:95:61;10819:407:116;22510:95:61;22616:17;22668:15;22680:2;22678:1;22668:9;;:15;:::i;:::-;22660:24;;;:::i;:::-;22705:56;;-1:-1:-1;;;22705:56:61;;-1:-1:-1;;;;;11461:15:116;;;22705:56:61;;;11443:34:116;11513:15;;;11493:18;;;11486:43;22652:33:61;;-1:-1:-1;22705:33:61;;;;;;11378:18:116;;22705:56:61;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;22697:117;;;;-1:-1:-1;;;22697:117:61;;;;;;;:::i;:::-;22500:321;22387:434;;;;:::o;21872:285::-;21973:16;22008;22021:2;22018;22008:9;;:16;:::i;:::-;22000:25;;;:::i;:::-;22045:38;;-1:-1:-1;;;22045:38:61;;;;;12385:25:116;;;21992:34:61;;-1:-1:-1;21992:34:61;;-1:-1:-1;;;;;22045:28:61;;;;;12358:18:116;;22045:38:61;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1;;;;;22045:52:61;;22037:113;;;;-1:-1:-1;;;22037:113:61;;;;;;;:::i;:::-;21963:194;21872:285;;;:::o;21248:421::-;21468:16;21481:2;21478;21468:9;;:16;:::i;:::-;21460:25;;;:::i;:::-;21452:39;21444:94;;;;-1:-1:-1;;;21444:94:61;;;;;;;:::i;:::-;21557:47;;-1:-1:-1;;;21557:47:61;;-1:-1:-1;;;;;11461:15:116;;;21557:47:61;;;11443:34:116;11513:15;;;11493:18;;;11486:43;21557:25:61;;;;;11378:18:116;;21557:47:61;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;21608:1;21557:52;21549:113;;;;-1:-1:-1;;;21549:113:61;;;;;;;:::i;14:347:116:-;65:8;75:6;129:3;122:4;114:6;110:17;106:27;96:55;;147:1;144;137:12;96:55;-1:-1:-1;170:20:116;;213:18;202:30;;199:50;;;245:1;242;235:12;199:50;282:4;274:6;270:17;258:29;;334:3;327:4;318:6;310;306:19;302:30;299:39;296:59;;;351:1;348;341:12;296:59;14:347;;;;;:::o;366:131::-;-1:-1:-1;;;;;441:31:116;;431:42;;421:70;;487:1;484;477:12;421:70;366:131;:::o;502:1449::-;677:6;685;693;701;709;717;725;733;741;749;802:3;790:9;781:7;777:23;773:33;770:53;;;819:1;816;809:12;770:53;859:9;846:23;888:18;929:2;921:6;918:14;915:34;;;945:1;942;935:12;915:34;984:58;1034:7;1025:6;1014:9;1010:22;984:58;:::i;:::-;1061:8;;-1:-1:-1;958:84:116;-1:-1:-1;1149:2:116;1134:18;;1121:32;;-1:-1:-1;1165:16:116;;;1162:36;;;1194:1;1191;1184:12;1162:36;1233:60;1285:7;1274:8;1263:9;1259:24;1233:60;:::i;:::-;1312:8;;-1:-1:-1;1207:86:116;-1:-1:-1;1394:2:116;1379:18;;1366:32;;-1:-1:-1;1451:2:116;1436:18;;1423:32;;-1:-1:-1;1467:16:116;;;1464:36;;;1496:1;1493;1486:12;1464:36;;1535:60;1587:7;1576:8;1565:9;1561:24;1535:60;:::i;:::-;1614:8;;-1:-1:-1;1509:86:116;-1:-1:-1;;1696:3:116;1681:19;;1668:33;;-1:-1:-1;1751:3:116;1736:19;;1723:33;1765:31;1723:33;1765:31;:::i;:::-;1815:5;-1:-1:-1;1872:3:116;1857:19;;1844:33;1886;1844;1886;:::i;:::-;1938:7;1928:17;;;502:1449;;;;;;;;;;;;;:::o;3132:415::-;3334:2;3316:21;;;3373:2;3353:18;;;3346:30;-1:-1:-1;;;;;;;;;;;3407:2:116;3392:18;;3385:62;-1:-1:-1;;;3478:2:116;3463:18;;3456:49;3537:3;3522:19;;3132:415::o;3552:331::-;3657:9;3668;3710:8;3698:10;3695:24;3692:44;;;3732:1;3729;3722:12;3692:44;3761:6;3751:8;3748:20;3745:40;;;3781:1;3778;3771:12;3745:40;-1:-1:-1;;3807:23:116;;;3852:25;;;;;-1:-1:-1;3552:331:116:o;3888:323::-;-1:-1:-1;;;;;;4008:19:116;;4084:11;;;;4115:1;4107:10;;4104:101;;;4192:2;4186;4179:3;4176:1;4172:11;4169:1;4165:19;4161:28;4157:2;4153:37;4149:46;4140:55;;4104:101;;;3888:323;;;;:::o;5063:413::-;5265:2;5247:21;;;5304:2;5284:18;;;5277:30;-1:-1:-1;;;;;;;;;;;5338:2:116;5323:18;;5316:62;-1:-1:-1;;;5409:2:116;5394:18;;5387:47;5466:3;5451:19;;5063:413::o;6339:255::-;6459:19;;6498:2;6490:11;;6487:101;;;-1:-1:-1;;6559:2:116;6555:12;;;6552:1;6548:20;6544:33;6533:45;6339:255;;;;:::o;8268:127::-;8329:10;8324:3;8320:20;8317:1;8310:31;8360:4;8357:1;8350:15;8384:4;8381:1;8374:15;9223:337;-1:-1:-1;;9344:19:116;;9431:11;;;;9462:2;9454:11;;9451:103;;;9524:2;9520:12;;;;9517:1;9513:20;9509:29;;;9501:38;9497:47;;;;9223:337;-1:-1:-1;;9223:337:116:o;9993:406::-;10195:2;10177:21;;;10234:2;10214:18;;;10207:30;10273:34;10268:2;10253:18;;10246:62;-1:-1:-1;;;10339:2:116;10324:18;;10317:40;10389:3;10374:19;;9993:406::o;11540:277::-;11607:6;11660:2;11648:9;11639:7;11635:23;11631:32;11628:52;;;11676:1;11673;11666:12;11628:52;11708:9;11702:16;11761:5;11754:13;11747:21;11740:5;11737:32;11727:60;;11783:1;11780;11773:12;11727:60;11806:5;11540:277;-1:-1:-1;;;11540:277:116:o;11822:412::-;12024:2;12006:21;;;12063:2;12043:18;;;12036:30;12102:34;12097:2;12082:18;;12075:62;-1:-1:-1;;;12168:2:116;12153:18;;12146:46;12224:3;12209:19;;11822:412::o;12421:251::-;12491:6;12544:2;12532:9;12523:7;12519:23;12515:32;12512:52;;;12560:1;12557;12550:12;12512:52;12592:9;12586:16;12611:31;12636:5;12611:31;:::i;12677:184::-;12747:6;12800:2;12788:9;12779:7;12775:23;12771:32;12768:52;;;12816:1;12813;12806:12;12768:52;-1:-1:-1;12839:16:116;;12677:184;-1:-1:-1;12677:184:116:o","linkReferences":{}},"methodIdentifiers":{"afterAllHook(bytes,bytes,bytes32,bytes,bytes32,address,address)":"ed463367","afterHook(bytes,bytes,bytes32,bytes,bytes32,address,address)":"d3eddcc5","beforeAllHook(bytes,bytes,bytes32,bytes,bytes32,address,address)":"414c3e33","beforeHook(bytes,bytes,bytes32,bytes,bytes32,address,address)":"a145832a"},"rawMetadata":"{\"compiler\":{\"version\":\"0.8.23+commit.f704f362\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"},{\"internalType\":\"ModeCode\",\"name\":\"\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"},{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"afterAllHook\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"},{\"internalType\":\"ModeCode\",\"name\":\"\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"},{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"afterHook\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"},{\"internalType\":\"ModeCode\",\"name\":\"\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"},{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"beforeAllHook\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes\",\"name\":\"_terms\",\"type\":\"bytes\"},{\"internalType\":\"bytes\",\"name\":\"\",\"type\":\"bytes\"},{\"internalType\":\"ModeCode\",\"name\":\"_mode\",\"type\":\"bytes32\"},{\"internalType\":\"bytes\",\"name\":\"_executionCallData\",\"type\":\"bytes\"},{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"_delegator\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"beforeHook\",\"outputs\":[],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"details\":\"ERC-721 and ERC-1155 intentionally share the `setApprovalForAll(address,bool)` selector; this enforcer handles both via the `IERC721` interface (the selector and ABI are identical, so a typed `IERC1155` import is unnecessary for the external call). ERC-20 and ERC-721 likewise share the `approve(address,uint256)` selector, and are disambiguated by inspecting the first parameter (see branching rules below).The execution must transfer zero native value and carry one of the supported approval calldatas. Branching is determined as follows: - selector `approve(address token, address spender, uint160 amount, uint48 expiration)` (Permit2): - `target` MUST equal the canonical Permit2 deployment (`_PERMIT2`), and - calldata length MUST be 132 bytes (4-byte selector + four 32-byte words), and - the third parameter (amount) MUST be zero, and - the fourth parameter (expiration) MUST be zero. - selector `lockdown((address,address)[])` (Permit2): - `target` MUST equal the canonical Permit2 deployment (`_PERMIT2`). - The calldata is otherwise unconstrained: every entry of the array structurally forces the corresponding `(token, spender)` Permit2 allowance `amount` to zero (`expiration` and `nonce` are left untouched). There is no parameter the delegate could supply to grant new authority, so no further calldata validation is performed. - selector `invalidateNonces(address,address,uint48)` (Permit2): - `target` MUST equal the canonical Permit2 deployment (`_PERMIT2`). - The calldata is otherwise unconstrained: Permit2's `invalidateNonces` strictly monotonically increases the stored nonce for the `(caller, token, spender)` triple (it reverts if `newNonce <= oldNonce` and caps the per-call delta at `type(uint16).max`). It cannot create or extend an allowance, so no further calldata validation is performed. - calldata length 68 bytes (4-byte selector + two 32-byte words), shared by `approve(address,uint256)` and `setApprovalForAll(address,bool)`: - selector `setApprovalForAll(address operator, bool approved)`: - `approved` MUST be false, and - `isApprovedForAll(delegator, operator)` MUST currently be true on the target. - selector `approve(address, uint256)` (shared by ERC-20 and ERC-721): - if the first parameter is `address(0)` the call is treated as an ERC-721 per-token revocation: - `getApproved(tokenId)` on the target MUST currently return a non-zero address. - otherwise the call is treated as an ERC-20 revocation: - the second parameter (amount) MUST be zero, and - `allowance(delegator, spender)` on the target MUST currently return non-zero.All six accepted calldatas structurally result in a net reduction of permissions on the target (amount `0`, spender `address(0)`, `approved` `false`, per-pair Permit2 amount zeroing, or strictly monotonic Permit2 nonce bump). A delegate using this enforcer can therefore never be granted new authority over the delegator's assets \\u2014 only existing approvals can be cleared and pending Permit2 signatures invalidated.Unlike the ERC-20 / ERC-721 / `setApprovalForAll` primitives, the three Permit2 branches perform no on-chain liveness pre-check. The structural constraints (canonical Permit2 target, fixed selector, and \\u2014 for `approve` \\u2014 zero amount and zero expiration) already guarantee the call can only reduce permissions; if no Permit2 state exists for the targeted `(token, spender)` pair(s) the execution is either a harmless no-op or (for `invalidateNonces`) reverts inside Permit2. Restrict which pairs the delegate may target by composing this enforcer with `AllowedCalldataEnforcer` or `ExactCalldataEnforcer`. Note that for `lockdown` such pinning also has to fix the array length and ABI head words, since the calldata is dynamic; `ExactCalldataEnforcer` is usually the cleaner option there.Permit2 revocation surface \\u2014 what each primitive does and does not cover: - `approve(token, spender, 0, 0)` zeros `amount` and sets `expiration` to `block.timestamp` for the caller's `(token, spender)` allowance. Pending signed permits are NOT invalidated (their `nonce` is unaffected). - `lockdown` zeros `amount` only. `expiration` and `nonce` are unchanged. Pending signed permits are NOT invalidated. - `invalidateNonces` strictly monotonically increases the stored `nonce`, rendering all signed-but-unredeemed `permit` payloads with a now-stale nonce uncollectable. It does NOT zero on-chain `amount` or `expiration`. To fully sever Permit2 exposure to a spender, both an on-chain allowance revocation (bit 3 or 4) AND a nonce invalidation (bit 5) are typically required. Enabling only one leaves the other vector live.DoS surface on bit 5 (`invalidateNonces`). A delegate granted bit 5 can advance the stored nonce for any `(token, spender)` pair the caveat does not pin (Permit2 caps the per-call delta at `type(uint16).max`, but a determined delegate can repeat until `nonce == type(uint48).max`, after which the root delegator can no longer sign new permits for that pair). This is never an authority escalation \\u2014 it can only invalidate, never create \\u2014 but it is a denial-of-service vector for the delegator's future signed-permit flow. When granting bit 5, pin the `(token, spender)` pair via `AllowedCalldataEnforcer` / `ExactCalldataEnforcer` and/or rate-limit the delegation with `LimitedCallsEnforcer`.Trust assumption \\u2014 canonical Permit2 deployment. The Permit2 branches assume the canonical Uniswap-deployed Permit2 contract is at `_PERMIT2` on the target chain. On chains where Uniswap has deployed Permit2 (mainnet, Base, Arbitrum, Optimism, Polygon, BNB, Avalanche, etc.) this is a safe deterministic address. On chains where canonical Permit2 is NOT deployed: - if the address is empty, the executor's call returns successfully with no effect (harmless no-op); - if a *different* contract happens to live at that address, the selector dispatches into whatever that contract does. The `approve(0, 0)` branch is partially self-protected by its structural calldata checks (any contract under that selector would have to interpret the layout identically to grant authority), but `lockdown` and `invalidateNonces` have no such structural moat. Delegators on chains without canonical Permit2 should NOT enable bits 3, 4, or 5.REDELEGATION WARNING \\u2014 link-local pre-check vs. root-level execution. The `_delegator` argument passed to `beforeHook` is the delegator of the specific delegation that carries the caveat, not the root of a redelegation chain. The DelegationManager always executes the downstream call against the *root* delegator's account (the account at the end of the leaf-to-root chain). On a root-level delegation (chain length 1) the two are the same and the pre-check queries the account whose storage will actually be mutated \\u2014 this is the intended usage. On an intermediate (redelegation) link the two differ. The implications are different per primitive group: (a) ERC-20 / ERC-721 / `setApprovalForAll` branches \\u2014 pre-check is link-local. The pre-check queries the *intermediate* delegator's approval state, while the execution mutates the *root* delegator's storage. A redelegator adding this caveat to constrain their delegate is very likely expecting the pre-check to run against the root. That expectation is wrong \\u2014 the check is link-local. Concrete example. Alice -> Bob -> Carol. Alice's link has no caveat (Bob has full authority over Alice). Bob places this enforcer on his delegation to Carol, intending \\\"Carol can only revoke an existing approval on Alice's account\\\". When Carol redeems, the hook fires with `_delegator = Bob`, not Alice, so: - if Bob has no allowance to the same spender on the target, the hook reverts even when Alice does have one (Carol cannot use the chain, even though the revocation would have been valid for Alice); - if Bob happens to have some allowance, the hook passes and the execution clears Alice's allowance \\u2014 independently of whether Alice actually had an allowance to clear. (b) Permit2 branches \\u2014 no pre-check at all. The Permit2 branches do not consult `_delegator` (no on-chain liveness check is performed). On an intermediate link this means the link-local sanity guard that exists for the ERC-20/721/operator branches is simply absent: the hook always passes (subject only to the per-flag and target checks), and the executed call zeros / bumps the *root* delegator's Permit2 state for whatever `(token, spender)` pair the delegate supplies. Neither (a) nor (b) is an authority escalation (the structural constraints above still apply \\u2014 the call can only reduce permissions). But the sanity guard is misaligned with the executed effect, and for the Permit2 branches it is absent entirely. Composition with `AllowedCalldataEnforcer` / `ExactCalldataEnforcer` to pin the `(token, spender)` pair is therefore load-bearing for any redelegated Permit2 caveat. If a redelegator needs a root-scoped guarantee (e.g. \\\"Carol may only revoke one of Alice's specific approvals\\\") they should rely on structural caveats that compose cleanly across links, such as `AllowedTargetsEnforcer` (restrict which token contract), `AllowedCalldataEnforcer` (pin the exact spender or tokenId), or `ExactCalldataEnforcer`. Placing `ApprovalRevocationEnforcer` on an intermediate link in the hope of validating the root's approval state does not achieve that.The \\\"pre-existing approval\\\" check is a liveness/sanity guard ensuring the call is not a no-op at the time the hook runs. It is not a race-free invariant: the delegator could independently clear the approval between the hook and the execution. In that case the execution is still safe \\u2014 it simply becomes a no-op.Delegators who want to restrict revocation to specific tokens should compose this enforcer with `AllowedTargetsEnforcer`.INCOMPATIBILITY \\u2014 ERC-20 tokens that revert on zero-value `approve`. A small number of non-standard ERC-20 tokens (notably BNB on Ethereum mainnet, and a handful of older tokens) revert when `approve(spender, 0)` is called. Because the ERC-20 branch of this enforcer strictly requires `amount == 0` and provides no alternative revocation primitive (e.g. `decreaseAllowance`), allowances previously granted on such tokens CANNOT be revoked through this enforcer \\u2014 the executed `approve(spender, 0)` will revert inside the token contract. Delegators holding these tokens should revoke their allowances directly from the owning account (or via a different revocation path), and should be aware before signing a delegation or batch that includes this enforcer for such a token. The Permit2, ERC-721, and `setApprovalForAll` branches are unaffected.This enforcer operates only in single call type and default execution mode.\",\"kind\":\"dev\",\"methods\":{\"afterAllHook(bytes,bytes,bytes32,bytes,bytes32,address,address)\":{\"details\":\"This function MUST revert if the conditions are not met.\",\"params\":{\"_args\":\"An optional input parameter set by the redeemer at time of invocation.\",\"_delegationHash\":\"The hash of the delegation.\",\"_delegator\":\"The address of the delegator.\",\"_executionCalldata\":\"The data representing the execution.\",\"_mode\":\"The mode of execution for the executionCalldata.\",\"_redeemer\":\"The address that is redeeming the delegation.\",\"_terms\":\"The terms to enforce set by the delegator.\"}},\"afterHook(bytes,bytes,bytes32,bytes,bytes32,address,address)\":{\"details\":\"This function MUST revert if the conditions are not met.\",\"params\":{\"_args\":\"An optional input parameter set by the redeemer at time of invocation.\",\"_delegationHash\":\"The hash of the delegation.\",\"_delegator\":\"The address of the delegator.\",\"_executionCalldata\":\"The data representing the execution.\",\"_mode\":\"The mode of execution for the executionCalldata.\",\"_redeemer\":\"The address that is redeeming the delegation.\",\"_terms\":\"The terms to enforce set by the delegator.\"}},\"beforeAllHook(bytes,bytes,bytes32,bytes,bytes32,address,address)\":{\"details\":\"This function MUST revert if the conditions are not met.\",\"params\":{\"_args\":\"An optional input parameter set by the redeemer at time of invocation.\",\"_delegationHash\":\"The hash of the delegation.\",\"_delegator\":\"The address of the delegator.\",\"_executionCalldata\":\"The data representing the execution.\",\"_mode\":\"The mode of execution for the executionCalldata.\",\"_redeemer\":\"The address that is redeeming the delegation.\",\"_terms\":\"The terms to enforce set by the delegator.\"}},\"beforeHook(bytes,bytes,bytes32,bytes,bytes32,address,address)\":{\"params\":{\"_delegator\":\"The delegator of the delegation carrying this caveat (link-local, not the chain root). See the contract-level NatSpec for the implications in redelegation chains.\",\"_executionCallData\":\"Single execution targeting the token contract.\",\"_mode\":\"Must be single call type and default execution mode.\",\"_terms\":\"1-byte bitmask selecting which revocation primitives are allowed. See the contract NatSpec.\"}}},\"stateVariables\":{\"_PERMISSION_ERC20_APPROVE\":{\"details\":\"Permission flags packed into the single-byte terms bitmask.\"},\"_PERMIT2\":{\"details\":\"Canonical Permit2 deployment address (deterministic across EVM chains where Uniswap has deployed it, e.g. mainnet, Base, Arbitrum, Optimism, etc.). See the contract-level \\\"Trust assumption\\\" NatSpec for the implications on chains where canonical Permit2 is not deployed.\"},\"_PERMIT2_APPROVE_SELECTOR\":{\"details\":\"`bytes4(keccak256(\\\"approve(address,address,uint160,uint48)\\\"))` \\u2014 Permit2's `approve` selector.\"},\"_PERMIT2_INVALIDATE_NONCES_SELECTOR\":{\"details\":\"`bytes4(keccak256(\\\"invalidateNonces(address,address,uint48)\\\"))` \\u2014 Permit2's nonce-invalidation selector. The new nonce is required by Permit2 to be strictly greater than the current nonce (with a per-call delta capped at `type(uint16).max`); it can therefore only invalidate signed-but-unredeemed `permit` payloads, never create or extend an allowance.\"},\"_PERMIT2_LOCKDOWN_SELECTOR\":{\"details\":\"`bytes4(keccak256(\\\"lockdown((address,address)[])\\\"))` \\u2014 Permit2's batch revocation selector. Every entry of the array unconditionally zeros `amount` for the corresponding `(token, spender)` pair on the caller; `expiration` and `nonce` are left untouched. No parameter can be used to grant authority.\"}},\"title\":\"ApprovalRevocationEnforcer\",\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{\"afterAllHook(bytes,bytes,bytes32,bytes,bytes32,address,address)\":{\"notice\":\"Enforces conditions after all actions in a batch redemption process have been executed.\"},\"afterHook(bytes,bytes,bytes32,bytes,bytes32,address,address)\":{\"notice\":\"Enforces conditions after the execution tied to a specific delegation in the redemption process.\"},\"beforeAllHook(bytes,bytes,bytes32,bytes,bytes32,address,address)\":{\"notice\":\"Enforces conditions before any actions in a batch redemption process begin.\"},\"beforeHook(bytes,bytes,bytes32,bytes,bytes32,address,address)\":{\"notice\":\"Requires the execution to revoke an existing token approval owned by `_delegator`, and that the revocation primitive used is permitted by `_terms`.\"}},\"notice\":\"Allows a delegate to clear existing token approvals. The delegator controls which revocation primitives the delegate may perform via a 1-byte bitmask in `terms`: Bit 0 (`0x01`) \\u2014 ERC-20 `approve(spender, 0)` (spender non-zero, amount zero) Bit 1 (`0x02`) \\u2014 ERC-721 per-token `approve(address(0), tokenId)` Bit 2 (`0x04`) \\u2014 ERC-721 / ERC-1155 `setApprovalForAll(operator, false)` Bit 3 (`0x08`) \\u2014 Permit2 `approve(token, spender, 0, 0)` against the canonical Permit2 deployment Bit 4 (`0x10`) \\u2014 Permit2 `lockdown((address,address)[])` against the canonical Permit2 deployment Bit 5 (`0x20`) \\u2014 Permit2 `invalidateNonces(token, spender, newNonce)` against the canonical Permit2 deployment Bits 6-7 \\u2014 Reserved; MUST be zero. Examples: `0x01` \\u2014 delegate may only clear ERC-20 allowances. `0x04` \\u2014 delegate may only revoke operator approvals. `0x08` \\u2014 delegate may only revoke a single Permit2 allowance per call. `0x10` \\u2014 delegate may only batch-revoke Permit2 allowances via `lockdown`. `0x20` \\u2014 delegate may only invalidate Permit2 nonces (kill pending signed permits). `0x38` \\u2014 delegate may use all three Permit2 primitives (`approve` + `lockdown` + `invalidateNonces`), which together fully sever a Permit2 (token, spender) exposure (on-chain allowance + pending signed permits). `0x3F` \\u2014 delegate may use all six revocation primitives. Terms MUST be exactly 1 byte, MUST not be zero, and MUST NOT set any reserved bit.\",\"version\":1}},\"settings\":{\"compilationTarget\":{\"src/enforcers/ApprovalRevocationEnforcer.sol\":\"ApprovalRevocationEnforcer\"},\"evmVersion\":\"london\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[\":@FCL/=lib/FCL/solidity/src/\",\":@SCL/=lib/SCL/src/\",\":@account-abstraction/=lib/account-abstraction/contracts/\",\":@bytes-utils/=lib/solidity-bytes-utils/contracts/\",\":@erc7579/=lib/erc7579-implementation/src/\",\":@openzeppelin/contracts-upgradeable/=lib/openzeppelin-upgradable-contracts/contracts/\",\":@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/\",\":@solidity-stringutils/=lib/solidity-stringutils/src/\",\":@solidity/=lib/SCL/src/\",\":FCL/=lib/FCL/solidity/src/\",\":FreshCryptoLib/=lib/FreshCryptoLib/solidity/src/\",\":SCL/=lib/SCL/\",\":account-abstraction/=lib/account-abstraction/contracts/\",\":ds-test/=lib/forge-std/lib/ds-test/src/\",\":erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/\",\":erc7579-implementation/=lib/erc7579-implementation/src/\",\":forge-std/=lib/forge-std/src/\",\":openzeppelin-contracts/=lib/openzeppelin-contracts/\",\":sentinellist/=lib/erc7579-implementation/node_modules/@rhinestone/sentinellist/src/\",\":solady/=lib/erc7579-implementation/node_modules/solady/src/\",\":solidity-bytes-utils/=lib/solidity-bytes-utils/contracts/\",\":solidity-stringutils/=lib/solidity-stringutils/\"]},\"sources\":{\"lib/account-abstraction/contracts/interfaces/PackedUserOperation.sol\":{\"keccak256\":\"0x1129b46381db68eddbc5cb49e50664667b66b03c480453858e7b25eabe444359\",\"license\":\"GPL-3.0\",\"urls\":[\"bzz-raw://499a948aba60480dba6e25c763b8d918f1c246eb7a3302e04f493e080f3295be\",\"dweb:/ipfs/QmeRhhswf4NACcBKam2PyjpTP2ddSm648kah5kkQJsvwz3\"]},\"lib/erc7579-implementation/src/interfaces/IERC7579Account.sol\":{\"keccak256\":\"0x08368aba5f1bb83eb57e9f8d31a04edee4be69159b4844a6443f65100917011c\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://789244f53fb8b25f4bacb8ac0b9c2f5ea2067a9c5c7bd13fd00e10618e52e2ff\",\"dweb:/ipfs/QmVSbGSyDpfL6UfyvaMBQHmV9u52wway1h3caJnvXVgkvn\"]},\"lib/erc7579-implementation/src/lib/ExecutionLib.sol\":{\"keccak256\":\"0xb436732367ed8a45ddfaa95c9c977ee7674ff5755353c3792a38608640406126\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://8a60f10547e9d25e96e516406f16a35ce075dd4db79b7dccd92f90224c9f23b4\",\"dweb:/ipfs/QmcC3jpNmo7kT1oU8taPuZRjcWh88J3d4rv3hYsQKRVBwW\"]},\"lib/erc7579-implementation/src/lib/ModeLib.sol\":{\"keccak256\":\"0x02ce0fc8805d1427841f32e2335157e3fae18e99d5c49ba46d53e12fcd1a5185\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://9900d0dfb593b136a058a16f1817f60d5096c3eca6adea662381e12c241b14b4\",\"dweb:/ipfs/QmdsfLLtfzY9oEpZxk1Ve1PLuPQdphZcNK92L6mHa8n1h7\"]},\"lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol\":{\"keccak256\":\"0xee2337af2dc162a973b4be6d3f7c16f06298259e0af48c5470d2839bfa8a22f4\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://30c476b4b2f405c1bb3f0bae15b006d129c80f1bfd9d0f2038160a3bb9745009\",\"dweb:/ipfs/Qmb3VcuDufv6xbHeVgksC4tHpc5gKYVqBEwjEXW72XzSvN\"]},\"lib/openzeppelin-contracts/contracts/token/ERC721/IERC721.sol\":{\"keccak256\":\"0xe0e3a2099f2e2ce3579dd35548f613928739642058dfec95b1745f93364ce3de\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://36a3930518e5c4a3c83839aaa136b863af513e6eeee6a3654e8f910f8007f827\",\"dweb:/ipfs/QmcU1b6SYYUMiXJ6jd5HY6sgYjJLdBu4smak1X1FDgkoaA\"]},\"lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol\":{\"keccak256\":\"0xc859863e3bda7ec3cddf6dafe2ffe91bcbe648d1395b856b839c32ee9617c44c\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://a9d5417888b873cf2225ed5d50b2a67be97c1504134a2a580512168d587ad82e\",\"dweb:/ipfs/QmNr5fTb2heFW658NZn7dDnofZgFvQTnNxKRJ3wdnR1skX\"]},\"src/enforcers/ApprovalRevocationEnforcer.sol\":{\"keccak256\":\"0x7870b8711976fe608e6c796ef949dbba168848cb97b987443446f833d93fab10\",\"license\":\"MIT AND Apache-2.0\",\"urls\":[\"bzz-raw://727b8beec31bb5c79ccfdfa0145ffbef5c02d7e45385cff8087ca2e56690b20d\",\"dweb:/ipfs/QmX8Uoi3PhryQV4yBgUkdik746nK74ZdP52TBdWge9Hax2\"]},\"src/enforcers/CaveatEnforcer.sol\":{\"keccak256\":\"0xe6e44a3dfcb667fba89d7dc73ba06327add01a9ef4ecd11f3627d97e337041d5\",\"license\":\"MIT AND Apache-2.0\",\"urls\":[\"bzz-raw://79988dc7b56022300549c6ede1c7075fdcadb308191e0ece4f85f94f69036bb5\",\"dweb:/ipfs/QmRG1q4NFfo6RpoVWMdK3679eLg2fUru4dUESwCcmxzvoz\"]},\"src/interfaces/ICaveatEnforcer.sol\":{\"keccak256\":\"0x61c2d69cb8cc4b04d5ce786ff1b38c07009a262b64900e58a109239942bfad16\",\"license\":\"MIT AND Apache-2.0\",\"urls\":[\"bzz-raw://a67f09f1229e703574dff9103daafa494e90e22577691f180ecfc2c3f50a3ecc\",\"dweb:/ipfs/QmVFXq8mx2PMkgMLoh3pTyQqkH9umwRUjKXtjeZscktf79\"]},\"src/utils/Constants.sol\":{\"keccak256\":\"0x5f1e2913a96b7f88bc7fbd2de329465822ae0d438ff212883c6dd3bd5e9609a5\",\"license\":\"MIT AND Apache-2.0\",\"urls\":[\"bzz-raw://51a750792520aaf10cb246112d5554bb33007edd8be97b78e90be9847d663978\",\"dweb:/ipfs/QmbQSZGNeG9f8DNVbCEo1SGRoJU4fQJwQfUHrzQNayFWNs\"]},\"src/utils/Types.sol\":{\"keccak256\":\"0xda4094eb4839087157f34750fb4d1a2d6241fbb8ee0d41fd1f2905209e6a05b0\",\"license\":\"MIT AND Apache-2.0\",\"urls\":[\"bzz-raw://1fab0d416245579b01d908ffb607f41ace7b103c1c6a3b9d0f8b32f29ac46247\",\"dweb:/ipfs/QmfDWFbdCEkvaKnPpZmDPtxYVBxHt2XA2zY9jon9Jm7pFf\"]}},\"version\":1}","metadata":{"compiler":{"version":"0.8.23+commit.f704f362"},"language":"Solidity","output":{"abi":[{"inputs":[{"internalType":"bytes","name":"","type":"bytes"},{"internalType":"bytes","name":"","type":"bytes"},{"internalType":"ModeCode","name":"","type":"bytes32"},{"internalType":"bytes","name":"","type":"bytes"},{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"stateMutability":"nonpayable","type":"function","name":"afterAllHook"},{"inputs":[{"internalType":"bytes","name":"","type":"bytes"},{"internalType":"bytes","name":"","type":"bytes"},{"internalType":"ModeCode","name":"","type":"bytes32"},{"internalType":"bytes","name":"","type":"bytes"},{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"stateMutability":"nonpayable","type":"function","name":"afterHook"},{"inputs":[{"internalType":"bytes","name":"","type":"bytes"},{"internalType":"bytes","name":"","type":"bytes"},{"internalType":"ModeCode","name":"","type":"bytes32"},{"internalType":"bytes","name":"","type":"bytes"},{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"stateMutability":"nonpayable","type":"function","name":"beforeAllHook"},{"inputs":[{"internalType":"bytes","name":"_terms","type":"bytes"},{"internalType":"bytes","name":"","type":"bytes"},{"internalType":"ModeCode","name":"_mode","type":"bytes32"},{"internalType":"bytes","name":"_executionCallData","type":"bytes"},{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"address","name":"_delegator","type":"address"},{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function","name":"beforeHook"}],"devdoc":{"kind":"dev","methods":{"afterAllHook(bytes,bytes,bytes32,bytes,bytes32,address,address)":{"details":"This function MUST revert if the conditions are not met.","params":{"_args":"An optional input parameter set by the redeemer at time of invocation.","_delegationHash":"The hash of the delegation.","_delegator":"The address of the delegator.","_executionCalldata":"The data representing the execution.","_mode":"The mode of execution for the executionCalldata.","_redeemer":"The address that is redeeming the delegation.","_terms":"The terms to enforce set by the delegator."}},"afterHook(bytes,bytes,bytes32,bytes,bytes32,address,address)":{"details":"This function MUST revert if the conditions are not met.","params":{"_args":"An optional input parameter set by the redeemer at time of invocation.","_delegationHash":"The hash of the delegation.","_delegator":"The address of the delegator.","_executionCalldata":"The data representing the execution.","_mode":"The mode of execution for the executionCalldata.","_redeemer":"The address that is redeeming the delegation.","_terms":"The terms to enforce set by the delegator."}},"beforeAllHook(bytes,bytes,bytes32,bytes,bytes32,address,address)":{"details":"This function MUST revert if the conditions are not met.","params":{"_args":"An optional input parameter set by the redeemer at time of invocation.","_delegationHash":"The hash of the delegation.","_delegator":"The address of the delegator.","_executionCalldata":"The data representing the execution.","_mode":"The mode of execution for the executionCalldata.","_redeemer":"The address that is redeeming the delegation.","_terms":"The terms to enforce set by the delegator."}},"beforeHook(bytes,bytes,bytes32,bytes,bytes32,address,address)":{"params":{"_delegator":"The delegator of the delegation carrying this caveat (link-local, not the chain root). See the contract-level NatSpec for the implications in redelegation chains.","_executionCallData":"Single execution targeting the token contract.","_mode":"Must be single call type and default execution mode.","_terms":"1-byte bitmask selecting which revocation primitives are allowed. See the contract NatSpec."}}},"version":1},"userdoc":{"kind":"user","methods":{"afterAllHook(bytes,bytes,bytes32,bytes,bytes32,address,address)":{"notice":"Enforces conditions after all actions in a batch redemption process have been executed."},"afterHook(bytes,bytes,bytes32,bytes,bytes32,address,address)":{"notice":"Enforces conditions after the execution tied to a specific delegation in the redemption process."},"beforeAllHook(bytes,bytes,bytes32,bytes,bytes32,address,address)":{"notice":"Enforces conditions before any actions in a batch redemption process begin."},"beforeHook(bytes,bytes,bytes32,bytes,bytes32,address,address)":{"notice":"Requires the execution to revoke an existing token approval owned by `_delegator`, and that the revocation primitive used is permitted by `_terms`."}},"version":1}},"settings":{"remappings":["@FCL/=lib/FCL/solidity/src/","@SCL/=lib/SCL/src/","@account-abstraction/=lib/account-abstraction/contracts/","@bytes-utils/=lib/solidity-bytes-utils/contracts/","@erc7579/=lib/erc7579-implementation/src/","@openzeppelin/contracts-upgradeable/=lib/openzeppelin-upgradable-contracts/contracts/","@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/","@solidity-stringutils/=lib/solidity-stringutils/src/","@solidity/=lib/SCL/src/","FCL/=lib/FCL/solidity/src/","FreshCryptoLib/=lib/FreshCryptoLib/solidity/src/","SCL/=lib/SCL/","account-abstraction/=lib/account-abstraction/contracts/","ds-test/=lib/forge-std/lib/ds-test/src/","erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/","erc7579-implementation/=lib/erc7579-implementation/src/","forge-std/=lib/forge-std/src/","openzeppelin-contracts/=lib/openzeppelin-contracts/","sentinellist/=lib/erc7579-implementation/node_modules/@rhinestone/sentinellist/src/","solady/=lib/erc7579-implementation/node_modules/solady/src/","solidity-bytes-utils/=lib/solidity-bytes-utils/contracts/","solidity-stringutils/=lib/solidity-stringutils/"],"optimizer":{"enabled":true,"runs":200},"metadata":{"bytecodeHash":"ipfs"},"compilationTarget":{"src/enforcers/ApprovalRevocationEnforcer.sol":"ApprovalRevocationEnforcer"},"evmVersion":"london","libraries":{}},"sources":{"lib/account-abstraction/contracts/interfaces/PackedUserOperation.sol":{"keccak256":"0x1129b46381db68eddbc5cb49e50664667b66b03c480453858e7b25eabe444359","urls":["bzz-raw://499a948aba60480dba6e25c763b8d918f1c246eb7a3302e04f493e080f3295be","dweb:/ipfs/QmeRhhswf4NACcBKam2PyjpTP2ddSm648kah5kkQJsvwz3"],"license":"GPL-3.0"},"lib/erc7579-implementation/src/interfaces/IERC7579Account.sol":{"keccak256":"0x08368aba5f1bb83eb57e9f8d31a04edee4be69159b4844a6443f65100917011c","urls":["bzz-raw://789244f53fb8b25f4bacb8ac0b9c2f5ea2067a9c5c7bd13fd00e10618e52e2ff","dweb:/ipfs/QmVSbGSyDpfL6UfyvaMBQHmV9u52wway1h3caJnvXVgkvn"],"license":"MIT"},"lib/erc7579-implementation/src/lib/ExecutionLib.sol":{"keccak256":"0xb436732367ed8a45ddfaa95c9c977ee7674ff5755353c3792a38608640406126","urls":["bzz-raw://8a60f10547e9d25e96e516406f16a35ce075dd4db79b7dccd92f90224c9f23b4","dweb:/ipfs/QmcC3jpNmo7kT1oU8taPuZRjcWh88J3d4rv3hYsQKRVBwW"],"license":"MIT"},"lib/erc7579-implementation/src/lib/ModeLib.sol":{"keccak256":"0x02ce0fc8805d1427841f32e2335157e3fae18e99d5c49ba46d53e12fcd1a5185","urls":["bzz-raw://9900d0dfb593b136a058a16f1817f60d5096c3eca6adea662381e12c241b14b4","dweb:/ipfs/QmdsfLLtfzY9oEpZxk1Ve1PLuPQdphZcNK92L6mHa8n1h7"],"license":"MIT"},"lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol":{"keccak256":"0xee2337af2dc162a973b4be6d3f7c16f06298259e0af48c5470d2839bfa8a22f4","urls":["bzz-raw://30c476b4b2f405c1bb3f0bae15b006d129c80f1bfd9d0f2038160a3bb9745009","dweb:/ipfs/Qmb3VcuDufv6xbHeVgksC4tHpc5gKYVqBEwjEXW72XzSvN"],"license":"MIT"},"lib/openzeppelin-contracts/contracts/token/ERC721/IERC721.sol":{"keccak256":"0xe0e3a2099f2e2ce3579dd35548f613928739642058dfec95b1745f93364ce3de","urls":["bzz-raw://36a3930518e5c4a3c83839aaa136b863af513e6eeee6a3654e8f910f8007f827","dweb:/ipfs/QmcU1b6SYYUMiXJ6jd5HY6sgYjJLdBu4smak1X1FDgkoaA"],"license":"MIT"},"lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol":{"keccak256":"0xc859863e3bda7ec3cddf6dafe2ffe91bcbe648d1395b856b839c32ee9617c44c","urls":["bzz-raw://a9d5417888b873cf2225ed5d50b2a67be97c1504134a2a580512168d587ad82e","dweb:/ipfs/QmNr5fTb2heFW658NZn7dDnofZgFvQTnNxKRJ3wdnR1skX"],"license":"MIT"},"src/enforcers/ApprovalRevocationEnforcer.sol":{"keccak256":"0x7870b8711976fe608e6c796ef949dbba168848cb97b987443446f833d93fab10","urls":["bzz-raw://727b8beec31bb5c79ccfdfa0145ffbef5c02d7e45385cff8087ca2e56690b20d","dweb:/ipfs/QmX8Uoi3PhryQV4yBgUkdik746nK74ZdP52TBdWge9Hax2"],"license":"MIT AND Apache-2.0"},"src/enforcers/CaveatEnforcer.sol":{"keccak256":"0xe6e44a3dfcb667fba89d7dc73ba06327add01a9ef4ecd11f3627d97e337041d5","urls":["bzz-raw://79988dc7b56022300549c6ede1c7075fdcadb308191e0ece4f85f94f69036bb5","dweb:/ipfs/QmRG1q4NFfo6RpoVWMdK3679eLg2fUru4dUESwCcmxzvoz"],"license":"MIT AND Apache-2.0"},"src/interfaces/ICaveatEnforcer.sol":{"keccak256":"0x61c2d69cb8cc4b04d5ce786ff1b38c07009a262b64900e58a109239942bfad16","urls":["bzz-raw://a67f09f1229e703574dff9103daafa494e90e22577691f180ecfc2c3f50a3ecc","dweb:/ipfs/QmVFXq8mx2PMkgMLoh3pTyQqkH9umwRUjKXtjeZscktf79"],"license":"MIT AND Apache-2.0"},"src/utils/Constants.sol":{"keccak256":"0x5f1e2913a96b7f88bc7fbd2de329465822ae0d438ff212883c6dd3bd5e9609a5","urls":["bzz-raw://51a750792520aaf10cb246112d5554bb33007edd8be97b78e90be9847d663978","dweb:/ipfs/QmbQSZGNeG9f8DNVbCEo1SGRoJU4fQJwQfUHrzQNayFWNs"],"license":"MIT AND Apache-2.0"},"src/utils/Types.sol":{"keccak256":"0xda4094eb4839087157f34750fb4d1a2d6241fbb8ee0d41fd1f2905209e6a05b0","urls":["bzz-raw://1fab0d416245579b01d908ffb607f41ace7b103c1c6a3b9d0f8b32f29ac46247","dweb:/ipfs/QmfDWFbdCEkvaKnPpZmDPtxYVBxHt2XA2zY9jon9Jm7pFf"],"license":"MIT AND Apache-2.0"}},"version":1},"id":61} \ No newline at end of file diff --git a/packages/delegation-abis/src/bytecode.ts b/packages/delegation-abis/src/bytecode.ts index 20f0c499..f58159ff 100644 --- a/packages/delegation-abis/src/bytecode.ts +++ b/packages/delegation-abis/src/bytecode.ts @@ -2,6 +2,7 @@ export { bytecode as Address } from './bytecode/Address'; export { bytecode as AllowedCalldataEnforcer } from './bytecode/AllowedCalldataEnforcer'; export { bytecode as AllowedMethodsEnforcer } from './bytecode/AllowedMethodsEnforcer'; export { bytecode as AllowedTargetsEnforcer } from './bytecode/AllowedTargetsEnforcer'; +export { bytecode as ApprovalRevocationEnforcer } from './bytecode/ApprovalRevocationEnforcer'; export { bytecode as ArgsEqualityCheckEnforcer } from './bytecode/ArgsEqualityCheckEnforcer'; export { bytecode as Base64 } from './bytecode/Base64'; export { bytecode as Base64URL } from './bytecode/Base64URL'; diff --git a/packages/delegation-abis/src/bytecode/ApprovalRevocationEnforcer.ts b/packages/delegation-abis/src/bytecode/ApprovalRevocationEnforcer.ts new file mode 100644 index 00000000..69c54c0b --- /dev/null +++ b/packages/delegation-abis/src/bytecode/ApprovalRevocationEnforcer.ts @@ -0,0 +1 @@ +export const bytecode = '0x608060405234801561001057600080fd5b50610f82806100206000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c8063414c3e3314610051578063a145832a1461006d578063d3eddcc514610051578063ed46336714610051575b600080fd5b61006b61005f366004610bff565b50505050505050505050565b005b61006b61007b366004610bff565b85610087816000610617565b6100d85760405162461bcd60e51b815260206004820181905260248201527f436176656174456e666f726365723a696e76616c69642d63616c6c2d7479706560448201526064015b60405180910390fd5b86600881901b6100e9816000610617565b6101435760405162461bcd60e51b815260206004820152602560248201527f436176656174456e666f726365723a696e76616c69642d657865637574696f6e6044820152642d7479706560d81b60648201526084016100cf565b5060006101508d8d61062e565b90506000803660006101628c8c61075b565b9350935093509350826000146101b95760405162461bcd60e51b81526020600482015260286024820152600080516020610f2d83398151915260448201526769642d76616c756560c01b60648201526084016100cf565b60048110156101da5760405162461bcd60e51b81526004016100cf90610cd2565b60006101e96004828486610d13565b6101f291610d3d565b90506378ae83bb60e01b6001600160e01b0319821601610282576008861660000361026c5760405162461bcd60e51b81526020600482015260366024820152600080516020610f0d8339815191526044820152751d0c8b585c1c1c9bdd994b5b9bdd0b585b1b1bddd95960521b60648201526084016100cf565b6102778584846107ac565b505050505050610609565b6333acd78160e01b6001600160e01b031982160161033857601086166000036103015760405162461bcd60e51b81526020600482015260376024820152600080516020610f0d83398151915260448201527f74322d6c6f636b646f776e2d6e6f742d616c6c6f77656400000000000000000060648201526084016100cf565b6001600160a01b0385166e22d473030f116ddee9f6b43ac78ba3146102775760405162461bcd60e51b81526004016100cf90610d6d565b632689a37160e21b6001600160e01b03198216016103b95760208616600003610301576040805162461bcd60e51b8152602060048201526024810191909152600080516020610f0d83398151915260448201527f74322d696e76616c69646174652d6e6f6e6365732d6e6f742d616c6c6f77656460648201526084016100cf565b604482146103d95760405162461bcd60e51b81526004016100cf90610cd2565b635dd34b9b60e01b6001600160e01b0319821601610476576004861660000361046a5760405162461bcd60e51b815260206004820152603860248201527f417070726f76616c5265766f636174696f6e456e666f726365723a736574417060448201527f70726f76616c466f72416c6c2d6e6f742d616c6c6f776564000000000000000060648201526084016100cf565b6102778584848d6108fb565b63f6a1584d60e01b6001600160e01b03198216016105c157600061049e602460048587610d13565b6104a791610dac565b90506001600160a01b03811661053857600287166000036105285760405162461bcd60e51b815260206004820152603560248201527f417070726f76616c5265766f636174696f6e456e666f726365723a65726337326044820152740c4b585c1c1c9bdd994b5b9bdd0b585b1b1bddd959605a1b60648201526084016100cf565b610533868585610a25565b6105b5565b600187166000036105a85760405162461bcd60e51b815260206004820152603460248201527f417070726f76616c5265766f636174696f6e456e666f726365723a65726332306044820152730b585c1c1c9bdd994b5b9bdd0b585b1b1bddd95960621b60648201526084016100cf565b6105b58685858e85610ad9565b50505050505050610609565b60405162461bcd60e51b81526020600482015260296024820152600080516020610f2d8339815191526044820152681a590b5b595d1a1bd960ba1b60648201526084016100cf565b505050505050505050505050565b6001600160f81b0319828116908216145b92915050565b6000600182146106865760405162461bcd60e51b815260206004820152602f6024820152600080516020610f2d83398151915260448201526e0d2c85ae8cae4dae65ad8cadccee8d608b1b60648201526084016100cf565b8282600081811061069957610699610dca565b919091013560f81c915050600081900361070b5760405162461bcd60e51b815260206004820152602d60248201527f417070726f76616c5265766f636174696f6e456e666f726365723a6e6f2d6d6560448201526c1d1a1bd91ccb585b1b1bddd959609a1b60648201526084016100cf565b60c08116156106285760405162461bcd60e51b81526020600482015260286024820152600080516020610f2d83398151915260448201526769642d7465726d7360c01b60648201526084016100cf565b600080368161076d6014828789610d13565b61077691610de0565b60601c9350610789603460148789610d13565b61079291610dac565b92506107a18560348189610d13565b949793965094505050565b6001600160a01b0383166e22d473030f116ddee9f6b43ac78ba3146107e35760405162461bcd60e51b81526004016100cf90610d6d565b608481146108475760405162461bcd60e51b815260206004820152603b6024820152600080516020610f0d83398151915260448201527f74322d696e76616c69642d657865637574696f6e2d6c656e677468000000000060648201526084016100cf565b610855606460448385610d13565b61085e91610dac565b1561087b5760405162461bcd60e51b81526004016100cf90610e13565b610889608460648385610d13565b61089291610dac565b156108f65760405162461bcd60e51b815260206004820152602e60248201527f417070726f76616c5265766f636174696f6e456e666f726365723a6e6f6e2d7a60448201526d32b93796b2bc3834b930ba34b7b760911b60648201526084016100cf565b505050565b610909604460248486610d13565b61091291610dac565b156109735760405162461bcd60e51b815260206004820152602b60248201527f417070726f76616c5265766f636174696f6e456e666f726365723a6e6f742d6160448201526a16b932bb37b1b0ba34b7b760a91b60648201526084016100cf565b6000610983602460048587610d13565b61098c91610dac565b60405163e985e9c560e01b81526001600160a01b03848116600483015280831660248301529192509086169063e985e9c590604401602060405180830381865afa1580156109de573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a029190610e5d565b610a1e5760405162461bcd60e51b81526004016100cf90610e86565b5050505050565b6000610a35604460248486610d13565b610a3e91610dac565b60405163020604bf60e21b8152600481018290529091506000906001600160a01b0386169063081812fc90602401602060405180830381865afa158015610a89573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610aad9190610ed6565b6001600160a01b031603610ad35760405162461bcd60e51b81526004016100cf90610e86565b50505050565b610ae7604460248587610d13565b610af091610dac565b15610b0d5760405162461bcd60e51b81526004016100cf90610e13565b604051636eb1769f60e11b81526001600160a01b038381166004830152828116602483015286169063dd62ed3e90604401602060405180830381865afa158015610b5b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610b7f9190610ef3565b600003610a1e5760405162461bcd60e51b81526004016100cf90610e86565b60008083601f840112610bb057600080fd5b50813567ffffffffffffffff811115610bc857600080fd5b602083019150836020828501011115610be057600080fd5b9250929050565b6001600160a01b0381168114610bfc57600080fd5b50565b60008060008060008060008060008060e08b8d031215610c1e57600080fd5b8a3567ffffffffffffffff80821115610c3657600080fd5b610c428e838f01610b9e565b909c509a5060208d0135915080821115610c5b57600080fd5b610c678e838f01610b9e565b909a50985060408d0135975060608d0135915080821115610c8757600080fd5b50610c948d828e01610b9e565b90965094505060808b0135925060a08b0135610caf81610be7565b915060c08b0135610cbf81610be7565b809150509295989b9194979a5092959850565b6020808252603390820152600080516020610f2d8339815191526040820152720d2c85acaf0cac6eae8d2dedc5ad8cadccee8d606b1b606082015260800190565b60008085851115610d2357600080fd5b83861115610d3057600080fd5b5050820193919092039150565b6001600160e01b03198135818116916004851015610d655780818660040360031b1b83161692505b505092915050565b6020808252603190820152600080516020610f2d8339815191526040820152701a590b5c195c9b5a5d0c8b5d185c99d95d607a1b606082015260800190565b8035602083101561062857600019602084900360031b1b1692915050565b634e487b7160e01b600052603260045260246000fd5b6bffffffffffffffffffffffff198135818116916014851015610d655760149490940360031b84901b1690921692915050565b6020808252602a908201527f417070726f76616c5265766f636174696f6e456e666f726365723a6e6f6e2d7a604082015269195c9bcb585b5bdd5b9d60b21b606082015260800190565b600060208284031215610e6f57600080fd5b81518015158114610e7f57600080fd5b9392505050565b60208082526030908201527f417070726f76616c5265766f636174696f6e456e666f726365723a6e6f2d617060408201526f70726f76616c2d746f2d7265766f6b6560801b606082015260800190565b600060208284031215610ee857600080fd5b8151610e7f81610be7565b600060208284031215610f0557600080fd5b505191905056fe417070726f76616c5265766f636174696f6e456e666f726365723a7065726d69417070726f76616c5265766f636174696f6e456e666f726365723a696e76616ca26469706673582212200b9b4fc3ceb80b33a7a3836360e7e30ce648157075b95685bdfd33482dcf19f764736f6c63430008170033' as const; \ No newline at end of file diff --git a/packages/delegation-abis/src/index.ts b/packages/delegation-abis/src/index.ts index 2e3e70bf..0786ed68 100644 --- a/packages/delegation-abis/src/index.ts +++ b/packages/delegation-abis/src/index.ts @@ -2,6 +2,7 @@ export { abi as Address } from './abis/Address'; export { abi as AllowedCalldataEnforcer } from './abis/AllowedCalldataEnforcer'; export { abi as AllowedMethodsEnforcer } from './abis/AllowedMethodsEnforcer'; export { abi as AllowedTargetsEnforcer } from './abis/AllowedTargetsEnforcer'; +export { abi as ApprovalRevocationEnforcer } from './abis/ApprovalRevocationEnforcer'; export { abi as ArgsEqualityCheckEnforcer } from './abis/ArgsEqualityCheckEnforcer'; export { abi as Base64 } from './abis/Base64'; export { abi as Base64URL } from './abis/Base64URL'; diff --git a/packages/delegation-core/CHANGELOG.md b/packages/delegation-core/CHANGELOG.md index 0b7e19eb..23bf3354 100644 --- a/packages/delegation-core/CHANGELOG.md +++ b/packages/delegation-core/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Encoding and decoding utils for `ApprovalRevocationEnforcer` enforcer args and terms ([#226](https://github.com/metamask/smart-accounts-kit/pull/226)) + ## [2.1.0] ### Changed diff --git a/packages/delegation-core/src/caveats/approvalRevocationEnforcer.ts b/packages/delegation-core/src/caveats/approvalRevocationEnforcer.ts new file mode 100644 index 00000000..74bf89fe --- /dev/null +++ b/packages/delegation-core/src/caveats/approvalRevocationEnforcer.ts @@ -0,0 +1,187 @@ +/* eslint-disable no-bitwise */ +/** + * ## ApprovalRevocationEnforcer + * + * Grants authority to revoke token approvals via: + * - ERC-20 `approve(spender, 0)` (spender non-zero, amount zero) + * - ERC-721 per-token `approve(address(0), tokenId)` + * - ERC-721 / ERC-1155 `setApprovalForAll(operator, false)` + * + * Terms are encoded as exactly **one byte**, interpreted as a bitmask: + * | Bit | Hex mask | Revocation primitive | + * |-----|----------|-------------------| + * | 0 | `0x01` | ERC-20 `approve(spender, 0)` | + * | 1 | `0x02` | ERC-721 `approve(address(0), tokenId)` | + * | 2 | `0x04` | `setApprovalForAll(operator, false)` (ERC-721 & ERC-1155) | + * | 3 | `0x08` | Permit2 `approve(token, spender, 0, 0)` | + * | 4 | `0x10` | Permit2 `lockdown((address,address)[])` | + * | 5 | `0x20` | Permit2 `invalidateNonces(token, spender, newNonce)` | * + * + * Bits 6–7 must be zero on the wire. + */ + +import type { BytesLike } from '@metamask/utils'; + +import { + assertHexByteExactLength, + extractNumber, + toHexString, +} from '../internalUtils'; +import { + bytesLikeToHex, + defaultOptions, + prepareResult, + type EncodingOptions, + type ResultValue, +} from '../returns'; +import type { Hex } from '../types'; + +const BIT_ERC20_APPROVE_ZERO = 0x01; +const BIT_ERC721_PER_TOKEN_CLEAR = 0x02; +const BIT_SET_APPROVAL_FOR_ALL_REVOKE = 0x04; +const BIT_PERMIT2_APPROVE_ZERO = 0x08; +const BIT_PERMIT2_LOCKDOWN = 0x10; +const BIT_PERMIT2_INVALIDATE_NONCES = 0x20; + +const ALLOWED_APPROVAL_REVOCATION_MAX_MASK = + BIT_ERC20_APPROVE_ZERO | + BIT_ERC721_PER_TOKEN_CLEAR | + BIT_SET_APPROVAL_FOR_ALL_REVOKE | + BIT_PERMIT2_APPROVE_ZERO | + BIT_PERMIT2_LOCKDOWN | + BIT_PERMIT2_INVALIDATE_NONCES; + +const NO_FLAGS_SET_ERROR = + 'Invalid ApprovalRevocation terms: at least one revocation primitive must be enabled'; + +/** + * Human-readable selection of which revocation primitives the caveat allows. + * Encode with {@link createApprovalRevocationTerms}; decode with {@link decodeApprovalRevocationTerms}. + */ +export type ApprovalRevocationTerms = { + /** Allow revoking ERC-20 allowances via `approve(spender, 0)`. */ + erc20Approve: boolean; + /** Allow clearing ERC-721 per-token approval via `approve(address(0), tokenId)`. */ + erc721Approve: boolean; + /** Allow revoking operator access via `setApprovalForAll(operator, false)` (ERC-721 / ERC-1155). */ + erc721SetApprovalForAll: boolean; + /** Allow revoking Permit2 approvals via `approve(token, spender, 0, 0)`. */ + permit2ApproveZero: boolean; + /** Allow revoking Permit2 lockdown via `lockdown((address,address)[])`. */ + permit2Lockdown: boolean; + /** Allow revoking Permit2 invalidateNonces via `invalidateNonces(token, spender, newNonce)`. */ + permit2InvalidateNonces: boolean; +}; + +/** + * Maps {@link ApprovalRevocationTerms} to the single-byte bitmask on the wire. + * + * @param terms - Selected Revocation primitives. + * @returns Integer mask in specifying the allowed revocation primitives. + * @throws Error if no flags are set. + */ +function termsToMask(terms: ApprovalRevocationTerms): number { + let mask = 0; + if (terms.erc20Approve) { + mask |= BIT_ERC20_APPROVE_ZERO; + } + if (terms.erc721Approve) { + mask |= BIT_ERC721_PER_TOKEN_CLEAR; + } + if (terms.erc721SetApprovalForAll) { + mask |= BIT_SET_APPROVAL_FOR_ALL_REVOKE; + } + if (terms.permit2ApproveZero) { + mask |= BIT_PERMIT2_APPROVE_ZERO; + } + if (terms.permit2Lockdown) { + mask |= BIT_PERMIT2_LOCKDOWN; + } + if (terms.permit2InvalidateNonces) { + mask |= BIT_PERMIT2_INVALIDATE_NONCES; + } + if (mask === 0) { + throw new Error(NO_FLAGS_SET_ERROR); + } + return mask; +} + +/** + * Parses a validated 1..63 bitmask into {@link ApprovalRevocationTerms}. + * + * @param mask - Integer byte value; only bits 0-5 may be set, and at least one must be set. + * @returns Flag object for encoding/decoding. + * @throws Error if reserved bits are set or the mask is zero. + */ +function maskToTerms(mask: number): ApprovalRevocationTerms { + if (mask > ALLOWED_APPROVAL_REVOCATION_MAX_MASK) { + throw new Error( + 'Invalid ApprovalRevocation terms: reserved bits must be zero (only bits 0-5 are defined)', + ); + } + + if (mask === 0) { + throw new Error(NO_FLAGS_SET_ERROR); + } + + return { + erc20Approve: (mask & BIT_ERC20_APPROVE_ZERO) !== 0, + erc721Approve: (mask & BIT_ERC721_PER_TOKEN_CLEAR) !== 0, + erc721SetApprovalForAll: (mask & BIT_SET_APPROVAL_FOR_ALL_REVOKE) !== 0, + permit2ApproveZero: (mask & BIT_PERMIT2_APPROVE_ZERO) !== 0, + permit2Lockdown: (mask & BIT_PERMIT2_LOCKDOWN) !== 0, + permit2InvalidateNonces: (mask & BIT_PERMIT2_INVALIDATE_NONCES) !== 0, + }; +} + +/** + * Creates terms for an ApprovalRevocation caveat. + * + * @param terms - Which Revocation primitives are permitted. At least one flag must be `true`. + * @param encodingOptions - The encoding options for the result. + * @returns Encoded terms (one byte). + * @throws Error if no Revocation primitive is enabled. + */ +export function createApprovalRevocationTerms( + terms: ApprovalRevocationTerms, + encodingOptions?: EncodingOptions<'hex'>, +): Hex; +export function createApprovalRevocationTerms( + terms: ApprovalRevocationTerms, + encodingOptions: EncodingOptions<'bytes'>, +): Uint8Array; +/** + * Creates terms for an ApprovalRevocation caveat. + * + * @param terms - Which Revocation primitives are permitted. At least one flag must be `true`. + * @param encodingOptions - The encoding options for the result. + * @returns Encoded terms (one byte). + * @throws Error if no Revocation primitive is enabled. + */ +export function createApprovalRevocationTerms( + terms: ApprovalRevocationTerms, + encodingOptions: EncodingOptions = defaultOptions, +): Hex | Uint8Array { + const mask = termsToMask(terms); + const hexValue = `0x${toHexString({ value: mask, size: 1 })}`; + return prepareResult(hexValue, encodingOptions); +} + +/** + * Decodes terms for an ApprovalRevocation caveat from encoded data. + * + * @param terms - The encoded terms as a hex string or Uint8Array (exactly one byte). + * @returns The decoded {@link ApprovalRevocationTerms}. + */ +export function decodeApprovalRevocationTerms( + terms: BytesLike, +): ApprovalRevocationTerms { + const hexTerms = bytesLikeToHex(terms); + assertHexByteExactLength( + hexTerms, + 1, + 'Invalid ApprovalRevocation terms: must be exactly 1 byte', + ); + const mask = extractNumber(hexTerms, 0, 1); + return maskToTerms(mask); +} diff --git a/packages/delegation-core/src/caveats/index.ts b/packages/delegation-core/src/caveats/index.ts index 6c4bc081..0e433a64 100644 --- a/packages/delegation-core/src/caveats/index.ts +++ b/packages/delegation-core/src/caveats/index.ts @@ -77,6 +77,11 @@ export { createAllowedTargetsTerms, decodeAllowedTargetsTerms, } from './allowedTargets'; +export { + createApprovalRevocationTerms, + decodeApprovalRevocationTerms, +} from './approvalRevocationEnforcer'; +export type { ApprovalRevocationTerms } from './approvalRevocationEnforcer'; export { createArgsEqualityCheckTerms, decodeArgsEqualityCheckTerms, diff --git a/packages/delegation-core/src/index.ts b/packages/delegation-core/src/index.ts index ca25ecca..67b76123 100644 --- a/packages/delegation-core/src/index.ts +++ b/packages/delegation-core/src/index.ts @@ -6,6 +6,8 @@ export type { export { BalanceChangeType } from './caveats/types'; +export type { ApprovalRevocationTerms } from './caveats'; + export { createValueLteTerms, decodeValueLteTerms, @@ -51,6 +53,8 @@ export { decodeAllowedMethodsTerms, createAllowedTargetsTerms, decodeAllowedTargetsTerms, + createApprovalRevocationTerms, + decodeApprovalRevocationTerms, createArgsEqualityCheckTerms, decodeArgsEqualityCheckTerms, createBlockNumberTerms, diff --git a/packages/delegation-core/test/caveats/approvalRevocationEnforcer.test.ts b/packages/delegation-core/test/caveats/approvalRevocationEnforcer.test.ts new file mode 100644 index 00000000..20331656 --- /dev/null +++ b/packages/delegation-core/test/caveats/approvalRevocationEnforcer.test.ts @@ -0,0 +1,206 @@ +/* eslint-disable no-bitwise */ +import { describe, it, expect } from 'vitest'; + +import { + createApprovalRevocationTerms, + decodeApprovalRevocationTerms, + type ApprovalRevocationTerms, +} from '../../src/caveats/approvalRevocationEnforcer'; + +const ALL_FALSE = { + erc20Approve: false, + erc721Approve: false, + erc721SetApprovalForAll: false, + permit2ApproveZero: false, + permit2Lockdown: false, + permit2InvalidateNonces: false, +} satisfies ApprovalRevocationTerms; + +/** Every valid on-chain byte 1..63 as decoded terms (spec bit layout). */ +const ALL_VALID_TERM_SETS: ApprovalRevocationTerms[] = Array.from( + { length: 0x3f }, + (_, index) => { + const mask = index + 1; + return { + erc20Approve: (mask & 0x01) !== 0, + erc721Approve: (mask & 0x02) !== 0, + erc721SetApprovalForAll: (mask & 0x04) !== 0, + permit2ApproveZero: (mask & 0x08) !== 0, + permit2Lockdown: (mask & 0x10) !== 0, + permit2InvalidateNonces: (mask & 0x20) !== 0, + }; + }, +); + +const RESERVED_BITS_ERROR = + 'Invalid ApprovalRevocation terms: reserved bits must be zero (only bits 0-5 are defined)'; + +describe('ApprovalRevocationEnforcer', () => { + describe('createApprovalRevocationTerms', () => { + const BYTE_LEN = 1; + + it('encodes only ERC-20 zero-amount approval revocation', () => { + expect( + createApprovalRevocationTerms({ + ...ALL_FALSE, + erc20Approve: true, + }), + ).toBe('0x01'); + }); + + it('encodes ERC-721 clear per-token approval', () => { + expect( + createApprovalRevocationTerms({ + ...ALL_FALSE, + erc721Approve: true, + }), + ).toBe('0x02'); + }); + + it('encodes setApprovalForAll(operator, false)', () => { + expect( + createApprovalRevocationTerms({ + ...ALL_FALSE, + erc721SetApprovalForAll: true, + }), + ).toBe('0x04'); + }); + + it('encodes Permit2 approve(token, spender, 0, 0)', () => { + expect( + createApprovalRevocationTerms({ + ...ALL_FALSE, + permit2ApproveZero: true, + }), + ).toBe('0x08'); + }); + + it('encodes Permit2 lockdown((address,address)[])', () => { + expect( + createApprovalRevocationTerms({ + ...ALL_FALSE, + permit2Lockdown: true, + }), + ).toBe('0x10'); + }); + + it('encodes Permit2 invalidateNonces(token, spender, newNonce)', () => { + expect( + createApprovalRevocationTerms({ + ...ALL_FALSE, + permit2InvalidateNonces: true, + }), + ).toBe('0x20'); + }); + + it('combines all revocation primitive flags', () => { + expect( + createApprovalRevocationTerms({ + erc20Approve: true, + erc721Approve: true, + erc721SetApprovalForAll: true, + permit2ApproveZero: true, + permit2Lockdown: true, + permit2InvalidateNonces: true, + }), + ).toBe('0x3f'); + }); + + it('throws when no flags are set', () => { + expect(() => createApprovalRevocationTerms(ALL_FALSE)).toThrow( + 'at least one revocation primitive must be enabled', + ); + }); + + describe('bytes return type', () => { + it('returns one byte', () => { + const encodedBytes = createApprovalRevocationTerms( + { + erc20Approve: true, + erc721Approve: true, + erc721SetApprovalForAll: false, + permit2ApproveZero: true, + permit2Lockdown: false, + permit2InvalidateNonces: true, + }, + { out: 'bytes' }, + ); + expect(encodedBytes).toBeInstanceOf(Uint8Array); + expect(encodedBytes).toHaveLength(BYTE_LEN); + expect(encodedBytes[0]).toBe(0x2b); + }); + }); + }); + + describe('decodeApprovalRevocationTerms', () => { + it('round-trips with createApprovalRevocationTerms', () => { + const original: ApprovalRevocationTerms = { + erc20Approve: true, + erc721Approve: false, + erc721SetApprovalForAll: true, + permit2ApproveZero: false, + permit2Lockdown: true, + permit2InvalidateNonces: true, + }; + const encoded = createApprovalRevocationTerms(original); + expect(decodeApprovalRevocationTerms(encoded)).toStrictEqual(original); + }); + + it('round-trips every valid combination', () => { + for (const terms of ALL_VALID_TERM_SETS) { + expect( + decodeApprovalRevocationTerms(createApprovalRevocationTerms(terms)), + ).toStrictEqual(terms); + } + }); + + it('accepts Uint8Array', () => { + const bytes = createApprovalRevocationTerms( + { + erc20Approve: false, + erc721Approve: false, + erc721SetApprovalForAll: true, + permit2ApproveZero: true, + permit2Lockdown: false, + permit2InvalidateNonces: true, + }, + { out: 'bytes' }, + ); + expect(decodeApprovalRevocationTerms(bytes)).toStrictEqual({ + erc20Approve: false, + erc721Approve: false, + erc721SetApprovalForAll: true, + permit2ApproveZero: true, + permit2Lockdown: false, + permit2InvalidateNonces: true, + }); + }); + + it('throws when length is not 1 byte', () => { + expect(() => decodeApprovalRevocationTerms('0x')).toThrow( + 'must be exactly 1 byte', + ); + expect(() => decodeApprovalRevocationTerms('0x0102')).toThrow( + 'must be exactly 1 byte', + ); + }); + + it('throws when reserved bits are set on-chain', () => { + expect(() => decodeApprovalRevocationTerms('0x40')).toThrow( + RESERVED_BITS_ERROR, + ); + expect(() => decodeApprovalRevocationTerms('0x80')).toThrow( + RESERVED_BITS_ERROR, + ); + expect(() => decodeApprovalRevocationTerms('0xff')).toThrow( + RESERVED_BITS_ERROR, + ); + }); + + it('throws when no flags are set on-chain', () => { + expect(() => decodeApprovalRevocationTerms('0x00')).toThrow( + 'at least one revocation primitive must be enabled', + ); + }); + }); +}); diff --git a/packages/delegation-core/test/caveats/decoders.test.ts b/packages/delegation-core/test/caveats/decoders.test.ts index 62bf1fd4..d0ce8321 100644 --- a/packages/delegation-core/test/caveats/decoders.test.ts +++ b/packages/delegation-core/test/caveats/decoders.test.ts @@ -21,6 +21,8 @@ import { decodeRedeemerTerms, createAllowedCalldataTerms, decodeAllowedCalldataTerms, + createApprovalRevocationTerms, + decodeApprovalRevocationTerms, createArgsEqualityCheckTerms, decodeArgsEqualityCheckTerms, createExactCalldataTerms, @@ -188,6 +190,22 @@ describe('Terms Decoders', () => { }); }); + describe('decodeApprovalRevocationTerms', () => { + it('correctly decodes encoded terms', () => { + const original = { + erc20Approve: true, + erc721Approve: false, + erc721SetApprovalForAll: true, + permit2ApproveZero: false, + permit2Lockdown: false, + permit2InvalidateNonces: false, + }; + const encoded = createApprovalRevocationTerms(original); + const decoded = decodeApprovalRevocationTerms(encoded); + expect(decoded).toEqual(original); + }); + }); + describe('decodeArgsEqualityCheckTerms', () => { it('correctly decodes encoded terms', () => { const original = { args: '0x1234567890abcdef' as `0x${string}` }; diff --git a/packages/delegation-deployments/CHANGELOG.md b/packages/delegation-deployments/CHANGELOG.md index c1d4f824..b399bb68 100644 --- a/packages/delegation-deployments/CHANGELOG.md +++ b/packages/delegation-deployments/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Add chain deployment for Intuition mainnet and testnet ([#206](https://github.com/MetaMask/smart-accounts-kit/pull/206)) +- Deployment address for `ApprovalRevocationEnforcer` added ([#226](https://github.com/metamask/smart-accounts-kit/pull/226)) ## [1.3.0] diff --git a/packages/delegation-deployments/src/contractAddresses.ts b/packages/delegation-deployments/src/contractAddresses.ts index 30c45c2e..7c1062bf 100644 --- a/packages/delegation-deployments/src/contractAddresses.ts +++ b/packages/delegation-deployments/src/contractAddresses.ts @@ -10,6 +10,7 @@ export const DEPLOYMENTS_1_3_0 = { AllowedCalldataEnforcer: '0xc2b0d624c1c4319760C96503BA27C347F3260f55', AllowedMethodsEnforcer: '0x2c21fD0Cb9DC8445CB3fb0DC5E7Bb0Aca01842B5', AllowedTargetsEnforcer: '0x7F20f61b1f09b08D970938F6fa563634d65c4EeB', + ApprovalRevocationEnforcer: '0xe264F1f09A19505a1ca1a86D5b01E8bFdb64324A', BlockNumberEnforcer: '0x5d9818dF0AE3f66e9c3D0c5029DAF99d1823ca6c', DeployedEnforcer: '0x24ff2AA430D53a8CD6788018E902E098083dcCd2', ERC20BalanceChangeEnforcer: '0xcdF6aB796408598Cea671d79506d7D48E97a5437', diff --git a/packages/delegator-e2e/test/caveats/approvalRevocation.test.ts b/packages/delegator-e2e/test/caveats/approvalRevocation.test.ts new file mode 100644 index 00000000..885248dc --- /dev/null +++ b/packages/delegator-e2e/test/caveats/approvalRevocation.test.ts @@ -0,0 +1,189 @@ +import { beforeEach, expect, test } from 'vitest'; +import { + createExecution, + ExecutionMode, + Implementation, + ROOT_AUTHORITY, + toMetaMaskSmartAccount, +} from '@metamask/smart-accounts-kit'; +import type { + Delegation, + MetaMaskSmartAccount, +} from '@metamask/smart-accounts-kit'; +import { + createCaveatBuilder, + encodeDelegations, + encodeExecutionCalldatas, +} from '@metamask/smart-accounts-kit/utils'; +import { + deployErc20Token, + deploySmartAccount, + fundAddressWithErc20Token, + gasPrice, + publicClient, + randomAddress, + sponsoredBundlerClient, +} from '../utils/helpers'; +import { encodeFunctionData, parseEther, type Address, type Hex } from 'viem'; +import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'; +import { expectUserOperationToSucceed } from '../utils/assertions'; +import * as ERC20Token from '../../contracts/out/ERC20Token.sol/ERC20Token.json'; + +const { abi: erc20TokenAbi } = ERC20Token; + +let aliceSmartAccount: MetaMaskSmartAccount; +let bobSmartAccount: MetaMaskSmartAccount; +let erc20TokenAddress: Hex; + +beforeEach(async () => { + const alice = privateKeyToAccount(generatePrivateKey()); + const bob = privateKeyToAccount(generatePrivateKey()); + + erc20TokenAddress = await deployErc20Token(); + + aliceSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [alice.address, [], [], []], + deploySalt: '0x1', + signer: { account: alice }, + }); + await deploySmartAccount(aliceSmartAccount); + + bobSmartAccount = await toMetaMaskSmartAccount({ + client: publicClient, + implementation: Implementation.Hybrid, + deployParams: [bob.address, [], [], []], + deploySalt: '0x1', + signer: { account: bob }, + }); + + await fundAddressWithErc20Token( + aliceSmartAccount.address, + erc20TokenAddress, + parseEther('10'), + ); +}); + +test('maincase: Bob revokes an existing ERC20 approval', async () => { + const spender = randomAddress(); + const initialAllowance = parseEther('3'); + + await setErc20Approval(aliceSmartAccount, spender, initialAllowance); + + const allowanceBefore = await getErc20Allowance( + aliceSmartAccount.address, + spender, + ); + expect(allowanceBefore).toEqual(initialAllowance); + + const delegation: Delegation = { + delegate: bobSmartAccount.address, + delegator: aliceSmartAccount.address, + authority: ROOT_AUTHORITY, + salt: '0x0', + caveats: createCaveatBuilder(aliceSmartAccount.environment) + .addCaveat('approvalRevocation', { + erc20Approve: true, + erc721Approve: false, + erc721SetApprovalForAll: false, + permit2ApproveZero: false, + permit2Lockdown: false, + permit2InvalidateNonces: false, + }) + .build(), + signature: '0x', + }; + + const signedDelegation = { + ...delegation, + signature: await aliceSmartAccount.signDelegation({ delegation }), + }; + + const execution = createExecution({ + target: erc20TokenAddress, + callData: encodeFunctionData({ + abi: erc20TokenAbi, + functionName: 'approve', + args: [spender, 0n], + }), + }); + + const redeemData = encodeFunctionData({ + abi: bobSmartAccount.abi, + functionName: 'redeemDelegations', + args: [ + [encodeDelegations([signedDelegation])], + [ExecutionMode.SingleDefault], + encodeExecutionCalldatas([[execution]]), + ], + }); + + const userOpHash = await sponsoredBundlerClient.sendUserOperation({ + account: bobSmartAccount, + calls: [ + { + to: bobSmartAccount.address, + data: redeemData, + }, + ], + ...gasPrice, + }); + + const receipt = await sponsoredBundlerClient.waitForUserOperationReceipt({ + hash: userOpHash, + }); + expectUserOperationToSucceed(receipt); + + const allowanceAfter = await getErc20Allowance( + aliceSmartAccount.address, + spender, + ); + expect(allowanceAfter).toEqual(0n); +}); + +const setErc20Approval = async ( + owner: MetaMaskSmartAccount, + spender: Address, + amount: bigint, +) => { + const approveCallData = encodeFunctionData({ + abi: erc20TokenAbi, + functionName: 'approve', + args: [spender, amount], + }); + + const userOpHash = await sponsoredBundlerClient.sendUserOperation({ + account: owner, + calls: [ + { + to: erc20TokenAddress, + data: approveCallData, + }, + ], + ...gasPrice, + }); + + const receipt = await sponsoredBundlerClient.waitForUserOperationReceipt({ + hash: userOpHash, + }); + expectUserOperationToSucceed(receipt); +}; + +const getErc20Allowance = async ( + owner: Address, + spender: Address, +): Promise => { + const result = await publicClient.readContract({ + address: erc20TokenAddress, + abi: erc20TokenAbi, + functionName: 'allowance', + args: [owner, spender], + }); + + if (typeof result !== 'bigint') { + throw new Error('Result is not a bigint'); + } + + return result; +}; diff --git a/packages/delegator-e2e/test/caveats/nativeTokenStreaming.test.ts b/packages/delegator-e2e/test/caveats/nativeTokenStreaming.test.ts index cb75d604..dc3da8da 100644 --- a/packages/delegator-e2e/test/caveats/nativeTokenStreaming.test.ts +++ b/packages/delegator-e2e/test/caveats/nativeTokenStreaming.test.ts @@ -184,9 +184,9 @@ test('Bob redeems the delegation with exact initial amount', async () => { test('Bob attempts to redeem the delegation with amount exceeding initial allowance', async () => { const initialAmount = parseEther('5'); const maxAmount = parseEther('20'); - const amountPerSecond = parseEther('2'); + const amountPerSecond = parseEther('1'); const startTime = currentTime; - const transferAmount = parseEther('6'); + const transferAmount = parseEther('50'); const targetAddress = randomAddress(); diff --git a/packages/smart-accounts-kit/CHANGELOG.md b/packages/smart-accounts-kit/CHANGELOG.md index 926837ed..62e5769e 100644 --- a/packages/smart-accounts-kit/CHANGELOG.md +++ b/packages/smart-accounts-kit/CHANGELOG.md @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- ERC-7715 `token-approval-revocation` permission type ([#226](https://github.com/MetaMask/smart-accounts-kit/pull/226)) +- `CaveatBuilder` for `ApprovalRevocationEnforcer`, deployment address added to `SmartAccountsEnvironment` ([#226](https://github.com/metamask/smart-accounts-kit/pull/226)) + +### Deprecated + +- Deprecated `erc20-token-revocation` in favor of `token-approval-revocation` ([#226](https://github.com/MetaMask/smart-accounts-kit/pull/226)) + ## [1.5.0] ### Added diff --git a/packages/smart-accounts-kit/src/actions/erc7715Mapping.ts b/packages/smart-accounts-kit/src/actions/erc7715Mapping.ts index eb2676bd..d401cec5 100644 --- a/packages/smart-accounts-kit/src/actions/erc7715Mapping.ts +++ b/packages/smart-accounts-kit/src/actions/erc7715Mapping.ts @@ -9,6 +9,7 @@ import type { PermissionRequest, PermissionTypes as RpcPermissionTypes, Rule, + TokenApprovalRevocationPermission as RpcTokenApprovalRevocationPermission, } from '@metamask/7715-permission-types'; import { getAddress, hexToNumber, isAddress, toHex, type Hex } from 'viem'; @@ -27,6 +28,7 @@ import type { PermissionTypes as DeveloperPermissionTypes, RpcGetGrantedExecutionPermissionsResult, RpcGetSupportedExecutionPermissionsResult, + TokenApprovalRevocationPermission, } from './erc7715Types'; // ============================================================================= @@ -156,6 +158,11 @@ function getPermissionRequestToRpcConverter( erc20TokenRevocationPermissionToRpc( permission as Erc20TokenRevocationPermission, ); + case 'token-approval-revocation': + return (permission) => + tokenApprovalRevocationPermissionToRpc( + permission as TokenApprovalRevocationPermission, + ); default: throw new Error(`Unsupported permission type: ${permissionType}`); } @@ -407,6 +414,44 @@ function erc20TokenRevocationPermissionToRpc( }; } +/** + * Convert token approval revocation permission to RPC format. + * + * @param permission the token approval revocation permission + * @returns the token approval revocation permission in RPC format + */ +function tokenApprovalRevocationPermissionToRpc( + permission: TokenApprovalRevocationPermission, +): RpcTokenApprovalRevocationPermission { + const { + data: { + erc20Approve, + erc721Approve, + erc721SetApprovalForAll, + permit2ApproveZero, + permit2Lockdown, + permit2InvalidateNonces, + justification, + }, + isAdjustmentAllowed, + } = permission; + + const data = { + erc20Approve, + erc721Approve, + erc721SetApprovalForAll, + permit2ApproveZero, + permit2Lockdown, + permit2InvalidateNonces, + ...(justification ? { justification } : {}), + }; + return { + type: 'token-approval-revocation', + data, + isAdjustmentAllowed, + }; +} + // ============================================================================= // RPC → Developer friendly types (response conversion) // ============================================================================= diff --git a/packages/smart-accounts-kit/src/actions/erc7715Types.ts b/packages/smart-accounts-kit/src/actions/erc7715Types.ts index e7780a36..c9bab66c 100644 --- a/packages/smart-accounts-kit/src/actions/erc7715Types.ts +++ b/packages/smart-accounts-kit/src/actions/erc7715Types.ts @@ -4,6 +4,7 @@ import type { PermissionResponse as RpcPermissionResponse, Rule, } from '@metamask/7715-permission-types'; +import type { ApprovalRevocationTerms } from '@metamask/delegation-core'; import type { Client, Account, @@ -112,14 +113,29 @@ export type Erc20TokenAllowancePermission = BasePermission & { /** * ERC-20 token revocation permission. + * + * @deprecated Use {@link TokenApprovalRevocationPermission} instead. */ export type Erc20TokenRevocationPermission = BasePermission & { + /** + * @deprecated Use `token-approval-revocation` instead. + */ type: 'erc20-token-revocation'; data: { justification?: string; }; }; +/** + * Token approval revocation permission. + */ +export type TokenApprovalRevocationPermission = BasePermission & { + type: 'token-approval-revocation'; + data: ApprovalRevocationTerms & { + justification?: string; + }; +}; + /** * Permission types. */ @@ -130,7 +146,8 @@ export type PermissionTypes = | Erc20TokenStreamPermission | Erc20TokenPeriodicPermission | Erc20TokenAllowancePermission - | Erc20TokenRevocationPermission; + | Erc20TokenRevocationPermission + | TokenApprovalRevocationPermission; /** * Parameters for a single permission request (input to requestExecutionPermissions). diff --git a/packages/smart-accounts-kit/src/actions/index.ts b/packages/smart-accounts-kit/src/actions/index.ts index a3892ca1..bd1adecf 100644 --- a/packages/smart-accounts-kit/src/actions/index.ts +++ b/packages/smart-accounts-kit/src/actions/index.ts @@ -92,6 +92,7 @@ export { type Erc20TokenPeriodicPermission, type Erc20TokenAllowancePermission, type Erc20TokenRevocationPermission, + type TokenApprovalRevocationPermission, type RpcGetSupportedExecutionPermissionsResult, type RpcGetGrantedExecutionPermissionsResult, type RpcSupportedPermissionInfo, diff --git a/packages/smart-accounts-kit/src/caveatBuilder/approvalRevocationBuilder.ts b/packages/smart-accounts-kit/src/caveatBuilder/approvalRevocationBuilder.ts new file mode 100644 index 00000000..741339e3 --- /dev/null +++ b/packages/smart-accounts-kit/src/caveatBuilder/approvalRevocationBuilder.ts @@ -0,0 +1,38 @@ +import { + createApprovalRevocationTerms, + type ApprovalRevocationTerms, +} from '@metamask/delegation-core'; + +import type { Caveat, SmartAccountsEnvironment } from '../types'; + +export const approvalRevocation = 'approvalRevocation'; + +export type ApprovalRevocationBuilderConfig = ApprovalRevocationTerms; + +/** + * Builds a caveat struct for the ApprovalRevocationEnforcer. + * + * @param environment - The SmartAccountsEnvironment. + * @param config - The configuration object containing enabled revocation primitives. + * @returns The Caveat. + */ +export const approvalRevocationBuilder = ( + environment: SmartAccountsEnvironment, + config: ApprovalRevocationBuilderConfig, +): Caveat => { + const terms = createApprovalRevocationTerms(config); + + const { + caveatEnforcers: { ApprovalRevocationEnforcer }, + } = environment; + + if (!ApprovalRevocationEnforcer) { + throw new Error('ApprovalRevocationEnforcer not found in environment'); + } + + return { + enforcer: ApprovalRevocationEnforcer, + terms, + args: '0x00', + }; +}; diff --git a/packages/smart-accounts-kit/src/caveatBuilder/coreCaveatBuilder.ts b/packages/smart-accounts-kit/src/caveatBuilder/coreCaveatBuilder.ts index 36f05ba9..54939959 100644 --- a/packages/smart-accounts-kit/src/caveatBuilder/coreCaveatBuilder.ts +++ b/packages/smart-accounts-kit/src/caveatBuilder/coreCaveatBuilder.ts @@ -5,6 +5,10 @@ import { } from './allowedCalldataBuilder'; import { allowedMethods, allowedMethodsBuilder } from './allowedMethodsBuilder'; import { allowedTargets, allowedTargetsBuilder } from './allowedTargetsBuilder'; +import { + approvalRevocation, + approvalRevocationBuilder, +} from './approvalRevocationBuilder'; import { argsEqualityCheck, argsEqualityCheckBuilder, @@ -92,6 +96,7 @@ import type { CaveatType } from '../constants'; type CoreCaveatMapByString = { allowedMethods: typeof allowedMethodsBuilder; allowedTargets: typeof allowedTargetsBuilder; + approvalRevocation: typeof approvalRevocationBuilder; deployed: typeof deployedBuilder; allowedCalldata: typeof allowedCalldataBuilder; erc20BalanceChange: typeof erc20BalanceChangeBuilder; @@ -172,6 +177,7 @@ export const createCaveatBuilder = ( const caveatBuilder = new CaveatBuilder(environment, config) .extend(allowedMethods, allowedMethodsBuilder) .extend(allowedTargets, allowedTargetsBuilder) + .extend(approvalRevocation, approvalRevocationBuilder) .extend(deployed, deployedBuilder) .extend(allowedCalldata, allowedCalldataBuilder) .extend(erc20BalanceChange, erc20BalanceChangeBuilder) diff --git a/packages/smart-accounts-kit/src/caveats.ts b/packages/smart-accounts-kit/src/caveats.ts index f74e214b..b2f33779 100644 --- a/packages/smart-accounts-kit/src/caveats.ts +++ b/packages/smart-accounts-kit/src/caveats.ts @@ -3,6 +3,7 @@ import { decodeERC20StreamingTerms, decodeERC20TransferAmountTerms, decodeERC20BalanceChangeTerms, + decodeApprovalRevocationTerms, decodeAllowedMethodsTerms, decodeAllowedTargetsTerms, decodeArgsEqualityCheckTerms, @@ -40,6 +41,7 @@ import { } from 'viem'; import type { CoreCaveatConfiguration } from './caveatBuilder/coreCaveatBuilder'; +import { CaveatType } from './constants'; import type { Caveat, SmartAccountsEnvironment } from './types'; export const CAVEAT_ABI_TYPE_COMPONENTS = [ @@ -105,113 +107,142 @@ export const decodeCaveat = ({ }): CoreCaveatConfiguration => { switch (enforcer.toLowerCase()) { case caveatEnforcers.AllowedCalldataEnforcer?.toLowerCase(): - return { type: 'allowedCalldata', ...decodeAllowedCalldataTerms(terms) }; + return { + type: CaveatType.AllowedCalldata, + ...decodeAllowedCalldataTerms(terms), + }; case caveatEnforcers.AllowedMethodsEnforcer?.toLowerCase(): - return { type: 'allowedMethods', ...decodeAllowedMethodsTerms(terms) }; + return { + type: CaveatType.AllowedMethods, + ...decodeAllowedMethodsTerms(terms), + }; + case caveatEnforcers.ApprovalRevocationEnforcer?.toLowerCase(): + return { + type: CaveatType.ApprovalRevocation, + ...decodeApprovalRevocationTerms(terms), + }; case caveatEnforcers.AllowedTargetsEnforcer?.toLowerCase(): - return { type: 'allowedTargets', ...decodeAllowedTargetsTerms(terms) }; + return { + type: CaveatType.AllowedTargets, + ...decodeAllowedTargetsTerms(terms), + }; case caveatEnforcers.ArgsEqualityCheckEnforcer?.toLowerCase(): return { - type: 'argsEqualityCheck', + type: CaveatType.ArgsEqualityCheck, ...decodeArgsEqualityCheckTerms(terms), }; case caveatEnforcers.BlockNumberEnforcer?.toLowerCase(): - return { type: 'blockNumber', ...decodeBlockNumberTerms(terms) }; + return { type: CaveatType.BlockNumber, ...decodeBlockNumberTerms(terms) }; case caveatEnforcers.DeployedEnforcer?.toLowerCase(): - return { type: 'deployed', ...decodeDeployedTerms(terms) }; + return { type: CaveatType.Deployed, ...decodeDeployedTerms(terms) }; case caveatEnforcers.ERC20BalanceChangeEnforcer?.toLowerCase(): return { - type: 'erc20BalanceChange', + type: CaveatType.Erc20BalanceChange, ...decodeERC20BalanceChangeTerms(terms), }; case caveatEnforcers.ERC20TransferAmountEnforcer?.toLowerCase(): return { - type: 'erc20TransferAmount', + type: CaveatType.Erc20TransferAmount, ...decodeERC20TransferAmountTerms(terms), }; case caveatEnforcers.ERC20StreamingEnforcer?.toLowerCase(): - return { type: 'erc20Streaming', ...decodeERC20StreamingTerms(terms) }; + return { + type: CaveatType.Erc20Streaming, + ...decodeERC20StreamingTerms(terms), + }; case caveatEnforcers.ERC721BalanceChangeEnforcer?.toLowerCase(): return { - type: 'erc721BalanceChange', + type: CaveatType.Erc721BalanceChange, ...decodeERC721BalanceChangeTerms(terms), }; case caveatEnforcers.ERC721TransferEnforcer?.toLowerCase(): - return { type: 'erc721Transfer', ...decodeERC721TransferTerms(terms) }; + return { + type: CaveatType.Erc721Transfer, + ...decodeERC721TransferTerms(terms), + }; case caveatEnforcers.ERC1155BalanceChangeEnforcer?.toLowerCase(): return { - type: 'erc1155BalanceChange', + type: CaveatType.Erc1155BalanceChange, ...decodeERC1155BalanceChangeTerms(terms), }; case caveatEnforcers.IdEnforcer?.toLowerCase(): - return { type: 'id', ...decodeIdTerms(terms) }; + return { type: CaveatType.Id, ...decodeIdTerms(terms) }; case caveatEnforcers.LimitedCallsEnforcer?.toLowerCase(): - return { type: 'limitedCalls', ...decodeLimitedCallsTerms(terms) }; + return { + type: CaveatType.LimitedCalls, + ...decodeLimitedCallsTerms(terms), + }; case caveatEnforcers.NonceEnforcer?.toLowerCase(): - return { type: 'nonce', ...decodeNonceTerms(terms) }; + return { type: CaveatType.Nonce, ...decodeNonceTerms(terms) }; case caveatEnforcers.TimestampEnforcer?.toLowerCase(): - return { type: 'timestamp', ...decodeTimestampTerms(terms) }; + return { type: CaveatType.Timestamp, ...decodeTimestampTerms(terms) }; case caveatEnforcers.ValueLteEnforcer?.toLowerCase(): - return { type: 'valueLte', ...decodeValueLteTerms(terms) }; + return { type: CaveatType.ValueLte, ...decodeValueLteTerms(terms) }; case caveatEnforcers.NativeTokenTransferAmountEnforcer?.toLowerCase(): return { - type: 'nativeTokenTransferAmount', + type: CaveatType.NativeTokenTransferAmount, ...decodeNativeTokenTransferAmountTerms(terms), }; case caveatEnforcers.NativeBalanceChangeEnforcer?.toLowerCase(): return { - type: 'nativeBalanceChange', + type: CaveatType.NativeBalanceChange, ...decodeNativeBalanceChangeTerms(terms), }; case caveatEnforcers.NativeTokenStreamingEnforcer?.toLowerCase(): return { - type: 'nativeTokenStreaming', + type: CaveatType.NativeTokenStreaming, ...decodeNativeTokenStreamingTerms(terms), }; case caveatEnforcers.NativeTokenPaymentEnforcer?.toLowerCase(): return { - type: 'nativeTokenPayment', + type: CaveatType.NativeTokenPayment, ...decodeNativeTokenPaymentTerms(terms), }; case caveatEnforcers.RedeemerEnforcer?.toLowerCase(): - return { type: 'redeemer', ...decodeRedeemerTerms(terms) }; + return { type: CaveatType.Redeemer, ...decodeRedeemerTerms(terms) }; case caveatEnforcers.SpecificActionERC20TransferBatchEnforcer?.toLowerCase(): return { - type: 'specificActionERC20TransferBatch', + type: CaveatType.SpecificActionERC20TransferBatch, ...decodeSpecificActionERC20TransferBatchTerms(terms), }; case caveatEnforcers.ERC20PeriodTransferEnforcer?.toLowerCase(): return { - type: 'erc20PeriodTransfer', + type: CaveatType.Erc20PeriodTransfer, ...decodeERC20TokenPeriodTransferTerms(terms), }; case caveatEnforcers.NativeTokenPeriodTransferEnforcer?.toLowerCase(): return { - type: 'nativeTokenPeriodTransfer', + type: CaveatType.NativeTokenPeriodTransfer, ...decodeNativeTokenPeriodTransferTerms(terms), }; case caveatEnforcers.ExactCalldataBatchEnforcer?.toLowerCase(): return { - type: 'exactCalldataBatch', + type: CaveatType.ExactCalldataBatch, ...decodeExactCalldataBatchTerms(terms), }; case caveatEnforcers.ExactCalldataEnforcer?.toLowerCase(): - return { type: 'exactCalldata', ...decodeExactCalldataTerms(terms) }; + return { + type: CaveatType.ExactCalldata, + ...decodeExactCalldataTerms(terms), + }; case caveatEnforcers.ExactExecutionEnforcer?.toLowerCase(): - return { type: 'exactExecution', ...decodeExactExecutionTerms(terms) }; + return { + type: CaveatType.ExactExecution, + ...decodeExactExecutionTerms(terms), + }; case caveatEnforcers.ExactExecutionBatchEnforcer?.toLowerCase(): return { - type: 'exactExecutionBatch', + type: CaveatType.ExactExecutionBatch, ...decodeExactExecutionBatchTerms(terms), }; case caveatEnforcers.MultiTokenPeriodEnforcer?.toLowerCase(): return { - type: 'multiTokenPeriod', + type: CaveatType.MultiTokenPeriod, ...decodeMultiTokenPeriodTerms(terms), }; case caveatEnforcers.OwnershipTransferEnforcer?.toLowerCase(): return { - type: 'ownershipTransfer', + type: CaveatType.OwnershipTransfer, ...decodeOwnershipTransferTerms(terms), }; default: diff --git a/packages/smart-accounts-kit/src/constants.ts b/packages/smart-accounts-kit/src/constants.ts index 43a66e8c..3d320f5f 100644 --- a/packages/smart-accounts-kit/src/constants.ts +++ b/packages/smart-accounts-kit/src/constants.ts @@ -62,6 +62,7 @@ export enum ScopeType { export enum CaveatType { AllowedMethods = 'allowedMethods', AllowedTargets = 'allowedTargets', + ApprovalRevocation = 'approvalRevocation', Deployed = 'deployed', AllowedCalldata = 'allowedCalldata', Erc20BalanceChange = 'erc20BalanceChange', diff --git a/packages/smart-accounts-kit/src/smartAccountsEnvironment.ts b/packages/smart-accounts-kit/src/smartAccountsEnvironment.ts index 2dfd8601..28e32ad6 100644 --- a/packages/smart-accounts-kit/src/smartAccountsEnvironment.ts +++ b/packages/smart-accounts-kit/src/smartAccountsEnvironment.ts @@ -8,6 +8,7 @@ import { SCL_RIP7212, AllowedTargetsEnforcer, AllowedMethodsEnforcer, + ApprovalRevocationEnforcer, DeployedEnforcer, TimestampEnforcer, NonceEnforcer, @@ -48,6 +49,7 @@ import { SCL_RIP7212 as SCLRIP7212Bytecode, AllowedTargetsEnforcer as AllowedTargetsEnforcerBytecode, AllowedMethodsEnforcer as AllowedMethodsEnforcerBytecode, + ApprovalRevocationEnforcer as ApprovalRevocationEnforcerBytecode, DeployedEnforcer as DeployedEnforcerBytecode, TimestampEnforcer as TimestampEnforcerBytecode, NonceEnforcer as NonceEnforcerBytecode, @@ -159,6 +161,7 @@ export function getSmartAccountsEnvironmentV1(contracts: { AllowedCalldataEnforcer: contracts.AllowedCalldataEnforcer, AllowedMethodsEnforcer: contracts.AllowedMethodsEnforcer, AllowedTargetsEnforcer: contracts.AllowedTargetsEnforcer, + ApprovalRevocationEnforcer: contracts.ApprovalRevocationEnforcer, ArgsEqualityCheckEnforcer: contracts.ArgsEqualityCheckEnforcer, BlockNumberEnforcer: contracts.BlockNumberEnforcer, DeployedEnforcer: contracts.DeployedEnforcer, @@ -261,6 +264,10 @@ export async function deploySmartAccountsEnvironment( abi: AllowedMethodsEnforcer, bytecode: AllowedMethodsEnforcerBytecode, }, + ApprovalRevocationEnforcer: { + abi: ApprovalRevocationEnforcer, + bytecode: ApprovalRevocationEnforcerBytecode, + }, ArgsEqualityCheckEnforcer: { abi: ArgsEqualityCheckEnforcer, bytecode: ArgsEqualityCheckEnforcerBytecode, diff --git a/packages/smart-accounts-kit/test/actions/erc7715Mapping.test.ts b/packages/smart-accounts-kit/test/actions/erc7715Mapping.test.ts index 5ccf6bcb..982c1a37 100644 --- a/packages/smart-accounts-kit/test/actions/erc7715Mapping.test.ts +++ b/packages/smart-accounts-kit/test/actions/erc7715Mapping.test.ts @@ -190,6 +190,38 @@ describe('erc7715Mapping', () => { }, }); }); + + it('preserves token-approval-revocation data, including false flags', () => { + const rpcPermission = { + type: 'token-approval-revocation', + isAdjustmentAllowed: true, + data: { + erc20Approve: true, + erc721Approve: false, + erc721SetApprovalForAll: true, + permit2ApproveZero: false, + permit2Lockdown: true, + permit2InvalidateNonces: false, + justification: 'Revoke token approvals', + }, + } as const; + + const result = permissionTypeFromRpc(rpcPermission); + + expect(result).toStrictEqual({ + type: 'token-approval-revocation', + isAdjustmentAllowed: true, + data: { + erc20Approve: true, + erc721Approve: false, + erc721SetApprovalForAll: true, + permit2ApproveZero: false, + permit2Lockdown: true, + permit2InvalidateNonces: false, + justification: 'Revoke token approvals', + }, + }); + }); }); describe('permissionResponsesFromRpc', () => { @@ -772,6 +804,47 @@ describe('erc7715Mapping', () => { }); }); + it('converts token-approval-revocation: preserves all flags', () => { + const permissionRequest = { + chainId: 1, + permission: { + type: 'token-approval-revocation', + data: { + erc20Approve: true, + erc721Approve: false, + erc721SetApprovalForAll: true, + permit2ApproveZero: false, + permit2Lockdown: true, + permit2InvalidateNonces: false, + justification: 'Revoke token approvals', + }, + isAdjustmentAllowed: true, + }, + to: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcd', + } as const; + + const result = permissionRequestToRpc(permissionRequest); + + expect(result).toStrictEqual({ + chainId: '0x1', + permission: { + type: 'token-approval-revocation', + data: { + erc20Approve: true, + erc721Approve: false, + erc721SetApprovalForAll: true, + permit2ApproveZero: false, + permit2Lockdown: true, + permit2InvalidateNonces: false, + justification: 'Revoke token approvals', + }, + isAdjustmentAllowed: true, + }, + to: '0xabcdefabcdefabcdefabcdefabcdefabcdefabcd', + rules: [], + }); + }); + it('throws for unsupported permission type', () => { const permissionRequest = { chainId: 1, diff --git a/packages/smart-accounts-kit/test/caveatBuilder/approvalRevocationBuilder.test.ts b/packages/smart-accounts-kit/test/caveatBuilder/approvalRevocationBuilder.test.ts new file mode 100644 index 00000000..03fcd743 --- /dev/null +++ b/packages/smart-accounts-kit/test/caveatBuilder/approvalRevocationBuilder.test.ts @@ -0,0 +1,57 @@ +import { createApprovalRevocationTerms } from '@metamask/delegation-core'; +import { expect, describe, it } from 'vitest'; + +import { approvalRevocationBuilder } from '../../src/caveatBuilder/approvalRevocationBuilder'; +import type { SmartAccountsEnvironment } from '../../src/types'; +import { randomAddress } from '../utils'; + +describe('approvalRevocationBuilder()', () => { + const environment = { + caveatEnforcers: { ApprovalRevocationEnforcer: randomAddress() }, + } as any as SmartAccountsEnvironment; + + const buildWithConfig = () => { + return approvalRevocationBuilder(environment, { + erc20Approve: true, + erc721Approve: false, + erc721SetApprovalForAll: true, + permit2ApproveZero: false, + permit2Lockdown: true, + permit2InvalidateNonces: false, + }); + }; + + it('should build a caveat with valid terms', () => { + const caveat = buildWithConfig(); + const terms = createApprovalRevocationTerms({ + erc20Approve: true, + erc721Approve: false, + erc721SetApprovalForAll: true, + permit2ApproveZero: false, + permit2Lockdown: true, + permit2InvalidateNonces: false, + }); + + expect(caveat).to.deep.equal({ + enforcer: environment.caveatEnforcers.ApprovalRevocationEnforcer, + terms, + args: '0x00', + }); + }); + + it('should throw when enforcer is missing in environment', () => { + expect(() => + approvalRevocationBuilder( + { caveatEnforcers: {} } as any as SmartAccountsEnvironment, + { + erc20Approve: true, + erc721Approve: false, + erc721SetApprovalForAll: false, + permit2ApproveZero: false, + permit2Lockdown: false, + permit2InvalidateNonces: false, + }, + ), + ).to.throw('ApprovalRevocationEnforcer not found in environment'); + }); +}); diff --git a/packages/smart-accounts-kit/test/caveatBuilder/createCaveatBuilder.test.ts b/packages/smart-accounts-kit/test/caveatBuilder/createCaveatBuilder.test.ts index b454b52f..7bec937d 100644 --- a/packages/smart-accounts-kit/test/caveatBuilder/createCaveatBuilder.test.ts +++ b/packages/smart-accounts-kit/test/caveatBuilder/createCaveatBuilder.test.ts @@ -1,4 +1,7 @@ -import { BalanceChangeType } from '@metamask/delegation-core'; +import { + BalanceChangeType, + createApprovalRevocationTerms, +} from '@metamask/delegation-core'; import { concat, encodePacked, isAddress, pad, toHex } from 'viem'; import type { Address } from 'viem/accounts'; import { expect, describe, it } from 'vitest'; @@ -13,6 +16,7 @@ describe('createCaveatBuilder()', () => { caveatEnforcers: { AllowedMethodsEnforcer: randomBytes(20), AllowedTargetsEnforcer: randomBytes(20), + ApprovalRevocationEnforcer: randomBytes(20), DeployedEnforcer: randomBytes(20), AllowedCalldataEnforcer: randomBytes(20), ERC20BalanceChangeEnforcer: randomBytes(20), @@ -103,6 +107,37 @@ describe('createCaveatBuilder()', () => { expect(isAddress(caveat.enforcer)).to.equal(true); }); + it("should add an 'approvalRevocation' caveat", () => { + const builder = createCaveatBuilder(environment); + const revocationConfig = { + erc20Approve: true, + erc721Approve: false, + erc721SetApprovalForAll: true, + permit2ApproveZero: false, + permit2Lockdown: true, + permit2InvalidateNonces: false, + }; + + const caveats = builder + .addCaveat('approvalRevocation', revocationConfig) + .build(); + + expect(caveats).to.deep.equal([ + { + enforcer: environment.caveatEnforcers.ApprovalRevocationEnforcer, + terms: createApprovalRevocationTerms(revocationConfig), + args: '0x00', + }, + ]); + + const caveat = caveats[0]; + if (!caveat) { + throw new Error('caveat is not set'); + } + + expect(isAddress(caveat.enforcer)).to.equal(true); + }); + it("should add a 'deployed' caveat", () => { const builder = createCaveatBuilder(environment); @@ -538,6 +573,36 @@ describe('createCaveatBuilder()', () => { expect(isAddress(caveat.enforcer)).to.equal(true); }); + it('should add approvalRevocation caveat using CaveatType enum', () => { + const builder = createCaveatBuilder(environment); + const revocationConfig = { + erc20Approve: true, + erc721Approve: false, + erc721SetApprovalForAll: true, + permit2ApproveZero: false, + permit2Lockdown: true, + permit2InvalidateNonces: false, + }; + + const caveats = builder + .addCaveat(CaveatType.ApprovalRevocation, revocationConfig) + .build(); + + expect(caveats).to.deep.equal([ + { + enforcer: environment.caveatEnforcers.ApprovalRevocationEnforcer, + terms: createApprovalRevocationTerms(revocationConfig), + args: '0x00', + }, + ]); + + const caveat = caveats[0]; + if (!caveat) { + throw new Error('caveat is not set'); + } + expect(isAddress(caveat.enforcer)).to.equal(true); + }); + it('should add deployed caveat using CaveatType enum', () => { const builder = createCaveatBuilder(environment); const contractAddress = randomAddress(); diff --git a/packages/smart-accounts-kit/test/caveats.test.ts b/packages/smart-accounts-kit/test/caveats.test.ts index 15dad6dd..04d238ae 100644 --- a/packages/smart-accounts-kit/test/caveats.test.ts +++ b/packages/smart-accounts-kit/test/caveats.test.ts @@ -6,6 +6,7 @@ import type { SmartAccountsEnvironment } from '../src/types'; const delegationCoreMocks = vi.hoisted(() => ({ decodeAllowedCalldataTerms: vi.fn(() => ({})), + decodeApprovalRevocationTerms: vi.fn(() => ({})), decodeAllowedMethodsTerms: vi.fn(() => ({})), decodeAllowedTargetsTerms: vi.fn(() => ({})), decodeArgsEqualityCheckTerms: vi.fn(() => ({})), @@ -54,6 +55,11 @@ const DECODE_CASES: { decoder: 'decodeAllowedCalldataTerms', type: 'allowedCalldata', }, + { + enforcerKey: 'ApprovalRevocationEnforcer', + decoder: 'decodeApprovalRevocationTerms', + type: 'approvalRevocation', + }, { enforcerKey: 'AllowedMethodsEnforcer', decoder: 'decodeAllowedMethodsTerms',