├── README.md
├── berapolia-tutorial
├── README.md
├── contracts
│ ├── Token.vy
│ ├── imports
│ │ ├── ecdsa.vy
│ │ ├── eip712_domain_separator.vy
│ │ ├── message_hash_utils.vy
│ │ └── ownable.vy
│ ├── interfaces
│ │ ├── IERC1155.vyi
│ │ ├── IERC1155MetadataURI.vyi
│ │ ├── IERC1155Receiver.vyi
│ │ ├── IERC20Permit.vyi
│ │ ├── IERC4906.vyi
│ │ ├── IERC5267.vyi
│ │ ├── IERC721Enumerable.vyi
│ │ ├── IERC721Metadata.vyi
│ │ ├── IERC721Permit.vyi
│ │ └── IERC721Receiver.vyi
│ └── snapshot
│ │ ├── step-01
│ │ └── Token.vy
│ │ ├── step-02
│ │ ├── Minter.vy
│ │ └── Token.vy
│ │ ├── step-03
│ │ ├── Minter.vy
│ │ └── Token.vy
│ │ ├── step-04
│ │ ├── Minter.vy
│ │ └── Token.vy
│ │ ├── step-05
│ │ ├── Minter.vy
│ │ └── Token.vy
│ │ ├── step-06
│ │ ├── Minter.vy
│ │ └── Token.vy
│ │ ├── step-07
│ │ ├── Minter.vy
│ │ └── Token.vy
│ │ ├── step-08
│ │ ├── Minter.vy
│ │ └── Token.vy
│ │ ├── step-09
│ │ ├── Minter.vy
│ │ └── Token.vy
│ │ ├── step-10
│ │ ├── Minter.vy
│ │ └── Token.vy
│ │ ├── step-11
│ │ ├── Minter.vy
│ │ └── Token.vy
│ │ └── step-12
│ │ ├── Minter.vy
│ │ └── Token.vy
├── pyproject.toml
├── requirements.in
├── requirements.txt
├── scripts
│ ├── deploy_curve_pool.ipynb
│ └── deploy_token.ipynb
└── tests
│ ├── conftest.py
│ └── test_minter.py
├── crvusd
├── arb
│ ├── .gitattributes
│ ├── .gitignore
│ ├── contracts
│ │ └── Arbitrage.vy
│ ├── scripts
│ │ └── arbitrage.py
│ └── settings.py
└── peg-keeper
│ ├── brownie-config.yaml
│ └── scripts
│ └── pegkeeper.py
├── lesson-01-setup
├── README.md
├── crvUSDBull.jpg
└── vyper-token
│ ├── .gitattributes
│ ├── .github
│ └── workflows
│ │ └── main.yaml
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── contracts
│ ├── Token.py
│ └── Token.vy
│ ├── requirements.txt
│ ├── scripts
│ └── token.py
│ └── tests
│ ├── conftest.py
│ ├── test_approve.py
│ ├── test_transfer.py
│ └── test_transferFrom.py
├── lesson-02-contract
├── README.md
├── solution.diff
├── solved
│ ├── .gitattributes
│ ├── .github
│ │ └── workflows
│ │ │ └── main.yaml
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── contracts
│ │ ├── Minter.bak
│ │ ├── Minter.vy
│ │ └── Token.vy
│ ├── requirements.txt
│ ├── scripts
│ │ └── token.py
│ └── tests
│ │ ├── conftest.py
│ │ ├── test_approve.py
│ │ ├── test_minter.py
│ │ ├── test_transfer.py
│ │ └── test_transferFrom.py
├── tests.diff
└── unsolved
│ ├── .gitattributes
│ ├── .github
│ └── workflows
│ │ └── main.yaml
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── contracts
│ └── Token.vy
│ ├── requirements.txt
│ ├── scripts
│ └── token.py
│ └── tests
│ ├── conftest.py
│ ├── test_approve.py
│ ├── test_minter.py
│ ├── test_transfer.py
│ └── test_transferFrom.py
├── lesson-03-state-variables
├── README.md
├── solution.diff
├── solved
│ ├── .gitattributes
│ ├── .github
│ │ └── workflows
│ │ │ └── main.yaml
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── contracts
│ │ ├── Minter.vy
│ │ └── Token.vy
│ ├── requirements.txt
│ ├── scripts
│ │ └── token.py
│ └── tests
│ │ ├── conftest.py
│ │ ├── test_approve.py
│ │ ├── test_minter.py
│ │ ├── test_transfer.py
│ │ └── test_transferFrom.py
├── tests.diff
└── unsolved
│ ├── .gitattributes
│ ├── .github
│ └── workflows
│ │ └── main.yaml
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── contracts
│ ├── Minter.vy
│ └── Token.vy
│ ├── requirements.txt
│ ├── scripts
│ └── token.py
│ └── tests
│ ├── conftest.py
│ ├── test_approve.py
│ ├── test_minter.py
│ ├── test_transfer.py
│ └── test_transferFrom.py
├── lesson-04-assert
├── README.md
├── solution.diff
├── solved
│ ├── .gitattributes
│ ├── .github
│ │ └── workflows
│ │ │ └── main.yaml
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── contracts
│ │ ├── Minter.vy
│ │ └── Token.vy
│ ├── requirements.txt
│ ├── scripts
│ │ └── token.py
│ └── tests
│ │ ├── conftest.py
│ │ ├── test_approve.py
│ │ ├── test_minter.py
│ │ ├── test_transfer.py
│ │ └── test_transferFrom.py
├── tests.diff
└── unsolved
│ ├── .gitattributes
│ ├── .github
│ └── workflows
│ │ └── main.yaml
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── contracts
│ ├── Minter.vy
│ └── Token.vy
│ ├── requirements.txt
│ ├── scripts
│ └── token.py
│ └── tests
│ ├── conftest.py
│ ├── test_approve.py
│ ├── test_minter.py
│ ├── test_transfer.py
│ └── test_transferFrom.py
├── lesson-05-interface
├── README.md
├── solution.diff
├── solved
│ ├── .gitattributes
│ ├── .github
│ │ └── workflows
│ │ │ └── main.yaml
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── contracts
│ │ ├── Minter.vy
│ │ └── Token.vy
│ ├── requirements.txt
│ ├── scripts
│ │ └── token.py
│ └── tests
│ │ ├── conftest.py
│ │ ├── test_approve.py
│ │ ├── test_minter.py
│ │ ├── test_transfer.py
│ │ └── test_transferFrom.py
├── tests.diff
└── unsolved
│ ├── .gitattributes
│ ├── .github
│ └── workflows
│ │ └── main.yaml
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── contracts
│ ├── Minter.vy
│ └── Token.vy
│ ├── requirements.txt
│ ├── scripts
│ └── token.py
│ └── tests
│ ├── conftest.py
│ ├── test_approve.py
│ ├── test_minter.py
│ ├── test_transfer.py
│ └── test_transferFrom.py
├── lesson-06-import
├── README.md
├── solution.diff
├── solved
│ ├── .gitattributes
│ ├── .github
│ │ └── workflows
│ │ │ └── main.yaml
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── contracts
│ │ ├── Minter.vy
│ │ └── Token.vy
│ ├── requirements.txt
│ ├── scripts
│ │ └── token.py
│ └── tests
│ │ ├── conftest.py
│ │ ├── test_approve.py
│ │ ├── test_minter.py
│ │ ├── test_transfer.py
│ │ └── test_transferFrom.py
├── tests.diff
└── unsolved
│ ├── .gitattributes
│ ├── .github
│ └── workflows
│ │ └── main.yaml
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── contracts
│ ├── Minter.vy
│ └── Token.vy
│ ├── requirements.txt
│ ├── scripts
│ └── token.py
│ └── tests
│ ├── .conftest.py.swp
│ ├── .test_minter.py.swp
│ ├── conftest.py
│ ├── test_approve.py
│ ├── test_minter.py
│ ├── test_transfer.py
│ └── test_transferFrom.py
├── lesson-07-scope
├── README.md
├── solution.diff
├── solved
│ ├── .gitattributes
│ ├── .github
│ │ └── workflows
│ │ │ └── main.yaml
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── brownie_config.yaml
│ ├── contracts
│ │ ├── Minter.vy
│ │ └── Token.vy
│ ├── requirements.txt
│ ├── scripts
│ │ └── token.py
│ └── tests
│ │ ├── conftest.py
│ │ ├── test_approve.py
│ │ ├── test_lend.py
│ │ ├── test_minter.py
│ │ ├── test_transfer.py
│ │ └── test_transferFrom.py
├── tests.diff
└── unsolved
│ ├── .gitattributes
│ ├── .github
│ └── workflows
│ │ └── main.yaml
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── brownie_config.yaml
│ ├── contracts
│ ├── Minter.vy
│ └── Token.vy
│ ├── requirements.txt
│ ├── scripts
│ └── token.py
│ └── tests
│ ├── conftest.py
│ ├── test_approve.py
│ ├── test_lend.py
│ ├── test_minter.py
│ ├── test_transfer.py
│ └── test_transferFrom.py
├── lesson-08-math
├── README.md
├── solution.diff
├── solved
│ ├── .gitattributes
│ ├── .github
│ │ └── workflows
│ │ │ └── main.yaml
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── brownie-config.yaml
│ ├── contracts
│ │ ├── Minter.vy
│ │ ├── TestMath.vy
│ │ └── Token.vy
│ ├── requirements.txt
│ ├── scripts
│ │ └── token.py
│ └── tests
│ │ ├── conftest.py
│ │ ├── test_approve.py
│ │ ├── test_lend.py
│ │ ├── test_minter.py
│ │ ├── test_transfer.py
│ │ └── test_transferFrom.py
├── tests.diff
└── unsolved
│ ├── .gitattributes
│ ├── .github
│ └── workflows
│ │ └── main.yaml
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── brownie-config.yaml
│ ├── contracts
│ ├── Minter.vy
│ ├── TestMath.vy
│ └── Token.vy
│ ├── requirements.txt
│ ├── scripts
│ └── token.py
│ └── tests
│ ├── conftest.py
│ ├── test_approve.py
│ ├── test_lend.py
│ ├── test_minter.py
│ ├── test_transfer.py
│ └── test_transferFrom.py
├── lesson-09-struct
├── README.md
├── solution.diff
├── solved
│ ├── .gitattributes
│ ├── .github
│ │ └── workflows
│ │ │ └── main.yaml
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── brownie-config.yaml
│ ├── contracts
│ │ ├── Minter.vy
│ │ └── Token.vy
│ ├── requirements.txt
│ ├── scripts
│ │ └── token.py
│ └── tests
│ │ ├── conftest.py
│ │ ├── test_approve.py
│ │ ├── test_lend.py
│ │ ├── test_minter.py
│ │ ├── test_transfer.py
│ │ ├── test_transferFrom.py
│ │ └── test_withdraw.py
├── tests.diff
└── unsolved
│ ├── .gitattributes
│ ├── .github
│ └── workflows
│ │ └── main.yaml
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── brownie-config.yaml
│ ├── contracts
│ ├── Minter.vy
│ └── Token.vy
│ ├── requirements.txt
│ ├── scripts
│ └── token.py
│ └── tests
│ ├── conftest.py
│ ├── test_approve.py
│ ├── test_lend.py
│ ├── test_minter.py
│ ├── test_transfer.py
│ ├── test_transferFrom.py
│ └── test_withdraw.py
├── lesson-10-hashmap
├── README.md
├── solution.diff
├── solved
│ ├── .gitattributes
│ ├── .github
│ │ └── workflows
│ │ │ └── main.yaml
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── brownie-config.yaml
│ ├── contracts
│ │ ├── Minter.vy
│ │ └── Token.vy
│ ├── requirements.txt
│ ├── scripts
│ │ └── token.py
│ └── tests
│ │ ├── conftest.py
│ │ ├── test_approve.py
│ │ ├── test_lend.py
│ │ ├── test_minter.py
│ │ ├── test_transfer.py
│ │ ├── test_transferFrom.py
│ │ └── test_withdraw.py
├── tests.diff
└── unsolved
│ ├── .gitattributes
│ ├── .github
│ └── workflows
│ │ └── main.yaml
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── brownie-config.yaml
│ ├── contracts
│ ├── Minter.vy
│ └── Token.vy
│ ├── requirements.txt
│ ├── scripts
│ └── token.py
│ └── tests
│ ├── conftest.py
│ ├── test_approve.py
│ ├── test_lend.py
│ ├── test_minter.py
│ ├── test_transfer.py
│ ├── test_transferFrom.py
│ └── test_withdraw.py
├── lesson-11-event
├── README.md
├── solution.diff
├── solved
│ ├── .gitattributes
│ ├── .github
│ │ └── workflows
│ │ │ └── main.yaml
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── brownie-config.yaml
│ ├── contracts
│ │ ├── Minter.vy
│ │ └── Token.vy
│ ├── requirements.txt
│ ├── scripts
│ │ └── token.py
│ └── tests
│ │ ├── conftest.py
│ │ ├── test_approve.py
│ │ ├── test_lend.py
│ │ ├── test_minter.py
│ │ ├── test_transfer.py
│ │ ├── test_transferFrom.py
│ │ └── test_withdraw.py
├── tests.diff
└── unsolved
│ ├── .gitattributes
│ ├── .github
│ └── workflows
│ │ └── main.yaml
│ ├── .gitignore
│ ├── LICENSE
│ ├── README.md
│ ├── brownie-config.yaml
│ ├── contracts
│ ├── Minter.vy
│ └── Token.vy
│ ├── requirements.txt
│ ├── scripts
│ └── token.py
│ └── tests
│ ├── conftest.py
│ ├── test_approve.py
│ ├── test_lend.py
│ ├── test_minter.py
│ ├── test_transfer.py
│ ├── test_transferFrom.py
│ └── test_withdraw.py
└── lesson-12-dynarray
├── README.md
├── solution.diff
├── solved
├── .DS_Store
├── .gitattributes
├── .github
│ └── workflows
│ │ └── main.yaml
├── .gitignore
├── LICENSE
├── README.md
├── brownie-config.yaml
├── contracts
│ ├── Minter.vy
│ └── Token.vy
├── requirements.txt
├── scripts
│ └── token.py
└── tests
│ ├── conftest.py
│ ├── test_approve.py
│ ├── test_lend.py
│ ├── test_minter.py
│ ├── test_transfer.py
│ ├── test_transferFrom.py
│ └── test_withdraw.py
├── tests.diff
└── unsolved
├── .gitattributes
├── .github
└── workflows
│ └── main.yaml
├── .gitignore
├── LICENSE
├── README.md
├── brownie-config.yaml
├── contracts
├── Minter.vy
└── Token.vy
├── requirements.txt
├── scripts
└── token.py
└── tests
├── conftest.py
├── test_approve.py
├── test_lend.py
├── test_minter.py
├── test_transfer.py
├── test_transferFrom.py
└── test_withdraw.py
/berapolia-tutorial/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Berapolia Tutorial - April 2
4 |
5 | Presentation:
6 | * [Presentation](https://docs.google.com/presentation/d/11TzJfJhBc1zc0HbI46zP7Znes1Kyqe-7ShAAHmEEues/edit?usp=sharing)
7 |
8 | Repositories:
9 | * [Boa](https://github.com/vyperlang/titanoboa/)
10 | * [Snekmate](https://github.com/pcaversaccio/snekmate)
11 | * [Curve Lite](https://github.com/curvefi/curve-core)
12 | * [Mamushi](https://github.com/benber86/mamushi)
13 | * [Ruff](https://github.com/astral-sh/ruff)
14 |
15 | Links:
16 | * [Vyper Style](https://docs.vyperlang.org/en/stable/style-guide.html)
17 | * [Curve Style](https://github.com/CurveDocs/curve-docs/pull/324)
18 | * [Ape Academy](https://academy.apeworx.io/)
19 |
--------------------------------------------------------------------------------
/berapolia-tutorial/contracts/interfaces/IERC721Receiver.vyi:
--------------------------------------------------------------------------------
1 | # pragma version ~=0.4.1
2 | """
3 | @title EIP-721 Token Receiver Interface Definition
4 | @custom:contract-name IERC721Receiver
5 | @license GNU Affero General Public License v3.0 only
6 | @author pcaversaccio
7 | @notice The interface definition for any contract
8 | that wants to support safe transfers from
9 | ERC-721 asset contracts. For more details,
10 | please refer to:
11 | https://eips.ethereum.org/EIPS/eip-721#specification.
12 |
13 | On how to use interfaces in Vyper, please visit:
14 | https://vyper.readthedocs.io/en/latest/interfaces.html#interfaces.
15 | """
16 |
17 |
18 | @external
19 | def onERC721Received(
20 | _operator: address, _from: address, _tokenId: uint256, _data: Bytes[1_024]
21 | ) -> bytes4:
22 | """
23 | @dev Whenever a `_tokenId` token is transferred to
24 | this contract via `safeTransferFrom` by
25 | `_operator` from `_from`, this function is called.
26 | @notice It must return its function selector to
27 | confirm the token transfer. If any other value
28 | is returned or the interface is not implemented
29 | by the recipient, the transfer will be reverted.
30 | @param _operator The 20-byte address which called
31 | the `safeTransferFrom` function.
32 | @param _from The 20-byte address which previously
33 | owned the token.
34 | @param _tokenId The 32-byte identifier of the token.
35 | @param _data The maximum 1,024-byte additional data
36 | with no specified format.
37 | @return bytes4 The 4-byte function selector of `onERC721Received`.
38 | """
39 | ...
40 |
--------------------------------------------------------------------------------
/berapolia-tutorial/contracts/snapshot/step-02/Minter.vy:
--------------------------------------------------------------------------------
1 | # @version 0.4.1
2 |
3 | # pragma optimize codesize
4 |
5 |
6 | """
7 | @title CRV Stablecoin Minter
8 | @license MIT
9 | @author Curve Finance
10 | @notice Mint a stablecoin backed by $CRV
11 | @dev Sample implementation of an ERC-20 backed stablecoin
12 | """
13 |
14 |
15 | @external
16 | def mint():
17 | """
18 | @notice Mint a quantity of stablecoins
19 | @dev This function is intentionally not implemented!
20 | """
21 | pass
22 |
--------------------------------------------------------------------------------
/berapolia-tutorial/contracts/snapshot/step-03/Minter.vy:
--------------------------------------------------------------------------------
1 | # @version 0.4.1
2 |
3 | # pragma optimize codesize
4 |
5 |
6 | """
7 | @title CRV Stablecoin Minter
8 | @license MIT
9 | @author Curve Finance
10 | @notice Mint a stablecoin backed by $CRV
11 | @dev Sample implementation of an ERC-20 backed stablecoin
12 | """
13 |
14 |
15 | @external
16 | def mint():
17 | """
18 | @notice Mint a quantity of stablecoins
19 | @dev This function is intentionally not implemented!
20 | """
21 | pass
22 |
--------------------------------------------------------------------------------
/berapolia-tutorial/contracts/snapshot/step-04/Minter.vy:
--------------------------------------------------------------------------------
1 | # @version 0.4.1
2 |
3 | # pragma optimize codesize
4 |
5 |
6 | """
7 | @title CRV Stablecoin Minter
8 | @license MIT
9 | @author Curve Finance
10 | @notice Mint a stablecoin backed by $CRV
11 | @dev Sample implementation of an ERC-20 backed stablecoin
12 | """
13 |
14 |
15 | @external
16 | def mint():
17 | """
18 | @notice Mint a quantity of stablecoins
19 | @dev This function is intentionally not implemented!
20 | """
21 | pass
22 |
--------------------------------------------------------------------------------
/berapolia-tutorial/contracts/snapshot/step-05/Minter.vy:
--------------------------------------------------------------------------------
1 | # @version 0.4.1
2 |
3 | # pragma optimize codesize
4 |
5 |
6 | """
7 | @title CRV Stablecoin Minter
8 | @license MIT
9 | @author Curve Finance
10 | @notice Mint a stablecoin backed by $CRV
11 | @dev Sample implementation of an ERC-20 backed stablecoin
12 | """
13 |
14 |
15 | interface Token:
16 | def mint(owner: address, amount: uint256): nonpayable
17 |
18 |
19 | stablecoin: public(Token)
20 |
21 |
22 | @deploy
23 | def __init__(stablecoin_addr: address):
24 | self.stablecoin = Token(stablecoin_addr)
25 |
26 |
27 | @external
28 | def mint(amount: uint256):
29 | """
30 | @notice Mint a quantity of stablecoins
31 | @param amount Amount of stablecoins to mint
32 | """
33 | extcall self.stablecoin.mint(msg.sender, amount)
34 |
--------------------------------------------------------------------------------
/berapolia-tutorial/contracts/snapshot/step-06/Minter.vy:
--------------------------------------------------------------------------------
1 | # @version 0.4.1
2 |
3 | # pragma optimize codesize
4 |
5 |
6 | """
7 | @title CRV Stablecoin Minter
8 | @license MIT
9 | @author Curve Finance
10 | @notice Mint a stablecoin backed by $CRV
11 | @dev Sample implementation of an ERC-20 backed stablecoin
12 | """
13 |
14 | import Token as token
15 | from ethereum.ercs import IERC20
16 |
17 | # crvUSD
18 | stablecoin: public(token.__interface__)
19 |
20 | # CRV
21 | lending_token: public(IERC20)
22 |
23 |
24 | @deploy
25 | def __init__(stablecoin_addr: address, lending_token_addr: address):
26 | self.stablecoin = token.__at__(stablecoin_addr)
27 | self.lending_token = IERC20(lending_token_addr)
28 |
29 |
30 | @external
31 | def mint(amount: uint256):
32 | """
33 | @notice Mint a quantity of stablecoins
34 | @param amount Amount of stablecoins to mint
35 | """
36 | assert (
37 | staticcall self.lending_token.allowance(msg.sender, self) >= amount
38 | ) # dev: "!approval"
39 | assert (
40 | staticcall self.lending_token.balanceOf(msg.sender) >= amount
41 | ) # dev: "!balance"
42 | extcall self.lending_token.transferFrom(msg.sender, self, amount)
43 | extcall self.stablecoin.mint(msg.sender, amount)
44 |
--------------------------------------------------------------------------------
/berapolia-tutorial/pyproject.toml:
--------------------------------------------------------------------------------
1 | [tool.ruff]
2 | line-length = 100
3 |
--------------------------------------------------------------------------------
/berapolia-tutorial/requirements.in:
--------------------------------------------------------------------------------
1 | vyper>=0.4.1
2 | titanoboa>=0.2.6
3 | web3
4 |
5 | # Style
6 | mamushi>=0.0.4
7 | ruff>=0.11.2
8 |
--------------------------------------------------------------------------------
/berapolia-tutorial/tests/test_minter.py:
--------------------------------------------------------------------------------
1 | import boa
2 |
3 | def test_minter_exists(minter, stablecoin):
4 | assert minter.stablecoin() == stablecoin.address
5 |
--------------------------------------------------------------------------------
/crvusd/arb/.gitattributes:
--------------------------------------------------------------------------------
1 | *.sol linguist-language=Solidity
2 | *.vy linguist-language=Python
3 |
--------------------------------------------------------------------------------
/crvusd/arb/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 | .env
3 | .history
4 | .hypothesis/
5 | build/
6 | reports/
7 |
--------------------------------------------------------------------------------
/crvusd/peg-keeper/brownie-config.yaml:
--------------------------------------------------------------------------------
1 | autofetch_sources: true
2 | networks:
3 | default: mainnet-fork
4 |
--------------------------------------------------------------------------------
/crvusd/peg-keeper/scripts/pegkeeper.py:
--------------------------------------------------------------------------------
1 | import warnings
2 |
3 | import requests
4 | from brownie import accounts, history, Contract
5 |
6 | warnings.filterwarnings("ignore", category=DeprecationWarning, module="eth_abi.codec")
7 |
8 |
9 | def main():
10 | pegkeepers = [
11 | "0xaA346781dDD7009caa644A4980f044C50cD2ae22",
12 | "0xE7cd2b4EB1d98CD6a4A48B6071D46401Ac7DC5C8",
13 | "0x6B765d07cf966c745B340AdCa67749fE75B5c345",
14 | "0x1ef89Ed0eDd93D1EC09E4c07373f69C49f4dcCae",
15 | ]
16 | max_val = 0
17 | best_addr = None
18 |
19 | for pegkeeper in pegkeepers:
20 | _p = Contract(pegkeeper)
21 | profit = _p.estimate_caller_profit() / 10**18
22 | print(Contract(_p.pool()).name(), profit)
23 | if profit > max_val:
24 | max_val, best_addr = profit, _p
25 |
26 | # Call update on the best pool
27 | if max_val > 0:
28 | best_addr.update({"from": accounts[0]})
29 | lp = Contract(best_addr.pool())
30 | print(
31 | f"Using {lp.name()} actually receives", lp.balanceOf(accounts[0]) / 10**18
32 | )
33 | gas_price = get_gas_price()
34 | eth_price = get_eth_price()
35 | print(
36 | history[-1].gas_used * gas_price * eth_price / 10**18,
37 | f"@ {gas_price/(10 ** 9)} gwei",
38 | )
39 |
40 |
41 | def get_eth_price():
42 | tricrypto = Contract("0xd51a44d3fae010294c616388b506acda1bfaae46")
43 | return tricrypto.price_oracle(1) / 10**18
44 |
45 |
46 | def get_gas_price():
47 | resp = requests.get("https://api.curve.fi/api/getGas").json()
48 | gas_price = resp["data"]["gas"]["rapid"]
49 | return gas_price
50 |
--------------------------------------------------------------------------------
/lesson-01-setup/crvUSDBull.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/curvefi/vyper-tutorial/c7f45d979d6a87e90a3d1ce9912d4f47c5bf8aa2/lesson-01-setup/crvUSDBull.jpg
--------------------------------------------------------------------------------
/lesson-01-setup/vyper-token/.gitattributes:
--------------------------------------------------------------------------------
1 | *.sol linguist-language=Solidity
2 | *.vy linguist-language=Python
3 |
--------------------------------------------------------------------------------
/lesson-01-setup/vyper-token/.github/workflows/main.yaml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | pull_request:
4 | schedule:
5 | # run this workflow every Monday at 1PM UTC
6 | - cron: "* 13 * * 1"
7 |
8 | name: main workflow
9 |
10 | env:
11 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
12 |
13 | jobs:
14 |
15 | tests:
16 | runs-on: ubuntu-latest
17 |
18 | steps:
19 | - uses: actions/checkout@v2
20 |
21 | - name: Cache Compiler Installations
22 | uses: actions/cache@v2
23 | with:
24 | path: |
25 | ~/.solcx
26 | ~/.vvm
27 | key: compiler-cache
28 |
29 | - name: Setup Node.js
30 | uses: actions/setup-node@v1
31 |
32 | - name: Install Ganache
33 | run: npm install -g ganache-cli@6.10.1
34 |
35 | - name: Setup Python 3.8
36 | uses: actions/setup-python@v2
37 | with:
38 | python-version: 3.8
39 |
40 | - name: Install Requirements
41 | run: pip install -r requirements.txt
42 |
43 | - name: Run Tests
44 | run: brownie test -C
45 |
--------------------------------------------------------------------------------
/lesson-01-setup/vyper-token/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 | .history
3 | .hypothesis/
4 | build/
5 | reports/
6 |
--------------------------------------------------------------------------------
/lesson-01-setup/vyper-token/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Brownie Mixes
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/lesson-01-setup/vyper-token/README.md:
--------------------------------------------------------------------------------
1 | # Introducing Brownie
2 |
3 | ## [🎥 Video 1: Introduction 🎬](https://www.youtube.com/watch?v=nkvIFE2QVp0&list=PLVOHzVzbg7bFUaOGwN0NOgkTItUAVyBBQ)
4 |
5 | Brownie is a Python-based development and testing framework for smart contracts targeting the Ethereum Virtual Machine
6 |
7 | ## Features
8 | * Full support for Solidity and Vyper
9 | * Contract testing via pytest, including trace-based coverage evaluation
10 | * Property-based and stateful testing via hypothesis
11 | * Powerful debugging tools, including python-style tracebacks and custom error strings
12 | * Built-in console for quick project interaction
13 | * Support for ethPM packages
14 |
15 | # Resources
16 |
17 | ## Tutorial Resources:
18 | * [Github](https://github.com/curvefi/brownie-tutorial)
19 | * [YouTube](https://www.youtube.com/playlist?list=PLVOHzVzbg7bFUaOGwN0NOgkTItUAVyBBQ)
20 |
21 | ## Informative
22 | * [Documentation (HTML)](https://eth-brownie.readthedocs.io/)
23 | * [Documentation (PDF)](https://eth-brownie.readthedocs.io/_/downloads/en/stable/pdf/)
24 | * [Tutorial](https://medium.com/@iamdefinitelyahuman/getting-started-with-brownie-part-1-9b2181f4cb99)
25 |
26 | ## Social Media
27 | * [Github](https://github.com/eth-brownie/brownie)
28 | * [Gitter](https://gitter.im/eth-brownie/community)
29 | * [Twitter](https://twitter.com/BrownieEth)
30 |
--------------------------------------------------------------------------------
/lesson-01-setup/vyper-token/requirements.txt:
--------------------------------------------------------------------------------
1 | eth-brownie>=1.11.0,<2.0.0
2 |
--------------------------------------------------------------------------------
/lesson-01-setup/vyper-token/scripts/token.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | from brownie import Token, accounts
4 |
5 |
6 | def main():
7 | return Token.deploy("Test Token", "TST", 18, 1e21, {'from': accounts[0]})
8 |
--------------------------------------------------------------------------------
/lesson-01-setup/vyper-token/tests/conftest.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import pytest
4 |
5 |
6 | @pytest.fixture(scope="function", autouse=True)
7 | def isolate(fn_isolation):
8 | # perform a chain rewind after completing each test, to ensure proper isolation
9 | # https://eth-brownie.readthedocs.io/en/v1.10.3/tests-pytest-intro.html#isolation-fixtures
10 | pass
11 |
12 |
13 | @pytest.fixture(scope="module")
14 | def token(Token, accounts):
15 | return Token.deploy("Test Token", "TST", 18, 1e21, {"from": accounts[0]})
16 |
--------------------------------------------------------------------------------
/lesson-01-setup/vyper-token/tests/test_approve.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import pytest
4 |
5 |
6 | @pytest.mark.parametrize("idx", range(5))
7 | def test_initial_approval_is_zero(token, accounts, idx):
8 | assert token.allowance(accounts[0], accounts[idx]) == 0
9 |
10 |
11 | def test_approve(token, accounts):
12 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
13 |
14 | assert token.allowance(accounts[0], accounts[1]) == 10**19
15 |
16 |
17 | def test_modify_approve(token, accounts):
18 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
19 | token.approve(accounts[1], 12345678, {"from": accounts[0]})
20 |
21 | assert token.allowance(accounts[0], accounts[1]) == 12345678
22 |
23 |
24 | def test_revoke_approve(token, accounts):
25 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
26 | token.approve(accounts[1], 0, {"from": accounts[0]})
27 |
28 | assert token.allowance(accounts[0], accounts[1]) == 0
29 |
30 |
31 | def test_approve_self(token, accounts):
32 | token.approve(accounts[0], 10**19, {"from": accounts[0]})
33 |
34 | assert token.allowance(accounts[0], accounts[0]) == 10**19
35 |
36 |
37 | def test_only_affects_target(token, accounts):
38 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
39 |
40 | assert token.allowance(accounts[1], accounts[0]) == 0
41 |
42 |
43 | def test_returns_true(token, accounts):
44 | tx = token.approve(accounts[1], 10**19, {"from": accounts[0]})
45 |
46 | assert tx.return_value is True
47 |
48 |
49 | def test_approval_event_fires(accounts, token):
50 | tx = token.approve(accounts[1], 10**19, {"from": accounts[0]})
51 |
52 | assert len(tx.events) == 1
53 | assert tx.events["Approval"].values() == [accounts[0], accounts[1], 10**19]
54 |
--------------------------------------------------------------------------------
/lesson-02-contract/solution.diff:
--------------------------------------------------------------------------------
1 | Only in solved/contracts/: Minter.vy
2 |
--------------------------------------------------------------------------------
/lesson-02-contract/solved/.gitattributes:
--------------------------------------------------------------------------------
1 | *.sol linguist-language=Solidity
2 | *.vy linguist-language=Python
3 |
--------------------------------------------------------------------------------
/lesson-02-contract/solved/.github/workflows/main.yaml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | pull_request:
4 | schedule:
5 | # run this workflow every Monday at 1PM UTC
6 | - cron: "* 13 * * 1"
7 |
8 | name: main workflow
9 |
10 | env:
11 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
12 |
13 | jobs:
14 |
15 | tests:
16 | runs-on: ubuntu-latest
17 |
18 | steps:
19 | - uses: actions/checkout@v2
20 |
21 | - name: Cache Compiler Installations
22 | uses: actions/cache@v2
23 | with:
24 | path: |
25 | ~/.solcx
26 | ~/.vvm
27 | key: compiler-cache
28 |
29 | - name: Setup Node.js
30 | uses: actions/setup-node@v1
31 |
32 | - name: Install Ganache
33 | run: npm install -g ganache-cli@6.10.1
34 |
35 | - name: Setup Python 3.8
36 | uses: actions/setup-python@v2
37 | with:
38 | python-version: 3.8
39 |
40 | - name: Install Requirements
41 | run: pip install -r requirements.txt
42 |
43 | - name: Run Tests
44 | run: brownie test -C
45 |
--------------------------------------------------------------------------------
/lesson-02-contract/solved/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 | .history
3 | .hypothesis/
4 | build/
5 | reports/
6 |
--------------------------------------------------------------------------------
/lesson-02-contract/solved/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Brownie Mixes
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/lesson-02-contract/solved/contracts/Minter.bak:
--------------------------------------------------------------------------------
1 | # @version 0.3.3
2 |
3 | @external
4 | def mint(quantity: uint256) -> bool:
5 | return True
6 |
7 |
8 |
--------------------------------------------------------------------------------
/lesson-02-contract/solved/contracts/Minter.vy:
--------------------------------------------------------------------------------
1 | # @version 0.3.3
2 |
3 | """
4 | @title CRV Stablecoin Minter
5 | @license MIT
6 | @author Curve Finance
7 | @notice Mint a stablecoin backed by $CRV
8 | @dev Sample implementation of an ERC-20 backed stablecoin
9 | """
10 |
11 | @external
12 | def mint():
13 | """
14 | @notice Mint a quantity of stablecoins
15 | @dev This function is intentionally not implemented!
16 | """
17 | pass
18 |
--------------------------------------------------------------------------------
/lesson-02-contract/solved/requirements.txt:
--------------------------------------------------------------------------------
1 | eth-brownie>=1.11.0,<2.0.0
2 |
--------------------------------------------------------------------------------
/lesson-02-contract/solved/scripts/token.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | from brownie import Token, accounts
4 |
5 |
6 | def main():
7 | return Token.deploy("Test Token", "TST", 18, 1e21, {'from': accounts[0]})
8 |
--------------------------------------------------------------------------------
/lesson-02-contract/solved/tests/conftest.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import pytest
4 |
5 |
6 | @pytest.fixture(scope="function", autouse=True)
7 | def isolate(fn_isolation):
8 | # perform a chain rewind after completing each test, to ensure proper isolation
9 | # https://eth-brownie.readthedocs.io/en/v1.10.3/tests-pytest-intro.html#isolation-fixtures
10 | pass
11 |
12 |
13 | @pytest.fixture(scope="module")
14 | def token(Token, accounts):
15 | return Token.deploy("Test Token", "TST", 18, 1e21, {"from": accounts[0]})
16 |
17 |
18 | @pytest.fixture(scope="module")
19 | def minter(Minter, accounts):
20 | return Minter.deploy({"from": accounts[0]})
21 |
--------------------------------------------------------------------------------
/lesson-02-contract/solved/tests/test_approve.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import pytest
4 |
5 |
6 | @pytest.mark.parametrize("idx", range(5))
7 | def test_initial_approval_is_zero(token, accounts, idx):
8 | assert token.allowance(accounts[0], accounts[idx]) == 0
9 |
10 |
11 | def test_approve(token, accounts):
12 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
13 |
14 | assert token.allowance(accounts[0], accounts[1]) == 10**19
15 |
16 |
17 | def test_modify_approve(token, accounts):
18 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
19 | token.approve(accounts[1], 12345678, {"from": accounts[0]})
20 |
21 | assert token.allowance(accounts[0], accounts[1]) == 12345678
22 |
23 |
24 | def test_revoke_approve(token, accounts):
25 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
26 | token.approve(accounts[1], 0, {"from": accounts[0]})
27 |
28 | assert token.allowance(accounts[0], accounts[1]) == 0
29 |
30 |
31 | def test_approve_self(token, accounts):
32 | token.approve(accounts[0], 10**19, {"from": accounts[0]})
33 |
34 | assert token.allowance(accounts[0], accounts[0]) == 10**19
35 |
36 |
37 | def test_only_affects_target(token, accounts):
38 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
39 |
40 | assert token.allowance(accounts[1], accounts[0]) == 0
41 |
42 |
43 | def test_returns_true(token, accounts):
44 | tx = token.approve(accounts[1], 10**19, {"from": accounts[0]})
45 |
46 | assert tx.return_value is True
47 |
48 |
49 | def test_approval_event_fires(accounts, token):
50 | tx = token.approve(accounts[1], 10**19, {"from": accounts[0]})
51 |
52 | assert len(tx.events) == 1
53 | assert tx.events["Approval"].values() == [accounts[0], accounts[1], 10**19]
54 |
--------------------------------------------------------------------------------
/lesson-02-contract/solved/tests/test_minter.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 | import brownie
3 |
4 |
5 | def test_minter_deployed(minter):
6 | assert hasattr(minter, "mint")
7 |
--------------------------------------------------------------------------------
/lesson-02-contract/tests.diff:
--------------------------------------------------------------------------------
1 | diff -r ../lesson-1-setup/vyper-token/tests/conftest.py unsolved/tests/conftest.py
2 | 15a16,20
3 | >
4 | >
5 | > @pytest.fixture(scope="module")
6 | > def minter(Minter, accounts):
7 | > return Minter.deploy({"from": accounts[0]})
8 | Only in unsolved/tests: test_minter.py
9 |
--------------------------------------------------------------------------------
/lesson-02-contract/unsolved/.gitattributes:
--------------------------------------------------------------------------------
1 | *.sol linguist-language=Solidity
2 | *.vy linguist-language=Python
3 |
--------------------------------------------------------------------------------
/lesson-02-contract/unsolved/.github/workflows/main.yaml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | pull_request:
4 | schedule:
5 | # run this workflow every Monday at 1PM UTC
6 | - cron: "* 13 * * 1"
7 |
8 | name: main workflow
9 |
10 | env:
11 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
12 |
13 | jobs:
14 |
15 | tests:
16 | runs-on: ubuntu-latest
17 |
18 | steps:
19 | - uses: actions/checkout@v2
20 |
21 | - name: Cache Compiler Installations
22 | uses: actions/cache@v2
23 | with:
24 | path: |
25 | ~/.solcx
26 | ~/.vvm
27 | key: compiler-cache
28 |
29 | - name: Setup Node.js
30 | uses: actions/setup-node@v1
31 |
32 | - name: Install Ganache
33 | run: npm install -g ganache-cli@6.10.1
34 |
35 | - name: Setup Python 3.8
36 | uses: actions/setup-python@v2
37 | with:
38 | python-version: 3.8
39 |
40 | - name: Install Requirements
41 | run: pip install -r requirements.txt
42 |
43 | - name: Run Tests
44 | run: brownie test -C
45 |
--------------------------------------------------------------------------------
/lesson-02-contract/unsolved/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 | .history
3 | .hypothesis/
4 | build/
5 | reports/
6 |
--------------------------------------------------------------------------------
/lesson-02-contract/unsolved/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Brownie Mixes
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/lesson-02-contract/unsolved/requirements.txt:
--------------------------------------------------------------------------------
1 | eth-brownie>=1.11.0,<2.0.0
2 |
--------------------------------------------------------------------------------
/lesson-02-contract/unsolved/scripts/token.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | from brownie import Token, accounts
4 |
5 |
6 | def main():
7 | return Token.deploy("Test Token", "TST", 18, 1e21, {'from': accounts[0]})
8 |
--------------------------------------------------------------------------------
/lesson-02-contract/unsolved/tests/conftest.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import pytest
4 |
5 |
6 | @pytest.fixture(scope="function", autouse=True)
7 | def isolate(fn_isolation):
8 | # perform a chain rewind after completing each test, to ensure proper isolation
9 | # https://eth-brownie.readthedocs.io/en/v1.10.3/tests-pytest-intro.html#isolation-fixtures
10 | pass
11 |
12 |
13 | @pytest.fixture(scope="module")
14 | def token(Token, accounts):
15 | return Token.deploy("Test Token", "TST", 18, 1e21, {"from": accounts[0]})
16 |
17 |
18 | @pytest.fixture(scope="module")
19 | def minter(Minter, accounts):
20 | return Minter.deploy({"from": accounts[0]})
21 |
--------------------------------------------------------------------------------
/lesson-02-contract/unsolved/tests/test_approve.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import pytest
4 |
5 |
6 | @pytest.mark.parametrize("idx", range(5))
7 | def test_initial_approval_is_zero(token, accounts, idx):
8 | assert token.allowance(accounts[0], accounts[idx]) == 0
9 |
10 |
11 | def test_approve(token, accounts):
12 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
13 |
14 | assert token.allowance(accounts[0], accounts[1]) == 10**19
15 |
16 |
17 | def test_modify_approve(token, accounts):
18 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
19 | token.approve(accounts[1], 12345678, {"from": accounts[0]})
20 |
21 | assert token.allowance(accounts[0], accounts[1]) == 12345678
22 |
23 |
24 | def test_revoke_approve(token, accounts):
25 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
26 | token.approve(accounts[1], 0, {"from": accounts[0]})
27 |
28 | assert token.allowance(accounts[0], accounts[1]) == 0
29 |
30 |
31 | def test_approve_self(token, accounts):
32 | token.approve(accounts[0], 10**19, {"from": accounts[0]})
33 |
34 | assert token.allowance(accounts[0], accounts[0]) == 10**19
35 |
36 |
37 | def test_only_affects_target(token, accounts):
38 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
39 |
40 | assert token.allowance(accounts[1], accounts[0]) == 0
41 |
42 |
43 | def test_returns_true(token, accounts):
44 | tx = token.approve(accounts[1], 10**19, {"from": accounts[0]})
45 |
46 | assert tx.return_value is True
47 |
48 |
49 | def test_approval_event_fires(accounts, token):
50 | tx = token.approve(accounts[1], 10**19, {"from": accounts[0]})
51 |
52 | assert len(tx.events) == 1
53 | assert tx.events["Approval"].values() == [accounts[0], accounts[1], 10**19]
54 |
--------------------------------------------------------------------------------
/lesson-02-contract/unsolved/tests/test_minter.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 | import brownie
3 |
4 |
5 | def test_minter_deployed(minter):
6 | assert hasattr(minter, "mint")
7 |
--------------------------------------------------------------------------------
/lesson-03-state-variables/solution.diff:
--------------------------------------------------------------------------------
1 | Only in unsolved/contracts: .Minter.vy.swp
2 | diff unsolved/contracts/Token.vy solved/contracts/Token.vy
3 | 123a124,134
4 | >
5 | >
6 | > @external
7 | > def mint(to_addr: address, amount: uint256):
8 | > """
9 | > @notice Mint tokens
10 | > @param to_addr Address to receive tokens
11 | > @param amount Number of tokens to mint
12 | > """
13 | > self.totalSupply += amount
14 | > self.balances[to_addr] += amount
15 |
--------------------------------------------------------------------------------
/lesson-03-state-variables/solved/.gitattributes:
--------------------------------------------------------------------------------
1 | *.sol linguist-language=Solidity
2 | *.vy linguist-language=Python
3 |
--------------------------------------------------------------------------------
/lesson-03-state-variables/solved/.github/workflows/main.yaml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | pull_request:
4 | schedule:
5 | # run this workflow every Monday at 1PM UTC
6 | - cron: "* 13 * * 1"
7 |
8 | name: main workflow
9 |
10 | env:
11 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
12 |
13 | jobs:
14 |
15 | tests:
16 | runs-on: ubuntu-latest
17 |
18 | steps:
19 | - uses: actions/checkout@v2
20 |
21 | - name: Cache Compiler Installations
22 | uses: actions/cache@v2
23 | with:
24 | path: |
25 | ~/.solcx
26 | ~/.vvm
27 | key: compiler-cache
28 |
29 | - name: Setup Node.js
30 | uses: actions/setup-node@v1
31 |
32 | - name: Install Ganache
33 | run: npm install -g ganache-cli@6.10.1
34 |
35 | - name: Setup Python 3.8
36 | uses: actions/setup-python@v2
37 | with:
38 | python-version: 3.8
39 |
40 | - name: Install Requirements
41 | run: pip install -r requirements.txt
42 |
43 | - name: Run Tests
44 | run: brownie test -C
45 |
--------------------------------------------------------------------------------
/lesson-03-state-variables/solved/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 | .history
3 | .hypothesis/
4 | build/
5 | reports/
6 |
--------------------------------------------------------------------------------
/lesson-03-state-variables/solved/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Brownie Mixes
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/lesson-03-state-variables/solved/contracts/Minter.vy:
--------------------------------------------------------------------------------
1 | # @version 0.3.3
2 |
3 | """
4 | @title CRV Stablecoin Minter
5 | @license MIT
6 | @author Curve Finance
7 | @notice Mint a stablecoin backed by $CRV
8 | @dev Sample implementation of an ERC-20 backed stablecoin
9 | """
10 |
11 | @external
12 | def mint():
13 | """
14 | @notice Mint a quantity of stablecoins
15 | @dev This function is intentionally not implemented!
16 | """
17 | pass
18 |
--------------------------------------------------------------------------------
/lesson-03-state-variables/solved/requirements.txt:
--------------------------------------------------------------------------------
1 | eth-brownie>=1.11.0,<2.0.0
2 |
--------------------------------------------------------------------------------
/lesson-03-state-variables/solved/scripts/token.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | from brownie import Token, accounts
4 |
5 |
6 | def main():
7 | return Token.deploy("Test Token", "TST", 18, 1e21, {'from': accounts[0]})
8 |
--------------------------------------------------------------------------------
/lesson-03-state-variables/solved/tests/conftest.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import pytest
4 |
5 |
6 | @pytest.fixture(scope="function", autouse=True)
7 | def isolate(fn_isolation):
8 | # perform a chain rewind after completing each test, to ensure proper isolation
9 | # https://eth-brownie.readthedocs.io/en/v1.10.3/tests-pytest-intro.html#isolation-fixtures
10 | pass
11 |
12 |
13 | @pytest.fixture(scope="module")
14 | def token(Token, accounts):
15 | return Token.deploy("Test Token", "TST", 18, 1e21, {"from": accounts[0]})
16 |
17 |
18 | @pytest.fixture(scope="module")
19 | def minter(Minter, accounts):
20 | return Minter.deploy({"from": accounts[0]})
21 |
--------------------------------------------------------------------------------
/lesson-03-state-variables/solved/tests/test_approve.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import pytest
4 |
5 |
6 | @pytest.mark.parametrize("idx", range(5))
7 | def test_initial_approval_is_zero(token, accounts, idx):
8 | assert token.allowance(accounts[0], accounts[idx]) == 0
9 |
10 |
11 | def test_approve(token, accounts):
12 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
13 |
14 | assert token.allowance(accounts[0], accounts[1]) == 10**19
15 |
16 |
17 | def test_modify_approve(token, accounts):
18 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
19 | token.approve(accounts[1], 12345678, {"from": accounts[0]})
20 |
21 | assert token.allowance(accounts[0], accounts[1]) == 12345678
22 |
23 |
24 | def test_revoke_approve(token, accounts):
25 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
26 | token.approve(accounts[1], 0, {"from": accounts[0]})
27 |
28 | assert token.allowance(accounts[0], accounts[1]) == 0
29 |
30 |
31 | def test_approve_self(token, accounts):
32 | token.approve(accounts[0], 10**19, {"from": accounts[0]})
33 |
34 | assert token.allowance(accounts[0], accounts[0]) == 10**19
35 |
36 |
37 | def test_only_affects_target(token, accounts):
38 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
39 |
40 | assert token.allowance(accounts[1], accounts[0]) == 0
41 |
42 |
43 | def test_returns_true(token, accounts):
44 | tx = token.approve(accounts[1], 10**19, {"from": accounts[0]})
45 |
46 | assert tx.return_value is True
47 |
48 |
49 | def test_approval_event_fires(accounts, token):
50 | tx = token.approve(accounts[1], 10**19, {"from": accounts[0]})
51 |
52 | assert len(tx.events) == 1
53 | assert tx.events["Approval"].values() == [accounts[0], accounts[1], 10**19]
54 |
--------------------------------------------------------------------------------
/lesson-03-state-variables/solved/tests/test_minter.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 | import brownie
3 |
4 |
5 | def test_minter_deployed(minter):
6 | assert hasattr(minter, "mint")
7 |
8 |
9 | def test_token_balance_updates_on_mint(token, accounts):
10 | quantity = 10**18
11 | init_bal = token.balanceOf(accounts[0])
12 |
13 | token.mint(accounts[0], quantity)
14 | assert token.balanceOf(accounts[0]) == init_bal + quantity
15 |
16 |
17 | def test_token_total_supply_updates_on_mint(token, accounts):
18 | quantity = 10**18
19 | init_supply = token.totalSupply()
20 |
21 | token.mint(accounts[0], quantity)
22 | assert token.totalSupply() == init_supply + quantity
23 |
--------------------------------------------------------------------------------
/lesson-03-state-variables/tests.diff:
--------------------------------------------------------------------------------
1 | diff -r ../lesson-02-contract/solved/tests/test_minter.py unsolved/tests/test_minter.py
2 | 6a7,22
3 | >
4 | >
5 | > def test_token_balance_updates_on_mint(token, accounts):
6 | > quantity = 10**18
7 | > init_bal = token.balanceOf(accounts[0])
8 | >
9 | > token.mint(accounts[0], quantity)
10 | > assert token.balanceOf(accounts[0]) == init_bal + quantity
11 | >
12 | >
13 | > def test_token_total_supply_updates_on_mint(token, accounts):
14 | > quantity = 10**18
15 | > init_supply = token.totalSupply()
16 | >
17 | > token.mint(accounts[0], quantity)
18 | > assert token.totalSupply() == init_supply + quantity
19 |
--------------------------------------------------------------------------------
/lesson-03-state-variables/unsolved/.gitattributes:
--------------------------------------------------------------------------------
1 | *.sol linguist-language=Solidity
2 | *.vy linguist-language=Python
3 |
--------------------------------------------------------------------------------
/lesson-03-state-variables/unsolved/.github/workflows/main.yaml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | pull_request:
4 | schedule:
5 | # run this workflow every Monday at 1PM UTC
6 | - cron: "* 13 * * 1"
7 |
8 | name: main workflow
9 |
10 | env:
11 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
12 |
13 | jobs:
14 |
15 | tests:
16 | runs-on: ubuntu-latest
17 |
18 | steps:
19 | - uses: actions/checkout@v2
20 |
21 | - name: Cache Compiler Installations
22 | uses: actions/cache@v2
23 | with:
24 | path: |
25 | ~/.solcx
26 | ~/.vvm
27 | key: compiler-cache
28 |
29 | - name: Setup Node.js
30 | uses: actions/setup-node@v1
31 |
32 | - name: Install Ganache
33 | run: npm install -g ganache-cli@6.10.1
34 |
35 | - name: Setup Python 3.8
36 | uses: actions/setup-python@v2
37 | with:
38 | python-version: 3.8
39 |
40 | - name: Install Requirements
41 | run: pip install -r requirements.txt
42 |
43 | - name: Run Tests
44 | run: brownie test -C
45 |
--------------------------------------------------------------------------------
/lesson-03-state-variables/unsolved/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 | .history
3 | .hypothesis/
4 | build/
5 | reports/
6 |
--------------------------------------------------------------------------------
/lesson-03-state-variables/unsolved/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Brownie Mixes
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/lesson-03-state-variables/unsolved/contracts/Minter.vy:
--------------------------------------------------------------------------------
1 | # @version 0.3.3
2 |
3 | """
4 | @title CRV Stablecoin Minter
5 | @license MIT
6 | @author Curve Finance
7 | @notice Mint a stablecoin backed by $CRV
8 | @dev Sample implementation of an ERC-20 backed stablecoin
9 | """
10 |
11 | @external
12 | def mint():
13 | """
14 | @notice Mint a quantity of stablecoins
15 | @dev This function is intentionally not implemented!
16 | """
17 | pass
18 |
--------------------------------------------------------------------------------
/lesson-03-state-variables/unsolved/requirements.txt:
--------------------------------------------------------------------------------
1 | eth-brownie>=1.11.0,<2.0.0
2 |
--------------------------------------------------------------------------------
/lesson-03-state-variables/unsolved/scripts/token.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | from brownie import Token, accounts
4 |
5 |
6 | def main():
7 | return Token.deploy("Test Token", "TST", 18, 1e21, {'from': accounts[0]})
8 |
--------------------------------------------------------------------------------
/lesson-03-state-variables/unsolved/tests/conftest.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import pytest
4 |
5 |
6 | @pytest.fixture(scope="function", autouse=True)
7 | def isolate(fn_isolation):
8 | # perform a chain rewind after completing each test, to ensure proper isolation
9 | # https://eth-brownie.readthedocs.io/en/v1.10.3/tests-pytest-intro.html#isolation-fixtures
10 | pass
11 |
12 |
13 | @pytest.fixture(scope="module")
14 | def token(Token, accounts):
15 | return Token.deploy("Test Token", "TST", 18, 1e21, {"from": accounts[0]})
16 |
17 |
18 | @pytest.fixture(scope="module")
19 | def minter(Minter, accounts):
20 | return Minter.deploy({"from": accounts[0]})
21 |
--------------------------------------------------------------------------------
/lesson-03-state-variables/unsolved/tests/test_approve.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import pytest
4 |
5 |
6 | @pytest.mark.parametrize("idx", range(5))
7 | def test_initial_approval_is_zero(token, accounts, idx):
8 | assert token.allowance(accounts[0], accounts[idx]) == 0
9 |
10 |
11 | def test_approve(token, accounts):
12 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
13 |
14 | assert token.allowance(accounts[0], accounts[1]) == 10**19
15 |
16 |
17 | def test_modify_approve(token, accounts):
18 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
19 | token.approve(accounts[1], 12345678, {"from": accounts[0]})
20 |
21 | assert token.allowance(accounts[0], accounts[1]) == 12345678
22 |
23 |
24 | def test_revoke_approve(token, accounts):
25 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
26 | token.approve(accounts[1], 0, {"from": accounts[0]})
27 |
28 | assert token.allowance(accounts[0], accounts[1]) == 0
29 |
30 |
31 | def test_approve_self(token, accounts):
32 | token.approve(accounts[0], 10**19, {"from": accounts[0]})
33 |
34 | assert token.allowance(accounts[0], accounts[0]) == 10**19
35 |
36 |
37 | def test_only_affects_target(token, accounts):
38 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
39 |
40 | assert token.allowance(accounts[1], accounts[0]) == 0
41 |
42 |
43 | def test_returns_true(token, accounts):
44 | tx = token.approve(accounts[1], 10**19, {"from": accounts[0]})
45 |
46 | assert tx.return_value is True
47 |
48 |
49 | def test_approval_event_fires(accounts, token):
50 | tx = token.approve(accounts[1], 10**19, {"from": accounts[0]})
51 |
52 | assert len(tx.events) == 1
53 | assert tx.events["Approval"].values() == [accounts[0], accounts[1], 10**19]
54 |
--------------------------------------------------------------------------------
/lesson-03-state-variables/unsolved/tests/test_minter.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 | import brownie
3 |
4 |
5 | def test_minter_deployed(minter):
6 | assert hasattr(minter, "mint")
7 |
8 |
9 | def test_token_balance_updates_on_mint(token, accounts):
10 | quantity = 10**18
11 | init_bal = token.balanceOf(accounts[0])
12 |
13 | token.mint(accounts[0], quantity)
14 | assert token.balanceOf(accounts[0]) == init_bal + quantity
15 |
16 |
17 | def test_token_total_supply_updates_on_mint(token, accounts):
18 | quantity = 10**18
19 | init_supply = token.totalSupply()
20 |
21 | token.mint(accounts[0], quantity)
22 | assert token.totalSupply() == init_supply + quantity
23 |
--------------------------------------------------------------------------------
/lesson-04-assert/README.md:
--------------------------------------------------------------------------------
1 | # Lesson 4: Assert Statements
2 |
3 | ## [🎥 Video 4 Assert 🎬](https://youtu.be/GzVXWXI5JdM)
4 |
5 | Vyper relies frequently on `assert` statements add additional security.
6 |
7 | In this lesson we secure our ERC20 to restrict minting functionality only to approved addresses. We'll also cover public state variables, constructor functions, and the msg object.
8 |
9 |
10 | ## ASSERT STATEMENT
11 |
12 | An `assert` statement evaluates a condition at the current line of code.
13 | If the condition evaluates to FALSE, the transaction reverts.
14 | Optionally, an error message may be surfaced to the user.
15 |
16 | assert value == 5, "Value is not five"
17 |
18 |
19 | ## PUBLIC STATE VARIABLES
20 |
21 | If a state variable is marked as public, the compiler will automatically
22 | create an external getter function so anybody may read its value.
23 |
24 | data: public(uint256)
25 |
26 |
27 | ## CONSTRUCTOR FUNCTION
28 |
29 | `__init__` is a special initialization function called once during deployment.
30 | This function is commonly used to set initial values for storage variables.
31 | It cannot reference other contract functions.
32 |
33 | def __init__():
34 | self.owner = msg.sender
35 |
36 |
37 | ## MESSAGE OBJECT
38 |
39 | In Vyper, `msg` is a reserved environment variable containing
40 | information about the current transaction.
41 |
42 | msg.sender # Address calling the transaction
43 | msg.value # Amount of wei sent with the message
44 | msg.gas # Remaining gas
45 | msg.data # Bytes of message calldata
46 |
47 |
--------------------------------------------------------------------------------
/lesson-04-assert/solution.diff:
--------------------------------------------------------------------------------
1 | diff solved/contracts/Token.vy unsolved/contracts/Token.vy
2 | 33,34d32
3 | < owner: public(address)
4 | < minter: public(address)
5 | 43,44d40
6 | < self.owner = msg.sender
7 | < self.minter = msg.sender
8 | 137d132
9 | < assert self.minter == msg.sender
10 | 140,150d134
11 | <
12 | <
13 | < @external
14 | < def set_minter(minter_addr: address):
15 | < """
16 | < @notice Update the address allowed to mint a token
17 | < @dev Must be called by Contract owner
18 | < @param minter_addr Address to become new minter
19 | < """
20 | < assert self.owner == msg.sender
21 | < self.minter = minter_addr
22 |
--------------------------------------------------------------------------------
/lesson-04-assert/solved/.gitattributes:
--------------------------------------------------------------------------------
1 | *.sol linguist-language=Solidity
2 | *.vy linguist-language=Python
3 |
--------------------------------------------------------------------------------
/lesson-04-assert/solved/.github/workflows/main.yaml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | pull_request:
4 | schedule:
5 | # run this workflow every Monday at 1PM UTC
6 | - cron: "* 13 * * 1"
7 |
8 | name: main workflow
9 |
10 | env:
11 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
12 |
13 | jobs:
14 |
15 | tests:
16 | runs-on: ubuntu-latest
17 |
18 | steps:
19 | - uses: actions/checkout@v2
20 |
21 | - name: Cache Compiler Installations
22 | uses: actions/cache@v2
23 | with:
24 | path: |
25 | ~/.solcx
26 | ~/.vvm
27 | key: compiler-cache
28 |
29 | - name: Setup Node.js
30 | uses: actions/setup-node@v1
31 |
32 | - name: Install Ganache
33 | run: npm install -g ganache-cli@6.10.1
34 |
35 | - name: Setup Python 3.8
36 | uses: actions/setup-python@v2
37 | with:
38 | python-version: 3.8
39 |
40 | - name: Install Requirements
41 | run: pip install -r requirements.txt
42 |
43 | - name: Run Tests
44 | run: brownie test -C
45 |
--------------------------------------------------------------------------------
/lesson-04-assert/solved/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 | .history
3 | .hypothesis/
4 | build/
5 | reports/
6 |
--------------------------------------------------------------------------------
/lesson-04-assert/solved/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Brownie Mixes
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/lesson-04-assert/solved/contracts/Minter.vy:
--------------------------------------------------------------------------------
1 | # @version 0.3.3
2 |
3 | """
4 | @title CRV Stablecoin Minter
5 | @license MIT
6 | @author Curve Finance
7 | @notice Mint a stablecoin backed by $CRV
8 | @dev Sample implementation of an ERC-20 backed stablecoin
9 | """
10 |
11 | @external
12 | def mint():
13 | """
14 | @notice Mint a quantity of stablecoins
15 | @dev This function is intentionally not implemented!
16 | """
17 | pass
18 |
--------------------------------------------------------------------------------
/lesson-04-assert/solved/requirements.txt:
--------------------------------------------------------------------------------
1 | eth-brownie>=1.11.0,<2.0.0
2 |
--------------------------------------------------------------------------------
/lesson-04-assert/solved/scripts/token.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | from brownie import Token, accounts
4 |
5 |
6 | def main():
7 | return Token.deploy("Test Token", "TST", 18, 1e21, {'from': accounts[0]})
8 |
--------------------------------------------------------------------------------
/lesson-04-assert/solved/tests/conftest.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import pytest
4 |
5 |
6 | @pytest.fixture(scope="function", autouse=True)
7 | def isolate(fn_isolation):
8 | # perform a chain rewind after completing each test, to ensure proper isolation
9 | # https://eth-brownie.readthedocs.io/en/v1.10.3/tests-pytest-intro.html#isolation-fixtures
10 | pass
11 |
12 |
13 | @pytest.fixture(scope="module")
14 | def token(Token, accounts):
15 | return Token.deploy("Test Token", "TST", 18, 1e21, {"from": accounts[0]})
16 |
17 |
18 | @pytest.fixture(scope="module")
19 | def minter(Minter, accounts):
20 | return Minter.deploy({"from": accounts[0]})
21 |
--------------------------------------------------------------------------------
/lesson-04-assert/solved/tests/test_approve.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import pytest
4 |
5 |
6 | @pytest.mark.parametrize("idx", range(5))
7 | def test_initial_approval_is_zero(token, accounts, idx):
8 | assert token.allowance(accounts[0], accounts[idx]) == 0
9 |
10 |
11 | def test_approve(token, accounts):
12 | token.approve(accounts[1], 10 ** 19, {"from": accounts[0]})
13 |
14 | assert token.allowance(accounts[0], accounts[1]) == 10 ** 19
15 |
16 |
17 | def test_modify_approve(token, accounts):
18 | token.approve(accounts[1], 10 ** 19, {"from": accounts[0]})
19 | token.approve(accounts[1], 12345678, {"from": accounts[0]})
20 |
21 | assert token.allowance(accounts[0], accounts[1]) == 12345678
22 |
23 |
24 | def test_revoke_approve(token, accounts):
25 | token.approve(accounts[1], 10 ** 19, {"from": accounts[0]})
26 | token.approve(accounts[1], 0, {"from": accounts[0]})
27 |
28 | assert token.allowance(accounts[0], accounts[1]) == 0
29 |
30 |
31 | def test_approve_self(token, accounts):
32 | token.approve(accounts[0], 10 ** 19, {"from": accounts[0]})
33 |
34 | assert token.allowance(accounts[0], accounts[0]) == 10 ** 19
35 |
36 |
37 | def test_only_affects_target(token, accounts):
38 | token.approve(accounts[1], 10 ** 19, {"from": accounts[0]})
39 |
40 | assert token.allowance(accounts[1], accounts[0]) == 0
41 |
42 |
43 | def test_returns_true(token, accounts):
44 | tx = token.approve(accounts[1], 10 ** 19, {"from": accounts[0]})
45 |
46 | assert tx.return_value is True
47 |
48 |
49 | def test_approval_event_fires(accounts, token):
50 | tx = token.approve(accounts[1], 10 ** 19, {"from": accounts[0]})
51 |
52 | assert len(tx.events) == 1
53 | assert tx.events["Approval"].values() == [accounts[0], accounts[1], 10 ** 19]
54 |
--------------------------------------------------------------------------------
/lesson-04-assert/solved/tests/test_minter.py:
--------------------------------------------------------------------------------
1 | import brownie
2 | from brownie import *
3 |
4 |
5 | def test_minter_deployed(minter):
6 | assert hasattr(minter, "mint")
7 |
8 |
9 | def test_token_balance_updates_on_mint(token, accounts):
10 | quantity = 10 ** 18
11 | init_bal = token.balanceOf(accounts[0])
12 |
13 | token.mint(accounts[0], quantity, {"from": token.owner()})
14 | assert token.balanceOf(accounts[0]) == init_bal + quantity
15 |
16 |
17 | def test_token_total_supply_updates_on_mint(token, accounts):
18 | quantity = 10 ** 18
19 | init_supply = token.totalSupply()
20 |
21 | token.mint(accounts[0], quantity, {"from": token.owner()})
22 | assert token.totalSupply() == init_supply + quantity
23 |
24 |
25 | def test_owner_can_set_minter(token, accounts):
26 | assert token.minter() != accounts[1]
27 |
28 | token.set_minter(accounts[1], {"from": token.owner()})
29 | assert token.minter() == accounts[1]
30 |
31 |
32 | def test_nonowner_cannot_set_minter(token, accounts):
33 | hacker = accounts[1]
34 | assert hacker != token.owner()
35 |
36 | with brownie.reverts():
37 | token.set_minter(hacker, {"from": hacker})
38 |
39 |
40 | def test_nonminter_cannot_mint(token, accounts):
41 | hacker = accounts[1]
42 | assert hacker != token.minter()
43 |
44 | with brownie.reverts():
45 | token.mint(hacker, 10 ** 18, {"from": hacker})
46 |
--------------------------------------------------------------------------------
/lesson-04-assert/tests.diff:
--------------------------------------------------------------------------------
1 | diff ../lesson-03-state-variables/solved/tests/test_minter.py unsolved/tests/test_minter.py
2 | 1d0
3 | < #!/usr/bin/python3
4 | 2a2
5 | > from brownie import *
6 | 13c13
7 | < token.mint(accounts[0], quantity)
8 | ---
9 | > token.mint(accounts[0], quantity, {"from": token.owner()})
10 | 18c18
11 | < quantity = 10**18
12 | ---
13 | > quantity = 10 ** 18
14 | 21c21
15 | < token.mint(accounts[0], quantity)
16 | ---
17 | > token.mint(accounts[0], quantity, {"from": token.owner()})
18 | 22a23,45
19 | >
20 | >
21 | > def test_owner_can_set_minter(token, accounts):
22 | > assert token.minter() != accounts[1]
23 | >
24 | > token.set_minter(accounts[1], {"from": token.owner()})
25 | > assert token.minter() == accounts[1]
26 | >
27 | >
28 | > def test_nonowner_cannot_set_minter(token, accounts):
29 | > hacker = accounts[1]
30 | > assert hacker != token.owner()
31 | >
32 | > with brownie.reverts():
33 | > token.set_minter(hacker, {"from": hacker})
34 | >
35 | >
36 | > def test_nonminter_cannot_mint(token, accounts):
37 | > hacker = accounts[1]
38 | > assert hacker != token.minter()
39 | >
40 | > with brownie.reverts():
41 | > token.mint(hacker, 10 ** 18, {"from": hacker})
42 |
--------------------------------------------------------------------------------
/lesson-04-assert/unsolved/.gitattributes:
--------------------------------------------------------------------------------
1 | *.sol linguist-language=Solidity
2 | *.vy linguist-language=Python
3 |
--------------------------------------------------------------------------------
/lesson-04-assert/unsolved/.github/workflows/main.yaml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | pull_request:
4 | schedule:
5 | # run this workflow every Monday at 1PM UTC
6 | - cron: "* 13 * * 1"
7 |
8 | name: main workflow
9 |
10 | env:
11 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
12 |
13 | jobs:
14 |
15 | tests:
16 | runs-on: ubuntu-latest
17 |
18 | steps:
19 | - uses: actions/checkout@v2
20 |
21 | - name: Cache Compiler Installations
22 | uses: actions/cache@v2
23 | with:
24 | path: |
25 | ~/.solcx
26 | ~/.vvm
27 | key: compiler-cache
28 |
29 | - name: Setup Node.js
30 | uses: actions/setup-node@v1
31 |
32 | - name: Install Ganache
33 | run: npm install -g ganache-cli@6.10.1
34 |
35 | - name: Setup Python 3.8
36 | uses: actions/setup-python@v2
37 | with:
38 | python-version: 3.8
39 |
40 | - name: Install Requirements
41 | run: pip install -r requirements.txt
42 |
43 | - name: Run Tests
44 | run: brownie test -C
45 |
--------------------------------------------------------------------------------
/lesson-04-assert/unsolved/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 | .history
3 | .hypothesis/
4 | build/
5 | reports/
6 |
--------------------------------------------------------------------------------
/lesson-04-assert/unsolved/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Brownie Mixes
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/lesson-04-assert/unsolved/contracts/Minter.vy:
--------------------------------------------------------------------------------
1 | # @version 0.3.3
2 |
3 | """
4 | @title CRV Stablecoin Minter
5 | @license MIT
6 | @author Curve Finance
7 | @notice Mint a stablecoin backed by $CRV
8 | @dev Sample implementation of an ERC-20 backed stablecoin
9 | """
10 |
11 | @external
12 | def mint():
13 | """
14 | @notice Mint a quantity of stablecoins
15 | @dev This function is intentionally not implemented!
16 | """
17 | pass
18 |
--------------------------------------------------------------------------------
/lesson-04-assert/unsolved/requirements.txt:
--------------------------------------------------------------------------------
1 | eth-brownie>=1.11.0,<2.0.0
2 |
--------------------------------------------------------------------------------
/lesson-04-assert/unsolved/scripts/token.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | from brownie import Token, accounts
4 |
5 |
6 | def main():
7 | return Token.deploy("Test Token", "TST", 18, 1e21, {'from': accounts[0]})
8 |
--------------------------------------------------------------------------------
/lesson-04-assert/unsolved/tests/conftest.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import pytest
4 |
5 |
6 | @pytest.fixture(scope="function", autouse=True)
7 | def isolate(fn_isolation):
8 | # perform a chain rewind after completing each test, to ensure proper isolation
9 | # https://eth-brownie.readthedocs.io/en/v1.10.3/tests-pytest-intro.html#isolation-fixtures
10 | pass
11 |
12 |
13 | @pytest.fixture(scope="module")
14 | def token(Token, accounts):
15 | return Token.deploy("Test Token", "TST", 18, 1e21, {"from": accounts[0]})
16 |
17 |
18 | @pytest.fixture(scope="module")
19 | def minter(Minter, accounts):
20 | return Minter.deploy({"from": accounts[0]})
21 |
--------------------------------------------------------------------------------
/lesson-04-assert/unsolved/tests/test_approve.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import pytest
4 |
5 |
6 | @pytest.mark.parametrize("idx", range(5))
7 | def test_initial_approval_is_zero(token, accounts, idx):
8 | assert token.allowance(accounts[0], accounts[idx]) == 0
9 |
10 |
11 | def test_approve(token, accounts):
12 | token.approve(accounts[1], 10 ** 19, {"from": accounts[0]})
13 |
14 | assert token.allowance(accounts[0], accounts[1]) == 10 ** 19
15 |
16 |
17 | def test_modify_approve(token, accounts):
18 | token.approve(accounts[1], 10 ** 19, {"from": accounts[0]})
19 | token.approve(accounts[1], 12345678, {"from": accounts[0]})
20 |
21 | assert token.allowance(accounts[0], accounts[1]) == 12345678
22 |
23 |
24 | def test_revoke_approve(token, accounts):
25 | token.approve(accounts[1], 10 ** 19, {"from": accounts[0]})
26 | token.approve(accounts[1], 0, {"from": accounts[0]})
27 |
28 | assert token.allowance(accounts[0], accounts[1]) == 0
29 |
30 |
31 | def test_approve_self(token, accounts):
32 | token.approve(accounts[0], 10 ** 19, {"from": accounts[0]})
33 |
34 | assert token.allowance(accounts[0], accounts[0]) == 10 ** 19
35 |
36 |
37 | def test_only_affects_target(token, accounts):
38 | token.approve(accounts[1], 10 ** 19, {"from": accounts[0]})
39 |
40 | assert token.allowance(accounts[1], accounts[0]) == 0
41 |
42 |
43 | def test_returns_true(token, accounts):
44 | tx = token.approve(accounts[1], 10 ** 19, {"from": accounts[0]})
45 |
46 | assert tx.return_value is True
47 |
48 |
49 | def test_approval_event_fires(accounts, token):
50 | tx = token.approve(accounts[1], 10 ** 19, {"from": accounts[0]})
51 |
52 | assert len(tx.events) == 1
53 | assert tx.events["Approval"].values() == [accounts[0], accounts[1], 10 ** 19]
54 |
--------------------------------------------------------------------------------
/lesson-04-assert/unsolved/tests/test_minter.py:
--------------------------------------------------------------------------------
1 | import brownie
2 | from brownie import *
3 |
4 |
5 | def test_minter_deployed(minter):
6 | assert hasattr(minter, "mint")
7 |
8 |
9 | def test_token_balance_updates_on_mint(token, accounts):
10 | quantity = 10 ** 18
11 | init_bal = token.balanceOf(accounts[0])
12 |
13 | token.mint(accounts[0], quantity, {"from": token.owner()})
14 | assert token.balanceOf(accounts[0]) == init_bal + quantity
15 |
16 |
17 | def test_token_total_supply_updates_on_mint(token, accounts):
18 | quantity = 10 ** 18
19 | init_supply = token.totalSupply()
20 |
21 | token.mint(accounts[0], quantity, {"from": token.owner()})
22 | assert token.totalSupply() == init_supply + quantity
23 |
24 |
25 | def test_owner_can_set_minter(token, accounts):
26 | assert token.minter() != accounts[1]
27 |
28 | token.set_minter(accounts[1], {"from": token.owner()})
29 | assert token.minter() == accounts[1]
30 |
31 |
32 | def test_nonowner_cannot_set_minter(token, accounts):
33 | hacker = accounts[1]
34 | assert hacker != token.owner()
35 |
36 | with brownie.reverts():
37 | token.set_minter(hacker, {"from": hacker})
38 |
39 |
40 | def test_nonminter_cannot_mint(token, accounts):
41 | hacker = accounts[1]
42 | assert hacker != token.minter()
43 |
44 | with brownie.reverts():
45 | token.mint(hacker, 10 ** 18, {"from": hacker})
46 |
--------------------------------------------------------------------------------
/lesson-05-interface/solution.diff:
--------------------------------------------------------------------------------
1 | 10a11,15
2 | > interface Token:
3 | > def mint(to_addr: address, amount: uint256): nonpayable
4 | >
5 | > token: public(Token)
6 | >
7 | 12c17,21
8 | < def mint():
9 | ---
10 | > def __init__(token_addr: address):
11 | > self.token = Token(token_addr)
12 | >
13 | > @external
14 | > def mint(quantity: uint256):
15 | 15c24
16 | < @dev This function is intentionally not implemented!
17 | ---
18 | > @param quantity Quantity to mint
19 | 17c26
20 | < pass
21 | ---
22 | > self.token.mint(msg.sender, quantity)
23 |
--------------------------------------------------------------------------------
/lesson-05-interface/solved/.gitattributes:
--------------------------------------------------------------------------------
1 | *.sol linguist-language=Solidity
2 | *.vy linguist-language=Python
3 |
--------------------------------------------------------------------------------
/lesson-05-interface/solved/.github/workflows/main.yaml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | pull_request:
4 | schedule:
5 | # run this workflow every Monday at 1PM UTC
6 | - cron: "* 13 * * 1"
7 |
8 | name: main workflow
9 |
10 | env:
11 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
12 |
13 | jobs:
14 |
15 | tests:
16 | runs-on: ubuntu-latest
17 |
18 | steps:
19 | - uses: actions/checkout@v2
20 |
21 | - name: Cache Compiler Installations
22 | uses: actions/cache@v2
23 | with:
24 | path: |
25 | ~/.solcx
26 | ~/.vvm
27 | key: compiler-cache
28 |
29 | - name: Setup Node.js
30 | uses: actions/setup-node@v1
31 |
32 | - name: Install Ganache
33 | run: npm install -g ganache-cli@6.10.1
34 |
35 | - name: Setup Python 3.8
36 | uses: actions/setup-python@v2
37 | with:
38 | python-version: 3.8
39 |
40 | - name: Install Requirements
41 | run: pip install -r requirements.txt
42 |
43 | - name: Run Tests
44 | run: brownie test -C
45 |
--------------------------------------------------------------------------------
/lesson-05-interface/solved/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 | .history
3 | .hypothesis/
4 | build/
5 | reports/
6 |
--------------------------------------------------------------------------------
/lesson-05-interface/solved/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Brownie Mixes
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/lesson-05-interface/solved/contracts/Minter.vy:
--------------------------------------------------------------------------------
1 | # @version 0.3.3
2 |
3 | """
4 | @title CRV Stablecoin Minter
5 | @license MIT
6 | @author Curve Finance
7 | @notice Mint a stablecoin backed by $CRV
8 | @dev Sample implementation of an ERC-20 backed stablecoin
9 | """
10 |
11 | interface Token:
12 | def mint(to_addr: address, amount: uint256): nonpayable
13 |
14 | token: public(Token)
15 |
16 | @external
17 | def __init__(token_addr: address):
18 | self.token = Token(token_addr)
19 |
20 | @external
21 | def mint(quantity: uint256):
22 | """
23 | @notice Mint a quantity of stablecoins
24 | @param quantity Quantity to mint
25 | """
26 | self.token.mint(msg.sender, quantity)
27 |
--------------------------------------------------------------------------------
/lesson-05-interface/solved/requirements.txt:
--------------------------------------------------------------------------------
1 | eth-brownie>=1.11.0,<2.0.0
2 |
--------------------------------------------------------------------------------
/lesson-05-interface/solved/scripts/token.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | from brownie import Token, accounts
4 |
5 |
6 | def main():
7 | return Token.deploy("Test Token", "TST", 18, 1e21, {'from': accounts[0]})
8 |
--------------------------------------------------------------------------------
/lesson-05-interface/solved/tests/conftest.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import pytest
4 |
5 |
6 | @pytest.fixture(scope="function", autouse=True)
7 | def isolate(fn_isolation):
8 | # perform a chain rewind after completing each test, to ensure proper isolation
9 | # https://eth-brownie.readthedocs.io/en/v1.10.3/tests-pytest-intro.html#isolation-fixtures
10 | pass
11 |
12 |
13 | @pytest.fixture(scope="module")
14 | def token(Token, accounts):
15 | return Token.deploy("Test Token", "TST", 18, 1e21, {"from": accounts[0]})
16 |
17 |
18 | @pytest.fixture(scope="module")
19 | def minter(Minter, accounts, token):
20 | minter = Minter.deploy(token, {"from": accounts[0]})
21 | token.set_minter(minter, {"from": token.owner()})
22 | return minter
23 |
--------------------------------------------------------------------------------
/lesson-05-interface/solved/tests/test_approve.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import pytest
4 |
5 |
6 | @pytest.mark.parametrize("idx", range(5))
7 | def test_initial_approval_is_zero(token, accounts, idx):
8 | assert token.allowance(accounts[0], accounts[idx]) == 0
9 |
10 |
11 | def test_approve(token, accounts):
12 | token.approve(accounts[1], 10 ** 19, {"from": accounts[0]})
13 |
14 | assert token.allowance(accounts[0], accounts[1]) == 10 ** 19
15 |
16 |
17 | def test_modify_approve(token, accounts):
18 | token.approve(accounts[1], 10 ** 19, {"from": accounts[0]})
19 | token.approve(accounts[1], 12345678, {"from": accounts[0]})
20 |
21 | assert token.allowance(accounts[0], accounts[1]) == 12345678
22 |
23 |
24 | def test_revoke_approve(token, accounts):
25 | token.approve(accounts[1], 10 ** 19, {"from": accounts[0]})
26 | token.approve(accounts[1], 0, {"from": accounts[0]})
27 |
28 | assert token.allowance(accounts[0], accounts[1]) == 0
29 |
30 |
31 | def test_approve_self(token, accounts):
32 | token.approve(accounts[0], 10 ** 19, {"from": accounts[0]})
33 |
34 | assert token.allowance(accounts[0], accounts[0]) == 10 ** 19
35 |
36 |
37 | def test_only_affects_target(token, accounts):
38 | token.approve(accounts[1], 10 ** 19, {"from": accounts[0]})
39 |
40 | assert token.allowance(accounts[1], accounts[0]) == 0
41 |
42 |
43 | def test_returns_true(token, accounts):
44 | tx = token.approve(accounts[1], 10 ** 19, {"from": accounts[0]})
45 |
46 | assert tx.return_value is True
47 |
48 |
49 | def test_approval_event_fires(accounts, token):
50 | tx = token.approve(accounts[1], 10 ** 19, {"from": accounts[0]})
51 |
52 | assert len(tx.events) == 1
53 | assert tx.events["Approval"].values() == [accounts[0], accounts[1], 10 ** 19]
54 |
--------------------------------------------------------------------------------
/lesson-05-interface/solved/tests/test_minter.py:
--------------------------------------------------------------------------------
1 | import brownie
2 | from brownie import *
3 |
4 |
5 | def test_minter_deployed(minter):
6 | assert hasattr(minter, "mint")
7 |
8 |
9 | def test_token_balance_updates_on_mint(token, minter, accounts):
10 | quantity = 10 ** 18
11 | init_bal = token.balanceOf(accounts[0])
12 |
13 | minter.mint(quantity, {"from": accounts[0]})
14 | assert token.balanceOf(accounts[0]) == init_bal + quantity
15 |
16 |
17 | def test_token_total_supply_updates_on_mint(token, minter, accounts):
18 | quantity = 10 ** 18
19 | init_supply = token.totalSupply()
20 |
21 | minter.mint(quantity, {"from": accounts[0]})
22 | assert token.totalSupply() == init_supply + quantity
23 |
24 |
25 | def test_owner_can_set_minter(token, accounts):
26 | assert token.minter() != accounts[1]
27 |
28 | token.set_minter(accounts[1], {"from": token.owner()})
29 | assert token.minter() == accounts[1]
30 |
31 |
32 | def test_nonowner_cannot_set_minter(token, accounts):
33 | hacker = accounts[1]
34 | assert hacker != token.owner()
35 |
36 | with brownie.reverts():
37 | token.set_minter(hacker, {"from": hacker})
38 |
39 |
40 | def test_nonminter_cannot_mint(token, accounts):
41 | hacker = accounts[1]
42 | assert hacker != token.minter()
43 |
44 | with brownie.reverts():
45 | token.mint(hacker, 10 ** 18, {"from": hacker})
46 |
--------------------------------------------------------------------------------
/lesson-05-interface/tests.diff:
--------------------------------------------------------------------------------
1 | diff -r ../lesson-04-assert/solved/tests/conftest.py unsolved/tests/conftest.py
2 | 19,20c19,22
3 | < def minter(Minter, accounts):
4 | < return Minter.deploy({"from": accounts[0]})
5 | ---
6 | > def minter(Minter, accounts, token):
7 | > minter = Minter.deploy(token, {"from": accounts[0]})
8 | > token.set_minter(minter, {"from": token.owner()})
9 | > return minter
10 | diff -r ../lesson-04-assert/solved/tests/test_minter.py unsolved/tests/test_minter.py
11 | 9c9
12 | < def test_token_balance_updates_on_mint(token, accounts):
13 | ---
14 | > def test_token_balance_updates_on_mint(token, minter, accounts):
15 | 13c13
16 | < token.mint(accounts[0], quantity, {"from": token.owner()})
17 | ---
18 | > minter.mint(quantity, {"from": accounts[0]})
19 | 17c17
20 | < def test_token_total_supply_updates_on_mint(token, accounts):
21 | ---
22 | > def test_token_total_supply_updates_on_mint(token, minter, accounts):
23 | 21c21
24 | < token.mint(accounts[0], quantity, {"from": token.owner()})
25 | ---
26 | > minter.mint(quantity, {"from": accounts[0]})
27 |
--------------------------------------------------------------------------------
/lesson-05-interface/unsolved/.gitattributes:
--------------------------------------------------------------------------------
1 | *.sol linguist-language=Solidity
2 | *.vy linguist-language=Python
3 |
--------------------------------------------------------------------------------
/lesson-05-interface/unsolved/.github/workflows/main.yaml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | pull_request:
4 | schedule:
5 | # run this workflow every Monday at 1PM UTC
6 | - cron: "* 13 * * 1"
7 |
8 | name: main workflow
9 |
10 | env:
11 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
12 |
13 | jobs:
14 |
15 | tests:
16 | runs-on: ubuntu-latest
17 |
18 | steps:
19 | - uses: actions/checkout@v2
20 |
21 | - name: Cache Compiler Installations
22 | uses: actions/cache@v2
23 | with:
24 | path: |
25 | ~/.solcx
26 | ~/.vvm
27 | key: compiler-cache
28 |
29 | - name: Setup Node.js
30 | uses: actions/setup-node@v1
31 |
32 | - name: Install Ganache
33 | run: npm install -g ganache-cli@6.10.1
34 |
35 | - name: Setup Python 3.8
36 | uses: actions/setup-python@v2
37 | with:
38 | python-version: 3.8
39 |
40 | - name: Install Requirements
41 | run: pip install -r requirements.txt
42 |
43 | - name: Run Tests
44 | run: brownie test -C
45 |
--------------------------------------------------------------------------------
/lesson-05-interface/unsolved/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 | .history
3 | .hypothesis/
4 | build/
5 | reports/
6 |
--------------------------------------------------------------------------------
/lesson-05-interface/unsolved/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Brownie Mixes
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/lesson-05-interface/unsolved/contracts/Minter.vy:
--------------------------------------------------------------------------------
1 | # @version 0.3.3
2 |
3 | """
4 | @title CRV Stablecoin Minter
5 | @license MIT
6 | @author Curve Finance
7 | @notice Mint a stablecoin backed by $CRV
8 | @dev Sample implementation of an ERC-20 backed stablecoin
9 | """
10 |
11 | @external
12 | def mint():
13 | """
14 | @notice Mint a quantity of stablecoins
15 | @dev This function is intentionally not implemented!
16 | """
17 | pass
18 |
--------------------------------------------------------------------------------
/lesson-05-interface/unsolved/requirements.txt:
--------------------------------------------------------------------------------
1 | eth-brownie>=1.11.0,<2.0.0
2 |
--------------------------------------------------------------------------------
/lesson-05-interface/unsolved/scripts/token.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | from brownie import Token, accounts
4 |
5 |
6 | def main():
7 | return Token.deploy("Test Token", "TST", 18, 1e21, {'from': accounts[0]})
8 |
--------------------------------------------------------------------------------
/lesson-05-interface/unsolved/tests/conftest.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import pytest
4 |
5 |
6 | @pytest.fixture(scope="function", autouse=True)
7 | def isolate(fn_isolation):
8 | # perform a chain rewind after completing each test, to ensure proper isolation
9 | # https://eth-brownie.readthedocs.io/en/v1.10.3/tests-pytest-intro.html#isolation-fixtures
10 | pass
11 |
12 |
13 | @pytest.fixture(scope="module")
14 | def token(Token, accounts):
15 | return Token.deploy("Test Token", "TST", 18, 1e21, {"from": accounts[0]})
16 |
17 |
18 | @pytest.fixture(scope="module")
19 | def minter(Minter, accounts, token):
20 | minter = Minter.deploy(token, {"from": accounts[0]})
21 | token.set_minter(minter, {"from": token.owner()})
22 | return minter
23 |
--------------------------------------------------------------------------------
/lesson-05-interface/unsolved/tests/test_minter.py:
--------------------------------------------------------------------------------
1 | import brownie
2 | from brownie import *
3 |
4 |
5 | def test_minter_deployed(minter):
6 | assert hasattr(minter, "mint")
7 |
8 |
9 | def test_token_balance_updates_on_mint(token, minter, accounts):
10 | quantity = 10 ** 18
11 | init_bal = token.balanceOf(accounts[0])
12 |
13 | minter.mint(quantity, {"from": accounts[0]})
14 | assert token.balanceOf(accounts[0]) == init_bal + quantity
15 |
16 |
17 | def test_token_total_supply_updates_on_mint(token, minter, accounts):
18 | quantity = 10 ** 18
19 | init_supply = token.totalSupply()
20 |
21 | minter.mint(quantity, {"from": accounts[0]})
22 | assert token.totalSupply() == init_supply + quantity
23 |
24 |
25 | def test_owner_can_set_minter(token, accounts):
26 | assert token.minter() != accounts[1]
27 |
28 | token.set_minter(accounts[1], {"from": token.owner()})
29 | assert token.minter() == accounts[1]
30 |
31 |
32 | def test_nonowner_cannot_set_minter(token, accounts):
33 | hacker = accounts[1]
34 | assert hacker != token.owner()
35 |
36 | with brownie.reverts():
37 | token.set_minter(hacker, {"from": hacker})
38 |
39 |
40 | def test_nonminter_cannot_mint(token, accounts):
41 | hacker = accounts[1]
42 | assert hacker != token.minter()
43 |
44 | with brownie.reverts():
45 | token.mint(hacker, 10 ** 18, {"from": hacker})
46 |
--------------------------------------------------------------------------------
/lesson-06-import/README.md:
--------------------------------------------------------------------------------
1 | # Lesson 6: Imports
2 |
3 | ## [🎥 Video 6 : Import 🎬](https://youtu.be/Scp9SEJj-FI)
4 |
5 | In this lesson we streamline our ability to work with other contracts by importing external interfaces directly.
6 |
7 | https://vyper.readthedocs.io/en/stable/interfaces.html
8 |
9 |
10 | ## IMPORTING INTERFACES
11 |
12 | In Vyper, you can directly import an interface stored within a file.
13 | Import a Vyper contract and automatically extract the interface for you.
14 |
15 |
16 | ### ABSOLUTE IMPORTS
17 |
18 | Absolute imports must include an alias for the package or the compiler will raise an error.
19 |
20 | import as
21 |
22 |
23 | ### RELATIVE IMPORTS
24 |
25 | You can also use the "from" syntax for relative or absolute imports
26 |
27 | from import
28 | from import as
29 |
30 |
31 | ### BUILT-IN INTERFACES
32 |
33 | Vyper includes common [built-in interfaces](https://github.com/vyperlang/vyper) for common token standards.
34 | For example, you can import an ERC20 token with a simple command:
35 |
36 | from vyper.interfaces import ERC20
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/lesson-06-import/solution.diff:
--------------------------------------------------------------------------------
1 | diff -r unsolved/contracts/Minter.vy solved/contracts/Minter.vy
2 | 1c1
3 | < # @version 0.3.3
4 | ---
5 | > # @version 0.3.7
6 | 11,12c11,12
7 | < interface Token:
8 | < def mint(to_addr: address, amount: uint256): nonpayable
9 | ---
10 | > import Token as Token
11 | > from vyper.interfaces import ERC20
12 | 14c14,15
13 | < token: public(Token)
14 | ---
15 | > stablecoin: public(Token) # $crvUSD
16 | > lending_token: public(ERC20) # $CRV
17 | 17,18c18,20
18 | < def __init__(token_addr: address):
19 | < self.token = Token(token_addr)
20 | ---
21 | > def __init__(token_addr: address, lending_token_addr: address):
22 | > self.stablecoin = Token(token_addr)
23 | > self.lending_token = ERC20(lending_token_addr)
24 | 26c28,31
25 | < self.token.mint(msg.sender, quantity)
26 | ---
27 | > assert self.lending_token.allowance(msg.sender, self) >= quantity, "Lacks Approval"
28 | > assert self.lending_token.balanceOf(msg.sender) >= quantity, "Lacks Balance"
29 | > self.lending_token.transferFrom(msg.sender, self, quantity)
30 | > self.stablecoin.mint(msg.sender, quantity)
31 | diff -r unsolved/contracts/Token.vy solved/contracts/Token.vy
32 | 1c1
33 | < # @version ^0.2.0
34 | ---
35 | > # @version ^0.3.7
36 |
--------------------------------------------------------------------------------
/lesson-06-import/solved/.gitattributes:
--------------------------------------------------------------------------------
1 | *.sol linguist-language=Solidity
2 | *.vy linguist-language=Python
3 |
--------------------------------------------------------------------------------
/lesson-06-import/solved/.github/workflows/main.yaml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | pull_request:
4 | schedule:
5 | # run this workflow every Monday at 1PM UTC
6 | - cron: "* 13 * * 1"
7 |
8 | name: main workflow
9 |
10 | env:
11 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
12 |
13 | jobs:
14 |
15 | tests:
16 | runs-on: ubuntu-latest
17 |
18 | steps:
19 | - uses: actions/checkout@v2
20 |
21 | - name: Cache Compiler Installations
22 | uses: actions/cache@v2
23 | with:
24 | path: |
25 | ~/.solcx
26 | ~/.vvm
27 | key: compiler-cache
28 |
29 | - name: Setup Node.js
30 | uses: actions/setup-node@v1
31 |
32 | - name: Install Ganache
33 | run: npm install -g ganache-cli@6.10.1
34 |
35 | - name: Setup Python 3.8
36 | uses: actions/setup-python@v2
37 | with:
38 | python-version: 3.8
39 |
40 | - name: Install Requirements
41 | run: pip install -r requirements.txt
42 |
43 | - name: Run Tests
44 | run: brownie test -C
45 |
--------------------------------------------------------------------------------
/lesson-06-import/solved/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 | .history
3 | .hypothesis/
4 | build/
5 | reports/
6 |
--------------------------------------------------------------------------------
/lesson-06-import/solved/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Brownie Mixes
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/lesson-06-import/solved/contracts/Minter.vy:
--------------------------------------------------------------------------------
1 | # @version 0.3.7
2 |
3 | """
4 | @title CRV Stablecoin Minter
5 | @license MIT
6 | @author Curve Finance
7 | @notice Mint a stablecoin backed by $CRV
8 | @dev Sample implementation of an ERC-20 backed stablecoin
9 | """
10 |
11 | import Token as Token
12 | from vyper.interfaces import ERC20
13 |
14 | stablecoin: public(Token) # $crvUSD
15 | lending_token: public(ERC20) # $CRV
16 |
17 | @external
18 | def __init__(token_addr: address, lending_token_addr: address):
19 | self.stablecoin = Token(token_addr)
20 | self.lending_token = ERC20(lending_token_addr)
21 |
22 | @external
23 | def mint(quantity: uint256):
24 | """
25 | @notice Mint a quantity of stablecoins
26 | @param quantity Quantity to mint
27 | """
28 | assert self.lending_token.allowance(msg.sender, self) >= quantity, "Lacks Approval"
29 | assert self.lending_token.balanceOf(msg.sender) >= quantity, "Lacks Balance"
30 | self.lending_token.transferFrom(msg.sender, self, quantity)
31 | self.stablecoin.mint(msg.sender, quantity)
32 |
--------------------------------------------------------------------------------
/lesson-06-import/solved/requirements.txt:
--------------------------------------------------------------------------------
1 | eth-brownie>=1.11.0,<2.0.0
2 |
--------------------------------------------------------------------------------
/lesson-06-import/solved/scripts/token.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | from brownie import Token, accounts
4 |
5 |
6 | def main():
7 | return Token.deploy("Test Token", "TST", 18, 1e21, {'from': accounts[0]})
8 |
--------------------------------------------------------------------------------
/lesson-06-import/solved/tests/conftest.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import pytest
4 | from brownie_tokens import MintableForkToken
5 | from brownie import network
6 |
7 | @pytest.fixture(scope="function", autouse=True)
8 | def isolate(fn_isolation):
9 | # perform a chain rewind after completing each test, to ensure proper isolation
10 | # https://eth-brownie.readthedocs.io/en/v1.10.3/tests-pytest-intro.html#isolation-fixtures
11 | pass
12 |
13 |
14 | @pytest.fixture(scope="module")
15 | def token(Token, accounts):
16 | return Token.deploy("Test Token", "TST", 18, 1e21, {"from": accounts[0]})
17 |
18 |
19 | @pytest.fixture(scope="module")
20 | def crv(accounts, Token):
21 | if 'fork' in network.show_active():
22 | crv = MintableForkToken('0xD533a949740bb3306d119CC777fa900bA034cd52')
23 | crv._mint_for_testing(accounts[0], 10 ** 18)
24 | else:
25 | crv = Token.deploy("Dummy CRV", "CRV", 18, 10 ** 18, {'from': accounts[0]})
26 | return crv
27 |
28 |
29 | @pytest.fixture(scope="module")
30 | def minter(Minter, accounts, token, crv):
31 | minter = Minter.deploy(token, crv, {"from": accounts[0]})
32 | token.set_minter(minter, {"from": token.owner()})
33 | crv.approve(minter, crv.balanceOf(accounts[0]), {'from': accounts[0]})
34 | return minter
35 |
--------------------------------------------------------------------------------
/lesson-06-import/solved/tests/test_approve.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import pytest
4 |
5 |
6 | @pytest.mark.parametrize("idx", range(5))
7 | def test_initial_approval_is_zero(token, accounts, idx):
8 | assert token.allowance(accounts[0], accounts[idx]) == 0
9 |
10 |
11 | def test_approve(token, accounts):
12 | token.approve(accounts[1], 10 ** 19, {"from": accounts[0]})
13 |
14 | assert token.allowance(accounts[0], accounts[1]) == 10 ** 19
15 |
16 |
17 | def test_modify_approve(token, accounts):
18 | token.approve(accounts[1], 10 ** 19, {"from": accounts[0]})
19 | token.approve(accounts[1], 12345678, {"from": accounts[0]})
20 |
21 | assert token.allowance(accounts[0], accounts[1]) == 12345678
22 |
23 |
24 | def test_revoke_approve(token, accounts):
25 | token.approve(accounts[1], 10 ** 19, {"from": accounts[0]})
26 | token.approve(accounts[1], 0, {"from": accounts[0]})
27 |
28 | assert token.allowance(accounts[0], accounts[1]) == 0
29 |
30 |
31 | def test_approve_self(token, accounts):
32 | token.approve(accounts[0], 10 ** 19, {"from": accounts[0]})
33 |
34 | assert token.allowance(accounts[0], accounts[0]) == 10 ** 19
35 |
36 |
37 | def test_only_affects_target(token, accounts):
38 | token.approve(accounts[1], 10 ** 19, {"from": accounts[0]})
39 |
40 | assert token.allowance(accounts[1], accounts[0]) == 0
41 |
42 |
43 | def test_returns_true(token, accounts):
44 | tx = token.approve(accounts[1], 10 ** 19, {"from": accounts[0]})
45 |
46 | assert tx.return_value is True
47 |
48 |
49 | def test_approval_event_fires(accounts, token):
50 | tx = token.approve(accounts[1], 10 ** 19, {"from": accounts[0]})
51 |
52 | assert len(tx.events) == 1
53 | assert tx.events["Approval"].values() == [accounts[0], accounts[1], 10 ** 19]
54 |
--------------------------------------------------------------------------------
/lesson-06-import/solved/tests/test_minter.py:
--------------------------------------------------------------------------------
1 | import brownie
2 | from brownie import *
3 |
4 |
5 | def test_minter_deployed(minter):
6 | assert hasattr(minter, "mint")
7 |
8 |
9 | def test_token_balance_updates_on_mint(token, minter, accounts):
10 | quantity = 10 ** 18
11 | init_bal = token.balanceOf(accounts[0])
12 |
13 | minter.mint(quantity, {"from": accounts[0]})
14 | assert token.balanceOf(accounts[0]) == init_bal + quantity
15 |
16 |
17 | def test_token_total_supply_updates_on_mint(token, minter, accounts):
18 | quantity = 10 ** 18
19 | init_supply = token.totalSupply()
20 |
21 | minter.mint(quantity, {"from": accounts[0]})
22 | assert token.totalSupply() == init_supply + quantity
23 |
24 |
25 | def test_owner_can_set_minter(token, accounts):
26 | assert token.minter() != accounts[1]
27 |
28 | token.set_minter(accounts[1], {"from": token.owner()})
29 | assert token.minter() == accounts[1]
30 |
31 |
32 | def test_nonowner_cannot_set_minter(token, accounts):
33 | hacker = accounts[1]
34 | assert hacker != token.owner()
35 |
36 | with brownie.reverts():
37 | token.set_minter(hacker, {"from": hacker})
38 |
39 |
40 | def test_nonminter_cannot_mint(token, accounts):
41 | hacker = accounts[1]
42 | assert hacker != token.minter()
43 |
44 | with brownie.reverts():
45 | token.mint(hacker, 10 ** 18, {"from": hacker})
46 |
47 | def test_crv_transfers_on_mint(token, crv, accounts, minter):
48 | init_bal = crv.balanceOf(accounts[0])
49 | minter.mint(10 ** 18, {"from": accounts[0]})
50 | assert crv.balanceOf(accounts[0]) < init_bal
51 |
--------------------------------------------------------------------------------
/lesson-06-import/tests.diff:
--------------------------------------------------------------------------------
1 | diff -r ../lesson-05-interface/solved/tests/conftest.py unsolved/tests/conftest.py
2 | 4c4,5
3 | <
4 | ---
5 | > from brownie_tokens import MintableForkToken
6 | > from brownie import network
7 | 19,20c20,31
8 | < def minter(Minter, accounts, token):
9 | < minter = Minter.deploy(token, {"from": accounts[0]})
10 | ---
11 | > def crv(accounts, Token):
12 | > if 'fork' in network.show_active():
13 | > crv = MintableForkToken('0xD533a949740bb3306d119CC777fa900bA034cd52')
14 | > crv._mint_for_testing(accounts[0], 10 ** 18)
15 | > else:
16 | > crv = Token.deploy("Dummy CRV", "CRV", 18, 10 ** 18, {'from': accounts[0]})
17 | > return crv
18 | >
19 | >
20 | > @pytest.fixture(scope="module")
21 | > def minter(Minter, accounts, token, crv):
22 | > minter = Minter.deploy(token, crv, {"from": accounts[0]})
23 | 21a33
24 | > crv.approve(minter, crv.balanceOf(accounts[0]), {'from': accounts[0]})
25 | diff -r ../lesson-05-interface/solved/tests/test_minter.py unsolved/tests/test_minter.py
26 | 45a46,50
27 | >
28 | > def test_crv_transfers_on_mint(token, crv, accounts, minter):
29 | > init_bal = crv.balanceOf(accounts[0])
30 | > minter.mint(10 ** 18, {"from": accounts[0]})
31 | > assert crv.balanceOf(accounts[0]) < init_bal
32 |
--------------------------------------------------------------------------------
/lesson-06-import/unsolved/.gitattributes:
--------------------------------------------------------------------------------
1 | *.sol linguist-language=Solidity
2 | *.vy linguist-language=Python
3 |
--------------------------------------------------------------------------------
/lesson-06-import/unsolved/.github/workflows/main.yaml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | pull_request:
4 | schedule:
5 | # run this workflow every Monday at 1PM UTC
6 | - cron: "* 13 * * 1"
7 |
8 | name: main workflow
9 |
10 | env:
11 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
12 |
13 | jobs:
14 |
15 | tests:
16 | runs-on: ubuntu-latest
17 |
18 | steps:
19 | - uses: actions/checkout@v2
20 |
21 | - name: Cache Compiler Installations
22 | uses: actions/cache@v2
23 | with:
24 | path: |
25 | ~/.solcx
26 | ~/.vvm
27 | key: compiler-cache
28 |
29 | - name: Setup Node.js
30 | uses: actions/setup-node@v1
31 |
32 | - name: Install Ganache
33 | run: npm install -g ganache-cli@6.10.1
34 |
35 | - name: Setup Python 3.8
36 | uses: actions/setup-python@v2
37 | with:
38 | python-version: 3.8
39 |
40 | - name: Install Requirements
41 | run: pip install -r requirements.txt
42 |
43 | - name: Run Tests
44 | run: brownie test -C
45 |
--------------------------------------------------------------------------------
/lesson-06-import/unsolved/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 | .history
3 | .hypothesis/
4 | build/
5 | reports/
6 |
--------------------------------------------------------------------------------
/lesson-06-import/unsolved/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Brownie Mixes
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/lesson-06-import/unsolved/contracts/Minter.vy:
--------------------------------------------------------------------------------
1 | # @version 0.3.3
2 |
3 | """
4 | @title CRV Stablecoin Minter
5 | @license MIT
6 | @author Curve Finance
7 | @notice Mint a stablecoin backed by $CRV
8 | @dev Sample implementation of an ERC-20 backed stablecoin
9 | """
10 |
11 | interface Token:
12 | def mint(to_addr: address, amount: uint256): nonpayable
13 |
14 | token: public(Token)
15 |
16 | @external
17 | def __init__(token_addr: address):
18 | self.token = Token(token_addr)
19 |
20 | @external
21 | def mint(quantity: uint256):
22 | """
23 | @notice Mint a quantity of stablecoins
24 | @param quantity Quantity to mint
25 | """
26 | self.token.mint(msg.sender, quantity)
27 |
--------------------------------------------------------------------------------
/lesson-06-import/unsolved/requirements.txt:
--------------------------------------------------------------------------------
1 | eth-brownie>=1.11.0,<2.0.0
2 |
--------------------------------------------------------------------------------
/lesson-06-import/unsolved/scripts/token.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | from brownie import Token, accounts
4 |
5 |
6 | def main():
7 | return Token.deploy("Test Token", "TST", 18, 1e21, {'from': accounts[0]})
8 |
--------------------------------------------------------------------------------
/lesson-06-import/unsolved/tests/.conftest.py.swp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/curvefi/vyper-tutorial/c7f45d979d6a87e90a3d1ce9912d4f47c5bf8aa2/lesson-06-import/unsolved/tests/.conftest.py.swp
--------------------------------------------------------------------------------
/lesson-06-import/unsolved/tests/.test_minter.py.swp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/curvefi/vyper-tutorial/c7f45d979d6a87e90a3d1ce9912d4f47c5bf8aa2/lesson-06-import/unsolved/tests/.test_minter.py.swp
--------------------------------------------------------------------------------
/lesson-06-import/unsolved/tests/conftest.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import pytest
4 | from brownie_tokens import MintableForkToken
5 | from brownie import network
6 |
7 | @pytest.fixture(scope="function", autouse=True)
8 | def isolate(fn_isolation):
9 | # perform a chain rewind after completing each test, to ensure proper isolation
10 | # https://eth-brownie.readthedocs.io/en/v1.10.3/tests-pytest-intro.html#isolation-fixtures
11 | pass
12 |
13 |
14 | @pytest.fixture(scope="module")
15 | def token(Token, accounts):
16 | return Token.deploy("Test Token", "TST", 18, 1e21, {"from": accounts[0]})
17 |
18 |
19 | @pytest.fixture(scope="module")
20 | def crv(accounts, Token):
21 | if 'fork' in network.show_active():
22 | crv = MintableForkToken('0xD533a949740bb3306d119CC777fa900bA034cd52')
23 | crv._mint_for_testing(accounts[0], 10 ** 18)
24 | else:
25 | crv = Token.deploy("Dummy CRV", "CRV", 18, 10 ** 18, {'from': accounts[0]})
26 | return crv
27 |
28 |
29 | @pytest.fixture(scope="module")
30 | def minter(Minter, accounts, token, crv):
31 | minter = Minter.deploy(token, crv, {"from": accounts[0]})
32 | token.set_minter(minter, {"from": token.owner()})
33 | crv.approve(minter, crv.balanceOf(accounts[0]), {'from': accounts[0]})
34 | return minter
35 |
--------------------------------------------------------------------------------
/lesson-06-import/unsolved/tests/test_approve.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import pytest
4 |
5 |
6 | @pytest.mark.parametrize("idx", range(5))
7 | def test_initial_approval_is_zero(token, accounts, idx):
8 | assert token.allowance(accounts[0], accounts[idx]) == 0
9 |
10 |
11 | def test_approve(token, accounts):
12 | token.approve(accounts[1], 10 ** 19, {"from": accounts[0]})
13 |
14 | assert token.allowance(accounts[0], accounts[1]) == 10 ** 19
15 |
16 |
17 | def test_modify_approve(token, accounts):
18 | token.approve(accounts[1], 10 ** 19, {"from": accounts[0]})
19 | token.approve(accounts[1], 12345678, {"from": accounts[0]})
20 |
21 | assert token.allowance(accounts[0], accounts[1]) == 12345678
22 |
23 |
24 | def test_revoke_approve(token, accounts):
25 | token.approve(accounts[1], 10 ** 19, {"from": accounts[0]})
26 | token.approve(accounts[1], 0, {"from": accounts[0]})
27 |
28 | assert token.allowance(accounts[0], accounts[1]) == 0
29 |
30 |
31 | def test_approve_self(token, accounts):
32 | token.approve(accounts[0], 10 ** 19, {"from": accounts[0]})
33 |
34 | assert token.allowance(accounts[0], accounts[0]) == 10 ** 19
35 |
36 |
37 | def test_only_affects_target(token, accounts):
38 | token.approve(accounts[1], 10 ** 19, {"from": accounts[0]})
39 |
40 | assert token.allowance(accounts[1], accounts[0]) == 0
41 |
42 |
43 | def test_returns_true(token, accounts):
44 | tx = token.approve(accounts[1], 10 ** 19, {"from": accounts[0]})
45 |
46 | assert tx.return_value is True
47 |
48 |
49 | def test_approval_event_fires(accounts, token):
50 | tx = token.approve(accounts[1], 10 ** 19, {"from": accounts[0]})
51 |
52 | assert len(tx.events) == 1
53 | assert tx.events["Approval"].values() == [accounts[0], accounts[1], 10 ** 19]
54 |
--------------------------------------------------------------------------------
/lesson-06-import/unsolved/tests/test_minter.py:
--------------------------------------------------------------------------------
1 | import brownie
2 | from brownie import *
3 |
4 |
5 | def test_minter_deployed(minter):
6 | assert hasattr(minter, "mint")
7 |
8 |
9 | def test_token_balance_updates_on_mint(token, minter, accounts):
10 | quantity = 10 ** 18
11 | init_bal = token.balanceOf(accounts[0])
12 |
13 | minter.mint(quantity, {"from": accounts[0]})
14 | assert token.balanceOf(accounts[0]) == init_bal + quantity
15 |
16 |
17 | def test_token_total_supply_updates_on_mint(token, minter, accounts):
18 | quantity = 10 ** 18
19 | init_supply = token.totalSupply()
20 |
21 | minter.mint(quantity, {"from": accounts[0]})
22 | assert token.totalSupply() == init_supply + quantity
23 |
24 |
25 | def test_owner_can_set_minter(token, accounts):
26 | assert token.minter() != accounts[1]
27 |
28 | token.set_minter(accounts[1], {"from": token.owner()})
29 | assert token.minter() == accounts[1]
30 |
31 |
32 | def test_nonowner_cannot_set_minter(token, accounts):
33 | hacker = accounts[1]
34 | assert hacker != token.owner()
35 |
36 | with brownie.reverts():
37 | token.set_minter(hacker, {"from": hacker})
38 |
39 |
40 | def test_nonminter_cannot_mint(token, accounts):
41 | hacker = accounts[1]
42 | assert hacker != token.minter()
43 |
44 | with brownie.reverts():
45 | token.mint(hacker, 10 ** 18, {"from": hacker})
46 |
47 | def test_crv_transfers_on_mint(token, crv, accounts, minter):
48 | init_bal = crv.balanceOf(accounts[0])
49 | minter.mint(10 ** 18, {"from": accounts[0]})
50 | assert crv.balanceOf(accounts[0]) < init_bal
51 |
--------------------------------------------------------------------------------
/lesson-07-scope/README.md:
--------------------------------------------------------------------------------
1 | # Lesson 7: Variable Scope
2 |
3 | ## [🎥 Video 7 : Scope 🎬](https://youtu.be/w__9qQh_taE)
4 |
5 | In this lesson we'll fetch some basic price data for $CRV to serve as the oracle price. In the process we'll cover topics such as returns, function scope decorators, and local variables.
6 |
7 | Note that now we are working against contracts deployed to mainnet, so tests will work better if you run them using a mainnet fork. We also recommend installing Brownie Token Tester. Full instructions are available in unit 6 of the accompanying [Brownie Tutorial](https://github.com/curvefi/brownie-tutorial/tree/main/lesson-06-tokens).
8 |
9 |
10 | ## INTERNAL FUNCTIONS
11 |
12 | A function decorated with an [@internal decorator](https://vyper.readthedocs.io/en/stable/scoping-and-declarations.html) can only be called from within the contract.
13 | Internal functions can be called via the `self` object, and must be called after they are defined.
14 |
15 |
16 | ## VARIABLE SCOPE
17 |
18 | Vyper has three types of variables, accessible within [different scopes](https://vyper.readthedocs.io/en/stable/scoping-and-declarations.html).
19 |
20 | * Storage: data written to the blockchain, globally accessible via the `self` object
21 | * Memory: helper variables used within the scope of a single function and discarded
22 | * Calldata: arguments passed to a function by the contract caller, then discarded
23 |
--------------------------------------------------------------------------------
/lesson-07-scope/solution.diff:
--------------------------------------------------------------------------------
1 | 13a14,21
2 | > interface CRVOracle:
3 | > def price_oracle() -> uint256: view
4 | >
5 | > interface ETHOracle:
6 | > def price_oracle(arg: uint256) -> uint256: view
7 | >
8 | > # Token Addresses
9 | >
10 | 16a25,29
11 | > # Oracle Addresses
12 | >
13 | > crv_eth_oracle: public(CRVOracle)
14 | > eth_usd_oracle: public(ETHOracle)
15 | >
16 | 21a35,51
17 | > # Price Oracles
18 | > self.crv_eth_oracle = CRVOracle(0x8301AE4fc9c624d1D396cbDAa1ed877821D7C511)
19 | > self.eth_usd_oracle = ETHOracle(0xD51a44d3FaE010294C616388b506AcdA1bfAAE46)
20 | >
21 | >
22 | > @internal
23 | > @view
24 | > def _price_usd() -> uint256:
25 | > token_price_eth: uint256 = self.crv_eth_oracle.price_oracle()
26 | > eth_price_usd: uint256 = self.eth_usd_oracle.price_oracle(1)
27 | > return token_price_eth * eth_price_usd / 10 ** 18
28 | >
29 | > @external
30 | > @view
31 | > def price_usd() -> uint256:
32 | > return self._price_usd()
33 | >
34 |
--------------------------------------------------------------------------------
/lesson-07-scope/solved/.gitattributes:
--------------------------------------------------------------------------------
1 | *.sol linguist-language=Solidity
2 | *.vy linguist-language=Python
3 |
--------------------------------------------------------------------------------
/lesson-07-scope/solved/.github/workflows/main.yaml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | pull_request:
4 | schedule:
5 | # run this workflow every Monday at 1PM UTC
6 | - cron: "* 13 * * 1"
7 |
8 | name: main workflow
9 |
10 | env:
11 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
12 |
13 | jobs:
14 |
15 | tests:
16 | runs-on: ubuntu-latest
17 |
18 | steps:
19 | - uses: actions/checkout@v2
20 |
21 | - name: Cache Compiler Installations
22 | uses: actions/cache@v2
23 | with:
24 | path: |
25 | ~/.solcx
26 | ~/.vvm
27 | key: compiler-cache
28 |
29 | - name: Setup Node.js
30 | uses: actions/setup-node@v1
31 |
32 | - name: Install Ganache
33 | run: npm install -g ganache-cli@6.10.1
34 |
35 | - name: Setup Python 3.8
36 | uses: actions/setup-python@v2
37 | with:
38 | python-version: 3.8
39 |
40 | - name: Install Requirements
41 | run: pip install -r requirements.txt
42 |
43 | - name: Run Tests
44 | run: brownie test -C
45 |
--------------------------------------------------------------------------------
/lesson-07-scope/solved/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 | .history
3 | .hypothesis/
4 | build/
5 | reports/
6 |
--------------------------------------------------------------------------------
/lesson-07-scope/solved/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Brownie Mixes
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/lesson-07-scope/solved/brownie_config.yaml:
--------------------------------------------------------------------------------
1 | autofetch_sources: true
2 |
--------------------------------------------------------------------------------
/lesson-07-scope/solved/requirements.txt:
--------------------------------------------------------------------------------
1 | eth-brownie>=1.11.0,<2.0.0
2 |
--------------------------------------------------------------------------------
/lesson-07-scope/solved/scripts/token.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | from brownie import Token, accounts
4 |
5 |
6 | def main():
7 | return Token.deploy("Test Token", "TST", 18, 1e21, {'from': accounts[0]})
8 |
--------------------------------------------------------------------------------
/lesson-07-scope/solved/tests/conftest.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import pytest
4 | from brownie_tokens import MintableForkToken
5 | from brownie import network
6 |
7 | @pytest.fixture(scope="function", autouse=True)
8 | def isolate(fn_isolation):
9 | # perform a chain rewind after completing each test, to ensure proper isolation
10 | # https://eth-brownie.readthedocs.io/en/v1.10.3/tests-pytest-intro.html#isolation-fixtures
11 | pass
12 |
13 |
14 | @pytest.fixture(scope="module")
15 | def token(Token, accounts):
16 | return Token.deploy("Test Token", "TST", 18, 1e21, {"from": accounts[0]})
17 |
18 | @pytest.fixture(scope="module")
19 | def is_forked():
20 | if 'fork' in network.show_active():
21 | return True
22 | else:
23 | return False
24 |
25 | @pytest.fixture(scope="module")
26 | def crv(accounts, Token, is_forked):
27 | if is_forked:
28 | crv = MintableForkToken('0xD533a949740bb3306d119CC777fa900bA034cd52')
29 | crv._mint_for_testing(accounts[0], 10 ** 18)
30 | else:
31 | crv = Token.deploy("Dummy CRV", "CRV", 18, 10 ** 18, {'from': accounts[0]})
32 | return crv
33 |
34 |
35 | @pytest.fixture(scope="module")
36 | def minter(Minter, accounts, token, crv):
37 | minter = Minter.deploy(token, crv, {"from": accounts[0]})
38 | token.set_minter(minter, {"from": token.owner()})
39 | crv.approve(minter, crv.balanceOf(accounts[0]), {'from': accounts[0]})
40 | return minter
41 |
--------------------------------------------------------------------------------
/lesson-07-scope/solved/tests/test_approve.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import pytest
4 |
5 |
6 | @pytest.mark.parametrize("idx", range(5))
7 | def test_initial_approval_is_zero(token, accounts, idx):
8 | assert token.allowance(accounts[0], accounts[idx]) == 0
9 |
10 |
11 | def test_approve(token, accounts):
12 | token.approve(accounts[1], 10 ** 19, {"from": accounts[0]})
13 |
14 | assert token.allowance(accounts[0], accounts[1]) == 10 ** 19
15 |
16 |
17 | def test_modify_approve(token, accounts):
18 | token.approve(accounts[1], 10 ** 19, {"from": accounts[0]})
19 | token.approve(accounts[1], 12345678, {"from": accounts[0]})
20 |
21 | assert token.allowance(accounts[0], accounts[1]) == 12345678
22 |
23 |
24 | def test_revoke_approve(token, accounts):
25 | token.approve(accounts[1], 10 ** 19, {"from": accounts[0]})
26 | token.approve(accounts[1], 0, {"from": accounts[0]})
27 |
28 | assert token.allowance(accounts[0], accounts[1]) == 0
29 |
30 |
31 | def test_approve_self(token, accounts):
32 | token.approve(accounts[0], 10 ** 19, {"from": accounts[0]})
33 |
34 | assert token.allowance(accounts[0], accounts[0]) == 10 ** 19
35 |
36 |
37 | def test_only_affects_target(token, accounts):
38 | token.approve(accounts[1], 10 ** 19, {"from": accounts[0]})
39 |
40 | assert token.allowance(accounts[1], accounts[0]) == 0
41 |
42 |
43 | def test_returns_true(token, accounts):
44 | tx = token.approve(accounts[1], 10 ** 19, {"from": accounts[0]})
45 |
46 | assert tx.return_value is True
47 |
48 |
49 | def test_approval_event_fires(accounts, token):
50 | tx = token.approve(accounts[1], 10 ** 19, {"from": accounts[0]})
51 |
52 | assert len(tx.events) == 1
53 | assert tx.events["Approval"].values() == [accounts[0], accounts[1], 10 ** 19]
54 |
--------------------------------------------------------------------------------
/lesson-07-scope/solved/tests/test_lend.py:
--------------------------------------------------------------------------------
1 | from brownie import *
2 | import requests, json
3 | import pytest
4 |
5 | def test_price_oracle_function_exists(minter):
6 | assert hasattr(minter, 'price_usd')
7 |
8 | def test_price_oracle_returns_value(minter, is_forked):
9 | if not is_forked:
10 | pytest.skip()
11 | url = f"https://api.coingecko.com/api/v3/simple/price?ids=curve-dao-token&vs_currencies=usd"
12 | price = json.loads(requests.get(url).content).get('curve-dao-token').get('usd')
13 | assert (minter.price_usd() / 10 ** 18) > price * .95
14 | assert (minter.price_usd() / 10 ** 18) < price * 1.05
15 |
--------------------------------------------------------------------------------
/lesson-07-scope/solved/tests/test_minter.py:
--------------------------------------------------------------------------------
1 | import brownie
2 | from brownie import *
3 |
4 |
5 | def test_minter_deployed(minter):
6 | assert hasattr(minter, "mint")
7 |
8 |
9 | def test_token_balance_updates_on_mint(token, minter, accounts):
10 | quantity = 10 ** 18
11 | init_bal = token.balanceOf(accounts[0])
12 |
13 | minter.mint(quantity, {"from": accounts[0]})
14 | assert token.balanceOf(accounts[0]) == init_bal + quantity
15 |
16 |
17 | def test_token_total_supply_updates_on_mint(token, minter, accounts):
18 | quantity = 10 ** 18
19 | init_supply = token.totalSupply()
20 |
21 | minter.mint(quantity, {"from": accounts[0]})
22 | assert token.totalSupply() == init_supply + quantity
23 |
24 |
25 | def test_owner_can_set_minter(token, accounts):
26 | assert token.minter() != accounts[1]
27 |
28 | token.set_minter(accounts[1], {"from": token.owner()})
29 | assert token.minter() == accounts[1]
30 |
31 |
32 | def test_nonowner_cannot_set_minter(token, accounts):
33 | hacker = accounts[1]
34 | assert hacker != token.owner()
35 |
36 | with brownie.reverts():
37 | token.set_minter(hacker, {"from": hacker})
38 |
39 |
40 | def test_nonminter_cannot_mint(token, accounts):
41 | hacker = accounts[1]
42 | assert hacker != token.minter()
43 |
44 | with brownie.reverts():
45 | token.mint(hacker, 10 ** 18, {"from": hacker})
46 |
47 | def test_crv_transfers_on_mint(token, crv, accounts, minter):
48 | init_bal = crv.balanceOf(accounts[0])
49 | minter.mint(10 ** 18, {"from": accounts[0]})
50 | assert crv.balanceOf(accounts[0]) < init_bal
51 |
--------------------------------------------------------------------------------
/lesson-07-scope/tests.diff:
--------------------------------------------------------------------------------
1 | diff ../lesson-06-import/solved/tests/conftest.py unsolved/tests/conftest.py
2 | 18d17
3 | <
4 | 20c19
5 | < def crv(accounts, Token):
6 | ---
7 | > def is_forked():
8 | 21a21,27
9 | > return True
10 | > else:
11 | > return False
12 | >
13 | > @pytest.fixture(scope="module")
14 | > def crv(accounts, Token, is_forked):
15 | > if is_forked:
16 | Only in unsolved/tests/: test_lend.py
17 |
--------------------------------------------------------------------------------
/lesson-07-scope/unsolved/.gitattributes:
--------------------------------------------------------------------------------
1 | *.sol linguist-language=Solidity
2 | *.vy linguist-language=Python
3 |
--------------------------------------------------------------------------------
/lesson-07-scope/unsolved/.github/workflows/main.yaml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | pull_request:
4 | schedule:
5 | # run this workflow every Monday at 1PM UTC
6 | - cron: "* 13 * * 1"
7 |
8 | name: main workflow
9 |
10 | env:
11 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
12 |
13 | jobs:
14 |
15 | tests:
16 | runs-on: ubuntu-latest
17 |
18 | steps:
19 | - uses: actions/checkout@v2
20 |
21 | - name: Cache Compiler Installations
22 | uses: actions/cache@v2
23 | with:
24 | path: |
25 | ~/.solcx
26 | ~/.vvm
27 | key: compiler-cache
28 |
29 | - name: Setup Node.js
30 | uses: actions/setup-node@v1
31 |
32 | - name: Install Ganache
33 | run: npm install -g ganache-cli@6.10.1
34 |
35 | - name: Setup Python 3.8
36 | uses: actions/setup-python@v2
37 | with:
38 | python-version: 3.8
39 |
40 | - name: Install Requirements
41 | run: pip install -r requirements.txt
42 |
43 | - name: Run Tests
44 | run: brownie test -C
45 |
--------------------------------------------------------------------------------
/lesson-07-scope/unsolved/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 | .history
3 | .hypothesis/
4 | build/
5 | reports/
6 |
--------------------------------------------------------------------------------
/lesson-07-scope/unsolved/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Brownie Mixes
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/lesson-07-scope/unsolved/brownie_config.yaml:
--------------------------------------------------------------------------------
1 | autofetch_sources: true
2 |
--------------------------------------------------------------------------------
/lesson-07-scope/unsolved/contracts/Minter.vy:
--------------------------------------------------------------------------------
1 | # @version 0.3.7
2 |
3 | """
4 | @title CRV Stablecoin Minter
5 | @license MIT
6 | @author Curve Finance
7 | @notice Mint a stablecoin backed by $CRV
8 | @dev Sample implementation of an ERC-20 backed stablecoin
9 | """
10 |
11 | import Token as Token
12 | from vyper.interfaces import ERC20
13 |
14 | stablecoin: public(Token) # $crvUSD
15 | lending_token: public(ERC20) # $CRV
16 |
17 | @external
18 | def __init__(token_addr: address, lending_token_addr: address):
19 | self.stablecoin = Token(token_addr)
20 | self.lending_token = ERC20(lending_token_addr)
21 |
22 | @external
23 | def mint(quantity: uint256):
24 | """
25 | @notice Mint a quantity of stablecoins
26 | @param quantity Quantity to mint
27 | """
28 | assert self.lending_token.allowance(msg.sender, self) >= quantity, "Lacks Approval"
29 | assert self.lending_token.balanceOf(msg.sender) >= quantity, "Lacks Balance"
30 | self.lending_token.transferFrom(msg.sender, self, quantity)
31 | self.stablecoin.mint(msg.sender, quantity)
32 |
--------------------------------------------------------------------------------
/lesson-07-scope/unsolved/requirements.txt:
--------------------------------------------------------------------------------
1 | eth-brownie>=1.11.0,<2.0.0
2 |
--------------------------------------------------------------------------------
/lesson-07-scope/unsolved/scripts/token.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | from brownie import Token, accounts
4 |
5 |
6 | def main():
7 | return Token.deploy("Test Token", "TST", 18, 1e21, {'from': accounts[0]})
8 |
--------------------------------------------------------------------------------
/lesson-07-scope/unsolved/tests/conftest.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import pytest
4 | from brownie_tokens import MintableForkToken
5 | from brownie import network
6 |
7 | @pytest.fixture(scope="function", autouse=True)
8 | def isolate(fn_isolation):
9 | # perform a chain rewind after completing each test, to ensure proper isolation
10 | # https://eth-brownie.readthedocs.io/en/v1.10.3/tests-pytest-intro.html#isolation-fixtures
11 | pass
12 |
13 |
14 | @pytest.fixture(scope="module")
15 | def token(Token, accounts):
16 | return Token.deploy("Test Token", "TST", 18, 1e21, {"from": accounts[0]})
17 |
18 | @pytest.fixture(scope="module")
19 | def is_forked():
20 | if 'fork' in network.show_active():
21 | return True
22 | else:
23 | return False
24 |
25 | @pytest.fixture(scope="module")
26 | def crv(accounts, Token, is_forked):
27 | if is_forked:
28 | crv = MintableForkToken('0xD533a949740bb3306d119CC777fa900bA034cd52')
29 | crv._mint_for_testing(accounts[0], 10 ** 18)
30 | else:
31 | crv = Token.deploy("Dummy CRV", "CRV", 18, 10 ** 18, {'from': accounts[0]})
32 | return crv
33 |
34 |
35 | @pytest.fixture(scope="module")
36 | def minter(Minter, accounts, token, crv):
37 | minter = Minter.deploy(token, crv, {"from": accounts[0]})
38 | token.set_minter(minter, {"from": token.owner()})
39 | crv.approve(minter, crv.balanceOf(accounts[0]), {'from': accounts[0]})
40 | return minter
41 |
--------------------------------------------------------------------------------
/lesson-07-scope/unsolved/tests/test_lend.py:
--------------------------------------------------------------------------------
1 | from brownie import *
2 | import requests, json
3 | import pytest
4 |
5 | def test_price_oracle_function_exists(minter):
6 | assert hasattr(minter, 'price_usd')
7 |
8 | def test_price_oracle_returns_value(minter, is_forked):
9 | if not is_forked:
10 | pytest.skip()
11 | url = f"https://api.coingecko.com/api/v3/simple/price?ids=curve-dao-token&vs_currencies=usd"
12 | price = json.loads(requests.get(url).content).get('curve-dao-token').get('usd')
13 | assert (minter.price_usd() / 10 ** 18) > price * .95
14 | assert (minter.price_usd() / 10 ** 18) < price * 1.05
15 |
--------------------------------------------------------------------------------
/lesson-07-scope/unsolved/tests/test_minter.py:
--------------------------------------------------------------------------------
1 | import brownie
2 | from brownie import *
3 |
4 |
5 | def test_minter_deployed(minter):
6 | assert hasattr(minter, "mint")
7 |
8 |
9 | def test_token_balance_updates_on_mint(token, minter, accounts):
10 | quantity = 10 ** 18
11 | init_bal = token.balanceOf(accounts[0])
12 |
13 | minter.mint(quantity, {"from": accounts[0]})
14 | assert token.balanceOf(accounts[0]) == init_bal + quantity
15 |
16 |
17 | def test_token_total_supply_updates_on_mint(token, minter, accounts):
18 | quantity = 10 ** 18
19 | init_supply = token.totalSupply()
20 |
21 | minter.mint(quantity, {"from": accounts[0]})
22 | assert token.totalSupply() == init_supply + quantity
23 |
24 |
25 | def test_owner_can_set_minter(token, accounts):
26 | assert token.minter() != accounts[1]
27 |
28 | token.set_minter(accounts[1], {"from": token.owner()})
29 | assert token.minter() == accounts[1]
30 |
31 |
32 | def test_nonowner_cannot_set_minter(token, accounts):
33 | hacker = accounts[1]
34 | assert hacker != token.owner()
35 |
36 | with brownie.reverts():
37 | token.set_minter(hacker, {"from": hacker})
38 |
39 |
40 | def test_nonminter_cannot_mint(token, accounts):
41 | hacker = accounts[1]
42 | assert hacker != token.minter()
43 |
44 | with brownie.reverts():
45 | token.mint(hacker, 10 ** 18, {"from": hacker})
46 |
47 | def test_crv_transfers_on_mint(token, crv, accounts, minter):
48 | init_bal = crv.balanceOf(accounts[0])
49 | minter.mint(10 ** 18, {"from": accounts[0]})
50 | assert crv.balanceOf(accounts[0]) < init_bal
51 |
--------------------------------------------------------------------------------
/lesson-08-math/README.md:
--------------------------------------------------------------------------------
1 | # Lesson 8: Integer Math
2 |
3 | ## [🎥 Video 8 : Math 🎬](https://youtu.be/YL1Dg4y9CVs)
4 |
5 | In this lesson we're adding a `get_dy` function, which computes the number of $crvUSD stablecoins received for `dx`, an input of the collateral token ($CRV).
6 |
7 | In the process the lesson highlights some of the intricacies of working with integer arithmetic within smart contracts. A supplemental file TestMath.vy displayed in the video is included in the `contracts` directory.
8 |
9 | Vyper contains additional advanced math functions. For further reading:
10 |
11 | * [Vyper Built In Functions](https://vyper.readthedocs.io/en/stable/built-in-functions.html)
12 | * [Curve Optimized Library](https://github.com/bout3fiddy/boa-tricrypto/blob/main/contracts/CurveCryptoMathOptimized3.vy)
13 |
14 |
15 | ## INTEGER MATH
16 |
17 | [Arithmetic in Vyper](https://vyper.readthedocs.io/en/stable/types.html#arithmetic-operators) uses the basic operators from Python.
18 | All arithmetic operators utilize safemath by default and revert on overflow.
19 |
20 | * `x + y` Addition
21 | * `x - y` Subtraction
22 | * `-x` Unary minus/Negation
23 | * `x * y` Multiplication
24 | * `x / y` Integer Division
25 | * `x ** y` Exponentiation
26 | * `x % y` Modulo
27 |
28 |
29 | ## DECIMAL TYPES
30 |
31 | Vyper supports a [decimal variable type](https://vyper.readthedocs.io/en/stable/types.html#decimals) that renders as `fixed168x10` in the ABI.
32 | This tutorial instead follows the convention of shifting 18 decimal places and rounding it off as a uint.
33 |
--------------------------------------------------------------------------------
/lesson-08-math/solution.diff:
--------------------------------------------------------------------------------
1 | 28a29,32
2 | > # Collateralization
3 | > collateral_pct: public(uint256)
4 | >
5 | >
6 | 37a42,44
7 | > # Collateralization
8 | > self.collateral_pct = 10 ** 18 * 4 / 5
9 | >
10 | 45a53,60
11 | >
12 | > @internal
13 | > @view
14 | > def _get_dy(quantity: uint256) -> uint256:
15 | > liq_price: uint256 = self._price_usd() * self.collateral_pct / 10 ** 18
16 | > return quantity * liq_price / 10 ** 18
17 | >
18 | >
19 | 54a70,81
20 | >
21 | > @external
22 | > @view
23 | > def get_dy(quantity:uint256) -> uint256:
24 | > """
25 | > @notice Get amount of USD for a deposit of $CRV
26 | > @param quantity $CRV tokens to deposit
27 | > @return Number of $crvUSD returned
28 | > """
29 | > return self._get_dy(quantity)
30 | >
31 | >
32 | 65c92
33 | < self.stablecoin.mint(msg.sender, quantity)
34 | ---
35 | > self.stablecoin.mint(msg.sender, self._get_dy(quantity))
36 |
--------------------------------------------------------------------------------
/lesson-08-math/solved/.gitattributes:
--------------------------------------------------------------------------------
1 | *.sol linguist-language=Solidity
2 | *.vy linguist-language=Python
3 |
--------------------------------------------------------------------------------
/lesson-08-math/solved/.github/workflows/main.yaml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | pull_request:
4 | schedule:
5 | # run this workflow every Monday at 1PM UTC
6 | - cron: "* 13 * * 1"
7 |
8 | name: main workflow
9 |
10 | env:
11 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
12 |
13 | jobs:
14 |
15 | tests:
16 | runs-on: ubuntu-latest
17 |
18 | steps:
19 | - uses: actions/checkout@v2
20 |
21 | - name: Cache Compiler Installations
22 | uses: actions/cache@v2
23 | with:
24 | path: |
25 | ~/.solcx
26 | ~/.vvm
27 | key: compiler-cache
28 |
29 | - name: Setup Node.js
30 | uses: actions/setup-node@v1
31 |
32 | - name: Install Ganache
33 | run: npm install -g ganache-cli@6.10.1
34 |
35 | - name: Setup Python 3.8
36 | uses: actions/setup-python@v2
37 | with:
38 | python-version: 3.8
39 |
40 | - name: Install Requirements
41 | run: pip install -r requirements.txt
42 |
43 | - name: Run Tests
44 | run: brownie test -C
45 |
--------------------------------------------------------------------------------
/lesson-08-math/solved/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 | .history
3 | .hypothesis/
4 | build/
5 | reports/
6 |
--------------------------------------------------------------------------------
/lesson-08-math/solved/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Brownie Mixes
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/lesson-08-math/solved/brownie-config.yaml:
--------------------------------------------------------------------------------
1 | autofetch_sources: true
2 | networks:
3 | default: mainnet-fork
4 |
--------------------------------------------------------------------------------
/lesson-08-math/solved/contracts/TestMath.vy:
--------------------------------------------------------------------------------
1 | # @version 0.3.7
2 |
3 | coin_price1: public(uint256)
4 | coin_price2: public(decimal)
5 | coin_price3: public(uint256)
6 | coin_price4: public(uint256)
7 |
8 | @external
9 | def __init__():
10 | self.coin_price1 = 69 / 100
11 | self.coin_price2 = 69.0 / 100.0
12 | self.coin_price3 = 69 / 100 * 10 ** 18
13 | self.coin_price4 = 10 ** 18 * 69 / 100
14 |
15 | @external
16 | @view
17 | def test_overflow() -> uint256:
18 | return self.coin_price4 ** 5 / 10 ** 18
19 |
20 |
--------------------------------------------------------------------------------
/lesson-08-math/solved/requirements.txt:
--------------------------------------------------------------------------------
1 | eth-brownie>=1.11.0,<2.0.0
2 |
--------------------------------------------------------------------------------
/lesson-08-math/solved/scripts/token.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | from brownie import Token, accounts
4 |
5 |
6 | def main():
7 | return Token.deploy("Test Token", "TST", 18, 1e21, {'from': accounts[0]})
8 |
--------------------------------------------------------------------------------
/lesson-08-math/solved/tests/conftest.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import pytest
4 | from brownie import network
5 | from brownie_tokens import MintableForkToken
6 |
7 |
8 | @pytest.fixture(scope="function", autouse=True)
9 | def isolate(fn_isolation):
10 | # perform a chain rewind after completing each test, to ensure proper isolation
11 | # https://eth-brownie.readthedocs.io/en/v1.10.3/tests-pytest-intro.html#isolation-fixtures
12 | pass
13 |
14 |
15 | @pytest.fixture(scope="module")
16 | def token(Token, accounts):
17 | return Token.deploy("Test Token", "TST", 18, 1e21, {"from": accounts[0]})
18 |
19 |
20 | @pytest.fixture(scope="module")
21 | def is_forked():
22 | if "fork" in network.show_active():
23 | return True
24 | else:
25 | return False
26 |
27 |
28 | @pytest.fixture(scope="module")
29 | def crv(accounts, Token, is_forked):
30 | if is_forked:
31 | crv = MintableForkToken("0xD533a949740bb3306d119CC777fa900bA034cd52")
32 | crv._mint_for_testing(accounts[0], 10**18)
33 | else:
34 | crv = Token.deploy("Dummy CRV", "CRV", 18, 10**18, {"from": accounts[0]})
35 | return crv
36 |
37 |
38 | @pytest.fixture(scope="module")
39 | def minter(Minter, accounts, token, crv):
40 | minter = Minter.deploy(token, crv, {"from": accounts[0]})
41 | token.set_minter(minter, {"from": token.owner()})
42 | crv.approve(minter, crv.balanceOf(accounts[0]), {"from": accounts[0]})
43 | return minter
44 |
--------------------------------------------------------------------------------
/lesson-08-math/solved/tests/test_approve.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import pytest
4 |
5 |
6 | @pytest.mark.parametrize("idx", range(5))
7 | def test_initial_approval_is_zero(token, accounts, idx):
8 | assert token.allowance(accounts[0], accounts[idx]) == 0
9 |
10 |
11 | def test_approve(token, accounts):
12 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
13 |
14 | assert token.allowance(accounts[0], accounts[1]) == 10**19
15 |
16 |
17 | def test_modify_approve(token, accounts):
18 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
19 | token.approve(accounts[1], 12345678, {"from": accounts[0]})
20 |
21 | assert token.allowance(accounts[0], accounts[1]) == 12345678
22 |
23 |
24 | def test_revoke_approve(token, accounts):
25 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
26 | token.approve(accounts[1], 0, {"from": accounts[0]})
27 |
28 | assert token.allowance(accounts[0], accounts[1]) == 0
29 |
30 |
31 | def test_approve_self(token, accounts):
32 | token.approve(accounts[0], 10**19, {"from": accounts[0]})
33 |
34 | assert token.allowance(accounts[0], accounts[0]) == 10**19
35 |
36 |
37 | def test_only_affects_target(token, accounts):
38 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
39 |
40 | assert token.allowance(accounts[1], accounts[0]) == 0
41 |
42 |
43 | def test_returns_true(token, accounts):
44 | tx = token.approve(accounts[1], 10**19, {"from": accounts[0]})
45 |
46 | assert tx.return_value is True
47 |
48 |
49 | def test_approval_event_fires(accounts, token):
50 | tx = token.approve(accounts[1], 10**19, {"from": accounts[0]})
51 |
52 | assert len(tx.events) == 1
53 | assert tx.events["Approval"].values() == [accounts[0], accounts[1], 10**19]
54 |
--------------------------------------------------------------------------------
/lesson-08-math/solved/tests/test_lend.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | import pytest
4 | import requests
5 | from brownie import *
6 |
7 |
8 | def test_price_oracle_function_exists(minter):
9 | assert hasattr(minter, "price_usd")
10 |
11 |
12 | def test_price_oracle_returns_value(minter, is_forked):
13 | if not is_forked:
14 | pytest.skip()
15 | url = f"https://api.coingecko.com/api/v3/simple/price?ids=curve-dao-token&vs_currencies=usd"
16 | price = json.loads(requests.get(url).content).get("curve-dao-token").get("usd")
17 | assert (minter.price_usd() / 10**18) > price * 0.95
18 | assert (minter.price_usd() / 10**18) < price * 1.05
19 |
--------------------------------------------------------------------------------
/lesson-08-math/unsolved/.gitattributes:
--------------------------------------------------------------------------------
1 | *.sol linguist-language=Solidity
2 | *.vy linguist-language=Python
3 |
--------------------------------------------------------------------------------
/lesson-08-math/unsolved/.github/workflows/main.yaml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | pull_request:
4 | schedule:
5 | # run this workflow every Monday at 1PM UTC
6 | - cron: "* 13 * * 1"
7 |
8 | name: main workflow
9 |
10 | env:
11 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
12 |
13 | jobs:
14 |
15 | tests:
16 | runs-on: ubuntu-latest
17 |
18 | steps:
19 | - uses: actions/checkout@v2
20 |
21 | - name: Cache Compiler Installations
22 | uses: actions/cache@v2
23 | with:
24 | path: |
25 | ~/.solcx
26 | ~/.vvm
27 | key: compiler-cache
28 |
29 | - name: Setup Node.js
30 | uses: actions/setup-node@v1
31 |
32 | - name: Install Ganache
33 | run: npm install -g ganache-cli@6.10.1
34 |
35 | - name: Setup Python 3.8
36 | uses: actions/setup-python@v2
37 | with:
38 | python-version: 3.8
39 |
40 | - name: Install Requirements
41 | run: pip install -r requirements.txt
42 |
43 | - name: Run Tests
44 | run: brownie test -C
45 |
--------------------------------------------------------------------------------
/lesson-08-math/unsolved/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 | .history
3 | .hypothesis/
4 | build/
5 | reports/
6 |
--------------------------------------------------------------------------------
/lesson-08-math/unsolved/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Brownie Mixes
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/lesson-08-math/unsolved/brownie-config.yaml:
--------------------------------------------------------------------------------
1 | autofetch_sources: true
2 | networks:
3 | default: mainnet-fork
4 |
--------------------------------------------------------------------------------
/lesson-08-math/unsolved/contracts/TestMath.vy:
--------------------------------------------------------------------------------
1 | # @version 0.3.7
2 |
3 | coin_price1: public(uint256)
4 | coin_price2: public(decimal)
5 | coin_price3: public(uint256)
6 | coin_price4: public(uint256)
7 |
8 | @external
9 | def __init__():
10 | self.coin_price1 = 69 / 100
11 | self.coin_price2 = 69.0 / 100.0
12 | self.coin_price3 = 69 / 100 * 10 ** 18
13 | self.coin_price4 = 10 ** 18 * 69 / 100
14 |
15 | @external
16 | @view
17 | def test_overflow() -> uint256:
18 | return self.coin_price4 ** 5 / 10 ** 18
19 |
20 |
--------------------------------------------------------------------------------
/lesson-08-math/unsolved/requirements.txt:
--------------------------------------------------------------------------------
1 | eth-brownie>=1.11.0,<2.0.0
2 |
--------------------------------------------------------------------------------
/lesson-08-math/unsolved/scripts/token.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | from brownie import Token, accounts
4 |
5 |
6 | def main():
7 | return Token.deploy("Test Token", "TST", 18, 1e21, {'from': accounts[0]})
8 |
--------------------------------------------------------------------------------
/lesson-08-math/unsolved/tests/conftest.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import pytest
4 | from brownie import network
5 | from brownie_tokens import MintableForkToken
6 |
7 |
8 | @pytest.fixture(scope="function", autouse=True)
9 | def isolate(fn_isolation):
10 | # perform a chain rewind after completing each test, to ensure proper isolation
11 | # https://eth-brownie.readthedocs.io/en/v1.10.3/tests-pytest-intro.html#isolation-fixtures
12 | pass
13 |
14 |
15 | @pytest.fixture(scope="module")
16 | def token(Token, accounts):
17 | return Token.deploy("Test Token", "TST", 18, 1e21, {"from": accounts[0]})
18 |
19 |
20 | @pytest.fixture(scope="module")
21 | def is_forked():
22 | if "fork" in network.show_active():
23 | return True
24 | else:
25 | return False
26 |
27 |
28 | @pytest.fixture(scope="module")
29 | def crv(accounts, Token, is_forked):
30 | if is_forked:
31 | crv = MintableForkToken("0xD533a949740bb3306d119CC777fa900bA034cd52")
32 | crv._mint_for_testing(accounts[0], 10**18)
33 | else:
34 | crv = Token.deploy("Dummy CRV", "CRV", 18, 10**18, {"from": accounts[0]})
35 | return crv
36 |
37 |
38 | @pytest.fixture(scope="module")
39 | def minter(Minter, accounts, token, crv):
40 | minter = Minter.deploy(token, crv, {"from": accounts[0]})
41 | token.set_minter(minter, {"from": token.owner()})
42 | crv.approve(minter, crv.balanceOf(accounts[0]), {"from": accounts[0]})
43 | return minter
44 |
--------------------------------------------------------------------------------
/lesson-08-math/unsolved/tests/test_approve.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import pytest
4 |
5 |
6 | @pytest.mark.parametrize("idx", range(5))
7 | def test_initial_approval_is_zero(token, accounts, idx):
8 | assert token.allowance(accounts[0], accounts[idx]) == 0
9 |
10 |
11 | def test_approve(token, accounts):
12 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
13 |
14 | assert token.allowance(accounts[0], accounts[1]) == 10**19
15 |
16 |
17 | def test_modify_approve(token, accounts):
18 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
19 | token.approve(accounts[1], 12345678, {"from": accounts[0]})
20 |
21 | assert token.allowance(accounts[0], accounts[1]) == 12345678
22 |
23 |
24 | def test_revoke_approve(token, accounts):
25 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
26 | token.approve(accounts[1], 0, {"from": accounts[0]})
27 |
28 | assert token.allowance(accounts[0], accounts[1]) == 0
29 |
30 |
31 | def test_approve_self(token, accounts):
32 | token.approve(accounts[0], 10**19, {"from": accounts[0]})
33 |
34 | assert token.allowance(accounts[0], accounts[0]) == 10**19
35 |
36 |
37 | def test_only_affects_target(token, accounts):
38 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
39 |
40 | assert token.allowance(accounts[1], accounts[0]) == 0
41 |
42 |
43 | def test_returns_true(token, accounts):
44 | tx = token.approve(accounts[1], 10**19, {"from": accounts[0]})
45 |
46 | assert tx.return_value is True
47 |
48 |
49 | def test_approval_event_fires(accounts, token):
50 | tx = token.approve(accounts[1], 10**19, {"from": accounts[0]})
51 |
52 | assert len(tx.events) == 1
53 | assert tx.events["Approval"].values() == [accounts[0], accounts[1], 10**19]
54 |
--------------------------------------------------------------------------------
/lesson-08-math/unsolved/tests/test_lend.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | import pytest
4 | import requests
5 | from brownie import *
6 |
7 |
8 | def test_price_oracle_function_exists(minter):
9 | assert hasattr(minter, "price_usd")
10 |
11 |
12 | def test_price_oracle_returns_value(minter, is_forked):
13 | if not is_forked:
14 | pytest.skip()
15 | url = f"https://api.coingecko.com/api/v3/simple/price?ids=curve-dao-token&vs_currencies=usd"
16 | price = json.loads(requests.get(url).content).get("curve-dao-token").get("usd")
17 | assert (minter.price_usd() / 10**18) > price * 0.95
18 | assert (minter.price_usd() / 10**18) < price * 1.05
19 |
--------------------------------------------------------------------------------
/lesson-09-struct/README.md:
--------------------------------------------------------------------------------
1 | # Lesson 9: Struct Data Types
2 |
3 | ## [🎥 Video 9 : Struct 🎬](https://youtu.be/fdo-UiKvHAo)
4 |
5 | In this lesson we're adding a repayment function along with a state variable to store important loan information. By the end of this unit, the contract will only be capable of managing a single loan -- we'll add the capacity to track several user loans in the next unit on hashmaps.
6 |
7 | This lesson introduces the `struct` datatype to manage loan parameters. Structs somewhat resemble Python objects, in that they can contain disparate data types which are readily accessible.
8 |
9 | Our solution will use a `struct` to track loan parameters including the following:
10 |
11 | ```
12 | struct Loan:
13 | liquidation_price: uint256
14 | deposit_amount: uint256
15 | ```
16 |
17 | In the script, we create a state variable `open_loan` instantiated as a Loan struct
18 |
19 | ```
20 | open_loan: public(Loan)
21 | ```
22 |
23 | We can now instantiate the variable using the following syntax. The solution file does so in the following fashion:
24 |
25 | ```
26 | self.open_loan = Loan({liquidation_price: liq_price, deposit_amount: quantity})
27 | ```
28 |
29 | Now the properties of the struct are easy to access. One could easily return the liquidation price using the following::
30 |
31 | ```
32 | return self.open_loan.liquidation_price
33 | ```
34 |
35 |
36 | ## FURTHER READING
37 |
38 | * [Vyper Struct Documentation](https://github.com/bout3fiddy/boa-tricrypto/blob/main/contracts/CurveCryptoMathOptimized3.vy)
39 | * [Bowtied Island: Vyper Structs and Mappings](https://bowtiedisland.com/vyper-for-beginners-structs-and-mappings/)
40 |
--------------------------------------------------------------------------------
/lesson-09-struct/solution.diff:
--------------------------------------------------------------------------------
1 | 23a24,27
2 | > struct Loan:
3 | > liquidation_price: uint256
4 | > deposit_amount: uint256
5 | >
6 | 38a43
7 | > open_loan: public(Loan)
8 | 76a82,88
9 | > @internal
10 | > @view
11 | > def _repay_amount() -> uint256:
12 | > loan: Loan = self.open_loan
13 | > return loan.liquidation_price * loan.deposit_amount / 10 ** 18
14 | >
15 | >
16 | 101a114,123
17 | > @external
18 | > @view
19 | > def repay_amount() -> uint256:
20 | > """
21 | > @notice Get repayment amount
22 | > @return Amount of tokens required to repay
23 | > """
24 | > return self._repay_amount()
25 | >
26 | >
27 | 119a142,143
28 | > liq_price: uint256 = self._price_usd() * self.collateral_pct / 10 ** 18
29 | > self.open_loan = Loan({liquidation_price: liq_price, deposit_amount: quantity})
30 |
--------------------------------------------------------------------------------
/lesson-09-struct/solved/.gitattributes:
--------------------------------------------------------------------------------
1 | *.sol linguist-language=Solidity
2 | *.vy linguist-language=Python
3 |
--------------------------------------------------------------------------------
/lesson-09-struct/solved/.github/workflows/main.yaml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | pull_request:
4 | schedule:
5 | # run this workflow every Monday at 1PM UTC
6 | - cron: "* 13 * * 1"
7 |
8 | name: main workflow
9 |
10 | env:
11 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
12 |
13 | jobs:
14 |
15 | tests:
16 | runs-on: ubuntu-latest
17 |
18 | steps:
19 | - uses: actions/checkout@v2
20 |
21 | - name: Cache Compiler Installations
22 | uses: actions/cache@v2
23 | with:
24 | path: |
25 | ~/.solcx
26 | ~/.vvm
27 | key: compiler-cache
28 |
29 | - name: Setup Node.js
30 | uses: actions/setup-node@v1
31 |
32 | - name: Install Ganache
33 | run: npm install -g ganache-cli@6.10.1
34 |
35 | - name: Setup Python 3.8
36 | uses: actions/setup-python@v2
37 | with:
38 | python-version: 3.8
39 |
40 | - name: Install Requirements
41 | run: pip install -r requirements.txt
42 |
43 | - name: Run Tests
44 | run: brownie test -C
45 |
--------------------------------------------------------------------------------
/lesson-09-struct/solved/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 | .history
3 | .hypothesis/
4 | build/
5 | reports/
6 |
--------------------------------------------------------------------------------
/lesson-09-struct/solved/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Brownie Mixes
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/lesson-09-struct/solved/brownie-config.yaml:
--------------------------------------------------------------------------------
1 | autofetch_sources: true
2 |
--------------------------------------------------------------------------------
/lesson-09-struct/solved/requirements.txt:
--------------------------------------------------------------------------------
1 | eth-brownie>=1.11.0,<2.0.0
2 |
--------------------------------------------------------------------------------
/lesson-09-struct/solved/scripts/token.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | from brownie import Token, accounts
4 |
5 |
6 | def main():
7 | return Token.deploy("Test Token", "TST", 18, 1e21, {'from': accounts[0]})
8 |
--------------------------------------------------------------------------------
/lesson-09-struct/solved/tests/conftest.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import pytest
4 | from brownie import network
5 | from brownie_tokens import MintableForkToken
6 |
7 |
8 | @pytest.fixture(scope="function", autouse=True)
9 | def isolate(fn_isolation):
10 | # perform a chain rewind after completing each test, to ensure proper isolation
11 | # https://eth-brownie.readthedocs.io/en/v1.10.3/tests-pytest-intro.html#isolation-fixtures
12 | pass
13 |
14 |
15 | @pytest.fixture(scope="module")
16 | def token(Token, accounts):
17 | return Token.deploy("Test Token", "TST", 18, 1e21, {"from": accounts[0]})
18 |
19 |
20 | @pytest.fixture(scope="module")
21 | def is_forked():
22 | if "fork" in network.show_active():
23 | return True
24 | else:
25 | return False
26 |
27 |
28 | @pytest.fixture(scope="module")
29 | def crv(accounts, Token, is_forked):
30 | if is_forked:
31 | crv = MintableForkToken("0xD533a949740bb3306d119CC777fa900bA034cd52")
32 | crv._mint_for_testing(accounts[0], 10**18)
33 | else:
34 | crv = Token.deploy("Dummy CRV", "CRV", 18, 10**18, {"from": accounts[0]})
35 | return crv
36 |
37 |
38 | @pytest.fixture(scope="module")
39 | def minter(Minter, accounts, token, crv):
40 | minter = Minter.deploy(token, crv, {"from": accounts[0]})
41 | token.set_minter(minter, {"from": token.owner()})
42 | crv.approve(minter, crv.balanceOf(accounts[0]), {"from": accounts[0]})
43 | return minter
44 |
--------------------------------------------------------------------------------
/lesson-09-struct/solved/tests/test_approve.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import pytest
4 |
5 |
6 | @pytest.mark.parametrize("idx", range(5))
7 | def test_initial_approval_is_zero(token, accounts, idx):
8 | assert token.allowance(accounts[0], accounts[idx]) == 0
9 |
10 |
11 | def test_approve(token, accounts):
12 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
13 |
14 | assert token.allowance(accounts[0], accounts[1]) == 10**19
15 |
16 |
17 | def test_modify_approve(token, accounts):
18 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
19 | token.approve(accounts[1], 12345678, {"from": accounts[0]})
20 |
21 | assert token.allowance(accounts[0], accounts[1]) == 12345678
22 |
23 |
24 | def test_revoke_approve(token, accounts):
25 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
26 | token.approve(accounts[1], 0, {"from": accounts[0]})
27 |
28 | assert token.allowance(accounts[0], accounts[1]) == 0
29 |
30 |
31 | def test_approve_self(token, accounts):
32 | token.approve(accounts[0], 10**19, {"from": accounts[0]})
33 |
34 | assert token.allowance(accounts[0], accounts[0]) == 10**19
35 |
36 |
37 | def test_only_affects_target(token, accounts):
38 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
39 |
40 | assert token.allowance(accounts[1], accounts[0]) == 0
41 |
42 |
43 | def test_returns_true(token, accounts):
44 | tx = token.approve(accounts[1], 10**19, {"from": accounts[0]})
45 |
46 | assert tx.return_value is True
47 |
48 |
49 | def test_approval_event_fires(accounts, token):
50 | tx = token.approve(accounts[1], 10**19, {"from": accounts[0]})
51 |
52 | assert len(tx.events) == 1
53 | assert tx.events["Approval"].values() == [accounts[0], accounts[1], 10**19]
54 |
--------------------------------------------------------------------------------
/lesson-09-struct/solved/tests/test_lend.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | import pytest
4 | import requests
5 | from brownie import *
6 |
7 |
8 | def test_price_oracle_function_exists(minter):
9 | assert hasattr(minter, "price_usd")
10 |
11 |
12 | def test_price_oracle_returns_value(minter, is_forked):
13 | if not is_forked:
14 | pytest.skip()
15 | url = f"https://api.coingecko.com/api/v3/simple/price?ids=curve-dao-token&vs_currencies=usd"
16 | price = json.loads(requests.get(url).content).get("curve-dao-token").get("usd")
17 | assert (minter.price_usd() / 10**18) > price * 0.95
18 | assert (minter.price_usd() / 10**18) < price * 1.05
19 |
--------------------------------------------------------------------------------
/lesson-09-struct/solved/tests/test_withdraw.py:
--------------------------------------------------------------------------------
1 | import brownie
2 | import pytest
3 | from brownie import *
4 |
5 |
6 | def test_repay_exists(minter):
7 | assert hasattr(minter, "repay_amount")
8 |
9 |
10 | def test_open_loan_exists(minter):
11 | assert hasattr(minter, "open_loan")
12 |
13 |
14 | def test_repay_amount_works(minter, accounts, is_forked):
15 | if not is_forked:
16 | pytest.skip()
17 | quantity = 10**18
18 | minter.mint(quantity, {"from": accounts[0]})
19 | expected_price = minter.price_usd() * 0.8 / 10**18
20 | assert round(minter.repay_amount() / 10**18, 10) == round(expected_price, 10)
21 |
22 |
23 | def test_open_loan_works(minter, accounts, is_forked):
24 | if not is_forked:
25 | pytest.skip()
26 | quantity = 10**18
27 | minter.mint(quantity, {"from": accounts[0]})
28 |
29 | # Check Liquidation Price
30 | assert round(minter.open_loan()[0] / 10**18, 5) == round(
31 | minter.price_usd() * 0.8 / 10**18, 5
32 | )
33 |
34 | # Check Deposit Amount
35 | assert minter.open_loan()[1] == quantity
36 |
--------------------------------------------------------------------------------
/lesson-09-struct/tests.diff:
--------------------------------------------------------------------------------
1 | Only in unsolved/tests: test_withdraw.py
2 |
--------------------------------------------------------------------------------
/lesson-09-struct/unsolved/.gitattributes:
--------------------------------------------------------------------------------
1 | *.sol linguist-language=Solidity
2 | *.vy linguist-language=Python
3 |
--------------------------------------------------------------------------------
/lesson-09-struct/unsolved/.github/workflows/main.yaml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | pull_request:
4 | schedule:
5 | # run this workflow every Monday at 1PM UTC
6 | - cron: "* 13 * * 1"
7 |
8 | name: main workflow
9 |
10 | env:
11 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
12 |
13 | jobs:
14 |
15 | tests:
16 | runs-on: ubuntu-latest
17 |
18 | steps:
19 | - uses: actions/checkout@v2
20 |
21 | - name: Cache Compiler Installations
22 | uses: actions/cache@v2
23 | with:
24 | path: |
25 | ~/.solcx
26 | ~/.vvm
27 | key: compiler-cache
28 |
29 | - name: Setup Node.js
30 | uses: actions/setup-node@v1
31 |
32 | - name: Install Ganache
33 | run: npm install -g ganache-cli@6.10.1
34 |
35 | - name: Setup Python 3.8
36 | uses: actions/setup-python@v2
37 | with:
38 | python-version: 3.8
39 |
40 | - name: Install Requirements
41 | run: pip install -r requirements.txt
42 |
43 | - name: Run Tests
44 | run: brownie test -C
45 |
--------------------------------------------------------------------------------
/lesson-09-struct/unsolved/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 | .history
3 | .hypothesis/
4 | build/
5 | reports/
6 |
--------------------------------------------------------------------------------
/lesson-09-struct/unsolved/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Brownie Mixes
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/lesson-09-struct/unsolved/brownie-config.yaml:
--------------------------------------------------------------------------------
1 | autofetch_sources: true
2 |
--------------------------------------------------------------------------------
/lesson-09-struct/unsolved/requirements.txt:
--------------------------------------------------------------------------------
1 | eth-brownie>=1.11.0,<2.0.0
2 |
--------------------------------------------------------------------------------
/lesson-09-struct/unsolved/scripts/token.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | from brownie import Token, accounts
4 |
5 |
6 | def main():
7 | return Token.deploy("Test Token", "TST", 18, 1e21, {'from': accounts[0]})
8 |
--------------------------------------------------------------------------------
/lesson-09-struct/unsolved/tests/conftest.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import pytest
4 | from brownie import network
5 | from brownie_tokens import MintableForkToken
6 |
7 |
8 | @pytest.fixture(scope="function", autouse=True)
9 | def isolate(fn_isolation):
10 | # perform a chain rewind after completing each test, to ensure proper isolation
11 | # https://eth-brownie.readthedocs.io/en/v1.10.3/tests-pytest-intro.html#isolation-fixtures
12 | pass
13 |
14 |
15 | @pytest.fixture(scope="module")
16 | def token(Token, accounts):
17 | return Token.deploy("Test Token", "TST", 18, 1e21, {"from": accounts[0]})
18 |
19 |
20 | @pytest.fixture(scope="module")
21 | def is_forked():
22 | if "fork" in network.show_active():
23 | return True
24 | else:
25 | return False
26 |
27 |
28 | @pytest.fixture(scope="module")
29 | def crv(accounts, Token, is_forked):
30 | if is_forked:
31 | crv = MintableForkToken("0xD533a949740bb3306d119CC777fa900bA034cd52")
32 | crv._mint_for_testing(accounts[0], 10**18)
33 | else:
34 | crv = Token.deploy("Dummy CRV", "CRV", 18, 10**18, {"from": accounts[0]})
35 | return crv
36 |
37 |
38 | @pytest.fixture(scope="module")
39 | def minter(Minter, accounts, token, crv):
40 | minter = Minter.deploy(token, crv, {"from": accounts[0]})
41 | token.set_minter(minter, {"from": token.owner()})
42 | crv.approve(minter, crv.balanceOf(accounts[0]), {"from": accounts[0]})
43 | return minter
44 |
--------------------------------------------------------------------------------
/lesson-09-struct/unsolved/tests/test_approve.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import pytest
4 |
5 |
6 | @pytest.mark.parametrize("idx", range(5))
7 | def test_initial_approval_is_zero(token, accounts, idx):
8 | assert token.allowance(accounts[0], accounts[idx]) == 0
9 |
10 |
11 | def test_approve(token, accounts):
12 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
13 |
14 | assert token.allowance(accounts[0], accounts[1]) == 10**19
15 |
16 |
17 | def test_modify_approve(token, accounts):
18 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
19 | token.approve(accounts[1], 12345678, {"from": accounts[0]})
20 |
21 | assert token.allowance(accounts[0], accounts[1]) == 12345678
22 |
23 |
24 | def test_revoke_approve(token, accounts):
25 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
26 | token.approve(accounts[1], 0, {"from": accounts[0]})
27 |
28 | assert token.allowance(accounts[0], accounts[1]) == 0
29 |
30 |
31 | def test_approve_self(token, accounts):
32 | token.approve(accounts[0], 10**19, {"from": accounts[0]})
33 |
34 | assert token.allowance(accounts[0], accounts[0]) == 10**19
35 |
36 |
37 | def test_only_affects_target(token, accounts):
38 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
39 |
40 | assert token.allowance(accounts[1], accounts[0]) == 0
41 |
42 |
43 | def test_returns_true(token, accounts):
44 | tx = token.approve(accounts[1], 10**19, {"from": accounts[0]})
45 |
46 | assert tx.return_value is True
47 |
48 |
49 | def test_approval_event_fires(accounts, token):
50 | tx = token.approve(accounts[1], 10**19, {"from": accounts[0]})
51 |
52 | assert len(tx.events) == 1
53 | assert tx.events["Approval"].values() == [accounts[0], accounts[1], 10**19]
54 |
--------------------------------------------------------------------------------
/lesson-09-struct/unsolved/tests/test_lend.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | import pytest
4 | import requests
5 | from brownie import *
6 |
7 |
8 | def test_price_oracle_function_exists(minter):
9 | assert hasattr(minter, "price_usd")
10 |
11 |
12 | def test_price_oracle_returns_value(minter, is_forked):
13 | if not is_forked:
14 | pytest.skip()
15 | url = f"https://api.coingecko.com/api/v3/simple/price?ids=curve-dao-token&vs_currencies=usd"
16 | price = json.loads(requests.get(url).content).get("curve-dao-token").get("usd")
17 | assert (minter.price_usd() / 10**18) > price * 0.95
18 | assert (minter.price_usd() / 10**18) < price * 1.05
19 |
--------------------------------------------------------------------------------
/lesson-09-struct/unsolved/tests/test_withdraw.py:
--------------------------------------------------------------------------------
1 | import brownie
2 | import pytest
3 | from brownie import *
4 |
5 |
6 | def test_repay_exists(minter):
7 | assert hasattr(minter, "repay_amount")
8 |
9 |
10 | def test_open_loan_exists(minter):
11 | assert hasattr(minter, "open_loan")
12 |
13 |
14 | def test_repay_amount_works(minter, accounts, is_forked):
15 | if not is_forked:
16 | pytest.skip()
17 | quantity = 10**18
18 | minter.mint(quantity, {"from": accounts[0]})
19 | expected_price = minter.price_usd() * 0.8 / 10**18
20 | assert round(minter.repay_amount() / 10**18, 10) == round(expected_price, 10)
21 |
22 |
23 | def test_open_loan_works(minter, accounts, is_forked):
24 | if not is_forked:
25 | pytest.skip()
26 | quantity = 10**18
27 | minter.mint(quantity, {"from": accounts[0]})
28 |
29 | # Check Liquidation Price
30 | assert round(minter.open_loan()[0] / 10**18, 5) == round(
31 | minter.price_usd() * 0.8 / 10**18, 5
32 | )
33 |
34 | # Check Deposit Amount
35 | assert minter.open_loan()[1] == quantity
36 |
--------------------------------------------------------------------------------
/lesson-10-hashmap/README.md:
--------------------------------------------------------------------------------
1 | # Lesson 10: HashMap Mappings
2 |
3 | ## [🎥 Video 10 : HashMap 🎬](https://youtu.be/VX1zjKTOUx0)
4 |
5 | To complete our repayment function, we upgrade our contract to manage one loan per user via a Vyper `HashMap` mapping.
6 |
7 | A `HashMap` in Vyper maps a keccak256 hash of every possible key to a value, initially set to the variable's default value (usually zero).
8 | This optimizes for quick lookup and efficient storage, but has no concept of "length" and therefore cannot be iterated over. Otherwise, a HashMap resembles a Python dictionary in its syntax.
9 |
10 | We use a mapping to upgrade our `open_loan` variable, which could only track a single user, to map any user address to its associated `Loan` struct.
11 |
12 | ```
13 | open_loans: public(HashMap[address, Loan])
14 | ```
15 |
16 | Then we update every subsequent use of `open_loan` to pass an address as index as if it were a Python dictionary.
17 |
18 | ```
19 | self.open_loans[msg.sender] = Loan({liquidation_price: liq_price, deposit_amount: quantity})
20 | ```
21 |
22 | We have all the tools necessary to finalize our repay function, which transfers the necessary quantity of stablecoins from the user and returns collateral.
23 |
--------------------------------------------------------------------------------
/lesson-10-hashmap/solution.diff:
--------------------------------------------------------------------------------
1 | diff unsolved/contracts/Minter.vy solved/contracts/Minter.vy
2 | 43c43
3 | < open_loan: public(Loan)
4 | ---
5 | > open_loans: public(HashMap[address, Loan])
6 | 84,85c84,85
7 | < def _repay_amount() -> uint256:
8 | < loan: Loan = self.open_loan
9 | ---
10 | > def _repay_amount(addr: address) -> uint256:
11 | > loan: Loan = self.open_loans[addr]
12 | 116c116
13 | < def repay_amount() -> uint256:
14 | ---
15 | > def repay_amount(addr: address) -> uint256:
16 | 118a119
17 | > @param addr Address to lookup
18 | 121c122
19 | < return self._repay_amount()
20 | ---
21 | > return self._repay_amount(addr)
22 | 143c144
23 | < self.open_loan = Loan({liquidation_price: liq_price, deposit_amount: quantity})
24 | ---
25 | > self.open_loans[msg.sender] = Loan({liquidation_price: liq_price, deposit_amount: quantity})
26 | 146a148,168
27 | >
28 | >
29 | > @external
30 | > def repay():
31 | > """
32 | > @notice Repay loan in full
33 | > @dev Will revert if user lacks approvals or the balance to repay in full
34 | > """
35 | > # Run checks
36 | > user: address = msg.sender
37 | > assert self.stablecoin.balanceOf(user) >= self._repay_amount(user)
38 | >
39 | > # Try to transfer stablecoin
40 | > self.stablecoin.transferFrom(user, self, self._repay_amount(user))
41 | >
42 | > # Clear out the loan
43 | > quantity: uint256 = self.open_loans[user].deposit_amount
44 | > self.open_loans[user] = Loan({liquidation_price: 0, deposit_amount: 0})
45 | >
46 | > # Return the user's collateral
47 | > self.lending_token.transfer(user, quantity)
48 |
--------------------------------------------------------------------------------
/lesson-10-hashmap/solved/.gitattributes:
--------------------------------------------------------------------------------
1 | *.sol linguist-language=Solidity
2 | *.vy linguist-language=Python
3 |
--------------------------------------------------------------------------------
/lesson-10-hashmap/solved/.github/workflows/main.yaml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | pull_request:
4 | schedule:
5 | # run this workflow every Monday at 1PM UTC
6 | - cron: "* 13 * * 1"
7 |
8 | name: main workflow
9 |
10 | env:
11 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
12 |
13 | jobs:
14 |
15 | tests:
16 | runs-on: ubuntu-latest
17 |
18 | steps:
19 | - uses: actions/checkout@v2
20 |
21 | - name: Cache Compiler Installations
22 | uses: actions/cache@v2
23 | with:
24 | path: |
25 | ~/.solcx
26 | ~/.vvm
27 | key: compiler-cache
28 |
29 | - name: Setup Node.js
30 | uses: actions/setup-node@v1
31 |
32 | - name: Install Ganache
33 | run: npm install -g ganache-cli@6.10.1
34 |
35 | - name: Setup Python 3.8
36 | uses: actions/setup-python@v2
37 | with:
38 | python-version: 3.8
39 |
40 | - name: Install Requirements
41 | run: pip install -r requirements.txt
42 |
43 | - name: Run Tests
44 | run: brownie test -C
45 |
--------------------------------------------------------------------------------
/lesson-10-hashmap/solved/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 | .history
3 | .hypothesis/
4 | build/
5 | reports/
6 |
--------------------------------------------------------------------------------
/lesson-10-hashmap/solved/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Brownie Mixes
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/lesson-10-hashmap/solved/brownie-config.yaml:
--------------------------------------------------------------------------------
1 | autofetch_sources: true
2 | networks:
3 | default: mainnet-fork
4 |
--------------------------------------------------------------------------------
/lesson-10-hashmap/solved/requirements.txt:
--------------------------------------------------------------------------------
1 | eth-brownie>=1.11.0,<2.0.0
2 |
--------------------------------------------------------------------------------
/lesson-10-hashmap/solved/scripts/token.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | from brownie import Token, accounts
4 |
5 |
6 | def main():
7 | return Token.deploy("Test Token", "TST", 18, 1e21, {'from': accounts[0]})
8 |
--------------------------------------------------------------------------------
/lesson-10-hashmap/solved/tests/conftest.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import pytest
4 | from brownie import network
5 | from brownie_tokens import MintableForkToken
6 |
7 |
8 | @pytest.fixture(scope="function", autouse=True)
9 | def isolate(fn_isolation):
10 | # perform a chain rewind after completing each test, to ensure proper isolation
11 | # https://eth-brownie.readthedocs.io/en/v1.10.3/tests-pytest-intro.html#isolation-fixtures
12 | pass
13 |
14 |
15 | @pytest.fixture(scope="module")
16 | def token(Token, accounts):
17 | return Token.deploy("Test Token", "TST", 18, 1e21, {"from": accounts[0]})
18 |
19 |
20 | @pytest.fixture(scope="module")
21 | def is_forked():
22 | if "fork" in network.show_active():
23 | return True
24 | else:
25 | return False
26 |
27 |
28 | @pytest.fixture(scope="module")
29 | def crv(accounts, Token, is_forked):
30 | if is_forked:
31 | crv = MintableForkToken("0xD533a949740bb3306d119CC777fa900bA034cd52")
32 | crv._mint_for_testing(accounts[0], 10**18)
33 | else:
34 | crv = Token.deploy("Dummy CRV", "CRV", 18, 10**18, {"from": accounts[0]})
35 | return crv
36 |
37 |
38 | @pytest.fixture(scope="module")
39 | def minter(Minter, accounts, token, crv):
40 | minter = Minter.deploy(token, crv, {"from": accounts[0]})
41 | token.set_minter(minter, {"from": token.owner()})
42 | crv.approve(minter, crv.balanceOf(accounts[0]), {"from": accounts[0]})
43 | return minter
44 |
--------------------------------------------------------------------------------
/lesson-10-hashmap/solved/tests/test_approve.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import pytest
4 |
5 |
6 | @pytest.mark.parametrize("idx", range(5))
7 | def test_initial_approval_is_zero(token, accounts, idx):
8 | assert token.allowance(accounts[0], accounts[idx]) == 0
9 |
10 |
11 | def test_approve(token, accounts):
12 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
13 |
14 | assert token.allowance(accounts[0], accounts[1]) == 10**19
15 |
16 |
17 | def test_modify_approve(token, accounts):
18 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
19 | token.approve(accounts[1], 12345678, {"from": accounts[0]})
20 |
21 | assert token.allowance(accounts[0], accounts[1]) == 12345678
22 |
23 |
24 | def test_revoke_approve(token, accounts):
25 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
26 | token.approve(accounts[1], 0, {"from": accounts[0]})
27 |
28 | assert token.allowance(accounts[0], accounts[1]) == 0
29 |
30 |
31 | def test_approve_self(token, accounts):
32 | token.approve(accounts[0], 10**19, {"from": accounts[0]})
33 |
34 | assert token.allowance(accounts[0], accounts[0]) == 10**19
35 |
36 |
37 | def test_only_affects_target(token, accounts):
38 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
39 |
40 | assert token.allowance(accounts[1], accounts[0]) == 0
41 |
42 |
43 | def test_returns_true(token, accounts):
44 | tx = token.approve(accounts[1], 10**19, {"from": accounts[0]})
45 |
46 | assert tx.return_value is True
47 |
48 |
49 | def test_approval_event_fires(accounts, token):
50 | tx = token.approve(accounts[1], 10**19, {"from": accounts[0]})
51 |
52 | assert len(tx.events) == 1
53 | assert tx.events["Approval"].values() == [accounts[0], accounts[1], 10**19]
54 |
--------------------------------------------------------------------------------
/lesson-10-hashmap/solved/tests/test_lend.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | import pytest
4 | import requests
5 | from brownie import *
6 |
7 |
8 | def test_price_oracle_function_exists(minter):
9 | assert hasattr(minter, "price_usd")
10 |
11 |
12 | def test_price_oracle_returns_value(minter, is_forked):
13 | if not is_forked:
14 | pytest.skip()
15 | url = f"https://api.coingecko.com/api/v3/simple/price?ids=curve-dao-token&vs_currencies=usd"
16 | price = json.loads(requests.get(url).content).get("curve-dao-token").get("usd")
17 | assert (minter.price_usd() / 10**18) > price * 0.95
18 | assert (minter.price_usd() / 10**18) < price * 1.05
19 |
--------------------------------------------------------------------------------
/lesson-10-hashmap/unsolved/.gitattributes:
--------------------------------------------------------------------------------
1 | *.sol linguist-language=Solidity
2 | *.vy linguist-language=Python
3 |
--------------------------------------------------------------------------------
/lesson-10-hashmap/unsolved/.github/workflows/main.yaml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | pull_request:
4 | schedule:
5 | # run this workflow every Monday at 1PM UTC
6 | - cron: "* 13 * * 1"
7 |
8 | name: main workflow
9 |
10 | env:
11 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
12 |
13 | jobs:
14 |
15 | tests:
16 | runs-on: ubuntu-latest
17 |
18 | steps:
19 | - uses: actions/checkout@v2
20 |
21 | - name: Cache Compiler Installations
22 | uses: actions/cache@v2
23 | with:
24 | path: |
25 | ~/.solcx
26 | ~/.vvm
27 | key: compiler-cache
28 |
29 | - name: Setup Node.js
30 | uses: actions/setup-node@v1
31 |
32 | - name: Install Ganache
33 | run: npm install -g ganache-cli@6.10.1
34 |
35 | - name: Setup Python 3.8
36 | uses: actions/setup-python@v2
37 | with:
38 | python-version: 3.8
39 |
40 | - name: Install Requirements
41 | run: pip install -r requirements.txt
42 |
43 | - name: Run Tests
44 | run: brownie test -C
45 |
--------------------------------------------------------------------------------
/lesson-10-hashmap/unsolved/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 | .history
3 | .hypothesis/
4 | build/
5 | reports/
6 |
--------------------------------------------------------------------------------
/lesson-10-hashmap/unsolved/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Brownie Mixes
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/lesson-10-hashmap/unsolved/brownie-config.yaml:
--------------------------------------------------------------------------------
1 | autofetch_sources: true
2 | networks:
3 | default: mainnet-fork
4 |
--------------------------------------------------------------------------------
/lesson-10-hashmap/unsolved/requirements.txt:
--------------------------------------------------------------------------------
1 | eth-brownie>=1.11.0,<2.0.0
2 |
--------------------------------------------------------------------------------
/lesson-10-hashmap/unsolved/scripts/token.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | from brownie import Token, accounts
4 |
5 |
6 | def main():
7 | return Token.deploy("Test Token", "TST", 18, 1e21, {'from': accounts[0]})
8 |
--------------------------------------------------------------------------------
/lesson-10-hashmap/unsolved/tests/conftest.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import pytest
4 | from brownie import network
5 | from brownie_tokens import MintableForkToken
6 |
7 |
8 | @pytest.fixture(scope="function", autouse=True)
9 | def isolate(fn_isolation):
10 | # perform a chain rewind after completing each test, to ensure proper isolation
11 | # https://eth-brownie.readthedocs.io/en/v1.10.3/tests-pytest-intro.html#isolation-fixtures
12 | pass
13 |
14 |
15 | @pytest.fixture(scope="module")
16 | def token(Token, accounts):
17 | return Token.deploy("Test Token", "TST", 18, 1e21, {"from": accounts[0]})
18 |
19 |
20 | @pytest.fixture(scope="module")
21 | def is_forked():
22 | if "fork" in network.show_active():
23 | return True
24 | else:
25 | return False
26 |
27 |
28 | @pytest.fixture(scope="module")
29 | def crv(accounts, Token, is_forked):
30 | if is_forked:
31 | crv = MintableForkToken("0xD533a949740bb3306d119CC777fa900bA034cd52")
32 | crv._mint_for_testing(accounts[0], 10**18)
33 | else:
34 | crv = Token.deploy("Dummy CRV", "CRV", 18, 10**18, {"from": accounts[0]})
35 | return crv
36 |
37 |
38 | @pytest.fixture(scope="module")
39 | def minter(Minter, accounts, token, crv):
40 | minter = Minter.deploy(token, crv, {"from": accounts[0]})
41 | token.set_minter(minter, {"from": token.owner()})
42 | crv.approve(minter, crv.balanceOf(accounts[0]), {"from": accounts[0]})
43 | return minter
44 |
--------------------------------------------------------------------------------
/lesson-10-hashmap/unsolved/tests/test_approve.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import pytest
4 |
5 |
6 | @pytest.mark.parametrize("idx", range(5))
7 | def test_initial_approval_is_zero(token, accounts, idx):
8 | assert token.allowance(accounts[0], accounts[idx]) == 0
9 |
10 |
11 | def test_approve(token, accounts):
12 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
13 |
14 | assert token.allowance(accounts[0], accounts[1]) == 10**19
15 |
16 |
17 | def test_modify_approve(token, accounts):
18 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
19 | token.approve(accounts[1], 12345678, {"from": accounts[0]})
20 |
21 | assert token.allowance(accounts[0], accounts[1]) == 12345678
22 |
23 |
24 | def test_revoke_approve(token, accounts):
25 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
26 | token.approve(accounts[1], 0, {"from": accounts[0]})
27 |
28 | assert token.allowance(accounts[0], accounts[1]) == 0
29 |
30 |
31 | def test_approve_self(token, accounts):
32 | token.approve(accounts[0], 10**19, {"from": accounts[0]})
33 |
34 | assert token.allowance(accounts[0], accounts[0]) == 10**19
35 |
36 |
37 | def test_only_affects_target(token, accounts):
38 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
39 |
40 | assert token.allowance(accounts[1], accounts[0]) == 0
41 |
42 |
43 | def test_returns_true(token, accounts):
44 | tx = token.approve(accounts[1], 10**19, {"from": accounts[0]})
45 |
46 | assert tx.return_value is True
47 |
48 |
49 | def test_approval_event_fires(accounts, token):
50 | tx = token.approve(accounts[1], 10**19, {"from": accounts[0]})
51 |
52 | assert len(tx.events) == 1
53 | assert tx.events["Approval"].values() == [accounts[0], accounts[1], 10**19]
54 |
--------------------------------------------------------------------------------
/lesson-10-hashmap/unsolved/tests/test_lend.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | import pytest
4 | import requests
5 | from brownie import *
6 |
7 |
8 | def test_price_oracle_function_exists(minter):
9 | assert hasattr(minter, "price_usd")
10 |
11 |
12 | def test_price_oracle_returns_value(minter, is_forked):
13 | if not is_forked:
14 | pytest.skip()
15 | url = f"https://api.coingecko.com/api/v3/simple/price?ids=curve-dao-token&vs_currencies=usd"
16 | price = json.loads(requests.get(url).content).get("curve-dao-token").get("usd")
17 | assert (minter.price_usd() / 10**18) > price * 0.95
18 | assert (minter.price_usd() / 10**18) < price * 1.05
19 |
--------------------------------------------------------------------------------
/lesson-11-event/solution.diff:
--------------------------------------------------------------------------------
1 | 27a28,30
2 | > event Liquidation:
3 | > user: address
4 | > loan: Loan
5 | 28a32
6 | >
7 | 88a93,98
8 | > @internal
9 | > @view
10 | > def _can_liquidate(user: address) -> bool:
11 | > return self._price_usd() < self.open_loans[user].liquidation_price
12 | >
13 | >
14 | 124a135,145
15 | > @external
16 | > @view
17 | > def can_liquidate(user: address) -> bool:
18 | > """
19 | > @notice Is user subject to liquidation?
20 | > @param user Address of user to check
21 | > @return bool True if up for liquidation
22 | > """
23 | > return self._can_liquidate(user)
24 | >
25 | >
26 | 168a190,207
27 | >
28 | >
29 | > @external
30 | > def liquidate(user: address):
31 | > """
32 | > @notice Liquidate a user if token price drops below liquidation price
33 | > @param user Address to be liquidated
34 | > """
35 | > # Verify price is below liquidation
36 | > assert self._can_liquidate(user)
37 | >
38 | > # Clear out loan data
39 | > log Liquidation(user, self.open_loans[user])
40 | > self.open_loans[user] = Loan({liquidation_price: 0, deposit_amount: 0})
41 | >
42 | > # Liquidate
43 | > transfer_val: uint256 = self.open_loans[user].deposit_amount
44 | > self.lending_token.transfer(msg.sender, transfer_val)
45 |
--------------------------------------------------------------------------------
/lesson-11-event/solved/.gitattributes:
--------------------------------------------------------------------------------
1 | *.sol linguist-language=Solidity
2 | *.vy linguist-language=Python
3 |
--------------------------------------------------------------------------------
/lesson-11-event/solved/.github/workflows/main.yaml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | pull_request:
4 | schedule:
5 | # run this workflow every Monday at 1PM UTC
6 | - cron: "* 13 * * 1"
7 |
8 | name: main workflow
9 |
10 | env:
11 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
12 |
13 | jobs:
14 |
15 | tests:
16 | runs-on: ubuntu-latest
17 |
18 | steps:
19 | - uses: actions/checkout@v2
20 |
21 | - name: Cache Compiler Installations
22 | uses: actions/cache@v2
23 | with:
24 | path: |
25 | ~/.solcx
26 | ~/.vvm
27 | key: compiler-cache
28 |
29 | - name: Setup Node.js
30 | uses: actions/setup-node@v1
31 |
32 | - name: Install Ganache
33 | run: npm install -g ganache-cli@6.10.1
34 |
35 | - name: Setup Python 3.8
36 | uses: actions/setup-python@v2
37 | with:
38 | python-version: 3.8
39 |
40 | - name: Install Requirements
41 | run: pip install -r requirements.txt
42 |
43 | - name: Run Tests
44 | run: brownie test -C
45 |
--------------------------------------------------------------------------------
/lesson-11-event/solved/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 | .history
3 | .hypothesis/
4 | build/
5 | reports/
6 |
--------------------------------------------------------------------------------
/lesson-11-event/solved/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Brownie Mixes
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/lesson-11-event/solved/brownie-config.yaml:
--------------------------------------------------------------------------------
1 | autofetch_sources: true
2 | networks:
3 | default: mainnet-fork
4 |
--------------------------------------------------------------------------------
/lesson-11-event/solved/requirements.txt:
--------------------------------------------------------------------------------
1 | eth-brownie>=1.11.0,<2.0.0
2 |
--------------------------------------------------------------------------------
/lesson-11-event/solved/scripts/token.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | from brownie import Token, accounts
4 |
5 |
6 | def main():
7 | return Token.deploy("Test Token", "TST", 18, 1e21, {'from': accounts[0]})
8 |
--------------------------------------------------------------------------------
/lesson-11-event/solved/tests/conftest.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import pytest
4 | from brownie import network
5 | from brownie_tokens import MintableForkToken
6 |
7 |
8 | @pytest.fixture(scope="function", autouse=True)
9 | def isolate(fn_isolation):
10 | # perform a chain rewind after completing each test, to ensure proper isolation
11 | # https://eth-brownie.readthedocs.io/en/v1.10.3/tests-pytest-intro.html#isolation-fixtures
12 | pass
13 |
14 |
15 | @pytest.fixture(scope="module")
16 | def token(Token, accounts):
17 | return Token.deploy("Test Token", "TST", 18, 1e21, {"from": accounts[0]})
18 |
19 |
20 | @pytest.fixture(scope="module")
21 | def is_forked():
22 | if "fork" in network.show_active():
23 | return True
24 | else:
25 | return False
26 |
27 |
28 | @pytest.fixture(scope="module")
29 | def crv(accounts, Token, is_forked):
30 | if is_forked:
31 | crv = MintableForkToken("0xD533a949740bb3306d119CC777fa900bA034cd52")
32 | crv._mint_for_testing(accounts[0], 10**18)
33 | else:
34 | crv = Token.deploy("Dummy CRV", "CRV", 18, 10**18, {"from": accounts[0]})
35 | return crv
36 |
37 |
38 | @pytest.fixture(scope="module")
39 | def minter(Minter, accounts, token, crv):
40 | minter = Minter.deploy(token, crv, {"from": accounts[0]})
41 | token.set_minter(minter, {"from": token.owner()})
42 | crv.approve(minter, crv.balanceOf(accounts[0]), {"from": accounts[0]})
43 | return minter
44 |
--------------------------------------------------------------------------------
/lesson-11-event/solved/tests/test_approve.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import pytest
4 |
5 |
6 | @pytest.mark.parametrize("idx", range(5))
7 | def test_initial_approval_is_zero(token, accounts, idx):
8 | assert token.allowance(accounts[0], accounts[idx]) == 0
9 |
10 |
11 | def test_approve(token, accounts):
12 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
13 |
14 | assert token.allowance(accounts[0], accounts[1]) == 10**19
15 |
16 |
17 | def test_modify_approve(token, accounts):
18 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
19 | token.approve(accounts[1], 12345678, {"from": accounts[0]})
20 |
21 | assert token.allowance(accounts[0], accounts[1]) == 12345678
22 |
23 |
24 | def test_revoke_approve(token, accounts):
25 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
26 | token.approve(accounts[1], 0, {"from": accounts[0]})
27 |
28 | assert token.allowance(accounts[0], accounts[1]) == 0
29 |
30 |
31 | def test_approve_self(token, accounts):
32 | token.approve(accounts[0], 10**19, {"from": accounts[0]})
33 |
34 | assert token.allowance(accounts[0], accounts[0]) == 10**19
35 |
36 |
37 | def test_only_affects_target(token, accounts):
38 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
39 |
40 | assert token.allowance(accounts[1], accounts[0]) == 0
41 |
42 |
43 | def test_returns_true(token, accounts):
44 | tx = token.approve(accounts[1], 10**19, {"from": accounts[0]})
45 |
46 | assert tx.return_value is True
47 |
48 |
49 | def test_approval_event_fires(accounts, token):
50 | tx = token.approve(accounts[1], 10**19, {"from": accounts[0]})
51 |
52 | assert len(tx.events) == 1
53 | assert tx.events["Approval"].values() == [accounts[0], accounts[1], 10**19]
54 |
--------------------------------------------------------------------------------
/lesson-11-event/solved/tests/test_lend.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | import pytest
4 | import requests
5 | from brownie import *
6 |
7 |
8 | def test_price_oracle_function_exists(minter):
9 | assert hasattr(minter, "price_usd")
10 |
11 |
12 | def test_price_oracle_returns_value(minter, is_forked):
13 | if not is_forked:
14 | pytest.skip()
15 | url = f"https://api.coingecko.com/api/v3/simple/price?ids=curve-dao-token&vs_currencies=usd"
16 | price = json.loads(requests.get(url).content).get("curve-dao-token").get("usd")
17 | assert (minter.price_usd() / 10**18) > price * 0.95
18 | assert (minter.price_usd() / 10**18) < price * 1.05
19 |
--------------------------------------------------------------------------------
/lesson-11-event/tests.diff:
--------------------------------------------------------------------------------
1 | diff --color ../lesson-10-hashmap/solved/tests/test_withdraw.py unsolved/tests/test_withdraw.py
2 | 65a66,107
3 | >
4 | > def test_liquidate_event(minter, accounts, is_forked, token, crv):
5 | > if not is_forked:
6 | > pytest.skip()
7 | > quantity = 10**18
8 | >
9 | > user = accounts[1]
10 | > crv._mint_for_testing(user, quantity)
11 | >
12 | > init_crv = crv.balanceOf(user)
13 | > init_token = token.balanceOf(user)
14 | > expected = minter.get_dy(quantity)
15 | >
16 | > crv.approve(minter, quantity, {"from": user})
17 | > minter.mint(quantity, {"from": user})
18 | >
19 | > oracle = Contract(minter.crv_eth_oracle())
20 | > init_price = minter.price_usd()
21 | > target_bal = oracle.balances(1)
22 | > crv._mint_for_testing(accounts[0], target_bal)
23 | > crv.approve(oracle, 2 ** 256 - 1, {'from': accounts[0]})
24 | > oracle.add_liquidity([0, target_bal], 0, {'from': accounts[0]})
25 | >
26 | > i = 0
27 | > lp = Contract('0xEd4064f376cB8d68F770FB1Ff088a3d0F3FF5c4d')
28 | >
29 | > while minter.can_liquidate(accounts[1]) == False:
30 | > i += 1
31 | > crv._mint_for_testing(accounts[0], target_bal)
32 | > oracle.exchange(1, 0, target_bal, 0, {'from': accounts[0]})
33 | > chain.mine(timedelta=60 * 60 * 24 )
34 | >
35 | > if i == 50:
36 | > assert False, "Could not manipulate price"
37 | >
38 | > loan_info = minter.open_loans(accounts[1])
39 | > assert loan_info[0] > 0
40 | > assert loan_info[1] > 0
41 | >
42 | > tx = minter.liquidate(accounts[1], {'from': accounts[0]})
43 | > assert tx.events['Liquidation']['user'] == accounts[1]
44 | > assert tx.events['Liquidation']['loan'] == loan_info
45 |
--------------------------------------------------------------------------------
/lesson-11-event/unsolved/.gitattributes:
--------------------------------------------------------------------------------
1 | *.sol linguist-language=Solidity
2 | *.vy linguist-language=Python
3 |
--------------------------------------------------------------------------------
/lesson-11-event/unsolved/.github/workflows/main.yaml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | pull_request:
4 | schedule:
5 | # run this workflow every Monday at 1PM UTC
6 | - cron: "* 13 * * 1"
7 |
8 | name: main workflow
9 |
10 | env:
11 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
12 |
13 | jobs:
14 |
15 | tests:
16 | runs-on: ubuntu-latest
17 |
18 | steps:
19 | - uses: actions/checkout@v2
20 |
21 | - name: Cache Compiler Installations
22 | uses: actions/cache@v2
23 | with:
24 | path: |
25 | ~/.solcx
26 | ~/.vvm
27 | key: compiler-cache
28 |
29 | - name: Setup Node.js
30 | uses: actions/setup-node@v1
31 |
32 | - name: Install Ganache
33 | run: npm install -g ganache-cli@6.10.1
34 |
35 | - name: Setup Python 3.8
36 | uses: actions/setup-python@v2
37 | with:
38 | python-version: 3.8
39 |
40 | - name: Install Requirements
41 | run: pip install -r requirements.txt
42 |
43 | - name: Run Tests
44 | run: brownie test -C
45 |
--------------------------------------------------------------------------------
/lesson-11-event/unsolved/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 | .history
3 | .hypothesis/
4 | build/
5 | reports/
6 |
--------------------------------------------------------------------------------
/lesson-11-event/unsolved/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Brownie Mixes
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/lesson-11-event/unsolved/brownie-config.yaml:
--------------------------------------------------------------------------------
1 | autofetch_sources: true
2 | networks:
3 | default: mainnet-fork
4 |
--------------------------------------------------------------------------------
/lesson-11-event/unsolved/requirements.txt:
--------------------------------------------------------------------------------
1 | eth-brownie>=1.11.0,<2.0.0
2 |
--------------------------------------------------------------------------------
/lesson-11-event/unsolved/scripts/token.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | from brownie import Token, accounts
4 |
5 |
6 | def main():
7 | return Token.deploy("Test Token", "TST", 18, 1e21, {'from': accounts[0]})
8 |
--------------------------------------------------------------------------------
/lesson-11-event/unsolved/tests/conftest.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import pytest
4 | from brownie import network
5 | from brownie_tokens import MintableForkToken
6 |
7 |
8 | @pytest.fixture(scope="function", autouse=True)
9 | def isolate(fn_isolation):
10 | # perform a chain rewind after completing each test, to ensure proper isolation
11 | # https://eth-brownie.readthedocs.io/en/v1.10.3/tests-pytest-intro.html#isolation-fixtures
12 | pass
13 |
14 |
15 | @pytest.fixture(scope="module")
16 | def token(Token, accounts):
17 | return Token.deploy("Test Token", "TST", 18, 1e21, {"from": accounts[0]})
18 |
19 |
20 | @pytest.fixture(scope="module")
21 | def is_forked():
22 | if "fork" in network.show_active():
23 | return True
24 | else:
25 | return False
26 |
27 |
28 | @pytest.fixture(scope="module")
29 | def crv(accounts, Token, is_forked):
30 | if is_forked:
31 | crv = MintableForkToken("0xD533a949740bb3306d119CC777fa900bA034cd52")
32 | crv._mint_for_testing(accounts[0], 10**18)
33 | else:
34 | crv = Token.deploy("Dummy CRV", "CRV", 18, 10**18, {"from": accounts[0]})
35 | return crv
36 |
37 |
38 | @pytest.fixture(scope="module")
39 | def minter(Minter, accounts, token, crv):
40 | minter = Minter.deploy(token, crv, {"from": accounts[0]})
41 | token.set_minter(minter, {"from": token.owner()})
42 | crv.approve(minter, crv.balanceOf(accounts[0]), {"from": accounts[0]})
43 | return minter
44 |
--------------------------------------------------------------------------------
/lesson-11-event/unsolved/tests/test_approve.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import pytest
4 |
5 |
6 | @pytest.mark.parametrize("idx", range(5))
7 | def test_initial_approval_is_zero(token, accounts, idx):
8 | assert token.allowance(accounts[0], accounts[idx]) == 0
9 |
10 |
11 | def test_approve(token, accounts):
12 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
13 |
14 | assert token.allowance(accounts[0], accounts[1]) == 10**19
15 |
16 |
17 | def test_modify_approve(token, accounts):
18 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
19 | token.approve(accounts[1], 12345678, {"from": accounts[0]})
20 |
21 | assert token.allowance(accounts[0], accounts[1]) == 12345678
22 |
23 |
24 | def test_revoke_approve(token, accounts):
25 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
26 | token.approve(accounts[1], 0, {"from": accounts[0]})
27 |
28 | assert token.allowance(accounts[0], accounts[1]) == 0
29 |
30 |
31 | def test_approve_self(token, accounts):
32 | token.approve(accounts[0], 10**19, {"from": accounts[0]})
33 |
34 | assert token.allowance(accounts[0], accounts[0]) == 10**19
35 |
36 |
37 | def test_only_affects_target(token, accounts):
38 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
39 |
40 | assert token.allowance(accounts[1], accounts[0]) == 0
41 |
42 |
43 | def test_returns_true(token, accounts):
44 | tx = token.approve(accounts[1], 10**19, {"from": accounts[0]})
45 |
46 | assert tx.return_value is True
47 |
48 |
49 | def test_approval_event_fires(accounts, token):
50 | tx = token.approve(accounts[1], 10**19, {"from": accounts[0]})
51 |
52 | assert len(tx.events) == 1
53 | assert tx.events["Approval"].values() == [accounts[0], accounts[1], 10**19]
54 |
--------------------------------------------------------------------------------
/lesson-11-event/unsolved/tests/test_lend.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | import pytest
4 | import requests
5 | from brownie import *
6 |
7 |
8 | def test_price_oracle_function_exists(minter):
9 | assert hasattr(minter, "price_usd")
10 |
11 |
12 | def test_price_oracle_returns_value(minter, is_forked):
13 | if not is_forked:
14 | pytest.skip()
15 | url = f"https://api.coingecko.com/api/v3/simple/price?ids=curve-dao-token&vs_currencies=usd"
16 | price = json.loads(requests.get(url).content).get("curve-dao-token").get("usd")
17 | assert (minter.price_usd() / 10**18) > price * 0.95
18 | assert (minter.price_usd() / 10**18) < price * 1.05
19 |
--------------------------------------------------------------------------------
/lesson-12-dynarray/README.md:
--------------------------------------------------------------------------------
1 | # Lesson 12: DynArray
2 |
3 | ## [🎥 Video 12 : Dynamic Arrays 🎬](https://youtu.be/txx68frMlho)
4 |
5 | With our stablecoin at feature parity, this lesson covers more advanced concepts.
6 |
7 | Specifically, we focus on upgrading our stablecoin to utilize the concept of liquidity bands from the actual $crvUSD. Instead of adding liquidity at a specific liquidation price, the real $crvUSD adds liquidity at a range of liquidity prices, and smoothly liquidates and de-liquidates as the price sweeps throughout this range.
8 |
9 | To accomplish this, we introduce two new concepts in this video. We store the concept of liquidity bands as a Vyper `dynamic array`, and store the maximum number of possible bands as a `constant`.
10 |
11 | Due to the complexity of the solution, we do not walk through the entire upgrade in the video. Our solution is stored in the `solved/` directory and may be different from your solution. We encourage you to share your solution with us for feedback.
12 |
13 |
14 | ## VYPER DYNAMIC ARRAY
15 | A variable type that functions similar to a Python array, up to a maximum length.
16 | The EVM can't process uncapped arrays, so forcing a ceiling allows for compiler optimizations.
17 |
18 | my_array: DynArray[type, max_length]
19 |
20 |
21 | ## VYPER CONSTANT
22 | A constant can be used whenever Vyper requires a fixed number, like in loops.
23 | Constants are the only state variable type defined directly on instantiation.
24 |
25 | MY_CONSTANT: constant(uint256) = 10000
26 |
27 |
28 | ## FURTHER READING
29 |
30 | * https://github.com/curvefi/curve-stablecoin
31 | * https://github.com/curvefi/curve-stablecoin-js
32 |
--------------------------------------------------------------------------------
/lesson-12-dynarray/solved/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/curvefi/vyper-tutorial/c7f45d979d6a87e90a3d1ce9912d4f47c5bf8aa2/lesson-12-dynarray/solved/.DS_Store
--------------------------------------------------------------------------------
/lesson-12-dynarray/solved/.gitattributes:
--------------------------------------------------------------------------------
1 | *.sol linguist-language=Solidity
2 | *.vy linguist-language=Python
3 |
--------------------------------------------------------------------------------
/lesson-12-dynarray/solved/.github/workflows/main.yaml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | pull_request:
4 | schedule:
5 | # run this workflow every Monday at 1PM UTC
6 | - cron: "* 13 * * 1"
7 |
8 | name: main workflow
9 |
10 | env:
11 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
12 |
13 | jobs:
14 |
15 | tests:
16 | runs-on: ubuntu-latest
17 |
18 | steps:
19 | - uses: actions/checkout@v2
20 |
21 | - name: Cache Compiler Installations
22 | uses: actions/cache@v2
23 | with:
24 | path: |
25 | ~/.solcx
26 | ~/.vvm
27 | key: compiler-cache
28 |
29 | - name: Setup Node.js
30 | uses: actions/setup-node@v1
31 |
32 | - name: Install Ganache
33 | run: npm install -g ganache-cli@6.10.1
34 |
35 | - name: Setup Python 3.8
36 | uses: actions/setup-python@v2
37 | with:
38 | python-version: 3.8
39 |
40 | - name: Install Requirements
41 | run: pip install -r requirements.txt
42 |
43 | - name: Run Tests
44 | run: brownie test -C
45 |
--------------------------------------------------------------------------------
/lesson-12-dynarray/solved/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 | .history
3 | .hypothesis/
4 | build/
5 | reports/
6 |
--------------------------------------------------------------------------------
/lesson-12-dynarray/solved/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Brownie Mixes
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/lesson-12-dynarray/solved/brownie-config.yaml:
--------------------------------------------------------------------------------
1 | autofetch_sources: true
2 | networks:
3 | default: mainnet-fork
4 |
--------------------------------------------------------------------------------
/lesson-12-dynarray/solved/requirements.txt:
--------------------------------------------------------------------------------
1 | eth-brownie>=1.11.0,<2.0.0
2 |
--------------------------------------------------------------------------------
/lesson-12-dynarray/solved/scripts/token.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | from brownie import Token, accounts
4 |
5 |
6 | def main():
7 | return Token.deploy("Test Token", "TST", 18, 1e21, {'from': accounts[0]})
8 |
--------------------------------------------------------------------------------
/lesson-12-dynarray/solved/tests/conftest.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import pytest
4 | from brownie import network
5 | from brownie_tokens import MintableForkToken
6 |
7 |
8 | @pytest.fixture(scope="function", autouse=True)
9 | def isolate(fn_isolation):
10 | # perform a chain rewind after completing each test, to ensure proper isolation
11 | # https://eth-brownie.readthedocs.io/en/v1.10.3/tests-pytest-intro.html#isolation-fixtures
12 | pass
13 |
14 |
15 | @pytest.fixture(scope="module")
16 | def token(Token, accounts):
17 | return Token.deploy("Test Token", "TST", 18, 1e21, {"from": accounts[0]})
18 |
19 |
20 | @pytest.fixture(scope="module")
21 | def is_forked():
22 | if "fork" in network.show_active():
23 | return True
24 | else:
25 | return False
26 |
27 |
28 | @pytest.fixture(scope="module")
29 | def crv(accounts, Token, is_forked):
30 | if is_forked:
31 | crv = MintableForkToken("0xD533a949740bb3306d119CC777fa900bA034cd52")
32 | crv._mint_for_testing(accounts[0], 10**18)
33 | else:
34 | crv = Token.deploy("Dummy CRV", "CRV", 18, 10**18, {"from": accounts[0]})
35 | return crv
36 |
37 |
38 | @pytest.fixture(scope="module")
39 | def minter(Minter, accounts, token, crv):
40 | minter = Minter.deploy(token, crv, {"from": accounts[0]})
41 | token.set_minter(minter, {"from": token.owner()})
42 | crv.approve(minter, crv.balanceOf(accounts[0]), {"from": accounts[0]})
43 | return minter
44 |
--------------------------------------------------------------------------------
/lesson-12-dynarray/solved/tests/test_approve.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import pytest
4 |
5 |
6 | @pytest.mark.parametrize("idx", range(5))
7 | def test_initial_approval_is_zero(token, accounts, idx):
8 | assert token.allowance(accounts[0], accounts[idx]) == 0
9 |
10 |
11 | def test_approve(token, accounts):
12 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
13 |
14 | assert token.allowance(accounts[0], accounts[1]) == 10**19
15 |
16 |
17 | def test_modify_approve(token, accounts):
18 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
19 | token.approve(accounts[1], 12345678, {"from": accounts[0]})
20 |
21 | assert token.allowance(accounts[0], accounts[1]) == 12345678
22 |
23 |
24 | def test_revoke_approve(token, accounts):
25 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
26 | token.approve(accounts[1], 0, {"from": accounts[0]})
27 |
28 | assert token.allowance(accounts[0], accounts[1]) == 0
29 |
30 |
31 | def test_approve_self(token, accounts):
32 | token.approve(accounts[0], 10**19, {"from": accounts[0]})
33 |
34 | assert token.allowance(accounts[0], accounts[0]) == 10**19
35 |
36 |
37 | def test_only_affects_target(token, accounts):
38 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
39 |
40 | assert token.allowance(accounts[1], accounts[0]) == 0
41 |
42 |
43 | def test_returns_true(token, accounts):
44 | tx = token.approve(accounts[1], 10**19, {"from": accounts[0]})
45 |
46 | assert tx.return_value is True
47 |
48 |
49 | def test_approval_event_fires(accounts, token):
50 | tx = token.approve(accounts[1], 10**19, {"from": accounts[0]})
51 |
52 | assert len(tx.events) == 1
53 | assert tx.events["Approval"].values() == [accounts[0], accounts[1], 10**19]
54 |
--------------------------------------------------------------------------------
/lesson-12-dynarray/solved/tests/test_lend.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | import pytest
4 | import requests
5 | from brownie import *
6 |
7 |
8 | def test_price_oracle_function_exists(minter):
9 | assert hasattr(minter, "price_usd")
10 |
11 |
12 | def test_price_oracle_returns_value(minter, is_forked):
13 | if not is_forked:
14 | pytest.skip()
15 | url = f"https://api.coingecko.com/api/v3/simple/price?ids=curve-dao-token&vs_currencies=usd"
16 | price = json.loads(requests.get(url).content).get("curve-dao-token").get("usd")
17 | assert (minter.price_usd() / 10**18) > price * 0.95
18 | assert (minter.price_usd() / 10**18) < price * 1.05
19 |
--------------------------------------------------------------------------------
/lesson-12-dynarray/unsolved/.gitattributes:
--------------------------------------------------------------------------------
1 | *.sol linguist-language=Solidity
2 | *.vy linguist-language=Python
3 |
--------------------------------------------------------------------------------
/lesson-12-dynarray/unsolved/.github/workflows/main.yaml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | pull_request:
4 | schedule:
5 | # run this workflow every Monday at 1PM UTC
6 | - cron: "* 13 * * 1"
7 |
8 | name: main workflow
9 |
10 | env:
11 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
12 |
13 | jobs:
14 |
15 | tests:
16 | runs-on: ubuntu-latest
17 |
18 | steps:
19 | - uses: actions/checkout@v2
20 |
21 | - name: Cache Compiler Installations
22 | uses: actions/cache@v2
23 | with:
24 | path: |
25 | ~/.solcx
26 | ~/.vvm
27 | key: compiler-cache
28 |
29 | - name: Setup Node.js
30 | uses: actions/setup-node@v1
31 |
32 | - name: Install Ganache
33 | run: npm install -g ganache-cli@6.10.1
34 |
35 | - name: Setup Python 3.8
36 | uses: actions/setup-python@v2
37 | with:
38 | python-version: 3.8
39 |
40 | - name: Install Requirements
41 | run: pip install -r requirements.txt
42 |
43 | - name: Run Tests
44 | run: brownie test -C
45 |
--------------------------------------------------------------------------------
/lesson-12-dynarray/unsolved/.gitignore:
--------------------------------------------------------------------------------
1 | __pycache__
2 | .history
3 | .hypothesis/
4 | build/
5 | reports/
6 |
--------------------------------------------------------------------------------
/lesson-12-dynarray/unsolved/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Brownie Mixes
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/lesson-12-dynarray/unsolved/brownie-config.yaml:
--------------------------------------------------------------------------------
1 | autofetch_sources: true
2 | networks:
3 | default: mainnet-fork
4 |
--------------------------------------------------------------------------------
/lesson-12-dynarray/unsolved/requirements.txt:
--------------------------------------------------------------------------------
1 | eth-brownie>=1.11.0,<2.0.0
2 |
--------------------------------------------------------------------------------
/lesson-12-dynarray/unsolved/scripts/token.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | from brownie import Token, accounts
4 |
5 |
6 | def main():
7 | return Token.deploy("Test Token", "TST", 18, 1e21, {'from': accounts[0]})
8 |
--------------------------------------------------------------------------------
/lesson-12-dynarray/unsolved/tests/conftest.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import pytest
4 | from brownie import network
5 | from brownie_tokens import MintableForkToken
6 |
7 |
8 | @pytest.fixture(scope="function", autouse=True)
9 | def isolate(fn_isolation):
10 | # perform a chain rewind after completing each test, to ensure proper isolation
11 | # https://eth-brownie.readthedocs.io/en/v1.10.3/tests-pytest-intro.html#isolation-fixtures
12 | pass
13 |
14 |
15 | @pytest.fixture(scope="module")
16 | def token(Token, accounts):
17 | return Token.deploy("Test Token", "TST", 18, 1e21, {"from": accounts[0]})
18 |
19 |
20 | @pytest.fixture(scope="module")
21 | def is_forked():
22 | if "fork" in network.show_active():
23 | return True
24 | else:
25 | return False
26 |
27 |
28 | @pytest.fixture(scope="module")
29 | def crv(accounts, Token, is_forked):
30 | if is_forked:
31 | crv = MintableForkToken("0xD533a949740bb3306d119CC777fa900bA034cd52")
32 | crv._mint_for_testing(accounts[0], 10**18)
33 | else:
34 | crv = Token.deploy("Dummy CRV", "CRV", 18, 10**18, {"from": accounts[0]})
35 | return crv
36 |
37 |
38 | @pytest.fixture(scope="module")
39 | def minter(Minter, accounts, token, crv):
40 | minter = Minter.deploy(token, crv, {"from": accounts[0]})
41 | token.set_minter(minter, {"from": token.owner()})
42 | crv.approve(minter, crv.balanceOf(accounts[0]), {"from": accounts[0]})
43 | return minter
44 |
--------------------------------------------------------------------------------
/lesson-12-dynarray/unsolved/tests/test_approve.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python3
2 |
3 | import pytest
4 |
5 |
6 | @pytest.mark.parametrize("idx", range(5))
7 | def test_initial_approval_is_zero(token, accounts, idx):
8 | assert token.allowance(accounts[0], accounts[idx]) == 0
9 |
10 |
11 | def test_approve(token, accounts):
12 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
13 |
14 | assert token.allowance(accounts[0], accounts[1]) == 10**19
15 |
16 |
17 | def test_modify_approve(token, accounts):
18 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
19 | token.approve(accounts[1], 12345678, {"from": accounts[0]})
20 |
21 | assert token.allowance(accounts[0], accounts[1]) == 12345678
22 |
23 |
24 | def test_revoke_approve(token, accounts):
25 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
26 | token.approve(accounts[1], 0, {"from": accounts[0]})
27 |
28 | assert token.allowance(accounts[0], accounts[1]) == 0
29 |
30 |
31 | def test_approve_self(token, accounts):
32 | token.approve(accounts[0], 10**19, {"from": accounts[0]})
33 |
34 | assert token.allowance(accounts[0], accounts[0]) == 10**19
35 |
36 |
37 | def test_only_affects_target(token, accounts):
38 | token.approve(accounts[1], 10**19, {"from": accounts[0]})
39 |
40 | assert token.allowance(accounts[1], accounts[0]) == 0
41 |
42 |
43 | def test_returns_true(token, accounts):
44 | tx = token.approve(accounts[1], 10**19, {"from": accounts[0]})
45 |
46 | assert tx.return_value is True
47 |
48 |
49 | def test_approval_event_fires(accounts, token):
50 | tx = token.approve(accounts[1], 10**19, {"from": accounts[0]})
51 |
52 | assert len(tx.events) == 1
53 | assert tx.events["Approval"].values() == [accounts[0], accounts[1], 10**19]
54 |
--------------------------------------------------------------------------------
/lesson-12-dynarray/unsolved/tests/test_lend.py:
--------------------------------------------------------------------------------
1 | import json
2 |
3 | import pytest
4 | import requests
5 | from brownie import *
6 |
7 |
8 | def test_price_oracle_function_exists(minter):
9 | assert hasattr(minter, "price_usd")
10 |
11 |
12 | def test_price_oracle_returns_value(minter, is_forked):
13 | if not is_forked:
14 | pytest.skip()
15 | url = f"https://api.coingecko.com/api/v3/simple/price?ids=curve-dao-token&vs_currencies=usd"
16 | price = json.loads(requests.get(url).content).get("curve-dao-token").get("usd")
17 | assert (minter.price_usd() / 10**18) > price * 0.95
18 | assert (minter.price_usd() / 10**18) < price * 1.05
19 |
--------------------------------------------------------------------------------