From 91b45eaa071cbaf8c43262d19b1e38ab2177913f Mon Sep 17 00:00:00 2001 From: "Eugene Y. Q. Shen" Date: Mon, 16 Sep 2024 06:17:29 -0700 Subject: [PATCH] [CHAIN-47] move p into monorepo (#7) --- .gitmodules | 6 ++ p/.gitignore | 15 +++ p/README.md | 3 + p/foundry.toml | 28 +++++ p/lib/forge-std | 1 + p/lib/openzeppelin-contracts-upgradeable | 1 + p/script/DeployToken.s.sol | 37 +++++++ p/src/P.sol | 131 +++++++++++++++++++++++ p/src/interfaces/IDeploy.sol | 16 +++ p/src/interfaces/IDeployer.sol | 54 ++++++++++ p/src/interfaces/IP.sol | 15 +++ p/src/proxy/PProxy.sol | 15 +++ 12 files changed, 322 insertions(+) create mode 100644 p/.gitignore create mode 100644 p/README.md create mode 100644 p/foundry.toml create mode 160000 p/lib/forge-std create mode 160000 p/lib/openzeppelin-contracts-upgradeable create mode 100644 p/script/DeployToken.s.sol create mode 100644 p/src/P.sol create mode 100644 p/src/interfaces/IDeploy.sol create mode 100644 p/src/interfaces/IDeployer.sol create mode 100644 p/src/interfaces/IP.sol create mode 100644 p/src/proxy/PProxy.sol diff --git a/.gitmodules b/.gitmodules index f2b6936..05e497a 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,6 +4,12 @@ [submodule "nest/lib/openzeppelin-contracts-upgradeable"] path = nest/lib/openzeppelin-contracts-upgradeable url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable +[submodule "p/lib/forge-std"] + path = p/lib/forge-std + url = https://github.com/foundry-rs/forge-std +[submodule "p/lib/openzeppelin-contracts-upgradeable"] + path = p/lib/openzeppelin-contracts-upgradeable + url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable [submodule "smart-wallets/lib/forge-std"] path = smart-wallets/lib/forge-std url = https://github.com/foundry-rs/forge-std diff --git a/p/.gitignore b/p/.gitignore new file mode 100644 index 0000000..c38a98e --- /dev/null +++ b/p/.gitignore @@ -0,0 +1,15 @@ +# Compiler files +cache/ +out/ + +# Ignores development broadcast logs +!/broadcast +/broadcast/*/31337/ +/broadcast/*/18230/ +/broadcast/**/dry-run/ + +# Docs +docs/ + +# Dotenv file +.env diff --git a/p/README.md b/p/README.md new file mode 100644 index 0000000..bcc2735 --- /dev/null +++ b/p/README.md @@ -0,0 +1,3 @@ +## P Smart Contracts + +$P is the governance token of Plume. diff --git a/p/foundry.toml b/p/foundry.toml new file mode 100644 index 0000000..398f890 --- /dev/null +++ b/p/foundry.toml @@ -0,0 +1,28 @@ +[profile.default] +solc = "0.8.25" +evm_version = "paris" +src = "src" +out = "out" +libs = ["lib"] +ffi = true +ast = true +build_info = true +extra_output = ["storageLayout"] +optimizer = true +optimizer_runs = 200 # used default optimization level when deploying $P to mainnet + +[fmt] +single_line_statement_blocks = "multi" +multiline_func_header = "params_first" +sort_imports = true +contract_new_lines = true +bracket_spacing = true +int_types = "long" +quote_style = "double" +number_underscore = "thousands" +wrap_comments = true + +remappings = [ + "@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/", + "@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/", +] diff --git a/p/lib/forge-std b/p/lib/forge-std new file mode 160000 index 0000000..1714bee --- /dev/null +++ b/p/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 1714bee72e286e73f76e320d110e0eaf5c4e649d diff --git a/p/lib/openzeppelin-contracts-upgradeable b/p/lib/openzeppelin-contracts-upgradeable new file mode 160000 index 0000000..723f8ca --- /dev/null +++ b/p/lib/openzeppelin-contracts-upgradeable @@ -0,0 +1 @@ +Subproject commit 723f8cab09cdae1aca9ec9cc1cfa040c2d4b06c1 diff --git a/p/script/DeployToken.s.sol b/p/script/DeployToken.s.sol new file mode 100644 index 0000000..d4e43a0 --- /dev/null +++ b/p/script/DeployToken.s.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import "forge-std/Script.sol"; + +import { P } from "../src/P.sol"; +import { IDeployer } from "../src/interfaces/IDeployer.sol"; +import { PProxy } from "../src/proxy/PProxy.sol"; + +/** + * @title DeployToken + * @author Eugene Y. Q. Shen + * @notice Deploys P to a deterministic address 0x4C1746A800D224393fE2470C70A35717eD4eA5F1 + */ +contract DeployToken is Script { + + bytes32 private constant DEPLOY_SALT = keccak256("P"); + address private constant DEPLOYER_ADDRESS = 0x6513Aedb4D1593BA12e50644401D976aebDc90d8; + + function run(address admin) external { + vm.startBroadcast(); + + P pImpl = new P(); + console.log("pImpl deployed to:", address(pImpl)); + + address pProxy = IDeployer(DEPLOYER_ADDRESS).deploy( + abi.encodePacked( + type(PProxy).creationCode, abi.encode(pImpl, abi.encodeWithSelector(P.initialize.selector, admin)) + ), + DEPLOY_SALT + ); + console.log("pProxy deployed to:", pProxy); + + vm.stopBroadcast(); + } + +} diff --git a/p/src/P.sol b/p/src/P.sol new file mode 100644 index 0000000..3892ba6 --- /dev/null +++ b/p/src/P.sol @@ -0,0 +1,131 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { AccessControlUpgradeable } from "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol"; +import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; +import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import { ERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; +import { ERC20BurnableUpgradeable } from + "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20BurnableUpgradeable.sol"; +import { ERC20PausableUpgradeable } from + "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PausableUpgradeable.sol"; +import { ERC20PermitUpgradeable } from + "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol"; + +/** + * @title P + * @author Eugene Y. Q. Shen + * @notice ERC20 token that is the governance token for Plume Network + */ +contract P is + Initializable, + AccessControlUpgradeable, + ERC20Upgradeable, + ERC20BurnableUpgradeable, + ERC20PausableUpgradeable, + ERC20PermitUpgradeable, + UUPSUpgradeable +{ + + // Constants + + /// @notice Role for the admin of P + bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE"); + /// @notice Role for the upgrader of P + bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE"); + /// @notice Role for the minter of P + bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE"); + /// @notice Role for the burner of P + bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE"); + /// @notice Role for the pauser of P + bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE"); + + // Initializer + + /// @custom:oz-upgrades-unsafe-allow constructor + constructor() { + _disableInitializers(); + } + + /** + * @notice Initialize P + * @dev Give all roles to the admin address passed into the constructor + * @param admin Address of the admin of P + */ + function initialize(address admin) public initializer { + __ERC20_init("Plume", "P"); + __ERC20Burnable_init(); + __ERC20Pausable_init(); + __AccessControl_init(); + __UUPSUpgradeable_init(); + __ERC20Permit_init("Plume"); + + _grantRole(DEFAULT_ADMIN_ROLE, admin); + _grantRole(ADMIN_ROLE, admin); + _grantRole(MINTER_ROLE, admin); + _grantRole(BURNER_ROLE, admin); + _grantRole(PAUSER_ROLE, admin); + _grantRole(UPGRADER_ROLE, admin); + } + + // Override Functions + + /** + * @notice Revert when `msg.sender` is not authorized to upgrade the contract + * @param newImplementation Address of the new implementation + */ + function _authorizeUpgrade(address newImplementation) internal override onlyRole(UPGRADER_ROLE) { } + + /** + * @notice Update the balance of `from` and `to` before and after token transfer + * @param from Address to transfer tokens from + * @param to Address to transfer tokens to + * @param value Amount of tokens to transfer + */ + function _update( + address from, + address to, + uint256 value + ) internal override(ERC20Upgradeable, ERC20PausableUpgradeable) { + super._update(from, to, value); + } + + // User Functions + + /** + * @notice Mint new P tokens + * @dev Only the minter can mint new tokens + * @param to Address to mint tokens to + * @param amount Amount of tokens to mint + */ + function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) { + _mint(to, amount); + } + + /** + * @notice Burn P tokens + * @dev Only the burner can burn tokens + * @param from Address to burn tokens from + * @param amount Amount of tokens to burn + */ + function burn(address from, uint256 amount) external onlyRole(BURNER_ROLE) { + _burn(from, amount); + } + + /** + * @notice Pause the contract + * @dev Only the pauser can pause the contract + */ + function pause() external onlyRole(PAUSER_ROLE) { + _pause(); + } + + /** + * @notice Unpause the contract + * @dev Only the pauser can unpause the contract + */ + function unpause() external onlyRole(PAUSER_ROLE) { + _unpause(); + } + +} diff --git a/p/src/interfaces/IDeploy.sol b/p/src/interfaces/IDeploy.sol new file mode 100644 index 0000000..90774af --- /dev/null +++ b/p/src/interfaces/IDeploy.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +// https://github.com/axelarnetwork/axelar-gmp-sdk-solidity/blob/5f15a1036215f8b9c8eeb6438d352172b430dd38/contracts/interfaces/IDeploy.sol + +pragma solidity ^0.8.0; + +/** + * @title IDeploy Interface + * @notice This interface defines the errors for a contract that is responsible for deploying new contracts. + */ +interface IDeploy { + + error EmptyBytecode(); + error AlreadyDeployed(); + error DeployFailed(); + +} diff --git a/p/src/interfaces/IDeployer.sol b/p/src/interfaces/IDeployer.sol new file mode 100644 index 0000000..817255c --- /dev/null +++ b/p/src/interfaces/IDeployer.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT +// https://github.com/axelarnetwork/axelar-gmp-sdk-solidity/blob/5f15a1036215f8b9c8eeb6438d352172b430dd38/contracts/interfaces/IDeployer.sol + +pragma solidity ^0.8.0; + +import { IDeploy } from "./IDeploy.sol"; + +/** + * @title IDeployer Interface + * @notice This interface defines the contract responsible for deploying and optionally initializing new contracts + * via a specified deployment method. + */ +interface IDeployer is IDeploy { + + error DeployInitFailed(); + + event Deployed(address indexed deployedAddress, address indexed sender, bytes32 indexed salt, bytes32 bytecodeHash); + + /** + * @notice Deploys a contract using a deployment method defined by derived contracts. + * @param bytecode The bytecode of the contract to be deployed + * @param salt A salt to influence the contract address + * @return deployedAddress_ The address of the deployed contract + */ + function deploy(bytes memory bytecode, bytes32 salt) external payable returns (address deployedAddress_); + + /** + * @notice Deploys a contract using a deployment method defined by derived contracts and initializes it. + * @param bytecode The bytecode of the contract to be deployed + * @param salt A salt to influence the contract address + * @param init Init data used to initialize the deployed contract + * @return deployedAddress_ The address of the deployed contract + */ + function deployAndInit( + bytes memory bytecode, + bytes32 salt, + bytes calldata init + ) external payable returns (address deployedAddress_); + + /** + * @notice Returns the address where a contract will be stored if deployed via {deploy} or {deployAndInit} by + * `sender`. + * @param bytecode The bytecode of the contract + * @param sender The address that will deploy the contract + * @param salt The salt that will be used to influence the contract address + * @return deployedAddress_ The address that the contract will be deployed to + */ + function deployedAddress( + bytes calldata bytecode, + address sender, + bytes32 salt + ) external view returns (address deployedAddress_); + +} diff --git a/p/src/interfaces/IP.sol b/p/src/interfaces/IP.sol new file mode 100644 index 0000000..099407f --- /dev/null +++ b/p/src/interfaces/IP.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; +import { IERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol"; + +interface IP is IERC20, IERC20Metadata, IERC20Permit { + + function mint(address to, uint256 amount) external; + function burn(address from, uint256 amount) external; + function pause() external; + function unpause() external; + +} diff --git a/p/src/proxy/PProxy.sol b/p/src/proxy/PProxy.sol new file mode 100644 index 0000000..9012371 --- /dev/null +++ b/p/src/proxy/PProxy.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; + +/** + * @title PProxy + * @author Eugene Y. Q. Shen + * @notice Proxy contract for P + */ +contract PProxy is ERC1967Proxy { + + constructor(address logic, bytes memory data) ERC1967Proxy(logic, data) { } + +}