Skip to content

Commit

Permalink
Cairo v0.1.0.
Browse files Browse the repository at this point in the history
  • Loading branch information
liorgold2 committed Mar 31, 2021
1 parent 211d827 commit 8a7f04f
Show file tree
Hide file tree
Showing 139 changed files with 6,186 additions and 2,089 deletions.
Empty file removed .gitmodules
Empty file.
5 changes: 4 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
FROM ubuntu:18.04

RUN apt update
RUN apt install -y cmake python3.7 libgmp3-dev g++ python3-pip python3.7-dev npm
RUN apt install -y cmake python3.7 libgmp3-dev g++ python3-pip python3.7-dev python3.7-venv npm

COPY . /app/

Expand All @@ -15,6 +15,9 @@ RUN make all -j8
# Run tests.
RUN ctest -V

WORKDIR /app/
RUN src/starkware/cairo/lang/package_test/run_test.sh

# Build the Visual Studio Code extension.
WORKDIR /app/src/starkware/cairo/lang/ide/vscode-cairo
RUN npm install -g vsce
Expand Down
3 changes: 1 addition & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ Clone the repository and initialize the git submodules using:
```bash
> git clone [email protected]:starkware-libs/cairo-lang.git
> cd cairo-lang
> git submodule update --init
```

Build the docker image:
Expand All @@ -55,7 +54,7 @@ Once the docker image is built, you can fetch the python package zip file using:

```bash
> container_id=$(docker create cairo)
> docker cp ${container_id}:/app/cairo-lang-0.0.3.zip .
> docker cp ${container_id}:/app/cairo-lang-0.1.0.zip .
> docker rm -v ${container_id}
```

1 change: 0 additions & 1 deletion src/cmake_utils/pip_rules.cmake
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# Note: Consider having multiple pip setups (a different requirements.txt)
# Note: STAMP_FILE is a dummy output file for a target that holds all of the reqeuired file level
# dependencies of the target. If you have a custom command that needs a dependency target to run
# before, it should depend on the target and the stamp file.
Expand Down
183 changes: 73 additions & 110 deletions src/demo/amm_demo/amm.cairo
Original file line number Diff line number Diff line change
@@ -1,44 +1,42 @@
%builtins output pedersen range_check

from starkware.cairo.common.cairo_builtins import HashBuiltin
from starkware.cairo.common.dict import dict_new, dict_squash, dict_update
from starkware.cairo.common.dict import dict_new, dict_read, dict_squash, dict_update, dict_write
from starkware.cairo.common.dict_access import DictAccess
from starkware.cairo.common.hash import pedersen_hash
from starkware.cairo.common.hash import hash2
from starkware.cairo.common.math import assert_nn_le, unsigned_div_rem
from starkware.cairo.common.registers import get_fp_and_pc
from starkware.cairo.common.small_merkle_tree import small_merkle_tree

struct Account:
member public_key : felt = 0
member token_a_balance : felt = 1
member token_b_balance : felt = 2
const SIZE = 3
member public_key : felt
member token_a_balance : felt
member token_b_balance : felt
end

# The maximum amount of each token that belongs to the AMM.
const MAX_BALANCE = %[ 2**64 - 1 %]

struct AmmState:
# A dictionary that tracks the accounts' state.
member account_dict_start : DictAccess* = 0
member account_dict_end : DictAccess* = 1
member account_dict_start : DictAccess*
member account_dict_end : DictAccess*
# The amount of the tokens currently in the AMM.
# Must be in the range [0, MAX_BALANCE].
member token_a_balance : felt = 2
member token_b_balance : felt = 3
const SIZE = 4
member token_a_balance : felt
member token_b_balance : felt
end

func modify_account(range_check_ptr, state : AmmState, account_id, diff_a, diff_b) -> (
range_check_ptr, state : AmmState, key):
func modify_account{range_check_ptr}(state : AmmState, account_id, diff_a, diff_b) -> (
state : AmmState, key):
alloc_locals
local old_account : Account*
%{
# Retrieve the pointer to the current state of the
# account using the dict manager (see dict.cairo).
ids.old_account = __dict_manager.get_dict(
ids.state.account_dict_end)[ids.account_id]
%}

