import { Fr } from '@aztec/circuits.js';
import { toBigIntBE, toHex } from '@aztec/foundation/bigint-buffer';
import { keccak, pedersenHash } from '@aztec/foundation/crypto';
import { createDebugLogger } from '@aztec/foundation/log';
import fs from 'fs';
/**
 * A class that provides utility functions for interacting with the chain.
 */
export class CheatCodes {
    constructor(
    /**
     * The cheat codes for ethereum (L1).
     */
    eth, 
    /**
     * The cheat codes for aztec.
     */
    aztec) {
        this.eth = eth;
        this.aztec = aztec;
    }
    static create(rpcUrl, pxe) {
        const ethCheatCodes = new EthCheatCodes(rpcUrl);
        const aztecCheatCodes = new AztecCheatCodes(pxe, ethCheatCodes);
        return new CheatCodes(ethCheatCodes, aztecCheatCodes);
    }
}
/**
 * A class that provides utility functions for interacting with ethereum (L1).
 */
export class EthCheatCodes {
    constructor(
    /**
     * The RPC URL to use for interacting with the chain
     */
    rpcUrl, 
    /**
     * The logger to use for the eth cheatcodes
     */
    logger = createDebugLogger('aztec:cheat_codes:eth')) {
        this.rpcUrl = rpcUrl;
        this.logger = logger;
    }
    async rpcCall(method, params) {
        const paramsString = JSON.stringify(params);
        const content = {
            body: `{"jsonrpc":"2.0", "method": "${method}", "params": ${paramsString}, "id": 1}`,
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
        };
        return await (await fetch(this.rpcUrl, content)).json();
    }
    /**
     * Get the current blocknumber
     * @returns The current block number
     */
    async blockNumber() {
        const res = await this.rpcCall('eth_blockNumber', []);
        return parseInt(res.result, 16);
    }
    /**
     * Get the current chainId
     * @returns The current chainId
     */
    async chainId() {
        const res = await this.rpcCall('eth_chainId', []);
        return parseInt(res.result, 16);
    }
    /**
     * Get the current timestamp
     * @returns The current timestamp
     */
    async timestamp() {
        const res = await this.rpcCall('eth_getBlockByNumber', ['latest', true]);
        return parseInt(res.result.timestamp, 16);
    }
    /**
     * Advance the chain by a number of blocks
     * @param numberOfBlocks - The number of blocks to mine
     * @returns The current chainId
     */
    async mine(numberOfBlocks = 1) {
        const res = await this.rpcCall('hardhat_mine', [numberOfBlocks]);
        if (res.error) {
            throw new Error(`Error mining: ${res.error.message}`);
        }
        this.logger(`Mined ${numberOfBlocks} blocks`);
    }
    /**
     * Set the next block timestamp
     * @param timestamp - The timestamp to set the next block to
     */
    async setNextBlockTimestamp(timestamp) {
        const res = await this.rpcCall('evm_setNextBlockTimestamp', [timestamp]);
        if (res.error) {
            throw new Error(`Error setting next block timestamp: ${res.error.message}`);
        }
        this.logger(`Set next block timestamp to ${timestamp}`);
    }
    /**
     * Dumps the current chain state to a file.
     * @param fileName - The file name to dump state into
     */
    async dumpChainState(fileName) {
        const res = await this.rpcCall('hardhat_dumpState', []);
        if (res.error) {
            throw new Error(`Error dumping state: ${res.error.message}`);
        }
        const jsonContent = JSON.stringify(res.result);
        fs.writeFileSync(`${fileName}.json`, jsonContent, 'utf8');
        this.logger(`Dumped state to ${fileName}`);
    }
    /**
     * Loads the chain state from a file.
     * @param fileName - The file name to load state from
     */
    async loadChainState(fileName) {
        const data = JSON.parse(fs.readFileSync(`${fileName}.json`, 'utf8'));
        const res = await this.rpcCall('hardhat_loadState', [data]);
        if (res.error) {
            throw new Error(`Error loading state: ${res.error.message}`);
        }
        this.logger(`Loaded state from ${fileName}`);
    }
    /**
     * Load the value at a storage slot of a contract address on eth
     * @param contract - The contract address
     * @param slot - The storage slot
     * @returns - The value at the storage slot
     */
    async load(contract, slot) {
        const res = await this.rpcCall('eth_getStorageAt', [contract.toString(), toHex(slot), 'latest']);
        return BigInt(res.result);
    }
    /**
     * Set the value at a storage slot of a contract address on eth
     * @param contract - The contract address
     * @param slot - The storage slot
     * @param value - The value to set the storage slot to
     */
    async store(contract, slot, value) {
        // for the rpc call, we need to change value to be a 32 byte hex string.
        const res = await this.rpcCall('hardhat_setStorageAt', [contract.toString(), toHex(slot), toHex(value, true)]);
        if (res.error) {
            throw new Error(`Error setting storage for contract ${contract} at ${slot}: ${res.error.message}`);
        }
        this.logger(`Set storage for contract ${contract} at ${slot} to ${value}`);
    }
    /**
     * Computes the slot value for a given map and key.
     * @param baseSlot - The base slot of the map (specified in Aztec.nr contract)
     * @param key - The key to lookup in the map
     * @returns The storage slot of the value in the map
     */
    keccak256(baseSlot, key) {
        // abi encode (removing the 0x) - concat key and baseSlot (both padded to 32 bytes)
        const abiEncoded = toHex(key, true).substring(2) + toHex(baseSlot, true).substring(2);
        return toBigIntBE(keccak(Buffer.from(abiEncoded, 'hex')));
    }
    /**
     * Send transactions impersonating an externally owned account or contract.
     * @param who - The address to impersonate
     */
    async startImpersonating(who) {
        const res = await this.rpcCall('hardhat_impersonateAccount', [who.toString()]);
        if (res.error) {
            throw new Error(`Error impersonating ${who}: ${res.error.message}`);
        }
        this.logger(`Impersonating ${who}`);
    }
    /**
     * Stop impersonating an account that you are currently impersonating.
     * @param who - The address to stop impersonating
     */
    async stopImpersonating(who) {
        const res = await this.rpcCall('hardhat_stopImpersonatingAccount', [who.toString()]);
        if (res.error) {
            throw new Error(`Error when stopping the impersonation of ${who}: ${res.error.message}`);
        }
        this.logger(`Stopped impersonating ${who}`);
    }
    /**
     * Set the bytecode for a contract
     * @param contract - The contract address
     * @param bytecode - The bytecode to set
     */
    async etch(contract, bytecode) {
        const res = await this.rpcCall('hardhat_setCode', [contract.toString(), bytecode]);
        if (res.error) {
            throw new Error(`Error setting bytecode for ${contract}: ${res.error.message}`);
        }
        this.logger(`Set bytecode for ${contract} to ${bytecode}`);
    }
    /**
     * Get the bytecode for a contract
     * @param contract - The contract address
     * @returns The bytecode for the contract
     */
    async getBytecode(contract) {
        const res = await this.rpcCall('eth_getCode', [contract.toString(), 'latest']);
        return res.result;
    }
}
/**
 * A class that provides utility functions for interacting with the aztec chain.
 */
