Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

3 - Dry run entrypoint #42

Merged
merged 21 commits into from
Jul 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ run-hdp:
./tools/make/launch_cairo_files.py -run_hdp
@echo "HDP run complete."

contract-dry-run:
@echo "Compiling and running HDP..."
@echo "Total number of steps will be shown at the end of the run."
./tools/make/launch_cairo_files.py -contract_dry_run
@echo "HDP run complete."

format-cairo:
@echo "Formatting all .cairo files..."
./tools/make/format_cairo_files.sh
Expand Down
8 changes: 8 additions & 0 deletions Scarb.lock
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
# Code generated by scarb DO NOT EDIT.
version = 1

[[package]]
name = "dry_run_test"
version = "0.1.0"
dependencies = [
"hdp_cairo",
"snforge_std",
]

[[package]]
name = "hdp_cairo"
version = "0.1.0"
Expand Down
3 changes: 2 additions & 1 deletion Scarb.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ edition = "2023_11"

members = [
"cairo",
"src/contracts/simple_linear_regression"
"src/contracts/simple_linear_regression",
"src/contracts/dry_run_test"
]

[workspace.dependencies]
Expand Down
4 changes: 2 additions & 2 deletions cairo/src/lib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ pub mod memorizer;

#[derive(Serde, Drop)]
pub struct HDP {
pub header_memorizer: Memorizer
pub header_memorizer: Memorizer,
pub account_memorizer: Memorizer,
}

