Skip to content

Commit

Permalink
feat: code-based instance configuration support (#492)
Browse files Browse the repository at this point in the history
**Motivation:**

Code-based instance configurations serve as an alternative to our
standard JSON-based instance configuration. JSON-based configs will work
in most use cases but they are limited when a more advanced setup is
required. Code-based configs use the power of llama scripts to enable
features such as creating strategies or accounts with multiple logic
contracts and creating permissions based on dynamic conditions (e.g.
deploying a guard and then using the deployed address as the target
address).

User flow:
- Deployer calls `just deploy-instance` using something like
`advancedInstanceConfig.json` as the config file (required to have
instant execution strategy as the first strategy and role 1 assigned to
deployer)
- Deployer creates a contract similar to
`src/llama-scripts/LlamaInstanceConfigScriptTemplate.sol` and inherits
from `LlamaInstanceConfigBase`. Contract is deployed and address is used
in next step.
- Deployer adds correct parameters to
`run-configure-advanced-instance-script` in the `justfile` and calls
`just configure-advanced-instance`

**Modifications:**

- Added a `ConfigureAdvancedLlamaInstance.s.sol` script that can be run
for an instance deployed using an advanced configuration (instant
execution strategy on setRolePermission with a single policyholder).
- This script authorizes and executes `LlamaInstanceConfigScript.sol`
which can be edited to describe the instance configuration using code.
It also includes the post configuration cleanup that removes any trace
of the config bot.
- Added tests
- Added a justfile command and documentation

**Result:**

Instances can be deployed using a standard JSON-based configuration or
an advanced code-based configuration.
  • Loading branch information
AustinGreen authored Nov 9, 2023
1 parent d2be19f commit 17a6779
Show file tree
Hide file tree
Showing 11 changed files with 552 additions and 8 deletions.
6 changes: 6 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ run-script script_name flags='' sig='' args='':

run-deploy-instance-script flags: (run-script 'DeployLlamaInstance' flags '--sig "run(address,string)"' '$SCRIPT_DEPLOYER_ADDRESS "llamaInstanceConfig.json"')

run-configure-advanced-instance-script flags: (run-script 'ConfigureAdvancedLlamaInstance' flags '--sig "run(address,string,address,address,string)"' '$SCRIPT_DEPLOYER_ADDRESS "advancedInstanceConfig.json" <DEPLOYED_LLAMA_CORE> <DEPLOYED_INSTANCE_CONFIG_SCRIPT> <UPDATED_ROLE_DESCRIPTION>')

dry-run-deploy: (run-script 'DeployLlamaFactory')

deploy: (run-script 'DeployLlamaFactory' '--broadcast --verify --build-info --build-info-path build_info')
Expand All @@ -29,3 +31,7 @@ verify: (run-script 'DeployLlamaFactory' '--verify --resume')
dry-run-deploy-instance: (run-deploy-instance-script '')

deploy-instance: (run-deploy-instance-script '--broadcast --verify')

dry-run-configure-advanced-instance: (run-configure-advanced-instance-script '')

configure-advanced-instance: (run-configure-advanced-instance-script '--broadcast --verify')
153 changes: 153 additions & 0 deletions script/ConfigureAdvancedLlamaInstance.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

import {Script, stdJson} from "forge-std/Script.sol";

import {Clones} from "@openzeppelin/proxy/Clones.sol";

import {ILlamaStrategy} from "src/interfaces/ILlamaStrategy.sol";
import {LlamaCore} from "src/LlamaCore.sol";
import {LlamaPolicy} from "src/LlamaPolicy.sol";
import {LlamaInstanceConfigScriptTemplate} from "src/llama-scripts/LlamaInstanceConfigScriptTemplate.sol";
import {DeployUtils} from "script/DeployUtils.sol";
import {Action, ActionInfo, PermissionData} from "src/lib/Structs.sol";
import {RoleDescription} from "src/lib/UDVTs.sol";

contract ConfigureAdvancedLlamaInstance is Script {
using stdJson for string;

uint8 constant CONFIG_ROLE = 1;

// The bootstrap strategy must be set as an instant execution strategy for this script to run
ILlamaStrategy bootstrapStrategy;

function _authorizeScript(address deployer, LlamaCore core, address configurationScript) internal {
// Grant the CONFIG_ROLE permission to authorize scripts with the instant execution strategy
LlamaPolicy policy = core.policy();
PermissionData memory scriptAuthorizePermission =
PermissionData(address(core), LlamaCore.setScriptAuthorization.selector, bootstrapStrategy);
bytes memory authPermissionData =
abi.encodeCall(LlamaPolicy.setRolePermission, (CONFIG_ROLE, scriptAuthorizePermission, true));

vm.broadcast(deployer);
uint256 authPermissionActionId = core.createAction(
CONFIG_ROLE,
bootstrapStrategy,
address(policy),
0,
authPermissionData,
"# Grant permission to authorize configuration script\n\nGrant the configuration bot permission to authorize scripts."
);
ActionInfo memory authPermissionActionInfo = ActionInfo(
authPermissionActionId, deployer, CONFIG_ROLE, bootstrapStrategy, address(policy), 0, authPermissionData
);

vm.broadcast(deployer);
core.queueAction(authPermissionActionInfo);

vm.broadcast(deployer);
core.executeAction(authPermissionActionInfo);

// Create an action to authorize the instance configuration script
bytes memory authorizeData = abi.encodeCall(LlamaCore.setScriptAuthorization, (configurationScript, true));

vm.broadcast(deployer);
uint256 authorizeActionId = core.createAction(
CONFIG_ROLE,
bootstrapStrategy,
address(core),
0,
authorizeData,
"# Authorize configuration script\n\nAuthorize the instance configuration script."
);
ActionInfo memory authorizeActionInfo =
ActionInfo(authorizeActionId, deployer, CONFIG_ROLE, bootstrapStrategy, address(core), 0, authorizeData);

vm.broadcast(deployer);
core.queueAction(authorizeActionInfo);

vm.broadcast(deployer);
core.executeAction(authorizeActionInfo);
}

function _executeScript(
address deployer,
LlamaCore core,
address configurationScript,
string memory updatedRoleDescription
) internal {
//Grant the CONFIG_ROLE permission to execute the deployed script with the instant execution strategy
LlamaPolicy policy = core.policy();

// This assumes that the selector matches the execute function's selector in LlamaInstanceConfigScriptTemplate
PermissionData memory scriptExecutePermission =
PermissionData(configurationScript, LlamaInstanceConfigScriptTemplate.execute.selector, bootstrapStrategy);
bytes memory executePermissionData =
abi.encodeCall(LlamaPolicy.setRolePermission, (CONFIG_ROLE, scriptExecutePermission, true));

vm.broadcast(deployer);
uint256 executePermissionActionId = core.createAction(
CONFIG_ROLE,
bootstrapStrategy,
address(policy),
0,
executePermissionData,
"# Grant permission to execute configuration script\n\nGive the config bot permission to call the execute function on the instance configuration script."
);
ActionInfo memory executePermissionActionInfo = ActionInfo(
executePermissionActionId, deployer, CONFIG_ROLE, bootstrapStrategy, address(policy), 0, executePermissionData
);

vm.broadcast(deployer);
core.queueAction(executePermissionActionInfo);

vm.broadcast(deployer);
core.executeAction(executePermissionActionInfo);

// Create an action to call execute on the instance configuration script
RoleDescription updatedDescription = RoleDescription.wrap(bytes32(bytes(updatedRoleDescription)));
bytes memory executeData =
abi.encodeCall(LlamaInstanceConfigScriptTemplate.execute, (deployer, bootstrapStrategy, updatedDescription));

vm.broadcast(deployer);
uint256 executeActionId = core.createAction(
CONFIG_ROLE,
bootstrapStrategy,
configurationScript,
0,
executeData,
"# Execute configuration script\n\nExecute the instance configuration script."
);

ActionInfo memory executeActionInfo =
ActionInfo(executeActionId, deployer, CONFIG_ROLE, bootstrapStrategy, configurationScript, 0, executeData);

vm.broadcast(deployer);
core.queueAction(executeActionInfo);

vm.broadcast(deployer);
core.executeAction(executeActionInfo);
}

function run(
address deployer,
string memory configFile,
LlamaCore core,
address configurationScript,
string memory updatedRoleDescription
) public {
// Get bootstrap strategy
string memory jsonInput = DeployUtils.readScriptInput(configFile);
address strategyLogic = address(jsonInput.readAddress(".strategyLogic"));
bytes[] memory encodedStrategies = DeployUtils.readStrategies(jsonInput);
bootstrapStrategy =
ILlamaStrategy(Clones.predictDeterministicAddress(strategyLogic, keccak256(encodedStrategies[0]), address(core)));

// Grant the config bot permission to authorize scripts and authorize the instance configuration script
_authorizeScript(deployer, core, configurationScript);

// Grant the config bot permission to execute the instance configuration script and execute the instance
// configuration script
_executeScript(deployer, core, configurationScript, updatedRoleDescription);
}
}
11 changes: 10 additions & 1 deletion script/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

The current Llama scripts are:
* `DeployLlamaFactory.s.sol`, which deploys the LlamaFactory, logic/implementation contracts, and LlamaLens to new chains
* `DeployLlamaInstance.s.sol`, which deploys new Llama instances
* `DeployLlamaInstance.s.sol`, which deploys new Llama instances using a JSON-based configuration
* `ConfigureAdvancedLlamaInstance.s.sol`, which completes the initialization of advanced instance deployments with a code-based configuration

Additionally, both `DeployLlamaFactory` and `DeployLlamaInstance` are called during the test bootstrap process to establish the state against which most of the test suite runs.

Expand All @@ -25,6 +26,14 @@ Therefore, we help enforce this at the protocol level with the following behavio

A key part of ensuring the instance is not misconfigured is ensuring that the `bootstrapStrategy` is a valid strategy that can actually be executed. This is checked in deploy scripts, because the strategy can have any logic, so it's not necessarily possible to check this at the protocol level.

### Standard vs Advanced Deployments

Standard deployments define their instance configuration in a JSON file. The `DeployLlamaInstance` script uses this JSON-based configuration as the input to call the `LlamaFactory` deploy function. This is the preferred deployment method for most instances.

For instances that want a more flexible, code-based deployment method, they can use the `DeployLlamaInstance` script along with the `ConfigureAdvancedInstance` script. This can be used to handle advanced use cases such as configuring both absolute and relative strategies. The `DeployLlamaInstance` script is run using a configuration similar to `script/input/31337/advancedInstanceConfig.json`. This deploys the instance with a single configuration bot policyholder. The configuration file for advanced deployments must have an instant execution strategy as the bootstrap strategy (first strategy in `initialStrategies`) and role #1 must be assigned to the deployer of the `ConfigureAdvancedInstance` script.

We've provided `src/scripts/LlamaInstanceConfigBase.sol` as a parent contract for configuration scripts to simplify the process of removing all traces of the config bot from the instance post-configuration. Please note that the body of the instance configuration script must assign roles, permissions, and strategies in a functional way or else there's a risk that the instance will be unaccessible. We recommend using the JSON-based configuration by default because it has built-in protection against these user errors.

## DeployLlamaFactory

To perform a dry-run of the `DeployLlamaFactory` script on a network, first set the
Expand Down
37 changes: 37 additions & 0 deletions script/input/11155111/advancedInstanceConfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"comment": "This is the default configuration for code-based deployments. The strategy logic is the `LlamaRelativeQuantityQuorum` contract.",
"factory": "0xFf5d4E226D9A3496EECE31083a8F493edd79AbEB",
"instanceName": "Code Based Config Example",
"instanceColor": "#6A45EC",
"instanceLogo": "<g><path fill='#fff' d='M94.528 446.858h-6.905v2.912h2.658v15.141h-3.424v2.911h10.199v-2.911h-2.528v-18.053ZM107.476 446.858h-6.904v2.912h2.657v15.141h-3.423v2.911h10.199v-2.911h-2.529v-18.053ZM117.016 457.442c.144-1.5 1.17-2.3 3.019-2.3 1.849 0 2.774.8 2.774 2.533v.422l-5.085.626c-3.019.379-5.388 1.703-5.388 4.688 0 2.97 2.239 4.673 5.633 4.673 2.846 0 4.103-1.092 4.695-2.024h.289v1.762h3.958v-9.55c0-4.164-2.658-6.042-6.789-6.042-4.161 0-6.703 1.878-7.006 4.775v.437h3.9Zm-.506 5.78c0-1.151.867-1.704 2.124-1.879l4.175-.553v.656c0 2.46-1.618 3.697-3.843 3.697-1.56 0-2.456-.757-2.456-1.921ZM130.126 467.822h3.9v-9.812c0-1.878.982-2.694 2.355-2.694 1.228 0 1.834.714 1.834 1.922v10.584h3.901v-9.812c0-1.878.982-2.694 2.34-2.694 1.242 0 1.835.714 1.835 1.922v10.584h3.914v-11.123c0-2.766-1.733-4.411-4.377-4.411-2.369 0-3.467 1.034-4.102 2.169h-.289c-.434-.99-1.503-2.169-3.698-2.169-2.153 0-3.091.903-3.612 1.82h-.289v-1.529h-3.712v15.243ZM157.105 457.442c.145-1.5 1.17-2.3 3.019-2.3 1.849 0 2.774.8 2.774 2.533v.422l-5.085.626c-3.02.379-5.388 1.703-5.388 4.688 0 2.97 2.239 4.673 5.633 4.673 2.846 0 4.103-1.092 4.695-2.024h.289v1.762H167v-9.55c0-4.164-2.658-6.042-6.789-6.042-4.161 0-6.703 1.878-7.006 4.775v.437h3.9Zm-.506 5.78c0-1.151.867-1.704 2.124-1.879l4.175-.553v.656c0 2.46-1.618 3.697-3.843 3.697-1.56 0-2.456-.757-2.456-1.921Z'/><g fill='#6A45EC'><path d='M36.956 458.249a1.37 1.37 0 0 1 .39-.942c.245-.251.577-.397.927-.406H55.8a6.262 6.262 0 0 0 4.458-1.855 6.352 6.352 0 0 0 1.369-2.06 6.406 6.406 0 0 0 .482-2.433V428h-4.96v22.553a1.371 1.371 0 0 1-.389.942 1.351 1.351 0 0 1-.927.407H38.29a6.263 6.263 0 0 0-4.445 1.861A6.383 6.383 0 0 0 32 458.249v15.568h4.956v-15.568ZM64.304 432.298h-.22l.21.213v4.65h4.561l2.14 2.179a6.286 6.286 0 0 0-3.617 2.205 6.394 6.394 0 0 0-1.422 4.016v28.262h4.936v-28.269c.005-.356.148-.696.397-.948.25-.252.587-.395.94-.4h.76c2.912 0 3.9-1.638 4.192-2.325.293-.688.744-2.565-1.316-4.651l-4.831-4.915H69.53M51.193 471.362c1.083 0 2.154.216 3.153.638a8.153 8.153 0 0 1 2.665 1.817h6a13.224 13.224 0 0 0-4.85-5.438 13.059 13.059 0 0 0-6.968-2.017c-2.464 0-4.879.699-6.968 2.017a13.225 13.225 0 0 0-4.85 5.438h6A8.154 8.154 0 0 1 48.038 472a8.101 8.101 0 0 1 3.154-.638Z'/></g></g>",
"strategyLogic": "0x81F7D26fD7d814bFcEF78239a32c0BA5282C98Dc",
"strategyType": 1,
"accountLogic": "0x915Af6753f03D2687Fa923b2987625e21e2991aE",
"initialStrategies": [
{
"approvalPeriod": 0,
"approvalRole": 1,
"comment": "Instant execution",
"disapprovalRole": 0,
"expirationPeriod": 432000,
"forceApprovalRoles": [],
"forceDisapprovalRoles": [],
"isFixedLengthApprovalPeriod": false,
"minApprovalPct": 0,
"minDisapprovalPct": 10001,
"queuingPeriod": 0
}
],
"initialAccounts": [],
"initialRoleDescriptions": ["Configuration Bot"],
"initialRoleHolders": [
{
"comment": "This assigns the Configuration Bot role to the configuration bot.",
"expiration": 18446744073709551615,
"policyholder": "0x144316A2AAdB8E36cCD32F07cB22484E96e58AD7",
"quantity": 1,
"role": 1
}
],
"initialRolePermissions": []
}
37 changes: 37 additions & 0 deletions script/input/31337/advancedInstanceConfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"comment": "This is the default configuration for code-based deployments. The strategy logic is the `LlamaRelativeQuantityQuorum` contract.",
"factory": "0xDEb1E9a6Be7Baf84208BB6E10aC9F9bbE1D70809",
"instanceName": "Llama",
"instanceColor": "#6A45EC",
"instanceLogo": "<g><path fill='#fff' d='M94.528 446.858h-6.905v2.912h2.658v15.141h-3.424v2.911h10.199v-2.911h-2.528v-18.053ZM107.476 446.858h-6.904v2.912h2.657v15.141h-3.423v2.911h10.199v-2.911h-2.529v-18.053ZM117.016 457.442c.144-1.5 1.17-2.3 3.019-2.3 1.849 0 2.774.8 2.774 2.533v.422l-5.085.626c-3.019.379-5.388 1.703-5.388 4.688 0 2.97 2.239 4.673 5.633 4.673 2.846 0 4.103-1.092 4.695-2.024h.289v1.762h3.958v-9.55c0-4.164-2.658-6.042-6.789-6.042-4.161 0-6.703 1.878-7.006 4.775v.437h3.9Zm-.506 5.78c0-1.151.867-1.704 2.124-1.879l4.175-.553v.656c0 2.46-1.618 3.697-3.843 3.697-1.56 0-2.456-.757-2.456-1.921ZM130.126 467.822h3.9v-9.812c0-1.878.982-2.694 2.355-2.694 1.228 0 1.834.714 1.834 1.922v10.584h3.901v-9.812c0-1.878.982-2.694 2.34-2.694 1.242 0 1.835.714 1.835 1.922v10.584h3.914v-11.123c0-2.766-1.733-4.411-4.377-4.411-2.369 0-3.467 1.034-4.102 2.169h-.289c-.434-.99-1.503-2.169-3.698-2.169-2.153 0-3.091.903-3.612 1.82h-.289v-1.529h-3.712v15.243ZM157.105 457.442c.145-1.5 1.17-2.3 3.019-2.3 1.849 0 2.774.8 2.774 2.533v.422l-5.085.626c-3.02.379-5.388 1.703-5.388 4.688 0 2.97 2.239 4.673 5.633 4.673 2.846 0 4.103-1.092 4.695-2.024h.289v1.762H167v-9.55c0-4.164-2.658-6.042-6.789-6.042-4.161 0-6.703 1.878-7.006 4.775v.437h3.9Zm-.506 5.78c0-1.151.867-1.704 2.124-1.879l4.175-.553v.656c0 2.46-1.618 3.697-3.843 3.697-1.56 0-2.456-.757-2.456-1.921Z'/><g fill='#6A45EC'><path d='M36.956 458.249a1.37 1.37 0 0 1 .39-.942c.245-.251.577-.397.927-.406H55.8a6.262 6.262 0 0 0 4.458-1.855 6.352 6.352 0 0 0 1.369-2.06 6.406 6.406 0 0 0 .482-2.433V428h-4.96v22.553a1.371 1.371 0 0 1-.389.942 1.351 1.351 0 0 1-.927.407H38.29a6.263 6.263 0 0 0-4.445 1.861A6.383 6.383 0 0 0 32 458.249v15.568h4.956v-15.568ZM64.304 432.298h-.22l.21.213v4.65h4.561l2.14 2.179a6.286 6.286 0 0 0-3.617 2.205 6.394 6.394 0 0 0-1.422 4.016v28.262h4.936v-28.269c.005-.356.148-.696.397-.948.25-.252.587-.395.94-.4h.76c2.912 0 3.9-1.638 4.192-2.325.293-.688.744-2.565-1.316-4.651l-4.831-4.915H69.53M51.193 471.362c1.083 0 2.154.216 3.153.638a8.153 8.153 0 0 1 2.665 1.817h6a13.224 13.224 0 0 0-4.85-5.438 13.059 13.059 0 0 0-6.968-2.017c-2.464 0-4.879.699-6.968 2.017a13.225 13.225 0 0 0-4.85 5.438h6A8.154 8.154 0 0 1 48.038 472a8.101 8.101 0 0 1 3.154-.638Z'/></g></g>",
"strategyLogic": "0xA8452Ec99ce0C64f20701dB7dD3abDb607c00496",
"strategyType": 1,
"accountLogic": "0xDB8cFf278adCCF9E9b5da745B44E754fC4EE3C76",
"initialStrategies": [
{
"approvalPeriod": 0,
"approvalRole": 1,
"comment": "Instant execution",
"disapprovalRole": 0,
"expirationPeriod": 432000,
"forceApprovalRoles": [],
"forceDisapprovalRoles": [],
"isFixedLengthApprovalPeriod": false,
"minApprovalPct": 0,
"minDisapprovalPct": 10001,
"queuingPeriod": 0
}
],
"initialAccounts": [],
"initialRoleDescriptions": ["Configuration Bot"],
"initialRoleHolders": [
{
"comment": "This assigns the Configuration Bot role to the instance deployer.",
"expiration": 18446744073709551615,
"policyholder": "0x3d9fEa8AeD0249990133132Bb4BC8d07C6a8259a",
"quantity": 1,
"role": 1
}
],
"initialRolePermissions": []
}
41 changes: 41 additions & 0 deletions src/llama-scripts/LlamaInstanceConfigBase.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

