# Self Audit: [BIP-XXX] Permissions Preset Update Request #2 (Revised version of BIP-442) This is an attempt into improved transparency and efficiency for preset update requests. It aims to make payloads readable and summarize the most relevant information for stakeholders to quickly be able to understand proposed changes. Methodology was derived from [Due Dilligence: BIP-442 Karpatkey Payload](https://forum.balancer.fi/t/bip-442-permissions-preset-update-request-2/5209/3#due-dilligence-bip-442-karpatkey-payload-1) post by [@gosuto](https://forum.balancer.fi/u/gosuto). This served as a great starting point and model for this post. You can find the payload in the proposal: [[BIP-XXX] Permissions Preset Update Request #2 (Revised version of BIP-442)](https://forum.balancer.fi/t/bip-xxx-permissions-preset-update-request-2-revised-version-of-bip-442/5281) Transactions included in the payload consist of one of the folowing two categories: - ERC20 token approvals: approvals are necessary for interacting with relevant smart contracts for treasury strategy - Scope changes applied to the [Roles](https://etherscan.io/address/0xd8dd9164e765bef903e429c9462e51f0ea8514f9) contract, which handles permissions given to karpatkey to execute on the [Avatar Safe](https://app.safe.global/home?safe=eth:0x0EFcCBb9E2C09Ea29551879bd9Da32362b32fc89) All function calls generate events that contain the data needed to understand the actions being executed. So the result of the succesfull execution can be understood by simulating the payload execution and analysing emitted events. This simulation was done using Tenderly. This is the [Tenderly Simulation Link](https://dashboard.tenderly.co/public/safe/safe-apps/simulator/57c873f8-5ddf-4a75-b68b-5891d9788fd1). There are a total of 61 transactions included in the preset update. Each of this actions has an id which links the description to the corresponding position in the payload, for treaceability. The 61 actions consist of: - 16 approvals - 14 scopeTarget - 6 scopeAllowFunction - 16 scopeFunction - 9 scopeParameterAsOneOf ## Approval ERC20 token approvals are needed to interact with the contracts of interest. Events emitted have the following structure (and they get emitted by the token contract that's being approved for spending): ``` event Approval(address owner, address spender, uint256 value) ``` In all cases the owner is the [Avatar Safe](https://etherscan.io/address/0x0EFcCBb9E2C09Ea29551879bd9Da32362b32fc89) and the value is infinite. | id | token | spender | |-----|---------------------------------------------------------------|--------------------------------------------------------------------| | 1 | 0xae7ab96520de3a18e5e111b5eaab095312d7fe84 (stETH) | 0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0 (wstETH) | | 2 | 0xae7ab96520de3a18e5e111b5eaab095312d7fe84 (stETH) | 0x889edc2edab5f40e902b864ad4d7ade8e412f9b1 (lido:unstETH) | | 3 | 0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0 (wstETH) | 0x889edc2edab5f40e902b864ad4d7ade8e412f9b1 (lido:unstETH) | | 4 | 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 (USDC) | 0xc3d688b66703497daa19211eedff47f25384cdc3 (compound_v3:cUSDCv3) | | 5 | 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 (USDC) | 0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2 (aave_v3:POOL_V3) | | 6 | 0x6b175474e89094c44da98b954eedeac495271d0f (DAI) | 0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2 (aave_v3:POOL_V3) | | 7 | 0xae78736cd615f374d3085123a210448e74fc6393 (rETH) | 0x16d5a408e807db8ef7c578279beeee6b228f1c1c (rocket_pool:SWAP_ROUTER) | | 8 | 0x6b175474e89094c44da98b954eedeac495271d0f (DAI) | 0x373238337bfe1146fb49989fc222523f83081ddb (maker:DSR_MANAGER) | | 9 | 0xc00e94cb662c3520282e6f5717214004a7f26888 (COMP) | 0x5550d13389bb70f45fcef58f19f6b6e87f6e747d (sushiswap:ROUTE_PROCESSOR_3_2) | | 10 | 0x6b175474e89094c44da98b954eedeac495271d0f (DAI) | 0x5550d13389bb70f45fcef58f19f6b6e87f6e747d (sushiswap:ROUTE_PROCESSOR_3_2) | | 11 | 0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48 (USDC) | 0x5550d13389bb70f45fcef58f19f6b6e87f6e747d (sushiswap:ROUTE_PROCESSOR_3_2) | | 12 | 0xdac17f958d2ee523a2206206994597c13d831ec7 (USDT) | 0x5550d13389bb70f45fcef58f19f6b6e87f6e747d (sushiswap:ROUTE_PROCESSOR_3_2) | | 13 | 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2 (WETH) | 0x5550d13389bb70f45fcef58f19f6b6e87f6e747d (sushiswap:ROUTE_PROCESSOR_3_2) | | 14 | 0xae78736cd615f374d3085123a210448e74fc6393 (rETH) | 0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45 (uniswapv3:ROUTER_2) | | 15 | 0xae78736cd615f374d3085123a210448e74fc6393 (rETH) | 0xba12222222228d8ba445958a75a0704d566bf2c8 (balancer:VAULT) | | 16 | 0xae7ab96520de3a18e5e111b5eaab095312d7fe84 (stETH) | 0xdc24316b9ae028f1497c275eb9192a3ea0f67022 (curve:stETH_ETH_POOL) | ## Scope Changes Besides the (more readable) approvals, this payload makes changes to the `Roles` modifier contract, which is currently enabled on the managed safe. Permissions of this type can be seen as well through the events emitted, each having its particular structure. This changes are detailed in the next sections, grouped by the action they perform. ### scopeTarget To interact with contracts they have to be scoped first (the contract address). `scopeTarget` function scopes calls to an address, limited to specific function signatures, and per function scoping rules. `ScopeTarget` events have the following structure: ``` event ScopeTarget( uint16 role, address targetAddress ) ``` - `role` here is the `roleId` used to identify the role being modified. Current configuration has `roleId = 1` In all cases, `scopeTarget` is called in the `Roles` contract with `role = 1`. | id | target | |-----|--------------------------------------------------------------| | 17 | 0x889edc2edab5f40e902b864ad4d7ade8e412f9b1 (lido:unstETH) | | 21 | 0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9 (AAVE) | | 23 | 0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2 (aave_v3:POOL_V3) | | 28 | 0xc3d688b66703497daa19211eedff47f25384cdc3 (compound_v3:cUSDCv3) | | 31 | 0x1b0e765f6224c21223aea2af16c1c46e38885a40 (compound_v3:CometRewards) | | 33 | 0x373238337bfe1146fb49989fc222523f83081ddb (maker:DSR_MANAGER) | | 37 | 0xdd3f50f8a6cafbe9b31a427582963f465e745af8 (rocket_pool:DEPOSIT_POOL) | | 39 | 0xae78736cd615f374d3085123a210448e74fc6393 (rETH) | | 41 | 0x16d5a408e807db8ef7c578279beeee6b228f1c1c (rocket_pool:SWAP_ROUTER) | | 44 | 0x5550d13389bb70f45fcef58f19f6b6e87f6e747d (sushiswap:ROUTE_PROCESSOR_3_2) | | 48 | 0xdc24316b9ae028f1497c275eb9192a3ea0f67022 (curve:stETH_ETH_POOL) | | 50 | 0x4da27a545c0c5b758a6ba100e3a049001de870f5 (aave_v2:stkAAVE) | | 53 | 0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45 (uniswapv3:ROUTER_2) | | 57 | 0xba12222222228d8ba445958a75a0704d566bf2c8 (balancer:VAULT) | ### scopeAllowFunction In order to call functions in scoped targets (contracts), functions have to be scoped. `scopeAllowFunction` function allows a specific function signature on a scoped target (enables to call function from already scoped contract address in `scopeTarget` without any constraint). `ScopeAllowFunction` events have the following structure: ``` event ScopeAllowFunction( uint16 role, address targetAddress, bytes4 selector, ExecutionOptions options, uint256 resultingScopeConfig ) ``` - `role` here is the `roleId` used to identify the role being modified - `selector` here is the function signature being scoped - `options` here is the index of the `ExecutionOptions` enum, it can give permission to send ether, use delegate_call or both ``` enum ExecutionOptions { None, Send, DelegateCall, Both } ``` In all cases, `scopeAllowFunction` is called in the `Roles` contract with `role = 1`. | id | targetAddress | selector | options | |----|-----------------------------------------------------------------------|----------------------------------------------------------------------------------------|----------| | 20 | 0x889edc2edab5f40e902b864ad4d7ade8e412f9b1 (lido:unstETH) | 0xe3afe0a3 (claimWithdrawals(uint256[],uint256[])) | 0 (None) | | 38 | 0xdd3f50f8a6cafbe9b31a427582963f465e745af8 (rocket_pool:DEPOSIT_POOL) | 0xd0e30db0 (deposit()) | 1 (Send) | | 40 | 0xae78736cd615f374d3085123a210448e74fc6393 (rETH) | 0x42966c68 (burn(uint256)) | 0 (None) | | 42 | 0x16d5a408e807db8ef7c578279beeee6b228f1c1c (rocket_pool:SWAP_ROUTER) | 0x55362f4d (swapTo(uint256,uint256,uint256,uint256)) | 1 (Send) | | 43 | 0x16d5a408e807db8ef7c578279beeee6b228f1c1c (rocket_pool:SWAP_ROUTER) | 0xa824ae8b (swapFrom(uint256,uint256,uint256,uint256,uint256)) | 0 (None) | | 49 | 0xdc24316b9ae028f1497c275eb9192a3ea0f67022 (curve:stETH_ETH_POOL) | 0x3df02124 (exchange(int128,int128,uint256,uint256)) | 1 (Send) | ### scopeFunction Works the same way as scopeAllowFunction, but allows for constraining of call paramters only to single values. `scopeFunction` function sets scoping rules for a function, on a scoped address. `ScopeFunction` events have the following structure: ``` event ScopeFunction( uint16 role, address targetAddress, bytes4 functionSig, bool[] isParamScoped, ParameterType[] paramType, Comparison[] paramComp, bytes[] compValue, ExecutionOptions options, uint256 resultingScopeConfig ) ``` - `role` here is the `roleId` used to identify the role being modified - `functionSig` here is the function signature being scoped - `isParamScoped` is a bool array with value 0 for non-scoped (non-constrained) and 1 for scoped parameters (constrained) - `paramType` here is the index of the `ParameterType` enum, it determines the type of parameter being passed for constraining the interaction - `paramComp` - `compValue` values the parameter can take - `options` here is the index of the `ExecutionOptions` enum, it can give permission to send ether, use delegate_call or both ``` enum ParameterType { Static, Dynamic, Dynamic32 } ``` In all cases, `scopeFunction` is called in the Roles contract with `role = 1`. | id | targetAddress | functionSig | isParamScoped | compValue | options | |----|------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------|---------------------------------------------------------------------------------------------------------|----------| | 18 | 0x889edc2edab5f40e902b864ad4d7ade8e412f9b1 (lido:unstETH) | 0xd6681042 (requestWithdrawals(uint256[] _amounts, address _owner)) | [False, True] | "(ANY, Avatar)" | 0 (None) | | 19 | 0x889edc2edab5f40e902b864ad4d7ade8e412f9b1 (lido:unstETH) | 0x19aa6257 (requestWithdrawalsWstETH(uint256[] _amounts, address _owner)) | [False, True] | "(ANY, Avatar)" | 0 (None) | | 22 | 0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9 (AAVE) | 0x5c19a95c (delegate(address delegatee)) | [True] | (karpatkey Gov Safe) | 0 (None) | | 24 | 0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2 (aave_v3:POOL_V3) | 0x617ba037 (supply(address asset, uint256 amount, address onBehalfOf, unit16 referralCode)) | [False, False, True] | "(ANY, ANY, Avatar)" | 0 (None) | | 26 | 0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2 (aave_v3:POOL_V3) | 0x69328dec (withdraw(address asset, uint256 amount, address onBehalfOf)) | [False, False, True] | "(ANY, ANY, Avatar)" | 0 (None) | | 29 | 0xc3d688b66703497daa19211eedff47f25384cdc3 (compound_v3:cUSDCv3) | 0xf2b9fdb8 (supply(address asset, uint256 amount)) | [True] | (USDC) | 0 (None) | | 30 | 0xc3d688b66703497daa19211eedff47f25384cdc3 (compound_v3:cUSDCv3) | 0xf3fef3a3 (withdraw(address asset, uint256 amount)) | [True] | (USDC) | 0 (None) | | 32 | 0x1b0e765f6224c21223aea2af16c1c46e38885a40 (compound_v3:CometRewards) | 0xb7034f7e (claim(address comet, address src, bool shouldAccrue)) | [True, True] | "(cUSDCv3, Avatar)" | 0 (None) | | 34 | 0x373238337bfe1146fb49989fc222523f83081ddb (maker:DSR_MANAGER) | 0x3b4da69f (join(address dst, uint256 wad)) | [True] | (Avatar) | 0 (None) | | 35 | 0x373238337bfe1146fb49989fc222523f83081ddb (maker:DSR_MANAGER) | 0xef693bed (exit(address dst, uint256 wad)) | [True] | (Avatar) | 0 (None) | | 36 | 0x373238337bfe1146fb49989fc222523f83081ddb (maker:DSR_MANAGER) | 0xeb0dff66 (exitAll(address dst)) | [True] | (Avatar) | 0 (None) | | 45 | 0x5550d13389bb70f45fcef58f19f6b6e87f6e747d (sushiswap:ROUTE_PROCESSOR_3_2) | 0x2646478b (processRoute(address tokenIn, uint256 amountIn, address tokenOut,uint256 amountOutMin, address to, bytes route)) | [False, False, False, False, True] | "(ANY, ANY, ANY, ANY, Avatar)" | 0 (None) | | 51 | 0x4da27a545c0c5b758a6ba100e3a049001de870f5 (aave_v2:stkAAVE) | 0x955e18af (claimRewardsAndStake(address to,uint256 amount)) | [True] | (Avatar) | 0 (None) | | 52 | 0x4da27a545c0c5b758a6ba100e3a049001de870f5 (aave_v2:stkAAVE) | 0x5c19a95c (delegate(address delegatee)) | [True] | (karpatkey Gov Safe) | 0 (None) | | 54 | 0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45 (uniswapv3:ROUTER_2) | 0x04e45aaf (exactInputSingle((address tokenIn, address tokenOut, uint24 fee, address recipient, uint256 amountIn, uint256 amountOutMinimum, uint160 sqrtPriceLimitX96))) | [False, False, False, True] |(ANY, ANY, ANY, Avatar) | 0 (None) | | 58 | 0xba12222222228d8ba445958a75a0704d566bf2c8 (balancer:VAULT) | 0x52bbbe29 (swap((bytes32 poolId, uint8 kind, address assetIn, address assetOut, uint256 amount, bytes userData),(address sender, bool fromInternalBalance, address recipient, bool toInternalBalance), uint256 limit, uint256 deadline)) | [True, True, True, True, True, False, False, False, True, False, False, False, True, True] | (#N/A Avatar, ZERO_ADDRESS, Avatar, ZERO_ADDRESS, NULL, NULL, NULL, ZERO_ADDRESS, NULL, NULL, NULL, #N/A ZERO_ADDRESS,) | 0 (None) | ### scopeParameterAsOneOf `scopeParameterAsOneOf` function sets and enforces scoping rules, for a single parameter of a scoped function, on a scoped target (constraining the argument to be an element belonging in a given set). Parameter will be scoped with comparison type OneOf. Permissions of this type can be seen as well through the events emitted, which have the following structure: ``` event ScopeParameterAsOneOf( uint16 role, address targetAddress, bytes4 functionSig, uint256 index, ParameterType paramType, bytes[] compValues, uint256 resultingScopeConfig ) ``` - `role` here is the `roleId` used to identify the role being modified - `functionSig` here is the function signature being scoped - `index` here is the parameter index in the function - `paramType` here is the index of the `ParameterType` enum - `compValues` array containing the values the parameter can take - `options` here is the index of the `ExecutionOptions` enum, it can give permission to send ether, use delegate_call or both In all cases, scopeFunction is called in the `Roles` contract with `role = 1`. | id_ | targetAddress | functionSig | index | compValues | |-----|--------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------|------------------------------------------------------------------------------------------------------------------------------------------------------------------| | 25 | 0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2 (aave_v3:POOL_V3) | 0x617ba037 (supply(address asset, uint256 amount, address onBehalfOf, unit16 referralCode)) | (0) | "(DAI, USDC)" | | 27 | 0x87870bca3f3fd6335c3f4ce8392d69350b4fa4e2 (aave_v3:POOL_V3) | 0x69328dec (withdraw(address asset, uint256 amount, address onBehalfOf)) | (0) | "(DAI, USDC)" | | 46 | 0x5550d13389bb70f45fcef58f19f6b6e87f6e747d (sushiswap:ROUTE_PROCESSOR_3_2) | 0x2646478b (processRoute(address tokenIn, uint256 amountIn, address tokenOut,uint256 amountOutMin, address to, bytes route)) | (0) | "(COMP, DAI, USDC, USDT, WETH)" | | 47 | 0x5550d13389bb70f45fcef58f19f6b6e87f6e747d (sushiswap:ROUTE_PROCESSOR_3_2) | 0x2646478b (processRoute(address tokenIn, uint256 amountIn, address tokenOut,uint256 amountOutMin, address to, bytes route)) | (2) | "(DAI, USDC, USDT, WETH)" | | 55 | 0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45 (uniswapv3:ROUTER_2) | 0x04e45aaf (exactInputSingle((address tokenIn, address tokenOut, uint24 fee, address recipient, uint256 amountIn, uint256 amountOutMinimum, uint160 sqrtPriceLimitX96))) | (0) | "(AAVE, COMP, DAI, rETH, rETH2, sETH2, SWISE, USDC, USDT, WBTC, WETH)" | | 56 | 0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45 (uniswapv3:ROUTER_2) | 0x04e45aaf (exactInputSingle((address tokenIn, address tokenOut, uint24 fee, address recipient, uint256 amountIn, uint256 amountOutMinimum, uint160 sqrtPriceLimitX96))) | (1) | "(DAI, sETH2, USDC, USDT, WBTC, WETH)" | | 59 | 0xba12222222228d8ba445958a75a0704d566bf2c8 (balancer:VAULT) | 0x52bbbe29 (swap((bytes32 poolId, uint8 kind, address assetIn, address assetOut, uint256 amount, bytes userData),(address sender, bool fromInternalBalance, address recipient, bool toInternalBalance), uint256 limit, uint256 deadline)) | (7) | "(balancer:B_50COMP_50WETH, balancer:B_60WETH_40DAI, balancer:B_50USDC_50WETH, balancer:B_stETH_STABLE, balancer:B_rETH_STABLE)" | | 60 | 0xba12222222228d8ba445958a75a0704d566bf2c8 (balancer:VAULT) | 0x52bbbe29 (swap((bytes32 poolId, uint8 kind, address assetIn, address assetOut, uint256 amount, bytes userData),(address sender, bool fromInternalBalance, address recipient, bool toInternalBalance), uint256 limit, uint256 deadline)) | (9) | "(COMP, WETH, wstETH, rETH)" | | 61 | 0xba12222222228d8ba445958a75a0704d566bf2c8 (balancer:VAULT) | 0x52bbbe29 (swap((bytes32 poolId, uint8 kind, address assetIn, address assetOut, uint256 amount, bytes userData),(address sender, bool fromInternalBalance, address recipient, bool toInternalBalance), uint256 limit, uint256 deadline)) | (10) | "(WETH, DAI, USDC, wstETH, rETH)" | # Findings & Conclusion - `ScopeTarget` - There are `target` scopes that are redundant (they have already been made) - this is because Roles SDK includes the scoping aganig when building the payload (same happens with scopeFunction, scopeParameterAsOneOf): - Balancer Vault - Uniswap v3 router - stETH token - stkAAVE - `ScopeFunction` - Any asset can be supplied to AAVE v3 but on behalf of Avatar - Later constrained in `ScopeParameterAsOneOf` - Any asset can be swapped into Sushiswap but to avatar only - Later constrained in `ScopeParameterAsOneOf` - any asset in and out of Uniswap v3 but to only Avatar - Later constrained in `ScopeParameterAsOneOf` - any asset in and out of Balancer:Vault but to only Avatar - Later constrained in `ScopeParameterAsOneOf` # References [Due Dilligence: BIP-442 Karpatkey Payload](https://forum.balancer.fi/t/bip-442-permissions-preset-update-request-2/5209/3#due-dilligence-bip-442-karpatkey-payload-1) - [@gosuto](https://forum.balancer.fi/u/gosuto) https://github.com/gnosis/zodiac-modifier-roles-v1