-
Notifications
You must be signed in to change notification settings - Fork 11.1k
/
flash_lender.move
178 lines (147 loc) · 6.84 KB
/
flash_lender.move
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
// Copyright (c) 2022, Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0
/// A flash loan that works for any Coin type
module defi::flash_lender {
use sui::balance::{Self, Balance};
use sui::coin::{Self, Coin};
use sui::object::{Self, ID, UID};
use sui::transfer;
use sui::tx_context::{Self, TxContext};
/// A shared object offering flash loans to any buyer willing to pay `fee`.
struct FlashLender<phantom T> has key {
id: UID,
/// Coins available to be lent to prospective borrowers
to_lend: Balance<T>,
/// Number of `Coin<T>`'s that will be charged for the loan.
/// In practice, this would probably be a percentage, but
/// we use a flat fee here for simplicity.
fee: u64,
}
/// A "hot potato" struct recording the number of `Coin<T>`'s that
/// were borrowed. Because this struct does not have the `key` or
/// `store` ability, it cannot be transferred or otherwise placed in
/// persistent storage. Because it does not have the `drop` ability,
/// it cannot be discarded. Thus, the only way to get rid of this
/// struct is to call `repay` sometime during the transaction that created it,
/// which is exactly what we want from a flash loan.
struct Receipt<phantom T> {
/// ID of the flash lender object the debt holder borrowed from
flash_lender_id: ID,
/// Total amount of funds the borrower must repay: amount borrowed + the fee
repay_amount: u64
}
/// An object conveying the privilege to withdraw funds from and deposit funds to the
/// `FlashLender` instance with ID `flash_lender_id`. Initially granted to the creator
/// of the `FlashLender`, and only one `AdminCap` per lender exists.
struct AdminCap has key, store {
id: UID,
flash_lender_id: ID,
}
/// Attempted to borrow more than the `FlashLender` has.
/// Try borrowing a smaller amount.
const ELoanTooLarge: u64 = 0;
/// Tried to repay an amount other than `repay_amount` (i.e., the amount borrowed + the fee).
/// Try repaying the proper amount.
const EInvalidRepaymentAmount: u64 = 1;
/// Attempted to repay a `FlashLender` that was not the source of this particular debt.
/// Try repaying the correct lender.
const ERepayToWrongLender: u64 = 2;
/// Attempted to perform an admin-only operation without valid permissions
/// Try using the correct `AdminCap`
const EAdminOnly: u64 = 3;
/// Attempted to withdraw more than the `FlashLender` has.
/// Try withdrawing a smaller amount.
const EWithdrawTooLarge: u64 = 4;
// === Creating a flash lender ===
/// Create a shared `FlashLender` object that makes `to_lend` available for borrowing.
/// Any borrower will need to repay the borrowed amount and `fee` by the end of the
/// current transaction.
public fun new<T>(to_lend: Balance<T>, fee: u64, ctx: &mut TxContext): AdminCap {
let id = object::new(ctx);
let flash_lender_id = object::uid_to_inner(&id);
let flash_lender = FlashLender { id, to_lend, fee };
// make the `FlashLender` a shared object so anyone can request loans
transfer::share_object(flash_lender);
// give the creator admin permissions
AdminCap { id: object::new(ctx), flash_lender_id }
}
/// Same as `new`, but transfer `WithdrawCap` to the transaction sender
public entry fun create<T>(to_lend: Coin<T>, fee: u64, ctx: &mut TxContext) {
let balance = coin::into_balance(to_lend);
let withdraw_cap = new(balance, fee, ctx);
transfer::transfer(withdraw_cap, tx_context::sender(ctx))
}
// === Core functionality: requesting a loan and repaying it ===
/// Request a loan of `amount` from `lender`. The returned `Receipt<T>` "hot potato" ensures
/// that the borrower will call `repay(lender, ...)` later on in this tx.
/// Aborts if `amount` is greater that the amount that `lender` has available for lending.
public fun loan<T>(
self: &mut FlashLender<T>, amount: u64, ctx: &mut TxContext
): (Coin<T>, Receipt<T>) {
let to_lend = &mut self.to_lend;
assert!(balance::value(to_lend) >= amount, ELoanTooLarge);
let loan = coin::take(to_lend, amount, ctx);
let repay_amount = amount + self.fee;
let receipt = Receipt { flash_lender_id: object::id(self), repay_amount };
(loan, receipt)
}
/// Repay the loan recorded by `receipt` to `lender` with `payment`.
/// Aborts if the repayment amount is incorrect or `lender` is not the `FlashLender`
/// that issued the original loan.
public fun repay<T>(self: &mut FlashLender<T>, payment: Coin<T>, receipt: Receipt<T>) {
let Receipt { flash_lender_id, repay_amount } = receipt;
assert!(object::id(self) == flash_lender_id, ERepayToWrongLender);
assert!(coin::value(&payment) == repay_amount, EInvalidRepaymentAmount);
coin::put(&mut self.to_lend, payment)
}
// === Admin-only functionality ===
/// Allow admin for `self` to withdraw funds.
public fun withdraw<T>(
self: &mut FlashLender<T>,
admin_cap: &AdminCap,
amount: u64,
ctx: &mut TxContext
): Coin<T> {
// only the holder of the `AdminCap` for `self` can withdraw funds
check_admin(self, admin_cap);
let to_lend = &mut self.to_lend;
assert!(balance::value(to_lend) >= amount, EWithdrawTooLarge);
coin::take(to_lend, amount, ctx)
}
/// Allow admin to add more funds to `self`
public entry fun deposit<T>(
self: &mut FlashLender<T>, admin_cap: &AdminCap, coin: Coin<T>
) {
// only the holder of the `AdminCap` for `self` can deposit funds
check_admin(self, admin_cap);
coin::put(&mut self.to_lend, coin);
}
/// Allow admin to update the fee for `self`
public entry fun update_fee<T>(
self: &mut FlashLender<T>, admin_cap: &AdminCap, new_fee: u64
) {
// only the holder of the `AdminCap` for `self` can update the fee
check_admin(self, admin_cap);
self.fee = new_fee
}
fun check_admin<T>(self: &FlashLender<T>, admin_cap: &AdminCap) {
assert!(object::borrow_id(self) == &admin_cap.flash_lender_id, EAdminOnly);
}
// === Reads ===
/// Return the current fee for `self`
public fun fee<T>(self: &FlashLender<T>): u64 {
self.fee
}
/// Return the maximum amount available for borrowing
public fun max_loan<T>(self: &FlashLender<T>): u64 {
balance::value(&self.to_lend)
}
/// Return the amount that the holder of `self` must repay
public fun repay_amount<T>(self: &Receipt<T>): u64 {
self.repay_amount
}
/// Return the amount that the holder of `self` must repay
public fun flash_lender_id<T>(self: &Receipt<T>): ID {
self.flash_lender_id
}
}