export class AztecCheatCodes {
    constructor(
    /**
     * The PXE Service to use for interacting with the chain
     */
    pxe, 
    /**
     * The eth cheat codes.
     */
    eth, 
    /**
     * The logger to use for the aztec cheatcodes
     */
    logger = createDebugLogger('aztec:cheat_codes:aztec')) {
        this.pxe = pxe;
        this.eth = eth;
        this.logger = logger;
    }
    /**
     * Computes the slot value for a given map and key.
     * @param baseSlot - The base slot of the map (specified in Aztec.nr contract)
     * @param key - The key to lookup in the map
     * @returns The storage slot of the value in the map
     */
    computeSlotInMap(baseSlot, key) {
        // Based on `at` function in
        // aztec3-packages/yarn-project/aztec-nr/aztec/src/state_vars/map.nr
        return Fr.fromBuffer(pedersenHash([new Fr(baseSlot), new Fr(key)].map(f => f.toBuffer())));
    }
    /**
     * Get the current blocknumber
     * @returns The current block number
     */
    async blockNumber() {
        return await this.pxe.getBlockNumber();
    }
    /**
     * Set time of the next execution on aztec.
     * It also modifies time on eth for next execution and stores this time as the last rollup block on the rollup contract.
     * @param to - The timestamp to set the next block to (must be greater than current time)
     */
    async warp(to) {
        const rollupContract = (await this.pxe.getNodeInfo()).l1ContractAddresses.rollupAddress;
        await this.eth.setNextBlockTimestamp(to);
        // also store this time on the rollup contract (slot 1 tracks `lastBlockTs`).
        // This is because when the sequencer executes public functions, it uses the timestamp stored in the rollup contract.
        await this.eth.store(rollupContract, 1n, BigInt(to));
        // also store this on slot 2 of the rollup contract (`lastWarpedBlockTs`) which tracks the last time warp was used.
        await this.eth.store(rollupContract, 2n, BigInt(to));
    }
    /**
     * Loads the value stored at the given slot in the public storage of the given contract.
     * @param who - The address of the contract
     * @param slot - The storage slot to lookup
     * @returns The value stored at the given slot
     */
    async loadPublic(who, slot) {
        const storageValue = await this.pxe.getPublicStorageAt(who, new Fr(slot));
        if (storageValue === undefined) {
            throw new Error(`Storage slot ${slot} not found`);
        }
        return storageValue;
    }
    /**
     * Loads the value stored at the given slot in the private storage of the given contract.
     * @param contract - The address of the contract
     * @param owner - The owner for whom the notes are encrypted
     * @param slot - The storage slot to lookup
     * @returns The notes stored at the given slot
     */
    async loadPrivate(owner, contract, slot) {
        const extendedNotes = await this.pxe.getNotes({ owner, contractAddress: contract, storageSlot: new Fr(slot) });
        return extendedNotes.map(extendedNote => extendedNote.note);
    }
}
//# sourceMappingURL=data:application/json;base64, 