├── 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 | |
23 | 區塊鏈基礎知識
24 | |
25 |
26 | 根據線上已有的學習資源,了解基本的區塊鏈及以太坊相關概念(合約、Solidity 語法、交易、錢包、節點等)
27 | |
28 |
29 |
30 | |
31 | 上手實作及問題思考
32 | |
33 | 透過實戰演練及特定主題研究,對區塊鏈有更深一層的了解。 |
34 |
35 |
36 | |
37 | 經典 DeFi 機制的賞析
38 | |
39 |
40 | 了解經典的 DeFi 基礎建設,並能夠複製、整合、加以改進,由學習層面進階到實際應用層面
41 | |
42 |
43 |
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 |
--------------------------------------------------------------------------------