diff --git a/README.md b/README.md index 683506c18..ad2f2396c 100644 --- a/README.md +++ b/README.md @@ -139,8 +139,7 @@ DAPP架构请参考文章--[从架构维度看Web2.0与Web3.0应用之别](https 32. [chainlink-keeper](basic/32-chainlink-keeper/README.md) ✅ 33. [pooltogether](basic/33-pooltogether/README.md) ⌛ 34. [subgraph](basic/34-subgraph/readme.md) ⬜ -35. [StarkNet](https://starkware.co/starknet/) ⬜ -欢迎提交 PR,[添加新的基础任务或者更新上面的任务](https://github.com/rebase-network/Dapp-Learning/issues/new) +35. [StarkNet](basic/35-starkNet/readme.md) ⌛ 36. [NFT FileCoin](basic/36-nft-ipfs/README.md) ✅ 37. [Charm.fi](https://github.com/charmfinance/alpha-vaults-contracts) ✅ 38. [Flashbots provider with ethers.js](https://github.com/flashbots/ethers-provider-flashbots-bundle) ⬜ @@ -186,9 +185,15 @@ DAPP架构请参考文章--[从架构维度看Web2.0与Web3.0应用之别](https 开发者可以在开发群里发起项目研究倡议,建立小组,进行协作。 ## DeFi 进阶 - 建议先阅读DEFI经典书籍: + **建议先阅读DEFI经典书籍:** [How to DeFi](https://assets.coingecko.com/books/how-to-defi/How_to_DeFi_Chinese.pdf) [How to DeFi:Advanced](https://nigdaemon.gitbook.io/how-to-defi-advanced-zhogn-wen-b/) + **DEFI学习路线图:** +交易 -> 借贷 ->衍生品 +可以参考我们学习小组的学习路线图,配合视频学习更佳: +- 交易: uniV1(task13) -> uniV2 -> uniV3 -> CurveV1 -> CurveV2 +- 借贷: Aave -> Compound -> Liquity -> Euler +- 衍生品: SNX -> YFI -> Perpetual 01. [UniswapV2](defi/Uniswap-V2/readme.md) ✅ 02. [UniswapV3](defi/Uniswap-V3/readme.md) ✅ 03. [Compound](defi/Compound/readme.md) ✅ diff --git a/basic/22-zk-snarkjs/readme.md b/basic/22-zk-snarkjs/readme.md index e1d5a4d9f..648237b13 100644 --- a/basic/22-zk-snarkjs/readme.md +++ b/basic/22-zk-snarkjs/readme.md @@ -141,6 +141,5 @@ npx snarkjs zkey export soliditycalldata public.json proof.json ## 参考资料 -https://learnblockchain.cn/article/1078 -https://iden3.io/blog/circom-and-snarkjs-tutorial2.html -https://github.com/iden3/circom/blob/master/TUTORIAL.md +- 创建第一个零知识 snark 电路: https://learnblockchain.cn/article/1078 +- circom2 doc:https://docs.circom.io/circom-language/basic-operators/ diff --git a/basic/30-zksync-layer2/imgs/zkrollup.png b/basic/30-zksync-layer2/imgs/zkrollup.png new file mode 100644 index 000000000..7cd8d0f33 Binary files /dev/null and b/basic/30-zksync-layer2/imgs/zkrollup.png differ diff --git a/basic/30-zksync-layer2/readme.md b/basic/30-zksync-layer2/readme.md index e13654a5c..8fa51a1b1 100644 --- a/basic/30-zksync-layer2/readme.md +++ b/basic/30-zksync-layer2/readme.md @@ -2,6 +2,13 @@ [主页](https://github.com/matter-labs/zksync) +## ZK-Rollup +zkRollup在链下利用Merkle tree存储账户状态,由Operator收集用户的交易,交易收集完成后Operator会执行每个交易(校验余额,校验nonce,校验签名,执行状态转换),当交易执行完成后会产生一个新的Merkle tree Root,为了证明链下状态转移是正确的,Operator会在交易执行完成后生成一个零知识证明的proof。 +Operator执行交易后本地的merkle tree root会由prev state root转换成post state root。 +![zkrollup](./imgs/zkrollup.png) + + + ## 基本架构 zkSync 的基本组成有: @@ -24,10 +31,10 @@ Server application 的职能主要有: ## 参考链接 -https://zhuanlan.zhihu.com/p/363029544 +- 一文读懂 Layer2 方案 zkSync 基本原理https://zhuanlan.zhihu.com/p/363029544 -https://www.jianshu.com/u/ac3aed07477e +- zksync 源码分析: https://www.jianshu.com/u/ac3aed07477e -https://zhuanlan.zhihu.com/p/343212894 +- 李星L2 - zkSync源代码导读: https://zhuanlan.zhihu.com/p/343212894 -https://mp.weixin.qq.com/s/TxZ5W9rx6OF8qB4ZU9XrKA +- 以太坊 Layer 2 扩容方案及用例综述: https://mp.weixin.qq.com/s/TxZ5W9rx6OF8qB4ZU9XrKA diff --git a/basic/31-dune-analytics-nansen/readme.md b/basic/31-dune-analytics-nansen/readme.md index 434d54099..0bcfdb4ab 100644 --- a/basic/31-dune-analytics-nansen/readme.md +++ b/basic/31-dune-analytics-nansen/readme.md @@ -279,6 +279,7 @@ SELECT DISTINCT(tablename) FROM pg_catalog.pg_tables ## 参考链接 - 视频:https://www.bilibili.com/video/BV1ZK4y137Ce + - nansen说明书: https://github.com/rebase-network/Dapp-Learning-Arsenal/blob/main/papers/7-tool/nansen中文说明书.pdf - https://qiita.com/shooter/items/3b66fc6400bc49854ffe @@ -292,4 +293,13 @@ SELECT DISTINCT(tablename) FROM pg_catalog.pg_tables - https://app.flipsidecrypto.com/velocity - https://glassnode.com/ -- 视频: https://ournetwork.mirror.xyz/gP16wLY-9BA1E_ZuOSv1EUAgYGfK9mELNza8cfgMWPQ \ No newline at end of file + +- 视频: https://ournetwork.mirror.xyz/gP16wLY-9BA1E_ZuOSv1EUAgYGfK9mELNza8cfgMWPQ + +- Dune Youtube channel: + +- Dune docs: + +- Dune abstractions 常用查询语句: + +- PostgreSQL freeCodeCamp教程: \ No newline at end of file diff --git a/basic/35-starkNet/.gitignore b/basic/35-starkNet/.gitignore new file mode 100644 index 000000000..5a26890bd --- /dev/null +++ b/basic/35-starkNet/.gitignore @@ -0,0 +1,3 @@ +contract_abi.json +contract_compiled.json +starknet-artifacts/ \ No newline at end of file diff --git a/basic/35-starkNet/README.md b/basic/35-starkNet/README.md new file mode 100644 index 000000000..a94881181 --- /dev/null +++ b/basic/35-starkNet/README.md @@ -0,0 +1,184 @@ +# StarkNet + +## intro + +StarkNet 是一个无需许可的去中心化 ZK-Rollup,作为以太坊上的 L2 网络运行,任何 dApp 都可以在不影响以太坊的可组合性和安全性的情况下实现无限规模的计算。 + +`Cairo` 是一种用于编写可证明程序的编程语言,其中一方可以向另一方证明某个计算已正确执行。 + +StarkNet 将 Cairo 编程语言用于其基础设施和编写 StarkNet 合约。 + +## Setting up the environment + +具体安装可以查看官方文档,这里以 Linux 环境为例 (Mac M1 芯片安装一直存在问题) + +1. 为 Cairo 创建一个独立的 python 虚拟环境 + + ```sh + python3.7 -m venv ~/cairo_venv + source ~/cairo_venv/bin/activate + ``` + + 建议使用 python3.7 版本,其他版本可能有依赖安装问题 + +2. 安装 pip 依赖 + + ```sh + pip3 install ecdsa fastecdsa sympy + ``` + +3. 安装 cairo-lang + + ```sh + pip3 install cairo-lang + ``` + + 验证是否安装成功 + + ```sh + # (cairo_venv) (python37) + cairo-compile --version # cairo-compile 0.6.2 + ``` + +## Your first contract + +### 创建 `contract.cairo` 文件 + +```python +# Declare this file as a StarkNet contract and set the required +# builtins. +%lang starknet +%builtins pedersen range_check + +from starkware.cairo.common.cairo_builtins import HashBuiltin + +# Define a storage variable. +@storage_var +func balance() -> (res : felt): +end + +# Increases the balance by the given amount. +@external +func increase_balance{ + syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, + range_check_ptr}(amount : felt): + let (res) = balance.read() + balance.write(res + amount) + return () +end + +# Returns the current balance. +@view +func get_balance{ + syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, + range_check_ptr}() -> (res : felt): + let (res) = balance.read() + return (res) +end +``` + +- `%lang starknet` 声明该文件为 StarkNet contract, 需要使用 `starknet-compile` 命令来编译,而非 `cairo-compile` +- `%builtins pedersen range_check` 引入两个内建函数 +- `@storage_var` 声明 storage 变量 + - `balance.read()` 读取变量值 + - `balance.write(newValue)` 将 newValue 值写入 balance + - 部署合约时,所有 storage 变量都将初始化为 0 +- `@external` 外部调用方法 +- `func increase_balance{pedersen_ptr:HashBuiltin*}` + - 大括号内为声明的隐式参数 `Implicit arguments` + - 隐式参数会自动向函数添加参数和返回值,而不用显式的在 return 语句中添加返回值 +- `let (res) = ..` 声明局部变量 + +### 部署合约 + +命令行引入网络配置 + +```sh +export STARKNET_NETWORK=alpha-goerli +``` + +部署合约命令 + +```sh +starknet deploy --contract contract_compiled.json +``` + +部署成功后输出: + +```sh +Deploy transaction was sent. +Contract address: 0x032e72fd53f838b7d4479fe38e4e33a8e95e06b3afaa197995e0046db2f5b97d +Transaction hash: 0x50b37bd192aed1fa131b83f1b034bc41f2b46174a2b93d8fb5ec326ca8b4679 +``` + +### 与合约交互 + +`CONTRACT_ADDRESS` 替换为你的合约地址 + +```sh +starknet invoke \ + --address CONTRACT_ADDRESS \ + --abi contract_abi.json \ + --function increase_balance \ + --inputs 1234 +``` + +调用成功,输出 + +```sh +Invoke transaction was sent. +Contract address: 0x032e72fd53f838b7d4479fe38e4e33a8e95e06b3afaa197995e0046db2f5b97d +Transaction hash: 0x139207f26d5e14507f62ff0f2eb68dcff43c107f1ee0a75489e8def0fbcc5bd +``` + +查询交易信息, `TRANSACTION_HASH` 替换为你的交易 hash + +```sh +starknet tx_status --hash TRANSACTION_HASH +``` + +输出 + +```sh +{ + "block_hash": "0x6170597aaf501317aa8cdf762bca4295c7f3a2ab03e1b2e6811a257acc6d026", + "tx_status": "ACCEPTED_ON_L2" +} +``` + +The possible statuses are: + +- NOT_RECEIVED: The transaction has not been received yet (i.e., not written to storage). +- RECEIVED: The transaction was received by the sequencer. +- PENDING: The transaction passed the validation and entered the pending block. +- REJECTED: The transaction failed validation and thus was skipped. +- ACCEPTED_ON_L2: The transaction passed the validation and entered an actual created block. +- ACCEPTED_ON_L1: The transaction was accepted on-chain. + +### Query the balance + +`CONTRACT_ADDRESS` 替换为合约地址 + +```sh +starknet call \ + --address CONTRACT_ADDRESS \ + --abi contract_abi.json \ + --function get_balance +``` + +输出 + +```sh +1234 +``` + +## to-do + +- starknet 合约语法解析 + +## reference + +- official website +- develop docs +- blockscan +- StarkNet AMM demo diff --git a/basic/35-starkNet/contract.cairo b/basic/35-starkNet/contract.cairo new file mode 100644 index 000000000..412831b0d --- /dev/null +++ b/basic/35-starkNet/contract.cairo @@ -0,0 +1,30 @@ +# Declare this file as a StarkNet contract and set the required +# builtins. +%lang starknet +%builtins pedersen range_check + +from starkware.cairo.common.cairo_builtins import HashBuiltin + +# Define a storage variable. +@storage_var +func balance() -> (res : felt): +end + +# Increases the balance by the given amount. +@external +func increase_balance{ + syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, + range_check_ptr}(amount : felt): + let (res) = balance.read() + balance.write(res + amount) + return () +end + +# Returns the current balance. +@view +func get_balance{ + syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, + range_check_ptr}() -> (res : felt): + let (res) = balance.read() + return (res) +end \ No newline at end of file diff --git a/basic/35-starkNet/starknet-hardhat/README.md b/basic/35-starkNet/starknet-hardhat/README.md new file mode 100644 index 000000000..09832e5fe --- /dev/null +++ b/basic/35-starkNet/starknet-hardhat/README.md @@ -0,0 +1,87 @@ +# starknet-hardhat-plugin + +starknet 的 hardhat 插件使用 + +## starknet-dev-net + +starknet 的本地测试网络 + +安装 + +```sh +pip3 install starknet-devnet +``` + +启动本地服务(localhost:5000) + +```sh +starknet-devnet -p 5000 +``` + +## usage + +安装依赖 + +```sh +yarn install +``` + +设置 `starknet` 路径,即安装cairo环境的python venv路径 + +- `cairo.version` 和 `cairo.venv` 两个字段只能出现一个,若设置version,则需要手动编译合约 +- `mocha.starknetNetwork` 设置为 alpha 将使用默认的starknet测试网配置 (alpha-goerli),设置为devnet则需要本地运行starknet-dev-net作为本地测试网络 + +```ts +// hardhat.config.js +... +const config: HardhatUserConfig = { + cairo: { + // version: "0.6.2", // alternatively choose one of the two venv options below + + // uses (my-venv) defined by `python -m venv path/to/my-venv` + venv: "path/to/my-venv" // <-- put your dir + + // uses the currently active Python environment (hopefully with available Starknet commands!) + // venv: "active" + }, + networks: { + devnet: { + url: "http://localhost:5000" + } + }, + mocha: { + starknetNetwork: "devnet" + // starknetNetwork: "alpha" + } +}; +``` + +编译合约 + +```sh +yarn compile +``` + +运行测试文件 + +```sh +yarn test +``` + +或 + +```sh +npx hardhat test ./test/... +``` + +## to-do + +- 使用 hardhat 部署到测试网,一直连接超时 +- 使用 hardhat verify 在区块链浏览器上做源码认证 + +## reference + +- hardhat 插件 +- 插件使用示例 +- starkNet-dev-net + diff --git a/basic/35-starkNet/starknet-hardhat/contracts/auth_contract.cairo b/basic/35-starkNet/starknet-hardhat/contracts/auth_contract.cairo new file mode 100644 index 000000000..e3b1980a1 --- /dev/null +++ b/basic/35-starkNet/starknet-hardhat/contracts/auth_contract.cairo @@ -0,0 +1,63 @@ +# Declare this file as a StarkNet contract and set the required +# builtins. +%lang starknet +%builtins pedersen range_check ecdsa + +from starkware.cairo.common.cairo_builtins import ( + HashBuiltin, SignatureBuiltin) +from starkware.cairo.common.hash import hash2 +from starkware.cairo.common.signature import ( + verify_ecdsa_signature) +from starkware.starknet.common.syscalls import get_tx_signature + +@constructor +func constructor{ + syscall_ptr : felt*, + pedersen_ptr : HashBuiltin*, + range_check_ptr +} (lucky_user : felt, initial_balance : felt): + balance.write(lucky_user, initial_balance) + return () +end + +# Define a storage variable. +@storage_var +func balance(user : felt) -> (res : felt): +end + +# Increases the balance of the given user by the given amount. +@external +func increase_balance{ + syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, + range_check_ptr, ecdsa_ptr : SignatureBuiltin*}( + user : felt, amount : felt): + # Fetch the signature. + let (sig_len : felt, sig : felt*) = get_tx_signature() + + # Verify the signature length. + assert sig_len = 2 + + # Compute the hash of the message. + # The hash of (x, 0) is equivalent to the hash of (x). + let (amount_hash) = hash2{hash_ptr=pedersen_ptr}(amount, 0) + + # Verify the user's signature. + verify_ecdsa_signature( + message=amount_hash, + public_key=user, + signature_r=sig[0], + signature_s=sig[1]) + + let (res) = balance.read(user=user) + balance.write(user, res + amount) + return () +end + +# Returns the balance of the given user. +@view +func get_balance{ + syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, + range_check_ptr}(user : felt) -> (res : felt): + let (res) = balance.read(user=user) + return (res) +end diff --git a/basic/35-starkNet/starknet-hardhat/contracts/contract.cairo b/basic/35-starkNet/starknet-hardhat/contracts/contract.cairo new file mode 100644 index 000000000..8e8e9fc4a --- /dev/null +++ b/basic/35-starkNet/starknet-hardhat/contracts/contract.cairo @@ -0,0 +1,147 @@ +# Declare this file as a StarkNet contract and set the required +# builtins. +%lang starknet +%builtins pedersen range_check + +from starkware.cairo.common.cairo_builtins import HashBuiltin +from starkware.starknet.common.syscalls import get_tx_signature +from starkware.cairo.common.math import unsigned_div_rem +from util import almost_equal as aeq + +# Define a storage variable. +@storage_var +func balance() -> (res : felt): +end + +@constructor +func constructor{ + syscall_ptr : felt*, + pedersen_ptr : HashBuiltin*, + range_check_ptr +} (initial_balance : felt): + balance.write(initial_balance) + return () +end + +# Increases the balance by the given amount. +@external +func increase_balance{ + syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, + range_check_ptr}(amount1 : felt, amount2 : felt): + let (res) = balance.read() + balance.write(res + amount1 + amount2) + return () +end + +@view +func increase_balance_with_even{ + syscall_ptr: felt*, pedersen_ptr: HashBuiltin*, + range_check_ptr}(amount: felt): + + let (div, rem) = unsigned_div_rem(amount, 2) + assert rem = 0 # assert even + let (res) = balance.read() + balance.write(res + amount) + return () +end + +# Returns the current balance. +@view +func get_balance{ + syscall_ptr : felt*, pedersen_ptr : HashBuiltin*, + range_check_ptr}() -> (res : felt): + let (res) = balance.read() + return (res) +end + +############ tuples + +struct Point: + member x : felt + member y : felt +end + +struct PointPair: + member p1 : Point + member p2 : Point + member extra : felt +end + +struct TupleHolder: + member tuple : (felt, felt) + member extra : felt +end + +@view +func dummy_tuple_holder() -> (tuple_holder: TupleHolder): + return ( + tuple_holder=TupleHolder( + tuple=(2, 3), + extra=4 + ) + ) +end + +@view +func identity(a_len: felt, a: felt*) -> (res_len: felt, res: felt*, res_len_squared: felt): + return( + res_len=a_len, + res=a, + res_len_squared=a_len * a_len + ) +end + +@view +func sum_points_to_tuple(points : (Point, Point)) -> (res: (felt, felt)): + return ( + res=( + points[0].x + points[1].x, + points[0].y + points[1].y + ) + ) +end + +@view +func sum_point_pair(pointPair: PointPair) -> (res: Point): + return ( + res=Point( + x=pointPair.p1.x + pointPair.p2.x + pointPair.extra, + y=pointPair.p1.y + pointPair.p2.y + pointPair.extra + ) + ) +end + +@view +func add_extra_to_tuple(tuple_holder: TupleHolder) -> (res: Point): + return ( + res=Point( + x=tuple_holder.tuple[0] + tuple_holder.extra, + y=tuple_holder.tuple[1] + tuple_holder.extra + ) + ) +end + +@view +func use_almost_equal(a, b) -> (res): + let (res) = aeq(a=a, b=b) + return (res) +end + +########### arrays + +@external +func sum_array( + a_len : felt, a : felt*) -> (res): + if a_len == 0: + return (res=0) + end + let (rest) = sum_array(a_len=a_len - 1, a=a + 1) + return (res=a[0] + rest) +end + +@external +func get_signature{syscall_ptr : felt*, pedersen_ptr : HashBuiltin*}() -> ( + res_len : felt, res : felt*): + let (sig_len, sig) = get_tx_signature() + return (res_len=sig_len, res=sig) +end diff --git a/basic/35-starkNet/starknet-hardhat/contracts/submodule/util.cairo b/basic/35-starkNet/starknet-hardhat/contracts/submodule/util.cairo new file mode 100644 index 000000000..332bc54be --- /dev/null +++ b/basic/35-starkNet/starknet-hardhat/contracts/submodule/util.cairo @@ -0,0 +1,7 @@ +%lang starknet +%builtins range_check + +@view +func foo(a) -> (res): + return (res=42) +end diff --git a/basic/35-starkNet/starknet-hardhat/contracts/util.cairo b/basic/35-starkNet/starknet-hardhat/contracts/util.cairo new file mode 100644 index 000000000..32141a04b --- /dev/null +++ b/basic/35-starkNet/starknet-hardhat/contracts/util.cairo @@ -0,0 +1,6 @@ +func almost_equal(a, b) -> (res: felt): + if (a - b) * (a - b - 1) * (a - b + 1) == 0: + return (res=1) + end + return (res=0) +end diff --git a/basic/35-starkNet/starknet-hardhat/deploy-invoke.sh b/basic/35-starkNet/starknet-hardhat/deploy-invoke.sh new file mode 100755 index 000000000..f3f13d2f4 --- /dev/null +++ b/basic/35-starkNet/starknet-hardhat/deploy-invoke.sh @@ -0,0 +1,10 @@ +#!/bin/bash +output=$(starknet deploy --contract starknet-artifacts/contracts/contract.cairo/contract.json --network alpha) +echo $output +deploy_tx_id=$(echo $output | sed -r "s/.*Transaction ID: (\w*).*/\1/") +address=$(echo $output | sed -r "s/.*Contract address: (\w*).*/\1/") +echo "Address: $address" +echo "tx_id: $deploy_tx_id" +starknet invoke --function increase_balance --inputs 10 20 --network alpha --address $address --abi starknet-artifacts/contracts/contract.cairo/contract_abi.json +#starknet tx_status --id $deploy_tx_id --network alpha +starknet call --function get_balance --network alpha --address $address --abi starknet-artifacts/contracts/contract.cairo/contract_abi.json diff --git a/basic/35-starkNet/starknet-hardhat/hardhat.config.ts b/basic/35-starkNet/starknet-hardhat/hardhat.config.ts new file mode 100644 index 000000000..34d051dde --- /dev/null +++ b/basic/35-starkNet/starknet-hardhat/hardhat.config.ts @@ -0,0 +1,29 @@ +import { HardhatUserConfig } from "hardhat/types"; +import "@shardlabs/starknet-hardhat-plugin"; + +/** + * @type import('hardhat/config').HardhatUserConfig + */ +const config: HardhatUserConfig = { + cairo: { + // version: "0.6.2", // alternatively choose one of the two venv options below + + // uses (my-venv) defined by `python -m venv path/to/my-venv` + // venv: "path/to/my-venv" + venv: "/home/usr/cairo_venv" // <-- put your dir + + // uses the currently active Python environment (hopefully with available Starknet commands!) + // venv: "active" + }, + networks: { + devnet: { + url: "http://localhost:5000" + } + }, + mocha: { + starknetNetwork: "devnet" // 本地 starknet-dev-net 网络 + // starknetNetwork: "alpha" // starknet alpha-goerli 测试网络 + } +}; + +export default config; diff --git a/basic/35-starkNet/starknet-hardhat/package.json b/basic/35-starkNet/starknet-hardhat/package.json new file mode 100644 index 000000000..d43b10e18 --- /dev/null +++ b/basic/35-starkNet/starknet-hardhat/package.json @@ -0,0 +1,26 @@ +{ + "name": "cairo", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "compile": "npx hardhat starknet-compile", + "test": "npx hardhat test" + }, + "author": "", + "license": "ISC", + "devDependencies": { + "@shardlabs/starknet-hardhat-plugin": "latest", + "@types/chai": "^4.2.22", + "@types/mocha": "^9.0.0", + "@types/node": "^16.11.10", + "chai": "^4.3.4", + "ethers": "^5.4.6", + "hardhat": "^2.6.2", + "ts-node": "^10.4.0", + "typescript": "^4.5.2" + }, + "dependencies": { + "@toruslabs/starkware-crypto": "^1.0.0" + } +} diff --git a/basic/35-starkNet/starknet-hardhat/test/constants.ts b/basic/35-starkNet/starknet-hardhat/test/constants.ts new file mode 100644 index 000000000..fa975c543 --- /dev/null +++ b/basic/35-starkNet/starknet-hardhat/test/constants.ts @@ -0,0 +1,2 @@ +/** 10 min */ +export const TIMEOUT = 600_000; \ No newline at end of file diff --git a/basic/35-starkNet/starknet-hardhat/test/contract-factory-test.ts b/basic/35-starkNet/starknet-hardhat/test/contract-factory-test.ts new file mode 100644 index 000000000..e3bb390ae --- /dev/null +++ b/basic/35-starkNet/starknet-hardhat/test/contract-factory-test.ts @@ -0,0 +1,7 @@ +import { starknet } from "hardhat"; + +describe("ContractFactory", () => { + it("should be created", async () => { + await starknet.getContractFactory("contract"); + }); +}); diff --git a/basic/35-starkNet/starknet-hardhat/test/function-args-test.ts b/basic/35-starkNet/starknet-hardhat/test/function-args-test.ts new file mode 100644 index 000000000..b28d05875 --- /dev/null +++ b/basic/35-starkNet/starknet-hardhat/test/function-args-test.ts @@ -0,0 +1,123 @@ +import { expect } from "chai"; +import { starknet } from "hardhat"; +import { TIMEOUT } from "./constants"; +import { StarknetContract, StarknetContractFactory } from "hardhat/types/runtime"; + +describe("Starknet", function () { + this.timeout(TIMEOUT); + let contractFactory: StarknetContractFactory; + let contract: StarknetContract; + + before(async function() { + // assumes contract.cairo has been compiled + contractFactory = await starknet.getContractFactory("contract"); + contract = await contractFactory.deploy({ initial_balance: 0 }); + }); + + it("should work if provided number of arguments is the same as the expected", async function() { + const { res: balanceBefore } = await contract.call("get_balance"); + expect(balanceBefore).to.deep.equal(0n); + }); + + it("should fail if provided number of arguments is less than the expected", async function() { + try { + await contract.call("sum_array"); + expect.fail("Should have failed on passing too few arguments."); + } catch(err: any) { + expect(err.message).to.equal("sum_array: Expected 1 argument, got 0."); + } + }); + + it("should fail if provided number of arguments is more than the expected", async function() { + try { + await contract.call("sum_array", {a: [1, 2, 3, 4], b: 4 }); + expect.fail("Should have failed on passing extra argument."); + } catch(err: any) { + expect(err.message).to.equal("sum_array: Expected 1 argument, got 2."); + } + }); + + it("should work if not providing the array length when having an array as argument", async function() { + const { res: sum } = await contract.call("sum_array", {a: [1, 2, 3, 4] }); + expect(sum).to.deep.equal(10n); + }); + + it("should fail if providing the array length when having an array as argument", async function() { + try { + await contract.call("sum_array", {a_len: 4, a: [1, 2, 3, 4]}); + expect.fail("Should have failed on passing extra argument."); + } catch(err: any) { + expect(err.message).to.equal("sum_array: Expected 1 argument, got 2."); + } + }); + + it("should work if providing the exact amount of members in a tuple, with the exact amount of members in a nested struct", async function() { + const { res: sum } = await contract.call("sum_points_to_tuple", { + points: [ + { x: 1, y: 2 }, + { x: 3, y: 4 }, + ] + }); + expect(sum).to.deep.equal([ 4n, 6n ]); + }); + + it("should fail if providing too many members in a tuple", async function() { + try { + await contract.call("sum_points_to_tuple", { + points: [ + { x: 1, y: 2 }, + { x: 3, y: 4 }, + { x: 3, y: 4 } + ] + }); + expect.fail("Should have failed on passing more members than expected."); + } catch(err: any) { + expect(err.message).to.equal('"points": Expected 2 members, got 3.'); + } + }); + + it("should fail if providing too few members in a tuple", async function() { + try { + // passing Points (1, 2) and (3, 4) in a tuple + await contract.call("sum_points_to_tuple", { + points: [ + { x: 1, y: 2 } + ] + }); + expect.fail("Should have failed on passing less members than expected."); + } catch(err: any) { + expect(err.message).to.equal('"points": Expected 2 members, got 1.'); + } + }); + + it("should fail if providing too few members to a nested struct", async function() { + try { + // passing Points (1, 2) and (3, 4) in a tuple + await contract.call("sum_points_to_tuple", { + points: [ + { x: 1 }, + { x: 3, y: 4, z: 5 } + ] + }); + expect.fail("Should have failed on passing less members than expected."); + } catch(err: any) { + expect(err.message).to.equal('"points[0]": Expected 2 members, got 1.'); + } + }); + + it("should fail if providing too many members to a nested struct", async function() { + try { + // passing Points (1, 2) and (3, 4) in a tuple + await contract.call("sum_points_to_tuple", { + points: [ + { x: 1, y: 2}, + { x: 3, y: 4, z: 5 } + ] + }); + expect.fail("Should have failed on passing more members than expected"); + } catch(err: any) { + expect(err.message).to.equal('"points[1]": Expected 2 members, got 3.'); + } + }); + +}); diff --git a/basic/35-starkNet/starknet-hardhat/test/path-test.ts b/basic/35-starkNet/starknet-hardhat/test/path-test.ts new file mode 100644 index 000000000..cf4c6dc13 --- /dev/null +++ b/basic/35-starkNet/starknet-hardhat/test/path-test.ts @@ -0,0 +1,49 @@ +import { expect } from "chai"; +import { starknet } from "hardhat"; +import { TIMEOUT } from "./constants"; + +const AMBIGUOUS_ERR_MSG = "More than one file was found because the path provided is ambiguous, please specify a relative path"; + +describe("getContractFactory", function() { + this.timeout(TIMEOUT); + + it("should handle file name without extension", async function() { + await starknet.getContractFactory("contract"); + }); + + it("should handle file name with extension", async function() { + await starknet.getContractFactory("contract.cairo"); + }); + + it("should handle path without extension", async function() { + await starknet.getContractFactory("contracts/contract"); + }); + + it("should handle path with extension", async function() { + await starknet.getContractFactory("contracts/contract.cairo"); + }); + + it("should throw if name without extension ambiguous", async function() { + try { + await starknet.getContractFactory("util"); + expect.fail("Should have failed"); + } catch (err: any) { + expect(err.message).to.equal(AMBIGUOUS_ERR_MSG); + } + + await starknet.getContractFactory("contracts/util"); + await starknet.getContractFactory("contracts/submodule/util"); + }); + + it("should throw if name with extension ambiguous", async function() { + try { + await starknet.getContractFactory("util.cairo"); + expect.fail("Should have failed"); + } catch (err: any) { + expect(err.message).to.equal(AMBIGUOUS_ERR_MSG); + } + + await starknet.getContractFactory("contracts/util.cairo"); + await starknet.getContractFactory("contracts/submodule/util.cairo"); + }); +}); diff --git a/basic/35-starkNet/starknet-hardhat/test/quick-test.ts b/basic/35-starkNet/starknet-hardhat/test/quick-test.ts new file mode 100644 index 000000000..b389ce62d --- /dev/null +++ b/basic/35-starkNet/starknet-hardhat/test/quick-test.ts @@ -0,0 +1,23 @@ +import { expect } from "chai"; +import { starknet } from "hardhat"; +import { StarknetContract, StarknetContractFactory } from "hardhat/types/runtime"; +import { TIMEOUT } from "./constants"; + +describe("Starknet", function() { + this.timeout(TIMEOUT); + it("should work for a fresh deployment", async function() { + console.log("Started deployment"); + const contractFactory: StarknetContractFactory = await starknet.getContractFactory("contract"); + const contract: StarknetContract = await contractFactory.deploy({ initial_balance: 0 }); + console.log("Deployed at", contract.address); + + const { res: balanceBefore } = await contract.call("get_balance"); + expect(balanceBefore).to.deep.equal(0n); + + await contract.invoke("increase_balance", { amount1: 10, amount2: 20 }); + console.log("Increased by 10 + 20"); + + const { res: balanceAfter } = await contract.call("get_balance"); + expect(balanceAfter).to.deep.equal(30n); + }); +}); diff --git a/basic/35-starkNet/starknet-hardhat/test/sample-test.ts b/basic/35-starkNet/starknet-hardhat/test/sample-test.ts new file mode 100644 index 000000000..bcea4ed24 --- /dev/null +++ b/basic/35-starkNet/starknet-hardhat/test/sample-test.ts @@ -0,0 +1,147 @@ +import { expect } from "chai"; +import { starknet } from "hardhat"; +import { StarknetContract, StarknetContractFactory } from "hardhat/types/runtime"; +import { TIMEOUT } from "./constants"; + +describe("Starknet", function () { + this.timeout(TIMEOUT); + let preservedAddress: string; + + let contractFactory: StarknetContractFactory; + + before(async function() { + // assumes contract.cairo has been compiled + contractFactory = await starknet.getContractFactory("contract"); + }); + + it("should fail if constructor arguments not provided", async function() { + try { + await contractFactory.deploy(); + expect.fail("Should have failed on not passing constructor calldata."); + } catch(err: any) { + expect(err.message).to.equal("Constructor arguments required but not provided."); + } + }); + + it("should work for a fresh deployment", async function() { + console.log("Started deployment"); + const contract: StarknetContract = await contractFactory.deploy({ initial_balance: 0 }); + console.log("Deployed at", contract.address); + preservedAddress = contract.address; + + const { res: balanceBefore } = await contract.call("get_balance"); + expect(balanceBefore).to.deep.equal(0n); + + await contract.invoke("increase_balance", { amount1: 10, amount2: 20 }); + console.log("Increased by 10 + 20"); + + const { res: balanceAfter } = await contract.call("get_balance"); + expect(balanceAfter).to.deep.equal(30n); + }); + + it("should work for a previously deployed contract", async function() { + const contract = contractFactory.getContractAt(preservedAddress); + const { res: balance } = await contract.call("get_balance"); + expect(balance).to.deep.equal(30n); + }); + + it("should work with tuples", async function() { + const contract = contractFactory.getContractAt(preservedAddress); + // passing Points (1, 2) and (3, 4) in a tuple + const { res: sum } = await contract.call("sum_points_to_tuple", { + points: [ + { x: 1, y: 2 }, + { x: 3, y: 4 } + ] + }); + expect(sum).to.deep.equal([4n, 6n]); + }); + + it("should work with complex tuples", async function() { + const contract = contractFactory.getContractAt(preservedAddress); + // passing PointPair ((1, 2), (3, 4), 5) + // the five is an extra number added to each member of the sum Point + const { res: sum } = await contract.call("sum_point_pair", { + pointPair: { + p1: { x: 1, y: 2 }, + p2: { x: 3, y: 4 }, + extra: 5 + } + }); + expect(sum).to.deep.equal({ x: 9n, y: 11n }); + }); + + async function testArray(args: {a: number[]}, expected: bigint) { + const contract = contractFactory.getContractAt(preservedAddress); + const { res: sum } = await contract.call("sum_array", args); + expect(sum).to.deep.equal(expected); + } + + it("should work with a non-empty array", async function() { + await testArray({a: [1, 2, 3, 4] }, 10n); + }); + + it("should work with an empty array", async function() { + await testArray({a: []}, 0n); + }); + + it("should work with returned arrays", async function() { + const contract = contractFactory.getContractAt(preservedAddress); + const a = [1n, 2n, 3n]; + const execution = await contract.call("identity", { a }); + const arrLengthSquared = a.length * a.length; + expect(execution).to.deep.equal({ + res: a, + res_len: BigInt(a.length), + res_len_squared: BigInt(arrLengthSquared) + }); + }); + + it("should work with imported custom functions", async function() { + const contract = contractFactory.getContractAt(preservedAddress); + const { res: res0 } = await contract.call("use_almost_equal", {a: 1, b: 3}); + expect(res0).to.deep.equal(0n); // 0 as in false + + const { res: res1 } = await contract.call("use_almost_equal", {a: 1, b: 2}); + expect(res1).to.deep.equal(1n); // 1 as in true + }); + + it("should work with BigInt arguments instead of numbers", async function() { + const contract = contractFactory.getContractAt(preservedAddress); + + // mixing bigint and number on purpose (to show it's possible) + const { res: res1 } = await contract.call("use_almost_equal", { a: 1n, b: 2 }); + expect(res1).to.deep.equal(1n); // 1 as in true + }); + + it("should handle rejected transactions", async function() { + const contract = contractFactory.getContractAt(preservedAddress); + + const { res: balanceBeforeEven } = await contract.call("get_balance"); + + // should pass + await contract.invoke("increase_balance_with_even", { amount: 2n }); + + const { res: balanceAfterEven } = await contract.call("get_balance"); + expect(balanceAfterEven).to.deep.equal(balanceBeforeEven + 2n); + + try { + await contract.invoke("increase_balance_with_even", { amount: 3 }); + expect.fail("Should have failed on invoking with an odd number."); + } catch (err: any) { + expect(err.message).to.deep.equal("Transaction rejected."); + } + }); + + it("should provide an expected address when a contract is deployed with salt", async function() { + const EXPECTED_ADDRESS = "0x0479ace715103887f28e331401eb05bfe8c4bf3c3efa6943367c9e741f23297c"; + const addressSalt: string = "0x99"; + + console.log("Started deployment"); + const contractFactory: StarknetContractFactory = await starknet.getContractFactory("contract"); + const contract: StarknetContract = await contractFactory.deploy({ initial_balance: 0 },addressSalt); + console.log("Deployed at", contract.address); + + expect(contract.address).to.deep.equal(EXPECTED_ADDRESS); + }); +}); diff --git a/basic/35-starkNet/starknet-hardhat/test/signing-test.ts b/basic/35-starkNet/starknet-hardhat/test/signing-test.ts new file mode 100644 index 000000000..85a15a7db --- /dev/null +++ b/basic/35-starkNet/starknet-hardhat/test/signing-test.ts @@ -0,0 +1,71 @@ +import { expect } from "chai"; +import { starknet } from "hardhat"; +import { pedersen, ec, sign } from "@toruslabs/starkware-crypto" +import { TIMEOUT } from "./constants"; + +describe("Starknet", function () { + this.timeout(TIMEOUT); + + it("should handle signing transactions with previously calculated hashes", async function() { + // assumes auth_contract.cairo has been compiled + const authContractFactory = await starknet.getContractFactory("auth_contract"); + + + // be sure to pass big numbers as strings to avoid precision errors + const publicKey = BigInt("1628448741648245036800002906075225705100596136133912895015035902954123957052"); + const authContract = await authContractFactory.deploy({ + lucky_user: publicKey, + initial_balance: 1000 + }); + console.log("Deployed authContract at", authContract.address); + + const signature = [// previously calculated for amount and publicKey used in this case + BigInt("1225578735933442828068102633747590437426782890965066746429241472187377583468"), + BigInt("3568809569741913715045370357918125425757114920266578211811626257903121825123") + ]; + + await authContract.invoke("increase_balance", { + user: publicKey, + amount: 4321 + }, signature); + + const { res: balance } = await authContract.call("get_balance", { + user: publicKey + }); + expect(balance).to.deep.equal(5321n); + }); + + it("should handle signing transactions using the starkware-crypto library", async function() { + // assumes auth_contract.cairo has been compiled + const authContractFactory = await starknet.getContractFactory("auth_contract"); + + const amount = 4321; + // be sure to pass big numbers as strings to avoid precision errors + const privateKey = BigInt("1628448741648245036800002906075225705100596136133912895015035902954123957052"); + const keyPair = ec.keyFromPrivate(privateKey, 'hex'); + const publicKey = ec.keyFromPublic(keyPair.getPublic(true, 'hex'), 'hex').pub.getX().toString(16); + const publicKeyFelt = BigInt("0x" + publicKey) + const authContract = await authContractFactory.deploy({ + lucky_user: publicKeyFelt, + initial_balance: 1000 + }); + console.log("Deployed authContract at", authContract.address); + + const messageHash = pedersen([amount,0]); + const signedMessage = sign(keyPair, messageHash); + const signature = [// previously calculated for amount and publicKey used in this case + BigInt("0x" + signedMessage.r.toString(16)), + BigInt("0x" + signedMessage.s.toString(16)) + ]; + + await authContract.invoke("increase_balance", { + user: publicKeyFelt, + amount: amount + }, signature); + + const { res: balance } = await authContract.call("get_balance", { + user: publicKeyFelt + }); + expect(balance).to.deep.equal(5321n); + }); +}); diff --git a/basic/35-starkNet/starknet-hardhat/tsconfig.json b/basic/35-starkNet/starknet-hardhat/tsconfig.json new file mode 100644 index 000000000..22dad01db --- /dev/null +++ b/basic/35-starkNet/starknet-hardhat/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "target": "es2020", + "module": "commonjs", + "strict": true, + "esModuleInterop": true, + "outDir": "dist" + }, + "include": ["./scripts", "./test"], + "files": ["./hardhat.config.ts"] +} diff --git a/basic/39-Multicall/README.md b/basic/39-Multicall/README.md index 03e7b6905..02f32117c 100644 --- a/basic/39-Multicall/README.md +++ b/basic/39-Multicall/README.md @@ -29,7 +29,8 @@ Multicall.js 库具有以下这些特点: 与常规 Muticall 合约调用不同,indexed-finance multicall 不用依赖链上已经部署成功的 multicall 合约,而是将 muticall 请求放到了待部署合约的 constructor 中,通过假部署的方式,拿到链上查询的结果。 indexed-finance multicall 的“骚操作”: 1. constructor中进行 muticall 请求 -2. 利用 `assembly` 修改evm的返回数据,将本来为 revert 的信息,替换为muticall请求结果 +2. 利用 `assembly` 修改evm的返回数据,替换为muticall请求结果 + - 使用 `eth_call` 执行交易,在EVM中会先执行,再回退交易状态 ```solidity contract MultiCall { diff --git a/basic/58-EVM/readme.md b/basic/58-EVM/readme.md index c6574c43a..bfc972599 100644 --- a/basic/58-EVM/readme.md +++ b/basic/58-EVM/readme.md @@ -48,6 +48,7 @@ Solidity Bytecode and Opcode Basics diff --git a/basic/63-htlc-crosschain/README.md b/basic/63-htlc-crosschain/README.md index 6c8157982..fbb465b98 100644 --- a/basic/63-htlc-crosschain/README.md +++ b/basic/63-htlc-crosschain/README.md @@ -155,3 +155,4 @@ npx hardhat test/HashedTimelock.js - 哈希时间锁代码样例: - 跨链技术介绍:https://medium.com/electron-labs/what-is-bdlc-and-how-it-works-5716fbbacde8 - 轻客户端介绍: https://blog.ethereum.org/2015/01/10/light-clients-proof-stake/ +- 跨链下半场究竟都怎么跨?跨什么?:https://mirror.xyz/0x22034f804960B9B34353d6A7595cC4E83ac0daBe/cu5_hT-WPNZCNtpbPUzp0O9LKGk-pepF5I5s3SDbKTw diff --git a/basic/67-Go-Ethereum-Code-Analysis/readme.md b/basic/67-Go-Ethereum-Code-Analysis/readme.md index 1a28f0e27..65911cf4b 100644 --- a/basic/67-Go-Ethereum-Code-Analysis/readme.md +++ b/basic/67-Go-Ethereum-Code-Analysis/readme.md @@ -2,8 +2,11 @@ 各自建文件夹填充内容 ## 白皮书 -https://godorz.info/2021/10/ethereum-yellow-paper/ -https://mirror.xyz/0x56706F118e42AE069F20c5636141B844D1324AE1/Muq3LyriRK731jEj4jz3halgSu8BIlag4_637-hkeEg +- 黄皮书:https://godorz.info/2021/10/ethereum-yellow-paper/ +- 以太坊黄皮书公式解析: https://mirror.xyz/0x56706F118e42AE069F20c5636141B844D1324AE1/Muq3LyriRK731jEj4jz3halgSu8BIlag4_637-hkeEg +- [深入探索 CALL 指令参数](https://godorz.info/2021/10/dive-into-call-param0/) +- https://godorz.info/2021/10/ethereum-yellow-paper/ +- https://mirror.xyz/0x56706F118e42AE069F20c5636141B844D1324AE1/Muq3LyriRK731jEj4jz3halgSu8BIlag4_637-hkeEg ## 黄皮书 ECC related:\ @@ -14,6 +17,7 @@ https://godorz.info/2021/10/ethereum-yellow-paper/ \ https://learnblockchain.cn/article/2725 \ https://learnblockchain.cn/article/2817 + ## 共识算法 ## 网络模块 diff --git a/crypto/readme.md b/crypto/readme.md index 6b6cb9331..a139c2342 100644 --- a/crypto/readme.md +++ b/crypto/readme.md @@ -23,4 +23,5 @@ https://blog.goodaudience.com/understanding-zero-knowledge-proofs-through-simple - zkevm book: https://hackmd.io/@liangcc/zkvmbook/https%3A%2F%2Fhackmd.io%2FHfCsKWfWRT-B_k5j3LjIVw - 笔记: https://www.yuque.com/u428635/scg32w/edmn74#a8ffe0d3 - stark: https://aszepieniec.github.io/stark-anatomy/ -- readpaper: https://readpaper.com/ \ No newline at end of file +- readpaper: https://readpaper.com/ +- Cryptograph course by Dr. Julian Hosp \ No newline at end of file diff --git a/defi/Curve-V1/README.md b/defi/Curve-V1/README.md index bbd125f62..e20f4603d 100644 --- a/defi/Curve-V1/README.md +++ b/defi/Curve-V1/README.md @@ -17,4 +17,5 @@ - - curve投票 https://tokenbrice.xyz/crv-wars/#sdvecrv-vs-cvxcrv - imtoken介绍:https://imtoken.fans/t/topic/16656/4 +- Curve帝国编年简史:资本永不眠: https://mirror.xyz/cryptolunatic.eth/uKjxSopOVcMFyvttJWDcCKkfu-Jp_iaEXxDNql2vf6w diff --git a/defi/Euler/readme.md b/defi/Euler/readme.md index 2a125d18e..5ba52481b 100644 --- a/defi/Euler/readme.md +++ b/defi/Euler/readme.md @@ -4,3 +4,8 @@ euler 白皮书: https://docs.euler.finance/getting-started/white-paper ## 参考链接 +- dapp learning分享会: +https://docs.google.com/presentation/d/1rOjH4qjVAD-p6Pt8Woc7sFz4iRHdgZB6nt2BrTBICs8/edit#slide=id.p +- delphi lab,Dynamic interest rates using control theory: +https://cdn.delphidigital.io/uploads/2021/06/Dynamic-Interest-Rate-Model-Based-On-Control-Theory.pdf + diff --git a/defi/Uniswap-V2/oracle/contract/ExampleSlidingWindowOracle.sol b/defi/Uniswap-V2/oracle/contract/ExampleSlidingWindowOracle.sol new file mode 100644 index 000000000..d0a50d401 --- /dev/null +++ b/defi/Uniswap-V2/oracle/contract/ExampleSlidingWindowOracle.sol @@ -0,0 +1,125 @@ +pragma solidity =0.6.6; + +import '@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol'; +import '@uniswap/v2-core/contracts/interfaces/IUniswapV2Pair.sol'; +import '@uniswap/lib/contracts/libraries/FixedPoint.sol'; + +import '../libraries/SafeMath.sol'; +import '../libraries/UniswapV2Library.sol'; +import '../libraries/UniswapV2OracleLibrary.sol'; + +// sliding window oracle that uses observations collected over a window to provide moving price averages in the past +// `windowSize` with a precision of `windowSize / granularity` +// note this is a singleton oracle and only needs to be deployed once per desired parameters, which +// differs from the simple oracle which must be deployed once per pair. +contract ExampleSlidingWindowOracle { + using FixedPoint for *; + using SafeMath for uint; + + struct Observation { + uint timestamp; + uint price0Cumulative; + uint price1Cumulative; + } + + address public immutable factory; + // the desired amount of time over which the moving average should be computed, e.g. 24 hours + uint public immutable windowSize; //24 hours + // the number of observations stored for each pair, i.e. how many price observations are stored for the window. + // as granularity increases from 1, more frequent updates are needed, but moving averages become more precise. + // averages are computed over intervals with sizes in the range: + // [windowSize - (windowSize / granularity) * 2, windowSize] + // e.g. if the window size is 24 hours, and the granularity is 24, the oracle will return the average price for + // the period: + // [now - [22 hours, 24 hours], now] + uint8 public immutable granularity; //1 + // this is redundant with granularity and windowSize, but stored for gas savings & informational purposes. + uint public immutable periodSize; //windowSize/granularity 24 + + // mapping from pair address to a list of price observations of that pair + mapping(address => Observation[]) public pairObservations; + + constructor(address factory_, uint windowSize_, uint8 granularity_) public { + require(granularity_ > 1, 'SlidingWindowOracle: GRANULARITY'); + require( + (periodSize = windowSize_ / granularity_) * granularity_ == windowSize_, + 'SlidingWindowOracle: WINDOW_NOT_EVENLY_DIVISIBLE' + ); + factory = factory_; + windowSize = windowSize_; + granularity = granularity_; + } + + // returns the index of the observation corresponding to the given timestamp + function observationIndexOf(uint timestamp) public view returns (uint8 index) { + uint epochPeriod = timestamp / periodSize; + return uint8(epochPeriod % granularity); + } + + // returns the observation from the oldest epoch (at the beginning of the window) relative to the current time + function getFirstObservationInWindow(address pair) private view returns (Observation storage firstObservation) { + uint8 observationIndex = observationIndexOf(block.timestamp); + // no overflow issue. if observationIndex + 1 overflows, result is still zero. + uint8 firstObservationIndex = (observationIndex + 1) % granularity; + firstObservation = pairObservations[pair][firstObservationIndex]; + } + + // update the cumulative price for the observation at the current timestamp. each observation is updated at most + // once per epoch period. + function update(address tokenA, address tokenB) external { + address pair = UniswapV2Library.pairFor(factory, tokenA, tokenB); + + // populate the array with empty observations (first call only) + for (uint i = pairObservations[pair].length; i < granularity; i++) { + pairObservations[pair].push(); + } + + // get the observation for the current period + uint8 observationIndex = observationIndexOf(block.timestamp); + Observation storage observation = pairObservations[pair][observationIndex]; + + // we only want to commit updates once per period (i.e. windowSize / granularity) + uint timeElapsed = block.timestamp - observation.timestamp; + if (timeElapsed > periodSize) { + (uint price0Cumulative, uint price1Cumulative,) = UniswapV2OracleLibrary.currentCumulativePrices(pair); + observation.timestamp = block.timestamp; + observation.price0Cumulative = price0Cumulative; + observation.price1Cumulative = price1Cumulative; + } + } + + // given the cumulative prices of the start and end of a period, and the length of the period, compute the average + // price in terms of how much amount out is received for the amount in + function computeAmountOut( + uint priceCumulativeStart, uint priceCumulativeEnd, + uint timeElapsed, uint amountIn + ) private pure returns (uint amountOut) { + // overflow is desired. + FixedPoint.uq112x112 memory priceAverage = FixedPoint.uq112x112( + uint224((priceCumulativeEnd - priceCumulativeStart) / timeElapsed) + ); + amountOut = priceAverage.mul(amountIn).decode144(); + } + + // returns the amount out corresponding to the amount in for a given token using the moving average over the time + // range [now - [windowSize, windowSize - periodSize * 2], now] + // update must have been called for the bucket corresponding to timestamp `now - windowSize` + function consult(address tokenIn, uint amountIn, address tokenOut) external view returns (uint amountOut) { + address pair = UniswapV2Library.pairFor(factory, tokenIn, tokenOut); + Observation storage firstObservation = getFirstObservationInWindow(pair); + + uint timeElapsed = block.timestamp - firstObservation.timestamp; + require(timeElapsed <= windowSize, 'SlidingWindowOracle: MISSING_HISTORICAL_OBSERVATION'); + // should never happen. + require(timeElapsed >= windowSize - periodSize * 2, 'SlidingWindowOracle: UNEXPECTED_TIME_ELAPSED'); + + (uint price0Cumulative, uint price1Cumulative,) = UniswapV2OracleLibrary.currentCumulativePrices(pair); + (address token0,) = UniswapV2Library.sortTokens(tokenIn, tokenOut); + + if (token0 == tokenIn) { + return computeAmountOut(firstObservation.price0Cumulative, price0Cumulative, timeElapsed, amountIn); + } else { + return computeAmountOut(firstObservation.price1Cumulative, price1Cumulative, timeElapsed, amountIn); + } + } +} \ No newline at end of file diff --git a/defi/Uniswap-V2/oracle/readme.md b/defi/Uniswap-V2/oracle/readme.md new file mode 100644 index 000000000..7021fb8fc --- /dev/null +++ b/defi/Uniswap-V2/oracle/readme.md @@ -0,0 +1,7 @@ +# Oracle +## twap + uniswap v2 滑动窗口的twap价格实现;请参考合约目录下的ExampleSlidingWindowOracle; +## 参考链接 +- chainlink vs twap: https://smartcontentpublication.medium.com/twap-oracles-vs-chainlink-price-feeds-a-comparative-analysis-8155a3483cbd +- univ2 twap github :https://github.com/Uniswap/v2-periphery/tree/master/contracts/examples + diff --git a/defi/Uniswap-V2/readme.md b/defi/Uniswap-V2/readme.md index 802d4578d..dd1941e2a 100644 --- a/defi/Uniswap-V2/readme.md +++ b/defi/Uniswap-V2/readme.md @@ -49,3 +49,4 @@ Interface 是 User Interface 的含义,此为 Uniswap 网站的代码,包括 - 将UniswapV2部署到所有区块链—去中心化交易所Uniswap多链部署教学视频: - V2交易界面: https://app.uniswap.org/#/swap?use=V2 - starli: https://learnblockchain.cn/article/1480 +- 代码解析: https://ethereum.org/vi/developers/tutorials/uniswap-v2-annotated-code/ diff --git a/defi/readme.md b/defi/readme.md index 5151db277..3859a20ff 100644 --- a/defi/readme.md +++ b/defi/readme.md @@ -1,4 +1,10 @@ # DEFI +DEFI学习路线图: +交易 -> 借贷 ->衍生品 +可以根据我们学习小组的学习路线图: +- 交易: uniV1(task13) -> uniV2 -> uniV3 -> CurveV1 -> CurveV2 +- 借贷: Aave -> Compound -> Liquity -> Euler +- 衍生品: SNX -> YFI -> Perpetual ## 参考链接