# Define a reference to state.account_dict_end so that we
# can use it as an implicit argument to the dict functions.
let account_dict_end = state.account_dict_end

# Retrieve the pointer to the current state of the account.
let (local old_account : Account*) = dict_read{dict_ptr=account_dict_end}(key=account_id)

# Compute the new account values.
tempvar new_token_a_balance = (
Expand All @@ -47,8 +45,8 @@ func modify_account(range_check_ptr, state : AmmState, account_id, diff_a, diff_
old_account.token_b_balance + diff_b)

# Verify that the new balances are positive.
let (range_check_ptr) = assert_nn_le(range_check_ptr, new_token_a_balance, MAX_BALANCE)
let (range_check_ptr) = assert_nn_le(range_check_ptr, new_token_b_balance, MAX_BALANCE)
assert_nn_le(new_token_a_balance, MAX_BALANCE)
assert_nn_le(new_token_b_balance, MAX_BALANCE)

# Create a new Account instance.
local new_account : Account
Expand All @@ -58,54 +56,44 @@ func modify_account(range_check_ptr, state : AmmState, account_id, diff_a, diff_

# Perform the account update.
let (__fp__, _) = get_fp_and_pc()
let (dict_ptr) = dict_update(
dict_ptr=state.account_dict_end,
key=account_id,
prev_value=cast(old_account, felt),
new_value=cast(&new_account, felt))
dict_write{dict_ptr=account_dict_end}(key=account_id, new_value=cast(&new_account, felt))

# Construct and return the new state.
local new_state : AmmState
assert new_state.account_dict_start = (
state.account_dict_start)
assert new_state.account_dict_end = dict_ptr
assert new_state.account_dict_end = account_dict_end
assert new_state.token_a_balance = state.token_a_balance
assert new_state.token_b_balance = state.token_b_balance

return (range_check_ptr=range_check_ptr, state=new_state, key=old_account.public_key)
return (state=new_state, key=old_account.public_key)
end

# Represents a swap transaction between a user and the AMM.
struct SwapTransaction:
member account_id = 0
member token_a_amount = 1
const SIZE = 2
member account_id : felt
member token_a_amount : felt
end

func swap(range_check_ptr, state : AmmState, transaction : SwapTransaction*) -> (
range_check_ptr, state : AmmState):
func swap{range_check_ptr}(state : AmmState, transaction : SwapTransaction*) -> (state : AmmState):
alloc_locals

tempvar a = transaction.token_a_amount
tempvar x = state.token_a_balance
tempvar y = state.token_b_balance

# Check that a is in range.
let (range_check_ptr) = assert_nn_le(range_check_ptr, a, MAX_BALANCE)
assert_nn_le(a, MAX_BALANCE)

# Compute the amount of token_b the user will get:
# b = (y * a) / (x + a).
let (range_check_ptr, b, _) = unsigned_div_rem(range_check_ptr, y * a, x + a)
let (b, _) = unsigned_div_rem(y * a, x + a)
# Make sure that b is also in range.
let (range_check_ptr) = assert_nn_le(range_check_ptr, b, MAX_BALANCE)
assert_nn_le(b, MAX_BALANCE)

# Update the user's account.
let (range_check_ptr, state, key) = modify_account(
range_check_ptr=range_check_ptr,
state=state,
account_id=transaction.account_id,
diff_a=-a,
diff_b=b)
let (state, key) = modify_account(
state=state, account_id=transaction.account_id, diff_a=-a, diff_b=b)

# Here you should verify the user has signed on a message
# specifying that they would like to sell 'a' tokens of
Expand All @@ -116,8 +104,8 @@ func swap(range_check_ptr, state : AmmState, transaction : SwapTransaction*) ->
# are in range.
tempvar new_x = x + a
tempvar new_y = y - b
let (range_check_ptr) = assert_nn_le(range_check_ptr, new_x, MAX_BALANCE)
let (range_check_ptr) = assert_nn_le(range_check_ptr, new_y, MAX_BALANCE)
assert_nn_le(new_x, MAX_BALANCE)
assert_nn_le(new_y, MAX_BALANCE)

# Update the state.
local new_state : AmmState
Expand All @@ -136,84 +124,71 @@ func swap(range_check_ptr, state : AmmState, transaction : SwapTransaction*) ->
f'received {ids.b} tokens of type token_b.')
%}

