"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.deployBeaconProxy = exports.queryOptimismMessageStatus = exports.executeOnAllConnectors = exports.getProviderFromHardhatConfig = exports.getConnectorDeployments = exports.getMessagingProtocolConfig = exports.verify = exports.getDeploymentName = exports.getConnectorName = exports.getRelayerProxyConfig = exports.getProtocolNetwork = exports.ProtocolNetworks = exports.ProtocolNetwork = exports.mustGetEnv = void 0;
const dotenv_1 = require("dotenv");
const ethers_1 = require("ethers");
const sdk_1 = require("@eth-optimism/sdk");
const shared_1 = require("../deployConfig/shared");
const deployments_json_1 = __importDefault(require("../deployments.json"));
const config_1 = require("./config");
(0, dotenv_1.config)();
const mustGetEnv = (_env) => {
    const env = _env ?? process.env.ENV ?? "staging";
    if (env !== "staging" && env !== "production" && env !== "local") {
        throw new Error(`Unrecognized env: ${env}`);
    }
    return env;
};
exports.mustGetEnv = mustGetEnv;
var ProtocolNetwork;
(function (ProtocolNetwork) {
    ProtocolNetwork["MAINNET"] = "mainnet";
    ProtocolNetwork["TESTNET"] = "testnet";
    ProtocolNetwork["LOCAL"] = "local";
})(ProtocolNetwork = exports.ProtocolNetwork || (exports.ProtocolNetwork = {}));
exports.ProtocolNetworks = {
    // local networks
    "1337": ProtocolNetwork.LOCAL,
    "1338": ProtocolNetwork.LOCAL,
    // testnets
    "5": ProtocolNetwork.TESTNET,
    "420": ProtocolNetwork.TESTNET,
    "80001": ProtocolNetwork.TESTNET,
    "97": ProtocolNetwork.TESTNET,
    "421613": ProtocolNetwork.TESTNET,
    "280": ProtocolNetwork.TESTNET,
    "59140": ProtocolNetwork.TESTNET,
    // mainnets
    "1": ProtocolNetwork.MAINNET,
    "10": ProtocolNetwork.MAINNET,
    "56": ProtocolNetwork.MAINNET,
    "137": ProtocolNetwork.MAINNET,
    "42161": ProtocolNetwork.MAINNET,
    "100": ProtocolNetwork.MAINNET,
};
const getProtocolNetwork = (_chain) => {
    const chain = _chain.toString();
    // If chain 1337 or 1338, use local network.
    return exports.ProtocolNetworks[chain] ?? ProtocolNetwork.LOCAL;
};
exports.getProtocolNetwork = getProtocolNetwork;
const getRelayerProxyConfig = (_chain) => {
    const feeCollector = ethers_1.constants.AddressZero;
    const gelatoRelayer = ethers_1.constants.AddressZero;
    return { feeCollector, gelatoRelayer };
};
exports.getRelayerProxyConfig = getRelayerProxyConfig;
const getConnectorName = (config, connectorChainId, deployChainId) => {
    deployChainId = deployChainId ?? connectorChainId;
    const naming = config.configs[connectorChainId];
    if (!naming) {
        throw new Error(`Could not find ${connectorChainId} in config`);
    }
    // Only spoke connectors deployed for mainnet contracts
    return `${naming.prefix}${config.hub === deployChainId && !naming.prefix.includes("Mainnet") ? shared_1.HUB_PREFIX : shared_1.SPOKE_PREFIX}Connector`;
};
exports.getConnectorName = getConnectorName;
// These contracts do not have a `Staging` deployment
const NON_STAGING_CONTRACTS = ["TestERC20", "TestWETH", "LPToken"];
const getDeploymentName = (_contractName, _env, _networkName) => {
    const env = (0, exports.mustGetEnv)(_env);
    let contractName = _contractName;
    console.log(contractName, _env, _networkName);
    if (contractName.includes("Multichain")) {
        const networkName = _networkName.charAt(0).toUpperCase() + _networkName.slice(1).toLowerCase();
        contractName = contractName.replace("Multichain", networkName);
    }
    if (env !== "staging" || NON_STAGING_CONTRACTS.includes(contractName)) {
        return contractName;
    }
    return `${contractName}Staging`;
};
exports.getDeploymentName = getDeploymentName;
const verify = async (hre, address, constructorArguments = [], libraries = {}) => {
    try {
        await hre.run("verify:verify", {
            address,
            constructorArguments,
            libraries,
        });
    }
    catch (e) {
        if (e.message.toLowerCase().includes("already verified")) {
            console.log(`${address} already verified`);
            return;
        }
        console.log(`Error verifying contract at ${address}:`, e);
    }
};
exports.verify = verify;
// Gets the messaging protocol config for a given chain
const getMessagingProtocolConfig = (protocolNetwork) => {
    // TODO: "tesnet"  => "mainnet"  for production
    const protocol = shared_1.MESSAGING_PROTOCOL_CONFIGS[protocolNetwork];
    if (!protocol || !protocol.configs[protocol.hub]) {
        throw new Error(`Network ${protocolNetwork} is not supported! (no messaging config)`);
    }
    return protocol;
};
exports.getMessagingProtocolConfig = getMessagingProtocolConfig;
const getConnectorDeployments = (env, protocolNetwork) => {
    const protocol = (0, exports.getMessagingProtocolConfig)(protocolNetwork);
    const connectors = [];
    Object.keys(protocol.configs).forEach((_chainId) => {
        const chainId = +_chainId;
        if (protocol.hub === chainId) {
            // On the hub, you only need to connect the mainnet l1 connector (no mirror)
            connectors.push({
                chain: protocol.hub,
                name: (0, exports.getDeploymentName)((0, exports.getConnectorName)(protocol, protocol.hub), env),
                mirrorName: undefined,
                mirrorChain: undefined,
            });
            return;
        }
        // When not on the hub, there will be a name for both the hub and spoke side connectors
        const hubName = (0, exports.getDeploymentName)((0, exports.getConnectorName)(protocol, chainId, protocol.hub), env);
        const spokeName = (0, exports.getDeploymentName)((0, exports.getConnectorName)(protocol, chainId), env);
        connectors.push({
            chain: protocol.hub,
            name: hubName,
            mirrorName: spokeName,
            mirrorChain: chainId,
        });
        connectors.push({
            chain: chainId,
            name: spokeName,
            mirrorName: hubName,
            mirrorChain: protocol.hub,
        });
    });
    const getAddressAndAbi = (name, chain) => {
        const [record] = deployments_json_1.default[chain.toString()] ?? [undefined];
        if (!record) {
            throw new Error(`Deployment records not found for ${chain}`);
        }
        const { address, abi } = record.contracts[name] ?? {};
        if (!address || !abi) {
            throw new Error(`Deployment values not found for ${name} on ${chain}`);
        }
        return { address, abi };
    };
    // get deployments for connectors
    const deployments = connectors.map(({ name, chain, mirrorName, mirrorChain }) => {
        // Get deployment records
        const { address, abi } = getAddressAndAbi(name, chain);
        const mirrorConnector = mirrorName && mirrorChain ? getAddressAndAbi(mirrorName, mirrorChain).address : undefined;
        return { address, abi, mirrorConnector, chain, mirrorChain, name };
    });
    return deployments;
};
exports.getConnectorDeployments = getConnectorDeployments;
const getProviderFromHardhatConfig = (chainId) => {
    // Get the provider address from the hardhat config on given chain
    const url = Object.values(config_1.hardhatNetworks).find((n) => n?.chainId === chainId)?.url;
    if (!url) {
        throw new Error(`No provider url found for ${chainId}`);
    }
    return new ethers_1.providers.JsonRpcProvider(url, chainId);
};
exports.getProviderFromHardhatConfig = getProviderFromHardhatConfig;
const executeOnAllConnectors = async (env, protocolNetwork, fn) => {
    const deployments = (0, exports.getConnectorDeployments)(env, protocolNetwork);
    const results = [];
    for (const deploy of deployments) {
        results.push(await fn(deploy, (0, exports.getProviderFromHardhatConfig)(deploy.chain)));
    }
    return results;
};
exports.executeOnAllConnectors = executeOnAllConnectors;
// Retrieves the status of an optimism message
const queryOptimismMessageStatus = async (hash, l1ChainId, l2ChainId, l1Provider, l2Provider, relay, signer) => {
    const crossChainMessenger = new sdk_1.CrossChainMessenger({
        l1ChainId,
        l2ChainId,
        l1SignerOrProvider: l1Provider,
        l2SignerOrProvider: l2Provider,
    });
    const status = await crossChainMessenger.getMessageStatus(hash);
    const [message] = await crossChainMessenger.getMessagesByTransaction(hash);
    console.log("message", { ...message, minGasLimit: message.minGasLimit.toString() });
    const mapping = {
        [sdk_1.MessageStatus.UNCONFIRMED_L1_TO_L2_MESSAGE]: "Unconfirmed L1 -> L2",
        [sdk_1.MessageStatus.FAILED_L1_TO_L2_MESSAGE]: "Failed L1 -> L2",
        [sdk_1.MessageStatus.STATE_ROOT_NOT_PUBLISHED]: "State root not published",
        [sdk_1.MessageStatus.IN_CHALLENGE_PERIOD]: "In challenge period",
        [sdk_1.MessageStatus.READY_FOR_RELAY]: "Ready for relay",
        [sdk_1.MessageStatus.RELAYED]: "Relayed",
    };
    if (relay && status === sdk_1.MessageStatus.READY_FOR_RELAY) {
        const tx = await crossChainMessenger.finalizeMessage(hash, { signer });
        console.log("relay message tx submitted:", tx.hash);
        const receipt = await tx.wait();
        console.log("relay message tx mined:", receipt.transactionHash);
    }
    return mapping[status];
};
exports.queryOptimismMessageStatus = queryOptimismMessageStatus;
const deployBeaconProxy = async (name, args, deployer, hre, implementationArgs = [], deployName) => {
    // get names
    deployName = deployName ?? name;
    const implementationName = (0, exports.getDeploymentName)(deployName);
    const upgradeBeaconName = (0, exports.getDeploymentName)(`${deployName}UpgradeBeacon`);
    const proxyName = (0, exports.getDeploymentName)(`${deployName}UpgradeBeaconProxy`);
    const upgradeBeaconControllerName = (0, exports.getDeploymentName)(`UpgradeBeaconController`);
    // get data + factories
    const factory = await hre.ethers.getContractFactory(name, deployer.address);
    const initData = factory.interface.encodeFunctionData("initialize", args);
    // Get controller deployment
    let controllerDeployment = await hre.deployments.getOrNull(upgradeBeaconControllerName);
    if (!controllerDeployment) {
        controllerDeployment = await hre.deployments.deploy(upgradeBeaconControllerName, {
            from: deployer.address,
            log: true,
            contract: "UpgradeBeaconController",
        });
    }
    // Check if already deployed
    let proxyDeployment = await hre.deployments.getOrNull(proxyName);
    let implementation;
    let beaconAddress;
    if (proxyDeployment) {
        console.log(`${implementationName} proxy deployed. upgrading...`);
        // Get beacon and implementation addresses
        beaconAddress = (await hre.deployments.getOrNull(upgradeBeaconName))?.address;
        implementation = (await hre.deployments.getOrNull(implementationName))?.address;
        if (!implementation || !beaconAddress) {
            throw new Error(`Could not find beacon or implementation address for ${name}`);
        }
        // Check if theres an upgrade needed by checking the deployed code
        const artifact = await hre.deployments.getArtifact(name);
        const deployment = await hre.deployments.getOrNull(implementationName);
        if (artifact.deployedBytecode !== deployment?.deployedBytecode) {
            // Must upgrade the proxy
            // First, deploy new implementation
            const upgradeDeployment = await hre.deployments.deploy(implementationName, {
                args: implementationArgs,
                from: deployer.address,
                skipIfAlreadyDeployed: false,
                log: true,
                contract: name,
            });
            implementation = upgradeDeployment.address;
            console.log(`upgrading proxy to implementation logic at: ${implementation}`);
            // Then, upgrade proxy via beacon controller
            const controller = new ethers_1.Contract(controllerDeployment.address, controllerDeployment.abi).connect(deployer);
            const upgrade = await controller.upgrade(beaconAddress, implementation, { gasLimit: ethers_1.BigNumber.from(1000000) });
            console.log(`${implementationName} upgrade transaction:`, upgrade.hash);
            const receipt = await upgrade.wait();
            console.log(`${implementationName} upgrade tx mined:`, receipt.transactionHash);
        }
        else {
            console.log(`no upgrade needed, using implementation at: ${implementation}`);
        }
    }
    else {
        console.log(`Deploying ${implementationName} with upgradeable scheme`);
        // 1. Deploy implementation
        const implementationDeployment = await hre.deployments.deploy(implementationName, {
            args: implementationArgs,
            from: deployer.address,
            skipIfAlreadyDeployed: true,
            log: true,
            contract: name,
        });
        implementation = implementationDeployment.address;
        console.log(`deployed implementation: ${implementation}`);
        // 2. Deploy UpgradeBeacon
        const beaconDeployment = await hre.deployments.deploy(upgradeBeaconName, {
            args: [implementation, controllerDeployment.address],
            from: deployer.address,
            skipIfAlreadyDeployed: true,
            log: true,
            contract: "UpgradeBeacon",
        });
        beaconAddress = beaconDeployment.address;
        // 3. Deploy UpgradeBeaconProxy
        proxyDeployment = await hre.deployments.deploy(proxyName, {
            args: [beaconAddress, initData],
            from: deployer.address,
            skipIfAlreadyDeployed: true,
            log: true,
            contract: "UpgradeBeaconProxy",
        });
    }
    const proxy = new ethers_1.Contract(proxyDeployment.address, (await hre.deployments.getOrNull(implementationName)).abi).connect(deployer);
    return proxy;
};
exports.deployBeaconProxy = deployBeaconProxy;
//# sourceMappingURL=utils.js.map