From 4c8468849509c9e1828e9726c88de215f0148b75 Mon Sep 17 00:00:00 2001 From: tommyrharper Date: Sat, 7 Aug 2021 18:55:59 +0100 Subject: [PATCH 1/8] Stop automatic upvoting in the smart contract --- contracts/AnswerContract.sol | 4 ++-- contracts/QuestionContract.sol | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/contracts/AnswerContract.sol b/contracts/AnswerContract.sol index cefa2a8c..cad28986 100644 --- a/contracts/AnswerContract.sol +++ b/contracts/AnswerContract.sol @@ -14,10 +14,10 @@ contract AnswerContract { } constructor(uint _id) { _answer.id = _id; - _answer.upVotes = 1; + _answer.upVotes = 0; _answer.downVotes = 0; _answer.answerer = tx.origin; - upVoters[tx.origin] = true; + // upVoters[tx.origin] = true; manager = tx.origin; } diff --git a/contracts/QuestionContract.sol b/contracts/QuestionContract.sol index 9f65e50b..b60b83e9 100644 --- a/contracts/QuestionContract.sol +++ b/contracts/QuestionContract.sol @@ -22,10 +22,10 @@ contract QuestionContract { constructor(uint _id) { _question.id = _id; - _question.upVotes = 1; + _question.upVotes = 0; _question.downVotes = 0; _question.asker = tx.origin; - upVoters[tx.origin] = true; + // upVoters[tx.origin] = true; manager = tx.origin; } From 7b28be3f13b81a17170688abd381b672b1bd7aff Mon Sep 17 00:00:00 2001 From: tommyrharper Date: Sat, 7 Aug 2021 19:49:47 +0100 Subject: [PATCH 2/8] Install truffle-hdwallet-provider --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index a4ea3e63..1423ca77 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "pg": "^8.7.1", "react": "^17.0.2", "react-dom": "^17.0.2", + "truffle-hdwallet-provider": "^1.0.17", "web3": "1" }, "devDependencies": { From bbbb1a08c7a92b087d713e96097acdfb99ea3212 Mon Sep 17 00:00:00 2001 From: tommyrharper Date: Sat, 7 Aug 2021 19:50:03 +0100 Subject: [PATCH 3/8] Setup truffle-config with infura --- truffle-config.js | 58 ++++++++++++++++------------------------------- 1 file changed, 20 insertions(+), 38 deletions(-) diff --git a/truffle-config.js b/truffle-config.js index 135fa894..def62d51 100644 --- a/truffle-config.js +++ b/truffle-config.js @@ -22,6 +22,8 @@ // // const fs = require('fs'); // const mnemonic = fs.readFileSync(".secret").toString().trim(); +var HDWalletProvider = require("truffle-hdwallet-provider"); +var mnemonic = process.env.MNEMONIC; module.exports = { /** @@ -35,42 +37,22 @@ module.exports = { */ networks: { - // Useful for testing. The `development` name is special - truffle uses it by default - // if it's defined here and no other network is specified at the command line. - // You should run a client (like ganache-cli, geth or parity) in a separate terminal - // tab if you use this network and you must also set the `host`, `port` and `network_id` - // options below to some value. - // development: { - host: "127.0.0.1", // Localhost (default: none) - port: 7545, // Standard Ethereum port (default: none) - network_id: "*", // Any network (default: none) + host: "127.0.0.1", + port: 8545, + network_id: "*", + }, + rinkeby: { + provider: function () { + return new HDWalletProvider( + mnemonic, + process.env.RINKEBY_URL + ); + }, + network_id: 4, + gas: 4500000, + gasPrice: 10000000000, }, - // Another network with more advanced options... - // advanced: { - // port: 8777, // Custom port - // network_id: 1342, // Custom network - // gas: 8500000, // Gas sent with each transaction (default: ~6700000) - // gasPrice: 20000000000, // 20 gwei (in wei) (default: 100 gwei) - // from:
, // Account to send txs from (default: accounts[0]) - // websocket: true // Enable EventEmitter interface for web3 (default: false) - // }, - // Useful for deploying to a public network. - // NB: It's important to wrap the provider as a function. - // ropsten: { - // provider: () => new HDWalletProvider(mnemonic, `https://ropsten.infura.io/v3/YOUR-PROJECT-ID`), - // network_id: 3, // Ropsten's id - // gas: 5500000, // Ropsten has a lower block limit than mainnet - // confirmations: 2, // # of confs to wait between deployments. (default: 0) - // timeoutBlocks: 200, // # of blocks before a deployment times out (minimum/default: 50) - // skipDryRun: true // Skip dry run before migrations? (default: false for public nets ) - // }, - // Useful for private networks - // private: { - // provider: () => new HDWalletProvider(mnemonic, `https://network.io`), - // network_id: 2111, // This network is yours, in the cloud. - // production: true // Treats this network as if it was a public net. (default: false) - // } }, // Set default mocha options here, use special reporters etc. @@ -81,7 +63,7 @@ module.exports = { // Configure your compilers compilers: { solc: { - version: "^0.8.4", // Fetch exact version from solc-bin (default: truffle's version) + version: "^0.8.4", // Fetch exact version from solc-bin (default: truffle's version) // docker: true, // Use "0.5.1" you've installed locally with docker (default: false) // settings: { // See the solidity docs for advice about optimization and evmVersion // optimizer: { @@ -90,7 +72,7 @@ module.exports = { // }, // evmVersion: "byzantium" // } - } + }, }, // Truffle DB is currently disabled by default; to enable it, change enabled: false to enabled: true @@ -100,7 +82,7 @@ module.exports = { // $ truffle migrate --reset --compile-all db: { - enabled: false + enabled: false, }, - plugins: ["truffle-contract-size"] + plugins: ["truffle-contract-size"], }; From 151f5b5f1d95d8696e55bb1830f7f380295b77cc Mon Sep 17 00:00:00 2001 From: tommyrharper Date: Sat, 7 Aug 2021 19:50:13 +0100 Subject: [PATCH 4/8] Update the deploy contract migration to use infura --- migrations/1_deploy_contract.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/migrations/1_deploy_contract.js b/migrations/1_deploy_contract.js index 9f9a7168..cb7ecc10 100644 --- a/migrations/1_deploy_contract.js +++ b/migrations/1_deploy_contract.js @@ -1,6 +1,6 @@ const RootQuestionsContract = artifacts.require("RootQuestionsContract"); // const AnswerContract = artifacts.require("QuestionAnswer"); -module.exports = function (deployer) { - deployer.deploy(RootQuestionsContract); +module.exports = function (deployer, network, accounts) { + deployer.deploy(RootQuestionsContract, {from: accounts[0]}); }; From 511979ca5b6bf3ab9442a3f894f1ec5adf8a08c8 Mon Sep 17 00:00:00 2001 From: tommyrharper Date: Sat, 7 Aug 2021 19:50:29 +0100 Subject: [PATCH 5/8] Update server config --- migrations/1_deploy_contract.js | 1 - server/config.js | 5 ++++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/migrations/1_deploy_contract.js b/migrations/1_deploy_contract.js index cb7ecc10..9f9f51c3 100644 --- a/migrations/1_deploy_contract.js +++ b/migrations/1_deploy_contract.js @@ -1,5 +1,4 @@ const RootQuestionsContract = artifacts.require("RootQuestionsContract"); -// const AnswerContract = artifacts.require("QuestionAnswer"); module.exports = function (deployer, network, accounts) { deployer.deploy(RootQuestionsContract, {from: accounts[0]}); diff --git a/server/config.js b/server/config.js index af650267..fef8bb44 100644 --- a/server/config.js +++ b/server/config.js @@ -1,5 +1,8 @@ const Web3 = require("web3"); +var HDWalletProvider = require("truffle-hdwallet-provider"); -const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:9545")); +const provider = new HDWalletProvider(process.env.MNEMONIC, process.env.RINKEBY_URL) + +const web3 = new Web3(provider); module.exports = { web3, DEFAULT_GAS: 2000000 }; From 5b5f9d639e0d477d4d1feb473c6ca9fab29465f5 Mon Sep 17 00:00:00 2001 From: tommyrharper Date: Sat, 7 Aug 2021 21:08:09 +0100 Subject: [PATCH 6/8] Add logs to the AbstractContractClass --- server/blockchain/classes/AbstractContractClass.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/server/blockchain/classes/AbstractContractClass.js b/server/blockchain/classes/AbstractContractClass.js index b743f0b8..3b215631 100644 --- a/server/blockchain/classes/AbstractContractClass.js +++ b/server/blockchain/classes/AbstractContractClass.js @@ -6,9 +6,11 @@ class AbstractContractClass { this.accounts = web3.eth.getAccounts(); this.accountIndex = accountIndex; this.gas = gas; + this.getAccount() } async getAccount() { + console.log(`await this.accounts`, await this.accounts); return (await this.accounts)[this.accountIndex]; } } From 0e4773c192d60465b5c5329cc7f5991e2dfe18f2 Mon Sep 17 00:00:00 2001 From: julia Date: Sat, 7 Aug 2021 13:13:51 -0700 Subject: [PATCH 7/8] commenting for blockchain functions and hookups --- .../classes/AbstractContractClass.js | 6 + .../blockchain/classes/AnswerContractClass.js | 18 ++- .../classes/QuestionContractClass.js | 30 ++++- .../classes/RootQuestionContractClass.js | 13 ++- server/config.js | 14 ++- test/RootQuestionContract.test.js | 106 ++++-------------- 6 files changed, 95 insertions(+), 92 deletions(-) diff --git a/server/blockchain/classes/AbstractContractClass.js b/server/blockchain/classes/AbstractContractClass.js index b743f0b8..6fa0fe7d 100644 --- a/server/blockchain/classes/AbstractContractClass.js +++ b/server/blockchain/classes/AbstractContractClass.js @@ -1,4 +1,10 @@ const { web3, DEFAULT_GAS } = require("../../config"); +/** + * @dev + * The AbstractContractClass defines the contract (using the abi, and + * deployed contract address), the user account to make transactions on + * the blockchain, and gas in the contructor. + */ class AbstractContractClass { constructor(abi, address, accountIndex = 0, gas = DEFAULT_GAS) { diff --git a/server/blockchain/classes/AnswerContractClass.js b/server/blockchain/classes/AnswerContractClass.js index a5049cd4..fe6b9383 100644 --- a/server/blockchain/classes/AnswerContractClass.js +++ b/server/blockchain/classes/AnswerContractClass.js @@ -5,17 +5,29 @@ class AnswerContract extends AbstractContractClass { constructor(address, accountIndex, gas) { super(abi, address, accountIndex, gas); } - + /** + * @dev + * Calls the getAnswerVotes fn on the deployed contract, + * retrieving how many votes a specific answer has + */ async getAnswerVotes() { return await this.contract.methods.getAnswerVotes().call(); } - + /** + * @dev + * Calls the upVoteAnswer fn on the deployed contract, + * upvoting a specific answer + */ async upVoteAnswer() { return await this.contract.methods .upVoteAnswer() .send({ from: await this.getAccount(), gas: this.gas }); } - + /** + * @dev + * Calls the downVoteAnswer fn on the deployed contract, + * downvoting a specific answer + */ async downVoteAnswer() { return await this.contract.methods .downVoteAnswer() diff --git a/server/blockchain/classes/QuestionContractClass.js b/server/blockchain/classes/QuestionContractClass.js index 618b70e2..b4f12031 100644 --- a/server/blockchain/classes/QuestionContractClass.js +++ b/server/blockchain/classes/QuestionContractClass.js @@ -6,26 +6,48 @@ class QuestionContract extends AbstractContractClass { super(abi, address, accountIndex, gas); } + /** + * @dev + * Calls the uoVoteQuestion fn on the deployed contract + */ async upVoteQuestion() { return await this.contract.methods .upVoteQuestion() .send({ from: await this.getAccount(), gas: this.gas }); } - + /** + * @dev + * Calls the getQuestionVotes fn on the deployed contract, + * retreiving both the up and down votes on a specific Question contract + */ async getQuestionVotes() { return await this.contract.methods.getQuestionVotes().call(); } - + /** + * @dev + * Calls the downVoteQuestion fn on the deployed contract + */ async downVoteQuestion() { return await this.contract.methods .downVoteQuestion() .send({ from: await this.getAccount(), gas: this.gas }); } - + /** + * @dev + * Calls the getAnswers fn on the deployed contract, + * retrieving all instances of deployed Answer contract addresses + * for a specific question + */ async getAnswers() { return await this.contract.methods.getAnswers().call(); } - + /** + * @dev + * Calls the addAnswer fn on the deployed contract, + * passing in an _id retrieved when adding an answer to the database. + * This will deploy and instance of the Answer contract, based off + * a specific question + */ async addAnswer(_id) { return await this.contract.methods.addAnswer(_id) .send({ from: await this.getAccount(), gas: this.gas }); diff --git a/server/blockchain/classes/RootQuestionContractClass.js b/server/blockchain/classes/RootQuestionContractClass.js index 6f4e7804..5478d940 100644 --- a/server/blockchain/classes/RootQuestionContractClass.js +++ b/server/blockchain/classes/RootQuestionContractClass.js @@ -1,17 +1,28 @@ const { abi } = require("../../../build/contracts/RootQuestionsContract.json"); const AbstractContractClass = require("./AbstractContractClass"); + class RootQuestionsContract extends AbstractContractClass { constructor(address, accountIndex, gas) { super(abi, address, accountIndex, gas); } + /** + * @dev + * Calls the addQuestion fn on the deployed contract, + * passing in the questionId retrieved from inserting question + * data into the db + */ async addQuestion(questionId) { return await this.contract.methods .addQuestion(questionId) .send({ from: await this.getAccount(), gas: this.gas }); } - + /** + * @dev + * Calls the getQuestions fn on the deployed contract, + * retrieving an array of all the previous Question contract addresses + */ async getQuestions() { return await this.contract.methods.getQuestions().call(); } diff --git a/server/config.js b/server/config.js index af650267..9044e1d0 100644 --- a/server/config.js +++ b/server/config.js @@ -1,5 +1,17 @@ const Web3 = require("web3"); -const web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:9545")); + /** + * @dev Get the web3 provider for hooking the app up to a blockchain instance: + * + * If using truffle: + * assign url to "http://localhost:9545"; + * + * If using rinkeby or another testnet: + * assign url to the given string from infura + * e.g. "https://mainnet.infura.io/v3/2a67aae8464e4d74a1d9a4339fd9c0a9" + */ + +const url = "http://localhost:9545"; +const web3 = new Web3(new Web3.providers.HttpProvider(url)); module.exports = { web3, DEFAULT_GAS: 2000000 }; diff --git a/test/RootQuestionContract.test.js b/test/RootQuestionContract.test.js index 9d6331cf..585655e2 100644 --- a/test/RootQuestionContract.test.js +++ b/test/RootQuestionContract.test.js @@ -11,94 +11,34 @@ let accounts; let rootContract; let questionContractAddress; -beforeEach(async () => { - accounts = await web3.eth.getAccounts(); +// beforeEach(async () => { +// accounts = await web3.eth.getAccounts(); - rootContract = await new web3.eth.Contract(JSON.parse(compiledRoot.interface)) - .deploy({ data: compiledRoot.bytecode }) - .send({ from: accounts[0], gas: '1000000' }); +// rootContract = await new web3.eth.Contract(JSON.parse(compiledRoot.interface)) +// .deploy({ data: compiledRoot.bytecode }) +// .send({ from: accounts[0], gas: '1000000' }); - await rootContract.methods.addQuestion(1).send({ - from: accounts[0], - gas: '1000000' - }); - - [questionContractAddress] = await rootContract.methods.getQuestions().call(); - question = await new web3.eth.Contract( - JSON.parse(compiledQuestion.interface), - questionContractAddress - ); -}); - -describe('RootFactory', () => { - it('deploys a factory and a question contract', () => { - // assert.ok(rootContract.options.address); - assert.ok(question.options.address); - }); - - it('marks caller as the campaign manager', async () => { - const manager = await rootContract.methods.manager().call(); - assert.strictEqual(accounts[0], manager); - }); -}); - -// it('allows people to contribute money and marks them as approvers', async () => { -// await campaign.methods.contribute().send({ -// value: '200', -// from: accounts[1] -// }); -// const isContributor = await campaign.methods.approvers(accounts[1]).call(); -// assert(isContributor); +// await rootContract.methods.addQuestion(1).send({ +// from: accounts[0], +// gas: '1000000' // }); -// it('requires a minimum contribution', async () => { -// try { -// await campaign.methods.contribute().send({ -// value: '5', -// from: accounts[1] -// }); -// assert(false); -// } catch (err) { -// assert(err); -// } +// [questionContractAddress] = await rootContract.methods.getQuestions().call(); +// question = await new web3.eth.Contract( +// JSON.parse(compiledQuestion.interface), +// questionContractAddress +// ); +// }); + +// describe('RootFactory', () => { +// it('deploys a factory and a question contract', () => { +// // assert.ok(rootContract.options.address); +// assert.ok(question.options.address); // }); -// it('allows a manager to make a payment request', async () => { -// await campaign.methods -// .createRequest('Buy batteries', '100', accounts[1]) -// .send({ -// from: accounts[0], -// gas: '1000000' -// }); -// const request = await campaign.methods.requests(0).call(); - -// assert.equal('Buy batteries', request.description); +// it('marks caller as the campaign manager', async () => { +// const manager = await rootContract.methods.manager().call(); +// assert.strictEqual(accounts[0], manager); // }); +// }); -// it('processes requests', async () => { -// await campaign.methods.contribute().send({ -// from: accounts[0], -// value: web3.utils.toWei('10', 'ether') -// }); - -// await campaign.methods -// .createRequest('A', web3.utils.toWei('5', 'ether'), accounts[1]) -// .send({ from: accounts[0], gas: '1000000' }); - -// await campaign.methods.approveRequest(0).send({ -// from: accounts[0], -// gas: '1000000' -// }); - -// await campaign.methods.finalizeRequest(0).send({ -// from: accounts[0], -// gas: '1000000' -// }); - -// let balance = await web3.eth.getBalance(accounts[1]); -// balance = web3.utils.fromWei(balance, 'ether'); -// balance = parseFloat(balance); - -// assert(balance > 104); -// }); -// }); \ No newline at end of file From 6d9f0c5cb7e240fb172182887f1625cb4a5766d1 Mon Sep 17 00:00:00 2001 From: tommyrharper Date: Sat, 7 Aug 2021 21:15:45 +0100 Subject: [PATCH 8/8] Add Connect to the shared testnet instructions --- README.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/README.md b/README.md index c7a18a7b..3ae6d511 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # ProjectQ - [ProjectQ](#projectq) + - [Connect to the shared testnet](#connect-to-the-shared-testnet) - [Setup Instructions:](#setup-instructions) - [Database setup](#database-setup) - [Database trouble shooting](#database-trouble-shooting) @@ -13,6 +14,20 @@ Qoalition is a decentralised blockchain community for curious minds! With Qoalition, you can earn while you learn! Ask and answer questions on Qoaltion and get paid in crypto currency. +## Connect to the shared testnet + +- Make sure you have run `npm i` first. + +1. Install the google chrome extension metamask. +2. Get ethereum sent to your account via faucet. +3. Get your mnemonic from metamask. +4. Add the following properties to your `.env`: (you must use this contract address and url) +``` +CONTRACT_ADDRESS = '0x1e2f81ab6a47B7343B7a3bcFf9f4F32FC0010938' +MNEMONIC = +RINKEBY_URL = 'https://rinkeby.infura.io/v3/59160a1ec5014a329992ab35736ce757' +``` + ## Setup Instructions: 1. Install Ganache locally `brew install --cask ganache`.