return (range_check_ptr=range_check_ptr, state=new_state)
return (state=new_state)
end

func transaction_loop(
range_check_ptr, state : AmmState, transactions : SwapTransaction**, n_transactions) -> (
range_check_ptr, state : AmmState):
func transaction_loop{range_check_ptr}(
state : AmmState, transactions : SwapTransaction**, n_transactions) -> (state : AmmState):
if n_transactions == 0:
return (range_check_ptr=range_check_ptr, state=state)
return (state=state)
end

let first_transaction : SwapTransaction* = [transactions]
let (range_check_ptr, state) = swap(
range_check_ptr=range_check_ptr, state=state, transaction=first_transaction)
transaction_loop(
range_check_ptr=range_check_ptr,
state=state,
transactions=transactions + 1,
n_transactions=n_transactions - 1)
return (...)
let (state) = swap(state=state, transaction=first_transaction)

return transaction_loop(
state=state, transactions=transactions + 1, n_transactions=n_transactions - 1)
end

# Returns a hash committing to the account's state using the
# following formula:
# H(H(public_key, token_a_balance), token_b_balance).
# where H is the Pedersen hash function.
func hash_account(pedersen_ptr : HashBuiltin*, account : Account*) -> (
pedersen_ptr : HashBuiltin*, res : felt):
func hash_account{pedersen_ptr : HashBuiltin*}(account : Account*) -> (res : felt):
let res = account.public_key
let (pedersen_ptr, res) = pedersen_hash(pedersen_ptr, res, account.token_a_balance)
let (pedersen_ptr, res) = pedersen_hash(pedersen_ptr, res, account.token_b_balance)
return (pedersen_ptr=pedersen_ptr, res=res)
let (res) = hash2{hash_ptr=pedersen_ptr}(res, account.token_a_balance)
let (res) = hash2{hash_ptr=pedersen_ptr}(res, account.token_b_balance)
return (res=res)
end

# For each entry in the input dict (represented by dict_start
# and dict_end) write an entry to the output dict (represented by
# hash_dict_start and hash_dict_end) after applying hash_account
# on prev_value and new_value and keeping the same key.
func hash_dict_values(
pedersen_ptr : HashBuiltin*, dict_start : DictAccess*, dict_end : DictAccess*,
hash_dict_start : DictAccess*) -> (
pedersen_ptr : HashBuiltin*, hash_dict_end : DictAccess*):
func hash_dict_values{pedersen_ptr : HashBuiltin*}(
dict_start : DictAccess*, dict_end : DictAccess*, hash_dict_start : DictAccess*) -> (
hash_dict_end : DictAccess*):
if dict_start == dict_end:
return (pedersen_ptr=pedersen_ptr, hash_dict_end=hash_dict_start)
return (hash_dict_end=hash_dict_start)
end

# Compute the hash of the account before and after the
# change.
let (pedersen_ptr, prev_hash) = hash_account(
pedersen_ptr, account=cast(dict_start.prev_value, Account*))
let (pedersen_ptr, new_hash) = hash_account(
pedersen_ptr, account=cast(dict_start.new_value, Account*))
let (prev_hash) = hash_account(account=cast(dict_start.prev_value, Account*))
let (new_hash) = hash_account(account=cast(dict_start.new_value, Account*))

