├── chapter_9 ├── hello.txt ├── hello2.txt ├── hello3.txt ├── hello4.txt ├── sample_directory │ ├── hello.txt │ ├── hello2.txt │ ├── hello3.txt │ └── inner_directory │ │ └── hello4.txt ├── hello_big.txt ├── hash_directory.py ├── hash_big_file.py ├── create_hash_from_content.py ├── merkle_dag.py ├── test_merkle_dag_node.py └── merkle_tree.py ├── chapter_16 └── dex │ ├── ape-config.yaml │ ├── .gitignore │ ├── tests │ ├── conftest.py │ └── test_DEX.py │ └── contracts │ ├── DEX.vy │ └── HelloToken.vy ├── chapter_10 ├── decentralized_videos │ ├── videos │ │ ├── __init__.py │ │ ├── migrations │ │ │ └── __init__.py │ │ ├── admin.py │ │ ├── tests.py │ │ ├── apps.py │ │ ├── urls.py │ │ ├── templates │ │ │ └── videos │ │ │ │ ├── index.html │ │ │ │ ├── base.html │ │ │ │ ├── video.html │ │ │ │ └── upload.html │ │ ├── views.py │ │ └── models.py │ ├── decentralized_videos │ │ ├── __init__.py │ │ ├── urls.py │ │ ├── asgi.py │ │ ├── wsgi.py │ │ └── settings.py │ └── manage.py ├── videos_sharing_smart_contract │ ├── ape-config.yaml │ ├── .gitignore │ ├── tests │ │ ├── conftest.py │ │ └── test_video_sharing.py │ ├── scripts │ │ ├── deploy.py │ │ ├── bootstrap.py │ │ └── upload.py │ └── contracts │ │ └── VideoSharing.vy ├── get_unicorn.py ├── download_cat_picture.py ├── download_a_directory_of_cute_cat_picture.py └── add_file.py ├── chapter_12 └── erc20 │ ├── ape-config.yaml │ ├── .gitignore │ ├── tests │ ├── conftest.py │ └── test_VerySimpleToken.py │ └── contracts │ ├── VerySimpleToken.vy │ ├── TokenWithInflation.vy │ ├── HelloToken.vy │ └── CrowdSaleToken.vy ├── chapter_13 └── nft │ ├── ape-config.yaml │ ├── .gitignore │ ├── contracts │ ├── VerySimpleNFT.vy │ └── HelloNFT.vy │ └── tests │ ├── conftest.py │ ├── test_VerySimpleNFT.py │ └── test_HelloNFT.py ├── chapter_15 └── vault │ ├── ape-config.yaml │ ├── .gitignore │ ├── tests │ ├── test_vault.py │ ├── conftest.py │ └── test_lending.py │ └── contracts │ ├── Lending.vy │ ├── HelloToken.vy │ ├── Vault.vy │ └── LendingVault.vy ├── chapter_8 └── wallet │ ├── ape-config.yaml │ ├── .gitignore │ └── scripts │ ├── add_wallet_to_db.py │ ├── wallet_transfer.py │ ├── wallet_transfer_address_from_db.py │ └── full_wallet.py ├── chapter_17 ├── token-gated-blog │ ├── .npmrc │ ├── src │ │ ├── vite-env.d.ts │ │ ├── index.css │ │ ├── wagmi.ts │ │ ├── main.tsx │ │ └── App.tsx │ ├── README.md │ ├── vite.config.ts │ ├── biome.json │ ├── tsconfig.node.json │ ├── index.html │ ├── .gitignore │ ├── tsconfig.json │ └── package.json ├── token-gated-smart-contract │ ├── ape-config.yaml │ ├── .gitignore │ ├── scripts │ │ ├── deploy.py │ │ └── send_nft.py │ └── contracts │ │ └── HelloNFT.vy ├── sign_message.py ├── sign_in_ethereum.py └── token-gated-backend │ └── token_gated_app.py ├── chapter_6 └── voting_app │ ├── ape-config.yaml │ ├── .gitignore │ ├── tests │ ├── conftest.py │ └── test_voting_app.py │ └── contracts │ ├── VotingApp.vy │ └── DelegateVotingApp.vy ├── chapter_11 └── arbitrum │ ├── ape-config.yaml │ ├── .gitignore │ ├── contracts │ └── SimpleStorage.vy │ └── scripts │ └── deploy.py ├── chapter_14 └── nft_marketplace │ ├── ape-config.yaml │ ├── .gitignore │ ├── tests │ ├── conftest.py │ └── test_NFTMarketplace.py │ └── contracts │ ├── NFTMarketplace.vy │ └── HelloNFT.vy ├── chapter_4 ├── env_var.py ├── get_password.py ├── hello_web3.py ├── execute_retrieve.py ├── bytecode.txt ├── SimpleContract.vy ├── query_event.py ├── abi.json ├── my_keystore │ └── UTC--2023-07-08T18-06-12.161925018Z--a7603e35744ea8d88f5f66f0de7bce24f2bf8f2f ├── estimate_gas.py ├── send_eth.py ├── send_eth_on_geth.py ├── execute_store.py ├── deploy.py └── execute_donate.py ├── chapter_7 ├── voting_app │ ├── ape-config.yaml │ ├── .gitignore │ ├── scripts │ │ ├── deploy.py │ │ ├── vote.py │ │ ├── add_proposals.py │ │ ├── give_right_to_vote.py │ │ └── voting_gui_app.py │ └── contracts │ │ └── VotingApp.vy ├── hello_pyside.py ├── hello_button.py ├── simple_app.py └── complex_hello.py ├── chapter_5 └── simple_storage │ ├── ape-config.yaml │ ├── .gitignore │ ├── contracts │ └── SimpleStorage.vy │ ├── tests │ ├── conftest.py │ └── test_simple_storage.py │ └── scripts │ ├── deploy.py │ └── update_num.py ├── chapter_3 ├── Storage.vy ├── EventLogging.vy ├── EnvVar.vy ├── StorageClient.vy ├── ControlStructures.vy ├── Annotations.vy ├── DataTypes.vy └── StorageInit.vy ├── chapter_1 ├── nelsonkey.pub ├── simple_blockchain.py ├── falsify_message.py ├── nelsonkey.pem ├── validate_message.py └── verify_message.py ├── chapter_2 ├── main.py └── 1_Storage.sol ├── LICENSE ├── .gitignore └── README.md /chapter_9/hello.txt: -------------------------------------------------------------------------------- 1 | I am a good boy. 2 | -------------------------------------------------------------------------------- /chapter_9/hello2.txt: -------------------------------------------------------------------------------- 1 | I am a good girl. 2 | -------------------------------------------------------------------------------- /chapter_9/hello3.txt: -------------------------------------------------------------------------------- 1 | I am a good horse. 2 | -------------------------------------------------------------------------------- /chapter_9/hello4.txt: -------------------------------------------------------------------------------- 1 | I am a good girl. 2 | -------------------------------------------------------------------------------- /chapter_16/dex/ape-config.yaml: -------------------------------------------------------------------------------- 1 | name: DEX 2 | -------------------------------------------------------------------------------- /chapter_10/decentralized_videos/videos/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /chapter_12/erc20/ape-config.yaml: -------------------------------------------------------------------------------- 1 | name: HelloToken 2 | -------------------------------------------------------------------------------- /chapter_13/nft/ape-config.yaml: -------------------------------------------------------------------------------- 1 | name: HelloNFT 2 | -------------------------------------------------------------------------------- /chapter_15/vault/ape-config.yaml: -------------------------------------------------------------------------------- 1 | name: Lending 2 | -------------------------------------------------------------------------------- /chapter_8/wallet/ape-config.yaml: -------------------------------------------------------------------------------- 1 | name: Wallet 2 | -------------------------------------------------------------------------------- /chapter_17/token-gated-blog/.npmrc: -------------------------------------------------------------------------------- 1 | legacy-peer-deps = true -------------------------------------------------------------------------------- /chapter_6/voting_app/ape-config.yaml: -------------------------------------------------------------------------------- 1 | name: Voting App 2 | -------------------------------------------------------------------------------- /chapter_9/sample_directory/hello.txt: -------------------------------------------------------------------------------- 1 | I am a good boy. 2 | -------------------------------------------------------------------------------- /chapter_10/decentralized_videos/videos/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /chapter_11/arbitrum/ape-config.yaml: -------------------------------------------------------------------------------- 1 | name: ArbitrumContract 2 | -------------------------------------------------------------------------------- /chapter_9/sample_directory/hello2.txt: -------------------------------------------------------------------------------- 1 | I am a good girl. 2 | -------------------------------------------------------------------------------- /chapter_9/sample_directory/hello3.txt: -------------------------------------------------------------------------------- 1 | I am a good horse. 2 | -------------------------------------------------------------------------------- /chapter_10/decentralized_videos/decentralized_videos/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /chapter_14/nft_marketplace/ape-config.yaml: -------------------------------------------------------------------------------- 1 | name: NFT Marketplace 2 | -------------------------------------------------------------------------------- /chapter_17/token-gated-smart-contract/ape-config.yaml: -------------------------------------------------------------------------------- 1 | name: TokenGated 2 | -------------------------------------------------------------------------------- /chapter_4/env_var.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | print(os.environ["PRIVATE_KEY"]) 4 | -------------------------------------------------------------------------------- /chapter_9/sample_directory/inner_directory/hello4.txt: -------------------------------------------------------------------------------- 1 | I am a good girl. 2 | -------------------------------------------------------------------------------- /chapter_10/videos_sharing_smart_contract/ape-config.yaml: -------------------------------------------------------------------------------- 1 | name: VideoSharing.vy 2 | -------------------------------------------------------------------------------- /chapter_17/token-gated-blog/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /chapter_4/get_password.py: -------------------------------------------------------------------------------- 1 | from getpass import getpass 2 | 3 | password = getpass() 4 | print(password) 5 | -------------------------------------------------------------------------------- /chapter_9/hello_big.txt: -------------------------------------------------------------------------------- 1 | I am a big boy. 2 | I am a tall girl. 3 | I am a fast horse. 4 | I am a slow dragon. 5 | -------------------------------------------------------------------------------- /chapter_10/decentralized_videos/videos/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /chapter_10/decentralized_videos/videos/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /chapter_7/voting_app/ape-config.yaml: -------------------------------------------------------------------------------- 1 | name: VotingApp 2 | 3 | geth: 4 | ethereum: 5 | local: 6 | uri: http://127.0.0.1:8545 7 | -------------------------------------------------------------------------------- /chapter_5/simple_storage/ape-config.yaml: -------------------------------------------------------------------------------- 1 | name: Simple Storage 2 | 3 | geth: 4 | ethereum: 5 | local: 6 | uri: http://127.0.0.1:7545 7 | -------------------------------------------------------------------------------- /chapter_5/simple_storage/.gitignore: -------------------------------------------------------------------------------- 1 | # Ape stuff 2 | .build/ 3 | .cache/ 4 | 5 | # Python 6 | .env 7 | .venv 8 | .pytest_cache 9 | __pycache__ 10 | -------------------------------------------------------------------------------- /chapter_13/nft/.gitignore: -------------------------------------------------------------------------------- 1 | # Ape stuff 2 | .build/ 3 | .cache/ 4 | 5 | # Python 6 | .env 7 | .venv 8 | .pytest_cache 9 | .python-version 10 | __pycache__ 11 | -------------------------------------------------------------------------------- /chapter_16/dex/.gitignore: -------------------------------------------------------------------------------- 1 | # Ape stuff 2 | .build/ 3 | .cache/ 4 | 5 | # Python 6 | .env 7 | .venv 8 | .pytest_cache 9 | .python-version 10 | __pycache__ 11 | -------------------------------------------------------------------------------- /chapter_11/arbitrum/.gitignore: -------------------------------------------------------------------------------- 1 | # Ape stuff 2 | .build/ 3 | .cache/ 4 | 5 | # Python 6 | .env 7 | .venv 8 | .pytest_cache 9 | .python-version 10 | __pycache__ 11 | -------------------------------------------------------------------------------- /chapter_12/erc20/.gitignore: -------------------------------------------------------------------------------- 1 | # Ape stuff 2 | .build/ 3 | .cache/ 4 | 5 | # Python 6 | .env 7 | .venv 8 | .pytest_cache 9 | .python-version 10 | __pycache__ 11 | -------------------------------------------------------------------------------- /chapter_15/vault/.gitignore: -------------------------------------------------------------------------------- 1 | # Ape stuff 2 | .build/ 3 | .cache/ 4 | 5 | # Python 6 | .env 7 | .venv 8 | .pytest_cache 9 | .python-version 10 | __pycache__ 11 | -------------------------------------------------------------------------------- /chapter_6/voting_app/.gitignore: -------------------------------------------------------------------------------- 1 | # Ape stuff 2 | .build/ 3 | .cache/ 4 | 5 | # Python 6 | .env 7 | .venv 8 | .pytest_cache 9 | .python-version 10 | __pycache__ 11 | -------------------------------------------------------------------------------- /chapter_7/voting_app/.gitignore: -------------------------------------------------------------------------------- 1 | # Ape stuff 2 | .build/ 3 | .cache/ 4 | 5 | # Python 6 | .env 7 | .venv 8 | .pytest_cache 9 | .python-version 10 | __pycache__ 11 | -------------------------------------------------------------------------------- /chapter_8/wallet/.gitignore: -------------------------------------------------------------------------------- 1 | # Ape stuff 2 | .build/ 3 | .cache/ 4 | 5 | # Python 6 | .env 7 | .venv 8 | .pytest_cache 9 | .python-version 10 | __pycache__ 11 | -------------------------------------------------------------------------------- /chapter_14/nft_marketplace/.gitignore: -------------------------------------------------------------------------------- 1 | # Ape stuff 2 | .build/ 3 | .cache/ 4 | 5 | # Python 6 | .env 7 | .venv 8 | .pytest_cache 9 | .python-version 10 | __pycache__ 11 | -------------------------------------------------------------------------------- /chapter_17/token-gated-blog/README.md: -------------------------------------------------------------------------------- 1 | This is a [Vite](https://vitejs.dev) project bootstrapped with [`create-wagmi`](https://github.com/wevm/wagmi/tree/main/packages/create-wagmi). 2 | -------------------------------------------------------------------------------- /chapter_10/videos_sharing_smart_contract/.gitignore: -------------------------------------------------------------------------------- 1 | # Ape stuff 2 | .build/ 3 | .cache/ 4 | 5 | # Python 6 | .env 7 | .venv 8 | .pytest_cache 9 | .python-version 10 | __pycache__ 11 | -------------------------------------------------------------------------------- /chapter_17/token-gated-smart-contract/.gitignore: -------------------------------------------------------------------------------- 1 | # Ape stuff 2 | .build/ 3 | .cache/ 4 | 5 | # Python 6 | .env 7 | .venv 8 | .pytest_cache 9 | .python-version 10 | __pycache__ 11 | -------------------------------------------------------------------------------- /chapter_9/hash_directory.py: -------------------------------------------------------------------------------- 1 | from merkle_dag import MerkleDAGNode 2 | 3 | 4 | outer_directory = 'sample_directory' 5 | 6 | node = MerkleDAGNode(outer_directory) 7 | print(node) 8 | print(node.content) 9 | -------------------------------------------------------------------------------- /chapter_10/decentralized_videos/videos/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class VideosConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'videos' 7 | -------------------------------------------------------------------------------- /chapter_17/token-gated-blog/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /chapter_3/Storage.vy: -------------------------------------------------------------------------------- 1 | # @version ^0.3.0 2 | 3 | number: uint256 4 | 5 | @external 6 | def store(num: uint256): 7 | self.number = num 8 | 9 | @external 10 | def retrieve() -> uint256: 11 | return self.number 12 | -------------------------------------------------------------------------------- /chapter_3/EventLogging.vy: -------------------------------------------------------------------------------- 1 | # @version ^0.3.0 2 | 3 | event Donation: 4 | donatur: indexed(address) 5 | amount: uint256 6 | 7 | 8 | @external 9 | @payable 10 | def donate(): 11 | log Donation(msg.sender, msg.value) 12 | -------------------------------------------------------------------------------- /chapter_11/arbitrum/contracts/SimpleStorage.vy: -------------------------------------------------------------------------------- 1 | # @version ^0.3.0 2 | 3 | num: uint256 4 | 5 | @external 6 | def store(num: uint256): 7 | self.num = num 8 | 9 | @external 10 | def retrieve() -> uint256: 11 | return self.num 12 | -------------------------------------------------------------------------------- /chapter_10/decentralized_videos/decentralized_videos/urls.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.urls import include, path 3 | 4 | urlpatterns = [ 5 | path('', include('videos.urls')), 6 | path('admin/', admin.site.urls), 7 | ] 8 | -------------------------------------------------------------------------------- /chapter_5/simple_storage/contracts/SimpleStorage.vy: -------------------------------------------------------------------------------- 1 | # @version ^0.3.0 2 | 3 | num: uint256 4 | 5 | @external 6 | def store(num: uint256): 7 | self.num = num 8 | 9 | @external 10 | def retrieve() -> uint256: 11 | return self.num 12 | -------------------------------------------------------------------------------- /chapter_12/erc20/tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.fixture 5 | def deployer(accounts): 6 | return accounts[0] 7 | 8 | @pytest.fixture 9 | def contract(deployer, project): 10 | return deployer.deploy(project.VerySimpleToken) 11 | -------------------------------------------------------------------------------- /chapter_5/simple_storage/tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.fixture 5 | def deployer(accounts): 6 | return accounts[0] 7 | 8 | @pytest.fixture 9 | def contract(deployer, project): 10 | return deployer.deploy(project.SimpleStorage) 11 | -------------------------------------------------------------------------------- /chapter_3/EnvVar.vy: -------------------------------------------------------------------------------- 1 | # @version ^0.3.0 2 | 3 | donatur: address 4 | donation: uint256 5 | time: uint256 6 | 7 | 8 | @external 9 | @payable 10 | def donate(): 11 | self.donatur = msg.sender 12 | self.donation = msg.value 13 | self.time = block.timestamp 14 | -------------------------------------------------------------------------------- /chapter_10/videos_sharing_smart_contract/tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.fixture 5 | def deployer(accounts): 6 | return accounts[0] 7 | 8 | @pytest.fixture 9 | def contract(deployer, project): 10 | return deployer.deploy(project.VideoSharing) 11 | -------------------------------------------------------------------------------- /chapter_17/token-gated-blog/biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "formatter": { 3 | "enabled": true, 4 | "indentStyle": "space", 5 | "lineWidth": 120 6 | }, 7 | "linter": { 8 | "enabled": true 9 | }, 10 | "organizeImports": { 11 | "enabled": true 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /chapter_5/simple_storage/tests/test_simple_storage.py: -------------------------------------------------------------------------------- 1 | def test_retrieve(contract): 2 | num = contract.retrieve.call() 3 | assert num == 0 4 | 5 | def test_store(contract, deployer): 6 | contract.store(19, sender=deployer) 7 | num = contract.retrieve.call() 8 | assert num == 19 9 | -------------------------------------------------------------------------------- /chapter_17/token-gated-blog/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /chapter_1/nelsonkey.pub: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDQWDH4RNBs+MUglimNql0uWqYU 3 | rDvC9sC8bXRp8j2xrMfaloX2rpCfpA3+bHCCxRwi5zLPPkdyG9DlgqpsgRrjwSx6 4 | HQYj5y9SAJJMAPO2TqGlXl7Yfn/OOhrll7nk9k6SBnSokhlJxG4ev7nHlb2u5JTj 5 | vralu46OZLARYqiy/QIDAQAB 6 | -----END PUBLIC KEY----- 7 | -------------------------------------------------------------------------------- /chapter_4/hello_web3.py: -------------------------------------------------------------------------------- 1 | from web3 import Web3 2 | 3 | w3 = Web3(Web3.HTTPProvider('http://127.0.0.1:7545')) 4 | 5 | print(f"Python script is connected? {w3.is_connected()}") 6 | 7 | account_balance = w3.eth.get_balance('0x500deEDD3136cddF24CEE2e5Cf5503F4Aa83eCfa') 8 | 9 | print(f"The balance of the 3rd account is {account_balance}") 10 | -------------------------------------------------------------------------------- /chapter_10/decentralized_videos/videos/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from . import views 3 | 4 | urlpatterns = [ 5 | path('', views.index, name='index'), 6 | path('video//', views.video, name='video'), 7 | path('upload-video', views.upload, name='upload'), 8 | path('like-video', views.like, name='like'), 9 | ] 10 | -------------------------------------------------------------------------------- /chapter_10/get_unicorn.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import aioipfs 3 | 4 | async def cat_file(): 5 | client = aioipfs.AsyncIPFS() 6 | 7 | hash = 'QmY7MiYeySnsed1Z3KxqDVYuM8pfiT5gGTqprNaNhUpZgR' 8 | result = await client.cat(hash) 9 | print(result) 10 | await client.close() 11 | 12 | loop = asyncio.get_event_loop() 13 | loop.run_until_complete(cat_file()) 14 | -------------------------------------------------------------------------------- /chapter_4/execute_retrieve.py: -------------------------------------------------------------------------------- 1 | from web3 import Web3 2 | 3 | with open("abi.json") as f: 4 | abi = f.read().strip() 5 | 6 | w3 = Web3(Web3.IPCProvider('/tmp/geth.ipc')) 7 | 8 | address = "0x8bb44f25E5b25ac14c8A9f5BFAcdFd1a700bA18B" 9 | contract = w3.eth.contract(address=address, abi=abi) 10 | 11 | number = contract.functions.retrieve().call() 12 | print(number) 13 | -------------------------------------------------------------------------------- /chapter_3/StorageClient.vy: -------------------------------------------------------------------------------- 1 | # @version ^0.3.0 2 | 3 | interface Storage: 4 | def retrieve() -> uint256: view 5 | 6 | storage_contract: Storage 7 | 8 | @external 9 | def __init__(storage_address: address): 10 | self.storage_contract = Storage(storage_address) 11 | 12 | @external 13 | def call_retrieve() -> uint256: 14 | return self.storage_contract.retrieve() 15 | -------------------------------------------------------------------------------- /chapter_10/download_cat_picture.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import aioipfs 3 | 4 | async def get(): 5 | client = aioipfs.AsyncIPFS() 6 | 7 | cute_cat_picture = 'QmW2WQi7j6c7UgJTarActp7tDNikE4B2qXtFCfLPdsgaTQ/cat.jpg' 8 | await client.get(cute_cat_picture, dstdir='.') 9 | await client.close() 10 | 11 | loop = asyncio.get_event_loop() 12 | loop.run_until_complete(get()) 13 | -------------------------------------------------------------------------------- /chapter_17/token-gated-blog/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Create Wagmi 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /chapter_6/voting_app/tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.fixture 5 | def deployer(accounts): 6 | return accounts[0] 7 | 8 | @pytest.fixture 9 | def contract(deployer, project): 10 | return deployer.deploy(project.VotingApp) 11 | 12 | @pytest.fixture 13 | def delegate_contract(deployer, project): 14 | return deployer.deploy(project.DelegateVotingApp) 15 | -------------------------------------------------------------------------------- /chapter_10/download_a_directory_of_cute_cat_picture.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import aioipfs 3 | 4 | async def get(): 5 | client = aioipfs.AsyncIPFS() 6 | 7 | cute_cat_dir = 'QmW2WQi7j6c7UgJTarActp7tDNikE4B2qXtFCfLPdsgaTQ' 8 | await client.get(cute_cat_dir, dstdir='.') 9 | await client.close() 10 | 11 | loop = asyncio.get_event_loop() 12 | loop.run_until_complete(get()) 13 | -------------------------------------------------------------------------------- /chapter_4/bytecode.txt: -------------------------------------------------------------------------------- 1 | 0x6100ab61000f6000396100ab6000f36003361161000c57610093565b60003560e01c63ed88c68e8118610052576004361061009957337f5d8bc849764969eb1bcc6d0a2f55999d0167c1ccec240a4f39cf664ca9c4148e3460405260206040a2005b3461009957636057361d8118610072576024361061009957600435600055005b632e64cec1811861009157600436106100995760005460405260206040f35b505b60006000fd5b600080fda165767970657283000307000b 2 | -------------------------------------------------------------------------------- /chapter_11/arbitrum/scripts/deploy.py: -------------------------------------------------------------------------------- 1 | from ape import accounts, project 2 | import os 3 | 4 | 5 | def main(): 6 | password = os.environ["MY_PASSWORD"] 7 | dev = accounts.load("dev") 8 | dev.set_autosign(True, passphrase=password) 9 | contract = project.SimpleStorage.deploy(sender=dev) 10 | num_value = contract.retrieve.call() 11 | print(f"The num value is {num_value}") 12 | -------------------------------------------------------------------------------- /chapter_16/dex/tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.fixture 5 | def deployer(accounts): 6 | return accounts[0] 7 | 8 | @pytest.fixture 9 | def token(deployer, project): 10 | return deployer.deploy(project.HelloToken, "Hello", "HEL", 3, 100) 11 | 12 | @pytest.fixture 13 | def exchange(deployer, project, token): 14 | return deployer.deploy(project.DEX, token.address) 15 | -------------------------------------------------------------------------------- /chapter_17/token-gated-blog/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /chapter_13/nft/contracts/VerySimpleNFT.vy: -------------------------------------------------------------------------------- 1 | #pragma version ^0.3.0 2 | 3 | ownerOf: public(HashMap[uint256, address]) 4 | 5 | @external 6 | def __init__(): 7 | for i in range(10): 8 | self.ownerOf[i] = msg.sender 9 | 10 | @external 11 | def transfer(tokenId: uint256, destination: address): 12 | if self.ownerOf[tokenId] == msg.sender: 13 | self.ownerOf[tokenId] = destination 14 | -------------------------------------------------------------------------------- /chapter_4/SimpleContract.vy: -------------------------------------------------------------------------------- 1 | # @version ^0.3.0 2 | 3 | event Donation: 4 | donatur: indexed(address) 5 | amount: uint256 6 | 7 | num: uint256 8 | 9 | @external 10 | def store(num: uint256): 11 | self.num = num 12 | 13 | @external 14 | def retrieve() -> uint256: 15 | return self.num 16 | 17 | @external 18 | @payable 19 | def donate(): 20 | log Donation(msg.sender, msg.value) 21 | -------------------------------------------------------------------------------- /chapter_5/simple_storage/scripts/deploy.py: -------------------------------------------------------------------------------- 1 | from ape import accounts, project 2 | import os 3 | 4 | 5 | def main(): 6 | password = os.environ["MY_PASSWORD"] 7 | dev = accounts.load("dev") 8 | dev.set_autosign(True, passphrase=password) 9 | contract = project.SimpleStorage.deploy(sender=dev) 10 | num_value = contract.retrieve.call() 11 | print(f"The num value is {num_value}") 12 | -------------------------------------------------------------------------------- /chapter_10/add_file.py: -------------------------------------------------------------------------------- 1 | import asyncio 2 | import aioipfs 3 | 4 | async def add_file(): 5 | client = aioipfs.AsyncIPFS() 6 | 7 | files = ['hello.txt'] 8 | async for added_file in client.add(files): 9 | print('Imported file {0}, CID: {1}'.format( 10 | added_file['Name'], added_file['Hash'])) 11 | await client.close() 12 | 13 | loop = asyncio.get_event_loop() 14 | loop.run_until_complete(add_file()) 15 | -------------------------------------------------------------------------------- /chapter_12/erc20/contracts/VerySimpleToken.vy: -------------------------------------------------------------------------------- 1 | #pragma version ^0.3.0 2 | 3 | balances: public(HashMap[address, uint256]) 4 | 5 | @external 6 | def __init__(): 7 | self.balances[msg.sender] = 10000 8 | 9 | @external 10 | def transfer(_to: address, _amount: uint256) -> bool: 11 | assert self.balances[msg.sender] >= _amount 12 | self.balances[msg.sender] -= _amount 13 | self.balances[_to] += _amount 14 | return True 15 | -------------------------------------------------------------------------------- /chapter_17/token-gated-smart-contract/scripts/deploy.py: -------------------------------------------------------------------------------- 1 | from ape import accounts, project 2 | import os 3 | 4 | 5 | def main(): 6 | password = os.environ["MY_PASSWORD"] 7 | username = os.environ["ACCOUNT_USERNAME"] 8 | deployer = accounts.load(username) 9 | deployer.set_autosign(True, passphrase=password) 10 | contract = project.HelloNFT.deploy(sender=deployer) 11 | print(f"Deployed to {contract.address}") 12 | -------------------------------------------------------------------------------- /chapter_7/voting_app/scripts/deploy.py: -------------------------------------------------------------------------------- 1 | from ape import accounts, project 2 | import os 3 | 4 | 5 | def main(): 6 | password = os.environ["CHAIRPERSON_PASSWORD"] 7 | chairperson = accounts.load("chairperson") 8 | chairperson.set_autosign(True, passphrase=password) 9 | contract = project.VotingApp.deploy(sender=chairperson) 10 | chairperson = contract.chairperson() 11 | print(f"The chairperson account is {chairperson}") 12 | -------------------------------------------------------------------------------- /chapter_10/videos_sharing_smart_contract/scripts/deploy.py: -------------------------------------------------------------------------------- 1 | from ape import accounts, project 2 | import os 3 | 4 | 5 | def main(): 6 | password = os.environ["VIDEO_ACCOUNT_PASSWORD"] 7 | account = os.environ["VIDEO_ACCOUNT"] 8 | deployer = accounts.load(account) 9 | deployer.set_autosign(True, passphrase=password) 10 | contract = project.VideoSharing.deploy(sender=deployer) 11 | print(f"The contract address is {contract.address}") 12 | -------------------------------------------------------------------------------- /chapter_3/ControlStructures.vy: -------------------------------------------------------------------------------- 1 | # @version ^0.3.0 2 | 3 | @external 4 | @pure 5 | def sum() -> int128: 6 | s: int128 = 0 7 | for i in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]: 8 | s += i 9 | return s 10 | 11 | @external 12 | @pure 13 | def greet(time: String[10]) -> String[20]: 14 | if time == "morning": 15 | return "Good morning!" 16 | elif time == "evening": 17 | return "Good evening!" 18 | else: 19 | return "How are you?" 20 | -------------------------------------------------------------------------------- /chapter_9/hash_big_file.py: -------------------------------------------------------------------------------- 1 | from os import listdir 2 | from hashlib import sha256 3 | from merkle_tree import MerkleTree 4 | 5 | 6 | hashes = {} 7 | 8 | file = 'hello_big.txt' 9 | with open(file) as f: 10 | lines = f.read().split('\n') 11 | hash = [] 12 | hash_of_hash = [] 13 | merkle_tree = MerkleTree(lines) 14 | root_hash = merkle_tree.root_hash 15 | 16 | hashes[root_hash] = [] 17 | for line in lines: 18 | hashes[root_hash].append(line) 19 | 20 | print(hashes) 21 | -------------------------------------------------------------------------------- /chapter_7/voting_app/scripts/vote.py: -------------------------------------------------------------------------------- 1 | from ape import accounts, project 2 | import os 3 | 4 | 5 | def main(): 6 | password = os.environ["VOTER_PASSWORD"] 7 | address = os.environ["VOTING_APP_ADDRESS"] 8 | voter_account = os.environ["VOTER_ACCOUNT"] 9 | proposal = os.environ["PROPOSAL"] 10 | voter = accounts.load(voter_account) 11 | voter.set_autosign(True, passphrase=password) 12 | contract = project.VotingApp.at(address) 13 | contract.vote(proposal, sender=voter) 14 | -------------------------------------------------------------------------------- /chapter_4/query_event.py: -------------------------------------------------------------------------------- 1 | from web3 import Web3 2 | 3 | with open("abi.json") as f: 4 | abi = f.read().strip() 5 | 6 | w3 = Web3(Web3.IPCProvider('/tmp/geth.ipc')) 7 | 8 | address = "0x8bb44f25E5b25ac14c8A9f5BFAcdFd1a700bA18B" 9 | contract = w3.eth.contract(address=address, abi=abi) 10 | 11 | logs = contract.events.Donation().get_logs(fromBlock: 0) 12 | 13 | for log in logs: 14 | print(f"The donatur address is {log.args.donatur}") 15 | print(f"The donation amount is {log.args.amount}") 16 | -------------------------------------------------------------------------------- /chapter_7/voting_app/scripts/add_proposals.py: -------------------------------------------------------------------------------- 1 | from ape import accounts, project 2 | import os 3 | 4 | 5 | def main(): 6 | password = os.environ["CHAIRPERSON_PASSWORD"] 7 | address = os.environ["VOTING_APP_ADDRESS"] 8 | chairperson = accounts.load("chairperson") 9 | chairperson.set_autosign(True, passphrase=password) 10 | contract = project.VotingApp.at(address) 11 | contract.addProposal("beach", sender=chairperson) 12 | contract.addProposal("mountain", sender=chairperson) 13 | -------------------------------------------------------------------------------- /chapter_17/token-gated-smart-contract/scripts/send_nft.py: -------------------------------------------------------------------------------- 1 | from ape import accounts, project 2 | import os 3 | 4 | 5 | def main(): 6 | address = os.environ["NFT_ADDRESS"] 7 | destination = os.environ["NFT_RECIPIENT"] 8 | password = os.environ["MY_PASSWORD"] 9 | username = os.environ["ACCOUNT_USERNAME"] 10 | deployer = accounts.load(username) 11 | deployer.set_autosign(True, passphrase=password) 12 | contract = project.HelloNFT.at(address) 13 | contract.mint(destination, 1, sender=deployer) 14 | -------------------------------------------------------------------------------- /chapter_5/simple_storage/scripts/update_num.py: -------------------------------------------------------------------------------- 1 | from ape import accounts, project 2 | import os 3 | 4 | 5 | def main(): 6 | password = os.environ["MY_PASSWORD"] 7 | address = os.environ["SIMPLE_STORAGE_ADDRESS"] 8 | dev = accounts.load("dev") 9 | dev.set_autosign(True, passphrase=password) 10 | contract = project.SimpleStorage.at(address) 11 | contract.store(19, sender=dev) 12 | print("Updating num value") 13 | num_value = contract.retrieve.call() 14 | print(f"The num value is {num_value}") 15 | -------------------------------------------------------------------------------- /chapter_10/decentralized_videos/decentralized_videos/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for decentralized_videos project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'decentralized_videos.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /chapter_10/decentralized_videos/decentralized_videos/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for decentralized_videos project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'decentralized_videos.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /chapter_9/create_hash_from_content.py: -------------------------------------------------------------------------------- 1 | from os import listdir 2 | from hashlib import sha256 3 | 4 | 5 | files = [f for f in listdir('.') if 'hello' in f] 6 | 7 | hashes = {} 8 | 9 | for file in files: 10 | with open(file) as f: 11 | content = f.read().encode('utf-8') 12 | hash_of_content = sha256(content).hexdigest() 13 | hashes[hash_of_content] = content 14 | 15 | content = hashes['20c38a7a55fc8a8e7f45fde7247a0436d97826c20c5e7f8c978e6d59fa895fd2'] 16 | print(content.decode('utf-8')) 17 | 18 | print(len(hashes)) 19 | -------------------------------------------------------------------------------- /chapter_10/videos_sharing_smart_contract/scripts/bootstrap.py: -------------------------------------------------------------------------------- 1 | from ape import accounts, project 2 | import os 3 | 4 | 5 | def main(): 6 | address = os.environ["VIDEO_SHARING_ADDRESS"] 7 | password = os.environ["VIDEO_ACCOUNT_PASSWORD"] 8 | destination = os.environ["DESTINATION_ADDRESS"] 9 | account = os.environ["VIDEO_ACCOUNT"] 10 | deployer = accounts.load(account) 11 | deployer.set_autosign(True, passphrase=password) 12 | contract = project.VideoSharing.at(address) 13 | contract.transfer(destination, 100, sender=deployer) 14 | -------------------------------------------------------------------------------- /chapter_4/abi.json: -------------------------------------------------------------------------------- 1 | [{"name": "Donation", "inputs": [{"name": "donatur", "type": "address", "indexed": true}, {"name": "amount", "type": "uint256", "indexed": false}], "anonymous": false, "type": "event"}, {"stateMutability": "nonpayable", "type": "function", "name": "store", "inputs": [{"name": "num", "type": "uint256"}], "outputs": []}, {"stateMutability": "nonpayable", "type": "function", "name": "retrieve", "inputs": [], "outputs": [{"name": "", "type": "uint256"}]}, {"stateMutability": "payable", "type": "function", "name": "donate", "inputs": [], "outputs": []}] 2 | -------------------------------------------------------------------------------- /chapter_4/my_keystore/UTC--2023-07-08T18-06-12.161925018Z--a7603e35744ea8d88f5f66f0de7bce24f2bf8f2f: -------------------------------------------------------------------------------- 1 | {"address":"a7603e35744ea8d88f5f66f0de7bce24f2bf8f2f","crypto":{"cipher":"aes-128-ctr","ciphertext":"9c2991fd52790e9999f2c8dbb852bbacbe739a51a53e65828ca4504cfabc5a48","cipherparams":{"iv":"16fc0ef88d2b0e289f77abc37074a59f"},"kdf":"scrypt","kdfparams":{"dklen":32,"n":262144,"p":1,"r":8,"salt":"47d1369fe9e3420bc582decc82a00b1e819f86ecfcbef365a62da61e87831441"},"mac":"0e34746eac55fa4c788b590688df3f3e4c69f9f0a9f1e4b99aec44b5d8020d93"},"id":"2ca750c5-01fb-4fd8-9d20-3f69c2444afa","version":3} -------------------------------------------------------------------------------- /chapter_13/nft/tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.fixture 5 | def deployer(accounts): 6 | return accounts[0] 7 | 8 | @pytest.fixture 9 | def contract(deployer, project): 10 | return deployer.deploy(project.VerySimpleNFT) 11 | 12 | @pytest.fixture 13 | def erc721_contract(deployer, project): 14 | return deployer.deploy(project.HelloNFT) 15 | 16 | @pytest.fixture 17 | def mint_nfts(erc721_contract, deployer, num_nfts=5): 18 | for i in range(num_nfts): 19 | erc721_contract.mint(deployer, i, sender=deployer) 20 | return num_nfts 21 | -------------------------------------------------------------------------------- /chapter_13/nft/tests/test_VerySimpleNFT.py: -------------------------------------------------------------------------------- 1 | def test_ownerOf(contract, deployer): 2 | for i in range(10): 3 | assert deployer == contract.ownerOf(i) 4 | 5 | def test_transfer(contract, deployer, accounts): 6 | destination_account = accounts[1] 7 | tokenId = 2 8 | contract.transfer(tokenId, destination_account, sender=deployer) 9 | assert destination_account == contract.ownerOf(tokenId) 10 | for i in range(2): 11 | assert deployer == contract.ownerOf(i) 12 | for i in range(3, 10): 13 | assert deployer == contract.ownerOf(i) 14 | -------------------------------------------------------------------------------- /chapter_2/main.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI 2 | import os 3 | 4 | app = FastAPI() 5 | 6 | counter_file = "counter.txt" 7 | 8 | counter = 0 9 | 10 | @app.get("/") 11 | async def root(): 12 | global counter 13 | if os.path.isfile(counter_file): 14 | with open(counter_file, "r") as f: 15 | try: 16 | counter = int(f.read().strip()) 17 | except ValueError: 18 | counter = 0 19 | counter += 1 20 | with open(counter_file, "w") as f: 21 | f.write(str(counter)) 22 | return {"counter": counter} 23 | -------------------------------------------------------------------------------- /chapter_14/nft_marketplace/tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.fixture 5 | def deployer(accounts): 6 | return accounts[0] 7 | 8 | @pytest.fixture 9 | def marketplace_contract(deployer, project): 10 | return deployer.deploy(project.NFTMarketplace) 11 | 12 | @pytest.fixture 13 | def nft_contract(deployer, project): 14 | return deployer.deploy(project.HelloNFT) 15 | 16 | @pytest.fixture 17 | def mint_nfts(nft_contract, deployer, num_nfts=5): 18 | for i in range(num_nfts): 19 | nft_contract.mint(deployer, i, sender=deployer) 20 | return num_nfts 21 | -------------------------------------------------------------------------------- /chapter_17/token-gated-blog/src/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | background-color: #181818; 3 | color: rgba(255, 255, 255, 0.87); 4 | color-scheme: light dark; 5 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; 6 | font-synthesis: none; 7 | font-weight: 400; 8 | line-height: 1.5; 9 | text-rendering: optimizeLegibility; 10 | 11 | -webkit-font-smoothing: antialiased; 12 | -moz-osx-font-smoothing: grayscale; 13 | -webkit-text-size-adjust: 100%; 14 | } 15 | 16 | @media (prefers-color-scheme: light) { 17 | :root { 18 | background-color: #f8f8f8; 19 | color: #181818; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /chapter_3/Annotations.vy: -------------------------------------------------------------------------------- 1 | # @version ^0.3.0 2 | 3 | author: String[100] 4 | donatur: String[100] 5 | 6 | @external 7 | def __init__(): 8 | self.author = "Arjuna Sky Kok" 9 | 10 | @external 11 | @pure 12 | def add(x: int128, y: int128) -> int128: 13 | return x + y 14 | 15 | @external 16 | @view 17 | def get_name_and_title() -> String[200]: 18 | return concat("Mr. ", self.author) 19 | 20 | @external 21 | @nonpayable 22 | def change_name(new_name: String[100]): 23 | self.author = new_name 24 | 25 | @external 26 | @payable 27 | def donate(donatur_name: String[100]): 28 | self.donatur = donatur_name 29 | -------------------------------------------------------------------------------- /chapter_7/hello_pyside.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from PySide6 import QtCore, QtWidgets 3 | 4 | 5 | class TextWidget(QtWidgets.QWidget): 6 | def __init__(self): 7 | super().__init__() 8 | 9 | self.label = QtWidgets.QLabel("Hello Pyside") 10 | self.button = QtWidgets.QPushButton("Button") 11 | 12 | self.layout = QtWidgets.QVBoxLayout(self) 13 | self.layout.addWidget(self.label) 14 | self.layout.addWidget(self.button) 15 | 16 | 17 | if __name__ == "__main__": 18 | app = QtWidgets.QApplication([]) 19 | 20 | widget = TextWidget() 21 | widget.show() 22 | 23 | sys.exit(app.exec()) 24 | -------------------------------------------------------------------------------- /chapter_12/erc20/contracts/TokenWithInflation.vy: -------------------------------------------------------------------------------- 1 | #pragma version ^0.3.0 2 | 3 | balances: public(HashMap[address, uint256]) 4 | owner: public(address) 5 | 6 | @external 7 | def __init__(): 8 | self.balances[msg.sender] = 10000 9 | self.owner = msg.sender 10 | 11 | @external 12 | def transfer(_to: address, _amount: uint256) -> bool: 13 | assert self.balances[msg.sender] >= _amount 14 | self.balances[msg.sender] -= _amount 15 | self.balances[_to] += _amount 16 | return True 17 | 18 | @external 19 | def mint(_new_supply: uint256): 20 | assert msg.sender == self.owner 21 | self.balances[msg.sender] = _new_supply 22 | -------------------------------------------------------------------------------- /chapter_4/estimate_gas.py: -------------------------------------------------------------------------------- 1 | from web3 import Web3 2 | 3 | with open("bytecode.txt") as f: 4 | bytecode = f.read().strip() 5 | 6 | with open("abi.json") as f: 7 | abi = f.read().strip() 8 | 9 | deployer_address = "0xA7603e35744EA8d88F5F66f0dE7bcE24f2Bf8F2f" 10 | 11 | w3 = Web3(Web3.IPCProvider('/tmp/geth.ipc')) 12 | 13 | contract = w3.eth.contract(abi=abi, bytecode=bytecode) 14 | 15 | total_gas = contract.constructor().estimate_gas({ 16 | 'from': deployer_address, 17 | 'nonce': w3.eth.get_transaction_count(deployer_address), 18 | 'gas': 200000, 19 | 'gasPrice': Web3.to_wei('50', 'gwei') 20 | }) 21 | 22 | print(total_gas) 23 | -------------------------------------------------------------------------------- /chapter_12/erc20/tests/test_VerySimpleToken.py: -------------------------------------------------------------------------------- 1 | def test_balances(contract, deployer): 2 | balance = contract.balances(deployer) 3 | assert balance == 10000 4 | 5 | def test_transfer(contract, deployer, accounts): 6 | destination_account = accounts[1] 7 | balance = contract.balances(destination_account) 8 | assert balance == 0 9 | balance = contract.balances(deployer) 10 | assert balance == 10000 11 | contract.transfer(destination_account, 200, sender=deployer) 12 | balance = contract.balances(destination_account) 13 | assert balance == 200 14 | balance = contract.balances(deployer) 15 | assert balance == 9800 16 | -------------------------------------------------------------------------------- /chapter_17/token-gated-blog/src/wagmi.ts: -------------------------------------------------------------------------------- 1 | import { http, createConfig } from 'wagmi' 2 | import { mainnet, sepolia } from 'wagmi/chains' 3 | import { coinbaseWallet, injected, walletConnect } from 'wagmi/connectors' 4 | 5 | export const config = createConfig({ 6 | chains: [mainnet, sepolia], 7 | connectors: [ 8 | injected(), 9 | coinbaseWallet({ appName: 'Create Wagmi' }), 10 | walletConnect({ projectId: import.meta.env.VITE_WC_PROJECT_ID }), 11 | ], 12 | transports: { 13 | [mainnet.id]: http(), 14 | [sepolia.id]: http(), 15 | }, 16 | }) 17 | 18 | declare module 'wagmi' { 19 | interface Register { 20 | config: typeof config 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /chapter_3/DataTypes.vy: -------------------------------------------------------------------------------- 1 | # @version ^0.3.0 2 | 3 | life_is_beautiful: bool 4 | var_int1: int8 5 | var_int2: int64 6 | var_int3: int128 7 | var_int4: int256 8 | var_uint1: uint8 9 | var_uint2: uint64 10 | var_uint3: uint128 11 | var_uint4: uint256 12 | pi: decimal 13 | my_grandma_wallet: address 14 | var_byte1: bytes32 15 | var_byte2: bytes18 16 | var_bytes: Bytes[56] 17 | author: String[100] 18 | enum Direction: 19 | NORTH 20 | SOUTH 21 | WEST 22 | EAST 23 | direction: Direction 24 | my_list: int16[5] 25 | my_dynamic_array: DynArray[int128, 5] 26 | struct Permission: 27 | write: bool 28 | execute: bool 29 | my_permission: Permission 30 | donaturs: HashMap[address, uint256] 31 | -------------------------------------------------------------------------------- /chapter_2/1_Storage.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: GPL-3.0 2 | 3 | pragma solidity >=0.8.2 <0.9.0; 4 | 5 | /** 6 | * @title Storage 7 | * @dev Store & retrieve value in a variable 8 | * @custom:dev-run-script ./scripts/deploy_with_ethers.ts 9 | */ 10 | contract Storage { 11 | 12 | uint256 number; 13 | 14 | /** 15 | * @dev Store value in variable 16 | * @param num value to store 17 | */ 18 | function store(uint256 num) public { 19 | number = num; 20 | } 21 | 22 | /** 23 | * @dev Return value 24 | * @return value of 'number' 25 | */ 26 | function retrieve() public view returns (uint256){ 27 | return number; 28 | } 29 | } -------------------------------------------------------------------------------- /chapter_17/sign_message.py: -------------------------------------------------------------------------------- 1 | from ape import accounts 2 | from eth_account.messages import encode_defunct 3 | from ape.types.signatures import recover_signer 4 | import os 5 | 6 | username = os.environ["ACCOUNT_USERNAME"] 7 | account = accounts.load(username) 8 | password = os.environ["MY_PASSWORD"] 9 | account.set_autosign(True, passphrase=password) 10 | 11 | message = encode_defunct(text="Hello PacktPub!") 12 | signature = account.sign_message(message) 13 | print(f"The address that creates the signature: {account.address}") 14 | print(f"Signature: {signature}") 15 | 16 | recovered_signer = recover_signer(message, signature) 17 | print(f"The address that comes from the signature: {recovered_signer}") 18 | -------------------------------------------------------------------------------- /chapter_15/vault/tests/test_vault.py: -------------------------------------------------------------------------------- 1 | def test_init(vault_contract, deployer, token_contract): 2 | assert 0 == vault_contract.totalAssets() 3 | assert 0 == vault_contract.totalSupply() 4 | assert token_contract.address == vault_contract.asset() 5 | 6 | def test_deposit(vault_contract, depositor, token_contract, give_token): 7 | deposit_amount = 1000 8 | shares = 1000 9 | token_contract.approve(vault_contract, deposit_amount, sender=depositor) 10 | 11 | assert 0 == vault_contract.totalAssets() 12 | vault_contract.deposit(deposit_amount, sender=depositor) 13 | assert deposit_amount == vault_contract.totalAssets() 14 | assert shares == vault_contract.balanceOf(depositor) 15 | -------------------------------------------------------------------------------- /chapter_1/simple_blockchain.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import json 3 | 4 | 5 | class Block: 6 | id = None 7 | history = None 8 | parent_id = None 9 | parent_hash = None 10 | 11 | block_A = Block() 12 | block_A.id = 1 13 | block_A.history = 'Nelson likes cat' 14 | 15 | block_B = Block() 16 | block_B.id = 2 17 | block_B.history = 'Marie likes dog' 18 | block_B.parent_id = block_A.id 19 | block_B.parent_hash = hashlib.sha256(json.dumps(block_A.__dict__).encode('utf-8')).hexdigest() 20 | 21 | block_C = Block() 22 | block_C.id = 3 23 | block_C.history = 'Marie likes dog' 24 | block_C.parent_id = block_B.id 25 | block_C.parent_hash = hashlib.sha256(json.dumps(block_B.__dict__).encode('utf-8')).hexdigest() 26 | -------------------------------------------------------------------------------- /chapter_7/hello_button.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from PySide6 import QtCore, QtWidgets 3 | 4 | 5 | class ButtonWidget(QtWidgets.QWidget): 6 | def __init__(self): 7 | super().__init__() 8 | 9 | self.button = QtWidgets.QPushButton("Button") 10 | 11 | self.layout = QtWidgets.QVBoxLayout(self) 12 | self.layout.addWidget(self.button) 13 | 14 | self.button.clicked.connect(self.button_on_clicked) 15 | 16 | @QtCore.Slot() 17 | def button_on_clicked(self): 18 | print("Clicking the button") 19 | 20 | 21 | if __name__ == "__main__": 22 | app = QtWidgets.QApplication([]) 23 | 24 | widget = ButtonWidget() 25 | widget.show() 26 | 27 | sys.exit(app.exec()) 28 | -------------------------------------------------------------------------------- /chapter_7/voting_app/scripts/give_right_to_vote.py: -------------------------------------------------------------------------------- 1 | from ape import accounts, project 2 | import os 3 | 4 | 5 | def main(): 6 | password = os.environ["CHAIRPERSON_PASSWORD"] 7 | address = os.environ["VOTING_APP_ADDRESS"] 8 | voter_1 = os.environ["VOTER_1_ADDRESS"] 9 | voter_2 = os.environ["VOTER_2_ADDRESS"] 10 | voter_3 = os.environ["VOTER_3_ADDRESS"] 11 | chairperson = accounts.load("chairperson") 12 | chairperson.set_autosign(True, passphrase=password) 13 | contract = project.VotingApp.at(address) 14 | contract.giveRightToVote(voter_1, 1, sender=chairperson) 15 | contract.giveRightToVote(voter_2, 1, sender=chairperson) 16 | contract.giveRightToVote(voter_3, 1, sender=chairperson) 17 | -------------------------------------------------------------------------------- /chapter_17/token-gated-blog/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "noEmit": true, 15 | "jsx": "react-jsx", 16 | 17 | /* Linting */ 18 | "strict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": ["src"], 24 | "references": [{ "path": "./tsconfig.node.json" }] 25 | } 26 | -------------------------------------------------------------------------------- /chapter_17/token-gated-blog/src/main.tsx: -------------------------------------------------------------------------------- 1 | import { QueryClient, QueryClientProvider } from '@tanstack/react-query' 2 | import { Buffer } from 'buffer' 3 | import React from 'react' 4 | import ReactDOM from 'react-dom/client' 5 | import { WagmiProvider } from 'wagmi' 6 | 7 | import App from './App.tsx' 8 | import { config } from './wagmi.ts' 9 | 10 | import './index.css' 11 | 12 | globalThis.Buffer = Buffer 13 | 14 | const queryClient = new QueryClient() 15 | 16 | ReactDOM.createRoot(document.getElementById('root')!).render( 17 | 18 | 19 | 20 | 21 | 22 | 23 | , 24 | ) 25 | -------------------------------------------------------------------------------- /chapter_1/falsify_message.py: -------------------------------------------------------------------------------- 1 | from cryptography.hazmat.primitives import hashes 2 | from cryptography.hazmat.primitives.asymmetric import padding 3 | from cryptography.hazmat.backends import default_backend 4 | from cryptography.hazmat.primitives.asymmetric import rsa 5 | from cryptography.hazmat.primitives import serialization 6 | 7 | message = b'Nelson hates cat' 8 | signature = b'Fake Signature' 9 | 10 | with open("nelsonkey.pub", "rb") as key_file: 11 | public_key = serialization.load_pem_public_key( 12 | key_file.read(), 13 | backend=default_backend()) 14 | 15 | public_key.verify( 16 | signature, 17 | message, 18 | padding.PSS(mgf=padding.MGF1(hashes.SHA256()), 19 | salt_length=padding.PSS.MAX_LENGTH), 20 | hashes.SHA256()) 21 | -------------------------------------------------------------------------------- /chapter_10/decentralized_videos/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'decentralized_videos.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /chapter_17/token-gated-blog/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "token-gated-blog", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "lint": "biome check .", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@tanstack/react-query": "5.0.5", 14 | "react": "^18.2.0", 15 | "react-dom": "^18.2.0", 16 | "viem": "latest", 17 | "wagmi": "latest" 18 | }, 19 | "devDependencies": { 20 | "@biomejs/biome": "^1.1.2", 21 | "@types/react": "^18.2.23", 22 | "@types/react-dom": "^18.2.8", 23 | "@vitejs/plugin-react": "^4.1.0", 24 | "@wagmi/cli": "latest", 25 | "buffer": "^6.0.3", 26 | "typescript": "^5.2.2", 27 | "vite": "^4.4.9" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /chapter_3/StorageInit.vy: -------------------------------------------------------------------------------- 1 | # @version ^0.3.0 2 | 3 | my_grandma_wallet: address 4 | author: String[100] 5 | fib_list: int16[5] 6 | fib_dynamic_array: DynArray[int128, 5] 7 | struct Permission: 8 | write: bool 9 | execute: bool 10 | my_permission: Permission 11 | donaturs: HashMap[address, uint256] 12 | 13 | 14 | @external 15 | def __init__(): 16 | self.my_grandma_wallet = 0xde93510CFa39Ab92BF927399F799DbE71997Ee0b 17 | self.author = "Arjuna Sky Kok" 18 | self.fib_list = [1, 1, 2, 3, 5] 19 | self.fib_dynamic_array.append(1) 20 | self.fib_dynamic_array.append(1) 21 | self.fib_dynamic_array.append(2) 22 | self.my_permission = Permission({write: True, execute: False}) 23 | self.donaturs[self.my_grandma_wallet] = 4000000000000000000 24 | 25 | donation_target : int128 = 80000000000000000000 26 | -------------------------------------------------------------------------------- /chapter_4/send_eth.py: -------------------------------------------------------------------------------- 1 | from web3 import Web3 2 | 3 | w3 = Web3(Web3.HTTPProvider('http://127.0.0.1:7545')) 4 | 5 | sender_private_key = "0x65c82117597db8b300fa9ef72f4d487d54a5008a6305a510cc20e5824b4f02b6" 6 | 7 | sender_address = "0x500deEDD3136cddF24CEE2e5Cf5503F4Aa83eCfa" 8 | 9 | recipient_address = "0x4CA4C9dd641bd1678812338f118ba3a1fde58201" 10 | 11 | amount = Web3.to_wei(5, 'ether') 12 | 13 | transaction = { 14 | 'from': sender_address, 15 | 'to': recipient_address, 16 | 'value': amount, 17 | 'gas': 21000, 18 | 'gasPrice': Web3.to_wei('50', 'gwei'), 19 | 'nonce': w3.eth.get_transaction_count(sender_address), 20 | } 21 | 22 | signed_transaction = w3.eth.account.sign_transaction(transaction, sender_private_key) 23 | 24 | transaction_hash = w3.eth.send_raw_transaction(signed_transaction.rawTransaction) 25 | 26 | print(transaction_hash) 27 | -------------------------------------------------------------------------------- /chapter_10/decentralized_videos/videos/templates/videos/index.html: -------------------------------------------------------------------------------- 1 | {% extends "videos/base.html" %} 2 | 3 | {% block content %} 4 |
5 |
6 | {% for video in videos %} 7 | {% cycle '
' '' '' '' %} 8 |
9 |
10 |
11 |
12 | 13 |
14 |
15 | 18 |
19 |
20 | {% cycle '' '' '' '
' %} 21 | {% endfor %} 22 |
23 |
24 | {% endblock %} 25 | -------------------------------------------------------------------------------- /chapter_17/sign_in_ethereum.py: -------------------------------------------------------------------------------- 1 | from siwe import SiweMessage 2 | import siwe 3 | 4 | 5 | eip_4361_string = """ 6 | service.org wants you to sign in with your Ethereum account: 7 | 0x8e6E9F42fB6d2052cf452A50B3550e1F7A04FaD0 8 | 9 | I accept the ServiceOrg Terms of Service: https://service.org/tos 10 | 11 | URI: https://service.org/login 12 | Version: 1 13 | Chain ID: 1 14 | Nonce: 32891756 15 | Issued At: 2021-09-30T16:25:24Z 16 | Resources: 17 | - ipfs://bafybeiemxf5abjwjbikoz4mc3a3dla6ual3jsgpdr4cjr3oz3evfyavhwq/ 18 | - https://example.com/my-web2-claim.json 19 | """ 20 | message = SiweMessage.from_message(message=eip_4361_string, abnf=False) 21 | signature = "0xedb8936240893b956378822f3663d8a9c29f59ac904de206108768e5e054b4441cc673af036cef8bb90c0186017fa8db8f1551be97c176374c0a5bd2cacd27d91c" 22 | 23 | try: 24 | message.verify(signature=signature) 25 | print("Signature is valid") 26 | except siwe.VerificationError: 27 | print("Signature is invalid") 28 | -------------------------------------------------------------------------------- /chapter_7/simple_app.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from PySide6 import QtCore, QtWidgets 3 | 4 | 5 | class SimpleAppWidget(QtWidgets.QWidget): 6 | def __init__(self): 7 | super().__init__() 8 | 9 | self.label = QtWidgets.QLabel("Hello") 10 | self.textedit = QtWidgets.QTextEdit("") 11 | self.button = QtWidgets.QPushButton("Say Hello") 12 | 13 | self.layout = QtWidgets.QVBoxLayout(self) 14 | self.layout.addWidget(self.label) 15 | self.layout.addWidget(self.textedit) 16 | self.layout.addWidget(self.button) 17 | 18 | self.button.clicked.connect(self.button_on_clicked) 19 | 20 | @QtCore.Slot() 21 | def button_on_clicked(self): 22 | text = self.textedit.toPlainText() 23 | self.label.setText(f"Hello {text}") 24 | 25 | 26 | if __name__ == "__main__": 27 | app = QtWidgets.QApplication([]) 28 | 29 | widget = SimpleAppWidget() 30 | widget.show() 31 | 32 | sys.exit(app.exec()) 33 | -------------------------------------------------------------------------------- /chapter_1/nelsonkey.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBANBYMfhE0Gz4xSCW 3 | KY2qXS5aphSsO8L2wLxtdGnyPbGsx9qWhfaukJ+kDf5scILFHCLnMs8+R3Ib0OWC 4 | qmyBGuPBLHodBiPnL1IAkkwA87ZOoaVeXth+f846GuWXueT2TpIGdKiSGUnEbh6/ 5 | uceVva7klOO+tqW7jo5ksBFiqLL9AgMBAAECgYBtKJhdzKgrQBgAX8y7tbQGkmYL 6 | 1MWm+6O11d81lz7ttWosw8rCmWmaytiuIeC9E0sPmnTLkoTaa2qCpXhnSylrkc/e 7 | k0+uZ6mze+WXKfdhJWqd42tbd0Fi5RaFYhvBsIFdv8ncqzav3QNOiRxKIFgex4yv 8 | 24qhToDszxSYaolenQJBAP+BCMo0eJ+nuKEs0H/kl2O+2bKov0uM0RDQmAZt/RhS 9 | 8vZcmjq80iwr/E4FcU860L2KMaDC+kS8fFLeux200QcCQQDQv7nqoFSg/NQaTFO7 10 | diXO1SRrwY70TTT8Ecd7n037vMjMNPrhvYep460yFJQXgoJNk2teswgXKDtMbhIQ 11 | uI7bAkBp/unamPIBqd7aXnEQ3ZBAfQhrKb5SZVTGGQM4h6Cb2q2Yrsn83CCE2qqM 12 | 440iRVAbkZ0NCFYy9c7NwkD2DfE5AkEAjSfYdAuOrn3Ify/1vuGZl08Rnbv5CsHa 13 | fkkXd0S12vBBZ7S7oUIkJoFKcH7x1SPvML3q8NJnOiF+V6Rmmt/x2QJBAP84xCMz 14 | kN3mVqJYyRe8/JRmSdMpi3u30AVSiFfKfW4FKHayDPtUaoXsISkXHuabcoSlxRBb 15 | csEPtQyOiz5FHfw= 16 | -----END PRIVATE KEY----- 17 | -------------------------------------------------------------------------------- /chapter_7/complex_hello.py: -------------------------------------------------------------------------------- 1 | import sys, random 2 | from PySide6 import QtCore, QtWidgets, QtGui 3 | 4 | class MyWidget(QtWidgets.QWidget): 5 | def __init__(self): 6 | super().__init__() 7 | 8 | self.hello = ["Hallo Welt", "Hei maailma", "Hola Mundo", "Привет мир"] 9 | 10 | self.button = QtWidgets.QPushButton("Click me!") 11 | self.text = QtWidgets.QLabel("Hello World", 12 | alignment=QtCore.Qt.AlignCenter) 13 | 14 | self.layout = QtWidgets.QVBoxLayout(self) 15 | self.layout.addWidget(self.text) 16 | self.layout.addWidget(self.button) 17 | 18 | self.button.clicked.connect(self.magic) 19 | 20 | @QtCore.Slot() 21 | def magic(self): 22 | self.text.setText(random.choice(self.hello)) 23 | 24 | if __name__ == "__main__": 25 | app = QtWidgets.QApplication([]) 26 | 27 | widget = MyWidget() 28 | widget.resize(800, 600) 29 | widget.show() 30 | 31 | sys.exit(app.exec()) 32 | -------------------------------------------------------------------------------- /chapter_4/send_eth_on_geth.py: -------------------------------------------------------------------------------- 1 | from web3 import Web3 2 | 3 | w3 = Web3(Web3.IPCProvider('/tmp/geth.ipc')) 4 | 5 | with open("my_keystore/UTC--2023-07-08T18-06-12.161925018Z--a7603e35744ea8d88f5f66f0de7bce24f2bf8f2f") as keyfile: 6 | encrypted_key = keyfile.read() 7 | password = "mypassword123" 8 | sender_private_key = w3.eth.account.decrypt(encrypted_key, password) 9 | 10 | sender_address = "0xA7603e35744EA8d88F5F66f0dE7bcE24f2Bf8F2f" 11 | 12 | recipient_address = Web3.to_checksum_address("0xd30561464ee30ff007e8e1c49aa950448db485bd") 13 | 14 | amount = Web3.to_wei(2, 'ether') 15 | 16 | transaction = { 17 | 'chainId': 1337, 18 | 'from': sender_address, 19 | 'to': recipient_address, 20 | 'value': amount, 21 | 'gas': 21000, 22 | 'gasPrice': Web3.to_wei('50', 'gwei'), 23 | 'nonce': w3.eth.get_transaction_count(sender_address), 24 | } 25 | 26 | signed_transaction = w3.eth.account.sign_transaction(transaction, sender_private_key) 27 | 28 | transaction_hash = w3.eth.send_raw_transaction(signed_transaction.rawTransaction) 29 | 30 | print(transaction_hash) 31 | -------------------------------------------------------------------------------- /chapter_4/execute_store.py: -------------------------------------------------------------------------------- 1 | from web3 import Web3 2 | 3 | with open("abi.json") as f: 4 | abi = f.read().strip() 5 | 6 | w3 = Web3(Web3.IPCProvider('/tmp/geth.ipc')) 7 | 8 | account_address = "0xA7603e35744EA8d88F5F66f0dE7bcE24f2Bf8F2f" 9 | 10 | with open("my_keystore/UTC--2023-07-08T18-06-12.161925018Z--a7603e35744ea8d88f5f66f0de7bce24f2bf8f2f") as keyfile: 11 | encrypted_key = keyfile.read() 12 | password = "mypassword123" 13 | account_private_key = w3.eth.account.decrypt(encrypted_key, password) 14 | 15 | address = "0x8bb44f25E5b25ac14c8A9f5BFAcdFd1a700bA18B" 16 | contract = w3.eth.contract(address=address, abi=abi) 17 | 18 | transaction = contract.functions.store(99).build_transaction({ 19 | 'from': account_address, 20 | 'nonce': w3.eth.get_transaction_count(account_address), 21 | 'gas': 200000, 22 | 'gasPrice': Web3.to_wei('50', 'gwei') 23 | }) 24 | 25 | signed_transaction = w3.eth.account.sign_transaction(transaction, account_private_key) 26 | 27 | transaction_hash = w3.eth.send_raw_transaction(signed_transaction.rawTransaction) 28 | 29 | print(transaction_hash) 30 | -------------------------------------------------------------------------------- /chapter_4/deploy.py: -------------------------------------------------------------------------------- 1 | from web3 import Web3 2 | 3 | w3 = Web3(Web3.IPCProvider('/tmp/geth.ipc')) 4 | 5 | with open("bytecode.txt") as f: 6 | bytecode = f.read().strip() 7 | 8 | with open("abi.json") as f: 9 | abi = f.read().strip() 10 | 11 | with open("my_keystore/UTC--2023-07-08T18-06-12.161925018Z--a7603e35744ea8d88f5f66f0de7bce24f2bf8f2f") as keyfile: 12 | encrypted_key = keyfile.read() 13 | password = "mypassword123" 14 | deployer_private_key = w3.eth.account.decrypt(encrypted_key, password) 15 | 16 | deployer_address = "0xA7603e35744EA8d88F5F66f0dE7bcE24f2Bf8F2f" 17 | 18 | contract = w3.eth.contract(abi=abi, bytecode=bytecode) 19 | 20 | transaction = contract.constructor().build_transaction({ 21 | 'from': deployer_address, 22 | 'nonce': w3.eth.get_transaction_count(deployer_address), 23 | 'gas': 200000, 24 | 'gasPrice': Web3.to_wei('50', 'gwei') 25 | }) 26 | 27 | signed_transaction = w3.eth.account.sign_transaction(transaction, deployer_private_key) 28 | 29 | transaction_hash = w3.eth.send_raw_transaction(signed_transaction.rawTransaction) 30 | 31 | print(transaction_hash) 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Packt 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 | -------------------------------------------------------------------------------- /chapter_10/videos_sharing_smart_contract/scripts/upload.py: -------------------------------------------------------------------------------- 1 | from ape import accounts, project 2 | import aioipfs 3 | import asyncio 4 | import os 5 | 6 | async def add(file_path): 7 | client = aioipfs.AsyncIPFS() 8 | 9 | files = [file_path] 10 | hash = None 11 | async for added_file in client.add(files): 12 | hash = added_file['Hash'] 13 | await client.close() 14 | return hash 15 | 16 | def main(): 17 | 18 | address = os.environ["VIDEO_SHARING_ADDRESS"] 19 | password = os.environ["VIDEO_ACCOUNT_PASSWORD"] 20 | account = os.environ["VIDEO_ACCOUNT"] 21 | deployer = accounts.load(account) 22 | deployer.set_autosign(True, passphrase=password) 23 | contract = project.VideoSharing.at(address) 24 | 25 | directory = '../stock_videos' 26 | movies = os.listdir(directory) 27 | for index, movie in enumerate(movies): 28 | movie_path = directory + '/' + movie 29 | loop = asyncio.get_event_loop() 30 | ipfs_path = loop.run_until_complete(add(movie_path)) 31 | title = movie.rstrip('.mp4')[:19] 32 | contract.upload_video(ipfs_path, title, sender=deployer) 33 | -------------------------------------------------------------------------------- /chapter_4/execute_donate.py: -------------------------------------------------------------------------------- 1 | from web3 import Web3 2 | 3 | with open("abi.json") as f: 4 | abi = f.read().strip() 5 | 6 | w3 = Web3(Web3.IPCProvider('/tmp/geth.ipc')) 7 | 8 | account_address = "0xA7603e35744EA8d88F5F66f0dE7bcE24f2Bf8F2f" 9 | 10 | with open("my_keystore/UTC--2023-07-08T18-06-12.161925018Z--a7603e35744ea8d88f5f66f0de7bce24f2bf8f2f") as keyfile: 11 | encrypted_key = keyfile.read() 12 | password = "mypassword123" 13 | account_private_key = w3.eth.account.decrypt(encrypted_key, password) 14 | 15 | address = "0x8bb44f25E5b25ac14c8A9f5BFAcdFd1a700bA18B" 16 | contract = w3.eth.contract(address=address, abi=abi) 17 | 18 | transaction = contract.functions.donate().build_transaction({ 19 | 'from': account_address, 20 | 'value': Web3.to_wei('1', 'ether'), 21 | 'nonce': w3.eth.get_transaction_count(account_address), 22 | 'gas': 200000, 23 | 'gasPrice': Web3.to_wei('50', 'gwei') 24 | }) 25 | 26 | signed_transaction = w3.eth.account.sign_transaction(transaction, account_private_key) 27 | 28 | transaction_hash = w3.eth.send_raw_transaction(signed_transaction.rawTransaction) 29 | 30 | print(transaction_hash) 31 | -------------------------------------------------------------------------------- /chapter_15/vault/tests/conftest.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | @pytest.fixture 5 | def deployer(accounts): 6 | return accounts[0] 7 | 8 | @pytest.fixture 9 | def borrower(accounts): 10 | return accounts[1] 11 | 12 | @pytest.fixture 13 | def depositor(accounts): 14 | return accounts[2] 15 | 16 | @pytest.fixture 17 | def lending_contract(deployer, project, token_contract, borrower): 18 | loan_amount = 1000 19 | collateral = 1 * 10 ** 18 20 | interest = 10 21 | return deployer.deploy(project.Lending, 22 | borrower, 23 | token_contract.address, 24 | loan_amount, 25 | collateral, 26 | interest) 27 | 28 | @pytest.fixture 29 | def token_contract(deployer, project): 30 | return deployer.deploy(project.HelloToken, "Hello", "HEL", 3, 100) 31 | 32 | @pytest.fixture 33 | def vault_contract(deployer, project, token_contract): 34 | return deployer.deploy(project.Vault, token_contract.address) 35 | 36 | @pytest.fixture 37 | def give_token(token_contract, lending_contract, deployer, depositor): 38 | token_amount = 1000 39 | token_contract.transfer(lending_contract, token_amount, sender=deployer) 40 | token_contract.transfer(depositor, token_amount, sender=deployer) 41 | -------------------------------------------------------------------------------- /chapter_10/decentralized_videos/videos/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render, redirect 2 | from videos.models import videos_sharing 3 | 4 | 5 | def index(request): 6 | videos = videos_sharing.recent_videos() 7 | context = {'videos': videos} 8 | return render(request, 'videos/index.html', context) 9 | 10 | def video(request, video_user, index): 11 | video = videos_sharing.get_video(video_user, index) 12 | context = {'video': video} 13 | return render(request, 'videos/video.html', context) 14 | 15 | def upload(request): 16 | context = {} 17 | if request.POST: 18 | video_user = request.POST['video_user'] 19 | title = request.POST['title'] 20 | video_file = request.FILES['video_file'] 21 | password = request.POST['password'] 22 | videos_sharing.upload_video(video_user, password, video_file, title) 23 | context['upload_success'] = True 24 | return render(request, 'videos/upload.html', context) 25 | 26 | def like(request): 27 | video_user = request.POST['video_user'] 28 | index = int(request.POST['index']) 29 | password = request.POST['password'] 30 | video_liker = request.POST['video_liker'] 31 | videos_sharing.like_video(video_liker, password, video_user, index) 32 | return redirect('video', video_user=video_user, index=index) 33 | -------------------------------------------------------------------------------- /chapter_10/decentralized_videos/videos/templates/videos/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Decentralized Videos Sharing Application 7 | 8 | 9 | 10 | 11 |
12 |
13 |
14 |

15 | Packt Pub Decentralized Videos Sharing Application 16 |

17 | 29 |
30 |
31 |
32 | {% block content %} 33 | {% endblock %} 34 | 35 | 36 | -------------------------------------------------------------------------------- /chapter_1/validate_message.py: -------------------------------------------------------------------------------- 1 | from cryptography.hazmat.primitives import hashes 2 | from cryptography.hazmat.primitives.asymmetric import padding 3 | from cryptography.hazmat.backends import default_backend 4 | from cryptography.hazmat.primitives.asymmetric import rsa 5 | from cryptography.hazmat.primitives import serialization 6 | 7 | def fetch_public_key(user): 8 | with open(user.decode('ascii') + "key.pub", "rb") as key_file: 9 | public_key = serialization.load_pem_public_key( 10 | key_file.read(), 11 | backend=default_backend()) 12 | return public_key 13 | 14 | # Message coming from user 15 | message = b"Nelson likes cat" 16 | 17 | # Signature coming from user 18 | signature = b'\x98\xe3\xd0\xae\xbb7\x1e\x04?AP\x98\xd9\x9cs\x12\x13\x14\xe9\x03\xaa\x94\xc8\xd7\xea;\xc9\xc5\xb5~\xa8c\xe2\n\xeb\x97\xe6\xcb\x8f$\x1a\x82\xd7n4tf\xbe\xd4\xb8\xd1^L\xd0\x98J\x9d\x93\xb9$\xe1e\x87\xaa\xadV\xd0%\xa3\xa7\xb5R/\x15\x17\xd6 \xe3\x91\xa9\x1c\x90\xef\x8eNS\xa6\x92\x9f\xb4B\x8dv\xbfs{\xd2\xf6m\t\x93)\x10U\x04\xe0\x81\x97\x80T\x94\xcb\x0f\x18\xbd\x0f\xb3\xbe"\xf8\xb8U\xc5\xf7\xfb^\xd5@' 19 | 20 | user = message.split()[0].lower() 21 | # fetch public key from Nelson 22 | public_key = fetch_public_key(user) 23 | # … verify the message like before 24 | public_key.verify( 25 | signature, 26 | message, 27 | padding.PSS(mgf=padding.MGF1(hashes.SHA256()), 28 | salt_length=padding.PSS.MAX_LENGTH), 29 | hashes.SHA256()) 30 | -------------------------------------------------------------------------------- /chapter_15/vault/tests/test_lending.py: -------------------------------------------------------------------------------- 1 | def test_init(lending_contract, deployer, give_token, borrower): 2 | assert borrower == lending_contract.borrower() 3 | 4 | def test_borrow(lending_contract, token_contract, deployer, give_token, borrower): 5 | collateral = 1 * 10 ** 18 6 | approved_amount = 10000 7 | loan_amount = 1000 8 | assert lending_contract.loan_taken() == False 9 | assert token_contract.balanceOf(borrower) == 0 10 | assert lending_contract.balance == 0 11 | lending_contract.borrow(value=collateral, sender=borrower) 12 | assert lending_contract.loan_taken() == True 13 | assert token_contract.balanceOf(borrower) == loan_amount 14 | assert lending_contract.balance == collateral 15 | 16 | def test_repay(lending_contract, token_contract, deployer, give_token, borrower): 17 | collateral = 1 * 10 ** 18 18 | approved_amount = 10000 19 | loan_amount = 1000 20 | lending_contract.borrow(value=collateral, sender=borrower) 21 | interest = collateral / 10 22 | 23 | token_contract.approve(lending_contract, approved_amount, sender=borrower) 24 | assert lending_contract.loan_taken() == True 25 | assert token_contract.balanceOf(borrower) == loan_amount 26 | assert lending_contract.balance == collateral 27 | lending_contract.repay(sender=borrower) 28 | assert lending_contract.loan_taken() == False 29 | assert token_contract.balanceOf(borrower) == 0 30 | assert lending_contract.balance == interest 31 | -------------------------------------------------------------------------------- /chapter_8/wallet/scripts/add_wallet_to_db.py: -------------------------------------------------------------------------------- 1 | import sys, os, json 2 | from PySide6 import QtCore, QtWidgets 3 | from ape import accounts 4 | 5 | 6 | class AddAddressToDbWidget(QtWidgets.QWidget): 7 | 8 | home_dir = os.path.expanduser('~') 9 | wallet_db_path = f"{home_dir}/.wallet.json" 10 | 11 | def __init__(self): 12 | super().__init__() 13 | 14 | self.layout = QtWidgets.QFormLayout(self) 15 | self.save_button = QtWidgets.QPushButton("Save") 16 | self.address_field = QtWidgets.QTextEdit("") 17 | self.address_field.setFixedHeight(30) 18 | self.label_field = QtWidgets.QTextEdit("") 19 | self.label_field.setFixedHeight(30) 20 | self.layout.addRow("Address:", self.address_field) 21 | self.layout.addRow("Label:", self.label_field) 22 | self.layout.addRow("", self.save_button) 23 | 24 | self.save_button.clicked.connect(self.save_button_on_clicked) 25 | 26 | @QtCore.Slot() 27 | def save_button_on_clicked(self): 28 | address = self.address_field.toPlainText() 29 | label = self.label_field.toPlainText() 30 | if not os.path.exists(self.wallet_db_path): 31 | data = {} 32 | else: 33 | with open(self.wallet_db_path, "r") as f: 34 | data = json.load(f) 35 | data[label] = address 36 | with open(self.wallet_db_path, "w") as f: 37 | json.dump(data, f) 38 | self.close() 39 | 40 | 41 | def main(): 42 | app = QtWidgets.QApplication([]) 43 | 44 | widget = AddAddressToDbWidget() 45 | widget.show() 46 | 47 | sys.exit(app.exec()) 48 | -------------------------------------------------------------------------------- /chapter_9/merkle_dag.py: -------------------------------------------------------------------------------- 1 | from os import listdir 2 | from hashlib import sha256 3 | from os.path import isdir 4 | from pathlib import Path 5 | from merkle_tree import MerkleTree 6 | 7 | 8 | class MerkleDAGNode: 9 | 10 | def __init__(self, filepath : str): 11 | self.pointers = {} 12 | self.dirtype = isdir(filepath) 13 | self.filename = Path(filepath).name 14 | if not self.dirtype: 15 | with open(filepath) as f: 16 | self.content = f.read() 17 | self.hash = self._hash((self.filename + self.content).encode('utf-8')) 18 | else: 19 | self.content = self._iterate_directory_contents(filepath) 20 | nodes_in_str_array = list(map(lambda x: str(x), self.content)) 21 | if nodes_in_str_array: 22 | self.hash = self._hash((self.filename + MerkleTree(nodes_in_str_array).root_hash).encode('utf-8')) 23 | else: 24 | self.hash = self._hash(self.filename.encode('utf-8')) 25 | 26 | def _hash(self, data : bytes) -> bytes: 27 | return sha256(data).hexdigest() 28 | 29 | def _iterate_directory_contents(self, directory : str): 30 | nodes = [] 31 | for f in listdir(directory): 32 | merkle_dag_node = MerkleDAGNode(directory + '/' + f) 33 | nodes.append(merkle_dag_node) 34 | self.pointers[f] = merkle_dag_node 35 | return nodes 36 | 37 | def __repr__(self): 38 | return 'MerkleDAGNode: ' + self.hash + ' || ' + self.filename 39 | 40 | def __eq__(self, other): 41 | if isinstance(other, MerkleDAGNode): 42 | return self.hash == other.hash 43 | return False 44 | -------------------------------------------------------------------------------- /chapter_8/wallet/scripts/wallet_transfer.py: -------------------------------------------------------------------------------- 1 | import sys, os 2 | from PySide6 import QtCore, QtWidgets 3 | from ape import accounts 4 | 5 | 6 | class TransferWidget(QtWidgets.QWidget): 7 | username = os.environ["WALLET_USERNAME"] 8 | password = os.environ["WALLET_PASSWORD"] 9 | 10 | def __init__(self): 11 | super().__init__() 12 | 13 | eth_label = QtWidgets.QLabel("ETH") 14 | self.amount_field = QtWidgets.QTextEdit("") 15 | self.amount_field.setFixedHeight(30) 16 | amount_layout = QtWidgets.QHBoxLayout() 17 | amount_layout.addWidget(self.amount_field) 18 | amount_layout.addWidget(eth_label) 19 | 20 | self.address_field = QtWidgets.QTextEdit("") 21 | self.address_field.setFixedHeight(30) 22 | self.address_field.setPlaceholderText("Address") 23 | self.transfer_button = QtWidgets.QPushButton("Transfer") 24 | 25 | self.layout = QtWidgets.QVBoxLayout(self) 26 | self.layout.addLayout(amount_layout) 27 | self.layout.addWidget(self.address_field) 28 | self.layout.addWidget(self.transfer_button) 29 | 30 | self.transfer_button.clicked.connect(self.transfer_button_on_clicked) 31 | 32 | @QtCore.Slot() 33 | def transfer_button_on_clicked(self): 34 | address = self.address_field.toPlainText() 35 | amount = self.amount_field.toPlainText() 36 | amount = amount + "000000000000000000" 37 | wallet_account = accounts.load(self.username) 38 | wallet_account.set_autosign(True, passphrase=self.password) 39 | wallet_account.transfer(address, amount) 40 | self.close() 41 | 42 | 43 | def main(): 44 | app = QtWidgets.QApplication([]) 45 | 46 | widget = TransferWidget() 47 | widget.show() 48 | 49 | sys.exit(app.exec()) 50 | -------------------------------------------------------------------------------- /chapter_10/decentralized_videos/videos/templates/videos/video.html: -------------------------------------------------------------------------------- 1 | {% extends "videos/base.html" %} 2 | 3 | {% block content %} 4 |
5 |
6 | 12 | 16 |
17 |
18 | 19 | {{ video.aggregate_likes }} 20 |
21 |
22 |
23 |
24 | {% csrf_token %} 25 | 26 | 27 |
28 | 29 |
30 | 31 |
32 |
33 |
34 | 35 |
36 | 37 |
38 |
39 |
40 | 41 |
42 |
43 |
44 |
45 |
46 | {% endblock %} 47 | -------------------------------------------------------------------------------- /chapter_7/voting_app/scripts/voting_gui_app.py: -------------------------------------------------------------------------------- 1 | from ape import accounts, project 2 | import os, sys 3 | from PySide6 import QtWidgets, QtCore 4 | 5 | 6 | class VotingWidget(QtWidgets.QWidget): 7 | def __init__(self): 8 | super().__init__() 9 | 10 | self.label = QtWidgets.QLabel("Voting Blockchain App") 11 | 12 | self.combobox = QtWidgets.QComboBox() 13 | self.combobox.addItems(["---"]) 14 | 15 | self.button = QtWidgets.QPushButton("Vote") 16 | 17 | self.layout = QtWidgets.QVBoxLayout(self) 18 | self.layout.addWidget(self.label) 19 | self.layout.addWidget(self.combobox) 20 | self.layout.addWidget(self.button) 21 | 22 | self.button.clicked.connect(self.button_on_clicked) 23 | 24 | @QtCore.Slot() 25 | def button_on_clicked(self): 26 | proposal = self.combobox.currentText() 27 | index = self.combobox.currentIndex() 28 | print(f"The selected proposal is {proposal}") 29 | print(f"The selected index is {index}") 30 | 31 | password = os.environ["VOTER_PASSWORD"] 32 | address = os.environ["VOTING_APP_ADDRESS"] 33 | voter_account = os.environ["VOTER_ACCOUNT"] 34 | voter = accounts.load(voter_account) 35 | voter.set_autosign(True, passphrase=password) 36 | 37 | contract = project.VotingApp.at(address) 38 | contract.vote(index-1, sender=voter) 39 | 40 | 41 | def main(): 42 | voting_app_address = os.environ["VOTING_APP_ADDRESS"] 43 | contract = project.VotingApp.at(voting_app_address) 44 | amount_proposals = contract.amountProposals() 45 | 46 | app = QtWidgets.QApplication([]) 47 | 48 | widget = VotingWidget() 49 | widget.show() 50 | 51 | for i in range(amount_proposals): 52 | proposal_name = contract.proposals(i).name 53 | widget.combobox.addItem(proposal_name) 54 | 55 | sys.exit(app.exec()) 56 | -------------------------------------------------------------------------------- /chapter_1/verify_message.py: -------------------------------------------------------------------------------- 1 | from cryptography.hazmat.primitives import hashes 2 | from cryptography.hazmat.primitives.asymmetric import padding 3 | from cryptography.hazmat.backends import default_backend 4 | from cryptography.hazmat.primitives.asymmetric import rsa 5 | from cryptography.hazmat.primitives import serialization 6 | 7 | # Configuration 8 | GENERATE_PRIVATE_KEY = False 9 | DERIVE_PUBLIC_KEY_FROM_PRIVATE_KEY = False 10 | PRIVATE_KEY_FILE = "nelsonkey.pem" 11 | PUBLIC_KEY_FILE = "nelsonkey.pub" 12 | MESSAGE = b"Nelson likes cat" 13 | 14 | if GENERATE_PRIVATE_KEY: 15 | # Generate private key 16 | private_key = rsa.generate_private_key( 17 | public_exponent=65537, 18 | key_size=2048, 19 | backend=default_backend() 20 | ) 21 | else: 22 | # Load private key from pem file 23 | with open(PRIVATE_KEY_FILE, "rb") as key_file: 24 | private_key = serialization.load_pem_private_key( 25 | key_file.read(), 26 | password=None, 27 | backend=default_backend() 28 | ) 29 | 30 | signature = private_key.sign( 31 | MESSAGE, 32 | padding.PSS( 33 | mgf=padding.MGF1(hashes.SHA256()), 34 | salt_length=padding.PSS.MAX_LENGTH 35 | ), 36 | hashes.SHA256() 37 | ) 38 | 39 | 40 | if DERIVE_PUBLIC_KEY_FROM_PRIVATE_KEY: 41 | # Getting public key from private key 42 | public_key = private_key.public_key() 43 | else: 44 | # Load public key from file 45 | with open(PUBLIC_KEY_FILE, "rb") as key_file: 46 | public_key = serialization.load_pem_public_key( 47 | key_file.read(), 48 | backend=default_backend() 49 | ) 50 | 51 | public_key.verify( 52 | signature, 53 | MESSAGE, 54 | padding.PSS( 55 | mgf=padding.MGF1(hashes.SHA256()), 56 | salt_length=padding.PSS.MAX_LENGTH 57 | ), 58 | hashes.SHA256() 59 | ) 60 | 61 | print(signature) 62 | -------------------------------------------------------------------------------- /chapter_6/voting_app/contracts/VotingApp.vy: -------------------------------------------------------------------------------- 1 | # @version ^0.3.0 2 | 3 | struct Voter: 4 | weight: uint256 5 | voted: bool 6 | vote: uint256 7 | 8 | struct Proposal: 9 | name: String[100] 10 | voteCount: uint256 11 | 12 | voters: public(HashMap[address, Voter]) 13 | proposals: public(HashMap[uint256, Proposal]) 14 | voterCount: public(uint256) 15 | chairperson: public(address) 16 | amountProposals: public(uint256) 17 | 18 | MAX_NUM_PROPOSALS: constant(uint256) = 3 19 | 20 | @external 21 | def __init__(): 22 | self.chairperson = msg.sender 23 | 24 | @external 25 | def addProposal(_proposalName: String[100]): 26 | assert msg.sender == self.chairperson 27 | i: uint256 = self.amountProposals 28 | self.proposals[i] = Proposal({ 29 | name: _proposalName, 30 | voteCount: 0 31 | }) 32 | self.amountProposals += 1 33 | 34 | @external 35 | def giveRightToVote(voter: address, _weight: uint256): 36 | assert msg.sender == self.chairperson 37 | assert not self.voters[voter].voted 38 | assert self.voters[voter].weight == 0 39 | self.voters[voter].weight = _weight 40 | self.voterCount += 1 41 | 42 | @external 43 | def vote(proposal: uint256): 44 | assert not self.voters[msg.sender].voted 45 | assert proposal < self.amountProposals 46 | 47 | self.voters[msg.sender].vote = proposal 48 | self.voters[msg.sender].voted = True 49 | 50 | self.proposals[proposal].voteCount += self.voters[msg.sender].weight 51 | self.voters[msg.sender].weight = 0 52 | 53 | @view 54 | @internal 55 | def _winningProposal() -> uint256: 56 | winning_vote_count: uint256 = 0 57 | winning_proposal: uint256 = 0 58 | for i in range(MAX_NUM_PROPOSALS): 59 | if self.proposals[i].voteCount > winning_vote_count: 60 | winning_vote_count = self.proposals[i].voteCount 61 | winning_proposal = i 62 | return winning_proposal 63 | 64 | @view 65 | @external 66 | def winningProposal() -> uint256: 67 | return self._winningProposal() 68 | 69 | @view 70 | @external 71 | def winnerName() -> String[100]: 72 | return self.proposals[self._winningProposal()].name 73 | -------------------------------------------------------------------------------- /chapter_7/voting_app/contracts/VotingApp.vy: -------------------------------------------------------------------------------- 1 | # @version ^0.3.0 2 | 3 | struct Voter: 4 | weight: uint256 5 | voted: bool 6 | vote: uint256 7 | 8 | struct Proposal: 9 | name: String[100] 10 | voteCount: uint256 11 | 12 | voters: public(HashMap[address, Voter]) 13 | proposals: public(HashMap[uint256, Proposal]) 14 | voterCount: public(uint256) 15 | chairperson: public(address) 16 | amountProposals: public(uint256) 17 | 18 | MAX_NUM_PROPOSALS: constant(uint256) = 3 19 | 20 | @external 21 | def __init__(): 22 | self.chairperson = msg.sender 23 | 24 | @external 25 | def addProposal(_proposalName: String[100]): 26 | assert msg.sender == self.chairperson 27 | i: uint256 = self.amountProposals 28 | self.proposals[i] = Proposal({ 29 | name: _proposalName, 30 | voteCount: 0 31 | }) 32 | self.amountProposals += 1 33 | 34 | @external 35 | def giveRightToVote(voter: address, _weight: uint256): 36 | assert msg.sender == self.chairperson 37 | assert not self.voters[voter].voted 38 | assert self.voters[voter].weight == 0 39 | self.voters[voter].weight = _weight 40 | self.voterCount += 1 41 | 42 | @external 43 | def vote(proposal: uint256): 44 | assert not self.voters[msg.sender].voted 45 | assert proposal < self.amountProposals 46 | 47 | self.voters[msg.sender].vote = proposal 48 | self.voters[msg.sender].voted = True 49 | 50 | self.proposals[proposal].voteCount += self.voters[msg.sender].weight 51 | self.voters[msg.sender].weight = 0 52 | 53 | @view 54 | @internal 55 | def _winningProposal() -> uint256: 56 | winning_vote_count: uint256 = 0 57 | winning_proposal: uint256 = 0 58 | for i in range(MAX_NUM_PROPOSALS): 59 | if self.proposals[i].voteCount > winning_vote_count: 60 | winning_vote_count = self.proposals[i].voteCount 61 | winning_proposal = i 62 | return winning_proposal 63 | 64 | @view 65 | @external 66 | def winningProposal() -> uint256: 67 | return self._winningProposal() 68 | 69 | @view 70 | @external 71 | def winnerName() -> String[100]: 72 | return self.proposals[self._winningProposal()].name 73 | -------------------------------------------------------------------------------- /chapter_15/vault/contracts/Lending.vy: -------------------------------------------------------------------------------- 1 | #pragma version ^0.3.0 2 | 3 | event Lend: 4 | _borrower: indexed(address) 5 | _amount: indexed(uint256) 6 | 7 | event Repay: 8 | _borrower: indexed(address) 9 | _amount: indexed(uint256) 10 | 11 | interface ERC20_Interface: 12 | def transfer(_recipient: address, _amount: uint256): nonpayable 13 | def transferFrom(_sender: address, _recipient: address, _amount: uint256): nonpayable 14 | 15 | token: public(address) 16 | loan_amount: public(uint256) 17 | collateral: public(uint256) 18 | borrower: public(address) 19 | loan_taken: public(bool) 20 | interest: public(uint256) 21 | owner: public(address) 22 | 23 | @external 24 | def __init__(_borrower: address, 25 | _token: address, 26 | _loan_amount: uint256, 27 | _collateral: uint256, 28 | _interest: uint256): 29 | self.token = _token 30 | self.loan_amount = _loan_amount 31 | self.collateral = _collateral 32 | self.borrower = _borrower 33 | self.interest = _interest 34 | self.owner = msg.sender 35 | 36 | @external 37 | @payable 38 | def borrow(): 39 | assert msg.sender == self.borrower, "Only the borrower can borrow the asset" 40 | assert msg.value == self.collateral, "Collateral is not enough" 41 | ERC20_Interface(self.token).transfer(msg.sender, self.loan_amount) 42 | self.loan_taken = True 43 | 44 | log Lend(msg.sender, self.loan_amount) 45 | 46 | @external 47 | def repay(): 48 | assert msg.sender == self.borrower, "Only the borrower can repay the loan" 49 | assert self.loan_taken == True, "Loan has not been taken" 50 | 51 | cut: uint256 = self.interest * self.collateral / 100 52 | payback: uint256 = self.collateral - cut 53 | 54 | ERC20_Interface(self.token).transferFrom(msg.sender, self, self.loan_amount) 55 | send(msg.sender, payback) 56 | 57 | self.loan_taken = False 58 | 59 | log Repay(msg.sender, self.loan_amount) 60 | 61 | @external 62 | def withdraw_eth(): 63 | assert msg.sender == self.owner, "Only the owner can withdraw ETH" 64 | assert self.loan_taken == False, "Cannot withdraw while loan is active" 65 | send(msg.sender, self.balance) 66 | -------------------------------------------------------------------------------- /chapter_9/test_merkle_dag_node.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from merkle_dag import MerkleDAGNode 3 | 4 | 5 | outer_directory = 'sample_directory' 6 | file1 = 'hello.txt' 7 | file2 = 'hello2.txt' 8 | file3 = 'hello3.txt' 9 | inner_directory = 'inner_directory' 10 | inner_file = 'hello4.txt' 11 | 12 | 13 | class TestMerkleDAGNode(unittest.TestCase): 14 | 15 | def test_file_node(self): 16 | file_node = MerkleDAGNode(outer_directory + '/' + file1) 17 | self.assertEqual(file_node.filename, file1) 18 | self.assertEqual(file_node.hash, '8ced218a323755a7d4969187449177bb2338658c354c7174e21285b579ae2bca') 19 | self.assertEqual(file_node.content, 'I am a good boy.\n') 20 | 21 | def test_directory_with_single_file_node(self): 22 | dir_node = MerkleDAGNode(outer_directory + '/' + inner_directory) 23 | self.assertEqual(dir_node.filename, inner_directory) 24 | self.assertEqual(dir_node.hash, 'c075280aef64223bd38b1bed1017599852180a37baa0eacce28bb92ac5492eb9') 25 | content_of_directory = [ 26 | MerkleDAGNode(outer_directory + '/' + inner_directory + '/' + inner_file), 27 | ] 28 | self.assertEqual(dir_node.content[0], content_of_directory[0]) 29 | 30 | def test_directory_with_multiple_files_and_single_directory_node(self): 31 | dir_node = MerkleDAGNode(outer_directory) 32 | self.assertEqual(dir_node.filename, outer_directory) 33 | self.assertEqual(dir_node.hash, 'ec618189b9de0dae250ab5fa0fd9bf1abc158935c66ff8595446f5f9b929e037') 34 | content_of_directory = [ 35 | MerkleDAGNode(outer_directory + '/' + file1), 36 | MerkleDAGNode(outer_directory + '/' + file2), 37 | MerkleDAGNode(outer_directory + '/' + file3), 38 | MerkleDAGNode(outer_directory + '/' + inner_directory) 39 | ] 40 | self.assertEqual(dir_node.pointers[file1], content_of_directory[0]) 41 | self.assertEqual(dir_node.pointers[file2], content_of_directory[1]) 42 | self.assertEqual(dir_node.pointers[file3], content_of_directory[2]) 43 | self.assertEqual(dir_node.pointers[inner_directory], content_of_directory[3]) 44 | 45 | 46 | if __name__ == '__main__': 47 | unittest.main() 48 | -------------------------------------------------------------------------------- /chapter_8/wallet/scripts/wallet_transfer_address_from_db.py: -------------------------------------------------------------------------------- 1 | import sys, os, json 2 | from PySide6 import QtCore, QtWidgets 3 | from ape import accounts 4 | 5 | 6 | class TransferWidget(QtWidgets.QWidget): 7 | username = os.environ["WALLET_USERNAME"] 8 | password = os.environ["WALLET_PASSWORD"] 9 | home_dir = os.path.expanduser('~') 10 | wallet_db_path = f"{home_dir}/.wallet.json" 11 | 12 | def __init__(self): 13 | super().__init__() 14 | 15 | eth_label = QtWidgets.QLabel("ETH") 16 | self.amount_field = QtWidgets.QTextEdit("") 17 | self.amount_field.setFixedHeight(30) 18 | amount_layout = QtWidgets.QHBoxLayout() 19 | amount_layout.addWidget(self.amount_field) 20 | amount_layout.addWidget(eth_label) 21 | 22 | self.address_combobox = QtWidgets.QComboBox() 23 | if not os.path.exists(self.wallet_db_path): 24 | data = {} 25 | else: 26 | with open(self.wallet_db_path, "r") as f: 27 | data = json.load(f) 28 | options = ["---"] 29 | for k,v in data.items(): 30 | options.append(k + " - " + v) 31 | self.address_combobox.addItems(options) 32 | 33 | self.transfer_button = QtWidgets.QPushButton("Transfer") 34 | self.transfer_button.clicked.connect(self.transfer_button_on_clicked) 35 | 36 | self.layout = QtWidgets.QVBoxLayout(self) 37 | self.layout.addLayout(amount_layout) 38 | self.layout.addWidget(self.address_combobox) 39 | self.layout.addWidget(self.transfer_button) 40 | 41 | @QtCore.Slot() 42 | def transfer_button_on_clicked(self): 43 | label_address = self.address_combobox.currentText() 44 | if label_address == "---": 45 | return 46 | label, address = label_address.split(" - ") 47 | amount = self.amount_field.toPlainText() 48 | amount = amount + "000000000000000000" 49 | wallet_account = accounts.load(self.username) 50 | wallet_account.set_autosign(True, passphrase=self.password) 51 | wallet_account.transfer(address, amount) 52 | self.close() 53 | 54 | 55 | def main(): 56 | app = QtWidgets.QApplication([]) 57 | 58 | widget = TransferWidget() 59 | widget.show() 60 | 61 | sys.exit(app.exec()) 62 | -------------------------------------------------------------------------------- /chapter_14/nft_marketplace/tests/test_NFTMarketplace.py: -------------------------------------------------------------------------------- 1 | from ape.exceptions import ContractLogicError 2 | import pytest 3 | 4 | def test_setNFTPrice(marketplace_contract, nft_contract, mint_nfts, deployer, accounts): 5 | tokenId = 1 6 | price = 1000 7 | marketplace_contract.setNFTPrice(nft_contract.address, tokenId, price, sender=deployer) 8 | assert marketplace_contract.prices(nft_contract.address, tokenId) == price 9 | 10 | def test_buyNFT(marketplace_contract, nft_contract, mint_nfts, deployer, accounts): 11 | tokenId = 1 12 | price = 1000 13 | user = accounts[1] 14 | initial_balance_deployer = deployer.balance 15 | initial_balance_user = user.balance 16 | marketplace_contract.setNFTPrice(nft_contract.address, tokenId, price, sender=deployer) 17 | nft_contract.approve(marketplace_contract.address, tokenId, sender=deployer) 18 | marketplace_contract.buyNFT(nft_contract.address, tokenId, value=price, sender=user) 19 | assert marketplace_contract.prices(nft_contract.address, tokenId) == 0 20 | assert nft_contract.ownerOf(tokenId) == user 21 | 22 | def test_proposeNFTPrice(marketplace_contract, nft_contract, mint_nfts, deployer, accounts): 23 | tokenId = 1 24 | price = 2000 25 | user = accounts[1] 26 | marketplace_contract.proposeNFTPrice(nft_contract.address, tokenId, price, value=price, sender=user) 27 | assert marketplace_contract.proposals(nft_contract.address, tokenId, user) == price 28 | 29 | def test_cancelProposalNFTPrice(marketplace_contract, nft_contract, mint_nfts, deployer, accounts): 30 | tokenId = 1 31 | price = 2000 32 | user = accounts[1] 33 | marketplace_contract.proposeNFTPrice(nft_contract.address, tokenId, price, value=price, sender=user) 34 | marketplace_contract.cancelProposalNFTPrice(nft_contract.address, tokenId, sender=user) 35 | assert marketplace_contract.proposals(nft_contract.address, tokenId, user) == 0 36 | 37 | def test_acceptNFTProposal(marketplace_contract, nft_contract, mint_nfts, deployer, accounts): 38 | tokenId = 1 39 | price = 2000 40 | user = accounts[1] 41 | nft_contract.approve(marketplace_contract.address, tokenId, sender=deployer) 42 | marketplace_contract.proposeNFTPrice(nft_contract.address, tokenId, price, value=price, sender=user) 43 | marketplace_contract.acceptNFTProposal(nft_contract.address, tokenId, user, sender=deployer) 44 | assert nft_contract.ownerOf(tokenId) == user 45 | -------------------------------------------------------------------------------- /chapter_14/nft_marketplace/contracts/NFTMarketplace.vy: -------------------------------------------------------------------------------- 1 | #pragma version ^0.3.0 2 | 3 | interface ERC721_Interface: 4 | def transferFrom(_from: address, _to: address, _tokenId: uint256): nonpayable 5 | def ownerOf(_tokenId: uint256) -> address: view 6 | 7 | prices: public(HashMap[address, HashMap[uint256, uint256]]) 8 | 9 | proposals: public(HashMap[address, HashMap[uint256, HashMap[address, uint256]]]) 10 | 11 | @external 12 | @payable 13 | @nonreentrant("lock") 14 | def buyNFT(nftAddress: address, tokenId: uint256): 15 | assert self.prices[nftAddress][tokenId] != 0 16 | 17 | assert msg.value >= self.prices[nftAddress][tokenId] 18 | 19 | buyer: address = msg.sender 20 | 21 | nftContract: ERC721_Interface = ERC721_Interface(nftAddress) 22 | 23 | seller: address = nftContract.ownerOf(tokenId) 24 | 25 | nftContract.transferFrom(seller, buyer, tokenId) 26 | 27 | send(seller, self.prices[nftAddress][tokenId]) 28 | 29 | if msg.value > self.prices[nftAddress][tokenId]: 30 | send(buyer, msg.value - self.prices[nftAddress][tokenId]) 31 | 32 | self.prices[nftAddress][tokenId] = 0 33 | 34 | @external 35 | def setNFTPrice(nftAddress: address, tokenId: uint256, price: uint256): 36 | nftContract: ERC721_Interface = ERC721_Interface(nftAddress) 37 | 38 | assert nftContract.ownerOf(tokenId) == msg.sender 39 | 40 | self.prices[nftAddress][tokenId] = price 41 | 42 | @external 43 | @payable 44 | def proposeNFTPrice(nftAddress: address, tokenId: uint256, proposedPrice: uint256): 45 | assert msg.value == proposedPrice, "ETH is not same as proposed price" 46 | 47 | self.proposals[nftAddress][tokenId][msg.sender] = proposedPrice 48 | 49 | @external 50 | @nonreentrant("lock2") 51 | def cancelProposalNFTPrice(nftAddress: address, tokenId: uint256): 52 | proposedPrice: uint256 = self.proposals[nftAddress][tokenId][msg.sender] 53 | 54 | assert proposedPrice > 0, "Proposed price is zero" 55 | 56 | self.proposals[nftAddress][tokenId][msg.sender] = 0 57 | 58 | send(msg.sender, proposedPrice) 59 | 60 | @external 61 | @nonreentrant("lock3") 62 | def acceptNFTProposal(nftAddress: address, tokenId: uint256, buyer: address): 63 | nftContract: ERC721_Interface = ERC721_Interface(nftAddress) 64 | 65 | assert nftContract.ownerOf(tokenId) == msg.sender 66 | 67 | assert self.proposals[nftAddress][tokenId][buyer] != 0 68 | 69 | proposedPrice: uint256 = self.proposals[nftAddress][tokenId][buyer] 70 | 71 | nftContract.transferFrom(msg.sender, buyer, tokenId) 72 | 73 | send(msg.sender, proposedPrice) 74 | 75 | self.proposals[nftAddress][tokenId][buyer] = 0 76 | 77 | self.prices[nftAddress][tokenId] = 0 78 | -------------------------------------------------------------------------------- /chapter_10/decentralized_videos/videos/templates/videos/upload.html: -------------------------------------------------------------------------------- 1 | {% extends "videos/base.html" %} 2 | 3 | {% block content %} 4 |
5 |
6 | 12 |
13 | {% if upload_success %} 14 |
15 | Uploading video file is successful! 16 |
17 | {% endif %} 18 |
19 | {% csrf_token %} 20 |
21 | 22 |
23 | 24 |
25 |
26 |
27 | 28 |
29 | 43 |
44 |
45 |
46 | 47 |
48 | 49 |
50 |
51 |
52 | 53 |
54 | 55 |
56 |
57 |
58 | 59 |
60 |
61 |
62 |
63 |
64 | 72 | {% endblock %} 73 | -------------------------------------------------------------------------------- /chapter_16/dex/contracts/DEX.vy: -------------------------------------------------------------------------------- 1 | #pragma version ^0.3.0 2 | 3 | # Based on https://jeiwan.net/posts/programming-defi-uniswap-1/ 4 | 5 | 6 | from vyper.interfaces import ERC20 as IERC20 7 | 8 | 9 | interface Exchange: 10 | def getReserve() -> uint256: view 11 | def getAmount(inputAmount: uint256, 12 | inputReserve: uint256, 13 | outputReserve: uint256) -> uint256: view 14 | 15 | tokenAddress: public(address) 16 | 17 | 18 | @external 19 | def __init__(_token: address): 20 | assert _token != ZERO_ADDRESS, "invalid token address" 21 | 22 | self.tokenAddress = _token 23 | 24 | 25 | @external 26 | @payable 27 | def addLiquidity(_tokenAmount: uint256): 28 | token: IERC20 = IERC20(self.tokenAddress) 29 | token.transferFrom(msg.sender, self, _tokenAmount) 30 | 31 | 32 | @external 33 | @view 34 | def getReserve() -> uint256: 35 | return IERC20(self.tokenAddress).balanceOf(self) 36 | 37 | 38 | @external 39 | @view 40 | def getAmount(inputAmount: uint256, 41 | inputReserve: uint256, 42 | outputReserve: uint256) -> uint256: 43 | assert inputReserve > 0 and outputReserve > 0, "invalid reserves" 44 | 45 | return (inputAmount * outputReserve) / (inputReserve + inputAmount) 46 | 47 | 48 | @external 49 | @view 50 | def getTokenAmount(_ethSold: uint256) -> uint256: 51 | assert _ethSold > 0, "ethSold is too small" 52 | 53 | tokenReserve : uint256 = Exchange(self).getReserve() 54 | 55 | return Exchange(self).getAmount(_ethSold, self.balance, tokenReserve) 56 | 57 | 58 | @external 59 | @view 60 | def getEthAmount(_tokenSold: uint256) -> uint256: 61 | assert _tokenSold > 0, "tokenSold is too small" 62 | 63 | tokenReserve : uint256 = Exchange(self).getReserve() 64 | 65 | return Exchange(self).getAmount(_tokenSold, tokenReserve, self.balance) 66 | 67 | 68 | @external 69 | @payable 70 | def ethToTokenSwap(_minTokens: uint256): 71 | tokenReserve : uint256 = Exchange(self).getReserve() 72 | tokensBought : uint256 = Exchange(self).getAmount(msg.value, 73 | self.balance - msg.value, 74 | tokenReserve) 75 | 76 | assert tokensBought >= _minTokens, "insufficient output amount" 77 | 78 | IERC20(self.tokenAddress).transfer(msg.sender, tokensBought) 79 | 80 | 81 | @external 82 | def tokenToEthSwap(_tokensSold: uint256, _minEth: uint256): 83 | tokenReserve: uint256 = Exchange(self).getReserve() 84 | ethBought: uint256 = Exchange(self).getAmount(_tokensSold, 85 | tokenReserve, 86 | self.balance) 87 | 88 | assert ethBought >= _minEth, "insufficient output amount" 89 | 90 | IERC20(self.tokenAddress).transferFrom(msg.sender, self, _tokensSold) 91 | send(msg.sender, ethBought) 92 | -------------------------------------------------------------------------------- /chapter_12/erc20/contracts/HelloToken.vy: -------------------------------------------------------------------------------- 1 | #pragma version ^0.3.0 2 | 3 | from vyper.interfaces import ERC20 4 | from vyper.interfaces import ERC20Detailed 5 | 6 | implements: ERC20 7 | implements: ERC20Detailed 8 | 9 | event Transfer: 10 | sender: indexed(address) 11 | receiver: indexed(address) 12 | value: uint256 13 | 14 | event Approval: 15 | owner: indexed(address) 16 | spender: indexed(address) 17 | value: uint256 18 | 19 | name: public(String[32]) 20 | symbol: public(String[32]) 21 | decimals: public(uint8) 22 | 23 | balanceOf: public(HashMap[address, uint256]) 24 | allowance: public(HashMap[address, HashMap[address, uint256]]) 25 | totalSupply: public(uint256) 26 | 27 | 28 | @external 29 | def __init__(_name: String[32], _symbol: String[32], _decimals: uint8, _supply: uint256): 30 | init_supply: uint256 = _supply * 10 ** convert(_decimals, uint256) 31 | self.name = _name 32 | self.symbol = _symbol 33 | self.decimals = _decimals 34 | self.balanceOf[msg.sender] = init_supply 35 | self.totalSupply = init_supply 36 | log Transfer(empty(address), msg.sender, init_supply) 37 | 38 | 39 | @external 40 | def transfer(_to : address, _value : uint256) -> bool: 41 | """ 42 | @dev Transfer token for a specified address 43 | @param _to The address to transfer to. 44 | @param _value The amount to be transferred. 45 | """ 46 | self.balanceOf[msg.sender] -= _value 47 | self.balanceOf[_to] += _value 48 | log Transfer(msg.sender, _to, _value) 49 | return True 50 | 51 | 52 | @external 53 | def transferFrom(_from : address, _to : address, _value : uint256) -> bool: 54 | """ 55 | @dev Transfer tokens from one address to another. 56 | @param _from address The address which you want to send tokens from 57 | @param _to address The address which you want to transfer to 58 | @param _value uint256 the amount of tokens to be transferred 59 | """ 60 | self.balanceOf[_from] -= _value 61 | self.balanceOf[_to] += _value 62 | self.allowance[_from][msg.sender] -= _value 63 | log Transfer(_from, _to, _value) 64 | return True 65 | 66 | 67 | @external 68 | def approve(_spender : address, _value : uint256) -> bool: 69 | """ 70 | @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. 71 | Beware that changing an allowance with this method brings the risk that someone may use both the old 72 | and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this 73 | race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: 74 | https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 75 | @param _spender The address which will spend the funds. 76 | @param _value The amount of tokens to be spent. 77 | """ 78 | self.allowance[msg.sender][_spender] = _value 79 | log Approval(msg.sender, _spender, _value) 80 | return True 81 | -------------------------------------------------------------------------------- /chapter_15/vault/contracts/HelloToken.vy: -------------------------------------------------------------------------------- 1 | #pragma version ^0.3.0 2 | 3 | from vyper.interfaces import ERC20 4 | from vyper.interfaces import ERC20Detailed 5 | 6 | implements: ERC20 7 | implements: ERC20Detailed 8 | 9 | event Transfer: 10 | sender: indexed(address) 11 | receiver: indexed(address) 12 | value: uint256 13 | 14 | event Approval: 15 | owner: indexed(address) 16 | spender: indexed(address) 17 | value: uint256 18 | 19 | name: public(String[32]) 20 | symbol: public(String[32]) 21 | decimals: public(uint8) 22 | 23 | balanceOf: public(HashMap[address, uint256]) 24 | allowance: public(HashMap[address, HashMap[address, uint256]]) 25 | totalSupply: public(uint256) 26 | 27 | 28 | @external 29 | def __init__(_name: String[32], _symbol: String[32], _decimals: uint8, _supply: uint256): 30 | init_supply: uint256 = _supply * 10 ** convert(_decimals, uint256) 31 | self.name = _name 32 | self.symbol = _symbol 33 | self.decimals = _decimals 34 | self.balanceOf[msg.sender] = init_supply 35 | self.totalSupply = init_supply 36 | log Transfer(empty(address), msg.sender, init_supply) 37 | 38 | 39 | @external 40 | def transfer(_to : address, _value : uint256) -> bool: 41 | """ 42 | @dev Transfer token for a specified address 43 | @param _to The address to transfer to. 44 | @param _value The amount to be transferred. 45 | """ 46 | self.balanceOf[msg.sender] -= _value 47 | self.balanceOf[_to] += _value 48 | log Transfer(msg.sender, _to, _value) 49 | return True 50 | 51 | 52 | @external 53 | def transferFrom(_from : address, _to : address, _value : uint256) -> bool: 54 | """ 55 | @dev Transfer tokens from one address to another. 56 | @param _from address The address which you want to send tokens from 57 | @param _to address The address which you want to transfer to 58 | @param _value uint256 the amount of tokens to be transferred 59 | """ 60 | self.balanceOf[_from] -= _value 61 | self.balanceOf[_to] += _value 62 | self.allowance[_from][msg.sender] -= _value 63 | log Transfer(_from, _to, _value) 64 | return True 65 | 66 | 67 | @external 68 | def approve(_spender : address, _value : uint256) -> bool: 69 | """ 70 | @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. 71 | Beware that changing an allowance with this method brings the risk that someone may use both the old 72 | and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this 73 | race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: 74 | https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 75 | @param _spender The address which will spend the funds. 76 | @param _value The amount of tokens to be spent. 77 | """ 78 | self.allowance[msg.sender][_spender] = _value 79 | log Approval(msg.sender, _spender, _value) 80 | return True 81 | -------------------------------------------------------------------------------- /chapter_16/dex/contracts/HelloToken.vy: -------------------------------------------------------------------------------- 1 | #pragma version ^0.3.0 2 | 3 | from vyper.interfaces import ERC20 4 | from vyper.interfaces import ERC20Detailed 5 | 6 | implements: ERC20 7 | implements: ERC20Detailed 8 | 9 | event Transfer: 10 | sender: indexed(address) 11 | receiver: indexed(address) 12 | value: uint256 13 | 14 | event Approval: 15 | owner: indexed(address) 16 | spender: indexed(address) 17 | value: uint256 18 | 19 | name: public(String[32]) 20 | symbol: public(String[32]) 21 | decimals: public(uint8) 22 | 23 | balanceOf: public(HashMap[address, uint256]) 24 | allowance: public(HashMap[address, HashMap[address, uint256]]) 25 | totalSupply: public(uint256) 26 | 27 | 28 | @external 29 | def __init__(_name: String[32], _symbol: String[32], _decimals: uint8, _supply: uint256): 30 | init_supply: uint256 = _supply * 10 ** convert(_decimals, uint256) 31 | self.name = _name 32 | self.symbol = _symbol 33 | self.decimals = _decimals 34 | self.balanceOf[msg.sender] = init_supply 35 | self.totalSupply = init_supply 36 | log Transfer(empty(address), msg.sender, init_supply) 37 | 38 | 39 | @external 40 | def transfer(_to : address, _value : uint256) -> bool: 41 | """ 42 | @dev Transfer token for a specified address 43 | @param _to The address to transfer to. 44 | @param _value The amount to be transferred. 45 | """ 46 | self.balanceOf[msg.sender] -= _value 47 | self.balanceOf[_to] += _value 48 | log Transfer(msg.sender, _to, _value) 49 | return True 50 | 51 | 52 | @external 53 | def transferFrom(_from : address, _to : address, _value : uint256) -> bool: 54 | """ 55 | @dev Transfer tokens from one address to another. 56 | @param _from address The address which you want to send tokens from 57 | @param _to address The address which you want to transfer to 58 | @param _value uint256 the amount of tokens to be transferred 59 | """ 60 | self.balanceOf[_from] -= _value 61 | self.balanceOf[_to] += _value 62 | self.allowance[_from][msg.sender] -= _value 63 | log Transfer(_from, _to, _value) 64 | return True 65 | 66 | 67 | @external 68 | def approve(_spender : address, _value : uint256) -> bool: 69 | """ 70 | @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. 71 | Beware that changing an allowance with this method brings the risk that someone may use both the old 72 | and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this 73 | race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: 74 | https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 75 | @param _spender The address which will spend the funds. 76 | @param _value The amount of tokens to be spent. 77 | """ 78 | self.allowance[msg.sender][_spender] = _value 79 | log Approval(msg.sender, _spender, _value) 80 | return True 81 | -------------------------------------------------------------------------------- /chapter_9/merkle_tree.py: -------------------------------------------------------------------------------- 1 | from hashlib import sha256 2 | from typing import List 3 | from math import ceil 4 | 5 | 6 | class MerkleTree: 7 | 8 | def __init__(self, leaf_nodes : List[str]): 9 | self.hash_nodes : List[str] = [] 10 | self.leaf_nodes : List[str] = leaf_nodes 11 | self._turn_leaf_nodes_to_hash_nodes() 12 | if len(leaf_nodes) < 4: 13 | self.root_hash = self._hash_list() 14 | else: 15 | self.root_hash = self._build_root_hash() 16 | 17 | def _hash_list(self): 18 | long_node = "".join(self.hash_nodes) 19 | return self._hash(long_node.encode('utf-8')) 20 | 21 | def _turn_leaf_nodes_to_hash_nodes(self): 22 | for node in self.leaf_nodes: 23 | self.hash_nodes.append(self._hash(node.encode('utf-8'))) 24 | 25 | def _hash(self, data : bytes) -> bytes: 26 | return sha256(data).hexdigest() 27 | 28 | def _build_root_hash(self) -> bytes: 29 | parent_amount = ceil(len(self.hash_nodes) / 2) 30 | nodes : List[str] = self.hash_nodes 31 | 32 | while parent_amount > 1: 33 | parents : List[bytes] = [] 34 | i = 0 35 | while i < len(nodes): 36 | node1 = nodes[i] 37 | if i + 1 >= len(nodes): 38 | node2 = None 39 | else: 40 | node2 = nodes[i+1] 41 | parents.append(self._convert_parent_from_two_nodes(node1, node2)) 42 | i += 2 43 | parent_amount = len(parents) 44 | nodes = parents 45 | 46 | return parents[0] 47 | 48 | def _convert_parent_from_two_nodes(self, node1 : bytes, node2) -> bytes: 49 | if node2 == None: 50 | return self._hash((node1 + node1).encode('utf-8')) 51 | return self._hash((node1 + node2).encode('utf-8')) 52 | 53 | 54 | if __name__ == '__main__': 55 | leaf_nodes = ['cat', 'dog', 'bird', 'whale'] 56 | merkle_tree = MerkleTree(leaf_nodes) 57 | # ba2b19423245563949658c3f98ebbc337671706e037267a7d95be78dc95f0f31 58 | print(merkle_tree.root_hash) 59 | 60 | leaf_nodes = ['cat', 'dog', 'bird', 'whale', 'unicorn', 'pegasus', 'elephant', 'mouse'] 61 | merkle_tree = MerkleTree(leaf_nodes) 62 | # 20a9a6aa60cc29d62189a5a4d2388a272b2a0f249264dd0da3a7eecdfdc044bd 63 | print(merkle_tree.root_hash) 64 | 65 | leaf_nodes = ['cat', 'dog', 'bird', 'whale', 'unicorn'] 66 | merkle_tree = MerkleTree(leaf_nodes) 67 | # 6182e4e70b821ecc35d788bb4801f9b26bec75e935129e3fcd66648a5fdd79f4 68 | print(merkle_tree.root_hash) 69 | 70 | leaf_nodes = ['cat', 'dog', 'bird', 'whale', 'unicorn', 'pegasus', 'elephant'] 71 | merkle_tree = MerkleTree(leaf_nodes) 72 | # 8149c6504e532f0b03631ab3779f44b4f72edcef7e842120fe2a9198fac8a08e 73 | print(merkle_tree.root_hash) 74 | 75 | leaf_nodes = ['cat', 'dog'] 76 | merkle_tree = MerkleTree(leaf_nodes) 77 | # d2cc68f933eebbeb605e65d4fa80ab8a76b87a40fce6fab348431f2f0c199603 78 | print(merkle_tree.root_hash) 79 | -------------------------------------------------------------------------------- /chapter_13/nft/tests/test_HelloNFT.py: -------------------------------------------------------------------------------- 1 | from ape.exceptions import ContractLogicError 2 | import pytest 3 | 4 | def test_init(erc721_contract, deployer): 5 | assert "Hello NFT" == erc721_contract.name() 6 | assert "HEL" == erc721_contract.symbol() 7 | 8 | def test_balanceOf(erc721_contract, deployer, mint_nfts): 9 | assert 5 == erc721_contract.balanceOf(deployer) 10 | 11 | def test_ownerOf(erc721_contract, deployer, mint_nfts): 12 | for i in range(5): 13 | assert deployer == erc721_contract.ownerOf(i) 14 | 15 | def test_transfer(erc721_contract, deployer, mint_nfts, accounts): 16 | user = accounts[1] 17 | nftId = 2 18 | assert 5 == erc721_contract.balanceOf(deployer) 19 | assert 0 == erc721_contract.balanceOf(user) 20 | assert deployer == erc721_contract.ownerOf(nftId) 21 | erc721_contract.transferFrom(deployer, user, nftId, sender=deployer) 22 | assert 4 == erc721_contract.balanceOf(deployer) 23 | assert 1 == erc721_contract.balanceOf(user) 24 | assert user == erc721_contract.ownerOf(nftId) 25 | 26 | def test_approve(erc721_contract, deployer, mint_nfts, accounts): 27 | user1 = accounts[1] 28 | user2 = accounts[2] 29 | nftId = 2 30 | otherNftId = 3 31 | 32 | assert erc721_contract.getApproved(nftId) == '0x0000000000000000000000000000000000000000' 33 | assert erc721_contract.getApproved(otherNftId) == '0x0000000000000000000000000000000000000000' 34 | with pytest.raises(ContractLogicError): 35 | erc721_contract.transferFrom(deployer, user2, nftId, sender=user1) 36 | erc721_contract.transferFrom(deployer, user2, otherNftId, sender=user1) 37 | 38 | erc721_contract.approve(user1, nftId, sender=deployer) 39 | assert erc721_contract.getApproved(nftId) == user1 40 | assert erc721_contract.getApproved(otherNftId) == '0x0000000000000000000000000000000000000000' 41 | 42 | assert deployer == erc721_contract.ownerOf(nftId) 43 | erc721_contract.transferFrom(deployer, user2, nftId, sender=user1) 44 | assert user2 == erc721_contract.ownerOf(nftId) 45 | 46 | with pytest.raises(ContractLogicError): 47 | erc721_contract.transferFrom(deployer, user2, otherNftId, sender=user1) 48 | 49 | def test_setApprovalForAll(erc721_contract, deployer, mint_nfts, accounts): 50 | user1 = accounts[1] 51 | user2 = accounts[2] 52 | nftId = 2 53 | otherNftId = 3 54 | 55 | with pytest.raises(ContractLogicError): 56 | erc721_contract.transferFrom(deployer, user2, nftId, sender=user1) 57 | erc721_contract.transferFrom(deployer, user2, otherNftId, sender=user1) 58 | 59 | erc721_contract.setApprovalForAll(user1, True, sender=deployer) 60 | 61 | assert deployer == erc721_contract.ownerOf(nftId) 62 | erc721_contract.transferFrom(deployer, user2, nftId, sender=user1) 63 | assert user2 == erc721_contract.ownerOf(nftId) 64 | 65 | assert deployer == erc721_contract.ownerOf(otherNftId) 66 | erc721_contract.transferFrom(deployer, user2, otherNftId, sender=user1) 67 | assert user2 == erc721_contract.ownerOf(otherNftId) 68 | -------------------------------------------------------------------------------- /chapter_8/wallet/scripts/full_wallet.py: -------------------------------------------------------------------------------- 1 | import sys, os, json, time 2 | from PySide6 import QtCore, QtWidgets 3 | from ape import accounts 4 | 5 | 6 | class WorkerThread(QtCore.QThread): 7 | username = os.environ["WALLET_USERNAME"] 8 | update_label = QtCore.Signal(str) 9 | 10 | def run(self): 11 | while True: 12 | time.sleep(1) 13 | wallet_account = accounts.load(self.username) 14 | self.update_label.emit(str(wallet_account.balance)) 15 | 16 | 17 | class TransferWidget(QtWidgets.QWidget): 18 | username = os.environ["WALLET_USERNAME"] 19 | password = os.environ["WALLET_PASSWORD"] 20 | home_dir = os.path.expanduser('~') 21 | wallet_db_path = f"{home_dir}/.wallet.json" 22 | 23 | def __init__(self): 24 | super().__init__() 25 | 26 | balance_layout = QtWidgets.QHBoxLayout() 27 | balance_label = QtWidgets.QLabel("My Balance:") 28 | self.balance_field = QtWidgets.QLabel("ETH") 29 | balance_layout.addWidget(balance_label) 30 | balance_layout.addWidget(self.balance_field) 31 | 32 | eth_label = QtWidgets.QLabel("ETH") 33 | self.amount_field = QtWidgets.QTextEdit("") 34 | self.amount_field.setFixedHeight(30) 35 | amount_layout = QtWidgets.QHBoxLayout() 36 | amount_layout.addWidget(self.amount_field) 37 | amount_layout.addWidget(eth_label) 38 | 39 | self.address_combobox = QtWidgets.QComboBox() 40 | if not os.path.exists(self.wallet_db_path): 41 | data = {} 42 | else: 43 | with open(self.wallet_db_path, "r") as f: 44 | data = json.load(f) 45 | options = ["---"] 46 | for k,v in data.items(): 47 | options.append(k + " - " + v) 48 | self.address_combobox.addItems(options) 49 | 50 | self.transfer_button = QtWidgets.QPushButton("Transfer") 51 | self.transfer_button.clicked.connect(self.transfer_button_on_clicked) 52 | 53 | self.layout = QtWidgets.QVBoxLayout(self) 54 | self.layout.addLayout(balance_layout) 55 | self.layout.addLayout(amount_layout) 56 | self.layout.addWidget(self.address_combobox) 57 | self.layout.addWidget(self.transfer_button) 58 | 59 | self.thread = WorkerThread() 60 | self.thread.update_label.connect(self.update_balance) 61 | self.thread.start() 62 | 63 | def update_balance(self, balance): 64 | balance = int(balance) / 10**18 65 | self.balance_field.setText(str(balance) + " ETH") 66 | 67 | @QtCore.Slot() 68 | def transfer_button_on_clicked(self): 69 | label_address = self.address_combobox.currentText() 70 | if label_address == "---": 71 | return 72 | label, address = label_address.split(" - ") 73 | amount = self.amount_field.toPlainText() 74 | amount = amount + "000000000000000000" 75 | wallet_account = accounts.load(self.username) 76 | wallet_account.set_autosign(True, passphrase=self.password) 77 | wallet_account.transfer(address, amount) 78 | self.address_combobox.setCurrentIndex(0) 79 | self.amount_field.setText("") 80 | 81 | 82 | def main(): 83 | app = QtWidgets.QApplication([]) 84 | 85 | widget = TransferWidget() 86 | widget.show() 87 | 88 | sys.exit(app.exec()) 89 | -------------------------------------------------------------------------------- /chapter_16/dex/tests/test_DEX.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | 4 | def test_addLiquidity(exchange, token, deployer): 5 | ETH = 100 6 | token.approve(exchange.address, 2000, sender=deployer) 7 | token_amount = 200 8 | exchange.addLiquidity(token_amount, value=ETH, sender=deployer) 9 | 10 | assert exchange.balance == ETH 11 | reserve = exchange.getReserve() 12 | assert reserve == token_amount 13 | 14 | 15 | def test_getTokenAmount(exchange, token, deployer): 16 | token_amount = 20000 17 | token.approve(exchange.address, token_amount, sender=deployer) 18 | ETH = 10000 19 | exchange.addLiquidity(token_amount, value=ETH, sender=deployer) 20 | 21 | tokensOut = exchange.getTokenAmount(10) 22 | assert tokensOut == 19 23 | 24 | tokensOut = exchange.getTokenAmount(100) 25 | assert tokensOut == 198 26 | 27 | tokensOut = exchange.getTokenAmount(1000) 28 | assert tokensOut == 1818 29 | 30 | tokensOut = exchange.getTokenAmount(5000) 31 | assert tokensOut == 6666 32 | 33 | 34 | def test_getETHAmount(exchange, token, deployer): 35 | token_amount = 20000 36 | token.approve(exchange.address, token_amount, sender=deployer) 37 | ETH = 10000 38 | exchange.addLiquidity(token_amount, value=ETH, sender=deployer) 39 | 40 | ethOut = exchange.getEthAmount(10) 41 | assert ethOut == 4 42 | 43 | ethOut = exchange.getEthAmount(100) 44 | assert ethOut == 49 45 | 46 | ethOut = exchange.getEthAmount(1000) 47 | assert ethOut == 476 48 | 49 | ethOut = exchange.getEthAmount(10000) 50 | assert ethOut == 3333 51 | 52 | ethOut = exchange.getEthAmount(15000) 53 | assert ethOut == 4285 54 | 55 | 56 | def test_ethToTokenSwap(exchange, token, deployer, accounts): 57 | token_amount = 20000 58 | token.approve(exchange.address, token_amount, sender=deployer) 59 | ETH = 10000 60 | exchange.addLiquidity(token_amount, value=ETH, sender=deployer) 61 | 62 | exchange.ethToTokenSwap(15, value=10, sender=accounts[1]) 63 | assert token.balanceOf(accounts[1]) == 19 64 | 65 | exchange.ethToTokenSwap(180, value=100, sender=accounts[2]) 66 | assert token.balanceOf(accounts[2]) == 197 67 | 68 | exchange.ethToTokenSwap(1600, value=1000, sender=accounts[3]) 69 | assert token.balanceOf(accounts[3]) == 1780 70 | 71 | 72 | def test_tokenToEthSwap(exchange, token, deployer, accounts): 73 | token_amount = 20000 74 | token.approve(exchange.address, token_amount, sender=deployer) 75 | ETH = 10000 76 | exchange.addLiquidity(token_amount, value=ETH, sender=deployer) 77 | 78 | token.transfer(accounts[1], token_amount, sender=deployer) 79 | token.transfer(accounts[2], token_amount, sender=deployer) 80 | token.transfer(accounts[3], token_amount, sender=deployer) 81 | 82 | initial_balance = accounts[1].balance 83 | sold_token = 100 84 | ethAmount = 49 85 | token.approve(exchange.address, token_amount, sender=accounts[1]) 86 | exchange.tokenToEthSwap(sold_token, 40, sender=accounts[1]) 87 | assert token.balanceOf(accounts[1]) == token_amount - sold_token 88 | 89 | initial_balance = accounts[2].balance 90 | sold_token = 1000 91 | token.approve(exchange, sold_token, sender=accounts[2]) 92 | exchange.tokenToEthSwap(sold_token, 400, sender=accounts[2]) 93 | assert token.balanceOf(accounts[2]) == token_amount - sold_token 94 | -------------------------------------------------------------------------------- /chapter_6/voting_app/tests/test_voting_app.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from ape.exceptions import ContractLogicError 3 | 4 | 5 | def test_chairperson(contract, deployer): 6 | chairperson = contract.chairperson() 7 | assert chairperson == deployer 8 | 9 | def test_addProposal(contract, deployer): 10 | assert contract.amountProposals() == 0 11 | assert contract.proposals(0).name == "" 12 | contract.addProposal("beach", sender=deployer) 13 | assert contract.amountProposals() == 1 14 | assert contract.proposals(0).name == "beach" 15 | 16 | def test_addProposal_fail(contract, accounts): 17 | with pytest.raises(ContractLogicError): 18 | contract.addProposal("beach", sender=accounts[1]) 19 | 20 | def test_giveRightToVote(contract, deployer, accounts): 21 | user = accounts[1] 22 | assert contract.voterCount() == 0 23 | assert contract.voters(user).weight == 0 24 | contract.giveRightToVote(user, 1, sender=deployer) 25 | assert contract.voterCount() == 1 26 | assert contract.voters(user).weight == 1 27 | 28 | power_user = accounts[2] 29 | assert contract.voters(power_user).weight == 0 30 | contract.giveRightToVote(power_user, 9, sender=deployer) 31 | assert contract.voterCount() == 2 32 | assert contract.voters(power_user).weight == 9 33 | 34 | def test_giveRightToVote_fail(contract, deployer, accounts): 35 | user = accounts[1] 36 | with pytest.raises(ContractLogicError): 37 | contract.giveRightToVote(user, 1, sender=accounts[1]) 38 | 39 | def test_vote(contract, deployer, accounts): 40 | contract.addProposal("beach", sender=deployer) 41 | contract.addProposal("mountain", sender=deployer) 42 | user = accounts[1] 43 | user2 = accounts[2] 44 | contract.giveRightToVote(user, 1, sender=deployer) 45 | contract.giveRightToVote(user2, 1, sender=deployer) 46 | 47 | assert contract.voters(user).weight == 1 48 | assert contract.voters(user).voted == False 49 | assert contract.voters(user).vote == 0 50 | assert contract.proposals(0).voteCount == 0 51 | contract.vote(0, sender=user) 52 | assert contract.proposals(0).voteCount == 1 53 | assert contract.voters(user).weight == 0 54 | assert contract.voters(user).voted == True 55 | assert contract.voters(user).vote == 0 56 | 57 | assert contract.voters(user2).weight == 1 58 | assert contract.voters(user2).voted == False 59 | assert contract.voters(user2).vote == 0 60 | assert contract.proposals(1).voteCount == 0 61 | contract.vote(1, sender=user2) 62 | assert contract.proposals(1).voteCount == 1 63 | assert contract.voters(user2).weight == 0 64 | assert contract.voters(user2).voted == True 65 | assert contract.voters(user2).vote == 1 66 | 67 | def test_vote_fail(contract, deployer, accounts): 68 | contract.addProposal("beach", sender=deployer) 69 | contract.addProposal("mountain", sender=deployer) 70 | user = accounts[1] 71 | contract.giveRightToVote(user, 1, sender=deployer) 72 | 73 | contract.vote(0, sender=user) 74 | with pytest.raises(ContractLogicError): 75 | contract.vote(0, sender=user) 76 | 77 | def test_winnerName(contract, deployer, accounts): 78 | contract.addProposal("beach", sender=deployer) 79 | contract.addProposal("mountain", sender=deployer) 80 | user1 = accounts[1] 81 | user2 = accounts[2] 82 | user3 = accounts[3] 83 | contract.giveRightToVote(user1, 1, sender=deployer) 84 | contract.giveRightToVote(user2, 1, sender=deployer) 85 | contract.giveRightToVote(user3, 1, sender=deployer) 86 | 87 | contract.vote(0, sender=user1) 88 | contract.vote(1, sender=user2) 89 | contract.vote(1, sender=user3) 90 | 91 | assert contract.proposals(0).voteCount == 1 92 | assert contract.proposals(1).voteCount == 2 93 | assert contract.winnerName() == "mountain" 94 | -------------------------------------------------------------------------------- /chapter_6/voting_app/contracts/DelegateVotingApp.vy: -------------------------------------------------------------------------------- 1 | # @version ^0.3.0 2 | 3 | struct Voter: 4 | weight: uint256 5 | voted: bool 6 | delegate: address 7 | vote: uint256 8 | 9 | struct Proposal: 10 | name: String[100] 11 | voteCount: uint256 12 | 13 | voters: public(HashMap[address, Voter]) 14 | proposals: public(HashMap[uint256, Proposal]) 15 | voterCount: public(uint256) 16 | chairperson: public(address) 17 | amountProposals: public(uint256) 18 | 19 | MAX_NUM_PROPOSALS: constant(uint256) = 3 20 | 21 | 22 | @view 23 | @internal 24 | def _delegated(addr: address) -> bool: 25 | return self.voters[addr].delegate != empty(address) 26 | 27 | @view 28 | @external 29 | def delegated(addr: address) -> bool: 30 | return self._delegated(addr) 31 | 32 | @view 33 | @internal 34 | def _directlyVoted(addr: address) -> bool: 35 | return self.voters[addr].voted and (self.voters[addr].delegate == empty(address)) 36 | 37 | @view 38 | @external 39 | def directlyVoted(addr: address) -> bool: 40 | return self._directlyVoted(addr) 41 | 42 | @external 43 | def __init__(): 44 | self.chairperson = msg.sender 45 | 46 | @external 47 | def addProposal(_proposalName: String[100]): 48 | assert msg.sender == self.chairperson 49 | i: uint256 = self.amountProposals 50 | self.proposals[i] = Proposal({ 51 | name: _proposalName, 52 | voteCount: 0 53 | }) 54 | self.amountProposals += 1 55 | 56 | @external 57 | def giveRightToVote(voter: address, _weight: uint256): 58 | assert msg.sender == self.chairperson 59 | assert not self.voters[voter].voted 60 | assert self.voters[voter].weight == 0 61 | self.voters[voter].weight = _weight 62 | self.voterCount += 1 63 | 64 | @internal 65 | def _forwardWeight(delegate_with_weight_to_forward: address): 66 | assert self._delegated(delegate_with_weight_to_forward) 67 | assert self.voters[delegate_with_weight_to_forward].weight > 0 68 | 69 | target: address = self.voters[delegate_with_weight_to_forward].delegate 70 | for i in range(4): 71 | if self._delegated(target): 72 | target = self.voters[target].delegate 73 | else: 74 | break 75 | 76 | weight_to_forward: uint256 = self.voters[delegate_with_weight_to_forward].weight 77 | self.voters[delegate_with_weight_to_forward].weight = 0 78 | self.voters[target].weight += weight_to_forward 79 | 80 | if self._directlyVoted(target): 81 | self.proposals[self.voters[target].vote].voteCount += weight_to_forward 82 | self.voters[target].weight = 0 83 | 84 | @external 85 | def delegate(to: address): 86 | assert not self.voters[msg.sender].voted 87 | assert to != msg.sender 88 | assert to != empty(address) 89 | 90 | self.voters[msg.sender].voted = True 91 | self.voters[msg.sender].delegate = to 92 | 93 | self._forwardWeight(msg.sender) 94 | 95 | @external 96 | def vote(proposal: uint256): 97 | assert not self.voters[msg.sender].voted 98 | assert proposal < self.amountProposals 99 | 100 | self.voters[msg.sender].vote = proposal 101 | self.voters[msg.sender].voted = True 102 | 103 | self.proposals[proposal].voteCount += self.voters[msg.sender].weight 104 | self.voters[msg.sender].weight = 0 105 | 106 | @view 107 | @internal 108 | def _winningProposal() -> uint256: 109 | winning_vote_count: uint256 = 0 110 | winning_proposal: uint256 = 0 111 | for i in range(MAX_NUM_PROPOSALS): 112 | if self.proposals[i].voteCount > winning_vote_count: 113 | winning_vote_count = self.proposals[i].voteCount 114 | winning_proposal = i 115 | return winning_proposal 116 | 117 | @view 118 | @external 119 | def winningProposal() -> uint256: 120 | return self._winningProposal() 121 | 122 | @view 123 | @external 124 | def winnerName() -> String[100]: 125 | return self.proposals[self._winningProposal()].name 126 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ 161 | # 162 | chapter_10/stock_videos/ 163 | chapter_10/decentralized_videos/static 164 | chapter_10/decentralized_videos/media 165 | -------------------------------------------------------------------------------- /chapter_10/decentralized_videos/decentralized_videos/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for decentralized_videos project. 3 | 4 | Generated by 'django-admin startproject' using Django 4.2.7. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.2/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/4.2/ref/settings/ 11 | """ 12 | 13 | from pathlib import Path 14 | 15 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 16 | BASE_DIR = Path(__file__).resolve().parent.parent 17 | 18 | 19 | # Quick-start development settings - unsuitable for production 20 | # See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/ 21 | 22 | # SECURITY WARNING: keep the secret key used in production secret! 23 | SECRET_KEY = 'django-insecure-x9k%iqly2=xok&axx!ec6st68!y09&q(162!t)#8ignbok' 24 | 25 | # SECURITY WARNING: don't run with debug turned on in production! 26 | DEBUG = True 27 | 28 | ALLOWED_HOSTS = [] 29 | 30 | 31 | # Application definition 32 | 33 | INSTALLED_APPS = [ 34 | 'django.contrib.admin', 35 | 'django.contrib.auth', 36 | 'django.contrib.contenttypes', 37 | 'django.contrib.sessions', 38 | 'django.contrib.messages', 39 | 'django.contrib.staticfiles', 40 | 'videos' 41 | ] 42 | 43 | MIDDLEWARE = [ 44 | 'django.middleware.security.SecurityMiddleware', 45 | 'django.contrib.sessions.middleware.SessionMiddleware', 46 | 'django.middleware.common.CommonMiddleware', 47 | 'django.middleware.csrf.CsrfViewMiddleware', 48 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 49 | 'django.contrib.messages.middleware.MessageMiddleware', 50 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 51 | ] 52 | 53 | ROOT_URLCONF = 'decentralized_videos.urls' 54 | 55 | TEMPLATES = [ 56 | { 57 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 58 | 'DIRS': [], 59 | 'APP_DIRS': True, 60 | 'OPTIONS': { 61 | 'context_processors': [ 62 | 'django.template.context_processors.debug', 63 | 'django.template.context_processors.request', 64 | 'django.contrib.auth.context_processors.auth', 65 | 'django.contrib.messages.context_processors.messages', 66 | ], 67 | }, 68 | }, 69 | ] 70 | 71 | WSGI_APPLICATION = 'decentralized_videos.wsgi.application' 72 | 73 | 74 | # Database 75 | # https://docs.djangoproject.com/en/4.2/ref/settings/#databases 76 | 77 | DATABASES = { 78 | 'default': { 79 | 'ENGINE': 'django.db.backends.sqlite3', 80 | 'NAME': BASE_DIR / 'db.sqlite3', 81 | } 82 | } 83 | 84 | 85 | # Password validation 86 | # https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators 87 | 88 | AUTH_PASSWORD_VALIDATORS = [ 89 | { 90 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 91 | }, 92 | { 93 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 94 | }, 95 | { 96 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 97 | }, 98 | { 99 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 100 | }, 101 | ] 102 | 103 | 104 | # Internationalization 105 | # https://docs.djangoproject.com/en/4.2/topics/i18n/ 106 | 107 | LANGUAGE_CODE = 'en-us' 108 | 109 | TIME_ZONE = 'UTC' 110 | 111 | USE_I18N = True 112 | 113 | USE_TZ = True 114 | 115 | 116 | # Static files (CSS, JavaScript, Images) 117 | # https://docs.djangoproject.com/en/4.2/howto/static-files/ 118 | 119 | STATIC_URL = 'static/' 120 | STATICFILES_DIRS = [ 121 | Path(BASE_DIR, "static"), 122 | ] 123 | MEDIA_URL = 'media/' 124 | MEDIA_ROOT = Path(BASE_DIR, 'media') 125 | 126 | # Default primary key field type 127 | # https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field 128 | 129 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' 130 | 131 | -------------------------------------------------------------------------------- /chapter_10/videos_sharing_smart_contract/contracts/VideoSharing.vy: -------------------------------------------------------------------------------- 1 | # @version ^0.3.0 2 | 3 | struct Video: 4 | path: String[50] 5 | title: String[20] 6 | 7 | event Transfer: 8 | _from: indexed(address) 9 | _to: indexed(address) 10 | _value: uint256 11 | 12 | event Approval: 13 | _owner: indexed(address) 14 | _spender: indexed(address) 15 | _value: uint256 16 | 17 | event UploadVideo: 18 | _user: indexed(address) 19 | _index: uint256 20 | 21 | event LikeVideo: 22 | _video_liker: indexed(address) 23 | _video_uploader: indexed(address) 24 | _index: uint256 25 | 26 | user_videos_index: HashMap[address, uint256] 27 | 28 | name: public(String[20]) 29 | symbol: public(String[3]) 30 | totalSupply: public(uint256) 31 | decimals: public(uint256) 32 | balances: HashMap[address, uint256] 33 | allowed: HashMap[address, HashMap[address, uint256]] 34 | 35 | all_videos: HashMap[address, HashMap[uint256, Video]] 36 | likes_videos: HashMap[Bytes[100], bool] 37 | aggregate_likes: HashMap[Bytes[100], uint256] 38 | 39 | 40 | @external 41 | def __init__(): 42 | _initialSupply: uint256 = 500 43 | _decimals: uint256 = 3 44 | self.totalSupply = _initialSupply * 10 ** _decimals 45 | self.balances[msg.sender] = self.totalSupply 46 | self.name = 'Video Sharing Coin' 47 | self.symbol = 'VID' 48 | self.decimals = _decimals 49 | log Transfer(ZERO_ADDRESS, msg.sender, self.totalSupply) 50 | 51 | @external 52 | @view 53 | def balanceOf(_owner: address) -> uint256: 54 | return self.balances[_owner] 55 | 56 | @internal 57 | def _transfer(_source: address, _to: address, _amount: uint256) -> bool: 58 | assert self.balances[_source] >= _amount 59 | self.balances[_source] -= _amount 60 | self.balances[_to] += _amount 61 | log Transfer(_source, _to, _amount) 62 | return True 63 | 64 | @external 65 | def transfer(_to: address, _amount: uint256) -> bool: 66 | return self._transfer(msg.sender, _to, _amount) 67 | 68 | @external 69 | def transferFrom(_from: address, _to: address, _value: uint256) -> bool: 70 | assert _value <= self.allowed[_from][msg.sender] 71 | assert _value <= self.balances[_from] 72 | self.balances[_from] -= _value 73 | self.allowed[_from][msg.sender] -= _value 74 | self.balances[_to] += _value 75 | log Transfer(_from, _to, _value) 76 | return True 77 | 78 | @external 79 | def approve(_spender: address, _amount: uint256) -> bool: 80 | self.allowed[msg.sender][_spender] = _amount 81 | log Approval(msg.sender, _spender, _amount) 82 | return True 83 | 84 | @external 85 | @view 86 | def allowance(_owner: address, _spender: address) -> uint256: 87 | return self.allowed[_owner][_spender] 88 | 89 | @external 90 | def upload_video(_video_path: String[50], _video_title: String[20]) -> bool: 91 | _index: uint256 = self.user_videos_index[msg.sender] 92 | self.all_videos[msg.sender][_index] = Video({ path: _video_path, title: _video_title }) 93 | self.user_videos_index[msg.sender] += 1 94 | log UploadVideo(msg.sender, _index) 95 | return True 96 | 97 | @external 98 | @view 99 | def latest_videos_index(_user: address) -> uint256: 100 | return self.user_videos_index[_user] 101 | 102 | @external 103 | @view 104 | def videos_path(_user: address, _index: uint256) -> String[50]: 105 | return self.all_videos[_user][_index].path 106 | 107 | @external 108 | @view 109 | def videos_title(_user: address, _index: uint256) -> String[20]: 110 | return self.all_videos[_user][_index].title 111 | 112 | @external 113 | def like_video(_user: address, _index: uint256) -> bool: 114 | _msg_sender_str: bytes32 = convert(msg.sender, bytes32) 115 | _user_str: bytes32 = convert(_user, bytes32) 116 | _index_str: bytes32 = convert(_index, bytes32) 117 | _key: Bytes[100] = concat(_msg_sender_str, _user_str, _index_str) 118 | _likes_key: Bytes[100] = concat(_user_str, _index_str) 119 | assert _index < self.user_videos_index[_user] 120 | assert self.likes_videos[_key] == False 121 | self.likes_videos[_key] = True 122 | self.aggregate_likes[_likes_key] += 1 123 | self._transfer(msg.sender, _user, 1) 124 | log LikeVideo(msg.sender, _user, _index) 125 | return True 126 | 127 | @external 128 | @view 129 | def video_has_been_liked(_user_like: address, _user_video: address, _index: uint256) -> bool: 130 | _user_like_str: bytes32 = convert(_user_like, bytes32) 131 | _user_video_str: bytes32 = convert(_user_video, bytes32) 132 | _index_str: bytes32 = convert(_index, bytes32) 133 | _key: Bytes[100] = concat(_user_like_str, _user_video_str, _index_str) 134 | return self.likes_videos[_key] 135 | 136 | @external 137 | @view 138 | def video_aggregate_likes(_user_video: address, _index: uint256) -> uint256: 139 | _user_video_str: bytes32 = convert(_user_video, bytes32) 140 | _index_str: bytes32 = convert(_index, bytes32) 141 | _key: Bytes[100] = concat(_user_video_str, _index_str) 142 | return self.aggregate_likes[_key] 143 | -------------------------------------------------------------------------------- /chapter_10/videos_sharing_smart_contract/tests/test_video_sharing.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from ape.exceptions import ContractLogicError 3 | 4 | def upload_video(contract, account, video_path, video_title): 5 | contract.upload_video(video_path, video_title, sender=account) 6 | 7 | def transfer_coins(contract, source, destination, amount): 8 | contract.transfer(destination, amount, sender=source) 9 | 10 | def like_video(contract, video_liker, video_uploader, index): 11 | contract.like_video(video_uploader, index, sender=video_liker) 12 | 13 | def test_upload_video(contract, deployer): 14 | video_sharing = contract 15 | 16 | video_uploader = deployer 17 | 18 | index = video_sharing.latest_videos_index(video_uploader) 19 | assert index == 0 20 | 21 | upload_video(video_sharing, video_uploader, 'video-ipfs-path', "video title") 22 | 23 | index = video_sharing.latest_videos_index(video_uploader) 24 | path = video_sharing.videos_path(video_uploader, 0) 25 | title = video_sharing.videos_title(video_uploader, 0) 26 | assert index == 1 27 | assert path == 'video-ipfs-path' 28 | assert title == "video title" 29 | 30 | upload_video(video_sharing, video_uploader, 'video-ipfs-path2', "video title2") 31 | 32 | index = video_sharing.latest_videos_index(video_uploader) 33 | path = video_sharing.videos_path(video_uploader, 1) 34 | title = video_sharing.videos_title(video_uploader, 1) 35 | assert index == 2 36 | assert path == 'video-ipfs-path2' 37 | assert title == "video title2" 38 | 39 | events = video_sharing.UploadVideo.query("*", start_block=-1) 40 | 41 | assert events["event_arguments"][0]["_user"] == video_uploader 42 | assert events["event_arguments"][0]["_index"] == 0 43 | 44 | assert events["event_arguments"][1]["_user"] == video_uploader 45 | assert events["event_arguments"][1]["_index"] == 1 46 | 47 | def test_like_video(contract, accounts): 48 | video_sharing = contract 49 | 50 | manager = accounts[0] 51 | video_uploader = accounts[1] 52 | video_liker = accounts[2] 53 | video_liker2 = accounts[3] 54 | 55 | transfer_coins(video_sharing, manager, video_liker, 100) 56 | transfer_coins(video_sharing, manager, video_liker2, 100) 57 | transfer_coins(video_sharing, manager, video_uploader, 50) 58 | upload_video(video_sharing, video_uploader, 'video-ipfs-path', "video title") 59 | 60 | liked = video_sharing.video_has_been_liked(video_liker, video_uploader, 0) 61 | assert liked == False 62 | liked2 = video_sharing.video_has_been_liked(video_liker2, video_uploader, 0) 63 | assert liked2 == False 64 | video_uploader_balance = video_sharing.balanceOf(video_uploader) 65 | assert video_uploader_balance == 50 66 | video_liker_balance = video_sharing.balanceOf(video_liker) 67 | assert video_liker_balance == 100 68 | video_liker2_balance = video_sharing.balanceOf(video_liker2) 69 | assert video_liker2_balance == 100 70 | aggregate_likes = video_sharing.video_aggregate_likes(video_uploader, 0) 71 | assert aggregate_likes == 0 72 | 73 | like_video(video_sharing, video_liker, video_uploader, 0) 74 | 75 | liked = video_sharing.video_has_been_liked(video_liker, video_uploader, 0) 76 | assert liked == True 77 | liked2 = video_sharing.video_has_been_liked(video_liker2, video_uploader, 0) 78 | assert liked2 == False 79 | video_uploader_balance = video_sharing.balanceOf(video_uploader) 80 | assert video_uploader_balance == 51 81 | video_liker_balance = video_sharing.balanceOf(video_liker) 82 | assert video_liker_balance == 99 83 | video_liker2_balance = video_sharing.balanceOf(video_liker2) 84 | assert video_liker2_balance == 100 85 | aggregate_likes = video_sharing.video_aggregate_likes(video_uploader, 0) 86 | assert aggregate_likes == 1 87 | 88 | like_video(video_sharing, video_liker2, video_uploader, 0) 89 | 90 | liked = video_sharing.video_has_been_liked(video_liker2, video_uploader, 0) 91 | assert liked == True 92 | liked2 = video_sharing.video_has_been_liked(video_liker2, video_uploader, 0) 93 | assert liked2 == True 94 | video_uploader_balance = video_sharing.balanceOf(video_uploader) 95 | assert video_uploader_balance == 52 96 | video_liker_balance = video_sharing.balanceOf(video_liker) 97 | assert video_liker_balance == 99 98 | video_liker2_balance = video_sharing.balanceOf(video_liker2) 99 | assert video_liker2_balance == 99 100 | aggregate_likes = video_sharing.video_aggregate_likes(video_uploader, 0) 101 | assert aggregate_likes == 2 102 | 103 | events = video_sharing.LikeVideo.query("*", start_block=-1) 104 | 105 | assert events["event_arguments"][0]["_video_liker"] == video_liker 106 | assert events["event_arguments"][0]["_video_uploader"] == video_uploader 107 | assert events["event_arguments"][0]["_index"] == 0 108 | 109 | assert events["event_arguments"][1]["_video_liker"] == video_liker2 110 | assert events["event_arguments"][1]["_video_uploader"] == video_uploader 111 | assert events["event_arguments"][1]["_index"] == 0 112 | 113 | with pytest.raises(ContractLogicError): 114 | like_video(video_sharing, video_liker, video_uploader, 0) 115 | -------------------------------------------------------------------------------- /chapter_12/erc20/contracts/CrowdSaleToken.vy: -------------------------------------------------------------------------------- 1 | #pragma version ^0.3.0 2 | 3 | from vyper.interfaces import ERC20 4 | from vyper.interfaces import ERC20Detailed 5 | 6 | implements: ERC20 7 | implements: ERC20Detailed 8 | 9 | event Transfer: 10 | sender: indexed(address) 11 | receiver: indexed(address) 12 | value: uint256 13 | 14 | event Approval: 15 | owner: indexed(address) 16 | spender: indexed(address) 17 | value: uint256 18 | 19 | event Payment: 20 | buyer: indexed(address) 21 | value: uint256 22 | 23 | name: public(String[32]) 24 | symbol: public(String[32]) 25 | decimals: public(uint8) 26 | 27 | balanceOf: public(HashMap[address, uint256]) 28 | allowance: public(HashMap[address, HashMap[address, uint256]]) 29 | totalSupply: public(uint256) 30 | 31 | ethBalances: public(HashMap[address, uint256]) 32 | 33 | beneficiary: public(address) 34 | minFundingGoal: public(uint256) 35 | maxFundingGoal: public(uint256) 36 | amountRaised: public(uint256) 37 | deadline: public(uint256) 38 | price: public(uint256) 39 | fundingGoalReached: public(bool) 40 | crowdsaleClosed: public(bool) 41 | 42 | @external 43 | def __init__(_name: String[32], _symbol: String[32], _decimals: uint8, _supply: uint256): 44 | init_supply: uint256 = _supply * 10 ** convert(_decimals, uint256) 45 | self.name = _name 46 | self.symbol = _symbol 47 | self.decimals = _decimals 48 | self.balanceOf[msg.sender] = init_supply 49 | self.totalSupply = init_supply 50 | log Transfer(empty(address), msg.sender, init_supply) 51 | 52 | self.beneficiary = msg.sender 53 | self.minFundingGoal = as_wei_value(30, "ether") 54 | self.maxFundingGoal = as_wei_value(50, "ether") 55 | self.deadline = block.timestamp + 3600 * 24 * 100 # 100 days 56 | self.price = as_wei_value(1, "ether") / 100 57 | self.fundingGoalReached = False 58 | self.crowdsaleClosed = False 59 | 60 | @external 61 | @payable 62 | def __default__(): 63 | assert msg.sender != self.beneficiary 64 | assert self.crowdsaleClosed == False 65 | assert self.amountRaised + msg.value < self.maxFundingGoal 66 | assert msg.value >= as_wei_value(0.01, "ether") 67 | self.ethBalances[msg.sender] += msg.value 68 | self.amountRaised += msg.value 69 | tokenAmount: uint256 = msg.value / self.price 70 | self.balanceOf[msg.sender] += tokenAmount 71 | self.balanceOf[self.beneficiary] -= tokenAmount 72 | log Payment(msg.sender, msg.value) 73 | 74 | @external 75 | def checkGoalReached(): 76 | assert block.timestamp > self.deadline 77 | if self.amountRaised >= self.minFundingGoal: 78 | self.fundingGoalReached = True 79 | self.crowdsaleClosed = True 80 | 81 | @external 82 | def safeWithdrawal(): 83 | assert self.crowdsaleClosed == True 84 | if self.fundingGoalReached == False: 85 | if msg.sender != self.beneficiary: 86 | if self.ethBalances[msg.sender] > 0: 87 | ethBalance: uint256 = self.ethBalances[msg.sender] 88 | self.ethBalances[msg.sender] = 0 89 | self.balanceOf[self.beneficiary] += self.balanceOf[msg.sender] 90 | self.balanceOf[msg.sender] = 0 91 | send(msg.sender, ethBalance) 92 | if self.fundingGoalReached == True: 93 | if msg.sender == self.beneficiary: 94 | if self.balance > 0: 95 | send(msg.sender, self.balance) 96 | 97 | @external 98 | def transfer(_to : address, _value : uint256) -> bool: 99 | """ 100 | @dev Transfer token for a specified address 101 | @param _to The address to transfer to. 102 | @param _value The amount to be transferred. 103 | """ 104 | self.balanceOf[msg.sender] -= _value 105 | self.balanceOf[_to] += _value 106 | log Transfer(msg.sender, _to, _value) 107 | return True 108 | 109 | 110 | @external 111 | def transferFrom(_from : address, _to : address, _value : uint256) -> bool: 112 | """ 113 | @dev Transfer tokens from one address to another. 114 | @param _from address The address which you want to send tokens from 115 | @param _to address The address which you want to transfer to 116 | @param _value uint256 the amount of tokens to be transferred 117 | """ 118 | self.balanceOf[_from] -= _value 119 | self.balanceOf[_to] += _value 120 | self.allowance[_from][msg.sender] -= _value 121 | log Transfer(_from, _to, _value) 122 | return True 123 | 124 | 125 | @external 126 | def approve(_spender : address, _value : uint256) -> bool: 127 | """ 128 | @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender. 129 | Beware that changing an allowance with this method brings the risk that someone may use both the old 130 | and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this 131 | race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards: 132 | https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 133 | @param _spender The address which will spend the funds. 134 | @param _value The amount of tokens to be spent. 135 | """ 136 | self.allowance[msg.sender][_spender] = _value 137 | log Approval(msg.sender, _spender, _value) 138 | return True 139 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hands-On Blockchain for Python Developers 2 | 3 | Hands-On Blockchain for Python Developers 4 | 5 | This is the code repository for [Hands-On Blockchain for Python Developers](https://www.packtpub.com/en-us/product/hands-on-blockchain-for-python-developers-9781805121367?utm_source=github&utm_medium=repository&utm_campaign=), published by Packt. 6 | 7 | **Empowering Python developers in the world of blockchain and smart contracts** 8 | 9 | ## What is this book about? 10 | We are living in the age of decentralized fi nance and NFTs. People swap tokens on Uniswap, borrow assets from Aave, send payments with stablecoins, trade art NFTs on OpenSea, and more. To build applications of this kind, you need to know how to write smart contracts. 11 | 12 | This book covers the following exciting features: 13 | * Understand blockchain and smart contracts 14 | * Learn how to write smart contracts with Vyper 15 | * Explore how to use the web3.py library and Ape Framework 16 | * Discover related technologies such as Layer 2 and IPFS 17 | * Gain a step-by-step guide to writing an automated market maker (AMM) decentralized exchange (DEX) smart contract 18 | * Build innovative, interactive, and token-gated Web3 NFT applications 19 | 20 | If you feel this book is for you, get your [copy](https://www.amazon.com/dp/1805121367) today! 21 | 22 | https://www.packtpub.com/ 24 | 25 | ## Instructions and Navigations 26 | All of the code is organized into folders. 27 | 28 | The code will look like the following: 29 | ``` 30 | from ape import accounts, project 31 | import os 32 | def main(): 33 | password = os.environ["MY_PASSWORD"] 34 | dev = accounts.load("dev") 35 | dev.set_autosign(True, passphrase=password) 36 | contract = project.SimpleStorage.deploy(sender=dev) 37 | num_value = contract.retrieve.call() 38 | print(f"The num value is {num_value}") 39 | ``` 40 | 41 | **Following is what you need for this book:** 42 | This blockchain book is for developers interested in understanding blockchain and smart contracts. It is suitable for both technology enthusiasts looking to explore blockchain technology and programmers who aspire to become smart contract engineers. Basic knowledge of GNU/Linux and Python programming is mandatory to get started with this book. 43 | 44 | With the following software and hardware list you can run all code files present in the book (Chapter 1-17). 45 | ### Software and Hardware List 46 | | Chapter | Software required | OS required | 47 | | -------- | ------------------------------------ | ----------------------------------- | 48 | | 1-17 | Python (minimum version 3.10 required) | Windows, Mac OS X, and Linux (Any) | 49 | | 1-17 | Vyper 0.3.10 (versions 0.4.x and above will not work) | Windows, Mac OS X, and Linux (Any) | 50 | | 1-17 | Ape Framework 0.7.23 (versions 0.8.x and above will not work) | Windows, Mac OS X, and Linux (Any) | 51 | | 1-17 | Modern browsers (Mozilla, Firefox, Chrome, etc) | Windows, Mac OS X, and Linux (Any) | 52 | | 1-17 | Remix | Windows, Mac OS X, and Linux (Any) | 53 | | 1-17 | Ganache | Windows, Mac OS X, and Linux (Any) | 54 | | 1-17 | Hardhat | Windows, Mac OS X, and Linux (Any) | 55 | | 1-17 | Geth | Windows, Mac OS X, and Linux (Any) | 56 | | 1-17 | Web3.py | Windows, Mac OS X, and Linux (Any) | 57 | | 1-17 | PySide 6 | Windows, Mac OS X, and Linux (Any) | 58 | | 1-17 | Kubo | Windows, Mac OS X, and Linux (Any) | 59 | | 1-17 | aioipfs | Windows, Mac OS X, and Linux (Any) | 60 | | 1-17 | Alchemy | Windows, Mac OS X, and Linux (Any) | 61 | | 1-17 | Infura | Windows, Mac OS X, and Linux (Any) | 62 | | 1-17 | Django | Windows, Mac OS X, and Linux (Any) | 63 | | 1-17 | FastAPI | Windows, Mac OS X, and Linux (Any) | 64 | | 1-17 | Node.js | Windows, Mac OS X, and Linux (Any) | 65 | | 1-17 | Pnpm | Windows, Mac OS X, and Linux (Any) | 66 | | 1-17 | React | Windows, Mac OS X, and Linux (Any) | 67 | | 1-17 | Wagmi | Windows, Mac OS X, and Linux (Any) | 68 | | 1-17 | MetaMask | Windows, Mac OS X, and Linux (Any) | 69 | 70 | ### Related products 71 | * Solidity Programming Essentials [[Packt]](https://www.packtpub.com/en-ar/product/solidity-programming-essentials-9781803231181?utm_source=github&utm_medium=repository&utm_campaign=9781839216862) [[Amazon]](https://www.amazon.com/dp/1803231181) 72 | 73 | * Applied Computational Thinking with Python [[Packt]](https://www.packtpub.com/en-IT/product/applied-computational-thinking-with-python-9781837632305?utm_source=github&utm_medium=repository&utm_campaign=9781803239545) [[Amazon]](https://www.amazon.com/dp/1837632308) 74 | 75 | ## Get to Know the Author 76 | **Arjuna Sky Kok** 77 | is a skilled software engineer with a passion for all things related to finance and 78 | technology. He lives in Jakarta, where he studied mathematics and programming at Binus University. 79 | Arjuna's academic achievements include double degrees in Computer Science and Mathematics. 80 | Currently, he is focusing his talent in the crypto space, as he believes that DeFi and NFT will serve as 81 | the foundation for future fi nance. He also has a keen interest in AI, especially Generative AI. Outside 82 | of work, Arjuna enjoys watching anime, listening to J-pop songs, and playing basketball. 83 | -------------------------------------------------------------------------------- /chapter_17/token-gated-backend/token_gated_app.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime, timedelta, timezone 2 | from string import Template 3 | import string 4 | import json 5 | import secrets 6 | import os 7 | from typing import Union 8 | 9 | from ape import Contract 10 | from ape import networks 11 | from ethpm_types import ContractType 12 | 13 | from fastapi import Depends, FastAPI, HTTPException, status 14 | from fastapi.security import OAuth2PasswordBearer 15 | from fastapi.middleware.cors import CORSMiddleware 16 | import jwt 17 | from pydantic import BaseModel 18 | from typing_extensions import Annotated 19 | from siwe import SiweMessage 20 | import siwe 21 | 22 | # to get a string like this run: 23 | # openssl rand -hex 32 24 | SECRET_KEY = "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7" 25 | ALGORITHM = "HS256" 26 | ACCESS_TOKEN_EXPIRE_MINUTES = 30 27 | 28 | EIP_4361_STRING = Template(""" 29 | packtpub.com wants you to sign in with your Ethereum account: 30 | $address 31 | 32 | I accept the PacktPub Terms of Service: https://www.packtpub.com/en-us/help/terms-and-conditions 33 | 34 | URI: http://127.0.0.1:8000/token 35 | Version: 1 36 | Chain ID: 1 37 | Nonce: $nonce 38 | Issued At: $nonce_time 39 | Resources: 40 | - https://github.com/PacktPublishing/Hands-On-Blockchain-for-Python-Developers--2nd-Edition 41 | """) 42 | 43 | nonces_data = {} 44 | 45 | oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token") 46 | 47 | 48 | class Token(BaseModel): 49 | access_token: str 50 | token_type: str 51 | 52 | class Nonce(BaseModel): 53 | nonce: str 54 | nonce_time: str 55 | 56 | class Crypto(BaseModel): 57 | address: str 58 | signature: str 59 | 60 | class User(BaseModel): 61 | address: str 62 | 63 | def create_access_token(data: dict, expires_delta: Union[timedelta, None] = None): 64 | to_encode = data.copy() 65 | if expires_delta: 66 | expire = datetime.now(timezone.utc) + expires_delta 67 | else: 68 | expire = datetime.now(timezone.utc) + timedelta(minutes=15) 69 | to_encode.update({"exp": expire}) 70 | encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM) 71 | return encoded_jwt 72 | 73 | def generate_nonce(length=16): 74 | chars = string.ascii_letters + string.digits 75 | return ''.join(secrets.choice(chars) for _ in range(length)) 76 | 77 | 78 | app = FastAPI() 79 | 80 | origins = [ 81 | "http://localhost:5173", 82 | ] 83 | 84 | app.add_middleware( 85 | CORSMiddleware, 86 | allow_origins=origins, 87 | allow_credentials=True, 88 | allow_methods=["*"], 89 | allow_headers=["*"] 90 | ) 91 | 92 | 93 | @app.get("/nonce/{address}") 94 | async def nonce(address: str) -> Nonce: 95 | nonce = generate_nonce() 96 | nonce_time = datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ") 97 | nonces_data[address] = {"nonce": nonce, "nonce_time": nonce_time} 98 | return nonces_data[address] 99 | 100 | @app.post("/token") 101 | async def login_for_access_token( 102 | crypto: Crypto, 103 | ) -> Token: 104 | address = crypto.address 105 | nonce = nonces_data[address]["nonce"] 106 | nonce_time = nonces_data[address]["nonce_time"] 107 | signature = crypto.signature 108 | eip_string = EIP_4361_STRING.substitute(address=address, nonce=nonce, nonce_time=nonce_time) 109 | message = SiweMessage.from_message(message=eip_string, abnf=False) 110 | try: 111 | message.verify(signature=signature) 112 | access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) 113 | access_token = create_access_token( 114 | data={"sub": address}, expires_delta=access_token_expires 115 | ) 116 | return Token(access_token=access_token, token_type="bearer") 117 | except siwe.VerificationError: 118 | raise HTTPException( 119 | status_code=status.HTTP_401_UNAUTHORIZED, 120 | detail="Incorrect signature", 121 | headers={"WWW-Authenticate": "Bearer"}, 122 | ) 123 | 124 | async def get_current_user(token: Annotated[str, Depends(oauth2_scheme)]): 125 | credentials_exception = HTTPException( 126 | status_code=status.HTTP_401_UNAUTHORIZED, 127 | detail="Could not validate credentials", 128 | headers={"WWW-Authenticate": "Bearer"}, 129 | ) 130 | try: 131 | payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM]) 132 | address: str = payload.get("sub") 133 | if address is None: 134 | raise credentials_exception 135 | except jwt.exceptions.InvalidSignatureError: 136 | raise credentials_exception 137 | exists = address in nonces_data 138 | if not exists: 139 | raise credentials_exception 140 | return User(address=address) 141 | 142 | async def get_current_active_user( 143 | current_user: Annotated[User, Depends(get_current_user)], 144 | ): 145 | return current_user 146 | 147 | @app.get("/me", response_model=User) 148 | async def read_users_me( 149 | current_user: Annotated[User, Depends(get_current_active_user)], 150 | ): 151 | return current_user 152 | 153 | BLOCKCHAIN_NETWORK = "local" 154 | BLOCKCHAIN_PROVIDER = "geth" 155 | 156 | @app.get("/content") 157 | async def read_content( 158 | current_user: Annotated[User, Depends(get_current_active_user)], 159 | ): 160 | with open('../token-gated-smart-contract/.build/HelloNFT.json') as f: 161 | contract = json.load(f) 162 | abi = contract['abi'] 163 | 164 | nft_address = os.environ["NFT_ADDRESS"] 165 | with networks.ethereum[BLOCKCHAIN_NETWORK].use_provider(BLOCKCHAIN_PROVIDER): 166 | ct = ContractType.parse_obj({"abi": abi}) 167 | NFTSmartContract = Contract(nft_address, ct) 168 | own_nft = NFTSmartContract.balanceOf(current_user.address) > 0 169 | 170 | if own_nft: 171 | return {"content": "Premium content"} 172 | else: 173 | return {"content": "Basic content"} 174 | -------------------------------------------------------------------------------- /chapter_17/token-gated-blog/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { useAccount, useConnect, useDisconnect, useSignMessage } from 'wagmi' 2 | import { useState, useEffect } from 'react'; 3 | 4 | function App() { 5 | const account = useAccount() 6 | const { connectors, connect, status, error } = useConnect() 7 | const { disconnect } = useDisconnect() 8 | const { signMessageAsync } = useSignMessage() 9 | const message = ` 10 | packtpub.com wants you to sign in with your Ethereum account: 11 | $address 12 | 13 | I accept the PacktPub Terms of Service: https://www.packtpub.com/en-us/help/terms-and-conditions 14 | 15 | URI: http://127.0.0.1:8000/token 16 | Version: 1 17 | Chain ID: 1 18 | Nonce: $nonce 19 | Issued At: $time 20 | Resources: 21 | - https://github.com/PacktPublishing/Hands-On-Blockchain-for-Python-Developers--2nd-Edition 22 | `; 23 | 24 | const [nonce, setNonce] = useState('') 25 | const [nonceTime, setNonceTime] = useState('') 26 | const [token, setToken] = useState('') 27 | const [signature, setSignature] = useState('') 28 | const [profile, setProfile] = useState('') 29 | const [content, setContent] = useState('') 30 | const fetchNonce = async () => { 31 | try { 32 | const address = account.addresses[0] 33 | const response = await fetch(`http://localhost:8000/nonce/${address}`, 34 | {method: 'GET', 35 | headers: {'Content-Type': 'application/json'}, 36 | }) 37 | const nonceData = await response.json() 38 | const nonceValue = nonceData.nonce 39 | const nonceTimeValue = nonceData.nonce_time 40 | 41 | setNonce(nonceValue) 42 | setNonceTime(nonceTimeValue) 43 | } catch (error) { 44 | console.error('Error fetching nonce:', error) 45 | } 46 | }; 47 | 48 | 49 | return ( 50 | <> 51 |
52 |

Account

53 | 54 |
55 | status: {account.status} 56 |
57 | addresses: {JSON.stringify(account.addresses)} 58 |
59 | chainId: {account.chainId} 60 |
61 | 62 | {account.status === 'connected' && ( 63 | 66 | )} 67 |
68 | 69 |
70 |

Connect

71 | {connectors.map((connector) => ( 72 | 79 | ))} 80 |
{status}
81 |
{error?.message}
82 |
83 | 84 |
85 |

Nonce

86 | 92 |
Nonce: {nonce}
93 |
94 | 95 |
96 |

Sign Message

97 | 115 |
Signature: {signature}
116 |
Access Token: {token}
117 |
118 | 119 |
120 |

Profile

121 | 134 |
Profile: {profile}
135 |
136 | 137 |
138 |

Content

139 | 152 |
Content: {content}
153 |
154 | 155 | ) 156 | } 157 | 158 | export default App 159 | -------------------------------------------------------------------------------- /chapter_13/nft/contracts/HelloNFT.vy: -------------------------------------------------------------------------------- 1 | #pragma version ^0.3.0 2 | 3 | # Adapted from https://github.com/vyperlang/vyper/blob/master/examples/tokens/ERC721.vy 4 | 5 | from vyper.interfaces import ERC165 6 | from vyper.interfaces import ERC721 7 | 8 | implements: ERC721 9 | implements: ERC165 10 | 11 | event Transfer: 12 | _from: indexed(address) 13 | _to: indexed(address) 14 | _tokenId: indexed(uint256) 15 | 16 | event Approval: 17 | _owner: indexed(address) 18 | _approved: indexed(address) 19 | _tokenId: indexed(uint256) 20 | 21 | event ApprovalForAll: 22 | _owner: indexed(address) 23 | _operator: indexed(address) 24 | _approved: bool 25 | 26 | interface ERC721Receiver: 27 | def onERC721Received( 28 | _operator: address, 29 | _from: address, 30 | _tokenId: uint256, 31 | _data: Bytes[1024] 32 | ) -> bytes4: nonpayable 33 | 34 | name: public(String[32]) 35 | 36 | symbol: public(String[32]) 37 | 38 | idToOwner: HashMap[uint256, address] 39 | 40 | idToApprovals: HashMap[uint256, address] 41 | 42 | ownerToNFTokenCount: HashMap[address, uint256] 43 | 44 | ownerToOperators: HashMap[address, HashMap[address, bool]] 45 | 46 | minter: address 47 | 48 | baseURL: String[53] 49 | 50 | SUPPORTED_INTERFACES: constant(bytes4[2]) = [ 51 | # ERC165 interface ID of ERC165 52 | 0x01ffc9a7, 53 | # ERC165 interface ID of ERC721 54 | 0x80ac58cd, 55 | ] 56 | 57 | @external 58 | def __init__(): 59 | self.minter = msg.sender 60 | self.baseURL = "https://packtpub.com/metadata/" 61 | self.name = "Hello NFT" 62 | self.symbol = "HEL" 63 | 64 | @view 65 | @external 66 | def supportsInterface(interface_id: bytes4) -> bool: 67 | return interface_id in SUPPORTED_INTERFACES 68 | 69 | @view 70 | @external 71 | def balanceOf(_owner: address) -> uint256: 72 | assert _owner != empty(address) 73 | return self.ownerToNFTokenCount[_owner] 74 | 75 | @view 76 | @external 77 | def ownerOf(_tokenId: uint256) -> address: 78 | owner: address = self.idToOwner[_tokenId] 79 | assert owner != empty(address) 80 | return owner 81 | 82 | @view 83 | @external 84 | def getApproved(_tokenId: uint256) -> address: 85 | assert self.idToOwner[_tokenId] != empty(address) 86 | return self.idToApprovals[_tokenId] 87 | 88 | @view 89 | @external 90 | def isApprovedForAll(_owner: address, _operator: address) -> bool: 91 | return (self.ownerToOperators[_owner])[_operator] 92 | 93 | @view 94 | @internal 95 | def _isApprovedOrOwner(_spender: address, _tokenId: uint256) -> bool: 96 | owner: address = self.idToOwner[_tokenId] 97 | spenderIsOwner: bool = owner == _spender 98 | spenderIsApproved: bool = _spender == self.idToApprovals[_tokenId] 99 | spenderIsApprovedForAll: bool = (self.ownerToOperators[owner])[_spender] 100 | return (spenderIsOwner or spenderIsApproved) or spenderIsApprovedForAll 101 | 102 | @internal 103 | def _addTokenTo(_to: address, _tokenId: uint256): 104 | assert self.idToOwner[_tokenId] == empty(address) 105 | self.idToOwner[_tokenId] = _to 106 | self.ownerToNFTokenCount[_to] += 1 107 | 108 | @internal 109 | def _removeTokenFrom(_from: address, _tokenId: uint256): 110 | assert self.idToOwner[_tokenId] == _from 111 | self.idToOwner[_tokenId] = empty(address) 112 | self.ownerToNFTokenCount[_from] -= 1 113 | 114 | @internal 115 | def _clearApproval(_owner: address, _tokenId: uint256): 116 | assert self.idToOwner[_tokenId] == _owner 117 | if self.idToApprovals[_tokenId] != empty(address): 118 | self.idToApprovals[_tokenId] = empty(address) 119 | 120 | @internal 121 | def _transferFrom(_from: address, _to: address, _tokenId: uint256, _sender: address): 122 | assert self._isApprovedOrOwner(_sender, _tokenId) 123 | assert _to != empty(address) 124 | self._clearApproval(_from, _tokenId) 125 | self._removeTokenFrom(_from, _tokenId) 126 | self._addTokenTo(_to, _tokenId) 127 | log Transfer(_from, _to, _tokenId) 128 | 129 | @external 130 | @payable 131 | def transferFrom(_from: address, _to: address, _tokenId: uint256): 132 | self._transferFrom(_from, _to, _tokenId, msg.sender) 133 | 134 | @external 135 | @payable 136 | def safeTransferFrom( 137 | _from: address, 138 | _to: address, 139 | _tokenId: uint256, 140 | _data: Bytes[1024]=b"" 141 | ): 142 | self._transferFrom(_from, _to, _tokenId, msg.sender) 143 | if _to.is_contract: 144 | returnValue: bytes4 = ERC721Receiver(_to).onERC721Received(msg.sender, _from, _tokenId, _data) 145 | assert returnValue == method_id("onERC721Received(address,address,uint256,bytes)", output_type=bytes4) 146 | 147 | @external 148 | @payable 149 | def approve(_approved: address, _tokenId: uint256): 150 | owner: address = self.idToOwner[_tokenId] 151 | assert owner != empty(address) 152 | assert _approved != owner 153 | senderIsOwner: bool = self.idToOwner[_tokenId] == msg.sender 154 | senderIsApprovedForAll: bool = (self.ownerToOperators[owner])[msg.sender] 155 | assert (senderIsOwner or senderIsApprovedForAll) 156 | self.idToApprovals[_tokenId] = _approved 157 | log Approval(owner, _approved, _tokenId) 158 | 159 | @external 160 | def setApprovalForAll(_operator: address, _approved: bool): 161 | assert _operator != msg.sender 162 | self.ownerToOperators[msg.sender][_operator] = _approved 163 | log ApprovalForAll(msg.sender, _operator, _approved) 164 | 165 | @external 166 | def mint(_to: address, _tokenId: uint256) -> bool: 167 | assert msg.sender == self.minter 168 | assert _to != empty(address) 169 | self._addTokenTo(_to, _tokenId) 170 | log Transfer(empty(address), _to, _tokenId) 171 | return True 172 | 173 | @external 174 | def burn(_tokenId: uint256): 175 | assert self._isApprovedOrOwner(msg.sender, _tokenId) 176 | owner: address = self.idToOwner[_tokenId] 177 | assert owner != empty(address) 178 | self._clearApproval(owner, _tokenId) 179 | self._removeTokenFrom(owner, _tokenId) 180 | log Transfer(owner, empty(address), _tokenId) 181 | 182 | @view 183 | @external 184 | def tokenURI(tokenId: uint256) -> String[132]: 185 | return concat(self.baseURL, uint2str(tokenId)) 186 | -------------------------------------------------------------------------------- /chapter_14/nft_marketplace/contracts/HelloNFT.vy: -------------------------------------------------------------------------------- 1 | #pragma version ^0.3.0 2 | 3 | # Adapted from https://github.com/vyperlang/vyper/blob/master/examples/tokens/ERC721.vy 4 | 5 | from vyper.interfaces import ERC165 6 | from vyper.interfaces import ERC721 7 | 8 | implements: ERC721 9 | implements: ERC165 10 | 11 | event Transfer: 12 | _from: indexed(address) 13 | _to: indexed(address) 14 | _tokenId: indexed(uint256) 15 | 16 | event Approval: 17 | _owner: indexed(address) 18 | _approved: indexed(address) 19 | _tokenId: indexed(uint256) 20 | 21 | event ApprovalForAll: 22 | _owner: indexed(address) 23 | _operator: indexed(address) 24 | _approved: bool 25 | 26 | interface ERC721Receiver: 27 | def onERC721Received( 28 | _operator: address, 29 | _from: address, 30 | _tokenId: uint256, 31 | _data: Bytes[1024] 32 | ) -> bytes4: nonpayable 33 | 34 | name: public(String[32]) 35 | 36 | symbol: public(String[32]) 37 | 38 | idToOwner: HashMap[uint256, address] 39 | 40 | idToApprovals: HashMap[uint256, address] 41 | 42 | ownerToNFTokenCount: HashMap[address, uint256] 43 | 44 | ownerToOperators: HashMap[address, HashMap[address, bool]] 45 | 46 | minter: address 47 | 48 | baseURL: String[53] 49 | 50 | SUPPORTED_INTERFACES: constant(bytes4[2]) = [ 51 | # ERC165 interface ID of ERC165 52 | 0x01ffc9a7, 53 | # ERC165 interface ID of ERC721 54 | 0x80ac58cd, 55 | ] 56 | 57 | @external 58 | def __init__(): 59 | self.minter = msg.sender 60 | self.baseURL = "https://packtpub.com/metadata/" 61 | self.name = "Hello NFT" 62 | self.symbol = "HEL" 63 | 64 | @view 65 | @external 66 | def supportsInterface(interface_id: bytes4) -> bool: 67 | return interface_id in SUPPORTED_INTERFACES 68 | 69 | @view 70 | @external 71 | def balanceOf(_owner: address) -> uint256: 72 | assert _owner != empty(address) 73 | return self.ownerToNFTokenCount[_owner] 74 | 75 | @view 76 | @external 77 | def ownerOf(_tokenId: uint256) -> address: 78 | owner: address = self.idToOwner[_tokenId] 79 | assert owner != empty(address) 80 | return owner 81 | 82 | @view 83 | @external 84 | def getApproved(_tokenId: uint256) -> address: 85 | assert self.idToOwner[_tokenId] != empty(address) 86 | return self.idToApprovals[_tokenId] 87 | 88 | @view 89 | @external 90 | def isApprovedForAll(_owner: address, _operator: address) -> bool: 91 | return (self.ownerToOperators[_owner])[_operator] 92 | 93 | @view 94 | @internal 95 | def _isApprovedOrOwner(_spender: address, _tokenId: uint256) -> bool: 96 | owner: address = self.idToOwner[_tokenId] 97 | spenderIsOwner: bool = owner == _spender 98 | spenderIsApproved: bool = _spender == self.idToApprovals[_tokenId] 99 | spenderIsApprovedForAll: bool = (self.ownerToOperators[owner])[_spender] 100 | return (spenderIsOwner or spenderIsApproved) or spenderIsApprovedForAll 101 | 102 | @internal 103 | def _addTokenTo(_to: address, _tokenId: uint256): 104 | assert self.idToOwner[_tokenId] == empty(address) 105 | self.idToOwner[_tokenId] = _to 106 | self.ownerToNFTokenCount[_to] += 1 107 | 108 | @internal 109 | def _removeTokenFrom(_from: address, _tokenId: uint256): 110 | assert self.idToOwner[_tokenId] == _from 111 | self.idToOwner[_tokenId] = empty(address) 112 | self.ownerToNFTokenCount[_from] -= 1 113 | 114 | @internal 115 | def _clearApproval(_owner: address, _tokenId: uint256): 116 | assert self.idToOwner[_tokenId] == _owner 117 | if self.idToApprovals[_tokenId] != empty(address): 118 | self.idToApprovals[_tokenId] = empty(address) 119 | 120 | @internal 121 | def _transferFrom(_from: address, _to: address, _tokenId: uint256, _sender: address): 122 | assert self._isApprovedOrOwner(_sender, _tokenId) 123 | assert _to != empty(address) 124 | self._clearApproval(_from, _tokenId) 125 | self._removeTokenFrom(_from, _tokenId) 126 | self._addTokenTo(_to, _tokenId) 127 | log Transfer(_from, _to, _tokenId) 128 | 129 | @external 130 | @payable 131 | def transferFrom(_from: address, _to: address, _tokenId: uint256): 132 | self._transferFrom(_from, _to, _tokenId, msg.sender) 133 | 134 | @external 135 | @payable 136 | def safeTransferFrom( 137 | _from: address, 138 | _to: address, 139 | _tokenId: uint256, 140 | _data: Bytes[1024]=b"" 141 | ): 142 | self._transferFrom(_from, _to, _tokenId, msg.sender) 143 | if _to.is_contract: 144 | returnValue: bytes4 = ERC721Receiver(_to).onERC721Received(msg.sender, _from, _tokenId, _data) 145 | assert returnValue == method_id("onERC721Received(address,address,uint256,bytes)", output_type=bytes4) 146 | 147 | @external 148 | @payable 149 | def approve(_approved: address, _tokenId: uint256): 150 | owner: address = self.idToOwner[_tokenId] 151 | assert owner != empty(address) 152 | assert _approved != owner 153 | senderIsOwner: bool = self.idToOwner[_tokenId] == msg.sender 154 | senderIsApprovedForAll: bool = (self.ownerToOperators[owner])[msg.sender] 155 | assert (senderIsOwner or senderIsApprovedForAll) 156 | self.idToApprovals[_tokenId] = _approved 157 | log Approval(owner, _approved, _tokenId) 158 | 159 | @external 160 | def setApprovalForAll(_operator: address, _approved: bool): 161 | assert _operator != msg.sender 162 | self.ownerToOperators[msg.sender][_operator] = _approved 163 | log ApprovalForAll(msg.sender, _operator, _approved) 164 | 165 | @external 166 | def mint(_to: address, _tokenId: uint256) -> bool: 167 | assert msg.sender == self.minter 168 | assert _to != empty(address) 169 | self._addTokenTo(_to, _tokenId) 170 | log Transfer(empty(address), _to, _tokenId) 171 | return True 172 | 173 | @external 174 | def burn(_tokenId: uint256): 175 | assert self._isApprovedOrOwner(msg.sender, _tokenId) 176 | owner: address = self.idToOwner[_tokenId] 177 | assert owner != empty(address) 178 | self._clearApproval(owner, _tokenId) 179 | self._removeTokenFrom(owner, _tokenId) 180 | log Transfer(owner, empty(address), _tokenId) 181 | 182 | @view 183 | @external 184 | def tokenURI(tokenId: uint256) -> String[132]: 185 | return concat(self.baseURL, uint2str(tokenId)) 186 | -------------------------------------------------------------------------------- /chapter_17/token-gated-smart-contract/contracts/HelloNFT.vy: -------------------------------------------------------------------------------- 1 | #pragma version ^0.3.0 2 | 3 | # Adapted from https://github.com/vyperlang/vyper/blob/master/examples/tokens/ERC721.vy 4 | 5 | from vyper.interfaces import ERC165 6 | from vyper.interfaces import ERC721 7 | 8 | implements: ERC721 9 | implements: ERC165 10 | 11 | event Transfer: 12 | _from: indexed(address) 13 | _to: indexed(address) 14 | _tokenId: indexed(uint256) 15 | 16 | event Approval: 17 | _owner: indexed(address) 18 | _approved: indexed(address) 19 | _tokenId: indexed(uint256) 20 | 21 | event ApprovalForAll: 22 | _owner: indexed(address) 23 | _operator: indexed(address) 24 | _approved: bool 25 | 26 | interface ERC721Receiver: 27 | def onERC721Received( 28 | _operator: address, 29 | _from: address, 30 | _tokenId: uint256, 31 | _data: Bytes[1024] 32 | ) -> bytes4: nonpayable 33 | 34 | name: public(String[32]) 35 | 36 | symbol: public(String[32]) 37 | 38 | idToOwner: HashMap[uint256, address] 39 | 40 | idToApprovals: HashMap[uint256, address] 41 | 42 | ownerToNFTokenCount: HashMap[address, uint256] 43 | 44 | ownerToOperators: HashMap[address, HashMap[address, bool]] 45 | 46 | minter: address 47 | 48 | baseURL: String[53] 49 | 50 | SUPPORTED_INTERFACES: constant(bytes4[2]) = [ 51 | # ERC165 interface ID of ERC165 52 | 0x01ffc9a7, 53 | # ERC165 interface ID of ERC721 54 | 0x80ac58cd, 55 | ] 56 | 57 | @external 58 | def __init__(): 59 | self.minter = msg.sender 60 | self.baseURL = "https://packtpub.com/metadata/" 61 | self.name = "Hello NFT" 62 | self.symbol = "HEL" 63 | 64 | @view 65 | @external 66 | def supportsInterface(interface_id: bytes4) -> bool: 67 | return interface_id in SUPPORTED_INTERFACES 68 | 69 | @view 70 | @external 71 | def balanceOf(_owner: address) -> uint256: 72 | assert _owner != empty(address) 73 | return self.ownerToNFTokenCount[_owner] 74 | 75 | @view 76 | @external 77 | def ownerOf(_tokenId: uint256) -> address: 78 | owner: address = self.idToOwner[_tokenId] 79 | assert owner != empty(address) 80 | return owner 81 | 82 | @view 83 | @external 84 | def getApproved(_tokenId: uint256) -> address: 85 | assert self.idToOwner[_tokenId] != empty(address) 86 | return self.idToApprovals[_tokenId] 87 | 88 | @view 89 | @external 90 | def isApprovedForAll(_owner: address, _operator: address) -> bool: 91 | return (self.ownerToOperators[_owner])[_operator] 92 | 93 | @view 94 | @internal 95 | def _isApprovedOrOwner(_spender: address, _tokenId: uint256) -> bool: 96 | owner: address = self.idToOwner[_tokenId] 97 | spenderIsOwner: bool = owner == _spender 98 | spenderIsApproved: bool = _spender == self.idToApprovals[_tokenId] 99 | spenderIsApprovedForAll: bool = (self.ownerToOperators[owner])[_spender] 100 | return (spenderIsOwner or spenderIsApproved) or spenderIsApprovedForAll 101 | 102 | @internal 103 | def _addTokenTo(_to: address, _tokenId: uint256): 104 | assert self.idToOwner[_tokenId] == empty(address) 105 | self.idToOwner[_tokenId] = _to 106 | self.ownerToNFTokenCount[_to] += 1 107 | 108 | @internal 109 | def _removeTokenFrom(_from: address, _tokenId: uint256): 110 | assert self.idToOwner[_tokenId] == _from 111 | self.idToOwner[_tokenId] = empty(address) 112 | self.ownerToNFTokenCount[_from] -= 1 113 | 114 | @internal 115 | def _clearApproval(_owner: address, _tokenId: uint256): 116 | assert self.idToOwner[_tokenId] == _owner 117 | if self.idToApprovals[_tokenId] != empty(address): 118 | self.idToApprovals[_tokenId] = empty(address) 119 | 120 | @internal 121 | def _transferFrom(_from: address, _to: address, _tokenId: uint256, _sender: address): 122 | assert self._isApprovedOrOwner(_sender, _tokenId) 123 | assert _to != empty(address) 124 | self._clearApproval(_from, _tokenId) 125 | self._removeTokenFrom(_from, _tokenId) 126 | self._addTokenTo(_to, _tokenId) 127 | log Transfer(_from, _to, _tokenId) 128 | 129 | @external 130 | @payable 131 | def transferFrom(_from: address, _to: address, _tokenId: uint256): 132 | self._transferFrom(_from, _to, _tokenId, msg.sender) 133 | 134 | @external 135 | @payable 136 | def safeTransferFrom( 137 | _from: address, 138 | _to: address, 139 | _tokenId: uint256, 140 | _data: Bytes[1024]=b"" 141 | ): 142 | self._transferFrom(_from, _to, _tokenId, msg.sender) 143 | if _to.is_contract: 144 | returnValue: bytes4 = ERC721Receiver(_to).onERC721Received(msg.sender, _from, _tokenId, _data) 145 | assert returnValue == method_id("onERC721Received(address,address,uint256,bytes)", output_type=bytes4) 146 | 147 | @external 148 | @payable 149 | def approve(_approved: address, _tokenId: uint256): 150 | owner: address = self.idToOwner[_tokenId] 151 | assert owner != empty(address) 152 | assert _approved != owner 153 | senderIsOwner: bool = self.idToOwner[_tokenId] == msg.sender 154 | senderIsApprovedForAll: bool = (self.ownerToOperators[owner])[msg.sender] 155 | assert (senderIsOwner or senderIsApprovedForAll) 156 | self.idToApprovals[_tokenId] = _approved 157 | log Approval(owner, _approved, _tokenId) 158 | 159 | @external 160 | def setApprovalForAll(_operator: address, _approved: bool): 161 | assert _operator != msg.sender 162 | self.ownerToOperators[msg.sender][_operator] = _approved 163 | log ApprovalForAll(msg.sender, _operator, _approved) 164 | 165 | @external 166 | def mint(_to: address, _tokenId: uint256) -> bool: 167 | assert msg.sender == self.minter 168 | assert _to != empty(address) 169 | self._addTokenTo(_to, _tokenId) 170 | log Transfer(empty(address), _to, _tokenId) 171 | return True 172 | 173 | @external 174 | def burn(_tokenId: uint256): 175 | assert self._isApprovedOrOwner(msg.sender, _tokenId) 176 | owner: address = self.idToOwner[_tokenId] 177 | assert owner != empty(address) 178 | self._clearApproval(owner, _tokenId) 179 | self._removeTokenFrom(owner, _tokenId) 180 | log Transfer(owner, empty(address), _tokenId) 181 | 182 | @view 183 | @external 184 | def tokenURI(tokenId: uint256) -> String[132]: 185 | return concat(self.baseURL, uint2str(tokenId)) 186 | -------------------------------------------------------------------------------- /chapter_10/decentralized_videos/videos/models.py: -------------------------------------------------------------------------------- 1 | import os.path, json 2 | import asyncio 3 | import aioipfs 4 | import cv2 5 | import os 6 | from ape import accounts, Contract, project 7 | from ape import networks 8 | from ethpm_types import ContractType 9 | from ape_ethereum.transactions import Receipt 10 | from web3 import Web3, IPCProvider 11 | from decentralized_videos.settings import STATICFILES_DIRS, STATIC_URL, BASE_DIR, MEDIA_ROOT 12 | 13 | BLOCKCHAIN_NETWORK = "local" 14 | BLOCKCHAIN_PROVIDER = "geth" 15 | 16 | loop = asyncio.new_event_loop() 17 | asyncio.set_event_loop(loop) 18 | 19 | async def get(ipfs_path, download_path): 20 | client = aioipfs.AsyncIPFS() 21 | 22 | await client.get(ipfs_path, dstdir=download_path) 23 | await client.close() 24 | 25 | async def add(file_path): 26 | client = aioipfs.AsyncIPFS() 27 | 28 | files = [file_path] 29 | hash = None 30 | async for added_file in client.add(files): 31 | hash = added_file['Hash'] 32 | await client.close() 33 | return hash 34 | 35 | class VideosSharing: 36 | 37 | def __init__(self): 38 | self.w3 = Web3(IPCProvider('/tmp/geth.ipc')) 39 | self.address = Web3.to_checksum_address(os.environ["VIDEO_SHARING_ADDRESS"]) 40 | 41 | with open('../videos_sharing_smart_contract/.build/VideoSharing.json') as f: 42 | contract = json.load(f) 43 | self.abi = contract['abi'] 44 | 45 | def recent_videos(self, amount=20): 46 | with networks.ethereum[BLOCKCHAIN_NETWORK].use_provider(BLOCKCHAIN_PROVIDER): 47 | ct = ContractType.parse_obj({"abi": self.abi}) 48 | self.SmartContract = Contract(self.address, ct) 49 | events = self.SmartContract.UploadVideo.query("*", start_block=0) 50 | 51 | videos = [] 52 | for event in events["event_arguments"]: 53 | video = {} 54 | video['user'] = event['_user'] 55 | video['index'] = event['_index'] 56 | video['path'] = self.get_video_path(video['user'], video['index']) 57 | video['title'] = self.get_video_title(video['user'], video['index']) 58 | video['thumbnail'] = self.get_video_thumbnail(video['path']) 59 | videos.append(video) 60 | videos.reverse() 61 | return videos[:amount] 62 | 63 | def get_video_path(self, user, index): 64 | with networks.ethereum[BLOCKCHAIN_NETWORK].use_provider(BLOCKCHAIN_PROVIDER): 65 | ct = ContractType.parse_obj({"abi": self.abi}) 66 | self.SmartContract = Contract(self.address, ct) 67 | return self.SmartContract.videos_path(user, index) 68 | 69 | def get_video_title(self, user, index): 70 | with networks.ethereum[BLOCKCHAIN_NETWORK].use_provider(BLOCKCHAIN_PROVIDER): 71 | ct = ContractType.parse_obj({"abi": self.abi}) 72 | self.SmartContract = Contract(self.address, ct) 73 | return self.SmartContract.videos_title(user, index) 74 | 75 | def get_video_thumbnail(self, ipfs_path): 76 | thumbnail_file = str(STATICFILES_DIRS[0]) + '/' + ipfs_path + '.png' 77 | url_file = STATIC_URL + '/' + ipfs_path + '.png' 78 | print(thumbnail_file) 79 | 80 | if os.path.isfile(thumbnail_file): 81 | return url_file 82 | else: 83 | return "https://bulma.io/images/placeholders/640x480.png" 84 | 85 | def get_video(self, user, index): 86 | video = {} 87 | ipfs_path = self.get_video_path(user, index) 88 | video_title = self.get_video_title(user, index) 89 | video_file = str(STATICFILES_DIRS[0]) + '/' + ipfs_path + '.mp4' 90 | thumbnail_file = str(STATICFILES_DIRS[0]) + '/' + ipfs_path + '.png' 91 | video['title'] = video_title 92 | video['user'] = user 93 | video['index'] = index 94 | with networks.ethereum[BLOCKCHAIN_NETWORK].use_provider(BLOCKCHAIN_PROVIDER): 95 | ct = ContractType.parse_obj({"abi": self.abi}) 96 | self.SmartContract = Contract(self.address, ct) 97 | video['aggregate_likes'] = self.SmartContract.video_aggregate_likes(user, index) 98 | 99 | if os.path.isfile(video_file): 100 | video['url'] = STATIC_URL + '/' + ipfs_path + '.mp4' 101 | else: 102 | loop.run_until_complete(get(ipfs_path, str(BASE_DIR))) 103 | video['url'] = STATIC_URL + '/' + ipfs_path + '.mp4' 104 | os.rename(str(BASE_DIR) + '/' + ipfs_path, str(STATICFILES_DIRS[0]) + '/' + ipfs_path + '.mp4') 105 | 106 | if not os.path.isfile(thumbnail_file): 107 | self.process_thumbnail(ipfs_path) 108 | 109 | return video 110 | 111 | def upload_video(self, video_user, password, video_file, title): 112 | video_path = str(MEDIA_ROOT) + '/video.mp4' 113 | with open(video_path, 'wb+') as destination: 114 | for chunk in video_file.chunks(): 115 | destination.write(chunk) 116 | ipfs_path = loop.run_until_complete(add(video_path)) 117 | title = title[:19] 118 | with networks.ethereum[BLOCKCHAIN_NETWORK].use_provider(BLOCKCHAIN_PROVIDER): 119 | ct = ContractType.parse_obj({"abi": self.abi}) 120 | self.SmartContract = Contract(self.address, ct) 121 | sender = accounts.load(video_user) 122 | sender.set_autosign(True, passphrase=password) 123 | self.SmartContract.upload_video(ipfs_path, title, sender=sender) 124 | 125 | def process_thumbnail(self, ipfs_path): 126 | thumbnail_file = str(STATICFILES_DIRS[0]) + '/' + ipfs_path + '.png' 127 | if not os.path.isfile(thumbnail_file): 128 | video_path = str(STATICFILES_DIRS[0]) + '/' + ipfs_path + '.mp4' 129 | cap = cv2.VideoCapture(video_path) 130 | cap.set(cv2.CAP_PROP_POS_FRAMES, 0) 131 | _, frame = cap.read() 132 | cv2.imwrite(thumbnail_file, frame) 133 | 134 | def like_video(self, video_liker, password, video_user, index): 135 | with networks.ethereum[BLOCKCHAIN_NETWORK].use_provider(BLOCKCHAIN_PROVIDER): 136 | ct = ContractType.parse_obj({"abi": self.abi}) 137 | self.SmartContract = Contract(self.address, ct) 138 | sender = accounts.load(video_liker) 139 | if self.SmartContract.video_has_been_liked(sender.address, video_user, index): 140 | return 141 | sender.set_autosign(True, passphrase=password) 142 | self.SmartContract.like_video(video_user, index, sender=sender) 143 | 144 | 145 | videos_sharing = VideosSharing() 146 | -------------------------------------------------------------------------------- /chapter_15/vault/contracts/Vault.vy: -------------------------------------------------------------------------------- 1 | #pragma version ^0.3.0 2 | 3 | # From https://github.com/fubuloubu/ERC4626/blob/main/contracts/VyperVault.vy 4 | 5 | from vyper.interfaces import ERC20 6 | from vyper.interfaces import ERC4626 7 | 8 | implements: ERC20 9 | implements: ERC4626 10 | 11 | ##### ERC20 ##### 12 | 13 | totalSupply: public(uint256) 14 | balanceOf: public(HashMap[address, uint256]) 15 | allowance: public(HashMap[address, HashMap[address, uint256]]) 16 | 17 | NAME: constant(String[11]) = "Hello Vault" 18 | SYMBOL: constant(String[6]) = "vHELLO" 19 | DECIMALS: constant(uint8) = 18 20 | 21 | event Transfer: 22 | sender: indexed(address) 23 | receiver: indexed(address) 24 | amount: uint256 25 | 26 | event Approval: 27 | owner: indexed(address) 28 | spender: indexed(address) 29 | allowance: uint256 30 | 31 | ##### ERC4626 ##### 32 | 33 | asset: public(ERC20) 34 | 35 | event Deposit: 36 | depositor: indexed(address) 37 | receiver: indexed(address) 38 | assets: uint256 39 | shares: uint256 40 | 41 | event Withdraw: 42 | withdrawer: indexed(address) 43 | receiver: indexed(address) 44 | owner: indexed(address) 45 | assets: uint256 46 | shares: uint256 47 | 48 | 49 | @external 50 | def __init__(asset: ERC20): 51 | self.asset = asset 52 | 53 | 54 | @view 55 | @external 56 | def name() -> String[11]: 57 | return NAME 58 | 59 | 60 | @view 61 | @external 62 | def symbol() -> String[6]: 63 | return SYMBOL 64 | 65 | 66 | @view 67 | @external 68 | def decimals() -> uint8: 69 | return DECIMALS 70 | 71 | 72 | @external 73 | def transfer(receiver: address, amount: uint256) -> bool: 74 | self.balanceOf[msg.sender] -= amount 75 | self.balanceOf[receiver] += amount 76 | log Transfer(msg.sender, receiver, amount) 77 | return True 78 | 79 | 80 | @external 81 | def approve(spender: address, amount: uint256) -> bool: 82 | self.allowance[msg.sender][spender] = amount 83 | log Approval(msg.sender, spender, amount) 84 | return True 85 | 86 | 87 | @external 88 | def transferFrom(sender: address, receiver: address, amount: uint256) -> bool: 89 | self.allowance[sender][msg.sender] -= amount 90 | self.balanceOf[sender] -= amount 91 | self.balanceOf[receiver] += amount 92 | log Transfer(sender, receiver, amount) 93 | return True 94 | 95 | 96 | @view 97 | @external 98 | def totalAssets() -> uint256: 99 | return self.asset.balanceOf(self) 100 | 101 | 102 | @view 103 | @internal 104 | def _convertToAssets(shareAmount: uint256) -> uint256: 105 | totalSupply: uint256 = self.totalSupply 106 | if totalSupply == 0: 107 | return 0 108 | 109 | return shareAmount * self.asset.balanceOf(self) / totalSupply 110 | 111 | 112 | @view 113 | @external 114 | def convertToAssets(shareAmount: uint256) -> uint256: 115 | return self._convertToAssets(shareAmount) 116 | 117 | 118 | @view 119 | @internal 120 | def _convertToShares(assetAmount: uint256) -> uint256: 121 | totalSupply: uint256 = self.totalSupply 122 | totalAssets: uint256 = self.asset.balanceOf(self) 123 | if totalAssets == 0 or totalSupply == 0: 124 | return assetAmount # 1:1 price 125 | 126 | return assetAmount * totalSupply / totalAssets 127 | 128 | 129 | @view 130 | @external 131 | def convertToShares(assetAmount: uint256) -> uint256: 132 | return self._convertToShares(assetAmount) 133 | 134 | 135 | @view 136 | @external 137 | def maxDeposit(owner: address) -> uint256: 138 | return MAX_UINT256 139 | 140 | 141 | @view 142 | @external 143 | def previewDeposit(assets: uint256) -> uint256: 144 | return self._convertToShares(assets) 145 | 146 | 147 | @external 148 | def deposit(assets: uint256, receiver: address=msg.sender) -> uint256: 149 | shares: uint256 = self._convertToShares(assets) 150 | self.asset.transferFrom(msg.sender, self, assets) 151 | 152 | self.totalSupply += shares 153 | self.balanceOf[receiver] += shares 154 | log Transfer(empty(address), receiver, shares) 155 | log Deposit(msg.sender, receiver, assets, shares) 156 | return shares 157 | 158 | 159 | @view 160 | @external 161 | def maxMint(owner: address) -> uint256: 162 | return MAX_UINT256 163 | 164 | 165 | @view 166 | @external 167 | def previewMint(shares: uint256) -> uint256: 168 | assets: uint256 = self._convertToAssets(shares) 169 | 170 | # NOTE: Vyper does lazy eval on if, so this avoids SLOADs most of the time 171 | if assets == 0 and self.asset.balanceOf(self) == 0: 172 | return shares # NOTE: Assume 1:1 price if nothing deposited yet 173 | 174 | return assets 175 | 176 | 177 | @external 178 | def mint(shares: uint256, receiver: address=msg.sender) -> uint256: 179 | assets: uint256 = self._convertToAssets(shares) 180 | 181 | if assets == 0 and self.asset.balanceOf(self) == 0: 182 | assets = shares # NOTE: Assume 1:1 price if nothing deposited yet 183 | 184 | self.asset.transferFrom(msg.sender, self, assets) 185 | 186 | self.totalSupply += shares 187 | self.balanceOf[receiver] += shares 188 | log Transfer(empty(address), receiver, shares) 189 | log Deposit(msg.sender, receiver, assets, shares) 190 | return assets 191 | 192 | 193 | @view 194 | @external 195 | def maxWithdraw(owner: address) -> uint256: 196 | return MAX_UINT256 # real max is `self.asset.balanceOf(self)` 197 | 198 | 199 | @view 200 | @external 201 | def previewWithdraw(assets: uint256) -> uint256: 202 | shares: uint256 = self._convertToShares(assets) 203 | 204 | # NOTE: Vyper does lazy eval on if, so this avoids SLOADs most of the time 205 | if shares == assets and self.totalSupply == 0: 206 | return 0 # NOTE: Nothing to redeem 207 | 208 | return shares 209 | 210 | 211 | @external 212 | def withdraw(assets: uint256, receiver: address=msg.sender, owner: address=msg.sender) -> uint256: 213 | shares: uint256 = self._convertToShares(assets) 214 | 215 | # NOTE: Vyper does lazy eval on if, so this avoids SLOADs most of the time 216 | if shares == assets and self.totalSupply == 0: 217 | raise # Nothing to redeem 218 | 219 | if owner != msg.sender: 220 | self.allowance[owner][msg.sender] -= shares 221 | 222 | self.totalSupply -= shares 223 | self.balanceOf[owner] -= shares 224 | 225 | self.asset.transfer(receiver, assets) 226 | log Transfer(owner, empty(address), shares) 227 | log Withdraw(msg.sender, receiver, owner, assets, shares) 228 | return shares 229 | 230 | 231 | @view 232 | @external 233 | def maxRedeem(owner: address) -> uint256: 234 | return MAX_UINT256 # real max is `self.totalSupply` 235 | 236 | 237 | @view 238 | @external 239 | def previewRedeem(shares: uint256) -> uint256: 240 | return self._convertToAssets(shares) 241 | 242 | 243 | @external 244 | def redeem(shares: uint256, receiver: address=msg.sender, owner: address=msg.sender) -> uint256: 245 | if owner != msg.sender: 246 | self.allowance[owner][msg.sender] -= shares 247 | 248 | assets: uint256 = self._convertToAssets(shares) 249 | self.totalSupply -= shares 250 | self.balanceOf[owner] -= shares 251 | 252 | self.asset.transfer(receiver, assets) 253 | log Transfer(owner, empty(address), shares) 254 | log Withdraw(msg.sender, receiver, owner, assets, shares) 255 | return assets 256 | 257 | -------------------------------------------------------------------------------- /chapter_15/vault/contracts/LendingVault.vy: -------------------------------------------------------------------------------- 1 | #pragma version ^0.3.0 2 | 3 | # From https://github.com/fubuloubu/ERC4626/blob/main/contracts/VyperVault.vy 4 | 5 | event Lend: 6 | _borrower: indexed(address) 7 | _amount: indexed(uint256) 8 | 9 | event Repay: 10 | _borrower: indexed(address) 11 | _amount: indexed(uint256) 12 | 13 | loan_amount: public(uint256) 14 | collateral: public(uint256) 15 | borrower: public(address) 16 | loan_taken: public(bool) 17 | interest: public(uint256) 18 | owner: public(address) 19 | 20 | from vyper.interfaces import ERC20 21 | from vyper.interfaces import ERC4626 22 | 23 | implements: ERC20 24 | implements: ERC4626 25 | 26 | ##### ERC20 ##### 27 | 28 | totalSupply: public(uint256) 29 | balanceOf: public(HashMap[address, uint256]) 30 | allowance: public(HashMap[address, HashMap[address, uint256]]) 31 | 32 | NAME: constant(String[11]) = "Hello Vault" 33 | SYMBOL: constant(String[6]) = "vHELLO" 34 | DECIMALS: constant(uint8) = 18 35 | 36 | event Transfer: 37 | sender: indexed(address) 38 | receiver: indexed(address) 39 | amount: uint256 40 | 41 | event Approval: 42 | owner: indexed(address) 43 | spender: indexed(address) 44 | allowance: uint256 45 | 46 | ##### ERC4626 ##### 47 | 48 | asset: public(ERC20) 49 | 50 | event Deposit: 51 | depositor: indexed(address) 52 | receiver: indexed(address) 53 | assets: uint256 54 | shares: uint256 55 | 56 | event Withdraw: 57 | withdrawer: indexed(address) 58 | receiver: indexed(address) 59 | owner: indexed(address) 60 | assets: uint256 61 | shares: uint256 62 | 63 | 64 | @external 65 | def __init__(asset: ERC20, 66 | _borrower: address, 67 | _loan_amount: uint256, 68 | _collateral: uint256, 69 | _interest: uint256): 70 | self.asset = asset 71 | self.loan_amount = _loan_amount 72 | self.collateral = _collateral 73 | self.borrower = _borrower 74 | self.interest = _interest 75 | self.owner = msg.sender 76 | 77 | 78 | @view 79 | @external 80 | def name() -> String[11]: 81 | return NAME 82 | 83 | 84 | @view 85 | @external 86 | def symbol() -> String[6]: 87 | return SYMBOL 88 | 89 | 90 | @view 91 | @external 92 | def decimals() -> uint8: 93 | return DECIMALS 94 | 95 | 96 | @external 97 | def transfer(receiver: address, amount: uint256) -> bool: 98 | self.balanceOf[msg.sender] -= amount 99 | self.balanceOf[receiver] += amount 100 | log Transfer(msg.sender, receiver, amount) 101 | return True 102 | 103 | 104 | @external 105 | def approve(spender: address, amount: uint256) -> bool: 106 | self.allowance[msg.sender][spender] = amount 107 | log Approval(msg.sender, spender, amount) 108 | return True 109 | 110 | 111 | @external 112 | def transferFrom(sender: address, receiver: address, amount: uint256) -> bool: 113 | self.allowance[sender][msg.sender] -= amount 114 | self.balanceOf[sender] -= amount 115 | self.balanceOf[receiver] += amount 116 | log Transfer(sender, receiver, amount) 117 | return True 118 | 119 | 120 | @view 121 | @external 122 | def totalAssets() -> uint256: 123 | return self.asset.balanceOf(self) 124 | 125 | 126 | @view 127 | @internal 128 | def _convertToAssets(shareAmount: uint256) -> uint256: 129 | totalSupply: uint256 = self.totalSupply 130 | if totalSupply == 0: 131 | return 0 132 | 133 | return shareAmount * self.asset.balanceOf(self) / totalSupply 134 | 135 | 136 | @view 137 | @external 138 | def convertToAssets(shareAmount: uint256) -> uint256: 139 | return self._convertToAssets(shareAmount) 140 | 141 | 142 | @view 143 | @internal 144 | def _convertToShares(assetAmount: uint256) -> uint256: 145 | totalSupply: uint256 = self.totalSupply 146 | totalAssets: uint256 = self.asset.balanceOf(self) 147 | if totalAssets == 0 or totalSupply == 0: 148 | return assetAmount # 1:1 price 149 | 150 | return assetAmount * totalSupply / totalAssets 151 | 152 | 153 | @view 154 | @external 155 | def convertToShares(assetAmount: uint256) -> uint256: 156 | return self._convertToShares(assetAmount) 157 | 158 | 159 | @view 160 | @external 161 | def maxDeposit(owner: address) -> uint256: 162 | return MAX_UINT256 163 | 164 | 165 | @view 166 | @external 167 | def previewDeposit(assets: uint256) -> uint256: 168 | return self._convertToShares(assets) 169 | 170 | 171 | @external 172 | def deposit(assets: uint256, receiver: address=msg.sender) -> uint256: 173 | shares: uint256 = self._convertToShares(assets) 174 | self.asset.transferFrom(msg.sender, self, assets) 175 | 176 | self.totalSupply += shares 177 | self.balanceOf[receiver] += shares 178 | log Transfer(empty(address), receiver, shares) 179 | log Deposit(msg.sender, receiver, assets, shares) 180 | return shares 181 | 182 | 183 | @view 184 | @external 185 | def maxMint(owner: address) -> uint256: 186 | return MAX_UINT256 187 | 188 | 189 | @view 190 | @external 191 | def previewMint(shares: uint256) -> uint256: 192 | assets: uint256 = self._convertToAssets(shares) 193 | 194 | # NOTE: Vyper does lazy eval on if, so this avoids SLOADs most of the time 195 | if assets == 0 and self.asset.balanceOf(self) == 0: 196 | return shares # NOTE: Assume 1:1 price if nothing deposited yet 197 | 198 | return assets 199 | 200 | 201 | @external 202 | def mint(shares: uint256, receiver: address=msg.sender) -> uint256: 203 | assets: uint256 = self._convertToAssets(shares) 204 | 205 | if assets == 0 and self.asset.balanceOf(self) == 0: 206 | assets = shares # NOTE: Assume 1:1 price if nothing deposited yet 207 | 208 | self.asset.transferFrom(msg.sender, self, assets) 209 | 210 | self.totalSupply += shares 211 | self.balanceOf[receiver] += shares 212 | log Transfer(empty(address), receiver, shares) 213 | log Deposit(msg.sender, receiver, assets, shares) 214 | return assets 215 | 216 | 217 | @view 218 | @external 219 | def maxWithdraw(owner: address) -> uint256: 220 | return MAX_UINT256 # real max is `self.asset.balanceOf(self)` 221 | 222 | 223 | @view 224 | @external 225 | def previewWithdraw(assets: uint256) -> uint256: 226 | shares: uint256 = self._convertToShares(assets) 227 | 228 | # NOTE: Vyper does lazy eval on if, so this avoids SLOADs most of the time 229 | if shares == assets and self.totalSupply == 0: 230 | return 0 # NOTE: Nothing to redeem 231 | 232 | return shares 233 | 234 | 235 | @external 236 | def withdraw(assets: uint256, receiver: address=msg.sender, owner: address=msg.sender) -> uint256: 237 | shares: uint256 = self._convertToShares(assets) 238 | 239 | # NOTE: Vyper does lazy eval on if, so this avoids SLOADs most of the time 240 | if shares == assets and self.totalSupply == 0: 241 | raise # Nothing to redeem 242 | 243 | if owner != msg.sender: 244 | self.allowance[owner][msg.sender] -= shares 245 | 246 | self.totalSupply -= shares 247 | self.balanceOf[owner] -= shares 248 | 249 | self.asset.transfer(receiver, assets) 250 | log Transfer(owner, empty(address), shares) 251 | log Withdraw(msg.sender, receiver, owner, assets, shares) 252 | return shares 253 | 254 | 255 | @view 256 | @external 257 | def maxRedeem(owner: address) -> uint256: 258 | return MAX_UINT256 # real max is `self.totalSupply` 259 | 260 | 261 | @view 262 | @external 263 | def previewRedeem(shares: uint256) -> uint256: 264 | return self._convertToAssets(shares) 265 | 266 | 267 | @external 268 | def redeem(shares: uint256, receiver: address=msg.sender, owner: address=msg.sender) -> uint256: 269 | if owner != msg.sender: 270 | self.allowance[owner][msg.sender] -= shares 271 | 272 | assets: uint256 = self._convertToAssets(shares) 273 | self.totalSupply -= shares 274 | self.balanceOf[owner] -= shares 275 | 276 | self.asset.transfer(receiver, assets) 277 | log Transfer(owner, empty(address), shares) 278 | log Withdraw(msg.sender, receiver, owner, assets, shares) 279 | return assets 280 | 281 | 282 | @external 283 | @payable 284 | def borrow(): 285 | assert msg.sender == self.borrower, "Only the borrower can borrow the asset" 286 | assert msg.value == self.collateral, "Collateral is not enough" 287 | self.asset.transfer(msg.sender, self.loan_amount) 288 | self.loan_taken = True 289 | 290 | log Lend(msg.sender, self.loan_amount) 291 | 292 | 293 | @external 294 | def repay(): 295 | assert msg.sender == self.borrower, "Only the borrower can repay the loan" 296 | assert self.loan_taken == True, "Loan has not been taken" 297 | 298 | cut: uint256 = self.interest * self.collateral / 100 299 | payback: uint256 = self.collateral - cut 300 | 301 | self.asset.transferFrom(msg.sender, self, self.loan_amount) 302 | send(msg.sender, payback) 303 | 304 | self.loan_taken = False 305 | 306 | log Repay(msg.sender, self.loan_amount) 307 | --------------------------------------------------------------------------------