diff --git a/migrations/deploy.js b/migrations/deploy.js deleted file mode 100644 index d87be2fa..00000000 --- a/migrations/deploy.js +++ /dev/null @@ -1,62 +0,0 @@ - -const hre = require('hardhat') -const fs = require('fs'); -const c = require('config'); -CUSTOM_PROOF_IMPORT -const saveMetadata = require('./metadata').saveMetadata - -const functionNames = [FUNCTION_NAMES]; -const vkInput = []; -let vk = []; -functionNames.forEach((name) => { - const vkJson = JSON.parse( - fs.readFileSync(`/app/orchestration/common/db/${name}_vk.key`, "utf-8") - ); - if (vkJson.scheme) { - vk = Object.values(vkJson).slice(2).flat(Infinity); - } else { - vk = Object.values(vkJson).flat(Infinity); - } - vkInput.push(vk); -}); - - -async function main () { - try { - - const chainId = (await hre.ethers.provider.getNetwork()).chainId - const Verifier = await hre.ethers.getContractFactory('Verifier'); - const verifier = await Verifier.deploy(); - await verifier.waitForDeployment(); - const verifierAddress = await verifier.getAddress(); - let blockNumber = await hre.ethers.provider.getBlockNumber(); - let deployTx = await verifier.deploymentTransaction().wait() - console.log('Verifier deployed to:', contractAddress, 'tx hash:', deployTx.hash); - saveMetadata(verifierAddress, 'Verifier', chainId, blockNumber, deployTx.hash); - console.log('Verifier deployed to:', verifierAddress); - - CUSTOM_CONTRACTS - // Deploy AssignShield with Verifier and vkInput - const CONTRACT_NAME = await hre.ethers.getContractFactory('CONTRACT_NAME'); - const contractShield = await CONTRACT_NAME.deploy(verifierAddress, vkInput); - await contractShield.waitForDeployment(); - const contractAddress = await contractShield.getAddress(); - blockNumber = await hre.ethers.provider.getBlockNumber(); - console.log('CONTRACT_NAME deployed to:', contractAddress); - deployTx = await contractShield.deploymentTransaction().wait() - console.log('CONTRACT_NAME deployed to:', contractAddress, 'tx hash:', deployTx.hash); - saveMetadata(contractAddress, 'CONTRACT_NAME', chainId, blockNumber, deployTx.hash); - console.log("Deployment successful! Exiting..."); - - // Ensure all pending tasks are completed before exit - setTimeout(() => process.exit(0), 1000); -} catch (error) { - console.error('Deployment failed:', error); - process.exit(1); -} -} - -main().catch((error) => { - console.error(error) - process.exitCode = 1 -}) \ No newline at end of file diff --git a/migrations/metadata.js b/migrations/metadata.js deleted file mode 100644 index f168989b..00000000 --- a/migrations/metadata.js +++ /dev/null @@ -1,66 +0,0 @@ -const fs = require('fs') - -function saveMetadata ( - contractDeployedAddress, - contractName, - networkId, - blockNumber, - transactionHash -) { - - const projectFolder = process.env.PROJECT_PATH || '/app' - const buildFolder = projectFolder + '/build/contracts/' - - if (!fs.existsSync(buildFolder)) { - fs.mkdirSync(buildFolder, { recursive: true }); - } - - const deployedFileName = buildFolder + contractName + '.json' - - let deployedMetadata = { - contractName: '', - abi: {}, - networks: {} - } - const deployedPositionMetadata = { - address: '', - transactionHash: '', - blockNumber: 0 - } - - if (fs.existsSync(deployedFileName)) { - const oldDeployedMetadata = JSON.parse(fs.readFileSync(deployedFileName, 'utf-8')) - if (oldDeployedMetadata.contractName === contractName) { - deployedMetadata = oldDeployedMetadata - } - } - - - const hardhatArtifactContractPath = './artifacts/contracts' - // console.log("hardhatArtifactContractPath: ", hardhatArtifactContractPath); - const hardhatArtifactPath = - hardhatArtifactContractPath + - '/' + contractName + '.sol' + - '/' + - contractName + - '.json' - // console.log("hardhatArtifactPath: ", hardhatArtifactPath); - - const compilationData = fs.readFileSync(hardhatArtifactPath, 'utf-8') - const abi = JSON.parse(compilationData).abi - const contractNameFromHardhat = JSON.parse(compilationData).contractName - - deployedMetadata.abi = abi - deployedMetadata.contractName = contractNameFromHardhat - deployedPositionMetadata.address = contractDeployedAddress - deployedPositionMetadata.blockNumber = blockNumber - deployedPositionMetadata.transactionHash = transactionHash - deployedMetadata.networks[networkId] = deployedPositionMetadata - - console.log('Writing: ...') - fs.writeFileSync(deployedFileName, JSON.stringify(deployedMetadata, null, 2), "utf-8") -} - -module.exports = { - saveMetadata -} \ No newline at end of file diff --git a/src/boilerplate/common/bin/setup-public b/src/boilerplate/common/bin/setup-public new file mode 100644 index 00000000..75bc5167 --- /dev/null +++ b/src/boilerplate/common/bin/setup-public @@ -0,0 +1,109 @@ +#!/bin/bash +set -e + +GREEN='\033[0;32m' +RED='\033[0;31m' +NC='\033[0m' + +while getopts "n:a:m:k:r:s:" arg; do + case $arg in + n) + network=$OPTARG + echo networkvalue $OPTARG + ;; + a) + account=$OPTARG + echo accountvalue $OPTARG + ;; + m) + mnemonic=$OPTARG + echo mnemonicvalue $OPTARG + ;; + k) + key=$OPTARG + echo keyvalue $OPTARG + ;; + r) + rpc=$OPTARG + echo rpcvalue $OPTARG + ;; + s) + setup=$OPTARG + echo setup $OPTARG + ;; + esac +done + +cp docker-compose.zapp.override.default.yml docker-compose.zapp.override.yml + +cp deploy_default.sh deploy.sh + +cp config/default_standard.js config/default.js + +cp bin/default_startup bin/startup + + +rm -rf proving-files + +perl -i -pe "s,docker-compose.zapp.yml -f docker-compose.zapp.override.yml,docker-compose.zapp.yml,g" package.json + +if [[ $network == 'amoy' ]] || [[ $network == 'sepolia' ]] || [[ $network == 'goerli' ]] || [[ $network == 'cardona' ]]|| [[ $network == 'zkEVM' ]] || [[ $network == 'base-mainnet' ]] +then +perl -i -pe "s,DEFAULT_ACCOUNT: '',DEFAULT_ACCOUNT: \'$account\',g" docker-compose.zapp.override.yml +perl -i -pe "s,DEFAULT_ACCOUNT_MNEMONIC: '',DEFAULT_ACCOUNT_MNEMONIC: \'$mnemonic\',g" docker-compose.zapp.override.yml +perl -i -pe "s,KEY: '',KEY: \'$key\',g" docker-compose.zapp.override.yml +perl -i -pe "s,docker-compose.zapp.yml up,docker-compose.zapp.yml -f docker-compose.zapp.override.yml up,g" bin/startup +perl -i -pe "s,docker-compose.zapp.yml,docker-compose.zapp.yml -f docker-compose.zapp.override.yml,g" package.json +perl -i -pe "s,docker compose -f docker-compose.zapp.yml -f docker-compose.zapp.override.yml up -d ganache, ## up ganache service for ganache,g" bin/startup +perl -i -pe "s,! nc -z localhost 8545,false,g" bin/startup +fi + +if [[ $network == 'amoy' ]] +then +perl -i -pe "if (!\$found1 && s/RPC_URL: ''/RPC_URL: 'wss:\/\/polygon-amoy.g.alchemy.com\/v2\/$rpc'/) { \$found1 = 1 }" docker-compose.zapp.override.yml +perl -i -pe "if (!\$found2 && s/RPC_URL: ''/RPC_URL: 'https:\/\/polygon-amoy.g.alchemy.com\/v2\/$rpc'/) { \$found2 = 1 }" docker-compose.zapp.override.yml +perl -i -pe "if (!\$found3 && s/RPC_URL: ''/RPC_URL: 'wss:\/\/polygon-amoy.g.alchemy.com\/v2\/$rpc'/) { \$found3 = 1 }" docker-compose.zapp.override.yml +perl -i -pe "s,migrations/deploy.js,migrations/deploy.js --network amoy,g" deploy.sh +fi + + +if [[ $network == 'sepolia' ]] +then +perl -i -pe "s,RPC_URL: '',RPC_URL: \'wss://sepolia.infura.io/ws/v3//$rpc\',g" docker-compose.zapp.override.yml +perl -i -pe "s,migrations/deploy.js,migrations/deploy.js --network sepolia,g" deploy.sh +fi + +if [[ $network == 'goerli' ]] +then +perl -i -pe "s,RPC_URL: '',RPC_URL: \'wss://goerli.infura.io/ws/v3/$rpc\',g" docker-compose.zapp.override.yml +perl -i -pe "s,migrations/deploy.js,migrations/deploy.js --network goerli,g" deploy.sh +fi + +if [[ $network == 'cardona' ]] +then +perl -i -pe "if (!\$found1 && s/RPC_URL: ''/RPC_URL: 'wss:\/\/polygon-zkevm-cardona.blastapi.io\/$rpc'/) { \$found1 = 1 }" docker-compose.zapp.override.yml +perl -i -pe "if (!\$found2 && s/RPC_URL: ''/RPC_URL: 'https:\/\/polygon-zkevm-cardona.blastapi.io\/$rpc'/) { \$found2 = 1 }" docker-compose.zapp.override.yml +perl -i -pe "if (!\$found3 && s/RPC_URL: ''/RPC_URL: 'wss:\/\/polygon-zkevm-cardona.blastapi.io\/$rpc'/) { \$found3 = 1 }" docker-compose.zapp.override.yml +perl -i -pe "s,migrations/deploy.js,migrations/deploy.js --network cardona,g" deploy.sh +perl -i -pe "s,defaultGasPrice: 30000000000,defaultGasPrice: 5,g" config/default.js +fi + +if [[ $network == 'zkEVM' ]] +then +perl -i -pe "if (!\$found1 && s/RPC_URL: ''/RPC_URL: 'wss:\/\/polygon-zkevm-mainnet.blastapi.io\/$rpc'/) { \$found1 = 1 }" docker-compose.zapp.override.yml +perl -i -pe "if (!\$found2 && s/RPC_URL: ''/RPC_URL: 'https:\/\/polygon-zkevm-mainnet.blastapi.io\/$rpc'/) { \$found2 = 1 }" docker-compose.zapp.override.yml +perl -i -pe "if (!\$found3 && s/RPC_URL: ''/RPC_URL: 'wss:\/\/polygon-zkevm-mainnet.blastapi.io\/$rpc'/) { \$found3 = 1 }" docker-compose.zapp.override.yml +perl -i -pe "s,migrations/deploy.js,migrations/deploy.js --network zkEVM,g" deploy.sh +perl -i -pe "s,defaultGasPrice: 30000000000,defaultGasPrice: 5,g" config/default.js +fi + +if [[ $network == 'base-mainnet' ]] +then +perl -i -pe "if (!\$found1 && s/RPC_URL: ''/RPC_URL: 'wss:\/\/base-mainnet.g.alchemy.com\/v2\/$rpc'/) { \$found1 = 1 }" docker-compose.zapp.override.yml +perl -i -pe "if (!\$found2 && s/RPC_URL: ''/RPC_URL: 'https:\/\/base-mainnet.g.alchemy.com\/v2\/$rpc'/) { \$found2 = 1 }" docker-compose.zapp.override.yml +perl -i -pe "if (!\$found3 && s/RPC_URL: ''/RPC_URL: 'wss:\/\/base-mainnet.g.alchemy.com\/v2\/$rpc'/) { \$found3 = 1 }" docker-compose.zapp.override.yml +perl -i -pe "s,migrations/deploy.js,migrations/deploy.js --network base-mainnet,g" deploy.sh +perl -i -pe "s,defaultGasPrice: 30000000000,//defaultGasPrice: 30000000000,g" config/default.js +fi + +printf "\n${GREEN}*** Finished! ***${NC}\n" diff --git a/src/boilerplate/common/migrations/deploy-public.js b/src/boilerplate/common/migrations/deploy-public.js new file mode 100644 index 00000000..84e011e5 --- /dev/null +++ b/src/boilerplate/common/migrations/deploy-public.js @@ -0,0 +1,39 @@ + +const hre = require('hardhat') +const fs = require('fs'); +const c = require('config'); +CUSTOM_CONTRACT_IMPORT +CUSTOM_PROOF_IMPORT +const saveMetadata = require('./metadata').saveMetadata + + +async function main () { + try { + + const chainId = (await hre.ethers.provider.getNetwork()).chainId; + + CUSTOM_CONTRACTS + // Deploy AssignShield + const CONTRACT_NAME = await hre.ethers.getContractFactory('CONTRACT_NAME'); + const contractShield = await CONTRACT_NAME.deploy(CUSTOM_INPUTS); + await contractShield.waitForDeployment(); + const contractAddress = await contractShield.getAddress(); + blockNumber = await hre.ethers.provider.getBlockNumber(); + console.log('CONTRACT_NAME deployed to:', contractAddress); + deployTx = await contractShield.deploymentTransaction().wait() + console.log('CONTRACT_NAME deployed to:', contractAddress, 'tx hash:', deployTx.hash); + saveMetadata(contractAddress, 'CONTRACT_NAME', "", chainId, blockNumber, deployTx.hash); + console.log("Deployment successful! Exiting..."); + + // Ensure all pending tasks are completed before exit + setTimeout(() => process.exit(0), 1000); +} catch (error) { + console.error('Deployment failed:', error); + process.exit(1); +} +} + +main().catch((error) => { + console.error(error) + process.exitCode = 1 +}) \ No newline at end of file diff --git a/src/boilerplate/contract/solidity/nodes/ContractBoilerplateGenerator.ts b/src/boilerplate/contract/solidity/nodes/ContractBoilerplateGenerator.ts index 08a4be52..ab14f3d6 100644 --- a/src/boilerplate/contract/solidity/nodes/ContractBoilerplateGenerator.ts +++ b/src/boilerplate/contract/solidity/nodes/ContractBoilerplateGenerator.ts @@ -114,68 +114,69 @@ class ContractBoilerplateGenerator { let paramtype: string; let params : any[]; let functionName: string; - - for ([functionName, parameterList] of Object.entries(circuitParams)) { - for ([paramtype, params] of Object.entries(parameterList)){ - const returnpara = {}; - if(paramtype === 'returnParameters'){ - returnpara[ paramtype ] = params; - delete parameterList[ paramtype ]; - } - const newList: string[] = []; - - params?.forEach(circuitParamNode => { - switch (circuitParamNode.bpType) { - case 'nullification': - if (circuitParamNode.isNullified) { - newList.push('nullifier'); - } else { - // we use a nullification node for accessed, not nullified, states - newList.push('checkNullifier') - } - break; - case 'newCommitment': - newList.push(circuitParamNode.bpType); - break; - case 'oldCommitmentExistence': - if (!newList.includes(circuitParamNode.bpType)) newList.push(circuitParamNode.bpType); - break; - case 'encryption': - - returnpara['encryptionParameters'] ??= []; - returnpara['encryptionParameters'].push(circuitParamNode.bpType); - break; - case undefined: { - if ( - circuitParamNode.nodeType === 'VariableDeclaration' && - !circuitParamNode.isPrivate && - !newList.some(str => str === circuitParamNode.name) - ){ - if (circuitParamNode.typeName?.members) { - newList.push(...circuitParamNode.typeName.members.map(m => `${circuitParamNode.name}.${m.name}`)); + if (circuitParams) { + for ([functionName, parameterList] of Object.entries(circuitParams)) { + for ([paramtype, params] of Object.entries(parameterList)) { + const returnpara = {}; + if(paramtype === 'returnParameters'){ + returnpara[ paramtype ] = params; + delete parameterList[ paramtype ]; + } + const newList: string[] = []; + + params?.forEach(circuitParamNode => { + switch (circuitParamNode.bpType) { + case 'nullification': + if (circuitParamNode.isNullified) { + newList.push('nullifier'); + } else { + // we use a nullification node for accessed, not nullified, states + newList.push('checkNullifier') + } + break; + case 'newCommitment': + newList.push(circuitParamNode.bpType); break; - } else if (circuitParamNode.typeName?.name.includes(`[`)) { - // TODO arrays of structs/structs of arrays/more robust soln - const arrayLen = circuitParamNode.typeName?.name.match(/(?<=\[)(\d+)(?=\])/); - for (let index = 0; index < +arrayLen[0]; index++) { - newList.push(`${circuitParamNode.name}[${index}]`); + case 'oldCommitmentExistence': + if (!newList.includes(circuitParamNode.bpType)) newList.push(circuitParamNode.bpType); + break; + case 'encryption': + + returnpara['encryptionParameters'] ??= []; + returnpara['encryptionParameters'].push(circuitParamNode.bpType); + break; + case undefined: { + if ( + circuitParamNode.nodeType === 'VariableDeclaration' && + !circuitParamNode.isPrivate && + !newList.some(str => str === circuitParamNode.name) + ){ + if (circuitParamNode.typeName?.members) { + newList.push(...circuitParamNode.typeName.members.map(m => `${circuitParamNode.name}.${m.name}`)); + break; + } else if (circuitParamNode.typeName?.name.includes(`[`)) { + // TODO arrays of structs/structs of arrays/more robust soln + const arrayLen = circuitParamNode.typeName?.name.match(/(?<=\[)(\d+)(?=\])/); + for (let index = 0; index < +arrayLen[0]; index++) { + newList.push(`${circuitParamNode.name}[${index}]`); + } + break; + } else newList.push(circuitParamNode.name); } + } + break; + + default: break; - } else newList.push(circuitParamNode.name); } - } - break; - - default: - break; + }); + parameterList[ paramtype ] = newList; + parameterList = {...parameterList, ...returnpara}; } - }); - parameterList[ paramtype ] = newList; - parameterList = {...parameterList, ...returnpara}; + circuitParams[ functionName ] = parameterList; + } } - circuitParams[ functionName ] = parameterList; - - } + const constructorContainsSecret = Object.values(this.scope.bindings).some((binding: any) => binding.node.kind === 'constructor'); return { nullifiersRequired, oldCommitmentAccessRequired, newCommitmentsRequired, containsAccessedOnlyState, encryptionRequired, constructorContainsSecret, circuitParams, isjoinSplitCommitmentsFunction}; }, diff --git a/src/boilerplate/contract/solidity/raw/ContractBoilerplateGenerator.ts b/src/boilerplate/contract/solidity/raw/ContractBoilerplateGenerator.ts index 911c7dbf..dea9db85 100644 --- a/src/boilerplate/contract/solidity/raw/ContractBoilerplateGenerator.ts +++ b/src/boilerplate/contract/solidity/raw/ContractBoilerplateGenerator.ts @@ -226,32 +226,34 @@ class ContractBoilerplateGenerator { ]; const verifyInputs: string[] = []; const joinSplitCommitmentsInputs: string[] = []; - for (let [name, _params] of Object.entries(circuitParams)) { - if (_params) - for (let [type, _inputs] of Object.entries(_params)) { - const counter = { - customInputs: 0, - newNullifiers: 0, - checkNullifiers: 0, - newCommitments: 0, - encryption: 0, - }; - _inputs?.map(i => verifyInputsMap(type, i, counter)); - - - - } - - if(_params && !(Object.keys(_params).includes('returnParameters')) &&!(Object.keys(_params).includes('encryptionParameters'))) verifyInput.push(` - inputs[k++] = 1;`) - - verifyInputs.push(` - if (functionId == uint(FunctionNames.${name})) { - uint k = 0; - ${verifyInput.join('')} + if (circuitParams) { + for (let [name, _params] of Object.entries(circuitParams)) { + if (_params) + for (let [type, _inputs] of Object.entries(_params)) { + const counter = { + customInputs: 0, + newNullifiers: 0, + checkNullifiers: 0, + newCommitments: 0, + encryption: 0, + }; + _inputs?.map(i => verifyInputsMap(type, i, counter)); + + + + } - }`) - verifyInput =[]; + if(_params && !(Object.keys(_params).includes('returnParameters')) &&!(Object.keys(_params).includes('encryptionParameters'))) verifyInput.push(` + inputs[k++] = 1;`) + + verifyInputs.push(` + if (functionId == uint(FunctionNames.${name})) { + uint k = 0; + ${verifyInput.join('')} + + }`) + verifyInput =[]; + } } const verification: string[] = [ diff --git a/src/boilerplate/orchestration/javascript/nodes/boilerplate-generator.ts b/src/boilerplate/orchestration/javascript/nodes/boilerplate-generator.ts index 43fd6aa8..0097a485 100644 --- a/src/boilerplate/orchestration/javascript/nodes/boilerplate-generator.ts +++ b/src/boilerplate/orchestration/javascript/nodes/boilerplate-generator.ts @@ -396,6 +396,7 @@ export function buildBoilerplateNode(nodeType: string, fields: any = {}): any { constructorParams = [], contractImports = [], isConstructor = false, + fullyPublicContract = false, } = fields; return { nodeType, @@ -404,6 +405,7 @@ export function buildBoilerplateNode(nodeType: string, fields: any = {}): any { constructorParams, contractImports, isConstructor, + fullyPublicContract, }; } case 'EditableCommitmentCommonFilesBoilerplate': { diff --git a/src/boilerplate/orchestration/javascript/raw/toOrchestration.ts b/src/boilerplate/orchestration/javascript/raw/toOrchestration.ts index 056a9868..63b214e2 100644 --- a/src/boilerplate/orchestration/javascript/raw/toOrchestration.ts +++ b/src/boilerplate/orchestration/javascript/raw/toOrchestration.ts @@ -481,7 +481,8 @@ export const OrchestrationCodeBoilerPlate: any = (node: any) => { case 'FunctionDefinition': // the main function class - if (node.name !== 'cnstrctr') {functionSig.push( + if (node.name !== 'cnstrctr') { + functionSig.push( `export class ${(node.name).charAt(0).toUpperCase() + node.name.slice(1)}Manager { constructor(web3) { this.web3 = web3; @@ -581,10 +582,10 @@ export const OrchestrationCodeBoilerPlate: any = (node: any) => { statements: lines, }; } - if(rtnparams.includes('bool: bool')) { + if (rtnparams.includes('bool: bool')) { return { signature: [ - ` + `${functionSig} \n async ${node.name}(${params} ${states}) {`, `\n const bool = true; \n return { ${txReturns} ${rtnparams}, ${publicReturns} }; \n} @@ -718,7 +719,10 @@ export const OrchestrationCodeBoilerPlate: any = (node: any) => { })); } } - if (node.isConstructor) lines.push(`\nfs.writeFileSync("/app/orchestration/common/db/constructorTx.json", JSON.stringify(tx, null, 4));`) + if (node.isConstructor) + lines.push( + `\nfs.writeFileSync("/app/orchestration/common/db/constructorTx.json", JSON.stringify(tx, null, 4));`, + ); return { statements: [ `\n// Write new commitment preimage to db: \n`, @@ -1113,10 +1117,13 @@ export const OrchestrationCodeBoilerPlate: any = (node: any) => { statements: [ `\n\n// Save transaction for the constructor: \nconst tx = { ${lines}}; + \n if (!fs.existsSync("/app/orchestration/common/db")) { + \n fs.mkdirSync("/app/orchestration/common/db", { recursive: true }); + \n } \nfs.writeFileSync("/app/orchestration/common/db/constructorTx.json", JSON.stringify(tx, null, 4));` - ] - } - } + ], + }; + } if (node.publicInputs[0]) { node.publicInputs.forEach((input: any) => { @@ -1152,7 +1159,6 @@ export const OrchestrationCodeBoilerPlate: any = (node: any) => { ], }; } - return { statements: [ `${returnsCallPublic} diff --git a/src/codeGenerators/common.ts b/src/codeGenerators/common.ts index 0bcfcafc..8d9cef5c 100644 --- a/src/codeGenerators/common.ts +++ b/src/codeGenerators/common.ts @@ -18,8 +18,9 @@ export interface localFile { export const collectImportFiles = ( file: string, context: string, + fullyPublicContract?: boolean, contextDirPath?: string, - fileName: string = '', + fileName = '', ) => { const lines = file.split('\n'); let ImportStatementList: string[]; @@ -99,7 +100,12 @@ export const collectImportFiles = ( const relPath = path.relative('.', absPath); const exists = fs.existsSync(relPath); if (!exists) continue; - const f = fs.readFileSync(relPath, 'utf8'); + let readPath = relPath; + // When the contract is fully public, we use deploy-public.js instead of deploy.js + if (fullyPublicContract && readPath.includes('deploy.js')) { + readPath = relPath.replace('deploy.js', 'deploy-public.js'); + } + const f = fs.readFileSync(readPath, 'utf8'); const n = path.basename(absPath, path.extname(absPath)); const shortRelPath = path.relative(path.resolve(fileURLToPath(import.meta.url), '../../../'), absPath); const writePath = context === 'orchestration' ? path.join( @@ -129,7 +135,15 @@ export const collectImportFiles = ( file: f, }); - localFiles = localFiles.concat(collectImportFiles(f, context, path.dirname(relPath), context === 'contract' ? n : '')); + localFiles = localFiles.concat( + collectImportFiles( + f, + context, + fullyPublicContract, + path.dirname(relPath), + context === 'contract' ? n : '', + ), + ); } // remove duplicate files after recursion: diff --git a/src/codeGenerators/contract/solidity/toContract.ts b/src/codeGenerators/contract/solidity/toContract.ts index dd3e810f..f52cd827 100644 --- a/src/codeGenerators/contract/solidity/toContract.ts +++ b/src/codeGenerators/contract/solidity/toContract.ts @@ -83,21 +83,20 @@ function codeGenerator(node: any) { break; } - // add any public return here, node.returnParameters.parameters.forEach(params => { // We check that params.name is defined because otherwise this is a commitment - if(!params.isSecret && params.name != undefined && params.typeDescriptions.typeString != 'bool') { + if(!params.isSecret && params.name != undefined) { returnType.push(params.typeDescriptions.typeString); - returnParams.push(params.name); + if (params.name === "") returnParams.push("\"\""); + else returnParams.push(params.name); } - }) + }); const functionSignature = `${functionType} (${codeGenerator(node.parameters)}) ${node.visibility} ${node.stateMutability} ${returnType.length > 0 ? `returns (${returnType})`: ``}{`; let body = codeGenerator(node.body); let msgSigCheck = body.slice(body.indexOf('bytes4 sig'), body.indexOf('verify') ) - if(!node.msgSigRequired) - body = body.replace(msgSigCheck, ' '); - return ` + if (!node.msgSigRequired) body = body.replace(msgSigCheck, ' '); + return ` ${functionSignature} ${body} @@ -149,7 +148,7 @@ function codeGenerator(node: any) { if(node.initialValue.nodeType === 'InternalFunctionCall'){ if(node.interactsWithSecret) return ; return ` - ${declarations} = ${initialValue.replace(/\s+/g,' ').trim()}`; + ${declarations} = ${initialValue.replace(/\s+/g,' ').trim()};`; } return ` ${declarations} = ${initialValue};`; @@ -166,19 +165,31 @@ function codeGenerator(node: any) { let expr = codeGenerator(node.expression); if (typeof expr === 'undefined' || expr === null) return ''; let semicolon = ''; - if (node.expression.nodeType === 'FunctionCall') { + if ( + node.expression.nodeType === 'FunctionCall' || + node.expression.nodeType === 'UnaryOperation' || + node.expression.nodeType === 'InternalFunctionCall' + ) { semicolon = ';'; } return `${expr}${semicolon}`; } case 'Return': - return ` `; - case 'Break': + case 'Break': return `break;`; + case 'Conditional': { + const condition = codeGenerator(node.condition); + const trueBody = codeGenerator(node.trueExpression); + const falseBody = codeGenerator(node.falseExpression); + return `${condition} ? + ${trueBody} + : ${falseBody}`; + } + case 'Continue': return 'continue;'; @@ -205,11 +216,16 @@ function codeGenerator(node: any) { return `${codeGenerator(node.expression)}(${codeGenerator(node.arguments)})`; case 'UnaryOperation': - if (node.operator === '!'){ + if (node.operator === '!') { return `${node.operator}${codeGenerator(node.subExpression)}`; } - if (node.prefix === true) return `${node.operator}${codeGenerator(node.subExpression)};`; - return `${codeGenerator(node.subExpression)} ${node.operator};`; + if (node.prefix === true) { + if (node.operator === 'delete') { + return `${node.operator} ${codeGenerator(node.subExpression)}`; + } + return `${node.operator}${codeGenerator(node.subExpression)}`; + } + return `${codeGenerator(node.subExpression)}${node.operator}`; case 'EmitStatement': return `\t \t \t \temit ${codeGenerator(node.eventCall)};`; @@ -219,48 +235,47 @@ function codeGenerator(node: any) { const args = node.arguments.map(codeGenerator); return `${expression}(${args.join(', ')})`; } - case 'InternalFunctionCall' :{ - if(node.parameters ){ - if(node.internalFunctionInteractsWithSecret) - return `\t \t \t \t ${node.name} (${node.parameters});` - return `\t \t \t \t ${node.name} (${node.parameters.map(codeGenerator)});` - } else { - const args = node.arguments.map(codeGenerator); - return `\t \t \t \t${node.name} (${args.join(', ')});` + case 'InternalFunctionCall': { + if (node.parameters) { + if (node.internalFunctionInteractsWithSecret) + return `\t \t \t \t ${node.name} (${node.parameters})`; + return `\t \t \t \t ${node.name} (${node.parameters.map( + codeGenerator, + )})`; } + const args = node.arguments.map(codeGenerator); + return `\t \t \t \t${node.name} (${args.join(', ')})`; } - - case 'IfStatement': - { - let trueStatements: any = ``; - let falseStatements: any= ``; - let initialStatements: any= codeGenerator(node.condition); - for (let i =0; i { // remove any duplicates from fnction parameters fnParam = [...new Set(fnParam)]; // Adding Return parameters - let returnParams: string[] = []; - let returnParamsName = fn.returnParameters.parameters - .filter((paramnode: any) => (paramnode.isSecret || paramnode.typeName.name === 'bool')) - .map(paramnode => (paramnode.name)) || []; // Adapt - if(returnParamsName.length > 0){ + const returnParams: string[] = []; + const returnParamsName = + fn.returnParameters.parameters + .filter( + (paramnode: any) => + paramnode.isSecret || paramnode.typeName.name === 'bool', + ) + .map(paramnode => paramnode.name) || []; // Adapt + if (returnParamsName?.length > 0) { returnParamsName.forEach(param => { - if(param !== 'true') - returnParams.push(param+'_newCommitmentValue'); - else - returnParams.push('bool'); + if (param !== 'true') returnParams.push(param + '_newCommitmentValue'); + else returnParams.push('bool'); }); } @@ -400,7 +402,7 @@ const prepareMigrationsFile = (file: localFile, node: any) => { file.file = file.file.replace(/CONTRACT_NAME/g, node.contractName); file.file = file.file.replace( /FUNCTION_NAMES/g, - `'${node.functionNames.join(`', '`)}'`, + node.functionNames.length ? `'${node.functionNames.join(`', '`)}'` : ``, ); // collect any extra constructor parameters const constructorParamNames = node.constructorParams?.filter((obj: any) => !obj.isSecret).map((obj: any) => obj.name) || ``; @@ -1074,16 +1076,31 @@ export default function fileGenerator(node: any) { case 'SetupCommonFilesBoilerplate': { // complex setup files which require some setting up: + const { fullyPublicContract } = node; const files = collectImportFiles( [ `import './common/write-vk.mjs'`, `import './common/zkp-setup.mjs'`, `import './common/migrations/deploy.js'`, + `import './common/web3.mjs'`, + `import './common/contract.mjs'`, + `import './common/timber.mjs'`, + `import './common/number-theory.mjs'`, + `import './common/mongo.mjs'`, + `import './common/hash-lookup.mjs'`, ].join('\n'), 'orchestration', + fullyPublicContract, ); - - let readPath = path.resolve(fileURLToPath(import.meta.url), '../../../../../src/boilerplate/common/bin/setup'); + let readPath = fullyPublicContract + ? path.resolve( + fileURLToPath(import.meta.url), + '../../../../../src/boilerplate/common/bin/setup-public', + ) + : path.resolve( + fileURLToPath(import.meta.url), + '../../../../../src/boilerplate/common/bin/setup', + ); const setupScript = { filepath: 'bin/setup', file: fs.readFileSync(readPath, 'utf8') }; files.push(setupScript); readPath = path.resolve(fileURLToPath(import.meta.url), '../../../../../src/boilerplate/common/bin/startup'); diff --git a/src/index.ts b/src/index.ts index 48854e39..edb36ee3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -23,13 +23,15 @@ const zappify = (options: any) => { let path = checks(zolAST); - path = ownership(path, options); + const result = ownership(path, options); + path = result.updatedASTPath; + const { isContractPublic, contractName } = result; options.circuitAST = toCircuit(zolAST, options); toOrchestration(path, options); - toContract(zolAST, options); + toContract(zolAST, options, isContractPublic, contractName); return zolAST; }; diff --git a/src/transformers/ownership.ts b/src/transformers/ownership.ts index 110c4de8..18affeae 100644 --- a/src/transformers/ownership.ts +++ b/src/transformers/ownership.ts @@ -13,6 +13,8 @@ function transformation1(ast: any, options?: any) { stopTraversal: false, skipSubNodes: false, options, + isContractPublic: undefined, + contractName: undefined, }; // We'll start by calling the traverser function with our ast and a visitor. @@ -20,16 +22,21 @@ function transformation1(ast: any, options?: any) { ast.traverse(explode(ownershipVisitor), state); logger.verbose('Performing final error checks on the zol AST...'); ast.traverse(explode(errorChecksVisitor), state); + const { isContractPublic, contractName } = state; // At the end of our transformer function we'll return the new ast that we // just created. - return ast; + return { ast, isContractPublic, contractName }; } // A transformer function which will accept an ast. export default function ownership(astPath: any, options?: any) { logger.verbose('Performing ownership checks on the zol AST...'); - const updatedASTPath = transformation1(astPath, options); + const { + ast: updatedASTPath, + isContractPublic, + contractName, + } = transformation1(astPath, options); logger.verbose('Owners assigned.'); - return updatedASTPath; + return { updatedASTPath, isContractPublic, contractName }; } diff --git a/src/transformers/toContract.ts b/src/transformers/toContract.ts index f73c4bbe..e3337650 100644 --- a/src/transformers/toContract.ts +++ b/src/transformers/toContract.ts @@ -8,7 +8,28 @@ import codeGenerator from '../codeGenerators/contract/solidity/toContract.js'; import { transformation1 } from './visitors/common.js'; // A transformer function which will accept an ast. -export default function toContract(ast: object, options: any) { +export default function toContract( + ast: object, + options: any, + isContractPublic: boolean, + contractName: string, +) { + if (isContractPublic) { + // If the contract is fully public, we copy the original contract into the shield contract + const shieldFileName = `${contractName}Shield.sol`; + const shieldContractFilePath = `${options.contractsDirPath}/${shieldFileName}`; + fs.copyFileSync(options.inputFilePath, shieldContractFilePath); + + // Replace all instances of the contract name with contractNameShield + let fileContent = fs.readFileSync(shieldContractFilePath, 'utf8'); + // Use word boundaries to replace the contract name everywhere it appears + const contractNameRegex = new RegExp(`\\b${contractName}\\b`, 'g'); + fileContent = fileContent.replace(contractNameRegex, `${contractName}Shield`); + fs.writeFileSync(shieldContractFilePath, fileContent); + + return; + } + // transpile to a contract AST: const state = { stopTraversal: false, diff --git a/src/transformers/visitors/checks/interactsWithSecretVisitor.ts b/src/transformers/visitors/checks/interactsWithSecretVisitor.ts index d223686b..8f12556a 100644 --- a/src/transformers/visitors/checks/interactsWithSecretVisitor.ts +++ b/src/transformers/visitors/checks/interactsWithSecretVisitor.ts @@ -63,7 +63,7 @@ export default { exit(path: NodePath) { const expressionPath = path.getAncestorOfType('ExpressionStatement') || path.parentPath; - if (path.isExternalFunctionCall()) { + if (path.isExternalFunctionCall() || path.isInternalFunctionCall()) { path.markContainsPublic(); expressionPath.traversePathsFast(markSubtreeInteractsWithPublic, { publicPath: path, diff --git a/src/transformers/visitors/checks/unsupportedVisitor.ts b/src/transformers/visitors/checks/unsupportedVisitor.ts index da1897b2..5273e816 100644 --- a/src/transformers/visitors/checks/unsupportedVisitor.ts +++ b/src/transformers/visitors/checks/unsupportedVisitor.ts @@ -59,15 +59,6 @@ export default { }, }, - WhileStatement: { - enter(node: any) { - throw new ZKPError( - 'While statements are unsupported in zero-knowledge proof circuits because they cannot handle dynamic loops.', - node, - ); - }, - }, - IfStatement: { enter(node: any) { if (['Identifier', 'Literal'].includes(node.condition.nodeType)) diff --git a/src/transformers/visitors/orchestrationInternalFunctionCallVisitor.ts b/src/transformers/visitors/orchestrationInternalFunctionCallVisitor.ts index e512a8de..3c778d5b 100644 --- a/src/transformers/visitors/orchestrationInternalFunctionCallVisitor.ts +++ b/src/transformers/visitors/orchestrationInternalFunctionCallVisitor.ts @@ -572,6 +572,14 @@ const internalCallVisitor = { }) }, }, +FunctionDefinition: { + enter(path: NodePath, state: any) { + const { node, parent, scope } = path; + if (!scope.modifiesSecretState()) { + state.skipSubNodes = true; + } + }, +}, FunctionCall: { enter(path: NodePath, state: any) { const { node, parent, scope } = path; diff --git a/src/transformers/visitors/ownership/errorChecksVisitor.ts b/src/transformers/visitors/ownership/errorChecksVisitor.ts index 3e5d7769..00811c18 100644 --- a/src/transformers/visitors/ownership/errorChecksVisitor.ts +++ b/src/transformers/visitors/ownership/errorChecksVisitor.ts @@ -181,7 +181,7 @@ export default { exit(path: NodePath, state: any) { const { scope } = path; if (path.node.stateMutability === 'pure'){ - throw new TODOError(`We currently do not support pure functions.`, path.node); + state.containsPureFunction = true; } if (path.node.stateMutability === 'view' && path.node.body.containsSecret){ throw new TODOError(`We currently do not support view functions that involve secret variables.`, path.node); @@ -220,13 +220,12 @@ export default { // bindings are contract scope level, so we track global states here const { scope } = path; - if (state.isContractPublic) { - throw new Error( - 'The contract is fully public and does not use secret variables, making it incompatible with the transpiler. ' + - 'Ensure your contract manipulates secret variables to generate a valid ZApp.', - ); + if (!state.isContractPublic && state.containsPureFunction) { + throw new TODOError(`We currently do not support pure functions.`, path.node); } + state.contractName = path.node.name; + for (const [, binding] of Object.entries(scope.bindings)) { if (!(binding instanceof VariableBinding)) continue; binding.prelimTraversalErrorChecks(); diff --git a/src/transformers/visitors/toCircuitVisitor.ts b/src/transformers/visitors/toCircuitVisitor.ts index b1cbfe47..667ce2c6 100644 --- a/src/transformers/visitors/toCircuitVisitor.ts +++ b/src/transformers/visitors/toCircuitVisitor.ts @@ -1492,10 +1492,8 @@ const visitor = { parent._newASTPointer[path.containerName] = newNode; return; } - if (path.isExternalFunctionCall() || path.isExportedSymbol()) { // External function calls are the fiddliest of things, because they must be retained in the Solidity contract, rather than brought into the circuit. With this in mind, it's easiest (from the pov of writing this transpiler) if External function calls appear at the very start or very end of a function. If they appear interspersed around the middle, we'd either need multiple circuits per Zolidity function, or we'd need a set of circuit parameters (non-secret params / return-params) per external function call, and both options are too painful for now. - // ignore external function calls; they'll be retained in Solidity, so won't be copied over to a circuit. state.skipSubNodes = true; } diff --git a/src/transformers/visitors/toContractVisitor.ts b/src/transformers/visitors/toContractVisitor.ts index 129d63c5..a3d895d1 100644 --- a/src/transformers/visitors/toContractVisitor.ts +++ b/src/transformers/visitors/toContractVisitor.ts @@ -9,6 +9,7 @@ import { VariableBinding } from '../../traverse/Binding.js'; import { ContractDefinitionIndicator,FunctionDefinitionIndicator } from '../../traverse/Indicator.js'; import { interactsWithSecretVisitor, parentnewASTPointer, internalFunctionCallVisitor } from './common.js'; import { param } from 'express/lib/request.js'; +import { exit } from 'process'; // here we find any public state variables which interact with secret states // and hence need to be included in the verification calculation @@ -162,23 +163,32 @@ export default { }, exit(path: NodePath, state: any) { - const { node, parent, scope } = path; - const sourceUnitNodes = parent._newASTPointer[0].nodes; - const contractNodes = node._newASTPointer; - let parameterList: any = {}; - let functionName: string; - let returnParameterList: any = {}; - let returnfunctionName: string; - for ([functionName, parameterList] of Object.entries(state.circuitParams)) { - if(state.returnpara){ - for ([returnfunctionName, returnParameterList] of Object.entries(state.returnpara)){ - if(functionName === returnfunctionName ){ - parameterList = parameterList && returnParameterList ? {... parameterList, ... returnParameterList} : parameterList; - state.circuitParams[ functionName ] = parameterList; - } + const { node, parent, scope } = path; + const sourceUnitNodes = parent._newASTPointer[0].nodes; + const contractNodes = node._newASTPointer; + let parameterList: any = {}; + let functionName: string; + let returnParameterList: any = {}; + let returnfunctionName: string; + if (state.circuitParams) { + for ([functionName, parameterList] of Object.entries( + state.circuitParams, + )) { + if (state.returnpara) { + for ([returnfunctionName, returnParameterList] of Object.entries( + state.returnpara, + )) { + if (functionName === returnfunctionName) { + parameterList = + parameterList && returnParameterList + ? { ...parameterList, ...returnParameterList } + : parameterList; + state.circuitParams[functionName] = parameterList; } } } + } + } const contractIndex = sourceUnitNodes.findIndex( @@ -371,42 +381,49 @@ export default { ParameterList: { enter(path: NodePath, state: any) { const { node, parent, scope } = path; - let returnName : string[] =[]; - if(path.key === 'parameters'){ - const newNode = buildNode('ParameterList'); - node._newASTPointer = newNode.parameters; - parent._newASTPointer[path.containerName] = newNode; - } else if(path.key === 'returnParameters'){ - parent.body.statements.forEach(node => { - if(node.nodeType === 'Return'){ - if(node.expression.nodeType === 'TupleExpression'){ - node.expression.components.forEach(component => { - if(component.name){ - returnName?.push(component.name); + const returnName: string[] = []; + if (path.key === 'parameters') { + const newNode = buildNode('ParameterList'); + node._newASTPointer = newNode.parameters; + parent._newASTPointer[path.containerName] = newNode; + } else if (path.key === 'returnParameters') { + parent.body.statements.forEach(node => { + if (node.nodeType === 'Return') { + if (node.expression.nodeType === 'TupleExpression') { + node.expression.components.forEach(component => { + if (component.name) { + returnName?.push(component.name); + } else returnName?.push(component.value); + }); + } else if (node.expression.nodeType === 'IndexAccess') { + if (node.expression.baseExpression.nodeType === 'IndexAccess') { + returnName?.push( + `${node.expression.baseExpression.baseExpression.name}[${node.expression.baseExpression.indexExpression.name}][${node.expression.indexExpression.name}]`, + ); + } else { + returnName?.push( + `${node.expression.baseExpression.name}[${node.expression.indexExpression.name}]`, + ); + } + } else if (node.expression.name) { + returnName?.push(node.expression.name); + } else { + returnName?.push(node.expression.value); } - else - returnName?.push(component.value); - }); - } else{ - if(node.expression.name) - returnName?.push(node.expression.name); - else - returnName?.push(node.expression.value); - } - } - }); + } + }); + node.parameters.forEach((node, index) => { + if (node.nodeType === 'VariableDeclaration') { + node.name = returnName[index]; + } + }); - node.parameters.forEach((node, index) => { - if(node.nodeType === 'VariableDeclaration'){ - node.name = returnName[index]; + const newNode = buildNode('ParameterList'); + node._newASTPointer = newNode.parameters; + parent._newASTPointer[path.containerName] = newNode; } - }); + }, - const newNode = buildNode('ParameterList'); - node._newASTPointer = newNode.parameters; - parent._newASTPointer[path.containerName] = newNode; - } - }, exit(path: NodePath, state: any){ const { node, parent, scope } = path; if(path.key === 'returnParameters'){ @@ -474,14 +491,19 @@ export default { state.skipSubNodes=true; return; } - const newNode = buildNode(node.nodeType, { - condition: node.condition, - trueBody: node.trueBody, - falseBody: node.falseBody - }); + const newNode = buildNode(node.nodeType, {}); node._newASTPointer = newNode; parentnewASTPointer(parent, path, newNode, parent._newASTPointer[path.containerName]); }, + + exit(path: NodePath) { + const { node, parent } = path; + node._newASTPointer.condition = node.condition._newASTPointer; + node._newASTPointer.trueBody = node.trueBody._newASTPointer; + node._newASTPointer.falseBody = node.falseBody + ? node.falseBody._newASTPointer + : {}; + }, }, ForStatement: { @@ -552,11 +574,20 @@ export default { }, }, + Conditional: { + enter(path: NodePath) { + const { node, parent } = path; + const newNode = buildNode('Conditional', {}); + node._newASTPointer = newNode; + parentnewASTPointer(parent, path, newNode , parent._newASTPointer[path.containerName]); + }, + }, + TupleExpression: { enter(path: NodePath) { const { node, parent } = path; const newNode = buildNode(node.nodeType); - node._newASTPointer = newNode.components; + node._newASTPointer = newNode; parent._newASTPointer[path.containerName] = newNode; }, }, @@ -781,8 +812,16 @@ DoWhileStatement: { enter(path: NodePath) { const { node, parent } = path; const { name } = node; - - const newNode = buildNode('Identifier', { name }); + const contractName = + path.getAncestorOfType('ContractDefinition')?.node.name; + let newNode; + if ( + node.typeDescriptions.typeString === `type(contract ${contractName})` + ) { + newNode = buildNode('Identifier', { name: `${name}Shield` }); + } else { + newNode = buildNode('Identifier', { name }); + } // node._newASTPointer = // no pointer needed, because this is a leaf, so we won't be recursing any further. parentnewASTPointer(parent, path, newNode , parent._newASTPointer[path.containerName]); @@ -853,7 +892,7 @@ DoWhileStatement: { const { node, parent, scope } = path; let newNode: any; // If this node is a require statement, it might include arguments which themselves are expressions which need to be traversed. So rather than build a corresponding 'assert' node upon entry, we'll first traverse into the arguments, build their nodes, and then upon _exit_ build the assert node. - if (path.isRequireStatement() || path.isRevertStatement() || (node.expression.memberName && node.expression.memberName === 'push')) { + if (path.isRequireStatement() || path.isRevertStatement() || (node.expression.memberName && node.expression.memberName === 'push') || node.expression.nodeType === 'MemberAccess') { // If the 'require' statement contains secret state variables, we'll presume the circuit will perform that logic, so we'll do nothing in the contract. const subState = { interactsWithSecret: false }; path.traversePathsFast(interactsWithSecretVisitor, subState); @@ -875,11 +914,11 @@ DoWhileStatement: { // Like External function calls ,it's easiest (from the pov of writing this transpiler) if Event calls appear at the very start or very end of a function. // TODO: need a warning message to this effect ^^^ if (parent.nodeType === 'EmitStatement') { - newNode = buildNode('FunctionCall'); - node._newASTPointer = newNode; - parentnewASTPointer(parent, path, newNode , parent._newASTPointer[path.containerName]); - return; - } + newNode = buildNode('FunctionCall'); + node._newASTPointer = newNode; + parentnewASTPointer(parent, path, newNode , parent._newASTPointer[path.containerName]); + return; + } if (path.isExternalFunctionCall()) { // External function calls are the fiddliest of things, because they must be retained in the Solidity contract, rather than brought into the circuit. With this in mind, it's easiest (from the pov of writing this transpiler) if External function calls appear at the very start or very end of a function. If they appear interspersed around the middle, we'd either need multiple circuits per Zolidity function, or we'd need a set of circuit parameters (non-secret params / return-params) per external function call, and both options are too painful for now. @@ -950,6 +989,7 @@ DoWhileStatement: { }); node._newASTPointer = newNode; parentnewASTPointer(parent, path, newNode , parent._newASTPointer[path.containerName]); + state.skipSubNodes = true; return; } @@ -957,18 +997,29 @@ DoWhileStatement: { node._newASTPointer = newNode; parentnewASTPointer(parent, path, newNode , parent._newASTPointer[path.containerName]); } - if (node.kind !== 'typeConversion') { - newNode = buildNode('FunctionCall'); + if (node.kind === 'typeConversion') { + newNode = buildNode('TypeConversion', { + type: node.typeDescriptions.typeString, + }); + node._newASTPointer = newNode; + parentnewASTPointer(parent, path, newNode , parent._newASTPointer[path.containerName]); + return; + } + if (node.expression.nodeType === 'NewExpression') { + newNode = buildNode('NewExpression', { + typeName: node.expression.typeName.name, + arguments: node.arguments, + argumentTypes: node.expression.argumentTypes, + }); node._newASTPointer = newNode; parentnewASTPointer(parent, path, newNode , parent._newASTPointer[path.containerName]); state.skipSubNodes = true; return; } - newNode = buildNode('TypeConversion', { - type: node.typeDescriptions.typeString, - }); - node._newASTPointer = newNode; - parentnewASTPointer(parent, path, newNode , parent._newASTPointer[path.containerName]); + newNode = buildNode('FunctionCall'); + node._newASTPointer = newNode; + parentnewASTPointer(parent, path, newNode , parent._newASTPointer[path.containerName]); + state.skipSubNodes = true; }, }, } diff --git a/src/transformers/visitors/toOrchestrationVisitor.ts b/src/transformers/visitors/toOrchestrationVisitor.ts index 12a44985..686ae5ab 100644 --- a/src/transformers/visitors/toOrchestrationVisitor.ts +++ b/src/transformers/visitors/toOrchestrationVisitor.ts @@ -372,6 +372,7 @@ const visitor = { const { node, parent, scope } = path; node._newASTPointer = parent._newASTPointer; const contractName = `${node.name}Shield`; + state.fullyPublicContract = true; if (scope.indicators.zkSnarkVerificationRequired) { const newNode = buildNode('File', { fileName: 'test', @@ -468,6 +469,9 @@ const visitor = { file.nodes[0].constructorParams = state.constructorParams; file.nodes[0].contractImports = state.contractImports; } + if (file.nodeType === 'SetupCommonFilesBoilerplate') { + file.fullyPublicContract = state.fullyPublicContract; + } } // Internal Call Visitor path.traverse(explode(internalCallVisitor), state); @@ -490,6 +494,7 @@ const visitor = { enter(path: NodePath, state: any) { const { node, parent, scope } = path; if (scope.modifiesSecretState()) { + state.fullyPublicContract = false; const contractName = `${parent.name}Shield`; const fnName = path.getUniqueFunctionName(); node.fileName = fnName; @@ -575,10 +580,9 @@ const visitor = { ); } } - } else { state.skipSubNodes = true; - } + } if (node.kind === 'constructor') { state.constructorParams ??= []; for (const param of node.parameters.parameters) { @@ -592,7 +596,6 @@ const visitor = { ); } } - }, exit(path: NodePath, state: any) { @@ -1345,9 +1348,13 @@ const visitor = { }, Block: { - enter(path: NodePath) { + enter(path: NodePath, state: any) { const { node, parent } = path; - + const fnDefPath = path.getAncestorOfType('FunctionDefinition'); + if (fnDefPath && !fnDefPath.scope.modifiesSecretState()) { + state.skipSubNodes = true; + return; + } // ts complains if I don't include a number in this list if (['trueBody', 'falseBody', 99999999].includes(path.containerName)) { node._newASTPointer = parent._newASTPointer[path.containerName]; @@ -1513,7 +1520,7 @@ const visitor = { if (node.expression.nodeType === 'Assignment' || node.expression.nodeType === 'UnaryOperation') { let { leftHandSide: lhs } = node.expression; if (!lhs) lhs = node.expression.subExpression; - indicator = scope.getReferencedIndicator(lhs, true); + indicator = scope.getReferencedIndicator(lhs, true); let name = indicator.isMapping ? indicator.name diff --git a/src/traverse/Binding.ts b/src/traverse/Binding.ts index 536c3c56..3e3f554c 100644 --- a/src/traverse/Binding.ts +++ b/src/traverse/Binding.ts @@ -63,6 +63,8 @@ export class Binding { case 'DoWhileStatement': case 'EnumDefinition': case 'NewExpression': + case 'InheritanceSpecifier': + case 'IdentifierPath': return false; default: logger.error(`Hitherto unknown nodeType '${nodeType}'`); diff --git a/src/traverse/NodePath.ts b/src/traverse/NodePath.ts index 7471e665..c549bd79 100644 --- a/src/traverse/NodePath.ts +++ b/src/traverse/NodePath.ts @@ -655,7 +655,8 @@ export default class NodePath { isExternalContractInstance(node: any = this.node): boolean { const varDecNode = this.getReferencedNode(node); - return this.isExternalContractInstanceDeclaration(varDecNode); + if (varDecNode) return this.isExternalContractInstanceDeclaration(varDecNode); + return this.isExternalContractInstanceDeclaration(); } isExternalFunctionCall(): boolean { diff --git a/src/traverse/Scope.ts b/src/traverse/Scope.ts index 385ba6bc..90022b25 100644 --- a/src/traverse/Scope.ts +++ b/src/traverse/Scope.ts @@ -237,8 +237,11 @@ export class Scope { referencedIndicator.update(path); } - - if (!referencedNode.stateVariable && referencedNode.nodeType !== 'FunctionDefinition') { + if ( + !referencedNode.stateVariable && + referencedNode.nodeType !== 'FunctionDefinition' && + functionDefScope.indicators[referencedId] + ) { functionDefScope.indicators[referencedId].update(path); } @@ -290,6 +293,8 @@ export class Scope { case 'DoWhileStatement': case 'EnumDefinition': case 'NewExpression': + case 'InheritanceSpecifier': + case 'IdentifierPath': break; // And again, if we haven't recognized the nodeType then we'll throw an diff --git a/src/types/solidity-types.ts b/src/types/solidity-types.ts index 22d3951c..aa738b14 100644 --- a/src/types/solidity-types.ts +++ b/src/types/solidity-types.ts @@ -74,6 +74,7 @@ export function getVisitableKeys(nodeType: string): string[] { case 'MsgSender': case 'EnumDefinition': case 'NewExpression': + case 'IdentifierPath': return []; // And again, if we haven't recognized the nodeType then we'll throw an @@ -301,6 +302,19 @@ export function buildNode(nodeType: string, fields: any = {}): any { }), }); } + case 'NewExpression': { + const { + typeName = {}, + arguments: args = {}, + argumentTypes = {}, + } = fields; + return { + nodeType, + typeName, + arguments: args, + argumentTypes, + }; + } case 'VariableDeclarationStatement': { const { declarations = [], initialValue = {} } = fields; return {