diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..241108b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "[solidity]": { + "editor.defaultFormatter": "NomicFoundation.hardhat-solidity" + }, + "[toml]": { + "editor.defaultFormatter": "tamasfe.even-better-toml" + }, + "solidity.formatter": "forge" +} diff --git a/package.json b/package.json index 3caee6c..d3db60b 100644 --- a/package.json +++ b/package.json @@ -28,12 +28,12 @@ "dependencies": { "dotenv": "16.4.5", "ethers": "^6.13.2", - "viem": "^2.19.2" + "viem": "^2.19.6" }, "devDependencies": { - "concurrently": "8.2.1", - "prettier": "^3.0.3", - "typescript": "^5.2.2", + "concurrently": "8.2.2", + "prettier": "^3.3.3", + "typescript": "^5.5.4", "wait-port": "1.1.0" }, "engines": { diff --git a/packages/app/.env.example b/packages/app/.env.example index 6e15834..7257c6f 100644 --- a/packages/app/.env.example +++ b/packages/app/.env.example @@ -2,6 +2,7 @@ VITE_ALCHEMY_API_KEY= VITE_PRIVY_API_KEY= VITE_PIMLICO_API_KEY= +VITE_PINATA_API_KEY= - - +PINATA_API_SECRET= +PINATA_API_JWT= \ No newline at end of file diff --git a/packages/contracts/.editorconfig b/packages/contracts/.editorconfig new file mode 100644 index 0000000..746ae31 --- /dev/null +++ b/packages/contracts/.editorconfig @@ -0,0 +1,19 @@ +# EditorConfig http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# All files +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.sol] +indent_size = 4 + +[*.tree] +indent_size = 1 diff --git a/packages/contracts/.env.example b/packages/contracts/.env.example new file mode 100644 index 0000000..298ee4a --- /dev/null +++ b/packages/contracts/.env.example @@ -0,0 +1,5 @@ +export API_KEY_ALCHEMY="YOUR_API_KEY_ALCHEMY" +export API_KEY_ARBISCAN="YOUR_API_KEY_ARBISCAN" +export API_KEY_INFURA="YOUR_API_KEY_INFURA" +export PRIVATE_KEY="YOUR_MNEMONIC" +export FOUNDRY_PROFILE="default" \ No newline at end of file diff --git a/packages/contracts/.gitignore b/packages/contracts/.gitignore index 63973dc..19f25ad 100644 --- a/packages/contracts/.gitignore +++ b/packages/contracts/.gitignore @@ -1,35 +1,24 @@ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. -# dependencies - +# directories +cache +coverage node_modules -dist -dev-dist -.DS_Store -DS_Store - -node_modules -/.pnp -.pnp.js - +out -# production - -build - -# local env files - -.env\*.local -.env +# files +*.env +*.log +.DS_Store +.pnp.* +lcov.info +package-lock.json +pnpm-lock.yaml +yarn.lock -# forge -out/ -cache/ -cache_hardhat/ -node_modules/ -bindings/ -artifacts/ +# broadcasts +!broadcast +broadcast/* +broadcast/*/31337/ -# Ignore MUD deploy artifacts -deploys/**/*.json diff --git a/packages/contracts/.prettierignore b/packages/contracts/.prettierignore index 25bb65f..3996d20 100644 --- a/packages/contracts/.prettierignore +++ b/packages/contracts/.prettierignore @@ -1,5 +1,17 @@ -dist -node_modules -contracts/out -contracts/cache +# directories +broadcast cache +coverage +node_modules +out + +# files +*.env +*.log +.DS_Store +.pnp.* +bun.lockb +lcov.info +package-lock.json +pnpm-lock.yaml +yarn.lock diff --git a/packages/contracts/.prettierrc.json b/packages/contracts/.prettierrc.json index d3be6d2..44c8b9a 100644 --- a/packages/contracts/.prettierrc.json +++ b/packages/contracts/.prettierrc.json @@ -1,5 +1,9 @@ { + "bracketSpacing": true, + "printWidth": 120, + "proseWrap": "always", + "singleQuote": false, "tabWidth": 2, - "useTabs": false, - "trailingComma": "all" + "trailingComma": "all", + "useTabs": false } diff --git a/packages/contracts/.solhint.json b/packages/contracts/.solhint.json index 0ff4bbe..311f3d1 100644 --- a/packages/contracts/.solhint.json +++ b/packages/contracts/.solhint.json @@ -2,10 +2,17 @@ "extends": ["solhint:recommended"], "plugins": [], "rules": { - "compiler-version": ["error", ">=0.8.0"], + "code-complexity": ["error", 8], + "compiler-version": ["error", ">=0.8.25"], + "func-name-mixedcase": "off", + "func-visibility": ["error", { "ignoreConstructors": true }], + "max-line-length": ["error", 120], + "named-parameters-mapping": "warn", + "no-console": "off", + "not-rely-on-time": "off", + "one-contract-per-file": true, "avoid-low-level-calls": "off", "no-inline-assembly": "off", - "func-visibility": ["warn", { "ignoreConstructors": true }], "no-empty-blocks": "off", "no-complex-fallback": "off" } diff --git a/packages/contracts/foundry.toml b/packages/contracts/foundry.toml index aa29f55..9ae44c3 100644 --- a/packages/contracts/foundry.toml +++ b/packages/contracts/foundry.toml @@ -1,18 +1,38 @@ [profile.default] -solc = '0.8.21' +solc = "0.8.25" ffi = false -fuzz_runs = 256 +auto_detect_solc = false +block_timestamp = 1_680_220_800 # March 31, 2023 at 00:00 GMT +bytecode_hash = "none" +evm_version = "shanghai" +fuzz = { runs = 1_000 } +gas_reports = ["*"] optimizer = true -optimizer_runs = 3000 +optimizer_runs = 10_000 verbosity = 2 +libs = ['lib'] +out = 'out' +script = "script" src = 'src' test = 'test' -out = 'out' -libs = ['lib'] -allow_paths = [ - # pnpm symlinks to the project root's node_modules - "../../node_modules", -] + +[profile.ci] + fuzz = { runs = 10_000 } + verbosity = 4 + +[etherscan] + arbitrum = { key = "${API_KEY_ARBISCAN}" } + +[fmt] + bracket_spacing = true + int_types = "long" + line_length = 120 + multiline_func_header = "all" + number_underscore = "thousands" + quote_style = "double" + tab_width = 2 + wrap_comments = true + extra_output_files = [ "abi", "evm.bytecode" diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 3ed97fb..d7bbac3 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -4,6 +4,9 @@ "private": true, "description": "Contracts for Protocol", "scripts": { + "account": "node script/ListAccount.js", + "chain": "anvil --config-out localhost.json", + "compile": "forge compile", "dev": "forge build", "prettier": "prettier --write 'src/**/*.sol'", "solhint": "solhint --config ./.solhint.json 'src/**/*.sol' --fix", @@ -15,14 +18,20 @@ "deploy:arbitrum-sepolia": "source .env && FOUNDRY_PROFILE=arbitrum-sepolia forge script script/ArbitrumSepolia.s.sol:ArbitrumScript --private-key $FORGE_PRIVATE_KEY --etherscan-api-key $ETHERSCAN_API_KEY --broadcast" }, "dependencies": { - "@ethereum-attestation-service/eas-contracts": "1.7.1" + "@ethereum-attestation-service/eas-contracts": "1.7.1", + "@openzeppelin/contracts": "4.8.3", + "@openzeppelin/contracts-upgradeable": "4.8.3" }, "devDependencies": { + "@types/prettier": "2", + "@types/qrcode": "1", + "envfile": "~6.18.0", + "qrcode": "~1.5.3", + "toml": "~3.0.0", "solidity-coverage": "^0.8.12", - "solhint": "^5.0.1", - "ds-test": "https://github.com/dapphub/ds-test.git#e282159d5170298eb2455a6c05280ab5a73a4ef0", - "forge-std": "https://github.com/foundry-rs/forge-std.git#74cfb77e308dd188d2f58864aaf44963ae6b88b1", + "solhint": "^5.0.3", + "forge-std": "github:foundry-rs/forge-std#v1.8.1", "prettier": "^3.3.3", - "prettier-plugin-solidity": "^1.3.1" + "prettier-plugin-solidity": "^1.4.0" } } diff --git a/packages/contracts/remappings.txt b/packages/contracts/remappings.txt index 32d0ded..9fad027 100644 --- a/packages/contracts/remappings.txt +++ b/packages/contracts/remappings.txt @@ -1,6 +1,6 @@ +ds-test=./node_modules/ds-test/src/ +forge-std=./node_modules/forge-std/src/ @openzeppelin/contracts=./node_modules/@openzeppelin/contracts/ @openzeppelin/contracts-upgradeable=./node_modules/@openzeppelin/contracts-upgradeable/ -eas-contracts=./node_modules/@ethereum-attestation-service/eas-contracts/contracts/ -forge-std=./node_modules/forge-std/src/ -ds-test=./node_modules/ds-test/src/ -tokenbound=./lib/tokenbound/src/ \ No newline at end of file +@eas=./node_modules/@ethereum-attestation-service/eas-contracts/contracts/ +@tokenbound=./lib/tokenbound/src/ diff --git a/packages/contracts/script/ListAccount.js b/packages/contracts/script/ListAccount.js new file mode 100644 index 0000000..8f51217 --- /dev/null +++ b/packages/contracts/script/ListAccount.js @@ -0,0 +1,68 @@ +const dotenv = require("dotenv"); +dotenv.config(); +const path = require("path"); +const { ethers, Wallet } = require("ethers"); +const QRCode = require("qrcode"); +const fs = require("fs"); +const toml = require("toml"); + +const ALCHEMY_API_KEY = process.env.ALCHEMY_API_KEY || "oKxs-03sij-U_N0iOlrSsZFr29-IqbuF"; + +async function getBalanceForEachNetwork(address) { + try { + // Read the foundry.toml file + const foundryTomlPath = path.join(__dirname, "..", "foundry.toml"); + const tomlString = fs.readFileSync(foundryTomlPath, "utf-8"); + + // Parse the tomlString to get the JS object representation + const parsedToml = toml.parse(tomlString); + + // Extract rpc_endpoints from parsedToml + const rpcEndpoints = parsedToml.rpc_endpoints; + + // Replace placeholders in the rpc_endpoints section + function replaceENVAlchemyKey(input) { + return input.replace("${ALCHEMY_API_KEY}", ALCHEMY_API_KEY); + } + + for (const networkName in rpcEndpoints) { + if (networkName === "localhost" || networkName === "default_network") continue; + + const networkUrl = replaceENVAlchemyKey(rpcEndpoints[networkName]); + + try { + const provider = new ethers.providers.JsonRpcProvider(networkUrl); + const balance = await provider.getBalance(address); + console.log("--", networkName, "-- 📡"); + console.log(" balance:", +ethers.utils.formatEther(balance)); + console.log(" nonce:", +(await provider.getTransactionCount(address))); + } catch (e) { + console.log("Can't connect to network", networkName); + console.log(); + } + } + } catch (error) { + console.error("Error reading foundry.toml:", error); + } +} +async function main() { + const privateKey = process.env.DEPLOYER_PRIVATE_KEY; + + if (!privateKey) { + console.log("🚫️ You don't have a deployer account. Run `yarn generate` first"); + return; + } + + // Get account from private key. + const wallet = new Wallet(privateKey); + const address = wallet.address; + console.log(await QRCode.toString(address, { type: "terminal", small: true })); + console.log("Public address:", address, "\n"); + + await getBalanceForEachNetwork(address); +} + +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/packages/contracts/script/generateAccount.js b/packages/contracts/script/generateAccount.js new file mode 100644 index 0000000..d6b2be3 --- /dev/null +++ b/packages/contracts/script/generateAccount.js @@ -0,0 +1,49 @@ +const ethers = require("ethers"); +const { parse, stringify } = require("envfile"); +const fs = require("fs"); + +const envFilePath = "./.env"; + +/** + * Generate a new random private key and write it to the .env file + * @param existingEnvConfig + */ +const setNewEnvConfig = (existingEnvConfig = {}) => { + console.log("👛 Generating new Wallet"); + const randomWallet = ethers.Wallet.createRandom(); + + const newEnvConfig = { + ...existingEnvConfig, + DEPLOYER_PRIVATE_KEY: randomWallet.privateKey, + }; + + // Store in .env + fs.writeFileSync(envFilePath, stringify(newEnvConfig)); + console.log("📄 Private Key saved to packages/foundry/.env file"); + console.log("🪄 Generated wallet address:", randomWallet.address); +}; + +async function main() { + if (!fs.existsSync(envFilePath)) { + console.log("entered here"); + // No .env file yet. + setNewEnvConfig(); + return; + } + + // .env file exists + const existingEnvConfig = parse(fs.readFileSync(envFilePath).toString()); + if (existingEnvConfig.DEPLOYER_PRIVATE_KEY) { + console.log( + "⚠️ You already have a deployer account. Check the packages/foundry/.env file" + ); + return; + } + + setNewEnvConfig(existingEnvConfig); +} + +main().catch((error) => { + console.error(error); + process.exitCode = 1; +}); diff --git a/packages/contracts/script/generateTsAbis.js b/packages/contracts/script/generateTsAbis.js new file mode 100644 index 0000000..699c914 --- /dev/null +++ b/packages/contracts/script/generateTsAbis.js @@ -0,0 +1,148 @@ +const fs = require("fs"); +const path = require("path"); +//@ts-expect-error This script runs after `forge deploy` therefore its deterministic that it will present +// const deployments = require("../deployments.json"); +const prettier = require("prettier"); + +const generatedContractComment = ` +/** + * This file is autogenerated by Scaffold-ETH. + * You should not edit it manually or your changes might be overwritten. + */ +`; + +function getDirectories(path) { + return fs.readdirSync(path).filter(function (file) { + return fs.statSync(path + "/" + file).isDirectory(); + }); +} +function getFiles(path) { + return fs.readdirSync(path).filter(function (file) { + return fs.statSync(path + "/" + file).isFile(); + }); +} +function getArtifactOfContract(contractName) { + const current_path_to_artifacts = path.join( + __dirname, + "..", + `out/${contractName}.sol` + ); + const artifactJson = JSON.parse( + fs.readFileSync(`${current_path_to_artifacts}/${contractName}.json`) + ); + + return artifactJson; +} + +function getInheritedFromContracts(artifact) { + let inheritedFromContracts = []; + if (artifact?.ast) { + for (const astNode of artifact.ast.nodes) { + if (astNode.nodeType == "ContractDefinition") { + if (astNode.baseContracts.length > 0) { + inheritedFromContracts = astNode.baseContracts.map( + ({ baseName }) => baseName.name + ); + } + } + } + } + return inheritedFromContracts; +} + +function getInheritedFunctions(mainArtifact) { + const inheritedFromContracts = getInheritedFromContracts(mainArtifact); + const inheritedFunctions = {}; + for (const inheritanceContractName of inheritedFromContracts) { + const { + abi, + ast: { absolutePath }, + } = getArtifactOfContract(inheritanceContractName); + for (const abiEntry of abi) { + if (abiEntry.type == "function") { + inheritedFunctions[abiEntry.name] = absolutePath; + } + } + } + return inheritedFunctions; +} + +function main() { + const current_path_to_broadcast = path.join( + __dirname, + "..", + "broadcast/Deploy.s.sol" + ); + const current_path_to_deployments = path.join(__dirname, "..", "deployments"); + + const chains = getDirectories(current_path_to_broadcast); + const Deploymentchains = getFiles(current_path_to_deployments); + + const deployments = {}; + + Deploymentchains.forEach((chain) => { + if (!chain.endsWith(".json")) return; + chain = chain.slice(0, -5); + var deploymentObject = JSON.parse( + fs.readFileSync(`${current_path_to_deployments}/${chain}.json`) + ); + deployments[chain] = deploymentObject; + }); + + const allGeneratedContracts = {}; + + chains.forEach((chain) => { + allGeneratedContracts[chain] = {}; + const broadCastObject = JSON.parse( + fs.readFileSync(`${current_path_to_broadcast}/${chain}/run-latest.json`) + ); + const transactionsCreate = broadCastObject.transactions.filter( + (transaction) => transaction.transactionType == "CREATE" + ); + transactionsCreate.forEach((transaction) => { + const artifact = getArtifactOfContract(transaction.contractName); + allGeneratedContracts[chain][ + deployments[chain][transaction.contractAddress] || + transaction.contractName + ] = { + address: transaction.contractAddress, + abi: artifact.abi, + inheritedFunctions: getInheritedFunctions(artifact), + }; + }); + }); + + const TARGET_DIR = "../nextjs/contracts/"; + + const fileContent = Object.entries(allGeneratedContracts).reduce( + (content, [chainId, chainConfig]) => { + return `${content}${parseInt(chainId).toFixed(0)}:${JSON.stringify( + chainConfig, + null, + 2 + )},`; + }, + "" + ); + + if (!fs.existsSync(TARGET_DIR)) { + fs.mkdirSync(TARGET_DIR); + } + fs.writeFileSync( + `${TARGET_DIR}deployedContracts.ts`, + prettier.format( + `${generatedContractComment} import { GenericContractsDeclaration } from "~~/utils/scaffold-eth/contract"; \n\n + const deployedContracts = {${fileContent}} as const; \n\n export default deployedContracts satisfies GenericContractsDeclaration`, + { + parser: "typescript", + } + ) + ); +} + +try { + main(); +} catch (error) { + console.error(error); + process.exitCode = 1; +} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index a108dfa..924b55f 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,3 +1,2 @@ packages: - packages/* - - packages/clients/*