# 2.1.0 - Compliance checks

{% hint style="success" %}
The new implementation contract was deployed and verified by Kiln at the following address: [0xE0e27f00E7455188E93F6afFcbf82b15c7d4E871](https://etherscan.io/address/0xE0e27f00E7455188E93F6afFcbf82b15c7d4E871#code). You can check the source code on Etherscan to verify it matches the expected implementation.\
\
The corresponding audit report is available [here](https://security.kiln.fi/resources), under "Audit - Kiln Onchain v2 Blocklist addition - Cantina".&#x20;
{% endhint %}

## Upgrade content: 2.1.0 - Compliance checks&#x20;

To strengthen sanctions-compliance controls, we have introduced a lightweight upgrade to the Native20 (integrator) contract. With this version you will be able to enforce onchain OFAC oracle checks and manage an allowlist/blocklist with granular action management.

**New features:**

(Opt-in) Activate OFAC screening: &#x20;

* New depositors can't interact with your integrator contract if they are on the OFAC sanctionned list
* User that is invested in the product and get sanctionned (part of the OFAC sanctionned list) can't withdraw their funds

### 1. Introduction

Each Native20 deployment is a **TUPProxy** (Transparent Upgradeable Pausable Proxy) aka integration contract. The proxy holds all user state (balances, approvals, pool config). The implementation contract holds the logic. Upgrading swaps the logic without touching user state.

This upgrade introduces `AccountList` rights enforcement. This means that after upgrade, users need **default rights** set to be able to stake and request exits. If you only call `upgradeTo()`, users will be locked out until `setDefaultRights()` is called separately by the admin, this mean we must have access to both admins in order to perform the upgrade.

**Key actors involved in the upgrade:**

| Role                  | What it does                  | How to find it                                                 |
| --------------------- | ----------------------------- | -------------------------------------------------------------- |
| **Proxy Admin**       | Can call `upgradeTo()`        | Stored in the proxy's ERC1967 admin slot                       |
| **Integration Admin** | Can call `setDefaultRights()` | Stored in the contract's `$admin` slot, readable via `admin()` |

These are usually not the same address. The upgrade itself requires the **proxy admin**. Setting default rights requires the **integration admin**.

***

### 2. Prerequisites <a href="#id-2-prerequisites" id="id-2-prerequisites"></a>

You need:

* **Foundry** installed (`forge`, `cast`)
* An **Ethereum RPC endpoint** (Infura, Alchemy, or your own node)
* The **proxy admin private key** or access to the multisig that controls each proxy
* The **integration admin private key** or access to the multisig that can call `setDefaultRights`

Set your RPC URL:

```bash
export ETH_RPC_URL=<your RPC>
```

***

### 3. Understanding the Architecture <a href="#id-3-understanding-the-architecture" id="id-3-understanding-the-architecture"></a>

Each Native20 deployment looks like this:

```
User
  |
  v
TUPProxy (holds state: balances, pool config, rights, etc.)
  |
  |-- ERC1967 admin slot --> Proxy Admin address (can upgrade)
  |-- ERC1967 impl slot  --> Current Implementation address (logic)
  |
  v
Native20 Implementation (stateless logic)
```

**Key ERC1967 storage slots:**

| Slot                                                                 | Contents               |
| -------------------------------------------------------------------- | ---------------------- |
| `0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc` | Implementation address |
| `0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103` | Proxy admin address    |

**Rights bit values** (defined in `MultiPool.sol`):

| Right          | Hex   | Decimal | Bit |
| -------------- | ----- | ------- | --- |
| `FORBIDDEN`    | `0x1` | 1       | 0   |
| `STAKE`        | `0x2` | 2       | 1   |
| `TRANSFER`     | `0x4` | 4       | 2   |
| `REQUEST_EXIT` | `0x8` | 8       | 3   |

The default rights value you want for normal operation is **`STAKE | REQUEST_EXIT` = `0xA` (decimal 10)**. This sets the rights to be iso with the behavior before the upgrade.

***

### 4. Identifying Proxy Admin Addresses <a href="#id-4-identifying-proxy-admin-addresses" id="id-4-identifying-proxy-admin-addresses"></a>

First we set our target contract address as environment variable:

```bash
export PROXY_ADDRESS=0x...  # The proxy address you want to upgrade
```

Read the admin from the ERC1967 storage slot:

```bash
cast storage $PROXY_ADDRESS 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103 | cast parse-bytes32-address
```

Read the **current implementation** address:

```bash
cast storage $PROXY_ADDRESS 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc | cast parse-bytes32-address
```

To read the **integration admin** (the one that can call `setDefaultRights`):

```bash
cast call $PROXY_ADDRESS 'admin()(address)'
```

{% hint style="info" %}
Alternatively you can also check theses values in Etherscan.
{% endhint %}

***

### 5. Record Pre-Upgrade State (Optional but Recommended) <a href="#id-5-record-pre-upgrade-state-optional-but-recommended" id="id-5-record-pre-upgrade-state-optional-but-recommended"></a>

Before upgrading, it's a good idea to record the current state to perform a quick sanity check after the upgrade. This includes:

* Rate (`cast call $PROXY_ADDRESS 'rate()(uint256)'`)
* Total supply (`cast call $PROXY_ADDRESS 'totalSupply()(uint256)'`)
* Total underlying supply (`cast call $PROXY_ADDRESS 'totalUnderlyingSupply()(uint256)'`)

### 6. Performing the Upgrade <a href="#id-6-performing-the-upgrade" id="id-6-performing-the-upgrade"></a>

The new implementation contract was deployed and verified by Kiln at the following address: [`0xE0e27f00E7455188E93F6afFcbf82b15c7d4E871`](https://etherscan.io/address/0xE0e27f00E7455188E93F6afFcbf82b15c7d4E871#code). You can check the source code on Etherscan to verify it matches the expected implementation.

#### `upgradeTo()` + `setDefaultRights()` (Two transactions mandatory) <a href="#upgradeto--setdefaultrights-two-transactions" id="upgradeto--setdefaultrights-two-transactions"></a>

Step 1 and 2 must be done in order and should be done as close together as possible to minimize disruption. Users will be unable to stake or request exits between the two steps.

*Step 1 — Upgrade (requires proxy admin):*

```bash
export NEW_IMPL=0xE0e27f00E7455188E93F6afFcbf82b15c7d4E871
cast send $PROXY_ADDRESS 'upgradeTo(address)' $NEW_IMPL --ledger
```

*Step 2 — Set default rights no OFAC screening (requires integration admin):*

```bash
cast send $PROXY_ADDRESS 'setDefaultRights(uint248)' 10 --ledger
```

*Step 3 (optional) — Enable OFAC sanctioned screening (requires integration admin):*

```shellscript
cast send $PROXY_ADDRESS 'setSanctionsActivation(bool)' true --ledger
```

{% hint style="info" %}
Note that if this feature is enabled OFAC wallet can't deposit more and OFAC sanctionned wallet will never be able to withdraw from the contract.&#x20;
{% endhint %}

#### If using multisig (e.g., Gnosis Safe) <a href="#if-using-multisig-eg-gnosis-safe" id="if-using-multisig-eg-gnosis-safe"></a>

Start by creating both proposals (upgrade and set rights) in the multisig interface, bring them all to n-1 signatures, and get the last quorum members together to execute them back-to-back.

Step by step:

1. With the proxy admin multisig, create a transaction to call `upgradeTo(new_impl)` on the proxy address.
2. With the integration admin multisig, create a second transaction in to call `setDefaultRights(10)` on the proxy address.
3. Get all the necessary signatures but 1 on both transactions.
4. Coordinate the last signers on both multisig. First perform the last signature and execute the upgrade proposal, once done do the last signature and execute the set rights transaction as soon as possible.

***

### 7. Post-Upgrade Verification <a href="#id-7-post-upgrade-verification" id="id-7-post-upgrade-verification"></a>

After each upgrade, verify:

#### 7.1 Implementation changed <a href="#id-71-implementation-changed" id="id-71-implementation-changed"></a>

```bash
cast storage $PROXY_ADDRESS 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc | cast parse-bytes32-address
# Should output $NEW_IMPL
```

#### 7.2 Default rights are set <a href="#id-72-default-rights-are-set" id="id-72-default-rights-are-set"></a>

```bash
cast call $PROXY_ADDRESS 'defaultRights()(uint248)'
# Should output: 10 (or 0xa)
```

#### 7.3 Basic view functions still work and return expected values <a href="#id-73-basic-view-functions-still-work-and-return-expected-values" id="id-73-basic-view-functions-still-work-and-return-expected-values"></a>

```bash
cast call $PROXY_ADDRESS 'name()(string)'
cast call $PROXY_ADDRESS 'symbol()(string)'
cast call $PROXY_ADDRESS 'totalSupply()(uint256)'
cast call $PROXY_ADDRESS 'totalUnderlyingSupply()(uint256)'
```

#### 7.4 Rate is preserved <a href="#id-74-rate-is-preserved" id="id-74-rate-is-preserved"></a>

The rate should be the same as before upgrade. If you recorded the rate before:

```bash
cast call $PROXY_ADDRESS 'rate()(uint256)'
# Compare with the value recorded before upgrade
```

#### 7.5 Staking works (optional, on a test fork) <a href="#id-75-staking-works-optional-on-a-test-fork" id="id-75-staking-works-optional-on-a-test-fork"></a>

On a mainnet fork, verify a fresh user can stake:

```bash
anvil --fork-url $ETH_RPC_URL
cast send $PROXY_ADDRESS 'stake()' --value 0.01ether --private-key $TEST_KEY --rpc-url http://localhost:8545
```

***

#### APPENDIX : Verifying the upgrade on a local fork first <a href="#appendix--verifying-the-upgrade-on-a-local-fork-first" id="appendix--verifying-the-upgrade-on-a-local-fork-first"></a>

Always test on a mainnet fork before executing on mainnet:

```bash
# Start a local fork
anvil --fork-url $ETH_RPC_URL --auto-impersonate
```

In another terminal you can the following bash commands, replacing the PROXY\_ADDRESS with your target

```bash
# Set the RPC URL to the local fork
export ETH_RPC_URL=http://localhost:8545

export PROXY_ADDRESS= #  The proxy address you want to upgrade

export PROXY_ADMIN_ADDRESS=$(cast storage $PROXY_ADDRESS 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103 | cast parse-bytes32-address)
export INTEGRATION_ADMIN_ADDRESS=$(cast call $PROXY_ADDRESS 'admin()(address)')
export NEW_IMPL=0xE0e27f00E7455188E93F6afFcbf82b15c7d4E871   # New implementation address

# Fund the impersonated accounts (proxy admin is usually a contract/multisig with no ETH)
cast send $PROXY_ADMIN_ADDRESS --value 1ether \
  --from 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 \
  --unlocked 
cast send $INTEGRATION_ADMIN_ADDRESS --value 1ether \
  --from 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 \
  --unlocked 

# Impersonate the proxy admin
cast send $PROXY_ADDRESS 'upgradeTo(address)' $NEW_IMPL \
  --from $PROXY_ADMIN_ADDRESS \
  --unlocked \
  --gas-limit 500000

# Impersonate the integration admin to set default rights
cast send $PROXY_ADDRESS 'setDefaultRights(uint248)' 10 \
  --from $INTEGRATION_ADMIN_ADDRESS \
  --unlocked \
  --gas-limit 500000 

# Test staking
cast send $PROXY_ADDRESS 'stake()' --value 1ether \
  --from 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 \
  --unlocked 

# Check balance of default anvil account (should have staked tokens)
cast call $PROXY_ADDRESS 'balanceOf(address)(uint256)' 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 

# Test requesting exit (use the balance from staking, not a hardcoded value)
BALANCE=$(cast call $PROXY_ADDRESS 'balanceOf(address)(uint256)' 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 | awk '{print $1}')

cast send $PROXY_ADDRESS 'requestExit(uint256)' $BALANCE \
  --from 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 \
  --unlocked 
```

#### Need help ?&#x20;

If you need assistance to perform the upgrade of if you have questions, please reach out to our support team.&#x20;


---

# 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://docs.kiln.fi/v1/kiln-products/onchain/pooled-staking/contracts-upgrades/2.1.0-compliance-checks.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.
