CallerPolicy cannot be reinstalled for the same (policy id, wallet) after uninstalling because uninstall leaves the policy status as Deprecated, while install only accepts Status.NA.
This makes a CallerPolicy id effectively single-use for a wallet. It also makes it hard for an account to rotate the allowed requesting protocols under the same policy id, even though other modules in this repository (for example TimelockPolicy) support uninstall/reinstall lifecycle semantics.
Relevant code
In src/policies/CallerPolicy.sol, install only permits the initial NA state:
function _policyOninstall(bytes32 id, bytes calldata _data) internal override {
require(status[id][msg.sender] == Status.NA, PolicyAlreadyInstalled());
address[] memory callers = abi.decode(_data, (address[]));
require(callers.length > 0, EmptyCallers());
for (uint256 i = 0; i < callers.length; i++) {
require(callers[i] != address(0), ZeroAddressCaller());
allowedCaller[id][callers[i]][msg.sender] = true;
}
status[id][msg.sender] = Status.Live;
}
But uninstall never resets that status back to NA; it leaves it as Deprecated:
function _policyOnUninstall(bytes32 id, bytes calldata _data) internal override {
require(status[id][msg.sender] == Status.Live, PolicyNotLive());
status[id][msg.sender] = Status.Deprecated;
}
As a result, this sequence fails:
- install
CallerPolicy for (POLICY_ID, wallet)
- uninstall it
- reinstall
CallerPolicy for the same (POLICY_ID, wallet)
The third step reverts with PolicyAlreadyInstalled().
Minimal repro
I added this test to test/btt/CallerPolicyValidation.t.sol:
function test_WhenReinstallingAfterUninstallWithSamePolicyId() external {
// First install allows ALLOWED_CALLER_1.
_installPolicyWithCaller(WALLET_1, ALLOWED_CALLER_1);
// Uninstall the policy for the same wallet and policy id.
vm.startPrank(WALLET_1);
policy.onUninstall(abi.encodePacked(POLICY_ID));
vm.stopPrank();
assertEq(
uint256(policy.status(POLICY_ID, WALLET_1)),
uint256(Status.Deprecated),
"Status should be Deprecated after uninstall"
);
// Reinstalling the same policy id with a new caller should be possible after uninstall.
address[] memory newCallers = new address[](1);
newCallers[0] = ALLOWED_CALLER_2;
bytes memory reinstallData = abi.encodePacked(POLICY_ID, abi.encode(newCallers));
vm.startPrank(WALLET_1);
policy.onInstall(reinstallData);
vm.stopPrank();
assertEq(
uint256(policy.status(POLICY_ID, WALLET_1)),
uint256(Status.Live),
"Status should be Live after reinstall"
);
bytes32 testHash = keccak256("test_message");
vm.startPrank(WALLET_1);
uint256 oldCallerResult = policy.checkSignaturePolicy(
POLICY_ID, ALLOWED_CALLER_1, testHash, ""
);
uint256 newCallerResult = policy.checkSignaturePolicy(
POLICY_ID, ALLOWED_CALLER_2, testHash, ""
);
vm.stopPrank();
assertEq(oldCallerResult, 1, "Old caller should not remain allowed after reinstall");
assertEq(newCallerResult, 0, "New caller should be allowed after reinstall");
}
Running only that test:
forge test --match-contract CallerPolicyValidationTest --match-test test_WhenReinstallingAfterUninstallWithSamePolicyId -vv
fails with:
Ran 1 test for test/btt/CallerPolicyValidation.t.sol:CallerPolicyValidationTest
[FAIL: PolicyAlreadyInstalled()] test_WhenReinstallingAfterUninstallWithSamePolicyId() (gas: 144331)
Suite result: FAILED. 0 passed; 1 failed; 0 skipped
Expected behavior
After uninstalling, one of these semantics would be clearer:
- allow reinstalling the same
(policy id, wallet) by treating Deprecated as reinstallable, while replacing/clearing the old allowedCaller entries; or
- reset status to
NA on uninstall and clear the previous caller allowlist; or
- explicitly document that
CallerPolicy ids are intentionally single-use and callers must always use a fresh policy id after uninstall.
The first two options seem more consistent with normal ERC-7579 module lifecycle expectations. They also avoid stale allowedCaller entries becoming live again accidentally if reinstall support is added later without clearing the old allowlist.
Environment
Checked against zerodevapp/kernel-7579-plugins at commit 332deed.
CallerPolicycannot be reinstalled for the same(policy id, wallet)after uninstalling because uninstall leaves the policy status asDeprecated, while install only acceptsStatus.NA.This makes a
CallerPolicyid effectively single-use for a wallet. It also makes it hard for an account to rotate the allowed requesting protocols under the same policy id, even though other modules in this repository (for exampleTimelockPolicy) support uninstall/reinstall lifecycle semantics.Relevant code
In
src/policies/CallerPolicy.sol, install only permits the initialNAstate:But uninstall never resets that status back to
NA; it leaves it asDeprecated:As a result, this sequence fails:
CallerPolicyfor(POLICY_ID, wallet)CallerPolicyfor the same(POLICY_ID, wallet)The third step reverts with
PolicyAlreadyInstalled().Minimal repro
I added this test to
test/btt/CallerPolicyValidation.t.sol:Running only that test:
fails with:
Expected behavior
After uninstalling, one of these semantics would be clearer:
(policy id, wallet)by treatingDeprecatedas reinstallable, while replacing/clearing the oldallowedCallerentries; orNAon uninstall and clear the previous caller allowlist; orCallerPolicyids are intentionally single-use and callers must always use a fresh policy id after uninstall.The first two options seem more consistent with normal ERC-7579 module lifecycle expectations. They also avoid stale
allowedCallerentries becoming live again accidentally if reinstall support is added later without clearing the old allowlist.Environment
Checked against
zerodevapp/kernel-7579-pluginsat commit332deed.