Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ out = "out"
libs = ["lib", "dependencies"]
optimizer = true
optimizer_runs = 15
via_ir = true
extra_output_files = ['abi']
fs_permissions = [
{ access = "read", path = "./script/res" },
{ access = "read", path = "./test/res" }
{ access = "read", path = "./script/res" },
{ access = "read", path = "./test/res" },
]

[rpc_endpoints]
Expand Down
3 changes: 2 additions & 1 deletion script/deploy-pump-factory-full-lifecycle.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,8 @@ contract DeployPumpCorpFactoryFullLifeCycleScript is Script {
securityClass: SecurityClass.SAFE,
securitySeries: SecuritySeries.SeriesA,
extension: address(0),
defaultLegend: defaultLegend
defaultLegend: defaultLegend,
printerExtensionData: ""
});

string[] memory roundFirstPartyValues = new string[](5);
Expand Down
3 changes: 2 additions & 1 deletion script/public-round-test-deploy.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,8 @@ contract PublicRoundTestDeploy is Script {
securityClass: SecurityClass.SAFE,
securitySeries: SecuritySeries.SeriesA,
extension: address(0),
defaultLegend: defaultLegend
defaultLegend: defaultLegend,
printerExtensionData: hex""
});

// SAFE template id 1 requires 5 global fields and 5 party fields
Expand Down
186 changes: 104 additions & 82 deletions src/CertificateImageBuilder.sol

Large diffs are not rendered by default.

457 changes: 5 additions & 452 deletions src/CertificateImageBuilderContract.sol

Large diffs are not rendered by default.

434 changes: 240 additions & 194 deletions src/CertificateImageContentBuilder.sol

Large diffs are not rendered by default.

181 changes: 73 additions & 108 deletions src/CertificateUriBuilder.sol
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import "./interfaces/ICyberAgreementRegistry.sol";
import "./interfaces/ICertificateImageBuilder.sol";
import {RestrictionType, RestrictiveLegend} from "./interfaces/ICyberCertPrinter.sol";
import "./storage/extensions/ICertificateExtension.sol";
import "./interfaces/ICyberCertPrinter.sol";
import "./libs/auth.sol";

interface ICertificateUnitsReserved {
Expand Down Expand Up @@ -157,6 +158,18 @@ contract CertificateUriBuilder is UUPSUpgradeable, BorgAuthACL {
}
}

function restrictiveLegendsToStringArray(
RestrictiveLegend[] memory legends
) public pure returns (string[] memory texts) {
texts = new string[](legends.length);
for (uint256 i = 0; i < legends.length; i++) {
texts[i] = legends[i].text;
if (bytes(texts[i]).length == 0) {
texts[i] = legends[i].title;
}
}
}

