From 8d12549b92a07e7b1eaf8e92d3623185b3538f28 Mon Sep 17 00:00:00 2001 From: merisman <6125373+merisman@users.noreply.github.com> Date: Sun, 6 Apr 2025 11:31:00 -0600 Subject: [PATCH 01/18] Updating Close Flow, condition checks --- src/DealManager.sol | 16 ++++++++++++++++ src/libs/LexScroWLite.sol | 31 ++++++++++++++++++++++++++++--- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/src/DealManager.sol b/src/DealManager.sol index 69639b1..e442b10 100644 --- a/src/DealManager.sol +++ b/src/DealManager.sol @@ -36,6 +36,8 @@ contract DealManager is Initializable, UUPSUpgradeable, BorgAuthACL, LexScroWLit mapping(bytes32 => string[]) public counterPartyValues; + error AgreementConditionsNotMet(); + /// @custom:oz-upgrades-unsafe-allow constructor constructor() { } @@ -150,6 +152,18 @@ contract DealManager is Initializable, UUPSUpgradeable, BorgAuthACL, LexScroWLit return agreementId; } + function signDealAndPay( + bytes32 agreementId, + address signer, + bytes memory signature, + string[] memory partyValues, + bool _fillUnallocated, + string memory name + ) public { + ICyberDealRegistry(DEAL_REGISTRY).signContractFor(signer, agreementId, partyValues, signature, _fillUnallocated); + updateEscrow(agreementId, msg.sender); + } + function proposeAndSignClosedDeal( address _certPrinterAddress, uint256 _certId, @@ -180,9 +194,11 @@ contract DealManager is Initializable, UUPSUpgradeable, BorgAuthACL, LexScroWLit if (keccak256(abi.encode(counterPartyCheck)) != keccak256(abi.encode(partyValues))) revert CounterPartyValueMismatch(); } + if(!conditionCheck(agreementId)) revert AgreementConditionsNotMet(); ICyberDealRegistry(DEAL_REGISTRY).signContractFor(signer, agreementId, partyValues, signature, _fillUnallocated); updateEscrow(agreementId, msg.sender); + handleCounterPartyPayment(agreementId); finalizeDeal(agreementId, name); diff --git a/src/libs/LexScroWLite.sol b/src/libs/LexScroWLite.sol index 474513e..6f3cfb6 100644 --- a/src/libs/LexScroWLite.sol +++ b/src/libs/LexScroWLite.sol @@ -30,6 +30,7 @@ abstract contract LexScroWLite is Initializable { enum EscrowStatus { PENDING, + PAID, FINALIZED, VOIDED } @@ -48,6 +49,9 @@ abstract contract LexScroWLite is Initializable { mapping(bytes32 => ICondition[]) public conditionsByEscrow; error DealExpired(); + error EscrowNotPending(); + error EscrowNotPaid(); + error CounterPartyNotSet(); constructor() { } @@ -67,19 +71,40 @@ abstract contract LexScroWLite is Initializable { escrows[agreementId].counterParty = counterParty; } + function handleCounterPartyPayment(bytes32 agreementId) public { + Escrow storage deal = escrows[agreementId]; + if(deal.status != EscrowStatus.PENDING) revert EscrowNotPending(); + if(deal.counterParty == address(0)) revert CounterPartyNotSet(); + + for(uint256 i = 0; i < deal.buyerAssets.length; i++) { + if(deal.buyerAssets[i].tokenType == TokenType.ERC20) { + IERC20(deal.buyerAssets[i].tokenAddress).transferFrom(deal.counterParty, address(this), deal.buyerAssets[i].amount); + } + else if(deal.buyerAssets[i].tokenType == TokenType.ERC721) { + IERC721(deal.buyerAssets[i].tokenAddress).safeTransferFrom(deal.counterParty, address(this), deal.buyerAssets[i].tokenId); + } + else if(deal.buyerAssets[i].tokenType == TokenType.ERC1155) { + IERC1155(deal.buyerAssets[i].tokenAddress).safeTransferFrom(deal.counterParty, address(this), deal.buyerAssets[i].tokenId, deal.buyerAssets[i].amount, ""); + } + } + + deal.status = EscrowStatus.PAID; + } + function finalizeDeal(bytes32 agreementId, string memory buyerName) public { Escrow storage deal = escrows[agreementId]; if(block.timestamp > deal.expiry) revert DealExpired(); + if(deal.status != EscrowStatus.PAID) revert EscrowNotPaid(); for(uint256 i = 0; i < deal.buyerAssets.length; i++) { if(deal.buyerAssets[i].tokenType == TokenType.ERC20) { - IERC20(deal.buyerAssets[i].tokenAddress).transferFrom(deal.counterParty, ICyberCorp(CORP).companyPayable(), deal.buyerAssets[i].amount); + IERC20(deal.buyerAssets[i].tokenAddress).transfer(ICyberCorp(CORP).companyPayable(), deal.buyerAssets[i].amount); } else if(deal.buyerAssets[i].tokenType == TokenType.ERC721) { - IERC721(deal.buyerAssets[i].tokenAddress).safeTransferFrom(deal.counterParty, ICyberCorp(CORP).companyPayable(), deal.buyerAssets[i].tokenId); + IERC721(deal.buyerAssets[i].tokenAddress).safeTransferFrom(address(this), ICyberCorp(CORP).companyPayable(), deal.buyerAssets[i].tokenId); } else if(deal.buyerAssets[i].tokenType == TokenType.ERC1155) { - IERC1155(deal.buyerAssets[i].tokenAddress).safeTransferFrom(deal.counterParty, ICyberCorp(CORP).companyPayable(), deal.buyerAssets[i].tokenId, deal.buyerAssets[i].amount, ""); + IERC1155(deal.buyerAssets[i].tokenAddress).safeTransferFrom(address(this), ICyberCorp(CORP).companyPayable(), deal.buyerAssets[i].tokenId, deal.buyerAssets[i].amount, ""); } } From 3961384902c9e58040221a82a52d2f2f8757c9f0 Mon Sep 17 00:00:00 2001 From: merisman <6125373+merisman@users.noreply.github.com> Date: Sun, 6 Apr 2025 17:27:43 -0600 Subject: [PATCH 02/18] More delayed close methods --- src/CyberDealRegistry.sol | 20 ++++++++++++++++++++ src/DealManager.sol | 19 ++++++++++++++++--- src/interfaces/IDealManager.sol | 18 ++++++++++++++++++ src/libs/LexScroWLite.sol | 3 ++- test/CyberCorpTest.t.sol | 4 ++-- 5 files changed, 58 insertions(+), 6 deletions(-) diff --git a/src/CyberDealRegistry.sol b/src/CyberDealRegistry.sol index 69858c3..6d992ba 100644 --- a/src/CyberDealRegistry.sol +++ b/src/CyberDealRegistry.sol @@ -31,6 +31,7 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { mapping(address => uint256) signedAt; // Timestamp when each party signed (0 if unsigned) uint256 numSignatures; // Number of parties who have signed bytes32 transactionHash; // Hash of the transaction that created this contract + bool isVoided; // Whether the contract has been voided } // This data is what is signed by each party @@ -318,6 +319,25 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { } } + function voidContractFor(bytes32 contractId, address party, bytes calldata signature) public { + //make sure the party is a party to the contract + if(!isParty(contractId, party)) revert NotAParty(); + + //verify the signature + if (!_verifySignature(party, SignatureData({ + contractId: contractId, + legalContractUri: templates[agreements[contractId].templateId].legalContractUri, + globalFields: templates[agreements[contractId].templateId].globalFields, + partyFields: templates[agreements[contractId].templateId].partyFields, + globalValues: agreements[contractId].globalValues, + partyValues: new string[](0) + }), signature)) revert SignatureVerificationFailed(); + + AgreementData storage agreementData = agreements[contractId]; + if(agreementData.isVoided) revert ContractAlreadyVoided(); + agreementData.isVoided = true; + } + function getParties( bytes32 contractId ) external view returns (address[] memory) { diff --git a/src/DealManager.sol b/src/DealManager.sol index e442b10..a0829ea 100644 --- a/src/DealManager.sol +++ b/src/DealManager.sol @@ -187,7 +187,7 @@ contract DealManager is Initializable, UUPSUpgradeable, BorgAuthACL, LexScroWLit ICyberDealRegistry(DEAL_REGISTRY).signContractFor(proposer, agreementId, partyValues, signature, false); } - function finalizeDeal(address signer, bytes32 agreementId, string[] memory partyValues, bytes memory signature, bool _fillUnallocated, string memory name) public { + function signAndFinalizeDeal(address signer, bytes32 agreementId, string[] memory partyValues, bytes memory signature, bool _fillUnallocated, string memory name) public { string[] memory counterPartyCheck = counterPartyValues[agreementId]; if(counterPartyCheck.length > 0) { @@ -196,7 +196,9 @@ contract DealManager is Initializable, UUPSUpgradeable, BorgAuthACL, LexScroWLit if(!conditionCheck(agreementId)) revert AgreementConditionsNotMet(); - ICyberDealRegistry(DEAL_REGISTRY).signContractFor(signer, agreementId, partyValues, signature, _fillUnallocated); + if(!ICyberDealRegistry(DEAL_REGISTRY).hasSigned(agreementId, signer)) + ICyberDealRegistry(DEAL_REGISTRY).signContractFor(signer, agreementId, partyValues, signature, _fillUnallocated); + updateEscrow(agreementId, msg.sender); handleCounterPartyPayment(agreementId); finalizeDeal(agreementId, name); @@ -211,7 +213,7 @@ contract DealManager is Initializable, UUPSUpgradeable, BorgAuthACL, LexScroWLit ); } - function voidExpiredDeal(bytes32 agreementId) public { + function voidExpiredDeal(bytes32 agreementId, bytes memory signature) public { Escrow storage deal = escrows[agreementId]; for(uint256 i = 0; i < deal.corpAssets.length; i++) { if(deal.corpAssets[i].tokenType == TokenType.ERC721) { @@ -219,8 +221,19 @@ contract DealManager is Initializable, UUPSUpgradeable, BorgAuthACL, LexScroWLit } } voidEscrow(agreementId); + ICyberDealRegistry(DEAL_REGISTRY).voidContractFor(agreementId, msg.sender, signature); } + function revokeDeal(bytes32 agreementId) public { + if(escrows[agreementId].status == EscrowStatus.PENDING) + ICyberDealRegistry(DEAL_REGISTRY).voidContract(agreementId); + else + revert DealNotPending(); + ICyberDealRegistry(DEAL_REGISTRY).voidContract(agreementId); + } + + function + /*function addCondition(Logic _op, address _condition) public onlyOwner { _addCondition(_op, _condition); } diff --git a/src/interfaces/IDealManager.sol b/src/interfaces/IDealManager.sol index 4eed670..5e49616 100644 --- a/src/interfaces/IDealManager.sol +++ b/src/interfaces/IDealManager.sol @@ -32,6 +32,15 @@ interface IDealManager { uint256 expiry ) external returns (bytes32 agreementId); + function signDealAndPay( + address signer, + bytes32 _agreementId, + string[] memory _partyValues, + bytes memory signature, + bool _fillUnallocated, + string memory name + ) external; + function proposeAndSignClosedDeal( address _certPrinterAddress, uint256 _certId, @@ -58,6 +67,15 @@ interface IDealManager { string memory buyerName ) external; + function signAndFinalizeDeal( + address signer, + bytes32 _agreementId, + string[] memory _partyValues, + bytes memory signature, + bool _fillUnallocated, + string memory buyerName + ) external; + function voidExpiredDeal( bytes32 _agreementId ) external; diff --git a/src/libs/LexScroWLite.sol b/src/libs/LexScroWLite.sol index 6f3cfb6..d4f807e 100644 --- a/src/libs/LexScroWLite.sol +++ b/src/libs/LexScroWLite.sol @@ -8,9 +8,10 @@ import "../interfaces/ICyberCorp.sol"; import "../interfaces/ICyberDealRegistry.sol"; import "../interfaces/ICyberCertPrinter.sol"; import "../interfaces/ICondition.sol"; +import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; -abstract contract LexScroWLite is Initializable { +abstract contract LexScroWLite is Initializable, ReentrancyGuard { address public CORP; ICyberDealRegistry public DEAL_REGISTRY; diff --git a/test/CyberCorpTest.t.sol b/test/CyberCorpTest.t.sol index c4d65aa..08984c0 100644 --- a/test/CyberCorpTest.t.sol +++ b/test/CyberCorpTest.t.sol @@ -240,7 +240,7 @@ contract CyberCorpTest is Test { newPartyPk ); - dealManager.finalizeDeal( + dealManager.signAndFinalizeDeal( newPartyAddr, contractId, counterPartyValues, @@ -506,7 +506,7 @@ contract CyberCorpTest is Test { address(dealManager), _paymentAmount ); - dealManager.finalizeDeal( + dealManager.signAndFinalizeDeal( newPartyAddr, id, partyValuesB, From 0d1e81a6ea7c85ca89cb7ff5153ed5bfa3a30bcd Mon Sep 17 00:00:00 2001 From: merisman <6125373+merisman@users.noreply.github.com> Date: Mon, 7 Apr 2025 20:29:43 -0400 Subject: [PATCH 03/18] Updating cyber registry for handling the different void/cancel cases --- src/CyberDealRegistry.sol | 25 ++++++++++++++++++++----- src/DealManager.sol | 18 ++++++++++++------ src/interfaces/ICyberDealRegistry.sol | 9 +++++++++ src/interfaces/IDealManager.sol | 16 +++++++++++++++- src/libs/LexScroWLite.sol | 9 +++++++++ test/CyberCorpTest.t.sol | 2 +- 6 files changed, 66 insertions(+), 13 deletions(-) diff --git a/src/CyberDealRegistry.sol b/src/CyberDealRegistry.sol index 6d992ba..c625c02 100644 --- a/src/CyberDealRegistry.sol +++ b/src/CyberDealRegistry.sol @@ -31,7 +31,8 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { mapping(address => uint256) signedAt; // Timestamp when each party signed (0 if unsigned) uint256 numSignatures; // Number of parties who have signed bytes32 transactionHash; // Hash of the transaction that created this contract - bool isVoided; // Whether the contract has been voided + address finalizer; + bool finalized; } // This data is what is signed by each party @@ -56,6 +57,10 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { // A mapping connecting an address to all the agreements they are a party to mapping(address => bytes32[]) public agreementsForParty; + mapping(bytes32 => address[]) public voidedBy; + + mapping(bytes32 => uint256) public expiry; + event TemplateCreated( bytes32 indexed templateId, string indexed title, @@ -91,6 +96,7 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { error TitleEmpty(); error InvalidPartyCount(); error ClosedAgreementPartyValueMismatch(); + error ContractAlreadyVoided(); /// @custom:oz-upgrades-unsafe-allow constructor constructor() { @@ -323,6 +329,8 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { //make sure the party is a party to the contract if(!isParty(contractId, party)) revert NotAParty(); + AgreementData storage agreementData = agreements[contractId]; + //verify the signature if (!_verifySignature(party, SignatureData({ contractId: contractId, @@ -330,12 +338,15 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { globalFields: templates[agreements[contractId].templateId].globalFields, partyFields: templates[agreements[contractId].templateId].partyFields, globalValues: agreements[contractId].globalValues, - partyValues: new string[](0) + partyValues: agreementData.partyValues[party] }), signature)) revert SignatureVerificationFailed(); + + for (uint256 i = 0; i < voidedBy[contractId].length; i++) { + if(voidedBy[contractId][i] == party) revert ContractAlreadyVoided(); + } + + voidedBy[contractId].push(party); - AgreementData storage agreementData = agreements[contractId]; - if(agreementData.isVoided) revert ContractAlreadyVoided(); - agreementData.isVoided = true; } function getParties( @@ -640,6 +651,10 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { return string(bstr); } + function isVoided(bytes32 contractId) external view returns (bool) { + return ((voidedBy[contractId].length == agreements[contractId].numSignatures) || (voidedBy[contractId].length == 1 && expiry[contractId] < block.timestamp)); + } + function _authorizeUpgrade(address newImplementation) internal virtual override onlyOwner {} // Add a function to get the transaction hash diff --git a/src/DealManager.sol b/src/DealManager.sol index a0829ea..93a742c 100644 --- a/src/DealManager.sol +++ b/src/DealManager.sol @@ -37,6 +37,8 @@ contract DealManager is Initializable, UUPSUpgradeable, BorgAuthACL, LexScroWLit mapping(bytes32 => string[]) public counterPartyValues; error AgreementConditionsNotMet(); + error DealNotPending(); + /// @custom:oz-upgrades-unsafe-allow constructor constructor() { @@ -213,7 +215,7 @@ contract DealManager is Initializable, UUPSUpgradeable, BorgAuthACL, LexScroWLit ); } - function voidExpiredDeal(bytes32 agreementId, bytes memory signature) public { + function voidExpiredDeal(bytes32 agreementId, address signer, bytes memory signature) public { Escrow storage deal = escrows[agreementId]; for(uint256 i = 0; i < deal.corpAssets.length; i++) { if(deal.corpAssets[i].tokenType == TokenType.ERC721) { @@ -221,18 +223,22 @@ contract DealManager is Initializable, UUPSUpgradeable, BorgAuthACL, LexScroWLit } } voidEscrow(agreementId); - ICyberDealRegistry(DEAL_REGISTRY).voidContractFor(agreementId, msg.sender, signature); + ICyberDealRegistry(DEAL_REGISTRY).voidContractFor(agreementId, signer, signature); } - function revokeDeal(bytes32 agreementId) public { + function revokeDeal(bytes32 agreementId, address signer, bytes memory signature) public { if(escrows[agreementId].status == EscrowStatus.PENDING) - ICyberDealRegistry(DEAL_REGISTRY).voidContract(agreementId); + ICyberDealRegistry(DEAL_REGISTRY).voidContractFor(agreementId, signer, signature); else revert DealNotPending(); - ICyberDealRegistry(DEAL_REGISTRY).voidContract(agreementId); + ICyberDealRegistry(DEAL_REGISTRY).voidContractFor(agreementId, signer, signature); } - function + function signToVoid(bytes32 agreementId, address signer, bytes memory signature) public { + ICyberDealRegistry(DEAL_REGISTRY).voidContractFor(agreementId, signer, signature); + if(ICyberDealRegistry(DEAL_REGISTRY).isVoided(agreementId) && escrows[agreementId].status == EscrowStatus.PAID) + voidEscrow(agreementId); + } /*function addCondition(Logic _op, address _condition) public onlyOwner { _addCondition(_op, _condition); diff --git a/src/interfaces/ICyberDealRegistry.sol b/src/interfaces/ICyberDealRegistry.sol index 18f232a..6df5c65 100644 --- a/src/interfaces/ICyberDealRegistry.sol +++ b/src/interfaces/ICyberDealRegistry.sol @@ -77,6 +77,13 @@ interface ICyberDealRegistry { bool fillUnallocated // to fill a 0 address or not ) external; + //function voidContractFor(bytes32 contractId, address party, bytes calldata signature) public { + function voidContractFor( + bytes32 contractId, + address party, + bytes calldata signature + ) external; + function getParties(bytes32 contractId) external view returns (address[] memory); function hasSigned(bytes32 contractId, address signer) external view returns (bool); @@ -120,6 +127,8 @@ interface ICyberDealRegistry { address signer ) external view returns (string[] memory signerValues); + function isVoided(bytes32 contractId) external view returns (bool); + function getAgreementsForParty(address party) external view returns (bytes32[] memory); function getContractJson(bytes32 contractId) external view returns (string memory); diff --git a/src/interfaces/IDealManager.sol b/src/interfaces/IDealManager.sol index 5e49616..47cda9e 100644 --- a/src/interfaces/IDealManager.sol +++ b/src/interfaces/IDealManager.sol @@ -77,7 +77,21 @@ interface IDealManager { ) external; function voidExpiredDeal( - bytes32 _agreementId + bytes32 _agreementId, + address signer, + bytes memory signature + ) external; + + function revokeDeal( + bytes32 _agreementId, + address signer, + bytes memory signature + ) external; + + function signToVoid( + bytes32 _agreementId, + address signer, + bytes memory signature ) external; function initialize( diff --git a/src/libs/LexScroWLite.sol b/src/libs/LexScroWLite.sol index d4f807e..4cb0301 100644 --- a/src/libs/LexScroWLite.sol +++ b/src/libs/LexScroWLite.sol @@ -141,6 +141,15 @@ abstract contract LexScroWLite is Initializable, ReentrancyGuard { return true; } + function voidAndRefund(bytes32 agreementId ) public { + if(escrows[agreementId].status != EscrowStatus.PAID) revert EscrowNotPaid(); + voidEscrow(agreementId); + for(uint256 i = 0; i < escrows[agreementId].corpAssets.length; i++) { + IERC20(escrows[agreementId].corpAssets[i].tokenAddress).transfer(escrows[agreementId].counterParty, escrows[agreementId].corpAssets[i].amount); + } + + } + function voidEscrow(bytes32 agreementId) internal { escrows[agreementId].status = EscrowStatus.VOIDED; } diff --git a/test/CyberCorpTest.t.sol b/test/CyberCorpTest.t.sol index 08984c0..3710906 100644 --- a/test/CyberCorpTest.t.sol +++ b/test/CyberCorpTest.t.sol @@ -318,7 +318,7 @@ contract CyberCorpTest is Test { //wait for 1000000 blocks vm.warp(block.timestamp + 1000001); vm.startPrank(testAddress); - IDealManager(dealManagerAddr).voidExpiredDeal(contractId); + IDealManager(dealManagerAddr).voidExpiredDeal(contractId, testAddress, signature); vm.stopPrank(); } From f8c06b32a3bf4e5f4ab4077b3dd619232064f909 Mon Sep 17 00:00:00 2001 From: merisman <6125373+merisman@users.noreply.github.com> Date: Mon, 7 Apr 2025 23:01:37 -0400 Subject: [PATCH 04/18] Finalizing CyberCerts, Adding more state checks --- src/CyberDealRegistry.sol | 34 ++++++++++++++++++++++---- src/DealManager.sol | 35 ++++++++++++++++++++------- src/interfaces/ICyberDealRegistry.sol | 12 +++++++-- src/libs/LexScroWLite.sol | 24 ++++++++++++------ test/CyberCorpTest.t.sol | 3 ++- 5 files changed, 83 insertions(+), 25 deletions(-) diff --git a/src/CyberDealRegistry.sol b/src/CyberDealRegistry.sol index c625c02..847be04 100644 --- a/src/CyberDealRegistry.sol +++ b/src/CyberDealRegistry.sol @@ -33,6 +33,7 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { bytes32 transactionHash; // Hash of the transaction that created this contract address finalizer; bool finalized; + bytes32 secretHash; } // This data is what is signed by each party @@ -97,6 +98,9 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { error InvalidPartyCount(); error ClosedAgreementPartyValueMismatch(); error ContractAlreadyVoided(); + error NotFinalizer(); + error ContractAlreadyFinalized(); + error ContractNotFullySigned(); /// @custom:oz-upgrades-unsafe-allow constructor constructor() { @@ -121,6 +125,11 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { "SignatureData(bytes32 contractId,string legalContractUri,string[] globalFields,string[] partyFields,string[] globalValues,string[] partyValues)" ); } + + modifier onlyFinalizer(bytes32 contractId) { + if(agreements[contractId].finalizer != msg.sender) revert NotFinalizer(); + _; + } function createTemplate( bytes32 templateId, @@ -157,7 +166,8 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { bytes32 templateId, uint256 salt, string[] memory globalValues, - address[] memory parties + address[] memory parties, + address finalizer ) external returns (bytes32 contractId) { //create hash from templateId, globalValues, and parties contractId = keccak256(abi.encode(templateId, salt, globalValues, parties)); @@ -191,6 +201,7 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { agreementData.globalValues = globalValues; agreementData.parties = parties; agreementData.transactionHash = blockhash(block.number - 1); // Store the transaction hash + agreementData.finalizer = finalizer; emit ContractCreated(contractId, templateId, parties); @@ -206,7 +217,8 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { string[] memory globalValues, address[] memory parties, string[] memory creatingPartyValues, - string[] memory counterPartyValues + string[] memory counterPartyValues, + address finalizer ) external returns (bytes32 contractId) { //create hash from templateId, globalValues, and parties contractId = keccak256(abi.encode(templateId, salt, globalValues, parties)); @@ -250,6 +262,7 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { agreementData.transactionHash = blockhash(block.number - 1); // Store the transaction hash agreementData.partyValues[parties[0]] = creatingPartyValues; agreementData.partyValues[parties[1]] = counterPartyValues; + agreementData.finalizer = finalizer; emit ContractCreated(contractId, templateId, parties); @@ -346,10 +359,17 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { } voidedBy[contractId].push(party); - } - function getParties( + function finalizeContract(bytes32 contractId) public onlyFinalizer(contractId) { + AgreementData storage agreementData = agreements[contractId]; + if(agreementData.finalized) revert ContractAlreadyFinalized(); + if(agreementData.parties.length == 0) revert ContractDoesNotExist(); + if(!allPartiesSigned(contractId)) revert ContractNotFullySigned(); + agreementData.finalized = true; + } + + function getParties( bytes32 contractId ) external view returns (address[] memory) { return agreements[contractId].parties; @@ -370,7 +390,7 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { return agreements[contractId].signedAt[signer]; } - function allPartiesSigned(bytes32 contractId) external view returns (bool) { + function allPartiesSigned(bytes32 contractId) public view returns (bool) { return agreements[contractId].numSignatures == agreements[contractId].parties.length; @@ -651,6 +671,10 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { return string(bstr); } + function isFinalized(bytes32 contractId) external view returns (bool) { + return agreements[contractId].finalized; + } + function isVoided(bytes32 contractId) external view returns (bool) { return ((voidedBy[contractId].length == agreements[contractId].numSignatures) || (voidedBy[contractId].length == 1 && expiry[contractId] < block.timestamp)); } diff --git a/src/DealManager.sol b/src/DealManager.sol index 93a742c..7351ea9 100644 --- a/src/DealManager.sol +++ b/src/DealManager.sol @@ -68,7 +68,7 @@ contract DealManager is Initializable, UUPSUpgradeable, BorgAuthACL, LexScroWLit uint256 expiry ) public onlyOwner returns (bytes32 agreementId){ IIssuanceManager(ISSUANCE_MANAGER).createCert(_certPrinterAddress, address(this), _certDetails); - agreementId = ICyberDealRegistry(DEAL_REGISTRY).createContract(_templateId, _salt, _globalValues, _parties); + agreementId = ICyberDealRegistry(DEAL_REGISTRY).createContract(_templateId, _salt, _globalValues, _parties, address(this)); Token[] memory corpAssets = new Token[](1); corpAssets[0] = Token(TokenType.ERC721, _certPrinterAddress, _certId, 1); @@ -107,7 +107,7 @@ contract DealManager is Initializable, UUPSUpgradeable, BorgAuthACL, LexScroWLit uint256 expiry ) public onlyOwner returns (bytes32 agreementId){ IIssuanceManager(ISSUANCE_MANAGER).createCert(_certPrinterAddress, address(this), _certDetails); - agreementId = ICyberDealRegistry(DEAL_REGISTRY).createClosedContract(_templateId, _salt, _globalValues, _parties, _creatingPartyValues, _counterPartyValues); + agreementId = ICyberDealRegistry(DEAL_REGISTRY).createClosedContract(_templateId, _salt, _globalValues, _parties, _creatingPartyValues, _counterPartyValues, address(this)); Token[] memory corpAssets = new Token[](1); corpAssets[0] = Token(TokenType.ERC721, _certPrinterAddress, _certId, 1); @@ -162,8 +162,13 @@ contract DealManager is Initializable, UUPSUpgradeable, BorgAuthACL, LexScroWLit bool _fillUnallocated, string memory name ) public { + if(ICyberDealRegistry(DEAL_REGISTRY).isVoided(agreementId)) revert DealVoided(); + if(ICyberDealRegistry(DEAL_REGISTRY).isFinalized(agreementId)) revert DealAlreadyFinalized(); + if(escrows[agreementId].status != EscrowStatus.PENDING) revert DealNotPending(); + ICyberDealRegistry(DEAL_REGISTRY).signContractFor(signer, agreementId, partyValues, signature, _fillUnallocated); - updateEscrow(agreementId, msg.sender); + updateEscrow(agreementId, msg.sender, name); + handleCounterPartyPayment(agreementId); } function proposeAndSignClosedDeal( @@ -190,9 +195,12 @@ contract DealManager is Initializable, UUPSUpgradeable, BorgAuthACL, LexScroWLit } function signAndFinalizeDeal(address signer, bytes32 agreementId, string[] memory partyValues, bytes memory signature, bool _fillUnallocated, string memory name) public { + if(ICyberDealRegistry(DEAL_REGISTRY).isVoided(agreementId)) revert DealVoided(); + if(ICyberDealRegistry(DEAL_REGISTRY).isFinalized(agreementId)) revert DealAlreadyFinalized(); + if(escrows[agreementId].status != EscrowStatus.PENDING) revert DealNotPending(); + string[] memory counterPartyCheck = counterPartyValues[agreementId]; - if(counterPartyCheck.length > 0) - { + if(counterPartyCheck.length > 0) { if (keccak256(abi.encode(counterPartyCheck)) != keccak256(abi.encode(partyValues))) revert CounterPartyValueMismatch(); } @@ -201,17 +209,26 @@ contract DealManager is Initializable, UUPSUpgradeable, BorgAuthACL, LexScroWLit if(!ICyberDealRegistry(DEAL_REGISTRY).hasSigned(agreementId, signer)) ICyberDealRegistry(DEAL_REGISTRY).signContractFor(signer, agreementId, partyValues, signature, _fillUnallocated); - updateEscrow(agreementId, msg.sender); + updateEscrow(agreementId, msg.sender, name); handleCounterPartyPayment(agreementId); - finalizeDeal(agreementId, name); - + finalizeDeal(agreementId); + } + function finalizeDeal(bytes32 agreementId) public { + if(ICyberDealRegistry(DEAL_REGISTRY).isVoided(agreementId)) revert DealVoided(); + if(escrows[agreementId].status != EscrowStatus.PAID) revert DealNotPaid(); + if(ICyberDealRegistry(DEAL_REGISTRY).isFinalized(agreementId)) revert DealAlreadyFinalized(); + if(!ICyberDealRegistry(DEAL_REGISTRY).allPartiesSigned(agreementId)) revert DealNotFullySigned(); + if(!conditionCheck(agreementId)) revert AgreementConditionsNotMet(); + + ICyberDealRegistry(DEAL_REGISTRY).finalizeContract(agreementId); + finalizeEscrow(agreementId); emit DealFinalized( agreementId, msg.sender, CORP, address(DEAL_REGISTRY), - _fillUnallocated + false ); } diff --git a/src/interfaces/ICyberDealRegistry.sol b/src/interfaces/ICyberDealRegistry.sol index 6df5c65..7cc915e 100644 --- a/src/interfaces/ICyberDealRegistry.sol +++ b/src/interfaces/ICyberDealRegistry.sol @@ -51,7 +51,8 @@ interface ICyberDealRegistry { bytes32 templateId, uint256 salt, string[] memory globalValues, - address[] memory parties + address[] memory parties, + address finalizer ) external returns (bytes32); function createClosedContract( @@ -60,7 +61,8 @@ interface ICyberDealRegistry { string[] memory globalValues, address[] memory parties, string[] memory creatingPartyValues, - string[] memory counterPartyValues + string[] memory counterPartyValues, + address finalizer ) external returns (bytes32); function signContract( @@ -84,6 +86,8 @@ interface ICyberDealRegistry { bytes calldata signature ) external; + function finalizeContract(bytes32 contractId) external; + function getParties(bytes32 contractId) external view returns (address[] memory); function hasSigned(bytes32 contractId, address signer) external view returns (bool); @@ -134,4 +138,8 @@ interface ICyberDealRegistry { function getContractJson(bytes32 contractId) external view returns (string memory); function getContractTransactionHash(bytes32 contractId) external view returns (bytes32); + + function isFinalized(bytes32 contractId) external view returns (bool); + + function allPartiesFinalized(bytes32 contractId) external view returns (bool); } diff --git a/src/libs/LexScroWLite.sol b/src/libs/LexScroWLite.sol index 4cb0301..cc98cb9 100644 --- a/src/libs/LexScroWLite.sol +++ b/src/libs/LexScroWLite.sol @@ -53,6 +53,12 @@ abstract contract LexScroWLite is Initializable, ReentrancyGuard { error EscrowNotPending(); error EscrowNotPaid(); error CounterPartyNotSet(); + error DealNotFullySigned(); + error DealNotFinalized(); + error DealAlreadyFinalized(); + error DealNotVoided(); + error DealNotPaid(); + error DealVoided(); constructor() { } @@ -62,17 +68,22 @@ abstract contract LexScroWLite is Initializable, ReentrancyGuard { DEAL_REGISTRY = ICyberDealRegistry(_dealRegistry); } - function createEscrow(bytes32 agreementId, address counterParty, Token[] memory corpAssets, Token[] memory buyerAssets, uint256 expiry) public { + function createEscrow(bytes32 agreementId, address counterParty, Token[] memory corpAssets, Token[] memory buyerAssets, uint256 expiry) internal { bytes memory blankSignature = abi.encodePacked(bytes32(0)); escrows[agreementId] = Escrow(agreementId, counterParty, corpAssets, buyerAssets, blankSignature, expiry, EscrowStatus.PENDING); } - function updateEscrow(bytes32 agreementId, address counterParty) public + function updateEscrow(bytes32 agreementId, address counterParty, string memory buyerName) internal { escrows[agreementId].counterParty = counterParty; + + Escrow storage deal = escrows[agreementId]; + + Endorsement memory newEndorsement = Endorsement(address(this), block.timestamp, deal.signature, address(DEAL_REGISTRY), agreementId, deal.counterParty, buyerName); + ICyberCertPrinter(deal.corpAssets[0].tokenAddress).addEndorsement(deal.corpAssets[0].tokenId, newEndorsement); } - function handleCounterPartyPayment(bytes32 agreementId) public { + function handleCounterPartyPayment(bytes32 agreementId) internal { Escrow storage deal = escrows[agreementId]; if(deal.status != EscrowStatus.PENDING) revert EscrowNotPending(); if(deal.counterParty == address(0)) revert CounterPartyNotSet(); @@ -92,7 +103,7 @@ abstract contract LexScroWLite is Initializable, ReentrancyGuard { deal.status = EscrowStatus.PAID; } - function finalizeDeal(bytes32 agreementId, string memory buyerName) public { + function finalizeEscrow(bytes32 agreementId) internal { Escrow storage deal = escrows[agreementId]; if(block.timestamp > deal.expiry) revert DealExpired(); if(deal.status != EscrowStatus.PAID) revert EscrowNotPaid(); @@ -109,9 +120,6 @@ abstract contract LexScroWLite is Initializable, ReentrancyGuard { } } - Endorsement memory newEndorsement = Endorsement(address(this), block.timestamp, deal.signature, address(DEAL_REGISTRY), agreementId, deal.counterParty, buyerName); - ICyberCertPrinter(deal.corpAssets[0].tokenAddress).addEndorsement(deal.corpAssets[0].tokenId, newEndorsement); - //transfer tokens for(uint256 i = 0; i < deal.corpAssets.length; i++) { if(deal.corpAssets[i].tokenType == TokenType.ERC20) { @@ -141,7 +149,7 @@ abstract contract LexScroWLite is Initializable, ReentrancyGuard { return true; } - function voidAndRefund(bytes32 agreementId ) public { + function voidAndRefund(bytes32 agreementId) internal { if(escrows[agreementId].status != EscrowStatus.PAID) revert EscrowNotPaid(); voidEscrow(agreementId); for(uint256 i = 0; i < escrows[agreementId].corpAssets.length; i++) { diff --git a/test/CyberCorpTest.t.sol b/test/CyberCorpTest.t.sol index 3710906..cc84196 100644 --- a/test/CyberCorpTest.t.sol +++ b/test/CyberCorpTest.t.sol @@ -353,7 +353,8 @@ contract CyberCorpTest is Test { bytes32(uint256(1)), block.timestamp, globalValues, - parties + parties, + address(testAddress) ); bytes32 contractId = keccak256( From d462c2f0350d63e2d4d3c72c075dfa39038e8cbd Mon Sep 17 00:00:00 2001 From: merisman <6125373+merisman@users.noreply.github.com> Date: Mon, 7 Apr 2025 23:06:36 -0400 Subject: [PATCH 05/18] More state checks. --- src/CyberDealRegistry.sol | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/CyberDealRegistry.sol b/src/CyberDealRegistry.sol index 847be04..fd2f034 100644 --- a/src/CyberDealRegistry.sol +++ b/src/CyberDealRegistry.sol @@ -101,6 +101,7 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { error NotFinalizer(); error ContractAlreadyFinalized(); error ContractNotFullySigned(); + error ContractExpired(); /// @custom:oz-upgrades-unsafe-allow constructor constructor() { @@ -292,6 +293,9 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { Template memory template = templates[agreementData.templateId]; if (agreementData.parties.length == 0) revert ContractDoesNotExist(); if (agreementData.signedAt[signer] != 0) revert AlreadySigned(); + if (isVoided(contractId)) revert ContractAlreadyVoided(); + if (agreementData.finalized) revert ContractAlreadyFinalized(); + if (expiry[contractId] > 0 && expiry[contractId] < block.timestamp) revert ContractExpired(); if (!isParty(contractId, signer)) { // Not a named party, so check if there's an open slot @@ -343,6 +347,7 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { if(!isParty(contractId, party)) revert NotAParty(); AgreementData storage agreementData = agreements[contractId]; + if (agreementData.finalized) revert ContractAlreadyFinalized(); //verify the signature if (!_verifySignature(party, SignatureData({ @@ -366,6 +371,9 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { if(agreementData.finalized) revert ContractAlreadyFinalized(); if(agreementData.parties.length == 0) revert ContractDoesNotExist(); if(!allPartiesSigned(contractId)) revert ContractNotFullySigned(); + if(isVoided(contractId)) revert ContractAlreadyVoided(); + if(expiry[contractId] > 0 && expiry[contractId] < block.timestamp) revert ContractExpired(); + agreementData.finalized = true; } From cdd735420802fc3a3d60c4ed588c8c5212cce8dc Mon Sep 17 00:00:00 2001 From: merisman <6125373+merisman@users.noreply.github.com> Date: Mon, 7 Apr 2025 23:13:56 -0400 Subject: [PATCH 06/18] More state and updates to lexscrow lite --- src/CyberDealRegistry.sol | 4 +- src/libs/LexScroWLite.sol | 82 +++++++++++++++++++++++---------------- 2 files changed, 51 insertions(+), 35 deletions(-) diff --git a/src/CyberDealRegistry.sol b/src/CyberDealRegistry.sol index fd2f034..67073a6 100644 --- a/src/CyberDealRegistry.sol +++ b/src/CyberDealRegistry.sol @@ -683,8 +683,8 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { return agreements[contractId].finalized; } - function isVoided(bytes32 contractId) external view returns (bool) { - return ((voidedBy[contractId].length == agreements[contractId].numSignatures) || (voidedBy[contractId].length == 1 && expiry[contractId] < block.timestamp)); + function isVoided(bytes32 contractId) public view returns (bool) { + return ((voidedBy[contractId].length == agreements[contractId].numSignatures && voidedBy[contractId].length > 0) || (voidedBy[contractId].length == 1 && expiry[contractId] < block.timestamp)); } function _authorizeUpgrade(address newImplementation) internal virtual override onlyOwner {} diff --git a/src/libs/LexScroWLite.sol b/src/libs/LexScroWLite.sol index cc98cb9..50760d5 100644 --- a/src/libs/LexScroWLite.sol +++ b/src/libs/LexScroWLite.sol @@ -103,37 +103,62 @@ abstract contract LexScroWLite is Initializable, ReentrancyGuard { deal.status = EscrowStatus.PAID; } - function finalizeEscrow(bytes32 agreementId) internal { + function voidAndRefund(bytes32 agreementId) internal nonReentrant { Escrow storage deal = escrows[agreementId]; + if(deal.status != EscrowStatus.PAID) revert EscrowNotPaid(); + if(!ICyberDealRegistry(DEAL_REGISTRY).isVoided(agreementId)) revert DealNotVoided(); + + // Refund buyer assets first + for(uint256 i = 0; i < deal.buyerAssets.length; i++) { + if(deal.buyerAssets[i].tokenType == TokenType.ERC20) { + IERC20(deal.buyerAssets[i].tokenAddress).transfer(deal.counterParty, deal.buyerAssets[i].amount); + } + else if(deal.buyerAssets[i].tokenType == TokenType.ERC721) { + IERC721(deal.buyerAssets[i].tokenAddress).safeTransferFrom(address(this), deal.counterParty, deal.buyerAssets[i].tokenId); + } + else if(deal.buyerAssets[i].tokenType == TokenType.ERC1155) { + IERC1155(deal.buyerAssets[i].tokenAddress).safeTransferFrom(address(this), deal.counterParty, deal.buyerAssets[i].tokenId, deal.buyerAssets[i].amount, ""); + } + } + + deal.status = EscrowStatus.VOIDED; + } + + function finalizeEscrow(bytes32 agreementId) internal nonReentrant { + Escrow storage deal = escrows[agreementId]; + + // Check all conditions before proceeding if(block.timestamp > deal.expiry) revert DealExpired(); if(deal.status != EscrowStatus.PAID) revert EscrowNotPaid(); - for(uint256 i = 0; i < deal.buyerAssets.length; i++) { - if(deal.buyerAssets[i].tokenType == TokenType.ERC20) { - IERC20(deal.buyerAssets[i].tokenAddress).transfer(ICyberCorp(CORP).companyPayable(), deal.buyerAssets[i].amount); - } - else if(deal.buyerAssets[i].tokenType == TokenType.ERC721) { - IERC721(deal.buyerAssets[i].tokenAddress).safeTransferFrom(address(this), ICyberCorp(CORP).companyPayable(), deal.buyerAssets[i].tokenId); - } - else if(deal.buyerAssets[i].tokenType == TokenType.ERC1155) { - IERC1155(deal.buyerAssets[i].tokenAddress).safeTransferFrom(address(this), ICyberCorp(CORP).companyPayable(), deal.buyerAssets[i].tokenId, deal.buyerAssets[i].amount, ""); - } - } + // Update state before external calls + deal.status = EscrowStatus.FINALIZED; - //transfer tokens - for(uint256 i = 0; i < deal.corpAssets.length; i++) { - if(deal.corpAssets[i].tokenType == TokenType.ERC20) { - IERC20(deal.corpAssets[i].tokenAddress).transfer(deal.counterParty, deal.corpAssets[i].amount); - } - else if(deal.corpAssets[i].tokenType == TokenType.ERC721) { - IERC721(deal.corpAssets[i].tokenAddress).safeTransferFrom(address(this), deal.counterParty, deal.corpAssets[i].tokenId); - } - else if(deal.corpAssets[i].tokenType == TokenType.ERC1155) { - IERC1155(deal.corpAssets[i].tokenAddress).safeTransferFrom(address(this), deal.counterParty, deal.corpAssets[i].tokenId, deal.corpAssets[i].amount, ""); + // Transfer buyer assets to company + for(uint256 i = 0; i < deal.buyerAssets.length; i++) { + if(deal.buyerAssets[i].tokenType == TokenType.ERC20) { + IERC20(deal.buyerAssets[i].tokenAddress).transfer(ICyberCorp(CORP).companyPayable(), deal.buyerAssets[i].amount); + } + else if(deal.buyerAssets[i].tokenType == TokenType.ERC721) { + IERC721(deal.buyerAssets[i].tokenAddress).safeTransferFrom(address(this), ICyberCorp(CORP).companyPayable(), deal.buyerAssets[i].tokenId); + } + else if(deal.buyerAssets[i].tokenType == TokenType.ERC1155) { + IERC1155(deal.buyerAssets[i].tokenAddress).safeTransferFrom(address(this), ICyberCorp(CORP).companyPayable(), deal.buyerAssets[i].tokenId, deal.buyerAssets[i].amount, ""); + } } - } - deal.status = EscrowStatus.FINALIZED; + // Transfer corp assets to counter party + for(uint256 i = 0; i < deal.corpAssets.length; i++) { + if(deal.corpAssets[i].tokenType == TokenType.ERC20) { + IERC20(deal.corpAssets[i].tokenAddress).transfer(deal.counterParty, deal.corpAssets[i].amount); + } + else if(deal.corpAssets[i].tokenType == TokenType.ERC721) { + IERC721(deal.corpAssets[i].tokenAddress).safeTransferFrom(address(this), deal.counterParty, deal.corpAssets[i].tokenId); + } + else if(deal.corpAssets[i].tokenType == TokenType.ERC1155) { + IERC1155(deal.corpAssets[i].tokenAddress).safeTransferFrom(address(this), deal.counterParty, deal.corpAssets[i].tokenId, deal.corpAssets[i].amount, ""); + } + } } function conditionCheck(bytes32 agreementId) public view returns (bool) { @@ -149,15 +174,6 @@ abstract contract LexScroWLite is Initializable, ReentrancyGuard { return true; } - function voidAndRefund(bytes32 agreementId) internal { - if(escrows[agreementId].status != EscrowStatus.PAID) revert EscrowNotPaid(); - voidEscrow(agreementId); - for(uint256 i = 0; i < escrows[agreementId].corpAssets.length; i++) { - IERC20(escrows[agreementId].corpAssets[i].tokenAddress).transfer(escrows[agreementId].counterParty, escrows[agreementId].corpAssets[i].amount); - } - - } - function voidEscrow(bytes32 agreementId) internal { escrows[agreementId].status = EscrowStatus.VOIDED; } From da9a97db79dcbdbd07e01cba1f7efb95cf7176ce Mon Sep 17 00:00:00 2001 From: merisman <6125373+merisman@users.noreply.github.com> Date: Tue, 8 Apr 2025 13:52:47 -0400 Subject: [PATCH 07/18] cert ID in deal manager fix --- src/CyberCorpFactory.sol | 8 ++-- src/DealManager.sol | 68 ++++++++++++++------------------- src/interfaces/IDealManager.sol | 7 +--- 3 files changed, 35 insertions(+), 48 deletions(-) diff --git a/src/CyberCorpFactory.sol b/src/CyberCorpFactory.sol index 516724a..83d728e 100644 --- a/src/CyberCorpFactory.sol +++ b/src/CyberCorpFactory.sol @@ -136,9 +136,9 @@ contract CyberCorpFactory { certPrinterAddress = address(certPrinter); // Create and sign deal - id = IDealManager(dealManagerAddress).proposeAndSignDeal( + uint256 certId; + (id, certId) = IDealManager(dealManagerAddress).proposeAndSignDeal( certPrinterAddress, - certPrinter.totalSupply(), stable, _paymentAmount, _templateId, @@ -194,9 +194,9 @@ contract CyberCorpFactory { certPrinterAddress = address(certPrinter); // Create and sign deal - id = IDealManager(dealManagerAddress).proposeAndSignClosedDeal( + uint256 certId; + (id, certId) = IDealManager(dealManagerAddress).proposeAndSignClosedDeal( certPrinterAddress, - certPrinter.totalSupply(), stable, _paymentAmount, _templateId, diff --git a/src/DealManager.sol b/src/DealManager.sol index 7351ea9..4c62ed1 100644 --- a/src/DealManager.sol +++ b/src/DealManager.sol @@ -57,7 +57,6 @@ contract DealManager is Initializable, UUPSUpgradeable, BorgAuthACL, LexScroWLit function proposeDeal( address _certPrinterAddress, - uint256 _certId, address _paymentToken, uint256 _paymentAmount, bytes32 _templateId, @@ -66,12 +65,12 @@ contract DealManager is Initializable, UUPSUpgradeable, BorgAuthACL, LexScroWLit address[] memory _parties, CertificateDetails memory _certDetails, uint256 expiry - ) public onlyOwner returns (bytes32 agreementId){ - IIssuanceManager(ISSUANCE_MANAGER).createCert(_certPrinterAddress, address(this), _certDetails); + ) public onlyOwner returns (bytes32 agreementId, uint256 certId){ + certId = IIssuanceManager(ISSUANCE_MANAGER).createCert(_certPrinterAddress, address(this), _certDetails); agreementId = ICyberDealRegistry(DEAL_REGISTRY).createContract(_templateId, _salt, _globalValues, _parties, address(this)); Token[] memory corpAssets = new Token[](1); - corpAssets[0] = Token(TokenType.ERC721, _certPrinterAddress, _certId, 1); + corpAssets[0] = Token(TokenType.ERC721, _certPrinterAddress, certId, 1); Token[] memory buyerAssets = new Token[](1); buyerAssets[0] = Token(TokenType.ERC20, _paymentToken, 0, _paymentAmount); @@ -80,7 +79,7 @@ contract DealManager is Initializable, UUPSUpgradeable, BorgAuthACL, LexScroWLit emit DealProposed( agreementId, _certPrinterAddress, - _certId, + certId, _paymentToken, _paymentAmount, _templateId, @@ -88,13 +87,10 @@ contract DealManager is Initializable, UUPSUpgradeable, BorgAuthACL, LexScroWLit address(DEAL_REGISTRY), _parties ); - - return agreementId; } function proposeClosedDeal( address _certPrinterAddress, - uint256 _certId, address _paymentToken, uint256 _paymentAmount, bytes32 _templateId, @@ -105,12 +101,12 @@ contract DealManager is Initializable, UUPSUpgradeable, BorgAuthACL, LexScroWLit string[] memory _creatingPartyValues, string[] memory _counterPartyValues, uint256 expiry - ) public onlyOwner returns (bytes32 agreementId){ - IIssuanceManager(ISSUANCE_MANAGER).createCert(_certPrinterAddress, address(this), _certDetails); + ) public onlyOwner returns (bytes32 agreementId, uint256 certId){ + certId = IIssuanceManager(ISSUANCE_MANAGER).createCert(_certPrinterAddress, address(this), _certDetails); agreementId = ICyberDealRegistry(DEAL_REGISTRY).createClosedContract(_templateId, _salt, _globalValues, _parties, _creatingPartyValues, _counterPartyValues, address(this)); Token[] memory corpAssets = new Token[](1); - corpAssets[0] = Token(TokenType.ERC721, _certPrinterAddress, _certId, 1); + corpAssets[0] = Token(TokenType.ERC721, _certPrinterAddress, certId, 1); Token[] memory buyerAssets = new Token[](1); buyerAssets[0] = Token(TokenType.ERC20, _paymentToken, 0, _paymentAmount); @@ -119,7 +115,7 @@ contract DealManager is Initializable, UUPSUpgradeable, BorgAuthACL, LexScroWLit emit DealProposed( agreementId, _certPrinterAddress, - _certId, + certId, _paymentToken, _paymentAmount, _templateId, @@ -127,13 +123,10 @@ contract DealManager is Initializable, UUPSUpgradeable, BorgAuthACL, LexScroWLit address(DEAL_REGISTRY), _parties ); - - return agreementId; } function proposeAndSignDeal( address _certPrinterAddress, - uint256 _certId, address _paymentToken, uint256 _paymentAmount, bytes32 _templateId, @@ -145,35 +138,15 @@ contract DealManager is Initializable, UUPSUpgradeable, BorgAuthACL, LexScroWLit bytes memory signature, string[] memory partyValues, // These are the party values for the proposer uint256 expiry - ) public returns (bytes32 agreementId){ - agreementId = proposeDeal(_certPrinterAddress, _certId, _paymentToken, _paymentAmount, _templateId, _salt, _globalValues, _parties, _certDetails, expiry); + ) public returns (bytes32 agreementId, uint256 certId){ + (agreementId, certId) = proposeDeal(_certPrinterAddress, _paymentToken, _paymentAmount, _templateId, _salt, _globalValues, _parties, _certDetails, expiry); // NOTE: proposer is expected to be listed as a party in the parties array. escrows[agreementId].signature = signature; ICyberDealRegistry(DEAL_REGISTRY).signContractFor(proposer, agreementId, partyValues, signature, false); - - return agreementId; - } - - function signDealAndPay( - bytes32 agreementId, - address signer, - bytes memory signature, - string[] memory partyValues, - bool _fillUnallocated, - string memory name - ) public { - if(ICyberDealRegistry(DEAL_REGISTRY).isVoided(agreementId)) revert DealVoided(); - if(ICyberDealRegistry(DEAL_REGISTRY).isFinalized(agreementId)) revert DealAlreadyFinalized(); - if(escrows[agreementId].status != EscrowStatus.PENDING) revert DealNotPending(); - - ICyberDealRegistry(DEAL_REGISTRY).signContractFor(signer, agreementId, partyValues, signature, _fillUnallocated); - updateEscrow(agreementId, msg.sender, name); - handleCounterPartyPayment(agreementId); } function proposeAndSignClosedDeal( address _certPrinterAddress, - uint256 _certId, address _paymentToken, uint256 _paymentAmount, bytes32 _templateId, @@ -186,14 +159,31 @@ contract DealManager is Initializable, UUPSUpgradeable, BorgAuthACL, LexScroWLit string[] memory partyValues, string[] memory _counterPartyValues, uint256 expiry - ) public returns (bytes32 agreementId){ - agreementId = proposeClosedDeal(_certPrinterAddress, _certId, _paymentToken, _paymentAmount, _templateId, _salt, _globalValues, _parties, _certDetails, partyValues, _counterPartyValues, expiry); + ) public returns (bytes32 agreementId, uint256 certId){ + (agreementId, certId) = proposeClosedDeal(_certPrinterAddress, _paymentToken, _paymentAmount, _templateId, _salt, _globalValues, _parties, _certDetails, partyValues, _counterPartyValues, expiry); // NOTE: proposer is expected to be listed as a party in the parties array. escrows[agreementId].signature = signature; counterPartyValues[agreementId] = _counterPartyValues; ICyberDealRegistry(DEAL_REGISTRY).signContractFor(proposer, agreementId, partyValues, signature, false); } + function signDealAndPay( + bytes32 agreementId, + address signer, + bytes memory signature, + string[] memory partyValues, + bool _fillUnallocated, + string memory name + ) public { + if(ICyberDealRegistry(DEAL_REGISTRY).isVoided(agreementId)) revert DealVoided(); + if(ICyberDealRegistry(DEAL_REGISTRY).isFinalized(agreementId)) revert DealAlreadyFinalized(); + if(escrows[agreementId].status != EscrowStatus.PENDING) revert DealNotPending(); + + ICyberDealRegistry(DEAL_REGISTRY).signContractFor(signer, agreementId, partyValues, signature, _fillUnallocated); + updateEscrow(agreementId, msg.sender, name); + handleCounterPartyPayment(agreementId); + } + function signAndFinalizeDeal(address signer, bytes32 agreementId, string[] memory partyValues, bytes memory signature, bool _fillUnallocated, string memory name) public { if(ICyberDealRegistry(DEAL_REGISTRY).isVoided(agreementId)) revert DealVoided(); if(ICyberDealRegistry(DEAL_REGISTRY).isFinalized(agreementId)) revert DealAlreadyFinalized(); diff --git a/src/interfaces/IDealManager.sol b/src/interfaces/IDealManager.sol index 47cda9e..0076a6b 100644 --- a/src/interfaces/IDealManager.sol +++ b/src/interfaces/IDealManager.sol @@ -6,7 +6,6 @@ import "./IIssuanceManager.sol"; interface IDealManager { function proposeDeal( address _certPrinterAddress, - uint256 _certId, address _paymentToken, uint256 _paymentAmount, bytes32 _templateId, @@ -18,7 +17,6 @@ interface IDealManager { function proposeAndSignDeal( address _certPrinterAddress, - uint256 _certId, address _paymentToken, uint256 _paymentAmount, bytes32 _templateId, @@ -30,7 +28,7 @@ interface IDealManager { bytes memory signature, string[] memory paryValues, uint256 expiry - ) external returns (bytes32 agreementId); + ) external returns (bytes32 agreementId, uint256 certId); function signDealAndPay( address signer, @@ -43,7 +41,6 @@ interface IDealManager { function proposeAndSignClosedDeal( address _certPrinterAddress, - uint256 _certId, address _paymentToken, uint256 _paymentAmount, bytes32 _templateId, @@ -56,7 +53,7 @@ interface IDealManager { string[] memory partyValues, string[] memory counterPartyValues, uint256 expiry - ) external returns (bytes32 agreementId); + ) external returns (bytes32 agreementId, uint256 certId); function finalizeDeal( address signer, From 6701b506ce74ee5c67f703224477e101ba14ac1d Mon Sep 17 00:00:00 2001 From: merisman <6125373+merisman@users.noreply.github.com> Date: Tue, 8 Apr 2025 15:55:06 -0400 Subject: [PATCH 08/18] Adding secret, adding tests, fixing void flows. --- src/CyberCorpFactory.sol | 4 + src/CyberDealRegistry.sol | 18 +- src/DealManager.sol | 30 +- src/interfaces/ICyberDealRegistry.sol | 10 +- src/interfaces/IDealManager.sol | 27 +- test/CyberCorpTest.t.sol | 1221 ++++++++++++++++++++++++- 6 files changed, 1277 insertions(+), 33 deletions(-) diff --git a/src/CyberCorpFactory.sol b/src/CyberCorpFactory.sol index 83d728e..f99739b 100644 --- a/src/CyberCorpFactory.sol +++ b/src/CyberCorpFactory.sol @@ -113,6 +113,7 @@ contract CyberCorpFactory { string[] memory _partyValues, bytes memory signature, CertificateDetails memory _details, + bytes32 secretHash, uint256 expiry ) external returns (address cyberCorpAddress, address authAddress, address issuanceManagerAddress, address dealManagerAddress, address certPrinterAddress, bytes32 id) { @@ -149,6 +150,7 @@ contract CyberCorpFactory { msg.sender, signature, _partyValues, + secretHash, expiry ); @@ -171,6 +173,7 @@ contract CyberCorpFactory { bytes memory signature, CertificateDetails memory _details, string[] memory _counterPartyValues, + bytes32 secretHash, uint256 expiry ) external returns (address cyberCorpAddress, address authAddress, address issuanceManagerAddress, address dealManagerAddress, address certPrinterAddress, bytes32 id) { @@ -208,6 +211,7 @@ contract CyberCorpFactory { signature, _partyValues, _counterPartyValues, + secretHash, expiry ); diff --git a/src/CyberDealRegistry.sol b/src/CyberDealRegistry.sol index 67073a6..0d74425 100644 --- a/src/CyberDealRegistry.sol +++ b/src/CyberDealRegistry.sol @@ -62,6 +62,8 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { mapping(bytes32 => uint256) public expiry; + mapping(bytes32 => bytes32) public secrets; + event TemplateCreated( bytes32 indexed templateId, string indexed title, @@ -102,6 +104,7 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { error ContractAlreadyFinalized(); error ContractNotFullySigned(); error ContractExpired(); + error InvalidSecret(); /// @custom:oz-upgrades-unsafe-allow constructor constructor() { @@ -168,6 +171,7 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { uint256 salt, string[] memory globalValues, address[] memory parties, + bytes32 secretHash, address finalizer ) external returns (bytes32 contractId) { //create hash from templateId, globalValues, and parties @@ -203,7 +207,7 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { agreementData.parties = parties; agreementData.transactionHash = blockhash(block.number - 1); // Store the transaction hash agreementData.finalizer = finalizer; - + secrets[contractId] = secretHash; emit ContractCreated(contractId, templateId, parties); // Add to the party's list of agreements @@ -219,6 +223,7 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { address[] memory parties, string[] memory creatingPartyValues, string[] memory counterPartyValues, + bytes32 secretHash, address finalizer ) external returns (bytes32 contractId) { //create hash from templateId, globalValues, and parties @@ -264,7 +269,7 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { agreementData.partyValues[parties[0]] = creatingPartyValues; agreementData.partyValues[parties[1]] = counterPartyValues; agreementData.finalizer = finalizer; - + secrets[contractId] = secretHash; emit ContractCreated(contractId, templateId, parties); // Add to the party's list of agreements @@ -277,9 +282,10 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { bytes32 contractId, string[] memory partyValues, bytes calldata signature, - bool fillUnallocated // to fill a 0 address or not + bool fillUnallocated, // to fill a 0 address or not + string memory secret ) external { - signContractFor(msg.sender, contractId, partyValues, signature, fillUnallocated); + signContractFor(msg.sender, contractId, partyValues, signature, fillUnallocated, secret); } function signContractFor( @@ -287,7 +293,8 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { bytes32 contractId, string[] memory partyValues, bytes calldata signature, - bool fillUnallocated // to fill a 0 address or not + bool fillUnallocated, // to fill a 0 address or not + string memory secret ) public { AgreementData storage agreementData = agreements[contractId]; Template memory template = templates[agreementData.templateId]; @@ -298,6 +305,7 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { if (expiry[contractId] > 0 && expiry[contractId] < block.timestamp) revert ContractExpired(); if (!isParty(contractId, signer)) { + if(secrets[contractId] > 0 && keccak256(abi.encode(secret)) != secrets[contractId]) revert InvalidSecret(); // Not a named party, so check if there's an open slot uint256 firstOpenPartyIndex = getFirstOpenPartyIndex(contractId); if (firstOpenPartyIndex == 0 || !fillUnallocated) diff --git a/src/DealManager.sol b/src/DealManager.sol index 4c62ed1..c062238 100644 --- a/src/DealManager.sol +++ b/src/DealManager.sol @@ -64,10 +64,11 @@ contract DealManager is Initializable, UUPSUpgradeable, BorgAuthACL, LexScroWLit string[] memory _globalValues, address[] memory _parties, CertificateDetails memory _certDetails, + bytes32 secretHash, uint256 expiry ) public onlyOwner returns (bytes32 agreementId, uint256 certId){ certId = IIssuanceManager(ISSUANCE_MANAGER).createCert(_certPrinterAddress, address(this), _certDetails); - agreementId = ICyberDealRegistry(DEAL_REGISTRY).createContract(_templateId, _salt, _globalValues, _parties, address(this)); + agreementId = ICyberDealRegistry(DEAL_REGISTRY).createContract(_templateId, _salt, _globalValues, _parties, secretHash, address(this)); Token[] memory corpAssets = new Token[](1); corpAssets[0] = Token(TokenType.ERC721, _certPrinterAddress, certId, 1); @@ -100,10 +101,11 @@ contract DealManager is Initializable, UUPSUpgradeable, BorgAuthACL, LexScroWLit CertificateDetails memory _certDetails, string[] memory _creatingPartyValues, string[] memory _counterPartyValues, + bytes32 secretHash, uint256 expiry ) public onlyOwner returns (bytes32 agreementId, uint256 certId){ certId = IIssuanceManager(ISSUANCE_MANAGER).createCert(_certPrinterAddress, address(this), _certDetails); - agreementId = ICyberDealRegistry(DEAL_REGISTRY).createClosedContract(_templateId, _salt, _globalValues, _parties, _creatingPartyValues, _counterPartyValues, address(this)); + agreementId = ICyberDealRegistry(DEAL_REGISTRY).createClosedContract(_templateId, _salt, _globalValues, _parties, _creatingPartyValues, _counterPartyValues, secretHash, address(this)); Token[] memory corpAssets = new Token[](1); corpAssets[0] = Token(TokenType.ERC721, _certPrinterAddress, certId, 1); @@ -137,12 +139,13 @@ contract DealManager is Initializable, UUPSUpgradeable, BorgAuthACL, LexScroWLit address proposer, bytes memory signature, string[] memory partyValues, // These are the party values for the proposer + bytes32 secretHash, uint256 expiry ) public returns (bytes32 agreementId, uint256 certId){ - (agreementId, certId) = proposeDeal(_certPrinterAddress, _paymentToken, _paymentAmount, _templateId, _salt, _globalValues, _parties, _certDetails, expiry); + (agreementId, certId) = proposeDeal(_certPrinterAddress, _paymentToken, _paymentAmount, _templateId, _salt, _globalValues, _parties, _certDetails, secretHash, expiry); // NOTE: proposer is expected to be listed as a party in the parties array. escrows[agreementId].signature = signature; - ICyberDealRegistry(DEAL_REGISTRY).signContractFor(proposer, agreementId, partyValues, signature, false); + ICyberDealRegistry(DEAL_REGISTRY).signContractFor(proposer, agreementId, partyValues, signature, false, ""); } function proposeAndSignClosedDeal( @@ -158,33 +161,37 @@ contract DealManager is Initializable, UUPSUpgradeable, BorgAuthACL, LexScroWLit bytes memory signature, string[] memory partyValues, string[] memory _counterPartyValues, + bytes32 secretHash, uint256 expiry ) public returns (bytes32 agreementId, uint256 certId){ - (agreementId, certId) = proposeClosedDeal(_certPrinterAddress, _paymentToken, _paymentAmount, _templateId, _salt, _globalValues, _parties, _certDetails, partyValues, _counterPartyValues, expiry); + (agreementId, certId) = proposeClosedDeal(_certPrinterAddress, _paymentToken, _paymentAmount, _templateId, _salt, _globalValues, _parties, _certDetails, partyValues, _counterPartyValues, secretHash, expiry); // NOTE: proposer is expected to be listed as a party in the parties array. escrows[agreementId].signature = signature; counterPartyValues[agreementId] = _counterPartyValues; - ICyberDealRegistry(DEAL_REGISTRY).signContractFor(proposer, agreementId, partyValues, signature, false); + ICyberDealRegistry(DEAL_REGISTRY).signContractFor(proposer, agreementId, partyValues, signature, false, ""); } function signDealAndPay( - bytes32 agreementId, address signer, + bytes32 agreementId, bytes memory signature, string[] memory partyValues, bool _fillUnallocated, - string memory name + string memory name, + string memory secret ) public { if(ICyberDealRegistry(DEAL_REGISTRY).isVoided(agreementId)) revert DealVoided(); if(ICyberDealRegistry(DEAL_REGISTRY).isFinalized(agreementId)) revert DealAlreadyFinalized(); if(escrows[agreementId].status != EscrowStatus.PENDING) revert DealNotPending(); + //check if the deal has expired + if(escrows[agreementId].expiry < block.timestamp) revert DealExpired(); - ICyberDealRegistry(DEAL_REGISTRY).signContractFor(signer, agreementId, partyValues, signature, _fillUnallocated); + ICyberDealRegistry(DEAL_REGISTRY).signContractFor(signer, agreementId, partyValues, signature, _fillUnallocated, secret); updateEscrow(agreementId, msg.sender, name); handleCounterPartyPayment(agreementId); } - function signAndFinalizeDeal(address signer, bytes32 agreementId, string[] memory partyValues, bytes memory signature, bool _fillUnallocated, string memory name) public { + function signAndFinalizeDeal(address signer, bytes32 agreementId, string[] memory partyValues, bytes memory signature, bool _fillUnallocated, string memory name, string memory secret) public { if(ICyberDealRegistry(DEAL_REGISTRY).isVoided(agreementId)) revert DealVoided(); if(ICyberDealRegistry(DEAL_REGISTRY).isFinalized(agreementId)) revert DealAlreadyFinalized(); if(escrows[agreementId].status != EscrowStatus.PENDING) revert DealNotPending(); @@ -197,7 +204,7 @@ contract DealManager is Initializable, UUPSUpgradeable, BorgAuthACL, LexScroWLit if(!conditionCheck(agreementId)) revert AgreementConditionsNotMet(); if(!ICyberDealRegistry(DEAL_REGISTRY).hasSigned(agreementId, signer)) - ICyberDealRegistry(DEAL_REGISTRY).signContractFor(signer, agreementId, partyValues, signature, _fillUnallocated); + ICyberDealRegistry(DEAL_REGISTRY).signContractFor(signer, agreementId, partyValues, signature, _fillUnallocated, secret); updateEscrow(agreementId, msg.sender, name); handleCounterPartyPayment(agreementId); @@ -238,7 +245,6 @@ contract DealManager is Initializable, UUPSUpgradeable, BorgAuthACL, LexScroWLit ICyberDealRegistry(DEAL_REGISTRY).voidContractFor(agreementId, signer, signature); else revert DealNotPending(); - ICyberDealRegistry(DEAL_REGISTRY).voidContractFor(agreementId, signer, signature); } function signToVoid(bytes32 agreementId, address signer, bytes memory signature) public { diff --git a/src/interfaces/ICyberDealRegistry.sol b/src/interfaces/ICyberDealRegistry.sol index 7cc915e..2f0bec1 100644 --- a/src/interfaces/ICyberDealRegistry.sol +++ b/src/interfaces/ICyberDealRegistry.sol @@ -52,6 +52,7 @@ interface ICyberDealRegistry { uint256 salt, string[] memory globalValues, address[] memory parties, + bytes32 secretHash, address finalizer ) external returns (bytes32); @@ -62,13 +63,17 @@ interface ICyberDealRegistry { address[] memory parties, string[] memory creatingPartyValues, string[] memory counterPartyValues, + bytes32 secretHash, address finalizer + + ) external returns (bytes32); function signContract( bytes32 contractId, string[] memory partyValues, - bool fillUnallocated + bool fillUnallocated, + string memory secret ) external; function signContractFor( @@ -76,7 +81,8 @@ interface ICyberDealRegistry { bytes32 contractId, string[] memory partyValues, bytes calldata signature, - bool fillUnallocated // to fill a 0 address or not + bool fillUnallocated, // to fill a 0 address or not + string memory secret ) external; //function voidContractFor(bytes32 contractId, address party, bytes calldata signature) public { diff --git a/src/interfaces/IDealManager.sol b/src/interfaces/IDealManager.sol index 0076a6b..a4573cd 100644 --- a/src/interfaces/IDealManager.sol +++ b/src/interfaces/IDealManager.sol @@ -27,17 +27,10 @@ interface IDealManager { address proposer, bytes memory signature, string[] memory paryValues, + bytes32 secretHash, uint256 expiry ) external returns (bytes32 agreementId, uint256 certId); - function signDealAndPay( - address signer, - bytes32 _agreementId, - string[] memory _partyValues, - bytes memory signature, - bool _fillUnallocated, - string memory name - ) external; function proposeAndSignClosedDeal( address _certPrinterAddress, @@ -52,6 +45,7 @@ interface IDealManager { bytes memory signature, string[] memory partyValues, string[] memory counterPartyValues, + bytes32 secretHash, uint256 expiry ) external returns (bytes32 agreementId, uint256 certId); @@ -61,8 +55,20 @@ interface IDealManager { string[] memory _partyValues, bytes memory signature, bool _fillUnallocated, - string memory buyerName + string memory buyerName, + string memory secret + ) external; + + function signDealAndPay( + address signer, + bytes32 agreementId, + bytes memory signature, + string[] memory partyValues, + bool _fillUnallocated, + string memory name, + string memory secret ) external; + function signAndFinalizeDeal( address signer, @@ -70,7 +76,8 @@ interface IDealManager { string[] memory _partyValues, bytes memory signature, bool _fillUnallocated, - string memory buyerName + string memory buyerName, + string memory secret ) external; function voidExpiredDeal( diff --git a/test/CyberCorpTest.t.sol b/test/CyberCorpTest.t.sol index cc84196..94757bf 100644 --- a/test/CyberCorpTest.t.sol +++ b/test/CyberCorpTest.t.sol @@ -137,6 +137,7 @@ contract CyberCorpTest is Test { partyValues, signature, _details, + bytes32(0), block.timestamp + 1000000 ); vm.stopPrank(); @@ -214,6 +215,7 @@ contract CyberCorpTest is Test { signature, _details, counterPartyValues, + bytes32(0), block.timestamp + 1000000 ); vm.stopPrank(); @@ -246,7 +248,8 @@ contract CyberCorpTest is Test { counterPartyValues, newPartySignature, true, - "Counter Party Name" + "Counter Party Name", + "" ); vm.stopPrank(); @@ -311,6 +314,7 @@ contract CyberCorpTest is Test { partyValues, signature, _details, + bytes32(0), block.timestamp + 1000000 ); vm.stopPrank(); @@ -354,6 +358,7 @@ contract CyberCorpTest is Test { block.timestamp, globalValues, parties, + bytes32(0), address(testAddress) ); @@ -378,7 +383,8 @@ contract CyberCorpTest is Test { id, partyValues, signature, - false + false, + "" ); string memory contractURI = registry.getContractJson( bytes32(uint256(1)) @@ -401,7 +407,7 @@ contract CyberCorpTest is Test { vm.stopPrank(); vm.startPrank(newPartyAddr); - registry.signContract(id, partyValuesB, signature, true); + registry.signContract(id, partyValuesB, signature, true, ""); contractURI = registry.getContractJson(id); console.log(contractURI); vm.stopPrank(); @@ -476,6 +482,7 @@ contract CyberCorpTest is Test { partyValues, proposerSignature, _details, + bytes32(0), block.timestamp + 1000000 ); vm.stopPrank(); @@ -513,7 +520,237 @@ contract CyberCorpTest is Test { partyValuesB, newPartySignature, true, - "John Doe" + "John Doe", + "" + ); + vm.stopPrank(); + } + + function testSecretHashFailure() public { + vm.startPrank(testAddress); + CertificateDetails memory _details = CertificateDetails({ + signingOfficerName: "", + signingOfficerTitle: "", + investmentAmount: 0, + issuerUSDValuationAtTimeofInvestment: 10000000, + unitsRepresented: 0, + legalDetails: "Legal Details, jusidictione etc", + issuerSignatureURI: "" + }); + + string[] memory globalValues = new string[](1); + globalValues[0] = "Global Value 1"; + address[] memory parties = new address[](2); + parties[0] = testAddress; + parties[1] = address(0); + uint256 _paymentAmount = 1000000000000000000; + string[] memory partyValues = new string[](1); + partyValues[0] = "Party Value 1"; + + // Create secret hash from "passphrase" + bytes32 secretHash = keccak256(abi.encodePacked("passphrase")); + + bytes32 contractId = keccak256( + abi.encode(bytes32(uint256(1)), block.timestamp, globalValues, parties) + ); + + string[] memory globalFields = new string[](1); + globalFields[0] = "Global Field 1"; + string[] memory partyFields = new string[](1); + partyFields[0] = "Party Field 1"; + + bytes memory proposerSignature = _signAgreementTypedData( + registry.DOMAIN_SEPARATOR(), + registry.SIGNATUREDATA_TYPEHASH(), + contractId, + "ipfs.io/ipfs/[cid]", + globalFields, + partyFields, + globalValues, + partyValues, + testPrivateKey + ); + + ( + address cyberCorp, + address auth, + address issuanceManager, + address dealManagerAddr, + address cyberCertPrinterAddr, + bytes32 id + ) = cyberCorpFactory.deployCyberCorpAndCreateOffer( + block.timestamp, + "CyberCorp", + testAddress, + "SAFE", + "SAFE", + "ipfs.io/ipfs/[cid]", + SecurityClass.SAFE, + SecuritySeries.SeriesPreSeed, + bytes32(uint256(1)), + globalValues, + parties, + _paymentAmount, + partyValues, + proposerSignature, + _details, + secretHash, + block.timestamp + 1000000 + ); + vm.stopPrank(); + + uint256 newPartyPk = 80085; + address newPartyAddr = vm.addr(newPartyPk); + string[] memory partyValuesB = new string[](1); + partyValuesB[0] = "Party Value B"; + + vm.startPrank(newPartyAddr); + bytes memory newPartySignature = _signAgreementTypedData( + registry.DOMAIN_SEPARATOR(), + registry.SIGNATUREDATA_TYPEHASH(), + contractId, + "ipfs.io/ipfs/[cid]", + globalFields, + partyFields, + globalValues, + partyValuesB, + newPartyPk + ); + IDealManager dealManager = IDealManager(dealManagerAddr); + deal( + 0x036CbD53842c5426634e7929541eC2318f3dCF7e, + newPartyAddr, + _paymentAmount + ); + IERC20(0x036CbD53842c5426634e7929541eC2318f3dCF7e).approve( + address(dealManager), + _paymentAmount + ); + + // Try to sign and finalize with wrong passphrase + vm.expectRevert(); // Expect revert due to invalid secret + dealManager.signAndFinalizeDeal( + newPartyAddr, + id, + partyValuesB, + newPartySignature, + true, + "John Doe", + "wrongpassphrase" // Using wrong passphrase + ); + vm.stopPrank(); + } + + function testSecretHashSuccess() public { + vm.startPrank(testAddress); + CertificateDetails memory _details = CertificateDetails({ + signingOfficerName: "", + signingOfficerTitle: "", + investmentAmount: 0, + issuerUSDValuationAtTimeofInvestment: 10000000, + unitsRepresented: 0, + legalDetails: "Legal Details, jusidictione etc", + issuerSignatureURI: "" + }); + + string[] memory globalValues = new string[](1); + globalValues[0] = "Global Value 1"; + address[] memory parties = new address[](2); + parties[0] = testAddress; + parties[1] = address(0); + uint256 _paymentAmount = 1000000000000000000; + string[] memory partyValues = new string[](1); + partyValues[0] = "Party Value 1"; + + // Create secret hash from "passphrase" + bytes32 secretHash = keccak256(abi.encode("passphrase")); + + bytes32 contractId = keccak256( + abi.encode(bytes32(uint256(1)), block.timestamp, globalValues, parties) + ); + + string[] memory globalFields = new string[](1); + globalFields[0] = "Global Field 1"; + string[] memory partyFields = new string[](1); + partyFields[0] = "Party Field 1"; + + bytes memory proposerSignature = _signAgreementTypedData( + registry.DOMAIN_SEPARATOR(), + registry.SIGNATUREDATA_TYPEHASH(), + contractId, + "ipfs.io/ipfs/[cid]", + globalFields, + partyFields, + globalValues, + partyValues, + testPrivateKey + ); + + ( + address cyberCorp, + address auth, + address issuanceManager, + address dealManagerAddr, + address cyberCertPrinterAddr, + bytes32 id + ) = cyberCorpFactory.deployCyberCorpAndCreateOffer( + block.timestamp, + "CyberCorp", + testAddress, + "SAFE", + "SAFE", + "ipfs.io/ipfs/[cid]", + SecurityClass.SAFE, + SecuritySeries.SeriesPreSeed, + bytes32(uint256(1)), + globalValues, + parties, + _paymentAmount, + partyValues, + proposerSignature, + _details, + secretHash, + block.timestamp + 1000000 + ); + vm.stopPrank(); + + uint256 newPartyPk = 80085; + address newPartyAddr = vm.addr(newPartyPk); + string[] memory partyValuesB = new string[](1); + partyValuesB[0] = "Party Value B"; + + vm.startPrank(newPartyAddr); + bytes memory newPartySignature = _signAgreementTypedData( + registry.DOMAIN_SEPARATOR(), + registry.SIGNATUREDATA_TYPEHASH(), + contractId, + "ipfs.io/ipfs/[cid]", + globalFields, + partyFields, + globalValues, + partyValuesB, + newPartyPk + ); + IDealManager dealManager = IDealManager(dealManagerAddr); + deal( + 0x036CbD53842c5426634e7929541eC2318f3dCF7e, + newPartyAddr, + _paymentAmount + ); + IERC20(0x036CbD53842c5426634e7929541eC2318f3dCF7e).approve( + address(dealManager), + _paymentAmount + ); + + // Sign and finalize with correct passphrase + dealManager.signAndFinalizeDeal( + newPartyAddr, + id, + partyValuesB, + newPartySignature, + true, + "John Doe", + "passphrase" // Using correct passphrase ); vm.stopPrank(); } @@ -568,4 +805,980 @@ contract CyberCorpTest is Test { } return keccak256(abi.encodePacked(hashes)); } + + function testRevokeDealBeforePayment() public { + vm.startPrank(testAddress); + CertificateDetails memory _details = CertificateDetails({ + signingOfficerName: "", + signingOfficerTitle: "", + investmentAmount: 0, + issuerUSDValuationAtTimeofInvestment: 10000000, + unitsRepresented: 0, + legalDetails: "Legal Details, jusidictione etc", + issuerSignatureURI: "" + }); + + string[] memory globalFields = new string[](1); + globalFields[0] = "Global Field 1"; + string[] memory partyFields = new string[](1); + partyFields[0] = "Party Field 1"; + string[] memory globalValues = new string[](1); + globalValues[0] = "Global Value 1"; + address[] memory parties = new address[](2); + parties[0] = testAddress; + parties[1] = address(0); + uint256 _paymentAmount = 1000000000000000000; + string[] memory partyValues = new string[](1); + partyValues[0] = "Party Value 1"; + + bytes32 contractId = keccak256( + abi.encode(bytes32(uint256(1)), block.timestamp, globalValues, parties) + ); + + bytes memory signature = _signAgreementTypedData( + registry.DOMAIN_SEPARATOR(), + registry.SIGNATUREDATA_TYPEHASH(), + contractId, + "ipfs.io/ipfs/[cid]", + globalFields, + partyFields, + globalValues, + partyValues, + testPrivateKey + ); + + ( + address cyberCorp, + address auth, + address issuanceManager, + address dealManagerAddr, + address cyberCertPrinterAddr, + bytes32 id + ) = cyberCorpFactory.deployCyberCorpAndCreateOffer( + block.timestamp, + "CyberCorp", + testAddress, + "SAFE", + "SAFE", + "ipfs.io/ipfs/[cid]", + SecurityClass.SAFE, + SecuritySeries.SeriesPreSeed, + bytes32(uint256(1)), + globalValues, + parties, + _paymentAmount, + partyValues, + signature, + _details, + bytes32(0), + block.timestamp + 1000000 + ); + + // Revoke deal before payment + IDealManager(dealManagerAddr).revokeDeal(id, testAddress, signature); + vm.stopPrank(); + } + + function testRevokeDealAfterPayment() public { + vm.startPrank(testAddress); + CertificateDetails memory _details = CertificateDetails({ + signingOfficerName: "", + signingOfficerTitle: "", + investmentAmount: 0, + issuerUSDValuationAtTimeofInvestment: 10000000, + unitsRepresented: 0, + legalDetails: "Legal Details, jusidictione etc", + issuerSignatureURI: "" + }); + + string[] memory globalFields = new string[](1); + globalFields[0] = "Global Field 1"; + string[] memory partyFields = new string[](1); + partyFields[0] = "Party Field 1"; + string[] memory globalValues = new string[](1); + globalValues[0] = "Global Value 1"; + address[] memory parties = new address[](2); + parties[0] = testAddress; + parties[1] = address(0); + uint256 _paymentAmount = 1000000000000000000; + string[] memory partyValues = new string[](1); + partyValues[0] = "Party Value 1"; + + bytes32 contractId = keccak256( + abi.encode(bytes32(uint256(1)), block.timestamp, globalValues, parties) + ); + + bytes memory signature = _signAgreementTypedData( + registry.DOMAIN_SEPARATOR(), + registry.SIGNATUREDATA_TYPEHASH(), + contractId, + "ipfs.io/ipfs/[cid]", + globalFields, + partyFields, + globalValues, + partyValues, + testPrivateKey + ); + + ( + address cyberCorp, + address auth, + address issuanceManager, + address dealManagerAddr, + address cyberCertPrinterAddr, + bytes32 id + ) = cyberCorpFactory.deployCyberCorpAndCreateOffer( + block.timestamp, + "CyberCorp", + testAddress, + "SAFE", + "SAFE", + "ipfs.io/ipfs/[cid]", + SecurityClass.SAFE, + SecuritySeries.SeriesPreSeed, + bytes32(uint256(1)), + globalValues, + parties, + _paymentAmount, + partyValues, + signature, + _details, + bytes32(0), + block.timestamp + 1000000 + ); + + // have a buyer sign and pay + + uint256 newPartyPk = 80085; + address newPartyAddr = vm.addr(newPartyPk); + string[] memory partyValuesB = new string[](1); + partyValuesB[0] = "Party Value B"; + + vm.startPrank(newPartyAddr); + bytes memory newPartySignature = _signAgreementTypedData( + registry.DOMAIN_SEPARATOR(), + registry.SIGNATUREDATA_TYPEHASH(), + contractId, + "ipfs.io/ipfs/[cid]", + globalFields, + partyFields, + globalValues, + partyValuesB, + newPartyPk + ); + deal( + 0x036CbD53842c5426634e7929541eC2318f3dCF7e, + newPartyAddr, + _paymentAmount + ); + IERC20(0x036CbD53842c5426634e7929541eC2318f3dCF7e).approve( + address(dealManagerAddr), + _paymentAmount + ); + + IDealManager(dealManagerAddr).signDealAndPay(newPartyAddr, id, newPartySignature, partyValuesB, true, "John Doe", "passphrase"); + vm.stopPrank(); + + // Try to revoke after payment - should fail + vm.expectRevert(); + IDealManager(dealManagerAddr).revokeDeal(id, testAddress, signature); + vm.stopPrank(); + } + + function testSignToVoidAfterPayment() public { + vm.startPrank(testAddress); + CertificateDetails memory _details = CertificateDetails({ + signingOfficerName: "", + signingOfficerTitle: "", + investmentAmount: 0, + issuerUSDValuationAtTimeofInvestment: 10000000, + unitsRepresented: 0, + legalDetails: "Legal Details, jusidictione etc", + issuerSignatureURI: "" + }); + + string[] memory globalFields = new string[](1); + globalFields[0] = "Global Field 1"; + string[] memory partyFields = new string[](1); + partyFields[0] = "Party Field 1"; + string[] memory globalValues = new string[](1); + globalValues[0] = "Global Value 1"; + address[] memory parties = new address[](2); + parties[0] = testAddress; + parties[1] = address(0); + uint256 _paymentAmount = 1000000000000000000; + string[] memory partyValues = new string[](1); + partyValues[0] = "Party Value 1"; + + bytes32 contractId = keccak256( + abi.encode(bytes32(uint256(1)), block.timestamp, globalValues, parties) + ); + + bytes memory signature = _signAgreementTypedData( + registry.DOMAIN_SEPARATOR(), + registry.SIGNATUREDATA_TYPEHASH(), + contractId, + "ipfs.io/ipfs/[cid]", + globalFields, + partyFields, + globalValues, + partyValues, + testPrivateKey + ); + + ( + address cyberCorp, + address auth, + address issuanceManager, + address dealManagerAddr, + address cyberCertPrinterAddr, + bytes32 id + ) = cyberCorpFactory.deployCyberCorpAndCreateOffer( + block.timestamp, + "CyberCorp", + testAddress, + "SAFE", + "SAFE", + "ipfs.io/ipfs/[cid]", + SecurityClass.SAFE, + SecuritySeries.SeriesPreSeed, + bytes32(uint256(1)), + globalValues, + parties, + _paymentAmount, + partyValues, + signature, + _details, + bytes32(0), + block.timestamp + 1000000 + ); + + // Sign to void after payment + IDealManager(dealManagerAddr).signToVoid(id, testAddress, signature); + vm.stopPrank(); + } + + function testVoidExpiredDeal() public { + vm.startPrank(testAddress); + CertificateDetails memory _details = CertificateDetails({ + signingOfficerName: "", + signingOfficerTitle: "", + investmentAmount: 0, + issuerUSDValuationAtTimeofInvestment: 10000000, + unitsRepresented: 0, + legalDetails: "Legal Details, jusidictione etc", + issuerSignatureURI: "" + }); + + string[] memory globalFields = new string[](1); + globalFields[0] = "Global Field 1"; + string[] memory partyFields = new string[](1); + partyFields[0] = "Party Field 1"; + string[] memory globalValues = new string[](1); + globalValues[0] = "Global Value 1"; + address[] memory parties = new address[](2); + parties[0] = testAddress; + parties[1] = address(0); + uint256 _paymentAmount = 1000000000000000000; + string[] memory partyValues = new string[](1); + partyValues[0] = "Party Value 1"; + + bytes32 contractId = keccak256( + abi.encode(bytes32(uint256(1)), block.timestamp, globalValues, parties) + ); + + bytes memory signature = _signAgreementTypedData( + registry.DOMAIN_SEPARATOR(), + registry.SIGNATUREDATA_TYPEHASH(), + contractId, + "ipfs.io/ipfs/[cid]", + globalFields, + partyFields, + globalValues, + partyValues, + testPrivateKey + ); + + ( + address cyberCorp, + address auth, + address issuanceManager, + address dealManagerAddr, + address cyberCertPrinterAddr, + bytes32 id + ) = cyberCorpFactory.deployCyberCorpAndCreateOffer( + block.timestamp, + "CyberCorp", + testAddress, + "SAFE", + "SAFE", + "ipfs.io/ipfs/[cid]", + SecurityClass.SAFE, + SecuritySeries.SeriesPreSeed, + bytes32(uint256(1)), + globalValues, + parties, + _paymentAmount, + partyValues, + signature, + _details, + bytes32(0), + block.timestamp + 1000000 + ); + + // Fast forward time to after expiry + vm.warp(block.timestamp + 1000001); + + // Void expired deal + IDealManager(dealManagerAddr).voidExpiredDeal(id, testAddress, signature); + vm.stopPrank(); + } + + function testFinalizeDealWithoutPayment() public { + vm.startPrank(testAddress); + CertificateDetails memory _details = CertificateDetails({ + signingOfficerName: "", + signingOfficerTitle: "", + investmentAmount: 0, + issuerUSDValuationAtTimeofInvestment: 10000000, + unitsRepresented: 0, + legalDetails: "Legal Details, jusidictione etc", + issuerSignatureURI: "" + }); + + string[] memory globalFields = new string[](1); + globalFields[0] = "Global Field 1"; + string[] memory partyFields = new string[](1); + partyFields[0] = "Party Field 1"; + string[] memory globalValues = new string[](1); + globalValues[0] = "Global Value 1"; + address[] memory parties = new address[](2); + parties[0] = testAddress; + parties[1] = address(0); + uint256 _paymentAmount = 1000000000000000000; + string[] memory partyValues = new string[](1); + partyValues[0] = "Party Value 1"; + + bytes32 contractId = keccak256( + abi.encode(bytes32(uint256(1)), block.timestamp, globalValues, parties) + ); + + bytes memory signature = _signAgreementTypedData( + registry.DOMAIN_SEPARATOR(), + registry.SIGNATUREDATA_TYPEHASH(), + contractId, + "ipfs.io/ipfs/[cid]", + globalFields, + partyFields, + globalValues, + partyValues, + testPrivateKey + ); + + ( + address cyberCorp, + address auth, + address issuanceManager, + address dealManagerAddr, + address cyberCertPrinterAddr, + bytes32 id + ) = cyberCorpFactory.deployCyberCorpAndCreateOffer( + block.timestamp, + "CyberCorp", + testAddress, + "SAFE", + "SAFE", + "ipfs.io/ipfs/[cid]", + SecurityClass.SAFE, + SecuritySeries.SeriesPreSeed, + bytes32(uint256(1)), + globalValues, + parties, + _paymentAmount, + partyValues, + signature, + _details, + bytes32(0), + block.timestamp + 1000000 + ); + + // Try to finalize without payment - should fail + vm.expectRevert(); + IDealManager(dealManagerAddr).finalizeDeal( + testAddress, + id, + partyValues, + signature, + false, + "John Doe", + "" + ); + vm.stopPrank(); + } + + function testSignDealAndPay() public { + vm.startPrank(testAddress); + CertificateDetails memory _details = CertificateDetails({ + signingOfficerName: "", + signingOfficerTitle: "", + investmentAmount: 0, + issuerUSDValuationAtTimeofInvestment: 10000000, + unitsRepresented: 0, + legalDetails: "Legal Details, jusidictione etc", + issuerSignatureURI: "" + }); + + string[] memory globalFields = new string[](1); + globalFields[0] = "Global Field 1"; + string[] memory partyFields = new string[](1); + partyFields[0] = "Party Field 1"; + string[] memory globalValues = new string[](1); + globalValues[0] = "Global Value 1"; + address[] memory parties = new address[](2); + parties[0] = testAddress; + parties[1] = address(0); + uint256 _paymentAmount = 1000000000000000000; + string[] memory partyValues = new string[](1); + partyValues[0] = "Party Value 1"; + + bytes32 contractId = keccak256( + abi.encode(bytes32(uint256(1)), block.timestamp, globalValues, parties) + ); + + bytes memory signature = _signAgreementTypedData( + registry.DOMAIN_SEPARATOR(), + registry.SIGNATUREDATA_TYPEHASH(), + contractId, + "ipfs.io/ipfs/[cid]", + globalFields, + partyFields, + globalValues, + partyValues, + testPrivateKey + ); + + ( + address cyberCorp, + address auth, + address issuanceManager, + address dealManagerAddr, + address cyberCertPrinterAddr, + bytes32 id + ) = cyberCorpFactory.deployCyberCorpAndCreateOffer( + block.timestamp, + "CyberCorp", + testAddress, + "SAFE", + "SAFE", + "ipfs.io/ipfs/[cid]", + SecurityClass.SAFE, + SecuritySeries.SeriesPreSeed, + bytes32(uint256(1)), + globalValues, + parties, + _paymentAmount, + partyValues, + signature, + _details, + bytes32(0), + block.timestamp + 1000000 + ); + + uint256 newPartyPk = 80085; + address newPartyAddr = vm.addr(newPartyPk); + string[] memory partyValuesB = new string[](1); + partyValuesB[0] = "Party Value B"; + + vm.startPrank(newPartyAddr); + bytes memory newPartySignature = _signAgreementTypedData( + registry.DOMAIN_SEPARATOR(), + registry.SIGNATUREDATA_TYPEHASH(), + contractId, + "ipfs.io/ipfs/[cid]", + globalFields, + partyFields, + globalValues, + partyValuesB, + newPartyPk + ); + + deal( + 0x036CbD53842c5426634e7929541eC2318f3dCF7e, + newPartyAddr, + _paymentAmount + ); + IERC20(0x036CbD53842c5426634e7929541eC2318f3dCF7e).approve( + address(dealManagerAddr), + _paymentAmount + ); + + IDealManager(dealManagerAddr).signDealAndPay( + newPartyAddr, + id, + newPartySignature, + partyValuesB, + true, + "John Doe", + "" + ); + vm.stopPrank(); + } + + function testFinalizeDealTwice() public { + vm.startPrank(testAddress); + CertificateDetails memory _details = CertificateDetails({ + signingOfficerName: "", + signingOfficerTitle: "", + investmentAmount: 0, + issuerUSDValuationAtTimeofInvestment: 10000000, + unitsRepresented: 0, + legalDetails: "Legal Details, jusidictione etc", + issuerSignatureURI: "" + }); + + string[] memory globalFields = new string[](1); + globalFields[0] = "Global Field 1"; + string[] memory partyFields = new string[](1); + partyFields[0] = "Party Field 1"; + string[] memory globalValues = new string[](1); + globalValues[0] = "Global Value 1"; + address[] memory parties = new address[](2); + parties[0] = testAddress; + parties[1] = address(0); + uint256 _paymentAmount = 1000000000000000000; + string[] memory partyValues = new string[](1); + partyValues[0] = "Party Value 1"; + + bytes32 contractId = keccak256( + abi.encode(bytes32(uint256(1)), block.timestamp, globalValues, parties) + ); + + bytes memory signature = _signAgreementTypedData( + registry.DOMAIN_SEPARATOR(), + registry.SIGNATUREDATA_TYPEHASH(), + contractId, + "ipfs.io/ipfs/[cid]", + globalFields, + partyFields, + globalValues, + partyValues, + testPrivateKey + ); + + ( + address cyberCorp, + address auth, + address issuanceManager, + address dealManagerAddr, + address cyberCertPrinterAddr, + bytes32 id + ) = cyberCorpFactory.deployCyberCorpAndCreateOffer( + block.timestamp, + "CyberCorp", + testAddress, + "SAFE", + "SAFE", + "ipfs.io/ipfs/[cid]", + SecurityClass.SAFE, + SecuritySeries.SeriesPreSeed, + bytes32(uint256(1)), + globalValues, + parties, + _paymentAmount, + partyValues, + signature, + _details, + bytes32(0), + block.timestamp + 1000000 + ); + + uint256 newPartyPk = 80085; + address newPartyAddr = vm.addr(newPartyPk); + string[] memory partyValuesB = new string[](1); + partyValuesB[0] = "Party Value B"; + + vm.startPrank(newPartyAddr); + bytes memory newPartySignature = _signAgreementTypedData( + registry.DOMAIN_SEPARATOR(), + registry.SIGNATUREDATA_TYPEHASH(), + contractId, + "ipfs.io/ipfs/[cid]", + globalFields, + partyFields, + globalValues, + partyValuesB, + newPartyPk + ); + + deal( + 0x036CbD53842c5426634e7929541eC2318f3dCF7e, + newPartyAddr, + _paymentAmount + ); + IERC20(0x036CbD53842c5426634e7929541eC2318f3dCF7e).approve( + address(dealManagerAddr), + _paymentAmount + ); + + IDealManager(dealManagerAddr).signAndFinalizeDeal( + newPartyAddr, + id, + partyValuesB, + newPartySignature, + true, + "John Doe", + "" + ); + + // Try to finalize again - should fail + vm.expectRevert(); + IDealManager(dealManagerAddr).finalizeDeal( + testAddress, + id, + partyValues, + signature, + false, + "", + "" + ); + vm.stopPrank(); + } + + function testVoidDealAfterFinalization() public { + vm.startPrank(testAddress); + CertificateDetails memory _details = CertificateDetails({ + signingOfficerName: "", + signingOfficerTitle: "", + investmentAmount: 0, + issuerUSDValuationAtTimeofInvestment: 10000000, + unitsRepresented: 0, + legalDetails: "Legal Details, jusidictione etc", + issuerSignatureURI: "" + }); + + string[] memory globalFields = new string[](1); + globalFields[0] = "Global Field 1"; + string[] memory partyFields = new string[](1); + partyFields[0] = "Party Field 1"; + string[] memory globalValues = new string[](1); + globalValues[0] = "Global Value 1"; + address[] memory parties = new address[](2); + parties[0] = testAddress; + parties[1] = address(0); + uint256 _paymentAmount = 1000000000000000000; + string[] memory partyValues = new string[](1); + partyValues[0] = "Party Value 1"; + + bytes32 contractId = keccak256( + abi.encode(bytes32(uint256(1)), block.timestamp, globalValues, parties) + ); + + bytes memory signature = _signAgreementTypedData( + registry.DOMAIN_SEPARATOR(), + registry.SIGNATUREDATA_TYPEHASH(), + contractId, + "ipfs.io/ipfs/[cid]", + globalFields, + partyFields, + globalValues, + partyValues, + testPrivateKey + ); + + ( + address cyberCorp, + address auth, + address issuanceManager, + address dealManagerAddr, + address cyberCertPrinterAddr, + bytes32 id + ) = cyberCorpFactory.deployCyberCorpAndCreateOffer( + block.timestamp, + "CyberCorp", + testAddress, + "SAFE", + "SAFE", + "ipfs.io/ipfs/[cid]", + SecurityClass.SAFE, + SecuritySeries.SeriesPreSeed, + bytes32(uint256(1)), + globalValues, + parties, + _paymentAmount, + partyValues, + signature, + _details, + bytes32(0), + block.timestamp + 1000000 + ); + + uint256 newPartyPk = 80085; + address newPartyAddr = vm.addr(newPartyPk); + string[] memory partyValuesB = new string[](1); + partyValuesB[0] = "Party Value B"; + + vm.startPrank(newPartyAddr); + bytes memory newPartySignature = _signAgreementTypedData( + registry.DOMAIN_SEPARATOR(), + registry.SIGNATUREDATA_TYPEHASH(), + contractId, + "ipfs.io/ipfs/[cid]", + globalFields, + partyFields, + globalValues, + partyValuesB, + newPartyPk + ); + + deal( + 0x036CbD53842c5426634e7929541eC2318f3dCF7e, + newPartyAddr, + _paymentAmount + ); + IERC20(0x036CbD53842c5426634e7929541eC2318f3dCF7e).approve( + address(dealManagerAddr), + _paymentAmount + ); + + IDealManager(dealManagerAddr).signAndFinalizeDeal( + newPartyAddr, + id, + partyValuesB, + newPartySignature, + true, + "John Doe", + "" + ); + + // Try to void after finalization - should fail + vm.expectRevert(); + IDealManager(dealManagerAddr).voidExpiredDeal(id, testAddress, signature); + vm.stopPrank(); + } + + function testSignDealWithInvalidSecret() public { + vm.startPrank(testAddress); + CertificateDetails memory _details = CertificateDetails({ + signingOfficerName: "", + signingOfficerTitle: "", + investmentAmount: 0, + issuerUSDValuationAtTimeofInvestment: 10000000, + unitsRepresented: 0, + legalDetails: "Legal Details, jusidictione etc", + issuerSignatureURI: "" + }); + + string[] memory globalFields = new string[](1); + globalFields[0] = "Global Field 1"; + string[] memory partyFields = new string[](1); + partyFields[0] = "Party Field 1"; + string[] memory globalValues = new string[](1); + globalValues[0] = "Global Value 1"; + address[] memory parties = new address[](2); + parties[0] = testAddress; + parties[1] = address(0); + uint256 _paymentAmount = 1000000000000000000; + string[] memory partyValues = new string[](1); + partyValues[0] = "Party Value 1"; + + bytes32 secretHash = keccak256(abi.encodePacked("passphrase")); + + bytes32 contractId = keccak256( + abi.encode(bytes32(uint256(1)), block.timestamp, globalValues, parties) + ); + + bytes memory signature = _signAgreementTypedData( + registry.DOMAIN_SEPARATOR(), + registry.SIGNATUREDATA_TYPEHASH(), + contractId, + "ipfs.io/ipfs/[cid]", + globalFields, + partyFields, + globalValues, + partyValues, + testPrivateKey + ); + + ( + address cyberCorp, + address auth, + address issuanceManager, + address dealManagerAddr, + address cyberCertPrinterAddr, + bytes32 id + ) = cyberCorpFactory.deployCyberCorpAndCreateOffer( + block.timestamp, + "CyberCorp", + testAddress, + "SAFE", + "SAFE", + "ipfs.io/ipfs/[cid]", + SecurityClass.SAFE, + SecuritySeries.SeriesPreSeed, + bytes32(uint256(1)), + globalValues, + parties, + _paymentAmount, + partyValues, + signature, + _details, + secretHash, + block.timestamp + 1000000 + ); + + uint256 newPartyPk = 80085; + address newPartyAddr = vm.addr(newPartyPk); + string[] memory partyValuesB = new string[](1); + partyValuesB[0] = "Party Value B"; + + vm.startPrank(newPartyAddr); + bytes memory newPartySignature = _signAgreementTypedData( + registry.DOMAIN_SEPARATOR(), + registry.SIGNATUREDATA_TYPEHASH(), + contractId, + "ipfs.io/ipfs/[cid]", + globalFields, + partyFields, + globalValues, + partyValuesB, + newPartyPk + ); + + deal( + 0x036CbD53842c5426634e7929541eC2318f3dCF7e, + newPartyAddr, + _paymentAmount + ); + IERC20(0x036CbD53842c5426634e7929541eC2318f3dCF7e).approve( + address(dealManagerAddr), + _paymentAmount + ); + + // Try to sign with invalid secret - should fail + vm.expectRevert(); + IDealManager(dealManagerAddr).signDealAndPay( + newPartyAddr, + id, + newPartySignature, + partyValuesB, + true, + "John Doe", + "wrongpassphrase" + ); + vm.stopPrank(); + } + + function testSignDealWithExpiredContract() public { + vm.startPrank(testAddress); + CertificateDetails memory _details = CertificateDetails({ + signingOfficerName: "", + signingOfficerTitle: "", + investmentAmount: 0, + issuerUSDValuationAtTimeofInvestment: 10000000, + unitsRepresented: 0, + legalDetails: "Legal Details, jusidictione etc", + issuerSignatureURI: "" + }); + + string[] memory globalFields = new string[](1); + globalFields[0] = "Global Field 1"; + string[] memory partyFields = new string[](1); + partyFields[0] = "Party Field 1"; + + string[] memory globalValues = new string[](1); + globalValues[0] = "Global Value 1"; + address[] memory parties = new address[](2); + parties[0] = testAddress; + parties[1] = address(0); + uint256 _paymentAmount = 1000000000000000000; + string[] memory partyValues = new string[](1); + partyValues[0] = "Party Value 1"; + + bytes32 contractId = keccak256( + abi.encode(bytes32(uint256(1)), block.timestamp, globalValues, parties) + ); + + bytes memory signature = _signAgreementTypedData( + registry.DOMAIN_SEPARATOR(), + registry.SIGNATUREDATA_TYPEHASH(), + contractId, + "ipfs.io/ipfs/[cid]", + globalFields, + partyFields, + globalValues, + partyValues, + testPrivateKey + ); + + ( + address cyberCorp, + address auth, + address issuanceManager, + address dealManagerAddr, + address cyberCertPrinterAddr, + bytes32 id + ) = cyberCorpFactory.deployCyberCorpAndCreateOffer( + block.timestamp, + "CyberCorp", + testAddress, + "SAFE", + "SAFE", + "ipfs.io/ipfs/[cid]", + SecurityClass.SAFE, + SecuritySeries.SeriesPreSeed, + bytes32(uint256(1)), + globalValues, + parties, + _paymentAmount, + partyValues, + signature, + _details, + bytes32(0), + block.timestamp + 1000000 + ); + + // Fast forward time to after expiry + vm.warp(block.timestamp + 1000001); + + uint256 newPartyPk = 80085; + address newPartyAddr = vm.addr(newPartyPk); + string[] memory partyValuesB = new string[](1); + partyValuesB[0] = "Party Value B"; + + vm.startPrank(newPartyAddr); + bytes memory newPartySignature = _signAgreementTypedData( + registry.DOMAIN_SEPARATOR(), + registry.SIGNATUREDATA_TYPEHASH(), + contractId, + "ipfs.io/ipfs/[cid]", + globalFields, + partyFields, + globalValues, + partyValuesB, + newPartyPk + ); + + deal( + 0x036CbD53842c5426634e7929541eC2318f3dCF7e, + newPartyAddr, + _paymentAmount + ); + IERC20(0x036CbD53842c5426634e7929541eC2318f3dCF7e).approve( + address(dealManagerAddr), + _paymentAmount + ); + + // Try to sign expired contract - should fail + vm.expectRevert(); + IDealManager(dealManagerAddr).signDealAndPay( + newPartyAddr, + id, + newPartySignature, + partyValuesB, + true, + "John Doe", + "" + ); + vm.stopPrank(); + } } From 400428287fa7bbc97ad3fb8e590890c4905c1cee Mon Sep 17 00:00:00 2001 From: merisman <6125373+merisman@users.noreply.github.com> Date: Wed, 9 Apr 2025 00:02:49 -0400 Subject: [PATCH 09/18] Added string[][] for partyValues throughout, other pr fixes. --- src/CyberCorpFactory.sol | 65 +---------- src/CyberDealRegistry.sol | 116 ++++++------------- src/DealManager.sol | 118 ++++++++----------- src/interfaces/ICyberDealRegistry.sol | 17 +-- src/interfaces/IDealManager.sol | 26 ++--- src/libs/LexScroWLite.sol | 7 +- test/CyberCorpTest.t.sol | 159 ++++++++++++++++---------- 7 files changed, 196 insertions(+), 312 deletions(-) diff --git a/src/CyberCorpFactory.sol b/src/CyberCorpFactory.sol index f99739b..bbc2c8a 100644 --- a/src/CyberCorpFactory.sol +++ b/src/CyberCorpFactory.sol @@ -110,9 +110,10 @@ contract CyberCorpFactory { string[] memory _globalValues, address[] memory _parties, uint256 _paymentAmount, - string[] memory _partyValues, + string[][] memory _partyValues, bytes memory signature, CertificateDetails memory _details, + address[] memory conditions, bytes32 secretHash, uint256 expiry ) external returns (address cyberCorpAddress, address authAddress, address issuanceManagerAddress, address dealManagerAddress, address certPrinterAddress, bytes32 id) { @@ -150,67 +151,7 @@ contract CyberCorpFactory { msg.sender, signature, _partyValues, - secretHash, - expiry - ); - - } - - function deployCyberCorpAndCreateClosedOffer( - uint256 salt, - string memory companyName, - address _companyPayable, - string memory certName, - string memory certSymbol, - string memory certificateUri, - SecurityClass securityClass, - SecuritySeries securitySeries, - bytes32 _templateId, - string[] memory _globalValues, - address[] memory _parties, - uint256 _paymentAmount, - string[] memory _partyValues, - bytes memory signature, - CertificateDetails memory _details, - string[] memory _counterPartyValues, - bytes32 secretHash, - uint256 expiry - ) external returns (address cyberCorpAddress, address authAddress, address issuanceManagerAddress, address dealManagerAddress, address certPrinterAddress, bytes32 id) { - - //create bytes32 salt - bytes32 corpSalt = keccak256(abi.encodePacked(salt)); - - (cyberCorpAddress, authAddress, issuanceManagerAddress, dealManagerAddress) = deployCyberCorp( - corpSalt, - companyName, - "", - "", - "", - "", - _companyPayable, - msg.sender - ); - - //append companyname " " and then the certName - string memory certNameWithCompany = string.concat(companyName, " ", certName); - ICyberCertPrinter certPrinter = ICyberCertPrinter(IIssuanceManager(issuanceManagerAddress).createCertPrinter("", certNameWithCompany, certSymbol, certificateUri, securityClass, securitySeries)); - certPrinterAddress = address(certPrinter); - - // Create and sign deal - uint256 certId; - (id, certId) = IDealManager(dealManagerAddress).proposeAndSignClosedDeal( - certPrinterAddress, - stable, - _paymentAmount, - _templateId, - salt, - _globalValues, - _parties, - _details, - msg.sender, - signature, - _partyValues, - _counterPartyValues, + conditions, secretHash, expiry ); diff --git a/src/CyberDealRegistry.sol b/src/CyberDealRegistry.sol index 0d74425..4b4b3a6 100644 --- a/src/CyberDealRegistry.sol +++ b/src/CyberDealRegistry.sol @@ -30,10 +30,10 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { mapping(address => string[]) partyValues; // Each signer's field data mapping(address => uint256) signedAt; // Timestamp when each party signed (0 if unsigned) uint256 numSignatures; // Number of parties who have signed - bytes32 transactionHash; // Hash of the transaction that created this contract address finalizer; bool finalized; bytes32 secretHash; + uint256 expiry; } // This data is what is signed by each party @@ -58,11 +58,7 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { // A mapping connecting an address to all the agreements they are a party to mapping(address => bytes32[]) public agreementsForParty; - mapping(bytes32 => address[]) public voidedBy; - - mapping(bytes32 => uint256) public expiry; - - mapping(bytes32 => bytes32) public secrets; + mapping(bytes32 => address[]) public voidRequestedBy; event TemplateCreated( bytes32 indexed templateId, @@ -105,6 +101,7 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { error ContractNotFullySigned(); error ContractExpired(); error InvalidSecret(); + error MismatchedPartyValuesLength(); /// @custom:oz-upgrades-unsafe-allow constructor constructor() { @@ -171,16 +168,17 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { uint256 salt, string[] memory globalValues, address[] memory parties, + string[][] memory partyValues, bytes32 secretHash, - address finalizer + address finalizer, + uint256 expiry ) external returns (bytes32 contractId) { - //create hash from templateId, globalValues, and parties contractId = keccak256(abi.encode(templateId, salt, globalValues, parties)); if (agreements[contractId].parties.length > 0) { revert ContractAlreadyExists(); } - Template storage template = templates[templateId]; + Template storage template = templates[templateId]; if (bytes(template.legalContractUri).length == 0) { revert TemplateDoesNotExist(); } @@ -205,71 +203,23 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { agreementData.templateId = templateId; agreementData.globalValues = globalValues; agreementData.parties = parties; - agreementData.transactionHash = blockhash(block.number - 1); // Store the transaction hash agreementData.finalizer = finalizer; - secrets[contractId] = secretHash; - emit ContractCreated(contractId, templateId, parties); - - // Add to the party's list of agreements - for (uint256 i = 0; i < parties.length; i++) { - agreementsForParty[parties[i]].push(contractId); - } - } - - function createClosedContract( - bytes32 templateId, - uint256 salt, - string[] memory globalValues, - address[] memory parties, - string[] memory creatingPartyValues, - string[] memory counterPartyValues, - bytes32 secretHash, - address finalizer - ) external returns (bytes32 contractId) { - //create hash from templateId, globalValues, and parties - contractId = keccak256(abi.encode(templateId, salt, globalValues, parties)); - if (agreements[contractId].parties.length > 0) { - revert ContractAlreadyExists(); - } - - Template storage template = templates[templateId]; - if (bytes(template.legalContractUri).length == 0) { - revert TemplateDoesNotExist(); - } + agreementData.expiry = expiry; + agreementData.secretHash = secretHash; - if (globalValues.length != template.globalFields.length) { - revert MismatchedFieldsLength(); - } - - if((creatingPartyValues.length != counterPartyValues.length) || (creatingPartyValues.length != template.partyFields.length) || (counterPartyValues.length != template.partyFields.length)) { - revert MismatchedFieldsLength(); - } - - if(parties.length != 2) { - revert InvalidPartyCount(); - } - - if (parties[0] == address(0) || parties[1] == address(0)) { - revert FirstPartyZeroAddress(); - } - - for (uint256 i = 0; i < parties.length; i++) { - for (uint256 j = i + 1; j < parties.length; j++) { - if (parties[i] == parties[j]) { - revert DuplicateParty(); - } + //check all arrays inside partyValues are the same length + for (uint256 i = 0; i < partyValues.length; i++) { + if (partyValues[i].length != template.partyFields.length) { + revert MismatchedFieldsLength(); } + //matching address cannot be 0 + if (parties[i] == address(0)) { + revert FirstPartyZeroAddress(); + } + //set agreement partyValues + agreements[contractId].partyValues[parties[i]] = partyValues[i]; } - AgreementData storage agreementData = agreements[contractId]; - agreementData.templateId = templateId; - agreementData.globalValues = globalValues; - agreementData.parties = parties; - agreementData.transactionHash = blockhash(block.number - 1); // Store the transaction hash - agreementData.partyValues[parties[0]] = creatingPartyValues; - agreementData.partyValues[parties[1]] = counterPartyValues; - agreementData.finalizer = finalizer; - secrets[contractId] = secretHash; emit ContractCreated(contractId, templateId, parties); // Add to the party's list of agreements @@ -302,10 +252,10 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { if (agreementData.signedAt[signer] != 0) revert AlreadySigned(); if (isVoided(contractId)) revert ContractAlreadyVoided(); if (agreementData.finalized) revert ContractAlreadyFinalized(); - if (expiry[contractId] > 0 && expiry[contractId] < block.timestamp) revert ContractExpired(); + if (agreementData.expiry > 0 && agreementData.expiry < block.timestamp) revert ContractExpired(); if (!isParty(contractId, signer)) { - if(secrets[contractId] > 0 && keccak256(abi.encode(secret)) != secrets[contractId]) revert InvalidSecret(); + if(agreementData.secretHash > 0 && keccak256(abi.encode(secret)) != agreementData.secretHash) revert InvalidSecret(); // Not a named party, so check if there's an open slot uint256 firstOpenPartyIndex = getFirstOpenPartyIndex(contractId); if (firstOpenPartyIndex == 0 || !fillUnallocated) @@ -346,6 +296,9 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { emit AgreementSigned(contractId, signer, timestamp); if (totalSignatures == agreementData.parties.length) { + if(agreementData.finalizer == address(0)) + agreementData.finalized = true; + emit ContractFullySigned(contractId, timestamp); } } @@ -354,6 +307,7 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { //make sure the party is a party to the contract if(!isParty(contractId, party)) revert NotAParty(); + AgreementData storage agreementData = agreements[contractId]; if (agreementData.finalized) revert ContractAlreadyFinalized(); @@ -367,11 +321,11 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { partyValues: agreementData.partyValues[party] }), signature)) revert SignatureVerificationFailed(); - for (uint256 i = 0; i < voidedBy[contractId].length; i++) { - if(voidedBy[contractId][i] == party) revert ContractAlreadyVoided(); + for (uint256 i = 0; i < voidRequestedBy[contractId].length; i++) { + if(voidRequestedBy[contractId][i] == party) revert ContractAlreadyVoided(); } - voidedBy[contractId].push(party); + voidRequestedBy[contractId].push(party); } function finalizeContract(bytes32 contractId) public onlyFinalizer(contractId) { @@ -380,7 +334,7 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { if(agreementData.parties.length == 0) revert ContractDoesNotExist(); if(!allPartiesSigned(contractId)) revert ContractNotFullySigned(); if(isVoided(contractId)) revert ContractAlreadyVoided(); - if(expiry[contractId] > 0 && expiry[contractId] < block.timestamp) revert ContractExpired(); + if(agreementData.expiry > 0 && agreementData.expiry < block.timestamp) revert ContractExpired(); agreementData.finalized = true; } @@ -427,8 +381,7 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { string[][] memory partyValues, uint256[] memory signedAt, uint256 numSignatures, - bool isComplete, - bytes32 transactionHash + bool isComplete ) { AgreementData storage agreementData = agreements[contractId]; @@ -456,8 +409,7 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { allPartyValues, allSignedAt, agreementData.numSignatures, - agreementData.numSignatures == agreementData.parties.length, - agreementData.transactionHash + agreementData.numSignatures == agreementData.parties.length ); } @@ -692,13 +644,9 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { } function isVoided(bytes32 contractId) public view returns (bool) { - return ((voidedBy[contractId].length == agreements[contractId].numSignatures && voidedBy[contractId].length > 0) || (voidedBy[contractId].length == 1 && expiry[contractId] < block.timestamp)); + return ((voidRequestedBy[contractId].length == agreements[contractId].numSignatures && voidRequestedBy[contractId].length > 0) || (voidRequestedBy[contractId].length == 1 && agreements[contractId].expiry < block.timestamp)); } function _authorizeUpgrade(address newImplementation) internal virtual override onlyOwner {} - // Add a function to get the transaction hash - function getContractTransactionHash(bytes32 contractId) external view returns (bytes32) { - return agreements[contractId].transactionHash; - } } diff --git a/src/DealManager.sol b/src/DealManager.sol index c062238..f7b9b0c 100644 --- a/src/DealManager.sol +++ b/src/DealManager.sol @@ -38,7 +38,9 @@ contract DealManager is Initializable, UUPSUpgradeable, BorgAuthACL, LexScroWLit error AgreementConditionsNotMet(); error DealNotPending(); - + error PartyValuesLengthMismatch(); + error ConditionAlreadyExists(); + error ConditionDoesNotExist(); /// @custom:oz-upgrades-unsafe-allow constructor constructor() { @@ -64,48 +66,14 @@ contract DealManager is Initializable, UUPSUpgradeable, BorgAuthACL, LexScroWLit string[] memory _globalValues, address[] memory _parties, CertificateDetails memory _certDetails, + string[][] memory _partyValues, + address[] memory conditions, bytes32 secretHash, uint256 expiry ) public onlyOwner returns (bytes32 agreementId, uint256 certId){ - certId = IIssuanceManager(ISSUANCE_MANAGER).createCert(_certPrinterAddress, address(this), _certDetails); - agreementId = ICyberDealRegistry(DEAL_REGISTRY).createContract(_templateId, _salt, _globalValues, _parties, secretHash, address(this)); - - Token[] memory corpAssets = new Token[](1); - corpAssets[0] = Token(TokenType.ERC721, _certPrinterAddress, certId, 1); - - Token[] memory buyerAssets = new Token[](1); - buyerAssets[0] = Token(TokenType.ERC20, _paymentToken, 0, _paymentAmount); - createEscrow(agreementId, _parties[1], corpAssets, buyerAssets, expiry); - - emit DealProposed( - agreementId, - _certPrinterAddress, - certId, - _paymentToken, - _paymentAmount, - _templateId, - CORP, - address(DEAL_REGISTRY), - _parties - ); - } - function proposeClosedDeal( - address _certPrinterAddress, - address _paymentToken, - uint256 _paymentAmount, - bytes32 _templateId, - uint256 _salt, - string[] memory _globalValues, - address[] memory _parties, - CertificateDetails memory _certDetails, - string[] memory _creatingPartyValues, - string[] memory _counterPartyValues, - bytes32 secretHash, - uint256 expiry - ) public onlyOwner returns (bytes32 agreementId, uint256 certId){ + agreementId = ICyberDealRegistry(DEAL_REGISTRY).createContract(_templateId, _salt, _globalValues, _parties, _partyValues, secretHash, address(this), expiry); certId = IIssuanceManager(ISSUANCE_MANAGER).createCert(_certPrinterAddress, address(this), _certDetails); - agreementId = ICyberDealRegistry(DEAL_REGISTRY).createClosedContract(_templateId, _salt, _globalValues, _parties, _creatingPartyValues, _counterPartyValues, secretHash, address(this)); Token[] memory corpAssets = new Token[](1); corpAssets[0] = Token(TokenType.ERC721, _certPrinterAddress, certId, 1); @@ -114,7 +82,12 @@ contract DealManager is Initializable, UUPSUpgradeable, BorgAuthACL, LexScroWLit buyerAssets[0] = Token(TokenType.ERC20, _paymentToken, 0, _paymentAmount); createEscrow(agreementId, _parties[1], corpAssets, buyerAssets, expiry); - emit DealProposed( + //set conditions + for(uint256 i = 0; i < conditions.length; i++) { + conditionsByEscrow[agreementId].push(ICondition(conditions[i])); + } + + emit DealProposed( agreementId, _certPrinterAddress, certId, @@ -138,37 +111,20 @@ contract DealManager is Initializable, UUPSUpgradeable, BorgAuthACL, LexScroWLit CertificateDetails memory _certDetails, address proposer, bytes memory signature, - string[] memory partyValues, // These are the party values for the proposer - bytes32 secretHash, - uint256 expiry - ) public returns (bytes32 agreementId, uint256 certId){ - (agreementId, certId) = proposeDeal(_certPrinterAddress, _paymentToken, _paymentAmount, _templateId, _salt, _globalValues, _parties, _certDetails, secretHash, expiry); - // NOTE: proposer is expected to be listed as a party in the parties array. - escrows[agreementId].signature = signature; - ICyberDealRegistry(DEAL_REGISTRY).signContractFor(proposer, agreementId, partyValues, signature, false, ""); - } - - function proposeAndSignClosedDeal( - address _certPrinterAddress, - address _paymentToken, - uint256 _paymentAmount, - bytes32 _templateId, - uint256 _salt, - string[] memory _globalValues, - address[] memory _parties, - CertificateDetails memory _certDetails, - address proposer, - bytes memory signature, - string[] memory partyValues, - string[] memory _counterPartyValues, + string[][] memory _partyValues, // These are the party values for the proposer + address[] memory conditions, bytes32 secretHash, uint256 expiry ) public returns (bytes32 agreementId, uint256 certId){ - (agreementId, certId) = proposeClosedDeal(_certPrinterAddress, _paymentToken, _paymentAmount, _templateId, _salt, _globalValues, _parties, _certDetails, partyValues, _counterPartyValues, secretHash, expiry); + if(_partyValues.length > _parties.length) revert PartyValuesLengthMismatch(); + (agreementId, certId) = proposeDeal(_certPrinterAddress, _paymentToken, _paymentAmount, _templateId, _salt, _globalValues, _parties, _certDetails, _partyValues, conditions, secretHash, expiry); // NOTE: proposer is expected to be listed as a party in the parties array. escrows[agreementId].signature = signature; - counterPartyValues[agreementId] = _counterPartyValues; - ICyberDealRegistry(DEAL_REGISTRY).signContractFor(proposer, agreementId, partyValues, signature, false, ""); + if(_partyValues.length > 1) { + if(_partyValues[1].length != _partyValues[0].length) revert PartyValuesLengthMismatch(); + counterPartyValues[agreementId] = _partyValues[1]; + } + ICyberDealRegistry(DEAL_REGISTRY).signContractFor(proposer, agreementId, _partyValues[0], signature, false, ""); } function signDealAndPay( @@ -186,6 +142,11 @@ contract DealManager is Initializable, UUPSUpgradeable, BorgAuthACL, LexScroWLit //check if the deal has expired if(escrows[agreementId].expiry < block.timestamp) revert DealExpired(); + string[] memory counterPartyCheck = counterPartyValues[agreementId]; + if(counterPartyCheck.length > 0) { + if (keccak256(abi.encode(counterPartyCheck)) != keccak256(abi.encode(partyValues))) revert CounterPartyValueMismatch(); + } + ICyberDealRegistry(DEAL_REGISTRY).signContractFor(signer, agreementId, partyValues, signature, _fillUnallocated, secret); updateEscrow(agreementId, msg.sender, name); handleCounterPartyPayment(agreementId); @@ -250,16 +211,31 @@ contract DealManager is Initializable, UUPSUpgradeable, BorgAuthACL, LexScroWLit function signToVoid(bytes32 agreementId, address signer, bytes memory signature) public { ICyberDealRegistry(DEAL_REGISTRY).voidContractFor(agreementId, signer, signature); if(ICyberDealRegistry(DEAL_REGISTRY).isVoided(agreementId) && escrows[agreementId].status == EscrowStatus.PAID) - voidEscrow(agreementId); + voidAndRefund(agreementId); } - /*function addCondition(Logic _op, address _condition) public onlyOwner { - _addCondition(_op, _condition); + function addCondition(bytes32 agreementId, address condition) public { + //make sure the contract is still pending + if(escrows[agreementId].status != EscrowStatus.PENDING) revert DealNotPending(); + //make sure the condition is not already in the list + for(uint256 i = 0; i < conditionsByEscrow[agreementId].length; i++) { + if(conditionsByEscrow[agreementId][i] == ICondition(condition)) revert ConditionAlreadyExists(); + } + conditionsByEscrow[agreementId].push(ICondition(condition)); } - function removeCondition(address _condition) public onlyOwner { - _removeCondition(_condition); - }*/ + function removeConditionAt(bytes32 agreementId, uint256 index) public { + //make sure the contract is still pending + if(escrows[agreementId].status != EscrowStatus.PENDING) revert DealNotPending(); + //make sure the condition is in the list + if(index >= conditionsByEscrow[agreementId].length) revert ConditionDoesNotExist(); + + //remove the index and shift the array + for(uint256 i = index; i < conditionsByEscrow[agreementId].length - 1; i++) { + conditionsByEscrow[agreementId][i] = conditionsByEscrow[agreementId][i + 1]; + } + conditionsByEscrow[agreementId].pop(); + } function _authorizeUpgrade(address newImplementation) internal virtual override onlyOwner {} diff --git a/src/interfaces/ICyberDealRegistry.sol b/src/interfaces/ICyberDealRegistry.sol index 2f0bec1..ae12f88 100644 --- a/src/interfaces/ICyberDealRegistry.sol +++ b/src/interfaces/ICyberDealRegistry.sol @@ -52,23 +52,12 @@ interface ICyberDealRegistry { uint256 salt, string[] memory globalValues, address[] memory parties, + string[][] memory partyValues, bytes32 secretHash, - address finalizer + address finalizer, + uint256 expiry ) external returns (bytes32); - function createClosedContract( - bytes32 templateId, - uint256 salt, - string[] memory globalValues, - address[] memory parties, - string[] memory creatingPartyValues, - string[] memory counterPartyValues, - bytes32 secretHash, - address finalizer - - - ) external returns (bytes32); - function signContract( bytes32 contractId, string[] memory partyValues, diff --git a/src/interfaces/IDealManager.sol b/src/interfaces/IDealManager.sol index a4573cd..1c85a1c 100644 --- a/src/interfaces/IDealManager.sol +++ b/src/interfaces/IDealManager.sol @@ -5,17 +5,6 @@ import "./IIssuanceManager.sol"; interface IDealManager { function proposeDeal( - address _certPrinterAddress, - address _paymentToken, - uint256 _paymentAmount, - bytes32 _templateId, - uint256 _salt, - string[] memory _globalValues, - address[] memory _parties, - CertificateDetails memory _certDetails - ) external returns (bytes32 agreementId); - - function proposeAndSignDeal( address _certPrinterAddress, address _paymentToken, uint256 _paymentAmount, @@ -24,15 +13,13 @@ interface IDealManager { string[] memory _globalValues, address[] memory _parties, CertificateDetails memory _certDetails, - address proposer, - bytes memory signature, - string[] memory paryValues, + string[][] memory _partyValues, + address[] memory conditions, bytes32 secretHash, uint256 expiry - ) external returns (bytes32 agreementId, uint256 certId); - + ) external returns (bytes32 agreementId); - function proposeAndSignClosedDeal( + function proposeAndSignDeal( address _certPrinterAddress, address _paymentToken, uint256 _paymentAmount, @@ -43,12 +30,13 @@ interface IDealManager { CertificateDetails memory _certDetails, address proposer, bytes memory signature, - string[] memory partyValues, - string[] memory counterPartyValues, + string[][] memory paryValues, + address[] memory conditions, bytes32 secretHash, uint256 expiry ) external returns (bytes32 agreementId, uint256 certId); + function finalizeDeal( address signer, bytes32 _agreementId, diff --git a/src/libs/LexScroWLite.sol b/src/libs/LexScroWLite.sol index 50760d5..0c3f0eb 100644 --- a/src/libs/LexScroWLite.sol +++ b/src/libs/LexScroWLite.sol @@ -60,6 +60,10 @@ abstract contract LexScroWLite is Initializable, ReentrancyGuard { error DealNotPaid(); error DealVoided(); + event DealVoidedAt(bytes32 agreementId, address counterParty, uint256 timestamp); + event DealPaidAt(bytes32 agreementId, address counterParty, uint256 timestamp); + event DealFinalizedAt(bytes32 agreementId, address counterParty, uint256 timestamp); + constructor() { } @@ -121,7 +125,7 @@ abstract contract LexScroWLite is Initializable, ReentrancyGuard { } } - deal.status = EscrowStatus.VOIDED; + voidEscrow(agreementId); } function finalizeEscrow(bytes32 agreementId) internal nonReentrant { @@ -176,6 +180,7 @@ abstract contract LexScroWLite is Initializable, ReentrancyGuard { function voidEscrow(bytes32 agreementId) internal { escrows[agreementId].status = EscrowStatus.VOIDED; + emit DealVoidedAt(agreementId, escrows[agreementId].counterParty, block.timestamp); } function getEscrowDetails(bytes32 agreementId) public view returns (Escrow memory) { diff --git a/test/CyberCorpTest.t.sol b/test/CyberCorpTest.t.sol index 94757bf..80da597 100644 --- a/test/CyberCorpTest.t.sol +++ b/test/CyberCorpTest.t.sol @@ -24,6 +24,7 @@ contract CyberCorpTest is Test { uint256 testPrivateKey; address testAddress; address counterPartyAddress = 0x1A762EfF397a3C519da3dF9FCDDdca7D1BD43B5e; + address[] conditions = new address[](0); function setUp() public { ///deploy cyberCertPrinterImplementation @@ -96,8 +97,9 @@ contract CyberCorpTest is Test { parties[0] = address(testAddress); parties[1] = address(0); uint256 _paymentAmount = 1000000000000000000; - string[] memory partyValues = new string[](1); - partyValues[0] = "Party Value 1"; + string[][] memory partyValues = new string[][](1); + partyValues[0] = new string[](1); + partyValues[0][0] = "Party Value 1"; bytes32 contractId = keccak256( abi.encode(bytes32(uint256(1)), block.timestamp, globalValues, parties) @@ -116,7 +118,7 @@ contract CyberCorpTest is Test { globalFields, partyFields, globalValues, - partyValues, + partyValues[0], testPrivateKey ); @@ -137,6 +139,7 @@ contract CyberCorpTest is Test { partyValues, signature, _details, + conditions, bytes32(0), block.timestamp + 1000000 ); @@ -163,8 +166,11 @@ contract CyberCorpTest is Test { parties[0] = address(testAddress); parties[1] = address(newPartyAddr); uint256 _paymentAmount = 1000000000000000000; - string[] memory partyValues = new string[](1); - partyValues[0] = "Party Value 1"; + string[][] memory partyValues = new string[][](2); + partyValues[0] = new string[](1); + partyValues[0][0] = "Party Value 1"; + partyValues[1] = new string[](1); + partyValues[1][0] = "Counter Party Value 1"; bytes32 contractId = keccak256( abi.encode(bytes32(uint256(1)), block.timestamp, globalValues, parties) @@ -175,6 +181,7 @@ contract CyberCorpTest is Test { string[] memory partyFields = new string[](1); partyFields[0] = "Party Field 1"; + bytes memory signature = _signAgreementTypedData( registry.DOMAIN_SEPARATOR(), registry.SIGNATUREDATA_TYPEHASH(), @@ -183,12 +190,10 @@ contract CyberCorpTest is Test { globalFields, partyFields, globalValues, - partyValues, + partyValues[0], testPrivateKey ); - string[] memory counterPartyValues = new string[](1); - counterPartyValues[0] = "Counter Party Value 1"; vm.startPrank(testAddress); ( @@ -198,7 +203,7 @@ contract CyberCorpTest is Test { address dealManagerAddr, address cyberCertPrinterAddr, bytes32 id - ) = cyberCorpFactory.deployCyberCorpAndCreateClosedOffer( + ) = cyberCorpFactory.deployCyberCorpAndCreateOffer( block.timestamp, "CyberCorp", testAddress, @@ -214,7 +219,7 @@ contract CyberCorpTest is Test { partyValues, signature, _details, - counterPartyValues, + conditions, bytes32(0), block.timestamp + 1000000 ); @@ -238,14 +243,14 @@ contract CyberCorpTest is Test { globalFields, partyFields, globalValues, - counterPartyValues, + partyValues[1], newPartyPk ); dealManager.signAndFinalizeDeal( newPartyAddr, contractId, - counterPartyValues, + partyValues[1], newPartySignature, true, "Counter Party Name", @@ -273,8 +278,9 @@ contract CyberCorpTest is Test { parties[0] = address(testAddress); parties[1] = address(0); uint256 _paymentAmount = 1000000000000000000; - string[] memory partyValues = new string[](1); - partyValues[0] = "Party Value 1"; + string[][] memory partyValues = new string[][](1); + partyValues[0] = new string[](1); + partyValues[0][0] = "Party Value 1"; bytes32 contractId = keccak256( abi.encode(bytes32(uint256(1)), block.timestamp, globalValues, parties) @@ -293,7 +299,7 @@ contract CyberCorpTest is Test { globalFields, partyFields, globalValues, - partyValues, + partyValues[0], testPrivateKey ); @@ -314,6 +320,7 @@ contract CyberCorpTest is Test { partyValues, signature, _details, + conditions, bytes32(0), block.timestamp + 1000000 ); @@ -338,8 +345,9 @@ contract CyberCorpTest is Test { partyFields[0] = "Party Field 1"; string[] memory globalValues = new string[](1); globalValues[0] = "Global Value 1"; - string[] memory partyValues = new string[](1); - partyValues[0] = "Party Value 1"; + string[][] memory partyValues = new string[][](1); + partyValues[0] = new string[](1); + partyValues[0][0] = "Party Value 1"; string[] memory partyValuesB = new string[](1); partyValuesB[0] = "Party Value B"; @@ -358,8 +366,10 @@ contract CyberCorpTest is Test { block.timestamp, globalValues, parties, + partyValues, bytes32(0), - address(testAddress) + address(testAddress), + block.timestamp + 1000000 ); bytes32 contractId = keccak256( @@ -374,14 +384,14 @@ contract CyberCorpTest is Test { globalFields, partyFields, globalValues, - partyValues, + partyValues[0], testPrivateKey ); registry.signContractFor( testAddress, id, - partyValues, + partyValues[0], signature, false, "" @@ -435,8 +445,9 @@ contract CyberCorpTest is Test { parties[0] = testAddress; parties[1] = address(0); uint256 _paymentAmount = 1000000000000000000; - string[] memory partyValues = new string[](1); - partyValues[0] = "Party Value 1"; + string[][] memory partyValues = new string[][](1); + partyValues[0] = new string[](1); + partyValues[0][0] = "Party Value 1"; bytes32 contractId = keccak256( abi.encode(bytes32(uint256(1)), block.timestamp, globalValues, parties) @@ -455,7 +466,7 @@ contract CyberCorpTest is Test { globalFields, partyFields, globalValues, - partyValues, + partyValues[0], testPrivateKey ); @@ -482,6 +493,7 @@ contract CyberCorpTest is Test { partyValues, proposerSignature, _details, + conditions, bytes32(0), block.timestamp + 1000000 ); @@ -544,8 +556,9 @@ contract CyberCorpTest is Test { parties[0] = testAddress; parties[1] = address(0); uint256 _paymentAmount = 1000000000000000000; - string[] memory partyValues = new string[](1); - partyValues[0] = "Party Value 1"; + string[][] memory partyValues = new string[][](1); + partyValues[0] = new string[](1); + partyValues[0][0] = "Party Value 1"; // Create secret hash from "passphrase" bytes32 secretHash = keccak256(abi.encodePacked("passphrase")); @@ -567,7 +580,7 @@ contract CyberCorpTest is Test { globalFields, partyFields, globalValues, - partyValues, + partyValues[0], testPrivateKey ); @@ -594,6 +607,7 @@ contract CyberCorpTest is Test { partyValues, proposerSignature, _details, + conditions, secretHash, block.timestamp + 1000000 ); @@ -659,8 +673,9 @@ contract CyberCorpTest is Test { parties[0] = testAddress; parties[1] = address(0); uint256 _paymentAmount = 1000000000000000000; - string[] memory partyValues = new string[](1); - partyValues[0] = "Party Value 1"; + string[][] memory partyValues = new string[][](1); + partyValues[0] = new string[](1); + partyValues[0][0] = "Party Value 1"; // Create secret hash from "passphrase" bytes32 secretHash = keccak256(abi.encode("passphrase")); @@ -682,7 +697,7 @@ contract CyberCorpTest is Test { globalFields, partyFields, globalValues, - partyValues, + partyValues[0], testPrivateKey ); @@ -709,6 +724,7 @@ contract CyberCorpTest is Test { partyValues, proposerSignature, _details, + conditions, secretHash, block.timestamp + 1000000 ); @@ -828,8 +844,9 @@ contract CyberCorpTest is Test { parties[0] = testAddress; parties[1] = address(0); uint256 _paymentAmount = 1000000000000000000; - string[] memory partyValues = new string[](1); - partyValues[0] = "Party Value 1"; + string[][] memory partyValues = new string[][](1); + partyValues[0] = new string[](1); + partyValues[0][0] = "Party Value 1"; bytes32 contractId = keccak256( abi.encode(bytes32(uint256(1)), block.timestamp, globalValues, parties) @@ -843,7 +860,7 @@ contract CyberCorpTest is Test { globalFields, partyFields, globalValues, - partyValues, + partyValues[0], testPrivateKey ); @@ -870,6 +887,7 @@ contract CyberCorpTest is Test { partyValues, signature, _details, + conditions, bytes32(0), block.timestamp + 1000000 ); @@ -901,8 +919,9 @@ contract CyberCorpTest is Test { parties[0] = testAddress; parties[1] = address(0); uint256 _paymentAmount = 1000000000000000000; - string[] memory partyValues = new string[](1); - partyValues[0] = "Party Value 1"; + string[][] memory partyValues = new string[][](1); + partyValues[0] = new string[](1); + partyValues[0][0] = "Party Value 1"; bytes32 contractId = keccak256( abi.encode(bytes32(uint256(1)), block.timestamp, globalValues, parties) @@ -916,7 +935,7 @@ contract CyberCorpTest is Test { globalFields, partyFields, globalValues, - partyValues, + partyValues[0], testPrivateKey ); @@ -943,6 +962,7 @@ contract CyberCorpTest is Test { partyValues, signature, _details, + conditions, bytes32(0), block.timestamp + 1000000 ); @@ -1007,8 +1027,9 @@ contract CyberCorpTest is Test { parties[0] = testAddress; parties[1] = address(0); uint256 _paymentAmount = 1000000000000000000; - string[] memory partyValues = new string[](1); - partyValues[0] = "Party Value 1"; + string[][] memory partyValues = new string[][](1); + partyValues[0] = new string[](1); + partyValues[0][0] = "Party Value 1"; bytes32 contractId = keccak256( abi.encode(bytes32(uint256(1)), block.timestamp, globalValues, parties) @@ -1022,7 +1043,7 @@ contract CyberCorpTest is Test { globalFields, partyFields, globalValues, - partyValues, + partyValues[0], testPrivateKey ); @@ -1049,6 +1070,7 @@ contract CyberCorpTest is Test { partyValues, signature, _details, + conditions, bytes32(0), block.timestamp + 1000000 ); @@ -1080,8 +1102,10 @@ contract CyberCorpTest is Test { parties[0] = testAddress; parties[1] = address(0); uint256 _paymentAmount = 1000000000000000000; - string[] memory partyValues = new string[](1); - partyValues[0] = "Party Value 1"; + string[][] memory partyValues = new string[][](1); + partyValues[0] = new string[](1); + partyValues[0][0] = "Party Value 1"; + bytes32 contractId = keccak256( abi.encode(bytes32(uint256(1)), block.timestamp, globalValues, parties) @@ -1095,7 +1119,7 @@ contract CyberCorpTest is Test { globalFields, partyFields, globalValues, - partyValues, + partyValues[0], testPrivateKey ); @@ -1122,6 +1146,7 @@ contract CyberCorpTest is Test { partyValues, signature, _details, + conditions, bytes32(0), block.timestamp + 1000000 ); @@ -1156,8 +1181,9 @@ contract CyberCorpTest is Test { parties[0] = testAddress; parties[1] = address(0); uint256 _paymentAmount = 1000000000000000000; - string[] memory partyValues = new string[](1); - partyValues[0] = "Party Value 1"; + string[][] memory partyValues = new string[][](1); + partyValues[0] = new string[](1); + partyValues[0][0] = "Party Value 1"; bytes32 contractId = keccak256( abi.encode(bytes32(uint256(1)), block.timestamp, globalValues, parties) @@ -1171,7 +1197,7 @@ contract CyberCorpTest is Test { globalFields, partyFields, globalValues, - partyValues, + partyValues[0], testPrivateKey ); @@ -1198,6 +1224,7 @@ contract CyberCorpTest is Test { partyValues, signature, _details, + conditions, bytes32(0), block.timestamp + 1000000 ); @@ -1207,7 +1234,7 @@ contract CyberCorpTest is Test { IDealManager(dealManagerAddr).finalizeDeal( testAddress, id, - partyValues, + partyValues[0], signature, false, "John Doe", @@ -1238,8 +1265,9 @@ contract CyberCorpTest is Test { parties[0] = testAddress; parties[1] = address(0); uint256 _paymentAmount = 1000000000000000000; - string[] memory partyValues = new string[](1); - partyValues[0] = "Party Value 1"; + string[][] memory partyValues = new string[][](1); + partyValues[0] = new string[](1); + partyValues[0][0] = "Party Value 1"; bytes32 contractId = keccak256( abi.encode(bytes32(uint256(1)), block.timestamp, globalValues, parties) @@ -1253,7 +1281,7 @@ contract CyberCorpTest is Test { globalFields, partyFields, globalValues, - partyValues, + partyValues[0], testPrivateKey ); @@ -1280,6 +1308,7 @@ contract CyberCorpTest is Test { partyValues, signature, _details, + conditions, bytes32(0), block.timestamp + 1000000 ); @@ -1346,8 +1375,9 @@ contract CyberCorpTest is Test { parties[0] = testAddress; parties[1] = address(0); uint256 _paymentAmount = 1000000000000000000; - string[] memory partyValues = new string[](1); - partyValues[0] = "Party Value 1"; + string[][] memory partyValues = new string[][](1); + partyValues[0] = new string[](1); + partyValues[0][0] = "Party Value 1"; bytes32 contractId = keccak256( abi.encode(bytes32(uint256(1)), block.timestamp, globalValues, parties) @@ -1361,7 +1391,7 @@ contract CyberCorpTest is Test { globalFields, partyFields, globalValues, - partyValues, + partyValues[0], testPrivateKey ); @@ -1388,6 +1418,7 @@ contract CyberCorpTest is Test { partyValues, signature, _details, + conditions, bytes32(0), block.timestamp + 1000000 ); @@ -1435,7 +1466,7 @@ contract CyberCorpTest is Test { IDealManager(dealManagerAddr).finalizeDeal( testAddress, id, - partyValues, + partyValues[0], signature, false, "", @@ -1466,8 +1497,9 @@ contract CyberCorpTest is Test { parties[0] = testAddress; parties[1] = address(0); uint256 _paymentAmount = 1000000000000000000; - string[] memory partyValues = new string[](1); - partyValues[0] = "Party Value 1"; + string[][] memory partyValues = new string[][](1); + partyValues[0] = new string[](1); + partyValues[0][0] = "Party Value 1"; bytes32 contractId = keccak256( abi.encode(bytes32(uint256(1)), block.timestamp, globalValues, parties) @@ -1481,7 +1513,7 @@ contract CyberCorpTest is Test { globalFields, partyFields, globalValues, - partyValues, + partyValues[0], testPrivateKey ); @@ -1508,6 +1540,7 @@ contract CyberCorpTest is Test { partyValues, signature, _details, + conditions, bytes32(0), block.timestamp + 1000000 ); @@ -1578,8 +1611,9 @@ contract CyberCorpTest is Test { parties[0] = testAddress; parties[1] = address(0); uint256 _paymentAmount = 1000000000000000000; - string[] memory partyValues = new string[](1); - partyValues[0] = "Party Value 1"; + string[][] memory partyValues = new string[][](1); + partyValues[0] = new string[](1); + partyValues[0][0] = "Party Value 1"; bytes32 secretHash = keccak256(abi.encodePacked("passphrase")); @@ -1595,7 +1629,7 @@ contract CyberCorpTest is Test { globalFields, partyFields, globalValues, - partyValues, + partyValues[0], testPrivateKey ); @@ -1622,6 +1656,7 @@ contract CyberCorpTest is Test { partyValues, signature, _details, + conditions, secretHash, block.timestamp + 1000000 ); @@ -1691,8 +1726,9 @@ contract CyberCorpTest is Test { parties[0] = testAddress; parties[1] = address(0); uint256 _paymentAmount = 1000000000000000000; - string[] memory partyValues = new string[](1); - partyValues[0] = "Party Value 1"; + string[][] memory partyValues = new string[][](1); + partyValues[0] = new string[](1); + partyValues[0][0] = "Party Value 1"; bytes32 contractId = keccak256( abi.encode(bytes32(uint256(1)), block.timestamp, globalValues, parties) @@ -1706,7 +1742,7 @@ contract CyberCorpTest is Test { globalFields, partyFields, globalValues, - partyValues, + partyValues[0], testPrivateKey ); @@ -1733,6 +1769,7 @@ contract CyberCorpTest is Test { partyValues, signature, _details, + conditions, bytes32(0), block.timestamp + 1000000 ); From 2a9177c0f3772da87e425eda0d6af5f2dbac2b21 Mon Sep 17 00:00:00 2001 From: merisman <6125373+merisman@users.noreply.github.com> Date: Wed, 9 Apr 2025 00:04:23 -0400 Subject: [PATCH 10/18] adding auth on add/remove conditions --- src/DealManager.sol | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/DealManager.sol b/src/DealManager.sol index f7b9b0c..bc7fbf1 100644 --- a/src/DealManager.sol +++ b/src/DealManager.sol @@ -214,7 +214,7 @@ contract DealManager is Initializable, UUPSUpgradeable, BorgAuthACL, LexScroWLit voidAndRefund(agreementId); } - function addCondition(bytes32 agreementId, address condition) public { + function addCondition(bytes32 agreementId, address condition) public onlyOwner { //make sure the contract is still pending if(escrows[agreementId].status != EscrowStatus.PENDING) revert DealNotPending(); //make sure the condition is not already in the list @@ -224,7 +224,7 @@ contract DealManager is Initializable, UUPSUpgradeable, BorgAuthACL, LexScroWLit conditionsByEscrow[agreementId].push(ICondition(condition)); } - function removeConditionAt(bytes32 agreementId, uint256 index) public { + function removeConditionAt(bytes32 agreementId, uint256 index) public onlyOwner { //make sure the contract is still pending if(escrows[agreementId].status != EscrowStatus.PENDING) revert DealNotPending(); //make sure the condition is in the list @@ -237,6 +237,5 @@ contract DealManager is Initializable, UUPSUpgradeable, BorgAuthACL, LexScroWLit conditionsByEscrow[agreementId].pop(); } - function _authorizeUpgrade(address newImplementation) internal virtual override onlyOwner {} } From 3cb172252b40a422667bd2fa6c5d9a7f48fd6055 Mon Sep 17 00:00:00 2001 From: merisman <6125373+merisman@users.noreply.github.com> Date: Wed, 9 Apr 2025 00:16:56 -0400 Subject: [PATCH 11/18] Updates to voiding, adding timestamped event on void --- src/CyberDealRegistry.sol | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/CyberDealRegistry.sol b/src/CyberDealRegistry.sol index 4b4b3a6..a156cad 100644 --- a/src/CyberDealRegistry.sol +++ b/src/CyberDealRegistry.sol @@ -32,6 +32,7 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { uint256 numSignatures; // Number of parties who have signed address finalizer; bool finalized; + bool voided; bytes32 secretHash; uint256 expiry; } @@ -80,6 +81,12 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { uint256 timestamp ); + event ContractVoided( + bytes32 indexed contractId, + address[] voidSigners, + uint256 timestamp + ); + event ContractFullySigned(bytes32 indexed contractId, uint256 timestamp); error TemplateAlreadyExists(); @@ -326,6 +333,26 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { } voidRequestedBy[contractId].push(party); + + //void the contract if the number of void requests is equal to the number of parties or if the expiry is in the past + if(voidRequestedBy[contractId].length == agreements[contractId].parties.length && voidRequestedBy[contractId].length > 0) + { + agreementData.voided = true; + } + else if (voidRequestedBy[contractId].length == 1 && agreements[contractId].expiry < block.timestamp) + { + agreementData.voided = true; + } + + for (uint256 i = 0; i < agreementData.parties.length; i++) { + if(agreementData.parties[0] == party && agreementData.numSignatures == 1) + { + agreementData.voided = true; + } + } + + if(agreementData.voided) + emit ContractVoided(contractId, voidRequestedBy[contractId], block.timestamp); } function finalizeContract(bytes32 contractId) public onlyFinalizer(contractId) { @@ -644,7 +671,7 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { } function isVoided(bytes32 contractId) public view returns (bool) { - return ((voidRequestedBy[contractId].length == agreements[contractId].numSignatures && voidRequestedBy[contractId].length > 0) || (voidRequestedBy[contractId].length == 1 && agreements[contractId].expiry < block.timestamp)); + return agreements[contractId].voided; } function _authorizeUpgrade(address newImplementation) internal virtual override onlyOwner {} From fce5c0f73112ad1a4fad95f8580ee88109e9788b Mon Sep 17 00:00:00 2001 From: merisman <6125373+merisman@users.noreply.github.com> Date: Wed, 9 Apr 2025 00:17:46 -0400 Subject: [PATCH 12/18] Adding finalization events. --- src/CyberDealRegistry.sol | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/CyberDealRegistry.sol b/src/CyberDealRegistry.sol index a156cad..70f3bc7 100644 --- a/src/CyberDealRegistry.sol +++ b/src/CyberDealRegistry.sol @@ -87,6 +87,12 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { uint256 timestamp ); + event ContractFinalized( + bytes32 indexed contractId, + address finalizer, + uint256 timestamp + ); + event ContractFullySigned(bytes32 indexed contractId, uint256 timestamp); error TemplateAlreadyExists(); @@ -304,7 +310,10 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { if (totalSignatures == agreementData.parties.length) { if(agreementData.finalizer == address(0)) + { agreementData.finalized = true; + emit ContractFinalized(contractId, msg.sender, timestamp); + } emit ContractFullySigned(contractId, timestamp); } @@ -364,6 +373,7 @@ contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { if(agreementData.expiry > 0 && agreementData.expiry < block.timestamp) revert ContractExpired(); agreementData.finalized = true; + emit ContractFinalized(contractId, msg.sender, block.timestamp); } function getParties( From 89d09458c56097de62213d966c092a712df42757 Mon Sep 17 00:00:00 2001 From: merisman <6125373+merisman@users.noreply.github.com> Date: Wed, 9 Apr 2025 00:25:55 -0400 Subject: [PATCH 13/18] Renaming CyberDealRegistry => CyberAgreementRegistry --- script/deploy.s.sol | 10 +++--- ...egistry.sol => CyberAgreementRegistry.sol} | 4 +-- src/CyberCorpFactory.sol | 2 +- src/DealManager.sol | 34 +++++++++---------- ...gistry.sol => ICyberAgreementRegistry.sol} | 2 +- src/libs/LexScroWLite.sol | 8 ++--- test/CyberCorpTest.t.sol | 10 +++--- 7 files changed, 35 insertions(+), 35 deletions(-) rename src/{CyberDealRegistry.sol => CyberAgreementRegistry.sol} (99%) rename src/interfaces/{ICyberDealRegistry.sol => ICyberAgreementRegistry.sol} (99%) diff --git a/script/deploy.s.sol b/script/deploy.s.sol index a1fb6b3..17841ae 100644 --- a/script/deploy.s.sol +++ b/script/deploy.s.sol @@ -10,7 +10,7 @@ import {IssuanceManagerFactory} from "../src/IssuanceManagerFactory.sol"; import {CyberCorpSingleFactory} from "../src/CyberCorpSingleFactory.sol"; import {CyberAgreementFactory} from "../src/CyberAgreementFactory.sol"; import {BorgAuth} from "../src/libs/auth.sol"; -import {CyberDealRegistry} from "../src/CyberDealRegistry.sol"; +import {CyberAgreementRegistry} from "../src/CyberAgreementRegistry.sol"; import {DealManagerFactory} from "../src/DealManagerFactory.sol"; import {IDealManager} from "../src/interfaces/IDealManager.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; @@ -30,18 +30,18 @@ contract BaseScript is Script { cyberCertPrinter.initialize("", "", "", "ipfs.io/ipfs/[cid]", address(0), SecurityClass.SAFE, SecuritySeries.SeriesPreSeed); address cyberCorpSingleFactory = address(new CyberCorpSingleFactory()); address dealManagerFactory = address(new DealManagerFactory()); - address registry = address(new CyberDealRegistry()); - CyberDealRegistry(registry).initialize(address(auth)); + address registry = address(new CyberAgreementRegistry()); + CyberAgreementRegistry(registry).initialize(address(auth)); string[] memory globalFields = new string[](1); globalFields[0] = "Global Field 1"; string[] memory partyFields = new string[](1); partyFields[0] = "Party Field 1"; - CyberDealRegistry(registry).createTemplate(bytes32(uint256(1)), "SAFE", "ipfs.io/ipfs/[cid]", globalFields, partyFields); + CyberAgreementRegistry(registry).createTemplate(bytes32(uint256(1)), "SAFE", "ipfs.io/ipfs/[cid]", globalFields, partyFields); CyberCorpFactory cyberCorpFactory = new CyberCorpFactory(address(registry), cyberCertPrinterImplementation, issuanceManagerFactory, cyberCorpSingleFactory, dealManagerFactory); console.log("cyberCertPrinterImplementation: ", address(cyberCertPrinterImplementation)); - console.log("CyberDealRegistry: ", address(registry)); + console.log("CyberAgreementRegistry: ", address(registry)); console.log("CyberCorpFactory: ", address(cyberCorpFactory)); /* address deployerAddress = vm.addr(vm.envUint("PRIVATE_KEY_MAIN")); diff --git a/src/CyberDealRegistry.sol b/src/CyberAgreementRegistry.sol similarity index 99% rename from src/CyberDealRegistry.sol rename to src/CyberAgreementRegistry.sol index 70f3bc7..ea43c28 100644 --- a/src/CyberDealRegistry.sol +++ b/src/CyberAgreementRegistry.sol @@ -6,11 +6,11 @@ import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; import "./libs/auth.sol"; import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; -contract CyberDealRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { +contract CyberAgreementRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { using ECDSA for bytes32; // Domain information - string public constant name = "CyberDealRegistry"; + string public constant name = "CyberAgreementRegistry"; string public version; bytes32 public DOMAIN_SEPARATOR; // Type hash for AgreementData diff --git a/src/CyberCorpFactory.sol b/src/CyberCorpFactory.sol index bbc2c8a..79de400 100644 --- a/src/CyberCorpFactory.sol +++ b/src/CyberCorpFactory.sol @@ -11,7 +11,7 @@ import "./interfaces/IDealManager.sol"; import "./interfaces/ICyberCorpSingleFactory.sol"; import "./interfaces/ICyberCertPrinter.sol"; import "./interfaces/ICyberAgreementFactory.sol"; -import "./interfaces/ICyberDealRegistry.sol"; +import "./interfaces/ICyberAgreementRegistry.sol"; import "./CyberCorpConstants.sol"; contract CyberCorpFactory { diff --git a/src/DealManager.sol b/src/DealManager.sol index bc7fbf1..91fdc7d 100644 --- a/src/DealManager.sol +++ b/src/DealManager.sol @@ -72,7 +72,7 @@ contract DealManager is Initializable, UUPSUpgradeable, BorgAuthACL, LexScroWLit uint256 expiry ) public onlyOwner returns (bytes32 agreementId, uint256 certId){ - agreementId = ICyberDealRegistry(DEAL_REGISTRY).createContract(_templateId, _salt, _globalValues, _parties, _partyValues, secretHash, address(this), expiry); + agreementId = ICyberAgreementRegistry(DEAL_REGISTRY).createContract(_templateId, _salt, _globalValues, _parties, _partyValues, secretHash, address(this), expiry); certId = IIssuanceManager(ISSUANCE_MANAGER).createCert(_certPrinterAddress, address(this), _certDetails); Token[] memory corpAssets = new Token[](1); @@ -124,7 +124,7 @@ contract DealManager is Initializable, UUPSUpgradeable, BorgAuthACL, LexScroWLit if(_partyValues[1].length != _partyValues[0].length) revert PartyValuesLengthMismatch(); counterPartyValues[agreementId] = _partyValues[1]; } - ICyberDealRegistry(DEAL_REGISTRY).signContractFor(proposer, agreementId, _partyValues[0], signature, false, ""); + ICyberAgreementRegistry(DEAL_REGISTRY).signContractFor(proposer, agreementId, _partyValues[0], signature, false, ""); } function signDealAndPay( @@ -136,8 +136,8 @@ contract DealManager is Initializable, UUPSUpgradeable, BorgAuthACL, LexScroWLit string memory name, string memory secret ) public { - if(ICyberDealRegistry(DEAL_REGISTRY).isVoided(agreementId)) revert DealVoided(); - if(ICyberDealRegistry(DEAL_REGISTRY).isFinalized(agreementId)) revert DealAlreadyFinalized(); + if(ICyberAgreementRegistry(DEAL_REGISTRY).isVoided(agreementId)) revert DealVoided(); + if(ICyberAgreementRegistry(DEAL_REGISTRY).isFinalized(agreementId)) revert DealAlreadyFinalized(); if(escrows[agreementId].status != EscrowStatus.PENDING) revert DealNotPending(); //check if the deal has expired if(escrows[agreementId].expiry < block.timestamp) revert DealExpired(); @@ -147,14 +147,14 @@ contract DealManager is Initializable, UUPSUpgradeable, BorgAuthACL, LexScroWLit if (keccak256(abi.encode(counterPartyCheck)) != keccak256(abi.encode(partyValues))) revert CounterPartyValueMismatch(); } - ICyberDealRegistry(DEAL_REGISTRY).signContractFor(signer, agreementId, partyValues, signature, _fillUnallocated, secret); + ICyberAgreementRegistry(DEAL_REGISTRY).signContractFor(signer, agreementId, partyValues, signature, _fillUnallocated, secret); updateEscrow(agreementId, msg.sender, name); handleCounterPartyPayment(agreementId); } function signAndFinalizeDeal(address signer, bytes32 agreementId, string[] memory partyValues, bytes memory signature, bool _fillUnallocated, string memory name, string memory secret) public { - if(ICyberDealRegistry(DEAL_REGISTRY).isVoided(agreementId)) revert DealVoided(); - if(ICyberDealRegistry(DEAL_REGISTRY).isFinalized(agreementId)) revert DealAlreadyFinalized(); + if(ICyberAgreementRegistry(DEAL_REGISTRY).isVoided(agreementId)) revert DealVoided(); + if(ICyberAgreementRegistry(DEAL_REGISTRY).isFinalized(agreementId)) revert DealAlreadyFinalized(); if(escrows[agreementId].status != EscrowStatus.PENDING) revert DealNotPending(); string[] memory counterPartyCheck = counterPartyValues[agreementId]; @@ -164,8 +164,8 @@ contract DealManager is Initializable, UUPSUpgradeable, BorgAuthACL, LexScroWLit if(!conditionCheck(agreementId)) revert AgreementConditionsNotMet(); - if(!ICyberDealRegistry(DEAL_REGISTRY).hasSigned(agreementId, signer)) - ICyberDealRegistry(DEAL_REGISTRY).signContractFor(signer, agreementId, partyValues, signature, _fillUnallocated, secret); + if(!ICyberAgreementRegistry(DEAL_REGISTRY).hasSigned(agreementId, signer)) + ICyberAgreementRegistry(DEAL_REGISTRY).signContractFor(signer, agreementId, partyValues, signature, _fillUnallocated, secret); updateEscrow(agreementId, msg.sender, name); handleCounterPartyPayment(agreementId); @@ -173,13 +173,13 @@ contract DealManager is Initializable, UUPSUpgradeable, BorgAuthACL, LexScroWLit } function finalizeDeal(bytes32 agreementId) public { - if(ICyberDealRegistry(DEAL_REGISTRY).isVoided(agreementId)) revert DealVoided(); + if(ICyberAgreementRegistry(DEAL_REGISTRY).isVoided(agreementId)) revert DealVoided(); if(escrows[agreementId].status != EscrowStatus.PAID) revert DealNotPaid(); - if(ICyberDealRegistry(DEAL_REGISTRY).isFinalized(agreementId)) revert DealAlreadyFinalized(); - if(!ICyberDealRegistry(DEAL_REGISTRY).allPartiesSigned(agreementId)) revert DealNotFullySigned(); + if(ICyberAgreementRegistry(DEAL_REGISTRY).isFinalized(agreementId)) revert DealAlreadyFinalized(); + if(!ICyberAgreementRegistry(DEAL_REGISTRY).allPartiesSigned(agreementId)) revert DealNotFullySigned(); if(!conditionCheck(agreementId)) revert AgreementConditionsNotMet(); - ICyberDealRegistry(DEAL_REGISTRY).finalizeContract(agreementId); + ICyberAgreementRegistry(DEAL_REGISTRY).finalizeContract(agreementId); finalizeEscrow(agreementId); emit DealFinalized( agreementId, @@ -198,19 +198,19 @@ contract DealManager is Initializable, UUPSUpgradeable, BorgAuthACL, LexScroWLit } } voidEscrow(agreementId); - ICyberDealRegistry(DEAL_REGISTRY).voidContractFor(agreementId, signer, signature); + ICyberAgreementRegistry(DEAL_REGISTRY).voidContractFor(agreementId, signer, signature); } function revokeDeal(bytes32 agreementId, address signer, bytes memory signature) public { if(escrows[agreementId].status == EscrowStatus.PENDING) - ICyberDealRegistry(DEAL_REGISTRY).voidContractFor(agreementId, signer, signature); + ICyberAgreementRegistry(DEAL_REGISTRY).voidContractFor(agreementId, signer, signature); else revert DealNotPending(); } function signToVoid(bytes32 agreementId, address signer, bytes memory signature) public { - ICyberDealRegistry(DEAL_REGISTRY).voidContractFor(agreementId, signer, signature); - if(ICyberDealRegistry(DEAL_REGISTRY).isVoided(agreementId) && escrows[agreementId].status == EscrowStatus.PAID) + ICyberAgreementRegistry(DEAL_REGISTRY).voidContractFor(agreementId, signer, signature); + if(ICyberAgreementRegistry(DEAL_REGISTRY).isVoided(agreementId) && escrows[agreementId].status == EscrowStatus.PAID) voidAndRefund(agreementId); } diff --git a/src/interfaces/ICyberDealRegistry.sol b/src/interfaces/ICyberAgreementRegistry.sol similarity index 99% rename from src/interfaces/ICyberDealRegistry.sol rename to src/interfaces/ICyberAgreementRegistry.sol index ae12f88..ad179e3 100644 --- a/src/interfaces/ICyberDealRegistry.sol +++ b/src/interfaces/ICyberAgreementRegistry.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: unlicensed pragma solidity ^0.8.0; -interface ICyberDealRegistry { +interface ICyberAgreementRegistry { struct Template { string legalContractUri; string title; diff --git a/src/libs/LexScroWLite.sol b/src/libs/LexScroWLite.sol index 0c3f0eb..3bc44fc 100644 --- a/src/libs/LexScroWLite.sol +++ b/src/libs/LexScroWLite.sol @@ -5,7 +5,7 @@ import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol"; import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import "../interfaces/ICyberCorp.sol"; -import "../interfaces/ICyberDealRegistry.sol"; +import "../interfaces/ICyberAgreementRegistry.sol"; import "../interfaces/ICyberCertPrinter.sol"; import "../interfaces/ICondition.sol"; import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; @@ -14,7 +14,7 @@ import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; abstract contract LexScroWLite is Initializable, ReentrancyGuard { address public CORP; - ICyberDealRegistry public DEAL_REGISTRY; + ICyberAgreementRegistry public DEAL_REGISTRY; enum TokenType { ERC20, @@ -69,7 +69,7 @@ abstract contract LexScroWLite is Initializable, ReentrancyGuard { function __LexScroWLite_init(address _corp, address _dealRegistry) internal onlyInitializing { CORP = _corp; - DEAL_REGISTRY = ICyberDealRegistry(_dealRegistry); + DEAL_REGISTRY = ICyberAgreementRegistry(_dealRegistry); } function createEscrow(bytes32 agreementId, address counterParty, Token[] memory corpAssets, Token[] memory buyerAssets, uint256 expiry) internal { @@ -110,7 +110,7 @@ abstract contract LexScroWLite is Initializable, ReentrancyGuard { function voidAndRefund(bytes32 agreementId) internal nonReentrant { Escrow storage deal = escrows[agreementId]; if(deal.status != EscrowStatus.PAID) revert EscrowNotPaid(); - if(!ICyberDealRegistry(DEAL_REGISTRY).isVoided(agreementId)) revert DealNotVoided(); + if(!ICyberAgreementRegistry(DEAL_REGISTRY).isVoided(agreementId)) revert DealNotVoided(); // Refund buyer assets first for(uint256 i = 0; i < deal.buyerAssets.length; i++) { diff --git a/test/CyberCorpTest.t.sol b/test/CyberCorpTest.t.sol index 80da597..6bcf5fe 100644 --- a/test/CyberCorpTest.t.sol +++ b/test/CyberCorpTest.t.sol @@ -10,7 +10,7 @@ import {CyberCorpSingleFactory} from "../src/CyberCorpSingleFactory.sol"; import {CyberAgreementFactory} from "../src/CyberAgreementFactory.sol"; import "../src/CyberCorpConstants.sol"; import {BorgAuth} from "../src/libs/auth.sol"; -import {CyberDealRegistry} from "../src/CyberDealRegistry.sol"; +import {CyberAgreementRegistry} from "../src/CyberAgreementRegistry.sol"; import {DealManagerFactory} from "../src/DealManagerFactory.sol"; import {IDealManager} from "../src/interfaces/IDealManager.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; @@ -20,7 +20,7 @@ contract CyberCorpTest is Test { // Counter public counter; CyberCorpFactory cyberCorpFactory; - CyberDealRegistry registry; + CyberAgreementRegistry registry; uint256 testPrivateKey; address testAddress; address counterPartyAddress = 0x1A762EfF397a3C519da3dF9FCDDdca7D1BD43B5e; @@ -55,8 +55,8 @@ contract CyberCorpTest is Test { address dealManagerFactory = address(new DealManagerFactory()); - registry = new CyberDealRegistry(); - CyberDealRegistry(registry).initialize(address(auth)); + registry = new CyberAgreementRegistry(); + CyberAgreementRegistry(registry).initialize(address(auth)); string[] memory globalFields = new string[](1); globalFields[0] = "Global Field 1"; string[] memory partyFields = new string[](1); @@ -337,7 +337,7 @@ contract CyberCorpTest is Test { vm.startPrank(testAddress); BorgAuth auth = new BorgAuth(); auth.initialize(); - CyberDealRegistry registry = new CyberDealRegistry(); + CyberAgreementRegistry registry = new CyberAgreementRegistry(); registry.initialize(address(auth)); string[] memory globalFields = new string[](1); globalFields[0] = "Global Field 1"; From 973108c0d88e9e0a583b578ffe28291a32c39f73 Mon Sep 17 00:00:00 2001 From: merisman <6125373+merisman@users.noreply.github.com> Date: Wed, 9 Apr 2025 15:06:14 -0400 Subject: [PATCH 14/18] Updates to voidRequestedBy, adding counterparty values --- src/CyberAgreementRegistry.sol | 47 ++++++++++++++++++++++------------ src/DealManager.sol | 7 +++-- 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/src/CyberAgreementRegistry.sol b/src/CyberAgreementRegistry.sol index ea43c28..63e8174 100644 --- a/src/CyberAgreementRegistry.sol +++ b/src/CyberAgreementRegistry.sol @@ -35,6 +35,7 @@ contract CyberAgreementRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { bool voided; bytes32 secretHash; uint256 expiry; + address[] voidRequestedBy; } // This data is what is signed by each party @@ -59,8 +60,6 @@ contract CyberAgreementRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { // A mapping connecting an address to all the agreements they are a party to mapping(address => bytes32[]) public agreementsForParty; - mapping(bytes32 => address[]) public voidRequestedBy; - event TemplateCreated( bytes32 indexed templateId, string indexed title, @@ -141,7 +140,7 @@ contract CyberAgreementRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { } modifier onlyFinalizer(bytes32 contractId) { - if(agreements[contractId].finalizer != msg.sender) revert NotFinalizer(); + if(agreements[contractId].finalizer != msg.sender && agreements[contractId].finalizer != address(0)) revert NotFinalizer(); _; } @@ -275,6 +274,7 @@ contract CyberAgreementRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { revert NotAParty(); // There is a spare slot, assign the sender to this slot. agreementData.parties[firstOpenPartyIndex] = signer; + agreementsForParty[agreementData.parties[firstOpenPartyIndex]].push(contractId); } //verify if the contract is closed @@ -337,31 +337,28 @@ contract CyberAgreementRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { partyValues: agreementData.partyValues[party] }), signature)) revert SignatureVerificationFailed(); - for (uint256 i = 0; i < voidRequestedBy[contractId].length; i++) { - if(voidRequestedBy[contractId][i] == party) revert ContractAlreadyVoided(); + for (uint256 i = 0; i < agreementData.voidRequestedBy.length; i++) { + if(agreementData.voidRequestedBy[i] == party) revert ContractAlreadyVoided(); } - voidRequestedBy[contractId].push(party); + agreementData.voidRequestedBy.push(party); + - //void the contract if the number of void requests is equal to the number of parties or if the expiry is in the past - if(voidRequestedBy[contractId].length == agreements[contractId].parties.length && voidRequestedBy[contractId].length > 0) + if(agreementData.expiry < block.timestamp) { agreementData.voided = true; } - else if (voidRequestedBy[contractId].length == 1 && agreements[contractId].expiry < block.timestamp) + else if(agreementData.voidRequestedBy.length == agreementData.parties.length && agreementData.voidRequestedBy.length > 0) { agreementData.voided = true; } - - for (uint256 i = 0; i < agreementData.parties.length; i++) { - if(agreementData.parties[0] == party && agreementData.numSignatures == 1) - { - agreementData.voided = true; - } + else if(agreementData.parties[0] == party && agreementData.numSignatures == 1) + { + agreementData.voided = true; } if(agreementData.voided) - emit ContractVoided(contractId, voidRequestedBy[contractId], block.timestamp); + emit ContractVoided(contractId, agreementData.voidRequestedBy, block.timestamp); } function finalizeContract(bytes32 contractId) public onlyFinalizer(contractId) { @@ -517,6 +514,10 @@ contract CyberAgreementRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { return 0; } + function getVoidRequestedBy(bytes32 contractId) external view returns (address[] memory) { + return agreements[contractId].voidRequestedBy; + } + function getContractJson(bytes32 contractId) external view returns (string memory) { AgreementData storage agreementData = agreements[contractId]; Template storage template = templates[agreementData.templateId]; @@ -580,7 +581,19 @@ contract CyberAgreementRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { // Add metadata json = string.concat(json, '}, "numSignatures": ', _uint256ToString(agreementData.numSignatures)); json = string.concat(json, ', "isComplete": ', agreementData.numSignatures == agreementData.parties.length ? 'true' : 'false'); - json = string.concat(json, '}'); + // Add voided status + json = string.concat(json, ', "voided": ', agreementData.voided ? 'true' : 'false'); + // loop and add voidRequestedBy + json = string.concat(json, ', "voidRequestedBy": ['); + for (uint256 i = 0; i < agreementData.voidRequestedBy.length; i++) { + json = string.concat(json, _addressToString(agreementData.voidRequestedBy[i])); + if (i + 1 < agreementData.voidRequestedBy.length) { + json = string.concat(json, ','); + } + } + // add finalized status + json = string.concat(json, ', "finalized": ', agreementData.finalized ? 'true' : 'false'); + json = string.concat(json, ']}'); return json; } diff --git a/src/DealManager.sol b/src/DealManager.sol index 91fdc7d..8e2dbe3 100644 --- a/src/DealManager.sol +++ b/src/DealManager.sol @@ -146,7 +146,8 @@ contract DealManager is Initializable, UUPSUpgradeable, BorgAuthACL, LexScroWLit if(counterPartyCheck.length > 0) { if (keccak256(abi.encode(counterPartyCheck)) != keccak256(abi.encode(partyValues))) revert CounterPartyValueMismatch(); } - + else + counterPartyValues[agreementId] = partyValues; ICyberAgreementRegistry(DEAL_REGISTRY).signContractFor(signer, agreementId, partyValues, signature, _fillUnallocated, secret); updateEscrow(agreementId, msg.sender, name); handleCounterPartyPayment(agreementId); @@ -161,7 +162,9 @@ contract DealManager is Initializable, UUPSUpgradeable, BorgAuthACL, LexScroWLit if(counterPartyCheck.length > 0) { if (keccak256(abi.encode(counterPartyCheck)) != keccak256(abi.encode(partyValues))) revert CounterPartyValueMismatch(); } - + else + counterPartyValues[agreementId] = partyValues; + if(!conditionCheck(agreementId)) revert AgreementConditionsNotMet(); if(!ICyberAgreementRegistry(DEAL_REGISTRY).hasSigned(agreementId, signer)) From 305322ac782f2f84abcc70809935bdbed23fe618 Mon Sep 17 00:00:00 2001 From: merisman <6125373+merisman@users.noreply.github.com> Date: Thu, 10 Apr 2025 16:07:06 -0400 Subject: [PATCH 15/18] Updating Void Signature --- src/CyberAgreementRegistry.sol | 52 +++++++++++++++++++---- test/CyberCorpTest.t.sol | 75 +++++++++++++++++++++++++++++++--- 2 files changed, 114 insertions(+), 13 deletions(-) diff --git a/src/CyberAgreementRegistry.sol b/src/CyberAgreementRegistry.sol index 63e8174..e954cb4 100644 --- a/src/CyberAgreementRegistry.sol +++ b/src/CyberAgreementRegistry.sol @@ -15,6 +15,7 @@ contract CyberAgreementRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { bytes32 public DOMAIN_SEPARATOR; // Type hash for AgreementData bytes32 public SIGNATUREDATA_TYPEHASH; + bytes32 public VOIDSIGNATUREDATA_TYPEHASH; struct Template { string legalContractUri; // Off-chain legal contract URI @@ -48,6 +49,11 @@ contract CyberAgreementRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { string[] partyValues; } + struct VoidSignatureData { + bytes32 contractId; + address party; + } + // Closed Agreement Data mapping(bytes32 => string[]) public closedAgreementValues; @@ -137,6 +143,10 @@ contract CyberAgreementRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { SIGNATUREDATA_TYPEHASH = keccak256( "SignatureData(bytes32 contractId,string legalContractUri,string[] globalFields,string[] partyFields,string[] globalValues,string[] partyValues)" ); + + VOIDSIGNATUREDATA_TYPEHASH = keccak256( + "VoidSignatureData(bytes32 contractId,address party)" + ); } modifier onlyFinalizer(bytes32 contractId) { @@ -323,18 +333,13 @@ contract CyberAgreementRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { //make sure the party is a party to the contract if(!isParty(contractId, party)) revert NotAParty(); - AgreementData storage agreementData = agreements[contractId]; if (agreementData.finalized) revert ContractAlreadyFinalized(); //verify the signature - if (!_verifySignature(party, SignatureData({ + if (!_verifyVoidSignature(party, VoidSignatureData({ contractId: contractId, - legalContractUri: templates[agreements[contractId].templateId].legalContractUri, - globalFields: templates[agreements[contractId].templateId].globalFields, - partyFields: templates[agreements[contractId].templateId].partyFields, - globalValues: agreements[contractId].globalValues, - partyValues: agreementData.partyValues[party] + party: party }), signature)) revert SignatureVerificationFailed(); for (uint256 i = 0; i < agreementData.voidRequestedBy.length; i++) { @@ -343,7 +348,6 @@ contract CyberAgreementRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { agreementData.voidRequestedBy.push(party); - if(agreementData.expiry < block.timestamp) { agreementData.voided = true; @@ -699,4 +703,36 @@ contract CyberAgreementRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { function _authorizeUpgrade(address newImplementation) internal virtual override onlyOwner {} + function _verifyVoidSignature( + address signer, + VoidSignatureData memory data, + bytes memory signature + ) internal view returns (bool) { + // Hash the data (VoidSignatureData) according to EIP-712 + bytes32 digest = _hashVoidTypedDataV4(data); + + // Recover the signer address + address recoveredSigner = digest.recover(signature); + + // Check if the recovered address matches the expected signer + return recoveredSigner == signer; + } + + // Helper function to hash the typed data (VoidSignatureData) according to EIP-712 + function _hashVoidTypedDataV4(VoidSignatureData memory data) internal view returns (bytes32) { + return keccak256( + abi.encodePacked( + "\x19\x01", + DOMAIN_SEPARATOR, + keccak256( + abi.encode( + VOIDSIGNATUREDATA_TYPEHASH, + data.contractId, + data.party + ) + ) + ) + ); + } + } diff --git a/test/CyberCorpTest.t.sol b/test/CyberCorpTest.t.sol index 6bcf5fe..1f7d56e 100644 --- a/test/CyberCorpTest.t.sol +++ b/test/CyberCorpTest.t.sol @@ -303,6 +303,14 @@ contract CyberCorpTest is Test { testPrivateKey ); + bytes memory voidSignature = _signVoidRequest( + registry.DOMAIN_SEPARATOR(), + registry.VOIDSIGNATUREDATA_TYPEHASH(), + contractId, + testAddress, + testPrivateKey + ); + vm.startPrank(testAddress); (address cyberCorp, address auth, address issuanceManager, address dealManagerAddr, address cyberCertPrinterAddr, bytes32 id) = cyberCorpFactory.deployCyberCorpAndCreateOffer( block.timestamp, @@ -329,7 +337,7 @@ contract CyberCorpTest is Test { //wait for 1000000 blocks vm.warp(block.timestamp + 1000001); vm.startPrank(testAddress); - IDealManager(dealManagerAddr).voidExpiredDeal(contractId, testAddress, signature); + IDealManager(dealManagerAddr).voidExpiredDeal(contractId, testAddress, voidSignature); vm.stopPrank(); } @@ -811,6 +819,31 @@ contract CyberCorpTest is Test { return signature; } + function _signVoidRequest( + bytes32 _domainSeparator, + bytes32 _typeHash, + bytes32 contractId, + address party, + uint256 privKey + ) internal pure returns (bytes memory signature) { + // Create the message hash using the same approach as the contract + bytes32 structHash = keccak256( + abi.encode( + _typeHash, + contractId, + party + ) + ); + + bytes32 digest = keccak256( + abi.encodePacked("\x19\x01", _domainSeparator, structHash) + ); + + (uint8 v, bytes32 r, bytes32 s) = vm.sign(privKey, digest); + signature = abi.encodePacked(r, s, v); + return signature; + } + // Add this helper function to your test contract function _hashStringArray( string[] memory array @@ -864,6 +897,14 @@ contract CyberCorpTest is Test { testPrivateKey ); + bytes memory voidSignature = _signVoidRequest( + registry.DOMAIN_SEPARATOR(), + registry.VOIDSIGNATUREDATA_TYPEHASH(), + contractId, + testAddress, + testPrivateKey + ); + ( address cyberCorp, address auth, @@ -893,7 +934,7 @@ contract CyberCorpTest is Test { ); // Revoke deal before payment - IDealManager(dealManagerAddr).revokeDeal(id, testAddress, signature); + IDealManager(dealManagerAddr).revokeDeal(id, testAddress, voidSignature); vm.stopPrank(); } @@ -1047,6 +1088,14 @@ contract CyberCorpTest is Test { testPrivateKey ); + bytes memory voidSignature = _signVoidRequest( + registry.DOMAIN_SEPARATOR(), + registry.VOIDSIGNATUREDATA_TYPEHASH(), + contractId, + testAddress, + testPrivateKey + ); + ( address cyberCorp, address auth, @@ -1076,7 +1125,7 @@ contract CyberCorpTest is Test { ); // Sign to void after payment - IDealManager(dealManagerAddr).signToVoid(id, testAddress, signature); + IDealManager(dealManagerAddr).signToVoid(id, testAddress, voidSignature); vm.stopPrank(); } @@ -1123,6 +1172,14 @@ contract CyberCorpTest is Test { testPrivateKey ); + bytes memory voidSignature = _signVoidRequest( + registry.DOMAIN_SEPARATOR(), + registry.VOIDSIGNATUREDATA_TYPEHASH(), + contractId, + testAddress, + testPrivateKey + ); + ( address cyberCorp, address auth, @@ -1155,7 +1212,7 @@ contract CyberCorpTest is Test { vm.warp(block.timestamp + 1000001); // Void expired deal - IDealManager(dealManagerAddr).voidExpiredDeal(id, testAddress, signature); + IDealManager(dealManagerAddr).voidExpiredDeal(id, testAddress, voidSignature); vm.stopPrank(); } @@ -1515,6 +1572,14 @@ contract CyberCorpTest is Test { globalValues, partyValues[0], testPrivateKey + ); + + bytes memory voidSignature = _signVoidRequest( + registry.DOMAIN_SEPARATOR(), + registry.VOIDSIGNATUREDATA_TYPEHASH(), + contractId, + testAddress, + testPrivateKey ); ( @@ -1585,7 +1650,7 @@ contract CyberCorpTest is Test { // Try to void after finalization - should fail vm.expectRevert(); - IDealManager(dealManagerAddr).voidExpiredDeal(id, testAddress, signature); + IDealManager(dealManagerAddr).voidExpiredDeal(id, testAddress, voidSignature); vm.stopPrank(); } From 93895d566ae92e6354e7aa75cd10fedebcaf40b2 Mon Sep 17 00:00:00 2001 From: greypixel Date: Fri, 11 Apr 2025 12:18:58 +0100 Subject: [PATCH 16/18] Event tweaks --- src/CyberAgreementRegistry.sol | 7 +++++++ src/DealManager.sol | 8 ++++++-- src/libs/LexScroWLite.sol | 22 ++++++++++++---------- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/CyberAgreementRegistry.sol b/src/CyberAgreementRegistry.sol index e954cb4..9a8ddaf 100644 --- a/src/CyberAgreementRegistry.sol +++ b/src/CyberAgreementRegistry.sol @@ -85,6 +85,12 @@ contract CyberAgreementRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { address indexed party, uint256 timestamp ); + + event VoidRequested( + bytes32 indexed contractId, + address indexed party + ); + event ContractVoided( bytes32 indexed contractId, @@ -347,6 +353,7 @@ contract CyberAgreementRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { } agreementData.voidRequestedBy.push(party); + emit VoidRequested(contractId, party); if(agreementData.expiry < block.timestamp) { diff --git a/src/DealManager.sol b/src/DealManager.sol index 8e2dbe3..1818bd1 100644 --- a/src/DealManager.sol +++ b/src/DealManager.sol @@ -23,7 +23,9 @@ contract DealManager is Initializable, UUPSUpgradeable, BorgAuthACL, LexScroWLit bytes32 templateId, address corp, address dealRegistry, - address[] parties + address[] parties, + address[] conditions, + bool hasSecret ); event DealFinalized( @@ -96,7 +98,9 @@ contract DealManager is Initializable, UUPSUpgradeable, BorgAuthACL, LexScroWLit _templateId, CORP, address(DEAL_REGISTRY), - _parties + _parties, + conditions, + secretHash > 0 ); } diff --git a/src/libs/LexScroWLite.sol b/src/libs/LexScroWLite.sol index 3bc44fc..b6abfc7 100644 --- a/src/libs/LexScroWLite.sol +++ b/src/libs/LexScroWLite.sol @@ -60,9 +60,9 @@ abstract contract LexScroWLite is Initializable, ReentrancyGuard { error DealNotPaid(); error DealVoided(); - event DealVoidedAt(bytes32 agreementId, address counterParty, uint256 timestamp); - event DealPaidAt(bytes32 agreementId, address counterParty, uint256 timestamp); - event DealFinalizedAt(bytes32 agreementId, address counterParty, uint256 timestamp); + event DealVoidedAt(bytes32 indexed agreementId, address agreementRegistry, uint256 timestamp); + event DealPaidAt(bytes32 indexed agreementId, address agreementRegistry, uint256 timestamp); + event DealFinalizedAt(bytes32 indexed agreementId, address agreementRegistry, uint256 timestamp); constructor() { } @@ -77,7 +77,7 @@ abstract contract LexScroWLite is Initializable, ReentrancyGuard { escrows[agreementId] = Escrow(agreementId, counterParty, corpAssets, buyerAssets, blankSignature, expiry, EscrowStatus.PENDING); } - function updateEscrow(bytes32 agreementId, address counterParty, string memory buyerName) internal + function updateEscrow(bytes32 agreementId, address counterParty, string memory buyerName) internal { escrows[agreementId].counterParty = counterParty; @@ -104,14 +104,15 @@ abstract contract LexScroWLite is Initializable, ReentrancyGuard { } } - deal.status = EscrowStatus.PAID; + emit DealPaidAt(agreementId, address(DEAL_REGISTRY), block.timestamp); + deal.status = EscrowStatus.PAID; } function voidAndRefund(bytes32 agreementId) internal nonReentrant { Escrow storage deal = escrows[agreementId]; if(deal.status != EscrowStatus.PAID) revert EscrowNotPaid(); if(!ICyberAgreementRegistry(DEAL_REGISTRY).isVoided(agreementId)) revert DealNotVoided(); - + // Refund buyer assets first for(uint256 i = 0; i < deal.buyerAssets.length; i++) { if(deal.buyerAssets[i].tokenType == TokenType.ERC20) { @@ -130,13 +131,14 @@ abstract contract LexScroWLite is Initializable, ReentrancyGuard { function finalizeEscrow(bytes32 agreementId) internal nonReentrant { Escrow storage deal = escrows[agreementId]; - + // Check all conditions before proceeding if(block.timestamp > deal.expiry) revert DealExpired(); if(deal.status != EscrowStatus.PAID) revert EscrowNotPaid(); // Update state before external calls deal.status = EscrowStatus.FINALIZED; + emit DealFinalizedAt(agreementId, address(DEAL_REGISTRY), block.timestamp); // Transfer buyer assets to company for(uint256 i = 0; i < deal.buyerAssets.length; i++) { @@ -170,9 +172,9 @@ abstract contract LexScroWLite is Initializable, ReentrancyGuard { Escrow memory deal = escrows[agreementId]; //convert bytes32 to bytes bytes memory agreementIdBytes = abi.encodePacked(agreementId); - + for(uint256 i = 0; i < conditionsToCheck.length; i++) { - if(!ICondition(conditionsToCheck[i]).checkCondition(address(this), msg.sig, agreementIdBytes)) + if(!ICondition(conditionsToCheck[i]).checkCondition(address(this), msg.sig, agreementIdBytes)) return false; } return true; @@ -180,7 +182,7 @@ abstract contract LexScroWLite is Initializable, ReentrancyGuard { function voidEscrow(bytes32 agreementId) internal { escrows[agreementId].status = EscrowStatus.VOIDED; - emit DealVoidedAt(agreementId, escrows[agreementId].counterParty, block.timestamp); + emit DealVoidedAt(agreementId, address(DEAL_REGISTRY), block.timestamp); } function getEscrowDetails(bytes32 agreementId) public view returns (Escrow memory) { From 7d09c00f3547d08f6074b791ca3f987ede900d9e Mon Sep 17 00:00:00 2001 From: greypixel Date: Fri, 11 Apr 2025 15:30:49 +0100 Subject: [PATCH 17/18] Fix toJSON function --- src/CyberAgreementRegistry.sol | 401 +++++++++++++++++++++------------ 1 file changed, 256 insertions(+), 145 deletions(-) diff --git a/src/CyberAgreementRegistry.sol b/src/CyberAgreementRegistry.sol index 9a8ddaf..f5de7ed 100644 --- a/src/CyberAgreementRegistry.sol +++ b/src/CyberAgreementRegistry.sol @@ -7,16 +7,15 @@ import "./libs/auth.sol"; import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol"; contract CyberAgreementRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { - using ECDSA for bytes32; // Domain information - string public constant name = "CyberAgreementRegistry"; + string public constant name = "CyberAgreementRegistry"; string public version; bytes32 public DOMAIN_SEPARATOR; // Type hash for AgreementData bytes32 public SIGNATUREDATA_TYPEHASH; bytes32 public VOIDSIGNATUREDATA_TYPEHASH; - + struct Template { string legalContractUri; // Off-chain legal contract URI string title; @@ -38,7 +37,7 @@ contract CyberAgreementRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { uint256 expiry; address[] voidRequestedBy; } - + // This data is what is signed by each party struct SignatureData { bytes32 contractId; @@ -85,12 +84,8 @@ contract CyberAgreementRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { address indexed party, uint256 timestamp ); - - event VoidRequested( - bytes32 indexed contractId, - address indexed party - ); + event VoidRequested(bytes32 indexed contractId, address indexed party); event ContractVoided( bytes32 indexed contractId, @@ -128,38 +123,42 @@ contract CyberAgreementRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { error MismatchedPartyValuesLength(); /// @custom:oz-upgrades-unsafe-allow constructor - constructor() { - } + constructor() {} function initialize(address _auth) public initializer { __UUPSUpgradeable_init(); __BorgAuthACL_init(_auth); - + version = "1"; DOMAIN_SEPARATOR = keccak256( abi.encode( - keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), + keccak256( + "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" + ), keccak256(bytes(name)), keccak256(bytes(version)), block.chainid, address(this) ) ); - + SIGNATUREDATA_TYPEHASH = keccak256( - "SignatureData(bytes32 contractId,string legalContractUri,string[] globalFields,string[] partyFields,string[] globalValues,string[] partyValues)" - ); - + "SignatureData(bytes32 contractId,string legalContractUri,string[] globalFields,string[] partyFields,string[] globalValues,string[] partyValues)" + ); + VOIDSIGNATUREDATA_TYPEHASH = keccak256( - "VoidSignatureData(bytes32 contractId,address party)" - ); + "VoidSignatureData(bytes32 contractId,address party)" + ); } modifier onlyFinalizer(bytes32 contractId) { - if(agreements[contractId].finalizer != msg.sender && agreements[contractId].finalizer != address(0)) revert NotFinalizer(); + if ( + agreements[contractId].finalizer != msg.sender && + agreements[contractId].finalizer != address(0) + ) revert NotFinalizer(); _; } - + function createTemplate( bytes32 templateId, string memory title, @@ -201,12 +200,14 @@ contract CyberAgreementRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { address finalizer, uint256 expiry ) external returns (bytes32 contractId) { - contractId = keccak256(abi.encode(templateId, salt, globalValues, parties)); + contractId = keccak256( + abi.encode(templateId, salt, globalValues, parties) + ); if (agreements[contractId].parties.length > 0) { revert ContractAlreadyExists(); } - Template storage template = templates[templateId]; + Template storage template = templates[templateId]; if (bytes(template.legalContractUri).length == 0) { revert TemplateDoesNotExist(); } @@ -255,7 +256,7 @@ contract CyberAgreementRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { agreementsForParty[parties[i]].push(contractId); } } - + function signContract( bytes32 contractId, string[] memory partyValues, @@ -263,14 +264,21 @@ contract CyberAgreementRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { bool fillUnallocated, // to fill a 0 address or not string memory secret ) external { - signContractFor(msg.sender, contractId, partyValues, signature, fillUnallocated, secret); + signContractFor( + msg.sender, + contractId, + partyValues, + signature, + fillUnallocated, + secret + ); } - + function signContractFor( address signer, bytes32 contractId, string[] memory partyValues, - bytes calldata signature, + bytes calldata signature, bool fillUnallocated, // to fill a 0 address or not string memory secret ) public { @@ -280,36 +288,51 @@ contract CyberAgreementRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { if (agreementData.signedAt[signer] != 0) revert AlreadySigned(); if (isVoided(contractId)) revert ContractAlreadyVoided(); if (agreementData.finalized) revert ContractAlreadyFinalized(); - if (agreementData.expiry > 0 && agreementData.expiry < block.timestamp) revert ContractExpired(); + if (agreementData.expiry > 0 && agreementData.expiry < block.timestamp) + revert ContractExpired(); if (!isParty(contractId, signer)) { - if(agreementData.secretHash > 0 && keccak256(abi.encode(secret)) != agreementData.secretHash) revert InvalidSecret(); + if ( + agreementData.secretHash > 0 && + keccak256(abi.encode(secret)) != agreementData.secretHash + ) revert InvalidSecret(); // Not a named party, so check if there's an open slot uint256 firstOpenPartyIndex = getFirstOpenPartyIndex(contractId); if (firstOpenPartyIndex == 0 || !fillUnallocated) revert NotAParty(); // There is a spare slot, assign the sender to this slot. agreementData.parties[firstOpenPartyIndex] = signer; - agreementsForParty[agreementData.parties[firstOpenPartyIndex]].push(contractId); + agreementsForParty[agreementData.parties[firstOpenPartyIndex]].push( + contractId + ); } //verify if the contract is closed - if(agreementData.partyValues[signer].length > 0) { + if (agreementData.partyValues[signer].length > 0) { //check that the submitted partyValues match - if (keccak256(abi.encode(agreementData.partyValues[signer])) != keccak256(abi.encode(partyValues))) { + if ( + keccak256(abi.encode(agreementData.partyValues[signer])) != + keccak256(abi.encode(partyValues)) + ) { revert ClosedAgreementPartyValueMismatch(); } } - + // Verify the signature - if (!_verifySignature(signer, SignatureData({ - contractId: contractId, - legalContractUri: template.legalContractUri, - globalFields: template.globalFields, - partyFields: template.partyFields, - globalValues: agreementData.globalValues, - partyValues: partyValues - }), signature)) { + if ( + !_verifySignature( + signer, + SignatureData({ + contractId: contractId, + legalContractUri: template.legalContractUri, + globalFields: template.globalFields, + partyFields: template.partyFields, + globalValues: agreementData.globalValues, + partyValues: partyValues + }), + signature + ) + ) { revert SignatureVerificationFailed(); } @@ -325,8 +348,7 @@ contract CyberAgreementRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { emit AgreementSigned(contractId, signer, timestamp); if (totalSignatures == agreementData.parties.length) { - if(agreementData.finalizer == address(0)) - { + if (agreementData.finalizer == address(0)) { agreementData.finalized = true; emit ContractFinalized(contractId, msg.sender, timestamp); } @@ -335,51 +357,68 @@ contract CyberAgreementRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { } } - function voidContractFor(bytes32 contractId, address party, bytes calldata signature) public { + function voidContractFor( + bytes32 contractId, + address party, + bytes calldata signature + ) public { //make sure the party is a party to the contract - if(!isParty(contractId, party)) revert NotAParty(); + if (!isParty(contractId, party)) revert NotAParty(); AgreementData storage agreementData = agreements[contractId]; if (agreementData.finalized) revert ContractAlreadyFinalized(); //verify the signature - if (!_verifyVoidSignature(party, VoidSignatureData({ - contractId: contractId, - party: party - }), signature)) revert SignatureVerificationFailed(); + if ( + !_verifyVoidSignature( + party, + VoidSignatureData({contractId: contractId, party: party}), + signature + ) + ) revert SignatureVerificationFailed(); for (uint256 i = 0; i < agreementData.voidRequestedBy.length; i++) { - if(agreementData.voidRequestedBy[i] == party) revert ContractAlreadyVoided(); + if (agreementData.voidRequestedBy[i] == party) + revert ContractAlreadyVoided(); } agreementData.voidRequestedBy.push(party); emit VoidRequested(contractId, party); - if(agreementData.expiry < block.timestamp) - { + if (agreementData.expiry < block.timestamp) { agreementData.voided = true; - } - else if(agreementData.voidRequestedBy.length == agreementData.parties.length && agreementData.voidRequestedBy.length > 0) - { + } else if ( + agreementData.voidRequestedBy.length == + agreementData.parties.length && + agreementData.voidRequestedBy.length > 0 + ) { agreementData.voided = true; - } - else if(agreementData.parties[0] == party && agreementData.numSignatures == 1) - { + } else if ( + agreementData.parties[0] == party && + agreementData.numSignatures == 1 + ) { agreementData.voided = true; } - if(agreementData.voided) - emit ContractVoided(contractId, agreementData.voidRequestedBy, block.timestamp); + if (agreementData.voided) + emit ContractVoided( + contractId, + agreementData.voidRequestedBy, + block.timestamp + ); } - function finalizeContract(bytes32 contractId) public onlyFinalizer(contractId) { + function finalizeContract( + bytes32 contractId + ) public onlyFinalizer(contractId) { AgreementData storage agreementData = agreements[contractId]; - if(agreementData.finalized) revert ContractAlreadyFinalized(); - if(agreementData.parties.length == 0) revert ContractDoesNotExist(); - if(!allPartiesSigned(contractId)) revert ContractNotFullySigned(); - if(isVoided(contractId)) revert ContractAlreadyVoided(); - if(agreementData.expiry > 0 && agreementData.expiry < block.timestamp) revert ContractExpired(); - + if (agreementData.finalized) revert ContractAlreadyFinalized(); + if (agreementData.parties.length == 0) revert ContractDoesNotExist(); + if (!allPartiesSigned(contractId)) revert ContractNotFullySigned(); + if (isVoided(contractId)) revert ContractAlreadyVoided(); + if (agreementData.expiry > 0 && agreementData.expiry < block.timestamp) + revert ContractExpired(); + agreementData.finalized = true; emit ContractFinalized(contractId, msg.sender, block.timestamp); } @@ -402,7 +441,7 @@ contract CyberAgreementRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { bytes32 contractId, address signer ) external view returns (uint256) { - return agreements[contractId].signedAt[signer]; + return agreements[contractId].signedAt[signer]; } function allPartiesSigned(bytes32 contractId) public view returns (bool) { @@ -435,8 +474,12 @@ contract CyberAgreementRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { if (agreementData.parties.length == 0) revert ContractDoesNotExist(); // Collect all party values and timestamps - string[][] memory allPartyValues = new string[][](agreementData.parties.length); - uint256[] memory allSignedAt = new uint256[](agreementData.parties.length); + string[][] memory allPartyValues = new string[][]( + agreementData.parties.length + ); + uint256[] memory allSignedAt = new uint256[]( + agreementData.parties.length + ); for (uint256 i = 0; i < agreementData.parties.length; i++) { address party = agreementData.parties[i]; @@ -525,31 +568,44 @@ contract CyberAgreementRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { return 0; } - function getVoidRequestedBy(bytes32 contractId) external view returns (address[] memory) { + function getVoidRequestedBy( + bytes32 contractId + ) external view returns (address[] memory) { return agreements[contractId].voidRequestedBy; } - function getContractJson(bytes32 contractId) external view returns (string memory) { + function getContractJson( + bytes32 contractId + ) external view returns (string memory) { AgreementData storage agreementData = agreements[contractId]; Template storage template = templates[agreementData.templateId]; - + // Start with basic fields - string memory json = string(abi.encodePacked( - '{"templateId": "', - _bytes32ToString(contractId), - '", "title": "', - template.title, - '", "legalContractUri": "', - template.legalContractUri, - '", "ContractFields": {' - )); + string memory json = string( + abi.encodePacked( + '{"templateId": "', + _bytes32ToString(agreementData.templateId), // Corrected to use agreementData.templateId + '", "title": "', + template.title, + '", "legalContractUri": "', + template.legalContractUri, + '", "ContractFields": {' + ) + ); // Add global fields and values as key-value pairs if (template.globalFields.length > 0) { for (uint256 i = 0; i < template.globalFields.length; i++) { - json = string.concat(json, '"', template.globalFields[i], '": "', agreementData.globalValues[i], '"'); + json = string.concat( + json, + '"', + template.globalFields[i], + '": "', + agreementData.globalValues[i], + '"' + ); if (i + 1 < template.globalFields.length) { - json = string.concat(json, ','); + json = string.concat(json, ","); } } } @@ -559,55 +615,93 @@ contract CyberAgreementRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { if (agreementData.parties.length > 0) { for (uint256 i = 0; i < agreementData.parties.length; i++) { address party = agreementData.parties[i]; - json = string.concat(json, '"', _addressToString(party), '": {'); - + json = string.concat( + json, + '"', + _addressToString(party), + '": {' + ); + // Add party fields and values if (template.partyFields.length > 0) { string[] memory values = agreementData.partyValues[party]; for (uint256 j = 0; j < template.partyFields.length; j++) { - json = string.concat(json, '"', template.partyFields[j], '": "'); + json = string.concat( + json, + '"', + template.partyFields[j], + '": "' + ); if (values.length > j) { json = string.concat(json, values[j]); } json = string.concat(json, '"'); if (j + 1 < template.partyFields.length) { - json = string.concat(json, ','); + json = string.concat(json, ","); } } } - + // Add signature timestamp if (template.partyFields.length > 0) { - json = string.concat(json, ','); + json = string.concat(json, ","); } - json = string.concat(json, '"signedAt": ', _uint256ToString(agreementData.signedAt[party])); - json = string.concat(json, '}'); - + json = string.concat( + json, + '"signedAt": ', + _uint256ToString(agreementData.signedAt[party]) + ); + json = string.concat(json, "}"); + if (i + 1 < agreementData.parties.length) { - json = string.concat(json, ','); + json = string.concat(json, ","); } } } - + // Add metadata - json = string.concat(json, '}, "numSignatures": ', _uint256ToString(agreementData.numSignatures)); - json = string.concat(json, ', "isComplete": ', agreementData.numSignatures == agreementData.parties.length ? 'true' : 'false'); + json = string.concat( + json, + '}, "numSignatures": ', + _uint256ToString(agreementData.numSignatures) + ); + json = string.concat( + json, + ', "isComplete": ', + agreementData.numSignatures == agreementData.parties.length + ? "true" + : "false" + ); // Add voided status - json = string.concat(json, ', "voided": ', agreementData.voided ? 'true' : 'false'); - // loop and add voidRequestedBy + json = string.concat( + json, + ', "voided": ', + agreementData.voided ? "true" : "false" + ); + // loop and add voidRequestedBy json = string.concat(json, ', "voidRequestedBy": ['); for (uint256 i = 0; i < agreementData.voidRequestedBy.length; i++) { - json = string.concat(json, _addressToString(agreementData.voidRequestedBy[i])); + json = string.concat( + json, + '"', + _addressToString(agreementData.voidRequestedBy[i]), + '"' + ); if (i + 1 < agreementData.voidRequestedBy.length) { - json = string.concat(json, ','); + json = string.concat(json, ","); } } + json = string.concat(json, "]"); // add finalized status - json = string.concat(json, ', "finalized": ', agreementData.finalized ? 'true' : 'false'); - json = string.concat(json, ']}'); + json = string.concat( + json, + ', "finalized": ', + agreementData.finalized ? "true" : "false" + ); + json = string.concat(json, "}"); return json; } - + function _verifySignature( address signer, SignatureData memory data, @@ -622,30 +716,35 @@ contract CyberAgreementRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { // Check if the recovered address matches the expected signer return recoveredSigner == signer; } - + // Helper function to hash the typed data (SignatureData) according to EIP-712 - function _hashTypedDataV4(SignatureData memory data) internal view returns (bytes32) { - return keccak256( - abi.encodePacked( - "\x19\x01", - DOMAIN_SEPARATOR, - keccak256( - abi.encode( - SIGNATUREDATA_TYPEHASH, - data.contractId, - keccak256(bytes(data.legalContractUri)), - _hashStringArray(data.globalFields), - _hashStringArray(data.partyFields), - _hashStringArray(data.globalValues), - _hashStringArray(data.partyValues) + function _hashTypedDataV4( + SignatureData memory data + ) internal view returns (bytes32) { + return + keccak256( + abi.encodePacked( + "\x19\x01", + DOMAIN_SEPARATOR, + keccak256( + abi.encode( + SIGNATUREDATA_TYPEHASH, + data.contractId, + keccak256(bytes(data.legalContractUri)), + _hashStringArray(data.globalFields), + _hashStringArray(data.partyFields), + _hashStringArray(data.globalValues), + _hashStringArray(data.partyValues) + ) ) ) - ) - ); + ); } - + // Helper function to hash string arrays - function _hashStringArray(string[] memory array) internal pure returns (bytes32) { + function _hashStringArray( + string[] memory array + ) internal pure returns (bytes32) { bytes32[] memory hashes = new bytes32[](array.length); for (uint256 i = 0; i < array.length; i++) { hashes[i] = keccak256(bytes(array[i])); @@ -654,31 +753,39 @@ contract CyberAgreementRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { } // Helper function to convert bytes32 to string - function _bytes32ToString(bytes32 _bytes32) internal pure returns (string memory) { + function _bytes32ToString( + bytes32 _bytes32 + ) internal pure returns (string memory) { bytes memory bytesArray = new bytes(64); for (uint256 i = 0; i < 32; i++) { uint8 b = uint8(uint8(bytes1(bytes32(_bytes32) >> (8 * (31 - i))))); - bytesArray[i*2] = bytes1(uint8(b/16 + (b/16 < 10 ? 48 : 87))); - bytesArray[i*2+1] = bytes1(uint8(b%16 + (b%16 < 10 ? 48 : 87))); + bytesArray[i * 2] = bytes1(uint8(b / 16 + (b / 16 < 10 ? 48 : 87))); + bytesArray[i * 2 + 1] = bytes1( + uint8((b % 16) + (b % 16 < 10 ? 48 : 87)) + ); } return string(bytesArray); } // Helper function to convert address to string - function _addressToString(address _addr) internal pure returns (string memory) { + function _addressToString( + address _addr + ) internal pure returns (string memory) { bytes memory s = new bytes(40); for (uint256 i = 0; i < 20; i++) { bytes1 b = bytes1(uint8(uint160(_addr) >> (8 * (19 - i)))); uint8 hi = uint8(b) >> 4; uint8 lo = uint8(b) & 0x0f; - s[2*i] = bytes1(hi + (hi < 10 ? 48 : 87)); - s[2*i+1] = bytes1(lo + (lo < 10 ? 48 : 87)); + s[2 * i] = bytes1(hi + (hi < 10 ? 48 : 87)); + s[2 * i + 1] = bytes1(lo + (lo < 10 ? 48 : 87)); } return string(abi.encodePacked("0x", s)); } // Helper function to convert uint256 to string - function _uint256ToString(uint256 _i) internal pure returns (string memory) { + function _uint256ToString( + uint256 _i + ) internal pure returns (string memory) { if (_i == 0) { return "0"; } @@ -691,7 +798,7 @@ contract CyberAgreementRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { bytes memory bstr = new bytes(len); uint256 k = len; while (_i != 0) { - k = k-1; + k = k - 1; uint8 temp = uint8(48 + (_i % 10)); bytes1 b1 = bytes1(temp); bstr[k] = b1; @@ -708,7 +815,9 @@ contract CyberAgreementRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { return agreements[contractId].voided; } - function _authorizeUpgrade(address newImplementation) internal virtual override onlyOwner {} + function _authorizeUpgrade( + address newImplementation + ) internal virtual override onlyOwner {} function _verifyVoidSignature( address signer, @@ -726,20 +835,22 @@ contract CyberAgreementRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { } // Helper function to hash the typed data (VoidSignatureData) according to EIP-712 - function _hashVoidTypedDataV4(VoidSignatureData memory data) internal view returns (bytes32) { - return keccak256( - abi.encodePacked( - "\x19\x01", - DOMAIN_SEPARATOR, - keccak256( - abi.encode( - VOIDSIGNATUREDATA_TYPEHASH, - data.contractId, - data.party + function _hashVoidTypedDataV4( + VoidSignatureData memory data + ) internal view returns (bytes32) { + return + keccak256( + abi.encodePacked( + "\x19\x01", + DOMAIN_SEPARATOR, + keccak256( + abi.encode( + VOIDSIGNATUREDATA_TYPEHASH, + data.contractId, + data.party + ) ) ) - ) - ); + ); } - } From d88409a12c0b45893bf2fe86fd5d5696347c82af Mon Sep 17 00:00:00 2001 From: greypixel Date: Mon, 14 Apr 2025 14:07:16 +0100 Subject: [PATCH 18/18] Skip duplicate revert for multiple zeroAddress parties --- src/CyberAgreementRegistry.sol | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/CyberAgreementRegistry.sol b/src/CyberAgreementRegistry.sol index f5de7ed..25c7902 100644 --- a/src/CyberAgreementRegistry.sol +++ b/src/CyberAgreementRegistry.sol @@ -221,6 +221,9 @@ contract CyberAgreementRegistry is Initializable, UUPSUpgradeable, BorgAuthACL { } for (uint256 i = 0; i < parties.length; i++) { + if (parties[i] == address(0)) { + continue; // it's possible to have multiple unknown parties + } for (uint256 j = i + 1; j < parties.length; j++) { if (parties[i] == parties[j]) { revert DuplicateParty();