# Add an entry to the output dict.
let (hash_dict_start) = dict_update(
dict_ptr=hash_dict_start, key=dict_start.key, prev_value=prev_hash, new_value=new_hash)
hash_dict_values(
pedersen_ptr=pedersen_ptr,
dict_update{dict_ptr=hash_dict_start}(
key=dict_start.key, prev_value=prev_hash, new_value=new_hash)
return hash_dict_values(
dict_start=dict_start + DictAccess.SIZE,
dict_end=dict_end,
hash_dict_start=hash_dict_start)
return (...)
end

const LOG_N_ACCOUNTS = 10

# Computes the Merkle roots before and after the batch.
# Hint argument: initial_account_dict should be a dictionary
# from account_id to an address in memory of the Account struct.
func compute_merkle_roots(pedersen_ptr : HashBuiltin*, range_check_ptr, state : AmmState) -> (
pedersen_ptr : HashBuiltin*, range_check_ptr, root_before, root_after):
func compute_merkle_roots{pedersen_ptr : HashBuiltin*, range_check_ptr}(state : AmmState) -> (
root_before, root_after):
alloc_locals

# Squash the account dictionary.
let (local range_check_ptr, squashed_dict_start, squashed_dict_end) = dict_squash(
range_check_ptr=range_check_ptr,
dict_accesses_start=state.account_dict_start,
dict_accesses_end=state.account_dict_end)
let (squashed_dict_start, squashed_dict_end) = dict_squash(
dict_accesses_start=state.account_dict_start, dict_accesses_end=state.account_dict_end)
local range_check_ptr = range_check_ptr

# Hash the dict values.
%{
Expand All @@ -232,24 +207,18 @@ func compute_merkle_roots(pedersen_ptr : HashBuiltin*, range_check_ptr, state :
token_b_balance)
%}
let (local hash_dict_start : DictAccess*) = dict_new()
let (pedersen_ptr, hash_dict_end) = hash_dict_values(
pedersen_ptr=pedersen_ptr,
let (hash_dict_end) = hash_dict_values(
dict_start=squashed_dict_start,
dict_end=squashed_dict_end,
hash_dict_start=hash_dict_start)

# Compute the two Merkle roots.
let (pedersen_ptr, root_before, root_after) = small_merkle_tree(
hash_ptr=pedersen_ptr,
let (root_before, root_after) = small_merkle_tree{hash_ptr=pedersen_ptr}(
squashed_dict_start=hash_dict_start,
squashed_dict_end=hash_dict_end,
height=LOG_N_ACCOUNTS)

return (
pedersen_ptr=pedersen_ptr,
range_check_ptr=range_check_ptr,
root_before=root_before,
root_after=root_after)
return (root_before=root_before, root_after=root_after)
end

func get_transactions() -> (transactions : SwapTransaction**, n_transactions : felt):
Expand Down Expand Up @@ -296,20 +265,18 @@ end
# The output of the AMM program.
struct AmmBatchOutput:
# The balances of the AMM before applying the batch.
member token_a_before : felt = 0
member token_b_before : felt = 1
member token_a_before : felt
member token_b_before : felt
# The balances of the AMM after applying the batch.
member token_a_after : felt = 2
member token_b_after : felt = 3
member token_a_after : felt
member token_b_after : felt
# The account Merkle roots before and after applying
# the batch.
member account_root_before : felt = 4
member account_root_after : felt = 5
const SIZE = 6
member account_root_before : felt
member account_root_after : felt
end

func main(output_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr) -> (
output_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr):
func main{output_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr}():
alloc_locals

# Create the initial state.
Expand Down Expand Up @@ -338,21 +305,17 @@ func main(output_ptr : felt*, pedersen_ptr : HashBuiltin*, range_check_ptr) -> (

# Execute the transactions.
let (transactions, n_transactions) = get_transactions()
let (range_check_ptr, state : AmmState) = transaction_loop(
range_check_ptr=range_check_ptr,
state=state,
transactions=transactions,
n_transactions=n_transactions)
let (state : AmmState) = transaction_loop(
state=state, transactions=transactions, n_transactions=n_transactions)

# Output the AMM's balances after applying the batch.
assert output.token_a_after = state.token_a_balance
assert output.token_b_after = state.token_b_balance

# Write the Merkle roots to the output.
let (pedersen_ptr, range_check_ptr, root_before, root_after) = compute_merkle_roots(
pedersen_ptr=pedersen_ptr, range_check_ptr=range_check_ptr, state=state)
let (root_before, root_after) = compute_merkle_roots(state=state)
assert output.account_root_before = root_before
assert output.account_root_after = root_after

return (output_ptr=output_ptr, pedersen_ptr=pedersen_ptr, range_check_ptr=range_check_ptr)
return ()
end
Loading

0 comments on commit 8a7f04f

Please sign in to comment.