diff --git a/.debug.env.example b/.debug.env.example new file mode 100644 index 0000000..74bfec6 --- /dev/null +++ b/.debug.env.example @@ -0,0 +1,10 @@ +# Fork block number to debug +BLOCK=0x0 +# Caller +FROM=0x0000000000000000000000000000000000000000 +# Callee +TO=0x0000000000000000000000000000000000000000 +# Sent Value +VALUE=0x27cdb0997a65b2de99 +# Call Data +CALLDATA=0x0 diff --git a/.gitmodules b/.gitmodules index 8a76839..ca8e125 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,7 +3,10 @@ url = https://github.com/foundry-rs/forge-std [submodule "lib/openzeppelin-contracts"] path = lib/openzeppelin-contracts - url = https://github.com/openzeppelin/openzeppelin-contracts + url = https://github.com/OpenZeppelin/openzeppelin-contracts [submodule "lib/solady"] path = lib/solady url = https://github.com/Vectorized/solady +[submodule "lib/contract-libs"] + path = lib/contract-libs + url = https://github.com/axieinfinity/contract-libs diff --git a/debug.sh b/debug.sh new file mode 100755 index 0000000..f09e41d --- /dev/null +++ b/debug.sh @@ -0,0 +1,39 @@ +# Source (or "dot") the .env file to load environment variables +if [ -f .env ]; then + source .debug.env +else + echo "Error: .debug.env file not found." +fi + +verify_arg="" +extra_argument="" +op_command="" + +for arg in "$@"; do + case $arg in + --trezor) + op_command="" + extra_argument+=trezor@ + ;; + --broadcast) + op_command="op run --env-file="./.env" --" + ;; + --log) + set -- "${@/#--log/}" + extra_argument+=log@ + ;; + *) ;; + esac +done + +# Remove the @ character from the end of extra_argument +extra_argument="${extra_argument%%@}" + +echo Debug Tx... +echo From: ${FROM} +echo To: ${TO} +echo Value: ${VALUE} +echo Calldata: +cast pretty-calldata ${CALLDATA} +calldata=$(cast calldata 'trace(uint256,address,address,uint256,bytes)' ${BLOCK} ${FROM} ${TO} ${VALUE} ${CALLDATA}) +${op_command} forge script ${verify_arg} --legacy ${@} script/OnchainDebugger.s.sol --sig 'run(bytes,string)' ${calldata} "${extra_argument}" diff --git a/deployments/ronin-mainnet/.chainId b/deployments/ronin-mainnet/.chainId new file mode 100644 index 0000000..145262f --- /dev/null +++ b/deployments/ronin-mainnet/.chainId @@ -0,0 +1 @@ +2020 \ No newline at end of file diff --git a/deployments/ronin-testnet/.chainId b/deployments/ronin-testnet/.chainId new file mode 100644 index 0000000..c5e035c --- /dev/null +++ b/deployments/ronin-testnet/.chainId @@ -0,0 +1 @@ +2021 \ No newline at end of file diff --git a/foundry.toml b/foundry.toml index 934c3e3..726a500 100644 --- a/foundry.toml +++ b/foundry.toml @@ -2,10 +2,10 @@ src = "src" out = "out" libs = ["lib"] - +ffi = true # See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options -solc = '0.8.21' +solc = '0.8.23' extra_output = ["devdoc", "userdoc", "storagelayout"] evm_version = 'istanbul' use_literal_content = true @@ -14,3 +14,11 @@ fs_permissions = [{ access = "read-write", path = "./" }] [fmt] line_length = 120 tab_width = 2 +bracket_spacing = true + +[rpc_endpoints] +ethereum = "https://eth.llamarpc.com" +goerli = "https://ethereum-goerli.publicnode.com" +ronin-mainnet = "https://api-partner.roninchain.com/rpc" +ronin-testnet = "https://saigon-archive.roninchain.com/rpc" +localhost = "http://localhost:8545" \ No newline at end of file diff --git a/lib/contract-libs b/lib/contract-libs new file mode 160000 index 0000000..2388600 --- /dev/null +++ b/lib/contract-libs @@ -0,0 +1 @@ +Subproject commit 2388600fbc1874ac588c5f779c498beb52213ca3 diff --git a/remappings.txt b/remappings.txt index a72797d..952eae5 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,3 +1 @@ -ds-test/=lib/forge-std/lib/ds-test/src/ -forge-std/=lib/forge-std/src/ -@openzeppelin/=lib/openzeppelin-contracts/ \ No newline at end of file +foundry-deployment-kit/=script/ \ No newline at end of file diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..52ff955 --- /dev/null +++ b/run.sh @@ -0,0 +1,27 @@ +verify_arg="" +extra_argument="" +op_command="op run --env-file="./.env" --" + +for arg in "$@"; do + case $arg in + --trezor) + op_command="" + extra_argument+=trezor@ + ;; + --broadcast) + op_command="op run --env-file="./.env" --" + # verify_arg="--verify --verifier sourcify --verifier-url https://sourcify.roninchain.com/server/" + ;; + --log) + set -- "${@/#--log/}" + extra_argument+=log@ + ;; + *) ;; + esac +done + +# Remove the @ character from the end of extra_argument +extra_argument="${extra_argument%%@}" + +calldata=$(cast calldata 'run()') +${op_command} forge script ${verify_arg} --legacy ${@} --sig 'run(bytes,string)' ${calldata} "${extra_argument}" diff --git a/script/ArtifactFactory.sol b/script/ArtifactFactory.sol new file mode 100644 index 0000000..b0d2ebc --- /dev/null +++ b/script/ArtifactFactory.sol @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { Vm } from "../lib/forge-std/src/Vm.sol"; +import { stdJson } from "../lib/forge-std/src/StdJson.sol"; +import { StdStyle } from "../lib/forge-std/src/StdStyle.sol"; +import { console2 as console } from "../lib/forge-std/src/console2.sol"; +import { LibString } from "../lib/solady/src/utils/LibString.sol"; +import { JSONParserLib } from "../lib/solady/src/utils/JSONParserLib.sol"; +import { IArtifactFactory } from "./interfaces/IArtifactFactory.sol"; +import { IGeneralConfig } from "./interfaces/IGeneralConfig.sol"; +import { LibSharedAddress } from "./libraries/LibSharedAddress.sol"; + +contract ArtifactFactory is IArtifactFactory { + using stdJson for *; + using StdStyle for *; + using LibString for *; + using JSONParserLib for *; + + Vm internal constant vm = Vm(LibSharedAddress.VM); + IGeneralConfig public constant CONFIG = IGeneralConfig(LibSharedAddress.CONFIG); + + function generateArtifact( + address deployer, + address contractAddr, + string memory contractAbsolutePath, + string calldata fileName, + bytes calldata args, + uint256 nonce + ) external { + console.log( + string.concat( + fileName, + " will be deployed at: ", + CONFIG.getExplorer(CONFIG.getCurrentNetwork()), + "address/", + contractAddr.toHexString() + ).green(), + string.concat("(nonce: ", nonce.toString(), ")") + ); + if (!CONFIG.getRuntimeConfig().log) { + console.log("Skipping artifact generation for:", vm.getLabel(contractAddr), "\n"); + return; + } + string memory dirPath = CONFIG.getDeploymentDirectory(CONFIG.getCurrentNetwork()); + string memory filePath = string.concat(dirPath, fileName, ".json"); + + string memory json; + uint256 numDeployments = 1; + + if (vm.exists(filePath)) { + string memory existedJson = vm.readFile(filePath); + if (vm.keyExists(existedJson, ".numDeployments")) { + numDeployments = vm.parseJsonUint(vm.readFile(filePath), ".numDeployments"); + numDeployments += 1; + } + } + + json.serialize("args", args); + json.serialize("nonce", nonce); + json.serialize("isFoundry", true); + json.serialize("deployer", deployer); + json.serialize("chainId", block.chainid); + json.serialize("address", contractAddr); + json.serialize("blockNumber", block.number); + json.serialize("timestamp", block.timestamp); + json.serialize("contractAbsolutePath", contractAbsolutePath); + json.serialize("numDeployments", numDeployments); + + string[] memory s = contractAbsolutePath.split(":"); + string memory artifactPath = s.length == 2 + ? string.concat("./out/", s[0], "/", s[1], ".json") + : string.concat("./out/", contractAbsolutePath, "/", contractAbsolutePath.replace(".sol", ".json")); + string memory artifact = vm.readFile(artifactPath); + JSONParserLib.Item memory item = artifact.parse(); + + json.serialize("abi", item.at('"abi"').value()); + json.serialize("ast", item.at('"ast"').value()); + json.serialize("devdoc", item.at('"devdoc"').value()); + json.serialize("userdoc", item.at('"userdoc"').value()); + json.serialize("metadata", item.at('"rawMetadata"').value()); + json.serialize("storageLayout", item.at('"storageLayout"').value()); + json.serialize("bytecode", item.at('"bytecode"').at('"object"').value()); + json = json.serialize("deployedBytecode", item.at('"deployedBytecode"').at('"object"').value()); + + json.write(filePath); + } +} diff --git a/script/BaseDeploy.s.sol b/script/BaseDeploy.s.sol deleted file mode 100644 index f8620ac..0000000 --- a/script/BaseDeploy.s.sol +++ /dev/null @@ -1,144 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; - -import {ProxyAdmin, ITransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/ProxyAdmin.sol"; -import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; -import {Vm} from "forge-std/Vm.sol"; -import {console2} from "forge-std/console2.sol"; -import {StdStyle} from "forge-std/StdStyle.sol"; -import {BaseScript} from "./BaseScript.s.sol"; -import {LogGenerator} from "./LogGenerator.s.sol"; -import "./GeneralConfig.s.sol"; -import {IDeployScript} from "./interfaces/IDeployScript.sol"; - -abstract contract BaseDeploy is BaseScript { - using StdStyle for string; - - bytes public constant EMPTY_ARGS = ""; - - address internal _deployer; - bool internal _alreadySetUp; - bytes internal _overridenArgs; - LogGenerator internal _logger; - mapping(ContractKey contractKey => IDeployScript deployScript) internal _deployScript; - - modifier trySetUp() { - if (!_alreadySetUp) { - setUp(); - _alreadySetUp = true; - } - _; - } - - function setUp() public virtual override { - vm.pauseGasMetering(); - _alreadySetUp = true; - super.setUp(); - - _logger = new LogGenerator(vm, _config); - _setUpDependencies(); - } - - function _setUpDependencies() internal virtual {} - - function _setDependencyDeployScript(ContractKey contractKey, address deployScript) internal { - _deployScript[contractKey] = IDeployScript(deployScript); - } - - function loadContractOrDeploy(ContractKey contractKey) public returns (address payable contractAddr) { - string memory contractName = _config.getContractName(contractKey); - try _config.getAddressFromCurrentNetwork(contractKey) returns (address payable addr) { - contractAddr = addr; - } catch { - console2.log(string.concat("Deployment for ", contractName, " not found, try fresh deploy ...").yellow()); - contractAddr = _deployScript[contractKey].run(); - } - } - - function setArgs(bytes memory args) public returns (IDeployScript) { - _overridenArgs = args; - return IDeployScript(address(this)); - } - - function arguments() public returns (bytes memory args) { - args = _overridenArgs.length == 0 ? _defaultArguments() : _overridenArgs; - } - - function _defaultArguments() internal virtual returns (bytes memory args) {} - - function _deployImmutable(ContractKey contractKey, bytes memory args) internal returns (address payable deployed) { - string memory contractName = _config.getContractName(contractKey); - string memory contractFilename = _config.getContractFileName(contractKey); - uint256 nonce; - (deployed, nonce) = _deployRaw(contractFilename, args); - vm.label(deployed, contractName); - - _config.setAddress(_network, contractKey, deployed); - _logger.generateDeploymentArtifact(_deployer, deployed, contractName, contractName, args, nonce); - } - - function _upgradeProxy(ContractKey contractKey, bytes memory args) internal returns (address payable proxy) { - string memory contractName = _config.getContractName(contractKey); - string memory contractFilename = _config.getContractFileName(contractKey); - - uint256 logicNonce; - address logic; - (logic, logicNonce) = _deployRaw(contractFilename, EMPTY_ARGS); - - ProxyAdmin proxyAdmin = ProxyAdmin(_config.getAddressFromCurrentNetwork(ContractKey.ProxyAdmin)); - proxy = _config.getAddressFromCurrentNetwork(contractKey); - _upgradeRaw(proxyAdmin, ITransparentUpgradeableProxy(proxy), logic, args); - - _logger.generateDeploymentArtifact( - _deployer, logic, contractName, string.concat(contractName, "Logic"), EMPTY_ARGS, logicNonce - ); - } - - function _deployProxy(ContractKey contractKey, bytes memory args) internal returns (address payable deployed) { - string memory contractName = _config.getContractName(contractKey); - string memory contractFilename = _config.getContractFileName(contractKey); - (address logic, uint256 logicNonce) = _deployRaw(contractFilename, EMPTY_ARGS); - - uint256 proxyNonce; - (deployed, proxyNonce) = _deployRaw( - "TransparentUpgradeableProxy.sol:TransparentUpgradeableProxy", - abi.encode(logic, _config.getAddressFromCurrentNetwork(ContractKey.ProxyAdmin), args) - ); - vm.label(deployed, contractName); - - _config.setAddress(_network, contractKey, deployed); - _logger.generateDeploymentArtifact( - _deployer, logic, contractName, string.concat(contractName, "Logic"), EMPTY_ARGS, logicNonce - ); - _logger.generateDeploymentArtifact( - _deployer, deployed, "TransparentUpgradeableProxy", string.concat(contractName, "Proxy"), args, proxyNonce - ); - } - - function _deployRaw(string memory filename, bytes memory args) - internal - returns (address payable deployed, uint256 nonce) - { - nonce = vm.getNonce(_deployer); - address expectedAddr = computeCreateAddress(_deployer, nonce); - - vm.resumeGasMetering(); - vm.broadcast(_deployer); - deployed = payable(deployCode(filename, args)); - vm.pauseGasMetering(); - - require(deployed == expectedAddr, "deployed != expectedAddr"); - } - - function _upgradeRaw(ProxyAdmin proxyAdmin, ITransparentUpgradeableProxy proxy, address logic, bytes memory args) - internal - { - address owner = proxyAdmin.owner(); - - vm.broadcast(owner); - vm.resumeGasMetering(); - if (args.length == 0) proxyAdmin.upgradeAndCall(proxy, logic, ""); - else proxyAdmin.upgradeAndCall(proxy, logic, args); - vm.pauseGasMetering(); - } -} diff --git a/script/BaseGeneralConfig.sol b/script/BaseGeneralConfig.sol new file mode 100644 index 0000000..437796e --- /dev/null +++ b/script/BaseGeneralConfig.sol @@ -0,0 +1,125 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { Vm } from "../lib/forge-std/src/Vm.sol"; +import { StdStyle } from "../lib/forge-std/src/StdStyle.sol"; +import { console2 as console } from "../lib/forge-std/src/console2.sol"; +import { LibString } from "../lib/solady/src/utils/LibString.sol"; +import { WalletConfig } from "./configs/WalletConfig.sol"; +import { RuntimeConfig } from "./configs/RuntimeConfig.sol"; +import { MigrationConfig } from "./configs/MigrationConfig.sol"; +import { TNetwork, NetworkConfig } from "./configs/NetworkConfig.sol"; +import { EnumerableSet, TContract, ContractConfig } from "./configs/ContractConfig.sol"; +import { ISharedParameter } from "./interfaces/configs/ISharedParameter.sol"; +import { DefaultNetwork } from "./utils/DefaultNetwork.sol"; +import { DefaultContract } from "./utils/DefaultContract.sol"; +import { LibSharedAddress } from "./libraries/LibSharedAddress.sol"; + +contract BaseGeneralConfig is RuntimeConfig, WalletConfig, ContractConfig, NetworkConfig, MigrationConfig { + using StdStyle for string; + using LibString for string; + using EnumerableSet for EnumerableSet.AddressSet; + + Vm internal constant vm = Vm(LibSharedAddress.VM); + + fallback() external { + if (msg.sig == ISharedParameter.sharedArguments.selector) { + bytes memory returnData = getRawSharedArguments(); + assembly ("memory-safe") { + return(add(returnData, 0x20), mload(returnData)) + } + } else { + revert("GeneralConfig: Unknown instruction, please rename interface to sharedArguments()"); + } + } + + constructor(string memory absolutePath, string memory deploymentRoot) + NetworkConfig(deploymentRoot) + ContractConfig(absolutePath, deploymentRoot) + { + _setUpNetworks(); + _setUpContracts(); + _setUpDefaultSender(); + _storeDeploymentData(deploymentRoot); + } + + function _setUpNetworks() internal virtual { + setNetworkInfo( + DefaultNetwork.Local.chainId(), + DefaultNetwork.Local.key(), + DefaultNetwork.Local.chainAlias(), + DefaultNetwork.Local.deploymentDir(), + DefaultNetwork.Local.envLabel(), + DefaultNetwork.Local.explorer() + ); + setNetworkInfo( + DefaultNetwork.RoninTestnet.chainId(), + DefaultNetwork.RoninTestnet.key(), + DefaultNetwork.RoninTestnet.chainAlias(), + DefaultNetwork.RoninTestnet.deploymentDir(), + DefaultNetwork.RoninTestnet.envLabel(), + DefaultNetwork.RoninTestnet.explorer() + ); + setNetworkInfo( + DefaultNetwork.RoninMainnet.chainId(), + DefaultNetwork.RoninMainnet.key(), + DefaultNetwork.RoninMainnet.chainAlias(), + DefaultNetwork.RoninMainnet.deploymentDir(), + DefaultNetwork.RoninMainnet.envLabel(), + DefaultNetwork.RoninMainnet.explorer() + ); + } + + function _setUpContracts() internal virtual { + _contractNameMap[DefaultContract.ProxyAdmin.key()] = DefaultContract.ProxyAdmin.name(); + + setAddress( + DefaultNetwork.RoninTestnet.key(), DefaultContract.ProxyAdmin.key(), 0x505d91E8fd2091794b45b27f86C045529fa92CD7 + ); + setAddress( + DefaultNetwork.RoninMainnet.key(), DefaultContract.ProxyAdmin.key(), 0xA3e7d085E65CB0B916f6717da876b7bE5cC92f03 + ); + } + + function _setUpDefaultSender() internal virtual { + // by default we will read private key from .env + _envPk = vm.envUint(getPrivateKeyEnvLabel(getCurrentNetwork())); + _envSender = vm.rememberKey(_envPk); + + label(block.chainid, _envSender, "ENVSender"); + console.log("GeneralConfig:", vm.getLabel(_envSender)); + } + + function getSender() public view virtual override returns (address payable sender) { + sender = _option.trezor ? payable(_trezorSender) : payable(_envSender); + require(sender != address(0), "GeneralConfig: Sender is address(0x0)"); + } + + function setAddress(TNetwork network, TContract contractType, address contractAddr) public virtual { + uint256 chainId = _networkDataMap[network].chainId; + string memory contractName = getContractName(contractType); + require(chainId != 0 && bytes(contractName).length != 0, "GeneralConfig: Network or Contract Key not found"); + + label(chainId, contractAddr, contractName); + _contractAddrSet[chainId].add(contractAddr); + _contractTypeMap[chainId][contractAddr] = contractType; + _contractAddrMap[chainId][contractName] = contractAddr; + } + + function getAddress(TNetwork network, TContract contractType) public view virtual returns (address payable) { + return getAddressByRawData(_networkDataMap[network].chainId, getContractName(contractType)); + } + + function getAllAddresses(TNetwork network) public view virtual returns (address payable[] memory) { + return getAllAddressesByRawData(_networkDataMap[network].chainId); + } + + function _handleRuntimeConfig() internal virtual override { + if (_option.trezor) { + string memory str = vm.envString(deployerEnvLabel()); + _trezorSender = vm.parseAddress(str.replace(trezorPrefix(), "")); + label(block.chainid, _trezorSender, "TrezorSender"); + console.log("GeneralConfig:", vm.getLabel(_trezorSender)); + } + } +} diff --git a/script/BaseMigration.s.sol b/script/BaseMigration.s.sol new file mode 100644 index 0000000..72b5263 --- /dev/null +++ b/script/BaseMigration.s.sol @@ -0,0 +1,239 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { ProxyAdmin } from "../lib/openzeppelin-contracts/contracts/proxy/transparent/ProxyAdmin.sol"; +import { + ITransparentUpgradeableProxy, + TransparentUpgradeableProxy +} from "../lib/openzeppelin-contracts/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; +import { LibString } from "../lib/solady/src/utils/LibString.sol"; +import { console, LibSharedAddress, StdStyle, IScriptExtended, ScriptExtended } from "./extensions/ScriptExtended.s.sol"; +import { IArtifactFactory, ArtifactFactory } from "./ArtifactFactory.sol"; +import { IMigrationScript } from "./interfaces/IMigrationScript.sol"; +import { LibProxy } from "./libraries/LibProxy.sol"; +import { DefaultContract } from "./utils/DefaultContract.sol"; +import { TContract } from "./types/Types.sol"; + +abstract contract BaseMigration is ScriptExtended { + using StdStyle for string; + using LibString for bytes32; + using LibProxy for address payable; + + IArtifactFactory public constant ARTIFACT_FACTORY = IArtifactFactory(LibSharedAddress.ARTIFACT_FACTORY); + + bytes internal _overriddenArgs; + mapping(TContract contractType => IMigrationScript deployScript) internal _deployScript; + + function setUp() public virtual override { + super.setUp(); + vm.label(address(ARTIFACT_FACTORY), "ArtifactFactory"); + deploySharedAddress(address(ARTIFACT_FACTORY), type(ArtifactFactory).creationCode); + _injectDependencies(); + _storeRawSharedArguments(); + } + + function _storeRawSharedArguments() internal virtual { + CONFIG.setRawSharedArguments(_sharedArguments()); + } + + function _sharedArguments() internal virtual returns (bytes memory rawSharedArgs); + + function _injectDependencies() internal virtual { } + + function _defaultArguments() internal virtual returns (bytes memory) { } + + function loadContractOrDeploy(TContract contractType) public virtual returns (address payable contractAddr) { + string memory contractName = CONFIG.getContractName(contractType); + try CONFIG.getAddressFromCurrentNetwork(contractType) returns (address payable addr) { + contractAddr = addr; + } catch { + console.log(string.concat("Deployment for ", contractName, " not found, try fresh deploy ...").yellow()); + contractAddr = _deployScript[contractType].run(); + } + } + + function overrideArgs(bytes memory args) public virtual returns (IMigrationScript) { + _overriddenArgs = args; + return IMigrationScript(address(this)); + } + + function arguments() public virtual returns (bytes memory args) { + args = _overriddenArgs.length == 0 ? _defaultArguments() : _overriddenArgs; + } + + function _deployImmutable(TContract contractType) internal virtual returns (address payable deployed) { + deployed = _deployImmutable(contractType, arguments()); + } + + function _deployImmutable(TContract contractType, bytes memory args) + internal + virtual + logFn(string.concat("_deployImmutable ", TContract.unwrap(contractType).unpackOne())) + returns (address payable deployed) + { + string memory contractName = CONFIG.getContractName(contractType); + string memory contractAbsolutePath = CONFIG.getContractAbsolutePath(contractType); + uint256 nonce; + (deployed, nonce) = _deployRaw(contractAbsolutePath, args); + CONFIG.setAddress(network(), contractType, deployed); + ARTIFACT_FACTORY.generateArtifact(sender(), deployed, contractAbsolutePath, contractName, args, nonce); + } + + function _deployLogic(TContract contractType) + internal + virtual + logFn(string.concat("_deployLogic ", TContract.unwrap(contractType).unpackOne())) + returns (address payable logic) + { + string memory contractName = CONFIG.getContractName(contractType); + string memory contractAbsolutePath = CONFIG.getContractAbsolutePath(contractType); + + uint256 logicNonce; + (logic, logicNonce) = _deployRaw(contractAbsolutePath, EMPTY_ARGS); + CONFIG.label(block.chainid, logic, string.concat(contractName, "::Logic")); + ARTIFACT_FACTORY.generateArtifact( + sender(), logic, contractAbsolutePath, string.concat(contractName, "Logic"), EMPTY_ARGS, logicNonce + ); + } + + function _deployProxy(TContract contractType) internal virtual returns (address payable deployed) { + deployed = _deployProxy(contractType, arguments()); + } + + function _deployProxy(TContract contractType, bytes memory args) + internal + virtual + logFn(string.concat("_deployProxy ", TContract.unwrap(contractType).unpackOne())) + returns (address payable deployed) + { + string memory contractName = CONFIG.getContractName(contractType); + + address logic = _deployLogic(contractType); + string memory proxyAbsolutePath = "TransparentUpgradeableProxy.sol:TransparentUpgradeableProxy"; + uint256 proxyNonce; + address proxyAdmin = CONFIG.getAddressFromCurrentNetwork(DefaultContract.ProxyAdmin.key()); + assertTrue(proxyAdmin != address(0x0), "BaseMigration: Null ProxyAdmin"); + + (deployed, proxyNonce) = _deployRaw(proxyAbsolutePath, abi.encode(logic, proxyAdmin, args)); + + // validate proxy admin + address actualProxyAdmin = deployed.getProxyAdmin(); + assertEq( + actualProxyAdmin, + proxyAdmin, + string.concat( + "BaseMigration: Invalid proxy admin\n", + "Actual: ", + vm.toString(actualProxyAdmin), + "\nExpected: ", + vm.toString(proxyAdmin) + ) + ); + + CONFIG.setAddress(network(), contractType, deployed); + ARTIFACT_FACTORY.generateArtifact( + sender(), deployed, proxyAbsolutePath, string.concat(contractName, "Proxy"), args, proxyNonce + ); + } + + function _deployRaw(string memory filename, bytes memory args) + internal + virtual + returns (address payable deployed, uint256 nonce) + { + nonce = vm.getNonce(sender()); + vm.broadcast(sender()); + deployed = payable(deployCode(filename, args)); + } + + function _mockUpgradeProxy(TContract contractType) + internal + virtual + logFn(string.concat("_mockUpgradeProxy ", TContract.unwrap(contractType).unpackOne())) + returns (address payable proxy) + { + proxy = _mockUpgradeProxy(contractType, arguments()); + } + + function _mockUpgradeProxy(TContract contractType, bytes memory args) + internal + virtual + logFn(string.concat("_mockUpgradeProxy ", TContract.unwrap(contractType).unpackOne())) + returns (address payable proxy) + { + address logic = _deployLogic(contractType); + proxy = CONFIG.getAddress(network(), contractType); + _mockUpgradeRaw(proxy.getProxyAdmin(), proxy, logic, args); + } + + function _upgradeProxy(TContract contractType) + internal + virtual + logFn(string.concat("_upgradeProxy ", TContract.unwrap(contractType).unpackOne())) + returns (address payable proxy) + { + proxy = _upgradeProxy(contractType, arguments()); + } + + function _upgradeProxy(TContract contractType, bytes memory args) + internal + virtual + logFn(string.concat("_upgradeProxy ", TContract.unwrap(contractType).unpackOne())) + returns (address payable proxy) + { + address logic = _deployLogic(contractType); + proxy = CONFIG.getAddress(network(), contractType); + _upgradeRaw(proxy.getProxyAdmin(), proxy, logic, args); + } + + function _mockUpgradeRaw(address proxyAdmin, address payable proxy, address logic, bytes memory args) + internal + virtual + { + if (proxyAdmin.code.length == 0) { + vm.prank(proxyAdmin); + if (args.length == 0) ITransparentUpgradeableProxy(proxy).upgradeTo(logic); + else ITransparentUpgradeableProxy(proxy).upgradeToAndCall(logic, args); + } else { + try ProxyAdmin(proxyAdmin).owner() returns (address owner) { + vm.prank(owner); + if (args.length == 0) { + ProxyAdmin(proxyAdmin).upgrade(ITransparentUpgradeableProxy(proxy), logic); + } else { + ProxyAdmin(proxyAdmin).upgradeAndCall(ITransparentUpgradeableProxy(proxy), logic, args); + } + } catch { + vm.prank(proxyAdmin); + if (args.length == 0) ITransparentUpgradeableProxy(proxy).upgradeTo(logic); + else ITransparentUpgradeableProxy(proxy).upgradeToAndCall(logic, args); + } + } + } + + function _upgradeRaw(address proxyAdmin, address payable proxy, address logic, bytes memory args) internal virtual { + if (proxyAdmin.code.length == 0) { + vm.broadcast(proxyAdmin); + if (args.length == 0) ITransparentUpgradeableProxy(proxy).upgradeTo(logic); + else ITransparentUpgradeableProxy(proxy).upgradeToAndCall(logic, args); + } else { + try ProxyAdmin(proxyAdmin).owner() returns (address owner) { + vm.broadcast(owner); + if (args.length == 0) { + ProxyAdmin(proxyAdmin).upgrade(ITransparentUpgradeableProxy(proxy), logic); + } else { + ProxyAdmin(proxyAdmin).upgradeAndCall(ITransparentUpgradeableProxy(proxy), logic, args); + } + } catch { + revert("BaseMigration: Unhandled case for upgrading proxy!"); + } + } + } + + function _setDependencyDeployScript(TContract contractType, IScriptExtended deployScript) internal virtual { + _deployScript[contractType] = IMigrationScript(address(deployScript)); + } + + function _setDependencyDeployScript(TContract contractType, address deployScript) internal virtual { + _deployScript[contractType] = IMigrationScript(deployScript); + } +} diff --git a/script/BaseScript.s.sol b/script/BaseScript.s.sol deleted file mode 100644 index dc75ace..0000000 --- a/script/BaseScript.s.sol +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.0; - -import {Script, console} from "forge-std/Script.sol"; -import {StdAssertions} from "forge-std/StdAssertions.sol"; -import "./GeneralConfig.s.sol"; - -contract BaseScript is Script, StdAssertions { - bytes32 public constant GENERAL_CONFIG_SALT = keccak256(bytes(type(GeneralConfig).name)); - - GeneralConfig internal _config; - Network internal _network; - - modifier onMainnet() { - _network = Network.RoninMainnet; - _; - } - - modifier onTestnet() { - _network = Network.RoninTestnet; - _; - } - - modifier onLocalHost() { - _network = Network.Local; - _; - } - - function setUp() public virtual { - // allow diferrent deploy scripts to share same config storage - // predict general config address - address cfgAddr = computeCreate2Address( - GENERAL_CONFIG_SALT, hashInitCode(abi.encodePacked(type(GeneralConfig).creationCode), abi.encode(vm)) - ); - vm.allowCheatcodes(cfgAddr); - // skip if general config already deployed - if (cfgAddr.code.length == 0) { - vm.prank(CREATE2_FACTORY); - new GeneralConfig{ salt: GENERAL_CONFIG_SALT }(vm); - } - - _config = GeneralConfig(payable(cfgAddr)); - _network = _config.getCurrentNetwork(); - } - - function fail() internal override { - super.fail(); - revert("Got failed assertion"); - } -} diff --git a/script/GeneralConfig.s.sol b/script/GeneralConfig.s.sol deleted file mode 100644 index 8a03dda..0000000 --- a/script/GeneralConfig.s.sol +++ /dev/null @@ -1,222 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; - -import {Vm, VmSafe} from "forge-std/Vm.sol"; -import {LibString} from "solady/src/utils/LibString.sol"; - -enum ContractKey { - ProxyAdmin, - // RNS - RNSRegistry, - RNSRegistrar, - NameWrapper, - NameChecker, - DummyOracle, - RNSAuction, - RNSDomainPrice, - PublicResolver, - ReverseRegistrar, - StablePriceOracle, - RONRegistrarController, - // Land Staking - LandStakingPool, - LandStakingManager -} - -enum Network { - Local, - RoninMainnet, - RoninTestnet -} - -library ChainId { - uint256 public constant LOCAL = 31337; - uint256 public constant RONIN_MAINNET = 2020; - uint256 public constant RONIN_TESTNET = 2021; -} - -contract GeneralConfig { - using LibString for string; - - struct NetworkInfo { - uint256 chainId; - string privateKeyEnvLabel; - string deploymentDir; - } - - string public constant LOCAL_ENV_LABEL = "LOCAL_PK"; - string public constant TESTNET_ENV_LABEL = "TESTNET_PK"; - string public constant MAINNET_ENV_LABEL = "MAINNET_PK"; - string public constant DEPLOYMENT_ROOT = "deployments/"; - string public constant LOCAL_DIR = "local/"; - string public constant RONIN_TESTNET_DIR = "ronin-testnet/"; - string public constant RONIN_MAINNET_DIR = "ronin-mainnet/"; - - Vm private immutable _vm; - - mapping(Network networkIdx => NetworkInfo) private _networkInfoMap; - mapping(uint256 chainId => Network networkIdx) private _networkMap; - mapping(ContractKey contractIdx => string contractName) private _contractNameMap; - mapping(uint256 chainId => mapping(string name => address addr)) private _contractAddrMap; - - constructor(Vm vm) payable { - _vm = vm; - - _setUpNetwork(); - _mapContractKeysToNames(); - - _setUpHardHatDeploymentInfo(); - _setUpFoundryDeploymentInfo(); - - // Manuallly setup for testnet - setAddress(Network.RoninTestnet, ContractKey.LandStakingManager, 0x087c35EEe6b9f697f8CC0062762130E505A39003); - setAddress(Network.RoninTestnet, ContractKey.LandStakingPool, 0x74f4aeB84F1535A777CC9B24f4C4B703F2402868); - - // Manually setup for mainnet - setAddress(Network.RoninMainnet, ContractKey.LandStakingManager, 0x7f27E35170472E7f107d3e55C2B9bCd44aA01dD5); - setAddress(Network.RoninMainnet, ContractKey.LandStakingPool, 0xb2A5110F163eC592F8F0D4207253D8CbC327d9fB); - - // Manually setup for localhost - setAddress(Network.Local, ContractKey.ProxyAdmin, 0x70997970C51812dc3A010C7d01b50e0d17dc79C8); - } - - function _mapContractKeysToNames() internal { - // setup contract name - _contractNameMap[ContractKey.ProxyAdmin] = "ProxyAdmin"; - _contractNameMap[ContractKey.NameChecker] = "NameChecker"; - _contractNameMap[ContractKey.NameWrapper] = "NameWrapper"; - _contractNameMap[ContractKey.RNSAuction] = "RNSAuction"; - _contractNameMap[ContractKey.RNSRegistry] = "RNSRegistry"; - _contractNameMap[ContractKey.RNSRegistrar] = "RNSRegistrar"; - _contractNameMap[ContractKey.DummyOracle] = "MockDummyOracle"; - _contractNameMap[ContractKey.RNSDomainPrice] = "RNSDomainPrice"; - _contractNameMap[ContractKey.PublicResolver] = "PublicResolver"; - _contractNameMap[ContractKey.LandStakingPool] = "LandStakingPool"; - _contractNameMap[ContractKey.ReverseRegistrar] = "ReverseRegistrar"; - _contractNameMap[ContractKey.LandStakingManager] = "LandStakingManager"; - _contractNameMap[ContractKey.StablePriceOracle] = "MockStablePriceOracle"; - _contractNameMap[ContractKey.RONRegistrarController] = "RONRegistrarController"; - } - - function _setUpNetwork() internal { - _networkMap[ChainId.LOCAL] = Network.Local; - _networkInfoMap[Network.Local] = NetworkInfo(ChainId.LOCAL, LOCAL_ENV_LABEL, LOCAL_DIR); - - _networkMap[ChainId.RONIN_TESTNET] = Network.RoninTestnet; - _networkInfoMap[Network.RoninTestnet] = NetworkInfo(ChainId.RONIN_TESTNET, TESTNET_ENV_LABEL, RONIN_TESTNET_DIR); - - _networkMap[ChainId.RONIN_MAINNET] = Network.RoninMainnet; - _networkInfoMap[Network.RoninMainnet] = NetworkInfo(ChainId.RONIN_MAINNET, MAINNET_ENV_LABEL, RONIN_MAINNET_DIR); - } - - function _setUpFoundryDeploymentInfo() internal {} - - function _setUpHardHatDeploymentInfo() internal { - VmSafe.DirEntry[] memory deployments = _vm.readDir(DEPLOYMENT_ROOT); - - for (uint256 i; i < deployments.length;) { - VmSafe.DirEntry[] memory entries = _vm.readDir(deployments[i].path); - uint256 chainId = _vm.parseUint(_vm.readFile(string.concat(deployments[i].path, "/.chainId"))); - string[] memory s = deployments[i].path.split("/"); - string memory prefix = s[s.length - 1]; - - for (uint256 j; j < entries.length;) { - string memory path = entries[j].path; - - if (path.endsWith(".json")) { - // filter out logic deployments - if (!path.endsWith("Logic.json")) { - string[] memory splitteds = path.split("/"); - string memory contractName = splitteds[splitteds.length - 1]; - string memory suffix = path.endsWith("Proxy.json") ? "Proxy.json" : ".json"; - // remove suffix - assembly ("memory-safe") { - mstore(contractName, sub(mload(contractName), mload(suffix))) - } - string memory json = _vm.readFile(path); - address contractAddr = _vm.parseJsonAddress(json, ".address"); - - _vm.label(contractAddr, string.concat(prefix, ".", contractName)); - _contractAddrMap[chainId][contractName] = contractAddr; - } - } - - unchecked { - ++j; - } - } - - unchecked { - ++i; - } - } - } - - function setAddressForCurrentNetwork(ContractKey contractKey, address contractAddr) public { - setAddress(getCurrentNetwork(), contractKey, contractAddr); - } - - function setAddress(Network network, ContractKey contractKey, address contractAddr) public { - uint256 chainId = _networkInfoMap[network].chainId; - string memory contractName = _contractNameMap[contractKey]; - require(chainId != 0 && bytes(contractName).length != 0, "Network or Contract Key not found"); - - _contractAddrMap[chainId][contractName] = contractAddr; - } - - function getDeploymentDirectoryFromCurrentNetwork() public view returns (string memory dirPath) { - dirPath = getDeploymentDirectory(getCurrentNetwork()); - } - - function getDeploymentDirectory(Network network) public view returns (string memory dirPath) { - string memory dirName = _networkInfoMap[network].deploymentDir; - require(bytes(dirName).length != 0, "Deployment dir not found"); - dirPath = string.concat(DEPLOYMENT_ROOT, dirName); - } - - function getPrivateKeyEnvLabelFromCurrentNetwork() public view returns (string memory privatekeyEnvLabel) { - privatekeyEnvLabel = getPrivateKeyEnvLabel(getCurrentNetwork()); - } - - function getPrivateKeyEnvLabel(Network network) public view returns (string memory privateKeyEnvLabel) { - privateKeyEnvLabel = _networkInfoMap[network].privateKeyEnvLabel; - require(bytes(privateKeyEnvLabel).length != 0, "ENV label not found"); - } - - function getContractName(ContractKey contractKey) public view returns (string memory name) { - name = _contractNameMap[contractKey]; - require(bytes(name).length != 0, "Contract Key not found"); - } - - function getContractFileName(ContractKey contractKey) public view returns (string memory filename) { - string memory contractName = getContractName(contractKey); - filename = string.concat(contractName, ".sol:", contractName); - } - - function getCurrentNetwork() public view returns (Network network) { - network = _networkMap[block.chainid]; - } - - function getNetworkByChainId(uint256 chainId) public view returns (Network network) { - network = _networkMap[chainId]; - } - - function getAddress(Network network, ContractKey contractKey) public view returns (address payable) { - return getAddressByRawData(_networkInfoMap[network].chainId, _contractNameMap[contractKey]); - } - - function getAddressFromCurrentNetwork(ContractKey contractKey) public view returns (address payable) { - string memory contractName = _contractNameMap[contractKey]; - require(bytes(contractName).length != 0, "Contract Key not found"); - return getAddressByRawData(block.chainid, contractName); - } - - function getAddressByString(string memory contractName) public view returns (address payable) { - return getAddressByRawData(block.chainid, contractName); - } - - function getAddressByRawData(uint256 chainId, string memory contractName) public view returns (address payable addr) { - addr = payable(_contractAddrMap[chainId][contractName]); - require(addr != address(0), string.concat("address not found: ", contractName)); - } -} diff --git a/script/LogGenerator.s.sol b/script/LogGenerator.s.sol deleted file mode 100644 index badd8ae..0000000 --- a/script/LogGenerator.s.sol +++ /dev/null @@ -1,75 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; - -import {Vm} from "forge-std/Vm.sol"; -import {StdStyle} from "forge-std/StdStyle.sol"; -import {console2} from "forge-std/console2.sol"; -import {stdJson} from "forge-std/StdJson.sol"; -import {GeneralConfig} from "./GeneralConfig.s.sol"; -import {JSONParserLib} from "solady/src/utils/JSONParserLib.sol"; - -contract LogGenerator { - using StdStyle for *; - using stdJson for string; - using JSONParserLib for *; - - Vm private immutable _vm; - GeneralConfig private immutable _config; - - constructor(Vm vm, GeneralConfig config) { - _vm = vm; - _config = config; - } - - function generateDeploymentArtifact( - address deployer, - address contractAddr, - string memory contractName, - string memory fileName, - bytes memory args, - uint256 nonce - ) external { - // skip writing artifact if network is localhost - // if (_network == Network.LocalHost) return; - string memory dirPath = _config.getDeploymentDirectory(_config.getCurrentNetwork()); - string memory filePath = string.concat(dirPath, fileName, ".json"); - - string memory json; - uint256 numDeployments = 1; - - if (_vm.exists(filePath)) { - string memory existedJson = _vm.readFile(filePath); - if (_vm.keyExists(existedJson, ".numDeployments")) { - numDeployments = _vm.parseJsonUint(_vm.readFile(filePath), ".numDeployments"); - numDeployments += 1; - } - } - - json.serialize("nonce", nonce); - json.serialize("args", args); - json.serialize("chainId", block.chainid); - json.serialize("deployer", deployer); - json.serialize("address", contractAddr); - json.serialize("timestamp", block.timestamp); - json.serialize("contractName", contractName); - json.serialize("numDeployments", numDeployments); - json.serialize("blockNumber", block.number); - json.serialize("isFoundry", true); - - string memory artifactPath = string.concat("./out/", contractName, ".sol/", contractName, ".json"); - string memory artifact = _vm.readFile(artifactPath); - JSONParserLib.Item memory item = artifact.parse(); - - json.serialize("bytecode", item.at('"bytecode"').at('"object"').value()); - json.serialize("deployedBytecode", item.at('"deployedBytecode"').at('"object"').value()); - json.serialize("storageLayout", item.at('"storageLayout"').value()); - json.serialize("userdoc", item.at('"userdoc"').value()); - json.serialize("devdoc", item.at('"devdoc"').value()); - json.serialize("abi", item.at('"abi"').value()); - json = json.serialize("metadata", item.at('"rawMetadata"').value()); - - json.write(filePath); - - console2.log(string.concat(fileName, " deployed at:").green(), contractAddr.green()); - } -} diff --git a/script/OnchainDebugger.s.sol b/script/OnchainDebugger.s.sol new file mode 100644 index 0000000..86614c3 --- /dev/null +++ b/script/OnchainDebugger.s.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { console2 as console } from "forge-std/console2.sol"; +import { ScriptExtended } from "./extensions/ScriptExtended.s.sol"; +import { BaseGeneralConfig } from "./BaseGeneralConfig.sol"; +import { LibErrorHandler } from "../lib/contract-libs/src/LibErrorHandler.sol"; + +contract OnchainDebugger is ScriptExtended { + using LibErrorHandler for bool; + + modifier rollFork(uint256 forkBlock) { + if (forkBlock != 0) { + vm.rollFork(forkBlock); + console.log("OnchainDebugger: Rolling to fork block number:", forkBlock); + } + _; + } + + function _configByteCode() internal virtual override returns (bytes memory) { + return abi.encodePacked(type(BaseGeneralConfig).creationCode, abi.encode("", "deployments/")); + } + + function trace(uint256 forkBlock, address from, address to, uint256 value, bytes calldata callData) + public + rollFork(forkBlock) + { + vm.prank(from); + (bool success, bytes memory returnOrRevertData) = to.call{ value: value }(callData); + success.handleRevert(msg.sig, returnOrRevertData); + } +} diff --git a/script/SignUtil.s.sol b/script/SignUtil.s.sol new file mode 100644 index 0000000..1e281bd --- /dev/null +++ b/script/SignUtil.s.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { ECDSA } from "../lib/openzeppelin-contracts/contracts/utils/cryptography/ECDSA.sol"; +import { LibSig } from "./libraries/LibSig.sol"; +import { Script } from "../lib/forge-std/src/Script.sol"; +import { LibString } from "../lib/solady/src/utils/LibString.sol"; +import { BaseGeneralConfig } from "./BaseGeneralConfig.sol"; +import { console, BaseMigration } from "./BaseMigration.s.sol"; + +contract SignUtil is Script { + function run() external { + uint256 pk = vm.envUint("TESTNET_PK"); + console.log("sender", vm.rememberKey(vm.envUint("TESTNET_PK"))); + + string memory signData = "hello"; + bytes32 digest = ECDSA.toEthSignedMessageHash(bytes(signData)); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(pk, digest); + bytes memory vmSig = LibSig.merge(v, r, s); + + string[] memory commandInput = new string[](6); + commandInput[0] = "cast"; + commandInput[1] = "wallet"; + commandInput[2] = "sign"; + commandInput[3] = "--private-key"; + commandInput[4] = LibString.toHexString(pk); + commandInput[5] = signData; + bytes memory castSig = vm.ffi(commandInput); + + console.log("pk", LibString.toHexString(pk)); + console.log("vmSig", vm.toString(vmSig)); + console.log("castSig", vm.toString(castSig)); + } +} diff --git a/script/configs/ContractConfig.sol b/script/configs/ContractConfig.sol new file mode 100644 index 0000000..2c3a47a --- /dev/null +++ b/script/configs/ContractConfig.sol @@ -0,0 +1,169 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { EnumerableSet } from "../../lib/openzeppelin-contracts/contracts/utils/structs/EnumerableSet.sol"; +import { Vm, VmSafe } from "../../lib/forge-std/src/Vm.sol"; +import { console2 as console } from "../../lib/forge-std/src/console2.sol"; +import { StdStyle } from "../../lib/forge-std/src/StdStyle.sol"; +import { LibString } from "../../lib/solady/src/utils/LibString.sol"; +import { IContractConfig } from "../interfaces/configs/IContractConfig.sol"; +import { LibSharedAddress } from "../libraries/LibSharedAddress.sol"; +import { TContract } from "../types/Types.sol"; + +abstract contract ContractConfig is IContractConfig { + using LibString for *; + using StdStyle for string; + using EnumerableSet for EnumerableSet.AddressSet; + + Vm private constant vm = Vm(LibSharedAddress.VM); + + string private _absolutePath; + string private _deploymentRoot; + + mapping(TContract contractType => string contractName) internal _contractNameMap; + mapping(TContract contractType => string absolutePath) internal _contractAbsolutePathMap; + + mapping(uint256 chainId => EnumerableSet.AddressSet) internal _contractAddrSet; + mapping(uint256 chainId => mapping(string name => address addr)) internal _contractAddrMap; + mapping(uint256 chainId => mapping(address addr => TContract contractType)) internal _contractTypeMap; + + constructor(string memory absolutePath, string memory deploymentRoot) { + _absolutePath = absolutePath; + _deploymentRoot = deploymentRoot; + } + + function getContractTypeByRawData(uint256 chainId, address contractAddr) + public + view + virtual + returns (TContract contractType) + { + contractType = _contractTypeMap[chainId][contractAddr]; + require( + TContract.unwrap(contractType) != bytes32(0x0), + string.concat( + "ContractConfig(getContractTypeByRawData): ContractType not found (", contractType.contractName(), ")" + ) + ); + } + + function getContractTypeFromCurrentNetwok(address contractAddr) public view virtual returns (TContract contractType) { + return getContractTypeByRawData(block.chainid, contractAddr); + } + + function setContractAbsolutePathMap(TContract contractType, string memory absolutePath) public virtual { + _contractAbsolutePathMap[contractType] = absolutePath; + } + + function getContractName(TContract contractType) public view virtual returns (string memory name) { + string memory contractTypeName = contractType.contractName(); + name = _contractNameMap[contractType]; + name = keccak256(bytes(contractTypeName)) == keccak256(bytes(name)) ? name : contractTypeName; + require( + bytes(name).length != 0, + string.concat( + "ContractConfig(getContractName): Contract Type not found (", + contractTypeName, + ")\n", + "Storage Name Map: ", + _contractNameMap[contractType] + ) + ); + } + + function getContractAbsolutePath(TContract contractType) public view virtual returns (string memory name) { + if (bytes(_contractAbsolutePathMap[contractType]).length != 0) { + name = string.concat( + _contractAbsolutePathMap[contractType], _contractNameMap[contractType], ".sol:", _contractNameMap[contractType] + ); + } else if (bytes(_absolutePath).length != 0) { + name = string.concat(_absolutePath, _contractNameMap[contractType], ".sol:", _contractNameMap[contractType]); + } else { + name = string.concat(_contractNameMap[contractType], ".sol"); + } + } + + function getAddressFromCurrentNetwork(TContract contractType) public view virtual returns (address payable) { + string memory contractName = getContractName(contractType); + require( + bytes(contractName).length != 0, + string.concat( + "ContractConfig(getAddressFromCurrentNetwork): Contract Type not found (", contractType.contractName(), ")" + ) + ); + return getAddressByRawData(block.chainid, contractName); + } + + function getAddressByString(string calldata contractName) public view virtual returns (address payable) { + return getAddressByRawData(block.chainid, contractName); + } + + function getAddressByRawData(uint256 chainId, string memory contractName) + public + view + virtual + returns (address payable addr) + { + addr = payable(_contractAddrMap[chainId][contractName]); + require( + addr != address(0x0), string.concat("ContractConfig(getAddressByRawData): Address not found: ", contractName) + ); + } + + function getAllAddressesByRawData(uint256 chainId) public view virtual returns (address payable[] memory addrs) { + address[] memory v = _contractAddrSet[chainId].values(); + assembly ("memory-safe") { + addrs := v + } + } + + function label(uint256 chainId, address contractAddr, string memory contractName) public virtual { + vm.label( + contractAddr, + string.concat("(", vm.toString(chainId).blue(), ")", contractName.yellow(), "[", vm.toString(contractAddr), "]") + ); + } + + function _storeDeploymentData(string memory deploymentRoot) internal virtual { + if (!vm.exists(deploymentRoot)) { + console.log("ContractConfig:", "No deployments folder, skip loading"); + return; + } + VmSafe.DirEntry[] memory deployments = vm.readDir(deploymentRoot); + + for (uint256 i; i < deployments.length;) { + VmSafe.DirEntry[] memory entries = vm.readDir(deployments[i].path); + uint256 chainId = vm.parseUint(vm.readFile(string.concat(deployments[i].path, "/.chainId"))); + + for (uint256 j; j < entries.length;) { + string memory path = entries[j].path; + + if (path.endsWith(".json")) { + string[] memory splitteds = path.split("/"); + string memory contractName = splitteds[splitteds.length - 1]; + string memory suffix = path.endsWith("Proxy.json") ? "Proxy.json" : ".json"; + // remove suffix + contractName = contractName.replace(suffix, ""); + string memory json = vm.readFile(path); + address contractAddr = vm.parseJsonAddress(json, ".address"); + label(chainId, contractAddr, contractName); + + // filter out logic deployments + if (!path.endsWith("Logic.json")) { + _contractAddrSet[chainId].add(contractAddr); + _contractAddrMap[chainId][contractName] = contractAddr; + _contractTypeMap[chainId][contractAddr] = TContract.wrap(contractName.packOne()); + } + } + + unchecked { + ++j; + } + } + + unchecked { + ++i; + } + } + } +} diff --git a/script/configs/MigrationConfig.sol b/script/configs/MigrationConfig.sol new file mode 100644 index 0000000..319ef34 --- /dev/null +++ b/script/configs/MigrationConfig.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { IMigrationConfig } from "../interfaces/configs/IMigrationConfig.sol"; + +abstract contract MigrationConfig is IMigrationConfig { + bytes internal _migrationConfig; + + function setRawSharedArguments(bytes memory config) public virtual { + if (_migrationConfig.length != 0) return; + _migrationConfig = config; + } + + function getRawSharedArguments() public view virtual returns (bytes memory) { + return _migrationConfig; + } +} diff --git a/script/configs/NetworkConfig.sol b/script/configs/NetworkConfig.sol new file mode 100644 index 0000000..53466da --- /dev/null +++ b/script/configs/NetworkConfig.sol @@ -0,0 +1,122 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { Vm } from "../../lib/forge-std/src/Vm.sol"; +import { StdStyle } from "../../lib/forge-std/src/StdStyle.sol"; +import { console2 as console } from "../../lib/forge-std/src/console2.sol"; +import { INetworkConfig } from "../interfaces/configs/INetworkConfig.sol"; +import { LibSharedAddress } from "../libraries/LibSharedAddress.sol"; +import { TNetwork } from "../types/Types.sol"; + +abstract contract NetworkConfig is INetworkConfig { + Vm private constant vm = Vm(LibSharedAddress.VM); + uint256 private constant NULL_FORK_ID = uint256(keccak256("NULL_FORK_ID")); + + string internal _deploymentRoot; + bool internal _isForkModeEnabled; + mapping(TNetwork network => NetworkData) internal _networkDataMap; + mapping(uint256 chainId => TNetwork network) internal _networkMap; + + constructor(string memory deploymentRoot) { + _deploymentRoot = deploymentRoot; + console.log( + string.concat("Block Number: ", vm.toString(block.number)), + "|", + string.concat("Timestamp: ", vm.toString(block.timestamp)) + ); + } + + function getDeploymentRoot() public virtual returns (string memory) { + return _deploymentRoot; + } + + function setForkMode(bool shouldEnable) public virtual { + _isForkModeEnabled = shouldEnable; + } + + function getDeploymentDirectory(TNetwork network) public view virtual returns (string memory dirPath) { + string memory dirName = _networkDataMap[network].deploymentDir; + require(bytes(dirName).length != 0, "NetworkConfig: Deployment directory not found"); + dirPath = string.concat(_deploymentRoot, dirName); + } + + function setNetworkInfo( + uint256 chainId, + TNetwork network, + string memory chainAlias, + string memory deploymentDir, + string memory privateKeyEnvLabel, + string memory explorer + ) public virtual { + _networkMap[chainId] = network; + _networkDataMap[network] = + NetworkData(tryCreateFork(chainAlias, chainId), chainId, chainAlias, deploymentDir, privateKeyEnvLabel, explorer); + } + + function getExplorer(TNetwork network) public view virtual returns (string memory link) { + link = _networkDataMap[network].explorer; + } + + function getAlias(TNetwork network) public view virtual returns (string memory networkAlias) { + networkAlias = _networkDataMap[network].chainAlias; + require(bytes(networkAlias).length != 0, "NetworkConfig: Network alias not found"); + } + + function getForkId(TNetwork network) public view virtual returns (uint256 forkId) { + forkId = _networkDataMap[network].forkId; + require(forkId != NULL_FORK_ID, "NetworkConfig: Network fork is not created"); + } + + function createFork(TNetwork network) public returns (uint256 forkId) { + NetworkData memory networkData = _networkDataMap[network]; + setForkMode({ shouldEnable: true }); + forkId = tryCreateFork(networkData.chainAlias, networkData.chainId); + _networkDataMap[network].forkId = forkId; + } + + function tryCreateFork(string memory chainAlias, uint256 chainId) public virtual returns (uint256) { + uint256 currentFork; + try vm.activeFork() returns (uint256 forkId) { + currentFork = forkId; + } catch { + console.log(StdStyle.yellow("NetworkConfig: fork mode disabled, no active fork")); + return NULL_FORK_ID; + } + if (chainId == block.chainid) { + console.log( + StdStyle.yellow(string.concat("NetworkConfig: ", chainAlias, " is already created and active at forkId:")), + currentFork + ); + return currentFork; + } + if (!_isForkModeEnabled) return NULL_FORK_ID; + try vm.createFork(vm.rpcUrl(chainAlias)) returns (uint256 forkId) { + console.log(StdStyle.blue(string.concat("NetworkConfig: ", chainAlias, " fork created with forkId:")), forkId); + return forkId; + } catch { + console.log(StdStyle.red("NetworkConfig: Cannot create fork with url:"), vm.rpcUrl(chainAlias)); + return NULL_FORK_ID; + } + } + + function switchTo(TNetwork network) public virtual { + console.log(StdStyle.blue("\n>>"), "Switching to:", StdStyle.yellow(_networkDataMap[network].chainAlias), "\n"); + uint256 forkId = _networkDataMap[network].forkId; + require(forkId != NULL_FORK_ID, "Network Config: Unexists fork!"); + vm.selectFork(forkId); + require(_networkDataMap[network].chainId == block.chainid, "NetworkConfig: Switch chain failed"); + } + + function getPrivateKeyEnvLabel(TNetwork network) public view virtual returns (string memory privateKeyEnvLabel) { + privateKeyEnvLabel = _networkDataMap[network].privateKeyEnvLabel; + require(bytes(privateKeyEnvLabel).length != 0, "Network Config: ENV label not found"); + } + + function getCurrentNetwork() public view virtual returns (TNetwork network) { + network = _networkMap[block.chainid]; + } + + function getNetworkByChainId(uint256 chainId) public view virtual returns (TNetwork network) { + network = _networkMap[chainId]; + } +} diff --git a/script/configs/RuntimeConfig.sol b/script/configs/RuntimeConfig.sol new file mode 100644 index 0000000..6a4c54e --- /dev/null +++ b/script/configs/RuntimeConfig.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { StdStyle } from "../../lib/forge-std/src/StdStyle.sol"; +import { console2 as console } from "../../lib/forge-std/src/console2.sol"; +import { LibString } from "../../lib/solady/src/utils/LibString.sol"; +import { IRuntimeConfig } from "../interfaces/configs/IRuntimeConfig.sol"; + +abstract contract RuntimeConfig is IRuntimeConfig { + using LibString for string; + + bool internal _resolved; + Option internal _option; + string internal _rawCommand; + + function getCommand() public view virtual returns (string memory) { + return _rawCommand; + } + + function resolveCommand(string calldata command) external virtual { + if (_resolved) return; + if (bytes(command).length != 0) { + string[] memory args = command.split("@"); + uint256 length = args.length; + + for (uint256 i; i < length;) { + if (args[i].eq("log")) _option.log = true; + else if (args[i].eq("trezor")) _option.trezor = true; + else console.log(StdStyle.yellow("Unsupported command: "), args[i]); + + unchecked { + ++i; + } + } + } + + _rawCommand = command; + _resolved = true; + + _handleRuntimeConfig(); + } + + function getRuntimeConfig() public view returns (Option memory option) { + option = _option; + } + + function _handleRuntimeConfig() internal virtual; +} diff --git a/script/configs/WalletConfig.sol b/script/configs/WalletConfig.sol new file mode 100644 index 0000000..d690afc --- /dev/null +++ b/script/configs/WalletConfig.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { IWalletConfig } from "../interfaces/configs/IWalletConfig.sol"; + +abstract contract WalletConfig is IWalletConfig { + uint256 internal _envPk; + address internal _envSender; + address internal _trezorSender; + + function getSender() public view virtual returns (address payable sender); + + function trezorPrefix() public view virtual returns (string memory) { + return "trezor://"; + } + + function deployerEnvLabel() public view virtual returns (string memory) { + return "DEPLOYER"; + } +} diff --git a/script/extensions/ScriptExtended.s.sol b/script/extensions/ScriptExtended.s.sol new file mode 100644 index 0000000..a67be98 --- /dev/null +++ b/script/extensions/ScriptExtended.s.sol @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { StdStyle } from "../../lib/forge-std/src/StdStyle.sol"; +import { console, Script } from "../../lib/forge-std/src/Script.sol"; +import { StdAssertions } from "../../lib/forge-std/src/StdAssertions.sol"; +import { IGeneralConfig } from "../interfaces/IGeneralConfig.sol"; +import { TNetwork, IScriptExtended } from "../interfaces/IScriptExtended.sol"; +import { LibErrorHandler } from "../../lib/contract-libs/src/LibErrorHandler.sol"; +import { LibSharedAddress } from "../libraries/LibSharedAddress.sol"; +import { TContract } from "../types/Types.sol"; + +abstract contract ScriptExtended is Script, StdAssertions, IScriptExtended { + using LibErrorHandler for bool; + + bytes public constant EMPTY_ARGS = ""; + IGeneralConfig public constant CONFIG = IGeneralConfig(LibSharedAddress.CONFIG); + + modifier logFn(string memory fnName) { + console.log("> ", StdStyle.blue(fnName), "..."); + _; + } + + modifier onlyOn(TNetwork networkType) { + require(network() == networkType, string.concat("ScriptExtended: Only allowed on ", CONFIG.getAlias(networkType))); + _; + } + + function setUp() public virtual { + vm.label(address(CONFIG), "GeneralConfig"); + deploySharedAddress(address(CONFIG), _configByteCode()); + } + + function _configByteCode() internal virtual returns (bytes memory); + + function run(bytes calldata callData, string calldata command) public virtual { + CONFIG.resolveCommand(command); + (bool success, bytes memory data) = address(this).delegatecall(callData); + success.handleRevert(msg.sig, data); + } + + function network() public view virtual returns (TNetwork) { + return CONFIG.getCurrentNetwork(); + } + + function forkId() public view virtual returns (uint256) { + return CONFIG.getForkId(network()); + } + + function sender() public view virtual returns (address payable) { + return CONFIG.getSender(); + } + + function fail() internal override { + super.fail(); + revert("ScriptExtended: Got failed assertion"); + } + + function deploySharedAddress(address where, bytes memory bytecode) public { + if (where.code.length == 0) { + vm.makePersistent(where); + vm.allowCheatcodes(where); + deployCodeTo(bytecode, where); + } + } + + function deploySharedMigration(TContract contractType, bytes memory bytecode) public returns (address where) { + where = address(ripemd160(abi.encode(contractType))); + deploySharedAddress(where, bytecode); + } + + function deployCodeTo(bytes memory creationCode, address where) internal { + deployCodeTo(EMPTY_ARGS, creationCode, 0, where); + } + + function deployCodeTo(bytes memory creationCode, uint256 value, address where) internal { + deployCodeTo(EMPTY_ARGS, creationCode, value, where); + } + + function deployCodeTo(bytes memory args, bytes memory creationCode, uint256 value, address where) internal { + vm.etch(where, abi.encodePacked(creationCode, args)); + (bool success, bytes memory runtimeBytecode) = where.call{ value: value }(""); + assertTrue(success, "ScriptExtended: Failed to create runtime bytecode."); + vm.etch(where, runtimeBytecode); + } +} diff --git a/script/interfaces/IArtifactFactory.sol b/script/interfaces/IArtifactFactory.sol new file mode 100644 index 0000000..b621725 --- /dev/null +++ b/script/interfaces/IArtifactFactory.sol @@ -0,0 +1,13 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +interface IArtifactFactory { + function generateArtifact( + address deployer, + address contractAddr, + string calldata contractAbsolutePath, + string calldata fileName, + bytes calldata args, + uint256 nonce + ) external; +} diff --git a/script/interfaces/IGeneralConfig.sol b/script/interfaces/IGeneralConfig.sol new file mode 100644 index 0000000..dd5cb74 --- /dev/null +++ b/script/interfaces/IGeneralConfig.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { IWalletConfig } from "./configs/IWalletConfig.sol"; +import { IRuntimeConfig } from "./configs/IRuntimeConfig.sol"; +import { IMigrationConfig } from "./configs/IMigrationConfig.sol"; +import { TNetwork, INetworkConfig } from "./configs/INetworkConfig.sol"; +import { TContract, IContractConfig } from "./configs/IContractConfig.sol"; + +interface IGeneralConfig is IWalletConfig, IRuntimeConfig, INetworkConfig, IContractConfig, IMigrationConfig { + function setAddress(TNetwork network, TContract contractType, address contractAddr) external; + + function getAddress(TNetwork network, TContract contractType) external view returns (address payable); + + function getAllAddresses(TNetwork network) external view returns (address payable[] memory); +} diff --git a/script/interfaces/IDeployScript.sol b/script/interfaces/IMigrationScript.sol similarity index 50% rename from script/interfaces/IDeployScript.sol rename to script/interfaces/IMigrationScript.sol index d5b010e..6a820f6 100644 --- a/script/interfaces/IDeployScript.sol +++ b/script/interfaces/IMigrationScript.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; -interface IDeployScript { +interface IMigrationScript { function run() external returns (address payable); - function setArgs(bytes calldata args) external returns (IDeployScript); + function overrideArgs(bytes calldata args) external returns (IMigrationScript); } diff --git a/script/interfaces/IScriptExtended.sol b/script/interfaces/IScriptExtended.sol new file mode 100644 index 0000000..a45fa55 --- /dev/null +++ b/script/interfaces/IScriptExtended.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { TNetwork } from "../types/Types.sol"; + +interface IScriptExtended { + function run(bytes calldata callData, string calldata command) external; + + function network() external view returns (TNetwork); + + function forkId() external view returns (uint256); + + function sender() external view returns (address payable); +} diff --git a/script/interfaces/configs/IContractConfig.sol b/script/interfaces/configs/IContractConfig.sol new file mode 100644 index 0000000..4d91aa7 --- /dev/null +++ b/script/interfaces/configs/IContractConfig.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { TContract } from "../../types/Types.sol"; + +interface IContractConfig { + function getContractTypeByRawData(uint256 chainId, address contractAddr) + external + view + returns (TContract contractType); + + function label(uint256 chainId, address contractAddr, string memory contractName) external; + + function getContractTypeFromCurrentNetwok(address contractAddr) external view returns (TContract contractType); + + function getContractName(TContract contractType) external view returns (string memory name); + + function getContractAbsolutePath(TContract contractType) external view returns (string memory name); + + function getAddressFromCurrentNetwork(TContract contractType) external view returns (address payable); + + function getAddressByString(string calldata contractName) external view returns (address payable); + + function getAddressByRawData(uint256 chainId, string calldata contractName) + external + view + returns (address payable addr); + + function getAllAddressesByRawData(uint256 chainId) external view returns (address payable[] memory addrs); +} diff --git a/script/interfaces/configs/IMigrationConfig.sol b/script/interfaces/configs/IMigrationConfig.sol new file mode 100644 index 0000000..1496f92 --- /dev/null +++ b/script/interfaces/configs/IMigrationConfig.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +interface IMigrationConfig { + function setRawSharedArguments(bytes calldata migrationConfig) external; + + function getRawSharedArguments() external view returns (bytes memory); +} diff --git a/script/interfaces/configs/INetworkConfig.sol b/script/interfaces/configs/INetworkConfig.sol new file mode 100644 index 0000000..940e603 --- /dev/null +++ b/script/interfaces/configs/INetworkConfig.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { TNetwork } from "../../types/Types.sol"; + +interface INetworkConfig { + struct NetworkData { + uint256 forkId; + uint256 chainId; + string chainAlias; + string deploymentDir; + string privateKeyEnvLabel; + string explorer; + } + + function setNetworkInfo( + uint256 chainId, + TNetwork network, + string calldata chainAlias, + string calldata deploymentDir, + string calldata privateKeyEnvLabel, + string calldata explorer + ) external; + + function setForkMode(bool shouldEnable) external; + + function createFork(TNetwork network) external returns (uint256 forkId); + + function getExplorer(TNetwork network) external view returns (string memory link); + + function getForkId(TNetwork network) external view returns (uint256 forkId); + + function getAlias(TNetwork network) external view returns (string memory networkAlias); + + function switchTo(TNetwork network) external; + + function tryCreateFork(string calldata chainAlias, uint256 chainId) external returns (uint256); + + function getDeploymentDirectory(TNetwork network) external view returns (string memory dirPath); + + function getDeploymentRoot() external returns (string memory); + + function getCurrentNetwork() external view returns (TNetwork network); + + function getPrivateKeyEnvLabel(TNetwork network) external view returns (string memory privateKeyEnvLabel); + + function getNetworkByChainId(uint256 chainId) external view returns (TNetwork network); +} diff --git a/script/interfaces/configs/IRuntimeConfig.sol b/script/interfaces/configs/IRuntimeConfig.sol new file mode 100644 index 0000000..28dae4d --- /dev/null +++ b/script/interfaces/configs/IRuntimeConfig.sol @@ -0,0 +1,15 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +interface IRuntimeConfig { + struct Option { + bool log; + bool trezor; + } + + function getCommand() external view returns (string memory); + + function resolveCommand(string calldata command) external; + + function getRuntimeConfig() external view returns (Option memory options); +} diff --git a/script/interfaces/configs/ISharedParameter.sol b/script/interfaces/configs/ISharedParameter.sol new file mode 100644 index 0000000..93f5523 --- /dev/null +++ b/script/interfaces/configs/ISharedParameter.sol @@ -0,0 +1,6 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +interface ISharedParameter { + function sharedArguments() external; +} diff --git a/script/interfaces/configs/IWalletConfig.sol b/script/interfaces/configs/IWalletConfig.sol new file mode 100644 index 0000000..86879aa --- /dev/null +++ b/script/interfaces/configs/IWalletConfig.sol @@ -0,0 +1,10 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +interface IWalletConfig { + function getSender() external view returns (address payable sender); + + function trezorPrefix() external view returns (string memory); + + function deployerEnvLabel() external view returns (string memory); +} diff --git a/script/libraries/LibProxy.sol b/script/libraries/LibProxy.sol new file mode 100644 index 0000000..b4dee57 --- /dev/null +++ b/script/libraries/LibProxy.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { Vm } from "../../lib/forge-std/src/Vm.sol"; +import { LibSharedAddress } from "./LibSharedAddress.sol"; + +library LibProxy { + Vm internal constant vm = Vm(LibSharedAddress.VM); + bytes32 internal constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103; + bytes32 internal constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc; + + function getProxyAdmin(address payable proxy) internal returns (address payable proxyAdmin) { + proxyAdmin = payable(address(uint160(uint256(vm.load(address(proxy), ADMIN_SLOT))))); + require( + proxyAdmin != address(0x0), + string.concat("LibProxy: Null ProxyAdmin, Provided address: ", vm.getLabel(proxy), " is not EIP1967 Proxy") + ); + } + + function getProxyImplementation(address payable proxy) internal returns (address payable impl) { + impl = payable(address(uint160(uint256(vm.load(address(proxy), IMPLEMENTATION_SLOT))))); + require( + impl != address(0x0), + string.concat("LibProxy: Null Implementation, Provided address: ", vm.getLabel(proxy), " is not EIP1967 Proxy") + ); + } +} diff --git a/script/libraries/LibSharedAddress.sol b/script/libraries/LibSharedAddress.sol new file mode 100644 index 0000000..4504619 --- /dev/null +++ b/script/libraries/LibSharedAddress.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +library LibSharedAddress { + address internal constant CONFIG = address(uint160(uint256(keccak256("config")))); + address internal constant VM = address(uint160(uint256(keccak256("hevm cheat code")))); + address internal constant ARTIFACT_FACTORY = address(uint160(uint256(keccak256("logger")))); +} diff --git a/script/libraries/LibSig.sol b/script/libraries/LibSig.sol new file mode 100644 index 0000000..4b7e6f8 --- /dev/null +++ b/script/libraries/LibSig.sol @@ -0,0 +1,33 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +library LibSig { + /** + * @dev Merges the ECDSA values into a single signature bytes + * @param v ECDSA recovery value + * @param r ECDSA r value + * @param s ECDSA s value + * @return signature Combined signature bytes + */ + function merge(uint8 v, bytes32 r, bytes32 s) internal pure returns (bytes memory signature) { + signature = new bytes(65); + assembly ("memory-safe") { + mstore(add(signature, 0x20), r) + mstore(add(signature, 0x40), s) + mstore8(add(signature, 0x60), v) + } + } + + /** + * @dev Splits the signature bytes into ECDSA values + * @param signature Signature bytes to split + * @return r s v Tuple of ECDSA values + */ + function split(bytes calldata signature) internal pure returns (bytes32 r, bytes32 s, uint8 v) { + assembly ("memory-safe") { + r := calldataload(signature.offset) + s := calldataload(add(signature.offset, 0x20)) + v := byte(0, calldataload(add(signature.offset, 0x40))) + } + } +} diff --git a/script/sample/SampleGeneralConfig.sol b/script/sample/SampleGeneralConfig.sol new file mode 100644 index 0000000..2384c5a --- /dev/null +++ b/script/sample/SampleGeneralConfig.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { BaseGeneralConfig } from "foundry-deployment-kit/BaseGeneralConfig.sol"; +import { Contract } from "./utils/Contract.sol"; + +contract SampleGeneralConfig is BaseGeneralConfig { + constructor() BaseGeneralConfig("", "deployments/") { } + + function _setUpContracts() internal virtual override { + super._setUpContracts(); + + _contractNameMap[Contract.Sample.key()] = Contract.Sample.name(); + // {SamepleClone} share same logic as {Sample} + _contractNameMap[Contract.SampleClone.key()] = Contract.Sample.name(); + _contractNameMap[Contract.SampleProxy.key()] = Contract.SampleProxy.name(); + + // allow diffrent contracts to share same logic + _contractNameMap[Contract.tSLP.key()] = "Token"; + _contractNameMap[Contract.tAXS.key()] = "Token"; + _contractNameMap[Contract.tWETH.key()] = "Token"; + _contractNameMap[Contract.tWRON.key()] = "Token"; + _contractNameMap[Contract.tBERRY.key()] = "Token"; + } +} diff --git a/script/sample/SampleMigration.s.sol b/script/sample/SampleMigration.s.sol new file mode 100644 index 0000000..6817bcc --- /dev/null +++ b/script/sample/SampleMigration.s.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { BaseMigration } from "foundry-deployment-kit/BaseMigration.s.sol"; +import { DefaultNetwork } from "foundry-deployment-kit/utils/DefaultNetwork.sol"; +import { SampleGeneralConfig } from "./SampleGeneralConfig.sol"; +import { ISharedArgument } from "./interfaces/ISharedArgument.sol"; + +contract SampleMigration is BaseMigration { + ISharedArgument public constant config = ISharedArgument(address(CONFIG)); + + function _configByteCode() internal virtual override returns (bytes memory) { + return abi.encodePacked(type(SampleGeneralConfig).creationCode); + } + + function _sharedArguments() internal virtual override returns (bytes memory rawArgs) { + ISharedArgument.SharedParameter memory param; + + if (network() == DefaultNetwork.RoninTestnet.key()) { + param.message = "Sample Ronin Testnet"; + param.proxyMessage = "Sample Proxy Ronin Testnet"; + } + if (network() == DefaultNetwork.RoninMainnet.key()) { + param.message = "Sample Ronin Mainnet"; + param.proxyMessage = "Sample Proxy Ronin Mainnet"; + } + if (network() == DefaultNetwork.Local.key()) { + param.message = "Sample Anvil"; + param.proxyMessage = "Sample Proxy Anvil"; + } + + rawArgs = abi.encode(param); + } +} diff --git a/script/sample/contracts/SampleCloneDeploy.s.sol b/script/sample/contracts/SampleCloneDeploy.s.sol new file mode 100644 index 0000000..5a73e6f --- /dev/null +++ b/script/sample/contracts/SampleCloneDeploy.s.sol @@ -0,0 +1,28 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { Sample } from "src/Sample.sol"; +import { Contract } from "../utils/Contract.sol"; +import { ISharedArgument, SampleMigration } from "../SampleMigration.s.sol"; +import { SampleDeploy } from "./SampleDeploy.s.sol"; + +contract SampleCloneDeploy is SampleMigration { + function _injectDependencies() internal virtual override { + // simple creation for deploy migration + _setDependencyDeployScript(Contract.Sample.key(), new SampleDeploy()); + // create deploy migration with deterministic address + _setDependencyDeployScript( + Contract.Sample.key(), deploySharedMigration(Contract.Sample.key(), type(SampleDeploy).creationCode) + ); + } + + function _defaultArguments() internal virtual override returns (bytes memory args) { + ISharedArgument.SharedParameter memory param = config.sharedArguments(); + args = abi.encode(param.message); + } + + function run() public virtual returns (Sample instance) { + instance = Sample(_deployImmutable(Contract.SampleClone.key())); + assertEq(instance.getMessage(), config.sharedArguments().message); + } +} diff --git a/script/sample/contracts/SampleDeploy.s.sol b/script/sample/contracts/SampleDeploy.s.sol new file mode 100644 index 0000000..7784c74 --- /dev/null +++ b/script/sample/contracts/SampleDeploy.s.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { Sample } from "src/Sample.sol"; +import { Contract } from "../utils/Contract.sol"; +import { ISharedArgument, SampleMigration } from "../SampleMigration.s.sol"; + +contract SampleDeploy is SampleMigration { + function _defaultArguments() internal virtual override returns (bytes memory args) { + ISharedArgument.SharedParameter memory param = config.sharedArguments(); + args = abi.encode(param.message); + } + + function run() public virtual returns (Sample instance) { + instance = Sample(_deployImmutable(Contract.Sample.key())); + assertEq(instance.getMessage(), config.sharedArguments().message); + } +} diff --git a/script/sample/contracts/SampleProxyDeploy.s.sol b/script/sample/contracts/SampleProxyDeploy.s.sol new file mode 100644 index 0000000..5bf56c5 --- /dev/null +++ b/script/sample/contracts/SampleProxyDeploy.s.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { SampleProxy } from "src/SampleProxy.sol"; +import { Contract } from "../utils/Contract.sol"; +import { ISharedArgument, SampleMigration } from "../SampleMigration.s.sol"; + +contract SampleProxyDeploy is SampleMigration { + function _defaultArguments() internal virtual override returns (bytes memory args) { + ISharedArgument.SharedParameter memory param = ISharedArgument(address(CONFIG)).sharedArguments(); + args = abi.encodeCall(SampleProxy.initialize, (param.proxyMessage)); + } + + function run() public virtual returns (SampleProxy instance) { + instance = SampleProxy(_deployProxy(Contract.SampleProxy.key())); + assertEq(instance.getMessage(), ISharedArgument(address(CONFIG)).sharedArguments().proxyMessage); + } +} diff --git a/script/sample/interfaces/ISharedArgument.sol b/script/sample/interfaces/ISharedArgument.sol new file mode 100644 index 0000000..5d12e63 --- /dev/null +++ b/script/sample/interfaces/ISharedArgument.sol @@ -0,0 +1,22 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { IGeneralConfig } from "foundry-deployment-kit/interfaces/IGeneralConfig.sol"; + +interface ISharedArgument is IGeneralConfig { + struct SharedParameter { + string message; + string proxyMessage; + address mFactory; + address testnetFactory; + bytes32 mPairCodeHash; + bytes32 testnetpairCodeHash; + address mWRON; + address mSLP; + address mAXS; + address mWETH; + address mBERRY; + } + + function sharedArguments() external view returns (SharedParameter memory param); +} diff --git a/script/sample/ronin-mainnet/xxxxyyzz_DeploySample/xxxxyyzz_DeploySample.s.sol b/script/sample/ronin-mainnet/xxxxyyzz_DeploySample/xxxxyyzz_DeploySample.s.sol new file mode 100644 index 0000000..23564e8 --- /dev/null +++ b/script/sample/ronin-mainnet/xxxxyyzz_DeploySample/xxxxyyzz_DeploySample.s.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { DefaultNetwork } from "foundry-deployment-kit/utils/DefaultNetwork.sol"; +import { ISharedArgument, SampleMigration } from "../../SampleMigration.s.sol"; +import { Sample, SampleDeploy } from "../../contracts/SampleDeploy.s.sol"; +import { SampleProxy, SampleProxyDeploy } from "../../contracts/SampleProxyDeploy.s.sol"; + +contract Migration__XXXXYYZZ_DeploySample is SampleMigration { + function _sharedArguments() internal virtual override returns (bytes memory args) { + args = super._sharedArguments(); + + ISharedArgument.SharedParameter memory param = abi.decode(args, (ISharedArgument.SharedParameter)); + param.message = "Migration__XXXXYYZZ_DeploySample@MainnetSample"; + param.proxyMessage = "Migration__XXXXYYZZ_DeploySample@MainnetProxySample"; + + args = abi.encode(param); + } + + function run() public onlyOn(DefaultNetwork.RoninMainnet.key()) { + Sample sample = new SampleDeploy().run(); + SampleProxy sampleProxy = new SampleProxyDeploy().run(); + + assertEq(sample.getMessage(), "Migration__XXXXYYZZ_DeploySample@MainnetSample"); + assertEq(sampleProxy.getMessage(), "Migration__XXXXYYZZ_DeploySample@MainnetProxySample"); + } +} diff --git a/script/sample/ronin-testnet/20231204-DeployMockERC20.s.sol/20231204_DeployMockERC20.s.sol b/script/sample/ronin-testnet/20231204-DeployMockERC20.s.sol/20231204_DeployMockERC20.s.sol new file mode 100644 index 0000000..b3a1fd7 --- /dev/null +++ b/script/sample/ronin-testnet/20231204-DeployMockERC20.s.sol/20231204_DeployMockERC20.s.sol @@ -0,0 +1,66 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { console2 as console } from "forge-std/console2.sol"; +import { DefaultNetwork } from "foundry-deployment-kit/utils/DefaultNetwork.sol"; +import { Contract } from "../../utils/Contract.sol"; +import { ISharedArgument, SampleMigration } from "../../SampleMigration.s.sol"; +import { Token } from "../../../../src/Token.sol"; +import { WNT } from "../../../../src/WNT.sol"; + +contract Migration__20231204_DeployMockERC20 is SampleMigration { + function _sharedArguments() internal virtual override returns (bytes memory args) { + args = super._sharedArguments(); + + ISharedArgument.SharedParameter memory param = abi.decode(args, (ISharedArgument.SharedParameter)); + + param.mAXS = 0x97a9107C1793BC407d6F527b77e7fff4D812bece; + param.mSLP = 0xa8754b9Fa15fc18BB59458815510E40a12cD2014; + param.mWETH = 0xc99a6A985eD2Cac1ef41640596C5A5f9F4E19Ef5; + param.mWRON = 0xe514d9DEB7966c8BE0ca922de8a064264eA6bcd4; + param.mBERRY = 0x1B918543B518E34902e1E8dd76052BeE43C762Ff; + + config.label(2021, param.mAXS, "AXS"); + config.label(2021, param.mSLP, "SLP"); + config.label(2021, param.mWETH, "WETH"); + config.label(2021, param.mWRON, "WRON"); + config.label(2021, param.mBERRY, "BERRY"); + + args = abi.encode(param); + } + + function run() public onlyOn(DefaultNetwork.RoninTestnet.key()) { + ISharedArgument.SharedParameter memory param = config.sharedArguments(); + + config.createFork(DefaultNetwork.RoninMainnet.key()); + config.switchTo(DefaultNetwork.RoninMainnet.key()); + + uint256 mAXSTotalSupply = Token(param.mAXS).totalSupply(); + uint256 mSLPTotalSupply = Token(param.mSLP).totalSupply(); + uint256 mWETHTotalSupply = Token(param.mWETH).totalSupply(); + uint256 mBERRYTotalSupply = Token(param.mBERRY).totalSupply(); + + console.log("mAXSTotalSupply", mAXSTotalSupply); + console.log("mSLPTotalSupply", mSLPTotalSupply); + console.log("mWETHTotalSupply", mWETHTotalSupply); + console.log("mBERRYTotalSupply", mBERRYTotalSupply); + + config.switchTo(DefaultNetwork.RoninTestnet.key()); + + Token tAXS = Token(_deployImmutable(Contract.tAXS.key(), abi.encode("Axie Infinity Shard", "AXS"))); + Token tSLP = Token(_deployImmutable(Contract.tSLP.key(), abi.encode("Smooth Love Potion", "SLP"))); + Token tWETH = Token(_deployImmutable(Contract.tWETH.key(), abi.encode("Wrapped Ether", "WETH"))); + Token tBERRY = Token(_deployImmutable(Contract.tBERRY.key(), abi.encode("BERRY", "BERRY"))); + + address admin = makeAddr("admin"); + + vm.startBroadcast(); + + tAXS.mint(admin, mAXSTotalSupply); + tSLP.mint(admin, mSLPTotalSupply); + tWETH.mint(admin, mWETHTotalSupply); + tBERRY.mint(admin, mBERRYTotalSupply); + + vm.stopBroadcast(); + } +} diff --git a/script/sample/ronin-testnet/xxxxyyzz-DeploySample/xxxxyyzz_DeploySample.s.sol b/script/sample/ronin-testnet/xxxxyyzz-DeploySample/xxxxyyzz_DeploySample.s.sol new file mode 100644 index 0000000..035afc6 --- /dev/null +++ b/script/sample/ronin-testnet/xxxxyyzz-DeploySample/xxxxyyzz_DeploySample.s.sol @@ -0,0 +1,27 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { DefaultNetwork } from "foundry-deployment-kit/utils/DefaultNetwork.sol"; +import { ISharedArgument, SampleMigration } from "../../SampleMigration.s.sol"; +import { Sample, SampleDeploy } from "../../contracts/SampleDeploy.s.sol"; +import { SampleProxy, SampleProxyDeploy } from "../../contracts/SampleProxyDeploy.s.sol"; + +contract Migration__XXXXYYZZ_DeploySample is SampleMigration { + function _sharedArguments() internal virtual override returns (bytes memory args) { + args = super._sharedArguments(); + + ISharedArgument.SharedParameter memory param = abi.decode(args, (ISharedArgument.SharedParameter)); + param.message = "Migration__XXXXYYZZ_DeploySample@TestnetSample"; + param.proxyMessage = "Migration__XXXXYYZZ_DeploySample@TestnetProxySample"; + + args = abi.encode(param); + } + + function run() public onlyOn(DefaultNetwork.RoninTestnet.key()) { + Sample sample = new SampleDeploy().run(); + SampleProxy sampleProxy = new SampleProxyDeploy().run(); + + assertEq(sample.getMessage(), "Migration__XXXXYYZZ_DeploySample@TestnetSample"); + assertEq(sampleProxy.getMessage(), "Migration__XXXXYYZZ_DeploySample@TestnetProxySample"); + } +} diff --git a/script/sample/utils/Contract.sol b/script/sample/utils/Contract.sol new file mode 100644 index 0000000..9a0de1a --- /dev/null +++ b/script/sample/utils/Contract.sol @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { LibString } from "lib/solady/src/utils/LibString.sol"; +import { TContract } from "foundry-deployment-kit/types/Types.sol"; + +enum Contract { + tSLP, + tAXS, + tWRON, + tBERRY, + tWETH, + Sample, + SampleClone, + SampleProxy +} + +using { key, name } for Contract global; + +function key(Contract contractEnum) pure returns (TContract) { + return TContract.wrap(LibString.packOne(name(contractEnum))); +} + +function name(Contract contractEnum) pure returns (string memory) { + if (contractEnum == Contract.Sample) return "Sample"; + if (contractEnum == Contract.tBERRY) return "tBERRY"; + if (contractEnum == Contract.tWETH) return "tWETH"; + if (contractEnum == Contract.tSLP) return "tSLP"; + if (contractEnum == Contract.tAXS) return "tAXS"; + if (contractEnum == Contract.tWRON) return "tWRON"; + if (contractEnum == Contract.SampleClone) return "SampleClone"; + if (contractEnum == Contract.SampleProxy) return "SampleProxy"; + revert("Contract: Unknown contract"); +} diff --git a/script/types/Types.sol b/script/types/Types.sol new file mode 100644 index 0000000..36f0193 --- /dev/null +++ b/script/types/Types.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { LibString } from "../../lib/solady/src/utils/LibString.sol"; + +type TNetwork is bytes32; + +type TContract is bytes32; + +using LibString for bytes32; +using { networkName, networkEq as ==, networkNeq as != } for TNetwork global; +using { contractName, contractEq as ==, contractNeq as != } for TContract global; + +function networkName(TNetwork network) pure returns (string memory) { + return TNetwork.unwrap(network).unpackOne(); +} + +function contractName(TContract contractType) pure returns (string memory) { + return TContract.unwrap(contractType).unpackOne(); +} + +function networkEq(TNetwork a, TNetwork b) pure returns (bool) { + return TNetwork.unwrap(a) == TNetwork.unwrap(b); +} + +function networkNeq(TNetwork a, TNetwork b) pure returns (bool) { + return TNetwork.unwrap(a) != TNetwork.unwrap(b); +} + +function contractEq(TContract a, TContract b) pure returns (bool) { + return TContract.unwrap(a) == TContract.unwrap(b); +} + +function contractNeq(TContract a, TContract b) pure returns (bool) { + return TContract.unwrap(a) != TContract.unwrap(b); +} diff --git a/script/utils/DefaultContract.sol b/script/utils/DefaultContract.sol new file mode 100644 index 0000000..dcdb822 --- /dev/null +++ b/script/utils/DefaultContract.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { LibString } from "../../lib/solady/src/utils/LibString.sol"; +import { TContract } from "../types/Types.sol"; + +enum DefaultContract { ProxyAdmin } + +using { key, name } for DefaultContract global; + +function key(DefaultContract defaultContract) pure returns (TContract) { + return TContract.wrap(LibString.packOne(name(defaultContract))); +} + +function name(DefaultContract defaultContract) pure returns (string memory) { + if (defaultContract == DefaultContract.ProxyAdmin) return "ProxyAdmin"; + revert("DefaultContract: Unknown contract"); +} diff --git a/script/utils/DefaultNetwork.sol b/script/utils/DefaultNetwork.sol new file mode 100644 index 0000000..7a5a9eb --- /dev/null +++ b/script/utils/DefaultNetwork.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { LibString } from "../../lib/solady/src/utils/LibString.sol"; +import { TNetwork } from "../types/Types.sol"; + +enum DefaultNetwork { + Local, + RoninTestnet, + RoninMainnet +} + +using { key, name, chainId, chainAlias, envLabel, deploymentDir, explorer } for DefaultNetwork global; + +function chainId(DefaultNetwork defaultNetwork) pure returns (uint256) { + if (defaultNetwork == DefaultNetwork.Local) return 31337; + if (defaultNetwork == DefaultNetwork.RoninMainnet) return 2020; + if (defaultNetwork == DefaultNetwork.RoninTestnet) return 2021; + revert("DefaultNetwork: Unknown chain id"); +} + +function explorer(DefaultNetwork defaultNetwork) pure returns (string memory link) { + if (defaultNetwork == DefaultNetwork.RoninMainnet) return "https://app.roninchain.com/"; + if (defaultNetwork == DefaultNetwork.RoninTestnet) return "https://saigon-app.roninchain.com/"; + return ""; +} + +function key(DefaultNetwork defaultNetwork) pure returns (TNetwork) { + return TNetwork.wrap(LibString.packOne(name(defaultNetwork))); +} + +function name(DefaultNetwork defaultNetwork) pure returns (string memory) { + if (defaultNetwork == DefaultNetwork.Local) return "Local"; + if (defaultNetwork == DefaultNetwork.RoninTestnet) return "RoninTestnet"; + if (defaultNetwork == DefaultNetwork.RoninMainnet) return "RoninMainnet"; + revert("DefaultNetwork: Unknown network name"); +} + +function deploymentDir(DefaultNetwork defaultNetwork) pure returns (string memory) { + if (defaultNetwork == DefaultNetwork.Local) return "local/"; + if (defaultNetwork == DefaultNetwork.RoninTestnet) return "ronin-testnet/"; + if (defaultNetwork == DefaultNetwork.RoninMainnet) return "ronin-mainnet/"; + revert("DefaultNetwork: Unknown network deployment directory"); +} + +function envLabel(DefaultNetwork defaultNetwork) pure returns (string memory) { + if (defaultNetwork == DefaultNetwork.Local) return "LOCAL_PK"; + if (defaultNetwork == DefaultNetwork.RoninTestnet) return "TESTNET_PK"; + if (defaultNetwork == DefaultNetwork.RoninMainnet) return "MAINNET_PK"; + revert("DefaultNetwork: Unknown private key env label"); +} + +function chainAlias(DefaultNetwork defaultNetwork) pure returns (string memory) { + if (defaultNetwork == DefaultNetwork.Local) return "local"; + if (defaultNetwork == DefaultNetwork.RoninTestnet) return "ronin-testnet"; + if (defaultNetwork == DefaultNetwork.RoninMainnet) return "ronin-mainnet"; + revert("DefaultNetwork: Unknown network alias"); +} diff --git a/src/Sample.sol b/src/Sample.sol new file mode 100644 index 0000000..5766c24 --- /dev/null +++ b/src/Sample.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +contract Sample { + string internal _message; + + constructor(string memory message) { + _message = message; + } + + function setMessage(string memory message) public { + _message = message; + } + + function getMessage() public view returns (string memory) { + return _message; + } +} diff --git a/src/SampleProxy.sol b/src/SampleProxy.sol new file mode 100644 index 0000000..97682d8 --- /dev/null +++ b/src/SampleProxy.sol @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { Initializable } from "../lib/openzeppelin-contracts/contracts/proxy/utils/Initializable.sol"; + +contract SampleProxy is Initializable { + uint256[50] private __gap; + + string internal _message; + + constructor() { + _disableInitializers(); + } + + function initialize(string calldata message) external initializer { + _message = message; + } + + function setMessage(string memory message) public { + _message = message; + } + + function getMessage() public view returns (string memory) { + return _message; + } +} diff --git a/src/Token.sol b/src/Token.sol new file mode 100644 index 0000000..0a6d9c9 --- /dev/null +++ b/src/Token.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { ERC20 } from "../lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; + +contract Token is ERC20 { + constructor(string memory name_, string memory symbol_) ERC20(name_, symbol_) { } + + function mint(address to, uint256 supply) external { + _mint(to, supply); + } +} diff --git a/src/WNT.sol b/src/WNT.sol new file mode 100644 index 0000000..28cb8ce --- /dev/null +++ b/src/WNT.sol @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import { ERC20 } from "../lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; +import { IWNT } from "./interfaces/IWNT.sol"; +import { LibNativeTransfer } from "./libraries/LibNativeTransfer.sol"; + +/// @notice Minimalist and modern Wrapped Ether implementation. +/// @author Solmate +/// (https://github.com/transmissions11/solmate/blob/main/src/tokens/WETH.sol) +/// @author Inspired by WETH9 +/// (https://github.com/dapphub/ds-weth/blob/master/src/weth9.sol) +contract WNT is IWNT, ERC20 { + constructor(string memory name_, string memory symbol_) payable ERC20(name_, symbol_) { } + + function deposit() public payable virtual { + address sender = _msgSender(); + _mint(sender, msg.value); + emit Deposit(sender, msg.value); + } + + function withdraw(uint256 amount) public virtual { + address sender = _msgSender(); + _burn(sender, amount); + emit Withdrawal(sender, amount); + LibNativeTransfer.transfer(sender, amount, 2300); + } + + receive() external payable virtual { + deposit(); + } +} diff --git a/src/interfaces/IWNT.sol b/src/interfaces/IWNT.sol new file mode 100644 index 0000000..67c2ce7 --- /dev/null +++ b/src/interfaces/IWNT.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +import { IERC20 } from "../../lib/openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; + +interface IWNT is IERC20 { + event Deposit(address indexed from, uint256 amount); + + event Withdrawal(address indexed to, uint256 amount); + + function deposit() external payable; + + function withdraw(uint256 amount) external; +} diff --git a/src/libraries/LibErrorHandler.sol b/src/libraries/LibErrorHandler.sol new file mode 100644 index 0000000..8202fca --- /dev/null +++ b/src/libraries/LibErrorHandler.sol @@ -0,0 +1,40 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.19; + +library LibErrorHandler { + /// @dev Reserves error definition to upload to signature database. + error ExternalCallFailed(bytes4 msgSig, bytes4 callSig); + + /// @notice handle low level call revert if call failed, + /// If extcall return empty bytes, reverts with custom error. + /// @param status Status of external call + /// @param callSig function signature of the calldata + /// @param returnOrRevertData bytes result from external call + function handleRevert(bool status, bytes4 callSig, bytes memory returnOrRevertData) internal pure { + // Get the function signature of current context + bytes4 msgSig = msg.sig; + assembly ("memory-safe") { + if iszero(status) { + // Load the length of bytes array + let revertLength := mload(returnOrRevertData) + // Check if length != 0 => revert following reason from external call + if iszero(iszero(revertLength)) { + // Start of revert data bytes. The 0x20 offset is always the same. + revert(add(returnOrRevertData, 0x20), revertLength) + } + + // Load free memory pointer + let ptr := mload(0x40) + // Store 4 bytes the function selector of ExternalCallFailed(msg.sig, callSig) + // Equivalent to revert ExternalCallFailed(bytes4,bytes4) + mstore(ptr, 0x49bf4104) + // Store 4 bytes of msgSig parameter in the next slot + mstore(add(ptr, 0x20), msgSig) + // Store 4 bytes of callSig parameter in the next slot + mstore(add(ptr, 0x40), callSig) + // Revert 68 bytes of error starting from 0x1c + revert(add(ptr, 0x1c), 0x44) + } + } + } +} diff --git a/src/libraries/LibNativeTransfer.sol b/src/libraries/LibNativeTransfer.sol new file mode 100644 index 0000000..6e5a330 --- /dev/null +++ b/src/libraries/LibNativeTransfer.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +import { LibErrorHandler } from "./LibErrorHandler.sol"; + +/** + * @title NativeTransferHelper + */ +library LibNativeTransfer { + using LibErrorHandler for bool; + + /** + * @dev Transfers Native Coin and wraps result for the method caller to a recipient. + */ + function transfer(address to, uint256 value, uint256 gasAmount) internal { + (bool success, bytes memory returnOrRevertData) = trySendValue(to, value, gasAmount); + success.handleRevert(bytes4(0x0), returnOrRevertData); + } + + /** + * @dev Unsafe send `amount` Native to the address `to`. If the sender's balance is insufficient, + * the call does not revert. + * + * Note: + * - Does not assert whether the balance of sender is sufficient. + * - Does not assert whether the recipient accepts NATIVE. + * - Consider using `ReentrancyGuard` before calling this function. + * + */ + function trySendValue(address to, uint256 value, uint256 gasAmount) + internal + returns (bool success, bytes memory returnOrRevertData) + { + (success, returnOrRevertData) = to.call{ value: value, gas: gasAmount }(""); + } +} diff --git a/upload-sig.sh b/upload-sig.sh new file mode 100755 index 0000000..c6fd92f --- /dev/null +++ b/upload-sig.sh @@ -0,0 +1,86 @@ +# Default network value +networkName="ronin-testnet" +# Function to print usage and exit +usage() { + echo "Usage: $0 -c " + echo " -c: Specify the network (ronin-testnet or ronin-mainnet)" + exit 1 +} +# Parse command-line options +while getopts "c:" opt; do + case $opt in + c) + case "$OPTARG" in + ronin-testnet) + child_folder="ronin-testnet" + networkName="ronin-testnet" + ;; + ronin-mainnet) + child_folder="ronin-mainnet" + networkName="ronin-mainnet" + ;; + *) + echo "Unknown network specified: $OPTARG" + usage + ;; + esac + ;; + *) + usage + ;; + esac +done +# Shift the processed options out of the argument list +shift $((OPTIND - 1)) +# Define the deployments folder by concatenating it with the child folder +folder="deployments/$child_folder" +# Check if the specified folder exists +if [ ! -d "$folder" ]; then + echo "Error: The specified folder does not exist for the selected network." + exit 1 +fi +index=0 +for file in "$folder"/*.json; do + # Check if the file exists and is a regular file + if [ -f "$file" ] && [ "$(basename "$file")" != ".chainId" ]; then + # Extract contractName and address from the JSON file + contractName=$(jq -r '.contractName' "$file") + # Check if contractName and address are not empty + if [ -n "$contractName" ]; then + # Increment the index + ((index++)) + ( + # Initialize arrays to store events and errors keys + events_keys=() + errors_keys=() + # Get events and errors JSON data + events=$(forge inspect $contractName events) + errors=$(forge inspect $contractName errors) + # Extract keys and populate the arrays + while read -r key; do + events_keys+=("\"event $key\"") + done <<<"$(echo "$events" | jq -r 'keys[]')" + while read -r key; do + errors_keys+=("\"$key\"") + done <<<"$(echo "$errors" | jq -r 'keys[]')" + # Combine keys from events and errors + all_keys=("${events_keys[@]}" "${errors_keys[@]}") + echo cast upload-signature "${all_keys[@]}" + # Call cast upload-signature + cast upload-signature "${all_keys[@]}" + ) & + else + echo "Error: Missing contractName or address in $file" + fi + fi + + # Check if index is a multiple of 10, then wait + if [ $((index % 10)) -eq 0 ]; then + wait + fi + +done + +forge selectors upload --all & + +wait