Skip to content

Commit

Permalink
docs: kakarot precompiles (#1610)
Browse files Browse the repository at this point in the history
adds code doc on the kakarot precompiles

<!-- Reviewable:start -->
- - -
This change is [<img src="https://reviewable.io/review_button.svg"
height="34" align="absmiddle"
alt="Reviewable"/>](https://reviewable.io/reviews/kkrt-labs/kakarot/1610)
<!-- Reviewable:end -->

---------

Co-authored-by: Oba <[email protected]>
  • Loading branch information
enitrat and obatirou authored Nov 15, 2024
1 parent 8e37d05 commit 0fbea3a
Show file tree
Hide file tree
Showing 2 changed files with 209 additions and 12 deletions.
41 changes: 37 additions & 4 deletions cairo_zero/kakarot/precompiles/kakarot_precompiles.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,42 @@ const NUMBER_OF_CALLS_BYTES = 32;
const CAIRO_PRECOMPILE_GAS = 10000;
const CAIRO_MESSAGE_GAS = 5000;

// ! Contains precompiles that are specific to Kakarot.
// !
// ! Kakarot extends the features of the EVM by allowing communication between Cairo and EVM contracts,
// ! and the sending of transactions to L1.
// !
// ! There are various considerations that one must take into account when using these precompiles.
// ! We currently have 4 different "precompiles".
// ! - 0x75001: Whitelisted Cairo Precompile. Allows any whitelisted caller to execute a Cairo call.
// ! The whitelisting is based on the address of the caller. 75001 can be called using DELEGATECALL
// ! / CALLCODE. Any contract calling 75001 must be whitelisted, as malicious contract would be able
// ! to execute arbitrary actions on behalf of the caller due to the use of DELEGATECALL / CALLCODE.
// ! The biggest use case for this precompile is the mechanism of `DualVmToken`, which allows a
// ! Solidity contract to wrap a Starknet ERC20 token and interact with it as if it were an ERC20
// ! token on Ethereum.
// ! A contract should never be whitelisted for usage without extensive review and
// ! auditing.
// !
// ! - 0x75002: Whitelisted Cairo Message Precompile. Allows the whitelisted caller to send messages to
// ! L1. This can only be used by the L2KakarotMessaging contract. The message sent to L1 must be
// ! formatted in a specific way, and only allowing L2KakarotMessaging to send messages to L1
// ! ensures this format is respected.
// !
// ! - 0x75003: Multicall Precompile. Allows the caller to execute `n` Cairo calls in a single
// ! precompile call. This precompile cannot be called with DELEGATECALL / CALLCODE. As such, it can
// ! be used permissionlessly by any contract.
// !
// ! - 0x75004: Cairo Call Precompile. Allows the caller to execute a single Cairo call. This
// ! precompile cannot be called with DELEGATECALL / CALLCODE. As such, it can be used
// ! permissionlessly by any contract.
// !
namespace KakarotPrecompiles {
// @notice Executes a cairo contract/class.
// @dev If called with 0x75001, the caller _must_ be whitelisted, as this could be called by CALLCODE / DELEGATECALL.
// @dev If called with 0x75004, the caller can be anyone, as DELEGATECALL / CALLCODE are not allowed.
// @dev If called with 0x75001, the caller _must_ be whitelisted beforehand, as this could be
// called by CALLCODE / DELEGATECALL.
// @dev If called with 0x75004, the caller can be anyone, as DELEGATECALL / CALLCODE are not
// allowed, which _must_ be enforced upstream.
// @dev The input is formatted as:
// @dev [starknet_address: bytes32][starknet_selector:bytes32][data_offset: bytes32][data_len: bytes32][data: bytes[]]
// @param input_len The length of the input in bytes.
Expand Down Expand Up @@ -75,7 +107,7 @@ namespace KakarotPrecompiles {
}

// @notice Executes a batch of calls to cairo contracts.
// @dev Cannot be called with CALLCODE / DELEGATECALL - _must_ be checked upstream.
// @dev Cannot be called with CALLCODE / DELEGATECALL - _must_ be enforced upstream.
// @dev The input is formatted as:
// @dev [number_of_calls: bytes32]
// @dev [to_1: bytes32][selector_1:bytes32][calldata_offset_1: bytes32][calldata_len_1: bytes32][calldata_1: bytes[]]...[to_n:bytes32]...
Expand Down Expand Up @@ -123,7 +155,8 @@ namespace KakarotPrecompiles {
}

// @notice Sends a message to L1.
// @dev Requires a whitelisted caller, as only the L2KakarotMessaging contract is allowed to send messages to L1.
// @dev Only the L2KakarotMessaging contract is allowed to send messages to L1. The caller must
// be whitelisted, and this whitelist _mut_ be enforced upstream.
// @param input_len The length of the input in bytes.
// @param input The input data.
// @param caller_address unused
Expand Down
180 changes: 172 additions & 8 deletions docs/general/cairo_precompiles.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# Cairo Precompiles

Kakarot zkEVM being a Starknet appchain, it is technically possible to run Cairo
Contracts on Kakarot. The purpose of this document is to explain the design
behind the Cairo precompiles, which are the Cairo contracts that are deployed on
Kakarot to provide additional functionality to the users.
Kakarot zkEVM being deployed on a Starknet chain, it is technically possible to
run Cairo Contracts on Kakarot. The purpose of this document is to explain the
design behind the Cairo precompiles, which are the Cairo contracts that are
deployed on Kakarot to provide additional functionality to the users.

## Requirements

Expand All @@ -27,6 +27,170 @@ precompile will never cause the transaction to revert, meaning that:

From these principles, we can derive the following design.

## Precompiles

There are 4 precompiles currently deployed on Kakarot:

- 0x75001: Whitelisted Cairo Precompile
- 0x75002: Whitelisted Cairo Message Precompile
- 0x75003: Multicall Precompile
- 0x75004: Cairo Call Precompile

### 0x75001: Whitelisted Cairo Precompile

This precompile allows any whitelisted caller to execute a Cairo contract. The
whitelisting is based on the address of the caller. This precompile can be
called using `DELEGATECALL` / `CALLCODE`. However, it cannot be called in a
nested DELEGATECALL scenario. As such, it should only be used by contracts that
have been thoroughly audited and are known to be secure.

Let's define three different flows to interact with the precompile, and the
expected behavior for each.

1. **Successful Flow** A participant Alice wants to interact with the
`DualVMToken` contract. The `DualVMToken` contract has been whitelisted by
the Kakarot team, meaning that it is authorized to call the `0x75001`
precompile. Alice calls the `DualVMToken` contract to transfer Starknet
Tokens, which will internally call the `0x75001` with a DELEGATECALL. Because
DualVMToken is whitelisted, the call will pass validation and the Cairo
contract will be executed. Alice's tokens are transferred to the recipient.

2. **Failed Flow 1** A participant Alice wants to interact with a random
`Contract_X` contract. Alice calls `Contract_X`, which then calls the
`DualVMToken` contract with a DELEGATECALL. The `DualVMToken` contract is
whitelisted to call the `0x75001` precompile. However, it is forbidden to
delegatecall into a contract that is whitelisted to call the `0x75001`
precompile. To check this, we verify whether the `evm.message.address.evm` of
the call is whitelisted. Here, because of the delegatecall behavior, this
resolves to `Contract_X`, which is not whitelisted. The call will thus fail.

3. **Failed Flow 2** A participant Alice wants to interact with the
`NonWhitelistedContract` contract, which is not whitelisted to call the
`0x75001` precompile. Alice calls `NonWhitelistedContract`, which then calls
the `0x75001` precompile with a DELEGATECALL. Because
`NonWhitelistedContract` is not whitelisted, the call will fail validation
and the transaction will be reverted. Alice's call to
`NonWhitelistedContract` will therefore fail.

```mermaid
sequenceDiagram
participant Alice
participant Contract_X
participant DualVMToken
participant NonWhitelistedContract
participant Precompile_75001
rect rgb(200, 255, 200)
Note over Alice,Precompile_75001: Successful Flow
Alice->>DualVMToken: Call
Note right of Alice: msg.sender = Alice<br/>msg.address.evm = DualVMToken
DualVMToken->>Precompile_75001: delegatecall
Note right of DualVMToken: msg.sender = Alice<br/>msg.address.evm = DualVMToken
Note over Precompile_75001: Check if DualVMToken<br/>is whitelisted ✓
Precompile_75001-->>DualVMToken: Success ✓
DualVMToken-->>Alice: Success ✓
end
rect rgb(255, 200, 200)
Note over Alice,Precompile_75001: Failed Flow 1 - Nested Delegatecall
Alice->>Contract_X: Call
Note right of Alice: msg.sender = Alice<br/>msg.address.evm = Contract_X
Contract_X->>DualVMToken: delegatecall
Note right of Contract_X: msg.sender = Alice<br/>msg.address.evm = Contract_X
DualVMToken->>Precompile_75001: delegatecall
Note over Precompile_75001: Fails: Precompile called<br/>during delegatecall ✗
Precompile_75001-->>DualVMToken: Fail ✗
DualVMToken-->>Contract_X: Fail ✗
Contract_X-->>Alice: Fail ✗
end
rect rgb(255, 200, 200)
Note over Alice,Precompile_75001: Failed Flow 2 - Non-whitelisted Contract
Alice->>NonWhitelistedContract: Call
Note right of Alice: msg.sender = Alice<br/>msg.address.evm = NonWhitelistedContract
NonWhitelistedContract->>Precompile_75001: delegatecall
Note right of NonWhitelistedContract: msg.sender = Alice<br/>msg.address.evm = NonWhitelistedContract
Note over Precompile_75001: Check if NonWhitelistedContract<br/>is whitelisted ✗
Precompile_75001-->>NonWhitelistedContract: Fail ✗
NonWhitelistedContract-->>Alice: Fail ✗
end
```

### 0x75002: Whitelisted Cairo Message Precompile

This precompile allows any whitelisted caller to execute a Cairo contract. The
whitelisting is based on the address of the caller. The purpose of the whitelist
is to ensure that messages sent to L1 are following a specific format (`to`,
`sender`, `data`).

```mermaid
sequenceDiagram
participant Alice
participant L2KakarotMessaging
participant NonWhitelistedContract
participant Precompile_75002
rect rgb(200, 255, 200)
Note over Alice,Precompile_75002: Successful Flow - Whitelisted Contract
Alice->>L2KakarotMessaging: Call
L2KakarotMessaging->>Precompile_75002: Execute Cairo Message
Note over Precompile_75002: Check if L2KakarotMessaging<br/>is whitelisted ✓
Note over Precompile_75002: Process message with:<br/>- to<br/>- sender<br/>- data
Precompile_75002-->>L2KakarotMessaging: Success ✓
L2KakarotMessaging-->>Alice: Success ✓
end
rect rgb(255, 200, 200)
Note over Alice,Precompile_75002: Failed Flow - Non-whitelisted Contract
Alice->>NonWhitelistedContract: Call
NonWhitelistedContract->>Precompile_75002: Execute Cairo Message
Note over Precompile_75002: Check if NonWhitelistedContract<br/>is whitelisted ✗
Precompile_75002-->>NonWhitelistedContract: Fail ✗
NonWhitelistedContract-->>Alice: Fail ✗
end
```

### 0x75003: Multicall Precompile

Allows the caller to execute `n` Cairo calls in a single precompile call. This
precompile cannot be called with DELEGATECALL / CALLCODE. As such, it can be
used permissionlessly by any contract.

```mermaid
sequenceDiagram
participant Alice
participant Contract_X
participant Precompile_75003
participant CairoContract
rect rgb(200, 255, 200)
Note over Alice,CairoContract: Successful Flow - Direct Call
Alice->>Precompile_75003: Direct Call with cairo_calls[]
Note over Precompile_75003: Check: Not delegatecall ✓
loop For each call in cairo_calls
Precompile_75003->>Alice: execute_starknet_call
Note right of Precompile_75003: Calls back to Alice's<br/>Starknet contract
Alice->>CairoContract: Execute on Starknet
end
Precompile_75003-->>Alice: Success ✓
end
rect rgb(255, 200, 200)
Note over Alice,CairoContract: Failed Flow - Delegatecall Attempt
Alice->>Contract_X: Call
Contract_X->>Precompile_75003: delegatecall
Note over Precompile_75003: Check: Is delegatecall ✗<br/>Operation not permitted
Precompile_75003-->>Contract_X: Fail ✗
Contract_X-->>Alice: Fail ✗
end
```

### 0x75004: Cairo Call Precompile

Same as `0x75003`, but for a single Cairo call.

## Design

Interacting with the Cairo precompiles will only be done by calling specific EVM
Expand Down Expand Up @@ -65,7 +229,7 @@ library CairoLib {
/// @dev The Cairo precompile contract's address.
address constant CAIRO_PRECOMPILE_ADDRESS = 0x0000000000000000000000000000000000075001;
/// @notice Performs a low-level call to a Cairo contract deployed on the Starknet appchain.
/// @notice Performs a low-level call to a Cairo contract deployed on the Starknet chain.
/// @dev Used with intent to modify the state of the Cairo contract.
/// @param contractAddress The address of the Cairo contract.
/// @param functionSelector The function selector of the Cairo contract function to be called.
Expand All @@ -77,7 +241,7 @@ library CairoLib {
uint256[] memory data
) internal returns (bytes memory returnData);
/// @notice Performs a low-level call to a Cairo contract deployed on the Starknet appchain.
/// @notice Performs a low-level call to a Cairo contract deployed on the Starknet chain.
/// @dev Used with intent to read the state of the Cairo contract.
/// @param contractAddress The address of the Cairo contract.
/// @param functionSelector The function selector of the Cairo contract function to be called.
Expand All @@ -90,7 +254,7 @@ library CairoLib {
) internal view returns (bytes memory returnData);
/// @dev Performs a low-level call to a Cairo class declared on the Starknet appchain.
/// @dev Performs a low-level call to a Cairo class declared on the Starknet chain.
/// @param classHash The class hash of the Cairo class.
/// @param functionSelector The function selector of the Cairo class function to be called.
/// @param data The input data for the Cairo class function.
Expand All @@ -109,7 +273,7 @@ library CairoLib {
It contains three functions, `callContract`, `staticcallContract` and
`libraryCall` that allow the user to call a Cairo contract or class deployed on
the Starknet appchain. The method takes three arguments:
the Starknet chain. The method takes three arguments:

- `contractAddress` or `classHash`: The address of the Cairo contract to call /
class hash to call
Expand Down

0 comments on commit 0fbea3a

Please sign in to comment.