-
Notifications
You must be signed in to change notification settings - Fork 40
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
feat: Erc20Wrapper extension #498
base: main
Are you sure you want to change the base?
Changes from all commits
1944622
0cca59f
3bab6ac
038020b
7587d34
961353e
a347f69
9c66630
bd69a58
1bedc16
9d5419c
2d6a02f
398da73
3405bf2
dfc1e00
4274d2b
fd1de52
fb6a894
102611b
1f450b2
fae5882
f8d684a
d13ad47
ab401e2
4070411
4ac2b5c
104c45a
b6e1d6e
1b15a10
1114ccf
2a1cd43
c22a404
0124a8d
d680703
9781c4e
a99faa4
afc75a3
cd0259a
293b12f
2535fef
0b92aab
7a31409
bc8aa8f
c9a1711
6fd4866
5737ffb
3fb1c19
5f0dcae
bf7a556
452a405
358a235
b97a6fe
0fb0183
e97fea9
3802a5a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,255 @@ | ||
//! Extension of the ERC-20 token contract to support token wrapping. | ||
//! | ||
//! Users can deposit and withdraw "underlying tokens" and receive a matching | ||
//! number of "wrapped tokens". This is useful in conjunction with other | ||
//! modules. | ||
//! | ||
//! WARNING: Any mechanism in which the underlying token changes the {balanceOf} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Adjust the docs to the current implementation. We do not have |
||
//! of an account without an explicit transfer may desynchronize this contract's | ||
//! supply and its underlying balance. Please exercise caution when wrapping | ||
//! tokens that may undercollateralize the wrapper (i.e. wrapper's total supply | ||
//! is higher than its underlying balance). See {_recover} for recovering value | ||
//! accrued to the wrapper. | ||
|
||
use alloy_primitives::{Address, U256}; | ||
use alloy_sol_macro::sol; | ||
use stylus_sdk::{ | ||
call::Call, | ||
contract, msg, | ||
prelude::storage, | ||
storage::{StorageAddress, TopLevelStorage}, | ||
stylus_proc::SolidityError, | ||
}; | ||
|
||
use crate::token::erc20::{ | ||
self, | ||
utils::{safe_erc20, IErc20 as IErc20Solidity, ISafeErc20, SafeErc20}, | ||
Erc20, | ||
}; | ||
|
||
sol! { | ||
Ifechukwudaniel marked this conversation as resolved.
Show resolved
Hide resolved
|
||
/// Indicates that he address is not a valid ERC-20 token. | ||
/// | ||
/// * `address` - Address of the invalid underling ERC-20 token. | ||
#[derive(Debug)] | ||
#[allow(missing_docs)] | ||
error ERC20InvalidUnderlying(address token); | ||
|
||
/// Indicates that the address is not an Invalid Sender address. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe there is sth wrong with this docs... |
||
/// | ||
/// * `sender` - Address is an invalid sender. | ||
#[derive(Debug)] | ||
#[allow(missing_docs)] | ||
error ERC20InvalidSender(address sender); | ||
|
||
/// Indicates that The address is not a valid Invalid Asset. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe there is sth wrong with this docs... |
||
/// | ||
/// * `asset` - Address of the invalid address of the token. | ||
#[derive(Debug)] | ||
#[allow(missing_docs)] | ||
error InvalidAsset(address asset); | ||
|
||
/// Indicates that the address is not an invalid receiver addresss. | ||
/// | ||
/// * `receiver` - Address of the invalid receiver. | ||
#[derive(Debug)] | ||
#[allow(missing_docs)] | ||
error ERC20InvalidReceiver(address receiver); | ||
|
||
} | ||
|
||
/// An [`Erc20Wrapper`] error. | ||
#[derive(SolidityError, Debug)] | ||
pub enum Error { | ||
/// Error type from [`SafeErc20`] contract [`safe_erc20::Error`]. | ||
SafeErc20(safe_erc20::Error), | ||
|
||
/// The Sender Address is not valid. | ||
InvalidSender(ERC20InvalidSender), | ||
|
||
/// The Reciver Address is not valid. | ||
InvalidReceiver(ERC20InvalidReceiver), | ||
|
||
/// The underlying token couldn't be wrapped. | ||
InvalidUnderlying(ERC20InvalidUnderlying), | ||
|
||
/// The address is not a valid ERC-20 token. | ||
InvalidAsset(InvalidAsset), | ||
|
||
/// Error type from [`Erc20`] contract [`erc20::Error`]. | ||
Erc20(erc20::Error), | ||
} | ||
/// State of an [`Erc20Wrapper`] token. | ||
#[storage] | ||
pub struct Erc20Wrapper { | ||
/// Token Address of the underline token | ||
#[allow(clippy::used_underscore_binding)] | ||
pub(crate) underlying_address: StorageAddress, | ||
|
||
/// [`SafeErc20`] contract | ||
safe_erc20: SafeErc20, | ||
} | ||
|
||
/// ERC-20 Wrapper Standard Interface | ||
pub trait IErc20Wrapper { | ||
/// The error type associated to the` trait implementation. | ||
Check warning on line 95 in contracts/src/token/erc20/extensions/wrapper.rs
|
||
type Error: Into<alloc::vec::Vec<u8>>; | ||
|
||
/// Returns the address of the underlying token that is been wrapped. | ||
fn underlying(&self) -> Address; | ||
|
||
/// Allow a user to deposit underlying tokens and mint the corresponding | ||
/// number of wrapped token | ||
/// | ||
/// Arguments: | ||
/// | ||
/// * `&mut self` - Write access to the contract's state. | ||
/// * `account` - The account to deposit tokens to. | ||
/// * `value` - The amount of tokens to deposit. | ||
/// | ||
/// # Errors | ||
/// | ||
/// * If the sender address is `contract:address()` or invalid, | ||
/// [`Error::InvalidSender`] is returned. | ||
/// * If the receiver address is `contract:address()` or invalid, | ||
/// [`Error::InvalidReceiver`] is returned. | ||
fn deposit_for( | ||
&mut self, | ||
account: Address, | ||
value: U256, | ||
erc20: &mut Erc20, | ||
) -> Result<bool, Self::Error>; | ||
|
||
/// Allow a user to burn a number of wrapped tokens and withdraw the | ||
/// corresponding number of underlying tokens. | ||
/// | ||
/// Arguments: | ||
/// | ||
/// * `&mut self` - Write access to the contract's state. | ||
/// * `account` - The account to withdraw tokens to. | ||
/// * `value` - The amount of tokens to withdraw. | ||
/// * `erc20` - A mutable reference to the Erc20 contract. | ||
/// | ||
/// # Errors | ||
/// | ||
/// * If the receiver address is `contract:address()` or invalid, | ||
/// [`Error::InvalidReceiver`] is returned. | ||
fn withdraw_to( | ||
Ifechukwudaniel marked this conversation as resolved.
Show resolved
Hide resolved
|
||
&mut self, | ||
account: Address, | ||
value: U256, | ||
erc20: &mut Erc20, | ||
) -> Result<bool, Self::Error>; | ||
} | ||
|
||
/// NOTE: Implementation of [`TopLevelStorage`] to be able use `&mut self` when | ||
/// calling other contracts and not `&mut (impl TopLevelStorage + | ||
/// BorrowMut<Self>)`. Should be fixed in the future by the Stylus team. | ||
unsafe impl TopLevelStorage for Erc20Wrapper {} | ||
|
||
impl IErc20Wrapper for Erc20Wrapper { | ||
type Error = Error; | ||
|
||
fn underlying(&self) -> Address { | ||
self.underlying_address.get() | ||
} | ||
|
||
fn deposit_for( | ||
&mut self, | ||
account: Address, | ||
value: U256, | ||
erc20: &mut Erc20, | ||
) -> Result<bool, Error> { | ||
let sender = msg::sender(); | ||
|
||
if sender == contract::address() { | ||
return Err(Error::InvalidReceiver(ERC20InvalidReceiver { | ||
receiver: account, | ||
})); | ||
} | ||
|
||
if account == contract::address() { | ||
return Err(Error::InvalidSender(ERC20InvalidSender { | ||
sender: contract::address(), | ||
})); | ||
} | ||
|
||
self.safe_erc20.safe_transfer_from( | ||
self.underlying(), | ||
sender, | ||
contract::address(), | ||
value, | ||
)?; | ||
erc20._mint(account, value)?; | ||
|
||
Ok(true) | ||
} | ||
|
||
fn withdraw_to( | ||
&mut self, | ||
account: Address, | ||
value: U256, | ||
erc20: &mut Erc20, | ||
) -> Result<bool, Error> { | ||
if account == contract::address() { | ||
return Err(Error::InvalidReceiver(ERC20InvalidReceiver { | ||
receiver: account, | ||
})); | ||
} | ||
erc20._burn(account, value)?; | ||
self.safe_erc20.safe_transfer(self.underlying(), account, value)?; | ||
Ok(true) | ||
} | ||
} | ||
|
||
impl Erc20Wrapper { | ||
/// Mints wrapped tokens to cover any underlying tokens that might have been | ||
/// mistakenly transferred or acquired through rebasing mechanisms. | ||
/// | ||
/// This is an internal function that can be exposed with access control if | ||
/// required. | ||
/// | ||
/// Arguments: | ||
/// | ||
/// * `&mut self` - Write access to the contract's state. | ||
/// * `account` - The account to mint tokens to. | ||
/// * `erc20` - A mutable reference to the Erc20 contract. | ||
/// | ||
/// # Errors | ||
/// | ||
/// If the external call for balance of fails , then the error | ||
/// [`Error::InvalidAsset`] is returned. | ||
pub fn _recover( | ||
Ifechukwudaniel marked this conversation as resolved.
Show resolved
Hide resolved
|
||
&mut self, | ||
account: Address, | ||
erc20: &mut Erc20, | ||
) -> Result<U256, Error> { | ||
let underline_token = IErc20Solidity::new(self.underlying()); | ||
let value = underline_token | ||
.balance_of(Call::new_in(self), contract::address()) | ||
.map_err(|_| InvalidAsset { asset: contract::address() })?; | ||
erc20._mint(account, value)?; | ||
Ok(U256::from(value)) | ||
} | ||
} | ||
|
||
// TODO: Add missing tests once `motsu` supports calling external contracts. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should have motsu version that support calling external contracts tomorrow :) |
||
#[cfg(all(test, feature = "std"))] | ||
mod tests { | ||
use alloy_primitives::address; | ||
use stylus_sdk::prelude::storage; | ||
|
||
use super::{Erc20Wrapper, IErc20Wrapper}; | ||
|
||
#[storage] | ||
struct Erc20WrapperTestExample { | ||
wrapper: Erc20Wrapper, | ||
} | ||
|
||
#[motsu::test] | ||
Check failure on line 249 in contracts/src/token/erc20/extensions/wrapper.rs
|
||
fn underlying_works(contract: Erc20WrapperTestExample) { | ||
let asset = address!("DeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF"); | ||
contract.wrapper.underlying_address.set(asset); | ||
assert_eq!(contract.wrapper.underlying(), asset); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
= ERC-20 Wrapper | ||
|
||
Extension of the ERC-20 token contract to support token wrapping. | ||
|
||
Users can deposit and withdraw "underlying tokens" and receive a matching number of "wrapped tokens". | ||
This is useful in conjunction with other modules. | ||
|
||
|
||
[[usage]] | ||
== Usage | ||
|
||
In order to make your ERC20 `wrapped token`: | ||
|
||
[source,rust] | ||
---- | ||
use alloy_primitives::{Address, U256}; | ||
use openzeppelin_stylus::token::erc20::{ | ||
extensions::{Erc20Metadata, Erc20Wrapper, IERC20Wrapper}, | ||
Erc20, | ||
}; | ||
use stylus_sdk::prelude::{entrypoint, public, storage}; | ||
|
||
#[entrypoint] | ||
#[storage] | ||
struct Erc20WrapperExample { | ||
#[borrow] | ||
pub erc20: Erc20, | ||
#[borrow] | ||
pub metadata: Erc20Metadata, | ||
#[borrow] | ||
pub wrapper: Erc20Wrapper, | ||
} | ||
|
||
#[public] | ||
#[inherit(Erc20, Erc20Metadata)] | ||
impl Erc20WrapperExample { | ||
fn underlying(&self) -> Address { | ||
self.wrapper.underlying() | ||
} | ||
|
||
fn deposit_to( | ||
&mut self, | ||
account: Address, | ||
value: U256, | ||
) -> Result<bool, Vec<u8>> { | ||
Ok(self.wrapper.deposit_to(account, value, &mut self.erc20)?) | ||
} | ||
|
||
fn withdraw_to( | ||
&mut self, | ||
account: Address, | ||
value: U256, | ||
) -> Result<bool, Vec<u8>> { | ||
Ok(self.wrapper.withdraw_to(account, value, &mut self.erc20)?) | ||
} | ||
} | ||
---- |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please apply this #511 Rust docs layout in this contract.