diff --git a/.env.template b/.env.template index 033556dc..acea525a 100644 --- a/.env.template +++ b/.env.template @@ -16,7 +16,7 @@ RLC_OFT_TOKEN_NAME="RLC OFT" RLC_TOKEN_SYMBOL="RLC" # Transaction Settings -SENDER_ADDRESS=0x +OWNER_ADDRESS=0x # API Keys ETHERSCAN_API_KEY= diff --git a/.gitmodules b/.gitmodules index 81a210d9..f69ae656 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,6 +10,12 @@ [submodule "lib/rlc-faucet-contract"] path = lib/rlc-faucet-contract url = https://github.com/iExecBlockchainComputing/rlc-faucet-contract +[submodule "lib/openzeppelin-contracts-upgradeable"] + path = lib/openzeppelin-contracts-upgradeable + url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable +[submodule "lib/openzeppelin-foundry-upgrades"] + path = lib/openzeppelin-foundry-upgrades + url = https://github.com/OpenZeppelin/openzeppelin-foundry-upgrades [submodule "lib/openzeppelin-contracts"] path = lib/openzeppelin-contracts url = https://github.com/OpenZeppelin/openzeppelin-contracts diff --git a/Makefile b/Makefile index ab93ebeb..af85e53e 100644 --- a/Makefile +++ b/Makefile @@ -29,14 +29,14 @@ deploy-oft: configure-adapter: @echo "Configuring RLCAdapter on SEPOLIA..." - forge script script/ConfigureRLCAdapter.s.sol:Configure \ + forge script script/RLCAdapter.s.sol:Configure \ --rpc-url $(SEPOLIA_RPC_URL) \ --account $(ACCOUNT) \ --broadcast \ -vvv configure-oft: @echo "Configuring RLCOFT on Arbitrum SEPOLIA..." - forge script script/ConfigureRLCOFT.s.sol:Configure \ + forge script script/RLCOFT.s.sol:Configure \ --rpc-url $(ARBITRUM_SEPOLIA_RPC_URL) \ --account $(ACCOUNT) \ --broadcast \ @@ -64,7 +64,7 @@ verify-adapter: forge verify-contract \ --chain-id 11155111 \ --watch \ - --constructor-args $(shell cast abi-encode "constructor(address,address,address)" $(RLC_SEPOLIA_ADDRESS) $(LAYER_ZERO_SEPOLIA_ENDPOINT_ADDRESS) $(SENDER_ADDRESS)) \ + --constructor-args $(shell cast abi-encode "constructor(address,address,address)" $(RLC_SEPOLIA_ADDRESS) $(LAYER_ZERO_SEPOLIA_ENDPOINT_ADDRESS) $(OWNER_ADDRESS)) \ --etherscan-api-key $(ETHERSCAN_API_KEY) \ $(RLC_SEPOLIA_ADAPTER_ADDRESS) \ src/RLCAdapter.sol:RLCAdapter @@ -74,7 +74,7 @@ verify-oft: forge verify-contract \ --chain-id 421614 \ --watch \ - --constructor-args $(shell cast abi-encode "constructor(string,string,address,address)" $(RLC_OFT_TOKEN_NAME) $(RLC_TOKEN_SYMBOL) $(LAYER_ZERO_ARBITRUM_SEPOLIA_ENDPOINT_ADDRESS) $(SENDER_ADDRESS)) \ + --constructor-args $(shell cast abi-encode "constructor(string,string,address,address)" $(RLC_OFT_TOKEN_NAME) $(RLC_TOKEN_SYMBOL) $(LAYER_ZERO_ARBITRUM_SEPOLIA_ENDPOINT_ADDRESS) $(OWNER_ADDRESS)) \ --etherscan-api-key $(ARBISCAN_API_KEY) \ $(RLC_ARBITRUM_SEPOLIA_OFT_ADDRESS) \ src/RLCOFT.sol:RLCOFT diff --git a/README.md b/README.md index 2c50c63e..f881031e 100644 --- a/README.md +++ b/README.md @@ -20,21 +20,32 @@ The system consists of two main components: ## Installation 1. Clone the repository + ```bash git clone https://github.com/iExecBlockchainComputing/rlc-multichain.git cd rlc-multichain ``` 2. Install dependencies + ```bash forge install ``` 3. Create a `.env` file + ```sh cp .env.template .env # and edit .env content ``` +**Note:** To run scripts, you must save a wallet in the Foundry keystore. Use the following command to import a wallet with a raw private key: + +```bash +cast wallet import --private-key +``` + +Alternatively, you can use a mnemonic by specifying the `--mnemonic-path` option. Remember the `` you choose, and set it in your `.env` file under the `ACCOUNT` field. + ## Contract Overview Instead of duplicating code that may become outdated, here are links to the key contracts in the repository: @@ -47,21 +58,25 @@ Instead of duplicating code that may become outdated, here are links to the key The deployment process involves four steps: 1. Deploy the RLCAdapter on Ethereum Sepolia: + ```bash make deploy-adapter ``` 2. Deploy the RLCOFT on Arbitrum Sepolia: + ```bash make deploy-oft ``` 3. Configure the RLCAdapter to trust the RLCOFT contract: + ```bash make configure-adapter ``` 4. Configure the RLCOFT to trust the RLCAdapter contract: + ```bash make configure-oft ``` @@ -79,6 +94,7 @@ make send-tokens-to-arbitrum-sepolia ``` This will: + 1. Approve the RLCAdapter to spend your RLC tokens 2. Initiate the cross-chain transfer through LayerZero 3. Lock tokens in the adapter and mint equivalent tokens on Arbitrum @@ -90,6 +106,7 @@ make send-tokens-to-sepolia ``` This will: + 1. Burn RLCOFT tokens on Arbitrum 2. Send a cross-chain message to the adapter 3. Release the original RLC tokens on Ethereum @@ -118,6 +135,7 @@ This will: ## Gas Costs and Fees LayerZero transactions require fees to cover: + 1. Gas on the source chain 2. Gas on the destination chain (prepaid) 3. LayerZero relayer fees @@ -126,9 +144,8 @@ The scripts automatically calculate these fees and include them in the transacti ## Troubleshooting - ## References - [LayerZero Documentation](https://layerzero.gitbook.io/docs/) - [OFT Contracts](https://github.com/LayerZero-Labs/solidity-examples/tree/main/contracts/token/oft) -- [iExec Platform Documentation](https://docs.iex.ec/) \ No newline at end of file +- [iExec Platform Documentation](https://docs.iex.ec/) diff --git a/foundry.toml b/foundry.toml index badc4a84..37bf8d29 100644 --- a/foundry.toml +++ b/foundry.toml @@ -3,13 +3,26 @@ src = "src" out = "out" libs = ["lib"] fs_permissions = [{ access = "read-write", path = "./" }] +optimizer = true +optimizer_runs = 200 +via_ir = true + +## Needed by openzepplin upgrade plugin +ffi = true +ast = true +build_info = true +extra_output = ["storageLayout"] remappings = [ + '@layerzerolabs/oft-evm-upgradeable/=lib/devtools/packages/oft-evm-upgradeable/', '@layerzerolabs/oft-evm/=lib/devtools/packages/oft-evm/', + '@layerzerolabs/oapp-evm-upgradeable/=lib/devtools/packages/oapp-evm-upgradeable/', '@layerzerolabs/oapp-evm/=lib/devtools/packages/oapp-evm/', '@layerzerolabs/lz-evm-protocol-v2/=lib/layerzero-v2/packages/layerzero-v2/evm/protocol', + '@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/', '@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/', + '@openzeppelin-foundry/contracts/=lib/openzeppelin-foundry-upgrades/src/', ] -# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options \ No newline at end of file +# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/lib/openzeppelin-contracts b/lib/openzeppelin-contracts index 69c8def5..e4f70216 160000 --- a/lib/openzeppelin-contracts +++ b/lib/openzeppelin-contracts @@ -1 +1 @@ -Subproject commit 69c8def5f222ff96f2b5beff05dfba996368aa79 +Subproject commit e4f70216d759d8e6a64144a9e1f7bbeed78e7079 diff --git a/lib/openzeppelin-contracts-upgradeable b/lib/openzeppelin-contracts-upgradeable new file mode 160000 index 00000000..60b305a8 --- /dev/null +++ b/lib/openzeppelin-contracts-upgradeable @@ -0,0 +1 @@ +Subproject commit 60b305a8f3ff0c7688f02ac470417b6bbf1c4d27 diff --git a/lib/openzeppelin-foundry-upgrades b/lib/openzeppelin-foundry-upgrades new file mode 160000 index 00000000..cbce1e00 --- /dev/null +++ b/lib/openzeppelin-foundry-upgrades @@ -0,0 +1 @@ +Subproject commit cbce1e00305e943aa1661d43f41e5ac72c662b07 diff --git a/script/ConfigureRLCAdapter.s.sol b/script/ConfigureRLCAdapter.s.sol deleted file mode 100644 index 07e7c300..00000000 --- a/script/ConfigureRLCAdapter.s.sol +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-FileCopyrightText: 2025 IEXEC BLOCKCHAIN TECH -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.22; - -import {Script, console} from "forge-std/Script.sol"; -import {RLCAdapter} from "../src/RLCAdapter.sol"; - -contract Configure is Script { - function run() external { - vm.startBroadcast(); - - // RLCAdapter on Ethereum Sepolia - address adapterAddress = vm.envAddress("RLC_SEPOLIA_ADAPTER_ADDRESS"); // Add your RLCAdapter address here - RLCAdapter adapter = RLCAdapter(adapterAddress); - - // RLCOFT on Arbitrum Sepolia - address oftAddress = vm.envAddress("RLC_ARBITRUM_SEPOLIA_OFT_ADDRESS"); - uint16 arbitrumSepoliaChainId = uint16(vm.envUint("LAYER_ZERO_ARBITRUM_SEPOLIA_CHAIN_ID")); - - // Set trusted remote - adapter.setPeer(arbitrumSepoliaChainId, bytes32(uint256(uint160(oftAddress)))); - - vm.stopBroadcast(); - } -} diff --git a/script/ConfigureRLCOFT.s.sol b/script/ConfigureRLCOFT.s.sol deleted file mode 100644 index 06fa150a..00000000 --- a/script/ConfigureRLCOFT.s.sol +++ /dev/null @@ -1,25 +0,0 @@ -// SPDX-FileCopyrightText: 2025 IEXEC BLOCKCHAIN TECH -// SPDX-License-Identifier: Apache-2.0 -pragma solidity ^0.8.22; - -import {Script, console} from "forge-std/Script.sol"; -import {RLCOFT} from "../src/RLCOFT.sol"; - -contract Configure is Script { - function run() external { - vm.startBroadcast(); - - // RLCOFT on Arbitrum Sepolia - address oftAddress = vm.envAddress("RLC_ARBITRUM_SEPOLIA_OFT_ADDRESS"); - RLCOFT oft = RLCOFT(oftAddress); - - // RLCAdapter on Ethereum Sepolia - address adapterAddress = vm.envAddress("RLC_SEPOLIA_ADAPTER_ADDRESS"); // Add your RLCAdapter address here - uint16 ethereumSepoliaChainId = uint16(vm.envUint("LAYER_ZERO_SEPOLIA_CHAIN_ID")); // LayerZero chain ID for Ethereum Sepolia - - // Set trusted remote - oft.setPeer(ethereumSepoliaChainId, bytes32(uint256(uint160(adapterAddress)))); - - vm.stopBroadcast(); - } -} diff --git a/script/RLCAdapter.s.sol b/script/RLCAdapter.s.sol index f1b504af..2baa7d38 100644 --- a/script/RLCAdapter.s.sol +++ b/script/RLCAdapter.s.sol @@ -3,24 +3,50 @@ pragma solidity ^0.8.22; import {Script, console} from "forge-std/Script.sol"; +import {Upgrades, Options} from "@openzeppelin-foundry/contracts/Upgrades.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import {RLCAdapter} from "../src/RLCAdapter.sol"; import {EnvUtils} from "./UpdateEnvUtils.sol"; contract Deploy is Script { - function setUp() public {} - function run() external { vm.startBroadcast(); address rlcToken = vm.envAddress("RLC_SEPOLIA_ADDRESS"); // RLC token address on sepolia testnet address lzEndpoint = vm.envAddress("LAYER_ZERO_SEPOLIA_ENDPOINT_ADDRESS"); // LayerZero sepolia endpoint - address delegate = vm.envAddress("SENDER_ADDRESS"); // Your actual wallet address + address ownerAddress = vm.envAddress("OWNER_ADDRESS"); // Your actual wallet address + + // Deploy the RLCAdapter contract + RLCAdapter rlcAdapterImplementation = new RLCAdapter(rlcToken, lzEndpoint); + console.log("RLCAdapter implementation deployed at:", address(rlcAdapterImplementation)); - RLCAdapter rlcAdapter = new RLCAdapter(rlcToken, lzEndpoint, delegate); - console.log("RLCAdapter deployed at:", address(rlcAdapter)); + // Deploy the proxy contract + ERC1967Proxy rlcAdapterProxy = new ERC1967Proxy( + address(rlcAdapterImplementation), + abi.encodeWithSelector(rlcAdapterImplementation.initialize.selector, ownerAddress) + ); + console.log("RLCAdapter proxy deployed at:", address(rlcAdapterProxy)); vm.stopBroadcast(); - EnvUtils.updateEnvVariable("RLC_SEPOLIA_ADAPTER_ADDRESS", address(rlcAdapter)); + EnvUtils.updateEnvVariable("RLC_SEPOLIA_ADAPTER_ADDRESS", address(rlcAdapterProxy)); + } +} + +contract Configure is Script { + function run() external { + vm.startBroadcast(); + + // RLCAdapter on Ethereum Sepolia + address adapterAddress = vm.envAddress("RLC_SEPOLIA_ADAPTER_ADDRESS"); // Read this variable from .env file + RLCAdapter adapter = RLCAdapter(adapterAddress); + + // RLCOFT on Arbitrum Sepolia + address oftAddress = vm.envAddress("RLC_ARBITRUM_SEPOLIA_OFT_ADDRESS"); + uint16 arbitrumSepoliaChainId = uint16(vm.envUint("LAYER_ZERO_ARBITRUM_SEPOLIA_CHAIN_ID")); //TODO: remove or make it chain agnostic + // Set trusted remote + adapter.setPeer(arbitrumSepoliaChainId, bytes32(uint256(uint160(oftAddress)))); + + vm.stopBroadcast(); } } diff --git a/script/RLCOFT.s.sol b/script/RLCOFT.s.sol index 63f731f9..b8f832f6 100644 --- a/script/RLCOFT.s.sol +++ b/script/RLCOFT.s.sol @@ -3,25 +3,51 @@ pragma solidity ^0.8.22; import {Script, console} from "forge-std/Script.sol"; +import {Upgrades, Options} from "openzeppelin-foundry-upgrades/Upgrades.sol"; import {RLCOFT} from "../src/RLCOFT.sol"; import {EnvUtils} from "./UpdateEnvUtils.sol"; +import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; contract Deploy is Script { - function setUp() public {} - function run() external { vm.startBroadcast(); string memory name = vm.envString("RLC_OFT_TOKEN_NAME"); string memory symbol = vm.envString("RLC_TOKEN_SYMBOL"); address lzEndpoint = vm.envAddress("LAYER_ZERO_ARBITRUM_SEPOLIA_ENDPOINT_ADDRESS"); - address delegate = vm.envAddress("SENDER_ADDRESS"); + address owner = vm.envAddress("OWNER_ADDRESS"); + + RLCOFT rlcOFTImplementation = new RLCOFT(lzEndpoint); + console.log("RLCOFT implementation deployed at:", address(rlcOFTImplementation)); - RLCOFT rlcOFT = new RLCOFT(name, symbol, lzEndpoint, delegate); - console.log("rlcOFT deployed at:", address(rlcOFT)); + // Deploy the proxy contract + ERC1967Proxy rlcOFTProxy = new ERC1967Proxy( + address(rlcOFTImplementation), + abi.encodeWithSelector(rlcOFTImplementation.initialize.selector, name, symbol, owner) + ); + console.log("RLCOFT proxy deployed at:", address(rlcOFTProxy)); vm.stopBroadcast(); - EnvUtils.updateEnvVariable("RLC_ARBITRUM_SEPOLIA_OFT_ADDRESS", address(rlcOFT)); + EnvUtils.updateEnvVariable("RLC_ARBITRUM_SEPOLIA_OFT_ADDRESS", address(rlcOFTProxy)); + } +} + +contract Configure is Script { + function run() external { + vm.startBroadcast(); + + // RLCOFT on Arbitrum Sepolia + address oftAddress = vm.envAddress("RLC_ARBITRUM_SEPOLIA_OFT_ADDRESS"); + RLCOFT oft = RLCOFT(oftAddress); + + // RLCAdapter on Ethereum Sepolia + address adapterAddress = vm.envAddress("RLC_SEPOLIA_ADAPTER_ADDRESS"); // Read this variable from .env file + uint16 ethereumSepoliaChainId = uint16(vm.envUint("LAYER_ZERO_SEPOLIA_CHAIN_ID")); // LayerZero chain ID for Ethereum Sepolia - TODO: remove or make it chain agnostic + + // Set trusted remote + oft.setPeer(ethereumSepoliaChainId, bytes32(uint256(uint160(adapterAddress)))); + + vm.stopBroadcast(); } } diff --git a/script/SendArbitrumToEthereum.s.sol b/script/SendArbitrumToEthereum.s.sol index 88a44f76..5ea21f4b 100644 --- a/script/SendArbitrumToEthereum.s.sol +++ b/script/SendArbitrumToEthereum.s.sol @@ -25,7 +25,7 @@ contract SendTokensToSepolia is Script { // Transfer parameters uint16 destinationChainId = uint16(vm.envUint("LAYER_ZERO_SEPOLIA_CHAIN_ID")); // LayerZero chain ID for Ethereum Sepolia - address recipientAddress = vm.envAddress("SENDER_ADDRESS"); // Recipient on Ethereum Sepolia + address recipientAddress = vm.envAddress("OWNER_ADDRESS"); // Recipient on Ethereum Sepolia console.log("Recipient: %s", recipientAddress); uint256 amount = 5 * 10 ** 18; // RLC tokens (adjust the amount as needed) diff --git a/script/SendEthereumToArbitrum.s.sol b/script/SendEthereumToArbitrum.s.sol index 9dba2b37..d5868cd7 100644 --- a/script/SendEthereumToArbitrum.s.sol +++ b/script/SendEthereumToArbitrum.s.sol @@ -28,7 +28,7 @@ contract SendTokensToArbitrumSepolia is Script { // Transfer parameters uint16 destinationChainId = uint16(vm.envUint("LAYER_ZERO_ARBITRUM_SEPOLIA_CHAIN_ID")); // LayerZero chain ID for Arbitrum Sepolia - address recipientAddress = vm.envAddress("SENDER_ADDRESS"); // Recipient on Arbitrum (your address) + address recipientAddress = vm.envAddress("OWNER_ADDRESS"); // Recipient on Arbitrum (your address) uint256 amount = 5 * 10 ** 9; // RLC tokens (adjust the amount as needed) // First, approve the adapter to spend your tokens diff --git a/src/RLCAdapter.sol b/src/RLCAdapter.sol index 4d90ebfa..df3bcc32 100644 --- a/src/RLCAdapter.sol +++ b/src/RLCAdapter.sol @@ -3,13 +3,44 @@ pragma solidity ^0.8.22; -import {OFTAdapter} from "@layerzerolabs/oft-evm/contracts/OFTAdapter.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {OFTAdapterUpgradeable} from "@layerzerolabs/oft-evm-upgradeable/contracts/oft/OFTAdapterUpgradeable.sol"; +import {AccessControlDefaultAdminRulesUpgradeable} from + "@openzeppelin/contracts-upgradeable/access/extensions/AccessControlDefaultAdminRulesUpgradeable.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; /// @notice OFTAdapter uses a deployed ERC-20 token and safeERC20 to interact with the OFTCore contract. -contract RLCAdapter is Ownable, OFTAdapter { - constructor(address _token, address _lzEndpoint, address _owner) - Ownable(_owner) - OFTAdapter(_token, _lzEndpoint, _owner) - {} +// There can only be one OFT Adapter deployed per chain. Multiple OFT Adapters break omnichain unified +// liquidity by effectively creating token pools. +contract RLCAdapter is OFTAdapterUpgradeable, UUPSUpgradeable, AccessControlDefaultAdminRulesUpgradeable { + // Upgrader Role RLCAdapter contracts. + bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE"); + // Bridge Minter Role required for minting RLC Token + bytes32 public constant BRIDGE_ROLE = keccak256("BRIDGE_ROLE"); + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor(address _token, address _lzEndpoint) OFTAdapterUpgradeable(_token, _lzEndpoint) { + _disableInitializers(); + } + + /// @notice Initializes the contract + /// @param _owner Address of the contract owner + function initialize(address _owner) public initializer { + __Ownable_init(_owner); + __OFTAdapter_init(_owner); + __UUPSUpgradeable_init(); + __AccessControlDefaultAdminRules_init(0, _owner); + _grantRole(UPGRADER_ROLE, _owner); + } + + function owner() + public + view + override(OwnableUpgradeable, AccessControlDefaultAdminRulesUpgradeable) + returns (address) + { + return OwnableUpgradeable.owner(); + } + + function _authorizeUpgrade(address newImplementation) internal virtual override {} } diff --git a/src/RLCOFT.sol b/src/RLCOFT.sol index 360aa427..37c899ad 100644 --- a/src/RLCOFT.sol +++ b/src/RLCOFT.sol @@ -2,16 +2,41 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.22; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; -import {OFT} from "@layerzerolabs/oft-evm/contracts/OFT.sol"; +import {OFTUpgradeable} from "@layerzerolabs/oft-evm-upgradeable/contracts/oft/OFTUpgradeable.sol"; +import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import {AccessControlDefaultAdminRulesUpgradeable} from + "@openzeppelin/contracts-upgradeable/access/extensions/AccessControlDefaultAdminRulesUpgradeable.sol"; import {ITokenSpender} from "src/ITokenSpender.sol"; +import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; /// @notice OFT is an ERC-20 token that extends the OFTCore contract. -contract RLCOFT is Ownable, OFT { - constructor(string memory _name, string memory _symbol, address _lzEndpoint, address _delegate) - Ownable(_delegate) - OFT(_name, _symbol, _lzEndpoint, _delegate) - {} +contract RLCOFT is OFTUpgradeable, UUPSUpgradeable, AccessControlDefaultAdminRulesUpgradeable { + // Upgrader Role RLCAdapter contracts. + bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE"); + // Bridge Minter Role required for minting RLC Token + bytes32 public constant BRIDGE_ROLE = keccak256("BRIDGE_ROLE"); + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor(address _lzEndpoint) OFTUpgradeable(_lzEndpoint) { + _disableInitializers(); + } + + /// @notice Initializes the contract + /// @param _name Name of the token + /// @param _symbol Symbol of the token + /// @param _owner Address of the contract owner + function initialize(string memory _name, string memory _symbol, address _owner) public initializer { + __Ownable_init(_owner); + __OFT_init(_name, _symbol, _owner); + __UUPSUpgradeable_init(); + __AccessControlDefaultAdminRules_init(0, _owner); + _grantRole(UPGRADER_ROLE, _owner); + } + + /// @notice Authorizes an upgrade to a new implementation + /// @dev Can only be called by the owner + /// @param newImplementation Address of the new implementation + function _authorizeUpgrade(address newImplementation) internal override onlyRole(UPGRADER_ROLE) {} /** * @dev Override the decimals function to return 9 instead of the default 18 @@ -26,6 +51,15 @@ contract RLCOFT is Ownable, OFT { return true; } + function owner() + public + view + override(OwnableUpgradeable, AccessControlDefaultAdminRulesUpgradeable) + returns (address) + { + return OwnableUpgradeable.owner(); + } + /** * Approve and then call the approved contract in a single tx */