Skip to content

Commit

Permalink
feat: add whitelisting for pragma precompile caller (#1177)
Browse files Browse the repository at this point in the history
<!--- Please provide a general summary of your changes in the title
above -->

<!-- Give an estimate of the time you spent on this PR in terms of work
days.
Did you spend 0.5 days on this PR or rather 2 days?  -->

Time spent on this PR: 0.5d

## Pull request type

<!-- Please try to limit your pull request to one type,
submit multiple pull requests if needed. -->

Please check the type of change your PR introduces:

- [ ] Bugfix
- [ ] Feature
- [ ] Code style update (formatting, renaming)
- [ ] Refactoring (no functional changes, no api changes)
- [ ] Build related changes
- [ ] Documentation content changes
- [ ] Other (please describe):

## What is the current behavior?

<!-- Please describe the current behavior that you are modifying,
or link to a relevant issue. -->

Resolves #<Issue number>

## What is the new behavior?

<!-- Please describe the behavior or changes that are being added by
this PR. -->

-
-
-

<!-- 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/1177)
<!-- Reviewable:end -->
  • Loading branch information
enitrat committed Jun 4, 2024
1 parent 094a55c commit befcb93
Show file tree
Hide file tree
Showing 11 changed files with 208 additions and 11 deletions.
38 changes: 38 additions & 0 deletions src/kakarot/errors.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,44 @@ namespace Errors {
return (28, error);
}

func unauthorizedPrecompile() -> (error_len: felt, error: felt*) {
let (error) = get_label_location(unauthorized_precompile_error_message);
return (31, error);
unauthorized_precompile_error_message:
dw 'K';
dw 'a';
dw 'k';
dw 'a';
dw 'r';
dw 'o';
dw 't';
dw ':';
dw ' ';
dw 'u';
dw 'n';
dw 'a';
dw 'u';
dw 't';
dw 'h';
dw 'o';
dw 'r';
dw 'i';
dw 'z';
dw 'e';
dw 'd';
dw 'P';
dw 'r';
dw 'e';
dw 'c';
dw 'o';
dw 'm';
dw 'p';
dw 'i';
dw 'l';
dw 'e';
}

