diff --git a/packages/common/src/ai/descriptions/solidity.ts b/packages/common/src/ai/descriptions/solidity.ts index ab9b5c5ae..0be00162e 100644 --- a/packages/common/src/ai/descriptions/solidity.ts +++ b/packages/common/src/ai/descriptions/solidity.ts @@ -31,7 +31,7 @@ export const solidityERC20Descriptions = { flashmint: "Whether to include built-in flash loans to allow lending tokens without requiring collateral as long as they're returned in the same transaction.", crossChainBridging: - 'Whether to allow authorized bridge contracts to mint and burn tokens for cross-chain transfers. Options are to use custom bridges on any chain, or the SuperchainERC20 standard with the predeployed SuperchainTokenBridge. The SuperchainERC20 feature is only available on chains in the Superchain, and requires deploying your contract to the same address on every chain in the Superchain.', + 'Whether to allow authorized bridge contracts to mint and burn tokens for cross-chain transfers. Options are to use custom bridges on any chain, to embed an ERC-7786 based bridge directly in the token contract, or to use the SuperchainERC20 standard with the predeployed SuperchainTokenBridge. The SuperchainERC20 feature is only available on chains in the Superchain, and requires deploying your contract to the same address on every chain in the Superchain.', premintChainId: 'The chain ID of the network on which to premint tokens.', callback: 'Whether to include support for code execution after transfers and approvals on recipient contracts in a single transaction.', diff --git a/packages/core/solidity/hardhat.config.js b/packages/core/solidity/hardhat.config.js index 2ef5c7125..5c320d9b3 100644 --- a/packages/core/solidity/hardhat.config.js +++ b/packages/core/solidity/hardhat.config.js @@ -66,6 +66,7 @@ module.exports = { enabled: true, runs: 200, }, + viaIR: true, }, }, }; diff --git a/packages/core/solidity/package.json b/packages/core/solidity/package.json index 36e41201c..d94fb8ad1 100644 --- a/packages/core/solidity/package.json +++ b/packages/core/solidity/package.json @@ -28,7 +28,7 @@ }, "devDependencies": { "@openzeppelin/community-contracts": "git+https://github.com/OpenZeppelin/openzeppelin-community-contracts.git#b0ddd27", - "@openzeppelin/contracts": "^5.5.0", + "@openzeppelin/contracts": "git+https://github.com/OpenZeppelin/openzeppelin-contracts.git#master", "@openzeppelin/contracts-upgradeable": "^5.5.0", "@types/node": "^20.0.0", "@types/semver": "^7.5.7", @@ -41,4 +41,4 @@ "ts-node": "^10.4.0", "typescript": "^5.0.0" } -} +} \ No newline at end of file diff --git a/packages/core/solidity/remappings.txt b/packages/core/solidity/remappings.txt index 7f8667df0..db505e700 100644 --- a/packages/core/solidity/remappings.txt +++ b/packages/core/solidity/remappings.txt @@ -1,2 +1,3 @@ @openzeppelin/community-contracts/=node_modules/@openzeppelin/community-contracts/contracts/ @openzeppelin/contracts-upgradeable/=node_modules/@openzeppelin/contracts-upgradeable/ +@openzeppelin/contracts/=node_modules/@openzeppelin/contracts/contracts/ \ No newline at end of file diff --git a/packages/core/solidity/src/environments/hardhat/package.json b/packages/core/solidity/src/environments/hardhat/package.json index e8f6a2706..491cdb7f8 100644 --- a/packages/core/solidity/src/environments/hardhat/package.json +++ b/packages/core/solidity/src/environments/hardhat/package.json @@ -11,6 +11,7 @@ "devDependencies": { "@nomicfoundation/hardhat-toolbox": "^6.1.0", "@openzeppelin/contracts": "^5.5.0", + "@openzeppelin/contracts-unreleased": "git+https://github.com/OpenZeppelin/openzeppelin-contracts.git#master", "hardhat": "^2.22.0" } } \ No newline at end of file diff --git a/packages/core/solidity/src/environments/hardhat/upgradeable/package.json b/packages/core/solidity/src/environments/hardhat/upgradeable/package.json index da79327fe..711388cf3 100644 --- a/packages/core/solidity/src/environments/hardhat/upgradeable/package.json +++ b/packages/core/solidity/src/environments/hardhat/upgradeable/package.json @@ -11,6 +11,7 @@ "devDependencies": { "@nomicfoundation/hardhat-toolbox": "^6.1.0", "@openzeppelin/contracts": "^5.5.0", + "@openzeppelin/contracts-unreleased": "git+https://github.com/OpenZeppelin/openzeppelin-contracts.git#master", "@openzeppelin/contracts-upgradeable": "^5.5.0", "@openzeppelin/hardhat-upgrades": "^3.0.0", "hardhat": "^2.22.0" diff --git a/packages/core/solidity/src/erc20.test.ts b/packages/core/solidity/src/erc20.test.ts index 41b3d0763..be6dcd83b 100644 --- a/packages/core/solidity/src/erc20.test.ts +++ b/packages/core/solidity/src/erc20.test.ts @@ -168,6 +168,37 @@ testERC20('erc20 crossChainBridging custom managed', { access: 'managed', }); +testERC20('erc20 crossChainBridging embedded', { + crossChainBridging: 'embedded', +}); + +testERC20('erc20 crossChainBridging embedded ownable', { + crossChainBridging: 'embedded', + access: 'ownable', +}); + +testERC20('erc20 crossChainBridging embedded ownable mintable burnable', { + crossChainBridging: 'embedded', + access: 'ownable', + mintable: true, + burnable: true, +}); + +testERC20('erc20 crossChainBridging embedded roles', { + crossChainBridging: 'embedded', + access: 'roles', +}); + +testERC20('erc20 crossChainBridging embedded managed', { + crossChainBridging: 'embedded', + access: 'managed', +}); + +testERC20('erc20 crossChainBridging embedded upgradeable', { + crossChainBridging: 'embedded', + upgradeable: 'transparent', +}); + testERC20('erc20 crossChainBridging superchain', { crossChainBridging: 'superchain', }); diff --git a/packages/core/solidity/src/erc20.test.ts.md b/packages/core/solidity/src/erc20.test.ts.md index b383936ca..07b8556fa 100644 --- a/packages/core/solidity/src/erc20.test.ts.md +++ b/packages/core/solidity/src/erc20.test.ts.md @@ -546,13 +546,15 @@ Generated by [AVA](https://avajs.dev). import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";␊ import {ERC20Bridgeable} from "@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Bridgeable.sol";␊ import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";␊ + import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";␊ ␊ - contract MyToken is ERC20, ERC20Bridgeable, ERC20Permit {␊ + contract MyToken is ERC20, ERC20Bridgeable, Ownable, ERC20Permit {␊ address public tokenBridge;␊ error Unauthorized();␊ ␊ - constructor(address tokenBridge_)␊ + constructor(address tokenBridge_, address initialOwner)␊ ERC20("MyToken", "MTK")␊ + Ownable(initialOwner)␊ ERC20Permit("MyToken")␊ {␊ require(tokenBridge_ != address(0), "Invalid tokenBridge_ address");␊ @@ -562,6 +564,10 @@ Generated by [AVA](https://avajs.dev). function _checkTokenBridge(address caller) internal view override {␊ if (caller != tokenBridge) revert Unauthorized();␊ }␊ + ␊ + function setTokenBridge(address tokenBridge_) public onlyOwner {␊ + tokenBridge = tokenBridge_;␊ + }␊ }␊ ` @@ -578,14 +584,14 @@ Generated by [AVA](https://avajs.dev). import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";␊ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";␊ ␊ - contract MyToken is ERC20, ERC20Bridgeable, ERC20Permit, Ownable {␊ + contract MyToken is ERC20, ERC20Bridgeable, Ownable, ERC20Permit {␊ address public tokenBridge;␊ error Unauthorized();␊ ␊ constructor(address tokenBridge_, address initialOwner)␊ ERC20("MyToken", "MTK")␊ - ERC20Permit("MyToken")␊ Ownable(initialOwner)␊ + ERC20Permit("MyToken")␊ {␊ require(tokenBridge_ != address(0), "Invalid tokenBridge_ address");␊ tokenBridge = tokenBridge_;␊ @@ -594,6 +600,10 @@ Generated by [AVA](https://avajs.dev). function _checkTokenBridge(address caller) internal view override {␊ if (caller != tokenBridge) revert Unauthorized();␊ }␊ + ␊ + function setTokenBridge(address tokenBridge_) public onlyOwner {␊ + tokenBridge = tokenBridge_;␊ + }␊ }␊ ` @@ -611,7 +621,7 @@ Generated by [AVA](https://avajs.dev). import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";␊ import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";␊ ␊ - contract MyToken is ERC20, ERC20Bridgeable, ERC20Burnable, Ownable, ERC20Permit {␊ + contract MyToken is ERC20, ERC20Bridgeable, Ownable, ERC20Burnable, ERC20Permit {␊ address public tokenBridge;␊ error Unauthorized();␊ ␊ @@ -627,6 +637,10 @@ Generated by [AVA](https://avajs.dev). function _checkTokenBridge(address caller) internal view override {␊ if (caller != tokenBridge) revert Unauthorized();␊ }␊ + ␊ + function setTokenBridge(address tokenBridge_) public onlyOwner {␊ + tokenBridge = tokenBridge_;␊ + }␊ ␊ function mint(address to, uint256 amount) public onlyOwner {␊ _mint(to, amount);␊ @@ -706,6 +720,211 @@ Generated by [AVA](https://avajs.dev). }␊ ` +## erc20 crossChainBridging embedded + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts ^5.5.0␊ + pragma solidity ^0.8.27;␊ + ␊ + import {CrosschainLinked} from "@openzeppelin/contracts/crosschain/CrosschainLinked.sol";␊ + import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";␊ + import {ERC20Crosschain} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Crosschain.sol";␊ + import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";␊ + import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";␊ + ␊ + contract MyToken is ERC20, ERC20Crosschain, Ownable, ERC20Permit {␊ + constructor(CrosschainLinked.Link[] memory links, address initialOwner)␊ + ERC20("MyToken", "MTK")␊ + CrosschainLinked(links)␊ + Ownable(initialOwner)␊ + ERC20Permit("MyToken")␊ + {}␊ + ␊ + function setLink(address gateway, bytes memory counterpart, bool allowOverride)␊ + public␊ + onlyOwner␊ + {␊ + _setLink(gateway, counterpart, allowOverride);␊ + }␊ + }␊ + ` + +## erc20 crossChainBridging embedded ownable + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts ^5.5.0␊ + pragma solidity ^0.8.27;␊ + ␊ + import {CrosschainLinked} from "@openzeppelin/contracts/crosschain/CrosschainLinked.sol";␊ + import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";␊ + import {ERC20Crosschain} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Crosschain.sol";␊ + import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";␊ + import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";␊ + ␊ + contract MyToken is ERC20, ERC20Crosschain, Ownable, ERC20Permit {␊ + constructor(CrosschainLinked.Link[] memory links, address initialOwner)␊ + ERC20("MyToken", "MTK")␊ + CrosschainLinked(links)␊ + Ownable(initialOwner)␊ + ERC20Permit("MyToken")␊ + {}␊ + ␊ + function setLink(address gateway, bytes memory counterpart, bool allowOverride)␊ + public␊ + onlyOwner␊ + {␊ + _setLink(gateway, counterpart, allowOverride);␊ + }␊ + }␊ + ` + +## erc20 crossChainBridging embedded ownable mintable burnable + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts ^5.5.0␊ + pragma solidity ^0.8.27;␊ + ␊ + import {CrosschainLinked} from "@openzeppelin/contracts/crosschain/CrosschainLinked.sol";␊ + import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";␊ + import {ERC20Burnable} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";␊ + import {ERC20Crosschain} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Crosschain.sol";␊ + import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";␊ + import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";␊ + ␊ + contract MyToken is ERC20, ERC20Crosschain, Ownable, ERC20Burnable, ERC20Permit {␊ + constructor(CrosschainLinked.Link[] memory links, address initialOwner)␊ + ERC20("MyToken", "MTK")␊ + CrosschainLinked(links)␊ + Ownable(initialOwner)␊ + ERC20Permit("MyToken")␊ + {}␊ + ␊ + function setLink(address gateway, bytes memory counterpart, bool allowOverride)␊ + public␊ + onlyOwner␊ + {␊ + _setLink(gateway, counterpart, allowOverride);␊ + }␊ + ␊ + function mint(address to, uint256 amount) public onlyOwner {␊ + _mint(to, amount);␊ + }␊ + }␊ + ` + +## erc20 crossChainBridging embedded roles + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts ^5.5.0␊ + pragma solidity ^0.8.27;␊ + ␊ + import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";␊ + import {CrosschainLinked} from "@openzeppelin/contracts/crosschain/CrosschainLinked.sol";␊ + import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";␊ + import {ERC20Crosschain} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Crosschain.sol";␊ + import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";␊ + ␊ + contract MyToken is ERC20, ERC20Crosschain, AccessControl, ERC20Permit {␊ + bytes32 public constant CROSSCHAIN_LINKER_ROLE = keccak256("CROSSCHAIN_LINKER_ROLE");␊ + ␊ + constructor(CrosschainLinked.Link[] memory links, address defaultAdmin, address crosschainLinker)␊ + ERC20("MyToken", "MTK")␊ + CrosschainLinked(links)␊ + ERC20Permit("MyToken")␊ + {␊ + _grantRole(DEFAULT_ADMIN_ROLE, defaultAdmin);␊ + _grantRole(CROSSCHAIN_LINKER_ROLE, crosschainLinker);␊ + }␊ + ␊ + function setLink(address gateway, bytes memory counterpart, bool allowOverride)␊ + public␊ + onlyRole(CROSSCHAIN_LINKER_ROLE)␊ + {␊ + _setLink(gateway, counterpart, allowOverride);␊ + }␊ + }␊ + ` + +## erc20 crossChainBridging embedded managed + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts ^5.5.0␊ + pragma solidity ^0.8.27;␊ + ␊ + import {AccessManaged} from "@openzeppelin/contracts/access/manager/AccessManaged.sol";␊ + import {CrosschainLinked} from "@openzeppelin/contracts/crosschain/CrosschainLinked.sol";␊ + import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";␊ + import {ERC20Crosschain} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Crosschain.sol";␊ + import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";␊ + ␊ + contract MyToken is ERC20, ERC20Crosschain, AccessManaged, ERC20Permit {␊ + constructor(CrosschainLinked.Link[] memory links, address initialAuthority)␊ + ERC20("MyToken", "MTK")␊ + CrosschainLinked(links)␊ + AccessManaged(initialAuthority)␊ + ERC20Permit("MyToken")␊ + {}␊ + ␊ + function setLink(address gateway, bytes memory counterpart, bool allowOverride)␊ + public␊ + restricted␊ + {␊ + _setLink(gateway, counterpart, allowOverride);␊ + }␊ + }␊ + ` + +## erc20 crossChainBridging embedded upgradeable + +> Snapshot 1 + + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts ^5.5.0␊ + pragma solidity ^0.8.27;␊ + ␊ + import {CrosschainLinkedUpgradeable} from "@openzeppelin/contracts-upgradeable/crosschain/CrosschainLinkedUpgradeable.sol";␊ + import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";␊ + import {ERC20CrosschainUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20CrosschainUpgradeable.sol";␊ + import {ERC20PermitUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol";␊ + import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol";␊ + import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";␊ + ␊ + contract MyToken is Initializable, ERC20Upgradeable, ERC20CrosschainUpgradeable, OwnableUpgradeable, ERC20PermitUpgradeable {␊ + /// @custom:oz-upgrades-unsafe-allow constructor␊ + constructor() {␊ + _disableInitializers();␊ + }␊ + ␊ + function initialize(CrosschainLinked.Link[] memory links, address initialOwner)␊ + public␊ + initializer␊ + {␊ + __ERC20_init("MyToken", "MTK");␊ + __ERC20Crosschain_init();␊ + __CrosschainLinked_init(links);␊ + __Ownable_init(initialOwner);␊ + __ERC20Permit_init("MyToken");␊ + }␊ + ␊ + function setLink(address gateway, bytes memory counterpart, bool allowOverride)␊ + public␊ + onlyOwner␊ + {␊ + _setLink(gateway, counterpart, allowOverride);␊ + }␊ + }␊ + ` + ## erc20 crossChainBridging superchain > Snapshot 1 @@ -861,8 +1080,9 @@ Generated by [AVA](https://avajs.dev). import {ERC20BridgeableUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/draft-ERC20BridgeableUpgradeable.sol";␊ import {ERC20PermitUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol";␊ import {Initializable} from "@openzeppelin/contracts/proxy/utils/Initializable.sol";␊ + import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";␊ ␊ - contract MyToken is Initializable, ERC20Upgradeable, ERC20BridgeableUpgradeable, ERC20PermitUpgradeable {␊ + contract MyToken is Initializable, ERC20Upgradeable, ERC20BridgeableUpgradeable, OwnableUpgradeable, ERC20PermitUpgradeable {␊ /// @custom:storage-location erc7201:myProject.MyToken␊ struct MyTokenStorage {␊ address tokenBridge;␊ @@ -878,9 +1098,13 @@ Generated by [AVA](https://avajs.dev). _disableInitializers();␊ }␊ ␊ - function initialize(address tokenBridge_) public initializer {␊ + function initialize(address tokenBridge_, address initialOwner)␊ + public␊ + initializer␊ + {␊ __ERC20_init("MyToken", "MTK");␊ __ERC20Bridgeable_init();␊ + __Ownable_init(initialOwner);␊ __ERC20Permit_init("MyToken");␊ ␊ require(tokenBridge_ != address(0), "Invalid tokenBridge_ address");␊ @@ -896,6 +1120,11 @@ Generated by [AVA](https://avajs.dev). function _getMyTokenStorage() private pure returns (MyTokenStorage storage $) {␊ assembly { $.slot := MYTOKEN_STORAGE_LOCATION }␊ }␊ + ␊ + function setTokenBridge(address tokenBridge_) public onlyOwner {␊ + MyTokenStorage storage $ = _getMyTokenStorage();␊ + $.tokenBridge = tokenBridge_;␊ + }␊ }␊ ` @@ -970,13 +1199,15 @@ Generated by [AVA](https://avajs.dev). import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";␊ import {ERC20Bridgeable} from "@openzeppelin/contracts/token/ERC20/extensions/draft-ERC20Bridgeable.sol";␊ import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";␊ + import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";␊ ␊ - contract MyToken is ERC20, ERC20Bridgeable, ERC20Permit {␊ + contract MyToken is ERC20, ERC20Bridgeable, Ownable, ERC20Permit {␊ address public tokenBridge;␊ error Unauthorized();␊ ␊ - constructor(address tokenBridge_, address recipient)␊ + constructor(address tokenBridge_, address initialOwner, address recipient)␊ ERC20("MyToken", "MTK")␊ + Ownable(initialOwner)␊ ERC20Permit("MyToken")␊ {␊ require(tokenBridge_ != address(0), "Invalid tokenBridge_ address");␊ @@ -989,6 +1220,10 @@ Generated by [AVA](https://avajs.dev). function _checkTokenBridge(address caller) internal view override {␊ if (caller != tokenBridge) revert Unauthorized();␊ }␊ + ␊ + function setTokenBridge(address tokenBridge_) public onlyOwner {␊ + tokenBridge = tokenBridge_;␊ + }␊ }␊ ` diff --git a/packages/core/solidity/src/erc20.test.ts.snap b/packages/core/solidity/src/erc20.test.ts.snap index a99c9f791..bceccc161 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 173a5c384..24c6b3e75 100644 --- a/packages/core/solidity/src/erc20.ts +++ b/packages/core/solidity/src/erc20.ts @@ -16,7 +16,7 @@ import { OptionsError } from './error'; import { toUint256, UINT256_MAX } from './utils/convert-strings'; import { setNamespacedStorage, toStorageStructInstantiation } from './set-namespaced-storage'; -export const crossChainBridgingOptions = [false, 'custom', 'superchain'] as const; +export const crossChainBridgingOptions = [false, 'custom', 'embedded', 'superchain'] as const; export type CrossChainBridging = (typeof crossChainBridgingOptions)[number]; export interface ERC20Options extends CommonOptions { @@ -79,7 +79,13 @@ export function printERC20(opts: ERC20Options = defaults): string { } export function isAccessControlRequired(opts: Partial): boolean { - return opts.mintable || opts.pausable || opts.upgradeable === 'uups'; + return ( + opts.mintable || + opts.pausable || + opts.upgradeable === 'uups' || + opts.crossChainBridging === 'custom' || + opts.crossChainBridging === 'embedded' + ); } export function buildERC20(opts: ERC20Options): ContractBuilder { @@ -308,6 +314,38 @@ function addFlashMint(c: ContractBuilder) { } function addCrossChainBridging( + c: ContractBuilder, + crossChainBridging: 'custom' | 'embedded' | 'superchain', + access: Access, + upgradeable: Upgradeable, + namespacePrefix: string, +) { + if (crossChainBridging === 'embedded') { + addERC20Crosschain(c, access); + } else { + addERC20Bridgeable(c, crossChainBridging, access, upgradeable, namespacePrefix); + } +} + +function addERC20Crosschain(c: ContractBuilder, access: Access) { + const ERC20Crosschain = { + name: 'ERC20Crosschain', + path: '@openzeppelin/contracts/token/ERC20/extensions/ERC20Crosschain.sol', + }; + c.addParent(ERC20Crosschain); + + const CrosschainLinked = { + name: 'CrosschainLinked', + path: '@openzeppelin/contracts/crosschain/CrosschainLinked.sol', + }; + c.addConstructionOnly(CrosschainLinked, [{ lit: 'links' }]); + c.addConstructorArgument({ type: 'CrosschainLinked.Link[] memory', name: 'links' }); + + requireAccessControl(c, functions.setLink, access, 'CROSSCHAIN_LINKER', 'crosschainLinker'); + c.addFunctionCode('_setLink(gateway, counterpart, allowOverride);', functions.setLink); +} + +function addERC20Bridgeable( c: ContractBuilder, crossChainBridging: 'custom' | 'superchain', access: Access, @@ -339,10 +377,14 @@ function addCrossChainBridging( } function addCustomBridging(c: ContractBuilder, access: Access, upgradeable: Upgradeable, namespacePrefix: string) { + if (access === false) { + access = 'ownable'; + } + switch (access) { - case false: case 'ownable': { if (!upgradeable) { + // Add variable and constructor logic using state variable const addedBridge = c.addStateVariable(`address public tokenBridge;`, false); if (addedBridge) { c.addConstructorArgument({ type: 'address', name: 'tokenBridge_' }); @@ -350,7 +392,12 @@ function addCustomBridging(c: ContractBuilder, access: Access, upgradeable: Upgr c.addConstructorCode(`tokenBridge = tokenBridge_;`); } c.setFunctionBody([`if (caller != tokenBridge) revert Unauthorized();`], functions._checkTokenBridge, 'view'); + + // Add bridge setter + requireAccessControl(c, functions.setTokenBridge, access, 'TOKEN_BRIDGE_SETTER', 'tokenBridgeSetter'); + c.addFunctionCode('tokenBridge = tokenBridge_;', functions.setTokenBridge); } else { + // Add variable and constructor logic using namespaced storage setNamespacedStorage(c, ['address tokenBridge;'], namespacePrefix); c.addConstructorArgument({ type: 'address', name: 'tokenBridge_' }); @@ -364,6 +411,13 @@ function addCustomBridging(c: ContractBuilder, access: Access, upgradeable: Upgr functions._checkTokenBridge, 'view', ); + + // Add bridge setter + requireAccessControl(c, functions.setTokenBridge, access, 'TOKEN_BRIDGE_SETTER', 'tokenBridgeSetter'); + c.setFunctionBody( + [toStorageStructInstantiation(c.name), '$.tokenBridge = tokenBridge_;'], + functions.setTokenBridge, + ); } break; } @@ -483,4 +537,18 @@ export const functions = defineFunctions({ kind: 'internal' as const, args: [{ name: 'caller', type: 'address' }], }, + + setTokenBridge: { + kind: 'public' as const, + args: [{ name: 'tokenBridge_', type: 'address' }], + }, + + setLink: { + kind: 'public' as const, + args: [ + { name: 'gateway', type: 'address' }, + { name: 'counterpart', type: 'bytes memory' }, + { name: 'allowOverride', type: 'bool' }, + ], + }, }); diff --git a/packages/core/solidity/src/stablecoin.ts b/packages/core/solidity/src/stablecoin.ts index 6cb726c41..a329c884c 100644 --- a/packages/core/solidity/src/stablecoin.ts +++ b/packages/core/solidity/src/stablecoin.ts @@ -9,6 +9,7 @@ import { defaults as erc20defaults, withDefaults as withERC20Defaults, functions as erc20functions, + isAccessControlRequired as erc20isAccessControlRequired, } from './erc20'; export type Limitations = 'allowlist' | 'blocklist'; @@ -41,7 +42,7 @@ export function printStablecoin(opts: StablecoinOptions = defaults): string { } export function isAccessControlRequired(opts: Partial): boolean { - return opts.mintable || opts.restrictions !== false || opts.freezable || opts.pausable || opts.upgradeable === 'uups'; + return erc20isAccessControlRequired(opts) || !!opts.restrictions || !!opts.freezable; } export function buildStablecoin(opts: StablecoinOptions): Contract { diff --git a/packages/mcp/src/solidity/schemas.ts b/packages/mcp/src/solidity/schemas.ts index 6beeb1ff4..956c993b1 100644 --- a/packages/mcp/src/solidity/schemas.ts +++ b/packages/mcp/src/solidity/schemas.ts @@ -65,6 +65,7 @@ export const erc20Schema = { flashmint: z.boolean().optional().describe(solidityERC20Descriptions.flashmint), crossChainBridging: z .literal('custom') + .or(z.literal('embedded')) .or(z.literal('superchain')) .optional() .describe(solidityERC20Descriptions.crossChainBridging), diff --git a/packages/ui/api/ai-assistant/function-definitions/solidity.ts b/packages/ui/api/ai-assistant/function-definitions/solidity.ts index 6ad098923..1c276a590 100644 --- a/packages/ui/api/ai-assistant/function-definitions/solidity.ts +++ b/packages/ui/api/ai-assistant/function-definitions/solidity.ts @@ -57,7 +57,7 @@ export const solidityERC20AIFunctionDefinition = { crossChainBridging: { anyOf: [ { type: 'boolean', enum: [false] }, - { type: 'string', enum: extractStringEnumValues()(['custom', 'superchain']) }, + { type: 'string', enum: extractStringEnumValues()(['custom', 'embedded', 'superchain']) }, ], description: solidityERC20Descriptions.crossChainBridging, }, diff --git a/packages/ui/src/solidity/ERC20Controls.svelte b/packages/ui/src/solidity/ERC20Controls.svelte index 7c351a45d..d40ec86a7 100644 --- a/packages/ui/src/solidity/ERC20Controls.svelte +++ b/packages/ui/src/solidity/ERC20Controls.svelte @@ -187,6 +187,14 @@ Uses custom bridge contract(s) as authorized token bridge(s). + + {#if !omitFeatures?.includes('superchain')} + + {#if !omitFeatures?.includes('superchain')} + + {#if !omitFeatures?.includes('superchain')}