# Security Model for Depositors / Point Sellers

## Security components

Rumpel Wallet is built on top of [Safe](https://docs.gnosis.io/safe/latest/). Each user wallet is simply a standard Safe with a special Rumpel [Module](https://docs.safe.global/advanced/smart-account-modules) and Rumpel [Guard](https://docs.safe.global/advanced/smart-account-guards) added on. Therefore, our approach to security is largely inherited from Safe, and to understand the scope of an admin’s power, you need only look at these two main added components.

[**Rumpel Guard**](https://rumpel-docs.gitbook.io/docs/how-does-rumpel-work/..#rumpel-guard) – restricts Rumpel user actions to specific calls added onto the **allowlist**

[**Rumpel Module**](https://rumpel-docs.gitbook.io/docs/how-does-rumpel-work/..#rumpel-module) – enables the admin to make calls on a user's behalf, unless the calls are on the **blocklist**<br>

## **Why is an admin involved at all?**

[Rumpel Point Tokens](https://rumpel-docs.gitbook.io/docs/how-does-rumpel-work/..#point-token) are claims on future reward tokens, so when the reward tokens are released, the admin must be able to sweep them from user wallets into the Point Token Vault for pToken redemption. If a user were to withdraw the rewards first, pTokens would go unbacked.

However, there are many assets that we can be confident will never be reward tokens, and therefore, we can *permanently* enable many actions for users, and disable them for admins. Our contracts are designed with this in mind.

## How our security works

The **module blocklist** and the **guard allowlist** block and allow *calls.* Calls are specific function selectors at specific addresses such as `USDC.transfer` or `RSUSDE.deposit`. While users are the only owners of their 1-of-1 Safe, the Guard allowlist blocks all calls by default, preventing users from making any malicious contract calls. Therefore, only “allowed” calls can be executed by the user.

When a call is added to the module blocklist, an admin can no longer make it on behalf of the user’s Safe. *This list is one-way/immutable, meaning once a call is added, it cannot be removed.*

* [`addBlockedModuleCall` function ](https://github.com/sense-finance/rumpel-wallet/blob/b885434fc856f8debba7039ab22b7f4112604fd2/src/RumpelModule.sol#L68)
* [blocklist check](https://github.com/sense-finance/rumpel-wallet/blob/b885434fc856f8debba7039ab22b7f4112604fd2/src/RumpelModule.sol#L40)

🔒 During deployment, `enableModule` and `disableModule` are added to the blocklist ([tx1](https://etherscan.io/tx/0x6c39e579839eaebcedd4faa20985e204ffcef8ddf19f42cbdbdb83cb73480797), [tx2](https://etherscan.io/tx/0x72ffcf5e68471560c2bf4255a60def6d9cf89e3a4567b9e044809a7d903a0f0f)), so admins are prevented from changing the module itself to get around the blocklist.

When a call is added to the guard allowlist, users are able to make that call via their Safe. An admin can add and remove these allowed calls, *except* if they’re set to `PERMANENTLY_ON`, in which case an admin can no longer remove them.

* [`PERMANENTLY_ON` enforcement](https://github.com/sense-finance/rumpel-wallet/blob/b885434fc856f8debba7039ab22b7f4112604fd2/src/RumpelGuard.sol#L101)&#x20;
* [allowlist check](https://github.com/sense-finance/rumpel-wallet/blob/b885434fc856f8debba7039ab22b7f4112604fd2/src/RumpelGuard.sol#L77)

To put it simply, in the case of assets:

1. **Blocklist**: If an asset is on the blocklist, the admin cannot withdraw it from users
2. **Allowlist**: If an asset is on the allowlist, users can withdraw the asset
3. **Permanent Allowlist**: When active for an asset, an admin cannot remove it from the allowlist

### Example transaction

As defined above, there are three possible actions an admin can take:

* add a call to the guard allowlist as `ON`
* and add a call to the allowlist as `PERMANENTLY_ON`
* add a call to the module blocklist

[Here](https://etherscan.io/tx/0x26892f4648581219b2717161a2061ffec56817d74db1c9ca922285dd6659b12e) is a recent batch transaction where each of the three state transitions takes place. Let’s examine it by going through the logs.

1. Add a call to the guard allowlist as `ON`\
   \
   This first log shows a call added to the guard allowlist with a list state of `1`, corresponding to `ON`. The target is WBTC and the function selector is `transfer(address,uint256)`, so this action adds `WBTC.transfer` to the allowlist – but an admin has the ability to remove it in the future.

<figure><img src="https://1867312591-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FpufmDi4GX1YNAMUXZqqn%2Fuploads%2FGtzek5EQH2cNTgUXDFRB%2Fimage.png?alt=media&#x26;token=fe41c471-8422-4713-9fb6-b0721fdbaf57" alt=""><figcaption></figcaption></figure>

2. Add a call to the guard allowlist as `PERMANENTLY_ON`\
   \
   This next one shows a call added to the guard allowlist with a list state of `2`, corresponding to `PERMANENTLY_ON`. The target is [RE7LRT](https://etherscan.io/token/0x84631c0d0081FDe56DeB72F6DE77abBbF6A9f93a) and the function selector is `transfer(address,uint256)`, so this action adds `RE7LRT.transfer` to the allowlist – and an admin has no way of removing it.

<figure><img src="https://1867312591-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FpufmDi4GX1YNAMUXZqqn%2Fuploads%2FS3pEZJDXaXE6SGNZgSaZ%2Fimage.png?alt=media&#x26;token=c634e908-560f-4778-90e1-d9f7c2bf06fe" alt=""><figcaption></figcaption></figure>

3. Add a call to the module blocklist\
   \
   This last one shows a call added to the module blocklist. The target is RE7LRT and the function selector is `approve(address,uint256)`, so the action adds `RE7LRT.approve` to the blocklist. Per the list’s design, an admin cannot remove it, so an admin will never be able to make that call on behalf of a user.

<figure><img src="https://1867312591-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FpufmDi4GX1YNAMUXZqqn%2Fuploads%2FupP30kI5usLJ7BmKjOM4%2Fimage.png?alt=media&#x26;token=92fb34a8-ee36-4438-8f51-8d95813c0933" alt=""><figcaption></figcaption></figure>

\
As a result of these 3 changes:

* Users can transfer `WBTC` from their Safe
* Users can transfer `RE7LRT` from their Safe, and an admin can never disallow it
* An admin can never give another address approvals on the `RE7LRT` in a user’s Safe

## Audits

The Rumpel Wallet contracts have been [audited several times](https://rumpel-docs.gitbook.io/docs/resources/audits-and-bug-bounty) to verify that they behave as outlined above.

We also maintain an ongoing [bug bounty program through Sherlock](https://audits.sherlock.xyz/bug-bounties/29).

## Verification

The blocklist & allowlist can easily be verified using events. Here is a Python script that does so:

```python
import os
from dotenv import load_dotenv
from web3 import Web3

# Load environment variables from .env file
load_dotenv()

# Connect to an Ethereum node
w3 = Web3(
    Web3.HTTPProvider(
        f"<https://eth-mainnet.g.alchemy.com/v2/>{os.environ.get('ALCHEMY_API_KEY', '')}"
    )
)

# Contract addresses
RUMPEL_GUARD_ADDRESS = "0x9000FeF2846A5253fD2C6ed5241De0fddb404302"
RUMPEL_MODULE_ADDRESS = "0x28c3498B4956f4aD8d4549ACA8F66260975D361a"
POINT_TOKENIZATION_VAULT_ADDRESS = "0xe47F9Dbbfe98d6930562017ee212C1A1Ae45ba61"

# ABIs
RUMPEL_GUARD_ABI = """[
    {"anonymous":false,"inputs":[{"indexed":true,"name":"target","type":"address"},{"indexed":true,"name":"functionSelector","type":"bytes4"},{"indexed":false,"name":"allowListState","type":"uint8"}],"name":"SetCallAllowed","type":"event"}
]"""

RUMPEL_MODULE_ABI = """[
    {"anonymous":false,"inputs":[{"indexed":true,"name":"target","type":"address"},{"indexed":true,"name":"functionSelector","type":"bytes4"}],"name":"SetModuleCallBlocked","type":"event"}
]"""

# Create contract instances
rumpel_guard = w3.eth.contract(address=RUMPEL_GUARD_ADDRESS, abi=RUMPEL_GUARD_ABI)
rumpel_module = w3.eth.contract(address=RUMPEL_MODULE_ADDRESS, abi=RUMPEL_MODULE_ABI)

# Token and Protocol addresses
TOKEN_ADDRESSES = {
    "0x82f5104b23FF2FA54C2345F821dAc9369e9E0B26": "RSUSDE",
    "0x7a4EffD87C2f3C55CA251080b1343b605f327E3a": "RSTETH",
    "0xe1B4d34E8754600962Cd944B535180Bd758E6c2e": "AGETH",
    "0x9D39A5DE30e57443BfF2A8307A4256c8797A3497": "SUSDE",
    "0x4c9EDD5852cd905f086C759E8383e09bff1E68B3": "USDE",
    "0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0": "WSTETH",
    "0xBE3cA34D0E877A1Fc889BD5231D65477779AFf4e": "KUSDE",
    "0x2DABcea55a12d73191AeCe59F508b191Fb68AdaC": "KWEETH",
    "0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee": "WEETH",
    "0x49446A0874197839D15395B908328a74ccc96Bc0": "MSTETH",
    "0x84631c0d0081FDe56DeB72F6DE77abBbF6A9f93a": "RE7LRT",
    "0x7F43fDe12A40dE708d908Fb3b9BFB8540d9Ce444": "RE7RWBTC",
    "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599": "WBTC",
    "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84": "stETH",
    "0x917ceE801a67f933F2e6b33fC0cD1ED2d5909D88": "weETHs",
    "0x0000000000000000000000000000000000000000": "ETH",
}

PROTOCOL_ADDRESSES = {
    "0x4095F064B8d3c3548A3bebfd0Bbfd04750E30077": "Morpho Bundler",
    "0xF047ab4c75cebf0eB9ed34Ae2c186f3611aEAfa6": "Zircuit Restaking Pool",
    "0xC329400492c6ff2438472D4651Ad17389fCb843a": "Symbiotic wstETH Collateral",
    "0x19d0D8e6294B7a04a2733FE433444704B791939A": "Symbiotic sUSDe Collateral",
    "0x54e44DbB92dBA848ACe27F44c0CB4268981eF1CC": "Karak Vault Supervisor",
    "0xAfa904152E04aBFf56701223118Be2832A4449E0": "Karak Delegation Supervisor",
    "0x8707f238936c12c309bfc2B9959C35828AcFc512": "Ethena LP Staking",
}

# Known function selectors
KNOWN_SELECTORS = {
    "23b872dd": "transferFrom",
    "a9059cbb": "transfer",
    "095ea7b3": "approve",
    "ac9650d8": "multicall",
    "f7654176": "depositFor",
    "2e1a7d4d": "withdraw",
    "b6b55f25": "deposit",
    "a0712d68": "mint",
    "db006a75": "redeem",
    "2e17de78": "unstake",
    "70ed1731": "cooldownAssets",
    "b88ba9c3": "cooldownShares",
    "a694fc3a": "stake",
    "5d36b190": "gimmieShares",
    "3f2e5fc3": "returnShares",
    "5bcee7d8": "depositAndGimmie",
    "92dca407": "startWithdraw",
    "86e9a1f7": "finishWithdraw",
    "441a3e70": "registerWithdrawal",
    "610b5925": "enableModule",
    "e009cfde": "disableModule",
}

def get_pretty_name(address):
    if address == "0x0000000000000000000000000000000000000000":
        return "User Safe"
    return (
        "Point Tokenization Vault"
        if address == POINT_TOKENIZATION_VAULT_ADDRESS
        else (
            TOKEN_ADDRESSES.get(address, f"{address} (Token)")
            if address in TOKEN_ADDRESSES
            else (
                PROTOCOL_ADDRESSES.get(address, f"{address} (Protocol)")
                if address in PROTOCOL_ADDRESSES
                else address
            )
        )
    )

def get_function_name(selector):
    return KNOWN_SELECTORS.get(selector, f"Unknown ({selector})")

def print_allow_list():
    print("RumpelGuard Allow List:")
    print("=======================")

    logs = rumpel_guard.events.SetCallAllowed().get_logs(from_block=0)
    allow_list = {}

    for log in logs:
        target = log.args.target
        selector = log.args.functionSelector.hex()
        state = log.args.allowListState

        if target not in allow_list:
            allow_list[target] = {}

        allow_list[target][selector] = state

    for target, selectors in allow_list.items():
        if len(selectors) == 1 and list(selectors.values())[0] == 0:
            continue

        print(f"\\n{get_pretty_name(target)}:")
        for selector, state in selectors.items():
            state_str = ["OFF", "ON", "PERMANENTLY_ON"][state]
            if state_str != "OFF":
                print(f"  {get_function_name(selector)}: {state_str}")

def print_block_list():
    print("\\nRumpelModule Block List:")
    print("========================")

    logs = rumpel_module.events.SetModuleCallBlocked().get_logs(from_block=0)
    block_list = {}

    for log in logs:
        target = log.args.target
        selector = log.args.functionSelector.hex()

        if target not in block_list:
            block_list[target] = set()

        block_list[target].add(selector)

    for target, selectors in block_list.items():
        print(f"\\n{get_pretty_name(target)}:")
        for selector in selectors:
            print(f"  {get_function_name(selector)}")

if __name__ == "__main__":
    with open("lists.txt", "w") as f:
        # Redirect stdout to the file
        import sys

        original_stdout = sys.stdout
        sys.stdout = f

        print_allow_list()
        print("\\n")  # Add more space between lists
        print_block_list()

        # Restore stdout
        sys.stdout = original_stdout

    print("Output has been saved to lists.txt")

```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://rumpel-docs.gitbook.io/docs/how-does-rumpel-work/security-model-for-depositors-point-sellers.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
