diff --git a/.gitignore b/.gitignore index c61302beb..ebc56178c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ dist *.tsbuildinfo node_modules - .env .env.local + +package.json diff --git a/packages/core/stylus/src/contract.ts b/packages/core/stylus/src/contract.ts index cad4f1a78..be8f4d7a1 100644 --- a/packages/core/stylus/src/contract.ts +++ b/packages/core/stylus/src/contract.ts @@ -35,6 +35,7 @@ export interface BaseImplementedTrait { * Lower numbers are higher priority, undefined is lowest priority. */ priority?: number; + omit_inherit?: boolean; } export interface ImplementedTrait extends BaseImplementedTrait { @@ -93,7 +94,7 @@ export class ContractBuilder implements Contract { get errors(): Error[] { return [...this.errorsMap.values()]; } - + get constants(): Variable[] { return [...this.constantsMap.values()]; } @@ -116,11 +117,8 @@ export class ContractBuilder implements Contract { return existingTrait; } else { const t: ImplementedTrait = { - name: baseTrait.name, + ...baseTrait, functions: [], - storage: baseTrait.storage, - section: baseTrait.section, - priority: baseTrait.priority, }; this.implementedTraitsMap.set(key, t); return t; diff --git a/packages/core/stylus/src/erc20.test.ts b/packages/core/stylus/src/erc20.test.ts index 9dbd12c36..907643d26 100644 --- a/packages/core/stylus/src/erc20.test.ts +++ b/packages/core/stylus/src/erc20.test.ts @@ -38,6 +38,17 @@ testERC20('erc20 burnable', { burnable: true, }); +testERC20('erc20 flash-mint', { + permit: false, + flashmint: true, +}); + +testERC20('erc20 burnable flash-mint', { + permit: false, + burnable: true, + flashmint: true, +}); + // testERC20('erc20 pausable', { // permit: false, // pausable: true, @@ -49,6 +60,12 @@ testERC20('erc20 burnable', { // pausable: true, // }); +// testERC20('erc20 flash-mint pausable', { +// permit: false, +// flashmint: true, +// pausable: true, +// }); + testERC20('erc20 permit', { permit: true, }); @@ -58,6 +75,11 @@ testERC20('erc20 permit burnable', { burnable: true, }); +testERC20('erc20 permit flash-mint', { + permit: true, + flashmint: true, +}); + // testERC20('erc20 permit pausable', { // permit: true, // pausable: true, @@ -69,10 +91,17 @@ testERC20('erc20 permit burnable', { // pausable: true, // }); +// testERC20('erc20 permit flash-mint pausable', { +// permit: true, +// flashmint: true, +// pausable: true, +// }); + testERC20('erc20 full - complex name', { name: 'Custom $ Token', burnable: true, permit: true, + flashmint: true, // pausable: true, }); @@ -87,6 +116,7 @@ testAPIEquivalence('erc20 API full', { name: 'CustomToken', burnable: true, permit: true, + flashmint: true, // pausable: true, }); diff --git a/packages/core/stylus/src/erc20.test.ts.md b/packages/core/stylus/src/erc20.test.ts.md index f1d704af3..9b97293ca 100644 --- a/packages/core/stylus/src/erc20.test.ts.md +++ b/packages/core/stylus/src/erc20.test.ts.md @@ -100,6 +100,100 @@ Generated by [AVA](https://avajs.dev). }␊ ` +## erc20 flash-mint + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Stylus ^0.2.0-alpha.3␊ + ␊ + #![cfg_attr(not(any(test, feature = "export-abi")), no_main)]␊ + extern crate alloc;␊ + ␊ + use openzeppelin_stylus::token::erc20::{Erc20, IErc20};␊ + use openzeppelin_stylus::token::erc20::extensions::{Erc20FlashMint, IErc3156FlashLender};␊ + use stylus_sdk::abi::Bytes;␊ + use stylus_sdk::prelude::{entrypoint, public, storage};␊ + ␊ + #[entrypoint]␊ + #[storage]␊ + struct MyToken {␊ + #[borrow]␊ + erc20: Erc20,␊ + #[borrow]␊ + flash_mint: Erc20FlashMint,␊ + }␊ + ␊ + #[public]␊ + #[inherit(Erc20)]␊ + impl MyToken {␊ + fn max_flash_loan(&self, token: Address) -> U256 {␊ + self.flash_mint.max_flash_loan(token, &self.erc20)␊ + }␊ + ␊ + fn flash_fee(&self, token: Address, value: U256) -> Result> {␊ + self.flash_mint.flash_fee(token, value).map_err(|e| e.into())␊ + }␊ + ␊ + fn flash_loan(&mut self, receiver: Address, token: Address, value: U256, data: Bytes) -> Result> {␊ + self.flash_mint.flash_loan(receiver, token, value, data, &mut self.erc20).map_err(|e| e.into())␊ + }␊ + }␊ + ` + +## erc20 burnable flash-mint + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Stylus ^0.2.0-alpha.3␊ + ␊ + #![cfg_attr(not(any(test, feature = "export-abi")), no_main)]␊ + extern crate alloc;␊ + ␊ + use alloc::vec::Vec;␊ + use alloy_primitives::{Address, U256};␊ + use openzeppelin_stylus::token::erc20::{Erc20, IErc20};␊ + use openzeppelin_stylus::token::erc20::extensions::{␊ + Erc20FlashMint, IErc20Burnable, IErc3156FlashLender␊ + };␊ + use stylus_sdk::abi::Bytes;␊ + use stylus_sdk::prelude::{entrypoint, public, storage};␊ + ␊ + #[entrypoint]␊ + #[storage]␊ + struct MyToken {␊ + #[borrow]␊ + erc20: Erc20,␊ + #[borrow]␊ + flash_mint: Erc20FlashMint,␊ + }␊ + ␊ + #[public]␊ + #[inherit(Erc20)]␊ + impl MyToken {␊ + fn burn(&mut self, value: U256) -> Result<(), Vec> {␊ + self.erc20.burn(value).map_err(|e| e.into())␊ + }␊ + ␊ + fn burn_from(&mut self, account: Address, value: U256) -> Result<(), Vec> {␊ + self.erc20.burn_from(account, value).map_err(|e| e.into())␊ + }␊ + ␊ + fn max_flash_loan(&self, token: Address) -> U256 {␊ + self.flash_mint.max_flash_loan(token, &self.erc20)␊ + }␊ + ␊ + fn flash_fee(&self, token: Address, value: U256) -> Result> {␊ + self.flash_mint.flash_fee(token, value).map_err(|e| e.into())␊ + }␊ + ␊ + fn flash_loan(&mut self, receiver: Address, token: Address, value: U256, data: Bytes) -> Result> {␊ + self.flash_mint.flash_loan(receiver, token, value, data, &mut self.erc20).map_err(|e| e.into())␊ + }␊ + }␊ + ` + ## erc20 permit > Snapshot 1 @@ -180,6 +274,58 @@ Generated by [AVA](https://avajs.dev). }␊ ` +## erc20 permit flash-mint + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts for Stylus ^0.2.0-alpha.3␊ + ␊ + #![cfg_attr(not(any(test, feature = "export-abi")), no_main)]␊ + extern crate alloc;␊ + ␊ + use openzeppelin_stylus::token::erc20::extensions::{␊ + Erc20FlashMint, Erc20Permit, IErc3156FlashLender␊ + };␊ + use openzeppelin_stylus::token::erc20::IErc20;␊ + use openzeppelin_stylus::utils::cryptography::eip712::IEip712;␊ + use stylus_sdk::abi::Bytes;␊ + use stylus_sdk::prelude::{entrypoint, public, storage};␊ + ␊ + #[entrypoint]␊ + #[storage]␊ + struct MyToken {␊ + #[borrow]␊ + flash_mint: Erc20FlashMint,␊ + #[borrow]␊ + erc20_permit: Erc20Permit,␊ + }␊ + ␊ + #[storage]␊ + struct Eip712 {}␊ + ␊ + impl IEip712 for Eip712 {␊ + const NAME: &'static str = "ERC-20 Permit Example";␊ + const VERSION: &'static str = "1";␊ + }␊ + ␊ + #[public]␊ + #[inherit(Erc20Permit)]␊ + impl MyToken {␊ + fn max_flash_loan(&self, token: Address) -> U256 {␊ + self.flash_mint.max_flash_loan(token, &self.erc20_permit)␊ + }␊ + ␊ + fn flash_fee(&self, token: Address, value: U256) -> Result> {␊ + self.flash_mint.flash_fee(token, value).map_err(|e| e.into())␊ + }␊ + ␊ + fn flash_loan(&mut self, receiver: Address, token: Address, value: U256, data: Bytes) -> Result> {␊ + self.flash_mint.flash_loan(receiver, token, value, data, &mut self.erc20_permit).map_err(|e| e.into())␊ + }␊ + }␊ + ` + ## erc20 full - complex name > Snapshot 1 @@ -192,14 +338,19 @@ Generated by [AVA](https://avajs.dev). ␊ use alloc::vec::Vec;␊ use alloy_primitives::{Address, U256};␊ - use openzeppelin_stylus::token::erc20::extensions::{Erc20Permit, IErc20Burnable};␊ + use openzeppelin_stylus::token::erc20::extensions::{␊ + Erc20FlashMint, Erc20Permit, IErc20Burnable, IErc3156FlashLender␊ + };␊ use openzeppelin_stylus::token::erc20::IErc20;␊ use openzeppelin_stylus::utils::cryptography::eip712::IEip712;␊ + use stylus_sdk::abi::Bytes;␊ use stylus_sdk::prelude::{entrypoint, public, storage};␊ ␊ #[entrypoint]␊ #[storage]␊ struct CustomToken {␊ + #[borrow]␊ + flash_mint: Erc20FlashMint,␊ #[borrow]␊ erc20_permit: Erc20Permit,␊ }␊ @@ -215,6 +366,18 @@ Generated by [AVA](https://avajs.dev). #[public]␊ #[inherit(Erc20Permit)]␊ impl CustomToken {␊ + fn max_flash_loan(&self, token: Address) -> U256 {␊ + self.flash_mint.max_flash_loan(token, &self.erc20_permit)␊ + }␊ + ␊ + fn flash_fee(&self, token: Address, value: U256) -> Result> {␊ + self.flash_mint.flash_fee(token, value).map_err(|e| e.into())␊ + }␊ + ␊ + fn flash_loan(&mut self, receiver: Address, token: Address, value: U256, data: Bytes) -> Result> {␊ + self.flash_mint.flash_loan(receiver, token, value, data, &mut self.erc20_permit).map_err(|e| e.into())␊ + }␊ + ␊ fn burn(&mut self, value: U256) -> Result<(), Vec> {␊ self.erc20_permit.burn(value).map_err(|e| e.into())␊ }␊ diff --git a/packages/core/stylus/src/erc20.test.ts.snap b/packages/core/stylus/src/erc20.test.ts.snap index 826ed3e9b..d68333652 100644 Binary files a/packages/core/stylus/src/erc20.test.ts.snap and b/packages/core/stylus/src/erc20.test.ts.snap differ diff --git a/packages/core/stylus/src/erc20.ts b/packages/core/stylus/src/erc20.ts index 0ea6e6587..58de67f2f 100644 --- a/packages/core/stylus/src/erc20.ts +++ b/packages/core/stylus/src/erc20.ts @@ -12,6 +12,7 @@ export interface ERC20Options extends CommonContractOptions { burnable?: boolean; pausable?: boolean; permit?: boolean; + flashmint?: boolean; } export const defaults: Required = { @@ -19,8 +20,9 @@ export const defaults: Required = { burnable: false, pausable: false, permit: true, + flashmint: false, access: commonDefaults.access, - info: commonDefaults.info + info: commonDefaults.info, } as const; export function printERC20(opts: ERC20Options = defaults): string { @@ -34,6 +36,7 @@ function withDefaults(opts: ERC20Options): Required { burnable: opts.burnable ?? defaults.burnable, pausable: opts.pausable ?? defaults.pausable, permit: opts.permit ?? defaults.permit, + flashmint: opts.flashmint ?? defaults.flashmint, }; } @@ -50,7 +53,7 @@ export function buildERC20(opts: ERC20Options): Contract { const trait = allOpts.permit ? addPermit(c, allOpts.pausable) : addBase(c, allOpts.pausable); - + addMetadata(c); if (allOpts.pausable) { @@ -61,6 +64,10 @@ export function buildERC20(opts: ERC20Options): Contract { addBurnable(c, allOpts.pausable, trait); } + if (allOpts.flashmint) { + addFlashMint(c, allOpts.pausable, trait); + } + setAccessControl(c, allOpts.access); setInfo(c, allOpts.info); @@ -86,7 +93,7 @@ function addBase(c: ContractBuilder, pausable: boolean): BaseImplementedTrait { 'self.pausable.when_not_paused()?;', ]); } - + return erc20Trait; } @@ -96,9 +103,9 @@ function addPermit(c: ContractBuilder, pausable: boolean): BaseImplementedTrait c.addUseClause('openzeppelin_stylus::utils::cryptography::eip712', 'IEip712'); c.addImplementedTrait(erc20PermitTrait); - c.addEip712("ERC-20 Permit Example", "1"); - - if (pausable) { + c.addEip712('ERC-20 Permit Example', '1'); + + if (pausable) { // Add transfer & permit functions with pause checks c.addUseClause('alloc::vec', 'Vec'); c.addUseClause('alloy_primitives', 'Address'); @@ -115,7 +122,7 @@ function addPermit(c: ContractBuilder, pausable: boolean): BaseImplementedTrait 'self.pausable.when_not_paused()?;', ]); } - + return erc20PermitTrait; } @@ -144,12 +151,31 @@ function addBurnable(c: ContractBuilder, pausable: boolean, trait: BaseImplement } } +function addFlashMint(c: ContractBuilder, pausable: boolean, baseTrait: BaseImplementedTrait) { + c.addUseClause('openzeppelin_stylus::token::erc20::extensions', 'Erc20FlashMint'); + c.addUseClause('openzeppelin_stylus::token::erc20::extensions', 'IErc3156FlashLender'); + + c.addUseClause('stylus_sdk::abi', 'Bytes'); + + c.addImplementedTrait(flashMintTrait); + + c.addFunction(flashMintTrait, functions(baseTrait).max_flash_loan); + c.addFunction(flashMintTrait, functions(baseTrait).flash_fee); + c.addFunction(flashMintTrait, functions(baseTrait).flash_loan); + + if (pausable) { + c.addFunctionCodeBefore(flashMintTrait, functions(baseTrait).flash_loan, [ + 'self.pausable.when_not_paused()?;', + ]); + } +} + const erc20Trait: BaseImplementedTrait = { name: 'Erc20', storage: { name: 'erc20', type: 'Erc20', - } + }, }; const erc20PermitTrait: BaseImplementedTrait = { @@ -157,7 +183,16 @@ const erc20PermitTrait: BaseImplementedTrait = { storage: { name: 'erc20_permit', type: 'Erc20Permit', - } + }, +}; + +const flashMintTrait: BaseImplementedTrait = { + name: 'Erc20FlashMint', + storage: { + name: 'flash_mint', + type: 'Erc20FlashMint', + }, + omit_inherit: true }; // const erc20MetadataTrait: BaseImplementedTrait = { @@ -168,7 +203,7 @@ const erc20PermitTrait: BaseImplementedTrait = { // } // } -const functions = (trait: BaseImplementedTrait) => +const functions = (baseTrait: BaseImplementedTrait) => defineFunctions({ // Token Functions transfer: { @@ -179,7 +214,7 @@ const functions = (trait: BaseImplementedTrait) => ], returns: 'Result>', code: [ - `self.${trait.storage.name}.transfer(to, value).map_err(|e| e.into())`, + `self.${baseTrait.storage.name}.transfer(to, value).map_err(|e| e.into())`, ], }, transfer_from: { @@ -191,7 +226,7 @@ const functions = (trait: BaseImplementedTrait) => ], returns: 'Result>', code: [ - `self.${trait.storage.name}.transfer_from(from, to, value).map_err(|e| e.into())`, + `self.${baseTrait.storage.name}.transfer_from(from, to, value).map_err(|e| e.into())`, ], }, @@ -210,7 +245,7 @@ const functions = (trait: BaseImplementedTrait) => burn: { args: [getSelfArg(), { name: 'value', type: 'U256' }], returns: 'Result<(), Vec>', - code: [`self.${trait.storage.name}.burn(value).map_err(|e| e.into())`], + code: [`self.${baseTrait.storage.name}.burn(value).map_err(|e| e.into())`], }, burn_from: { args: [ @@ -220,7 +255,7 @@ const functions = (trait: BaseImplementedTrait) => ], returns: 'Result<(), Vec>', code: [ - `self.${trait.storage.name}.burn_from(account, value).map_err(|e| e.into())`, + `self.${baseTrait.storage.name}.burn_from(account, value).map_err(|e| e.into())`, ], }, @@ -237,7 +272,42 @@ const functions = (trait: BaseImplementedTrait) => ], returns: 'Result<(), Vec>', code: [ - `self.${trait.storage.name}.permit(owner, spender, value, deadline, v, r, s).map_err(|e| e.into())`, + `self.${baseTrait.storage.name}.permit(owner, spender, value, deadline, v, r, s).map_err(|e| e.into())`, + ], + }, + + max_flash_loan: { + args: [ + getSelfArg("immutable"), + { name: 'token', type: 'Address' }, + ], + returns: 'U256', + code: [ + `self.${flashMintTrait.storage.name}.max_flash_loan(token, &self.${baseTrait.storage.name})`, + ], + }, + flash_fee: { + args: [ + getSelfArg("immutable"), + { name: 'token', type: 'Address' }, + { name: 'value', type: 'U256' }, + ], + returns: 'Result>', + code: [ + `self.${flashMintTrait.storage.name}.flash_fee(token, value).map_err(|e| e.into())`, + ], + }, + flash_loan: { + args: [ + getSelfArg(), + { name: 'receiver', type: 'Address' }, + { name: 'token', type: 'Address' }, + { name: 'value', type: 'U256' }, + { name: 'data', type: 'Bytes' }, + ], + returns: 'Result>', + code: [ + `self.${flashMintTrait.storage.name}.flash_loan(receiver, token, value, data, &mut self.${baseTrait.storage.name}).map_err(|e| e.into())`, ], }, }); diff --git a/packages/core/stylus/src/generate/erc20.ts b/packages/core/stylus/src/generate/erc20.ts index 381ead1a0..c48ed73e4 100644 --- a/packages/core/stylus/src/generate/erc20.ts +++ b/packages/core/stylus/src/generate/erc20.ts @@ -10,8 +10,9 @@ const blueprint = { burnable: booleans, pausable: booleans, permit: booleans, + flashmint: booleans, access: accessOptions, - info: infoOptions + info: infoOptions, }; export function* generateERC20Options(): Generator> { diff --git a/packages/core/stylus/src/print.ts b/packages/core/stylus/src/print.ts index b2f24eea5..5c42c2143 100644 --- a/packages/core/stylus/src/print.ts +++ b/packages/core/stylus/src/print.ts @@ -192,11 +192,14 @@ function printEip712(eip712?: EIP712): Lines[] { } function printImplementedTraits(contractName: string, sortedGroups: [string, ImplementedTrait[]][]): Lines[] { - const traitNames = sortedGroups.flatMap(([_, impls]) => impls).map(trait => trait.name); - + const traitNames = sortedGroups + .flatMap(([_, impls]) => impls) + .filter(trait => !trait.omit_inherit) + .map(trait => trait.name); + const inheritAttribute = traitNames.length > 0 - ? `#[inherit(${traitNames.join(', ')})]` - : "#[inherit]"; + ? `#[inherit(${traitNames.join(', ')})]` + : "#[inherit]"; const header = [ '#[public]', diff --git a/packages/ui/src/stylus/ERC20Controls.svelte b/packages/ui/src/stylus/ERC20Controls.svelte index 712f99c47..a36017c4e 100644 --- a/packages/ui/src/stylus/ERC20Controls.svelte +++ b/packages/ui/src/stylus/ERC20Controls.svelte @@ -59,6 +59,14 @@ Without paying gas, token holders will be able to allow third parties to transfer from their account. + +