"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.transferOwnership = exports.optionDefinitions = void 0;
const command_line_args_1 = __importDefault(require("command-line-args"));
const ethers_1 = require("ethers");
const utils_1 = require("ethers/lib/utils");
const dotenv_1 = require("dotenv");
const domain_1 = require("../../domain");
const utils_2 = require("../../utils");
const config_1 = require("../../config");
const helpers_1 = require("./helpers");
(0, dotenv_1.config)();
exports.optionDefinitions = [
    { name: "env", type: String },
    { name: "network", type: String },
    { name: "desired", type: String },
    { name: "domains", type: String, multiple: true },
    { name: "apply", type: Boolean, defaultValue: false },
];
const transferOwnership = async () => {
    let cmdArgs;
    try {
        cmdArgs = (0, command_line_args_1.default)(exports.optionDefinitions);
    }
    catch (err) {
        throw new Error(`Parsing arguments failed, cmdArgs: ${process.argv}`);
    }
    // get desired wallet if possible
    let desiredWallet;
    if (process.env.DESIRED_WALLET) {
        desiredWallet = ethers_1.Wallet.fromMnemonic(process.env.DESIRED_WALLET);
    }
    // get deployer
    const privateKey = process.env.PRIVATE_KEY;
    const mnemonic = process.env.DEPLOYER || process.env.DEPLOYER_MNEMONIC || process.env.MNEMONIC;
    if (!mnemonic && !privateKey) {
        throw new Error("Deployer mnemonic was not specified. Please specify `deployer` in the config file, " +
            "or set DEPLOYER or DEPLOYER_MNEMONIC in env.");
    }
    // Convert deployer from mnemonic to Wallet.
    let wallet;
    if (privateKey) {
        wallet = new ethers_1.Wallet(privateKey);
    }
    else {
        wallet = ethers_1.Wallet.fromMnemonic(mnemonic);
    }
    console.log("wallet: ", wallet.address);
    // get default config values
    const { env: _env, domains: _domains, network: _network, desired, apply } = cmdArgs;
    const env = _env ?? "staging";
    const network = _network ?? "testnet";
    const domains = _domains ?? helpers_1.SUPPORTED_DOMAINS[network];
    // config validation
    if (!["staging", "production"].includes(env)) {
        throw new Error(`Environment should be either staging or production, env: ${env}`);
    }
    if (!["testnet", "mainnet"].includes(network)) {
        throw new Error(`Network should be either testnet or mainnet, network: ${network}`);
    }
    if (!desired || desired === ethers_1.constants.AddressZero || !(0, utils_1.isAddress)(desired)) {
        throw new Error(`Desired owner must be a valid address, desired: ${desired}`);
    }
    // pull all ownable deployments
    const allDeployments = {};
    domains.forEach((domain) => {
        const chain = (0, domain_1.domainToChainId)(+domain);
        const config = Object.values(config_1.hardhatNetworks).find((h) => h.chainId === chain);
        if (!config?.url) {
            throw new Error(`No hardhat network config provider found for chain ${chain}`);
        }
        const deployments = (0, helpers_1.getOwnableDeployments)(chain, wallet.connect((0, utils_2.getProviderFromHardhatConfig)(chain)), helpers_1.HUBS[network] === +domain, env);
        allDeployments[chain] = deployments;
    });
    // for each deployment:
    // - if current owner is correct, ignore
    // - if proposed owner is correct, check deadline.
    //   - if deadline is passed, execute
    //   - if deadline is not passed, ignore
    // - if proposed owner is incorrect, propose new owner
    // only send txs if apply is true, otherwise just log
    // define helper for ^^
    const handleOwnership = async (name, contract, provider) => {
        // Check the owner
        const owner = await contract.owner();
        if (owner.toLowerCase() === desired.toLowerCase()) {
            // already done, continue
            return;
        }
        const proposed = await contract.proposed();
        // handle case where the desired owner is already proposed
        if (proposed.toLowerCase() === desired.toLowerCase()) {
            // check deadline
            const timestamp = await contract.proposedTimestamp();
            const delay = await contract.delay();
            const sec = Math.floor(Date.now() / 1000) + 5;
            if (timestamp.add(delay).gt(sec)) {
                // deadline not passed, do nothing
                console.log(`desired owner for ${name} already proposed, deadline not elapsed. Waiting ${timestamp
                    .add(delay)
                    .sub(sec)
                    .toNumber()}s`);
                return;
            }
            // Deadline is passed, execute
            let sender = wallet.connect(provider);
            if (desiredWallet && desiredWallet.address.toLowerCase() === proposed.toLowerCase()) {
                sender = desiredWallet.connect(provider);
            }
            // Sanity check: wallet is proposed
            if (sender.address.toLowerCase() !== proposed.toLowerCase()) {
                throw new Error(`Wallet is not proposed owner, sender: ${sender.address}, proposed: ${proposed}. Trying to accept owner for ${name} (${contract.address}).`);
            }
            console.log(`accepting new owner for ${name} (${contract.address}):`);
            console.log(`- current: ${owner}`);
            console.log(`- accepting: ${desired}`);
            if (apply) {
                const tx = await contract.connect(sender).acceptProposedOwner();
                console.log(`tx: ${tx.hash}`);
                await tx.wait();
                console.log(`tx mined`);
            }
            return;
        }
        // need to propose new owner
        // Sanity check: wallet is current owner
        if (wallet.address.toLowerCase() !== owner.toLowerCase()) {
            throw new Error(`Wallet is not current owner, wallet: ${wallet.address}, owner: ${owner}`);
        }
        // propose new owner
        console.log(`proposing new owner for ${name} (${contract.address}):`);
        console.log(`- current: ${owner}`);
        console.log(`- proposing: ${desired}`);
        if (apply) {
            const tx = await contract.proposeNewOwner(desired);
            console.log(`tx: ${tx.hash}`);
            await tx.wait();
            console.log(`tx mined`);
        }
    };
    // for each deployment, update the owner
    for (const chain of Object.keys(allDeployments)) {
        const provider = (0, utils_2.getProviderFromHardhatConfig)(+chain);
        console.log(`\n========== Handling execution layer ownership on ${chain} ==========`);
        for (const deployment of Object.values(allDeployments[+chain].execution)) {
            await handleOwnership(deployment.name, deployment.contract, provider);
        }
        console.log(`\n========== Handling messaging layer ownership on ${chain} ==========`);
        for (const [key, deployments] of Object.entries(allDeployments[+chain].messaging)) {
            if (key === "HubConnectors") {
                // handle separately
                continue;
            }
            await handleOwnership(deployments.name, deployments.contract, provider);
        }
        // handle hub connectors if on hub
        if (helpers_1.HUBS[network] === (0, domain_1.chainIdToDomain)(+chain)) {
            for (const deployments of Object.values(allDeployments[+chain].messaging.HubConnectors)) {
                await handleOwnership(deployments.name, deployments.contract, provider);
            }
        }
    }
};
exports.transferOwnership = transferOwnership;
//# sourceMappingURL=transferOwnership.js.map