function restrictiveLegendsToJson(RestrictiveLegend[] memory arr) public pure returns (string memory) {
string memory json = "[";
for (uint256 i = 0; i < arr.length; i++) {
Expand Down Expand Up @@ -518,15 +531,15 @@ struct CertificateDetails {
address contractAddress,
address extension
) public view returns (string memory) {
return buildCertificateUri(
string memory json = buildCertificateUriNotEncoded(
cyberCORPName,
cyberCORPType,
cyberCORPJurisdiction,
cyberCORPContactDetails,
securityType,
securitySeries,
certificateUri,
legacyLegendsToRestrictiveLegends(certLegend),
certLegend,
details,
endorsements,
owner,
Expand All @@ -536,106 +549,8 @@ struct CertificateDetails {
contractAddress,
extension
);
}

function buildCertificateUri(
string memory cyberCORPName,
string memory cyberCORPType,
string memory cyberCORPJurisdiction,
string memory cyberCORPContactDetails,
SecurityClass securityType,
SecuritySeries securitySeries,
string memory certificateUri,
RestrictiveLegend[] memory certLegend,
CertificateDetails memory details,
Endorsement[] memory endorsements,
OwnerDetails memory owner,
address registry,
bytes32 agreementId,
uint256 tokenId,
address contractAddress,
address extension
) public view returns (string memory) {
// Start building the JSON string with ERC-721 metadata standard format
// Build on-chain SVG image using the image builder

// Fetch timestamp from registry in scoped block to reduce stack pressure
uint256 certTimestamp = _getAgreementTimestamp(registry, agreementId);

CertificateSVGParams memory svgParams = CertificateSVGParams({
corpName: cyberCORPName,
securityType: securityType,
securitySeries: securitySeries,
officerName: details.signingOfficerName,
officerTitle: details.signingOfficerTitle,
units: details.unitsRepresented,
valuation: details.issuerUSDValuationAtTimeOfInvestment,
jurisdiction: cyberCORPJurisdiction,
ownerName: owner.name,
tokenId: tokenId,
certificateUri: certificateUri
});

string memory svg = ICertificateImageBuilder(imageBuilder).buildCertificateSVG(svgParams, certTimestamp);
string memory imageDataUri = string(abi.encodePacked('data:image/svg+xml;base64,', Base64.encode(bytes(svg))));

string memory json = string(abi.encodePacked(
'{"title": "MetaLeX Tokenized Certificate",',
'"type": "', securityClassToString(securityType),
'", "image": "', imageDataUri, '",',
'"attributes": [', buildAttributes(owner, details, cyberCORPName, cyberCORPType, cyberCORPJurisdiction, cyberCORPContactDetails, securityType, securitySeries, certificateUri),
'],'
));

// Add all existing properties at root level
json = string.concat(
json,
'"cyberCORPName": "', cyberCORPName,
'", "cyberCORPType": "', cyberCORPType,
'", "cyberCORPJurisdiction": "', cyberCORPJurisdiction,
'", "cyberCORPContactDetails": "', cyberCORPContactDetails,
'", "securityType": "', securityClassToString(securityType),
'", "securitySeries": "', securitySeriesToString(securitySeries),
'", "certificateUri": "', certificateUri,
'"'
);

// Add certificate details
json = string.concat(json,
', "signingOfficerName": "', details.signingOfficerName,
'", "signingOfficerTitle": "', details.signingOfficerTitle,
'", "investmentAmountUSD": "', from18DecimalsToString(details.investmentAmountUSD),
'", "issuerUSDValuationAtTimeOfInvestment": "', from18DecimalsToString(details.issuerUSDValuationAtTimeOfInvestment),
'", "unitsRepresented": "', from18DecimalsToString(details.unitsRepresented),
'", "unitsReserved": "', unitsReservedToString(contractAddress, tokenId),
'", "legalDetails": "', details.legalDetails,
'"'
);

//add extensionData
if (extension != address(0) && details.extensionData.length > 0) {
json = string.concat(json, ICertificateExtension(extension).getExtensionURI(details.extensionData));
}

// Add endorsement history
json = string.concat(json, ', "endorsementHistory": ', buildEndorsementHistory(endorsements, registry, agreementId));

// Add current owner details
json = string.concat(json,
', "currentOwner": {',
'"name": "', owner.name,
'", "ownerAddress": "', addressToString(owner.ownerAddress),
'"}'
);

// Add restrictive legends at the end
json = string.concat(json, ', "restrictiveLegends": ', restrictiveLegendsToJson(certLegend));

// Close the main JSON object
json = string.concat(json, '}');
json = Base64.encode(bytes(string(json)));
json = string(abi.encodePacked('data:application/json;base64,', json));
return json;
json = Base64.encode(bytes(json));
return string(abi.encodePacked('data:application/json;base64,', json));
}

function buildCertificateUriNotEncoded(
Expand Down Expand Up @@ -696,7 +611,7 @@ struct CertificateDetails {
) public view returns (string memory) {
// Start building the JSON string with ERC-721 metadata standard format
// Build on-chain SVG image using the image builder

// Fetch timestamp from registry in scoped block to reduce stack pressure
uint256 certTimestamp = _getAgreementTimestamp(registry, agreementId);

Expand All @@ -711,7 +626,13 @@ struct CertificateDetails {
jurisdiction: cyberCORPJurisdiction,
ownerName: owner.name,
tokenId: tokenId,
certificateUri: certificateUri
certificateUri: certificateUri,
issuerAddress: contractAddress,
ownerAddress: owner.ownerAddress,
consideration: details.investmentAmountUSD,
blockNumber: block.number,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Stop using the current block in tokenURI metadata

For any certificate whose issue timestamp is otherwise stable, tokenURI() now changes on every block because the SVG embeds the block number from the read call rather than a stored issuance/signing block. That makes the base64 JSON/image churn over time and displays the viewer's query block as if it were the certificate's authorization block, which will confuse indexers and cached marketplace metadata.

Useful? React with 👍 / 👎.

transferRestrictions: restrictiveLegendsToStringArray(certLegend),
isVoided: _isCertVoided(contractAddress, tokenId)
});

string memory svg = ICertificateImageBuilder(imageBuilder).buildCertificateSVG(svgParams, certTimestamp);
Expand All @@ -738,6 +659,7 @@ struct CertificateDetails {
'"'
);


// Add certificate details
json = string.concat(json,
', "signingOfficerName": "', details.signingOfficerName,
Expand All @@ -751,8 +673,11 @@ struct CertificateDetails {
);

//add extensionData
if (extension != address(0) && details.extensionData.length > 0) {
json = string.concat(json, ICertificateExtension(extension).getExtensionURI(details.extensionData));
if (extension != address(0)) {
bytes memory printerExtensionData = _getPrinterExtensionData(contractAddress);
if (details.extensionData.length > 0 || printerExtensionData.length > 0) {
json = string.concat(json, _getExtensionURI(extension, printerExtensionData, details.extensionData));
}
}

// Add endorsement history
Expand All @@ -771,11 +696,51 @@ struct CertificateDetails {

// Close the main JSON object
json = string.concat(json, '}');
//json = Base64.encode(bytes(string(json)));
//json = string(abi.encodePacked('data:application/json;base64,', json));
return json;
}

/// @notice Checks whether the certificate has been voided on the printer contract
function _isCertVoided(address certPrinter, uint256 tokenId) internal view returns (bool) {
if (certPrinter == address(0)) return false;
try ICyberCertPrinter(certPrinter).isVoided(tokenId) returns (bool voided) {
return voided;
} catch {
return false;
}
}

function _getPrinterExtensionData(address certPrinter) internal view returns (bytes memory) {
if (certPrinter == address(0)) return "";
try ICyberCertPrinter(certPrinter).getPrinterExtensionData() returns (bytes memory data) {
return data;
} catch {
return "";
}
}

function _getExtensionURI(
address extension,
bytes memory printerExtensionData,
bytes memory certificateExtensionData
) internal view returns (string memory) {
(bool success, bytes memory result) = extension.staticcall(
abi.encodeWithSelector(
ICertificateExtension.getExtensionURI.selector,
printerExtensionData,
certificateExtensionData
)
);

if (success) {
return abi.decode(result, (string));
}

if (certificateExtensionData.length == 0) return "";

// Fall back to legacy one-blob extensions without certPrinter data.
return ILegacyCertificateExtension(extension).getExtensionURI(certificateExtensionData);
}

function _authorizeUpgrade(
address newImplementation
) internal virtual override onlyOwner {}
Expand Down
13 changes: 12 additions & 1 deletion src/CyberCertPrinter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ contract CyberCertPrinter is Initializable, ERC721EnumerableUpgradeable {
event RestrictionHookSet(uint256 indexed id, address indexed hookAddress);
event GlobalRestrictionHookSet(address indexed hookAddress);
event GlobalTransferableSet(bool indexed transferable);
event PrinterExtensionDataSet();
// Emitted from the storage library via delegatecall; declared here for the ABI
event UnitsReservedUpdated(uint256 indexed tokenId, uint256 unitsReserved);

Expand All @@ -112,7 +113,7 @@ contract CyberCertPrinter is Initializable, ERC721EnumerableUpgradeable {
}

// Called by proxy on deployment (if needed)
function initialize(string[] memory _defaultLegend, string memory name, string memory ticker, string memory _certificateUri, address _issuanceManager, SecurityClass _securityType, SecuritySeries _securitySeries, address _extension) external initializer {
function initialize(string[] memory _defaultLegend, string memory name, string memory ticker, string memory _certificateUri, address _issuanceManager, SecurityClass _securityType, SecuritySeries _securitySeries, address _extension, bytes memory _printerExtensionData) external initializer {
__ERC721_init(name, ticker);
__ERC721Enumerable_init_unchained();

Expand All @@ -124,6 +125,7 @@ contract CyberCertPrinter is Initializable, ERC721EnumerableUpgradeable {
s.certificateUri = _certificateUri;
s.endorsementRequired = true;
s.extension = _extension;
s.printerExtensionData = _printerExtensionData;
}

function updateIssuanceManager(address _issuanceManager) external onlyIssuanceManager {
Expand Down Expand Up @@ -487,6 +489,15 @@ contract CyberCertPrinter is Initializable, ERC721EnumerableUpgradeable {
return CyberCertPrinterStorage._getExtensionData(tokenId);
}

function setPrinterExtensionData(bytes calldata data) external onlyIssuanceManager {
CyberCertPrinterStorage.cyberCertStorage().printerExtensionData = data;
emit PrinterExtensionDataSet();
}

function getPrinterExtensionData() external view returns (bytes memory) {
return CyberCertPrinterStorage.cyberCertStorage().printerExtensionData;
}

function setExtension(uint256 tokenId, address extension) external onlyIssuanceManager {
CyberCertPrinterStorage.cyberCertStorage().extension = extension;
}
Expand Down
6 changes: 6 additions & 0 deletions src/CyberCorpConstants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,12 @@ struct CertificateSVGParams {
string ownerName;
uint256 tokenId;
string certificateUri;
address issuerAddress;
address ownerAddress;
uint256 consideration;
uint256 blockNumber;
string[] transferRestrictions;
bool isVoided;
}


Expand Down
4 changes: 3 additions & 1 deletion src/CyberCorpFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ contract CyberCorpFactory is UUPSUpgradeable, BorgAuthACL {
SecuritySeries securitySeries;
address extension;
string[] defaultLegend;
bytes printerExtensionData;
}

event CyberCorpDeployed(
Expand Down Expand Up @@ -358,7 +359,8 @@ contract CyberCorpFactory is UUPSUpgradeable, BorgAuthACL {
_certData[i].uri,
_certData[i].securityClass,
_certData[i].securitySeries,
_certData[i].extension
_certData[i].extension,
_certData[i].printerExtensionData
)
);
certPrinterAddress[i] = address(certPrinter);
Expand Down
3 changes: 2 additions & 1 deletion src/DealManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -425,7 +425,8 @@ contract DealManager is
_certData[i].uri,
_certData[i].securityClass,
_certData[i].securitySeries,
_certData[i].extension
_certData[i].extension,
_certData[i].printerExtensionData
);
}
}
Expand Down
8 changes: 6 additions & 2 deletions src/IssuanceManager.sol
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ contract IssuanceManager is Initializable, BorgAuthACL, UUPSUpgradeable {
uint256 indexed id,
bool isWhitelisted
);
event CertPrinterExtensionDataSet(address indexed certPrinter);

event CyberScripDeployed(
address indexed certPrinterAddress,
address indexed cyberScripAddress,
Expand Down Expand Up @@ -216,7 +218,8 @@ contract IssuanceManager is Initializable, BorgAuthACL, UUPSUpgradeable {
string memory _certificateUri,
SecurityClass _securityType,
SecuritySeries _securitySeries,
address _extension
address _extension,
bytes memory _printerExtensionData
) public onlyOwner returns (address) {
return
IssuanceManagerStorage.executeCreateCertPrinter(
Expand All @@ -226,7 +229,8 @@ contract IssuanceManager is Initializable, BorgAuthACL, UUPSUpgradeable {
_certificateUri,
_securityType,
_securitySeries,
_extension
_extension,
_printerExtensionData
);
}

Expand Down
Loading
Loading