├── section3 ├── SimpleSwap │ ├── .gitmodules │ ├── .solcover.js │ ├── tsconfig.json │ ├── foundry.toml │ ├── remappings.txt │ ├── test │ │ ├── utils.ts │ │ ├── fixtures.ts │ │ └── foundry │ │ │ ├── helper │ │ │ └── SimpleSwapSetUp.sol │ │ │ ├── SimpleSwap.constructor.t.sol │ │ │ └── SimpleSwap.swap.t.sol │ ├── .prettierrc.yaml │ ├── .gitignore │ ├── contracts │ │ ├── SimpleSwap.sol │ │ ├── test │ │ │ └── TestERC20.sol │ │ └── interface │ │ │ └── ISimpleSwap.sol │ ├── .solhint.json │ ├── hardhat.config.ts │ ├── README.md │ └── package.json ├── CompoundPractice │ ├── .gitmodules │ ├── foundry.toml │ ├── .gitignore │ ├── test │ │ ├── helper │ │ │ └── CompoundPracticeSetUp.sol │ │ └── CompoundPractice.t.sol │ └── README.md ├── FlashLoanPractice │ ├── .gitmodules │ ├── .env.example │ ├── foundry.toml │ ├── .gitignore │ ├── src │ │ ├── BalanceChecker.sol │ │ ├── AaveFlashLoan.sol │ │ ├── FlashSwapLiquidate.sol │ │ └── Borrower.sol │ ├── test │ │ ├── AaveFlashLoan.t.sol │ │ └── FlashSwapLiquidate.t.sol │ └── README.md ├── FlashSwapPractice │ ├── .gitmodules │ ├── .solcover.js │ ├── tsconfig.json │ ├── contracts │ │ ├── interfaces │ │ │ └── IFakeLendingProtocol.sol │ │ ├── test │ │ │ ├── interfaces │ │ │ │ └── IWETH9.sol │ │ │ ├── TestERC20.sol │ │ │ └── TestWETH9.sol │ │ ├── FakeLendingProtocol.sol │ │ ├── Liquidator.sol │ │ └── Arbitrage.sol │ ├── .prettierrc.yaml │ ├── foundry.toml │ ├── remappings.txt │ ├── .solhint.json │ ├── .gitignore │ ├── hardhat.config.ts │ ├── README.md │ ├── package.json │ └── test │ │ ├── FlashSwapPractice.sol │ │ ├── ArbitragePractice.sol │ │ └── helper │ │ └── FlashSwapSetUp.sol ├── SandwichPractice │ ├── .gitmodules │ ├── .solcover.js │ ├── tsconfig.json │ ├── contracts │ │ └── test │ │ │ ├── interfaces │ │ │ └── IWETH9.sol │ │ │ ├── TestERC20.sol │ │ │ └── TestWETH9.sol │ ├── .prettierrc.yaml │ ├── foundry.toml │ ├── remappings.txt │ ├── .solhint.json │ ├── .gitignore │ ├── hardhat.config.ts │ ├── README.md │ ├── package.json │ └── test │ │ ├── helper │ │ └── SandwichSetUp.sol │ │ └── SandwichPractice.t.sol ├── UniswapV2Practice │ ├── .gitmodules │ ├── .solcover.js │ ├── tsconfig.json │ ├── foundry.toml │ ├── remappings.txt │ ├── .prettierrc.yaml │ ├── contracts │ │ ├── Attack.sol │ │ ├── test │ │ │ └── TestERC20.sol │ │ └── Bank.sol │ ├── .gitignore │ ├── .solhint.json │ ├── package.json │ ├── README.md │ └── test │ │ ├── Bank.t.sol │ │ └── UniswapV2Practice.t.sol ├── LatenRisk │ ├── src │ │ ├── Exploit.sol │ │ └── challenge │ │ │ ├── AppworksToken.sol │ │ │ ├── PriceOracle.sol │ │ │ ├── SimpleOracle.sol │ │ │ ├── InterestRateModel.sol │ │ │ ├── CErc20Immutable.sol │ │ │ ├── EIP20Interface.sol │ │ │ ├── EIP20NonStandardInterface.sol │ │ │ ├── ComptrollerInterface.sol │ │ │ ├── ErrorReporter.sol │ │ │ ├── JumpRateModel.sol │ │ │ └── Challenge.sol │ ├── foundry.toml │ ├── .gitignore │ ├── .github │ │ └── workflows │ │ │ └── test.yml │ ├── README.md │ └── test │ │ └── Solve.t.sol ├── CompoundGovernancePractice │ ├── foundry.toml │ ├── .gitignore │ ├── src │ │ └── TestERC20.sol │ ├── .github │ │ └── workflows │ │ │ └── test.yml │ ├── README.md │ ├── script │ │ ├── Governance.s.sol │ │ └── Deploy.s.sol │ └── test │ │ └── Governance.t.sol ├── README.md ├── aggregator.md ├── AMM.md └── lending.md ├── appworks_school_L-550x279.png ├── section2 ├── RugPullHw │ ├── remappings.txt │ ├── src │ │ ├── TradingCenterV2.sol │ │ ├── Proxy.sol │ │ ├── Ownable.sol │ │ ├── UpgradeableProxy.sol │ │ └── TradingCenter.sol │ ├── foundry.toml │ ├── .gitignore │ ├── .github │ │ └── workflows │ │ │ └── test.yml │ ├── README.md │ └── test │ │ └── TradingCenterTest.t.sol ├── ERC721-practice.md ├── whitelist │ ├── foundry.toml │ ├── script │ │ └── Counter.s.sol │ ├── remappings.txt │ ├── .gitignore │ ├── .github │ │ └── workflows │ │ │ └── test.yml │ ├── src │ │ └── ERC721Whitelist.sol │ └── test │ │ └── ERC721Whitelist.t.sol ├── StandardProxy │ ├── src │ │ ├── UUPS │ │ │ ├── Proxiable.sol │ │ │ ├── UUPSMultiSigWalletV2.sol │ │ │ └── UUPSMultiSigWallet.sol │ │ ├── BeaconProxy │ │ │ ├── IBeacon.sol │ │ │ ├── BeaconProxy.sol │ │ │ └── UpgradeableBeacon.sol │ │ ├── utils │ │ │ ├── Proxy.sol │ │ │ └── Slots.sol │ │ ├── UpgradeableProxy.sol │ │ ├── MultiSigWallet │ │ │ ├── MultiSigWalletV2.sol │ │ │ └── MultiSigWallet.sol │ │ ├── StorageExamples.sol │ │ ├── UUPSProxy.sol │ │ ├── ERC1967Proxy.sol │ │ └── Transparent.sol │ ├── foundry.toml │ ├── .gitignore │ ├── script │ │ └── Counter.s.sol │ ├── .github │ │ └── workflows │ │ │ └── test.yml │ ├── README.md │ └── test │ │ ├── TransparentTest.t.sol │ │ ├── BeaconProxyTest.t.sol │ │ ├── UUPSTest.t.sol │ │ ├── SlotsManipulateTest.t.sol │ │ └── ERC1967ProxyTest.t.sol ├── internal-transaction.md ├── token-visibility.md ├── function-signature.md ├── ProxyPattern │ ├── foundry.toml │ ├── src │ │ ├── MultiSigWallet │ │ │ ├── MultiSigWalletV2.sol │ │ │ └── MultiSigWallet.sol │ │ ├── test │ │ │ ├── testERC20.sol │ │ │ └── testERC721.sol │ │ ├── UpgradeableProxy.sol │ │ ├── BasicProxy.sol │ │ └── Proxy.sol │ ├── .gitignore │ ├── remappings.txt │ ├── script │ │ └── Counter.s.sol │ ├── .github │ │ └── workflows │ │ │ └── test.yml │ ├── README.md │ └── test │ │ ├── BasicProxy.t.sol │ │ ├── MultiSigWalletV2.t.sol │ │ └── MultiSigWallet.t.sol ├── oracle.md ├── msgSender-vs-txOrigin.md ├── proxy-contract.md ├── Re-entrancy.md ├── static-call.md ├── random-number-generation.md ├── whitelist.md ├── EIP-712.md ├── view-pure-function-and-function-visibility.md ├── arithmetics.md ├── ERC20-Transfer-approval.md ├── contract-optimization.md └── README.md ├── README.md └── section1 └── README.md /section3/SimpleSwap/.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /section3/CompoundPractice/.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /section3/FlashLoanPractice/.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /section3/FlashSwapPractice/.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /section3/SandwichPractice/.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /section3/UniswapV2Practice/.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /section3/FlashLoanPractice/.env.example: -------------------------------------------------------------------------------- 1 | MAINNET_RPC_URL=https://abcdefg... 2 | -------------------------------------------------------------------------------- /section3/SimpleSwap/.solcover.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | skipFiles: ["contracts/test/TestERC20.sol"], 3 | } 4 | -------------------------------------------------------------------------------- /section3/FlashSwapPractice/.solcover.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | skipFiles: ["contracts/test/TestERC20.sol"], 3 | } 4 | -------------------------------------------------------------------------------- /section3/SandwichPractice/.solcover.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | skipFiles: ["contracts/test/TestERC20.sol"], 3 | } 4 | -------------------------------------------------------------------------------- /section3/UniswapV2Practice/.solcover.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | skipFiles: ["contracts/test/TestERC20.sol"], 3 | } 4 | -------------------------------------------------------------------------------- /appworks_school_L-550x279.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AppWorks-School/Blockchain-Resource/HEAD/appworks_school_L-550x279.png -------------------------------------------------------------------------------- /section2/RugPullHw/remappings.txt: -------------------------------------------------------------------------------- 1 | ds-test/=lib/forge-std/lib/ds-test/src/ 2 | forge-std/=lib/forge-std/src/ 3 | solmate/=lib/solmate/src/ 4 | -------------------------------------------------------------------------------- /section3/SimpleSwap/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "resolveJsonModule": true, 4 | "esModuleInterop": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /section3/FlashSwapPractice/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "resolveJsonModule": true, 4 | "esModuleInterop": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /section3/SandwichPractice/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "resolveJsonModule": true, 4 | "esModuleInterop": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /section3/UniswapV2Practice/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "resolveJsonModule": true, 4 | "esModuleInterop": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /section3/SimpleSwap/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = 'contracts' 3 | out = 'out' 4 | libs = ['node_modules', 'lib'] 5 | test = 'test' 6 | cache_path = 'cache_forge' -------------------------------------------------------------------------------- /section2/ERC721-practice.md: -------------------------------------------------------------------------------- 1 | # ERC721 實作 2 | 3 | ## 說明: 4 | 實作 ERC721 的合約,並回答為何 ERC721 需要 ERC721TokenReceiver? 5 | 6 | 7 | 8 | ## 參考資料 9 | 10 | --- 11 | [回階段二](./README.md) -------------------------------------------------------------------------------- /section2/whitelist/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["lib"] 5 | 6 | # See more config options https://github.com/foundry-rs/foundry/tree/master/config 7 | -------------------------------------------------------------------------------- /section3/FlashLoanPractice/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["lib"] 5 | 6 | # See more config options https://github.com/foundry-rs/foundry/tree/master/config -------------------------------------------------------------------------------- /section2/StandardProxy/src/UUPS/Proxiable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | interface Proxiable { 5 | function proxiableUUID() external pure returns (bytes32); 6 | } -------------------------------------------------------------------------------- /section3/LatenRisk/src/Exploit.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "../src/challenge/Challenge.sol"; 5 | 6 | contract Exploit { 7 | /* Code here */ 8 | } -------------------------------------------------------------------------------- /section2/RugPullHw/src/TradingCenterV2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SEE LICENSE IN LICENSE 2 | pragma solidity ^0.8.17; 3 | 4 | // TODO: Try to implement TradingCenterV2 here 5 | contract TradingCenterV2 { 6 | } -------------------------------------------------------------------------------- /section2/internal-transaction.md: -------------------------------------------------------------------------------- 1 | # internal transaction 2 | 3 | ## 說明: 4 | 請研究什麼是 internal transaction?如何查詢 internal transaction 的資料? 5 | 6 | 7 | 8 | ## 參考資料 9 | 10 | --- 11 | [回階段二](./README.md) 12 | -------------------------------------------------------------------------------- /section2/token-visibility.md: -------------------------------------------------------------------------------- 1 | # token visibility 2 | 3 | ## 說明: 4 | 請研究為什麼 ERC20 的代幣需要自己加入 MetaMask 才看得到餘額? 又為什麼 Etherscan 不用自己加入 ERC20 就看得到餘額? 5 | 6 | 7 | 8 | ## 參考資料 9 | 10 | --- 11 | [回階段二](./README.md) -------------------------------------------------------------------------------- /section2/function-signature.md: -------------------------------------------------------------------------------- 1 | # function signature 2 | 3 | ## 說明: 4 | 請研究什麼是 function signature,如果合約找不到交易中指定的的 function signature 會發生什麼事? 5 | 6 | 7 | 8 | ## 參考資料 9 | 10 | --- 11 | [回階段二](./README.md) 12 | -------------------------------------------------------------------------------- /section2/ProxyPattern/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["lib"] 5 | 6 | # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options 7 | -------------------------------------------------------------------------------- /section2/RugPullHw/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["lib"] 5 | 6 | # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options 7 | -------------------------------------------------------------------------------- /section3/LatenRisk/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["lib"] 5 | 6 | # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options 7 | -------------------------------------------------------------------------------- /section2/StandardProxy/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["lib"] 5 | 6 | # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options 7 | -------------------------------------------------------------------------------- /section2/StandardProxy/src/BeaconProxy/IBeacon.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SEE LICENSE IN LICENSE 2 | pragma solidity ^0.8.17; 3 | 4 | interface IBeacon { 5 | function implementation() external view returns (address); 6 | } -------------------------------------------------------------------------------- /section3/FlashSwapPractice/contracts/interfaces/IFakeLendingProtocol.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | interface IFakeLendingProtocol { 5 | function liquidatePosition() external; 6 | } 7 | -------------------------------------------------------------------------------- /section3/CompoundGovernancePractice/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["lib"] 5 | 6 | # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options 7 | -------------------------------------------------------------------------------- /section3/SimpleSwap/remappings.txt: -------------------------------------------------------------------------------- 1 | @openzeppelin/=node_modules/@openzeppelin/ 2 | ds-test/=lib/forge-std/lib/ds-test/src/ 3 | eth-gas-reporter/=node_modules/eth-gas-reporter/ 4 | forge-std/=lib/forge-std/src/ 5 | hardhat/=node_modules/hardhat/ 6 | -------------------------------------------------------------------------------- /section2/ProxyPattern/src/MultiSigWallet/MultiSigWalletV2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SEE LICENSE IN LICENSE 2 | pragma solidity ^0.8.17; 3 | 4 | import { MultiSigWallet } from "./MultiSigWallet.sol"; 5 | 6 | contract MultiSigWalletV2 { 7 | 8 | } -------------------------------------------------------------------------------- /section2/RugPullHw/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | # Ignores development broadcast logs 6 | !/broadcast 7 | /broadcast/*/31337/ 8 | /broadcast/**/dry-run/ 9 | 10 | # Docs 11 | docs/ 12 | 13 | # Dotenv file 14 | .env 15 | -------------------------------------------------------------------------------- /section3/CompoundPractice/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["lib"] 5 | fs_permissions = [{ access = "read", path = "./"}] 6 | 7 | # See more config options https://github.com/foundry-rs/foundry/tree/master/config 8 | -------------------------------------------------------------------------------- /section3/LatenRisk/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | # Ignores development broadcast logs 6 | !/broadcast 7 | /broadcast/*/31337/ 8 | /broadcast/**/dry-run/ 9 | 10 | # Docs 11 | docs/ 12 | 13 | # Dotenv file 14 | .env 15 | -------------------------------------------------------------------------------- /section3/UniswapV2Practice/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = "src" 3 | out = "out" 4 | libs = ["lib"] 5 | 6 | rpc_endpoints = {mainnet = MAINNET_RPC_ENDPOINT} 7 | 8 | # See more config options https://github.com/foundry-rs/foundry/tree/master/config -------------------------------------------------------------------------------- /section3/UniswapV2Practice/remappings.txt: -------------------------------------------------------------------------------- 1 | ds-test/=lib/forge-std/lib/ds-test/src/ 2 | forge-std/=lib/forge-std/src/ 3 | openzeppelin-contracts/=lib/openzeppelin-contracts/ 4 | v2-core/=lib/v2-core/contracts/ 5 | v2-periphery/=lib/v2-periphery/contracts/ 6 | -------------------------------------------------------------------------------- /section3/SandwichPractice/contracts/test/interfaces/IWETH9.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | interface IWETH9 { 5 | function deposit() external payable; 6 | 7 | function withdraw(uint256 _amount) external; 8 | } 9 | -------------------------------------------------------------------------------- /section3/SimpleSwap/test/utils.ts: -------------------------------------------------------------------------------- 1 | import bn from "bignumber.js" 2 | import { BigNumber } from "ethers" 3 | 4 | export function sqrt(value: BigNumber): BigNumber { 5 | return BigNumber.from(new bn(value.toString()).sqrt().toFixed().split(".")[0]) 6 | } 7 | -------------------------------------------------------------------------------- /section2/StandardProxy/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | # Ignores development broadcast logs 6 | !/broadcast 7 | /broadcast/*/31337/ 8 | /broadcast/**/dry-run/ 9 | 10 | # Docs 11 | docs/ 12 | 13 | # Dotenv file 14 | .env 15 | *.ans.* -------------------------------------------------------------------------------- /section3/FlashSwapPractice/contracts/test/interfaces/IWETH9.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | interface IWETH9 { 5 | function deposit() external payable; 6 | 7 | function withdraw(uint256 _amount) external; 8 | } 9 | -------------------------------------------------------------------------------- /section3/SimpleSwap/.prettierrc.yaml: -------------------------------------------------------------------------------- 1 | semi: false 2 | printWidth: 120 3 | singleQuote: false 4 | trailingComma: all 5 | arrowParens: avoid 6 | tabWidth: 4 7 | bracketSpacing: true 8 | overrides: 9 | - files: "*.json" 10 | options: 11 | tabWidth: 2 -------------------------------------------------------------------------------- /section2/ProxyPattern/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | # Ignores development broadcast logs 6 | !/broadcast 7 | /broadcast/*/31337/ 8 | /broadcast/**/dry-run/ 9 | 10 | # Docs 11 | docs/ 12 | 13 | # Dotenv file 14 | .env 15 | 16 | *.ans.* -------------------------------------------------------------------------------- /section3/CompoundGovernancePractice/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | # Ignores development broadcast logs 6 | !/broadcast 7 | /broadcast/*/31337/ 8 | /broadcast/**/dry-run/ 9 | 10 | # Docs 11 | docs/ 12 | 13 | # Dotenv file 14 | .env 15 | -------------------------------------------------------------------------------- /section3/FlashSwapPractice/.prettierrc.yaml: -------------------------------------------------------------------------------- 1 | semi: false 2 | printWidth: 120 3 | singleQuote: false 4 | trailingComma: all 5 | arrowParens: avoid 6 | tabWidth: 4 7 | bracketSpacing: true 8 | overrides: 9 | - files: "*.json" 10 | options: 11 | tabWidth: 2 -------------------------------------------------------------------------------- /section3/SandwichPractice/.prettierrc.yaml: -------------------------------------------------------------------------------- 1 | semi: false 2 | printWidth: 120 3 | singleQuote: false 4 | trailingComma: all 5 | arrowParens: avoid 6 | tabWidth: 4 7 | bracketSpacing: true 8 | overrides: 9 | - files: "*.json" 10 | options: 11 | tabWidth: 2 -------------------------------------------------------------------------------- /section3/UniswapV2Practice/.prettierrc.yaml: -------------------------------------------------------------------------------- 1 | semi: false 2 | printWidth: 120 3 | singleQuote: false 4 | trailingComma: all 5 | arrowParens: avoid 6 | tabWidth: 4 7 | bracketSpacing: true 8 | overrides: 9 | - files: "*.json" 10 | options: 11 | tabWidth: 2 -------------------------------------------------------------------------------- /section2/oracle.md: -------------------------------------------------------------------------------- 1 | # 預言機(oracle) 2 | 3 | ## 說明: 4 | 請研究什麼是 oracle, 並研究怎麼使用 Chainlink 的 oracle,並回答使用 Chainlink 跟使用 AMM 的報價有什麼不一樣? 5 | 6 | 7 | 8 | ## 參考資料 9 | 10 | - [Chainlink document](https://docs.chain.link/ethereum/) 11 | 12 | --- 13 | [回階段二](./README.md) 14 | -------------------------------------------------------------------------------- /section3/CompoundPractice/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | # Ignores development broadcast logs 6 | !/broadcast 7 | /broadcast/*/31337/ 8 | /broadcast/**/dry-run/ 9 | 10 | # Docs 11 | docs/ 12 | 13 | # Dotenv file 14 | .env 15 | 16 | *.ans.* 17 | -------------------------------------------------------------------------------- /section3/FlashLoanPractice/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | # Ignores development broadcast logs 6 | !/broadcast 7 | /broadcast/*/31337/ 8 | /broadcast/**/dry-run/ 9 | 10 | # Docs 11 | docs/ 12 | 13 | # Dotenv file 14 | .env 15 | 16 | *.ans.* 17 | -------------------------------------------------------------------------------- /section3/UniswapV2Practice/contracts/Attack.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.17; 2 | 3 | contract Attack { 4 | address public immutable bank; 5 | 6 | constructor(address _bank) { 7 | bank = _bank; 8 | } 9 | 10 | function attack() external {} 11 | } 12 | -------------------------------------------------------------------------------- /section2/ProxyPattern/remappings.txt: -------------------------------------------------------------------------------- 1 | @openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/ 2 | ds-test/=lib/forge-std/lib/ds-test/src/ 3 | erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/ 4 | forge-std/=lib/forge-std/src/ 5 | openzeppelin-contracts/=lib/openzeppelin-contracts/ 6 | -------------------------------------------------------------------------------- /section3/FlashSwapPractice/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = 'contracts' 3 | out = 'out' 4 | libs = ['node_modules', 'lib'] 5 | test = 'test' 6 | cache_path = 'cache_forge' 7 | fs_permissions = [ 8 | { access = "read", path = "./out"}, 9 | { access = "read", path = "./node_modules/@uniswap"} 10 | ] -------------------------------------------------------------------------------- /section3/SandwichPractice/foundry.toml: -------------------------------------------------------------------------------- 1 | [profile.default] 2 | src = 'contracts' 3 | out = 'out' 4 | libs = ['node_modules', 'lib'] 5 | test = 'test' 6 | cache_path = 'cache_forge' 7 | fs_permissions = [ 8 | { access = "read", path = "./out"}, 9 | { access = "read", path = "./node_modules/@uniswap"} 10 | ] -------------------------------------------------------------------------------- /section2/whitelist/script/Counter.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import "forge-std/Script.sol"; 5 | 6 | contract CounterScript is Script { 7 | function setUp() public {} 8 | 9 | function run() public { 10 | vm.broadcast(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /section2/ProxyPattern/script/Counter.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import {Script, console2} from "forge-std/Script.sol"; 5 | 6 | contract CounterScript is Script { 7 | function setUp() public {} 8 | 9 | function run() public { 10 | vm.broadcast(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /section2/StandardProxy/script/Counter.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import {Script, console2} from "forge-std/Script.sol"; 5 | 6 | contract CounterScript is Script { 7 | function setUp() public {} 8 | 9 | function run() public { 10 | vm.broadcast(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /section2/msgSender-vs-txOrigin.md: -------------------------------------------------------------------------------- 1 | # msg.sender v.s. tx.origin 2 | 3 | 4 | ## 說明: 5 | 實作 A, B 兩個合約,並回答以下狀況的 msg.sender 跟 tx.origin 分別是誰? 6 | 1. User 呼叫 A 合約 7 | 2. User 呼叫 A 合約,A 合約呼叫 B 合約 8 | 3. User 呼叫 A 合約,A 合約呼叫 B 合約,B 合約呼叫 B 合約 9 | 10 | 11 | 12 | 13 | ## 參考資料 14 | 15 | --- 16 | [回階段二](./README.md) 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /section2/proxy-contract.md: -------------------------------------------------------------------------------- 1 | # 代理合約(proxy contract) 2 | 3 | ## 說明: 4 | 了解什麼是 Proxy,常見的 Proxy contract 有哪些種類,以及用 Proxy 更新合約有什麼限制。 5 | 6 | 接著動手將一個合約加上 Proxy 功能並進行升級,以及實作 Ethernaut - 06 Delegation。 7 | 8 | 9 | 10 | ## 參考資料 11 | 12 | - [Proxy patterns](https://blog.openzeppelin.com/proxy-patterns/) 13 | 14 | --- 15 | [回階段二](./README.md) 16 | -------------------------------------------------------------------------------- /section3/SimpleSwap/.gitignore: -------------------------------------------------------------------------------- 1 | artifacts 2 | cache 3 | node_modules 4 | coverage.json 5 | coverage 6 | typechain 7 | flattened 8 | slither 9 | /.DS_Store 10 | .idea 11 | contracts/hardhat-dependency-compiler/* 12 | .openzeppelin/unknown-31337.json 13 | .env* 14 | forge-cache/ 15 | cache_forge/ 16 | out/ 17 | typechain-types 18 | 19 | *.ans.* -------------------------------------------------------------------------------- /section3/SimpleSwap/contracts/SimpleSwap.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | import { ISimpleSwap } from "./interface/ISimpleSwap.sol"; 5 | import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 6 | 7 | contract SimpleSwap is ISimpleSwap, ERC20 { 8 | // Implement core logic here 9 | } 10 | -------------------------------------------------------------------------------- /section3/UniswapV2Practice/.gitignore: -------------------------------------------------------------------------------- 1 | artifacts 2 | cache 3 | node_modules 4 | coverage.json 5 | coverage 6 | typechain 7 | flattened 8 | slither 9 | /.DS_Store 10 | .idea 11 | contracts/hardhat-dependency-compiler/* 12 | .openzeppelin/unknown-31337.json 13 | .env* 14 | forge-cache/ 15 | cache_forge/ 16 | out/ 17 | typechain-types 18 | 19 | *.ans.* -------------------------------------------------------------------------------- /section3/CompoundGovernancePractice/src/TestERC20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.20; 2 | 3 | import { ERC20 } from "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; 4 | 5 | contract TestERC20 is ERC20 { 6 | constructor(uint256 supply, string memory name, string memory symbol) ERC20(name, symbol) { 7 | _mint(msg.sender, supply); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /section3/FlashSwapPractice/remappings.txt: -------------------------------------------------------------------------------- 1 | @openzeppelin/=node_modules/@openzeppelin/ 2 | @uniswap/=node_modules/@uniswap/ 3 | ds-test/=lib/forge-std/lib/ds-test/src/ 4 | eth-gas-reporter/=node_modules/eth-gas-reporter/ 5 | forge-std/=lib/forge-std/src/ 6 | hardhat/=node_modules/hardhat/ 7 | v2-core/=lib/v2-core/contracts/ 8 | v2-periphery/=lib/v2-periphery/contracts/ 9 | -------------------------------------------------------------------------------- /section3/SandwichPractice/remappings.txt: -------------------------------------------------------------------------------- 1 | @openzeppelin/=node_modules/@openzeppelin/ 2 | @uniswap/=node_modules/@uniswap/ 3 | ds-test/=lib/forge-std/lib/ds-test/src/ 4 | eth-gas-reporter/=node_modules/eth-gas-reporter/ 5 | forge-std/=lib/forge-std/src/ 6 | hardhat/=node_modules/hardhat/ 7 | v2-core/=lib/v2-core/contracts/ 8 | v2-periphery/=lib/v2-periphery/contracts/ 9 | -------------------------------------------------------------------------------- /section2/ProxyPattern/src/test/testERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 | 6 | contract testERC20 is ERC20 { 7 | constructor() ERC20("Test erc20", "TEST20") {} 8 | 9 | function mint(address _to, uint256 _amount) external { 10 | _mint(_to, _amount); 11 | } 12 | } -------------------------------------------------------------------------------- /section2/Re-entrancy.md: -------------------------------------------------------------------------------- 1 | # 重入攻擊(Re-entrancy) 2 | 3 | ## 說明: 4 | 請解釋重入攻擊(Re-entrancy Attack),提出防止重入攻擊的最佳實踐以及手段,並實作 Ethernaut - 10 Re-entrancy。 5 | 6 | 7 | ## 參考資料 8 | - [What is a Re-Entrancy Attack?](https://quantstamp.com/blog/what-is-a-re-entrancy-attack) 9 | 10 | - [弯道超车老司机戏耍智能合约 | 成都链安漏洞分析连载第三期 —— 竞态条件漏洞](https://mp.weixin.qq.com/s/WWouXpxoejY_2oRs9TS38Q) 11 | 12 | 13 | --- 14 | [回階段二](./README.md) 15 | -------------------------------------------------------------------------------- /section2/static-call.md: -------------------------------------------------------------------------------- 1 | # Static call 2 | 3 | ## 說明: 4 | 請研究什麼是 web3.js 或 ethers.js 的 static call,他們有什麼用處?與 EVM 中的 STATICCALL opcode 有什麼不同? 5 | 6 | 7 | 8 | ## 參考資料 9 | 10 | - [Ethers.js contract callStatic](https://docs.ethers.io/v5/single-page/#/v5/api/contract/contract/-%23-contract-callStatic) 11 | 12 | - [STATICCALL opcode](https://eips.ethereum.org/EIPS/eip-214) 13 | 14 | --- 15 | [回階段二](./README.md) 16 | -------------------------------------------------------------------------------- /section2/random-number-generation.md: -------------------------------------------------------------------------------- 1 | # 亂數產生(random number generation) 2 | 3 | ## 說明: 4 | 實作一個可以產生亂數的合約,實作完後回答區塊鏈產生亂數為何很困難,確認自己的做法產生的亂數是否可以被預測,以及產生亂數的最佳實踐為何? 5 | 6 | 7 | 8 | ## 參考資料 9 | - [此地無人生還:區塊鏈隨機數的原罪與救贖](https://kknews.cc/zh-tw/tech/q6x83qy.html) 10 | 11 | - [Introduction to Chainlink VRF](https://docs.chain.link/docs/chainlink-vrf/) 12 | 13 | - [RANDAO](https://github.com/randao/randao) 14 | 15 | --- 16 | [回階段二](./README.md) 17 | -------------------------------------------------------------------------------- /section2/ProxyPattern/src/UpgradeableProxy.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import { Proxy } from "./Proxy.sol"; 5 | 6 | contract UpgradeableProxy is Proxy { 7 | // TODO: 8 | // 1. inherit or copy the code from BasicProxy 9 | // 2. add upgradeTo function to upgrade the implementation contract 10 | // 3. add upgradeToAndCall, which upgrade the implemnetation contract and call the init function again 11 | } -------------------------------------------------------------------------------- /section2/whitelist.md: -------------------------------------------------------------------------------- 1 | # 白名單 2 | 3 | ## 說明 4 | 請實作一個白名單系統,只有在白名單內的地址可以呼叫特定函數,如果白名單數量很小(例如五個),或是白名單數量很大(例如一千個),分別要怎麼做比較好?請使用 merkle tree 做一個函數,判斷 msg.sender 是否在白名單內。 5 | 6 | 7 | 8 | ## 參考資料 9 | - [Using Merkle Trees for NFT Whitelists](https://medium.com/@ItsCuzzo/using-merkle-trees-for-nft-whitelists-523b58ada3f9) 10 | 11 | - [Solidity合约中Merkle Root验证的一点实践](https://blog.csdn.net/Alex_Jeram/article/details/123025438) 12 | 13 | 14 | --- 15 | [回階段二](./README.md) 16 | -------------------------------------------------------------------------------- /section2/ProxyPattern/src/test/testERC721.sol: -------------------------------------------------------------------------------- 1 | 2 | // SPDX-License-Identifier: SEE LICENSE IN LICENSE 3 | pragma solidity ^0.8.17; 4 | 5 | import { ERC721 } from "@openzeppelin/contracts/token/ERC721/ERC721.sol"; 6 | 7 | contract testERC721 is ERC721 { 8 | constructor() ERC721("Test erc721", "TEST721") {} 9 | 10 | uint256 public currentIndex; 11 | 12 | function mint(address _to) external { 13 | _mint(_to, currentIndex); 14 | currentIndex++; 15 | } 16 | } -------------------------------------------------------------------------------- /section3/FlashLoanPractice/src/BalanceChecker.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.19; 2 | 3 | import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol"; 4 | 5 | contract BalanceChecker { 6 | address public constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; 7 | bool public pass; 8 | 9 | function checkBalance() public { 10 | uint256 requiredBalance = 10_000_000 * 10 ** 6; 11 | pass = (IERC20(USDC).balanceOf(msg.sender) > requiredBalance); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /section2/whitelist/remappings.txt: -------------------------------------------------------------------------------- 1 | ds-test/=lib/forge-std/lib/ds-test/src/ 2 | forge-std/=lib/forge-std/src/ 3 | murky/=lib/murky/ 4 | openzeppelin-contracts/=lib/murky/lib/openzeppelin-contracts/ 5 | lib/forge-std:ds-test/=lib/forge-std/lib/ds-test/src/ 6 | lib/murky:ds-test/=lib/murky/lib/forge-std/lib/ds-test/src/ 7 | lib/murky:forge-std/=lib/murky/lib/forge-std/src/ 8 | lib/murky:openzeppelin-contracts/=lib/murky/lib/openzeppelin-contracts/ 9 | oz/=lib/openzeppelin-contracts/contracts/ -------------------------------------------------------------------------------- /section3/README.md: -------------------------------------------------------------------------------- 1 | # 階段三:對經典 DeFi 機制的賞析以及較複雜的實作 2 | 3 | ## 說明: 4 | 第三階段將會從實作練習,提升到更為接近實際應用場景的案例演練。在此之前需要對區塊鏈有相當程度的理解,因此建議將前兩個階段確實完成後,再進行此階段。 5 | 6 | 7 | [Ethernaut](https://ethernaut.openzeppelin.com/) 需至少完成 15 ~ 20 題,以確保對區塊鏈的熟悉程度以及安全意識。 8 | 9 | ## 應用實作練習: 10 | ### [借貸協議與閃電貸(Flash Loan)](./lending.md) 11 | ### [自動化做市商(Automated Market Maker, AMM)](./AMM.md) 12 | ### [聚合器(Aggregator)](./aggregator.md) 13 | 14 | --- 15 | [上一階段](../section2/README.md)・[回主頁](../README.md) 16 | -------------------------------------------------------------------------------- /section2/ProxyPattern/src/BasicProxy.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import { Proxy } from "./Proxy.sol"; 5 | 6 | contract BasicProxy is Proxy { 7 | // TODO: 8 | // 1. add a variable that stores the address of the implementation contract 9 | // 2. add a constructor that takes the address of the implementation contract and stores it in the variable 10 | // 3. add a fallback function that delegates the call to the implementation contract 11 | } -------------------------------------------------------------------------------- /section3/SimpleSwap/.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["prettier"], 3 | "extends": "solhint:recommended", 4 | "rules": { 5 | "prettier/prettier": "error", 6 | "max-line-length": "error", 7 | "compiler-version": "off", 8 | "ordering": "warn", 9 | "reason-string": ["warn", { "maxLength": 48 }], 10 | "func-name-mixedcase": "off", 11 | "private-vars-leading-underscore": "warn", 12 | "func-visibility": ["warn", { "ignoreConstructors": true }] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /section3/FlashSwapPractice/.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["prettier"], 3 | "extends": "solhint:recommended", 4 | "rules": { 5 | "prettier/prettier": "error", 6 | "max-line-length": "error", 7 | "compiler-version": "off", 8 | "ordering": "warn", 9 | "reason-string": ["warn", { "maxLength": 48 }], 10 | "func-name-mixedcase": "off", 11 | "private-vars-leading-underscore": "warn", 12 | "func-visibility": ["warn", { "ignoreConstructors": true }] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /section3/SandwichPractice/.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["prettier"], 3 | "extends": "solhint:recommended", 4 | "rules": { 5 | "prettier/prettier": "error", 6 | "max-line-length": "error", 7 | "compiler-version": "off", 8 | "ordering": "warn", 9 | "reason-string": ["warn", { "maxLength": 48 }], 10 | "func-name-mixedcase": "off", 11 | "private-vars-leading-underscore": "warn", 12 | "func-visibility": ["warn", { "ignoreConstructors": true }] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /section3/UniswapV2Practice/.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["prettier"], 3 | "extends": "solhint:recommended", 4 | "rules": { 5 | "prettier/prettier": "error", 6 | "max-line-length": "error", 7 | "compiler-version": "off", 8 | "ordering": "warn", 9 | "reason-string": ["warn", { "maxLength": 48 }], 10 | "func-name-mixedcase": "off", 11 | "private-vars-leading-underscore": "warn", 12 | "func-visibility": ["warn", { "ignoreConstructors": true }] 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /section2/StandardProxy/src/UUPS/UUPSMultiSigWalletV2.sol: -------------------------------------------------------------------------------- 1 | 2 | // SPDX-License-Identifier: MIT 3 | pragma solidity ^0.8.17; 4 | 5 | import { MultiSigWalletV2 } from "../MultiSigWallet/MultiSigWalletV2.sol"; 6 | import { Slots } from "../utils/Slots.sol"; 7 | import { Proxiable } from "./Proxiable.sol"; 8 | 9 | contract UUPSMultiSigWalletV2 is Slots, MultiSigWalletV2, Proxiable { 10 | 11 | function proxiableUUID() external pure returns (bytes32) { 12 | return bytes32(keccak256("PROXIABLE")); 13 | } 14 | } -------------------------------------------------------------------------------- /section2/whitelist/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | # Ignores development broadcast logs 6 | !/broadcast 7 | /broadcast/*/31337/ 8 | /broadcast/**/dry-run/ 9 | 10 | # Docs 11 | docs/ 12 | 13 | artifacts 14 | node_modules 15 | coverage.json 16 | coverage 17 | typechain 18 | flattened 19 | slither 20 | /.DS_Store 21 | .idea 22 | contracts/hardhat-dependency-compiler/* 23 | .openzeppelin/unknown-31337.json 24 | .env* 25 | forge-cache/ 26 | cache_forge/ 27 | typechain-types 28 | 29 | *.ans.* 30 | -------------------------------------------------------------------------------- /section3/FlashSwapPractice/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | # Ignores development broadcast logs 6 | !/broadcast 7 | /broadcast/*/31337/ 8 | /broadcast/**/dry-run/ 9 | 10 | # Docs 11 | docs/ 12 | 13 | artifacts 14 | node_modules 15 | coverage.json 16 | coverage 17 | typechain 18 | flattened 19 | slither 20 | /.DS_Store 21 | .idea 22 | contracts/hardhat-dependency-compiler/* 23 | .openzeppelin/unknown-31337.json 24 | .env* 25 | forge-cache/ 26 | cache_forge/ 27 | typechain-types 28 | 29 | *.ans.* -------------------------------------------------------------------------------- /section3/SandwichPractice/.gitignore: -------------------------------------------------------------------------------- 1 | # Compiler files 2 | cache/ 3 | out/ 4 | 5 | # Ignores development broadcast logs 6 | !/broadcast 7 | /broadcast/*/31337/ 8 | /broadcast/**/dry-run/ 9 | 10 | # Docs 11 | docs/ 12 | 13 | artifacts 14 | node_modules 15 | coverage.json 16 | coverage 17 | typechain 18 | flattened 19 | slither 20 | /.DS_Store 21 | .idea 22 | contracts/hardhat-dependency-compiler/* 23 | .openzeppelin/unknown-31337.json 24 | .env* 25 | forge-cache/ 26 | cache_forge/ 27 | typechain-types 28 | 29 | *.ans.* -------------------------------------------------------------------------------- /section2/ProxyPattern/src/Proxy.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | contract Proxy { 5 | function _delegate(address _implementation) internal virtual { 6 | assembly { 7 | calldatacopy(0, 0, calldatasize()) 8 | let result := delegatecall(gas(), _implementation, 0, calldatasize(), 0, 0) 9 | returndatacopy(0, 0, returndatasize()) 10 | switch result 11 | case 0 { revert(0, returndatasize()) } 12 | default { return(0, returndatasize()) } 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /section2/StandardProxy/src/utils/Proxy.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | contract Proxy { 5 | function _delegate(address _implementation) internal virtual { 6 | assembly { 7 | calldatacopy(0, 0, calldatasize()) 8 | let result := delegatecall(gas(), _implementation, 0, calldatasize(), 0, 0) 9 | returndatacopy(0, 0, returndatasize()) 10 | switch result 11 | case 0 { revert(0, returndatasize()) } 12 | default { return(0, returndatasize()) } 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /section2/RugPullHw/src/Proxy.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SEE LICENSE IN LICENSE 2 | pragma solidity 0.8.17; 3 | 4 | contract Proxy { 5 | function _delegate(address _implementation) internal virtual { 6 | assembly { 7 | calldatacopy(0, 0, calldatasize()) 8 | let result := delegatecall(gas(), _implementation, 0, calldatasize(), 0, 0) 9 | returndatacopy(0, 0, returndatasize()) 10 | switch result 11 | case 0 { revert(0, returndatasize()) } 12 | default { return(0, returndatasize()) } 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /section2/EIP-712.md: -------------------------------------------------------------------------------- 1 | # EIP-712 2 | 3 | ## 說明: 4 | 實作 EIP-712,讓你的 MetaMask 可以正確顯示要使用者簽名的資訊,而不是一串 hex code。 5 | 6 | 7 | 8 | ## 參考資料 9 | - [EIPs/eip-712.md at master · ethereum/EIPs · GitHub](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md) 10 | 11 | - [EIP712 is here: What to expect and how to use it](https://medium.com/metamask/eip712-is-coming-what-to-expect-and-how-to-use-it-bb92fd1a7a26) 12 | 13 | - [EIP712, a full-stack example](https://medium.com/coinmonks/eip712-a-full-stack-example-e12185b03d54) 14 | 15 | --- 16 | [回階段二](./README.md) -------------------------------------------------------------------------------- /section2/view-pure-function-and-function-visibility.md: -------------------------------------------------------------------------------- 1 | # view / pure function 及 function visibility 2 | 3 | ## 說明: 4 | 請深入理解 View, Pure, Internal, External, Public, Private 的作用,並回答如果以下函數如果有或沒有 View ,在鏈上合約互動跟鏈下由 web3.js 或 ethers.js 呼叫會有什麼差別? 5 | 6 | ``` 7 | function getAccountBalance(address account) public returns (uint256) { 8 | return balance[account]; 9 | } 10 | ``` 11 | 12 | 13 | 14 | ## 參考資料 15 | - [Solidity Visibility Modifiers ](https://blog.oliverjumpertz.dev/solidity-visibility-modifiers) 16 | 17 | 18 | --- 19 | [回階段二](./README.md) 20 | -------------------------------------------------------------------------------- /section3/LatenRisk/src/challenge/AppworksToken.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.20; 2 | 3 | import "../../lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; 4 | 5 | contract AppworksToken is ERC20 { 6 | address public owner; 7 | 8 | constructor(string memory name_, string memory symbol_, address owner_) ERC20(name_, symbol_) { 9 | owner = owner_; 10 | } 11 | 12 | modifier onlyOwner() { 13 | require(msg.sender == owner); 14 | _; 15 | } 16 | 17 | function mint(address to, uint256 amount) external onlyOwner { 18 | _mint(to, amount); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /section3/FlashSwapPractice/hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import "@nomicfoundation/hardhat-toolbox" 2 | import { HardhatUserConfig } from "hardhat/config" 3 | 4 | const config: HardhatUserConfig = { 5 | solidity: { 6 | version: "0.8.17", 7 | settings: { 8 | optimizer: { enabled: true, runs: 100 }, 9 | evmVersion: "berlin", 10 | // for smock to mock contracts 11 | outputSelection: { 12 | "*": { 13 | "*": ["storageLayout"], 14 | }, 15 | }, 16 | }, 17 | }, 18 | } 19 | 20 | export default config -------------------------------------------------------------------------------- /section3/SandwichPractice/hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import "@nomicfoundation/hardhat-toolbox" 2 | import { HardhatUserConfig } from "hardhat/config" 3 | 4 | const config: HardhatUserConfig = { 5 | solidity: { 6 | version: "0.8.17", 7 | settings: { 8 | optimizer: { enabled: true, runs: 100 }, 9 | evmVersion: "berlin", 10 | // for smock to mock contracts 11 | outputSelection: { 12 | "*": { 13 | "*": ["storageLayout"], 14 | }, 15 | }, 16 | }, 17 | }, 18 | } 19 | 20 | export default config -------------------------------------------------------------------------------- /section3/SimpleSwap/hardhat.config.ts: -------------------------------------------------------------------------------- 1 | import "@nomicfoundation/hardhat-toolbox" 2 | import { HardhatUserConfig } from "hardhat/config" 3 | 4 | const config: HardhatUserConfig = { 5 | solidity: { 6 | version: "0.8.17", 7 | settings: { 8 | optimizer: { enabled: true, runs: 100 }, 9 | evmVersion: "berlin", 10 | // for smock to mock contracts 11 | outputSelection: { 12 | "*": { 13 | "*": ["storageLayout"], 14 | }, 15 | }, 16 | }, 17 | }, 18 | } 19 | 20 | export default config 21 | -------------------------------------------------------------------------------- /section3/aggregator.md: -------------------------------------------------------------------------------- 1 | # 聚合器(Aggregator) 2 | 3 | ## 目的:理解聚合器(Aggregator)的原理與實作 4 | 5 | ## 請欣賞 [Yearn](https://docs.yearn.finance/) 的合約,並實作一個 vault 跟一個 strategy,需要達到以下功能 6 | 1. vault 可以接受 deposit,並發行對應的 yToken,例如 USDC => yUSDC 7 | 2. vault 可以接受 withdraw,收回 yToken 返回對應資產 8 | 3. vault 有個 price 可以反應 token => yToken 的價值 9 | 3. vault 可以把資產交給 strategy,也可以從 strategy 取回資產 10 | 4. strategy 要可以賺取收益,可以先用自己轉錢進去的方式假裝有在賺錢 11 | 3. 當 strategy 有收益或虧損, vault 要可以正確反應資產價值 12 | 13 | 14 | ## 參考資料 15 | - [What is yearn.finance? (YFI)](https://www.kraken.com/learn/what-is-yearn-finance-yfi) 16 | 17 | 18 | --- 19 | [回階段三](./README.md) 20 | -------------------------------------------------------------------------------- /section3/SandwichPractice/contracts/test/TestERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 | 6 | contract TestERC20 is ERC20 { 7 | uint8 internal _decimals; 8 | 9 | constructor(string memory name, string memory symbol, uint8 decimalsArg) ERC20(name, symbol) { 10 | _decimals = decimalsArg; 11 | } 12 | 13 | function mint(address to, uint256 amount) public { 14 | _mint(to, amount); 15 | } 16 | 17 | function decimals() public view virtual override returns (uint8) { 18 | return _decimals; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /section3/SimpleSwap/contracts/test/TestERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 | 6 | contract TestERC20 is ERC20 { 7 | address private immutable owner; 8 | 9 | modifier onlyOwner() { 10 | require(msg.sender == owner, "TestERC20: only owner"); 11 | _; 12 | } 13 | 14 | constructor(string memory name, string memory symbol) ERC20(name, symbol) { 15 | owner = msg.sender; 16 | } 17 | 18 | function mint(address to, uint256 amount) external onlyOwner { 19 | _mint(to, amount); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /section3/FlashSwapPractice/contracts/test/TestERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 | 6 | contract TestERC20 is ERC20 { 7 | uint8 internal _decimals; 8 | 9 | constructor(string memory name, string memory symbol, uint8 decimalsArg) ERC20(name, symbol) { 10 | _decimals = decimalsArg; 11 | } 12 | 13 | function mint(address to, uint256 amount) public { 14 | _mint(to, amount); 15 | } 16 | 17 | function decimals() public view virtual override returns (uint8) { 18 | return _decimals; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /section3/SandwichPractice/README.md: -------------------------------------------------------------------------------- 1 | # Sandwich Attack Practice 2 | We have 2 practices and 2 discussions in `SandwichPractice.t.sol`, follow the instructions in the comments to pass the test. 3 | 4 | If you are interested in the sandwich attack, you can read more in this [repository](https://github.com/libevm/subway) 5 | 6 | 7 | ## Local Development 8 | Clone this repository, install Node.js dependencies, and build the source code: 9 | 10 | ```bash 11 | git clone git@github.com:AppWorks-School/Blockchain-Resource.git 12 | cd Blockchain-Resource/section3/SandwichPractice 13 | npm install 14 | forge install 15 | forge build 16 | forge test 17 | ``` 18 | 19 | -------------------------------------------------------------------------------- /section3/UniswapV2Practice/contracts/test/TestERC20.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | import { ERC20 } from "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; 5 | 6 | contract TestERC20 is ERC20 { 7 | uint8 internal _decimals; 8 | 9 | constructor(string memory name, string memory symbol, uint8 decimalsArg) ERC20(name, symbol) { 10 | _decimals = decimalsArg; 11 | } 12 | 13 | function mint(address to, uint256 amount) public { 14 | _mint(to, amount); 15 | } 16 | 17 | function decimals() public view virtual override returns (uint8) { 18 | return _decimals; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /section2/arithmetics.md: -------------------------------------------------------------------------------- 1 | # 合約運算 2 | 3 | ## 說明 4 | 請實作一個運算用的合約,可以處理 uint256 的加減乘除,並且在溢位時進行 revert,實作完後,查詢並理解最佳實踐為何? 5 | 6 | 7 | 8 | ## 參考資料 9 | - [Utilities - OpenZeppelin Docs](https://docs.openzeppelin.com/contracts/4.x/api/utils) 10 | - [Using Safe Math library to prevent from overflows - EthereumDev](https://ethereumdev.io/using-safe-math-library-to-prevent-from-overflows/) 11 | - [openzeppelin-contracts/SafeMath.sol at master](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/math/SafeMath.sol) 12 | - [Solidity v0.8.0 Breaking Changes](https://docs.soliditylang.org/en/v0.8.15/080-breaking-changes.html) 13 | --- 14 | [回階段二](./README.md) 15 | -------------------------------------------------------------------------------- /section2/ERC20-Transfer-approval.md: -------------------------------------------------------------------------------- 1 | # ERC20 - Transfer approval 2 | 3 | ## 說明: 4 | 請詳讀 ERC20 的合約,自己實作一次 ERC20 並完成: 5 | 1. 透過 `transfer` 從交易發起者的地址轉出代幣 6 | 2. 透過 `transferFrom` 從非交易發起者的地址將代幣轉入交易發起者的地址 7 | 8 | 接著回答: 9 | - 請問為何要先 `approve` 才能使用 `transferFrom`? 10 | - 有沒有其他作法能夠達到 `approve` 的效果,又可以不用把 `approve` 跟 `transferFrom` 分成兩筆交易,只要一筆交易就能達成目的? 11 | 12 | ## 參考資料 13 | 14 | - [ERC-20 TOKEN STANDARD](https://ethereum.org/en/developers/docs/standards/tokens/erc-20/) 15 | 16 | - [ERC-777 TOKEN STANDARD](https://ethereum.org/zh-tw/developers/docs/standards/tokens/erc-777/) 17 | 18 | - [EIP-2612: permit – 712-signed approvals](https://eips.ethereum.org/EIPS/eip-2612) 19 | 20 | --- 21 | [回階段二](./README.md) 22 | -------------------------------------------------------------------------------- /section3/LatenRisk/src/challenge/PriceOracle.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.20; 2 | 3 | // Copyright 2020 Compound Labs, Inc. 4 | 5 | import "./CToken.sol"; 6 | 7 | abstract contract PriceOracle { 8 | /// @notice Indicator that this is a PriceOracle contract (for inspection) 9 | bool public constant isPriceOracle = true; 10 | 11 | /** 12 | * @notice Get the underlying price of a cToken asset 13 | * @param cToken The cToken to get the underlying price of 14 | * @return The underlying asset price mantissa (scaled by 1e18). 15 | * Zero means the price is unavailable. 16 | */ 17 | function getUnderlyingPrice(CToken cToken) external view virtual returns (uint256); 18 | } 19 | -------------------------------------------------------------------------------- /section3/LatenRisk/src/challenge/SimpleOracle.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.20; 2 | 3 | import "./PriceOracle.sol"; 4 | 5 | contract SimpleOracle is PriceOracle { 6 | address public owner; 7 | mapping(address => uint256) priceOf; 8 | 9 | constructor() { 10 | owner = msg.sender; 11 | } 12 | 13 | modifier onlyOwner() { 14 | require(msg.sender == owner); 15 | _; 16 | } 17 | 18 | function setPrice(address cToken, uint256 price) external onlyOwner { 19 | priceOf[cToken] = price; 20 | } 21 | 22 | function getUnderlyingPrice(CToken cToken) external view virtual override returns (uint256) { 23 | return priceOf[address(cToken)]; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /section3/FlashSwapPractice/contracts/test/TestWETH9.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 | import { IWETH9 } from "./interfaces/IWETH9.sol"; 6 | 7 | contract TestWETH9 is IWETH9, ERC20("WETH", "WETH") { 8 | function deposit() external payable { 9 | _mint(msg.sender, msg.value); 10 | } 11 | 12 | function withdraw(uint256 amount) external { 13 | require(balanceOf(msg.sender) >= amount, "Insufficient balance"); 14 | _burn(msg.sender, amount); 15 | payable(msg.sender).transfer(amount); 16 | } 17 | 18 | receive() external payable { 19 | _mint(msg.sender, msg.value); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /section3/SandwichPractice/contracts/test/TestWETH9.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; 5 | import { IWETH9 } from "./interfaces/IWETH9.sol"; 6 | 7 | contract TestWETH9 is IWETH9, ERC20("WETH", "WETH") { 8 | function deposit() external payable { 9 | _mint(msg.sender, msg.value); 10 | } 11 | 12 | function withdraw(uint256 amount) external { 13 | require(balanceOf(msg.sender) >= amount, "Insufficient balance"); 14 | _burn(msg.sender, amount); 15 | payable(msg.sender).transfer(amount); 16 | } 17 | 18 | receive() external payable { 19 | _mint(msg.sender, msg.value); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /section3/UniswapV2Practice/contracts/Bank.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.17; 2 | 3 | contract Bank { 4 | mapping(address => uint256) public balances; 5 | 6 | // Reentrancy Guard 7 | // uint256 private _unlock = 1; 8 | // modifier lock() { 9 | // require(_unlock == 1, "Locked"); 10 | // _unlock = 2; 11 | // _; 12 | // _unlock = 1; 13 | // } 14 | 15 | function deposit() external payable { 16 | balances[msg.sender] += msg.value; 17 | } 18 | 19 | function withdraw() external { 20 | (bool success, ) = msg.sender.call{ value: balances[msg.sender] }(""); 21 | require(success, "Withdraw Failed"); 22 | 23 | balances[msg.sender] = 0; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /section3/UniswapV2Practice/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@appworks-school/uniswap-v2-practice", 3 | "version": "1.0.0", 4 | "description": "Appworks school uniswap v2 practice", 5 | "scripts": { 6 | "build": "forge build", 7 | "test": "forge test" 8 | }, 9 | "engines": { 10 | "node": ">=16", 11 | "npm": ">=7" 12 | }, 13 | "license": "MIT", 14 | "devDependencies": { 15 | "eslint-config-prettier": "8.5.0", 16 | "hardhat": "2.14.0", 17 | "lint-staged": "11.0.0", 18 | "prettier": "2.3.0", 19 | "prettier-plugin-solidity": "1.0.0-rc.1" 20 | }, 21 | "lint-staged": { 22 | "*.ts": [ 23 | "prettier --write" 24 | ], 25 | "*.sol": [ 26 | "prettier --write", 27 | "solhint" 28 | ] 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /section2/RugPullHw/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: workflow_dispatch 4 | 5 | env: 6 | FOUNDRY_PROFILE: ci 7 | 8 | jobs: 9 | check: 10 | strategy: 11 | fail-fast: true 12 | 13 | name: Foundry project 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | with: 18 | submodules: recursive 19 | 20 | - name: Install Foundry 21 | uses: foundry-rs/foundry-toolchain@v1 22 | with: 23 | version: nightly 24 | 25 | - name: Run Forge build 26 | run: | 27 | forge --version 28 | forge build --sizes 29 | id: build 30 | 31 | - name: Run Forge tests 32 | run: | 33 | forge test -vvv 34 | id: test 35 | -------------------------------------------------------------------------------- /section2/whitelist/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: workflow_dispatch 4 | 5 | env: 6 | FOUNDRY_PROFILE: ci 7 | 8 | jobs: 9 | check: 10 | strategy: 11 | fail-fast: true 12 | 13 | name: Foundry project 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | with: 18 | submodules: recursive 19 | 20 | - name: Install Foundry 21 | uses: foundry-rs/foundry-toolchain@v1 22 | with: 23 | version: nightly 24 | 25 | - name: Run Forge build 26 | run: | 27 | forge --version 28 | forge build --sizes 29 | id: build 30 | 31 | - name: Run Forge tests 32 | run: | 33 | forge test -vvv 34 | id: test 35 | -------------------------------------------------------------------------------- /section3/CompoundPractice/test/helper/CompoundPracticeSetUp.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.19; 2 | 3 | import "forge-std/Test.sol"; 4 | 5 | contract CompoundPracticeSetUp is Test { 6 | address borrowerAddress; 7 | 8 | function setUp() public virtual { 9 | string memory path = string( 10 | abi.encodePacked(vm.projectRoot(), "/test/helper/Borrower.json") 11 | ); 12 | string memory json = vm.readFile(path); 13 | bytes memory creationCode = vm.parseBytes(abi.decode(vm.parseJson(json, ".bytecode"), (string))); 14 | 15 | address addr; 16 | assembly { 17 | addr := create(0, add(creationCode, 0x20), mload(creationCode)) 18 | } 19 | require(addr != address(0), "Borrower deploy failed"); 20 | 21 | borrowerAddress = addr; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /section3/LatenRisk/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: workflow_dispatch 4 | 5 | env: 6 | FOUNDRY_PROFILE: ci 7 | 8 | jobs: 9 | check: 10 | strategy: 11 | fail-fast: true 12 | 13 | name: Foundry project 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | with: 18 | submodules: recursive 19 | 20 | - name: Install Foundry 21 | uses: foundry-rs/foundry-toolchain@v1 22 | with: 23 | version: nightly 24 | 25 | - name: Run Forge build 26 | run: | 27 | forge --version 28 | forge build --sizes 29 | id: build 30 | 31 | - name: Run Forge tests 32 | run: | 33 | forge test -vvv 34 | id: test 35 | -------------------------------------------------------------------------------- /section2/ProxyPattern/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: workflow_dispatch 4 | 5 | env: 6 | FOUNDRY_PROFILE: ci 7 | 8 | jobs: 9 | check: 10 | strategy: 11 | fail-fast: true 12 | 13 | name: Foundry project 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | with: 18 | submodules: recursive 19 | 20 | - name: Install Foundry 21 | uses: foundry-rs/foundry-toolchain@v1 22 | with: 23 | version: nightly 24 | 25 | - name: Run Forge build 26 | run: | 27 | forge --version 28 | forge build --sizes 29 | id: build 30 | 31 | - name: Run Forge tests 32 | run: | 33 | forge test -vvv 34 | id: test 35 | -------------------------------------------------------------------------------- /section2/StandardProxy/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: workflow_dispatch 4 | 5 | env: 6 | FOUNDRY_PROFILE: ci 7 | 8 | jobs: 9 | check: 10 | strategy: 11 | fail-fast: true 12 | 13 | name: Foundry project 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | with: 18 | submodules: recursive 19 | 20 | - name: Install Foundry 21 | uses: foundry-rs/foundry-toolchain@v1 22 | with: 23 | version: nightly 24 | 25 | - name: Run Forge build 26 | run: | 27 | forge --version 28 | forge build --sizes 29 | id: build 30 | 31 | - name: Run Forge tests 32 | run: | 33 | forge test -vvv 34 | id: test 35 | -------------------------------------------------------------------------------- /section3/CompoundGovernancePractice/.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: workflow_dispatch 4 | 5 | env: 6 | FOUNDRY_PROFILE: ci 7 | 8 | jobs: 9 | check: 10 | strategy: 11 | fail-fast: true 12 | 13 | name: Foundry project 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | with: 18 | submodules: recursive 19 | 20 | - name: Install Foundry 21 | uses: foundry-rs/foundry-toolchain@v1 22 | with: 23 | version: nightly 24 | 25 | - name: Run Forge build 26 | run: | 27 | forge --version 28 | forge build --sizes 29 | id: build 30 | 31 | - name: Run Forge tests 32 | run: | 33 | forge test -vvv 34 | id: test 35 | -------------------------------------------------------------------------------- /section2/StandardProxy/src/UUPS/UUPSMultiSigWallet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import { MultiSigWallet } from "../MultiSigWallet/MultiSigWallet.sol"; 5 | import { Slots } from "../utils/Slots.sol"; 6 | import { Proxiable } from "./Proxiable.sol"; 7 | 8 | contract UUPSMultiSigWallet is Slots, MultiSigWallet, Proxiable { 9 | 10 | function proxiableUUID() public pure returns (bytes32) { 11 | return bytes32(keccak256("PROXIABLE")); 12 | } 13 | 14 | function updateCodeAddress(address newImplementation, bytes memory data) external onlyAdmin { 15 | // TODO: 16 | // 1. check if newimplementation is compatible with proxiable 17 | // 2. update the implementation address 18 | // 3. initialize proxy, if data exist, then initialize proxy with _data 19 | } 20 | } -------------------------------------------------------------------------------- /section3/UniswapV2Practice/README.md: -------------------------------------------------------------------------------- 1 | # UniswapV2Practice 2 | In `UniswapV2Practice.t.sol`, we have four practices and two discussions. Please follow the instructions provided in the comments to successfully pass the tests. 3 | 4 | # Reentrancy Practice 5 | Please exploit the bank contract in `Bank.t.sol` 6 | 7 | ## Reference: 8 | - UniswapV2-core: https://github.com/Uniswap/v2-core 9 | - UniswapV2-periphery: https://github.com/Uniswap/v2-periphery 10 | 11 | ## Environment 12 | - Add `MAINNET_RPC_ENDPOINT` to `foundry.toml` 13 | 14 | ## Local Development 15 | Clone this repository, install Node.js dependencies, and build the source code: 16 | 17 | ```bash 18 | git clone git@github.com:AppWorks-School/Blockchain-Resource.git 19 | cd Blockchain-Resource/section3/UniswapV2Practice 20 | forge install 21 | forge build 22 | forge test 23 | ``` 24 | -------------------------------------------------------------------------------- /section2/StandardProxy/src/BeaconProxy/BeaconProxy.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import { IBeacon } from "./IBeacon.sol"; 5 | import { Slots } from "../utils/Slots.sol"; 6 | import { Proxy } from "../utils/Proxy.sol"; 7 | 8 | contract BeaconProxy is Slots, Proxy { 9 | 10 | bytes32 constant BEACON_SLOT = bytes32(uint256(keccak256("eip1967.proxy.beacon")) - 1); 11 | 12 | constructor(address _beacon) { 13 | // TODO: set beacon address at BEACON_SLOT 14 | } 15 | 16 | function _getBeacon() internal view returns (address) { 17 | return _getSlotToAddress(BEACON_SLOT); 18 | } 19 | 20 | function _implemenation() internal view returns (address) { 21 | // TODO: return implementation address from beacon 22 | } 23 | 24 | fallback() external payable { 25 | _delegate(_implemenation()); 26 | } 27 | } -------------------------------------------------------------------------------- /section2/StandardProxy/src/UpgradeableProxy.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import { Proxy } from "./utils/Proxy.sol"; 5 | 6 | contract UpgradeableProxy is Proxy { 7 | 8 | address impl; 9 | address admin; 10 | 11 | constructor(address _impl, bytes memory _data) payable { 12 | admin = msg.sender; 13 | impl = _impl; 14 | if (_data.length > 0) { 15 | (bool success, ) = impl.delegatecall(_data); 16 | require(success, "init failed"); 17 | } 18 | } 19 | 20 | modifier onlyAdmin { 21 | require(msg.sender == admin, "only admin"); 22 | _; 23 | } 24 | 25 | function upgradeToAndCall(address _impl, bytes calldata _data) external payable onlyAdmin { 26 | impl = _impl; 27 | (bool success, ) = impl.delegatecall(_data); 28 | require(success, "init failed"); 29 | } 30 | } -------------------------------------------------------------------------------- /section2/StandardProxy/src/MultiSigWallet/MultiSigWalletV2.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SEE LICENSE IN LICENSE 2 | pragma solidity ^0.8.17; 3 | 4 | import { MultiSigWallet } from "./MultiSigWallet.sol"; 5 | 6 | contract MultiSigWalletV2 is MultiSigWallet { 7 | 8 | bool public v2Initialized; 9 | 10 | function VERSION() external view virtual override returns (string memory) { 11 | return "0.0.2"; 12 | } 13 | 14 | function v2Initialize() external { 15 | require(!v2Initialized, "already initialized"); 16 | v2Initialized = true; 17 | } 18 | 19 | function cancelTransaction() external onlyOwner { 20 | require(v2Initialized, "not initialized"); 21 | delete transactions[transactions.length - 1]; 22 | } 23 | 24 | function upgradeToAndCall_23573451() external pure returns (string memory) { 25 | return "23573451"; 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /section2/contract-optimization.md: -------------------------------------------------------------------------------- 1 | # 合約使用優化 2 | 3 | ## 說明: 4 | 5 | 請學習在 EVM 中從 storage 讀取與寫入的 Gas Fee 是如何計算的,在理解後請解釋為什麼有些合約不使用 boolean 而是使用 uint 0, 1 進行判斷? 又為何有些使用場景不使用 0, 1 而是使用 1, 2 來取代 boolean? 6 | 7 | 8 | ## 參考資料 9 | - [Solidity Bytecode and Opcode Basics](https://medium.com/@blockchain101/solidity-bytecode-and-opcode-basics-672e9b1a88c2) 10 | 11 | - [Ethereum VM (EVM) Opcodes and Instruction Reference](https://github.com/crytic/evm-opcodes) 12 | 13 | - [How to reduce Gas cost in Smart contract?](https://vishwasbanand.medium.com/how-to-reduce-gas-cost-in-smart-contract-9563a573be00) 14 | 15 | - [EIP-2929: Gas cost increases for state access opcodes](https://eips.ethereum.org/EIPS/eip-2929) 16 | 17 | - [OpenZeppelin ReentrancyGuard](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol) 18 | 19 | 20 | --- 21 | [回階段二](./README.md) 22 | -------------------------------------------------------------------------------- /section2/RugPullHw/src/Ownable.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SEE LICENSE IN LICENSE 2 | pragma solidity ^0.8.17; 3 | 4 | contract Ownable { 5 | bytes32 internal constant _OWNER_SLOT_ = 0xa7b53796fd2d99cb1f5ae019b54f9e024446c3d12b483f733ccc62ed04eb126b; 6 | 7 | function initializeOwnable(address _owner) internal { 8 | setOwner(_owner); 9 | } 10 | 11 | function getOwner() public view returns (address owner) { 12 | assembly { 13 | owner := sload(_OWNER_SLOT_) 14 | } 15 | } 16 | 17 | function setOwner(address _owner) internal { 18 | assembly { 19 | sstore(_OWNER_SLOT_, _owner) 20 | } 21 | } 22 | modifier onlyOwner() { 23 | require(msg.sender == getOwner(), "Ownable: caller is not the owner"); 24 | _; 25 | } 26 | 27 | function transferOwnership(address newOwner) external onlyOwner { 28 | require(newOwner != address(0), "Ownable: new owner is the zero address"); 29 | setOwner(newOwner); 30 | } 31 | } -------------------------------------------------------------------------------- /section3/UniswapV2Practice/test/Bank.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.17; 3 | 4 | import "forge-std/Test.sol"; 5 | import { Bank } from "../contracts/Bank.sol"; 6 | import { Attack } from "../contracts/Attack.sol"; 7 | 8 | contract BankTest is Test { 9 | Bank public bank; 10 | address public alice = makeAddr("Alice"); 11 | address public attacker = makeAddr("Attacker"); 12 | 13 | function setUp() public { 14 | bank = new Bank(); 15 | 16 | // mint 10 ETH to alice 17 | deal(alice, 10 ether); 18 | 19 | // mint 1 ETH to attacker 20 | deal(attacker, 1 ether); 21 | 22 | // alice deposit 10 ETH to contract 23 | vm.prank(alice); 24 | bank.deposit{ value: 10 ether }(); 25 | } 26 | 27 | function test_attack() public { 28 | // 1. Deploy attack contract 29 | // 2. Exploit the bank 30 | 31 | assertEq(address(bank).balance, 0); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /section3/FlashLoanPractice/src/AaveFlashLoan.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.19; 2 | 3 | import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol"; 4 | import {BalanceChecker} from "./BalanceChecker.sol"; 5 | import { 6 | IFlashLoanSimpleReceiver, 7 | IPoolAddressesProvider, 8 | IPool 9 | } from "aave-v3-core/contracts/flashloan/interfaces/IFlashLoanSimpleReceiver.sol"; 10 | 11 | // TODO: Inherit IFlashLoanSimpleReceiver 12 | contract AaveFlashLoan { 13 | address constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; 14 | address constant POOL_ADDRESSES_PROVIDER = 0x2f39d218133AFaB8F2B819B1066c7E434Ad94E9e; 15 | 16 | function execute(BalanceChecker checker) external { 17 | // TODO 18 | } 19 | 20 | function ADDRESSES_PROVIDER() public view returns (IPoolAddressesProvider) { 21 | return IPoolAddressesProvider(POOL_ADDRESSES_PROVIDER); 22 | } 23 | 24 | function POOL() public view returns (IPool) { 25 | return IPool(ADDRESSES_PROVIDER().getPool()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /section2/StandardProxy/src/StorageExamples.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SEE LICENSE IN LICENSE 2 | pragma solidity ^0.8.17; 3 | 4 | // 1. Gap the storage contract 5 | contract LogicGapExample { 6 | uint256[50] private __gap; 7 | uint256 public normaldata1; 8 | uint256 public normaldata2; 9 | bytes32 public normaldata3; 10 | } 11 | 12 | // 2. Inherit the storage contract 13 | // Both logic contract and storage contract inherit this storage contract 14 | contract CommonStorageExample { 15 | address public implementation; 16 | address public admin; 17 | } 18 | 19 | // 3. Logic contract inherit the storage contract to store data 20 | contract EternalStorage { 21 | mapping(bytes32 => uint256) internal uintStorage; 22 | mapping(bytes32 => string) internal stringStorage; 23 | mapping(bytes32 => address) internal addressStorage; 24 | mapping(bytes32 => bytes) internal bytesStorage; 25 | mapping(bytes32 => bool) internal boolStorage; 26 | mapping(bytes32 => int256) internal intStorage; 27 | } -------------------------------------------------------------------------------- /section2/RugPullHw/src/UpgradeableProxy.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SEE LICENSE IN LICENSE 2 | pragma solidity ^0.8.17; 3 | 4 | import { Proxy } from "./Proxy.sol"; 5 | import { Ownable } from "./Ownable.sol"; 6 | 7 | contract UpgradeableProxy is Proxy, Ownable { 8 | 9 | bytes32 internal constant _IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; 10 | 11 | constructor(address _implementation) { 12 | _setImpl(_implementation); 13 | initializeOwnable(msg.sender); 14 | } 15 | 16 | function implementation() public view returns (address impl) { 17 | assembly { 18 | impl := sload(_IMPLEMENTATION_SLOT) 19 | } 20 | } 21 | 22 | function _setImpl(address _newImpl) internal { 23 | assembly { 24 | sstore(_IMPLEMENTATION_SLOT, _newImpl) 25 | } 26 | } 27 | 28 | function upgradeTo(address _implementation) external onlyOwner { 29 | _setImpl(_implementation); 30 | } 31 | 32 | fallback() external payable { 33 | _delegate(implementation()); 34 | } 35 | 36 | receive() external payable {} 37 | } -------------------------------------------------------------------------------- /section3/SimpleSwap/README.md: -------------------------------------------------------------------------------- 1 | # SimpleSwap 2 | Implement a **Simple AMM Swap** contract with a **0% fee** ratio in `contracts/SimpleSwap.sol`. Ensure that you **override all the external functions defined in `ISimpleSwap.sol`**, and that the implementation passes all the tests. 3 | 4 | It is recommended to first read the NatSpec documentation in `ISimpleSwap.sol` before implementing the contract. If there are any uncertainties regarding the function's purpose or implementation, feel free to discuss them in the Discord channel. 5 | 6 | Reference: 7 | - UniswapV2-core: https://github.com/Uniswap/v2-core 8 | - UniswapV2-periphery: https://github.com/Uniswap/v2-periphery 9 | 10 | 11 | ## Local Development 12 | You need Node.js 16+ to build. Use [nvm](https://github.com/nvm-sh/nvm) to install it. 13 | 14 | Clone this repository, install Node.js dependencies, and build the source code: 15 | 16 | ```bash 17 | git clone git@github.com:AppWorks-School/Blockchain-Resource.git 18 | cd Blockchain-Resource/section3/SimpleSwap 19 | npm install 20 | npm run test:hardhat 21 | npm run test:foundry 22 | ``` 23 | 24 | -------------------------------------------------------------------------------- /section3/FlashLoanPractice/test/AaveFlashLoan.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import "forge-std/Test.sol"; 5 | import "../src/BalanceChecker.sol"; 6 | import "../src/AaveFlashLoan.sol"; 7 | 8 | contract AaveFlashLoanTest is Test { 9 | address constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48; 10 | 11 | BalanceChecker public checker; 12 | AaveFlashLoan public aaveFlashLoan; 13 | 14 | function setUp() public { 15 | string memory rpc = vm.envString("MAINNET_RPC_URL"); 16 | vm.createSelectFork(rpc); 17 | 18 | checker = new BalanceChecker(); 19 | aaveFlashLoan = new AaveFlashLoan(); 20 | 21 | uint256 initialBalance = 50_000 * 10 ** 6; 22 | deal(USDC, address(aaveFlashLoan), initialBalance); 23 | 24 | vm.label(address(checker), "BalanceChecker"); 25 | vm.label(address(aaveFlashLoan), "Flash Loan"); 26 | } 27 | 28 | function testAaveFlashLoan() public { 29 | assertEq(checker.pass(), false); 30 | 31 | aaveFlashLoan.execute(checker); 32 | 33 | assertEq(checker.pass(), true); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /section2/whitelist/src/ERC721Whitelist.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SEE LICENSE IN LICENSE 2 | pragma solidity ^0.8.17; 3 | 4 | import { ERC721, ERC721Enumerable } from "oz/token/ERC721/extensions/ERC721Enumerable.sol"; 5 | import "oz/utils/cryptography/MerkleProof.sol"; 6 | 7 | contract ERC721Whitelist is ERC721Enumerable { 8 | 9 | bytes32 immutable private merkleRoot; 10 | mapping(address => bool) public claimed; 11 | 12 | constructor(string memory _name, string memory _symbol, bytes32 _merkleRoot) ERC721(_name, _symbol) { 13 | merkleRoot = _merkleRoot; 14 | } 15 | 16 | function inWhitelist(bytes32[] memory _merkleProof, address _who) internal view returns (bool) { 17 | bytes32 leaf = keccak256(abi.encodePacked(_who)); 18 | return MerkleProof.verify(_merkleProof, merkleRoot, leaf); 19 | } 20 | 21 | 22 | function mint(bytes32[] memory _merkleProof) external { 23 | // TODO: 24 | // 1. Check if the user is in the whitelist 25 | // 2. Check if the user has already claimed the token 26 | // 3. Mint the token to the user 27 | // 4. Mark the user as claimed 28 | } 29 | } -------------------------------------------------------------------------------- /section2/StandardProxy/src/BeaconProxy/UpgradeableBeacon.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import { Slots } from "../utils/Slots.sol"; 5 | import { Proxy } from "../utils/Proxy.sol"; 6 | 7 | contract UpgradeableBeacon is Slots, Proxy { 8 | 9 | bytes32 constant IMPLEMENTATION_SLOT = bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1); 10 | bytes32 constant ADMIN_SLOT = bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1); 11 | 12 | constructor(address _impl) { 13 | // TODO: 14 | // 1. set admin owner address at ADMIN_SLOT 15 | // 2. set implementation address at IMPLEMENTATION_SLOT 16 | } 17 | 18 | modifier onlyAdmin { 19 | // TODO: 20 | // 1. check if msg.sender is equal to admin owner address, if no then revert with Message "only admin" 21 | _; 22 | } 23 | 24 | function upgradeTo(address newImplementation) external onlyAdmin { 25 | // TODO: set implementation address at IMPLEMENTATION_SLOT 26 | } 27 | 28 | function implementation() external view returns (address) { 29 | // TODO: return implementation address 30 | } 31 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

