diff --git a/packages/core/solidity/CHANGELOG.md b/packages/core/solidity/CHANGELOG.md index ba49d7ad9..2dde0f65f 100644 --- a/packages/core/solidity/CHANGELOG.md +++ b/packages/core/solidity/CHANGELOG.md @@ -3,6 +3,7 @@ ## Unreleased - Add validation for ERC20 premint field. ([#488](https://github.com/OpenZeppelin/contracts-wizard/pull/488)) +- Add `callback` in ERC20 features. ([#500](https://github.com/OpenZeppelin/contracts-wizard/pull/500)) ## 0.5.3 (2025-03-13) diff --git a/packages/core/solidity/src/erc20.test.ts b/packages/core/solidity/src/erc20.test.ts index f609e42dc..2ea481d3d 100644 --- a/packages/core/solidity/src/erc20.test.ts +++ b/packages/core/solidity/src/erc20.test.ts @@ -116,6 +116,10 @@ testERC20('erc20 mintable with roles', { access: 'roles', }); +testERC20('erc20 callback', { + callback: true, +}); + testERC20('erc20 permit', { permit: true, }); @@ -249,6 +253,7 @@ testERC20('erc20 full crossChainBridging custom non-upgradeable', { burnable: true, mintable: true, pausable: true, + callback: true, permit: true, votes: true, flashmint: true, @@ -262,6 +267,7 @@ testERC20('erc20 full upgradeable transparent', { burnable: true, mintable: true, pausable: true, + callback: true, permit: true, votes: true, flashmint: true, @@ -274,6 +280,7 @@ testERC20('erc20 full upgradeable uups', { burnable: true, mintable: true, pausable: true, + callback: true, permit: true, votes: true, flashmint: true, @@ -286,6 +293,7 @@ testERC20('erc20 full upgradeable uups managed', { burnable: true, mintable: true, pausable: true, + callback: true, permit: true, votes: true, flashmint: true, @@ -304,6 +312,7 @@ testAPIEquivalence('erc20 API full upgradeable', { burnable: true, mintable: true, pausable: true, + callback: true, permit: true, votes: true, flashmint: true, diff --git a/packages/core/solidity/src/erc20.test.ts.md b/packages/core/solidity/src/erc20.test.ts.md index ed14a684d..dd310f481 100644 --- a/packages/core/solidity/src/erc20.test.ts.md +++ b/packages/core/solidity/src/erc20.test.ts.md @@ -352,6 +352,23 @@ Generated by [AVA](https://avajs.dev). }␊ ` +## erc20 callback + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts ^5.0.0␊ + pragma solidity ^0.8.22;␊ + ␊ + import {ERC1363} from "@openzeppelin/contracts/token/ERC20/extensions/ERC1363.sol";␊ + import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";␊ + import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";␊ + ␊ + contract MyToken is ERC20, ERC1363, ERC20Permit {␊ + constructor() ERC20("MyToken", "MTK") ERC20Permit("MyToken") {}␊ + }␊ + ` + ## erc20 permit > Snapshot 1 @@ -915,6 +932,7 @@ Generated by [AVA](https://avajs.dev). pragma solidity ^0.8.22;␊ ␊ import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";␊ + import {ERC1363} from "@openzeppelin/contracts/token/ERC20/extensions/ERC1363.sol";␊ import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";␊ import {ERC20Bridgeable} from "@openzeppelin/community-contracts/contracts/token/ERC20/extensions/ERC20Bridgeable.sol";␊ import {ERC20Burnable} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";␊ @@ -924,7 +942,7 @@ Generated by [AVA](https://avajs.dev). import {ERC20Votes} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";␊ import {Nonces} from "@openzeppelin/contracts/utils/Nonces.sol";␊ ␊ - contract MyToken is ERC20, ERC20Bridgeable, AccessControl, ERC20Burnable, ERC20Pausable, ERC20Permit, ERC20Votes, ERC20FlashMint {␊ + contract MyToken is ERC20, ERC20Bridgeable, AccessControl, ERC20Burnable, ERC20Pausable, ERC1363, ERC20Permit, ERC20Votes, ERC20FlashMint {␊ bytes32 public constant TOKEN_BRIDGE_ROLE = keccak256("TOKEN_BRIDGE_ROLE");␊ error Unauthorized();␊ bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");␊ @@ -971,7 +989,7 @@ Generated by [AVA](https://avajs.dev). function supportsInterface(bytes4 interfaceId)␊ public␊ view␊ - override(ERC20Bridgeable, AccessControl)␊ + override(ERC20Bridgeable, AccessControl, ERC1363)␊ returns (bool)␊ {␊ return super.supportsInterface(interfaceId);␊ @@ -997,6 +1015,7 @@ Generated by [AVA](https://avajs.dev). pragma solidity ^0.8.22;␊ ␊ import {AccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";␊ + import {ERC1363Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC1363Upgradeable.sol";␊ import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";␊ import {ERC20BurnableUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20BurnableUpgradeable.sol";␊ import {ERC20FlashMintUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20FlashMintUpgradeable.sol";␊ @@ -1006,7 +1025,7 @@ Generated by [AVA](https://avajs.dev). import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";␊ import {NoncesUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/NoncesUpgradeable.sol";␊ ␊ - contract MyToken is Initializable, ERC20Upgradeable, ERC20BurnableUpgradeable, ERC20PausableUpgradeable, AccessControlUpgradeable, ERC20PermitUpgradeable, ERC20VotesUpgradeable, ERC20FlashMintUpgradeable {␊ + contract MyToken is Initializable, ERC20Upgradeable, ERC20BurnableUpgradeable, ERC20PausableUpgradeable, AccessControlUpgradeable, ERC1363Upgradeable, ERC20PermitUpgradeable, ERC20VotesUpgradeable, ERC20FlashMintUpgradeable {␊ bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");␊ bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");␊ ␊ @@ -1022,6 +1041,7 @@ Generated by [AVA](https://avajs.dev). __ERC20Burnable_init();␊ __ERC20Pausable_init();␊ __AccessControl_init();␊ + __ERC1363_init();␊ __ERC20Permit_init("MyToken");␊ __ERC20Votes_init();␊ __ERC20FlashMint_init();␊ @@ -1052,6 +1072,15 @@ Generated by [AVA](https://avajs.dev). {␊ super._update(from, to, value);␊ }␊ + ␊ + function supportsInterface(bytes4 interfaceId)␊ + public␊ + view␊ + override(AccessControlUpgradeable, ERC1363Upgradeable)␊ + returns (bool)␊ + {␊ + return super.supportsInterface(interfaceId);␊ + }␊ ␊ function nonces(address owner)␊ public␊ @@ -1073,6 +1102,7 @@ Generated by [AVA](https://avajs.dev). pragma solidity ^0.8.22;␊ ␊ import {AccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";␊ + import {ERC1363Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC1363Upgradeable.sol";␊ import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";␊ import {ERC20BurnableUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20BurnableUpgradeable.sol";␊ import {ERC20FlashMintUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20FlashMintUpgradeable.sol";␊ @@ -1083,7 +1113,7 @@ Generated by [AVA](https://avajs.dev). import {NoncesUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/NoncesUpgradeable.sol";␊ import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";␊ ␊ - contract MyToken is Initializable, ERC20Upgradeable, ERC20BurnableUpgradeable, ERC20PausableUpgradeable, AccessControlUpgradeable, ERC20PermitUpgradeable, ERC20VotesUpgradeable, ERC20FlashMintUpgradeable, UUPSUpgradeable {␊ + contract MyToken is Initializable, ERC20Upgradeable, ERC20BurnableUpgradeable, ERC20PausableUpgradeable, AccessControlUpgradeable, ERC1363Upgradeable, ERC20PermitUpgradeable, ERC20VotesUpgradeable, ERC20FlashMintUpgradeable, UUPSUpgradeable {␊ bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");␊ bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");␊ bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE");␊ @@ -1100,6 +1130,7 @@ Generated by [AVA](https://avajs.dev). __ERC20Burnable_init();␊ __ERC20Pausable_init();␊ __AccessControl_init();␊ + __ERC1363_init();␊ __ERC20Permit_init("MyToken");␊ __ERC20Votes_init();␊ __ERC20FlashMint_init();␊ @@ -1138,6 +1169,15 @@ Generated by [AVA](https://avajs.dev). {␊ super._update(from, to, value);␊ }␊ + ␊ + function supportsInterface(bytes4 interfaceId)␊ + public␊ + view␊ + override(AccessControlUpgradeable, ERC1363Upgradeable)␊ + returns (bool)␊ + {␊ + return super.supportsInterface(interfaceId);␊ + }␊ ␊ function nonces(address owner)␊ public␊ @@ -1159,6 +1199,7 @@ Generated by [AVA](https://avajs.dev). pragma solidity ^0.8.22;␊ ␊ import {AccessManagedUpgradeable} from "@openzeppelin/contracts-upgradeable/access/manager/AccessManagedUpgradeable.sol";␊ + import {ERC1363Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC1363Upgradeable.sol";␊ import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";␊ import {ERC20BurnableUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20BurnableUpgradeable.sol";␊ import {ERC20FlashMintUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20FlashMintUpgradeable.sol";␊ @@ -1169,7 +1210,7 @@ Generated by [AVA](https://avajs.dev). import {NoncesUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/NoncesUpgradeable.sol";␊ import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";␊ ␊ - contract MyToken is Initializable, ERC20Upgradeable, ERC20BurnableUpgradeable, ERC20PausableUpgradeable, AccessManagedUpgradeable, ERC20PermitUpgradeable, ERC20VotesUpgradeable, ERC20FlashMintUpgradeable, UUPSUpgradeable {␊ + contract MyToken is Initializable, ERC20Upgradeable, ERC20BurnableUpgradeable, ERC20PausableUpgradeable, AccessManagedUpgradeable, ERC1363Upgradeable, ERC20PermitUpgradeable, ERC20VotesUpgradeable, ERC20FlashMintUpgradeable, UUPSUpgradeable {␊ /// @custom:oz-upgrades-unsafe-allow constructor␊ constructor() {␊ _disableInitializers();␊ @@ -1182,6 +1223,7 @@ Generated by [AVA](https://avajs.dev). __ERC20Burnable_init();␊ __ERC20Pausable_init();␊ __AccessManaged_init(initialAuthority);␊ + __ERC1363_init();␊ __ERC20Permit_init("MyToken");␊ __ERC20Votes_init();␊ __ERC20FlashMint_init();␊ diff --git a/packages/core/solidity/src/erc20.test.ts.snap b/packages/core/solidity/src/erc20.test.ts.snap index 7619e4332..7cac25439 100644 Binary files a/packages/core/solidity/src/erc20.test.ts.snap and b/packages/core/solidity/src/erc20.test.ts.snap differ diff --git a/packages/core/solidity/src/erc20.ts b/packages/core/solidity/src/erc20.ts index 450225479..481a29a7b 100644 --- a/packages/core/solidity/src/erc20.ts +++ b/packages/core/solidity/src/erc20.ts @@ -26,6 +26,7 @@ export interface ERC20Options extends CommonOptions { premint?: string; premintChainId?: string; mintable?: boolean; + callback?: boolean; permit?: boolean; /** * Whether to keep track of historical balances for voting in on-chain governance, and optionally specify the clock mode. @@ -44,6 +45,7 @@ export const defaults: Required = { premint: '0', premintChainId: '', mintable: false, + callback: false, permit: true, votes: false, flashmint: false, @@ -62,6 +64,7 @@ export function withDefaults(opts: ERC20Options): Required { premint: opts.premint || defaults.premint, premintChainId: opts.premintChainId || defaults.premintChainId, mintable: opts.mintable ?? defaults.mintable, + callback: opts.callback ?? defaults.callback, permit: opts.permit ?? defaults.permit, votes: opts.votes ?? defaults.votes, flashmint: opts.flashmint ?? defaults.flashmint, @@ -106,6 +109,10 @@ export function buildERC20(opts: ERC20Options): ContractBuilder { addMintable(c, access); } + if (allOpts.callback) { + addCallback(c); + } + // Note: Votes requires Permit if (allOpts.permit || allOpts.votes) { addPermit(c, allOpts.name); @@ -247,6 +254,15 @@ function addMintable(c: ContractBuilder, access: Access) { c.addFunctionCode('_mint(to, amount);', functions.mint); } +function addCallback(c: ContractBuilder) { + const ERC1363 = { + name: 'ERC1363', + path: '@openzeppelin/contracts/token/ERC20/extensions/ERC1363.sol', + }; + c.addParent(ERC1363); + c.addOverride(ERC1363, supportsInterface); +} + function addPermit(c: ContractBuilder, name: string) { const ERC20Permit = { name: 'ERC20Permit', diff --git a/packages/core/solidity/src/generate/erc20.ts b/packages/core/solidity/src/generate/erc20.ts index 9f48e16d5..49040e511 100644 --- a/packages/core/solidity/src/generate/erc20.ts +++ b/packages/core/solidity/src/generate/erc20.ts @@ -13,6 +13,7 @@ const blueprint = { burnable: booleans, pausable: booleans, mintable: booleans, + callback: booleans, permit: booleans, votes: [...booleans, ...clockModeOptions] as const, flashmint: booleans, diff --git a/packages/core/solidity/src/generate/stablecoin.ts b/packages/core/solidity/src/generate/stablecoin.ts index 8eba148fd..581f8e999 100644 --- a/packages/core/solidity/src/generate/stablecoin.ts +++ b/packages/core/solidity/src/generate/stablecoin.ts @@ -12,6 +12,7 @@ const erc20Basic = { burnable: [false] as const, pausable: [false] as const, mintable: [false] as const, + callback: [false] as const, permit: [false] as const, votes: [false] as const, flashmint: [false] as const, @@ -28,6 +29,7 @@ const erc20Full = { burnable: [true] as const, pausable: [true] as const, mintable: [true] as const, + callback: [true] as const, permit: [true] as const, votes: ['timestamp'] as const, flashmint: [true] as const, diff --git a/packages/core/solidity/src/stablecoin.test.ts b/packages/core/solidity/src/stablecoin.test.ts index edcc1b735..941efaa10 100644 --- a/packages/core/solidity/src/stablecoin.test.ts +++ b/packages/core/solidity/src/stablecoin.test.ts @@ -78,6 +78,10 @@ testStablecoin('stablecoin mintable with roles', { access: 'roles', }); +testStablecoin('stablecoin callback', { + callback: true, +}); + testStablecoin('stablecoin permit', { permit: true, }); @@ -118,6 +122,7 @@ testStablecoin('stablecoin full', { burnable: true, mintable: true, pausable: true, + callback: true, permit: true, votes: true, flashmint: true, @@ -142,6 +147,7 @@ testAPIEquivalence('stablecoin API full', { burnable: true, mintable: true, pausable: true, + callback: true, permit: true, votes: true, flashmint: true, diff --git a/packages/core/solidity/src/stablecoin.test.ts.md b/packages/core/solidity/src/stablecoin.test.ts.md index 5d6de2c37..63178728e 100644 --- a/packages/core/solidity/src/stablecoin.test.ts.md +++ b/packages/core/solidity/src/stablecoin.test.ts.md @@ -289,6 +289,23 @@ Generated by [AVA](https://avajs.dev). }␊ ` +## stablecoin callback + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts ^5.0.0␊ + pragma solidity ^0.8.22;␊ + ␊ + import {ERC1363} from "@openzeppelin/contracts/token/ERC20/extensions/ERC1363.sol";␊ + import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";␊ + import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";␊ + ␊ + contract MyStablecoin is ERC20, ERC1363, ERC20Permit {␊ + constructor() ERC20("MyStablecoin", "MST") ERC20Permit("MyStablecoin") {}␊ + }␊ + ` + ## stablecoin permit > Snapshot 1 @@ -575,6 +592,7 @@ Generated by [AVA](https://avajs.dev). pragma solidity ^0.8.22;␊ ␊ import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";␊ + import {ERC1363} from "@openzeppelin/contracts/token/ERC20/extensions/ERC1363.sol";␊ import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";␊ import {ERC20Allowlist} from "@openzeppelin/community-contracts/contracts/token/ERC20/extensions/ERC20Allowlist.sol";␊ import {ERC20Bridgeable} from "@openzeppelin/community-contracts/contracts/token/ERC20/extensions/ERC20Bridgeable.sol";␊ @@ -586,7 +604,7 @@ Generated by [AVA](https://avajs.dev). import {ERC20Votes} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Votes.sol";␊ import {Nonces} from "@openzeppelin/contracts/utils/Nonces.sol";␊ ␊ - contract MyStablecoin is ERC20, ERC20Bridgeable, AccessControl, ERC20Burnable, ERC20Pausable, ERC20Permit, ERC20Votes, ERC20FlashMint, ERC20Custodian, ERC20Allowlist {␊ + contract MyStablecoin is ERC20, ERC20Bridgeable, AccessControl, ERC20Burnable, ERC20Pausable, ERC1363, ERC20Permit, ERC20Votes, ERC20FlashMint, ERC20Custodian, ERC20Allowlist {␊ bytes32 public constant TOKEN_BRIDGE_ROLE = keccak256("TOKEN_BRIDGE_ROLE");␊ error Unauthorized();␊ bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");␊ @@ -656,7 +674,7 @@ Generated by [AVA](https://avajs.dev). function supportsInterface(bytes4 interfaceId)␊ public␊ view␊ - override(ERC20Bridgeable, AccessControl)␊ + override(ERC20Bridgeable, AccessControl, ERC1363)␊ returns (bool)␊ {␊ return super.supportsInterface(interfaceId);␊ diff --git a/packages/core/solidity/src/stablecoin.test.ts.snap b/packages/core/solidity/src/stablecoin.test.ts.snap index c0503d7f1..247efd1e8 100644 Binary files a/packages/core/solidity/src/stablecoin.test.ts.snap and b/packages/core/solidity/src/stablecoin.test.ts.snap differ diff --git a/packages/ui/src/solidity/ERC20Controls.svelte b/packages/ui/src/solidity/ERC20Controls.svelte index 148fe9071..4b00ca0f8 100644 --- a/packages/ui/src/solidity/ERC20Controls.svelte +++ b/packages/ui/src/solidity/ERC20Controls.svelte @@ -111,6 +111,14 @@ + + + + + +