diff --git a/script/deploy-parentco-factory.s.sol b/script/deploy-parentco-factory.s.sol index 3699526e..654627d0 100644 --- a/script/deploy-parentco-factory.s.sol +++ b/script/deploy-parentco-factory.s.sol @@ -1,48 +1,145 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.28; -import {Script} from "forge-std/Script.sol"; -import {console2} from "forge-std/console2.sol"; -import {ParentCoFactory} from "../src/ParentCoFactory.sol"; +import "./libs/SafeUtils.sol"; import {BorgAuth} from "../src/libs/auth.sol"; -import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; -import {DeploymentConstants} from "./libs/DeploymentConstants.sol"; +import {CompanyOfficer} from "../src/CyberCorpConstants.sol"; import {CyberAgreementRegistry} from "../src/CyberAgreementRegistry.sol"; import {CyberAgreementUtils} from "../test/libs/CyberAgreementUtils.sol"; -import {CompanyOfficer} from "../src/CyberCorpConstants.sol"; +import {DeploymentConstants} from "./libs/DeploymentConstants.sol"; +import {ERC1967Proxy} from "openzeppelin-contracts/proxy/ERC1967/ERC1967Proxy.sol"; +import {GnosisTransaction} from "./libs/safe.sol"; +import {ParentCoFactory} from "../src/ParentCoFactory.sol"; +import {Script} from "forge-std/Script.sol"; +import {console2} from "forge-std/console2.sol"; contract DeployParentCoFactoryScript is Script { uint256 internal constant BASE_SEPOLIA_CHAIN_ID = 84532; - address internal constant BASE_USDC = - 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913; - - function run() public returns (ParentCoFactory parentCoFactory) { - return - runWithArgs( - vm.envUint("PRIVATE_KEY_MAIN"), - 0x42069BaBe92462393FaFdc653A88F958B64EC9A3, // test corp payable - 0x42069BaBe92462393FaFdc653A88F958B64EC9A3 // test officer EOA - ); + uint256 internal constant BASE_CHAIN_ID = 8453; + + function run() public returns (ParentCoFactory parentCoFactory, GnosisTransaction[] memory safeTxs) { + + // production + + address[] memory eoas = vm.envAddress("PARENT_CO_OFFICER_EOAS", ","); + string[] memory names = vm.envString("PARENT_CO_OFFICER_NAMES", ","); + string[] memory contacts = vm.envString("PARENT_CO_OFFICER_CONTACTS", ","); + string[] memory titles = vm.envString("PARENT_CO_OFFICER_TITLES", ","); + CompanyOfficer[] memory parentCoOfficers = new CompanyOfficer[](eoas.length); + for (uint256 i = 0; i < eoas.length ; i++) { + parentCoOfficers[i] = CompanyOfficer({ + eoa: eoas[i], + name: names[i], + contact: contacts[i], + title: titles[i] + }); + } + + return runWithArgs({ + chainId: DeploymentConstants.BASE, + deployerPrivateKey: vm.envUint("PRIVATE_KEY_MAIN"), + saltStr: "ParentCoFactory.deploy.v1", + segCoTemplateId: keccak256("ParentCo.SegCo.v1"), + segCoDocName: "UMIA FOUNDER/OPERATOR LEGAL PACK", + segCoDocUri: "ipfs://bafybeicqgnzaa4zm7nlkrnuuf7xdyhughisnougerjliewynjtdaioiv5e", + boardConsentTemplateId: keccak256("ParentCo.BoardConsent.v1"), + boardConsentName: "ACTION BY UNANIMOUS WRITTEN CONSENT OF THE BOARD OF DIRECTORS OF UMIA LAUNCHER SPC", + boardConsentUri: "ipfs://bafkreicr65qbif4vprnt5l2ovzqjbywwgmnply3sz7neb6qzjbb2njcrw4", + parentCoPayable: 0x299FF70C049c0a6d591319fA7BaD86e24a671436, + parentCoName: "UMIA LAUNCHER, SPC", + parentCoType: "Segregated Portfolio Company", + parentCoJurisdiction: "Cayman Islands", + parentCoContactDetails: "c/o TTA Corporate Services Limited, Harbour Place, 2nd Floor, North Wing, 103 South Church Street, P.O. Box 472, George Town, Grand Cayman KY1-1106, Cayman Islands", + parentCoDefaultDisputeResolution: "confidential, binding arbitration", + parentCoOfficers: parentCoOfficers + }); + +// // testnet +// +// CompanyOfficer[] memory parentCoOfficers = new CompanyOfficer[](2); +// parentCoOfficers[0] = CompanyOfficer({ +// eoa: 0x8E9603BcB5D974Ed9C870510F3665F67CE5c5bDe, +// name: "Test Officer 1", +// contact: "test1@parentco.example", +// title: "Director" +// }); +// parentCoOfficers[1] = CompanyOfficer({ +// eoa: 0xE8ABA57F64Db674E35fADBb497c3bea4bD69F787, +// name: "Test Officer 2", +// contact: "test2@parentco.example", +// title: "Director" +// }); +// +// return runWithArgs({ +// chainId: DeploymentConstants.ETH_SEPOLIA, +// deployerPrivateKey: vm.envUint("PRIVATE_KEY_MAIN"), +// saltStr: "ParentCoFactory.deploy.v2.dev1", +// segCoTemplateId: keccak256("ParentCo.Test2.SegCo.v1"), +// segCoDocName: "FOUNDER/OPERATOR LEGAL PACK", +// segCoDocUri: "ipfs://parentco-test-segco-template", +// boardConsentTemplateId: keccak256("ParentCo.Test2.BoardConsent.v1"), +// boardConsentName: "ParentCo Test Board Consent", +// boardConsentUri: "ipfs://parentco-test-board-consent-template", +// parentCoPayable: 0x8E9603BcB5D974Ed9C870510F3665F67CE5c5bDe, +// parentCoName: "Test ParentCo LLC dev1", +// parentCoType: "limited liability company", +// parentCoJurisdiction: "Delaware", +// parentCoContactDetails: "test@parentco.example", +// parentCoDefaultDisputeResolution: "binding arbitration", +// parentCoOfficers: parentCoOfficers +// }); } function runWithArgs( + uint256 chainId, uint256 deployerPrivateKey, - address corpPayable, - address officerAddress - ) public returns (ParentCoFactory parentCoFactory) { - string memory parentCoOfficerName = "Test ParentCo Officer"; - string memory parentCoOfficerContact = "test@parentco.example"; - string memory parentCoOfficerTitle = "Director"; + string memory saltStr, + bytes32 segCoTemplateId, + string memory segCoDocName, + string memory segCoDocUri, + bytes32 boardConsentTemplateId, + string memory boardConsentName, + string memory boardConsentUri, + address parentCoPayable, + string memory parentCoName, + string memory parentCoType, + string memory parentCoJurisdiction, + string memory parentCoContactDetails, + string memory parentCoDefaultDisputeResolution, + CompanyOfficer[] memory parentCoOfficers + ) public returns (ParentCoFactory parentCoFactory, GnosisTransaction[] memory safeTxs) { + DeploymentConstants.Deps memory deps = DeploymentConstants.deps(chainId); + DeploymentConstants.CoreDeployment memory deployment = DeploymentConstants.coreV2(chainId); - DeploymentConstants.CoreDeployment memory deployment = DeploymentConstants - .coreV2(BASE_SEPOLIA_CHAIN_ID); + address paymentToken = deps.usdc; + + CyberAgreementRegistry registry = CyberAgreementRegistry( + deployment.cyberAgreementRegistry + ); address deployerAddress = vm.addr(deployerPrivateKey); - bytes32 salt = bytes32(keccak256("ParentCoFactory.deploy.v2")); + bytes32 salt = bytes32(keccak256(bytes(saltStr))); + + console2.log("==== Configs ===="); + console2.log("deployer: %s", deployerAddress); + console2.log("saltStr: %s", saltStr); + for (uint256 i = 0; i < parentCoOfficers.length; i++) { + console2.log("parentCoOfficers %d:", i); + console2.log(" EOA: %s", parentCoOfficers[i].eoa); + console2.log(" name: %s", parentCoOfficers[i].name); + console2.log(" contact: %s", parentCoOfficers[i].contact); + console2.log(" title: %s", parentCoOfficers[i].title); + console2.log(""); + } + console2.log("UMIA FOUNDER/OPERATOR LEGAL PACK template ID:"); + console2.logBytes32(segCoTemplateId); + console2.log("ACTION BY UNANIMOUS WRITTEN CONSENT OF THE BOARD OF DIRECTORS OF UMIA LAUNCHER SPC template ID:"); + console2.logBytes32(boardConsentTemplateId); + console2.log(""); vm.startBroadcast(deployerPrivateKey); - BorgAuth auth = new BorgAuth{salt: salt}(deployerAddress); + BorgAuth parentCoFactoryAuth = new BorgAuth{salt: salt}(deployerAddress); parentCoFactory = ParentCoFactory( address( @@ -50,23 +147,22 @@ contract DeployParentCoFactoryScript is Script { address(new ParentCoFactory{salt: salt}()), abi.encodeWithSelector( ParentCoFactory.initialize.selector, - address(auth), + address(parentCoFactoryAuth), deployment.cyberAgreementRegistry, deployment.issuanceManagerFactory, deployment.cyberCorpSingleFactory, deployment.dealManagerFactory, deployment.roundManagerFactory, deployment.uriBuilder, - BASE_USDC + paymentToken ) ) ) ); - parentCoFactory.setParentCoOfficerEOA(officerAddress); - parentCoFactory.setParentCoOfficerName(parentCoOfficerName); - parentCoFactory.setParentCoOfficerContact(parentCoOfficerContact); - parentCoFactory.setParentCoOfficerTitle(parentCoOfficerTitle); + // Configure ParentCo officer and escrowed signature BEFORE revoking deployer ownership + + parentCoFactory.setParentCoOfficers(parentCoOfficers); ( address parentCorp, @@ -75,28 +171,42 @@ contract DeployParentCoFactoryScript is Script { address parentDealMgr, address parentRoundMgr ) = parentCoFactory.createParentCorp( - bytes32(keccak256("Test2 ParentCo LLC")), - "Test ParentCo LLC", - "limited liability company", - "Delaware", - "test@parentco.example", - "binding arbitration", - corpPayable + bytes32(keccak256(bytes(parentCoName))), // use parent corp name as the salt str + parentCoName, + parentCoType, + parentCoJurisdiction, + parentCoContactDetails, + parentCoDefaultDisputeResolution, + parentCoPayable ); - // Escrow signature bytes for parent signing path (placeholder/test value). - bytes memory parentEscrowSig = hex"73f62ac9b08c813401a02a16a920a106e525ac65dff992dccfd2cb42e5423db6725bb1b4d6e0244a635665f4965514512253613e3b032491f7ec85c2f657154e1a"; - parentCoFactory.setParentCoSignatureHash(parentEscrowSig); + for (uint256 i = 0; i < parentCoOfficers.length; i++) + parentCoFactoryAuth.updateRole(parentCoOfficers[i].eoa, parentCoFactoryAuth.OWNER_ROLE()); + parentCoFactoryAuth.updateRole(parentCoPayable, parentCoFactoryAuth.OWNER_ROLE()); - CyberAgreementRegistry registry = CyberAgreementRegistry( + // Deployer to self-revoke ownership + parentCoFactoryAuth.zeroOwner(); + + console2.log("==== Deployed ===="); + console2.log("ParentCoFactory Auth:", address(parentCoFactoryAuth)); + console2.log( + "CyberAgreementRegistry:", deployment.cyberAgreementRegistry ); + console2.log("IssuanceManagerFactory:", deployment.issuanceManagerFactory); + console2.log("CyberCorpSingleFactory:", deployment.cyberCorpSingleFactory); + console2.log("DealManagerFactory:", deployment.dealManagerFactory); + console2.log("RoundManagerFactory:", deployment.roundManagerFactory); + console2.log("CertificateUriBuilder:", deployment.uriBuilder); + console2.log("ParentCoFactory (proxy):", address(parentCoFactory)); + console2.log("ParentCorp:", parentCorp); + console2.log("ParentAuth:", parentAuth); + console2.log("ParentIssuance:", parentIssuance); + console2.log("ParentDealMgr:", parentDealMgr); + console2.log("ParentRoundMgr:", parentRoundMgr); + console2.log(""); // Create (or no-op if already present) templates used by deployCorpContractFor. - bytes32 segCoTemplateId = keccak256("ParentCo.Test2.SegCo.v1"); - bytes32 boardConsentTemplateId = keccak256( - "ParentCo.Test2.BoardConsent.v1" - ); string[] memory globalFields = new string[](8); globalFields[0] = "founderName"; @@ -112,137 +222,39 @@ contract DeployParentCoFactoryScript is Script { partyFields[0] = "name"; partyFields[1] = "contactDetails"; - try - registry.createTemplate( + safeTxs = new GnosisTransaction[](2); + // create Founder Sig Pack template + safeTxs[0] = GnosisTransaction({ + to: address(registry), + value: 0, + data: abi.encodeWithSelector( + registry.createTemplate.selector, segCoTemplateId, - "ParentCo Test SegCo Agreement", - "ipfs://parentco-test-segco-template", + segCoDocName, + segCoDocUri, globalFields, partyFields ) - {} catch {} - - try - registry.createTemplate( + }); + safeTxs[1] = GnosisTransaction({ + to: address(registry), + value: 0, + data: abi.encodeWithSelector( + registry.createTemplate.selector, boardConsentTemplateId, - "ParentCo Test Board Consent", - "ipfs://parentco-test-board-consent-template", + boardConsentName, + boardConsentUri, globalFields, partyFields ) - {} catch {} - - // Build SubCorp inputs matching ParentCoFactory's strict field checks. - uint256 subCorpSalt = uint256(keccak256("ParentCo.Test2.SubCorp.v1")); - string memory subCompanyName = "Test SubCo SPV 1"; - string memory subCompanyType = "series limited liability company"; - string memory subCompanyJurisdiction = "Delaware"; - string memory subCompanyContact = "subco@parentco.example"; - string memory subDisputeResolution = "binding arbitration"; - - string[] memory globalValues = new string[](8); - globalValues[0] = "Test Founder"; - globalValues[1] = "Test ParentCo Enterprise"; - globalValues[2] = subCompanyName; - globalValues[3] = subCompanyType; - globalValues[4] = subCompanyJurisdiction; - globalValues[5] = subCompanyContact; - globalValues[6] = "TSC1"; - globalValues[7] = "Test SubCo One"; - - string[] memory partyValues = new string[](2); - partyValues[0] = "Test Deployer Officer"; - partyValues[1] = "deployer@parentco.example"; - - CompanyOfficer memory subOfficer = CompanyOfficer({ - eoa: deployerAddress, - name: partyValues[0], - contact: partyValues[1], - title: "Founder" }); - // Pre-compute agreement id and signer signature expected by signContractFor. - address[] memory agreementParties = new address[](2); - agreementParties[0] = officerAddress; - agreementParties[1] = deployerAddress; - bytes32 agreementId = keccak256( - abi.encode(segCoTemplateId, subCorpSalt, globalValues, agreementParties) - ); - - ( - string memory legalContractUri, - , - string[] memory templateGlobalFields, - string[] memory templatePartyFields - ) = registry.getTemplateDetails(segCoTemplateId); - - bytes memory deployerSignature = CyberAgreementUtils.signAgreementTypedData( - vm, - registry.DOMAIN_SEPARATOR(), - registry.SIGNATUREDATA_TYPEHASH(), - agreementId, - legalContractUri, - templateGlobalFields, - templatePartyFields, - globalValues, - partyValues, - deployerPrivateKey - ); - - ( - address subCorp, - address subAuth, - address subIssuance, - address subDealMgr, - address subRoundMgr, - address[] memory subCertPrinters, - bytes32 subAgreementId, - uint256[] memory subCertIds - ) = parentCoFactory.deployCorpContractFor( - subCorpSalt, - subCompanyName, - subCompanyType, - subCompanyJurisdiction, - subCompanyContact, - subDisputeResolution, - corpPayable, - subOfficer, - segCoTemplateId, - boardConsentTemplateId, - globalValues, - partyValues, - deployerSignature, - deployerAddress - ); - - auth.updateRole(officerAddress, auth.OWNER_ROLE()); - auth.updateRole(corpPayable, auth.OWNER_ROLE()); + string memory safeTxJson = SafeUtils.formatSafeTxJson(safeTxs, chainId); - console2.log("Auth:", address(auth)); - console2.log( - "CyberAgreementRegistry:", - deployment.cyberAgreementRegistry - ); - console2.log("IssuanceManagerFactory:", deployment.issuanceManagerFactory); - console2.log("CyberCorpSingleFactory:", deployment.cyberCorpSingleFactory); - console2.log("DealManagerFactory:", deployment.dealManagerFactory); - console2.log("RoundManagerFactory:", deployment.roundManagerFactory); - console2.log("CertificateUriBuilder:", deployment.uriBuilder); - console2.log("ParentCoFactory (proxy):", address(parentCoFactory)); - console2.log("ParentCorp:", parentCorp); - console2.log("ParentAuth:", parentAuth); - console2.log("ParentIssuance:", parentIssuance); - console2.log("ParentDealMgr:", parentDealMgr); - console2.log("ParentRoundMgr:", parentRoundMgr); - console2.log("SubCorp:", subCorp); - console2.log("SubAuth:", subAuth); - console2.log("SubIssuance:", subIssuance); - console2.log("SubDealMgr:", subDealMgr); - console2.log("SubRoundMgr:", subRoundMgr); - console2.log("SubAgreementId:"); - console2.logBytes32(subAgreementId); - console2.log("SubCertPrinters count:", subCertPrinters.length); - console2.log("SubCertIds count:", subCertIds.length); + console2.log("Safe tx JSON (can be imported to Safe Transaction Builder):"); + console2.log("==== JSON data start ===="); + console2.log(safeTxJson); + console2.log("==== JSON data end ===="); vm.stopBroadcast(); } diff --git a/script/deploy-umia.factory.sol b/script/deploy-umia.factory.sol deleted file mode 100644 index 2b243285..00000000 --- a/script/deploy-umia.factory.sol +++ /dev/null @@ -1,208 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity ^0.8.28; - -import {Script} from "forge-std/Script.sol"; -import {console} from "forge-std/console.sol"; - -import {CyberAgreementUtils} from "../test/libs/CyberAgreementUtils.sol"; -import {MetaDAOFactory} from "../src/MetaDAOFactory.sol"; -import {CyberAgreementRegistry} from "../src/CyberAgreementRegistry.sol"; -import {IssuanceManagerFactory} from "../src/IssuanceManagerFactory.sol"; -import {IssuanceManager} from "../src/IssuanceManager.sol"; -import {CyberCorpSingleFactory} from "../src/CyberCorpSingleFactory.sol"; -import {CyberCorp} from "../src/CyberCorp.sol"; -import {DealManagerFactory, DealManager} from "../src/DealManagerFactory.sol"; -import {RoundManagerFactory, RoundManager} from "../src/RoundManagerFactory.sol"; -import {CertificateUriBuilder} from "../src/CertificateUriBuilder.sol"; -import {CyberCertPrinter} from "../src/CyberCertPrinter.sol"; -import {CyberScrip} from "../src/CyberScrip.sol"; -import {BorgAuth} from "../src/libs/auth.sol"; -import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; -import {CompanyOfficer} from "../src/CyberCorpConstants.sol"; - -contract DeployScript is Script { - // Hard-coded since we don't have programmatic access to CyberAgreementRegistry's underlying types - string constant DOMAIN_SEPARATOR_TYPE = "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"; - string constant ESCROW_SIGNATUREDATA_TYPE = "EscrowSignatureData(string legalContractUri,string[] partyFields,string[] partyValues)"; - - struct DomainSeparator { - string name; - string version; - uint256 chainId; - address verifyingContract; - } - - struct EscrowSignatureData { - string legalContractUri; - string[] partyFields; - string[] partyValues; - } - - function run() public returns ( - CyberAgreementRegistry registry, - MetaDAOFactory metaDAOFactory - ) { - return runWithArgs( - vm.envUint("PRIVATE_KEY_MAIN"), // deployerPrivateKey - 0x59026c9A3871505c8E5fb0B021e274a0B28547F6, // corpPayable - 0x76A6168B69f8f1b27E06dC77a30F2D1C92733e7A, // officerAddress - hex"73f62ac9b08c813401a02a16a920a106e525ac65dff992dccfd2cb42e5423db6725bb1b4d6e0244a635665f4965514512253613e3b032491f7ec85c2f657154e1a" // metadaoEscrowSig - ); - } - - function runWithArgs( - uint256 deployerPrivateKey, - address corpPayable, - address officerAddress, - bytes memory metadaoEscrowSig - ) public returns (CyberAgreementRegistry registry, MetaDAOFactory metaDAOFactory) { - // Other configs - string memory metaDAOOfficerName = "Test Umia Officer"; - string memory metaDAOOfficerContact = "Test Contact"; - string memory metaDAOOfficerTitle = "Director & Management Shareholder"; - - address deployerAddress = vm.addr(deployerPrivateKey); - vm.startBroadcast(deployerPrivateKey); - - bytes32 salt = bytes32(keccak256("UmiaFactory.deploy.v1")); - - address stable = 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913; // USDC @ Base - - BorgAuth auth = new BorgAuth{salt: salt}(deployerAddress); - - address registry = 0xa9E808B8eCBB60Bb19abF026B5b863215BC4c134; - // Create templates - - string[] memory globalFields = new string[](8); - globalFields[0] = "founderName"; - globalFields[1] = "enterpriseName"; - globalFields[2] = "companyName"; - globalFields[3] = "companyType"; - globalFields[4] = "companyJurisdiction"; - globalFields[5] = "companyContactDetails"; - globalFields[6] = "tokenSymbol"; - globalFields[7] = "tokenName"; - string[] memory partyFields = new string[](2); - partyFields[0] = "name"; - partyFields[1] = "contactDetails"; - - // Create template for SegCo - string memory segCoAgreementTitle = "Test Umia Futarchy Governance SPC - SegCo combined v 1.0"; - string memory segCoAgreementUri = "ipfs://bafybeifpvfwxfmobk7nhflsczqiynp3ca5urvyk3duh7s3rwptcnfzhuje"; - - // Create template for Board Consent - string memory boardConsentTitle = "Test Futarchy Governance SPC - Board Consent - Approval of SegCo v 1.0"; - string memory boardConsentUri = "ipfs://bafkreic7dscoigvwjc23vzvkmzophm34kpafu6nrctykq5bif63lqvpuoa"; - - - address uriBuilder = 0x5500c095ea7dE6F8a5E15949e24B80604cc670A3; - - address issuanceManagerFactory = 0xA32547aAdAA4975082D729c79e79dBaE4385EBCf; - - address cyberCorpSingleFactory = 0xc8e084D3f8B3b326FCc894C7afD28F4904196406; - - address dealManagerFactory = 0x975df8A99C895d04ae158F8C91Ba562Fce3ECDA3; - - // upgrade CyberAgreementRegistry - address newAgreementRegistryImplementation = address(new CyberAgreementRegistry{salt: salt}()); - CyberAgreementRegistry(registry).upgradeToAndCall(newAgreementRegistryImplementation, ""); - - MetaDAOFactory metaDAOFactory = MetaDAOFactory( - address( - new ERC1967Proxy{salt: salt}( - address(new MetaDAOFactory{salt: salt}()), - abi.encodeWithSelector( - MetaDAOFactory.initialize.selector, - address(auth), - address(registry), - address(issuanceManagerFactory), - address(cyberCorpSingleFactory), - address(dealManagerFactory), - address(0), - address(uriBuilder), - address(stable) - ) - ) - ) - ); - - // Configure MetaDAO officer and escrowed signature BEFORE revoking deployer ownership - metaDAOFactory.setMetaDAOOfficerEOA(officerAddress); - metaDAOFactory.setMetaDAOOfficerName(metaDAOOfficerName); - metaDAOFactory.setMetaDAOOfficerContact(metaDAOOfficerContact); - metaDAOFactory.setMetaDAOOfficerTitle(metaDAOOfficerTitle); - - // Create the parent corp (one-time). Reverts if called again. - (address parentCorp, - address parentAuth, - address parentIssuance, - address parentDealMgr, - address parentRoundMgr) = metaDAOFactory.createParentCorp( - bytes32(keccak256("Futarchy Governance SPC")), - "Futarchy Governance SPC", - "segregated portfolio company", - "Cayman Islands", - "market.governed.civilization@metadao.fi", - "binding arbitration", - corpPayable - ); - - // Assign roles and revoke EOA ownership (after setup) - auth.updateRole(address(officerAddress), auth.OWNER_ROLE()); - auth.updateRole(address(corpPayable), auth.OWNER_ROLE()); - - - console.log("Auth:", address(auth)); - console.log("CyberAgreementRegistry:", address(registry)); - console.log("CertificateUriBuilder:", address(uriBuilder)); - console.log("IssuanceManagerFactory:", address(issuanceManagerFactory)); - console.log("CyberCorpSingleFactory:", address(cyberCorpSingleFactory)); - console.log("DealManagerFactory:", address(dealManagerFactory)); - // console.log("RoundManagerFactory:", address(roundManagerFactory)); - //console.log("CyberCertPrinter Impl:", address(cyberCertPrinterImplementation)); - // console.log("CyberScrip Impl:", address(cyberCert20Implementation)); - console.log("MetaDAOFactory (proxy):", address(metaDAOFactory)); - console.log("ParentCorp:", parentCorp); - console.log("ParentAuth:", parentAuth); - console.log("ParentIssuance:", parentIssuance); - console.log("ParentDealMgr:", parentDealMgr); - console.log("NewAgreementRegistryImplementation:", address(newAgreementRegistryImplementation)); - //console.log("ParentRoundMgr:", parentRoundMgr); - - vm.stopBroadcast(); - - return (CyberAgreementRegistry(registry), metaDAOFactory); - } - - function _formatEscrowAgreementTypedDataJson( - CyberAgreementRegistry registry, - string memory contractUri, - string[] memory partyFields, - string[] memory partyValues - ) internal returns (string memory) { - string memory domainSeparatorJson = vm.serializeJsonType( - DOMAIN_SEPARATOR_TYPE, - abi.encode(DomainSeparator({ - name: registry.name(), - version: registry.version(), - chainId: block.chainid, - verifyingContract: address(registry) - })) - ); - - string memory escrowSignatureDataJson = vm.serializeJsonType( - ESCROW_SIGNATUREDATA_TYPE, - abi.encode(EscrowSignatureData({ - legalContractUri: contractUri, - partyFields: partyFields, - partyValues: partyValues - })) - ); - - // Build the json string with the temporary buffer at key "outputKey" - vm.serializeString("outputKey", "domain", domainSeparatorJson); - vm.serializeString("outputKey", "message", escrowSignatureDataJson); - vm.serializeString("outputKey", "primaryType", "EscrowSignatureData"); - return vm.serializeString("outputKey", "types", "{\"EIP712Domain\":[{\"name\":\"name\",\"type\":\"string\"},{\"name\":\"version\",\"type\":\"string\"},{\"name\":\"chainId\",\"type\":\"uint256\"},{\"name\":\"verifyingContract\",\"type\":\"address\"}],\"EscrowSignatureData\":[{\"name\":\"legalContractUri\",\"type\":\"string\"},{\"name\":\"partyFields\",\"type\":\"string[]\"},{\"name\":\"partyValues\",\"type\":\"string[]\"}]}"); - } -} \ No newline at end of file diff --git a/script/libs/DeploymentConstants.sol b/script/libs/DeploymentConstants.sol index 2b510504..8b23ff47 100644 --- a/script/libs/DeploymentConstants.sol +++ b/script/libs/DeploymentConstants.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.28; library DeploymentConstants { error UnsupportedChain(uint256 chainId); + uint256 internal constant ETH = 1; uint256 internal constant BASE = 8453; uint256 internal constant ETH_SEPOLIA = 11155111; @@ -25,6 +26,16 @@ library DeploymentConstants { address lexchexCondition; } + struct UmiaDeployment { + address parentCoFactory; + bytes32 segCoTemplateId; + bytes32 boardConsentTemplateId; + } + + struct Deps { + address usdc; + } + /// @notice Latest CyberCorps V2 deployment constants. /// @dev Source: script/res/deployment-addresses.md function coreV2(uint256 chainId) @@ -38,10 +49,10 @@ library DeploymentConstants { metalexSafe: 0x68Ab3F79622cBe74C9683aA54D7E1BBdCAE8003C, auth: 0x033012a1eDA6e2E00D12CD37c5b63B9440ef5E01, cyberCorpFactory: 0x51413048f3Dfc4516e95BC8e249341B1D53B6cB2, - issuanceManagerFactory: 0xbbD386D237f3b407E6511A52488850b1Da0cCad2, + issuanceManagerFactory: 0xbbD386D237f3b407E6511A52488850b1Da0cCad2, // different from all other chains cyberCorpSingleFactory: 0xBE0D3D13AA07501beAC9b72dE9e9292E66C7A5C4, dealManagerFactory: 0x3982b078f2ac306219c9540Ebc908360a960C251, - roundManagerFactory: 0x9E2A3a07711Ce4b5A2F4D62a5c8f8B5307Af9C34, + roundManagerFactory: 0x9E2A3a07711Ce4b5A2F4D62a5c8f8B5307Af9C34, // different from all other chains cyberAgreementRegistry: 0xa9E808B8eCBB60Bb19abF026B5b863215BC4c134, uriBuilder: 0x5500c095ea7dE6F8a5E15949e24B80604cc670A3, lexchexAuth: 0xeAdeaD5C4A6747D4959489742c143bCDb95a01c2, @@ -56,10 +67,10 @@ library DeploymentConstants { metalexSafe: 0x68Ab3F79622cBe74C9683aA54D7E1BBdCAE8003C, auth: 0x033012a1eDA6e2E00D12CD37c5b63B9440ef5E01, cyberCorpFactory: 0x51413048f3Dfc4516e95BC8e249341B1D53B6cB2, - issuanceManagerFactory: 0xD353972D7955F421d94d0eA8c42c88c417F7155A, + issuanceManagerFactory: 0xD353972D7955F421d94d0eA8c42c88c417F7155A, // except base-sepolia cyberCorpSingleFactory: 0xBE0D3D13AA07501beAC9b72dE9e9292E66C7A5C4, dealManagerFactory: 0x3982b078f2ac306219c9540Ebc908360a960C251, - roundManagerFactory: 0xc9d5d0DeDD124f9351E5880469f25AB41869aeb9, + roundManagerFactory: 0xc9d5d0DeDD124f9351E5880469f25AB41869aeb9, // except base-sepolia cyberAgreementRegistry: 0xa9E808B8eCBB60Bb19abF026B5b863215BC4c134, uriBuilder: 0x5500c095ea7dE6F8a5E15949e24B80604cc670A3, lexchexAuth: 0xeAdeaD5C4A6747D4959489742c143bCDb95a01c2, @@ -69,4 +80,58 @@ library DeploymentConstants { }); } } + + function umia(uint256 chainId) + internal + pure + returns (UmiaDeployment memory deployment) + { + if (chainId == ETH) { + return UmiaDeployment({ + parentCoFactory: 0x5c6D411600774c8fE1Aa805d78F03202d7FCD47F, + segCoTemplateId: 0xd9e0fbb89f8e4e973f05d6b40b6a41e3a9af845b604e9acc7aa4f2a0c37009d8, + boardConsentTemplateId: 0x93ac1365e39b1d8237c84cf969b752ffbb717f7d8144eb47562b4060bcd91c30 + }); + } else if (chainId == ETH_SEPOLIA) { + return UmiaDeployment({ + parentCoFactory: 0x0c6Fc81BEd7f91f7a3b3594CCc66484893634Bf9, + segCoTemplateId: 0xb6da5c8e53767592c0eeb4c5c0d77eae7e1e2e795190e7237d837b3fbc98ed75, + boardConsentTemplateId: 0xc02175e98621a996529fb751b30e0b7a8344ece3b00f46a29c1e904c9da87a46 + }); + } else if (chainId == BASE_SEPOLIA) { + return UmiaDeployment({ + parentCoFactory: 0xC1304898FAfF45cA2B07C0f4E10B77843eD5a47B, + segCoTemplateId: 0xb6da5c8e53767592c0eeb4c5c0d77eae7e1e2e795190e7237d837b3fbc98ed75, + boardConsentTemplateId: 0xc02175e98621a996529fb751b30e0b7a8344ece3b00f46a29c1e904c9da87a46 + }); + } else { + revert UnsupportedChain(chainId); + } + } + + function deps(uint256 chainId) + internal + pure + returns (Deps memory deps) + { + if (chainId == ETH) { + return Deps({ + usdc: 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 + }); + } else if (chainId == BASE) { + return Deps({ + usdc: 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 + }); + } else if (chainId == ETH_SEPOLIA) { + return Deps({ + usdc: 0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238 + }); + } else if (chainId == BASE_SEPOLIA) { + return Deps({ + usdc: 0x036CbD53842c5426634e7929541eC2318f3dCF7e + }); + } else { + revert UnsupportedChain(chainId); + } + } } diff --git a/script/libs/SafeUtils.sol b/script/libs/SafeUtils.sol new file mode 100644 index 00000000..eb4b3c00 --- /dev/null +++ b/script/libs/SafeUtils.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.20; + +import {Vm, console2} from "forge-std/Test.sol"; +import {GnosisTransaction} from "./safe.sol"; + +// Access hidden cheatcodes +interface EnhancedVm is Vm { + function serializeJsonType(string calldata typeDescription, bytes memory value) external pure returns (string memory json); +} + +library SafeUtils { + EnhancedVm constant vm = EnhancedVm(address(uint160(uint256(keccak256("hevm cheat code"))))); + + struct SafeTxImport { + string version; + string chainId; + uint256 createdAt; + SafeTxMeta meta; + SafeTx[] transactions; + } + + struct SafeTxMeta { + string name; + string description; + string txBuilderVersion; + string createdFromSafeAddress; + string createdFromOwnerAddress; + string checksum; + } + + struct SafeTx { + address to; + string value; + bytes data; + } + + function formatSafeTxJson(GnosisTransaction[] memory safeTxs, uint256 chainId) internal returns (string memory) { + SafeTx[] memory convertedSafeTxs = new SafeTx[](safeTxs.length); + for (uint256 i = 0; i < safeTxs.length; i++) { + convertedSafeTxs[i] = SafeTx({ + to: safeTxs[i].to, + value: vm.toString(safeTxs[i].value), + data: safeTxs[i].data + }); + } + + return vm.serializeJsonType( + // it is important to include the input argument names as the utility will use them + "SafeTxImport(string version,string chainId,uint256 createdAt,SafeTxMeta meta,SafeTx[] transactions)SafeTxMeta(string name,string description,string txBuilderVersion,string createdFromSafeAddress,string createdFromOwnerAddress,string checksum)SafeTx(address to,string value,bytes data)", + abi.encode(SafeTxImport({ + version: "1.0", + chainId: vm.toString(chainId), + createdAt: block.timestamp * 1000, + meta: SafeTxMeta({ + name: "Transactions Batch", + description: "", + txBuilderVersion: "", + createdFromSafeAddress: "", + createdFromOwnerAddress: "", + checksum: "" + }), + transactions: convertedSafeTxs + })) + ); + } + + function parseSafeTxJson(string memory json) internal returns (SafeTxImport memory) { + return abi.decode(vm.parseJson(json), (SafeTxImport)); + } +} diff --git a/src/ParentCoFactory.sol b/src/ParentCoFactory.sol index ec826e3c..bb2871fc 100644 --- a/src/ParentCoFactory.sol +++ b/src/ParentCoFactory.sol @@ -55,6 +55,7 @@ import "./storage/CyberCertPrinterStorage.sol"; import "./libs/auth.sol"; import "@openzeppelin/contracts/utils/Create2.sol"; import "@openzeppelin/contracts/utils/Strings.sol"; +import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; @@ -74,6 +75,7 @@ interface ICyberCorpLocal { contract ParentCoFactory is UUPSUpgradeable, BorgAuthACL, IERC721Receiver { using Strings for string; + using ECDSA for bytes32; error InvalidSalt(); error RoundManagerAlreadyExists(); @@ -88,7 +90,11 @@ contract ParentCoFactory is UUPSUpgradeable, BorgAuthACL, IERC721Receiver { bytes public parentCoSignatureHash; address public stable; // stored ParentCo officer details used in agreements - CompanyOfficer public parentCoOfficer; + CompanyOfficer[] public parentCoOfficers; + + // EIP-712 + bytes32 public DOMAIN_SEPARATOR; + bytes32 public ESCROW_AUTHORIZATION_TYPEHASH; // Parent corp (ParentCo) deployment record address public parentCorp; @@ -99,7 +105,7 @@ contract ParentCoFactory is UUPSUpgradeable, BorgAuthACL, IERC721Receiver { bool public parentCorpCreated; //adjust storage gap based on new variable - uint256[38] private __gap; // keep storage gap similar to CyberCorpFactory + uint256[39] private __gap; // keep storage gap similar to CyberCorpFactory struct CyberCertData { string name; @@ -150,11 +156,14 @@ contract ParentCoFactory is UUPSUpgradeable, BorgAuthACL, IERC721Receiver { address oldRoundManagerFactory ); + event RoundManagerDeployed(address indexed cyberCorp, address indexed roundManager); + event UriBuilderUpdated(address indexed uriBuilder, address oldUriBuilder); event RegistryAddressUpdated(address indexed registryAddress, address oldRegistryAddress); error GlobalOrPartyValuesMismatch(); error OfficerValuesMismatch(); + error UnauthorizedEscrowSigner(); function initialize( address _auth, @@ -176,6 +185,17 @@ contract ParentCoFactory is UUPSUpgradeable, BorgAuthACL, IERC721Receiver { roundManagerFactory = _roundManagerFactory; uriBuilder = _uriBuilder; stable = _stable; + + DOMAIN_SEPARATOR = keccak256( + abi.encode( + keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), + keccak256("ParentCoFactory"), + keccak256("1"), + block.chainid, + address(this) + ) + ); + ESCROW_AUTHORIZATION_TYPEHASH = keccak256("EscrowAuthorization(string name,string contact)"); } function setParentCoSignatureHash(bytes memory _parentCoSignatureHash) public onlyOwner { @@ -186,24 +206,29 @@ contract ParentCoFactory is UUPSUpgradeable, BorgAuthACL, IERC721Receiver { stable = _stable; } - function setParentCoOfficer(CompanyOfficer memory _officer) public onlyOwner { - parentCoOfficer = _officer; - } - - function setParentCoOfficerEOA(address _eoa) public onlyOwner { - parentCoOfficer.eoa = _eoa; + function setParentCoOfficers(CompanyOfficer[] memory _officers) public onlyOwner { + delete parentCoOfficers; + for (uint256 i = 0; i < _officers.length; i++) + parentCoOfficers.push(_officers[i]); } - function setParentCoOfficerName(string memory _name) public onlyOwner { - parentCoOfficer.name = _name; - } - - function setParentCoOfficerContact(string memory _contact) public onlyOwner { - parentCoOfficer.contact = _contact; - } - - function setParentCoOfficerTitle(string memory _title) public onlyOwner { - parentCoOfficer.title = _title; + /// @notice Authorization should include everything used to escrow-sign: + /// - escrow contract (implicit) + /// - EOA (implicit) + /// - name (explicit) + /// - contact (explicit) + function escrowAuthorizationHash(string memory name, string memory contact) public view returns (bytes32) { + return keccak256( + abi.encodePacked( + "\x19\x01", + DOMAIN_SEPARATOR, + keccak256(abi.encode( + ESCROW_AUTHORIZATION_TYPEHASH, + keccak256(bytes(name)), + keccak256(bytes(contact)) + )) + ) + ); } function setIssuanceManagerFactory(address _issuanceManagerFactory) external onlyOwner { @@ -319,8 +344,27 @@ contract ParentCoFactory is UUPSUpgradeable, BorgAuthACL, IERC721Receiver { dealManagerFactory ); + if (ICyberCorp(cyberCorpAddress).roundManager() != address(0)) { + revert RoundManagerAlreadyExists(); + } + + roundManagerAddress = IRoundManagerFactory(roundManagerFactory).deployRoundManager(salt); + + IRoundManagerInit(roundManagerAddress).initialize( + address(BorgAuthACL(cyberCorpAddress).AUTH()), + cyberCorpAddress, + registryAddress, + ICyberCorpLocal(cyberCorpAddress).issuanceManager(), + roundManagerFactory + ); + + ICyberCorp(cyberCorpAddress).setRoundManager(roundManagerAddress); + + emit RoundManagerDeployed(cyberCorpAddress, roundManagerAddress); + BorgAuth(authAddress).updateRole(issuanceManagerAddress, 99); BorgAuth(authAddress).updateRole(dealManagerAddress, 99); + BorgAuth(authAddress).updateRole(roundManagerAddress, 99); emit CorpDeployed(cyberCorpAddress, authAddress, issuanceManagerAddress, dealManagerAddress, roundManagerAddress, address(0), 0, _officer.eoa); } @@ -342,7 +386,7 @@ contract ParentCoFactory is UUPSUpgradeable, BorgAuthACL, IERC721Receiver { address roundMgr ) { if (parentCorpCreated) revert("ParentCorpAlreadyCreated"); - CompanyOfficer memory officer = parentCoOfficer; + CompanyOfficer memory officer = parentCoOfficers[0]; if (officer.eoa == address(0)) revert("ParentCoOfficerNotSet"); ( @@ -362,6 +406,11 @@ contract ParentCoFactory is UUPSUpgradeable, BorgAuthACL, IERC721Receiver { officer ); + // Add the remaining officers + for (uint256 i = 1; i < parentCoOfficers.length; i++) { + ICyberCorp(corp).addOfficer(parentCoOfficers[i]); + } + parentCorp = corp; parentAuth = auth; parentIssuanceManager = issuance; @@ -380,7 +429,7 @@ contract ParentCoFactory is UUPSUpgradeable, BorgAuthACL, IERC721Receiver { string memory companyContactDetails, string memory defaultDisputeResolution, address _companyPayable, - CompanyOfficer memory _officer, + CompanyOfficer memory _officer, // PC always has only one officer bytes32 _segCoTemplateId, bytes32 _boardConsentTempateId, string[] memory _globalValues, @@ -400,6 +449,22 @@ contract ParentCoFactory is UUPSUpgradeable, BorgAuthACL, IERC721Receiver { uint256[] memory certIds ) { + // Resolve which officer pre-signed the escrow authorization + CompanyOfficer memory signingOfficer; + bool signerFound; + for (uint256 i = 0; i < parentCoOfficers.length; i++) { + address escrowSigner = escrowAuthorizationHash( + parentCoOfficers[i].name, + parentCoOfficers[i].contact + ).recover(parentCoSignatureHash); + if (parentCoOfficers[i].eoa == escrowSigner) { + signingOfficer = parentCoOfficers[i]; + signerFound = true; + break; + } + } + if (!signerFound) revert UnauthorizedEscrowSigner(); + // Check: validate key fields if (_partyValues.length < 2 @@ -419,13 +484,13 @@ contract ParentCoFactory is UUPSUpgradeable, BorgAuthACL, IERC721Receiver { // Effect: construct parties address[] memory partiesOverride = new address[](2); - partiesOverride[0] = parentCoOfficer.eoa; + partiesOverride[0] = signingOfficer.eoa; partiesOverride[1] = deployer; string[][] memory partyValuesOverride = new string[][](2); partyValuesOverride[0] = new string[](2); - partyValuesOverride[0][0] = parentCoOfficer.name; - partyValuesOverride[0][1] = parentCoOfficer.contact; + partyValuesOverride[0][0] = signingOfficer.name; + partyValuesOverride[0][1] = signingOfficer.contact; partyValuesOverride[1] = _partyValues; //create bytes32 salt @@ -463,7 +528,7 @@ contract ParentCoFactory is UUPSUpgradeable, BorgAuthACL, IERC721Receiver { ICyberAgreementRegistry(registryAddress).signContractFor(deployer, agreementId, partyValuesOverride[1], signature, false, ""); ICyberAgreementRegistry(registryAddress).signContractWithEscrow( - parentCoOfficer.eoa, + signingOfficer.eoa, agreementId, partyValuesOverride[0], parentCoSignatureHash, @@ -488,7 +553,7 @@ contract ParentCoFactory is UUPSUpgradeable, BorgAuthACL, IERC721Receiver { ); ICyberAgreementRegistry(registryAddress).signContractWithEscrow( - parentCoOfficer.eoa, + signingOfficer.eoa, meetingNotesId, meetingNotesPartyValues[0], parentCoSignatureHash, diff --git a/src/interfaces/ICyberCorp.sol b/src/interfaces/ICyberCorp.sol index 8f747a5e..78fc5235 100644 --- a/src/interfaces/ICyberCorp.sol +++ b/src/interfaces/ICyberCorp.sol @@ -68,6 +68,7 @@ interface ICyberCorp { function setDealManager(address _dealManager) external; function setRoundManager(address _roundManager) external; function roundManager() external view returns (address); + function addOfficer(CompanyOfficer memory _officer) external; function addEscrowedOfficerSignature(bytes calldata signature) external; function setEscrowedOfficerSignature(uint256 index, bytes calldata signature) external; function getEscrowedOfficerSignature(uint256 index) external view returns (bytes memory); diff --git a/test/DeployParentCoFactory.t.sol b/test/DeployParentCoFactory.t.sol new file mode 100644 index 00000000..cdca6302 --- /dev/null +++ b/test/DeployParentCoFactory.t.sol @@ -0,0 +1,704 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.28; + +import {Test, Vm, console2} from "forge-std/Test.sol"; +import {CyberAgreementUtils} from "./libs/CyberAgreementUtils.sol"; +import {DeployParentCoFactoryScript} from "../script/deploy-parentco-factory.s.sol"; +import {GnosisTransaction} from "../script/libs/safe.sol"; +import {DeploymentConstants} from "../script/libs/DeploymentConstants.sol"; +import {CyberAgreementRegistry} from "../src/CyberAgreementRegistry.sol"; +import {ParentCoFactory} from "../src/ParentCoFactory.sol"; +import {CyberCorp} from "../src/CyberCorp.sol"; +import {CompanyOfficer} from "../src/CyberCorpConstants.sol"; +import {BorgAuth} from "../src/libs/auth.sol"; + +contract DeployParentCoFactoryTest is Test { + DeploymentConstants.CoreDeployment coreDeployment; + DeploymentConstants.Deps deps; + + CyberAgreementRegistry registry; + + address deployer; + uint256 deployerPrivKey; + + ParentCoFactory parentCoFactory; + address parentCoMultisig; + address parentCoOfficer1; + uint256 parentCoOfficer1PrivKey; + address parentCoOfficer2; + uint256 parentCoOfficer2PrivKey; + + CompanyOfficer[] parentCoOfficers; + + bytes32 segCoTemplateId = keccak256("ParentCo.SegCo.v1"); + bytes32 boardConsentTemplateId = keccak256("ParentCo.BoardConsent.v1"); + + address subCorpPayable; + address subCorpOfficer; + uint256 subCorpOfficerPrivKey; + + struct SubCorpResult { + address subCorp; + address subAuth; + address subIssuance; + address subDealMgr; + bytes32 expectedSegCoId; + bytes32 expectedBoardConsentId; + } + + function setUp() public { + coreDeployment = DeploymentConstants.coreV2(block.chainid); + deps = DeploymentConstants.deps(block.chainid); + + registry = CyberAgreementRegistry(coreDeployment.cyberAgreementRegistry); + + (deployer, deployerPrivKey) = makeAddrAndKey("deployer"); + + (parentCoMultisig, ) = makeAddrAndKey("parentCoMultisig"); + (parentCoOfficer1, parentCoOfficer1PrivKey) = makeAddrAndKey("parentCoOfficer1"); + (parentCoOfficer2, parentCoOfficer2PrivKey) = makeAddrAndKey("parentCoOfficer2"); + (subCorpPayable, ) = makeAddrAndKey("subCorpPayable"); + (subCorpOfficer, subCorpOfficerPrivKey) = makeAddrAndKey("subCorpOfficer"); + + parentCoOfficers.push(CompanyOfficer({ + eoa: parentCoOfficer1, + name: "Test ParentCo Officer 1", + contact: "test1@parentco.example", + title: "Director" + })); + parentCoOfficers.push(CompanyOfficer({ + eoa: parentCoOfficer2, + name: "Test ParentCo Officer 2", + contact: "test2@parentco.example", + title: "Director" + })); + + GnosisTransaction[] memory safeTxs; + (parentCoFactory, safeTxs) = (new DeployParentCoFactoryScript()).runWithArgs({ + chainId: block.chainid, + deployerPrivateKey: deployerPrivKey, + saltStr: "ParentCoFactory.deploy.v1", + segCoTemplateId: segCoTemplateId, + segCoDocName: "FOUNDER/OPERATOR LEGAL PACK", + segCoDocUri: "ipfs://parentco-test-segco-template", + boardConsentTemplateId: boardConsentTemplateId, + boardConsentName: "ParentCo Board Consent", + boardConsentUri: "ipfs://parentco-test-board-consent-template", + parentCoPayable: parentCoMultisig, + parentCoName: "Test ParentCo LLC", + parentCoType: "limited liability company", + parentCoJurisdiction: "Delaware", + parentCoContactDetails: "test@parentco.example", + parentCoDefaultDisputeResolution: "binding arbitration", + parentCoOfficers: parentCoOfficers + }); + + // simulate MetaLeX safe executing safe txs + for (uint256 i = 0; i < safeTxs.length; i++) { + vm.prank(coreDeployment.metalexSafe); + (bool success,) = safeTxs[i].to.call{value: safeTxs[i].value}(safeTxs[i].data); + vm.assertTrue(success); + } + + // Set escrow sig: parentCoOfficer1 signs the factory-specific EIP-712 authorization + bytes memory escrowSig = _createParentCoSignatureHash( + parentCoOfficer1PrivKey, + parentCoOfficers[0].name, + parentCoOfficers[0].contact + ); + vm.prank(parentCoOfficer1); + parentCoFactory.setParentCoSignatureHash(escrowSig); + } + + function _deploySubCorp() internal returns (SubCorpResult memory r) { + uint256 subCorpSalt = uint256(keccak256("ParentCo.Test2.SubCorp.v1")); + string memory subCompanyName = "Test SubCo SPV 1"; + string memory subCompanyType = "series limited liability company"; + string memory subCompanyJurisdiction = "Delaware"; + string memory subCompanyContact = "subco@parentco.example"; + string memory subDisputeResolution = "binding arbitration"; + + string[] memory globalValues = new string[](8); + globalValues[0] = "Test Founder"; + globalValues[1] = "Test ParentCo Enterprise"; + globalValues[2] = subCompanyName; + globalValues[3] = subCompanyType; + globalValues[4] = subCompanyJurisdiction; + globalValues[5] = subCompanyContact; + globalValues[6] = "TSC1"; + globalValues[7] = "Test SubCo One"; + + string[] memory partyValues = new string[](2); + partyValues[0] = "Test Deployer Officer"; + partyValues[1] = "deployer@parentco.example"; + + CompanyOfficer memory subOfficer = CompanyOfficer({ + eoa: subCorpOfficer, + name: partyValues[0], + contact: partyValues[1], + title: "Founder" + }); + + // Pre-compute expected agreement IDs (registry: keccak256(abi.encode(templateId, salt, globalValues, parties))) + // parentCoOfficer1 is the escrow signer set in setUp + address[] memory segCoParties = new address[](2); + segCoParties[0] = parentCoOfficer1; + segCoParties[1] = subCorpOfficer; + r.expectedSegCoId = keccak256(abi.encode(segCoTemplateId, subCorpSalt, globalValues, segCoParties)); + + address[] memory boardConsentParties = new address[](1); + boardConsentParties[0] = parentCoOfficer1; + r.expectedBoardConsentId = keccak256(abi.encode(boardConsentTemplateId, subCorpSalt, globalValues, boardConsentParties)); + + ( + string memory legalContractUri, + , + string[] memory templateGlobalFields, + string[] memory templatePartyFields + ) = registry.getTemplateDetails(segCoTemplateId); + + bytes memory subCorpOwnerSignature = CyberAgreementUtils.signAgreementTypedData( + vm, + registry.DOMAIN_SEPARATOR(), + registry.SIGNATUREDATA_TYPEHASH(), + r.expectedSegCoId, + legalContractUri, + templateGlobalFields, + templatePartyFields, + globalValues, + partyValues, + subCorpOfficerPrivKey + ); + + ( + r.subCorp, + r.subAuth, + r.subIssuance, + r.subDealMgr, + , + , + , + + ) = parentCoFactory.deployCorpContractFor( + subCorpSalt, + subCompanyName, + subCompanyType, + subCompanyJurisdiction, + subCompanyContact, + subDisputeResolution, + subCorpPayable, + subOfficer, + segCoTemplateId, + boardConsentTemplateId, + globalValues, + partyValues, + subCorpOwnerSignature, + subCorpOfficer + ); + } + + function test_parentCoOfficersInFactory() public { + (address eoa0, string memory name0, string memory contact0, string memory title0) = parentCoFactory.parentCoOfficers(0); + assertEq(eoa0, parentCoOfficer1); + assertEq(name0, "Test ParentCo Officer 1"); + assertEq(contact0, "test1@parentco.example"); + assertEq(title0, "Director"); + + (address eoa1, string memory name1, string memory contact1, string memory title1) = parentCoFactory.parentCoOfficers(1); + assertEq(eoa1, parentCoOfficer2); + assertEq(name1, "Test ParentCo Officer 2"); + assertEq(contact1, "test2@parentco.example"); + assertEq(title1, "Director"); + + vm.expectRevert(); + parentCoFactory.parentCoOfficers(2); + } + + function test_parentCorpOfficersInCyberCorp() public { + CyberCorp corp = CyberCorp(parentCoFactory.parentCorp()); + + (address eoa0, string memory name0, string memory contact0, string memory title0) = corp.companyOfficers(0); + assertEq(eoa0, parentCoOfficer1); + assertEq(name0, "Test ParentCo Officer 1"); + assertEq(contact0, "test1@parentco.example"); + assertEq(title0, "Director"); + + (address eoa1, string memory name1, string memory contact1, string memory title1) = corp.companyOfficers(1); + assertEq(eoa1, parentCoOfficer2); + assertEq(name1, "Test ParentCo Officer 2"); + assertEq(contact1, "test2@parentco.example"); + assertEq(title1, "Director"); + + assertTrue(corp.isCyberCORPOfficer(parentCoOfficer1)); + assertTrue(corp.isCyberCORPOfficer(parentCoOfficer2)); + assertFalse(corp.isCyberCORPOfficer(parentCoMultisig)); + + vm.expectRevert(); + corp.companyOfficers(2); + } + + function test_deployerHasSelfRevokedOwnerOfParentCoFactory() public { + assertEq(parentCoFactory.userRoles(deployer), 0); + } + + function test_parentCoMultisigIsCorpPayable() public { + CyberCorp corp = CyberCorp(parentCoFactory.parentCorp()); + assertEq(corp.companyPayable(), parentCoMultisig); + } + + function test_corpPayableIsOwnerOfParentCoFactory() public { + uint256 ownerRole = parentCoFactory.AUTH().OWNER_ROLE(); + assertGe(parentCoFactory.userRoles(parentCoMultisig), ownerRole); + } + + function test_parentCoOfficersAreOwnersOfParentCoFactory() public { + uint256 ownerRole = parentCoFactory.AUTH().OWNER_ROLE(); + assertGe(parentCoFactory.userRoles(parentCoOfficer1), ownerRole); + assertGe(parentCoFactory.userRoles(parentCoOfficer2), ownerRole); + } + + function test_parentCoOfficersAreOwnersOfParentCyberCorp() public { + CyberCorp corp = CyberCorp(parentCoFactory.parentCorp()); + uint256 ownerRole = corp.AUTH().OWNER_ROLE(); + assertGe(corp.userRoles(parentCoOfficer1), ownerRole); + assertGe(corp.userRoles(parentCoOfficer2), ownerRole); + } + + function test_deploySubCorp_revertIfEscrowSignerNotOfficer() public { + (, uint256 nonOfficerKey) = makeAddrAndKey("nonOfficer"); + bytes memory nonOfficerSig = _createParentCoSignatureHash( + nonOfficerKey, + parentCoOfficers[0].name, + parentCoOfficers[0].contact + ); + vm.prank(parentCoOfficer1); + parentCoFactory.setParentCoSignatureHash(nonOfficerSig); + + CompanyOfficer memory dummyOfficer = CompanyOfficer({eoa: address(0), name: "", contact: "", title: ""}); + string[] memory empty = new string[](0); + vm.expectRevert(ParentCoFactory.UnauthorizedEscrowSigner.selector); + parentCoFactory.deployCorpContractFor( + 0, "", "", "", "", "", address(0), dummyOfficer, + bytes32(0), bytes32(0), empty, empty, hex"", address(0) + ); + } + + function test_deploySubCorp_revertIfOfficerSignsWithAnotherOfficersIdentity() public { + // officer2 signs using officer1's name+contact — should not authenticate as officer1 + bytes memory spoofedSig = _createParentCoSignatureHash( + parentCoOfficer2PrivKey, + parentCoOfficers[0].name, + parentCoOfficers[0].contact + ); + vm.prank(parentCoOfficer1); + parentCoFactory.setParentCoSignatureHash(spoofedSig); + + CompanyOfficer memory dummyOfficer = CompanyOfficer({eoa: address(0), name: "", contact: "", title: ""}); + string[] memory empty = new string[](0); + vm.expectRevert(ParentCoFactory.UnauthorizedEscrowSigner.selector); + parentCoFactory.deployCorpContractFor( + 0, "", "", "", "", "", address(0), dummyOfficer, + bytes32(0), bytes32(0), empty, empty, hex"", address(0) + ); + } + + function test_deploySubCorp_revertIfEscrowSigForWrongFactory() public { + // Officer signs a valid EIP-712 sig but with address(0) as the factory field + bytes32 wrongDigest = keccak256(abi.encodePacked( + "\x19\x01", + parentCoFactory.DOMAIN_SEPARATOR(), + keccak256(abi.encode(parentCoFactory.ESCROW_AUTHORIZATION_TYPEHASH(), address(0))) + )); + (uint8 v, bytes32 r, bytes32 s) = vm.sign(parentCoOfficer1PrivKey, wrongDigest); + vm.prank(parentCoOfficer1); + parentCoFactory.setParentCoSignatureHash(abi.encodePacked(r, s, v)); + + CompanyOfficer memory dummyOfficer = CompanyOfficer({eoa: address(0), name: "", contact: "", title: ""}); + string[] memory empty = new string[](0); + vm.expectRevert(ParentCoFactory.UnauthorizedEscrowSigner.selector); + parentCoFactory.deployCorpContractFor( + 0, "", "", "", "", "", address(0), dummyOfficer, + bytes32(0), bytes32(0), empty, empty, hex"", address(0) + ); + } + + function test_deploySubCorp_returnsNonZeroAddresses() public { + SubCorpResult memory r = _deploySubCorp(); + assertTrue(r.subCorp != address(0)); + assertTrue(r.subAuth != address(0)); + assertTrue(r.subIssuance != address(0)); + assertTrue(r.subDealMgr != address(0)); + assertTrue(r.subCorp != r.subAuth); + assertTrue(r.subCorp != r.subIssuance); + assertTrue(r.subCorp != r.subDealMgr); + } + + function test_deploySubCorp_subCorpOfficerCorrect() public { + SubCorpResult memory r = _deploySubCorp(); + CyberCorp corp = CyberCorp(r.subCorp); + + (address eoa, string memory name, string memory contact, string memory title) = corp.companyOfficers(0); + assertEq(eoa, subCorpOfficer); + assertEq(name, "Test Deployer Officer"); + assertEq(contact, "deployer@parentco.example"); + assertEq(title, "Founder"); + assertTrue(corp.isCyberCORPOfficer(subCorpOfficer)); + } + + function test_deploySubCorp_subCorpPayableCorrect() public { + SubCorpResult memory r = _deploySubCorp(); + assertEq(CyberCorp(r.subCorp).companyPayable(), subCorpPayable); + } + + function test_deploySubCorp_subCorpAuthRoles() public { + SubCorpResult memory r = _deploySubCorp(); + BorgAuth subAuth = BorgAuth(r.subAuth); + uint256 ownerRole = subAuth.OWNER_ROLE(); + assertGe(subAuth.userRoles(subCorpOfficer), ownerRole); + } + + function test_deploySubCorp_agreementSignedByBothParties() public { + SubCorpResult memory r = _deploySubCorp(); + assertTrue(registry.hasSigned(r.expectedSegCoId, parentCoOfficer1)); + assertTrue(registry.hasSigned(r.expectedSegCoId, subCorpOfficer)); + assertTrue(registry.allPartiesSigned(r.expectedSegCoId)); + } + + function test_deploySubCorp_boardConsentSigned() public { + SubCorpResult memory r = _deploySubCorp(); + assertTrue(registry.hasSigned(r.expectedBoardConsentId, parentCoOfficer1)); + address[] memory parties = registry.getParties(r.expectedBoardConsentId); + assertEq(parties.length, 1); + assertEq(parties[0], parentCoOfficer1); + } + + function test_deploySubCorp_officer2CanSetEscrowAndDeploy() public { + bytes memory sig = _createParentCoSignatureHash( + parentCoOfficer2PrivKey, + parentCoOfficers[1].name, + parentCoOfficers[1].contact + ); + vm.prank(parentCoOfficer2); + parentCoFactory.setParentCoSignatureHash(sig); + + uint256 subCorpSalt = uint256(keccak256("ParentCo.Test2.SubCorp.v1")); + string memory subCompanyName = "Test SubCo SPV 1"; + string memory subCompanyType = "series limited liability company"; + string memory subCompanyJurisdiction = "Delaware"; + string memory subCompanyContact = "subco@parentco.example"; + string memory subDisputeResolution = "binding arbitration"; + + string[] memory globalValues = new string[](8); + globalValues[0] = "Test Founder"; + globalValues[1] = "Test ParentCo Enterprise"; + globalValues[2] = subCompanyName; + globalValues[3] = subCompanyType; + globalValues[4] = subCompanyJurisdiction; + globalValues[5] = subCompanyContact; + globalValues[6] = "TSC1"; + globalValues[7] = "Test SubCo One"; + + string[] memory partyValues = new string[](2); + partyValues[0] = "Test Deployer Officer"; + partyValues[1] = "deployer@parentco.example"; + + CompanyOfficer memory subOfficer = CompanyOfficer({ + eoa: subCorpOfficer, + name: partyValues[0], + contact: partyValues[1], + title: "Founder" + }); + + // With officer2 as escrow signer, party[0] = parentCoOfficer2 + address[] memory segCoParties = new address[](2); + segCoParties[0] = parentCoOfficer2; + segCoParties[1] = subCorpOfficer; + bytes32 agreementId = keccak256(abi.encode(segCoTemplateId, subCorpSalt, globalValues, segCoParties)); + + ( + string memory legalContractUri, + , + string[] memory templateGlobalFields, + string[] memory templatePartyFields + ) = registry.getTemplateDetails(segCoTemplateId); + + bytes memory agreementSig = CyberAgreementUtils.signAgreementTypedData( + vm, + registry.DOMAIN_SEPARATOR(), + registry.SIGNATUREDATA_TYPEHASH(), + agreementId, + legalContractUri, + templateGlobalFields, + templatePartyFields, + globalValues, + partyValues, + subCorpOfficerPrivKey + ); + + (address subCorp,,,,,, bytes32 retId,) = parentCoFactory.deployCorpContractFor( + subCorpSalt, + subCompanyName, + subCompanyType, + subCompanyJurisdiction, + subCompanyContact, + subDisputeResolution, + subCorpPayable, + subOfficer, + segCoTemplateId, + boardConsentTemplateId, + globalValues, + partyValues, + agreementSig, + subCorpOfficer + ); + + assertTrue(subCorp != address(0)); + assertTrue(registry.hasSigned(agreementId, parentCoOfficer2)); + assertTrue(registry.hasSigned(agreementId, subCorpOfficer)); + } + + function test_deploySubCorp_revertIfPartyValuesTooShort() public { + uint256 subCorpSalt = uint256(keccak256("ParentCo.Test2.SubCorp.v1")); + CompanyOfficer memory subOfficer = CompanyOfficer({ + eoa: subCorpOfficer, + name: "Test Deployer Officer", + contact: "deployer@parentco.example", + title: "Founder" + }); + string[] memory globalValues = new string[](8); + globalValues[0] = "Test Founder"; + globalValues[1] = "Test ParentCo Enterprise"; + globalValues[2] = "Test SubCo SPV 1"; + globalValues[3] = "series limited liability company"; + globalValues[4] = "Delaware"; + globalValues[5] = "subco@parentco.example"; + globalValues[6] = "TSC1"; + globalValues[7] = "Test SubCo One"; + string[] memory shortPartyValues = new string[](1); + shortPartyValues[0] = "Test Deployer Officer"; + + vm.expectRevert(ParentCoFactory.GlobalOrPartyValuesMismatch.selector); + parentCoFactory.deployCorpContractFor( + subCorpSalt, "Test SubCo SPV 1", "series limited liability company", + "Delaware", "subco@parentco.example", "binding arbitration", + subCorpPayable, subOfficer, segCoTemplateId, boardConsentTemplateId, + globalValues, shortPartyValues, hex"", subCorpOfficer + ); + } + + function test_deploySubCorp_revertIfOfficerNameMismatch() public { + uint256 subCorpSalt = uint256(keccak256("ParentCo.Test2.SubCorp.v1")); + CompanyOfficer memory subOfficer = CompanyOfficer({ + eoa: subCorpOfficer, + name: "Test Deployer Officer", + contact: "deployer@parentco.example", + title: "Founder" + }); + string[] memory globalValues = new string[](8); + globalValues[0] = "Test Founder"; + globalValues[1] = "Test ParentCo Enterprise"; + globalValues[2] = "Test SubCo SPV 1"; + globalValues[3] = "series limited liability company"; + globalValues[4] = "Delaware"; + globalValues[5] = "subco@parentco.example"; + globalValues[6] = "TSC1"; + globalValues[7] = "Test SubCo One"; + string[] memory partyValues = new string[](2); + partyValues[0] = "WRONG NAME"; // != officer.name + partyValues[1] = "deployer@parentco.example"; + + vm.expectRevert(ParentCoFactory.GlobalOrPartyValuesMismatch.selector); + parentCoFactory.deployCorpContractFor( + subCorpSalt, "Test SubCo SPV 1", "series limited liability company", + "Delaware", "subco@parentco.example", "binding arbitration", + subCorpPayable, subOfficer, segCoTemplateId, boardConsentTemplateId, + globalValues, partyValues, hex"", subCorpOfficer + ); + } + + function test_deploySubCorp_revertIfOfficerContactMismatch() public { + uint256 subCorpSalt = uint256(keccak256("ParentCo.Test2.SubCorp.v1")); + CompanyOfficer memory subOfficer = CompanyOfficer({ + eoa: subCorpOfficer, + name: "Test Deployer Officer", + contact: "deployer@parentco.example", + title: "Founder" + }); + string[] memory globalValues = new string[](8); + globalValues[0] = "Test Founder"; + globalValues[1] = "Test ParentCo Enterprise"; + globalValues[2] = "Test SubCo SPV 1"; + globalValues[3] = "series limited liability company"; + globalValues[4] = "Delaware"; + globalValues[5] = "subco@parentco.example"; + globalValues[6] = "TSC1"; + globalValues[7] = "Test SubCo One"; + string[] memory partyValues = new string[](2); + partyValues[0] = "Test Deployer Officer"; + partyValues[1] = "wrong@email.com"; // != officer.contact + + vm.expectRevert(ParentCoFactory.GlobalOrPartyValuesMismatch.selector); + parentCoFactory.deployCorpContractFor( + subCorpSalt, "Test SubCo SPV 1", "series limited liability company", + "Delaware", "subco@parentco.example", "binding arbitration", + subCorpPayable, subOfficer, segCoTemplateId, boardConsentTemplateId, + globalValues, partyValues, hex"", subCorpOfficer + ); + } + + function test_deploySubCorp_revertIfCompanyNameMismatch() public { + uint256 subCorpSalt = uint256(keccak256("ParentCo.Test2.SubCorp.v1")); + CompanyOfficer memory subOfficer = CompanyOfficer({ + eoa: subCorpOfficer, + name: "Test Deployer Officer", + contact: "deployer@parentco.example", + title: "Founder" + }); + string[] memory globalValues = new string[](8); + globalValues[0] = "Test Founder"; + globalValues[1] = "Test ParentCo Enterprise"; + globalValues[2] = "WRONG COMPANY NAME"; // != companyName arg below + globalValues[3] = "series limited liability company"; + globalValues[4] = "Delaware"; + globalValues[5] = "subco@parentco.example"; + globalValues[6] = "TSC1"; + globalValues[7] = "Test SubCo One"; + string[] memory partyValues = new string[](2); + partyValues[0] = "Test Deployer Officer"; + partyValues[1] = "deployer@parentco.example"; + + vm.expectRevert(ParentCoFactory.GlobalOrPartyValuesMismatch.selector); + parentCoFactory.deployCorpContractFor( + subCorpSalt, "Test SubCo SPV 1", "series limited liability company", + "Delaware", "subco@parentco.example", "binding arbitration", + subCorpPayable, subOfficer, segCoTemplateId, boardConsentTemplateId, + globalValues, partyValues, hex"", subCorpOfficer + ); + } + + function test_deploySubCorp_revertIfCompanyTypeMismatch() public { + uint256 subCorpSalt = uint256(keccak256("ParentCo.Test2.SubCorp.v1")); + CompanyOfficer memory subOfficer = CompanyOfficer({ + eoa: subCorpOfficer, + name: "Test Deployer Officer", + contact: "deployer@parentco.example", + title: "Founder" + }); + string[] memory globalValues = new string[](8); + globalValues[0] = "Test Founder"; + globalValues[1] = "Test ParentCo Enterprise"; + globalValues[2] = "Test SubCo SPV 1"; + globalValues[3] = "WRONG TYPE"; // != companyType arg below + globalValues[4] = "Delaware"; + globalValues[5] = "subco@parentco.example"; + globalValues[6] = "TSC1"; + globalValues[7] = "Test SubCo One"; + string[] memory partyValues = new string[](2); + partyValues[0] = "Test Deployer Officer"; + partyValues[1] = "deployer@parentco.example"; + + vm.expectRevert(ParentCoFactory.GlobalOrPartyValuesMismatch.selector); + parentCoFactory.deployCorpContractFor( + subCorpSalt, "Test SubCo SPV 1", "series limited liability company", + "Delaware", "subco@parentco.example", "binding arbitration", + subCorpPayable, subOfficer, segCoTemplateId, boardConsentTemplateId, + globalValues, partyValues, hex"", subCorpOfficer + ); + } + + function test_deploySubCorp_revertIfJurisdictionMismatch() public { + uint256 subCorpSalt = uint256(keccak256("ParentCo.Test2.SubCorp.v1")); + CompanyOfficer memory subOfficer = CompanyOfficer({ + eoa: subCorpOfficer, + name: "Test Deployer Officer", + contact: "deployer@parentco.example", + title: "Founder" + }); + string[] memory globalValues = new string[](8); + globalValues[0] = "Test Founder"; + globalValues[1] = "Test ParentCo Enterprise"; + globalValues[2] = "Test SubCo SPV 1"; + globalValues[3] = "series limited liability company"; + globalValues[4] = "Nevada"; // != companyJurisdiction arg below + globalValues[5] = "subco@parentco.example"; + globalValues[6] = "TSC1"; + globalValues[7] = "Test SubCo One"; + string[] memory partyValues = new string[](2); + partyValues[0] = "Test Deployer Officer"; + partyValues[1] = "deployer@parentco.example"; + + vm.expectRevert(ParentCoFactory.GlobalOrPartyValuesMismatch.selector); + parentCoFactory.deployCorpContractFor( + subCorpSalt, "Test SubCo SPV 1", "series limited liability company", + "Delaware", "subco@parentco.example", "binding arbitration", + subCorpPayable, subOfficer, segCoTemplateId, boardConsentTemplateId, + globalValues, partyValues, hex"", subCorpOfficer + ); + } + + function test_deploySubCorp_revertIfContactDetailsMismatch() public { + uint256 subCorpSalt = uint256(keccak256("ParentCo.Test2.SubCorp.v1")); + CompanyOfficer memory subOfficer = CompanyOfficer({ + eoa: subCorpOfficer, + name: "Test Deployer Officer", + contact: "deployer@parentco.example", + title: "Founder" + }); + string[] memory globalValues = new string[](8); + globalValues[0] = "Test Founder"; + globalValues[1] = "Test ParentCo Enterprise"; + globalValues[2] = "Test SubCo SPV 1"; + globalValues[3] = "series limited liability company"; + globalValues[4] = "Delaware"; + globalValues[5] = "wrong@contact.com"; // != companyContactDetails arg below + globalValues[6] = "TSC1"; + globalValues[7] = "Test SubCo One"; + string[] memory partyValues = new string[](2); + partyValues[0] = "Test Deployer Officer"; + partyValues[1] = "deployer@parentco.example"; + + vm.expectRevert(ParentCoFactory.GlobalOrPartyValuesMismatch.selector); + parentCoFactory.deployCorpContractFor( + subCorpSalt, "Test SubCo SPV 1", "series limited liability company", + "Delaware", "subco@parentco.example", "binding arbitration", + subCorpPayable, subOfficer, segCoTemplateId, boardConsentTemplateId, + globalValues, partyValues, hex"", subCorpOfficer + ); + } + + function test_deploySubCorp_revertIfOfficerEoaMismatch() public { + uint256 subCorpSalt = uint256(keccak256("ParentCo.Test2.SubCorp.v1")); + CompanyOfficer memory subOfficer = CompanyOfficer({ + eoa: address(1), // != deployer arg below + name: "Test Deployer Officer", + contact: "deployer@parentco.example", + title: "Founder" + }); + string[] memory globalValues = new string[](8); + globalValues[0] = "Test Founder"; + globalValues[1] = "Test ParentCo Enterprise"; + globalValues[2] = "Test SubCo SPV 1"; + globalValues[3] = "series limited liability company"; + globalValues[4] = "Delaware"; + globalValues[5] = "subco@parentco.example"; + globalValues[6] = "TSC1"; + globalValues[7] = "Test SubCo One"; + string[] memory partyValues = new string[](2); + partyValues[0] = "Test Deployer Officer"; + partyValues[1] = "deployer@parentco.example"; + + vm.expectRevert(ParentCoFactory.OfficerValuesMismatch.selector); + parentCoFactory.deployCorpContractFor( + subCorpSalt, "Test SubCo SPV 1", "series limited liability company", + "Delaware", "subco@parentco.example", "binding arbitration", + subCorpPayable, subOfficer, segCoTemplateId, boardConsentTemplateId, + globalValues, partyValues, hex"", subCorpOfficer + ); + } + + function _createParentCoSignatureHash(uint256 privKey, string memory name, string memory contact) internal returns (bytes memory) { + bytes32 escrowDigest = parentCoFactory.escrowAuthorizationHash(name, contact); + (uint8 v, bytes32 r_, bytes32 s) = vm.sign(privKey, escrowDigest); + return abi.encodePacked(r_, s, v); + } +} diff --git a/test/DeployParentCoFactoryAcceptance.t.sol b/test/DeployParentCoFactoryAcceptance.t.sol new file mode 100644 index 00000000..e2c2566b --- /dev/null +++ b/test/DeployParentCoFactoryAcceptance.t.sol @@ -0,0 +1,231 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.28; + +import {Test, Vm, console2} from "forge-std/Test.sol"; +import {CyberAgreementUtils} from "./libs/CyberAgreementUtils.sol"; +import {DeployParentCoFactoryScript} from "../script/deploy-parentco-factory.s.sol"; +import {GnosisTransaction} from "../script/libs/safe.sol"; +import {DeploymentConstants} from "../script/libs/DeploymentConstants.sol"; +import {CyberAgreementRegistry} from "../src/CyberAgreementRegistry.sol"; +import {ParentCoFactory} from "../src/ParentCoFactory.sol"; +import {CyberCorp} from "../src/CyberCorp.sol"; +import {CompanyOfficer} from "../src/CyberCorpConstants.sol"; +import {BorgAuth} from "../src/libs/auth.sol"; + +contract DeployParentCoFactoryAcceptanceTest is Test { + DeploymentConstants.CoreDeployment coreDeployment; + DeploymentConstants.UmiaDeployment umiaDeployment; + DeploymentConstants.Deps deps; + + CyberAgreementRegistry registry; + + ParentCoFactory parentCoFactory; + CompanyOfficer[] parentCoOfficers; + + address parentCorpTestOfficer; + uint256 parentCorpTestOfficerPrivKey; + + address subCorpPayable; + address subCorpOfficer; + uint256 subCorpOfficerPrivKey; + + /// @notice Assumes all contracts are deployed + function setUp() public { + coreDeployment = DeploymentConstants.coreV2(block.chainid); + umiaDeployment = DeploymentConstants.umia(block.chainid); + deps = DeploymentConstants.deps(block.chainid); + + (parentCorpTestOfficer, parentCorpTestOfficerPrivKey) = makeAddrAndKey("parentCorpTestOfficer"); + + (subCorpPayable, ) = makeAddrAndKey("subCorpPayable"); + (subCorpOfficer, subCorpOfficerPrivKey) = makeAddrAndKey("subCorpOfficer"); + + registry = CyberAgreementRegistry(coreDeployment.cyberAgreementRegistry); + + parentCoFactory = ParentCoFactory(umiaDeployment.parentCoFactory); + +// // Simulate MetaLeX creating the templates +// +// GnosisTransaction[] memory safeTxs = new GnosisTransaction[](2); +// safeTxs[0] = GnosisTransaction({ +// to: 0xa9E808B8eCBB60Bb19abF026B5b863215BC4c134, +// value: 0, +// data: hex"55f0c0c6d9e0fbb89f8e4e973f05d6b40b6a41e3a9af845b604e9acc7aa4f2a0c37009d800000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000004800000000000000000000000000000000000000000000000000000000000000020554d494120464f554e4445522f4f50455241544f52204c4547414c205041434b0000000000000000000000000000000000000000000000000000000000000042697066733a2f2f626166796265696371676e7a6161347a6d376e6c6b726e757566377864796875676869736e6f756765726a6c696577796e6a746461696f69763565000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000002c0000000000000000000000000000000000000000000000000000000000000000b666f756e6465724e616d65000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e656e74657270726973654e616d65000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b636f6d70616e794e616d65000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b636f6d70616e79547970650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013636f6d70616e794a7572697364696374696f6e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000015636f6d70616e79436f6e7461637444657461696c730000000000000000000000000000000000000000000000000000000000000000000000000000000000000b746f6b656e53796d626f6c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009746f6b656e4e616d65000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000046e616d6500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e636f6e7461637444657461696c73000000000000000000000000000000000000" +// }); +// safeTxs[1] = GnosisTransaction({ +// to: 0xa9E808B8eCBB60Bb19abF026B5b863215BC4c134, +// value: 0, +// data: hex"55f0c0c693ac1365e39b1d8237c84cf969b752ffbb717f7d8144eb47562b4060bcd91c3000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000004c00000000000000000000000000000000000000000000000000000000000000052414354494f4e20425920554e414e494d4f5553205752495454454e20434f4e53454e54204f462054484520424f415244204f46204449524543544f5253204f4620554d4941204c41554e434845522053504300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000042697066733a2f2f6261666b7265696372363571626966347670726e74356c326f767a716a62797777676d6e706c7933737a376e656236717a6a6262326e6a63727734000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000018000000000000000000000000000000000000000000000000000000000000001c000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000240000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000002c0000000000000000000000000000000000000000000000000000000000000000b666f756e6465724e616d65000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e656e74657270726973654e616d65000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b636f6d70616e794e616d65000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b636f6d70616e79547970650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000013636f6d70616e794a7572697364696374696f6e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000015636f6d70616e79436f6e7461637444657461696c730000000000000000000000000000000000000000000000000000000000000000000000000000000000000b746f6b656e53796d626f6c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000009746f6b656e4e616d65000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000046e616d6500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e636f6e7461637444657461696c73000000000000000000000000000000000000" +// }); +// +// for (uint256 i = 0; i < safeTxs.length; i++) { +// vm.prank(coreDeployment.metalexSafe); +// (bool success,) = safeTxs[i].to.call{value: safeTxs[i].value}(safeTxs[i].data); +// vm.assertTrue(success); +// } + +// // Simulate adding a test parent corp officer +// parentCoOfficers.push(CompanyOfficer({ +// eoa: parentCorpTestOfficer, +// name: "Test ParentCo Officer", +// contact: "test@parent.com", +// title: "Director" +// })); + + for(uint256 i = 0; true ; i++) { + try parentCoFactory.parentCoOfficers(i) returns (address eoa, string memory name, string memory contact, string memory title) { + parentCoOfficers.push(CompanyOfficer({ + eoa: eoa, + name: name, + contact: contact, + title: title + })); + } catch { + break; + } + } + +// // Simulate adding the test parent corp officer and have him sign the escrowed signature +// +// vm.prank(parentCoOfficers[1].eoa); +// parentCoFactory.setParentCoOfficers(parentCoOfficers); +// +// bytes memory escrowSig = _createParentCoSignatureHash( +// parentCorpTestOfficerPrivKey, +// parentCoOfficers[0].name, +// parentCoOfficers[0].contact +// ); +// +// vm.prank(parentCoOfficers[1].eoa); +// parentCoFactory.setParentCoSignatureHash(escrowSig); + } + + function test_deploySubCorp() public { + uint256 subCorpSalt = uint256(keccak256("ParentCo.Test2.SubCorp.v1")); + string memory subCompanyName = "Test SubCo SPV 1"; + string memory subCompanyType = "series limited liability company"; + string memory subCompanyJurisdiction = "Delaware"; + string memory subCompanyContact = "subco@parentco.example"; + string memory subDisputeResolution = "binding arbitration"; + + string[] memory globalValues = new string[](8); + globalValues[0] = "Test Founder"; + globalValues[1] = "Test ParentCo Enterprise"; + globalValues[2] = subCompanyName; + globalValues[3] = subCompanyType; + globalValues[4] = subCompanyJurisdiction; + globalValues[5] = subCompanyContact; + globalValues[6] = "TSC1"; + globalValues[7] = "Test SubCo One"; + + string[] memory partyValues = new string[](2); + partyValues[0] = "Test Deployer Officer"; + partyValues[1] = "deployer@parentco.example"; + + CompanyOfficer memory subOfficer = CompanyOfficer({ + eoa: subCorpOfficer, + name: partyValues[0], + contact: partyValues[1], + title: "Founder" + }); + + // With officer2 as escrow signer, party[0] = parentCoOfficer2 + address[] memory segCoParties = new address[](2); + segCoParties[0] = parentCoOfficers[0].eoa; + segCoParties[1] = subCorpOfficer; + bytes32 agreementId = keccak256(abi.encode(umiaDeployment.segCoTemplateId, subCorpSalt, globalValues, segCoParties)); + + ( + string memory legalContractUri, + , + string[] memory templateGlobalFields, + string[] memory templatePartyFields + ) = registry.getTemplateDetails(umiaDeployment.segCoTemplateId); + + bytes memory agreementSig = CyberAgreementUtils.signAgreementTypedData( + vm, + registry.DOMAIN_SEPARATOR(), + registry.SIGNATUREDATA_TYPEHASH(), + agreementId, + legalContractUri, + templateGlobalFields, + templatePartyFields, + globalValues, + partyValues, + subCorpOfficerPrivKey + ); + + (address subCorp,,,,,,,) = parentCoFactory.deployCorpContractFor( + subCorpSalt, + subCompanyName, + subCompanyType, + subCompanyJurisdiction, + subCompanyContact, + subDisputeResolution, + subCorpPayable, + subOfficer, + umiaDeployment.segCoTemplateId, + umiaDeployment.boardConsentTemplateId, + globalValues, + partyValues, + agreementSig, + subCorpOfficer + ); + + assertTrue(subCorp != address(0)); + assertTrue(registry.hasSigned(agreementId, parentCoOfficers[0].eoa)); + assertTrue(registry.hasSigned(agreementId, subCorpOfficer)); + } + + function test_realCalldata() public { + // Skip test if not on Base Sepolia + if(block.chainid != DeploymentConstants.BASE_SEPOLIA) { + console2.log("skipping unsupported chain ID: %d ...", block.chainid); + return; + } + + // https://sepolia.basescan.org/tx/0xd479c8e723bd1999945477fdb7fe9345f488c248b3e02731c050e442aba5d827 + vm.rollFork(40820661); // one block before the real tx + + string[] memory globalValues = new string[](8); + globalValues[0] = "alice"; + globalValues[1] = "TestCorp"; + globalValues[2] = "TestCorp S.P."; + globalValues[3] = "Segregated Portfolio of Segregated Portfolio Company"; + globalValues[4] = "Cayman Islands"; + + string[] memory partyValues = new string[](2); + partyValues[0] = "alice"; + partyValues[1] = "123"; + + (address subCorp,,,,,,,) = parentCoFactory.deployCorpContractFor({ + salt: 1777409598760, + companyName: "TestCorp S.P.", + companyType: "Segregated Portfolio of Segregated Portfolio Company", + companyJurisdiction: "Cayman Islands", + companyContactDetails: "", + defaultDisputeResolution: "", + _companyPayable: 0x0000000000000000000000000000000000000000, + _officer: CompanyOfficer({ + eoa: 0xd0c3D2b2D19854036a22aFB386920854A67DFC10, + name: "alice", + contact: "123", + title: "Operator" + }), + _segCoTemplateId: 0xb6da5c8e53767592c0eeb4c5c0d77eae7e1e2e795190e7237d837b3fbc98ed75, + _boardConsentTempateId: 0xc02175e98621a996529fb751b30e0b7a8344ece3b00f46a29c1e904c9da87a46, + _globalValues: globalValues, + _partyValues: partyValues, + signature: hex"cf3cd32652fc7998ac55b7c2f457a39519093d28bd0a42c8773f2755ea0ace7831eeb19883a81d501889402f28924a526212155922498b71ae5a109590705b7d1b", + deployer: 0xd0c3D2b2D19854036a22aFB386920854A67DFC10 + }); + + assertTrue(subCorp != address(0)); + } + + function _createParentCoSignatureHash(uint256 privKey, string memory name, string memory contact) internal returns (bytes memory) { + bytes32 escrowDigest = parentCoFactory.escrowAuthorizationHash(name, contact); + (uint8 v, bytes32 r_, bytes32 s) = vm.sign(privKey, escrowDigest); + return abi.encodePacked(r_, s, v); + } +}