import { expect } from "chai";
import { ethers, deployments, waffle } from "hardhat";
import "@nomiclabs/hardhat-ethers";
import { AddressZero } from "@ethersproject/constants";
import { getSafeWithOwners, getSafeSingleton, migrationContract } from "../utils/setup";
import deploymentData from "../json/safeDeployment.json";
import { executeContractCallWithSigners } from "../../src/utils/execution";

describe("Migration", async () => {

    const MigratedInterface = new ethers.utils.Interface([
        "function domainSeparator() view returns(bytes32)",
        "function masterCopy() view returns(address)",
    ])

    const [user1, user2] = waffle.provider.getWallets();

    const setupTests = deployments.createFixture(async ({ deployments }) => {
        await deployments.fixture();
        const singleton120 = (await (await user1.sendTransaction({ data: deploymentData.safe120 })).wait()).contractAddress
        const migration = await (await migrationContract()).deploy(singleton120)
        return {
            singleton: await getSafeSingleton(),
            singleton120,
            safe: await getSafeWithOwners([user1.address]),
            migration
        }
    })
    describe("constructor", async () => {
        it('can not use 0 Address', async () => {
            await setupTests()
            const tx = (await migrationContract()).getDeployTransaction(AddressZero)
            await expect(
                user1.sendTransaction(tx)
            ).to.be.revertedWith("Invalid singleton address provided")
        })
    })

    describe("migrate", async () => {
        it('can only be called from Safe itself', async () => {
            const { migration } = await setupTests()
            await expect(migration.migrate()).to.be.revertedWith("Migration should only be called via delegatecall")
        })

        it('can migrate', async () => {
            const { safe, migration, singleton120 } = await setupTests()
            // The emit matcher checks the address, which is the Safe as delegatecall is used
            const migrationSafe = migration.attach(safe.address)
            
            await expect(
                await ethers.provider.getStorageAt(safe.address, "0x06")
            ).to.be.eq("0x" + "".padEnd(64, "0"))

            await expect(
                executeContractCallWithSigners(safe, migration, "migrate", [], [user1], true)
            ).to.emit(migrationSafe, "ChangedMasterCopy").withArgs(singleton120)

            const expectedDomainSeparator = ethers.utils._TypedDataEncoder.hashDomain({ verifyingContract: safe.address })

            await expect(
                await ethers.provider.getStorageAt(safe.address, "0x06")
            ).to.be.eq(expectedDomainSeparator)

            const respData = await user1.call({to: safe.address, data: MigratedInterface.encodeFunctionData("domainSeparator")})
            await expect(MigratedInterface.decodeFunctionResult("domainSeparator", respData)[0]).to.be.eq(expectedDomainSeparator)

            const masterCopyResp = await user1.call({to: safe.address, data: MigratedInterface.encodeFunctionData("masterCopy")})
            await expect(MigratedInterface.decodeFunctionResult("masterCopy", masterCopyResp)[0]).to.be.eq(singleton120)
        })
    })
})