#[derive(Serde, Drop)]
Expand All @@ -14,5 +15,4 @@ pub struct RelocatableValue {
#[derive(Serde, Drop)]
struct Memorizer {
pub dict: RelocatableValue,
pub list: RelocatableValue,
}
1 change: 1 addition & 0 deletions cairo/src/memorizer.cairo
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
pub mod header_memorizer;
pub mod account_memorizer;
34 changes: 34 additions & 0 deletions cairo/src/memorizer/account_memorizer.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use hdp_cairo::Memorizer;
use starknet::syscalls::call_contract_syscall;
use starknet::{SyscallResult, SyscallResultTrait};

const ACCOUNT_MEMORIZER_ID: felt252 = 0x1;

const ACCOUNT_MEMORIZER_GET_BALANCE_ID: felt252 = 0x0;

#[derive(Serde, Drop)]
pub struct AccountKey {
pub chain_id: felt252,
pub block_number: felt252,
pub address: felt252,
}

#[generate_trait]
pub impl AccountMemorizerImpl of AccountMemorizerTrait {
fn get_balance(self: @Memorizer, key: AccountKey) -> u256 {
let value = call_contract_syscall(
ACCOUNT_MEMORIZER_ID.try_into().unwrap(),
ACCOUNT_MEMORIZER_GET_BALANCE_ID,
array![
*self.dict.segment_index,
*self.dict.offset,
key.chain_id,
key.block_number,
key.address,
]
.span()
)
.unwrap_syscall();
u256 { low: (*value[0]).try_into().unwrap(), high: (*value[1]).try_into().unwrap() }
}
}
26 changes: 5 additions & 21 deletions cairo/src/memorizer/header_memorizer.cairo
Original file line number Diff line number Diff line change
@@ -1,40 +1,24 @@
use hdp_cairo::Memorizer;
use starknet::syscalls::call_contract_syscall;
use starknet::{SyscallResult, SyscallResultTrait};
use core::poseidon::poseidon_hash_span;

const HEADER_MEMORIZER_ID: felt252 = 0x0;

const HEADER_MEMORIZER_GET_PARENT_ID: felt252 = 0x0;

#[derive(Serde, Drop)]
pub struct HeaderKey {
pub chain_id: felt252,
pub block_number: felt252,
}

pub trait HeaderKeyTrait {
fn derive(self: HeaderKey) -> felt252;
}

pub impl HeaderKeyImpl of HeaderKeyTrait {
fn derive(self: HeaderKey) -> felt252 {
poseidon_hash_span(array![self.chain_id, self.block_number].span())
}
}

#[generate_trait]
pub impl HeaderMemorizerImpl of HeaderMemorizerTrait {
fn get_parent(self: @Memorizer, key: HeaderKey) -> u256 {
let value = call_contract_syscall(
0x0.try_into().unwrap(),
HEADER_MEMORIZER_ID,
array![
*self.dict.segment_index,
*self.dict.offset,
*self.list.segment_index,
*self.list.offset,
key.chain_id,
key.block_number,
]
HEADER_MEMORIZER_ID.try_into().unwrap(),
HEADER_MEMORIZER_GET_PARENT_ID,
array![*self.dict.segment_index, *self.dict.offset, key.chain_id, key.block_number,]
.span()
)
.unwrap_syscall();
Expand Down
14 changes: 1 addition & 13 deletions environment.dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,6 @@ FROM python:3.9.0
SHELL ["/bin/bash", "-ci"]
WORKDIR /hdp-cairo

# Install Rust using Rustup
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y && \
echo 'export PATH="/root/.cargo/bin:$PATH"' >> /root/.bashrc

# Add Cargo executables to PATH
RUN mkdir -p /root/.local/bin && \
echo 'export PATH="/root/.local/bin:$PATH"' >> /root/.bashrc

# Install cairo1-run into PATH
RUN git clone https://github.com/HerodotusDev/cairo-vm.git && \
cd cairo-vm && git checkout aecbb3f01dacb6d3f90256c808466c2c37606252 && \
cd cairo1-run && cargo install --path .

# Copy project requirements and install them
COPY tools/make/requirements.txt tools/make/requirements.txt
RUN python -m pip install --upgrade pip && pip install -r tools/make/requirements.txt
Expand All @@ -34,6 +21,7 @@ RUN pip install .

# Compile the HDP
RUN cairo-compile --cairo_path="packages/eth_essentials" "src/hdp.cairo" --output "build/hdp.json"
RUN cairo-compile --cairo_path="packages/eth_essentials" "src/contract_dry_run.cairo" --output "build/contract_dry_run.json"

# Export HDP Program Hash
RUN cairo-hash-program --program build/hdp.json >> build/program_hash.txt
94 changes: 86 additions & 8 deletions packages/contract_bootloader/dryrun_syscall_handler.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,32 @@
from rlp import decode
from dataclasses import asdict
from typing import (
Dict,
Iterable,
Tuple,
)
from tools.py.utils import little_8_bytes_chunks_to_bytes
from starkware.cairo.lang.vm.relocatable import RelocatableValue, MaybeRelocatable
from starkware.cairo.lang.vm.memory_segments import MemorySegmentManager
from contract_bootloader.syscall_handler_base import SyscallHandlerBase
from starkware.cairo.common.dict import DictManager
from starkware.cairo.common.structs import CairoStructProxy
from tools.py.block_header import BlockHeaderDencun as Block
from starkware.cairo.lang.vm.crypto import poseidon_hash_many
from starkware.starknet.business_logic.execution.objects import (
CallResult,
)
from contract_bootloader.memorizer.memorizer import MemorizerId, Memorizer
from contract_bootloader.memorizer.account_memorizer import (
AbstractAccountMemorizerBase,
MemorizerFunctionId as AccountMemorizerFunctionId,
MemorizerKey as AccountMemorizerKey,
)
from contract_bootloader.provider.account_key_provider import (
AccountKeyEVMProvider,
)
from contract_bootloader.memorizer.header_memorizer import (
MemorizerKey as HeaderMemorizerKey,
)


class SyscallHandler(SyscallHandlerBase):
class DryRunSyscallHandler(SyscallHandlerBase):
"""
A handler for system calls; used by the BusinessLogic entry point execution.
"""
Expand All @@ -26,9 +36,13 @@ def __init__(
dict_manager: DictManager,
segments: MemorySegmentManager,
):
super().__init__(segments=segments, initial_syscall_ptr=None)
self.syscall_counter: Dict[str, int] = {}
super().__init__(
segments=segments,
initial_syscall_ptr=None,
)
self.syscall_counter: Dict[str, int] = dict()
self.dict_manager = dict_manager
self.fetch_keys_registry = list()

def set_syscall_ptr(self, syscall_ptr: RelocatableValue):
assert self._syscall_ptr is None, "syscall_ptr is already set."
Expand All @@ -49,8 +63,72 @@ def _call_contract_helper(
start_addr=request.calldata_start, end_addr=request.calldata_end
)

memorizerId = MemorizerId.from_int(request.contract_address)
if memorizerId == MemorizerId.Account:
total_size = Memorizer.size() + AccountMemorizerKey.size()

if len(calldata) != total_size:
raise ValueError(
f"Memorizer read must be initialized with a list of {total_size} integers"
)

function_id = AccountMemorizerFunctionId.from_int(request.selector)
memorizer = Memorizer(
dict_raw_ptrs=calldata[0 : Memorizer.size()],
dict_manager=self.dict_manager,
)

idx = Memorizer.size()
key = AccountMemorizerKey.from_int(
calldata[idx : idx + AccountMemorizerKey.size()]
)

handler = DryRunAccountMemorizerHandler(
memorizer=memorizer,
evm_provider_url="https://sepolia.ethereum.iosis.tech/",
)
retdata = handler.handle(function_id=function_id, key=key)

self.fetch_keys_registry.append(handler.fetch_keys_dict())

return CallResult(
gas_consumed=0,
failure_flag=0,
retdata=[],
retdata=list(retdata),
)

def clear_fetch_keys_registry(self):
self.fetch_keys_registry.clear()

def fetch_keys_dict(self) -> list[dict]:
return self.fetch_keys_registry


class DryRunAccountMemorizerHandler(AbstractAccountMemorizerBase):
def __init__(self, memorizer: Memorizer, evm_provider_url: str):
super().__init__(memorizer=memorizer)
self.evm_provider = AccountKeyEVMProvider(provider_url=evm_provider_url)
self.fetch_keys_registry: set[AccountMemorizerKey] = set()

def get_balance(self, key: AccountMemorizerKey) -> Tuple[int, int]:
self.fetch_keys_registry.add(key)
value = self.evm_provider.get_balance(key=key)
return (
value % 0x100000000000000000000000000000000,
value // 0x100000000000000000000000000000000,
)

def fetch_keys_dict(self) -> set:
def create_dict(key: AccountMemorizerKey):
data = dict()
data["key"] = key.to_dict()
if isinstance(key, HeaderMemorizerKey):
data["type"] = "HeaderMemorizerKey"
elif isinstance(key, AccountMemorizerKey):
data["type"] = "AccountMemorizerKey"
return data

dictionary = dict()
for fetch_key in list(self.fetch_keys_registry):
dictionary.update(create_dict(fetch_key))
return dictionary
12 changes: 6 additions & 6 deletions packages/contract_bootloader/execute_entry_point.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -138,14 +138,14 @@ func execute_entry_point{
local syscall_ptr: felt*;

%{
from contract_bootloader.syscall_handler import SyscallHandler

if '__dict_manager' not in globals():
from starkware.cairo.common.dict import DictManager
__dict_manager = DictManager()
if 'syscall_handler' not in globals():
from contract_bootloader.syscall_handler import SyscallHandler
if '__dict_manager' not in globals():
from starkware.cairo.common.dict import DictManager
__dict_manager = DictManager()
syscall_handler = SyscallHandler(segments=segments, dict_manager=__dict_manager)

ids.syscall_ptr = segments.add()
syscall_handler = SyscallHandler(segments=segments, dict_manager=__dict_manager)
syscall_handler.set_syscall_ptr(syscall_ptr=ids.syscall_ptr)
%}

Expand Down
Empty file.
68 changes: 68 additions & 0 deletions packages/contract_bootloader/memorizer/account_memorizer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
from web3 import Web3
from enum import Enum
from typing import List, Tuple
from abc import ABC, abstractmethod
from contract_bootloader.memorizer.memorizer import Memorizer
from marshmallow_dataclass import dataclass
from starkware.cairo.lang.vm.crypto import poseidon_hash_many


class MemorizerFunctionId(Enum):
GET_BALANCE = 0

@classmethod
def from_int(cls, value: int):
if not isinstance(value, int):
raise ValueError(f"Value must be an integer, got {type(value)}")
for member in cls:
if member.value == value:
return member
raise ValueError(f"{value} is not a valid {cls.__name__}")

@classmethod
def size(cls) -> int:
return 1


@dataclass(frozen=True)
class MemorizerKey:
chain_id: int
block_number: int
address: int

@classmethod
def from_int(cls, values: List[int]):
if len(values) != cls.size():
raise ValueError(
"MemorizerKey must be initialized with a list of three integers"
)
return cls(values[0], values[1], values[2])

def derive(self) -> int:
return poseidon_hash_many([self.chain_id, self.block_number, self.address])

def to_dict(self):
return {
"chain_id": self.chain_id,
"block_number": self.block_number,
"address": Web3.toChecksumAddress(hex(self.address)),
}

@classmethod
def size(cls) -> int:
return 3


class AbstractAccountMemorizerBase(ABC):
def __init__(self, memorizer: Memorizer):
self.memorizer = memorizer

def handle(
self, function_id: MemorizerFunctionId, key: MemorizerKey
) -> Tuple[int, int]:
if function_id == MemorizerFunctionId.GET_BALANCE:
return self.get_balance(key=key)

@abstractmethod
def get_balance(self, key: MemorizerKey) -> Tuple[int, int]:
pass
Loading
Loading