diff --git a/README.md b/README.md index 88ac943..2013046 100644 --- a/README.md +++ b/README.md @@ -60,4 +60,5 @@ From the root folder of the project: | Contracts | UI | CLI | Snapshot :----------:|:------------:|:--------:|:--------:| - v1.0.0 | v1.1.0 | v0.1.1 | v1.2.0 \ No newline at end of file + v1.0.0 | v1.1.0 | v0.1.1 | v1.2.0 | + v1.0.2 | v1.1.0 | v0.1.2 | v1.2.0 | \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 5cdd09d..3c6fe24 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,7 @@ "ethereumjs-util": "7.1.0", "ethers": "5.4.5", "inquirer": "^8.1.2", - "tribute-contracts": "^1.0.1", + "tribute-contracts": "^1.0.2", "truffle": "5.4.6", "web3": "^1.4.0" }, @@ -25434,9 +25434,9 @@ "optional": true }, "node_modules/tribute-contracts": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tribute-contracts/-/tribute-contracts-1.0.1.tgz", - "integrity": "sha512-wJLaIx7vmQVAN3DsjSITTZIi1RjsiGDXJVfQ4/EtongJ9LyCzrAh9xMGasGbFAbXdrHgkgRvbR0U7mQaWkZiag==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tribute-contracts/-/tribute-contracts-1.0.2.tgz", + "integrity": "sha512-u3FXj1Z91/gFOx6bIcGuKG1dQVfJNw3usOUYQtFKGiVl671OEbLnRbvtEFEaTn9BPp1wkbOUmSKLJNOUZqUuGw==", "dependencies": { "@openzeppelin/contracts": "^4.0.0", "@truffle/contract": "^4.3.13", @@ -49090,9 +49090,9 @@ "optional": true }, "tribute-contracts": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tribute-contracts/-/tribute-contracts-1.0.1.tgz", - "integrity": "sha512-wJLaIx7vmQVAN3DsjSITTZIi1RjsiGDXJVfQ4/EtongJ9LyCzrAh9xMGasGbFAbXdrHgkgRvbR0U7mQaWkZiag==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tribute-contracts/-/tribute-contracts-1.0.2.tgz", + "integrity": "sha512-u3FXj1Z91/gFOx6bIcGuKG1dQVfJNw3usOUYQtFKGiVl671OEbLnRbvtEFEaTn9BPp1wkbOUmSKLJNOUZqUuGw==", "requires": { "@openzeppelin/contracts": "^4.0.0", "@truffle/contract": "^4.3.13", diff --git a/package.json b/package.json index b3cd0d7..86576dc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tribute-contracts-cli", - "version": "0.1.1", + "version": "0.1.2", "description": "A command line interface tool to interact with @openlaw/tribute-contracts.", "main": "cli.js", "bin": { @@ -25,7 +25,7 @@ "ethereumjs-util": "7.1.0", "ethers": "5.4.5", "inquirer": "^8.1.2", - "tribute-contracts": "^1.0.1", + "tribute-contracts": "^1.0.2", "truffle": "5.4.6", "web3": "^1.4.0" }, diff --git a/src/contracts/adapters/managing-adapter.js b/src/contracts/adapters/managing-adapter.js index 5ede2a7..fd2f6c7 100644 --- a/src/contracts/adapters/managing-adapter.js +++ b/src/contracts/adapters/managing-adapter.js @@ -5,16 +5,22 @@ const { configs } = require("../../../cli-config"); const { sha3 } = require("tribute-contracts/utils/ContractUtil"); const { prepareVoteProposalData } = require("@openlaw/snapshot-js-erc712"); -const { entryDao } = require("tribute-contracts/utils/DeploymentUtil"); +const { + entryDao, + entryBank, +} = require("tribute-contracts/utils/DeploymentUtil"); const { getContract } = require("../../utils/contract"); const { submitSnapshotProposal } = require("../../services/snapshot-service"); -const { parseDaoFlags } = require("../core/dao-registry"); +const { parseDaoFlags, getExtensionAddress } = require("../core/dao-registry"); +const { parseBankFlags } = require("../extensions/bank-extension"); const { warn } = require("../../utils/logging"); const submitManagingProposal = async ( + updateType, adapterName, adapterAddress, - aclFlags, + daoAclFlags, + extensions, keys, values, data, @@ -22,13 +28,39 @@ const submitManagingProposal = async ( ) => { const configKeys = keys ? keys.split(",").map((k) => toBytes32(k)) : []; const configValues = values ? values.split(",").map((v) => v) : []; - const configAclFlags = parseDaoFlags(aclFlags); + const configAclFlags = parseDaoFlags(daoAclFlags); const { contract, provider, wallet } = getContract( "ManagingContract", configs.contracts.ManagingContract ); + let extensionAddresses = []; + let extensionAclFlags = []; + if (extensions && extensions.length > 0) { + for (let i in extensions) { + const ext = extensions[i]; + extensionAddresses.push(await getExtensionAddress(ext.id)); + switch (ext.id) { + case "bank": + // Convert the acl flag to the interger flag value + extensionAclFlags.push( + entryBank({ address: undefined }, parseBankFlags(ext.selectedFlags)) + .flags + ); + break; + default: + throw Error(`ACL flag not supported for extension: ${ext.name}`); + } + } + } + + const daoFlags = entryDao( + adapterName, + { address: adapterAddress }, + configAclFlags + ).flags; + return await submitSnapshotProposal( `Adapter: ${adapterName}`, "Creates/Update adapter", @@ -63,16 +95,15 @@ const submitManagingProposal = async ( configs.contracts.DaoRegistry, daoProposalId, { - adapterId: sha3(adapterName), - adapterAddress: adapterAddress, - flags: entryDao( - adapterName, - { address: adapterAddress }, - configAclFlags - ).flags, + adapterOrExtensionId: sha3(adapterName), + adapterOrExtensionAddr: adapterAddress, + updateType: updateType, + flags: daoFlags, + keys: configKeys, + values: configValues, + extensionAddresses: extensionAddresses, + extensionAclFlags: extensionAclFlags, }, - configKeys, - configValues, encodedData ? encodedData : ethers.utils.toUtf8Bytes(""), { from: wallet.address } ); diff --git a/src/contracts/core/dao-registry.js b/src/contracts/core/dao-registry.js index 313a287..bdc37d9 100644 --- a/src/contracts/core/dao-registry.js +++ b/src/contracts/core/dao-registry.js @@ -31,9 +31,17 @@ const getAdapterAddress = async (adapterId) => { "DaoRegistry", configs.contracts.DaoRegistry ); - return await contract.getAdapterAddress(adapterId); + return await contract.getAdapterAddress(sha3(adapterId)); } +const getExtensionAddress = async (extensionId) => { + const { contract } = getContract( + "DaoRegistry", + configs.contracts.DaoRegistry + ); + return await contract.getExtensionAddress(sha3(extensionId)); +}; + // TODO import from "tribute-contracts/utils/DeploymentUtil" v2.0.3 const daoAccessFlags = [ "REPLACE_ADAPTER", @@ -63,4 +71,5 @@ module.exports = { getAddressIfDelegated, getMemberAddress, getAdapterAddress, + getExtensionAddress, }; diff --git a/src/contracts/extensions/bank-extension.js b/src/contracts/extensions/bank-extension.js index 584e1a1..01dbbf8 100644 --- a/src/contracts/extensions/bank-extension.js +++ b/src/contracts/extensions/bank-extension.js @@ -1,6 +1,30 @@ const { getContract } = require("../../utils/contract"); const { configs } = require("../../../cli-config"); +// TODO import from "tribute-contracts/utils/DeploymentUtil" v2.x +const bankAclFlags = [ + "ADD_TO_BALANCE", + "SUB_FROM_BALANCE", + "INTERNAL_TRANSFER", + "WITHDRAW", + "EXECUTE", + "REGISTER_NEW_TOKEN", + "REGISTER_NEW_INTERNAL_TOKEN", + "UPDATE_TOKEN", +]; + +// TODO import from "tribute-contracts/utils/DeploymentUtil" v2.x +const parseBankFlags = (aclFlags) => { + return aclFlags + .map((f) => f.toUpperCase()) + .reduce((flags, flag) => { + if (bankAclFlags.includes(flag)) { + return { ...flags, [flag]: true }; + } + throw Error(`Invalid Bank Access Flag: ${flag}`); + }, {}); +}; + const getBalanceOf = async (memberAddress, tokenAddr) => { const { contract } = getContract( "BankExtension", @@ -17,4 +41,4 @@ const getPriorAmount = async (account, tokenAddr, blockNumber) => { return await contract.getPriorAmount(account, tokenAddr, blockNumber); }; -module.exports = { getBalanceOf, getPriorAmount }; +module.exports = { bankAclFlags, getBalanceOf, getPriorAmount, parseBankFlags }; diff --git a/src/interfaces/cli/commands/dao-registry-cmd.js b/src/interfaces/cli/commands/dao-registry-cmd.js index 6d70b61..cdea766 100644 --- a/src/interfaces/cli/commands/dao-registry-cmd.js +++ b/src/interfaces/cli/commands/dao-registry-cmd.js @@ -18,7 +18,7 @@ const daoRegistryCommands = (program) => { logEnvConfigs(configs); info(`AdapterId:\t\t${adapterId}`); - return getAdapterAddress(sha3(adapterId)) + return getAdapterAddress(adapterId) .then((data) => { success(`Adapter Address: \t${data}\n`); }) diff --git a/src/interfaces/cli/commands/managing-cmd.js b/src/interfaces/cli/commands/managing-cmd.js index 3f139ab..f7b5376 100644 --- a/src/interfaces/cli/commands/managing-cmd.js +++ b/src/interfaces/cli/commands/managing-cmd.js @@ -4,6 +4,9 @@ const { submitManagingProposal, processManagingProposal, } = require("../../../contracts/adapters/managing-adapter"); +const { + bankAclFlags, +} = require("../../../contracts/extensions/bank-extension"); const { configs } = require("../../../../cli-config"); const { daoAccessFlags } = require("../../../contracts/core/dao-registry"); @@ -23,34 +26,102 @@ const managingCommands = (program) => { ) .description("Submit a new managing proposal.") .action(async (adapterName, adapterAddress, keys, values, data) => { - await inquirer - .prompt([ - { - type: "checkbox", - message: "Select the ACL Flags or hit ENTER to skip", - name: "aclFlags", - choices: daoAccessFlags.map((f) => Object.assign({ name: f })), - }, - ]) - .then((anwsers) => { - notice(`\n ::: Submitting Managing proposal...\n`); - logEnvConfigs(configs, configs.contracts.ManagingContract); - info(`Adapter:\t\t${adapterName} @ ${adapterAddress}`); - info(`AccessFlags:\t\t${JSON.stringify(anwsers.aclFlags)}`); - info(`Keys:\t\t\t${keys ? keys : "n/a"}`); - info(`Values:\t\t\t${values ? values : "n/a"}`); - info(`Data:\t\t\t${data ? data : "n/a"}\n`); + const { updateType } = await inquirer.prompt([ + { + type: "list", + message: "Which type of contract are you going to update?", + name: "updateType", + choices: [ + { + name: "Adapter", + value: 1, + description: "If you want to Add/Remove/Update adapters", + }, + { + name: "Extension", + value: 2, + description: "If want to Add/Remove/Update extensions", + }, + ], + }, + ]); - return submitManagingProposal( - adapterName, - adapterAddress, - anwsers.aclFlags, - keys, - values, - data, - program.opts() - ); - }) + const { daoAclFlags } = await inquirer.prompt([ + { + type: "checkbox", + message: "Select the **DAO** ACL Flags or hit ENTER to skip", + name: "daoAclFlags", + choices: daoAccessFlags.map((f) => Object.assign({ name: f })), + }, + ]); + + const allExtensions = [ + { + name: "Bank", + id: "bank", + aclFlags: bankAclFlags, + selectedFlags: [], + }, + ]; + + const { requiredExtensions } = await inquirer.prompt([ + { + type: "checkbox", + message: "Does the adapter needs access to any of these extensions?", + name: "requiredExtensions", + choices: allExtensions, + }, + ]); + + const extensions = await requiredExtensions + .flatMap((name) => allExtensions.filter((ext) => ext.name === name)) + .reduce(async (res, extension) => { + const { flags } = await inquirer.prompt([ + { + type: "checkbox", + message: `Select the **${extension.name}** ACL Flags or hit ENTER to skip`, + name: "flags", + choices: extension.aclFlags.map((f) => + Object.assign({ name: f }) + ), + }, + ]); + if (flags && flags.length > 0) { + res.push({ ...extension, selectedFlags: flags }); + } + return res; + }, []); + + notice(`\n ::: Submitting Managing proposal...\n`); + logEnvConfigs(configs, configs.contracts.ManagingContract); + info(`Adapter:\t\t${adapterName} @ ${adapterAddress}`); + info(`AccessFlags:\t\t${JSON.stringify(daoAclFlags)}`); + info(`Keys:\t\t\t${keys ? keys : "n/a"}`); + info(`Values:\t\t\t${values ? values : "n/a"}`); + info(`Data:\t\t\t${data ? data : "n/a"}`); + info( + `Extensions:\t\t${ + extensions + ? JSON.stringify( + extensions.map((e) => + Object.assign({ [e.name]: e.selectedFlags }) + ) + ) + : "n/a" + }\n` + ); + + return submitManagingProposal( + updateType, + adapterName, + adapterAddress, + daoAclFlags, + extensions, + keys, + values, + data, + program.opts() + ) .then((data) => { success(`New Snapshot Proposal Id: ${data.snapshotProposalId}\n`); notice(`::: Managing proposal submitted!\n`);