From f091a72f61d53591837732f1204ebd33633f36d8 Mon Sep 17 00:00:00 2001 From: Barry Gitarts Date: Thu, 14 Mar 2019 18:11:16 -0400 Subject: [PATCH] add delegates model and syncing strategy --- app/actions/delegates.js | 75 +++++++++++++++++++ app/actions/pledges.js | 28 ++++--- app/components/PledgesTable.jsx | 2 +- app/components/TransferDialog.jsx | 6 +- .../dashboard/PledgeAllocationsChart.jsx | 4 +- app/components/projects/BackProject.jsx | 8 +- app/components/projects/hooks.js | 4 + app/components/table/TransferCard.jsx | 6 +- app/components/table/WithdrawCard.jsx | 6 +- app/components/table/hooks.js | 8 +- app/dapp.js | 2 + app/db.js | 4 +- app/model/delegate.js | 16 ++++ app/model/pledge.js | 7 +- app/model/schema.js | 16 +++- app/selectors/pledging.js | 2 +- app/utils/pledges.js | 16 +++- config/storage.js | 62 +++++++++++++++ 18 files changed, 233 insertions(+), 39 deletions(-) create mode 100644 app/actions/delegates.js create mode 100644 app/model/delegate.js create mode 100644 config/storage.js diff --git a/app/actions/delegates.js b/app/actions/delegates.js new file mode 100644 index 0000000..4f8445b --- /dev/null +++ b/app/actions/delegates.js @@ -0,0 +1,75 @@ +import database from '../db' +import { Q } from '@nozbe/watermelondb' +import { when } from 'ramda' +import { getPledgesWithDelegates } from './pledges' +import { getProfilesById } from './profiles' + +const delegatesCollection = database.collections.get('delegates') +export const getDelegateProfiles = async pledges => { + const ids = [] + pledges.forEach(pledge => { + const { delegates } = pledge + delegates.forEach(d => ids.push(d.idDelegate)) + }) + return getProfilesById(ids) +} + +const createDelegate = (newDelegate, delegateInfo, pledge, profile, idx) => { + newDelegate.profile.set(profile) + newDelegate.pledge.set(pledge) + newDelegate.idPledge = pledge.idPledge + newDelegate.delegateIndex = idx +} + +const delegateRecordExists = (profile, pledge, idx, existing) => { + const record = existing.find(delegate => { + const { delegateIndex } = delegate + if ( + profile.id == delegate.profile.id && + pledge.idPledge == delegate.idPledge && + idx == delegateIndex + ) return true + return false + }) + return record +} + +const batchAddDelegates = async (pledges, profiles, existing) => { + const batch = [] + pledges.forEach(pledge => { + const { delegates } = pledge + delegates.forEach((delegateInfo, idx) => { + const profile = profiles.find(p => p.idProfile == delegateInfo.idDelegate) + const exists = delegateRecordExists(profile, pledge, idx+1, existing) + if (!exists) { + batch.push( + delegatesCollection.prepareCreate( + newDelegate => createDelegate(newDelegate, delegateInfo, pledge, profile, idx+1) + ) + ) + } + }) + }) + return database.action(async () => await database.batch(...batch)) +} + +export const updateDelegates = async () => { + const pledges = await getPledgesWithDelegates() + const profiles = await getDelegateProfiles(pledges) + const delegates = await getExistingDelegates() + batchAddDelegates(pledges, profiles, delegates) +} + +export const getExistingDelegates = async () => { + const delegates = await delegatesCollection.query().fetch() + return delegates +} + +export const delegateExists = async (profileId, idPledge, idx) => { + const delegates = await delegatesCollection.query( + Q.where('profile_id', profileId), + Q.where('id_pledge', idPledge), + Q.where('delegate_index', idx) + ).fetch() + return delegates +} diff --git a/app/actions/pledges.js b/app/actions/pledges.js index 8f5fbdd..61f933d 100644 --- a/app/actions/pledges.js +++ b/app/actions/pledges.js @@ -5,9 +5,9 @@ import { getPledges, getAllPledges } from '../utils/pledges' import { getProfilesById } from './profiles' const createPledge = (pledge, data, profiles) => { - const { id, owner, amount, blockNumber, token, commitTime, nDelegates, pledgeState, intendedProject } = data + const { id, owner, amount, blockNumber, token, commitTime, nDelegates, pledgeState, intendedProject, delegates } = data const profile = profiles.find(p => p.idProfile == owner) - pledge.pledgeId = Number(id) + pledge.idPledge = Number(id) pledge.owner = Number(owner) pledge.amount = amount pledge.token = token @@ -16,6 +16,7 @@ const createPledge = (pledge, data, profiles) => { pledge.pledgeState = Number(pledgeState) pledge.intendedProject = Number(intendedProject) pledge.blockNumber = Number(blockNumber) + pledge.delegates = delegates pledge.profile.set(profile) } @@ -36,17 +37,17 @@ export const batchAddPledges = async (pledges, profiles = []) => { } const getLastPledge = pledges => { - const pledgeId = pledges.length - ? pledges.sort((a,b) => b.pledgeId - a.pledgeId)[0].pledgeId + const idPledge = pledges.length + ? pledges.sort((a,b) => b.idPledge - a.idPledge)[0].idPledge : 0 - return pledgeId + return idPledge } export const getAndAddPledges = async () => { const pledges = await getLocalPledges() - const pledgeId = getLastPledge(pledges) - const newPledges = await getAllPledges(pledgeId + 1) - const pledgeIds = newPledges.map(p => p.owner) - const profiles = await getProfilesById(pledgeIds) + const idPledge = getLastPledge(pledges) + const newPledges = await getAllPledges(idPledge + 1) + const idPledges = newPledges.map(p => p.owner) + const profiles = await getProfilesById(idPledges) batchAddPledges(newPledges, profiles) } @@ -55,7 +56,7 @@ export const updateStalePledges = async () => { const stalePledges = await getStalePledges() const updatedPledges = await getPledges(stalePledges) const batch = stalePledges.map(p => { - const updated = updatedPledges[p.pledgeId - 1] + const updated = updatedPledges[p.idPledge - 1] return p.prepareUpdate(p => { const { amount, nDelegates, pledgeState, blockNumber } = updated p.amount = amount @@ -87,3 +88,10 @@ export const getPledgeById = async id => { ).fetch() return event } + +export const getPledgesWithDelegates = async () => { + const event = await pledgesCollection.query( + Q.where('n_delegates', Q.gt(0)) + ).fetch() + return event +} diff --git a/app/components/PledgesTable.jsx b/app/components/PledgesTable.jsx index 7ebb331..ad97600 100644 --- a/app/components/PledgesTable.jsx +++ b/app/components/PledgesTable.jsx @@ -76,7 +76,7 @@ class PledgesTable extends Component {
( { - const { pledgeId, pledge } = row + const { idPledge, pledge } = row const { idSender, amount, idReceiver } = values - const args = [idSender, pledgeId, toWei(amount.toString()), idReceiver] + const args = [idSender, idPledge, toWei(amount.toString()), idReceiver] const toSend = transfer(...args) const estimatedGas = await toSend.estimateGas() @@ -68,7 +68,7 @@ const TransferDialog = ({ row, handleClose }) => ( Transfer Funds - {`Transfer ${values.amount || ''} ${values.amount && row ? getTokenLabel(row.pledge.token) : ''} from Pledge ${row.pledgeId} ${values.idReceiver ? 'to Giver/Delegate/Project' : ''} ${values.idReceiver || ''}`} + {`Transfer ${values.amount || ''} ${values.amount && row ? getTokenLabel(row.pledge.token) : ''} from Pledge ${row.idPledge} ${values.idReceiver ? 'to Giver/Delegate/Project' : ''} ${values.idReceiver || ''}`} { const labels = [] const backgroundColor = [] pledges.forEach((pledge, idx) => { - const { pledgeId, amount, token } = pledge + const { idPledge, amount, token } = pledge const converted = toEther(amount) data.push(converted) labels.push( - `pledge ${pledgeId} - ${getTokenLabel(token)}` + `pledge ${idPledge} - ${getTokenLabel(token)}` ) backgroundColor.push(getColor('Dark2-8', idx)) }) diff --git a/app/components/projects/BackProject.jsx b/app/components/projects/BackProject.jsx index 0483037..379949a 100644 --- a/app/components/projects/BackProject.jsx +++ b/app/components/projects/BackProject.jsx @@ -1,5 +1,4 @@ import React from 'react' -import web3 from 'Embark/web3' import { Formik } from 'formik' import withObservables from '@nozbe/with-observables' import { Q } from '@nozbe/watermelondb' @@ -89,10 +88,10 @@ const SubmissionSection = ({ classes, profiles }) => ( ) -function BackProject({classes, match, profile, projectAddedEvents}) { +function BackProject({classes, match, profile, projectAddedEvents, delegateAddedEvents}) { const projectId = match.params.id const { projectAge, projectAssets, manifest, delegateProfiles } = useProjectData(projectId, profile, projectAddedEvents) - console.log({delegateProfiles}) + console.log({delegateAddedEvents}) return (
@@ -108,5 +107,8 @@ export default withDatabase(withObservables([], ({ database, match }) => ({ ).observe(), projectAddedEvents: database.collections.get('lp_events').query( Q.where('event', 'ProjectAdded') + ).observe(), + delegateAddedEvents: database.collections.get('lp_events').query( + Q.where('event', 'DelegateAdded') ).observe() }))(StyledProject)) diff --git a/app/components/projects/hooks.js b/app/components/projects/hooks.js index 5fc4238..40cd0ad 100644 --- a/app/components/projects/hooks.js +++ b/app/components/projects/hooks.js @@ -8,6 +8,8 @@ import { databaseExists } from '../../utils/db' import { FundingContext } from '../../context' import { getDelegateProfiles } from '../../actions/profiles' +console.log({LiquidPledging}) + async function getProjectAge(id, events, setState){ const event = events.find(e => e.returnValues.idProject === id) const { timestamp } = await web3.eth.getBlock(event.blockNumber) @@ -17,6 +19,8 @@ async function getProjectAge(id, events, setState){ async function getProjectAssets(projectId, setState){ EmbarkJS.onReady(async (err) => { const projectInfo = await LiquidPledging.methods.getPledgeAdmin(projectId).call() + const pledgeInfo = await LiquidPledging.methods.getPledgeDelegate(5).call() + console.log({pledgeInfo}) const CID = projectInfo.url.split('/').slice(-1)[0] console.log({CID, projectInfo, ipfs}) getFiles(CID) diff --git a/app/components/table/TransferCard.jsx b/app/components/table/TransferCard.jsx index 5fac265..a1c8f53 100644 --- a/app/components/table/TransferCard.jsx +++ b/app/components/table/TransferCard.jsx @@ -23,9 +23,9 @@ function TransferCard({ row, handleClose, classes }) { <Formik initialValues={{}} onSubmit={async (values, { setSubmitting, resetForm, setStatus }) => { - const { pledgeId, pledge } = row + const { idPledge, pledge } = row const { idSender, amount, idReceiver } = values - const args = [idSender, pledgeId, toWei(amount.toString()), idReceiver] + const args = [idSender, idPledge, toWei(amount.toString()), idReceiver] const toSend = transfer(...args) const estimatedGas = await toSend.estimateGas() @@ -71,7 +71,7 @@ function TransferCard({ row, handleClose, classes }) { <CardContent> <Typography variant="h6" component="h2">Transfer Funds</Typography> <Typography variant="subheading"> - {`Transfer ${values.amount || ''} ${values.amount && row ? getTokenLabel(row.pledge.token) : ''} from Pledge ${row.pledgeId} ${values.idReceiver ? 'to Giver/Delegate/Project' : ''} ${values.idReceiver || ''}`} + {`Transfer ${values.amount || ''} ${values.amount && row ? getTokenLabel(row.pledge.token) : ''} from Pledge ${row.idPledge} ${values.idReceiver ? 'to Giver/Delegate/Project' : ''} ${values.idReceiver || ''}`} </Typography> <TextField autoFocus diff --git a/app/components/table/WithdrawCard.jsx b/app/components/table/WithdrawCard.jsx index e7efd8b..a2ef611 100644 --- a/app/components/table/WithdrawCard.jsx +++ b/app/components/table/WithdrawCard.jsx @@ -30,7 +30,7 @@ function Withdraw({ handleClose, classes, rowData, authorizedPayment }) { initialValues={{}} onSubmit={async (values, { setSubmitting, resetForm, setStatus }) => { const { amount } = values - const paymentId = isPaying ? authorizedPayment[0]['returnValues']['idPayment'] : rowData.pledgeId + const paymentId = isPaying ? authorizedPayment[0]['returnValues']['idPayment'] : rowData.idPledge const args = isPaying ? [paymentId] : [paymentId, toWei(amount)] const sendFn = isPaying ? confirmPayment : withdraw try { @@ -68,7 +68,7 @@ function Withdraw({ handleClose, classes, rowData, authorizedPayment }) { <Card className={classes.card} elevation={0}> <CardContent> <Typography variant="h6" component="h2"> - {`${isPaying ? 'Confirm' : ''} Withdraw${isPaying ? 'al' : ''} ${values.amount || ''} ${values.amount ? getTokenLabel(rowData.pledge.token) : ''} from Pledge ${rowData.pledgeId}`} + {`${isPaying ? 'Confirm' : ''} Withdraw${isPaying ? 'al' : ''} ${values.amount || ''} ${values.amount ? getTokenLabel(rowData.pledge.token) : ''} from Pledge ${rowData.idPledge}`} </Typography> {!isPaying && <TextField className={classes.amount} @@ -105,6 +105,6 @@ Withdraw.propTypes = { const styledWithdraw = withStyles(styles)(Withdraw) export default withDatabase(withObservables(['rowData'], ({ database, rowData }) => ({ authorizedPayment : database.collections.get('vault_events').query( - Q.where('ref', rowData.pledgeId) + Q.where('ref', rowData.idPledge) ).observe() }))(styledWithdraw)) diff --git a/app/components/table/hooks.js b/app/components/table/hooks.js index fe81110..961ad7a 100644 --- a/app/components/table/hooks.js +++ b/app/components/table/hooks.js @@ -2,17 +2,17 @@ import { useState, useEffect } from 'react' export function useRowData(rowData, handleClose) { const [show, setShow] = useState(null) - const [rowId, setRowId] = useState(rowData.pledgeId) + const [rowId, setRowId] = useState(rowData.idPledge) useEffect(() => { setShow(true) }, []) useEffect(() => { - const { pledgeId } = rowData - const samePledge = rowId === pledgeId + const { idPledge } = rowData + const samePledge = rowId === idPledge if (show && samePledge) close() - else setRowId(pledgeId) + else setRowId(idPledge) }, [rowData.timeStamp]) const close = () => { diff --git a/app/dapp.js b/app/dapp.js index 30d0cdb..06ae46f 100644 --- a/app/dapp.js +++ b/app/dapp.js @@ -11,6 +11,7 @@ import { getAndAddLpEvents } from './actions/lpEvents' import { getAndAddVaultEvents } from './actions/vaultEvents' import { addFormattedProfiles } from './actions/profiles' import { updateStalePledges, getAndAddPledges } from './actions/pledges' +import { updateDelegates } from './actions/delegates' const { getNetworkType } = web3.eth.net @@ -57,6 +58,7 @@ class App extends React.Component { await addFormattedProfiles() await getAndAddPledges() await updateStalePledges() + await updateDelegates() this.setState({ loading: false }) } diff --git a/app/db.js b/app/db.js index 9ef5cd6..73039d6 100644 --- a/app/db.js +++ b/app/db.js @@ -6,6 +6,7 @@ import LpEvent from './model/lpEvents' import VaultEvent from './model/vaultEvent' import Profile from './model/profile' import Pledge from './model/pledge' +import Delegate from './model/delegate' const dbName = 'LiquidFunding' const adapter = new LokiJSAdapter({ @@ -19,7 +20,8 @@ const database = new Database({ LpEvent, VaultEvent, Profile, - Pledge + Pledge, + Delegate ], actionsEnabled: true, }) diff --git a/app/model/delegate.js b/app/model/delegate.js new file mode 100644 index 0000000..d271bbb --- /dev/null +++ b/app/model/delegate.js @@ -0,0 +1,16 @@ +import { field, relation } from '@nozbe/watermelondb/decorators' +import { LiquidModel } from '../utils/models' + + +export default class Delegate extends LiquidModel { + static table = 'delegates' + static associations = { + profiles:{ type: 'belongs_to', key: 'profile_id' }, + pledges: { type: 'belongs_to', key: 'pledge_id' } + } + + @field('delegate_index') delegateIndex + @field('id_pledge') idPledge + @relation('profiles', 'profile_id') profile + @relation('pledges', 'pledge_id') pledge +} diff --git a/app/model/pledge.js b/app/model/pledge.js index 74d558a..e22a3e6 100644 --- a/app/model/pledge.js +++ b/app/model/pledge.js @@ -1,15 +1,15 @@ -import { action, field, relation } from '@nozbe/watermelondb/decorators' +import { action, field, relation, json } from '@nozbe/watermelondb/decorators' import { Q } from '@nozbe/watermelondb' import { LiquidModel } from '../utils/models' - +const sanitizeValues = json => json export default class Pledge extends LiquidModel { static table = 'pledges' static associations = { profiles: { type: 'belongs_to', key: 'profile_id' }, } - @field('pledge_id') pledgeId + @field('id_pledge') idPledge @field('owner_id') owner @field('amount') amount @field('token') token @@ -19,6 +19,7 @@ export default class Pledge extends LiquidModel { @field('pledge_state') pledgeState @field('block_number') blockNumber @relation('profiles', 'profile_id') profile + @json('delegates', sanitizeValues) delegates @action async transferTo(to, amount) { const toPledgeQuery = await this.collections.get('pledges').query( diff --git a/app/model/schema.js b/app/model/schema.js index 34333b1..c9d821e 100644 --- a/app/model/schema.js +++ b/app/model/schema.js @@ -41,16 +41,26 @@ export default appSchema({ tableSchema({ name: 'pledges', columns: [ - { name: 'pledge_id', type: 'number', isIndexed: true }, + { name: 'id_pledge', type: 'number', isIndexed: true }, { name: 'owner_id', type: 'number', isIndexed: true }, { name: 'amount', type: 'string' }, { name: 'token', type: 'string' }, { name: 'commit_time', type: 'number' }, - { name: 'n_delegates', type: 'number' }, + { name: 'n_delegates', type: 'number', isIndexed: true }, { name: 'intended_project', type: 'number' }, { name: 'pledge_state', type: 'number' }, { name: 'profile_id', type: 'string', isIndexed: true }, - { name: 'block_number', type: 'number', isIndexed: true } + { name: 'block_number', type: 'number', isIndexed: true }, + { name : 'delegates', type: 'string', isOptional: true } + ] + }), + tableSchema({ + name: 'delegates', + columns: [ + { name: 'profile_id', type: 'string', isIndexed: true }, + { name: 'pledge_id', type: 'string', isIndexed: true }, + { name: 'id_pledge', type: 'number', isIndexed: true }, + { name: 'delegate_index', type: 'number', isIndexed: true } ] }) ] diff --git a/app/selectors/pledging.js b/app/selectors/pledging.js index 17af697..cfa5dd8 100644 --- a/app/selectors/pledging.js +++ b/app/selectors/pledging.js @@ -45,7 +45,7 @@ const formatAndSumDepositWithdraws = (deposits, pledges, withdraws) => { let incomplete = false deposits.forEach(deposit => { const { amount, to } = deposit.returnValues - const pledge = pledges.find(p => Number(p.pledgeId) === Number(to)) + const pledge = pledges.find(p => Number(p.idPledge) === Number(to)) if (!pledge) { incomplete = true return diff --git a/app/utils/pledges.js b/app/utils/pledges.js index 2bd1873..bdf26d4 100644 --- a/app/utils/pledges.js +++ b/app/utils/pledges.js @@ -1,13 +1,25 @@ import web3 from 'Embark/web3' import LiquidPledging from 'Embark/contracts/LiquidPledging' -const { getPledgeAdmin, numberOfPledges, getPledge } = LiquidPledging.methods +const { getPledgeDelegate, numberOfPledges, getPledge } = LiquidPledging.methods +const getPledgeDelegates = (idPledge, numDelegates) => { + const delegates = [] + const num = Number(numDelegates) + if (!num) return delegates + for (let i = 1; i <= num; i++) { + delegates.push(getPledgeDelegate(idPledge, i).call()) + } + return Promise.all(delegates) +} + export const formatPledge = async (pledgePromise, idx) => { const pledge = await pledgePromise const blockNumber = await web3.eth.getBlockNumber() + const delegates = await getPledgeDelegates(idx+1, pledge.nDelegates) return { ...pledge, blockNumber, + delegates, id: idx + 1 } } @@ -24,7 +36,7 @@ export const getAllPledges = async (start = 1) => { export const getPledges = async (pledges = []) => { const updated = [] pledges.forEach(p => { - updated[p.pledgeId - 1] = getPledge(p.pledgeId).call() + updated[p.idPledge - 1] = getPledge(p.idPledge).call() }) return Promise.all(updated.map(formatPledge)) } diff --git a/config/storage.js b/config/storage.js new file mode 100644 index 0000000..18c0169 --- /dev/null +++ b/config/storage.js @@ -0,0 +1,62 @@ +module.exports = { + // default applies to all environments + default: { + enabled: true, + ipfs_bin: "ipfs", + available_providers: ["ipfs"], + upload: { + provider: "ipfs", + host: "localhost", + port: 5001 + }, + dappConnection: [ + { + provider:"ipfs", + host: "localhost", + port: 5001, + getUrl: "http://localhost:8080/ipfs/" + } + ] + // Configuration to start Swarm in the same terminal as `embark run` + /*,account: { + address: "YOUR_ACCOUNT_ADDRESS", // Address of account accessing Swarm + password: "PATH/TO/PASSWORD/FILE" // File containing the password of the account + }, + swarmPath: "PATH/TO/SWARM/EXECUTABLE" // Path to swarm executable (default: swarm)*/ + }, + + // default environment, merges with the settings in default + // assumed to be the intended environment by `embark run` + development: { + enabled: true, + upload: { + provider: "ipfs", + host: "localhost", + port: 5001, + getUrl: "http://localhost:8080/ipfs/" + } + }, + + // merges with the settings in default + // used with "embark run privatenet" + privatenet: { + }, + + rinkeby: { + }, + + // merges with the settings in default + // used with "embark run testnet" + testnet: { + }, + + // merges with the settings in default + // used with "embark run livenet" + livenet: { + }, + + // you can name an environment with specific settings and then specify with + // "embark run custom_name" + //custom_name: { + //} +};