diff --git a/cairo_zero/kakarot/precompiles/kakarot_precompiles.cairo b/cairo_zero/kakarot/precompiles/kakarot_precompiles.cairo index 97515bd32..78cbdd7ff 100644 --- a/cairo_zero/kakarot/precompiles/kakarot_precompiles.cairo +++ b/cairo_zero/kakarot/precompiles/kakarot_precompiles.cairo @@ -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. @@ -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]... @@ -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 diff --git a/docs/general/cairo_precompiles.md b/docs/general/cairo_precompiles.md index 743a62d40..f414a8040 100644 --- a/docs/general/cairo_precompiles.md +++ b/docs/general/cairo_precompiles.md @@ -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 @@ -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
msg.address.evm = DualVMToken + DualVMToken->>Precompile_75001: delegatecall + Note right of DualVMToken: msg.sender = Alice
msg.address.evm = DualVMToken + Note over Precompile_75001: Check if DualVMToken
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
msg.address.evm = Contract_X + Contract_X->>DualVMToken: delegatecall + Note right of Contract_X: msg.sender = Alice
msg.address.evm = Contract_X + DualVMToken->>Precompile_75001: delegatecall + Note over Precompile_75001: Fails: Precompile called
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
msg.address.evm = NonWhitelistedContract + NonWhitelistedContract->>Precompile_75001: delegatecall + Note right of NonWhitelistedContract: msg.sender = Alice
msg.address.evm = NonWhitelistedContract + Note over Precompile_75001: Check if NonWhitelistedContract
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
is whitelisted ✓ + Note over Precompile_75002: Process message with:
- to
- sender
- 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
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
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 ✗
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 @@ -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. @@ -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. @@ -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. @@ -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