Skip to content
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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 = <your_mnemonic_here>
RINKEBY_URL = 'https://rinkeby.infura.io/v3/59160a1ec5014a329992ab35736ce757'
```

## Setup Instructions:

1. Install Ganache locally `brew install --cask ganache`.
Expand Down
4 changes: 2 additions & 2 deletions contracts/AnswerContract.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
4 changes: 2 additions & 2 deletions contracts/QuestionContract.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
5 changes: 2 additions & 3 deletions migrations/1_deploy_contract.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
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]});
};
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
8 changes: 8 additions & 0 deletions server/blockchain/classes/AbstractContractClass.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
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) {
this.contract = new web3.eth.Contract(abi, address);
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];
}
}
Expand Down
18 changes: 15 additions & 3 deletions server/blockchain/classes/AnswerContractClass.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
30 changes: 26 additions & 4 deletions server/blockchain/classes/QuestionContractClass.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 });
Expand Down
13 changes: 12 additions & 1 deletion server/blockchain/classes/RootQuestionContractClass.js
Original file line number Diff line number Diff line change
@@ -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();
}
Expand Down
15 changes: 14 additions & 1 deletion server/config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
const Web3 = require("web3");
var HDWalletProvider = require("truffle-hdwallet-provider");

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 };
106 changes: 23 additions & 83 deletions test/RootQuestionContract.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
// });
// });
Loading