├── .gas-snapshot
├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── .gitmodules
├── .vscode
└── settings.json
├── LICENSE
├── README.md
├── foundry.toml
├── src
├── AdvancedFlashLender.sol
├── IntermediateFlashLender.sol
├── SimpleFlashLender.sol
└── interfaces
│ └── IFlashBorrower.sol
└── test
├── AdvancedFlashLender.t.sol
├── IntermediateFlashLender.t.sol
└── SimpleFlashLender.t.sol
/.gas-snapshot:
--------------------------------------------------------------------------------
1 | TestContract:testBar() (gas: 401)
2 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on:
3 | push:
4 | branches:
5 | - master
6 | pull_request:
7 |
8 | env:
9 | FOUNDRY_PROFILE: ci
10 |
11 | jobs:
12 | run-ci:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: actions/checkout@v2
16 |
17 | - name: Install Foundry
18 | uses: foundry-rs/foundry-toolchain@v1
19 | with:
20 | version: nightly
21 |
22 | - name: Install deps
23 | run: forge install
24 |
25 | - name: Check gas snapshots
26 | run: forge snapshot --check
27 |
28 | - name: Run tests
29 | run: forge test
30 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | cache/
2 | out/
3 | lib/
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "lib/forge-std"]
2 | path = lib/forge-std
3 | url = https://github.com/foundry-rs/forge-std
4 | [submodule "lib/solmate"]
5 | path = lib/solmate
6 | url = https://github.com/transmissions11/solmate
7 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "solidity.packageDefaultDependenciesContractsDirectory": "src",
3 | "solidity.packageDefaultDependenciesDirectory": "lib",
4 | "search.exclude": { "lib": true },
5 | "files.associations": {
6 | ".dapprc": "shellscript",
7 | ".gas-snapshot": "julia"
8 | }
9 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | This is free and unencumbered software released into the public domain.
2 |
3 | Anyone is free to copy, modify, publish, use, compile, sell, or
4 | distribute this software, either in source code form or as a compiled
5 | binary, for any purpose, commercial or non-commercial, and by any
6 | means.
7 |
8 | In jurisdictions that recognize copyright laws, the author or authors
9 | of this software dedicate any and all copyright interest in the
10 | software to the public domain. We make this dedication for the benefit
11 | of the public at large and to the detriment of our heirs and
12 | successors. We intend this dedication to be an overt act of
13 | relinquishment in perpetuity of all present and future rights to this
14 | software under copyright law.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | For more information, please refer to
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #
Penn Flashloan Workshop
2 |
3 | **Contracts for my Flashloan workshop at UPenn on November 12th**
4 |
5 | ## Introduction to Flashloans
6 | Flash loans are uncollateralized loans in which a user borrows and returns funds in the same transaction. If the user is unable to return the funds before the end of the transaction, the contract will simply revert. This means that the user will not lose any money and will simply go back to their original balance, with the only cost being the gas fee paid by the user.
7 |
8 | Due to their low-risk, simple nature, flash loans have become increasingly popular in the DeFi space. Being to the go-to way to borrow large sums of money without having to put up any collateral, they have been used to speculate on the price of various assets, to arbitrage between exchanges, and to fund liquidity pools.
9 |
10 | For example, flash loans are heavily used in arbitrage opportunities. Users can take out a flash loan, use the funds to buy an asset at a lower price on one exchange, and then sell it on another exchange for a higher price. The user can then return the loan amount plus a small fee to the protocol and keep the profit, which, due to the size of the loan, can be significant.
11 |
12 | ### Contracts
13 |
14 | #### **SimpleFlashLender.sol**
15 |
16 | This is the most basic flash lender contract. It only has a single function, `borrow(uint256 amount, IFlashLender borrower)`, which executes a flash loan. There are no fees charged on flash borrows. Deposits occur through raw ERC20 transfers and, since there is no `withdraw()` function, liquidity providers cannot withdraw their funds.
17 |
18 |
19 |
20 | #### **IntermediateFlashLender.sol**
21 |
22 | This is slightly more complex than the `SimpleFlashLender` contract. It now allows liquidity providers to deposit and withdraw funds through the `deposit(uint256)` and `withdraw(uint256)` functions. There are also fees charged during flash loans, *but* they can only be collected by the owner, who is the deployer of the contract. Liquidity providers are only entitled to their original deposits.
23 |
24 |
25 | #### **AdvancedFlashLender.sol**
26 |
27 | While this contract isn't yet ready for production use, it has a variety of feature that should (and would) be used in a flash lending protocol. This contract uses an "IOU" (aka share) based system to keep track of user balance. Essentially, liquidity providers are minted shares when they deposit tokens into the contract. As the contract starts to accrue more tokens through fees, the value of their shares increase in value and they are therefore able to withdraw more.
28 |
29 | For example, if you deposit 10 tokens into the contract when the total balance is at 100 tokens, you'll be minted 10% of the token supply. As the contract's token balance increases, the value of your shares increase. So, once the contract has a balance of 1000 tokens, your shares (which represent 10% of the token supply) will enable you to unlock 100 tokens.
30 |
31 | ## Getting Started
32 |
33 | Clone this template by running:
34 | ```sh
35 | git clone https://github.com/JetJadeja/flashloan-workshop.git
36 | ```
37 |
38 | Ensure to have foundry installed. You can easily install it by running:
39 | ```sh
40 | curl -L https://foundry.paradigm.xyz | bash
41 | ```
42 |
43 | Then, run foundryup in a new terminal session or after reloading your PATH. You can run foundryup by simply doing:
44 | ```sh
45 | foundryup
46 | ```
47 |
48 | ## Compiling and testing the codebase.
49 | To compile the codebase, you can run:
50 |
51 | ```sh
52 | forge build
53 | ```
54 |
55 | To test the codebase, you can run:
56 |
57 | ```sh
58 | forge test
59 | ```
--------------------------------------------------------------------------------
/foundry.toml:
--------------------------------------------------------------------------------
1 | [profile.ci]
2 | fuzz-runs = 10_000
3 |
--------------------------------------------------------------------------------
/src/AdvancedFlashLender.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense
2 | pragma solidity 0.8.17;
3 |
4 | // Interfaces
5 | import "src/interfaces/IFlashBorrower.sol";
6 | import "solmate/tokens/ERC20.sol";
7 |
8 | // Libraries
9 | import "solmate/utils/SafeTransferLib.sol";
10 |
11 | /// @title Advanced Flash Lender
12 | /// @author Jet Jadeja
13 | /// @notice Flash lender contract that incentivises token holders to supply liquidity to earn yield from fees.
14 | /// @notice This contract uses an "IOU" system (shares) to track the amount of tokens that are owed to each user.
15 | /// This basically means that it will mint tokens to liquidity providers when they deposit funds, and
16 | /// burn them when they withdraw funds. This allows the contract to keep track of how much each user
17 | /// has supplied.
18 | contract AdvancedFlashLender is ERC20 {
19 | using SafeTransferLib for ERC20;
20 |
21 | /*///////////////////////////////////////////////////////////////
22 | CONSTANTS
23 | //////////////////////////////////////////////////////////////*/
24 |
25 | /// @notice The address of the ERC20 contract.
26 | ERC20 public immutable TOKEN;
27 |
28 | /// @notice The percentage fee that is charged on flash loans.
29 | /// It is important to note that this is a "mantissa", which means that it is scaled by 1e18.
30 | /// So, a fee of 100% would actually be 1e18. A fee of 5% would be 0.05e18.
31 | uint256 public immutable FEE;
32 |
33 | /*///////////////////////////////////////////////////////////////
34 | CONSTRUCTOR
35 | //////////////////////////////////////////////////////////////*/
36 |
37 | /// @notice Set up the flash lender contract.
38 | constructor(ERC20 _TOKEN, uint256 _FEE) ERC20("Advanced Flash Lender Share", "AFLS", 18) {
39 | // Set our immutable values.
40 | TOKEN = _TOKEN;
41 | FEE = _FEE;
42 | }
43 |
44 | /*///////////////////////////////////////////////////////////////
45 | SHARE PRICE INTERFACE
46 | //////////////////////////////////////////////////////////////*/
47 |
48 | /// @notice Calculate the exchange rate between the share tokens and the underlying tokens.
49 | /// @return The exchange rate scaled by 1e18.
50 | function sharePrice() public view returns (uint256) {
51 | // Store the contracts's total underlying balance and IOU supply.
52 | uint256 supply = totalSupply;
53 | uint256 balance = TOKEN.balanceOf(address(this));
54 |
55 | // If the supply or balance is zero, return an exchange rate of 1.
56 | if (supply == 0 || balance == 0) return 1e18;
57 |
58 | // Calculate the exchange rate by diving the underlying balance by the share supply.
59 | return (balance * 1e18) / supply;
60 | }
61 |
62 | /// @notice Calculate the amount of underlying tokens that are held by a user.
63 | function balanceOfUnderlying(address account) public view returns (uint256) {
64 | // If this function is called in the middle of a transaction, the share price will change.
65 | require(!inFlashLoan, "Cannot call this function during a flash loan");
66 |
67 | // Calculate and return the amount of underlying tokens held by the user.
68 | return (balanceOf[account] * sharePrice()) / 1e18;
69 | }
70 |
71 | /*///////////////////////////////////////////////////////////////
72 | LIQUIDITY PROVIDER INTERFACE
73 | //////////////////////////////////////////////////////////////*/
74 |
75 | /// @notice Deposit funds into the flash lender contract.
76 | /// @param amount The amount of funds to deposit.
77 | function deposit(uint256 amount) external {
78 | // Ensure that the contract is not currently executing a flash loan.
79 | require(!inFlashLoan, "Cannot deposit while flash loan is active");
80 |
81 | // Transfer the tokens from the sender to the contract.
82 | // Must have tokens approved.
83 | TOKEN.safeTransferFrom(msg.sender, address(this), amount);
84 |
85 | // Mint shares to the depositor.
86 | _mint(msg.sender, (amount * 1e18) / sharePrice());
87 | }
88 |
89 | /// @notice Withdraw funds from the flash lender contract.
90 | /// @param amount The amount of funds to withdraw.
91 | /// Will fail if the liquidity provider does not have enough funds.
92 | function withdraw(uint256 amount) external {
93 | // Ensure that the contract is not currently executing a flash loan.
94 | require(!inFlashLoan, "Cannot withdraw while flash loan is active");
95 |
96 | // Calculate the amount of shares needed to withdraw the given amount of tokens.
97 | uint256 shares = (sharePrice() * amount) / 1e18;
98 |
99 | // Ensure that the liquidity provider has enough funds.
100 | // This technically isn't necessary. Solidity SafeMath would automatically revert.
101 | require(balanceOf[msg.sender] >= shares, "Not enough funds");
102 |
103 | // Burn the shares from the liquidity provider.
104 | _burn(msg.sender, (sharePrice() * amount) / 1e18);
105 |
106 | // Transfer the tokens from the contract to the liquidity provider.
107 | TOKEN.safeTransfer(msg.sender, amount);
108 | }
109 |
110 | /*///////////////////////////////////////////////////////////////
111 | LENDING INTERFACE
112 | //////////////////////////////////////////////////////////////*/
113 |
114 | /// @notice Boolean indicating whether the contract is currently in a flash loan.
115 | bool public inFlashLoan;
116 |
117 | /// @notice Borrow funds from the flash lender contract.
118 | function borrow(uint256 amount, IFlashBorrower borrower) external {
119 | // Ensure that the contract is not currently executing a flash loan.
120 | // We do this to prevent a reentrancy attack where the borrower calls borrow again, which
121 | // enables them to set inFlashLoan to false and bypass the isFlashLoan checks in deposit/withdraw.
122 | require(!inFlashLoan, "Cannot borrow while flash loan is active");
123 |
124 | // Set inFlashLoan to true.
125 | // We do this to prevent a potential attacker from taking advantage of the withdraw() function.
126 | // This is because the sharePrice function uses the token balance of the contract to calculate
127 | // the exchange rate. This value drops when tokens are being loaned out, which would cause the
128 | // share price to drop. This would allow an attacker to withdraw more tokens than they should be
129 | // able to, and then repay the loan with the extra tokens.
130 | inFlashLoan = true;
131 |
132 | // Store the current balance of the contract.
133 | uint256 balance = TOKEN.balanceOf(address(this));
134 |
135 | // Calculate the fee.
136 | uint256 fee = (amount * FEE) / 1e18;
137 |
138 | // Transfer the tokens from the contract to the borrower and call the executeOnFlashLoan function.
139 | TOKEN.safeTransfer(address(borrower), amount);
140 | borrower.executeOnFlashLoan(TOKEN, amount, amount + fee);
141 |
142 | // Ensure that the tokens have been returned to the contract.
143 | require(TOKEN.balanceOf(address(this)) >= balance + fee, "Borrower did not return funds");
144 |
145 | // Set inFlashLoan back to false.
146 | inFlashLoan = false;
147 | }
148 | }
149 |
--------------------------------------------------------------------------------
/src/IntermediateFlashLender.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense
2 | pragma solidity 0.8.17;
3 |
4 | // Interfaces
5 | import "src/interfaces/IFlashBorrower.sol";
6 | import "solmate/tokens/ERC20.sol";
7 |
8 | // Libraries
9 | import "solmate/utils/SafeTransferLib.sol";
10 |
11 | /// @title Intermediate Flash Lender
12 | /// @author Jet Jadeja
13 | /// @notice Flash lender contract that allows liquidity providers to deposit and withdraw funds.
14 | contract IntermediateFlashLender {
15 | using SafeTransferLib for ERC20;
16 |
17 | /*///////////////////////////////////////////////////////////////
18 | CONSTANTS
19 | //////////////////////////////////////////////////////////////*/
20 |
21 | /// @notice The address of the ERC20 contract.
22 | ERC20 public immutable TOKEN;
23 |
24 | /// @notice The owner of the contract.
25 | address public immutable OWNER;
26 |
27 | /// @notice The percentage fee that is charged on flash loans.
28 | /// It is important to note that this is a "mantissa", which means that it is scaled by 1e18.
29 | /// So, a fee of 100% would actually be 1e18. A fee of 5% would be 0.05e18.
30 | uint256 public immutable FEE;
31 |
32 | /*///////////////////////////////////////////////////////////////
33 | CONSTRUCTOR
34 | //////////////////////////////////////////////////////////////*/
35 |
36 | /// @notice Set up the flash lender contract.
37 | constructor(ERC20 _TOKEN, uint256 _FEE) {
38 | // Set our immutable values.
39 | TOKEN = _TOKEN;
40 | OWNER = msg.sender;
41 | FEE = _FEE;
42 | }
43 |
44 | /*///////////////////////////////////////////////////////////////
45 | FEE COLLECTION INTERFACE
46 | //////////////////////////////////////////////////////////////*/
47 |
48 | /// @notice Stores total fees collected.
49 | uint256 public totalFees;
50 |
51 | /// @notice Retrieve the fees that have been collected.
52 | function collectFees() external {
53 | // Ensure that the caller is the owner.
54 | require(msg.sender == OWNER, "Only the owner can collect fees");
55 |
56 | // Transfer the tokens from the contract to the owner.
57 | TOKEN.safeTransfer(OWNER, totalFees);
58 |
59 | // Reset the total fees to 0.
60 | delete totalFees;
61 | }
62 |
63 | /*///////////////////////////////////////////////////////////////
64 | LIQUIDITY PROVIDER INTERFACE
65 | //////////////////////////////////////////////////////////////*/
66 |
67 | /// @notice Stores liquidity provider balances.
68 | mapping(address => uint256) public balances;
69 |
70 | /// @notice Deposit funds into the flash lender contract.
71 | /// @param amount The amount of funds to deposit.
72 | function deposit(uint256 amount) external {
73 | // Ensure that the contract is not already in a flash loan.
74 | require(!inFlashLoan, "Already in a flash loan");
75 |
76 | // Transfer the tokens from the sender to the contract.
77 | // Must have tokens approved.
78 | TOKEN.safeTransferFrom(msg.sender, address(this), amount);
79 |
80 | // Update liquidity provider balances.
81 | balances[msg.sender] += amount;
82 | }
83 |
84 | /// @notice Withdraw funds from the flash lender contract.
85 | /// @param amount The amount of funds to withdraw.
86 | /// Will fail if the liquidity provider does not have enough funds.
87 | function withdraw(uint256 amount) external {
88 | // Ensure that the contract is not already in a flash loan.
89 | require(!inFlashLoan, "Already in a flash loan");
90 |
91 | // Ensure that the liquidity provider has enough funds.
92 | // This technically isn't necessary. Solidity SafeMath would automatically revert.
93 | require(balances[msg.sender] >= amount, "Not enough funds");
94 |
95 | // Update liquidity provider balances.
96 | balances[msg.sender] -= amount;
97 |
98 | // Transfer the tokens from the contract to the liquidity provider.
99 | TOKEN.safeTransfer(msg.sender, amount);
100 | }
101 |
102 | /*///////////////////////////////////////////////////////////////
103 | LENDING INTERFACE
104 | //////////////////////////////////////////////////////////////*/
105 |
106 | /// @notice Boolean indicating whether the contract is currently in a flash loan.
107 | bool public inFlashLoan;
108 |
109 | /// @notice Borrow funds from the flash lender contract.
110 | function borrow(uint256 amount, IFlashBorrower borrower) external {
111 | // Ensure that the contract is not already in a flash loan. If it is, set inFlashLoan to true.
112 | // This is to prevent reentrancy.
113 | require(!inFlashLoan, "Already in a flash loan");
114 | inFlashLoan = true;
115 |
116 | // Store the current balance of the contract.
117 | uint256 balance = TOKEN.balanceOf(address(this));
118 |
119 | // Calculate the fee and update the totalFees.
120 | uint256 fee = (amount * FEE) / 1e18;
121 | totalFees += fee;
122 |
123 | // Transfer the tokens from the contract to the borrower and call the executeOnFlashLoan function.
124 | TOKEN.safeTransfer(address(borrower), amount);
125 | borrower.executeOnFlashLoan(TOKEN, amount, amount + fee);
126 |
127 | // Ensure that the tokens have been returned to the contract.
128 | require(TOKEN.balanceOf(address(this)) >= balance + fee, "Borrower did not return funds");
129 |
130 | // Reset inFlashLoan to false.
131 | inFlashLoan = false;
132 | }
133 | }
--------------------------------------------------------------------------------
/src/SimpleFlashLender.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense
2 | pragma solidity 0.8.17;
3 |
4 | // Interfaces
5 | import "src/interfaces/IFlashBorrower.sol";
6 | import "solmate/tokens/ERC20.sol";
7 |
8 | // Libraries
9 | import "solmate/utils/SafeTransferLib.sol";
10 |
11 | /// @title Simple Flash Lender
12 | /// @author Jet Jadeja
13 | /// @notice Simple flash lender contract that allows liquidity providers to deposit and withdraw funds.
14 | contract SimpleFlashLender {
15 | using SafeTransferLib for ERC20;
16 |
17 | /*///////////////////////////////////////////////////////////////
18 | CONSTANTS
19 | //////////////////////////////////////////////////////////////*/
20 |
21 | /// @notice The address of the ERC20 contract.
22 | ERC20 public immutable TOKEN;
23 |
24 | /*///////////////////////////////////////////////////////////////
25 | CONSTRUCTOR
26 | //////////////////////////////////////////////////////////////*/
27 |
28 | /// @notice Set up the flash lender contract.
29 | constructor(ERC20 _TOKEN) {
30 | // Set our token to the correct ERC20 address.
31 | TOKEN = _TOKEN;
32 | }
33 |
34 | /*///////////////////////////////////////////////////////////////
35 | LENDING INTERFACE
36 | //////////////////////////////////////////////////////////////*/
37 |
38 | /// @notice Borrow funds from the flash lender contract.
39 | function borrow(uint256 amount, IFlashBorrower borrower) external {
40 | // Store the current balance of the contract.
41 | uint256 balance = TOKEN.balanceOf(address(this));
42 |
43 | // Transfer the tokens from the contract to the borrower.
44 | TOKEN.safeTransfer(address(borrower), amount);
45 |
46 |
47 | // Call the borrower's executeOnFlashLoan function.
48 | borrower.executeOnFlashLoan(TOKEN, amount, amount);
49 |
50 | // Ensure that the tokens have been returned to the contract.
51 | require(TOKEN.balanceOf(address(this)) >= balance, "Borrower did not return funds");
52 | }
53 | }
--------------------------------------------------------------------------------
/src/interfaces/IFlashBorrower.sol:
--------------------------------------------------------------------------------
1 | pragma solidity 0.8.17;
2 |
3 | // Interfaces
4 | import "solmate/tokens/ERC20.sol";
5 |
6 | /// @title Flash Borrower Interface
7 | /// @notice Interface for flash loan borrowers.
8 | interface IFlashBorrower {
9 | /// @notice The function that is called by the flash lender contract when funds are borrowed.
10 | /// @param token The address of the ERC20 token that is being borrowed.
11 | /// @param amount The amount of funds that are borrowed.
12 | /// @param total The amount that must be returned (including fees).
13 | function executeOnFlashLoan(ERC20 token, uint256 amount, uint256 total) external;
14 | }
--------------------------------------------------------------------------------
/test/AdvancedFlashLender.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense
2 | pragma solidity 0.8.17;
3 |
4 | import "src/AdvancedFlashLender.sol";
5 | import "src/interfaces/IFlashBorrower.sol";
6 |
7 | import "solmate/test/utils/mocks/MockERC20.sol";
8 | import "solmate/tokens/ERC20.sol";
9 |
10 | import {DSTestPlus} from "solmate/test/utils/DSTestPlus.sol";
11 | import "forge-std/console.sol";
12 |
13 |
14 | contract AdvancedFlashLenderTest is DSTestPlus {
15 | AdvancedFlashLender lender;
16 | MockERC20 token;
17 |
18 | function setUp() public {
19 | token = new MockERC20("Test Token", "TEST", 18);
20 | lender = new AdvancedFlashLender(ERC20(address(token)), 0.05e18); // 5% fee
21 |
22 | token.mint(address(this), 1000 ether);
23 | }
24 |
25 | function testDeposit() public {
26 | token.approve(address(lender), 1000 ether);
27 | lender.deposit(1000 ether);
28 |
29 | require(lender.balanceOf(address(this)) == 1000 ether);
30 | require(lender.balanceOfUnderlying(address(this)) == 1000 ether);
31 | }
32 |
33 | function testWithdraw() public {
34 | testDeposit();
35 | lender.withdraw(1000 ether);
36 |
37 | require(lender.balanceOf(address(this)) == 0);
38 | require(lender.balanceOfUnderlying(address(this)) == 0 ether);
39 | }
40 |
41 | function testBalanceIncrease() public {
42 | // Execute a flash loan and pay the 5% fee.
43 | testDeposit();
44 |
45 | GoodBorrower borrower = new GoodBorrower();
46 | token.mint(address(borrower), 50 ether);
47 | lender.borrow(1000 ether, IFlashBorrower(address(borrower)));
48 |
49 |
50 | // The balance of the lender should have increased by 5%.
51 | console.log(lender.balanceOfUnderlying(address(this)));
52 | console.log(lender.sharePrice());
53 | console.log(token.balanceOf(address(lender)), lender.totalSupply());
54 | console.log((token.balanceOf(address(lender)) * 1e18) / lender.totalSupply());
55 |
56 | require(lender.balanceOfUnderlying(address(this)) == 1050 ether, "Value");
57 | }
58 |
59 | function testLoan() public {
60 | testDeposit();
61 |
62 | GoodBorrower borrower = new GoodBorrower();
63 | token.mint(address(borrower), 50 ether);
64 | lender.borrow(1000 ether, IFlashBorrower(address(borrower)));
65 | }
66 |
67 | function testLoanSuccess() public {
68 | testDeposit();
69 |
70 | GoodBorrower borrower = new GoodBorrower();
71 | token.mint(address(borrower), 50 ether);
72 | lender.borrow(1000 ether, IFlashBorrower(address(borrower)));
73 | }
74 |
75 | function testFailRevertsIfFeeNotReturned() public {
76 | testDeposit();
77 |
78 | CheapBorrower borrower = new CheapBorrower();
79 | token.mint(address(borrower), 50 ether);
80 | lender.borrow(1000 ether, IFlashBorrower(address(borrower)));
81 | }
82 |
83 | function testFailRevertsIfNotReturned() public {
84 | testDeposit();
85 |
86 | BadBorrower borrower = new BadBorrower();
87 | lender.borrow(1000 ether, IFlashBorrower(address(borrower)));
88 | }
89 |
90 | function testFailRevertsIfSemiReturned() public {
91 | testDeposit();
92 |
93 | MistakenBorrower borrower = new MistakenBorrower();
94 | token.mint(address(borrower), 50 ether);
95 | lender.borrow(1000 ether, IFlashBorrower(address(borrower)));
96 | }
97 | }
98 |
99 | contract GoodBorrower {
100 | function executeOnFlashLoan(ERC20 token, uint256, uint256 total) external {
101 | token.transfer(msg.sender, total);
102 | }
103 | }
104 |
105 | contract CheapBorrower {
106 | function executeOnFlashLoan(ERC20 token, uint256 amount, uint256) external {
107 | token.transfer(msg.sender, amount);
108 | }
109 | }
110 |
111 | contract BadBorrower {
112 | function executeOnFlashLoan(ERC20 token, uint256, uint256) external {
113 | token.transfer(msg.sender, 0);
114 | }
115 | }
116 |
117 | contract MistakenBorrower {
118 | function executeOnFlashLoan(ERC20 token, uint256 amount, uint256) external {
119 | token.transfer(msg.sender, amount/2);
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/test/IntermediateFlashLender.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense
2 | pragma solidity 0.8.17;
3 |
4 | import "src/IntermediateFlashLender.sol";
5 | import "src/interfaces/IFlashBorrower.sol";
6 |
7 | import "solmate/test/utils/mocks/MockERC20.sol";
8 | import "solmate/tokens/ERC20.sol";
9 |
10 | import {DSTestPlus} from "solmate/test/utils/DSTestPlus.sol";
11 |
12 |
13 | contract IntermediateFlashLenderTest is DSTestPlus {
14 | IntermediateFlashLender lender;
15 | MockERC20 token;
16 |
17 | function setUp() public {
18 | token = new MockERC20("Test Token", "TEST", 18);
19 | lender = new IntermediateFlashLender(ERC20(address(token)), 0.05e18); // 5% fee
20 |
21 | token.mint(address(this), 1000 ether);
22 | }
23 |
24 | function testDeposit() public {
25 | token.approve(address(lender), 1000 ether);
26 | lender.deposit(1000 ether);
27 | }
28 |
29 | function testWithdraw() public {
30 | testDeposit();
31 | lender.withdraw(1000 ether);
32 | }
33 |
34 | function testLoan() public {
35 | testDeposit();
36 |
37 | GoodBorrower borrower = new GoodBorrower();
38 | token.mint(address(borrower), 50 ether);
39 | lender.borrow(1000 ether, IFlashBorrower(address(borrower)));
40 | }
41 |
42 | function testLoanSuccess() public {
43 | testDeposit();
44 |
45 | GoodBorrower borrower = new GoodBorrower();
46 | token.mint(address(borrower), 50 ether);
47 | lender.borrow(1000 ether, IFlashBorrower(address(borrower)));
48 | }
49 |
50 | function testFailRevertsIfFeeNotReturned() public {
51 | testDeposit();
52 |
53 | CheapBorrower borrower = new CheapBorrower();
54 | token.mint(address(borrower), 50 ether);
55 | lender.borrow(1000 ether, IFlashBorrower(address(borrower)));
56 | }
57 |
58 | function testFailRevertsIfNotReturned() public {
59 | testDeposit();
60 |
61 | BadBorrower borrower = new BadBorrower();
62 | lender.borrow(1000 ether, IFlashBorrower(address(borrower)));
63 | }
64 |
65 | function testFailRevertsIfSemiReturned() public {
66 | testDeposit();
67 |
68 | MistakenBorrower borrower = new MistakenBorrower();
69 | token.mint(address(borrower), 50 ether);
70 | lender.borrow(1000 ether, IFlashBorrower(address(borrower)));
71 | }
72 |
73 | function testWithdrawFees() public {
74 | testLoanSuccess();
75 | lender.collectFees();
76 | }
77 | }
78 |
79 | contract GoodBorrower {
80 | function executeOnFlashLoan(ERC20 token, uint256, uint256 total) external {
81 | token.transfer(msg.sender, total);
82 | }
83 | }
84 |
85 | contract CheapBorrower {
86 | function executeOnFlashLoan(ERC20 token, uint256 amount, uint256) external {
87 | token.transfer(msg.sender, amount);
88 | }
89 | }
90 |
91 | contract BadBorrower {
92 | function executeOnFlashLoan(ERC20 token, uint256, uint256) external {
93 | token.transfer(msg.sender, 0);
94 | }
95 | }
96 |
97 | contract MistakenBorrower {
98 | function executeOnFlashLoan(ERC20 token, uint256 amount, uint256) external {
99 | token.transfer(msg.sender, amount/2);
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/test/SimpleFlashLender.t.sol:
--------------------------------------------------------------------------------
1 | // SPDX-License-Identifier: Unlicense
2 | pragma solidity 0.8.17;
3 |
4 | import "src/SimpleFlashLender.sol";
5 | import "src/interfaces/IFlashBorrower.sol";
6 |
7 | import "solmate/test/utils/mocks/MockERC20.sol";
8 | import "solmate/tokens/ERC20.sol";
9 |
10 | import {DSTestPlus} from "solmate/test/utils/DSTestPlus.sol";
11 |
12 |
13 | contract SimpleFlashLenderTest is DSTestPlus {
14 | SimpleFlashLender lender;
15 | MockERC20 token;
16 |
17 | function setUp() public {
18 | token = new MockERC20("Test Token", "TEST", 18);
19 | lender = new SimpleFlashLender(ERC20(address(token)));
20 |
21 | token.mint(address(lender), 1000 ether);
22 | }
23 |
24 | function testLoan() public {
25 | GoodBorrower borrower = new GoodBorrower();
26 | lender.borrow(1000 ether, IFlashBorrower(address(borrower)));
27 | }
28 |
29 | function testLoanSuccess() public {
30 | GoodBorrower borrower = new GoodBorrower();
31 | lender.borrow(1000 ether, IFlashBorrower(address(borrower)));
32 | }
33 |
34 | function testFailRevertsIfNotReturned() public {
35 | BadBorrower borrower = new BadBorrower();
36 | lender.borrow(1000 ether, IFlashBorrower(address(borrower)));
37 | }
38 |
39 | function testFailRevertsIfSemiReturned() public {
40 | MistakenBorrower borrower = new MistakenBorrower();
41 | lender.borrow(1000 ether, IFlashBorrower(address(borrower)));
42 | }
43 |
44 | }
45 |
46 | contract GoodBorrower {
47 | function executeOnFlashLoan(ERC20 token, uint256 amount, uint256) external {
48 | token.transfer(msg.sender, amount);
49 | }
50 | }
51 |
52 | contract BadBorrower {
53 | function executeOnFlashLoan(ERC20 token, uint256, uint256) external {
54 | token.transfer(msg.sender, 0);
55 | }
56 | }
57 |
58 | contract MistakenBorrower {
59 | function executeOnFlashLoan(ERC20 token, uint256 amount, uint256) external {
60 | token.transfer(msg.sender, amount/2);
61 | }
62 | }
63 |
--------------------------------------------------------------------------------