func notImplementedPrecompile(address: felt) -> (error_len: felt, error: felt*) {
alloc_locals;
let (error) = alloc();
Expand Down
6 changes: 5 additions & 1 deletion src/kakarot/interpreter.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,15 @@ namespace Interpreter {
let is_pc_ge_code_len = is_le(evm.message.bytecode_len, pc);
if (is_pc_ge_code_len != FALSE) {
let is_precompile = Precompiles.is_precompile(evm.message.code_address);
let caller_address = evm.message.caller;
if (is_precompile != FALSE) {
let (
output_len, output, gas_used, precompile_reverted
) = Precompiles.exec_precompile(
evm.message.code_address, evm.message.calldata_len, evm.message.calldata
evm.message.code_address,
evm.message.calldata_len,
evm.message.calldata,
caller_address,
);
let evm = EVM.charge_gas(evm, gas_used);
let evm_reverted = is_not_zero(evm.reverted);
Expand Down
11 changes: 11 additions & 0 deletions src/kakarot/kakarot.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,17 @@ func set_account_contract_class_hash{
return Kakarot.set_account_contract_class_hash(account_contract_class_hash);
}

// @notice Sets the authorization of an EVM address to call Cairo Precompiles
// @param evm_address The EVM address
// @param authorized Whether the EVM address is authorized or not
@external
func set_authorized_cairo_precompile_caller{
syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr
}(evm_address: felt, authorized: felt) {
Ownable.assert_only_owner();
return Kakarot.set_authorized_cairo_precompile_caller(evm_address, authorized);
}

// @notice Set the Cairo1Helpers class hash
// @param cairo1_helpers_class_hash The Cairo1Helpers class hash
@external
Expand Down
11 changes: 11 additions & 0 deletions src/kakarot/library.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ from kakarot.storages import (
Kakarot_prev_randao,
Kakarot_block_gas_limit,
Kakarot_evm_to_starknet_address,
Kakarot_authorized_cairo_precompiles_callers,
)
from kakarot.events import evm_contract_deployed
from kakarot.interpreter import Interpreter
Expand Down Expand Up @@ -261,6 +262,16 @@ namespace Kakarot {
return ();
}

// @notice Sets the authorization of an EVM address to call Cairo Precompiles
// @param evm_address The EVM address
// @param authorized Whether the EVM address is authorized or not
func set_authorized_cairo_precompile_caller{
syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr
}(evm_address: felt, authorized: felt) {
Kakarot_authorized_cairo_precompiles_callers.write(evm_address, authorized);
return ();
}

// @notice Register the calling Starknet address for the given EVM address
// @dev Only the corresponding computed Starknet address can make this call to ensure that registered accounts are actually deployed.
// @param evm_address The EVM address of the account.
Expand Down
10 changes: 10 additions & 0 deletions src/kakarot/precompiles/kakarot_precompiles.cairo
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
%lang starknet

from starkware.cairo.common.cairo_builtins import HashBuiltin, BitwiseBuiltin
from starkware.cairo.common.math_cmp import is_le
from starkware.cairo.common.alloc import alloc
from starkware.starknet.common.syscalls import call_contract, library_call

from kakarot.errors import Errors
from kakarot.storages import Kakarot_authorized_cairo_precompiles_callers
from utils.utils import Helpers

const CALL_CONTRACT_SOLIDITY_SELECTOR = 0xb3eb2c1b;
Expand All @@ -12,6 +15,13 @@ const LIBRARY_CALL_SOLIDITY_SELECTOR = 0x5a9af197;
const CAIRO_PRECOMPILE_GAS = 10000;

namespace KakarotPrecompiles {
func is_caller_whitelisted{syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, range_check_ptr}(
caller_address: felt
) -> felt {
let (res) = Kakarot_authorized_cairo_precompiles_callers.read(caller_address);
return res;
}

func cairo_precompile{
syscall_ptr: felt*,
pedersen_ptr: HashBuiltin*,
Expand Down
38 changes: 36 additions & 2 deletions src/kakarot/precompiles/precompiles.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -69,17 +69,36 @@ namespace Precompiles {
pedersen_ptr: HashBuiltin*,
range_check_ptr,
bitwise_ptr: BitwiseBuiltin*,
}(evm_address: felt, input_len: felt, input: felt*) -> (
}(evm_address: felt, input_len: felt, input: felt*, caller_address: felt) -> (
output_len: felt, output: felt*, gas_used: felt, reverted: felt
) {
let is_eth_precompile = is_le(evm_address, LAST_ETHEREUM_PRECOMPILE_ADDRESS);
tempvar syscall_ptr = syscall_ptr;
tempvar pedersen_ptr = pedersen_ptr;
tempvar range_check_ptr = range_check_ptr;
jmp eth_precompile if is_eth_precompile != 0;

let is_rollup_precompile_ = is_rollup_precompile(evm_address);
tempvar syscall_ptr = syscall_ptr;
tempvar pedersen_ptr = pedersen_ptr;
tempvar range_check_ptr = range_check_ptr;
jmp rollup_precompile if is_rollup_precompile_ != 0;

let is_kakarot_precompile_ = is_kakarot_precompile(evm_address);
jmp kakarot_precompile if is_kakarot_precompile_ != 0;
let is_whitelisted = KakarotPrecompiles.is_caller_whitelisted(caller_address);
tempvar is_kakarot_and_whitelisted = is_kakarot_precompile_ * is_whitelisted;
tempvar syscall_ptr = syscall_ptr;
tempvar pedersen_ptr = pedersen_ptr;
tempvar range_check_ptr = range_check_ptr;
jmp kakarot_precompile if is_kakarot_and_whitelisted != 0;

// Prepare arguments if none of the above conditions are met
[ap] = syscall_ptr, ap++;
[ap] = pedersen_ptr, ap++;
[ap] = range_check_ptr, ap++;
[ap] = bitwise_ptr, ap++;
call unauthorized_precompile;
ret;

eth_precompile:
tempvar index = evm_address;
Expand Down Expand Up @@ -148,6 +167,21 @@ namespace Precompiles {
ret;
}

// @notice A placeholder for attempts to call a precompile without permissions
// @dev Halts execution.
// @param evm_address The evm_address.
// @param input_len The length of the input array.
// @param input The input array.
func unauthorized_precompile{
syscall_ptr: felt*,
pedersen_ptr: HashBuiltin*,
range_check_ptr,
bitwise_ptr: BitwiseBuiltin*,
}() -> (output_len: felt, output: felt*, gas_used: felt, reverted: felt) {
let (revert_reason_len, revert_reason) = Errors.unauthorizedPrecompile();
return (revert_reason_len, revert_reason, 0, Errors.EXCEPTIONAL_HALT);
}

// @notice A placeholder for precompile that don't exist.
// @dev Halts execution.
// @param evm_address The evm_address.
Expand Down
4 changes: 4 additions & 0 deletions src/kakarot/storages.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,7 @@ func Kakarot_prev_randao() -> (res: Uint256) {
@storage_var
func Kakarot_block_gas_limit() -> (res: felt) {
}

@storage_var
func Kakarot_authorized_cairo_precompiles_callers(address: felt) -> (res: felt) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import pytest
import pytest_asyncio

from kakarot_scripts.utils.kakarot import EvmTransactionError
from kakarot_scripts.utils.starknet import get_deployments, wait_for_transaction

ENTRY_TYPE_INDEX = {"SpotEntry": 0, "FutureEntry": 1, "GenericEntry": 2}
Expand Down Expand Up @@ -40,13 +41,19 @@ def serialize_data_type(data_type: dict) -> Tuple:


@pytest.fixture(autouse=True)
async def setup(get_contract, mocked_values, max_fee):
async def setup(get_contract, invoke, mocked_values, pragma_caller, max_fee):
pragma_oracle = get_contract("MockPragmaOracle")
tx = await pragma_oracle.functions["set_price"].invoke_v1(
*mocked_values,
max_fee=max_fee,
)
await wait_for_transaction(tx.hash)
await invoke(
"kakarot",
"set_authorized_cairo_precompile_caller",
int(pragma_caller.address, 16),
True,
)


@pytest_asyncio.fixture(scope="module")
Expand Down Expand Up @@ -138,3 +145,35 @@ async def test_should_return_data_median_for_query(
)
else:
assert res_maybe_expiration_timestamp == 0

@pytest.mark.parametrize(
"data_type, mocked_values",
[
(
{"SpotEntry": int.from_bytes(b"BTC/USD", byteorder="big")},
(
int.from_bytes(b"BTC/USD", byteorder="big"),
70000,
18,
1717143838,
1,
),
),
],
)
async def test_should_fail_unauthorized_caller(
self, get_contract, pragma_caller, invoke, data_type, max_fee, mocked_values
):
await invoke(
"kakarot",
"set_authorized_cairo_precompile_caller",
int(pragma_caller.address, 16),
False,
)
cairo_pragma = get_contract("MockPragmaOracle")
(cairo_res,) = await cairo_pragma.functions["get_data_median"].call(data_type)
solidity_input = serialize_data_type(data_type)

with pytest.raises(EvmTransactionError) as e:
await pragma_caller.getDataMedianSpot(solidity_input)
assert "CairoLib: call_contract failed" in str(e.value)
25 changes: 22 additions & 3 deletions tests/end_to_end/CairoPrecompiles/test_cairo_precompiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,24 @@ async def cleanup(get_contract, max_fee):
await wait_for_transaction(tx.hash)


@pytest_asyncio.fixture(scope="module")
async def cairo_counter_caller(deploy_contract, owner):
@pytest_asyncio.fixture()
async def cairo_counter_caller(deploy_contract, invoke, owner):
cairo_counter_address = get_deployments()["Counter"]["address"]
return await deploy_contract(
counter_contract = await deploy_contract(
"CairoPrecompiles",
"CairoCounterCaller",
cairo_counter_address,
caller_eoa=owner.starknet_contract,
)

await invoke(
"kakarot",
"set_authorized_cairo_precompile_caller",
int(counter_contract.address, 16),
True,
)
return counter_contract


@pytest.mark.asyncio(scope="module")
@pytest.mark.CairoPrecompiles
Expand Down Expand Up @@ -54,3 +62,14 @@ async def test_should_set_cairo_counter(
new_count = (await cairo_counter.functions["get"].call()).count

assert new_count == count

async def test_should_fail_precompile_caller_not_whitelisted(
self, deploy_contract, get_contract, max_fee
):
cairo_counter = get_contract("Counter")
cairo_counter_caller = await deploy_contract(
"CairoPrecompiles", "CairoCounterCaller", cairo_counter.address
)
with pytest.raises(Exception) as e:
await cairo_counter_caller.incrementCairoCounter(max_fee=max_fee)
assert "CairoLib: call_contract failed" in str(e.value)
6 changes: 5 additions & 1 deletion tests/src/kakarot/precompiles/test_precompiles.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,19 @@ func test__precompiles_run{
// Given
local address;
local input_len;
local caller_address;
let (local input) = alloc();
%{
ids.address = program_input["address"]
ids.input_len = len(program_input["input"])
ids.caller_address = program_input.get("caller_address", 0)
segments.write_arg(ids.input, program_input["input"])
%}

// When
let result = Precompiles.exec_precompile(evm_address=address, input_len=input_len, input=input);
let result = Precompiles.exec_precompile(
evm_address=address, input_len=input_len, input=input, caller_address=caller_address
);
let output_len = result.output_len;
let (output) = alloc();
memcpy(dst=output, src=result.output, len=output_len);
Expand Down
Loading

0 comments on commit befcb93

Please sign in to comment.