diff --git a/package-lock.json b/package-lock.json index 49d0759..27bcd2a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "hardhat-multibaas-plugin", - "version": "0.3.3", + "version": "0.3.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "hardhat-multibaas-plugin", - "version": "0.3.3", + "version": "0.3.4", "license": "MIT", "dependencies": { "@nomicfoundation/hardhat-ethers": "^3.0.6", @@ -17,6 +17,7 @@ "validator": "^13.12.0" }, "devDependencies": { + "@types/fs-extra": "^11.0.4", "@types/node": "^20.14.1", "@types/validator": "^13.11.10", "@typescript-eslint/eslint-plugin": "^7.11.0", @@ -26,6 +27,7 @@ "eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-import": "^2.29.1", "eslint-plugin-prettier": "^5.1.3", + "fs-extra": "^11.2.0", "husky": "^9.0.11", "lint-staged": "^15.2.5", "prettier": "^3.3.0", @@ -1493,11 +1495,32 @@ "@types/node": "*" } }, + "node_modules/@types/fs-extra": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-11.0.4.tgz", + "integrity": "sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/jsonfile": "*", + "@types/node": "*" + } + }, "node_modules/@types/json5": { "version": "0.0.29", "dev": true, "license": "MIT" }, + "node_modules/@types/jsonfile": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/@types/jsonfile/-/jsonfile-6.1.4.tgz", + "integrity": "sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/lru-cache": { "version": "5.1.1", "license": "MIT" @@ -3654,15 +3677,17 @@ "license": "MIT" }, "node_modules/fs-extra": { - "version": "7.0.1", - "license": "MIT", + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "dev": true, "dependencies": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" }, "engines": { - "node": ">=6 <7 || >=8" + "node": ">=14.14" } }, "node_modules/fs.realpath": { @@ -3979,6 +4004,20 @@ "node": ">=4" } }, + "node_modules/hardhat/node_modules/fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, "node_modules/hardhat/node_modules/has-flag": { "version": "3.0.0", "license": "MIT", @@ -3986,6 +4025,15 @@ "node": ">=4" } }, + "node_modules/hardhat/node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "license": "MIT", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, "node_modules/hardhat/node_modules/locate-path": { "version": "2.0.0", "license": "MIT", @@ -4041,6 +4089,15 @@ "node": ">=4" } }, + "node_modules/hardhat/node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/hardhat/node_modules/ws": { "version": "7.5.9", "license": "MIT", @@ -4630,8 +4687,13 @@ } }, "node_modules/jsonfile": { - "version": "4.0.0", - "license": "MIT", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, "optionalDependencies": { "graceful-fs": "^4.1.6" } @@ -6524,10 +6586,12 @@ "license": "MIT" }, "node_modules/universalify": { - "version": "0.1.2", - "license": "MIT", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, "engines": { - "node": ">= 4.0.0" + "node": ">= 10.0.0" } }, "node_modules/unpipe": { diff --git a/package.json b/package.json index 3a99114..6bdf1e0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "hardhat-multibaas-plugin", - "version": "0.3.3", + "version": "0.3.4", "main": "lib/index.js", "types": "lib/index.d.ts", "keywords": [ @@ -35,6 +35,7 @@ "validator": "^13.12.0" }, "devDependencies": { + "@types/fs-extra": "^11.0.4", "@types/node": "^20.14.1", "@types/validator": "^13.11.10", "@typescript-eslint/eslint-plugin": "^7.11.0", @@ -44,6 +45,7 @@ "eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-import": "^2.29.1", "eslint-plugin-prettier": "^5.1.3", + "fs-extra": "^11.2.0", "husky": "^9.0.11", "lint-staged": "^15.2.5", "prettier": "^3.3.0", diff --git a/sample/contracts/MetaCoin.sol b/sample/contracts/MetaCoin.sol index af91c00..bada2b9 100644 --- a/sample/contracts/MetaCoin.sol +++ b/sample/contracts/MetaCoin.sol @@ -8,28 +8,66 @@ import "./ConvertLib.sol"; // coin/token contracts. If you want to create a standards-compliant // token, see: https://github.com/ConsenSys/Tokens. Cheers! +/** + * @title MetaCoin + * @dev A simple coin-like contract example. This contract is not standards compliant and is meant for educational purposes. (devdoc) + * @notice A simple coin-like contract example. This contract is not standards compliant and is meant for educational purposes. (userdoc) + */ contract MetaCoin { - mapping (address => uint) balances; + mapping(address => uint) balances; - event Transfer(address indexed _from, address indexed _to, uint256 _value); + /** + * @dev Event triggered when coins are transferred. (devdoc) + * @notice Event triggered when coins are transferred. (userdoc) + * @param _from The address from which the coins are sent. + * @param _to The address to which the coins are sent. + * @param _value The amount of coins transferred. + */ + event Transfer(address indexed _from, address indexed _to, uint256 _value); - constructor() { - balances[tx.origin] = 10000; - } + /** + * @dev Constructor that gives the contract creator an initial balance of 10000 MetaCoins. (devdoc) + * @notice Constructor that gives the contract creator an initial balance of 10000 MetaCoins. (userdoc) + */ + constructor() { + balances[tx.origin] = 10000; + } - function sendCoin(address receiver, uint amount) public returns(bool sufficient) { - if (balances[msg.sender] < amount) return false; - balances[msg.sender] -= amount; - balances[receiver] += amount; - emit Transfer(msg.sender, receiver, amount); - return true; - } + /** + * @dev Transfers coins from sender's account to receiver's account if the sender has sufficient balance. (devdoc) + * @notice Send `amount` of MetaCoins to `receiver`. (userdoc) + * @param receiver The address of the receiver. + * @param amount The amount of MetaCoins to send. + * @return sufficient Returns true if the sender has enough balance, false otherwise. + */ + function sendCoin( + address receiver, + uint amount + ) public returns (bool sufficient) { + if (balances[msg.sender] < amount) return false; + balances[msg.sender] -= amount; + balances[receiver] += amount; + emit Transfer(msg.sender, receiver, amount); + return true; + } - function getBalanceInEth(address addr) public view returns(uint){ - return ConvertLib.convert(getBalance(addr),2); - } + /** + * @dev Converts the balance of `addr` from MetaCoins to Ether. (devdoc) + * @notice Get the balance of `addr` in Ether. (userdoc) + * @param addr The address whose balance is to be checked. + * @return The balance in Ether. + */ + function getBalanceInEth(address addr) public view returns (uint) { + return ConvertLib.convert(getBalance(addr), 2); + } - function getBalance(address addr) public view returns(uint) { - return balances[addr]; - } + /** + * @dev Returns the balance of `addr`. (devdoc) + * @notice Get the balance of `addr` in MetaCoins. (userdoc) + * @param addr The address whose balance is to be checked. + * @return The balance in MetaCoins. + */ + function getBalance(address addr) public view returns (uint) { + return balances[addr]; + } } diff --git a/sample/package-lock.json b/sample/package-lock.json index 84270df..99ec846 100644 --- a/sample/package-lock.json +++ b/sample/package-lock.json @@ -17,30 +17,32 @@ } }, "..": { - "version": "0.3.2", + "version": "0.3.4", "dev": true, "license": "MIT", "dependencies": { "@nomicfoundation/hardhat-ethers": "^3.0.6", "@openzeppelin/hardhat-upgrades": "^3.1.0", "axios": "^1.7.2", - "ethers": "^6.12.1", + "ethers": "^6.13.0", "hardhat": "^2.22.4", "validator": "^13.12.0" }, "devDependencies": { - "@types/node": "^20.12.12", + "@types/fs-extra": "^11.0.4", + "@types/node": "^20.14.1", "@types/validator": "^13.11.10", - "@typescript-eslint/eslint-plugin": "^7.10.0", - "@typescript-eslint/parser": "^7.10.0", + "@typescript-eslint/eslint-plugin": "^7.11.0", + "@typescript-eslint/parser": "^7.12.0", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-import": "^2.29.1", "eslint-plugin-prettier": "^5.1.3", + "fs-extra": "^11.2.0", "husky": "^9.0.11", "lint-staged": "^15.2.5", - "prettier": "^3.2.5", + "prettier": "^3.3.0", "ts-node": "^10.9.2", "typescript": "^5.4.5" } diff --git a/src/deploy.ts b/src/deploy.ts index ea7545c..7d63b64 100644 --- a/src/deploy.ts +++ b/src/deploy.ts @@ -4,10 +4,15 @@ import { FactoryOptions, HardhatEthersHelpers, } from "@nomicfoundation/hardhat-ethers/types"; +import { + HardhatRuntimeEnvironment, + Artifact, + BuildInfo, + MBConfig, +} from "hardhat/types"; import axios, { AxiosRequestConfig } from "axios"; import { ContractFactory, ethers, Signer } from "ethers"; import { HardhatUpgrades } from "@openzeppelin/hardhat-upgrades"; -import { MBConfig } from "hardhat/types"; import { URL } from "url"; import { MultiBaasAddress, @@ -21,6 +26,9 @@ import { DeployProxyResult, MBDeployerI, } from "./type-extensions"; +import path from "path"; +import { readJSON } from "fs-extra"; +import { ArtifactDBG, ExtendedCompilerOutputContract } from "./types"; type ethersT = typeof ethers & HardhatEthersHelpers; @@ -91,6 +99,8 @@ export class MBDeployer implements MBDeployerI { contractName: string, contract: ContractFactory, options: DeployOptions, + developerDoc: unknown, + userDoc: unknown, ): Promise { const contractLabel = options.contractLabel ?? contractName.toLowerCase(); if (contractLabel === undefined) throw new Error("Contract has no name"); @@ -180,8 +190,8 @@ export class MBDeployer implements MBDeployerI { rawAbi: contract.interface.formatJson(), // It seems `ethers.js` doesn't support parsing `devdoc` or `userdoc` from smart contracts. // Use empty structs for those fields. - developerDoc: "{}", - userDoc: "{}", + developerDoc: JSON.stringify(developerDoc) || "{}", + userDoc: JSON.stringify(userDoc) || "{}", version: contractVersion, contractName, }, @@ -368,6 +378,38 @@ export class MBDeployer implements MBDeployerI { return startingBlock; } + private async getContractBuildInfo( + contractName: string, + ): Promise> { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const hre = require("hardhat") as HardhatRuntimeEnvironment; + const artifactPaths: string[] = await hre.artifacts.getArtifactPaths(); + for (const artifactPath of artifactPaths) { + const artifactName = path.basename(artifactPath, ".json"); + if (artifactName !== contractName) { + continue; + } + const artifact = (await readJSON(artifactPath)) as Artifact; + const artifactDBGPath = path.join( + path.dirname(artifactPath), + artifactName + ".dbg.json", + ); + const artifactDBG = (await readJSON(artifactDBGPath)) as ArtifactDBG; + const buildInfoPath = path.join( + path.dirname(artifactDBGPath), + artifactDBG.buildInfo, + ); + const buildInfo: BuildInfo = (await readJSON(buildInfoPath)) as BuildInfo; + const contractBuildInfo = + buildInfo.output.contracts[artifact.sourceName] || {}; + return ( + (contractBuildInfo[artifactName] as ExtendedCompilerOutputContract) || + {} + ); + } + return {}; + } + /** * Deploy a contract with `contractName` name using `hardhat-ethers` plugin. * The contract's compiled bytecode and its ABI are uploaded to MultiBaas. @@ -393,12 +435,16 @@ export class MBDeployer implements MBDeployerI { signerOrOptions, ); + const contractBuildInfo = await this.getContractBuildInfo(contractName); + // after finishing compiling, upload the bytecode and // contract's data to MultiBaas const mbContract = await this.createMBContract( contractName, factory, options, + contractBuildInfo["devdoc"], + contractBuildInfo["userdoc"], ); if (typeof options.overrides === "object") { @@ -457,12 +503,16 @@ export class MBDeployer implements MBDeployerI { signerOrOptions, ); + const contractBuildInfo = await this.getContractBuildInfo(contractName); + // after finishing compiling, upload the bytecode and // contract's data to MultiBaas const mbContract = await this.createMBContract( contractName, factory, options, + contractBuildInfo["devdoc"], + contractBuildInfo["userdoc"], ); if (typeof options.overrides === "object") { diff --git a/src/index.ts b/src/index.ts index 1ccab5a..bf89de2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,12 +1,35 @@ // Copyright (c) 2021 Curvegrid Inc. - import "@nomicfoundation/hardhat-ethers"; import "@openzeppelin/hardhat-upgrades"; import { extendConfig, extendEnvironment } from "hardhat/config"; import { lazyObject } from "hardhat/plugins"; -import { HardhatConfig, HardhatUserConfig } from "hardhat/types"; +import { + HardhatConfig, + HardhatUserConfig, + SolcUserConfig, +} from "hardhat/types"; import { MBDeployer } from "./deploy"; import "./type-extensions"; +import { CompilerSettings, OutputSelection } from "./types"; + +// Function to ensure userdoc and devdoc are present in outputSelection +function ensureUserDocAndDevDoc(outputSelection: OutputSelection) { + const allContracts = outputSelection["*"] || {}; + if (!allContracts["*"]) { + allContracts["*"] = []; + } + + if (!allContracts["*"].includes("userdoc")) { + allContracts["*"].push("userdoc"); + } + + if (!allContracts["*"].includes("devdoc")) { + allContracts["*"].push("devdoc"); + } + + outputSelection["*"] = allContracts; + return outputSelection; +} extendEnvironment((hre) => { hre.mbDeployer = lazyObject(() => { @@ -30,5 +53,34 @@ extendConfig( } config.mbConfig = mbConfig; + + // Ensure the compiler settings are defined + if (!config.solidity) { + throw new Error("Solidity configuration is missing in hardhat.config.js"); + } + + // Ensure the compilers array is defined + if (!Array.isArray(config.solidity.compilers)) { + throw new Error( + "Solidity compilers array is missing in hardhat.config.js", + ); + } + + // Iterate over each compiler configuration and ensure userdoc and devdoc are included + config.solidity.compilers.forEach((compiler: SolcUserConfig) => { + if (!compiler.settings) { + compiler.settings = {}; + } + // Cast settings to CompilerSettings to safely access outputSelection + const settings = compiler.settings as CompilerSettings; + + if (!settings.outputSelection) { + settings.outputSelection = {} as OutputSelection; + } + + settings.outputSelection = ensureUserDocAndDevDoc( + settings.outputSelection, + ); + }); }, ); diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 0000000..0a40cc1 --- /dev/null +++ b/src/types.ts @@ -0,0 +1,21 @@ +import { CompilerOutputContract } from "hardhat/types"; + +export interface CompilerSettings { + outputSelection?: OutputSelection; +} + +export interface OutputSelection { + "*": { + "*": string[]; + "": string[]; + }; +} + +export interface ExtendedCompilerOutputContract extends CompilerOutputContract { + devdoc?: unknown; + userdoc?: unknown; +} + +export interface ArtifactDBG { + buildInfo: string; +}