區塊鏈自學資源彙整

2 |

3 | 4 |

5 | 6 |

7 | 這是一份由 AppWorks School 整理的 Solidity 區塊鏈開發自學教材,目的在降低區塊鏈知識學習門檻,讓更多人能夠接觸區塊鏈技術。透過階段式實作的方式,逐步學習區塊鏈相關知識,並統整線上的學習資源,以解決目前區塊鏈領域自學時資源散落不易搜集的問題。 8 |

9 | 10 |

11 | 開放式資源將會持續更新,歡迎各路好手提供建議和指正,讓這份資源更完整! 12 |

13 | 14 | --- 15 | 16 | # 目錄 17 | 教材主要分成三個部分: 18 | 19 | 20 | 21 | 22 | 25 | 28 | 29 | 30 | 33 | 34 | 35 | 36 | 39 | 42 | 43 |
23 | 區塊鏈基礎知識 24 | 26 | 根據線上已有的學習資源,了解基本的區塊鏈及以太坊相關概念(合約、Solidity 語法、交易、錢包、節點等) 27 |
31 | 上手實作及問題思考 32 | 透過實戰演練及特定主題研究,對區塊鏈有更深一層的了解。
37 | 經典 DeFi 機制的賞析 38 | 40 | 了解經典的 DeFi 基礎建設,並能夠複製、整合、加以改進,由學習層面進階到實際應用層面 41 |
44 | -------------------------------------------------------------------------------- /section3/SimpleSwap/test/fixtures.ts: -------------------------------------------------------------------------------- 1 | import { ethers } from "hardhat" 2 | import { SimpleSwap, SimpleSwap__factory, TestERC20, TestERC20__factory } from "../typechain-types" 3 | export interface SimpleSwapFixture { 4 | simpleSwap: SimpleSwap 5 | tokenA: TestERC20 6 | tokenB: TestERC20 7 | } 8 | 9 | export async function deploySimpleSwapFixture(): Promise { 10 | // Deploy tokenA, tokenB 11 | const ERC20Factory = (await ethers.getContractFactory("TestERC20")) as TestERC20__factory 12 | 13 | const tokenA = (await ERC20Factory.deploy("TokenA", "TokenA")) as TestERC20 14 | await tokenA.deployed() 15 | 16 | const tokenB = (await ERC20Factory.deploy("TokenB", "TokenB")) as TestERC20 17 | await tokenB.deployed() 18 | 19 | // Deploy SimpleSwap 20 | const SimpleSwapFactory = (await ethers.getContractFactory("SimpleSwap")) as SimpleSwap__factory 21 | const simpleSwap = (await SimpleSwapFactory.deploy(tokenA.address, tokenB.address)) as SimpleSwap 22 | await simpleSwap.deployed() 23 | 24 | return { 25 | simpleSwap, 26 | tokenA, 27 | tokenB, 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /section3/FlashSwapPractice/contracts/FakeLendingProtocol.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 5 | import { IUniswapV2Pair } from "v2-core/interfaces/IUniswapV2Pair.sol"; 6 | 7 | // This is a fake lending protocol for testing 8 | // when call liquidatePosition, user need to pay 80 USDC then get 1 ETH back 9 | contract FakeLendingProtocol { 10 | address internal immutable _USDC; 11 | 12 | constructor(address usdc) payable { 13 | require(msg.value == 1 ether, "Initial value must be 1 ether"); 14 | _USDC = usdc; 15 | } 16 | 17 | // Let's assume liquidate position can use 80 USDC to liquidate 1 ETH position 18 | // ETH original price is 100 USDC, discount 20% 19 | function liquidatePosition() external { 20 | bool success = IERC20(_USDC).transferFrom(msg.sender, address(this), 80 * 10 ** 6); 21 | require(success, "Transfer USDC failed"); 22 | 23 | (bool success2, ) = msg.sender.call{ value: address(this).balance }(""); 24 | require(success2, "Transfer ETH failed"); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /section3/FlashSwapPractice/README.md: -------------------------------------------------------------------------------- 1 | # Flash Swap Practice 2 | This is a UniswapV2 flash swap practice, our goal is to pass the test. 3 | 4 | ### Practice 1: `Liquidator.sol` 5 | `liquidate()` will call `FakeLendingProtocol.liquidatePosition()` to liquidate the position of the user. 6 | Follow the instructions in `contracts/Liquidator.sol` to complete the practice. 7 | (Do not change any other files) 8 | 9 | ### Practice 2: `Arbitrage.sol` 10 | `arbitrage()` will do the arbitrage between the given two pools (must be the same pair). 11 | Follow the instructions in `contracts/Arbitrage.sol` to complete the practice. 12 | For convenience, we will only practice method 1, and fix the borrowed amount to 5 WETH 13 | 14 | If you are interested in the flash swap arbitrage, you can read more in this [repository](https://github.com/paco0x/amm-arbitrageur) 15 | 16 | ## Local Development 17 | Clone this repository, install Node.js dependencies, and build the source code: 18 | 19 | ```bash 20 | git clone git@github.com:AppWorks-School/Blockchain-Resource.git 21 | cd Blockchain-Resource/section3/FlashSwapPractice 22 | npm install 23 | forge install 24 | forge build 25 | forge test 26 | ``` 27 | 28 | -------------------------------------------------------------------------------- /section2/README.md: -------------------------------------------------------------------------------- 1 | 2 | # 階段二:區塊鏈基礎實作及問題探討 3 | 4 | ## 說明: 5 | 第二階段會針對合約的基礎用法、常見的使用情境、以及具體的實作內容進行問題研究與練習。題目根據類型簡易分成三類,未來將會陸續新增。 6 | 7 | 本階段的問題解答,在網路上都很容易查到,但就於學習效果而言,不知道解答的前提下,難度將會大大提升。我們強烈建議在確實實作過後,再查詢答案,以確定自己的理解是否正確。 8 | 9 | ## 問題集: 10 | ### 合約基礎(語言篇) 11 | 1. [msg.sender v.s. tx.origin](./msgSender-vs-txOrigin.md) 12 | 2. [View / pure function 及 function visibility](./view-pure-function-and-function-visibility.md) 13 | 3. [Function signature](./function-signature.md) 14 | 4. [Static call](./static-call.md) 15 | 5. [合約運算](./arithmetics.md) 16 | 6. [內部交易(Internal Transaction)](./internal-transaction.md) 17 | 18 | 19 | ### 合約進階(實作篇) 20 | 1. [白名單(Whitelist)](./whitelist.md) 21 | 2. [預言機(Oracle)](./oracle.md) 22 | 3. [合約使用優化](./contract-optimization.md) 23 | 4. [代理合約(Proxy Contract)](./proxy-contract.md) 24 | 5. [亂數產生(Random Number Generation)](./random-number-generation.md) 25 | 6. [重入攻擊(Re-entrancy Attack)](./Re-entrancy.md) 26 | 27 | ### EIP 28 | 1. [ERC20 - Transfer approval](./ERC20-Transfer-approval.md) 29 | 2. [Token visibility](./token-visibility.md) 30 | 3. [ERC721 實作](./ERC721-practice.md) 31 | 4. [EIP-712 實作](./EIP-712.md) 32 | 33 | --- 34 | [上一階段](../section1/README.md) | [下一階段](../section3/README.md)・[回主頁](../README.md) 35 | -------------------------------------------------------------------------------- /section3/CompoundPractice/README.md: -------------------------------------------------------------------------------- 1 | # Compound Practice 2 | 3 | Welcome to the Compound Practice repository! The main purpose of this repository is to provide a platform for everyone to practice using Compound Finance and gain familiarity with its operational principles. 4 | 5 | ## Exercise 1 6 | Practice calling `mint` and `redeem` on cTokens, and simulate the accrual of interest on deposited funds over a period of time using Foundry's cheatcode. 7 | 8 | ## Environment Setup 9 | 10 | To get started with the Compound Practice repository, follow the steps below: 11 | 12 | 1. Clone the repository: 13 | ```shell 14 | git clone git@github.com:AppWorks-School/Blockchain-Resource.git 15 | ``` 16 | 17 | 2. Navigate to the Compound Practice directory: 18 | ```shell 19 | cd Blockchain-Resource/section3/CompoundPractice 20 | ``` 21 | 22 | 3. Install the necessary dependencies: 23 | ```shell 24 | forge install 25 | ``` 26 | 27 | 4. Build the project: 28 | ```shell 29 | forge build 30 | ``` 31 | 32 | 5. Run the tests in fork mode: 33 | ```shell 34 | forge test --fork-url $RPC_URL 35 | ``` 36 | 37 | Feel free to explore the code and dive into the exercises provided to enhance your understanding of Compound Finance. Happy practicing! 38 | -------------------------------------------------------------------------------- /section2/StandardProxy/src/UUPSProxy.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | // Standard contract of UUPS 5 | contract UUPSProxy { 6 | constructor(bytes memory constructData, address contractLogic) { 7 | assembly { // solium-disable-line 8 | sstore(0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7, contractLogic) 9 | } 10 | (bool success, ) = contractLogic.delegatecall(constructData); // solium-disable-line 11 | require(success, "Construction failed"); 12 | } 13 | 14 | fallback() external payable { 15 | assembly { // solium-disable-line 16 | let contractLogic := sload(0xc5f16f0fcc639fa48a6947836d9850f504798523bf8c9a3a87d5876cf622bcf7) 17 | calldatacopy(0x0, 0x0, calldatasize()) 18 | let success := delegatecall(sub(gas(), 10000), contractLogic, 0x0, calldatasize(), 0, 0) 19 | let retSz := returndatasize() 20 | returndatacopy(0, 0, retSz) 21 | switch success 22 | case 0 { 23 | revert(0, retSz) 24 | } 25 | default { 26 | return(0, retSz) 27 | } 28 | } 29 | } 30 | 31 | receive() external payable {} 32 | } -------------------------------------------------------------------------------- /section2/ProxyPattern/README.md: -------------------------------------------------------------------------------- 1 | ## Foundry 2 | 3 | **Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** 4 | 5 | Foundry consists of: 6 | 7 | - **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). 8 | - **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. 9 | - **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. 10 | - **Chisel**: Fast, utilitarian, and verbose solidity REPL. 11 | 12 | ## Documentation 13 | 14 | https://book.getfoundry.sh/ 15 | 16 | ## Usage 17 | 18 | ### Build 19 | 20 | ```shell 21 | $ forge build 22 | ``` 23 | 24 | ### Test 25 | 26 | ```shell 27 | $ forge test 28 | ``` 29 | 30 | ### Format 31 | 32 | ```shell 33 | $ forge fmt 34 | ``` 35 | 36 | ### Gas Snapshots 37 | 38 | ```shell 39 | $ forge snapshot 40 | ``` 41 | 42 | ### Anvil 43 | 44 | ```shell 45 | $ anvil 46 | ``` 47 | 48 | ### Deploy 49 | 50 | ```shell 51 | $ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key 52 | ``` 53 | 54 | ### Cast 55 | 56 | ```shell 57 | $ cast 58 | ``` 59 | 60 | ### Help 61 | 62 | ```shell 63 | $ forge --help 64 | $ anvil --help 65 | $ cast --help 66 | ``` 67 | -------------------------------------------------------------------------------- /section2/RugPullHw/README.md: -------------------------------------------------------------------------------- 1 | ## Foundry 2 | 3 | **Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** 4 | 5 | Foundry consists of: 6 | 7 | - **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). 8 | - **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. 9 | - **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. 10 | - **Chisel**: Fast, utilitarian, and verbose solidity REPL. 11 | 12 | ## Documentation 13 | 14 | https://book.getfoundry.sh/ 15 | 16 | ## Usage 17 | 18 | ### Build 19 | 20 | ```shell 21 | $ forge build 22 | ``` 23 | 24 | ### Test 25 | 26 | ```shell 27 | $ forge test 28 | ``` 29 | 30 | ### Format 31 | 32 | ```shell 33 | $ forge fmt 34 | ``` 35 | 36 | ### Gas Snapshots 37 | 38 | ```shell 39 | $ forge snapshot 40 | ``` 41 | 42 | ### Anvil 43 | 44 | ```shell 45 | $ anvil 46 | ``` 47 | 48 | ### Deploy 49 | 50 | ```shell 51 | $ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key 52 | ``` 53 | 54 | ### Cast 55 | 56 | ```shell 57 | $ cast 58 | ``` 59 | 60 | ### Help 61 | 62 | ```shell 63 | $ forge --help 64 | $ anvil --help 65 | $ cast --help 66 | ``` 67 | -------------------------------------------------------------------------------- /section2/StandardProxy/README.md: -------------------------------------------------------------------------------- 1 | ## Foundry 2 | 3 | **Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** 4 | 5 | Foundry consists of: 6 | 7 | - **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). 8 | - **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. 9 | - **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. 10 | - **Chisel**: Fast, utilitarian, and verbose solidity REPL. 11 | 12 | ## Documentation 13 | 14 | https://book.getfoundry.sh/ 15 | 16 | ## Usage 17 | 18 | ### Build 19 | 20 | ```shell 21 | $ forge build 22 | ``` 23 | 24 | ### Test 25 | 26 | ```shell 27 | $ forge test 28 | ``` 29 | 30 | ### Format 31 | 32 | ```shell 33 | $ forge fmt 34 | ``` 35 | 36 | ### Gas Snapshots 37 | 38 | ```shell 39 | $ forge snapshot 40 | ``` 41 | 42 | ### Anvil 43 | 44 | ```shell 45 | $ anvil 46 | ``` 47 | 48 | ### Deploy 49 | 50 | ```shell 51 | $ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key 52 | ``` 53 | 54 | ### Cast 55 | 56 | ```shell 57 | $ cast 58 | ``` 59 | 60 | ### Help 61 | 62 | ```shell 63 | $ forge --help 64 | $ anvil --help 65 | $ cast --help 66 | ``` 67 | -------------------------------------------------------------------------------- /section2/StandardProxy/src/ERC1967Proxy.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import { Slots } from "./utils/Slots.sol"; 5 | import { Proxy } from "./utils/Proxy.sol"; 6 | 7 | contract ERC1967Proxy is Slots, Proxy { 8 | 9 | constructor(address _impl, bytes memory _data) { 10 | // TODO: 11 | // 1. set the implementation address at bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1) 12 | // 2. set admin owner address at bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1) 13 | // 3. if data exist, then initialize proxy with _data 14 | } 15 | 16 | function implementation() public view returns (address impl) { 17 | // TODO: return the implementation address 18 | } 19 | 20 | modifier onlyAdmin { 21 | // TODO: check if msg.sender is equal to admin owner address 22 | _; 23 | } 24 | 25 | function upgradeToAndCall(address newImplementation, bytes memory _data) external onlyAdmin { 26 | // TODO: 27 | // 1. upgrade the implementation address 28 | // 2. initialize proxy, if data exist, then initialize proxy with _data 29 | } 30 | 31 | fallback() external payable virtual { 32 | _delegate(implementation()); 33 | } 34 | 35 | receive() external payable { 36 | _delegate(implementation()); 37 | } 38 | } -------------------------------------------------------------------------------- /section3/LatenRisk/README.md: -------------------------------------------------------------------------------- 1 | ## Foundry 2 | 3 | **Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** 4 | 5 | Foundry consists of: 6 | 7 | - **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). 8 | - **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. 9 | - **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. 10 | - **Chisel**: Fast, utilitarian, and verbose solidity REPL. 11 | 12 | ## Documentation 13 | 14 | https://book.getfoundry.sh/ 15 | 16 | ## Usage 17 | 18 | ### Build 19 | 20 | ```shell 21 | $ forge build 22 | ``` 23 | 24 | ### Test 25 | 26 | ```shell 27 | $ forge test 28 | ``` 29 | 30 | ### Format 31 | 32 | ```shell 33 | $ forge fmt 34 | ``` 35 | 36 | ### Gas Snapshots 37 | 38 | ```shell 39 | $ forge snapshot 40 | ``` 41 | 42 | ### Anvil 43 | 44 | ```shell 45 | $ anvil 46 | ``` 47 | 48 | ### Deploy 49 | 50 | ```shell 51 | $ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key 52 | ``` 53 | 54 | ### Cast 55 | 56 | ```shell 57 | $ cast 58 | ``` 59 | 60 | ### Help 61 | 62 | ```shell 63 | $ forge --help 64 | $ anvil --help 65 | $ cast --help 66 | ``` 67 | -------------------------------------------------------------------------------- /section2/StandardProxy/src/utils/Slots.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SEE LICENSE IN LICENSE 2 | pragma solidity ^0.8.17; 3 | 4 | contract Slots { 5 | 6 | function _setSlotToUint256(bytes32 _slot, uint256 value) internal { 7 | assembly { 8 | sstore(_slot, value) 9 | } 10 | } 11 | 12 | function _setSlotToAddress(bytes32 _slot, address value) internal { 13 | assembly { 14 | sstore(_slot, value) 15 | } 16 | } 17 | 18 | function _getSlotToAddress(bytes32 _slot) internal view returns (address value) { 19 | assembly { 20 | value := sload(_slot) 21 | } 22 | } 23 | } 24 | 25 | contract SlotsManipulate is Slots { 26 | 27 | function setAppworksWeek8(uint256 amount) external { 28 | // TODO: set AppworksWeek8 29 | } 30 | 31 | function setProxyImplementation(address _implementation) external { 32 | // TODO: set Proxy Implenmentation address 33 | } 34 | 35 | function setBeaconImplementation(address _implementation) external { 36 | // TODO: set Beacon Implenmentation address 37 | } 38 | 39 | function setAdminImplementation(address _who) external { 40 | // TODO: set Admin Implenmentation address 41 | } 42 | 43 | function setProxiable(address _implementation) external { 44 | // TODO: set Proxiable Implenmentation address 45 | } 46 | } -------------------------------------------------------------------------------- /section3/CompoundGovernancePractice/README.md: -------------------------------------------------------------------------------- 1 | ## Foundry 2 | 3 | **Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** 4 | 5 | Foundry consists of: 6 | 7 | - **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). 8 | - **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. 9 | - **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. 10 | - **Chisel**: Fast, utilitarian, and verbose solidity REPL. 11 | 12 | ## Documentation 13 | 14 | https://book.getfoundry.sh/ 15 | 16 | ## Usage 17 | 18 | ### Build 19 | 20 | ```shell 21 | $ forge build 22 | ``` 23 | 24 | ### Test 25 | 26 | ```shell 27 | $ forge test 28 | ``` 29 | 30 | ### Format 31 | 32 | ```shell 33 | $ forge fmt 34 | ``` 35 | 36 | ### Gas Snapshots 37 | 38 | ```shell 39 | $ forge snapshot 40 | ``` 41 | 42 | ### Anvil 43 | 44 | ```shell 45 | $ anvil 46 | ``` 47 | 48 | ### Deploy 49 | 50 | ```shell 51 | $ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key 52 | ``` 53 | 54 | ### Cast 55 | 56 | ```shell 57 | $ cast 58 | ``` 59 | 60 | ### Help 61 | 62 | ```shell 63 | $ forge --help 64 | $ anvil --help 65 | $ cast --help 66 | ``` 67 | -------------------------------------------------------------------------------- /section3/FlashLoanPractice/src/FlashSwapLiquidate.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.19; 2 | 3 | import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol"; 4 | import {IUniswapV2Callee} from "v2-core/interfaces/IUniswapV2Callee.sol"; 5 | import {IUniswapV2Factory} from "v2-core/interfaces/IUniswapV2Factory.sol"; 6 | import {IUniswapV2Pair} from "v2-core/interfaces/IUniswapV2Pair.sol"; 7 | import {IUniswapV2Router02} from "v2-periphery/interfaces/IUniswapV2Router02.sol"; 8 | import {CErc20} from "compound-protocol/contracts/CErc20.sol"; 9 | 10 | 11 | contract FlashSwapLiquidate is IUniswapV2Callee { 12 | IERC20 public USDC = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); 13 | IERC20 public DAI = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); 14 | CErc20 public cUSDC = CErc20(0x39AA39c021dfbaE8faC545936693aC917d5E7563); 15 | CErc20 public cDAI = CErc20(0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643); 16 | IUniswapV2Router02 public router = IUniswapV2Router02(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D); 17 | IUniswapV2Factory public factory = IUniswapV2Factory(0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f); 18 | 19 | 20 | function uniswapV2Call(address sender, uint256 amount0, uint256 amount1, bytes calldata data) external override { 21 | // TODO 22 | } 23 | 24 | function liquidate(address borrower, uint256 amountOut) external { 25 | // TODO 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /section3/SandwichPractice/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@appworks-school/sandwich-practice", 3 | "version": "1.0.0", 4 | "description": "Appworks school sandwich practice", 5 | "scripts": { 6 | "build": "forge build", 7 | "test": "forge test" 8 | }, 9 | "engines": { 10 | "node": ">=16", 11 | "npm": ">=7" 12 | }, 13 | "files": [ 14 | "artifacts/contracts/", 15 | "contracts/" 16 | ], 17 | "license": "MIT", 18 | "devDependencies": { 19 | "@nomicfoundation/hardhat-toolbox": "1.0.2", 20 | "@openzeppelin/contracts": "4.8.0", 21 | "@uniswap/v2-core": "1.0.1", 22 | "@uniswap/v2-periphery": "1.1.0-beta.0", 23 | "@typechain/hardhat": "6.1.4", 24 | "@types/chai": "4.3.3", 25 | "@types/mocha": "9.1.0", 26 | "@types/node": "18.11.9", 27 | "chai": "4.3.7", 28 | "eslint-config-prettier": "8.5.0", 29 | "hardhat": "2.14.0", 30 | "lint-staged": "11.0.0", 31 | "prettier": "2.3.0", 32 | "prettier-plugin-solidity": "1.0.0-rc.1", 33 | "solhint": "3.3.7", 34 | "solhint-plugin-prettier": "0.0.5", 35 | "ts-node": "10.9.1", 36 | "typescript": "4.8.4" 37 | }, 38 | "lint-staged": { 39 | "*.ts": [ 40 | "prettier --write" 41 | ], 42 | "*.sol": [ 43 | "prettier --write", 44 | "solhint" 45 | ] 46 | } 47 | } -------------------------------------------------------------------------------- /section3/FlashSwapPractice/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@appworks-school/sandwich-practice", 3 | "version": "1.0.0", 4 | "description": "Appworks school sandwich practice", 5 | "scripts": { 6 | "build": "forge build", 7 | "test": "forge test" 8 | }, 9 | "engines": { 10 | "node": ">=16", 11 | "npm": ">=7" 12 | }, 13 | "files": [ 14 | "artifacts/contracts/", 15 | "contracts/" 16 | ], 17 | "license": "MIT", 18 | "devDependencies": { 19 | "@nomicfoundation/hardhat-toolbox": "1.0.2", 20 | "@openzeppelin/contracts": "4.8.0", 21 | "@uniswap/v2-core": "1.0.1", 22 | "@uniswap/v2-periphery": "1.1.0-beta.0", 23 | "@typechain/hardhat": "6.1.4", 24 | "@types/chai": "4.3.3", 25 | "@types/mocha": "9.1.0", 26 | "@types/node": "18.11.9", 27 | "chai": "4.3.7", 28 | "eslint-config-prettier": "8.5.0", 29 | "hardhat": "2.14.0", 30 | "lint-staged": "11.0.0", 31 | "prettier": "2.3.0", 32 | "prettier-plugin-solidity": "1.0.0-rc.1", 33 | "solhint": "3.3.7", 34 | "solhint-plugin-prettier": "0.0.5", 35 | "ts-node": "10.9.1", 36 | "typescript": "4.8.4" 37 | }, 38 | "lint-staged": { 39 | "*.ts": [ 40 | "prettier --write" 41 | ], 42 | "*.sol": [ 43 | "prettier --write", 44 | "solhint" 45 | ] 46 | } 47 | } -------------------------------------------------------------------------------- /section3/SimpleSwap/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@appworks-school/simple-swap", 3 | "version": "1.0.0", 4 | "description": "Appworks school simple swap", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "hardhat compile", 8 | "coverage": "hardhat coverage", 9 | "clean": "rm -rf typechain && rm -rf artifacts && rm -rf cache", 10 | "test:hardhat": "hardhat test", 11 | "test:foundry": "forge test" 12 | }, 13 | "engines": { 14 | "node": ">=16", 15 | "npm": ">=7" 16 | }, 17 | "files": [ 18 | "artifacts/contracts/", 19 | "contracts/" 20 | ], 21 | "license": "MIT", 22 | "devDependencies": { 23 | "@nomicfoundation/hardhat-toolbox": "1.0.2", 24 | "@openzeppelin/contracts": "4.8.0", 25 | "@typechain/hardhat": "6.1.4", 26 | "@types/chai": "4.3.3", 27 | "@types/mocha": "9.1.0", 28 | "@types/node": "18.11.9", 29 | "chai": "4.3.7", 30 | "eslint-config-prettier": "8.5.0", 31 | "hardhat": "2.14.0", 32 | "lint-staged": "11.0.0", 33 | "prettier": "2.3.0", 34 | "prettier-plugin-solidity": "1.0.0-rc.1", 35 | "solhint": "3.3.7", 36 | "solhint-plugin-prettier": "0.0.5", 37 | "ts-node": "10.9.1", 38 | "typescript": "4.8.4" 39 | }, 40 | "lint-staged": { 41 | "*.ts": [ 42 | "prettier --write" 43 | ], 44 | "*.sol": [ 45 | "prettier --write", 46 | "solhint" 47 | ] 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /section3/FlashLoanPractice/test/FlashSwapLiquidate.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.19; 2 | 3 | import {Test} from "forge-std/Test.sol"; 4 | import {IERC20} from "openzeppelin/token/ERC20/IERC20.sol"; 5 | import {Borrower} from "../src/Borrower.sol"; 6 | import {FlashSwapLiquidate} from "../src/FlashSwapLiquidate.sol"; 7 | 8 | contract FlashSwapLiquidateTest is Test { 9 | IERC20 public DAI = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); 10 | FlashSwapLiquidate public liquidator; 11 | Borrower public borrower; 12 | 13 | function setUp() public { 14 | string memory rpc = vm.envString("MAINNET_RPC_URL"); 15 | vm.createSelectFork(rpc); 16 | 17 | borrower = new Borrower(); 18 | // The collateral factor will decrease after borrowing inside borrower.borrow(), 19 | // so that the borrower can be liquidated. 20 | // The borrower borrows USDC against DAI (Use DAI as collateral). 21 | borrower.borrow(); 22 | 23 | liquidator = new FlashSwapLiquidate(); 24 | 25 | vm.label(address(borrower), "Borrower"); 26 | vm.label(address(liquidator), "Liquidator"); 27 | } 28 | 29 | function testFlashSwapLiquidate() public { 30 | // Borrower borrowed 800k USDC 31 | // Close factor is 50% 32 | uint256 repayAmount = 400_000 * 10 ** 6; 33 | 34 | liquidator.liquidate(address(borrower), repayAmount); 35 | 36 | uint256 daiBalance = DAI.balanceOf(address(liquidator)); 37 | assertGt(daiBalance, 0); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /section3/LatenRisk/test/Solve.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.0; 3 | 4 | import "forge-std/Test.sol"; 5 | import "../src/challenge/Challenge.sol"; 6 | import "../src/Exploit.sol"; 7 | 8 | contract Solve is Test { 9 | Challenge public chall; 10 | 11 | AppworksToken public CUSD; 12 | AppworksToken public CStUSD; 13 | AppworksToken public CETH; 14 | AppworksToken public CWETH; 15 | 16 | CErc20Immutable public CCUSD; 17 | CErc20Immutable public CCStUSD; 18 | CErc20Immutable public CCETH; 19 | CErc20Immutable public CCWETH; 20 | 21 | Comptroller public comptroller; 22 | 23 | uint256 seed; 24 | address target; 25 | Deployer dd; 26 | 27 | function setUp() public { 28 | seed = 2023_12_18; 29 | target = address(uint160(seed)); 30 | dd = new Deployer(); 31 | 32 | chall = new Challenge(); 33 | chall.init(seed, address(this), address(dd)); 34 | 35 | CUSD = chall.CUSD(); 36 | CStUSD = chall.CStUSD(); 37 | CETH = chall.CETH(); 38 | CWETH = chall.CWETH(); 39 | 40 | CCUSD = chall.CCUSD(); 41 | CCStUSD = chall.CCStUSD(); 42 | CCETH = chall.CCETH(); 43 | CCWETH = chall.CCWETH(); 44 | 45 | comptroller = chall.comptroller(); 46 | } 47 | 48 | function testSolve() public { 49 | /* Solve here */ 50 | 51 | assertEq(chall.isSolved(), true); 52 | } 53 | } -------------------------------------------------------------------------------- /section2/RugPullHw/src/TradingCenter.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SEE LICENSE IN LICENSE 2 | pragma solidity ^0.8.17; 3 | 4 | // erc20 interface 5 | interface IERC20 { 6 | function totalSupply() external view returns (uint256); 7 | function balanceOf(address account) external view returns (uint256); 8 | function transfer(address recipient, uint256 amount) external returns (bool); 9 | function allowance(address owner, address spender) external view returns (uint256); 10 | function approve(address spender, uint256 amount) external returns (bool); 11 | function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); 12 | event Transfer(address indexed from, address indexed to, uint256 value); 13 | event Approval(address indexed owner, address indexed spender, uint256 value); 14 | } 15 | 16 | contract TradingCenter { 17 | 18 | bool public initialized; 19 | 20 | IERC20 public usdt; 21 | IERC20 public usdc; 22 | 23 | function initialize(IERC20 _usdt, IERC20 _usdc) public { 24 | require(initialized == false, "already initialized"); 25 | initialized = true; 26 | usdt = _usdt; 27 | usdc = _usdc; 28 | } 29 | 30 | function exchange(IERC20 token0, uint256 amount) public { 31 | require(token0 == usdt || token0 == usdc, "invalid token"); 32 | IERC20 token1 = token0 == usdt ? usdc : usdt; 33 | token0.transferFrom(msg.sender, address(this), amount); 34 | token1.transfer(msg.sender, amount); 35 | } 36 | } -------------------------------------------------------------------------------- /section1/README.md: -------------------------------------------------------------------------------- 1 | # 階段一:區塊鏈基礎知識 2 | 3 | ## 說明: 4 | 這裡整理幾個較完整的線上學習資源: 5 | 6 | - [Ethereum and Solidity: The Complete Developer's Guide](https://www.udemy.com/course/ethereum-and-solidity-the-complete-developers-guide/) 7 | - [零基礎邁向區塊鏈工程師:Solidity 智能合約](https://hahow.in/courses/5b3cdd6ed03140001eebeadc) 8 | - [CryptoZombies](https://cryptozombies.io/) 9 | 10 | #### 備註: 11 | - 區塊鏈技術演進快速,線上資源大概率會有過時的內容,但基礎概念基本上不會變,在實作細節上要特別注意。 12 | 13 |
14 | 15 | ## What you'll learn 16 | 上述資源完成後,應該要能夠清楚的回答以下問題: 17 | 1. 什麼是合約(Smart Contract)? 18 | 2. 什麼是 PoW(Proof-of-Work)? 19 | 3. 比特幣和以太坊挖礦的流程為何? 20 | 4. 以太坊交易的流程為何? 21 | 5. 公鑰&私鑰的基本加密以及簽名原理? 22 | 6. 冷錢包和熱錢包的差異? 23 | 7. 為何發起一筆交易需要礦工費(Gas Fee)? 24 | 8. 如何使用 Solidity 線上 IDE:[Remix](https://remix.ethereum.org/)? 25 | 9. 如何在以太坊上部署一個合約? 26 | 10. Solidity 語法的基礎? 27 | 11. 如何在 Etherscan 上看到自己部署的合約? 28 | 29 | ## Next steps 30 | 31 | 1. ### 學習使用 Hardhat 框架 32 | 33 | Remix 是相當適合初學者使用的 IDE,但對於撰寫自動化測試、複雜的合約應用,或是要引入 library 進行開發,便顯得過於簡陋。為了進行後續的實作練習,需要在本機架構自己的開發環境。 34 | 35 | 請參閱並學習使用 [Hardhat](https://hardhat.org/) 框架,以利後續的學習進行。特別注意如何使用 Hardhat 進行 mainnet fork / testnet fork 的功能。 36 | 37 | 2. ### 在 local 執行 [Ethernaut](https://github.com/OpenZeppelin/ethernaut) 38 | 39 | 原有的 Rinkeby, Kovan 等 testnet 將不會被過渡到 Proof of Stake 升級,預計在 Q2/Q3 2023 前會陸續關停([Ropsten, Rinkeby & Kiln Deprecation Announcement](https://blog.ethereum.org/2022/06/21/testnet-deprecation/))。 40 | 41 | 為了避免在測試網上的 Ethernaut 不穩定,因此要學習如何在本地的測試鏈部署 Ethernaut 來練習。 42 | 43 | 44 | --- 45 | [下一階段](../section2/README.md)・[回主頁](../README.md) 46 | -------------------------------------------------------------------------------- /section3/FlashLoanPractice/src/Borrower.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.19; 2 | 3 | import { CErc20 } from "compound-protocol/contracts/CErc20.sol"; 4 | import { EIP20Interface } from "compound-protocol/contracts/EIP20Interface.sol"; 5 | import { Comptroller } from "compound-protocol/contracts/Comptroller.sol"; 6 | import "forge-std/Test.sol"; 7 | 8 | contract Borrower is Test { 9 | EIP20Interface public USDC = EIP20Interface(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); 10 | address public DAI = 0x6B175474E89094C44Da98b954EedeAC495271d0F; 11 | CErc20 public cUSDC = CErc20(0x39AA39c021dfbaE8faC545936693aC917d5E7563); 12 | CErc20 public cDAI = CErc20(0x5d3a536E4D6DbD6114cc1Ead35777bAB948E3643); 13 | Comptroller public comptroller = Comptroller(0x3d9819210A31b4961b30EF54bE2aeD79B9c9Cd3B); 14 | address public admin = 0x6d903f6003cca6255D85CcA4D3B5E5146dC33925; 15 | 16 | function borrow() public { 17 | uint256 mintAmount = 1_000_000 * 10 ** 18; 18 | uint256 borrowAmount = 800_000 * 10 ** 6; 19 | 20 | deal(DAI, address(this), mintAmount); 21 | (bool success, ) = DAI.call(abi.encodeWithSignature("approve(address,uint256)", address(cDAI), mintAmount)); 22 | require(success, "Approve failed"); 23 | 24 | cDAI.mint(mintAmount); 25 | address[] memory addrs = new address[](1); 26 | addrs[0] = address(cDAI); 27 | comptroller.enterMarkets(addrs); 28 | 29 | cUSDC.borrow(borrowAmount); 30 | 31 | vm.prank(admin); 32 | uint256 err = comptroller._setCollateralFactor(cDAI, 0.7 * 1e18); 33 | require(err == 0, "Set collateral factor failed"); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /section3/LatenRisk/src/challenge/InterestRateModel.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.20; 2 | 3 | // Copyright 2020 Compound Labs, Inc. 4 | 5 | /** 6 | * @title Compound's InterestRateModel Interface 7 | * @author Compound 8 | */ 9 | abstract contract InterestRateModel { 10 | /// @notice Indicator that this is an InterestRateModel contract (for inspection) 11 | bool public constant isInterestRateModel = true; 12 | 13 | /** 14 | * @notice Calculates the current borrow interest rate per block 15 | * @param cash The total amount of cash the market has 16 | * @param borrows The total amount of borrows the market has outstanding 17 | * @param reserves The total amount of reserves the market has 18 | * @return The borrow rate per block (as a percentage, and scaled by 1e18) 19 | */ 20 | function getBorrowRate(uint256 cash, uint256 borrows, uint256 reserves) external view virtual returns (uint256); 21 | 22 | /** 23 | * @notice Calculates the current supply interest rate per block 24 | * @param cash The total amount of cash the market has 25 | * @param borrows The total amount of borrows the market has outstanding 26 | * @param reserves The total amount of reserves the market has 27 | * @param reserveFactorMantissa The current reserve factor the market has 28 | * @return The supply rate per block (as a percentage, and scaled by 1e18) 29 | */ 30 | function getSupplyRate(uint256 cash, uint256 borrows, uint256 reserves, uint256 reserveFactorMantissa) 31 | external 32 | view 33 | virtual 34 | returns (uint256); 35 | } 36 | -------------------------------------------------------------------------------- /section2/StandardProxy/test/TransparentTest.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import { Test, console } from "forge-std/Test.sol"; 5 | import { Transparent } from "../src/Transparent.sol"; 6 | import { MultiSigWallet, MultiSigWalletV2 } from "../src/MultiSigWallet/MultiSigWalletV2.sol"; 7 | 8 | interface ITransparentUpgradeableProxy { 9 | function upgradeToAndCall(address newImplementation, bytes memory data) external; 10 | } 11 | 12 | contract TransparentTest is Test { 13 | 14 | address public admin = makeAddr("admin"); 15 | address public alice = makeAddr("alice"); 16 | address public bob = makeAddr("bob"); 17 | address public carol = makeAddr("carol"); 18 | address public receiver = makeAddr("receiver"); 19 | 20 | Transparent proxy; 21 | MultiSigWallet wallet; 22 | MultiSigWalletV2 walletV2; 23 | MultiSigWallet proxyWallet; 24 | MultiSigWalletV2 proxyWalletV2; 25 | 26 | function setUp() public { 27 | vm.startPrank(admin); 28 | wallet = new MultiSigWallet(); 29 | walletV2 = new MultiSigWalletV2(); 30 | proxy = new Transparent( 31 | address(wallet), 32 | abi.encodeWithSelector(wallet.initialize.selector, [alice, bob, carol]) 33 | ); 34 | vm.stopPrank(); 35 | } 36 | 37 | function test_transparent_upgradeToAndCall_success() public { 38 | // TODO: 39 | // 1. check if proxy is correctly proxied, assert that proxyWallet.VERSION() is "0.0.1" 40 | // 2. assert if user call upgradeToAndCall will revert 41 | // 3. upgrade to V2 42 | // 4. assert user call upgradeToAndCall will return "23573451" 43 | } 44 | } -------------------------------------------------------------------------------- /section3/LatenRisk/src/challenge/CErc20Immutable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.20; 2 | 3 | import "./CErc20.sol"; 4 | 5 | // Copyright 2020 Compound Labs, Inc. 6 | 7 | /** 8 | * @title Compound's CErc20Immutable Contract 9 | * @notice CTokens which wrap an EIP-20 underlying and are immutable 10 | * @author Compound 11 | */ 12 | contract CErc20Immutable is CErc20 { 13 | /** 14 | * @notice Construct a new money market 15 | * @param underlying_ The address of the underlying asset 16 | * @param comptroller_ The address of the Comptroller 17 | * @param interestRateModel_ The address of the interest rate model 18 | * @param initialExchangeRateMantissa_ The initial exchange rate, scaled by 1e18 19 | * @param name_ ERC-20 name of this token 20 | * @param symbol_ ERC-20 symbol of this token 21 | * @param decimals_ ERC-20 decimal precision of this token 22 | * @param admin_ Address of the administrator of this token 23 | */ 24 | constructor( 25 | address underlying_, 26 | ComptrollerInterface comptroller_, 27 | InterestRateModel interestRateModel_, 28 | uint256 initialExchangeRateMantissa_, 29 | string memory name_, 30 | string memory symbol_, 31 | uint8 decimals_, 32 | address payable admin_ 33 | ) { 34 | // Creator of the contract is admin during initialization 35 | admin = payable(msg.sender); 36 | 37 | // Initialize the market 38 | initialize( 39 | underlying_, comptroller_, interestRateModel_, initialExchangeRateMantissa_, name_, symbol_, decimals_ 40 | ); 41 | 42 | // Set the proper admin now that initialization is done 43 | admin = admin_; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /section2/whitelist/test/ERC721Whitelist.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SEE LICENSE IN LICENSE 2 | pragma solidity ^0.8.17; 3 | 4 | import { Test } from "forge-std/Test.sol"; 5 | import { ERC721Whitelist } from "../src/ERC721Whitelist.sol"; 6 | import { Merkle } from "murky/src/Merkle.sol"; 7 | import "forge-std/console.sol"; 8 | 9 | contract ERC721WhitelistTest is Test { 10 | 11 | ERC721Whitelist public whitelist; 12 | Account public user1 = makeAccount("leaf1"); 13 | Account public user2 = makeAccount("leaf2"); 14 | Account public user3 = makeAccount("leaf3"); 15 | Account public user4 = makeAccount("leaf4"); 16 | Merkle m = new Merkle(); 17 | 18 | bytes32[] public leaf; 19 | 20 | function setUp() public { 21 | leaf = new bytes32[](4); 22 | leaf[0] = keccak256(abi.encodePacked(user1.addr)); 23 | leaf[1] = keccak256(abi.encodePacked(user2.addr)); 24 | leaf[2] = keccak256(abi.encodePacked(user3.addr)); 25 | leaf[3] = keccak256(abi.encodePacked(user4.addr)); 26 | 27 | bytes32 root = m.getRoot(leaf); 28 | 29 | whitelist = new ERC721Whitelist("Whitelist", "WHT", root); 30 | } 31 | 32 | function test_mint() public { 33 | vm.startPrank(user1.addr); 34 | uint256 indexInLeaf = 0; 35 | bytes32[] memory proof = m.getProof(leaf, indexInLeaf); 36 | whitelist.mint(proof); 37 | vm.stopPrank(); 38 | } 39 | 40 | function test_mint_fail_with_wrong_index() public { 41 | // TODO: 42 | // 1. Prank user1 43 | // 2. Mint to user1 with wrong index, should revert with "ERC721Whitelist: Invalid proof." 44 | } 45 | 46 | function test_mint_fail_when_duplicate() public { 47 | // TODO: 48 | // 1. Prank user1 49 | // 2. Mint to user1 50 | // 3. Mint to user1 again, should revert with "ERC721Whitelist: Already claimed." 51 | } 52 | } -------------------------------------------------------------------------------- /section2/StandardProxy/test/BeaconProxyTest.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import { Test, console } from "forge-std/Test.sol"; 5 | import { BeaconProxy } from "../src/BeaconProxy/BeaconProxy.sol"; 6 | import { UpgradeableBeacon } from "../src/BeaconProxy/UpgradeableBeacon.sol"; 7 | import { UpgradeableProxy } from "../src/UpgradeableProxy.sol"; 8 | import { MultiSigWallet, MultiSigWalletV2 } from "../src/MultiSigWallet/MultiSigWalletV2.sol"; 9 | 10 | contract BeaconProxyTest is Test { 11 | 12 | address public admin = makeAddr("admin"); 13 | address public alice = makeAddr("alice"); 14 | address public bob = makeAddr("bob"); 15 | address public carol = makeAddr("carol"); 16 | address public receiver = makeAddr("receiver"); 17 | 18 | BeaconProxy proxy1; 19 | BeaconProxy proxy2; 20 | BeaconProxy proxy3; 21 | UpgradeableBeacon beacon; 22 | 23 | MultiSigWallet wallet; 24 | MultiSigWalletV2 walletV2; 25 | MultiSigWallet proxyWallet; 26 | MultiSigWalletV2 proxyWalletV2; 27 | 28 | function setUp() public { 29 | vm.startPrank(admin); 30 | 31 | wallet = new MultiSigWallet(); 32 | walletV2 = new MultiSigWalletV2(); 33 | beacon = new UpgradeableBeacon(address(wallet)); 34 | proxy1 = new BeaconProxy(address(beacon)); 35 | proxy2 = new BeaconProxy(address(beacon)); 36 | proxy3 = new BeaconProxy(address(beacon)); 37 | 38 | vm.stopPrank(); 39 | } 40 | 41 | function test_BeaconProxy_upgradeTo() public { 42 | // TODO: 43 | // 1. check if proxy is correctly proxied, assert that proxyWallet.VERSION() is "0.0.1", both with proxy1, proxy2, proxy3 44 | // 2. upgrade beacon implementation 45 | // 3. check if proxy is correctly proxied, assert that proxyWallet.VERSION() is "0.0.2", both with proxy1, proxy2, proxy3 46 | } 47 | } -------------------------------------------------------------------------------- /section3/FlashLoanPractice/README.md: -------------------------------------------------------------------------------- 1 | # Flash Loan Practice 2 | 3 | Welcome to the Flash Loan Practice repository! The main purpose of this repository is to provide a platform for everyone to practice using flash loan and gain familiarity with its operational principles. 4 | 5 | ## Exercise 1 6 | 7 | In this exercise, you will practice how to use flash loans in Aave v3. You need to implement `src/AaveFlashLoan.sol` to pass the tests in `test/AaveFlashLoan.t.sol`. In order to pass the test, you need to borrow enough USDC in the contract, and call the `checkBalance` function in the `BalanceChecker` contract. 8 | 9 | ## Exercise 2 10 | In this exercise, you will learn how to use flash swaps in Uniswap v2 to liquidate positions in Compound v2. You need to implement `src/FlashSwapLiquidate.sol` to pass the tests in `test/FlashSwapLiquidate.t.sol`. In order to pass the test, you need to borrow money through Uniswap v2, liquidate a specified address's USDC lending position in Compound v2, and realize a profit in Dai tokens (the balance of Dai should finally be greater than 0). 11 | 12 | ## Environment Setup 13 | 14 | To get started with the Compound Practice repository, follow the steps below: 15 | 16 | 1. Clone the repository: 17 | 18 | ```shell 19 | git clone git@github.com:AppWorks-School/Blockchain-Resource.git 20 | ``` 21 | 22 | 2. Navigate to the Compound Practice directory: 23 | 24 | ```shell 25 | cd Blockchain-Resource/section3/FlashLoanPractice 26 | ``` 27 | 28 | 3. Install the necessary dependencies: 29 | 30 | ```shell 31 | forge install 32 | ``` 33 | 34 | 4. Build the project: 35 | 36 | ```shell 37 | forge build 38 | ``` 39 | 40 | 5. Run the tests: 41 | 42 | ```shell 43 | forge test 44 | ``` 45 | 46 | Feel free to explore the code and dive into the exercises provided to enhance your understanding of flash loan. Happy practicing! 47 | -------------------------------------------------------------------------------- /section3/FlashSwapPractice/test/FlashSwapPractice.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | import "forge-std/Test.sol"; 5 | import { FlashSwapSetUp } from "./helper/FlashSwapSetUp.sol"; 6 | import { FakeLendingProtocol } from "../contracts/FakeLendingProtocol.sol"; 7 | import { Liquidator } from "../contracts/Liquidator.sol"; 8 | 9 | contract FlashSwapPracticeTest is FlashSwapSetUp { 10 | FakeLendingProtocol fakeLendingProtocol; 11 | Liquidator liquidator; 12 | 13 | address maker = makeAddr("Maker"); 14 | 15 | function setUp() public override { 16 | super.setUp(); 17 | 18 | // mint 100 ETH, 10000 USDC to maker 19 | vm.deal(maker, 100 ether); 20 | usdc.mint(maker, 10_000 * 10 ** usdc.decimals()); 21 | 22 | // maker provide 100 ETH, 10000 USDC to wethUsdcPool 23 | vm.startPrank(maker); 24 | usdc.approve(address(uniswapV2Router), 10_000 * 10 ** usdc.decimals()); 25 | uniswapV2Router.addLiquidityETH{ value: 100 ether }( 26 | address(usdc), 27 | 10_000 * 10 ** usdc.decimals(), 28 | 0, 29 | 0, 30 | maker, 31 | block.timestamp 32 | ); 33 | vm.stopPrank(); 34 | 35 | // deploy fake lending protocol 36 | fakeLendingProtocol = new FakeLendingProtocol{ value: 1 ether }(address(usdc)); 37 | 38 | // deploy liquidator 39 | liquidator = new Liquidator(address(fakeLendingProtocol), address(uniswapV2Router), address(uniswapV2Factory)); 40 | } 41 | 42 | function test_flash_swap() public { 43 | address[] memory path = new address[](2); 44 | path[0] = address(weth); 45 | path[1] = address(usdc); 46 | 47 | liquidator.liquidate(path, 80 * 10 ** usdc.decimals()); 48 | 49 | uint256 profit = address(liquidator).balance; 50 | assertEq(profit, 191121752353835700); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /section2/StandardProxy/test/UUPSTest.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import { Test, console } from "forge-std/Test.sol"; 5 | import { UUPSProxy } from "../src/UUPSProxy.sol"; 6 | import { UUPSMultiSigWallet } from "../src/UUPS/UUPSMultiSigWallet.sol"; 7 | import { UUPSMultiSigWalletV2 } from "../src/UUPS/UUPSMultiSigWalletV2.sol"; 8 | import { MultiSigWallet, MultiSigWalletV2 } from "../src/MultiSigWallet/MultiSigWalletV2.sol"; 9 | 10 | contract NoProxiableContract {} 11 | 12 | contract UUPSTest is Test { 13 | 14 | address public admin = makeAddr("admin"); 15 | address public alice = makeAddr("alice"); 16 | address public bob = makeAddr("bob"); 17 | address public carol = makeAddr("carol"); 18 | address public receiver = makeAddr("receiver"); 19 | 20 | UUPSProxy proxy; 21 | UUPSMultiSigWallet wallet; 22 | UUPSMultiSigWalletV2 walletV2; 23 | UUPSMultiSigWallet proxyWallet; 24 | UUPSMultiSigWalletV2 proxyWalletV2; 25 | 26 | function setUp() public { 27 | vm.startPrank(admin); 28 | wallet = new UUPSMultiSigWallet(); 29 | walletV2 = new UUPSMultiSigWalletV2(); 30 | proxy = new UUPSProxy( 31 | abi.encodeWithSelector(wallet.initialize.selector, [alice, bob, carol]), 32 | address(wallet) 33 | ); 34 | vm.stopPrank(); 35 | } 36 | 37 | function test_UUPS_updateCodeAddress_success() public { 38 | // TODO: 39 | // 1. check if proxy is correctly proxied, assert that proxyWallet.VERSION() is "0.0.1" 40 | // 2. upgrade to UUPSMultiSigWalletV2 by calling updateCodeAddress 41 | // 3. assert that proxyWallet.VERSION() is "0.0.2" 42 | // 4. assert updateCodeAddress is gone by calling updateCodeAddress with low-level call or UUPSMutliSigWallet 43 | } 44 | 45 | function test_UUPS_updateCodeAddress_revert_if_no_proxiableUUID() public { 46 | // TODO: 47 | // 1. deploy NoProxiableContract 48 | // 2. upgrade to NoProxiableContract by calling updateCodeAddress, which should revert 49 | } 50 | } -------------------------------------------------------------------------------- /section2/StandardProxy/test/SlotsManipulateTest.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SEE LICENSE IN LICENSE 2 | pragma solidity ^0.8.17; 3 | 4 | import { Test, stdStorage, StdStorage } from "forge-std/Test.sol"; 5 | import { SlotsManipulate } from "../src/utils/Slots.sol"; 6 | 7 | contract Sample { 8 | address private _var; 9 | constructor(address var_) { 10 | _var = var_; 11 | } 12 | } 13 | 14 | contract SlotManipulateTest is Test { 15 | 16 | using stdStorage for StdStorage; 17 | 18 | address randomAddress; 19 | SlotsManipulate instance; 20 | 21 | function setUp() public { 22 | instance = new SlotsManipulate(); 23 | } 24 | 25 | function bytes32ToAddress(bytes32 _bytes32) internal pure returns (address) { 26 | return address(uint160(uint256(_bytes32))); 27 | } 28 | 29 | function test_Vm_Load() public { 30 | // test vm.load 31 | Sample sample = new Sample(randomAddress); 32 | address firstSlot = bytes32ToAddress(vm.load(address(sample), bytes32(0))); 33 | assertEq(firstSlot, randomAddress); 34 | } 35 | 36 | function test_value_set() public { 37 | // TODO: 38 | // 1. set bytes32(keccak256("appwork.week8")) to 2023_4_27 39 | 40 | // 2. Assert that the value is set 41 | // assertEq( 42 | // uint256(vm.load(address(instance), keccak256("appworks.week8"))), 43 | // 2023_10_26 44 | // ); 45 | } 46 | 47 | function test_set_Proxy_Implementation() public { 48 | // TODO: 49 | // 1. set Proxy Implementation address 50 | // 2. assert that value is set 51 | } 52 | 53 | function test_set_Beacon_Implementation() public { 54 | // TODO: 55 | // 1. set Beacon Implementation address 56 | // 2. assert that value is set 57 | } 58 | 59 | function test_set_Admin_Implementation() public { 60 | // TODO: 61 | // 1. set admin address 62 | // 2. assert that value is set 63 | } 64 | 65 | function test_set_Proxiable_Implementation() public { 66 | // TODO: 67 | // 1. set Proxiable address 68 | // 2. assert that value is set 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /section3/SimpleSwap/test/foundry/helper/SimpleSwapSetUp.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SEE LICENSE IN LICENSE 2 | pragma solidity 0.8.17; 3 | 4 | import { Test } from "forge-std/Test.sol"; 5 | import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 6 | import { TestERC20 } from "../../../contracts/test/TestERC20.sol"; 7 | import { SimpleSwap } from "../../../contracts/SimpleSwap.sol"; 8 | import { ISimpleSwapEvent } from "../../../contracts/interface/ISimpleSwap.sol"; 9 | 10 | contract SimpleSwapSetUp is Test, ISimpleSwapEvent { 11 | address public taker = makeAddr("taker"); 12 | address public maker = makeAddr("maker"); 13 | 14 | TestERC20 public tokenA; 15 | TestERC20 public tokenB; 16 | uint256 public tokenADecimals; 17 | uint256 public tokenBDecimals; 18 | uint256 public slpDecimals; 19 | 20 | SimpleSwap public simpleSwap; 21 | 22 | function setUp() public virtual { 23 | tokenB = new TestERC20("token B", "TKB"); 24 | tokenA = new TestERC20("token A", "TKA"); 25 | 26 | tokenADecimals = tokenA.decimals(); 27 | tokenBDecimals = tokenB.decimals(); 28 | simpleSwap = new SimpleSwap(address(tokenA), address(tokenB)); 29 | slpDecimals = simpleSwap.decimals(); 30 | 31 | tokenA.mint(taker, 1000 * 10 ** tokenADecimals); 32 | tokenA.mint(maker, 1000 * 10 ** tokenADecimals); 33 | tokenB.mint(taker, 1000 * 10 ** tokenBDecimals); 34 | tokenB.mint(maker, 1000 * 10 ** tokenBDecimals); 35 | 36 | vm.startPrank(taker); 37 | tokenA.approve(address(simpleSwap), 1000 * 10 ** tokenADecimals); 38 | tokenB.approve(address(simpleSwap), 1000 * 10 ** tokenADecimals); 39 | vm.stopPrank(); 40 | 41 | vm.startPrank(maker); 42 | tokenA.approve(address(simpleSwap), 1000 * 10 ** tokenADecimals); 43 | tokenB.approve(address(simpleSwap), 1000 * 10 ** tokenADecimals); 44 | vm.stopPrank(); 45 | 46 | vm.label(address(tokenA), "tokenA"); 47 | vm.label(address(tokenB), "tokenB"); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /section2/ProxyPattern/test/BasicProxy.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import { Test } from "forge-std/Test.sol"; 5 | import { testERC20 } from "../src/test/testERC20.sol"; 6 | import { testERC721 } from "../src/test/testERC721.sol"; 7 | import { MultiSigWallet } from "../src/MultiSigWallet/MultiSigWallet.sol"; 8 | import { BasicProxy } from "../src/BasicProxy.sol"; 9 | 10 | contract BasicProxyTest is Test { 11 | 12 | address public admin = makeAddr("admin"); 13 | address public alice = makeAddr("alice"); 14 | address public bob = makeAddr("bob"); 15 | address public carol = makeAddr("carol"); 16 | address public receiver = makeAddr("receiver"); 17 | 18 | MultiSigWallet public wallet; 19 | BasicProxy public proxy; 20 | MultiSigWallet public proxyWallet; 21 | 22 | testERC20 public erc20; 23 | testERC721 public erc721; 24 | 25 | function setUp() public { 26 | // vm.startPrank(admin); 27 | // wallet = new MultiSigWallet([alice, bob, carol]); 28 | 29 | // 1. deploy proxy contract, which implementation should points at wallet's address 30 | 31 | // 2. proxyWallet is a pointer that treats proxy contract as MultiSigWallet 32 | 33 | // vm.deal(proxy, 100 ether); 34 | // vm.stopPrank(); 35 | } 36 | 37 | function test_updateOwner() public { 38 | // 1. try to update Owner 39 | 40 | // 2. check the owner1 is alice, owner2 is bob and owner3 is carol 41 | } 42 | 43 | function test_submit_tx() public { 44 | // 1. prank as one of the owner 45 | 46 | // 2. submit a transaction that transfer 10 ether to bob 47 | 48 | // Does it success? Why? 49 | } 50 | 51 | function test_call_initialize_and_check() public { 52 | // 1. call initialize function 53 | 54 | // 2. check the owner1, owner2, owner3 is initialized 55 | } 56 | 57 | function test_call_initialize_and_submit_tx() public { 58 | 59 | // 1. call initialize function 60 | 61 | // 2. submit a transaction that transfer 10 ether to bob 62 | 63 | // 3. check the transaction is submitted 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /section2/StandardProxy/src/Transparent.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SEE LICENSE IN LICENSE 2 | pragma solidity ^0.8.17; 3 | 4 | import { Slots } from "./utils/Slots.sol"; 5 | import { Proxy } from "./utils/Proxy.sol"; 6 | import { console } from "forge-std/console.sol"; 7 | 8 | interface ITransparentUpgradeableProxy { 9 | function upgradeToAndCall(address newImplementation, bytes memory data) external; 10 | } 11 | 12 | contract Transparent is Slots, Proxy { 13 | 14 | bytes32 constant IMPLEMENTATION_SLOT = bytes32(uint256(keccak256("eip1967.proxy.implementation")) - 1); 15 | bytes32 constant ADMIN_SLOT = bytes32(uint256(keccak256("eip1967.proxy.admin")) - 1); 16 | 17 | constructor(address _implementation, bytes memory data) { 18 | // TODO: 19 | // 1. set the implementation address at bytes32(uint256(keccak256('eip1967.proxy.implementation')) - 1) 20 | // 2. set admin owner address at bytes32(uint256(keccak256('eip1967.proxy.admin')) - 1) 21 | // 3. if data exist, then initialize proxy with _data 22 | } 23 | 24 | function _getAdmin() internal view returns (address) { 25 | // TODO: return the admin owner address 26 | } 27 | 28 | function _upgradeToAndCall(address newImplementation, bytes memory data) internal { 29 | _setSlotToAddress(IMPLEMENTATION_SLOT, newImplementation); 30 | if (data.length > 0) { 31 | (bool success, ) = newImplementation.delegatecall(data); 32 | require(success, "Transparent: upgradeToAndCall failed"); 33 | } 34 | } 35 | 36 | fallback() external payable { 37 | // TODO: 38 | // 1. check if msg.sender is equal to admin owner address, if no then delegatecall to implementation address 39 | // 2. if yes, then check if function selector is equal to upgradeToAndCall, if no then revert with Message "Transparent: admin could only upgradeAndCall" 40 | // 3. if yes, upgrade the implementation address and initialize proxy with data 41 | } 42 | 43 | receive() external payable {} 44 | 45 | function implementation() internal view returns (address impl) { 46 | return _getSlotToAddress(IMPLEMENTATION_SLOT); 47 | } 48 | } -------------------------------------------------------------------------------- /section2/ProxyPattern/test/MultiSigWalletV2.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import { Test } from "forge-std/Test.sol"; 5 | import { testERC20 } from "../src/test/testERC20.sol"; 6 | import { testERC721 } from "../src/test/testERC721.sol"; 7 | import { MultiSigWalletV2, MultiSigWallet } from "../src/MultiSigWallet/MultiSigWalletV2.sol"; 8 | import { UpgradeableProxy } from "../src/UpgradeableProxy.sol"; 9 | 10 | contract MultiSigWalletV2Test is Test { 11 | 12 | address public admin = makeAddr("admin"); 13 | address public alice = makeAddr("alice"); 14 | address public bob = makeAddr("bob"); 15 | address public carol = makeAddr("carol"); 16 | address public receiver = makeAddr("receiver"); 17 | 18 | MultiSigWallet public wallet; 19 | MultiSigWalletV2 public walletV2; 20 | 21 | testERC20 public erc20; 22 | testERC721 public erc721; 23 | 24 | UpgradeableProxy public proxy; 25 | MultiSigWallet public proxyWallet; 26 | MultiSigWalletV2 public proxyWalletV2; 27 | 28 | function setUp() public { 29 | // vm.startPrank(admin); 30 | // wallet = new MultiSigWallet(); 31 | // walletV2 = new MultiSigWalletV2(); 32 | 33 | // proxy = new UpgradeableProxy(address(wallet)); 34 | // proxyWallet = MultiSigWallet(address(proxy)); 35 | // vm.deal(address(proxy), 100 ether); 36 | 37 | // proxyWallet.initialize([alice, bob, carol]); 38 | // vm.stopPrank(); 39 | } 40 | 41 | function test_upgrade_can_upgrade() public { 42 | // 1. assert wallet is initialized and point to correct implementation 43 | 44 | // 2. test proxyWallet's function works 45 | 46 | // 3. upgrade to V2 47 | 48 | // 4. assert wallet is upgraded 49 | 50 | // 5. assert cancel function is added 51 | } 52 | 53 | function test_upgradeAndCall_can_upgrade() public { 54 | // 1. assert wallet is initialized and point to correct implementation 55 | 56 | // 2. test proxyWallet's function works 57 | 58 | // 3. upgrade to V2 59 | 60 | // 4. assert wallet is upgraded 61 | 62 | // 5. assert cancel function is added 63 | } 64 | 65 | } -------------------------------------------------------------------------------- /section3/SimpleSwap/test/foundry/SimpleSwap.constructor.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SEE LICENSE IN LICENSE 2 | pragma solidity 0.8.17; 3 | 4 | import { Test } from "forge-std/Test.sol"; 5 | import { SimpleSwap } from "../../contracts/SimpleSwap.sol"; 6 | import { TestERC20 } from "../../contracts/test/TestERC20.sol"; 7 | 8 | contract SimpleSwapConstructorTest is Test { 9 | TestERC20 public tokenA; 10 | TestERC20 public tokenB; 11 | SimpleSwap public simpleSwap; 12 | 13 | address public alice = makeAddr("alice"); 14 | 15 | function setUp() public { 16 | tokenA = new TestERC20("token A", "TKA"); 17 | tokenB = new TestERC20("token B", "TKB"); 18 | } 19 | 20 | function test_revert_constructor_tokenA_is_not_a_contract() public { 21 | vm.expectRevert("SimpleSwap: TOKENA_IS_NOT_CONTRACT"); 22 | simpleSwap = new SimpleSwap(alice, address(tokenB)); 23 | } 24 | 25 | function test_revert_constructor_tokenB_is_not_a_contract() public { 26 | vm.expectRevert("SimpleSwap: TOKENB_IS_NOT_CONTRACT"); 27 | simpleSwap = new SimpleSwap(address(tokenA), alice); 28 | } 29 | 30 | function test_revert_constructor_tokenA_tokenB_identical() public { 31 | vm.expectRevert("SimpleSwap: TOKENA_TOKENB_IDENTICAL_ADDRESS"); 32 | simpleSwap = new SimpleSwap(address(tokenA), address(tokenA)); 33 | } 34 | 35 | function test_constructor_reserve_should_be_zero_after_initialize() public { 36 | simpleSwap = new SimpleSwap(address(tokenA), address(tokenB)); 37 | uint256 reserve1; 38 | uint256 reserve2; 39 | 40 | (reserve1, reserve2) = simpleSwap.getReserves(); 41 | assertEq(reserve1, 0); 42 | assertEq(reserve2, 0); 43 | } 44 | 45 | function test_constructor_tokenA_should_be_less_than_tokenB() public { 46 | simpleSwap = new SimpleSwap(address(tokenA), address(tokenB)); 47 | address swapTokenA = simpleSwap.getTokenA(); 48 | address swapTokenB = simpleSwap.getTokenB(); 49 | assertLe(uint256(uint160(swapTokenA)), uint256(uint160(swapTokenB))); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /section2/StandardProxy/test/ERC1967ProxyTest.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | import { Test, console } from "forge-std/Test.sol"; 5 | import { ERC1967Proxy } from "../src/ERC1967Proxy.sol"; 6 | import { UpgradeableProxy } from "../src/UpgradeableProxy.sol"; 7 | import { MultiSigWallet, MultiSigWalletV2 } from "../src/MultiSigWallet/MultiSigWalletV2.sol"; 8 | 9 | contract ERC1967ProxyTest is Test { 10 | 11 | address public admin = makeAddr("admin"); 12 | address public alice = makeAddr("alice"); 13 | address public bob = makeAddr("bob"); 14 | address public carol = makeAddr("carol"); 15 | address public receiver = makeAddr("receiver"); 16 | 17 | ERC1967Proxy proxy; 18 | MultiSigWallet wallet; 19 | MultiSigWalletV2 walletV2; 20 | MultiSigWallet proxyWallet; 21 | MultiSigWalletV2 proxyWalletV2; 22 | 23 | function setUp() public { 24 | vm.startPrank(admin); 25 | wallet = new MultiSigWallet(); 26 | walletV2 = new MultiSigWalletV2(); 27 | proxy = new ERC1967Proxy( 28 | address(wallet), 29 | abi.encodeWithSelector(wallet.initialize.selector, [alice, bob, carol]) 30 | ); 31 | vm.stopPrank(); 32 | } 33 | 34 | function test_storage_collision_on_upgradeProxy() public { 35 | UpgradeableProxy upgradeProxy; 36 | upgradeProxy = new UpgradeableProxy( 37 | address(wallet), 38 | abi.encodeWithSelector(wallet.initialize.selector, [alice, bob, carol]) 39 | ); 40 | vm.expectRevert(); 41 | MultiSigWallet(address(upgradeProxy)).VERSION(); 42 | } 43 | 44 | function test_ERC1967_avoid_storage_collision() public { 45 | // TODO: 46 | // 1. check if proxy is correctly proxied, assert that proxyWallet.VERSION() is "0.0.1" 47 | // 2. test upgradeToAndCall won't result in storage collision 48 | // 3. assert contract is updated 49 | } 50 | 51 | function test_ERC1967_onlyAdmin_can_upgrade() public { 52 | // TODO: test if only admin could upgrade this proxy 53 | } 54 | 55 | function test_call_upgradeToAndCall_23573451() public { 56 | // TODO: 57 | // 1. upgrade to V2 and initiliaze 58 | // 2. test if you could call upgradeToAndCall_23573451() 59 | } 60 | } -------------------------------------------------------------------------------- /section3/AMM.md: -------------------------------------------------------------------------------- 1 | # 自動化做市商(Automated Market Maker, AMM) 2 | 3 | 4 | 請先理解什麼是 AMM([Uniswap V2](https://docs.uniswap.org/protocol/V2/introduction)),並依照下面順序實作一個簡單的 AMM 5 | 1. 實作一個 AMM 合約,繼承 ERC20 並實作一個 `init(uint256 _amount)`,會收取用戶 _amount 這麼多金額的 USDC 和 USDT,並發給用戶 1e18 數量的 ERC20 token(Liquidity Provider Token, LP token) 6 | 7 | 2. 實作一個 `swap(address _tokenIn, address _tokenOut, uint256 _amount)` 把 USDC/USDT 互換,互換比例(價格)隨意 8 | 9 | 3. 實作一個 `provideLiquidity(addrerss _tokenIn, uint256 _amount)` 可以把 USDC 或 USDT 存進去 10 | 11 | 4. 實作一個 `_calculateSwapOutAmount(address _tokenIn, address _tokenOut, uint256 _amountIn) returns (uint256 amountOut)`,並把他使用在 swap 內,並滿足兩個條件 12 | (1) _amountIn 越高, amountOut 越高,但 _amountIn / amountOut 比值(價格)要越高,例如 10 USDC 可以換 10 USDT,但 20 USDC 只能換 18 USDT 13 | (2) 合約內 _tokenIn / _tokenOut 比值越高,_amountIn / amountOut 比值(價格)越高 14 | 15 | 5. 把 provideLiquidity 改成同時提供兩個幣種,`provideLiquidity(addrerss _tokenA, address _tokenB, uint256 _amountA, uint256 _amountB)` 且合約只會拿走比值跟合約內 token 比例一樣的數字,例如合約內有 100 USDC + 200 USDT, _tokenA = USDC, _tokenB = USDT, _amountA = 100, _amountB = 100,則合約只應該拿走 50 USDC + 100 USDT 16 | 17 | 6. 讓 provideLiquidity 會發送 ERC20 LP token 給用戶,且 `發送的數量 / LP total supply` = `提供流動性的數量 / 合約內的 token 數量`,例如以第五題的例子,原本 LP total supply = 1e18,那應該發給用戶 5e17 18 | 19 | 7. 實作 `removeLiquidity(uint256 _amount)` 讓合約收回 LP token,並按比例把 USDC/USDT 還給用戶 20 | 21 | 進階題: 22 | - 在實作的 AMM 中加上手續費的機制,對於每一筆換幣交易收取 0.3% 的手續費(使用 X 代幣購買 Y 代幣時收取 0.3% 的 X 代幣), 23 | 並確保該交易對中的流動性提供者可以根據提供流動性的比例均分手續費 24 | 25 | - 學習 [Uniswap V3](https://docs.uniswap.org/protocol/introduction) 26 | 27 | - 學習 [Curve](https://curve.readthedocs.io/) 28 | 29 | ## 參考資料 30 | - [What Is an Automated Market Maker?](https://www.coindesk.com/learn/2021/08/20/what-is-an-automated-market-maker/) 31 | 32 | - [Uniswap v2 實作 : 從創建交易對到Ether 換 Dai 投入 Compound](https://medium.com/taipei-ethereum-meetup/uniswap-v2-implementation-and-combination-with-compound-262ff338efa) 33 | 34 | - [淺談無常損失 (Impermanent Loss) 及其避險方式](https://medium.com/@cic.ethan/%E6%B7%BA%E8%AB%87%E7%84%A1%E5%B8%B8%E6%90%8D%E5%A4%B1-impermanent-loss-%E5%8F%8A%E5%85%B6%E9%81%BF%E9%9A%AA%E6%96%B9%E5%BC%8F-2ec23978b767) 35 | 36 | - [Uniswap v3 详解](https://liaoph.com/uniswap-v3-1/) 37 | 38 | 39 | --- 40 | [回階段三](./README.md) 41 | -------------------------------------------------------------------------------- /section3/CompoundPractice/test/CompoundPractice.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.19; 3 | 4 | import { EIP20Interface } from "compound-protocol/contracts/EIP20Interface.sol"; 5 | import { CErc20 } from "compound-protocol/contracts/CErc20.sol"; 6 | import "test/helper/CompoundPracticeSetUp.sol"; 7 | 8 | interface IBorrower { 9 | function borrow() external; 10 | } 11 | 12 | contract CompoundPracticeTest is CompoundPracticeSetUp { 13 | EIP20Interface public USDC = EIP20Interface(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); 14 | CErc20 public cUSDC = CErc20(0x39AA39c021dfbaE8faC545936693aC917d5E7563); 15 | address public user; 16 | 17 | IBorrower public borrower; 18 | 19 | function setUp() public override { 20 | super.setUp(); 21 | 22 | // Deployed in CompoundPracticeSetUp helper 23 | borrower = IBorrower(borrowerAddress); 24 | vm.makePersistent(address(borrower)); 25 | 26 | user = makeAddr("User"); 27 | 28 | uint256 initialBalance = 10000 * 10 ** USDC.decimals(); 29 | deal(address(USDC), user, initialBalance); 30 | 31 | vm.label(address(cUSDC), "cUSDC"); 32 | vm.label(borrowerAddress, "Borrower"); 33 | } 34 | 35 | function test_compound_mint_interest() public { 36 | vm.startPrank(user); 37 | // TODO: 1. Mint some cUSDC with USDC 38 | 39 | // TODO: 2. Modify block state to generate interest 40 | 41 | // TODO: 3. Redeem and check the redeemed amount 42 | } 43 | 44 | function test_compound_mint_interest_with_borrower() public { 45 | vm.startPrank(user); 46 | // TODO: 1. Mint some cUSDC with USDC 47 | 48 | // 2. Borrower contract will borrow some USDC 49 | borrower.borrow(); 50 | 51 | // TODO: 3. Modify block state to generate interest 52 | 53 | 54 | // TODO: 4. Redeem and check the redeemed amount 55 | } 56 | 57 | function test_compound_mint_interest_with_borrower_advanced() public { 58 | vm.startPrank(user); 59 | // TODO: 1. Mint some cUSDC with USDC 60 | 61 | 62 | address anotherBorrower = makeAddr("Another Borrower"); 63 | // TODO: 2. Borrow some USDC with another borrower 64 | // vm.startPrank(anotherBorrower); 65 | 66 | // TODO: 3. Modify block state to generate interest 67 | 68 | 69 | // TODO: 4. Redeem and check the redeemed amount 70 | // vm.startPrank(user); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /section3/CompoundGovernancePractice/script/Governance.s.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.20; 2 | 3 | import "forge-std/Script.sol"; 4 | import { Unitroller } from "compound-protocol/contracts/Unitroller.sol"; 5 | import { Comptroller } from "compound-protocol/contracts/Comptroller.sol"; 6 | import { SimplePriceOracle } from "compound-protocol/contracts/SimplePriceOracle.sol"; 7 | import { WhitePaperInterestRateModel } from "compound-protocol/contracts/WhitePaperInterestRateModel.sol"; 8 | import { CErc20Delegate } from "compound-protocol/contracts/CErc20Delegate.sol"; 9 | import { CErc20Delegator } from "compound-protocol/contracts/CErc20Delegator.sol"; 10 | import { UnitrollerAdminStorage } from "compound-protocol/contracts/ComptrollerStorage.sol"; 11 | import { GovernorBravoDelegator } from "compound-protocol/contracts/Governance/GovernorBravoDelegator.sol"; 12 | import { GovernorBravoDelegateStorageV2 } from "compound-protocol/contracts/Governance/GovernorBravoInterfaces.sol"; 13 | import { CToken } from "compound-protocol/contracts/CToken.sol"; 14 | import { Comp } from "compound-protocol/contracts/Governance/Comp.sol"; 15 | import { GovernorBravoDelegate } from "src/GovernorBravoDelegate.sol"; 16 | import { Timelock } from "src/Timelock.sol"; 17 | 18 | contract GovernanceScript is Script { 19 | Unitroller constant public unitroller = Unitroller(payable(0x63d005EA741704dDA3d74Cb54a5bf6F3b1Dc86DB)); 20 | CErc20Delegator constant public cToken = CErc20Delegator(payable(0xdc25E4DDd051De774566ACC5a7442284e659FeeC)); 21 | GovernorBravoDelegator constant public bravo = GovernorBravoDelegator(payable(0x561adf66bEf90969783d6E6D118e16Fd6856F862)); 22 | Timelock constant public timelock = Timelock(payable(0x93C485BC5F028C36dFf0B9add0dCAfb080cb7dd7)); 23 | Comp constant public comp = Comp(payable(0x8dCb0C9a616bEdcf70eB826BA8Cfc8a11b420EE7)); 24 | 25 | function delegateVotingPower() public { 26 | // TODO: Distribute Comp into two addresses, delegate one address to yourself, 27 | // and delegate the other address to your team member. 28 | 29 | } 30 | 31 | function propose() public { 32 | // TODO: Submit a proposal, remember that your address requires 200 COMP of voting power. 33 | 34 | } 35 | 36 | function vote() public { 37 | // TODO: Vote for proposals that you prefer. 38 | 39 | } 40 | 41 | function queueProposal() public { 42 | // TODO: Send the approved proposal to the timelock. 43 | 44 | } 45 | 46 | function executeProposal() public { 47 | // TODO: Execute the proposal in the timelock. 48 | 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /section3/FlashSwapPractice/contracts/Liquidator.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; 5 | import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 6 | import { IUniswapV2Pair } from "v2-core/interfaces/IUniswapV2Pair.sol"; 7 | import { IUniswapV2Callee } from "v2-core/interfaces/IUniswapV2Callee.sol"; 8 | import { IUniswapV2Factory } from "v2-core/interfaces/IUniswapV2Factory.sol"; 9 | import { IUniswapV2Router01 } from "v2-periphery/interfaces/IUniswapV2Router01.sol"; 10 | import { IWETH } from "v2-periphery/interfaces/IWETH.sol"; 11 | import { IFakeLendingProtocol } from "./interfaces/IFakeLendingProtocol.sol"; 12 | 13 | // This is liquidator contract for testing, 14 | // all you need to implement is flash swap from uniswap pool and call lending protocol liquidate function in uniswapV2Call 15 | // lending protocol liquidate rule can be found in FakeLendingProtocol.sol 16 | contract Liquidator is IUniswapV2Callee, Ownable { 17 | address internal immutable _FAKE_LENDING_PROTOCOL; 18 | address internal immutable _UNISWAP_ROUTER; 19 | address internal immutable _UNISWAP_FACTORY; 20 | address internal immutable _WETH9; 21 | uint256 internal constant _MINIMUM_PROFIT = 0.01 ether; 22 | 23 | constructor(address lendingProtocol, address uniswapRouter, address uniswapFactory) { 24 | _FAKE_LENDING_PROTOCOL = lendingProtocol; 25 | _UNISWAP_ROUTER = uniswapRouter; 26 | _UNISWAP_FACTORY = uniswapFactory; 27 | _WETH9 = IUniswapV2Router01(uniswapRouter).WETH(); 28 | } 29 | 30 | // 31 | // EXTERNAL NON-VIEW ONLY OWNER 32 | // 33 | 34 | function withdraw() external onlyOwner { 35 | (bool success, ) = msg.sender.call{ value: address(this).balance }(""); 36 | require(success, "Withdraw failed"); 37 | } 38 | 39 | function withdrawTokens(address token, uint256 amount) external onlyOwner { 40 | require(IERC20(token).transfer(msg.sender, amount), "Withdraw failed"); 41 | } 42 | 43 | // 44 | // EXTERNAL NON-VIEW 45 | // 46 | 47 | function uniswapV2Call(address sender, uint256 amount0, uint256 amount1, bytes calldata data) external override { 48 | // TODO 49 | } 50 | 51 | // we use single hop path for testing 52 | function liquidate(address[] calldata path, uint256 amountOut) external { 53 | require(amountOut > 0, "AmountOut must be greater than 0"); 54 | // TODO 55 | } 56 | 57 | receive() external payable {} 58 | } 59 | -------------------------------------------------------------------------------- /section3/FlashSwapPractice/test/ArbitragePractice.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | import "forge-std/Test.sol"; 5 | import { Arbitrage } from "../contracts/Arbitrage.sol"; 6 | import { FlashSwapSetUp } from "./helper/FlashSwapSetUp.sol"; 7 | 8 | contract ArbitragePracticeTest is FlashSwapSetUp { 9 | Arbitrage public arbitrage; 10 | address maker = makeAddr("Maker"); 11 | 12 | function setUp() public override { 13 | super.setUp(); 14 | 15 | // mint 100 ETH, 10000 USDC to maker 16 | vm.deal(maker, 100 ether); 17 | usdc.mint(maker, 10_000 * 10 ** usdc.decimals()); 18 | 19 | // maker provide liquidity to wethUsdcPool, wethUsdcSushiPool 20 | vm.startPrank(maker); 21 | // maker provide 50 ETH, 4000 USDC to wethUsdcPool 22 | usdc.approve(address(uniswapV2Router), 4_000 * 10 ** usdc.decimals()); 23 | uniswapV2Router.addLiquidityETH{ value: 50 ether }( 24 | address(usdc), 25 | 4_000 * 10 ** usdc.decimals(), 26 | 0, 27 | 0, 28 | maker, 29 | block.timestamp 30 | ); 31 | 32 | // maker provide 50 ETH, 6000 USDC to wethUsdcSushiPool 33 | usdc.approve(address(sushiSwapV2Router), 6_000 * 10 ** usdc.decimals()); 34 | sushiSwapV2Router.addLiquidityETH{ value: 50 ether }( 35 | address(usdc), 36 | 6_000 * 10 ** usdc.decimals(), 37 | 0, 38 | 0, 39 | maker, 40 | block.timestamp 41 | ); 42 | vm.stopPrank(); 43 | 44 | // deploy arbitrage contract 45 | arbitrage = new Arbitrage(); 46 | } 47 | 48 | // Uni pool price is 1 ETH = 80 USDC (lower price pool) 49 | // Sushi pool price is 1 ETH = 120 USDC (higher price pool) 50 | // We can arbitrage between these two pools 51 | // Method 1 is 52 | // - borrow WETH from lower price pool 53 | // - swap WETH for USDC in higher price pool 54 | // - repay USDC to lower pool 55 | // Method 2 is 56 | // - borrow USDC from higher price pool 57 | // - swap USDC for WETH in lower pool 58 | // - repay WETH to higher pool 59 | // for testing convenient, we implement the method 1 here, and the exact WETH borrow amount is 5 WETH 60 | function test_arbitrage_with_flash_swap() public { 61 | uint256 borrowETH = 5 ether; 62 | // token0 is WETH, token1 is USDC 63 | arbitrage.arbitrage(address(wethUsdcPool), address(wethUsdcSushiPool), borrowETH); 64 | 65 | // we can earn 98.184746 with 5 ETH flash swap 66 | assertEq(usdc.balanceOf(address(arbitrage)), 98184746); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /section2/StandardProxy/src/MultiSigWallet/MultiSigWallet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | contract MultiSigWallet { 5 | 6 | address public adminOwner; 7 | address public owner1; 8 | address public owner2; 9 | address public owner3; 10 | bool public initialized; 11 | 12 | uint256 constant public CONFIRMATION_REQUIRED = 2; 13 | 14 | struct Transaction { 15 | address to; 16 | uint value; 17 | bytes data; 18 | bool executed; 19 | uint numConfirmations; 20 | } 21 | 22 | mapping(uint => mapping(address => bool)) public isConfirmed; 23 | Transaction[] public transactions; 24 | 25 | event SubmitTransaction(uint indexed txIndex, address indexed to, uint value); 26 | event ExecuteTransaction(uint indexed txIndex); 27 | 28 | function initialize(address[3] memory _owners) external { 29 | require(initialized == false, "already initialized"); 30 | adminOwner = msg.sender; 31 | owner1 = _owners[0]; 32 | owner2 = _owners[1]; 33 | owner3 = _owners[2]; 34 | initialized = true; 35 | } 36 | 37 | modifier onlyOwner { 38 | require(msg.sender == owner1 || msg.sender == owner2 || msg.sender == owner3, "not owner"); 39 | _; 40 | } 41 | 42 | modifier onlyAdmin { 43 | require(msg.sender == adminOwner, "MultiSig: only admin"); 44 | _; 45 | } 46 | 47 | function submitTransaction(address _to, uint256 _value, bytes calldata data) external onlyOwner { 48 | uint txIndex = transactions.length; 49 | 50 | transactions.push(Transaction({ 51 | to: _to, 52 | value: _value, 53 | data: data, 54 | executed: false, 55 | numConfirmations: 0 56 | })); 57 | emit SubmitTransaction(txIndex, _to, _value); 58 | } 59 | 60 | function confirmTransaction() external onlyOwner { 61 | uint256 _txIndex = transactions.length - 1; 62 | Transaction storage transaction = transactions[_txIndex]; 63 | transaction.numConfirmations += 1; 64 | isConfirmed[_txIndex][msg.sender] = true; 65 | } 66 | 67 | function executeTransaction() external onlyOwner { 68 | uint256 _txIndex = transactions.length - 1; 69 | Transaction storage transaction = transactions[_txIndex]; 70 | require(!transaction.executed, "tx already executed"); 71 | 72 | if (transaction.numConfirmations >= CONFIRMATION_REQUIRED) { 73 | transaction.executed = true; 74 | (bool success, ) = transaction.to.call{value: transaction.value}(transaction.data); 75 | require(success, "tx failed"); 76 | emit ExecuteTransaction(_txIndex); 77 | } 78 | } 79 | 80 | function VERSION() external view virtual returns (string memory) { 81 | return "0.0.1"; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /section2/ProxyPattern/src/MultiSigWallet/MultiSigWallet.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity ^0.8.17; 3 | 4 | contract MultiSigWallet { 5 | 6 | address public adminOwner; 7 | address public owner1; 8 | address public owner2; 9 | address public owner3; 10 | 11 | uint256 constant public CONFIRMATION_REQUIRED = 2; 12 | 13 | struct Transaction { 14 | address to; 15 | uint value; 16 | bytes data; 17 | bool executed; 18 | uint numConfirmations; 19 | } 20 | 21 | mapping(uint => mapping(address => bool)) public isConfirmed; 22 | Transaction[] public transactions; 23 | 24 | event SubmitTransaction(uint indexed txIndex, address indexed to, uint value); 25 | event ExecuteTransaction(uint indexed txIndex); 26 | 27 | constructor(address[3] memory _owners) { 28 | adminOwner = msg.sender; 29 | owner1 = _owners[0]; 30 | owner2 = _owners[1]; 31 | owner3 = _owners[2]; 32 | } 33 | 34 | modifier onlyOwner { 35 | require(msg.sender == owner1 || msg.sender == owner2 || msg.sender == owner3, "not owner"); 36 | _; 37 | } 38 | 39 | modifier onlyAdmin { 40 | require(msg.sender == adminOwner, "not admin"); 41 | _; 42 | } 43 | 44 | function submitTransaction(address _to, uint256 _value, bytes calldata data) external onlyOwner { 45 | uint txIndex = transactions.length; 46 | 47 | transactions.push(Transaction({ 48 | to: _to, 49 | value: _value, 50 | data: data, 51 | executed: false, 52 | numConfirmations: 0 53 | })); 54 | emit SubmitTransaction(txIndex, _to, _value); 55 | } 56 | 57 | function confirmTransaction() external onlyOwner { 58 | uint256 _txIndex = transactions.length - 1; 59 | Transaction storage transaction = transactions[_txIndex]; 60 | transaction.numConfirmations += 1; 61 | isConfirmed[_txIndex][msg.sender] = true; 62 | } 63 | 64 | function executeTransaction() external onlyOwner { 65 | uint256 _txIndex = transactions.length - 1; 66 | Transaction storage transaction = transactions[_txIndex]; 67 | require(!transaction.executed, "tx already executed"); 68 | 69 | if (transaction.numConfirmations >= CONFIRMATION_REQUIRED) { 70 | transaction.executed = true; 71 | (bool success, ) = transaction.to.call{value: transaction.value}(transaction.data); 72 | require(success, "tx failed"); 73 | emit ExecuteTransaction(_txIndex); 74 | } 75 | } 76 | 77 | function updateOwner(address _newOwner1, address _newOwner2, address _newOwner3) external virtual { 78 | owner1 = _newOwner1; 79 | owner2 = _newOwner2; 80 | owner3 = _newOwner3; 81 | } 82 | 83 | function destroy() external onlyAdmin { 84 | selfdestruct(payable(adminOwner)); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /section3/LatenRisk/src/challenge/EIP20Interface.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.20; 2 | 3 | // Copyright 2020 Compound Labs, Inc. 4 | 5 | /** 6 | * @title ERC 20 Token Standard Interface 7 | * https://eips.ethereum.org/EIPS/eip-20 8 | */ 9 | interface EIP20Interface { 10 | function name() external view returns (string memory); 11 | function symbol() external view returns (string memory); 12 | function decimals() external view returns (uint8); 13 | 14 | /** 15 | * @notice Get the total number of tokens in circulation 16 | * @return The supply of tokens 17 | */ 18 | function totalSupply() external view returns (uint256); 19 | 20 | /** 21 | * @notice Gets the balance of the specified address 22 | * @param owner The address from which the balance will be retrieved 23 | * @return balance The balance 24 | */ 25 | function balanceOf(address owner) external view returns (uint256 balance); 26 | 27 | /** 28 | * @notice Transfer `amount` tokens from `msg.sender` to `dst` 29 | * @param dst The address of the destination account 30 | * @param amount The number of tokens to transfer 31 | * @return success Whether or not the transfer succeeded 32 | */ 33 | function transfer(address dst, uint256 amount) external returns (bool success); 34 | 35 | /** 36 | * @notice Transfer `amount` tokens from `src` to `dst` 37 | * @param src The address of the source account 38 | * @param dst The address of the destination account 39 | * @param amount The number of tokens to transfer 40 | * @return success Whether or not the transfer succeeded 41 | */ 42 | function transferFrom(address src, address dst, uint256 amount) external returns (bool success); 43 | 44 | /** 45 | * @notice Approve `spender` to transfer up to `amount` from `src` 46 | * @dev This will overwrite the approval amount for `spender` 47 | * and is subject to issues noted [here](https://eips.ethereum.org/EIPS/eip-20#approve) 48 | * @param spender The address of the account which may transfer tokens 49 | * @param amount The number of tokens that are approved (-1 means infinite) 50 | * @return success Whether or not the approval succeeded 51 | */ 52 | function approve(address spender, uint256 amount) external returns (bool success); 53 | 54 | /** 55 | * @notice Get the current allowance from `owner` for `spender` 56 | * @param owner The address of the account which owns the tokens to be spent 57 | * @param spender The address of the account which may transfer tokens 58 | * @return remaining The number of tokens allowed to be spent (-1 means infinite) 59 | */ 60 | function allowance(address owner, address spender) external view returns (uint256 remaining); 61 | 62 | event Transfer(address indexed from, address indexed to, uint256 amount); 63 | event Approval(address indexed owner, address indexed spender, uint256 amount); 64 | } 65 | -------------------------------------------------------------------------------- /section3/FlashSwapPractice/contracts/Arbitrage.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol"; 5 | import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; 6 | import { IUniswapV2Pair } from "v2-core/interfaces/IUniswapV2Pair.sol"; 7 | import { IUniswapV2Callee } from "v2-core/interfaces/IUniswapV2Callee.sol"; 8 | 9 | // This is a practice contract for flash swap arbitrage 10 | contract Arbitrage is IUniswapV2Callee, Ownable { 11 | 12 | // 13 | // EXTERNAL NON-VIEW ONLY OWNER 14 | // 15 | 16 | function withdraw() external onlyOwner { 17 | (bool success, ) = msg.sender.call{ value: address(this).balance }(""); 18 | require(success, "Withdraw failed"); 19 | } 20 | 21 | function withdrawTokens(address token, uint256 amount) external onlyOwner { 22 | require(IERC20(token).transfer(msg.sender, amount), "Withdraw failed"); 23 | } 24 | 25 | // 26 | // EXTERNAL NON-VIEW 27 | // 28 | 29 | function uniswapV2Call(address sender, uint256 amount0, uint256 amount1, bytes calldata data) external override { 30 | // TODO 31 | } 32 | 33 | // Method 1 is 34 | // - borrow WETH from lower price pool 35 | // - swap WETH for USDC in higher price pool 36 | // - repay USDC to lower pool 37 | // Method 2 is 38 | // - borrow USDC from higher price pool 39 | // - swap USDC for WETH in lower pool 40 | // - repay WETH to higher pool 41 | // for testing convenient, we implement the method 1 here 42 | function arbitrage(address priceLowerPool, address priceHigherPool, uint256 borrowETH) external { 43 | // TODO 44 | } 45 | 46 | // 47 | // INTERNAL PURE 48 | // 49 | 50 | // copy from UniswapV2Library 51 | function _getAmountIn( 52 | uint256 amountOut, 53 | uint256 reserveIn, 54 | uint256 reserveOut 55 | ) internal pure returns (uint256 amountIn) { 56 | require(amountOut > 0, "UniswapV2Library: INSUFFICIENT_OUTPUT_AMOUNT"); 57 | require(reserveIn > 0 && reserveOut > 0, "UniswapV2Library: INSUFFICIENT_LIQUIDITY"); 58 | uint256 numerator = reserveIn * amountOut * 1000; 59 | uint256 denominator = (reserveOut - amountOut) * 997; 60 | amountIn = numerator / denominator + 1; 61 | } 62 | 63 | // copy from UniswapV2Library 64 | function _getAmountOut( 65 | uint256 amountIn, 66 | uint256 reserveIn, 67 | uint256 reserveOut 68 | ) internal pure returns (uint256 amountOut) { 69 | require(amountIn > 0, "UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT"); 70 | require(reserveIn > 0 && reserveOut > 0, "UniswapV2Library: INSUFFICIENT_LIQUIDITY"); 71 | uint256 amountInWithFee = amountIn * 997; 72 | uint256 numerator = amountInWithFee * reserveOut; 73 | uint256 denominator = reserveIn * 1000 + amountInWithFee; 74 | amountOut = numerator / denominator; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /section3/lending.md: -------------------------------------------------------------------------------- 1 | # 借貸協議與閃電貸(Flash Loan) 2 | 3 | ## 目的:理解區塊鏈的借貸協議怎麼做,以及閃電貸怎麼使用 4 | 請賞析 [Compound](https://docs.compound.finance/v2/) 的合約,並依序實作以下 5 | 1. 撰寫一個 Foundry 的 Script,該 Script 要能夠部署一個 CErc20Delegator(`CErc20Delegator.sol`,以下簡稱 cERC20),一個 Unitroller(`Unitroller.sol`) 以及他們的 Implementation 合約和合約初始化時相關必要合約。請遵循以下細節: 6 | * cERC20 的 decimals 皆為 18 7 | * 自行部署一個 cERC20 的 underlying ERC20 token,decimals 為 18 8 | * 使用 `SimplePriceOracle` 作為 Oracle 9 | * 使用 `WhitePaperInterestRateModel` 作為利率模型,利率模型合約中的借貸利率設定為 0% 10 | * 初始 exchangeRate 為 1:1 11 | 12 | 2. 讓 User1 mint/redeem cERC20,請透過 Foundry test case (你可以繼承上題的 script 或是用其他方式實現部署) 實現以下場景: 13 | * User1 使用 100 顆(100 * 10^18) ERC20 去 mint 出 100 cERC20 token,再用 100 cERC20 token redeem 回 100 顆 ERC20 14 | 15 | 3. 讓 User1 borrow/repay 16 | * 部署第二份 cERC20 合約,以下稱它們的 underlying tokens 為 token A 與 token B。 17 | * 在 Oracle 中設定一顆 token A 的價格為 $1,一顆 token B 的價格為 $100 18 | * Token B 的 collateral factor 為 50% 19 | * User1 使用 1 顆 token B 來 mint cToken 20 | * User1 使用 token B 作為抵押品來借出 50 顆 token A 21 | 22 | 4. 延續 (3.) 的借貸場景,調整 token B 的 collateral factor,讓 User1 被 User2 清算 23 | 24 | 5. 延續 (3.) 的借貸場景,調整 oracle 中 token B 的價格,讓 User1 被 User2 清算 25 | 26 | 6. 請使用 Foundry 的 fork testing 模式撰寫測試,並使用 AAVE v3 的 [Flash loan](https://docs.aave.com/developers/guides/flash-loans) 來清算 User1,請遵循以下細節: 27 | * Fork Ethereum mainnet at block 17465000([Reference](https://book.getfoundry.sh/forge/fork-testing#examples)) 28 | * cERC20 的 decimals 皆為 18,初始 exchangeRate 為 1:1 29 | * Close factor 設定為 50% 30 | * Liquidation incentive 設為 8% (1.08 * 1e18) 31 | * 使用 USDC 以及 UNI 代幣來作為 token A 以及 Token B 32 | * 在 Oracle 中設定 USDC 的價格為 $1,UNI 的價格為 $5 33 | * 設定 UNI 的 collateral factor 為 50% 34 | * User1 使用 1000 顆 UNI 作為抵押品借出 2500 顆 USDC 35 | * 將 UNI 價格改為 $4 使 User1 產生 Shortfall,並讓 User2 透過 AAVE 的 Flash loan 來借錢清算 User1 36 | * 可以自行檢查清算 50% 後是不是大約可以賺 63 USDC 37 | * 在合約中如需將 UNI 換成 USDC 可以使用以下程式碼片段: 38 | ```javascript 39 | // https://docs.uniswap.org/protocol/guides/swaps/single-swaps 40 | 41 | ISwapRouter.ExactInputSingleParams memory swapParams = 42 | ISwapRouter.ExactInputSingleParams({ 43 | tokenIn: UNI_ADDRESS, 44 | tokenOut: USDC_ADDRESS, 45 | fee: 3000, // 0.3% 46 | recipient: address(this), 47 | deadline: block.timestamp, 48 | amountIn: uniAmount, 49 | amountOutMinimum: 0, 50 | sqrtPriceLimitX96: 0 51 | }); 52 | 53 | // The call to `exactInputSingle` executes the swap. 54 | // swap Router = 0xE592427A0AEce92De3Edee1F18E0157C05861564 55 | uint256 amountOut = swapRouter.exactInputSingle(swapParams); 56 | ``` 57 | 58 | 進階題: 59 | 1. 使用一套治理框架(例如 Governor Bravo 加上 Timelock)完成 Comptroller 中的設置 60 | 2. 賞析 [UniswapAnchoredView](https://etherscan.io/address/0x50ce56A3239671Ab62f185704Caedf626352741e#code) 合約並使用其作為 Comptroller 中設置的 oracle 來實現清算 61 | 3. 設計一個能透過 Flash loan 清算多種代幣類型的智能合約 62 | 4. 研究 [Aave](https://aave.com/) 協議,比較這些借貸協議在功能上與合約開發上的差異 63 | 64 | ## 參考資料 65 | 66 | --- 67 | [回階段三](./README.md) 68 | -------------------------------------------------------------------------------- /section3/CompoundGovernancePractice/test/Governance.t.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.8.20; 2 | 3 | import "forge-std/Test.sol"; 4 | import { Unitroller } from "compound-protocol/contracts/Unitroller.sol"; 5 | import { Comptroller } from "compound-protocol/contracts/Comptroller.sol"; 6 | import { SimplePriceOracle } from "compound-protocol/contracts/SimplePriceOracle.sol"; 7 | import { WhitePaperInterestRateModel } from "compound-protocol/contracts/WhitePaperInterestRateModel.sol"; 8 | import { CErc20Delegate } from "compound-protocol/contracts/CErc20Delegate.sol"; 9 | import { CErc20Delegator } from "compound-protocol/contracts/CErc20Delegator.sol"; 10 | import { UnitrollerAdminStorage } from "compound-protocol/contracts/ComptrollerStorage.sol"; 11 | import { GovernorBravoDelegator } from "compound-protocol/contracts/Governance/GovernorBravoDelegator.sol"; 12 | import { GovernorBravoDelegateStorageV2 } from "compound-protocol/contracts/Governance/GovernorBravoInterfaces.sol"; 13 | import { CToken } from "compound-protocol/contracts/CToken.sol"; 14 | import { Comp } from "compound-protocol/contracts/Governance/Comp.sol"; 15 | import { GovernorBravoDelegate } from "src/GovernorBravoDelegate.sol"; 16 | import { Timelock } from "src/Timelock.sol"; 17 | 18 | contract GovernanceTest is Test { 19 | Unitroller constant public unitroller = Unitroller(payable(0x63d005EA741704dDA3d74Cb54a5bf6F3b1Dc86DB)); 20 | CErc20Delegator constant public cToken = CErc20Delegator(payable(0xdc25E4DDd051De774566ACC5a7442284e659FeeC)); 21 | GovernorBravoDelegator constant public bravo = GovernorBravoDelegator(payable(0x561adf66bEf90969783d6E6D118e16Fd6856F862)); 22 | Timelock constant public timelock = Timelock(payable(0x93C485BC5F028C36dFf0B9add0dCAfb080cb7dd7)); 23 | Comp constant public comp = Comp(payable(0x8dCb0C9a616bEdcf70eB826BA8Cfc8a11b420EE7)); 24 | 25 | function test_governance() public { 26 | address admin = 0x87176364A742aa3cd6a41De1A2E910602881AB7d; 27 | vm.startPrank(admin); 28 | 29 | comp.delegate(admin); 30 | vm.roll(block.number + 1); 31 | 32 | // Create a proposal 33 | address[] memory targets = new address[](1); 34 | targets[0] = address(unitroller); 35 | 36 | uint256[] memory values = new uint256[](1); 37 | values[0] = 0; 38 | 39 | string[] memory signatures = new string[](1); 40 | signatures[0]; 41 | 42 | bytes[] memory calldatas = new bytes[](1); 43 | calldatas[0] = abi.encodeCall(Comptroller._supportMarket, (CToken(address(cToken)))); 44 | 45 | string memory description = "Support Market"; 46 | 47 | uint256 proposalId = GovernorBravoDelegate(address(bravo)).propose(targets, values, signatures, calldatas, description); 48 | 49 | // Vote for the proposal 50 | vm.roll(block.number + 25 + 1); 51 | GovernorBravoDelegate(address(bravo)).castVote(proposalId, 1); 52 | 53 | vm.roll(block.number + 25 + 1 + 100 + 1); 54 | GovernorBravoDelegate(address(bravo)).queue(proposalId); 55 | 56 | vm.warp(block.timestamp + 5 minutes + 1); 57 | GovernorBravoDelegate(address(bravo)).execute(proposalId); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /section3/LatenRisk/src/challenge/EIP20NonStandardInterface.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.20; 2 | 3 | // Copyright 2020 Compound Labs, Inc. 4 | 5 | /** 6 | * @title EIP20NonStandardInterface 7 | * @dev Version of ERC20 with no return values for `transfer` and `transferFrom` 8 | * See https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca 9 | */ 10 | interface EIP20NonStandardInterface { 11 | /** 12 | * @notice Get the total number of tokens in circulation 13 | * @return The supply of tokens 14 | */ 15 | function totalSupply() external view returns (uint256); 16 | 17 | /** 18 | * @notice Gets the balance of the specified address 19 | * @param owner The address from which the balance will be retrieved 20 | * @return balance The balance 21 | */ 22 | function balanceOf(address owner) external view returns (uint256 balance); 23 | 24 | /// 25 | /// !!!!!!!!!!!!!! 26 | /// !!! NOTICE !!! `transfer` does not return a value, in violation of the ERC-20 specification 27 | /// !!!!!!!!!!!!!! 28 | /// 29 | 30 | /** 31 | * @notice Transfer `amount` tokens from `msg.sender` to `dst` 32 | * @param dst The address of the destination account 33 | * @param amount The number of tokens to transfer 34 | */ 35 | function transfer(address dst, uint256 amount) external; 36 | 37 | /// 38 | /// !!!!!!!!!!!!!! 39 | /// !!! NOTICE !!! `transferFrom` does not return a value, in violation of the ERC-20 specification 40 | /// !!!!!!!!!!!!!! 41 | /// 42 | 43 | /** 44 | * @notice Transfer `amount` tokens from `src` to `dst` 45 | * @param src The address of the source account 46 | * @param dst The address of the destination account 47 | * @param amount The number of tokens to transfer 48 | */ 49 | function transferFrom(address src, address dst, uint256 amount) external; 50 | 51 | /** 52 | * @notice Approve `spender` to transfer up to `amount` from `src` 53 | * @dev This will overwrite the approval amount for `spender` 54 | * and is subject to issues noted [here](https://eips.ethereum.org/EIPS/eip-20#approve) 55 | * @param spender The address of the account which may transfer tokens 56 | * @param amount The number of tokens that are approved 57 | * @return success Whether or not the approval succeeded 58 | */ 59 | function approve(address spender, uint256 amount) external returns (bool success); 60 | 61 | /** 62 | * @notice Get the current allowance from `owner` for `spender` 63 | * @param owner The address of the account which owns the tokens to be spent 64 | * @param spender The address of the account which may transfer tokens 65 | * @return remaining The number of tokens allowed to be spent 66 | */ 67 | function allowance(address owner, address spender) external view returns (uint256 remaining); 68 | 69 | event Transfer(address indexed from, address indexed to, uint256 amount); 70 | event Approval(address indexed owner, address indexed spender, uint256 amount); 71 | } 72 | -------------------------------------------------------------------------------- /section3/SimpleSwap/contracts/interface/ISimpleSwap.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | interface ISimpleSwapEvent { 5 | /// @param sender The address of the msg sender 6 | /// @param amountA The amount of tokenA to add liquidity 7 | /// @param amountB The amount of tokenB to add liquidity 8 | /// @param liquidity The amount of liquidity to mint 9 | event AddLiquidity(address indexed sender, uint256 amountA, uint256 amountB, uint256 liquidity); 10 | 11 | /// @param sender The address of the msg sender 12 | /// @param amountA The amount of tokenA to remove liquidity 13 | /// @param amountB The amount of tokenB to remove liquidity 14 | /// @param liquidity The amount of liquidity to burn 15 | event RemoveLiquidity(address indexed sender, uint256 amountA, uint256 amountB, uint256 liquidity); 16 | 17 | /// @param sender The address of the msg sender 18 | /// @param tokenIn The address of the token to swap from 19 | /// @param tokenOut The address of the token to swap to 20 | /// @param amountIn The amount of tokenIn to swap 21 | /// @param amountOut The amount of tokenOut to receive 22 | event Swap( 23 | address indexed sender, 24 | address indexed tokenIn, 25 | address indexed tokenOut, 26 | uint256 amountIn, 27 | uint256 amountOut 28 | ); 29 | } 30 | 31 | interface ISimpleSwap is ISimpleSwapEvent { 32 | /// @notice Swap tokenIn for tokenOut with amountIn 33 | /// @param tokenIn The address of the token to swap from 34 | /// @param tokenOut The address of the token to swap to 35 | /// @param amountIn The amount of tokenIn to swap 36 | /// @return amountOut The amount of tokenOut received 37 | function swap(address tokenIn, address tokenOut, uint256 amountIn) external returns (uint256 amountOut); 38 | 39 | /// @notice Add liquidity to the pool 40 | /// @param amountAIn The amount of tokenA to add 41 | /// @param amountBIn The amount of tokenB to add 42 | /// @return amountA The actually amount of tokenA added 43 | /// @return amountB The actually amount of tokenB added 44 | /// @return liquidity The amount of liquidity minted 45 | function addLiquidity( 46 | uint256 amountAIn, 47 | uint256 amountBIn 48 | ) external returns (uint256 amountA, uint256 amountB, uint256 liquidity); 49 | 50 | /// @notice Remove liquidity from the pool 51 | /// @param liquidity The amount of liquidity to remove 52 | /// @return amountA The amount of tokenA received 53 | /// @return amountB The amount of tokenB received 54 | function removeLiquidity(uint256 liquidity) external returns (uint256 amountA, uint256 amountB); 55 | 56 | /// @notice Get the reserves of the pool 57 | /// @return reserveA The reserve of tokenA 58 | /// @return reserveB The reserve of tokenB 59 | function getReserves() external view returns (uint256 reserveA, uint256 reserveB); 60 | 61 | /// @notice Get the address of tokenA 62 | /// @return tokenA The address of tokenA 63 | function getTokenA() external view returns (address tokenA); 64 | 65 | /// @notice Get the address of tokenB 66 | /// @return tokenB The address of tokenB 67 | function getTokenB() external view returns (address tokenB); 68 | } 69 | -------------------------------------------------------------------------------- /section3/LatenRisk/src/challenge/ComptrollerInterface.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.20; 2 | 3 | // Copyright 2020 Compound Labs, Inc. 4 | 5 | abstract contract ComptrollerInterface { 6 | /// @notice Indicator that this is a Comptroller contract (for inspection) 7 | bool public constant isComptroller = true; 8 | 9 | /** 10 | * Assets You Are In ** 11 | */ 12 | 13 | function enterMarkets(address[] calldata cTokens) external virtual returns (uint256[] memory); 14 | function exitMarket(address cToken) external virtual returns (uint256); 15 | 16 | /** 17 | * Policy Hooks ** 18 | */ 19 | 20 | function mintAllowed(address cToken, address minter, uint256 mintAmount) external virtual returns (uint256); 21 | function mintVerify(address cToken, address minter, uint256 mintAmount, uint256 mintTokens) external virtual; 22 | 23 | function redeemAllowed(address cToken, address redeemer, uint256 redeemTokens) external virtual returns (uint256); 24 | function redeemVerify(address cToken, address redeemer, uint256 redeemAmount, uint256 redeemTokens) 25 | external 26 | virtual; 27 | 28 | function borrowAllowed(address cToken, address borrower, uint256 borrowAmount) external virtual returns (uint256); 29 | function borrowVerify(address cToken, address borrower, uint256 borrowAmount) external virtual; 30 | 31 | function repayBorrowAllowed(address cToken, address payer, address borrower, uint256 repayAmount) 32 | external 33 | virtual 34 | returns (uint256); 35 | function repayBorrowVerify( 36 | address cToken, 37 | address payer, 38 | address borrower, 39 | uint256 repayAmount, 40 | uint256 borrowerIndex 41 | ) external virtual; 42 | 43 | function liquidateBorrowAllowed( 44 | address cTokenBorrowed, 45 | address cTokenCollateral, 46 | address liquidator, 47 | address borrower, 48 | uint256 repayAmount 49 | ) external virtual returns (uint256); 50 | function liquidateBorrowVerify( 51 | address cTokenBorrowed, 52 | address cTokenCollateral, 53 | address liquidator, 54 | address borrower, 55 | uint256 repayAmount, 56 | uint256 seizeTokens 57 | ) external virtual; 58 | 59 | function seizeAllowed( 60 | address cTokenCollateral, 61 | address cTokenBorrowed, 62 | address liquidator, 63 | address borrower, 64 | uint256 seizeTokens 65 | ) external virtual returns (uint256); 66 | function seizeVerify( 67 | address cTokenCollateral, 68 | address cTokenBorrowed, 69 | address liquidator, 70 | address borrower, 71 | uint256 seizeTokens 72 | ) external virtual; 73 | 74 | function transferAllowed(address cToken, address src, address dst, uint256 transferTokens) 75 | external 76 | virtual 77 | returns (uint256); 78 | function transferVerify(address cToken, address src, address dst, uint256 transferTokens) external virtual; 79 | 80 | /** 81 | * Liquidity/Liquidation Calculations ** 82 | */ 83 | 84 | function liquidateCalculateSeizeTokens(address cTokenBorrowed, address cTokenCollateral, uint256 repayAmount) 85 | external 86 | view 87 | virtual 88 | returns (uint256, uint256); 89 | } 90 | -------------------------------------------------------------------------------- /section3/SandwichPractice/test/helper/SandwichSetUp.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | import "forge-std/Test.sol"; 5 | import { IUniswapV2Factory } from "v2-core/interfaces/IUniswapV2Factory.sol"; 6 | import { IUniswapV2Pair } from "v2-core/interfaces/IUniswapV2Pair.sol"; 7 | import { IUniswapV2Router01 } from "v2-periphery/interfaces/IUniswapV2Router01.sol"; 8 | import { TestWETH9 } from "../../contracts/test/TestWETH9.sol"; 9 | import { TestERC20 } from "../../contracts/test/TestERC20.sol"; 10 | 11 | contract SandwichSetUp is Test { 12 | TestWETH9 public weth; 13 | TestERC20 public usdc; 14 | IUniswapV2Factory public uniswapV2Factory; 15 | IUniswapV2Router01 public uniswapV2Router; 16 | IUniswapV2Pair public wethUsdcPool; 17 | 18 | function setUp() public virtual { 19 | usdc = _create_erc20("USD Coin", "USDC", 6); 20 | weth = _create_weth9(); 21 | uniswapV2Factory = _create_uniswap_v2_factory(); 22 | uniswapV2Router = _create_uniswap_v2_router(address(uniswapV2Factory), address(weth)); 23 | wethUsdcPool = _create_pool(address(uniswapV2Factory), address(weth), address(usdc)); 24 | 25 | vm.label(address(uniswapV2Factory), "UniswapV2Factory"); 26 | vm.label(address(uniswapV2Router), "UniswapV2Router"); 27 | vm.label(address(wethUsdcPool), "WethUsdcPool"); 28 | vm.label(address(weth), "WETH9"); 29 | vm.label(address(usdc), "USDC"); 30 | } 31 | 32 | function _create_weth9() public returns (TestWETH9) { 33 | weth = new TestWETH9(); 34 | return weth; 35 | } 36 | 37 | function _create_erc20(string memory name, string memory symbol, uint8 decimals) public returns (TestERC20) { 38 | usdc = new TestERC20(name, symbol, decimals); 39 | return usdc; 40 | } 41 | 42 | function _create_pool(address factory, address tokenA, address tokenB) public returns (IUniswapV2Pair) { 43 | address pool = IUniswapV2Factory(factory).createPair(tokenA, tokenB); 44 | return IUniswapV2Pair(pool); 45 | } 46 | 47 | function _create_uniswap_v2_factory() internal returns (IUniswapV2Factory) { 48 | string memory path = string( 49 | abi.encodePacked(vm.projectRoot(), "/node_modules/@uniswap/v2-core/build/UniswapV2Factory.json") 50 | ); 51 | string memory artifact = vm.readFile(path); 52 | bytes memory creationCode = vm.parseBytes(abi.decode(vm.parseJson(artifact, ".bytecode"), (string))); 53 | creationCode = abi.encodePacked(creationCode, abi.encode(address(0))); 54 | address anotherAddress; 55 | 56 | assembly { 57 | anotherAddress := create(0, add(creationCode, 0x20), mload(creationCode)) 58 | } 59 | 60 | return IUniswapV2Factory(anotherAddress); 61 | } 62 | 63 | function _create_uniswap_v2_router(address factory, address weth9) internal returns (IUniswapV2Router01) { 64 | string memory path = string( 65 | abi.encodePacked(vm.projectRoot(), "/node_modules/@uniswap/v2-periphery/build/UniswapV2Router01.json") 66 | ); 67 | string memory artifact = vm.readFile(path); 68 | bytes memory creationCode = vm.parseBytes(abi.decode(vm.parseJson(artifact, ".bytecode"), (string))); 69 | 70 | creationCode = abi.encodePacked(creationCode, abi.encode(factory), abi.encode(weth9)); 71 | address anotherAddress; 72 | 73 | assembly { 74 | anotherAddress := create(0, add(creationCode, 0x20), mload(creationCode)) 75 | } 76 | 77 | return IUniswapV2Router01(anotherAddress); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /section3/SandwichPractice/test/SandwichPractice.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | import "forge-std/Test.sol"; 5 | import { SandwichSetUp } from "./helper/SandwichSetUp.sol"; 6 | 7 | contract SandwichPracticeTest is SandwichSetUp { 8 | address public maker = makeAddr("Maker"); 9 | address public victim = makeAddr("Victim"); 10 | address public attacker = makeAddr("Attacker"); 11 | uint256 public victimUsdcAmountOutMin; 12 | uint256 makerInitialEthBalance; 13 | uint256 makerInitialUsdcBalance; 14 | uint256 attackerInitialEthBalance; 15 | uint256 victimInitialEthBalance; 16 | 17 | function setUp() public override { 18 | super.setUp(); 19 | 20 | makerInitialEthBalance = 100 ether; 21 | makerInitialUsdcBalance = 10_000 * 10 ** usdc.decimals(); 22 | attackerInitialEthBalance = 5 ether; 23 | victimInitialEthBalance = 1 ether; 24 | 25 | // mint 100 ETH, 10000 USDC to maker 26 | vm.deal(maker, makerInitialEthBalance); 27 | usdc.mint(maker, makerInitialUsdcBalance); 28 | 29 | // mint 100 ETH to attacker 30 | vm.deal(attacker, attackerInitialEthBalance); 31 | 32 | // mint 1 ETH to victim 33 | vm.deal(victim, victimInitialEthBalance); 34 | 35 | // maker provide 100 ETH, 10000 USDC to wethUsdcPool 36 | vm.startPrank(maker); 37 | usdc.approve(address(uniswapV2Router), makerInitialUsdcBalance); 38 | uniswapV2Router.addLiquidityETH{ value: makerInitialEthBalance }( 39 | address(usdc), 40 | makerInitialUsdcBalance, 41 | 0, 42 | 0, 43 | maker, 44 | block.timestamp 45 | ); 46 | vm.stopPrank(); 47 | } 48 | 49 | modifier attackerModifier() { 50 | _attackerAction1(); 51 | _; 52 | _attackerAction2(); 53 | _checkAttackerProfit(); 54 | } 55 | 56 | // Do not modify this test function 57 | function test_sandwich_attack_with_profit() public attackerModifier { 58 | // victim swap 1 ETH to USDC with usdcAmountOutMin 59 | vm.startPrank(victim); 60 | address[] memory path = new address[](2); 61 | path[0] = address(weth); 62 | path[1] = address(usdc); 63 | 64 | // # Discussion 1: how to get victim tx detail info ? 65 | // without attacker action, original usdc amount out is 98715803, use 5% slippage 66 | // originalUsdcAmountOutMin = 93780012; 67 | uint256 originalUsdcAmountOut = 98715803; 68 | uint256 originalUsdcAmountOutMin = (originalUsdcAmountOut * 95) / 100; 69 | 70 | uniswapV2Router.swapExactETHForTokens{ value: 1 ether }( 71 | originalUsdcAmountOutMin, 72 | path, 73 | victim, 74 | block.timestamp 75 | ); 76 | vm.stopPrank(); 77 | 78 | // check victim usdc balance >= originalUsdcAmountOutMin (93780012) 79 | assertGe(usdc.balanceOf(victim), originalUsdcAmountOutMin); 80 | } 81 | 82 | // # Practice 1: attacker sandwich attack 83 | function _attackerAction1() internal { 84 | // victim swap ETH to USDC (front-run victim) 85 | // implement here 86 | } 87 | 88 | // # Practice 2: attacker sandwich attack 89 | function _attackerAction2() internal { 90 | // victim swap USDC to ETH 91 | // implement here 92 | } 93 | 94 | // # Discussion 2: how to maximize profit ? 95 | function _checkAttackerProfit() internal { 96 | uint256 profit = attacker.balance - attackerInitialEthBalance; 97 | assertGt(profit, 0); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /section2/RugPullHw/test/TradingCenterTest.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SEE LICENSE IN LICENSE 2 | pragma solidity ^0.8.17; 3 | 4 | import "forge-std/Test.sol"; 5 | import "solmate/tokens/ERC20.sol"; 6 | import { TradingCenter, IERC20 } from "../src/TradingCenter.sol"; 7 | import { TradingCenterV2 } from "../src/TradingCenterV2.sol"; 8 | import { UpgradeableProxy } from "../src/UpgradeableProxy.sol"; 9 | 10 | contract FiatToken is ERC20 { 11 | constructor(string memory name, string memory symbol, uint8 decimals) ERC20(name, symbol, decimals){} 12 | } 13 | 14 | contract TradingCenterTest is Test { 15 | 16 | // Owner and users 17 | address owner = makeAddr("owner"); 18 | address user1 = makeAddr("user1"); 19 | address user2 = makeAddr("user2"); 20 | 21 | // Contracts 22 | TradingCenter tradingCenter; 23 | TradingCenter proxyTradingCenter; 24 | UpgradeableProxy proxy; 25 | IERC20 usdt; 26 | IERC20 usdc; 27 | 28 | // Initial balances 29 | uint256 initialBalance = 100000 ether; 30 | uint256 userInitialBalance = 10000 ether; 31 | 32 | function setUp() public { 33 | 34 | vm.startPrank(owner); 35 | // 1. Owner deploys TradingCenter 36 | tradingCenter = new TradingCenter(); 37 | // 2. Owner deploys UpgradeableProxy with TradingCenter address 38 | proxy = new UpgradeableProxy(address(tradingCenter)); 39 | // 3. Assigns proxy address to have interface of TradingCenter 40 | proxyTradingCenter = TradingCenter(address(proxy)); 41 | // 4. Deploy usdt and usdc 42 | FiatToken usdtERC20 = new FiatToken("USDT", "USDT", 18); 43 | FiatToken usdcERC20 = new FiatToken("USDC", "USDC", 18); 44 | // 5. Assign usdt and usdc to have interface of IERC20 45 | usdt = IERC20(address(usdtERC20)); 46 | usdc = IERC20(address(usdcERC20)); 47 | // 6. owner initialize on proxyTradingCenter 48 | proxyTradingCenter.initialize(usdt, usdc); 49 | vm.stopPrank(); 50 | 51 | // Let proxyTradingCenter to have some initial balances of usdt and usdc 52 | deal(address(usdt), address(proxyTradingCenter), initialBalance); 53 | deal(address(usdc), address(proxyTradingCenter), initialBalance); 54 | // Let user1 and user2 to have some initial balances of usdt and usdc 55 | deal(address(usdt), user1, userInitialBalance); 56 | deal(address(usdc), user1, userInitialBalance); 57 | deal(address(usdt), user2, userInitialBalance); 58 | deal(address(usdc), user2, userInitialBalance); 59 | 60 | // user1 approve to proxyTradingCenter 61 | vm.startPrank(user1); 62 | usdt.approve(address(proxyTradingCenter), type(uint256).max); 63 | usdc.approve(address(proxyTradingCenter), type(uint256).max); 64 | vm.stopPrank(); 65 | 66 | // user1 approve to proxyTradingCenter 67 | vm.startPrank(user2); 68 | usdt.approve(address(proxyTradingCenter), type(uint256).max); 69 | usdc.approve(address(proxyTradingCenter), type(uint256).max); 70 | vm.stopPrank(); 71 | } 72 | 73 | function testUpgrade() public { 74 | // TODO: 75 | // Let's pretend that you are proxy owner 76 | // Try to upgrade the proxy to TradingCenterV2 77 | // And check if all state are correct (initialized, usdt address, usdc address) 78 | assertEq(proxyTradingCenter.initialized(), true); 79 | assertEq(address(proxyTradingCenter.usdc()), address(usdc)); 80 | assertEq(address(proxyTradingCenter.usdt()), address(usdt)); 81 | } 82 | 83 | function testRugPull() public { 84 | 85 | // TODO: 86 | // Let's pretend that you are proxy owner 87 | // Try to upgrade the proxy to TradingCenterV2 88 | // And empty users' usdc and usdt 89 | 90 | // Assert users's balances are 0 91 | assertEq(usdt.balanceOf(user1), 0); 92 | assertEq(usdc.balanceOf(user1), 0); 93 | assertEq(usdt.balanceOf(user2), 0); 94 | assertEq(usdc.balanceOf(user2), 0); 95 | } 96 | } -------------------------------------------------------------------------------- /section3/CompoundGovernancePractice/script/Deploy.s.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity 0.8.20; 3 | 4 | import "forge-std/Script.sol"; 5 | import { Unitroller } from "compound-protocol/contracts/Unitroller.sol"; 6 | import { Comptroller } from "compound-protocol/contracts/Comptroller.sol"; 7 | import { SimplePriceOracle } from "compound-protocol/contracts/SimplePriceOracle.sol"; 8 | import { WhitePaperInterestRateModel } from "compound-protocol/contracts/WhitePaperInterestRateModel.sol"; 9 | import { CErc20Delegate } from "compound-protocol/contracts/CErc20Delegate.sol"; 10 | import { CErc20Delegator } from "compound-protocol/contracts/CErc20Delegator.sol"; 11 | import { UnitrollerAdminStorage } from "compound-protocol/contracts/ComptrollerStorage.sol"; 12 | import { GovernorBravoDelegator } from "compound-protocol/contracts/Governance/GovernorBravoDelegator.sol"; 13 | import { GovernorBravoDelegateStorageV2 } from "compound-protocol/contracts/Governance/GovernorBravoInterfaces.sol"; 14 | import { Comp } from "compound-protocol/contracts/Governance/Comp.sol"; 15 | import { GovernorBravoDelegate } from "src/GovernorBravoDelegate.sol"; 16 | import { Timelock } from "src/Timelock.sol"; 17 | 18 | import { TestERC20 } from "src/TestERC20.sol"; 19 | 20 | contract TimelockWithTransferOwnership is Timelock { 21 | constructor(address admin, uint256 delay) Timelock(admin, delay) {} 22 | 23 | function transferOwnership(address newAdmin) external { 24 | require(msg.sender == admin, "Only owner"); 25 | 26 | admin = newAdmin; 27 | } 28 | } 29 | 30 | contract ComptrollerWithTransferOwnership is Comptroller { 31 | function transferOwnership(address newAdmin) external { 32 | require(msg.sender == admin, "Only owner"); 33 | 34 | admin = newAdmin; 35 | } 36 | } 37 | 38 | contract Deploy is Script { 39 | function run() public { 40 | uint256 privateKey = vm.envUint("PRIVATE_KEY"); 41 | vm.startBroadcast(privateKey); 42 | address payable sender = payable(vm.addr(privateKey)); 43 | 44 | // Comptroller 45 | ComptrollerWithTransferOwnership comptroller = new ComptrollerWithTransferOwnership(); 46 | Unitroller unitroller = new Unitroller(); 47 | unitroller._setPendingImplementation(address(comptroller)); 48 | comptroller._become(unitroller); 49 | 50 | // Oracle 51 | SimplePriceOracle oracle = new SimplePriceOracle(); 52 | Comptroller(address(unitroller))._setPriceOracle(oracle); 53 | 54 | // Interest rate model 55 | WhitePaperInterestRateModel irModel = new WhitePaperInterestRateModel(0, 0); 56 | 57 | TestERC20 token = new TestERC20(1000000 * 1e18, "Test Token", "TOKEN"); 58 | 59 | // CToken 60 | CErc20Delegate cERC20Delegate = new CErc20Delegate(); 61 | CErc20Delegator cToken = new CErc20Delegator( 62 | address(token), 63 | Comptroller(address(unitroller)), 64 | irModel, 65 | 1 * 10 ** 18, 66 | "Compound test token", 67 | "cToken0", 68 | 18, 69 | sender, 70 | address(cERC20Delegate), 71 | "" 72 | ); 73 | 74 | // Governance 75 | Comp comp = new Comp(sender); 76 | TimelockWithTransferOwnership timelock = new TimelockWithTransferOwnership(sender, 5 minutes); // Timelock delay measured in time 77 | GovernorBravoDelegate bravoImplementation = new GovernorBravoDelegate(); 78 | GovernorBravoDelegator bravo = new GovernorBravoDelegator( 79 | address(timelock), 80 | address(comp), 81 | sender, 82 | address(bravoImplementation), 83 | 100, // Voting period measured in blocks 84 | 25, // Voting delay measured in blocks 85 | 199 * 1e18 86 | ); 87 | 88 | timelock.transferOwnership(address(bravo)); 89 | ComptrollerWithTransferOwnership(address(unitroller)).transferOwnership(address(timelock)); 90 | 91 | console2.log("Unitroller =", address(unitroller)); 92 | console2.log("CToken =", address(cToken)); 93 | console2.log("GovernorBravo =", address(bravo)); 94 | console2.log("Timelock =", address(timelock)); 95 | console2.log("Comp =", address(comp)); 96 | 97 | vm.stopBroadcast(); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /section3/FlashSwapPractice/test/helper/FlashSwapSetUp.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | import "forge-std/Test.sol"; 5 | import { IUniswapV2Factory } from "v2-core/interfaces/IUniswapV2Factory.sol"; 6 | import { IUniswapV2Pair } from "v2-core/interfaces/IUniswapV2Pair.sol"; 7 | import { IUniswapV2Router01 } from "v2-periphery/interfaces/IUniswapV2Router01.sol"; 8 | import { TestWETH9 } from "../../contracts/test/TestWETH9.sol"; 9 | import { TestERC20 } from "../../contracts/test/TestERC20.sol"; 10 | 11 | contract FlashSwapSetUp is Test { 12 | TestWETH9 public weth; 13 | TestERC20 public usdc; 14 | IUniswapV2Factory public uniswapV2Factory; 15 | IUniswapV2Factory public sushiSwapV2Factory; 16 | IUniswapV2Router01 public uniswapV2Router; 17 | IUniswapV2Router01 public sushiSwapV2Router; 18 | IUniswapV2Pair public wethUsdcPool; 19 | IUniswapV2Pair public wethUsdcSushiPool; 20 | 21 | function setUp() public virtual { 22 | usdc = _create_erc20("USD Coin", "USDC", 6); 23 | weth = _create_weth9(); 24 | uniswapV2Factory = _create_uniswap_v2_factory(); 25 | sushiSwapV2Factory = _create_uniswap_v2_factory(); 26 | uniswapV2Router = _create_uniswap_v2_router(address(uniswapV2Factory), address(weth)); 27 | sushiSwapV2Router = _create_uniswap_v2_router(address(sushiSwapV2Factory), address(weth)); 28 | wethUsdcPool = _create_pool(address(uniswapV2Factory), address(weth), address(usdc)); 29 | wethUsdcSushiPool = _create_pool(address(sushiSwapV2Factory), address(weth), address(usdc)); 30 | 31 | vm.label(address(uniswapV2Factory), "UniswapV2Factory"); 32 | vm.label(address(sushiSwapV2Factory), "SushiSwapV2Factory"); 33 | vm.label(address(uniswapV2Router), "UniswapV2Router"); 34 | vm.label(address(sushiSwapV2Router), "SushiSwapV2Router"); 35 | vm.label(address(wethUsdcPool), "WethUsdcPool"); 36 | vm.label(address(wethUsdcSushiPool), "WethUsdcSushiPool"); 37 | vm.label(address(weth), "WETH9"); 38 | vm.label(address(usdc), "USDC"); 39 | } 40 | 41 | function _create_weth9() public returns (TestWETH9) { 42 | weth = new TestWETH9(); 43 | return weth; 44 | } 45 | 46 | function _create_erc20(string memory name, string memory symbol, uint8 decimals) public returns (TestERC20) { 47 | usdc = new TestERC20(name, symbol, decimals); 48 | return usdc; 49 | } 50 | 51 | function _create_pool(address factory, address tokenA, address tokenB) public returns (IUniswapV2Pair) { 52 | address pool = IUniswapV2Factory(factory).createPair(tokenA, tokenB); 53 | return IUniswapV2Pair(pool); 54 | } 55 | 56 | function _create_uniswap_v2_factory() internal returns (IUniswapV2Factory) { 57 | string memory path = string( 58 | abi.encodePacked(vm.projectRoot(), "/node_modules/@uniswap/v2-core/build/UniswapV2Factory.json") 59 | ); 60 | string memory artifact = vm.readFile(path); 61 | bytes memory creationCode = vm.parseBytes(abi.decode(vm.parseJson(artifact, ".bytecode"), (string))); 62 | creationCode = abi.encodePacked(creationCode, abi.encode(address(0))); 63 | address anotherAddress; 64 | 65 | assembly { 66 | anotherAddress := create(0, add(creationCode, 0x20), mload(creationCode)) 67 | } 68 | 69 | return IUniswapV2Factory(anotherAddress); 70 | } 71 | 72 | function _create_uniswap_v2_router(address factory, address weth9) internal returns (IUniswapV2Router01) { 73 | string memory path = string( 74 | abi.encodePacked(vm.projectRoot(), "/node_modules/@uniswap/v2-periphery/build/UniswapV2Router01.json") 75 | ); 76 | string memory artifact = vm.readFile(path); 77 | bytes memory creationCode = vm.parseBytes(abi.decode(vm.parseJson(artifact, ".bytecode"), (string))); 78 | 79 | creationCode = abi.encodePacked(creationCode, abi.encode(factory), abi.encode(weth9)); 80 | address anotherAddress; 81 | 82 | assembly { 83 | anotherAddress := create(0, add(creationCode, 0x20), mload(creationCode)) 84 | } 85 | 86 | return IUniswapV2Router01(anotherAddress); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /section3/UniswapV2Practice/test/UniswapV2Practice.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.13; 3 | 4 | import "forge-std/Test.sol"; 5 | import { IUniswapV2Router01 } from "v2-periphery/interfaces/IUniswapV2Router01.sol"; 6 | import { IUniswapV2Factory } from "v2-core/interfaces/IUniswapV2Factory.sol"; 7 | import { IUniswapV2Pair } from "v2-core/interfaces/IUniswapV2Pair.sol"; 8 | import { TestERC20 } from "../contracts/test/TestERC20.sol"; 9 | 10 | contract UniswapV2PracticeTest is Test { 11 | IUniswapV2Router01 public constant UNISWAP_V2_ROUTER = 12 | IUniswapV2Router01(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D); 13 | IUniswapV2Factory public constant UNISWAP_V2_FACTORY = 14 | IUniswapV2Factory(0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f); 15 | address public constant WETH9 = address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); 16 | 17 | TestERC20 public testUSDC; 18 | IUniswapV2Pair public WETHTestUSDCPair; 19 | address public taker = makeAddr("Taker"); 20 | address public maker = makeAddr("Maker"); 21 | 22 | function setUp() public { 23 | // fork block 24 | vm.createSelectFork("mainnet", 17254242); 25 | 26 | // deploy test USDC 27 | testUSDC = _create_erc20("Test USDC", "USDC", 6); 28 | 29 | // mint 100 ETH, 10000 USDC to maker 30 | deal(maker, 100 ether); 31 | deal(address(testUSDC), maker, 10000 * 10 ** testUSDC.decimals()); 32 | 33 | // mint 1 ETH, 100 USDC to taker 34 | deal(taker, 1 ether); 35 | deal(address(testUSDC), taker, 100 * 10 ** testUSDC.decimals()); 36 | 37 | // create ETH/USDC pair 38 | WETHTestUSDCPair = IUniswapV2Pair(UNISWAP_V2_FACTORY.createPair(address(WETH9), address(testUSDC))); 39 | 40 | vm.label(address(UNISWAP_V2_ROUTER), "UNISWAP_V2_ROUTER"); 41 | vm.label(address(UNISWAP_V2_FACTORY), "UNISWAP_V2_FACTORY"); 42 | vm.label(address(WETH9), "WETH9"); 43 | vm.label(address(testUSDC), "TestUSDC"); 44 | } 45 | 46 | // # Practice 1: maker add liquidity (100 ETH, 10000 USDC) 47 | function test_maker_addLiquidityETH() public { 48 | // Implement here 49 | 50 | // Checking 51 | IUniswapV2Pair wethUsdcPair = IUniswapV2Pair(UNISWAP_V2_FACTORY.getPair(address(WETH9), address(testUSDC))); 52 | (uint112 reserve0, uint112 reserve1, ) = wethUsdcPair.getReserves(); 53 | assertEq(reserve0, 10000 * 10 ** testUSDC.decimals()); 54 | assertEq(reserve1, 100 ether); 55 | } 56 | 57 | // # Practice 2: taker swap exact 1 ETH for testUSDC 58 | function test_taker_swapExactETHForTokens() public { 59 | uint256 takerOriginalUsdcBalance = testUSDC.balanceOf(taker); 60 | // Implement here 61 | 62 | // Checking 63 | // # Discussion 1: why 98715803 ? 64 | assertEq(testUSDC.balanceOf(taker) - takerOriginalUsdcBalance, 98715803); 65 | assertEq(taker.balance, 0); 66 | } 67 | 68 | // # Practice 3: taker swap exact 100 USDC for ETH 69 | function test_taker_swapExactTokensForETH() public { 70 | uint256 takerOriginalETHBalance = taker.balance; 71 | // Implement here 72 | 73 | // Checking 74 | // # Discussion 2: why 987158034397061298 ? 75 | assertEq(testUSDC.balanceOf(taker), 0); 76 | assertEq(taker.balance - takerOriginalETHBalance, 987158034397061298); 77 | } 78 | 79 | // # Practice 4: maker remove all liquidity 80 | function test_maker_removeLiquidityETH() public { 81 | // Implement here 82 | 83 | // Checking 84 | IUniswapV2Pair wethUsdcPair = IUniswapV2Pair(UNISWAP_V2_FACTORY.getPair(address(WETH9), address(testUSDC))); 85 | (uint112 reserve0, uint112 reserve1, ) = wethUsdcPair.getReserves(); 86 | assertEq(reserve0, 1); // MINIMUM_LIQUIDITY 87 | assertEq(reserve1, 100000000); // MINIMUM_LIQUIDITY 88 | assertEq(testUSDC.balanceOf(maker), 10000 * 10 ** testUSDC.decimals() - 1); 89 | assertEq(maker.balance, 100 ether - 100000000); 90 | } 91 | 92 | function _create_erc20(string memory name, string memory symbol, uint8 decimals) internal returns (TestERC20) { 93 | TestERC20 testERC20 = new TestERC20(name, symbol, decimals); 94 | return testERC20; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /section3/SimpleSwap/test/foundry/SimpleSwap.swap.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity 0.8.17; 3 | 4 | import { SimpleSwapSetUp } from "./helper/SimpleSwapSetUp.sol"; 5 | 6 | contract SimpleSwapSwapTest is SimpleSwapSetUp { 7 | function setUp() public override { 8 | super.setUp(); 9 | uint256 amountA = 100 * 10 ** tokenADecimals; 10 | uint256 amountB = 100 * 10 ** tokenBDecimals; 11 | vm.prank(maker); 12 | simpleSwap.addLiquidity(amountA, amountB); 13 | } 14 | 15 | function test_revert_when_tokenIn_is_not_tokenA_or_tokenB() public { 16 | address tokenIn = address(0); 17 | address tokenOut = address(tokenB); 18 | uint256 amountIn = 10 * 10 ** tokenADecimals; 19 | 20 | vm.prank(taker); 21 | vm.expectRevert("SimpleSwap: INVALID_TOKEN_IN"); 22 | simpleSwap.swap(tokenIn, tokenOut, amountIn); 23 | } 24 | 25 | function test_revert_when_tokenOut_is_not_tokenA_or_tokenB() public { 26 | address tokenIn = address(tokenA); 27 | address tokenOut = address(0); 28 | uint256 amountIn = 10 * 10 ** tokenADecimals; 29 | 30 | vm.prank(taker); 31 | vm.expectRevert("SimpleSwap: INVALID_TOKEN_OUT"); 32 | simpleSwap.swap(tokenIn, tokenOut, amountIn); 33 | } 34 | 35 | function test_revert_when_tokenIn_is_the_same_as_tokenOut() public { 36 | address tokenIn = address(tokenA); 37 | address tokenOut = address(tokenA); 38 | uint256 amountIn = 10 * 10 ** tokenADecimals; 39 | 40 | vm.prank(taker); 41 | vm.expectRevert("SimpleSwap: IDENTICAL_ADDRESS"); 42 | simpleSwap.swap(tokenIn, tokenOut, amountIn); 43 | } 44 | 45 | function test_revert_when_amountIn_is_zero() public { 46 | address tokenIn = address(tokenA); 47 | address tokenOut = address(tokenB); 48 | uint256 amountIn = 0; 49 | 50 | vm.prank(taker); 51 | vm.expectRevert("SimpleSwap: INSUFFICIENT_INPUT_AMOUNT"); 52 | simpleSwap.swap(tokenIn, tokenOut, amountIn); 53 | } 54 | 55 | function test_swap_from_tokenA_to_tokenB() public { 56 | address tokenIn = address(tokenA); 57 | address tokenOut = address(tokenB); 58 | uint256 amountIn = 100 * 10 ** tokenADecimals; 59 | uint256 amountOut = 50 * 10 ** tokenBDecimals; 60 | 61 | uint256 takerBalanceABefore = tokenA.balanceOf(taker); 62 | uint256 takerBalanceBBefore = tokenB.balanceOf(taker); 63 | uint256 simpleSwapBalanceABefore = tokenA.balanceOf(address(simpleSwap)); 64 | uint256 simpleSwapBalanceBBefore = tokenB.balanceOf(address(simpleSwap)); 65 | 66 | vm.startPrank(taker); 67 | vm.expectEmit(true, true, true, true); 68 | emit Swap(taker, tokenIn, tokenOut, amountIn, amountOut); 69 | simpleSwap.swap(tokenIn, tokenOut, amountIn); 70 | assertEq(tokenA.balanceOf(taker), takerBalanceABefore - amountIn); 71 | assertEq(tokenB.balanceOf(taker), takerBalanceBBefore + amountOut); 72 | assertEq(tokenA.balanceOf(address(simpleSwap)), simpleSwapBalanceABefore + amountIn); 73 | assertEq(tokenB.balanceOf(address(simpleSwap)), simpleSwapBalanceBBefore - amountOut); 74 | vm.stopPrank(); 75 | } 76 | 77 | function test_swap_from_tokenB_to_tokenA() public { 78 | address tokenIn = address(tokenB); 79 | address tokenOut = address(tokenA); 80 | uint256 amountIn = 100 * 10 ** tokenBDecimals; 81 | uint256 amountOut = 50 * 10 ** tokenADecimals; 82 | 83 | uint256 takerBalanceABefore = tokenA.balanceOf(taker); 84 | uint256 takerBalanceBBefore = tokenB.balanceOf(taker); 85 | uint256 simpleSwapBalanceABefore = tokenA.balanceOf(address(simpleSwap)); 86 | uint256 simpleSwapBalanceBBefore = tokenB.balanceOf(address(simpleSwap)); 87 | 88 | vm.startPrank(taker); 89 | vm.expectEmit(true, true, true, true); 90 | emit Swap(taker, tokenIn, tokenOut, amountIn, amountOut); 91 | simpleSwap.swap(tokenIn, tokenOut, amountIn); 92 | assertEq(tokenA.balanceOf(taker), takerBalanceABefore + amountOut); 93 | assertEq(tokenB.balanceOf(taker), takerBalanceBBefore - amountIn); 94 | assertEq(tokenA.balanceOf(address(simpleSwap)), simpleSwapBalanceABefore - amountOut); 95 | assertEq(tokenB.balanceOf(address(simpleSwap)), simpleSwapBalanceBBefore + amountIn); 96 | vm.stopPrank(); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /section2/ProxyPattern/test/MultiSigWallet.t.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: SEE LICENSE IN LICENSE 2 | pragma solidity ^0.8.17; 3 | 4 | import { Test } from "forge-std/Test.sol"; 5 | import { testERC20 } from "../src/test/testERC20.sol"; 6 | import { testERC721 } from "../src/test/testERC721.sol"; 7 | import { MultiSigWallet } from "../src/MultiSigWallet/MultiSigWallet.sol"; 8 | 9 | contract MultiSigWalletTest is Test { 10 | 11 | address public admin = makeAddr("admin"); 12 | address public alice = makeAddr("alice"); 13 | address public bob = makeAddr("bob"); 14 | address public carol = makeAddr("carol"); 15 | address public receiver = makeAddr("receiver"); 16 | 17 | MultiSigWallet public wallet; 18 | 19 | testERC20 public erc20; 20 | testERC721 public erc721; 21 | 22 | function setUp() public { 23 | vm.prank(admin); 24 | wallet = new MultiSigWallet([alice, bob, carol]); 25 | vm.deal(address(wallet), 100 ether); 26 | 27 | erc20 = new testERC20(); 28 | erc721 = new testERC721(); 29 | } 30 | 31 | function test_submitTransaction() public { 32 | // 1. prank as one of the owner 33 | vm.startPrank(alice); 34 | // 2. form a transfer transaction 35 | wallet.submitTransaction(bob, 10 ether, ""); 36 | vm.stopPrank(); 37 | 38 | // 3. check the transaction is submitted 39 | (address to, uint256 value, bytes memory data,,)= wallet.transactions(0); 40 | assertEq(to, bob); 41 | assertEq(value, 10 ether); 42 | assertEq(data.length, 0); 43 | } 44 | 45 | function test_confirmTransaction() public { 46 | // 1. prank as one of the owner 47 | vm.startPrank(alice); 48 | // 2. form a transfer transaction 49 | wallet.submitTransaction(bob, 10 ether, ""); 50 | vm.stopPrank(); 51 | // 3. bob confirm the transaction 52 | vm.prank(bob); 53 | wallet.confirmTransaction(); 54 | 55 | // 4. check the transaction is confirmed 56 | assertEq(wallet.isConfirmed(0, bob), true); 57 | } 58 | 59 | function test_executeTransaction() public { 60 | // 1. prank as one of the owner 61 | vm.startPrank(alice); 62 | // 2. form a transfer transaction 63 | wallet.submitTransaction(bob, 10 ether, ""); 64 | vm.stopPrank(); 65 | // 3. bob confirm the transaction 66 | vm.prank(bob); 67 | wallet.confirmTransaction(); 68 | // 4. carol confirm the transaction 69 | vm.prank(carol); 70 | wallet.confirmTransaction(); 71 | 72 | // 5. execute the transaction 73 | vm.prank(alice); 74 | wallet.executeTransaction(); 75 | 76 | // 6. check the transaction is executed 77 | (,,,bool executed,) = wallet.transactions(0); 78 | assertEq(executed, true); 79 | assertEq(bob.balance, 10 ether); 80 | assertEq(address(wallet).balance, 90 ether); 81 | } 82 | 83 | function test_mint_erc20() public { 84 | // 1. prank as one of the owner 85 | vm.startPrank(alice); 86 | // 2. form a transfer transaction 87 | bytes memory mintERC20 = abi.encodeWithSelector(testERC20.mint.selector, address(wallet), 10 ether); 88 | wallet.submitTransaction(address(erc20), 0, mintERC20); 89 | vm.stopPrank(); 90 | 91 | // 3. bob confirm the transaction 92 | vm.prank(bob); 93 | wallet.confirmTransaction(); 94 | 95 | // 4. carol confirm the transaction 96 | vm.prank(carol); 97 | wallet.confirmTransaction(); 98 | 99 | // 5. execute the transaction 100 | vm.prank(alice); 101 | wallet.executeTransaction(); 102 | 103 | // 6. check the transaction is executed 104 | (,,,bool executed,) = wallet.transactions(0); 105 | assertEq(executed, true); 106 | assertEq(erc20.balanceOf(address(wallet)), 10 ether); 107 | } 108 | 109 | function test_mint_erc721() public { 110 | // 1. prank as one of the owner 111 | vm.startPrank(alice); 112 | // 2. form a transfer transaction 113 | bytes memory mintERC721 = abi.encodeWithSelector(testERC721.mint.selector, address(wallet)); 114 | wallet.submitTransaction(address(erc721), 0, mintERC721); 115 | vm.stopPrank(); 116 | 117 | // 3. bob confirm the transaction 118 | vm.prank(bob); 119 | wallet.confirmTransaction(); 120 | 121 | // 4. carol confirm the transaction 122 | vm.prank(carol); 123 | wallet.confirmTransaction(); 124 | 125 | // 5. execute the transaction 126 | vm.prank(alice); 127 | wallet.executeTransaction(); 128 | 129 | // 6. check the transaction is executed 130 | (,,,bool executed,) = wallet.transactions(0); 131 | assertEq(executed, true); 132 | assertEq(erc721.balanceOf(address(wallet)), 1); 133 | } 134 | } -------------------------------------------------------------------------------- /section3/LatenRisk/src/challenge/ErrorReporter.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.20; 2 | 3 | // Copyright 2020 Compound Labs, Inc. 4 | 5 | contract ComptrollerErrorReporter { 6 | enum Error { 7 | NO_ERROR, 8 | UNAUTHORIZED, 9 | COMPTROLLER_MISMATCH, 10 | INSUFFICIENT_SHORTFALL, 11 | INSUFFICIENT_LIQUIDITY, 12 | INVALID_CLOSE_FACTOR, 13 | INVALID_COLLATERAL_FACTOR, 14 | INVALID_LIQUIDATION_INCENTIVE, 15 | MARKET_NOT_ENTERED, // no longer possible 16 | MARKET_NOT_LISTED, 17 | MARKET_ALREADY_LISTED, 18 | MATH_ERROR, 19 | NONZERO_BORROW_BALANCE, 20 | PRICE_ERROR, 21 | REJECTION, 22 | SNAPSHOT_ERROR, 23 | TOO_MANY_ASSETS, 24 | TOO_MUCH_REPAY 25 | } 26 | 27 | enum FailureInfo { 28 | ACCEPT_ADMIN_PENDING_ADMIN_CHECK, 29 | ACCEPT_PENDING_IMPLEMENTATION_ADDRESS_CHECK, 30 | EXIT_MARKET_BALANCE_OWED, 31 | EXIT_MARKET_REJECTION, 32 | SET_CLOSE_FACTOR_OWNER_CHECK, 33 | SET_CLOSE_FACTOR_VALIDATION, 34 | SET_COLLATERAL_FACTOR_OWNER_CHECK, 35 | SET_COLLATERAL_FACTOR_NO_EXISTS, 36 | SET_COLLATERAL_FACTOR_VALIDATION, 37 | SET_COLLATERAL_FACTOR_WITHOUT_PRICE, 38 | SET_IMPLEMENTATION_OWNER_CHECK, 39 | SET_LIQUIDATION_INCENTIVE_OWNER_CHECK, 40 | SET_LIQUIDATION_INCENTIVE_VALIDATION, 41 | SET_MAX_ASSETS_OWNER_CHECK, 42 | SET_PENDING_ADMIN_OWNER_CHECK, 43 | SET_PENDING_IMPLEMENTATION_OWNER_CHECK, 44 | SET_PRICE_ORACLE_OWNER_CHECK, 45 | SUPPORT_MARKET_EXISTS, 46 | SUPPORT_MARKET_OWNER_CHECK, 47 | SET_PAUSE_GUARDIAN_OWNER_CHECK 48 | } 49 | 50 | /** 51 | * @dev `error` corresponds to enum Error; `info` corresponds to enum FailureInfo, and `detail` is an arbitrary 52 | * contract-specific code that enables us to report opaque error codes from upgradeable contracts. 53 | * 54 | */ 55 | event Failure(uint256 error, uint256 info, uint256 detail); 56 | 57 | /** 58 | * @dev use this when reporting a known error from the money market or a non-upgradeable collaborator 59 | */ 60 | function fail(Error err, FailureInfo info) internal returns (uint256) { 61 | emit Failure(uint256(err), uint256(info), 0); 62 | 63 | return uint256(err); 64 | } 65 | 66 | /** 67 | * @dev use this when reporting an opaque error from an upgradeable collaborator contract 68 | */ 69 | function failOpaque(Error err, FailureInfo info, uint256 opaqueError) internal returns (uint256) { 70 | emit Failure(uint256(err), uint256(info), opaqueError); 71 | 72 | return uint256(err); 73 | } 74 | } 75 | 76 | contract TokenErrorReporter { 77 | uint256 public constant NO_ERROR = 0; // support legacy return codes 78 | 79 | error TransferComptrollerRejection(uint256 errorCode); 80 | error TransferNotAllowed(); 81 | error TransferNotEnough(); 82 | error TransferTooMuch(); 83 | 84 | error MintComptrollerRejection(uint256 errorCode); 85 | error MintFreshnessCheck(); 86 | 87 | error RedeemComptrollerRejection(uint256 errorCode); 88 | error RedeemFreshnessCheck(); 89 | error RedeemTransferOutNotPossible(); 90 | 91 | error BorrowComptrollerRejection(uint256 errorCode); 92 | error BorrowFreshnessCheck(); 93 | error BorrowCashNotAvailable(); 94 | 95 | error RepayBorrowComptrollerRejection(uint256 errorCode); 96 | error RepayBorrowFreshnessCheck(); 97 | 98 | error LiquidateComptrollerRejection(uint256 errorCode); 99 | error LiquidateFreshnessCheck(); 100 | error LiquidateCollateralFreshnessCheck(); 101 | error LiquidateAccrueBorrowInterestFailed(uint256 errorCode); 102 | error LiquidateAccrueCollateralInterestFailed(uint256 errorCode); 103 | error LiquidateLiquidatorIsBorrower(); 104 | error LiquidateCloseAmountIsZero(); 105 | error LiquidateCloseAmountIsUintMax(); 106 | error LiquidateRepayBorrowFreshFailed(uint256 errorCode); 107 | 108 | error LiquidateSeizeComptrollerRejection(uint256 errorCode); 109 | error LiquidateSeizeLiquidatorIsBorrower(); 110 | 111 | error AcceptAdminPendingAdminCheck(); 112 | 113 | error SetComptrollerOwnerCheck(); 114 | error SetPendingAdminOwnerCheck(); 115 | 116 | error SetReserveFactorAdminCheck(); 117 | error SetReserveFactorFreshCheck(); 118 | error SetReserveFactorBoundsCheck(); 119 | 120 | error AddReservesFactorFreshCheck(uint256 actualAddAmount); 121 | 122 | error ReduceReservesAdminCheck(); 123 | error ReduceReservesFreshCheck(); 124 | error ReduceReservesCashNotAvailable(); 125 | error ReduceReservesCashValidation(); 126 | 127 | error SetInterestRateModelOwnerCheck(); 128 | error SetInterestRateModelFreshCheck(); 129 | } 130 | -------------------------------------------------------------------------------- /section3/LatenRisk/src/challenge/JumpRateModel.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.8.10; 2 | 3 | // Copyright 2020 Compound Labs, Inc. 4 | 5 | import "./InterestRateModel.sol"; 6 | 7 | /** 8 | * @title Compound's JumpRateModel Contract 9 | * @author Compound 10 | */ 11 | contract JumpRateModel is InterestRateModel { 12 | event NewInterestParams( 13 | uint256 baseRatePerBlock, uint256 multiplierPerBlock, uint256 jumpMultiplierPerBlock, uint256 kink 14 | ); 15 | 16 | uint256 private constant BASE = 1e18; 17 | 18 | /** 19 | * @notice The approximate number of blocks per year that is assumed by the interest rate model 20 | */ 21 | uint256 public constant blocksPerYear = 2102400; 22 | 23 | /** 24 | * @notice The multiplier of utilization rate that gives the slope of the interest rate 25 | */ 26 | uint256 public multiplierPerBlock; 27 | 28 | /** 29 | * @notice The base interest rate which is the y-intercept when utilization rate is 0 30 | */ 31 | uint256 public baseRatePerBlock; 32 | 33 | /** 34 | * @notice The multiplierPerBlock after hitting a specified utilization point 35 | */ 36 | uint256 public jumpMultiplierPerBlock; 37 | 38 | /** 39 | * @notice The utilization point at which the jump multiplier is applied 40 | */ 41 | uint256 public kink; 42 | 43 | /** 44 | * @notice Construct an interest rate model 45 | * @param baseRatePerYear The approximate target base APR, as a mantissa (scaled by BASE) 46 | * @param multiplierPerYear The rate of increase in interest rate wrt utilization (scaled by BASE) 47 | * @param jumpMultiplierPerYear The multiplierPerBlock after hitting a specified utilization point 48 | * @param kink_ The utilization point at which the jump multiplier is applied 49 | */ 50 | constructor(uint256 baseRatePerYear, uint256 multiplierPerYear, uint256 jumpMultiplierPerYear, uint256 kink_) 51 | public 52 | { 53 | baseRatePerBlock = baseRatePerYear / blocksPerYear; 54 | multiplierPerBlock = multiplierPerYear / blocksPerYear; 55 | jumpMultiplierPerBlock = jumpMultiplierPerYear / blocksPerYear; 56 | kink = kink_; 57 | 58 | emit NewInterestParams(baseRatePerBlock, multiplierPerBlock, jumpMultiplierPerBlock, kink); 59 | } 60 | 61 | /** 62 | * @notice Calculates the utilization rate of the market: `borrows / (cash + borrows - reserves)` 63 | * @param cash The amount of cash in the market 64 | * @param borrows The amount of borrows in the market 65 | * @param reserves The amount of reserves in the market (currently unused) 66 | * @return The utilization rate as a mantissa between [0, BASE] 67 | */ 68 | function utilizationRate(uint256 cash, uint256 borrows, uint256 reserves) public pure returns (uint256) { 69 | // Utilization rate is 0 when there are no borrows 70 | if (borrows == 0) { 71 | return 0; 72 | } 73 | 74 | return borrows * BASE / (cash + borrows - reserves); 75 | } 76 | 77 | /** 78 | * @notice Calculates the current borrow rate per block, with the error code expected by the market 79 | * @param cash The amount of cash in the market 80 | * @param borrows The amount of borrows in the market 81 | * @param reserves The amount of reserves in the market 82 | * @return The borrow rate percentage per block as a mantissa (scaled by BASE) 83 | */ 84 | function getBorrowRate(uint256 cash, uint256 borrows, uint256 reserves) public view override returns (uint256) { 85 | uint256 util = utilizationRate(cash, borrows, reserves); 86 | 87 | if (util <= kink) { 88 | return (util * multiplierPerBlock / BASE) + baseRatePerBlock; 89 | } else { 90 | uint256 normalRate = (kink * multiplierPerBlock / BASE) + baseRatePerBlock; 91 | uint256 excessUtil = util - kink; 92 | return (excessUtil * jumpMultiplierPerBlock / BASE) + normalRate; 93 | } 94 | } 95 | 96 | /** 97 | * @notice Calculates the current supply rate per block 98 | * @param cash The amount of cash in the market 99 | * @param borrows The amount of borrows in the market 100 | * @param reserves The amount of reserves in the market 101 | * @param reserveFactorMantissa The current reserve factor for the market 102 | * @return The supply rate percentage per block as a mantissa (scaled by BASE) 103 | */ 104 | function getSupplyRate(uint256 cash, uint256 borrows, uint256 reserves, uint256 reserveFactorMantissa) 105 | public 106 | view 107 | override 108 | returns (uint256) 109 | { 110 | uint256 oneMinusReserveFactor = BASE - reserveFactorMantissa; 111 | uint256 borrowRate = getBorrowRate(cash, borrows, reserves); 112 | uint256 rateToPool = borrowRate * oneMinusReserveFactor / BASE; 113 | return utilizationRate(cash, borrows, reserves) * rateToPool / BASE; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /section3/LatenRisk/src/challenge/Challenge.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: UNLICENSED 2 | pragma solidity ^0.8.20; 3 | 4 | import "./Comptroller.sol"; 5 | import "./JumpRateModel.sol"; 6 | import "./CErc20Immutable.sol"; 7 | import "./AppworksToken.sol"; 8 | import "./SimpleOracle.sol"; 9 | 10 | contract Deployer { 11 | function create(address underlying_, 12 | ComptrollerInterface comptroller_, 13 | InterestRateModel interestRateModel_, 14 | uint256 initialExchangeRateMantissa_, 15 | string memory name_, 16 | string memory symbol_) external returns (CErc20Immutable) { 17 | return new CErc20Immutable(underlying_, comptroller_, interestRateModel_, initialExchangeRateMantissa_, name_, symbol_, uint8(18), payable(msg.sender)); 18 | } 19 | 20 | function create2(string memory a, string memory b) external returns (AppworksToken) { 21 | return new AppworksToken(a, b, msg.sender); 22 | } 23 | } 24 | 25 | 26 | contract Challenge { 27 | uint256 public seed; 28 | 29 | Comptroller public comptroller; 30 | JumpRateModel public rateModel; 31 | 32 | AppworksToken public CUSD; 33 | AppworksToken public CStUSD; 34 | AppworksToken public CETH; 35 | AppworksToken public CWETH; 36 | 37 | CErc20Immutable public CCUSD; 38 | CErc20Immutable public CCStUSD; 39 | CErc20Immutable public CCETH; 40 | CErc20Immutable public CCWETH; 41 | 42 | SimpleOracle public oracle; 43 | 44 | bool public initialized; 45 | 46 | function init(uint256 _seed, address _caller, address deployer) external { 47 | require(!initialized); 48 | initialized = true; 49 | Deployer dd = Deployer(address(deployer)); 50 | 51 | seed = _seed; 52 | CUSD = dd.create2("CUSD", "cUSD"); 53 | CStUSD = dd.create2("CStUSD", "cStUSD"); 54 | CETH = dd.create2("CETH", "cETH"); 55 | CWETH = dd.create2("CWETH", "cWETH"); 56 | 57 | CUSD.mint(address(this), 10000 ether); 58 | CStUSD.mint(address(this), 10000 ether); 59 | CETH.mint(address(this), 10000 ether); 60 | CWETH.mint(address(this), 10000 ether); 61 | 62 | rateModel = new JumpRateModel(2102400, 2102400, 2102400, type(uint256).max); 63 | comptroller = new Comptroller(); 64 | oracle = new SimpleOracle(); 65 | 66 | CCUSD = 67 | dd.create(address(CUSD), ComptrollerInterface(address(comptroller)), InterestRateModel(address(rateModel)), 1e18, "CCUSD", "cCUSD"); 68 | CCStUSD = 69 | dd.create(address(CStUSD), ComptrollerInterface(address(comptroller)), InterestRateModel(address(rateModel)), 1e18, "CCStUSD", "cCStUSD"); 70 | CCETH = 71 | dd.create(address(CETH), ComptrollerInterface(address(comptroller)), InterestRateModel(address(rateModel)), 1e18, "CCETH", "cCETH"); 72 | CCWETH = 73 | dd.create(address(CWETH), ComptrollerInterface(address(comptroller)), InterestRateModel(address(rateModel)), 1e18, "CCWETH", "cCWETH"); 74 | 75 | CUSD.approve(address(CCUSD), type(uint256).max); 76 | CStUSD.approve(address(CCStUSD), type(uint256).max); 77 | CETH.approve(address(CCETH), type(uint256).max); 78 | CWETH.approve(address(CCWETH), type(uint256).max); 79 | 80 | comptroller._supportMarket(CToken(CCUSD)); 81 | comptroller._supportMarket(CToken(CCStUSD)); 82 | comptroller._supportMarket(CToken(CCETH)); 83 | comptroller._supportMarket(CToken(CCWETH)); 84 | 85 | oracle.setPrice(address(CCUSD), 1e18); 86 | oracle.setPrice(address(CCStUSD), 1e18); 87 | oracle.setPrice(address(CCETH), 200e18); 88 | oracle.setPrice(address(CCWETH), 200e18); 89 | 90 | comptroller._setPriceOracle(PriceOracle(address(oracle))); 91 | 92 | comptroller._setCollateralFactor(CToken(CCUSD), 0.9 ether); 93 | comptroller._setCollateralFactor(CToken(CCStUSD), 0.9 ether); 94 | comptroller._setCollateralFactor(CToken(CCETH), 0.7 ether); 95 | comptroller._setCollateralFactor(CToken(CCWETH), 0.7 ether); 96 | 97 | comptroller._setCloseFactor(0.5 ether); 98 | comptroller._setLiquidationIncentive(1 ether); 99 | 100 | CCUSD.mint(10000 ether); 101 | CCStUSD.mint(10000 ether); 102 | CCETH.mint(10000 ether); 103 | // CWETH.mint(10000 ether); 104 | 105 | CWETH.mint(_caller, 10000 ether); 106 | } 107 | 108 | function isSolved() external view returns (bool) { 109 | address target = address(uint160(seed)); 110 | 111 | uint256 initUSD = 10000 ether * oracle.getUnderlyingPrice(CToken(CCWETH)) / 1e18; 112 | 113 | uint256 profitUSD = ( 114 | CUSD.balanceOf(target) * oracle.getUnderlyingPrice(CToken(CCUSD)) 115 | + CStUSD.balanceOf(target) * oracle.getUnderlyingPrice(CToken(CCStUSD)) 116 | + CETH.balanceOf(target) * oracle.getUnderlyingPrice(CToken(CCETH)) 117 | + CWETH.balanceOf(target) * oracle.getUnderlyingPrice(CToken(CCWETH)) 118 | ) / 1e18 - initUSD; 119 | 120 | require(profitUSD > 10000 ether * 200); 121 | 122 | return true; 123 | } 124 | } 125 | --------------------------------------------------------------------------------