From 1944622f62870ebc790599a8e78b7b727988185e Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Thu, 16 Jan 2025 00:41:21 +0000 Subject: [PATCH 01/46] chore: wrapper approch --- contracts/src/token/erc20/extensions/mod.rs | 2 + .../src/token/erc20/extensions/wrapper.rs | 150 ++++++++++++++++++ 2 files changed, 152 insertions(+) create mode 100644 contracts/src/token/erc20/extensions/wrapper.rs diff --git a/contracts/src/token/erc20/extensions/mod.rs b/contracts/src/token/erc20/extensions/mod.rs index 549e29894..cac5480a5 100644 --- a/contracts/src/token/erc20/extensions/mod.rs +++ b/contracts/src/token/erc20/extensions/mod.rs @@ -4,9 +4,11 @@ pub mod capped; pub mod flash_mint; pub mod metadata; pub mod permit; +pub mod wrapper; pub use burnable::IErc20Burnable; pub use capped::Capped; pub use flash_mint::{Erc20FlashMint, IErc3156FlashLender}; pub use metadata::{Erc20Metadata, IErc20Metadata}; pub use permit::Erc20Permit; +pub use wrapper::{IERC20Wrapper,Erc20Wrapper}; diff --git a/contracts/src/token/erc20/extensions/wrapper.rs b/contracts/src/token/erc20/extensions/wrapper.rs new file mode 100644 index 000000000..868c9838d --- /dev/null +++ b/contracts/src/token/erc20/extensions/wrapper.rs @@ -0,0 +1,150 @@ +//! 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. For example, combining this wrapping mechanism with {ERC20Votes} will allow the +//! wrapping of an existing "basic" ERC-20 into a governance token. +//! +//! WARNING: Any mechanism in which the underlying token changes the {balanceOf} 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::{ + contract, evm, msg, + prelude::storage, + storage::{StorageAddress,TopLevelStorage}, + stylus_proc::SolidityError, +}; + +use crate::token::erc20::{ + self, + utils::{ + safe_erc20::{self, ISafeErc20}, + SafeErc20, + }, + extensions::Erc20Metadata, + Erc20 +}; + +use super::IErc20Metadata; + + +sol! { + #[derive(Debug)] + #[allow(missing_docs)] + error ERC20InvalidUnderlying(address token); + +} + +/// An [`Erc20Wrapper`] error. +#[derive(SolidityError, Debug)] +pub enum Error { + /// Error type from [`SafeErc20`] contract [`safe_erc20::Error`]. + SafeErc20(safe_erc20::Error), + /// Indicates an error where a redemption operation failed because the + /// supplied `shares` exceeded the maximum allowed for the `owner`. + InvalidUnderlying(ERC20InvalidUnderlying), + /// Error type from [`Erc20`] contract [`erc20::Error`]. + Erc20(erc20::Error), +} +/// State of an [`Erc4626`] token. +#[storage] +pub struct Erc20Wrapper { + /// Token Address of the vault + #[allow(clippy::used_underscore_binding)] + pub _underlying: StorageAddress, + +} + +/// ERC-4626 Tokenized Vault Standard Interface +pub trait IERC20Wrapper { + /// The error type associated to this ERC-4626 trait implementation. + type Error: Into>; + + /// Allow a user to deposit underlying tokens and mint the corresponding number of wrapped token + /// + /// # Examples + /// + /// ```rust,ignore + /// fn decimals(&self) -> Result { + /// self.token.decimals(account, &self.erc20) + /// } + /// `` + fn decimals(&self,erc20: &mut Erc20Metadata) -> u8; + + /// Returns the address of the underlying token that is bben wrapped. + fn underlying(&self) -> Address; + + /// Allow a user to deposit underlying tokens and mint the corresponding number of wrapped token + /// + /// # Examples + /// + /// ```rust,ignore + /// fn deposit_to(&self,account:Address, value:U256) -> Result { + /// self.token.deposit_to(account,value, &self.erc20, &self.safe_erc20) + /// } + /// ``` + fn deposit_to(&self,account:Address, value:U256, erc20: &mut Erc20, safe_erc20: &mut SafeErc20) -> Result; + + /// Allow a user to burn a number of wrapped tokens and withdraw the corresponding number of underlying tokens. + /// + /// # Examples + /// + /// ```rust,ignore + /// fn withdraw_to(&self,account:Address, value:U256) -> Result { + /// self.token.deposit_to(account,value, &self.erc20, &self.safe_erc20) + /// } + /// ``` + fn withdraw_to(&self,account:Address, value:U256, erc20: &mut Erc20, safe_erc20: &mut SafeErc20) -> Result; +} + +/// NOTE: Implementation of [`TopLevelStorage`] to be able use `&mut self` when +/// calling other contracts and not `&mut (impl TopLevelStorage + +/// BorrowMut)`. 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.get() + } + + fn decimals(&self,erc20_metadata: &mut Erc20Metadata) -> u8 { + erc20_metadata.decimals() + } + + fn deposit_to(&self,account:Address, value:U256, erc20: &mut Erc20, safe_erc20: &mut SafeErc20) -> Result { + let underlined_token = self._underlying.get(); + let sender = msg::sender(); + if account == contract::address() { + return Err(Error::InvalidUnderlying(ERC20InvalidUnderlying { token: contract::address() })); + } + + if sender == contract::address() { + return Err(Error::InvalidUnderlying(ERC20InvalidUnderlying { token: account })); + } + safe_erc20.safe_transfer_from(underlined_token, sender, contract::address(), value)?; + erc20._mint(account, value)?; + Ok(true) + } + + fn withdraw_to(&self,account:Address, value:U256, erc20: &mut Erc20, safe_erc20: &mut SafeErc20) -> Result { + let underlined_token = self._underlying.get(); + if account == contract::address() { + return Err(Error::InvalidUnderlying(ERC20InvalidUnderlying { token: contract::address() })); + } + erc20._burn(account, value)?; + safe_erc20.safe_transfer(underlined_token, account, value)?; + Ok(true) + } + +} + +impl Erc20Wrapper { + fn _recover(&self) -> Address { + return contract::address(); + } +} \ No newline at end of file From 0cca59f7ee972ad85710d2f31d35b1d049f78e6e Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Thu, 16 Jan 2025 00:42:18 +0000 Subject: [PATCH 02/46] chore: code fmt --- contracts/src/token/erc20/extensions/mod.rs | 2 +- .../src/token/erc20/extensions/wrapper.rs | 108 ++++++++++++------ 2 files changed, 74 insertions(+), 36 deletions(-) diff --git a/contracts/src/token/erc20/extensions/mod.rs b/contracts/src/token/erc20/extensions/mod.rs index cac5480a5..4b31b6202 100644 --- a/contracts/src/token/erc20/extensions/mod.rs +++ b/contracts/src/token/erc20/extensions/mod.rs @@ -11,4 +11,4 @@ pub use capped::Capped; pub use flash_mint::{Erc20FlashMint, IErc3156FlashLender}; pub use metadata::{Erc20Metadata, IErc20Metadata}; pub use permit::Erc20Permit; -pub use wrapper::{IERC20Wrapper,Erc20Wrapper}; +pub use wrapper::{Erc20Wrapper, IERC20Wrapper}; diff --git a/contracts/src/token/erc20/extensions/wrapper.rs b/contracts/src/token/erc20/extensions/wrapper.rs index 868c9838d..a42f2fbf1 100644 --- a/contracts/src/token/erc20/extensions/wrapper.rs +++ b/contracts/src/token/erc20/extensions/wrapper.rs @@ -1,36 +1,38 @@ //! 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. For example, combining this wrapping mechanism with {ERC20Votes} will allow the -//! wrapping of an existing "basic" ERC-20 into a governance token. +//! Users can deposit and withdraw "underlying tokens" and receive a matching +//! number of "wrapped tokens". This is useful in conjunction with other +//! modules. For example, combining this wrapping mechanism with {ERC20Votes} +//! will allow the wrapping of an existing "basic" ERC-20 into a governance +//! token. //! -//! WARNING: Any mechanism in which the underlying token changes the {balanceOf} 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. +//! WARNING: Any mechanism in which the underlying token changes the {balanceOf} +//! 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::{ contract, evm, msg, prelude::storage, - storage::{StorageAddress,TopLevelStorage}, + storage::{StorageAddress, TopLevelStorage}, stylus_proc::SolidityError, }; +use super::IErc20Metadata; use crate::token::erc20::{ self, + extensions::Erc20Metadata, utils::{ safe_erc20::{self, ISafeErc20}, SafeErc20, }, - extensions::Erc20Metadata, - Erc20 + Erc20, }; -use super::IErc20Metadata; - - sol! { #[derive(Debug)] #[allow(missing_docs)] @@ -54,8 +56,7 @@ pub enum Error { pub struct Erc20Wrapper { /// Token Address of the vault #[allow(clippy::used_underscore_binding)] - pub _underlying: StorageAddress, - + pub _underlying: StorageAddress, } /// ERC-4626 Tokenized Vault Standard Interface @@ -63,7 +64,8 @@ pub trait IERC20Wrapper { /// The error type associated to this ERC-4626 trait implementation. type Error: Into>; - /// Allow a user to deposit underlying tokens and mint the corresponding number of wrapped token + /// Allow a user to deposit underlying tokens and mint the corresponding + /// number of wrapped token /// /// # Examples /// @@ -72,12 +74,13 @@ pub trait IERC20Wrapper { /// self.token.decimals(account, &self.erc20) /// } /// `` - fn decimals(&self,erc20: &mut Erc20Metadata) -> u8; + fn decimals(&self, erc20: &mut Erc20Metadata) -> u8; /// Returns the address of the underlying token that is bben wrapped. fn underlying(&self) -> Address; - /// Allow a user to deposit underlying tokens and mint the corresponding number of wrapped token + /// Allow a user to deposit underlying tokens and mint the corresponding + /// number of wrapped token /// /// # Examples /// @@ -86,9 +89,16 @@ pub trait IERC20Wrapper { /// self.token.deposit_to(account,value, &self.erc20, &self.safe_erc20) /// } /// ``` - fn deposit_to(&self,account:Address, value:U256, erc20: &mut Erc20, safe_erc20: &mut SafeErc20) -> Result; - - /// Allow a user to burn a number of wrapped tokens and withdraw the corresponding number of underlying tokens. + fn deposit_to( + &self, + account: Address, + value: U256, + erc20: &mut Erc20, + safe_erc20: &mut SafeErc20, + ) -> Result; + + /// Allow a user to burn a number of wrapped tokens and withdraw the + /// corresponding number of underlying tokens. /// /// # Examples /// @@ -97,7 +107,13 @@ pub trait IERC20Wrapper { /// self.token.deposit_to(account,value, &self.erc20, &self.safe_erc20) /// } /// ``` - fn withdraw_to(&self,account:Address, value:U256, erc20: &mut Erc20, safe_erc20: &mut SafeErc20) -> Result; + fn withdraw_to( + &self, + account: Address, + value: U256, + erc20: &mut Erc20, + safe_erc20: &mut SafeErc20, + ) -> Result; } /// NOTE: Implementation of [`TopLevelStorage`] to be able use `&mut self` when @@ -112,39 +128,61 @@ impl IERC20Wrapper for Erc20Wrapper { self._underlying.get() } - fn decimals(&self,erc20_metadata: &mut Erc20Metadata) -> u8 { - erc20_metadata.decimals() + fn decimals(&self, erc20_metadata: &mut Erc20Metadata) -> u8 { + erc20_metadata.decimals() } - fn deposit_to(&self,account:Address, value:U256, erc20: &mut Erc20, safe_erc20: &mut SafeErc20) -> Result { + fn deposit_to( + &self, + account: Address, + value: U256, + erc20: &mut Erc20, + safe_erc20: &mut SafeErc20, + ) -> Result { let underlined_token = self._underlying.get(); let sender = msg::sender(); - if account == contract::address() { - return Err(Error::InvalidUnderlying(ERC20InvalidUnderlying { token: contract::address() })); + if account == contract::address() { + return Err(Error::InvalidUnderlying(ERC20InvalidUnderlying { + token: contract::address(), + })); } if sender == contract::address() { - return Err(Error::InvalidUnderlying(ERC20InvalidUnderlying { token: account })); + return Err(Error::InvalidUnderlying(ERC20InvalidUnderlying { + token: account, + })); } - safe_erc20.safe_transfer_from(underlined_token, sender, contract::address(), value)?; + safe_erc20.safe_transfer_from( + underlined_token, + sender, + contract::address(), + value, + )?; erc20._mint(account, value)?; Ok(true) } - fn withdraw_to(&self,account:Address, value:U256, erc20: &mut Erc20, safe_erc20: &mut SafeErc20) -> Result { + fn withdraw_to( + &self, + account: Address, + value: U256, + erc20: &mut Erc20, + safe_erc20: &mut SafeErc20, + ) -> Result { let underlined_token = self._underlying.get(); - if account == contract::address() { - return Err(Error::InvalidUnderlying(ERC20InvalidUnderlying { token: contract::address() })); + if account == contract::address() { + return Err(Error::InvalidUnderlying(ERC20InvalidUnderlying { + token: contract::address(), + })); } erc20._burn(account, value)?; safe_erc20.safe_transfer(underlined_token, account, value)?; Ok(true) } - } impl Erc20Wrapper { fn _recover(&self) -> Address { - return contract::address(); + return contract::address(); } -} \ No newline at end of file +} From 3bab6ac101e3b29611effbd1e5858ba1485b471a Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Thu, 16 Jan 2025 00:44:52 +0000 Subject: [PATCH 03/46] chore: comments --- contracts/src/token/erc20/extensions/wrapper.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/src/token/erc20/extensions/wrapper.rs b/contracts/src/token/erc20/extensions/wrapper.rs index a42f2fbf1..c2b44f78a 100644 --- a/contracts/src/token/erc20/extensions/wrapper.rs +++ b/contracts/src/token/erc20/extensions/wrapper.rs @@ -16,7 +16,7 @@ use alloy_primitives::{Address, U256}; use alloy_sol_macro::sol; use stylus_sdk::{ - contract, evm, msg, + contract,msg, prelude::storage, storage::{StorageAddress, TopLevelStorage}, stylus_proc::SolidityError, @@ -45,8 +45,8 @@ sol! { pub enum Error { /// Error type from [`SafeErc20`] contract [`safe_erc20::Error`]. SafeErc20(safe_erc20::Error), - /// Indicates an error where a redemption operation failed because the - /// supplied `shares` exceeded the maximum allowed for the `owner`. + + /// The underlying token couldn't be wrapped. InvalidUnderlying(ERC20InvalidUnderlying), /// Error type from [`Erc20`] contract [`erc20::Error`]. Erc20(erc20::Error), @@ -185,4 +185,4 @@ impl Erc20Wrapper { fn _recover(&self) -> Address { return contract::address(); } -} +} \ No newline at end of file From 038020bbb109254804b1f0ca83fe097a7c3c22eb Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Thu, 16 Jan 2025 04:32:18 +0000 Subject: [PATCH 04/46] chore: erc20 wrapper token --- examples/erc20-wrapper/Cargo.toml | 24 ++++ examples/erc20-wrapper/src/constructor.sol | 15 +++ examples/erc20-wrapper/src/lib.rs | 120 +++++++++++++++++++ examples/erc20-wrapper/tests/abi/mod.rs | 36 ++++++ examples/erc20-wrapper/tests/erc20wrapper.rs | 63 ++++++++++ 5 files changed, 258 insertions(+) create mode 100644 examples/erc20-wrapper/Cargo.toml create mode 100644 examples/erc20-wrapper/src/constructor.sol create mode 100644 examples/erc20-wrapper/src/lib.rs create mode 100644 examples/erc20-wrapper/tests/abi/mod.rs create mode 100644 examples/erc20-wrapper/tests/erc20wrapper.rs diff --git a/examples/erc20-wrapper/Cargo.toml b/examples/erc20-wrapper/Cargo.toml new file mode 100644 index 000000000..19cb2feca --- /dev/null +++ b/examples/erc20-wrapper/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "erc20-wrapper" +edition.workspace = true +license.workspace = true +repository.workspace = true +publish = false +version.workspace = true + +[dependencies] +openzeppelin-stylus.workspace = true +alloy-primitives.workspace = true +stylus-sdk.workspace = true + +[dev-dependencies] +alloy.workspace = true +eyre.workspace = true +tokio.workspace = true +e2e.workspace = true + +[features] +e2e = [] + +[lib] +crate-type = ["lib", "cdylib"] diff --git a/examples/erc20-wrapper/src/constructor.sol b/examples/erc20-wrapper/src/constructor.sol new file mode 100644 index 000000000..ed34902e7 --- /dev/null +++ b/examples/erc20-wrapper/src/constructor.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.21; + +contract Erc20WrapperExample { + mapping(address account => uint256) private _balances; + mapping(address account => mapping(address spender => uint256)) + private _allowances; + uint256 private _totalSupply; + string private _name; + string private _symbol; + constructor(string memory name_, string memory symbol_) { + _name = name_; + _symbol = symbol_; + } +} diff --git a/examples/erc20-wrapper/src/lib.rs b/examples/erc20-wrapper/src/lib.rs new file mode 100644 index 000000000..bc75a0b93 --- /dev/null +++ b/examples/erc20-wrapper/src/lib.rs @@ -0,0 +1,120 @@ +#![cfg_attr(not(test), no_main)] +extern crate alloc; + +use alloc::vec::Vec; + +use alloy_primitives::{Address, FixedBytes, U256}; +use openzeppelin_stylus::{ + token::erc20::{ + extensions::{Erc20Wrapper,IERC20Wrapper}, + Erc20, IErc20, + }, + utils::{introspection::erc165::IErc165, Pausable}, +}; +use stylus_sdk::prelude::{entrypoint, public, storage}; + +const DECIMALS: u8 = 10; + +#[entrypoint] +#[storage] +struct Erc20WrapperExample { + #[borrow] + pub erc20: Erc20, + #[borrow] + pub wrapper: Erc20Wrapper, +} + +#[public] +#[inherit(Erc20)] +impl Erc20WrapperExample { + // Overrides the default [`Metadata::decimals`], and sets it to `10`. + // + // If you don't provide this method in the `entrypoint` contract, it will + // default to `18`. + // pub fn decimals(&self) -> u8 { + // DECIMALS + // } + + // pub fn burn(&mut self, value: U256) -> Result<(), Vec> { + // self.pausable.when_not_paused()?; + // self.erc20.burn(value).map_err(|e| e.into()) + // } + + // pub fn burn_from( + // &mut self, + // account: Address, + // value: U256, + // ) -> Result<(), Vec> { + // self.pausable.when_not_paused()?; + // self.erc20.burn_from(account, value).map_err(|e| e.into()) + // } + + // // Add token minting feature. + // // + // // Make sure to handle `Capped` properly. You should not call + // // [`Erc20::_update`] to mint tokens -- it will the break `Capped` + // // mechanism. + // pub fn mint( + // &mut self, + // account: Address, + // value: U256, + // ) -> Result<(), Vec> { + // self.pausable.when_not_paused()?; + // let max_supply = self.capped.cap(); + + // // Overflow check required. + // let supply = self + // .erc20 + // .total_supply() + // .checked_add(value) + // .expect("new supply should not exceed `U256::MAX`"); + + // if supply > max_supply { + // return Err(capped::Error::ExceededCap( + // capped::ERC20ExceededCap { + // increased_supply: supply, + // cap: max_supply, + // }, + // ))?; + // } + + // self.erc20._mint(account, value)?; + // Ok(()) + // } + + // pub fn transfer( + // &mut self, + // to: Address, + // value: U256, + // ) -> Result> { + // self.pausable.when_not_paused()?; + // self.erc20.transfer(to, value).map_err(|e| e.into()) + // } + + // pub fn transfer_from( + // &mut self, + // from: Address, + // to: Address, + // value: U256, + // ) -> Result> { + // self.pausable.when_not_paused()?; + // self.erc20.transfer_from(from, to, value).map_err(|e| e.into()) + // } + + // fn supports_interface(interface_id: FixedBytes<4>) -> bool { + // Erc20::supports_interface(interface_id) + // || Erc20Metadata::supports_interface(interface_id) + // } + + // /// WARNING: These functions are intended for **testing purposes** only. In + // /// **production**, ensure strict access control to prevent unauthorized + // /// pausing or unpausing, which can disrupt contract functionality. Remove + // /// or secure these functions before deployment. + // pub fn pause(&mut self) -> Result<(), Vec> { + // self.pausable.pause().map_err(|e| e.into()) + // } + + // pub fn unpause(&mut self) -> Result<(), Vec> { + // self.pausable.unpause().map_err(|e| e.into()) + // } +} diff --git a/examples/erc20-wrapper/tests/abi/mod.rs b/examples/erc20-wrapper/tests/abi/mod.rs new file mode 100644 index 000000000..0676af51d --- /dev/null +++ b/examples/erc20-wrapper/tests/abi/mod.rs @@ -0,0 +1,36 @@ +#![allow(dead_code)] +#![allow(clippy::too_many_arguments)] +use alloy::sol; + +sol!( + #[sol(rpc)] + contract Erc20Wrapper { + function name() external view returns (string name); + function symbol() external view returns (string symbol); + function totalSupply() external view returns (uint256 totalSupply); + function balanceOf(address account) external view returns (uint256 balance); + function transfer(address recipient, uint256 amount) external returns (bool); + function allowance(address owner, address spender) external view returns (uint256 allowance); + function approve(address spender, uint256 amount) external returns (bool); + function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); + + function decimals() external view returns (uint8 decimals); + function underlying() external view returns (address underlying); + function depositFor(address account, uint256 value) external returns (bool); + function withdrawTo(address account, uint256 value) external returns (bool); + + error ERC20InvalidUnderlying(address token); + + error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed); + error ERC20InvalidSender(address sender); + error ERC20InvalidReceiver(address receiver); + error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed); + error ERC20InvalidSpender(address spender); + + #[derive(Debug, PartialEq)] + event Transfer(address indexed from, address indexed to, uint256 value); + #[derive(Debug, PartialEq)] + event Approval(address indexed owner, address indexed spender, uint256 value); + + } +); diff --git a/examples/erc20-wrapper/tests/erc20wrapper.rs b/examples/erc20-wrapper/tests/erc20wrapper.rs new file mode 100644 index 000000000..3549b8e4d --- /dev/null +++ b/examples/erc20-wrapper/tests/erc20wrapper.rs @@ -0,0 +1,63 @@ +#![cfg(feature = "e2e")] + +use abi::Erc20Wrapper; +use alloy::{ + primitives::{uint, Address, U256}, + sol, +}; +use e2e::{ + receipt, send, watch, Account, EventExt, Panic, PanicCode, ReceiptExt, + Revert, +}; +use eyre::Result; + +use crate::Erc20WrapperExample::constructorCall; + +mod abi; + +sol!("src/constructor.sol"); + +const TOKEN_NAME: &str = "Test Token"; +const TOKEN_SYMBOL: &str = "TTK"; + +const WRAPPED_TOKEN_NAME: &str = "WRAPPED Test Token"; +const WRAPPED_TOKEN_SYMBOL: &str = "WTTK"; + + +impl Default for constructorCall { + fn default() -> Self { + ctr() + } +} + +fn ctr() -> constructorCall { + Erc20WrapperExample::constructorCall { + name_: WRAPPED_TOKEN_NAME.to_owned(), + symbol_: WRAPPED_TOKEN_SYMBOL.to_owned() + } +} + +// ============================================================================ +// Integration Tests: ERC-20 Token + Metadata Extension +// ============================================================================ + +#[e2e::test] +async fn constructs(alice: Account) -> Result<()> { + let contract_addr = alice + .as_deployer() + .with_default_constructor::() + .deploy() + .await? + .address()?; + let contract = Erc20Wrapper::new(contract_addr, &alice.wallet); + + let name = contract.name().call().await?.name; + let symbol = contract.symbol().call().await?.symbol; + let decimals = contract.decimals().call().await?.decimals; + + assert_eq!(name, WRAPPED_TOKEN_NAME.to_owned()); + assert_eq!(symbol, WRAPPED_TOKEN_SYMBOL.to_owned()); + assert_eq!(decimals, 10); + + Ok(()) +} From 7587d3486df894e7d7ef86b8b5f6f313a7318b7a Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Thu, 16 Jan 2025 04:35:36 +0000 Subject: [PATCH 05/46] chore: code fmt --- Cargo.lock | 13 +++++++++++++ Cargo.toml | 2 ++ contracts/src/token/erc20/extensions/wrapper.rs | 6 +++--- examples/erc20-wrapper/src/lib.rs | 12 ++++++------ examples/erc20-wrapper/tests/erc20wrapper.rs | 3 +-- 5 files changed, 25 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fa26f1e73..6286b8a90 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1741,6 +1741,19 @@ dependencies = [ "tokio", ] +[[package]] +name = "erc20-wrapper" +version = "0.2.0-alpha.2" +dependencies = [ + "alloy", + "alloy-primitives", + "e2e", + "eyre", + "openzeppelin-stylus", + "stylus-sdk", + "tokio", +] + [[package]] name = "erc721-consecutive-example" version = "0.2.0-alpha.2" diff --git a/Cargo.toml b/Cargo.toml index 610d339e7..1918bc8cc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ members = [ "examples/erc20", "examples/erc20-permit", "examples/erc20-flash-mint", + "examples/erc20-wrapper", "examples/erc721", "examples/erc721-consecutive", "examples/erc721-metadata", @@ -34,6 +35,7 @@ default-members = [ "examples/erc20", "examples/erc20-permit", "examples/erc20-flash-mint", + "examples/erc20-wrapper", "examples/erc721", "examples/erc721-consecutive", "examples/erc721-metadata", diff --git a/contracts/src/token/erc20/extensions/wrapper.rs b/contracts/src/token/erc20/extensions/wrapper.rs index c2b44f78a..a383eebeb 100644 --- a/contracts/src/token/erc20/extensions/wrapper.rs +++ b/contracts/src/token/erc20/extensions/wrapper.rs @@ -16,7 +16,7 @@ use alloy_primitives::{Address, U256}; use alloy_sol_macro::sol; use stylus_sdk::{ - contract,msg, + contract, msg, prelude::storage, storage::{StorageAddress, TopLevelStorage}, stylus_proc::SolidityError, @@ -45,7 +45,7 @@ sol! { pub enum Error { /// Error type from [`SafeErc20`] contract [`safe_erc20::Error`]. SafeErc20(safe_erc20::Error), - + /// The underlying token couldn't be wrapped. InvalidUnderlying(ERC20InvalidUnderlying), /// Error type from [`Erc20`] contract [`erc20::Error`]. @@ -185,4 +185,4 @@ impl Erc20Wrapper { fn _recover(&self) -> Address { return contract::address(); } -} \ No newline at end of file +} diff --git a/examples/erc20-wrapper/src/lib.rs b/examples/erc20-wrapper/src/lib.rs index bc75a0b93..cce6479b7 100644 --- a/examples/erc20-wrapper/src/lib.rs +++ b/examples/erc20-wrapper/src/lib.rs @@ -6,7 +6,7 @@ use alloc::vec::Vec; use alloy_primitives::{Address, FixedBytes, U256}; use openzeppelin_stylus::{ token::erc20::{ - extensions::{Erc20Wrapper,IERC20Wrapper}, + extensions::{Erc20Wrapper, IERC20Wrapper}, Erc20, IErc20, }, utils::{introspection::erc165::IErc165, Pausable}, @@ -106,11 +106,11 @@ impl Erc20WrapperExample { // || Erc20Metadata::supports_interface(interface_id) // } - // /// WARNING: These functions are intended for **testing purposes** only. In - // /// **production**, ensure strict access control to prevent unauthorized - // /// pausing or unpausing, which can disrupt contract functionality. Remove - // /// or secure these functions before deployment. - // pub fn pause(&mut self) -> Result<(), Vec> { + // /// WARNING: These functions are intended for **testing purposes** only. + // In /// **production**, ensure strict access control to prevent + // unauthorized /// pausing or unpausing, which can disrupt contract + // functionality. Remove /// or secure these functions before + // deployment. pub fn pause(&mut self) -> Result<(), Vec> { // self.pausable.pause().map_err(|e| e.into()) // } diff --git a/examples/erc20-wrapper/tests/erc20wrapper.rs b/examples/erc20-wrapper/tests/erc20wrapper.rs index 3549b8e4d..91716a663 100644 --- a/examples/erc20-wrapper/tests/erc20wrapper.rs +++ b/examples/erc20-wrapper/tests/erc20wrapper.rs @@ -23,7 +23,6 @@ const TOKEN_SYMBOL: &str = "TTK"; const WRAPPED_TOKEN_NAME: &str = "WRAPPED Test Token"; const WRAPPED_TOKEN_SYMBOL: &str = "WTTK"; - impl Default for constructorCall { fn default() -> Self { ctr() @@ -33,7 +32,7 @@ impl Default for constructorCall { fn ctr() -> constructorCall { Erc20WrapperExample::constructorCall { name_: WRAPPED_TOKEN_NAME.to_owned(), - symbol_: WRAPPED_TOKEN_SYMBOL.to_owned() + symbol_: WRAPPED_TOKEN_SYMBOL.to_owned(), } } From 961353e6c0a3473c94a4b10d8474f79088912abe Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Thu, 16 Jan 2025 04:43:10 +0000 Subject: [PATCH 06/46] chore: wrapper example --- examples/erc20-wrapper/src/lib.rs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/examples/erc20-wrapper/src/lib.rs b/examples/erc20-wrapper/src/lib.rs index cce6479b7..6cb0a6928 100644 --- a/examples/erc20-wrapper/src/lib.rs +++ b/examples/erc20-wrapper/src/lib.rs @@ -3,13 +3,9 @@ extern crate alloc; use alloc::vec::Vec; -use alloy_primitives::{Address, FixedBytes, U256}; -use openzeppelin_stylus::{ - token::erc20::{ - extensions::{Erc20Wrapper, IERC20Wrapper}, - Erc20, IErc20, - }, - utils::{introspection::erc165::IErc165, Pausable}, +use openzeppelin_stylus::token::erc20::{ + extensions::{Erc20Wrapper, IERC20Wrapper}, + Erc20, }; use stylus_sdk::prelude::{entrypoint, public, storage}; From a347f694a85362b723d110536a38caa37ae43e67 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Sat, 25 Jan 2025 20:56:46 +0000 Subject: [PATCH 07/46] chore: erc20 wrapper --- contracts/src/token/erc20/extensions/wrapper.rs | 2 +- examples/erc20-wrapper/src/lib.rs | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/contracts/src/token/erc20/extensions/wrapper.rs b/contracts/src/token/erc20/extensions/wrapper.rs index a383eebeb..a58e0f159 100644 --- a/contracts/src/token/erc20/extensions/wrapper.rs +++ b/contracts/src/token/erc20/extensions/wrapper.rs @@ -51,7 +51,7 @@ pub enum Error { /// Error type from [`Erc20`] contract [`erc20::Error`]. Erc20(erc20::Error), } -/// State of an [`Erc4626`] token. +/// State of an [`Erc20Wrapper`] token. #[storage] pub struct Erc20Wrapper { /// Token Address of the vault diff --git a/examples/erc20-wrapper/src/lib.rs b/examples/erc20-wrapper/src/lib.rs index 6cb0a6928..302f88d0f 100644 --- a/examples/erc20-wrapper/src/lib.rs +++ b/examples/erc20-wrapper/src/lib.rs @@ -4,7 +4,7 @@ extern crate alloc; use alloc::vec::Vec; use openzeppelin_stylus::token::erc20::{ - extensions::{Erc20Wrapper, IERC20Wrapper}, + extensions::{Erc20Wrapper, Erc20Metadata}, Erc20, }; use stylus_sdk::prelude::{entrypoint, public, storage}; @@ -17,11 +17,13 @@ struct Erc20WrapperExample { #[borrow] pub erc20: Erc20, #[borrow] + pub metadata: Erc20Metadata, + #[borrow] pub wrapper: Erc20Wrapper, } #[public] -#[inherit(Erc20)] +#[inherit(Erc20,)] impl Erc20WrapperExample { // Overrides the default [`Metadata::decimals`], and sets it to `10`. // From 9c66630906996b65cbb18a90210957dd813ecb53 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Sat, 25 Jan 2025 20:57:51 +0000 Subject: [PATCH 08/46] chore: removed variable --- examples/erc20-wrapper/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/erc20-wrapper/src/lib.rs b/examples/erc20-wrapper/src/lib.rs index 302f88d0f..4c25bac57 100644 --- a/examples/erc20-wrapper/src/lib.rs +++ b/examples/erc20-wrapper/src/lib.rs @@ -4,7 +4,7 @@ extern crate alloc; use alloc::vec::Vec; use openzeppelin_stylus::token::erc20::{ - extensions::{Erc20Wrapper, Erc20Metadata}, + extensions::{Erc20Metadata, Erc20Wrapper}, Erc20, }; use stylus_sdk::prelude::{entrypoint, public, storage}; @@ -23,7 +23,7 @@ struct Erc20WrapperExample { } #[public] -#[inherit(Erc20,)] +#[inherit(Erc20)] impl Erc20WrapperExample { // Overrides the default [`Metadata::decimals`], and sets it to `10`. // From bd69a58b750a637391a83fd09ce4d176e01541e2 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Sun, 26 Jan 2025 17:45:43 +0000 Subject: [PATCH 09/46] chore: wrapper and example --- .../src/token/erc20/extensions/wrapper.rs | 138 ++++++++++++------ examples/erc20-wrapper/src/lib.rs | 13 +- 2 files changed, 98 insertions(+), 53 deletions(-) diff --git a/contracts/src/token/erc20/extensions/wrapper.rs b/contracts/src/token/erc20/extensions/wrapper.rs index a58e0f159..f4743c358 100644 --- a/contracts/src/token/erc20/extensions/wrapper.rs +++ b/contracts/src/token/erc20/extensions/wrapper.rs @@ -16,16 +16,15 @@ 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 super::IErc20Metadata; use crate::token::erc20::{ self, - extensions::Erc20Metadata, utils::{ safe_erc20::{self, ISafeErc20}, SafeErc20, @@ -33,11 +32,46 @@ use crate::token::erc20::{ Erc20, }; +mod token { + + #![allow(missing_docs)] + #![cfg_attr(coverage_nightly, coverage(off))] + + use alloc::vec; + + use stylus_sdk::stylus_proc::sol_interface; + + sol_interface! { + /// Solidity Interface of the ERC-20 token. + interface IErc20 { + function balanceOf(address account) external view returns (uint256); + function totalSupply() external view returns (uint256); + } + } +} + +use token::IErc20 as IErc20Solidity; + sol! { #[derive(Debug)] #[allow(missing_docs)] error ERC20InvalidUnderlying(address token); + #[derive(Debug)] + #[allow(missing_docs)] + error ERC20InvalidSender(address sender); + + /// The address is not a valid ERC-20 token. + /// + /// * `asset` - Address of the invalid ERC-20 token. + #[derive(Debug)] + #[allow(missing_docs)] + error InvalidAsset(address asset); + + #[derive(Debug)] + #[allow(missing_docs)] + error ERC20InvalidReceiver(address receiver); + } /// An [`Erc20Wrapper`] error. @@ -46,73 +80,69 @@ 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 vault + /// Token Address of the underline token #[allow(clippy::used_underscore_binding)] - pub _underlying: StorageAddress, + pub(crate) _underlying: StorageAddress, + + /// [`SafeErc20`] contract + safe_erc20: SafeErc20, } /// ERC-4626 Tokenized Vault Standard Interface pub trait IERC20Wrapper { - /// The error type associated to this ERC-4626 trait implementation. + /// The error type associated to this ERC20Wrapper trait implementation. type Error: Into>; - /// Allow a user to deposit underlying tokens and mint the corresponding - /// number of wrapped token - /// - /// # Examples - /// - /// ```rust,ignore - /// fn decimals(&self) -> Result { - /// self.token.decimals(account, &self.erc20) - /// } - /// `` - fn decimals(&self, erc20: &mut Erc20Metadata) -> u8; - - /// Returns the address of the underlying token that is bben wrapped. + /// 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 + /// Allow a user to deposit underlying tokens and mint the corresponding /// number of wrapped token /// - /// # Examples + /// Arguments: /// - /// ```rust,ignore - /// fn deposit_to(&self,account:Address, value:U256) -> Result { - /// self.token.deposit_to(account,value, &self.erc20, &self.safe_erc20) - /// } - /// ``` + /// * `&mut self` - Write access to the contract's state. + /// * `account` - The account to deposit tokens to. + /// * `value` - The amount of tokens to deposit. fn deposit_to( - &self, + &mut self, account: Address, value: U256, erc20: &mut Erc20, - safe_erc20: &mut SafeErc20, ) -> Result; /// Allow a user to burn a number of wrapped tokens and withdraw the /// corresponding number of underlying tokens. /// - /// # Examples + /// Arguments: /// - /// ```rust,ignore - /// fn withdraw_to(&self,account:Address, value:U256) -> Result { - /// self.token.deposit_to(account,value, &self.erc20, &self.safe_erc20) - /// } - /// ``` + /// * `&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. fn withdraw_to( - &self, + &mut self, account: Address, value: U256, erc20: &mut Erc20, - safe_erc20: &mut SafeErc20, ) -> Result; } @@ -128,16 +158,11 @@ impl IERC20Wrapper for Erc20Wrapper { self._underlying.get() } - fn decimals(&self, erc20_metadata: &mut Erc20Metadata) -> u8 { - erc20_metadata.decimals() - } - fn deposit_to( - &self, + &mut self, account: Address, value: U256, erc20: &mut Erc20, - safe_erc20: &mut SafeErc20, ) -> Result { let underlined_token = self._underlying.get(); let sender = msg::sender(); @@ -152,7 +177,7 @@ impl IERC20Wrapper for Erc20Wrapper { token: account, })); } - safe_erc20.safe_transfer_from( + self.safe_erc20.safe_transfer_from( underlined_token, sender, contract::address(), @@ -163,11 +188,10 @@ impl IERC20Wrapper for Erc20Wrapper { } fn withdraw_to( - &self, + &mut self, account: Address, value: U256, erc20: &mut Erc20, - safe_erc20: &mut SafeErc20, ) -> Result { let underlined_token = self._underlying.get(); if account == contract::address() { @@ -176,13 +200,33 @@ impl IERC20Wrapper for Erc20Wrapper { })); } erc20._burn(account, value)?; - safe_erc20.safe_transfer(underlined_token, account, value)?; + self.safe_erc20.safe_transfer(underlined_token, account, value)?; Ok(true) } } impl Erc20Wrapper { - fn _recover(&self) -> Address { - return contract::address(); + /// 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. + pub fn _recover( + &mut self, + account: Address, + erc20: &mut Erc20, + ) -> Result { + let token = IErc20Solidity::new(self.underlying()); + let value = token + .balance_of(Call::new_in(self), contract::address()) + .map_err(|_| InvalidAsset { asset: contract::address() })?; + erc20._mint(account, value)?; + Ok(U256::from(value)) } } diff --git a/examples/erc20-wrapper/src/lib.rs b/examples/erc20-wrapper/src/lib.rs index 4c25bac57..895c0345c 100644 --- a/examples/erc20-wrapper/src/lib.rs +++ b/examples/erc20-wrapper/src/lib.rs @@ -3,14 +3,15 @@ extern crate alloc; use alloc::vec::Vec; -use openzeppelin_stylus::token::erc20::{ - extensions::{Erc20Metadata, Erc20Wrapper}, - Erc20, +use openzeppelin_stylus::token::{ + erc20::{ + extensions::{Erc20Metadata, Erc20Wrapper}, + Erc20, + }, + erc721::extensions::Erc721Metadata, }; use stylus_sdk::prelude::{entrypoint, public, storage}; -const DECIMALS: u8 = 10; - #[entrypoint] #[storage] struct Erc20WrapperExample { @@ -23,7 +24,7 @@ struct Erc20WrapperExample { } #[public] -#[inherit(Erc20)] +#[inherit(Erc20, Erc721Metadata)] impl Erc20WrapperExample { // Overrides the default [`Metadata::decimals`], and sets it to `10`. // From 1bedc1691fed13a7d72a76a5d3f76f606c78b8d2 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Sun, 26 Jan 2025 23:46:03 +0000 Subject: [PATCH 10/46] chore: erc20 wrapper example --- examples/erc20-wrapper/src/lib.rs | 121 ++++++------------------------ 1 file changed, 24 insertions(+), 97 deletions(-) diff --git a/examples/erc20-wrapper/src/lib.rs b/examples/erc20-wrapper/src/lib.rs index 895c0345c..96d5186a3 100644 --- a/examples/erc20-wrapper/src/lib.rs +++ b/examples/erc20-wrapper/src/lib.rs @@ -3,13 +3,11 @@ extern crate alloc; use alloc::vec::Vec; -use openzeppelin_stylus::token::{ - erc20::{ - extensions::{Erc20Metadata, Erc20Wrapper}, +use alloy_primitives::{Address, U256}; +use openzeppelin_stylus::token::erc20::{ + extensions::{Erc20Metadata, Erc20Wrapper,IERC20Wrapper}, Erc20, - }, - erc721::extensions::Erc721Metadata, -}; + }; use stylus_sdk::prelude::{entrypoint, public, storage}; #[entrypoint] @@ -24,96 +22,25 @@ struct Erc20WrapperExample { } #[public] -#[inherit(Erc20, Erc721Metadata)] +#[inherit(Erc20, Erc20Metadata)] impl Erc20WrapperExample { - // Overrides the default [`Metadata::decimals`], and sets it to `10`. - // - // If you don't provide this method in the `entrypoint` contract, it will - // default to `18`. - // pub fn decimals(&self) -> u8 { - // DECIMALS - // } - - // pub fn burn(&mut self, value: U256) -> Result<(), Vec> { - // self.pausable.when_not_paused()?; - // self.erc20.burn(value).map_err(|e| e.into()) - // } - - // pub fn burn_from( - // &mut self, - // account: Address, - // value: U256, - // ) -> Result<(), Vec> { - // self.pausable.when_not_paused()?; - // self.erc20.burn_from(account, value).map_err(|e| e.into()) - // } - - // // Add token minting feature. - // // - // // Make sure to handle `Capped` properly. You should not call - // // [`Erc20::_update`] to mint tokens -- it will the break `Capped` - // // mechanism. - // pub fn mint( - // &mut self, - // account: Address, - // value: U256, - // ) -> Result<(), Vec> { - // self.pausable.when_not_paused()?; - // let max_supply = self.capped.cap(); - - // // Overflow check required. - // let supply = self - // .erc20 - // .total_supply() - // .checked_add(value) - // .expect("new supply should not exceed `U256::MAX`"); - - // if supply > max_supply { - // return Err(capped::Error::ExceededCap( - // capped::ERC20ExceededCap { - // increased_supply: supply, - // cap: max_supply, - // }, - // ))?; - // } - - // self.erc20._mint(account, value)?; - // Ok(()) - // } - - // pub fn transfer( - // &mut self, - // to: Address, - // value: U256, - // ) -> Result> { - // self.pausable.when_not_paused()?; - // self.erc20.transfer(to, value).map_err(|e| e.into()) - // } - - // pub fn transfer_from( - // &mut self, - // from: Address, - // to: Address, - // value: U256, - // ) -> Result> { - // self.pausable.when_not_paused()?; - // self.erc20.transfer_from(from, to, value).map_err(|e| e.into()) - // } - - // fn supports_interface(interface_id: FixedBytes<4>) -> bool { - // Erc20::supports_interface(interface_id) - // || Erc20Metadata::supports_interface(interface_id) - // } - - // /// WARNING: These functions are intended for **testing purposes** only. - // In /// **production**, ensure strict access control to prevent - // unauthorized /// pausing or unpausing, which can disrupt contract - // functionality. Remove /// or secure these functions before - // deployment. pub fn pause(&mut self) -> Result<(), Vec> { - // self.pausable.pause().map_err(|e| e.into()) - // } - - // pub fn unpause(&mut self) -> Result<(), Vec> { - // self.pausable.unpause().map_err(|e| e.into()) - // } + fn underlying(&self) -> Address { + self.wrapper.underlying() + } + + fn deposit_to( + &mut self, + account: Address, + value: U256 + ) -> Result> { + Ok(self.wrapper.deposit_to(account, value, &mut self.erc20)?) + } + + fn withdraw_to( + &mut self, + account: Address, + value: U256 + ) -> Result> { + Ok(self.wrapper.withdraw_to(account, value, &mut self.erc20)?) + } } From 9d5419cec2073f2d433349d009951e7b6daa8403 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Sun, 26 Jan 2025 23:49:17 +0000 Subject: [PATCH 11/46] cargo fmt --- examples/erc20-wrapper/src/lib.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/erc20-wrapper/src/lib.rs b/examples/erc20-wrapper/src/lib.rs index 96d5186a3..e54dfc2eb 100644 --- a/examples/erc20-wrapper/src/lib.rs +++ b/examples/erc20-wrapper/src/lib.rs @@ -5,9 +5,9 @@ use alloc::vec::Vec; use alloy_primitives::{Address, U256}; use openzeppelin_stylus::token::erc20::{ - extensions::{Erc20Metadata, Erc20Wrapper,IERC20Wrapper}, - Erc20, - }; + extensions::{Erc20Metadata, Erc20Wrapper, IERC20Wrapper}, + Erc20, +}; use stylus_sdk::prelude::{entrypoint, public, storage}; #[entrypoint] @@ -25,13 +25,13 @@ struct Erc20WrapperExample { #[inherit(Erc20, Erc20Metadata)] impl Erc20WrapperExample { fn underlying(&self) -> Address { - self.wrapper.underlying() + self.wrapper.underlying() } fn deposit_to( &mut self, account: Address, - value: U256 + value: U256, ) -> Result> { Ok(self.wrapper.deposit_to(account, value, &mut self.erc20)?) } @@ -39,7 +39,7 @@ impl Erc20WrapperExample { fn withdraw_to( &mut self, account: Address, - value: U256 + value: U256, ) -> Result> { Ok(self.wrapper.withdraw_to(account, value, &mut self.erc20)?) } From 2d6a02f994b8c25e1c582d005be1bc25baa5a40c Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Mon, 27 Jan 2025 00:15:50 +0000 Subject: [PATCH 12/46] chore: fixed constructor test --- examples/erc20-wrapper/tests/erc20wrapper.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/erc20-wrapper/tests/erc20wrapper.rs b/examples/erc20-wrapper/tests/erc20wrapper.rs index 91716a663..97391a5af 100644 --- a/examples/erc20-wrapper/tests/erc20wrapper.rs +++ b/examples/erc20-wrapper/tests/erc20wrapper.rs @@ -49,14 +49,13 @@ async fn constructs(alice: Account) -> Result<()> { .await? .address()?; let contract = Erc20Wrapper::new(contract_addr, &alice.wallet); - let name = contract.name().call().await?.name; let symbol = contract.symbol().call().await?.symbol; let decimals = contract.decimals().call().await?.decimals; assert_eq!(name, WRAPPED_TOKEN_NAME.to_owned()); assert_eq!(symbol, WRAPPED_TOKEN_SYMBOL.to_owned()); - assert_eq!(decimals, 10); + assert_eq!(decimals, 18); Ok(()) } From 398da73d13173ae86b2f56e7947eb47497d15364 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Mon, 27 Jan 2025 00:21:47 +0000 Subject: [PATCH 13/46] chore: wrapper --- examples/erc20-wrapper/tests/erc20wrapper.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/erc20-wrapper/tests/erc20wrapper.rs b/examples/erc20-wrapper/tests/erc20wrapper.rs index 97391a5af..5d2a2ed5d 100644 --- a/examples/erc20-wrapper/tests/erc20wrapper.rs +++ b/examples/erc20-wrapper/tests/erc20wrapper.rs @@ -37,7 +37,7 @@ fn ctr() -> constructorCall { } // ============================================================================ -// Integration Tests: ERC-20 Token + Metadata Extension +// Integration Tests: ERC-20 Token + Metadata Extension + ERC-20 Wrapper // ============================================================================ #[e2e::test] @@ -53,7 +53,7 @@ async fn constructs(alice: Account) -> Result<()> { let symbol = contract.symbol().call().await?.symbol; let decimals = contract.decimals().call().await?.decimals; - assert_eq!(name, WRAPPED_TOKEN_NAME.to_owned()); + assert_eq!(name, WRAPPED_TOKEN_NAME.to_owned()); assert_eq!(symbol, WRAPPED_TOKEN_SYMBOL.to_owned()); assert_eq!(decimals, 18); From 3405bf2086c6fffc605e85e7d4aa2130eb502af7 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Mon, 27 Jan 2025 01:11:51 +0000 Subject: [PATCH 14/46] chore: fomrmat --- examples/erc20-wrapper/src/constructor.sol | 10 +++- examples/erc20-wrapper/tests/erc20wrapper.rs | 20 ++++---- examples/erc20-wrapper/tests/mock/erc20.rs | 48 ++++++++++++++++++++ examples/erc20-wrapper/tests/mock/mod.rs | 1 + 4 files changed, 66 insertions(+), 13 deletions(-) create mode 100644 examples/erc20-wrapper/tests/mock/erc20.rs create mode 100644 examples/erc20-wrapper/tests/mock/mod.rs diff --git a/examples/erc20-wrapper/src/constructor.sol b/examples/erc20-wrapper/src/constructor.sol index ed34902e7..8f79d3645 100644 --- a/examples/erc20-wrapper/src/constructor.sol +++ b/examples/erc20-wrapper/src/constructor.sol @@ -2,14 +2,22 @@ pragma solidity ^0.8.21; contract Erc20WrapperExample { + + mapping(address account => uint256) private _balances; mapping(address account => mapping(address spender => uint256)) private _allowances; uint256 private _totalSupply; string private _name; string private _symbol; - constructor(string memory name_, string memory symbol_) { + + + address private _underlyingToken; + + + constructor(string memory name_, string memory symbol_, address underlyingToken_) { _name = name_; _symbol = symbol_; + _underlyingToken = underlyingToken_; } } diff --git a/examples/erc20-wrapper/tests/erc20wrapper.rs b/examples/erc20-wrapper/tests/erc20wrapper.rs index 5d2a2ed5d..ddfdad530 100644 --- a/examples/erc20-wrapper/tests/erc20wrapper.rs +++ b/examples/erc20-wrapper/tests/erc20wrapper.rs @@ -14,23 +14,18 @@ use eyre::Result; use crate::Erc20WrapperExample::constructorCall; mod abi; +mod mock; -sol!("src/constructor.sol"); +use mock::{erc20, erc20::ERC20Mock}; -const TOKEN_NAME: &str = "Test Token"; -const TOKEN_SYMBOL: &str = "TTK"; +sol!("src/constructor.sol"); const WRAPPED_TOKEN_NAME: &str = "WRAPPED Test Token"; const WRAPPED_TOKEN_SYMBOL: &str = "WTTK"; -impl Default for constructorCall { - fn default() -> Self { - ctr() - } -} - -fn ctr() -> constructorCall { +fn ctr(asset: Address) -> constructorCall { Erc20WrapperExample::constructorCall { + underlyingToken_: asset, name_: WRAPPED_TOKEN_NAME.to_owned(), symbol_: WRAPPED_TOKEN_SYMBOL.to_owned(), } @@ -42,9 +37,10 @@ fn ctr() -> constructorCall { #[e2e::test] async fn constructs(alice: Account) -> Result<()> { + let asset_address = erc20::deploy(&alice.wallet).await?; let contract_addr = alice .as_deployer() - .with_default_constructor::() + .with_constructor(ctr(asset_address)) .deploy() .await? .address()?; @@ -53,7 +49,7 @@ async fn constructs(alice: Account) -> Result<()> { let symbol = contract.symbol().call().await?.symbol; let decimals = contract.decimals().call().await?.decimals; - assert_eq!(name, WRAPPED_TOKEN_NAME.to_owned()); + assert_eq!(name, WRAPPED_TOKEN_NAME.to_owned()); assert_eq!(symbol, WRAPPED_TOKEN_SYMBOL.to_owned()); assert_eq!(decimals, 18); diff --git a/examples/erc20-wrapper/tests/mock/erc20.rs b/examples/erc20-wrapper/tests/mock/erc20.rs new file mode 100644 index 000000000..68514daea --- /dev/null +++ b/examples/erc20-wrapper/tests/mock/erc20.rs @@ -0,0 +1,48 @@ +#![allow(dead_code)] +#![cfg(feature = "e2e")] +use alloy::{primitives::Address, sol}; +use e2e::Wallet; + +sol! { + #[allow(missing_docs)] + // Built with Remix IDE; solc v0.8.21+commit.d9974bed + #[sol(rpc, bytecode="608060405234801562000010575f80fd5b506040518060400160405280600981526020017f45524332304d6f636b00000000000000000000000000000000000000000000008152506040518060400160405280600381526020017f4d544b000000000000000000000000000000000000000000000000000000000081525081600390816200008e91906200030d565b508060049081620000a091906200030d565b505050620003f1565b5f81519050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52604160045260245ffd5b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f60028204905060018216806200012557607f821691505b6020821081036200013b576200013a620000e0565b5b50919050565b5f819050815f5260205f209050919050565b5f6020601f8301049050919050565b5f82821b905092915050565b5f600883026200019f7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8262000162565b620001ab868362000162565b95508019841693508086168417925050509392505050565b5f819050919050565b5f819050919050565b5f620001f5620001ef620001e984620001c3565b620001cc565b620001c3565b9050919050565b5f819050919050565b6200021083620001d5565b620002286200021f82620001fc565b8484546200016e565b825550505050565b5f90565b6200023e62000230565b6200024b81848462000205565b505050565b5b818110156200027257620002665f8262000234565b60018101905062000251565b5050565b601f821115620002c1576200028b8162000141565b620002968462000153565b81016020851015620002a6578190505b620002be620002b58562000153565b83018262000250565b50505b505050565b5f82821c905092915050565b5f620002e35f1984600802620002c6565b1980831691505092915050565b5f620002fd8383620002d2565b9150826002028217905092915050565b6200031882620000a9565b67ffffffffffffffff811115620003345762000333620000b3565b5b6200034082546200010d565b6200034d82828562000276565b5f60209050601f83116001811462000383575f84156200036e578287015190505b6200037a8582620002f0565b865550620003e9565b601f198416620003938662000141565b5f5b82811015620003bc5784890151825560018201915060208501945060208101905062000395565b86831015620003dc5784890151620003d8601f891682620002d2565b8355505b6001600288020188555050505b505050505050565b610f4580620003ff5f395ff3fe608060405234801561000f575f80fd5b50600436106100a7575f3560e01c806340c10f191161006f57806340c10f191461016557806370a08231146101815780638483acfe146101b157806395d89b41146101cd578063a9059cbb146101eb578063dd62ed3e1461021b576100a7565b806306fdde03146100ab578063095ea7b3146100c957806318160ddd146100f957806323b872dd14610117578063313ce56714610147575b5f80fd5b6100b361024b565b6040516100c09190610bbe565b60405180910390f35b6100e360048036038101906100de9190610c6f565b6102db565b6040516100f09190610cc7565b60405180910390f35b6101016102ee565b60405161010e9190610cef565b60405180910390f35b610131600480360381019061012c9190610d08565b6102f7565b60405161013e9190610cc7565b60405180910390f35b61014f61030c565b60405161015c9190610d73565b60405180910390f35b61017f600480360381019061017a9190610c6f565b610314565b005b61019b60048036038101906101969190610d8c565b610322565b6040516101a89190610cef565b60405180910390f35b6101cb60048036038101906101c69190610d08565b610333565b005b6101d5610343565b6040516101e29190610bbe565b60405180910390f35b61020560048036038101906102009190610c6f565b6103d3565b6040516102129190610cc7565b60405180910390f35b61023560048036038101906102309190610db7565b6103e6565b6040516102429190610cef565b60405180910390f35b60606003805461025a90610e22565b80601f016020809104026020016040519081016040528092919081815260200182805461028690610e22565b80156102d15780601f106102a8576101008083540402835291602001916102d1565b820191905f5260205f20905b8154815290600101906020018083116102b457829003601f168201915b5050505050905090565b5f6102e683836103f9565b905092915050565b5f600254905090565b5f61030384848461041b565b90509392505050565b5f6012905090565b61031e8282610449565b5050565b5f61032c826104c8565b9050919050565b61033e83838361050d565b505050565b60606004805461035290610e22565b80601f016020809104026020016040519081016040528092919081815260200182805461037e90610e22565b80156103c95780601f106103a0576101008083540402835291602001916103c9565b820191905f5260205f20905b8154815290600101906020018083116103ac57829003601f168201915b5050505050905090565b5f6103de838361051f565b905092915050565b5f6103f18383610541565b905092915050565b5f806104036105c3565b905061041081858561050d565b600191505092915050565b5f806104256105c3565b90506104328582856105ca565b61043d85858561065c565b60019150509392505050565b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036104b9575f6040517fec442f050000000000000000000000000000000000000000000000000000000081526004016104b09190610e61565b60405180910390fd5b6104c45f838361074c565b5050565b5f805f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20549050919050565b61051a8383836001610965565b505050565b5f806105296105c3565b905061053681858561065c565b600191505092915050565b5f60015f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054905092915050565b5f33905090565b5f6105d584846103e6565b90507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81146106565781811015610647578281836040517ffb8f41b200000000000000000000000000000000000000000000000000000000815260040161063e93929190610e7a565b60405180910390fd5b61065584848484035f610965565b5b50505050565b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16036106cc575f6040517f96c6fd1e0000000000000000000000000000000000000000000000000000000081526004016106c39190610e61565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff160361073c575f6040517fec442f050000000000000000000000000000000000000000000000000000000081526004016107339190610e61565b60405180910390fd5b61074783838361074c565b505050565b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff160361079c578060025f8282546107909190610edc565b9250508190555061086a565b5f805f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054905081811015610825578381836040517fe450d38c00000000000000000000000000000000000000000000000000000000815260040161081c93929190610e7a565b60405180910390fd5b8181035f808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2081905550505b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036108b1578060025f82825403925050819055506108fb565b805f808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f82825401925050819055505b8173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040516109589190610cef565b60405180910390a3505050565b5f73ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff16036109d5575f6040517fe602df050000000000000000000000000000000000000000000000000000000081526004016109cc9190610e61565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603610a45575f6040517f94280d62000000000000000000000000000000000000000000000000000000008152600401610a3c9190610e61565b60405180910390fd5b8160015f8673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20819055508015610b2e578273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92584604051610b259190610cef565b60405180910390a35b50505050565b5f81519050919050565b5f82825260208201905092915050565b5f5b83811015610b6b578082015181840152602081019050610b50565b5f8484015250505050565b5f601f19601f8301169050919050565b5f610b9082610b34565b610b9a8185610b3e565b9350610baa818560208601610b4e565b610bb381610b76565b840191505092915050565b5f6020820190508181035f830152610bd68184610b86565b905092915050565b5f80fd5b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f610c0b82610be2565b9050919050565b610c1b81610c01565b8114610c25575f80fd5b50565b5f81359050610c3681610c12565b92915050565b5f819050919050565b610c4e81610c3c565b8114610c58575f80fd5b50565b5f81359050610c6981610c45565b92915050565b5f8060408385031215610c8557610c84610bde565b5b5f610c9285828601610c28565b9250506020610ca385828601610c5b565b9150509250929050565b5f8115159050919050565b610cc181610cad565b82525050565b5f602082019050610cda5f830184610cb8565b92915050565b610ce981610c3c565b82525050565b5f602082019050610d025f830184610ce0565b92915050565b5f805f60608486031215610d1f57610d1e610bde565b5b5f610d2c86828701610c28565b9350506020610d3d86828701610c28565b9250506040610d4e86828701610c5b565b9150509250925092565b5f60ff82169050919050565b610d6d81610d58565b82525050565b5f602082019050610d865f830184610d64565b92915050565b5f60208284031215610da157610da0610bde565b5b5f610dae84828501610c28565b91505092915050565b5f8060408385031215610dcd57610dcc610bde565b5b5f610dda85828601610c28565b9250506020610deb85828601610c28565b9150509250929050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52602260045260245ffd5b5f6002820490506001821680610e3957607f821691505b602082108103610e4c57610e4b610df5565b5b50919050565b610e5b81610c01565b82525050565b5f602082019050610e745f830184610e52565b92915050565b5f606082019050610e8d5f830186610e52565b610e9a6020830185610ce0565b610ea76040830184610ce0565b949350505050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f610ee682610c3c565b9150610ef183610c3c565b9250828201905080821115610f0957610f08610eaf565b5b9291505056fea2646970667358221220383e898342e74543d1bfb6186eff00b4ae7a39d4ecde6190742c5e9f2a7a2e9364736f6c63430008150033")] + // SPDX-License-Identifier: MIT + contract ERC20Mock is ERC20 { + constructor() ERC20("ERC20Mock", "MTK") {} + + function approve(address spender, uint256 value) public override returns (bool) { + return super.approve(spender, value); + } + + function regular_approve(address owner, address spender, uint256 amount) public { + super._approve(owner, spender, amount); + } + + function balanceOf(address account) public override view returns (uint256) { + return super.balanceOf(account); + } + + function mint(address account, uint256 value) public { + super._mint(account, value); + } + + function transfer(address to, uint256 amount) public override returns (bool) { + return super.transfer(to, amount); + } + + function transferFrom(address from, address to, uint256 value) public override returns (bool) { + return super.transferFrom(from, to, value); + } + + function allowance(address owner, address spender) public view override returns (uint256) { + return super.allowance(owner, spender); + } + } +} + +pub async fn deploy(wallet: &Wallet) -> eyre::Result
{ + // Deploy the contract. + let contract = ERC20Mock::deploy(wallet).await?; + Ok(*contract.address()) +} diff --git a/examples/erc20-wrapper/tests/mock/mod.rs b/examples/erc20-wrapper/tests/mock/mod.rs new file mode 100644 index 000000000..8f3777f6b --- /dev/null +++ b/examples/erc20-wrapper/tests/mock/mod.rs @@ -0,0 +1 @@ +pub mod erc20; From dfc1e00d43c5ba532547e8f25c3f72599ce6b3e8 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Mon, 27 Jan 2025 02:09:37 +0000 Subject: [PATCH 15/46] chore: CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2e0340f4d..354f90517 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,12 +5,14 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + ## [Unreleased] ### Added - Implement `AddAssignChecked` for `StorageUint`. #474 - `Erc20FlashMint` extension. #407 +- `Erc20Wrapper` "Token Wrapping contract". #498 ### Changed From 4274d2ba68d50a37df5154f367cffedbf822947c Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Mon, 27 Jan 2025 02:22:29 +0000 Subject: [PATCH 16/46] chore:docs --- docs/modules/ROOT/pages/erc20-wrapper.adoc | 58 ++++++++++++++++++++++ docs/modules/ROOT/pages/erc20.adoc | 2 + 2 files changed, 60 insertions(+) create mode 100644 docs/modules/ROOT/pages/erc20-wrapper.adoc diff --git a/docs/modules/ROOT/pages/erc20-wrapper.adoc b/docs/modules/ROOT/pages/erc20-wrapper.adoc new file mode 100644 index 000000000..c3e7e6563 --- /dev/null +++ b/docs/modules/ROOT/pages/erc20-wrapper.adoc @@ -0,0 +1,58 @@ += 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. +For example, combining this wrapping mechanism with ERC20Votes will allow the wrapping of an existing "basic" ERC-20 into a governance token. + + +[[usage]] +== Usage + +In order to make your ERC20 token `wrapped`,: + +[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> { + Ok(self.wrapper.deposit_to(account, value, &mut self.erc20)?) + } + + fn withdraw_to( + &mut self, + account: Address, + value: U256, + ) -> Result> { + Ok(self.wrapper.withdraw_to(account, value, &mut self.erc20)?) + } +} +---- diff --git a/docs/modules/ROOT/pages/erc20.adoc b/docs/modules/ROOT/pages/erc20.adoc index 869d8ddcc..dca994b31 100644 --- a/docs/modules/ROOT/pages/erc20.adoc +++ b/docs/modules/ROOT/pages/erc20.adoc @@ -84,3 +84,5 @@ Additionally, there are multiple custom extensions, including: * xref:erc20-permit.adoc[ERC-20 Permit]: gasless approval of tokens (standardized as https://eips.ethereum.org/EIPS/eip-2612[`EIP-2612`]). * xref:erc20-flash-mint.adoc[ERC-20 Flash-Mint]: token level support for flash loans through the minting and burning of ephemeral tokens (standardized as https://eips.ethereum.org/EIPS/eip-3156[`EIP-3156`]). + + * xref:erc20-wrapper.adoc[ERC-20 Wrapper]: Extension of the ERC-20 token contract to support token wrapping (standardized as https://eips.ethereum.org/EIPS/eip-3156[`EIP-3156`]). From fd1de52d21a7e5035d82e720cccf787161f9a9c1 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Mon, 27 Jan 2025 02:23:54 +0000 Subject: [PATCH 17/46] chore: removed docs --- docs/modules/ROOT/pages/erc20.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/ROOT/pages/erc20.adoc b/docs/modules/ROOT/pages/erc20.adoc index dca994b31..353a65dad 100644 --- a/docs/modules/ROOT/pages/erc20.adoc +++ b/docs/modules/ROOT/pages/erc20.adoc @@ -85,4 +85,4 @@ Additionally, there are multiple custom extensions, including: * xref:erc20-flash-mint.adoc[ERC-20 Flash-Mint]: token level support for flash loans through the minting and burning of ephemeral tokens (standardized as https://eips.ethereum.org/EIPS/eip-3156[`EIP-3156`]). - * xref:erc20-wrapper.adoc[ERC-20 Wrapper]: Extension of the ERC-20 token contract to support token wrapping (standardized as https://eips.ethereum.org/EIPS/eip-3156[`EIP-3156`]). + * xref:erc20-wrapper.adoc[ERC-20 Wrapper]: Extension of the ERC-20 token contract to support token wrapping . From fb6a8949683f12b67347bb6b8ed05c55c4739f6a Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Mon, 27 Jan 2025 02:31:34 +0000 Subject: [PATCH 18/46] chore: erc20 wrappers docs --- docs/modules/ROOT/pages/erc20-wrapper.adoc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/modules/ROOT/pages/erc20-wrapper.adoc b/docs/modules/ROOT/pages/erc20-wrapper.adoc index c3e7e6563..d872cd18d 100644 --- a/docs/modules/ROOT/pages/erc20-wrapper.adoc +++ b/docs/modules/ROOT/pages/erc20-wrapper.adoc @@ -4,13 +4,12 @@ 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. -For example, combining this wrapping mechanism with ERC20Votes will allow the wrapping of an existing "basic" ERC-20 into a governance token. [[usage]] == Usage -In order to make your ERC20 token `wrapped`,: +In order to make your ERC20 `wrapped token`: [source,rust] ---- From 102611b73fb535406b6e449452be8a3abfd5b15c Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Mon, 27 Jan 2025 12:16:48 +0000 Subject: [PATCH 19/46] chore: fix underlying state --- contracts/src/token/erc20/extensions/wrapper.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/src/token/erc20/extensions/wrapper.rs b/contracts/src/token/erc20/extensions/wrapper.rs index f4743c358..44c6884b2 100644 --- a/contracts/src/token/erc20/extensions/wrapper.rs +++ b/contracts/src/token/erc20/extensions/wrapper.rs @@ -100,7 +100,7 @@ pub enum Error { pub struct Erc20Wrapper { /// Token Address of the underline token #[allow(clippy::used_underscore_binding)] - pub(crate) _underlying: StorageAddress, + pub(crate) underlying: StorageAddress, /// [`SafeErc20`] contract safe_erc20: SafeErc20, @@ -155,7 +155,7 @@ impl IERC20Wrapper for Erc20Wrapper { type Error = Error; fn underlying(&self) -> Address { - self._underlying.get() + self.underlying.get() } fn deposit_to( @@ -164,7 +164,7 @@ impl IERC20Wrapper for Erc20Wrapper { value: U256, erc20: &mut Erc20, ) -> Result { - let underlined_token = self._underlying.get(); + let underlined_token = self.underlying.get(); let sender = msg::sender(); if account == contract::address() { return Err(Error::InvalidUnderlying(ERC20InvalidUnderlying { @@ -193,7 +193,7 @@ impl IERC20Wrapper for Erc20Wrapper { value: U256, erc20: &mut Erc20, ) -> Result { - let underlined_token = self._underlying.get(); + let underlined_token = self.underlying.get(); if account == contract::address() { return Err(Error::InvalidUnderlying(ERC20InvalidUnderlying { token: contract::address(), From 1f450b27c83a59c73ec6ebae57558bdd1b6f6c6b Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Mon, 27 Jan 2025 15:57:20 +0000 Subject: [PATCH 20/46] chore: fixed clippy error --- .../src/token/erc20/extensions/wrapper.rs | 38 +++++++++++++------ 1 file changed, 27 insertions(+), 11 deletions(-) diff --git a/contracts/src/token/erc20/extensions/wrapper.rs b/contracts/src/token/erc20/extensions/wrapper.rs index 44c6884b2..65f486bf6 100644 --- a/contracts/src/token/erc20/extensions/wrapper.rs +++ b/contracts/src/token/erc20/extensions/wrapper.rs @@ -2,9 +2,7 @@ //! //! Users can deposit and withdraw "underlying tokens" and receive a matching //! number of "wrapped tokens". This is useful in conjunction with other -//! modules. For example, combining this wrapping mechanism with {ERC20Votes} -//! will allow the wrapping of an existing "basic" ERC-20 into a governance -//! token. +//! modules. //! //! WARNING: Any mechanism in which the underlying token changes the {balanceOf} //! of an account without an explicit transfer may desynchronize this contract's @@ -57,6 +55,7 @@ sol! { #[allow(missing_docs)] error ERC20InvalidUnderlying(address token); + #[derive(Debug)] #[allow(missing_docs)] error ERC20InvalidSender(address sender); @@ -106,9 +105,9 @@ pub struct Erc20Wrapper { safe_erc20: SafeErc20, } -/// ERC-4626 Tokenized Vault Standard Interface +/// ERC-20 Wrapper Standard Interface pub trait IERC20Wrapper { - /// The error type associated to this ERC20Wrapper trait implementation. + /// The error type associated to this `ERC20Wrapper` trait implementation. type Error: Into>; /// Returns the address of the underlying token that is been wrapped. @@ -122,6 +121,13 @@ pub trait IERC20Wrapper { /// * `&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_to( &mut self, account: Address, @@ -138,6 +144,11 @@ pub trait IERC20Wrapper { /// * `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( &mut self, account: Address, @@ -167,14 +178,14 @@ impl IERC20Wrapper for Erc20Wrapper { let underlined_token = self.underlying.get(); let sender = msg::sender(); if account == contract::address() { - return Err(Error::InvalidUnderlying(ERC20InvalidUnderlying { - token: contract::address(), + return Err(Error::InvalidSender(ERC20InvalidSender { + sender: contract::address(), })); } if sender == contract::address() { - return Err(Error::InvalidUnderlying(ERC20InvalidUnderlying { - token: account, + return Err(Error::InvalidReceiver(ERC20InvalidReceiver { + receiver: account, })); } self.safe_erc20.safe_transfer_from( @@ -195,8 +206,8 @@ impl IERC20Wrapper for Erc20Wrapper { ) -> Result { let underlined_token = self.underlying.get(); if account == contract::address() { - return Err(Error::InvalidUnderlying(ERC20InvalidUnderlying { - token: contract::address(), + return Err(Error::InvalidReceiver(ERC20InvalidReceiver { + receiver: account, })); } erc20._burn(account, value)?; @@ -217,6 +228,11 @@ impl Erc20Wrapper { /// * `&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( &mut self, account: Address, From fae58822fe4e55573acc64831bfb4a0f62d3263d Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Mon, 27 Jan 2025 15:59:20 +0000 Subject: [PATCH 21/46] chore: fixed clippy warnings --- contracts/src/token/erc20/extensions/wrapper.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/contracts/src/token/erc20/extensions/wrapper.rs b/contracts/src/token/erc20/extensions/wrapper.rs index 65f486bf6..125056e98 100644 --- a/contracts/src/token/erc20/extensions/wrapper.rs +++ b/contracts/src/token/erc20/extensions/wrapper.rs @@ -124,9 +124,9 @@ pub trait IERC20Wrapper { /// /// # Errors /// - /// * If the sender address is `contract:address()` or invalid, + /// * If the sender address is `contract:address()` or invalid, /// [`Error::InvalidSender`] is returned. - /// * If the receiver address is `contract:address()` or invalid, + /// * If the receiver address is `contract:address()` or invalid, /// [`Error::InvalidReceiver`] is returned. fn deposit_to( &mut self, @@ -144,10 +144,10 @@ pub trait IERC20Wrapper { /// * `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, + /// * If the receiver address is `contract:address()` or invalid, /// [`Error::InvalidReceiver`] is returned. fn withdraw_to( &mut self, @@ -228,7 +228,7 @@ impl Erc20Wrapper { /// * `&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 From f8d684a033a19c9338bf9be95ae4a07fe00798db Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Tue, 28 Jan 2025 03:09:55 +0000 Subject: [PATCH 22/46] docs --- contracts/src/token/erc20/extensions/wrapper.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/contracts/src/token/erc20/extensions/wrapper.rs b/contracts/src/token/erc20/extensions/wrapper.rs index 125056e98..fbf75dfab 100644 --- a/contracts/src/token/erc20/extensions/wrapper.rs +++ b/contracts/src/token/erc20/extensions/wrapper.rs @@ -67,6 +67,9 @@ sol! { #[allow(missing_docs)] error InvalidAsset(address asset); + /// The address is not a valid ERC-20 token. + /// + /// * `asset` - Address of the invalid ERC-20 token. #[derive(Debug)] #[allow(missing_docs)] error ERC20InvalidReceiver(address receiver); From d13ad4723b84ca77b86e3a0aceb1350d75a638c1 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Tue, 28 Jan 2025 03:41:22 +0000 Subject: [PATCH 23/46] docs: added missing docs --- contracts/src/token/erc20/extensions/wrapper.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/contracts/src/token/erc20/extensions/wrapper.rs b/contracts/src/token/erc20/extensions/wrapper.rs index fbf75dfab..47400ef78 100644 --- a/contracts/src/token/erc20/extensions/wrapper.rs +++ b/contracts/src/token/erc20/extensions/wrapper.rs @@ -51,25 +51,30 @@ mod token { use token::IErc20 as IErc20Solidity; sol! { + /// 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. + /// + /// * `sender` - Address is an ivalid sender. #[derive(Debug)] #[allow(missing_docs)] error ERC20InvalidSender(address sender); - /// The address is not a valid ERC-20 token. + /// Indicates that The address is not a valid Invalid Asset. /// - /// * `asset` - Address of the invalid ERC-20 token. + /// * `asset` - Address of the invalid address of the token. #[derive(Debug)] #[allow(missing_docs)] error InvalidAsset(address asset); - /// The address is not a valid ERC-20 token. + /// Indicates thata the address is not an invalid receiver addresss. /// - /// * `asset` - Address of the invalid ERC-20 token. + /// * `receiver` - Address of the invalid receiver. #[derive(Debug)] #[allow(missing_docs)] error ERC20InvalidReceiver(address receiver); From ab401e2d2316d337194ea09a09642babfe09d230 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Tue, 28 Jan 2025 03:42:46 +0000 Subject: [PATCH 24/46] chore: format --- contracts/src/token/erc20/extensions/wrapper.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/token/erc20/extensions/wrapper.rs b/contracts/src/token/erc20/extensions/wrapper.rs index 47400ef78..501a7b9ac 100644 --- a/contracts/src/token/erc20/extensions/wrapper.rs +++ b/contracts/src/token/erc20/extensions/wrapper.rs @@ -52,7 +52,7 @@ use token::IErc20 as IErc20Solidity; sol! { /// 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)] From 4070411327c4b0940f859ddddbea975307eacede Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Tue, 28 Jan 2025 03:46:21 +0000 Subject: [PATCH 25/46] docs: ivalid to invalid --- contracts/src/token/erc20/extensions/wrapper.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/token/erc20/extensions/wrapper.rs b/contracts/src/token/erc20/extensions/wrapper.rs index 501a7b9ac..c5fe2b9e7 100644 --- a/contracts/src/token/erc20/extensions/wrapper.rs +++ b/contracts/src/token/erc20/extensions/wrapper.rs @@ -60,7 +60,7 @@ sol! { /// Indicates that the address is not an Invalid Sender address. /// - /// * `sender` - Address is an ivalid sender. + /// * `sender` - Address is an invalid sender. #[derive(Debug)] #[allow(missing_docs)] error ERC20InvalidSender(address sender); From 4ac2b5cc0b955ecc16b8c5202f94f8bc7447aaa8 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Tue, 28 Jan 2025 06:32:31 +0000 Subject: [PATCH 26/46] test: underlying test works --- .../src/token/erc20/extensions/wrapper.rs | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/contracts/src/token/erc20/extensions/wrapper.rs b/contracts/src/token/erc20/extensions/wrapper.rs index c5fe2b9e7..f3000b693 100644 --- a/contracts/src/token/erc20/extensions/wrapper.rs +++ b/contracts/src/token/erc20/extensions/wrapper.rs @@ -107,7 +107,7 @@ pub enum Error { pub struct Erc20Wrapper { /// Token Address of the underline token #[allow(clippy::used_underscore_binding)] - pub(crate) underlying: StorageAddress, + pub(crate) underlying_address: StorageAddress, /// [`SafeErc20`] contract safe_erc20: SafeErc20, @@ -174,7 +174,7 @@ impl IERC20Wrapper for Erc20Wrapper { type Error = Error; fn underlying(&self) -> Address { - self.underlying.get() + self.underlying_address.get() } fn deposit_to( @@ -183,7 +183,7 @@ impl IERC20Wrapper for Erc20Wrapper { value: U256, erc20: &mut Erc20, ) -> Result { - let underlined_token = self.underlying.get(); + let underlined_token = self.underlying_address.get(); let sender = msg::sender(); if account == contract::address() { return Err(Error::InvalidSender(ERC20InvalidSender { @@ -212,7 +212,7 @@ impl IERC20Wrapper for Erc20Wrapper { value: U256, erc20: &mut Erc20, ) -> Result { - let underlined_token = self.underlying.get(); + let underlined_token = self.underlying_address.get(); if account == contract::address() { return Err(Error::InvalidReceiver(ERC20InvalidReceiver { receiver: account, @@ -254,3 +254,20 @@ impl Erc20Wrapper { Ok(U256::from(value)) } } + +// TODO: Add missing tests once `motsu` supports calling external contracts. +#[cfg(all(test, feature = "std"))] +mod tests { + + use alloy_primitives::{address, U256, U8}; + use stylus_sdk::{msg, prelude::storage}; + + use super::Erc20Wrapper; + + #[motsu::test] + fn underlying_works(contract: Erc20WrapperExample) { + let asset = address!("DeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF"); + contract.underlying_address.set(asset); + assert_eq!(contract.underlying(), asset); + } +} From 104c45a4f7856b3ef02829324a95727de01a2d80 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Tue, 28 Jan 2025 06:35:16 +0000 Subject: [PATCH 27/46] test: test fixes --- contracts/src/token/erc20/extensions/wrapper.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/contracts/src/token/erc20/extensions/wrapper.rs b/contracts/src/token/erc20/extensions/wrapper.rs index f3000b693..9a9b7dba2 100644 --- a/contracts/src/token/erc20/extensions/wrapper.rs +++ b/contracts/src/token/erc20/extensions/wrapper.rs @@ -259,13 +259,12 @@ impl Erc20Wrapper { #[cfg(all(test, feature = "std"))] mod tests { - use alloy_primitives::{address, U256, U8}; - use stylus_sdk::{msg, prelude::storage}; + use alloy_primitives::address; use super::Erc20Wrapper; #[motsu::test] - fn underlying_works(contract: Erc20WrapperExample) { + fn underlying_works(contract: Erc20Wrapper) { let asset = address!("DeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF"); contract.underlying_address.set(asset); assert_eq!(contract.underlying(), asset); From 2a1cd4375553603c1f9787e8c2f054aab02d9a69 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Tue, 4 Feb 2025 02:29:25 +0000 Subject: [PATCH 28/46] unit test --- contracts/src/token/erc20/extensions/wrapper.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/contracts/src/token/erc20/extensions/wrapper.rs b/contracts/src/token/erc20/extensions/wrapper.rs index 9a9b7dba2..a7c04f367 100644 --- a/contracts/src/token/erc20/extensions/wrapper.rs +++ b/contracts/src/token/erc20/extensions/wrapper.rs @@ -258,15 +258,20 @@ impl Erc20Wrapper { // TODO: Add missing tests once `motsu` supports calling external contracts. #[cfg(all(test, feature = "std"))] mod tests { - use alloy_primitives::address; + use stylus_sdk::prelude::storage; + + use super::{Erc20Wrapper, IERC20Wrapper}; - use super::Erc20Wrapper; + #[storage] + struct Erc20WrapperTestExample { + wrapper: Erc20Wrapper, + } #[motsu::test] - fn underlying_works(contract: Erc20Wrapper) { + fn underlying_works(contract:Erc20WrapperTestExample ) { let asset = address!("DeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF"); - contract.underlying_address.set(asset); - assert_eq!(contract.underlying(), asset); + contract.wrapper.underlying_address.set(asset); + assert_eq!(contract.wrapper.underlying(), asset); } } From c22a404476c65f3dbd2352214d890095defd1c38 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Tue, 4 Feb 2025 02:31:39 +0000 Subject: [PATCH 29/46] chore: erc20 wrapper --- Cargo.lock | 13 +- .../src/token/erc20/extensions/wrapper.rs | 2 +- examples/erc20-wrapper/tests/erc20wrapper.rs | 117 +++++++++++++++--- 3 files changed, 112 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6eef45e0d..f0cab8646 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1741,11 +1741,22 @@ dependencies = [ "tokio", ] +[[package]] +name = "erc20-wrapper" +version = "0.2.0-alpha.3" +dependencies = [ + "alloy", + "alloy-primitives", + "e2e", + "eyre", + "openzeppelin-stylus", + "stylus-sdk", + "tokio", +] [[package]] name = "erc4626-example" version = "0.2.0-alpha.3" - dependencies = [ "alloy", "alloy-primitives", diff --git a/contracts/src/token/erc20/extensions/wrapper.rs b/contracts/src/token/erc20/extensions/wrapper.rs index a7c04f367..38b348d47 100644 --- a/contracts/src/token/erc20/extensions/wrapper.rs +++ b/contracts/src/token/erc20/extensions/wrapper.rs @@ -269,7 +269,7 @@ mod tests { } #[motsu::test] - fn underlying_works(contract:Erc20WrapperTestExample ) { + fn underlying_works(contract: Erc20WrapperTestExample) { let asset = address!("DeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF"); contract.wrapper.underlying_address.set(asset); assert_eq!(contract.wrapper.underlying(), asset); diff --git a/examples/erc20-wrapper/tests/erc20wrapper.rs b/examples/erc20-wrapper/tests/erc20wrapper.rs index ddfdad530..82a2d59de 100644 --- a/examples/erc20-wrapper/tests/erc20wrapper.rs +++ b/examples/erc20-wrapper/tests/erc20wrapper.rs @@ -23,35 +23,116 @@ sol!("src/constructor.sol"); const WRAPPED_TOKEN_NAME: &str = "WRAPPED Test Token"; const WRAPPED_TOKEN_SYMBOL: &str = "WTTK"; -fn ctr(asset: Address) -> constructorCall { +fn ctr(asset_addr: Address) -> constructorCall { Erc20WrapperExample::constructorCall { - underlyingToken_: asset, + underlyingToken_: asset_addr, name_: WRAPPED_TOKEN_NAME.to_owned(), symbol_: WRAPPED_TOKEN_SYMBOL.to_owned(), } } -// ============================================================================ -// Integration Tests: ERC-20 Token + Metadata Extension + ERC-20 Wrapper -// ============================================================================ +async fn deploy( + account: &Account, + initial_tokens: U256, +) -> Result<(Address, Address)> { + let asset_addr = erc20::deploy(&account.wallet).await?; -#[e2e::test] -async fn constructs(alice: Account) -> Result<()> { - let asset_address = erc20::deploy(&alice.wallet).await?; - let contract_addr = alice + let contract_addr = account .as_deployer() - .with_constructor(ctr(asset_address)) + .with_constructor(ctr(asset_addr)) .deploy() .await? .address()?; - let contract = Erc20Wrapper::new(contract_addr, &alice.wallet); - let name = contract.name().call().await?.name; - let symbol = contract.symbol().call().await?.symbol; - let decimals = contract.decimals().call().await?.decimals; - assert_eq!(name, WRAPPED_TOKEN_NAME.to_owned()); - assert_eq!(symbol, WRAPPED_TOKEN_SYMBOL.to_owned()); - assert_eq!(decimals, 18); + // Mint initial tokens to the vault + if initial_tokens > U256::ZERO { + let asset = ERC20Mock::new(asset_addr, &account.wallet); + _ = watch!(asset.mint(contract_addr, initial_tokens))?; + } + + Ok((contract_addr, asset_addr)) +} + +// ============================================================================ +// Integration Tests: ERC-20 Token + Metadata Extension + ERC-20 Wrapper +// ============================================================================ + +mod constructor { + + use super::*; + + #[e2e::test] + async fn success(alice: Account) -> Result<()> { + let asset_address = erc20::deploy(&alice.wallet).await?; + let contract_addr = alice + .as_deployer() + .with_constructor(ctr(asset_address)) + .deploy() + .await? + .address()?; + let contract = Erc20Wrapper::new(contract_addr, &alice.wallet); + let name = contract.name().call().await?.name; + assert_eq!(name, WRAPPED_TOKEN_NAME.to_owned()); + + let symbol = contract.symbol().call().await?.symbol; + assert_eq!(symbol, WRAPPED_TOKEN_SYMBOL.to_owned()); + + let decimals = contract.decimals().call().await?.decimals; + assert_eq!(decimals, 18); + + Ok(()) + } +} + +mod deposit_to { - Ok(()) + use super::*; + + #[e2e::test] + async fn success(alice: Account) -> Result<()> { + let (contract_addr, _) = deploy(&alice, U256::ZERO).await?; + let contract = Erc20Wrapper::new(contract_addr, &alice.wallet); + Ok(()) + } + + #[e2e::test] + async fn reverts_for_invalid_receiver(alice: Account) -> Result<()> { + Ok(()) + } + + #[e2e::test] + async fn reverts_for_invalid_sender(alice: Account) -> Result<()> { + Ok(()) + } + + #[e2e::test] + async fn reflects_balance_after_withdraw_for(alice: Account) -> Result<()> { + Ok(()) + } +} + +mod withdraw_to { + use super::*; + + #[e2e::test] + async fn success(alice: Account) -> Result<()> { + let (contract_addr, _) = deploy(&alice, U256::ZERO).await?; + let contract = Erc20Wrapper::new(contract_addr, &alice.wallet); + Ok(()) + } + + #[e2e::test] + async fn reverts_for_invalid_receiver(alice: Account) -> Result<()> { + Ok(()) + } + + #[e2e::test] + async fn reverts_for_invalid_sender(alice: Account) -> Result<()> { + Ok(()) + } + + #[e2e::test] + async fn reflects_balance_after_deposit_for(alice: Account) -> Result<()> { + Ok(()) + } } From d68070385bd2addac8a9bb32f5a625abfe737aa7 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Tue, 4 Feb 2025 13:47:21 +0000 Subject: [PATCH 30/46] chore: function name fixes --- .../src/token/erc20/extensions/wrapper.rs | 16 +++++----- examples/erc20-wrapper/src/lib.rs | 4 +-- examples/erc20-wrapper/tests/abi/mod.rs | 4 +++ examples/erc20-wrapper/tests/erc20wrapper.rs | 30 +++++++++++++++++-- 4 files changed, 43 insertions(+), 11 deletions(-) diff --git a/contracts/src/token/erc20/extensions/wrapper.rs b/contracts/src/token/erc20/extensions/wrapper.rs index 38b348d47..3d61a3585 100644 --- a/contracts/src/token/erc20/extensions/wrapper.rs +++ b/contracts/src/token/erc20/extensions/wrapper.rs @@ -136,7 +136,7 @@ pub trait IERC20Wrapper { /// [`Error::InvalidSender`] is returned. /// * If the receiver address is `contract:address()` or invalid, /// [`Error::InvalidReceiver`] is returned. - fn deposit_to( + fn deposit_for( &mut self, account: Address, value: U256, @@ -177,7 +177,7 @@ impl IERC20Wrapper for Erc20Wrapper { self.underlying_address.get() } - fn deposit_to( + fn deposit_for( &mut self, account: Address, value: U256, @@ -185,17 +185,19 @@ impl IERC20Wrapper for Erc20Wrapper { ) -> Result { let underlined_token = self.underlying_address.get(); let sender = msg::sender(); - if account == contract::address() { - return Err(Error::InvalidSender(ERC20InvalidSender { - sender: contract::address(), - })); - } 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( underlined_token, sender, diff --git a/examples/erc20-wrapper/src/lib.rs b/examples/erc20-wrapper/src/lib.rs index e54dfc2eb..5f1519d53 100644 --- a/examples/erc20-wrapper/src/lib.rs +++ b/examples/erc20-wrapper/src/lib.rs @@ -28,12 +28,12 @@ impl Erc20WrapperExample { self.wrapper.underlying() } - fn deposit_to( + fn deposit_for( &mut self, account: Address, value: U256, ) -> Result> { - Ok(self.wrapper.deposit_to(account, value, &mut self.erc20)?) + Ok(self.wrapper.deposit_for(account, value, &mut self.erc20)?) } fn withdraw_to( diff --git a/examples/erc20-wrapper/tests/abi/mod.rs b/examples/erc20-wrapper/tests/abi/mod.rs index 0676af51d..5b1b8e7de 100644 --- a/examples/erc20-wrapper/tests/abi/mod.rs +++ b/examples/erc20-wrapper/tests/abi/mod.rs @@ -14,9 +14,13 @@ sol!( function approve(address spender, uint256 amount) external returns (bool); function transferFrom(address sender, address recipient, uint256 amount) external returns (bool); + #[derive(Debug)] function decimals() external view returns (uint8 decimals); + #[derive(Debug)] function underlying() external view returns (address underlying); + #[derive(Debug)] function depositFor(address account, uint256 value) external returns (bool); + #[derive(Debug)] function withdrawTo(address account, uint256 value) external returns (bool); error ERC20InvalidUnderlying(address token); diff --git a/examples/erc20-wrapper/tests/erc20wrapper.rs b/examples/erc20-wrapper/tests/erc20wrapper.rs index 82a2d59de..6c1ebe847 100644 --- a/examples/erc20-wrapper/tests/erc20wrapper.rs +++ b/examples/erc20-wrapper/tests/erc20wrapper.rs @@ -86,22 +86,48 @@ mod constructor { mod deposit_to { + use std::println; + use super::*; #[e2e::test] - async fn success(alice: Account) -> Result<()> { - let (contract_addr, _) = deploy(&alice, U256::ZERO).await?; + async fn executes_with_approval(alice: Account) -> Result<()> { + let (contract_addr, asset) = deploy(&alice, U256::ZERO).await?; + let asset = ERC20Mock::new(asset, &alice.wallet); let contract = Erc20Wrapper::new(contract_addr, &alice.wallet); + Ok(()) } #[e2e::test] async fn reverts_for_invalid_receiver(alice: Account) -> Result<()> { + let (contract_addr, _) = deploy(&alice, U256::ZERO).await?; + let contract = Erc20Wrapper::new(contract_addr, &alice.wallet); + let err = contract + .depositFor(contract_addr, uint!(1000_U256)) + .call() + .await + .expect_err("should return `InvalidSender`"); + println!("err: {}", err); + assert!(err.reverted_with(Erc20Wrapper::ERC20InvalidSender { + sender: contract_addr + })); Ok(()) } #[e2e::test] async fn reverts_for_invalid_sender(alice: Account) -> Result<()> { + let alice_addr: Address = alice.address(); + let (contract_addr, _) = deploy(&alice, U256::ZERO).await?; + let contract = Erc20Wrapper::new(contract_addr, &alice.wallet); + // let err = contract + // .depositFor(alice.address(), uint!(1000_U256)) + // .call() + // .await + // .expect_err("should return `InvalidReceiver`"); + // assert!(err.reverted_with(Erc20Wrapper::ERC20InvalidSender { + // sender: alice_addr + // })); Ok(()) } From 9781c4eb227dd21b50d289174bd2d33581aee2b6 Mon Sep 17 00:00:00 2001 From: "ifechukwudaniel.eth" Date: Wed, 5 Feb 2025 12:00:28 +0000 Subject: [PATCH 31/46] Update contracts/src/token/erc20/extensions/mod.rs Co-authored-by: Daniel Bigos --- contracts/src/token/erc20/extensions/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/token/erc20/extensions/mod.rs b/contracts/src/token/erc20/extensions/mod.rs index 27dc58ab7..b72b76fca 100644 --- a/contracts/src/token/erc20/extensions/mod.rs +++ b/contracts/src/token/erc20/extensions/mod.rs @@ -13,4 +13,4 @@ pub use erc4626::{Erc4626, IErc4626}; pub use flash_mint::{Erc20FlashMint, IErc3156FlashLender}; pub use metadata::{Erc20Metadata, IErc20Metadata}; pub use permit::Erc20Permit; -pub use wrapper::{Erc20Wrapper, IERC20Wrapper}; +pub use wrapper::{Erc20Wrapper, IErc20Wrapper}; From a99faa42aada8738e62921db8d8bd358b19609b5 Mon Sep 17 00:00:00 2001 From: "ifechukwudaniel.eth" Date: Wed, 5 Feb 2025 12:09:14 +0000 Subject: [PATCH 32/46] Update contracts/src/token/erc20/extensions/wrapper.rs Co-authored-by: Daniel Bigos --- contracts/src/token/erc20/extensions/wrapper.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/token/erc20/extensions/wrapper.rs b/contracts/src/token/erc20/extensions/wrapper.rs index 3d61a3585..17e53832a 100644 --- a/contracts/src/token/erc20/extensions/wrapper.rs +++ b/contracts/src/token/erc20/extensions/wrapper.rs @@ -114,7 +114,7 @@ pub struct Erc20Wrapper { } /// ERC-20 Wrapper Standard Interface -pub trait IERC20Wrapper { +pub trait IErc20Wrapper { /// The error type associated to this `ERC20Wrapper` trait implementation. type Error: Into>; From afc75a39ec9c32f0dc8ce3094d972a4d08c3fc02 Mon Sep 17 00:00:00 2001 From: "ifechukwudaniel.eth" Date: Wed, 5 Feb 2025 12:09:30 +0000 Subject: [PATCH 33/46] Update contracts/src/token/erc20/extensions/wrapper.rs Co-authored-by: Daniel Bigos --- contracts/src/token/erc20/extensions/wrapper.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/token/erc20/extensions/wrapper.rs b/contracts/src/token/erc20/extensions/wrapper.rs index 17e53832a..8d9924d96 100644 --- a/contracts/src/token/erc20/extensions/wrapper.rs +++ b/contracts/src/token/erc20/extensions/wrapper.rs @@ -115,7 +115,7 @@ pub struct Erc20Wrapper { /// ERC-20 Wrapper Standard Interface pub trait IErc20Wrapper { - /// The error type associated to this `ERC20Wrapper` trait implementation. + /// The error type associated to the` trait implementation. type Error: Into>; /// Returns the address of the underlying token that is been wrapped. From cd0259ab7a15a0175b28828d93cdabe203df8766 Mon Sep 17 00:00:00 2001 From: "ifechukwudaniel.eth" Date: Wed, 5 Feb 2025 12:10:34 +0000 Subject: [PATCH 34/46] Update contracts/src/token/erc20/extensions/wrapper.rs Co-authored-by: Daniel Bigos --- contracts/src/token/erc20/extensions/wrapper.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/token/erc20/extensions/wrapper.rs b/contracts/src/token/erc20/extensions/wrapper.rs index 8d9924d96..980ce06bc 100644 --- a/contracts/src/token/erc20/extensions/wrapper.rs +++ b/contracts/src/token/erc20/extensions/wrapper.rs @@ -221,7 +221,7 @@ impl IERC20Wrapper for Erc20Wrapper { })); } erc20._burn(account, value)?; - self.safe_erc20.safe_transfer(underlined_token, account, value)?; + self.safe_erc20.safe_transfer(self.underlying(), account, value)?; Ok(true) } } From 2535fefec9a493ced7d7dc2ac94b5894849ec2b6 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Wed, 5 Feb 2025 19:28:29 +0000 Subject: [PATCH 35/46] chore: test fixes test --- .../src/token/erc20/extensions/wrapper.rs | 32 ++------- examples/erc20-wrapper/src/lib.rs | 2 +- examples/erc20-wrapper/tests/abi/mod.rs | 2 + examples/erc20-wrapper/tests/erc20wrapper.rs | 71 +++++++++++++++---- 4 files changed, 65 insertions(+), 42 deletions(-) diff --git a/contracts/src/token/erc20/extensions/wrapper.rs b/contracts/src/token/erc20/extensions/wrapper.rs index 980ce06bc..264f2a1d9 100644 --- a/contracts/src/token/erc20/extensions/wrapper.rs +++ b/contracts/src/token/erc20/extensions/wrapper.rs @@ -23,32 +23,10 @@ use stylus_sdk::{ use crate::token::erc20::{ self, - utils::{ - safe_erc20::{self, ISafeErc20}, - SafeErc20, - }, - Erc20, + utils::{safe_erc20, IErc20 as IErc20Solidity, ISafeErc20, SafeErc20}, + Erc20, IErc20, }; -mod token { - - #![allow(missing_docs)] - #![cfg_attr(coverage_nightly, coverage(off))] - - use alloc::vec; - - use stylus_sdk::stylus_proc::sol_interface; - - sol_interface! { - /// Solidity Interface of the ERC-20 token. - interface IErc20 { - function balanceOf(address account) external view returns (uint256); - function totalSupply() external view returns (uint256); - } - } -} - -use token::IErc20 as IErc20Solidity; sol! { /// Indicates that he address is not a valid ERC-20 token. @@ -170,7 +148,7 @@ pub trait IErc20Wrapper { /// BorrowMut)`. Should be fixed in the future by the Stylus team. unsafe impl TopLevelStorage for Erc20Wrapper {} -impl IERC20Wrapper for Erc20Wrapper { +impl IErc20Wrapper for Erc20Wrapper { type Error = Error; fn underlying(&self) -> Address { @@ -248,8 +226,8 @@ impl Erc20Wrapper { account: Address, erc20: &mut Erc20, ) -> Result { - let token = IErc20Solidity::new(self.underlying()); - let value = token + 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)?; diff --git a/examples/erc20-wrapper/src/lib.rs b/examples/erc20-wrapper/src/lib.rs index 5f1519d53..10610ffae 100644 --- a/examples/erc20-wrapper/src/lib.rs +++ b/examples/erc20-wrapper/src/lib.rs @@ -5,7 +5,7 @@ use alloc::vec::Vec; use alloy_primitives::{Address, U256}; use openzeppelin_stylus::token::erc20::{ - extensions::{Erc20Metadata, Erc20Wrapper, IERC20Wrapper}, + extensions::{Erc20Metadata, Erc20Wrapper, IErc20Wrapper}, Erc20, }; use stylus_sdk::prelude::{entrypoint, public, storage}; diff --git a/examples/erc20-wrapper/tests/abi/mod.rs b/examples/erc20-wrapper/tests/abi/mod.rs index 5b1b8e7de..ffdc04d00 100644 --- a/examples/erc20-wrapper/tests/abi/mod.rs +++ b/examples/erc20-wrapper/tests/abi/mod.rs @@ -23,6 +23,8 @@ sol!( #[derive(Debug)] function withdrawTo(address account, uint256 value) external returns (bool); + error SafeErc20FailedOperation(address token); + error ERC20InvalidUnderlying(address token); error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed); diff --git a/examples/erc20-wrapper/tests/erc20wrapper.rs b/examples/erc20-wrapper/tests/erc20wrapper.rs index 6c1ebe847..2cfdb4828 100644 --- a/examples/erc20-wrapper/tests/erc20wrapper.rs +++ b/examples/erc20-wrapper/tests/erc20wrapper.rs @@ -86,21 +86,26 @@ mod constructor { mod deposit_to { - use std::println; - use super::*; #[e2e::test] async fn executes_with_approval(alice: Account) -> Result<()> { - let (contract_addr, asset) = deploy(&alice, U256::ZERO).await?; - let asset = ERC20Mock::new(asset, &alice.wallet); + let (contract_addr, asset_addr) = deploy(&alice, U256::ZERO).await?; + let alice_address = alice.address(); + let asset = ERC20Mock::new(asset_addr, &alice.wallet); let contract = Erc20Wrapper::new(contract_addr, &alice.wallet); - + _ = watch!(asset.mint(alice.address(), uint!(1000_U256)))?; + _ = asset.regular_approve( + alice_address, + contract_addr, + uint!(1000_U256), + ); + ///_ = watch!(contract.depositFor(alice_address, uint!(1000_U256)))?; Ok(()) } #[e2e::test] - async fn reverts_for_invalid_receiver(alice: Account) -> Result<()> { + async fn reverts_for_invalid_sender(alice: Account) -> Result<()> { let (contract_addr, _) = deploy(&alice, U256::ZERO).await?; let contract = Erc20Wrapper::new(contract_addr, &alice.wallet); let err = contract @@ -108,7 +113,6 @@ mod deposit_to { .call() .await .expect_err("should return `InvalidSender`"); - println!("err: {}", err); assert!(err.reverted_with(Erc20Wrapper::ERC20InvalidSender { sender: contract_addr })); @@ -116,7 +120,7 @@ mod deposit_to { } #[e2e::test] - async fn reverts_for_invalid_sender(alice: Account) -> Result<()> { + async fn reverts_minting_to_wrapper_contract(alice: Account) -> Result<()> { let alice_addr: Address = alice.address(); let (contract_addr, _) = deploy(&alice, U256::ZERO).await?; let contract = Erc20Wrapper::new(contract_addr, &alice.wallet); @@ -132,7 +136,41 @@ mod deposit_to { } #[e2e::test] - async fn reflects_balance_after_withdraw_for(alice: Account) -> Result<()> { + async fn reverts_when_missing_approval(alice: Account) -> Result<()> { + let (contract_addr, asset_addr) = deploy(&alice, U256::ZERO).await?; + let alice_address = alice.address(); + let contract = Erc20Wrapper::new(contract_addr, &alice.wallet); + let err = contract + .depositFor(alice_address, uint!(1000_U256)) + .call() + .await + .expect_err("should return `SafeErc20FailedOperation`"); + assert!(err.reverted_with(Erc20Wrapper::SafeErc20FailedOperation { + token: asset_addr + })); + Ok(()) + } + + #[e2e::test] + async fn reverts_when_inssuficient_balance(alice: Account) -> Result<()> { + let (contract_addr, asset_addr) = deploy(&alice, U256::ZERO).await?; + let alice_address = alice.address(); + let contract = Erc20Wrapper::new(contract_addr, &alice.wallet); + let err = contract + .depositFor(alice_address, uint!(1000_U256)) + .call() + .await + .expect_err("should return `ERC20InsufficientBalance`"); + // assert!(err.reverted_with(Erc20Wrapper::ERC20InsufficientBalance { + // sender: alice_address, + // balance: uint!(0_U256) + // needed: uint!(1000_U256) + // })); + Ok(()) + } + + #[e2e::test] + async fn reflects_balance_after_deposit_for(alice: Account) -> Result<()> { Ok(()) } } @@ -147,13 +185,18 @@ mod withdraw_to { Ok(()) } - #[e2e::test] - async fn reverts_for_invalid_receiver(alice: Account) -> Result<()> { - Ok(()) - } - #[e2e::test] async fn reverts_for_invalid_sender(alice: Account) -> Result<()> { + let (contract_addr, _) = deploy(&alice, U256::ZERO).await?; + let contract = Erc20Wrapper::new(contract_addr, &alice.wallet); + let err = contract + .withdrawTo(contract_addr, uint!(1000_U256)) + .call() + .await + .expect_err("should return `InvalidReciver`"); + assert!(err.reverted_with(Erc20Wrapper::ERC20InvalidReceiver { + receiver: contract_addr + })); Ok(()) } From 0b92aabaac394eb03ffa3afa6d043d5aabfc5d5a Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Wed, 5 Feb 2025 20:41:56 +0000 Subject: [PATCH 36/46] chore: fixes --- contracts/src/token/erc20/extensions/wrapper.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/token/erc20/extensions/wrapper.rs b/contracts/src/token/erc20/extensions/wrapper.rs index 264f2a1d9..3f8de69a8 100644 --- a/contracts/src/token/erc20/extensions/wrapper.rs +++ b/contracts/src/token/erc20/extensions/wrapper.rs @@ -241,7 +241,7 @@ mod tests { use alloy_primitives::address; use stylus_sdk::prelude::storage; - use super::{Erc20Wrapper, IERC20Wrapper}; + use super::{Erc20Wrapper, IErc20Wrapper}; #[storage] struct Erc20WrapperTestExample { From 7a31409a6e0fc2bc62f26d5066ed5b783811e2c7 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Wed, 5 Feb 2025 20:43:25 +0000 Subject: [PATCH 37/46] chore: fmt --- contracts/src/token/erc20/extensions/wrapper.rs | 1 - examples/erc20-wrapper/tests/abi/mod.rs | 2 +- examples/erc20-wrapper/tests/erc20wrapper.rs | 4 ++-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/contracts/src/token/erc20/extensions/wrapper.rs b/contracts/src/token/erc20/extensions/wrapper.rs index 3f8de69a8..93779458e 100644 --- a/contracts/src/token/erc20/extensions/wrapper.rs +++ b/contracts/src/token/erc20/extensions/wrapper.rs @@ -27,7 +27,6 @@ use crate::token::erc20::{ Erc20, IErc20, }; - sol! { /// Indicates that he address is not a valid ERC-20 token. /// diff --git a/examples/erc20-wrapper/tests/abi/mod.rs b/examples/erc20-wrapper/tests/abi/mod.rs index ffdc04d00..5b86ec451 100644 --- a/examples/erc20-wrapper/tests/abi/mod.rs +++ b/examples/erc20-wrapper/tests/abi/mod.rs @@ -24,7 +24,7 @@ sol!( function withdrawTo(address account, uint256 value) external returns (bool); error SafeErc20FailedOperation(address token); - + error ERC20InvalidUnderlying(address token); error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed); diff --git a/examples/erc20-wrapper/tests/erc20wrapper.rs b/examples/erc20-wrapper/tests/erc20wrapper.rs index 2cfdb4828..341957290 100644 --- a/examples/erc20-wrapper/tests/erc20wrapper.rs +++ b/examples/erc20-wrapper/tests/erc20wrapper.rs @@ -152,8 +152,8 @@ mod deposit_to { } #[e2e::test] - async fn reverts_when_inssuficient_balance(alice: Account) -> Result<()> { - let (contract_addr, asset_addr) = deploy(&alice, U256::ZERO).await?; + async fn reverts_when_inssuficient_balance(alice: Account) -> Result<()> { + let (contract_addr, asset_addr) = deploy(&alice, U256::ZERO).await?; let alice_address = alice.address(); let contract = Erc20Wrapper::new(contract_addr, &alice.wallet); let err = contract From bc8aa8f9d27ab2fed263611a18b978ee44440e8b Mon Sep 17 00:00:00 2001 From: "ifechukwudaniel.eth" Date: Wed, 5 Feb 2025 20:46:59 +0000 Subject: [PATCH 38/46] Update contracts/src/token/erc20/extensions/wrapper.rs Co-authored-by: Daniel Bigos --- contracts/src/token/erc20/extensions/wrapper.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/src/token/erc20/extensions/wrapper.rs b/contracts/src/token/erc20/extensions/wrapper.rs index 93779458e..6642a952b 100644 --- a/contracts/src/token/erc20/extensions/wrapper.rs +++ b/contracts/src/token/erc20/extensions/wrapper.rs @@ -176,7 +176,7 @@ impl IErc20Wrapper for Erc20Wrapper { } self.safe_erc20.safe_transfer_from( - underlined_token, + self.underlying(), sender, contract::address(), value, From c9a171110d0981711f1031ac942d5e26ac95e949 Mon Sep 17 00:00:00 2001 From: "ifechukwudaniel.eth" Date: Wed, 5 Feb 2025 20:50:45 +0000 Subject: [PATCH 39/46] Update contracts/src/token/erc20/extensions/wrapper.rs Co-authored-by: Daniel Bigos --- contracts/src/token/erc20/extensions/wrapper.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/src/token/erc20/extensions/wrapper.rs b/contracts/src/token/erc20/extensions/wrapper.rs index 6642a952b..1ad91d8e7 100644 --- a/contracts/src/token/erc20/extensions/wrapper.rs +++ b/contracts/src/token/erc20/extensions/wrapper.rs @@ -160,7 +160,6 @@ impl IErc20Wrapper for Erc20Wrapper { value: U256, erc20: &mut Erc20, ) -> Result { - let underlined_token = self.underlying_address.get(); let sender = msg::sender(); if sender == contract::address() { From 6fd48663d8054a7bdd2c066fefb8e474e1e0c7f7 Mon Sep 17 00:00:00 2001 From: "ifechukwudaniel.eth" Date: Wed, 5 Feb 2025 21:42:48 +0000 Subject: [PATCH 40/46] Update contracts/src/token/erc20/extensions/wrapper.rs Co-authored-by: Daniel Bigos --- contracts/src/token/erc20/extensions/wrapper.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/src/token/erc20/extensions/wrapper.rs b/contracts/src/token/erc20/extensions/wrapper.rs index 1ad91d8e7..0f31ef177 100644 --- a/contracts/src/token/erc20/extensions/wrapper.rs +++ b/contracts/src/token/erc20/extensions/wrapper.rs @@ -190,7 +190,6 @@ impl IErc20Wrapper for Erc20Wrapper { value: U256, erc20: &mut Erc20, ) -> Result { - let underlined_token = self.underlying_address.get(); if account == contract::address() { return Err(Error::InvalidReceiver(ERC20InvalidReceiver { receiver: account, From 5737ffb9f0db4026c6b09885f13cea2a0e3f3ba0 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Wed, 5 Feb 2025 20:53:04 +0000 Subject: [PATCH 41/46] chore: move erc20Wrapper to unreleased section --- CHANGELOG.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7cfca3bc0..1b979502b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,8 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ### Added - -- +- `Erc20Wrapper` "Token Wrapping contract". #498 ### Changed @@ -34,7 +33,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Implement `mul_div` for `U256`. #465 - Implement `AddAssignChecked` for `StorageUint`. #474 - `Erc20FlashMint` extension. #407 -- `Erc20Wrapper` "Token Wrapping contract". #498 + ### Changed From 3fb1c194953aca39a8a0b1e9c91c29ad279457ac Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Thu, 6 Feb 2025 23:58:09 +0000 Subject: [PATCH 42/46] chore: tests and constructor --- .../src/token/erc20/extensions/wrapper.rs | 4 +-- examples/erc20-wrapper/src/constructor.sol | 9 ++--- examples/erc20-wrapper/tests/erc20wrapper.rs | 36 +++++++++++-------- 3 files changed, 28 insertions(+), 21 deletions(-) diff --git a/contracts/src/token/erc20/extensions/wrapper.rs b/contracts/src/token/erc20/extensions/wrapper.rs index 0f31ef177..bac7c2366 100644 --- a/contracts/src/token/erc20/extensions/wrapper.rs +++ b/contracts/src/token/erc20/extensions/wrapper.rs @@ -24,7 +24,7 @@ use stylus_sdk::{ use crate::token::erc20::{ self, utils::{safe_erc20, IErc20 as IErc20Solidity, ISafeErc20, SafeErc20}, - Erc20, IErc20, + Erc20 }; sol! { @@ -49,7 +49,7 @@ sol! { #[allow(missing_docs)] error InvalidAsset(address asset); - /// Indicates thata the address is not an invalid receiver addresss. + /// Indicates that the address is not an invalid receiver addresss. /// /// * `receiver` - Address of the invalid receiver. #[derive(Debug)] diff --git a/examples/erc20-wrapper/src/constructor.sol b/examples/erc20-wrapper/src/constructor.sol index 8f79d3645..8f642b3da 100644 --- a/examples/erc20-wrapper/src/constructor.sol +++ b/examples/erc20-wrapper/src/constructor.sol @@ -2,19 +2,20 @@ pragma solidity ^0.8.21; contract Erc20WrapperExample { - + // Erc20 Token Storage mapping(address account => uint256) private _balances; mapping(address account => mapping(address spender => uint256)) private _allowances; uint256 private _totalSupply; + + // Erc20 Metadata Storage string private _name; string private _symbol; - - + + // Erc20 Wrapper Storage address private _underlyingToken; - constructor(string memory name_, string memory symbol_, address underlyingToken_) { _name = name_; _symbol = symbol_; diff --git a/examples/erc20-wrapper/tests/erc20wrapper.rs b/examples/erc20-wrapper/tests/erc20wrapper.rs index 341957290..80474b3fc 100644 --- a/examples/erc20-wrapper/tests/erc20wrapper.rs +++ b/examples/erc20-wrapper/tests/erc20wrapper.rs @@ -100,7 +100,7 @@ mod deposit_to { contract_addr, uint!(1000_U256), ); - ///_ = watch!(contract.depositFor(alice_address, uint!(1000_U256)))?; + _ = watch!(contract.depositFor(alice_address, uint!(1000_U256)))?; Ok(()) } @@ -124,14 +124,14 @@ mod deposit_to { let alice_addr: Address = alice.address(); let (contract_addr, _) = deploy(&alice, U256::ZERO).await?; let contract = Erc20Wrapper::new(contract_addr, &alice.wallet); - // let err = contract - // .depositFor(alice.address(), uint!(1000_U256)) - // .call() - // .await - // .expect_err("should return `InvalidReceiver`"); - // assert!(err.reverted_with(Erc20Wrapper::ERC20InvalidSender { - // sender: alice_addr - // })); + let err = contract + .depositFor(alice.address(), uint!(1000_U256)) + .call() + .await + .expect_err("should return `InvalidReceiver`"); + assert!(err.reverted_with(Erc20Wrapper::ERC20InvalidSender { + sender: alice_addr + })); Ok(()) } @@ -161,16 +161,19 @@ mod deposit_to { .call() .await .expect_err("should return `ERC20InsufficientBalance`"); - // assert!(err.reverted_with(Erc20Wrapper::ERC20InsufficientBalance { - // sender: alice_address, - // balance: uint!(0_U256) - // needed: uint!(1000_U256) - // })); + assert!(err.reverted_with(Erc20Wrapper::ERC20InsufficientBalance { + sender: alice_address, + balance: uint!(0_U256), + needed: uint!(1000_U256) + })); Ok(()) } #[e2e::test] async fn reflects_balance_after_deposit_for(alice: Account) -> Result<()> { + let (contract_addr, asset_addr) = deploy(&alice, U256::ZERO).await?; + let alice_address = alice.address(); + let contract = Erc20Wrapper::new(contract_addr, &alice.wallet); Ok(()) } } @@ -201,7 +204,10 @@ mod withdraw_to { } #[e2e::test] - async fn reflects_balance_after_deposit_for(alice: Account) -> Result<()> { + async fn reflects_balance_after_withdraw_to(alice: Account, bob: Account) -> Result<()> { + let (contract_addr, asset_addr) = deploy(&alice, U256::ZERO).await?; + let alice_address = alice.address(); + let contract = Erc20Wrapper::new(contract_addr, &alice.wallet); Ok(()) } } From 5f0dcaef1937b0f1738b0db79c39722231ee409e Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Fri, 7 Feb 2025 00:02:42 +0000 Subject: [PATCH 43/46] chore: fmt --- contracts/src/token/erc20/extensions/wrapper.rs | 2 +- examples/erc20-wrapper/tests/erc20wrapper.rs | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/contracts/src/token/erc20/extensions/wrapper.rs b/contracts/src/token/erc20/extensions/wrapper.rs index bac7c2366..b18212cb8 100644 --- a/contracts/src/token/erc20/extensions/wrapper.rs +++ b/contracts/src/token/erc20/extensions/wrapper.rs @@ -24,7 +24,7 @@ use stylus_sdk::{ use crate::token::erc20::{ self, utils::{safe_erc20, IErc20 as IErc20Solidity, ISafeErc20, SafeErc20}, - Erc20 + Erc20, }; sol! { diff --git a/examples/erc20-wrapper/tests/erc20wrapper.rs b/examples/erc20-wrapper/tests/erc20wrapper.rs index 80474b3fc..634f70754 100644 --- a/examples/erc20-wrapper/tests/erc20wrapper.rs +++ b/examples/erc20-wrapper/tests/erc20wrapper.rs @@ -204,8 +204,11 @@ mod withdraw_to { } #[e2e::test] - async fn reflects_balance_after_withdraw_to(alice: Account, bob: Account) -> Result<()> { - let (contract_addr, asset_addr) = deploy(&alice, U256::ZERO).await?; + async fn reflects_balance_after_withdraw_to( + alice: Account, + bob: Account, + ) -> Result<()> { + let (contract_addr, asset_addr) = deploy(&alice, U256::ZERO).await?; let alice_address = alice.address(); let contract = Erc20Wrapper::new(contract_addr, &alice.wallet); Ok(()) From 452a405974095f3aa4eed7c980de64ad198120c4 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Sun, 9 Feb 2025 16:53:23 +0000 Subject: [PATCH 44/46] format --- contracts/src/token/erc20/extensions/wrapper.rs | 1 + examples/erc20-wrapper/tests/erc20wrapper.rs | 11 +++++------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/contracts/src/token/erc20/extensions/wrapper.rs b/contracts/src/token/erc20/extensions/wrapper.rs index b18212cb8..9531facb9 100644 --- a/contracts/src/token/erc20/extensions/wrapper.rs +++ b/contracts/src/token/erc20/extensions/wrapper.rs @@ -181,6 +181,7 @@ impl IErc20Wrapper for Erc20Wrapper { value, )?; erc20._mint(account, value)?; + Ok(true) } diff --git a/examples/erc20-wrapper/tests/erc20wrapper.rs b/examples/erc20-wrapper/tests/erc20wrapper.rs index 634f70754..f8175d524 100644 --- a/examples/erc20-wrapper/tests/erc20wrapper.rs +++ b/examples/erc20-wrapper/tests/erc20wrapper.rs @@ -94,12 +94,11 @@ mod deposit_to { let alice_address = alice.address(); let asset = ERC20Mock::new(asset_addr, &alice.wallet); let contract = Erc20Wrapper::new(contract_addr, &alice.wallet); - _ = watch!(asset.mint(alice.address(), uint!(1000_U256)))?; - _ = asset.regular_approve( - alice_address, - contract_addr, - uint!(1000_U256), - ); + + // _ = watch!(asset.mint(alice.address(), uint!(1000_U256)))?; + + _ = asset.approve(alice_address, uint!(1000_U256)); + _ = watch!(contract.depositFor(alice_address, uint!(1000_U256)))?; Ok(()) } From e97fea9f1fe9e126dda889556ae4af2f95ee0938 Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Wed, 12 Feb 2025 03:15:34 +0000 Subject: [PATCH 45/46] chore:test changes --- .../src/token/erc20/extensions/wrapper.rs | 2 +- examples/erc20-wrapper/tests/abi/mod.rs | 1 + examples/erc20-wrapper/tests/erc20wrapper.rs | 30 +++++++++++++++---- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/contracts/src/token/erc20/extensions/wrapper.rs b/contracts/src/token/erc20/extensions/wrapper.rs index 9531facb9..0deaa2683 100644 --- a/contracts/src/token/erc20/extensions/wrapper.rs +++ b/contracts/src/token/erc20/extensions/wrapper.rs @@ -172,7 +172,7 @@ impl IErc20Wrapper for Erc20Wrapper { return Err(Error::InvalidSender(ERC20InvalidSender { sender: contract::address(), })); - } + } self.safe_erc20.safe_transfer_from( self.underlying(), diff --git a/examples/erc20-wrapper/tests/abi/mod.rs b/examples/erc20-wrapper/tests/abi/mod.rs index 5b86ec451..f6967231d 100644 --- a/examples/erc20-wrapper/tests/abi/mod.rs +++ b/examples/erc20-wrapper/tests/abi/mod.rs @@ -32,6 +32,7 @@ sol!( error ERC20InvalidReceiver(address receiver); error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed); error ERC20InvalidSpender(address spender); + error InvalidAsset(address asset); #[derive(Debug, PartialEq)] event Transfer(address indexed from, address indexed to, uint256 value); diff --git a/examples/erc20-wrapper/tests/erc20wrapper.rs b/examples/erc20-wrapper/tests/erc20wrapper.rs index f8175d524..856343687 100644 --- a/examples/erc20-wrapper/tests/erc20wrapper.rs +++ b/examples/erc20-wrapper/tests/erc20wrapper.rs @@ -88,18 +88,38 @@ mod deposit_to { use super::*; + #[e2e::test] - async fn executes_with_approval(alice: Account) -> Result<()> { + async fn executes_with_approval(alice: Account, bob: Account) -> Result<()> { + let initial_supply = uint!(1000_U256); let (contract_addr, asset_addr) = deploy(&alice, U256::ZERO).await?; let alice_address = alice.address(); let asset = ERC20Mock::new(asset_addr, &alice.wallet); let contract = Erc20Wrapper::new(contract_addr, &alice.wallet); - // _ = watch!(asset.mint(alice.address(), uint!(1000_U256)))?; + _ = watch!(asset.mint(alice_address, initial_supply))?; + _ = watch!(asset.approve(alice_address, initial_supply))?; + let receipt = send!(contract.depositFor(alice_address, initial_supply))?; + println!("receipt: {:#?}", receipt); + Ok(()) + } - _ = asset.approve(alice_address, uint!(1000_U256)); + #[e2e::test] + async fn reverts_when_invalid_asset(alice: Account) -> Result<()> { + let invalid_asset = alice.address(); + let contract_addr = alice + .as_deployer() + .with_constructor(ctr(invalid_asset)) + .deploy() + .await? + .address()?; + let contract = Erc20Wrapper::new(contract_addr, &alice.wallet); + let err = send!(contract.depositFor(invalid_asset, uint!(10_U256))) + .expect_err("should return `InvalidAsset`"); + // assert!( + // err.reverted_with(Erc20Wrapper::InvalidAsset { asset: invalid_asset }) + // ); - _ = watch!(contract.depositFor(alice_address, uint!(1000_U256)))?; Ok(()) } @@ -151,7 +171,7 @@ mod deposit_to { } #[e2e::test] - async fn reverts_when_inssuficient_balance(alice: Account) -> Result<()> { + async fn reverts_when_insuficient_balance(alice: Account) -> Result<()> { let (contract_addr, asset_addr) = deploy(&alice, U256::ZERO).await?; let alice_address = alice.address(); let contract = Erc20Wrapper::new(contract_addr, &alice.wallet); From 3802a5aff58a519f76f480eb33dfa8f3a7d71cde Mon Sep 17 00:00:00 2001 From: Angry Coding Date: Wed, 12 Feb 2025 15:21:14 +0000 Subject: [PATCH 46/46] chore: fmt --- contracts/src/token/erc20/extensions/wrapper.rs | 2 +- examples/erc20-wrapper/tests/erc20wrapper.rs | 15 +++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/contracts/src/token/erc20/extensions/wrapper.rs b/contracts/src/token/erc20/extensions/wrapper.rs index 0deaa2683..9531facb9 100644 --- a/contracts/src/token/erc20/extensions/wrapper.rs +++ b/contracts/src/token/erc20/extensions/wrapper.rs @@ -172,7 +172,7 @@ impl IErc20Wrapper for Erc20Wrapper { return Err(Error::InvalidSender(ERC20InvalidSender { sender: contract::address(), })); - } + } self.safe_erc20.safe_transfer_from( self.underlying(), diff --git a/examples/erc20-wrapper/tests/erc20wrapper.rs b/examples/erc20-wrapper/tests/erc20wrapper.rs index 856343687..9ba7ee8ca 100644 --- a/examples/erc20-wrapper/tests/erc20wrapper.rs +++ b/examples/erc20-wrapper/tests/erc20wrapper.rs @@ -88,9 +88,11 @@ mod deposit_to { use super::*; - #[e2e::test] - async fn executes_with_approval(alice: Account, bob: Account) -> Result<()> { + async fn executes_with_approval( + alice: Account, + bob: Account, + ) -> Result<()> { let initial_supply = uint!(1000_U256); let (contract_addr, asset_addr) = deploy(&alice, U256::ZERO).await?; let alice_address = alice.address(); @@ -99,12 +101,13 @@ mod deposit_to { _ = watch!(asset.mint(alice_address, initial_supply))?; _ = watch!(asset.approve(alice_address, initial_supply))?; - let receipt = send!(contract.depositFor(alice_address, initial_supply))?; + let receipt = + send!(contract.depositFor(alice_address, initial_supply))?; println!("receipt: {:#?}", receipt); Ok(()) } - #[e2e::test] + #[e2e::test] async fn reverts_when_invalid_asset(alice: Account) -> Result<()> { let invalid_asset = alice.address(); let contract_addr = alice @@ -117,8 +120,8 @@ mod deposit_to { let err = send!(contract.depositFor(invalid_asset, uint!(10_U256))) .expect_err("should return `InvalidAsset`"); // assert!( - // err.reverted_with(Erc20Wrapper::InvalidAsset { asset: invalid_asset }) - // ); + // err.reverted_with(Erc20Wrapper::InvalidAsset { asset: + // invalid_asset }) ); Ok(()) }