Skip to content

Commit a1043c2

Browse files
authored
Merge pull request #160 from Fairmint/dev
Production Release: 4 May, 2025
2 parents e934e86 + 74083f9 commit a1043c2

25 files changed

Lines changed: 1824 additions & 65 deletions

.github/workflows/integrate.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ jobs:
3232
uses: foundry-rs/foundry-toolchain@v1
3333
with:
3434
version: stable
35+
cache: true
36+
cache-key: ${{ github.job }}-${{ github.sha }}
3537

3638
- name: Run Forge Install Script
3739
run: |

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ solana/*
4343
*.ignore.js
4444

4545
captables
46+
migration*
4647

4748

4849
outpu2t.txt

.husky/pre-commit

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
yarn flightcheck
2+
yarn test:chain
23
cd chain && forge fmt

chain/lib/openzeppelin-contracts

chain/script/SyncDiamonds.s.sol

Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ contract SyncDiamondsScript is Script, SyncFacetsScript {
3838
if (referenceFacets[i].functionSelectors[0] == capTableFacets[j].functionSelectors[0]) {
3939
found = true;
4040

41+
// Check if facet address or selectors are different
42+
bool needsUpdate = referenceFacets[i].facetAddress != capTableFacets[j].facetAddress;
43+
4144
// Check if selectors match exactly
4245
bool selectorsMatch =
4346
referenceFacets[i].functionSelectors.length == capTableFacets[j].functionSelectors.length;
@@ -50,33 +53,26 @@ contract SyncDiamondsScript is Script, SyncFacetsScript {
5053
}
5154
}
5255

53-
if (!selectorsMatch) {
56+
if (!selectorsMatch || needsUpdate) {
5457
LibDeployment.FacetType facetType =
5558
LibDeployment.getFacetTypeFromSelector(referenceFacets[i].functionSelectors[0]);
5659
string memory facetName = LibDeployment.getFacetCutInfo(facetType).name;
57-
console.log("\nChange detected in facet", facetName, "due to:", "selector mismatch");
60+
console.log(
61+
"\nChange detected in facet",
62+
facetName,
63+
"due to:",
64+
!selectorsMatch ? "selector mismatch" : "address mismatch"
65+
);
5866
console.log("\nUpdating facet:", facetName);
5967

6068
// Deploy new facet
6169
vm.startBroadcast(vm.envUint("PRIVATE_KEY"));
6270
address newFacet = LibDeployment.deployFacet(facetType);
6371
vm.stopBroadcast();
6472

65-
console.log("STOCK_FACET=", newFacet);
66-
67-
// First, replace existing selectors
68-
vm.startBroadcast(vm.envUint("PRIVATE_KEY"));
69-
IDiamondCut.FacetCut[] memory replaceCut = new IDiamondCut.FacetCut[](1);
70-
replaceCut[0] = IDiamondCut.FacetCut({
71-
facetAddress: newFacet,
72-
action: IDiamondCut.FacetCutAction.Replace,
73-
functionSelectors: capTableFacets[j].functionSelectors
74-
});
75-
IDiamondCut(capTable).diamondCut(replaceCut, address(0), "");
76-
vm.stopBroadcast();
77-
console.log("Existing selectors replaced successfully");
73+
console.log("New facet deployed at:", newFacet);
7874

79-
// Then, add new selectors
75+
// Find new selectors that don't exist in current selectors
8076
bytes4[] memory newSelectors = new bytes4[](referenceFacets[i].functionSelectors.length);
8177
uint256 newSelectorCount = 0;
8278

@@ -94,6 +90,7 @@ contract SyncDiamondsScript is Script, SyncFacetsScript {
9490
}
9591
}
9692

93+
// Add new selectors if any
9794
if (newSelectorCount > 0) {
9895
// Resize array to actual count
9996
bytes4[] memory finalNewSelectors = new bytes4[](newSelectorCount);
@@ -112,6 +109,18 @@ contract SyncDiamondsScript is Script, SyncFacetsScript {
112109
vm.stopBroadcast();
113110
console.log("New selectors added successfully");
114111
}
112+
113+
// Replace existing selectors
114+
vm.startBroadcast(vm.envUint("PRIVATE_KEY"));
115+
IDiamondCut.FacetCut[] memory replaceCut = new IDiamondCut.FacetCut[](1);
116+
replaceCut[0] = IDiamondCut.FacetCut({
117+
facetAddress: newFacet,
118+
action: IDiamondCut.FacetCutAction.Replace,
119+
functionSelectors: capTableFacets[j].functionSelectors
120+
});
121+
IDiamondCut(capTable).diamondCut(replaceCut, address(0), "");
122+
vm.stopBroadcast();
123+
console.log("Existing selectors replaced successfully");
115124
}
116125
break;
117126
}

chain/script/SyncSingleFacet.s.sol

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.20;
3+
4+
import "forge-std/Script.sol";
5+
import "forge-std/console.sol";
6+
import { IDiamondCut } from "diamond-3-hardhat/interfaces/IDiamondCut.sol";
7+
import { IDiamondLoupe } from "diamond-3-hardhat/interfaces/IDiamondLoupe.sol";
8+
import { LibDeployment } from "./DeployFactory.s.sol";
9+
import { DiamondLoupeFacet } from "diamond-3-hardhat/facets/DiamondLoupeFacet.sol";
10+
import { IssuerFacet } from "@facets/IssuerFacet.sol";
11+
import { StakeholderFacet } from "@facets/StakeholderFacet.sol";
12+
import { StockClassFacet } from "@facets/StockClassFacet.sol";
13+
import { StockFacet } from "@facets/StockFacet.sol";
14+
import { ConvertiblesFacet } from "@facets/ConvertiblesFacet.sol";
15+
import { EquityCompensationFacet } from "@facets/EquityCompensationFacet.sol";
16+
import { StockPlanFacet } from "@facets/StockPlanFacet.sol";
17+
import { WarrantFacet } from "@facets/WarrantFacet.sol";
18+
import { StakeholderNFTFacet } from "@facets/StakeholderNFTFacet.sol";
19+
import { AccessControlFacet } from "@facets/AccessControlFacet.sol";
20+
21+
contract SyncSingleFacetScript is Script {
22+
using LibDeployment for *;
23+
24+
// Core facet operations
25+
function addFacet(address diamond, address newFacet, bytes4[] memory selectors) public {
26+
IDiamondCut.FacetCut[] memory cut = new IDiamondCut.FacetCut[](1);
27+
cut[0] = IDiamondCut.FacetCut({
28+
facetAddress: newFacet,
29+
action: IDiamondCut.FacetCutAction.Add,
30+
functionSelectors: selectors
31+
});
32+
IDiamondCut(diamond).diamondCut(cut, address(0), "");
33+
}
34+
35+
function replaceFacet(address diamond, address newFacet, bytes4[] memory selectors) public {
36+
IDiamondCut.FacetCut[] memory cut = new IDiamondCut.FacetCut[](1);
37+
cut[0] = IDiamondCut.FacetCut({
38+
facetAddress: newFacet,
39+
action: IDiamondCut.FacetCutAction.Replace,
40+
functionSelectors: selectors
41+
});
42+
43+
try IDiamondCut(diamond).diamondCut(cut, address(0), "") {
44+
console.log("Facet replaced successfully");
45+
} catch Error(string memory reason) {
46+
console.log("Failed to replace facet:", reason);
47+
revert(reason);
48+
} catch (bytes memory) {
49+
console.log("Failed to replace facet (no reason)");
50+
revert("Unknown error during facet replacement");
51+
}
52+
}
53+
54+
function removeFacet(address diamond, bytes4[] memory selectors) public {
55+
IDiamondCut.FacetCut[] memory cut = new IDiamondCut.FacetCut[](1);
56+
cut[0] = IDiamondCut.FacetCut({
57+
facetAddress: address(0),
58+
action: IDiamondCut.FacetCutAction.Remove,
59+
functionSelectors: selectors
60+
});
61+
IDiamondCut(diamond).diamondCut(cut, address(0), "");
62+
}
63+
64+
function upgradeSingleFacet(LibDeployment.FacetType facetType) public {
65+
address referenceDiamond = vm.envAddress("REFERENCE_DIAMOND");
66+
// solhint-disable func-name-mixedcase
67+
string memory RPC_URL = vm.envOr("RPC_URL", string("http://localhost:8545"));
68+
console.log("RPC_URL: %s", RPC_URL);
69+
70+
// Get deployed facets
71+
uint256 fork = vm.createFork(RPC_URL);
72+
vm.selectFork(fork);
73+
IDiamondLoupe.Facet[] memory deployedFacets = IDiamondLoupe(referenceDiamond).facets();
74+
75+
// Find the facet we want to upgrade
76+
bytes4 targetSelector = LibDeployment.getFacetCutInfo(facetType).selectors[0];
77+
address currentFacetAddress = address(0);
78+
bytes4[] memory currentSelectors;
79+
80+
// Find current facet address and selectors
81+
for (uint256 i = 0; i < deployedFacets.length; i++) {
82+
if (deployedFacets[i].functionSelectors[0] == targetSelector) {
83+
currentFacetAddress = deployedFacets[i].facetAddress;
84+
currentSelectors = deployedFacets[i].functionSelectors;
85+
break;
86+
}
87+
}
88+
89+
if (currentFacetAddress == address(0)) {
90+
revert("Facet not found in deployed diamond");
91+
}
92+
93+
// Deploy new facet on the same chain
94+
vm.startBroadcast(vm.envUint("PRIVATE_KEY"));
95+
address newFacet = LibDeployment.deployFacet(facetType);
96+
vm.stopBroadcast();
97+
98+
console.log("New facet deployed at:", newFacet);
99+
100+
// Verify the new facet has code
101+
uint256 codeSize;
102+
assembly {
103+
codeSize := extcodesize(newFacet)
104+
}
105+
require(codeSize > 0, "New facet has no code");
106+
107+
// Get all selectors for the facet type
108+
LibDeployment.FacetCutInfo memory info = LibDeployment.getFacetCutInfo(facetType);
109+
110+
// Find new selectors that don't exist in current selectors
111+
bytes4[] memory newSelectors = new bytes4[](info.selectors.length);
112+
uint256 newSelectorCount = 0;
113+
114+
for (uint256 i = 0; i < info.selectors.length; i++) {
115+
bool exists = false;
116+
if (currentSelectors.length > 0) {
117+
for (uint256 j = 0; j < currentSelectors.length; j++) {
118+
if (info.selectors[i] == currentSelectors[j]) {
119+
exists = true;
120+
break;
121+
}
122+
}
123+
}
124+
if (!exists) {
125+
newSelectors[newSelectorCount] = info.selectors[i];
126+
newSelectorCount++;
127+
}
128+
}
129+
130+
// Resize newSelectors array to actual count
131+
bytes4[] memory finalNewSelectors = new bytes4[](newSelectorCount);
132+
for (uint256 i = 0; i < newSelectorCount; i++) {
133+
finalNewSelectors[i] = newSelectors[i];
134+
}
135+
136+
if (newSelectorCount > 0) {
137+
console.log("Adding new selectors...");
138+
vm.startBroadcast(vm.envUint("PRIVATE_KEY"));
139+
addFacet(referenceDiamond, newFacet, finalNewSelectors);
140+
vm.stopBroadcast();
141+
console.log("New selectors added successfully");
142+
}
143+
144+
if (currentSelectors.length > 0) {
145+
console.log("Replacing existing selectors...");
146+
vm.startBroadcast(vm.envUint("PRIVATE_KEY"));
147+
replaceFacet(referenceDiamond, newFacet, currentSelectors);
148+
vm.stopBroadcast();
149+
console.log("Existing selectors replaced successfully");
150+
}
151+
}
152+
153+
function _getBytecode(address addr) internal view returns (bytes memory) {
154+
uint256 size;
155+
assembly {
156+
size := extcodesize(addr)
157+
}
158+
bytes memory code = new bytes(size);
159+
assembly {
160+
extcodecopy(addr, add(code, 0x20), 0, size)
161+
}
162+
return code;
163+
}
164+
165+
function run() external virtual {
166+
// Example usage:
167+
// forge script script/SyncSingleFacet.s.sol:SyncSingleFacetScript --sig "upgradeSingleFacet(uint8)" 4 --broadcast --rpc-url $RPC_URL
168+
// Where 4 is the enum value for StockFacet
169+
upgradeSingleFacet(LibDeployment.FacetType.Warrant);
170+
}
171+
}

chain/src/facets/WarrantFacet.sol

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ contract WarrantFacet {
1616
}
1717

1818
ValidationLib.validateStakeholder(params.stakeholder_id);
19-
ValidationLib.validateQuantity(params.quantity);
2019

2120
// Create and store position
2221
ds.warrantActivePositions.securities[params.security_id] =

chain/test/WarrantIssuance.t.sol

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ contract DiamondWarrantIssuanceTest is DiamondTestBase {
5555
IWarrantFacet(address(capTable)).issueWarrant(params);
5656
}
5757

58-
function test_RevertZeroQuantity() public {
58+
function test_IssueZeroQuantity() public {
5959
bytes16 stakeholderId = createStakeholder();
6060
bytes16 securityId = 0xd3373e0a4dd940000000000000000001;
6161
bytes16 id = 0xd3373e0a4dd940000000000000000002;
@@ -70,7 +70,8 @@ contract DiamondWarrantIssuanceTest is DiamondTestBase {
7070
security_law_exemptions_mapping: "REG_D",
7171
exercise_triggers_mapping: "TIME_BASED"
7272
});
73-
vm.expectRevert(abi.encodeWithSignature("InvalidQuantity()"));
73+
vm.expectEmit(true, true, false, true, address(capTable));
74+
emit TxHelper.TxCreated(TxType.WARRANT_ISSUANCE, abi.encode(params));
7475
IWarrantFacet(address(capTable)).issueWarrant(params);
7576
}
7677
}

scripts/deleteIssuerData.script.js

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
#!/usr/bin/env node
2+
3+
import { connectDB } from "../src/db/config/mongoose";
4+
import { deleteIssuerData } from "../src/tests/integration/utils";
5+
import Issuer from "../src/db/objects/Issuer";
6+
import chalk from "chalk";
7+
import readline from "readline";
8+
9+
const rl = readline.createInterface({
10+
input: process.stdin,
11+
output: process.stdout,
12+
});
13+
14+
const printUsage = () => {
15+
console.log(`
16+
Usage: node deleteIssuerData.script.js <issuer-id>
17+
18+
Arguments:
19+
issuer-id The ID of the issuer whose data should be deleted
20+
21+
Example:
22+
node deleteIssuerData.script.js <issuer-id>
23+
`);
24+
};
25+
26+
const confirmDeletion = (issuerName) => {
27+
return new Promise((resolve) => {
28+
console.log(chalk.yellow(`\n⚠️ You are about to delete all data for issuer: ${chalk.bold(issuerName)}`));
29+
console.log(chalk.red("This action cannot be undone!"));
30+
rl.question(chalk.yellow("\nAre you sure you want to proceed? (y/N): "), (answer) => {
31+
resolve(answer.toLowerCase() === "y");
32+
});
33+
});
34+
};
35+
36+
const main = async () => {
37+
const issuerId = process.argv[2];
38+
39+
if (!issuerId) {
40+
console.error(chalk.red("Error: Issuer ID is required"));
41+
printUsage();
42+
process.exit(1);
43+
}
44+
45+
try {
46+
console.log(chalk.blue("Connecting to DB..."));
47+
await connectDB();
48+
console.log(chalk.green("Connected to DB"));
49+
50+
// const issuer = await Issuer.findOne({ _id: issuerId });
51+
// if (!issuer) {
52+
// console.error(chalk.red("Error: Issuer not found"));
53+
// process.exit(1);
54+
// }
55+
56+
// const confirmed = await confirmDeletion(issuer.legal_name);
57+
// if (!confirmed) {
58+
// console.log(chalk.green("Operation cancelled"));
59+
// process.exit(0);
60+
// }
61+
62+
await deleteIssuerData(issuerId);
63+
console.log(chalk.green("Successfully deleted issuer data"));
64+
} catch (error) {
65+
console.error(chalk.red("Error:"), error.message);
66+
process.exit(1);
67+
} finally {
68+
rl.close();
69+
}
70+
};
71+
72+
main();

0 commit comments

Comments
 (0)