Skip to content

CallerPolicy cannot be reinstalled with the same policy id after uninstall #63

Description

@chenshj73

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:

  1. install CallerPolicy for (POLICY_ID, wallet)
  2. uninstall it
  3. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions