├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── contracts ├── DutchAuction.cairo └── cmp.cairo └── tests └── test_DutchAuction.py /.gitignore: -------------------------------------------------------------------------------- 1 | venv/ 2 | artifacts/ 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | share/python-wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | MANIFEST 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .nox/ 45 | .coverage 46 | .coverage.* 47 | .cache 48 | nosetests.xml 49 | coverage.xml 50 | *.cover 51 | *.py,cover 52 | .hypothesis/ 53 | .pytest_cache/ 54 | cover/ 55 | 56 | # Translations 57 | *.mo 58 | *.pot 59 | 60 | # Django stuff: 61 | *.log 62 | local_settings.py 63 | db.sqlite3 64 | db.sqlite3-journal 65 | 66 | # Flask stuff: 67 | instance/ 68 | .webassets-cache 69 | 70 | # Scrapy stuff: 71 | .scrapy 72 | 73 | # Sphinx documentation 74 | docs/_build/ 75 | 76 | # PyBuilder 77 | .pybuilder/ 78 | target/ 79 | 80 | # Jupyter Notebook 81 | .ipynb_checkpoints 82 | 83 | # IPython 84 | profile_default/ 85 | ipython_config.py 86 | 87 | # pyenv 88 | # For a library or package, you might want to ignore these files since the code is 89 | # intended to run in multiple environments; otherwise, check them in: 90 | # .python-version 91 | 92 | # pipenv 93 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 94 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 95 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 96 | # install all needed dependencies. 97 | #Pipfile.lock 98 | 99 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 100 | __pypackages__/ 101 | 102 | # Celery stuff 103 | celerybeat-schedule 104 | celerybeat.pid 105 | 106 | # SageMath parsed files 107 | *.sage.py 108 | 109 | # Environments 110 | .env 111 | .venv 112 | env/ 113 | venv/ 114 | ENV/ 115 | env.bak/ 116 | venv.bak/ 117 | 118 | # Spyder project settings 119 | .spyderproject 120 | .spyproject 121 | 122 | # Rope project settings 123 | .ropeproject 124 | 125 | # mkdocs documentation 126 | /site 127 | 128 | # mypy 129 | .mypy_cache/ 130 | .dmypy.json 131 | dmypy.json 132 | 133 | # Pyre type checker 134 | .pyre/ 135 | 136 | # pytype static type analyzer 137 | .pytype/ 138 | 139 | # Cython debug symbols 140 | cython_debug/ 141 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 exp_table 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Build and test 2 | build :; nile compile 3 | test :; pytest tests/ 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # StarkNet Playground 2 | 3 | My lil' playground. Feeling cute, might port some of my Solidity contracts here, idk. 4 | 5 | ## Getting started 6 | 7 | Clone this project: 8 | ```sh 9 | git clone https://github.com/exp-table/starknet-playground.git 10 | cd starknet-playground 11 | ``` 12 | 13 | Create a [virtualenv](https://docs.python.org/3/library/venv.html) and activate it: 14 | 15 | ```sh 16 | python3 -m venv env 17 | source env/bin/activate 18 | ``` 19 | 20 | Install `nile`: 21 | 22 | ```sh 23 | pip install cairo-nile 24 | ``` 25 | 26 | Use `nile` to quickly set up your development environment: 27 | 28 | ```sh 29 | nile init 30 | ... 31 | ✨ Cairo successfully installed! 32 | ... 33 | ✅ Dependencies successfully installed 34 | 🗄 Creating project directory tree 35 | ⛵️ Nile project ready! Try running: 36 | ``` 37 | This command creates the project directory structure and installs `cairo-lang`, `starknet-devnet`, `pytest`, and `pytest-asyncio` for you. The template includes a makefile to build the project (`make build`) and run tests (`make test`). 38 | 39 | ## A few notes regarding the contracts 40 | ### cmp.cairo 41 | Holds basic comparison operators not present in the modules starkware offers. 42 | 43 | ### DutchAuction.cairo 44 | For the moment, any logic regarding the handling of the currency used for paying is not implemented. For simplicity and elegance, we will probably let the user handles it on the contract interacting with the DutchAuction. 45 | ⚠️ Waiting for native support of `timestamp `. -------------------------------------------------------------------------------- /contracts/DutchAuction.cairo: -------------------------------------------------------------------------------- 1 | # Declare this file as a StarkNet contract and set the required 2 | # builtins. 3 | %lang starknet 4 | %builtins pedersen range_check 5 | 6 | from starkware.cairo.common.cairo_builtins import HashBuiltin 7 | from starkware.cairo.common.alloc import alloc 8 | from starkware.cairo.common.math_cmp import is_le, is_le_felt 9 | from starkware.cairo.common.math import assert_not_zero 10 | 11 | from cmp import is_ge, assert_is_ge 12 | 13 | struct Auction: 14 | member starting_price : felt 15 | member decreasing_constant : felt 16 | member starting_time : felt 17 | member duration : felt 18 | end 19 | 20 | @storage_var 21 | func _auctions(auction_id : felt) -> (res : Auction): 22 | end 23 | 24 | @external 25 | func setAuction{ 26 | syscall_ptr : felt*, 27 | pedersen_ptr : HashBuiltin*, 28 | range_check_ptr 29 | }( 30 | auction_id : felt, 31 | starting_price : felt, 32 | decreasing_constant : felt, 33 | starting_time : felt, 34 | duration : felt 35 | ): 36 | alloc_locals 37 | local new_auction: Auction = Auction(starting_price, decreasing_constant, starting_time, duration) 38 | _auctions.write(auction_id, value=new_auction) 39 | return () 40 | end 41 | 42 | @view 43 | func getAuction{ 44 | syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, 45 | range_check_ptr}(auction_id : felt) -> (auction : Auction): 46 | let (auction) = _auctions.read(auction_id) 47 | return (auction) 48 | end 49 | 50 | @view 51 | func getPrice{ 52 | syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, 53 | range_check_ptr}(auction_id : felt) -> (price : felt): 54 | alloc_locals 55 | let (auction) = _auctions.read(auction_id) 56 | let (now) = get_block_timestamp() 57 | let (bool) = is_le(now, auction.starting_time) 58 | if bool == 1: 59 | return (price=auction.starting_price) 60 | end 61 | let (bool) = is_ge(now, auction.starting_time + auction.duration) 62 | if bool == 1: 63 | return(price=auction.starting_price - auction.decreasing_constant * auction.duration) 64 | else: 65 | return (price=auction.starting_price - auction.decreasing_constant * (now - auction.starting_time)) 66 | end 67 | end 68 | 69 | @external 70 | func verifyBid{ 71 | syscall_ptr : felt*, 72 | pedersen_ptr : HashBuiltin*, 73 | range_check_ptr 74 | }(auction_id : felt) -> (price : felt): 75 | alloc_locals 76 | let (auction:Auction) = _auctions.read(auction_id) 77 | let (now) = get_block_timestamp() 78 | assert_not_zero(auction.starting_time) #check auction exists 79 | assert_is_ge(now, auction.starting_time) #check auction has started 80 | let (price) = getPrice(auction_id) 81 | # do some checks w.r.t. the price 82 | # require(msg.value >= pricePaid, "PURCHASE:INCORRECT MSG.VALUE"); 83 | # refund the difference 84 | # if (msg.value - pricePaid > 0) Address.sendValue(payable(msg.sender), msg.value-pricePaid); 85 | return (price) 86 | end 87 | 88 | # TMP HACK 89 | #################### 90 | 91 | @storage_var 92 | func _block_timestamp() -> (res: felt): 93 | end 94 | 95 | @view 96 | func get_block_timestamp{ 97 | syscall_ptr: felt*, 98 | pedersen_ptr: HashBuiltin*, 99 | range_check_ptr}() -> (block_timestamp: felt): 100 | let (res) = _block_timestamp.read() 101 | return (block_timestamp=res) 102 | end 103 | 104 | @external 105 | func set_block_timestamp{ 106 | syscall_ptr: felt*, 107 | pedersen_ptr: HashBuiltin*, 108 | range_check_ptr}(new_block_timestamp: felt): 109 | _block_timestamp.write(new_block_timestamp) 110 | return () 111 | end -------------------------------------------------------------------------------- /contracts/cmp.cairo: -------------------------------------------------------------------------------- 1 | from starkware.cairo.common.math_cmp import is_nn, is_le_felt 2 | 3 | # WARNING ! Naive implementation of >= ! shouldn't be used for production 4 | # func is_ge{range_check_ptr}(a:felt, b:felt) -> (res : felt): 5 | # let (le) = is_le_felt(a+1, b) 6 | # if le == 1: 7 | # return (res=0) 8 | # else: 9 | # return (res=1) 10 | # end 11 | # end 12 | 13 | func is_ge{range_check_ptr}(a:felt, b:felt) -> (res : felt): 14 | return is_nn(a - b) 15 | end 16 | 17 | func assert_is_ge{range_check_ptr}(a:felt, b:felt): 18 | let (flag) = is_ge(a, b) 19 | assert flag = 1 20 | return () 21 | end -------------------------------------------------------------------------------- /tests/test_DutchAuction.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | import asyncio 3 | from starkware.starknet.testing.starknet import Starknet 4 | 5 | @pytest.fixture(scope='module') 6 | def event_loop(): 7 | return asyncio.new_event_loop() 8 | 9 | @pytest.fixture(scope='module') 10 | async def dutch_factory(): 11 | starknet = await Starknet.empty() 12 | dutch = await starknet.deploy( 13 | "contracts/DutchAuction.cairo", 14 | cairo_path=["contracts"] 15 | ) 16 | return starknet, dutch 17 | 18 | 19 | # values to construct new auction 20 | starting_price, dec_constant, starting_time, duration = 200, 1, 1000, 100 21 | time_delta = 15 22 | 23 | @pytest.mark.asyncio 24 | async def test_auction_creation(dutch_factory): 25 | _, dutch = dutch_factory 26 | await dutch.setAuction(0, starting_price, dec_constant, starting_time, duration).invoke() 27 | executed_info = await dutch.getAuction(0).call() 28 | assert executed_info.result.auction == (starting_price, dec_constant, starting_time, duration) 29 | 30 | @pytest.mark.asyncio 31 | async def test_auction_started(dutch_factory): 32 | _, dutch = dutch_factory 33 | #set timestamp 34 | await dutch.set_block_timestamp(starting_time).invoke() 35 | executed_info = await dutch.getPrice(0).call() 36 | assert executed_info.result == (starting_price,) 37 | 38 | @pytest.mark.asyncio 39 | async def test_middle_auction(dutch_factory): 40 | _, dutch = dutch_factory 41 | #set timestamp 42 | await dutch.set_block_timestamp(starting_time+time_delta).invoke() 43 | executed_info = await dutch.getPrice(0).call() 44 | assert executed_info.result == (starting_price - (time_delta * dec_constant),) 45 | 46 | @pytest.mark.asyncio 47 | async def test_end_auction(dutch_factory): 48 | _, dutch = dutch_factory 49 | #set timestamp 50 | await dutch.set_block_timestamp(starting_time*2).invoke() 51 | executed_info = await dutch.getPrice(0).call() 52 | assert executed_info.result == (starting_price - (duration * dec_constant),) --------------------------------------------------------------------------------