import {LlamaBaseScript} from "src/llama-scripts/LlamaBaseScript.sol";
import {ILlamaStrategy} from "src/interfaces/ILlamaStrategy.sol";
import {LlamaCore} from "src/LlamaCore.sol";
import {LlamaPolicy} from "src/LlamaPolicy.sol";
import {PermissionData} from "src/lib/Structs.sol";
import {RoleDescription} from "src/lib/UDVTs.sol";

abstract contract LlamaInstanceConfigBase is LlamaBaseScript {
uint8 constant CONFIG_ROLE = 1;

function _postConfigurationCleanup(
address configPolicyHolder,
LlamaCore core,
ILlamaStrategy bootstrapStrategy,
RoleDescription description,
PermissionData memory executePermission
) internal {
LlamaPolicy policy = core.policy();
PermissionData memory authorizePermission =
PermissionData(address(core), LlamaCore.setScriptAuthorization.selector, bootstrapStrategy);

// Rename role #1 description
policy.updateRoleDescription(CONFIG_ROLE, description);

// Unauthorize configuration script
core.setScriptAuthorization(SELF, false);

// Remove configuration policyholder
policy.revokePolicy(configPolicyHolder);

// Unauthorize instant execution strategy
core.setStrategyAuthorization(bootstrapStrategy, false);

// Remove role #1 permissions to authorize and execute scripts
policy.setRolePermission(CONFIG_ROLE, authorizePermission, false);
policy.setRolePermission(CONFIG_ROLE, executePermission, false);
}
}
25 changes: 25 additions & 0 deletions src/llama-scripts/LlamaInstanceConfigScriptTemplate.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

import {LlamaInstanceConfigBase} from "src/llama-scripts/LlamaInstanceConfigBase.sol";
import {ILlamaStrategy} from "src/interfaces/ILlamaStrategy.sol";
import {LlamaCore} from "src/LlamaCore.sol";
import {PermissionData} from "src/lib/Structs.sol";
import {RoleDescription} from "src/lib/UDVTs.sol";

contract LlamaInstanceConfigScriptTemplate is LlamaInstanceConfigBase {
function execute(address configPolicyHolder, ILlamaStrategy bootstrapStrategy, RoleDescription description)
external
onlyDelegateCall
{
LlamaCore core = LlamaCore(msg.sender);

// The selector needs to be this contract's execute function
PermissionData memory executePermission =
PermissionData(SELF, LlamaInstanceConfigScriptTemplate.execute.selector, bootstrapStrategy);

// Insert configuration code here

_postConfigurationCleanup(configPolicyHolder, core, bootstrapStrategy, description, executePermission);
}
}
Loading

0 comments on commit 17a6779

Please sign in to comment.