From af55ca0311a82f0cd28ae9c94fd87fc2cbb7f239 Mon Sep 17 00:00:00 2001 From: Richard Ramos Date: Wed, 5 Jun 2019 08:59:00 -0400 Subject: [PATCH] feat: approval and allocation in separate operations (#35) --- app/js/components/Admin.js | 90 +++++++++++++++++++++++++++++++--- app/js/services/Meritocracy.js | 72 ++++++++++++++++----------- 2 files changed, 126 insertions(+), 36 deletions(-) diff --git a/app/js/components/Admin.js b/app/js/components/Admin.js index b261984..85e90e6 100644 --- a/app/js/components/Admin.js +++ b/app/js/components/Admin.js @@ -12,13 +12,19 @@ import { removeContributor, forfeitAllocation, lastForfeited, - allocate + allocate, + getAllowance, + approve, + resetAllowance, + getSNTBalance } from '../services/Meritocracy'; import { sortByAlpha } from '../utils'; import moment from 'moment'; import './admin.scss'; +const toBN = web3.utils.toBN; + class Admin extends React.Component { state = { contributorName: '', @@ -32,7 +38,9 @@ class Admin extends React.Component { sortBy: 'label', tab: 'admin', sntPerContributor: 0, - lastForfeited: null + lastForfeited: null, + allowance: '0', + balance: '0', }; async componentDidMount() { @@ -42,11 +50,18 @@ class Admin extends React.Component { this.setState({ busy: false, contributorList }); this.getLastForfeitDate(); + this.getAllowance(); } catch (error) { this.setState({ errorMsg: error.message || error }); } } + getAllowance = async () => { + const allowance = await getAllowance(); + const balance = await getSNTBalance(); + this.setState({allowance, balance}); + } + onChange = (name, e) => { this.setState({ [name]: e.target.value }); }; @@ -84,7 +99,50 @@ class Admin extends React.Component { try { await allocate(sntAmount); - this.setState({ busy: false, successMsg: 'Funds allocated!' }); + this.setState({ busy: false, successMsg: 'Funds allocated!'}); + this.getAllowance(); + } catch (error) { + this.setState({ error: error.message || error, busy: false }); + } + }; + + approve = async e => { + e.preventDefault(); + + /* eslint-disable-next-line no-alert*/ + if (!confirm('Are you sure?')) return; + + this.setState({ busy: true, successMsg: '', error: '' }); + + const { contributorList, sntPerContributor } = this.state; + const sntAmount = web3.utils.toWei((contributorList.length * parseInt(sntPerContributor, 10)).toString(), 'ether'); + + try { + await approve(sntAmount); + this.setState({ + busy: false, + successMsg: (contributorList.length * parseInt(sntPerContributor, 10)) + ' SNT approved for allocation', + allowance: sntAmount + }); + + this.getAllowance(); + } catch (error) { + this.setState({ error: error.message || error, busy: false }); + } + }; + + resetAllowance = async e => { + e.preventDefault(); + + /* eslint-disable-next-line no-alert*/ + if (!confirm('Are you sure?')) return; + + this.setState({ busy: true, successMsg: '', error: '' }); + + try { + await resetAllowance(); + this.setState({ busy: false, successMsg: 'Allowance reset to 0', allowance: '0' }); + this.getAllowance(); } catch (error) { this.setState({ error: error.message || error, busy: false }); } @@ -141,13 +199,23 @@ class Admin extends React.Component { successMsg, focusedContributorIndex, tab, - sntPerContributor + sntPerContributor, + allowance, + balance } = this.state; const currentContributor = focusedContributorIndex > -1 ? contributorList[focusedContributorIndex] : {}; const nextForfeit = (lastForfeited ? lastForfeited * 1000 : new Date().getTime()) + 86400 * 6 * 1000; const nextForfeitDate = new Date(nextForfeit).toLocaleDateString() + ' ' + new Date(nextForfeit).toLocaleTimeString(); + const totalSntForContributors = web3.utils.toWei(toBN(contributorList.length * parseInt(sntPerContributor || '0', 10)), "ether"); + + + const enoughBalance = toBN(balance).gte(toBN(totalSntForContributors)); + const shouldApprove = toBN(totalSntForContributors).gt(toBN(0)) && toBN(allowance).lt(toBN(totalSntForContributors)); + const shouldReset = toBN(allowance).gt(toBN(0)) && toBN(allowance).lt(toBN(totalSntForContributors)); + const canAllocate = toBN(totalSntForContributors).gt(toBN(0)) && toBN(allowance).gte(toBN(totalSntForContributors)); + return ( this.setState({ tab })}> @@ -214,7 +282,7 @@ class Admin extends React.Component { SNT per contributor - Total: {contributorList.length * parseInt(sntPerContributor, 10) || 0} SNT + Total: {contributorList.length * parseInt(sntPerContributor, 10) || 0} SNT, (Balance: {web3.utils.fromWei(balance, "ether")} SNT, Approved: {web3.utils.fromWei(allowance, "ether")} SNT) - + { enoughBalance && canAllocate && } + { enoughBalance && shouldApprove && !shouldReset && } + { shouldApprove && shouldReset && }
diff --git a/app/js/services/Meritocracy.js b/app/js/services/Meritocracy.js index 1f91e27..86d6842 100644 --- a/app/js/services/Meritocracy.js +++ b/app/js/services/Meritocracy.js @@ -106,45 +106,63 @@ export function forfeitAllocation() { resolve(receipt); } catch (error) { const message = 'Error forfeiting allocation'; - console.error(message); console.error(error); reject(message); } }); } +export function getSNTBalance() { + return new Promise(async (resolve) => { + const mainAccount = web3.eth.defaultAccount; + resolve(await SNT.methods.balanceOf(mainAccount).call()); + }); +} + + +export function getAllowance() { + return new Promise(async (resolve) => { + const mainAccount = web3.eth.defaultAccount; + resolve(await SNT.methods.allowance(mainAccount, Meritocracy.options.address).call()); + }); +} + +export function resetAllowance() { + return new Promise(async (resolve, reject) => { + const mainAccount = web3.eth.defaultAccount; + try { + const toSend = SNT.methods.approve(Meritocracy.options.address, '0'); + const gas = await toSend.estimateGas({ from: mainAccount }); + const receipt = await toSend.send({ from: mainAccount, gas: gas + 1000 }); + resolve(receipt); + } catch(error) { + reject(error); + } + }); +} + +export function approve(sntAmount) { + return new Promise(async (resolve, reject) => { + const mainAccount = web3.eth.defaultAccount; + try { + const toSend = SNT.methods.approve(Meritocracy.options.address, sntAmount); + const gas = await toSend.estimateGas({ from: mainAccount }); + const receipt = await toSend.send({ from: mainAccount, gas: gas + 1000 }); + resolve(receipt); + } catch(error) { + reject(error); + } + }); +} + export function allocate(sntAmount) { return new Promise(async (resolve, reject) => { const mainAccount = web3.eth.defaultAccount; try { let toSend, gas; - - const balance = web3.utils.toBN(await SNT.methods.balanceOf(mainAccount).call()); - const allowance = web3.utils.toBN(await SNT.methods.allowance(mainAccount, Meritocracy.options.address).call()); - - if (balance.lt(web3.utils.toBN(sntAmount))) { - throw new Error('Not enough SNT'); - } - - if (allowance.gt(web3.utils.toBN('0')) && allowance.lt(web3.utils.toBN(sntAmount))) { - alert('Reset allowance to 0'); - toSend = SNT.methods.approve(Meritocracy.options.address, '0'); - gas = await toSend.estimateGas({ from: mainAccount }); - await toSend.send({ from: mainAccount, gas: gas + 1000 }); - } - - if (allowance.eq(web3.utils.toBN('0'))) { - alert(`Approving ${web3.utils.fromWei(sntAmount, 'ether')} to meritocracy contract`); - toSend = SNT.methods.approve(Meritocracy.options.address, sntAmount); - gas = await toSend.estimateGas({ from: mainAccount }); - await toSend.send({ from: mainAccount, gas: gas + 1000 }); - } - - alert('Allocating SNT'); toSend = Meritocracy.methods.allocate(sntAmount); gas = await toSend.estimateGas({ from: mainAccount }); await toSend.send({ from: mainAccount, gas: gas + 1000 }); - resolve(true); } catch (error) { let message; @@ -152,10 +170,8 @@ export function allocate(sntAmount) { if (error.message === 'Not enough SNT') { message = 'Not enough SNT'; } else { - message = 'Error forfeiting allocation'; + message = 'Error doing allocation'; } - - console.error(message); console.error(error); reject(message); }