Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Erc20flash mint #407

Open
wants to merge 22 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
ab91bbe
chore: Implemented ERC3156FlashLender and ERC3156FlashBorrower
Ifechukwudaniel Nov 13, 2024
7862012
chore: removed lender
Ifechukwudaniel Nov 14, 2024
a7c0160
chore: flashmint
Ifechukwudaniel Nov 14, 2024
70d67a1
Merge branch 'main' into erc20flashMint
Ifechukwudaniel Nov 14, 2024
47053bc
chore : cargo format
Ifechukwudaniel Nov 14, 2024
6ea75eb
Update contracts/src/token/erc20/extensions/flashmint.rs
Ifechukwudaniel Nov 14, 2024
53ee21a
Update contracts/src/token/erc20/extensions/flashmint.rs
Ifechukwudaniel Nov 14, 2024
3bd25cf
chore: added docs for IERC3156FlashBorrower
Ifechukwudaniel Nov 14, 2024
b9cce5d
chore: docs
Ifechukwudaniel Nov 14, 2024
03f6f38
Merge branch 'main' into erc20flashMint
Ifechukwudaniel Nov 15, 2024
6aa5b6d
Create erc20-flashloan.adoc
Ifechukwudaniel Nov 15, 2024
ebee8d5
Merge pull request #1 from Ifechukwudaniel/patch-1
Ifechukwudaniel Nov 15, 2024
55672f6
chore: removed flashlaon and smaller changes
Ifechukwudaniel Nov 16, 2024
626ae03
chore: cargo format
Ifechukwudaniel Nov 16, 2024
f8cdcf2
chore: abi and constructor
Ifechukwudaniel Nov 16, 2024
85e62b1
chore: flashloan example setup
Ifechukwudaniel Nov 16, 2024
5feeb90
chore: changed erc20FlashMint to erc20FlashMint
Ifechukwudaniel Nov 17, 2024
47aad90
chore: erc20flashmint example
Ifechukwudaniel Nov 17, 2024
99a8089
chore: mock, abi and initial e2e test
Ifechukwudaniel Nov 17, 2024
1b20ea6
chore: created Erc3156FlashBorrowerMock
Ifechukwudaniel Nov 18, 2024
4d08485
chore: minor changes
Ifechukwudaniel Nov 18, 2024
c3271bf
chore: cargo fmt fix
Ifechukwudaniel Nov 18, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ members = [
"lib/e2e-proc",
"examples/erc20",
"examples/erc20-permit",
"examples/erc20-flashmint",
"examples/erc721",
"examples/erc721-consecutive",
"examples/erc721-metadata",
Expand All @@ -32,6 +33,7 @@ default-members = [
"lib/e2e-proc",
"examples/erc20",
"examples/erc20-permit",
"examples/erc20-flashmint",
"examples/erc721",
"examples/erc721-consecutive",
"examples/erc721-metadata",
Expand Down
326 changes: 326 additions & 0 deletions contracts/src/token/erc20/extensions/flashmint.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,326 @@
//! Optional Flashloan extension of the ERC-20 standard.
//! using the IERC3156FlashBorrower interface to borrow tokens.

Ifechukwudaniel marked this conversation as resolved.
Show resolved Hide resolved
use alloy_primitives::{b256, Address, Bytes, B256, U256};
use alloy_sol_types::sol;
use stylus_sdk::{
abi::Bytes as AbiBytes, call::Call, contract, msg, prelude::*,
};

use crate::token::erc20::{
self, utils::borrower::IERC3156FlashBorrower, Erc20, IErc20,
};

Ifechukwudaniel marked this conversation as resolved.
Show resolved Hide resolved
sol! {
/// Indicate an error related to an unsupported loan token.
/// This occurs when the specified token cannot be used for loans.
#[derive(Debug)]
#[allow(missing_docs)]
error ERC3156UnsupportedToken(address token);

/// Indicate an error related to the loan amount exceeds the maximum.
/// The requested amount is higher than the allowed loan for this token max_loan.
#[derive(Debug)]
#[allow(missing_docs)]
error ERC3156ExceededMaxLoan(uint256 max_loan);

/// Indicate an error related to an invalid flash loan receiver.
/// The receiver does not implement the required `onFlashLoan` function.
#[derive(Debug)]
#[allow(missing_docs)]
error ERC3156InvalidReceiver(address receiver);

}

/// Extension of [`Erc20`] that allows token holders to destroy both
/// their own tokens and those that they have an allowance for,
/// in a way that can be recognized off-chain (via event analysis).
pub trait IERC3156FlashLender {
/// The error type associated to this ERC-20 Burnable trait implementation.
type Error: Into<alloc::vec::Vec<u8>>;

/// Returns the maximum amount of tokens that can be borrowed
/// from this contract in a flash loan.
///
/// For tokens that are not supported, this function returns
/// `U256::MIN`.
///
/// * `token` - The address of the ERC-20 token that will be loaned.
fn max_flash_loan(&self, token: Address) -> U256;

/// Calculates the fee for a flash loan.
///
/// The fee is a fixed percentage of the borrowed amount.
///
/// If the token is not supported, the function returns an
/// `UnsupportedToken` error.
///
/// * `token` - The address of the ERC-20 token that will be loaned.
/// * `amount` - The amount of tokens that will be loaned.
fn flash_fee(
&self,
token: Address,
amount: U256,
) -> Result<U256, Self::Error>;

/// Executes a flash loan.
///
/// This function is part of the ERC-3156 (Flash Loans) standard.
///
/// * `receiver` - The contract that will receive the flash loan.
/// * `token` - The ERC-20 token that will be loaned.
/// * `amount` - The amount of tokens that will be loaned.
/// * `data` - Arbitrary data that can be passed to the receiver contract.
///
/// The function must return `true` if the flash loan was successful,
/// and revert otherwise.
fn flash_loan(
&mut self,
receiver: Address,
token: Address,
amount: U256,
data: AbiBytes,
) -> Result<bool, Self::Error>;
}

/// A Permit error.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong docs

#[derive(SolidityError, Debug)]
pub enum Error {
/// Indicate an error related to an unsupported loan token.
/// This occurs when the specified token cannot be used for loans.
UnsupportedToken(ERC3156UnsupportedToken),
Ifechukwudaniel marked this conversation as resolved.
Show resolved Hide resolved

/// Indicate an error related to the loan amount exceeds the maximum.
/// The requested amount is higher than the allowed loan for this token
/// max_loan.
ExceededMaxLoan(ERC3156ExceededMaxLoan),

/// Indicate an error related to an invalid flash loan receiver.
/// The receiver does not implement the required `onFlashLoan` function.
InvalidReceiver(ERC3156InvalidReceiver),

/// Error type from [`Erc20`] contract [`erc20::Error`].
Erc20(erc20::Error),
}

sol_storage! {
pub struct Erc20Flashmint {
uint256 _flash_fee_amount;
address _flash_fee_receiver_address;
Erc20 erc20;
}
}

unsafe impl TopLevelStorage for Erc20Flashmint {}

const RETURN_VALUE: B256 =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add comment above this to make it clear what this value is encoding:

// `keccak256("ERC3156FlashBorrower.onFlashLoan")`

b256!("439148f0bbc682ca079e46d6e2c2f0c1e3b820f1a291b069d8882abf8cf18dd9");

#[public]
impl IERC3156FlashLender for Erc20Flashmint {
type Error = Error;

fn max_flash_loan(&self, token: Address) -> U256 {
self.erc20.total_supply()
// if token == contract::address() {
// return U256::MAX - self.erc20.total_supply();
// }
// U256::MIN
}

fn flash_fee(
&self,
token: Address,
amount: U256,
) -> Result<U256, Self::Error> {
if token != contract::address() {
return Err(Error::UnsupportedToken(ERC3156UnsupportedToken {
token,
}));
}
Ok(self._flash_fee(token, amount))
}

fn flash_loan(
&mut self,
receiver: Address,
token: Address,
value: U256,
data: AbiBytes,
) -> Result<bool, Self::Error> {
let max_loan = self.max_flash_loan(token);
if value > max_loan {
return Err(Error::ExceededMaxLoan(ERC3156ExceededMaxLoan {
max_loan,
}));
}

let fee = self.flash_fee(token, value)?;
self.erc20._mint(receiver, value)?;
let loan_reciver = IERC3156FlashBorrower::new(receiver);
0xNeshi marked this conversation as resolved.
Show resolved Hide resolved
if Address::has_code(&loan_reciver) {
return Err(Error::InvalidReceiver(ERC3156InvalidReceiver {
receiver,
}));
}
let call = Call::new();
let loan_return = loan_reciver.on_flash_loan(
call,
msg::sender(),
token,
value,
fee,
Bytes::from(data.0),
);
if loan_return.is_err() {
return Err(Error::InvalidReceiver(ERC3156InvalidReceiver {
receiver,
}));
}
if loan_return.ok() != Some(RETURN_VALUE) {
return Err(Error::InvalidReceiver(ERC3156InvalidReceiver {
receiver,
}));
}

let flash_fee_receiver = self._flash_fee_receiver();
self.erc20._spend_allowance(receiver, msg::sender(), value + fee)?;
if fee.is_zero() || flash_fee_receiver.is_zero() {
self.erc20._burn(receiver, value + fee)?;
} else {
self.erc20._burn(receiver, value)?;
self.erc20._transfer(receiver, flash_fee_receiver, fee)?;
}

Ok(true)
}
}

impl Erc20Flashmint {
/// Calculates the fee for a flash loan.
///
/// The fee is currently fixed at 0.
///
/// * `token` - The ERC-20 token that will be loaned.
/// * `value` - The amount of tokens that will be loaned.
pub fn _flash_fee(&self, token: Address, value: U256) -> U256 {
let _ = token;
let _ = value;
if self._flash_fee_amount.is_zero() {
return U256::MIN;
}
self._flash_fee_amount.get()
}

/// Returns the address of the receiver contract that will receive the flash
/// loan. The default implementation returns `Address::ZERO`.
pub fn _flash_fee_receiver(&self) -> Address {
if self._flash_fee_receiver_address.eq(&Address::ZERO) {
return self._flash_fee_receiver_address.get();
}
Address::ZERO
}
}

#[cfg(all(test, feature = "std"))]
mod tests {
use alloy_primitives::{address, uint, U256};
use stylus_sdk::msg;

use super::Erc20FlashMint;
use crate::token::erc20::{
extensions::flashmint::IERC3156FlashLender, Erc20, IErc20,
};

#[motsu::test]
fn max_flash_loan_token_match(contract: Erc20FlashMint) {
let token = address!("dce82b5f92c98f27f116f70491a487effdb6a2a9");
let max_flash_loan = contract.max_flash_loan(token);
assert_eq!(max_flash_loan, U256::MAX);
}

#[motsu::test]
fn max_flash_loan_token_mismatch(contract: Erc20FlashMint) {
let token = address!("dce82b5f92c98f27f116f70491a487effdb6a2a6");
let max_flash_loan = contract.max_flash_loan(token);
assert_eq!(max_flash_loan, U256::MIN);
}

#[motsu::test]
fn burns_errors_when_insufficient_balance(contract: Erc20FlashMint) {
let zero = U256::ZERO;
let one = uint!(1_U256);
let sender = msg::sender();

assert_eq!(zero, contract.erc20.balance_of(sender));

// let result = contract.burn(one);
// assert!(matches!(result, Err(Error::InsufficientBalance(_))));
}

// #[motsu::test]
// fn burn_from(contract: Erc20) {
// let alice = address!("A11CEacF9aa32246d767FCCD72e02d6bCbcC375d");
// let sender = msg::sender();

// // Alice approves `msg::sender`.
// let one = uint!(1_U256);
// contract._allowances.setter(alice).setter(sender).set(one);

// // Mint some tokens for Alice.
// let two = uint!(2_U256);
// contract._update(Address::ZERO, alice, two).unwrap();
// assert_eq!(two, contract.balance_of(alice));
// assert_eq!(two, contract.total_supply());

// contract.burn_from(alice, one).unwrap();

// assert_eq!(one, contract.balance_of(alice));
// assert_eq!(one, contract.total_supply());
// assert_eq!(U256::ZERO, contract.allowance(alice, sender));
// }

// #[motsu::test]
// fn burns_from_errors_when_insufficient_balance(contract: Erc20) {
// let alice = address!("A11CEacF9aa32246d767FCCD72e02d6bCbcC375d");

// // Alice approves `msg::sender`.
// let zero = U256::ZERO;
// let one = uint!(1_U256);

// contract._allowances.setter(alice).setter(msg::sender()).set(one);
// assert_eq!(zero, contract.balance_of(alice));

// let one = uint!(1_U256);

// let result = contract.burn_from(alice, one);
// assert!(matches!(result, Err(Error::InsufficientBalance(_))));
// }

// #[motsu::test]
// fn burns_from_errors_when_invalid_approver(contract: Erc20) {
// let one = uint!(1_U256);

// contract
// ._allowances
// .setter(Address::ZERO)
// .setter(msg::sender())
// .set(one);

// let result = contract.burn_from(Address::ZERO, one);
// assert!(matches!(result, Err(Error::InvalidApprover(_))));
// }

// #[motsu::test]
// fn burns_from_errors_when_insufficient_allowance(contract: Erc20) {
// let alice = address!("A11CEacF9aa32246d767FCCD72e02d6bCbcC375d");

// // Mint some tokens for Alice.
// let one = uint!(1_U256);
// contract._update(Address::ZERO, alice, one).unwrap();
// assert_eq!(one, contract.balance_of(alice));

// let result = contract.burn_from(alice, one);
// assert!(matches!(result, Err(Error::InsufficientAllowance(_))));
// }
}
2 changes: 2 additions & 0 deletions contracts/src/token/erc20/extensions/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
//! Common extensions to the ERC-20 standard.
pub mod burnable;
pub mod capped;
pub mod flashmint;
pub mod metadata;
pub mod permit;

pub use burnable::IErc20Burnable;
pub use capped::Capped;
pub use flashmint::{Erc20Flashmint, IERC3156FlashLender};
pub use metadata::{Erc20Metadata, IErc20Metadata};
pub use permit::Erc20Permit;
Loading