Skip to content
This repository has been archived by the owner on Jan 9, 2025. It is now read-only.

Commit

Permalink
feat: public cairo_call precompile (#1509)
Browse files Browse the repository at this point in the history
A public cairo_call precompile at address 0x75004, with
delegate/callcode disabled.

reworked a bit the solidity contracts that are used solely for testing
purposed.

todo next:
- rework the public cairo lib to use 75003 and 75004 (multicall,
singlecall cairo precompiles) instead of 75001
- recompile the pragma contracts with these new precomps

<!-- 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/1509)
<!-- Reviewable:end -->
  • Loading branch information
enitrat authored Oct 16, 2024
1 parent a053c59 commit 2b8ec8c
Show file tree
Hide file tree
Showing 12 changed files with 289 additions and 99 deletions.
5 changes: 3 additions & 2 deletions cairo_zero/kakarot/constants.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,10 @@ namespace Constants {
const P256VERIFY_PRECOMPILE = 0x100;

// Kakarot precompiles
const CAIRO_CALL_PRECOMPILE = 0x75001;
const CAIRO_WHITELISTED_CALL_PRECOMPILE = 0x75001;
const CAIRO_MESSAGING_PRECOMPILE = 0x75002;
const CAIRO_BATCH_CALL_PRECOMPILE = 0x75003;
const CAIRO_MULTICALL_PRECOMPILE = 0x75003;
const CAIRO_CALL_PRECOMPILE = 0x75004;

// FIELD PRIME
const FELT252_PRIME_HIGH = 0x8000000000000110000000000000000;
Expand Down
11 changes: 6 additions & 5 deletions cairo_zero/kakarot/precompiles/kakarot_precompiles.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,14 @@ const CAIRO_MESSAGE_GAS = 5000;

namespace KakarotPrecompiles {
// @notice Executes a cairo contract/class.
// @dev Requires a whitelisted caller, as this could be called by CALLCODE / DELEGATECALL
// @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 The input is formatted as:
// @dev [selector: bytes4][starknet_address: bytes32][starknet_selector:bytes32][data_offset: bytes32][data_len: bytes32][data: bytes[]]
// @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.
// @param input The input data.
// @param caller_address The address of the caller of the precompile. Delegatecall rules apply.
func cairo_precompile{
func cairo_call_precompile{
syscall_ptr: felt*,
pedersen_ptr: HashBuiltin*,
range_check_ptr,
Expand Down Expand Up @@ -76,12 +77,12 @@ namespace KakarotPrecompiles {
// @notice Executes a batch of calls to cairo contracts.
// @dev Cannot be called with CALLCODE / DELEGATECALL - _must_ be checked upstream.
// @dev The input is formatted as:
// @dev [selector: bytes4][number_of_calls: bytes4]
// @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]...
// @param input_len The length of the input in bytes.
// @param input The input data.
// @param caller_address The address of the caller of the precompile.
func multicall_cairo_precompile{
func cairo_multicall_precompile{
syscall_ptr: felt*,
pedersen_ptr: HashBuiltin*,
range_check_ptr,
Expand Down
6 changes: 4 additions & 2 deletions cairo_zero/kakarot/precompiles/precompiles.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -158,11 +158,13 @@ namespace Precompiles {
// Kakarot precompiles. Offset must have been computed appropriately,
// based on the total number of kakarot precompiles
jmp rel offset;
call KakarotPrecompiles.cairo_precompile; // offset 0x0c: precompile 0x75001
call KakarotPrecompiles.cairo_call_precompile; // offset 0x0c: precompile 0x75001
ret;
call KakarotPrecompiles.cairo_message; // offset 0x0d: precompile 0x75002
ret;
call KakarotPrecompiles.multicall_cairo_precompile; // offset 0x0e: precompile 0x75003
call KakarotPrecompiles.cairo_multicall_precompile; // offset 0x0e: precompile 0x75003
ret;
call KakarotPrecompiles.cairo_call_precompile; // offset 0x0f: precompile 0x75004
ret;
}

Expand Down
22 changes: 16 additions & 6 deletions cairo_zero/kakarot/precompiles/precompiles_helpers.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ from kakarot.storages import Kakarot_authorized_cairo_precompiles_callers
const LAST_ETHEREUM_PRECOMPILE_ADDRESS = 0x0a;
const FIRST_ROLLUP_PRECOMPILE_ADDRESS = Constants.P256VERIFY_PRECOMPILE;
const LAST_ROLLUP_PRECOMPILE_ADDRESS = Constants.P256VERIFY_PRECOMPILE;
const FIRST_KAKAROT_PRECOMPILE_ADDRESS = Constants.CAIRO_CALL_PRECOMPILE;
const LAST_KAKAROT_PRECOMPILE_ADDRESS = Constants.CAIRO_BATCH_CALL_PRECOMPILE;
const FIRST_KAKAROT_PRECOMPILE_ADDRESS = Constants.CAIRO_WHITELISTED_CALL_PRECOMPILE;
const LAST_KAKAROT_PRECOMPILE_ADDRESS = Constants.CAIRO_CALL_PRECOMPILE;

namespace PrecompilesHelpers {
func is_rollup_precompile{range_check_ptr}(address: felt) -> felt {
Expand Down Expand Up @@ -46,7 +46,7 @@ namespace PrecompilesHelpers {
// @param precompile_address The address of the precompile.
// @return Whether the precompile address requires a whitelist.
func requires_whitelist(precompile_address: felt) -> felt {
if (precompile_address == Constants.CAIRO_CALL_PRECOMPILE) {
if (precompile_address == Constants.CAIRO_WHITELISTED_CALL_PRECOMPILE) {
return TRUE;
}
if (precompile_address == Constants.CAIRO_MESSAGING_PRECOMPILE) {
Expand All @@ -65,10 +65,18 @@ namespace PrecompilesHelpers {
return res;
}

func is_delegatecall_protected(precompile_address: felt) -> felt {
let is_cairo_multicall = Helpers.is_zero(
precompile_address - Constants.CAIRO_MULTICALL_PRECOMPILE
);
let is_cairo_call = Helpers.is_zero(precompile_address - Constants.CAIRO_CALL_PRECOMPILE);
return is_cairo_multicall + is_cairo_call;
}

// @notice Returns whether the call to the precompile is authorized.
// @dev A call is authorized if:
// a. The precompile requires a whitelist AND the CODE_ADDRESS of the caller is whitelisted
// b. The precompile is CAIRO_BATCH_CALL_PRECOMPILE and the precompile address is the same as the message address (NOT a DELEGATECALL / CALLCODE).
// b. The precompile is CAIRO_MULTICALL_PRECOMPILE and the precompile address is the same as the message address (NOT a DELEGATECALL / CALLCODE).
// @param precompile_address The address of the precompile.
// @param caller_code_address The code_address of the precompile caller.
// @param caller_address The address of the caller.
Expand Down Expand Up @@ -101,8 +109,10 @@ namespace PrecompilesHelpers {
let range_check_ptr = [ap - 2];
let authorized = [ap - 1];

// Ensure that calls to CAIRO_BATCH_CALL_PRECOMPILE are not made through a delegatecall / callcode.
if (precompile_address == Constants.CAIRO_BATCH_CALL_PRECOMPILE) {
// Ensure that calls to CAIRO_CALL_PRECOMPILE or CAIRO_CALL_PRECOMPILE are not made through
// a delegatecall / callcode.
let is_delegatecall_protected_ = is_delegatecall_protected(precompile_address);
if (is_delegatecall_protected_ != FALSE) {
let is_not_delegatecall = Helpers.is_zero(message_address - precompile_address);
tempvar authorized = authorized * is_not_delegatecall;
} else {
Expand Down
148 changes: 148 additions & 0 deletions solidity_contracts/src/CairoPrecompiles/CallCairoPrecompileTest.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0 <0.9.0;

contract CallCairoPrecompileTest {
using CallCairoLib for uint256;

/// @dev The cairo contract to call
uint256 immutable cairoCounter;

constructor(uint256 cairoContractAddress) {
cairoCounter = cairoContractAddress;
}

function getCairoCounter() public view returns (uint256 counterValue) {
uint256[] memory data = new uint256[](0);
bytes memory returnData = cairoCounter.staticcallCairo("get", data);

// The return data is a 256-bit integer, so we can directly cast it to uint256
return abi.decode(returnData, (uint256));
}

/// @notice Calls the Cairo contract to increment its internal counter
function incrementCairoCounter() external {
uint256[] memory data = new uint256[](0);
cairoCounter.callCairo("inc", data);
}

/// @notice Calls the Cairo contract to set its internal counter to an arbitrary value
/// @dev Called with a regular call, the caller's address will be this contract's address
/// @dev The counter value is split into two 128-bit values to match the Cairo contract's expected inputs (u256 is composed of two u128s)
/// @param newCounter The new counter value to set
function setCairoCounter(uint256 newCounter) external {
// The u256 input must be split into two u128 values to match the expected cairo input
uint128 newCounterLow = uint128(newCounter);
uint128 newCounterHigh = uint128(newCounter >> 128);

uint256[] memory data = new uint256[](2);
data[0] = uint256(newCounterLow);
data[1] = uint256(newCounterHigh);
cairoCounter.callCairo("set_counter", data);
}

/// @notice Calls the Cairo contract to get the (starknet) address of the last caller
/// @return lastCaller The starknet address of the last caller
function getLastCaller() external view returns (uint256 lastCaller) {
uint256[] memory data = new uint256[](0);
bytes memory returnData = cairoCounter.staticcallCairo("get_last_caller", data);

return abi.decode(returnData, (uint256));
}

/// @notice Calls the Cairo contract to increment its internal counter
/// @dev The delegatecall preserves the caller's context, so the caller's address will
/// be the caller of this function.
/// @dev Should always fail, as MulticallCairo does not support delegatecalls.
function incrementCairoCounterDelegatecall() external {
uint256[] memory data = new uint256[](0);
cairoCounter.delegatecallCairo("inc", data);
}

/// @notice Calls the Cairo contract to increment its internal counter
/// @dev Called with a regular call, the caller's address will be this contract's address
/// @dev Should always fail, as MulticallCairo does not support callcode.
function incrementCairoCounterCallcode() external {
uint256[] memory data = new uint256[](0);
cairoCounter.callcodeCairo("inc", data);
}
}

library CallCairoLib {
address constant CALL_CAIRO_PRECOMPILE = 0x0000000000000000000000000000000000075004;

function callCairo(uint256 contractAddress, string memory functionName, uint256[] memory data)
internal
returns (bytes memory)
{
uint256 functionSelector = uint256(keccak256(bytes(functionName))) % 2 ** 250;
bytes memory callData = abi.encode(contractAddress, functionSelector, data);

(bool success, bytes memory result) = CALL_CAIRO_PRECOMPILE.call(callData);
require(success, string(abi.encodePacked("CairoLib: cairo call failed with: ", result)));

return result;
}

function staticcallCairo(uint256 contractAddress, string memory functionName, uint256[] memory data)
internal
view
returns (bytes memory)
{
uint256 functionSelector = uint256(keccak256(bytes(functionName))) % 2 ** 250;
bytes memory callData = abi.encode(contractAddress, functionSelector, data);

(bool success, bytes memory result) = CALL_CAIRO_PRECOMPILE.staticcall(callData);
require(success, string(abi.encodePacked("CairoLib: cairo call failed with: ", result)));

return result;
}

function delegatecallCairo(uint256 contractAddress, string memory functionName, uint256[] memory data)
internal
returns (bytes memory)
{
uint256 functionSelector = uint256(keccak256(bytes(functionName))) % 2 ** 250;
bytes memory callData = abi.encode(contractAddress, functionSelector, data);

(bool success, bytes memory result) = CALL_CAIRO_PRECOMPILE.delegatecall(callData);
require(success, string(abi.encodePacked("CairoLib: cairo call failed with: ", result)));

return result;
}

function callcodeCairo(uint256 contractAddress, string memory functionName, uint256[] memory data)
internal
returns (bytes memory)
{
uint256 functionSelector = uint256(keccak256(bytes(functionName))) % 2 ** 250;
bytes memory callData = abi.encode(contractAddress, functionSelector, data);

bool success;
bytes memory result;

// Use inline assembly for callcode
assembly {
// Allocate memory for the return data
let ptr := mload(0x40)

// Perform the callcode
success := callcode(gas(), CALL_CAIRO_PRECOMPILE, 0, add(callData, 0x20), mload(callData), ptr, 0)

// Retrieve the size of the return data
let size := returndatasize()

// Store the size of the return data
mstore(result, size)

// Copy the return data
returndatacopy(add(result, 0x20), 0, size)

// Update the free memory pointer
mstore(0x40, add(result, add(0x20, size)))
}

require(success, string(abi.encodePacked("CairoLib: call_contract failed with: ", result)));

return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ pragma solidity >=0.8.0 <0.9.0;

/// @notice A contract that performs various call types to the Kakarot MulticallCairo precompile.
/// @dev Only meant to test the MulticallCairo precompile when called from a Solidity Contract.
contract MulticallCairoCounterCaller {
contract MulticallCairoPrecompileTest {
using MulticallCairoLib for uint256;

/// @dev The cairo contract to call
Expand All @@ -12,16 +12,14 @@ contract MulticallCairoCounterCaller {
/// @dev The cairo function selector to call - `inc`
uint256 constant FUNCTION_SELECTOR_INC = uint256(keccak256("inc")) % 2 ** 250;

/// @dev The cairo function selector to call - `set_counter`
uint256 constant FUNCTION_SELECTOR_SET_COUNTER = uint256(keccak256("set_counter")) % 2 ** 250;

constructor(uint256 cairoContractAddress) {
cairoCounter = cairoContractAddress;
}

/// @notice Calls the Cairo contract to increment its internal counter
function incrementCairoCounter() external {
cairoCounter.callCairo("inc");
uint256[] memory data = new uint256[](0);
cairoCounter.callCairo("inc", data);
}

/// @notice Calls the Cairo contract to increment its internal counter in a batch of multiple calls
Expand All @@ -42,14 +40,16 @@ contract MulticallCairoCounterCaller {
/// be the caller of this function.
/// @dev Should always fail, as MulticallCairo does not support delegatecalls.
function incrementCairoCounterDelegatecall() external {
cairoCounter.delegatecallCairo("inc");
uint256[] memory data = new uint256[](0);
cairoCounter.delegatecallCairo("inc", data);
}

/// @notice Calls the Cairo contract to increment its internal counter
/// @dev Called with a regular call, the caller's address will be this contract's address
/// @dev Should always fail, as MulticallCairo does not support callcode.
function incrementCairoCounterCallcode() external {
cairoCounter.callcodeCairo("inc");
uint256[] memory data = new uint256[](0);
cairoCounter.callcodeCairo("inc", data);
}
}

Expand Down Expand Up @@ -78,10 +78,11 @@ library MulticallCairoLib {
return result;
}

function callCairo(uint256 contractAddress, uint256 functionSelector, uint256[] memory data)
function callCairo(uint256 contractAddress, string memory functionName, uint256[] memory data)
internal
returns (bytes memory)
{
uint256 functionSelector = uint256(keccak256(bytes(functionName))) % 2 ** 250;
bytes memory callData =
bytes.concat(abi.encode(uint256(1)), abi.encode(contractAddress, functionSelector, data));

Expand All @@ -91,28 +92,11 @@ library MulticallCairoLib {
return result;
}

function callCairo(uint256 contractAddress, uint256 functionSelector) internal returns (bytes memory) {
uint256[] memory data = new uint256[](0);
return callCairo(contractAddress, functionSelector, data);
}

function callCairo(uint256 contractAddress, string memory functionName, uint256[] memory data)
function delegatecallCairo(uint256 contractAddress, string memory functionName, uint256[] memory data)
internal
returns (bytes memory)
{
uint256 functionSelector = uint256(keccak256(bytes(functionName))) % 2 ** 250;
return callCairo(contractAddress, functionSelector, data);
}

function callCairo(uint256 contractAddress, string memory functionName) internal returns (bytes memory) {
uint256[] memory data = new uint256[](0);
return callCairo(contractAddress, functionName, data);
}

function delegatecallCairo(uint256 contractAddress, uint256 functionSelector, uint256[] memory data)
internal
returns (bytes memory)
{
bytes memory callData =
bytes.concat(abi.encode(uint256(1)), abi.encode(contractAddress, functionSelector, data));

Expand All @@ -126,29 +110,11 @@ library MulticallCairoLib {
return result;
}

function delegatecallCairo(uint256 contractAddress, uint256 functionSelector) internal returns (bytes memory) {
uint256[] memory data = new uint256[](0);
return delegatecallCairo(contractAddress, functionSelector, data);
}

function delegatecallCairo(uint256 contractAddress, string memory functionName, uint256[] memory data)
function callcodeCairo(uint256 contractAddress, string memory functionName, uint256[] memory data)
internal
returns (bytes memory)
{
uint256 functionSelector = uint256(keccak256(bytes(functionName))) % 2 ** 250;
return delegatecallCairo(contractAddress, functionSelector, data);
}

function delegatecallCairo(uint256 contractAddress, string memory functionName) internal returns (bytes memory) {
uint256[] memory data = new uint256[](0);
uint256 functionSelector = uint256(keccak256(bytes(functionName))) % 2 ** 250;
return delegatecallCairo(contractAddress, functionSelector, data);
}

function callcodeCairo(uint256 contractAddress, uint256 functionSelector, uint256[] memory data)
internal
returns (bytes memory)
{
bytes memory callData =
bytes.concat(abi.encode(uint256(1)), abi.encode(contractAddress, functionSelector, data));

Expand Down Expand Up @@ -180,10 +146,4 @@ library MulticallCairoLib {

return result;
}

function callcodeCairo(uint256 contractAddress, string memory functionName) internal returns (bytes memory) {
uint256[] memory data = new uint256[](0);
uint256 functionSelector = uint256(keccak256(bytes(functionName))) % 2 ** 250;
return callcodeCairo(contractAddress, functionSelector, data);
}
}
Loading

0 comments on commit 2b8ec8c

Please sign in to comment.